@gajae-code/coding-agent 0.18.6 → 0.18.7

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 (58) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/types/capability/types.d.ts +4 -0
  3. package/dist/types/config/settings.d.ts +4 -0
  4. package/dist/types/discovery/helpers.d.ts +2 -2
  5. package/dist/types/runtime-mcp/elicitation.d.ts +12 -0
  6. package/dist/types/sdk/broker/lifecycle.d.ts +9 -0
  7. package/dist/types/sdk/broker/task-artifact-owner-validation.d.ts +55 -0
  8. package/dist/types/sdk/bus/telegram-daemon.d.ts +3 -2
  9. package/dist/types/sdk/host/host.d.ts +6 -0
  10. package/dist/types/sdk/router/session-router.d.ts +7 -0
  11. package/dist/types/session/internal/managed-session-storage.d.ts +11 -1
  12. package/dist/types/session/internal/task-artifact-owner-access.d.ts +17 -0
  13. package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +3 -0
  14. package/dist/types/session/session-retirement.d.ts +47 -0
  15. package/dist/types/session/session-storage.d.ts +0 -46
  16. package/dist/types/session/session-transcript-header.d.ts +2 -0
  17. package/dist/types/session/task-artifact-owner-codec.d.ts +107 -0
  18. package/dist/types/session/task-artifact-owner-retirement.d.ts +7 -0
  19. package/dist/types/session/task-artifact-owner.d.ts +10 -0
  20. package/package.json +7 -7
  21. package/scripts/resident-memory-bench.ts +174 -20
  22. package/src/capability/index.ts +1 -0
  23. package/src/capability/types.ts +4 -0
  24. package/src/cli/config-cli.ts +8 -18
  25. package/src/cli-main.ts +7 -8
  26. package/src/commands/sdk.ts +112 -21
  27. package/src/config/settings.ts +44 -4
  28. package/src/discovery/helpers.ts +9 -3
  29. package/src/discovery/ssh.ts +8 -3
  30. package/src/gjc-runtime/gc-runtime.ts +2 -1
  31. package/src/hindsight/config.ts +9 -3
  32. package/src/internal-urls/docs-index.generated.ts +2 -2
  33. package/src/modes/acp/acp-agent.ts +75 -3
  34. package/src/runtime-mcp/client.ts +13 -4
  35. package/src/runtime-mcp/elicitation.ts +142 -0
  36. package/src/sdk/broker/broker.ts +8 -1
  37. package/src/sdk/broker/lifecycle.ts +109 -7
  38. package/src/sdk/broker/task-artifact-owner-validation.ts +259 -0
  39. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  40. package/src/sdk/bus/chat-daemon-runtime.ts +3 -0
  41. package/src/sdk/bus/index.ts +3 -3
  42. package/src/sdk/bus/telegram-daemon.ts +4 -2
  43. package/src/sdk/host/host.ts +6 -0
  44. package/src/sdk/host/session-runtime.test.ts +43 -2
  45. package/src/sdk/host/session-runtime.ts +2 -2
  46. package/src/sdk/mcp/server.ts +15 -3
  47. package/src/sdk/router/session-router.ts +19 -2
  48. package/src/sdk/session.ts +14 -0
  49. package/src/session/agent-session.ts +57 -6
  50. package/src/session/internal/managed-session-storage.ts +107 -1
  51. package/src/session/internal/task-artifact-owner-access.ts +229 -0
  52. package/src/session/internal/task-artifact-owner-transcript.ts +68 -0
  53. package/src/session/session-retirement.ts +155 -0
  54. package/src/session/session-storage.ts +1 -160
  55. package/src/session/session-transcript-header.ts +17 -0
  56. package/src/session/task-artifact-owner-codec.ts +578 -0
  57. package/src/session/task-artifact-owner-retirement.ts +475 -0
  58. package/src/session/task-artifact-owner.ts +289 -0
@@ -0,0 +1,289 @@
1
+ import * as crypto from "node:crypto";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { ArtifactManager } from "./artifacts";
5
+ import { acquireManagedLock, type ManagedSessionDescendantStore } from "./internal/managed-session-storage";
6
+ import {
7
+ locatorFromManifest,
8
+ newSessionRootStore,
9
+ type OwnerManifest,
10
+ openOwnerStore,
11
+ readOwnerManifest,
12
+ } from "./internal/task-artifact-owner-access";
13
+ import type { NativeDirectoryTreeSnapshot } from "./session-storage";
14
+ import {
15
+ assertSafeRelativePath,
16
+ assertSessionRoot,
17
+ OWNER_DIRECTORY,
18
+ OWNER_LOCK_DIRECTORY,
19
+ OWNER_MANIFEST,
20
+ OWNER_SCHEMA_VERSION,
21
+ ownerIdForSession,
22
+ ownerRelativePath,
23
+ parseTaskArtifactOwnerLocator,
24
+ sameTreeContents,
25
+ type TaskArtifactOwnerLocator,
26
+ type TaskArtifactOwnerStorageContext,
27
+ } from "./task-artifact-owner-codec";
28
+
29
+ export interface ManagedTaskArtifactOwner {
30
+ readonly locator: TaskArtifactOwnerLocator;
31
+ readonly manager: ArtifactManager;
32
+ }
33
+
34
+ function makeOwnerManager(ownerStore: ManagedSessionDescendantStore): ArtifactManager {
35
+ return new ArtifactManager(ownerStore);
36
+ }
37
+
38
+ function legacyPathOverlapsOwner(
39
+ sessionsRoot: string,
40
+ sourceRelativePath: string,
41
+ destinationRelativePath: string,
42
+ ): boolean {
43
+ const source = path.resolve(sessionsRoot, sourceRelativePath);
44
+ const destination = path.resolve(sessionsRoot, destinationRelativePath);
45
+ const sourceToDestination = path.relative(source, destination);
46
+ const destinationToSource = path.relative(destination, source);
47
+ return (
48
+ source !== destination &&
49
+ ((!path.isAbsolute(sourceToDestination) &&
50
+ sourceToDestination !== ".." &&
51
+ !sourceToDestination.startsWith(`..${path.sep}`)) ||
52
+ (!path.isAbsolute(destinationToSource) &&
53
+ destinationToSource !== ".." &&
54
+ !destinationToSource.startsWith(`..${path.sep}`)))
55
+ );
56
+ }
57
+
58
+ async function copyLegacyArtifactTree(
59
+ store: ManagedSessionDescendantStore,
60
+ sourceRelativePath: string,
61
+ destinationRelativePath: string,
62
+ sourceSnapshot: NativeDirectoryTreeSnapshot,
63
+ ): Promise<void> {
64
+ for (const entry of sourceSnapshot.entries) {
65
+ if (entry.relativePath === "") continue;
66
+ const destination = path.posix.join(destinationRelativePath, entry.relativePath);
67
+ if (entry.kind === "directory") {
68
+ store.ensureDirectory(destination);
69
+ continue;
70
+ }
71
+ const sourceName = path.posix.join(sourceRelativePath, entry.relativePath);
72
+ const captured = store.readExpected(sourceName);
73
+ if (
74
+ !captured ||
75
+ captured.identity.dev.toString() !== entry.dev ||
76
+ captured.identity.ino.toString() !== entry.ino ||
77
+ String(captured.identity.size) !== entry.size ||
78
+ captured.identity.mtimeNs.toString() !== entry.mtimeNs ||
79
+ captured.identity.ctimeNs.toString() !== entry.ctimeNs ||
80
+ crypto.createHash("sha256").update(captured.bytes).digest("hex") !== entry.sha256
81
+ )
82
+ throw new Error("task_artifact_owner_source_changed");
83
+ await store.publishNoReplace(destination, captured.bytes);
84
+ }
85
+ const sourceAfter = store.captureTree(sourceRelativePath);
86
+ const destinationAfter = store.captureTree(destinationRelativePath);
87
+ if (
88
+ JSON.stringify(sourceAfter) !== JSON.stringify(sourceSnapshot) ||
89
+ !sameTreeContents(sourceSnapshot, destinationAfter)
90
+ )
91
+ throw new Error("task_artifact_owner_copy_mismatch");
92
+ }
93
+
94
+ function taskArtifactOwnerFromStore(
95
+ locator: TaskArtifactOwnerLocator,
96
+ ownerStore: ManagedSessionDescendantStore,
97
+ ): ManagedTaskArtifactOwner {
98
+ return Object.freeze({ locator: Object.freeze({ ...locator }), manager: makeOwnerManager(ownerStore) });
99
+ }
100
+
101
+ interface FailureState {
102
+ failed: boolean;
103
+ value: unknown;
104
+ }
105
+
106
+ function recordFailure(state: FailureState, error: unknown, message: string): void {
107
+ state.value = state.failed ? new AggregateError([state.value, error], message) : error;
108
+ state.failed = true;
109
+ }
110
+
111
+ function closeManagedStore(
112
+ store: ManagedSessionDescendantStore | undefined,
113
+ state: FailureState,
114
+ message: string,
115
+ ): void {
116
+ if (!store) return;
117
+ try {
118
+ store.close();
119
+ } catch (error) {
120
+ recordFailure(state, error, message);
121
+ }
122
+ }
123
+
124
+ function closeReturnedOwner(result: ManagedTaskArtifactOwner | undefined, state: FailureState): void {
125
+ closeManagedStore(result?.manager.getManagedStore(), state, "Closing the task artifact owner also failed.");
126
+ }
127
+
128
+ /** Reopen an owner only beneath the captured managed sessions-root authority. */
129
+ export function restoreManagedTaskArtifactOwner(
130
+ context: TaskArtifactOwnerStorageContext,
131
+ sessionId: string,
132
+ locatorValue: unknown,
133
+ ): ManagedTaskArtifactOwner {
134
+ const locator = parseTaskArtifactOwnerLocator(locatorValue);
135
+ if (!locator) throw new Error("task_artifact_owner_locator_missing");
136
+ const rootStore = newSessionRootStore(context);
137
+ const state: FailureState = { failed: false, value: undefined };
138
+ let result: ManagedTaskArtifactOwner | undefined;
139
+ try {
140
+ const ownerStore = openOwnerStore(context, rootStore, locator, sessionId);
141
+ result = taskArtifactOwnerFromStore(locator, ownerStore);
142
+ } catch (error) {
143
+ recordFailure(state, error, "Restoring the task artifact owner failed.");
144
+ }
145
+ closeManagedStore(rootStore, state, "Restoring the task artifact owner and closing its root store failed.");
146
+ if (state.failed) {
147
+ closeReturnedOwner(result, state);
148
+ throw state.value;
149
+ }
150
+ if (!result) throw new Error("task_artifact_owner_unavailable");
151
+ return result;
152
+ }
153
+
154
+ /** Establish or restore the logical-session owner under the captured managed sessions authority. */
155
+ export async function ensureManagedTaskArtifactOwner(
156
+ context: TaskArtifactOwnerStorageContext,
157
+ sessionId: string,
158
+ locatorValue: unknown,
159
+ legacyArtifactRelativePath?: string,
160
+ ): Promise<ManagedTaskArtifactOwner> {
161
+ assertSessionRoot(context);
162
+ const locator = parseTaskArtifactOwnerLocator(locatorValue);
163
+ const ownerId = locator?.ownerId ?? ownerIdForSession(sessionId);
164
+ if (ownerId !== ownerIdForSession(sessionId)) throw new Error("task_artifact_owner_session_mismatch");
165
+ const lock = await acquireManagedLock(
166
+ path.join(context.sessionsRoot, OWNER_LOCK_DIRECTORY),
167
+ `owner-${ownerId}`,
168
+ context.rootAuthority,
169
+ context.securityPolicy,
170
+ );
171
+
172
+ let result: ManagedTaskArtifactOwner | undefined;
173
+ const state: FailureState = { failed: false, value: undefined };
174
+ try {
175
+ lock.assertOwned();
176
+ if (locator) {
177
+ result = restoreManagedTaskArtifactOwner(context, sessionId, locator);
178
+ } else {
179
+ const rootStore = newSessionRootStore(context);
180
+ let ownerStore: ManagedSessionDescendantStore | undefined;
181
+ try {
182
+ rootStore.verifyRootSecurity();
183
+ const ownerRelative = ownerRelativePath(ownerId);
184
+ const ownerPath = path.join(context.sessionsRoot, ownerRelative);
185
+ let existingDirectory = false;
186
+ try {
187
+ const stat = fs.lstatSync(ownerPath, { bigint: true });
188
+ if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error("task_artifact_owner_replaced");
189
+ existingDirectory = true;
190
+ } catch (error) {
191
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
192
+ }
193
+
194
+ if (existingDirectory) {
195
+ const identity = rootStore.captureDirectoryIdentity(ownerRelative);
196
+ const existingLocator: TaskArtifactOwnerLocator = {
197
+ schemaVersion: OWNER_SCHEMA_VERSION,
198
+ ownerId,
199
+ directoryDev: identity.dev,
200
+ directoryIno: identity.ino,
201
+ };
202
+ const manifest: OwnerManifest = readOwnerManifest(rootStore, existingLocator);
203
+ if (manifest.sessionId !== sessionId) throw new Error("task_artifact_owner_session_mismatch");
204
+ const restoredLocator = locatorFromManifest(manifest);
205
+ const restoredStore = openOwnerStore(context, rootStore, restoredLocator, sessionId);
206
+ result = taskArtifactOwnerFromStore(restoredLocator, restoredStore);
207
+ } else {
208
+ let legacySource: { relativePath: string; snapshot: NativeDirectoryTreeSnapshot } | undefined;
209
+ if (legacyArtifactRelativePath !== undefined) {
210
+ assertSafeRelativePath(legacyArtifactRelativePath);
211
+ if (legacyPathOverlapsOwner(context.sessionsRoot, legacyArtifactRelativePath, ownerRelative))
212
+ throw new Error("task_artifact_owner_legacy_path_overlaps_owner");
213
+ try {
214
+ fs.lstatSync(path.join(context.sessionsRoot, legacyArtifactRelativePath), { bigint: true });
215
+ legacySource = {
216
+ relativePath: legacyArtifactRelativePath,
217
+ snapshot: rootStore.captureTree(legacyArtifactRelativePath),
218
+ };
219
+ } catch (error) {
220
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
221
+ }
222
+ }
223
+
224
+ rootStore.ensureDirectory(OWNER_DIRECTORY);
225
+ const identity = rootStore.ensureDirectory(ownerRelative);
226
+ const createdLocator: TaskArtifactOwnerLocator = {
227
+ schemaVersion: OWNER_SCHEMA_VERSION,
228
+ ownerId,
229
+ directoryDev: identity.dev.toString(),
230
+ directoryIno: identity.ino.toString(),
231
+ };
232
+ ownerStore = rootStore.deriveSubtree(ownerRelative);
233
+ if (legacySource) {
234
+ await copyLegacyArtifactTree(
235
+ rootStore,
236
+ legacySource.relativePath,
237
+ ownerRelative,
238
+ legacySource.snapshot,
239
+ );
240
+ }
241
+ ownerStore.fsyncTree();
242
+ lock.assertOwned();
243
+ await ownerStore.publishNoReplace(
244
+ OWNER_MANIFEST,
245
+ Buffer.from(`${JSON.stringify({ ...createdLocator, sessionId })}\n`, "utf8"),
246
+ );
247
+ const verifiedStore = openOwnerStore(context, rootStore, createdLocator, sessionId);
248
+ try {
249
+ ownerStore.close();
250
+ } catch (error) {
251
+ try {
252
+ verifiedStore.close();
253
+ } catch (closeError) {
254
+ throw new AggregateError(
255
+ [error, closeError],
256
+ "Opening the owner and closing its writer store failed.",
257
+ );
258
+ }
259
+ throw error;
260
+ }
261
+ ownerStore = undefined;
262
+ result = taskArtifactOwnerFromStore(createdLocator, verifiedStore);
263
+ }
264
+ } catch (error) {
265
+ recordFailure(state, error, "Provisioning the task artifact owner failed.");
266
+ }
267
+ closeManagedStore(
268
+ ownerStore,
269
+ state,
270
+ "Provisioning the task artifact owner and closing its writer store failed.",
271
+ );
272
+ closeManagedStore(rootStore, state, "Provisioning the task artifact owner and closing its root store failed.");
273
+ }
274
+ } catch (error) {
275
+ recordFailure(state, error, "Task artifact owner setup failed.");
276
+ }
277
+
278
+ try {
279
+ await lock.release();
280
+ } catch (releaseError) {
281
+ recordFailure(state, releaseError, "Task artifact owner setup and lock release failed.");
282
+ }
283
+ if (state.failed) {
284
+ closeReturnedOwner(result, state);
285
+ throw state.value;
286
+ }
287
+ if (!result) throw new Error("task_artifact_owner_unavailable");
288
+ return result;
289
+ }