@jungjaehoon/mama-os 0.57.0 → 0.58.1

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 (99) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/dist/api/attachment-actions.d.ts.map +1 -1
  4. package/dist/api/attachment-actions.js +5 -3
  5. package/dist/api/attachment-actions.js.map +1 -1
  6. package/dist/api/code-act-actions.d.ts +28 -0
  7. package/dist/api/code-act-actions.d.ts.map +1 -0
  8. package/dist/api/code-act-actions.js +216 -0
  9. package/dist/api/code-act-actions.js.map +1 -0
  10. package/dist/api/help-actions.d.ts +15 -0
  11. package/dist/api/help-actions.d.ts.map +1 -0
  12. package/dist/api/help-actions.js +182 -0
  13. package/dist/api/help-actions.js.map +1 -0
  14. package/dist/api/owner-timezone-actions.d.ts.map +1 -1
  15. package/dist/api/owner-timezone-actions.js +1 -1
  16. package/dist/api/owner-timezone-actions.js.map +1 -1
  17. package/dist/api/record-actions.d.ts +8 -0
  18. package/dist/api/record-actions.d.ts.map +1 -0
  19. package/dist/api/record-actions.js +53 -0
  20. package/dist/api/record-actions.js.map +1 -0
  21. package/dist/api/report-actions.d.ts +0 -2
  22. package/dist/api/report-actions.d.ts.map +1 -1
  23. package/dist/api/report-actions.js +5 -5
  24. package/dist/api/report-actions.js.map +1 -1
  25. package/dist/api/report-source-actions.js +2 -2
  26. package/dist/api/report-source-actions.js.map +1 -1
  27. package/dist/api/viewer-server.d.ts +1 -0
  28. package/dist/api/viewer-server.d.ts.map +1 -1
  29. package/dist/api/viewer-server.js +21 -17
  30. package/dist/api/viewer-server.js.map +1 -1
  31. package/dist/api/work-actions.d.ts +0 -13
  32. package/dist/api/work-actions.d.ts.map +1 -1
  33. package/dist/api/work-actions.js +6 -37
  34. package/dist/api/work-actions.js.map +1 -1
  35. package/dist/cli/commands/daemon.d.ts.map +1 -1
  36. package/dist/cli/commands/daemon.js +14 -2
  37. package/dist/cli/commands/daemon.js.map +1 -1
  38. package/dist/connectors/framework/polling-scheduler.d.ts.map +1 -1
  39. package/dist/connectors/framework/polling-scheduler.js +9 -0
  40. package/dist/connectors/framework/polling-scheduler.js.map +1 -1
  41. package/dist/gateways/telegram-format.js +2 -2
  42. package/dist/gateways/telegram-format.js.map +1 -1
  43. package/dist/operator/board-read-views.d.ts.map +1 -1
  44. package/dist/operator/board-read-views.js +3 -3
  45. package/dist/operator/board-read-views.js.map +1 -1
  46. package/dist/operator/board-slot-instructions.d.ts +4 -6
  47. package/dist/operator/board-slot-instructions.d.ts.map +1 -1
  48. package/dist/operator/board-slot-instructions.js +11 -24
  49. package/dist/operator/board-slot-instructions.js.map +1 -1
  50. package/dist/runtime/action-mcp-server.d.ts.map +1 -1
  51. package/dist/runtime/action-mcp-server.js +30 -9
  52. package/dist/runtime/action-mcp-server.js.map +1 -1
  53. package/dist/runtime/action-surface.d.ts +1 -1
  54. package/dist/runtime/action-surface.d.ts.map +1 -1
  55. package/dist/runtime/action-surface.js +26 -4
  56. package/dist/runtime/action-surface.js.map +1 -1
  57. package/dist/runtime/native-session.d.ts +6 -0
  58. package/dist/runtime/native-session.d.ts.map +1 -1
  59. package/dist/runtime/native-session.js +13 -15
  60. package/dist/runtime/native-session.js.map +1 -1
  61. package/dist/runtime/owner-runtime.d.ts +14 -3
  62. package/dist/runtime/owner-runtime.d.ts.map +1 -1
  63. package/dist/runtime/owner-runtime.js +77 -13
  64. package/dist/runtime/owner-runtime.js.map +1 -1
  65. package/dist/runtime/owner-system-prompt.d.ts +6 -8
  66. package/dist/runtime/owner-system-prompt.d.ts.map +1 -1
  67. package/dist/runtime/owner-system-prompt.js +67 -73
  68. package/dist/runtime/owner-system-prompt.js.map +1 -1
  69. package/dist/runtime/recent-owner-exchanges.d.ts +0 -2
  70. package/dist/runtime/recent-owner-exchanges.d.ts.map +1 -1
  71. package/dist/runtime/recent-owner-exchanges.js +0 -26
  72. package/dist/runtime/recent-owner-exchanges.js.map +1 -1
  73. package/dist/runtime/record-orders.d.ts +49 -0
  74. package/dist/runtime/record-orders.d.ts.map +1 -0
  75. package/dist/runtime/record-orders.js +152 -0
  76. package/dist/runtime/record-orders.js.map +1 -0
  77. package/dist/runtime/report-scheduler.d.ts.map +1 -1
  78. package/dist/runtime/report-scheduler.js +14 -11
  79. package/dist/runtime/report-scheduler.js.map +1 -1
  80. package/dist/runtime/stimulus-delivery.d.ts +25 -27
  81. package/dist/runtime/stimulus-delivery.d.ts.map +1 -1
  82. package/dist/runtime/stimulus-delivery.js +184 -268
  83. package/dist/runtime/stimulus-delivery.js.map +1 -1
  84. package/dist/runtime/timezone.d.ts +2 -0
  85. package/dist/runtime/timezone.d.ts.map +1 -1
  86. package/dist/runtime/timezone.js +9 -0
  87. package/dist/runtime/timezone.js.map +1 -1
  88. package/dist/runtime/turn-orders.d.ts +86 -0
  89. package/dist/runtime/turn-orders.d.ts.map +1 -0
  90. package/dist/runtime/turn-orders.js +268 -0
  91. package/dist/runtime/turn-orders.js.map +1 -0
  92. package/dist/utils/untrusted-content.d.ts.map +1 -1
  93. package/dist/utils/untrusted-content.js +4 -1
  94. package/dist/utils/untrusted-content.js.map +1 -1
  95. package/package.json +2 -2
  96. package/dist/runtime/report-prompts.d.ts +0 -14
  97. package/dist/runtime/report-prompts.d.ts.map +0 -1
  98. package/dist/runtime/report-prompts.js +0 -69
  99. package/dist/runtime/report-prompts.js.map +0 -1
@@ -5,14 +5,14 @@ exports.sourceDeltaStimulusId = sourceDeltaStimulusId;
5
5
  exports.createStimulusIntake = createStimulusIntake;
6
6
  exports.renderWindowQueue = renderWindowQueue;
7
7
  exports.stimulusFailureReason = stimulusFailureReason;
8
+ exports.isRecordOrderRow = isRecordOrderRow;
8
9
  exports.createStimulusDelivery = createStimulusDelivery;
9
10
  const untrusted_content_js_1 = require("../utils/untrusted-content.js");
10
11
  const node_crypto_1 = require("node:crypto");
11
12
  const canonicalize_1 = require("@jungjaehoon/mama-core/canonicalize");
12
- const recent_owner_exchanges_js_1 = require("./recent-owner-exchanges.js");
13
13
  const owner_system_prompt_js_1 = require("./owner-system-prompt.js");
14
- const report_prompts_js_1 = require("./report-prompts.js");
15
- const work_actions_js_1 = require("../api/work-actions.js");
14
+ const turn_orders_js_1 = require("./turn-orders.js");
15
+ const timezone_js_1 = require("./timezone.js");
16
16
  exports.OWNER_RUNTIME_SESSION_KEY = 'owner:runtime';
17
17
  function sourceObservationHandle(ref) {
18
18
  if (typeof ref.observationRef !== 'string' || ref.observationRef.trim() === '') {
@@ -122,14 +122,6 @@ function createStimulusIntake(runtime, principalId) {
122
122
  occurredAt: input.occurredAt,
123
123
  ...(input.payload === undefined ? {} : { payload: input.payload }),
124
124
  }),
125
- acceptNativeEvent: (input) => runtime.accept({
126
- id: input.id,
127
- kind: 'native_event',
128
- principalId,
129
- channelKey: input.channelKey,
130
- occurredAt: input.occurredAt,
131
- ...(input.payload === undefined ? {} : { payload: input.payload }),
132
- }),
133
125
  };
134
126
  }
135
127
  function payloadCarriesMessageText(payload) {
@@ -139,22 +131,6 @@ function payloadCarriesMessageText(payload) {
139
131
  return (Array.isArray(refs) &&
140
132
  refs.some((ref) => ref !== null && typeof ref === 'object' && !Array.isArray(ref) && 'contentPreview' in ref));
141
133
  }
142
- function localStamp(iso, timeZone) {
143
- const ms = Date.parse(iso);
144
- if (!Number.isFinite(ms))
145
- throw new Error(`A message line needs a source time, got ${iso}`);
146
- const values = Object.fromEntries(new Intl.DateTimeFormat('en-CA', {
147
- timeZone,
148
- month: '2-digit',
149
- day: '2-digit',
150
- hour: '2-digit',
151
- minute: '2-digit',
152
- hourCycle: 'h23',
153
- })
154
- .formatToParts(new Date(ms))
155
- .map((part) => [part.type, part.value]));
156
- return `${values.month}-${values.day} ${values.hour}:${values.minute}`;
157
- }
158
134
  function textField(value) {
159
135
  return typeof value === 'string' ? value : '';
160
136
  }
@@ -179,7 +155,7 @@ function messageLines(payload, timeZone) {
179
155
  const channel = channelName
180
156
  ? `${textField(ref.connector)}:${channelName}`
181
157
  : textField(ref.channel) || textField(ref.connector);
182
- lines.push(`[${localStamp(textField(ref.sourceAt), timeZone.get())}] ${channel} · ${author} · ${textField(ref.observationRef)}: ${text}`);
158
+ lines.push(`[${(0, timezone_js_1.localStamp)(textField(ref.sourceAt), timeZone.get())}] ${channel} · ${author} · ${textField(ref.observationRef)}: ${text}`);
183
159
  }
184
160
  return lines;
185
161
  }
@@ -237,216 +213,66 @@ function renderWindowQueue(queue) {
237
213
  }
238
214
  return (0, untrusted_content_js_1.wrapUntrustedContent)('source_delta', lines.join('\n'));
239
215
  }
240
- function boundedStimulus(row, liveSourceDelta, options, candidates = []) {
241
- if (row.kind === 'scheduled')
242
- return (0, report_prompts_js_1.buildScheduledReportPrompt)(row.payload, new Date(row.occurredAt), {
243
- wikiEnabled: options.wikiEnabled,
244
- messenger: options.formattingRoutes?.reports,
245
- timeZone: options.timeZone.get(),
246
- });
216
+ /**
217
+ * Instructions a replay window needs and live turns do not: the window's orchestration and its
218
+ * recording rules, moved here from the standing prompt.
219
+ */
220
+ function replayInstructions(backend, wikiEnabled) {
221
+ return [
222
+ 'replay_instructions:',
223
+ `- You are the orchestrator of this window and must know what happened. Note the time your turn starts. Plan from sections A, B, C, suspected duplicates and unresolved; decide new work (C) yourself and give it an owner; give each native subagent a disjoint set of work items (with their full source lines, history and current revisions) and the topic pages it owns, and wait for every receipt (each commitmentId with revision before and after, created commitmentIds, topic pages updated, anything it could not do). Then read back with work.list view=items changedSince=<your turn start>, compare it with the receipts, and settle gaps, conflicts and duplicates yourself. Only then write the journal's judgment section, the board, Home.md and lessons. The window's current_work already lists every item with its current revision; do not list the whole ledger again. Each subagent adds one entry per moved item to daily/YYYY-MM-DD.md, grouped by project, which you create before dispatching.`,
224
+ `- ${owner_system_prompt_js_1.SUBAGENT_RUNTIME_RULES[backend].replace('only when an order asks for one', 'for this window')} When a subagent finishes you verify and integrate its result and do not spawn another for the same objective.`,
225
+ `- For a moved item, revise or create the work item (work.revise, work.create) with a summary of what changed and why, derived_from links to its observations, and the assignee and roles the evidence points to. Record "unconfirmed" only when no observation points to anyone. Set eventDatetime to the source event time, not replay time. When current_work supplies a revision, pass it as expectedRevision; for another write in the same window use the revision the previous write returned.`,
226
+ wikiEnabled
227
+ ? `- With end_of_window_instructions, finish the day's work changes before updating each affected board section with report.publish and topic wiki page with manage.wiki.update.`
228
+ : `- With end_of_window_instructions, finish the day's work changes before updating each affected board section with report.publish.`,
229
+ '- A replay window is history and is not delivered to the owner: it ends without a marker.',
230
+ ];
231
+ }
232
+ /** The replay window text: the window material and its instructions. */
233
+ function replayWindowText(row, options) {
247
234
  const lines = [
248
- '## Bounded stimulus',
235
+ '## Replay window',
249
236
  `owner timezone: ${options.timeZone.get()}`,
250
- `kind: ${row.kind ?? 'unknown'}`,
251
237
  `stimulus_id: ${row.stimulusId}`,
252
238
  `channel: ${row.channelKey}`,
253
239
  `occurred_at: ${new Date(row.occurredAt).toISOString()}`,
254
- `preview: ${row.kind === 'source_delta' ? (0, untrusted_content_js_1.wrapUntrustedContent)('source_delta', JSON.stringify(row.preview)) : JSON.stringify(row.preview)}`,
240
+ 'delivery: replay window, not delivered to the owner',
241
+ ...replayInstructions(options.backend, options.wikiEnabled ?? false),
255
242
  ];
256
- if (row.kind === 'owner_message') {
257
- const messenger = row.stimulusId.split(':', 1)[0];
258
- lines.push(`messenger: ${messenger}`);
259
- const payload = row.payload;
260
- const input = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload.input : undefined;
261
- const attachments = input && typeof input === 'object' && !Array.isArray(input) ? input.attachments : undefined;
262
- if (Array.isArray(attachments)) {
263
- for (const attachment of attachments) {
264
- if (!attachment || typeof attachment !== 'object' || Array.isArray(attachment))
265
- continue;
266
- const name = JSON.stringify(attachment.name);
267
- if (typeof attachment.error === 'string') {
268
- lines.push(`attachment: name=${name} error=${JSON.stringify(attachment.error)}`);
269
- }
270
- else {
271
- lines.push(`attachment: name=${name} path=${JSON.stringify(attachment.path)}${typeof attachment.size === 'number' ? ` size=${attachment.size} bytes` : ''}`);
272
- }
273
- }
274
- }
275
- }
276
- const messages = row.kind === 'source_delta' ? messageLines(row.payload, options.timeZone) : null;
243
+ const messages = messageLines(row.payload, options.timeZone);
277
244
  if (messages === null)
278
245
  lines.push(`refs: ${JSON.stringify(row.refs)}`);
279
- if (row.kind === 'source_delta') {
280
- if (liveSourceDelta) {
281
- lines.push(`response_routing: ${(0, owner_system_prompt_js_1.liveDeltaRoutingInstruction)(options.backend ?? 'codex', options.wikiEnabled)}`);
282
- lines.push(`formatting: ${options.formattingRoutes?.notifications ?? 'telegram'}`);
283
- if (candidates.length > 0)
284
- lines.push('candidates (you decide):', ...candidates);
246
+ lines.push(row.refs.length === 0
247
+ ? 'source_read: this replay window has no source messages.'
248
+ : messages !== null
249
+ ? 'source_read: each message line below carries its full text; use the source read action with observationRefs only for a raw record or attachment you need, batched per connector (the first segment of the channel) with source set to that connector.'
250
+ : 'source_read: read these refs with the source read action, in one batched call with observationRefs; content remains bounded per ref.');
251
+ const payload = row.payload;
252
+ const replay = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload.replay : undefined;
253
+ if (replay && typeof replay === 'object' && !Array.isArray(replay)) {
254
+ const instructions = replay.endInstructions;
255
+ if (typeof instructions === 'string' && instructions.trim() !== '') {
256
+ lines.push(`window_end_instructions: ${instructions}`);
285
257
  }
286
- lines.push(row.refs.length === 0
287
- ? 'source_read: this replay window has no source messages.'
288
- : messages !== null
289
- ? 'source_read: each message line below carries its full text (a Trello action rendered from its record); use the source read action named in your standing instructions with observationRefs only for a raw record or attachment you need, batched per connector (the first segment of the channel) with source set to that connector.'
290
- : 'source_read: read these delta refs with the source read action named in your standing instructions, in one batched call with observationRefs; content remains bounded per ref.');
291
- const payload = row.payload;
292
- const replay = payload && typeof payload === 'object' && !Array.isArray(payload)
293
- ? payload.replay
294
- : undefined;
295
- if (replay && typeof replay === 'object' && !Array.isArray(replay)) {
296
- const instructions = replay.endInstructions;
297
- if (typeof instructions === 'string' && instructions.trim() !== '') {
298
- lines.push(`window_end_instructions: ${instructions}`);
299
- }
300
- if (replay.queue !== undefined) {
301
- lines.push('window_queue:', renderWindowQueue(replay.queue));
302
- if (messages !== null) {
303
- const work = ledgerLines(replay);
304
- lines.push(`current_work (commitmentId | revision | title | stage | status | assignee | lastEventTime), ${String(work.length)} items:`, ...work);
305
- }
306
- return lines.join('\n');
258
+ if (replay.queue !== undefined) {
259
+ lines.push('window_queue:', renderWindowQueue(replay.queue));
260
+ if (messages !== null) {
261
+ const work = ledgerLines(replay);
262
+ lines.push(`current_work (commitmentId | revision | title | stage | status | assignee | lastEventTime), ${String(work.length)} items:`, ...work);
307
263
  }
308
- }
309
- if (messages !== null) {
310
- const work = ledgerLines(replay);
311
- lines.push(`current_work (commitmentId | revision | title | stage | status | assignee | lastEventTime), ${String(work.length)} items:`, ...work, `messages (${options.timeZone.get()}, channel · sender · observationRef: text), ${String(messages.length)} lines:`, ...messages.map((message) => (0, untrusted_content_js_1.wrapUntrustedContent)('source_delta', message)));
312
264
  return lines.join('\n');
313
265
  }
314
266
  }
267
+ if (messages !== null) {
268
+ const work = ledgerLines(replay);
269
+ lines.push(`current_work (commitmentId | revision | title | stage | status | assignee | lastEventTime), ${String(work.length)} items:`, ...work, `messages (${options.timeZone.get()}, channel · sender · observationRef: text), ${String(messages.length)} lines:`, ...messages.map((message) => (0, untrusted_content_js_1.wrapUntrustedContent)('source_delta', message)));
270
+ return lines.join('\n');
271
+ }
315
272
  if (row.payload !== undefined)
316
- lines.push(`payload: ${row.kind === 'source_delta' ? (0, untrusted_content_js_1.wrapUntrustedContent)('source_delta', JSON.stringify(row.payload)) : JSON.stringify(row.payload)}`);
273
+ lines.push(`payload: ${(0, untrusted_content_js_1.wrapUntrustedContent)('source_delta', JSON.stringify(row.payload))}`);
317
274
  return lines.join('\n');
318
275
  }
319
- function sourcePayloadObject(row) {
320
- return row.payload && typeof row.payload === 'object' && !Array.isArray(row.payload)
321
- ? row.payload
322
- : null;
323
- }
324
- function payloadRefs(payload) {
325
- return Array.isArray(payload.refs)
326
- ? payload.refs.filter((ref) => ref !== null && typeof ref === 'object' && !Array.isArray(ref))
327
- : [];
328
- }
329
- /** The delta's text: its bounded preview lines (what the collector saw) and any ref previews. */
330
- function stimulusText(row) {
331
- const payload = sourcePayloadObject(row);
332
- if (!payload)
333
- return '';
334
- const preview = Array.isArray(payload.preview)
335
- ? payload.preview.filter((line) => typeof line === 'string')
336
- : [];
337
- return [...preview, ...payloadRefs(payload).map((ref) => textField(ref.contentPreview))]
338
- .filter(Boolean)
339
- .join(' ');
340
- }
341
- /** The delta's channel key and the channel labels its refs carry. */
342
- function stimulusChannels(row) {
343
- const payload = sourcePayloadObject(row);
344
- if (!payload)
345
- return new Set();
346
- return new Set([
347
- textField(payload.channel),
348
- ...payloadRefs(payload).flatMap((ref) => {
349
- const metadata = ref.metadata && typeof ref.metadata === 'object' && !Array.isArray(ref.metadata)
350
- ? ref.metadata
351
- : {};
352
- return [
353
- textField(ref.channel),
354
- textField(ref.channelName),
355
- textField(metadata.channelName),
356
- ];
357
- }),
358
- ].filter(Boolean));
359
- }
360
- function pipelineText(value) {
361
- return typeof value === 'string' ? value : '';
362
- }
363
- function pipelineTime(value) {
364
- const time = typeof value === 'number' ? value : Date.parse(pipelineText(value));
365
- return Number.isFinite(time) ? new Date(time).toISOString() : '';
366
- }
367
- function relatedWorkCandidates(row, workItems) {
368
- const rows = Array.isArray(workItems) ? workItems : [];
369
- const channels = stimulusChannels(row);
370
- const query = stimulusText(row);
371
- const cutoff = row.occurredAt - 14 * 24 * 60 * 60 * 1000;
372
- const candidates = rows.flatMap((raw) => {
373
- if (!raw || typeof raw !== 'object' || Array.isArray(raw))
374
- return [];
375
- const item = raw;
376
- const title = pipelineText(item.title);
377
- const commitmentId = pipelineText(item.commitmentId);
378
- const changedAt = typeof item.updatedAt === 'number' ? item.updatedAt : Number.NaN;
379
- if (!title || !commitmentId || !Number.isFinite(changedAt) || changedAt < cutoff)
380
- return [];
381
- const sameChannel = channels.has(pipelineText(item.sourceChannel));
382
- const overlap = (0, work_actions_js_1.workListTitleTextScore)(query, title);
383
- if (!sameChannel && overlap === 0)
384
- return [];
385
- return [{ item, title, commitmentId, overlap, sameChannel, changedAt }];
386
- });
387
- return candidates
388
- .sort((a, b) => Number(b.sameChannel) - Number(a.sameChannel) ||
389
- b.overlap - a.overlap ||
390
- b.changedAt - a.changedAt)
391
- .slice(0, 5)
392
- .map(({ item, title, commitmentId }) => `${title} | ${pipelineText(item.stage) || '-'} | ${pipelineText(item.assignee) || '-'} | ${pipelineTime(item.updatedAt)} | ${commitmentId}`);
393
- }
394
- function oneLine(value) {
395
- return value.replace(/\s+/g, ' ').trim();
396
- }
397
- function guidanceLine(entry) {
398
- const when = entry.applies_when
399
- ? `applies when: ${oneLine(entry.applies_when)}`
400
- : `summary: ${oneLine(entry.summary)}`;
401
- return `${oneLine(entry.id)} | ${entry.kind} | ${oneLine(entry.topic)} | ${when}`;
402
- }
403
- function guidanceVersion(entry) {
404
- return `${String(entry.updated_at)}\0${entry.kind}\0${entry.status}`;
405
- }
406
- function activeGuidance(entries) {
407
- return entries
408
- .filter((entry) => entry.status === 'active')
409
- .slice()
410
- .sort((left, right) => left.id.localeCompare(right.id));
411
- }
412
- function renderGuidanceIndex(entries) {
413
- return [
414
- '<guidance-index>',
415
- ...activeGuidance(entries).map(guidanceLine),
416
- '</guidance-index>',
417
- ].join('\n');
418
- }
419
- function renderGuidanceDelta(entries, previous) {
420
- const changes = [];
421
- for (const entry of entries) {
422
- const before = previous.get(entry.id);
423
- const after = guidanceVersion(entry);
424
- if (before === after)
425
- continue;
426
- if (entry.status === 'active') {
427
- changes.push(`${before === undefined ? 'added' : 'revised'}: ${guidanceLine(entry)}`);
428
- }
429
- else if (before?.endsWith('\0active')) {
430
- changes.push(`retired: ${oneLine(entry.id)} | ${entry.kind} | ${oneLine(entry.topic)} | status: ${entry.status}`);
431
- }
432
- }
433
- return changes.length === 0
434
- ? ''
435
- : [
436
- '<guidance-delta>',
437
- ...changes.sort((left, right) => left.localeCompare(right)),
438
- '</guidance-delta>',
439
- ].join('\n');
440
- }
441
- /** The turn carries the stimulus plus the session index or guidance changes. */
442
- function assembledContent(row, sessionBlocks, liveSourceDelta, options, candidates = []) {
443
- return [
444
- {
445
- type: 'text',
446
- text: [...sessionBlocks, boundedStimulus(row, liveSourceDelta, options, candidates)].join('\n\n'),
447
- },
448
- ];
449
- }
450
276
  /** One bounded log line; preserve the original thrown error for runtime settlement. */
451
277
  function stimulusFailureReason(error) {
452
278
  return (error instanceof Error ? error.message : String(error))
@@ -472,21 +298,115 @@ function replaySourceCeiling(row) {
472
298
  }
473
299
  return Number(replay.windowEndMs) - 1;
474
300
  }
301
+ function isRecordOrderRow(row) {
302
+ return row.kind === 'scheduled' && row.channelKey === turn_orders_js_1.RECORD_ORDER_CHANNEL;
303
+ }
304
+ function ownerText(row) {
305
+ const payload = row.payload;
306
+ return payload &&
307
+ typeof payload === 'object' &&
308
+ !Array.isArray(payload) &&
309
+ typeof payload.text === 'string'
310
+ ? payload.text
311
+ : '';
312
+ }
313
+ /** What a row's turn says, by kind; a live delta with only history lines has no turn. */
314
+ function planTurn(row, options) {
315
+ const zone = options.timeZone.get();
316
+ if (row.kind === 'owner_message') {
317
+ return {
318
+ kind: 'turn',
319
+ lessonQuery: ownerText(row),
320
+ render: (lessons) => (0, turn_orders_js_1.ownerMessageOrder)({
321
+ messenger: row.stimulusId.split(':', 1)[0],
322
+ occurredAt: row.occurredAt,
323
+ payload: row.payload,
324
+ }, lessons, { timeZone: zone }),
325
+ };
326
+ }
327
+ if (row.kind === 'source_delta') {
328
+ if (replaySourceCeiling(row) !== undefined)
329
+ return { kind: 'turn', lessonQuery: null, render: () => replayWindowText(row, options) };
330
+ // Kagemusha filters at collection: the reference is when the row was accepted, so a delta
331
+ // that waited behind a backlog is still delivered.
332
+ const all = (0, turn_orders_js_1.deltaLines)(row.payload);
333
+ const lines = (0, turn_orders_js_1.liveDeltaLines)(all, row.createdAt);
334
+ if (lines.length === 0)
335
+ return {
336
+ kind: 'skip',
337
+ reason: `all ${all.length} lines were older than the six-hour backfill guard when accepted`,
338
+ };
339
+ return {
340
+ kind: 'turn',
341
+ lessonQuery: lines.map((line) => line.text).join(' '),
342
+ render: (lessons, now) => (0, turn_orders_js_1.deltaNotifyOrder)(lines, row.channelKey, now, lessons, { timeZone: zone }),
343
+ };
344
+ }
345
+ if (row.kind === 'scheduled') {
346
+ if (row.channelKey === turn_orders_js_1.RECORD_ORDER_CHANNEL) {
347
+ const record = (0, turn_orders_js_1.parseRecordOrder)(row.payload);
348
+ return {
349
+ kind: 'turn',
350
+ lessonQuery: null,
351
+ render: (_lessons, now) => (0, turn_orders_js_1.deltaRecordOrder)(record, now, {
352
+ backend: options.backend,
353
+ timeZone: zone,
354
+ wikiEnabled: options.wikiEnabled ?? false,
355
+ }),
356
+ };
357
+ }
358
+ if (row.channelKey === turn_orders_js_1.REPORT_CHANNEL)
359
+ return {
360
+ kind: 'turn',
361
+ lessonQuery: null,
362
+ render: () => (0, turn_orders_js_1.scheduledReportOrder)(row.payload, new Date(row.occurredAt), {
363
+ backend: options.backend,
364
+ timeZone: zone,
365
+ messenger: options.formattingRoutes?.reports ?? 'telegram',
366
+ }),
367
+ };
368
+ throw new Error(`Scheduled input on unknown channel ${row.channelKey}`);
369
+ }
370
+ throw new Error(`No owner turn is assembled for stimulus kind ${row.kind ?? 'unknown'}`);
371
+ }
475
372
  /** Deliver every model-bearing kind through one serialized owner session. */
476
373
  function createStimulusDelivery(options) {
477
374
  let serialTail = Promise.resolve();
478
375
  let activeReplaySourceEndMs;
479
- const guidanceBySessionKey = new Map();
376
+ // Lessons shown in this session, cleared on a new session or a new local day: compactions are
377
+ // not observable, so a day bounds how long a lesson stays out of view.
378
+ let shownLessons = new Set();
379
+ let shownDay = '';
380
+ const pickLessons = async (query, newSession) => {
381
+ const day = (0, timezone_js_1.localDateKey)(Date.now(), options.timeZone.get());
382
+ if (newSession || day !== shownDay) {
383
+ shownLessons = new Set();
384
+ shownDay = day;
385
+ }
386
+ if (query === null || query.trim() === '' || !options.lessons)
387
+ return [];
388
+ const fresh = (await options.lessons(query))
389
+ .filter((lesson) => !shownLessons.has(lesson.id))
390
+ .slice(0, 3);
391
+ for (const lesson of fresh)
392
+ shownLessons.add(lesson.id);
393
+ return fresh;
394
+ };
480
395
  const deliverResult = async (row, result, modelRunId = result.modelRunId) => {
481
396
  if (row.kind === 'owner_message')
482
397
  await options.onOwnerResult?.(row, result);
483
- if (row.kind === 'source_delta' && replaySourceCeiling(row) === undefined)
398
+ if (row.kind === 'source_delta' && replaySourceCeiling(row) === undefined) {
399
+ // The record order is durable before the notify reply goes anywhere.
400
+ options.recordOrders?.enqueueFirst(row);
484
401
  await options.onSourceResult?.(row, result);
485
- if (row.kind === 'scheduled')
486
- await options.onScheduledResult?.(row, result);
487
- if (row.kind === 'native_event')
488
- await options.onNativeEventResult?.(row, result);
489
- await options.onDelivered?.(row, modelRunId);
402
+ }
403
+ if (row.kind === 'scheduled') {
404
+ if (isRecordOrderRow(row))
405
+ await options.recordOrders?.onResult(row, modelRunId ?? null);
406
+ else
407
+ await options.onScheduledResult?.(row, result);
408
+ }
409
+ await options.onDelivered?.(row, modelRunId ?? null);
490
410
  };
491
411
  const deliver = async (row, context) => {
492
412
  let release;
@@ -496,46 +416,32 @@ function createStimulusDelivery(options) {
496
416
  });
497
417
  await previous;
498
418
  let modelRunId = null;
499
- let pendingGuidanceState;
500
419
  try {
501
420
  const replaySourceEndMs = replaySourceCeiling(row);
502
421
  activeReplaySourceEndMs = replaySourceEndMs;
503
- const liveSourceDelta = row.kind === 'source_delta' && replaySourceEndMs === undefined;
504
- if (row.kind !== 'owner_message' &&
505
- row.kind !== 'source_delta' &&
506
- row.kind !== 'scheduled' &&
507
- row.kind !== 'native_event') {
508
- throw new Error('Stimulus kind is missing; no owner turn can be assembled');
422
+ const plan = planTurn(row, options);
423
+ if (plan.kind === 'skip') {
424
+ // Kagemusha's backfill guard: only history lines, so no live turn and no record order.
425
+ await options.onSkipped?.(row, plan.reason);
426
+ await options.onDelivered?.(row, null);
427
+ return;
509
428
  }
510
- const result = await context.run(assembledContent(row, [], liveSourceDelta, options), {
429
+ const text = (blocks) => [
430
+ { type: 'text', text: blocks.filter((block) => block !== '').join('\n\n') },
431
+ ];
432
+ const result = await context.run(text([plan.render([], new Date())]), {
511
433
  onModelRunStarted: (id) => {
512
434
  modelRunId = id;
513
435
  },
514
436
  prepareSessionContent: async ({ isNewSession }) => {
515
- const sessionBlocks = [];
516
- const pipeline = isNewSession ? await options.openWorkPipeline?.() : undefined;
517
- const entries = await options.guidanceResolver();
518
- const sessionKey = exports.OWNER_RUNTIME_SESSION_KEY;
519
- const lastDelivered = guidanceBySessionKey.get(sessionKey);
520
- if (isNewSession) {
521
- sessionBlocks.push(renderGuidanceIndex(entries));
522
- if (pipeline !== undefined) {
523
- sessionBlocks.push(`<open-work-pipeline>\n${JSON.stringify(pipeline)}\n</open-work-pipeline>`);
524
- }
525
- const exchanges = (0, recent_owner_exchanges_js_1.renderRecentOwnerExchanges)((await options.recentOwnerExchanges?.(row)) ?? []);
526
- if (exchanges)
527
- sessionBlocks.push(exchanges);
528
- }
529
- else if (lastDelivered === undefined) {
530
- sessionBlocks.push(renderGuidanceIndex(entries));
531
- }
532
- else {
533
- const delta = renderGuidanceDelta(entries, lastDelivered);
534
- if (delta)
535
- sessionBlocks.push(delta);
536
- }
537
- pendingGuidanceState = new Map(entries.map((entry) => [entry.id, guidanceVersion(entry)]));
538
- return assembledContent(row, sessionBlocks, liveSourceDelta, options, liveSourceDelta ? relatedWorkCandidates(row, await options.openWorkCandidates?.()) : []);
437
+ const lessons = await pickLessons(plan.lessonQuery, isNewSession);
438
+ const start = isNewSession
439
+ ? (0, turn_orders_js_1.sessionStartBlock)((await options.recentOwnerExchanges?.(row)) ?? [], new Date(), {
440
+ backend: options.backend,
441
+ timeZone: options.timeZone.get(),
442
+ })
443
+ : '';
444
+ return text([start, plan.render(lessons, new Date())]);
539
445
  },
540
446
  sessionKey: exports.OWNER_RUNTIME_SESSION_KEY,
541
447
  source: row.kind,
@@ -543,9 +449,6 @@ function createStimulusDelivery(options) {
543
449
  sourceMessageRef: row.stimulusId,
544
450
  ...(replaySourceEndMs === undefined ? {} : { replaySourceEndMs }),
545
451
  });
546
- if (pendingGuidanceState !== undefined) {
547
- guidanceBySessionKey.set(exports.OWNER_RUNTIME_SESSION_KEY, pendingGuidanceState);
548
- }
549
452
  // A commit failure withholds result provenance, but the opened run still identifies this turn.
550
453
  modelRunId = result.modelRunId ?? modelRunId;
551
454
  await deliverResult(row, result, modelRunId);
@@ -559,10 +462,21 @@ function createStimulusDelivery(options) {
559
462
  release();
560
463
  }
561
464
  };
465
+ const lost = async (row, reason) => {
466
+ if (isRecordOrderRow(row))
467
+ await options.recordOrders?.onLost(row, reason);
468
+ };
562
469
  return {
563
470
  deliver,
564
471
  prefer: ['owner_message'],
565
- ...(options.onUncertain === undefined ? {} : { onUncertain: options.onUncertain }),
472
+ onUncertain: async (row, reason) => {
473
+ await lost(row, reason);
474
+ await options.onUncertain?.(row, reason);
475
+ },
476
+ onDead: async (row, reason) => {
477
+ await lost(row, reason);
478
+ await options.onDead?.(row, reason);
479
+ },
566
480
  reconcile: async (row) => {
567
481
  const result = options.readResult?.(row);
568
482
  if (result) {
@@ -572,9 +486,11 @@ function createStimulusDelivery(options) {
572
486
  // Core invokes reconciliation only when no delivery in this process owns
573
487
  // the input. Never rerun an orphan dispatched to a model without its final result.
574
488
  if (row.nativeDelivery?.state === 'dispatching' || row.nativeDelivery?.state === 'accepted') {
575
- const reason = row.kind === 'scheduled'
576
- ? 'Scheduled report interrupted before completion; retry on the next report tick'
577
- : `${row.kind} interrupted before completion; no stored result`;
489
+ const reason = isRecordOrderRow(row)
490
+ ? 'Record order interrupted before completion; the ledger check decides the next attempt'
491
+ : row.kind === 'scheduled'
492
+ ? 'Scheduled report interrupted before completion; retry on the next report tick'
493
+ : `${row.kind} interrupted before completion; no stored result`;
578
494
  await options.onFailed?.(row, reason, null);
579
495
  // Core parks the orphan uncertain, preserving its receipt and any result.
580
496
  throw new Error(reason);