agent-conveyor 0.1.9 → 0.1.10

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/README.md CHANGED
@@ -394,6 +394,12 @@ tmux attach -t codex-live-test
394
394
  for prepared, skipped, and blocked role wakeups. This command does not send
395
395
  `send_message_to_thread`; the Codex app/operator layer sends prompts whose
396
396
  actions report `send_ready=true`.
397
+ - `app-wakeup-record-delivery TASK --role manager|worker --dispatch-receipt ID
398
+ --delivery-status sent|skipped|blocked [--thread-id ID] [--reason TEXT]
399
+ [--json]` — Record what the Codex app/operator layer did with a wake action.
400
+ `sent` is accepted only when the referenced `app-wakeup-dispatch` receipt has
401
+ a matching `ready_to_send` action with `send_ready=true` and the same thread
402
+ id; healthy and blocked roles must be recorded as `skipped` or `blocked`.
397
403
  - `discover [QUERY] [--all] [--limit N]` / `search [QUERY]` — Search tasks,
398
404
  registered sessions, active bindings, and recent telemetry in one JSON result.
399
405
  Use this for conversational setup when a manager or Codex session needs to
@@ -131,6 +131,9 @@ export function runTypescriptRuntimeCommand(options) {
131
131
  if (parsed.command === "app-wakeup-dispatch") {
132
132
  return runAppWakeupDispatchCommand(parsed, options);
133
133
  }
134
+ if (parsed.command === "app-wakeup-record-delivery") {
135
+ return runAppWakeupRecordDeliveryCommand(parsed, options);
136
+ }
134
137
  if (parsed.command === "qa-plan") {
135
138
  return runQaPlanCommand(parsed);
136
139
  }
@@ -546,9 +549,12 @@ function parseRuntimeArgs(args, env) {
546
549
  captureTranscriptLines: DEFAULT_HISTORY_LINES,
547
550
  captureTranscriptMode: "segment",
548
551
  decisionId: null,
552
+ deliveryStatus: null,
553
+ dispatchReceipt: null,
549
554
  force: false,
550
555
  message: null,
551
556
  reason: null,
557
+ threadId: null,
552
558
  consumeNext: false,
553
559
  wait: false,
554
560
  requireAcks: false,
@@ -1833,7 +1839,7 @@ function parseRuntimeArgs(args, env) {
1833
1839
  index = queue.length;
1834
1840
  }
1835
1841
  else if (arg === "--reason") {
1836
- if (command !== "finish-task" && command !== "stop-task" && command !== "record-decision" && command !== "compact-worker") {
1842
+ if (command !== "finish-task" && command !== "stop-task" && command !== "record-decision" && command !== "compact-worker" && command !== "app-wakeup-record-delivery") {
1837
1843
  return { command, enabled, error: "Unsupported TypeScript runtime option: --reason", explicit, flags, task };
1838
1844
  }
1839
1845
  const value = valueAfter(queue, index, arg);
@@ -1843,6 +1849,39 @@ function parseRuntimeArgs(args, env) {
1843
1849
  flags.reason = value.value;
1844
1850
  index += 1;
1845
1851
  }
1852
+ else if (arg === "--dispatch-receipt") {
1853
+ if (command !== "app-wakeup-record-delivery") {
1854
+ return { command, enabled, error: "Unsupported TypeScript runtime option: --dispatch-receipt", explicit, flags, task };
1855
+ }
1856
+ const value = valueAfter(queue, index, arg);
1857
+ if (value.error) {
1858
+ return { command, enabled, error: value.error, explicit, flags, task };
1859
+ }
1860
+ flags.dispatchReceipt = value.value;
1861
+ index += 1;
1862
+ }
1863
+ else if (arg === "--delivery-status") {
1864
+ if (command !== "app-wakeup-record-delivery") {
1865
+ return { command, enabled, error: "Unsupported TypeScript runtime option: --delivery-status", explicit, flags, task };
1866
+ }
1867
+ const value = valueAfter(queue, index, arg);
1868
+ if (value.error) {
1869
+ return { command, enabled, error: value.error, explicit, flags, task };
1870
+ }
1871
+ flags.deliveryStatus = value.value;
1872
+ index += 1;
1873
+ }
1874
+ else if (arg === "--thread-id") {
1875
+ if (command !== "app-wakeup-record-delivery") {
1876
+ return { command, enabled, error: "Unsupported TypeScript runtime option: --thread-id", explicit, flags, task };
1877
+ }
1878
+ const value = valueAfter(queue, index, arg);
1879
+ if (value.error) {
1880
+ return { command, enabled, error: value.error, explicit, flags, task };
1881
+ }
1882
+ flags.threadId = value.value;
1883
+ index += 1;
1884
+ }
1846
1885
  else if (arg === "--message") {
1847
1886
  if (command !== "finish-task"
1848
1887
  && command !== "stop-task"
@@ -3552,6 +3591,175 @@ function runAppWakeupDispatchCommand(parsed, options) {
3552
3591
  database.close();
3553
3592
  }
3554
3593
  }
3594
+ function runAppWakeupRecordDeliveryCommand(parsed, options) {
3595
+ const taskName = requireTask(parsed);
3596
+ if (parsed.flags.role !== "manager" && parsed.flags.role !== "worker") {
3597
+ return errorResult("app-wakeup-record-delivery requires --role manager|worker");
3598
+ }
3599
+ const role = parsed.flags.role;
3600
+ if (!parsed.flags.dispatchReceipt) {
3601
+ return errorResult("app-wakeup-record-delivery requires --dispatch-receipt");
3602
+ }
3603
+ const deliveryStatus = parseAppWakeupDeliveryStatus(parsed.flags.deliveryStatus);
3604
+ if (deliveryStatus instanceof Error) {
3605
+ return errorResult(deliveryStatus.message);
3606
+ }
3607
+ const database = openRuntimeDatabase(parsed, options);
3608
+ try {
3609
+ const task = taskRowForDiagnostics(database, taskName);
3610
+ const source = database.prepare(`
3611
+ select id, task_id, event_type, attributes_json
3612
+ from telemetry_events
3613
+ where id = ?
3614
+ limit 1
3615
+ `).get(parsed.flags.dispatchReceipt);
3616
+ if (!source) {
3617
+ throw new Error(`Unknown app wakeup dispatch receipt: ${parsed.flags.dispatchReceipt}`);
3618
+ }
3619
+ if (source.event_type !== "app_wakeup_dispatch_planned") {
3620
+ throw new Error(`Receipt ${source.id} is ${source.event_type}, expected app_wakeup_dispatch_planned.`);
3621
+ }
3622
+ if (source.task_id !== task.id) {
3623
+ throw new Error(`Receipt ${source.id} does not belong to task ${task.name}.`);
3624
+ }
3625
+ const attributes = parseAppWakeupDispatchAttributes(source.attributes_json, source.id);
3626
+ const action = attributes.actions.find((candidate) => candidate.role === role);
3627
+ if (!action) {
3628
+ throw new Error(`Receipt ${source.id} has no ${role} wake action.`);
3629
+ }
3630
+ validateAppWakeupDelivery({ action, deliveryStatus, role, threadId: parsed.flags.threadId });
3631
+ const timestamp = nowIsoSeconds(options);
3632
+ const eventId = emitTelemetrySync(database, {
3633
+ actor: "manager",
3634
+ attributes: {
3635
+ delivery_status: deliveryStatus,
3636
+ dispatch_receipt: source.id,
3637
+ dispatch_required: attributes.summary.dispatcher_required,
3638
+ reason: parsed.flags.reason,
3639
+ role,
3640
+ source_action_status: action.status,
3641
+ source_send_ready: action.send_ready === true,
3642
+ thread_id: parsed.flags.threadId ?? action.thread_id ?? null,
3643
+ thread_title: action.thread_title ?? null,
3644
+ },
3645
+ correlation: {
3646
+ command: "app-wakeup-record-delivery",
3647
+ dispatch_receipt: source.id,
3648
+ role,
3649
+ thread_id: parsed.flags.threadId ?? action.thread_id ?? null,
3650
+ },
3651
+ eventType: "app_wakeup_delivery_recorded",
3652
+ severity: deliveryStatus === "sent" ? "info" : "warning",
3653
+ summary: `App wakeup delivery ${deliveryStatus} for ${role} on ${task.name}.`,
3654
+ taskId: task.id,
3655
+ timestamp,
3656
+ });
3657
+ const output = {
3658
+ delivery: {
3659
+ reason: parsed.flags.reason,
3660
+ role,
3661
+ source_action_status: action.status,
3662
+ source_send_ready: action.send_ready === true,
3663
+ status: deliveryStatus,
3664
+ thread_id: parsed.flags.threadId ?? action.thread_id ?? null,
3665
+ },
3666
+ receipt: {
3667
+ event_id: eventId,
3668
+ event_type: "app_wakeup_delivery_recorded",
3669
+ recorded_at: timestamp,
3670
+ },
3671
+ source: {
3672
+ dispatch_receipt: source.id,
3673
+ dispatch_required: attributes.summary.dispatcher_required,
3674
+ dispatcher: attributes.dispatcher,
3675
+ },
3676
+ task: { id: task.id, name: task.name },
3677
+ };
3678
+ if (parsed.flags.json) {
3679
+ return jsonResult(output);
3680
+ }
3681
+ return {
3682
+ exitCode: 0,
3683
+ handled: true,
3684
+ stdout: [
3685
+ `App wakeup delivery ${deliveryStatus} recorded for ${role} on ${task.name}.`,
3686
+ `Source receipt: ${source.id}`,
3687
+ `Receipt: ${eventId}`,
3688
+ attributes.summary.dispatcher_required ? "Dispatch still required by source receipt." : "Dispatch healthy in source receipt.",
3689
+ ].join("\n") + "\n",
3690
+ };
3691
+ }
3692
+ finally {
3693
+ database.close();
3694
+ }
3695
+ }
3696
+ function parseAppWakeupDeliveryStatus(value) {
3697
+ if (value === "sent" || value === "skipped" || value === "blocked") {
3698
+ return value;
3699
+ }
3700
+ return new Error("app-wakeup-record-delivery requires --delivery-status sent|skipped|blocked");
3701
+ }
3702
+ function parseAppWakeupDispatchAttributes(value, receiptId) {
3703
+ let parsed;
3704
+ try {
3705
+ parsed = JSON.parse(value);
3706
+ }
3707
+ catch {
3708
+ throw new Error(`Receipt ${receiptId} has invalid attributes JSON.`);
3709
+ }
3710
+ if (!parsed || typeof parsed !== "object") {
3711
+ throw new Error(`Receipt ${receiptId} has invalid attributes shape.`);
3712
+ }
3713
+ const attributes = parsed;
3714
+ if (!Array.isArray(attributes.actions)) {
3715
+ throw new Error(`Receipt ${receiptId} is missing actions.`);
3716
+ }
3717
+ const summary = attributes.summary;
3718
+ if (!summary || typeof summary !== "object" || typeof summary.dispatcher_required !== "boolean") {
3719
+ throw new Error(`Receipt ${receiptId} is missing dispatcher_required summary.`);
3720
+ }
3721
+ const dispatcher = attributes.dispatcher && typeof attributes.dispatcher === "object"
3722
+ ? attributes.dispatcher
3723
+ : {};
3724
+ return {
3725
+ actions: attributes.actions,
3726
+ dispatcher,
3727
+ summary: { dispatcher_required: Boolean(summary.dispatcher_required) },
3728
+ };
3729
+ }
3730
+ function validateAppWakeupDelivery(options) {
3731
+ const sourceStatus = parseAppWakeupSourceActionStatus(options.action.status);
3732
+ if (sourceStatus instanceof Error) {
3733
+ throw sourceStatus;
3734
+ }
3735
+ if (options.deliveryStatus === "sent") {
3736
+ if (sourceStatus !== "ready_to_send" || options.action.send_ready !== true) {
3737
+ throw new Error(`Cannot record sent wakeup for ${options.role}; source action is ${sourceStatus}.`);
3738
+ }
3739
+ if (!options.threadId) {
3740
+ throw new Error("app-wakeup-record-delivery --delivery-status sent requires --thread-id.");
3741
+ }
3742
+ if (options.action.thread_id !== options.threadId) {
3743
+ throw new Error(`Thread id mismatch for ${options.role}; source action targets ${options.action.thread_id ?? "(none)"}.`);
3744
+ }
3745
+ return;
3746
+ }
3747
+ if (options.deliveryStatus === "skipped") {
3748
+ if (sourceStatus !== "skipped_healthy") {
3749
+ throw new Error(`Cannot record skipped wakeup for ${options.role}; source action is ${sourceStatus}.`);
3750
+ }
3751
+ return;
3752
+ }
3753
+ if (sourceStatus !== "blocked_missing_thread") {
3754
+ throw new Error(`Cannot record blocked wakeup for ${options.role}; source action is ${sourceStatus}.`);
3755
+ }
3756
+ }
3757
+ function parseAppWakeupSourceActionStatus(value) {
3758
+ if (value === "ready_to_send" || value === "skipped_healthy" || value === "blocked_missing_thread") {
3759
+ return value;
3760
+ }
3761
+ return new Error(`Unsupported app wakeup source action status: ${value ?? "(missing)"}.`);
3762
+ }
3555
3763
  function renderAppLoopStatusText(status) {
3556
3764
  const lines = [
3557
3765
  `App loop ${status.task.name}: ${status.ok ? "ok" : "attention required"}`,
@@ -13890,6 +14098,7 @@ function isDefaultRuntimeCommand(command) {
13890
14098
  || command === "app-loop-status"
13891
14099
  || command === "app-wakeup-plan"
13892
14100
  || command === "app-wakeup-dispatch"
14101
+ || command === "app-wakeup-record-delivery"
13893
14102
  || command === "loop-templates"
13894
14103
  || command === "loop-triggers"
13895
14104
  || command === "ralph-loop-presets"