@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
package/src/session/artifacts.ts
CHANGED
|
@@ -9,10 +9,8 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
9
9
|
import * as fs from "node:fs/promises";
|
|
10
10
|
|
|
11
11
|
import * as path from "node:path";
|
|
12
|
-
import { isValidArtifactId } from "../utils/artifact-id";
|
|
13
12
|
import {
|
|
14
13
|
ensureManagedDirectory,
|
|
15
|
-
type ManagedFileSnapshot,
|
|
16
14
|
type ManagedSessionDescendantStore,
|
|
17
15
|
publishManagedFileNoReplace,
|
|
18
16
|
} from "./internal/managed-session-storage";
|
|
@@ -35,9 +33,6 @@ function isSafeFilename(filename: string): boolean {
|
|
|
35
33
|
return /^[a-zA-Z0-9_.-]+$/.test(filename);
|
|
36
34
|
}
|
|
37
35
|
|
|
38
|
-
const MANAGED_OUTPUT_GENERATION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
39
|
-
const SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
40
|
-
|
|
41
36
|
function parseManagedOutputGeneration(value: Uint8Array, outputFilenamePrefix: string): ManagedOutputGeneration | null {
|
|
42
37
|
try {
|
|
43
38
|
const parsed = JSON.parse(Buffer.from(value).toString("utf8")) as Partial<ManagedOutputGeneration>;
|
|
@@ -48,19 +43,10 @@ function parseManagedOutputGeneration(value: Uint8Array, outputFilenamePrefix: s
|
|
|
48
43
|
typeof parsed.outputSha256 !== "string" ||
|
|
49
44
|
typeof parsed.metadataSizeBytes !== "number" ||
|
|
50
45
|
typeof parsed.metadataSha256 !== "string" ||
|
|
51
|
-
!Number.isSafeInteger(parsed.outputSizeBytes) ||
|
|
52
|
-
(parsed.outputSizeBytes ?? -1) < 0 ||
|
|
53
|
-
!Number.isSafeInteger(parsed.metadataSizeBytes) ||
|
|
54
|
-
(parsed.metadataSizeBytes ?? -1) < 0 ||
|
|
55
|
-
!SHA256_PATTERN.test(parsed.outputSha256) ||
|
|
56
|
-
!SHA256_PATTERN.test(parsed.metadataSha256) ||
|
|
57
46
|
!isSafeFilename(parsed.outputFilename) ||
|
|
58
47
|
!isSafeFilename(parsed.metadataFilename) ||
|
|
59
48
|
!parsed.outputFilename.startsWith(`${outputFilenamePrefix}.`) ||
|
|
60
49
|
!parsed.outputFilename.endsWith(".output") ||
|
|
61
|
-
!MANAGED_OUTPUT_GENERATION_ID_PATTERN.test(
|
|
62
|
-
parsed.outputFilename.slice(outputFilenamePrefix.length + 1, -".output".length),
|
|
63
|
-
) ||
|
|
64
50
|
parsed.metadataFilename !== `${parsed.outputFilename}.meta.json`
|
|
65
51
|
)
|
|
66
52
|
return null;
|
|
@@ -71,36 +57,12 @@ function parseManagedOutputGeneration(value: Uint8Array, outputFilenamePrefix: s
|
|
|
71
57
|
}
|
|
72
58
|
|
|
73
59
|
function sameGeneration(left: ManagedOutputGeneration, right: ManagedOutputGeneration): boolean {
|
|
74
|
-
return
|
|
75
|
-
left.outputFilename === right.outputFilename &&
|
|
76
|
-
left.metadataFilename === right.metadataFilename &&
|
|
77
|
-
left.outputSizeBytes === right.outputSizeBytes &&
|
|
78
|
-
left.outputSha256 === right.outputSha256 &&
|
|
79
|
-
left.metadataSizeBytes === right.metadataSizeBytes &&
|
|
80
|
-
left.metadataSha256 === right.metadataSha256
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function referencesGeneration(value: Uint8Array, generation: ManagedOutputGeneration): boolean {
|
|
85
|
-
try {
|
|
86
|
-
const parsed = JSON.parse(Buffer.from(value).toString("utf8")) as Partial<ManagedOutputGeneration>;
|
|
87
|
-
return (
|
|
88
|
-
parsed.outputFilename === generation.outputFilename && parsed.metadataFilename === generation.metadataFilename
|
|
89
|
-
);
|
|
90
|
-
} catch {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
60
|
+
return left.outputFilename === right.outputFilename && left.metadataFilename === right.metadataFilename;
|
|
93
61
|
}
|
|
94
62
|
export interface ArtifactSaveOptions {
|
|
95
63
|
maxBytes?: number;
|
|
96
64
|
}
|
|
97
65
|
|
|
98
|
-
export interface ArtifactSaveReceipt {
|
|
99
|
-
id: string;
|
|
100
|
-
complete: boolean;
|
|
101
|
-
omittedBytes?: number;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
66
|
/**
|
|
105
67
|
* Manages artifact storage for a session.
|
|
106
68
|
*
|
|
@@ -117,7 +79,6 @@ export class ArtifactManager {
|
|
|
117
79
|
readonly #store: ManagedSessionDescendantStore | undefined;
|
|
118
80
|
#dirCreated = false;
|
|
119
81
|
#initialized = false;
|
|
120
|
-
#initializing: Promise<void> | undefined;
|
|
121
82
|
|
|
122
83
|
/**
|
|
123
84
|
* @param dir Directory that will hold artifact files. Created lazily on first save.
|
|
@@ -152,21 +113,15 @@ export class ArtifactManager {
|
|
|
152
113
|
}
|
|
153
114
|
|
|
154
115
|
async #ensureDir(): Promise<void> {
|
|
155
|
-
if (this.#
|
|
156
|
-
|
|
157
|
-
this.#
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
await this.#scanExistingIds();
|
|
164
|
-
this.#initialized = true;
|
|
165
|
-
})().finally(() => {
|
|
166
|
-
this.#initializing = undefined;
|
|
167
|
-
});
|
|
116
|
+
if (!this.#dirCreated) {
|
|
117
|
+
if (this.#store) this.#store.ensureDirectory();
|
|
118
|
+
else ensureManagedDirectory(this.#dir);
|
|
119
|
+
this.#dirCreated = true;
|
|
120
|
+
}
|
|
121
|
+
if (!this.#initialized) {
|
|
122
|
+
await this.#scanExistingIds();
|
|
123
|
+
this.#initialized = true;
|
|
168
124
|
}
|
|
169
|
-
await this.#initializing;
|
|
170
125
|
}
|
|
171
126
|
|
|
172
127
|
#filename(id: string, toolType: string): string {
|
|
@@ -204,116 +159,58 @@ export class ArtifactManager {
|
|
|
204
159
|
}
|
|
205
160
|
await this.#ensureDir();
|
|
206
161
|
if (!this.#store) throw new Error("Managed output generation requires retained authority");
|
|
207
|
-
const mutationFence = await this.#store.acquireMutationFence(`${selectorFilename}.publish`);
|
|
208
|
-
try {
|
|
209
|
-
const priorSelector = this.#store.readExpected(selectorFilename);
|
|
210
|
-
const priorGeneration = priorSelector
|
|
211
|
-
? parseManagedOutputGeneration(priorSelector.bytes, outputFilenamePrefix)
|
|
212
|
-
: null;
|
|
213
|
-
const generationId = randomUUID();
|
|
214
|
-
const outputFilename = `${outputFilenamePrefix}.${generationId}.output`;
|
|
215
|
-
const metadataFilename = `${outputFilename}.meta.json`;
|
|
216
|
-
const generation: ManagedOutputGeneration = {
|
|
217
|
-
outputFilename,
|
|
218
|
-
metadataFilename,
|
|
219
|
-
outputSizeBytes: outputBytes.byteLength,
|
|
220
|
-
outputSha256: sha256(outputBytes),
|
|
221
|
-
metadataSizeBytes: metadataBytes.byteLength,
|
|
222
|
-
metadataSha256: sha256(metadataBytes),
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
// Immutable generations are not visible until the selector is replaced.
|
|
226
|
-
let stagedOutput: ManagedFileSnapshot | undefined;
|
|
227
|
-
let stagedMetadata: ManagedFileSnapshot | undefined;
|
|
228
|
-
const removeStaged = () => {
|
|
229
|
-
for (const [filename, snapshot] of [
|
|
230
|
-
[metadataFilename, stagedMetadata],
|
|
231
|
-
[outputFilename, stagedOutput],
|
|
232
|
-
] as const) {
|
|
233
|
-
if (!snapshot) continue;
|
|
234
|
-
try {
|
|
235
|
-
this.#store!.removeExpected(filename, snapshot);
|
|
236
|
-
} catch {
|
|
237
|
-
// An identity mismatch is not authority to remove a successor.
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
try {
|
|
242
|
-
await this.#store.publishNoReplace(outputFilename, outputBytes);
|
|
243
|
-
stagedOutput = this.#store.readExpected(outputFilename) ?? undefined;
|
|
244
|
-
if (
|
|
245
|
-
!stagedOutput ||
|
|
246
|
-
stagedOutput.bytes.byteLength !== generation.outputSizeBytes ||
|
|
247
|
-
sha256(stagedOutput.bytes) !== generation.outputSha256
|
|
248
|
-
) {
|
|
249
|
-
throw new Error("managed_output_generation_verification_failed");
|
|
250
|
-
}
|
|
251
|
-
await this.#store.publishNoReplace(metadataFilename, metadataBytes);
|
|
252
|
-
stagedMetadata = this.#store.readExpected(metadataFilename) ?? undefined;
|
|
253
|
-
if (
|
|
254
|
-
!stagedMetadata ||
|
|
255
|
-
stagedMetadata.bytes.byteLength !== generation.metadataSizeBytes ||
|
|
256
|
-
sha256(stagedMetadata.bytes) !== generation.metadataSha256
|
|
257
|
-
) {
|
|
258
|
-
throw new Error("managed_output_generation_verification_failed");
|
|
259
|
-
}
|
|
260
|
-
} catch (error) {
|
|
261
|
-
removeStaged();
|
|
262
|
-
throw error;
|
|
263
|
-
}
|
|
264
162
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
163
|
+
const priorSelector = this.#store.readExpected(selectorFilename);
|
|
164
|
+
const priorGeneration = priorSelector
|
|
165
|
+
? parseManagedOutputGeneration(priorSelector.bytes, outputFilenamePrefix)
|
|
166
|
+
: null;
|
|
167
|
+
const generationId = randomUUID();
|
|
168
|
+
const outputFilename = `${outputFilenamePrefix}.${generationId}.output`;
|
|
169
|
+
const metadataFilename = `${outputFilename}.meta.json`;
|
|
170
|
+
const generation: ManagedOutputGeneration = {
|
|
171
|
+
outputFilename,
|
|
172
|
+
metadataFilename,
|
|
173
|
+
outputSizeBytes: outputBytes.byteLength,
|
|
174
|
+
outputSha256: sha256(outputBytes),
|
|
175
|
+
metadataSizeBytes: metadataBytes.byteLength,
|
|
176
|
+
metadataSha256: sha256(metadataBytes),
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Immutable generations are not visible until the selector is replaced.
|
|
180
|
+
await this.#store.publishNoReplace(outputFilename, outputBytes);
|
|
181
|
+
await this.#store.publishNoReplace(metadataFilename, metadataBytes);
|
|
182
|
+
const stagedOutput = this.#store.readExpected(outputFilename);
|
|
183
|
+
const stagedMetadata = this.#store.readExpected(metadataFilename);
|
|
184
|
+
if (
|
|
185
|
+
!stagedOutput ||
|
|
186
|
+
!stagedMetadata ||
|
|
187
|
+
stagedOutput.bytes.byteLength !== generation.outputSizeBytes ||
|
|
188
|
+
stagedMetadata.bytes.byteLength !== generation.metadataSizeBytes ||
|
|
189
|
+
sha256(stagedOutput.bytes) !== generation.outputSha256 ||
|
|
190
|
+
sha256(stagedMetadata.bytes) !== generation.metadataSha256
|
|
191
|
+
) {
|
|
192
|
+
throw new Error("managed_output_generation_verification_failed");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
await this.#store.replace(selectorFilename, Buffer.from(JSON.stringify(generation), "utf8"));
|
|
196
|
+
const publishedSelector = this.#store.readExpected(selectorFilename);
|
|
197
|
+
const publishedGeneration = publishedSelector
|
|
198
|
+
? parseManagedOutputGeneration(publishedSelector.bytes, outputFilenamePrefix)
|
|
199
|
+
: null;
|
|
200
|
+
if (!publishedGeneration || !sameGeneration(publishedGeneration, generation)) {
|
|
201
|
+
throw new Error("managed_output_selector_verification_failed");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Cleanup cannot affect the selected generation or publication outcome.
|
|
205
|
+
if (priorGeneration && !sameGeneration(priorGeneration, generation)) {
|
|
206
|
+
for (const filename of [priorGeneration.outputFilename, priorGeneration.metadataFilename]) {
|
|
285
207
|
try {
|
|
286
|
-
|
|
287
|
-
|
|
208
|
+
const previous = this.#store.readExpected(filename);
|
|
209
|
+
if (previous) this.#store.removeExpected(filename, previous);
|
|
288
210
|
} catch {
|
|
289
|
-
//
|
|
211
|
+
// Retain unreachable generations for a later safe cleanup.
|
|
290
212
|
}
|
|
291
|
-
if (observedRead && (!observed || !referencesGeneration(observed.bytes, generation))) removeStaged();
|
|
292
|
-
else if (rollbackSelector(observed)) removeStaged();
|
|
293
|
-
throw error;
|
|
294
213
|
}
|
|
295
|
-
const publishedSelector = this.#store.readExpected(selectorFilename);
|
|
296
|
-
const publishedGeneration = publishedSelector
|
|
297
|
-
? parseManagedOutputGeneration(publishedSelector.bytes, outputFilenamePrefix)
|
|
298
|
-
: null;
|
|
299
|
-
if (!publishedGeneration || !sameGeneration(publishedGeneration, generation)) {
|
|
300
|
-
if (rollbackSelector(publishedSelector)) removeStaged();
|
|
301
|
-
throw new Error("managed_output_selector_verification_failed");
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Cleanup cannot affect the selected generation or publication outcome.
|
|
305
|
-
if (priorGeneration && !sameGeneration(priorGeneration, generation)) {
|
|
306
|
-
for (const filename of [priorGeneration.outputFilename, priorGeneration.metadataFilename]) {
|
|
307
|
-
try {
|
|
308
|
-
const previous = this.#store.readExpected(filename);
|
|
309
|
-
if (previous) this.#store.removeExpected(filename, previous);
|
|
310
|
-
} catch {
|
|
311
|
-
// Retain unreachable generations for a later safe cleanup.
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
} finally {
|
|
316
|
-
await mutationFence?.release();
|
|
317
214
|
}
|
|
318
215
|
}
|
|
319
216
|
async publishNamedNoReplace(filename: string, bytes: Uint8Array): Promise<void> {
|
|
@@ -355,8 +252,8 @@ export class ArtifactManager {
|
|
|
355
252
|
for (const file of files) {
|
|
356
253
|
// Files are named: {id}.{toolType}.log
|
|
357
254
|
const match = file.match(/^(\d+)\..*\.log$/);
|
|
358
|
-
if (match
|
|
359
|
-
const id =
|
|
255
|
+
if (match) {
|
|
256
|
+
const id = parseInt(match[1], 10);
|
|
360
257
|
if (id > maxId) maxId = id;
|
|
361
258
|
}
|
|
362
259
|
}
|
|
@@ -368,9 +265,6 @@ export class ArtifactManager {
|
|
|
368
265
|
* IDs are sequential within the session.
|
|
369
266
|
*/
|
|
370
267
|
allocateId(): number {
|
|
371
|
-
if (!Number.isSafeInteger(this.#nextId) || this.#nextId < 0) {
|
|
372
|
-
throw new Error("Artifact id space exhausted");
|
|
373
|
-
}
|
|
374
268
|
return this.#nextId++;
|
|
375
269
|
}
|
|
376
270
|
|
|
@@ -387,28 +281,25 @@ export class ArtifactManager {
|
|
|
387
281
|
return { id, path: path.join(this.#dir, this.#filename(id, toolType)) };
|
|
388
282
|
}
|
|
389
283
|
|
|
390
|
-
/**
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
): Promise<
|
|
284
|
+
/**
|
|
285
|
+
* Save content as an artifact and return the artifact ID.
|
|
286
|
+
* Content is written to a private temporary inode, synced, and linked into
|
|
287
|
+
* the artifact directory only after the complete terminal payload exists.
|
|
288
|
+
*/
|
|
289
|
+
async save(content: string, toolType: string, options: ArtifactSaveOptions = {}): Promise<string> {
|
|
396
290
|
await this.#ensureDir();
|
|
397
291
|
const id = String(this.allocateId());
|
|
398
292
|
const maxBytes = Math.max(0, options.maxBytes ?? DEFAULT_ARTIFACT_MAX_BYTES);
|
|
399
293
|
const contentBytes = Buffer.byteLength(content, "utf-8");
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
294
|
+
const published =
|
|
295
|
+
contentBytes > maxBytes
|
|
296
|
+
? (() => {
|
|
297
|
+
const truncated = truncateHeadBytes(content, maxBytes);
|
|
298
|
+
return `${truncated.text}\n[artifact truncated after ${truncated.bytes} bytes; omitted at least ${contentBytes - truncated.bytes} bytes]\n`;
|
|
299
|
+
})()
|
|
300
|
+
: content;
|
|
405
301
|
await this.#publish(published, this.#filename(id, toolType));
|
|
406
|
-
return
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/** Save content as an artifact and return the artifact ID. */
|
|
410
|
-
async save(content: string, toolType: string, options: ArtifactSaveOptions = {}): Promise<string> {
|
|
411
|
-
return (await this.saveWithReceipt(content, toolType, options)).id;
|
|
302
|
+
return id;
|
|
412
303
|
}
|
|
413
304
|
|
|
414
305
|
/**
|
|
@@ -439,36 +330,8 @@ export class ArtifactManager {
|
|
|
439
330
|
* @param id Artifact ID (numeric string)
|
|
440
331
|
*/
|
|
441
332
|
async getPath(id: string): Promise<string | null> {
|
|
442
|
-
if (!isValidArtifactId(id)) return null;
|
|
443
333
|
const files = await this.listFiles();
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
const filename = matches[0]!;
|
|
447
|
-
if (this.#store) {
|
|
448
|
-
try {
|
|
449
|
-
return this.#store.readExpected(filename) ? path.join(this.#dir, filename) : null;
|
|
450
|
-
} catch {
|
|
451
|
-
return null;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
try {
|
|
455
|
-
const rootBefore = await fs.lstat(this.#dir, { bigint: true });
|
|
456
|
-
if (!rootBefore.isDirectory() || rootBefore.isSymbolicLink()) return null;
|
|
457
|
-
const artifactPath = path.join(this.#dir, filename);
|
|
458
|
-
const leaf = await fs.lstat(artifactPath, { bigint: true });
|
|
459
|
-
if (!leaf.isFile() || leaf.isSymbolicLink() || leaf.nlink !== 1n) return null;
|
|
460
|
-
const rootAfter = await fs.lstat(this.#dir, { bigint: true });
|
|
461
|
-
if (
|
|
462
|
-
!rootAfter.isDirectory() ||
|
|
463
|
-
rootAfter.isSymbolicLink() ||
|
|
464
|
-
rootAfter.dev !== rootBefore.dev ||
|
|
465
|
-
rootAfter.ino !== rootBefore.ino
|
|
466
|
-
) {
|
|
467
|
-
return null;
|
|
468
|
-
}
|
|
469
|
-
return artifactPath;
|
|
470
|
-
} catch {
|
|
471
|
-
return null;
|
|
472
|
-
}
|
|
334
|
+
const match = files.find(f => f.startsWith(`${id}.`));
|
|
335
|
+
return match ? path.join(this.#dir, match) : null;
|
|
473
336
|
}
|
|
474
337
|
}
|
|
@@ -56,31 +56,18 @@ export interface ClientBridgeTerminalOutput {
|
|
|
56
56
|
|
|
57
57
|
export interface ClientBridgeTerminalHandle {
|
|
58
58
|
terminalId: string;
|
|
59
|
-
/** Exact provider lease/session identity that owns this terminal. */
|
|
60
|
-
providerLeaseId: string;
|
|
61
59
|
waitForExit(): Promise<ClientBridgeTerminalExitStatus>;
|
|
62
60
|
currentOutput(): Promise<ClientBridgeTerminalOutput>;
|
|
63
61
|
kill(): Promise<void>;
|
|
64
62
|
release(): Promise<void>;
|
|
65
|
-
/** Full authenticated provider identity, when supplied by the transport. */
|
|
66
|
-
providerLeaseIdentity?: ClientBridgeProviderLeaseIdentity;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface ClientBridgeProviderLeaseIdentity {
|
|
70
|
-
leaseId: string;
|
|
71
|
-
connectionId: string;
|
|
72
|
-
connectionGeneration: number;
|
|
73
|
-
fence: string;
|
|
74
63
|
}
|
|
75
64
|
|
|
76
65
|
export interface ClientBridgeCreateTerminalParams {
|
|
77
|
-
toolCallId: string;
|
|
78
66
|
command: string;
|
|
79
67
|
args?: string[];
|
|
80
68
|
env?: Array<{ name: string; value: string }>;
|
|
81
69
|
cwd?: string;
|
|
82
70
|
outputByteLimit?: number;
|
|
83
|
-
signal?: AbortSignal;
|
|
84
71
|
}
|
|
85
72
|
|
|
86
73
|
export interface ClientBridge {
|
|
@@ -90,8 +77,6 @@ export interface ClientBridge {
|
|
|
90
77
|
readTextFile?(params: { path: string; line?: number; limit?: number }): Promise<string>;
|
|
91
78
|
writeTextFile?(params: { path: string; content: string }): Promise<void>;
|
|
92
79
|
createTerminal?(params: ClientBridgeCreateTerminalParams): Promise<ClientBridgeTerminalHandle>;
|
|
93
|
-
/** Fence the exact provider identity after cleanup ownership becomes uncertain. */
|
|
94
|
-
quarantineProviderLease?(providerLease: string | ClientBridgeProviderLeaseIdentity, reason: string): Promise<void>;
|
|
95
80
|
requestPermission?(
|
|
96
81
|
toolCall: ClientBridgePermissionToolCall,
|
|
97
82
|
options: ClientBridgePermissionOption[],
|