@chibitek/chibi 0.1.160 → 0.1.162

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 (72) hide show
  1. package/dist/assets/{abnfDiagram-O67JEVCF-CpuRvzQ3.js → abnfDiagram-O67JEVCF-D0_WX2x_.js} +1 -1
  2. package/dist/assets/{arc-ko1e0R6h.js → arc-lzBXqTbJ.js} +1 -1
  3. package/dist/assets/{architectureDiagram-NJMV4G6O-OZ3SsOOa.js → architectureDiagram-NJMV4G6O-KQZbO2M3.js} +1 -1
  4. package/dist/assets/{blockDiagram-BEXU5L5S-DLRkBkjs.js → blockDiagram-BEXU5L5S-zb3pW64Y.js} +1 -1
  5. package/dist/assets/{c4Diagram-YGBWAQC7-e8Q3qMnw.js → c4Diagram-YGBWAQC7-CADaXczd.js} +1 -1
  6. package/dist/assets/channel-Br57lm8F.js +1 -0
  7. package/dist/assets/{chunk-2Q5K7J3B-hCDqaM4Q.js → chunk-2Q5K7J3B-DJP27cEp.js} +1 -1
  8. package/dist/assets/{chunk-3FUC2YCW-Dy3Pahu-.js → chunk-3FUC2YCW-Cp9Z7Ybl.js} +2 -2
  9. package/dist/assets/{chunk-5VM5RSS4-QMK3iFBF.js → chunk-5VM5RSS4-DjXxom_0.js} +1 -1
  10. package/dist/assets/{chunk-GTNCS2PH-6fyp9ERp.js → chunk-GTNCS2PH-DpEJ08zA.js} +1 -1
  11. package/dist/assets/{chunk-GWA4HPMP-DWvOhTaO.js → chunk-GWA4HPMP-CQ4Kadr-.js} +1 -1
  12. package/dist/assets/{chunk-JWPE2WC7-DgohMW2S.js → chunk-JWPE2WC7-CwXmVMdh.js} +1 -1
  13. package/dist/assets/{chunk-NETBCI7D-sRGeD2Y7.js → chunk-NETBCI7D-Vl_kib_R.js} +1 -1
  14. package/dist/assets/{chunk-WEXAMYUT-CZslD_VF.js → chunk-WEXAMYUT-BhAVbfJe.js} +1 -1
  15. package/dist/assets/{chunk-XXDRQBXY-D7ocO3VN.js → chunk-XXDRQBXY-s1a4nKQE.js} +1 -1
  16. package/dist/assets/{classDiagram-v2-NBCMYWYE-T1lLqHHW.js → classDiagram-v2-NBCMYWYE-DeGQSNky.js} +1 -1
  17. package/dist/assets/{cose-bilkent-JH36ORCC-BdeLiJr3.js → cose-bilkent-JH36ORCC-D5sUHYFG.js} +1 -1
  18. package/dist/assets/{cynefin-OW5HDTMX-BydCA0pv.js → cynefin-OW5HDTMX-DmTvN9T2.js} +1 -1
  19. package/dist/assets/{cynefinDiagram-VND7K2PF-DBxckjwQ.js → cynefinDiagram-VND7K2PF-Cqtt1jCF.js} +1 -1
  20. package/dist/assets/{dagre-6A5THRUB-CAsvq6Zk.js → dagre-6A5THRUB-DRZKkSJg.js} +1 -1
  21. package/dist/assets/{diagram-22UHCM2B-DTwH3rA_.js → diagram-22UHCM2B-1od4dKOU.js} +1 -1
  22. package/dist/assets/{diagram-3UASUU5V-BNYj1bey.js → diagram-3UASUU5V-D3_yCQOJ.js} +1 -1
  23. package/dist/assets/{diagram-ATOU4E4O-CihwYFxb.js → diagram-ATOU4E4O-D9UgsVGC.js} +1 -1
  24. package/dist/assets/{diagram-CDSNMT55-nouwk3s2.js → diagram-CDSNMT55-DMxsJ_X5.js} +1 -1
  25. package/dist/assets/{diagram-MLGK6HIB-BoKewJeA.js → diagram-MLGK6HIB-CK47Pp3D.js} +1 -1
  26. package/dist/assets/{diagram-MPIPVDR6-CrE1m9DQ.js → diagram-MPIPVDR6-D0axYDk-.js} +1 -1
  27. package/dist/assets/{ebnfDiagram-ZINNZB2B-DRj0aU4Y.js → ebnfDiagram-ZINNZB2B-hkbV8j2o.js} +1 -1
  28. package/dist/assets/{elk-276RUBZZ-Do0JzCG5.js → elk-276RUBZZ-DL9jncwy.js} +1 -1
  29. package/dist/assets/{erDiagram-OPXOYQCR-CRsI9pXb.js → erDiagram-OPXOYQCR-etb_6SR6.js} +1 -1
  30. package/dist/assets/{flowDiagram-KWPJA3E3-dtD6wm3G.js → flowDiagram-KWPJA3E3-4Dgg4I8f.js} +1 -1
  31. package/dist/assets/{ganttDiagram-FUAMR5RP-Cn7KyLCo.js → ganttDiagram-FUAMR5RP-DRBB4AaO.js} +1 -1
  32. package/dist/assets/{gitGraphDiagram-X574FWY7-CGH_VWzq.js → gitGraphDiagram-X574FWY7-CzgKN-tE.js} +1 -1
  33. package/dist/assets/{index-Bk8iAodB.js → index-BT2giPxq.js} +1 -1
  34. package/dist/assets/{index-BIcziktH.js → index-fHtcwzVK.js} +179 -179
  35. package/dist/assets/{infoDiagram-VRGFBTTK-CQKsYTCL.js → infoDiagram-VRGFBTTK-DwpjLCMe.js} +1 -1
  36. package/dist/assets/{ishikawaDiagram-OU5B5YK6-D5catDe0.js → ishikawaDiagram-OU5B5YK6-H-CWnU1-.js} +1 -1
  37. package/dist/assets/{journeyDiagram-ZHPQQLJL-KjY8mki9.js → journeyDiagram-ZHPQQLJL-CY-oDrpq.js} +1 -1
  38. package/dist/assets/{kanban-definition-PNTS6WVX-BKCyJtge.js → kanban-definition-PNTS6WVX-DMGIKH3g.js} +1 -1
  39. package/dist/assets/{linear-B-_obUkq.js → linear-BHhtTLK8.js} +1 -1
  40. package/dist/assets/{mermaid.core-DlSzuDKt.js → mermaid.core-BWK_Ui3n.js} +5 -5
  41. package/dist/assets/{mindmap-definition-NLK3R4M7-BFQSaYKh.js → mindmap-definition-NLK3R4M7-Cc17hw2c.js} +1 -1
  42. package/dist/assets/{pegDiagram-GJSIUBJH-l51izlev.js → pegDiagram-GJSIUBJH-BqFjUBJK.js} +1 -1
  43. package/dist/assets/{pieDiagram-5QR66LMP-6NEXRoZX.js → pieDiagram-5QR66LMP-ikmgTCa3.js} +1 -1
  44. package/dist/assets/{quadrantDiagram-O4NWA36T-BCC9XlOe.js → quadrantDiagram-O4NWA36T-CC3s5Dsc.js} +1 -1
  45. package/dist/assets/{railroadDiagram-XR7U4H2S-Dd4TmJ86.js → railroadDiagram-XR7U4H2S-Dtwf869F.js} +1 -1
  46. package/dist/assets/{requirementDiagram-PLB6GJNP-C1E5DaDb.js → requirementDiagram-PLB6GJNP-C5p7AgwQ.js} +1 -1
  47. package/dist/assets/{sankeyDiagram-IPEJSGJF-Ci6r7abb.js → sankeyDiagram-IPEJSGJF-8NKN770X.js} +1 -1
  48. package/dist/assets/{sequenceDiagram-PO4LG4MO-BJ-I6WOh.js → sequenceDiagram-PO4LG4MO-DQucgrWO.js} +1 -1
  49. package/dist/assets/{sizeCapture-INFHLROL-Cs5QbLOP.js → sizeCapture-INFHLROL-Cxmcj9Nl.js} +1 -1
  50. package/dist/assets/{stateDiagram-v2-GCMORJYK-Bw--rdvq.js → stateDiagram-v2-GCMORJYK-DEdvHXnJ.js} +1 -1
  51. package/dist/assets/{swimlanes-2SLR337P-B1O9IEz5.js → swimlanes-2SLR337P-BOGePqj1.js} +1 -1
  52. package/dist/assets/swimlanesDiagram-TC7HE7FX-BSe9TZ6t.js +8 -0
  53. package/dist/assets/{timeline-definition-EJHVYXUP-CTZ7DXaw.js → timeline-definition-EJHVYXUP-DfpRxJgE.js} +1 -1
  54. package/dist/assets/{usecaseDiagram-POWQR4AR-DjwgNG0H.js → usecaseDiagram-POWQR4AR-BOmQFDT4.js} +1 -1
  55. package/dist/assets/{vennDiagram-UO4OBE2U-DrdJG55h.js → vennDiagram-UO4OBE2U-84pfbvUi.js} +1 -1
  56. package/dist/assets/{wardleyDiagram-VNRHLVJA-HVq776nz.js → wardleyDiagram-VNRHLVJA-Cbxnw8ip.js} +1 -1
  57. package/dist/assets/{xychartDiagram-PMCCYNJV-DPf9VEua.js → xychartDiagram-PMCCYNJV-BLiz8vqY.js} +1 -1
  58. package/dist/index.html +1 -1
  59. package/dist-server/container-mcp.js +7 -6
  60. package/dist-server/index.js +1176 -927
  61. package/dist-server/openmausbot.js +31 -9
  62. package/dist-server/pair-cli.js +31 -9
  63. package/dist-server/server/checkpoint-recovery.js +18 -0
  64. package/dist-server/server/config.js +6 -6
  65. package/dist-server/server/drivers/acp/core.js +17 -17
  66. package/dist-server/server/engine-failover.js +4 -4
  67. package/dist-server/server/index.js +9 -8
  68. package/dist-server/server/routine-mochii-sync.js +252 -0
  69. package/dist-server/vps-container-mcp.js +7 -6
  70. package/package.json +1 -1
  71. package/dist/assets/channel-Lp7erhx-.js +0 -1
  72. package/dist/assets/swimlanesDiagram-TC7HE7FX-C6wGgYKP.js +0 -8
@@ -20622,18 +20622,19 @@ var init_config = __esm({
20622
20622
  * marketplace checks) and personal skills (~/.agents/skills,
20623
20623
  * $CODEX_HOME/skills). */
20624
20624
  codexUserMcp: external_exports.boolean().optional(),
20625
- /** LLM-generated titles for new bot threads. Off until explicitly
20626
- * enabled; a one-shot that fails or answers junk leaves the first-message
20627
- * snippet in place — see llmThreadTitlesEnabled. */
20625
+ /** LLM-generated titles for new bot threads. Off until explicitly enabled; a one-shot
20626
+ * that fails or answers junk leaves the first-message snippet in place — see llmThreadTitlesEnabled. */
20628
20627
  llmThreadTitles: external_exports.boolean().optional(),
20629
- /** Small internal jobs (thread titles) run on Apple Intelligence on this
20630
- * Mac when it is available. On unless set to false. */
20631
- onDeviceHelpers: external_exports.boolean().optional()
20628
+ /** Small internal jobs (thread titles) run on Apple Intelligence on this Mac when available. On unless false. */
20629
+ onDeviceHelpers: external_exports.boolean().optional(),
20630
+ /** Mirror routines into private Mochii tasks (server/routine-mochii-sync.ts). Off unless true; no Settings toggle. */
20631
+ routineMochiiSync: external_exports.boolean().optional()
20632
20632
  });
20633
20633
  addonsConfigSchema = external_exports.object(
20634
20634
  Object.fromEntries(ADDONS.map((addon) => [addon.id, external_exports.boolean().optional()]))
20635
20635
  ).strict();
20636
20636
  setupProgressSchema = external_exports.object({
20637
+ foundationRoles: external_exports.array(external_exports.enum(["assistant", "inbox", "research", "coder", "community", "ops"])).max(3).optional(),
20637
20638
  picks: external_exports.array(external_exports.string().trim().min(1).max(40)).max(20).optional(),
20638
20639
  chibitek: external_exports.boolean().optional(),
20639
20640
  steps: external_exports.record(external_exports.string().trim().min(1).max(60), external_exports.enum(["done", "skipped"])).refine((steps) => Object.keys(steps).length <= 60, "too many setup steps").optional(),
@@ -25444,6 +25445,19 @@ var init_mcp_tool_ref = __esm({
25444
25445
  }
25445
25446
  });
25446
25447
 
25448
+ // server/checkpoint-recovery.ts
25449
+ function isBrokenAntigravityCheckpoint(driverKind, message2) {
25450
+ return driverKind === "antigravityAgent" && message2.includes("could not find doneCh for checkpoint");
25451
+ }
25452
+ function isAntigravityCheckpointReply(driverKind, text4) {
25453
+ return driverKind === "antigravityAgent" && /^Agent execution error: could not find doneCh for checkpoint\s*$/.test(text4.trim());
25454
+ }
25455
+ var init_checkpoint_recovery = __esm({
25456
+ "server/checkpoint-recovery.ts"() {
25457
+ "use strict";
25458
+ }
25459
+ });
25460
+
25447
25461
  // server/resume-recovery.ts
25448
25462
  function classifyResumeFailure(state2) {
25449
25463
  if (state2.producedOutput || state2.promptSubmitted) return "after-accept";
@@ -26428,6 +26442,12 @@ function createAcpDriver(support12) {
26428
26442
  session.acp.failAll(new Error("turn settled"));
26429
26443
  session.current = null;
26430
26444
  active.delete(threadId);
26445
+ if (isAntigravityCheckpointReply(DRIVER_KIND8, current.state.text)) {
26446
+ ok = false;
26447
+ stopReason = "invalid_checkpoint";
26448
+ emit({ ...base3(threadId, current.turnId), type: "runtime.error", message: current.state.text.trim() });
26449
+ closeSession(threadId, "invalid-checkpoint");
26450
+ }
26431
26451
  current.flushAssistantText();
26432
26452
  let finalOk = ok;
26433
26453
  let finalStopReason = stopReason;
@@ -27234,7 +27254,8 @@ ${instructed.text}` : instructed.text;
27234
27254
  type: "runtime.error",
27235
27255
  message: errorMessage
27236
27256
  });
27237
- settle(threadId, session, false, reason ?? "failed");
27257
+ if (isBrokenAntigravityCheckpoint(DRIVER_KIND8, errorMessage)) closeSession(threadId, "invalid-checkpoint");
27258
+ settle(threadId, session, false, isBrokenAntigravityCheckpoint(DRIVER_KIND8, errorMessage) ? "invalid_checkpoint" : reason ?? "failed");
27238
27259
  }
27239
27260
  } catch (e) {
27240
27261
  if (!state2.settled) {
@@ -27251,10 +27272,10 @@ ${instructed.text}` : instructed.text;
27251
27272
  ...needsAuth ? { setup: true } : {},
27252
27273
  ...failure ? { failure } : {}
27253
27274
  });
27254
- if ((logAuth || !needsAuth && (stalled || e?.acpSessionFailure === true || code === "upstream_outage")) && session.child.exitCode === null && !session.closing) {
27275
+ if ((logAuth || !needsAuth && (stalled || isBrokenAntigravityCheckpoint(DRIVER_KIND8, message2) || e?.acpSessionFailure === true || code === "upstream_outage")) && session.child.exitCode === null && !session.closing) {
27255
27276
  closeSession(threadId, logAuth ? "provider-auth" : stalled ? "prompt-stall" : "rpc-failure");
27256
27277
  }
27257
- settle(threadId, session, false, needsAuth ? "auth_required" : "rpc_error");
27278
+ settle(threadId, session, false, needsAuth ? "auth_required" : isBrokenAntigravityCheckpoint(DRIVER_KIND8, message2) ? "invalid_checkpoint" : "rpc_error");
27258
27279
  }
27259
27280
  }
27260
27281
  })();
@@ -27343,6 +27364,7 @@ var init_core3 = __esm({
27343
27364
  init_mcp_tool_images();
27344
27365
  init_mcp_tool_ref();
27345
27366
  init_redact2();
27367
+ init_checkpoint_recovery();
27346
27368
  init_resume_recovery();
27347
27369
  init_retry();
27348
27370
  init_engine_failure();
@@ -20622,18 +20622,19 @@ var init_config = __esm({
20622
20622
  * marketplace checks) and personal skills (~/.agents/skills,
20623
20623
  * $CODEX_HOME/skills). */
20624
20624
  codexUserMcp: external_exports.boolean().optional(),
20625
- /** LLM-generated titles for new bot threads. Off until explicitly
20626
- * enabled; a one-shot that fails or answers junk leaves the first-message
20627
- * snippet in place — see llmThreadTitlesEnabled. */
20625
+ /** LLM-generated titles for new bot threads. Off until explicitly enabled; a one-shot
20626
+ * that fails or answers junk leaves the first-message snippet in place — see llmThreadTitlesEnabled. */
20628
20627
  llmThreadTitles: external_exports.boolean().optional(),
20629
- /** Small internal jobs (thread titles) run on Apple Intelligence on this
20630
- * Mac when it is available. On unless set to false. */
20631
- onDeviceHelpers: external_exports.boolean().optional()
20628
+ /** Small internal jobs (thread titles) run on Apple Intelligence on this Mac when available. On unless false. */
20629
+ onDeviceHelpers: external_exports.boolean().optional(),
20630
+ /** Mirror routines into private Mochii tasks (server/routine-mochii-sync.ts). Off unless true; no Settings toggle. */
20631
+ routineMochiiSync: external_exports.boolean().optional()
20632
20632
  });
20633
20633
  addonsConfigSchema = external_exports.object(
20634
20634
  Object.fromEntries(ADDONS.map((addon) => [addon.id, external_exports.boolean().optional()]))
20635
20635
  ).strict();
20636
20636
  setupProgressSchema = external_exports.object({
20637
+ foundationRoles: external_exports.array(external_exports.enum(["assistant", "inbox", "research", "coder", "community", "ops"])).max(3).optional(),
20637
20638
  picks: external_exports.array(external_exports.string().trim().min(1).max(40)).max(20).optional(),
20638
20639
  chibitek: external_exports.boolean().optional(),
20639
20640
  steps: external_exports.record(external_exports.string().trim().min(1).max(60), external_exports.enum(["done", "skipped"])).refine((steps) => Object.keys(steps).length <= 60, "too many setup steps").optional(),
@@ -25444,6 +25445,19 @@ var init_mcp_tool_ref = __esm({
25444
25445
  }
25445
25446
  });
25446
25447
 
25448
+ // server/checkpoint-recovery.ts
25449
+ function isBrokenAntigravityCheckpoint(driverKind, message2) {
25450
+ return driverKind === "antigravityAgent" && message2.includes("could not find doneCh for checkpoint");
25451
+ }
25452
+ function isAntigravityCheckpointReply(driverKind, text4) {
25453
+ return driverKind === "antigravityAgent" && /^Agent execution error: could not find doneCh for checkpoint\s*$/.test(text4.trim());
25454
+ }
25455
+ var init_checkpoint_recovery = __esm({
25456
+ "server/checkpoint-recovery.ts"() {
25457
+ "use strict";
25458
+ }
25459
+ });
25460
+
25447
25461
  // server/resume-recovery.ts
25448
25462
  function classifyResumeFailure(state2) {
25449
25463
  if (state2.producedOutput || state2.promptSubmitted) return "after-accept";
@@ -26428,6 +26442,12 @@ function createAcpDriver(support12) {
26428
26442
  session.acp.failAll(new Error("turn settled"));
26429
26443
  session.current = null;
26430
26444
  active.delete(threadId);
26445
+ if (isAntigravityCheckpointReply(DRIVER_KIND8, current.state.text)) {
26446
+ ok = false;
26447
+ stopReason = "invalid_checkpoint";
26448
+ emit({ ...base3(threadId, current.turnId), type: "runtime.error", message: current.state.text.trim() });
26449
+ closeSession(threadId, "invalid-checkpoint");
26450
+ }
26431
26451
  current.flushAssistantText();
26432
26452
  let finalOk = ok;
26433
26453
  let finalStopReason = stopReason;
@@ -27234,7 +27254,8 @@ ${instructed.text}` : instructed.text;
27234
27254
  type: "runtime.error",
27235
27255
  message: errorMessage
27236
27256
  });
27237
- settle(threadId, session, false, reason ?? "failed");
27257
+ if (isBrokenAntigravityCheckpoint(DRIVER_KIND8, errorMessage)) closeSession(threadId, "invalid-checkpoint");
27258
+ settle(threadId, session, false, isBrokenAntigravityCheckpoint(DRIVER_KIND8, errorMessage) ? "invalid_checkpoint" : reason ?? "failed");
27238
27259
  }
27239
27260
  } catch (e) {
27240
27261
  if (!state2.settled) {
@@ -27251,10 +27272,10 @@ ${instructed.text}` : instructed.text;
27251
27272
  ...needsAuth ? { setup: true } : {},
27252
27273
  ...failure ? { failure } : {}
27253
27274
  });
27254
- if ((logAuth || !needsAuth && (stalled || e?.acpSessionFailure === true || code === "upstream_outage")) && session.child.exitCode === null && !session.closing) {
27275
+ if ((logAuth || !needsAuth && (stalled || isBrokenAntigravityCheckpoint(DRIVER_KIND8, message2) || e?.acpSessionFailure === true || code === "upstream_outage")) && session.child.exitCode === null && !session.closing) {
27255
27276
  closeSession(threadId, logAuth ? "provider-auth" : stalled ? "prompt-stall" : "rpc-failure");
27256
27277
  }
27257
- settle(threadId, session, false, needsAuth ? "auth_required" : "rpc_error");
27278
+ settle(threadId, session, false, needsAuth ? "auth_required" : isBrokenAntigravityCheckpoint(DRIVER_KIND8, message2) ? "invalid_checkpoint" : "rpc_error");
27258
27279
  }
27259
27280
  }
27260
27281
  })();
@@ -27343,6 +27364,7 @@ var init_core3 = __esm({
27343
27364
  init_mcp_tool_images();
27344
27365
  init_mcp_tool_ref();
27345
27366
  init_redact2();
27367
+ init_checkpoint_recovery();
27346
27368
  init_resume_recovery();
27347
27369
  init_retry();
27348
27370
  init_engine_failure();
@@ -0,0 +1,18 @@
1
+ /** This identifies an unusable saved session, never permission to replay a
2
+ * submitted prompt. Only a subsequent explicit turn may rebuild the context. */
3
+ export function isBrokenAntigravityCheckpoint(driverKind, message) {
4
+ return driverKind === "antigravityAgent" && message.includes("could not find doneCh for checkpoint");
5
+ }
6
+ export function invalidateBrokenCheckpoint(store, botId, driverKind, event) {
7
+ if (event.providerInstanceId && isBrokenAntigravityCheckpoint(driverKind, event.message)) {
8
+ // Undefined is absent on disk and engineIsFresh rebuilds from Atomic's
9
+ // transcript on the next send, including after an application restart.
10
+ store.setResumeCursor(botId, event.providerInstanceId, undefined, event.threadId);
11
+ }
12
+ }
13
+ /** Antigravity also sends its execution failure as a chunked assistant reply
14
+ * followed by end_turn. A quoted error inside a normal answer is not failure. */
15
+ export function isAntigravityCheckpointReply(driverKind, text) {
16
+ return driverKind === "antigravityAgent"
17
+ && /^Agent execution error: could not find doneCh for checkpoint\s*$/.test(text.trim());
18
+ }
@@ -277,13 +277,13 @@ const featureConfigSchema = z.object({
277
277
  * marketplace checks) and personal skills (~/.agents/skills,
278
278
  * $CODEX_HOME/skills). */
279
279
  codexUserMcp: z.boolean().optional(),
280
- /** LLM-generated titles for new bot threads. Off until explicitly
281
- * enabled; a one-shot that fails or answers junk leaves the first-message
282
- * snippet in place — see llmThreadTitlesEnabled. */
280
+ /** LLM-generated titles for new bot threads. Off until explicitly enabled; a one-shot
281
+ * that fails or answers junk leaves the first-message snippet in place — see llmThreadTitlesEnabled. */
283
282
  llmThreadTitles: z.boolean().optional(),
284
- /** Small internal jobs (thread titles) run on Apple Intelligence on this
285
- * Mac when it is available. On unless set to false. */
283
+ /** Small internal jobs (thread titles) run on Apple Intelligence on this Mac when available. On unless false. */
286
284
  onDeviceHelpers: z.boolean().optional(),
285
+ /** Mirror routines into private Mochii tasks (server/routine-mochii-sync.ts). Off unless true; no Settings toggle. */
286
+ routineMochiiSync: z.boolean().optional(),
287
287
  });
288
288
  /** Settings → Add-ons: one workspace switch per bundled add-on
289
289
  * (shared/addons.ts). Absent means the add-on's own default. Strict, so a
@@ -293,8 +293,8 @@ const addonsConfigSchema = z.object(Object.fromEntries(ADDONS.map((addon) => [ad
293
293
  * it survives cleared site data and is shared by every paired client. Hint
294
294
  * ids are short renderer-chosen slugs; the list is capped so a buggy client
295
295
  * cannot grow the file without bound. */
296
- /** The setup wizard's progress (src/lib/setup-wizard.ts SetupProgress). */
297
296
  const setupProgressSchema = z.object({
297
+ foundationRoles: z.array(z.enum(["assistant", "inbox", "research", "coder", "community", "ops"])).max(3).optional(),
298
298
  picks: z.array(z.string().trim().min(1).max(40)).max(20).optional(),
299
299
  chibitek: z.boolean().optional(),
300
300
  steps: z.record(z.string().trim().min(1).max(60), z.enum(["done", "skipped"]))
@@ -48,6 +48,7 @@ import { commandSummary, toolDetailPreview } from "../../tool-summary.js";
48
48
  import { extractMcpImages } from "../../mcp-tool-images.js";
49
49
  import { mcpToolRefFromLabel } from "../../../shared/mcp-tool-ref.js";
50
50
  import { redactSecretsInText } from "../../redact.js";
51
+ import { isBrokenAntigravityCheckpoint, isAntigravityCheckpointReply } from "../../checkpoint-recovery.js";
51
52
  import { recoveryPromptFor } from "../../resume-recovery.js";
52
53
  import { isModelRejection } from "../retry.js";
53
54
  import { engineFailureFromCode, isCredentialDead, isOpaqueRequestRejection, logAuthFailure, parseResetAt, resetHintFromData } from "../../engine-failure.js";
@@ -464,14 +465,15 @@ export function createAcpDriver(support) {
464
465
  // turn synchronously must find this session free
465
466
  session.current = null;
466
467
  active.delete(threadId);
468
+ if (isAntigravityCheckpointReply(DRIVER_KIND, current.state.text)) {
469
+ ok = false;
470
+ stopReason = "invalid_checkpoint";
471
+ emit({ ...base(threadId, current.turnId), type: "runtime.error", message: current.state.text.trim() });
472
+ closeSession(threadId, "invalid-checkpoint");
473
+ }
467
474
  current.flushAssistantText();
468
- // `end_turn` with nothing to show for it — no reply, no image, no
469
- // tool result — is a lost turn, not a success. An engine can report
470
- // exactly that (a provider may cut a reasoning-only stream and
471
- // still answer end_turn), and ok:true would end the thread quietly
472
- // while the person's message went unanswered. Keep the completion,
473
- // but report it as a failure so terminal chips, incidents and
474
- // follow-ups see what happened.
475
+ // A successful end_turn without a reply or tool result is a lost
476
+ // turn. Report failure so terminal chips and follow-ups reflect it.
475
477
  let finalOk = ok;
476
478
  let finalStopReason = stopReason;
477
479
  if (finalOk && finalStopReason === null && !current.state.producedItem) {
@@ -1467,7 +1469,9 @@ export function createAcpDriver(support) {
1467
1469
  type: "runtime.error",
1468
1470
  message: errorMessage,
1469
1471
  });
1470
- settle(threadId, session, false, reason ?? "failed");
1472
+ if (isBrokenAntigravityCheckpoint(DRIVER_KIND, errorMessage))
1473
+ closeSession(threadId, "invalid-checkpoint");
1474
+ settle(threadId, session, false, isBrokenAntigravityCheckpoint(DRIVER_KIND, errorMessage) ? "invalid_checkpoint" : reason ?? "failed");
1471
1475
  }
1472
1476
  }
1473
1477
  catch (e) {
@@ -1492,18 +1496,14 @@ export function createAcpDriver(support) {
1492
1496
  ...(needsAuth ? { setup: true } : {}),
1493
1497
  ...(failure ? { failure } : {}),
1494
1498
  });
1495
- // Internal RPC failures can leave a live child poisoned just
1496
- // like a silent prompt. Evict before completion listeners can
1497
- // start the next turn. Never replay this accepted prompt: it
1498
- // may already have executed tools. The next explicit turn can
1499
- // resume the recorded session on a fresh process.
1500
- // An agent refused by its provider mid-prompt is still running
1501
- // (retrying on its own): it goes too.
1502
- if ((logAuth || (!needsAuth && (stalled || e?.acpSessionFailure === true
1499
+ // Evict unusable sessions without replaying accepted prompts.
1500
+ // The harness invalidates broken checkpoints durably so the
1501
+ // next explicit turn rebuilds from the retained transcript.
1502
+ if ((logAuth || (!needsAuth && (stalled || isBrokenAntigravityCheckpoint(DRIVER_KIND, message) || e?.acpSessionFailure === true
1503
1503
  || code === "upstream_outage"))) && session.child.exitCode === null && !session.closing) {
1504
1504
  closeSession(threadId, logAuth ? "provider-auth" : stalled ? "prompt-stall" : "rpc-failure");
1505
1505
  }
1506
- settle(threadId, session, false, needsAuth ? "auth_required" : "rpc_error");
1506
+ settle(threadId, session, false, needsAuth ? "auth_required" : isBrokenAntigravityCheckpoint(DRIVER_KIND, message) ? "invalid_checkpoint" : "rpc_error");
1507
1507
  }
1508
1508
  }
1509
1509
  })();
@@ -49,10 +49,10 @@ export const NO_ANSWER_FAILURE = "The engine ended the turn without an answer.";
49
49
  function unrecognisedFailure(turn) {
50
50
  return { kind: "provider_error", message: turn.lastError ?? NO_ANSWER_FAILURE };
51
51
  }
52
- /** Ways a turn ends failed that are not the engine's fault: the person (or
53
- * the harness on their behalf) stopped it. Every other failed end moves the
54
- * turn to the next engine. */
55
- const NOT_A_PROVIDER_FAILURE = new Set(["interrupted", "cancelled"]);
52
+ /** Stops that must not dispatch the request again: cancellation or a broken
53
+ * checkpoint whose accepted prompt may already have executed actions.
54
+ * The next explicit turn rebuilds that checkpoint from retained history. */
55
+ const NOT_A_PROVIDER_FAILURE = new Set(["interrupted", "cancelled", "invalid_checkpoint"]);
56
56
  export class FailoverGate {
57
57
  turns = new Map();
58
58
  onFailure;
@@ -1,3 +1,4 @@
1
+ import { invalidateBrokenCheckpoint } from "./checkpoint-recovery.js";
1
2
  import { createModelExpectation, ModelEvidenceLedger } from "./model-evidence-context.js";
2
3
  import { reactToMessage } from "./message-reactions.js";
3
4
  import { hasUnboundGoalGuard, goalGuardForEvent, addGoalGuard, removeGoalGuard, runPersonalGoalTurn, updatePersonalGoalReceipt } from "./personal-goal-turn.js";
@@ -175,6 +176,7 @@ import { agentBrowserFrame, agentBrowserIntegration, browserEngineEncryptionKey,
175
176
  import { withZipListings } from "./zip-listing.js";
176
177
  import { createScreenFrameSource, startPeriodicCapture } from "./screen-frame-source.js";
177
178
  import { screenFrameHash, screenSurfaceForTool, screenTouchingTool, settledFrameIsNews } from "./screen-frame-gate.js";
179
+ import { RoutineMochiiSync } from "./routine-mochii-sync.js";
178
180
  import { RoutineRequestService } from "./routine-requests.js";
179
181
  import { buildBotOverview } from "./bot-overview.js";
180
182
  import { ProfileRequestService } from "./profile-requests.js";
@@ -2430,6 +2432,7 @@ function noteNexusLocalMemory(botId) {
2430
2432
  }
2431
2433
  }
2432
2434
  nexusMemory.start();
2435
+ const routineMochiiSync = RoutineMochiiSync.start({ dataDir: DATA_DIR, config: () => cfg, listRoutines: () => routines?.listRoutines() ?? [], listRuns: () => routines?.listRuns() ?? [], botName: (id) => store.bot(id)?.name, timeZone: () => routineTimeZone() });
2433
2436
  // Register the Private Bot's Nexus key as soon as there is a Private Bot, so
2434
2437
  // nothing else can register first (Nexus trusts the first key).
2435
2438
  // (Deferred: the store is created further down.)
@@ -8170,22 +8173,20 @@ bus.subscribe((event) => {
8170
8173
  });
8171
8174
  break;
8172
8175
  case "runtime.error":
8176
+ if (bot)
8177
+ invalidateBrokenCheckpoint(store, bot.id, event.providerInstanceId ? registry.get(event.providerInstanceId)?.driverKind : undefined, event);
8173
8178
  if (event.message?.trim())
8174
8179
  lastTurnError.set(event.threadId, event.message.trim().slice(0, 300));
8175
8180
  pushMessage({
8176
8181
  role: "bot",
8177
8182
  kind: "activity",
8178
- // The category travels with the line so every surface words it the
8179
- // same way; an error line is never a lost connection either way.
8183
+ // Keep the category consistent across surfaces.
8180
8184
  // The harness's own stop line is kept whole: it ends with where to fix
8181
8185
  // the account, which a 160-character cut would drop.
8182
8186
  tool: { name: `error: ${event.message.slice(0, event.category === "account" || event.category === "failover_exhausted" ? 600 : 160)}`, ok: false, setup: event.setup, ...(event.terminal ? { terminal: true } : {}),
8183
8187
  ...(event.category ? { errorCategory: event.category } : {}) },
8184
8188
  });
8185
- // a setup error means the engine could not even start: the bot is
8186
- // dead until something changes, not merely idle. The next successful
8187
- // dispatch moves it to working; turn.completed (which follows a setup
8188
- // failure) is told to leave "dead" alone.
8189
+ // Setup failures remain dead until a later successful dispatch.
8189
8190
  if (event.setup && bot)
8190
8191
  store.setTaskActivity(bot.id, event.threadId, "dead");
8191
8192
  break;
@@ -11274,7 +11275,7 @@ routines = new RoutineManager({
11274
11275
  }
11275
11276
  },
11276
11277
  interruptGoal: interruptRoutineGroupGoal,
11277
- onRunChanged: syncRoutineRunToSource,
11278
+ onRunChanged: (run) => { syncRoutineRunToSource(run); routineMochiiSync.poke(); },
11278
11279
  onRunFailed: (run) => {
11279
11280
  if (run.threadId) {
11280
11281
  pendingDelegationWakes.delete(run.threadId);
@@ -25523,7 +25524,7 @@ const gracefulShutdown = createGracefulShutdown({
25523
25524
  () => setupLogins.stopAll(),
25524
25525
  () => flushAllMemoryJournals(),
25525
25526
  // the outbox is already on disk; this only stops its retry timer
25526
- () => nexusMemory.stop(),
25527
+ () => { nexusMemory.stop(); routineMochiiSync.stop(); },
25527
25528
  () => flushUsageLedger(DATA_DIR),
25528
25529
  () => flushDecisionLog(DATA_DIR),
25529
25530
  () => flushAdminActivity(DATA_DIR),
@@ -0,0 +1,252 @@
1
+ // Mirrors each routine into one private Mochii task: its schedule and next
2
+ // run on the task, and every finished run as a comment. Off unless the
3
+ // install sets features.routineMochiiSync, and it needs the configured
4
+ // `mochii` MCP server, which acts as the signed-in person, so each person's
5
+ // routines land in their own Mochii as their own private tasks.
6
+ //
7
+ // Fail closed: a new task is first created with a content-free title. Only
8
+ // when Mochii answers that it is private and keyed to this routine (the
9
+ // create contract: is_private, idempotency_key, replayed) does routine
10
+ // content follow. An older Mochii that ignores the contract gets its stray
11
+ // task deleted, and sync pauses for a few hours before checking again.
12
+ //
13
+ // No outbox: the routines and their run receipts are already on disk, so each
14
+ // pass compares them with what Mochii has confirmed (routine-mochii-sync.json)
15
+ // and sends the difference. A failed pass is simply retried on the next one.
16
+ import { existsSync, readFileSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ import { writeFileAtomic } from "./atomic.js";
19
+ import { customMcpServers } from "./config.js";
20
+ import { nexusClientFor, NexusUnavailableError } from "./nexus-memory/client.js";
21
+ import { scheduleText } from "./routine-requests.js";
22
+ const PASS_EVERY_MS = 5 * 60_000;
23
+ const POKE_DELAY_MS = 3_000;
24
+ const BLOCK_MS = 6 * 60 * 60_000;
25
+ /** Runs already finished when a routine is first mirrored: only the latest few become comments. */
26
+ const BACKFILL_RUNS = 5;
27
+ const MAX_COMMENT_CHARS = 4_000;
28
+ const FINISHED = new Set(["completed", "failed", "cancelled", "missed"]);
29
+ const PROBE_TITLE = "Atomic routine";
30
+ export function routineMochiiSyncEnabled(cfg) {
31
+ return cfg.features?.routineMochiiSync === true && Boolean(customMcpServers(cfg).mochii);
32
+ }
33
+ export const idempotencyKey = (routineId) => `atomic-routine:${routineId}`;
34
+ function record(value) {
35
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
36
+ }
37
+ export function taskTitle(routine) {
38
+ return `Routine: ${routine.name}${routine.enabled ? "" : " (paused)"}`;
39
+ }
40
+ export function taskDescription(routine, bot, timeZone) {
41
+ const next = routine.enabled && routine.nextRunAt != null ? new Date(routine.nextRunAt).toISOString() : "none";
42
+ return [
43
+ "Managed by Atomic. Edits made here are replaced on the next sync.",
44
+ "",
45
+ `Bot: ${bot ?? routine.botId}`,
46
+ `Schedule: ${scheduleText(routine.schedule, timeZone)}`,
47
+ `Status: ${routine.enabled ? "active" : "paused"}`,
48
+ `Next run: ${next}`,
49
+ "",
50
+ "Prompt:",
51
+ routine.prompt,
52
+ ].join("\n");
53
+ }
54
+ export function runComment(run) {
55
+ const when = new Date(run.finishedAt ?? run.scheduledFor).toISOString();
56
+ const body = (run.status === "failed" ? run.error ?? run.output : run.output ?? run.error)?.trim();
57
+ const clipped = body && body.length > MAX_COMMENT_CHARS ? `${body.slice(0, MAX_COMMENT_CHARS)}\n\n(cut short; the full report is in Atomic)` : body;
58
+ return `Run ${run.status} · ${when}${run.manual ? " · started by hand" : ""}${clipped ? `\n\n${clipped}` : ""}`;
59
+ }
60
+ export class RoutineMochiiSync {
61
+ path;
62
+ now;
63
+ state;
64
+ client = null;
65
+ clientKey = "";
66
+ running = null;
67
+ poked;
68
+ timer;
69
+ deps;
70
+ constructor(deps) {
71
+ this.deps = deps;
72
+ this.path = join(deps.dataDir, "routine-mochii-sync.json");
73
+ this.now = deps.now ?? Date.now;
74
+ this.state = this.load();
75
+ }
76
+ static start(deps) {
77
+ const sync = new RoutineMochiiSync(deps);
78
+ sync.start();
79
+ return sync;
80
+ }
81
+ start() {
82
+ this.timer ??= setInterval(() => void this.pass(), PASS_EVERY_MS);
83
+ this.timer.unref?.();
84
+ this.poke();
85
+ }
86
+ stop() {
87
+ clearInterval(this.timer);
88
+ clearTimeout(this.poked);
89
+ this.timer = undefined;
90
+ void this.client?.close();
91
+ this.client = null;
92
+ }
93
+ /** A run changed: sync soon rather than waiting for the next pass. */
94
+ poke() {
95
+ if (this.poked || !routineMochiiSyncEnabled(this.deps.config()))
96
+ return;
97
+ this.poked = setTimeout(() => {
98
+ this.poked = undefined;
99
+ void this.pass();
100
+ }, POKE_DELAY_MS);
101
+ this.poked.unref?.();
102
+ }
103
+ status() {
104
+ return {
105
+ enabled: routineMochiiSyncEnabled(this.deps.config()),
106
+ mirrored: Object.keys(this.state.tasks).length,
107
+ blockedUntil: this.state.blockedUntil ?? null,
108
+ blockedReason: this.state.blockedReason ?? null,
109
+ };
110
+ }
111
+ /** One reconcile pass; concurrent callers share it. */
112
+ pass() {
113
+ this.running ??= this.reconcile()
114
+ .catch((error) => this.log(`pass stopped: ${error instanceof Error ? error.message : String(error)}`))
115
+ .finally(() => { this.running = null; });
116
+ return this.running;
117
+ }
118
+ async reconcile() {
119
+ const cfg = this.deps.config();
120
+ if (!routineMochiiSyncEnabled(cfg))
121
+ return;
122
+ if (this.state.blockedUntil && this.now() < this.state.blockedUntil)
123
+ return;
124
+ const client = this.clientFor(cfg);
125
+ if (!client)
126
+ return;
127
+ const routines = this.deps.listRoutines();
128
+ const runs = this.deps.listRuns();
129
+ for (const routine of routines) {
130
+ const mine = runs.filter((run) => run.routineId === routine.id && FINISHED.has(run.status));
131
+ try {
132
+ if (!(await this.mirror(client, routine, mine)))
133
+ return;
134
+ }
135
+ catch (error) {
136
+ if (error instanceof NexusUnavailableError)
137
+ throw error;
138
+ this.log(`routine ${routine.id}: ${error instanceof Error ? error.message : String(error)}`);
139
+ // The task is gone from Mochii: forget it so the next pass creates it again.
140
+ if (/not found|404/i.test(String(error)))
141
+ this.forget(routine.id);
142
+ }
143
+ }
144
+ const live = new Set(routines.map((routine) => routine.id));
145
+ for (const [routineId, mirrored] of Object.entries(this.state.tasks)) {
146
+ if (live.has(routineId))
147
+ continue;
148
+ try {
149
+ await client.call("update_task", { id: mirrored.taskId, status: "cancelled" });
150
+ await client.call("add_comment", { task_id: mirrored.taskId, content: "This routine was deleted in Atomic." });
151
+ }
152
+ catch (error) {
153
+ if (error instanceof NexusUnavailableError)
154
+ throw error;
155
+ }
156
+ this.forget(routineId);
157
+ }
158
+ }
159
+ /** False when Mochii failed the privacy check: stop the whole pass. */
160
+ async mirror(client, routine, finished) {
161
+ let mirrored = this.state.tasks[routine.id];
162
+ if (!mirrored) {
163
+ const taskId = await this.createPrivate(client, routine.id);
164
+ if (!taskId)
165
+ return false;
166
+ const byAge = [...finished].sort((a, b) => (a.finishedAt ?? a.scheduledFor) - (b.finishedAt ?? b.scheduledFor));
167
+ mirrored = { taskId, fingerprint: "", runs: byAge.slice(0, -BACKFILL_RUNS).map((run) => run.id) };
168
+ this.state.tasks[routine.id] = mirrored;
169
+ this.save();
170
+ }
171
+ const title = taskTitle(routine);
172
+ const description = taskDescription(routine, this.deps.botName(routine.botId), this.deps.timeZone());
173
+ const due = routine.enabled && routine.nextRunAt != null ? new Date(routine.nextRunAt).toISOString() : undefined;
174
+ const fingerprint = JSON.stringify([title, description, due]);
175
+ if (fingerprint !== mirrored.fingerprint) {
176
+ await client.call("update_task", { id: mirrored.taskId, title, description, status: "pending", ...(due ? { due_date: due } : {}) });
177
+ mirrored.fingerprint = fingerprint;
178
+ this.save();
179
+ }
180
+ const posted = new Set(mirrored.runs);
181
+ const fresh = finished
182
+ .filter((run) => !posted.has(run.id))
183
+ .sort((a, b) => (a.finishedAt ?? a.scheduledFor) - (b.finishedAt ?? b.scheduledFor));
184
+ for (const run of fresh) {
185
+ await client.call("add_comment", { task_id: mirrored.taskId, content: runComment(run) });
186
+ mirrored.runs.push(run.id);
187
+ this.save();
188
+ }
189
+ const kept = new Set(finished.map((run) => run.id));
190
+ if (mirrored.runs.some((id) => !kept.has(id))) {
191
+ mirrored.runs = mirrored.runs.filter((id) => kept.has(id));
192
+ this.save();
193
+ }
194
+ return true;
195
+ }
196
+ /** Creates (or, after a crash, gets back) the routine's task with a content-free title. */
197
+ async createPrivate(client, routineId) {
198
+ const key = idempotencyKey(routineId);
199
+ const body = await client.call("create_task", { title: PROBE_TITLE, is_private: true, idempotency_key: key });
200
+ const task = record(body.data);
201
+ const taskId = typeof task?.id === "string" ? task.id : null;
202
+ if (taskId && task?.is_private === true && task.idempotency_key === key && typeof body.replayed === "boolean")
203
+ return taskId;
204
+ if (taskId && task?.is_private !== true) {
205
+ await client.call("delete_task", { id: taskId }).catch((error) => this.log(`could not remove a non-private task ${taskId}: ${String(error)}`));
206
+ }
207
+ this.state.blockedUntil = this.now() + BLOCK_MS;
208
+ this.state.blockedReason = "Mochii does not support private routine tasks yet";
209
+ this.save();
210
+ this.log(`${this.state.blockedReason}; nothing was sent, checking again later`);
211
+ return null;
212
+ }
213
+ forget(routineId) {
214
+ delete this.state.tasks[routineId];
215
+ this.save();
216
+ }
217
+ clientFor(cfg) {
218
+ if (this.deps.clientFor)
219
+ return (this.client ??= this.deps.clientFor(cfg));
220
+ const spec = customMcpServers(cfg).mochii;
221
+ const key = JSON.stringify(spec ?? null);
222
+ if (key !== this.clientKey) {
223
+ void this.client?.close();
224
+ this.client = nexusClientFor(spec);
225
+ this.clientKey = key;
226
+ }
227
+ return this.client;
228
+ }
229
+ load() {
230
+ try {
231
+ if (existsSync(this.path)) {
232
+ const parsed = record(JSON.parse(readFileSync(this.path, "utf8")));
233
+ if (parsed && record(parsed.tasks))
234
+ return parsed;
235
+ }
236
+ }
237
+ catch (error) {
238
+ this.log(`ignoring unreadable ${this.path}: ${String(error)}`);
239
+ }
240
+ return { tasks: {} };
241
+ }
242
+ save() {
243
+ if (this.state.blockedUntil && this.now() >= this.state.blockedUntil) {
244
+ delete this.state.blockedUntil;
245
+ delete this.state.blockedReason;
246
+ }
247
+ writeFileAtomic(this.path, JSON.stringify(this.state, null, 2));
248
+ }
249
+ log(message) {
250
+ (this.deps.log ?? ((line) => console.warn(`[routine-mochii-sync] ${line}`)))(message);
251
+ }
252
+ }