@rubytech/create-maxy-code 0.1.547 → 0.1.548

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 (112) hide show
  1. package/dist/index.js +14 -0
  2. package/package.json +1 -1
  3. package/payload/platform/lib/routine-templates/dist/index.d.ts +22 -16
  4. package/payload/platform/lib/routine-templates/dist/index.d.ts.map +1 -1
  5. package/payload/platform/lib/routine-templates/dist/index.js +15 -10
  6. package/payload/platform/lib/routine-templates/dist/index.js.map +1 -1
  7. package/payload/platform/lib/routine-templates/dist/roster.d.ts +3 -3
  8. package/payload/platform/lib/routine-templates/dist/roster.d.ts.map +1 -1
  9. package/payload/platform/lib/routine-templates/src/__tests__/seed.test.ts +15 -11
  10. package/payload/platform/lib/routine-templates/src/index.ts +23 -17
  11. package/payload/platform/lib/routine-templates/src/roster.ts +3 -3
  12. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +41 -9
  13. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
  14. package/payload/platform/plugins/docs/references/admin-ui.md +40 -8
  15. package/payload/platform/plugins/scheduling/PLUGIN.md +6 -0
  16. package/payload/platform/plugins/scheduling/mcp/dist/index.js +6 -2
  17. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  18. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/update-summary.test.d.ts +2 -0
  19. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/update-summary.test.d.ts.map +1 -0
  20. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/update-summary.test.js +54 -0
  21. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/update-summary.test.js.map +1 -0
  22. package/payload/platform/plugins/scheduling/mcp/dist/lib/update-summary.d.ts +17 -0
  23. package/payload/platform/plugins/scheduling/mcp/dist/lib/update-summary.d.ts.map +1 -0
  24. package/payload/platform/plugins/scheduling/mcp/dist/lib/update-summary.js +43 -0
  25. package/payload/platform/plugins/scheduling/mcp/dist/lib/update-summary.js.map +1 -0
  26. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/converge-routine-description.test.d.ts +2 -0
  27. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/converge-routine-description.test.d.ts.map +1 -0
  28. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/converge-routine-description.test.js +87 -0
  29. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/converge-routine-description.test.js.map +1 -0
  30. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/description-coverage.test.d.ts +2 -0
  31. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/description-coverage.test.d.ts.map +1 -0
  32. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/description-coverage.test.js +123 -0
  33. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/description-coverage.test.js.map +1 -0
  34. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +36 -0
  35. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
  36. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +89 -0
  37. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
  38. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +13 -1
  39. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  40. package/payload/platform/plugins/scheduling/mcp/dist/scripts/converge-routine-description.d.ts +35 -0
  41. package/payload/platform/plugins/scheduling/mcp/dist/scripts/converge-routine-description.d.ts.map +1 -0
  42. package/payload/platform/plugins/scheduling/mcp/dist/scripts/converge-routine-description.js +56 -0
  43. package/payload/platform/plugins/scheduling/mcp/dist/scripts/converge-routine-description.js.map +1 -0
  44. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-summary.test.d.ts +2 -0
  45. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-summary.test.d.ts.map +1 -0
  46. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-summary.test.js +80 -0
  47. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-summary.test.js.map +1 -0
  48. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts +2 -1
  49. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
  50. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +19 -1
  51. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
  52. package/payload/platform/plugins/telegram/PLUGIN.md +2 -0
  53. package/payload/platform/plugins/telegram/mcp/dist/index.js +47 -47
  54. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
  55. package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.d.ts +16 -0
  56. package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.d.ts.map +1 -0
  57. package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.js +50 -0
  58. package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.js.map +1 -0
  59. package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.d.ts +8 -0
  60. package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.d.ts.map +1 -0
  61. package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.js +12 -0
  62. package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.js.map +1 -0
  63. package/payload/platform/plugins/telegram/references/setup-guide.md +2 -0
  64. package/payload/platform/plugins/telegram/skills/configure/SKILL.md +6 -1
  65. package/payload/platform/services/telegram-channel/dist/notification.d.ts +8 -3
  66. package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -1
  67. package/payload/platform/services/telegram-channel/dist/notification.js +10 -3
  68. package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -1
  69. package/payload/platform/services/telegram-channel/dist/server.js +1 -1
  70. package/payload/platform/services/telegram-channel/dist/server.js.map +1 -1
  71. package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts +28 -0
  72. package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts.map +1 -1
  73. package/payload/platform/services/telegram-channel/dist/turn-follow.js +33 -1
  74. package/payload/platform/services/telegram-channel/dist/turn-follow.js.map +1 -1
  75. package/payload/platform/services/webchat-channel/dist/notification.d.ts +8 -3
  76. package/payload/platform/services/webchat-channel/dist/notification.d.ts.map +1 -1
  77. package/payload/platform/services/webchat-channel/dist/notification.js +10 -3
  78. package/payload/platform/services/webchat-channel/dist/notification.js.map +1 -1
  79. package/payload/platform/services/webchat-channel/dist/server.js +1 -1
  80. package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
  81. package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts +28 -0
  82. package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts.map +1 -1
  83. package/payload/platform/services/webchat-channel/dist/turn-follow.js +33 -1
  84. package/payload/platform/services/webchat-channel/dist/turn-follow.js.map +1 -1
  85. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +28 -4
  86. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  87. package/payload/platform/services/whatsapp-channel/dist/notification.js +30 -7
  88. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  89. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  90. package/payload/platform/services/whatsapp-channel/dist/server.js +13 -5
  91. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  92. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts +2 -0
  93. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -1
  94. package/payload/platform/services/whatsapp-channel/dist/targets.js +14 -1
  95. package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -1
  96. package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts +28 -0
  97. package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts.map +1 -1
  98. package/payload/platform/services/whatsapp-channel/dist/turn-follow.js +34 -1
  99. package/payload/platform/services/whatsapp-channel/dist/turn-follow.js.map +1 -1
  100. package/payload/server/{chunk-MHNHG3GL.js → chunk-G6ULACHG.js} +1 -1
  101. package/payload/server/{chunk-Q33KT7YJ.js → chunk-JOVREC6E.js} +117 -1
  102. package/payload/server/{chunk-WGMIUXXG.js → chunk-SKYZG6OT.js} +11 -0
  103. package/payload/server/{manager-W32LVPCT.js → manager-2VEJKVEO.js} +2 -2
  104. package/payload/server/maxy-edge.js +2 -2
  105. package/payload/server/public/assets/routines-OGFXwTFO.js +2 -0
  106. package/payload/server/public/assets/routines-OGFXwTFO.js.br +0 -0
  107. package/payload/server/public/assets/routines-OGFXwTFO.js.gz +0 -0
  108. package/payload/server/public/routines.html +1 -1
  109. package/payload/server/server.js +278 -169
  110. package/payload/server/public/assets/routines-Ds0yz7_C.js +0 -2
  111. package/payload/server/public/assets/routines-Ds0yz7_C.js.br +0 -0
  112. package/payload/server/public/assets/routines-Ds0yz7_C.js.gz +0 -0
package/dist/index.js CHANGED
@@ -3405,6 +3405,20 @@ function setupAccount() {
3405
3405
  env: { ...neo4jEnv, PLATFORM_ROOT: join(INSTALL_DIR, "platform") },
3406
3406
  });
3407
3407
  }
3408
+ // Task 2361 — collapse the two routine description properties onto
3409
+ // `:Event.description`, preferring whichever value the operator or the agent
3410
+ // wrote. Runs after the seed for the same reasons the convergence above does:
3411
+ // the schema exists by then, and the same Neo4j env is already built. Scoped to
3412
+ // this install's accounts via PLATFORM_ROOT, so a co-resident brand sharing one
3413
+ // Neo4j is untouched. Idempotent: a converged install matches nothing and
3414
+ // prints converged=0.
3415
+ const convergeRoutineDescription = join(INSTALL_DIR, "platform/plugins/scheduling/mcp/dist/scripts/converge-routine-description.js");
3416
+ if (existsSync(convergeRoutineDescription)) {
3417
+ shell("node", [convergeRoutineDescription], {
3418
+ cwd: INSTALL_DIR,
3419
+ env: { ...neo4jEnv, PLATFORM_ROOT: join(INSTALL_DIR, "platform") },
3420
+ });
3421
+ }
3408
3422
  // Task 165 — register every bundled specialist subagent at
3409
3423
  // $CLAUDE_CONFIG_DIR/agents/<name>.md. Claude Code's `--agent <name>`
3410
3424
  // discovery only resolves under this path; without these symlinks,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.547",
3
+ "version": "0.1.548",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -70,13 +70,14 @@ export interface RoutineVerifyCounts {
70
70
  actual: number;
71
71
  suspended: number;
72
72
  nextRunNull: number;
73
- /** Task 2214 — roster rows holding no description. Zero is the healthy value
74
- * on every account, not only a fresh one: `VERIFY_CYPHER` matches
75
- * `routineTemplate IS NOT NULL`, so operator-created routines are outside
76
- * its scope and cannot contribute, and `ON MATCH` writes the description
77
- * unconditionally, so an operator-edited row is covered too. Anything above
78
- * zero while `expected === actual` is a MERGE that wrote no description
79
- * the one failure here that emits no error and does not reproduce. */
73
+ /** Roster rows holding no description. Zero is the healthy value on every
74
+ * account, not only a fresh one: `VERIFY_CYPHER` matches `routineTemplate IS
75
+ * NOT NULL`, so operator-created routines are outside its scope and cannot
76
+ * contribute. Anything above zero while `expected === actual` is a MERGE that
77
+ * wrote no description on create, or a row whose key left the roster before
78
+ * the convergence ran the one failure here that emits no error and does not
79
+ * reproduce. `[schedule-audit] op=description-coverage` is the wider twin,
80
+ * over every automation row rather than the roster alone. */
80
81
  descNull: number;
81
82
  }
82
83
  export interface RoutineSeedReport {
@@ -124,17 +125,22 @@ export declare function resolveOwnerTimezone(session: SeedSessionLike, accountId
124
125
  * destination, or reset a retimed cron. Only the prompt, the name and the
125
126
  * version move, and only when the row is behind and unedited.
126
127
  *
127
- * `routineDescription` (Task 2214) is the one property ON MATCH writes on
128
- * every match rather than only on the refresh branch, and that is not an
129
- * exception to the rule above. It is roster-owned: the routines route projects
130
- * it but its PATCH field set refuses it, so no operator edit exists for this
131
- * to overwrite. Gating it on `refreshing` would leave every operator-edited
132
- * row description-less permanently, because `skip-operator-edited` takes
133
- * neither branch, and that row's card would show a title and nothing else. */
134
- export declare const MERGE_CYPHER = "\nUNWIND $rows AS row\nOPTIONAL MATCH (pre:Event {accountId: $accountId, routineTemplate: row.key})\nWITH row,\n pre IS NOT NULL AS existed,\n coalesce(pre.routineTemplateVersion, 0) AS priorVersion,\n pre.operatorEdited IS NOT NULL AS edited\nWITH row, existed, priorVersion, edited,\n (existed AND NOT edited AND priorVersion < $version) AS refreshing\nMERGE (e:Event {accountId: $accountId, routineTemplate: row.key})\n ON CREATE SET e.eventId = randomUUID(),\n e.name = row.name,\n e.routineDescription = row.description,\n e.agentPrompt = row.prompt,\n e.recurrence = row.recurrence,\n e.routineTemplateVersion = $version,\n e.eventStatus = 'suspended',\n e.nextRun = null,\n e.agentChannel = null,\n e.agentDestination = null,\n e.actionPlugin = null,\n e.actionTool = null,\n e.actionArgs = null,\n e.sourcePlugin = null,\n e.timezone = $timezone,\n e.createdBySource = 'installer',\n e.createdByAgent = 'system',\n e.createdByTool = 'routine-roster-seed',\n e.createdAt = $now,\n e.updatedAt = $now\n ON MATCH SET e.agentPrompt = CASE WHEN refreshing THEN row.prompt ELSE e.agentPrompt END,\n e.name = CASE WHEN refreshing THEN row.name ELSE e.name END,\n e.routineDescription = row.description,\n e.routineTemplateVersion = CASE WHEN refreshing THEN $version ELSE e.routineTemplateVersion END,\n e.updatedAt = CASE WHEN refreshing THEN $now ELSE e.updatedAt END\nRETURN row.key AS key,\n e.eventId AS eventId,\n CASE WHEN NOT existed THEN 'create'\n WHEN priorVersion >= $version THEN 'unchanged'\n WHEN edited THEN 'skip-operator-edited'\n ELSE 'refresh-version' END AS action";
128
+ * `description` (Task 2361) is guarded exactly as the prompt and the name are,
129
+ * and that is a change of shape from Task 2214, which introduced it. It used to
130
+ * be written on every match, on the grounds that it was roster-owned and the
131
+ * routines PATCH refused it, so no operator edit existed for the seed to
132
+ * clobber, and that gating it would leave every operator-edited row
133
+ * description-less permanently because `skip-operator-edited` takes neither
134
+ * branch. Both halves of that argument are gone. The routines PATCH now accepts
135
+ * `description` and stamps `operatorEdited`, so an unconditional write would
136
+ * destroy the operator's own caption on the next version bump; and the
137
+ * scheduling plugin's one-shot `converge-routine-description` has already given
138
+ * every pre-existing row a description, so no row is left holding only a
139
+ * title. */
140
+ export declare const MERGE_CYPHER = "\nUNWIND $rows AS row\nOPTIONAL MATCH (pre:Event {accountId: $accountId, routineTemplate: row.key})\nWITH row,\n pre IS NOT NULL AS existed,\n coalesce(pre.routineTemplateVersion, 0) AS priorVersion,\n pre.operatorEdited IS NOT NULL AS edited\nWITH row, existed, priorVersion, edited,\n (existed AND NOT edited AND priorVersion < $version) AS refreshing\nMERGE (e:Event {accountId: $accountId, routineTemplate: row.key})\n ON CREATE SET e.eventId = randomUUID(),\n e.name = row.name,\n e.description = row.description,\n e.agentPrompt = row.prompt,\n e.recurrence = row.recurrence,\n e.routineTemplateVersion = $version,\n e.eventStatus = 'suspended',\n e.nextRun = null,\n e.agentChannel = null,\n e.agentDestination = null,\n e.actionPlugin = null,\n e.actionTool = null,\n e.actionArgs = null,\n e.sourcePlugin = null,\n e.timezone = $timezone,\n e.createdBySource = 'installer',\n e.createdByAgent = 'system',\n e.createdByTool = 'routine-roster-seed',\n e.createdAt = $now,\n e.updatedAt = $now\n ON MATCH SET e.agentPrompt = CASE WHEN refreshing THEN row.prompt ELSE e.agentPrompt END,\n e.name = CASE WHEN refreshing THEN row.name ELSE e.name END,\n e.description = CASE WHEN refreshing THEN row.description ELSE e.description END,\n e.routineTemplateVersion = CASE WHEN refreshing THEN $version ELSE e.routineTemplateVersion END,\n e.updatedAt = CASE WHEN refreshing THEN $now ELSE e.updatedAt END\nRETURN row.key AS key,\n e.eventId AS eventId,\n CASE WHEN NOT existed THEN 'create'\n WHEN priorVersion >= $version THEN 'unchanged'\n WHEN edited THEN 'skip-operator-edited'\n ELSE 'refresh-version' END AS action";
135
141
  /** The post-condition read. Counts every roster row the account actually holds,
136
142
  * independently of what the MERGE above believes it wrote. */
137
- export declare const VERIFY_CYPHER = "\nMATCH (e:Event {accountId: $accountId})\nWHERE e.routineTemplate IS NOT NULL\nRETURN count(e) AS actual,\n sum(CASE WHEN e.eventStatus = 'suspended' THEN 1 ELSE 0 END) AS suspended,\n sum(CASE WHEN e.nextRun IS NULL THEN 1 ELSE 0 END) AS nextRunNull,\n sum(CASE WHEN e.routineDescription IS NULL THEN 1 ELSE 0 END) AS descNull";
143
+ export declare const VERIFY_CYPHER = "\nMATCH (e:Event {accountId: $accountId})\nWHERE e.routineTemplate IS NOT NULL\nRETURN count(e) AS actual,\n sum(CASE WHEN e.eventStatus = 'suspended' THEN 1 ELSE 0 END) AS suspended,\n sum(CASE WHEN e.nextRun IS NULL THEN 1 ELSE 0 END) AS nextRunNull,\n sum(CASE WHEN e.description IS NULL THEN 1 ELSE 0 END) AS descNull";
138
144
  export interface SeedRoutineRosterArgs {
139
145
  session: SeedSessionLike;
140
146
  accountId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAA4C,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAE7F,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC5F,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;2EAG2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAC;CAClH;AAED;;;;;;;4EAO4E;AAC5E,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,iBAAiB,GAAG,sBAAsB,GAAG,WAAW,CAAC;AAErG,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAED;;;;;;0EAM0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;2EAMuE;IACvE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,eAAe,GAAG,QAAQ,CAAC;IAC3C,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;CACf;AAYD;4DAC4D;AAC5D,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,eAAe,GAAG,QAAQ,CAAA;CAAE,CAAC,CAa1E;AAED;;;;;;;;;;;;;;;;;;;;+EAoB+E;AAC/E,eAAO,MAAM,YAAY,28DAwCwB,CAAC;AAElD;+DAC+D;AAC/D,eAAO,MAAM,aAAa,+VAMuD,CAAC;AAElF,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,MAAM,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACpC,+DAA+D;IAC/D,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B;sEACkE;IAClE,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgF/F"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAA4C,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAE7F,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC5F,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;2EAG2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAC;CAClH;AAED;;;;;;;4EAO4E;AAC5E,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,iBAAiB,GAAG,sBAAsB,GAAG,WAAW,CAAC;AAErG,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAED;;;;;;0EAM0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;;kEAO8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,eAAe,GAAG,QAAQ,CAAC;IAC3C,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;CACf;AAYD;4DAC4D;AAC5D,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,eAAe,GAAG,QAAQ,CAAA;CAAE,CAAC,CAa1E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;aAyBa;AACb,eAAO,MAAM,YAAY,8+DAwCwB,CAAC;AAElD;+DAC+D;AAC/D,eAAO,MAAM,aAAa,wVAMgD,CAAC;AAE3E,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,MAAM,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACpC,+DAA+D;IAC/D,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B;sEACkE;IAClE,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgF/F"}
@@ -92,13 +92,18 @@ async function resolveOwnerTimezone(session, accountId) {
92
92
  * destination, or reset a retimed cron. Only the prompt, the name and the
93
93
  * version move, and only when the row is behind and unedited.
94
94
  *
95
- * `routineDescription` (Task 2214) is the one property ON MATCH writes on
96
- * every match rather than only on the refresh branch, and that is not an
97
- * exception to the rule above. It is roster-owned: the routines route projects
98
- * it but its PATCH field set refuses it, so no operator edit exists for this
99
- * to overwrite. Gating it on `refreshing` would leave every operator-edited
100
- * row description-less permanently, because `skip-operator-edited` takes
101
- * neither branch, and that row's card would show a title and nothing else. */
95
+ * `description` (Task 2361) is guarded exactly as the prompt and the name are,
96
+ * and that is a change of shape from Task 2214, which introduced it. It used to
97
+ * be written on every match, on the grounds that it was roster-owned and the
98
+ * routines PATCH refused it, so no operator edit existed for the seed to
99
+ * clobber, and that gating it would leave every operator-edited row
100
+ * description-less permanently because `skip-operator-edited` takes neither
101
+ * branch. Both halves of that argument are gone. The routines PATCH now accepts
102
+ * `description` and stamps `operatorEdited`, so an unconditional write would
103
+ * destroy the operator's own caption on the next version bump; and the
104
+ * scheduling plugin's one-shot `converge-routine-description` has already given
105
+ * every pre-existing row a description, so no row is left holding only a
106
+ * title. */
102
107
  exports.MERGE_CYPHER = `
103
108
  UNWIND $rows AS row
104
109
  OPTIONAL MATCH (pre:Event {accountId: $accountId, routineTemplate: row.key})
@@ -111,7 +116,7 @@ WITH row, existed, priorVersion, edited,
111
116
  MERGE (e:Event {accountId: $accountId, routineTemplate: row.key})
112
117
  ON CREATE SET e.eventId = randomUUID(),
113
118
  e.name = row.name,
114
- e.routineDescription = row.description,
119
+ e.description = row.description,
115
120
  e.agentPrompt = row.prompt,
116
121
  e.recurrence = row.recurrence,
117
122
  e.routineTemplateVersion = $version,
@@ -131,7 +136,7 @@ MERGE (e:Event {accountId: $accountId, routineTemplate: row.key})
131
136
  e.updatedAt = $now
132
137
  ON MATCH SET e.agentPrompt = CASE WHEN refreshing THEN row.prompt ELSE e.agentPrompt END,
133
138
  e.name = CASE WHEN refreshing THEN row.name ELSE e.name END,
134
- e.routineDescription = row.description,
139
+ e.description = CASE WHEN refreshing THEN row.description ELSE e.description END,
135
140
  e.routineTemplateVersion = CASE WHEN refreshing THEN $version ELSE e.routineTemplateVersion END,
136
141
  e.updatedAt = CASE WHEN refreshing THEN $now ELSE e.updatedAt END
137
142
  RETURN row.key AS key,
@@ -148,7 +153,7 @@ WHERE e.routineTemplate IS NOT NULL
148
153
  RETURN count(e) AS actual,
149
154
  sum(CASE WHEN e.eventStatus = 'suspended' THEN 1 ELSE 0 END) AS suspended,
150
155
  sum(CASE WHEN e.nextRun IS NULL THEN 1 ELSE 0 END) AS nextRunNull,
151
- sum(CASE WHEN e.routineDescription IS NULL THEN 1 ELSE 0 END) AS descNull`;
156
+ sum(CASE WHEN e.description IS NULL THEN 1 ELSE 0 END) AS descNull`;
152
157
  /**
153
158
  * Seed (or re-seed) the roster for one account and report what happened.
154
159
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -43,9 +43,9 @@ export interface RoutineTemplate {
43
43
  /** The title shown on `/routines`. */
44
44
  readonly name: string;
45
45
  /** One sentence under the title on the card: what this does and what it will
46
- * not do. Roster-owned and read-only projected by the routines route but
47
- * absent from its PATCH field set, so no operator edit exists for the seed to
48
- * overwrite and the MERGE rewrites it on every match. */
46
+ * not do. Seeded onto `e.description`, which the operator can then edit from
47
+ * the routines page. The seed rewrites it only on a version refresh of an
48
+ * unedited row, so an operator's own wording survives every upgrade. */
49
49
  readonly description: string;
50
50
  /** The proposed cron. Written ON CREATE only, never refreshed, so an operator
51
51
  * who retimes a routine keeps their expression across upgrades. */
@@ -1 +1 @@
1
- {"version":3,"file":"roster.d.ts","sourceRoot":"","sources":["../src/roster.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH;;;gFAGgF;AAChF,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,MAAM,WAAW,eAAe;IAC9B;;mEAE+D;IAC/D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;8DAG0D;IAC1D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;wEACoE;IACpE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAgKD;;;;;;;;iFAQiF;AACjF,eAAO,MAAM,cAAc,EAAE,SAAS,eAAe,EAyGpD,CAAC;AAEF;;6DAE6D;AAC7D,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAAqC,CAAC"}
1
+ {"version":3,"file":"roster.d.ts","sourceRoot":"","sources":["../src/roster.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH;;;gFAGgF;AAChF,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,MAAM,WAAW,eAAe;IAC9B;;mEAE+D;IAC/D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;6EAGyE;IACzE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;wEACoE;IACpE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAgKD;;;;;;;;iFAQiF;AACjF,eAAO,MAAM,cAAc,EAAE,SAAS,eAAe,EAyGpD,CAAC;AAEF;;6DAE6D;AAC7D,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAAqC,CAAC"}
@@ -84,7 +84,7 @@ describe("MERGE statement shape", () => {
84
84
  for (const required of [
85
85
  "eventId",
86
86
  "name",
87
- "routineDescription",
87
+ "description",
88
88
  "agentPrompt",
89
89
  "recurrence",
90
90
  "routineTemplateVersion",
@@ -121,20 +121,24 @@ describe("MERGE statement shape", () => {
121
121
 
122
122
  it("ON MATCH touches ONLY the prompt, name, description, version and updatedAt", () => {
123
123
  expect(new Set(propertiesSetIn("ON MATCH SET"))).toEqual(
124
- new Set(["agentPrompt", "name", "routineDescription", "routineTemplateVersion", "updatedAt"]),
124
+ new Set(["agentPrompt", "name", "description", "routineTemplateVersion", "updatedAt"]),
125
125
  );
126
126
  });
127
127
 
128
- it("ON MATCH writes the description unconditionally, not only when refreshing", () => {
129
- // Task 2214. The description is roster-owned and absent from the routines
130
- // PATCH field set, so there is no operator edit for this to clobber.
131
- // Gating it on `refreshing` would leave every operator-edited row without
132
- // a description permanently: `skip-operator-edited` takes neither branch,
133
- // so its card would show a title and nothing else, for ever.
128
+ it("ON MATCH writes the description only when refreshing, exactly as the prompt and name are", () => {
129
+ // The 2214 shape wrote it on every match, because gating it would have left
130
+ // every operator-edited row description-less for ever: skip-operator-edited
131
+ // takes neither branch. Task 2361's one-shot convergence removes that
132
+ // argument by giving every pre-existing row a description before the first
133
+ // guarded MERGE runs, and from here the description is operator-writable, so
134
+ // an unconditional write would clobber the operator's own text on every
135
+ // upgrade.
134
136
  const start = MERGE_CYPHER.indexOf("ON MATCH SET");
135
137
  const body = MERGE_CYPHER.slice(start, MERGE_CYPHER.indexOf("RETURN"));
136
- expect(body).toContain("e.routineDescription = row.description");
137
- expect(body).not.toContain("e.routineDescription = CASE");
138
+ expect(body).toContain(
139
+ "e.description = CASE WHEN refreshing THEN row.description ELSE e.description END",
140
+ );
141
+ expect(body).not.toContain("e.routineDescription");
138
142
  });
139
143
 
140
144
  it("verify counts the descriptions the graph actually holds", () => {
@@ -142,7 +146,7 @@ describe("MERGE statement shape", () => {
142
146
  // MERGE succeeds, every action reads `refresh-version`, and the property
143
147
  // is simply absent. `expected` and `actual` agree in that state.
144
148
  expect(VERIFY_CYPHER).toContain(
145
- "sum(CASE WHEN e.routineDescription IS NULL THEN 1 ELSE 0 END) AS descNull",
149
+ "sum(CASE WHEN e.description IS NULL THEN 1 ELSE 0 END) AS descNull",
146
150
  );
147
151
  });
148
152
 
@@ -71,13 +71,14 @@ export interface RoutineVerifyCounts {
71
71
  actual: number;
72
72
  suspended: number;
73
73
  nextRunNull: number;
74
- /** Task 2214 — roster rows holding no description. Zero is the healthy value
75
- * on every account, not only a fresh one: `VERIFY_CYPHER` matches
76
- * `routineTemplate IS NOT NULL`, so operator-created routines are outside
77
- * its scope and cannot contribute, and `ON MATCH` writes the description
78
- * unconditionally, so an operator-edited row is covered too. Anything above
79
- * zero while `expected === actual` is a MERGE that wrote no description
80
- * the one failure here that emits no error and does not reproduce. */
74
+ /** Roster rows holding no description. Zero is the healthy value on every
75
+ * account, not only a fresh one: `VERIFY_CYPHER` matches `routineTemplate IS
76
+ * NOT NULL`, so operator-created routines are outside its scope and cannot
77
+ * contribute. Anything above zero while `expected === actual` is a MERGE that
78
+ * wrote no description on create, or a row whose key left the roster before
79
+ * the convergence ran the one failure here that emits no error and does not
80
+ * reproduce. `[schedule-audit] op=description-coverage` is the wider twin,
81
+ * over every automation row rather than the roster alone. */
81
82
  descNull: number;
82
83
  }
83
84
 
@@ -154,13 +155,18 @@ export async function resolveOwnerTimezone(
154
155
  * destination, or reset a retimed cron. Only the prompt, the name and the
155
156
  * version move, and only when the row is behind and unedited.
156
157
  *
157
- * `routineDescription` (Task 2214) is the one property ON MATCH writes on
158
- * every match rather than only on the refresh branch, and that is not an
159
- * exception to the rule above. It is roster-owned: the routines route projects
160
- * it but its PATCH field set refuses it, so no operator edit exists for this
161
- * to overwrite. Gating it on `refreshing` would leave every operator-edited
162
- * row description-less permanently, because `skip-operator-edited` takes
163
- * neither branch, and that row's card would show a title and nothing else. */
158
+ * `description` (Task 2361) is guarded exactly as the prompt and the name are,
159
+ * and that is a change of shape from Task 2214, which introduced it. It used to
160
+ * be written on every match, on the grounds that it was roster-owned and the
161
+ * routines PATCH refused it, so no operator edit existed for the seed to
162
+ * clobber, and that gating it would leave every operator-edited row
163
+ * description-less permanently because `skip-operator-edited` takes neither
164
+ * branch. Both halves of that argument are gone. The routines PATCH now accepts
165
+ * `description` and stamps `operatorEdited`, so an unconditional write would
166
+ * destroy the operator's own caption on the next version bump; and the
167
+ * scheduling plugin's one-shot `converge-routine-description` has already given
168
+ * every pre-existing row a description, so no row is left holding only a
169
+ * title. */
164
170
  export const MERGE_CYPHER = `
165
171
  UNWIND $rows AS row
166
172
  OPTIONAL MATCH (pre:Event {accountId: $accountId, routineTemplate: row.key})
@@ -173,7 +179,7 @@ WITH row, existed, priorVersion, edited,
173
179
  MERGE (e:Event {accountId: $accountId, routineTemplate: row.key})
174
180
  ON CREATE SET e.eventId = randomUUID(),
175
181
  e.name = row.name,
176
- e.routineDescription = row.description,
182
+ e.description = row.description,
177
183
  e.agentPrompt = row.prompt,
178
184
  e.recurrence = row.recurrence,
179
185
  e.routineTemplateVersion = $version,
@@ -193,7 +199,7 @@ MERGE (e:Event {accountId: $accountId, routineTemplate: row.key})
193
199
  e.updatedAt = $now
194
200
  ON MATCH SET e.agentPrompt = CASE WHEN refreshing THEN row.prompt ELSE e.agentPrompt END,
195
201
  e.name = CASE WHEN refreshing THEN row.name ELSE e.name END,
196
- e.routineDescription = row.description,
202
+ e.description = CASE WHEN refreshing THEN row.description ELSE e.description END,
197
203
  e.routineTemplateVersion = CASE WHEN refreshing THEN $version ELSE e.routineTemplateVersion END,
198
204
  e.updatedAt = CASE WHEN refreshing THEN $now ELSE e.updatedAt END
199
205
  RETURN row.key AS key,
@@ -211,7 +217,7 @@ WHERE e.routineTemplate IS NOT NULL
211
217
  RETURN count(e) AS actual,
212
218
  sum(CASE WHEN e.eventStatus = 'suspended' THEN 1 ELSE 0 END) AS suspended,
213
219
  sum(CASE WHEN e.nextRun IS NULL THEN 1 ELSE 0 END) AS nextRunNull,
214
- sum(CASE WHEN e.routineDescription IS NULL THEN 1 ELSE 0 END) AS descNull`;
220
+ sum(CASE WHEN e.description IS NULL THEN 1 ELSE 0 END) AS descNull`;
215
221
 
216
222
  export interface SeedRoutineRosterArgs {
217
223
  session: SeedSessionLike;
@@ -45,9 +45,9 @@ export interface RoutineTemplate {
45
45
  /** The title shown on `/routines`. */
46
46
  readonly name: string;
47
47
  /** One sentence under the title on the card: what this does and what it will
48
- * not do. Roster-owned and read-only projected by the routines route but
49
- * absent from its PATCH field set, so no operator edit exists for the seed to
50
- * overwrite and the MERGE rewrites it on every match. */
48
+ * not do. Seeded onto `e.description`, which the operator can then edit from
49
+ * the routines page. The seed rewrites it only on a version refresh of an
50
+ * unedited row, so an operator's own wording survives every upgrade. */
51
51
  readonly description: string;
52
52
  /** The proposed cron. Written ON CREATE only, never refreshed, so an operator
53
53
  * who retimes a routine keeps their expression across upgrades. */
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: platform-architecture
3
3
  description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
4
- content-hash: sha256:27883a95ee231fb265b37617f71dfdf1a71e28fccf76d09f9e75c0726c777f48
4
+ content-hash: sha256:db7c14326a115e7d05b687b72bef0ba3892f1614ee46484e0e90414bf2c16f27
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -3484,13 +3484,27 @@ The Routines surface (`/routines`) is the admin view of the account's
3484
3484
  automation `:Event` nodes — the device's scheduled agent turns and tool
3485
3485
  dispatches, which the Calendar surface deliberately excludes (`calendar.ts`
3486
3486
  unions only appointment events, `actionPlugin IS NULL`). It renders one card per
3487
- routine (title, the roster's one-sentence description where the routine has one,
3488
- human-readable schedule, next run, channel/destination or tool, status); a card
3489
- opens a detail modal modeled on the calendar `EventDetailModal`. The description
3490
- is roster-owned: the seed is its only writer, `GET /api/admin/routines` projects
3491
- it read-only, and the PATCH route refuses it, so an operator-created routine has
3492
- none and its card renders with no line and no placeholder.
3493
- The modal edits the prose instruction (`agentPrompt`), the dispatch binding
3487
+ routine (title, its one-line description where it has one, human-readable
3488
+ schedule, next run, channel/destination or tool, status); a card opens a detail
3489
+ modal modeled on the calendar `EventDetailModal`. A shipped routine is seeded with
3490
+ a description and the operator can rewrite it from the modal; a routine nobody has
3491
+ captioned renders with no line and no placeholder. Editing it stamps the row
3492
+ operator-owned, so the next roster refresh leaves the operator's wording alone,
3493
+ and recomputes the row's search vector, which is built from the name and the
3494
+ description. A description is never dispatched: `agentPrompt` is the only text a
3495
+ fire sends.
3496
+ The read-only view shows two prose blocks, each under its own heading, in the
3497
+ order **Description** then **Instruction sent to the agent**. Both use the same
3498
+ header-above-prose shape, so neither reads as a different kind of thing from the
3499
+ other. Before this the modal showed one unheaded block, so the caption and the
3500
+ multi-KB prompt that actually fires were indistinguishable. The instruction
3501
+ heading never reads the bare word "Instruction", in either mode: that collides
3502
+ with the `## Instruction` block the platform appends to every dispatched channel
3503
+ turn, and an agent read the two as the same field. The Description block renders
3504
+ on every kind of routine, including a tool-action routine and a bare trigger,
3505
+ because both already show it on the card; the instruction block belongs to an
3506
+ agent routine, the only kind that has one. The modal edits the description, the
3507
+ prose instruction (`agentPrompt`), the dispatch binding
3494
3508
  (`agentChannel` and `agentDestination`, chosen from a server-supplied roster
3495
3509
  rather than typed), and the schedule via a friendly builder (frequency
3496
3510
  daily/weekly/monthly, day-of-week, day-of-month, time) that falls back to a
@@ -3551,7 +3565,7 @@ The route (`server/routes/admin/routines.ts`) is account-scoped like `calendar.t
3551
3565
  | `GET /api/admin/routines/:eventId` | One routine, same automation+account guard. |
3552
3566
  | `GET /api/admin/routines/:eventId/runs` | One routine's `:RoutineRun` history, newest first. `limit` clamps to 1..500 and defaults to 50, so a caller that wants the full page must ask for it. |
3553
3567
  | `GET /api/admin/routines/:eventId/runs.csv` | The same projection as a `text/csv` attachment with `Content-Disposition`, and **no** limit, so it is not the same row set as a clamped `/runs` call. Re-parses its own output and returns 500 rather than serving a document with fewer rows than the query returned. Zero rows is 404, not a header-only file: the query cannot tell a routine with no runs from an `eventId` on another account. |
3554
- | `PATCH /api/admin/routines/:eventId` | Edits `agentPrompt`, `recurrence`, and the `agentChannel`/`agentDestination` pair. Cron validation and the timezone-aware `nextRun` recompute copy the scheduling plugin's `schedule-update` tool. The binding is revalidated against the same house-authority rule, shared as `platform/lib/agent-dispatch-rule`; it is refused `partial-binding`, `invalid-channel`, `no-prompt`, `not-registered`, `cross-account`, `action-routine` (a routine that runs a tool keeps it — an agent binding outranks the action at fire time) or `clear-would-orphan` (clearing the only field holding a one-time routine inside the automation filter would hide it from every routines surface), and no refusal writes. Both fields as `null` clears the binding. An invalid cron is rejected 400 with no write. Only creation and deletion stay agent-only (`schedule-event`/`schedule-cancel`). |
3568
+ | `PATCH /api/admin/routines/:eventId` | Edits `agentPrompt`, `description`, `recurrence`, and the `agentChannel`/`agentDestination` pair. A `description` write stamps `operatorEdited` and recomputes the row's `embedding` from `Event: <name>` plus the new text, reading the name from the row rather than the request body; the embedder returning nothing leaves the previous vector in place and the save still succeeds. A whitespace-only `description` is stored as null, so "no caption" is one state in the graph. Cron validation and the timezone-aware `nextRun` recompute copy the scheduling plugin's `schedule-update` tool. The binding is revalidated against the same house-authority rule, shared as `platform/lib/agent-dispatch-rule`; it is refused `partial-binding`, `invalid-channel`, `no-prompt`, `not-registered`, `cross-account`, `action-routine` (a routine that runs a tool keeps it — an agent binding outranks the action at fire time) or `clear-would-orphan` (clearing the only field holding a one-time routine inside the automation filter would hide it from every routines surface), and no refusal writes. Both fields as `null` clears the binding. An invalid cron is rejected 400 with no write. Only creation and deletion stay agent-only (`schedule-event`/`schedule-cancel`). |
3555
3569
  | `GET /api/admin/dispatch-destinations` | Every destination the session's account may bind a routine to, across both channels, each labelled by the authoritative list it came from (`House admin`, `Account manager`, `Telegram admin`), plus an optional `name`: a display-only person name resolved from `:Person` in the row's own account scope — the house for a `House admin` row, the managed sub-account for an `Account manager` row, and never for Telegram, whose destinations are chat ids. Computed from the same lists the validator reads and filtered by the same rule, so the picker cannot offer what the save refuses; `name` is never part of what Save posts, and a graph outage returns an unnamed roster rather than an error. |
3556
3570
  | `POST /api/admin/routines/:eventId/suspend` | Writes `eventStatus = 'suspended'` on a routine currently `'scheduled'`. 404s otherwise, so a second suspend is not a silent no-op. |
3557
3571
  | `POST /api/admin/routines/:eventId/resume` | Returns a suspended routine to `'scheduled'`. 404s if it is not suspended, 409s if it cannot be resumed. |
@@ -3632,6 +3646,24 @@ would say so. `completed` gets its own bucket even though no code path writes it
3632
3646
  today, because it remains part of the shipped enum and one row carrying it would
3633
3647
  otherwise pin `other` above zero forever and kill the alarm.
3634
3648
 
3649
+ `[schedule-audit] op=description-coverage acct=<8> routines=<n>
3650
+ withDescription=<n> withInstruction=<n> toolActions=<n>` is the pair behind the
3651
+ two prose blocks the modal shows. A routine carrying an instruction and no
3652
+ description is a card whose title is all the operator gets; one carrying a
3653
+ description and no instruction is worse, because it looks explained, fires on
3654
+ schedule and sends nothing. Neither state writes an event of its own, so only a
3655
+ periodic count reveals them, and the counts sit on one line beside the total
3656
+ because any one of them alone answers the wrong question. `toolActions` is on the
3657
+ line because `withInstruction` is otherwise comparable to nothing: a tool-action
3658
+ routine has no instruction and is not silent, it runs a tool, so an account
3659
+ holding one would sit at `routines > withInstruction` for ever and the alarm
3660
+ would be dead from that point on. The genuinely silent count is `routines -
3661
+ toolActions - withInstruction`, the same reconciliation `other` performs in the
3662
+ suspension census. Emitted per account on every sweep tick including a healthy
3663
+ one, over the same automation set the routes list. The roster seed's own
3664
+ `descNull` counter is its narrow twin, scoped to seeded rows and so blind to
3665
+ exactly the operator-created ones most likely to carry no caption.
3666
+
3635
3667
  **The shipped roster.** Every account starts with eleven routines —
3636
3668
  `start-of-day`, `inbound-check`, `end-of-day`, `appointment-reminder`,
3637
3669
  `commitment-chase`, `calendar-reconcile`, `contact-reconcile`, `crm-reconcile`,
@@ -9,6 +9,12 @@ Invoked by the admin agent directly.
9
9
 
10
10
  This is the platform's release timeline, newest first. Each entry shows the date it shipped and the version it shipped in, so you can tell the operator how current their install is. To compare, read the installed version from `capabilities-here` and match it against the versions below. Keep answers high level and in plain English; this is a summary, not a full commit log.
11
11
 
12
+ ## 2026-08-02 (0.1.548)
13
+
14
+ - Telegram now works per account: each bot has its own address and secret, a scheduled message goes to the right account, and a check reports any bot that is set up but never delivers.
15
+ - Every WhatsApp reply and document send now names the account it belongs to, and a send whose account does not match its conversation is refused.
16
+ - Each turn now records how much the assistant actually had to work with, so a turn that came back thin can be told apart from one that failed.
17
+
12
18
  ## 2026-08-02 (0.1.547)
13
19
 
14
20
  - A routine's past runs now read as cards showing status, channel and the message actually sent, with a refresh and a way back in. Runs with no captured output are flagged.
@@ -99,13 +99,27 @@ The Routines surface (`/routines`) is the admin view of the account's
99
99
  automation `:Event` nodes — the device's scheduled agent turns and tool
100
100
  dispatches, which the Calendar surface deliberately excludes (`calendar.ts`
101
101
  unions only appointment events, `actionPlugin IS NULL`). It renders one card per
102
- routine (title, the roster's one-sentence description where the routine has one,
103
- human-readable schedule, next run, channel/destination or tool, status); a card
104
- opens a detail modal modeled on the calendar `EventDetailModal`. The description
105
- is roster-owned: the seed is its only writer, `GET /api/admin/routines` projects
106
- it read-only, and the PATCH route refuses it, so an operator-created routine has
107
- none and its card renders with no line and no placeholder.
108
- The modal edits the prose instruction (`agentPrompt`), the dispatch binding
102
+ routine (title, its one-line description where it has one, human-readable
103
+ schedule, next run, channel/destination or tool, status); a card opens a detail
104
+ modal modeled on the calendar `EventDetailModal`. A shipped routine is seeded with
105
+ a description and the operator can rewrite it from the modal; a routine nobody has
106
+ captioned renders with no line and no placeholder. Editing it stamps the row
107
+ operator-owned, so the next roster refresh leaves the operator's wording alone,
108
+ and recomputes the row's search vector, which is built from the name and the
109
+ description. A description is never dispatched: `agentPrompt` is the only text a
110
+ fire sends.
111
+ The read-only view shows two prose blocks, each under its own heading, in the
112
+ order **Description** then **Instruction sent to the agent**. Both use the same
113
+ header-above-prose shape, so neither reads as a different kind of thing from the
114
+ other. Before this the modal showed one unheaded block, so the caption and the
115
+ multi-KB prompt that actually fires were indistinguishable. The instruction
116
+ heading never reads the bare word "Instruction", in either mode: that collides
117
+ with the `## Instruction` block the platform appends to every dispatched channel
118
+ turn, and an agent read the two as the same field. The Description block renders
119
+ on every kind of routine, including a tool-action routine and a bare trigger,
120
+ because both already show it on the card; the instruction block belongs to an
121
+ agent routine, the only kind that has one. The modal edits the description, the
122
+ prose instruction (`agentPrompt`), the dispatch binding
109
123
  (`agentChannel` and `agentDestination`, chosen from a server-supplied roster
110
124
  rather than typed), and the schedule via a friendly builder (frequency
111
125
  daily/weekly/monthly, day-of-week, day-of-month, time) that falls back to a
@@ -166,7 +180,7 @@ The route (`server/routes/admin/routines.ts`) is account-scoped like `calendar.t
166
180
  | `GET /api/admin/routines/:eventId` | One routine, same automation+account guard. |
167
181
  | `GET /api/admin/routines/:eventId/runs` | One routine's `:RoutineRun` history, newest first. `limit` clamps to 1..500 and defaults to 50, so a caller that wants the full page must ask for it. |
168
182
  | `GET /api/admin/routines/:eventId/runs.csv` | The same projection as a `text/csv` attachment with `Content-Disposition`, and **no** limit, so it is not the same row set as a clamped `/runs` call. Re-parses its own output and returns 500 rather than serving a document with fewer rows than the query returned. Zero rows is 404, not a header-only file: the query cannot tell a routine with no runs from an `eventId` on another account. |
169
- | `PATCH /api/admin/routines/:eventId` | Edits `agentPrompt`, `recurrence`, and the `agentChannel`/`agentDestination` pair. Cron validation and the timezone-aware `nextRun` recompute copy the scheduling plugin's `schedule-update` tool. The binding is revalidated against the same house-authority rule, shared as `platform/lib/agent-dispatch-rule`; it is refused `partial-binding`, `invalid-channel`, `no-prompt`, `not-registered`, `cross-account`, `action-routine` (a routine that runs a tool keeps it — an agent binding outranks the action at fire time) or `clear-would-orphan` (clearing the only field holding a one-time routine inside the automation filter would hide it from every routines surface), and no refusal writes. Both fields as `null` clears the binding. An invalid cron is rejected 400 with no write. Only creation and deletion stay agent-only (`schedule-event`/`schedule-cancel`). |
183
+ | `PATCH /api/admin/routines/:eventId` | Edits `agentPrompt`, `description`, `recurrence`, and the `agentChannel`/`agentDestination` pair. A `description` write stamps `operatorEdited` and recomputes the row's `embedding` from `Event: <name>` plus the new text, reading the name from the row rather than the request body; the embedder returning nothing leaves the previous vector in place and the save still succeeds. A whitespace-only `description` is stored as null, so "no caption" is one state in the graph. Cron validation and the timezone-aware `nextRun` recompute copy the scheduling plugin's `schedule-update` tool. The binding is revalidated against the same house-authority rule, shared as `platform/lib/agent-dispatch-rule`; it is refused `partial-binding`, `invalid-channel`, `no-prompt`, `not-registered`, `cross-account`, `action-routine` (a routine that runs a tool keeps it — an agent binding outranks the action at fire time) or `clear-would-orphan` (clearing the only field holding a one-time routine inside the automation filter would hide it from every routines surface), and no refusal writes. Both fields as `null` clears the binding. An invalid cron is rejected 400 with no write. Only creation and deletion stay agent-only (`schedule-event`/`schedule-cancel`). |
170
184
  | `GET /api/admin/dispatch-destinations` | Every destination the session's account may bind a routine to, across both channels, each labelled by the authoritative list it came from (`House admin`, `Account manager`, `Telegram admin`), plus an optional `name`: a display-only person name resolved from `:Person` in the row's own account scope — the house for a `House admin` row, the managed sub-account for an `Account manager` row, and never for Telegram, whose destinations are chat ids. Computed from the same lists the validator reads and filtered by the same rule, so the picker cannot offer what the save refuses; `name` is never part of what Save posts, and a graph outage returns an unnamed roster rather than an error. |
171
185
  | `POST /api/admin/routines/:eventId/suspend` | Writes `eventStatus = 'suspended'` on a routine currently `'scheduled'`. 404s otherwise, so a second suspend is not a silent no-op. |
172
186
  | `POST /api/admin/routines/:eventId/resume` | Returns a suspended routine to `'scheduled'`. 404s if it is not suspended, 409s if it cannot be resumed. |
@@ -247,6 +261,24 @@ would say so. `completed` gets its own bucket even though no code path writes it
247
261
  today, because it remains part of the shipped enum and one row carrying it would
248
262
  otherwise pin `other` above zero forever and kill the alarm.
249
263
 
264
+ `[schedule-audit] op=description-coverage acct=<8> routines=<n>
265
+ withDescription=<n> withInstruction=<n> toolActions=<n>` is the pair behind the
266
+ two prose blocks the modal shows. A routine carrying an instruction and no
267
+ description is a card whose title is all the operator gets; one carrying a
268
+ description and no instruction is worse, because it looks explained, fires on
269
+ schedule and sends nothing. Neither state writes an event of its own, so only a
270
+ periodic count reveals them, and the counts sit on one line beside the total
271
+ because any one of them alone answers the wrong question. `toolActions` is on the
272
+ line because `withInstruction` is otherwise comparable to nothing: a tool-action
273
+ routine has no instruction and is not silent, it runs a tool, so an account
274
+ holding one would sit at `routines > withInstruction` for ever and the alarm
275
+ would be dead from that point on. The genuinely silent count is `routines -
276
+ toolActions - withInstruction`, the same reconciliation `other` performs in the
277
+ suspension census. Emitted per account on every sweep tick including a healthy
278
+ one, over the same automation set the routes list. The roster seed's own
279
+ `descNull` counter is its narrow twin, scoped to seeded rows and so blind to
280
+ exactly the operator-created ones most likely to carry no caption.
281
+
250
282
  **The shipped roster.** Every account starts with eleven routines —
251
283
  `start-of-day`, `inbound-check`, `end-of-day`, `appointment-reminder`,
252
284
  `commitment-chase`, `calendar-reconcile`, `contact-reconcile`, `crm-reconcile`,
@@ -204,6 +204,12 @@ Use standard 5-field cron syntax: `minute hour day-of-month month day-of-week`.
204
204
  | `0 0 1 * *` | First of each month at midnight |
205
205
  | `*/30 * * * *` | Every 30 minutes |
206
206
 
207
+ ## What an update confirms
208
+
209
+ `schedule-update` returns the properties it wrote: `Event updated: <id> — fields: description, recurrence`. Companions count — changing `recurrence` also writes `nextRun`, changing `eventStatus` also writes `suspendedInBulk`, and setting an `agentDispatch` also clears `actionPlugin`, `actionTool` and `actionArgs`. Four properties are never named: `updatedAt` and `operatorEdited` are stamps, `embedding` is a derived search vector, and `notes` is reported as `note appended` instead, and only when the caller supplied one that was not empty.
210
+
211
+ When `agentPrompt` is not among the fields, the line ends `(the instruction sent to the agent was not changed)`. That clause is the answer to "did my prompt rewrite land" — a confirmation without it means the dispatch prompt was written. Editing a meeting opens `Meeting updated:` and names the caller's own field names, not the stored `title`/`startsAt`/`endsAt`.
212
+
207
213
  ## Skip next
208
214
 
209
215
  For recurring events, `schedule-update` with `skipNext: true` advances `nextRun` by one cycle without triggering. Use when the user says "skip tomorrow's briefing" or similar.
@@ -7,6 +7,7 @@ import { scheduleList } from "./tools/schedule-list.js";
7
7
  import { scheduleGet } from "./tools/schedule-get.js";
8
8
  import { scheduleRuns } from "./tools/schedule-runs.js";
9
9
  import { scheduleUpdate, EVENT_STATUSES } from "./tools/schedule-update.js";
10
+ import { describeUpdate } from "./lib/update-summary.js";
10
11
  import { scheduleCancel } from "./tools/schedule-cancel.js";
11
12
  import { scheduleExportIcs } from "./tools/schedule-export-ics.js";
12
13
  import { scheduleImportIcs } from "./tools/schedule-import-ics.js";
@@ -328,11 +329,14 @@ eagerTool(server, "schedule-update", "Update an event's properties or append a n
328
329
  if (!accountId)
329
330
  return refuseNoAccount("schedule-update");
330
331
  try {
331
- await scheduleUpdate({ ...params, accountId });
332
+ const written = await scheduleUpdate({ ...params, accountId });
332
333
  if (params.skipNext) {
333
334
  return { content: [{ type: "text", text: `Skipped next occurrence of event: ${params.eventId}` }] };
334
335
  }
335
- return { content: [{ type: "text", text: `Event updated: ${params.eventId}` }] };
336
+ // Task 2362 name the properties written. The flat `Event updated: <id>` this replaced read
337
+ // identically for a description edit and for a dispatch-prompt rewrite, and an agent read it
338
+ // as proof its own successful rewrite had not landed.
339
+ return { content: [{ type: "text", text: describeUpdate(params.eventId, written) }] };
336
340
  }
337
341
  catch (err) {
338
342
  return {