agent-conveyor 0.1.24 → 0.1.25
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 +20 -13
- package/dist/cli/typescript-runtime.js +70 -9
- package/dist/cli/typescript-runtime.js.map +1 -1
- package/dist/runtime/app-autonomy.d.ts +29 -2
- package/dist/runtime/app-autonomy.js +101 -0
- package/dist/runtime/app-autonomy.js.map +1 -1
- package/package.json +1 -1
- package/plugin/agent-conveyor/plugin.json +1 -1
- package/plugin/agent-conveyor/skills/conveyor-create-pair/SKILL.md +11 -7
- package/plugin/agent-conveyor/skills/conveyor-create-worker-set/SKILL.md +8 -5
- package/plugin/agent-conveyor/skills/conveyor-smoke-app-connections/SKILL.md +71 -46
package/README.md
CHANGED
|
@@ -189,15 +189,19 @@ the no-tmux binding with `create-disposable-binding`, point the worker at
|
|
|
189
189
|
that the loop is ready. Pair and worker-set skills run
|
|
190
190
|
`conveyor-smoke-app-connections` before real task work by default. Required
|
|
191
191
|
smoke first checks package/plugin/ledger/thread metadata, then starts a
|
|
192
|
-
nonce-scoped `app-smoke` session and blocks real work until the
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
192
|
+
nonce-scoped `app-smoke` session and blocks real work until the worker has
|
|
193
|
+
accepted smoke and delivered the manager report, then the manager validates
|
|
194
|
+
that report. Both roles need visible app-thread send receipts, fresh app
|
|
195
|
+
heartbeats, durable received/accepted acknowledgements, and an
|
|
196
|
+
`app-smoke status` result with `real_work_allowed=true`. The plain CLI records
|
|
197
|
+
and evaluates receipts; the Codex app skill/operator layer must perform live
|
|
198
|
+
`send_message_to_thread` delivery and record `sent`, `blocked`, or
|
|
199
|
+
skipped/advisory evidence. If a smoke role blocks and later succeeds, the later
|
|
200
|
+
accepted receipt for the same smoke id/nonce becomes the current terminal role
|
|
201
|
+
state. After required smoke passes, pair and worker-set skills start
|
|
202
|
+
`app-autopilot` before real work so the just-proved sessions keep polling. A
|
|
203
|
+
setup is autonomous only when the emitted heartbeat automation specs have been
|
|
204
|
+
applied and recorded with `app-autopilot record-automation`, or explicitly
|
|
201
205
|
deferred as `manual-poll only`; smoke-passed by itself only proves connection
|
|
202
206
|
plumbing at that moment.
|
|
203
207
|
When the manager is itself running in the Codex app and
|
|
@@ -538,16 +542,19 @@ stay out of receipts.
|
|
|
538
542
|
a matching `ready_to_send` action with `send_ready=true` and the same thread
|
|
539
543
|
id; healthy roles must be recorded as `skipped`; missing-thread blockers and
|
|
540
544
|
failed app-thread sends must be recorded as `blocked` with a reason.
|
|
541
|
-
- `app-autopilot start|stop|status TASK [--dispatcher-id ID]
|
|
545
|
+
- `app-autopilot start|stop|status|record-automation TASK [--dispatcher-id ID]
|
|
542
546
|
[--interval SECONDS] [--watch-iterations N] [--stale-after N]
|
|
543
|
-
[--quiet-after N] [--json]` —
|
|
547
|
+
[--quiet-after N] [--role manager|worker --automation-id ID] [--json]` —
|
|
544
548
|
Manage the pair-level app-native heartbeat policy for the active
|
|
545
549
|
manager/worker binding. `start` and `stop` write telemetry receipts and emit
|
|
546
550
|
the exact manager/worker Codex app heartbeat automation specs plus the
|
|
547
551
|
bounded Dispatch watch command. A plain shell CLI cannot call Codex app
|
|
548
552
|
thread tools, so create/pause those heartbeat automations from a Codex app
|
|
549
|
-
operator session using the emitted specs
|
|
550
|
-
|
|
553
|
+
operator session using the emitted specs, then run `record-automation` once
|
|
554
|
+
per created role automation. Conveyor remains the durable source of truth
|
|
555
|
+
through Dispatch, inboxes, automation-applied receipts, wake receipts, and app
|
|
556
|
+
heartbeat status. `status` reports `plan.readiness`; do not call a loop
|
|
557
|
+
autonomous unless `plan.readiness.autonomous_ready=true`.
|
|
551
558
|
`status` also reports `plan.quiescence`: when the loop is healthy, has no
|
|
552
559
|
`next_actions`, and both roles have produced `--quiet-after` paired
|
|
553
560
|
heartbeats since the last command or inbox-consumption receipt, it recommends
|
|
@@ -444,13 +444,14 @@ function commandHelpText(program, command) {
|
|
|
444
444
|
` ${program} dispatch --once --type nudge_worker --path /tmp/work/workerctl.db`,
|
|
445
445
|
],
|
|
446
446
|
"app-autopilot": [
|
|
447
|
-
`usage: ${program} app-autopilot start|stop|status <task> [--dispatcher-id ID] [--interval SECONDS] [--watch-iterations N] [--stale-after N] [--quiet-after N] ${path} [--json]`,
|
|
447
|
+
`usage: ${program} app-autopilot start|stop|status|record-automation <task> [--dispatcher-id ID] [--interval SECONDS] [--watch-iterations N] [--stale-after N] [--quiet-after N] [--role manager|worker --automation-id ID] ${path} [--json]`,
|
|
448
448
|
"",
|
|
449
449
|
"Manage the app-native heartbeat policy for a bound manager/worker pair.",
|
|
450
450
|
"The CLI records policy receipts and emits Codex app heartbeat automation specs; app-thread automation creation still happens through Codex app tools.",
|
|
451
451
|
"",
|
|
452
452
|
"Examples:",
|
|
453
453
|
` ${program} app-autopilot start dogfood --dispatcher-id dispatch-local --path /tmp/work/workerctl.db --json`,
|
|
454
|
+
` ${program} app-autopilot record-automation dogfood --role worker --automation-id conveyor-dogfood-worker-heartbeat --path /tmp/work/workerctl.db --json`,
|
|
454
455
|
` ${program} app-autopilot status dogfood --path /tmp/work/workerctl.db`,
|
|
455
456
|
` ${program} app-autopilot stop dogfood --path /tmp/work/workerctl.db --json`,
|
|
456
457
|
],
|
|
@@ -544,6 +545,7 @@ function parseRuntimeArgs(args, env) {
|
|
|
544
545
|
all: false,
|
|
545
546
|
allowAdditionalReceipt: false,
|
|
546
547
|
action: null,
|
|
548
|
+
automationId: null,
|
|
547
549
|
artifactPath: null,
|
|
548
550
|
assetType: null,
|
|
549
551
|
assignment: null,
|
|
@@ -1450,6 +1452,17 @@ function parseRuntimeArgs(args, env) {
|
|
|
1450
1452
|
flags.codexProfile = value.value;
|
|
1451
1453
|
index += 1;
|
|
1452
1454
|
}
|
|
1455
|
+
else if (arg === "--automation-id") {
|
|
1456
|
+
if (command !== "app-autopilot") {
|
|
1457
|
+
return { command, enabled, error: "Unsupported TypeScript runtime option: --automation-id", explicit, flags, task };
|
|
1458
|
+
}
|
|
1459
|
+
const value = valueAfter(queue, index, arg);
|
|
1460
|
+
if (value.error) {
|
|
1461
|
+
return { command, enabled, error: value.error, explicit, flags, task };
|
|
1462
|
+
}
|
|
1463
|
+
flags.automationId = value.value;
|
|
1464
|
+
index += 1;
|
|
1465
|
+
}
|
|
1453
1466
|
else if (arg === "--session-dir") {
|
|
1454
1467
|
if (command !== "create-disposable-binding") {
|
|
1455
1468
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --session-dir", explicit, flags, task };
|
|
@@ -3130,7 +3143,7 @@ function parseRuntimeArgs(args, env) {
|
|
|
3130
3143
|
flags.subtype = arg;
|
|
3131
3144
|
}
|
|
3132
3145
|
else if (command === "app-autopilot" && flags.action === null) {
|
|
3133
|
-
if (!["start", "stop", "status"].includes(arg)) {
|
|
3146
|
+
if (!["record-automation", "start", "stop", "status"].includes(arg)) {
|
|
3134
3147
|
return { command, enabled, error: `Unsupported app-autopilot action: ${arg}`, explicit, flags, task };
|
|
3135
3148
|
}
|
|
3136
3149
|
flags.action = arg;
|
|
@@ -4349,8 +4362,8 @@ function renderAppWorkerRotationPlanText(plan) {
|
|
|
4349
4362
|
}
|
|
4350
4363
|
function runAppAutopilotCommand(parsed, options) {
|
|
4351
4364
|
const action = parsed.flags.action;
|
|
4352
|
-
if (action !== "start" && action !== "stop" && action !== "status") {
|
|
4353
|
-
return errorResult("app-autopilot requires an action: start, stop, or
|
|
4365
|
+
if (action !== "record-automation" && action !== "start" && action !== "stop" && action !== "status") {
|
|
4366
|
+
return errorResult("app-autopilot requires an action: start, stop, status, or record-automation");
|
|
4354
4367
|
}
|
|
4355
4368
|
const taskName = requireTask(parsed);
|
|
4356
4369
|
const database = openRuntimeDatabase(parsed, options);
|
|
@@ -4358,7 +4371,39 @@ function runAppAutopilotCommand(parsed, options) {
|
|
|
4358
4371
|
const timestamp = nowIsoSeconds(options);
|
|
4359
4372
|
const dbPath = runtimeDbPath(parsed, options);
|
|
4360
4373
|
const dispatcherId = parsed.flags.dispatcherId ?? "dispatch-local";
|
|
4374
|
+
const task = taskRowForDiagnostics(database, taskName);
|
|
4361
4375
|
const desiredState = action === "start" ? "active" : action === "stop" ? "stopped" : null;
|
|
4376
|
+
let receipt = null;
|
|
4377
|
+
if (action === "record-automation") {
|
|
4378
|
+
const role = parseAppSmokeRole(parsed.flags.role);
|
|
4379
|
+
if (role instanceof Error) {
|
|
4380
|
+
return errorResult(role.message);
|
|
4381
|
+
}
|
|
4382
|
+
const automationId = requiredStringFlag(parsed.flags.automationId, "--automation-id");
|
|
4383
|
+
const eventId = emitTelemetrySync(database, {
|
|
4384
|
+
actor: "operator",
|
|
4385
|
+
attributes: {
|
|
4386
|
+
automation_id: automationId,
|
|
4387
|
+
role,
|
|
4388
|
+
},
|
|
4389
|
+
correlation: {
|
|
4390
|
+
action,
|
|
4391
|
+
command: "app-autopilot",
|
|
4392
|
+
dispatcher_id: dispatcherId,
|
|
4393
|
+
role,
|
|
4394
|
+
},
|
|
4395
|
+
eventType: "app_autopilot_automation_applied",
|
|
4396
|
+
severity: "info",
|
|
4397
|
+
summary: `App autopilot ${role} automation applied for ${task.name}.`,
|
|
4398
|
+
taskId: task.id,
|
|
4399
|
+
timestamp,
|
|
4400
|
+
});
|
|
4401
|
+
receipt = {
|
|
4402
|
+
event_id: eventId,
|
|
4403
|
+
event_type: "app_autopilot_automation_applied",
|
|
4404
|
+
recorded_at: timestamp,
|
|
4405
|
+
};
|
|
4406
|
+
}
|
|
4362
4407
|
let plan = appAutopilotPlanSync(database, {
|
|
4363
4408
|
dbPath,
|
|
4364
4409
|
dispatchIntervalSeconds: parsed.flags.intervalSeconds,
|
|
@@ -4371,12 +4416,12 @@ function runAppAutopilotCommand(parsed, options) {
|
|
|
4371
4416
|
taskName,
|
|
4372
4417
|
watchIterations: parsed.flags.watchIterations ?? 1000000,
|
|
4373
4418
|
});
|
|
4374
|
-
let receipt = null;
|
|
4375
4419
|
if (action === "start" || action === "stop") {
|
|
4376
4420
|
const eventType = action === "start" ? "app_autopilot_started" : "app_autopilot_stopped";
|
|
4377
4421
|
const eventId = emitTelemetrySync(database, {
|
|
4378
4422
|
actor: "operator",
|
|
4379
4423
|
attributes: {
|
|
4424
|
+
automation_state: plan.automation_state,
|
|
4380
4425
|
automation_specs: plan.automation_specs.map((spec) => ({
|
|
4381
4426
|
can_create: spec.can_create,
|
|
4382
4427
|
interval_minutes: spec.interval_minutes,
|
|
@@ -4390,6 +4435,7 @@ function runAppAutopilotCommand(parsed, options) {
|
|
|
4390
4435
|
dispatcher_id: dispatcherId,
|
|
4391
4436
|
interval_minutes: plan.interval_minutes,
|
|
4392
4437
|
quiescence: plan.quiescence,
|
|
4438
|
+
readiness: plan.readiness,
|
|
4393
4439
|
summary: plan.summary,
|
|
4394
4440
|
},
|
|
4395
4441
|
correlation: {
|
|
@@ -4662,8 +4708,10 @@ function appSmokeRoleStatus(role, session, receipts, smoke, options) {
|
|
|
4662
4708
|
const roleReceipts = receipts.filter((receipt) => receipt.role === role && receipt.nonce === smoke.nonce);
|
|
4663
4709
|
const sent = roleReceipts.some((receipt) => receipt.status === "sent");
|
|
4664
4710
|
const received = roleReceipts.some((receipt) => receipt.status === "received");
|
|
4665
|
-
const
|
|
4666
|
-
const
|
|
4711
|
+
const terminalReceipts = roleReceipts.filter((receipt) => receipt.status === "accepted" || receipt.status === "blocked");
|
|
4712
|
+
const terminalReceipt = terminalReceipts.at(-1);
|
|
4713
|
+
const accepted = terminalReceipt?.status === "accepted";
|
|
4714
|
+
const blockedReceipt = terminalReceipt?.status === "blocked" ? terminalReceipt : undefined;
|
|
4667
4715
|
const heartbeatFresh = session.last_heartbeat_at !== null
|
|
4668
4716
|
&& session.last_heartbeat_at >= smoke.recorded_at
|
|
4669
4717
|
&& secondsBetweenIso(session.last_heartbeat_at, options.now) <= options.staleAfterSeconds;
|
|
@@ -4739,14 +4787,18 @@ function latestAppSmokeSessionSync(database, options) {
|
|
|
4739
4787
|
}
|
|
4740
4788
|
function appSmokeReceiptsSync(database, options) {
|
|
4741
4789
|
const rows = database.prepare(`
|
|
4742
|
-
select attributes_json
|
|
4790
|
+
select id, timestamp, attributes_json
|
|
4743
4791
|
from telemetry_events
|
|
4744
4792
|
where task_id = ?
|
|
4745
4793
|
and event_type = 'app_smoke_receipt_recorded'
|
|
4746
4794
|
and json_extract(attributes_json, '$.smoke_id') = ?
|
|
4747
4795
|
order by timestamp, id
|
|
4748
4796
|
`).all(options.taskId, options.smokeId);
|
|
4749
|
-
return rows.map((row) =>
|
|
4797
|
+
return rows.map((row) => ({
|
|
4798
|
+
...JSON.parse(row.attributes_json),
|
|
4799
|
+
event_id: row.id,
|
|
4800
|
+
recorded_at: row.timestamp,
|
|
4801
|
+
}));
|
|
4750
4802
|
}
|
|
4751
4803
|
function appSmokeBoundSessionsSync(database, taskId) {
|
|
4752
4804
|
const row = database.prepare(`
|
|
@@ -4952,6 +5004,7 @@ function renderAppWakeupPlanText(plan) {
|
|
|
4952
5004
|
function renderAppAutopilotText(result) {
|
|
4953
5005
|
const lines = [
|
|
4954
5006
|
`App autopilot ${result.action} for ${result.plan.task.name}: ${result.plan.desired_state}`,
|
|
5007
|
+
`Readiness: ${result.plan.readiness.state}${result.plan.readiness.autonomous_ready ? " (autonomous)" : " (setup required)"}`,
|
|
4955
5008
|
`Loop status: ${result.plan.status.ok ? "ok" : "attention required"}`,
|
|
4956
5009
|
`Dispatch: ${result.plan.dispatcher.state}${result.plan.dispatcher.required ? " required" : ""}`,
|
|
4957
5010
|
`Dispatch command: ${result.plan.control.dispatcher_command}`,
|
|
@@ -4973,12 +5026,19 @@ function renderAppAutopilotText(result) {
|
|
|
4973
5026
|
else {
|
|
4974
5027
|
lines.push("Last policy: unconfigured");
|
|
4975
5028
|
}
|
|
5029
|
+
for (const blocker of result.plan.readiness.blockers) {
|
|
5030
|
+
lines.push(`Readiness blocker: ${blocker}`);
|
|
5031
|
+
}
|
|
4976
5032
|
for (const spec of result.plan.automation_specs) {
|
|
4977
5033
|
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}`);
|
|
4978
5034
|
if (spec.blocker) {
|
|
4979
5035
|
lines.push(` blocker: ${spec.blocker}`);
|
|
4980
5036
|
}
|
|
4981
5037
|
}
|
|
5038
|
+
for (const role of result.plan.automation_state.applied_roles) {
|
|
5039
|
+
const receipt = result.plan.automation_state.receipts.find((item) => item.role === role);
|
|
5040
|
+
lines.push(`${role} automation applied: ${receipt?.automation_id ?? "(unknown)"}`);
|
|
5041
|
+
}
|
|
4982
5042
|
lines.push(result.plan.control.note);
|
|
4983
5043
|
return `${lines.join("\n")}\n`;
|
|
4984
5044
|
}
|
|
@@ -18966,6 +19026,7 @@ function telemetryEventsForRunSync(database, options) {
|
|
|
18966
19026
|
}
|
|
18967
19027
|
function appTaskDispatchSummarySync(database, options) {
|
|
18968
19028
|
const taskDispatchEventTypes = [
|
|
19029
|
+
"app_autopilot_automation_applied",
|
|
18969
19030
|
"app_autopilot_started",
|
|
18970
19031
|
"app_autopilot_stopped",
|
|
18971
19032
|
"app_heartbeat",
|