@gajae-code/coding-agent 0.12.21 → 0.13.0

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 (222) hide show
  1. package/CHANGELOG.md +67 -2
  2. package/dist/types/cli/args.d.ts +5 -2
  3. package/dist/types/cli/root-flags.d.ts +246 -0
  4. package/dist/types/cli.d.ts +11 -19
  5. package/dist/types/commands/daemon.d.ts +0 -30
  6. package/dist/types/commands/launch.d.ts +11 -19
  7. package/dist/types/commands/quick-lane.d.ts +32 -0
  8. package/dist/types/commands/sdk.d.ts +21 -15
  9. package/dist/types/config/file-lock-gc.d.ts +2 -0
  10. package/dist/types/config/model-equivalence.d.ts +20 -0
  11. package/dist/types/config/model-profile-activation.d.ts +62 -17
  12. package/dist/types/config/model-profiles.d.ts +10 -0
  13. package/dist/types/config/model-registry.d.ts +68 -1
  14. package/dist/types/config/model-resolver.d.ts +15 -4
  15. package/dist/types/config/models-config-schema.d.ts +7 -2
  16. package/dist/types/config/provider-selection-policy.d.ts +62 -0
  17. package/dist/types/config/settings-schema.d.ts +36 -78
  18. package/dist/types/config/settings.d.ts +2 -0
  19. package/dist/types/edit/renderer.d.ts +5 -2
  20. package/dist/types/edit/streaming.d.ts +13 -0
  21. package/dist/types/extensibility/custom-tools/types.d.ts +2 -0
  22. package/dist/types/extensibility/extensions/compact-handler.d.ts +2 -1
  23. package/dist/types/extensibility/extensions/types.d.ts +3 -0
  24. package/dist/types/gjc-runtime/gc-runtime.d.ts +19 -5
  25. package/dist/types/internal-urls/local-protocol.d.ts +1 -0
  26. package/dist/types/modes/components/model-selector.d.ts +0 -2
  27. package/dist/types/modes/components/provider-order-context.d.ts +45 -0
  28. package/dist/types/modes/components/provider-order-editor.d.ts +24 -0
  29. package/dist/types/modes/components/settings-defs.d.ts +5 -1
  30. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  31. package/dist/types/quick-lane/classify.d.ts +40 -0
  32. package/dist/types/runtime/stt-settings-setup.d.ts +18 -0
  33. package/dist/types/sdk/broker/broker.d.ts +56 -2
  34. package/dist/types/sdk/broker/operation-receipt-ledger.d.ts +25 -0
  35. package/dist/types/sdk/broker/session-index.d.ts +83 -2
  36. package/dist/types/sdk/broker/state-version.d.ts +1 -1
  37. package/dist/types/sdk/bus/notification-service.d.ts +6 -0
  38. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +8 -1
  39. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -1
  40. package/dist/types/sdk/bus/telegram-daemon.d.ts +26 -0
  41. package/dist/types/sdk/cli/rows.d.ts +98 -0
  42. package/dist/types/sdk/cli/session-cli.d.ts +35 -3
  43. package/dist/types/sdk/elevation/allowlist.d.ts +20 -0
  44. package/dist/types/sdk/elevation/audit.d.ts +93 -0
  45. package/dist/types/sdk/elevation/capability.d.ts +3 -0
  46. package/dist/types/sdk/elevation/digest.d.ts +72 -0
  47. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +85 -0
  48. package/dist/types/sdk/elevation/grant-ledger.d.ts +163 -0
  49. package/dist/types/sdk/elevation/owner.d.ts +48 -0
  50. package/dist/types/sdk/guides/cache.d.ts +98 -0
  51. package/dist/types/sdk/guides/catalog.d.ts +156 -0
  52. package/dist/types/sdk/guides/cli.d.ts +39 -0
  53. package/dist/types/sdk/guides/manifest.d.ts +66 -0
  54. package/dist/types/sdk/guides/verify.d.ts +66 -0
  55. package/dist/types/sdk/host/control/dispatch.d.ts +8 -0
  56. package/dist/types/sdk/host/control/index.d.ts +1 -1
  57. package/dist/types/sdk/host/control/operations.d.ts +3 -2
  58. package/dist/types/sdk/host/query/cursor.d.ts +10 -0
  59. package/dist/types/sdk/host/query/handlers.d.ts +21 -0
  60. package/dist/types/sdk/host/session-runtime.d.ts +9 -0
  61. package/dist/types/sdk/mcp/server.d.ts +2 -2
  62. package/dist/types/sdk/prompt-status.d.ts +11 -0
  63. package/dist/types/sdk/session.d.ts +4 -0
  64. package/dist/types/sdk/transport/index.d.ts +1 -0
  65. package/dist/types/sdk/transport/relay.d.ts +1 -0
  66. package/dist/types/sdk/transport/serve-cli.d.ts +20 -1
  67. package/dist/types/session/agent-session.d.ts +32 -18
  68. package/dist/types/session/blob-store.d.ts +13 -7
  69. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  70. package/dist/types/session/internal/managed-session-storage.d.ts +25 -1
  71. package/dist/types/session/internal/session-memory-sidecar.d.ts +5 -5
  72. package/dist/types/session/session-manager.d.ts +139 -10
  73. package/dist/types/session/session-storage.d.ts +48 -0
  74. package/dist/types/setup/provider-onboarding.d.ts +7 -1
  75. package/dist/types/slash-commands/builtin-registry.d.ts +14 -0
  76. package/dist/types/stt/setup.d.ts +2 -0
  77. package/dist/types/task/executor.d.ts +10 -2
  78. package/dist/types/tools/browser/runtime-diagnostics.d.ts +65 -0
  79. package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
  80. package/dist/types/tools/browser/tab-supervisor.d.ts +2 -0
  81. package/dist/types/tools/browser/tab-worker.d.ts +2 -0
  82. package/dist/types/tools/browser.d.ts +2 -0
  83. package/dist/types/tools/image-gen.d.ts +14 -18
  84. package/dist/types/tools/index.d.ts +2 -0
  85. package/dist/types/tools/resource-gc.d.ts +2 -0
  86. package/dist/types/utils/pasted-image-path.d.ts +6 -0
  87. package/package.json +9 -9
  88. package/scripts/generate-sdk-operation-inventory.ts +8 -0
  89. package/scripts/run-test-manifest.ts +61 -21
  90. package/scripts/verify-gjc-sdk-canonicalization.ts +9 -4
  91. package/src/cli/args.ts +62 -9
  92. package/src/cli/completion-cli.ts +1 -0
  93. package/src/cli/fast-help.ts +1 -0
  94. package/src/cli/root-flags.ts +100 -0
  95. package/src/cli/setup-cli.ts +6 -2
  96. package/src/cli.ts +42 -65
  97. package/src/commands/acp.ts +1 -1
  98. package/src/commands/daemon.ts +2 -31
  99. package/src/commands/gc.ts +1 -1
  100. package/src/commands/launch.ts +8 -129
  101. package/src/commands/quick-lane.ts +51 -0
  102. package/src/commands/sdk.ts +200 -26
  103. package/src/commands/setup.ts +4 -2
  104. package/src/config/file-lock-gc.ts +4 -1
  105. package/src/config/model-equivalence.ts +76 -7
  106. package/src/config/model-profile-activation.ts +1115 -265
  107. package/src/config/model-profiles.ts +134 -2
  108. package/src/config/model-registry.ts +352 -50
  109. package/src/config/model-resolver.ts +289 -38
  110. package/src/config/models-config-schema.ts +11 -4
  111. package/src/config/provider-selection-policy.ts +146 -0
  112. package/src/config/settings-schema.ts +50 -77
  113. package/src/config/settings.ts +40 -0
  114. package/src/coordinator-mcp/server.ts +32 -2
  115. package/src/edit/renderer.ts +175 -223
  116. package/src/edit/streaming.ts +108 -1
  117. package/src/export/html/index.ts +57 -7
  118. package/src/extensibility/custom-tools/types.ts +2 -0
  119. package/src/extensibility/extensions/compact-handler.ts +3 -2
  120. package/src/extensibility/extensions/runner.ts +6 -0
  121. package/src/extensibility/extensions/types.ts +3 -0
  122. package/src/gjc-runtime/gc-runtime.ts +379 -20
  123. package/src/gjc-runtime/launch-worktree.ts +5 -1
  124. package/src/gjc-runtime/team-runtime.ts +59 -2
  125. package/src/gjc-runtime/tmux-owner-isolation.ts +19 -0
  126. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +16 -1
  127. package/src/internal-urls/docs-index.generated.ts +17 -13
  128. package/src/internal-urls/local-protocol.ts +1 -0
  129. package/src/main.ts +40 -3
  130. package/src/memories/index.ts +4 -2
  131. package/src/modes/acp/acp-agent.ts +235 -91
  132. package/src/modes/components/model-selector.ts +206 -42
  133. package/src/modes/components/provider-order-context.ts +197 -0
  134. package/src/modes/components/provider-order-editor.ts +441 -0
  135. package/src/modes/components/settings-defs.ts +21 -1
  136. package/src/modes/components/settings-selector.ts +45 -2
  137. package/src/modes/components/tool-execution.ts +47 -15
  138. package/src/modes/controllers/command-controller.ts +6 -3
  139. package/src/modes/controllers/extension-ui-controller.ts +2 -0
  140. package/src/modes/controllers/input-controller.ts +73 -2
  141. package/src/modes/controllers/selector-controller.ts +260 -153
  142. package/src/modes/prompt-suggestion-controller.ts +1 -1
  143. package/src/modes/runtime-init.ts +1 -0
  144. package/src/modes/utils/ui-helpers.ts +18 -0
  145. package/src/prompts/tools/browser.md +1 -0
  146. package/src/quick-lane/classify.ts +257 -0
  147. package/src/rlm/index.ts +1 -1
  148. package/src/runtime/stt-settings-setup.ts +57 -0
  149. package/src/sdk/acp/adapter.ts +2 -1
  150. package/src/sdk/broker/broker.ts +780 -12
  151. package/src/sdk/broker/lifecycle.ts +70 -29
  152. package/src/sdk/broker/operation-receipt-ledger.ts +144 -0
  153. package/src/sdk/broker/session-index.ts +463 -48
  154. package/src/sdk/broker/state-version.ts +13 -9
  155. package/src/sdk/broker/transport.ts +14 -1
  156. package/src/sdk/bus/index.ts +34 -13
  157. package/src/sdk/bus/kind-aware-reconciliation.ts +21 -2
  158. package/src/sdk/bus/notification-service.ts +47 -1
  159. package/src/sdk/bus/prompt-reconciliation.ts +22 -3
  160. package/src/sdk/bus/telegram-daemon-contract.ts +4 -1
  161. package/src/sdk/bus/telegram-daemon.ts +54 -3
  162. package/src/sdk/cli/rows.ts +247 -0
  163. package/src/sdk/cli/session-cli.ts +930 -79
  164. package/src/sdk/elevation/allowlist.ts +54 -0
  165. package/src/sdk/elevation/audit.ts +208 -0
  166. package/src/sdk/elevation/capability.ts +41 -0
  167. package/src/sdk/elevation/digest.ts +129 -0
  168. package/src/sdk/elevation/dispatch-receipt.ts +184 -0
  169. package/src/sdk/elevation/grant-ledger.ts +1142 -0
  170. package/src/sdk/elevation/owner.ts +109 -0
  171. package/src/sdk/guides/cache.ts +363 -0
  172. package/src/sdk/guides/catalog.ts +554 -0
  173. package/src/sdk/guides/cli.ts +235 -0
  174. package/src/sdk/guides/manifest.ts +152 -0
  175. package/src/sdk/guides/verify.ts +178 -0
  176. package/src/sdk/host/control/dispatch.ts +49 -1
  177. package/src/sdk/host/control/index.ts +1 -0
  178. package/src/sdk/host/control/operations.ts +8 -1
  179. package/src/sdk/host/query/cursor.ts +47 -3
  180. package/src/sdk/host/query/handlers.ts +155 -2
  181. package/src/sdk/host/sdk-surface-parity.test.ts +1 -0
  182. package/src/sdk/host/session-runtime.test.ts +391 -1
  183. package/src/sdk/host/session-runtime.ts +308 -73
  184. package/src/sdk/host/surface-policy.ts +3 -0
  185. package/src/sdk/host/websocket-transport.ts +7 -1
  186. package/src/sdk/mcp/server.ts +88 -15
  187. package/src/sdk/prompt-status.ts +8 -0
  188. package/src/sdk/protocol/operation-inventory.generated.json +62 -0
  189. package/src/sdk/protocol/operation-registry.ts +3 -2
  190. package/src/sdk/session.ts +87 -39
  191. package/src/sdk/transport/index.ts +1 -0
  192. package/src/sdk/transport/relay.ts +3 -0
  193. package/src/sdk/transport/serve-cli.ts +111 -24
  194. package/src/secrets/obfuscator.ts +92 -12
  195. package/src/session/agent-session.ts +322 -93
  196. package/src/session/blob-store.ts +60 -8
  197. package/src/session/fallback-chain-controller.ts +35 -0
  198. package/src/session/internal/managed-session-scope.ts +25 -3
  199. package/src/session/internal/managed-session-storage.ts +574 -73
  200. package/src/session/internal/session-memory-sidecar.ts +19 -29
  201. package/src/session/session-manager.ts +2166 -743
  202. package/src/session/session-storage.ts +715 -48
  203. package/src/setup/provider-onboarding.ts +19 -3
  204. package/src/setup/provider-presets.json +3 -1
  205. package/src/slash-commands/builtin-registry.ts +85 -3
  206. package/src/stt/downloader.ts +2 -3
  207. package/src/stt/setup.ts +29 -4
  208. package/src/task/executor.ts +22 -18
  209. package/src/task/index.ts +24 -1
  210. package/src/tools/archive-reader.ts +11 -2
  211. package/src/tools/browser/runtime-diagnostics.ts +249 -0
  212. package/src/tools/browser/tab-protocol.ts +4 -0
  213. package/src/tools/browser/tab-supervisor.ts +5 -0
  214. package/src/tools/browser/tab-worker.ts +36 -1
  215. package/src/tools/browser.ts +7 -0
  216. package/src/tools/image-gen.ts +110 -166
  217. package/src/tools/index.ts +2 -0
  218. package/src/tools/read.ts +1 -1
  219. package/src/tools/resource-gc.ts +6 -1
  220. package/src/tools/tool-catalog.generated.ts +5 -1
  221. package/src/utils/pasted-image-path.ts +93 -0
  222. package/src/web/search/index.ts +3 -3
@@ -24,6 +24,8 @@ import {
24
24
  writeSessionLifecycleReady,
25
25
  } from "../sdk/broker/lifecycle";
26
26
  import { processIncarnation } from "../sdk/broker/process-incarnation";
27
+ import { runSdkSessionCli } from "../sdk/cli";
28
+ import { runSdkGuidesCli } from "../sdk/guides/cli";
27
29
  import { type CreateLifecycleAgentSessionResult, createLifecycleAgentSession } from "../sdk/lifecycle-session";
28
30
  import { listManagedSessionCandidates, resolveManagedSessionScope } from "../sdk/session-directory";
29
31
  import {
@@ -141,25 +143,31 @@ export const SESSION_HOST_FIRST_ATTACH_GRACE_MS = 60 * 60_000;
141
143
  const SESSION_HOST_ATTACHMENT_POLL_MS = 30_000;
142
144
 
143
145
  /**
144
- * Resolves once this host is provably abandoned: either it has been detached
145
- * from every client for the full idle grace, or nobody has come for it at all
146
- * for the full first-attach grace.
146
+ * Resolves once the host is provably abandoned: either it has been detached
147
+ * from every client for a full idle grace, or nobody has come for it at all
148
+ * for a full first-attach grace.
147
149
  *
148
150
  * `readAttachedClients` reports the host's own live client/socket subscription
149
- * count; `undefined` means the SDK endpoint publishes no such evidence yet and
150
- * is deliberately *not* treated as detachment, so a host can never be reaped on
151
- * missing evidence — only on observed absence of clients.
151
+ * count; `undefined` means the SDK endpoint publishes no such evidence — before
152
+ * startup, after teardown, or when every reader itself fails. That ambiguity is
153
+ * never instant detachment: it cannot reap on the poll that first sees it, it
154
+ * can only open a window. Which window depends on what was already observed. A
155
+ * host never seen attached accrues against the first-attach bound. A host that
156
+ * was seen attached and has since lost its evidence is in the *more* suspicious
157
+ * state — a runtime that retracted its registration during teardown looks
158
+ * exactly like this — so it accrues against the detached idle bound, alongside
159
+ * an observed count of zero. Every reachable state therefore carries a finite
160
+ * bound.
152
161
  *
153
162
  * `readWorkInFlight` reports whether the host is running agent work right now.
154
- * Work is positive proof that a client did come for this host: a prompt can
155
- * only arrive over the endpoint a client dialed. So for a host that has never
156
- * been *observed* attached — a client that connected, prompted and dropped
157
- * between two 30s polls, or a client count that is momentarily unreadable — the
158
- * first-attach window is measured from the last moment work was in flight
159
- * rather than from process start. That still bounds a host whose SDK runtime
160
- * never came up: with no transport it can never receive a prompt, so it never
161
- * reports work and its window keeps running from start. And any real turn ends,
162
- * after which the window resumes; live work defers the bound, never removes it.
163
+ * Work is positive proof a client did come for this host: a prompt can only
164
+ * arrive over an endpoint a client dialed. Live work therefore restarts both
165
+ * windows, which is what keeps a mid-prompt host alive whether its count reads
166
+ * zero or stops being readable at all. That still bounds a host whose SDK
167
+ * runtime never came up: with no transport it can never receive a prompt, so it
168
+ * never reports work and its window keeps running from process start. And any
169
+ * real turn ends, after which the window resumes; live work defers a bound, it
170
+ * never removes it.
163
171
  */
164
172
  export async function watchSessionHostClientAttachment(deps: {
165
173
  readAttachedClients: () => number | undefined;
@@ -182,16 +190,18 @@ export async function watchSessionHostClientAttachment(deps: {
182
190
  let unattendedSince = now();
183
191
  for (;;) {
184
192
  const attached = deps.readAttachedClients();
185
- if (readWorkInFlight()) unattendedSince = now();
186
- if (attached === undefined) {
187
- // No endpoint evidence at all is ambiguity, not abandonment; it still
188
- // accrues against the first-attach bound so a host whose SDK runtime
189
- // never came up cannot outlive that bound either.
190
- if (!everAttached && now() - unattendedSince >= firstAttachGraceMs) return;
191
- } else if (attached > 0) {
193
+ if (readWorkInFlight()) {
194
+ unattendedSince = now();
195
+ detachedSince = null;
196
+ }
197
+ if (attached !== undefined && attached > 0) {
192
198
  everAttached = true;
193
199
  detachedSince = null;
194
200
  } else if (everAttached) {
201
+ // An observed zero and no readable evidence at all are the same
202
+ // absence once a client has been seen: a runtime that retracted its
203
+ // registration stops publishing a count instead of reporting zero.
204
+ // Both accrue against the idle bound rather than running forever.
195
205
  detachedSince ??= now();
196
206
  if (now() - detachedSince >= idleGraceMs) return;
197
207
  } else if (now() - unattendedSince >= firstAttachGraceMs) {
@@ -697,6 +707,18 @@ export function parseSdkInternalArgv(argv: readonly string[]): SdkInternalArgv {
697
707
  throw new CliParseError("Invalid internal SDK invocation.");
698
708
  }
699
709
 
710
+ function parsePositiveTimeout(raw: string | undefined, flagName: string): number | undefined {
711
+ if (raw === undefined) return undefined;
712
+ if (!/^[0-9]+$/.test(raw)) {
713
+ throw new CliParseError(`Expected ${flagName} to be a positive safe integer, got "${raw}"`);
714
+ }
715
+ const value = Number(raw);
716
+ if (!Number.isSafeInteger(value) || value <= 0) {
717
+ throw new CliParseError(`Expected ${flagName} to be a positive safe integer, got "${raw}"`);
718
+ }
719
+ return value;
720
+ }
721
+
700
722
  class SdkServeHelp extends Command {
701
723
  static description = "gjc sdk serve --stdio | --socket <path> [--session <id>] [--pending-ceiling <bytes>]";
702
724
  static flags = {
@@ -708,16 +730,168 @@ class SdkServeHelp extends Command {
708
730
  async run(): Promise<void> {}
709
731
  }
710
732
 
733
+ class SdkSessionHelp extends Command {
734
+ static description =
735
+ "Manage SDK sessions: `gjc sdk session list|inspect|send|status|tail|elevate`, or the explicit raw hatch `gjc sdk session raw control|query|global`. The session CLI is broker-bound, credential-free by default, and elevation approval requires an attended operator.";
736
+ static args = {
737
+ verb: Args.string({
738
+ description: "Session verb",
739
+ required: false,
740
+ options: ["list", "inspect", "send", "status", "tail", "elevate", "raw"],
741
+ }),
742
+ target: Args.string({
743
+ description: "Session id (or the raw kind control|query|global for `raw`)",
744
+ required: false,
745
+ }),
746
+ opRef: Args.string({ description: "Operation reference to report on (status only)", required: false }),
747
+ };
748
+ static flags = {
749
+ "agent-dir": Flags.string({ description: "SDK broker state directory" }),
750
+ json: Flags.boolean({ description: "Emit JSON output (default for SDK session commands)" }),
751
+ repo: Flags.string({
752
+ description: "Workspace directory for saved-session resolution (default: current directory)",
753
+ }),
754
+ op: Flags.string({ description: "Raw control or global operation" }),
755
+ query: Flags.string({ description: "Raw query name" }),
756
+ kind: Flags.string({ description: "Elevation operation kind (control|global)", options: ["control", "global"] }),
757
+ "json-input": Flags.string({ description: "SDK request JSON object" }),
758
+ "json-input-file": Flags.string({ description: "Read SDK request JSON from a 0600 file" }),
759
+ "json-input-stdin": Flags.boolean({ description: "Read SDK request JSON from standard input" }),
760
+ "idempotency-key": Flags.string({ description: "Caller idempotency key required for SDK lifecycle globals" }),
761
+ "elevation-request-id": Flags.string({ description: "Approved elevation grant id for allowlisted raw controls" }),
762
+ confirm: Flags.boolean({ description: "Confirm a destructive SDK control operation" }),
763
+ cursor: Flags.string({
764
+ description:
765
+ "Raw query continuation cursor, or a saved checkpoint token to resume tail from (re-minted per connection)",
766
+ }),
767
+ "show-endpoint-credential": Flags.boolean({ description: "Allow session.get_endpoint secret output" }),
768
+ yes: Flags.boolean({ description: "Confirm endpoint credential output on a TTY" }),
769
+ text: Flags.string({ description: "Prompt text for send (alternative to --json-input)" }),
770
+ "op-ref": Flags.string({ description: "Operation reference for send (defaults to a generated ULID)" }),
771
+ wait: Flags.boolean({
772
+ description: "send --wait: poll turn.prompt_status until terminal or the wait window elapses (never cancels)",
773
+ }),
774
+ "timeout-ms": Flags.string({ description: "Wait window for send --wait, status, and live tail follow" }),
775
+ strict: Flags.boolean({ description: "tail --strict: fail closed (exit 1 retention_gap) on retention gaps" }),
776
+ "until-idle": Flags.boolean({
777
+ description: "tail --until-idle: exit once the observed event stream reaches a terminal turn state",
778
+ }),
779
+ "all-events": Flags.boolean({ description: "tail --all-events: include every event-ring kind" }),
780
+ };
781
+ async run(): Promise<void> {}
782
+ }
783
+
784
+ class SdkSessionCommand extends Command {
785
+ static description = SdkSessionHelp.description;
786
+ static args = SdkSessionHelp.args;
787
+ static flags = SdkSessionHelp.flags;
788
+ async run(): Promise<void> {
789
+ const { args, flags } = await this.parse(SdkSessionCommand);
790
+ const verb = args.verb;
791
+ const target = args.target;
792
+ const flagRec = flags as Record<string, unknown>;
793
+ const timeoutMs = parsePositiveTimeout(flagRec["timeout-ms"] as string | undefined, "--timeout-ms");
794
+ await runSdkSessionCli({
795
+ action: verb,
796
+ ...(verb === "raw"
797
+ ? {
798
+ rawAction: target,
799
+ sessionId: target === "control" || target === "query" ? args.opRef : undefined,
800
+ }
801
+ : { sessionId: verb === "list" ? undefined : target }),
802
+ opRef: verb === "status" ? args.opRef : (flagRec["op-ref"] as string | undefined),
803
+ operation: flagRec.op as string | undefined,
804
+ query: flagRec.query as string | undefined,
805
+ text: flagRec.text as string | undefined,
806
+ jsonInput: flagRec["json-input"] as string | undefined,
807
+ jsonInputFile: flagRec["json-input-file"] as string | undefined,
808
+ jsonInputStdin: Boolean(flagRec["json-input-stdin"]),
809
+ confirm: Boolean(flagRec.confirm),
810
+ idempotencyKey: flagRec["idempotency-key"] as string | undefined,
811
+ elevationRequestId: flagRec["elevation-request-id"] as string | undefined,
812
+ operationKind: flagRec.kind as "control" | "global" | undefined,
813
+ cursor: flagRec.cursor as string | undefined,
814
+ showEndpointCredential: Boolean(flagRec["show-endpoint-credential"]),
815
+ yes: Boolean(flagRec.yes),
816
+ wait: Boolean(flagRec.wait),
817
+ timeoutMs,
818
+ strict: Boolean(flagRec.strict),
819
+ untilIdle: Boolean(flagRec["until-idle"]),
820
+ allEvents: Boolean(flagRec["all-events"]),
821
+ agentDir: flagRec["agent-dir"] as string | undefined,
822
+ repo: flagRec.repo as string | undefined,
823
+ });
824
+ }
825
+ }
826
+
827
+ class SdkGuidesHelp extends Command {
828
+ static description = "Manage verified advisory SDK guides: refresh, list, show, status, or trust.";
829
+ static args = {
830
+ action: Args.string({ required: false, options: ["refresh", "list", "show", "status", "trust"] }),
831
+ guideId: Args.string({ required: false, description: "Guide id for show" }),
832
+ };
833
+ static flags = {
834
+ "agent-dir": Flags.string({ description: "SDK broker state directory" }),
835
+ url: Flags.string({ description: "HTTPS allowlisted manifest URL for refresh" }),
836
+ "timeout-ms": Flags.string({ description: "Bounded refresh timeout in milliseconds" }),
837
+ };
838
+ async run(): Promise<void> {}
839
+ }
840
+
841
+ class SdkGuidesCommand extends Command {
842
+ static description = SdkGuidesHelp.description;
843
+ static args = SdkGuidesHelp.args;
844
+ static flags = SdkGuidesHelp.flags;
845
+ async run(): Promise<void> {
846
+ const { args, flags } = await this.parse(SdkGuidesCommand);
847
+ const flagRec = flags as Record<string, unknown>;
848
+ await runSdkGuidesCli({
849
+ action: args.action,
850
+ guideId: args.guideId,
851
+ url: flagRec.url as string | undefined,
852
+ agentDir: flagRec["agent-dir"] as string | undefined,
853
+ timeoutMs: parsePositiveTimeout(flagRec["timeout-ms"] as string | undefined, "--timeout-ms"),
854
+ });
855
+ }
856
+ }
857
+
711
858
  export default class Sdk extends Command {
712
- static description = "gjc sdk serve --stdio | --socket <path> [--session <id>] [--pending-ceiling <bytes>]";
859
+ static description =
860
+ "gjc sdk serve --stdio | --socket <path> [--session <id>]; gjc sdk session list|inspect|send|status|tail; gjc sdk guides refresh|list|show|status|trust";
713
861
  static hidden = false;
714
862
  static delegateHelp = true;
715
- static args = { action: Args.string({ required: false, options: ["serve"] }) };
863
+ static args = { action: Args.string({ required: false, options: ["serve", "session", "guides"] }) };
716
864
  static flags = SdkServeHelp.flags;
717
865
  async run(): Promise<void> {
718
866
  const action = this.argv[0];
719
867
  if (this.argv.includes("--help") || this.argv.includes("-h")) {
720
- renderCommandHelp("gjc", action === "serve" ? "sdk serve" : "sdk", action === "serve" ? SdkServeHelp : Sdk);
868
+ const helpAction =
869
+ action === "serve"
870
+ ? "sdk serve"
871
+ : action === "session"
872
+ ? "sdk session"
873
+ : action === "guides"
874
+ ? "sdk guides"
875
+ : "sdk";
876
+ const helpCommand =
877
+ action === "serve"
878
+ ? SdkServeHelp
879
+ : action === "session"
880
+ ? SdkSessionCommand
881
+ : action === "guides"
882
+ ? SdkGuidesCommand
883
+ : Sdk;
884
+ renderCommandHelp("gjc", helpAction, helpCommand);
885
+ return;
886
+ }
887
+ if (action === "session") {
888
+ const instance = new SdkSessionCommand(this.argv.slice(1), this.config);
889
+ await instance.run();
890
+ return;
891
+ }
892
+ if (action === "guides") {
893
+ const instance = new SdkGuidesCommand(this.argv.slice(1), this.config);
894
+ await instance.run();
721
895
  return;
722
896
  }
723
897
  if (action === "serve") {
@@ -725,7 +899,7 @@ export default class Sdk extends Command {
725
899
  return;
726
900
  }
727
901
  if (action !== "broker-internal" && action !== "session-host-internal")
728
- throw new CliParseError("Expected action to be serve.");
902
+ throw new CliParseError("Expected action to be serve, session, or guides.");
729
903
  const internal = parseSdkInternalArgv(this.argv);
730
904
  if (internal.action === "session-host-internal") {
731
905
  await runSessionHost();
@@ -50,10 +50,12 @@ export default class Setup extends Command {
50
50
  "gjc-command": Flags.string({ description: "Command used to start `gjc mcp-serve coordinator`" }),
51
51
  target: Flags.string({ description: "Hermes config file target for config-only install" }),
52
52
  "profile-dir": Flags.string({ description: "Hermes profile directory for full setup install" }),
53
- preset: Flags.string({ description: "Provider preset: minimax, minimax-cn, or glm" }),
53
+ preset: Flags.string({ description: "Provider preset id (run setup provider --help to list available presets)" }),
54
54
  compat: Flags.string({ description: "Provider compatibility: openai or anthropic" }),
55
55
  provider: Flags.string({ description: "Provider id to add to models.yml" }),
56
- "base-url": Flags.string({ description: "Provider API base URL" }),
56
+ "base-url": Flags.string({
57
+ description: "Provider API base URL (required for proxy presets: litellm, openai-compatible-proxy)",
58
+ }),
57
59
  "api-key-env": Flags.string({ description: "Read provider API key from this environment variable" }),
58
60
  model: Flags.string({ description: "Model id to add (repeat or comma-separate)", multiple: true }),
59
61
  "models-path": Flags.string({ description: "Override models config path" }),
@@ -35,6 +35,8 @@ interface WalkState {
35
35
  export interface FileLocksGcCollectOptions {
36
36
  /** Override per-root entry budget (tests). Defaults to {@link FILE_LOCK_GC_MAX_WALK_ENTRIES}. */
37
37
  maxWalkEntries?: number;
38
+ /** Override global lock roots for isolated collection tests. */
39
+ roots?: readonly string[];
38
40
  }
39
41
 
40
42
  // Global, env-aware GJC lock roots. Per the approved scope this covers the
@@ -169,7 +171,8 @@ export async function collectFileLocksForGc(
169
171
  const warnings: GcWarning[] = [];
170
172
  const lockDirs = new Set<string>();
171
173
 
172
- for (const root of knownFileLockRoots(ctx)) {
174
+ const roots = options.roots ?? knownFileLockRoots(ctx);
175
+ for (const root of Array.from(new Set(roots.map(root => path.resolve(root))))) {
173
176
  // Fresh budget per root so a huge agent dir cannot starve config/spool.
174
177
  const state: WalkState = { entries: 0, truncated: false };
175
178
  await walkForLockDirs(root, 0, state, lockDirs, errors, maxWalkEntries);
@@ -24,6 +24,15 @@ export interface CanonicalModelIndex {
24
24
  records: CanonicalModelRecord[];
25
25
  byId: Map<string, CanonicalModelRecord>;
26
26
  bySelector: Map<string, string>;
27
+ /**
28
+ * Final-slash-segment lookup aliases: normalized alias key -> normalized
29
+ * variant selectors ("provider/id") of every variant whose model id ends in
30
+ * that segment. Variant-level: an alias never widens to sibling variants in
31
+ * the same canonical record that end in a different segment. Multi-target
32
+ * when distinct variants collide on the same segment. Built without
33
+ * rewriting the canonical or runtime identity of any variant.
34
+ */
35
+ aliases: Map<string, string[]>;
27
36
  }
28
37
 
29
38
  interface CanonicalReferenceData {
@@ -104,6 +113,22 @@ export function formatCanonicalVariantSelector(model: Model<Api>): string {
104
113
  return `${model.provider}/${model.id}`;
105
114
  }
106
115
 
116
+ /**
117
+ * Normalize a model id into its final-slash-segment alias key (trimmed and
118
+ * lowercased). A bare id without a slash yields the id itself; a multi-level
119
+ * id yields its last path segment. Returns undefined for empty ids.
120
+ */
121
+ export function getFinalSlashSegmentAliasKey(modelId: string): string | undefined {
122
+ const normalized = modelId.trim();
123
+ if (!normalized) {
124
+ return undefined;
125
+ }
126
+ const slashIndex = normalized.lastIndexOf("/");
127
+ const segment = slashIndex >= 0 ? normalized.slice(slashIndex + 1) : normalized;
128
+ const key = segment.trim().toLowerCase();
129
+ return key || undefined;
130
+ }
131
+
107
132
  /**
108
133
  * Compare equivalent provider variants using the caller's ranking policy.
109
134
  *
@@ -112,11 +137,16 @@ export function formatCanonicalVariantSelector(model: Model<Api>): string {
112
137
  * source, input plus cache-read cost, and original catalog order. Callers that
113
138
  * do not have a canonical index can omit those axes while retaining a stable,
114
139
  * total comparison.
140
+ *
141
+ * `providerRankFirst` swaps the first two axes so configured provider rank wins
142
+ * over vision support; canonical/alias resolution uses this order while every
143
+ * existing caller keeps the legacy vision-first order by default.
115
144
  */
116
145
  export function compareEquivalentModelVariants(
117
146
  left: Model<Api>,
118
147
  right: Model<Api>,
119
148
  options: {
149
+ providerRankFirst?: boolean;
120
150
  providerRank?: ReadonlyMap<string, number>;
121
151
  canonicalId?: string;
122
152
  leftSourceRank?: number;
@@ -128,17 +158,32 @@ export function compareEquivalentModelVariants(
128
158
  ): number {
129
159
  const leftVisionRank = Number(!left.input.includes("image"));
130
160
  const rightVisionRank = Number(!right.input.includes("image"));
131
- if (leftVisionRank !== rightVisionRank) return leftVisionRank - rightVisionRank;
132
161
 
133
- if (options.providerRank) {
162
+ const compareProviderRank = (): number => {
163
+ if (!options.providerRank) {
164
+ return 0;
165
+ }
134
166
  const leftProviderRank = options.providerRank.get(left.provider.toLowerCase()) ?? Number.MAX_SAFE_INTEGER;
135
167
  const rightProviderRank = options.providerRank.get(right.provider.toLowerCase()) ?? Number.MAX_SAFE_INTEGER;
136
- if (leftProviderRank !== rightProviderRank) return leftProviderRank - rightProviderRank;
168
+ return leftProviderRank - rightProviderRank;
169
+ };
170
+
171
+ if (options.providerRankFirst) {
172
+ const providerRankDiff = compareProviderRank();
173
+ if (providerRankDiff !== 0) return providerRankDiff;
174
+ if (leftVisionRank !== rightVisionRank) return leftVisionRank - rightVisionRank;
175
+ } else {
176
+ if (leftVisionRank !== rightVisionRank) return leftVisionRank - rightVisionRank;
177
+ const providerRankDiff = compareProviderRank();
178
+ if (providerRankDiff !== 0) return providerRankDiff;
137
179
  }
138
180
 
139
181
  if (options.canonicalId) {
140
- const leftExactRank = Number(left.id !== options.canonicalId);
141
- const rightExactRank = Number(right.id !== options.canonicalId);
182
+ // Compare case-insensitively against the normalized exactness key so a
183
+ // mixed-case model id still matches the alias/canonical lookup key.
184
+ const normalizedExactnessKey = options.canonicalId.trim().toLowerCase();
185
+ const leftExactRank = Number(left.id.trim().toLowerCase() !== normalizedExactnessKey);
186
+ const rightExactRank = Number(right.id.trim().toLowerCase() !== normalizedExactnessKey);
142
187
  if (leftExactRank !== rightExactRank) return leftExactRank - rightExactRank;
143
188
  }
144
189
 
@@ -835,6 +880,8 @@ export function buildCanonicalModelIndex(
835
880
  const compiledEquivalence = compileEquivalenceConfig(equivalence);
836
881
  const byId = new Map<string, CanonicalModelRecord>();
837
882
  const bySelector = new Map<string, string>();
883
+ const aliases = new Map<string, Set<string>>();
884
+ const ambiguousSelectors = new Set<string>();
838
885
 
839
886
  const compiledWithCache = compiledEquivalence as CompiledEquivalenceConfigWithCache;
840
887
  let modelCache = compiledWithCache[kModelResolutionCache];
@@ -865,7 +912,23 @@ export function buildCanonicalModelIndex(
865
912
  };
866
913
  nextRecord.variants.push(variant);
867
914
  byId.set(canonicalKey, nextRecord);
868
- bySelector.set(normalizeSelectorKey(selector), canonical.id);
915
+ const selectorKey = normalizeSelectorKey(selector);
916
+ if (bySelector.has(selectorKey) || ambiguousSelectors.has(selectorKey)) {
917
+ bySelector.delete(selectorKey);
918
+ ambiguousSelectors.add(selectorKey);
919
+ } else {
920
+ bySelector.set(selectorKey, canonical.id);
921
+ }
922
+ const aliasKey = getFinalSlashSegmentAliasKey(model.id);
923
+ if (aliasKey) {
924
+ const aliasSelector = selectorKey;
925
+ let targets = aliases.get(aliasKey);
926
+ if (!targets) {
927
+ targets = new Set<string>();
928
+ aliases.set(aliasKey, targets);
929
+ }
930
+ targets.add(aliasSelector);
931
+ }
869
932
  }
870
933
 
871
934
  const records = [...byId.values()].sort(compareCanonicalRecords);
@@ -873,5 +936,11 @@ export function buildCanonicalModelIndex(
873
936
  record.variants.sort(compareCanonicalVariants);
874
937
  }
875
938
 
876
- return { records, byId, bySelector };
939
+ const aliasesByKey = new Map<string, string[]>();
940
+ for (const [aliasKey, targets] of aliases) {
941
+ for (const ambiguousSelector of ambiguousSelectors) targets.delete(ambiguousSelector);
942
+ if (targets.size > 0) aliasesByKey.set(aliasKey, [...targets]);
943
+ }
944
+
945
+ return { records, byId, bySelector, aliases: aliasesByKey };
877
946
  }