@tea-agent/loop-agent 0.42.0-next.7 → 0.42.0-next.9

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 (92) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/application/dag/run-dag.js +40 -0
  3. package/dist/build-stamp.json +3 -3
  4. package/dist/executors/dag-pi-executor.js +313 -56
  5. package/dist/executors/shell-executor.js +50 -20
  6. package/dist/worker/console/chat/pi-runtime.js +20 -4
  7. package/dist/worker/console/chat/routes.js +44 -22
  8. package/dist/worker/console/chat/session-store.js +16 -0
  9. package/dist/worker/console/server.js +19 -1
  10. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BQBHX31n.js → abnfDiagram-N423BO3Z-C6n9_m0P.js} +1 -1
  11. package/dist/worker/console/static/assets/{arc-MSJ199eR.js → arc-DQh-IfZ1.js} +1 -1
  12. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DiRq2qMX.js → architectureDiagram-T3A2C74G-54NnrwUC.js} +1 -1
  13. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-rlaxFOvi.js → blockDiagram-VBNYF7ZC-pivRALGK.js} +1 -1
  14. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BSpi9ump.js → c4Diagram-5PPSVZJV-BR7OV2NJ.js} +1 -1
  15. package/dist/worker/console/static/assets/channel-DsZxrgqe.js +1 -0
  16. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BN8VNeh5.js → chunk-2GRJ4B5K-B1Aq6BcK.js} +1 -1
  17. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CDa1OVRA.js → chunk-2Q5K7J3B-CbdU0rjo.js} +1 -1
  18. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-zxMQ5b8E.js → chunk-5RXB4S5H-Dqi2GJeD.js} +1 -1
  19. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-XTqNlzH4.js → chunk-5VM5RSS4-BYn1Hu4R.js} +1 -1
  20. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DpZQfFTq.js → chunk-6Q2QTUOP-DRLjDL0k.js} +1 -1
  21. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-DPv743zu.js → chunk-GF5L2VYU-CY9Xx2jV.js} +1 -1
  22. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CFsCFoF4.js → chunk-JWPE2WC7-BNCZs7_z.js} +1 -1
  23. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CV__aJTt.js → chunk-KBJHAD2P-DZ8AStLL.js} +1 -1
  24. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BshKlNir.js → chunk-RYQCIY6F-DsxjYrzz.js} +1 -1
  25. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-B-A3ErBP.js → chunk-XXDRQBXY-Ddx1KC1l.js} +1 -1
  26. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuToPeGV.js +1 -0
  27. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuToPeGV.js +1 -0
  28. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-25xj8928.js → cose-bilkent-JH36ORCC-W9TveCnK.js} +1 -1
  29. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DFmv38cl.js → cynefin-VYW2F7L2-l9j_ztZH.js} +1 -1
  30. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-hReuWDQP.js → cynefinDiagram-MW4NZA55-BMJMKi4G.js} +1 -1
  31. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C9X3YPUI.js → dagre-VZM6K2ZE-Bb4mG9pH.js} +1 -1
  32. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CAVmqjg9.js → diagram-7IWD3JNH-BMgL_Qv0.js} +1 -1
  33. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DYNlLsIm.js → diagram-B4RE2ZJO-Bvo2T4OQ.js} +1 -1
  34. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BP5mlbfb.js → diagram-LBJQPF4R-_5kWRN9c.js} +1 -1
  35. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-KWOHK5ZM.js → diagram-Q27KOJAE-DqdMrltM.js} +1 -1
  36. package/dist/worker/console/static/assets/{diagram-UB23O5K3-D25Ro5y-.js → diagram-UB23O5K3-CDDYsNkp.js} +1 -1
  37. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-kLaahtbh.js → ebnfDiagram-BXEA7PRR-c4nfnZUV.js} +1 -1
  38. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-1tgKuLez.js → erDiagram-JOGREHBK-DnkUcNMs.js} +1 -1
  39. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DARr-Cvn.js → flowDiagram-UKHOOZJN-Dz0KGLZE.js} +1 -1
  40. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BgwjBv_V.js → ganttDiagram-PKOTCBZU-Cj1t1uka.js} +1 -1
  41. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DneIlYSI.js → gitGraphDiagram-DS77QQ5N-tp2FrBHd.js} +1 -1
  42. package/dist/worker/console/static/assets/{index-Dkv0ezuE.js → index-D9Sc0f0y.js} +84 -84
  43. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DA7ATqY6.js → infoDiagram-6WML65LV-DQwJS8DH.js} +1 -1
  44. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DnOS-E1Z.js → ishikawaDiagram-WSZJBQD7-eimCQWD4.js} +1 -1
  45. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-C8JnwJ_s.js → journeyDiagram-NVQOT4AX-Dd9Gbwgv.js} +1 -1
  46. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Cw0_OjEC.js → kanban-definition-27J2QSJJ-Qve3cyft.js} +1 -1
  47. package/dist/worker/console/static/assets/{linear-mrxq3fyn.js → linear-UXKSe36Z.js} +1 -1
  48. package/dist/worker/console/static/assets/{mermaid.core-Bagus6zF.js → mermaid.core-CWhj4JXN.js} +5 -5
  49. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-Bf95bLuW.js → mindmap-definition-FAOFIHXS-yTcwf4SJ.js} +1 -1
  50. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BvtUW6w2.js → pegDiagram-VL7TDLO6-_7qToaZP.js} +1 -1
  51. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CbWtT-Ry.js → pieDiagram-7S7Q4E2Y-CXmrKmDm.js} +1 -1
  52. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-ctnxxIdD.js → quadrantDiagram-CIZ2JOQS-hMmrJyaS.js} +1 -1
  53. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BZ7Z3X0-.js → railroadDiagram-AXF67PYL-BXw8tCBe.js} +1 -1
  54. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BMX1xnIy.js → requirementDiagram-LRYGKXZP-BmjgnLZl.js} +1 -1
  55. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-MShOQznz.js → sankeyDiagram-W5VNT64P-Bj77SPpN.js} +1 -1
  56. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CTGOazaB.js → sequenceDiagram-SI44F4Z6-B2FDVysL.js} +1 -1
  57. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-zGbfM4nV.js → sizeCapture-X5ZJPWSS-UChNY9ZZ.js} +1 -1
  58. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Brf8Wwzr.js → stateDiagram-OKZ733FA-wbAEkbd7.js} +1 -1
  59. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-yPoT19ft.js +1 -0
  60. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CvNw3ZqF.js → swimlanes-SLNWSIFB-DCEkU8HR.js} +2 -2
  61. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-VWdarchG.js +8 -0
  62. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-z2LyZpDt.js → timeline-definition-Z64GVDOM-BGp7H06U.js} +1 -1
  63. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-Bc_coprH.js → vennDiagram-T6HMQDX7-DsCIQuWR.js} +1 -1
  64. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-M1NSmWq-.js → wardleyDiagram-T6FBY63Y-cRZLP4Xe.js} +1 -1
  65. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-Ct0rjGXW.js → xychartDiagram-ELKLHX3M-CP0xLR9Y.js} +1 -1
  66. package/dist/worker/console/static/index.html +1 -1
  67. package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +10 -0
  68. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +31 -2
  69. package/dist/worker/console/static-src/operator-chat/useComposer.js +8 -3
  70. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +29 -6
  71. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +8 -2
  72. package/dist/worker/observe/routes.js +4 -0
  73. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +58 -9
  74. package/dist/workflows/dag/backend-test-scenario-param.js +156 -20
  75. package/dist/workflows/dag/backend-test-scenario-partitions.js +62 -1
  76. package/dist/workflows/dag/backend-test-writer-completeness.js +7 -3
  77. package/dist/workflows/dag/frontend-recovery-plan.js +2 -1
  78. package/dist/workflows/dag/frontend-recovery-run.js +33 -5
  79. package/dist/workflows/dag/frontend-writer-admission.js +44 -10
  80. package/dist/workflows/dag/init-hybrid.js +26 -5
  81. package/dist/workflows/dag/node-execution.js +76 -0
  82. package/dist/workflows/dag/rerun-feedback.js +59 -0
  83. package/dist/workflows/dag/rerun-task.js +8 -2
  84. package/dist/workflows/dag/runner.js +18 -11
  85. package/dist/workflows/dag/scheduler.js +21 -6
  86. package/docs/templates/backend-test-dag.json +1 -1
  87. package/package.json +1 -1
  88. package/dist/worker/console/static/assets/channel-BdTaMnYP.js +0 -1
  89. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cid7CSRK.js +0 -1
  90. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cid7CSRK.js +0 -1
  91. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CIkoculx.js +0 -1
  92. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-pJ-V5EdT.js +0 -8
@@ -58,6 +58,7 @@ import { readProjectGovernanceContext } from "../workflows/dag/project-governanc
58
58
  import { assertMavenPlanFresh, MavenPlanStaleError, } from "../verification/maven/index.js";
59
59
  async function runBackendScenarioParamPreflight(input) {
60
60
  const outputs = [];
61
+ const allowRepair = input.allowRepair !== false;
61
62
  const initial = await assessBackendScenarioParamConsistency({
62
63
  workspaceRoot: input.workspaceRoot,
63
64
  phase: "initial",
@@ -73,7 +74,7 @@ async function runBackendScenarioParamPreflight(input) {
73
74
  outputs.push(`scenarioParamInitial=${initialArtifacts.reportPath}`);
74
75
  let finalFacts;
75
76
  let finalMarkdown;
76
- if (initial.facts.repairEligible) {
77
+ if (allowRepair && initial.facts.repairEligible) {
77
78
  const repair = await applyDeterministicScenarioParamRepairs({
78
79
  workspaceRoot: input.workspaceRoot,
79
80
  facts: initial.facts,
@@ -979,6 +980,7 @@ async function executeBackendTestPipeline(input, meta) {
979
980
  workspaceRoot: input.cwd,
980
981
  runDir: meta.runDir,
981
982
  layout: backendLayout,
983
+ allowRepair: false,
982
984
  strictScenarioParamGate: Boolean((meta.spec.globalConstraints ?? []).some((item) => /strictScenarioParamGate\s*=\s*true/i.test(item))),
983
985
  });
984
986
  outputs.push(...scenario.outputs);
@@ -2297,19 +2299,38 @@ async function collectReviewRestartPhase(runDir, reviewNodeId) {
2297
2299
  }
2298
2300
  /** A+B (AC-009): deterministic §4.10 restart phase for a committed
2299
2301
  * `request_design_changes` fact. Mirrors the review collector. */
2300
- /** Number of findings attached to the committed request_design_changes
2301
- * terminal fact (0 when absent) — used for the admission advisory. */
2302
- async function collectDesignReviewFindingCount(runDir, designNodeId) {
2302
+ /** Read the committed design request as a bounded structured capsule. */
2303
+ async function collectDesignReviewFindings(runDir, designNodeId) {
2303
2304
  try {
2304
2305
  const records = await readTypedEventStoreFromJsonl(path.join(runDir, designNodeId, "design-typed-facts.jsonl"));
2305
2306
  const request = records.find((record) => record.phase === "committed" &&
2306
2307
  record.fact.kind ===
2307
2308
  "request_design_changes");
2308
- const findings = (request?.fact).findings;
2309
- return Array.isArray(findings) ? findings.length : 0;
2309
+ const fact = request?.fact;
2310
+ const findings = Array.isArray(fact?.findings)
2311
+ ? fact.findings.flatMap((finding) => {
2312
+ if (!finding || typeof finding !== "object")
2313
+ return [];
2314
+ const value = finding;
2315
+ if (!(["Critical", "Important", "Minor", "Info"].includes(value.severity) &&
2316
+ typeof value.issue === "string" && value.issue.trim().length > 0))
2317
+ return [];
2318
+ return [{
2319
+ severity: value.severity,
2320
+ ...(typeof value.file === "string" && value.file.trim() ? { file: value.file } : {}),
2321
+ ...(typeof value.line === "number" && Number.isInteger(value.line) && value.line > 0 ? { line: value.line } : {}),
2322
+ issue: value.issue,
2323
+ ...(typeof value.requiredChange === "string" && value.requiredChange.trim() ? { requiredChange: value.requiredChange } : {}),
2324
+ }];
2325
+ }).slice(0, 16)
2326
+ : [];
2327
+ const evidenceRefs = Array.isArray(fact?.evidenceRefs)
2328
+ ? fact.evidenceRefs.filter((ref) => typeof ref === "string" && ref.trim().length > 0).slice(0, 16)
2329
+ : [];
2330
+ return { findings, evidenceRefs };
2310
2331
  }
2311
2332
  catch {
2312
- return 0;
2333
+ return { findings: [], evidenceRefs: [] };
2313
2334
  }
2314
2335
  }
2315
2336
  async function collectDesignRestartPhase(runDir, designNodeId) {
@@ -2623,17 +2644,15 @@ async function executeFrontendWriterAdmission(input, meta) {
2623
2644
  const shell = input.task.shell;
2624
2645
  const config = shell.frontendWriterAdmission;
2625
2646
  try {
2626
- // 1. Design verdict handling (r12 policy change). The typed terminal
2647
+ // 1. Design verdict handling. The typed terminal
2627
2648
  // verdict is read from the committed typed design terminal fact, not
2628
2649
  // from the legacy first-line VERDICT text protocol (AC-003/AC-004).
2629
- // A request_design_changes verdict no longer blocks writer admission:
2630
- // it is downgraded to an advisory that rides along in the admission
2631
- // receipt (and reaches implement via the review node's upstream
2632
- // context). Blocking is owned by the deterministic gates (prewrite,
2633
- // write guard) and the final code review. A missing typed terminal
2634
- // verdict still fails closed. In small topology the design review node
2635
- // is absent and the check is vacuous.
2650
+ // A request_design_changes verdict blocks writer admission and is copied
2651
+ // into the receipt as a bounded advisory capsule for recovery. A missing
2652
+ // typed terminal verdict still fails closed. In small topology the design
2653
+ // review node is absent and the check is vacuous.
2636
2654
  let verdictPass = true;
2655
+ let designRequested = false;
2637
2656
  let designAdvisory;
2638
2657
  if (config.designReviewFromNodeId) {
2639
2658
  const designKinds = await collectDesignTerminalKinds(meta.runDir, config.designReviewFromNodeId);
@@ -2641,11 +2660,16 @@ async function executeFrontendWriterAdmission(input, meta) {
2641
2660
  verdictPass = false;
2642
2661
  }
2643
2662
  else if (designKinds[0] !== "approve_design") {
2663
+ designRequested = true;
2664
+ const designFindings = await collectDesignReviewFindings(meta.runDir, config.designReviewFromNodeId);
2644
2665
  designAdvisory = {
2645
- verdict: designKinds[0],
2666
+ verdict: "request_design_changes",
2646
2667
  restartPhase: await collectDesignRestartPhase(meta.runDir, config.designReviewFromNodeId),
2647
- findingCount: await collectDesignReviewFindingCount(meta.runDir, config.designReviewFromNodeId),
2668
+ findingCount: designFindings.findings.length,
2669
+ findings: designFindings.findings,
2670
+ evidenceRefs: designFindings.evidenceRefs,
2648
2671
  };
2672
+ verdictPass = false;
2649
2673
  }
2650
2674
  }
2651
2675
  // 2. Read the canonical contract + design-policy result.
@@ -2713,7 +2737,9 @@ async function executeFrontendWriterAdmission(input, meta) {
2713
2737
  allowedMockStrategies: config.allowedMockStrategies,
2714
2738
  workspaceFiles,
2715
2739
  });
2716
- // 6. Design verdict enforcement: request-revision is a blocked admission.
2740
+ // 6. Design verdict enforcement: request_design_changes is a blocked
2741
+ // admission. This keeps the writer fail-closed until the recovery plan
2742
+ // has incorporated the reviewer's concrete findings.
2717
2743
  const failureSource = policyRaw.classification === "blocked"
2718
2744
  ? "frontend-design-policy-shell"
2719
2745
  : "frontend-writer-admission-shell";
@@ -2722,8 +2748,12 @@ async function executeFrontendWriterAdmission(input, meta) {
2722
2748
  : [
2723
2749
  ...admission.findings,
2724
2750
  {
2725
- code: "design-approval-missing",
2726
- message: "design review typed terminal verdict missing or ambiguous (fail closed)",
2751
+ code: designRequested
2752
+ ? "design-review-requested"
2753
+ : "design-approval-missing",
2754
+ message: designRequested
2755
+ ? "design review requested changes; writer admission is blocked until recovery incorporates the committed findings"
2756
+ : "design review typed terminal verdict missing or ambiguous (fail closed)",
2727
2757
  },
2728
2758
  ];
2729
2759
  const classification = findings.length === 0 && admission.classification === "accepted"
@@ -2347,6 +2347,16 @@ export class ConsolePiRuntime {
2347
2347
  });
2348
2348
  }
2349
2349
  catch (error) {
2350
+ const code = error && typeof error === "object" && "code" in error
2351
+ ? String(error.code ?? "")
2352
+ : "";
2353
+ if (code === "SESSION_FILE_MISSING" || code === "ENOENT") {
2354
+ return {
2355
+ ok: false,
2356
+ code: "NO_SESSION_FILE",
2357
+ message: `Pi session file missing or not openable: ${init.sessionFile}`,
2358
+ };
2359
+ }
2350
2360
  return {
2351
2361
  ok: false,
2352
2362
  code: "REOPEN_FAILED",
@@ -3228,13 +3238,19 @@ export function createDefaultPiSdkBindings() {
3228
3238
  // wrong conversation under the durable Console sessionId. Fail closed
3229
3239
  // before open when the persisted file is absent.
3230
3240
  await access(opts.sessionFile);
3231
- return sdk.SessionManager.open(opts.sessionFile);
3232
3241
  }
3233
- catch {
3234
- // Missing/corrupt session file — caller fails closed rather than
3235
- // silently creating a fresh session and losing prior context.
3242
+ catch (error) {
3243
+ const code = error && typeof error === "object" && "code" in error
3244
+ ? String(error.code ?? "")
3245
+ : "";
3246
+ if (code === "ENOENT") {
3247
+ throw Object.assign(new Error(`SESSION_FILE_MISSING: ${opts.sessionFile}`), { code: "SESSION_FILE_MISSING", cause: error });
3248
+ }
3249
+ // File exists but is unreadable (permissions / I/O) — caller maps
3250
+ // undefined to 503 rather than silently creating a fresh session.
3236
3251
  return undefined;
3237
3252
  }
3253
+ return sdk.SessionManager.open(opts.sessionFile);
3238
3254
  },
3239
3255
  resolveModel: (opts) => {
3240
3256
  const rt = opts.modelRuntime;
@@ -13,6 +13,7 @@
13
13
  * Read routes (GET capabilities / sessions) are open to loopback.
14
14
  */
15
15
  import { randomUUID } from "node:crypto";
16
+ import { access } from "node:fs/promises";
16
17
  import { parseAssistantContent } from "./assistant-content.js";
17
18
  import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_DENIED_TOOLS, OPERATOR_CHAT_DENIED_OPERATOR_ACTIONS, } from "./tools.js";
18
19
  import { buildModelCallableToolSchemas } from "./tool-adapter.js";
@@ -474,7 +475,7 @@ function sendRuntimeUnavailable(res, error) {
474
475
  * handled here — only reopen/init readiness failures.
475
476
  */
476
477
  function mapReopenSessionFailure(reopen) {
477
- if (reopen.code === "NO_SESSION_FILE") {
478
+ if (reopen.code === "NO_SESSION_FILE" || reopen.code === "SESSION_FILE_MISSING") {
478
479
  return {
479
480
  status: 404,
480
481
  code: "PI_SESSION_NOT_FOUND",
@@ -494,6 +495,19 @@ function mapReopenSessionFailure(reopen) {
494
495
  message: reopen.message,
495
496
  };
496
497
  }
498
+ /** Drop an orphan catalog record whose promised Pi JSONL is gone. */
499
+ async function healMissingSessionFile(deps, sessionId, reopen) {
500
+ if (reopen && reopen.code !== "NO_SESSION_FILE" && reopen.code !== "SESSION_FILE_MISSING") {
501
+ return;
502
+ }
503
+ if (typeof deps.store.remove !== "function")
504
+ return;
505
+ await deps.store.remove(sessionId).catch(() => undefined);
506
+ }
507
+ async function mapFailedReopen(deps, sessionId, reopen) {
508
+ await healMissingSessionFile(deps, sessionId, reopen);
509
+ return mapReopenSessionFailure(reopen);
510
+ }
497
511
  export async function handleChatCapabilities(_req, res, deps) {
498
512
  const toolSchemas = buildModelCallableToolSchemas();
499
513
  sendJson(res, 200, {
@@ -738,10 +752,7 @@ export async function handleBranchChatSession(req, res, deps, sessionId) {
738
752
  sessionFile: record.sessionFile,
739
753
  });
740
754
  if (!reopened.ok) {
741
- const mapped = mapReopenSessionFailure({
742
- code: reopened.code,
743
- message: reopened.message,
744
- });
755
+ const mapped = await mapFailedReopen(deps, sessionId, reopened);
745
756
  sendJson(res, mapped.status, {
746
757
  ok: false,
747
758
  error: { code: mapped.code, message: mapped.message },
@@ -816,6 +827,29 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
816
827
  });
817
828
  return;
818
829
  }
830
+ if (record.sessionFile) {
831
+ try {
832
+ await access(record.sessionFile);
833
+ }
834
+ catch (error) {
835
+ const code = error && typeof error === "object" && "code" in error
836
+ ? String(error.code ?? "")
837
+ : "";
838
+ if (code === "ENOENT") {
839
+ await healMissingSessionFile(deps, sessionId, {
840
+ code: "NO_SESSION_FILE",
841
+ });
842
+ sendJson(res, 404, {
843
+ ok: false,
844
+ error: {
845
+ code: "PI_SESSION_NOT_FOUND",
846
+ message: `Pi session file missing or not openable: ${record.sessionFile}`,
847
+ },
848
+ });
849
+ return;
850
+ }
851
+ }
852
+ }
819
853
  await deps.operationLinker.recoverSession(sessionId);
820
854
  const composerDraft = await new ComposerDraftStore(deps.appData).get(sessionId);
821
855
  const snapshot = deps.events.snapshot(sessionId);
@@ -1494,10 +1528,7 @@ export async function handleReopenChatSession(req, res, deps, sessionId) {
1494
1528
  : {}),
1495
1529
  });
1496
1530
  if (!result.ok) {
1497
- const mapped = mapReopenSessionFailure({
1498
- code: result.code,
1499
- message: result.message,
1500
- });
1531
+ const mapped = await mapFailedReopen(deps, sessionId, result);
1501
1532
  sendJson(res, mapped.status, {
1502
1533
  ok: false,
1503
1534
  error: { code: mapped.code, message: mapped.message },
@@ -1583,10 +1614,7 @@ export async function handleChatPrompt(req, res, deps, sessionId) {
1583
1614
  : {}),
1584
1615
  });
1585
1616
  if (!reopen.ok) {
1586
- const mapped = mapReopenSessionFailure({
1587
- code: reopen.code,
1588
- message: reopen.message,
1589
- });
1617
+ const mapped = await mapFailedReopen(deps, sessionId, reopen);
1590
1618
  sendJson(res, mapped.status, {
1591
1619
  ok: false,
1592
1620
  error: { code: mapped.code, message: mapped.message },
@@ -1855,10 +1883,7 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
1855
1883
  : {}),
1856
1884
  });
1857
1885
  if (!reopen.ok) {
1858
- const mapped = mapReopenSessionFailure({
1859
- code: reopen.code,
1860
- message: reopen.message,
1861
- });
1886
+ const mapped = await mapFailedReopen(deps, sessionId, reopen);
1862
1887
  sendJson(res, mapped.status, {
1863
1888
  ok: false,
1864
1889
  accepted: false,
@@ -3050,7 +3075,7 @@ export async function ensureActiveSession(deps, sessionId) {
3050
3075
  : {}),
3051
3076
  });
3052
3077
  if (!reopened.ok) {
3053
- return { ok: false, ...mapReopenSessionFailure(reopened) };
3078
+ return { ok: false, ...(await mapFailedReopen(deps, sessionId, reopened)) };
3054
3079
  }
3055
3080
  return { ok: true };
3056
3081
  }
@@ -3997,10 +4022,7 @@ export async function handleChatCompact(req, res, deps, sessionId) {
3997
4022
  : {}),
3998
4023
  });
3999
4024
  if (!reopened.ok) {
4000
- const mapped = mapReopenSessionFailure({
4001
- code: reopened.code,
4002
- message: reopened.message,
4003
- });
4025
+ const mapped = await mapFailedReopen(deps, sessionId, reopened);
4004
4026
  sendJson(res, mapped.status, {
4005
4027
  ok: false,
4006
4028
  error: { code: mapped.code, message: mapped.message },
@@ -47,6 +47,16 @@ export function cleanChatTitle(value) {
47
47
  function recordPath(appData, sessionId) {
48
48
  return path.join(appData.chats, `${sessionId}.json`);
49
49
  }
50
+ /** True when `sessionFile` lives inside this store's dedicated Pi JSONL dir. */
51
+ export function isConsoleOwnedSessionFile(appData, sessionFile) {
52
+ const value = sessionFile?.trim();
53
+ if (!value)
54
+ return false;
55
+ const root = path.resolve(appData.chatSessions);
56
+ const target = path.resolve(value);
57
+ const relative = path.relative(root, target);
58
+ return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative);
59
+ }
50
60
  export class ChatSessionStore {
51
61
  appData;
52
62
  options;
@@ -263,8 +273,14 @@ export class ChatSessionStore {
263
273
  const record = await this.get(sessionId);
264
274
  if (!record)
265
275
  return false;
276
+ const sessionFile = record.sessionFile;
266
277
  await rm(recordPath(this.appData, sessionId), { force: true });
267
278
  this.options.onRecordChange?.(sessionId, undefined);
279
+ // Record first, then best-effort JSONL unlink. Final consistency is
280
+ // "both gone" — a leftover JSONL is harmless, a leftover record is not.
281
+ if (isConsoleOwnedSessionFile(this.appData, sessionFile) && sessionFile) {
282
+ await rm(sessionFile, { force: true }).catch(() => undefined);
283
+ }
268
284
  return true;
269
285
  });
270
286
  }
@@ -567,8 +567,26 @@ export async function createConsoleServer(options) {
567
567
  const stored = await context.chatRouteDeps.store.get(sessionId);
568
568
  if (stored)
569
569
  owner = context;
570
+ else if (!catalogEntry) {
571
+ // Catalog and store both miss: the session was deleted, not
572
+ // retargeted. Do not scan other workspaces or leak existence.
573
+ sendJson(res, 404, {
574
+ ok: false,
575
+ error: {
576
+ code: "PI_SESSION_NOT_FOUND",
577
+ message: `chat session not found: ${sessionId}`,
578
+ },
579
+ });
580
+ return;
581
+ }
570
582
  else {
571
- // Do not scan another workspace and disclose whether the id exists there.
583
+ chatSessionCatalog.remove(sessionId);
584
+ await registry
585
+ .detachSession(ownerRecord.workspaceId, sessionId)
586
+ .catch(() => undefined);
587
+ // Catalog had the id, but this workspace store does not.
588
+ // Keep the conflict code so a mismatched selection cannot
589
+ // discover whether the session lives elsewhere.
572
590
  sendJson(res, 403, {
573
591
  ok: false,
574
592
  error: {
@@ -1 +1 @@
1
- import{g as p,r as u,d as a}from"./chunk-6Q2QTUOP-DpZQfFTq.js";import{p as f}from"./chunk-JWPE2WC7-CFsCFoF4.js";import{_ as n,l as o}from"./mermaid.core-Bagus6zF.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-DFmv38cl.js";import"./index-Dkv0ezuE.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};
1
+ import{g as p,r as u,d as a}from"./chunk-6Q2QTUOP-DRLjDL0k.js";import{p as f}from"./chunk-JWPE2WC7-BNCZs7_z.js";import{_ as n,l as o}from"./mermaid.core-CWhj4JXN.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-l9j_ztZH.js";import"./index-D9Sc0f0y.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};
@@ -1 +1 @@
1
- import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-Bagus6zF.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
1
+ import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-CWhj4JXN.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};