@prestyj/cli 5.5.0 → 5.7.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.
Files changed (235) hide show
  1. package/assets/end-notification.mp3 +0 -0
  2. package/assets/skills/evidence-led-ui/LICENSES.md +68 -0
  3. package/assets/skills/evidence-led-ui/SKILL.md +139 -0
  4. package/{LICENSE → assets/skills/evidence-led-ui/corpus/awesome-design-md/LICENSE} +1 -1
  5. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/airbnb/DESIGN.md +545 -0
  6. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/airtable/DESIGN.md +554 -0
  7. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/apple/DESIGN.md +562 -0
  8. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/binance/DESIGN.md +634 -0
  9. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/bmw/DESIGN.md +544 -0
  10. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/bmw-m/DESIGN.md +503 -0
  11. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/bugatti/DESIGN.md +454 -0
  12. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/cal/DESIGN.md +542 -0
  13. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/claude/DESIGN.md +589 -0
  14. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/clay/DESIGN.md +541 -0
  15. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/clickhouse/DESIGN.md +544 -0
  16. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/cohere/DESIGN.md +451 -0
  17. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/coinbase/DESIGN.md +570 -0
  18. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/composio/DESIGN.md +506 -0
  19. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/cursor/DESIGN.md +537 -0
  20. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/dell-1996/DESIGN.md +632 -0
  21. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/elevenlabs/DESIGN.md +504 -0
  22. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/expo/DESIGN.md +526 -0
  23. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/ferrari/DESIGN.md +531 -0
  24. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/figma/DESIGN.md +578 -0
  25. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/framer/DESIGN.md +544 -0
  26. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/hashicorp/DESIGN.md +575 -0
  27. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/hp/DESIGN.md +670 -0
  28. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/ibm/DESIGN.md +550 -0
  29. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/intercom/DESIGN.md +546 -0
  30. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/kraken/DESIGN.md +125 -0
  31. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/lamborghini/DESIGN.md +288 -0
  32. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/linear.app/DESIGN.md +548 -0
  33. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/lovable/DESIGN.md +298 -0
  34. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/mastercard/DESIGN.md +365 -0
  35. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/meta/DESIGN.md +683 -0
  36. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/minimax/DESIGN.md +746 -0
  37. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/mintlify/DESIGN.md +852 -0
  38. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/miro/DESIGN.md +825 -0
  39. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/mistral.ai/DESIGN.md +773 -0
  40. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/mongodb/DESIGN.md +767 -0
  41. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/nike/DESIGN.md +575 -0
  42. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/nintendo-2001/DESIGN.md +649 -0
  43. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/notion/DESIGN.md +821 -0
  44. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/nvidia/DESIGN.md +640 -0
  45. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/ollama/DESIGN.md +539 -0
  46. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/opencode.ai/DESIGN.md +521 -0
  47. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/pinterest/DESIGN.md +597 -0
  48. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/playstation/DESIGN.md +661 -0
  49. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/posthog/DESIGN.md +690 -0
  50. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/raycast/DESIGN.md +669 -0
  51. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/renault/DESIGN.md +589 -0
  52. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/replicate/DESIGN.md +616 -0
  53. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/resend/DESIGN.md +585 -0
  54. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/revolut/DESIGN.md +636 -0
  55. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/runwayml/DESIGN.md +244 -0
  56. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/sanity/DESIGN.md +357 -0
  57. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/sentry/DESIGN.md +551 -0
  58. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/shopify/DESIGN.md +516 -0
  59. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/slack/DESIGN.md +482 -0
  60. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/spacex/DESIGN.md +363 -0
  61. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/spotify/DESIGN.md +246 -0
  62. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/starbucks/DESIGN.md +580 -0
  63. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/stripe/DESIGN.md +487 -0
  64. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/supabase/DESIGN.md +462 -0
  65. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/superhuman/DESIGN.md +448 -0
  66. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/tesla/DESIGN.md +286 -0
  67. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/theverge/DESIGN.md +339 -0
  68. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/together.ai/DESIGN.md +633 -0
  69. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/uber/DESIGN.md +636 -0
  70. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/vercel/DESIGN.md +736 -0
  71. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/vodafone/DESIGN.md +538 -0
  72. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/voltagent/DESIGN.md +521 -0
  73. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/warp/DESIGN.md +526 -0
  74. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/webflow/DESIGN.md +588 -0
  75. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/wired/DESIGN.md +497 -0
  76. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/wise/DESIGN.md +544 -0
  77. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/x.ai/DESIGN.md +465 -0
  78. package/assets/skills/evidence-led-ui/corpus/awesome-design-md/design-md/zapier/DESIGN.md +537 -0
  79. package/assets/skills/evidence-led-ui/data/corpus-manifest.json +768 -0
  80. package/assets/skills/evidence-led-ui/data/observations.json +47053 -0
  81. package/assets/skills/evidence-led-ui/references/anti-defaults.md +203 -0
  82. package/assets/skills/evidence-led-ui/references/archetypes.md +97 -0
  83. package/assets/skills/evidence-led-ui/references/craft-rulings.md +167 -0
  84. package/assets/skills/evidence-led-ui/references/methodology.md +72 -0
  85. package/assets/skills/evidence-led-ui/references/observed-patterns.md +68 -0
  86. package/assets/skills/evidence-led-ui/references/production-contract.md +120 -0
  87. package/assets/skills/evidence-led-ui/references/provenance.md +117 -0
  88. package/assets/skills/evidence-led-ui/references/quality-rubric.md +126 -0
  89. package/dist/app-sidecar.js +159 -43
  90. package/dist/app-sidecar.js.map +1 -1
  91. package/dist/chat-agents/general.test.js +3 -0
  92. package/dist/chat-agents/general.test.js.map +1 -1
  93. package/dist/chat-agents/jiwa.d.ts +53 -0
  94. package/dist/chat-agents/jiwa.d.ts.map +1 -0
  95. package/dist/chat-agents/jiwa.js +352 -0
  96. package/dist/chat-agents/jiwa.js.map +1 -0
  97. package/dist/chat-agents/jiwa.test.d.ts +2 -0
  98. package/dist/chat-agents/jiwa.test.d.ts.map +1 -0
  99. package/dist/chat-agents/jiwa.test.js +66 -0
  100. package/dist/chat-agents/jiwa.test.js.map +1 -0
  101. package/dist/chat-agents/shared.d.ts.map +1 -1
  102. package/dist/chat-agents/shared.js +5 -1
  103. package/dist/chat-agents/shared.js.map +1 -1
  104. package/dist/cli.d.ts.map +1 -1
  105. package/dist/cli.js +15 -0
  106. package/dist/cli.js.map +1 -1
  107. package/dist/core/agent-session-review-coverage.test.d.ts +2 -0
  108. package/dist/core/agent-session-review-coverage.test.d.ts.map +1 -0
  109. package/dist/core/agent-session-review-coverage.test.js +57 -0
  110. package/dist/core/agent-session-review-coverage.test.js.map +1 -0
  111. package/dist/core/agent-session.d.ts +13 -7
  112. package/dist/core/agent-session.d.ts.map +1 -1
  113. package/dist/core/agent-session.js +143 -38
  114. package/dist/core/agent-session.js.map +1 -1
  115. package/dist/core/compaction/compactor.test.js +1 -1
  116. package/dist/core/compaction/compactor.test.js.map +1 -1
  117. package/dist/core/event-bus.d.ts +2 -0
  118. package/dist/core/event-bus.d.ts.map +1 -1
  119. package/dist/core/event-bus.js.map +1 -1
  120. package/dist/core/goal-worker.test.js +3 -2
  121. package/dist/core/goal-worker.test.js.map +1 -1
  122. package/dist/core/ideal-review.d.ts +27 -0
  123. package/dist/core/ideal-review.d.ts.map +1 -1
  124. package/dist/core/ideal-review.js +64 -0
  125. package/dist/core/ideal-review.js.map +1 -1
  126. package/dist/core/ideal-review.test.js +34 -1
  127. package/dist/core/ideal-review.test.js.map +1 -1
  128. package/dist/core/loop-breaker.d.ts +23 -16
  129. package/dist/core/loop-breaker.d.ts.map +1 -1
  130. package/dist/core/loop-breaker.js +52 -11
  131. package/dist/core/loop-breaker.js.map +1 -1
  132. package/dist/core/loop-breaker.test.js +37 -16
  133. package/dist/core/loop-breaker.test.js.map +1 -1
  134. package/dist/core/lsp/client.d.ts +3 -0
  135. package/dist/core/lsp/client.d.ts.map +1 -1
  136. package/dist/core/lsp/client.js +20 -0
  137. package/dist/core/lsp/client.js.map +1 -1
  138. package/dist/core/lsp/manager.d.ts +32 -9
  139. package/dist/core/lsp/manager.d.ts.map +1 -1
  140. package/dist/core/lsp/manager.js +89 -37
  141. package/dist/core/lsp/manager.js.map +1 -1
  142. package/dist/core/lsp/manager.test.js +56 -4
  143. package/dist/core/lsp/manager.test.js.map +1 -1
  144. package/dist/core/nolan-prompt.d.ts +3 -3
  145. package/dist/core/nolan-prompt.d.ts.map +1 -1
  146. package/dist/core/nolan-prompt.js +10 -10
  147. package/dist/core/nolan-prompt.js.map +1 -1
  148. package/dist/core/nolan-prompt.test.js +17 -0
  149. package/dist/core/nolan-prompt.test.js.map +1 -1
  150. package/dist/core/run-lifecycle.d.ts +40 -0
  151. package/dist/core/run-lifecycle.d.ts.map +1 -0
  152. package/dist/core/run-lifecycle.js +95 -0
  153. package/dist/core/run-lifecycle.js.map +1 -0
  154. package/dist/core/run-lifecycle.test.d.ts +2 -0
  155. package/dist/core/run-lifecycle.test.d.ts.map +1 -0
  156. package/dist/core/run-lifecycle.test.js +64 -0
  157. package/dist/core/run-lifecycle.test.js.map +1 -0
  158. package/dist/core/session-manager.d.ts +25 -1
  159. package/dist/core/session-manager.d.ts.map +1 -1
  160. package/dist/core/session-manager.js +66 -1
  161. package/dist/core/session-manager.js.map +1 -1
  162. package/dist/core/session-manager.test.js +80 -2
  163. package/dist/core/session-manager.test.js.map +1 -1
  164. package/dist/core/skills-routing.test.d.ts +2 -0
  165. package/dist/core/skills-routing.test.d.ts.map +1 -0
  166. package/dist/core/skills-routing.test.js +70 -0
  167. package/dist/core/skills-routing.test.js.map +1 -0
  168. package/dist/core/skills.d.ts +2 -0
  169. package/dist/core/skills.d.ts.map +1 -1
  170. package/dist/core/skills.js +32 -9
  171. package/dist/core/skills.js.map +1 -1
  172. package/dist/core/subagent-manager.d.ts +40 -3
  173. package/dist/core/subagent-manager.d.ts.map +1 -1
  174. package/dist/core/subagent-manager.js +342 -30
  175. package/dist/core/subagent-manager.js.map +1 -1
  176. package/dist/core/subagent-manager.test.js +189 -3
  177. package/dist/core/subagent-manager.test.js.map +1 -1
  178. package/dist/core/subagent-store.d.ts +20 -0
  179. package/dist/core/subagent-store.d.ts.map +1 -0
  180. package/dist/core/subagent-store.js +177 -0
  181. package/dist/core/subagent-store.js.map +1 -0
  182. package/dist/core/subagent-store.test.d.ts +2 -0
  183. package/dist/core/subagent-store.test.d.ts.map +1 -0
  184. package/dist/core/subagent-store.test.js +94 -0
  185. package/dist/core/subagent-store.test.js.map +1 -0
  186. package/dist/modes/agent-home-mode.d.ts.map +1 -1
  187. package/dist/modes/agent-home-mode.js +10 -3
  188. package/dist/modes/agent-home-mode.js.map +1 -1
  189. package/dist/modes/serve-mode.d.ts.map +1 -1
  190. package/dist/modes/serve-mode.js +10 -3
  191. package/dist/modes/serve-mode.js.map +1 -1
  192. package/dist/modes/subagent-worker-mode.d.ts +2 -0
  193. package/dist/modes/subagent-worker-mode.d.ts.map +1 -1
  194. package/dist/modes/subagent-worker-mode.js +13 -3
  195. package/dist/modes/subagent-worker-mode.js.map +1 -1
  196. package/dist/system-prompt.js +2 -2
  197. package/dist/system-prompt.js.map +1 -1
  198. package/dist/system-prompt.test.js +7 -5
  199. package/dist/system-prompt.test.js.map +1 -1
  200. package/dist/tools/goals.d.ts +3 -3
  201. package/dist/tools/skill.d.ts.map +1 -1
  202. package/dist/tools/skill.js +8 -3
  203. package/dist/tools/skill.js.map +1 -1
  204. package/dist/tools/tasks.d.ts +2 -2
  205. package/dist/tools/web-fetch.d.ts +1 -1
  206. package/dist/ui/App.d.ts +7 -1
  207. package/dist/ui/App.d.ts.map +1 -1
  208. package/dist/ui/App.js +63 -17
  209. package/dist/ui/App.js.map +1 -1
  210. package/dist/ui/components/Footer.d.ts +1 -0
  211. package/dist/ui/components/Footer.d.ts.map +1 -1
  212. package/dist/ui/components/Footer.js +3 -3
  213. package/dist/ui/components/Footer.js.map +1 -1
  214. package/dist/ui/components/Footer.test.d.ts +2 -0
  215. package/dist/ui/components/Footer.test.d.ts.map +1 -0
  216. package/dist/ui/components/Footer.test.js +16 -0
  217. package/dist/ui/components/Footer.test.js.map +1 -0
  218. package/dist/ui/components/SkillsOverlay.d.ts.map +1 -1
  219. package/dist/ui/components/SkillsOverlay.js +8 -3
  220. package/dist/ui/components/SkillsOverlay.js.map +1 -1
  221. package/dist/ui/hooks/useAgentLoop.d.ts +8 -3
  222. package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
  223. package/dist/ui/hooks/useAgentLoop.js +70 -25
  224. package/dist/ui/hooks/useAgentLoop.js.map +1 -1
  225. package/dist/ui/hooks/useAgentLoop.test.js +27 -0
  226. package/dist/ui/hooks/useAgentLoop.test.js.map +1 -1
  227. package/dist/ui/hooks/useSessionPersistence.d.ts +4 -2
  228. package/dist/ui/hooks/useSessionPersistence.d.ts.map +1 -1
  229. package/dist/ui/hooks/useSessionPersistence.js +7 -1
  230. package/dist/ui/hooks/useSessionPersistence.js.map +1 -1
  231. package/dist/ui/render.d.ts +7 -0
  232. package/dist/ui/render.d.ts.map +1 -1
  233. package/dist/ui/render.js +4 -0
  234. package/dist/ui/render.js.map +1 -1
  235. package/package.json +6 -5
@@ -18,11 +18,13 @@ import os from "node:os";
18
18
  import path from "node:path";
19
19
  import { randomUUID } from "node:crypto";
20
20
  import { parseArgs } from "node:util";
21
- import { formatError } from "@prestyj/ai";
21
+ import { environmentSecrets, formatError, redactValue } from "@prestyj/ai";
22
22
  import { runJsonMode } from "./modes/json-mode.js";
23
23
  import { runSubagentWorkerMode } from "./modes/subagent-worker-mode.js";
24
24
  import { AgentSession } from "./core/agent-session.js";
25
+ import { RunLifecycle } from "./core/run-lifecycle.js";
25
26
  import { CHAT_AGENT_IDS, chatAgentSessionsDir, createChatAgent, parseChatAgentId, sessionsDirForChatAgent, switchChatAgent, } from "./chat-agents/index.js";
27
+ import { buildJiwaTools, JiwaStore } from "./chat-agents/jiwa.js";
26
28
  import { buildMemoryTools, MemoryStore } from "./chat-agents/memory.js";
27
29
  import { buildNolanSystemPrompt, buildNolanAutopilotSystemPrompt } from "./core/nolan-prompt.js";
28
30
  import { buildNolanDigest, buildNolanAutopilotContext, buildNolanAutopilotPlanContext, } from "./core/nolan-context.js";
@@ -517,6 +519,13 @@ async function main() {
517
519
  }
518
520
  },
519
521
  });
522
+ const jiwaStore = new JiwaStore({
523
+ onChange: ({ jiwa }) => {
524
+ for (const ctx of sessions.values()) {
525
+ ctx.broadcast("jiwa_change", { count: jiwa.length });
526
+ }
527
+ },
528
+ });
520
529
  // XP/rank progress — loaded once per daemon; awards fan out to every window.
521
530
  // Each frame is tagged `origin: true` only for the session that earned the
522
531
  // XP, so that window alone plays sounds/chips while the rest just re-render.
@@ -633,7 +642,7 @@ async function main() {
633
642
  const sessionPath = typeof body.sessionPath === "string" && body.sessionPath ? body.sessionPath : undefined;
634
643
  const id = randomUUID();
635
644
  try {
636
- const ctx = await createSession({ auth, paths, progress, memoryStore }, { id, mode, chatAgent, cwd: sessionCwd, sessionPath });
645
+ const ctx = await createSession({ auth, paths, progress, memoryStore, jiwaStore }, { id, mode, chatAgent, cwd: sessionCwd, sessionPath });
637
646
  sessions.set(id, ctx);
638
647
  log("INFO", "app-sidecar", "session created", {
639
648
  id,
@@ -886,7 +895,7 @@ async function createProgressManager(agentDir, broadcastAll) {
886
895
  * logger, PATH, shared auth file, and radio live at the daemon level.
887
896
  */
888
897
  async function createSession(deps, opts) {
889
- const { auth, progress, memoryStore } = deps;
898
+ const { auth, progress, memoryStore, jiwaStore } = deps;
890
899
  const paths = deps.paths;
891
900
  const mode = opts.mode;
892
901
  let chatAgent = opts.chatAgent;
@@ -921,7 +930,8 @@ async function createSession(deps, opts) {
921
930
  const clients = new Set();
922
931
  let clientSeq = 0;
923
932
  function broadcast(type, data) {
924
- const frame = `data: ${JSON.stringify({ type, data })}\n\n`;
933
+ const safePayload = redactValue({ type, data }, { secrets: environmentSecrets(process.env) });
934
+ const frame = `data: ${JSON.stringify(safePayload)}\n\n`;
925
935
  for (const c of clients)
926
936
  c.res.write(frame);
927
937
  }
@@ -1007,8 +1017,8 @@ async function createSession(deps, opts) {
1007
1017
  session = createChatAgent(chatAgent, {
1008
1018
  ...baseSessionOptions,
1009
1019
  sessionsDir: paths.sessionsDir,
1010
- additionalTools: buildMemoryTools(memoryStore),
1011
- getSystemPromptTail: () => memoryStore.renderForPrompt(),
1020
+ additionalTools: [...buildMemoryTools(memoryStore), ...buildJiwaTools(jiwaStore)],
1021
+ getSystemPromptTail: () => `${memoryStore.renderForPrompt()}\n\n${jiwaStore.renderForPrompt()}`,
1012
1022
  onAgentChange: async (nextAgent) => {
1013
1023
  chatAgent = nextAgent;
1014
1024
  broadcast("chat_agent_change", { chatAgent: nextAgent });
@@ -1129,6 +1139,13 @@ async function createSession(deps, opts) {
1129
1139
  session.eventBus.on("compaction_start", (d) => broadcast("compaction_start", d));
1130
1140
  session.eventBus.on("compaction_end", (d) => broadcast("compaction_end", d));
1131
1141
  let running = false;
1142
+ const runLifecycle = new RunLifecycle((runState) => {
1143
+ running = runState !== "idle";
1144
+ if (runState === "cancelling")
1145
+ broadcast("run_cancelling", { runState });
1146
+ });
1147
+ const cancelledRunEndGenerations = new Set();
1148
+ let pendingCancelDrain = null;
1132
1149
  let titleGenerated = false;
1133
1150
  // Bumped by /cancel — a run whose cancel generation changed mid-flight was
1134
1151
  // canceled and earns no XP.
@@ -1151,6 +1168,7 @@ async function createSession(deps, opts) {
1151
1168
  let autopilotCancelled = false;
1152
1169
  // Hard cap on review→prompt→review rounds per user turn (loop safety).
1153
1170
  const MAX_AUTOPILOT_ROUNDS = 3;
1171
+ const CANCEL_TIMEOUT_MS = 5_000;
1154
1172
  // Prompt bodies Autopilot Nolan injected into the BUILD session this
1155
1173
  // conversation. Passed into every Nolan digest so injected prompts render as
1156
1174
  // "Nolan autopilot (injected)" instead of `**User:**` — otherwise multi-round
@@ -1357,29 +1375,63 @@ async function createSession(deps, opts) {
1357
1375
  });
1358
1376
  }
1359
1377
  }
1360
- // Core run lifecycle shared by /prompt and the task runner: flips `running`,
1361
- // brackets the run with run_start/run_end, refreshes the footer extras, and
1362
- // generates the session title once. `label` is the text shown live with the
1363
- // run_start frame.
1378
+ function abortOwnedWork() {
1379
+ cancelGeneration++;
1380
+ abort.abort();
1381
+ // Stop a run-all sweep and every async child through AgentSession's signal.
1382
+ taskRunAll = false;
1383
+ autopilotCancelled = true;
1384
+ nolanAutoAbort.abort();
1385
+ }
1386
+ function installFreshRunControllers() {
1387
+ abort = new AbortController();
1388
+ session.setSignal(abort.signal);
1389
+ nolanAutoAbort = new AbortController();
1390
+ nolanAutoSession?.setSignal(nolanAutoAbort.signal);
1391
+ }
1392
+ function finishOwnedGeneration(generation, emitCancelledFallback) {
1393
+ const cancelled = runLifecycle.isCancellationRequested(generation);
1394
+ const settlement = runLifecycle.settle(generation);
1395
+ if (!settlement.settled)
1396
+ return cancelled;
1397
+ // A replacement signal is safe only after the provider-backed owner settled.
1398
+ installFreshRunControllers();
1399
+ if (cancelled && emitCancelledFallback && !cancelledRunEndGenerations.has(generation)) {
1400
+ cancelledRunEndGenerations.add(generation);
1401
+ broadcast("run_end", { cancelled: true, runState: runLifecycle.state });
1402
+ }
1403
+ return cancelled;
1404
+ }
1405
+ // Core provider-run bracket. Standalone runs own a lifecycle generation;
1406
+ // injected autopilot runs share the cycle's outer generation.
1364
1407
  async function runAgent(label, run) {
1365
- running = true;
1408
+ const ownsGeneration = !runLifecycle.running;
1409
+ const generation = ownsGeneration
1410
+ ? runLifecycle.begin(abortOwnedWork).generation
1411
+ : runLifecycle.generation;
1412
+ if (ownsGeneration)
1413
+ pendingCancelDrain = null;
1366
1414
  // Progress (Ranks): completed, non-canceled runs with ≥1 assistant turn earn
1367
1415
  // XP — prompt + any commits authored during the run window.
1368
1416
  const runStartedAt = Date.now();
1369
1417
  const cancelGenAtStart = cancelGeneration;
1370
1418
  const assistantsBeforeRun = countAssistantMessages(session.getMessages());
1371
1419
  let runSucceeded = false;
1372
- broadcast("run_start", { text: label });
1420
+ broadcast("run_start", { text: label, runState: runLifecycle.state });
1373
1421
  try {
1374
- await run();
1422
+ if (!runLifecycle.isCancellationRequested(generation))
1423
+ await run();
1375
1424
  runSucceeded = true;
1376
1425
  }
1377
1426
  catch (err) {
1378
- broadcastError("error", "run failed", err);
1427
+ if (!runLifecycle.isCancellationRequested(generation)) {
1428
+ broadcastError("error", "run failed", err);
1429
+ }
1379
1430
  }
1380
1431
  finally {
1381
- running = false;
1432
+ const cancelled = runLifecycle.isCancellationRequested(generation);
1382
1433
  if (runSucceeded &&
1434
+ !cancelled &&
1383
1435
  cancelGeneration === cancelGenAtStart &&
1384
1436
  countAssistantMessages(session.getMessages()) > assistantsBeforeRun) {
1385
1437
  // Fire-and-forget — XP must never delay or break run teardown.
@@ -1389,7 +1441,18 @@ async function createSession(deps, opts) {
1389
1441
  // background tasks — refresh the footer extras once it settles.
1390
1442
  gitBranch = await getGitBranch(cwd).catch(() => gitBranch);
1391
1443
  gitIsRepo = await isGitRepo(cwd).catch(() => gitIsRepo);
1392
- broadcast("run_end", {});
1444
+ if (ownsGeneration)
1445
+ finishOwnedGeneration(generation, false);
1446
+ // A cancelled injected run is still owned by the surrounding autopilot
1447
+ // cycle; its outer finalizer emits the one terminal cancelled run_end.
1448
+ if (!(cancelled && !ownsGeneration)) {
1449
+ if (cancelled)
1450
+ cancelledRunEndGenerations.add(generation);
1451
+ broadcast("run_end", {
1452
+ ...(cancelled ? { cancelled: true } : {}),
1453
+ runState: runLifecycle.state,
1454
+ });
1455
+ }
1393
1456
  // Autopilot's review loop is driven explicitly from POST /prompt (see
1394
1457
  // runAutopilotCycle), NOT from this shared finally — that keeps the
1395
1458
  // injected EZ Coder runs this cycle triggers from recursively re-entering
@@ -1398,12 +1461,8 @@ async function createSession(deps, opts) {
1398
1461
  // completed ones so they drop out of the Tasks modal automatically (users
1399
1462
  // never have to delete finished tasks by hand).
1400
1463
  broadcast("tasks_list", { tasks: pruneDoneTasksSync(cwd) });
1401
- // Queue drains into the run as steering, so it's empty by run_end —
1402
- // sync the webview indicator.
1403
1464
  broadcast("queued", { count: session.getQueuedCount() });
1404
1465
  broadcast("extras", footerExtras());
1405
- // Generate a session title once, after the first run, for the title bar
1406
- // (best-effort, async — don't block the response).
1407
1466
  if (!titleGenerated) {
1408
1467
  titleGenerated = true;
1409
1468
  void session.generateTitle().then((title) => {
@@ -1436,7 +1495,8 @@ async function createSession(deps, opts) {
1436
1495
  return parseAutopilotVerdict(lastAssistantText(ken.getMessages()));
1437
1496
  }
1438
1497
  catch (err) {
1439
- broadcastError("autopilot_error", "autopilot review failed", err);
1498
+ if (!autopilotCancelled)
1499
+ broadcastError("autopilot_error", "autopilot review failed", err);
1440
1500
  return null;
1441
1501
  }
1442
1502
  finally {
@@ -1511,6 +1571,8 @@ async function createSession(deps, opts) {
1511
1571
  async function runAutopilotCycle(originalRequest) {
1512
1572
  if (!autopilot || autopilotCancelled)
1513
1573
  return;
1574
+ const generation = runLifecycle.begin(abortOwnedWork).generation;
1575
+ pendingCancelDrain = null;
1514
1576
  autopilotActive = true;
1515
1577
  // Generation captured by the last plan review; acceptPlan re-checks it so
1516
1578
  // a user Accept/Reject landing mid-review always wins.
@@ -1625,6 +1687,8 @@ async function createSession(deps, opts) {
1625
1687
  }
1626
1688
  finally {
1627
1689
  autopilotActive = false;
1690
+ finishOwnedGeneration(generation, true);
1691
+ queueMicrotask(() => void runStrandedQueue());
1628
1692
  }
1629
1693
  }
1630
1694
  // ── Stranded-queue drain ───────────────────────────────
@@ -1817,7 +1881,7 @@ async function createSession(deps, opts) {
1817
1881
  });
1818
1882
  }
1819
1883
  function json(res, status, body) {
1820
- const payload = JSON.stringify(body);
1884
+ const payload = JSON.stringify(redactValue(body, { secrets: environmentSecrets(process.env) }));
1821
1885
  res.writeHead(status, {
1822
1886
  "content-type": "application/json",
1823
1887
  "access-control-allow-origin": "*",
@@ -1833,6 +1897,7 @@ async function createSession(deps, opts) {
1833
1897
  mode,
1834
1898
  chatAgent,
1835
1899
  running,
1900
+ runState: runLifecycle.state,
1836
1901
  ready: true,
1837
1902
  thinkingLevel: session.getThinkingLevel() ?? null,
1838
1903
  supportedThinkingLevels: getSupportedThinkingLevels(st.provider, st.model),
@@ -1867,6 +1932,26 @@ async function createSession(deps, opts) {
1867
1932
  .catch((error) => json(res, 500, { error: error instanceof Error ? error.message : String(error) }));
1868
1933
  return;
1869
1934
  }
1935
+ if (method === "GET" && url === "/jiwa") {
1936
+ void jiwaStore
1937
+ .snapshot()
1938
+ .then((snapshot) => json(res, 200, snapshot))
1939
+ .catch((error) => json(res, 500, { error: error instanceof Error ? error.message : String(error) }));
1940
+ return;
1941
+ }
1942
+ if (method === "DELETE" && url.startsWith("/jiwa/")) {
1943
+ const id = decodeURIComponent(url.slice("/jiwa/".length));
1944
+ if (!id) {
1945
+ json(res, 400, { error: "Jiwa entry id is required" });
1946
+ return;
1947
+ }
1948
+ void jiwaStore
1949
+ .forget(id)
1950
+ .then(() => jiwaStore.snapshot())
1951
+ .then((snapshot) => json(res, 200, snapshot))
1952
+ .catch((error) => json(res, 500, { error: error instanceof Error ? error.message : String(error) }));
1953
+ return;
1954
+ }
1870
1955
  if (method === "GET" && (url === "/events" || url.startsWith("/events?"))) {
1871
1956
  res.writeHead(200, {
1872
1957
  "content-type": "text/event-stream",
@@ -1885,6 +1970,7 @@ async function createSession(deps, opts) {
1885
1970
  mode,
1886
1971
  chatAgent,
1887
1972
  running,
1973
+ runState: runLifecycle.state,
1888
1974
  thinkingLevel: session.getThinkingLevel() ?? null,
1889
1975
  supportedThinkingLevels: getSupportedThinkingLevels(st.provider, st.model),
1890
1976
  supportsVideo: getModel(st.model)?.supportsVideo ?? false,
@@ -2381,6 +2467,13 @@ async function createSession(deps, opts) {
2381
2467
  json(res, 400, { error: "empty prompt" });
2382
2468
  return;
2383
2469
  }
2470
+ if (runLifecycle.running && runLifecycle.isCancellationRequested(runLifecycle.generation)) {
2471
+ json(res, 409, {
2472
+ error: runLifecycle.state === "cancelling" ? "run_cancelling" : "cancel_failed",
2473
+ runState: runLifecycle.state,
2474
+ });
2475
+ return;
2476
+ }
2384
2477
  if (running || autopilotActive) {
2385
2478
  // Queue prompts as mid-run steering (mirrors the CLI). Also queue while
2386
2479
  // an autopilot cycle is active but between injected runs (build idle,
@@ -2954,27 +3047,50 @@ async function createSession(deps, opts) {
2954
3047
  return;
2955
3048
  }
2956
3049
  if (method === "POST" && url === "/cancel") {
2957
- cancelGeneration++;
2958
- abort.abort();
2959
- abort = new AbortController();
2960
- session.setSignal(abort.signal);
2961
- running = false;
2962
- // Stop a run-all sweep so the next pending task isn't auto-started.
2963
- taskRunAll = false;
2964
- taskRunActive = false;
2965
- // Stop any in-flight autopilot cycle: flag it so the loop bails between
2966
- // steps, and abort a review that's mid-prompt on the nolanAuto session.
2967
- autopilotCancelled = true;
2968
- nolanAutoAbort.abort();
2969
- nolanAutoAbort = new AbortController();
2970
- nolanAutoSession?.setSignal(nolanAutoAbort.signal);
2971
- autopilotReviewing = false;
2972
- // Drop any queued steering and return it so the webview can restore it to
2973
- // the composer.
2974
- const drained = session.drainQueue();
2975
- broadcast("run_end", { cancelled: true });
2976
- broadcast("queued", { count: 0 });
2977
- json(res, 200, { cancelled: true, drained });
3050
+ void (async () => {
3051
+ // Even between task runs, cancellation stops the sweep. Active provider
3052
+ // ownership invokes the full abort hook exactly once through lifecycle.
3053
+ taskRunAll = false;
3054
+ autopilotCancelled = true;
3055
+ if (!runLifecycle.running) {
3056
+ nolanAutoAbort.abort();
3057
+ nolanAutoAbort = new AbortController();
3058
+ nolanAutoSession?.setSignal(nolanAutoAbort.signal);
3059
+ }
3060
+ const generation = runLifecycle.generation;
3061
+ if (!pendingCancelDrain || pendingCancelDrain.generation !== generation) {
3062
+ pendingCancelDrain = { generation, text: session.drainQueue() };
3063
+ broadcast("queued", { count: 0 });
3064
+ }
3065
+ const result = await runLifecycle.cancel(CANCEL_TIMEOUT_MS);
3066
+ const drained = pendingCancelDrain.text;
3067
+ if (result.status === "failed") {
3068
+ broadcast("cancel_failed", {
3069
+ error: "cancel_failed",
3070
+ reason: result.reason,
3071
+ runState: runLifecycle.state,
3072
+ });
3073
+ json(res, 504, {
3074
+ error: "cancel_failed",
3075
+ reason: result.reason,
3076
+ runState: runLifecycle.state,
3077
+ drained,
3078
+ });
3079
+ return;
3080
+ }
3081
+ json(res, 200, {
3082
+ cancelled: result.status === "cancelled",
3083
+ runState: runLifecycle.state,
3084
+ drained,
3085
+ });
3086
+ })().catch((error) => {
3087
+ broadcast("cancel_failed", { error: "cancel_failed", runState: runLifecycle.state });
3088
+ json(res, 500, {
3089
+ error: "cancel_failed",
3090
+ message: error instanceof Error ? error.message : String(error),
3091
+ runState: runLifecycle.state,
3092
+ });
3093
+ });
2978
3094
  return;
2979
3095
  }
2980
3096
  if (method === "POST" && url === "/new-session") {