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
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
inheritedFromSessionId,
|
|
25
25
|
isStoredSessionId,
|
|
26
26
|
positiveNumber as _positiveNumber,
|
|
27
|
+
compactCount,
|
|
28
|
+
compactCountField,
|
|
27
29
|
realTurnCount,
|
|
28
30
|
workspaceSummary,
|
|
29
31
|
} from './store-summary-fields.mjs';
|
|
@@ -54,26 +56,50 @@ const _computeCleanPreview = cleanSummaryPreview;
|
|
|
54
56
|
// cleaning runs a chain of regex replaces that each allocate a copy. Those
|
|
55
57
|
// short strings repeat from flush to flush, so the pure result is memoized per
|
|
56
58
|
// (max, text). Long raw message text is not kept here (see _previewFromMessage).
|
|
59
|
+
// Every new message's raw text (up to the text limit) also passes through, so
|
|
60
|
+
// the memo is bounded by the characters it holds, least recently used first
|
|
61
|
+
// out: at 8192 entries alone it kept ~16–32 MB of message text per `max`.
|
|
57
62
|
const CLEAN_PREVIEW_MEMO_TEXT_LIMIT = 2048;
|
|
58
63
|
const CLEAN_PREVIEW_MEMO_ENTRIES = 8192;
|
|
59
|
-
const
|
|
64
|
+
const CLEAN_PREVIEW_MEMO_MAX_CHARS = 512 * 1024;
|
|
65
|
+
const cleanPreviewMemo = new Map(); // max → { byText: Map<text, cleaned>, chars }
|
|
60
66
|
|
|
61
67
|
function _cleanPreview(text, max = 240) {
|
|
62
68
|
if (typeof text !== 'string' || text.length > CLEAN_PREVIEW_MEMO_TEXT_LIMIT) return _computeCleanPreview(text, max);
|
|
63
|
-
let
|
|
64
|
-
if (!
|
|
65
|
-
|
|
66
|
-
cleanPreviewMemo.set(max,
|
|
69
|
+
let memo = cleanPreviewMemo.get(max);
|
|
70
|
+
if (!memo) {
|
|
71
|
+
memo = { byText: new Map(), chars: 0 };
|
|
72
|
+
cleanPreviewMemo.set(max, memo);
|
|
67
73
|
}
|
|
74
|
+
const { byText } = memo;
|
|
68
75
|
let cleaned = byText.get(text);
|
|
69
|
-
if (cleaned
|
|
70
|
-
|
|
71
|
-
if (byText.size >= CLEAN_PREVIEW_MEMO_ENTRIES) byText.clear();
|
|
76
|
+
if (cleaned !== undefined) {
|
|
77
|
+
byText.delete(text);
|
|
72
78
|
byText.set(text, cleaned);
|
|
79
|
+
return cleaned;
|
|
80
|
+
}
|
|
81
|
+
cleaned = _computeCleanPreview(text, max);
|
|
82
|
+
byText.set(text, cleaned);
|
|
83
|
+
memo.chars += text.length + cleaned.length;
|
|
84
|
+
for (const [oldText, oldCleaned] of byText) {
|
|
85
|
+
if (byText.size <= CLEAN_PREVIEW_MEMO_ENTRIES && memo.chars <= CLEAN_PREVIEW_MEMO_MAX_CHARS) break;
|
|
86
|
+
byText.delete(oldText);
|
|
87
|
+
memo.chars -= oldText.length + oldCleaned.length;
|
|
73
88
|
}
|
|
74
89
|
return cleaned;
|
|
75
90
|
}
|
|
76
91
|
|
|
92
|
+
/** Entries and characters the preview memo holds, summed over every `max` (tests). */
|
|
93
|
+
export function _cleanPreviewMemoStatsForTest() {
|
|
94
|
+
let entries = 0;
|
|
95
|
+
let chars = 0;
|
|
96
|
+
for (const memo of cleanPreviewMemo.values()) {
|
|
97
|
+
entries += memo.byText.size;
|
|
98
|
+
chars += memo.chars;
|
|
99
|
+
}
|
|
100
|
+
return { entries, chars };
|
|
101
|
+
}
|
|
102
|
+
|
|
77
103
|
const sessionMessageProjectionMemo = new WeakMap();
|
|
78
104
|
// message → { raw, preview }. A session's first real user message is
|
|
79
105
|
// re-checked on every save; its text is re-derived (cheap) and the preview is
|
|
@@ -265,6 +291,8 @@ export function _sessionSummary(session) {
|
|
|
265
291
|
taskType: session.taskType || null,
|
|
266
292
|
messageCount: messageProjection.count,
|
|
267
293
|
...(messageProjection.incoming ? { incomingTail: messageProjection.incoming.slice() } : {}),
|
|
294
|
+
// The numbering epoch of messageCount: a compaction restarts it.
|
|
295
|
+
compactCount: compactCount(session.compaction) ?? 0,
|
|
268
296
|
realTurnCount: messageProjection.real,
|
|
269
297
|
title: _cleanPreview(session.title || '', 100),
|
|
270
298
|
preview: messageProjection.preview,
|
|
@@ -324,6 +352,7 @@ function _normalizeSummaryRow(row) {
|
|
|
324
352
|
taskType: row.taskType || null,
|
|
325
353
|
messageCount: Math.max(0, Math.floor(Number(row.messageCount) || 0)),
|
|
326
354
|
...(Array.isArray(row.incomingTail) ? { incomingTail: normalizedIncomingTail(row.incomingTail) } : {}),
|
|
355
|
+
...compactCountField(row),
|
|
327
356
|
realTurnCount: realTurnCount(row),
|
|
328
357
|
title: _cleanPreview(row.title || '', 100),
|
|
329
358
|
preview: _cleanPreview(row.preview || ''),
|
|
@@ -44,7 +44,11 @@ import {
|
|
|
44
44
|
import { attachmentHistoryRows } from './stored-attachment-history.mjs';
|
|
45
45
|
import { currentSessionCatalogDb, onSessionCatalogDbChange, sessionCatalogDb } from './catalog-db/index.mjs';
|
|
46
46
|
import { decodeCatalogCursor, encodeCatalogCursor, isAfterCursor } from './catalog-db/schema.mjs';
|
|
47
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
onSessionActivityChange,
|
|
49
|
+
onSessionApprovalCardChange,
|
|
50
|
+
onSessionBotWorkChange,
|
|
51
|
+
} from '../../../shared/session-activity-signal.mjs';
|
|
48
52
|
import { inFlightSessionActivity } from './manager/runtime-state.mjs';
|
|
49
53
|
import {
|
|
50
54
|
readCanonicalSessionRecord as readCanonicalLifecycle,
|
|
@@ -136,6 +140,10 @@ onSessionActivityChange(() => {
|
|
|
136
140
|
// A session started or stopped waiting on the user's approval: its catalog row
|
|
137
141
|
// (`awaitingApproval`) changed, though no store file did.
|
|
138
142
|
onSessionApprovalCardChange(() => notifyCatalogListeners('approval'));
|
|
143
|
+
// A bot started or stopped having a send in flight in a session: its catalog
|
|
144
|
+
// row (`handedBy`) changed, though neither a file nor the set of working
|
|
145
|
+
// sessions did (the work may already be running, or continue past the end).
|
|
146
|
+
onSessionBotWorkChange(() => notifyCatalogListeners('bot-work'));
|
|
139
147
|
|
|
140
148
|
/** `listener()` runs when a background pass finds a session file that changed
|
|
141
149
|
* without a directory or index change, i.e. when an ordinary listing would
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
} from '../../../../shared/transcript-metadata.mjs';
|
|
45
45
|
import { routineTriggerField } from '../../../../shared/routine-trigger.mjs';
|
|
46
46
|
import { isImportedMessage } from '../imported-message.mjs';
|
|
47
|
+
import { toolRunLine } from '../conversation-import/turns.mjs';
|
|
47
48
|
import { isContextAckMessage } from '../compact/user-message-kind.mjs';
|
|
48
49
|
import { isRoutineResultMessage, routineResultNotice } from '../routine-result.mjs';
|
|
49
50
|
import { heldRequestNotice, isHeldRequestMessage } from '../human-gate/held-message.mjs';
|
|
@@ -140,7 +141,64 @@ function restoredContentText(message) {
|
|
|
140
141
|
return typeof message?.content === 'string' ? message.content : toolResultText(message?.content);
|
|
141
142
|
}
|
|
142
143
|
|
|
144
|
+
// The runs of tool use an imported assistant message recorded (conversation-import
|
|
145
|
+
// turns.mjs), kept only where the text still holds exactly their canonical line.
|
|
146
|
+
function importedToolRuns(message) {
|
|
147
|
+
const runs = message?.meta?.importedToolRuns;
|
|
148
|
+
if (!isImportedMessage(message) || typeof message.content !== 'string' || !Array.isArray(runs)) return [];
|
|
149
|
+
let cursor = 0;
|
|
150
|
+
return runs.filter((run) => {
|
|
151
|
+
const valid =
|
|
152
|
+
Number.isInteger(run?.start) &&
|
|
153
|
+
Number.isInteger(run?.end) &&
|
|
154
|
+
run.start >= cursor &&
|
|
155
|
+
run.end > run.start &&
|
|
156
|
+
run.end <= message.content.length &&
|
|
157
|
+
Array.isArray(run.tools) &&
|
|
158
|
+
run.tools.length > 0 &&
|
|
159
|
+
run.tools.every((tool) => typeof tool?.name === 'string' && Number.isInteger(tool.count) && tool.count > 0) &&
|
|
160
|
+
message.content.slice(run.start, run.end) === toolRunLine(run.tools);
|
|
161
|
+
if (valid) cursor = run.end;
|
|
162
|
+
return valid;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// An imported reply with tool use: its text around each run as assistant rows,
|
|
167
|
+
// each run as tool rows (one per tool, counted). The calls' results were never
|
|
168
|
+
// imported; the `imported` mark lets the row say so.
|
|
169
|
+
function restoredImportedAssistantItems(message, runs, nextId) {
|
|
170
|
+
const { completion: _completion, ...metadata } = restoredTranscriptMetadata(message);
|
|
171
|
+
const at = Number(message?.meta?.transcript?.at);
|
|
172
|
+
const items = [];
|
|
173
|
+
const pushText = (raw) => {
|
|
174
|
+
const text = raw.trim();
|
|
175
|
+
if (text) items.push({ kind: 'assistant', id: nextId(), text, ...metadata });
|
|
176
|
+
};
|
|
177
|
+
let cursor = 0;
|
|
178
|
+
for (const run of runs) {
|
|
179
|
+
pushText(message.content.slice(cursor, run.start));
|
|
180
|
+
for (const { name, count } of run.tools) {
|
|
181
|
+
if (isTranscriptHiddenControlToolName(name)) continue;
|
|
182
|
+
items.push({
|
|
183
|
+
kind: 'tool',
|
|
184
|
+
id: nextId(),
|
|
185
|
+
name,
|
|
186
|
+
imported: true,
|
|
187
|
+
expanded: false,
|
|
188
|
+
count,
|
|
189
|
+
completedCount: count,
|
|
190
|
+
...(Number.isFinite(at) ? { at, startedAt: at, completedAt: at } : {}),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
cursor = run.end;
|
|
194
|
+
}
|
|
195
|
+
pushText(message.content.slice(cursor));
|
|
196
|
+
return items;
|
|
197
|
+
}
|
|
198
|
+
|
|
143
199
|
function restoredAssistantTranscriptItems(message, nextId) {
|
|
200
|
+
const runs = importedToolRuns(message);
|
|
201
|
+
if (runs.length) return restoredImportedAssistantItems(message, runs, nextId);
|
|
144
202
|
const text = restoredContentText(message).trim();
|
|
145
203
|
if (!text) return [];
|
|
146
204
|
const { completion, ...metadata } = restoredTranscriptMetadata(message);
|
|
@@ -339,7 +397,7 @@ function mergeRestoredToolItems(items) {
|
|
|
339
397
|
// Result-dependent hiding (built-in skill loads) resolves here, after
|
|
340
398
|
// every role:'tool' result has been attached to its restored call.
|
|
341
399
|
if (item?.kind === 'tool' && isTranscriptHiddenToolItem(item)) continue;
|
|
342
|
-
const mergeable = item?.kind === 'tool' && item.aggregate !== true;
|
|
400
|
+
const mergeable = item?.kind === 'tool' && item.aggregate !== true && item.imported !== true;
|
|
343
401
|
if (!mergeable) {
|
|
344
402
|
flushRun();
|
|
345
403
|
merged.push(item);
|
|
@@ -379,7 +437,8 @@ function restoredMessageItemUpperBound(message) {
|
|
|
379
437
|
const calls = restoredMessageToolCalls(message);
|
|
380
438
|
const content = message?.content;
|
|
381
439
|
const hasContent = typeof content === 'string' ? content.length > 0 : Array.isArray(content) && content.length > 0;
|
|
382
|
-
|
|
440
|
+
const importedRows = importedToolRuns(message).reduce((total, run) => total + run.tools.length + 1, 0);
|
|
441
|
+
return Number(hasContent) + completionCount + calls.length + importedRows + boundaries;
|
|
383
442
|
}
|
|
384
443
|
|
|
385
444
|
// One restored tool card for a parsed notification/completion envelope.
|
|
@@ -259,6 +259,29 @@ export const PROVIDER_WS_SEMANTIC_IDLE_TIMEOUT_MS = resolveTimeoutMs(
|
|
|
259
259
|
{ minMs: 10_000, maxMs: Math.max(10_000, STALL_ABORT_MS - STALL_TICK_MS) }
|
|
260
260
|
);
|
|
261
261
|
|
|
262
|
+
// First-OUTPUT window of one provider stream: from the stream opening until
|
|
263
|
+
// the model's first semantic output (reasoning, text or a tool call). Server
|
|
264
|
+
// metadata and keepalives (response.created / in_progress, an empty reasoning
|
|
265
|
+
// or message item, a role-only chunk, pings) neither end this phase nor start
|
|
266
|
+
// the semantic-idle clock, so a reasoning model may think silently past the
|
|
267
|
+
// idle window (issue #244: 4–6 min on a large post-tool-result prompt). The
|
|
268
|
+
// window sits one tick under the stall abort — the same ceiling the semantic
|
|
269
|
+
// idle uses — so the provider still fails (and may retry or fall back) before
|
|
270
|
+
// the agent watchdog's first-output deadline (600s default), and it is never
|
|
271
|
+
// shorter than the idle window it replaces. Not separately tunable: it
|
|
272
|
+
// follows STALL_TIMEOUT_S.
|
|
273
|
+
export const PROVIDER_FIRST_OUTPUT_TIMEOUT_MS = Math.max(
|
|
274
|
+
PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS,
|
|
275
|
+
PROVIDER_WS_SEMANTIC_IDLE_TIMEOUT_MS,
|
|
276
|
+
STALL_ABORT_MS - STALL_TICK_MS
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
/** The first-output window of a stream whose semantic idle is `idleMs`: a
|
|
280
|
+
* longer idle window (a provider profile) is never cut short by it. */
|
|
281
|
+
export function providerFirstOutputTimeoutMs(idleMs) {
|
|
282
|
+
return Math.max(idleMs, PROVIDER_FIRST_OUTPUT_TIMEOUT_MS);
|
|
283
|
+
}
|
|
284
|
+
|
|
262
285
|
/**
|
|
263
286
|
* The longest a provider's own first-byte handling may legitimately run
|
|
264
287
|
* before it gives up, over every attempt it permits: `windowMs(n)` is the wait
|
|
@@ -28,9 +28,9 @@ function prefixPowerShellUtf8(command) {
|
|
|
28
28
|
// UTF-8 prefix (+ optional filter-tee rescue), or a POSIX bash/zsh snapshot
|
|
29
29
|
// wrapper delivered through the environment. Returns `{ failure }` when the
|
|
30
30
|
// snapshot wrapper cannot be built.
|
|
31
|
-
export async function planShellInvocation({ command, resolvedSpec, cwd }) {
|
|
31
|
+
export async function planShellInvocation({ command, resolvedSpec, cwd, sessionId = null }) {
|
|
32
32
|
const { shell, shellArg, shellArgs, shellType } = resolvedSpec;
|
|
33
|
-
const spawnEnv = buildShellSpawnEnv(cwd);
|
|
33
|
+
const spawnEnv = buildShellSpawnEnv(cwd, process.env, sessionId);
|
|
34
34
|
const plan = {
|
|
35
35
|
execShell: shell,
|
|
36
36
|
execShellArg: shellArg,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { applySessionScratchEnv } from '../../../../../shared/session-scratch.mjs';
|
|
1
2
|
import { withSkillLauncherPath } from '../../../../../shared/skill-script-runtime.mjs';
|
|
2
3
|
import {
|
|
3
4
|
applyShellEgressPolicy,
|
|
@@ -6,7 +7,8 @@ import {
|
|
|
6
7
|
scrubRuntimeRootVars,
|
|
7
8
|
} from '../../env-scrub.mjs';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
// `sessionId` points TEMP/TMP/TMPDIR at that session's scratch directory.
|
|
11
|
+
export function buildShellSpawnEnv(cwd, baseEnv = process.env, sessionId = null) {
|
|
10
12
|
const spawnEnv = {
|
|
11
13
|
...baseEnv,
|
|
12
14
|
LANG: 'C.UTF-8',
|
|
@@ -24,5 +26,5 @@ export function buildShellSpawnEnv(cwd, baseEnv = process.env) {
|
|
|
24
26
|
// `mixdog-skill-node` (a skill's ${MIXDOG_SKILL_NODE}) resolves in every
|
|
25
27
|
// model shell; only this env gets the entry, never the daemon's own.
|
|
26
28
|
withSkillLauncherPath(spawnEnv);
|
|
27
|
-
return spawnEnv;
|
|
29
|
+
return applySessionScratchEnv(spawnEnv, sessionId);
|
|
28
30
|
}
|
|
@@ -116,7 +116,12 @@ export async function executeBashTool(args, workDir, options = {}) {
|
|
|
116
116
|
// A queued run, or a foreground tree that may outlive its result, settles later.
|
|
117
117
|
let runSettled = null;
|
|
118
118
|
try {
|
|
119
|
-
const invocation = await planShellInvocation({
|
|
119
|
+
const invocation = await planShellInvocation({
|
|
120
|
+
command,
|
|
121
|
+
resolvedSpec,
|
|
122
|
+
cwd: bashWorkDir,
|
|
123
|
+
sessionId: options?.callerSessionId || options?.sessionId || null,
|
|
124
|
+
});
|
|
120
125
|
if (invocation.failure) return invocation.failure;
|
|
121
126
|
combinedAbort = combineAbortSignals(await sessionAbortSignal(options?.sessionId), options?.abortSignal || null);
|
|
122
127
|
const startedAtMs = Date.now();
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// behavior, argument shapes, and the usage boundaries that only apply to that
|
|
6
6
|
// tool; cross-tool policy lives in rules/shared/*.md.
|
|
7
7
|
// Platform-specific command syntax belongs next to the command argument.
|
|
8
|
-
import { GIT_TOOL_DEF } from './git-command-tool.mjs';
|
|
8
|
+
import { GIT_AGENT_TOOL_DEF, GIT_TOOL_DEF } from './git-command-tool.mjs';
|
|
9
9
|
import { PUBLIC_PATH_BATCH_LIMIT, PUBLIC_READ_WINDOW_MAX } from './arg-guard.mjs';
|
|
10
10
|
import { GITHUB_TOOL_DEF } from '../../../../github/tool.mjs';
|
|
11
11
|
import { envFlag } from '../../../../shared/env.mjs';
|
|
@@ -50,7 +50,7 @@ export function buildShellRoutingRules(kind = _defaultShellKind) {
|
|
|
50
50
|
function buildShellDescription(kind) {
|
|
51
51
|
const run = _shellBackgroundDisabled
|
|
52
52
|
? 'Commands run in the foreground until completion.'
|
|
53
|
-
: `Holds ${_holdS}s; then, or on input/task notification, backgrounds with task_id (command keeps running). Completion is automatic; never poll, and never sleep/poll-loop inside the shell (background:true or wait_ms instead; for an external job, background a command that exits when it ends). Result needed now: task wait.
|
|
53
|
+
: `Holds ${_holdS}s; then, or on input/task notification, backgrounds with task_id (command keeps running). Completion is automatic; never poll, and never sleep/poll-loop inside the shell (background:true or wait_ms instead; for an external job, background a command that exits when it ends). Result needed now: task wait. Independent long runs: background:true; cancel when done.`;
|
|
54
54
|
return `Run programs, builds, tests and computation. ${buildShellRoutingRules(kind)} ${run}`;
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -88,6 +88,10 @@ export function bindShellKind(tool, kind) {
|
|
|
88
88
|
return bound;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
function taskTimeoutDescription(defaultMs) {
|
|
92
|
+
return `Wait ceiling in ms; else current output at the ceiling. Default ${defaultMs}; clamped to ${TASK_WAIT_TIMEOUT_MIN_MS}-${TASK_WAIT_TIMEOUT_MAX_MS}.`;
|
|
93
|
+
}
|
|
94
|
+
|
|
91
95
|
export const BUILTIN_TOOLS = [
|
|
92
96
|
{
|
|
93
97
|
name: 'read',
|
|
@@ -101,7 +105,7 @@ export const BUILTIN_TOOLS = [
|
|
|
101
105
|
compressible: false,
|
|
102
106
|
},
|
|
103
107
|
description:
|
|
104
|
-
"Read file windows or images. One call per read stage: {file_path, offset, limit} entries for every located site
|
|
108
|
+
"Read file windows or images. One call per read stage: {file_path, offset, limit} entries for every located site, never a whole large file. Content in context is never read again. Missing paths are reported, never replaced. Directories: list. Binaries: bounded hex. Rows carry no line numbers; `[lines a-b]` gives each block's range. Never count rows: before writing a line number, copy it from grep, code_graph or git diff output; otherwise cite path and symbol.",
|
|
105
109
|
inputSchema: {
|
|
106
110
|
type: 'object',
|
|
107
111
|
properties: {
|
|
@@ -129,8 +133,7 @@ export const BUILTIN_TOOLS = [
|
|
|
129
133
|
},
|
|
130
134
|
},
|
|
131
135
|
],
|
|
132
|
-
description:
|
|
133
|
-
'Path(s) or {file_path, offset, limit} entries; overlapping windows of one file merge. A glob fans out to ≤10 newest files, 25 lines each.',
|
|
136
|
+
description: 'Overlapping windows of one file merge. A glob fans out to ≤10 newest files.',
|
|
134
137
|
},
|
|
135
138
|
offset: {
|
|
136
139
|
type: 'integer',
|
|
@@ -167,14 +170,11 @@ export const BUILTIN_TOOLS = [
|
|
|
167
170
|
compressibleLossless: true,
|
|
168
171
|
},
|
|
169
172
|
description:
|
|
170
|
-
'Replace exact text in one file. old_string must match once unless replace_all is true. Every non-overlapping edit of the stage in one response, using existing text, not text another edit creates; many files → one edit per file
|
|
173
|
+
'Replace exact text in one file. old_string must match once unless replace_all is true. Every non-overlapping edit of the stage in one response, using existing text, not text another edit creates; many files → one edit per file. Widened replacements keep intervening lines verbatim.',
|
|
171
174
|
inputSchema: {
|
|
172
175
|
type: 'object',
|
|
173
176
|
properties: {
|
|
174
|
-
file_path: {
|
|
175
|
-
type: 'string',
|
|
176
|
-
description: 'Path to the file to modify.',
|
|
177
|
-
},
|
|
177
|
+
file_path: { type: 'string' },
|
|
178
178
|
old_string: {
|
|
179
179
|
type: 'string',
|
|
180
180
|
description:
|
|
@@ -187,7 +187,7 @@ export const BUILTIN_TOOLS = [
|
|
|
187
187
|
replace_all: {
|
|
188
188
|
type: 'boolean',
|
|
189
189
|
default: false,
|
|
190
|
-
description: 'Replace all occurrences of old_string
|
|
190
|
+
description: 'Replace all occurrences of old_string.',
|
|
191
191
|
},
|
|
192
192
|
},
|
|
193
193
|
required: ['file_path', 'old_string', 'new_string'],
|
|
@@ -254,7 +254,7 @@ export const BUILTIN_TOOLS = [
|
|
|
254
254
|
openWorldHint: false,
|
|
255
255
|
},
|
|
256
256
|
description:
|
|
257
|
-
'Manage shell tasks. Completion is delivered automatically; wait only when you need the result now, never poll with read. wait returns when the task finishes, or when new user input or any task notification arrives (the task keeps running). read/wait return new output, prioritizing the tail on completion.
|
|
257
|
+
'Manage shell tasks. Completion is delivered automatically; wait only when you need the result now, never poll with read. wait returns when the task finishes, or when new user input or any task notification arrives (the task keeps running). read/wait return new output, prioritizing the tail on completion. Use read on the log path for omitted content.',
|
|
258
258
|
inputSchema: {
|
|
259
259
|
type: 'object',
|
|
260
260
|
properties: {
|
|
@@ -267,13 +267,13 @@ export const BUILTIN_TOOLS = [
|
|
|
267
267
|
timeout_ms: {
|
|
268
268
|
type: 'integer',
|
|
269
269
|
minimum: 0,
|
|
270
|
-
description:
|
|
270
|
+
description: taskTimeoutDescription(TASK_WAIT_TIMEOUT_DEFAULT_MS),
|
|
271
271
|
},
|
|
272
272
|
output: {
|
|
273
273
|
type: 'string',
|
|
274
274
|
enum: ['new', 'tail'],
|
|
275
275
|
description:
|
|
276
|
-
'read/wait only. Default new; tail
|
|
276
|
+
'read/wait only. Default new; tail re-reads recent output without advancing the cursor, after an interruption or context loss. Up to 8 KiB per stream while running, 12 KiB on completion or replay; original logs are preserved.',
|
|
277
277
|
},
|
|
278
278
|
},
|
|
279
279
|
required: ['action'],
|
|
@@ -324,7 +324,7 @@ export const BUILTIN_TOOLS = [
|
|
|
324
324
|
description:
|
|
325
325
|
'Requested results; default 250; 0 unlimited. Context output caps at 40 blocks; continue via the returned offset.',
|
|
326
326
|
},
|
|
327
|
-
offset: { type: 'integer', minimum: 0
|
|
327
|
+
offset: { type: 'integer', minimum: 0 },
|
|
328
328
|
context: {
|
|
329
329
|
type: 'integer',
|
|
330
330
|
minimum: 0,
|
|
@@ -356,7 +356,7 @@ export const BUILTIN_TOOLS = [
|
|
|
356
356
|
compressible: true,
|
|
357
357
|
},
|
|
358
358
|
description:
|
|
359
|
-
'Wildcard file-path lookup under a known directory; directories never match.
|
|
359
|
+
'Wildcard file-path lookup under a known directory; directories never match. No preliminary listing. Unknown base: find first with a known path fragment.',
|
|
360
360
|
inputSchema: {
|
|
361
361
|
type: 'object',
|
|
362
362
|
properties: {
|
|
@@ -374,7 +374,7 @@ export const BUILTIN_TOOLS = [
|
|
|
374
374
|
description: 'mtime default (newest first); natural = raw walk order, cheaper on huge trees.',
|
|
375
375
|
},
|
|
376
376
|
limit: { type: 'integer', minimum: 0, description: 'Max entries; default 100; 0 unlimited.' },
|
|
377
|
-
offset: { type: 'integer', minimum: 0
|
|
377
|
+
offset: { type: 'integer', minimum: 0 },
|
|
378
378
|
include_noise: {
|
|
379
379
|
type: 'boolean',
|
|
380
380
|
description: 'Also search gitignored trees.',
|
|
@@ -404,7 +404,7 @@ export const BUILTIN_TOOLS = [
|
|
|
404
404
|
type: 'string',
|
|
405
405
|
minLength: 1,
|
|
406
406
|
pattern: '\\S',
|
|
407
|
-
description: '
|
|
407
|
+
description: 'Filename/path fragment; space-separated fragments AND-match within one path.',
|
|
408
408
|
},
|
|
409
409
|
path: { type: 'string', description: 'Base directory; omit for the current Project.' },
|
|
410
410
|
limit: { type: 'integer', minimum: 0, description: 'Max paths; default 25; 0 unlimited.' },
|
|
@@ -445,10 +445,36 @@ export const BUILTIN_TOOLS = [
|
|
|
445
445
|
maximum: 100,
|
|
446
446
|
description: 'Max entries; default 100; 0 = no page cap (absolute cap still applies).',
|
|
447
447
|
},
|
|
448
|
-
offset: { type: 'integer', minimum: 0
|
|
448
|
+
offset: { type: 'integer', minimum: 0 },
|
|
449
449
|
},
|
|
450
|
-
required: [],
|
|
451
450
|
additionalProperties: false,
|
|
452
451
|
},
|
|
453
452
|
},
|
|
454
453
|
];
|
|
454
|
+
|
|
455
|
+
// What a delegated agent's session sees of the tools whose text differs from
|
|
456
|
+
// the Lead's: git (worktree operations are Lead-only) and task (the agent wait
|
|
457
|
+
// default). Keyed by the shared definition, so a rebuilt list binds once.
|
|
458
|
+
const _taskTool = BUILTIN_TOOLS.find((tool) => tool.name === 'task');
|
|
459
|
+
const _agentTaskTool = {
|
|
460
|
+
..._taskTool,
|
|
461
|
+
inputSchema: {
|
|
462
|
+
..._taskTool.inputSchema,
|
|
463
|
+
properties: {
|
|
464
|
+
..._taskTool.inputSchema.properties,
|
|
465
|
+
timeout_ms: {
|
|
466
|
+
..._taskTool.inputSchema.properties.timeout_ms,
|
|
467
|
+
description: taskTimeoutDescription(AGENT_TASK_WAIT_DEFAULT_MS),
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
};
|
|
472
|
+
const _agentToolVariants = new Map([
|
|
473
|
+
[GIT_TOOL_DEF, GIT_AGENT_TOOL_DEF],
|
|
474
|
+
[_taskTool, _agentTaskTool],
|
|
475
|
+
]);
|
|
476
|
+
|
|
477
|
+
/** The agent-session variant of `tool`, or `tool` itself when it has none. */
|
|
478
|
+
export function bindAgentOwner(tool) {
|
|
479
|
+
return _agentToolVariants.get(tool) ?? tool;
|
|
480
|
+
}
|
|
@@ -324,7 +324,13 @@ export function rawContentCacheSet(fullPath, stat, rawBuf, now = Date.now()) {
|
|
|
324
324
|
if (!canPublishReadCache(fullPath)) return;
|
|
325
325
|
if (!fullPath || !Buffer.isBuffer(rawBuf)) return;
|
|
326
326
|
const version = fileVersion(stat);
|
|
327
|
-
if (
|
|
327
|
+
if (
|
|
328
|
+
!version ||
|
|
329
|
+
!canCacheReadFile(version) ||
|
|
330
|
+
rawBuf.length !== version.size ||
|
|
331
|
+
rawBuf.length > RAW_CONTENT_CACHE_MAX_BYTES
|
|
332
|
+
)
|
|
333
|
+
return;
|
|
328
334
|
const key = canonicalCachePath(fullPath);
|
|
329
335
|
rawContentCacheDelete(key);
|
|
330
336
|
RAW_CONTENT_CACHE.set(key, {
|
|
@@ -83,6 +83,9 @@ const SERVER_OPERATIONS = new Set(['daemon', 'instaweb']);
|
|
|
83
83
|
// probe into `git rev-parse exited 128` inside a plain directory.
|
|
84
84
|
const REPO_FREE_OPERATIONS = new Set(['version', 'help']);
|
|
85
85
|
|
|
86
|
+
const GIT_DESCRIPTION_HEAD =
|
|
87
|
+
'Run Git here, never through shell. An array runs in order and stops on failure; batch read-only commands in one. diff for known changes, status to discover them; history only when needed. git diff or git diff -- <paths> includes untracked files. Set include_stage_ids:true to select changes, then action:stage with diff_id/change_ids. Staging rejects stale or cross-Project diffs and returns per-file staged counts.';
|
|
88
|
+
|
|
86
89
|
export const GIT_TOOL_DEF = {
|
|
87
90
|
name: 'git',
|
|
88
91
|
title: 'Git',
|
|
@@ -94,8 +97,7 @@ export const GIT_TOOL_DEF = {
|
|
|
94
97
|
openWorldHint: true,
|
|
95
98
|
compressible: true,
|
|
96
99
|
},
|
|
97
|
-
description:
|
|
98
|
-
'Run Git here, never through shell. An array (max 10) runs in order and stops on failure; batch read-only commands in one. diff for known changes, status to discover them; history only when needed. git diff or git diff -- <paths> includes untracked files. Set include_stage_ids:true to select changes, then action:stage with diff_id/change_ids. Staging rejects stale or cross-Project diffs and returns per-file staged counts. Worktrees are managed only: action:worktree (Lead only) moves a session into/out of its managed worktree, merges, removes, lists, prunes; git worktree add/remove/list/prune map to managed operations (add -b keeps the session in place; add --detach makes a read-only snapshot worktree, removed when its turn ends; list --porcelain stays raw git; move/lock/unlock/repair are refused). Merge or remove only when the user asks; carry and large-size answers come from the user; force only on explicit request.',
|
|
100
|
+
description: `${GIT_DESCRIPTION_HEAD} Worktrees are managed only: action:worktree; git worktree add/remove/list/prune map to managed operations (add -b keeps the session in place; add --detach makes a read-only snapshot worktree, removed when its turn ends; list --porcelain stays raw git; move/lock/unlock/repair are refused). Merge or remove only when the user asks.`,
|
|
99
101
|
inputSchema: {
|
|
100
102
|
type: 'object',
|
|
101
103
|
properties: {
|
|
@@ -118,14 +120,14 @@ export const GIT_TOOL_DEF = {
|
|
|
118
120
|
minimum: 1,
|
|
119
121
|
maximum: GIT_OUTPUT_LIMIT_MAX,
|
|
120
122
|
description:
|
|
121
|
-
'Body line cap
|
|
123
|
+
'Body line cap; staging IDs are not capped. Default 500; git log defaults to 100. A cut ends with [output capped …] naming how to continue.',
|
|
122
124
|
},
|
|
123
125
|
include_stage_ids: {
|
|
124
126
|
type: 'boolean',
|
|
125
127
|
description:
|
|
126
128
|
'Include staging IDs outside the body cap for git diff or git diff -- <paths> only. Default false.',
|
|
127
129
|
},
|
|
128
|
-
diff_id: { type: 'string', description: 'stage: exact diff_id from git diff
|
|
130
|
+
diff_id: { type: 'string', description: 'stage: exact diff_id from git diff.' },
|
|
129
131
|
change_ids: {
|
|
130
132
|
anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 50 }],
|
|
131
133
|
description: 'stage: exact change ID or IDs to stage, including new files.',
|
|
@@ -158,6 +160,29 @@ export const GIT_TOOL_DEF = {
|
|
|
158
160
|
},
|
|
159
161
|
};
|
|
160
162
|
|
|
163
|
+
// Only the Lead host runs enter/return/merge/remove/prune (git-worktree-tool.mjs
|
|
164
|
+
// leadHost); an agent keeps `list`, so its schema omits every field the other
|
|
165
|
+
// operations take.
|
|
166
|
+
const LEAD_ONLY_WORKTREE_FIELDS = ['session_id', 'name', 'branch', 'create', 'carry', 'confirm_large', 'force'];
|
|
167
|
+
|
|
168
|
+
function buildGitAgentToolDef(base) {
|
|
169
|
+
const properties = { ...base.inputSchema.properties };
|
|
170
|
+
for (const field of LEAD_ONLY_WORKTREE_FIELDS) delete properties[field];
|
|
171
|
+
properties.action = {
|
|
172
|
+
...properties.action,
|
|
173
|
+
description: 'Default command. stage uses diff_id/change_ids instead of command. worktree uses worktree_action.',
|
|
174
|
+
};
|
|
175
|
+
properties.worktree_action = { type: 'string', enum: ['list'], description: 'list only.' };
|
|
176
|
+
return {
|
|
177
|
+
...base,
|
|
178
|
+
description: `${GIT_DESCRIPTION_HEAD} Worktrees are Lead-managed: only worktree_action:list and git worktree list work; every other worktree operation is refused.`,
|
|
179
|
+
inputSchema: { ...base.inputSchema, properties },
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** The git tool as a delegated agent sees it (see bindAgentOwner). */
|
|
184
|
+
export const GIT_AGENT_TOOL_DEF = buildGitAgentToolDef(GIT_TOOL_DEF);
|
|
185
|
+
|
|
161
186
|
function cleanText(value) {
|
|
162
187
|
return String(value || '')
|
|
163
188
|
.replace(/\r\n/g, '\n')
|
|
@@ -849,7 +874,8 @@ function snapshotContaining(repo) {
|
|
|
849
874
|
}
|
|
850
875
|
return (
|
|
851
876
|
Object.values(entries).find(
|
|
852
|
-
(record) =>
|
|
877
|
+
(record) =>
|
|
878
|
+
record?.kind === WORKTREE_SNAPSHOT_KIND && typeof record.path === 'string' && isWithin(record.path, repo)
|
|
853
879
|
) ?? null
|
|
854
880
|
);
|
|
855
881
|
}
|
|
@@ -101,6 +101,8 @@ function worktreeFailureText(code, message, details) {
|
|
|
101
101
|
return typeof details.path === 'string' && details.path
|
|
102
102
|
? `This branch is already checked out in ${details.path}.`
|
|
103
103
|
: 'This branch is already checked out in another worktree.';
|
|
104
|
+
case 'WORKTREE_DEFAULT_BRANCH':
|
|
105
|
+
return "The repository's default branch can't be opened in a worktree: the project folder could not switch to it while the worktree exists. Check it out in the project folder, or create a new branch for the worktree.";
|
|
104
106
|
case 'WORKTREE_REMOVAL_INCOMPLETE':
|
|
105
107
|
return 'The worktree could not be removed completely because a file is still in use. Close it and remove the worktree again.';
|
|
106
108
|
case 'WORKTREE_PATH_UNSAFE':
|
|
@@ -162,6 +164,8 @@ function nextStep(code, details, { raw }) {
|
|
|
162
164
|
return 'The workspace changed meanwhile; call again (the workspace is read afresh).';
|
|
163
165
|
case 'WORKTREE_BUSY_JOBS':
|
|
164
166
|
return 'Nothing was changed; try again once the session and its workers are idle.';
|
|
167
|
+
case 'WORKTREE_DEFAULT_BRANCH':
|
|
168
|
+
return 'Nothing was created. Work on a new branch instead (worktree_action:"enter" with branch and create:true), or ask the user to check the default branch out in the project folder.';
|
|
165
169
|
case 'MERGE_CONFLICT':
|
|
166
170
|
return 'Nothing was merged or forced. Resolve the conflict in the worktree (merge the target branch into it there), or ask the user.';
|
|
167
171
|
case 'WORKTREE_DIRTY':
|
|
@@ -316,7 +320,8 @@ async function rawAdd(plan, input, options) {
|
|
|
316
320
|
throw new Error(`git worktree add: ${value} is not supported for managed worktrees`);
|
|
317
321
|
}
|
|
318
322
|
if (ADD_BARE_FLAGS.has(value)) continue;
|
|
319
|
-
if (value.startsWith('-'))
|
|
323
|
+
if (value.startsWith('-'))
|
|
324
|
+
throw new Error(`git worktree add: option ${value} is not supported for managed worktrees`);
|
|
320
325
|
positional.push(value);
|
|
321
326
|
}
|
|
322
327
|
const [path, commitish = null] = positional;
|
|
@@ -436,8 +441,9 @@ async function rawPrune(plan, options) {
|
|
|
436
441
|
const WORKTREE_HELP = Object.freeze({
|
|
437
442
|
add: 'git worktree add [-b <branch>] [--detach] <path> [<commit-ish>]: a managed worktree for the branch (the session stays put), or a read-only snapshot for a detached commit.',
|
|
438
443
|
remove: 'git worktree remove [-f] <path>: the managed removal of that worktree.',
|
|
439
|
-
list:
|
|
440
|
-
prune:
|
|
444
|
+
list: "git worktree list [-v]: managed worktrees plus the repository's other worktrees. --porcelain stays raw git.",
|
|
445
|
+
prune:
|
|
446
|
+
'git worktree prune [-n|--dry-run] [-v]: the managed prune of this repository (clean, merged worktrees no session retains); -n only lists what it would remove.',
|
|
441
447
|
});
|
|
442
448
|
|
|
443
449
|
/** -h/--help before any `--` (the help answers instead of the operation running). */
|
|
@@ -450,7 +456,9 @@ function wantsHelp(args) {
|
|
|
450
456
|
function checkListArgs(args) {
|
|
451
457
|
for (const value of args) {
|
|
452
458
|
if (value === '-v' || value === '--verbose') continue;
|
|
453
|
-
throw new Error(
|
|
459
|
+
throw new Error(
|
|
460
|
+
`git worktree list: ${value.startsWith('-') ? `option ${value} is not supported` : `unexpected argument ${value}`}`
|
|
461
|
+
);
|
|
454
462
|
}
|
|
455
463
|
}
|
|
456
464
|
|
|
@@ -466,7 +474,8 @@ export async function executeRawWorktree(plan, input, options = {}) {
|
|
|
466
474
|
if (sub === 'list' && plan.args.includes('--porcelain')) return null;
|
|
467
475
|
try {
|
|
468
476
|
if (sub === 'list') checkListArgs(plan.args.slice(1));
|
|
469
|
-
if (sub === 'list')
|
|
477
|
+
if (sub === 'list')
|
|
478
|
+
return { text: okJson({ mapped: plan.command, ...(await listRows(options, plan.cwd)) }), failed: false };
|
|
470
479
|
if (sub === 'add') return { text: await rawAdd(plan, input, options), failed: false };
|
|
471
480
|
if (sub === 'remove') return { text: await rawRemove(plan, options), failed: false };
|
|
472
481
|
if (sub === 'prune') return { text: await rawPrune(plan, options), failed: false };
|
|
@@ -479,7 +488,9 @@ export async function executeRawWorktree(plan, input, options = {}) {
|
|
|
479
488
|
};
|
|
480
489
|
}
|
|
481
490
|
return {
|
|
482
|
-
text: fail(
|
|
491
|
+
text: fail(
|
|
492
|
+
`git worktree ${sub || '(no subcommand)'} is not supported; use add, remove, list, prune or action:"worktree"`
|
|
493
|
+
),
|
|
483
494
|
failed: true,
|
|
484
495
|
};
|
|
485
496
|
} catch (error) {
|
|
@@ -529,7 +540,8 @@ export async function executeWorktreeAction(input, workDir, options = {}) {
|
|
|
529
540
|
if (problem) return fail(problem);
|
|
530
541
|
const action = input.worktree_action;
|
|
531
542
|
try {
|
|
532
|
-
if (action === 'list')
|
|
543
|
+
if (action === 'list')
|
|
544
|
+
return okJson({ worktree_action: action, ...(await listRows(options, resolve(workDir || process.cwd()))) });
|
|
533
545
|
const host = leadHost(options, `git worktree_action ${action}`);
|
|
534
546
|
const sessionId = input.session_id?.trim() || null;
|
|
535
547
|
const request = {
|
|
@@ -545,7 +557,9 @@ export async function executeWorktreeAction(input, workDir, options = {}) {
|
|
|
545
557
|
}
|
|
546
558
|
: {}),
|
|
547
559
|
...(action === 'remove' ? { force: input.force === true } : {}),
|
|
548
|
-
...(action === 'prune'
|
|
560
|
+
...(action === 'prune'
|
|
561
|
+
? { repoCommonDir: (await repositoryAt(resolve(workDir || process.cwd())))?.repoCommonDir ?? null }
|
|
562
|
+
: {}),
|
|
549
563
|
txId: txIdOf(options, action),
|
|
550
564
|
};
|
|
551
565
|
const result = await runOn(host, request, options);
|