mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 08:29:42 +02:00
feat(agent-cases): tag agent-spawned case dirs and sweep their leftovers
A long orchestration creates one case directory per worker and deleting the sessions never removed them, so ~/codeman-cases accumulated scratch folders that were indistinguishable from real projects. They are now labelled and have a cleanup path. - src/agent-case-marker.ts: a case dir quick-start CREATES for an agent-driven spawn gets a .codeman-agent-case.json marker (when, by whom, parent session, mode). Only the create branch writes it, so a linked case, a cloned repo or any pre-existing path is never labelled; reading is total, so a malformed marker means "not agent-created" rather than a half-trusted entry. - The signal is the new X-Codeman-Agent-Origin header the skill preamble sets on its shared curl (preamble bumped to 1.22.0), or an agentOrigin body field, falling back to a resolved parentSessionId so a worker spawned by a stale skill copy is still labelled. - GET /api/cases publishes it as agentCreated; GET /api/cases/agent-created is a read-only cleanup listing adding inUse and modifiedAt; Add Case -> Manage badges each case and offers a review-then-delete sweep that names every directory in its confirm and skips any case a live session is working in. Removal stays on the existing DELETE /api/cases/:name. - Agent preamble caches are collected too: ~/.cache/codeman-agent-<id>.sh was written per claude session and never removed (236 leftovers measured on a working machine). Now deleted with the session and swept at boot, guarded by a live-session keep set plus a 7-day age floor. Verified end to end on an isolated instance: marker written for header, body and lineage-only spawns, absent with no agent signal and for a pre-existing directory; inUse flipping on session end; badge, sticky bar, confirm and sweep driven in a browser; preamble seeded on create, removed on delete, boot sweep taking only the aged orphans. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ import { dataPath } from '../config/instance.js';
|
||||
import { getCasesDir } from '../config/cases-dir.js';
|
||||
import { isMultiUserMode, maxSessionsPerUser, userCasesDir } from '../config/multiuser.js';
|
||||
import { SYNTHETIC_ADMIN, findUser } from '../user-store.js';
|
||||
import { AGENT_ORIGIN_SPAWNED_BY_SESSION, normalizeAgentOrigin } from '../agent-case-marker.js';
|
||||
|
||||
// Shared path constants used across route modules. CASES_DIR (project folders)
|
||||
// stays shared across instances; SETTINGS_PATH is per-instance runtime state.
|
||||
@@ -361,6 +362,33 @@ export function resolveParentSessionId(
|
||||
return parent.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve "an agent asked for this", the signal that labels a case directory
|
||||
* Codeman is about to CREATE as an agent scratch workspace (see agent-case-marker.ts).
|
||||
*
|
||||
* Two signals, in order:
|
||||
* 1. an explicit `agentOrigin` body field, or the `X-Codeman-Agent-Origin` header the
|
||||
* packaged skill sets once on its shared curl invocation, so every spawn recipe
|
||||
* carries it without a per-recipe edit. The body wins, mirroring parentSessionId;
|
||||
* 2. failing that, an already-RESOLVED parent session id. A create request that names
|
||||
* the session that spawned it came from an agent by construction: nothing in the
|
||||
* browser UI sets lineage. This is what still labels workers spawned by a stale
|
||||
* skill copy or by hand-rolled curl that only carries the lineage header.
|
||||
*
|
||||
* ⚠️ Decoration, like parentSessionId: never an ownership or permission signal, and
|
||||
* never a reason to fail a spawn. An unrecognised origin token is dropped by
|
||||
* `normalizeAgentOrigin` rather than rejected.
|
||||
*/
|
||||
export function resolveAgentCaseOrigin(
|
||||
req: FastifyRequest,
|
||||
bodyValue: string | undefined,
|
||||
resolvedParentSessionId: string | undefined
|
||||
): string | undefined {
|
||||
const header = req.headers['x-codeman-agent-origin'];
|
||||
const raw = bodyValue ?? (Array.isArray(header) ? header[0] : header);
|
||||
return normalizeAgentOrigin(raw) ?? (resolvedParentSessionId ? AGENT_ORIGIN_SPAWNED_BY_SESSION : undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and validate a request body against a Zod schema, or throw a structured 400 error.
|
||||
* Replaces the repeated pattern: `const r = Schema.safeParse(body); if (!r.success) return createErrorResponse(...)`.
|
||||
|
||||
Reference in New Issue
Block a user