@sema-agent/core 5.35.0 → 5.36.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.
- package/CHANGELOG.md +58 -0
- package/dist/agents/subagent.js +29 -2
- package/dist/core/auto-compaction.d.ts +23 -0
- package/dist/core/auto-compaction.js +8 -0
- package/dist/core/checkpoint-store.d.ts +16 -0
- package/dist/core/context-guard.d.ts +41 -0
- package/dist/core/context-guard.js +76 -0
- package/dist/core/memory-engine/engine.js +1 -1
- package/dist/core/park-selfcheck.d.ts +5 -0
- package/dist/core/runner/assemble-result.d.ts +3 -0
- package/dist/core/runner/assemble-result.js +3 -0
- package/dist/core/runner/git-status-frame.d.ts +219 -0
- package/dist/core/runner/git-status-frame.js +212 -0
- package/dist/core/runner/prepare-task.d.ts +16 -0
- package/dist/core/runner/prepare-task.js +27 -34
- package/dist/core/runner/runtask.js +266 -5
- package/dist/core/task-registry-agent.d.ts +15 -0
- package/dist/core/task-registry-agent.js +9 -0
- package/dist/core/task-registry.d.ts +3 -0
- package/dist/core/task-registry.js +4 -1
- package/dist/core/types.d.ts +27 -7
- package/dist/engine/harness/types.d.ts +65 -1
- package/dist/engine/harness/types.js +20 -0
- package/dist/engine/session/import-validate.js +10 -1
- package/dist/engine/session/session.d.ts +37 -1
- package/dist/engine/session/session.js +56 -1
- package/dist/internal/harness-types.d.ts +1 -0
- package/dist/internal/harness.d.ts +2 -0
- package/dist/internal/harness.js +2 -0
- package/dist/prompt-assembly/epoch.js +1 -1
- package/dist/prompt-assembly/event-registry.js +1 -0
- package/dist/prompts/default.d.ts +20 -7
- package/dist/prompts/default.js +2 -7
- package/package.json +1 -1
|
@@ -66,6 +66,26 @@ export class AgentHarnessError extends Error {
|
|
|
66
66
|
this.code = code;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
export const GIT_ANNOUNCEMENT_MAX_ENTRY_ID_CHARS = 256;
|
|
70
|
+
export function normalizeGitAnnouncement(v) {
|
|
71
|
+
if (typeof v !== "object" || v === null)
|
|
72
|
+
return undefined;
|
|
73
|
+
const src = v;
|
|
74
|
+
if (src.kind !== "full" && src.kind !== "degraded" && src.kind !== "unavailable" && src.kind !== "non-repo")
|
|
75
|
+
return undefined;
|
|
76
|
+
if (typeof src.hash !== "string" || !/^sha256:[0-9a-f]{64}$/.test(src.hash))
|
|
77
|
+
return undefined;
|
|
78
|
+
if (src.entryId !== undefined && (typeof src.entryId !== "string" || src.entryId.length === 0 || src.entryId.length > GIT_ANNOUNCEMENT_MAX_ENTRY_ID_CHARS))
|
|
79
|
+
return undefined;
|
|
80
|
+
if (src.pending !== undefined && src.pending !== true)
|
|
81
|
+
return undefined;
|
|
82
|
+
return {
|
|
83
|
+
kind: src.kind,
|
|
84
|
+
hash: src.hash,
|
|
85
|
+
...(src.entryId !== undefined ? { entryId: src.entryId } : {}),
|
|
86
|
+
...(src.pending === true ? { pending: true } : {}),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
69
89
|
export const WORKSPACE_STATE_MAX_PATH_CHARS = 4096;
|
|
70
90
|
export function normalizeWorkspaceState(value) {
|
|
71
91
|
if (value === null || typeof value !== "object")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SessionError, normalizeAnnouncedListing, normalizeWorkspaceState, normalizeCompactionStateCarrier, isValidThinkingLevelChange, isValidModelChange, isOptionalDisplayString, } from "../harness/types.js";
|
|
1
|
+
import { SessionError, normalizeAnnouncedListing, normalizeGitAnnouncement, normalizeWorkspaceState, normalizeCompactionStateCarrier, isValidThinkingLevelChange, isValidModelChange, isOptionalDisplayString, } from "../harness/types.js";
|
|
2
2
|
import { leafIdAfterEntry } from "./storage-base.js";
|
|
3
3
|
import { parseSessionTimestampMs } from "./timestamps.js";
|
|
4
4
|
import { flattenableUserText, normalizeEngineSegments } from "../../core/untrusted-text.js";
|
|
@@ -87,6 +87,11 @@ export class StreamingImportValidator {
|
|
|
87
87
|
throw new SessionError("invalid_session", `workspace_state entry "${e.id}" is structurally invalid`);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
else if (e.type === "git_announcement") {
|
|
91
|
+
if (!normalizeGitAnnouncement(e)) {
|
|
92
|
+
throw new SessionError("invalid_session", `git_announcement entry "${e.id}" is structurally invalid`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
90
95
|
if (e.type === "thinking_level_change" && !isValidThinkingLevelChange(e)) {
|
|
91
96
|
throw new SessionError("invalid_session", `thinking_level_change entry "${e.id}" carries an invalid thinkingLevel`);
|
|
92
97
|
}
|
|
@@ -119,6 +124,10 @@ export class StreamingImportValidator {
|
|
|
119
124
|
if (restatedListings !== undefined && !normalizeAnnouncedListing(restatedListings)) {
|
|
120
125
|
throw new SessionError("invalid_session", `compaction "${e.id}" carries a structurally invalid announcedListings restatement`);
|
|
121
126
|
}
|
|
127
|
+
const restatedGit = e.details?.gitAnnouncement;
|
|
128
|
+
if (restatedGit !== undefined && !normalizeGitAnnouncement(restatedGit)) {
|
|
129
|
+
throw new SessionError("invalid_session", `compaction "${e.id}" carries a structurally invalid gitAnnouncement restatement`);
|
|
130
|
+
}
|
|
122
131
|
const carrier = e.details;
|
|
123
132
|
const shaped = normalizeCompactionStateCarrier(e.details);
|
|
124
133
|
if (carrier?.thinkingLevel !== undefined && shaped.thinkingLevel === undefined) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ImageContent, TextContent } from "../llm/index.js";
|
|
2
2
|
import type { AgentMessage } from "../loop/types.js";
|
|
3
|
-
import type { Session, SessionContext, SessionMetadata, SessionStorage, SessionTreeEntry, WorkspaceState } from "../harness/types.js";
|
|
3
|
+
import type { GitAnnouncementState, Session, SessionContext, SessionMetadata, SessionStorage, SessionTreeEntry, WorkspaceState } from "../harness/types.js";
|
|
4
4
|
import type { BuildContextOptions } from "../harness/types.js";
|
|
5
5
|
import type { PromptEpochDescriptor } from "../../prompt-assembly/epoch.js";
|
|
6
6
|
/** Build model context from the active session branch and its latest state markers.
|
|
@@ -45,6 +45,31 @@ export declare class StoredSession<TMetadata extends SessionMetadata = SessionMe
|
|
|
45
45
|
skills?: readonly string[];
|
|
46
46
|
models?: readonly string[];
|
|
47
47
|
}): Promise<string>;
|
|
48
|
+
/**
|
|
49
|
+
* Git-status frame announced state (branch-authority mirror): persist the `(kind, hash)` tuple —
|
|
50
|
+
* announced form carries the frame's own entry id; a compaction restates the tuple as `pending`
|
|
51
|
+
* inside its `details.gitAnnouncement` instead (same-CAS with the new baseline). Called
|
|
52
|
+
* best-effort at every frame COMMIT point (append receipt in hand) — an append failure degrades
|
|
53
|
+
* to a conservative re-announcement on the next leg (duplicate-tolerant), never fails the run.
|
|
54
|
+
*/
|
|
55
|
+
appendGitAnnouncement(state: GitAnnouncementState): Promise<string>;
|
|
56
|
+
/**
|
|
57
|
+
* Branch-authority read of the git announced state: the nearest carrier walking back from the
|
|
58
|
+
* leaf (a `git_announcement` entry or a compaction's `details.gitAnnouncement` restatement;
|
|
59
|
+
* SNAPSHOT semantics — first hit wins). The pending gate is a MANDATORY one: a nearest carrier
|
|
60
|
+
* that is pending, or an announced carrier whose frame entryId is NOT on the active branch (a
|
|
61
|
+
* rewind cut the frame while a descendant mirror survived), reads as `status:"pending"` — the
|
|
62
|
+
* caller must conservatively re-announce rather than trust a mirror whose frame the model cannot
|
|
63
|
+
* see. Malformed carriers are skipped (defense in depth behind the import door). Undefined ⇒ no
|
|
64
|
+
* carrier visible on this branch (pre-migration session / bounded-tail floor cut every carrier) —
|
|
65
|
+
* callers fall back to the checkpoint mirror rung, then to conservative re-announcement.
|
|
66
|
+
*/
|
|
67
|
+
getGitAnnouncement(): Promise<{
|
|
68
|
+
kind: GitAnnouncementState["kind"];
|
|
69
|
+
hash: string;
|
|
70
|
+
status: "announced" | "pending";
|
|
71
|
+
entryId?: string;
|
|
72
|
+
} | undefined>;
|
|
48
73
|
/**
|
|
49
74
|
* design/155 (cli [1580]): persist the settle-time workspace state (tracked cwd + active
|
|
50
75
|
* EnterWorktree session) as a first-class typed entry. Called best-effort at task settle when the
|
|
@@ -115,3 +140,14 @@ export declare class StoredSession<TMetadata extends SessionMetadata = SessionMe
|
|
|
115
140
|
appendLabel(targetId: string, label: string | undefined): Promise<string>;
|
|
116
141
|
appendSessionName(name: string): Promise<string>;
|
|
117
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Falsification round 1 (F3) — is `entryId` not merely ON the active branch but inside the region
|
|
145
|
+
* a context rebuild can actually contain? A branch walk sees the whole tree; `buildSessionContext`
|
|
146
|
+
* replays only [latest compaction's firstKeptEntryId .. leaf] (plus the summary). A git frame that
|
|
147
|
+
* sits BEFORE the latest compaction's kept tail is therefore gone from every future request even
|
|
148
|
+
* though its id is a legitimate branch member — an announced mirror pointing at it must read as
|
|
149
|
+
* PENDING (re-announce), or the announced hash would suppress re-sends of bytes the model can no
|
|
150
|
+
* longer see. Shared by the session mirror walk and the run loop's checkpoint seeding rung (one
|
|
151
|
+
* rule, both readers). Conservative on damage: an unresolvable firstKeptEntryId reads as invisible.
|
|
152
|
+
*/
|
|
153
|
+
export declare function gitFrameContextVisible(branch: SessionTreeEntry[], entryId: string): boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { asAgentMessage, createCompactionSummaryMessage, createCustomMessage, } from "../harness/messages.js";
|
|
2
|
-
import { SessionError, isValidModelChange, normalizeAnnouncedListing, normalizeCompactionStateCarrier, normalizeWorkspaceState } from "../harness/types.js";
|
|
2
|
+
import { SessionError, isValidModelChange, normalizeAnnouncedListing, normalizeCompactionStateCarrier, normalizeGitAnnouncement, normalizeWorkspaceState } from "../harness/types.js";
|
|
3
3
|
import { normalizePromptEpoch } from "../../prompt-assembly/epoch.js";
|
|
4
4
|
import { budgetInvokedSkillsRetention, readElidedMessages, readRetainedInvokedSkills, renderInvokedSkillsRetention, } from "../compaction/utils.js";
|
|
5
5
|
const RETENTION_CLAMP_DEFAULT_CHARS_PER_TOKEN = 4;
|
|
@@ -181,6 +181,39 @@ export class StoredSession {
|
|
|
181
181
|
...shaped,
|
|
182
182
|
});
|
|
183
183
|
}
|
|
184
|
+
async appendGitAnnouncement(state) {
|
|
185
|
+
const shaped = normalizeGitAnnouncement(state);
|
|
186
|
+
if (!shaped) {
|
|
187
|
+
throw new SessionError("invalid_entry", "appendGitAnnouncement: state is not a structurally valid git announcement (closed kind, sha256 hash, bounded entryId)");
|
|
188
|
+
}
|
|
189
|
+
return this.appendTypedEntry({
|
|
190
|
+
type: "git_announcement",
|
|
191
|
+
id: await this.storage.createEntryId(),
|
|
192
|
+
parentId: await this.storage.getLeafId(),
|
|
193
|
+
timestamp: new Date().toISOString(),
|
|
194
|
+
...shaped,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
async getGitAnnouncement() {
|
|
198
|
+
const branch = await this.getBranch();
|
|
199
|
+
for (let i = branch.length - 1; i >= 0; i--) {
|
|
200
|
+
const entry = branch[i];
|
|
201
|
+
let carried;
|
|
202
|
+
if (entry.type === "git_announcement") {
|
|
203
|
+
carried = normalizeGitAnnouncement(entry);
|
|
204
|
+
}
|
|
205
|
+
else if (entry.type === "compaction") {
|
|
206
|
+
carried = normalizeGitAnnouncement(entry.details?.gitAnnouncement);
|
|
207
|
+
}
|
|
208
|
+
if (carried === undefined)
|
|
209
|
+
continue;
|
|
210
|
+
const announced = carried.pending !== true && carried.entryId !== undefined && gitFrameContextVisible(branch, carried.entryId);
|
|
211
|
+
return announced
|
|
212
|
+
? { kind: carried.kind, hash: carried.hash, status: "announced", entryId: carried.entryId }
|
|
213
|
+
: { kind: carried.kind, hash: carried.hash, status: "pending" };
|
|
214
|
+
}
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
184
217
|
async appendWorkspaceState(state) {
|
|
185
218
|
const shaped = normalizeWorkspaceState(state);
|
|
186
219
|
if (!shaped) {
|
|
@@ -314,3 +347,25 @@ export class StoredSession {
|
|
|
314
347
|
});
|
|
315
348
|
}
|
|
316
349
|
}
|
|
350
|
+
export function gitFrameContextVisible(branch, entryId) {
|
|
351
|
+
let frameIdx = -1;
|
|
352
|
+
let compactionIdx = -1;
|
|
353
|
+
let firstKeptId;
|
|
354
|
+
for (let i = 0; i < branch.length; i++) {
|
|
355
|
+
const e = branch[i];
|
|
356
|
+
if (e.id === entryId)
|
|
357
|
+
frameIdx = i;
|
|
358
|
+
if (e.type === "compaction") {
|
|
359
|
+
compactionIdx = i;
|
|
360
|
+
firstKeptId = e.firstKeptEntryId;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (frameIdx === -1)
|
|
364
|
+
return false;
|
|
365
|
+
if (compactionIdx === -1 || frameIdx > compactionIdx)
|
|
366
|
+
return true;
|
|
367
|
+
if (firstKeptId === undefined)
|
|
368
|
+
return false;
|
|
369
|
+
const keptIdx = branch.findIndex((e) => e.id === firstKeptId);
|
|
370
|
+
return keptIdx !== -1 && frameIdx >= keptIdx;
|
|
371
|
+
}
|
|
@@ -12,4 +12,5 @@ export type { AgentHarnessEvent, CompactionSettings, ExecutionEnv, ExecutionErro
|
|
|
12
12
|
export type { ExecutionEnvExecOptions, ExecResult } from "../engine/harness/types.js";
|
|
13
13
|
export type { SessionWriteOptions, CompactionEntry } from "../engine/harness/types.js";
|
|
14
14
|
export type { ActiveWorktreeSession, WorkspaceState } from "../engine/harness/types.js";
|
|
15
|
+
export type { GitAnnouncementKind, GitAnnouncementState } from "../engine/harness/types.js";
|
|
15
16
|
export type { SessionForkOptions } from "../engine/harness/types.js";
|
|
@@ -23,3 +23,5 @@ export { InMemorySessionStorage } from "../engine/session/memory-storage.js";
|
|
|
23
23
|
export { InMemorySessionRepo } from "../engine/session/memory-repo.js";
|
|
24
24
|
export { uuidv7 } from "../engine/session/uuid.js";
|
|
25
25
|
export { SessionError } from "../engine/harness/types.js";
|
|
26
|
+
export { normalizeGitAnnouncement } from "../engine/harness/types.js";
|
|
27
|
+
export { gitFrameContextVisible } from "../engine/session/session.js";
|
package/dist/internal/harness.js
CHANGED
|
@@ -12,3 +12,5 @@ export { InMemorySessionStorage } from "../engine/session/memory-storage.js";
|
|
|
12
12
|
export { InMemorySessionRepo } from "../engine/session/memory-repo.js";
|
|
13
13
|
export { uuidv7 } from "../engine/session/uuid.js";
|
|
14
14
|
export { SessionError } from "../engine/harness/types.js";
|
|
15
|
+
export { normalizeGitAnnouncement } from "../engine/harness/types.js";
|
|
16
|
+
export { gitFrameContextVisible } from "../engine/session/session.js";
|
|
@@ -50,7 +50,7 @@ const PROBE_INPUTS_BASE = {
|
|
|
50
50
|
roleBase: "\u0000probe-role\u0000",
|
|
51
51
|
roleAppend: "\u0000probe-append\u0000",
|
|
52
52
|
mcpInstructionsBlock: "\u0000probe-mcp\u0000",
|
|
53
|
-
environmentBlock: "\u0000probe-env\u0000",
|
|
53
|
+
environmentBlock: "\u0000probe-env@2\u0000",
|
|
54
54
|
memoryBlock: "\u0000probe-memory\u0000",
|
|
55
55
|
modelGuidance: "\u0000probe-guidance\u0000",
|
|
56
56
|
};
|
|
@@ -13,6 +13,7 @@ export const EVENT_PROMPT_REGISTRY = new Map([
|
|
|
13
13
|
{ kind: "skills_listing", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "on", rendererRef: "turn-attachments.ts#renderSkillsListingDelta" },
|
|
14
14
|
{ kind: "mcp_instructions", carrier: "message.user-prefix", trust: "external", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#renderMcpInstructionsDelta" },
|
|
15
15
|
{ kind: "mcp_dropped_tools", carrier: "message.user-prefix", trust: "external", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#renderMcpDroppedTools" },
|
|
16
|
+
{ kind: "git_status", carrier: "message.user-prefix", trust: "external", dedupe: "replace-by-key", defaultPolicy: "always", rendererRef: "git-status-frame.ts#renderGitStatusFrameBody" },
|
|
16
17
|
{ kind: "limit_approach_converge", carrier: "message.user-prefix", trust: "operator", dedupe: "once-per-run", defaultPolicy: "always", rendererRef: "runtask.ts#limitApproachFrames" },
|
|
17
18
|
{ kind: "limit_approach_deliver", carrier: "message.user-prefix", trust: "operator", dedupe: "once-per-run", defaultPolicy: "always", rendererRef: "runtask.ts#limitApproachFrames" },
|
|
18
19
|
{ kind: "final_verification", carrier: "message.user-prefix", trust: "operator", dedupe: "once-per-run", defaultPolicy: "always", rendererRef: "runtask.ts#final-verification" },
|
|
@@ -271,9 +271,13 @@ export interface EnvironmentFacts {
|
|
|
271
271
|
cwd?: string;
|
|
272
272
|
isGitRepo?: boolean;
|
|
273
273
|
/** design/99 §E14 — the current git branch (remote = the container's repo). `"HEAD (detached)"` when on a
|
|
274
|
-
* detached HEAD. Absent when not a git repo / no git. A prepare-time snapshot, like cwd (may go stale mid-run).
|
|
274
|
+
* detached HEAD. Absent when not a git repo / no git. A prepare-time snapshot, like cwd (may go stale mid-run).
|
|
275
|
+
* env-tail migration (#254 shape): NO LONGER RENDERED by {@link buildEnvironmentContext} — the
|
|
276
|
+
* branch is a turn-dynamic fact and rides the `git_status` frame instead; the field remains as a
|
|
277
|
+
* structured data carrier for callers that assembled it. */
|
|
275
278
|
gitBranch?: string;
|
|
276
|
-
/** design/99 §E14 — whether the working tree has uncommitted changes (`git status --porcelain` non-empty).
|
|
279
|
+
/** design/99 §E14 — whether the working tree has uncommitted changes (`git status --porcelain` non-empty).
|
|
280
|
+
* env-tail migration: NO LONGER RENDERED here (turn-dynamic; rides the `git_status` frame). */
|
|
277
281
|
gitDirty?: boolean;
|
|
278
282
|
/** design/99 §E14 — the working tree's toplevel (`git rev-parse --show-toplevel`). Lets the agent/shell know
|
|
279
283
|
* it may be in a linked worktree rather than the main checkout. */
|
|
@@ -319,10 +323,10 @@ export interface EnvironmentFacts {
|
|
|
319
323
|
/** A1 — outbound-network posture; `none` renders an explicit downloads-will-fail caveat (TB
|
|
320
324
|
* autopsy 2026-07-05: agents burned budget retrying downloads in a no-egress sandbox). */
|
|
321
325
|
sandboxEgress?: "none" | "allowlist" | "full";
|
|
322
|
-
/** H4 (CC 2.1.198 git snapshot) — the pre-rendered {@link buildGitSnapshot} block
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
+
/** H4 (CC 2.1.198 git snapshot) — the pre-rendered {@link buildGitSnapshot} block. env-tail
|
|
327
|
+
* migration (#254 shape): NO LONGER RENDERED by {@link buildEnvironmentContext} — the snapshot is
|
|
328
|
+
* the turn-dynamic git view and rides the `git_status` frame (re-sent only when the rendered view
|
|
329
|
+
* changes); the field remains as a structured data carrier for callers that assembled it. */
|
|
326
330
|
gitSnapshot?: string;
|
|
327
331
|
/** [1451] B-half — per-lane resume-continuity facts (deployment-supplied via `TaskSpec.envFacts.resumeFacts`,
|
|
328
332
|
* copied by prepare ONLY on a durable-resume leg). Each present field renders one honest sentence; absent
|
|
@@ -353,11 +357,20 @@ export interface EnvironmentFacts {
|
|
|
353
357
|
export declare function buildScratchpadSection(scratchpadDir: string): string;
|
|
354
358
|
/** CC 2.1.198 `Juo` — the git status truncation bound (chars) for {@link buildGitSnapshot}. */
|
|
355
359
|
export declare const GIT_STATUS_MAX_CHARS = 2000;
|
|
360
|
+
/** The CC-verbatim first paragraph of the git snapshot (CC 2.1.198 `Quo` head sentence). Kept as a
|
|
361
|
+
* named constant so the git-status FRAME renderer (the snapshot's carrier since the env-tail
|
|
362
|
+
* migration) can swap exactly this paragraph for its own update-semantics preamble — under the
|
|
363
|
+
* frame protocol the snapshot IS re-sent when the visible view changes, so the "will not update"
|
|
364
|
+
* claim would be false there. {@link buildGitSnapshot} itself still renders it byte-exact. */
|
|
365
|
+
export declare const GIT_SNAPSHOT_CC_PREAMBLE = "This is the git status at the start of the conversation. Note that this status is a snapshot in time, and will not update during the conversation.";
|
|
356
366
|
/**
|
|
357
367
|
* H4 — the CC 2.1.198 ENV git snapshot block (B1/B2; template + truncation copy verified against
|
|
358
368
|
* observed Claude Code v2.1.198 behavior). Pure renderer — the Runner gathers the raw values through the
|
|
359
369
|
* ExecutionEnv seam (remote = the container's repo) and calls this once per prepareTask (a durable
|
|
360
|
-
* resume re-prepares → re-snapshots, CC new-session 同型).
|
|
370
|
+
* resume re-prepares → re-snapshots, CC new-session 同型). env-tail migration (#254 shape): the
|
|
371
|
+
* rendered block now feeds the `git_status` TURN FRAME (src/core/runner/git-status-frame.ts, which
|
|
372
|
+
* swaps the {@link GIT_SNAPSHOT_CC_PREAMBLE} for an update-semantics preamble) instead of the
|
|
373
|
+
* `# Environment` system-prompt section — the template body itself stays CC-verbatim.
|
|
361
374
|
*
|
|
362
375
|
* Sanitization (deliberate CC deviation — CC injects raw): status/log/branch/user are REPO-controlled
|
|
363
376
|
* text (filenames, commit subjects, a hostile clone's config) landing in the TRUSTED prompt region →
|
package/dist/prompts/default.js
CHANGED
|
@@ -196,6 +196,7 @@ Only use \`/tmp\` if the user explicitly requests it.
|
|
|
196
196
|
The scratchpad directory is session-specific, isolated from the user's project, and can generally be used without permission prompts. Treat it as ephemeral — it may not survive a long suspension or a resume on a different worker; keep durable outputs in the working directory.`;
|
|
197
197
|
}
|
|
198
198
|
export const GIT_STATUS_MAX_CHARS = 2000;
|
|
199
|
+
export const GIT_SNAPSHOT_CC_PREAMBLE = "This is the git status at the start of the conversation. Note that this status is a snapshot in time, and will not update during the conversation.";
|
|
199
200
|
export function buildGitSnapshot(p) {
|
|
200
201
|
const tool = p.statusToolName ?? "Bash";
|
|
201
202
|
const status = sanitizeUntrustedText(p.status.trim());
|
|
@@ -205,7 +206,7 @@ export function buildGitSnapshot(p) {
|
|
|
205
206
|
`\n... (truncated because it exceeds 2k characters. If you need more information, run "git status" using ${tool})`
|
|
206
207
|
: status;
|
|
207
208
|
return [
|
|
208
|
-
|
|
209
|
+
GIT_SNAPSHOT_CC_PREAMBLE,
|
|
209
210
|
`Current branch: ${inlineUntrusted(p.branch)}`,
|
|
210
211
|
`Main branch (you will usually use this for PRs): ${inlineUntrusted(p.mainBranch)}`,
|
|
211
212
|
...(p.userName ? [`Git user: ${inlineUntrusted(p.userName)}`] : []),
|
|
@@ -255,10 +256,6 @@ export function buildEnvironmentContext(facts) {
|
|
|
255
256
|
}
|
|
256
257
|
if (facts.isGitRepo !== undefined)
|
|
257
258
|
lines.push(`Is a git repository: ${facts.isGitRepo ? "yes" : "no"}`);
|
|
258
|
-
if (facts.gitBranch)
|
|
259
|
-
lines.push(`Git branch: ${inlineUntrusted(facts.gitBranch)}`);
|
|
260
|
-
if (facts.gitDirty !== undefined)
|
|
261
|
-
lines.push(`Git working tree: ${facts.gitDirty ? "has uncommitted changes" : "clean"}`);
|
|
262
259
|
if (facts.gitWorktreeRoot)
|
|
263
260
|
lines.push(`Git worktree root: ${inlineUntrusted(facts.gitWorktreeRoot)}`);
|
|
264
261
|
if (facts.isLinkedWorktree) {
|
|
@@ -302,8 +299,6 @@ export function buildEnvironmentContext(facts) {
|
|
|
302
299
|
}
|
|
303
300
|
const base = lines.length > 0 ? `# Environment\n${lines.join("\n")}` : "";
|
|
304
301
|
const sections = [base, ...(facts.scratchpadDir ? [buildScratchpadSection(facts.scratchpadDir)] : [])].filter((s) => s.length > 0);
|
|
305
|
-
if (facts.gitSnapshot)
|
|
306
|
-
sections.push(facts.gitSnapshot);
|
|
307
302
|
return sections.join("\n\n");
|
|
308
303
|
}
|
|
309
304
|
export const CODE_AGENT_PROMPT = `You are a capable software-engineering agent that acts through tools.
|