@gajae-code/coding-agent 0.12.4 → 0.12.6
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 +36 -0
- package/README.md +4 -1
- package/dist/types/async/job-manager.d.ts +9 -1
- package/dist/types/eval/backend.d.ts +2 -2
- package/dist/types/exec/bash-executor.d.ts +10 -8
- package/dist/types/extensibility/extensions/runner.d.ts +9 -7
- package/dist/types/gjc-runtime/launch-tmux.d.ts +22 -3
- package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
- package/dist/types/gjc-runtime/team-launch.d.ts +3 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +14 -2
- package/dist/types/gjc-runtime/team-store.d.ts +0 -4
- package/dist/types/gjc-runtime/team-workers.d.ts +5 -0
- package/dist/types/gjc-runtime/tmux-common.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-provider-context.d.ts +57 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +8 -1
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +3 -0
- package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +83 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +57 -4
- package/dist/types/internal-urls/registry-helpers.d.ts +18 -0
- package/dist/types/modes/components/gjc-bundle-settings.d.ts +1 -0
- package/dist/types/modes/components/plugin-settings.d.ts +2 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/sdk/broker/broker.d.ts +18 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +13 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +5 -2
- package/dist/types/sdk/bus/index.d.ts +4 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -3
- package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -3
- package/dist/types/sdk/bus/topic-registry.d.ts +63 -3
- package/dist/types/sdk/host/host.d.ts +23 -2
- package/dist/types/sdk/host/reverse-leases.d.ts +7 -4
- package/dist/types/session/agent-session.d.ts +5 -4
- package/dist/types/session/artifacts.d.ts +8 -5
- package/dist/types/session/attempt-record-store.d.ts +19 -0
- package/dist/types/session/client-bridge.d.ts +14 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -23
- package/dist/types/session/messages.d.ts +4 -1
- package/dist/types/session/session-manager.d.ts +7 -5
- package/dist/types/session/session-storage.d.ts +8 -0
- package/dist/types/session/streaming-output.d.ts +32 -0
- package/dist/types/task/executor.d.ts +1 -1
- package/dist/types/task/index.d.ts +2 -1
- package/dist/types/task/receipt.d.ts +3 -0
- package/dist/types/task/types.d.ts +194 -0
- package/dist/types/task/worktree.d.ts +13 -6
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/fetch.d.ts +3 -0
- package/dist/types/tools/output-meta.d.ts +37 -5
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/tools/tool-result.d.ts +1 -0
- package/dist/types/utils/artifact-id.d.ts +2 -0
- package/dist/types/utils/git.d.ts +1 -0
- package/package.json +8 -8
- package/scripts/capture-sticky-viewport-showcase.ts +131 -5
- package/scripts/verify-gjc-sdk-canonicalization.ts +128 -183
- package/scripts/verify-sticky-viewport-showcase.ts +620 -93
- package/src/async/job-manager.ts +13 -5
- package/src/config/model-profiles.ts +8 -0
- package/src/config/settings-schema.ts +17 -3
- package/src/cursor.ts +20 -13
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +5 -2
- package/src/eval/backend.ts +2 -2
- package/src/exec/bash-executor.ts +425 -151
- package/src/extensibility/extensions/runner.ts +80 -6
- package/src/extensibility/extensions/wrapper.ts +22 -15
- package/src/extensibility/runtime-skill-discovery.ts +4 -6
- package/src/gjc-runtime/launch-tmux.ts +309 -293
- package/src/gjc-runtime/managed-owner-supervisor.ts +6 -2
- package/src/gjc-runtime/psmux-detect.ts +52 -6
- package/src/gjc-runtime/team-launch.ts +50 -2
- package/src/gjc-runtime/team-runtime.ts +716 -251
- package/src/gjc-runtime/team-store.ts +25 -7
- package/src/gjc-runtime/team-workers.ts +161 -1
- package/src/gjc-runtime/tmux-common.ts +16 -4
- package/src/gjc-runtime/tmux-gc.ts +37 -5
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -0
- package/src/gjc-runtime/tmux-provider-context.ts +451 -0
- package/src/gjc-runtime/tmux-sessions.ts +409 -156
- package/src/gjc-runtime/ultragoal-change-set.ts +90 -44
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +7 -0
- package/src/gjc-runtime/ultragoal-review-source.ts +378 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +640 -159
- package/src/gjc-runtime/workflow-manifest.generated.json +40 -1
- package/src/gjc-runtime/workflow-manifest.ts +12 -0
- package/src/internal-urls/agent-protocol.ts +98 -38
- package/src/internal-urls/artifact-protocol.ts +28 -17
- package/src/internal-urls/docs-index.generated.ts +6 -6
- package/src/internal-urls/local-protocol.ts +39 -18
- package/src/internal-urls/registry-helpers.ts +62 -0
- package/src/modes/acp/acp-agent.ts +51 -13
- package/src/modes/acp/acp-client-bridge.ts +154 -11
- package/src/modes/components/gjc-bundle-settings.ts +2 -0
- package/src/modes/components/plugin-settings.ts +5 -0
- package/src/modes/components/settings-selector.ts +4 -0
- package/src/modes/controllers/selector-controller.ts +1 -0
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/monitor.md +4 -4
- package/src/sdk/acp/adapter.ts +200 -9
- package/src/sdk/broker/broker.ts +93 -10
- package/src/sdk/broker/lifecycle-ledger.ts +227 -11
- package/src/sdk/broker/lifecycle.ts +692 -98
- package/src/sdk/bus/chat-daemon-control.ts +33 -5
- package/src/sdk/bus/index.ts +347 -14
- package/src/sdk/bus/telegram-daemon-contract.ts +7 -3
- package/src/sdk/bus/telegram-daemon.ts +283 -44
- package/src/sdk/bus/topic-registry.ts +133 -10
- package/src/sdk/host/host.ts +187 -9
- package/src/sdk/host/reverse-leases.ts +249 -42
- package/src/sdk/session.ts +8 -6
- package/src/session/agent-session.ts +930 -354
- package/src/session/artifacts.ts +211 -74
- package/src/session/attempt-record-store.ts +106 -0
- package/src/session/client-bridge.ts +15 -0
- package/src/session/internal/managed-session-scope.ts +332 -198
- package/src/session/internal/managed-session-storage.ts +162 -450
- package/src/session/messages.ts +32 -7
- package/src/session/session-manager.ts +31 -28
- package/src/session/session-storage.ts +190 -53
- package/src/session/streaming-output.ts +198 -40
- package/src/task/executor.ts +5 -2
- package/src/task/index.ts +277 -64
- package/src/task/provider-retry-status.ts +4 -1
- package/src/task/receipt.ts +26 -2
- package/src/task/render.ts +42 -1
- package/src/task/types.ts +52 -0
- package/src/task/worktree.ts +122 -43
- package/src/tools/bash.ts +818 -239
- package/src/tools/eval.ts +99 -4
- package/src/tools/fetch.ts +77 -15
- package/src/tools/find.ts +7 -3
- package/src/tools/monitor.ts +7 -1
- package/src/tools/output-meta.ts +706 -116
- package/src/tools/search.ts +9 -4
- package/src/tools/subagent-render.ts +5 -2
- package/src/tools/subagent.ts +8 -0
- package/src/tools/tool-result.ts +1 -0
- package/src/utils/artifact-id.ts +6 -0
- package/src/utils/git.ts +2 -0
|
@@ -2,6 +2,7 @@ import type { AgentToolUpdateCallback } from "@gajae-code/agent-core";
|
|
|
2
2
|
|
|
3
3
|
import { sanitizeText } from "@gajae-code/utils";
|
|
4
4
|
import { formatBytes } from "../tools/render-utils";
|
|
5
|
+
import { isValidArtifactId } from "../utils/artifact-id";
|
|
5
6
|
import { sanitizeWithOptionalSixelPassthrough } from "../utils/sixel";
|
|
6
7
|
|
|
7
8
|
function sanitizeOutputChunk(rawChunk: string): string {
|
|
@@ -49,6 +50,47 @@ export type TerminalArtifactPublisher = (
|
|
|
49
50
|
info: { totalBytes: number; omittedBytes: number },
|
|
50
51
|
) => Promise<TerminalArtifactPublishResult>;
|
|
51
52
|
|
|
53
|
+
const TERMINAL_ARTIFACT_PUBLISH_WAIT_MS = 500;
|
|
54
|
+
const TERMINAL_ARTIFACT_PENDING_LIMIT = 64;
|
|
55
|
+
const pendingTerminalArtifactPublishesByOwner = new WeakMap<object, Set<Promise<TerminalArtifactPublishResult>>>();
|
|
56
|
+
|
|
57
|
+
function pendingTerminalArtifactPublishesForOwner(owner: object): Set<Promise<TerminalArtifactPublishResult>> {
|
|
58
|
+
let pending = pendingTerminalArtifactPublishesByOwner.get(owner);
|
|
59
|
+
if (!pending) {
|
|
60
|
+
pending = new Set();
|
|
61
|
+
pendingTerminalArtifactPublishesByOwner.set(owner, pending);
|
|
62
|
+
}
|
|
63
|
+
return pending;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function terminalPublisherOwner(publisher: TerminalArtifactPublisher): object {
|
|
67
|
+
return (publisher as TerminalArtifactPublisher & { owner?: object }).owner ?? publisher;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function publishTerminalArtifactBounded(
|
|
71
|
+
publisher: TerminalArtifactPublisher,
|
|
72
|
+
content: string,
|
|
73
|
+
info: { totalBytes: number; omittedBytes: number },
|
|
74
|
+
): Promise<TerminalArtifactPublishResult> {
|
|
75
|
+
const pendingTerminalArtifactPublishes = pendingTerminalArtifactPublishesForOwner(terminalPublisherOwner(publisher));
|
|
76
|
+
if (pendingTerminalArtifactPublishes.size >= TERMINAL_ARTIFACT_PENDING_LIMIT) {
|
|
77
|
+
return { status: "failed", diagnostic: "pending publisher limit reached for this session" };
|
|
78
|
+
}
|
|
79
|
+
const promise = Promise.resolve().then(() => publisher(content, info));
|
|
80
|
+
pendingTerminalArtifactPublishes.add(promise);
|
|
81
|
+
void promise.then(
|
|
82
|
+
() => pendingTerminalArtifactPublishes.delete(promise),
|
|
83
|
+
() => pendingTerminalArtifactPublishes.delete(promise),
|
|
84
|
+
);
|
|
85
|
+
return Promise.race([
|
|
86
|
+
promise,
|
|
87
|
+
Bun.sleep(TERMINAL_ARTIFACT_PUBLISH_WAIT_MS).then(() => ({
|
|
88
|
+
status: "failed" as const,
|
|
89
|
+
diagnostic: `did not settle within ${TERMINAL_ARTIFACT_PUBLISH_WAIT_MS}ms`,
|
|
90
|
+
})),
|
|
91
|
+
]);
|
|
92
|
+
}
|
|
93
|
+
|
|
52
94
|
// =============================================================================
|
|
53
95
|
// Interfaces
|
|
54
96
|
// =============================================================================
|
|
@@ -64,18 +106,60 @@ export interface OutputSummary {
|
|
|
64
106
|
elidedBytes?: number;
|
|
65
107
|
/** Lines elided from the middle when head-retain mode is active. */
|
|
66
108
|
elidedLines?: number;
|
|
109
|
+
/** Exact source ranges retained by head/tail middle-elision windows. */
|
|
110
|
+
headRange?: { start: number; end: number };
|
|
111
|
+
tailRange?: { start: number; end: number };
|
|
112
|
+
/** The retained tail ends with only a byte fragment of its source line. */
|
|
113
|
+
lastLinePartial?: boolean;
|
|
114
|
+
/** The retained head ends with only a byte fragment of its source line. */
|
|
115
|
+
firstLinePartial?: boolean;
|
|
67
116
|
/** Bytes dropped by the per-line column cap (sum across all lines). */
|
|
68
117
|
columnDroppedBytes?: number;
|
|
69
118
|
/** Number of distinct lines that hit the per-line column cap. */
|
|
70
119
|
columnTruncatedLines?: number;
|
|
120
|
+
/** Configured per-line byte/column cap that caused visible omission. */
|
|
121
|
+
columnMax?: number;
|
|
71
122
|
/** Artifact ID for internal URL access (artifact://<id>) when output was persisted. */
|
|
72
123
|
artifactId?: string;
|
|
124
|
+
/** Artifact existence was proven by successful publication or file finalization. */
|
|
125
|
+
artifactVerified?: boolean;
|
|
73
126
|
/** Bytes omitted from artifact storage after the artifact hard cap was reached. */
|
|
74
127
|
artifactTruncatedBytes?: number;
|
|
128
|
+
/** Bytes dropped before the Bash executor received the native output stream. */
|
|
129
|
+
sourceTruncatedBytes?: number;
|
|
130
|
+
/** Exact source capture completeness could not be proven. */
|
|
131
|
+
sourceCaptureIncomplete?: boolean;
|
|
75
132
|
/** Bounded diagnostic when artifact writer or terminal publisher creation, write, finalization, or publication failed. */
|
|
76
133
|
artifactFailureDiagnostic?: string;
|
|
77
134
|
}
|
|
78
135
|
|
|
136
|
+
const kCurrentExecutionArtifactProof = Symbol("OutputSink.CurrentExecutionArtifactProof");
|
|
137
|
+
interface CurrentExecutionArtifactProof {
|
|
138
|
+
artifactId: string;
|
|
139
|
+
payload: string;
|
|
140
|
+
consumed: boolean;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Verify that this exact OutputSink summary came from a successful current-execution publication. */
|
|
144
|
+
export function hasCurrentExecutionArtifactProof(
|
|
145
|
+
summary: OutputSummary,
|
|
146
|
+
artifactId: string,
|
|
147
|
+
payload?: string,
|
|
148
|
+
): boolean {
|
|
149
|
+
const proof = (summary as OutputSummary & { [kCurrentExecutionArtifactProof]?: CurrentExecutionArtifactProof })[
|
|
150
|
+
kCurrentExecutionArtifactProof
|
|
151
|
+
];
|
|
152
|
+
if (
|
|
153
|
+
!proof ||
|
|
154
|
+
proof.artifactId !== artifactId ||
|
|
155
|
+
proof.consumed ||
|
|
156
|
+
(payload !== undefined && proof.payload !== payload)
|
|
157
|
+
)
|
|
158
|
+
return false;
|
|
159
|
+
proof.consumed = true;
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
79
163
|
export interface OutputSinkOptions {
|
|
80
164
|
/**
|
|
81
165
|
* Deprecated managed artifact pathname. Bare paths are deliberately ignored:
|
|
@@ -141,7 +225,10 @@ export interface TruncationResult {
|
|
|
141
225
|
elidedBytes?: number;
|
|
142
226
|
/** Lines elided from the middle (truncateMiddle only). */
|
|
143
227
|
elidedLines?: number;
|
|
228
|
+
headRange?: { start: number; end: number };
|
|
229
|
+
tailRange?: { start: number; end: number };
|
|
144
230
|
lastLinePartial?: boolean;
|
|
231
|
+
firstLinePartial?: boolean;
|
|
145
232
|
firstLineExceedsLimit?: boolean;
|
|
146
233
|
lastLineExceedsLimit?: boolean;
|
|
147
234
|
}
|
|
@@ -793,7 +880,7 @@ export function truncateMiddle(content: string, options: TruncationOptions = {})
|
|
|
793
880
|
const head = windows.head;
|
|
794
881
|
const tail = windows.tail;
|
|
795
882
|
if (!head || !tail) return noTruncResult(content, windows.totalLines, windows.totalBytes);
|
|
796
|
-
if (head.lines + tail.lines >= windows.totalLines)
|
|
883
|
+
if (head.lines + tail.lines >= windows.totalLines && tail.kind !== "partial-line")
|
|
797
884
|
return noTruncResult(content, windows.totalLines, windows.totalBytes);
|
|
798
885
|
const legacyElidedLines = Math.max(0, windows.totalLines - head.lines - tail.lines);
|
|
799
886
|
const legacyElidedBytes =
|
|
@@ -802,7 +889,7 @@ export function truncateMiddle(content: string, options: TruncationOptions = {})
|
|
|
802
889
|
: windows.elidedBytes;
|
|
803
890
|
const marker = formatMiddleElisionMarker(legacyElidedLines, legacyElidedBytes);
|
|
804
891
|
const markerBytes = Buffer.byteLength(marker, "utf-8");
|
|
805
|
-
|
|
892
|
+
const result: TruncationResult = {
|
|
806
893
|
content: `${head.content}\n${marker}\n${tail.content}`,
|
|
807
894
|
truncated: true,
|
|
808
895
|
truncatedBy: "middle",
|
|
@@ -816,6 +903,11 @@ export function truncateMiddle(content: string, options: TruncationOptions = {})
|
|
|
816
903
|
firstLineExceedsLimit: false,
|
|
817
904
|
...(tail.lastLinePartial ? { lastLineExceedsLimit: true } : {}),
|
|
818
905
|
};
|
|
906
|
+
Object.defineProperties(result, {
|
|
907
|
+
headRange: { value: { start: head.origin.startLine, end: head.origin.endLine } },
|
|
908
|
+
tailRange: { value: { start: tail.origin.startLine, end: tail.origin.endLine } },
|
|
909
|
+
});
|
|
910
|
+
return result;
|
|
819
911
|
}
|
|
820
912
|
}
|
|
821
913
|
}
|
|
@@ -932,6 +1024,8 @@ export class OutputSink {
|
|
|
932
1024
|
#head = "";
|
|
933
1025
|
#headBytes = 0;
|
|
934
1026
|
#headRetentionDisabled = false;
|
|
1027
|
+
#headEndsMidLine = false;
|
|
1028
|
+
#tailStartsMidLine = false;
|
|
935
1029
|
#totalLines = 0; // newline count
|
|
936
1030
|
#totalBytes = 0;
|
|
937
1031
|
#processedBytes = 0;
|
|
@@ -967,6 +1061,8 @@ export class OutputSink {
|
|
|
967
1061
|
#pendingArtifactContent?: string[];
|
|
968
1062
|
#artifactPublishAttempted = false;
|
|
969
1063
|
#finalized = false;
|
|
1064
|
+
#finalizing = false;
|
|
1065
|
+
#artifactFinalization?: Promise<string | undefined>;
|
|
970
1066
|
|
|
971
1067
|
#fileReady = false;
|
|
972
1068
|
|
|
@@ -1020,9 +1116,10 @@ export class OutputSink {
|
|
|
1020
1116
|
return this.#buffer;
|
|
1021
1117
|
}
|
|
1022
1118
|
|
|
1023
|
-
#setTail(text: string, bytes = Buffer.byteLength(text, "utf-8")): void {
|
|
1119
|
+
#setTail(text: string, bytes = Buffer.byteLength(text, "utf-8"), startsMidLine = false): void {
|
|
1024
1120
|
this.#buffer = text;
|
|
1025
1121
|
this.#bufferBytes = bytes;
|
|
1122
|
+
this.#tailStartsMidLine = startsMidLine;
|
|
1026
1123
|
}
|
|
1027
1124
|
|
|
1028
1125
|
#appendTail(text: string, bytes: number): void {
|
|
@@ -1037,9 +1134,12 @@ export class OutputSink {
|
|
|
1037
1134
|
|
|
1038
1135
|
#trimTailTo(maxBytes: number): void {
|
|
1039
1136
|
if (this.#bufferBytes <= maxBytes) return;
|
|
1040
|
-
const
|
|
1137
|
+
const previous = this.#buffer;
|
|
1138
|
+
const { text, bytes } = truncateTailBytes(previous, maxBytes);
|
|
1139
|
+
const removed = previous.slice(0, previous.length - text.length);
|
|
1041
1140
|
this.#buffer = text;
|
|
1042
1141
|
this.#bufferBytes = bytes;
|
|
1142
|
+
this.#tailStartsMidLine = removed.length > 0 && !removed.endsWith("\n");
|
|
1043
1143
|
}
|
|
1044
1144
|
|
|
1045
1145
|
/**
|
|
@@ -1050,6 +1150,7 @@ export class OutputSink {
|
|
|
1050
1150
|
// F21: with coalescing enabled, accumulate raw chunks and process them in batches; the default
|
|
1051
1151
|
// (disabled) path calls #ingest directly and is byte-identical to the historical per-chunk path.
|
|
1052
1152
|
push(chunk: string): void {
|
|
1153
|
+
if (this.#finalized || this.#finalizing) return;
|
|
1053
1154
|
if (!this.#coalesceSanitize) {
|
|
1054
1155
|
this.#ingest(chunk);
|
|
1055
1156
|
return;
|
|
@@ -1132,6 +1233,10 @@ export class OutputSink {
|
|
|
1132
1233
|
}
|
|
1133
1234
|
}
|
|
1134
1235
|
|
|
1236
|
+
if (this.#headBytes > 0 && this.#bufferBytes === 0 && tailBytes > 0) {
|
|
1237
|
+
this.#headEndsMidLine = !this.#head.endsWith("\n") && !tailChunk.startsWith("\n");
|
|
1238
|
+
this.#tailStartsMidLine = this.#headEndsMidLine;
|
|
1239
|
+
}
|
|
1135
1240
|
this.#pushTail(tailChunk, tailBytes);
|
|
1136
1241
|
}
|
|
1137
1242
|
|
|
@@ -1216,7 +1321,8 @@ export class OutputSink {
|
|
|
1216
1321
|
// Avoid creating a giant intermediate string when chunk alone dominates.
|
|
1217
1322
|
if (dataBytes >= threshold) {
|
|
1218
1323
|
const { text, bytes } = truncateTailBytes(chunk, threshold);
|
|
1219
|
-
|
|
1324
|
+
const removed = chunk.slice(0, chunk.length - text.length);
|
|
1325
|
+
this.#setTail(text, bytes, removed.length > 0 && !removed.endsWith("\n"));
|
|
1220
1326
|
} else {
|
|
1221
1327
|
// Intermediate size is bounded (<= threshold + dataBytes), safe to concat.
|
|
1222
1328
|
this.#appendTail(chunk, dataBytes);
|
|
@@ -1390,13 +1496,19 @@ export class OutputSink {
|
|
|
1390
1496
|
return this.#publishedArtifactId;
|
|
1391
1497
|
}
|
|
1392
1498
|
this.#artifactPublishAttempted = true;
|
|
1499
|
+
this.#finalizing = true;
|
|
1393
1500
|
const content = this.#pendingArtifactContent?.join("") ?? "";
|
|
1394
1501
|
const omittedBytes = this.#artifactTruncatedBytes;
|
|
1395
1502
|
try {
|
|
1396
|
-
const published = await this.#artifactPublisher
|
|
1503
|
+
const published = await publishTerminalArtifactBounded(this.#artifactPublisher!, content, {
|
|
1397
1504
|
totalBytes: this.#totalBytes,
|
|
1398
1505
|
omittedBytes,
|
|
1399
1506
|
});
|
|
1507
|
+
if (typeof published !== "object" || published === null || !("status" in published)) {
|
|
1508
|
+
this.#recordTerminalArtifactDiagnostic("failed", "publisher returned an invalid result");
|
|
1509
|
+
this.#artifactFinalizationFailed = true;
|
|
1510
|
+
return undefined;
|
|
1511
|
+
}
|
|
1400
1512
|
if (published.status === "unavailable") {
|
|
1401
1513
|
this.#recordTerminalArtifactDiagnostic("unavailable");
|
|
1402
1514
|
this.#artifactFinalizationFailed = true;
|
|
@@ -1407,8 +1519,13 @@ export class OutputSink {
|
|
|
1407
1519
|
this.#artifactFinalizationFailed = true;
|
|
1408
1520
|
return undefined;
|
|
1409
1521
|
}
|
|
1410
|
-
if (published.
|
|
1411
|
-
this.#recordTerminalArtifactDiagnostic("failed", "
|
|
1522
|
+
if (published.status !== "published") {
|
|
1523
|
+
this.#recordTerminalArtifactDiagnostic("failed", "publisher returned an invalid result");
|
|
1524
|
+
this.#artifactFinalizationFailed = true;
|
|
1525
|
+
return undefined;
|
|
1526
|
+
}
|
|
1527
|
+
if (!isValidArtifactId(published.artifactId)) {
|
|
1528
|
+
this.#recordTerminalArtifactDiagnostic("failed", "storage returned an invalid artifact id");
|
|
1412
1529
|
this.#artifactFinalizationFailed = true;
|
|
1413
1530
|
return undefined;
|
|
1414
1531
|
}
|
|
@@ -1432,6 +1549,48 @@ export class OutputSink {
|
|
|
1432
1549
|
}
|
|
1433
1550
|
}
|
|
1434
1551
|
|
|
1552
|
+
#shouldFinalizeArtifact(): boolean {
|
|
1553
|
+
return (
|
|
1554
|
+
this.#finalized ||
|
|
1555
|
+
this.#artifactFinalizationFailed ||
|
|
1556
|
+
this.#shouldPublishTerminalArtifact() ||
|
|
1557
|
+
this.#file !== undefined ||
|
|
1558
|
+
(this.#artifactPath !== undefined && this.#artifactTruncatedBytes > 0)
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
async #finalizeArtifact(): Promise<string | undefined> {
|
|
1563
|
+
let artifactId: string | undefined;
|
|
1564
|
+
if (this.#finalized) artifactId = this.#publishedArtifactId ?? this.#artifactId;
|
|
1565
|
+
else if (!this.#artifactFinalizationFailed) {
|
|
1566
|
+
if (this.#artifactPublisher && !this.#artifactPath) artifactId = await this.#publishTerminalArtifact();
|
|
1567
|
+
if (!this.#artifactFinalizationFailed && this.#artifactPath) {
|
|
1568
|
+
if (this.#artifactTruncatedBytes > 0) this.#createFileSink();
|
|
1569
|
+
if (this.#file) {
|
|
1570
|
+
this.#finalizing = true;
|
|
1571
|
+
await this.#writeArtifactTruncationNotice();
|
|
1572
|
+
try {
|
|
1573
|
+
await this.#file.sink.end();
|
|
1574
|
+
artifactId = this.#file.artifactId;
|
|
1575
|
+
this.#finalized = true;
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
this.#recordArtifactFailure("end", error);
|
|
1578
|
+
this.#artifactFinalizationFailed = true;
|
|
1579
|
+
this.#file = undefined;
|
|
1580
|
+
this.#fileReady = false;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
if (this.#finalized) {
|
|
1586
|
+
this.#pendingFileWrites = undefined;
|
|
1587
|
+
this.#pendingFileWriteBytes = 0;
|
|
1588
|
+
this.#fileReady = false;
|
|
1589
|
+
this.#pendingArtifactContent = undefined;
|
|
1590
|
+
}
|
|
1591
|
+
return artifactId;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1435
1594
|
createInput(): WritableStream<Uint8Array | string> {
|
|
1436
1595
|
const dec = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
1437
1596
|
const finalize = () => {
|
|
@@ -1457,17 +1616,20 @@ export class OutputSink {
|
|
|
1457
1616
|
* append directly to the tail without reordering the replacement.
|
|
1458
1617
|
*/
|
|
1459
1618
|
replace(text: string): void {
|
|
1619
|
+
if (this.#finalized || this.#finalizing) return;
|
|
1460
1620
|
this.#coalesceBuf = "";
|
|
1461
1621
|
const replacementBytes = Buffer.byteLength(text, "utf-8");
|
|
1462
1622
|
this.#head = "";
|
|
1463
1623
|
this.#headBytes = 0;
|
|
1464
1624
|
this.#setTail("");
|
|
1625
|
+
this.#headEndsMidLine = false;
|
|
1465
1626
|
|
|
1466
1627
|
if (this.#headLimit > 0) {
|
|
1467
1628
|
const head = truncateHeadBytes(text, this.#headLimit);
|
|
1468
1629
|
this.#appendHead(head.text, head.bytes);
|
|
1469
1630
|
const tailText = text.substring(head.text.length);
|
|
1470
|
-
this.#
|
|
1631
|
+
this.#headEndsMidLine = tailText.length > 0 && !head.text.endsWith("\n") && !tailText.startsWith("\n");
|
|
1632
|
+
this.#setTail(tailText, replacementBytes - head.bytes, this.#headEndsMidLine);
|
|
1471
1633
|
} else {
|
|
1472
1634
|
this.#setTail(text, replacementBytes);
|
|
1473
1635
|
}
|
|
@@ -1491,35 +1653,9 @@ export class OutputSink {
|
|
|
1491
1653
|
const totalLines = this.#sawData ? this.#totalLines + 1 : 0;
|
|
1492
1654
|
|
|
1493
1655
|
let artifactId: string | undefined;
|
|
1494
|
-
if (this.#
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
if (this.#artifactPublisher && !this.#artifactPath) {
|
|
1498
|
-
artifactId = await this.#publishTerminalArtifact();
|
|
1499
|
-
}
|
|
1500
|
-
if (!this.#artifactFinalizationFailed && this.#artifactPath) {
|
|
1501
|
-
if (this.#artifactTruncatedBytes > 0) this.#createFileSink();
|
|
1502
|
-
await this.#writeArtifactTruncationNotice();
|
|
1503
|
-
if (this.#file) {
|
|
1504
|
-
try {
|
|
1505
|
-
await this.#file.sink.end();
|
|
1506
|
-
artifactId = this.#file.artifactId;
|
|
1507
|
-
this.#finalized = true;
|
|
1508
|
-
} catch (error) {
|
|
1509
|
-
this.#recordArtifactFailure("end", error);
|
|
1510
|
-
this.#artifactFinalizationFailed = true;
|
|
1511
|
-
this.#file = undefined;
|
|
1512
|
-
this.#fileReady = false;
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
if (this.#finalized) {
|
|
1518
|
-
// Terminal: the artifact is closed; replay state is no longer needed.
|
|
1519
|
-
this.#pendingFileWrites = undefined;
|
|
1520
|
-
this.#pendingFileWriteBytes = 0;
|
|
1521
|
-
this.#fileReady = false;
|
|
1522
|
-
this.#pendingArtifactContent = undefined;
|
|
1656
|
+
if (this.#shouldFinalizeArtifact()) {
|
|
1657
|
+
this.#artifactFinalization ??= this.#finalizeArtifact();
|
|
1658
|
+
artifactId = await this.#artifactFinalization;
|
|
1523
1659
|
}
|
|
1524
1660
|
// Non-finalized dumps (no artifact sink ever opened) keep the raw replay
|
|
1525
1661
|
// queue so a later post-dump push that spills produces a CUMULATIVE
|
|
@@ -1543,6 +1679,8 @@ export class OutputSink {
|
|
|
1543
1679
|
let outputLines: number;
|
|
1544
1680
|
let elidedBytes: number | undefined;
|
|
1545
1681
|
let elidedLines: number | undefined;
|
|
1682
|
+
let headRange: { start: number; end: number } | undefined;
|
|
1683
|
+
let tailRange: { start: number; end: number } | undefined;
|
|
1546
1684
|
|
|
1547
1685
|
if (headBytes > 0 && effectiveTotalBytes > headBytes + tailBytes) {
|
|
1548
1686
|
// Middle was elided. Emit both explicit windows, including when they are
|
|
@@ -1555,7 +1693,12 @@ export class OutputSink {
|
|
|
1555
1693
|
const tailSep = tailBuf.startsWith("\n") ? "" : "\n";
|
|
1556
1694
|
body = `${headText}${headSep}${marker}${tailSep}${tailBuf}`;
|
|
1557
1695
|
outputBytes = headBytes + markerBytes + tailBytes + headSep.length + tailSep.length;
|
|
1558
|
-
outputLines =
|
|
1696
|
+
outputLines = body.length > 0 ? countNewlines(body) + 1 : 0;
|
|
1697
|
+
headRange = headLines > 0 && !this.#headEndsMidLine ? { start: 1, end: headLines } : undefined;
|
|
1698
|
+
tailRange =
|
|
1699
|
+
tailLines > 0 && !this.#tailStartsMidLine
|
|
1700
|
+
? { start: processedTotalLines - tailLines + 1, end: processedTotalLines }
|
|
1701
|
+
: undefined;
|
|
1559
1702
|
this.#truncated = true;
|
|
1560
1703
|
} else if (headBytes > 0) {
|
|
1561
1704
|
// Head + tail combine into the full buffered output (no overlap or elision).
|
|
@@ -1569,7 +1712,7 @@ export class OutputSink {
|
|
|
1569
1712
|
}
|
|
1570
1713
|
|
|
1571
1714
|
if (this.#columnDroppedBytes > 0) this.#truncated = true;
|
|
1572
|
-
|
|
1715
|
+
const summary: OutputSummary = {
|
|
1573
1716
|
output: `${noticeLine}${body}`,
|
|
1574
1717
|
truncated: this.#truncated,
|
|
1575
1718
|
totalLines,
|
|
@@ -1578,12 +1721,27 @@ export class OutputSink {
|
|
|
1578
1721
|
outputBytes,
|
|
1579
1722
|
elidedBytes,
|
|
1580
1723
|
elidedLines,
|
|
1724
|
+
headRange,
|
|
1725
|
+
tailRange,
|
|
1726
|
+
firstLinePartial: this.#headEndsMidLine || undefined,
|
|
1727
|
+
lastLinePartial: this.#tailStartsMidLine || undefined,
|
|
1581
1728
|
columnDroppedBytes: this.#columnDroppedBytes > 0 ? this.#columnDroppedBytes : undefined,
|
|
1582
1729
|
columnTruncatedLines: this.#columnTruncatedLines > 0 ? this.#columnTruncatedLines : undefined,
|
|
1730
|
+
columnMax: this.#columnDroppedBytes > 0 ? this.#maxColumns : undefined,
|
|
1583
1731
|
artifactTruncatedBytes: this.#artifactTruncatedBytes > 0 ? this.#artifactTruncatedBytes : undefined,
|
|
1584
1732
|
artifactFailureDiagnostic: this.#artifactFailureDiagnostic,
|
|
1585
1733
|
artifactId,
|
|
1734
|
+
artifactVerified: artifactId !== undefined && this.#publishedArtifactId === artifactId,
|
|
1586
1735
|
};
|
|
1736
|
+
if (summary.artifactVerified === true && artifactId !== undefined) {
|
|
1737
|
+
Object.defineProperty(summary, kCurrentExecutionArtifactProof, {
|
|
1738
|
+
value: { artifactId, payload: summary.output, consumed: false } satisfies CurrentExecutionArtifactProof,
|
|
1739
|
+
enumerable: true,
|
|
1740
|
+
configurable: false,
|
|
1741
|
+
writable: false,
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
return summary;
|
|
1587
1745
|
}
|
|
1588
1746
|
}
|
|
1589
1747
|
|
package/src/task/executor.ts
CHANGED
|
@@ -280,7 +280,7 @@ export class ManagedTaskPersistence {
|
|
|
280
280
|
throw new Error("Managed task persistence authority is unavailable");
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
async openSession(): Promise<SessionManager> {
|
|
283
|
+
async openSession(cwd: string): Promise<SessionManager> {
|
|
284
284
|
const store = this.#artifacts.getManagedStore();
|
|
285
285
|
if (!store) throw new Error("Managed task persistence authority is unavailable");
|
|
286
286
|
this.#artifacts.assertManagedBinding();
|
|
@@ -289,6 +289,8 @@ export class ManagedTaskPersistence {
|
|
|
289
289
|
sessionFile,
|
|
290
290
|
SessionManager.nestedManagedDestination(store, this.#artifacts.dir),
|
|
291
291
|
store,
|
|
292
|
+
undefined,
|
|
293
|
+
cwd,
|
|
292
294
|
);
|
|
293
295
|
this.#artifacts.assertManagedBinding();
|
|
294
296
|
return session;
|
|
@@ -1533,6 +1535,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1533
1535
|
requestedModel: modelSubstitutionWarning?.requested ?? resolvedModelString,
|
|
1534
1536
|
effectiveModel: resolvedModelString,
|
|
1535
1537
|
modelFellBack: authFallbackUsed === true,
|
|
1538
|
+
fastMode: progress.fastMode,
|
|
1536
1539
|
});
|
|
1537
1540
|
}
|
|
1538
1541
|
if (model?.contextWindow && model.contextWindow > 0) {
|
|
@@ -1547,7 +1550,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1547
1550
|
effectiveThinkingLevelForWarning = effectiveThinkingLevel;
|
|
1548
1551
|
|
|
1549
1552
|
const sessionManager = options.managedPersistence
|
|
1550
|
-
? await awaitAbortable(options.managedPersistence.openSession())
|
|
1553
|
+
? await awaitAbortable(options.managedPersistence.openSession(worktree ?? cwd))
|
|
1551
1554
|
: sessionFile
|
|
1552
1555
|
? await awaitAbortable(
|
|
1553
1556
|
SessionManager.open(sessionFile, SessionManager.explicitDestination(path.dirname(sessionFile))),
|