mixdog 0.9.46 → 0.9.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +14 -4
- package/scripts/agent-parallel-smoke.mjs +4 -1
- package/scripts/agent-route-batch-test.mjs +40 -0
- package/scripts/code-graph-aggregate-cwd-test.mjs +154 -0
- package/scripts/code-graph-description-contract.mjs +185 -0
- package/scripts/code-graph-disk-hit-test.mjs +55 -0
- package/scripts/code-graph-dispatch-test.mjs +96 -0
- package/scripts/compact-pressure-test.mjs +40 -0
- package/scripts/deferred-tool-loading-test.mjs +233 -0
- package/scripts/execution-completion-dedup-test.mjs +48 -0
- package/scripts/explore-prompt-policy-test.mjs +88 -3
- package/scripts/live-worker-smoke.mjs +68 -16
- package/scripts/memory-core-input-test.mjs +33 -13
- package/scripts/native-edit-wire-test.mjs +152 -0
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +294 -16
- package/scripts/patch-binary-cache-test.mjs +181 -0
- package/scripts/prompt-immediate-render-test.mjs +89 -0
- package/scripts/provider-toolcall-test.mjs +280 -15
- package/scripts/shell-failure-diagnostics-test.mjs +211 -0
- package/scripts/statusline-quota-hysteresis-test.mjs +26 -1
- package/scripts/streaming-tail-window-test.mjs +29 -0
- package/scripts/tool-failures.mjs +21 -3
- package/scripts/tool-smoke.mjs +263 -38
- package/scripts/tool-tui-presentation-test.mjs +17 -1
- package/scripts/tui-perf-run.ps1 +26 -0
- package/scripts/tui-transcript-perf-test.mjs +7 -1
- package/scripts/verify-release-assets-test.mjs +647 -0
- package/scripts/verify-release-assets.mjs +293 -0
- package/scripts/windows-hide-spawn-options-test.mjs +19 -0
- package/src/cli.mjs +1 -0
- package/src/defaults/cycle3-review-prompt.md +10 -4
- package/src/defaults/memory-promote-prompt.md +4 -3
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +16 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +35 -11
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +35 -11
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +28 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +34 -34
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +45 -38
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +36 -11
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +10 -6
- package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +13 -8
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +30 -16
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +25 -8
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +69 -4
- package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +12 -12
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +12 -7
- package/src/runtime/agent/orchestrator/tools/patch-binary-fetcher.mjs +77 -18
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +99 -24
- package/src/runtime/memory/lib/memory-action-handlers.mjs +21 -11
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +13 -5
- package/src/runtime/memory/lib/memory-cycle2.mjs +8 -5
- package/src/runtime/memory/lib/memory-cycle3.mjs +41 -6
- package/src/runtime/memory/lib/query-handlers.mjs +49 -6
- package/src/runtime/memory/lib/recall-format.mjs +21 -6
- package/src/runtime/memory/tool-defs.mjs +2 -3
- package/src/session-runtime/context-status.mjs +25 -16
- package/src/session-runtime/model-route-api.mjs +2 -0
- package/src/session-runtime/runtime-core.mjs +2 -2
- package/src/session-runtime/session-turn-api.mjs +4 -1
- package/src/session-runtime/tool-catalog.mjs +113 -19
- package/src/session-runtime/tool-defs.mjs +1 -0
- package/src/standalone/agent-tool/helpers.mjs +25 -6
- package/src/standalone/agent-tool.mjs +75 -41
- package/src/tui/App.jsx +4 -0
- package/src/tui/app/input-parsers.mjs +8 -9
- package/src/tui/components/Markdown.jsx +6 -1
- package/src/tui/components/Message.jsx +11 -2
- package/src/tui/components/PromptInput.jsx +19 -21
- package/src/tui/components/Spinner.jsx +4 -4
- package/src/tui/components/StatusLine.jsx +6 -6
- package/src/tui/components/TranscriptItem.jsx +2 -2
- package/src/tui/components/prompt-input/immediate-render.mjs +47 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +14 -9
- package/src/tui/dist/index.mjs +130 -45
- package/src/tui/engine/agent-job-feed.mjs +21 -2
- package/src/tui/engine/turn.mjs +12 -1
- package/src/tui/markdown/measure-rendered-rows.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +20 -0
- package/src/ui/statusline.mjs +5 -5
- package/src/vendor/statusline/src/gateway/session-routes.mjs +22 -10
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
estimateRequestReserveTokens,
|
|
3
3
|
estimateToolSchemaTokens,
|
|
4
|
-
estimateTranscriptContextUsage,
|
|
5
4
|
contextMessagesRevision,
|
|
6
5
|
resolveSessionCompactPolicy,
|
|
7
6
|
summarizeContextMessages,
|
|
8
7
|
toolSchemaSignature,
|
|
9
8
|
} from '../runtime/agent/orchestrator/session/context-utils.mjs';
|
|
10
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
resolveCompactionPressureTokens,
|
|
11
|
+
resolveWorkerCompactPolicy,
|
|
12
|
+
} from '../runtime/agent/orchestrator/session/loop/compact-policy.mjs';
|
|
11
13
|
import { estimateToolSchemaBreakdown } from './tool-catalog.mjs';
|
|
12
14
|
|
|
13
15
|
const DEFERRED_CATALOG_TOOL_PROVIDERS = new Set(['anthropic', 'anthropic-oauth']);
|
|
@@ -84,6 +86,15 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
84
86
|
lastOutputTokens: Number(session?.lastOutputTokens || 0),
|
|
85
87
|
lastCachedReadTokens: Number(session?.lastCachedReadTokens || 0),
|
|
86
88
|
lastCacheWriteTokens: Number(session?.lastCacheWriteTokens || 0),
|
|
89
|
+
contextPressureBaselineTokens: Number(session?.contextPressureBaselineTokens || 0),
|
|
90
|
+
contextPressureBaselineOutputTokens: Number(session?.contextPressureBaselineOutputTokens || 0),
|
|
91
|
+
contextPressureBaselineMessageCount: Number(session?.contextPressureBaselineMessageCount ?? -1),
|
|
92
|
+
contextPressureBaselineUpdatedAt: Number(session?.contextPressureBaselineUpdatedAt || 0),
|
|
93
|
+
contextPressureBaselineBoundary: session?.contextPressureBaselineBoundary || null,
|
|
94
|
+
contextPressureBaselineProvider: session?.contextPressureBaselineProvider || null,
|
|
95
|
+
contextPressureBaselineModel: session?.contextPressureBaselineModel || null,
|
|
96
|
+
contextPressureBaselineToolSignature: session?.contextPressureBaselineToolSignature || null,
|
|
97
|
+
contextPressureBaselinePrefixSignature: session?.contextPressureBaselinePrefixSignature || null,
|
|
87
98
|
totalInputTokens: Number(session?.totalInputTokens || 0),
|
|
88
99
|
totalUncachedInputTokens: Number(session?.totalUncachedInputTokens || 0),
|
|
89
100
|
totalOutputTokens: Number(session?.totalOutputTokens || 0),
|
|
@@ -148,10 +159,6 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
148
159
|
const rawWindow = Number(session?.rawContextWindow || session?.contextWindow || 0);
|
|
149
160
|
const effectiveWindow = Number(session?.contextWindow || rawWindow || 0);
|
|
150
161
|
const lastContextTokens = Number(session?.lastContextTokens || 0);
|
|
151
|
-
const estimatedContextTokens = estimateTranscriptContextUsage(messages, requestTools, {
|
|
152
|
-
messageCount: messageSummary.count,
|
|
153
|
-
estimatedMessageTokens: messageSummary.estimatedTokens,
|
|
154
|
-
});
|
|
155
162
|
const compactAt = Number(session?.compaction?.lastChangedAt || session?.compaction?.lastCompactAt || 0);
|
|
156
163
|
const usageAt = Number(session?.lastContextTokensUpdatedAt || 0);
|
|
157
164
|
const lastUsageStale = !!lastContextTokens && (
|
|
@@ -161,14 +168,6 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
161
168
|
);
|
|
162
169
|
const compactBoundaryTokens = Number(session?.compactBoundaryTokens || session?.compaction?.boundaryTokens || 0);
|
|
163
170
|
const displayWindow = compactBoundaryTokens || effectiveWindow;
|
|
164
|
-
// The transcript estimate is the single source of truth for the displayed
|
|
165
|
-
// context footprint. Provider-reported input_tokens (lastContextTokens)
|
|
166
|
-
// swing non-monotonically and are not window-bounded on some providers
|
|
167
|
-
// (e.g. OpenAI gpt-5.5 Responses API), so they are kept only as secondary
|
|
168
|
-
// metadata (lastApiRequestTokens / usage.lastContextTokens) and never feed
|
|
169
|
-
// the gauge numerator.
|
|
170
|
-
const usedTokens = estimatedContextTokens;
|
|
171
|
-
const freeTokens = displayWindow ? Math.max(0, displayWindow - usedTokens) : 0;
|
|
172
171
|
// Use the worker policy when a boundary is available so target/reserve
|
|
173
172
|
// headroom, trigger, buffer tokens, and buffer ratio stay identical to the
|
|
174
173
|
// auto-compact decision. Fall back only for incomplete session metadata.
|
|
@@ -176,6 +175,16 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
176
175
|
const compactPolicy = workerCompactPolicy?.boundaryTokens
|
|
177
176
|
? workerCompactPolicy
|
|
178
177
|
: resolveSessionCompactPolicy(session || {}, compactBoundaryTokens);
|
|
178
|
+
// Match the pre-provider-send auto-compact check exactly: the gauge uses
|
|
179
|
+
// the same provider-baseline-or-estimate pressure, including request and
|
|
180
|
+
// configured reserves, rather than a separate transcript-only estimate.
|
|
181
|
+
const compactionPressureTokens = resolveCompactionPressureTokens(
|
|
182
|
+
messageSummary.estimatedTokens,
|
|
183
|
+
compactPolicy,
|
|
184
|
+
{ messages, sessionRef: session },
|
|
185
|
+
);
|
|
186
|
+
const usedTokens = compactionPressureTokens;
|
|
187
|
+
const freeTokens = displayWindow ? Math.max(0, displayWindow - usedTokens) : 0;
|
|
179
188
|
const compactTriggerTokens = compactPolicy.triggerTokens || 0;
|
|
180
189
|
const compactBufferTokens = compactPolicy.bufferTokens || 0;
|
|
181
190
|
const compactBufferRatio = Number.isFinite(compactPolicy.bufferRatio)
|
|
@@ -193,7 +202,7 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
193
202
|
effectiveContextWindowPercent: session?.effectiveContextWindowPercent || null,
|
|
194
203
|
usedTokens,
|
|
195
204
|
usedSource: 'estimated',
|
|
196
|
-
currentEstimatedTokens:
|
|
205
|
+
currentEstimatedTokens: compactionPressureTokens,
|
|
197
206
|
lastApiRequestTokens: lastContextTokens || 0,
|
|
198
207
|
lastApiRequestStale: lastUsageStale,
|
|
199
208
|
freeTokens,
|
|
@@ -203,7 +212,7 @@ export function createContextStatus({ getSession, getRoute, getCurrentCwd, getMo
|
|
|
203
212
|
triggerTokens: compactTriggerTokens || null,
|
|
204
213
|
bufferTokens: compactBufferTokens || null,
|
|
205
214
|
bufferRatio: compactBufferRatio,
|
|
206
|
-
currentEstimatedTokens:
|
|
215
|
+
currentEstimatedTokens: compactionPressureTokens,
|
|
207
216
|
lastApiRequestTokens: lastContextTokens || 0,
|
|
208
217
|
lastApiRequestStale: lastUsageStale,
|
|
209
218
|
},
|
|
@@ -21,6 +21,7 @@ import { SUMMARY_PREFIX } from '../runtime/agent/orchestrator/session/compact.mj
|
|
|
21
21
|
import {
|
|
22
22
|
hasUserConversationMessage,
|
|
23
23
|
} from '../runtime/agent/orchestrator/session/manager/prompt-utils.mjs';
|
|
24
|
+
import { rebuildDeferredToolSurfaceForProvider } from './tool-catalog.mjs';
|
|
24
25
|
|
|
25
26
|
function isSummaryAnchorMessage(message) {
|
|
26
27
|
return message?.role === 'user'
|
|
@@ -179,6 +180,7 @@ export function createModelRouteApi(deps) {
|
|
|
179
180
|
}
|
|
180
181
|
if (session) {
|
|
181
182
|
const route = getRoute();
|
|
183
|
+
rebuildDeferredToolSurfaceForProvider(session, route.provider);
|
|
182
184
|
const updated = mgr.updateSessionRoute?.(session.id, {
|
|
183
185
|
provider: route.provider,
|
|
184
186
|
model: route.model,
|
|
@@ -303,8 +303,8 @@ const {
|
|
|
303
303
|
agentRouteFromConfig,
|
|
304
304
|
} = createWorkflowRouteHelpers({ resolveDefaultProvider, findPreset });
|
|
305
305
|
|
|
306
|
-
export function __renderToolSearchForTest(args = {}, session = {}, mode = 'full') {
|
|
307
|
-
return renderToolSearch(args, session, mode);
|
|
306
|
+
export function __renderToolSearchForTest(args = {}, session = {}, mode = 'full', options = {}) {
|
|
307
|
+
return renderToolSearch(args, session, mode, options);
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
export function __saveModelSettingsForTest(cfgMod, route, options = {}) {
|
|
@@ -320,7 +320,10 @@ export function createSessionTurnApi(deps) {
|
|
|
320
320
|
const catalog = Array.isArray(surface?.deferredToolCatalog)
|
|
321
321
|
? surface.deferredToolCatalog
|
|
322
322
|
: (Array.isArray(surface?.tools) ? surface.tools : []);
|
|
323
|
-
const activeNames = new Set(
|
|
323
|
+
const activeNames = new Set([
|
|
324
|
+
...(surface?.tools || []).map((tool) => tool?.name).filter(Boolean),
|
|
325
|
+
...(surface?.deferredCallableTools || []),
|
|
326
|
+
]);
|
|
324
327
|
const needle = clean(query).toLowerCase();
|
|
325
328
|
const rows = catalog.map((tool) => toolRow(tool, activeNames)).filter((row) => row.name);
|
|
326
329
|
const counts = splitToolStatusCounts(rows);
|
|
@@ -228,13 +228,22 @@ function activeToolForSurface(tool) {
|
|
|
228
228
|
|
|
229
229
|
function deferredProviderMode(provider) {
|
|
230
230
|
const p = clean(provider).toLowerCase();
|
|
231
|
-
if (p === 'gemini') return '
|
|
231
|
+
if (p === 'gemini') return 'manifest';
|
|
232
232
|
if (p === 'anthropic' || p === 'anthropic-oauth'
|
|
233
|
-
|| p === 'openai' || p === 'openai-oauth'
|
|
234
|
-
|| p === 'xai' || p === 'grok-oauth') {
|
|
233
|
+
|| p === 'openai' || p === 'openai-oauth') {
|
|
235
234
|
return 'native';
|
|
236
235
|
}
|
|
237
|
-
|
|
236
|
+
// xAI/Grok and every other OpenAI-compatible backend have no native
|
|
237
|
+
// tool_search/tool_search_output contract. Give them one complete canonical
|
|
238
|
+
// function array instead of a load-driven array whose bytes churn.
|
|
239
|
+
return 'canonical';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function nativeProviderFamily(provider) {
|
|
243
|
+
const p = clean(provider).toLowerCase();
|
|
244
|
+
if (p === 'openai' || p === 'openai-oauth') return 'openai';
|
|
245
|
+
if (p === 'anthropic' || p === 'anthropic-oauth') return 'anthropic';
|
|
246
|
+
return '';
|
|
238
247
|
}
|
|
239
248
|
|
|
240
249
|
export function filterDisallowedTools(tools, disallowed = []) {
|
|
@@ -324,6 +333,7 @@ function toolSearchNativePayload(catalog, names, provider = '') {
|
|
|
324
333
|
}
|
|
325
334
|
if (!refs.length) return null;
|
|
326
335
|
return {
|
|
336
|
+
provider: clean(provider).toLowerCase(),
|
|
327
337
|
toolReferences: refs,
|
|
328
338
|
openaiTools: tools,
|
|
329
339
|
summary: `Loaded deferred tools: ${refs.join(', ')}`,
|
|
@@ -386,11 +396,16 @@ function setDeferredToolState(session, names) {
|
|
|
386
396
|
}
|
|
387
397
|
|
|
388
398
|
export function deferredPoolToolNames(session) {
|
|
389
|
-
if (!session || session.deferredProviderMode === 'full'
|
|
399
|
+
if (!session || session.deferredProviderMode === 'full'
|
|
400
|
+
|| session.deferredProviderMode === 'manifest'
|
|
401
|
+
|| session.deferredProviderMode === 'canonical') return [];
|
|
390
402
|
const catalog = Array.isArray(session.deferredToolCatalog)
|
|
391
403
|
? session.deferredToolCatalog
|
|
392
404
|
: [];
|
|
393
|
-
const active = new Set(
|
|
405
|
+
const active = new Set([
|
|
406
|
+
...(session.tools || []).map((tool) => clean(tool?.name)).filter(Boolean),
|
|
407
|
+
...parseToolSelection(session.deferredCallableTools),
|
|
408
|
+
]);
|
|
394
409
|
const out = [];
|
|
395
410
|
for (const tool of catalog) {
|
|
396
411
|
const name = clean(tool?.name);
|
|
@@ -447,10 +462,10 @@ export function applyDeferredToolSurface(session, mode, extraTools = [], options
|
|
|
447
462
|
const catalog = sortedCatalogByMeasuredUsage([...byName.values()]);
|
|
448
463
|
const defaultNames = defaultDeferredToolNames(catalog, mode);
|
|
449
464
|
const storedNames = providerMode === 'native' ? [] : storedDeferredToolNames(session);
|
|
450
|
-
let selectedNames = providerMode === 'full'
|
|
465
|
+
let selectedNames = providerMode === 'full' || providerMode === 'manifest' || providerMode === 'canonical'
|
|
451
466
|
? sortedNamesByMeasuredUsage(catalog.map((tool) => clean(tool?.name)).filter(Boolean))
|
|
452
467
|
: [];
|
|
453
|
-
if (
|
|
468
|
+
if (!['full', 'manifest', 'canonical'].includes(providerMode)) {
|
|
454
469
|
selectedNames = storedNames.length ? canonicalDeferredToolNames(catalog, storedNames) : [];
|
|
455
470
|
if (!selectedNames.length || providerMode === 'native') selectedNames = sortedNamesByMeasuredUsage(defaultNames);
|
|
456
471
|
}
|
|
@@ -460,6 +475,7 @@ export function applyDeferredToolSurface(session, mode, extraTools = [], options
|
|
|
460
475
|
session.deferredDefaultTools = sortedNamesByMeasuredUsage(defaultNames);
|
|
461
476
|
session.deferredProviderMode = providerMode;
|
|
462
477
|
session.deferredNativeTools = providerMode === 'native';
|
|
478
|
+
session.deferredSurfaceMode = mode;
|
|
463
479
|
session.tools.length = 0;
|
|
464
480
|
const active = [];
|
|
465
481
|
for (const tool of catalog) {
|
|
@@ -468,6 +484,7 @@ export function applyDeferredToolSurface(session, mode, extraTools = [], options
|
|
|
468
484
|
session.tools.push(tool);
|
|
469
485
|
active.push(clean(tool?.name));
|
|
470
486
|
}
|
|
487
|
+
session.deferredCallableTools = sortedNamesByMeasuredUsage(active);
|
|
471
488
|
if (providerMode === 'native') {
|
|
472
489
|
const discovered = canonicalDeferredToolNames(catalog, session.deferredDiscoveredTools || []);
|
|
473
490
|
session.deferredSelectedTools = active;
|
|
@@ -491,6 +508,33 @@ export function applyDeferredToolSurface(session, mode, extraTools = [], options
|
|
|
491
508
|
return session;
|
|
492
509
|
}
|
|
493
510
|
|
|
511
|
+
export function rebuildDeferredToolSurfaceForProvider(session, provider) {
|
|
512
|
+
if (!session || !Array.isArray(session.tools)) return session;
|
|
513
|
+
const previousFamily = nativeProviderFamily(session.provider);
|
|
514
|
+
const nextFamily = nativeProviderFamily(provider);
|
|
515
|
+
const preserveNativeState = previousFamily && previousFamily === nextFamily;
|
|
516
|
+
const discovered = preserveNativeState
|
|
517
|
+
? canonicalDeferredToolNames(deferredCatalogUnion(session), session.deferredDiscoveredTools || [])
|
|
518
|
+
: [];
|
|
519
|
+
const catalog = deferredCatalogUnion(session).slice();
|
|
520
|
+
session.deferredDiscoveredTools = discovered;
|
|
521
|
+
applyDeferredToolSurface(
|
|
522
|
+
session,
|
|
523
|
+
session.deferredSurfaceMode || 'lead',
|
|
524
|
+
catalog,
|
|
525
|
+
{ provider },
|
|
526
|
+
);
|
|
527
|
+
if (session.deferredProviderMode === 'native' && discovered.length) {
|
|
528
|
+
session.deferredDiscoveredTools = discovered;
|
|
529
|
+
session.deferredCallableTools = sortedNamesByMeasuredUsage(new Set([
|
|
530
|
+
...(session.deferredCallableTools || []),
|
|
531
|
+
...discovered,
|
|
532
|
+
]));
|
|
533
|
+
session.deferredSelectedTools = session.deferredCallableTools.slice();
|
|
534
|
+
}
|
|
535
|
+
return session;
|
|
536
|
+
}
|
|
537
|
+
|
|
494
538
|
// FIRST-TURN deferred-surface refresh (claude-code turn-time deferred manifest).
|
|
495
539
|
// An MCP server may finish its handshake BETWEEN session-create and the first
|
|
496
540
|
// user send. Fold those LIVE MCP tools into the boot deferred catalog + the
|
|
@@ -502,7 +546,7 @@ export function applyDeferredToolSurface(session, mode, extraTools = [], options
|
|
|
502
546
|
// active — there is no deferred manifest to refresh).
|
|
503
547
|
export function refreshInitialDeferredMcpSurface(session, liveMcpTools) {
|
|
504
548
|
if (!session || !Array.isArray(session.messages)) return false;
|
|
505
|
-
if (session.deferredProviderMode === 'full') return false;
|
|
549
|
+
if (session.deferredProviderMode === 'full' || session.deferredProviderMode === 'canonical') return false;
|
|
506
550
|
const isMcp = (name) => typeof name === 'string' && name.startsWith('mcp__');
|
|
507
551
|
const byName = new Map();
|
|
508
552
|
for (const tool of Array.isArray(session.deferredToolCatalog) ? session.deferredToolCatalog : []) {
|
|
@@ -518,6 +562,15 @@ export function refreshInitialDeferredMcpSurface(session, liveMcpTools) {
|
|
|
518
562
|
}
|
|
519
563
|
if (!added) return false;
|
|
520
564
|
session.deferredToolCatalog = sortedCatalogByMeasuredUsage([...byName.values()]);
|
|
565
|
+
if (session.deferredProviderMode === 'manifest') {
|
|
566
|
+
const next = session.deferredToolCatalog.filter((tool) => (
|
|
567
|
+
session.deferredSurfaceMode !== 'readonly' || isReadonlySelectable(tool)
|
|
568
|
+
));
|
|
569
|
+
session.tools.splice(0, session.tools.length, ...next);
|
|
570
|
+
session.deferredCallableTools = next.map((tool) => clean(tool?.name)).filter(Boolean);
|
|
571
|
+
session.updatedAt = Date.now();
|
|
572
|
+
return true;
|
|
573
|
+
}
|
|
521
574
|
// Refresh MCP server instructions so a newly-connected server's block is
|
|
522
575
|
// included when BP1 is re-rendered below.
|
|
523
576
|
session.mcpServerInstructions = getMcpServerInstructionsMap();
|
|
@@ -558,9 +611,31 @@ export function refreshInitialDeferredMcpSurface(session, liveMcpTools) {
|
|
|
558
611
|
*/
|
|
559
612
|
export function reconcileDeferredMcpToolCatalog(session, liveMcpTools, options = {}) {
|
|
560
613
|
if (!session || !Array.isArray(session.messages)) return null;
|
|
561
|
-
if (session.deferredProviderMode === 'full') return null;
|
|
614
|
+
if (session.deferredProviderMode === 'full' || session.deferredProviderMode === 'canonical') return null;
|
|
562
615
|
const isMcp = (name) => typeof name === 'string' && name.startsWith('mcp__');
|
|
563
616
|
const live = Array.isArray(liveMcpTools) ? liveMcpTools : [];
|
|
617
|
+
if (session.deferredProviderMode === 'manifest') {
|
|
618
|
+
const byName = new Map();
|
|
619
|
+
for (const tool of Array.isArray(session.deferredToolCatalog) ? session.deferredToolCatalog : []) {
|
|
620
|
+
const name = clean(tool?.name);
|
|
621
|
+
if (name && !isMcp(name)) byName.set(name, tool);
|
|
622
|
+
}
|
|
623
|
+
for (const tool of live) {
|
|
624
|
+
const name = clean(tool?.name);
|
|
625
|
+
if (name && isMcp(name)) byName.set(name, activeToolForSurface(tool));
|
|
626
|
+
}
|
|
627
|
+
const catalog = sortedCatalogByMeasuredUsage([...byName.values()]);
|
|
628
|
+
const next = catalog.filter((tool) => (
|
|
629
|
+
session.deferredSurfaceMode !== 'readonly' || isReadonlySelectable(tool)
|
|
630
|
+
));
|
|
631
|
+
const before = JSON.stringify((session.tools || []).map((tool) => activeToolForSurface(tool)));
|
|
632
|
+
const after = JSON.stringify(next);
|
|
633
|
+
session.deferredToolCatalog = catalog;
|
|
634
|
+
session.tools.splice(0, session.tools.length, ...next);
|
|
635
|
+
session.deferredCallableTools = next.map((tool) => clean(tool?.name)).filter(Boolean);
|
|
636
|
+
if (before !== after) session.updatedAt = Date.now();
|
|
637
|
+
return before === after ? null : session.deferredCallableTools;
|
|
638
|
+
}
|
|
564
639
|
const lateCatalog = Array.isArray(session.deferredLateToolCatalog) ? session.deferredLateToolCatalog : [];
|
|
565
640
|
const active = new Set((session.tools || []).map((tool) => clean(tool?.name)).filter(Boolean));
|
|
566
641
|
|
|
@@ -700,18 +775,18 @@ function deliverDeferredAnnouncement(session, reminder, options) {
|
|
|
700
775
|
}
|
|
701
776
|
|
|
702
777
|
export function selectDeferredTools(session, names, mode, options = {}) {
|
|
703
|
-
const promoteToActive = options?.promoteToActive === true;
|
|
704
778
|
// Resolve against the union of the boot-frozen catalog and the late-connected
|
|
705
|
-
// MCP catalog so load_tool can load a late tool
|
|
706
|
-
//
|
|
779
|
+
// MCP catalog so load_tool can load a late tool. Native providers register it
|
|
780
|
+
// independently; canonical fallback providers already expose the full array.
|
|
707
781
|
const union = deferredCatalogUnion(session);
|
|
708
782
|
const catalog = union.length
|
|
709
783
|
? union
|
|
710
784
|
: (Array.isArray(session?.tools) ? session.tools : []);
|
|
711
|
-
const
|
|
785
|
+
const surfaceActive = new Set((session?.tools || []).map((tool) => clean(tool?.name)).filter(Boolean));
|
|
786
|
+
const active = new Set([...surfaceActive, ...parseToolSelection(session?.deferredCallableTools)]);
|
|
712
787
|
const native = session?.deferredProviderMode === 'native' || session?.deferredNativeTools === true;
|
|
713
788
|
const discovered = new Set(Array.isArray(session?.deferredDiscoveredTools) ? session.deferredDiscoveredTools : []);
|
|
714
|
-
const activateOnSurface = !native
|
|
789
|
+
const activateOnSurface = !native;
|
|
715
790
|
const byName = new Map();
|
|
716
791
|
for (const tool of catalog) {
|
|
717
792
|
const name = clean(tool?.name);
|
|
@@ -745,12 +820,14 @@ export function selectDeferredTools(session, names, mode, options = {}) {
|
|
|
745
820
|
discovered.delete(name);
|
|
746
821
|
} else {
|
|
747
822
|
discovered.add(name);
|
|
823
|
+
active.add(name);
|
|
748
824
|
}
|
|
749
825
|
added.push(name);
|
|
750
826
|
}
|
|
751
827
|
if (native) {
|
|
828
|
+
session.deferredCallableTools = sortedNamesByMeasuredUsage(active);
|
|
752
829
|
session.deferredDiscoveredTools = sortedNamesByMeasuredUsage(
|
|
753
|
-
[...discovered]
|
|
830
|
+
[...discovered],
|
|
754
831
|
);
|
|
755
832
|
session.deferredSelectedTools = sortedNamesByMeasuredUsage(active);
|
|
756
833
|
} else {
|
|
@@ -828,14 +905,31 @@ export function renderToolSearch(args = {}, session, mode = 'full', options = {}
|
|
|
828
905
|
}, null, 2);
|
|
829
906
|
}
|
|
830
907
|
|
|
908
|
+
// Native loads update only the callable registry and provider-native history
|
|
909
|
+
// payload. Canonical fallback providers already carry the complete array.
|
|
831
910
|
const toolSelection = selectDeferredTools(session, requestedNames, mode);
|
|
832
|
-
const nextActiveNames = new Set(
|
|
911
|
+
const nextActiveNames = new Set([
|
|
912
|
+
...(session?.tools || []).map((tool) => clean(tool?.name)).filter(Boolean),
|
|
913
|
+
...parseToolSelection(session?.deferredCallableTools),
|
|
914
|
+
]);
|
|
833
915
|
const loaded = toolSelection.added || [];
|
|
834
916
|
const alreadyActive = toolSelection.already || [];
|
|
835
917
|
const missing = toolSelection.missing || [];
|
|
836
918
|
const blocked = toolSelection.blocked || [];
|
|
837
|
-
const
|
|
838
|
-
? toolSearchNativePayload(catalog,
|
|
919
|
+
const nativeToolSearchBase = toolSelection.native
|
|
920
|
+
? (toolSearchNativePayload(catalog, loaded, session?.provider) || {
|
|
921
|
+
provider: clean(session?.provider).toLowerCase(),
|
|
922
|
+
toolReferences: [],
|
|
923
|
+
openaiTools: [],
|
|
924
|
+
summary: '',
|
|
925
|
+
})
|
|
926
|
+
: null;
|
|
927
|
+
const nativeSummary = [
|
|
928
|
+
...(loaded.length ? [`Loaded deferred tools: ${loaded.join(', ')}`] : []),
|
|
929
|
+
...(alreadyActive.length ? [`Already active: ${alreadyActive.join(', ')}`] : []),
|
|
930
|
+
].join('\n');
|
|
931
|
+
const nativeToolSearch = nativeToolSearchBase
|
|
932
|
+
? { ...nativeToolSearchBase, summary: nativeSummary || nativeToolSearchBase.summary }
|
|
839
933
|
: null;
|
|
840
934
|
const notes = [];
|
|
841
935
|
if (missing.length && pendingMcpServers.length) {
|
|
@@ -18,6 +18,7 @@ export const TOOL_SEARCH_TOOL = {
|
|
|
18
18
|
type: 'object',
|
|
19
19
|
properties: {
|
|
20
20
|
names: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], description: 'Exact deferred tool names/aliases to load.' },
|
|
21
|
+
select: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], description: 'Legacy alias for names; accepts exact names/aliases and select:name syntax.' },
|
|
21
22
|
},
|
|
22
23
|
additionalProperties: false,
|
|
23
24
|
},
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
//
|
|
2
|
-
// module-level mutable state lives here (the frontmatter perm cache and all
|
|
3
|
-
// timers stay in the facade / their owner modules). Behavior-preserving:
|
|
4
|
-
// function bodies are identical to the originals.
|
|
1
|
+
// Dependency-light helpers extracted from the agent-tool facade.
|
|
5
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
6
3
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
7
4
|
import {
|
|
8
5
|
normalizeAgentPermissionOrNone,
|
|
9
6
|
parseMarkdownFrontmatter,
|
|
10
7
|
} from '../../runtime/shared/markdown-frontmatter.mjs';
|
|
11
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
clearGatewaySessionRoute,
|
|
10
|
+
writeGatewaySessionRoutes,
|
|
11
|
+
} from '../../vendor/statusline/src/gateway/session-routes.mjs';
|
|
12
12
|
import { PRESET_ALIASES } from './tool-def.mjs';
|
|
13
13
|
|
|
14
14
|
export function envTimeoutMs(name, fallback) {
|
|
@@ -99,14 +99,33 @@ export function bridgeRouteForStatusline(preset = {}) {
|
|
|
99
99
|
return out;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
const pendingAgentStatuslineRoutes = new Map();
|
|
103
|
+
let pendingAgentStatuslineRouteFlush = null;
|
|
104
|
+
|
|
105
|
+
export function flushAgentStatuslineRoutes() {
|
|
106
|
+
if (pendingAgentStatuslineRouteFlush) {
|
|
107
|
+
clearImmediate(pendingAgentStatuslineRouteFlush);
|
|
108
|
+
pendingAgentStatuslineRouteFlush = null;
|
|
109
|
+
}
|
|
110
|
+
if (pendingAgentStatuslineRoutes.size === 0) return false;
|
|
111
|
+
const entries = [...pendingAgentStatuslineRoutes.values()];
|
|
112
|
+
pendingAgentStatuslineRoutes.clear();
|
|
113
|
+
try { return writeGatewaySessionRoutes(entries); } catch { return false; }
|
|
114
|
+
}
|
|
115
|
+
|
|
102
116
|
export function writeAgentStatuslineRoute(sessionId, preset) {
|
|
103
117
|
const route = bridgeRouteForStatusline(preset);
|
|
104
118
|
if (!sessionId || !route) return false;
|
|
105
|
-
|
|
119
|
+
pendingAgentStatuslineRoutes.set(sessionId, { sessionId, route });
|
|
120
|
+
if (!pendingAgentStatuslineRouteFlush) {
|
|
121
|
+
pendingAgentStatuslineRouteFlush = setImmediate(flushAgentStatuslineRoutes);
|
|
122
|
+
}
|
|
123
|
+
return true;
|
|
106
124
|
}
|
|
107
125
|
|
|
108
126
|
export function clearAgentStatuslineRoute(sessionId) {
|
|
109
127
|
if (!sessionId) return false;
|
|
128
|
+
pendingAgentStatuslineRoutes.delete(sessionId);
|
|
110
129
|
try { return clearGatewaySessionRoute(sessionId); } catch { return false; }
|
|
111
130
|
}
|
|
112
131
|
|
|
@@ -993,7 +993,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
993
993
|
// to whatever raw args carry.
|
|
994
994
|
let resolved = null;
|
|
995
995
|
if (!clean(args.model) || !clean(args.provider)) {
|
|
996
|
-
try { resolved =
|
|
996
|
+
try { resolved = resolveAgentSpawnPreset(cfgMod.loadConfig(), args)?.preset || null; }
|
|
997
997
|
catch { resolved = null; }
|
|
998
998
|
}
|
|
999
999
|
return sanitizeTaskMeta({
|
|
@@ -1127,58 +1127,92 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1127
1127
|
}, notifyContext);
|
|
1128
1128
|
}
|
|
1129
1129
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
if (abortErr) {
|
|
1130
|
+
const progressWatchdogs = new Map();
|
|
1131
|
+
let progressWatchdogTimer = null;
|
|
1132
|
+
|
|
1133
|
+
function stopProgressWatchdogTimerIfIdle() {
|
|
1134
|
+
if (progressWatchdogs.size > 0 || !progressWatchdogTimer) return;
|
|
1135
|
+
try { clearInterval(progressWatchdogTimer); } catch {}
|
|
1136
|
+
progressWatchdogTimer = null;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
function checkProgressIdleWatchdog(state) {
|
|
1140
|
+
const {
|
|
1141
|
+
sessionId,
|
|
1142
|
+
watchdogPolicy,
|
|
1143
|
+
agent,
|
|
1144
|
+
controller,
|
|
1145
|
+
anchorTs,
|
|
1146
|
+
} = state;
|
|
1147
|
+
if (controller.signal?.aborted) {
|
|
1148
|
+
progressWatchdogs.delete(sessionId);
|
|
1149
|
+
stopProgressWatchdogTimerIfIdle();
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
const now = Date.now();
|
|
1153
|
+
const snapshot = typeof mgr.getSessionProgressSnapshot === 'function'
|
|
1154
|
+
? mgr.getSessionProgressSnapshot(sessionId)
|
|
1155
|
+
: null;
|
|
1156
|
+
const abortErr = snapshot
|
|
1157
|
+
? evaluateAgentWatchdogAbort(snapshot, now, watchdogPolicy)
|
|
1158
|
+
: null;
|
|
1159
|
+
const sess = typeof mgr.getSession === 'function' ? mgr.getSession(sessionId) : null;
|
|
1160
|
+
const iteration = typeof sess?.lastIterationIndex === 'number' ? sess.lastIterationIndex : null;
|
|
1161
|
+
if (!abortErr && !snapshot) {
|
|
1162
|
+
const reported = mgr.getSessionLastProgressAt(sessionId);
|
|
1163
|
+
const last = reported || anchorTs;
|
|
1164
|
+
if (watchdogPolicy.idleStaleMs > 0 && now - last > watchdogPolicy.idleStaleMs) {
|
|
1166
1165
|
abortAgentProgressWatchdog(controller, {
|
|
1167
1166
|
sessionId,
|
|
1168
1167
|
agent,
|
|
1169
|
-
error:
|
|
1170
|
-
snapshot,
|
|
1168
|
+
error: new AgentStallAbortError(`agent task stale (${watchdogPolicy.idleStaleMs}ms without progress)`),
|
|
1171
1169
|
policy: watchdogPolicy,
|
|
1172
1170
|
now,
|
|
1173
1171
|
anchorTs,
|
|
1172
|
+
lastProgressAt: reported,
|
|
1174
1173
|
iteration,
|
|
1175
1174
|
});
|
|
1176
1175
|
}
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
if (abortErr) {
|
|
1179
|
+
abortAgentProgressWatchdog(controller, {
|
|
1180
|
+
sessionId,
|
|
1181
|
+
agent,
|
|
1182
|
+
error: abortErr,
|
|
1183
|
+
snapshot,
|
|
1184
|
+
policy: watchdogPolicy,
|
|
1185
|
+
now,
|
|
1186
|
+
anchorTs,
|
|
1187
|
+
iteration,
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
function ensureProgressWatchdogTimer() {
|
|
1193
|
+
if (progressWatchdogTimer || progressWatchdogs.size === 0) return;
|
|
1194
|
+
progressWatchdogTimer = setInterval(() => {
|
|
1195
|
+
for (const state of [...progressWatchdogs.values()]) {
|
|
1196
|
+
checkProgressIdleWatchdog(state);
|
|
1197
|
+
}
|
|
1177
1198
|
}, 1000);
|
|
1178
|
-
|
|
1199
|
+
progressWatchdogTimer.unref?.();
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
function startProgressIdleWatchdog(sessionId, watchdogPolicy, agent = null) {
|
|
1203
|
+
if (!sessionId || !agentWatchdogPolicyActive(watchdogPolicy)) return null;
|
|
1204
|
+
if (typeof mgr.getSessionProgressSnapshot !== 'function' && typeof mgr.getSessionLastProgressAt !== 'function') return null;
|
|
1205
|
+
if (typeof mgr.linkParentSignalToSession !== 'function') return null;
|
|
1206
|
+
const controller = new AbortController();
|
|
1207
|
+
const anchorTs = Date.now();
|
|
1208
|
+
try { mgr.linkParentSignalToSession(sessionId, controller.signal); } catch { return null; }
|
|
1209
|
+
const state = { sessionId, watchdogPolicy, agent, controller, anchorTs };
|
|
1210
|
+
progressWatchdogs.set(sessionId, state);
|
|
1211
|
+
ensureProgressWatchdogTimer();
|
|
1179
1212
|
return {
|
|
1180
1213
|
stop: () => {
|
|
1181
|
-
|
|
1214
|
+
if (progressWatchdogs.get(sessionId) === state) progressWatchdogs.delete(sessionId);
|
|
1215
|
+
stopProgressWatchdogTimerIfIdle();
|
|
1182
1216
|
},
|
|
1183
1217
|
};
|
|
1184
1218
|
}
|
package/src/tui/App.jsx
CHANGED
|
@@ -95,6 +95,7 @@ import {
|
|
|
95
95
|
TRANSCRIPT_WINDOW_MIN_ITEMS,
|
|
96
96
|
TRANSCRIPT_WINDOW_OVERSCAN_ROWS,
|
|
97
97
|
TRANSCRIPT_WINDOW_MAX_ITEMS,
|
|
98
|
+
TRANSCRIPT_WINDOW_TAIL_OVERSCAN_ROWS,
|
|
98
99
|
SELECTION_PAINT_INTERVAL_MS,
|
|
99
100
|
SCROLL_COALESCE_MS,
|
|
100
101
|
PROMPT_HISTORY_LIMIT,
|
|
@@ -3161,6 +3162,9 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false })
|
|
|
3161
3162
|
rightTone={attachOverlayHint ? inputHintTone : 'info'}
|
|
3162
3163
|
rightMessageWidth={attachOverlayHint ? (guardHintWidth || transientStatusWidth || 24) : 24}
|
|
3163
3164
|
themeEpoch={state.themeEpoch || 0}
|
|
3165
|
+
streamingWindowRows={transcriptTailPinned && item.id === state.streamingTail?.id
|
|
3166
|
+
? transcriptContentHeight + TRANSCRIPT_WINDOW_TAIL_OVERSCAN_ROWS
|
|
3167
|
+
: 0}
|
|
3164
3168
|
/>
|
|
3165
3169
|
);
|
|
3166
3170
|
// When measured-rows is on, wrap each row in a zero-cost flex column
|
|
@@ -98,14 +98,14 @@ export function parseMemoryCoreRows(text) {
|
|
|
98
98
|
// meta column instead (blank for common).
|
|
99
99
|
return null;
|
|
100
100
|
}
|
|
101
|
-
const match = raw.match(/^id=(\d+)\s
|
|
101
|
+
const match = raw.match(/^id=(\d+)\s+(.+?)(?:\s+—\s+(.+))?$/);
|
|
102
102
|
if (match) {
|
|
103
|
-
const [, id,
|
|
103
|
+
const [, id, element, summary = ''] = match;
|
|
104
104
|
return {
|
|
105
105
|
value: `core-${id}`,
|
|
106
106
|
// Display is summary-first: session injection only ever uses the
|
|
107
|
-
// summary sentence (buildSessionCoreMemoryPayload), so id/
|
|
108
|
-
//
|
|
107
|
+
// summary sentence (buildSessionCoreMemoryPayload), so id/element
|
|
108
|
+
// are UI noise. The element stays in the hidden fields for
|
|
109
109
|
// edit/delete plumbing.
|
|
110
110
|
label: `#${id}`,
|
|
111
111
|
meta: currentProjectId || '',
|
|
@@ -113,7 +113,6 @@ export function parseMemoryCoreRows(text) {
|
|
|
113
113
|
_line: raw,
|
|
114
114
|
_action: 'core-entry',
|
|
115
115
|
_id: Number(id),
|
|
116
|
-
_category: category,
|
|
117
116
|
_element: element,
|
|
118
117
|
_summary: summary || element,
|
|
119
118
|
_projectId: currentProjectId,
|
|
@@ -142,8 +141,8 @@ export function parseMemoryCandidateRows(text) {
|
|
|
142
141
|
if (!trimmed || /^core candidates:\s*none$/i.test(trimmed)) return [];
|
|
143
142
|
// Backend row shape (index.mjs ~3164), one candidate per line, no group
|
|
144
143
|
// headers:
|
|
145
|
-
// id=<n> project=<COMMON|slug>
|
|
146
|
-
const rowPattern = /^id=(\d+)\s+project=(\S+)\s
|
|
144
|
+
// id=<n> project=<COMMON|slug> score=<x.xx|-> <element> — <summary> (<reason>)
|
|
145
|
+
const rowPattern = /^id=(\d+)\s+project=(\S+)\s+score=(\S+)\s+(.+?)\s+—\s+(.+?)\s+\(([^)]*)\)$/;
|
|
147
146
|
return trimmed
|
|
148
147
|
.split('\n')
|
|
149
148
|
.filter((line) => line.trim())
|
|
@@ -151,10 +150,10 @@ export function parseMemoryCandidateRows(text) {
|
|
|
151
150
|
const raw = line.trim();
|
|
152
151
|
const match = raw.match(rowPattern);
|
|
153
152
|
if (match) {
|
|
154
|
-
const [, id, project,
|
|
153
|
+
const [, id, project, score, element, summary, reason] = match;
|
|
155
154
|
return {
|
|
156
155
|
value: `candidate-${id}`,
|
|
157
|
-
label: `#${id}
|
|
156
|
+
label: `#${id} ${element}`,
|
|
158
157
|
meta: project === 'COMMON' ? 'common' : project,
|
|
159
158
|
description: `${summary}${score !== '-' ? ` (score ${score})` : ''} — ${reason}`,
|
|
160
159
|
_line: raw,
|