mixdog 1.4.0 → 1.4.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.
- package/package.json +1 -1
- package/scripts/check-voice-runtime-inputs.mjs +25 -0
- package/scripts/gate-local.mjs +12 -3
- package/scripts/latest-guard.mjs +21 -9
- package/scripts/lib/isolated-test-env.mjs +10 -5
- package/scripts/lib/run-node-tests.mjs +37 -37
- package/scripts/lib/scoped-temp.d.mts +16 -0
- package/scripts/lib/scoped-temp.mjs +123 -0
- package/scripts/lib/stale-temp.mjs +44 -0
- package/scripts/post-release-check.mjs +1 -6
- package/scripts/pptx-kit-index.mjs +17 -4
- package/scripts/suite-health.mjs +10 -1
- package/scripts/test-data-dir.mjs +10 -7
- package/src/app.mjs +4 -0
- package/src/defaults/shipped-history.json +5 -0
- package/src/defaults/skills/browser-use/SKILL.md +6 -4
- package/src/defaults/skills/computer-use/SKILL.md +4 -2
- package/src/lib/rules-builder.cjs +29 -12
- package/src/output-styles/common.md +2 -2
- package/src/output-styles/simple.md +1 -1
- package/src/rules/agent/AGENT.md +5 -4
- package/src/rules/lead/LEAD.md +10 -5
- package/src/rules/shared/10-tool-workflow.md +3 -1
- package/src/rules/shared/45-destructive.md +0 -2
- package/src/rules/shared/50-execution.md +2 -0
- package/src/rules/shared/70-delivery.md +7 -9
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -6
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +1 -1
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +3 -1
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +7 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +210 -20
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
- package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +136 -5
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +20 -12
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +48 -22
- package/src/runtime/agent/orchestrator/providers/registry.mjs +12 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +41 -13
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +7 -6
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +7 -3
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +8 -2
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +160 -36
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +163 -22
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +132 -17
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
- package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +90 -22
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +33 -3
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +39 -21
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +27 -9
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +61 -6
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -8
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +12 -5
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +30 -7
- package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +37 -8
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +9 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +61 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -20
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +31 -5
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +22 -8
- package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +26 -4
- package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
- package/src/runtime/channels/lib/whisper-port.mjs +10 -1
- package/src/runtime/computer-bridge/action-schema.mjs +2 -2
- package/src/runtime/computer-bridge/core-actions.mjs +2 -2
- package/src/runtime/computer-bridge/error-recovery.mjs +5 -4
- package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
- package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
- package/src/runtime/local-provider/server-process.mjs +2 -1
- package/src/runtime/media/adapters/gemini-video.mjs +4 -1
- package/src/runtime/media/adapters/xai-media.mjs +2 -1
- package/src/runtime/memory/lib/embedding-worker.mjs +6 -3
- package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
- package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
- package/src/runtime/office/design/design-art-direction.mjs +29 -27
- package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
- package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
- package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
- package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
- package/src/runtime/office/quality/assurance-structure-docx.mjs +3 -1
- package/src/runtime/office/quality/design-review.mjs +1 -5
- package/src/runtime/office/quality/document-brief.mjs +1 -5
- package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
- package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
- package/src/runtime/shared/bot-owned-work.mjs +56 -0
- package/src/runtime/shared/defaults-separation.mjs +1 -4
- package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
- package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
- package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
- package/src/runtime/shared/handoff-artifacts.mjs +18 -8
- package/src/runtime/shared/idle-auto-clear-gate.mjs +29 -7
- package/src/runtime/shared/llm/cost.mjs +5 -1
- package/src/runtime/shared/loopback-listener.mjs +25 -2
- package/src/runtime/shared/model-route-chain.d.mts +7 -2
- package/src/runtime/shared/model-route-chain.mjs +22 -4
- package/src/runtime/shared/open-url.mjs +10 -0
- package/src/runtime/shared/session-activity-signal.mjs +14 -0
- package/src/runtime/shared/session-scratch.mjs +37 -0
- package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
- package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +3 -0
- package/src/runtime/shared/tool-work-units.mjs +2 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
- package/src/runtime/shared/worktrees/contract.mjs +4 -1
- package/src/runtime/shared/worktrees/git.mjs +15 -0
- package/src/runtime/shared/worktrees/manager.mjs +87 -38
- package/src/runtime/shared/worktrees/test-support.mjs +6 -10
- package/src/runtime/visualize-bridge/client.mjs +2 -0
- package/src/session-runtime/agent-home-delivery.mjs +41 -0
- package/src/session-runtime/agent-task-sessions.mjs +152 -63
- package/src/session-runtime/agent-tool-routing.mjs +8 -41
- package/src/session-runtime/boot/tools.mjs +5 -1
- package/src/session-runtime/bot-status.mjs +12 -6
- package/src/session-runtime/bot-target.mjs +38 -0
- package/src/session-runtime/channel-config-api.mjs +6 -1
- package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
- package/src/session-runtime/conversation-import/discovery.mjs +271 -25
- package/src/session-runtime/conversation-import/service.mjs +26 -5
- package/src/session-runtime/handoff-attachments.mjs +82 -31
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +12 -13
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +1 -0
- package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +8 -2
- package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
- package/src/session-runtime/lifecycle/session-catalog.mjs +9 -1
- package/src/session-runtime/model-route/set-route.mjs +11 -2
- package/src/session-runtime/provider-catalog-cache.mjs +82 -36
- package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
- package/src/session-runtime/provider-models.mjs +4 -2
- package/src/session-runtime/resource-recall-api.mjs +6 -1
- package/src/session-runtime/runtime-feature-gates.mjs +5 -1
- package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
- package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
- package/src/session-runtime/services/agent-tool/render.mjs +71 -17
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +6 -2
- package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
- package/src/session-runtime/services/provider-admin.mjs +2 -1
- package/src/session-runtime/tool-policy/policy-stage.mjs +1 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -4
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
- package/src/session-runtime/workspace/transitions.mjs +25 -0
- package/src/session-runtime/workspace/worktree-tool.mjs +26 -9
- package/src/standalone/daemon-session-control.mjs +18 -2
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
- package/src/standalone/session-service/session-calls/session-action.mjs +10 -3
- package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
- package/src/tui/app/app-view/floating-panel.jsx +7 -4
- package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
- package/src/tui/app/model-picker/route-selection.mjs +5 -6
- package/src/tui/app/provider-setup/oauth-actions.mjs +38 -20
- package/src/tui/app/provider-setup/provider-items.mjs +8 -2
- package/src/tui/app/provider-setup-picker.mjs +9 -5
- package/src/tui/components/QuestionPanel.jsx +13 -4
- package/src/tui/dist/index.mjs +75 -28
- package/src/tui/session/session-flow/queue/take-entries.mjs +11 -0
- package/src/ui/statusline-agents.mjs +1 -3
|
@@ -66,15 +66,20 @@ export function createAgentExecute({
|
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
// Targetless status is the same compact overview as list.
|
|
69
|
-
return renderResult(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
return renderResult(
|
|
70
|
+
{
|
|
71
|
+
// Idle workers stay open for follow-ups; the roster counts them and
|
|
72
|
+
// lists a bounded tail (or the `tag` matches) so they can be closed.
|
|
73
|
+
workers: views.list({
|
|
74
|
+
scanSessions: registry.wantsSessionScan(args),
|
|
75
|
+
context: scopedContext,
|
|
76
|
+
includeIdle: true,
|
|
77
|
+
includeClosed: true,
|
|
78
|
+
}),
|
|
79
|
+
jobs: views.listJobs(scopedContext),
|
|
80
|
+
},
|
|
81
|
+
{ listFilter: clean(args.tag) }
|
|
82
|
+
);
|
|
78
83
|
case 'read':
|
|
79
84
|
return await readJob(args, scopedContext);
|
|
80
85
|
case 'cleanup':
|
|
@@ -27,8 +27,12 @@ export function isActiveWorkerRow(row = {}) {
|
|
|
27
27
|
|
|
28
28
|
// A finished worker that is still open: it takes follow-ups and can be closed.
|
|
29
29
|
function isOpenIdleRow(row = {}) {
|
|
30
|
-
const status = String(row.status ?? '')
|
|
31
|
-
|
|
30
|
+
const status = String(row.status ?? '')
|
|
31
|
+
.trim()
|
|
32
|
+
.toLowerCase();
|
|
33
|
+
const stage = String(row.stage ?? '')
|
|
34
|
+
.trim()
|
|
35
|
+
.toLowerCase();
|
|
32
36
|
if (CANCELLED_WORKER_STATUS.test(stage) || CANCELLED_WORKER_STATUS.test(status)) return false;
|
|
33
37
|
return (status || stage) === 'idle';
|
|
34
38
|
}
|
|
@@ -86,7 +90,13 @@ function agentRow({ tag, session, runtime, progress, now, indexRow = null }) {
|
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
export function createAgentList({ mgr, refreshTagsFromSessions, agentSessionEntries, progress }) {
|
|
89
|
-
return function list({
|
|
93
|
+
return function list({
|
|
94
|
+
scanSessions = false,
|
|
95
|
+
context = {},
|
|
96
|
+
includeTerminal = false,
|
|
97
|
+
includeIdle = false,
|
|
98
|
+
includeClosed = false,
|
|
99
|
+
} = {}) {
|
|
90
100
|
refreshTagsFromSessions({ scanSessions, context });
|
|
91
101
|
const now = Date.now();
|
|
92
102
|
const rows = [];
|
|
@@ -98,7 +108,13 @@ export function createAgentList({ mgr, refreshTagsFromSessions, agentSessionEntr
|
|
|
98
108
|
// 2 s) would otherwise read every worker it ever ran, whole transcripts
|
|
99
109
|
// on first sight, synchronously on the daemon loop.
|
|
100
110
|
const state = rowState(session, runtime);
|
|
101
|
-
if (
|
|
111
|
+
if (
|
|
112
|
+
!includeTerminal &&
|
|
113
|
+
!isActiveWorkerRow(state) &&
|
|
114
|
+
!(includeIdle && isOpenIdleRow(state)) &&
|
|
115
|
+
!(includeClosed && state.status === 'closed')
|
|
116
|
+
)
|
|
117
|
+
continue;
|
|
102
118
|
rows.push(
|
|
103
119
|
agentRow({
|
|
104
120
|
tag,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// One-off model selection for a spawn the user explicitly pinned to a model.
|
|
2
2
|
// The agent's preset route stays the default; this only runs when `model` is
|
|
3
|
-
// passed. The query is matched against the
|
|
4
|
-
// then exact display name, then every query
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// passed. The query is matched against the quick/cached provider catalog —
|
|
4
|
+
// literal id, then normalized id, then exact display name, then every query
|
|
5
|
+
// word — and a catalog miss retries once on a catalog refreshed for the
|
|
6
|
+
// requested provider (concurrent misses share that refresh). One match runs;
|
|
7
|
+
// several or none fail with candidates instead of substituting another model.
|
|
7
8
|
import { clean } from './helpers.mjs';
|
|
8
9
|
|
|
9
10
|
const CANDIDATE_LIMIT = 8;
|
|
@@ -20,7 +21,7 @@ const terms = (value) =>
|
|
|
20
21
|
.filter(Boolean);
|
|
21
22
|
const label = (row) => `${row.provider}/${row.id}`;
|
|
22
23
|
|
|
23
|
-
function providerRows(rows, provider) {
|
|
24
|
+
export function providerRows(rows, provider) {
|
|
24
25
|
if (!provider) return rows;
|
|
25
26
|
const wanted = norm(provider);
|
|
26
27
|
const exact = rows.filter((row) => norm(row.provider) === wanted);
|
|
@@ -33,6 +34,8 @@ function matchModels(rows, query) {
|
|
|
33
34
|
// Nothing to match on ("???"): `[].every` would accept every row.
|
|
34
35
|
if (!words.length) return [];
|
|
35
36
|
const tiers = [
|
|
37
|
+
// A literal id wins over its normalized look-alikes (`foo-bar` vs `foo_bar`).
|
|
38
|
+
(row) => row.id === query,
|
|
36
39
|
(row) => norm(row.id) === wanted,
|
|
37
40
|
(row) => norm(row.display) === wanted,
|
|
38
41
|
(row) => words.every((word) => `${norm(row.id)} ${norm(row.display)}`.includes(word)),
|
|
@@ -49,6 +52,34 @@ function candidates(rows) {
|
|
|
49
52
|
return rows.length > CANDIDATE_LIMIT ? `${shown}, … (${rows.length} total)` : shown;
|
|
50
53
|
}
|
|
51
54
|
|
|
55
|
+
// A quick read answered from the loaded catalog marks every row complete.
|
|
56
|
+
// Otherwise a hit stands only when it is a literal id of the exact provider the
|
|
57
|
+
// caller named, which the full catalog would pick the same way.
|
|
58
|
+
function quickAnswerHolds(rows, hits, model, provider) {
|
|
59
|
+
if (rows.length > 0 && rows.every((row) => row.catalogComplete === true)) return true;
|
|
60
|
+
const wanted = norm(provider);
|
|
61
|
+
return hits.length > 0 && wanted !== '' && hits.every((row) => row.id === model && norm(row.provider) === wanted);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The forced refresh in flight per catalog reader and provider scope: concurrent
|
|
65
|
+
// misses share it instead of each refreshing.
|
|
66
|
+
const refreshing = new WeakMap();
|
|
67
|
+
|
|
68
|
+
// One forced refresh, scoped to the requested provider when one is given.
|
|
69
|
+
function refreshedRows(listProviderModels, provider) {
|
|
70
|
+
if (!refreshing.has(listProviderModels)) refreshing.set(listProviderModels, new Map());
|
|
71
|
+
const inflight = refreshing.get(listProviderModels);
|
|
72
|
+
const scope = norm(provider);
|
|
73
|
+
if (!inflight.has(scope)) {
|
|
74
|
+
const request = listProviderModels({ force: true, ...(provider ? { provider } : {}) });
|
|
75
|
+
inflight.set(
|
|
76
|
+
scope,
|
|
77
|
+
request.finally(() => inflight.delete(scope))
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return inflight.get(scope);
|
|
81
|
+
}
|
|
82
|
+
|
|
52
83
|
/** Resolve args.provider/args.model to one exact catalog route. Without a
|
|
53
84
|
* catalog (headless/daemon surfaces) exact provider + model pass through. */
|
|
54
85
|
export async function resolveRequestedModel(args, listProviderModels) {
|
|
@@ -64,10 +95,20 @@ export async function resolveRequestedModel(args, listProviderModels) {
|
|
|
64
95
|
if (!terms(model).length) throw new Error(`agent spawn: model "${model}" for provider "${provider}" not found`);
|
|
65
96
|
return args;
|
|
66
97
|
}
|
|
67
|
-
|
|
98
|
+
// The quick/cached catalog answers first. A loaded catalog is final, so its
|
|
99
|
+
// hit never waits on a provider; an incomplete one (cold or partial) may
|
|
100
|
+
// lack the model that wins, or one that makes the name ambiguous, so only a
|
|
101
|
+
// literal id inside an exact, explicitly named provider is taken from it.
|
|
102
|
+
// Anything else waits for the full catalog.
|
|
103
|
+
const quickRows = await listProviderModels({ quick: true });
|
|
104
|
+
let pool = providerRows(quickRows, provider);
|
|
68
105
|
let hits = matchModels(pool, model);
|
|
106
|
+
if (!quickAnswerHolds(quickRows, hits, model, provider)) {
|
|
107
|
+
pool = providerRows(await listProviderModels(), provider);
|
|
108
|
+
hits = matchModels(pool, model);
|
|
109
|
+
}
|
|
69
110
|
if (!hits.length) {
|
|
70
|
-
pool = providerRows(await listProviderModels
|
|
111
|
+
pool = providerRows(await refreshedRows(listProviderModels, provider), provider);
|
|
71
112
|
hits = matchModels(pool, model);
|
|
72
113
|
}
|
|
73
114
|
if (hits.length === 1) return { ...args, provider: hits[0].provider, model: hits[0].id };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Pure result-rendering and finish-classification helpers.
|
|
2
2
|
import { oneLine } from '../../../runtime/shared/clean.mjs';
|
|
3
|
+
import { isActiveWorkerRow } from './job-views/agent-list.mjs';
|
|
4
|
+
import { isTerminalJobStatus } from './job-views/session-progress.mjs';
|
|
3
5
|
import { compactIso, elapsedFromStamps, stripFinalAnswerWrapper } from './helpers.mjs';
|
|
4
6
|
|
|
5
7
|
// A worker that gets truncated mid-synthesis or produces an empty terminal
|
|
@@ -32,25 +34,77 @@ export function abnormalEmptyFinishError(result, agent) {
|
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
const ROSTER_TAIL = 5;
|
|
38
|
+
const ROSTER_MATCH_CAP = 25;
|
|
39
|
+
|
|
40
|
+
function workerLine(worker) {
|
|
41
|
+
const status = worker.status || 'idle';
|
|
42
|
+
const stage = worker.worker_stage || worker.stage;
|
|
43
|
+
const progress = oneLine(worker.last_progress);
|
|
44
|
+
const summary = progress.length > 60 ? `${progress.slice(0, 59)}…` : progress;
|
|
45
|
+
return `- ${worker.tag} ${status}${stage && stage !== status ? `/${stage}` : ''}${summary ? ` ${summary}` : ''}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function jobLine(job) {
|
|
49
|
+
const error = job.status === 'failed' ? oneLine(job.error) : '';
|
|
50
|
+
return `- ${job.task_id} ${job.type} ${job.status} ${job.tag || '-'}${error ? ` error=${error}` : ''}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const stamp = (row) => String(row.updatedAt || row.lastUsedAt || row.finishedAt || row.startedAt || '');
|
|
54
|
+
|
|
55
|
+
// Inactive rows: newest first (stable for rows without stamps).
|
|
56
|
+
const newestFirst = (rows) => rows.slice().sort((a, b) => stamp(b).localeCompare(stamp(a)));
|
|
57
|
+
|
|
58
|
+
// Inactive rows collapse to a count plus a short tail. A `tag` filter lists the
|
|
59
|
+
// matching inactive rows (bounded) so a specific one can still be found.
|
|
60
|
+
function inactiveSection(label, rows, render, tagOf, filter) {
|
|
61
|
+
if (rows.length === 0) return [];
|
|
62
|
+
const needle = filter.toLowerCase();
|
|
63
|
+
const matched = needle
|
|
64
|
+
? rows.filter((row) =>
|
|
65
|
+
String(tagOf(row) ?? '')
|
|
66
|
+
.toLowerCase()
|
|
67
|
+
.includes(needle)
|
|
68
|
+
)
|
|
69
|
+
: null;
|
|
70
|
+
const shown = newestFirst(matched ?? rows).slice(0, matched ? ROSTER_MATCH_CAP : ROSTER_TAIL);
|
|
71
|
+
const shownFilter = filter.replace(/\s+/g, ' ');
|
|
72
|
+
const head = matched
|
|
73
|
+
? `${label}: ${rows.length} (${matched.length} match tag "${shownFilter}", showing ${shown.length})`
|
|
74
|
+
: `${label}: ${rows.length} (latest ${shown.length}; pass tag=<text> to find one)`;
|
|
75
|
+
// Bulleted: a non-bullet line would make the list read as an authored response.
|
|
76
|
+
return [`- ${head}`, ...shown.map(render)];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// The /agents roster: active workers and running tasks in full; idle/closed
|
|
80
|
+
// workers and finished tasks as counts with a bounded tail.
|
|
81
|
+
function renderRoster(value, filter = '') {
|
|
37
82
|
const workers = Array.isArray(value.workers) ? value.workers : [];
|
|
38
83
|
const jobs = Array.isArray(value.jobs) ? value.jobs : [];
|
|
39
84
|
const lines = [`agents: ${workers.length} · tasks: ${jobs.length}`];
|
|
85
|
+
const isActive = (worker) => Boolean(worker.awaitingUser) || isActiveWorkerRow(worker);
|
|
86
|
+
const isRunning = (job) => !isTerminalJobStatus(job.status);
|
|
40
87
|
// Keep list bullets: agent cards distinguish these rows from authored responses.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
88
|
+
lines.push(...workers.filter(isActive).map(workerLine));
|
|
89
|
+
lines.push(...jobs.filter(isRunning).map(jobLine));
|
|
90
|
+
lines.push(
|
|
91
|
+
...inactiveSection(
|
|
92
|
+
'idle/closed agents',
|
|
93
|
+
workers.filter((w) => !isActive(w)),
|
|
94
|
+
workerLine,
|
|
95
|
+
(w) => w.tag,
|
|
96
|
+
filter
|
|
97
|
+
)
|
|
98
|
+
);
|
|
99
|
+
lines.push(
|
|
100
|
+
...inactiveSection(
|
|
101
|
+
'finished tasks',
|
|
102
|
+
jobs.filter((j) => !isRunning(j)),
|
|
103
|
+
jobLine,
|
|
104
|
+
(j) => j.tag || j.task_id,
|
|
105
|
+
filter
|
|
106
|
+
)
|
|
107
|
+
);
|
|
54
108
|
if (workers.length === 0 && jobs.length === 0) lines.push('(no agents or tasks)');
|
|
55
109
|
return lines.join('\n');
|
|
56
110
|
}
|
|
@@ -151,11 +205,11 @@ function renderContent(value) {
|
|
|
151
205
|
return `${header}\n${stripFinalAnswerWrapper(value.content)}`;
|
|
152
206
|
}
|
|
153
207
|
|
|
154
|
-
export function renderResult(value, { includeDiagnostics = false } = {}) {
|
|
208
|
+
export function renderResult(value, { includeDiagnostics = false, listFilter = '' } = {}) {
|
|
155
209
|
if (value === undefined || value === null) return '';
|
|
156
210
|
if (typeof value === 'string') return value;
|
|
157
211
|
if (value && typeof value === 'object') {
|
|
158
|
-
if (Array.isArray(value.workers) || Array.isArray(value.jobs)) return renderRoster(value);
|
|
212
|
+
if (Array.isArray(value.workers) || Array.isArray(value.jobs)) return renderRoster(value, listFilter);
|
|
159
213
|
// close-flow results carry `closed` plus task_id: render the close outcome.
|
|
160
214
|
// close-flow results carry `closed` plus task_id: render the close outcome.
|
|
161
215
|
if (value.closed !== undefined) return renderClosed(value);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { configuredAgentRouteCandidates } from '../../../runtime/shared/agent-route-config.mjs';
|
|
2
|
+
import { executionFallbacks } from '../../../runtime/shared/model-route-chain.mjs';
|
|
2
3
|
import {
|
|
3
4
|
agentPresetName,
|
|
4
5
|
clean,
|
|
@@ -29,8 +30,11 @@ export function resolveAgentSpawnPreset(config, args = {}) {
|
|
|
29
30
|
const agentName = normalizeAgentName(args.agent);
|
|
30
31
|
// The agent's stored route: config.agents, or a store bot's own folder.
|
|
31
32
|
const candidates = !clean(args.preset) ? configuredAgentRouteCandidates(config, agentName) : [];
|
|
32
|
-
const
|
|
33
|
-
const
|
|
33
|
+
const storedCandidate = candidates.find((candidate) => normalizeAgentRoute(candidate));
|
|
34
|
+
const agentRoute = storedCandidate ? normalizeAgentRoute(storedCandidate) : null;
|
|
35
|
+
const storedFallbacks = agentRoute
|
|
36
|
+
? executionFallbacks({ ...agentRoute, fallbacks: storedCandidate.fallbacks })
|
|
37
|
+
: undefined;
|
|
34
38
|
// Where the spawned session reads its fallback chain at each hop
|
|
35
39
|
// (route-fallback-chain.mjs): this agent's route, else Main's.
|
|
36
40
|
const fallbackSource = { agent: agentName };
|
|
@@ -28,7 +28,7 @@ export const AGENT_TOOL = {
|
|
|
28
28
|
agentHidden: false,
|
|
29
29
|
},
|
|
30
30
|
description:
|
|
31
|
-
'Run scoped agent work as background tasks. spawn/send return task_id immediately. Reuse one tag for one scope for its whole lifetime; give distinct tags only to independent scopes. Never mint a new tag because a session expired or was lost — same-tag spawn respawns it with the full brief.
|
|
31
|
+
'Run scoped agent work as background tasks. spawn/send return task_id immediately. Reuse one tag for one scope for its whole lifetime; give distinct tags only to independent scopes. Never mint a new tag because a session expired or was lost — same-tag spawn respawns it with the full brief. Completion notifications arrive automatically; use status/read only for manual recovery. Close finished agents yourself.',
|
|
32
32
|
inputSchema: {
|
|
33
33
|
type: 'object',
|
|
34
34
|
properties: {
|
|
@@ -36,21 +36,21 @@ export const AGENT_TOOL = {
|
|
|
36
36
|
type: 'string',
|
|
37
37
|
enum: ['spawn', 'send', 'list', 'close', 'cancel', 'status', 'read', 'cleanup'],
|
|
38
38
|
description:
|
|
39
|
-
'Required action. New spawn requires agent and one of prompt/message/file; send requires tag and one of prompt/message/file; read/status/close/cancel require task_id or tag; list/cleanup require no target.',
|
|
39
|
+
'Required action. New spawn requires agent and one of prompt/message/file; send requires tag and one of prompt/message/file; read/status/close/cancel require task_id or tag; list/cleanup require no target. list shows active agents and running tasks in full; idle/closed agents and finished tasks are counts plus a short latest tail (list with tag=<text> shows matching ones: case-insensitive substring, at most 25 per section).',
|
|
40
40
|
},
|
|
41
|
-
task_id: { type: 'string', description: 'Task ID returned by spawn/send
|
|
41
|
+
task_id: { type: 'string', description: 'Task ID returned by spawn/send.' },
|
|
42
42
|
agent: {
|
|
43
43
|
type: 'string',
|
|
44
|
-
description: 'Workflow agent id
|
|
44
|
+
description: 'Workflow agent id; same-tag reuse can inherit it.',
|
|
45
45
|
},
|
|
46
46
|
tag: {
|
|
47
47
|
type: 'string',
|
|
48
48
|
description:
|
|
49
|
-
'Stable scope handle
|
|
49
|
+
'Stable scope handle; never derive a variant by appending a number or suffix. spawn generates one when omitted.',
|
|
50
50
|
},
|
|
51
|
-
prompt: { type: 'string', description: 'Spawn task brief
|
|
52
|
-
message: { type: 'string', description: 'Send follow-up
|
|
53
|
-
file: { type: 'string', description: 'Path to task text;
|
|
51
|
+
prompt: { type: 'string', description: 'Spawn task brief.' },
|
|
52
|
+
message: { type: 'string', description: 'Send follow-up.' },
|
|
53
|
+
file: { type: 'string', description: 'Path to task text; used instead of prompt/message.' },
|
|
54
54
|
cwd: { type: 'string', description: 'Project directory for a new or respawned agent.' },
|
|
55
55
|
context: { type: 'string', description: 'Extra context for the spawn/send turn.' },
|
|
56
56
|
attachments: {
|
|
@@ -63,7 +63,7 @@ export const AGENT_TOOL = {
|
|
|
63
63
|
model: {
|
|
64
64
|
type: 'string',
|
|
65
65
|
description:
|
|
66
|
-
"Spawn only, and only when the user explicitly names a model: runs this one spawn on it instead of the agent's preset route
|
|
66
|
+
"Spawn only, and only when the user explicitly names a model: runs this one spawn on it instead of the agent's preset route. Id, display name, or words of it; ambiguous or unknown names fail with candidates. The agent still supplies role and permissions.",
|
|
67
67
|
},
|
|
68
68
|
provider: { type: 'string', description: 'Narrows model to one provider; requires model.' },
|
|
69
69
|
effort: { type: 'string', description: 'Reasoning effort for an explicit model.' },
|
|
@@ -1179,7 +1179,8 @@ export async function saveCustomProvider(cfgMod, rawInput) {
|
|
|
1179
1179
|
export async function saveCustomProviderKey(cfgMod, rawId, rawKey) {
|
|
1180
1180
|
requireLocalProviderManagement();
|
|
1181
1181
|
const id = String(rawId || '').trim();
|
|
1182
|
-
if (typeof rawKey !== 'string' || rawKey.length > CUSTOM_API_KEY_MAX)
|
|
1182
|
+
if (typeof rawKey !== 'string' || rawKey.length > CUSTOM_API_KEY_MAX)
|
|
1183
|
+
throw new TypeError('custom provider apiKey is invalid');
|
|
1183
1184
|
const key = rawKey.trim();
|
|
1184
1185
|
if (!key) throw new Error('API key is required');
|
|
1185
1186
|
const takesStoredKey = () => {
|
|
@@ -135,6 +135,7 @@ export function createPolicyStage({
|
|
|
135
135
|
: _buildBaseRules({
|
|
136
136
|
omitTools: [...denied, unusedModelEditToolName(getRoute()?.model)],
|
|
137
137
|
allowTools: schemaAllowedTools,
|
|
138
|
+
ownerIsAgent,
|
|
138
139
|
provider: getRoute()?.provider,
|
|
139
140
|
model: getRoute()?.model,
|
|
140
141
|
});
|
|
@@ -19,7 +19,11 @@ import {
|
|
|
19
19
|
writeJsonAtomicSync,
|
|
20
20
|
} from '../../runtime/shared/atomic-file.mjs';
|
|
21
21
|
import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
agentEntryFile,
|
|
24
|
+
agentEntryPath,
|
|
25
|
+
parseManifestText,
|
|
26
|
+
} from '../../runtime/agent/orchestrator/runtime-core/workflow/agents.mjs';
|
|
23
27
|
import { hasOwn } from '../../runtime/shared/object.mjs';
|
|
24
28
|
import { serializeFrontmatterDoc } from '../../runtime/shared/markdown-frontmatter.mjs';
|
|
25
29
|
import { isHiddenAgent } from '../../runtime/agent/orchestrator/internal-agents.mjs';
|
|
@@ -22,7 +22,11 @@ import {
|
|
|
22
22
|
|
|
23
23
|
/** Mixy's runtime id (agent-home.mjs MIXY_AGENT_ID). */
|
|
24
24
|
const MIXY_RUNTIME_ID = 'lead';
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
canonicalRouteProvider,
|
|
27
|
+
routeChain,
|
|
28
|
+
validateRouteChainProviders,
|
|
29
|
+
} from '../../runtime/shared/model-route-chain.mjs';
|
|
26
30
|
|
|
27
31
|
// Agent route persistence. Route-scope isolation: an agent route owns its
|
|
28
32
|
// model AND its effort/fast, stored in config.agents[<id>]. The shared
|
|
@@ -183,18 +187,25 @@ export function createAgentRouteApi(deps) {
|
|
|
183
187
|
}
|
|
184
188
|
if (!clean(requested.provider)) return clearOverride(id);
|
|
185
189
|
if (!clean(requested.model)) throw new Error('agent route requires provider and model');
|
|
190
|
+
// An explicit list is validated; the stored one may hold a fallback on the
|
|
191
|
+
// picked model's provider, which stays saved untouched and inactive.
|
|
192
|
+
const explicitFallbacks = hasOwn(requested, 'fallbacks');
|
|
186
193
|
const chain = routeChain({
|
|
187
194
|
...requested,
|
|
188
|
-
fallbacks:
|
|
195
|
+
fallbacks: explicitFallbacks ? requested.fallbacks : stored.fallbacks,
|
|
189
196
|
});
|
|
190
|
-
validateRouteChainProviders(chain);
|
|
197
|
+
validateRouteChainProviders(chain, { allowDuplicates: !explicitFallbacks });
|
|
191
198
|
const storedChain = routeChain(stored);
|
|
192
199
|
const resolved = [];
|
|
200
|
+
const usedProviders = new Set();
|
|
193
201
|
for (const entry of chain) {
|
|
202
|
+
const provider = canonicalRouteProvider(entry.provider);
|
|
203
|
+
const inactive = usedProviders.has(provider);
|
|
204
|
+
usedProviders.add(provider);
|
|
194
205
|
const previous = storedChain.find(
|
|
195
206
|
(candidate) => candidate.provider === entry.provider && candidate.model === entry.model
|
|
196
207
|
);
|
|
197
|
-
resolved.push(await resolveChainEntry(deps, entry, previous || {}));
|
|
208
|
+
resolved.push(inactive ? entry : await resolveChainEntry(deps, entry, previous || {}));
|
|
198
209
|
}
|
|
199
210
|
const [primary, ...fallbacks] = resolved;
|
|
200
211
|
const routeToSave = normalizeWorkflowRoute({ ...primary, fallbacks });
|
|
@@ -20,7 +20,7 @@ import { resolveDataDir } from './shared.mjs';
|
|
|
20
20
|
function applyWorkflowSlots(workflowInput, draft) {
|
|
21
21
|
for (const slot of WORKFLOW_ROUTE_SLOTS) {
|
|
22
22
|
if (!hasOwn(workflowInput, slot)) continue;
|
|
23
|
-
const normalized = normalizeWorkflowRoute(workflowInput[slot]);
|
|
23
|
+
const normalized = normalizeWorkflowRoute(workflowInput[slot], {}, { strict: true });
|
|
24
24
|
if (slot === 'lead') {
|
|
25
25
|
if (!normalized) continue;
|
|
26
26
|
draft.presets = upsertWorkflowPreset(draft.presets, 'lead', normalized);
|
|
@@ -40,7 +40,7 @@ function applyAgentRouteInput(agentInput, agentRoutes, configurableIds) {
|
|
|
40
40
|
const nextAgents = { ...agentRoutes };
|
|
41
41
|
for (const id of configurableIds) {
|
|
42
42
|
if (!hasOwn(agentInput, id)) continue;
|
|
43
|
-
const routeToSave = normalizeWorkflowRoute(agentInput[id]);
|
|
43
|
+
const routeToSave = normalizeWorkflowRoute(agentInput[id], {}, { strict: true });
|
|
44
44
|
if (routeToSave) nextAgents[id] = routeToSave;
|
|
45
45
|
else delete nextAgents[id];
|
|
46
46
|
}
|
|
@@ -67,7 +67,7 @@ export function createOnboardingApi(deps) {
|
|
|
67
67
|
const config = getConfig();
|
|
68
68
|
const nextConfig = { ...config };
|
|
69
69
|
const defaultRoute = hasOwn(payload, 'defaultRoute')
|
|
70
|
-
? normalizeWorkflowRoute(payload.defaultRoute, getRoute())
|
|
70
|
+
? normalizeWorkflowRoute(payload.defaultRoute, getRoute(), { strict: true })
|
|
71
71
|
: null;
|
|
72
72
|
const draft = {
|
|
73
73
|
presets: Array.isArray(nextConfig.presets) ? nextConfig.presets.slice() : [],
|
|
@@ -752,6 +752,30 @@ export function createWorkspaceTransitions({
|
|
|
752
752
|
return maintenance.pruneWorktrees({ ...(await maintenanceScope()), repoCommonDir });
|
|
753
753
|
}
|
|
754
754
|
|
|
755
|
+
/**
|
|
756
|
+
* Remove managed worktree `entryId` whose owner is gone (listed `ownerLive:
|
|
757
|
+
* false, removable: true`), on an explicit request: no owner lane exists to
|
|
758
|
+
* do it. No stored or live session may still use the folder; the manager
|
|
759
|
+
* reads the retention again under the repository lock (an owner that came
|
|
760
|
+
* back refuses) and keeps its guards: dirty or unmerged work needs `force`,
|
|
761
|
+
* other sessions' references and retained recovery data refuse, and only
|
|
762
|
+
* the managed checkout is deleted.
|
|
763
|
+
*/
|
|
764
|
+
async function removeOrphanWorktree({ entryId, force = false } = {}) {
|
|
765
|
+
const row = (await listWorktrees()).find((item) => item.id === entryId);
|
|
766
|
+
if (!row) throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
|
|
767
|
+
const removed = await withManagementFolders([row.path], true, async () => {
|
|
768
|
+
const { liveSessionIds, listSessionIds, manager: backendApi } = await maintenanceScope();
|
|
769
|
+
return backendApi.removeOrphanedWorktree({
|
|
770
|
+
entryId,
|
|
771
|
+
force: force === true,
|
|
772
|
+
liveSessionIds,
|
|
773
|
+
retainedSessionIds: await listSessionIds(),
|
|
774
|
+
});
|
|
775
|
+
});
|
|
776
|
+
return { removed, entryId, path: row.path };
|
|
777
|
+
}
|
|
778
|
+
|
|
755
779
|
/** After `sessionId` was deleted: autoCleanup removes its clean, merged worktree. */
|
|
756
780
|
async function cleanupDeletedSession(sessionId) {
|
|
757
781
|
// Its snapshots go with it below; no turn cleanup keeps retrying them.
|
|
@@ -1219,6 +1243,7 @@ export function createWorkspaceTransitions({
|
|
|
1219
1243
|
switchProject,
|
|
1220
1244
|
listWorktrees,
|
|
1221
1245
|
pruneWorktrees,
|
|
1246
|
+
removeOrphanWorktree,
|
|
1222
1247
|
cleanupDeletedSession,
|
|
1223
1248
|
onWorktreeSetup,
|
|
1224
1249
|
dispose,
|
|
@@ -27,12 +27,7 @@ const loadManager = () => import('../../runtime/shared/worktrees/manager.mjs');
|
|
|
27
27
|
* @param {((args: object, context: object) => Promise<any>)|null} [deps.executeSessionControl] Cross-session lane.
|
|
28
28
|
* @param {object|null} [deps.manager] Managed-worktree backend override (tests).
|
|
29
29
|
*/
|
|
30
|
-
export function createWorktreeToolHost({
|
|
31
|
-
getSession,
|
|
32
|
-
getWorkspaceApi,
|
|
33
|
-
executeSessionControl = null,
|
|
34
|
-
manager = null,
|
|
35
|
-
}) {
|
|
30
|
+
export function createWorktreeToolHost({ getSession, getWorkspaceApi, executeSessionControl = null, manager = null }) {
|
|
36
31
|
const backend = () => (manager ? Promise.resolve(manager) : loadManager());
|
|
37
32
|
|
|
38
33
|
function workspaceApi() {
|
|
@@ -102,7 +97,11 @@ export function createWorktreeToolHost({
|
|
|
102
97
|
'The project folder has no checked-out branch to return to (detached HEAD).'
|
|
103
98
|
);
|
|
104
99
|
}
|
|
105
|
-
return lane.call('selectBranch', {
|
|
100
|
+
return lane.call('selectBranch', {
|
|
101
|
+
branch: projectBranch,
|
|
102
|
+
expectedRevision: workspace.revision,
|
|
103
|
+
txId: request.txId,
|
|
104
|
+
});
|
|
106
105
|
}
|
|
107
106
|
|
|
108
107
|
/** The desktop's "merge": into the confirmed merge target, then the cleanup autoCleanup allows. */
|
|
@@ -116,6 +115,15 @@ export function createWorktreeToolHost({
|
|
|
116
115
|
});
|
|
117
116
|
}
|
|
118
117
|
|
|
118
|
+
/**
|
|
119
|
+
* The listed worktree matching `predicate` whose owner is gone and that the
|
|
120
|
+
* list reports removable, or null: no owner lane can remove it.
|
|
121
|
+
*/
|
|
122
|
+
async function orphan(predicate) {
|
|
123
|
+
const rows = await workspaceApi().listWorktrees();
|
|
124
|
+
return rows.find((row) => predicate(row) && row.ownerLive === false && row.removable === true) ?? null;
|
|
125
|
+
}
|
|
126
|
+
|
|
119
127
|
/**
|
|
120
128
|
* `git worktree remove <path>`: the managed removal of whichever owner
|
|
121
129
|
* holds it — this session's snapshot, or the worktree a session retains
|
|
@@ -129,6 +137,9 @@ export function createWorktreeToolHost({
|
|
|
129
137
|
);
|
|
130
138
|
}
|
|
131
139
|
if (entry.kind === 'snapshot') return workspaceApi().removeSnapshot({ path: entry.path, force: request.force });
|
|
140
|
+
if (entry.ownerSessionId !== callerSessionId && (await orphan((row) => row.id === entry.id))) {
|
|
141
|
+
return workspaceApi().removeOrphanWorktree({ entryId: entry.id, force: request.force === true });
|
|
142
|
+
}
|
|
132
143
|
if (!entry.ownerSessionId) {
|
|
133
144
|
throw new Error(
|
|
134
145
|
`git worktree remove: no session retains ${entry.path}; git worktree prune removes it once it is clean and merged`
|
|
@@ -161,8 +172,14 @@ export function createWorktreeToolHost({
|
|
|
161
172
|
return back(lane(), request);
|
|
162
173
|
case 'merge':
|
|
163
174
|
return merge(lane());
|
|
164
|
-
case 'remove':
|
|
165
|
-
|
|
175
|
+
case 'remove': {
|
|
176
|
+
const force = request.force === true;
|
|
177
|
+
if (request.sessionId && request.sessionId !== callerSessionId) {
|
|
178
|
+
const gone = await orphan((row) => row.ownerSessionId === request.sessionId);
|
|
179
|
+
if (gone) return workspaceApi().removeOrphanWorktree({ entryId: gone.id, force });
|
|
180
|
+
}
|
|
181
|
+
return lane().call('removeWorktree', { force });
|
|
182
|
+
}
|
|
166
183
|
case 'list':
|
|
167
184
|
return workspaceApi().listWorktrees({ includeSnapshots: true });
|
|
168
185
|
case 'prune':
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { createAgentTaskSessions, taskSessionPendingWork } from '../session-runtime/agent-task-sessions.mjs';
|
|
8
8
|
import { createBotStatus, scanCatalog, scopedBot } from '../session-runtime/bot-status.mjs';
|
|
9
9
|
import { handoffContent } from '../session-runtime/handoff-attachments.mjs';
|
|
10
|
+
import { BOT_TARGET_ACTIONS, isBotShaped, unknownBotError } from '../session-runtime/bot-target.mjs';
|
|
10
11
|
import { checkEffort, readModelCatalog } from '../session-runtime/task-session-route.mjs';
|
|
11
12
|
import { assertBotChainTarget, botChainOf } from '../runtime/shared/bot-hops.mjs';
|
|
12
13
|
import { agentIdOfHomeSession } from '../runtime/agent/orchestrator/session/agent-home.mjs';
|
|
@@ -82,7 +83,8 @@ async function storedTitle(sessionId) {
|
|
|
82
83
|
export function createSessionControl({
|
|
83
84
|
getSessionService,
|
|
84
85
|
agentTasks = createAgentTaskSessions({ getSessionService }),
|
|
85
|
-
|
|
86
|
+
listBots,
|
|
87
|
+
botStatus = createBotStatus({ getSessionService, listBots }),
|
|
86
88
|
// Agent work a session handed off and still waits on (agent-task-sessions.mjs).
|
|
87
89
|
pendingWork = taskSessionPendingWork,
|
|
88
90
|
liveWork = storedLiveWork,
|
|
@@ -92,7 +94,7 @@ export function createSessionControl({
|
|
|
92
94
|
const worktreeControl = createWorktreeControl(getSessionService);
|
|
93
95
|
const routeControl = createRouteControl(getSessionService);
|
|
94
96
|
|
|
95
|
-
|
|
97
|
+
async function control(args = {}, context = {}) {
|
|
96
98
|
const action = args.action;
|
|
97
99
|
const caller = requestCaller(action, context);
|
|
98
100
|
// `callerAgentId`: set only for an attended agent conversation (workspace-tools.mjs).
|
|
@@ -263,5 +265,19 @@ export function createSessionControl({
|
|
|
263
265
|
agentsWorking: (await pendingWork(sessionId)) === true,
|
|
264
266
|
quotaResume: snapshot.quotaResume ?? null,
|
|
265
267
|
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return async function executeSessionControl(args = {}, context = {}) {
|
|
271
|
+
try {
|
|
272
|
+
return await control(args, context);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
// A bot id is resolved to its home chat before this (workspace-tools.mjs),
|
|
275
|
+
// so a bot-shaped id the session service found no session for is no bot
|
|
276
|
+
// either: say which ids are.
|
|
277
|
+
const id = typeof args.session_id === 'string' ? args.session_id.trim() : '';
|
|
278
|
+
const missing = [`${id} is not available`, `${id} is unavailable`].some((text) => error?.message?.includes(text));
|
|
279
|
+
if (!BOT_TARGET_ACTIONS.has(args.action) || !isBotShaped(id) || !missing) throw error;
|
|
280
|
+
throw await unknownBotError(id, listBots, error);
|
|
281
|
+
}
|
|
266
282
|
};
|
|
267
283
|
}
|