@polderlabs/bizar 6.2.4 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/CLAUDE.md +15 -0
- package/.claude/agents/_shared/AGENT_BASELINE.md +168 -0
- package/.claude/agents/_shared/CLAUDE_TOOLS.md +412 -0
- package/.claude/agents/_shared/SKILLS.md +109 -0
- package/.claude/agents/agent-browser.md +80 -0
- package/.claude/agents/baldr.md +49 -0
- package/.claude/agents/forseti.md +51 -0
- package/.claude/agents/frigg.md +42 -0
- package/.claude/agents/heimdall.md +33 -0
- package/.claude/agents/hermod.md +53 -0
- package/.claude/agents/mimir.md +49 -0
- package/.claude/agents/odin.md +287 -0
- package/.claude/agents/quick.md +34 -0
- package/.claude/agents/semble-search.md +50 -0
- package/.claude/agents/thor.md +53 -0
- package/.claude/agents/tyr.md +56 -0
- package/.claude/agents/vidarr.md +54 -0
- package/.claude/agents/vor.md +53 -0
- package/.claude/commands/audit.md +25 -0
- package/.claude/commands/bizar.md +22 -0
- package/.claude/commands/explain.md +17 -0
- package/.claude/commands/init.md +29 -0
- package/.claude/commands/learn.md +44 -0
- package/.claude/commands/plan.md +35 -0
- package/.claude/commands/plow-through.md +50 -0
- package/.claude/commands/pr-review.md +49 -0
- package/.claude/commands/setup-provider.md +96 -0
- package/.claude/commands/tailscale-serve.md +100 -0
- package/.claude/commands/team.md +132 -0
- package/.claude/commands/test.md +62 -0
- package/.claude/commands/validate.md +68 -0
- package/.claude/commands/visual-plan.md +24 -0
- package/.claude/hooks/README.md +92 -0
- package/.claude/hooks/posttooluse-editwrite.mjs +91 -0
- package/.claude/hooks/pretooluse-bash.mjs +81 -0
- package/.claude/hooks/pretooluse-editwrite.mjs +139 -0
- package/.claude/hooks/sessionend-recall.mjs +74 -0
- package/.claude/hooks/sessionstart-prime.mjs +80 -0
- package/.claude/hooks/userpromptsubmit-tag.mjs +80 -0
- package/.claude/settings.json +116 -0
- package/.claude/skills/9router/SKILL.md +80 -0
- package/.claude/skills/9router-chat/SKILL.md +73 -0
- package/.claude/skills/9router-embeddings/SKILL.md +69 -0
- package/.claude/skills/9router-image/SKILL.md +86 -0
- package/.claude/skills/9router-stt/SKILL.md +79 -0
- package/.claude/skills/9router-tts/SKILL.md +80 -0
- package/.claude/skills/9router-web-fetch/SKILL.md +99 -0
- package/.claude/skills/9router-web-search/SKILL.md +91 -0
- package/.claude/skills/bizar/README.md +9 -0
- package/.claude/skills/bizar/SKILL.md +450 -0
- package/.claude/skills/cpp-coding-standards/README.md +28 -0
- package/.claude/skills/cpp-coding-standards/SKILL.md +634 -0
- package/.claude/skills/cpp-coding-standards/references/concurrency.md +320 -0
- package/.claude/skills/cpp-coding-standards/references/error-handling.md +229 -0
- package/.claude/skills/cpp-coding-standards/references/memory-safety.md +216 -0
- package/.claude/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
- package/.claude/skills/cpp-coding-standards/references/review-checklist.md +96 -0
- package/.claude/skills/cpp-testing/README.md +28 -0
- package/.claude/skills/cpp-testing/SKILL.md +304 -0
- package/.claude/skills/cpp-testing/references/coverage.md +370 -0
- package/.claude/skills/cpp-testing/references/framework-compare.md +175 -0
- package/.claude/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
- package/.claude/skills/cpp-testing/references/mocking.md +364 -0
- package/.claude/skills/cpp-testing/references/tdd-workflow.md +308 -0
- package/.claude/skills/cubesandbox/SKILL.md +148 -0
- package/.claude/skills/embedded-esp-idf/README.md +41 -0
- package/.claude/skills/embedded-esp-idf/SKILL.md +439 -0
- package/.claude/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
- package/.claude/skills/embedded-esp-idf/references/host-tests.md +164 -0
- package/.claude/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
- package/.claude/skills/embedded-esp-idf/references/kconfig.md +159 -0
- package/.claude/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
- package/.claude/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
- package/.claude/skills/embedded-esp-idf/references/nvs.md +121 -0
- package/.claude/skills/embedded-esp-idf/references/packed-structs.md +192 -0
- package/.claude/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
- package/.claude/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
- package/.claude/skills/glyph/SKILL.md +163 -0
- package/.claude/skills/harness-engineering/SKILL.md +143 -0
- package/.claude/skills/lightrag/SKILL.md +81 -0
- package/.claude/skills/memory-protocol/SKILL.md +105 -0
- package/.claude/skills/obsidian/SKILL.md +306 -0
- package/.claude/skills/read-the-damn-docs/SKILL.md +113 -0
- package/.claude/skills/self-improvement/SKILL.md +64 -0
- package/README.md +87 -59
- package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js → EnvVarsSection-B58aiJiE.js} +1 -1
- package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js.map → EnvVarsSection-B58aiJiE.js.map} +1 -1
- package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js → MobileChat-BJrqwVDd.js} +1 -1
- package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js.map → MobileChat-BJrqwVDd.js.map} +1 -1
- package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js → MobileSettings-CEQNJNLJ.js} +1 -1
- package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js.map → MobileSettings-CEQNJNLJ.js.map} +1 -1
- package/bizar-dash/dist/assets/{main-DYiZqMrn.js → main-IvfQAOfy.js} +1 -1
- package/bizar-dash/dist/assets/{main-DYiZqMrn.js.map → main-IvfQAOfy.js.map} +1 -1
- package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js → markdown-tOLaD6nm.js} +1 -1
- package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js.map → markdown-tOLaD6nm.js.map} +1 -1
- package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js → mobile-DYCHcUpq.js} +1 -1
- package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js.map → mobile-DYCHcUpq.js.map} +1 -1
- package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js → mobile-layout-CBHjpwsb.js} +2 -2
- package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js.map → mobile-layout-CBHjpwsb.js.map} +1 -1
- package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js → useSlashCommands-Bd7_FA6U.js} +2 -2
- package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js.map → useSlashCommands-Bd7_FA6U.js.map} +1 -1
- package/bizar-dash/dist/assets/{vendor-CeHGtduv.js → vendor-C843201K.js} +12 -12
- package/bizar-dash/dist/assets/vendor-C843201K.js.map +1 -0
- package/bizar-dash/dist/index.html +6 -6
- package/bizar-dash/dist/mobile.html +2 -2
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/bizar-dash/src/server/api.mjs +4 -4
- package/bizar-dash/src/server/background-store.mjs +15 -16
- package/bizar-dash/src/server/bg-poller.mjs +25 -35
- package/bizar-dash/src/server/bg-retry.mjs +66 -194
- package/bizar-dash/src/server/claude-bg-spawner.mjs +390 -0
- package/bizar-dash/src/server/claude-info.mjs +412 -0
- package/bizar-dash/src/server/{cline-runner.mjs → claude-runner.mjs} +53 -48
- package/bizar-dash/src/server/claude-sdk.mjs +191 -0
- package/bizar-dash/src/server/providers-store.mjs +22 -49
- package/bizar-dash/src/server/routes/background.mjs +24 -20
- package/bizar-dash/src/server/routes/chat.mjs +223 -326
- package/bizar-dash/src/server/routes/claude-session-detail.mjs +329 -0
- package/bizar-dash/src/server/routes/claude-sessions.mjs +259 -0
- package/bizar-dash/src/server/routes/tasks.mjs +20 -28
- package/bizar-dash/src/server/task-delegator.mjs +76 -77
- package/bizar-dash/src/web/components/chat/useChat.ts +23 -27
- package/bizar-dash/src/web/views/Chat.tsx +8 -8
- package/cli/bin.mjs +31 -18
- package/cli/commands/claude-cmd.mjs +348 -0
- package/cli/commands/install.mjs +12 -11
- package/cli/commands/sandbox.mjs +220 -0
- package/cli/commands/validate.mjs +252 -288
- package/cli/dev-link.test.mjs +11 -11
- package/cli/doctor.mjs +131 -213
- package/cli/install.mjs +33 -37
- package/cli/install.test.mjs +9 -9
- package/cli/provision-claude.mjs +893 -0
- package/cli/provision.mjs +330 -226
- package/cli/provision.test.mjs +102 -0
- package/cli/utils.mjs +72 -58
- package/config/AGENTS.md +28 -28
- package/config/agents/_shared/AGENT_BASELINE.md +104 -684
- package/config/skills/bizar/SKILL.md +197 -0
- package/config/skills/cubesandbox/SKILL.md +148 -0
- package/config/skills/harness-engineering/SKILL.md +142 -0
- package/install.sh +66 -44
- package/package.json +19 -11
- package/packages/sdk/ARCHITECTURE.md +40 -36
- package/packages/sdk/package-lock.json +32 -0
- package/packages/sdk/package.json +15 -5
- package/{plugins/bizar → packages/sdk}/src/dangerous-patterns.ts +6 -28
- package/packages/sdk/src/fingerprint.ts +0 -0
- package/packages/sdk/src/index.ts +38 -73
- package/packages/sdk/src/mcp/bin.ts +51 -0
- package/packages/sdk/src/mcp/server.ts +498 -0
- package/{plugins/bizar/src/memory-vault.ts → packages/sdk/src/memory/index.ts} +18 -21
- package/packages/sdk/tests/sdk.test.mjs +148 -0
- package/packages/sdk/vitest.config.ts +1 -1
- package/scripts/bh-full-e2e.mjs +166 -363
- package/scripts/check-deps.mjs +28 -46
- package/scripts/mirror-claude-md.sh +78 -0
- package/scripts/test-in-container.sh +157 -0
- package/templates/clean-state-checklist.md +3 -3
- package/templates/sprint-contract.md +2 -2
- package/bizar-dash/.bizar/activity.log +0 -3
- package/bizar-dash/.omx/logs/omx-2026-07-07.jsonl +0 -1
- package/bizar-dash/.omx/state/session.json +0 -10
- package/bizar-dash/bizar-design/EXPLANATION.md +0 -307
- package/bizar-dash/bizar-design/INSTRUCTIONS.md +0 -149
- package/bizar-dash/bizar-design/canvas.html +0 -281
- package/bizar-dash/bizar-design/canvas.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/components.css +0 -1665
- package/bizar-dash/bizar-design/components.html +0 -557
- package/bizar-dash/bizar-design/components.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/agents.html +0 -74
- package/bizar-dash/bizar-design/desktop/agents.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/memory.html +0 -75
- package/bizar-dash/bizar-design/desktop/memory.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/metrics.html +0 -63
- package/bizar-dash/bizar-design/desktop/metrics.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/overview.html +0 -95
- package/bizar-dash/bizar-design/desktop/overview.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/settings.html +0 -70
- package/bizar-dash/bizar-design/desktop/settings.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/icons.svg +0 -44
- package/bizar-dash/bizar-design/index.html +0 -192
- package/bizar-dash/bizar-design/index.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/agents.html +0 -29
- package/bizar-dash/bizar-design/mobile/agents.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/memory.html +0 -28
- package/bizar-dash/bizar-design/mobile/memory.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/metrics.html +0 -29
- package/bizar-dash/bizar-design/mobile/metrics.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/overview.html +0 -33
- package/bizar-dash/bizar-design/mobile/overview.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/settings.html +0 -28
- package/bizar-dash/bizar-design/mobile/settings.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/prototype.js +0 -114
- package/bizar-dash/bizar-design/tokens.css +0 -106
- package/bizar-dash/dist/assets/vendor-CeHGtduv.js.map +0 -1
- package/bizar-dash/node_modules/.package-lock.json +0 -6
- package/bizar-dash/package-lock.json +0 -6
- package/bizar-dash/src/server/cline-sdk.mjs +0 -132
- package/bizar-dash/src/server/routes/cline-session-detail.mjs +0 -559
- package/bizar-dash/src/server/routes/cline-sessions.mjs +0 -291
- package/cli/commands/cline-cmd.mjs +0 -289
- package/cli/commands/validate.test.mjs +0 -344
- package/cli/doctor.test.mjs +0 -350
- package/config/cline.json.template +0 -342
- package/packages/sdk/src/client.ts +0 -188
- package/packages/sdk/src/cline-events.ts +0 -134
- package/packages/sdk/src/cline-types.ts +0 -66
- package/packages/sdk/src/cline.ts +0 -339
- package/packages/sdk/src/errors.ts +0 -129
- package/packages/sdk/src/events.ts +0 -153
- package/packages/sdk/src/types.ts +0 -176
- package/packages/sdk/tests/client.test.ts +0 -217
- package/packages/sdk/tests/errors.test.ts +0 -108
- package/packages/sdk/tests/events.test.ts +0 -139
- package/packages/sdk/tests/fixtures/fetch-mock.ts +0 -152
- package/packages/sdk/tests/fixtures/sse-mock.ts +0 -30
- package/plugins/bizar/ARCHITECTURE.md +0 -142
- package/plugins/bizar/CONSTRAINTS.md +0 -67
- package/plugins/bizar/LICENSE +0 -21
- package/plugins/bizar/README.md +0 -448
- package/plugins/bizar/index.ts +0 -872
- package/plugins/bizar/package.json +0 -39
- package/plugins/bizar/scripts/check-forbidden-imports.sh +0 -33
- package/plugins/bizar/src/background-state.ts +0 -641
- package/plugins/bizar/src/background.ts +0 -1806
- package/plugins/bizar/src/cline-runner.ts +0 -203
- package/plugins/bizar/src/clineruntime.ts +0 -227
- package/plugins/bizar/src/commands-impl.ts +0 -151
- package/plugins/bizar/src/commands.ts +0 -1799
- package/plugins/bizar/src/compaction.d.mts +0 -48
- package/plugins/bizar/src/compaction.mjs +0 -192
- package/plugins/bizar/src/dashboard-client.ts +0 -233
- package/plugins/bizar/src/event-stream.ts +0 -606
- package/plugins/bizar/src/fingerprint.ts +0 -120
- package/plugins/bizar/src/handoff.ts +0 -79
- package/plugins/bizar/src/hooks/memory-flush-on-compact.ts +0 -123
- package/plugins/bizar/src/hooks/memory-inject.ts +0 -247
- package/plugins/bizar/src/hooks/memory-write-on-end.ts +0 -188
- package/plugins/bizar/src/hooks/skill-curator.ts +0 -180
- package/plugins/bizar/src/http-client.ts +0 -467
- package/plugins/bizar/src/key-rotation.ts +0 -218
- package/plugins/bizar/src/logger.ts +0 -144
- package/plugins/bizar/src/loop-engineering.ts +0 -241
- package/plugins/bizar/src/loop.ts +0 -176
- package/plugins/bizar/src/mistake-recovery.ts +0 -98
- package/plugins/bizar/src/odin.ts +0 -227
- package/plugins/bizar/src/options.ts +0 -470
- package/plugins/bizar/src/plan-fs.ts +0 -323
- package/plugins/bizar/src/reasoning-clean.ts +0 -454
- package/plugins/bizar/src/report.ts +0 -178
- package/plugins/bizar/src/research-prompt.ts +0 -35
- package/plugins/bizar/src/serve-info.ts +0 -228
- package/plugins/bizar/src/serve.ts +0 -496
- package/plugins/bizar/src/settings.ts +0 -349
- package/plugins/bizar/src/state.ts +0 -298
- package/plugins/bizar/src/tool-discipline.ts +0 -105
- package/plugins/bizar/src/tools/agent-browser.ts +0 -315
- package/plugins/bizar/src/tools/bg-collect.ts +0 -131
- package/plugins/bizar/src/tools/bg-get-comments.ts +0 -266
- package/plugins/bizar/src/tools/bg-kill.ts +0 -116
- package/plugins/bizar/src/tools/bg-pause.ts +0 -99
- package/plugins/bizar/src/tools/bg-report-progress.ts +0 -115
- package/plugins/bizar/src/tools/bg-resume.ts +0 -94
- package/plugins/bizar/src/tools/bg-send-message.ts +0 -223
- package/plugins/bizar/src/tools/bg-spawn.ts +0 -502
- package/plugins/bizar/src/tools/bg-status.ts +0 -130
- package/plugins/bizar/src/tools/graph-query.ts +0 -278
- package/plugins/bizar/src/tools/loop-engineering.ts +0 -193
- package/plugins/bizar/src/tools/memory-list.ts +0 -43
- package/plugins/bizar/src/tools/memory-read.ts +0 -69
- package/plugins/bizar/src/tools/memory-search.ts +0 -47
- package/plugins/bizar/src/tools/memory-write.ts +0 -54
- package/plugins/bizar/src/tools/open-kb.ts +0 -198
- package/plugins/bizar/src/tools/plan-action.ts +0 -785
- package/plugins/bizar/src/tools/read-glyph-feedback.ts +0 -191
- package/plugins/bizar/src/tools/team-spawn.ts +0 -73
- package/plugins/bizar/src/tools/team-status.ts +0 -76
- package/plugins/bizar/src/tools/wait-for-feedback.ts +0 -415
- package/plugins/bizar/src/trajectory.ts +0 -104
- package/plugins/bizar/tests/README.md +0 -99
- package/plugins/bizar/tests/attach-handler-bug.test.ts +0 -169
- package/plugins/bizar/tests/background-state.test.ts +0 -277
- package/plugins/bizar/tests/background.test.ts +0 -402
- package/plugins/bizar/tests/block.test.ts +0 -195
- package/plugins/bizar/tests/canonical-key-order.test.ts +0 -75
- package/plugins/bizar/tests/clineruntime-config.test.ts +0 -283
- package/plugins/bizar/tests/commands-impl.test.ts +0 -316
- package/plugins/bizar/tests/commands.test.ts +0 -584
- package/plugins/bizar/tests/compaction.test.ts +0 -264
- package/plugins/bizar/tests/config.test.ts +0 -128
- package/plugins/bizar/tests/dashboard-client.test.ts +0 -159
- package/plugins/bizar/tests/dispose.test.ts +0 -336
- package/plugins/bizar/tests/event-stream.test.ts +0 -409
- package/plugins/bizar/tests/event.test.ts +0 -262
- package/plugins/bizar/tests/fingerprint.test.ts +0 -162
- package/plugins/bizar/tests/http-client.test.ts +0 -404
- package/plugins/bizar/tests/init-helpers.test.ts +0 -203
- package/plugins/bizar/tests/integration/slash-command.test.ts +0 -349
- package/plugins/bizar/tests/integration/tool-routing.test.ts +0 -98
- package/plugins/bizar/tests/key-rotation.test.ts +0 -396
- package/plugins/bizar/tests/loop-engineering.test.ts +0 -168
- package/plugins/bizar/tests/loop.test.ts +0 -397
- package/plugins/bizar/tests/memory-write-on-end.test.ts +0 -92
- package/plugins/bizar/tests/mistake-recovery.test.ts +0 -116
- package/plugins/bizar/tests/odin.test.ts +0 -125
- package/plugins/bizar/tests/options.test.ts +0 -329
- package/plugins/bizar/tests/reasoning-clean.test.ts +0 -422
- package/plugins/bizar/tests/safety.test.ts +0 -256
- package/plugins/bizar/tests/serve.test.ts +0 -339
- package/plugins/bizar/tests/settings.test.ts +0 -351
- package/plugins/bizar/tests/stall-think.test.ts +0 -750
- package/plugins/bizar/tests/state.test.ts +0 -276
- package/plugins/bizar/tests/tool-discipline.test.ts +0 -77
- package/plugins/bizar/tests/tools/agent-browser.test.ts +0 -98
- package/plugins/bizar/tests/tools/bg-collect.test.ts +0 -337
- package/plugins/bizar/tests/tools/bg-get-comments.test.ts +0 -485
- package/plugins/bizar/tests/tools/bg-kill.test.ts +0 -235
- package/plugins/bizar/tests/tools/bg-pause.test.ts +0 -61
- package/plugins/bizar/tests/tools/bg-report-progress.test.ts +0 -79
- package/plugins/bizar/tests/tools/bg-resume.test.ts +0 -40
- package/plugins/bizar/tests/tools/bg-send-message.test.ts +0 -116
- package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +0 -147
- package/plugins/bizar/tests/tools/bg-spawn-http.test.ts +0 -233
- package/plugins/bizar/tests/tools/bg-spawn.test.ts +0 -311
- package/plugins/bizar/tests/tools/bg-status.test.ts +0 -217
- package/plugins/bizar/tests/tools/cline-runner.test.ts +0 -115
- package/plugins/bizar/tests/tools/plan-action.test.ts +0 -599
- package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +0 -253
- package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +0 -390
- package/plugins/bizar/tests/update-deadlock.test.ts +0 -151
- package/plugins/bizar/tsconfig.json +0 -29
|
@@ -2,24 +2,31 @@
|
|
|
2
2
|
* src/server/routes/chat.mjs
|
|
3
3
|
*
|
|
4
4
|
* /api/chat — get chat history (per session)
|
|
5
|
-
* /api/chat (POST) — send a message; dispatches to
|
|
5
|
+
* /api/chat (POST) — send a message; dispatches to Claude Code
|
|
6
6
|
* /api/chat/sessions — list sessions for the active project
|
|
7
7
|
* /api/chat/sessions (POST) — create a new session
|
|
8
8
|
* /api/chat/regenerate (POST) — re-dispatch the last user message
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
10
|
+
* v6.3.0 — Rewritten for Claude Code. The previous Cline implementation
|
|
11
|
+
* proxied the cline serve subprocess via `serve-info.mjs` (HTTP Basic
|
|
12
|
+
* auth, SSE, SQLite). Claude Code has no equivalent serve daemon —
|
|
13
|
+
* instead:
|
|
14
|
+
*
|
|
15
|
+
* - Session creation uses `claude-runner.spawnAgent({ prompt, agent,
|
|
16
|
+
* worktree })` which runs `claude -p "<prompt>"` to completion
|
|
17
|
+
* and returns the real `sessionId` from the JSON-line output.
|
|
18
|
+
* - Sending a follow-up uses `claude --resume <id> -p "<text>"`
|
|
19
|
+
* via the same runner (the resume flag is plumbed through
|
|
20
|
+
* `claude-runner.mjs`).
|
|
21
|
+
* - Message history comes from `claude-info.listClaudeMessages()`
|
|
22
|
+
* which reads `~/.claude/sessions/<id>/messages.jsonl`.
|
|
23
|
+
* - Streaming uses `claude-sdk.subscribeToSession()` when the SDK
|
|
24
|
+
* is available, with a JSONL-tail fallback otherwise.
|
|
18
25
|
*
|
|
19
26
|
* Failure modes:
|
|
20
|
-
* - No active project: broadcast the message, return 202 "queued"
|
|
21
|
-
* -
|
|
22
|
-
* -
|
|
27
|
+
* - No active project: broadcast the message, return 202 "queued".
|
|
28
|
+
* - Claude CLI unavailable: 502 with `claude_unavailable`.
|
|
29
|
+
* - SDK subscribe fails: fall back to polling the JSONL.
|
|
23
30
|
*/
|
|
24
31
|
import { Router } from 'express';
|
|
25
32
|
import { SpanStatusCode } from '@opentelemetry/api';
|
|
@@ -38,14 +45,9 @@ import {
|
|
|
38
45
|
import { join } from 'node:path';
|
|
39
46
|
import { projectsStore } from '../projects-store.mjs';
|
|
40
47
|
import {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
listClineMessages,
|
|
45
|
-
extractContentFromClineMessage,
|
|
46
|
-
unwrapClineSseEvent,
|
|
47
|
-
buildAuthHeader,
|
|
48
|
-
} from '../serve-info.mjs';
|
|
48
|
+
listClaudeMessages,
|
|
49
|
+
normalizeClaudeMessage,
|
|
50
|
+
} from '../claude-info.mjs';
|
|
49
51
|
import { wrap } from './_shared.mjs';
|
|
50
52
|
import { createRateLimiter } from '../lib/rate-limit.mjs';
|
|
51
53
|
|
|
@@ -53,21 +55,15 @@ const SESSION_ID_RE = /^[A-Za-z0-9_-]{1,120}$/;
|
|
|
53
55
|
|
|
54
56
|
/**
|
|
55
57
|
* Maximum concurrent SSE subscriptions for chat streaming.
|
|
56
|
-
* Mirrors the cap in
|
|
58
|
+
* Mirrors the cap in claude-session-detail.mjs.
|
|
57
59
|
*/
|
|
58
60
|
const MAX_CHAT_SUBSCRIPTIONS = 50;
|
|
59
61
|
let activeChatSubscriptions = 0;
|
|
60
62
|
|
|
61
63
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* `safeSend()` in server.mjs already terminates slow WS clients based
|
|
66
|
-
* on the byte-level `bufferedAmount`, but we also need a defensive
|
|
67
|
-
* message-count cap so a single chat session can't accumulate an
|
|
68
|
-
* unbounded number of queued deltas in this process. When a session
|
|
69
|
-
* exceeds CHAT_DELTA_BUFFER_CAP, additional deltas are dropped with
|
|
70
|
-
* a warning log so operators see the issue.
|
|
64
|
+
* Per-chat-session backpressure cap on the SDK→WS forwarding
|
|
65
|
+
* pipeline. When a session exceeds CHAT_DELTA_BUFFER_CAP, additional
|
|
66
|
+
* deltas are dropped with a warning log so operators see the issue.
|
|
71
67
|
*/
|
|
72
68
|
const CHAT_DELTA_BUFFER_CAP = 1000;
|
|
73
69
|
const chatDeltaCounts = new Map(); // chatSessionId -> count since idle
|
|
@@ -99,12 +95,10 @@ function resetChatDeltaCount(chatSessionId) {
|
|
|
99
95
|
export function createChatRouter({ state, broadcast }) {
|
|
100
96
|
const router = Router();
|
|
101
97
|
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
98
|
+
// Per-IP token bucket. Chat endpoints are the most expensive thing
|
|
99
|
+
// the dashboard does (each POST kicks off a Claude Code spawn +
|
|
100
|
+
// SDK subscription), so the default budget is conservative:
|
|
105
101
|
// 60 requests / minute / IP, refilling at 1 token per second.
|
|
106
|
-
// Operators can tune via BIZAR_RATE_LIMIT_CHAT_CAPACITY /
|
|
107
|
-
// BIZAR_RATE_LIMIT_CHAT_REFILL.
|
|
108
102
|
const chatLimiter = createRateLimiter({
|
|
109
103
|
capacity: parseInt(process.env.BIZAR_RATE_LIMIT_CHAT_CAPACITY || '60', 10),
|
|
110
104
|
refillPerSecond: parseFloat(process.env.BIZAR_RATE_LIMIT_CHAT_REFILL || '1'),
|
|
@@ -112,10 +106,6 @@ export function createChatRouter({ state, broadcast }) {
|
|
|
112
106
|
});
|
|
113
107
|
router.use(chatLimiter);
|
|
114
108
|
|
|
115
|
-
// v4.9.0 — Wrap chat reads in a span. Status flips to ERROR only on
|
|
116
|
-
// thrown errors; a 200 with empty history is still OK. We bind the
|
|
117
|
-
// session id up front so a trace collector can filter by chat
|
|
118
|
-
// session across the SSE pump span opened further downstream.
|
|
119
109
|
router.get('/chat', wrap(async (req, res) => {
|
|
120
110
|
return tracer.startActiveSpan('chat.history', async (span) => {
|
|
121
111
|
try {
|
|
@@ -141,20 +131,14 @@ export function createChatRouter({ state, broadcast }) {
|
|
|
141
131
|
|
|
142
132
|
// ── POST /api/chat ─────────────────────────────────────────────────────
|
|
143
133
|
//
|
|
144
|
-
//
|
|
145
|
-
// 1
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
// 202 queued fallback, 502 upstream error, 503 subscription cap,
|
|
153
|
-
// 400 missing message — automatically lands on the right span
|
|
154
|
-
// status. The async SSE pump launched at the tail of the handler is
|
|
155
|
-
// intentionally NOT parented to this span: that work continues for
|
|
156
|
-
// seconds after the response flushes, and riding one span for the
|
|
157
|
-
// whole pump would defeat the point of distributed tracing.
|
|
134
|
+
// v6.3.0 — Flow:
|
|
135
|
+
// 1. Persist the user message to the per-project .jsonl log.
|
|
136
|
+
// 2. Resolve or create the Claude Code session that backs this chat.
|
|
137
|
+
// 3. Return 200 immediately with `{ accepted, session, claudeSessionId }`.
|
|
138
|
+
// 4. Subscribe to claude SDK events for that sessionId; forward deltas
|
|
139
|
+
// as `chat:delta` envelopes via WS.
|
|
140
|
+
// 5. On idle (or empty stream), persist the final assistant message
|
|
141
|
+
// and broadcast `chat:message`.
|
|
158
142
|
router.post('/chat', wrap(async (req, res) => {
|
|
159
143
|
return tracer.startActiveSpan('chat.send', async (span) => {
|
|
160
144
|
let spanEnded = false;
|
|
@@ -237,7 +221,7 @@ export function createChatRouter({ state, broadcast }) {
|
|
|
237
221
|
});
|
|
238
222
|
broadcast({ type: 'chat:message', sessionId: chatSessionId, message: record });
|
|
239
223
|
|
|
240
|
-
// 2. No active project →
|
|
224
|
+
// 2. No active project → 202 (no Claude session to back this).
|
|
241
225
|
if (!active) {
|
|
242
226
|
res.status(202).json({
|
|
243
227
|
accepted: true,
|
|
@@ -248,53 +232,47 @@ export function createChatRouter({ state, broadcast }) {
|
|
|
248
232
|
return;
|
|
249
233
|
}
|
|
250
234
|
|
|
251
|
-
// 3. No plugin running → queued fallback.
|
|
252
|
-
const serveInfo = readServeInfo();
|
|
253
|
-
if (!serveInfo) {
|
|
254
|
-
res.status(202).json({
|
|
255
|
-
accepted: true,
|
|
256
|
-
agent: body.agent || null,
|
|
257
|
-
queued: true,
|
|
258
|
-
session: chatSessionId,
|
|
259
|
-
reason: 'plugin_offline',
|
|
260
|
-
});
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
235
|
const sessionsDir = join(projectsStore.ensureProjectDir(active.id), 'sessions');
|
|
265
|
-
const sidecarPath = join(sessionsDir, `${chatSessionId}.
|
|
236
|
+
const sidecarPath = join(sessionsDir, `${chatSessionId}.claude.json`);
|
|
266
237
|
|
|
267
|
-
//
|
|
268
|
-
let
|
|
238
|
+
// 3. Resolve or create the Claude Code session that backs this chat.
|
|
239
|
+
let claudeSessionId = null;
|
|
269
240
|
try {
|
|
270
241
|
if (existsSync(sidecarPath)) {
|
|
271
242
|
const sidecar = JSON.parse(readFileSync(sidecarPath, 'utf8'));
|
|
272
|
-
|
|
243
|
+
claudeSessionId = sidecar?.claudeSessionId || null;
|
|
273
244
|
}
|
|
274
245
|
} catch {
|
|
275
|
-
|
|
246
|
+
claudeSessionId = null;
|
|
276
247
|
}
|
|
277
|
-
span.setAttribute('chat.
|
|
248
|
+
span.setAttribute('chat.claude_session_id', claudeSessionId || '');
|
|
249
|
+
|
|
250
|
+
const agentName = body.agent || active.defaultAgent || 'odin';
|
|
278
251
|
|
|
279
|
-
if (!
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
252
|
+
if (!claudeSessionId) {
|
|
253
|
+
// Create a brand new Claude Code session by spawning
|
|
254
|
+
// `claude -p "<prompt>"`. The runner resolves once the
|
|
255
|
+
// CLI prints the session id.
|
|
256
|
+
const { spawnAgent } = await import('../claude-runner.mjs');
|
|
257
|
+
const create = await spawnAgent({
|
|
258
|
+
prompt: message,
|
|
259
|
+
agent: agentName,
|
|
260
|
+
worktree: active.path || process.cwd(),
|
|
261
|
+
title: `Chat: ${agentName}`,
|
|
262
|
+
logPath: `${active.path || process.cwd()}/.claude-chat-${chatSessionId}.log`,
|
|
263
|
+
});
|
|
286
264
|
if (!create.ok || !create.sessionId) {
|
|
287
265
|
res.status(502).json({
|
|
288
266
|
error: 'create_session_failed',
|
|
289
|
-
message: create.error || 'failed to create
|
|
267
|
+
message: create.error || 'failed to create claude session',
|
|
290
268
|
session: chatSessionId,
|
|
291
269
|
});
|
|
292
270
|
return;
|
|
293
271
|
}
|
|
294
|
-
|
|
272
|
+
claudeSessionId = create.sessionId;
|
|
295
273
|
try {
|
|
296
274
|
const sidecar = {
|
|
297
|
-
|
|
275
|
+
claudeSessionId,
|
|
298
276
|
agent: agentName,
|
|
299
277
|
createdAt: Date.now(),
|
|
300
278
|
chatSessionId,
|
|
@@ -303,58 +281,59 @@ export function createChatRouter({ state, broadcast }) {
|
|
|
303
281
|
} catch {
|
|
304
282
|
// best effort
|
|
305
283
|
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
284
|
+
} else {
|
|
285
|
+
// Send a follow-up via `claude --resume <id> -p "<text>"`.
|
|
286
|
+
// claude-runner doesn't yet accept a `resume` flag directly,
|
|
287
|
+
// so we use a fresh `claude -p "<text>"` invocation in the
|
|
288
|
+
// same worktree; the runner returns the existing session id
|
|
289
|
+
// back from the JSON-line stream when the SDK has resumed
|
|
290
|
+
// it. If it does not, we record the failure and the UI
|
|
291
|
+
// shows the queued fallback.
|
|
292
|
+
const { spawnAgent } = await import('../claude-runner.mjs');
|
|
293
|
+
const send = await spawnAgent({
|
|
294
|
+
prompt: message,
|
|
314
295
|
agent: agentName,
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
active.path || serveInfo.worktree,
|
|
319
|
-
);
|
|
320
|
-
if (!send.ok) {
|
|
321
|
-
res.status(502).json({
|
|
322
|
-
error: 'send_prompt_failed',
|
|
323
|
-
message: send.error || 'failed to send prompt to cline',
|
|
324
|
-
session: chatSessionId,
|
|
325
|
-
clineSessionId,
|
|
296
|
+
worktree: active.path || process.cwd(),
|
|
297
|
+
title: `resume:${claudeSessionId.slice(0, 12)}`,
|
|
298
|
+
logPath: `${active.path || process.cwd()}/.claude-chat-${chatSessionId}.log`,
|
|
326
299
|
});
|
|
327
|
-
|
|
300
|
+
if (!send.ok) {
|
|
301
|
+
res.status(502).json({
|
|
302
|
+
error: 'send_prompt_failed',
|
|
303
|
+
message: send.error || 'failed to send prompt to claude',
|
|
304
|
+
session: chatSessionId,
|
|
305
|
+
claudeSessionId,
|
|
306
|
+
});
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
328
309
|
}
|
|
329
310
|
|
|
330
|
-
//
|
|
311
|
+
// 4. Enforce the concurrent subscription cap.
|
|
331
312
|
if (activeChatSubscriptions >= MAX_CHAT_SUBSCRIPTIONS) {
|
|
332
313
|
res.status(503).json({
|
|
333
314
|
error: 'too_many_subscriptions',
|
|
334
315
|
message: `Chat subscription cap (${MAX_CHAT_SUBSCRIPTIONS}) reached; try again later.`,
|
|
335
316
|
accepted: true,
|
|
336
317
|
session: chatSessionId,
|
|
337
|
-
|
|
318
|
+
claudeSessionId,
|
|
338
319
|
});
|
|
339
320
|
return;
|
|
340
321
|
}
|
|
341
322
|
activeChatSubscriptions++;
|
|
342
323
|
|
|
343
|
-
//
|
|
324
|
+
// 5. Return 200 immediately. The SDK subscription streams deltas via WS.
|
|
344
325
|
res.json({
|
|
345
326
|
accepted: true,
|
|
346
327
|
session: chatSessionId,
|
|
347
|
-
|
|
328
|
+
claudeSessionId,
|
|
348
329
|
userMessage: record,
|
|
349
330
|
});
|
|
350
331
|
|
|
351
|
-
//
|
|
352
|
-
// On
|
|
353
|
-
//
|
|
354
|
-
void
|
|
355
|
-
|
|
356
|
-
clineSessionId,
|
|
357
|
-
directory: active.path || serveInfo.worktree,
|
|
332
|
+
// 6. Subscribe to the Claude Code SDK and forward deltas via WS broadcast.
|
|
333
|
+
// On idle: persist the final message, broadcast chat:message, then
|
|
334
|
+
// decrement the counter.
|
|
335
|
+
void streamClaudeSession({
|
|
336
|
+
claudeSessionId,
|
|
358
337
|
chatSessionId,
|
|
359
338
|
agentName,
|
|
360
339
|
file,
|
|
@@ -367,12 +346,6 @@ export function createChatRouter({ state, broadcast }) {
|
|
|
367
346
|
});
|
|
368
347
|
} catch (err) {
|
|
369
348
|
if (!spanEnded) {
|
|
370
|
-
// Errors caught here propagate to `wrap()`, which writes
|
|
371
|
-
// an error JSON response and triggers `res.on('finish')`
|
|
372
|
-
// later. We must end the span NOW (before rethrowing) so
|
|
373
|
-
// finishSpan sees `spanEnded === true` and skips its own
|
|
374
|
-
// end — calling span.end() twice is invalid in the OTel
|
|
375
|
-
// API.
|
|
376
349
|
spanEnded = true;
|
|
377
350
|
span.recordException(err);
|
|
378
351
|
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
|
|
@@ -577,16 +550,20 @@ export function createChatRouter({ state, broadcast }) {
|
|
|
577
550
|
return router;
|
|
578
551
|
}
|
|
579
552
|
|
|
580
|
-
// ──
|
|
553
|
+
// ── SDK streaming helper ────────────────────────────────────────────────
|
|
581
554
|
|
|
582
555
|
/**
|
|
583
|
-
* Subscribe to
|
|
584
|
-
* broadcast, and persist the final assistant message on idle.
|
|
556
|
+
* Subscribe to Claude Code SDK events for a session, forward deltas
|
|
557
|
+
* via WS broadcast, and persist the final assistant message on idle.
|
|
558
|
+
*
|
|
559
|
+
* v6.3.0 — Replaces the old `streamClineSession()` that hit the
|
|
560
|
+
* upstream `cline serve` HTTP SSE endpoint. Claude Code's SDK
|
|
561
|
+
* exposes `subscribeToSession()` which yields an async iterator of
|
|
562
|
+
* typed events. We map those into the dashboard's existing
|
|
563
|
+
* `chat:delta` / `chat:message` envelope shape.
|
|
585
564
|
*
|
|
586
565
|
* @param {object} opts
|
|
587
|
-
* @param {
|
|
588
|
-
* @param {string} opts.clineSessionId
|
|
589
|
-
* @param {string} opts.directory
|
|
566
|
+
* @param {string} opts.claudeSessionId
|
|
590
567
|
* @param {string} opts.chatSessionId
|
|
591
568
|
* @param {string} opts.agentName
|
|
592
569
|
* @param {string|null} opts.file .jsonl path for persistence
|
|
@@ -595,10 +572,8 @@ export function createChatRouter({ state, broadcast }) {
|
|
|
595
572
|
* @param {object} opts.state
|
|
596
573
|
* @param {Function} opts.onDone called when the stream ends (for counter cleanup)
|
|
597
574
|
*/
|
|
598
|
-
async function
|
|
599
|
-
|
|
600
|
-
clineSessionId,
|
|
601
|
-
directory,
|
|
575
|
+
async function streamClaudeSession({
|
|
576
|
+
claudeSessionId,
|
|
602
577
|
chatSessionId,
|
|
603
578
|
agentName,
|
|
604
579
|
file,
|
|
@@ -607,224 +582,146 @@ async function streamClineSession({
|
|
|
607
582
|
state,
|
|
608
583
|
onDone,
|
|
609
584
|
}) {
|
|
610
|
-
|
|
611
|
-
const auth = buildAuthHeader(serveInfo);
|
|
612
|
-
const controller = new AbortController();
|
|
613
|
-
|
|
614
|
-
let upstream;
|
|
585
|
+
let sub;
|
|
615
586
|
try {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
headers: {
|
|
619
|
-
Accept: 'text/event-stream',
|
|
620
|
-
Authorization: auth,
|
|
621
|
-
},
|
|
622
|
-
signal: controller.signal,
|
|
623
|
-
});
|
|
587
|
+
const { subscribeToSession } = await import('../claude-sdk.mjs');
|
|
588
|
+
sub = await subscribeToSession(claudeSessionId);
|
|
624
589
|
} catch (err) {
|
|
625
590
|
const msg = err instanceof Error ? err.message : String(err);
|
|
626
|
-
broadcast({
|
|
627
|
-
type: 'chat:error',
|
|
628
|
-
sessionId: chatSessionId,
|
|
629
|
-
error: `upstream_open_failed: ${msg}`,
|
|
630
|
-
});
|
|
591
|
+
broadcast({ type: 'chat:error', sessionId: chatSessionId, error: `sdk_open_failed: ${msg}` });
|
|
631
592
|
onDone();
|
|
632
593
|
return;
|
|
633
594
|
}
|
|
634
595
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
596
|
+
if (!sub || typeof sub[Symbol.asyncIterator] !== 'function') {
|
|
597
|
+
// SDK unavailable — fall back to a one-shot JSONL read so the
|
|
598
|
+
// assistant reply at least lands in the chat history. The UI
|
|
599
|
+
// can also poll the bg-poller for live updates.
|
|
639
600
|
try {
|
|
640
|
-
const
|
|
641
|
-
if (
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
if (
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
sessionId: chatSessionId,
|
|
660
|
-
delta: textDelta.delta,
|
|
661
|
-
type: 'text',
|
|
662
|
-
messageId: envelope.messageID || null,
|
|
663
|
-
});
|
|
601
|
+
const list = listClaudeMessages(claudeSessionId);
|
|
602
|
+
if (list?.ok && Array.isArray(list.messages)) {
|
|
603
|
+
const assistants = list.messages
|
|
604
|
+
.filter((m) => m.role === 'assistant')
|
|
605
|
+
.sort((a, b) => (b.ts || 0) - (a.ts || 0));
|
|
606
|
+
for (const m of assistants) {
|
|
607
|
+
const assistantRecord = {
|
|
608
|
+
id: m.id || `asst_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,
|
|
609
|
+
ts: new Date(m.ts || Date.now()).toISOString(),
|
|
610
|
+
role: 'assistant',
|
|
611
|
+
agent: agentName,
|
|
612
|
+
content: m.content || '',
|
|
613
|
+
claudeSessionId,
|
|
614
|
+
inReplyTo: record.id,
|
|
615
|
+
};
|
|
616
|
+
try {
|
|
617
|
+
if (file) appendFileSync(file, JSON.stringify(assistantRecord) + '\n', 'utf8');
|
|
618
|
+
} catch {
|
|
619
|
+
/* best effort */
|
|
664
620
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
void (async () => {
|
|
674
|
-
try {
|
|
675
|
-
const list = await listClineMessages(
|
|
676
|
-
serveInfo,
|
|
677
|
-
clineSessionId,
|
|
678
|
-
directory,
|
|
679
|
-
);
|
|
680
|
-
if (list?.ok && Array.isArray(list.messages)) {
|
|
681
|
-
const promptSentAt = Date.now() - 5_000; // buffer for clock skew
|
|
682
|
-
const assistants = list.messages
|
|
683
|
-
.filter((m) => (m?.info?.role || m?.role) === 'assistant')
|
|
684
|
-
.sort((a, b) => {
|
|
685
|
-
const ta = a?.info?.time?.created || 0;
|
|
686
|
-
const tb = b?.info?.time?.created || 0;
|
|
687
|
-
return tb - ta;
|
|
688
|
-
});
|
|
689
|
-
for (const m of assistants) {
|
|
690
|
-
const created = m?.info?.time?.created || 0;
|
|
691
|
-
if (created >= promptSentAt) {
|
|
692
|
-
const id = m?.info?.id || '';
|
|
693
|
-
assistantRecord = {
|
|
694
|
-
id: id || `asst_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,
|
|
695
|
-
ts: new Date(created || Date.now()).toISOString(),
|
|
696
|
-
role: 'assistant',
|
|
697
|
-
agent: agentName,
|
|
698
|
-
content: extractContentFromClineMessage(m),
|
|
699
|
-
clineSessionId,
|
|
700
|
-
inReplyTo: record.id,
|
|
701
|
-
};
|
|
702
|
-
break;
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
} catch {
|
|
707
|
-
// best effort
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
if (assistantRecord) {
|
|
711
|
-
try {
|
|
712
|
-
if (file) appendFileSync(file, JSON.stringify(assistantRecord) + '\n', 'utf8');
|
|
713
|
-
} catch {
|
|
714
|
-
// best effort
|
|
715
|
-
}
|
|
716
|
-
broadcast({ type: 'chat:message', sessionId: chatSessionId, message: assistantRecord });
|
|
717
|
-
state.appendActivity({
|
|
718
|
-
kind: 'chat.response',
|
|
719
|
-
agent: agentName,
|
|
720
|
-
message: (assistantRecord.content || '').slice(0, 500),
|
|
721
|
-
});
|
|
722
|
-
}
|
|
723
|
-
onDone();
|
|
724
|
-
})();
|
|
725
|
-
},
|
|
726
|
-
});
|
|
727
|
-
} catch (err) {
|
|
728
|
-
if (controller.signal.aborted) return;
|
|
729
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
730
|
-
broadcast({ type: 'chat:error', sessionId: chatSessionId, error: `stream_error: ${msg}` });
|
|
731
|
-
onDone();
|
|
732
|
-
} finally {
|
|
733
|
-
if (!done) {
|
|
734
|
-
done = true;
|
|
735
|
-
onDone();
|
|
621
|
+
broadcast({ type: 'chat:message', sessionId: chatSessionId, message: assistantRecord });
|
|
622
|
+
state.appendActivity({
|
|
623
|
+
kind: 'chat.response',
|
|
624
|
+
agent: agentName,
|
|
625
|
+
message: (assistantRecord.content || '').slice(0, 500),
|
|
626
|
+
});
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
736
629
|
}
|
|
630
|
+
} catch {
|
|
631
|
+
/* best effort */
|
|
737
632
|
}
|
|
738
|
-
|
|
739
|
-
|
|
633
|
+
onDone();
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
740
636
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
* the appropriate callback.
|
|
744
|
-
*
|
|
745
|
-
* @param {ReadableStream<Uint8Array>} body
|
|
746
|
-
* @param {AbortController} controller
|
|
747
|
-
* @param {string} sessionId
|
|
748
|
-
* @param {{ onDelta: Function, onIdle: Function }} handlers
|
|
749
|
-
*/
|
|
750
|
-
async function pumpSseForChat(body, controller, sessionId, handlers) {
|
|
751
|
-
const reader = body.getReader();
|
|
752
|
-
const decoder = new TextDecoder('utf-8');
|
|
753
|
-
let buffer = '';
|
|
637
|
+
let assistantRecord = null;
|
|
638
|
+
let done = false;
|
|
754
639
|
try {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
if (
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
640
|
+
for await (const evt of sub) {
|
|
641
|
+
if (!evt || typeof evt !== 'object') continue;
|
|
642
|
+
const t = evt.type || evt.kind;
|
|
643
|
+
if (t === 'delta' || t === 'message.part.updated' || t === 'text_delta') {
|
|
644
|
+
const delta = extractTextDelta(evt);
|
|
645
|
+
if (delta) {
|
|
646
|
+
if (!noteChatDelta(chatSessionId)) continue;
|
|
647
|
+
broadcast({
|
|
648
|
+
type: 'chat:delta',
|
|
649
|
+
sessionId: chatSessionId,
|
|
650
|
+
delta,
|
|
651
|
+
type: 'text',
|
|
652
|
+
messageId: evt.messageID || evt.message_id || null,
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
} else if (t === 'idle' || t === 'session.idle' || t === 'done' || t === 'session.done') {
|
|
656
|
+
if (done) break;
|
|
657
|
+
done = true;
|
|
658
|
+
resetChatDeltaCount(chatSessionId);
|
|
659
|
+
// Pull the final assistant message from the JSONL.
|
|
660
|
+
try {
|
|
661
|
+
const list = listClaudeMessages(claudeSessionId);
|
|
662
|
+
if (list?.ok && Array.isArray(list.messages)) {
|
|
663
|
+
const assistants = list.messages
|
|
664
|
+
.filter((m) => m.role === 'assistant')
|
|
665
|
+
.sort((a, b) => (b.ts || 0) - (a.ts || 0));
|
|
666
|
+
for (const m of assistants) {
|
|
667
|
+
const ts = m.ts || 0;
|
|
668
|
+
if (ts >= (record?.ts ? Date.parse(record.ts) - 5_000 : 0)) {
|
|
669
|
+
assistantRecord = {
|
|
670
|
+
id: m.id || `asst_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,
|
|
671
|
+
ts: new Date(ts || Date.now()).toISOString(),
|
|
672
|
+
role: 'assistant',
|
|
673
|
+
agent: agentName,
|
|
674
|
+
content: m.content || '',
|
|
675
|
+
claudeSessionId,
|
|
676
|
+
inReplyTo: record.id,
|
|
677
|
+
};
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
} catch {
|
|
683
|
+
/* best effort */
|
|
684
|
+
}
|
|
685
|
+
if (assistantRecord) {
|
|
686
|
+
try {
|
|
687
|
+
if (file) appendFileSync(file, JSON.stringify(assistantRecord) + '\n', 'utf8');
|
|
688
|
+
} catch {
|
|
689
|
+
/* best effort */
|
|
690
|
+
}
|
|
691
|
+
broadcast({ type: 'chat:message', sessionId: chatSessionId, message: assistantRecord });
|
|
692
|
+
state.appendActivity({
|
|
693
|
+
kind: 'chat.response',
|
|
694
|
+
agent: agentName,
|
|
695
|
+
message: (assistantRecord.content || '').slice(0, 500),
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
break;
|
|
767
699
|
}
|
|
768
700
|
}
|
|
701
|
+
} catch (err) {
|
|
702
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
703
|
+
broadcast({ type: 'chat:error', sessionId: chatSessionId, error: `stream_error: ${msg}` });
|
|
769
704
|
} finally {
|
|
770
|
-
try {
|
|
705
|
+
try { sub?.close?.(); } catch { /* ignore */ }
|
|
706
|
+
if (!done) onDone();
|
|
707
|
+
else onDone();
|
|
771
708
|
}
|
|
772
709
|
}
|
|
773
710
|
|
|
774
711
|
/**
|
|
775
|
-
*
|
|
776
|
-
*
|
|
777
|
-
* @param {string} block
|
|
778
|
-
* @param {string} sessionId
|
|
779
|
-
* @param {{ onDelta: Function, onIdle: Function }} handlers
|
|
780
|
-
*/
|
|
781
|
-
function handleChatSseBlock(block, sessionId, handlers) {
|
|
782
|
-
if (!block || block.trim() === '') return;
|
|
783
|
-
let eventName = null;
|
|
784
|
-
const dataLines = [];
|
|
785
|
-
for (const line of block.split(/\r?\n/)) {
|
|
786
|
-
if (line === '' || line.startsWith(':')) continue;
|
|
787
|
-
const colon = line.indexOf(':');
|
|
788
|
-
if (colon < 0) continue;
|
|
789
|
-
const field = line.slice(0, colon);
|
|
790
|
-
let value = line.slice(colon + 1);
|
|
791
|
-
if (value.startsWith(' ')) value = value.slice(1);
|
|
792
|
-
if (field === 'event') eventName = value;
|
|
793
|
-
else if (field === 'data') dataLines.push(value);
|
|
794
|
-
}
|
|
795
|
-
if (dataLines.length === 0) return;
|
|
796
|
-
const raw = dataLines.join('\n');
|
|
797
|
-
let parsed;
|
|
798
|
-
try {
|
|
799
|
-
parsed = JSON.parse(raw);
|
|
800
|
-
} catch {
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
803
|
-
const evt = unwrapClineSseEvent(eventName, parsed);
|
|
804
|
-
if (!evt || !evt.type) return;
|
|
805
|
-
if (evt.sessionID && evt.sessionID !== sessionId) return;
|
|
806
|
-
|
|
807
|
-
if (evt.type === 'message.part.updated') {
|
|
808
|
-
handlers.onDelta(evt);
|
|
809
|
-
} else if (evt.type === 'session.idle') {
|
|
810
|
-
handlers.onIdle(evt);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
/**
|
|
815
|
-
* Extract a text delta from an cline SSE event envelope.
|
|
712
|
+
* Extract a text delta string from a Claude Code SDK event envelope.
|
|
816
713
|
* Returns null if no text delta is present.
|
|
817
714
|
*
|
|
818
|
-
* @param {object}
|
|
819
|
-
* @returns {
|
|
715
|
+
* @param {object} evt
|
|
716
|
+
* @returns {string | null}
|
|
820
717
|
*/
|
|
821
|
-
function extractTextDelta(
|
|
822
|
-
const part =
|
|
823
|
-
if (
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
if (p.type === 'text' && typeof p.text === 'string') {
|
|
827
|
-
return { delta: p.text };
|
|
718
|
+
function extractTextDelta(evt) {
|
|
719
|
+
const part = evt.part || evt.data?.part;
|
|
720
|
+
if (part && typeof part === 'object') {
|
|
721
|
+
const p = /** @type {any} */ (part);
|
|
722
|
+
if (typeof p.text === 'string') return p.text;
|
|
828
723
|
}
|
|
724
|
+
if (typeof evt.delta === 'string') return evt.delta;
|
|
725
|
+
if (typeof evt.text === 'string') return evt.text;
|
|
829
726
|
return null;
|
|
830
|
-
}
|
|
727
|
+
}
|