@dsh-cc/subagent-task 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +201 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +214 -0
  4. package/README.zh.md +100 -0
  5. package/lib/background-start.d.ts +207 -0
  6. package/lib/background-start.d.ts.map +1 -0
  7. package/lib/background-start.js +354 -0
  8. package/lib/background-start.js.map +1 -0
  9. package/lib/catalog.d.ts +99 -0
  10. package/lib/catalog.d.ts.map +1 -0
  11. package/lib/catalog.js +197 -0
  12. package/lib/catalog.js.map +1 -0
  13. package/lib/epoch-collector.d.ts +126 -0
  14. package/lib/epoch-collector.d.ts.map +1 -0
  15. package/lib/epoch-collector.js +243 -0
  16. package/lib/epoch-collector.js.map +1 -0
  17. package/lib/index.d.ts +62 -0
  18. package/lib/index.d.ts.map +1 -0
  19. package/lib/index.js +143 -0
  20. package/lib/index.js.map +1 -0
  21. package/lib/preload-tools.d.ts +66 -0
  22. package/lib/preload-tools.d.ts.map +1 -0
  23. package/lib/preload-tools.js +101 -0
  24. package/lib/preload-tools.js.map +1 -0
  25. package/lib/registry.d.ts +49 -0
  26. package/lib/registry.d.ts.map +1 -0
  27. package/lib/registry.js +66 -0
  28. package/lib/registry.js.map +1 -0
  29. package/lib/resume-capture.d.ts +107 -0
  30. package/lib/resume-capture.d.ts.map +1 -0
  31. package/lib/resume-capture.js +232 -0
  32. package/lib/resume-capture.js.map +1 -0
  33. package/lib/sanitize-filter.d.ts +27 -0
  34. package/lib/sanitize-filter.d.ts.map +1 -0
  35. package/lib/sanitize-filter.js +95 -0
  36. package/lib/sanitize-filter.js.map +1 -0
  37. package/lib/strip-instructions.d.ts +47 -0
  38. package/lib/strip-instructions.d.ts.map +1 -0
  39. package/lib/strip-instructions.js +77 -0
  40. package/lib/strip-instructions.js.map +1 -0
  41. package/lib/suppress-settled.d.ts +45 -0
  42. package/lib/suppress-settled.d.ts.map +1 -0
  43. package/lib/suppress-settled.js +80 -0
  44. package/lib/suppress-settled.js.map +1 -0
  45. package/lib/tool.d.ts +49 -0
  46. package/lib/tool.d.ts.map +1 -0
  47. package/lib/tool.js +247 -0
  48. package/lib/tool.js.map +1 -0
  49. package/package.json +78 -0
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Per-workspace discovery of Claude Code `.claude/agents` definitions.
3
+ *
4
+ * The host process serves many workspaces at once, so discovery is keyed by
5
+ * the session's cwd (`cwdOf(agent)`) rather than the process cwd — a web host
6
+ * started from `~/.dsh` must still see `my-repo/.claude/agents`. Results are
7
+ * cached per root for the process lifetime: the registry does not watch the
8
+ * filesystem (v1), so editing an agent file takes effect on the next session
9
+ * for a workspace whose cache entry has not yet been created, and on process
10
+ * restart otherwise.
11
+ *
12
+ * @module @dsh-cc/subagent-task/registry
13
+ */
14
+ import type { AgentDefinition } from '@dsh-cc/claude-code-agents';
15
+ /** Options for the registry, mostly injectable seams for tests. */
16
+ export interface AgentRegistryOptions {
17
+ /** Override the user `.claude/agents` layer (hermetic tests). */
18
+ userDir?: string;
19
+ }
20
+ /**
21
+ * A process-level cache of discovered agent definitions keyed by workspace
22
+ * root. Discovery runs lazily on first use of a root; the cached promise is
23
+ * shared so concurrent pre-steps for the same workspace scan once.
24
+ */
25
+ export declare class AgentRegistry {
26
+ private readonly options;
27
+ private readonly cache;
28
+ constructor(options?: AgentRegistryOptions);
29
+ /**
30
+ * Ensure the definitions for one workspace root are loaded (once).
31
+ * @param root - the session workspace (the project layer root).
32
+ * @returns the merged definition map (project shadows user), or an empty map.
33
+ */
34
+ ensure(root: string): Promise<ReadonlyMap<string, AgentDefinition>>;
35
+ /**
36
+ * List every definition visible from one workspace root.
37
+ * @param root - the session workspace.
38
+ * @returns definitions sorted by name (project entries shadow user ones).
39
+ */
40
+ list(root: string): Promise<readonly AgentDefinition[]>;
41
+ /**
42
+ * Resolve one definition by its frontmatter `name`.
43
+ * @param root - the session workspace.
44
+ * @param type - the requested `subagent_type`.
45
+ * @returns the definition, or undefined when the type is unknown here.
46
+ */
47
+ resolve(root: string, type: string): Promise<AgentDefinition | undefined>;
48
+ }
49
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAEjE,mEAAmE;AACnE,MAAM,WAAW,oBAAoB;IACnC,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;GAIG;AACH,qBAAa,aAAa;IAGZ,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmE;gBAE5D,OAAO,GAAE,oBAAyB;IAE/D;;;;OAIG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAenE;;;;OAIG;IACG,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC;IAK7D;;;;;OAKG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;CAIhF"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Per-workspace discovery of Claude Code `.claude/agents` definitions.
3
+ *
4
+ * The host process serves many workspaces at once, so discovery is keyed by
5
+ * the session's cwd (`cwdOf(agent)`) rather than the process cwd — a web host
6
+ * started from `~/.dsh` must still see `my-repo/.claude/agents`. Results are
7
+ * cached per root for the process lifetime: the registry does not watch the
8
+ * filesystem (v1), so editing an agent file takes effect on the next session
9
+ * for a workspace whose cache entry has not yet been created, and on process
10
+ * restart otherwise.
11
+ *
12
+ * @module @dsh-cc/subagent-task/registry
13
+ */
14
+ import { loadClaudeCodeAgents } from '@dsh-cc/claude-code-agents';
15
+ /**
16
+ * A process-level cache of discovered agent definitions keyed by workspace
17
+ * root. Discovery runs lazily on first use of a root; the cached promise is
18
+ * shared so concurrent pre-steps for the same workspace scan once.
19
+ */
20
+ export class AgentRegistry {
21
+ options;
22
+ cache = new Map();
23
+ constructor(options = {}) {
24
+ this.options = options;
25
+ }
26
+ /**
27
+ * Ensure the definitions for one workspace root are loaded (once).
28
+ * @param root - the session workspace (the project layer root).
29
+ * @returns the merged definition map (project shadows user), or an empty map.
30
+ */
31
+ ensure(root) {
32
+ let pending = this.cache.get(root);
33
+ if (pending === undefined) {
34
+ pending = loadClaudeCodeAgents(root, {
35
+ ...this.options.userDir !== undefined ? { userDir: this.options.userDir } : {},
36
+ }).then(defs => {
37
+ const map = new Map();
38
+ for (const def of defs)
39
+ map.set(def.agentType, def);
40
+ return map;
41
+ });
42
+ this.cache.set(root, pending);
43
+ }
44
+ return pending;
45
+ }
46
+ /**
47
+ * List every definition visible from one workspace root.
48
+ * @param root - the session workspace.
49
+ * @returns definitions sorted by name (project entries shadow user ones).
50
+ */
51
+ async list(root) {
52
+ const map = await this.ensure(root);
53
+ return [...map.values()].sort((a, b) => a.agentType.localeCompare(b.agentType));
54
+ }
55
+ /**
56
+ * Resolve one definition by its frontmatter `name`.
57
+ * @param root - the session workspace.
58
+ * @param type - the requested `subagent_type`.
59
+ * @returns the definition, or undefined when the type is unknown here.
60
+ */
61
+ async resolve(root, type) {
62
+ const map = await this.ensure(root);
63
+ return map.get(type);
64
+ }
65
+ }
66
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AASjE;;;;GAIG;AACH,MAAM,OAAO,aAAa;IAGK;IAFZ,KAAK,GAAG,IAAI,GAAG,EAAyD,CAAA;IAEzF,YAA6B,UAAgC,EAAE;QAAlC,YAAO,GAAP,OAAO,CAA2B;IAAG,CAAC;IAEnE;;;;OAIG;IACH,MAAM,CAAC,IAAY;QACjB,IAAI,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAClC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO,GAAG,oBAAoB,CAAC,IAAI,EAAE;gBACnC,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE;aAC/E,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBACb,MAAM,GAAG,GAAG,IAAI,GAAG,EAA2B,CAAA;gBAC9C,KAAK,MAAM,GAAG,IAAI,IAAI;oBAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;gBACnD,OAAO,GAAG,CAAA;YACZ,CAAC,CAAC,CAAA;YACF,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAC/B,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,IAAI,CAAC,IAAY;QACrB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACnC,OAAO,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAA;IACjF,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,IAAY;QACtC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACnC,OAAO,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACtB,CAAC;CACF"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Spawn-time resume-pin capture (plan §4.3 + §4.5): build and write the pin
3
+ * for a continuable background child BEFORE `startContinuable`, and tombstone
4
+ * it when the creation throws.
5
+ *
6
+ * The capture flow lives in the task package (not resume-pins) because it is
7
+ * cordis-facing: it reads `ctx.llm.resolveCallConfig` for the §4.3 preflight
8
+ * and `ctx.logger` for the never-fail-the-spawn degradation warnings. Only
9
+ * pure helpers (`overlayRoute`, `probeWorkspace`) are exported for direct
10
+ * reuse and testing.
11
+ *
12
+ * Failure policy: a capture problem never fails the spawn. An unresolvable
13
+ * route degrades the pin to explicit-fields-only (`effective.complete:false` +
14
+ * a warning); an unwriteable pin skips capture entirely (a missing pin reads
15
+ * as a legacy/foreign child). Only the tombstone path rethrows nothing — the
16
+ * original `startContinuable` error is rethrown unchanged by the caller.
17
+ *
18
+ * @module @dsh-cc/subagent-task/resume-capture
19
+ */
20
+ import type { Context } from '@deepseek-ai/cordis';
21
+ import type { AgentDefinition, ToolRestriction } from '@dsh-cc/claude-code-agents';
22
+ import type { DetailedRoute } from '@dsh-cc/model-aliases';
23
+ import { PinStore, type PinWorkspace } from '@dsh-cc/subagent-resume-pins';
24
+ /** The additive `apply(ctx, config)` option that arms spawn-time capture. */
25
+ export interface ResumePinsConfig {
26
+ /** Directory holding one `<childId>.json` pin file per child. */
27
+ readonly pinsRoot: string;
28
+ /** Test seam: inject a store instead of constructing one over `pinsRoot`. */
29
+ readonly store?: PinStore;
30
+ }
31
+ /** The parent's route fields the harness child-agent spread inherits from. */
32
+ export interface ParentRouteLike {
33
+ readonly provider?: string;
34
+ readonly model?: string;
35
+ readonly maxTokens?: number;
36
+ }
37
+ /**
38
+ * The pre-preflight effective tuple: parent options overlaid with the alias
39
+ * route (§4.3). Mirrors the harness child-agent inheritance semantics
40
+ * (`child-agent.ts:73-79`): the parent's provider/model/maxTokens are spread
41
+ * conditionally, then the requested route's present fields win — `undefined`
42
+ * never shadows an inherited value.
43
+ */
44
+ export declare function overlayRoute(parent: ParentRouteLike, route: DetailedRoute['route']): ParentRouteLike & {
45
+ readonly reasoningEffort?: string;
46
+ };
47
+ /**
48
+ * Workspace/worktree identity via ONE best-effort git probe. `cwd` is always
49
+ * present; on any probe failure (non-repo, git missing, timeout) the git
50
+ * fields fall back to the `'unknown'` sentinel — the pin schema has no
51
+ * optional fields, and a sentinel is parseable and visibly not an identity.
52
+ * gitDir/gitCommonDir are cwd-anchored ABSOLUTE paths so pins pin an
53
+ * identity, not a cwd-relative spelling.
54
+ */
55
+ export declare function probeWorkspace(cwd: string): PinWorkspace;
56
+ /** Everything one pinned spawn contributes to the pin (§4.5). */
57
+ export interface CaptureInput {
58
+ readonly parentSessionId: string;
59
+ readonly label: string;
60
+ readonly childId: string;
61
+ /** A named definition (with discovery metadata); undefined for plain spawns. */
62
+ readonly definition?: AgentDefinition | undefined;
63
+ /** Atomic provenance resolution of the definition's `model` selector. */
64
+ readonly selector: DetailedRoute;
65
+ readonly parentRoute: ParentRouteLike;
66
+ /** The sanitized tool filter exactly as forwarded to the seam. */
67
+ readonly toolFilter?: ToolRestriction | undefined;
68
+ /** The session workspace cwd (workspace-identity probe root). */
69
+ readonly cwd: string;
70
+ }
71
+ /**
72
+ * Owns the write-before-spawn and tombstone-on-throw lifecycle for one
73
+ * mounted `resumePins` config. Never throws on capture trouble — a degraded
74
+ * or skipped pin is reported through `ctx.logger.warn` and the spawn proceeds.
75
+ */
76
+ export declare class SpawnPinCapture {
77
+ private readonly ctx;
78
+ readonly store: PinStore;
79
+ constructor(ctx: Context, store: PinStore);
80
+ /** Preallocate the durable child id (it becomes the child's session id). */
81
+ preallocateChildId(): string;
82
+ /** The selector recorded when no route service is mounted: plain inherit. */
83
+ static inheritSelector(model: string | undefined): DetailedRoute;
84
+ /**
85
+ * Build the pin and write it before the spawn. The spawn is NEVER failed by
86
+ * capture trouble and capture is never silently skipped: resolves
87
+ * `undefined` on success, or the failure reason on a skipped capture — the
88
+ * caller MUST surface it as an explicit `captureWarning` line in the
89
+ * subagent_fork tool result ("this child will resume with legacy
90
+ * semantics"), so an unpinned launch is never silent.
91
+ */
92
+ write(input: CaptureInput): Promise<string | undefined>;
93
+ /**
94
+ * Tombstone the pin after a failed `startContinuable`: delete the file; if
95
+ * the delete fails, rewrite it as blocked so it can never be mistaken for
96
+ * a live agent. Idempotent; never throws.
97
+ */
98
+ tombstone(childId: string): Promise<void>;
99
+ private build;
100
+ /**
101
+ * §4.3 preflight: materialize the complete tuple with explicit nulls for
102
+ * absent effort/maxTokens. Failure degrades to the explicit overlay fields
103
+ * with `complete:false` plus a warning — never a spawn failure.
104
+ */
105
+ private preflight;
106
+ }
107
+ //# sourceMappingURL=resume-capture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume-capture.d.ts","sourceRoot":"","sources":["../src/resume-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAC1D,OAAO,EACL,QAAQ,EAMR,KAAK,YAAY,EAGlB,MAAM,8BAA8B,CAAA;AAErC,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAA;CAC1B;AAED,8EAA8E;AAC9E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,GAC5B,eAAe,GAAG;IAAE,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,CASzD;AAuBD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAuBxD;AAYD,iEAAiE;AACjE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,gFAAgF;IAChF,QAAQ,CAAC,UAAU,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;IACjD,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;IAChC,QAAQ,CAAC,WAAW,EAAE,eAAe,CAAA;IACrC,kEAAkE;IAClE,QAAQ,CAAC,UAAU,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;IACjD,iEAAiE;IACjE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB;AAED;;;;GAIG;AACH,qBAAa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,QAAQ,CAAC,KAAK,EAAE,QAAQ;gBADP,GAAG,EAAE,OAAO,EACpB,KAAK,EAAE,QAAQ;IAG1B,4EAA4E;IAC5E,kBAAkB,IAAI,MAAM;IAI5B,6EAA6E;IAC7E,MAAM,CAAC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa;IAIhE;;;;;;;OAOG;IACG,KAAK,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAW7D;;;;OAIG;IACG,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAkBjC,KAAK;IA2CnB;;;;OAIG;YACW,SAAS;CAgCxB"}
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Spawn-time resume-pin capture (plan §4.3 + §4.5): build and write the pin
3
+ * for a continuable background child BEFORE `startContinuable`, and tombstone
4
+ * it when the creation throws.
5
+ *
6
+ * The capture flow lives in the task package (not resume-pins) because it is
7
+ * cordis-facing: it reads `ctx.llm.resolveCallConfig` for the §4.3 preflight
8
+ * and `ctx.logger` for the never-fail-the-spawn degradation warnings. Only
9
+ * pure helpers (`overlayRoute`, `probeWorkspace`) are exported for direct
10
+ * reuse and testing.
11
+ *
12
+ * Failure policy: a capture problem never fails the spawn. An unresolvable
13
+ * route degrades the pin to explicit-fields-only (`effective.complete:false` +
14
+ * a warning); an unwriteable pin skips capture entirely (a missing pin reads
15
+ * as a legacy/foreign child). Only the tombstone path rethrows nothing — the
16
+ * original `startContinuable` error is rethrown unchanged by the caller.
17
+ *
18
+ * @module @dsh-cc/subagent-task/resume-capture
19
+ */
20
+ import { spawnSync } from 'node:child_process';
21
+ import { randomUUID } from 'node:crypto';
22
+ import { isAbsolute, join } from 'node:path';
23
+ import { realpathSync } from 'node:fs';
24
+ import { definitionFingerprint, personaHash, } from '@dsh-cc/subagent-resume-pins';
25
+ /**
26
+ * The pre-preflight effective tuple: parent options overlaid with the alias
27
+ * route (§4.3). Mirrors the harness child-agent inheritance semantics
28
+ * (`child-agent.ts:73-79`): the parent's provider/model/maxTokens are spread
29
+ * conditionally, then the requested route's present fields win — `undefined`
30
+ * never shadows an inherited value.
31
+ */
32
+ export function overlayRoute(parent, route) {
33
+ return {
34
+ ...parent.provider !== undefined ? { provider: parent.provider } : {},
35
+ ...parent.model !== undefined ? { model: parent.model } : {},
36
+ ...parent.maxTokens !== undefined ? { maxTokens: parent.maxTokens } : {},
37
+ ...route?.provider !== undefined ? { provider: route.provider } : {},
38
+ ...route?.model !== undefined ? { model: route.model } : {},
39
+ ...route?.reasoningEffort !== undefined ? { reasoningEffort: route.reasoningEffort } : {},
40
+ };
41
+ }
42
+ /** The git probe timeout; a hung git must never delay a spawn. */
43
+ const GIT_PROBE_TIMEOUT_MS = 2_000;
44
+ /**
45
+ * Normalize one `git rev-parse` output to a cwd-anchored absolute path (git
46
+ * prints repository-relative values like `.git`), realpath'ed when cheap.
47
+ * Residual limit (documented): two DIFFERENT standalone repos initialized
48
+ * sequentially at the same path normalize to the same path strings, so that
49
+ * replacement is not detectable — the worktree↔standalone flip and cwd moves
50
+ * ARE (the comparison is the --git-dir path pair, per the implemented rule).
51
+ */
52
+ function normalizeGitPath(cwd, value) {
53
+ if (value.length === 0)
54
+ return 'unknown';
55
+ const absolute = isAbsolute(value) ? value : join(cwd, value);
56
+ try {
57
+ return realpathSync(absolute);
58
+ }
59
+ catch {
60
+ return absolute;
61
+ }
62
+ }
63
+ /**
64
+ * Workspace/worktree identity via ONE best-effort git probe. `cwd` is always
65
+ * present; on any probe failure (non-repo, git missing, timeout) the git
66
+ * fields fall back to the `'unknown'` sentinel — the pin schema has no
67
+ * optional fields, and a sentinel is parseable and visibly not an identity.
68
+ * gitDir/gitCommonDir are cwd-anchored ABSOLUTE paths so pins pin an
69
+ * identity, not a cwd-relative spelling.
70
+ */
71
+ export function probeWorkspace(cwd) {
72
+ const fallback = { cwd, gitDir: 'unknown', gitCommonDir: 'unknown', branch: 'unknown' };
73
+ let result;
74
+ try {
75
+ result = spawnSync('git', ['-C', cwd, 'rev-parse', '--git-dir', '--git-common-dir', '--abbrev-ref', 'HEAD'], { timeout: GIT_PROBE_TIMEOUT_MS, encoding: 'utf8' });
76
+ }
77
+ catch {
78
+ return fallback;
79
+ }
80
+ if (result.status !== 0 || typeof result.stdout !== 'string')
81
+ return fallback;
82
+ const [gitDir, gitCommonDir, branch] = result.stdout.trim().split('\n');
83
+ if (gitDir === undefined || gitCommonDir === undefined || branch === undefined || branch.length === 0) {
84
+ return fallback;
85
+ }
86
+ return {
87
+ cwd,
88
+ gitDir: normalizeGitPath(cwd, gitDir),
89
+ gitCommonDir: normalizeGitPath(cwd, gitCommonDir),
90
+ branch,
91
+ };
92
+ }
93
+ /**
94
+ * Owns the write-before-spawn and tombstone-on-throw lifecycle for one
95
+ * mounted `resumePins` config. Never throws on capture trouble — a degraded
96
+ * or skipped pin is reported through `ctx.logger.warn` and the spawn proceeds.
97
+ */
98
+ export class SpawnPinCapture {
99
+ ctx;
100
+ store;
101
+ constructor(ctx, store) {
102
+ this.ctx = ctx;
103
+ this.store = store;
104
+ }
105
+ /** Preallocate the durable child id (it becomes the child's session id). */
106
+ preallocateChildId() {
107
+ return randomUUID();
108
+ }
109
+ /** The selector recorded when no route service is mounted: plain inherit. */
110
+ static inheritSelector(model) {
111
+ return { selector: model, via: 'inherit', route: undefined };
112
+ }
113
+ /**
114
+ * Build the pin and write it before the spawn. The spawn is NEVER failed by
115
+ * capture trouble and capture is never silently skipped: resolves
116
+ * `undefined` on success, or the failure reason on a skipped capture — the
117
+ * caller MUST surface it as an explicit `captureWarning` line in the
118
+ * subagent_fork tool result ("this child will resume with legacy
119
+ * semantics"), so an unpinned launch is never silent.
120
+ */
121
+ async write(input) {
122
+ try {
123
+ this.store.write(await this.build(input));
124
+ return undefined;
125
+ }
126
+ catch (error) {
127
+ const reason = error.message;
128
+ this.ctx.logger.warn(`resume pin capture failed (spawn proceeds unpinned): ${reason}`);
129
+ return reason;
130
+ }
131
+ }
132
+ /**
133
+ * Tombstone the pin after a failed `startContinuable`: delete the file; if
134
+ * the delete fails, rewrite it as blocked so it can never be mistaken for
135
+ * a live agent. Idempotent; never throws.
136
+ */
137
+ async tombstone(childId) {
138
+ try {
139
+ this.store.remove(childId);
140
+ return;
141
+ }
142
+ catch {
143
+ // Fall through to the blocked rewrite.
144
+ }
145
+ try {
146
+ this.store.update(childId, (draft) => {
147
+ draft.resume = { state: 'blocked', reason: 'spawn-aborted' };
148
+ });
149
+ }
150
+ catch (error) {
151
+ this.ctx.logger.warn(`resume pin tombstone failed for child ${childId}: ${error.message}`);
152
+ }
153
+ }
154
+ async build(input) {
155
+ const route = input.selector.route;
156
+ const overlay = overlayRoute(input.parentRoute, route);
157
+ if (overlay.provider === undefined || overlay.model === undefined) {
158
+ throw new Error('no explicit provider/model route to pin');
159
+ }
160
+ const effective = await this.preflight(overlay);
161
+ const definition = input.definition === undefined
162
+ ? { kind: 'plain' }
163
+ : {
164
+ kind: 'named',
165
+ agentType: input.definition.agentType,
166
+ source: input.definition.source,
167
+ fingerprint: definitionFingerprint(input.definition),
168
+ personaHash: personaHash(input.definition.systemPrompt),
169
+ // Gate-time re-fingerprinting needs the discovery location (§4.4).
170
+ baseDir: input.definition.baseDir,
171
+ filename: input.definition.filename,
172
+ };
173
+ const modelSelector = {
174
+ raw: input.selector.selector ?? 'inherit',
175
+ via: input.selector.via,
176
+ };
177
+ return {
178
+ version: 1,
179
+ childId: input.childId,
180
+ parentSessionId: input.parentSessionId,
181
+ label: input.label,
182
+ mode: 'continuable-background',
183
+ createdAt: new Date().toISOString(),
184
+ definition,
185
+ modelSelector,
186
+ effective,
187
+ toolFilter: {
188
+ allow: [...(input.toolFilter?.allow ?? [])],
189
+ deny: [...(input.toolFilter?.deny ?? [])],
190
+ },
191
+ ...input.definition?.maxTurns !== undefined ? { maxTurns: input.definition.maxTurns } : {},
192
+ workspace: probeWorkspace(input.cwd),
193
+ resume: { state: 'ok' },
194
+ };
195
+ }
196
+ /**
197
+ * §4.3 preflight: materialize the complete tuple with explicit nulls for
198
+ * absent effort/maxTokens. Failure degrades to the explicit overlay fields
199
+ * with `complete:false` plus a warning — never a spawn failure.
200
+ */
201
+ async preflight(overlay) {
202
+ const llm = this.ctx.get('llm');
203
+ try {
204
+ if (llm === undefined)
205
+ throw new Error('no llm service for the preflight');
206
+ const config = await llm.resolveCallConfig({
207
+ provider: overlay.provider,
208
+ model: overlay.model,
209
+ ...(overlay.maxTokens !== undefined ? { maxTokens: overlay.maxTokens } : {}),
210
+ ...(overlay.reasoningEffort !== undefined ? { reasoningEffort: overlay.reasoningEffort } : {}),
211
+ });
212
+ return {
213
+ provider: config.provider,
214
+ model: config.model,
215
+ reasoningEffort: config.reasoningEffort ?? null,
216
+ maxTokens: config.maxTokens ?? null,
217
+ complete: true,
218
+ };
219
+ }
220
+ catch {
221
+ this.ctx.logger.warn('resume pin degraded: route not preflightable; only explicit options pinned');
222
+ return {
223
+ provider: overlay.provider,
224
+ model: overlay.model,
225
+ reasoningEffort: overlay.reasoningEffort ?? null,
226
+ maxTokens: overlay.maxTokens ?? null,
227
+ complete: false,
228
+ };
229
+ }
230
+ }
231
+ }
232
+ //# sourceMappingURL=resume-capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume-capture.js","sourceRoot":"","sources":["../src/resume-capture.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Sanitize a Task child's tool restriction against the live restrictable-name
3
+ * set: MCP wildcards expand, unknown names drop, an emptied allow-list is
4
+ * deny-all, and ToolSearch is auto-injected when the child holds MCP names.
5
+ *
6
+ * @module @dsh-cc/subagent-task/sanitize-filter
7
+ */
8
+ import type { ToolRestriction } from '@dsh-cc/claude-code-agents';
9
+ /**
10
+ * Sanitize a definition's tool restriction against the LIVE set of names the
11
+ * tools registry knows (registered or reserved — `ctx.tools.view(callingAgent)
12
+ * .restrictableNames`, read at execute time so deferred MCP reservations on
13
+ * the standing-scope layer are included).
14
+ *
15
+ * Rules:
16
+ * - A name survives only when the registry knows it. Everything else is
17
+ * dropped with a warning; there is no static legal-names set, so mounted
18
+ * MCP tools and any future registered row are accepted without code churn.
19
+ * - If the filter carried an `allow` list and any kept allow name is an MCP
20
+ * tool while `ToolSearch` is itself restrictable, `ToolSearch` is appended
21
+ * (deduped): the child otherwise holds MCP names with no load path.
22
+ * - If the filter carried an `allow` list and sanitization left nothing, the
23
+ * result is `{ allow: [] }` — omitting `allow` would WIDEN the child to
24
+ * every tool, so an emptied allow-list is pinned as deny-all, loudly.
25
+ */
26
+ export declare function sanitizeToolFilter(filter: ToolRestriction, warn: (m: string) => void, knownNames: ReadonlySet<string>): ToolRestriction;
27
+ //# sourceMappingURL=sanitize-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize-filter.d.ts","sourceRoot":"","sources":["../src/sanitize-filter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AA4CjE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EACzB,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,GAC9B,eAAe,CA+BjB"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Sanitize a Task child's tool restriction against the live restrictable-name
3
+ * set: MCP wildcards expand, unknown names drop, an emptied allow-list is
4
+ * deny-all, and ToolSearch is auto-injected when the child holds MCP names.
5
+ *
6
+ * @module @dsh-cc/subagent-task/sanitize-filter
7
+ */
8
+ /**
9
+ * The MCP public-name prefix every bridged MCP tool carries on `ctx.tools`.
10
+ */
11
+ const MCP_PUBLIC_PREFIX = 'mcp__';
12
+ /**
13
+ * Expand one raw filter entry into the concrete names it asks for.
14
+ *
15
+ * - Anything not MCP-qualified passes through untouched (it is then gated by
16
+ * the `knownNames` check).
17
+ * - A bare `mcp__` (no server segment) is dropped with a loud warning — it
18
+ * can never name a mounted tool.
19
+ * - `mcp__<server>` (no third segment) and `mcp__<server>__*` expand to every
20
+ * known MCP tool of that server (`mcp__<server>__` prefix), so frontmatter
21
+ * survives servers publishing new tools without a hash-suffix dance.
22
+ * - An exact `mcp__<server>__<tool>` passes through as written (the caller
23
+ * must use the public name, including any identity-hash suffix).
24
+ */
25
+ function expandFilterName(rawName, knownNames, warn) {
26
+ if (!rawName.startsWith(MCP_PUBLIC_PREFIX))
27
+ return [rawName];
28
+ const rest = rawName.slice(MCP_PUBLIC_PREFIX.length);
29
+ if (rest.length === 0) {
30
+ warn('cc-task: invalid MCP wildcard "mcp__" in a subagent toolFilter — expected mcp__<server> or mcp__<server>__<tool>');
31
+ return [];
32
+ }
33
+ const server = rest.endsWith('__*')
34
+ ? rest.slice(0, -'__*'.length)
35
+ : rest.includes('__')
36
+ ? undefined
37
+ : rest;
38
+ if (server === undefined)
39
+ return [rawName];
40
+ if (server.length === 0) {
41
+ warn(`cc-task: invalid MCP wildcard "${rawName}" in a subagent toolFilter — expected mcp__<server> or mcp__<server>__<tool>`);
42
+ return [];
43
+ }
44
+ return [...knownNames].filter(name => name.startsWith(`${MCP_PUBLIC_PREFIX}${server}__`));
45
+ }
46
+ /**
47
+ * Sanitize a definition's tool restriction against the LIVE set of names the
48
+ * tools registry knows (registered or reserved — `ctx.tools.view(callingAgent)
49
+ * .restrictableNames`, read at execute time so deferred MCP reservations on
50
+ * the standing-scope layer are included).
51
+ *
52
+ * Rules:
53
+ * - A name survives only when the registry knows it. Everything else is
54
+ * dropped with a warning; there is no static legal-names set, so mounted
55
+ * MCP tools and any future registered row are accepted without code churn.
56
+ * - If the filter carried an `allow` list and any kept allow name is an MCP
57
+ * tool while `ToolSearch` is itself restrictable, `ToolSearch` is appended
58
+ * (deduped): the child otherwise holds MCP names with no load path.
59
+ * - If the filter carried an `allow` list and sanitization left nothing, the
60
+ * result is `{ allow: [] }` — omitting `allow` would WIDEN the child to
61
+ * every tool, so an emptied allow-list is pinned as deny-all, loudly.
62
+ */
63
+ export function sanitizeToolFilter(filter, warn, knownNames) {
64
+ const clean = (names) => {
65
+ const out = [];
66
+ for (const rawName of names) {
67
+ for (const expanded of expandFilterName(rawName, knownNames, warn)) {
68
+ if (knownNames.has(expanded)) {
69
+ if (!out.includes(expanded))
70
+ out.push(expanded);
71
+ }
72
+ else {
73
+ warn(`cc-task: dropping unknown tool name "${expanded}" from a subagent toolFilter`);
74
+ }
75
+ }
76
+ }
77
+ return out;
78
+ };
79
+ const hadAllow = filter.allow !== undefined;
80
+ const allow = filter.allow !== undefined ? clean(filter.allow) : undefined;
81
+ const deny = filter.deny !== undefined ? clean(filter.deny) : undefined;
82
+ if (hadAllow && allow !== undefined && allow.length > 0 && allow.some(name => name.startsWith(MCP_PUBLIC_PREFIX))
83
+ && knownNames.has('ToolSearch') && !allow.includes('ToolSearch')) {
84
+ allow.push('ToolSearch');
85
+ }
86
+ if (hadAllow && (allow === undefined || allow.length === 0)) {
87
+ warn('cc-task: a subagent toolFilter allow-list matched no mounted tools '
88
+ + `(originals: ${(filter.allow ?? []).join(', ')}); the child will run with zero tools`);
89
+ }
90
+ return {
91
+ ...(hadAllow ? { allow: allow ?? [] } : {}),
92
+ ...(deny !== undefined && deny.length > 0 ? { deny } : {}),
93
+ };
94
+ }
95
+ //# sourceMappingURL=sanitize-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize-filter.js","sourceRoot":"","sources":["../src/sanitize-filter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;GAEG;AACH,MAAM,iBAAiB,GAAG,OAAO,CAAA;AAEjC;;;;;;;;;;;;GAYG;AACH,SAAS,gBAAgB,CACvB,OAAe,EACf,UAA+B,EAC/B,IAAyB;IAEzB,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,iBAAiB,CAAC;QAAE,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5D,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAA;IACpD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,IAAI,CAAC,kHAAkH,CAAC,CAAA;QACxH,OAAO,EAAE,CAAA;IACX,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QACjC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC;QAC9B,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YACnB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,IAAI,CAAA;IACV,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,CAAC,OAAO,CAAC,CAAA;IAC1C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,IAAI,CAAC,kCAAkC,OAAO,8EAA8E,CAAC,CAAA;QAC7H,OAAO,EAAE,CAAA;IACX,CAAC;IACD,OAAO,CAAC,GAAG,UAAU,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,iBAAiB,GAAG,MAAM,IAAI,CAAC,CAAC,CAAA;AAC3F,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAuB,EACvB,IAAyB,EACzB,UAA+B;IAE/B,MAAM,KAAK,GAAG,CAAC,KAAwB,EAAY,EAAE;QACnD,MAAM,GAAG,GAAa,EAAE,CAAA;QACxB,KAAK,MAAM,OAAO,IAAI,KAAK,EAAE,CAAC;YAC5B,KAAK,MAAM,QAAQ,IAAI,gBAAgB,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,CAAC;gBACnE,IAAI,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC7B,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC;wBAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;gBACjD,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,wCAAwC,QAAQ,8BAA8B,CAAC,CAAA;gBACtF,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAA;IACZ,CAAC,CAAA;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,KAAK,SAAS,CAAA;IAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAC1E,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACvE,IAAI,QAAQ,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;WAC5G,UAAU,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QACnE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IAC1B,CAAC;IACD,IAAI,QAAQ,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;QAC5D,IAAI,CACF,qEAAqE;cACnE,eAAe,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,uCAAuC,CACxF,CAAA;IACH,CAAC;IACD,OAAO;QACL,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3C,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3D,CAAA;AACH,CAAC"}