@jungjaehoon/mama-os 0.57.0 → 0.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/dist/api/attachment-actions.d.ts.map +1 -1
- package/dist/api/attachment-actions.js +5 -3
- package/dist/api/attachment-actions.js.map +1 -1
- package/dist/api/help-actions.d.ts +12 -0
- package/dist/api/help-actions.d.ts.map +1 -0
- package/dist/api/help-actions.js +70 -0
- package/dist/api/help-actions.js.map +1 -0
- package/dist/api/owner-timezone-actions.d.ts.map +1 -1
- package/dist/api/owner-timezone-actions.js +1 -1
- package/dist/api/owner-timezone-actions.js.map +1 -1
- package/dist/api/record-actions.d.ts +8 -0
- package/dist/api/record-actions.d.ts.map +1 -0
- package/dist/api/record-actions.js +53 -0
- package/dist/api/record-actions.js.map +1 -0
- package/dist/api/report-actions.d.ts +0 -2
- package/dist/api/report-actions.d.ts.map +1 -1
- package/dist/api/report-actions.js +5 -5
- package/dist/api/report-actions.js.map +1 -1
- package/dist/api/report-source-actions.js +2 -2
- package/dist/api/report-source-actions.js.map +1 -1
- package/dist/api/viewer-server.d.ts +1 -0
- package/dist/api/viewer-server.d.ts.map +1 -1
- package/dist/api/viewer-server.js +21 -17
- package/dist/api/viewer-server.js.map +1 -1
- package/dist/api/work-actions.d.ts +0 -13
- package/dist/api/work-actions.d.ts.map +1 -1
- package/dist/api/work-actions.js +6 -37
- package/dist/api/work-actions.js.map +1 -1
- package/dist/cli/commands/daemon.d.ts.map +1 -1
- package/dist/cli/commands/daemon.js +14 -2
- package/dist/cli/commands/daemon.js.map +1 -1
- package/dist/connectors/framework/polling-scheduler.d.ts.map +1 -1
- package/dist/connectors/framework/polling-scheduler.js +9 -0
- package/dist/connectors/framework/polling-scheduler.js.map +1 -1
- package/dist/gateways/telegram-format.js +2 -2
- package/dist/gateways/telegram-format.js.map +1 -1
- package/dist/operator/board-read-views.d.ts.map +1 -1
- package/dist/operator/board-read-views.js +3 -3
- package/dist/operator/board-read-views.js.map +1 -1
- package/dist/operator/board-slot-instructions.d.ts +4 -6
- package/dist/operator/board-slot-instructions.d.ts.map +1 -1
- package/dist/operator/board-slot-instructions.js +11 -24
- package/dist/operator/board-slot-instructions.js.map +1 -1
- package/dist/runtime/action-mcp-server.d.ts.map +1 -1
- package/dist/runtime/action-mcp-server.js +4 -5
- package/dist/runtime/action-mcp-server.js.map +1 -1
- package/dist/runtime/action-surface.d.ts +1 -1
- package/dist/runtime/action-surface.d.ts.map +1 -1
- package/dist/runtime/action-surface.js +13 -3
- package/dist/runtime/action-surface.js.map +1 -1
- package/dist/runtime/native-session.d.ts +6 -0
- package/dist/runtime/native-session.d.ts.map +1 -1
- package/dist/runtime/native-session.js +13 -15
- package/dist/runtime/native-session.js.map +1 -1
- package/dist/runtime/owner-runtime.d.ts +14 -3
- package/dist/runtime/owner-runtime.d.ts.map +1 -1
- package/dist/runtime/owner-runtime.js +77 -13
- package/dist/runtime/owner-runtime.js.map +1 -1
- package/dist/runtime/owner-system-prompt.d.ts +7 -8
- package/dist/runtime/owner-system-prompt.d.ts.map +1 -1
- package/dist/runtime/owner-system-prompt.js +64 -68
- package/dist/runtime/owner-system-prompt.js.map +1 -1
- package/dist/runtime/recent-owner-exchanges.d.ts +0 -2
- package/dist/runtime/recent-owner-exchanges.d.ts.map +1 -1
- package/dist/runtime/recent-owner-exchanges.js +0 -26
- package/dist/runtime/recent-owner-exchanges.js.map +1 -1
- package/dist/runtime/record-orders.d.ts +49 -0
- package/dist/runtime/record-orders.d.ts.map +1 -0
- package/dist/runtime/record-orders.js +152 -0
- package/dist/runtime/record-orders.js.map +1 -0
- package/dist/runtime/report-scheduler.d.ts.map +1 -1
- package/dist/runtime/report-scheduler.js +14 -11
- package/dist/runtime/report-scheduler.js.map +1 -1
- package/dist/runtime/stimulus-delivery.d.ts +25 -27
- package/dist/runtime/stimulus-delivery.d.ts.map +1 -1
- package/dist/runtime/stimulus-delivery.js +185 -268
- package/dist/runtime/stimulus-delivery.js.map +1 -1
- package/dist/runtime/timezone.d.ts +2 -0
- package/dist/runtime/timezone.d.ts.map +1 -1
- package/dist/runtime/timezone.js +9 -0
- package/dist/runtime/timezone.js.map +1 -1
- package/dist/runtime/turn-orders.d.ts +86 -0
- package/dist/runtime/turn-orders.d.ts.map +1 -0
- package/dist/runtime/turn-orders.js +272 -0
- package/dist/runtime/turn-orders.js.map +1 -0
- package/package.json +2 -2
- package/dist/runtime/report-prompts.d.ts +0 -14
- package/dist/runtime/report-prompts.d.ts.map +0 -1
- package/dist/runtime/report-prompts.js +0 -69
- 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
|
|
15
|
-
const
|
|
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,67 @@ function renderWindowQueue(queue) {
|
|
|
237
213
|
}
|
|
238
214
|
return (0, untrusted_content_js_1.wrapUntrustedContent)('source_delta', lines.join('\n'));
|
|
239
215
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
+
const action = (name) => (0, owner_system_prompt_js_1.actionName)(backend, name);
|
|
222
|
+
return [
|
|
223
|
+
'replay_instructions:',
|
|
224
|
+
`- 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 ${action('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.`,
|
|
225
|
+
`- ${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.`,
|
|
226
|
+
`- For a moved item, revise or create the work item (${action('work.revise')}, ${action('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.`,
|
|
227
|
+
wikiEnabled
|
|
228
|
+
? `- With end_of_window_instructions, finish the day's work changes before updating each affected board section with ${action('report.publish')} and topic wiki page with ${action('manage.wiki.update')}.`
|
|
229
|
+
: `- With end_of_window_instructions, finish the day's work changes before updating each affected board section with ${action('report.publish')}.`,
|
|
230
|
+
'- A replay window is history and is not delivered to the owner: it ends without a marker.',
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
/** The replay window text: the window material and its instructions. */
|
|
234
|
+
function replayWindowText(row, options) {
|
|
247
235
|
const lines = [
|
|
248
|
-
'##
|
|
236
|
+
'## Replay window',
|
|
249
237
|
`owner timezone: ${options.timeZone.get()}`,
|
|
250
|
-
`kind: ${row.kind ?? 'unknown'}`,
|
|
251
238
|
`stimulus_id: ${row.stimulusId}`,
|
|
252
239
|
`channel: ${row.channelKey}`,
|
|
253
240
|
`occurred_at: ${new Date(row.occurredAt).toISOString()}`,
|
|
254
|
-
|
|
241
|
+
'delivery: replay window, not delivered to the owner',
|
|
242
|
+
...replayInstructions(options.backend, options.wikiEnabled ?? false),
|
|
255
243
|
];
|
|
256
|
-
|
|
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;
|
|
244
|
+
const messages = messageLines(row.payload, options.timeZone);
|
|
277
245
|
if (messages === null)
|
|
278
246
|
lines.push(`refs: ${JSON.stringify(row.refs)}`);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
247
|
+
lines.push(row.refs.length === 0
|
|
248
|
+
? 'source_read: this replay window has no source messages.'
|
|
249
|
+
: messages !== null
|
|
250
|
+
? '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.'
|
|
251
|
+
: 'source_read: read these refs with the source read action, in one batched call with observationRefs; content remains bounded per ref.');
|
|
252
|
+
const payload = row.payload;
|
|
253
|
+
const replay = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload.replay : undefined;
|
|
254
|
+
if (replay && typeof replay === 'object' && !Array.isArray(replay)) {
|
|
255
|
+
const instructions = replay.endInstructions;
|
|
256
|
+
if (typeof instructions === 'string' && instructions.trim() !== '') {
|
|
257
|
+
lines.push(`window_end_instructions: ${instructions}`);
|
|
285
258
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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');
|
|
259
|
+
if (replay.queue !== undefined) {
|
|
260
|
+
lines.push('window_queue:', renderWindowQueue(replay.queue));
|
|
261
|
+
if (messages !== null) {
|
|
262
|
+
const work = ledgerLines(replay);
|
|
263
|
+
lines.push(`current_work (commitmentId | revision | title | stage | status | assignee | lastEventTime), ${String(work.length)} items:`, ...work);
|
|
307
264
|
}
|
|
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
265
|
return lines.join('\n');
|
|
313
266
|
}
|
|
314
267
|
}
|
|
268
|
+
if (messages !== null) {
|
|
269
|
+
const work = ledgerLines(replay);
|
|
270
|
+
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)));
|
|
271
|
+
return lines.join('\n');
|
|
272
|
+
}
|
|
315
273
|
if (row.payload !== undefined)
|
|
316
|
-
lines.push(`payload: ${
|
|
274
|
+
lines.push(`payload: ${(0, untrusted_content_js_1.wrapUntrustedContent)('source_delta', JSON.stringify(row.payload))}`);
|
|
317
275
|
return lines.join('\n');
|
|
318
276
|
}
|
|
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
277
|
/** One bounded log line; preserve the original thrown error for runtime settlement. */
|
|
451
278
|
function stimulusFailureReason(error) {
|
|
452
279
|
return (error instanceof Error ? error.message : String(error))
|
|
@@ -472,21 +299,115 @@ function replaySourceCeiling(row) {
|
|
|
472
299
|
}
|
|
473
300
|
return Number(replay.windowEndMs) - 1;
|
|
474
301
|
}
|
|
302
|
+
function isRecordOrderRow(row) {
|
|
303
|
+
return row.kind === 'scheduled' && row.channelKey === turn_orders_js_1.RECORD_ORDER_CHANNEL;
|
|
304
|
+
}
|
|
305
|
+
function ownerText(row) {
|
|
306
|
+
const payload = row.payload;
|
|
307
|
+
return payload &&
|
|
308
|
+
typeof payload === 'object' &&
|
|
309
|
+
!Array.isArray(payload) &&
|
|
310
|
+
typeof payload.text === 'string'
|
|
311
|
+
? payload.text
|
|
312
|
+
: '';
|
|
313
|
+
}
|
|
314
|
+
/** What a row's turn says, by kind; a live delta with only history lines has no turn. */
|
|
315
|
+
function planTurn(row, options) {
|
|
316
|
+
const zone = options.timeZone.get();
|
|
317
|
+
if (row.kind === 'owner_message') {
|
|
318
|
+
return {
|
|
319
|
+
kind: 'turn',
|
|
320
|
+
lessonQuery: ownerText(row),
|
|
321
|
+
render: (lessons) => (0, turn_orders_js_1.ownerMessageOrder)({
|
|
322
|
+
messenger: row.stimulusId.split(':', 1)[0],
|
|
323
|
+
occurredAt: row.occurredAt,
|
|
324
|
+
payload: row.payload,
|
|
325
|
+
}, lessons, { timeZone: zone }),
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
if (row.kind === 'source_delta') {
|
|
329
|
+
if (replaySourceCeiling(row) !== undefined)
|
|
330
|
+
return { kind: 'turn', lessonQuery: null, render: () => replayWindowText(row, options) };
|
|
331
|
+
// Kagemusha filters at collection: the reference is when the row was accepted, so a delta
|
|
332
|
+
// that waited behind a backlog is still delivered.
|
|
333
|
+
const all = (0, turn_orders_js_1.deltaLines)(row.payload);
|
|
334
|
+
const lines = (0, turn_orders_js_1.liveDeltaLines)(all, row.createdAt);
|
|
335
|
+
if (lines.length === 0)
|
|
336
|
+
return {
|
|
337
|
+
kind: 'skip',
|
|
338
|
+
reason: `all ${all.length} lines were older than the six-hour backfill guard when accepted`,
|
|
339
|
+
};
|
|
340
|
+
return {
|
|
341
|
+
kind: 'turn',
|
|
342
|
+
lessonQuery: lines.map((line) => line.text).join(' '),
|
|
343
|
+
render: (lessons, now) => (0, turn_orders_js_1.deltaNotifyOrder)(lines, row.channelKey, now, lessons, { timeZone: zone }),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
if (row.kind === 'scheduled') {
|
|
347
|
+
if (row.channelKey === turn_orders_js_1.RECORD_ORDER_CHANNEL) {
|
|
348
|
+
const record = (0, turn_orders_js_1.parseRecordOrder)(row.payload);
|
|
349
|
+
return {
|
|
350
|
+
kind: 'turn',
|
|
351
|
+
lessonQuery: null,
|
|
352
|
+
render: (_lessons, now) => (0, turn_orders_js_1.deltaRecordOrder)(record, now, {
|
|
353
|
+
backend: options.backend,
|
|
354
|
+
timeZone: zone,
|
|
355
|
+
wikiEnabled: options.wikiEnabled ?? false,
|
|
356
|
+
}),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
if (row.channelKey === turn_orders_js_1.REPORT_CHANNEL)
|
|
360
|
+
return {
|
|
361
|
+
kind: 'turn',
|
|
362
|
+
lessonQuery: null,
|
|
363
|
+
render: () => (0, turn_orders_js_1.scheduledReportOrder)(row.payload, new Date(row.occurredAt), {
|
|
364
|
+
backend: options.backend,
|
|
365
|
+
timeZone: zone,
|
|
366
|
+
messenger: options.formattingRoutes?.reports ?? 'telegram',
|
|
367
|
+
}),
|
|
368
|
+
};
|
|
369
|
+
throw new Error(`Scheduled input on unknown channel ${row.channelKey}`);
|
|
370
|
+
}
|
|
371
|
+
throw new Error(`No owner turn is assembled for stimulus kind ${row.kind ?? 'unknown'}`);
|
|
372
|
+
}
|
|
475
373
|
/** Deliver every model-bearing kind through one serialized owner session. */
|
|
476
374
|
function createStimulusDelivery(options) {
|
|
477
375
|
let serialTail = Promise.resolve();
|
|
478
376
|
let activeReplaySourceEndMs;
|
|
479
|
-
|
|
377
|
+
// Lessons shown in this session, cleared on a new session or a new local day: compactions are
|
|
378
|
+
// not observable, so a day bounds how long a lesson stays out of view.
|
|
379
|
+
let shownLessons = new Set();
|
|
380
|
+
let shownDay = '';
|
|
381
|
+
const pickLessons = async (query, newSession) => {
|
|
382
|
+
const day = (0, timezone_js_1.localDateKey)(Date.now(), options.timeZone.get());
|
|
383
|
+
if (newSession || day !== shownDay) {
|
|
384
|
+
shownLessons = new Set();
|
|
385
|
+
shownDay = day;
|
|
386
|
+
}
|
|
387
|
+
if (query === null || query.trim() === '' || !options.lessons)
|
|
388
|
+
return [];
|
|
389
|
+
const fresh = (await options.lessons(query))
|
|
390
|
+
.filter((lesson) => !shownLessons.has(lesson.id))
|
|
391
|
+
.slice(0, 3);
|
|
392
|
+
for (const lesson of fresh)
|
|
393
|
+
shownLessons.add(lesson.id);
|
|
394
|
+
return fresh;
|
|
395
|
+
};
|
|
480
396
|
const deliverResult = async (row, result, modelRunId = result.modelRunId) => {
|
|
481
397
|
if (row.kind === 'owner_message')
|
|
482
398
|
await options.onOwnerResult?.(row, result);
|
|
483
|
-
if (row.kind === 'source_delta' && replaySourceCeiling(row) === undefined)
|
|
399
|
+
if (row.kind === 'source_delta' && replaySourceCeiling(row) === undefined) {
|
|
400
|
+
// The record order is durable before the notify reply goes anywhere.
|
|
401
|
+
options.recordOrders?.enqueueFirst(row);
|
|
484
402
|
await options.onSourceResult?.(row, result);
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
403
|
+
}
|
|
404
|
+
if (row.kind === 'scheduled') {
|
|
405
|
+
if (isRecordOrderRow(row))
|
|
406
|
+
await options.recordOrders?.onResult(row, modelRunId ?? null);
|
|
407
|
+
else
|
|
408
|
+
await options.onScheduledResult?.(row, result);
|
|
409
|
+
}
|
|
410
|
+
await options.onDelivered?.(row, modelRunId ?? null);
|
|
490
411
|
};
|
|
491
412
|
const deliver = async (row, context) => {
|
|
492
413
|
let release;
|
|
@@ -496,46 +417,32 @@ function createStimulusDelivery(options) {
|
|
|
496
417
|
});
|
|
497
418
|
await previous;
|
|
498
419
|
let modelRunId = null;
|
|
499
|
-
let pendingGuidanceState;
|
|
500
420
|
try {
|
|
501
421
|
const replaySourceEndMs = replaySourceCeiling(row);
|
|
502
422
|
activeReplaySourceEndMs = replaySourceEndMs;
|
|
503
|
-
const
|
|
504
|
-
if (
|
|
505
|
-
|
|
506
|
-
row.
|
|
507
|
-
row
|
|
508
|
-
|
|
423
|
+
const plan = planTurn(row, options);
|
|
424
|
+
if (plan.kind === 'skip') {
|
|
425
|
+
// Kagemusha's backfill guard: only history lines, so no live turn and no record order.
|
|
426
|
+
await options.onSkipped?.(row, plan.reason);
|
|
427
|
+
await options.onDelivered?.(row, null);
|
|
428
|
+
return;
|
|
509
429
|
}
|
|
510
|
-
const
|
|
430
|
+
const text = (blocks) => [
|
|
431
|
+
{ type: 'text', text: blocks.filter((block) => block !== '').join('\n\n') },
|
|
432
|
+
];
|
|
433
|
+
const result = await context.run(text([plan.render([], new Date())]), {
|
|
511
434
|
onModelRunStarted: (id) => {
|
|
512
435
|
modelRunId = id;
|
|
513
436
|
},
|
|
514
437
|
prepareSessionContent: async ({ isNewSession }) => {
|
|
515
|
-
const
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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?.()) : []);
|
|
438
|
+
const lessons = await pickLessons(plan.lessonQuery, isNewSession);
|
|
439
|
+
const start = isNewSession
|
|
440
|
+
? (0, turn_orders_js_1.sessionStartBlock)((await options.recentOwnerExchanges?.(row)) ?? [], new Date(), {
|
|
441
|
+
backend: options.backend,
|
|
442
|
+
timeZone: options.timeZone.get(),
|
|
443
|
+
})
|
|
444
|
+
: '';
|
|
445
|
+
return text([start, plan.render(lessons, new Date())]);
|
|
539
446
|
},
|
|
540
447
|
sessionKey: exports.OWNER_RUNTIME_SESSION_KEY,
|
|
541
448
|
source: row.kind,
|
|
@@ -543,9 +450,6 @@ function createStimulusDelivery(options) {
|
|
|
543
450
|
sourceMessageRef: row.stimulusId,
|
|
544
451
|
...(replaySourceEndMs === undefined ? {} : { replaySourceEndMs }),
|
|
545
452
|
});
|
|
546
|
-
if (pendingGuidanceState !== undefined) {
|
|
547
|
-
guidanceBySessionKey.set(exports.OWNER_RUNTIME_SESSION_KEY, pendingGuidanceState);
|
|
548
|
-
}
|
|
549
453
|
// A commit failure withholds result provenance, but the opened run still identifies this turn.
|
|
550
454
|
modelRunId = result.modelRunId ?? modelRunId;
|
|
551
455
|
await deliverResult(row, result, modelRunId);
|
|
@@ -559,10 +463,21 @@ function createStimulusDelivery(options) {
|
|
|
559
463
|
release();
|
|
560
464
|
}
|
|
561
465
|
};
|
|
466
|
+
const lost = async (row, reason) => {
|
|
467
|
+
if (isRecordOrderRow(row))
|
|
468
|
+
await options.recordOrders?.onLost(row, reason);
|
|
469
|
+
};
|
|
562
470
|
return {
|
|
563
471
|
deliver,
|
|
564
472
|
prefer: ['owner_message'],
|
|
565
|
-
|
|
473
|
+
onUncertain: async (row, reason) => {
|
|
474
|
+
await lost(row, reason);
|
|
475
|
+
await options.onUncertain?.(row, reason);
|
|
476
|
+
},
|
|
477
|
+
onDead: async (row, reason) => {
|
|
478
|
+
await lost(row, reason);
|
|
479
|
+
await options.onDead?.(row, reason);
|
|
480
|
+
},
|
|
566
481
|
reconcile: async (row) => {
|
|
567
482
|
const result = options.readResult?.(row);
|
|
568
483
|
if (result) {
|
|
@@ -572,9 +487,11 @@ function createStimulusDelivery(options) {
|
|
|
572
487
|
// Core invokes reconciliation only when no delivery in this process owns
|
|
573
488
|
// the input. Never rerun an orphan dispatched to a model without its final result.
|
|
574
489
|
if (row.nativeDelivery?.state === 'dispatching' || row.nativeDelivery?.state === 'accepted') {
|
|
575
|
-
const reason = row
|
|
576
|
-
? '
|
|
577
|
-
:
|
|
490
|
+
const reason = isRecordOrderRow(row)
|
|
491
|
+
? 'Record order interrupted before completion; the ledger check decides the next attempt'
|
|
492
|
+
: row.kind === 'scheduled'
|
|
493
|
+
? 'Scheduled report interrupted before completion; retry on the next report tick'
|
|
494
|
+
: `${row.kind} interrupted before completion; no stored result`;
|
|
578
495
|
await options.onFailed?.(row, reason, null);
|
|
579
496
|
// Core parks the orphan uncertain, preserving its receipt and any result.
|
|
580
497
|
throw new Error(reason);
|