agent-conveyor 0.1.10 → 0.1.12
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 +22 -7
- package/dist/cli/typescript-runtime.js +161 -6
- package/dist/cli/typescript-runtime.js.map +1 -1
- package/dist/runtime/app-autonomy.d.ts +53 -0
- package/dist/runtime/app-autonomy.js +93 -0
- package/dist/runtime/app-autonomy.js.map +1 -1
- package/docs/manager-recipes.md +10 -0
- package/package.json +1 -1
- package/skills/manage-codex-workers/SKILL.md +20 -4
package/README.md
CHANGED
|
@@ -372,9 +372,13 @@ tmux attach -t codex-live-test
|
|
|
372
372
|
`heartbeat_recommendations` with role-specific poll prompts; Dispatch can
|
|
373
373
|
deliver into those inboxes, but a heartbeat or operator wake-up is still
|
|
374
374
|
required to make an idle app thread poll autonomously. Those recommendations
|
|
375
|
-
include
|
|
376
|
-
|
|
377
|
-
|
|
375
|
+
also include `wakeup_dispatch_command` and `delivery_receipt_commands` for
|
|
376
|
+
app-thread wake recovery. Use them to record sent, skipped, and blocked wake
|
|
377
|
+
outcomes after `app-wakeup-dispatch`; an app-thread send is not task
|
|
378
|
+
completion. The recommendations include a `teardown_policy`: an idle poll is
|
|
379
|
+
only a quiet interval, not a reason to delete or pause heartbeat automation;
|
|
380
|
+
heartbeat teardown belongs to the manager/operator after terminal closeout or
|
|
381
|
+
explicit operator instruction.
|
|
378
382
|
The optional
|
|
379
383
|
Codex app thread metadata is normally supplied after a Codex app manager has
|
|
380
384
|
used `create_thread` and `set_thread_title`; terminal-only users can omit it
|
|
@@ -400,6 +404,15 @@ tmux attach -t codex-live-test
|
|
|
400
404
|
`sent` is accepted only when the referenced `app-wakeup-dispatch` receipt has
|
|
401
405
|
a matching `ready_to_send` action with `send_ready=true` and the same thread
|
|
402
406
|
id; healthy and blocked roles must be recorded as `skipped` or `blocked`.
|
|
407
|
+
- `app-autopilot start|stop|status TASK [--dispatcher-id ID]
|
|
408
|
+
[--interval SECONDS] [--watch-iterations N] [--stale-after N] [--json]` —
|
|
409
|
+
Manage the pair-level app-native heartbeat policy for the active
|
|
410
|
+
manager/worker binding. `start` and `stop` write telemetry receipts and emit
|
|
411
|
+
the exact manager/worker Codex app heartbeat automation specs plus the
|
|
412
|
+
bounded Dispatch watch command. A plain shell CLI cannot call Codex app
|
|
413
|
+
thread tools, so create/pause those heartbeat automations from a Codex app
|
|
414
|
+
operator session using the emitted specs; Conveyor remains the durable source
|
|
415
|
+
of truth through Dispatch, inboxes, wake receipts, and app heartbeat status.
|
|
403
416
|
- `discover [QUERY] [--all] [--limit N]` / `search [QUERY]` — Search tasks,
|
|
404
417
|
registered sessions, active bindings, and recent telemetry in one JSON result.
|
|
405
418
|
Use this for conversational setup when a manager or Codex session needs to
|
|
@@ -1045,10 +1058,12 @@ Current dispatch state:
|
|
|
1045
1058
|
reaches `max_iterations`. For no-tmux Codex app sessions, treat
|
|
1046
1059
|
`communication.requires_polling=true` as requiring a heartbeat/wake layer:
|
|
1047
1060
|
a delivered pull inbox item does not by itself wake an idle app thread. Do
|
|
1048
|
-
not delete or pause heartbeats because an inbox poll is idle.
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1061
|
+
not delete or pause heartbeats because an inbox poll is idle. Use generated
|
|
1062
|
+
`heartbeat_recommendations.wakeup_dispatch_command` and
|
|
1063
|
+
`heartbeat_recommendations.delivery_receipt_commands` for stale-thread wake
|
|
1064
|
+
recovery receipts. A terminal manager decision should be followed by
|
|
1065
|
+
`finish-task --require-criteria-audit` or by an explicit blocker explaining
|
|
1066
|
+
why the task/binding still appears active.
|
|
1052
1067
|
- `register-worker`, `register-manager`, `sessions`, `discover`, and
|
|
1053
1068
|
`create-disposable-binding --json` expose a `communication` block per
|
|
1054
1069
|
session. Treat `session_kind='tmux'` plus `receive_style='push'` as direct
|
|
@@ -5,7 +5,7 @@ import { homedir, tmpdir } from "node:os";
|
|
|
5
5
|
import { dirname, join, relative, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { taskAuditSync } from "../runtime/audit.js";
|
|
8
|
-
import { appLoopStatusSync, appWakeupDispatchPlanSync, appWakeupPlanSync, directInboxPollCommand, } from "../runtime/app-autonomy.js";
|
|
8
|
+
import { appAutopilotPlanSync, appLoopStatusSync, appWakeupDispatchPlanSync, appWakeupPlanSync, directInboxPollCommand, } from "../runtime/app-autonomy.js";
|
|
9
9
|
import { classifyBusyWait, classifyStartupOutput } from "../runtime/classify.js";
|
|
10
10
|
import { exportTaskSync } from "../runtime/export.js";
|
|
11
11
|
import { ingestSessionSync } from "../runtime/ingest.js";
|
|
@@ -134,6 +134,9 @@ export function runTypescriptRuntimeCommand(options) {
|
|
|
134
134
|
if (parsed.command === "app-wakeup-record-delivery") {
|
|
135
135
|
return runAppWakeupRecordDeliveryCommand(parsed, options);
|
|
136
136
|
}
|
|
137
|
+
if (parsed.command === "app-autopilot") {
|
|
138
|
+
return runAppAutopilotCommand(parsed, options);
|
|
139
|
+
}
|
|
137
140
|
if (parsed.command === "qa-plan") {
|
|
138
141
|
return runQaPlanCommand(parsed);
|
|
139
142
|
}
|
|
@@ -385,6 +388,17 @@ function commandHelpText(program, command) {
|
|
|
385
388
|
` ${program} enqueue-nudge-worker my-task --message "Status and evidence?" --path /tmp/work/workerctl.db`,
|
|
386
389
|
` ${program} dispatch --once --type nudge_worker --path /tmp/work/workerctl.db`,
|
|
387
390
|
],
|
|
391
|
+
"app-autopilot": [
|
|
392
|
+
`usage: ${program} app-autopilot start|stop|status <task> [--dispatcher-id ID] [--interval SECONDS] [--watch-iterations N] [--stale-after N] ${path} [--json]`,
|
|
393
|
+
"",
|
|
394
|
+
"Manage the app-native heartbeat policy for a bound manager/worker pair.",
|
|
395
|
+
"The CLI records policy receipts and emits Codex app heartbeat automation specs; app-thread automation creation still happens through Codex app tools.",
|
|
396
|
+
"",
|
|
397
|
+
"Examples:",
|
|
398
|
+
` ${program} app-autopilot start dogfood --dispatcher-id dispatch-local --path /tmp/work/workerctl.db --json`,
|
|
399
|
+
` ${program} app-autopilot status dogfood --path /tmp/work/workerctl.db`,
|
|
400
|
+
` ${program} app-autopilot stop dogfood --path /tmp/work/workerctl.db --json`,
|
|
401
|
+
],
|
|
388
402
|
pair: [
|
|
389
403
|
`usage: ${program} pair --task <task> --worker-name <worker> --manager-name <manager> [options] ${path}`,
|
|
390
404
|
"",
|
|
@@ -1496,7 +1510,8 @@ function parseRuntimeArgs(args, env) {
|
|
|
1496
1510
|
&& command !== "app-heartbeat"
|
|
1497
1511
|
&& command !== "app-loop-status"
|
|
1498
1512
|
&& command !== "app-wakeup-plan"
|
|
1499
|
-
&& command !== "app-wakeup-dispatch"
|
|
1513
|
+
&& command !== "app-wakeup-dispatch"
|
|
1514
|
+
&& command !== "app-autopilot") {
|
|
1500
1515
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --dispatcher-id", explicit, flags, task };
|
|
1501
1516
|
}
|
|
1502
1517
|
const value = valueAfter(queue, index, arg);
|
|
@@ -2463,7 +2478,11 @@ function parseRuntimeArgs(args, env) {
|
|
|
2463
2478
|
index += 1;
|
|
2464
2479
|
}
|
|
2465
2480
|
else if (arg === "--stale-after") {
|
|
2466
|
-
if (command !== "app-heartbeat"
|
|
2481
|
+
if (command !== "app-heartbeat"
|
|
2482
|
+
&& command !== "app-loop-status"
|
|
2483
|
+
&& command !== "app-wakeup-plan"
|
|
2484
|
+
&& command !== "app-wakeup-dispatch"
|
|
2485
|
+
&& command !== "app-autopilot") {
|
|
2467
2486
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --stale-after", explicit, flags, task };
|
|
2468
2487
|
}
|
|
2469
2488
|
const parsedValue = valueAfter(queue, index, arg);
|
|
@@ -2676,7 +2695,7 @@ function parseRuntimeArgs(args, env) {
|
|
|
2676
2695
|
index += 1;
|
|
2677
2696
|
}
|
|
2678
2697
|
else if (arg === "--interval") {
|
|
2679
|
-
if (command !== "dispatch" && command !== "session-inbox" && command !== "manager-inbox" && command !== "worker-inbox") {
|
|
2698
|
+
if (command !== "dispatch" && command !== "session-inbox" && command !== "manager-inbox" && command !== "worker-inbox" && command !== "app-autopilot") {
|
|
2680
2699
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --interval", explicit, flags, task };
|
|
2681
2700
|
}
|
|
2682
2701
|
const parsedValue = valueAfter(queue, index, arg);
|
|
@@ -2691,7 +2710,7 @@ function parseRuntimeArgs(args, env) {
|
|
|
2691
2710
|
index += 1;
|
|
2692
2711
|
}
|
|
2693
2712
|
else if (arg === "--watch-iterations") {
|
|
2694
|
-
if (command !== "dispatch") {
|
|
2713
|
+
if (command !== "dispatch" && command !== "app-autopilot") {
|
|
2695
2714
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --watch-iterations", explicit, flags, task };
|
|
2696
2715
|
}
|
|
2697
2716
|
const parsedValue = valueAfter(queue, index, arg);
|
|
@@ -2824,6 +2843,12 @@ function parseRuntimeArgs(args, env) {
|
|
|
2824
2843
|
}
|
|
2825
2844
|
flags.subtype = arg;
|
|
2826
2845
|
}
|
|
2846
|
+
else if (command === "app-autopilot" && flags.action === null) {
|
|
2847
|
+
if (!["start", "stop", "status"].includes(arg)) {
|
|
2848
|
+
return { command, enabled, error: `Unsupported app-autopilot action: ${arg}`, explicit, flags, task };
|
|
2849
|
+
}
|
|
2850
|
+
flags.action = arg;
|
|
2851
|
+
}
|
|
2827
2852
|
else if ((command === "qa-plan" || command === "qa-run") && flags.subtype === null) {
|
|
2828
2853
|
flags.subtype = arg;
|
|
2829
2854
|
}
|
|
@@ -3693,6 +3718,88 @@ function runAppWakeupRecordDeliveryCommand(parsed, options) {
|
|
|
3693
3718
|
database.close();
|
|
3694
3719
|
}
|
|
3695
3720
|
}
|
|
3721
|
+
function runAppAutopilotCommand(parsed, options) {
|
|
3722
|
+
const action = parsed.flags.action;
|
|
3723
|
+
if (action !== "start" && action !== "stop" && action !== "status") {
|
|
3724
|
+
return errorResult("app-autopilot requires an action: start, stop, or status");
|
|
3725
|
+
}
|
|
3726
|
+
const taskName = requireTask(parsed);
|
|
3727
|
+
const database = openRuntimeDatabase(parsed, options);
|
|
3728
|
+
try {
|
|
3729
|
+
const timestamp = nowIsoSeconds(options);
|
|
3730
|
+
const dbPath = runtimeDbPath(parsed, options);
|
|
3731
|
+
const dispatcherId = parsed.flags.dispatcherId ?? "dispatch-local";
|
|
3732
|
+
const desiredState = action === "start" ? "active" : action === "stop" ? "stopped" : null;
|
|
3733
|
+
let plan = appAutopilotPlanSync(database, {
|
|
3734
|
+
dbPath,
|
|
3735
|
+
dispatchIntervalSeconds: parsed.flags.intervalSeconds,
|
|
3736
|
+
dispatcherId,
|
|
3737
|
+
desiredState,
|
|
3738
|
+
heartbeatIntervalMinutes: 2,
|
|
3739
|
+
heartbeatStaleSeconds: parsed.flags.appStaleAfterSeconds,
|
|
3740
|
+
now: timestamp,
|
|
3741
|
+
taskName,
|
|
3742
|
+
watchIterations: parsed.flags.watchIterations ?? 1000000,
|
|
3743
|
+
});
|
|
3744
|
+
let receipt = null;
|
|
3745
|
+
if (action === "start" || action === "stop") {
|
|
3746
|
+
const eventType = action === "start" ? "app_autopilot_started" : "app_autopilot_stopped";
|
|
3747
|
+
const eventId = emitTelemetrySync(database, {
|
|
3748
|
+
actor: "operator",
|
|
3749
|
+
attributes: {
|
|
3750
|
+
automation_specs: plan.automation_specs.map((spec) => ({
|
|
3751
|
+
can_create: spec.can_create,
|
|
3752
|
+
interval_minutes: spec.interval_minutes,
|
|
3753
|
+
name: spec.name,
|
|
3754
|
+
role: spec.role,
|
|
3755
|
+
target_thread_id: spec.target_thread_id,
|
|
3756
|
+
target_thread_title: spec.target_thread_title,
|
|
3757
|
+
})),
|
|
3758
|
+
desired_state: desiredState,
|
|
3759
|
+
dispatcher_command: plan.control.dispatcher_command,
|
|
3760
|
+
dispatcher_id: dispatcherId,
|
|
3761
|
+
interval_minutes: plan.interval_minutes,
|
|
3762
|
+
summary: plan.summary,
|
|
3763
|
+
},
|
|
3764
|
+
correlation: {
|
|
3765
|
+
action,
|
|
3766
|
+
command: "app-autopilot",
|
|
3767
|
+
dispatcher_id: dispatcherId,
|
|
3768
|
+
},
|
|
3769
|
+
eventType,
|
|
3770
|
+
severity: plan.summary.blocked_automations > 0 ? "warning" : "info",
|
|
3771
|
+
summary: `App autopilot ${action} for ${plan.task.name}.`,
|
|
3772
|
+
taskId: plan.task.id,
|
|
3773
|
+
timestamp,
|
|
3774
|
+
});
|
|
3775
|
+
receipt = {
|
|
3776
|
+
event_id: eventId,
|
|
3777
|
+
event_type: eventType,
|
|
3778
|
+
recorded_at: timestamp,
|
|
3779
|
+
};
|
|
3780
|
+
plan = {
|
|
3781
|
+
...plan,
|
|
3782
|
+
last_policy_event: receipt,
|
|
3783
|
+
};
|
|
3784
|
+
}
|
|
3785
|
+
const output = {
|
|
3786
|
+
action,
|
|
3787
|
+
plan,
|
|
3788
|
+
receipt,
|
|
3789
|
+
};
|
|
3790
|
+
if (parsed.flags.json) {
|
|
3791
|
+
return jsonResult(output);
|
|
3792
|
+
}
|
|
3793
|
+
return {
|
|
3794
|
+
exitCode: 0,
|
|
3795
|
+
handled: true,
|
|
3796
|
+
stdout: renderAppAutopilotText(output),
|
|
3797
|
+
};
|
|
3798
|
+
}
|
|
3799
|
+
finally {
|
|
3800
|
+
database.close();
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3696
3803
|
function parseAppWakeupDeliveryStatus(value) {
|
|
3697
3804
|
if (value === "sent" || value === "skipped" || value === "blocked") {
|
|
3698
3805
|
return value;
|
|
@@ -3803,6 +3910,32 @@ function renderAppWakeupPlanText(plan) {
|
|
|
3803
3910
|
}
|
|
3804
3911
|
return `${lines.join("\n")}\n`;
|
|
3805
3912
|
}
|
|
3913
|
+
function renderAppAutopilotText(result) {
|
|
3914
|
+
const lines = [
|
|
3915
|
+
`App autopilot ${result.action} for ${result.plan.task.name}: ${result.plan.desired_state}`,
|
|
3916
|
+
`Loop status: ${result.plan.status.ok ? "ok" : "attention required"}`,
|
|
3917
|
+
`Dispatch: ${result.plan.dispatcher.state}${result.plan.dispatcher.required ? " required" : ""}`,
|
|
3918
|
+
`Dispatch command: ${result.plan.control.dispatcher_command}`,
|
|
3919
|
+
`Wake dispatch: ${result.plan.control.wakeup_dispatch_command}`,
|
|
3920
|
+
];
|
|
3921
|
+
if (result.receipt) {
|
|
3922
|
+
lines.push(`Receipt: ${result.receipt.event_type} ${result.receipt.event_id}`);
|
|
3923
|
+
}
|
|
3924
|
+
else if (result.plan.last_policy_event) {
|
|
3925
|
+
lines.push(`Last policy: ${result.plan.last_policy_event.event_type} ${result.plan.last_policy_event.event_id}`);
|
|
3926
|
+
}
|
|
3927
|
+
else {
|
|
3928
|
+
lines.push("Last policy: unconfigured");
|
|
3929
|
+
}
|
|
3930
|
+
for (const spec of result.plan.automation_specs) {
|
|
3931
|
+
lines.push(`${spec.role} automation: ${spec.can_create ? "ready" : "blocked"} ${spec.name}`, ` thread: ${spec.target_thread_title ?? "(untitled)"} ${spec.target_thread_id ?? "(missing)"}`, ` schedule: ${spec.rrule}`);
|
|
3932
|
+
if (spec.blocker) {
|
|
3933
|
+
lines.push(` blocker: ${spec.blocker}`);
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
lines.push(result.plan.control.note);
|
|
3937
|
+
return `${lines.join("\n")}\n`;
|
|
3938
|
+
}
|
|
3806
3939
|
function boundAppSessionForRoleSync(database, options) {
|
|
3807
3940
|
const sessionJoin = options.role === "manager" ? "manager_session_id" : "worker_session_id";
|
|
3808
3941
|
const row = database.prepare(`
|
|
@@ -14099,6 +14232,7 @@ function isDefaultRuntimeCommand(command) {
|
|
|
14099
14232
|
|| command === "app-wakeup-plan"
|
|
14100
14233
|
|| command === "app-wakeup-dispatch"
|
|
14101
14234
|
|| command === "app-wakeup-record-delivery"
|
|
14235
|
+
|| command === "app-autopilot"
|
|
14102
14236
|
|| command === "loop-templates"
|
|
14103
14237
|
|| command === "loop-triggers"
|
|
14104
14238
|
|| command === "ralph-loop-presets"
|
|
@@ -17114,6 +17248,8 @@ function renderDisposableBindingText(result) {
|
|
|
17114
17248
|
lines.push(` worker: ${result.heartbeat_recommendations.worker.poll_command}`);
|
|
17115
17249
|
lines.push(` status: ${result.heartbeat_recommendations.status_command}`);
|
|
17116
17250
|
lines.push(` wakeup plan: ${result.heartbeat_recommendations.wakeup_plan_command}`);
|
|
17251
|
+
lines.push(` wakeup dispatch: ${result.heartbeat_recommendations.wakeup_dispatch_command}`);
|
|
17252
|
+
lines.push(` delivery sent: ${result.heartbeat_recommendations.delivery_receipt_commands.sent}`);
|
|
17117
17253
|
lines.push(` teardown: ${result.heartbeat_recommendations.teardown_policy.idle_poll}`);
|
|
17118
17254
|
lines.push(` closeout: ${result.heartbeat_recommendations.teardown_policy.terminal_closeout_command}`);
|
|
17119
17255
|
}
|
|
@@ -17127,6 +17263,8 @@ function disposableHeartbeatRecommendations(taskName, dbPath) {
|
|
|
17127
17263
|
const workerHeartbeatCommand = disposableAppHeartbeatCommand("worker", taskName, dbPath);
|
|
17128
17264
|
const managerInboxCommand = sessionPollCommand("manager", taskName, dbPath);
|
|
17129
17265
|
const workerInboxCommand = sessionPollCommand("worker", taskName, dbPath);
|
|
17266
|
+
const wakeupDispatchCommand = `${conveyorPollInvocation()} app-wakeup-dispatch ${shellQuote(taskName)} --path ${shellQuote(dbPath)} --json`;
|
|
17267
|
+
const deliveryReceiptCommands = disposableDeliveryReceiptCommands(taskName, dbPath);
|
|
17130
17268
|
return {
|
|
17131
17269
|
applies_when: {
|
|
17132
17270
|
can_receive_push: false,
|
|
@@ -17134,6 +17272,7 @@ function disposableHeartbeatRecommendations(taskName, dbPath) {
|
|
|
17134
17272
|
receive_style: "pull",
|
|
17135
17273
|
session_kind: "codex_app",
|
|
17136
17274
|
},
|
|
17275
|
+
delivery_receipt_commands: deliveryReceiptCommands,
|
|
17137
17276
|
interval_minutes: 2,
|
|
17138
17277
|
note: "Dispatch can deliver pull-required inbox items, but Codex app/no-tmux sessions still need a heartbeat or operator wake-up to poll while idle.",
|
|
17139
17278
|
status_command: `${conveyorPollInvocation()} app-loop-status ${shellQuote(taskName)} --path ${shellQuote(dbPath)} --json`,
|
|
@@ -17144,6 +17283,7 @@ function disposableHeartbeatRecommendations(taskName, dbPath) {
|
|
|
17144
17283
|
terminal_closeout_command: terminalCloseoutCommand,
|
|
17145
17284
|
worker_rule: "The worker must not own loop teardown and must not remove heartbeat automation based on idle polling.",
|
|
17146
17285
|
},
|
|
17286
|
+
wakeup_dispatch_command: wakeupDispatchCommand,
|
|
17147
17287
|
wakeup_plan_command: `${conveyorPollInvocation()} app-wakeup-plan ${shellQuote(taskName)} --path ${shellQuote(dbPath)} --json`,
|
|
17148
17288
|
manager: {
|
|
17149
17289
|
direct_inbox_command: managerInboxCommand,
|
|
@@ -17154,6 +17294,11 @@ function disposableHeartbeatRecommendations(taskName, dbPath) {
|
|
|
17154
17294
|
`Run the manager app heartbeat for task ${taskName}.`,
|
|
17155
17295
|
`Run: ${managerHeartbeatCommand}`,
|
|
17156
17296
|
`If the heartbeat output asks for direct inbox polling, run: ${managerInboxCommand}`,
|
|
17297
|
+
`For stale app-thread recovery with an auditable receipt, run: ${wakeupDispatchCommand}`,
|
|
17298
|
+
"Send app-thread wake prompts only for actions where `send_ready=true`; direct app-thread delivery is not task completion.",
|
|
17299
|
+
`After a successful app-thread send, record it with: ${deliveryReceiptCommands.sent}`,
|
|
17300
|
+
`For healthy skipped actions, record: ${deliveryReceiptCommands.skipped}`,
|
|
17301
|
+
`For missing-thread blocked actions, record: ${deliveryReceiptCommands.blocked}`,
|
|
17157
17302
|
"If an item is consumed, execute only that manager instruction, verify worker claims before recording conclusions, update Conveyor state as appropriate, and produce exactly one next worker task.",
|
|
17158
17303
|
"If no item is consumed, stop after a one-line idle receipt.",
|
|
17159
17304
|
"Do not delete, pause, or disable manager or worker heartbeat automation after an idle poll; an idle poll is only a quiet interval.",
|
|
@@ -17170,13 +17315,23 @@ function disposableHeartbeatRecommendations(taskName, dbPath) {
|
|
|
17170
17315
|
`Run the worker app heartbeat for task ${taskName}.`,
|
|
17171
17316
|
`Run: ${workerHeartbeatCommand}`,
|
|
17172
17317
|
`If the heartbeat output asks for direct inbox polling, run: ${workerInboxCommand}`,
|
|
17173
|
-
"If an item is consumed, execute only that single worker instruction and return exact commands, compact evidence, blockers/residual risk, and exactly one next recommended worker task.",
|
|
17318
|
+
"If an item is consumed, execute only that single worker instruction and return exact commands, compact evidence for any completion claim, blockers/residual risk, and exactly one next recommended worker task.",
|
|
17174
17319
|
"If no item is consumed, stop after a one-line idle receipt.",
|
|
17175
17320
|
"Do not delete, pause, or disable worker heartbeat automation after an idle poll; the manager or operator owns terminal loop teardown.",
|
|
17176
17321
|
].join("\n"),
|
|
17177
17322
|
},
|
|
17178
17323
|
};
|
|
17179
17324
|
}
|
|
17325
|
+
function disposableDeliveryReceiptCommands(taskName, dbPath) {
|
|
17326
|
+
const base = `${conveyorPollInvocation()} app-wakeup-record-delivery ${shellQuote(taskName)} --role <role> --dispatch-receipt <receipt.event_id>`;
|
|
17327
|
+
const pathAndJson = ` --path ${shellQuote(dbPath)} --json`;
|
|
17328
|
+
return {
|
|
17329
|
+
blocked: `${base} --delivery-status blocked${pathAndJson}`,
|
|
17330
|
+
note: "Run these only after app-wakeup-dispatch. Replace <role>, <receipt.event_id>, and <action.thread.id> from the dispatch JSON; sent is valid only for send_ready=true actions.",
|
|
17331
|
+
sent: `${base} --delivery-status sent --thread-id <action.thread.id>${pathAndJson}`,
|
|
17332
|
+
skipped: `${base} --delivery-status skipped${pathAndJson}`,
|
|
17333
|
+
};
|
|
17334
|
+
}
|
|
17180
17335
|
function disposableAppHeartbeatCommand(role, taskName, dbPath) {
|
|
17181
17336
|
return `${conveyorPollInvocation()} app-heartbeat ${shellQuote(taskName)} --role ${role} --path ${shellQuote(dbPath)} --json`;
|
|
17182
17337
|
}
|