mixdog 1.2.6 → 1.3.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/LICENSES/simple-icons-CC0-1.0.txt +30 -0
- package/NOTICE.md +71 -9
- package/package.json +4 -1
- package/scripts/check-model-recommendations.mjs +89 -0
- package/scripts/gate-local.mjs +7 -0
- package/scripts/lib/isolated-test-env.mjs +16 -0
- package/src/agents/reviewer/AGENT.md +4 -2
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +10 -0
- package/src/defaults/skills/browser-use/SKILL.md +5 -0
- package/src/defaults/skills/memory-management/SKILL.md +4 -1
- package/src/defaults/skills/setup/references/actions.md +14 -4
- package/src/defaults/skills/setup-guide/SKILL.md +178 -0
- package/src/defaults/skills/setup-guide/model-recommendations.json +333 -0
- package/src/lib/rules-builder.cjs +55 -6
- package/src/output-styles/common.md +13 -8
- package/src/rules/agent/AGENT.md +4 -0
- package/src/rules/agent-mode/AGENT_MODE.md +24 -0
- package/src/rules/agent-mode/MIXY.md +28 -0
- package/src/rules/agent-mode/ROUTINE.md +6 -0
- package/src/rules/lead/LEAD.md +7 -3
- package/src/rules/shared/70-delivery.md +3 -0
- package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +86 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +77 -14
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +241 -14
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +4 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +216 -0
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -4
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +10 -1
- package/src/runtime/agent/orchestrator/mcp/client.mjs +115 -11
- package/src/runtime/agent/orchestrator/mcp/oauth-account.mjs +97 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +49 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +59 -9
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +32 -7
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +45 -6
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +195 -6
- package/src/runtime/agent/orchestrator/mcp/server-connectors.mjs +17 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +10 -3
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +7 -8
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +17 -12
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -0
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +14 -2
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +96 -1
- package/src/runtime/agent/orchestrator/runtime-core/context-status-shape.mjs +4 -0
- package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +6 -4
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +14 -0
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +12 -6
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +32 -5
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +21 -5
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +40 -7
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +52 -20
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/agent-home.d.mts +4 -0
- package/src/runtime/agent/orchestrator/session/agent-home.mjs +33 -0
- package/src/runtime/agent/orchestrator/session/agent-mode.mjs +62 -0
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +31 -0
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +241 -0
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +166 -0
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +650 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +192 -0
- package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +95 -0
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +221 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +195 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +167 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +108 -6
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +74 -15
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +9 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -3
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-lifecycle-epoch.mjs +0 -8
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +44 -16
- package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +50 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +22 -5
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +39 -4
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +33 -24
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +58 -8
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +32 -5
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +86 -0
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +329 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +28 -9
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -12
- package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +46 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +0 -4
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +58 -10
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +92 -15
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +113 -5
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +4 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +25 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +75 -57
- package/src/runtime/attachments/store.mjs +6 -0
- package/src/runtime/browser-bridge/client.mjs +24 -0
- package/src/runtime/channels/lib/scheduler.mjs +40 -12
- package/src/runtime/channels/lib/schedules-db.mjs +71 -12
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +36 -16
- package/src/runtime/channels/lib/webhook.mjs +0 -1
- package/src/runtime/channels/lib/webhooks-db.mjs +100 -10
- package/src/runtime/channels/lib/worker-main.mjs +17 -20
- package/src/runtime/computer-bridge/client.mjs +28 -1
- package/src/runtime/connectors/gmail-app-password.mjs +690 -0
- package/src/runtime/connectors/google-mcp.mjs +446 -0
- package/src/runtime/effort-judge/judge-client.mjs +1 -1
- package/src/runtime/effort-judge/model-manifest.json +7 -7
- package/src/runtime/memory/lib/core-memory-access.mjs +69 -0
- package/src/runtime/memory/lib/core-memory-file.mjs +16 -8
- package/src/runtime/memory/lib/core-memory-index.mjs +15 -9
- package/src/runtime/memory/lib/core-memory-management.mjs +10 -4
- package/src/runtime/memory/lib/core-memory-store.mjs +53 -30
- package/src/runtime/memory/lib/core-memory-uniqueness.mjs +10 -6
- package/src/runtime/memory/lib/http-router/session-routes.mjs +3 -2
- package/src/runtime/memory/lib/memory-action-handlers/core-actions.mjs +44 -14
- package/src/runtime/memory/lib/memory-schema/core-and-meta.mjs +11 -0
- package/src/runtime/memory/lib/memory.mjs +7 -1
- package/src/runtime/memory/lib/query-core-recall.mjs +9 -1
- package/src/runtime/memory/lib/query-hybrid-search.mjs +2 -0
- package/src/runtime/memory/lib/tool-call-handler.mjs +2 -0
- package/src/runtime/memory/tool-defs.mjs +5 -0
- package/src/runtime/shared/agent-avatar-sprites.mjs +445 -0
- package/src/runtime/shared/agent-avatar.d.mts +64 -0
- package/src/runtime/shared/agent-avatar.mjs +320 -0
- package/src/runtime/shared/agent-kind.mjs +152 -0
- package/src/runtime/shared/agent-route-config.d.mts +4 -0
- package/src/runtime/shared/agent-route-config.mjs +30 -0
- package/src/runtime/shared/agent-store.mjs +132 -0
- package/src/runtime/shared/atomic-file.mjs +14 -1
- package/src/runtime/shared/bot-split-migration.mjs +457 -0
- package/src/runtime/shared/config-patch.mjs +20 -1
- package/src/runtime/shared/config.mjs +8 -1
- package/src/runtime/shared/connector-accounts.mjs +15 -0
- package/src/runtime/shared/connector-catalog.d.mts +51 -0
- package/src/runtime/shared/connector-catalog.mjs +228 -0
- package/src/runtime/shared/connector-triggers.d.mts +3 -0
- package/src/runtime/shared/connector-triggers.mjs +45 -0
- package/src/runtime/shared/err-text.mjs +3 -1
- package/src/runtime/shared/error-presentation.mjs +91 -6
- package/src/runtime/shared/google-oauth-client.mjs +24 -0
- package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
- package/src/runtime/shared/handoff-artifacts.mjs +61 -0
- package/src/runtime/shared/held-request-host.d.mts +12 -0
- package/src/runtime/shared/held-request-host.mjs +71 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +6 -1
- package/src/runtime/shared/llm/usage-accounting.mjs +7 -1
- package/src/runtime/shared/model-route-fallback.mjs +79 -7
- package/src/runtime/shared/plain-preview.d.mts +1 -0
- package/src/runtime/shared/plain-preview.mjs +33 -0
- package/src/runtime/shared/routine-limits.d.mts +11 -0
- package/src/runtime/shared/routine-limits.mjs +21 -0
- package/src/runtime/shared/routine-trigger.d.mts +10 -0
- package/src/runtime/shared/routine-trigger.mjs +44 -0
- package/src/runtime/shared/schedule-model-ref.mjs +9 -10
- package/src/runtime/shared/sensitive-path-rules.d.mts +15 -0
- package/src/runtime/shared/sensitive-path-rules.mjs +256 -0
- package/src/runtime/shared/slow-span.mjs +4 -12
- package/src/runtime/shared/statusline/statusline-segments.mjs +13 -1
- package/src/runtime/shared/transcript-metadata.mjs +10 -0
- package/src/runtime/shared/turn-changes.mjs +0 -8
- package/src/runtime/shared/usage-readout-totals.mjs +58 -0
- package/src/runtime/shared/user-data-guard.mjs +119 -38
- package/src/session-runtime/agent-home-delivery.mjs +826 -0
- package/src/session-runtime/agent-profile-changes.mjs +21 -0
- package/src/session-runtime/agent-room-delivery.mjs +133 -0
- package/src/session-runtime/agent-task-sessions.mjs +460 -0
- package/src/session-runtime/boot/apis.mjs +63 -18
- package/src/session-runtime/boot/facade.mjs +5 -8
- package/src/session-runtime/boot/infrastructure.mjs +28 -17
- package/src/session-runtime/boot/providers.mjs +14 -4
- package/src/session-runtime/boot/tools.mjs +3 -0
- package/src/session-runtime/channel-config-api.mjs +53 -5
- package/src/session-runtime/config-lifecycle.mjs +43 -2
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +36 -20
- package/src/session-runtime/cwd-plugins/plugin-status.mjs +6 -1
- package/src/session-runtime/handoff-attachments.mjs +593 -0
- package/src/session-runtime/held-requests.mjs +228 -0
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +121 -0
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +5 -1
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +31 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +56 -3
- package/src/session-runtime/internal-tool-executor.mjs +2 -0
- package/src/session-runtime/lifecycle/session-catalog.mjs +9 -0
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +4 -0
- package/src/session-runtime/lifecycle/shared.mjs +6 -0
- package/src/session-runtime/mcp-glue/server-catalog.mjs +8 -0
- package/src/session-runtime/mcp-glue/server-input.mjs +3 -0
- package/src/session-runtime/mcp-oauth-login.mjs +112 -15
- package/src/session-runtime/model-route/route-persist.mjs +2 -2
- package/src/session-runtime/model-route/route-tuning.mjs +7 -1
- package/src/session-runtime/model-route/set-route.mjs +35 -4
- package/src/session-runtime/native-web-search.mjs +2 -2
- package/src/session-runtime/new-session-config.mjs +6 -1
- package/src/session-runtime/owned-automation-run.mjs +229 -0
- package/src/session-runtime/resource-api.mjs +5 -0
- package/src/session-runtime/resource-mcp-api.mjs +107 -13
- package/src/session-runtime/resource-skills-api.mjs +12 -0
- package/src/session-runtime/room-member-gate.mjs +48 -0
- package/src/session-runtime/route-resolution.mjs +5 -11
- package/src/session-runtime/routine-home-turn.mjs +156 -0
- package/src/session-runtime/routine-owner.mjs +48 -0
- package/src/session-runtime/schedule-session-run.mjs +30 -64
- package/src/session-runtime/services/agent-tool/helpers.mjs +30 -1
- package/src/session-runtime/services/agent-tool/send-flow.mjs +11 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +19 -4
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +2 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +5 -8
- package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
- package/src/session-runtime/services/channel-admin.mjs +93 -24
- package/src/session-runtime/services/memory-access.mjs +37 -0
- package/src/session-runtime/services/provider-admin.mjs +74 -39
- package/src/session-runtime/services/schedule-access.mjs +91 -0
- package/src/session-runtime/session-create/session-options.mjs +34 -7
- package/src/session-runtime/session-hooks.mjs +16 -8
- package/src/session-runtime/setup-tool/executor.mjs +79 -6
- package/src/session-runtime/setup-tool/extended-actions.mjs +90 -20
- package/src/session-runtime/setup-tool/settings-contract.mjs +14 -6
- package/src/session-runtime/setup-tool/tool-defs.mjs +8 -0
- package/src/session-runtime/skills-api.mjs +16 -1
- package/src/session-runtime/tool-policy-refresh.mjs +249 -29
- package/src/session-runtime/turn/session-ops.mjs +8 -1
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +10 -2
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +14 -0
- package/src/session-runtime/webhook-session-run.mjs +49 -67
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +239 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +650 -25
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +67 -7
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +11 -1
- package/src/session-runtime/workflow-agents-api.mjs +4 -1
- package/src/standalone/daemon-agent-control.mjs +1 -8
- package/src/standalone/daemon-session-control.mjs +21 -4
- package/src/standalone/daemon.mjs +31 -5
- package/src/standalone/session-protocol.mjs +27 -0
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +46 -3
- package/src/standalone/session-service/agent-tree.mjs +4 -3
- package/src/standalone/session-service/session-calls/session-turns.mjs +28 -0
- package/src/standalone/session-service.mjs +19 -0
- package/src/tui/app/slash-commands.mjs +20 -0
- package/src/tui/dist/index.mjs +234 -95
- package/src/tui/session/context-state.mjs +6 -1
- package/src/tui/session/notice-surface.mjs +34 -4
- package/src/tui/session/queue-helpers.mjs +2 -1
- package/src/tui/session/session-api/extensions.mjs +4 -0
- package/src/tui/session/session-api/intake/submission.mjs +5 -0
- package/src/tui/session/session-api/integrations.mjs +8 -0
- package/src/tui/session/session-api/lifecycle/session-reset.mjs +13 -3
- package/src/tui/session/session-api/lifecycle.mjs +17 -1
- package/src/tui/session/session-api/routes.mjs +61 -5
- package/src/tui/session/session-flow/drain.mjs +12 -3
- package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -1
- package/src/tui/session/session-flow/queue/take-entries.mjs +3 -2
- package/src/tui/session/session-flow/steering.mjs +58 -24
- package/src/tui/session/tool-approval.mjs +26 -5
- package/src/tui/session/transcript-intake.mjs +8 -2
- package/src/tui/session/transcript-spill.mjs +5 -1
- package/src/tui/session/tui-steering-persist.mjs +65 -42
- package/src/tui/session/turn-live-call.mjs +64 -0
- package/src/tui/session/turn.mjs +34 -8
- package/src/session-runtime/automation-agents.mjs +0 -94
- package/src/session-runtime/automation-workflow.mjs +0 -59
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The summary's last-message line (a messenger row's preview): the newest
|
|
3
|
+
* user or assistant message with displayable text, and that line's routine
|
|
4
|
+
* provenance. Leaf module, shared by the summary index (store saves) and the
|
|
5
|
+
* catalog's strict file scan so both derive the same line.
|
|
6
|
+
*/
|
|
7
|
+
import { cleanSessionPreview, isSessionPreviewNoise, sessionMessageText } from '../runtime-core/session-text.mjs';
|
|
8
|
+
import { plainPreviewText } from '../../../shared/plain-preview.mjs';
|
|
9
|
+
import { isRoutineResultMessage, routineProvenance, routineResultNotice } from './routine-result.mjs';
|
|
10
|
+
|
|
11
|
+
export const LAST_MESSAGE_PREVIEW_MAX = 160;
|
|
12
|
+
|
|
13
|
+
/** cleanSessionPreview cut back to a word boundary within `max`. */
|
|
14
|
+
export function cleanSummaryPreview(text, max) {
|
|
15
|
+
const value = cleanSessionPreview(text, max);
|
|
16
|
+
return value.length > max
|
|
17
|
+
? value
|
|
18
|
+
.slice(0, max)
|
|
19
|
+
.replace(/\s+\S*$/, '')
|
|
20
|
+
.trim()
|
|
21
|
+
: value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The raw text a conversational message contributes as the last line (a
|
|
25
|
+
* routine result: its notice line); null for any other message. */
|
|
26
|
+
export function lastMessageRawText(message) {
|
|
27
|
+
if (message?.role !== 'user' && message?.role !== 'assistant') return null;
|
|
28
|
+
return isRoutineResultMessage(message) ? routineResultNotice(message).text : sessionMessageText(message.content);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The message's last-message line, '' when it has none (tool-only, synthetic). */
|
|
32
|
+
export function lastMessageLine(message) {
|
|
33
|
+
const raw = lastMessageRawText(message);
|
|
34
|
+
if (raw === null || isSessionPreviewNoise(raw)) return '';
|
|
35
|
+
return cleanSummaryPreview(plainPreviewText(raw), LAST_MESSAGE_PREVIEW_MAX);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** `{ lastMessagePreview, lastMessageRoutine }` of a transcript. */
|
|
39
|
+
export function lastMessageSummary(messages) {
|
|
40
|
+
const list = Array.isArray(messages) ? messages : [];
|
|
41
|
+
for (let index = list.length - 1; index >= 0; index -= 1) {
|
|
42
|
+
const line = lastMessageLine(list[index]);
|
|
43
|
+
if (line) return { lastMessagePreview: line, lastMessageRoutine: routineProvenance(list[index]) };
|
|
44
|
+
}
|
|
45
|
+
return { lastMessagePreview: '', lastMessageRoutine: null };
|
|
46
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { contextMeasurementStats } from '../../../../shared/context-measurement.mjs';
|
|
8
8
|
import { toolResultText } from './tool-result-text.mjs';
|
|
9
|
-
import {
|
|
9
|
+
import { createUserTextRestore } from './user-text.mjs';
|
|
10
10
|
import {
|
|
11
11
|
completionCardFromExecution,
|
|
12
12
|
parseModelVisibleCompletionWrapper,
|
|
@@ -38,8 +38,12 @@ import {
|
|
|
38
38
|
} from '../../../../shared/tool-execution-contract.mjs';
|
|
39
39
|
import { toolResultTerminalStatus } from '../../../../shared/tool-status.mjs';
|
|
40
40
|
import { transcriptAttachmentFields, transcriptRouteMetadataFields } from '../../../../shared/transcript-metadata.mjs';
|
|
41
|
+
import { routineTriggerField } from '../../../../shared/routine-trigger.mjs';
|
|
41
42
|
import { isImportedMessage } from '../imported-message.mjs';
|
|
42
43
|
import { isContextAckMessage } from '../compact/user-message-kind.mjs';
|
|
44
|
+
import { isRoutineResultMessage, routineResultNotice } from '../routine-result.mjs';
|
|
45
|
+
import { heldRequestNotice, isHeldRequestMessage } from '../human-gate/held-message.mjs';
|
|
46
|
+
import { isRoomRowMessage, roomRowItem } from '../agent-room.mjs';
|
|
43
47
|
|
|
44
48
|
/** A failed turn's recorded end: its route, reason and quota stop, the same
|
|
45
49
|
* fields the live turndone carries. */
|
|
@@ -86,6 +90,8 @@ function restoredTranscriptMetadata(message) {
|
|
|
86
90
|
// The attachments this prompt carried, by content hash: a restored row
|
|
87
91
|
// opens/downloads them by id from the attachment store.
|
|
88
92
|
...transcriptAttachmentFields(value),
|
|
93
|
+
// A routine fire this turn delivered: painted as its marker row.
|
|
94
|
+
...routineTriggerField(value),
|
|
89
95
|
// Skills this user turn explicitly loaded (header selection or `$name`).
|
|
90
96
|
...(Array.isArray(value.appliedSkills) && value.appliedSkills.length
|
|
91
97
|
? { appliedSkills: value.appliedSkills.filter((name) => typeof name === 'string' && name) }
|
|
@@ -332,7 +338,8 @@ function mergeRestoredToolItems(items) {
|
|
|
332
338
|
function restoredMessageItemUpperBound(message) {
|
|
333
339
|
const boundaries =
|
|
334
340
|
(Array.isArray(message?.meta?.sessionInheritances) ? message.meta.sessionInheritances.length : 0) +
|
|
335
|
-
(Array.isArray(message?.meta?.modelSwitches) ? message.meta.modelSwitches.length : 0)
|
|
341
|
+
(Array.isArray(message?.meta?.modelSwitches) ? message.meta.modelSwitches.length : 0) +
|
|
342
|
+
(Array.isArray(message?.meta?.workflowSwitches) ? message.meta.workflowSwitches.length : 0);
|
|
336
343
|
const completionCount =
|
|
337
344
|
Number(Boolean(message?.meta?.transcript?.completion)) +
|
|
338
345
|
(Array.isArray(message?.meta?.transcript?.previousCompletions)
|
|
@@ -372,17 +379,31 @@ function restoredNotificationToolItem(parsed, text, message, nextId, errorLabel)
|
|
|
372
379
|
};
|
|
373
380
|
}
|
|
374
381
|
|
|
375
|
-
function restoredUserTranscriptItems(message, nextId) {
|
|
382
|
+
function restoredUserTranscriptItems(message, nextId, userText) {
|
|
376
383
|
// Injected model-context payloads are model-visible but never user-authored:
|
|
377
384
|
// skill bodies (meta:'skill'), hook/system reminders, and tag-wrapped context
|
|
378
385
|
// blocks must not restore as user bubbles in any client.
|
|
379
386
|
if (message?.meta === 'skill' || message?.meta === 'hook') return [];
|
|
387
|
+
// A schedule's result in its owner's home thread: a notice row.
|
|
388
|
+
// A held request of an unattended run (human-gate/held-message.mjs) likewise.
|
|
389
|
+
if (isRoutineResultMessage(message) || isHeldRequestMessage(message)) {
|
|
390
|
+
const { messageId, at } = restoredTranscriptMetadata(message);
|
|
391
|
+
return [
|
|
392
|
+
{
|
|
393
|
+
kind: 'notice',
|
|
394
|
+
id: nextId(),
|
|
395
|
+
...(isHeldRequestMessage(message) ? heldRequestNotice(message) : routineResultNotice(message)),
|
|
396
|
+
...(messageId ? { messageId } : {}),
|
|
397
|
+
...(at ? { at } : {}),
|
|
398
|
+
},
|
|
399
|
+
];
|
|
400
|
+
}
|
|
380
401
|
// Imported prose renders verbatim: never a control row, card or display rewrite.
|
|
381
402
|
if (isImportedMessage(message)) {
|
|
382
403
|
const text = (typeof message.content === 'string' ? message.content : toolResultText(message.content)).trim();
|
|
383
404
|
return text ? [{ kind: 'user', id: nextId(), text, ...restoredTranscriptMetadata(message) }] : [];
|
|
384
405
|
}
|
|
385
|
-
const text =
|
|
406
|
+
const text = userText.text(message?.content).trim();
|
|
386
407
|
// Persisted async-completion wrappers ("Async shell task ... finished. Result:
|
|
387
408
|
// > ...") are the only durable record of a background completion — the live
|
|
388
409
|
// Response card is an event-time push that does not survive a transcript
|
|
@@ -413,7 +434,7 @@ function restoredUserTranscriptItems(message, nextId) {
|
|
|
413
434
|
}
|
|
414
435
|
const synthetic = parseSyntheticAgentMessage(text);
|
|
415
436
|
if (!synthetic) {
|
|
416
|
-
const imageRefs =
|
|
437
|
+
const imageRefs = userText.imageRefs(message?.content);
|
|
417
438
|
return [
|
|
418
439
|
{
|
|
419
440
|
kind: 'user',
|
|
@@ -421,6 +442,8 @@ function restoredUserTranscriptItems(message, nextId) {
|
|
|
421
442
|
text,
|
|
422
443
|
...restoredTranscriptMetadata(message),
|
|
423
444
|
...(imageRefs.length ? { imageRefs } : {}),
|
|
445
|
+
// Read by the model inside its running turn, as the live row says.
|
|
446
|
+
...(message?.meta?.source === 'steering' ? { steered: true } : {}),
|
|
424
447
|
},
|
|
425
448
|
];
|
|
426
449
|
}
|
|
@@ -440,7 +463,20 @@ export function modelSwitchItem(id, boundary) {
|
|
|
440
463
|
};
|
|
441
464
|
}
|
|
442
465
|
|
|
443
|
-
|
|
466
|
+
/** The transcript row of a mid-conversation workflow switch (live and restored). */
|
|
467
|
+
export function workflowSwitchItem(id, boundary) {
|
|
468
|
+
return {
|
|
469
|
+
kind: 'statusdone',
|
|
470
|
+
id,
|
|
471
|
+
status: 'workflow-switched',
|
|
472
|
+
label: 'Workflow switched',
|
|
473
|
+
from: boundary.from,
|
|
474
|
+
to: boundary.to,
|
|
475
|
+
at: boundary.at,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function restoreTranscriptRange(messages, start, sessionId, userText) {
|
|
444
480
|
const items = [];
|
|
445
481
|
const pendingToolCalls = new Map();
|
|
446
482
|
for (let index = start; index < messages.length; index += 1) {
|
|
@@ -450,8 +486,13 @@ function restoreTranscriptRange(messages, start, sessionId) {
|
|
|
450
486
|
// let a tail-only restore skip the prefix without first counting every old
|
|
451
487
|
// projected item.
|
|
452
488
|
const restoredId = () => `hist_${sessionId}_${index}_${++part}`;
|
|
489
|
+
// A group room's posted row (agent-room.mjs): one bubble or notice row.
|
|
490
|
+
if (isRoomRowMessage(message)) {
|
|
491
|
+
items.push(roomRowItem(message, restoredId()));
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
453
494
|
if (message?.role === 'user') {
|
|
454
|
-
items.push(...restoredUserTranscriptItems(message, restoredId));
|
|
495
|
+
items.push(...restoredUserTranscriptItems(message, restoredId, userText));
|
|
455
496
|
} else if (message?.role === 'assistant' && !isContextAckMessage(message, messages[index - 1])) {
|
|
456
497
|
items.push(...restoredAssistantTranscriptItems(message, restoredId));
|
|
457
498
|
items.push(...restoredToolCallItems(message, restoredId, pendingToolCalls));
|
|
@@ -477,6 +518,10 @@ function restoreTranscriptRange(messages, start, sessionId) {
|
|
|
477
518
|
if (!boundary?.provider || !boundary?.modelId) continue;
|
|
478
519
|
items.push(modelSwitchItem(restoredId(), boundary));
|
|
479
520
|
}
|
|
521
|
+
for (const boundary of Array.isArray(message?.meta?.workflowSwitches) ? message.meta.workflowSwitches : []) {
|
|
522
|
+
if (!boundary?.to) continue;
|
|
523
|
+
items.push(workflowSwitchItem(restoredId(), boundary));
|
|
524
|
+
}
|
|
480
525
|
if (message?.meta?.transcript?.previousCompletions?.length) {
|
|
481
526
|
const events = items.slice(eventStart);
|
|
482
527
|
if (events.every((item) => Number.isFinite(item.at))) {
|
|
@@ -490,9 +535,12 @@ function restoreTranscriptRange(messages, start, sessionId) {
|
|
|
490
535
|
|
|
491
536
|
export function restoreTranscriptItems(messages, { sessionId = 'session', itemLimit = Number.POSITIVE_INFINITY } = {}) {
|
|
492
537
|
const source = Array.isArray(messages) ? messages : [];
|
|
538
|
+
// One projection per load: a widened range reuses each prompt's parts and
|
|
539
|
+
// the paste-line reads share one budget.
|
|
540
|
+
const userText = createUserTextRestore();
|
|
493
541
|
const numericLimit = Number(itemLimit);
|
|
494
542
|
const limited = Number.isFinite(numericLimit) && numericLimit > 0;
|
|
495
|
-
if (!limited) return restoreTranscriptRange(source, 0, sessionId);
|
|
543
|
+
if (!limited) return restoreTranscriptRange(source, 0, sessionId, userText);
|
|
496
544
|
|
|
497
545
|
const limit = Math.max(1, Math.floor(numericLimit));
|
|
498
546
|
// Restore beyond the visible cap so a boundary tool run can merge exactly
|
|
@@ -506,7 +554,7 @@ export function restoreTranscriptItems(messages, { sessionId = 'session', itemLi
|
|
|
506
554
|
upperBound += restoredMessageItemUpperBound(source[start]);
|
|
507
555
|
}
|
|
508
556
|
|
|
509
|
-
let restored = restoreTranscriptRange(source, start, sessionId);
|
|
557
|
+
let restored = restoreTranscriptRange(source, start, sessionId, userText);
|
|
510
558
|
// Hidden/context messages and aggregate tool runs can make the cheap upper
|
|
511
559
|
// bound optimistic. Expand backward exponentially only when necessary.
|
|
512
560
|
let expansionTarget = Math.max(64, limit - restored.length);
|
|
@@ -516,7 +564,7 @@ export function restoreTranscriptItems(messages, { sessionId = 'session', itemLi
|
|
|
516
564
|
start -= 1;
|
|
517
565
|
expansion += restoredMessageItemUpperBound(source[start]);
|
|
518
566
|
}
|
|
519
|
-
restored = restoreTranscriptRange(source, start, sessionId);
|
|
567
|
+
restored = restoreTranscriptRange(source, start, sessionId, userText);
|
|
520
568
|
expansionTarget *= 2;
|
|
521
569
|
}
|
|
522
570
|
return restored.length > limit ? restored.slice(-limit) : restored;
|
|
@@ -116,14 +116,64 @@ function referencedFeedbackText(part) {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
const PASTED_LINES = /· (\d+) lines?$/;
|
|
120
|
+
const PASTED_TEXT_MAX_BYTES = 1024 * 1024;
|
|
121
|
+
/** Blob bytes one transcript load may read to count the lines of pastes
|
|
122
|
+
* whose stored name does not carry the count (pastes stored before it did). */
|
|
123
|
+
export const PASTED_LINE_READ_BUDGET_BYTES = 4 * 1024 * 1024;
|
|
124
|
+
|
|
125
|
+
/** Visible lines of a stored paste (as `pastedTextLineCount` counts them), or
|
|
126
|
+
* null when the count would take a read past the load's budget. */
|
|
127
|
+
function storedPasteLines(part, budget) {
|
|
128
|
+
const named = PASTED_LINES.exec(String(part.filename || ''));
|
|
129
|
+
if (named) return Number(named[1]);
|
|
130
|
+
const allowance = Math.min(PASTED_TEXT_MAX_BYTES, budget.remaining);
|
|
131
|
+
if (allowance <= 0) return null;
|
|
132
|
+
try {
|
|
133
|
+
const read = readAttachmentTextWithin(part, allowance);
|
|
134
|
+
if (read == null) return null;
|
|
135
|
+
budget.remaining -= Buffer.byteLength(read, 'utf8');
|
|
136
|
+
const value = read.replace(/\r\n?/g, '\n').replace(/\n+$/, '');
|
|
137
|
+
return value ? value.split('\n').length : 0;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
// A blob that failed its integrity check was read before it failed: the
|
|
140
|
+
// read counts against the cap (at most the allowance). A missing blob
|
|
141
|
+
// read nothing.
|
|
142
|
+
if (error?.code !== 'ENOENT') budget.remaining -= allowance;
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The chip token a stored pasted / attached text stood for in the live
|
|
148
|
+
* bubble (intake moved its body into the store and dropped the token). A
|
|
149
|
+
* paste whose line count is unknown shows as the plain `[Pasted text #n]`. */
|
|
150
|
+
function storedTextToken(part, number, budget) {
|
|
151
|
+
if (part?.type !== 'text') return '';
|
|
152
|
+
if (part.textKind === 'paste') {
|
|
153
|
+
const lines = storedPasteLines(part, budget);
|
|
154
|
+
return lines == null ? `[Pasted text #${number}]` : `[Pasted text #${number} +${lines} lines]`;
|
|
155
|
+
}
|
|
156
|
+
if (part.textKind === 'file') return `[File #${number}: ${part.filename || 'attachment.txt'}]`;
|
|
157
|
+
return '';
|
|
158
|
+
}
|
|
159
|
+
|
|
119
160
|
/** The display lines of a stored prompt's parts, and for each image part the
|
|
120
161
|
* line that draws it: the composer's metadata line right before it, else a
|
|
121
|
-
* bare line of its own (a bare line never stands for the next image).
|
|
122
|
-
|
|
162
|
+
* bare line of its own (a bare line never stands for the next image). A
|
|
163
|
+
* stored pasted or attached text shows as its chip token. */
|
|
164
|
+
function restoredUserParts(content, budget) {
|
|
123
165
|
const texts = [];
|
|
124
166
|
const images = [];
|
|
125
167
|
let pendingMetadata = '';
|
|
168
|
+
let storedTexts = 0;
|
|
126
169
|
for (const part of content) {
|
|
170
|
+
const token = isAttachmentReference(part) ? storedTextToken(part, storedTexts + 1, budget) : '';
|
|
171
|
+
if (token) {
|
|
172
|
+
storedTexts += 1;
|
|
173
|
+
texts.push(token);
|
|
174
|
+
pendingMetadata = '';
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
127
177
|
if (part?.type === 'image' || part?.type === 'input_image') {
|
|
128
178
|
let line = pendingMetadata;
|
|
129
179
|
if (!line) {
|
|
@@ -141,21 +191,48 @@ function restoredUserParts(content) {
|
|
|
141
191
|
return { texts, images };
|
|
142
192
|
}
|
|
143
193
|
|
|
194
|
+
/**
|
|
195
|
+
* The user-bubble projection of one transcript load: each stored prompt's
|
|
196
|
+
* parts are worked out once (its text and its image refs share them, and a
|
|
197
|
+
* load that widens its range sees the same messages again), and the blob
|
|
198
|
+
* reads that count paste lines share one byte budget.
|
|
199
|
+
*/
|
|
200
|
+
export function createUserTextRestore({ readBudgetBytes = PASTED_LINE_READ_BUDGET_BYTES } = {}) {
|
|
201
|
+
const budget = { remaining: readBudgetBytes };
|
|
202
|
+
const parts = new WeakMap();
|
|
203
|
+
const partsOf = (content) => {
|
|
204
|
+
let found = parts.get(content);
|
|
205
|
+
if (!found) {
|
|
206
|
+
found = restoredUserParts(content, budget);
|
|
207
|
+
parts.set(content, found);
|
|
208
|
+
}
|
|
209
|
+
return found;
|
|
210
|
+
};
|
|
211
|
+
return {
|
|
212
|
+
text(content) {
|
|
213
|
+
if (!Array.isArray(content)) {
|
|
214
|
+
return withVisualizeFeedbackDisplay(typeof content === 'string' ? content : toolResultText(content));
|
|
215
|
+
}
|
|
216
|
+
return withVisualizeFeedbackDisplay(partsOf(content).texts.join('\n'));
|
|
217
|
+
},
|
|
218
|
+
/** The stored original of each image the restored bubble draws, by the
|
|
219
|
+
* `[Image: …]` line that draws it. Every image keeps its slot (one
|
|
220
|
+
* without a stored original has no `ref`) so later images do not shift
|
|
221
|
+
* onto it; empty when no image has a stored original. */
|
|
222
|
+
imageRefs(content) {
|
|
223
|
+
if (!Array.isArray(content)) return [];
|
|
224
|
+
const slots = partsOf(content).images.map(({ part, line }) =>
|
|
225
|
+
isAttachmentReference(part) ? { line, ref: part.attachmentRef } : { line }
|
|
226
|
+
);
|
|
227
|
+
return slots.some((slot) => slot.ref) ? slots : [];
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
144
232
|
export function restoredUserText(content) {
|
|
145
|
-
|
|
146
|
-
return withVisualizeFeedbackDisplay(typeof content === 'string' ? content : toolResultText(content));
|
|
147
|
-
}
|
|
148
|
-
return withVisualizeFeedbackDisplay(restoredUserParts(content).texts.join('\n'));
|
|
233
|
+
return createUserTextRestore().text(content);
|
|
149
234
|
}
|
|
150
235
|
|
|
151
|
-
/** The stored original of each image the restored bubble draws, by the
|
|
152
|
-
* `[Image: …]` line that draws it. Every image keeps its slot (one without a
|
|
153
|
-
* stored original has no `ref`) so later images do not shift onto it; empty
|
|
154
|
-
* when no image has a stored original. */
|
|
155
236
|
export function restoredUserImageRefs(content) {
|
|
156
|
-
|
|
157
|
-
const slots = restoredUserParts(content).images.map(({ part, line }) =>
|
|
158
|
-
isAttachmentReference(part) ? { line, ref: part.attachmentRef } : { line }
|
|
159
|
-
);
|
|
160
|
-
return slots.some((slot) => slot.ref) ? slots : [];
|
|
237
|
+
return createUserTextRestore().imageRefs(content);
|
|
161
238
|
}
|
|
@@ -339,11 +339,23 @@ export function releaseShellJobWorkspace(jobId) {
|
|
|
339
339
|
if (workspaceLeases.get(key) === leave) workspaceLeases.delete(key);
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
/** True once `sessionId` closed in this runtime. */
|
|
343
|
+
export function isShellOwnerSessionClosed(sessionId) {
|
|
344
|
+
return closedOwnerSessions.has(String(sessionId || '').trim());
|
|
345
|
+
}
|
|
346
|
+
|
|
342
347
|
/** Flag a still-open job record as "termination unconfirmed" for the readouts. */
|
|
343
348
|
export function markShellJobTerminationUnconfirmed(jobId) {
|
|
344
349
|
return patchRunningShellJobRecord(jobId, { terminationUnconfirmed: true });
|
|
345
350
|
}
|
|
346
351
|
|
|
352
|
+
/** Record the pids a held job's termination watch last found alive, so a
|
|
353
|
+
* reader in another process can tell a live hold from a stale one. */
|
|
354
|
+
export function noteShellJobSurvivors(jobId, pids) {
|
|
355
|
+
const survivorPids = (pids || []).map(Number).filter((pid) => Number.isInteger(pid) && pid > 0);
|
|
356
|
+
return patchRunningShellJobRecord(jobId, { survivorPids });
|
|
357
|
+
}
|
|
358
|
+
|
|
347
359
|
function patchRunningShellJobRecord(jobId, patch) {
|
|
348
360
|
const key = validJobId(jobId);
|
|
349
361
|
if (!key) return Promise.resolve(false);
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
//
|
|
9
9
|
// MIXDOG_SEARCH_SERVER=0 disables; MIXDOG_SEARCH_SERVER_BIN overrides the
|
|
10
10
|
// executable.
|
|
11
|
-
import { existsSync } from 'node:fs';
|
|
11
|
+
import { existsSync, lstatSync, mkdirSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { basename, dirname, isAbsolute, join, resolve as resolvePath, sep } from 'node:path';
|
|
12
13
|
import { bindChildLifecycle, createNativeSearchTransport } from './native-search-transport.mjs';
|
|
13
14
|
import { invalidateBuiltinResultCache } from './cache-layers.mjs';
|
|
14
15
|
import { getPluginData } from '../../config.mjs';
|
|
@@ -254,6 +255,32 @@ export async function shutdownNativeSearchServer(reason = 'process-exit', timeou
|
|
|
254
255
|
|
|
255
256
|
export { bindChildLifecycle as _bindNativeSearchServerLifecycle };
|
|
256
257
|
|
|
258
|
+
// Inventories persisted before search roots were canonicalized (see
|
|
259
|
+
// canonicalPath) can hold another spelling of a folder's files under the
|
|
260
|
+
// folder's canonical key; loading one would keep serving every file twice.
|
|
261
|
+
// Drop that snapshot once per data dir, before a server of this process loads
|
|
262
|
+
// it. The marker records that the remaining snapshot was written by servers
|
|
263
|
+
// that only ever saw canonical roots.
|
|
264
|
+
const INVENTORY_SNAPSHOT_FILE = 'file-inventories-v1.bin';
|
|
265
|
+
const CANONICAL_ROOTS_MARKER = 'file-inventories-v1.canonical-roots';
|
|
266
|
+
let _inventorySnapshotChecked = false;
|
|
267
|
+
|
|
268
|
+
function dropAliasSpelledInventorySnapshot() {
|
|
269
|
+
if (_inventorySnapshotChecked) return;
|
|
270
|
+
_inventorySnapshotChecked = true;
|
|
271
|
+
const dir = join(getPluginData(), 'search-index');
|
|
272
|
+
const marker = join(dir, CANONICAL_ROOTS_MARKER);
|
|
273
|
+
if (existsSync(marker)) return;
|
|
274
|
+
try {
|
|
275
|
+
rmSync(join(dir, INVENTORY_SNAPSHOT_FILE), { force: true });
|
|
276
|
+
mkdirSync(dir, { recursive: true });
|
|
277
|
+
writeFileSync(marker, '');
|
|
278
|
+
} catch {
|
|
279
|
+
// A snapshot that cannot be removed now (held open by another server) is
|
|
280
|
+
// retried by the next process; search itself does not depend on it.
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
257
284
|
function _ensureServer() {
|
|
258
285
|
if (_server) return _server;
|
|
259
286
|
if (
|
|
@@ -261,6 +288,7 @@ function _ensureServer() {
|
|
|
261
288
|
Date.now() - _lastFailureAt < RESTART_BACKOFF_MS
|
|
262
289
|
)
|
|
263
290
|
return null;
|
|
291
|
+
dropAliasSpelledInventorySnapshot();
|
|
264
292
|
let transport;
|
|
265
293
|
try {
|
|
266
294
|
transport = createNativeSearchTransport({
|
|
@@ -633,15 +661,93 @@ function consumedDeadline(response, deadlineMs) {
|
|
|
633
661
|
);
|
|
634
662
|
}
|
|
635
663
|
|
|
664
|
+
// The server walks a root as the request spells it, but its file-change
|
|
665
|
+
// updates carry the resolved path. Any other spelling of the same folder —
|
|
666
|
+
// letter case on Windows, a junction or symlink, a `..` segment — therefore
|
|
667
|
+
// listed every file twice (one copy per spelling). Roots are resolved once,
|
|
668
|
+
// here, so the first walk, later updates and the inventory key agree.
|
|
669
|
+
function canonicalPath(path) {
|
|
670
|
+
try {
|
|
671
|
+
return realpathSync.native(path);
|
|
672
|
+
} catch {
|
|
673
|
+
return path;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// One positional operand resolved against the canonical cwd. An operand that
|
|
678
|
+
// is already canonical is sent unchanged. An exact file that is itself a
|
|
679
|
+
// symlink keeps its own name: --glob/--type filter the name the caller asked
|
|
680
|
+
// for. `file` tells restoreOperandSpelling how the server prints the operand.
|
|
681
|
+
function canonicalOperand(cwd, operand) {
|
|
682
|
+
const joined = isAbsolute(operand) ? operand : `${cwd}${sep}${operand}`;
|
|
683
|
+
let canonical;
|
|
684
|
+
let file;
|
|
685
|
+
try {
|
|
686
|
+
file = statSync(joined).isFile();
|
|
687
|
+
canonical =
|
|
688
|
+
file && lstatSync(joined).isSymbolicLink()
|
|
689
|
+
? join(realpathSync.native(dirname(joined)), basename(joined))
|
|
690
|
+
: realpathSync.native(joined);
|
|
691
|
+
} catch {
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
if (canonical === (isAbsolute(operand) ? operand : resolvePath(cwd, operand))) return null;
|
|
695
|
+
// The server prints results with `/` when the operand only uses `/`.
|
|
696
|
+
const sent = operand.includes('/') && !operand.includes('\\') ? canonical.replaceAll('\\', '/') : canonical;
|
|
697
|
+
if (sent === operand) return null;
|
|
698
|
+
// A directory is printed with its trailing separators trimmed.
|
|
699
|
+
const printed = (path) => (file ? path : path.replace(/[\\/]+$/, ''));
|
|
700
|
+
return { sent, from: printed(sent), to: printed(operand), file };
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Canonical cwd and operands for a search request, plus the operands whose
|
|
704
|
+
// spelling restoreOperandSpelling puts back into the result lines. Positional
|
|
705
|
+
// operands follow the last `--` (search-builders emits it before them; a `--`
|
|
706
|
+
// pattern is always an `-e` value before it).
|
|
707
|
+
function canonicalSearchRequest(cwd, argsList) {
|
|
708
|
+
const root = canonicalPath(cwd);
|
|
709
|
+
const args = argsList.map(String);
|
|
710
|
+
const remaps = [];
|
|
711
|
+
const operandsAt = args.lastIndexOf('--') + 1;
|
|
712
|
+
if (operandsAt > 0) {
|
|
713
|
+
for (let i = operandsAt; i < args.length; i += 1) {
|
|
714
|
+
const remap = canonicalOperand(root, args[i]);
|
|
715
|
+
if (!remap) continue;
|
|
716
|
+
args[i] = remap.sent;
|
|
717
|
+
remaps.push(remap);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
remaps.sort((a, b) => b.from.length - a.from.length);
|
|
721
|
+
return { cwd: root, args, remaps };
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Results under a canonicalized operand are printed by the server as
|
|
725
|
+
// `<operand><sep><relative>` (a directory) or as the operand itself followed
|
|
726
|
+
// by `:`/`-` fields (an exact file); show them under the caller's spelling.
|
|
727
|
+
function restoreOperandSpelling(lines, remaps) {
|
|
728
|
+
if (remaps.length === 0) return lines;
|
|
729
|
+
return lines.map((line) => {
|
|
730
|
+
if (typeof line !== 'string') return line;
|
|
731
|
+
for (const { from, to, file } of remaps) {
|
|
732
|
+
if (!line.startsWith(from)) continue;
|
|
733
|
+
const rest = line.slice(from.length);
|
|
734
|
+
if (rest !== '' && !(file ? /^[:-]/ : /^[\\/]/).test(rest)) continue;
|
|
735
|
+
return `${to}${rest}`;
|
|
736
|
+
}
|
|
737
|
+
return line;
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
|
|
636
741
|
/** Returns a runRgWindowedLines-shaped result, or null when the native server
|
|
637
742
|
* is disabled or the response carries no line list. */
|
|
638
743
|
export async function tryServeSearch(argsList, execOptions = {}, opts = {}) {
|
|
639
744
|
if (searchServerDisabled()) return null;
|
|
640
745
|
const deadlineMs = callerDeadlineMs(execOptions, REQUEST_TIMEOUT_MS);
|
|
746
|
+
const canonical = canonicalSearchRequest(String(execOptions.cwd || process.cwd()), argsList);
|
|
641
747
|
const buildRequest = (server, remaining) => ({
|
|
642
748
|
id: ++server.sequence,
|
|
643
|
-
cwd:
|
|
644
|
-
args:
|
|
749
|
+
cwd: canonical.cwd,
|
|
750
|
+
args: [...canonical.args],
|
|
645
751
|
offset: Math.max(0, Math.floor(Number(opts.offset) || 0)),
|
|
646
752
|
limit: Number.isFinite(Number(opts.limit)) && Number(opts.limit) > 0 ? Math.floor(Number(opts.limit)) : 0,
|
|
647
753
|
deadlineMs: softDeadlineMs(remaining),
|
|
@@ -701,7 +807,7 @@ export async function tryServeSearch(argsList, execOptions = {}, opts = {}) {
|
|
|
701
807
|
? response.walkErrorDetails.map(String).slice(0, 8)
|
|
702
808
|
: [];
|
|
703
809
|
return {
|
|
704
|
-
lines: response.lines,
|
|
810
|
+
lines: restoreOperandSpelling(response.lines, canonical.remaps),
|
|
705
811
|
complete: response.complete === true,
|
|
706
812
|
totalSeen: Math.max(0, Math.floor(Number(response.totalSeen) || 0)),
|
|
707
813
|
partial: response.partial === true,
|
|
@@ -731,7 +837,8 @@ export async function tryServeFuzzySearch(args, execOptions = {}) {
|
|
|
731
837
|
const response = await requestNativeWithRestart(
|
|
732
838
|
(server, remaining) => ({
|
|
733
839
|
id: ++server.sequence,
|
|
734
|
-
|
|
840
|
+
// Matches are relative to cwd, so only the root needs canonicalizing.
|
|
841
|
+
cwd: canonicalPath(String(args?.cwd || execOptions.cwd || process.cwd())),
|
|
735
842
|
fuzzy: String(args?.query || ''),
|
|
736
843
|
limit: Math.max(1, Math.min(1_000, Math.floor(Number(args?.limit) || 25))),
|
|
737
844
|
hidden: args?.hidden !== false,
|
|
@@ -813,4 +920,5 @@ export function _resetNativeSearchClientForTest() {
|
|
|
813
920
|
_binaryResolveStarted = false;
|
|
814
921
|
_warmPromise = null;
|
|
815
922
|
_lastTimeoutRecycleAt = 0;
|
|
923
|
+
_inventorySnapshotChecked = false;
|
|
816
924
|
}
|
|
@@ -28,8 +28,10 @@ import {
|
|
|
28
28
|
adoptShellWorkspaceAdmission,
|
|
29
29
|
clearTerminationHold,
|
|
30
30
|
completeShellJobRecord,
|
|
31
|
+
isShellOwnerSessionClosed,
|
|
31
32
|
listShellJobRecords,
|
|
32
33
|
markShellJobTerminationUnconfirmed,
|
|
34
|
+
noteShellJobSurvivors,
|
|
33
35
|
publishShellJobRecord,
|
|
34
36
|
registerTerminationHold,
|
|
35
37
|
retireShellJobRecord,
|
|
@@ -194,6 +196,8 @@ function trackShellJobRecord(task, options) {
|
|
|
194
196
|
sweep: killed,
|
|
195
197
|
onTerminated: finish,
|
|
196
198
|
releaseRequested: () => takeReleaseRequest(jobId),
|
|
199
|
+
ownerClosed: () => isShellOwnerSessionClosed(options?.ownerSessionId),
|
|
200
|
+
onSurvivors: (pids) => void noteShellJobSurvivors(jobId, pids),
|
|
197
201
|
onUnconfirmed: () => {
|
|
198
202
|
void markShellJobTerminationUnconfirmed(jobId);
|
|
199
203
|
registerTerminationHold(jobId, {
|
|
@@ -195,12 +195,22 @@ function sweepLiveIdentities(tracker) {
|
|
|
195
195
|
*
|
|
196
196
|
* `sweep` (a killed tree): a pass that finds captured identities alive
|
|
197
197
|
* terminates them and observes again in the same pass, so a cancellation
|
|
198
|
-
* ends with the tree gone instead of a held folder over live processes.
|
|
198
|
+
* ends with the tree gone instead of a held folder over live processes.
|
|
199
|
+
*
|
|
200
|
+
* `ownerClosed`: once the owning session has closed and the root has
|
|
201
|
+
* exited, a tree without a complete pre-kill snapshot is observed after
|
|
202
|
+
* exit (what is still linked to the root) instead of answering 'unknown'
|
|
203
|
+
* forever — no one is left to release it, and the close must end in a
|
|
204
|
+
* definitive state. `onSurvivors(pids)` reports the pids a pass that is
|
|
205
|
+
* not 'gone' found alive, whenever that set changes, so out-of-process
|
|
206
|
+
* readers can tell a live hold from a stale one. */
|
|
199
207
|
export function watchTermination({
|
|
200
208
|
tracker,
|
|
201
209
|
rootExited = () => true,
|
|
202
210
|
onTerminated,
|
|
203
211
|
onUnconfirmed = null,
|
|
212
|
+
onSurvivors = null,
|
|
213
|
+
ownerClosed = null,
|
|
204
214
|
releaseRequested = null,
|
|
205
215
|
intervalMs = TERMINATION_RECHECK_MS,
|
|
206
216
|
sweep = false,
|
|
@@ -208,6 +218,7 @@ export function watchTermination({
|
|
|
208
218
|
let timer = null;
|
|
209
219
|
let stopped = false;
|
|
210
220
|
let reportedUnconfirmed = false;
|
|
221
|
+
let survivorKey = null;
|
|
211
222
|
const stop = () => {
|
|
212
223
|
stopped = true;
|
|
213
224
|
if (timer) clearTimeout(timer);
|
|
@@ -223,6 +234,7 @@ export function watchTermination({
|
|
|
223
234
|
if (releaseRequested?.()) state = 'gone';
|
|
224
235
|
else
|
|
225
236
|
try {
|
|
237
|
+
if (!tracker.complete && rootExited() && ownerClosed?.()) tracker.acceptExitObservation();
|
|
226
238
|
observation = tracker.observe({ rootExited: rootExited() });
|
|
227
239
|
observation.catch(() => {});
|
|
228
240
|
// A pass that has not answered by the next cadence tick is unknown.
|
|
@@ -250,6 +262,18 @@ export function watchTermination({
|
|
|
250
262
|
/* presentation only */
|
|
251
263
|
}
|
|
252
264
|
}
|
|
265
|
+
if (state !== 'gone' && onSurvivors) {
|
|
266
|
+
const pids = tracker.live.map((entry) => entry.pid);
|
|
267
|
+
const key = pids.join(',');
|
|
268
|
+
if (key !== survivorKey) {
|
|
269
|
+
survivorKey = key;
|
|
270
|
+
try {
|
|
271
|
+
onSurvivors(pids);
|
|
272
|
+
} catch {
|
|
273
|
+
/* presentation only */
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
253
277
|
if (state === 'gone') {
|
|
254
278
|
try {
|
|
255
279
|
await onTerminated();
|
|
@@ -60,6 +60,14 @@ export async function promoteWithRetry(
|
|
|
60
60
|
*/
|
|
61
61
|
export function killAfterFailedPromotion(run, reason, promotionFailure) {
|
|
62
62
|
run.autoBackgrounded = false;
|
|
63
|
+
// The command finished while it was being promoted (the spawn server
|
|
64
|
+
// releases a foreground task's record at its exit, so the promotion found
|
|
65
|
+
// none): report that completion. Killing the exited root would have no
|
|
66
|
+
// pre-kill snapshot and hold the workspace as unconfirmed for good.
|
|
67
|
+
if (run.child.exitCode != null || run.child.signalCode != null) {
|
|
68
|
+
if (run.promotionClose) void run.settle(...run.promotionClose);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
63
71
|
if (reason === 'timeout') {
|
|
64
72
|
run.timedOut = true;
|
|
65
73
|
treeKillForceSettle(run, 'timeout');
|
|
@@ -8,6 +8,8 @@ import { unregisterOwnedShellTree } from './owned-shell-trees.mjs';
|
|
|
8
8
|
import {
|
|
9
9
|
adoptShellWorkspaceAdmission,
|
|
10
10
|
clearTerminationHold,
|
|
11
|
+
isShellOwnerSessionClosed,
|
|
12
|
+
noteShellJobSurvivors,
|
|
11
13
|
registerTerminationHold,
|
|
12
14
|
takeReleaseRequest,
|
|
13
15
|
} from '../builtin/lib/shell-job-records.mjs';
|
|
@@ -60,6 +62,8 @@ export function releaseAdmissionOnConfirmedTermination(run, { markNow = true } =
|
|
|
60
62
|
sweep: run.killed === true,
|
|
61
63
|
onTerminated: release,
|
|
62
64
|
onUnconfirmed: markNow ? null : () => keepTerminationUnconfirmedRecord(run),
|
|
65
|
+
onSurvivors: (pids) => void noteShellJobSurvivors(run.foregroundRecordId, pids),
|
|
66
|
+
ownerClosed: () => isShellOwnerSessionClosed(run.foregroundRecordInput?.ownerSessionId),
|
|
63
67
|
releaseRequested: () => takeReleaseRequest(run.foregroundRecordId),
|
|
64
68
|
});
|
|
65
69
|
}
|
|
@@ -164,6 +168,8 @@ export function createSettle(run, releasePromotedCapture) {
|
|
|
164
168
|
if (run.settled) return;
|
|
165
169
|
if (run.ownedTree) unregisterOwnedShellTree(run.foregroundRecordId, run.ownedTree);
|
|
166
170
|
if (run.autoBackgrounded) {
|
|
171
|
+
// A failed promotion settles the run with this close instead.
|
|
172
|
+
run.promotionClose = [exitCode, signal];
|
|
167
173
|
releasePromotedCapture();
|
|
168
174
|
return;
|
|
169
175
|
}
|