@gajae-code/coding-agent 0.11.3 → 0.11.5

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.
Files changed (213) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/types/cli/notify-cli.d.ts +5 -1
  3. package/dist/types/cli/worktree-cli.d.ts +8 -25
  4. package/dist/types/commands/gc.d.ts +4 -0
  5. package/dist/types/commands/worktree.d.ts +7 -32
  6. package/dist/types/config/settings-schema.d.ts +20 -0
  7. package/dist/types/export/html/template.generated.d.ts +1 -1
  8. package/dist/types/gjc-runtime/deep-interview-state.d.ts +28 -0
  9. package/dist/types/gjc-runtime/gc-runtime.d.ts +9 -0
  10. package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -0
  11. package/dist/types/gjc-runtime/managed-owner-admission.d.ts +42 -0
  12. package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +41 -0
  13. package/dist/types/gjc-runtime/state-argv.d.ts +38 -0
  14. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +9 -0
  15. package/dist/types/gjc-runtime/ultragoal-owner-loss-recovery.d.ts +94 -0
  16. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
  17. package/dist/types/harness-control-plane/owner.d.ts +3 -1
  18. package/dist/types/internal-urls/local-protocol.d.ts +18 -0
  19. package/dist/types/main.d.ts +2 -2
  20. package/dist/types/modes/components/ansi-display-validator.d.ts +2 -0
  21. package/dist/types/modes/components/custom-editor.d.ts +8 -3
  22. package/dist/types/modes/components/notifications-settings-editor.d.ts +4 -0
  23. package/dist/types/modes/components/session-observer-overlay.d.ts +3 -1
  24. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  25. package/dist/types/modes/components/tool-transcript-format.d.ts +77 -0
  26. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +3 -0
  27. package/dist/types/modes/controllers/goal-mode-controller.d.ts +8 -1
  28. package/dist/types/modes/controllers/input-controller.d.ts +8 -2
  29. package/dist/types/modes/controllers/selector-controller.d.ts +15 -0
  30. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +3 -0
  31. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +16 -0
  32. package/dist/types/plan-mode/approved-plan.d.ts +1 -0
  33. package/dist/types/runtime-mcp/manager.d.ts +2 -0
  34. package/dist/types/runtime-mcp/oauth-discovery.d.ts +12 -1
  35. package/dist/types/sdk/broker/broker.d.ts +1 -0
  36. package/dist/types/sdk/broker/discovery.d.ts +10 -0
  37. package/dist/types/sdk/broker/ensure.d.ts +24 -1
  38. package/dist/types/sdk/broker/internal.d.ts +3 -0
  39. package/dist/types/sdk/broker/session-index.d.ts +12 -0
  40. package/dist/types/sdk/broker/state-version.d.ts +2 -1
  41. package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -1
  42. package/dist/types/sdk/bus/chat-daemon-control.d.ts +39 -4
  43. package/dist/types/sdk/bus/config-commands.d.ts +8 -0
  44. package/dist/types/sdk/bus/config.d.ts +22 -0
  45. package/dist/types/sdk/bus/index.d.ts +162 -2
  46. package/dist/types/sdk/bus/notification-service.d.ts +71 -8
  47. package/dist/types/sdk/bus/session-control.d.ts +11 -8
  48. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +5 -0
  49. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -3
  50. package/dist/types/sdk/bus/telegram-daemon-control.d.ts +23 -4
  51. package/dist/types/sdk/bus/telegram-daemon.d.ts +214 -20
  52. package/dist/types/sdk/session.d.ts +16 -1
  53. package/dist/types/secrets/index.d.ts +4 -2
  54. package/dist/types/secrets/obfuscator.d.ts +1 -1
  55. package/dist/types/session/agent-session.d.ts +31 -2
  56. package/dist/types/session/artifacts.d.ts +30 -8
  57. package/dist/types/session/internal/managed-session-scope.d.ts +7 -0
  58. package/dist/types/session/internal/managed-session-storage.d.ts +63 -6
  59. package/dist/types/session/session-manager.d.ts +88 -18
  60. package/dist/types/session/session-storage.d.ts +27 -0
  61. package/dist/types/session/streaming-output.d.ts +4 -0
  62. package/dist/types/task/executor.d.ts +9 -0
  63. package/dist/types/tool-discovery/tool-index.d.ts +2 -0
  64. package/dist/types/tools/ask.d.ts +87 -12
  65. package/dist/types/tools/bash-skill-urls.d.ts +2 -2
  66. package/dist/types/tools/browser/screenshot-format.d.ts +8 -0
  67. package/dist/types/tools/browser/tab-protocol.d.ts +11 -0
  68. package/dist/types/tools/browser/tab-supervisor.d.ts +1 -1
  69. package/dist/types/tools/browser/tab-worker-path-resolver.d.ts +2 -0
  70. package/dist/types/tools/browser/tab-worker-smoke.d.ts +16 -0
  71. package/dist/types/tools/index.d.ts +4 -0
  72. package/dist/types/tools/path-utils.d.ts +9 -14
  73. package/dist/types/tools/render-utils.d.ts +1 -8
  74. package/dist/types/tools/resource-gc.d.ts +14 -0
  75. package/dist/types/utils/image-loading.d.ts +23 -0
  76. package/dist/types/utils/image-resize.d.ts +2 -0
  77. package/dist/types/utils/pasted-image-loading.d.ts +39 -0
  78. package/dist/types/utils/pasted-image-path.d.ts +21 -19
  79. package/dist/types/web/scrapers/html-to-markdown.d.ts +5 -0
  80. package/dist/types/web/scrapers/types.d.ts +1 -5
  81. package/package.json +25 -9
  82. package/scripts/generate-sdk-operation-inventory.ts +2 -0
  83. package/scripts/run-threat-model-check.ts +60 -0
  84. package/src/cli/notify-cli.ts +40 -18
  85. package/src/cli/worktree-cli.ts +9 -287
  86. package/src/cli.ts +10 -9
  87. package/src/commands/gc.ts +11 -1
  88. package/src/commands/sdk.ts +10 -4
  89. package/src/commands/worktree.ts +10 -53
  90. package/src/config/model-profiles.ts +15 -15
  91. package/src/config/settings-schema.ts +17 -0
  92. package/src/daemon/runtime.ts +2 -2
  93. package/src/defaults/gjc/skills/deep-interview/SKILL.md +70 -49
  94. package/src/defaults/gjc/skills/ralplan/SKILL.md +0 -6
  95. package/src/defaults/gjc/skills/team/SKILL.md +7 -9
  96. package/src/defaults/gjc/skills/ultragoal/SKILL.md +0 -2
  97. package/src/eval/js/shared/runtime.ts +1 -1
  98. package/src/eval/py/index.ts +2 -2
  99. package/src/export/html/template.generated.ts +1 -1
  100. package/src/extensibility/plugins/marketplace/manager.ts +94 -10
  101. package/src/gjc-runtime/deep-interview-recorder.ts +7 -0
  102. package/src/gjc-runtime/deep-interview-runtime.ts +10 -0
  103. package/src/gjc-runtime/deep-interview-state.ts +127 -3
  104. package/src/gjc-runtime/gc-render.ts +19 -1
  105. package/src/gjc-runtime/gc-runtime.ts +68 -3
  106. package/src/gjc-runtime/launch-tmux.ts +61 -6
  107. package/src/gjc-runtime/managed-owner-admission.ts +352 -0
  108. package/src/gjc-runtime/managed-owner-supervisor.ts +162 -0
  109. package/src/gjc-runtime/state-argv.ts +292 -0
  110. package/src/gjc-runtime/state-runtime.ts +80 -222
  111. package/src/gjc-runtime/team-runtime.ts +1 -1
  112. package/src/gjc-runtime/tmux-owner-isolation.ts +117 -0
  113. package/src/gjc-runtime/ultragoal-owner-loss-recovery.ts +328 -0
  114. package/src/gjc-runtime/ultragoal-runtime.ts +14 -0
  115. package/src/harness-control-plane/owner.ts +59 -3
  116. package/src/hindsight/client.ts +65 -2
  117. package/src/internal-urls/agent-protocol.ts +76 -5
  118. package/src/internal-urls/docs-index.generated.ts +7 -6
  119. package/src/internal-urls/local-protocol.ts +416 -18
  120. package/src/lsp/index.ts +4 -88
  121. package/src/main.ts +37 -13
  122. package/src/modes/acp/acp-agent.ts +8 -0
  123. package/src/modes/components/ansi-display-validator.ts +54 -0
  124. package/src/modes/components/custom-editor.ts +103 -32
  125. package/src/modes/components/custom-provider-wizard.ts +26 -1
  126. package/src/modes/components/notifications-settings-editor.ts +26 -2
  127. package/src/modes/components/session-observer-overlay.ts +40 -37
  128. package/src/modes/components/settings-selector.ts +3 -1
  129. package/src/modes/components/tool-transcript-format.ts +393 -0
  130. package/src/modes/components/transcript-viewer-overlay.ts +63 -1
  131. package/src/modes/controllers/goal-mode-controller.ts +20 -1
  132. package/src/modes/controllers/input-controller.ts +98 -37
  133. package/src/modes/controllers/plan-mode-controller.ts +2 -0
  134. package/src/modes/controllers/runtime-mcp-command-controller.ts +3 -0
  135. package/src/modes/controllers/selector-controller.ts +71 -9
  136. package/src/modes/interactive-mode.ts +14 -2
  137. package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -1
  138. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +17 -1
  139. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +4 -2
  140. package/src/modes/shared/agent-wire/workflow-gate-types.ts +120 -3
  141. package/src/plan-mode/approved-plan.ts +3 -1
  142. package/src/prompts/agents/architect.md +1 -0
  143. package/src/prompts/agents/critic.md +1 -0
  144. package/src/prompts/system/custom-system-prompt.md +1 -1
  145. package/src/prompts/system/system-prompt.md +2 -1
  146. package/src/prompts/tools/lsp.md +3 -3
  147. package/src/runtime-mcp/manager.ts +16 -1
  148. package/src/runtime-mcp/oauth-discovery.ts +116 -26
  149. package/src/sdk/broker/broker.ts +139 -32
  150. package/src/sdk/broker/discovery.ts +125 -0
  151. package/src/sdk/broker/ensure.ts +103 -19
  152. package/src/sdk/broker/internal.ts +10 -0
  153. package/src/sdk/broker/session-index.ts +309 -38
  154. package/src/sdk/broker/state-version.ts +5 -2
  155. package/src/sdk/bus/chat-daemon-cli.ts +72 -10
  156. package/src/sdk/bus/chat-daemon-control.ts +526 -130
  157. package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
  158. package/src/sdk/bus/config-commands.ts +22 -0
  159. package/src/sdk/bus/config.ts +37 -0
  160. package/src/sdk/bus/index.ts +384 -92
  161. package/src/sdk/bus/lifecycle-control-runtime.ts +53 -3
  162. package/src/sdk/bus/notification-service.ts +284 -62
  163. package/src/sdk/bus/recent-activity.ts +14 -5
  164. package/src/sdk/bus/session-control.ts +150 -44
  165. package/src/sdk/bus/telegram-daemon-cli.ts +20 -14
  166. package/src/sdk/bus/telegram-daemon-contract.ts +6 -3
  167. package/src/sdk/bus/telegram-daemon-control.ts +294 -76
  168. package/src/sdk/bus/telegram-daemon.ts +1842 -281
  169. package/src/sdk/protocol/operation-inventory.generated.json +11 -0
  170. package/src/sdk/session.ts +155 -27
  171. package/src/secrets/index.ts +53 -10
  172. package/src/secrets/obfuscator.ts +21 -18
  173. package/src/secrets/regex.ts +2 -0
  174. package/src/session/agent-session.ts +495 -253
  175. package/src/session/artifacts.ts +188 -23
  176. package/src/session/context-estimation.ts +7 -2
  177. package/src/session/internal/managed-session-scope.ts +215 -39
  178. package/src/session/internal/managed-session-storage.ts +1152 -46
  179. package/src/session/session-manager.ts +1055 -148
  180. package/src/session/session-storage.ts +109 -11
  181. package/src/session/streaming-output.ts +6 -0
  182. package/src/task/executor.ts +71 -26
  183. package/src/task/index.ts +63 -20
  184. package/src/tool-discovery/tool-index.ts +18 -0
  185. package/src/tools/ask.ts +329 -57
  186. package/src/tools/bash-allowed-prefixes.ts +79 -29
  187. package/src/tools/bash-skill-urls.ts +14 -3
  188. package/src/tools/bash.ts +2 -2
  189. package/src/tools/browser/launch.ts +3 -1
  190. package/src/tools/browser/readable.ts +1 -1
  191. package/src/tools/browser/screenshot-format.ts +34 -0
  192. package/src/tools/browser/tab-protocol.ts +3 -0
  193. package/src/tools/browser/tab-supervisor.ts +125 -122
  194. package/src/tools/browser/tab-worker-entry.ts +33 -14
  195. package/src/tools/browser/tab-worker-path-resolver.ts +67 -0
  196. package/src/tools/browser/tab-worker-smoke.ts +87 -0
  197. package/src/tools/browser/tab-worker.ts +7 -5
  198. package/src/tools/index.ts +4 -0
  199. package/src/tools/path-utils.ts +42 -35
  200. package/src/tools/plan-mode-guard.ts +1 -0
  201. package/src/tools/read.ts +7 -1
  202. package/src/tools/render-utils.ts +1 -27
  203. package/src/tools/resource-gc.ts +123 -34
  204. package/src/tools/sqlite-reader.ts +3 -2
  205. package/src/tools/subagent.ts +2 -0
  206. package/src/utils/image-loading.ts +85 -34
  207. package/src/utils/image-resize.ts +16 -9
  208. package/src/utils/pasted-image-loading.ts +514 -0
  209. package/src/utils/pasted-image-path.ts +223 -98
  210. package/src/web/scrapers/html-to-markdown.ts +68 -0
  211. package/src/web/scrapers/readthedocs.ts +1 -1
  212. package/src/web/scrapers/types.ts +1 -68
  213. package/src/web/search/providers/openai-compatible.ts +76 -23
@@ -4,10 +4,61 @@
4
4
  * Artifacts are stored in a directory alongside the session file,
5
5
  * accessible via artifact:// URLs.
6
6
  */
7
+
8
+ import { createHash, randomUUID } from "node:crypto";
7
9
  import * as fs from "node:fs/promises";
8
- import * as path from "node:path";
9
10
 
11
+ import * as path from "node:path";
12
+ import {
13
+ ensureManagedDirectory,
14
+ type ManagedSessionDescendantStore,
15
+ publishManagedFileNoReplace,
16
+ } from "./internal/managed-session-storage";
10
17
  import { DEFAULT_ARTIFACT_MAX_BYTES, truncateHeadBytes } from "./streaming-output";
18
+
19
+ export interface ManagedOutputGeneration {
20
+ outputFilename: string;
21
+ metadataFilename: string;
22
+ outputSizeBytes: number;
23
+ outputSha256: string;
24
+ metadataSizeBytes: number;
25
+ metadataSha256: string;
26
+ }
27
+
28
+ function sha256(bytes: Uint8Array): string {
29
+ return createHash("sha256").update(bytes).digest("hex");
30
+ }
31
+
32
+ function isSafeFilename(filename: string): boolean {
33
+ return /^[a-zA-Z0-9_.-]+$/.test(filename);
34
+ }
35
+
36
+ function parseManagedOutputGeneration(value: Uint8Array, outputFilenamePrefix: string): ManagedOutputGeneration | null {
37
+ try {
38
+ const parsed = JSON.parse(Buffer.from(value).toString("utf8")) as Partial<ManagedOutputGeneration>;
39
+ if (
40
+ typeof parsed.outputFilename !== "string" ||
41
+ typeof parsed.metadataFilename !== "string" ||
42
+ typeof parsed.outputSizeBytes !== "number" ||
43
+ typeof parsed.outputSha256 !== "string" ||
44
+ typeof parsed.metadataSizeBytes !== "number" ||
45
+ typeof parsed.metadataSha256 !== "string" ||
46
+ !isSafeFilename(parsed.outputFilename) ||
47
+ !isSafeFilename(parsed.metadataFilename) ||
48
+ !parsed.outputFilename.startsWith(`${outputFilenamePrefix}.`) ||
49
+ !parsed.outputFilename.endsWith(".output") ||
50
+ parsed.metadataFilename !== `${parsed.outputFilename}.meta.json`
51
+ )
52
+ return null;
53
+ return parsed as ManagedOutputGeneration;
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ function sameGeneration(left: ManagedOutputGeneration, right: ManagedOutputGeneration): boolean {
60
+ return left.outputFilename === right.outputFilename && left.metadataFilename === right.metadataFilename;
61
+ }
11
62
  export interface ArtifactSaveOptions {
12
63
  maxBytes?: number;
13
64
  }
@@ -25,14 +76,16 @@ export interface ArtifactSaveOptions {
25
76
  export class ArtifactManager {
26
77
  #nextId = 0;
27
78
  readonly #dir: string;
79
+ readonly #store: ManagedSessionDescendantStore | undefined;
28
80
  #dirCreated = false;
29
81
  #initialized = false;
30
82
 
31
83
  /**
32
84
  * @param dir Directory that will hold artifact files. Created lazily on first save.
33
85
  */
34
- constructor(dir: string) {
35
- this.#dir = dir;
86
+ constructor(target: string | ManagedSessionDescendantStore) {
87
+ this.#store = typeof target === "string" ? undefined : target;
88
+ this.#dir = typeof target === "string" ? target : target.dir;
36
89
  }
37
90
 
38
91
  /**
@@ -43,9 +96,26 @@ export class ArtifactManager {
43
96
  return this.#dir;
44
97
  }
45
98
 
99
+ getManagedRootAuthority() {
100
+ return this.#store?.rootAuthority;
101
+ }
102
+
103
+ getManagedSubtreeRootAuthority() {
104
+ return this.#store?.subtreeRootAuthority;
105
+ }
106
+
107
+ getManagedStore(): ManagedSessionDescendantStore | undefined {
108
+ return this.#store;
109
+ }
110
+
111
+ assertManagedBinding(): void {
112
+ this.#store?.assertBound();
113
+ }
114
+
46
115
  async #ensureDir(): Promise<void> {
47
116
  if (!this.#dirCreated) {
48
- await fs.mkdir(this.#dir, { recursive: true });
117
+ if (this.#store) this.#store.ensureDirectory();
118
+ else ensureManagedDirectory(this.#dir);
49
119
  this.#dirCreated = true;
50
120
  }
51
121
  if (!this.#initialized) {
@@ -54,6 +124,102 @@ export class ArtifactManager {
54
124
  }
55
125
  }
56
126
 
127
+ #filename(id: string, toolType: string): string {
128
+ if (!/^[a-zA-Z0-9_-]+$/.test(toolType)) throw new Error("Unsafe artifact tool type");
129
+ return `${id}.${toolType}.log`;
130
+ }
131
+
132
+ async #publish(content: string, filename: string): Promise<void> {
133
+ if (this.#store) await this.#store.publishNoReplace(filename, Buffer.from(content, "utf8"));
134
+ else await publishManagedFileNoReplace(path.join(this.#dir, filename), Buffer.from(content, "utf8"));
135
+ }
136
+
137
+ async replaceNamed(filename: string, content: string): Promise<void> {
138
+ if (!/^[a-zA-Z0-9_.-]+$/.test(filename)) throw new Error("Unsafe named artifact");
139
+ await this.#ensureDir();
140
+ if (this.#store) await this.#store.replace(filename, Buffer.from(content, "utf8"));
141
+ else await Bun.write(path.join(this.#dir, filename), content);
142
+ }
143
+
144
+ async replaceNamedBytes(filename: string, bytes: Uint8Array): Promise<void> {
145
+ if (!/^[a-zA-Z0-9_.-]+$/.test(filename)) throw new Error("Unsafe named artifact");
146
+ await this.#ensureDir();
147
+ if (this.#store) await this.#store.replace(filename, bytes);
148
+ else await Bun.write(path.join(this.#dir, filename), bytes);
149
+ }
150
+
151
+ async publishManagedOutputGeneration(
152
+ selectorFilename: string,
153
+ outputFilenamePrefix: string,
154
+ outputBytes: Uint8Array,
155
+ metadataBytes: Uint8Array,
156
+ ): Promise<void> {
157
+ if (!isSafeFilename(selectorFilename) || !isSafeFilename(outputFilenamePrefix)) {
158
+ throw new Error("Unsafe managed output generation");
159
+ }
160
+ await this.#ensureDir();
161
+ if (!this.#store) throw new Error("Managed output generation requires retained authority");
162
+
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]) {
207
+ try {
208
+ const previous = this.#store.readExpected(filename);
209
+ if (previous) this.#store.removeExpected(filename, previous);
210
+ } catch {
211
+ // Retain unreachable generations for a later safe cleanup.
212
+ }
213
+ }
214
+ }
215
+ }
216
+ async publishNamedNoReplace(filename: string, bytes: Uint8Array): Promise<void> {
217
+ if (!/^[a-zA-Z0-9_.-]+$/.test(filename)) throw new Error("Unsafe named artifact");
218
+ await this.#ensureDir();
219
+ if (this.#store) await this.#store.publishNoReplace(filename, bytes);
220
+ else await publishManagedFileNoReplace(path.join(this.#dir, filename), bytes);
221
+ }
222
+
57
223
  /**
58
224
  * Scan existing artifact files to find the next available ID.
59
225
  * This ensures we don't overwrite artifacts when resuming a session.
@@ -81,37 +247,36 @@ export class ArtifactManager {
81
247
  }
82
248
 
83
249
  /**
84
- * Allocate a new artifact path and ID without writing content.
250
+ * Reserve an artifact ID without exposing a writable managed pathname.
85
251
  *
86
- * @param toolType Tool name for file extension (e.g., "bash", "read")
252
+ * Streaming callers that only understand bare paths fail closed; use `save`
253
+ * for terminally published artifact content.
87
254
  */
88
- async allocatePath(toolType: string): Promise<{ id: string; path: string }> {
255
+ async allocatePath(toolType: string): Promise<{ id: string; path?: string }> {
89
256
  await this.#ensureDir();
90
257
  const id = String(this.allocateId());
91
- const filename = `${id}.${toolType}.log`;
92
- return { id, path: path.join(this.#dir, filename) };
258
+ if (this.#store) return { id };
259
+ return { id, path: path.join(this.#dir, this.#filename(id, toolType)) };
93
260
  }
94
261
 
95
262
  /**
96
263
  * Save content as an artifact and return the artifact ID.
97
- *
98
- * @param content Full content to save
99
- * @param toolType Tool name for file extension (e.g., "bash", "read")
100
- * @returns Artifact ID (numeric string)
264
+ * Content is written to a private temporary inode, synced, and linked into
265
+ * the artifact directory only after the complete terminal payload exists.
101
266
  */
102
267
  async save(content: string, toolType: string, options: ArtifactSaveOptions = {}): Promise<string> {
103
- const { id, path } = await this.allocatePath(toolType);
268
+ await this.#ensureDir();
269
+ const id = String(this.allocateId());
104
270
  const maxBytes = Math.max(0, options.maxBytes ?? DEFAULT_ARTIFACT_MAX_BYTES);
105
271
  const contentBytes = Buffer.byteLength(content, "utf-8");
106
- if (contentBytes > maxBytes) {
107
- const truncated = truncateHeadBytes(content, maxBytes);
108
- await Bun.write(
109
- path,
110
- `${truncated.text}\n[artifact truncated after ${truncated.bytes} bytes; omitted at least ${contentBytes - truncated.bytes} bytes]\n`,
111
- );
112
- } else {
113
- await Bun.write(path, content);
114
- }
272
+ const published =
273
+ contentBytes > maxBytes
274
+ ? (() => {
275
+ const truncated = truncateHeadBytes(content, maxBytes);
276
+ return `${truncated.text}\n[artifact truncated after ${truncated.bytes} bytes; omitted at least ${contentBytes - truncated.bytes} bytes]\n`;
277
+ })()
278
+ : content;
279
+ await this.#publish(published, this.#filename(id, toolType));
115
280
  return id;
116
281
  }
117
282
 
@@ -7,6 +7,7 @@
7
7
  * as well, so every surface reports the same numbers.
8
8
  */
9
9
  import { estimateTextTokensHeuristic } from "@gajae-code/agent-core/compaction";
10
+ import { toolWireSchema } from "@gajae-code/ai/utils/schema/wire";
10
11
  import type { Skill } from "../extensibility/skills";
11
12
  import type { Tool } from "../tools";
12
13
  import type { AgentSession } from "./agent-session";
@@ -28,9 +29,13 @@ export function estimateToolSchemaTokens(
28
29
  for (const tool of tools) {
29
30
  fragments.push(tool.name, tool.description);
30
31
  try {
31
- fragments.push(JSON.stringify(tool.parameters ?? {}));
32
+ fragments.push(JSON.stringify(toolWireSchema(tool as never)));
32
33
  } catch {
33
- // Schema may contain functions or cycles; ignore.
34
+ try {
35
+ fragments.push(JSON.stringify(tool.parameters ?? {}));
36
+ } catch {
37
+ // Schema may contain functions or cycles; ignore.
38
+ }
34
39
  }
35
40
  }
36
41
  return estimateTextTokensHeuristic(fragments);