@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.
- package/CHANGELOG.md +69 -0
- package/dist/agents/cascade.d.ts +2 -1
- package/dist/agents/peer-notices.d.ts +11 -1
- package/dist/agents/peer-session-drain.js +2 -0
- package/dist/agents/verify.d.ts +2 -1
- package/dist/core/ask-origin.d.ts +130 -0
- package/dist/core/ask-origin.js +35 -0
- package/dist/core/auto-mode-arming.d.ts +40 -1
- package/dist/core/auto-mode-arming.js +51 -3
- package/dist/core/auto-mode.d.ts +87 -10
- package/dist/core/auto-mode.js +34 -6
- package/dist/core/checkpoint-store.d.ts +41 -6
- package/dist/core/checkpoint-store.js +8 -0
- package/dist/core/hooks.d.ts +63 -19
- package/dist/core/hooks.js +37 -16
- package/dist/core/mcp.d.ts +47 -55
- package/dist/core/mcp.js +89 -31
- package/dist/core/park-selfcheck.js +3 -0
- package/dist/core/permission-rule-consent.d.ts +2 -11
- package/dist/core/permission-rule-consent.js +13 -62
- package/dist/core/permission-rule-org.d.ts +70 -54
- package/dist/core/permission-rule-org.js +47 -61
- package/dist/core/permission-rule-provider.d.ts +162 -0
- package/dist/core/permission-rule-provider.js +148 -0
- package/dist/core/permission-rule-session.d.ts +23 -19
- package/dist/core/permission-rule-session.js +5 -5
- package/dist/core/permission-rule-store.d.ts +46 -21
- package/dist/core/permission-rule-store.js +13 -6
- package/dist/core/permission-rule-sync.d.ts +2 -1
- package/dist/core/permission-rule-sync.js +11 -0
- package/dist/core/runner/assemble-result.d.ts +3 -2
- package/dist/core/runner/checkpoint-scope.d.ts +32 -0
- package/dist/core/runner/checkpoint-scope.js +4 -0
- package/dist/core/runner/contracts.d.ts +1878 -0
- package/dist/core/runner/contracts.js +1 -0
- package/dist/core/runner/denial-limit-arms.d.ts +57 -31
- package/dist/core/runner/denial-limit-arms.js +42 -17
- package/dist/core/runner/derived-route-fallback.d.ts +34 -0
- package/dist/core/runner/derived-route-fallback.js +16 -0
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +1 -1
- package/dist/core/runner/prepare-announce-once.d.ts +83 -0
- package/dist/core/runner/prepare-announce-once.js +105 -0
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +170 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +255 -0
- package/dist/core/runner/prepare-config-doors.d.ts +2 -10
- package/dist/core/runner/prepare-defer-classify.d.ts +86 -0
- package/dist/core/runner/prepare-defer-classify.js +107 -0
- package/dist/core/runner/prepare-delegation-surface.d.ts +104 -0
- package/dist/core/runner/prepare-delegation-surface.js +144 -0
- package/dist/core/runner/prepare-execution-env.d.ts +54 -0
- package/dist/core/runner/prepare-execution-env.js +86 -0
- package/dist/core/runner/prepare-file-history.d.ts +95 -0
- package/dist/core/runner/prepare-file-history.js +383 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +6 -8
- package/dist/core/runner/prepare-hands-readface.js +3 -3
- package/dist/core/runner/prepare-inherited-gate.d.ts +268 -0
- package/dist/core/runner/prepare-inherited-gate.js +266 -0
- package/dist/core/runner/prepare-listings.d.ts +77 -0
- package/dist/core/runner/prepare-listings.js +76 -0
- package/dist/core/runner/prepare-lsp.d.ts +55 -0
- package/dist/core/runner/prepare-lsp.js +27 -0
- package/dist/core/runner/prepare-memory.d.ts +1 -1
- package/dist/core/runner/prepare-offload-wrappers.d.ts +62 -0
- package/dist/core/runner/prepare-offload-wrappers.js +45 -0
- package/dist/core/runner/prepare-permission-rules.d.ts +132 -0
- package/dist/core/runner/prepare-permission-rules.js +140 -0
- package/dist/core/runner/prepare-project-context.d.ts +131 -0
- package/dist/core/runner/prepare-project-context.js +150 -0
- package/dist/core/runner/prepare-prompt-inputs.d.ts +138 -0
- package/dist/core/runner/prepare-prompt-inputs.js +141 -0
- package/dist/core/runner/prepare-protocol-tools.d.ts +91 -0
- package/dist/core/runner/prepare-protocol-tools.js +182 -0
- package/dist/core/runner/prepare-question-face.d.ts +119 -0
- package/dist/core/runner/prepare-question-face.js +83 -0
- package/dist/core/runner/prepare-run-refs.d.ts +89 -0
- package/dist/core/runner/prepare-run-refs.js +39 -0
- package/dist/core/runner/prepare-safety-scan.d.ts +3 -2
- package/dist/core/runner/prepare-task.d.ts +11 -1815
- package/dist/core/runner/prepare-task.js +138 -2542
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +111 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +219 -0
- package/dist/core/runner/prepare-wiring-manifest.d.ts +184 -0
- package/dist/core/runner/prepare-wiring-manifest.js +240 -0
- package/dist/core/runner/prepare-workspace-restore.d.ts +1 -27
- package/dist/core/runner/prepare-workspace-restore.js +1 -22
- package/dist/core/runner/rollback-stack.d.ts +32 -0
- package/dist/core/runner/rollback-stack.js +30 -0
- package/dist/core/runner/runtask.d.ts +11 -2
- package/dist/core/runner/runtask.js +27 -9
- package/dist/core/runner/workspace-path.d.ts +33 -0
- package/dist/core/runner/workspace-path.js +22 -0
- package/dist/core/sensitive-path-policy.d.ts +16 -0
- package/dist/core/sensitive-path-policy.js +1 -1
- package/dist/core/tool-policy.d.ts +57 -9
- package/dist/core/tool-policy.js +11 -0
- package/dist/core/types.d.ts +63 -51
- package/dist/core/wiring-manifest.d.ts +40 -3
- package/dist/core/wiring-manifest.js +4 -3
- package/dist/core/write-protect.d.ts +13 -2
- package/dist/core/write-protect.js +58 -29
- package/dist/engine/harness/types.d.ts +38 -16
- package/dist/engine/harness/types.js +25 -1
- package/dist/engine/session/session.d.ts +3 -11
- package/dist/index.d.ts +10 -6
- package/dist/index.js +9 -5
- package/dist/internal/harness.d.ts +1 -0
- package/dist/stores/file/adoption/adopt.d.ts +1 -1
- package/dist/stores/file/adoption/marker.d.ts +1 -1
- package/dist/stores/file/permission-rule-adopt.js +4 -3
- package/dist/stores/file/permission-rule-store.d.ts +65 -25
- package/dist/stores/file/permission-rule-store.js +215 -37
- package/dist/stores/file/task-list-store.d.ts +1 -1
- package/dist/tools/fs/read-face.d.ts +1 -1
- package/package.json +8 -2
- 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
|
+
}
|