@gajae-code/coding-agent 0.5.1 → 0.5.3
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 +31 -0
- package/README.md +1 -1
- package/dist/types/async/job-manager.d.ts +6 -0
- package/dist/types/cli/setup-cli.d.ts +8 -1
- package/dist/types/commands/setup.d.ts +7 -0
- package/dist/types/config/file-lock.d.ts +24 -2
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +62 -0
- package/dist/types/dap/client.d.ts +2 -1
- package/dist/types/edit/read-file.d.ts +6 -0
- package/dist/types/eval/js/context-manager.d.ts +3 -0
- package/dist/types/eval/js/executor.d.ts +1 -0
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +64 -2
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +7 -1
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +10 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +29 -0
- package/dist/types/lsp/types.d.ts +2 -0
- package/dist/types/modes/bridge/bridge-mode.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/oauth-selector.d.ts +1 -0
- package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +56 -1
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
- package/dist/types/modes/theme/defaults/index.d.ts +302 -0
- package/dist/types/modes/theme/theme.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -1
- package/dist/types/runtime/process-lifecycle.d.ts +108 -0
- package/dist/types/runtime-mcp/transports/stdio.d.ts +1 -0
- package/dist/types/runtime-mcp/types.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +17 -1
- package/dist/types/session/artifacts.d.ts +4 -1
- package/dist/types/session/history-storage.d.ts +2 -2
- package/dist/types/session/session-manager.d.ts +10 -1
- package/dist/types/session/streaming-output.d.ts +5 -0
- package/dist/types/setup/credential-import.d.ts +79 -0
- package/dist/types/slash-commands/helpers/fast-status-report.d.ts +76 -0
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/task/render.d.ts +1 -1
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +9 -0
- package/dist/types/tools/sqlite-reader.d.ts +2 -1
- package/dist/types/tools/subagent-render.d.ts +7 -1
- package/dist/types/tools/subagent.d.ts +21 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +5 -0
- package/dist/types/web/search/index.d.ts +4 -4
- package/dist/types/web/search/provider.d.ts +16 -20
- package/dist/types/web/search/providers/base.d.ts +2 -1
- package/dist/types/web/search/providers/openai-compatible.d.ts +9 -0
- package/dist/types/web/search/types.d.ts +14 -2
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +153 -39
- package/src/cli/args.ts +2 -0
- package/src/cli/fast-help.ts +2 -0
- package/src/cli/setup-cli.ts +138 -3
- package/src/commands/setup.ts +5 -1
- package/src/commands/ultragoal.ts +3 -1
- package/src/config/file-lock-gc.ts +14 -2
- package/src/config/file-lock.ts +63 -13
- package/src/config/model-profile-activation.ts +15 -3
- package/src/config/model-profiles.ts +15 -15
- package/src/config/model-registry.ts +21 -1
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings-schema.ts +62 -0
- package/src/dap/client.ts +105 -64
- package/src/dap/session.ts +44 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +30 -8
- package/src/edit/read-file.ts +19 -1
- package/src/eval/js/context-manager.ts +228 -65
- package/src/eval/js/executor.ts +2 -0
- package/src/eval/js/index.ts +1 -0
- package/src/eval/js/worker-core.ts +10 -6
- package/src/eval/py/executor.ts +68 -19
- package/src/eval/py/kernel.ts +46 -22
- package/src/eval/py/runner.py +68 -14
- package/src/exec/bash-executor.ts +49 -13
- package/src/gjc-runtime/deep-interview-recorder.ts +40 -0
- package/src/gjc-runtime/launch-tmux.ts +3 -4
- package/src/gjc-runtime/ralplan-runtime.ts +174 -12
- package/src/gjc-runtime/state-runtime.ts +2 -1
- package/src/gjc-runtime/state-writer.ts +254 -7
- package/src/gjc-runtime/tmux-gc.ts +88 -38
- package/src/gjc-runtime/tmux-sessions.ts +44 -6
- package/src/gjc-runtime/ultragoal-guard.ts +155 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +1227 -31
- package/src/gjc-runtime/workflow-manifest.generated.json +44 -0
- package/src/gjc-runtime/workflow-manifest.ts +12 -0
- package/src/harness-control-plane/owner.ts +3 -2
- package/src/harness-control-plane/rpc-adapter.ts +1 -1
- package/src/hooks/skill-state.ts +121 -2
- package/src/internal-urls/artifact-protocol.ts +10 -1
- package/src/internal-urls/docs-index.generated.ts +14 -10
- package/src/lsp/client.ts +64 -26
- package/src/lsp/defaults.json +1 -0
- package/src/lsp/index.ts +2 -1
- package/src/lsp/lspmux.ts +33 -9
- package/src/lsp/types.ts +2 -0
- package/src/main.ts +14 -4
- package/src/modes/acp/acp-agent.ts +4 -2
- package/src/modes/bridge/bridge-mode.ts +23 -1
- package/src/modes/components/assistant-message.ts +10 -2
- package/src/modes/components/bash-execution.ts +5 -1
- package/src/modes/components/eval-execution.ts +5 -1
- package/src/modes/components/history-search.ts +5 -2
- package/src/modes/components/model-selector.ts +60 -2
- package/src/modes/components/oauth-selector.ts +5 -0
- package/src/modes/components/provider-onboarding-selector.ts +6 -1
- package/src/modes/components/runtime-mcp-add-wizard.ts +58 -7
- package/src/modes/components/skill-message.ts +24 -16
- package/src/modes/components/tool-execution.ts +6 -0
- package/src/modes/controllers/extension-ui-controller.ts +33 -6
- package/src/modes/controllers/input-controller.ts +5 -0
- package/src/modes/controllers/selector-controller.ts +86 -2
- package/src/modes/interactive-mode.ts +11 -1
- package/src/modes/rpc/rpc-mode.ts +132 -18
- package/src/modes/shared/agent-wire/command-dispatch.ts +5 -2
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +3 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +16 -1
- package/src/modes/theme/defaults/claude-code.json +100 -0
- package/src/modes/theme/defaults/codex.json +100 -0
- package/src/modes/theme/defaults/index.ts +6 -0
- package/src/modes/theme/defaults/opencode.json +102 -0
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/types.ts +1 -1
- package/src/modes/utils/ui-helpers.ts +5 -2
- package/src/prompts/agents/executor.md +5 -2
- package/src/runtime/process-lifecycle.ts +400 -0
- package/src/runtime-mcp/manager.ts +164 -50
- package/src/runtime-mcp/transports/http.ts +12 -11
- package/src/runtime-mcp/transports/stdio.ts +64 -38
- package/src/runtime-mcp/types.ts +3 -0
- package/src/sdk.ts +39 -1
- package/src/session/agent-session.ts +190 -33
- package/src/session/artifacts.ts +17 -2
- package/src/session/blob-store.ts +36 -2
- package/src/session/history-storage.ts +32 -11
- package/src/session/session-manager.ts +99 -31
- package/src/session/streaming-output.ts +54 -3
- package/src/setup/credential-import.ts +429 -0
- package/src/skill-state/deep-interview-mutation-guard.ts +2 -1
- package/src/slash-commands/builtin-registry.ts +30 -3
- package/src/slash-commands/helpers/fast-status-report.ts +111 -0
- package/src/task/executor.ts +7 -1
- package/src/task/render.ts +18 -7
- package/src/tools/archive-reader.ts +10 -1
- package/src/tools/ask.ts +4 -2
- package/src/tools/bash.ts +11 -4
- package/src/tools/browser/tab-supervisor.ts +22 -0
- package/src/tools/browser.ts +38 -4
- package/src/tools/cron.ts +1 -1
- package/src/tools/read.ts +11 -12
- package/src/tools/sqlite-reader.ts +19 -5
- package/src/tools/subagent-render.ts +119 -29
- package/src/tools/subagent.ts +147 -7
- package/src/tools/ultragoal-ask-guard.ts +39 -0
- package/src/web/search/index.ts +25 -25
- package/src/web/search/provider.ts +178 -87
- package/src/web/search/providers/base.ts +2 -1
- package/src/web/search/providers/openai-compatible.ts +151 -0
- package/src/web/search/types.ts +47 -22
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
isBlobRef,
|
|
41
41
|
isImageDataUrl,
|
|
42
42
|
MemoryBlobStore,
|
|
43
|
+
ResidentBlobMissingError,
|
|
43
44
|
resolveImageData,
|
|
44
45
|
resolveImageDataUrl,
|
|
45
46
|
resolveResidentImageDataSync,
|
|
@@ -888,8 +889,27 @@ async function resolvePersistedBlobRefs(value: unknown, blobStore: BlobStore, ke
|
|
|
888
889
|
);
|
|
889
890
|
}
|
|
890
891
|
|
|
892
|
+
/**
|
|
893
|
+
* Run async tasks with bounded concurrency so an image-heavy resume never materializes
|
|
894
|
+
* every blob's base64 simultaneously (F8: avoids the transient OOM spike of an unbounded
|
|
895
|
+
* Promise.all over all historical images).
|
|
896
|
+
*/
|
|
897
|
+
const BLOB_RESOLVE_CONCURRENCY = 8;
|
|
898
|
+
async function runWithConcurrency(tasks: Array<() => Promise<void>>, limit: number): Promise<void> {
|
|
899
|
+
let next = 0;
|
|
900
|
+
const worker = async (): Promise<void> => {
|
|
901
|
+
while (next < tasks.length) {
|
|
902
|
+
const index = next;
|
|
903
|
+
next += 1;
|
|
904
|
+
await tasks[index]!();
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
const workerCount = Math.max(1, Math.min(limit, tasks.length));
|
|
908
|
+
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
909
|
+
}
|
|
910
|
+
|
|
891
911
|
async function resolveBlobRefsInEntries(entries: FileEntry[], blobStore: BlobStore): Promise<void> {
|
|
892
|
-
const
|
|
912
|
+
const tasks: Array<() => Promise<void>> = [];
|
|
893
913
|
|
|
894
914
|
for (const entry of entries) {
|
|
895
915
|
if (entry.type === "session") continue;
|
|
@@ -901,22 +921,19 @@ async function resolveBlobRefsInEntries(entries: FileEntry[], blobStore: BlobSto
|
|
|
901
921
|
contentArray = entry.content;
|
|
902
922
|
}
|
|
903
923
|
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
resolveImageData(blobStore, block.data)
|
|
909
|
-
|
|
910
|
-
}),
|
|
911
|
-
);
|
|
924
|
+
tasks.push(async () => {
|
|
925
|
+
if (contentArray) {
|
|
926
|
+
for (const block of contentArray) {
|
|
927
|
+
if (isImageBlock(block) && isBlobRef(block.data)) {
|
|
928
|
+
block.data = await resolveImageData(blobStore, block.data);
|
|
929
|
+
}
|
|
912
930
|
}
|
|
913
931
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
promises.push(resolvePersistedBlobRefs(entry, blobStore));
|
|
932
|
+
await resolvePersistedBlobRefs(entry, blobStore);
|
|
933
|
+
});
|
|
917
934
|
}
|
|
918
935
|
|
|
919
|
-
await
|
|
936
|
+
await runWithConcurrency(tasks, BLOB_RESOLVE_CONCURRENCY);
|
|
920
937
|
}
|
|
921
938
|
|
|
922
939
|
/**
|
|
@@ -1201,6 +1218,12 @@ interface ResidentBlobStores {
|
|
|
1201
1218
|
sessionFile?: string;
|
|
1202
1219
|
}
|
|
1203
1220
|
|
|
1221
|
+
type ResidentBlobMissingPolicy = "throw" | "placeholder";
|
|
1222
|
+
|
|
1223
|
+
function residentBlobMissingPlaceholder(error: ResidentBlobMissingError): string {
|
|
1224
|
+
return `[Session resident ${error.kind} blob missing: sha256:${error.hash}; original content unavailable]`;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1204
1227
|
function externalizeResidentValueSync(obj: unknown, stores: ResidentBlobStores, key?: string): unknown {
|
|
1205
1228
|
if (obj === null || obj === undefined) return obj;
|
|
1206
1229
|
if (typeof obj === "string") {
|
|
@@ -1256,6 +1279,7 @@ function materializeResidentValueSync(
|
|
|
1256
1279
|
stores: ResidentBlobStores,
|
|
1257
1280
|
key?: string,
|
|
1258
1281
|
cache = new Map<string, string>(),
|
|
1282
|
+
missingPolicy: ResidentBlobMissingPolicy = "throw",
|
|
1259
1283
|
): unknown {
|
|
1260
1284
|
if (obj === null || obj === undefined) return obj;
|
|
1261
1285
|
if (typeof obj === "string") return obj;
|
|
@@ -1263,19 +1287,28 @@ function materializeResidentValueSync(
|
|
|
1263
1287
|
const cacheKey = `${obj.kind}:${obj.ref}`;
|
|
1264
1288
|
const cached = cache.get(cacheKey);
|
|
1265
1289
|
if (cached !== undefined) return cached;
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
?
|
|
1271
|
-
:
|
|
1290
|
+
let resolved: string;
|
|
1291
|
+
try {
|
|
1292
|
+
resolved =
|
|
1293
|
+
obj.kind === "imageUrl"
|
|
1294
|
+
? resolveResidentImageDataUrlSync(stores.imageStore, obj.ref, stores)
|
|
1295
|
+
: obj.kind === "imageData"
|
|
1296
|
+
? resolveResidentImageDataSync(stores.imageStore, obj.ref, stores)
|
|
1297
|
+
: resolveTextBlobSync(stores.textStore, obj.ref, stores);
|
|
1298
|
+
} catch (err) {
|
|
1299
|
+
if (missingPolicy === "placeholder" && err instanceof ResidentBlobMissingError) {
|
|
1300
|
+
resolved = residentBlobMissingPlaceholder(err);
|
|
1301
|
+
} else {
|
|
1302
|
+
throw err;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1272
1305
|
cache.set(cacheKey, resolved);
|
|
1273
1306
|
return resolved;
|
|
1274
1307
|
}
|
|
1275
1308
|
if (Array.isArray(obj)) {
|
|
1276
1309
|
let changed = false;
|
|
1277
1310
|
const result = obj.map(item => {
|
|
1278
|
-
const newItem = materializeResidentValueSync(item, stores, key, cache);
|
|
1311
|
+
const newItem = materializeResidentValueSync(item, stores, key, cache, missingPolicy);
|
|
1279
1312
|
if (newItem !== item) changed = true;
|
|
1280
1313
|
return newItem;
|
|
1281
1314
|
});
|
|
@@ -1284,7 +1317,7 @@ function materializeResidentValueSync(
|
|
|
1284
1317
|
if (typeof obj === "object") {
|
|
1285
1318
|
let changed = false;
|
|
1286
1319
|
const entries = Object.entries(obj).map(([childKey, value]) => {
|
|
1287
|
-
const newValue = materializeResidentValueSync(value, stores, childKey, cache);
|
|
1320
|
+
const newValue = materializeResidentValueSync(value, stores, childKey, cache, missingPolicy);
|
|
1288
1321
|
if (newValue !== value) changed = true;
|
|
1289
1322
|
return [childKey, newValue] as const;
|
|
1290
1323
|
});
|
|
@@ -1297,8 +1330,9 @@ function materializeResidentEntrySync<T extends FileEntry | SessionEntry>(
|
|
|
1297
1330
|
entry: T,
|
|
1298
1331
|
stores: ResidentBlobStores,
|
|
1299
1332
|
cache: Map<string, string>,
|
|
1333
|
+
missingPolicy: ResidentBlobMissingPolicy = "throw",
|
|
1300
1334
|
): T {
|
|
1301
|
-
return materializeResidentValueSync(entry, stores, undefined, cache) as T;
|
|
1335
|
+
return materializeResidentValueSync(entry, stores, undefined, cache, missingPolicy) as T;
|
|
1302
1336
|
}
|
|
1303
1337
|
|
|
1304
1338
|
function materializeResidentEntriesSync<T extends FileEntry | SessionEntry>(
|
|
@@ -1308,6 +1342,37 @@ function materializeResidentEntriesSync<T extends FileEntry | SessionEntry>(
|
|
|
1308
1342
|
const cache = new Map<string, string>();
|
|
1309
1343
|
return entries.map(entry => materializeResidentEntrySync(entry, stores, cache));
|
|
1310
1344
|
}
|
|
1345
|
+
|
|
1346
|
+
function materializeResidentEntryForPersistenceSync<T extends FileEntry | SessionEntry>(
|
|
1347
|
+
entry: T,
|
|
1348
|
+
stores: ResidentBlobStores,
|
|
1349
|
+
cache: Map<string, string>,
|
|
1350
|
+
): T {
|
|
1351
|
+
return materializeResidentEntrySync(entry, stores, cache, "placeholder");
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
function materializeResidentEntriesForPersistenceSync<T extends FileEntry | SessionEntry>(
|
|
1355
|
+
entries: T[],
|
|
1356
|
+
stores: ResidentBlobStores,
|
|
1357
|
+
): T[] {
|
|
1358
|
+
const cache = new Map<string, string>();
|
|
1359
|
+
return entries.map(entry => materializeResidentEntryForPersistenceSync(entry, stores, cache));
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
export function residentBlobSentinelForTests(kind: ResidentBlobKind, ref: string): ResidentBlobSentinel {
|
|
1363
|
+
return residentBlobSentinel(kind, ref);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
export function materializeResidentEntriesForPersistenceForTests<T>(
|
|
1367
|
+
entries: T[],
|
|
1368
|
+
textStore: BlobStore,
|
|
1369
|
+
imageStore: BlobStore = textStore,
|
|
1370
|
+
): T[] {
|
|
1371
|
+
return materializeResidentEntriesForPersistenceSync(entries as Array<T & FileEntry>, {
|
|
1372
|
+
textStore,
|
|
1373
|
+
imageStore,
|
|
1374
|
+
}) as T[];
|
|
1375
|
+
}
|
|
1311
1376
|
function cloneJsonSemantic<T>(value: T): T {
|
|
1312
1377
|
if (value === null || value === undefined || typeof value !== "object") return value;
|
|
1313
1378
|
if (Array.isArray(value)) return value.map(item => cloneJsonSemantic(item)) as T;
|
|
@@ -2109,7 +2174,7 @@ export class SessionManager {
|
|
|
2109
2174
|
#labelRevision = 0;
|
|
2110
2175
|
#replayMetadataRevision = 0;
|
|
2111
2176
|
#materializedEntriesRevision = -1;
|
|
2112
|
-
#materializedEntriesCache:
|
|
2177
|
+
#materializedEntriesCache: SessionEntry[] | undefined;
|
|
2113
2178
|
#sessionContextCache: WeakRef<SessionContext> | undefined;
|
|
2114
2179
|
#sessionContextEntryRevision = -1;
|
|
2115
2180
|
#sessionContextLeafRevision = -1;
|
|
@@ -2817,7 +2882,7 @@ export class SessionManager {
|
|
|
2817
2882
|
await this.#queuePersistTask(async () => {
|
|
2818
2883
|
await this.#closePersistWriterInternal();
|
|
2819
2884
|
const entries = await Promise.all(
|
|
2820
|
-
|
|
2885
|
+
materializeResidentEntriesForPersistenceSync(this.#fileEntries, this.#residentBlobStores()).map(entry =>
|
|
2821
2886
|
prepareEntryForPersistence(entry, this.#blobStore),
|
|
2822
2887
|
),
|
|
2823
2888
|
);
|
|
@@ -2831,8 +2896,8 @@ export class SessionManager {
|
|
|
2831
2896
|
#rewriteFileSync(): void {
|
|
2832
2897
|
if (!this.persist || !this.#sessionFile) return;
|
|
2833
2898
|
this.#closePersistWriterInternalSync();
|
|
2834
|
-
const entries =
|
|
2835
|
-
prepareEntryForPersistenceSync(entry, this.#blobStore),
|
|
2899
|
+
const entries = materializeResidentEntriesForPersistenceSync(this.#fileEntries, this.#residentBlobStores()).map(
|
|
2900
|
+
entry => prepareEntryForPersistenceSync(entry, this.#blobStore),
|
|
2836
2901
|
);
|
|
2837
2902
|
this.#writeEntriesAtomicallySync(entries);
|
|
2838
2903
|
this.#needsFullRewriteOnNextPersist = false;
|
|
@@ -3139,7 +3204,11 @@ export class SessionManager {
|
|
|
3139
3204
|
this.#rewriteFile().catch(() => {});
|
|
3140
3205
|
return;
|
|
3141
3206
|
}
|
|
3142
|
-
const materializedEntry =
|
|
3207
|
+
const materializedEntry = materializeResidentEntryForPersistenceSync(
|
|
3208
|
+
entry,
|
|
3209
|
+
this.#residentBlobStores(),
|
|
3210
|
+
new Map(),
|
|
3211
|
+
);
|
|
3143
3212
|
const persistedEntry = prepareEntryForPersistenceSync(materializedEntry, this.#blobStore);
|
|
3144
3213
|
writer.writeSync(persistedEntry);
|
|
3145
3214
|
} catch (err) {
|
|
@@ -3608,15 +3677,14 @@ export class SessionManager {
|
|
|
3608
3677
|
* change the leaf pointer. Entries cannot be modified or deleted.
|
|
3609
3678
|
*/
|
|
3610
3679
|
#getMaterializedEntriesInternal(): SessionEntry[] {
|
|
3611
|
-
if (this.#materializedEntriesRevision === this.#entryRevision) {
|
|
3612
|
-
|
|
3613
|
-
if (cached) return cached;
|
|
3680
|
+
if (this.#materializedEntriesRevision === this.#entryRevision && this.#materializedEntriesCache) {
|
|
3681
|
+
return this.#materializedEntriesCache;
|
|
3614
3682
|
}
|
|
3615
3683
|
const resolvedTextBlobCache = new Map<string, string>();
|
|
3616
3684
|
const materializedEntries = this.#fileEntries
|
|
3617
3685
|
.filter((e): e is SessionEntry => e.type !== "session")
|
|
3618
3686
|
.map(entry => materializeResidentEntrySync(entry, this.#residentBlobStores(), resolvedTextBlobCache));
|
|
3619
|
-
this.#materializedEntriesCache =
|
|
3687
|
+
this.#materializedEntriesCache = materializedEntries;
|
|
3620
3688
|
this.#materializedEntriesRevision = this.#entryRevision;
|
|
3621
3689
|
return materializedEntries;
|
|
3622
3690
|
}
|
|
@@ -15,6 +15,7 @@ function sanitizeOutputChunk(rawChunk: string): string {
|
|
|
15
15
|
export const DEFAULT_MAX_LINES = 3000;
|
|
16
16
|
export const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB
|
|
17
17
|
export const DEFAULT_MAX_COLUMN = 1024; // Max chars per grep match line
|
|
18
|
+
export const DEFAULT_ARTIFACT_MAX_BYTES = 10 * 1024 * 1024; // 10MB
|
|
18
19
|
|
|
19
20
|
const NL = "\n";
|
|
20
21
|
|
|
@@ -41,6 +42,8 @@ export interface OutputSummary {
|
|
|
41
42
|
columnTruncatedLines?: number;
|
|
42
43
|
/** Artifact ID for internal URL access (artifact://<id>) when truncated */
|
|
43
44
|
artifactId?: string;
|
|
45
|
+
/** Bytes omitted from artifact storage after the artifact hard cap was reached. */
|
|
46
|
+
artifactTruncatedBytes?: number;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
export interface OutputSinkOptions {
|
|
@@ -61,6 +64,8 @@ export interface OutputSinkOptions {
|
|
|
61
64
|
* writes still respect the budget. Default 0 = no per-line cap.
|
|
62
65
|
*/
|
|
63
66
|
maxColumns?: number;
|
|
67
|
+
/** Hard cap for artifact writes/pending replay. Default DEFAULT_ARTIFACT_MAX_BYTES. */
|
|
68
|
+
artifactMaxBytes?: number;
|
|
64
69
|
onChunk?: (chunk: string) => void;
|
|
65
70
|
/** Minimum ms between onChunk calls. 0 = every chunk (default). */
|
|
66
71
|
chunkThrottleMs?: number;
|
|
@@ -668,6 +673,9 @@ export class OutputSink {
|
|
|
668
673
|
#sawData = false;
|
|
669
674
|
#truncated = false;
|
|
670
675
|
#lastChunkTime = 0;
|
|
676
|
+
#artifactBytes = 0;
|
|
677
|
+
#artifactTruncatedBytes = 0;
|
|
678
|
+
#artifactTruncationNoticeWritten = false;
|
|
671
679
|
|
|
672
680
|
// Per-line column cap streaming state (persists across `push` calls so a
|
|
673
681
|
// long line split across chunks still trips the same trigger).
|
|
@@ -697,6 +705,7 @@ export class OutputSink {
|
|
|
697
705
|
readonly #onRawChunk?: (chunk: string) => void;
|
|
698
706
|
readonly #chunkThrottleMs: number;
|
|
699
707
|
readonly #maxColumns: number;
|
|
708
|
+
readonly #artifactMaxBytes: number;
|
|
700
709
|
|
|
701
710
|
constructor(options?: OutputSinkOptions) {
|
|
702
711
|
const {
|
|
@@ -708,6 +717,7 @@ export class OutputSink {
|
|
|
708
717
|
onChunk,
|
|
709
718
|
chunkThrottleMs = 0,
|
|
710
719
|
onRawChunk,
|
|
720
|
+
artifactMaxBytes = DEFAULT_ARTIFACT_MAX_BYTES,
|
|
711
721
|
} = options ?? {};
|
|
712
722
|
this.#artifactPath = artifactPath;
|
|
713
723
|
this.#artifactId = artifactId;
|
|
@@ -717,6 +727,7 @@ export class OutputSink {
|
|
|
717
727
|
this.#onChunk = onChunk;
|
|
718
728
|
this.#onRawChunk = onRawChunk;
|
|
719
729
|
this.#chunkThrottleMs = chunkThrottleMs;
|
|
730
|
+
this.#artifactMaxBytes = Math.max(0, artifactMaxBytes);
|
|
720
731
|
}
|
|
721
732
|
|
|
722
733
|
#headText(): string {
|
|
@@ -907,6 +918,40 @@ export class OutputSink {
|
|
|
907
918
|
}
|
|
908
919
|
}
|
|
909
920
|
|
|
921
|
+
#artifactTruncationNotice(droppedBytes: number): string {
|
|
922
|
+
return `\n[artifact truncated after ${this.#artifactBytes} bytes; omitted at least ${droppedBytes} bytes]\n`;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
#capArtifactChunk(chunk: string, bytes: number): { chunk: string; bytes: number } | null {
|
|
926
|
+
if (bytes === 0) return null;
|
|
927
|
+
if (this.#artifactMaxBytes <= 0 || this.#artifactBytes >= this.#artifactMaxBytes) {
|
|
928
|
+
this.#artifactTruncatedBytes += bytes;
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
const room = this.#artifactMaxBytes - this.#artifactBytes;
|
|
932
|
+
if (bytes <= room) {
|
|
933
|
+
return { chunk, bytes };
|
|
934
|
+
}
|
|
935
|
+
const kept = truncateHeadBytes(chunk, room);
|
|
936
|
+
this.#artifactTruncatedBytes += bytes - kept.bytes;
|
|
937
|
+
return kept.bytes > 0 ? { chunk: kept.text, bytes: kept.bytes } : null;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
#writeArtifactTruncationNotice(): void {
|
|
941
|
+
if (this.#artifactTruncatedBytes <= 0 || this.#artifactTruncationNoticeWritten) return;
|
|
942
|
+
const notice = this.#artifactTruncationNotice(this.#artifactTruncatedBytes);
|
|
943
|
+
try {
|
|
944
|
+
if (this.#fileReady && this.#file) {
|
|
945
|
+
this.#file.sink.write(notice);
|
|
946
|
+
} else {
|
|
947
|
+
this.#queuePendingFileWrite(notice, Buffer.byteLength(notice, "utf-8"));
|
|
948
|
+
}
|
|
949
|
+
this.#artifactTruncationNoticeWritten = true;
|
|
950
|
+
} catch {
|
|
951
|
+
/* ignore */
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
910
955
|
#queuePendingFileWrite(chunk: string, bytes = Buffer.byteLength(chunk, "utf-8")): void {
|
|
911
956
|
if (!this.#pendingFileWrites) this.#pendingFileWrites = [chunk];
|
|
912
957
|
else this.#pendingFileWrites.push(chunk);
|
|
@@ -915,14 +960,17 @@ export class OutputSink {
|
|
|
915
960
|
}
|
|
916
961
|
|
|
917
962
|
#enqueueFileWrite(chunk: string, bytes: number): void {
|
|
963
|
+
const capped = this.#capArtifactChunk(chunk, bytes);
|
|
964
|
+
if (!capped) return;
|
|
965
|
+
this.#artifactBytes += capped.bytes;
|
|
918
966
|
if (!this.#fileReady || !this.#file) {
|
|
919
|
-
this.#queuePendingFileWrite(chunk, bytes);
|
|
967
|
+
this.#queuePendingFileWrite(capped.chunk, capped.bytes);
|
|
920
968
|
if (this.#willOverflow(bytes) || this.#pendingFileWriteBytes > this.#spillThreshold) this.#createFileSink();
|
|
921
969
|
return;
|
|
922
970
|
}
|
|
923
971
|
|
|
924
972
|
try {
|
|
925
|
-
this.#file.sink.write(chunk);
|
|
973
|
+
this.#file.sink.write(capped.chunk);
|
|
926
974
|
} catch {
|
|
927
975
|
try {
|
|
928
976
|
void this.#file.sink.end();
|
|
@@ -931,7 +979,7 @@ export class OutputSink {
|
|
|
931
979
|
}
|
|
932
980
|
this.#file = undefined;
|
|
933
981
|
this.#fileReady = false;
|
|
934
|
-
this.#queuePendingFileWrite(chunk, bytes);
|
|
982
|
+
this.#queuePendingFileWrite(capped.chunk, capped.bytes);
|
|
935
983
|
this.#createFileSink();
|
|
936
984
|
}
|
|
937
985
|
}
|
|
@@ -1019,6 +1067,8 @@ export class OutputSink {
|
|
|
1019
1067
|
const totalLines = this.#sawData ? this.#totalLines + 1 : 0;
|
|
1020
1068
|
|
|
1021
1069
|
let artifactId: string | undefined;
|
|
1070
|
+
if (this.#artifactTruncatedBytes > 0) this.#createFileSink();
|
|
1071
|
+
this.#writeArtifactTruncationNotice();
|
|
1022
1072
|
if (this.#file) {
|
|
1023
1073
|
artifactId = this.#file.artifactId;
|
|
1024
1074
|
await this.#file.sink.end();
|
|
@@ -1095,6 +1145,7 @@ export class OutputSink {
|
|
|
1095
1145
|
elidedLines,
|
|
1096
1146
|
columnDroppedBytes: this.#columnDroppedBytes > 0 ? this.#columnDroppedBytes : undefined,
|
|
1097
1147
|
columnTruncatedLines: this.#columnTruncatedLines > 0 ? this.#columnTruncatedLines : undefined,
|
|
1148
|
+
artifactTruncatedBytes: this.#artifactTruncatedBytes > 0 ? this.#artifactTruncatedBytes : undefined,
|
|
1098
1149
|
artifactId,
|
|
1099
1150
|
};
|
|
1100
1151
|
}
|