@gajae-code/coding-agent 0.12.6 → 0.12.8
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 +22 -1
- package/dist/types/async/job-manager.d.ts +36 -1
- package/dist/types/commands/notify.d.ts +4 -0
- package/dist/types/config/file-lock.d.ts +3 -0
- package/dist/types/config/model-discovery-manager.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +2 -0
- package/dist/types/config/resolve-config-value.d.ts +2 -1
- package/dist/types/config/settings-schema.d.ts +17 -1
- package/dist/types/eval/backend.d.ts +2 -2
- package/dist/types/exec/bash-executor.d.ts +8 -10
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -36
- package/dist/types/internal-urls/registry-helpers.d.ts +0 -18
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/components/hook-selector.d.ts +20 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +53 -4
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +4 -2
- package/dist/types/sdk/broker/broker.d.ts +5 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -3
- package/dist/types/sdk/bus/config.d.ts +75 -113
- package/dist/types/sdk/bus/discord-live-provider.d.ts +8 -2
- package/dist/types/sdk/bus/discord-provider.d.ts +19 -0
- package/dist/types/sdk/bus/index.d.ts +1 -1
- package/dist/types/sdk/bus/notification-orchestration.d.ts +127 -18
- package/dist/types/sdk/bus/notification-service.d.ts +31 -4
- package/dist/types/sdk/bus/session-control.d.ts +8 -3
- package/dist/types/sdk/bus/slack-live-provider.d.ts +9 -2
- package/dist/types/sdk/bus/slack-provider.d.ts +22 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
- package/dist/types/sdk/client/discovery.d.ts +2 -1
- package/dist/types/sdk/host/host.d.ts +2 -23
- package/dist/types/sdk/host/query/handlers.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +4 -7
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/providers.d.ts +22 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/session/artifacts.d.ts +5 -8
- package/dist/types/session/client-bridge.d.ts +0 -14
- package/dist/types/session/internal/managed-session-scope.d.ts +17 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +11 -4
- package/dist/types/session/internal/native-publish-outcome.d.ts +1 -0
- package/dist/types/session/internal/session-open-errors.d.ts +5 -0
- package/dist/types/session/messages.d.ts +1 -4
- package/dist/types/session/session-manager.d.ts +7 -6
- package/dist/types/session/session-storage.d.ts +2 -0
- package/dist/types/session/streaming-output.d.ts +0 -32
- package/dist/types/task/receipt.d.ts +1 -2
- package/dist/types/task/types.d.ts +43 -178
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/fetch.d.ts +0 -3
- package/dist/types/tools/output-meta.d.ts +5 -37
- package/dist/types/tools/subagent.d.ts +15 -0
- package/dist/types/tools/todo-write.d.ts +3 -0
- package/dist/types/tools/tool-result.d.ts +0 -1
- package/package.json +10 -8
- package/scripts/build-sdk-package-smoke.ts +51 -0
- package/scripts/capture-notifications-settings-showcase.ts +1 -1
- package/src/async/job-manager.ts +430 -46
- package/src/cli/notify-cli.ts +204 -94
- package/src/commands/notify.ts +19 -8
- package/src/config/file-lock.ts +6 -0
- package/src/config/model-discovery-manager.ts +50 -8
- package/src/config/model-registry.ts +862 -75
- package/src/config/resolve-config-value.ts +10 -8
- package/src/config/settings-schema.ts +25 -15
- package/src/config/settings.ts +15 -15
- package/src/cursor.ts +13 -20
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -3
- package/src/eval/backend.ts +2 -2
- package/src/exec/bash-executor.ts +151 -425
- package/src/gjc-runtime/session-state-sidecar.ts +12 -2
- package/src/gjc-runtime/ultragoal-runtime.ts +3 -280
- package/src/gjc-runtime/workflow-manifest.generated.json +1 -40
- package/src/gjc-runtime/workflow-manifest.ts +0 -12
- package/src/internal-urls/agent-protocol.ts +38 -98
- package/src/internal-urls/artifact-protocol.ts +17 -28
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/internal-urls/registry-helpers.ts +0 -62
- package/src/main.ts +43 -7
- package/src/modes/DESIGN.md +11 -11
- package/src/modes/acp/acp-agent.ts +0 -24
- package/src/modes/acp/acp-client-bridge.ts +11 -154
- package/src/modes/components/hook-selector.ts +155 -44
- package/src/modes/components/model-selector.ts +242 -49
- package/src/modes/components/notifications-settings-editor.ts +631 -85
- package/src/modes/controllers/extension-ui-controller.ts +114 -15
- package/src/modes/controllers/selector-controller.ts +464 -50
- package/src/prompts/tools/bash.md +2 -2
- package/src/sdk/acp/adapter.ts +9 -200
- package/src/sdk/broker/broker.ts +54 -10
- package/src/sdk/broker/lifecycle-ledger.ts +34 -1
- package/src/sdk/broker/lifecycle.ts +47 -50
- package/src/sdk/bus/chat-daemon-cli.ts +12 -5
- package/src/sdk/bus/chat-daemon-control.ts +35 -8
- package/src/sdk/bus/chat-daemon-runtime.ts +16 -2
- package/src/sdk/bus/config.ts +534 -286
- package/src/sdk/bus/discord-live-provider.ts +57 -2
- package/src/sdk/bus/discord-provider.ts +21 -0
- package/src/sdk/bus/index.ts +105 -359
- package/src/sdk/bus/notification-orchestration.ts +371 -68
- package/src/sdk/bus/notification-service.ts +431 -130
- package/src/sdk/bus/session-control.ts +119 -44
- package/src/sdk/bus/slack-live-provider.ts +78 -2
- package/src/sdk/bus/slack-provider.ts +24 -0
- package/src/sdk/bus/telegram-daemon-cli.ts +9 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
- package/src/sdk/bus/telegram-daemon-control.ts +3 -3
- package/src/sdk/bus/telegram-daemon.ts +23 -15
- package/src/sdk/client/discovery.ts +10 -4
- package/src/sdk/host/host.ts +9 -187
- package/src/sdk/host/query/handlers.ts +20 -1
- package/src/sdk/host/reverse-leases.ts +42 -249
- package/src/sdk/index.ts +1 -0
- package/src/sdk/protocol/operation-inventory.generated.json +18 -0
- package/src/sdk/protocol/operation-registry.ts +5 -2
- package/src/sdk/providers.ts +51 -0
- package/src/sdk/session-directory.ts +1 -0
- package/src/sdk/session.ts +10 -7
- package/src/session/agent-session.ts +59 -202
- package/src/session/artifacts.ts +74 -211
- package/src/session/client-bridge.ts +0 -15
- package/src/session/internal/managed-session-scope.ts +981 -450
- package/src/session/internal/managed-session-storage.ts +362 -124
- package/src/session/internal/native-publish-outcome.ts +3 -1
- package/src/session/internal/session-open-errors.ts +11 -0
- package/src/session/messages.ts +7 -32
- package/src/session/session-manager.ts +48 -15
- package/src/session/session-storage.ts +72 -5
- package/src/session/streaming-output.ts +40 -198
- package/src/slash-commands/builtin-registry.ts +82 -4
- package/src/task/index.ts +237 -65
- package/src/task/receipt.ts +2 -4
- package/src/task/render.ts +0 -30
- package/src/task/types.ts +10 -34
- package/src/tools/ask.ts +1 -1
- package/src/tools/bash.ts +239 -818
- package/src/tools/eval.ts +4 -99
- package/src/tools/fetch.ts +15 -77
- package/src/tools/find.ts +3 -7
- package/src/tools/output-meta.ts +114 -696
- package/src/tools/search.ts +4 -9
- package/src/tools/subagent.ts +97 -75
- package/src/tools/telegram-send.ts +4 -2
- package/src/tools/todo-write.ts +47 -0
- package/src/tools/tool-result.ts +0 -1
- package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +0 -83
- package/dist/types/utils/artifact-id.d.ts +0 -2
- package/src/gjc-runtime/ultragoal-review-source.ts +0 -378
- package/src/utils/artifact-id.ts +0 -6
|
@@ -11,16 +11,11 @@
|
|
|
11
11
|
* - agent://<id>?q=<query> - JSON extraction via query form
|
|
12
12
|
*/
|
|
13
13
|
import { createHash } from "node:crypto";
|
|
14
|
+
import * as fs from "node:fs/promises";
|
|
14
15
|
import * as path from "node:path";
|
|
15
16
|
import { isEnoent } from "@gajae-code/utils";
|
|
16
17
|
import { applyQuery, pathToQuery } from "./json-query";
|
|
17
|
-
import {
|
|
18
|
-
type AuthorizedArtifactsRoot,
|
|
19
|
-
authorizedArtifactsDirsFromContext,
|
|
20
|
-
closeAuthorizedArtifactsRoot,
|
|
21
|
-
openAuthorizedArtifactsRoot,
|
|
22
|
-
readAuthorizedArtifactFile,
|
|
23
|
-
} from "./registry-helpers";
|
|
18
|
+
import { authorizedArtifactsDirsFromContext } from "./registry-helpers";
|
|
24
19
|
import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext } from "./types";
|
|
25
20
|
|
|
26
21
|
interface AgentOutputMetadata {
|
|
@@ -41,24 +36,6 @@ interface ManagedOutputSelector {
|
|
|
41
36
|
metadataSha256: string;
|
|
42
37
|
}
|
|
43
38
|
|
|
44
|
-
const MAX_AGENT_SELECTOR_BYTES = 64 * 1024;
|
|
45
|
-
const MAX_AGENT_METADATA_BYTES = 1024 * 1024;
|
|
46
|
-
const MAX_AGENT_OUTPUT_BYTES = 16 * 1024 * 1024;
|
|
47
|
-
|
|
48
|
-
function readRetainedAgentFile(
|
|
49
|
-
outputId: string,
|
|
50
|
-
root: AuthorizedArtifactsRoot,
|
|
51
|
-
filename: string,
|
|
52
|
-
maxBytes: number,
|
|
53
|
-
): Buffer | null {
|
|
54
|
-
const captured = readAuthorizedArtifactFile(root, filename, maxBytes);
|
|
55
|
-
if (!captured) return null;
|
|
56
|
-
if (captured.size !== captured.bytes.byteLength) {
|
|
57
|
-
throw new Error(`agent://${outputId} retained file exceeds read capacity`);
|
|
58
|
-
}
|
|
59
|
-
return captured.bytes;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
39
|
function isSafeGenerationFilename(filename: unknown): filename is string {
|
|
63
40
|
return typeof filename === "string" && /^[a-zA-Z0-9_.-]+$/.test(filename);
|
|
64
41
|
}
|
|
@@ -79,14 +56,17 @@ function isManagedOutputSelector(value: unknown, outputId: string): value is Man
|
|
|
79
56
|
);
|
|
80
57
|
}
|
|
81
58
|
|
|
82
|
-
function readManagedOutputSelector(
|
|
59
|
+
async function readManagedOutputSelector(
|
|
83
60
|
outputId: string,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
61
|
+
selectorPath: string,
|
|
62
|
+
): Promise<ManagedOutputSelector | null> {
|
|
63
|
+
let raw: string;
|
|
64
|
+
try {
|
|
65
|
+
raw = await Bun.file(selectorPath).text();
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (isEnoent(error)) return null;
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
90
70
|
let parsed: unknown;
|
|
91
71
|
try {
|
|
92
72
|
parsed = JSON.parse(raw);
|
|
@@ -96,18 +76,6 @@ function readManagedOutputSelector(
|
|
|
96
76
|
if (!isManagedOutputSelector(parsed, outputId)) throw new Error(`agent://${outputId} malformed output selector`);
|
|
97
77
|
return parsed;
|
|
98
78
|
}
|
|
99
|
-
function sameManagedOutputSelector(left: ManagedOutputSelector, right: ManagedOutputSelector): boolean {
|
|
100
|
-
return (
|
|
101
|
-
left.outputFilename === right.outputFilename &&
|
|
102
|
-
left.metadataFilename === right.metadataFilename &&
|
|
103
|
-
left.outputSizeBytes === right.outputSizeBytes &&
|
|
104
|
-
left.outputSha256 === right.outputSha256 &&
|
|
105
|
-
left.metadataSizeBytes === right.metadataSizeBytes &&
|
|
106
|
-
left.metadataSha256 === right.metadataSha256
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const MANAGED_SELECTOR_READ_ATTEMPTS = 8;
|
|
111
79
|
|
|
112
80
|
function isAgentOutputMetadata(value: unknown, outputId: string): value is AgentOutputMetadata {
|
|
113
81
|
if (!value || typeof value !== "object") return false;
|
|
@@ -124,11 +92,18 @@ function isAgentOutputMetadata(value: unknown, outputId: string): value is Agent
|
|
|
124
92
|
|
|
125
93
|
async function verifyAgentOutputMetadata(
|
|
126
94
|
outputId: string,
|
|
127
|
-
|
|
95
|
+
foundPath: string,
|
|
96
|
+
metadataPath: string,
|
|
128
97
|
bytes: Buffer,
|
|
129
98
|
selector?: ManagedOutputSelector,
|
|
130
99
|
): Promise<void> {
|
|
131
|
-
|
|
100
|
+
let metaRaw: string;
|
|
101
|
+
try {
|
|
102
|
+
metaRaw = await Bun.file(metadataPath).text();
|
|
103
|
+
} catch (err) {
|
|
104
|
+
if (isEnoent(err)) throw new Error(`agent://${outputId} missing metadata`);
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
132
107
|
let parsed: unknown;
|
|
133
108
|
try {
|
|
134
109
|
parsed = JSON.parse(metaRaw);
|
|
@@ -138,7 +113,9 @@ async function verifyAgentOutputMetadata(
|
|
|
138
113
|
if (!isAgentOutputMetadata(parsed, outputId)) {
|
|
139
114
|
throw new Error(`agent://${outputId} malformed metadata`);
|
|
140
115
|
}
|
|
141
|
-
|
|
116
|
+
const metadataBytes = Buffer.from(metaRaw, "utf8");
|
|
117
|
+
const stat = await fs.stat(foundPath);
|
|
118
|
+
if (stat.size !== parsed.sizeBytes || bytes.byteLength !== parsed.sizeBytes) {
|
|
142
119
|
throw new Error(`agent://${outputId} size mismatch`);
|
|
143
120
|
}
|
|
144
121
|
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
@@ -195,64 +172,27 @@ export class AgentProtocolHandler implements ProtocolHandler {
|
|
|
195
172
|
let foundPath: string | undefined;
|
|
196
173
|
let foundMetadataPath: string | undefined;
|
|
197
174
|
let foundSelector: ManagedOutputSelector | undefined;
|
|
198
|
-
let foundBytes: Buffer | undefined;
|
|
199
|
-
let foundMetadataBytes: Buffer | undefined;
|
|
200
175
|
let anyDirExists = false;
|
|
201
176
|
|
|
202
177
|
for (const dir of dirs) {
|
|
203
|
-
let root: AuthorizedArtifactsRoot;
|
|
204
178
|
try {
|
|
205
|
-
|
|
179
|
+
await fs.stat(dir);
|
|
206
180
|
anyDirExists = true;
|
|
207
181
|
} catch (err) {
|
|
208
182
|
if (isEnoent(err)) continue;
|
|
209
183
|
throw err;
|
|
210
184
|
}
|
|
185
|
+
const selector = await readManagedOutputSelector(outputId, path.join(dir, `${outputId}.md.selector.json`));
|
|
186
|
+
const candidate = selector ? path.join(dir, selector.outputFilename) : path.join(dir, `${outputId}.md`);
|
|
187
|
+
const metadataPath = selector ? path.join(dir, selector.metadataFilename) : `${candidate}.meta.json`;
|
|
211
188
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (rawBuffer && metadataBuffer) {
|
|
220
|
-
if (selector) {
|
|
221
|
-
const refreshed = readManagedOutputSelector(outputId, root, selectorFilename);
|
|
222
|
-
if (!refreshed || !sameManagedOutputSelector(selector, refreshed)) {
|
|
223
|
-
if (attempt + 1 >= MANAGED_SELECTOR_READ_ATTEMPTS) {
|
|
224
|
-
throw new Error(`agent://${outputId} generation changed during read`);
|
|
225
|
-
}
|
|
226
|
-
if (!refreshed) throw new Error(`agent://${outputId} generation changed during read`);
|
|
227
|
-
selector = refreshed;
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
if (foundPath) throw new Error(`agent://${outputId} ambiguous id in authorized artifacts`);
|
|
232
|
-
foundPath = path.join(dir, candidateFilename);
|
|
233
|
-
foundMetadataPath = path.join(dir, metadataFilename);
|
|
234
|
-
foundSelector = selector ?? undefined;
|
|
235
|
-
foundBytes = rawBuffer;
|
|
236
|
-
foundMetadataBytes = metadataBuffer;
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
if (!selector) {
|
|
240
|
-
if (!rawBuffer) break;
|
|
241
|
-
throw new Error(`agent://${outputId} missing metadata`);
|
|
242
|
-
}
|
|
243
|
-
if (attempt + 1 >= MANAGED_SELECTOR_READ_ATTEMPTS) {
|
|
244
|
-
throw new Error(`agent://${outputId} generation changed during read`);
|
|
245
|
-
}
|
|
246
|
-
const refreshed = readManagedOutputSelector(outputId, root, selectorFilename);
|
|
247
|
-
if (!refreshed) throw new Error(`agent://${outputId} generation changed during read`);
|
|
248
|
-
if (sameManagedOutputSelector(selector, refreshed)) {
|
|
249
|
-
if (!rawBuffer) throw new Error(`agent://${outputId} selected generation unavailable`);
|
|
250
|
-
throw new Error(`agent://${outputId} missing metadata`);
|
|
251
|
-
}
|
|
252
|
-
selector = refreshed;
|
|
253
|
-
}
|
|
254
|
-
} finally {
|
|
255
|
-
closeAuthorizedArtifactsRoot(root);
|
|
189
|
+
await fs.stat(candidate);
|
|
190
|
+
if (foundPath) throw new Error(`agent://${outputId} ambiguous id in authorized artifacts`);
|
|
191
|
+
foundPath = candidate;
|
|
192
|
+
foundMetadataPath = metadataPath;
|
|
193
|
+
foundSelector = selector ?? undefined;
|
|
194
|
+
} catch (err) {
|
|
195
|
+
if (!isEnoent(err)) throw err;
|
|
256
196
|
}
|
|
257
197
|
}
|
|
258
198
|
|
|
@@ -260,12 +200,12 @@ export class AgentProtocolHandler implements ProtocolHandler {
|
|
|
260
200
|
throw new Error("No artifacts directory found");
|
|
261
201
|
}
|
|
262
202
|
|
|
263
|
-
if (!foundPath
|
|
203
|
+
if (!foundPath) {
|
|
264
204
|
throw new Error(`agent://${outputId} not found`);
|
|
265
205
|
}
|
|
266
206
|
|
|
267
|
-
const rawBytes =
|
|
268
|
-
await verifyAgentOutputMetadata(outputId,
|
|
207
|
+
const rawBytes = Buffer.from(await Bun.file(foundPath).arrayBuffer());
|
|
208
|
+
await verifyAgentOutputMetadata(outputId, foundPath, foundMetadataPath!, rawBytes, foundSelector);
|
|
269
209
|
const rawContent = rawBytes.toString("utf8");
|
|
270
210
|
const notes: string[] = [];
|
|
271
211
|
let content = rawContent;
|
|
@@ -12,14 +12,7 @@
|
|
|
12
12
|
import * as fs from "node:fs/promises";
|
|
13
13
|
import * as path from "node:path";
|
|
14
14
|
import { isEnoent } from "@gajae-code/utils";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
type AuthorizedArtifactsRoot,
|
|
18
|
-
authorizedArtifactsDirsFromContext,
|
|
19
|
-
closeAuthorizedArtifactsRoot,
|
|
20
|
-
openAuthorizedArtifactsRoot,
|
|
21
|
-
readAuthorizedArtifactFile,
|
|
22
|
-
} from "./registry-helpers";
|
|
15
|
+
import { authorizedArtifactsDirsFromContext } from "./registry-helpers";
|
|
23
16
|
import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext } from "./types";
|
|
24
17
|
|
|
25
18
|
export class ArtifactProtocolHandler implements ProtocolHandler {
|
|
@@ -31,7 +24,7 @@ export class ArtifactProtocolHandler implements ProtocolHandler {
|
|
|
31
24
|
if (!id) {
|
|
32
25
|
throw new Error("artifact:// URL requires a numeric ID: artifact://0");
|
|
33
26
|
}
|
|
34
|
-
if (
|
|
27
|
+
if (!/^\d+$/.test(id)) {
|
|
35
28
|
throw new Error(`artifact:// ID must be numeric, got: ${id}`);
|
|
36
29
|
}
|
|
37
30
|
|
|
@@ -42,33 +35,23 @@ export class ArtifactProtocolHandler implements ProtocolHandler {
|
|
|
42
35
|
}
|
|
43
36
|
|
|
44
37
|
let foundPath: string | undefined;
|
|
45
|
-
let foundBytes: Buffer | undefined;
|
|
46
|
-
let foundSize = 0;
|
|
47
38
|
let anyDirExists = false;
|
|
48
|
-
const MAX_ARTIFACT_READ_BYTES = 16 * 1024 * 1024;
|
|
49
39
|
|
|
50
40
|
for (const dir of dirs) {
|
|
51
|
-
let
|
|
41
|
+
let files: string[];
|
|
52
42
|
try {
|
|
53
|
-
|
|
43
|
+
files = await fs.readdir(dir);
|
|
54
44
|
anyDirExists = true;
|
|
55
45
|
} catch (err) {
|
|
56
46
|
if (isEnoent(err)) continue;
|
|
57
47
|
throw err;
|
|
58
48
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (f.endsWith(".meta.json") || !f.startsWith(`${id}.`)) continue;
|
|
63
|
-
const captured = readAuthorizedArtifactFile(root, f, MAX_ARTIFACT_READ_BYTES);
|
|
64
|
-
if (!captured) continue;
|
|
49
|
+
for (const f of files) {
|
|
50
|
+
if (f.endsWith(".meta.json")) continue;
|
|
51
|
+
if (f.startsWith(`${id}.`)) {
|
|
65
52
|
if (foundPath) throw new Error(`artifact://${id} ambiguous id in authorized artifacts`);
|
|
66
53
|
foundPath = path.join(dir, f);
|
|
67
|
-
foundBytes = captured.bytes;
|
|
68
|
-
foundSize = captured.size;
|
|
69
54
|
}
|
|
70
|
-
} finally {
|
|
71
|
-
closeAuthorizedArtifactsRoot(root);
|
|
72
55
|
}
|
|
73
56
|
}
|
|
74
57
|
|
|
@@ -76,14 +59,20 @@ export class ArtifactProtocolHandler implements ProtocolHandler {
|
|
|
76
59
|
throw new Error("No artifacts directory found");
|
|
77
60
|
}
|
|
78
61
|
|
|
79
|
-
if (!foundPath
|
|
62
|
+
if (!foundPath) {
|
|
80
63
|
throw new Error(`artifact://${id} not found`);
|
|
81
64
|
}
|
|
82
65
|
|
|
66
|
+
// F20: cap the materialized artifact so reading a huge spilled artifact cannot
|
|
67
|
+
// buffer GBs into memory (the range selector is applied downstream, so without a
|
|
68
|
+
// cap a `artifact://id:range` over a multi-GB artifact still reads it whole).
|
|
69
|
+
const MAX_ARTIFACT_READ_BYTES = 16 * 1024 * 1024;
|
|
70
|
+
const file = Bun.file(foundPath);
|
|
71
|
+
const fullSize = file.size;
|
|
83
72
|
const content =
|
|
84
|
-
|
|
85
|
-
? `${
|
|
86
|
-
:
|
|
73
|
+
fullSize > MAX_ARTIFACT_READ_BYTES
|
|
74
|
+
? `${await file.slice(0, MAX_ARTIFACT_READ_BYTES).text()}\n\n[Artifact truncated: first ${MAX_ARTIFACT_READ_BYTES} of ${fullSize} bytes shown; use a narrower range or a specialized tool for the full content.]`
|
|
75
|
+
: await file.text();
|
|
87
76
|
return {
|
|
88
77
|
url: url.href,
|
|
89
78
|
content,
|