@prestyj/cli 5.28.1 → 5.29.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/assets/motion/bin/contact-sheet.mjs +9 -3
- package/assets/motion/bin/cues.mjs +337 -0
- package/assets/motion/bin/library.mjs +53 -10
- package/assets/motion/bin/motion-blur.mjs +943 -0
- package/assets/motion/bin/motion-check.mjs +354 -3
- package/assets/motion/bin/music-fit.mjs +436 -0
- package/assets/motion/bin/pdf-extract.mjs +22 -10
- package/assets/motion/bin/reference-study.mjs +346 -0
- package/assets/motion/bin/score-synth.mjs +1052 -93
- package/assets/motion/library/README.md +50 -14
- package/assets/motion/library/kit/moves.js +1981 -0
- package/assets/motion/library/library.json +232 -0
- package/assets/motion/library/pieces/camera-rig/meta.json +13 -0
- package/assets/motion/library/pieces/camera-rig/piece.html +153 -0
- package/assets/motion/library/pieces/camera-rig/preview.jpg +0 -0
- package/assets/motion/library/pieces/chain-knock/meta.json +13 -0
- package/assets/motion/library/pieces/chain-knock/piece.html +195 -0
- package/assets/motion/library/pieces/chain-knock/preview.jpg +0 -0
- package/assets/motion/library/pieces/gather-to-logo/meta.json +13 -0
- package/assets/motion/library/pieces/gather-to-logo/piece.html +159 -0
- package/assets/motion/library/pieces/gather-to-logo/preview.jpg +0 -0
- package/assets/motion/library/pieces/morph-carry/meta.json +13 -0
- package/assets/motion/library/pieces/morph-carry/piece.html +173 -0
- package/assets/motion/library/pieces/morph-carry/preview.jpg +0 -0
- package/assets/motion/library/pieces/one-shape-journey/meta.json +13 -0
- package/assets/motion/library/pieces/one-shape-journey/piece.html +195 -0
- package/assets/motion/library/pieces/one-shape-journey/preview.jpg +0 -0
- package/assets/motion/library/pieces/open-from-subject/meta.json +13 -0
- package/assets/motion/library/pieces/open-from-subject/piece.html +168 -0
- package/assets/motion/library/pieces/open-from-subject/preview.jpg +0 -0
- package/assets/motion/library/pieces/request-to-result/meta.json +13 -0
- package/assets/motion/library/pieces/request-to-result/piece.html +212 -0
- package/assets/motion/library/pieces/request-to-result/preview.jpg +0 -0
- package/assets/motion/library/pieces/scale-dive/meta.json +13 -0
- package/assets/motion/library/pieces/scale-dive/piece.html +321 -0
- package/assets/motion/library/pieces/scale-dive/preview.jpg +0 -0
- package/assets/motion/library/pieces/screen-replica-steps/meta.json +13 -0
- package/assets/motion/library/pieces/screen-replica-steps/piece.html +366 -0
- package/assets/motion/library/pieces/screen-replica-steps/preview.jpg +0 -0
- package/assets/motion/library/pieces/zoom-into-card/meta.json +13 -0
- package/assets/motion/library/pieces/zoom-into-card/piece.html +179 -0
- package/assets/motion/library/pieces/zoom-into-card/preview.jpg +0 -0
- package/assets/motion/library/sheets/diagram.jpg +0 -0
- package/assets/motion/library/sheets/frame.jpg +0 -0
- package/assets/motion/library/sheets/transition.jpg +0 -0
- package/assets/motion/library/sheets/ui.jpg +0 -0
- package/assets/motion/references/build-sheet.md +206 -0
- package/assets/motion/references/runtime/determinism-rules.md +1 -1
- package/assets/motion/references/runtime/gsap-easing-and-stagger.md +29 -29
- package/assets/motion/references/runtime/inputs-and-assets.md +7 -12
- package/assets/motion/references/runtime/lint-validate-inspect.md +3 -3
- package/assets/motion/references/runtime/minimal-composition.md +1 -1
- package/assets/motion/references/runtime/preview-render.md +3 -3
- package/assets/motion/skills/app-walkthrough/SKILL.md +66 -0
- package/assets/motion/skills/before-after/SKILL.md +53 -0
- package/assets/motion/skills/brand-kit/SKILL.md +3 -3
- package/assets/motion/skills/dev-tool-video/SKILL.md +57 -0
- package/assets/motion/skills/launch-video/SKILL.md +62 -0
- package/assets/motion/skills/match-reference/SKILL.md +58 -0
- package/assets/motion/skills/motion/SKILL.md +78 -85
- package/assets/motion/skills/source-ingest/SKILL.md +17 -6
- package/assets/motion/skills/website-video/SKILL.md +59 -0
- package/assets/skills/bulletproof/SKILL.md +36 -11
- package/assets/skills/bulletproof/references/agent-surface.md +19 -9
- package/assets/skills/bulletproof/references/audit-protocol.md +20 -5
- package/assets/skills/bulletproof/references/platform-playbooks.md +5 -4
- package/assets/skills/bulletproof/references/provenance.md +26 -1
- package/assets/skills/bulletproof/references/secure-defaults.md +6 -5
- package/assets/skills/bulletproof/references/supply-chain.md +21 -17
- package/assets/skills/bulletproof/references/threat-landscape.md +28 -26
- package/assets/skills/bulletproof/references/verification.md +2 -0
- package/assets/skills/clarify/SKILL.md +25 -16
- package/assets/skills/code-review/SKILL.md +71 -13
- package/assets/skills/code-review/references/agent-diffs.md +27 -0
- package/assets/skills/code-review/references/tests.md +19 -0
- package/assets/skills/compliance-guard/SKILL.md +20 -5
- package/assets/skills/compliance-guard/references/artifacts.md +1 -1
- package/assets/skills/compliance-guard/references/eu-uk.md +16 -16
- package/assets/skills/compliance-guard/references/lawsuit-vectors.md +5 -5
- package/assets/skills/compliance-guard/references/provenance.md +41 -2
- package/assets/skills/compliance-guard/references/sector-gates.md +3 -3
- package/assets/skills/compliance-guard/references/security-baseline.md +2 -2
- package/assets/skills/compliance-guard/references/trigger-map.md +5 -5
- package/assets/skills/compliance-guard/references/us.md +27 -21
- package/assets/skills/durable/SKILL.md +87 -79
- package/assets/skills/durable/references/agent-db-safety.md +69 -0
- package/assets/skills/durable/references/backups-and-runtime.md +19 -12
- package/assets/skills/durable/references/migrations-and-schema.md +13 -6
- package/assets/skills/evidence-led-ui/SKILL.md +69 -127
- package/assets/skills/evidence-led-ui/references/anti-defaults.md +107 -208
- package/assets/skills/evidence-led-ui/references/direction.md +124 -0
- package/assets/skills/evidence-led-ui/references/production-contract.md +8 -0
- package/assets/skills/evidence-led-ui/references/provenance.md +24 -1
- package/assets/skills/lean/SKILL.md +90 -71
- package/assets/skills/lean/references/memory-and-processes.md +3 -2
- package/assets/skills/lean/references/playbooks.md +37 -12
- package/assets/skills/refactoring/SKILL.md +24 -3
- package/assets/skills/refactoring/references/agent-pitfalls.md +4 -1
- package/assets/skills/refactoring/references/legacy.md +21 -0
- package/assets/skills/root-cause/SKILL.md +20 -10
- package/assets/skills/shared-language/SKILL.md +16 -14
- package/assets/skills/tdd/SKILL.md +27 -15
- package/dist/app-sidecar.js +203 -47
- package/dist/app-sidecar.js.map +1 -1
- package/dist/cli.js +17 -26
- package/dist/cli.js.map +1 -1
- package/dist/core/acceptance-checks.d.ts +48 -0
- package/dist/core/acceptance-checks.js +144 -0
- package/dist/core/acceptance-checks.js.map +1 -0
- package/dist/core/agent-session.d.ts +106 -72
- package/dist/core/agent-session.js +539 -399
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agents.d.ts +6 -5
- package/dist/core/agents.js.map +1 -1
- package/dist/core/ask-user.d.ts +90 -8
- package/dist/core/ask-user.js +124 -13
- package/dist/core/ask-user.js.map +1 -1
- package/dist/core/bundled-agents.js +1 -3
- package/dist/core/bundled-agents.js.map +1 -1
- package/dist/core/cache-diagnostics.d.ts +68 -0
- package/dist/core/cache-diagnostics.js +196 -0
- package/dist/core/cache-diagnostics.js.map +1 -0
- package/dist/core/cache-expiry.d.ts +87 -0
- package/dist/core/cache-expiry.js +111 -0
- package/dist/core/cache-expiry.js.map +1 -0
- package/dist/core/compaction/compactor.js +78 -48
- package/dist/core/compaction/compactor.js.map +1 -1
- package/dist/core/compaction/plan-step-policy.d.ts +46 -0
- package/dist/core/compaction/plan-step-policy.js +57 -0
- package/dist/core/compaction/plan-step-policy.js.map +1 -0
- package/dist/core/destructive-git-guard.d.ts +90 -0
- package/dist/core/destructive-git-guard.js +871 -0
- package/dist/core/destructive-git-guard.js.map +1 -0
- package/dist/core/event-bus.d.ts +3 -0
- package/dist/core/event-bus.js +5 -0
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/injection-detect.d.ts +38 -0
- package/dist/core/injection-detect.js +232 -0
- package/dist/core/injection-detect.js.map +1 -0
- package/dist/core/keep-awake.d.ts +88 -0
- package/dist/core/keep-awake.js +251 -0
- package/dist/core/keep-awake.js.map +1 -0
- package/dist/core/mcp/client.d.ts +72 -0
- package/dist/core/mcp/client.js +264 -41
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/content.js +6 -2
- package/dist/core/mcp/content.js.map +1 -1
- package/dist/core/mcp/store.d.ts +6 -1
- package/dist/core/mcp/store.js +12 -1
- package/dist/core/mcp/store.js.map +1 -1
- package/dist/core/mcp/types.d.ts +18 -0
- package/dist/core/model-unavailable.d.ts +14 -0
- package/dist/core/model-unavailable.js +23 -0
- package/dist/core/model-unavailable.js.map +1 -0
- package/dist/core/node-debugger.d.ts +148 -0
- package/dist/core/node-debugger.js +642 -0
- package/dist/core/node-debugger.js.map +1 -0
- package/dist/core/package-threats.d.ts +18 -0
- package/dist/core/package-threats.js +168 -0
- package/dist/core/package-threats.js.map +1 -0
- package/dist/core/persistent-shell.d.ts +58 -6
- package/dist/core/persistent-shell.js +331 -49
- package/dist/core/persistent-shell.js.map +1 -1
- package/dist/core/process-manager.d.ts +14 -0
- package/dist/core/process-manager.js +61 -0
- package/dist/core/process-manager.js.map +1 -1
- package/dist/core/progress/git-xp.js +8 -14
- package/dist/core/progress/git-xp.js.map +1 -1
- package/dist/core/session-history.d.ts +12 -0
- package/dist/core/session-history.js +27 -0
- package/dist/core/session-history.js.map +1 -1
- package/dist/core/session-manager.d.ts +13 -1
- package/dist/core/session-manager.js +38 -18
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-summary-index.d.ts +37 -0
- package/dist/core/session-summary-index.js +172 -0
- package/dist/core/session-summary-index.js.map +1 -0
- package/dist/core/settings-manager.d.ts +2 -0
- package/dist/core/settings-manager.js +10 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/shell-threats-popular-packages.d.ts +11 -0
- package/dist/core/shell-threats-popular-packages.js +675 -0
- package/dist/core/shell-threats-popular-packages.js.map +1 -0
- package/dist/core/shell-threats.d.ts +8 -0
- package/dist/core/shell-threats.js +186 -0
- package/dist/core/shell-threats.js.map +1 -0
- package/dist/core/skills.js +3 -1
- package/dist/core/skills.js.map +1 -1
- package/dist/core/stream-rules.d.ts +30 -0
- package/dist/core/stream-rules.js +151 -0
- package/dist/core/stream-rules.js.map +1 -0
- package/dist/core/subagent-manager.d.ts +20 -5
- package/dist/core/subagent-manager.js +22 -8
- package/dist/core/subagent-manager.js.map +1 -1
- package/dist/core/subagent-receipt.d.ts +54 -0
- package/dist/core/subagent-receipt.js +276 -0
- package/dist/core/subagent-receipt.js.map +1 -0
- package/dist/core/subagent-turn-record.d.ts +2 -0
- package/dist/core/subagent-turn-record.js.map +1 -1
- package/dist/core/test-impact.d.ts +73 -0
- package/dist/core/test-impact.js +467 -0
- package/dist/core/test-impact.js.map +1 -0
- package/dist/core/thinking-level.d.ts +1 -1
- package/dist/core/thinking-level.js +1 -1
- package/dist/core/thinking-level.js.map +1 -1
- package/dist/core/verification-gate.d.ts +2 -0
- package/dist/core/verification-gate.js +4 -0
- package/dist/core/verification-gate.js.map +1 -1
- package/dist/core/verification-snapshot.js +3 -5
- package/dist/core/verification-snapshot.js.map +1 -1
- package/dist/core/workspace-guard.d.ts +18 -7
- package/dist/core/workspace-guard.js +227 -60
- package/dist/core/workspace-guard.js.map +1 -1
- package/dist/interactive.js +2 -1
- package/dist/interactive.js.map +1 -1
- package/dist/modes/subagent-worker-mode.js +34 -6
- package/dist/modes/subagent-worker-mode.js.map +1 -1
- package/dist/motion-agent/motion-agent.d.ts +6 -2
- package/dist/motion-agent/motion-agent.js +5 -7
- package/dist/motion-agent/motion-agent.js.map +1 -1
- package/dist/motion-agent/motion-prompt.d.ts +1 -1
- package/dist/motion-agent/motion-prompt.js +14 -19
- package/dist/motion-agent/motion-prompt.js.map +1 -1
- package/dist/motion-agent/motion-review.d.ts +10 -3
- package/dist/motion-agent/motion-review.js +15 -7
- package/dist/motion-agent/motion-review.js.map +1 -1
- package/dist/motion-agent/motion-studio-context.js +1 -1
- package/dist/motion-agent/motion-studio-context.js.map +1 -1
- package/dist/system-prompt.js +3 -1
- package/dist/system-prompt.js.map +1 -1
- package/dist/test-support/keep-alive.d.ts +14 -0
- package/dist/test-support/keep-alive.js +17 -0
- package/dist/test-support/keep-alive.js.map +1 -0
- package/dist/tools/ask-user.js +3 -3
- package/dist/tools/ask-user.js.map +1 -1
- package/dist/tools/bash-read-evidence.d.ts +10 -0
- package/dist/tools/bash-read-evidence.js +133 -0
- package/dist/tools/bash-read-evidence.js.map +1 -0
- package/dist/tools/bash.d.ts +10 -1
- package/dist/tools/bash.js +115 -7
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/debug.d.ts +54 -0
- package/dist/tools/debug.js +233 -0
- package/dist/tools/debug.js.map +1 -0
- package/dist/tools/edit.js +12 -4
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/goals.d.ts +1 -1
- package/dist/tools/index.d.ts +19 -2
- package/dist/tools/index.js +47 -7
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/prompt-hints.js +2 -0
- package/dist/tools/prompt-hints.js.map +1 -1
- package/dist/tools/read-tracker.d.ts +5 -0
- package/dist/tools/read-tracker.js +19 -9
- package/dist/tools/read-tracker.js.map +1 -1
- package/dist/tools/read.js +3 -2
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/skill.js +5 -0
- package/dist/tools/skill.js.map +1 -1
- package/dist/tools/subagent-control.js +44 -8
- package/dist/tools/subagent-control.js.map +1 -1
- package/dist/tools/subagent-shared.d.ts +29 -8
- package/dist/tools/subagent-shared.js +45 -14
- package/dist/tools/subagent-shared.js.map +1 -1
- package/dist/tools/subagent.d.ts +8 -2
- package/dist/tools/subagent.js +28 -10
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/task-output.js +3 -2
- package/dist/tools/task-output.js.map +1 -1
- package/dist/tools/task-send.d.ts +1 -1
- package/dist/tools/task-send.js +15 -1
- package/dist/tools/task-send.js.map +1 -1
- package/dist/tools/tool-tiers.d.ts +2 -2
- package/dist/tools/tool-tiers.js +3 -2
- package/dist/tools/tool-tiers.js.map +1 -1
- package/dist/tools/truncate.d.ts +21 -0
- package/dist/tools/truncate.js +187 -0
- package/dist/tools/truncate.js.map +1 -1
- package/dist/tools/ui-adopt.js +2 -0
- package/dist/tools/ui-adopt.js.map +1 -1
- package/dist/ui/App.d.ts +0 -4
- package/dist/ui/App.js +5 -28
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/ActivityIndicator.js +1 -0
- package/dist/ui/components/ActivityIndicator.js.map +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts +1 -8
- package/dist/ui/hooks/useAgentLoop.js +1 -119
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/render.d.ts +0 -4
- package/dist/ui/render.js +0 -2
- package/dist/ui/render.js.map +1 -1
- package/dist/utils/git.d.ts +77 -0
- package/dist/utils/git.js +285 -21
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/github-ci.js +2 -1
- package/dist/utils/github-ci.js.map +1 -1
- package/dist/utils/github.js +11 -9
- package/dist/utils/github.js.map +1 -1
- package/dist/utils/image.d.ts +14 -0
- package/dist/utils/image.js +16 -0
- package/dist/utils/image.js.map +1 -1
- package/dist/utils/process.d.ts +20 -0
- package/dist/utils/process.js +98 -0
- package/dist/utils/process.js.map +1 -1
- package/package.json +5 -5
- package/assets/motion/references/motion-language.md +0 -128
- package/assets/motion/skills/video-qa/SKILL.md +0 -89
- package/dist/core/ideal-review-subagent.d.ts +0 -56
- package/dist/core/ideal-review-subagent.js +0 -112
- package/dist/core/ideal-review-subagent.js.map +0 -1
- package/dist/core/ideal-review.d.ts +0 -82
- package/dist/core/ideal-review.js +0 -242
- package/dist/core/ideal-review.js.map +0 -1
- package/dist/motion-agent/motion-check-tool.d.ts +0 -35
- package/dist/motion-agent/motion-check-tool.js +0 -514
- package/dist/motion-agent/motion-check-tool.js.map +0 -1
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: shared-language
|
|
3
|
-
description: Use when a project's domain vocabulary is fuzzy or drifting, naming decisions keep recurring, or a hard-to-reverse architectural decision needs recording so it is not re-litigated or re-suggested
|
|
3
|
+
description: Use when a project's domain vocabulary is fuzzy or drifting (two names for one thing, one name for two), naming decisions keep recurring, or a hard-to-reverse architectural decision needs recording so it is not re-litigated or re-suggested — including mid-build when a term settles or a change contradicts an ADR. Do NOT use for throwaway scripts, projects too small to have recurring vocabulary, interviewing for requirements (clarify), or renames with no vocabulary decision (refactoring).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Shared Language
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Two artifacts: the glossary (`CONTEXT.md`) and decision records (`docs/adr/`). On entry, read both if they exist, then act on the trigger: term settled → glossary; hard call made → offer ADR; change contradicts an ADR → surface it.
|
|
9
9
|
|
|
10
10
|
## The glossary — CONTEXT.md
|
|
11
11
|
|
|
12
|
-
At the repo root. **A glossary and nothing else**: term —
|
|
12
|
+
At the repo root. **A glossary and nothing else**: `term — 1–3 line definition`, plus `Avoid: <synonyms>` where drift exists. No file paths, implementation, or history.
|
|
13
13
|
|
|
14
|
-
- Challenge fuzzy
|
|
15
|
-
-
|
|
16
|
-
- Update
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
- On first creation, add one line to the repo's instruction file (AGENTS.md, or CLAUDE.md if that is what the repo uses): `Read CONTEXT.md before naming anything.` CONTEXT.md is not auto-loaded — the pointer is what makes the glossary ambient in every session.
|
|
14
|
+
- Challenge fuzzy usage against it: "glossary says *cancellation* is pre-charge; this reads post-charge — which?"
|
|
15
|
+
- Test a candidate term with an invented edge case before recording it ("is a no-show a cancellation?").
|
|
16
|
+
- Update the moment a term settles; create the file lazily on the first settled term, never as an empty template.
|
|
17
|
+
- Code, tests, and UI use glossary terms verbatim. Code and glossary disagree → one is wrong; ask which.
|
|
18
|
+
- On creation, add to the repo's instruction file (AGENTS.md, or CLAUDE.md if that is what it uses): `Read CONTEXT.md before naming anything.` CONTEXT.md is not auto-loaded — that pointer is what makes it count in later sessions.
|
|
20
19
|
|
|
21
20
|
## Decision records — docs/adr/
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
Only when a decision is **hard to reverse**, **surprising without context**, and **a real tradeoff** — all three. Offer it; don't write unasked.
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
- Follow the repo's existing ADR format. None? Use the MADR 4 minimal shape: `NNNN-title-with-dashes.md` with Context and Problem Statement, Considered Options, Decision Outcome (chosen option + why), Consequences; `status: accepted` in front matter.
|
|
25
|
+
- Immutable once accepted — supersede, never edit: new ADR notes `Supersedes NNNN`; the old one's status becomes `superseded by NNNN` (the only allowed change).
|
|
26
|
+
- Before proposing a change that contradicts an ADR: honour it or raise the conflict. Never re-suggest a rejected option without new facts.
|
|
26
27
|
|
|
27
|
-
##
|
|
28
|
+
## Scaling: one agent or several
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
Main thread only. On a large repo, one `owl` may inventory competing terms across packages (occurrences with file:line); the main thread decides and edits.
|
|
31
|
+
|
|
32
|
+
Sources (accessed 3 October 2026): MADR 4.0.0, latest release (2024-09-17) — https://adr.github.io/madr/; ubiquitous language — https://martinfowler.com/bliki/UbiquitousLanguage.html
|
|
@@ -1,30 +1,42 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tdd
|
|
3
|
-
description: Use when the user asks for test-driven development, red-green-refactor,
|
|
3
|
+
description: Use when the user asks for test-driven development, red-green-refactor, "write the test first", or wants a feature or bug fix built test-first — including mid-build when they ask to switch to test-first. Do NOT use when tests are a verification step after the build, the project has no suite and the user has not asked for one, the change is throwaway probe code, or the goal is restructuring without behaviour change (refactoring) or finding an unknown cause (root-cause).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# TDD
|
|
7
7
|
|
|
8
|
-
Red → green, one slice at a time.
|
|
8
|
+
Red → green, one vertical slice at a time. Before the first test: agree seams (below). Then loop.
|
|
9
9
|
|
|
10
10
|
## Seams — agree before writing
|
|
11
11
|
|
|
12
|
-
A **seam** is
|
|
13
|
-
|
|
14
|
-
Before the first test, write down the seams under test and confirm them with the user — which boundaries get tests and which stay untested is a decision, and settling it up front is what keeps effort on critical paths instead of every edge case. No test at an unagreed seam.
|
|
12
|
+
A **seam** is a public boundary where behaviour is observable: exported function, HTTP route, CLI invocation. Tests live at seams, never in internals. List the seams under test and confirm them with the user (one `ask_user` call, recommended set marked). No test at an unagreed seam.
|
|
15
13
|
|
|
16
14
|
## The loop
|
|
17
15
|
|
|
18
|
-
1. **Red.** One failing test at an agreed seam
|
|
19
|
-
2. **Green.**
|
|
20
|
-
3. **Repeat.** The next test
|
|
21
|
-
4. **Refactor
|
|
16
|
+
1. **Red.** One failing test at an agreed seam for the next smallest real behaviour. Run it; confirm it fails for the right reason (assertion, not import/syntax error).
|
|
17
|
+
2. **Green.** Least code that passes. Run the test and the touched suite.
|
|
18
|
+
3. **Repeat.** The next test follows from what the last cycle taught. Never write all tests first, then all code.
|
|
19
|
+
4. **Refactor** only on green, as a separate step; rerun tests after.
|
|
20
|
+
|
|
21
|
+
## Test integrity (agent guard)
|
|
22
|
+
|
|
23
|
+
- **Tests are frozen during green.** Never edit, delete, skip, loosen, or `.only` a test to make it pass. If a test is wrong, stop, say why, and get agreement before changing it.
|
|
24
|
+
- Never special-case test inputs in production code (hard-coded returns for the test's literal).
|
|
25
|
+
- Before claiming done, `git diff` the test files; explain any change made during a green step.
|
|
26
|
+
|
|
27
|
+
## Test quality
|
|
28
|
+
|
|
29
|
+
- **Expected values from an outside source of truth** — a known literal, worked example, or the spec. Never recompute the expected value the way the code does.
|
|
30
|
+
- **Behaviour, not structure.** A test that breaks under a behaviour-preserving refactor is coupled to internals; rewrite it at the seam.
|
|
31
|
+
- **Mock only external boundaries** (network, clock, filesystem), and only when slow or stateful.
|
|
32
|
+
- **Name like a spec**: "user can check out with an empty cart".
|
|
33
|
+
- **Property tests for rules over many inputs** (parsers, round-trips, invariants) when the project already has a property library (fast-check, Hypothesis, proptest); otherwise a table of examples.
|
|
34
|
+
- **Optional strength check:** if a mutation tool is already installed (Stryker, mutmut, cargo-mutants), run it on the changed module; surviving mutants point to missing assertions. Do not install one unasked. Manual alternative: break one line of the new code, confirm a test goes red, revert.
|
|
35
|
+
|
|
36
|
+
Never claim a cycle green that you did not run.
|
|
22
37
|
|
|
23
|
-
##
|
|
38
|
+
## Scaling: one agent or several
|
|
24
39
|
|
|
25
|
-
|
|
26
|
-
- **Behavior, not structure.** Assert on what the interface does. A test that breaks under a refactor with unchanged behavior is coupled to implementation — rewrite it at the seam, never patch it with mocks of internals.
|
|
27
|
-
- **Real paths over mocks.** Exercise real code; mock only external boundaries (network, clock, filesystem) and only when they are slow or stateful.
|
|
28
|
-
- **Named like a spec.** "user can check out with an empty cart" — the name alone states the capability.
|
|
40
|
+
Main thread only — the loop is sequential by design. Never parallelise red/green steps or let a child edit tests.
|
|
29
41
|
|
|
30
|
-
|
|
42
|
+
Sources (accessed 3 October 2026): https://code.claude.com/docs/en/best-practices (failing test before the fix); https://stryker-mutator.io/docs/ (killed vs surviving mutants); https://hypothesis.readthedocs.io/
|
package/dist/app-sidecar.js
CHANGED
|
@@ -39,11 +39,12 @@ import { isWorkflowCommandText, countAssistantMessages, shouldStartAutopilotCycl
|
|
|
39
39
|
import { driveAutopilotCycle, frameAutopilotInjection } from "./core/autopilot-cycle.js";
|
|
40
40
|
import { describeRunVerification, describeTurnVerification } from "./core/run-status.js";
|
|
41
41
|
import { validateNolanModelPref, effectiveNolanModel, } from "./core/nolan-model.js";
|
|
42
|
-
import { normalizeAutopilotMarkersForHistory, normalizeAppMarkersForHistory, normalizeNolanTurnsForHistory, getHistoryMessageVisibility, replayMessagesInOrder, restoreUserRow, restoreAssistantTexts, resolveRestoredCommand, autopilotMarkerCopySeed, } from "./core/session-history.js";
|
|
42
|
+
import { normalizeAutopilotMarkersForHistory, normalizeAppMarkersForHistory, normalizeNolanTurnsForHistory, getHistoryMessageVisibility, replayMessagesInOrder, restoreUserRow, restoreAssistantTexts, resolveRestoredCommand, autopilotMarkerCopySeed, extractToolImagePaths, } from "./core/session-history.js";
|
|
43
43
|
import { sessionToMarkdown, defaultExportFilename, } from "./core/session-export.js";
|
|
44
44
|
import { AuthStorage } from "./core/auth-storage.js";
|
|
45
45
|
import { advancesPhase, explainPullFailure, isGgufShard, isValidHfRepoId, parseOllamaPullLine, pickGgufQuant, SHARDED_MESSAGE, toHfSearchRow, } from "./hf-pull.js";
|
|
46
46
|
import { cleanupToolOutputs } from "./tools/overflow.js";
|
|
47
|
+
import { spawnedTasks } from "./tools/subagent-shared.js";
|
|
47
48
|
import { readCappedBody } from "./utils/http-body.js";
|
|
48
49
|
import { fetchSubscriptionUsage, SubscriptionUsageError, XIAOMI_CREDITS_KEY, dualAuthProvider, oauthStorageKey, discoverLocalModels, findProbedModel, formatLocalModelId, parseLocalModelId, probeEndpoint, toModelInfo as localModelInfo, } from "@prestyj/core";
|
|
49
50
|
import { LocalEndpointError, addCustomEndpoint, listAllEndpoints, removeCustomEndpoint, syncEndpointCredentials, } from "./core/local-endpoint-store.js";
|
|
@@ -70,20 +71,25 @@ import { listSidecarSessions } from "./app-sidecar-sessions.js";
|
|
|
70
71
|
import { loadTasksSync, saveTasksSync, pruneDoneTasksSync, getNextPendingTask, markTaskInProgress, } from "./core/task-store.js";
|
|
71
72
|
import { initLogger, log } from "./core/logger.js";
|
|
72
73
|
import { installTerminationHandlers } from "./core/shutdown.js";
|
|
74
|
+
import { KeepAwake } from "./core/keep-awake.js";
|
|
73
75
|
import { RADIO_STATIONS, getCurrentStation, getRadioVolume, playRadio, setRadioVolume, stopRadio, } from "./core/radio.js";
|
|
74
76
|
import { enrichProcessPath } from "./core/shell-path.js";
|
|
75
|
-
import { downscaleForPreview, shrinkToFit, validateVisionImage } from "./utils/image.js";
|
|
77
|
+
import { downscaleForPreview, IMAGE_MEDIA_TYPES, shrinkToFit, validateVisionImage, } from "./utils/image.js";
|
|
78
|
+
import { readFileBounded } from "./tools/operations.js";
|
|
76
79
|
import { startServeMode } from "./modes/serve-mode.js";
|
|
77
80
|
import { installSteroids, probeSteroids } from "./core/steroids.js";
|
|
78
81
|
import { loadTelegramConfig, saveTelegramConfig, verifyBotToken } from "./core/telegram-config.js";
|
|
79
82
|
import { loadServers, addServer, removeServer, getServer, parseMcpAddCommand, MCPClientManager, McpOAuthStore, createElicitationBridge, } from "./core/mcp/index.js";
|
|
80
|
-
import { createAskUserBridge } from "./core/ask-user.js";
|
|
83
|
+
import { askSoftDeadlineMs, createAskUserBridge, deliverLateAnswer, } from "./core/ask-user.js";
|
|
81
84
|
import { createAskUserTool } from "./tools/ask-user.js";
|
|
82
85
|
import { buildSnapshot, levelForXp, rankForLevel } from "./core/progress/ranks.js";
|
|
83
86
|
import { loadProgress, peekProgress, updateProgress } from "./core/progress/store.js";
|
|
84
87
|
import { awardPrompt, awardCommits } from "./core/progress/engine.js";
|
|
85
88
|
import { detectNewCommits, repoKey } from "./core/progress/git-xp.js";
|
|
86
89
|
import { rebuildFromSessions } from "./core/progress/rebuild.js";
|
|
90
|
+
/** App-wide idle-sleep guard: every window's runs share one OS assertion. The
|
|
91
|
+
* `keepAwake` setting is applied at daemon start and live via /keep-awake. */
|
|
92
|
+
const keepAwake = new KeepAwake();
|
|
87
93
|
const AUTOMATION_PROVENANCE = {
|
|
88
94
|
source: "runtime",
|
|
89
95
|
kind: "automation",
|
|
@@ -570,6 +576,8 @@ async function main() {
|
|
|
570
576
|
// ~/.ezcoder/debug.log (initLogger truncates on each start).
|
|
571
577
|
const sidecarLog = path.join(paths.agentDir, "ezcoder-app-sidecar.log");
|
|
572
578
|
initLogger(sidecarLog);
|
|
579
|
+
// Apply the saved keepAwake setting before any run can acquire the guard.
|
|
580
|
+
keepAwake.setEnabled((await new SettingsManager(paths.settingsFile).load()).keepAwake);
|
|
573
581
|
// The desktop sidecar previously omitted the stream diagnostic hook used by
|
|
574
582
|
// the CLI, leaving device-specific provider stalls impossible to distinguish from
|
|
575
583
|
// event-loop starvation or a broken streaming network path. Keep routine
|
|
@@ -908,6 +916,40 @@ async function main() {
|
|
|
908
916
|
daemonJson(res, 200, { ok: true });
|
|
909
917
|
return;
|
|
910
918
|
}
|
|
919
|
+
// Keep-awake is app-wide (one OS assertion for every window), so its
|
|
920
|
+
// setting lives at the daemon level and applies live to in-flight runs.
|
|
921
|
+
if (method === "GET" && url === "/keep-awake") {
|
|
922
|
+
daemonJson(res, 200, { enabled: keepAwake.isEnabled });
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
if (method === "POST" && url === "/keep-awake") {
|
|
926
|
+
void daemonReadBody(req, res).then(async (raw) => {
|
|
927
|
+
if (raw === null)
|
|
928
|
+
return;
|
|
929
|
+
let enabled;
|
|
930
|
+
try {
|
|
931
|
+
enabled = JSON.parse(raw).enabled;
|
|
932
|
+
}
|
|
933
|
+
catch {
|
|
934
|
+
enabled = undefined;
|
|
935
|
+
}
|
|
936
|
+
if (typeof enabled !== "boolean") {
|
|
937
|
+
daemonJson(res, 400, { error: "enabled must be a boolean" });
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
keepAwake.setEnabled(enabled);
|
|
941
|
+
try {
|
|
942
|
+
const sm = new SettingsManager(paths.settingsFile);
|
|
943
|
+
await sm.load();
|
|
944
|
+
await sm.set("keepAwake", enabled);
|
|
945
|
+
}
|
|
946
|
+
catch (err) {
|
|
947
|
+
log("WARN", "app-sidecar", "failed to persist keepAwake", { err: String(err) });
|
|
948
|
+
}
|
|
949
|
+
daemonJson(res, 200, { enabled });
|
|
950
|
+
});
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
911
953
|
// Progress is daemon-level so the Home screen can paint before a project
|
|
912
954
|
// session exists; per-session callers still work through the same endpoint.
|
|
913
955
|
if (method === "GET" && url === "/progress") {
|
|
@@ -955,9 +997,16 @@ async function main() {
|
|
|
955
997
|
const shutdown = installTerminationHandlers({
|
|
956
998
|
scope: "app-sidecar",
|
|
957
999
|
teardown: async () => {
|
|
1000
|
+
// Before any await: background commands live outside the daemon's process
|
|
1001
|
+
// group, and the app force-kills that group after 3 s. If a slow session
|
|
1002
|
+
// teardown is still pending then, these would never be stopped.
|
|
1003
|
+
for (const c of sessions.values())
|
|
1004
|
+
c.stopBackgroundProcesses();
|
|
958
1005
|
clearInterval(parentWatch);
|
|
959
1006
|
// Radio playback is app-wide (one stream across all windows).
|
|
960
1007
|
stopRadio();
|
|
1008
|
+
// Drop the idle-sleep assertion first: session teardown may hang.
|
|
1009
|
+
keepAwake.dispose();
|
|
961
1010
|
// Close the ~/.ezcoder progress fs.watch handle.
|
|
962
1011
|
progress.dispose();
|
|
963
1012
|
await Promise.all([...sessions.values()].map((c) => c.dispose().catch(() => { })));
|
|
@@ -1312,9 +1361,33 @@ async function createSession(deps, opts) {
|
|
|
1312
1361
|
// MCP bridge above: broadcast over SSE, resolved when the webview POSTs
|
|
1313
1362
|
// /ask/:id. Registered ONLY here — a TUI/headless/subagent run has nobody to
|
|
1314
1363
|
// answer, so the tool is absent there rather than hanging on a dead channel.
|
|
1364
|
+
//
|
|
1365
|
+
// Soft deadline ("async ask"): past it the tool returns "no answer yet —
|
|
1366
|
+
// proceed on your best guess" and the question STAYS OPEN. A later answer
|
|
1367
|
+
// rides the ordinary user queue: steering into a live run, or the next turn
|
|
1368
|
+
// when idle. The deadline is short when nobody is watching (autopilot, task
|
|
1369
|
+
// run-all, a scheduled prompt).
|
|
1370
|
+
let scheduledRunActive = false;
|
|
1315
1371
|
const asks = createAskUserBridge({
|
|
1316
1372
|
broadcast: (prompt) => broadcast("ask_user", prompt),
|
|
1317
|
-
|
|
1373
|
+
timeoutMs: () => askSoftDeadlineMs(autopilot || autopilotActive || taskRunAll || scheduledRunActive),
|
|
1374
|
+
onTimeout: (prompt) => {
|
|
1375
|
+
log("WARN", "app-sidecar", "ask_user deadline passed; agent proceeding", { id: prompt.id });
|
|
1376
|
+
broadcast("ask_user_deferred", { id: prompt.id });
|
|
1377
|
+
},
|
|
1378
|
+
onClosed: (ids) => broadcast("ask_user_closed", { ids }),
|
|
1379
|
+
onLateAnswer: (late) => {
|
|
1380
|
+
log("INFO", "app-sidecar", "ask_user late answer queued", { id: late.prompt.id });
|
|
1381
|
+
deliverLateAnswer(late, {
|
|
1382
|
+
queueMessage: (text) => session.queueMessage(text),
|
|
1383
|
+
isBusy: () => running || runClaim.active || autopilotActive,
|
|
1384
|
+
onQueued: () => broadcast("queued", {
|
|
1385
|
+
count: session.getQueuedCount(),
|
|
1386
|
+
messages: session.listQueuedMessages(),
|
|
1387
|
+
}),
|
|
1388
|
+
startIdleRun: () => void runStrandedQueue(),
|
|
1389
|
+
});
|
|
1390
|
+
},
|
|
1318
1391
|
});
|
|
1319
1392
|
const askUserTool = createAskUserTool(asks.park);
|
|
1320
1393
|
// The session file path to resume (passed by the daemon's POST /session);
|
|
@@ -1867,11 +1940,14 @@ async function createSession(deps, opts) {
|
|
|
1867
1940
|
if (!d.silent)
|
|
1868
1941
|
broadcast("retry", { reason: d.reason, attempt: d.attempt, delayMs: d.delayMs });
|
|
1869
1942
|
});
|
|
1943
|
+
session.eventBus.on("stream_rule_triggered", (d) => broadcast("stream_rule_triggered", { rules: d.rules, source: d.source, toolName: d.toolName }));
|
|
1870
1944
|
session.eventBus.on("max_turns", (d) => broadcast("max_turns", d));
|
|
1871
1945
|
// The agent consumed queued steering at a turn boundary. Re-broadcast as the
|
|
1872
1946
|
// usual `queued` depth update so the webview drops the pending affordance the
|
|
1873
1947
|
// moment the message lands in the loop, not at run_end.
|
|
1874
1948
|
session.eventBus.on("queue_drained", (d) => broadcast("queued", { count: d.count, messages: session.listQueuedMessages() }));
|
|
1949
|
+
// A fresh/loaded session must not receive late answers to the old one's questions.
|
|
1950
|
+
session.eventBus.on("session_start", () => asks.closeDeferred());
|
|
1875
1951
|
session.eventBus.on("tool_call_start", (d) => {
|
|
1876
1952
|
toolCallNames.set(d.toolCallId, d.name);
|
|
1877
1953
|
broadcast("tool_call_start", d);
|
|
@@ -1931,12 +2007,38 @@ async function createSession(deps, opts) {
|
|
|
1931
2007
|
session.eventBus.on("hook", (d) => broadcast("hook", d));
|
|
1932
2008
|
session.eventBus.on("diagnostics", (d) => broadcast("diagnostics", d));
|
|
1933
2009
|
// Fires BEFORE the candidate final answer streams. The webview holds assistant
|
|
1934
|
-
// text back while armed, so an
|
|
2010
|
+
// text back while armed, so an injected follow-up supersedes a draft that was never
|
|
1935
2011
|
// painted instead of deleting one the user already started reading.
|
|
1936
2012
|
session.eventBus.on("hook_armed", (d) => broadcast("hook_armed", d));
|
|
1937
2013
|
session.eventBus.on("subagent_state", (d) => broadcast("subagent_state", d));
|
|
1938
2014
|
session.eventBus.on("compaction_start", (d) => broadcast("compaction_start", d));
|
|
1939
2015
|
session.eventBus.on("compaction_end", (d) => broadcast("compaction_end", d));
|
|
2016
|
+
// Cold-prompt-cache notice: push the fresh TTL anchor + context size whenever
|
|
2017
|
+
// a run or compaction settles. Expiry itself is time-based, so the webview
|
|
2018
|
+
// also re-reads `cacheExpiry` from /state when the user returns or types.
|
|
2019
|
+
for (const ev of ["agent_done", "compaction_end", "model_change"]) {
|
|
2020
|
+
session.eventBus.on(ev, () => broadcast("cache_expiry", session.getCacheExpiryStatus()));
|
|
2021
|
+
}
|
|
2022
|
+
// Keep the computer awake while this window's agent works: owned runs and
|
|
2023
|
+
// autopilot cycles (RunLifecycle state), Nolan replies, and background
|
|
2024
|
+
// sub-agents that outlive their parent run. Released on settle (completed,
|
|
2025
|
+
// failed or cancelled) and on session dispose.
|
|
2026
|
+
let releaseRunAwake = null;
|
|
2027
|
+
let releaseSubagentAwake = null;
|
|
2028
|
+
const activeSubagents = new Set();
|
|
2029
|
+
session.eventBus.on("subagent_state", (d) => {
|
|
2030
|
+
if (d.state === "starting" || d.state === "running")
|
|
2031
|
+
activeSubagents.add(d.agent_id);
|
|
2032
|
+
else
|
|
2033
|
+
activeSubagents.delete(d.agent_id);
|
|
2034
|
+
if (activeSubagents.size > 0) {
|
|
2035
|
+
releaseSubagentAwake ??= keepAwake.acquire("subagent");
|
|
2036
|
+
}
|
|
2037
|
+
else {
|
|
2038
|
+
releaseSubagentAwake?.();
|
|
2039
|
+
releaseSubagentAwake = null;
|
|
2040
|
+
}
|
|
2041
|
+
});
|
|
1940
2042
|
let running = false;
|
|
1941
2043
|
// Closes the window between `/prompt` deciding to start a run and `runAgent`
|
|
1942
2044
|
// flipping `running` — that stretch awaits, so Node yields inside it. See
|
|
@@ -1944,6 +2046,13 @@ async function createSession(deps, opts) {
|
|
|
1944
2046
|
const runClaim = new RunClaim();
|
|
1945
2047
|
const runLifecycle = new RunLifecycle((runState) => {
|
|
1946
2048
|
running = runState !== "idle";
|
|
2049
|
+
if (running) {
|
|
2050
|
+
releaseRunAwake ??= keepAwake.acquire("run");
|
|
2051
|
+
}
|
|
2052
|
+
else {
|
|
2053
|
+
releaseRunAwake?.();
|
|
2054
|
+
releaseRunAwake = null;
|
|
2055
|
+
}
|
|
1947
2056
|
if (runState === "cancelling")
|
|
1948
2057
|
broadcast("run_cancelling", { runState });
|
|
1949
2058
|
},
|
|
@@ -1970,10 +2079,8 @@ async function createSession(deps, opts) {
|
|
|
1970
2079
|
// Autopilot (auto-review) toggle for THIS window's project. Loaded from
|
|
1971
2080
|
// ezcoder-app.json on boot; flipped via POST /autopilot. When on, POST /prompt runs
|
|
1972
2081
|
// runAutopilotCycle after the user's turn settles — Nolan auto-reviews the work
|
|
1973
|
-
// and drives the review→prompt→review loop.
|
|
1974
|
-
// owner in this mode, so suppress the build session's redundant Ideal hook.
|
|
2082
|
+
// and drives the review→prompt→review loop.
|
|
1975
2083
|
let autopilot = mode === "code" && (await loadAutopilot(cwd));
|
|
1976
|
-
session.setIdealReviewSuppressed(autopilot);
|
|
1977
2084
|
// True while an autopilot review is in flight (used to defer nolanAuto model
|
|
1978
2085
|
// switches, like nolanRunning does for chat Nolan, and to drive the spinner).
|
|
1979
2086
|
let autopilotReviewing = false;
|
|
@@ -2173,9 +2280,6 @@ async function createSession(deps, opts) {
|
|
|
2173
2280
|
// (often >5 min) regardless of the user's global speedProfile pick.
|
|
2174
2281
|
forceLongCacheRetention: true,
|
|
2175
2282
|
});
|
|
2176
|
-
// Nolan is already the independent autopilot reviewer; recursively running
|
|
2177
|
-
// his own Ideal self-review adds latency and can corrupt the verdict shape.
|
|
2178
|
-
nolanAgent.setIdealReviewSuppressed(true);
|
|
2179
2283
|
await nolanAgent.initialize();
|
|
2180
2284
|
// Keep review text/tools silent; report usage only for whole-task accounting.
|
|
2181
2285
|
nolanAgent.eventBus.on("turn_end", (d) => {
|
|
@@ -2444,7 +2548,6 @@ async function createSession(deps, opts) {
|
|
|
2444
2548
|
const generation = runLifecycle.begin(abortOwnedWork).generation;
|
|
2445
2549
|
pendingCancelDrain = null;
|
|
2446
2550
|
autopilotActive = true;
|
|
2447
|
-
session.setIdealReviewSuppressed(true);
|
|
2448
2551
|
// Generation captured by the last plan review; acceptPlan re-checks it so
|
|
2449
2552
|
// a user Accept/Reject landing mid-review always wins.
|
|
2450
2553
|
let planGenAtReview = -1;
|
|
@@ -2562,7 +2665,6 @@ async function createSession(deps, opts) {
|
|
|
2562
2665
|
}
|
|
2563
2666
|
finally {
|
|
2564
2667
|
autopilotActive = false;
|
|
2565
|
-
session.setIdealReviewSuppressed(autopilot);
|
|
2566
2668
|
finishOwnedGeneration(generation, true, session.getVerificationProblem() ? "unverified" : "completed");
|
|
2567
2669
|
queueMicrotask(() => void runStrandedQueue());
|
|
2568
2670
|
}
|
|
@@ -2861,6 +2963,7 @@ async function createSession(deps, opts) {
|
|
|
2861
2963
|
supportedThinkingLevels: getSupportedThinkingLevels(st.provider, st.model),
|
|
2862
2964
|
supportsVideo: getModel(st.model)?.supportsVideo ?? false,
|
|
2863
2965
|
autopilot,
|
|
2966
|
+
cacheExpiry: session.getCacheExpiryStatus(),
|
|
2864
2967
|
...nolanStatePayload(),
|
|
2865
2968
|
...footerExtras(),
|
|
2866
2969
|
});
|
|
@@ -3340,7 +3443,16 @@ async function createSession(deps, opts) {
|
|
|
3340
3443
|
// Pre-index tool results by toolCallId so we can pair tool calls with
|
|
3341
3444
|
// their results (for sub-agent status + image extraction).
|
|
3342
3445
|
const toolResultMap = new Map();
|
|
3446
|
+
// Tool name per call, so restore only trusts generate_image's own text
|
|
3447
|
+
// when it reads extra images from disk (an MCP server could fake it).
|
|
3448
|
+
const toolNameById = new Map();
|
|
3343
3449
|
for (const msg of messages) {
|
|
3450
|
+
if (msg.role === "assistant" && typeof msg.content !== "string") {
|
|
3451
|
+
for (const c of msg.content) {
|
|
3452
|
+
if (c.type === "tool_call")
|
|
3453
|
+
toolNameById.set(c.id, c.name);
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3344
3456
|
if (msg.role !== "tool")
|
|
3345
3457
|
continue;
|
|
3346
3458
|
for (const tr of msg.content) {
|
|
@@ -3500,31 +3612,43 @@ async function createSession(deps, opts) {
|
|
|
3500
3612
|
const imageBlocks = tr.content.filter((c) => c.type === "image");
|
|
3501
3613
|
if (imageBlocks.length === 0)
|
|
3502
3614
|
continue;
|
|
3503
|
-
// Extract the path from the text block
|
|
3615
|
+
// Extract the path from the text block so the restored image
|
|
3616
|
+
// stays clickable (read / screenshot / generate_image formats).
|
|
3504
3617
|
const textBlock = tr.content.find((c) => c.type === "text" && "text" in c && typeof c.text === "string");
|
|
3505
3618
|
const textContent = textBlock && textBlock.type === "text" ? textBlock.text : "";
|
|
3506
|
-
const
|
|
3507
|
-
|
|
3508
|
-
//
|
|
3619
|
+
const imgPaths = extractToolImagePaths(textContent);
|
|
3620
|
+
// Downscale each image for the webview preview. Paths pair with
|
|
3621
|
+
// image blocks in order (a tool saves one file per image).
|
|
3509
3622
|
const toolImages = [];
|
|
3623
|
+
let blockIndex = 0;
|
|
3510
3624
|
for (const block of imageBlocks) {
|
|
3511
3625
|
if (block.type !== "image")
|
|
3512
3626
|
continue;
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3627
|
+
const imgPath = imgPaths[blockIndex++];
|
|
3628
|
+
const previewBuf = await downscaleForPreview(Buffer.from(block.data, "base64"));
|
|
3629
|
+
toolImages.push({
|
|
3630
|
+
src: `data:${block.mediaType};base64,${previewBuf.toString("base64")}`,
|
|
3631
|
+
path: imgPath,
|
|
3632
|
+
});
|
|
3633
|
+
}
|
|
3634
|
+
// generate_image persists only its first image's pixels; the
|
|
3635
|
+
// rest exist only on disk, so preview them from their files.
|
|
3636
|
+
// Skip silently if a file was moved or deleted since.
|
|
3637
|
+
const extraPaths = toolNameById.get(tr.toolCallId) === "generate_image"
|
|
3638
|
+
? imgPaths.slice(blockIndex)
|
|
3639
|
+
: [];
|
|
3640
|
+
for (const extraPath of extraPaths) {
|
|
3641
|
+
const mediaType = IMAGE_MEDIA_TYPES[path.extname(extraPath).toLowerCase()];
|
|
3642
|
+
if (!mediaType)
|
|
3643
|
+
continue;
|
|
3644
|
+
const raw = await readFileBounded(extraPath).catch(() => null);
|
|
3645
|
+
if (!raw)
|
|
3646
|
+
continue;
|
|
3647
|
+
const previewBuf = await downscaleForPreview(raw);
|
|
3648
|
+
toolImages.push({
|
|
3649
|
+
src: `data:${mediaType};base64,${previewBuf.toString("base64")}`,
|
|
3650
|
+
path: extraPath,
|
|
3651
|
+
});
|
|
3528
3652
|
}
|
|
3529
3653
|
if (toolImages.length > 0) {
|
|
3530
3654
|
history.push({
|
|
@@ -3609,18 +3733,25 @@ async function createSession(deps, opts) {
|
|
|
3609
3733
|
if (msg.role === "assistant" && typeof msg.content !== "string") {
|
|
3610
3734
|
const subagentCalls = msg.content.filter((c) => c.type === "tool_call" && (c.name === "subagent" || c.name === "spawn_agent"));
|
|
3611
3735
|
if (subagentCalls.length > 0) {
|
|
3612
|
-
const agents = subagentCalls.
|
|
3736
|
+
const agents = subagentCalls.flatMap((c) => {
|
|
3613
3737
|
const result = toolResultMap.get(c.id);
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
}
|
|
3738
|
+
// Async workers are intentionally non-resumable; restored rows are historical.
|
|
3739
|
+
const status = result?.isError ? "error" : "done";
|
|
3740
|
+
if (c.name === "spawn_agent") {
|
|
3741
|
+
// One row per child: a batch call starts several.
|
|
3742
|
+
return spawnedTasks(c.args).map((spawn) => ({
|
|
3743
|
+
agentName: spawn.task_name ?? spawn.agent,
|
|
3744
|
+
status,
|
|
3745
|
+
toolUseCount: 0,
|
|
3746
|
+
}));
|
|
3747
|
+
}
|
|
3748
|
+
return [
|
|
3749
|
+
{
|
|
3750
|
+
agentName: typeof c.args?.agent === "string" ? c.args.agent : undefined,
|
|
3751
|
+
status,
|
|
3752
|
+
toolUseCount: 0,
|
|
3753
|
+
},
|
|
3754
|
+
];
|
|
3624
3755
|
});
|
|
3625
3756
|
history.push({
|
|
3626
3757
|
role: "assistant",
|
|
@@ -3754,6 +3885,8 @@ async function createSession(deps, opts) {
|
|
|
3754
3885
|
// Claim the run NOW, synchronously. Everything below this line may
|
|
3755
3886
|
// yield, and `running` does not flip until runAgent begins.
|
|
3756
3887
|
claimedStart = runClaim.claim();
|
|
3888
|
+
// A scheduled prompt has nobody watching: short ask deadline.
|
|
3889
|
+
scheduledRunActive = meta?.scheduled === true;
|
|
3757
3890
|
json(res, 202, { accepted: true });
|
|
3758
3891
|
// Gate inputs captured around the run: whether this turn is a workflow
|
|
3759
3892
|
// slash command (attachment prompts skip slash expansion entirely), and
|
|
@@ -3852,8 +3985,10 @@ async function createSession(deps, opts) {
|
|
|
3852
3985
|
await runStrandedQueue();
|
|
3853
3986
|
})
|
|
3854
3987
|
.finally(() => {
|
|
3855
|
-
if (claimedStart)
|
|
3988
|
+
if (claimedStart) {
|
|
3989
|
+
scheduledRunActive = false;
|
|
3856
3990
|
runClaim.release();
|
|
3991
|
+
}
|
|
3857
3992
|
});
|
|
3858
3993
|
return;
|
|
3859
3994
|
}
|
|
@@ -3889,6 +4024,7 @@ async function createSession(deps, opts) {
|
|
|
3889
4024
|
}
|
|
3890
4025
|
json(res, 202, { accepted: true });
|
|
3891
4026
|
nolanRunning = true;
|
|
4027
|
+
const releaseNolanAwake = keepAwake.acquire("nolan");
|
|
3892
4028
|
broadcast("nolan_run_start", { text });
|
|
3893
4029
|
try {
|
|
3894
4030
|
const nolanAgent = await ensureNolanSession();
|
|
@@ -3905,6 +4041,7 @@ async function createSession(deps, opts) {
|
|
|
3905
4041
|
broadcastError("nolan_error", "nolan run failed", err);
|
|
3906
4042
|
}
|
|
3907
4043
|
finally {
|
|
4044
|
+
releaseNolanAwake();
|
|
3908
4045
|
nolanRunning = false;
|
|
3909
4046
|
broadcast("nolan_run_end", {});
|
|
3910
4047
|
const pending = pendingNolanModel;
|
|
@@ -3943,9 +4080,6 @@ async function createSession(deps, opts) {
|
|
|
3943
4080
|
return;
|
|
3944
4081
|
}
|
|
3945
4082
|
autopilot = enabled;
|
|
3946
|
-
// A toggle-off during an active cycle takes effect after Nolan finishes;
|
|
3947
|
-
// until then, injected build runs must not re-enable Ideal self-review.
|
|
3948
|
-
session.setIdealReviewSuppressed(enabled || autopilotActive);
|
|
3949
4083
|
await saveAutopilot(cwd, enabled);
|
|
3950
4084
|
log("INFO", "app-sidecar", "autopilot toggled", { enabled: String(enabled) });
|
|
3951
4085
|
broadcast("autopilot", { autopilot: enabled });
|
|
@@ -4471,6 +4605,18 @@ async function createSession(deps, opts) {
|
|
|
4471
4605
|
json(res, 200, payload);
|
|
4472
4606
|
return;
|
|
4473
4607
|
}
|
|
4608
|
+
if (method === "POST" && url === "/prewarm") {
|
|
4609
|
+
// Best-effort Anthropic cache prewarm on the composer's first keystroke.
|
|
4610
|
+
// Fire-and-forget: AgentSession.prewarm() applies its own gating (provider,
|
|
4611
|
+
// setting, history size, TTL, in-flight) and swallows request errors.
|
|
4612
|
+
if (!running) {
|
|
4613
|
+
void session.prewarm().catch((err) => {
|
|
4614
|
+
log("WARN", "app-sidecar", "prewarm failed", { err: String(err) });
|
|
4615
|
+
});
|
|
4616
|
+
}
|
|
4617
|
+
json(res, 202, { accepted: !running });
|
|
4618
|
+
return;
|
|
4619
|
+
}
|
|
4474
4620
|
if (method === "POST" && url === "/cancel") {
|
|
4475
4621
|
void (async () => {
|
|
4476
4622
|
// Even between task runs, cancellation stops the sweep. Active provider
|
|
@@ -5327,6 +5473,10 @@ async function createSession(deps, opts) {
|
|
|
5327
5473
|
json(res, 404, { error: "not found" });
|
|
5328
5474
|
}
|
|
5329
5475
|
async function dispose() {
|
|
5476
|
+
releaseRunAwake?.();
|
|
5477
|
+
releaseRunAwake = null;
|
|
5478
|
+
releaseSubagentAwake?.();
|
|
5479
|
+
releaseSubagentAwake = null;
|
|
5330
5480
|
elicitations.cancelAll();
|
|
5331
5481
|
asks.cancelAll();
|
|
5332
5482
|
tasksPollStopped = true;
|
|
@@ -5350,6 +5500,11 @@ async function createSession(deps, opts) {
|
|
|
5350
5500
|
await nolanAutoSession?.dispose().catch(() => { });
|
|
5351
5501
|
await session.dispose().catch(() => { });
|
|
5352
5502
|
}
|
|
5503
|
+
function stopBackgroundProcesses() {
|
|
5504
|
+
session.stopBackgroundProcesses();
|
|
5505
|
+
nolanSession?.stopBackgroundProcesses();
|
|
5506
|
+
nolanAutoSession?.stopBackgroundProcesses();
|
|
5507
|
+
}
|
|
5353
5508
|
return {
|
|
5354
5509
|
id: opts.id,
|
|
5355
5510
|
mode,
|
|
@@ -5363,6 +5518,7 @@ async function createSession(deps, opts) {
|
|
|
5363
5518
|
broadcast,
|
|
5364
5519
|
handle,
|
|
5365
5520
|
dispose,
|
|
5521
|
+
stopBackgroundProcesses,
|
|
5366
5522
|
};
|
|
5367
5523
|
}
|
|
5368
5524
|
main().catch(async (err) => {
|