@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
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Logger — thin wrapper over `client.app.log`.
|
|
3
|
-
*
|
|
4
|
-
* Spec requirements satisfied here:
|
|
5
|
-
* - §6.5 — honors `BIZAR_LOG_LEVEL` env var (`debug` | `info` | `warn` | `error`,
|
|
6
|
-
* default `info`, invalid values fall back to `info` with a warning).
|
|
7
|
-
* - §7.6 — never accepts or emits raw tool args / session content. Every call
|
|
8
|
-
* to `client.app.log` is `{ level, message }` (or the SDK's wrapped
|
|
9
|
-
* `{ body: { service, level, message } }` shape) where `message` is a
|
|
10
|
-
* static string the caller constructed. There is no second argument,
|
|
11
|
-
* no metadata object, no structured logging payload.
|
|
12
|
-
*
|
|
13
|
-
* The logger swallows all errors from the underlying `client.app.log` call.
|
|
14
|
-
* A failed log MUST NOT crash the plugin or block the tool hook.
|
|
15
|
-
*
|
|
16
|
-
* The interface is intentionally compatible with the `Logger` shape that
|
|
17
|
-
* Thor's `StateStore` and `LogWriter` accept: a single `log(opts)` method
|
|
18
|
-
* returning `void` (synchronous). We also expose level-specific convenience
|
|
19
|
-
* methods (`debug` / `info` / `warn` / `error`) that all delegate to `log`.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
23
|
-
|
|
24
|
-
const LEVEL_ORDER: Record<LogLevel, number> = {
|
|
25
|
-
debug: 0,
|
|
26
|
-
info: 1,
|
|
27
|
-
warn: 2,
|
|
28
|
-
error: 3,
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Minimal client surface the logger depends on. The real cline client's
|
|
33
|
-
* `app.log` is a generated SDK method with a complex Options<T> type; we
|
|
34
|
-
* accept any object with a compatible `app.log` method and treat the body
|
|
35
|
-
* as an opaque `unknown` at the boundary. The logger internally shapes the
|
|
36
|
-
* call to the SDK's expected `{ body: { service, level, message } }` form.
|
|
37
|
-
*/
|
|
38
|
-
export interface LoggerClient {
|
|
39
|
-
app: {
|
|
40
|
-
log: (input: unknown) => unknown;
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* The Logger interface matches the one defined in `state.ts` and `report.ts`
|
|
46
|
-
* (Thor's modules). A single `log(opts)` method, synchronous, returns `void`.
|
|
47
|
-
* The convenience methods are additive.
|
|
48
|
-
*/
|
|
49
|
-
export interface Logger {
|
|
50
|
-
log(opts: { level: LogLevel; message: string }): void;
|
|
51
|
-
debug(message: string): void;
|
|
52
|
-
info(message: string): void;
|
|
53
|
-
warn(message: string): void;
|
|
54
|
-
error(message: string): void;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface ParsedLogLevel {
|
|
58
|
-
level: LogLevel;
|
|
59
|
-
wasInvalid: boolean;
|
|
60
|
-
original: string | undefined;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function parseLogLevel(raw: string | undefined): ParsedLogLevel {
|
|
64
|
-
if (raw === undefined || raw === "") {
|
|
65
|
-
return { level: "info", wasInvalid: false, original: raw };
|
|
66
|
-
}
|
|
67
|
-
const lower = raw.toLowerCase();
|
|
68
|
-
if (lower === "debug" || lower === "info" || lower === "warn" || lower === "error") {
|
|
69
|
-
return { level: lower, wasInvalid: false, original: raw };
|
|
70
|
-
}
|
|
71
|
-
return { level: "info", wasInvalid: true, original: raw };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function readEnvLevel(): string | undefined {
|
|
75
|
-
try {
|
|
76
|
-
return typeof process !== "undefined" && process.env
|
|
77
|
-
? process.env.BIZAR_LOG_LEVEL
|
|
78
|
-
: undefined;
|
|
79
|
-
} catch {
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Service name stamped onto every log entry. Lets users filter app logs
|
|
86
|
-
* by service (e.g. `bizar`) in their diagnostic tooling.
|
|
87
|
-
*/
|
|
88
|
-
const SERVICE_NAME = "bizar";
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Construct a Logger bound to the cline client.
|
|
92
|
-
*
|
|
93
|
-
* @param client The cline client (we use only `client.app.log`).
|
|
94
|
-
* @param envLevel Optional override for `BIZAR_LOG_LEVEL`. If omitted, the
|
|
95
|
-
* env var is read once. Env vars are read at plugin init;
|
|
96
|
-
* mid-session changes are ignored (spec §6.5).
|
|
97
|
-
*/
|
|
98
|
-
export function createLogger(client: LoggerClient, envLevel?: string): Logger {
|
|
99
|
-
const raw = envLevel ?? readEnvLevel();
|
|
100
|
-
const parsed = parseLogLevel(raw);
|
|
101
|
-
const threshold = parsed.level;
|
|
102
|
-
|
|
103
|
-
if (parsed.wasInvalid) {
|
|
104
|
-
// Best-effort: warn about the invalid value, then proceed with "info".
|
|
105
|
-
// We call `client.app.log` directly because the logger is not yet wired
|
|
106
|
-
// to the threshold.
|
|
107
|
-
try {
|
|
108
|
-
client.app.log({
|
|
109
|
-
body: { service: SERVICE_NAME, level: "warn", message: `bizar: invalid BIZAR_LOG_LEVEL "${parsed.original ?? ""}", falling back to "info"` },
|
|
110
|
-
});
|
|
111
|
-
} catch {
|
|
112
|
-
// ignore — never let a warning crash init
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function emit(msgLevel: LogLevel, message: string): void {
|
|
117
|
-
if (LEVEL_ORDER[msgLevel] < LEVEL_ORDER[threshold]) return;
|
|
118
|
-
try {
|
|
119
|
-
client.app.log({
|
|
120
|
-
body: { service: SERVICE_NAME, level: msgLevel, message },
|
|
121
|
-
});
|
|
122
|
-
} catch {
|
|
123
|
-
// Swallow — a failed log MUST NOT crash the plugin.
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
log(opts) {
|
|
129
|
-
emit(opts.level, opts.message);
|
|
130
|
-
},
|
|
131
|
-
debug(message) {
|
|
132
|
-
emit("debug", message);
|
|
133
|
-
},
|
|
134
|
-
info(message) {
|
|
135
|
-
emit("info", message);
|
|
136
|
-
},
|
|
137
|
-
warn(message) {
|
|
138
|
-
emit("warn", message);
|
|
139
|
-
},
|
|
140
|
-
error(message) {
|
|
141
|
-
emit("error", message);
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
|
-
}
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* loop-engineering.ts
|
|
3
|
-
*
|
|
4
|
-
* v6.0.0 — Structured loop patterns for continuous agent execution.
|
|
5
|
-
*
|
|
6
|
-
* Inspired by:
|
|
7
|
-
* - https://github.com/cobusgreyling/loop-engineering
|
|
8
|
-
* - https://github.com/rudy2steiner/awesome-agent-loops
|
|
9
|
-
*
|
|
10
|
-
* Patterns supported (v1):
|
|
11
|
-
*
|
|
12
|
-
* 1. **ralph** — repeat until output contains `[STOP]` marker, or until
|
|
13
|
-
* `maxIterations` is reached, or until a manual stop signal.
|
|
14
|
-
* 2. **repl** — interactive, awaits user input between iterations
|
|
15
|
-
* (not yet implemented in v1).
|
|
16
|
-
* 3. **cron** — interval-based; reruns every N seconds.
|
|
17
|
-
* 4. **plan-execute** — decompose once, iterate over subtasks.
|
|
18
|
-
*
|
|
19
|
-
* Loops are stored on disk as JSON files at:
|
|
20
|
-
* ~/.bizar/loops/<loopId>.json
|
|
21
|
-
*
|
|
22
|
-
* Each loop has:
|
|
23
|
-
* - id, pattern, task, status (running | stopped | done)
|
|
24
|
-
* - iterations[] — log of { index, startedAt, durationMs, output, stopReason }
|
|
25
|
-
* - context — pattern-specific config (interval, maxIterations, etc.)
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import { mkdirSync, writeFileSync, readFileSync, existsSync, readdirSync, unlinkSync } from "node:fs";
|
|
29
|
-
import { join } from "node:path";
|
|
30
|
-
import { homedir } from "node:os";
|
|
31
|
-
|
|
32
|
-
export type LoopPattern = "ralph" | "repl" | "cron" | "plan-execute";
|
|
33
|
-
export type LoopStatus = "running" | "stopped" | "done";
|
|
34
|
-
|
|
35
|
-
export interface LoopIteration {
|
|
36
|
-
index: number;
|
|
37
|
-
startedAt: string;
|
|
38
|
-
durationMs: number;
|
|
39
|
-
output: string;
|
|
40
|
-
stopReason?: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface LoopContext {
|
|
44
|
-
maxIterations: number;
|
|
45
|
-
stopMarker?: string;
|
|
46
|
-
intervalSeconds?: number;
|
|
47
|
-
subtasks?: string[];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface Loop {
|
|
51
|
-
id: string;
|
|
52
|
-
pattern: LoopPattern;
|
|
53
|
-
task: string;
|
|
54
|
-
status: LoopStatus;
|
|
55
|
-
createdAt: string;
|
|
56
|
-
updatedAt: string;
|
|
57
|
-
context: LoopContext;
|
|
58
|
-
iterations: LoopIteration[];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const LOOPS_DIR = join(homedir(), ".bizar", "loops");
|
|
62
|
-
|
|
63
|
-
function ensureLoopsDir(): void {
|
|
64
|
-
if (!existsSync(LOOPS_DIR)) {
|
|
65
|
-
mkdirSync(LOOPS_DIR, { recursive: true });
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function generateLoopId(): string {
|
|
70
|
-
return `loop_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export interface StartLoopInput {
|
|
74
|
-
pattern: LoopPattern;
|
|
75
|
-
task: string;
|
|
76
|
-
context?: Partial<LoopContext>;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export interface StartLoopOutput {
|
|
80
|
-
ok: true;
|
|
81
|
-
loop: Loop;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function startLoop(input: StartLoopInput): StartLoopOutput {
|
|
85
|
-
ensureLoopsDir();
|
|
86
|
-
const id = generateLoopId();
|
|
87
|
-
const now = new Date().toISOString();
|
|
88
|
-
const loop: Loop = {
|
|
89
|
-
id,
|
|
90
|
-
pattern: input.pattern,
|
|
91
|
-
task: input.task,
|
|
92
|
-
status: "running",
|
|
93
|
-
createdAt: now,
|
|
94
|
-
updatedAt: now,
|
|
95
|
-
context: {
|
|
96
|
-
maxIterations: input.context?.maxIterations ?? 10,
|
|
97
|
-
stopMarker: input.context?.stopMarker,
|
|
98
|
-
intervalSeconds: input.context?.intervalSeconds,
|
|
99
|
-
subtasks: input.context?.subtasks,
|
|
100
|
-
},
|
|
101
|
-
iterations: [],
|
|
102
|
-
};
|
|
103
|
-
writeFileSync(join(LOOPS_DIR, `${id}.json`), JSON.stringify(loop, null, 2));
|
|
104
|
-
return { ok: true, loop };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function loadLoop(id: string): Loop | null {
|
|
108
|
-
const path = join(LOOPS_DIR, `${id}.json`);
|
|
109
|
-
if (!existsSync(path)) return null;
|
|
110
|
-
return JSON.parse(readFileSync(path, "utf8")) as Loop;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function saveLoop(loop: Loop): void {
|
|
114
|
-
loop.updatedAt = new Date().toISOString();
|
|
115
|
-
writeFileSync(join(LOOPS_DIR, `${loop.id}.json`), JSON.stringify(loop, null, 2));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Append an iteration result to a loop and update its status if a stop
|
|
120
|
-
* condition was met.
|
|
121
|
-
*/
|
|
122
|
-
export function appendIteration(
|
|
123
|
-
id: string,
|
|
124
|
-
result: Omit<LoopIteration, "startedAt"> & { startedAt?: string },
|
|
125
|
-
): Loop | null {
|
|
126
|
-
const loop = loadLoop(id);
|
|
127
|
-
if (!loop) return null;
|
|
128
|
-
const iter: LoopIteration = {
|
|
129
|
-
index: loop.iterations.length,
|
|
130
|
-
startedAt: result.startedAt ?? new Date().toISOString(),
|
|
131
|
-
durationMs: result.durationMs,
|
|
132
|
-
output: result.output,
|
|
133
|
-
stopReason: result.stopReason,
|
|
134
|
-
};
|
|
135
|
-
loop.iterations.push(iter);
|
|
136
|
-
if (loop.context.maxIterations > 0 && loop.iterations.length >= loop.context.maxIterations) {
|
|
137
|
-
loop.status = "done";
|
|
138
|
-
} else if (
|
|
139
|
-
loop.pattern === "ralph" &&
|
|
140
|
-
loop.context.stopMarker &&
|
|
141
|
-
result.output.includes(loop.context.stopMarker)
|
|
142
|
-
) {
|
|
143
|
-
loop.status = "done";
|
|
144
|
-
iter.stopReason = `stop marker "${loop.context.stopMarker}" found`;
|
|
145
|
-
}
|
|
146
|
-
saveLoop(loop);
|
|
147
|
-
return loop;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Mark a loop as stopped (manual stop signal).
|
|
152
|
-
*
|
|
153
|
-
* If `reason` is given AND the loop has at least one iteration, attach
|
|
154
|
-
* the reason to the most recent iteration. Otherwise, append a synthetic
|
|
155
|
-
* "stopped" iteration so the reason is preserved in the audit log.
|
|
156
|
-
*/
|
|
157
|
-
export function stopLoop(id: string, reason?: string): Loop | null {
|
|
158
|
-
const loop = loadLoop(id);
|
|
159
|
-
if (!loop) return null;
|
|
160
|
-
loop.status = "stopped";
|
|
161
|
-
if (reason) {
|
|
162
|
-
if (loop.iterations.length > 0) {
|
|
163
|
-
const last = loop.iterations[loop.iterations.length - 1];
|
|
164
|
-
if (last) last.stopReason = reason;
|
|
165
|
-
} else {
|
|
166
|
-
// Append a synthetic iteration so the reason isn't lost.
|
|
167
|
-
loop.iterations.push({
|
|
168
|
-
index: 0,
|
|
169
|
-
startedAt: new Date().toISOString(),
|
|
170
|
-
durationMs: 0,
|
|
171
|
-
output: "(loop stopped before any iterations ran)",
|
|
172
|
-
stopReason: reason,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
saveLoop(loop);
|
|
177
|
-
return loop;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Delete a loop from disk.
|
|
182
|
-
*/
|
|
183
|
-
export function deleteLoop(id: string): boolean {
|
|
184
|
-
const path = join(LOOPS_DIR, `${id}.json`);
|
|
185
|
-
if (!existsSync(path)) return false;
|
|
186
|
-
unlinkSync(path);
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* List all loops on disk, optionally filtered by status / pattern.
|
|
192
|
-
*/
|
|
193
|
-
export function listLoops(filter?: { status?: LoopStatus; pattern?: LoopPattern }): Loop[] {
|
|
194
|
-
ensureLoopsDir();
|
|
195
|
-
const files = readdirSync(LOOPS_DIR).filter((f) => f.endsWith(".json"));
|
|
196
|
-
const out: Loop[] = [];
|
|
197
|
-
for (const f of files) {
|
|
198
|
-
try {
|
|
199
|
-
const l = JSON.parse(readFileSync(join(LOOPS_DIR, f), "utf8")) as Loop;
|
|
200
|
-
if (filter?.status && l.status !== filter.status) continue;
|
|
201
|
-
if (filter?.pattern && l.pattern !== filter.pattern) continue;
|
|
202
|
-
out.push(l);
|
|
203
|
-
} catch {
|
|
204
|
-
// skip malformed
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return out.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Pattern execution: given a loop, return the prompt for the next
|
|
212
|
-
* iteration and the stop marker to look for (if any).
|
|
213
|
-
*/
|
|
214
|
-
export function buildNextIteration(loop: Loop): { prompt: string; stopMarker: string | null } {
|
|
215
|
-
switch (loop.pattern) {
|
|
216
|
-
case "ralph": {
|
|
217
|
-
const stopMarker = loop.context.stopMarker ?? "[STOP]";
|
|
218
|
-
const iterIndex = loop.iterations.length + 1;
|
|
219
|
-
const prompt = [
|
|
220
|
-
`Loop iteration ${iterIndex} of ${loop.context.maxIterations}.`,
|
|
221
|
-
`Task: ${loop.task}`,
|
|
222
|
-
``,
|
|
223
|
-
`Output the literal marker "${stopMarker}" on its own line when done.`,
|
|
224
|
-
].join("\n");
|
|
225
|
-
return { prompt, stopMarker };
|
|
226
|
-
}
|
|
227
|
-
case "cron":
|
|
228
|
-
return { prompt: loop.task, stopMarker: null };
|
|
229
|
-
case "plan-execute": {
|
|
230
|
-
const subs = loop.context.subtasks ?? [];
|
|
231
|
-
const nextIdx = loop.iterations.length;
|
|
232
|
-
const subtask = subs[nextIdx] ?? "(no more subtasks)";
|
|
233
|
-
return {
|
|
234
|
-
prompt: `Subtask ${nextIdx + 1} of ${subs.length}: ${subtask}`,
|
|
235
|
-
stopMarker: null,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
case "repl":
|
|
239
|
-
return { prompt: loop.task, stopMarker: null };
|
|
240
|
-
}
|
|
241
|
-
}
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Loop — the threshold decision tree.
|
|
3
|
-
*
|
|
4
|
-
* Spec requirements satisfied here:
|
|
5
|
-
* - §5.1 — algorithm. Count how many of the last `loopWindowSize` tool
|
|
6
|
-
* calls share the given fingerprint. Re-scan the window each call.
|
|
7
|
-
* - §5.4 — threshold table. The action returned by `decide()` corresponds
|
|
8
|
-
* to the highest threshold that the count meets or exceeds.
|
|
9
|
-
* - §5.5 — window vs counter semantics. The count is a re-scan of the
|
|
10
|
-
* window, not a running counter. The intermediate non-matching calls
|
|
11
|
-
* in the window do NOT "reset" anything; they just sit in the window
|
|
12
|
-
* and shift older matching entries out as the window rolls forward.
|
|
13
|
-
* - §7.2 — the `reason` field returned by `decide()` for warn/escalate/
|
|
14
|
-
* block actions is a static message from `handoff.ts` with only the
|
|
15
|
-
* tool name interpolated. No tool args, no LLM output, no agent-
|
|
16
|
-
* controlled content is ever included.
|
|
17
|
-
*
|
|
18
|
-
* The `now` parameter is part of the public signature for API consistency
|
|
19
|
-
* with the spec (§5.1 timing context) but is not consumed by the decision
|
|
20
|
-
* logic — callers use it to timestamp the current tool call when adding it
|
|
21
|
-
* to the state.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import type { SessionState } from "./state.js";
|
|
25
|
-
import type { NormalizedOptions } from "./options.js";
|
|
26
|
-
import {
|
|
27
|
-
warnMessage,
|
|
28
|
-
escalateMessage,
|
|
29
|
-
blockMessage,
|
|
30
|
-
} from "./handoff.js";
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* The decision returned by `decide()`. The `allow` variant carries no
|
|
34
|
-
* payload; the three action variants carry the canonical handoff message
|
|
35
|
-
* (via `reason`), the fingerprint that triggered the decision, and the
|
|
36
|
-
* repetition count in the window.
|
|
37
|
-
*/
|
|
38
|
-
export type Decision =
|
|
39
|
-
| { action: "allow" }
|
|
40
|
-
| {
|
|
41
|
-
action: "warn" | "escalate" | "block";
|
|
42
|
-
reason: string;
|
|
43
|
-
fingerprint: string;
|
|
44
|
-
count: number;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Implicit "log a warning" threshold from spec §5.4 row 1. This is always
|
|
49
|
-
* two below `loopThresholdWarn` so the gap between log-only and inject-warn
|
|
50
|
-
* stays constant when the user reconfigures. We `max(1, …)` so a user who
|
|
51
|
-
* sets `loopThresholdWarn = 1` does not push the log-only threshold below 1.
|
|
52
|
-
*/
|
|
53
|
-
function logOnlyThreshold(options: NormalizedOptions): number {
|
|
54
|
-
return Math.max(1, options.loopThresholdWarn - 2);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Look at the last `loopWindowSize` tool calls and return those that share
|
|
59
|
-
* the given fingerprint. The window is a slice of the most recent entries;
|
|
60
|
-
* older entries are ignored (spec §5.1 step 3).
|
|
61
|
-
*/
|
|
62
|
-
function windowMatches(
|
|
63
|
-
state: SessionState,
|
|
64
|
-
fingerprint: string,
|
|
65
|
-
windowSize: number,
|
|
66
|
-
): { calls: SessionState["toolCalls"]; count: number } {
|
|
67
|
-
if (state.toolCalls.length === 0 || windowSize <= 0) {
|
|
68
|
-
return { calls: [], count: 0 };
|
|
69
|
-
}
|
|
70
|
-
const start = Math.max(0, state.toolCalls.length - windowSize);
|
|
71
|
-
const window = state.toolCalls.slice(start);
|
|
72
|
-
const matches = window.filter((c) => c.fingerprint === fingerprint);
|
|
73
|
-
return { calls: matches, count: matches.length };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Recover the tool name from any call in `matches` that shares the
|
|
78
|
-
* fingerprint. The fingerprint is computed from `(tool, normalized args)`,
|
|
79
|
-
* so every call with this fingerprint has the same tool name. If the
|
|
80
|
-
* matches list is empty (should not happen when this is called — the
|
|
81
|
-
* caller only invokes us when `count >= 1`), fall back to `"unknown"`.
|
|
82
|
-
*/
|
|
83
|
-
function toolNameOf(matches: SessionState["toolCalls"]): string {
|
|
84
|
-
const first = matches[0];
|
|
85
|
-
return first ? first.tool : "unknown";
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Build a Decision payload for a non-allow action. Pulls the canonical
|
|
90
|
-
* handoff message for the given action and the tool name recovered from
|
|
91
|
-
* the matching window entries.
|
|
92
|
-
*/
|
|
93
|
-
function payload(
|
|
94
|
-
action: "warn" | "escalate" | "block",
|
|
95
|
-
fingerprint: string,
|
|
96
|
-
count: number,
|
|
97
|
-
matches: SessionState["toolCalls"],
|
|
98
|
-
): Decision {
|
|
99
|
-
const tool = toolNameOf(matches);
|
|
100
|
-
const reason =
|
|
101
|
-
action === "block"
|
|
102
|
-
? blockMessage(tool)
|
|
103
|
-
: action === "escalate"
|
|
104
|
-
? escalateMessage(tool)
|
|
105
|
-
: warnMessage(tool);
|
|
106
|
-
return { action, reason, fingerprint, count };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Decide what action to take for the current tool call.
|
|
111
|
-
*
|
|
112
|
-
* The function is pure: it does not mutate `state` and does not perform any
|
|
113
|
-
* I/O. The caller (the `tool.execute.before` hook in `index.ts`) is
|
|
114
|
-
* responsible for adding the current call to the state before invoking
|
|
115
|
-
* `decide()`, and for acting on the returned decision (log / inject / throw).
|
|
116
|
-
*
|
|
117
|
-
* @param state The current session state. Must already include the
|
|
118
|
-
* current tool call in `state.toolCalls` (the caller
|
|
119
|
-
* appends it before calling `decide()`).
|
|
120
|
-
* @param fingerprint The fingerprint of the current tool call.
|
|
121
|
-
* @param now Current epoch milliseconds. Part of the public signature
|
|
122
|
-
* for API consistency; not consumed by the decision logic.
|
|
123
|
-
* @param options Normalized plugin options.
|
|
124
|
-
*/
|
|
125
|
-
export function decide(
|
|
126
|
-
state: SessionState,
|
|
127
|
-
fingerprint: string,
|
|
128
|
-
now: number,
|
|
129
|
-
options: NormalizedOptions,
|
|
130
|
-
): Decision {
|
|
131
|
-
// Step 1: scan the window.
|
|
132
|
-
const { calls: matches, count } = windowMatches(
|
|
133
|
-
state,
|
|
134
|
-
fingerprint,
|
|
135
|
-
options.loopWindowSize,
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
// Step 2: apply thresholds from highest to lowest.
|
|
139
|
-
if (count >= options.loopThresholdBlock) {
|
|
140
|
-
return payload("block", fingerprint, count, matches);
|
|
141
|
-
}
|
|
142
|
-
if (count >= options.loopThresholdEscalate) {
|
|
143
|
-
return payload("escalate", fingerprint, count, matches);
|
|
144
|
-
}
|
|
145
|
-
if (count >= options.loopThresholdWarn) {
|
|
146
|
-
return payload("warn", fingerprint, count, matches);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Step 3: log-only band (spec §5.4 row 1). Sits between `allow` and the
|
|
150
|
-
// inject-warn threshold. The caller distinguishes this case from
|
|
151
|
-
// inject-warn by inspecting `count` against `options.loopThresholdWarn`.
|
|
152
|
-
const logOnly = logOnlyThreshold(options);
|
|
153
|
-
if (count >= logOnly) {
|
|
154
|
-
return payload("warn", fingerprint, count, matches);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// `now` is accepted for API consistency; reference it so strict no-unused
|
|
158
|
-
// linters do not flag the parameter.
|
|
159
|
-
void now;
|
|
160
|
-
|
|
161
|
-
return { action: "allow" };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Convenience helper for the caller: given a `warn` decision, was this the
|
|
166
|
-
* log-only band (no injection) or the inject-warn band? The caller uses
|
|
167
|
-
* this to decide whether to write a `client.app.log` line or to set a
|
|
168
|
-
* pending system-transform injection.
|
|
169
|
-
*/
|
|
170
|
-
export function isLogOnlyWarn(
|
|
171
|
-
decision: Decision,
|
|
172
|
-
options: NormalizedOptions,
|
|
173
|
-
): boolean {
|
|
174
|
-
if (decision.action !== "warn") return false;
|
|
175
|
-
return decision.count < options.loopThresholdWarn;
|
|
176
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* mistake-recovery.ts
|
|
3
|
-
*
|
|
4
|
-
* v6.0.1 — Built-in `onConsecutiveMistakeLimitReached` callback for the
|
|
5
|
-
* Cline runtime.
|
|
6
|
-
*
|
|
7
|
-
* Why this exists:
|
|
8
|
-
*
|
|
9
|
-
* The Cline runtime aborts a session after `execution.maxConsecutiveMistakes`
|
|
10
|
-
* identical-failure turns. The bundled CLI defaults this to 3 (via the
|
|
11
|
-
* `--retries` flag); the SDK default is 6. When the model emits malformed
|
|
12
|
-
* tool calls — e.g. `editor({ new_text: undefined })`, `ask_question({ options: null })`,
|
|
13
|
-
* `run_commands({ ... })` rejected by Zod — the counter increments on every
|
|
14
|
-
* iteration and the session is dead after a couple of bad turns.
|
|
15
|
-
*
|
|
16
|
-
* Rather than raising the threshold (which masks the model misbehaviour),
|
|
17
|
-
* we install a `onConsecutiveMistakeLimitReached` callback that:
|
|
18
|
-
*
|
|
19
|
-
* • For recoverable mistakes (invalid_tool_call, tool_execution_failed),
|
|
20
|
-
* injects a recovery notice into the conversation and resets the
|
|
21
|
-
* counter. Cline's `MistakeTracker.record()` returns
|
|
22
|
-
* `{ action: "continue", guidance: "..." }` and appends the guidance
|
|
23
|
-
* as a user message, then continues the run.
|
|
24
|
-
*
|
|
25
|
-
* • For infra errors (api_error), stops immediately. Retrying a
|
|
26
|
-
* network/provider failure is wasted work.
|
|
27
|
-
*
|
|
28
|
-
* This keeps the threshold tight (model discipline matters) while
|
|
29
|
-
* preventing a single bad turn from killing a long-running task.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
import type {
|
|
33
|
-
ConsecutiveMistakeLimitContext,
|
|
34
|
-
ConsecutiveMistakeLimitDecision,
|
|
35
|
-
} from "@cline/shared";
|
|
36
|
-
|
|
37
|
-
const RECOVERABLE_REASONS = new Set<ConsecutiveMistakeLimitContext["reason"]>([
|
|
38
|
-
"invalid_tool_call",
|
|
39
|
-
"tool_execution_failed",
|
|
40
|
-
]);
|
|
41
|
-
|
|
42
|
-
export interface MistakeRecoveryOptions {
|
|
43
|
-
/**
|
|
44
|
-
* Called after the recovery notice is appended but before the run
|
|
45
|
-
* continues. Receives the full context for observability.
|
|
46
|
-
*/
|
|
47
|
-
onRecovery?: (ctx: ConsecutiveMistakeLimitContext, guidance: string) => void;
|
|
48
|
-
/**
|
|
49
|
-
* Override the recovery guidance text. Defaults to `BUILTIN_RECOVERY_GUIDANCE`.
|
|
50
|
-
*/
|
|
51
|
-
guidance?: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* The default guidance appended to the conversation when consecutive
|
|
56
|
-
* recoverable mistakes trip the limit. Kept terse on purpose — longer
|
|
57
|
-
* text dilutes model attention.
|
|
58
|
-
*/
|
|
59
|
-
export const BUILTIN_RECOVERY_GUIDANCE = [
|
|
60
|
-
"You reached the consecutive-mistake limit. Before retrying:",
|
|
61
|
-
"",
|
|
62
|
-
"1. Re-read the failing tool's input schema. Every required field MUST be",
|
|
63
|
-
" populated with a non-null value of the right type.",
|
|
64
|
-
"2. For `editor`: `new_text` must be a non-empty string. If you intended",
|
|
65
|
-
" to delete content, set `new_text` to \"\" (empty string) and use",
|
|
66
|
-
" `applyPatch` or `delete_file` instead.",
|
|
67
|
-
"3. For `ask_question`: `options` must be an array of ≥2 non-empty",
|
|
68
|
-
" strings. Do not pass null, undefined, or arrays of length < 2.",
|
|
69
|
-
"4. For `run_commands`: `commands` must be a non-empty array of strings.",
|
|
70
|
-
"5. If you have made two mistakes on the SAME tool, switch tools or stop",
|
|
71
|
-
" and explain in plain text what went wrong instead of retrying.",
|
|
72
|
-
"",
|
|
73
|
-
"Acknowledge this notice and continue.",
|
|
74
|
-
].join("\n");
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Default stop reason when an unrecoverable mistake hits the limit.
|
|
78
|
-
*/
|
|
79
|
-
export const BUILTIN_STOP_REASON = "consecutive api_errors — stopping without retry";
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Build an `onConsecutiveMistakeLimitReached` callback.
|
|
83
|
-
*
|
|
84
|
-
* Pass the result directly to `cline.start({ config: { onConsecutiveMistakeLimitReached } })`.
|
|
85
|
-
*/
|
|
86
|
-
export function buildMistakeRecovery(
|
|
87
|
-
opts: MistakeRecoveryOptions = {},
|
|
88
|
-
): (ctx: ConsecutiveMistakeLimitContext) => Promise<ConsecutiveMistakeLimitDecision> | ConsecutiveMistakeLimitDecision {
|
|
89
|
-
const guidance = opts.guidance ?? BUILTIN_RECOVERY_GUIDANCE;
|
|
90
|
-
const onRecovery = opts.onRecovery;
|
|
91
|
-
return (ctx) => {
|
|
92
|
-
if (RECOVERABLE_REASONS.has(ctx.reason)) {
|
|
93
|
-
onRecovery?.(ctx, guidance);
|
|
94
|
-
return { action: "continue", guidance };
|
|
95
|
-
}
|
|
96
|
-
return { action: "stop", reason: BUILTIN_STOP_REASON };
|
|
97
|
-
};
|
|
98
|
-
}
|