@gajae-code/coding-agent 0.11.6 → 0.11.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.
Files changed (259) hide show
  1. package/CHANGELOG.md +92 -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/cli/args.d.ts +0 -2
  5. package/dist/types/commands/daemon.d.ts +2 -2
  6. package/dist/types/commands/sdk.d.ts +20 -2
  7. package/dist/types/config/keybindings.d.ts +34 -4
  8. package/dist/types/config/model-profile-activation.d.ts +4 -3
  9. package/dist/types/config/model-profile-contract.d.ts +38 -0
  10. package/dist/types/config/settings-schema.d.ts +70 -7
  11. package/dist/types/edit/modes/patch.d.ts +13 -0
  12. package/dist/types/edit/path-mutation-lock.d.ts +15 -0
  13. package/dist/types/eval/py/executor.d.ts +1 -1
  14. package/dist/types/eval/py/runner-artifact.d.ts +8 -0
  15. package/dist/types/eval/py/tool-bridge.d.ts +1 -2
  16. package/dist/types/gjc-runtime/repository-binding.d.ts +63 -0
  17. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +6 -5
  18. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +20 -0
  19. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +43 -5
  20. package/dist/types/main.d.ts +32 -1
  21. package/dist/types/modes/components/btw-panel.d.ts +1 -0
  22. package/dist/types/modes/components/model-selector.d.ts +2 -0
  23. package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
  24. package/dist/types/modes/components/tool-status-header.d.ts +4 -2
  25. package/dist/types/modes/components/welcome.d.ts +2 -0
  26. package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
  27. package/dist/types/modes/controllers/command-controller.d.ts +2 -0
  28. package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
  29. package/dist/types/modes/interactive-mode.d.ts +7 -2
  30. package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
  31. package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
  32. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
  33. package/dist/types/modes/types.d.ts +4 -0
  34. package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
  35. package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
  36. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  37. package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
  38. package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
  39. package/dist/types/sdk/broker/broker.d.ts +2 -0
  40. package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
  41. package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -1
  42. package/dist/types/sdk/bus/config-commands.d.ts +2 -2
  43. package/dist/types/sdk/bus/config.d.ts +2 -0
  44. package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
  45. package/dist/types/sdk/bus/index.d.ts +26 -1
  46. package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
  47. package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
  48. package/dist/types/sdk/bus/notification-service.d.ts +34 -0
  49. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +105 -0
  50. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +18 -3
  51. package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
  52. package/dist/types/sdk/bus/telegram-daemon.d.ts +189 -4
  53. package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
  54. package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
  55. package/dist/types/sdk/client/discovery.d.ts +2 -0
  56. package/dist/types/sdk/client/liveness.d.ts +7 -0
  57. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  58. package/dist/types/sdk/host/host.d.ts +1 -1
  59. package/dist/types/sdk/host/query/handlers.d.ts +9 -0
  60. package/dist/types/sdk/index.d.ts +2 -0
  61. package/dist/types/sdk/prompt-status.d.ts +88 -0
  62. package/dist/types/sdk/session-directory.d.ts +1 -1
  63. package/dist/types/sdk/startup-capability.d.ts +3 -0
  64. package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
  65. package/dist/types/sdk/transport/index.d.ts +12 -0
  66. package/dist/types/sdk/transport/relay.d.ts +43 -0
  67. package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
  68. package/dist/types/sdk/transport/socket.d.ts +7 -0
  69. package/dist/types/sdk/transport/stdio.d.ts +3 -0
  70. package/dist/types/session/agent-session.d.ts +30 -5
  71. package/dist/types/session/btw-contract.d.ts +16 -0
  72. package/dist/types/session/internal/managed-session-scope.d.ts +32 -9
  73. package/dist/types/session/internal/managed-session-storage.d.ts +27 -10
  74. package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
  75. package/dist/types/session/session-manager.d.ts +25 -4
  76. package/dist/types/session/session-storage.d.ts +18 -0
  77. package/dist/types/setup/credential-import.d.ts +5 -1
  78. package/dist/types/setup/provider-onboarding.d.ts +2 -0
  79. package/dist/types/ssh/utils.d.ts +1 -0
  80. package/dist/types/task/discovery.d.ts +3 -1
  81. package/dist/types/task/index.d.ts +3 -1
  82. package/dist/types/task/receipt.d.ts +2 -0
  83. package/dist/types/task/types.d.ts +94 -0
  84. package/dist/types/tools/ask.d.ts +454 -174
  85. package/dist/types/tools/fetch.d.ts +5 -8
  86. package/dist/types/tools/image-gen.d.ts +63 -1
  87. package/dist/types/tools/index.d.ts +4 -0
  88. package/dist/types/tools/skill-discovery.d.ts +7 -0
  89. package/dist/types/tools/sqlite-reader.d.ts +7 -0
  90. package/dist/types/utils/prompt-suggestion.d.ts +33 -0
  91. package/dist/types/utils/shell-snapshot.d.ts +12 -0
  92. package/dist/types/web/insane/bridge.d.ts +1 -0
  93. package/dist/types/web/insane/url-guard.d.ts +17 -0
  94. package/package.json +8 -8
  95. package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
  96. package/scripts/dogfood-repository-binding.ts +209 -0
  97. package/scripts/generate-sdk-operation-inventory.ts +4 -0
  98. package/scripts/run-test-manifest.ts +4 -4
  99. package/src/capability/index.ts +50 -42
  100. package/src/capability/types.ts +3 -0
  101. package/src/cli/args.ts +2 -92
  102. package/src/cli/notify-cli.ts +5 -38
  103. package/src/cli/setup-cli.ts +10 -4
  104. package/src/cli.ts +52 -9
  105. package/src/commands/daemon.ts +22 -5
  106. package/src/commands/sdk.ts +44 -11
  107. package/src/config/keybindings.ts +158 -25
  108. package/src/config/model-profile-activation.ts +7 -18
  109. package/src/config/model-profile-contract.ts +161 -0
  110. package/src/config/model-profiles.ts +18 -0
  111. package/src/config/model-registry.ts +11 -1
  112. package/src/config/settings-schema.ts +73 -7
  113. package/src/coordinator-mcp/model-preset.ts +21 -74
  114. package/src/defaults/gjc/skills/deep-interview/SKILL.md +2 -2
  115. package/src/defaults/gjc/skills/ralplan/SKILL.md +3 -1
  116. package/src/defaults/gjc/skills/ultragoal/SKILL.md +42 -1
  117. package/src/discovery/ssh.ts +12 -1
  118. package/src/edit/index.ts +3 -6
  119. package/src/edit/modes/patch.ts +38 -1
  120. package/src/edit/modes/replace.ts +42 -0
  121. package/src/edit/path-mutation-lock.ts +112 -0
  122. package/src/eval/py/executor.ts +30 -11
  123. package/src/eval/py/kernel.ts +1 -21
  124. package/src/eval/py/prelude.py +2 -2
  125. package/src/eval/py/runner-artifact.ts +84 -0
  126. package/src/eval/py/tool-bridge.ts +34 -18
  127. package/src/extensibility/gjc-plugins/runtime-adapters.ts +2 -1
  128. package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
  129. package/src/gjc-runtime/launch-tmux.ts +1 -1
  130. package/src/gjc-runtime/managed-owner-supervisor.ts +83 -4
  131. package/src/gjc-runtime/ralplan-runtime.ts +75 -3
  132. package/src/gjc-runtime/repository-binding.ts +267 -0
  133. package/src/gjc-runtime/session-state-sidecar.ts +25 -9
  134. package/src/gjc-runtime/tmux-owner-isolation.ts +2 -1
  135. package/src/gjc-runtime/tmux-sessions.ts +45 -6
  136. package/src/gjc-runtime/ultragoal-guard.ts +93 -15
  137. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +103 -0
  138. package/src/gjc-runtime/ultragoal-runtime.ts +456 -31
  139. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  140. package/src/gjc-runtime/workflow-manifest.ts +23 -0
  141. package/src/internal-urls/docs-index.generated.ts +12 -10
  142. package/src/internal-urls/local-protocol.ts +64 -21
  143. package/src/internal-urls/mcp-protocol.ts +73 -11
  144. package/src/main.ts +111 -37
  145. package/src/modes/DESIGN.md +5 -0
  146. package/src/modes/acp/acp-agent.ts +50 -16
  147. package/src/modes/action-registry.ts +1 -1
  148. package/src/modes/components/btw-panel.ts +82 -16
  149. package/src/modes/components/model-selector.ts +73 -11
  150. package/src/modes/components/plan-preview-overlay.ts +1 -1
  151. package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
  152. package/src/modes/components/tool-status-header.ts +25 -4
  153. package/src/modes/components/welcome.ts +22 -18
  154. package/src/modes/controllers/btw-controller.ts +119 -36
  155. package/src/modes/controllers/command-controller.ts +97 -54
  156. package/src/modes/controllers/event-controller.ts +10 -0
  157. package/src/modes/controllers/input-controller.ts +38 -2
  158. package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
  159. package/src/modes/controllers/selector-controller.ts +149 -63
  160. package/src/modes/interactive-mode.ts +57 -34
  161. package/src/modes/prompt-action-autocomplete.ts +32 -13
  162. package/src/modes/prompt-suggestion-controller.ts +96 -0
  163. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
  164. package/src/modes/types.ts +4 -0
  165. package/src/modes/utils/hotkeys-markdown.ts +64 -36
  166. package/src/modes/utils/ui-helpers.ts +5 -3
  167. package/src/prompts/agents/architect.md +0 -1
  168. package/src/prompts/agents/critic.md +0 -1
  169. package/src/prompts/system/btw-user.md +3 -8
  170. package/src/prompts/system/prompt-suggestion-system.md +32 -0
  171. package/src/prompts/system/system-prompt.md +6 -4
  172. package/src/prompts/tools/cron.md +3 -1
  173. package/src/prompts/tools/skill-discovery.md +1 -0
  174. package/src/runtime-mcp/client.ts +41 -55
  175. package/src/runtime-mcp/content-limits.ts +71 -0
  176. package/src/runtime-mcp/json-rpc.ts +23 -9
  177. package/src/runtime-mcp/manager.ts +51 -53
  178. package/src/runtime-mcp/oauth-flow.ts +0 -72
  179. package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
  180. package/src/runtime-mcp/transports/http.ts +63 -22
  181. package/src/sdk/broker/broker.ts +34 -20
  182. package/src/sdk/broker/lifecycle.ts +185 -36
  183. package/src/sdk/bus/chat-daemon-control.ts +19 -5
  184. package/src/sdk/bus/config-commands.ts +2 -2
  185. package/src/sdk/bus/config.ts +9 -1
  186. package/src/sdk/bus/conversation-store.ts +40 -6
  187. package/src/sdk/bus/index.ts +244 -70
  188. package/src/sdk/bus/lifecycle-commands.ts +63 -10
  189. package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
  190. package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
  191. package/src/sdk/bus/notification-orchestration.ts +12 -0
  192. package/src/sdk/bus/notification-service.ts +68 -20
  193. package/src/sdk/bus/prompt-reconciliation.ts +239 -0
  194. package/src/sdk/bus/recent-activity.ts +90 -26
  195. package/src/sdk/bus/telegram-daemon-contract.ts +18 -3
  196. package/src/sdk/bus/telegram-daemon-control.ts +147 -35
  197. package/src/sdk/bus/telegram-daemon.ts +2463 -450
  198. package/src/sdk/bus/telegram-reference.ts +20 -2
  199. package/src/sdk/bus/topic-registry.ts +451 -12
  200. package/src/sdk/cli/session-cli.ts +15 -2
  201. package/src/sdk/client/discovery.ts +19 -3
  202. package/src/sdk/client/liveness.ts +49 -0
  203. package/src/sdk/host/control/dispatch.ts +1 -1
  204. package/src/sdk/host/control/operations.ts +1 -1
  205. package/src/sdk/host/host.ts +2 -6
  206. package/src/sdk/host/query/handlers.ts +70 -5
  207. package/src/sdk/index.ts +12 -0
  208. package/src/sdk/prompt-status.ts +97 -0
  209. package/src/sdk/protocol/operation-inventory.generated.json +58 -0
  210. package/src/sdk/protocol/operation-registry.ts +18 -4
  211. package/src/sdk/session-directory.ts +5 -1
  212. package/src/sdk/session.ts +31 -5
  213. package/src/sdk/startup-capability.ts +9 -1
  214. package/src/sdk/transport/auth-preface.ts +82 -0
  215. package/src/sdk/transport/index.ts +18 -0
  216. package/src/sdk/transport/relay.ts +252 -0
  217. package/src/sdk/transport/serve-cli.ts +105 -0
  218. package/src/sdk/transport/socket.ts +197 -0
  219. package/src/sdk/transport/stdio.ts +17 -0
  220. package/src/session/agent-session.ts +292 -154
  221. package/src/session/blob-store.ts +25 -7
  222. package/src/session/btw-contract.ts +59 -0
  223. package/src/session/internal/managed-session-scope.ts +1163 -137
  224. package/src/session/internal/managed-session-storage.ts +397 -234
  225. package/src/session/internal/native-publish-outcome.ts +266 -0
  226. package/src/session/session-manager.ts +620 -403
  227. package/src/session/session-storage.ts +128 -11
  228. package/src/setup/credential-import.ts +28 -3
  229. package/src/setup/provider-onboarding.ts +30 -1
  230. package/src/setup/provider-presets.json +2 -1
  231. package/src/slash-commands/builtin-registry.ts +1 -1
  232. package/src/ssh/utils.ts +14 -0
  233. package/src/task/discovery.ts +8 -2
  234. package/src/task/gjc-command.ts +2 -2
  235. package/src/task/index.ts +121 -15
  236. package/src/task/receipt.ts +3 -0
  237. package/src/task/types.ts +34 -3
  238. package/src/tools/ask.ts +199 -89
  239. package/src/tools/ast-edit.ts +2 -2
  240. package/src/tools/bash-pty-selection.ts +2 -2
  241. package/src/tools/browser/screenshot-format.ts +6 -1
  242. package/src/tools/cron.ts +1 -1
  243. package/src/tools/fetch.ts +13 -131
  244. package/src/tools/image-gen.ts +476 -23
  245. package/src/tools/index.ts +4 -0
  246. package/src/tools/read.ts +2 -0
  247. package/src/tools/render-utils.ts +7 -1
  248. package/src/tools/skill-discovery.ts +39 -3
  249. package/src/tools/sqlite-reader.ts +63 -5
  250. package/src/utils/edit-mode.ts +2 -2
  251. package/src/utils/pasted-image-path.ts +15 -6
  252. package/src/utils/prompt-suggestion.ts +265 -0
  253. package/src/utils/shell-snapshot.ts +173 -63
  254. package/src/utils/sixel.ts +3 -3
  255. package/src/web/insane/bridge.ts +5 -1
  256. package/src/web/insane/url-guard.ts +140 -27
  257. package/src/web/scrapers/docs-rs.ts +30 -5
  258. package/src/web/scrapers/types.ts +19 -49
  259. package/src/web/scrapers/utils.ts +12 -26
@@ -259,6 +259,19 @@ export interface VerifiedSessionDeleteTarget {
259
259
  detachedArtifactsPath?: string;
260
260
  /** Identity-bound quarantine path retained when transcript unlink deferred cleanup. */
261
261
  detachedTranscriptPath?: string;
262
+ /** Native-retained publisher successor observed during transcript cleanup. */
263
+ retainedTranscriptSuccessorPath?: string;
264
+ /** Native-retained exchange placeholder observed during transcript cleanup. */
265
+ retainedTranscriptPlaceholderPath?: string;
266
+ /** Native-retained transcript entry whose identity could not be verified. */
267
+ retainedTranscriptUnknownPath?: string;
268
+ /** Native-retained publisher successor observed during cleanup. */
269
+ retainedArtifactsSuccessorPath?: string;
270
+ /** Native-retained exchange placeholder observed during cleanup. */
271
+ retainedArtifactsPlaceholderPath?: string;
272
+ /** Native-retained entry whose identity could not be verified. */
273
+ retainedArtifactsUnknownPath?: string;
274
+
262
275
  /** Caller-published, no-replace quarantine pathname for the next artifact detach. */
263
276
  plannedArtifactsPath?: string;
264
277
  /** Caller-published, no-replace quarantine pathname for the next transcript detach. */
@@ -288,6 +301,9 @@ export type VerifiedSessionDeleteResult =
288
301
  artifactsTree: NativeDirectoryTreeSnapshot;
289
302
  /** Transcript identity (unchanged) for retry binding. */
290
303
  transcriptIdentity: SessionStorageFileIdentity;
304
+ retainedSuccessorPath?: string;
305
+ retainedPlaceholderPath?: string;
306
+ retainedUnknownPath?: string;
291
307
  }
292
308
  | {
293
309
  kind: "cleanup_pending";
@@ -297,6 +313,9 @@ export type VerifiedSessionDeleteResult =
297
313
  transcriptIdentity: SessionStorageFileIdentity;
298
314
  /** Optional identity-bound transcript quarantine path for restart cleanup. */
299
315
  detachedTranscriptPath?: string;
316
+ retainedSuccessorPath?: string;
317
+ retainedPlaceholderPath?: string;
318
+ retainedUnknownPath?: string;
300
319
  };
301
320
 
302
321
  /** Default OS-close dispatcher: a direct `fs.closeSync`. */
@@ -306,7 +325,22 @@ const defaultCloseAdapter: SessionStorageWriterCloseAdapter = {
306
325
  },
307
326
  };
308
327
 
309
- type NativeExactUnlinkResult = { ok: true; detachedPath?: string } | { ok: false; code: string; detachedPath?: string };
328
+ type NativeExactUnlinkResult =
329
+ | {
330
+ ok: true;
331
+ detachedPath?: string;
332
+ retainedSuccessorPath?: string;
333
+ retainedPlaceholderPath?: string;
334
+ retainedUnknownPath?: string;
335
+ }
336
+ | {
337
+ ok: false;
338
+ code: string;
339
+ detachedPath?: string;
340
+ retainedSuccessorPath?: string;
341
+ retainedPlaceholderPath?: string;
342
+ retainedUnknownPath?: string;
343
+ };
310
344
  type NativeExactUnlink = (
311
345
  path: string,
312
346
  identity: {
@@ -766,7 +800,13 @@ export class FileSessionStorage implements SessionStorage {
766
800
  expectedArtifactsIdentity,
767
801
  expectedArtifactsTree,
768
802
  detachedArtifactsPath,
803
+ retainedArtifactsSuccessorPath,
804
+ retainedArtifactsPlaceholderPath,
805
+ retainedArtifactsUnknownPath,
769
806
  detachedTranscriptPath,
807
+ retainedTranscriptSuccessorPath,
808
+ retainedTranscriptPlaceholderPath,
809
+ retainedTranscriptUnknownPath,
770
810
  plannedArtifactsPath,
771
811
  plannedTranscriptPath,
772
812
  artifactsRemoved,
@@ -847,7 +887,8 @@ export class FileSessionStorage implements SessionStorage {
847
887
  if (
848
888
  !expectedArtifactsIdentity ||
849
889
  path.dirname(detachedArtifactsPath) !== path.dirname(transcriptPath) ||
850
- !path.basename(detachedArtifactsPath).startsWith(".gjc-delete-")
890
+ (!path.basename(detachedArtifactsPath).startsWith(".gjc-delete-") &&
891
+ !detachedArtifactsPath.endsWith(".removing"))
851
892
  ) {
852
893
  throw new SessionDeleteVerificationError("artifacts", "Detached artifact cleanup evidence is invalid");
853
894
  }
@@ -882,17 +923,20 @@ export class FileSessionStorage implements SessionStorage {
882
923
  artifactsIdentity: expectedArtifactsIdentity,
883
924
  detachedArtifactsPath: retainedRoot,
884
925
  artifactsTree: expectedArtifactsTree,
926
+ ...((removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath)
927
+ ? { retainedSuccessorPath: removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath }
928
+ : {}),
929
+ ...((removal.retainedPlaceholderPath ?? retainedArtifactsPlaceholderPath)
930
+ ? { retainedPlaceholderPath: removal.retainedPlaceholderPath ?? retainedArtifactsPlaceholderPath }
931
+ : {}),
932
+ ...((removal.retainedUnknownPath ?? retainedArtifactsUnknownPath)
933
+ ? { retainedUnknownPath: removal.retainedUnknownPath ?? retainedArtifactsUnknownPath }
934
+ : {}),
885
935
  transcriptIdentity,
886
936
  };
887
937
  }
888
938
  }
889
939
 
890
- if (artifactsRemoved && (detachedArtifactsPath || expectedArtifactsIdentity)) {
891
- throw new SessionDeleteVerificationError(
892
- "artifacts",
893
- "Artifact phase receipt conflicts with pending artifact cleanup",
894
- );
895
- }
896
940
  const artifactsDir = transcriptPath.slice(0, -6);
897
941
  const artifactsIdentity = this.#optionalDirectoryIdentity(artifactsDir);
898
942
  if (artifactsRemoved && artifactsIdentity) {
@@ -947,12 +991,48 @@ export class FileSessionStorage implements SessionStorage {
947
991
  directory: true,
948
992
  quarantineName: path.basename(plannedArtifactsPath),
949
993
  });
950
- if (!detach.ok || !detach.detachedPath) {
994
+ if (!detach.detachedPath) {
951
995
  throw new SessionDeleteVerificationError(
952
996
  "artifacts",
953
997
  `Exact artifact detach rejected: ${detach.ok ? "missing_path" : detach.code}`,
954
998
  );
955
999
  }
1000
+ if (!detach.ok && process.platform !== "win32") {
1001
+ let descriptor: number | undefined;
1002
+ try {
1003
+ descriptor = fs.openSync(
1004
+ path.dirname(transcriptPath),
1005
+ fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW,
1006
+ );
1007
+ const durableParent = fs.fstatSync(descriptor, { bigint: true });
1008
+ if (
1009
+ !durableParent.isDirectory() ||
1010
+ durableParent.dev !== parentIdentity.dev ||
1011
+ durableParent.ino !== parentIdentity.ino
1012
+ )
1013
+ throw new Error("parent_changed");
1014
+ fs.fsyncSync(descriptor);
1015
+ } catch (error) {
1016
+ throw new SessionDeleteVerificationError("artifacts", "durability_failed", { cause: toError(error) });
1017
+ } finally {
1018
+ if (descriptor !== undefined) fs.closeSync(descriptor);
1019
+ }
1020
+ }
1021
+ if (!detach.ok) {
1022
+ return {
1023
+ kind: "cleanup_pending",
1024
+ phase: "artifacts",
1025
+ error: new SessionDeleteVerificationError("artifacts", `Exact artifact detach retained: ${detach.code}`),
1026
+ artifactsIdentity,
1027
+ detachedArtifactsPath: detach.detachedPath,
1028
+ artifactsTree,
1029
+ ...(detach.retainedSuccessorPath ? { retainedSuccessorPath: detach.retainedSuccessorPath } : {}),
1030
+ ...(detach.retainedPlaceholderPath ? { retainedPlaceholderPath: detach.retainedPlaceholderPath } : {}),
1031
+ ...(detach.retainedUnknownPath ? { retainedUnknownPath: detach.retainedUnknownPath } : {}),
1032
+
1033
+ transcriptIdentity,
1034
+ };
1035
+ }
956
1036
  const removal = removeDirectoryTreeExact(detach.detachedPath, artifactsTree);
957
1037
  if (!removal.ok) {
958
1038
  const retainedRoot = removal.detachedPath ?? detach.detachedPath;
@@ -971,6 +1051,15 @@ export class FileSessionStorage implements SessionStorage {
971
1051
  artifactsIdentity,
972
1052
  detachedArtifactsPath: retainedRoot,
973
1053
  artifactsTree,
1054
+ ...((removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath)
1055
+ ? { retainedSuccessorPath: removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath }
1056
+ : {}),
1057
+ ...((removal.retainedPlaceholderPath ?? retainedArtifactsPlaceholderPath)
1058
+ ? { retainedPlaceholderPath: removal.retainedPlaceholderPath ?? retainedArtifactsPlaceholderPath }
1059
+ : {}),
1060
+ ...((removal.retainedUnknownPath ?? retainedArtifactsUnknownPath)
1061
+ ? { retainedUnknownPath: removal.retainedUnknownPath ?? retainedArtifactsUnknownPath }
1062
+ : {}),
974
1063
  transcriptIdentity,
975
1064
  };
976
1065
  }
@@ -1010,13 +1099,27 @@ export class FileSessionStorage implements SessionStorage {
1010
1099
  });
1011
1100
  if (!deletion.ok) {
1012
1101
  const error = exactUnlinkFailure(deletion);
1013
- if (error.kind === "identity" || error.kind === "symlink") throw error;
1102
+ const retainedAuthority =
1103
+ deletion.detachedPath ||
1104
+ deletion.retainedSuccessorPath ||
1105
+ deletion.retainedPlaceholderPath ||
1106
+ deletion.retainedUnknownPath;
1107
+ if ((error.kind === "identity" || error.kind === "symlink") && !retainedAuthority) throw error;
1014
1108
  return {
1015
1109
  kind: "cleanup_pending",
1016
1110
  phase: "transcript",
1017
1111
  error,
1018
1112
  transcriptIdentity,
1019
1113
  detachedTranscriptPath: deletion.detachedPath ?? detachedTranscriptPath,
1114
+ ...((deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath)
1115
+ ? { retainedSuccessorPath: deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath }
1116
+ : {}),
1117
+ ...((deletion.retainedPlaceholderPath ?? retainedTranscriptPlaceholderPath)
1118
+ ? { retainedPlaceholderPath: deletion.retainedPlaceholderPath ?? retainedTranscriptPlaceholderPath }
1119
+ : {}),
1120
+ ...((deletion.retainedUnknownPath ?? retainedTranscriptUnknownPath)
1121
+ ? { retainedUnknownPath: deletion.retainedUnknownPath ?? retainedTranscriptUnknownPath }
1122
+ : {}),
1020
1123
  };
1021
1124
  }
1022
1125
  return { kind: "deleted" };
@@ -1058,13 +1161,27 @@ export class FileSessionStorage implements SessionStorage {
1058
1161
  });
1059
1162
  if (!deletion.ok) {
1060
1163
  const error = exactUnlinkFailure(deletion);
1061
- if (error.kind === "identity" || error.kind === "symlink") throw error;
1164
+ const retainedAuthority =
1165
+ deletion.detachedPath ||
1166
+ deletion.retainedSuccessorPath ||
1167
+ deletion.retainedPlaceholderPath ||
1168
+ deletion.retainedUnknownPath;
1169
+ if ((error.kind === "identity" || error.kind === "symlink") && !retainedAuthority) throw error;
1062
1170
  return {
1063
1171
  kind: "cleanup_pending",
1064
1172
  phase: "transcript",
1065
1173
  error,
1066
1174
  transcriptIdentity,
1067
1175
  detachedTranscriptPath: deletion.detachedPath,
1176
+ ...((deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath)
1177
+ ? { retainedSuccessorPath: deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath }
1178
+ : {}),
1179
+ ...((deletion.retainedPlaceholderPath ?? retainedTranscriptPlaceholderPath)
1180
+ ? { retainedPlaceholderPath: deletion.retainedPlaceholderPath ?? retainedTranscriptPlaceholderPath }
1181
+ : {}),
1182
+ ...((deletion.retainedUnknownPath ?? retainedTranscriptUnknownPath)
1183
+ ? { retainedUnknownPath: deletion.retainedUnknownPath ?? retainedTranscriptUnknownPath }
1184
+ : {}),
1068
1185
  };
1069
1186
  }
1070
1187
  return { kind: "deleted" };
@@ -391,7 +391,7 @@ export function formatCredentialSummary(credential: ImportableCredential): strin
391
391
  const identityPart = identity ? ` ${identity}` : "";
392
392
  let expiry = "";
393
393
  if (credential.kind === "oauth" && credential.expiresAt !== undefined) {
394
- expiry = credential.expiresAt < Date.now() ? " [expired]" : "";
394
+ expiry = credential.expiresAt <= Date.now() ? " [expired]" : "";
395
395
  }
396
396
  return `${provider} · ${kind}${identityPart} · token ${credential.redactedToken}${expiry} (from ${credential.source})`;
397
397
  }
@@ -413,7 +413,10 @@ export function formatDiscoverySummary(result: CredentialDiscoveryResult): strin
413
413
  return lines;
414
414
  }
415
415
 
416
- export function isAutoImportOAuthCredential(credential: ImportableCredential): boolean {
416
+ /** Reason shown when automatic OAuth import rejects an unusable discovery record. */
417
+ export const INVALID_AUTO_IMPORT_OAUTH_EXPIRY_REASON = "OAuth credential has no valid future expiry";
418
+
419
+ function hasAutoImportOAuthProviderOrigin(credential: ImportableCredential): boolean {
417
420
  return (
418
421
  AUTO_IMPORT_OAUTH_PROVIDER_ORIGINS[credential.provider]?.has(credential.origin) === true &&
419
422
  credential.kind === "oauth" &&
@@ -421,8 +424,30 @@ export function isAutoImportOAuthCredential(credential: ImportableCredential): b
421
424
  );
422
425
  }
423
426
 
427
+ export function isAutoImportOAuthCredential(credential: ImportableCredential, now: number = Date.now()): boolean {
428
+ return (
429
+ hasAutoImportOAuthProviderOrigin(credential) &&
430
+ typeof credential.expiresAt === "number" &&
431
+ Number.isFinite(credential.expiresAt) &&
432
+ credential.expiresAt > now
433
+ );
434
+ }
435
+
436
+ /** Keep rejected OAuth discoveries visible to operators without offering them for automatic import. */
437
+ export function getAutoImportOAuthCredentialSkips(
438
+ credentials: readonly ImportableCredential[],
439
+ now: number = Date.now(),
440
+ ): SkippedCredential[] {
441
+ return credentials.flatMap(credential =>
442
+ hasAutoImportOAuthProviderOrigin(credential) && !isAutoImportOAuthCredential(credential, now)
443
+ ? [{ origin: credential.origin, source: credential.source, reason: INVALID_AUTO_IMPORT_OAUTH_EXPIRY_REASON }]
444
+ : [],
445
+ );
446
+ }
447
+
424
448
  export function filterAutoImportOAuthCredentials(credentials: readonly ImportableCredential[]): ImportableCredential[] {
425
- return credentials.filter(isAutoImportOAuthCredential);
449
+ const now = Date.now();
450
+ return credentials.filter(credential => isAutoImportOAuthCredential(credential, now));
426
451
  }
427
452
 
428
453
  /**
@@ -49,6 +49,7 @@ interface ProviderPreset {
49
49
  baseUrl: string;
50
50
  apiKeyEnv: string;
51
51
  models: readonly string[];
52
+ modelApi?: Readonly<Record<string, ProviderSetupApi>>;
52
53
  compat?: ProviderCompatConfig;
53
54
  }
54
55
 
@@ -113,6 +114,7 @@ function resolvePresetInput(input: ProviderSetupInput): {
113
114
  apiKey?: string;
114
115
  apiKeyEnv?: string;
115
116
  models: readonly string[];
117
+ modelApi?: Readonly<Record<string, ProviderSetupApi>>;
116
118
  api: ProviderSetupApi;
117
119
  compat?: ProviderCompatConfig;
118
120
  } {
@@ -152,11 +154,32 @@ function resolvePresetInput(input: ProviderSetupInput): {
152
154
  apiKey: input.apiKey,
153
155
  apiKeyEnv: input.apiKeyEnv ?? preset?.apiKeyEnv,
154
156
  models: input.models && input.models.length > 0 ? input.models : (preset?.models ?? []),
157
+ modelApi: preset?.modelApi,
155
158
  api: preset?.api ?? apiForCompatibility(compatibility),
156
159
  compat: preset?.compat,
157
160
  };
158
161
  }
159
162
 
163
+ export function validateModelApi(
164
+ modelApi: Readonly<Record<string, string>> | undefined,
165
+ models: readonly string[],
166
+ presetId: string,
167
+ ): void {
168
+ if (!modelApi) return;
169
+ const modelSet = new Set(models);
170
+ const validApis: readonly string[] = ["openai-responses", "openai-completions", "anthropic-messages"];
171
+ for (const [key, value] of Object.entries(modelApi)) {
172
+ if (!modelSet.has(key)) {
173
+ throw new Error(`Provider preset '${presetId}' declares modelApi for unknown model '${key}'.`);
174
+ }
175
+ if (!validApis.includes(value)) {
176
+ throw new Error(
177
+ `Provider preset '${presetId}' declares invalid modelApi value '${value}' for model '${key}'.`,
178
+ );
179
+ }
180
+ }
181
+ }
182
+
160
183
  function validateSetupInput(input: ProviderSetupInput): {
161
184
  providerId: string;
162
185
  baseUrl: string;
@@ -166,6 +189,7 @@ function validateSetupInput(input: ProviderSetupInput): {
166
189
  compatibility: ProviderCompatibility;
167
190
  api: ProviderSetupApi;
168
191
  compat?: ProviderCompatConfig;
192
+ modelApi?: Readonly<Record<string, ProviderSetupApi>>;
169
193
  preset?: ProviderPreset;
170
194
  } {
171
195
  const resolved = resolvePresetInput(input);
@@ -201,6 +225,7 @@ function validateSetupInput(input: ProviderSetupInput): {
201
225
 
202
226
  const models = parseModelList(resolved.models);
203
227
  if (models.length === 0) throw new Error("At least one model id is required.");
228
+ validateModelApi(resolved.modelApi, models, resolved.preset?.id ?? resolved.providerId);
204
229
 
205
230
  return {
206
231
  providerId,
@@ -210,6 +235,7 @@ function validateSetupInput(input: ProviderSetupInput): {
210
235
  models,
211
236
  compatibility: resolved.compatibility,
212
237
  api: resolved.api,
238
+ modelApi: resolved.modelApi,
213
239
  compat: resolved.compat,
214
240
  preset: resolved.preset,
215
241
  };
@@ -275,7 +301,10 @@ export async function addApiCompatibleProvider(input: ProviderSetupInput): Promi
275
301
  baseUrl: validated.baseUrl,
276
302
  api: validated.api,
277
303
  auth: "apiKey",
278
- models: validated.models.map(id => ({ id })),
304
+ models: validated.models.map(id => {
305
+ const api = validated.modelApi?.[id];
306
+ return api ? { id, api } : { id };
307
+ }),
279
308
  };
280
309
  if (validated.compat) provider.compat = validated.compat;
281
310
  if (validated.credentialSource === "env") {
@@ -1,5 +1,6 @@
1
1
  [
2
2
  {"id":"minimax","aliases":["minimax-code"],"name":"MiniMax Coding Plan","description":"OpenAI-compatible MiniMax Coding Plan endpoint","compatibility":"openai","api":"openai-completions","providerId":"minimax-code","baseUrl":"https://api.minimax.io/v1","apiKeyEnv":"MINIMAX_CODE_API_KEY","models":["minimax-m3"],"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"reasoningContentField":"reasoning_content"}},
3
3
  {"id":"minimax-cn","aliases":["minimax-code-cn","minimaxi"],"name":"MiniMax Coding Plan (China)","description":"OpenAI-compatible MiniMax China endpoint","compatibility":"openai","api":"openai-completions","providerId":"minimax-code-cn","baseUrl":"https://api.minimaxi.com/v1","apiKeyEnv":"MINIMAX_CODE_CN_API_KEY","models":["minimax-m3"],"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"reasoningContentField":"reasoning_content"}},
4
- {"id":"glm","aliases":["zai","z-ai","bigmodel"],"name":"GLM / zAI","description":"OpenAI-compatible GLM endpoint from zAI/BigModel","compatibility":"openai","api":"openai-completions","providerId":"glm-proxy","baseUrl":"https://api.z.ai/api/paas/v4","apiKeyEnv":"ZAI_API_KEY","models":["glm-4.6"],"compat":{"supportsDeveloperRole":false,"supportsReasoningEffort":false,"thinkingFormat":"zai","reasoningContentField":"reasoning_content"}}
4
+ {"id":"glm","aliases":["zai","z-ai","bigmodel"],"name":"GLM / zAI","description":"OpenAI-compatible GLM endpoint from zAI/BigModel","compatibility":"openai","api":"openai-completions","providerId":"glm-proxy","baseUrl":"https://api.z.ai/api/paas/v4","apiKeyEnv":"ZAI_API_KEY","models":["glm-4.6"],"compat":{"supportsDeveloperRole":false,"supportsReasoningEffort":false,"thinkingFormat":"zai","reasoningContentField":"reasoning_content"}},
5
+ {"id":"alibaba-token-plan","aliases":["alibaba","token-plan"],"name":"Alibaba Token Plan","description":"OpenAI-compatible Alibaba Token Plan endpoint","compatibility":"openai","api":"openai-completions","providerId":"alibaba-token-plan","baseUrl":"https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1","apiKeyEnv":"ALIBABA_TOKEN_PLAN_API_KEY","models":["qwen3.8-max-preview","glm-5.2","deepseek-v4-pro"],"modelApi":{"qwen3.8-max-preview":"openai-responses","glm-5.2":"openai-completions","deepseek-v4-pro":"openai-completions"},"compat":{"supportsDeveloperRole":false}}
5
6
  ]
@@ -1515,7 +1515,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
1515
1515
  },
1516
1516
  {
1517
1517
  name: "btw",
1518
- description: "Ask an ephemeral side question using the current session context",
1518
+ description: "Start an ephemeral multi-turn side chat using the current session context",
1519
1519
  inlineHint: "<question>",
1520
1520
  allowArgs: true,
1521
1521
  handleTui: async (command, runtime) => {
package/src/ssh/utils.ts CHANGED
@@ -3,6 +3,20 @@ export function sanitizeHostName(name: string): string {
3
3
  return sanitized.length > 0 ? sanitized : "remote";
4
4
  }
5
5
 
6
+ export function validateSshDestination(username: string | undefined, host: string): string | undefined {
7
+ if (host.startsWith("-") || username?.startsWith("-")) {
8
+ return "username and host must not begin with '-'";
9
+ }
10
+ if (/\0|\r|\n/.test(host) || (username !== undefined && /\0|\r|\n/.test(username))) {
11
+ return "username and host must not contain NUL or line breaks";
12
+ }
13
+ return undefined;
14
+ }
15
+
6
16
  export function buildSshTarget(username: string | undefined, host: string): string {
17
+ const validationError = validateSshDestination(username, host);
18
+ if (validationError) {
19
+ throw new Error(`Invalid SSH destination: ${validationError}`);
20
+ }
7
21
  return username ? `${username}@${host}` : host;
8
22
  }
@@ -14,6 +14,7 @@ import * as path from "node:path";
14
14
  import { logger } from "@gajae-code/utils";
15
15
  import { isProviderEnabled } from "../capability";
16
16
  import { findAllNearestProjectConfigDirs, getConfigDirs } from "../config";
17
+ import type { Settings } from "../config/settings";
17
18
  import { listClaudePluginRoots } from "../discovery/helpers";
18
19
  import { rootContainsGjcManifest } from "../extensibility/gjc-plugins/paths";
19
20
  import { loadBundledAgents, parseAgent } from "./agents";
@@ -57,8 +58,13 @@ async function loadAgentsFromDir(dir: string, source: AgentSource): Promise<Agen
57
58
  * Precedence (highest wins): .gjc project, .gjc user, GJC plugins, then bundled
58
59
  *
59
60
  * @param cwd - Current working directory for project agent discovery
61
+ * @param activeSettings - Settings used to resolve enabled providers.
60
62
  */
61
- export async function discoverAgents(cwd: string, home: string = os.homedir()): Promise<DiscoveryResult> {
63
+ export async function discoverAgents(
64
+ cwd: string,
65
+ home: string = os.homedir(),
66
+ activeSettings?: Settings,
67
+ ): Promise<DiscoveryResult> {
62
68
  const resolvedCwd = path.resolve(cwd);
63
69
  const agentSources = Array.from(new Set(getConfigDirs("", { project: false }).map(entry => entry.source)));
64
70
 
@@ -91,7 +97,7 @@ export async function discoverAgents(cwd: string, home: string = os.homedir()):
91
97
  }
92
98
 
93
99
  // Load agents from GJC marketplace plugins.
94
- const { roots: pluginRoots } = isProviderEnabled("claude-plugins")
100
+ const { roots: pluginRoots } = isProviderEnabled("claude-plugins", activeSettings)
95
101
  ? await listClaudePluginRoots(home, resolvedCwd)
96
102
  : { roots: [] };
97
103
  const nonGjcPluginRoots = [];
@@ -1,6 +1,6 @@
1
1
  import process from "node:process";
2
2
 
3
- import { $env } from "@gajae-code/utils";
3
+ import { $pickenv } from "@gajae-code/utils";
4
4
 
5
5
  interface GjcCommand {
6
6
  cmd: string;
@@ -12,7 +12,7 @@ const DEFAULT_CMD = process.platform === "win32" ? "gjc.cmd" : "gjc";
12
12
  const DEFAULT_SHELL = process.platform === "win32";
13
13
 
14
14
  export function resolveGjcCommand(): GjcCommand {
15
- const envCmd = $env.PI_SUBPROCESS_CMD;
15
+ const envCmd = $pickenv("GJC_SUBPROCESS_CMD", "PI_SUBPROCESS_CMD");
16
16
  if (envCmd?.trim()) {
17
17
  return { cmd: envCmd, args: [], shell: DEFAULT_SHELL };
18
18
  }