mixdog 0.9.85 → 0.9.86

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 (74) hide show
  1. package/README.md +7 -1
  2. package/package.json +3 -2
  3. package/src/defaults/agents.json +12 -0
  4. package/src/defaults/skills/setup/SKILL.md +44 -12
  5. package/src/help.mjs +40 -9
  6. package/src/lib/keychain-cjs.cjs +11 -1
  7. package/src/rules/agent/43-title-agent.md +22 -0
  8. package/src/rules/shared/01-tool.md +3 -3
  9. package/src/runtime/agent/orchestrator/mcp/client.mjs +24 -0
  10. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +84 -3
  11. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -2
  12. package/src/runtime/agent/orchestrator/session/context-utils.mjs +73 -45
  13. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +15 -18
  14. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +1 -1
  15. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -0
  16. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +135 -9
  17. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +31 -12
  18. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +4 -0
  19. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +14 -0
  20. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +170 -0
  21. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +45 -6
  22. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +26 -0
  23. package/src/runtime/agent/orchestrator/session/token-bpe.mjs +42 -0
  24. package/src/runtime/agent/orchestrator/session/token-native.mjs +186 -0
  25. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +15 -5
  26. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +20 -0
  27. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +1 -1
  28. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +24 -0
  29. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
  30. package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +286 -0
  31. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
  32. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +152 -18
  33. package/src/runtime/agent/orchestrator/tools/token-binary-fetcher.mjs +201 -0
  34. package/src/runtime/agent/orchestrator/tools/token-manifest.json +5 -0
  35. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +3 -3
  36. package/src/runtime/media/adapters/gemini-video.mjs +1 -1
  37. package/src/runtime/media/renditions.mjs +1 -1
  38. package/src/runtime/media/store.mjs +1 -1
  39. package/src/runtime/shared/atomic-file.mjs +25 -14
  40. package/src/runtime/shared/automation-attachments.mjs +3 -3
  41. package/src/runtime/shared/child-guardian.mjs +15 -5
  42. package/src/runtime/shared/child-spawn-gate.mjs +6 -12
  43. package/src/runtime/shared/resource-admission.mjs +7 -3
  44. package/src/runtime/shared/turn-snapshot.mjs +0 -3
  45. package/src/session-runtime/lifecycle-api.mjs +34 -9
  46. package/src/session-runtime/prewarm.mjs +35 -0
  47. package/src/session-runtime/remote-control.mjs +8 -47
  48. package/src/session-runtime/remote-transcript.mjs +3 -7
  49. package/src/session-runtime/route-preparation.mjs +24 -0
  50. package/src/session-runtime/runtime-core.mjs +37 -85
  51. package/src/session-runtime/session-turn-api.mjs +9 -2
  52. package/src/session-runtime/workflow-agents-api.mjs +3 -0
  53. package/src/standalone/agent-shard/shard-child.mjs +300 -0
  54. package/src/standalone/agent-shard/shard-pool.mjs +442 -0
  55. package/src/standalone/agent-tool.mjs +222 -0
  56. package/src/standalone/channel-daemon-transport.mjs +126 -185
  57. package/src/standalone/channel-daemon.mjs +9 -10
  58. package/src/tui/App.jsx +16 -9
  59. package/src/tui/app/channel-pickers.mjs +3 -8
  60. package/src/tui/app/route-pickers.mjs +117 -152
  61. package/src/tui/app/settings-picker.mjs +90 -89
  62. package/src/tui/app/slash-commands.mjs +7 -7
  63. package/src/tui/app/slash-dispatch.mjs +0 -45
  64. package/src/tui/components/StatusLine.jsx +1 -1
  65. package/src/tui/components/tool-execution/ResultBody.jsx +1 -1
  66. package/src/tui/dist/index.mjs +243 -323
  67. package/src/tui/engine/session-api-ext.mjs +24 -9
  68. package/src/tui/engine.mjs +7 -1
  69. package/src/tui/figures.mjs +0 -1
  70. package/src/ui/statusline-format.mjs +0 -1
  71. package/src/hooks/lib/permission-evaluator.cjs +0 -24
  72. package/src/lib/config-cjs.cjs +0 -61
  73. package/src/runtime/shared/launcher-control.mjs +0 -258
  74. package/src/runtime/shared/workspace-router.mjs +0 -259
@@ -59,6 +59,7 @@ import {
59
59
  } from './agent-tool/worker-rows.mjs';
60
60
  import { resolveAgentTerminalReapMs } from '../session-runtime/config-helpers.mjs';
61
61
  import { createWorkerIndex } from './agent-tool/worker-index.mjs';
62
+ import { agentShardEnabled, shardRequest } from './agent-shard/shard-pool.mjs';
62
63
  import {
63
64
  beginAgentTurnReview,
64
65
  completeAgentTurnReview,
@@ -734,6 +735,17 @@ export function createStandaloneAgent({
734
735
  if (currentMeta?.sessionId) {
735
736
  try { mgr.closeSession(currentMeta.sessionId, 'agent-task-cancel'); } catch {}
736
737
  }
738
+ // Sharded spawn: the live session exists only in the shard child —
739
+ // forward the cancel by sessionId (known after the `spawned` progress
740
+ // event) or tag.
741
+ if (agentShardEnabled() && (currentMeta?.tag || currentMeta?.sessionId)) {
742
+ try {
743
+ void shardRequest('cancel', {
744
+ tag: currentMeta?.tag || null,
745
+ sessionId: currentMeta?.sessionId || null,
746
+ }).catch(() => {});
747
+ } catch { /* best-effort */ }
748
+ }
737
749
  },
738
750
  run: async () => {
739
751
  const lease = await resourceAdmission.acquire('agent', {
@@ -758,6 +770,13 @@ export function createStandaloneAgent({
758
770
  function startDeferredSpawnJob(args, callerCwd, context, notifyContext, extras = {}) {
759
771
  return startJob('spawn', pendingSpawnMeta(args, extras), async (job) => {
760
772
  if (job?.status === 'cancelled') return null;
773
+ // P1 agent sharding (MIXDOG_AGENT_SHARD=1, default off): run the worker
774
+ // session in a shard child process instead of this event loop. Prompt
775
+ // resolution stays here (cheap); prep + the full agent loop run in the
776
+ // shard. Tag/index/watchdog parity lands in P2 (docs/agent-shard-design.md).
777
+ if (agentShardEnabled()) {
778
+ return await runShardSpawn(args, callerCwd, context, job);
779
+ }
761
780
  // prepareSpawn (ensureProvider/prepareAgentSession) runs before runSpawn
762
781
  // installs its progress watchdog, so guard prep with an internal env-
763
782
  // backed cap rather than exposing per-call timeout knobs on the agent
@@ -816,7 +835,199 @@ export function createStandaloneAgent({
816
835
  progressWatchdogs.start(sessionId, watchdogPolicy, agent);
817
836
 
818
837
  async function prepareSpawn(args, callerCwd = null, context = {}, prepState = null) {
838
+ // (sharded spawns never reach here — see startDeferredSpawnJob)
819
839
  refreshTagsFromSessions({ context });
840
+ return prepareSpawnInProcess(args, callerCwd, context, prepState);
841
+ }
842
+
843
+ // Tag-reuse routing for sharded workers (P2): a same-tag spawn/send while
844
+ // the shard runs the session mirrors the in-process busy-session enqueue.
845
+ // Discriminator: a non-terminal worker-index row whose session has no live
846
+ // runtime in THIS process. (mgr.getSession must not be used here — it
847
+ // falls back to the on-disk session file the shard child persists, which
848
+ // makes a shard-owned session look lead-live.) Returns the rendered queued
849
+ // result, or null to fall through to the normal path (unknown tag,
850
+ // terminal row, lead-owned runtime, or the shard no longer owns the
851
+ // session — e.g. after a shard crash, where respawn is the correct
852
+ // recovery).
853
+ async function tryShardEnqueue(target, args, context, extras = {}) {
854
+ if (!agentShardEnabled()) return null;
855
+ const value = clean(target);
856
+ if (!value) return null;
857
+ const row = readWorkerRows(context).find((r) => (
858
+ (clean(r.tag) === value || clean(r.sessionId) === value)
859
+ && rowMatchesContext(r, context)
860
+ && !isTerminalWorkerStatus(r.status || r.stage)
861
+ ));
862
+ if (!row) return null;
863
+ const rowSessionId = clean(row.sessionId);
864
+ const runtime = rowSessionId ? mgr.getSessionRuntime?.(rowSessionId) : null;
865
+ if (runtime?.controller?.signal && !runtime.controller.signal.aborted) return null;
866
+ const prompt = await resolvePrompt(args, clean(row.cwd) || defaultCwd || process.cwd());
867
+ if (!clean(prompt)) return null;
868
+ let queued = null;
869
+ try {
870
+ queued = await shardRequest('enqueue', {
871
+ tag: clean(row.tag) || null,
872
+ sessionId: rowSessionId || null,
873
+ prompt,
874
+ });
875
+ } catch { return null; }
876
+ if (queued?.queued !== true) return null;
877
+ return renderResult({
878
+ queued: true,
879
+ ...extras,
880
+ tag: clean(row.tag) || null,
881
+ sessionId: rowSessionId || null,
882
+ agent: clean(row.agent) || null,
883
+ queueDepth: queued.queueDepth || 1,
884
+ });
885
+ }
886
+
887
+ async function runShardSpawn(args, callerCwd, context, job) {
888
+ const agent = normalizeAgentName(args.agent);
889
+ if (!agent) throw new Error('agent spawn: agent is required');
890
+ const tag = clean(args.tag) || nextTag(agent, context);
891
+ const baseCwd = resolve(callerCwd || defaultCwd || process.cwd());
892
+ const workerCwd = clean(args.cwd) ? resolve(baseCwd, args.cwd) : baseCwd;
893
+ const prompt = withCwdHeader(await resolvePrompt(args, workerCwd), workerCwd);
894
+ const clientHostPid = terminalPidForContext(context) || null;
895
+ // P2 progress parity: the shard reports the bound session the moment it
896
+ // exists (`spawned`), so the lead mirrors its in-process spawn
897
+ // bookkeeping — tag maps, worker-index row, statusline route, job meta,
898
+ // SubagentStart — while the worker loop runs in the child.
899
+ let spawned = null;
900
+ const onSpawned = (payload = {}) => {
901
+ if (spawned || !payload.sessionId) return;
902
+ spawned = payload;
903
+ const sessionLike = {
904
+ id: payload.sessionId,
905
+ agent,
906
+ cwd: payload.cwd || workerCwd,
907
+ provider: payload.provider || null,
908
+ model: payload.model || null,
909
+ presetName: payload.presetName || null,
910
+ effort: payload.effort || null,
911
+ fast: payload.fast === true,
912
+ clientHostPid,
913
+ status: 'running',
914
+ };
915
+ cancelReap(payload.sessionId);
916
+ writeAgentStatuslineRoute(payload.sessionId, {
917
+ provider: payload.provider || null,
918
+ model: payload.model || null,
919
+ effort: payload.effort || null,
920
+ fast: payload.fast === true,
921
+ });
922
+ bindTag(tag, sessionLike, {
923
+ agent,
924
+ preset: payload.presetName || null,
925
+ provider: payload.provider || null,
926
+ model: payload.model || null,
927
+ effort: payload.effort || null,
928
+ fast: payload.fast === true,
929
+ status: 'running',
930
+ stage: 'running',
931
+ });
932
+ if (job) {
933
+ mergeJobMeta(job, {
934
+ tag,
935
+ sessionId: payload.sessionId,
936
+ agent,
937
+ preset: payload.presetName || null,
938
+ provider: payload.provider || null,
939
+ model: payload.model || null,
940
+ effort: payload.effort || null,
941
+ fast: payload.fast === true,
942
+ });
943
+ }
944
+ emitSubagentEvent('start', agent, { session_id: payload.sessionId, tag });
945
+ // Cancel raced the shard prep: the task is already terminal on the lead
946
+ // but the child just started its loop — abort it now that the sessionId
947
+ // is known, instead of letting it burn tokens to completion.
948
+ if (job?.status === 'cancelled') {
949
+ try { void shardRequest('cancel', { tag, sessionId: payload.sessionId }).catch(() => {}); } catch { /* best-effort */ }
950
+ }
951
+ };
952
+ let finalStatus = 'error';
953
+ try {
954
+ const response = await shardRequest('spawn', {
955
+ args: {
956
+ agent,
957
+ provider: args.provider,
958
+ model: args.model,
959
+ effort: args.effort,
960
+ fast: args.fast,
961
+ preset: args.preset,
962
+ },
963
+ tag,
964
+ cwd: workerCwd,
965
+ prompt,
966
+ context: args.context || null,
967
+ parentSessionId: clean(context?.callerSessionId || context?.sessionId) || null,
968
+ ownerSessionId: clean(context?.callerSessionId || context?.sessionId) || null,
969
+ clientHostPid,
970
+ }, {
971
+ onProgress: (event, payload) => { if (event === 'spawned') onSpawned(payload); },
972
+ });
973
+ // Same abnormal-empty promotion as the in-process completionValue; a
974
+ // watchdog partial handoff (stallAbort) is a valid completion and is
975
+ // exempt, matching runSpawn's catch path.
976
+ const abnormalError = response?.stallAbort === true
977
+ ? null
978
+ : abnormalEmptyFinishError(response, agent);
979
+ const value = {
980
+ tag,
981
+ sessionId: response.sessionId,
982
+ agent,
983
+ preset: response.presetName || null,
984
+ provider: response.provider || null,
985
+ model: response.model || null,
986
+ effort: response.effort || null,
987
+ fast: response.fast === true,
988
+ content: response.content || '',
989
+ ...(response.stallAbort === true ? { stallAbort: true } : {}),
990
+ ...(abnormalError ? { error: abnormalError } : {}),
991
+ };
992
+ if (job) job._terminalResultValue = value;
993
+ if (value.error) throw new Error(value.error);
994
+ finalStatus = 'idle';
995
+ return value;
996
+ } finally {
997
+ // Terminal bookkeeping parity with runSpawn's finally: worker-index row
998
+ // transitions to idle/error, the terminal-reap timer starts (tombstone +
999
+ // statusline cleanup), and SubagentStop fires. Only after `spawned` —
1000
+ // a prep failure inside the shard bound nothing on the lead.
1001
+ if (spawned?.sessionId) {
1002
+ upsertWorkerSessionDeferred({
1003
+ id: spawned.sessionId,
1004
+ agent,
1005
+ cwd: spawned.cwd || workerCwd,
1006
+ provider: spawned.provider || null,
1007
+ model: spawned.model || null,
1008
+ presetName: spawned.presetName || null,
1009
+ effort: spawned.effort || null,
1010
+ fast: spawned.fast === true,
1011
+ clientHostPid,
1012
+ status: finalStatus,
1013
+ }, tag, {
1014
+ agent,
1015
+ preset: spawned.presetName || null,
1016
+ provider: spawned.provider || null,
1017
+ model: spawned.model || null,
1018
+ effort: spawned.effort || null,
1019
+ fast: spawned.fast === true,
1020
+ status: finalStatus,
1021
+ stage: finalStatus,
1022
+ finishedAt: new Date().toISOString(),
1023
+ });
1024
+ scheduleReap(spawned.sessionId, spawned.provider || null);
1025
+ emitSubagentEvent('stop', agent, { session_id: spawned.sessionId, tag, status: finalStatus });
1026
+ }
1027
+ }
1028
+ }
1029
+
1030
+ async function prepareSpawnInProcess(args, callerCwd = null, context = {}, prepState = null) {
820
1031
  const config = cfgMod.loadConfig();
821
1032
  const agent = normalizeAgentName(args.agent);
822
1033
  if (!agent) throw new Error('agent spawn: agent is required');
@@ -1377,6 +1588,11 @@ export function createStandaloneAgent({
1377
1588
  if (type === 'cancel') return renderResult(close(args, scopedContext));
1378
1589
  if (type === 'close') return renderResult(close(args, scopedContext));
1379
1590
  if (type === 'send') {
1591
+ // Sharded busy worker: queue the follow-up into the shard-owned live
1592
+ // session (in-process busy sessions are handled below via
1593
+ // dispatchToExistingSession; their lead session resolves live).
1594
+ const shardQueued = await tryShardEnqueue(clean(args.tag || args.sessionId), args, scopedContext);
1595
+ if (shardQueued) return shardQueued;
1380
1596
  try {
1381
1597
  const prepared = await prepareSend(args, scopedContext);
1382
1598
  return dispatchToExistingSession(prepared, notifyContext);
@@ -1449,6 +1665,12 @@ export function createStandaloneAgent({
1449
1665
  let respawned = false;
1450
1666
  let spawnArgs = args;
1451
1667
  if (explicitTag) {
1668
+ // Sharded busy worker under this tag: queue into the shard-owned
1669
+ // live session BEFORE the live-reuse resolve — the lead's disk-read
1670
+ // session view would otherwise misroute this into an in-process
1671
+ // send against a shard-owned session file (dual writer).
1672
+ const shardQueued = await tryShardEnqueue(explicitTag, args, scopedContext, { reused: true });
1673
+ if (shardQueued) return shardQueued;
1452
1674
  // Resolve a LIVE same-tag session in this terminal (busy or idle).
1453
1675
  let liveSessionId = null;
1454
1676
  try {