agent-conveyor 0.1.7 → 0.1.8
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 +69 -19
- package/dist/cli/typescript-runtime.js +109 -15
- package/dist/cli/typescript-runtime.js.map +1 -1
- package/dist/runtime/codex-session.d.ts +6 -0
- package/dist/runtime/codex-session.js +27 -5
- package/dist/runtime/codex-session.js.map +1 -1
- package/dist/runtime/notifications.js +102 -1
- package/dist/runtime/notifications.js.map +1 -1
- package/dist/state/database.js +162 -24
- package/dist/state/database.js.map +1 -1
- package/dist/state/schema-v23.js +4 -2
- package/dist/state/schema-v23.js.map +1 -1
- package/dist/state/sqlite-contract.d.ts +1 -1
- package/dist/state/sqlite-contract.js +1 -1
- package/docs/landing-page.html +393 -40
- package/docs/manager-recipes.md +78 -0
- package/docs/typescript-migration/cli-contract.md +11 -14
- package/docs/typescript-migration/package-install-contract.md +1 -1
- package/docs/typescript-migration/qa-gate-matrix.md +7 -13
- package/docs/typescript-migration/sqlite-state-contract.md +2 -3
- package/docs/typescript-migration/t005-runtime-parity.md +12 -12
- package/package.json +1 -1
- package/skills/manage-codex-workers/SKILL.md +71 -16
package/README.md
CHANGED
|
@@ -131,6 +131,10 @@ successful JSON. Treat that warning as expected Node runtime noise when the
|
|
|
131
131
|
command exits 0 and the JSON result reports `"ok": true`.
|
|
132
132
|
Before publishing `agent-conveyor` to npm, use
|
|
133
133
|
[`docs/package-release.md`](docs/package-release.md).
|
|
134
|
+
The preferred publish path is the manual GitHub Actions `publish.yml` workflow
|
|
135
|
+
with npm Trusted Publishing enabled for the `npm-production` environment. Use
|
|
136
|
+
`publish=false` for artifact review and `publish=true` only for an approved
|
|
137
|
+
release version that is not already on npm.
|
|
134
138
|
|
|
135
139
|
For common manager setups, start with
|
|
136
140
|
[`docs/manager-recipes.md`](docs/manager-recipes.md). It maps natural-language
|
|
@@ -144,6 +148,10 @@ For a package-facing overview of these modes, open
|
|
|
144
148
|
[`docs/landing-page.html`](docs/landing-page.html) locally or host it as a
|
|
145
149
|
static landing page. From the repo, `npm run docs:landing` serves it at
|
|
146
150
|
`http://127.0.0.1:8765/`.
|
|
151
|
+
The GitHub Pages version lives at
|
|
152
|
+
[`neonwatty.github.io/agent-conveyor`](https://neonwatty.github.io/agent-conveyor/).
|
|
153
|
+
Use `node scripts/check-landing-page.mjs` for a docs-only desktop/mobile
|
|
154
|
+
screenshot gate; this does not run the full package release smoke.
|
|
147
155
|
|
|
148
156
|
After install, the intended Codex app entry point is natural language. Open a
|
|
149
157
|
new Codex app session in the target repo and say:
|
|
@@ -158,7 +166,15 @@ Require adversarial proof before another worker iteration.
|
|
|
158
166
|
The installed skill should call the `conveyor` CLI, choose names, create the
|
|
159
167
|
no-tmux binding with `create-disposable-binding`, point the worker at
|
|
160
168
|
`worker-inbox`, and use `loop-status` plus telemetry receipts before reporting
|
|
161
|
-
that the loop is ready.
|
|
169
|
+
that the loop is ready. When the manager is itself running in the Codex app and
|
|
170
|
+
thread tools are available, the skill should first call `create_thread` for a
|
|
171
|
+
fresh same-project worker, name it with `set_thread_title`, pass the returned
|
|
172
|
+
thread identity through `--worker-codex-app-thread-id` and
|
|
173
|
+
`--worker-codex-app-thread-title`, and use `send_message_to_thread` only to
|
|
174
|
+
deliver the generated `worker_handoff` bootstrap prompt. The raw terminal
|
|
175
|
+
`conveyor` CLI does not create Codex app threads by itself; if app thread tools
|
|
176
|
+
are unavailable, open a separate Codex app worker manually and paste the
|
|
177
|
+
`worker_handoff` prompt.
|
|
162
178
|
|
|
163
179
|
Dispatch is core infrastructure for supervised worker/manager pairs. The
|
|
164
180
|
`pair` workflow starts a detached Dispatch watch process by default so worker
|
|
@@ -309,16 +325,21 @@ tmux attach -t codex-live-test
|
|
|
309
325
|
Use `--accept-trust` only for directories you intentionally trust; it retries
|
|
310
326
|
Enter during startup discovery so fresh workspaces do not stall before
|
|
311
327
|
registration.
|
|
312
|
-
- `register-worker --name N [--pid P | --codex-session PATH] [--cwd D] [--tmux-session S]` —
|
|
328
|
+
- `register-worker --name N [--pid P | --codex-session PATH] [--cwd D] [--tmux-session S] [--codex-app-thread-id ID] [--codex-app-thread-title TITLE]` —
|
|
313
329
|
Register an already-running Codex session as a worker. Rollout JSONL is
|
|
314
330
|
auto-discovered from the pid via `lsof` unless `--codex-session` is given.
|
|
331
|
+
The optional Codex app thread flags are metadata supplied by the Codex app
|
|
332
|
+
skill/tool layer; they help humans identify the app thread but do not change
|
|
333
|
+
rollout ingest or Dispatch delivery.
|
|
315
334
|
- `register-manager --name N ...` — Same arguments; tmux is not required.
|
|
316
335
|
Both registration commands print a `communication` object. When
|
|
317
336
|
`--tmux-session` is present, `communication.session_kind='tmux'`,
|
|
318
337
|
`receive_style='push'`, and `delivery_mode='push'`; without tmux but with a
|
|
319
338
|
Codex rollout identity, `session_kind='codex_app'`, `receive_style='pull'`,
|
|
320
339
|
and `delivery_mode='pull_required'`, with the role-specific inbox polling
|
|
321
|
-
command template.
|
|
340
|
+
command template. The generated command may include a local
|
|
341
|
+
`PATH=.../bin:$PATH conveyor` prefix; preserve that prefix when sending the
|
|
342
|
+
command to a Codex app thread.
|
|
322
343
|
- `deregister <name>` — Mark a session gone. Refuses if the session is bound
|
|
323
344
|
to an active task.
|
|
324
345
|
- `sessions [--role worker|manager] [--state active|gone|all] [--include-legacy]
|
|
@@ -338,7 +359,7 @@ tmux attach -t codex-live-test
|
|
|
338
359
|
registration, so managers can detect whether a worker or manager is
|
|
339
360
|
tmux-push capable or must poll its mailbox.
|
|
340
361
|
- `tasks [--create NAME --goal G --summary S]` — List or create tasks.
|
|
341
|
-
- `create-disposable-binding TASK [--worker NAME] [--manager NAME] [--template TEMPLATE | --required-before-continue TYPE] [--adversarial]` —
|
|
362
|
+
- `create-disposable-binding TASK [--worker NAME] [--manager NAME] [--template TEMPLATE | --required-before-continue TYPE] [--adversarial] [--worker-codex-app-thread-id ID] [--worker-codex-app-thread-title TITLE] [--manager-codex-app-thread-id ID] [--manager-codex-app-thread-title TITLE]` —
|
|
342
363
|
Create a no-tmux manager/worker binding for real Ralph-loop slices. The
|
|
343
364
|
helper creates the task when missing, marks it managed, writes valid Codex
|
|
344
365
|
rollout JSONL files, registers worker and manager sessions with
|
|
@@ -346,7 +367,18 @@ tmux attach -t codex-live-test
|
|
|
346
367
|
custom Ralph-loop policy run, and prints replay commands for Dispatch,
|
|
347
368
|
`loop-status`, per-session `communication` metadata, plus a `worker_handoff`
|
|
348
369
|
prompt that tells Codex app workers to keep polling their worker inbox
|
|
349
|
-
through the bounded loop.
|
|
370
|
+
through the bounded loop using the exact generated command. For pull-required
|
|
371
|
+
Codex app sessions, the JSON output also includes
|
|
372
|
+
`heartbeat_recommendations` with role-specific poll prompts; Dispatch can
|
|
373
|
+
deliver into those inboxes, but a heartbeat or operator wake-up is still
|
|
374
|
+
required to make an idle app thread poll autonomously. Those recommendations
|
|
375
|
+
include a `teardown_policy`: an idle poll is only a quiet interval, not a
|
|
376
|
+
reason to delete or pause heartbeat automation; heartbeat teardown belongs to
|
|
377
|
+
the manager/operator after terminal closeout or explicit operator instruction.
|
|
378
|
+
The optional
|
|
379
|
+
Codex app thread metadata is normally supplied after a Codex app manager has
|
|
380
|
+
used `create_thread` and `set_thread_title`; terminal-only users can omit it
|
|
381
|
+
and still use the manual no-tmux handoff.
|
|
350
382
|
- `discover [QUERY] [--all] [--limit N]` / `search [QUERY]` — Search tasks,
|
|
351
383
|
registered sessions, active bindings, and recent telemetry in one JSON result.
|
|
352
384
|
Use this for conversational setup when a manager or Codex session needs to
|
|
@@ -407,8 +439,8 @@ tmux attach -t codex-live-test
|
|
|
407
439
|
or manager inspection; `manager_config` is not a valid criteria source.
|
|
408
440
|
To add a criterion and satisfy that same row after verification:
|
|
409
441
|
```bash
|
|
410
|
-
criterion_id=$(conveyor criteria my-task --add --criterion "Targeted prompt tests pass" --source worker_proposed --status proposed |
|
|
411
|
-
conveyor criteria my-task --satisfy "$criterion_id" --evidence-json '{"command":"
|
|
442
|
+
criterion_id=$(conveyor criteria my-task --add --criterion "Targeted prompt tests pass" --source worker_proposed --status proposed | node -e 'const fs = require("fs"); console.log(JSON.parse(fs.readFileSync(0, "utf8")).affected_criterion.id)')
|
|
443
|
+
conveyor criteria my-task --satisfy "$criterion_id" --evidence-json '{"command":"npm test -- --runInBand","status":"pass"}'
|
|
412
444
|
```
|
|
413
445
|
For mutation responses, treat `affected_criterion` as the authoritative
|
|
414
446
|
receipt for the row changed by that command. When a manager applies multiple
|
|
@@ -713,7 +745,7 @@ tmux attach -t codex-live-test
|
|
|
713
745
|
- `loop-status TASK --run RUN [--json]` — Summarize a Ralph-loop run for manager
|
|
714
746
|
review: policy template, iteration bounds, command states, routed
|
|
715
747
|
notifications, worker inbox backlog, evidence types, consumed-inbox
|
|
716
|
-
telemetry, failure counts, and a recommendation.
|
|
748
|
+
and iteration-advanced telemetry, failure counts, and a recommendation.
|
|
717
749
|
|
|
718
750
|
For real vertical slices, start with the Ralph loop operator guide in
|
|
719
751
|
`docs/qa/ralph-loop-operator-guide.md`. It explains the controlled
|
|
@@ -722,7 +754,12 @@ required evidence, adversarial proof, `loop-status`, and telemetry review pass
|
|
|
722
754
|
bar.
|
|
723
755
|
Use `create-disposable-binding` when the manager and worker are Codex app or
|
|
724
756
|
other no-tmux sessions and you want the same Dispatch rails without manual
|
|
725
|
-
task/session/bind setup.
|
|
757
|
+
task/session/bind setup. In a Codex app manager session, prefer a fresh
|
|
758
|
+
same-project `create_thread` worker plus `set_thread_title` before creating the
|
|
759
|
+
binding, then pass the worker thread id/title into Conveyor. Use `fork_thread`
|
|
760
|
+
only when the user explicitly asks to fork or resume this conversation. If app
|
|
761
|
+
thread tools are unavailable, create the binding anyway and paste the returned
|
|
762
|
+
`worker_handoff` prompt into a manually opened worker session.
|
|
726
763
|
- `enqueue-continue-iteration TASK --loop-run RUN --requested-iteration N` —
|
|
727
764
|
Queue a manager-requested next loop pass for Dispatch. The command refuses
|
|
728
765
|
same/current iteration requests before they become pending queue rows, while
|
|
@@ -980,16 +1017,27 @@ Current dispatch state:
|
|
|
980
1017
|
in `routed_notifications`, and threaded with `correlation_id`.
|
|
981
1018
|
- The session inbox is the same `routed_notifications` stream addressed by
|
|
982
1019
|
`target_session_id`: tmux push is optional transport. Codex app-based sessions
|
|
983
|
-
should long-poll with
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1020
|
+
should long-poll with the returned `communication.poll_command`. For
|
|
1021
|
+
disposable Ralph loops, use the generated `worker_handoff` prompt so the
|
|
1022
|
+
worker keeps polling until no inbox item remains or the loop reaches
|
|
1023
|
+
`max_iterations`. For no-tmux Codex app sessions, treat
|
|
1024
|
+
`communication.requires_polling=true` as requiring a heartbeat/wake layer:
|
|
1025
|
+
a delivered pull inbox item does not by itself wake an idle app thread. Do
|
|
1026
|
+
not delete or pause heartbeats because an inbox poll is idle. A terminal
|
|
1027
|
+
manager decision should be followed by `finish-task --require-criteria-audit`
|
|
1028
|
+
or by an explicit blocker explaining why the task/binding still appears
|
|
1029
|
+
active.
|
|
987
1030
|
- `register-worker`, `register-manager`, `sessions`, `discover`, and
|
|
988
1031
|
`create-disposable-binding --json` expose a `communication` block per
|
|
989
1032
|
session. Treat `session_kind='tmux'` plus `receive_style='push'` as direct
|
|
990
1033
|
tmux-delivery capable; treat `session_kind='codex_app'` plus
|
|
991
1034
|
`receive_style='pull'` as mailbox polling required for that worker or
|
|
992
1035
|
manager.
|
|
1036
|
+
- App-assisted setup may also expose `codex_app_thread_id` and
|
|
1037
|
+
`codex_app_thread_title` for sessions created or identified by Codex app
|
|
1038
|
+
thread tools. Treat those fields as human/app navigation metadata; the
|
|
1039
|
+
durable communication record is still `routed_notifications` plus inbox
|
|
1040
|
+
consumption telemetry.
|
|
993
1041
|
- Template-backed `continue_iteration` deliveries include `loop_policy` in the
|
|
994
1042
|
inbox payload, with template name, current/max iteration, cleanup policy,
|
|
995
1043
|
required evidence, artifact requirements, and recommended tools. Codex
|
|
@@ -1001,6 +1049,10 @@ Current dispatch state:
|
|
|
1001
1049
|
- Consuming a mailbox item records `dispatch_inbox_consumed` telemetry with the
|
|
1002
1050
|
notification id, signal type, delivery mode, target session role, and poll
|
|
1003
1051
|
count, so manager/worker dispatcher handoffs are visible in audit evidence.
|
|
1052
|
+
When the item is `continue_iteration`, Conveyor also advances the run
|
|
1053
|
+
metadata's durable `current_iteration` to the requested iteration and records
|
|
1054
|
+
`ralph_loop_iteration_advanced` telemetry keyed to the run, notification,
|
|
1055
|
+
command, and consuming session.
|
|
1004
1056
|
- If `doctor-self --json` reports `workerctl_on_path=false` inside a Codex app
|
|
1005
1057
|
session, run `conveyor ...` from the repository root or install the
|
|
1006
1058
|
local wrapper with `scripts/install-local --write`. Its `inside_tmux` check
|
|
@@ -1112,9 +1164,8 @@ scripts/rc-check --with-live-smoke-repeat
|
|
|
1112
1164
|
Underlying deterministic checks:
|
|
1113
1165
|
|
|
1114
1166
|
```bash
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
python3 -m py_compile scripts/workerctl scripts/check-resource-warnings workerctl/*.py
|
|
1167
|
+
scripts/check-resource-warnings -- npm test -- --runInBand
|
|
1168
|
+
npm run build
|
|
1118
1169
|
npm run migration:audit:final
|
|
1119
1170
|
scripts/package-smoke
|
|
1120
1171
|
scripts/release-check
|
|
@@ -1123,10 +1174,9 @@ scripts/release-check
|
|
|
1123
1174
|
For local parallel experiments, prefer:
|
|
1124
1175
|
|
|
1125
1176
|
```bash
|
|
1126
|
-
|
|
1177
|
+
npm test
|
|
1127
1178
|
```
|
|
1128
1179
|
|
|
1129
|
-
This gives the process a temporary `WORKERCTL_STATE_ROOT` and a test namespace.
|
|
1130
1180
|
The standard CI job remains serial.
|
|
1131
1181
|
|
|
1132
1182
|
GitHub Actions runs `scripts/rc-check --skip-live-smoke-repeat` and
|
|
@@ -1134,7 +1184,7 @@ GitHub Actions runs `scripts/rc-check --skip-live-smoke-repeat` and
|
|
|
1134
1184
|
remains local/manual because hosted runners may not have `codex`.
|
|
1135
1185
|
The ResourceWarning gate intentionally fails on any `ResourceWarning` text in
|
|
1136
1186
|
test output so finalization-time resource warnings cannot be hidden by a zero
|
|
1137
|
-
|
|
1187
|
+
test exit status.
|
|
1138
1188
|
|
|
1139
1189
|
Live local smoke gate:
|
|
1140
1190
|
|
|
@@ -425,6 +425,10 @@ function parseRuntimeArgs(args, env) {
|
|
|
425
425
|
candidate: null,
|
|
426
426
|
check: null,
|
|
427
427
|
classifyPrompt: null,
|
|
428
|
+
workerCodexAppThreadId: null,
|
|
429
|
+
workerCodexAppThreadTitle: null,
|
|
430
|
+
managerCodexAppThreadId: null,
|
|
431
|
+
managerCodexAppThreadTitle: null,
|
|
428
432
|
codexSession: null,
|
|
429
433
|
create: null,
|
|
430
434
|
createRun: null,
|
|
@@ -1947,6 +1951,31 @@ function parseRuntimeArgs(args, env) {
|
|
|
1947
1951
|
flags.manager = value.value;
|
|
1948
1952
|
index += 1;
|
|
1949
1953
|
}
|
|
1954
|
+
else if (arg === "--worker-codex-app-thread-id"
|
|
1955
|
+
|| arg === "--worker-codex-app-thread-title"
|
|
1956
|
+
|| arg === "--manager-codex-app-thread-id"
|
|
1957
|
+
|| arg === "--manager-codex-app-thread-title") {
|
|
1958
|
+
if (command !== "create-disposable-binding") {
|
|
1959
|
+
return { command, enabled, error: `Unsupported TypeScript runtime option: ${arg}`, explicit, flags, task };
|
|
1960
|
+
}
|
|
1961
|
+
const value = valueAfter(queue, index, arg);
|
|
1962
|
+
if (value.error) {
|
|
1963
|
+
return { command, enabled, error: value.error, explicit, flags, task };
|
|
1964
|
+
}
|
|
1965
|
+
if (arg === "--worker-codex-app-thread-id") {
|
|
1966
|
+
flags.workerCodexAppThreadId = value.value;
|
|
1967
|
+
}
|
|
1968
|
+
else if (arg === "--worker-codex-app-thread-title") {
|
|
1969
|
+
flags.workerCodexAppThreadTitle = value.value;
|
|
1970
|
+
}
|
|
1971
|
+
else if (arg === "--manager-codex-app-thread-id") {
|
|
1972
|
+
flags.managerCodexAppThreadId = value.value;
|
|
1973
|
+
}
|
|
1974
|
+
else {
|
|
1975
|
+
flags.managerCodexAppThreadTitle = value.value;
|
|
1976
|
+
}
|
|
1977
|
+
index += 1;
|
|
1978
|
+
}
|
|
1950
1979
|
else if (arg === "--template") {
|
|
1951
1980
|
if (command !== "create-disposable-binding" && command !== "loop-templates") {
|
|
1952
1981
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --template", explicit, flags, task };
|
|
@@ -3968,10 +3997,10 @@ function qaRunBuildClearLoop(context) {
|
|
|
3968
3997
|
const artifactDir = qaArtifactDir(context, "build-clear-loop", slug, run.id);
|
|
3969
3998
|
const buildReceipt = join(artifactDir, "build-passed.json");
|
|
3970
3999
|
mkdirSync(dirname(buildReceipt), { recursive: true });
|
|
3971
|
-
writeFileSync(buildReceipt, `${JSON.stringify(sortJson({ command: "
|
|
4000
|
+
writeFileSync(buildReceipt, `${JSON.stringify(sortJson({ command: "npm test -- --runInBand", result: "pass", status: "build_passed" }), null, 2)}\n`);
|
|
3972
4001
|
qaRecordLoopEvidence(context, task, run.id, "build_passed", "qa-run-build-clear-build-passed", {
|
|
3973
4002
|
artifactPath: buildReceipt,
|
|
3974
|
-
metadata: { command: "
|
|
4003
|
+
metadata: { command: "npm test -- --runInBand", result: "Focused build/test command passed before retry." },
|
|
3975
4004
|
});
|
|
3976
4005
|
enqueueQaContinue(context, task, run.id, "qa-run-build-clear-build-only", "Run after build evidence only.");
|
|
3977
4006
|
const buildOnlyDispatch = qaDispatchContinueOnce(context, "qa-run-build-clear-build-only");
|
|
@@ -4884,19 +4913,14 @@ function dispatchWatchCommand(workerctlPath, dispatcherId, dbPath) {
|
|
|
4884
4913
|
}
|
|
4885
4914
|
function installableSkillSources() {
|
|
4886
4915
|
const root = packageRootFromRuntimeModule();
|
|
4887
|
-
const
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
.map((name) => ({ name, source: join(candidate, name) }))
|
|
4894
|
-
.filter((skill) => existsSync(join(skill.source, "SKILL.md")));
|
|
4895
|
-
if (skills.length === 2) {
|
|
4896
|
-
return skills;
|
|
4897
|
-
}
|
|
4916
|
+
const candidate = join(root, "skills");
|
|
4917
|
+
const skills = ["manage-codex-workers", "codex-review"]
|
|
4918
|
+
.map((name) => ({ name, source: join(candidate, name) }))
|
|
4919
|
+
.filter((skill) => existsSync(join(skill.source, "SKILL.md")));
|
|
4920
|
+
if (skills.length === 2) {
|
|
4921
|
+
return skills;
|
|
4898
4922
|
}
|
|
4899
|
-
throw new Error("Bundled Agent Conveyor skills not found in skills
|
|
4923
|
+
throw new Error("Bundled Agent Conveyor skills not found in skills/.");
|
|
4900
4924
|
}
|
|
4901
4925
|
function runBindCommand(parsed, options) {
|
|
4902
4926
|
const unsupported = unsupportedBindOptions(parsed);
|
|
@@ -4997,6 +5021,8 @@ function runCreateDisposableBindingCommand(parsed, options) {
|
|
|
4997
5021
|
const workerRollout = writeDisposableRollout(sessionDir, workerName, cwd);
|
|
4998
5022
|
const managerRollout = writeDisposableRollout(sessionDir, managerName, cwd);
|
|
4999
5023
|
const worker = registerSessionSync(database, {
|
|
5024
|
+
codexAppThreadId: parsed.flags.workerCodexAppThreadId,
|
|
5025
|
+
codexAppThreadTitle: parsed.flags.workerCodexAppThreadTitle,
|
|
5000
5026
|
codexSessionPath: workerRollout.path,
|
|
5001
5027
|
cwd,
|
|
5002
5028
|
name: workerName,
|
|
@@ -5005,6 +5031,8 @@ function runCreateDisposableBindingCommand(parsed, options) {
|
|
|
5005
5031
|
tmuxSession: null,
|
|
5006
5032
|
});
|
|
5007
5033
|
const manager = registerSessionSync(database, {
|
|
5034
|
+
codexAppThreadId: parsed.flags.managerCodexAppThreadId,
|
|
5035
|
+
codexAppThreadTitle: parsed.flags.managerCodexAppThreadTitle,
|
|
5008
5036
|
codexSessionPath: managerRollout.path,
|
|
5009
5037
|
cwd,
|
|
5010
5038
|
name: managerName,
|
|
@@ -5043,6 +5071,8 @@ function runCreateDisposableBindingCommand(parsed, options) {
|
|
|
5043
5071
|
db_path: dbPath,
|
|
5044
5072
|
manager: {
|
|
5045
5073
|
communication: disposableSessionCommunication("manager", task.name, dbPath),
|
|
5074
|
+
codex_app_thread_id: manager.codex_app_thread_id,
|
|
5075
|
+
codex_app_thread_title: manager.codex_app_thread_title,
|
|
5046
5076
|
id: manager.session_id,
|
|
5047
5077
|
name: managerName,
|
|
5048
5078
|
rollout_path: managerRollout.path,
|
|
@@ -5068,11 +5098,14 @@ function runCreateDisposableBindingCommand(parsed, options) {
|
|
|
5068
5098
|
},
|
|
5069
5099
|
worker: {
|
|
5070
5100
|
communication: disposableSessionCommunication("worker", task.name, dbPath),
|
|
5101
|
+
codex_app_thread_id: worker.codex_app_thread_id,
|
|
5102
|
+
codex_app_thread_title: worker.codex_app_thread_title,
|
|
5071
5103
|
id: worker.session_id,
|
|
5072
5104
|
name: workerName,
|
|
5073
5105
|
rollout_path: workerRollout.path,
|
|
5074
5106
|
tmux_session: null,
|
|
5075
5107
|
},
|
|
5108
|
+
heartbeat_recommendations: disposableHeartbeatRecommendations(task.name, dbPath),
|
|
5076
5109
|
worker_handoff: disposableWorkerHandoff(task.name, run?.name ?? null, dbPath),
|
|
5077
5110
|
};
|
|
5078
5111
|
if (parsed.flags.json) {
|
|
@@ -16581,6 +16614,9 @@ function disposableWorkerHandoff(taskName, runName, dbPath) {
|
|
|
16581
16614
|
`You are the worker for task ${taskName}${loopClause}.`,
|
|
16582
16615
|
"Keep polling your Conveyor worker inbox until there are no items left or the loop reaches max_iterations. Consume the next item now, treat each consumed item as the manager's next instruction, complete the requested work, and report changed files, exact commands run, evidence, and any residual risk.",
|
|
16583
16616
|
"",
|
|
16617
|
+
"Because this is a pull-required Codex app/no-tmux session, autonomous operation requires a heartbeat/wake layer that repeats this worker inbox poll while the thread is idle. If no heartbeat automation is available, report the loop as manual-poll only.",
|
|
16618
|
+
"Do not delete, pause, or disable heartbeat automation just because an inbox poll is idle; the manager or operator owns terminal loop teardown.",
|
|
16619
|
+
"",
|
|
16584
16620
|
`Run: ${sessionPollCommand("worker", taskName, dbPath)}`,
|
|
16585
16621
|
].join("\n");
|
|
16586
16622
|
}
|
|
@@ -16595,14 +16631,72 @@ function renderDisposableBindingText(result) {
|
|
|
16595
16631
|
}
|
|
16596
16632
|
lines.push("Replay commands:");
|
|
16597
16633
|
lines.push(...result.replay_commands.map((command) => ` ${command}`));
|
|
16634
|
+
if (result.heartbeat_recommendations) {
|
|
16635
|
+
lines.push("Heartbeat recommendations:");
|
|
16636
|
+
lines.push(` interval: every ${result.heartbeat_recommendations.interval_minutes} minutes`);
|
|
16637
|
+
lines.push(` manager: ${result.heartbeat_recommendations.manager.poll_command}`);
|
|
16638
|
+
lines.push(` worker: ${result.heartbeat_recommendations.worker.poll_command}`);
|
|
16639
|
+
lines.push(` teardown: ${result.heartbeat_recommendations.teardown_policy.idle_poll}`);
|
|
16640
|
+
lines.push(` closeout: ${result.heartbeat_recommendations.teardown_policy.terminal_closeout_command}`);
|
|
16641
|
+
}
|
|
16598
16642
|
lines.push("Worker handoff:");
|
|
16599
16643
|
lines.push(result.worker_handoff);
|
|
16600
16644
|
return `${lines.join("\n")}\n`;
|
|
16601
16645
|
}
|
|
16646
|
+
function disposableHeartbeatRecommendations(taskName, dbPath) {
|
|
16647
|
+
const terminalCloseoutCommand = `${conveyorPollInvocation()} finish-task ${shellQuote(taskName)} --reason ${shellQuote("Verified terminal closeout")} --require-criteria-audit --path ${shellQuote(dbPath)}`;
|
|
16648
|
+
return {
|
|
16649
|
+
applies_when: {
|
|
16650
|
+
can_receive_push: false,
|
|
16651
|
+
delivery_mode: "pull_required",
|
|
16652
|
+
receive_style: "pull",
|
|
16653
|
+
session_kind: "codex_app",
|
|
16654
|
+
},
|
|
16655
|
+
interval_minutes: 2,
|
|
16656
|
+
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.",
|
|
16657
|
+
teardown_policy: {
|
|
16658
|
+
idle_poll: "Never delete, pause, or disable a manager or worker heartbeat because an inbox poll returned no item; that is only a quiet poll interval.",
|
|
16659
|
+
owner: "manager_or_operator",
|
|
16660
|
+
terminal_closeout: "Only the manager or operator should tear down heartbeats, and only after a terminal manager decision plus verified task closeout, or after explicit operator instruction.",
|
|
16661
|
+
terminal_closeout_command: terminalCloseoutCommand,
|
|
16662
|
+
worker_rule: "The worker must not own loop teardown and must not remove heartbeat automation based on idle polling.",
|
|
16663
|
+
},
|
|
16664
|
+
manager: {
|
|
16665
|
+
kind: "thread_heartbeat",
|
|
16666
|
+
poll_command: sessionPollCommand("manager", taskName, dbPath),
|
|
16667
|
+
prompt: [
|
|
16668
|
+
"Use the manage-codex-workers skill.",
|
|
16669
|
+
`Poll the manager inbox for task ${taskName}.`,
|
|
16670
|
+
`Run: ${sessionPollCommand("manager", taskName, dbPath)}`,
|
|
16671
|
+
"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.",
|
|
16672
|
+
"If no item is consumed, stop after a one-line idle receipt.",
|
|
16673
|
+
"Do not delete, pause, or disable manager or worker heartbeat automation after an idle poll; an idle poll is only a quiet interval.",
|
|
16674
|
+
`If all accepted criteria are satisfied, deferred, or rejected and there is no next worker task, record the terminal manager decision, run or report the result of: ${terminalCloseoutCommand}`,
|
|
16675
|
+
"After verified task closeout, explicitly report heartbeat teardown status; if the task remains managed/active, report that as a control-plane blocker instead of calling the loop complete.",
|
|
16676
|
+
].join("\n"),
|
|
16677
|
+
},
|
|
16678
|
+
worker: {
|
|
16679
|
+
kind: "thread_heartbeat",
|
|
16680
|
+
poll_command: sessionPollCommand("worker", taskName, dbPath),
|
|
16681
|
+
prompt: [
|
|
16682
|
+
"Use the manage-codex-workers skill.",
|
|
16683
|
+
`Poll the worker inbox for task ${taskName}.`,
|
|
16684
|
+
`Run: ${sessionPollCommand("worker", taskName, dbPath)}`,
|
|
16685
|
+
"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.",
|
|
16686
|
+
"If no item is consumed, stop after a one-line idle receipt.",
|
|
16687
|
+
"Do not delete, pause, or disable worker heartbeat automation after an idle poll; the manager or operator owns terminal loop teardown.",
|
|
16688
|
+
].join("\n"),
|
|
16689
|
+
},
|
|
16690
|
+
};
|
|
16691
|
+
}
|
|
16602
16692
|
function sessionPollCommand(role, taskName, dbPath) {
|
|
16603
16693
|
const inbox = role === "worker" ? "worker-inbox" : "manager-inbox";
|
|
16604
16694
|
const task = taskName ? shellQuote(taskName) : "<task>";
|
|
16605
|
-
return
|
|
16695
|
+
return `${conveyorPollInvocation()} ${inbox} ${task} --consume-next --wait --timeout 60 --path ${shellQuote(dbPath)} --json`;
|
|
16696
|
+
}
|
|
16697
|
+
function conveyorPollInvocation() {
|
|
16698
|
+
const binDir = join(packageRootFromRuntimeModule(), "bin");
|
|
16699
|
+
return pathIsExecutable(join(binDir, "conveyor")) ? `PATH=${shellQuote(binDir)}:$PATH conveyor` : "conveyor";
|
|
16606
16700
|
}
|
|
16607
16701
|
function resolveCodexStartupOptions(options) {
|
|
16608
16702
|
const defaults = {
|