@cloudcli-ai/cloudcli 1.37.1 → 1.37.2
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/dist/assets/abnfDiagram-N423BO3Z-C4UAoHJb.js +1 -0
- package/dist/assets/arc-B8jF-ja9.js +1 -0
- package/dist/assets/architectureDiagram-T3A2C74G-C_Wur_CU.js +36 -0
- package/dist/assets/blockDiagram-VBNYF7ZC-Cq4P1i8t.js +132 -0
- package/dist/assets/c4Diagram-5PPSVZJV-C1wG4eUj.js +10 -0
- package/dist/assets/channel-_falYbU1.js +1 -0
- package/dist/assets/chunk-2GRJ4B5K-B8fnBG6E.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-WdfPooPJ.js +1 -0
- package/dist/assets/chunk-5RXB4S5H-DhZJuG2E.js +231 -0
- package/dist/assets/chunk-5VM5RSS4-B0rjslal.js +15 -0
- package/dist/assets/chunk-6Q2QTUOP-CG2Oj4jL.js +88 -0
- package/dist/assets/chunk-GF5L2VYU-rrl2lRxY.js +206 -0
- package/dist/assets/chunk-JWPE2WC7-CjVbrVMA.js +1 -0
- package/dist/assets/chunk-KBJHAD2P-CRzKgU8S.js +1 -0
- package/dist/assets/chunk-RYQCIY6F-DBz4GTZF.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-wHxalsXk.js +1 -0
- package/dist/assets/classDiagram-JCYQIIEL-Dn-3CfpR.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-Dn-3CfpR.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-s1hygY8e.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-C4LGV15i.js +178 -0
- package/dist/assets/cynefinDiagram-MW4NZA55-C5uhu7B3.js +62 -0
- package/dist/assets/cytoscape.esm-C10L9Icf.js +331 -0
- package/dist/assets/dagre-VZM6K2ZE-B-OBTewm.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-7IWD3JNH-BjGKQxih.js +30 -0
- package/dist/assets/diagram-B4RE2ZJO-BMKz1X2L.js +3 -0
- package/dist/assets/diagram-LBJQPF4R-DNh-GhKC.js +24 -0
- package/dist/assets/diagram-Q27KOJAE-BaTG9jPT.js +24 -0
- package/dist/assets/diagram-UB23O5K3-BUwo2OvJ.js +41 -0
- package/dist/assets/ebnfDiagram-BXEA7PRR-DAZgfzX2.js +1 -0
- package/dist/assets/erDiagram-JOGREHBK-1w1CcGEb.js +85 -0
- package/dist/assets/flowDiagram-UKHOOZJN-D4EII8Jj.js +156 -0
- package/dist/assets/ganttDiagram-PKOTCBZU-ChvJZAgB.js +292 -0
- package/dist/assets/gitGraphDiagram-DS77QQ5N-DtOn41S8.js +106 -0
- package/dist/assets/graph-Cx-EqisW.js +1 -0
- package/dist/assets/index-CzPLBvXT.css +32 -0
- package/dist/assets/{index-DTMWjJj8.js → index-DFkBBuSA.js} +303 -304
- package/dist/assets/infoDiagram-6WML65LV-DN_PVEJZ.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-WSZJBQD7-DbkF1kXb.js +70 -0
- package/dist/assets/journeyDiagram-NVQOT4AX-pJwmhddE.js +139 -0
- package/dist/assets/kanban-definition-27J2QSJJ-PPAPgNp5.js +89 -0
- package/dist/assets/layout-D6BstSzF.js +1 -0
- package/dist/assets/linear-iPR-J9f7.js +1 -0
- package/dist/assets/map-CV9uif0g.js +1 -0
- package/dist/assets/mermaid.core-BEUL86Z6.js +312 -0
- package/dist/assets/mindmap-definition-FAOFIHXS-BvZoBib7.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pegDiagram-VL7TDLO6-DhYreaUd.js +1 -0
- package/dist/assets/pieDiagram-7S7Q4E2Y-CrOHlyD0.js +39 -0
- package/dist/assets/quadrantDiagram-CIZ2JOQS-D4g5yH3t.js +7 -0
- package/dist/assets/railroadDiagram-AXF67PYL-Dj39lHlf.js +1 -0
- package/dist/assets/requirementDiagram-LRYGKXZP-1AS4Zgm_.js +84 -0
- package/dist/assets/sankeyDiagram-W5VNT64P-CKlMn19P.js +40 -0
- package/dist/assets/sequenceDiagram-SI44F4Z6-B_j3qyvo.js +162 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-Bp2Op16v.js +1 -0
- package/dist/assets/stateDiagram-OKZ733FA-C0cN6DoY.js +1 -0
- package/dist/assets/stateDiagram-v2-UEYNNEHI-BvBxUUrZ.js +1 -0
- package/dist/assets/swimlanes-SLNWSIFB-BEvXOV9b.js +2 -0
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-CsE-x4YK.js +8 -0
- package/dist/assets/timeline-definition-Z64GVDOM-zdJQinvZ.js +120 -0
- package/dist/assets/vennDiagram-T6HMQDX7-DuF1uZMY.js +34 -0
- package/dist/assets/wardleyDiagram-T6FBY63Y-B9AlLQup.js +78 -0
- package/dist/assets/xychartDiagram-ELKLHX3M-DTJX48Qg.js +7 -0
- package/dist/index.html +2 -2
- package/dist-server/server/index.js +1 -2
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/agent/agent.module.js +2 -1
- package/dist-server/server/modules/agent/agent.module.js.map +1 -1
- package/dist-server/server/modules/auth/auth.middleware.js +1 -1
- package/dist-server/server/modules/auth/auth.middleware.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use-runtime.js +10 -0
- package/dist-server/server/modules/browser-use/browser-use-runtime.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js +3 -6
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
- package/dist-server/server/modules/browser-use/index.js +2 -0
- package/dist-server/server/modules/browser-use/index.js.map +1 -1
- package/dist-server/server/modules/cli/cli.js +9 -3
- package/dist-server/server/modules/cli/cli.js.map +1 -1
- package/dist-server/server/modules/cli/cli.module.js +0 -1
- package/dist-server/server/modules/cli/cli.module.js.map +1 -1
- package/dist-server/server/modules/cli/cli.service.js +0 -18
- package/dist-server/server/modules/cli/cli.service.js.map +1 -1
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +154 -0
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js.map +1 -0
- package/dist-server/server/modules/cli/tests/cli.service.test.js +0 -3
- package/dist-server/server/modules/cli/tests/cli.service.test.js.map +1 -1
- package/dist-server/server/modules/file-tree/file-tree.service.js +18 -10
- package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -1
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +62 -11
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
- package/dist-server/server/modules/git/git-branch.service.js +15 -0
- package/dist-server/server/modules/git/git-branch.service.js.map +1 -0
- package/dist-server/server/modules/git/git.routes.js +15 -7
- package/dist-server/server/modules/git/git.routes.js.map +1 -1
- package/dist-server/server/modules/git/tests/git-branch.service.test.js +52 -0
- package/dist-server/server/modules/git/tests/git-branch.service.test.js.map +1 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js +84 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js.map +1 -1
- package/dist-server/server/modules/notifications/index.js +3 -1
- package/dist-server/server/modules/notifications/index.js.map +1 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +22 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +254 -48
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +5 -0
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js +63 -7
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider.routes.test.js +35 -0
- package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -1
- package/dist-server/server/shared/utils.js +8 -0
- package/dist-server/server/shared/utils.js.map +1 -1
- package/package.json +6 -2
- package/scripts/promote-dist-server.mjs +50 -0
- package/server/index.ts +1 -2
- package/server/modules/agent/agent.module.ts +2 -1
- package/server/modules/auth/auth.middleware.ts +2 -2
- package/server/modules/browser-use/browser-use-runtime.ts +10 -0
- package/server/modules/browser-use/browser-use.service.ts +5 -8
- package/server/modules/browser-use/index.ts +3 -0
- package/server/modules/cli/cli.module.ts +0 -1
- package/server/modules/cli/cli.service.ts +0 -20
- package/server/modules/cli/cli.ts +9 -3
- package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -0
- package/server/modules/cli/tests/cli.service.test.ts +0 -3
- package/server/modules/file-tree/file-tree.service.ts +21 -10
- package/server/modules/file-tree/tests/file-tree.service.test.ts +65 -11
- package/server/modules/git/git-branch.service.ts +43 -0
- package/server/modules/git/git.routes.ts +16 -9
- package/server/modules/git/tests/git-branch.service.test.ts +60 -0
- package/server/modules/git/tests/git-init.routes.test.ts +83 -0
- package/server/modules/notifications/index.ts +2 -0
- package/server/modules/notifications/services/notification-orchestrator.service.js +24 -1
- package/server/modules/providers/list/claude/claude-runtime.provider.js +275 -54
- package/server/modules/providers/provider.routes.ts +5 -0
- package/server/modules/providers/services/session-conversations-search.service.ts +94 -7
- package/server/modules/providers/tests/provider.routes.test.ts +61 -0
- package/server/shared/types.ts +0 -1
- package/server/shared/utils.ts +10 -0
- package/server/tsconfig.json +6 -2
- package/README.de.md +0 -257
- package/README.ja.md +0 -249
- package/README.ko.md +0 -249
- package/README.md +0 -269
- package/README.ru.md +0 -257
- package/README.tr.md +0 -258
- package/README.zh-CN.md +0 -249
- package/README.zh-TW.md +0 -249
- package/dist/assets/index-D3jfzuKN.css +0 -32
- package/dist-server/server/constants/config.js +0 -6
- package/dist-server/server/constants/config.js.map +0 -1
- package/server/constants/config.ts +0 -5
|
@@ -28,6 +28,7 @@ import { CLAUDE_PREDEFINED_MODELS } from '@/modules/providers/list/claude/claude
|
|
|
28
28
|
import { resolveClaudeCodeExecutablePath } from '@/shared/claude-cli-path.js';
|
|
29
29
|
import {
|
|
30
30
|
createNotificationEvent,
|
|
31
|
+
notifyBackgroundWorkCompleted,
|
|
31
32
|
notifyRunFailed,
|
|
32
33
|
notifyRunStopped,
|
|
33
34
|
notifyUserIfEnabled
|
|
@@ -40,9 +41,32 @@ const pendingToolApprovals = new Map();
|
|
|
40
41
|
// terminal `complete` (aborted: true) to the client, so the run loop must not
|
|
41
42
|
// emit a second one when its generator winds down.
|
|
42
43
|
const abortedSessionIds = new Set();
|
|
44
|
+
// Query instances interrupted because a newer run took over their session id
|
|
45
|
+
// (see addSession). Their run loops must stay silent on wind-down: the map
|
|
46
|
+
// entry, the abort flag, and all client-facing events belong to the new run.
|
|
47
|
+
const supersededInstances = new WeakSet();
|
|
43
48
|
|
|
44
49
|
const TOOL_APPROVAL_TIMEOUT_MS = parseInt(process.env.CLAUDE_TOOL_APPROVAL_TIMEOUT_MS, 10) || 55000;
|
|
45
50
|
|
|
51
|
+
// How long background work is allowed to keep running after a turn ends. This drives
|
|
52
|
+
// two halves of the same behaviour:
|
|
53
|
+
//
|
|
54
|
+
// 1. Passed to the spawned CLI as CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS, which is how
|
|
55
|
+
// long it waits for still-running background *agents* before killing them.
|
|
56
|
+
// 2. A backstop on how long we hold the SDK's stdin open after a turn's `result`.
|
|
57
|
+
// The SDK closes stdin as soon as a turn ends, and the CLI reads that EOF as
|
|
58
|
+
// "print wind-down" — killing background *shells* after a short grace period,
|
|
59
|
+
// which the ceiling above does not cover. Holding stdin open also lets the CLI
|
|
60
|
+
// push follow-up turns (background-task completions, Monitor notifications,
|
|
61
|
+
// scheduled wake-ups).
|
|
62
|
+
//
|
|
63
|
+
// The hold normally ends long before this: a turn with nothing outstanding closes
|
|
64
|
+
// stdin immediately, background work releases it as soon as it reports back, and a
|
|
65
|
+
// new turn supersedes the previous hold. This ceiling only catches background work
|
|
66
|
+
// that never reports at all, so an abandoned session cannot leak a CLI process
|
|
67
|
+
// forever. The timer resets on every message, so it measures silence, not total time.
|
|
68
|
+
const BG_WAIT_CEILING_MS = 30 * 60 * 1000;
|
|
69
|
+
|
|
46
70
|
const TOOLS_REQUIRING_INTERACTION = new Set(['AskUserQuestion', 'ExitPlanMode']);
|
|
47
71
|
|
|
48
72
|
function resolveClaudeEffort(model, effort, modelsDefinition = CLAUDE_PREDEFINED_MODELS) {
|
|
@@ -165,10 +189,7 @@ function mapCliOptionsToSDK(options = {}) {
|
|
|
165
189
|
|
|
166
190
|
// Forward all host env vars (e.g. ANTHROPIC_BASE_URL) to the subprocess.
|
|
167
191
|
// Since SDK 0.2.113, options.env replaces process.env instead of overlaying it.
|
|
168
|
-
|
|
169
|
-
// still-running background agents after a turn ends is set by CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS
|
|
170
|
-
// (default 600000 ms; 0 = wait indefinitely) on this server's own environment.
|
|
171
|
-
sdkOptions.env = { ...process.env };
|
|
192
|
+
sdkOptions.env = { ...process.env, CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS: String(BG_WAIT_CEILING_MS) };
|
|
172
193
|
|
|
173
194
|
// Resolve the executable eagerly on Windows because the SDK uses raw child_process.spawn,
|
|
174
195
|
// which does not reliably follow npm's shell wrappers like cross-spawn does.
|
|
@@ -243,13 +264,37 @@ function mapCliOptionsToSDK(options = {}) {
|
|
|
243
264
|
* @param {string} sessionId - Session identifier
|
|
244
265
|
* @param {Object} queryInstance - SDK query instance
|
|
245
266
|
* @param {Object} writer - WebSocket writer for reconnect support
|
|
267
|
+
* @param {Function} releaseInput - Closes the held stdin stream so the CLI can exit
|
|
246
268
|
*/
|
|
247
|
-
function addSession(sessionId, queryInstance, writer = null) {
|
|
269
|
+
function addSession(sessionId, queryInstance, writer = null, releaseInput = null) {
|
|
270
|
+
const existing = activeSessions.get(sessionId);
|
|
271
|
+
// A different live instance under the same key means an earlier run was
|
|
272
|
+
// superseded without being stopped (e.g. an abort that raced run setup and
|
|
273
|
+
// found nothing to interrupt). Overwriting it here would strand its
|
|
274
|
+
// generator forever — this map entry is the only handle for interrupting
|
|
275
|
+
// it. Stop it directly rather than via abortClaudeSDKSession, whose
|
|
276
|
+
// session-keyed abortedSessionIds flag would be consumed by the new run
|
|
277
|
+
// and suppress its terminal `complete`.
|
|
278
|
+
const superseding = Boolean(
|
|
279
|
+
existing && existing.status === 'active' && existing.instance && existing.instance !== queryInstance
|
|
280
|
+
);
|
|
281
|
+
if (superseding) {
|
|
282
|
+
supersededInstances.add(existing.instance);
|
|
283
|
+
Promise.resolve()
|
|
284
|
+
.then(() => existing.instance.interrupt())
|
|
285
|
+
.catch((error) => {
|
|
286
|
+
console.error(`Error interrupting superseded run for session ${sessionId}:`, error?.message || error);
|
|
287
|
+
});
|
|
288
|
+
existing.releaseInput?.();
|
|
289
|
+
}
|
|
290
|
+
const carried = superseding ? null : existing;
|
|
248
291
|
activeSessions.set(sessionId, {
|
|
249
292
|
instance: queryInstance,
|
|
250
|
-
startTime: Date.now(),
|
|
293
|
+
startTime: carried?.startTime || Date.now(),
|
|
251
294
|
status: 'active',
|
|
252
|
-
writer
|
|
295
|
+
writer,
|
|
296
|
+
// Re-registered mid-run once the provider session id lands; keep the closer.
|
|
297
|
+
releaseInput: releaseInput || carried?.releaseInput || null
|
|
253
298
|
});
|
|
254
299
|
}
|
|
255
300
|
|
|
@@ -366,38 +411,93 @@ function extractTokenBudget(sdkMessage) {
|
|
|
366
411
|
};
|
|
367
412
|
}
|
|
368
413
|
|
|
414
|
+
// Tool calls that leave work running past the end of a turn. Bash only counts
|
|
415
|
+
// when it is explicitly backgrounded; the rest defer or watch work by nature.
|
|
416
|
+
const DEFERRED_WORK_TOOLS = new Set(['Monitor', 'ScheduleWakeup', 'CronCreate', 'TaskCreate']);
|
|
417
|
+
|
|
369
418
|
/**
|
|
370
|
-
*
|
|
419
|
+
* Detects tool calls that keep working after the turn's `result` arrives.
|
|
371
420
|
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
421
|
+
* Only turns that start background work need their CLI process held open; every
|
|
422
|
+
* other turn can let it exit immediately, as it did before the hold existed.
|
|
423
|
+
*
|
|
424
|
+
* @param {Object} sdkMessage - SDK stream message
|
|
425
|
+
* @returns {boolean} True when the message launches work that outlives the turn
|
|
426
|
+
*/
|
|
427
|
+
function startsBackgroundWork(sdkMessage) {
|
|
428
|
+
const content = sdkMessage?.message?.content;
|
|
429
|
+
if (!Array.isArray(content)) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return content.some((block) => {
|
|
434
|
+
if (block?.type !== 'tool_use') {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
if (block.name === 'Bash') {
|
|
438
|
+
return block.input?.run_in_background === true;
|
|
439
|
+
}
|
|
440
|
+
return DEFERRED_WORK_TOOLS.has(block.name);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Builds the SDK user messages for one turn.
|
|
446
|
+
*
|
|
447
|
+
* Always returns SDKUserMessage records rather than a bare string: a string
|
|
448
|
+
* prompt makes the SDK flag the query as single-turn and close stdin the moment
|
|
449
|
+
* the turn's `result` arrives, which kills the CLI's background tasks. Plain
|
|
450
|
+
* text turns carry string content; turns with image attachments carry the
|
|
451
|
+
* prompt text plus one base64 `image` block per attachment (read from the
|
|
452
|
+
* global `~/.cloudcli/assets` folder).
|
|
376
453
|
*
|
|
377
454
|
* @param {string} command - User prompt
|
|
378
455
|
* @param {Array} images - Image descriptors ({ path, name?, mimeType? })
|
|
379
456
|
* @param {Array} files - Non-image attachment descriptors
|
|
380
457
|
* @param {string} cwd - Project working directory attachment paths resolve against
|
|
381
|
-
* @returns {Promise<
|
|
458
|
+
* @returns {Promise<Array<Object>>} SDKUserMessage records for the turn
|
|
382
459
|
*/
|
|
383
|
-
async function
|
|
460
|
+
async function buildPromptMessages(command, images, files, cwd) {
|
|
384
461
|
const promptWithFiles = appendFilesInputTag(command, files);
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
462
|
+
const content = normalizeImageDescriptors(images).length === 0
|
|
463
|
+
? promptWithFiles
|
|
464
|
+
: await buildClaudeUserContent(promptWithFiles, images, cwd);
|
|
465
|
+
|
|
466
|
+
return [{
|
|
467
|
+
type: 'user',
|
|
468
|
+
message: {
|
|
469
|
+
role: 'user',
|
|
470
|
+
content
|
|
471
|
+
},
|
|
472
|
+
parent_tool_use_id: null,
|
|
473
|
+
timestamp: new Date().toISOString()
|
|
474
|
+
}];
|
|
475
|
+
}
|
|
388
476
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
477
|
+
/**
|
|
478
|
+
* Wraps prompt messages in an async iterable that yields them and then parks.
|
|
479
|
+
*
|
|
480
|
+
* The SDK closes the CLI's stdin as soon as its input iterable is exhausted (and
|
|
481
|
+
* immediately on `result` for string prompts). The CLI reads that EOF as the end
|
|
482
|
+
* of the run and kills anything still going in the background, so the iterable
|
|
483
|
+
* has to stay pending until we actually want the process gone.
|
|
484
|
+
*
|
|
485
|
+
* @param {Array<Object>} messages - SDKUserMessage records to send
|
|
486
|
+
* @returns {{ stream: AsyncIterable, release: () => void }} Stream plus its closer
|
|
487
|
+
*/
|
|
488
|
+
function createHeldPromptStream(messages) {
|
|
489
|
+
let release;
|
|
490
|
+
const held = new Promise((resolve) => { release = resolve; });
|
|
491
|
+
|
|
492
|
+
const stream = (async function* () {
|
|
493
|
+
for (const message of messages) {
|
|
494
|
+
yield message;
|
|
495
|
+
}
|
|
496
|
+
// Keeps stdin open — the CLI stays alive until release() is called.
|
|
497
|
+
await held;
|
|
400
498
|
})();
|
|
499
|
+
|
|
500
|
+
return { stream, release };
|
|
401
501
|
}
|
|
402
502
|
|
|
403
503
|
/**
|
|
@@ -486,6 +586,44 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
486
586
|
});
|
|
487
587
|
};
|
|
488
588
|
|
|
589
|
+
// Closes the held stdin stream so the CLI can wind down. Replaced once the
|
|
590
|
+
// stream exists; the finally block calls it no matter how the run ends.
|
|
591
|
+
let releasePromptStream = () => {};
|
|
592
|
+
let idleReleaseTimer = null;
|
|
593
|
+
// The client is told the turn is over as soon as `result` lands, even though
|
|
594
|
+
// the process lingers, so the UI never waits out the idle hold.
|
|
595
|
+
let turnCompleteSent = false;
|
|
596
|
+
// Set when a turn starts background work, cleared when the next `result`
|
|
597
|
+
// arrives — only turns with work still outstanding hold their process open.
|
|
598
|
+
let backgroundWorkPending = false;
|
|
599
|
+
// True while the process is being held open for background work, so a later
|
|
600
|
+
// `result` can be recognised as that work reporting back.
|
|
601
|
+
let heldForBackgroundWork = false;
|
|
602
|
+
|
|
603
|
+
// A new turn supersedes any earlier one still holding this session's process
|
|
604
|
+
// open, so held runs cannot stack up across a conversation.
|
|
605
|
+
if (sessionKey()) {
|
|
606
|
+
getSession(sessionKey())?.releaseInput?.();
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Arms (or re-arms) the idle countdown that eventually closes stdin.
|
|
610
|
+
const scheduleRelease = () => {
|
|
611
|
+
if (idleReleaseTimer) {
|
|
612
|
+
clearTimeout(idleReleaseTimer);
|
|
613
|
+
idleReleaseTimer = null;
|
|
614
|
+
}
|
|
615
|
+
idleReleaseTimer = setTimeout(() => {
|
|
616
|
+
idleReleaseTimer = null;
|
|
617
|
+
releasePromptStream();
|
|
618
|
+
}, BG_WAIT_CEILING_MS);
|
|
619
|
+
// Never let the hold keep the server process alive on its own.
|
|
620
|
+
idleReleaseTimer.unref?.();
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
// Hoisted above the try so the catch's cleanup can tell whether this run
|
|
624
|
+
// still owns the activeSessions entry (or was superseded by a newer run).
|
|
625
|
+
let queryInstance = null;
|
|
626
|
+
|
|
489
627
|
try {
|
|
490
628
|
const resolvedModel = await context.resolveResumeModel(sessionId, options.model);
|
|
491
629
|
let effortModels = CLAUDE_PREDEFINED_MODELS;
|
|
@@ -507,10 +645,10 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
507
645
|
sdkOptions.mcpServers = mcpServers;
|
|
508
646
|
}
|
|
509
647
|
|
|
510
|
-
//
|
|
511
|
-
//
|
|
512
|
-
// async generator cannot be replayed once consumed.
|
|
513
|
-
const
|
|
648
|
+
// Every turn uses streaming input so stdin stays open past the turn's
|
|
649
|
+
// `result`. The message list is reusable, but each query attempt needs its
|
|
650
|
+
// own stream because an async generator cannot be replayed once consumed.
|
|
651
|
+
const promptMessages = await buildPromptMessages(command, options.images, options.files, options.cwd);
|
|
514
652
|
|
|
515
653
|
sdkOptions.hooks = {
|
|
516
654
|
Notification: [{
|
|
@@ -613,10 +751,11 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
613
751
|
return { behavior: 'deny', message: decision.message ?? 'User denied tool use' };
|
|
614
752
|
};
|
|
615
753
|
|
|
616
|
-
let
|
|
754
|
+
let heldPrompt = createHeldPromptStream(promptMessages);
|
|
755
|
+
releasePromptStream = heldPrompt.release;
|
|
617
756
|
try {
|
|
618
757
|
queryInstance = query({
|
|
619
|
-
prompt:
|
|
758
|
+
prompt: heldPrompt.stream,
|
|
620
759
|
options: sdkOptions
|
|
621
760
|
});
|
|
622
761
|
} catch (hookError) {
|
|
@@ -624,15 +763,19 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
624
763
|
// Keep notification behavior operational via runtime events even if hook registration fails.
|
|
625
764
|
console.warn('Failed to initialize Claude query with hooks, retrying without hooks:', hookError?.message || hookError);
|
|
626
765
|
delete sdkOptions.hooks;
|
|
766
|
+
// Discard the abandoned stream and build a fresh one for the retry.
|
|
767
|
+
heldPrompt.release();
|
|
768
|
+
heldPrompt = createHeldPromptStream(promptMessages);
|
|
769
|
+
releasePromptStream = heldPrompt.release;
|
|
627
770
|
queryInstance = query({
|
|
628
|
-
prompt:
|
|
771
|
+
prompt: heldPrompt.stream,
|
|
629
772
|
options: sdkOptions
|
|
630
773
|
});
|
|
631
774
|
}
|
|
632
775
|
|
|
633
776
|
// Track the query instance for abort capability
|
|
634
777
|
if (sessionKey()) {
|
|
635
|
-
addSession(sessionKey(), queryInstance, ws);
|
|
778
|
+
addSession(sessionKey(), queryInstance, ws, releasePromptStream);
|
|
636
779
|
}
|
|
637
780
|
|
|
638
781
|
// Process streaming messages
|
|
@@ -642,7 +785,7 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
642
785
|
if (message.session_id && !capturedSessionId) {
|
|
643
786
|
|
|
644
787
|
capturedSessionId = message.session_id;
|
|
645
|
-
addSession(sessionKey(), queryInstance, ws);
|
|
788
|
+
addSession(sessionKey(), queryInstance, ws, releasePromptStream);
|
|
646
789
|
|
|
647
790
|
// Set session ID on writer
|
|
648
791
|
if (ws.setSessionId && typeof ws.setSessionId === 'function') {
|
|
@@ -677,36 +820,98 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
677
820
|
if (tokenBudgetData) {
|
|
678
821
|
ws.send(createNormalizedMessage({ kind: 'status', text: 'token_budget', tokenBudget: tokenBudgetData, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
|
|
679
822
|
}
|
|
823
|
+
|
|
824
|
+
if (startsBackgroundWork(message)) {
|
|
825
|
+
backgroundWorkPending = true;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (message.type === 'result') {
|
|
829
|
+
// The turn is done as far as the client is concerned.
|
|
830
|
+
const abortPending = sessionKey() ? abortedSessionIds.has(sessionKey()) : false;
|
|
831
|
+
if (!turnCompleteSent && !abortPending) {
|
|
832
|
+
turnCompleteSent = true;
|
|
833
|
+
ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 0 }));
|
|
834
|
+
notifyRunStopped({
|
|
835
|
+
userId: ws?.userId || null,
|
|
836
|
+
provider: 'claude',
|
|
837
|
+
sessionId: sessionId || capturedSessionId || null,
|
|
838
|
+
sessionName: sessionSummary,
|
|
839
|
+
stopReason: 'completed'
|
|
840
|
+
});
|
|
841
|
+
} else if (heldForBackgroundWork && !abortPending) {
|
|
842
|
+
// A result after the turn already reported complete means the work we
|
|
843
|
+
// held the process open for has finished and pushed a follow-up turn.
|
|
844
|
+
notifyBackgroundWorkCompleted({
|
|
845
|
+
userId: ws?.userId || null,
|
|
846
|
+
provider: 'claude',
|
|
847
|
+
sessionId: sessionId || capturedSessionId || null,
|
|
848
|
+
sessionName: sessionSummary
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
if (backgroundWorkPending) {
|
|
852
|
+
// Work started during this turn is still running. Hold the process
|
|
853
|
+
// open so it can finish and report back in a follow-up turn; the
|
|
854
|
+
// ceiling is only a backstop for work that never reports.
|
|
855
|
+
backgroundWorkPending = false;
|
|
856
|
+
heldForBackgroundWork = true;
|
|
857
|
+
scheduleRelease();
|
|
858
|
+
} else {
|
|
859
|
+
// Either nothing was backgrounded, or the background work just
|
|
860
|
+
// reported in — let the CLI exit now, as it always has.
|
|
861
|
+
heldForBackgroundWork = false;
|
|
862
|
+
releasePromptStream();
|
|
863
|
+
}
|
|
864
|
+
} else if (idleReleaseTimer) {
|
|
865
|
+
// Background activity after the turn — push the countdown back out.
|
|
866
|
+
scheduleRelease();
|
|
867
|
+
}
|
|
680
868
|
}
|
|
681
869
|
|
|
682
|
-
// Clean up session on completion
|
|
683
|
-
|
|
870
|
+
// Clean up session on completion — only while this run still owns the map
|
|
871
|
+
// entry. A superseding run may have replaced it, and deleting here would
|
|
872
|
+
// strand that run.
|
|
873
|
+
if (sessionKey() && getSession(sessionKey())?.instance === queryInstance) {
|
|
684
874
|
removeSession(sessionKey());
|
|
685
875
|
}
|
|
686
876
|
|
|
877
|
+
// A superseded run winds down silently: the map entry, the abort flag,
|
|
878
|
+
// and all client-facing events belong to the run that replaced it.
|
|
879
|
+
const superseded = supersededInstances.has(queryInstance);
|
|
880
|
+
|
|
687
881
|
// Send the terminal completion event — skipped for aborted runs, whose
|
|
688
|
-
// terminal `complete` (aborted: true) was already sent by abort-session
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
882
|
+
// terminal `complete` (aborted: true) was already sent by abort-session, and
|
|
883
|
+
// for runs that already reported completion when their `result` arrived.
|
|
884
|
+
const wasAborted = !superseded && sessionKey() ? abortedSessionIds.delete(sessionKey()) : false;
|
|
885
|
+
if (!turnCompleteSent && !superseded) {
|
|
886
|
+
turnCompleteSent = true;
|
|
887
|
+
if (!wasAborted) {
|
|
888
|
+
ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 0 }));
|
|
889
|
+
}
|
|
890
|
+
notifyRunStopped({
|
|
891
|
+
userId: ws?.userId || null,
|
|
892
|
+
provider: 'claude',
|
|
893
|
+
sessionId: sessionId || capturedSessionId || null,
|
|
894
|
+
sessionName: sessionSummary,
|
|
895
|
+
stopReason: wasAborted ? 'aborted' : 'completed'
|
|
896
|
+
});
|
|
692
897
|
}
|
|
693
|
-
notifyRunStopped({
|
|
694
|
-
userId: ws?.userId || null,
|
|
695
|
-
provider: 'claude',
|
|
696
|
-
sessionId: sessionId || capturedSessionId || null,
|
|
697
|
-
sessionName: sessionSummary,
|
|
698
|
-
stopReason: wasAborted ? 'aborted' : 'completed'
|
|
699
|
-
});
|
|
700
898
|
// Complete
|
|
701
899
|
|
|
702
900
|
} catch (error) {
|
|
703
901
|
console.error('SDK query error:', error);
|
|
704
902
|
|
|
705
|
-
// Clean up session on error
|
|
706
|
-
|
|
903
|
+
// Clean up session on error — only while this run still owns the map entry
|
|
904
|
+
// (a superseding run may have replaced it).
|
|
905
|
+
if (sessionKey() && getSession(sessionKey())?.instance === queryInstance) {
|
|
707
906
|
removeSession(sessionKey());
|
|
708
907
|
}
|
|
709
908
|
|
|
909
|
+
if (supersededInstances.has(queryInstance)) {
|
|
910
|
+
// Interrupted because a newer run took over this session id; that run
|
|
911
|
+
// owns the abort flag and all further client-facing events.
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
|
|
710
915
|
const wasAborted = sessionKey() ? abortedSessionIds.delete(sessionKey()) : false;
|
|
711
916
|
if (wasAborted) {
|
|
712
917
|
// The abort already produced the terminal complete; a generator throw
|
|
@@ -720,9 +925,13 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
720
925
|
? 'Claude Code is not installed. Please install it first: https://docs.anthropic.com/en/docs/claude-code'
|
|
721
926
|
: error.message;
|
|
722
927
|
|
|
723
|
-
// Send error to WebSocket, then the terminal complete
|
|
928
|
+
// Send error to WebSocket, then the terminal complete. A run that already
|
|
929
|
+
// reported completion and then failed during its post-turn hold still
|
|
930
|
+
// surfaces the error, but must not emit a second terminal complete.
|
|
724
931
|
ws.send(createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
|
|
725
|
-
|
|
932
|
+
if (!turnCompleteSent) {
|
|
933
|
+
ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 1 }));
|
|
934
|
+
}
|
|
726
935
|
notifyRunFailed({
|
|
727
936
|
userId: ws?.userId || null,
|
|
728
937
|
provider: 'claude',
|
|
@@ -730,6 +939,14 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
730
939
|
sessionName: sessionSummary,
|
|
731
940
|
error
|
|
732
941
|
});
|
|
942
|
+
} finally {
|
|
943
|
+
// Always close stdin — otherwise an aborted or failed run leaves the CLI
|
|
944
|
+
// process (and its MCP servers) alive until the server exits.
|
|
945
|
+
if (idleReleaseTimer) {
|
|
946
|
+
clearTimeout(idleReleaseTimer);
|
|
947
|
+
idleReleaseTimer = null;
|
|
948
|
+
}
|
|
949
|
+
releasePromptStream();
|
|
733
950
|
}
|
|
734
951
|
}
|
|
735
952
|
|
|
@@ -756,6 +973,10 @@ async function abortClaudeSDKSession(sessionId) {
|
|
|
756
973
|
// Call interrupt() on the query instance
|
|
757
974
|
await session.instance.interrupt();
|
|
758
975
|
|
|
976
|
+
// Release the held stdin stream; without this the CLI stays up for the rest
|
|
977
|
+
// of the post-turn hold even though the user cancelled.
|
|
978
|
+
session.releaseInput?.();
|
|
979
|
+
|
|
759
980
|
// Update session status
|
|
760
981
|
session.status = 'aborted';
|
|
761
982
|
|
|
@@ -840,6 +840,11 @@ router.get('/search/sessions', asyncHandler(async (req: Request, res: Response)
|
|
|
840
840
|
query,
|
|
841
841
|
limit,
|
|
842
842
|
signal: abortController.signal,
|
|
843
|
+
onTitleResults: (titleResults) => {
|
|
844
|
+
if (!closed) {
|
|
845
|
+
res.write(`event: title-results\ndata: ${JSON.stringify({ titleResults })}\n\n`);
|
|
846
|
+
}
|
|
847
|
+
},
|
|
843
848
|
onProgress: ({ projectResult, totalMatches, scannedProjects, totalProjects }) => {
|
|
844
849
|
if (closed) {
|
|
845
850
|
return;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import fsSync
|
|
1
|
+
import fsSync from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import readline from 'node:readline';
|
|
4
4
|
|
|
@@ -38,6 +38,15 @@ type ProjectConversationResult = {
|
|
|
38
38
|
sessions: SessionConversationResult[];
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
type SessionTitleSearchResult = {
|
|
42
|
+
sessionId: string;
|
|
43
|
+
provider: string;
|
|
44
|
+
projectId: string | null;
|
|
45
|
+
projectDisplayName: string;
|
|
46
|
+
sessionTitle: string;
|
|
47
|
+
lastActivity: string | null;
|
|
48
|
+
};
|
|
49
|
+
|
|
41
50
|
export type SessionConversationSearchProgressUpdate = {
|
|
42
51
|
projectResult: ProjectConversationResult | null;
|
|
43
52
|
totalMatches: number;
|
|
@@ -49,6 +58,7 @@ type SearchSessionConversationsInput = {
|
|
|
49
58
|
query: string;
|
|
50
59
|
limit: number;
|
|
51
60
|
signal?: AbortSignal;
|
|
61
|
+
onTitleResults?: (results: SessionTitleSearchResult[]) => void;
|
|
52
62
|
onProgress?: (update: SessionConversationSearchProgressUpdate) => void;
|
|
53
63
|
};
|
|
54
64
|
|
|
@@ -156,6 +166,67 @@ function toSummaryText(customName: string | null, fallback: string | null | unde
|
|
|
156
166
|
return trimmedFallback.length > 50 ? `${trimmedFallback.slice(0, 50)}...` : trimmedFallback;
|
|
157
167
|
}
|
|
158
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Finds visible sessions whose displayed title contains the query. Title
|
|
171
|
+
* matches are resolved from the database before transcript scanning so the UI
|
|
172
|
+
* can always present them first, including sessions without a transcript yet.
|
|
173
|
+
*/
|
|
174
|
+
function findSessionTitleResults(
|
|
175
|
+
sessions: SessionRepositoryRow[],
|
|
176
|
+
query: string,
|
|
177
|
+
limit: number,
|
|
178
|
+
): SessionTitleSearchResult[] {
|
|
179
|
+
const normalizedQuery = query.toLocaleLowerCase().replace(/\s+/g, ' ');
|
|
180
|
+
const projectCache = new Map<string, ReturnType<typeof projectsDb.getProjectPath>>();
|
|
181
|
+
|
|
182
|
+
return sessions
|
|
183
|
+
.flatMap((session) => {
|
|
184
|
+
const sessionTitle = toSummaryText(session.custom_name, session.session_id, session.session_id);
|
|
185
|
+
const normalizedTitle = sessionTitle.toLocaleLowerCase().replace(/\s+/g, ' ');
|
|
186
|
+
const matchIndex = normalizedTitle.indexOf(normalizedQuery);
|
|
187
|
+
if (matchIndex === -1) {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const projectPath = typeof session.project_path === 'string' && session.project_path.trim()
|
|
192
|
+
? session.project_path.trim()
|
|
193
|
+
: null;
|
|
194
|
+
const projectKey = projectPath ?? UNKNOWN_PROJECT_KEY;
|
|
195
|
+
if (!projectCache.has(projectKey)) {
|
|
196
|
+
projectCache.set(
|
|
197
|
+
projectKey,
|
|
198
|
+
projectPath ? projectsDb.getProjectPath(projectPath) : null,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const project = projectCache.get(projectKey) ?? null;
|
|
203
|
+
if (project?.isArchived) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return [{
|
|
208
|
+
sessionId: session.session_id,
|
|
209
|
+
provider: session.provider,
|
|
210
|
+
projectId: project?.project_id ?? null,
|
|
211
|
+
projectDisplayName: projectPath
|
|
212
|
+
? (project?.custom_project_name?.trim() || path.basename(projectPath) || projectPath)
|
|
213
|
+
: 'Unknown Project',
|
|
214
|
+
sessionTitle,
|
|
215
|
+
lastActivity: session.updated_at || session.created_at || null,
|
|
216
|
+
matchIndex,
|
|
217
|
+
}];
|
|
218
|
+
})
|
|
219
|
+
.sort((left, right) => {
|
|
220
|
+
if (left.matchIndex !== right.matchIndex) {
|
|
221
|
+
return left.matchIndex - right.matchIndex;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return new Date(right.lastActivity ?? 0).getTime() - new Date(left.lastActivity ?? 0).getTime();
|
|
225
|
+
})
|
|
226
|
+
.slice(0, limit)
|
|
227
|
+
.map(({ matchIndex: _matchIndex, ...result }) => result);
|
|
228
|
+
}
|
|
229
|
+
|
|
159
230
|
function isInternalContent(content: string): boolean {
|
|
160
231
|
return INTERNAL_CONTENT_PREFIXES.some((prefix) => content.startsWith(prefix));
|
|
161
232
|
}
|
|
@@ -1071,28 +1142,42 @@ async function parseSessionMatches(
|
|
|
1071
1142
|
return null;
|
|
1072
1143
|
}
|
|
1073
1144
|
|
|
1145
|
+
/**
|
|
1146
|
+
* Searches session titles and provider transcripts for the provider search
|
|
1147
|
+
* service and its route-level integration tests.
|
|
1148
|
+
*/
|
|
1074
1149
|
export async function searchConversations(
|
|
1075
1150
|
query: string,
|
|
1076
1151
|
limit = 50,
|
|
1077
1152
|
onProjectResult: ((update: SessionConversationSearchProgressUpdate) => void) | null = null,
|
|
1078
1153
|
signal: AbortSignal | null = null,
|
|
1079
|
-
|
|
1154
|
+
onTitleResults: ((results: SessionTitleSearchResult[]) => void) | null = null,
|
|
1155
|
+
): Promise<{
|
|
1156
|
+
results: ProjectConversationResult[];
|
|
1157
|
+
titleResults: SessionTitleSearchResult[];
|
|
1158
|
+
totalMatches: number;
|
|
1159
|
+
query: string;
|
|
1160
|
+
}> {
|
|
1080
1161
|
const safeQuery = typeof query === 'string' ? query.trim() : '';
|
|
1081
1162
|
const safeLimit = Math.max(1, Math.min(Number.isFinite(limit) ? limit : 50, 200));
|
|
1082
1163
|
const words = safeQuery.toLowerCase().split(/\s+/).filter((word) => word.length > 0);
|
|
1083
1164
|
|
|
1084
1165
|
if (words.length === 0) {
|
|
1085
|
-
return { results: [], totalMatches: 0, query: safeQuery };
|
|
1166
|
+
return { results: [], titleResults: [], totalMatches: 0, query: safeQuery };
|
|
1086
1167
|
}
|
|
1087
1168
|
|
|
1088
1169
|
const isAborted = () => signal?.aborted === true;
|
|
1089
1170
|
if (isAborted()) {
|
|
1090
|
-
return { results: [], totalMatches: 0, query: safeQuery };
|
|
1171
|
+
return { results: [], titleResults: [], totalMatches: 0, query: safeQuery };
|
|
1091
1172
|
}
|
|
1092
1173
|
|
|
1093
|
-
const
|
|
1174
|
+
const activeSessions = sessionsDb.getAllSessions();
|
|
1175
|
+
const titleResults = findSessionTitleResults(activeSessions, safeQuery, safeLimit);
|
|
1176
|
+
onTitleResults?.(titleResults);
|
|
1177
|
+
|
|
1178
|
+
const searchableSessions = normalizeSearchableSessions(activeSessions);
|
|
1094
1179
|
if (searchableSessions.length === 0) {
|
|
1095
|
-
return { results: [], totalMatches: 0, query: safeQuery };
|
|
1180
|
+
return { results: [], titleResults, totalMatches: 0, query: safeQuery };
|
|
1096
1181
|
}
|
|
1097
1182
|
|
|
1098
1183
|
const sessionsByPathKey = new Map<string, SearchableSessionRow[]>();
|
|
@@ -1123,7 +1208,7 @@ export async function searchConversations(
|
|
|
1123
1208
|
signal ?? undefined,
|
|
1124
1209
|
);
|
|
1125
1210
|
if (isAborted() || matchedFileKeys.size === 0) {
|
|
1126
|
-
return { results: [], totalMatches: 0, query: safeQuery };
|
|
1211
|
+
return { results: [], titleResults, totalMatches: 0, query: safeQuery };
|
|
1127
1212
|
}
|
|
1128
1213
|
|
|
1129
1214
|
const matchedSessionKeys = new Set<string>();
|
|
@@ -1207,6 +1292,7 @@ export async function searchConversations(
|
|
|
1207
1292
|
|
|
1208
1293
|
return {
|
|
1209
1294
|
results,
|
|
1295
|
+
titleResults,
|
|
1210
1296
|
totalMatches: runtime.totalMatches,
|
|
1211
1297
|
query: safeQuery,
|
|
1212
1298
|
};
|
|
@@ -1229,6 +1315,7 @@ export const sessionConversationsSearchService = {
|
|
|
1229
1315
|
input.limit,
|
|
1230
1316
|
input.onProgress ?? null,
|
|
1231
1317
|
input.signal ?? null,
|
|
1318
|
+
input.onTitleResults ?? null,
|
|
1232
1319
|
);
|
|
1233
1320
|
},
|
|
1234
1321
|
};
|