@gajae-code/coding-agent 0.11.5 → 0.11.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 (138) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/types/capability/index.d.ts +10 -20
  3. package/dist/types/capability/types.d.ts +3 -0
  4. package/dist/types/commands/daemon.d.ts +2 -2
  5. package/dist/types/commands/sdk.d.ts +20 -2
  6. package/dist/types/config/keybindings.d.ts +1 -1
  7. package/dist/types/config/settings-schema.d.ts +67 -7
  8. package/dist/types/eval/py/runner-artifact.d.ts +8 -0
  9. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +5 -5
  10. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +20 -0
  11. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +19 -5
  12. package/dist/types/main.d.ts +24 -1
  13. package/dist/types/modes/components/btw-panel.d.ts +1 -0
  14. package/dist/types/modes/components/model-selector.d.ts +2 -0
  15. package/dist/types/modes/components/tool-status-header.d.ts +1 -0
  16. package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
  17. package/dist/types/modes/interactive-mode.d.ts +2 -0
  18. package/dist/types/modes/types.d.ts +1 -0
  19. package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
  20. package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -0
  21. package/dist/types/sdk/bus/index.d.ts +1 -1
  22. package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
  23. package/dist/types/sdk/bus/notification-service.d.ts +34 -0
  24. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +13 -3
  25. package/dist/types/sdk/bus/telegram-daemon.d.ts +61 -3
  26. package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
  27. package/dist/types/sdk/client/discovery.d.ts +2 -0
  28. package/dist/types/sdk/client/liveness.d.ts +7 -0
  29. package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
  30. package/dist/types/sdk/transport/index.d.ts +12 -0
  31. package/dist/types/sdk/transport/relay.d.ts +43 -0
  32. package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
  33. package/dist/types/sdk/transport/socket.d.ts +7 -0
  34. package/dist/types/sdk/transport/stdio.d.ts +3 -0
  35. package/dist/types/session/agent-session.d.ts +28 -5
  36. package/dist/types/session/btw-contract.d.ts +16 -0
  37. package/dist/types/session/internal/managed-session-scope.d.ts +21 -7
  38. package/dist/types/session/internal/managed-session-storage.d.ts +10 -8
  39. package/dist/types/session/session-manager.d.ts +35 -4
  40. package/dist/types/session/yield-queue.d.ts +8 -0
  41. package/dist/types/setup/credential-import.d.ts +5 -1
  42. package/dist/types/setup/provider-onboarding.d.ts +2 -0
  43. package/dist/types/ssh/utils.d.ts +1 -0
  44. package/dist/types/task/discovery.d.ts +3 -1
  45. package/dist/types/tools/fetch.d.ts +5 -8
  46. package/dist/types/tools/image-gen.d.ts +16 -0
  47. package/dist/types/tools/skill-discovery.d.ts +7 -0
  48. package/dist/types/tools/sqlite-reader.d.ts +7 -0
  49. package/dist/types/utils/shell-snapshot.d.ts +12 -0
  50. package/dist/types/web/insane/bridge.d.ts +1 -0
  51. package/dist/types/web/insane/url-guard.d.ts +17 -0
  52. package/package.json +8 -8
  53. package/scripts/generate-sdk-operation-inventory.ts +3 -0
  54. package/src/capability/index.ts +50 -42
  55. package/src/capability/types.ts +3 -0
  56. package/src/cli/setup-cli.ts +10 -4
  57. package/src/cli.ts +34 -9
  58. package/src/commands/daemon.ts +22 -5
  59. package/src/commands/sdk.ts +42 -9
  60. package/src/config/keybindings.ts +1 -1
  61. package/src/config/model-profiles.ts +18 -0
  62. package/src/config/settings-schema.ts +71 -7
  63. package/src/defaults/gjc/skills/ultragoal/SKILL.md +42 -1
  64. package/src/discovery/ssh.ts +12 -1
  65. package/src/eval/py/kernel.ts +1 -21
  66. package/src/eval/py/runner-artifact.ts +84 -0
  67. package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
  68. package/src/gjc-runtime/ultragoal-guard.ts +88 -10
  69. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +103 -0
  70. package/src/gjc-runtime/ultragoal-runtime.ts +275 -12
  71. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  72. package/src/gjc-runtime/workflow-manifest.ts +23 -0
  73. package/src/internal-urls/docs-index.generated.ts +9 -8
  74. package/src/internal-urls/local-protocol.ts +32 -6
  75. package/src/main.ts +87 -28
  76. package/src/modes/action-registry.ts +1 -1
  77. package/src/modes/components/btw-panel.ts +82 -16
  78. package/src/modes/components/model-selector.ts +31 -3
  79. package/src/modes/components/tool-status-header.ts +6 -1
  80. package/src/modes/controllers/btw-controller.ts +119 -36
  81. package/src/modes/controllers/command-controller.ts +15 -0
  82. package/src/modes/controllers/extension-ui-controller.ts +14 -2
  83. package/src/modes/controllers/input-controller.ts +31 -0
  84. package/src/modes/controllers/selector-controller.ts +106 -12
  85. package/src/modes/interactive-mode.ts +16 -7
  86. package/src/modes/runtime-init.ts +16 -2
  87. package/src/modes/types.ts +1 -0
  88. package/src/prompts/system/btw-user.md +3 -8
  89. package/src/prompts/tools/skill-discovery.md +1 -0
  90. package/src/runtime-mcp/client.ts +41 -55
  91. package/src/runtime-mcp/content-limits.ts +71 -0
  92. package/src/runtime-mcp/json-rpc.ts +23 -9
  93. package/src/runtime-mcp/transports/http.ts +50 -16
  94. package/src/sdk/bus/chat-daemon-control.ts +20 -7
  95. package/src/sdk/bus/index.ts +8 -7
  96. package/src/sdk/bus/lifecycle-commands.ts +16 -7
  97. package/src/sdk/bus/notification-service.ts +41 -18
  98. package/src/sdk/bus/telegram-daemon-contract.ts +13 -3
  99. package/src/sdk/bus/telegram-daemon-control.ts +26 -8
  100. package/src/sdk/bus/telegram-daemon.ts +1736 -338
  101. package/src/sdk/bus/topic-registry.ts +451 -12
  102. package/src/sdk/client/discovery.ts +19 -3
  103. package/src/sdk/client/liveness.ts +49 -0
  104. package/src/sdk/host/control/dispatch.ts +14 -5
  105. package/src/sdk/protocol/operation-inventory.generated.json +29 -0
  106. package/src/sdk/session.ts +29 -5
  107. package/src/sdk/transport/auth-preface.ts +82 -0
  108. package/src/sdk/transport/index.ts +18 -0
  109. package/src/sdk/transport/relay.ts +252 -0
  110. package/src/sdk/transport/serve-cli.ts +105 -0
  111. package/src/sdk/transport/socket.ts +197 -0
  112. package/src/sdk/transport/stdio.ts +17 -0
  113. package/src/session/agent-session.ts +1212 -768
  114. package/src/session/blob-store.ts +19 -6
  115. package/src/session/btw-contract.ts +59 -0
  116. package/src/session/internal/managed-session-scope.ts +313 -57
  117. package/src/session/internal/managed-session-storage.ts +146 -35
  118. package/src/session/session-manager.ts +385 -85
  119. package/src/session/yield-queue.ts +20 -0
  120. package/src/setup/credential-import.ts +28 -3
  121. package/src/setup/provider-onboarding.ts +30 -1
  122. package/src/setup/provider-presets.json +2 -1
  123. package/src/slash-commands/builtin-registry.ts +37 -1
  124. package/src/ssh/utils.ts +14 -0
  125. package/src/task/discovery.ts +8 -2
  126. package/src/tools/ask-answer-registry.ts +15 -1
  127. package/src/tools/ask.ts +1 -1
  128. package/src/tools/fetch.ts +13 -131
  129. package/src/tools/image-gen.ts +301 -20
  130. package/src/tools/read.ts +2 -0
  131. package/src/tools/skill-discovery.ts +39 -3
  132. package/src/tools/sqlite-reader.ts +63 -5
  133. package/src/utils/pasted-image-path.ts +15 -6
  134. package/src/utils/shell-snapshot.ts +173 -63
  135. package/src/web/insane/bridge.ts +5 -1
  136. package/src/web/insane/url-guard.ts +138 -27
  137. package/src/web/scrapers/types.ts +19 -49
  138. package/src/web/scrapers/utils.ts +12 -26
@@ -251,19 +251,29 @@ export function formatNotificationStatusReport(report: NotificationStatusReport)
251
251
 
252
252
  // --- endpoint / daemon file readers -------------------------------------
253
253
 
254
- interface EndpointView {
254
+ export interface NotificationEndpointView {
255
255
  sessionId: string;
256
256
  pid: number | undefined;
257
257
  stale: boolean;
258
258
  }
259
259
 
260
- type EndpointFileRead =
261
- | { kind: "endpoint"; view: EndpointView; identity: NotificationEndpointFileIdentity }
260
+ export type NotificationEndpointLiveness = "live" | "dead" | "unknown";
261
+
262
+ /**
263
+ * Classification used by recovery and startup takeover. A file is an endpoint
264
+ * only when it has endpoint authority fields; lifecycle/audit records are never
265
+ * candidates for endpoint cleanup.
266
+ */
267
+ export type NotificationEndpointClassification =
268
+ | {
269
+ kind: "endpoint";
270
+ view: NotificationEndpointView;
271
+ liveness: NotificationEndpointLiveness;
272
+ identity: NotificationEndpointFileIdentity;
273
+ }
262
274
  | { kind: "non-endpoint" }
263
275
  | { kind: "unreadable" };
264
276
 
265
- type EndpointLiveness = "live" | "dead" | "unknown";
266
-
267
277
  /**
268
278
  * Classify an endpoint using owner-proof semantics. An endpoint is only `dead`
269
279
  * with positive proof: an explicit `stale` tombstone, or a recorded pid that is
@@ -271,7 +281,10 @@ type EndpointLiveness = "live" | "dead" | "unknown";
271
281
  * must never be treated as dead — removing it could delete a live session's
272
282
  * discovery file that simply omitted a pid.
273
283
  */
274
- function endpointLiveness(view: EndpointView, pidAlive: (pid: number) => boolean): EndpointLiveness {
284
+ export function notificationEndpointLiveness(
285
+ view: NotificationEndpointView,
286
+ pidAlive: (pid: number) => boolean,
287
+ ): NotificationEndpointLiveness {
275
288
  if (view.stale) return "dead";
276
289
  if (view.pid === undefined) return "unknown";
277
290
  return pidAlive(view.pid) ? "live" : "dead";
@@ -314,11 +327,19 @@ function isCanonicalLifecycleArtifactName(name: string): boolean {
314
327
  );
315
328
  }
316
329
 
317
- function unreadableEndpointResult(file: string): EndpointFileRead {
330
+ function unreadableEndpointResult(file: string): NotificationEndpointClassification {
318
331
  return isCanonicalLifecycleArtifactName(path.basename(file)) ? { kind: "non-endpoint" } : { kind: "unreadable" };
319
332
  }
320
333
 
321
- async function readEndpointView(fs: NotificationServiceFs, file: string): Promise<EndpointFileRead> {
334
+ /**
335
+ * Read and classify one endpoint candidate. The returned identity belongs to
336
+ * exactly the bytes inspected and is required for any later deletion.
337
+ */
338
+ export async function classifyNotificationEndpoint(
339
+ fs: Pick<NotificationServiceFs, "readEndpointFile">,
340
+ file: string,
341
+ pidAlive: (pid: number) => boolean,
342
+ ): Promise<NotificationEndpointClassification> {
322
343
  let endpoint: NotificationEndpointFile;
323
344
  let raw: string;
324
345
  try {
@@ -334,16 +355,19 @@ async function readEndpointView(fs: NotificationServiceFs, file: string): Promis
334
355
  return unreadableEndpointResult(file);
335
356
  }
336
357
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { kind: "non-endpoint" };
358
+ if (path.basename(file) === "broker.json") return { kind: "non-endpoint" };
337
359
  const rec = parsed as Record<string, unknown>;
338
360
  if (isLifecycleArtifact(rec) || typeof rec.url !== "string" || typeof rec.token !== "string")
339
361
  return { kind: "non-endpoint" };
362
+ const view: NotificationEndpointView = {
363
+ sessionId: typeof rec.sessionId === "string" ? rec.sessionId : path.basename(file, ".json"),
364
+ pid: safePositiveInteger(rec.pid),
365
+ stale: rec.stale === true,
366
+ };
340
367
  return {
341
368
  kind: "endpoint",
342
- view: {
343
- sessionId: typeof rec.sessionId === "string" ? rec.sessionId : path.basename(file, ".json"),
344
- pid: safePositiveInteger(rec.pid),
345
- stale: rec.stale === true,
346
- },
369
+ view,
370
+ liveness: notificationEndpointLiveness(view, pidAlive),
347
371
  identity: endpoint.identity,
348
372
  };
349
373
  }
@@ -604,14 +628,13 @@ export async function checkNotificationHealth(opts: HealthOptions): Promise<Noti
604
628
  let unknownEndpoints = 0;
605
629
  let unreadable = 0;
606
630
  for (const name of files) {
607
- const record = await readEndpointView(fs, path.join(dir, name));
631
+ const record = await classifyNotificationEndpoint(fs, path.join(dir, name), pidAlive);
608
632
  if (record.kind === "non-endpoint") continue;
609
633
  if (record.kind === "unreadable") {
610
634
  unreadable += 1;
611
635
  continue;
612
636
  }
613
- const view = record.view;
614
- switch (endpointLiveness(view, pidAlive)) {
637
+ switch (record.liveness) {
615
638
  case "live":
616
639
  live += 1;
617
640
  break;
@@ -1040,7 +1063,7 @@ export async function recoverNotifications(opts: RecoveryOptions): Promise<Notif
1040
1063
  let unreadable = 0;
1041
1064
  for (const name of files) {
1042
1065
  const file = path.join(dir, name);
1043
- const record = await readEndpointView(fs, file);
1066
+ const record = await classifyNotificationEndpoint(fs, file, pidAlive);
1044
1067
  if (record.kind === "non-endpoint") continue;
1045
1068
  if (record.kind === "unreadable") {
1046
1069
  // Leave unparseable files untouched: they may be mid-write by a live server.
@@ -1048,7 +1071,7 @@ export async function recoverNotifications(opts: RecoveryOptions): Promise<Notif
1048
1071
  continue;
1049
1072
  }
1050
1073
  const view = record.view;
1051
- if (endpointLiveness(view, pidAlive) !== "dead") {
1074
+ if (record.liveness !== "dead") {
1052
1075
  // Keep live AND unknown (PID-less) endpoints: only positive proof of
1053
1076
  // death (a stale tombstone or a dead pid) authorizes removal.
1054
1077
  kept += 1;
@@ -16,7 +16,17 @@ export const NOTIFICATION_PROTOCOL_VERSION = 3;
16
16
  * The current development baseline already includes #2299's generation 4,
17
17
  * incarnation fencing in generation 5, owner-lock authority in generation 6,
18
18
  * identity-atomic transition markers in generation 7, stable signaling plus
19
- * tri-state foreign-owner provenance in generation 8, and retained managed
20
- * filesystem authority changes in generation 9.
19
+ * tri-state foreign-owner provenance in generation 8, retained managed
20
+ * filesystem authority changes in generation 9, SDK-startup auto-reclaim of a
21
+ * confirmed-dead owner's lock in generation 10, legacy stopped-tombstone
22
+ * reclamation in generation 11, force-escalated SIGKILL of an unresponsive
23
+ * older-generation owner during automatic generation-upgrade reload in
24
+ * generation 12, restored macOS daemon signaling (kill(2) with a start-time
25
+ * incarnation recheck, replacing the darwin no-op) in generation 13, retained
26
+ * legacy stopped-lock reclamation in generation 14, Windows expected-identity
27
+ * ACL verification and repair in generation 15, identity-fenced stale endpoint
28
+ * startup recovery in generation 16, Telegram topic recovery authority fencing
29
+ * in generation 17, and fail-closed blank-token validation plus lifecycle-startup
30
+ * stop fencing in generation 18.
21
31
  */
22
- export const DAEMON_GENERATION = 9;
32
+ export const DAEMON_GENERATION = 18;
@@ -113,18 +113,29 @@ export interface DaemonProcessReference {
113
113
  }
114
114
 
115
115
  function defaultProcessReference(pid: number, platform = os.platform()): DaemonProcessReference | undefined {
116
- // Process.fromPid on Darwin still resolves identity and then signals a numeric
117
- // PID. A process can exit and its PID be reused in that interval, so daemon
118
- // control must not use it as privileged signaling authority.
119
- if (platform === "darwin") return undefined;
120
116
  try {
121
117
  const processRef = Process.fromPid(pid);
122
118
  if (!processRef || !isProcessIncarnation(processRef.incarnation)) return undefined;
119
+ const incarnation = processRef.incarnation;
123
120
  return {
124
- incarnation: processRef.incarnation,
121
+ incarnation,
125
122
  signalRoot: signal => {
126
123
  const nativeSignal = os.constants.signals[signal];
127
124
  if (nativeSignal === undefined) throw new Error(`Unsupported signal: ${signal}`);
125
+ // macOS exposes no pidfd and the native signal_root is a no-op there, so
126
+ // the daemon control plane previously had NO way to signal a live owner —
127
+ // every stop/reload of a live/hung daemon refused ("ownership changed;
128
+ // refusing to signal"), and only an external `kill -9` could recover it.
129
+ // Signal by numeric PID via kill(2), but re-read the immutable start-time
130
+ // incarnation immediately beforehand so a PID that exited and was reused
131
+ // since capture is never signaled; the residual window is the few
132
+ // instructions between this recheck and kill(2).
133
+ if (platform === "darwin") {
134
+ const current = Process.fromPid(pid) as { incarnation?: unknown } | null;
135
+ if (!current || current.incarnation !== incarnation) throw new Error("Pinned process is already gone");
136
+ process.kill(pid, signal);
137
+ return;
138
+ }
128
139
  const rootProcess = processRef as typeof processRef & { signalRoot(signal: number): boolean };
129
140
  if (!rootProcess.signalRoot(nativeSignal)) throw new Error("Pinned process is already gone");
130
141
  },
@@ -167,8 +178,8 @@ function defaultPidAlive(pid: number): boolean {
167
178
  try {
168
179
  process.kill(pid, 0);
169
180
  return true;
170
- } catch {
171
- return false;
181
+ } catch (error) {
182
+ return (error as NodeJS.ErrnoException).code !== "ESRCH";
172
183
  }
173
184
  }
174
185
 
@@ -366,7 +377,14 @@ export class TelegramDaemonController implements BuiltInDaemonController {
366
377
  }
367
378
 
368
379
  async reloadForGenerationUpgrade(opts: DaemonOperationOptions = {}): Promise<TelegramGenerationReloadResult> {
369
- const operation = await this.stopOrReload("reload", opts);
380
+ // A generation upgrade MUST replace an incompatible older-generation owner to
381
+ // avoid a permanent single-poller deadlock. Unlike a manual `gjc daemon
382
+ // reload`, this automatic path force-escalates to SIGKILL when the old owner
383
+ // ignores the cooperative SIGTERM within the graceful timeout, so SDK startup
384
+ // self-recovers instead of failing closed and asking the operator to rerun
385
+ // with --force. The SIGKILL remains fenced to the still-live, still-matching
386
+ // captured owner (same ownerId + pid), so a fresh replacement is never killed.
387
+ const operation = await this.stopOrReload("reload", { ...opts, force: true });
370
388
  if (!operation.ok) return { outcome: "failed", operation };
371
389
  const after = await this.status();
372
390
  return after.health === "running" ? { outcome: "ready", operation } : { outcome: "failed", operation };