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.
- package/README.md +7 -1
- package/package.json +3 -2
- package/src/defaults/agents.json +12 -0
- package/src/defaults/skills/setup/SKILL.md +44 -12
- package/src/help.mjs +40 -9
- package/src/lib/keychain-cjs.cjs +11 -1
- package/src/rules/agent/43-title-agent.md +22 -0
- package/src/rules/shared/01-tool.md +3 -3
- package/src/runtime/agent/orchestrator/mcp/client.mjs +24 -0
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +84 -3
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +73 -45
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +15 -18
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +135 -9
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +31 -12
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +170 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +45 -6
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/token-bpe.mjs +42 -0
- package/src/runtime/agent/orchestrator/session/token-native.mjs +186 -0
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +15 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +20 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +24 -0
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +286 -0
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +152 -18
- package/src/runtime/agent/orchestrator/tools/token-binary-fetcher.mjs +201 -0
- package/src/runtime/agent/orchestrator/tools/token-manifest.json +5 -0
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +3 -3
- package/src/runtime/media/adapters/gemini-video.mjs +1 -1
- package/src/runtime/media/renditions.mjs +1 -1
- package/src/runtime/media/store.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +25 -14
- package/src/runtime/shared/automation-attachments.mjs +3 -3
- package/src/runtime/shared/child-guardian.mjs +15 -5
- package/src/runtime/shared/child-spawn-gate.mjs +6 -12
- package/src/runtime/shared/resource-admission.mjs +7 -3
- package/src/runtime/shared/turn-snapshot.mjs +0 -3
- package/src/session-runtime/lifecycle-api.mjs +34 -9
- package/src/session-runtime/prewarm.mjs +35 -0
- package/src/session-runtime/remote-control.mjs +8 -47
- package/src/session-runtime/remote-transcript.mjs +3 -7
- package/src/session-runtime/route-preparation.mjs +24 -0
- package/src/session-runtime/runtime-core.mjs +37 -85
- package/src/session-runtime/session-turn-api.mjs +9 -2
- package/src/session-runtime/workflow-agents-api.mjs +3 -0
- package/src/standalone/agent-shard/shard-child.mjs +300 -0
- package/src/standalone/agent-shard/shard-pool.mjs +442 -0
- package/src/standalone/agent-tool.mjs +222 -0
- package/src/standalone/channel-daemon-transport.mjs +126 -185
- package/src/standalone/channel-daemon.mjs +9 -10
- package/src/tui/App.jsx +16 -9
- package/src/tui/app/channel-pickers.mjs +3 -8
- package/src/tui/app/route-pickers.mjs +117 -152
- package/src/tui/app/settings-picker.mjs +90 -89
- package/src/tui/app/slash-commands.mjs +7 -7
- package/src/tui/app/slash-dispatch.mjs +0 -45
- package/src/tui/components/StatusLine.jsx +1 -1
- package/src/tui/components/tool-execution/ResultBody.jsx +1 -1
- package/src/tui/dist/index.mjs +243 -323
- package/src/tui/engine/session-api-ext.mjs +24 -9
- package/src/tui/engine.mjs +7 -1
- package/src/tui/figures.mjs +0 -1
- package/src/ui/statusline-format.mjs +0 -1
- package/src/hooks/lib/permission-evaluator.cjs +0 -24
- package/src/lib/config-cjs.cjs +0 -61
- package/src/runtime/shared/launcher-control.mjs +0 -258
- 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 {
|