mixdog 1.2.6 → 1.3.0
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 +19 -0
- 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 +7 -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 +11 -4
- package/src/defaults/skills/setup-guide/SKILL.md +175 -0
- package/src/defaults/skills/setup-guide/model-recommendations.json +333 -0
- package/src/lib/rules-builder.cjs +55 -6
- 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 +21 -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 +182 -13
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +4 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +215 -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-login.mjs +10 -3
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- 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/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 +12 -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 +10 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +4 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +50 -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 +162 -0
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +641 -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 +165 -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/pre-dispatch-deny.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +16 -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 +7 -0
- 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/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 +27 -5
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +55 -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/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 +24 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +32 -5
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +70 -0
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +277 -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 +44 -1
- 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/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/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-route-config.d.mts +3 -0
- package/src/runtime/shared/agent-route-config.mjs +16 -0
- package/src/runtime/shared/atomic-file.mjs +14 -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 +52 -0
- package/src/runtime/shared/connector-catalog.mjs +230 -0
- package/src/runtime/shared/connector-triggers.d.mts +3 -0
- package/src/runtime/shared/connector-triggers.mjs +43 -0
- package/src/runtime/shared/google-oauth-client.mjs +24 -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/plain-preview.d.mts +1 -0
- package/src/runtime/shared/plain-preview.mjs +28 -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/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 +56 -0
- package/src/runtime/shared/user-data-guard.mjs +114 -36
- package/src/session-runtime/agent-home-delivery.mjs +459 -0
- package/src/session-runtime/agent-profile-changes.mjs +21 -0
- package/src/session-runtime/agent-room-delivery.mjs +92 -0
- package/src/session-runtime/agent-task-sessions.mjs +200 -0
- package/src/session-runtime/boot/apis.mjs +50 -18
- package/src/session-runtime/boot/facade.mjs +5 -8
- package/src/session-runtime/boot/infrastructure.mjs +28 -17
- package/src/session-runtime/boot/tools.mjs +3 -0
- package/src/session-runtime/channel-config-api.mjs +53 -5
- 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/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 +31 -2
- 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/set-route.mjs +14 -3
- 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/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 +10 -0
- 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/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 +68 -6
- package/src/session-runtime/setup-tool/extended-actions.mjs +72 -14
- package/src/session-runtime/setup-tool/settings-contract.mjs +8 -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 +238 -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 +3 -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 +556 -22
- 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 +11 -2
- package/src/standalone/daemon.mjs +18 -4
- package/src/standalone/session-protocol.mjs +27 -0
- package/src/standalone/session-service/session-calls/session-turns.mjs +9 -0
- package/src/standalone/session-service.mjs +19 -0
- package/src/tui/app/slash-commands.mjs +20 -0
- package/src/tui/dist/index.mjs +164 -86
- package/src/tui/session/context-state.mjs +6 -1
- package/src/tui/session/notice-surface.mjs +35 -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 +48 -3
- 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 +6 -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 +56 -0
- package/src/tui/session/turn.mjs +22 -6
- package/src/session-runtime/automation-agents.mjs +0 -94
- package/src/session-runtime/automation-workflow.mjs +0 -59
|
@@ -21,6 +21,8 @@ import { readStrictRecordFields } from './store-record-strict-scan.mjs';
|
|
|
21
21
|
import { isAgentOnlySession, isRootLeadSession, sessionVisibility } from './store-summary-visibility.mjs';
|
|
22
22
|
import { applySummaryLogText, summaryLogPath } from './store-summary-log.mjs';
|
|
23
23
|
import { dataDir } from './store-summary-locations.mjs';
|
|
24
|
+
import { normalizeRoutineProvenance } from './routine-result.mjs';
|
|
25
|
+
import { lastMessageLine, lastMessageSummary } from './store-summary-last-message.mjs';
|
|
24
26
|
import {
|
|
25
27
|
desktopSession,
|
|
26
28
|
importKey,
|
|
@@ -31,6 +33,7 @@ import {
|
|
|
31
33
|
workspaceSummary,
|
|
32
34
|
} from './store-summary-fields.mjs';
|
|
33
35
|
import { isRealTurnMessage } from './compact/user-message-kind.mjs';
|
|
36
|
+
import { sessionModeOf } from './agent-mode.mjs';
|
|
34
37
|
import { byActivityThenId } from './catalog-db/schema.mjs';
|
|
35
38
|
import {
|
|
36
39
|
sessionStampOf,
|
|
@@ -191,6 +194,7 @@ export function normalizedRow(row) {
|
|
|
191
194
|
status: String(row.status || (row.closed === true ? 'closed' : 'idle')),
|
|
192
195
|
owner: row.owner || 'user',
|
|
193
196
|
agent: row.agent || null,
|
|
197
|
+
sessionMode: sessionModeOf(row),
|
|
194
198
|
sourceType: row.sourceType || null,
|
|
195
199
|
sourceName: row.sourceName || null,
|
|
196
200
|
scopeKey: row.scopeKey || null,
|
|
@@ -210,6 +214,10 @@ export function normalizedRow(row) {
|
|
|
210
214
|
realTurnCount: realTurnCount(row),
|
|
211
215
|
title: cleanText(row.title, 100),
|
|
212
216
|
preview: cleanText(row.preview),
|
|
217
|
+
lastMessagePreview: cleanText(row.lastMessagePreview),
|
|
218
|
+
// Null for any line that is not a routine result, and on rows written
|
|
219
|
+
// before the marker existed (they show their plain text).
|
|
220
|
+
lastMessageRoutine: normalizeRoutineProvenance(row.lastMessageRoutine),
|
|
213
221
|
generation: typeof row.generation === 'number' ? row.generation : 0,
|
|
214
222
|
storageMtimeMs: positiveNumber(row.storageMtimeMs, 0),
|
|
215
223
|
storageSize: positiveNumber(row.storageSize, 0),
|
|
@@ -224,8 +232,9 @@ function leadRows(rows) {
|
|
|
224
232
|
}
|
|
225
233
|
|
|
226
234
|
// What a row derivation decodes of a record (store-record-strict-scan.mjs):
|
|
227
|
-
// rowFromSession reads its user messages whole
|
|
228
|
-
//
|
|
235
|
+
// rowFromSession reads its user messages whole, the newest messages back to
|
|
236
|
+
// the one with a last-message line whole too, only the role of the rest, and
|
|
237
|
+
// never these top-level fields — together ~10% of a record, mostly tool
|
|
229
238
|
// catalogs. Every other top-level field is decoded, so a field a row starts
|
|
230
239
|
// to read is there without touching this list.
|
|
231
240
|
const CATALOG_RECORD_READ = {
|
|
@@ -239,6 +248,7 @@ const CATALOG_RECORD_READ = {
|
|
|
239
248
|
'pendingDeferredToolDelta',
|
|
240
249
|
]),
|
|
241
250
|
fullMessageRoles: new Set(['user']),
|
|
251
|
+
fullTailUntil: (message) => Boolean(lastMessageLine(message)),
|
|
242
252
|
};
|
|
243
253
|
|
|
244
254
|
function rowFromSession(session) {
|
|
@@ -256,11 +266,15 @@ function rowFromSession(session) {
|
|
|
256
266
|
)
|
|
257
267
|
.map((message) => cleanText(messageText(message.content)))
|
|
258
268
|
.find(Boolean) || '';
|
|
269
|
+
// The last-message line comes from the transcript like the summary index
|
|
270
|
+
// derives it; without it a session saved after the index was written lost
|
|
271
|
+
// its contact-row preview whenever its row came from this file read.
|
|
259
272
|
return normalizedRow({
|
|
260
273
|
...session,
|
|
261
274
|
messageCount: messages.filter((message) => message?.role === 'user' || message?.role === 'assistant').length,
|
|
262
275
|
realTurnCount: messages.filter(isRealTurnMessage).length,
|
|
263
276
|
preview,
|
|
277
|
+
...lastMessageSummary(messages),
|
|
264
278
|
});
|
|
265
279
|
}
|
|
266
280
|
|
|
@@ -16,8 +16,9 @@ import {
|
|
|
16
16
|
writeJsonAtomicSync,
|
|
17
17
|
} from '../../../shared/atomic-file.mjs';
|
|
18
18
|
import { applySummaryLogText, encodeSummaryOps, summaryLogPath } from './store-summary-log.mjs';
|
|
19
|
-
import {
|
|
19
|
+
import { isSessionPreviewNoise, sessionMessageText } from '../runtime-core/session-text.mjs';
|
|
20
20
|
import { sessionVisibility } from './store-summary-visibility.mjs';
|
|
21
|
+
import { sessionModeOf } from './agent-mode.mjs';
|
|
21
22
|
import {
|
|
22
23
|
importKey,
|
|
23
24
|
inheritedFromSessionId,
|
|
@@ -27,6 +28,8 @@ import {
|
|
|
27
28
|
workspaceSummary,
|
|
28
29
|
} from './store-summary-fields.mjs';
|
|
29
30
|
import { isRealTurnMessage } from './compact/user-message-kind.mjs';
|
|
31
|
+
import { isRoutineResultMessage, normalizeRoutineProvenance, routineProvenance } from './routine-result.mjs';
|
|
32
|
+
import { LAST_MESSAGE_PREVIEW_MAX, cleanSummaryPreview, lastMessageRawText } from './store-summary-last-message.mjs';
|
|
30
33
|
|
|
31
34
|
export const SESSION_SUMMARY_INDEX_VERSION = 2;
|
|
32
35
|
|
|
@@ -37,15 +40,7 @@ export function summaryIndexPath() {
|
|
|
37
40
|
return join(getPluginData(), 'session-summaries.json');
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
const value = cleanSessionPreview(text, max);
|
|
42
|
-
return value.length > max
|
|
43
|
-
? value
|
|
44
|
-
.slice(0, max)
|
|
45
|
-
.replace(/\s+\S*$/, '')
|
|
46
|
-
.trim()
|
|
47
|
-
: value;
|
|
48
|
-
}
|
|
43
|
+
const _computeCleanPreview = cleanSummaryPreview;
|
|
49
44
|
|
|
50
45
|
// Every index flush re-normalizes EVERY row's stored title and preview, and
|
|
51
46
|
// cleaning runs a chain of regex replaces that each allocate a copy. Those
|
|
@@ -79,8 +74,13 @@ const sessionMessageProjectionMemo = new WeakMap();
|
|
|
79
74
|
// re-cleaned.
|
|
80
75
|
const messagePreviewMemo = new WeakMap();
|
|
81
76
|
|
|
77
|
+
// Routine results (a schedule's output in its owner's home thread) follow ONE
|
|
78
|
+
// rule here: each is exactly one message of `messageCount` (so a thread's
|
|
79
|
+
// unread count, messageCount minus the read cursor, rises by one per result),
|
|
80
|
+
// never a real turn (`realTurnCount`), never the first-prompt `preview` the
|
|
81
|
+
// title falls back to, and shown as its notice line in `lastMessagePreview`.
|
|
82
82
|
function _previewFromMessage(message) {
|
|
83
|
-
if (message?.role !== 'user') return '';
|
|
83
|
+
if (message?.role !== 'user' || isRoutineResultMessage(message)) return '';
|
|
84
84
|
const raw = sessionMessageText(message.content);
|
|
85
85
|
const memo = messagePreviewMemo.get(message);
|
|
86
86
|
if (memo && memo.raw === raw) return memo.preview;
|
|
@@ -89,6 +89,20 @@ function _previewFromMessage(message) {
|
|
|
89
89
|
return preview;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// message → { raw, preview } for the LAST conversational message, user or
|
|
93
|
+
// assistant alike (a messenger row's last-message line).
|
|
94
|
+
const lastMessagePreviewMemo = new WeakMap();
|
|
95
|
+
|
|
96
|
+
function _lastPreviewFromMessage(message) {
|
|
97
|
+
const raw = lastMessageRawText(message);
|
|
98
|
+
if (raw === null) return '';
|
|
99
|
+
const memo = lastMessagePreviewMemo.get(message);
|
|
100
|
+
if (memo && memo.raw === raw) return memo.preview;
|
|
101
|
+
const preview = isSessionPreviewNoise(raw) ? '' : _cleanPreview(raw, LAST_MESSAGE_PREVIEW_MAX);
|
|
102
|
+
lastMessagePreviewMemo.set(message, { raw, preview });
|
|
103
|
+
return preview;
|
|
104
|
+
}
|
|
105
|
+
|
|
92
106
|
function _sessionMessageProjection(session) {
|
|
93
107
|
const messages = Array.isArray(session?.messages) ? session.messages : [];
|
|
94
108
|
const cached = session && typeof session === 'object' ? sessionMessageProjectionMemo.get(session) : null;
|
|
@@ -134,6 +148,17 @@ function _sessionMessageProjection(session) {
|
|
|
134
148
|
}
|
|
135
149
|
}
|
|
136
150
|
}
|
|
151
|
+
// The newest line, read back from the tail every time: a streamed reply
|
|
152
|
+
// fills its message in place. Tool-only and synthetic rows are skipped, and
|
|
153
|
+
// each message's text is memoized, so this stays a few lookups per save.
|
|
154
|
+
// The line's routine provenance rides along, so a client labels a routine
|
|
155
|
+
// result by this marker and never by the line's wording.
|
|
156
|
+
let last = '';
|
|
157
|
+
let lastRoutine = null;
|
|
158
|
+
for (let index = messages.length - 1; index >= 0 && !last; index -= 1) {
|
|
159
|
+
last = _lastPreviewFromMessage(messages[index]);
|
|
160
|
+
if (last) lastRoutine = routineProvenance(messages[index]);
|
|
161
|
+
}
|
|
137
162
|
if (session && typeof session === 'object') {
|
|
138
163
|
sessionMessageProjectionMemo.set(session, {
|
|
139
164
|
length: messages.length,
|
|
@@ -144,7 +169,7 @@ function _sessionMessageProjection(session) {
|
|
|
144
169
|
previewMessage,
|
|
145
170
|
});
|
|
146
171
|
}
|
|
147
|
-
return { count, real, preview };
|
|
172
|
+
return { count, real, preview, last, lastRoutine };
|
|
148
173
|
}
|
|
149
174
|
|
|
150
175
|
function _desktopSessionSummary(value, cwd = null) {
|
|
@@ -178,6 +203,8 @@ export function _sessionSummary(session) {
|
|
|
178
203
|
status: String(session.status || (session.closed === true ? 'closed' : 'idle')),
|
|
179
204
|
owner: session.owner || 'user',
|
|
180
205
|
agent: session.agent || null,
|
|
206
|
+
// 'agent' | 'coding' (agent-mode.mjs); older records resolve it.
|
|
207
|
+
sessionMode: sessionModeOf(session),
|
|
181
208
|
sourceType: session.sourceType || null,
|
|
182
209
|
sourceName: session.sourceName || null,
|
|
183
210
|
scopeKey: session.scopeKey || null,
|
|
@@ -209,6 +236,8 @@ export function _sessionSummary(session) {
|
|
|
209
236
|
realTurnCount: messageProjection.real,
|
|
210
237
|
title: _cleanPreview(session.title || '', 100),
|
|
211
238
|
preview: messageProjection.preview,
|
|
239
|
+
lastMessagePreview: messageProjection.last,
|
|
240
|
+
lastMessageRoutine: messageProjection.lastRoutine,
|
|
212
241
|
generation: typeof session.generation === 'number' ? session.generation : 0,
|
|
213
242
|
storageMtimeMs: _positiveNumber(session.storageMtimeMs, 0),
|
|
214
243
|
storageSize: _positiveNumber(session.storageSize, 0),
|
|
@@ -234,6 +263,7 @@ function _normalizeSummaryRow(row) {
|
|
|
234
263
|
status: String(row.status || (row.closed === true ? 'closed' : 'idle')),
|
|
235
264
|
owner: row.owner || 'user',
|
|
236
265
|
agent: row.agent || null,
|
|
266
|
+
sessionMode: sessionModeOf(row),
|
|
237
267
|
sourceType: row.sourceType || null,
|
|
238
268
|
sourceName: row.sourceName || null,
|
|
239
269
|
scopeKey: row.scopeKey || null,
|
|
@@ -264,6 +294,8 @@ function _normalizeSummaryRow(row) {
|
|
|
264
294
|
realTurnCount: realTurnCount(row),
|
|
265
295
|
title: _cleanPreview(row.title || '', 100),
|
|
266
296
|
preview: _cleanPreview(row.preview || ''),
|
|
297
|
+
lastMessagePreview: _cleanPreview(row.lastMessagePreview || '', 160),
|
|
298
|
+
lastMessageRoutine: normalizeRoutineProvenance(row.lastMessageRoutine),
|
|
267
299
|
generation: typeof row.generation === 'number' ? row.generation : 0,
|
|
268
300
|
storageMtimeMs: _positiveNumber(row.storageMtimeMs, 0),
|
|
269
301
|
storageSize: _positiveNumber(row.storageSize, 0),
|
|
@@ -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
|
+
}
|
|
@@ -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)
|
|
@@ -377,6 +384,20 @@ function restoredUserTranscriptItems(message, nextId) {
|
|
|
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();
|
|
@@ -440,6 +461,19 @@ export function modelSwitchItem(id, boundary) {
|
|
|
440
461
|
};
|
|
441
462
|
}
|
|
442
463
|
|
|
464
|
+
/** The transcript row of a mid-conversation workflow switch (live and restored). */
|
|
465
|
+
export function workflowSwitchItem(id, boundary) {
|
|
466
|
+
return {
|
|
467
|
+
kind: 'statusdone',
|
|
468
|
+
id,
|
|
469
|
+
status: 'workflow-switched',
|
|
470
|
+
label: 'Workflow switched',
|
|
471
|
+
from: boundary.from,
|
|
472
|
+
to: boundary.to,
|
|
473
|
+
at: boundary.at,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
443
477
|
function restoreTranscriptRange(messages, start, sessionId) {
|
|
444
478
|
const items = [];
|
|
445
479
|
const pendingToolCalls = new Map();
|
|
@@ -450,6 +484,11 @@ function restoreTranscriptRange(messages, start, sessionId) {
|
|
|
450
484
|
// let a tail-only restore skip the prefix without first counting every old
|
|
451
485
|
// projected item.
|
|
452
486
|
const restoredId = () => `hist_${sessionId}_${index}_${++part}`;
|
|
487
|
+
// A group room's posted row (agent-room.mjs): one bubble or notice row.
|
|
488
|
+
if (isRoomRowMessage(message)) {
|
|
489
|
+
items.push(roomRowItem(message, restoredId()));
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
453
492
|
if (message?.role === 'user') {
|
|
454
493
|
items.push(...restoredUserTranscriptItems(message, restoredId));
|
|
455
494
|
} else if (message?.role === 'assistant' && !isContextAckMessage(message, messages[index - 1])) {
|
|
@@ -477,6 +516,10 @@ function restoreTranscriptRange(messages, start, sessionId) {
|
|
|
477
516
|
if (!boundary?.provider || !boundary?.modelId) continue;
|
|
478
517
|
items.push(modelSwitchItem(restoredId(), boundary));
|
|
479
518
|
}
|
|
519
|
+
for (const boundary of Array.isArray(message?.meta?.workflowSwitches) ? message.meta.workflowSwitches : []) {
|
|
520
|
+
if (!boundary?.to) continue;
|
|
521
|
+
items.push(workflowSwitchItem(restoredId(), boundary));
|
|
522
|
+
}
|
|
480
523
|
if (message?.meta?.transcript?.previousCompletions?.length) {
|
|
481
524
|
const events = items.slice(eventStart);
|
|
482
525
|
if (events.every((item) => Number.isFinite(item.at))) {
|
|
@@ -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
|
}
|