@gajae-code/coding-agent 0.12.5 → 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 +32 -0
- package/README.md +4 -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 +4 -3
- package/dist/types/sdk/bus/index.d.ts +4 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -2
- 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 +5 -2
- package/dist/types/session/messages.d.ts +4 -1
- package/dist/types/session/session-manager.d.ts +6 -4
- package/dist/types/session/session-storage.d.ts +8 -0
- package/dist/types/session/streaming-output.d.ts +32 -0
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/types.d.ts +178 -0
- 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/tool-result.d.ts +1 -0
- package/dist/types/utils/artifact-id.d.ts +2 -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/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 +5 -5
- 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 +691 -92
- package/src/sdk/bus/chat-daemon-control.ts +32 -6
- package/src/sdk/bus/index.ts +347 -14
- package/src/sdk/bus/telegram-daemon-contract.ts +4 -2
- 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 +7 -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 +252 -67
- package/src/session/internal/managed-session-storage.ts +105 -5
- package/src/session/messages.ts +32 -7
- package/src/session/session-manager.ts +19 -21
- package/src/session/session-storage.ts +146 -24
- package/src/session/streaming-output.ts +198 -40
- package/src/task/index.ts +45 -0
- package/src/task/provider-retry-status.ts +4 -1
- package/src/task/receipt.ts +4 -0
- package/src/task/render.ts +30 -0
- package/src/task/types.ts +34 -0
- 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/tool-result.ts +1 -0
- package/src/utils/artifact-id.ts +6 -0
|
@@ -1745,6 +1745,40 @@
|
|
|
1745
1745
|
"name": "goal-id",
|
|
1746
1746
|
"type": "string"
|
|
1747
1747
|
},
|
|
1748
|
+
{
|
|
1749
|
+
"appliesToVerbs": [
|
|
1750
|
+
"review-source"
|
|
1751
|
+
],
|
|
1752
|
+
"name": "cohort-id",
|
|
1753
|
+
"type": "string"
|
|
1754
|
+
},
|
|
1755
|
+
{
|
|
1756
|
+
"appliesToVerbs": [
|
|
1757
|
+
"review-source"
|
|
1758
|
+
],
|
|
1759
|
+
"name": "task-id",
|
|
1760
|
+
"type": "string"
|
|
1761
|
+
},
|
|
1762
|
+
{
|
|
1763
|
+
"appliesToVerbs": [
|
|
1764
|
+
"review-source"
|
|
1765
|
+
],
|
|
1766
|
+
"enumValues": [
|
|
1767
|
+
"cleaner",
|
|
1768
|
+
"architect",
|
|
1769
|
+
"qa",
|
|
1770
|
+
"critic"
|
|
1771
|
+
],
|
|
1772
|
+
"name": "lane",
|
|
1773
|
+
"type": "enum"
|
|
1774
|
+
},
|
|
1775
|
+
{
|
|
1776
|
+
"appliesToVerbs": [
|
|
1777
|
+
"review-source"
|
|
1778
|
+
],
|
|
1779
|
+
"name": "rerun-command",
|
|
1780
|
+
"type": "string"
|
|
1781
|
+
},
|
|
1748
1782
|
{
|
|
1749
1783
|
"appliesToVerbs": [
|
|
1750
1784
|
"steer"
|
|
@@ -1873,7 +1907,8 @@
|
|
|
1873
1907
|
"steer",
|
|
1874
1908
|
"classify-blocker",
|
|
1875
1909
|
"record-critic-verdict",
|
|
1876
|
-
"record-critic-gate-override"
|
|
1910
|
+
"record-critic-gate-override",
|
|
1911
|
+
"review-source"
|
|
1877
1912
|
],
|
|
1878
1913
|
"name": "json",
|
|
1879
1914
|
"type": "boolean"
|
|
@@ -1970,6 +2005,10 @@
|
|
|
1970
2005
|
"name": "quality-gate",
|
|
1971
2006
|
"surface": "command-positional"
|
|
1972
2007
|
},
|
|
2008
|
+
{
|
|
2009
|
+
"name": "review-source",
|
|
2010
|
+
"surface": "command-positional"
|
|
2011
|
+
},
|
|
1973
2012
|
{
|
|
1974
2013
|
"name": "graph",
|
|
1975
2014
|
"planned": true,
|
|
@@ -306,6 +306,7 @@ export const WORKFLOW_MANIFEST: Record<CanonicalGjcWorkflowSkill, SkillManifest>
|
|
|
306
306
|
"record-critic-verdict",
|
|
307
307
|
"record-critic-gate-override",
|
|
308
308
|
"quality-gate",
|
|
309
|
+
"review-source",
|
|
309
310
|
]),
|
|
310
311
|
...plannedVerbs(PLANNED_ADMIN_VERBS),
|
|
311
312
|
],
|
|
@@ -362,6 +363,16 @@ export const WORKFLOW_MANIFEST: Record<CanonicalGjcWorkflowSkill, SkillManifest>
|
|
|
362
363
|
{ name: "classification-event-id", type: "string", appliesToVerbs: ["record-critic-verdict"] },
|
|
363
364
|
{ name: "quality-gate-json", type: "string", appliesToVerbs: ["checkpoint", "quality-gate"] },
|
|
364
365
|
{ name: "goal-id", type: "string", appliesToVerbs: ["quality-gate"] },
|
|
366
|
+
{ name: "cohort-id", type: "string", appliesToVerbs: ["review-source"] },
|
|
367
|
+
{ name: "task-id", type: "string", appliesToVerbs: ["review-source"] },
|
|
368
|
+
{
|
|
369
|
+
name: "lane",
|
|
370
|
+
type: "enum",
|
|
371
|
+
enumValues: ["cleaner", "architect", "qa", "critic"],
|
|
372
|
+
appliesToVerbs: ["review-source"],
|
|
373
|
+
},
|
|
374
|
+
{ name: "rerun-command", type: "string", appliesToVerbs: ["review-source"] },
|
|
375
|
+
|
|
365
376
|
{ name: "goal-id", type: "string", appliesToVerbs: ["steer"] },
|
|
366
377
|
{ name: "goal-id", type: "string", appliesToVerbs: ["classify-blocker"] },
|
|
367
378
|
{
|
|
@@ -413,6 +424,7 @@ export const WORKFLOW_MANIFEST: Record<CanonicalGjcWorkflowSkill, SkillManifest>
|
|
|
413
424
|
"classify-blocker",
|
|
414
425
|
"record-critic-verdict",
|
|
415
426
|
"record-critic-gate-override",
|
|
427
|
+
"review-source",
|
|
416
428
|
],
|
|
417
429
|
},
|
|
418
430
|
{ name: "directive-json", type: "string", appliesToVerbs: ["steer"], planned: true },
|
|
@@ -11,11 +11,16 @@
|
|
|
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";
|
|
15
14
|
import * as path from "node:path";
|
|
16
15
|
import { isEnoent } from "@gajae-code/utils";
|
|
17
16
|
import { applyQuery, pathToQuery } from "./json-query";
|
|
18
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
type AuthorizedArtifactsRoot,
|
|
19
|
+
authorizedArtifactsDirsFromContext,
|
|
20
|
+
closeAuthorizedArtifactsRoot,
|
|
21
|
+
openAuthorizedArtifactsRoot,
|
|
22
|
+
readAuthorizedArtifactFile,
|
|
23
|
+
} from "./registry-helpers";
|
|
19
24
|
import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext } from "./types";
|
|
20
25
|
|
|
21
26
|
interface AgentOutputMetadata {
|
|
@@ -36,6 +41,24 @@ interface ManagedOutputSelector {
|
|
|
36
41
|
metadataSha256: string;
|
|
37
42
|
}
|
|
38
43
|
|
|
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
|
+
|
|
39
62
|
function isSafeGenerationFilename(filename: unknown): filename is string {
|
|
40
63
|
return typeof filename === "string" && /^[a-zA-Z0-9_.-]+$/.test(filename);
|
|
41
64
|
}
|
|
@@ -56,17 +79,14 @@ function isManagedOutputSelector(value: unknown, outputId: string): value is Man
|
|
|
56
79
|
);
|
|
57
80
|
}
|
|
58
81
|
|
|
59
|
-
|
|
82
|
+
function readManagedOutputSelector(
|
|
60
83
|
outputId: string,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (isEnoent(error)) return null;
|
|
68
|
-
throw error;
|
|
69
|
-
}
|
|
84
|
+
root: AuthorizedArtifactsRoot,
|
|
85
|
+
selectorFilename: string,
|
|
86
|
+
): ManagedOutputSelector | null {
|
|
87
|
+
const bytes = readRetainedAgentFile(outputId, root, selectorFilename, MAX_AGENT_SELECTOR_BYTES);
|
|
88
|
+
if (!bytes) return null;
|
|
89
|
+
const raw = bytes.toString("utf8");
|
|
70
90
|
let parsed: unknown;
|
|
71
91
|
try {
|
|
72
92
|
parsed = JSON.parse(raw);
|
|
@@ -76,6 +96,18 @@ async function readManagedOutputSelector(
|
|
|
76
96
|
if (!isManagedOutputSelector(parsed, outputId)) throw new Error(`agent://${outputId} malformed output selector`);
|
|
77
97
|
return parsed;
|
|
78
98
|
}
|
|
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;
|
|
79
111
|
|
|
80
112
|
function isAgentOutputMetadata(value: unknown, outputId: string): value is AgentOutputMetadata {
|
|
81
113
|
if (!value || typeof value !== "object") return false;
|
|
@@ -92,18 +124,11 @@ function isAgentOutputMetadata(value: unknown, outputId: string): value is Agent
|
|
|
92
124
|
|
|
93
125
|
async function verifyAgentOutputMetadata(
|
|
94
126
|
outputId: string,
|
|
95
|
-
|
|
96
|
-
metadataPath: string,
|
|
127
|
+
metadataBytes: Buffer,
|
|
97
128
|
bytes: Buffer,
|
|
98
129
|
selector?: ManagedOutputSelector,
|
|
99
130
|
): Promise<void> {
|
|
100
|
-
|
|
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
|
-
}
|
|
131
|
+
const metaRaw = metadataBytes.toString("utf8");
|
|
107
132
|
let parsed: unknown;
|
|
108
133
|
try {
|
|
109
134
|
parsed = JSON.parse(metaRaw);
|
|
@@ -113,9 +138,7 @@ async function verifyAgentOutputMetadata(
|
|
|
113
138
|
if (!isAgentOutputMetadata(parsed, outputId)) {
|
|
114
139
|
throw new Error(`agent://${outputId} malformed metadata`);
|
|
115
140
|
}
|
|
116
|
-
|
|
117
|
-
const stat = await fs.stat(foundPath);
|
|
118
|
-
if (stat.size !== parsed.sizeBytes || bytes.byteLength !== parsed.sizeBytes) {
|
|
141
|
+
if (bytes.byteLength !== parsed.sizeBytes) {
|
|
119
142
|
throw new Error(`agent://${outputId} size mismatch`);
|
|
120
143
|
}
|
|
121
144
|
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
@@ -172,27 +195,64 @@ export class AgentProtocolHandler implements ProtocolHandler {
|
|
|
172
195
|
let foundPath: string | undefined;
|
|
173
196
|
let foundMetadataPath: string | undefined;
|
|
174
197
|
let foundSelector: ManagedOutputSelector | undefined;
|
|
198
|
+
let foundBytes: Buffer | undefined;
|
|
199
|
+
let foundMetadataBytes: Buffer | undefined;
|
|
175
200
|
let anyDirExists = false;
|
|
176
201
|
|
|
177
202
|
for (const dir of dirs) {
|
|
203
|
+
let root: AuthorizedArtifactsRoot;
|
|
178
204
|
try {
|
|
179
|
-
|
|
205
|
+
root = openAuthorizedArtifactsRoot(dir);
|
|
180
206
|
anyDirExists = true;
|
|
181
207
|
} catch (err) {
|
|
182
208
|
if (isEnoent(err)) continue;
|
|
183
209
|
throw err;
|
|
184
210
|
}
|
|
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`;
|
|
188
211
|
try {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
212
|
+
const selectorFilename = `${outputId}.md.selector.json`;
|
|
213
|
+
let selector = readManagedOutputSelector(outputId, root, selectorFilename);
|
|
214
|
+
for (let attempt = 0; attempt < MANAGED_SELECTOR_READ_ATTEMPTS; attempt++) {
|
|
215
|
+
const candidateFilename = selector ? selector.outputFilename : `${outputId}.md`;
|
|
216
|
+
const metadataFilename = selector ? selector.metadataFilename : `${candidateFilename}.meta.json`;
|
|
217
|
+
const rawBuffer = readRetainedAgentFile(outputId, root, candidateFilename, MAX_AGENT_OUTPUT_BYTES);
|
|
218
|
+
const metadataBuffer = readRetainedAgentFile(outputId, root, metadataFilename, MAX_AGENT_METADATA_BYTES);
|
|
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);
|
|
196
256
|
}
|
|
197
257
|
}
|
|
198
258
|
|
|
@@ -200,12 +260,12 @@ export class AgentProtocolHandler implements ProtocolHandler {
|
|
|
200
260
|
throw new Error("No artifacts directory found");
|
|
201
261
|
}
|
|
202
262
|
|
|
203
|
-
if (!foundPath) {
|
|
263
|
+
if (!foundPath || !foundMetadataPath || !foundBytes || !foundMetadataBytes) {
|
|
204
264
|
throw new Error(`agent://${outputId} not found`);
|
|
205
265
|
}
|
|
206
266
|
|
|
207
|
-
const rawBytes =
|
|
208
|
-
await verifyAgentOutputMetadata(outputId,
|
|
267
|
+
const rawBytes = foundBytes;
|
|
268
|
+
await verifyAgentOutputMetadata(outputId, foundMetadataBytes, rawBytes, foundSelector);
|
|
209
269
|
const rawContent = rawBytes.toString("utf8");
|
|
210
270
|
const notes: string[] = [];
|
|
211
271
|
let content = rawContent;
|
|
@@ -12,7 +12,14 @@
|
|
|
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 {
|
|
15
|
+
import { isValidArtifactId } from "../utils/artifact-id";
|
|
16
|
+
import {
|
|
17
|
+
type AuthorizedArtifactsRoot,
|
|
18
|
+
authorizedArtifactsDirsFromContext,
|
|
19
|
+
closeAuthorizedArtifactsRoot,
|
|
20
|
+
openAuthorizedArtifactsRoot,
|
|
21
|
+
readAuthorizedArtifactFile,
|
|
22
|
+
} from "./registry-helpers";
|
|
16
23
|
import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext } from "./types";
|
|
17
24
|
|
|
18
25
|
export class ArtifactProtocolHandler implements ProtocolHandler {
|
|
@@ -24,7 +31,7 @@ export class ArtifactProtocolHandler implements ProtocolHandler {
|
|
|
24
31
|
if (!id) {
|
|
25
32
|
throw new Error("artifact:// URL requires a numeric ID: artifact://0");
|
|
26
33
|
}
|
|
27
|
-
if (
|
|
34
|
+
if (!isValidArtifactId(id)) {
|
|
28
35
|
throw new Error(`artifact:// ID must be numeric, got: ${id}`);
|
|
29
36
|
}
|
|
30
37
|
|
|
@@ -35,23 +42,33 @@ export class ArtifactProtocolHandler implements ProtocolHandler {
|
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
let foundPath: string | undefined;
|
|
45
|
+
let foundBytes: Buffer | undefined;
|
|
46
|
+
let foundSize = 0;
|
|
38
47
|
let anyDirExists = false;
|
|
48
|
+
const MAX_ARTIFACT_READ_BYTES = 16 * 1024 * 1024;
|
|
39
49
|
|
|
40
50
|
for (const dir of dirs) {
|
|
41
|
-
let
|
|
51
|
+
let root: AuthorizedArtifactsRoot;
|
|
42
52
|
try {
|
|
43
|
-
|
|
53
|
+
root = openAuthorizedArtifactsRoot(dir);
|
|
44
54
|
anyDirExists = true;
|
|
45
55
|
} catch (err) {
|
|
46
56
|
if (isEnoent(err)) continue;
|
|
47
57
|
throw err;
|
|
48
58
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
59
|
+
try {
|
|
60
|
+
const files = await fs.readdir(dir);
|
|
61
|
+
for (const f of files) {
|
|
62
|
+
if (f.endsWith(".meta.json") || !f.startsWith(`${id}.`)) continue;
|
|
63
|
+
const captured = readAuthorizedArtifactFile(root, f, MAX_ARTIFACT_READ_BYTES);
|
|
64
|
+
if (!captured) continue;
|
|
52
65
|
if (foundPath) throw new Error(`artifact://${id} ambiguous id in authorized artifacts`);
|
|
53
66
|
foundPath = path.join(dir, f);
|
|
67
|
+
foundBytes = captured.bytes;
|
|
68
|
+
foundSize = captured.size;
|
|
54
69
|
}
|
|
70
|
+
} finally {
|
|
71
|
+
closeAuthorizedArtifactsRoot(root);
|
|
55
72
|
}
|
|
56
73
|
}
|
|
57
74
|
|
|
@@ -59,20 +76,14 @@ export class ArtifactProtocolHandler implements ProtocolHandler {
|
|
|
59
76
|
throw new Error("No artifacts directory found");
|
|
60
77
|
}
|
|
61
78
|
|
|
62
|
-
if (!foundPath) {
|
|
79
|
+
if (!foundPath || !foundBytes) {
|
|
63
80
|
throw new Error(`artifact://${id} not found`);
|
|
64
81
|
}
|
|
65
82
|
|
|
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;
|
|
72
83
|
const content =
|
|
73
|
-
|
|
74
|
-
? `${
|
|
75
|
-
:
|
|
84
|
+
foundSize > foundBytes.byteLength
|
|
85
|
+
? `${foundBytes.toString("utf8")}\n\n[Artifact truncated: first ${foundBytes.byteLength} of ${foundSize} bytes shown; use a narrower range or a specialized tool for the full content.]`
|
|
86
|
+
: foundBytes.toString("utf8");
|
|
76
87
|
return {
|
|
77
88
|
url: url.href,
|
|
78
89
|
content,
|