@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
|
@@ -3,6 +3,8 @@ import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js"
|
|
|
3
3
|
import { AgentHarness, DEFAULT_COMPACTION_SETTINGS, uuidv7 } from "../../internal/harness.js";
|
|
4
4
|
import { snapshotActorAssertion } from "../../internal/llm.js";
|
|
5
5
|
import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, F012_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, remainingBudgetMicroUsd, readPendingSteerQueue, remainingTokens, LEGACY_PENDING_STEER_INPUT_ID, MAX_STEER_INPUT_ID_CHARS, validatePendingSteer, winnerFromOutcome, } from "../checkpoint-store.js";
|
|
6
|
+
import { GIT_STATUS_ECHO_PREVIEW, branchCarriesVisiblePositiveGitFrame, newestEngineGitFrame, stripGitStatusUnits } from "./git-status-frame.js";
|
|
7
|
+
import { gitFrameContextVisible, normalizeGitAnnouncement } from "../../internal/harness.js";
|
|
6
8
|
import { engineVersion } from "../version.js";
|
|
7
9
|
import { CONFIG_CATALOG_VERSION, declarationReasons, resolveEffectiveConfig } from "../../config/catalog.js";
|
|
8
10
|
import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
|
|
@@ -868,6 +870,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
868
870
|
workingFileAttachments: buildWorkingFileAttachments(spec, prepared),
|
|
869
871
|
...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
|
|
870
872
|
...runnerHooks.seamCCompactionOptions(prepared),
|
|
873
|
+
...gitRestateOption(prepared),
|
|
871
874
|
...windowSafetyOptions(event.model),
|
|
872
875
|
...runnerHooks.compactionHookOptions(spec, prepared.sessionId, passTrigger),
|
|
873
876
|
});
|
|
@@ -996,6 +999,96 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
996
999
|
};
|
|
997
1000
|
return onTurnBoundary;
|
|
998
1001
|
}
|
|
1002
|
+
function wrapGitFrame(body) {
|
|
1003
|
+
return `<system-reminder>\n${sanitizeUntrustedText(body)}\n</system-reminder>`;
|
|
1004
|
+
}
|
|
1005
|
+
async function resolveGitLegDelivery(prepared, cpMirror, report) {
|
|
1006
|
+
const ref = prepared.gitStatusRef;
|
|
1007
|
+
const frame = ref.frame;
|
|
1008
|
+
if (frame === undefined)
|
|
1009
|
+
return undefined;
|
|
1010
|
+
let prior;
|
|
1011
|
+
let pending = false;
|
|
1012
|
+
try {
|
|
1013
|
+
const branchReadout = await prepared.session.getGitAnnouncement?.();
|
|
1014
|
+
if (branchReadout !== undefined) {
|
|
1015
|
+
if (branchReadout.status === "pending") {
|
|
1016
|
+
pending = true;
|
|
1017
|
+
prior = { kind: branchReadout.kind, hash: branchReadout.hash };
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
prior = { kind: branchReadout.kind, hash: branchReadout.hash, ...(branchReadout.entryId !== undefined ? { entryId: branchReadout.entryId } : {}) };
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
else {
|
|
1024
|
+
const shaped = normalizeGitAnnouncement(cpMirror);
|
|
1025
|
+
if (shaped !== undefined) {
|
|
1026
|
+
if (shaped.pending === true || shaped.entryId === undefined) {
|
|
1027
|
+
pending = true;
|
|
1028
|
+
prior = { kind: shaped.kind, hash: shaped.hash };
|
|
1029
|
+
}
|
|
1030
|
+
else if (gitFrameContextVisible(await prepared.session.getBranch(), shaped.entryId)) {
|
|
1031
|
+
prior = { kind: shaped.kind, hash: shaped.hash, entryId: shaped.entryId };
|
|
1032
|
+
}
|
|
1033
|
+
else {
|
|
1034
|
+
pending = true;
|
|
1035
|
+
prior = { kind: shaped.kind, hash: shaped.hash };
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
catch (err) {
|
|
1041
|
+
pending = true;
|
|
1042
|
+
report(err instanceof Error ? err : new Error(String(err)));
|
|
1043
|
+
}
|
|
1044
|
+
if (prior !== undefined && !pending && prior.entryId !== undefined) {
|
|
1045
|
+
try {
|
|
1046
|
+
const newest = newestEngineGitFrame(await prepared.session.getBranch());
|
|
1047
|
+
if (newest === undefined || newest.entryId !== prior.entryId)
|
|
1048
|
+
pending = true;
|
|
1049
|
+
}
|
|
1050
|
+
catch (err) {
|
|
1051
|
+
pending = true;
|
|
1052
|
+
report(err instanceof Error ? err : new Error(String(err)));
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
if (prior !== undefined)
|
|
1056
|
+
ref.announced = pending ? { ...prior, pending: true } : { ...prior };
|
|
1057
|
+
const negative = frame.kind === "unavailable" || frame.kind === "non-repo";
|
|
1058
|
+
if (negative) {
|
|
1059
|
+
if (prior === undefined && !pending) {
|
|
1060
|
+
let mustDisown;
|
|
1061
|
+
try {
|
|
1062
|
+
mustDisown = branchCarriesVisiblePositiveGitFrame(await prepared.session.getBranch());
|
|
1063
|
+
}
|
|
1064
|
+
catch (err) {
|
|
1065
|
+
mustDisown = true;
|
|
1066
|
+
report(err instanceof Error ? err : new Error(String(err)));
|
|
1067
|
+
}
|
|
1068
|
+
return mustDisown ? frame.body : undefined;
|
|
1069
|
+
}
|
|
1070
|
+
if (prior !== undefined && !pending && prior.kind === frame.kind && prior.hash === frame.hash)
|
|
1071
|
+
return undefined;
|
|
1072
|
+
return frame.body;
|
|
1073
|
+
}
|
|
1074
|
+
if (!pending && prior !== undefined && prior.kind === frame.kind && prior.hash === frame.hash) {
|
|
1075
|
+
ref.protectedText = wrapGitFrame(frame.body);
|
|
1076
|
+
if (frame.kind === "full" && frame.shrunk !== undefined) {
|
|
1077
|
+
ref.wrappedShrink = { find: ref.protectedText, replace: wrapGitFrame(frame.shrunk.body) };
|
|
1078
|
+
}
|
|
1079
|
+
return undefined;
|
|
1080
|
+
}
|
|
1081
|
+
return frame.body;
|
|
1082
|
+
}
|
|
1083
|
+
function gitRestateOption(prepared) {
|
|
1084
|
+
const ref = prepared.gitStatusRef;
|
|
1085
|
+
if (ref.frame === undefined || ref.announced === undefined || ref.reassert === undefined)
|
|
1086
|
+
return {};
|
|
1087
|
+
const use = ref.overBudgetShrunk && ref.frame.shrunk !== undefined
|
|
1088
|
+
? { kind: "degraded", hash: ref.frame.shrunk.hash }
|
|
1089
|
+
: { kind: ref.frame.kind, hash: ref.frame.hash };
|
|
1090
|
+
return { gitRestate: { pending: use, land: ref.reassert } };
|
|
1091
|
+
}
|
|
999
1092
|
function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
1000
1093
|
const { spec, queue, internals, ident, parentToolCallId, subagentName, pushContent, emitCommitted, startedToolCallIds, toolStartAt, writeFamilyOf, toolLabels, postToolBatchHook, batchArgs } = deps;
|
|
1001
1094
|
const internalsNotifier = createSafeNotifier({
|
|
@@ -1040,6 +1133,19 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1040
1133
|
if (event.entryId !== undefined && (m.role === "user" || m.role === "assistant" || m.role === "toolResult")) {
|
|
1041
1134
|
emitCommitted(event.entryId, m.role, m.role === "toolResult" ? m.toolCallId : undefined);
|
|
1042
1135
|
}
|
|
1136
|
+
if (event.entryId !== undefined && m.role === "user" && prepared.gitStatusRef.pendingReceipt !== undefined) {
|
|
1137
|
+
const pr = prepared.gitStatusRef.pendingReceipt;
|
|
1138
|
+
const c = m.content;
|
|
1139
|
+
const text = typeof c === "string"
|
|
1140
|
+
? c
|
|
1141
|
+
: Array.isArray(c) && c.length >= 1 && c[0].type === "text"
|
|
1142
|
+
? c[0].text
|
|
1143
|
+
: undefined;
|
|
1144
|
+
if (text === pr.text) {
|
|
1145
|
+
delete prepared.gitStatusRef.pendingReceipt;
|
|
1146
|
+
pr.commit(event.entryId);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1043
1149
|
if (rs.degrade.degraded === undefined && m.role === "assistant") {
|
|
1044
1150
|
const deg = readDegradation(m);
|
|
1045
1151
|
if (deg)
|
|
@@ -1261,6 +1367,7 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1261
1367
|
type: "task_progress",
|
|
1262
1368
|
taskId: rs.telemetry.taskId,
|
|
1263
1369
|
...(internals?.delegationTaskType !== undefined ? { taskType: internals.delegationTaskType } : {}),
|
|
1370
|
+
...(internals?.cycleSeq !== undefined ? { seq: internals.cycleSeq } : {}),
|
|
1264
1371
|
...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
|
|
1265
1372
|
...(subagentName ? { name: subagentName } : {}),
|
|
1266
1373
|
usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart },
|
|
@@ -2175,12 +2282,12 @@ export class Runner {
|
|
|
2175
2282
|
return [];
|
|
2176
2283
|
const full = textOf(e.message);
|
|
2177
2284
|
if (m.engineMinted === true)
|
|
2178
|
-
return [full];
|
|
2285
|
+
return [stripGitStatusUnits(full)];
|
|
2179
2286
|
if (Array.isArray(m.engineSegments) && m.engineSegments.length > 0) {
|
|
2180
|
-
return m.engineSegments.map((s) => full.slice(Math.max(0, s.start), Math.max(0, s.end)));
|
|
2287
|
+
return m.engineSegments.map((s) => stripGitStatusUnits(full.slice(Math.max(0, s.start), Math.max(0, s.end))));
|
|
2181
2288
|
}
|
|
2182
2289
|
if (typeof m.enginePrefixChars === "number" && m.enginePrefixChars > 0)
|
|
2183
|
-
return [full.slice(0, m.enginePrefixChars)];
|
|
2290
|
+
return [stripGitStatusUnits(full.slice(0, m.enginePrefixChars))];
|
|
2184
2291
|
return [];
|
|
2185
2292
|
});
|
|
2186
2293
|
}
|
|
@@ -2692,6 +2799,7 @@ export class Runner {
|
|
|
2692
2799
|
workingFileAttachments: buildWorkingFileAttachments(spec, prepared),
|
|
2693
2800
|
...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
|
|
2694
2801
|
...this.seamCCompactionOptions(prepared),
|
|
2802
|
+
...gitRestateOption(prepared),
|
|
2695
2803
|
...windowSafetyOptions(prepared.harness.getModel()),
|
|
2696
2804
|
...this.compactionHookOptions(spec, prepared.sessionId, "forced"),
|
|
2697
2805
|
});
|
|
@@ -2762,6 +2870,80 @@ export class Runner {
|
|
|
2762
2870
|
recordCompactionReuse: (p, c) => this.recordCompactionReuse(p, c),
|
|
2763
2871
|
},
|
|
2764
2872
|
});
|
|
2873
|
+
prepared.gitStatusRef.reassert = async () => {
|
|
2874
|
+
const ref = prepared.gitStatusRef;
|
|
2875
|
+
const frame = ref.frame;
|
|
2876
|
+
if (frame === undefined)
|
|
2877
|
+
return;
|
|
2878
|
+
delete ref.mirrorOwed;
|
|
2879
|
+
const use = ref.overBudgetShrunk && frame.shrunk !== undefined
|
|
2880
|
+
? { kind: "degraded", body: frame.shrunk.body, hash: frame.shrunk.hash }
|
|
2881
|
+
: { kind: frame.kind, body: frame.body, hash: frame.hash };
|
|
2882
|
+
const wrapped = wrapGitFrame(use.body);
|
|
2883
|
+
try {
|
|
2884
|
+
const entryId = await prepared.session.appendMessage({
|
|
2885
|
+
role: "user",
|
|
2886
|
+
content: [{ type: "text", text: wrapped }],
|
|
2887
|
+
timestamp: Date.now(),
|
|
2888
|
+
engineMinted: true,
|
|
2889
|
+
});
|
|
2890
|
+
ref.announced = { kind: use.kind, hash: use.hash, entryId };
|
|
2891
|
+
ref.protectedText = wrapped;
|
|
2892
|
+
if (use.kind === "full" && frame.shrunk !== undefined) {
|
|
2893
|
+
ref.wrappedShrink = { find: wrapped, replace: wrapGitFrame(frame.shrunk.body) };
|
|
2894
|
+
}
|
|
2895
|
+
else {
|
|
2896
|
+
delete ref.wrappedShrink;
|
|
2897
|
+
}
|
|
2898
|
+
queue.push({ type: "steering_injected", source: "git_status", preview: GIT_STATUS_ECHO_PREVIEW[use.kind], ...ident() });
|
|
2899
|
+
rs.attach.attachmentsInjected += 1;
|
|
2900
|
+
try {
|
|
2901
|
+
await prepared.session.appendGitAnnouncement?.({ kind: use.kind, hash: use.hash, entryId });
|
|
2902
|
+
}
|
|
2903
|
+
catch (mirrorErr) {
|
|
2904
|
+
try {
|
|
2905
|
+
this.deps.onError?.(new Error(`git announcement mirror write failed (frame delivered; next leg re-announces): ${mirrorErr instanceof Error ? mirrorErr.message : String(mirrorErr)}`), { phase: "degraded", sessionId: prepared.sessionId });
|
|
2906
|
+
}
|
|
2907
|
+
catch {
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
catch (err) {
|
|
2912
|
+
ref.announced = { kind: use.kind, hash: use.hash, pending: true };
|
|
2913
|
+
try {
|
|
2914
|
+
this.deps.onError?.(new Error(`git status frame re-assert append failed (pending; retried at the next boundary): ${err instanceof Error ? err.message : String(err)}`), { phase: "degraded", sessionId: prepared.sessionId });
|
|
2915
|
+
}
|
|
2916
|
+
catch {
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
};
|
|
2920
|
+
const flushGitMirror = async () => {
|
|
2921
|
+
const owed = prepared.gitStatusRef.mirrorOwed;
|
|
2922
|
+
if (owed === undefined)
|
|
2923
|
+
return;
|
|
2924
|
+
if (prepared.gitStatusRef.overBudgetShrunk && owed.kind === "full") {
|
|
2925
|
+
delete prepared.gitStatusRef.mirrorOwed;
|
|
2926
|
+
return;
|
|
2927
|
+
}
|
|
2928
|
+
delete prepared.gitStatusRef.mirrorOwed;
|
|
2929
|
+
try {
|
|
2930
|
+
await prepared.session.appendGitAnnouncement?.(owed);
|
|
2931
|
+
}
|
|
2932
|
+
catch (mirrorErr) {
|
|
2933
|
+
prepared.gitStatusRef.mirrorOwed = owed;
|
|
2934
|
+
try {
|
|
2935
|
+
this.deps.onError?.(new Error(`git announcement mirror write failed (frame delivered; retried at the next serialization point): ${mirrorErr instanceof Error ? mirrorErr.message : String(mirrorErr)}`), { phase: "degraded", sessionId: prepared.sessionId });
|
|
2936
|
+
}
|
|
2937
|
+
catch {
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
};
|
|
2941
|
+
const unsubGitRetry = prepared.harness.on("turn_boundary", async () => {
|
|
2942
|
+
await flushGitMirror();
|
|
2943
|
+
if (prepared.gitStatusRef.announced?.pending === true)
|
|
2944
|
+
await prepared.gitStatusRef.reassert?.();
|
|
2945
|
+
return undefined;
|
|
2946
|
+
});
|
|
2765
2947
|
const unsubBoundary = prepared.harness.on("turn_boundary", onTurnBoundary);
|
|
2766
2948
|
const unsub = prepared.harness.subscribe((event) => {
|
|
2767
2949
|
switch (event.type) {
|
|
@@ -2839,6 +3021,25 @@ export class Runner {
|
|
|
2839
3021
|
continuation += "\n\n" + formatHookFeedback(renderOrphanedBackgroundTasks(orphans));
|
|
2840
3022
|
}
|
|
2841
3023
|
}
|
|
3024
|
+
let gitResumeDelivered;
|
|
3025
|
+
{
|
|
3026
|
+
const gitBody = await resolveGitLegDelivery(prepared, resume.cp.state.gitAnnouncement, (err) => {
|
|
3027
|
+
try {
|
|
3028
|
+
this.deps.onError?.(new Error(`git announcement ladder unreadable (conservative re-announce): ${err.message}`), { phase: "degraded", sessionId: prepared.sessionId });
|
|
3029
|
+
}
|
|
3030
|
+
catch {
|
|
3031
|
+
}
|
|
3032
|
+
});
|
|
3033
|
+
const gitFrame = prepared.gitStatusRef.frame;
|
|
3034
|
+
if (gitBody !== undefined && gitFrame !== undefined) {
|
|
3035
|
+
const wrappedGit = wrapGitFrame(gitBody);
|
|
3036
|
+
continuation += "\n\n" + wrappedGit;
|
|
3037
|
+
gitResumeDelivered = { kind: gitFrame.kind, hash: gitFrame.hash, wrapped: wrappedGit };
|
|
3038
|
+
if (gitFrame.kind === "full" && gitFrame.shrunk !== undefined) {
|
|
3039
|
+
prepared.gitStatusRef.wrappedShrink = { find: wrappedGit, replace: wrapGitFrame(gitFrame.shrunk.body) };
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
2842
3043
|
const engineSegments = continuation.length > 0 ? [{ start: 0, end: continuation.length }] : [];
|
|
2843
3044
|
const resumeFrames = [
|
|
2844
3045
|
...readPendingSteerQueue(resume.cp.state).map((entry) => ({ entry, source: "steer" })),
|
|
@@ -2868,7 +3069,23 @@ export class Runner {
|
|
|
2868
3069
|
}
|
|
2869
3070
|
if (resume !== undefined)
|
|
2870
3071
|
resume.decisionDelivered = true;
|
|
3072
|
+
if (gitResumeDelivered !== undefined) {
|
|
3073
|
+
const delivered = gitResumeDelivered;
|
|
3074
|
+
prepared.gitStatusRef.protectedText = delivered.wrapped;
|
|
3075
|
+
prepared.gitStatusRef.pendingReceipt = {
|
|
3076
|
+
text: continuation,
|
|
3077
|
+
commit: (entryId) => {
|
|
3078
|
+
prepared.gitStatusRef.announced = { kind: delivered.kind, hash: delivered.hash, entryId };
|
|
3079
|
+
queue.push({ type: "steering_injected", source: "git_status", preview: GIT_STATUS_ECHO_PREVIEW[delivered.kind], ...ident() });
|
|
3080
|
+
rs.attach.attachmentsInjected += 1;
|
|
3081
|
+
prepared.gitStatusRef.mirrorOwed = { kind: delivered.kind, hash: delivered.hash, entryId };
|
|
3082
|
+
},
|
|
3083
|
+
};
|
|
3084
|
+
}
|
|
2871
3085
|
final = await withBrainSinks(() => prepared.harness.prompt(continuation, { engineSegments }));
|
|
3086
|
+
await flushGitMirror();
|
|
3087
|
+
if (prepared.gitStatusRef.announced?.pending === true)
|
|
3088
|
+
await prepared.gitStatusRef.reassert?.();
|
|
2872
3089
|
}
|
|
2873
3090
|
}
|
|
2874
3091
|
else {
|
|
@@ -2900,6 +3117,27 @@ export class Runner {
|
|
|
2900
3117
|
promptBlocked = true;
|
|
2901
3118
|
}
|
|
2902
3119
|
}
|
|
3120
|
+
let gitLegDelivered;
|
|
3121
|
+
if (!promptBlocked) {
|
|
3122
|
+
const gitBody = await resolveGitLegDelivery(prepared, undefined, (err) => {
|
|
3123
|
+
try {
|
|
3124
|
+
this.deps.onError?.(new Error(`git announcement ladder unreadable (conservative re-announce): ${err.message}`), { phase: "degraded", sessionId: prepared.sessionId });
|
|
3125
|
+
}
|
|
3126
|
+
catch {
|
|
3127
|
+
}
|
|
3128
|
+
});
|
|
3129
|
+
const gitFrame = prepared.gitStatusRef.frame;
|
|
3130
|
+
if (gitBody !== undefined && gitFrame !== undefined) {
|
|
3131
|
+
const wrappedGit = wrapGitFrame(gitBody);
|
|
3132
|
+
const standalone = spec.images !== undefined && spec.images.length > 0;
|
|
3133
|
+
if (!standalone)
|
|
3134
|
+
effectiveObjective = `${wrappedGit}\n${effectiveObjective}`;
|
|
3135
|
+
gitLegDelivered = { kind: gitFrame.kind, hash: gitFrame.hash, standalone, wrapped: wrappedGit };
|
|
3136
|
+
if (gitFrame.kind === "full" && gitFrame.shrunk !== undefined) {
|
|
3137
|
+
prepared.gitStatusRef.wrappedShrink = { find: wrappedGit, replace: wrapGitFrame(gitFrame.shrunk.body) };
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
2903
3141
|
const firstFrames = [];
|
|
2904
3142
|
if (rs.attach.attachState !== undefined) {
|
|
2905
3143
|
const attach = rs.attach.attachState;
|
|
@@ -2937,17 +3175,18 @@ export class Runner {
|
|
|
2937
3175
|
effectiveObjective = `${firstFrames.map((f) => `<system-reminder>\n${sanitizeUntrustedText(f.body)}\n</system-reminder>`).join("\n")}\n${effectiveObjective}`;
|
|
2938
3176
|
}
|
|
2939
3177
|
}
|
|
3178
|
+
const gitQueuedChars = gitLegDelivered !== undefined && gitLegDelivered.standalone ? gitLegDelivered.wrapped.length : 0;
|
|
2940
3179
|
const precallMicroUsd = rs.budget.maxCostMicroUsd === undefined
|
|
2941
3180
|
? 0
|
|
2942
3181
|
: computeCostMicroUsd({
|
|
2943
|
-
totalInputTokens: Math.ceil(effectiveObjective.length / 4),
|
|
3182
|
+
totalInputTokens: Math.ceil((effectiveObjective.length + gitQueuedChars) / 4),
|
|
2944
3183
|
cacheReadTokens: 0,
|
|
2945
3184
|
cacheWriteTokens: 0,
|
|
2946
3185
|
cacheWriteTokensLong: 0,
|
|
2947
3186
|
outputTokens: prepared.model.maxTokens ?? DEFAULT_PRECALL_OUTPUT_TOKENS,
|
|
2948
3187
|
}, rs.telemetry.pricing);
|
|
2949
3188
|
const precallCeilingMicroUsd = prepared.suspendForResource !== undefined ? rs.budget.remainingMicroUsd : rs.budget.maxCostMicroUsd;
|
|
2950
|
-
const precallTokens = Math.ceil(effectiveObjective.length / 4) + (prepared.model.maxTokens ?? DEFAULT_PRECALL_OUTPUT_TOKENS);
|
|
3189
|
+
const precallTokens = Math.ceil((effectiveObjective.length + gitQueuedChars) / 4) + (prepared.model.maxTokens ?? DEFAULT_PRECALL_OUTPUT_TOKENS);
|
|
2951
3190
|
const precallCeilingTokens = prepared.suspendForResource !== undefined ? rs.budget.remainingTokens : rs.budget.maxTokensWindow;
|
|
2952
3191
|
const entryUsageRetryAfterMs = prepared.usageGovernance !== undefined ? await prepared.usageGovernance.check(Date.now()) : undefined;
|
|
2953
3192
|
if (promptBlocked) {
|
|
@@ -2979,6 +3218,21 @@ export class Runner {
|
|
|
2979
3218
|
const images = spec.images
|
|
2980
3219
|
? await Promise.all(spec.images.map((img) => toImageContent(img, this.deps.allowImageUrl)))
|
|
2981
3220
|
: undefined;
|
|
3221
|
+
if (gitLegDelivered !== undefined) {
|
|
3222
|
+
const delivered = gitLegDelivered;
|
|
3223
|
+
if (delivered.standalone)
|
|
3224
|
+
await prepared.harness.nextTurn(delivered.wrapped, { engineMinted: true });
|
|
3225
|
+
prepared.gitStatusRef.protectedText = delivered.wrapped;
|
|
3226
|
+
prepared.gitStatusRef.pendingReceipt = {
|
|
3227
|
+
text: delivered.standalone ? delivered.wrapped : effectiveObjective,
|
|
3228
|
+
commit: (entryId) => {
|
|
3229
|
+
prepared.gitStatusRef.announced = { kind: delivered.kind, hash: delivered.hash, entryId };
|
|
3230
|
+
queue.push({ type: "steering_injected", source: "git_status", preview: GIT_STATUS_ECHO_PREVIEW[delivered.kind], ...ident() });
|
|
3231
|
+
rs.attach.attachmentsInjected += 1;
|
|
3232
|
+
prepared.gitStatusRef.mirrorOwed = { kind: delivered.kind, hash: delivered.hash, entryId };
|
|
3233
|
+
},
|
|
3234
|
+
};
|
|
3235
|
+
}
|
|
2982
3236
|
for (const f of firstFrames) {
|
|
2983
3237
|
f.commit();
|
|
2984
3238
|
queue.push({ type: "steering_injected", source: f.source, preview: f.body.slice(0, 220), ...ident() });
|
|
@@ -3004,6 +3258,9 @@ export class Runner {
|
|
|
3004
3258
|
...(enginePrefixChars > 0 ? { enginePrefixChars } : {}),
|
|
3005
3259
|
...(objectiveActor !== undefined ? { actor: objectiveActor } : {}),
|
|
3006
3260
|
}));
|
|
3261
|
+
await flushGitMirror();
|
|
3262
|
+
if (prepared.gitStatusRef.announced?.pending === true)
|
|
3263
|
+
await prepared.gitStatusRef.reassert?.();
|
|
3007
3264
|
}
|
|
3008
3265
|
}
|
|
3009
3266
|
loopLatch.ended = true;
|
|
@@ -3033,6 +3290,7 @@ export class Runner {
|
|
|
3033
3290
|
}
|
|
3034
3291
|
notificationLaneLive = false;
|
|
3035
3292
|
unsubscribeTaskNotifications();
|
|
3293
|
+
unsubGitRetry();
|
|
3036
3294
|
unsubBoundary();
|
|
3037
3295
|
unsub();
|
|
3038
3296
|
}
|
|
@@ -3176,6 +3434,7 @@ export class Runner {
|
|
|
3176
3434
|
budgetAxis: rs.limits.budgetAxis,
|
|
3177
3435
|
blockedReason: prepared.blockedRef.reason,
|
|
3178
3436
|
conflict: prepared.conflictRef.hit,
|
|
3437
|
+
gitCoreOverBudget: prepared.gitStatusRef.terminalCode === "irreducible_core_over_budget",
|
|
3179
3438
|
outputInvalid: rs.degrade.outputInvalid,
|
|
3180
3439
|
suspendLoop: prepared.suspendLoopRef.hit,
|
|
3181
3440
|
suspendRef: prepared.suspendRef.token !== undefined && prepared.suspendRef.gate !== undefined
|
|
@@ -3304,6 +3563,7 @@ export class Runner {
|
|
|
3304
3563
|
type: "task_progress",
|
|
3305
3564
|
taskId: rs.telemetry.taskId,
|
|
3306
3565
|
...(internals?.delegationTaskType !== undefined ? { taskType: internals.delegationTaskType } : {}),
|
|
3566
|
+
...(internals?.cycleSeq !== undefined ? { seq: internals.cycleSeq } : {}),
|
|
3307
3567
|
...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
|
|
3308
3568
|
...(subagentName ? { name: subagentName } : {}),
|
|
3309
3569
|
usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart },
|
|
@@ -4316,6 +4576,7 @@ export class Runner {
|
|
|
4316
4576
|
workingFileAttachments: buildWorkingFileAttachments(spec, prepared),
|
|
4317
4577
|
...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
|
|
4318
4578
|
...this.seamCCompactionOptions(prepared),
|
|
4579
|
+
...gitRestateOption(prepared),
|
|
4319
4580
|
...this.compactionHookOptions(spec, prepared.sessionId, "auto"),
|
|
4320
4581
|
});
|
|
4321
4582
|
this.recordCompactionReuse(prepared, finishComp);
|
|
@@ -275,6 +275,21 @@ export declare function resolveBackgroundAgentByNameLane(core: DurableAgentCore,
|
|
|
275
275
|
* alone does NOT retain the child session, so declaring at register time acknowledged parks that
|
|
276
276
|
* a capacity/pin failure (or plain session-scope) could never drain. No-op on unknown ids. */
|
|
277
277
|
export declare function markRetainedContinuationLane(core: DurableAgentCore, id: string): void;
|
|
278
|
+
/**
|
|
279
|
+
* #258 — the stop-cycle generation of the RUN BEING SPAWNED under this row (the same counter the
|
|
280
|
+
* register/revive lanes keep on the handle: fresh spawn = 1, durable-seeded revive = the claimed
|
|
281
|
+
* row's seq, in-memory wake = the bump). The spawn lanes read it ONCE, right after registering, to
|
|
282
|
+
* thread into the child's `RunInternals.cycleSeq` — one authoritative source instead of each lane
|
|
283
|
+
* re-deriving the seed-vs-fresh arithmetic. `undefined` for an unknown id or a non-agent row.
|
|
284
|
+
*
|
|
285
|
+
* PARKED-BORN arm (falsification F1): a parked handle's counter still names the cycle that PARKED —
|
|
286
|
+
* the run this spawn lane is about to drive begins at the consume flip, which installs the bumped
|
|
287
|
+
* value (`flipped.seq = seq + 1`, the §7.2d consume write). Answering the pre-flip number made one
|
|
288
|
+
* resumed run speak two generations (spawn/ticks at n, terminal at n+1), so the parked arm answers
|
|
289
|
+
* the flip's target instead. A resume that loses the consume arbitration never runs, so the answer
|
|
290
|
+
* is never attached to a live cycle it doesn't describe.
|
|
291
|
+
*/
|
|
292
|
+
export declare function backgroundAgentCycleSeqLane(core: DurableAgentCore, id: string): number | undefined;
|
|
278
293
|
/** S2b RB-27② — REVIVE a settled background-agent row for a retained-session RESUME cycle: the
|
|
279
294
|
* row flips back to "running" with a fresh "attaching" channel and a bumped revive-cycle stamp
|
|
280
295
|
* (returned; the resume leg threads it through attach and settle so a stale cycle's late calls
|
|
@@ -867,6 +867,12 @@ export function markRetainedContinuationLane(core, id) {
|
|
|
867
867
|
if (handle && handle.type === "background_agent")
|
|
868
868
|
handle.retainedContinuation = true;
|
|
869
869
|
}
|
|
870
|
+
export function backgroundAgentCycleSeqLane(core, id) {
|
|
871
|
+
const handle = core.handles.get(id);
|
|
872
|
+
if (handle === undefined || handle.type !== "background_agent")
|
|
873
|
+
return undefined;
|
|
874
|
+
return handle.status === "parked" ? (handle.cycleSeq ?? 1) + 1 : handle.cycleSeq;
|
|
875
|
+
}
|
|
870
876
|
async function claimTerminalRowForRevive(core, store, handle, scope) {
|
|
871
877
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
872
878
|
let live;
|
|
@@ -972,6 +978,9 @@ export async function reviveBackgroundAgentLane(core, id, access, abort) {
|
|
|
972
978
|
return { ok: false, reason: "still_running" };
|
|
973
979
|
}
|
|
974
980
|
ensureDurableHeartbeatLane(core);
|
|
981
|
+
if (claim.status === "claimed" && typeof claim.row.seq === "number" && (handle.cycleSeq ?? 1) < claim.row.seq) {
|
|
982
|
+
handle.cycleSeq = claim.row.seq;
|
|
983
|
+
}
|
|
975
984
|
}
|
|
976
985
|
handle.status = "running";
|
|
977
986
|
handle.channelState = "attaching";
|
|
@@ -267,6 +267,9 @@ export declare class TaskRegistry {
|
|
|
267
267
|
suggestion?: string;
|
|
268
268
|
};
|
|
269
269
|
markRetainedContinuation(id: string): void;
|
|
270
|
+
/** #258 — the row's current stop-cycle counter, read by the spawn lanes right after registering to
|
|
271
|
+
* thread into the child's `RunInternals.cycleSeq`; see {@link backgroundAgentCycleSeqLane}. */
|
|
272
|
+
backgroundAgentCycleSeq(id: string): number | undefined;
|
|
270
273
|
/** ASYNC (revive arbitration) — the durable half is a guarded ownership claim on the row (awaited before the
|
|
271
274
|
* in-memory flip), so this leg and a cross-process claim arbitrate in one domain instead of both
|
|
272
275
|
* believing they own the cycle. See {@link reviveBackgroundAgentLane}. */
|
|
@@ -10,7 +10,7 @@ import { registerWorkflowLane, pollWorkflowLane, stopWorkflowLane } from "./task
|
|
|
10
10
|
import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, renderSpoolBody, spoolDropNote, droppedGapNote, alreadyTerminalStopNote, terminalTaskSummary, TASK_OUTPUT_MAX_CHARS, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccess, DURABLE_AGENT_HANDLE_RE, } from "./task-registry-shared.js";
|
|
11
11
|
export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR } from "./task-registry-shared.js";
|
|
12
12
|
import { TASK_OUTPUT_TOOL_NAME, TASK_STOP_TOOL_NAME, TASK_OUTPUT_CONTRACT, TASK_STOP_CONTRACT, TASK_OUTPUT_MISSING_ID_MESSAGE, TASK_STOP_MISSING_ID_MESSAGE, TASK_STOP_PARAMS, resolveTaskIdArg, REGISTRY_TASK_TOOL_CAPS, composeTaskOutputDescription, composeTaskOutputParams, composeTaskStopDescription, } from "./task-tool-shape.js";
|
|
13
|
-
import { durableAgentArmedLane, durableAgentRowProbeLane, beginDurableClaimLane, endDurableClaimLane, reapDurableAgentsLane, noteBackgroundAgentActivityLane, reapStaleSessionBackgroundAgentsLane, releaseDurableTranscriptAnchorLane, bindBackgroundAgentSessionLane, registerBackgroundAgentLane, recordBackgroundAgentOrgAdmissionLane, parkBackgroundAgentLane, reconcileParkedAgentsLane, claimParkedAgentLane, rollbackParkedClaimLane, consumeParkedFlipLane, finalizeParkedResumeLane, settleBackgroundAgentLane, abortBackgroundAgentsForOwnerLane, serveDurableAgentRowLane, resolveBackgroundAgentByNameLane, markRetainedContinuationLane, reviveBackgroundAgentLane, settleRevivedAgentLane, unmarkRetainedContinuationLane, attachAgentNotifyLane, deliverToRunningAgentLane, runningBackgroundAgentLabelsLane, runningAgentFooterLane, notFoundRunningAgentsTail, pollBackgroundAgentLane, stopBackgroundAgentLane, } from "./task-registry-agent.js";
|
|
13
|
+
import { durableAgentArmedLane, durableAgentRowProbeLane, beginDurableClaimLane, endDurableClaimLane, reapDurableAgentsLane, noteBackgroundAgentActivityLane, reapStaleSessionBackgroundAgentsLane, releaseDurableTranscriptAnchorLane, bindBackgroundAgentSessionLane, registerBackgroundAgentLane, recordBackgroundAgentOrgAdmissionLane, parkBackgroundAgentLane, reconcileParkedAgentsLane, claimParkedAgentLane, rollbackParkedClaimLane, consumeParkedFlipLane, finalizeParkedResumeLane, settleBackgroundAgentLane, abortBackgroundAgentsForOwnerLane, serveDurableAgentRowLane, resolveBackgroundAgentByNameLane, backgroundAgentCycleSeqLane, markRetainedContinuationLane, reviveBackgroundAgentLane, settleRevivedAgentLane, unmarkRetainedContinuationLane, attachAgentNotifyLane, deliverToRunningAgentLane, runningBackgroundAgentLabelsLane, runningAgentFooterLane, notFoundRunningAgentsTail, pollBackgroundAgentLane, stopBackgroundAgentLane, } from "./task-registry-agent.js";
|
|
14
14
|
export { canAccessWorkflowRun, clipTaskOutput, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE };
|
|
15
15
|
const BLOCK_DEFAULT_TIMEOUT_MS = 30_000;
|
|
16
16
|
const BLOCK_MAX_TIMEOUT_MS = 600_000;
|
|
@@ -191,6 +191,9 @@ export class TaskRegistry {
|
|
|
191
191
|
markRetainedContinuation(id) {
|
|
192
192
|
return markRetainedContinuationLane(this.core, id);
|
|
193
193
|
}
|
|
194
|
+
backgroundAgentCycleSeq(id) {
|
|
195
|
+
return backgroundAgentCycleSeqLane(this.core, id);
|
|
196
|
+
}
|
|
194
197
|
reviveBackgroundAgent(id, access, abort) {
|
|
195
198
|
return reviveBackgroundAgentLane(this.core, id, access, abort);
|
|
196
199
|
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -3647,9 +3647,15 @@ export type TaskEvent = ({
|
|
|
3647
3647
|
* (one event per attachment; a multi-attachment boundary still coalesces into ONE steer message
|
|
3648
3648
|
* on the model lane). [c209-C]: the listing family's FIRST-FRAME deliveries (initial roster /
|
|
3649
3649
|
* `<skills>` block riding the first user message, not a steer) emit the same echo frames.
|
|
3650
|
+
*
|
|
3651
|
+
* `git_status` (env-tail migration, additive member): the git-status frame — the turn-dynamic
|
|
3652
|
+
* git facts' carrier since they left the system prompt. Emitted at the frame's append RECEIPT
|
|
3653
|
+
* (first-frame / resume-continuation / compaction re-assert deliveries alike). Its `preview`
|
|
3654
|
+
* is a CONSTANT wording on purpose (never frame bytes): branch names and status text are
|
|
3655
|
+
* repo-controlled and must not enter the event telemetry plane through this echo.
|
|
3650
3656
|
*/
|
|
3651
3657
|
type: "steering_injected";
|
|
3652
|
-
source: "limit_approach" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools" | "final_verification";
|
|
3658
|
+
source: "limit_approach" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools" | "final_verification" | "git_status";
|
|
3653
3659
|
preview: string;
|
|
3654
3660
|
} & TaskEventIdentity) | ({
|
|
3655
3661
|
/**
|
|
@@ -3790,6 +3796,18 @@ export type TaskEvent = ({
|
|
|
3790
3796
|
* read absence as "not an agent".
|
|
3791
3797
|
*/
|
|
3792
3798
|
taskType?: DelegationTaskType;
|
|
3799
|
+
/**
|
|
3800
|
+
* #258 — the registry row's STOP-CYCLE generation this tick reports from (fresh
|
|
3801
|
+
* spawn = 1, every launched revival bumps it), the same counter `TaskNotificationPayload.seq`
|
|
3802
|
+
* and `BackgroundChildEvent.seq` already speak — one axis, not a third spelling. It answers
|
|
3803
|
+
* the one question a fleet consumer cannot otherwise decide when a frame arrives late: "late
|
|
3804
|
+
* first frame of the cycle I know (same value), or a revived run I have not folded yet
|
|
3805
|
+
* (higher value)?" Advisory observation, stamped at spawn/revive from the registry's own
|
|
3806
|
+
* counter; the settle-time ledger stays the authority. ABSENT is a fact, not a gap: a run
|
|
3807
|
+
* with no `a*` registry row (a SYNCHRONOUS delegated child, a workflow `wa*` agent, a
|
|
3808
|
+
* top-level run) has no generation concept, and absence must never be read as "cycle 1".
|
|
3809
|
+
*/
|
|
3810
|
+
seq?: number;
|
|
3793
3811
|
/** [1611] workflow-lane self-identification (server field-proof: the SSE-forwarded tick of a
|
|
3794
3812
|
* WORKFLOW child previously carried a bare uuid with no workflow identity — indistinguishable
|
|
3795
3813
|
* from an unknown nested subagent; the fleet lane had `wa*`+workflowRunId but this lane had
|
|
@@ -4294,12 +4312,14 @@ export interface BackgroundChildEvent {
|
|
|
4294
4312
|
resumable?: boolean;
|
|
4295
4313
|
/** terminal: the settled status. */
|
|
4296
4314
|
status?: "completed" | "killed" | "failed";
|
|
4297
|
-
/** terminal (
|
|
4298
|
-
* task_notification's `TaskNotificationPayload.seq` (same settle, same X3
|
|
4299
|
-
*
|
|
4300
|
-
*
|
|
4301
|
-
*
|
|
4302
|
-
*
|
|
4315
|
+
/** terminal + spawn/tick (#258 widened the carriers): the stop-cycle number — on a TERMINAL frame
|
|
4316
|
+
* a MIRROR of the sibling task_notification's `TaskNotificationPayload.seq` (same settle, same X3
|
|
4317
|
+
* snapshot); on SPAWN and TICK frames the registry row's generation at emit (fresh spawn = 1, a
|
|
4318
|
+
* revived cycle's bumped counter), so a fleet consumer can tell a LATE first frame from a revived
|
|
4319
|
+
* cycle's frame without waiting for the terminal. One axis with `task_progress`'s `seq` — the tick
|
|
4320
|
+
* mirrors the frame's own stamp. Same honest downgrade as ever: present whenever a cycle number is
|
|
4321
|
+
* knowable (the registry handle / retain ledger's `cycleSeq`, the durable row's `seq` on a tier-3
|
|
4322
|
+
* revival / parked resume), absent when no carrier exists (forging a period would lie). */
|
|
4303
4323
|
seq?: number;
|
|
4304
4324
|
/** terminal, P1-3(黑板 [1920]/[1921]/[1924]/[1925]): the cross-channel completion correlation id —
|
|
4305
4325
|
* MIRROR of the sibling task_notification's `TaskNotificationPayload.completionId` (same settle,
|
|
@@ -498,6 +498,53 @@ export interface WorkspaceState {
|
|
|
498
498
|
export interface WorkspaceStateEntry extends SessionTreeEntryBase, WorkspaceState {
|
|
499
499
|
type: "workspace_state";
|
|
500
500
|
}
|
|
501
|
+
/** The closed kind set of a git-status announcement (the availability half of the `(kind, hash)`
|
|
502
|
+
* comparison tuple): `full` = five-segment snapshot frame; `degraded` = branch+dirty two-line frame
|
|
503
|
+
* (the snapshot round-trip failed while the basic probe succeeded); `unavailable` / `non-repo` =
|
|
504
|
+
* tombstone frames announcing that earlier git frames no longer describe the tree. */
|
|
505
|
+
export type GitAnnouncementKind = "full" | "degraded" | "unavailable" | "non-repo";
|
|
506
|
+
/**
|
|
507
|
+
* The git-status frame ANNOUNCED STATE — which rendered git view the model has last been shown on
|
|
508
|
+
* this branch, as a `(kind, hash)` tuple (hash = content hash of the rendered frame body with the
|
|
509
|
+
* frame format version and the canonical repo root bound into the digest domain). Two-phase receipt
|
|
510
|
+
* protocol: `pending: true` = a re-announcement is OWED but its frame append has not produced a
|
|
511
|
+
* receipt (a compaction restates the tuple as pending in the same CAS that lands the summary; the
|
|
512
|
+
* announced form is written only once the frame's own append returned an entry id) — any reader
|
|
513
|
+
* that finds pending nearest MUST conservatively re-announce. `entryId` = the session entry of the
|
|
514
|
+
* message CARRYING the announced frame; an announced state whose entryId is not on the active
|
|
515
|
+
* branch (rewind cut it) is treated as pending by the read walk (branch-authority read ladder).
|
|
516
|
+
*/
|
|
517
|
+
export interface GitAnnouncementState {
|
|
518
|
+
kind: GitAnnouncementKind;
|
|
519
|
+
hash: string;
|
|
520
|
+
/** Session entry id of the message carrying the announced frame (absent on a pending restatement). */
|
|
521
|
+
entryId?: string;
|
|
522
|
+
/** Two-phase receipt: the tuple is owed but its frame append has no receipt yet. */
|
|
523
|
+
pending?: true;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* The git-status announced state as a FIRST-CLASS typed entry (same snapshot doctrine as
|
|
527
|
+
* {@link AnnouncedListingEntry}): the nearest carrier on the branch IS the announced state.
|
|
528
|
+
* Written at every frame COMMIT point (first-frame receipt / resume-continuation receipt /
|
|
529
|
+
* compaction re-assertion receipt); a compaction additionally restates the tuple as `pending`
|
|
530
|
+
* inside its own `details.gitAnnouncement` (same-CAS with the new baseline) until the re-asserted
|
|
531
|
+
* frame's append receipt lands. The checkpoint mirror (`CheckpointState.gitAnnouncement`) is the
|
|
532
|
+
* lower seed rung, consulted only when the branch carries no mirror at all.
|
|
533
|
+
*/
|
|
534
|
+
export interface GitAnnouncementEntry extends SessionTreeEntryBase, GitAnnouncementState {
|
|
535
|
+
type: "git_announcement";
|
|
536
|
+
}
|
|
537
|
+
/** Bounded cap for {@link normalizeGitAnnouncement} entry ids (self-generated ids are short; an
|
|
538
|
+
* oversize value is a forgery signal, same posture as the sibling caps). */
|
|
539
|
+
export declare const GIT_ANNOUNCEMENT_MAX_ENTRY_ID_CHARS = 256;
|
|
540
|
+
/**
|
|
541
|
+
* Strict shape gate for the git announced state — the SINGLE normalization the read walk, the
|
|
542
|
+
* public append, and the import-validate door all use (the {@link normalizeAnnouncedListing}
|
|
543
|
+
* posture). Returns a shaped copy carrying ONLY the known keys, or undefined when the value is not
|
|
544
|
+
* structurally valid: closed kind set, full `sha256:<hex64>` hash grammar (a prefix-only check
|
|
545
|
+
* would accept junk), bounded entryId, `pending` only as literal `true`.
|
|
546
|
+
*/
|
|
547
|
+
export declare function normalizeGitAnnouncement(v: unknown): GitAnnouncementState | undefined;
|
|
501
548
|
/** Bounded cap for {@link normalizeWorkspaceState} paths (a sha is capped by its hex shape check). */
|
|
502
549
|
export declare const WORKSPACE_STATE_MAX_PATH_CHARS = 4096;
|
|
503
550
|
/**
|
|
@@ -544,7 +591,7 @@ export declare function normalizeAnnouncedListing(v: unknown): {
|
|
|
544
591
|
models?: string[];
|
|
545
592
|
} | undefined;
|
|
546
593
|
/** All persisted session tree entry variants. */
|
|
547
|
-
export type SessionTreeEntry = MessageEntry | ThinkingLevelChangeEntry | ModelChangeEntry | CompactionEntry | CustomEntry | CustomMessageEntry | LabelEntry | SessionInfoEntry | LeafEntry | PromptEpochEntry | AnnouncedListingEntry | WorkspaceStateEntry;
|
|
594
|
+
export type SessionTreeEntry = MessageEntry | ThinkingLevelChangeEntry | ModelChangeEntry | CompactionEntry | CustomEntry | CustomMessageEntry | LabelEntry | SessionInfoEntry | LeafEntry | PromptEpochEntry | AnnouncedListingEntry | GitAnnouncementEntry | WorkspaceStateEntry;
|
|
548
595
|
export interface SessionContext {
|
|
549
596
|
messages: AgentMessage[];
|
|
550
597
|
thinkingLevel: string;
|
|
@@ -684,6 +731,23 @@ export interface Session<TMetadata extends SessionMetadata = SessionMetadata> {
|
|
|
684
731
|
* epoch restatement) calls it with `?.()` — an implementer without it degrades to the pre-epoch
|
|
685
732
|
* path (no restatement), never a crash. */
|
|
686
733
|
getPromptEpoch?(): Promise<import("../../prompt-assembly/epoch.js").PromptEpochDescriptor | undefined>;
|
|
734
|
+
/** Persist the git-status announced state as a first-class {@link GitAnnouncementEntry}
|
|
735
|
+
* (snapshot semantics; see `Session.appendGitAnnouncement`). OPTIONAL on the interface — same
|
|
736
|
+
* external-implementer posture as `getPromptEpoch`; consumers call it with `?.()` and an
|
|
737
|
+
* implementer without it degrades to conservative re-announcement every leg (duplicate-tolerant
|
|
738
|
+
* by design), never a crash. */
|
|
739
|
+
appendGitAnnouncement?(state: GitAnnouncementState): Promise<string>;
|
|
740
|
+
/** Branch-authority read of the git announced state (see `Session.getGitAnnouncement`): nearest
|
|
741
|
+
* carrier on the active branch, with a pending restatement — or an announced state whose frame
|
|
742
|
+
* entryId is NOT on the active branch — surfaced as `status:"pending"` (the reader must
|
|
743
|
+
* conservatively re-announce). Undefined ⇒ no carrier visible on this branch. OPTIONAL, same
|
|
744
|
+
* posture as `appendGitAnnouncement`. */
|
|
745
|
+
getGitAnnouncement?(): Promise<{
|
|
746
|
+
kind: GitAnnouncementKind;
|
|
747
|
+
hash: string;
|
|
748
|
+
status: "announced" | "pending";
|
|
749
|
+
entryId?: string;
|
|
750
|
+
} | undefined>;
|
|
687
751
|
}
|
|
688
752
|
export interface SessionCreateOptions {
|
|
689
753
|
id?: string;
|