@sema-agent/core 7.4.0 → 7.5.1

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 (115) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/dist/agents/cascade.d.ts +2 -1
  3. package/dist/agents/peer-notices.d.ts +11 -1
  4. package/dist/agents/peer-session-drain.js +2 -0
  5. package/dist/agents/verify.d.ts +2 -1
  6. package/dist/core/ask-origin.d.ts +130 -0
  7. package/dist/core/ask-origin.js +35 -0
  8. package/dist/core/auto-mode-arming.d.ts +40 -1
  9. package/dist/core/auto-mode-arming.js +51 -3
  10. package/dist/core/auto-mode.d.ts +87 -10
  11. package/dist/core/auto-mode.js +34 -6
  12. package/dist/core/checkpoint-store.d.ts +41 -6
  13. package/dist/core/checkpoint-store.js +8 -0
  14. package/dist/core/hooks.d.ts +63 -19
  15. package/dist/core/hooks.js +37 -16
  16. package/dist/core/mcp.d.ts +47 -55
  17. package/dist/core/mcp.js +89 -31
  18. package/dist/core/park-selfcheck.js +3 -0
  19. package/dist/core/permission-rule-consent.d.ts +2 -11
  20. package/dist/core/permission-rule-consent.js +13 -62
  21. package/dist/core/permission-rule-org.d.ts +70 -54
  22. package/dist/core/permission-rule-org.js +47 -61
  23. package/dist/core/permission-rule-provider.d.ts +162 -0
  24. package/dist/core/permission-rule-provider.js +148 -0
  25. package/dist/core/permission-rule-session.d.ts +23 -19
  26. package/dist/core/permission-rule-session.js +5 -5
  27. package/dist/core/permission-rule-store.d.ts +46 -21
  28. package/dist/core/permission-rule-store.js +13 -6
  29. package/dist/core/permission-rule-sync.d.ts +2 -1
  30. package/dist/core/permission-rule-sync.js +11 -0
  31. package/dist/core/runner/assemble-result.d.ts +3 -2
  32. package/dist/core/runner/checkpoint-scope.d.ts +32 -0
  33. package/dist/core/runner/checkpoint-scope.js +4 -0
  34. package/dist/core/runner/contracts.d.ts +1878 -0
  35. package/dist/core/runner/contracts.js +1 -0
  36. package/dist/core/runner/denial-limit-arms.d.ts +57 -31
  37. package/dist/core/runner/denial-limit-arms.js +42 -17
  38. package/dist/core/runner/derived-route-fallback.d.ts +34 -0
  39. package/dist/core/runner/derived-route-fallback.js +16 -0
  40. package/dist/core/runner/prepare-acquire-reconcile.d.ts +1 -1
  41. package/dist/core/runner/prepare-announce-once.d.ts +83 -0
  42. package/dist/core/runner/prepare-announce-once.js +105 -0
  43. package/dist/core/runner/prepare-caps-and-workflow.d.ts +170 -0
  44. package/dist/core/runner/prepare-caps-and-workflow.js +255 -0
  45. package/dist/core/runner/prepare-config-doors.d.ts +2 -10
  46. package/dist/core/runner/prepare-defer-classify.d.ts +86 -0
  47. package/dist/core/runner/prepare-defer-classify.js +107 -0
  48. package/dist/core/runner/prepare-delegation-surface.d.ts +104 -0
  49. package/dist/core/runner/prepare-delegation-surface.js +144 -0
  50. package/dist/core/runner/prepare-execution-env.d.ts +54 -0
  51. package/dist/core/runner/prepare-execution-env.js +86 -0
  52. package/dist/core/runner/prepare-file-history.d.ts +95 -0
  53. package/dist/core/runner/prepare-file-history.js +383 -0
  54. package/dist/core/runner/prepare-hands-readface.d.ts +6 -8
  55. package/dist/core/runner/prepare-hands-readface.js +3 -3
  56. package/dist/core/runner/prepare-inherited-gate.d.ts +268 -0
  57. package/dist/core/runner/prepare-inherited-gate.js +266 -0
  58. package/dist/core/runner/prepare-listings.d.ts +77 -0
  59. package/dist/core/runner/prepare-listings.js +76 -0
  60. package/dist/core/runner/prepare-lsp.d.ts +55 -0
  61. package/dist/core/runner/prepare-lsp.js +27 -0
  62. package/dist/core/runner/prepare-memory.d.ts +1 -1
  63. package/dist/core/runner/prepare-offload-wrappers.d.ts +62 -0
  64. package/dist/core/runner/prepare-offload-wrappers.js +45 -0
  65. package/dist/core/runner/prepare-permission-rules.d.ts +132 -0
  66. package/dist/core/runner/prepare-permission-rules.js +140 -0
  67. package/dist/core/runner/prepare-project-context.d.ts +131 -0
  68. package/dist/core/runner/prepare-project-context.js +150 -0
  69. package/dist/core/runner/prepare-prompt-inputs.d.ts +138 -0
  70. package/dist/core/runner/prepare-prompt-inputs.js +141 -0
  71. package/dist/core/runner/prepare-protocol-tools.d.ts +91 -0
  72. package/dist/core/runner/prepare-protocol-tools.js +182 -0
  73. package/dist/core/runner/prepare-question-face.d.ts +119 -0
  74. package/dist/core/runner/prepare-question-face.js +83 -0
  75. package/dist/core/runner/prepare-run-refs.d.ts +89 -0
  76. package/dist/core/runner/prepare-run-refs.js +39 -0
  77. package/dist/core/runner/prepare-safety-scan.d.ts +3 -2
  78. package/dist/core/runner/prepare-task.d.ts +11 -1815
  79. package/dist/core/runner/prepare-task.js +138 -2542
  80. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +111 -0
  81. package/dist/core/runner/prepare-tool-disclosure-mount.js +219 -0
  82. package/dist/core/runner/prepare-wiring-manifest.d.ts +184 -0
  83. package/dist/core/runner/prepare-wiring-manifest.js +240 -0
  84. package/dist/core/runner/prepare-workspace-restore.d.ts +1 -27
  85. package/dist/core/runner/prepare-workspace-restore.js +1 -22
  86. package/dist/core/runner/rollback-stack.d.ts +32 -0
  87. package/dist/core/runner/rollback-stack.js +30 -0
  88. package/dist/core/runner/runtask.d.ts +11 -2
  89. package/dist/core/runner/runtask.js +27 -9
  90. package/dist/core/runner/workspace-path.d.ts +33 -0
  91. package/dist/core/runner/workspace-path.js +22 -0
  92. package/dist/core/sensitive-path-policy.d.ts +16 -0
  93. package/dist/core/sensitive-path-policy.js +1 -1
  94. package/dist/core/tool-policy.d.ts +57 -9
  95. package/dist/core/tool-policy.js +11 -0
  96. package/dist/core/types.d.ts +63 -51
  97. package/dist/core/wiring-manifest.d.ts +40 -3
  98. package/dist/core/wiring-manifest.js +4 -3
  99. package/dist/core/write-protect.d.ts +13 -2
  100. package/dist/core/write-protect.js +58 -29
  101. package/dist/engine/harness/types.d.ts +38 -16
  102. package/dist/engine/harness/types.js +25 -1
  103. package/dist/engine/session/session.d.ts +3 -11
  104. package/dist/index.d.ts +10 -6
  105. package/dist/index.js +9 -5
  106. package/dist/internal/harness.d.ts +1 -0
  107. package/dist/stores/file/adoption/adopt.d.ts +1 -1
  108. package/dist/stores/file/adoption/marker.d.ts +1 -1
  109. package/dist/stores/file/permission-rule-adopt.js +4 -3
  110. package/dist/stores/file/permission-rule-store.d.ts +65 -25
  111. package/dist/stores/file/permission-rule-store.js +215 -37
  112. package/dist/stores/file/task-list-store.d.ts +1 -1
  113. package/dist/tools/fs/read-face.d.ts +1 -1
  114. package/package.json +8 -2
  115. package/test/export-surface.snapshot.json +76 -28
@@ -0,0 +1,131 @@
1
+ /**
2
+ * design/390 M9 — prepareTask's PROJECT-CONTEXT phase: the deployment's project memory snapshot (the
3
+ * `loadProjectMemory` seam: phase derivation, instruction sources, the framed + fenced project layer
4
+ * appended to the memory block), the skills roster (declaration rank, normalization, the 1MB load gate,
5
+ * the manifest-scope door, the `skill` tool mount), the two built-in memory pairs' mount-or-yield
6
+ * (shared-memory pair, engine-memory pair), and the skills-listing projection the run loop announces.
7
+ * The body is the driver's stretch moved verbatim; the interface below is the dependency list it had
8
+ * implicitly (design/238 R-1: a new read is an Input field, i.e. a review event).
9
+ *
10
+ * ASYNC BY CONTENT: the stretch awaits the session branch read, the seam itself and the read-state
11
+ * seeding (all inside the `loadProjectMemory` arm). It already sits past prepareTask's first await, so
12
+ * the one extra microtask the driver's `await` adds on the seam-less path opens no window any caller
13
+ * could reach (design/390 §1.5 S1 synchronicity rule: only pre-first-await stretches must stay sync).
14
+ *
15
+ * Throws pass through unchanged: `config.reserved_tool_name` (a caller tool named like the skill tool
16
+ * while skills are present) is the only refusal here; the seam's own throw and the seeding's are
17
+ * caught and forwarded to `deps.onError` (best-effort, never block the task).
18
+ */
19
+ import type { AgentTool } from "../../internal/harness.js";
20
+ import type { StoredSession } from "../session.js";
21
+ import type { RunnerDeps, TaskSpec, ToolSpec } from "../types.js";
22
+ import type { ActiveSkillScope } from "./active-skill-scope.js";
23
+ import type { AnnounceOnceSink, Prepared, PrepareResume, RunInternals, ToolFaceSnapshot } from "./contracts.js";
24
+ import { normalizeSkills } from "./synthetic-tools.js";
25
+ /**
26
+ * The ONE "has this session seen conversation?" predicate — the freshness question is asked three
27
+ * times in one prepare (project-memory phase, design/148 center adoption, the epoch-pin
28
+ * discriminator), and an entry-COUNT proxy at ANY of them re-creates the same lie: a host may write
29
+ * session_info / model_change / … rows before the first turn (naming a new chat, recording a model
30
+ * pick), and none of that is evidence of a prior session (5.29 merge-rescan: the count proxy
31
+ * survived at the adoption arm after the pin arm was fixed, silently costing a pre-named session
32
+ * its published center prompt for life). Conversation content — and only that — is the evidence.
33
+ */
34
+ export declare function hasConversationContent(branch: ReadonlyArray<{
35
+ type: string;
36
+ }>): boolean;
37
+ export interface PrepareProjectContextInput {
38
+ /** borrowed-readonly — the deployment seams this phase reads: `loadProjectMemory` (the snapshot),
39
+ * `onError` (the operator lane for a throwing seam / seeding / oversize skill), `sharedMemoryStores`
40
+ * (the shared-memory pair's switch: wired ⇒ mount). Receiver preserved (`deps.onError?.(…)`). */
41
+ deps: Pick<RunnerDeps, "loadProjectMemory" | "onError" | "sharedMemoryStores">;
42
+ /** borrowed-readonly — the REBOUND spec (`spec′`). Read: `sessionId` (phase derivation + the
43
+ * continuation projections), `skills` (the roster), `principal` (the shared-memory tools' trusted
44
+ * identity). Never mutated. */
45
+ spec: Pick<TaskSpec, "sessionId" | "skills" | "principal">;
46
+ /** borrowed-readonly — trusted internals: `agentName` (seam ctx), `inheritedManifestScope` (the
47
+ * parent's active skill frames, seeded as the base of the LIFO). `undefined` on a standalone prepare. */
48
+ internals: Pick<RunInternals, "agentName" | "inheritedManifestScope"> | undefined;
49
+ /** borrowed-readonly — the acquired session; only `getBranch()` is read (phase derivation walk).
50
+ * The read is best-effort: a throw keeps the provisional phase. */
51
+ session: Pick<StoredSession, "getBranch">;
52
+ /** borrowed-readonly — the acquired session's id (seam ctx cache key; operator-line context). */
53
+ sessionId: string;
54
+ /** borrowed-readonly — `spec.taskId ?? sessionId`: the shared-memory tools' trusted task identity. */
55
+ hostTaskId: string;
56
+ /** borrowed-readonly — the FINAL task root (post-restore): the seam's `cwd`. */
57
+ taskRootFinal: string;
58
+ /** borrowed-readonly — whether a real execution env is mounted (seam ctx flag). */
59
+ handsEnabled: boolean;
60
+ /** borrowed-readonly — the leg's effective-delegation facts; only `isNonForkChild` is read (the
61
+ * seam's `isSubagent` — one derivation shared with the prompt fact, never re-derived here). */
62
+ delegation: {
63
+ readonly isNonForkChild: boolean;
64
+ };
65
+ /** borrowed-readonly — the hands phase's read-state seeding closure (CREATED there, CALLED here for
66
+ * the snapshot's declared `seededFiles`); `undefined` on a hands-less task = nothing to seed. */
67
+ seedContextFiles: ((files: ReadonlyArray<{
68
+ path: string;
69
+ content: string;
70
+ }>) => Promise<void>) | undefined;
71
+ /** borrowed-readonly — the memory phase's composed block, as returned (recall discipline + engine
72
+ * files). This phase APPENDS the project layer and hands the result back as
73
+ * {@link PrepareProjectContextResult.memoryBlock}; the input value itself is never mutated. */
74
+ memoryBlockFromEngine: string | undefined;
75
+ /** borrowed-mutable — the run's LIFO skill-scope stack (identity is the contract: the scope policy
76
+ * and the `skill` tool alias this exact object). Mutated here: `push` of every inherited parent
77
+ * frame; after this phase only the `skill` tool's execute pushes/pops. */
78
+ skillScope: ActiveSkillScope;
79
+ /** borrowed-mutable — the shared mount array (later refs alias it; in-place contract). Mutated
80
+ * here: `push` of the `skill` tool, the shared-memory pair and the engine-memory pair. Occupancy
81
+ * is judged on this ALREADY-MOUNTED roster (names + aliases). */
82
+ tools: AgentTool[];
83
+ /** borrowed-readonly — the frozen task-start tool face; only `exclude` is read (pair yield ①). */
84
+ toolFaceSnapshot: Pick<ToolFaceSnapshot, "exclude">;
85
+ /** borrowed-readonly — the once-per-session announcement ledger; only its deduplicated `onError`
86
+ * sink is invoked (a pair's mount shape is a session-level fact, announced once). */
87
+ onceLedger: AnnounceOnceSink;
88
+ /** borrowed-readonly — the memory phase's engine-pair ToolSpecs (present iff the pair may mount). */
89
+ memoryTools: ToolSpec[] | undefined;
90
+ /** borrowed-readonly — the memory phase's session seat; only `!== undefined` is read (the
91
+ * blocked-mount disclosure fires only when a session mounted). */
92
+ memoryEngineSession: Prepared["memoryEngineSession"];
93
+ /** borrowed-readonly — the engine pair's early conjuncts (computed before the memory phase). */
94
+ memorySearchToolsPlanned: boolean;
95
+ /** borrowed-readonly — the engine-pair name the exclusion valve removed, if any (disclosure text). */
96
+ memoryPairExcludedName: string | undefined;
97
+ /** borrowed-readonly — the engine-pair name a declared tool already occupies, if any (disclosure text). */
98
+ memoryPairOccupiedName: string | undefined;
99
+ /** borrowed-readonly — the durable-resume carrier; read for `!== undefined` (seedAnnounced) and
100
+ * `seed.announcedListings.skills` (the removal-to-zero projection's gate). */
101
+ resume: Pick<PrepareResume, "seed"> | undefined;
102
+ }
103
+ export interface PrepareProjectContextResult {
104
+ /** owned — the memory block with the project layer appended at the tail (unchanged when the seam is
105
+ * unwired or returns null/blank). The driver binds it under a NEW name; the defer-classification
106
+ * stretch may still strip the recall-discipline segment from it. */
107
+ memoryBlock: string | undefined;
108
+ /** owned — the snapshot's declared instruction sources (non-empty only), for the run loop's
109
+ * instructions_change lane (`Prepared.instructionSources`). */
110
+ instructionSources: ReadonlyArray<{
111
+ path: string;
112
+ contentHash: string | null;
113
+ }> | undefined;
114
+ /** owned — the RAW snapshot text (pre-compose) for the compaction lanes' instruction-file seat
115
+ * (`Prepared.projectInstructionContent`). */
116
+ projectInstructionContent: string | undefined;
117
+ /** owned — the normalized, load-gated skills (first-wins dedupe, name-sorted). Consumed inside this
118
+ * phase (tool + listing); exposed as the phase's own product. */
119
+ skillSpecs: ReturnType<typeof normalizeSkills>;
120
+ /** owned — whether THIS task carries a manifested skill or inherits a parent frame (the skill-scope
121
+ * policy's activation predicate). */
122
+ hasSkillManifest: boolean;
123
+ /** owned — the skills-listing projection the run loop's announce lane consumes (`Prepared.skillsListing`). */
124
+ skillsListing: Prepared["skillsListing"];
125
+ /** owned — whether the shared-memory pair mounted here (the support-name pre-check reads it). */
126
+ sharedMemoryPairMounted: boolean;
127
+ /** owned — whether the engine-memory pair mounted here (the support-name pre-check reads it). */
128
+ memoryEnginePairMounted: boolean;
129
+ }
130
+ /** The M9 phase body — prepareTask's project-context stretch, verbatim (see the module header). */
131
+ export declare function prepareProjectContext(input: PrepareProjectContextInput): Promise<PrepareProjectContextResult>;
@@ -0,0 +1,150 @@
1
+ import { PROJECT_CONTEXT_FRAMING } from "../../prompts/default.js";
2
+ import { MEMORY_ENGINE_TOOL_NAMES } from "../memory-engine/tools.js";
3
+ import { composeMemoryBlock } from "../memory.js";
4
+ import { createSharedMemoryTools } from "../shared-memory/tools.js";
5
+ import { SHARED_MEMORY_TOOL_NAMES } from "../shared-memory/types.js";
6
+ import { defineTool } from "../tools.js";
7
+ import { SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, createSkillTool, normalizeSkills } from "./synthetic-tools.js";
8
+ export function hasConversationContent(branch) {
9
+ return branch.some((e) => e.type === "message" || e.type === "custom_message" || e.type === "compaction");
10
+ }
11
+ export async function prepareProjectContext(input) {
12
+ const { deps, spec, internals, session, sessionId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillScope, tools, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume } = input;
13
+ let memoryBlock = memoryBlockFromEngine;
14
+ let instructionSources;
15
+ let projectInstructionContent;
16
+ if (deps.loadProjectMemory) {
17
+ let projectMemoryPhase = spec.sessionId ? "resume" : "fresh";
18
+ if (spec.sessionId) {
19
+ try {
20
+ const branch = await session.getBranch();
21
+ const hasConversation = hasConversationContent(branch);
22
+ if (!hasConversation)
23
+ projectMemoryPhase = "fresh";
24
+ else {
25
+ for (let i = branch.length - 1; i >= 0; i--) {
26
+ const e = branch[i];
27
+ if (e.type === "compaction") {
28
+ projectMemoryPhase = "post-compact";
29
+ break;
30
+ }
31
+ if (e.type === "message" && e.message.role === "user")
32
+ break;
33
+ }
34
+ }
35
+ }
36
+ catch {
37
+ }
38
+ }
39
+ let loaded = null;
40
+ try {
41
+ loaded = await Promise.resolve(deps.loadProjectMemory({
42
+ cwd: taskRootFinal,
43
+ handsEnabled,
44
+ isSubagent: delegation.isNonForkChild,
45
+ ...(internals?.agentName ? { agentName: internals.agentName } : {}),
46
+ sessionId,
47
+ phase: projectMemoryPhase,
48
+ }));
49
+ }
50
+ catch (err) {
51
+ deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "config", sessionId });
52
+ }
53
+ const projectMem = loaded !== null && typeof loaded === "object" ? loaded.content : loaded;
54
+ const seededFiles = loaded !== null && typeof loaded === "object" ? loaded.seededFiles : undefined;
55
+ const declaredSources = loaded !== null && typeof loaded === "object" ? loaded.instructionSources : undefined;
56
+ if (declaredSources !== undefined && declaredSources.length > 0)
57
+ instructionSources = declaredSources;
58
+ if (seededFiles?.length && seedContextFiles) {
59
+ try {
60
+ await seedContextFiles(seededFiles);
61
+ }
62
+ catch (err) {
63
+ deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "config", sessionId });
64
+ }
65
+ }
66
+ if (projectMem != null && projectMem.trim()) {
67
+ projectInstructionContent = projectMem;
68
+ const projectBlock = `${PROJECT_CONTEXT_FRAMING}\n\n${composeMemoryBlock(projectMem, "project")}`;
69
+ memoryBlock = memoryBlock ? `${memoryBlock}\n\n${projectBlock}` : projectBlock;
70
+ }
71
+ }
72
+ const declaredSkillRank = new Map();
73
+ for (const s of spec.skills ?? [])
74
+ if (!declaredSkillRank.has(s.name))
75
+ declaredSkillRank.set(s.name, declaredSkillRank.size);
76
+ const skillSpecs = normalizeSkills(spec.skills ?? []).filter((s) => {
77
+ if (s.content.length <= SKILL_CONTENT_MAX_CHARS)
78
+ return true;
79
+ deps.onError?.(new Error(`Skill "${s.name}" content is ${s.content.length} chars, over the ${SKILL_CONTENT_MAX_CHARS}-char load limit — skill not loaded (skills are never truncated; shrink the body or move material to attachments/files).`), { phase: "config", sessionId });
80
+ return false;
81
+ });
82
+ const inheritedFrames = internals?.inheritedManifestScope ?? [];
83
+ for (const frame of inheritedFrames) {
84
+ skillScope.push(frame);
85
+ }
86
+ const hasSkillManifest = skillSpecs.some((s) => s.manifest !== undefined) || inheritedFrames.length > 0;
87
+ if (skillSpecs.length > 0) {
88
+ if (tools.some((t) => t.name === SKILL_TOOL_NAME)) {
89
+ const e = new Error(`Tool name "${SKILL_TOOL_NAME}" is reserved when spec.skills is present.`);
90
+ e.code = "config.reserved_tool_name";
91
+ throw e;
92
+ }
93
+ tools.push(createSkillTool(skillSpecs, skillScope));
94
+ }
95
+ const sharedMemoryProvider = deps.sharedMemoryStores;
96
+ let sharedMemoryPairMounted = false;
97
+ if (sharedMemoryProvider !== undefined) {
98
+ const excluded = new Set(toolFaceSnapshot.exclude ?? []);
99
+ const mountedNameDomain = new Set();
100
+ for (const t of tools) {
101
+ mountedNameDomain.add(t.name);
102
+ for (const alias of t.aliases ?? [])
103
+ mountedNameDomain.add(alias);
104
+ }
105
+ const excludedName = SHARED_MEMORY_TOOL_NAMES.find((n) => excluded.has(n));
106
+ const occupiedName = SHARED_MEMORY_TOOL_NAMES.find((n) => mountedNameDomain.has(n));
107
+ if (excludedName !== undefined || occupiedName !== undefined) {
108
+ onceLedger.onError(new Error(`Shared memory tools ${SHARED_MEMORY_TOOL_NAMES.join("/")} were NOT mounted: ` +
109
+ (excludedName !== undefined
110
+ ? `excludeTools removes "${excludedName}"`
111
+ : `this task already declares a tool named "${occupiedName}" (the built-in yields to it)`) +
112
+ " — the pair mounts together or not at all."), { phase: "config", sessionId, classification: "shared-memory-not-mounted" });
113
+ }
114
+ else {
115
+ tools.push(...createSharedMemoryTools({
116
+ provider: sharedMemoryProvider,
117
+ context: { sessionId, taskId: hostTaskId, ...(spec.principal !== undefined ? { principal: spec.principal } : {}) },
118
+ }).map((s) => defineTool(s)));
119
+ sharedMemoryPairMounted = true;
120
+ }
121
+ }
122
+ let memoryEnginePairMounted = false;
123
+ if (memoryTools !== undefined && memoryTools.length > 0) {
124
+ tools.push(...memoryTools.map((s) => defineTool(s)));
125
+ memoryEnginePairMounted = true;
126
+ }
127
+ else if (memoryEngineSession !== undefined && !memorySearchToolsPlanned) {
128
+ onceLedger.onError(new Error(`Memory tools ${MEMORY_ENGINE_TOOL_NAMES.join("/")} were NOT mounted: ` +
129
+ (memoryPairExcludedName !== undefined
130
+ ? `excludeTools removes "${memoryPairExcludedName}"`
131
+ : `this task already declares a tool named "${memoryPairOccupiedName}" (the built-in yields to it)`) +
132
+ " — these tools mount together or not at all."), { phase: "config", sessionId, classification: "memory-tools-not-mounted" });
133
+ }
134
+ const skillsListing = skillSpecs.length > 0
135
+ ? {
136
+ entries: skillSpecs.map((s) => ({
137
+ name: s.name,
138
+ description: s.description,
139
+ ...(s.files !== undefined ? { files: s.files.map((f) => ({ path: f.path })) } : {}),
140
+ ...(declaredSkillRank.get(s.name) !== undefined ? { declaredRank: declaredSkillRank.get(s.name) } : {}),
141
+ })),
142
+ seedAnnounced: resume !== undefined || spec.sessionId !== undefined,
143
+ }
144
+ :
145
+ (resume?.seed.announcedListings?.skills?.length ?? 0) > 0 || spec.sessionId !== undefined
146
+ ?
147
+ { entries: [], seedAnnounced: true }
148
+ : undefined;
149
+ return { memoryBlock, instructionSources, projectInstructionContent, skillSpecs, hasSkillManifest, skillsListing, sharedMemoryPairMounted, memoryEnginePairMounted };
150
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * design/390 M10 — prepareTask's PROMPT-INPUTS phase: the prompt provider selection, the runtime
3
+ * feature flags the harness-context block gates on (so it never claims a mechanism the task lacks),
4
+ * the three-source user system prompt + the caller's append block, the fenced MCP server-instructions
5
+ * block, the environment facts (client zone + date basis, knowledge cutoff, the deployment's sandbox
6
+ * axes, resume-continuity facts on a durable-resume leg only, the hands facts + the one batched env
7
+ * probe with its cwd/pwd alignment guard) and the git-status lane probe. The body is the driver's
8
+ * stretch moved verbatim; the interface is the dependency list it had implicitly (design/238 R-1).
9
+ *
10
+ * ASYNC BY CONTENT: the stretch awaits the env probe (hands) and the git-status lane probe (always).
11
+ * It sits past prepareTask's first await, so the driver's `await` adds no window a caller could reach.
12
+ *
13
+ * The one input computed by the DRIVER rather than read here: `failClosedReason` — the
14
+ * self-orchestration fail-closed advisory's text. Its helper takes the whole `TaskSpec`/`RunnerDeps`
15
+ * (it audits several deployment seats), which this phase's narrow `spec`/`deps` seats cannot satisfy
16
+ * (design/238 R-4); the driver evaluates the helper in the Input literal and this phase only announces
17
+ * the result on the operator lane, exactly where the stretch always did.
18
+ *
19
+ * Nothing here throws on its own: the env probe and the alignment guard are best-effort (a stub/odd env
20
+ * just omits the shell-derived facts), and the git lane degrades through `onDegrade` → `deps.onError`.
21
+ */
22
+ import type { ExecutionEnv, ThinkingLevel } from "../../internal/harness.js";
23
+ import type { Model } from "../../internal/llm.js";
24
+ import { type EnvironmentFacts, type PromptProvider } from "../../prompts/default.js";
25
+ import type { ReadFace } from "../../tools/fs/index.js";
26
+ import type { LockedPreflight } from "../locked-config.js";
27
+ import type { MaterializedMcp } from "../mcp.js";
28
+ import type { ResolvedRole } from "../roles.js";
29
+ import type { RunnerDeps, TaskSpec } from "../types.js";
30
+ import type { PrepareResume, RunInternals, ToolFaceSnapshot } from "./contracts.js";
31
+ import { type GitStatusLaneRef } from "./git-status-frame.js";
32
+ /** The runtime feature flags the stable prompt's harness-context block gates its sections on
33
+ * (design/64 §6.3: claim only what the task has). Spread into the assembly's StablePromptContext
34
+ * by the driver, which re-derives the two orchestration flags against the post-exclusion roster. */
35
+ export interface PromptFeatureFlags {
36
+ policyEnabled: boolean;
37
+ hooksEnabled: boolean;
38
+ isolationEnabled: boolean;
39
+ reminderMark: string;
40
+ readFaceOpen: boolean;
41
+ orchestrationEnabled: boolean;
42
+ orchestrationDeferred: boolean;
43
+ promptProfile: "simple" | "classic";
44
+ fableMitigations: boolean;
45
+ goalEnabled: boolean;
46
+ awarenessEnabled: boolean;
47
+ worktreeIsolated: boolean;
48
+ withinTaskCompactionEnabled: boolean;
49
+ isSubagent: boolean;
50
+ }
51
+ export interface PreparePromptInputsInput {
52
+ /** borrowed-readonly — the REBOUND spec (`spec′`). Read: `promptProvider`, `hooks` (flag), `systemPrompt`
53
+ * / `appendSystemPrompt` (the user prompt sources), `clientContext` (zone, email), `envFacts` (the
54
+ * deployment's sandbox + resume facts), `compaction` (the within-task flag). Never mutated. */
55
+ spec: Pick<TaskSpec, "promptProvider" | "hooks" | "systemPrompt" | "appendSystemPrompt" | "clientContext" | "envFacts" | "compaction">;
56
+ /** borrowed-readonly — deployment deps: `promptProvider` (fallback), `hooks` (flag), `onError` (the
57
+ * operator lane: fail-closed advisory, cwd misalignment, git-lane degrade), `modelKnowledgeCutoffs`.
58
+ * Receiver preserved (`deps.onError?.(…)`). */
59
+ deps: Pick<RunnerDeps, "promptProvider" | "hooks" | "onError" | "modelKnowledgeCutoffs">;
60
+ /** borrowed-readonly — the self-orchestration fail-closed advisory (null = nothing to announce),
61
+ * evaluated by the DRIVER against the whole spec/deps (see the module header). */
62
+ failClosedReason: string | null;
63
+ /** borrowed-readonly — the locked preflight; only `toolPolicy` presence is read (the policy flag). */
64
+ lockedPreflight: Pick<LockedPreflight, "toolPolicy">;
65
+ /** borrowed-readonly — whether self-orchestration is ACTIVE (the same gate that mounted the
66
+ * workflow tool): drives the orchestration prompt flags. */
67
+ selfOrchestrationActive: boolean;
68
+ /** borrowed-readonly — the task's execution env: `isIsolated` for the isolation flag, `exec` for the
69
+ * one batched probe, `canonicalPath` for the alignment guard. Never re-bound, never destroyed here. */
70
+ executionEnv: ExecutionEnv;
71
+ /** borrowed-readonly — the session's reminder mark (rides the prompt facts for the declaration). */
72
+ reminderMark: string;
73
+ /** borrowed-readonly — the resolved read face (the open-reads flag + the structured env fact). */
74
+ resolvedReadFace: ReadFace | undefined;
75
+ /** borrowed-readonly — the frozen tool face; only `defer` is read (the Workflow short-pointer flag). */
76
+ toolFaceSnapshot: Pick<ToolFaceSnapshot, "defer">;
77
+ /** borrowed-readonly — the resolved prompt profile (rides the flags to the legacy ctx). */
78
+ promptProfile: "simple" | "classic";
79
+ /** borrowed-readonly — the fable-mitigations profile axis (rides the flags with the profile). */
80
+ fableMitigations: boolean;
81
+ /** borrowed-readonly — trusted internals: `goalMode` (goal flag), `isolation` (worktree flag),
82
+ * `defaultSystemPrompt` (the third prompt source). `undefined` on a standalone prepare. */
83
+ internals: Pick<RunInternals, "goalMode" | "isolation" | "defaultSystemPrompt"> | undefined;
84
+ /** borrowed-readonly — the resolved, role-folded reasoning tier (the awareness flag's input). */
85
+ thinking: ThinkingLevel | undefined;
86
+ /** borrowed-readonly — the factory-minted isolated env, read for PRESENCE only (the worktree flag's
87
+ * belt over the fail-closed isolation guard). */
88
+ ownedEnv: ExecutionEnv | undefined;
89
+ /** borrowed-readonly — the leg's effective-delegation facts; only `isNonForkChild` is read. */
90
+ delegation: {
91
+ readonly isNonForkChild: boolean;
92
+ };
93
+ /** borrowed-readonly — the resolved role; only its `systemPrompt` preset is read (second source). */
94
+ resolvedRole: Pick<ResolvedRole, "systemPrompt">;
95
+ /** borrowed-readonly — the materialized MCP roster; only `serverInstructions` is read (fenced). */
96
+ mcp: Pick<MaterializedMcp, "serverInstructions">;
97
+ /** borrowed-readonly — the resolved model (id for the env line + the cutoff lookup). */
98
+ model: Model;
99
+ /** borrowed-readonly — the run's external-content-target fold (the single-source disclosure fact). */
100
+ externalContentTargetActive: boolean;
101
+ /** borrowed-readonly — the durable-resume carrier, read for PRESENCE only (resume facts copy on a
102
+ * resume leg and never on a fresh one). */
103
+ resume: PrepareResume | undefined;
104
+ /** borrowed-readonly — whether a real execution env is mounted (the hands facts + both probes). */
105
+ handsEnabled: boolean;
106
+ /** borrowed-readonly — the FINAL task root: the env `cwd` fact and the git lane's root. */
107
+ taskRootFinal: string;
108
+ /** borrowed-readonly — the canonical extra working roots (copied into the env facts). */
109
+ additionalRootsCanonical: readonly string[];
110
+ /** borrowed-readonly — the canonical extra read-only roots (copied into the env facts). */
111
+ additionalReadRootsCanonical: readonly string[];
112
+ /** borrowed-readonly — the acquired session's id (operator-line context). */
113
+ sessionId: string;
114
+ }
115
+ export interface PreparePromptInputsResult {
116
+ /** owned — the prompt provider in force (`spec` ?? `deps` ?? the default). */
117
+ provider: PromptProvider;
118
+ /** owned — the runtime feature flags (see {@link PromptFeatureFlags}). */
119
+ featureFlags: PromptFeatureFlags;
120
+ /** owned — the user system prompt: `spec.systemPrompt ?? resolvedRole.systemPrompt ?? internals.defaultSystemPrompt`. */
121
+ userSystemPrompt: string | undefined;
122
+ /** owned — the caller's append block (`spec.appendSystemPrompt`). */
123
+ userAppendSystemPrompt: string | undefined;
124
+ /** owned — the fenced MCP server-instructions block, or undefined when no server declared any. */
125
+ mcpInstructionsBlock: string | undefined;
126
+ /** owned — the environment facts the env tail renders from. Filled here; no later phase writes it
127
+ * (the run loop re-renders only the date through `renderWithDate`). */
128
+ envFacts: EnvironmentFacts;
129
+ /** owned — whether the client zone validated (the date-change seam re-renders in the same zone). */
130
+ tzValid: boolean;
131
+ /** owned — the client's declared zone as given (paired with `tzValid`; unvalidated on its own). */
132
+ userTz: string | undefined;
133
+ /** owned — the git-status lane's run-local state (empty on a hands-less leg); the run loop and the
134
+ * checkpoint serializer mutate its fields from here on (`announced`, `pendingReceipt`, …). */
135
+ gitStatusRef: GitStatusLaneRef;
136
+ }
137
+ /** The M10 phase body — prepareTask's prompt-inputs stretch, verbatim (see the module header). */
138
+ export declare function preparePromptInputs(input: PreparePromptInputsInput): Promise<PreparePromptInputsResult>;
@@ -0,0 +1,141 @@
1
+ import { defaultPromptProvider, formatLocalDate, isValidTimeZone } from "../../prompts/default.js";
2
+ import { isIsolated } from "../remote-env.js";
3
+ import { probeGitStatusLane } from "./git-status-frame.js";
4
+ import { fenceMcpServerInstructions } from "./turn-attachments.js";
5
+ const ULTRA_REASONING_TIERS = new Set(["xhigh", "max"]);
6
+ export async function preparePromptInputs(input) {
7
+ const { spec, deps, failClosedReason, lockedPreflight, selfOrchestrationActive, executionEnv, reminderMark, resolvedReadFace, toolFaceSnapshot, promptProfile, fableMitigations, internals, thinking, ownedEnv, delegation, resolvedRole, mcp, model, externalContentTargetActive, resume, handsEnabled, taskRootFinal, additionalRootsCanonical, additionalReadRootsCanonical, sessionId } = input;
8
+ const provider = spec.promptProvider ?? deps.promptProvider ?? defaultPromptProvider;
9
+ const promptPolicyEnabled = Boolean(lockedPreflight.toolPolicy);
10
+ const promptHooks = spec.hooks ?? deps.hooks;
11
+ const promptHooksEnabled = Boolean(promptHooks?.preToolUse || promptHooks?.postToolUse);
12
+ if (failClosedReason) {
13
+ deps.onError?.(new Error(failClosedReason), { phase: "config", sessionId });
14
+ }
15
+ const featureFlags = {
16
+ policyEnabled: promptPolicyEnabled,
17
+ hooksEnabled: promptHooksEnabled,
18
+ isolationEnabled: isIsolated(executionEnv),
19
+ reminderMark,
20
+ readFaceOpen: resolvedReadFace === "open",
21
+ orchestrationEnabled: selfOrchestrationActive,
22
+ orchestrationDeferred: selfOrchestrationActive && (toolFaceSnapshot.defer?.includes("Workflow") ?? false),
23
+ promptProfile,
24
+ fableMitigations,
25
+ goalEnabled: internals?.goalMode === true,
26
+ awarenessEnabled: thinking !== undefined && ULTRA_REASONING_TIERS.has(thinking),
27
+ worktreeIsolated: internals?.isolation === "worktree" && ownedEnv !== undefined,
28
+ withinTaskCompactionEnabled: (spec.compaction?.enabled ?? true) && (spec.compaction?.withinTask ?? true),
29
+ isSubagent: delegation.isNonForkChild,
30
+ };
31
+ const userSystemPrompt = spec.systemPrompt ?? resolvedRole.systemPrompt ?? internals?.defaultSystemPrompt;
32
+ const userAppendSystemPrompt = spec.appendSystemPrompt;
33
+ const mcpInstructionsBlock = mcp.serverInstructions.length
34
+ ? `# MCP Server Instructions\n${mcp.serverInstructions.map((s) => fenceMcpServerInstructions(s.server, s.text)).join("\n\n")}`
35
+ : undefined;
36
+ const userTz = spec.clientContext?.timeZone;
37
+ const tzValid = userTz !== undefined && isValidTimeZone(userTz);
38
+ const envFacts = { date: formatLocalDate(new Date(), tzValid ? userTz : undefined), modelId: model.id };
39
+ if (tzValid)
40
+ envFacts.timeZone = userTz;
41
+ if (spec.clientContext?.userEmail)
42
+ envFacts.userEmail = spec.clientContext.userEmail;
43
+ const declaredCutoff = deps.modelKnowledgeCutoffs?.[model.id];
44
+ if (declaredCutoff)
45
+ envFacts.knowledgeCutoff = declaredCutoff;
46
+ if (spec.envFacts?.profile)
47
+ envFacts.sandboxProfile = spec.envFacts.profile;
48
+ if (spec.envFacts?.capabilities && spec.envFacts.capabilities.length > 0)
49
+ envFacts.sandboxCapabilities = [...spec.envFacts.capabilities];
50
+ if (spec.envFacts?.pkgSource)
51
+ envFacts.sandboxPkgSource = spec.envFacts.pkgSource;
52
+ if (spec.envFacts?.egress === "none" || spec.envFacts?.egress === "allowlist" || spec.envFacts?.egress === "full")
53
+ envFacts.sandboxEgress = spec.envFacts.egress;
54
+ if (spec.envFacts?.scratchpadDir)
55
+ envFacts.scratchpadDir = spec.envFacts.scratchpadDir;
56
+ if (externalContentTargetActive)
57
+ envFacts.externalContentTarget = true;
58
+ if (resume !== undefined && spec.envFacts?.resumeFacts) {
59
+ const rf = spec.envFacts.resumeFacts;
60
+ const copy = {};
61
+ if (rf.processes === "preserved" || rf.processes === "lost")
62
+ copy.processes = rf.processes;
63
+ if (rf.scratch === "preserved" || rf.scratch === "lost")
64
+ copy.scratch = rf.scratch;
65
+ if (typeof rf.note === "string" && rf.note.length > 0)
66
+ copy.note = rf.note;
67
+ if (Object.keys(copy).length > 0)
68
+ envFacts.resumeFacts = copy;
69
+ }
70
+ if (handsEnabled) {
71
+ envFacts.cwd = taskRootFinal;
72
+ if (additionalRootsCanonical.length > 0) {
73
+ envFacts.additionalDirectories = [...additionalRootsCanonical];
74
+ }
75
+ if (resolvedReadFace !== undefined)
76
+ envFacts.readFace = resolvedReadFace;
77
+ if (additionalReadRootsCanonical.length > 0) {
78
+ envFacts.additionalReadDirectories = [...additionalReadRootsCanonical];
79
+ }
80
+ try {
81
+ const probe = await executionEnv.exec('uname -s; uname -r; (git rev-parse --is-inside-work-tree 2>/dev/null || echo false); (git symbolic-ref --short -q HEAD 2>/dev/null || echo "HEAD (detached)"); (git rev-parse --show-toplevel 2>/dev/null || echo); (test -n "$(git status --porcelain 2>/dev/null | head -1)" && echo dirty || echo clean); (s=$(ps -p $$ -o comm= 2>/dev/null); s=${s##*/}; echo "${s#-}"); (test "$(git rev-parse --git-dir 2>/dev/null)" != "$(git rev-parse --git-common-dir 2>/dev/null)" && echo linked || echo main); (pwd -P 2>/dev/null || pwd)', { cwd: envFacts.cwd, timeout: 10 });
82
+ if (probe.ok && probe.value.exitCode === 0) {
83
+ const lines = probe.value.stdout.split("\n");
84
+ if (lines.length >= 6) {
85
+ const [osName, osVer, git, branch, worktreeRoot, dirty] = lines;
86
+ if (osName?.trim())
87
+ envFacts.platform = osName.trim();
88
+ if (osVer?.trim())
89
+ envFacts.osVersion = osVer.trim();
90
+ envFacts.isGitRepo = (git ?? "").trim() === "true";
91
+ if (envFacts.isGitRepo) {
92
+ const b = (branch ?? "").trim();
93
+ if (b)
94
+ envFacts.gitBranch = b;
95
+ const root = (worktreeRoot ?? "").trim();
96
+ if (root)
97
+ envFacts.gitWorktreeRoot = root;
98
+ const d = (dirty ?? "").trim();
99
+ if (d === "dirty" || d === "clean")
100
+ envFacts.gitDirty = d === "dirty";
101
+ }
102
+ const sh = (lines[6] ?? "").trim();
103
+ if (sh)
104
+ envFacts.shell = sh;
105
+ if (envFacts.isGitRepo && (lines[7] ?? "").trim() === "linked")
106
+ envFacts.isLinkedWorktree = true;
107
+ const probedPwd = (lines[8] ?? "").trim();
108
+ if (probedPwd && envFacts.cwd && probedPwd !== envFacts.cwd) {
109
+ const canonOf = async (p) => {
110
+ try {
111
+ const c = await executionEnv.canonicalPath(p);
112
+ return c.ok ? c.value : p;
113
+ }
114
+ catch {
115
+ return p;
116
+ }
117
+ };
118
+ const canonicalProbed = await canonOf(probedPwd);
119
+ if (canonicalProbed !== (await canonOf(envFacts.cwd))) {
120
+ try {
121
+ deps.onError?.(new Error(`execution-env cwd misalignment: task root is "${envFacts.cwd}" but the env's shell reports pwd "${canonicalProbed}" — per-exec cwd injection may be ignored on this lane (execs/artifacts can land outside the workspace root)`), { phase: "config", sessionId });
122
+ }
123
+ catch {
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
130
+ catch {
131
+ }
132
+ }
133
+ const gitStatusRef = await probeGitStatusLane({
134
+ executionEnv,
135
+ envFacts,
136
+ handsEnabled,
137
+ taskRoot: taskRootFinal,
138
+ onDegrade: (reason) => deps.onError?.(new Error(`env git snapshot degraded — ${reason}`), { phase: "degraded", sessionId, classification: "env-git-snapshot" }),
139
+ });
140
+ return { provider, featureFlags, userSystemPrompt, userAppendSystemPrompt, mcpInstructionsBlock, envFacts, tzValid, userTz, gitStatusRef };
141
+ }