mixdog 1.2.0 → 1.2.1

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 (201) hide show
  1. package/README.md +7 -5
  2. package/package.json +1 -1
  3. package/src/defaults/shipped-history.json +4 -0
  4. package/src/defaults/skills/pptx/SKILL.md +6 -6
  5. package/src/defaults/skills/pptx/references/review.md +6 -4
  6. package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
  7. package/src/defaults/skills/setup/references/actions.md +13 -3
  8. package/src/defaults/skills/visualize/SKILL.md +12 -9
  9. package/src/rules/shared/60-verification.md +3 -1
  10. package/src/rules/shared/70-delivery.md +4 -2
  11. package/src/runtime/agent/orchestrator/cache-mtime.mjs +26 -5
  12. package/src/runtime/agent/orchestrator/config.mjs +10 -4
  13. package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
  14. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
  15. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
  16. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +86 -8
  17. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +5 -1
  18. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +65 -7
  19. package/src/runtime/agent/orchestrator/mcp/client.mjs +128 -44
  20. package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
  21. package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
  22. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +108 -17
  23. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  24. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +19 -11
  25. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
  26. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
  27. package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
  28. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +444 -0
  29. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  30. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
  31. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +9 -2
  32. package/src/runtime/agent/orchestrator/providers/gemini.mjs +41 -28
  33. package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
  34. package/src/runtime/agent/orchestrator/providers/model-proxy-bridge.mjs +1080 -0
  35. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +15 -0
  36. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +35 -0
  37. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +12 -0
  38. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
  39. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -1
  40. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
  41. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
  42. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +2 -1
  43. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +9 -3
  44. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +4 -4
  45. package/src/runtime/agent/orchestrator/providers/registry.mjs +35 -9
  46. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +3 -0
  47. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
  48. package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
  49. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +267 -84
  50. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
  51. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
  52. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
  53. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
  54. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +268 -0
  55. package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
  56. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
  57. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +6 -2
  58. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
  59. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
  60. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
  61. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
  62. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
  63. package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
  64. package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
  65. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
  66. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
  67. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +3 -2
  68. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +6 -4
  69. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +4 -3
  70. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
  71. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -3
  72. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +3 -1
  73. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +20 -12
  74. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +9 -2
  75. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +4 -0
  76. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +17 -4
  77. package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
  78. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +4 -3
  79. package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
  80. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
  81. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +817 -0
  82. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +7 -0
  83. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +19 -12
  84. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
  85. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +183 -374
  86. package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
  87. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +21 -2
  89. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +38 -17
  90. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
  91. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
  92. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
  93. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
  94. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +173 -22
  95. package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
  96. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +5 -4
  97. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +15 -2
  98. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
  99. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
  100. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
  101. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
  102. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
  103. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
  104. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
  105. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
  106. package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
  107. package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
  108. package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
  109. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
  110. package/src/runtime/attachments/store.d.mts +3 -0
  111. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
  112. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
  113. package/src/runtime/channels/lib/worker-main.mjs +17 -14
  114. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
  115. package/src/runtime/effort-judge/judge-client.mjs +56 -18
  116. package/src/runtime/memory/lib/memory.mjs +1 -1
  117. package/src/runtime/memory/lib/session-ingest.mjs +2 -2
  118. package/src/runtime/shared/atomic-file.mjs +25 -4
  119. package/src/runtime/shared/extension-scopes.mjs +5 -3
  120. package/src/runtime/shared/file-lock.mjs +37 -11
  121. package/src/runtime/shared/llm/anthropic-effort.mjs +14 -0
  122. package/src/runtime/shared/llm/model-catalog.mjs +0 -12
  123. package/src/runtime/shared/llm/provider-catalog-registry.mjs +6 -0
  124. package/src/runtime/shared/plugin-manifest.mjs +18 -9
  125. package/src/runtime/shared/pristine-execution-contract.json +9 -2
  126. package/src/runtime/shared/pristine-execution.mjs +28 -4
  127. package/src/runtime/shared/provider-accounts.mjs +124 -20
  128. package/src/runtime/shared/provider-api-key.mjs +12 -0
  129. package/src/runtime/shared/shell-display.mjs +9 -3
  130. package/src/runtime/shared/skill-mentions.d.mts +10 -0
  131. package/src/runtime/shared/skill-mentions.mjs +133 -0
  132. package/src/runtime/shared/skill-script-runtime.mjs +264 -0
  133. package/src/runtime/shared/tool-primitives.mjs +35 -2
  134. package/src/runtime/shared/turn-worktree-snapshot.mjs +25 -5
  135. package/src/runtime/shared/worker-requests.mjs +15 -2
  136. package/src/session-runtime/automation-agents.mjs +8 -2
  137. package/src/session-runtime/boot/apis.mjs +55 -0
  138. package/src/session-runtime/boot/config.mjs +5 -2
  139. package/src/session-runtime/boot/facade.mjs +1 -0
  140. package/src/session-runtime/boot/infrastructure.mjs +29 -18
  141. package/src/session-runtime/conversation-import/discovery.mjs +698 -0
  142. package/src/session-runtime/conversation-import/service.mjs +369 -0
  143. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +10 -5
  144. package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
  145. package/src/session-runtime/mcp-glue/connect.mjs +57 -10
  146. package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
  147. package/src/session-runtime/mcp-glue/server-catalog.mjs +115 -30
  148. package/src/session-runtime/mcp-glue.mjs +22 -2
  149. package/src/session-runtime/new-session-config.mjs +3 -2
  150. package/src/session-runtime/notification-bus.mjs +9 -2
  151. package/src/session-runtime/provider-auth/accounts.mjs +33 -15
  152. package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
  153. package/src/session-runtime/provider-readiness.mjs +16 -10
  154. package/src/session-runtime/resource-extension-sync.mjs +20 -5
  155. package/src/session-runtime/resource-mcp-api.mjs +101 -77
  156. package/src/session-runtime/resource-plugins-api.mjs +29 -41
  157. package/src/session-runtime/resource-scope-decorate.mjs +6 -8
  158. package/src/session-runtime/resource-skills-api.mjs +23 -2
  159. package/src/session-runtime/schedule-session-run.mjs +1 -1
  160. package/src/session-runtime/services/channel-session-router.mjs +4 -1
  161. package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
  162. package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
  163. package/src/session-runtime/services/provider-admin.mjs +64 -18
  164. package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
  165. package/src/session-runtime/setup-tool/executor.mjs +12 -1
  166. package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
  167. package/src/session-runtime/setup-tool/settings-contract.mjs +1 -0
  168. package/src/session-runtime/setup-tool/tool-defs.mjs +7 -1
  169. package/src/session-runtime/skills-api.mjs +7 -3
  170. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  171. package/src/session-runtime/webhook-session-run.mjs +16 -4
  172. package/src/skill-script-entry.mjs +36 -0
  173. package/src/standalone/daemon-stored-session-views.mjs +11 -3
  174. package/src/standalone/daemon.mjs +41 -4
  175. package/src/standalone/session-protocol.mjs +5 -0
  176. package/src/standalone/session-service/session-calls/session-action.mjs +6 -1
  177. package/src/tui/App.jsx +9 -1
  178. package/src/tui/app/app-view/floating-panel.jsx +9 -1
  179. package/src/tui/app/app-view.jsx +6 -0
  180. package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
  181. package/src/tui/app/model-options.mjs +2 -0
  182. package/src/tui/app/skill-mention-palette.mjs +88 -0
  183. package/src/tui/app/slash-palette.mjs +23 -7
  184. package/src/tui/app/transcript-row-estimate.mjs +11 -3
  185. package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
  186. package/src/tui/app/use-prompt-state.mjs +5 -0
  187. package/src/tui/components/Message.jsx +22 -1
  188. package/src/tui/components/PromptInput.jsx +21 -3
  189. package/src/tui/components/SlashCommandPalette.jsx +12 -3
  190. package/src/tui/components/TranscriptItem.jsx +9 -1
  191. package/src/tui/components/prompt-input/key-handler.mjs +21 -8
  192. package/src/tui/components/user-message-skills.mjs +38 -0
  193. package/src/tui/dist/index.mjs +507 -171
  194. package/src/tui/session/queue-helpers.mjs +7 -2
  195. package/src/tui/session/session-api/extensions.mjs +4 -2
  196. package/src/tui/session/session-api/settings.mjs +7 -0
  197. package/src/tui/session/session-flow/drain.mjs +14 -4
  198. package/src/tui/session/tui-steering-persist.mjs +25 -9
  199. package/src/tui/session/turn.mjs +17 -0
  200. package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
  201. package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
@@ -54,6 +54,15 @@ import {
54
54
  beginOAuthLogin as beginMuseCodeOAuthLogin,
55
55
  loginOAuth as loginMuseCodeOAuth,
56
56
  } from '../../runtime/agent/orchestrator/providers/muse-code-oauth-login.mjs';
57
+ import {
58
+ describeCommandCodeOAuthCredentials,
59
+ forgetCommandCodeOAuthCredentials,
60
+ hasCommandCodeOAuthCredentials,
61
+ } from '../../runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs';
62
+ import {
63
+ beginOAuthLogin as beginCommandCodeOAuthLogin,
64
+ loginOAuth as loginCommandCodeOAuth,
65
+ } from '../../runtime/agent/orchestrator/providers/command-code-oauth-login.mjs';
57
66
  import {
58
67
  beginCursorOAuthLogin,
59
68
  describeCursorOAuthCredentials,
@@ -80,6 +89,7 @@ import {
80
89
  changeProviderAccounts,
81
90
  removeProviderAccount,
82
91
  clearProviderAccountQuotaState,
92
+ providerAccountLimit,
83
93
  ACCOUNT_PROVIDERS,
84
94
  } from '../../runtime/shared/provider-accounts.mjs';
85
95
  import { currentProviderAccountId, withProviderAccount } from '../../runtime/shared/provider-auth-binding.mjs';
@@ -154,6 +164,16 @@ const OAUTH_PROVIDERS = Object.freeze([
154
164
  begin: beginMuseCodeOAuthLogin,
155
165
  login: loginMuseCodeOAuth,
156
166
  }),
167
+ Object.freeze({
168
+ id: 'command-code-oauth',
169
+ name: 'Command Code',
170
+ desc: 'Sign in with your Command Code account',
171
+ has: hasCommandCodeOAuthCredentials,
172
+ describe: describeCommandCodeOAuthCredentials,
173
+ forget: forgetCommandCodeOAuthCredentials,
174
+ begin: beginCommandCodeOAuthLogin,
175
+ login: loginCommandCodeOAuth,
176
+ }),
157
177
  Object.freeze({
158
178
  id: 'mimo-oauth',
159
179
  name: 'MiMo',
@@ -421,6 +441,12 @@ function customProviderRow(id, entry, checkSecrets = true) {
421
441
  const keyless = (config?.auth ?? entry.auth) === 'none';
422
442
  const stored = checkSecrets ? hasStoredSecret(SECRET_ACCOUNTS.agentApiKey(id)) : false;
423
443
  const unstoredStatus = configuredEnabled ? 'No Key' : 'Off';
444
+ let status = stored ? 'Set' : unstoredStatus;
445
+ let detail = stored ? 'stored in keychain' : 'custom endpoint';
446
+ if (keyless) {
447
+ status = configuredEnabled ? 'Keyless' : 'Off';
448
+ detail = 'no API key required';
449
+ }
424
450
  const row = {
425
451
  id,
426
452
  name: entry.name,
@@ -431,12 +457,12 @@ function customProviderRow(id, entry, checkSecrets = true) {
431
457
  group: 'api',
432
458
  type: 'api-key',
433
459
  enabled: configuredEnabled,
434
- authenticated: keyless ? true : checkSecrets ? stored : configuredEnabled,
460
+ authenticated: keyless || (checkSecrets ? stored : configuredEnabled),
435
461
  stored,
436
462
  env: false,
437
463
  envName: null,
438
- status: keyless ? (configuredEnabled ? 'Keyless' : 'Off') : stored ? 'Set' : unstoredStatus,
439
- detail: keyless ? 'no API key required' : stored ? 'stored in keychain' : 'custom endpoint',
464
+ status,
465
+ detail,
440
466
  };
441
467
  row.schemaVersion = config?.schemaVersion ?? entry.schemaVersion;
442
468
  row.auth = keyless ? 'none' : 'key';
@@ -584,7 +610,7 @@ export async function loginOAuthProvider(cfgMod, provider) {
584
610
  updateConfigProvider(cfgMod, id, { enabled: true });
585
611
  // Same reason as the interactive add-account flow: a fresh credential says
586
612
  // nothing about the quota the previous one exhausted.
587
- if (ACCOUNT_PROVIDERS.includes(id)) clearProviderAccountQuotaState(id, currentProviderAccountId(id));
613
+ if (ACCOUNT_PROVIDERS.includes(id)) await clearProviderAccountQuotaState(id, currentProviderAccountId(id));
588
614
  }
589
615
  return { provider: id, type: 'oauth', authenticated: Boolean(auth.authenticated), status: auth.status || null };
590
616
  }
@@ -621,7 +647,7 @@ function rosterAccountForIdentity(id, oauth, newAccountId, auth) {
621
647
  return match?.id || null;
622
648
  }
623
649
 
624
- function settleOAuthLogin(
650
+ async function settleOAuthLogin(
625
651
  { cfgMod, id, accountId: signedInId, addAccount, includeDefault, label, inAccount, oauth },
626
652
  result
627
653
  ) {
@@ -635,11 +661,11 @@ function settleOAuthLogin(
635
661
  renameSync(providerAccountPath(id, signedInId), providerAccountPath(id, existingId));
636
662
  accountId = existingId;
637
663
  }
638
- registerProviderAccount(id, accountId, { includeDefault, label });
664
+ await registerProviderAccount(id, accountId, { includeDefault, label });
639
665
  // A re-connect can follow a re-created subscription under the same
640
666
  // account id: start it from no recorded quota rather than from the
641
667
  // exhausted window the previous credential earned.
642
- clearProviderAccountQuotaState(id, accountId);
668
+ await clearProviderAccountQuotaState(id, accountId);
643
669
  updateConfigProvider(cfgMod, id, { enabled: true });
644
670
  }
645
671
  return {
@@ -688,12 +714,20 @@ export async function beginOAuthProviderLogin(cfgMod, provider, options = {}) {
688
714
  // The callback and a pasted code can both deliver the same sign-in; it must
689
715
  // settle once, because settling may move the credential to an existing id.
690
716
  let settled = null;
717
+ let settling = null;
691
718
  const finish = async (result) => {
692
719
  if (!result || cancelled) return null;
693
720
  if (settled) return settled;
694
- const outcome = settleOAuthLogin(login, result);
695
- if (outcome.authenticated) settled = outcome;
696
- return outcome;
721
+ // Settling is async now: a second delivery arriving mid-write shares it.
722
+ if (settling) return settling;
723
+ settling = settleOAuthLogin(login, result);
724
+ try {
725
+ const outcome = await settling;
726
+ if (outcome.authenticated) settled = outcome;
727
+ return outcome;
728
+ } finally {
729
+ settling = null;
730
+ }
697
731
  };
698
732
  return {
699
733
  provider: id,
@@ -721,9 +755,11 @@ export function listProviderAccounts(provider) {
721
755
  if (!oauth) throw new TypeError('Unknown OAuth provider.');
722
756
  const pool = readProviderAccountPool(provider);
723
757
  const accounts = pool.accounts.length ? pool.accounts : defaultAccountRows(provider, oauth);
758
+ const now = Date.now();
724
759
  return {
725
760
  provider,
726
761
  auto: pool.auto !== false,
762
+ policy: pool.policy,
727
763
  useCredits: pool.useCredits === true,
728
764
  selectedId: pool.selectedId || accounts[0]?.id || null,
729
765
  accounts: accounts.map((row) => {
@@ -740,6 +776,9 @@ export function listProviderAccounts(provider) {
740
776
  reauthRequired: auth.reauthRequired === true,
741
777
  usage: row.usage || null,
742
778
  blockedUntil: row.blockedUntil || null,
779
+ // The limit holding the account now: account-wide, or only for one
780
+ // model family ("7D Fable") while other models still run.
781
+ limit: providerAccountLimit(row, now, { useCredits: pool.useCredits === true }),
743
782
  ...(identity ? { identity } : {}),
744
783
  ...(typeof auth.email === 'string' && auth.email.trim() ? { email: auth.email.trim() } : {}),
745
784
  };
@@ -753,17 +792,23 @@ function defaultAccountRows(provider, oauth) {
753
792
  return authenticated ? [{ id: 'default', label: 'Account 1' }] : [];
754
793
  }
755
794
 
756
- export function updateProviderAccounts(provider, change) {
795
+ // Store the implicit signed-in `default` account in the roster, so a change or
796
+ // a usage measurement has a row to land on. Returns the current roster view.
797
+ export async function materializeProviderAccounts(provider) {
757
798
  const current = listProviderAccounts(provider);
758
- if (!readProviderAccountPool(provider).accounts.length && current.accounts.length) {
759
- registerProviderAccount(provider, 'default', { label: 'Account 1' });
760
- }
761
- changeProviderAccounts(provider, change);
799
+ if (readProviderAccountPool(provider).accounts.length || !current.accounts.length) return current;
800
+ await registerProviderAccount(provider, 'default', { label: 'Account 1' });
801
+ return listProviderAccounts(provider);
802
+ }
803
+
804
+ export async function updateProviderAccounts(provider, change) {
805
+ await materializeProviderAccounts(provider);
806
+ await changeProviderAccounts(provider, change);
762
807
  // Picking an account by hand is a decision to use it NOW, so the refusal
763
808
  // window and meter stored against it — both describing the state the user
764
809
  // just changed — are dropped here. Never in the pool's own fallback commit,
765
810
  // where clearing them would send the request back to a spent account.
766
- if (change?.selectedId !== undefined) clearProviderAccountQuotaState(provider, change.selectedId);
811
+ if (change?.selectedId !== undefined) await clearProviderAccountQuotaState(provider, change.selectedId);
767
812
  return listProviderAccounts(provider);
768
813
  }
769
814
 
@@ -859,7 +904,8 @@ async function resolveCustomInput(cfgMod, rawInput, options) {
859
904
  const config = normalizeCustomProviderConfig(input, options);
860
905
  const typed = typeof input.apiKey === 'string' ? input.apiKey.trim() : '';
861
906
  const keyless = config.auth === 'none';
862
- const apiKey = keyless ? '' : typed || (id ? getAgentApiKey(id) || '' : '');
907
+ let apiKey = '';
908
+ if (!keyless) apiKey = typed || (id ? getAgentApiKey(id) || '' : '');
863
909
  return { id, config, typed: keyless ? '' : typed, apiKey, keyless };
864
910
  }
865
911
 
@@ -1078,7 +1124,7 @@ export async function forgetProviderAuth(cfgModOrProvider, maybeProvider, reques
1078
1124
  throw new TypeError('Account is no longer connected.');
1079
1125
  }
1080
1126
  const result = withProviderAccount(id, accountId, () => oauth.forget());
1081
- const pool = removeProviderAccount(id, accountId);
1127
+ const pool = await removeProviderAccount(id, accountId);
1082
1128
  if (cfgMod) updateConfigProvider(cfgMod, id, { enabled: pool.accounts.length > 0 });
1083
1129
  return { provider: id, type: 'oauth', forgotten: true, removed: Boolean(result?.removed) };
1084
1130
  }
@@ -236,6 +236,8 @@ function providerDescription(id, group) {
236
236
  return 'Grok Build subscription quota';
237
237
  case 'muse-code-oauth':
238
238
  return 'Muse Code subscription quota';
239
+ case 'command-code-oauth':
240
+ return 'Command Code subscription quota';
239
241
  case 'mimo-oauth':
240
242
  return 'MiMo Token Plan subscription quota';
241
243
  case 'antigravity-oauth':
@@ -343,6 +345,7 @@ const PROVIDER_RANKS = {
343
345
  'anthropic-oauth': 20,
344
346
  'grok-oauth': 30,
345
347
  'muse-code-oauth': 33,
348
+ 'command-code-oauth': 33.5,
346
349
  'mimo-oauth': 34,
347
350
  'cursor-oauth': 35,
348
351
  'antigravity-oauth': 36,
@@ -146,6 +146,7 @@ function publicAccounts(pool) {
146
146
  return {
147
147
  selectedId: pool.selectedId,
148
148
  auto: pool.auto,
149
+ ...(pool.policy ? { policy: pool.policy } : {}),
149
150
  useCredits: pool.useCredits === true,
150
151
  accounts: pool.accounts.map(({ id, label, authenticated, reauthRequired }) => ({
151
152
  id,
@@ -166,9 +167,15 @@ function mcpRows(status) {
166
167
  transport: server.transport || server.type || '',
167
168
  toolCount: Number(server.toolCount || 0),
168
169
  source: server.source || '',
170
+ ...(server.owner?.kind === 'plugin'
171
+ ? { plugin: server.owner.id, pluginServer: server.owner.key, disabledReason: server.disabledReason || null }
172
+ : {}),
169
173
  error: server.error || null,
174
+ ...(server.warnings?.length ? { warnings: server.warnings } : {}),
170
175
  scope: Array.isArray(server.scope) ? server.scope : null,
171
176
  })),
177
+ ...(status?.warnings?.length ? { warnings: status.warnings } : {}),
178
+ ...(status?.declarationErrors?.length ? { declarationErrors: status.declarationErrors } : {}),
172
179
  };
173
180
  }
174
181
 
@@ -577,7 +584,11 @@ const SETUP_ACTION_HANDLERS = {
577
584
  const plugin = ((await rt.pluginsStatus())?.plugins || []).find((row) => row.name === name || row.id === name);
578
585
  if (!plugin) throw new Error(`plugin "${name}" not found`);
579
586
  const result = await rt.enablePluginMcp(plugin);
580
- return { serverName: result.serverName, mcp: mcpRows(result.status) };
587
+ return {
588
+ serverName: result.serverName,
589
+ ...(result.serverNames ? { serverNames: result.serverNames } : {}),
590
+ mcp: mcpRows(result.status),
591
+ };
581
592
  },
582
593
  };
583
594
 
@@ -34,6 +34,7 @@ function publicMcpConfig(row) {
34
34
  return {
35
35
  name: row.name,
36
36
  source: row.source,
37
+ ...(row.owner?.kind === 'plugin' ? { plugin: row.owner.id, pluginServer: row.owner.key, readOnly: true } : {}),
37
38
  enabled: row.enabled !== false,
38
39
  type: config.type || 'stdio',
39
40
  cwd: config.cwd || '',
@@ -196,6 +196,7 @@ export const SETUP_EXTENDED_PROPERTIES = Object.freeze({
196
196
  runInBackground: boolean,
197
197
  turnNotifications: boolean,
198
198
  usagePinned: boolean,
199
+ usageRailShortLong: boolean,
199
200
  computerObserveOnly: boolean,
200
201
  notificationVolume: {
201
202
  type: 'integer',
@@ -227,7 +227,7 @@ export const SETUP_TOOL_DEFS = Object.freeze([
227
227
  type: 'string',
228
228
  enum: [...INSTRUCTION_FILES_MODES],
229
229
  description:
230
- 'set_instruction_files: which user instruction files new sessions load (global ~/.codex/AGENTS.md, ~/.claude/CLAUDE.md, then the Project root\'s AGENTS.md/CLAUDE.md).',
230
+ "set_instruction_files: which user instruction files new sessions load (global ~/.codex/AGENTS.md, ~/.claude/CLAUDE.md, then the Project root's AGENTS.md/CLAUDE.md).",
231
231
  },
232
232
  name: {
233
233
  type: 'string',
@@ -244,6 +244,12 @@ export const SETUP_TOOL_DEFS = Object.freeze([
244
244
  properties: {
245
245
  selectedId: { type: 'string', description: 'Account to use now.' },
246
246
  auto: { type: 'boolean', description: 'Automatic switching to another account when one is exhausted.' },
247
+ policy: {
248
+ type: 'string',
249
+ enum: ['manual', 'limit', 'headroom'],
250
+ description:
251
+ 'Account switching: manual = selected account only; limit = switch when it reaches a limit; headroom = each new session starts on the account with the most headroom.',
252
+ },
247
253
  useCredits: { type: 'boolean', description: 'Allow paid credits once plan limits are reached.' },
248
254
  order: {
249
255
  type: 'array',
@@ -24,8 +24,8 @@ const DEFAULT_SKILL_BODY = '# Instructions\n\nDescribe how to use this skill.';
24
24
  export function createSkillsApi({ contextMod, getCwd, getTools = () => [] }) {
25
25
  const globalSkillsRoot = () => resolve(resolvePluginData(), 'skills');
26
26
 
27
- function skillsStatus() {
28
- const cwd = getCwd();
27
+ /** `cwd` overrides the session's folder (already validated by the caller). */
28
+ function skillsStatus({ cwd = getCwd() } = {}) {
29
29
  const skills = typeof contextMod.collectSkillsCached === 'function' ? contextMod.collectSkillsCached(cwd) : [];
30
30
  const norm = (value) =>
31
31
  String(value || '')
@@ -54,7 +54,10 @@ export function createSkillsApi({ contextMod, getCwd, getTools = () => [] }) {
54
54
  const source = sourceForSkill(skill);
55
55
  return {
56
56
  name: skill.name,
57
- enabled: !(contextMod.isSkillDisabled?.(skill.name) || contextMod.skillMissingFeature?.(skill.name)),
57
+ enabled: !(
58
+ contextMod.isSkillDisabled?.(skill.name, null, skills) ||
59
+ contextMod.skillMissingFeature?.(skill.name, null, skills)
60
+ ),
58
61
  description: skill.description || '',
59
62
  whenToUse: skill.whenToUse || '',
60
63
  filePath: skill.filePath || null,
@@ -90,6 +93,7 @@ export function createSkillsApi({ contextMod, getCwd, getTools = () => [] }) {
90
93
  if (typeof contextMod.isSkillDisabled === 'function' && contextMod.isSkillDisabled(skillName)) {
91
94
  return `Error: skill "${skillName}" is disabled`;
92
95
  }
96
+ if (contextMod.skillOutsideScope?.(skillName, getCwd())) return contextMod.skillOutsideScopeError(skillName);
93
97
  const skill = skillContent(skillName);
94
98
  // The general tool envelope keeps the main/Lead session identical to agent
95
99
  // loops: the model-visible tool_result is the short stub and the SKILL.md
@@ -91,6 +91,7 @@ export function createAskCallbacks({ transcript, notifyFnForSession, scheduleSea
91
91
  onStreamDelta: streamDelta(turn, options),
92
92
  drainSteering: options.drainSteering,
93
93
  onSteerMessage: options.onSteerMessage,
94
+ onSkillsApplied: options.onSkillsApplied,
94
95
  notifyFn: notifyFnForSession(session0.id),
95
96
  signal: turn.signal,
96
97
  };
@@ -35,6 +35,10 @@ export async function runWebhookSession({
35
35
  workflow = null,
36
36
  attachments = null,
37
37
  signal = null,
38
+ // Endpoint-authored instructions: the run's ONLY skill-selection input.
39
+ // `prompt` also carries the fenced external payload, which must never
40
+ // select a skill; absent means nothing selects.
41
+ skillSelection = '',
38
42
  }) {
39
43
  const endpoint = String(name || '').trim();
40
44
  const body = String(prompt || '').trim();
@@ -63,9 +67,17 @@ export async function runWebhookSession({
63
67
  // askOpts.signal is the parent-abort link: it is cascaded onto the turn
64
68
  // controller, so aborting it stops the in-flight provider/tool work instead
65
69
  // of leaving a timed-out delivery's session running with live side effects.
66
- const ask = (turnContent) =>
67
- askSession(session.id, turnContent, null, null, projectCwd || undefined, undefined, signal ? { signal } : {});
68
- // Agents the run delegates to report back; each result is the next turn.
69
- const result = await runAutomationTurns(session.id, () => ask(content), ask);
70
+ const ask = (turnContent, askOpts) =>
71
+ askSession(session.id, turnContent, null, null, projectCwd || undefined, undefined, {
72
+ ...askOpts,
73
+ ...(signal ? { signal } : {}),
74
+ });
75
+ // Agents the run delegates to report back; each result is the next turn
76
+ // (runAutomationTurns marks those turns as task notifications).
77
+ const result = await runAutomationTurns(
78
+ session.id,
79
+ () => ask(content, { skillSelection: String(skillSelection ?? '') }),
80
+ ask
81
+ );
70
82
  return { sessionId: session.id, result: String(result?.content || '') };
71
83
  }
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ // Entry the skill-script launcher runs (src/runtime/shared/skill-script-runtime.mjs):
3
+ // <runtime executable> skill-script-entry.mjs <script.mjs> [args...]
4
+ // The script then sees itself as the main module, exactly as under
5
+ // `node <script.mjs> [args...]`, and its exit code is the process's.
6
+
7
+ // Before anything can import sharp: Electron on Linux must use its wasm build.
8
+ await import('./runtime/shared/sharp-electron-linux.mjs');
9
+ // Node mode belongs to this process only; whatever the script starts is a
10
+ // normal child again.
11
+ delete process.env.ELECTRON_RUN_AS_NODE;
12
+
13
+ const { pathToFileURL } = await import('node:url');
14
+ const { canonicalSkillScriptPath, decodeSkillArgv, SKILL_ARGV_ENV } = await import(
15
+ './runtime/shared/skill-script-runtime.mjs'
16
+ );
17
+
18
+ // The PowerShell `mixdog-skill-node` function passes its arguments in the
19
+ // environment (see renderPowerShellSkillFunction); the script never sees them.
20
+ const envArgv = process.argv.length === 2 ? decodeSkillArgv(process.env[SKILL_ARGV_ENV]) : null;
21
+ delete process.env[SKILL_ARGV_ENV];
22
+ const [execPath, , target, ...args] = envArgv ? [process.argv[0], process.argv[1], ...envArgv] : process.argv;
23
+ if (!target) {
24
+ console.error('Usage: <skill launcher> <script.mjs> [args...]');
25
+ process.exitCode = 2;
26
+ } else {
27
+ const script = canonicalSkillScriptPath(target);
28
+ // The scripts' main check compares import.meta.url with argv[1].
29
+ process.argv = [execPath, script, ...args];
30
+ try {
31
+ await import(pathToFileURL(script).href);
32
+ } catch (error) {
33
+ console.error(error?.stack || String(error));
34
+ process.exitCode = 1;
35
+ }
36
+ }
@@ -78,9 +78,17 @@ export function createStoredSessionViews({ desktopRuntime, dataDir }) {
78
78
  });
79
79
  }
80
80
  const store = await desktopRuntime.loadSessionStore();
81
- return store.listStoredSessionSummaries({
82
- refreshFromStorage: options.refreshFromStorage === true,
83
- });
81
+ const refreshFromStorage = options.refreshFromStorage === true;
82
+ // Paged ordinary catalog ({ sessions, nextCursor }) only on the explicit
83
+ // `page: { limit, cursor }` option (`limit` alone keeps its old meaning:
84
+ // ignored, full array). An authoritative refresh is never paged.
85
+ const page = options.page && typeof options.page === 'object' ? options.page : null;
86
+ if (page && !refreshFromStorage && store.listStoredSessionSummaryPage) {
87
+ return store.listStoredSessionSummaryPage({ limit: page.limit, cursor: page.cursor });
88
+ }
89
+ // The async listing may answer from the session catalog DB.
90
+ if (store.listStoredSessionSummariesAsync) return store.listStoredSessionSummariesAsync({ refreshFromStorage });
91
+ return store.listStoredSessionSummaries({ refreshFromStorage });
84
92
  },
85
93
  };
86
94
  }
@@ -49,6 +49,7 @@ import {
49
49
  startOwnerLivenessListener,
50
50
  } from './daemon-owner.mjs';
51
51
  import { remoteIntentPath } from '../runtime/shared/remote-intent.mjs';
52
+ import { modelProxyClientMode } from '../runtime/shared/model-proxy-mode.mjs';
52
53
  import { setChannelNotifySink } from '../runtime/channels/lib/parent-bridge.mjs';
53
54
  import { setOwnerContext } from '../runtime/channels/lib/runtime-paths.mjs';
54
55
  import { safeIpcSend } from '../runtime/shared/safe-ipc-send.mjs';
@@ -144,6 +145,11 @@ let ownerLiveness = null;
144
145
  // The endpoints daemon.json carries once published; a heartbeat re-publishes
145
146
  // them when the file goes missing (TEMP cleanup).
146
147
  let publishedEndpoints = null;
148
+ // Remote mode only: the loopback relay that lets headless runs started from
149
+ // model shells (skill scripts) reach the PC behind this daemon's model proxy
150
+ // channel (providers/model-proxy-bridge.mjs), and its running-call count.
151
+ let modelProxyBridge = null;
152
+ let modelProxyBridgeActive = 0;
147
153
  const eventLoopDelay = monitorEventLoopDelay({ resolution: 20 });
148
154
  eventLoopDelay.enable();
149
155
  let idleGc = null;
@@ -218,6 +224,13 @@ async function shutdown(reason, code = 0) {
218
224
  try {
219
225
  setChannelNotifySink(null);
220
226
  } catch {}
227
+ // Abort every relayed model call, close the bridge sockets and wait for the
228
+ // calls to settle before anything they depend on goes away.
229
+ try {
230
+ await modelProxyBridge?.close(`daemon shutdown: ${reason}`);
231
+ } catch (e) {
232
+ log(`model proxy bridge stop failed: ${e?.message || e}`);
233
+ }
221
234
  try {
222
235
  await sessionService?.stop?.(reason);
223
236
  } catch (e) {
@@ -292,7 +305,7 @@ async function shutdown(reason, code = 0) {
292
305
  * asks whether this process may exit; idle GC asks whether a pause is safe. */
293
306
  function inFlightWork() {
294
307
  return {
295
- activeCalls: (transport?.activeCount ?? 0) + (sessionTransport?.activeCount ?? 0),
308
+ activeCalls: (transport?.activeCount ?? 0) + (sessionTransport?.activeCount ?? 0) + modelProxyBridgeActive,
296
309
  queuedCalls: (transport?.queuedCount ?? 0) + (sessionTransport?.queuedCount ?? 0),
297
310
  busySessions: sessionService?.busyCount ?? 0,
298
311
  busyMemoryAgents: agentDispatchBroker?.snapshot?.().inFlight ?? 0,
@@ -562,9 +575,9 @@ function daemonStatusSnapshot() {
562
575
 
563
576
  /** The file every attacher reads to find this daemon: wire identity plus both
564
577
  * loopback endpoints and their privileged tokens. */
565
- function publishDaemonDiscovery({ channel, session }) {
578
+ function publishDaemonDiscovery({ channel, session, modelProxy = null }) {
566
579
  // A re-publish keeps the first publication's startedAt.
567
- publishedEndpoints = { channel, session, startedAt: publishedEndpoints?.startedAt ?? Date.now() };
580
+ publishedEndpoints = { channel, session, modelProxy, startedAt: publishedEndpoints?.startedAt ?? Date.now() };
568
581
  writeJsonAtomicSync(
569
582
  DAEMON_DISCOVERY_PATH,
570
583
  {
@@ -577,12 +590,34 @@ function publishDaemonDiscovery({ channel, session }) {
577
590
  endpoints: {
578
591
  channel: { port: channel.port, token: channel.token },
579
592
  session: { port: session.port, token: session.token },
593
+ ...(modelProxy ? { modelProxy: { port: modelProxy.port, token: modelProxy.token } } : {}),
580
594
  },
581
595
  },
582
596
  { compact: true, secret: true }
583
597
  );
584
598
  }
585
599
 
600
+ /** Remote mode only: the model proxy bridge endpoint to publish, or null. */
601
+ async function startModelProxyBridge(bootPhases) {
602
+ if (!modelProxyClientMode()) return null;
603
+ const [{ createModelProxyBridgeServer }, { getModelProxyChannel }] = await Promise.all([
604
+ import('../runtime/agent/orchestrator/providers/model-proxy-bridge.mjs'),
605
+ import('../runtime/agent/orchestrator/providers/model-proxy-channel.mjs'),
606
+ ]);
607
+ modelProxyBridge = createModelProxyBridgeServer({
608
+ channel: getModelProxyChannel(),
609
+ // Fenced or shutting down: no new relayed call.
610
+ admit: () => !shuttingDown && admitOwnerWork(),
611
+ trackWork: (active) => {
612
+ modelProxyBridgeActive = active;
613
+ },
614
+ log,
615
+ });
616
+ const endpoint = await bootPhases.measure('model-proxy-bridge-start', () => modelProxyBridge.start());
617
+ log(`model proxy bridge on 127.0.0.1:${endpoint.port}`);
618
+ return endpoint;
619
+ }
620
+
586
621
  /** Asked by both front doors before every register/call: an expired lease
587
622
  * fences here (answered 503), and after an event-loop stall nothing is
588
623
  * served until the record is re-verified as ours. */
@@ -598,6 +633,7 @@ function fenceDaemon(reason) {
598
633
  log(`${reason} — refusing new work and stopping so only one daemon serves`);
599
634
  sessionTransport?.commitDrain?.(reason);
600
635
  transport?.commitDrain?.(reason);
636
+ modelProxyBridge?.abortAll(reason);
601
637
  try {
602
638
  if (discoveryPid(DAEMON_DISCOVERY_PATH) === process.pid) rmSync(DAEMON_DISCOVERY_PATH, { force: true });
603
639
  } catch (e) {
@@ -890,6 +926,7 @@ async function main() {
890
926
  admitWork: admitOwnerWork,
891
927
  });
892
928
  const sessionEndpoint = await bootPhases.measure('session-transport-start', () => sessionTransport.start());
929
+ const modelProxyEndpoint = await startModelProxyBridge(bootPhases);
893
930
  // A boot whose holder stopped answering may have been superseded by a
894
931
  // contender between beats; never publish over the winner's endpoint. An
895
932
  // unreadable record (an antivirus lock) is not a takeover.
@@ -900,7 +937,7 @@ async function main() {
900
937
  exitRecorder.setReason(`owner claim superseded by pid=${ownerPid} before publish`, 0);
901
938
  process.exit(0);
902
939
  }
903
- publishDaemonDiscovery({ channel: { port, token }, session: sessionEndpoint });
940
+ publishDaemonDiscovery({ channel: { port, token }, session: sessionEndpoint, modelProxy: modelProxyEndpoint });
904
941
  bootPhases.mark('discovery-published');
905
942
  log(`session front door on 127.0.0.1:${sessionEndpoint.port}`);
906
943
 
@@ -13,6 +13,7 @@ export const SESSION_READ_ACTIONS = Object.freeze([
13
13
  'getChannelSetup',
14
14
  'getChannelWorkerStatus',
15
15
  'getCompactionSettings',
16
+ 'getConversationImportJobs',
16
17
  'getDeveloperSettings',
17
18
  'getDisabledSkills',
18
19
  'getMediaDefault',
@@ -57,6 +58,7 @@ export const SESSION_READ_ACTIONS = Object.freeze([
57
58
  'hooksStatus',
58
59
  'inheritancePreflight',
59
60
  'listAgents',
61
+ 'listImportableConversations',
60
62
  'listMediaAssets',
61
63
  'listMediaLanes',
62
64
  'listOutputStyles',
@@ -87,6 +89,7 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
87
89
  'cacheMediaThumbnail',
88
90
  'cancelMediaJob',
89
91
  'cancelOAuthProviderLogin',
92
+ 'cancelConversationImport',
90
93
  'checkForUpdate',
91
94
  'clear',
92
95
  'compact',
@@ -118,6 +121,7 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
118
121
  'pushNotice',
119
122
  'recall',
120
123
  'reconnectMcp',
124
+ 'registerConversationImportFile',
121
125
  'reloadPlugins',
122
126
  'reloadSkills',
123
127
  'rememberPromptHistory',
@@ -198,6 +202,7 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
198
202
  'setOrchestrationMode',
199
203
  'saveStarterGuideState',
200
204
  'skipOnboarding',
205
+ 'startConversationImport',
201
206
  'startMediaJob',
202
207
  'stopShellJob',
203
208
  'switchContext',
@@ -101,15 +101,20 @@ export function createSessionActionCalls(ctx) {
101
101
  if (typeof listSessions !== 'function') {
102
102
  throw new Error('session catalog is unavailable');
103
103
  }
104
- const sessions = await listSessions({
104
+ const listed = await listSessions({
105
105
  ...(options || {}),
106
106
  // Agent-only records are an internal ancestry/reuse source, never part
107
107
  // of the ordinary session catalog returned over the public transport.
108
108
  includeAgentOnly: false,
109
109
  });
110
+ // A `page: { limit, cursor }` request answers one page: { sessions, nextCursor }.
111
+ const page = listed && !Array.isArray(listed) && Array.isArray(listed.sessions) ? listed : null;
112
+ const sessions = page ? page.sessions : listed;
110
113
  const remoteSession = typeof getRemoteSessionState === 'function' ? await getRemoteSessionState() : null;
114
+ const nextCursor = typeof page?.nextCursor === 'string' ? page.nextCursor : null;
111
115
  return {
112
116
  sessions: sanitizeForWire(Array.isArray(sessions) ? sessions : []),
117
+ ...(page ? { nextCursor } : {}),
113
118
  remoteSession: sanitizeForWire(remoteSession) ?? null,
114
119
  };
115
120
  }
package/src/tui/App.jsx CHANGED
@@ -21,6 +21,7 @@ import { useApp, useStdin, useStdout } from 'ink';
21
21
  import { useSession } from './hooks/useSession.mjs';
22
22
  import { pickFolder } from '../standalone/folder-dialog.mjs';
23
23
  import { deriveSlashPalette } from './app/slash-palette.mjs';
24
+ import { skillMentionQuery, useSkillMentionSkills } from './app/skill-mention-palette.mjs';
24
25
  import { shouldSupersedePanelEpoch, supersedePanelEpoch } from './app/panel-epoch.mjs';
25
26
  import { createPanelSurface } from './app/panel-surface.mjs';
26
27
  import { resolvePickerState } from './app/picker-index-mode.mjs';
@@ -215,6 +216,8 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
215
216
  setPromptDraft,
216
217
  promptDraftOverride,
217
218
  setPromptDraftOverride,
219
+ promptCaret,
220
+ setPromptCaret,
218
221
  promptLayoutValueRef,
219
222
  setPromptLayoutRows,
220
223
  textEntryLayoutRows,
@@ -625,7 +628,8 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
625
628
  pastedTextsRef,
626
629
  });
627
630
 
628
- const { activeSlashQuery, slashCommands, slashPaletteOpen } = deriveSlashPalette({
631
+ const mentionSkills = useSkillMentionSkills(store, skillMentionQuery(promptDraft, promptCaret) !== null);
632
+ const { activeSlashQuery, slashCommands, slashPaletteOpen, paletteKind } = deriveSlashPalette({
629
633
  providerPrompt,
630
634
  settingsPrompt,
631
635
  toolApproval,
@@ -636,6 +640,8 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
636
640
  commandBusy: state.commandBusy,
637
641
  promptDraft,
638
642
  slashDismissedFor,
643
+ skills: mentionSkills,
644
+ promptCaret,
639
645
  });
640
646
  slashPaletteRef.current = { open: slashPaletteOpen, count: slashCommands.length };
641
647
  scrollFocusRef.current = {
@@ -833,10 +839,12 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
833
839
  hasUserMessages,
834
840
  initialStatusLine,
835
841
  onPromptDraftChange,
842
+ onPromptCaretChange: setPromptCaret,
836
843
  onSubmit,
837
844
  overlayHintAttachItemIndex,
838
845
  overlayHintFallbackRow,
839
846
  overlayHintOnLastItem,
847
+ paletteKind,
840
848
  panelInkMaskEpoch,
841
849
  panelTransitionEpoch,
842
850
  picker,