@cloudcli-ai/cloudcli 1.37.0 → 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-DFkBBuSA.js +1549 -0
- 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/commands/commands.routes.js +3 -3
- package/dist-server/server/modules/commands/commands.routes.js.map +1 -1
- package/dist-server/server/modules/commands/tests/commands.test.js +2 -7
- package/dist-server/server/modules/commands/tests/commands.test.js.map +1 -1
- package/dist-server/server/modules/database/index.js +2 -0
- package/dist-server/server/modules/database/index.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +18 -1
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/repositories/provider-models.js +112 -0
- package/dist-server/server/modules/database/repositories/provider-models.js.map +1 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js +63 -7
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/schema.js +28 -3
- package/dist-server/server/modules/database/schema.js.map +1 -1
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +102 -0
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +2 -1
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -1
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +22 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/file-tree/file-tree.module.js +5 -1
- package/dist-server/server/modules/file-tree/file-tree.module.js.map +1 -1
- package/dist-server/server/modules/file-tree/file-tree.service.js +60 -20
- 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 +143 -16
- 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-models.provider.js +46 -14
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +259 -60
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +58 -71
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +1 -54
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +7 -7
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +32 -669
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +105 -328
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +0 -12
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +144 -31
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-models.service.js +167 -199
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-runtime.service.js +2 -2
- package/dist-server/server/modules/providers/services/provider-runtime.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js +75 -11
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +62 -3
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js +53 -17
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-models.test.js +15 -126
- package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +5 -5
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js +175 -292
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js +2 -6
- package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +1 -0
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider.routes.test.js +161 -0
- package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js +87 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +5 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.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 +8 -4
- 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/commands/commands.routes.ts +3 -3
- package/server/modules/commands/tests/commands.test.ts +2 -7
- package/server/modules/database/index.ts +2 -0
- package/server/modules/database/migrations.ts +20 -0
- package/server/modules/database/repositories/provider-models.ts +168 -0
- package/server/modules/database/repositories/sessions.db.ts +85 -8
- package/server/modules/database/schema.ts +29 -3
- package/server/modules/database/tests/provider-models.db.integration.test.ts +125 -0
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +2 -1
- package/server/modules/database/tests/sessions.db.integration.test.ts +32 -0
- package/server/modules/file-tree/file-tree.module.ts +5 -1
- package/server/modules/file-tree/file-tree.service.ts +81 -22
- package/server/modules/file-tree/tests/file-tree.service.test.ts +167 -16
- 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-models.provider.ts +46 -14
- package/server/modules/providers/list/claude/claude-runtime.provider.js +280 -67
- package/server/modules/providers/list/codex/codex-models.provider.ts +58 -99
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +1 -60
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +7 -7
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +32 -710
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +105 -413
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +0 -13
- package/server/modules/providers/provider.routes.ts +190 -33
- package/server/modules/providers/services/provider-models.service.ts +223 -288
- package/server/modules/providers/services/provider-runtime.service.ts +2 -3
- package/server/modules/providers/services/session-conversations-search.service.ts +106 -11
- package/server/modules/providers/services/sessions.service.ts +87 -3
- package/server/modules/providers/tests/codex-sessions.test.ts +57 -18
- package/server/modules/providers/tests/opencode-models.test.ts +34 -135
- package/server/modules/providers/tests/opencode-sessions.test.ts +5 -5
- package/server/modules/providers/tests/provider-models.service.test.ts +353 -450
- package/server/modules/providers/tests/provider-runtime.service.test.ts +2 -6
- package/server/modules/providers/tests/provider-token-usage.service.test.ts +1 -0
- package/server/modules/providers/tests/provider.routes.test.ts +244 -0
- package/server/modules/providers/tests/sessions.service.test.ts +127 -0
- package/server/modules/websocket/services/chat-websocket.service.ts +5 -1
- package/server/shared/interfaces.ts +4 -6
- package/server/shared/types.ts +29 -18
- 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-DSlds633.css +0 -32
- package/dist/assets/index-eGqPeIEu.js +0 -1531
- 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
|
@@ -9,12 +9,12 @@ import type {
|
|
|
9
9
|
} from '@/shared/types.js';
|
|
10
10
|
import { buildDefaultProviderCurrentActiveModel } from '@/shared/utils.js';
|
|
11
11
|
|
|
12
|
-
export const
|
|
12
|
+
export const CLAUDE_PREDEFINED_MODELS: ProviderModelsDefinition = {
|
|
13
13
|
OPTIONS: [
|
|
14
14
|
{
|
|
15
15
|
value: 'default',
|
|
16
16
|
label: 'Default (recommended)',
|
|
17
|
-
description: 'Use the
|
|
17
|
+
description: 'Use the recommended model for your Claude account and deployment.',
|
|
18
18
|
effort: {
|
|
19
19
|
default: 'high',
|
|
20
20
|
values: [
|
|
@@ -25,10 +25,25 @@ export const CLAUDE_FALLBACK_MODELS: ProviderModelsDefinition = {
|
|
|
25
25
|
],
|
|
26
26
|
},
|
|
27
27
|
},
|
|
28
|
+
{
|
|
29
|
+
value: 'best',
|
|
30
|
+
label: 'Best available',
|
|
31
|
+
description: 'Use Fable 5 when available, otherwise the latest Opus model.',
|
|
32
|
+
effort: {
|
|
33
|
+
default: 'high',
|
|
34
|
+
values: [
|
|
35
|
+
{ value: 'low' },
|
|
36
|
+
{ value: 'medium' },
|
|
37
|
+
{ value: 'high' },
|
|
38
|
+
{ value: 'xhigh' },
|
|
39
|
+
{ value: 'max' },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
28
43
|
{
|
|
29
44
|
value: 'fable',
|
|
30
|
-
label: 'Fable',
|
|
31
|
-
description: '
|
|
45
|
+
label: 'Fable 5',
|
|
46
|
+
description: 'Most capable Claude model for the hardest, longest-running tasks.',
|
|
32
47
|
effort: {
|
|
33
48
|
default: 'high',
|
|
34
49
|
values: [
|
|
@@ -41,15 +56,16 @@ export const CLAUDE_FALLBACK_MODELS: ProviderModelsDefinition = {
|
|
|
41
56
|
},
|
|
42
57
|
},
|
|
43
58
|
{
|
|
44
|
-
value:
|
|
45
|
-
label:
|
|
46
|
-
description:
|
|
59
|
+
value: 'sonnet',
|
|
60
|
+
label: 'Sonnet',
|
|
61
|
+
description: 'Latest Sonnet model for everyday coding tasks.',
|
|
47
62
|
effort: {
|
|
48
63
|
default: 'high',
|
|
49
64
|
values: [
|
|
50
65
|
{ value: 'low' },
|
|
51
66
|
{ value: 'medium' },
|
|
52
67
|
{ value: 'high' },
|
|
68
|
+
{ value: 'xhigh' },
|
|
53
69
|
{ value: 'max' },
|
|
54
70
|
],
|
|
55
71
|
},
|
|
@@ -57,13 +73,14 @@ export const CLAUDE_FALLBACK_MODELS: ProviderModelsDefinition = {
|
|
|
57
73
|
{
|
|
58
74
|
value: 'sonnet[1m]',
|
|
59
75
|
label: 'Sonnet (1M context)',
|
|
60
|
-
description: 'Sonnet
|
|
76
|
+
description: 'Latest Sonnet model with a 1M context window.',
|
|
61
77
|
effort: {
|
|
62
78
|
default: 'high',
|
|
63
79
|
values: [
|
|
64
80
|
{ value: 'low' },
|
|
65
81
|
{ value: 'medium' },
|
|
66
82
|
{ value: 'high' },
|
|
83
|
+
{ value: 'xhigh' },
|
|
67
84
|
{ value: 'max' },
|
|
68
85
|
],
|
|
69
86
|
},
|
|
@@ -71,7 +88,7 @@ export const CLAUDE_FALLBACK_MODELS: ProviderModelsDefinition = {
|
|
|
71
88
|
{
|
|
72
89
|
value: 'opus',
|
|
73
90
|
label: 'Opus',
|
|
74
|
-
description: 'Opus
|
|
91
|
+
description: 'Latest Opus model for complex reasoning and coding tasks.',
|
|
75
92
|
effort: {
|
|
76
93
|
default: 'high',
|
|
77
94
|
values: [
|
|
@@ -85,8 +102,8 @@ export const CLAUDE_FALLBACK_MODELS: ProviderModelsDefinition = {
|
|
|
85
102
|
},
|
|
86
103
|
{
|
|
87
104
|
value: 'opus[1m]',
|
|
88
|
-
label: 'Opus
|
|
89
|
-
description: 'Opus
|
|
105
|
+
label: 'Opus (1M context)',
|
|
106
|
+
description: 'Latest Opus model with a 1M context window.',
|
|
90
107
|
effort: {
|
|
91
108
|
default: 'high',
|
|
92
109
|
values: [
|
|
@@ -101,7 +118,22 @@ export const CLAUDE_FALLBACK_MODELS: ProviderModelsDefinition = {
|
|
|
101
118
|
{
|
|
102
119
|
value: 'haiku',
|
|
103
120
|
label: 'Haiku',
|
|
104
|
-
description: '
|
|
121
|
+
description: 'Fast and efficient Claude model for simple tasks.',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
value: 'opusplan',
|
|
125
|
+
label: 'Opus Plan',
|
|
126
|
+
description: 'Use Opus while planning, then switch to Sonnet for execution.',
|
|
127
|
+
effort: {
|
|
128
|
+
default: 'high',
|
|
129
|
+
values: [
|
|
130
|
+
{ value: 'low' },
|
|
131
|
+
{ value: 'medium' },
|
|
132
|
+
{ value: 'high' },
|
|
133
|
+
{ value: 'xhigh' },
|
|
134
|
+
{ value: 'max' },
|
|
135
|
+
],
|
|
136
|
+
},
|
|
105
137
|
},
|
|
106
138
|
],
|
|
107
139
|
DEFAULT: 'default',
|
|
@@ -113,7 +145,7 @@ export const findClaudeModelOption = (model: string | undefined | null): Provide
|
|
|
113
145
|
return null;
|
|
114
146
|
}
|
|
115
147
|
|
|
116
|
-
return
|
|
148
|
+
return CLAUDE_PREDEFINED_MODELS.OPTIONS.find((option) => option.value === normalizedModel) ?? null;
|
|
117
149
|
};
|
|
118
150
|
type ClaudeInitEvent = {
|
|
119
151
|
sessionId?: string;
|
|
@@ -237,7 +269,7 @@ export class ClaudeProviderModels implements IProviderModels {
|
|
|
237
269
|
// const supportedModels = await queryInstance.supportedModels();
|
|
238
270
|
// queryInstance.close();
|
|
239
271
|
// return buildClaudeModelsDefinition(supportedModels);
|
|
240
|
-
return
|
|
272
|
+
return CLAUDE_PREDEFINED_MODELS;
|
|
241
273
|
}
|
|
242
274
|
|
|
243
275
|
async getCurrentActiveModel(sessionId?: string): Promise<ProviderCurrentActiveModel> {
|
|
@@ -24,10 +24,11 @@ import {
|
|
|
24
24
|
buildClaudeUserContent,
|
|
25
25
|
normalizeImageDescriptors
|
|
26
26
|
} from '@/shared/image-attachments.js';
|
|
27
|
-
import {
|
|
27
|
+
import { CLAUDE_PREDEFINED_MODELS } from '@/modules/providers/list/claude/claude-models.provider.js';
|
|
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,12 +41,35 @@ 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
|
-
function resolveClaudeEffort(model, effort, modelsDefinition =
|
|
72
|
+
function resolveClaudeEffort(model, effort, modelsDefinition = CLAUDE_PREDEFINED_MODELS) {
|
|
49
73
|
const selectedModel = modelsDefinition?.OPTIONS?.find((option) => option.value === model) || null;
|
|
50
74
|
const allowedEfforts = selectedModel?.effort?.values
|
|
51
75
|
?.map((value) => value.value) || [];
|
|
@@ -165,7 +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
|
-
sdkOptions.env = { ...process.env };
|
|
192
|
+
sdkOptions.env = { ...process.env, CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS: String(BG_WAIT_CEILING_MS) };
|
|
169
193
|
|
|
170
194
|
// Resolve the executable eagerly on Windows because the SDK uses raw child_process.spawn,
|
|
171
195
|
// which does not reliably follow npm's shell wrappers like cross-spawn does.
|
|
@@ -209,12 +233,12 @@ function mapCliOptionsToSDK(options = {}) {
|
|
|
209
233
|
|
|
210
234
|
sdkOptions.disallowedTools = settings.disallowedTools || [];
|
|
211
235
|
|
|
212
|
-
sdkOptions.model = options.model ||
|
|
236
|
+
sdkOptions.model = options.model || CLAUDE_PREDEFINED_MODELS.DEFAULT;
|
|
213
237
|
|
|
214
238
|
const resolvedEffort = resolveClaudeEffort(
|
|
215
239
|
sdkOptions.model,
|
|
216
240
|
effort,
|
|
217
|
-
options.effortModels ||
|
|
241
|
+
options.effortModels || CLAUDE_PREDEFINED_MODELS,
|
|
218
242
|
);
|
|
219
243
|
if (resolvedEffort) {
|
|
220
244
|
sdkOptions.effort = resolvedEffort;
|
|
@@ -240,13 +264,37 @@ function mapCliOptionsToSDK(options = {}) {
|
|
|
240
264
|
* @param {string} sessionId - Session identifier
|
|
241
265
|
* @param {Object} queryInstance - SDK query instance
|
|
242
266
|
* @param {Object} writer - WebSocket writer for reconnect support
|
|
267
|
+
* @param {Function} releaseInput - Closes the held stdin stream so the CLI can exit
|
|
243
268
|
*/
|
|
244
|
-
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;
|
|
245
291
|
activeSessions.set(sessionId, {
|
|
246
292
|
instance: queryInstance,
|
|
247
|
-
startTime: Date.now(),
|
|
293
|
+
startTime: carried?.startTime || Date.now(),
|
|
248
294
|
status: 'active',
|
|
249
|
-
writer
|
|
295
|
+
writer,
|
|
296
|
+
// Re-registered mid-run once the provider session id lands; keep the closer.
|
|
297
|
+
releaseInput: releaseInput || carried?.releaseInput || null
|
|
250
298
|
});
|
|
251
299
|
}
|
|
252
300
|
|
|
@@ -363,38 +411,93 @@ function extractTokenBudget(sdkMessage) {
|
|
|
363
411
|
};
|
|
364
412
|
}
|
|
365
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
|
+
|
|
366
418
|
/**
|
|
367
|
-
*
|
|
419
|
+
* Detects tool calls that keep working after the turn's `result` arrives.
|
|
368
420
|
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
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).
|
|
373
453
|
*
|
|
374
454
|
* @param {string} command - User prompt
|
|
375
455
|
* @param {Array} images - Image descriptors ({ path, name?, mimeType? })
|
|
376
456
|
* @param {Array} files - Non-image attachment descriptors
|
|
377
457
|
* @param {string} cwd - Project working directory attachment paths resolve against
|
|
378
|
-
* @returns {Promise<
|
|
458
|
+
* @returns {Promise<Array<Object>>} SDKUserMessage records for the turn
|
|
379
459
|
*/
|
|
380
|
-
async function
|
|
460
|
+
async function buildPromptMessages(command, images, files, cwd) {
|
|
381
461
|
const promptWithFiles = appendFilesInputTag(command, files);
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
+
}
|
|
385
476
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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;
|
|
397
498
|
})();
|
|
499
|
+
|
|
500
|
+
return { stream, release };
|
|
398
501
|
}
|
|
399
502
|
|
|
400
503
|
/**
|
|
@@ -483,9 +586,47 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
483
586
|
});
|
|
484
587
|
};
|
|
485
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
|
+
|
|
486
627
|
try {
|
|
487
628
|
const resolvedModel = await context.resolveResumeModel(sessionId, options.model);
|
|
488
|
-
let effortModels =
|
|
629
|
+
let effortModels = CLAUDE_PREDEFINED_MODELS;
|
|
489
630
|
try {
|
|
490
631
|
effortModels = await context.getProviderModels();
|
|
491
632
|
} catch (error) {
|
|
@@ -504,10 +645,10 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
504
645
|
sdkOptions.mcpServers = mcpServers;
|
|
505
646
|
}
|
|
506
647
|
|
|
507
|
-
//
|
|
508
|
-
//
|
|
509
|
-
// async generator cannot be replayed once consumed.
|
|
510
|
-
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);
|
|
511
652
|
|
|
512
653
|
sdkOptions.hooks = {
|
|
513
654
|
Notification: [{
|
|
@@ -610,14 +751,11 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
610
751
|
return { behavior: 'deny', message: decision.message ?? 'User denied tool use' };
|
|
611
752
|
};
|
|
612
753
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
process.env.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT = '300000';
|
|
616
|
-
|
|
617
|
-
let queryInstance;
|
|
754
|
+
let heldPrompt = createHeldPromptStream(promptMessages);
|
|
755
|
+
releasePromptStream = heldPrompt.release;
|
|
618
756
|
try {
|
|
619
757
|
queryInstance = query({
|
|
620
|
-
prompt:
|
|
758
|
+
prompt: heldPrompt.stream,
|
|
621
759
|
options: sdkOptions
|
|
622
760
|
});
|
|
623
761
|
} catch (hookError) {
|
|
@@ -625,22 +763,19 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
625
763
|
// Keep notification behavior operational via runtime events even if hook registration fails.
|
|
626
764
|
console.warn('Failed to initialize Claude query with hooks, retrying without hooks:', hookError?.message || hookError);
|
|
627
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;
|
|
628
770
|
queryInstance = query({
|
|
629
|
-
prompt:
|
|
771
|
+
prompt: heldPrompt.stream,
|
|
630
772
|
options: sdkOptions
|
|
631
773
|
});
|
|
632
774
|
}
|
|
633
775
|
|
|
634
|
-
// Restore immediately — Query constructor already captured the value
|
|
635
|
-
if (prevStreamTimeout !== undefined) {
|
|
636
|
-
process.env.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT = prevStreamTimeout;
|
|
637
|
-
} else {
|
|
638
|
-
delete process.env.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
776
|
// Track the query instance for abort capability
|
|
642
777
|
if (sessionKey()) {
|
|
643
|
-
addSession(sessionKey(), queryInstance, ws);
|
|
778
|
+
addSession(sessionKey(), queryInstance, ws, releasePromptStream);
|
|
644
779
|
}
|
|
645
780
|
|
|
646
781
|
// Process streaming messages
|
|
@@ -650,7 +785,7 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
650
785
|
if (message.session_id && !capturedSessionId) {
|
|
651
786
|
|
|
652
787
|
capturedSessionId = message.session_id;
|
|
653
|
-
addSession(sessionKey(), queryInstance, ws);
|
|
788
|
+
addSession(sessionKey(), queryInstance, ws, releasePromptStream);
|
|
654
789
|
|
|
655
790
|
// Set session ID on writer
|
|
656
791
|
if (ws.setSessionId && typeof ws.setSessionId === 'function') {
|
|
@@ -685,36 +820,98 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
685
820
|
if (tokenBudgetData) {
|
|
686
821
|
ws.send(createNormalizedMessage({ kind: 'status', text: 'token_budget', tokenBudget: tokenBudgetData, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
|
|
687
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
|
+
}
|
|
688
868
|
}
|
|
689
869
|
|
|
690
|
-
// Clean up session on completion
|
|
691
|
-
|
|
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) {
|
|
692
874
|
removeSession(sessionKey());
|
|
693
875
|
}
|
|
694
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
|
+
|
|
695
881
|
// Send the terminal completion event — skipped for aborted runs, whose
|
|
696
|
-
// terminal `complete` (aborted: true) was already sent by abort-session
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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
|
+
});
|
|
700
897
|
}
|
|
701
|
-
notifyRunStopped({
|
|
702
|
-
userId: ws?.userId || null,
|
|
703
|
-
provider: 'claude',
|
|
704
|
-
sessionId: sessionId || capturedSessionId || null,
|
|
705
|
-
sessionName: sessionSummary,
|
|
706
|
-
stopReason: wasAborted ? 'aborted' : 'completed'
|
|
707
|
-
});
|
|
708
898
|
// Complete
|
|
709
899
|
|
|
710
900
|
} catch (error) {
|
|
711
901
|
console.error('SDK query error:', error);
|
|
712
902
|
|
|
713
|
-
// Clean up session on error
|
|
714
|
-
|
|
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) {
|
|
715
906
|
removeSession(sessionKey());
|
|
716
907
|
}
|
|
717
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
|
+
|
|
718
915
|
const wasAborted = sessionKey() ? abortedSessionIds.delete(sessionKey()) : false;
|
|
719
916
|
if (wasAborted) {
|
|
720
917
|
// The abort already produced the terminal complete; a generator throw
|
|
@@ -728,9 +925,13 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
728
925
|
? 'Claude Code is not installed. Please install it first: https://docs.anthropic.com/en/docs/claude-code'
|
|
729
926
|
: error.message;
|
|
730
927
|
|
|
731
|
-
// 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.
|
|
732
931
|
ws.send(createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
|
|
733
|
-
|
|
932
|
+
if (!turnCompleteSent) {
|
|
933
|
+
ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 1 }));
|
|
934
|
+
}
|
|
734
935
|
notifyRunFailed({
|
|
735
936
|
userId: ws?.userId || null,
|
|
736
937
|
provider: 'claude',
|
|
@@ -738,6 +939,14 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
|
|
|
738
939
|
sessionName: sessionSummary,
|
|
739
940
|
error
|
|
740
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();
|
|
741
950
|
}
|
|
742
951
|
}
|
|
743
952
|
|
|
@@ -764,6 +973,10 @@ async function abortClaudeSDKSession(sessionId) {
|
|
|
764
973
|
// Call interrupt() on the query instance
|
|
765
974
|
await session.instance.interrupt();
|
|
766
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
|
+
|
|
767
980
|
// Update session status
|
|
768
981
|
session.status = 'aborted';
|
|
769
982
|
|