agent-conveyor 0.1.8 → 0.1.9

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/dist/index.d.ts CHANGED
@@ -7,6 +7,8 @@ export { configureConnectionSync, databaseHealthSync, initializeDatabaseSync, op
7
7
  export type { DatabaseCheck, DatabaseHealth } from "./state/database.js";
8
8
  export { taskAuditSync, TaskAuditError } from "./runtime/audit.js";
9
9
  export type { TaskAuditCorrelationChain, TaskAuditEvent, TaskAuditManagerDecision, TaskAuditResult, TaskAuditRoutedNotification, TaskAuditTask, } from "./runtime/audit.js";
10
+ export { appHeartbeatPollCommand, appLoopStatusCommand, appLoopStatusSync, appWakeupDispatchPlanSync, appWakeupPlanCommand, appWakeupPlanSync, directInboxPollCommand, } from "./runtime/app-autonomy.js";
11
+ export type { AppLoopDispatchState, AppLoopLeaseState, AppLoopNextAction, AppLoopRole, AppLoopRoleStatus, AppLoopStatus, AppWakeup, AppWakeupDispatchAction, AppWakeupDispatchActionStatus, AppWakeupDispatchPlan, AppWakeupPlan, } from "./runtime/app-autonomy.js";
10
12
  export { exportTaskAuditSubsetSync, exportTaskSync, TaskExportError } from "./runtime/export.js";
11
13
  export type { TaskExportManifest, TaskExportResult } from "./runtime/export.js";
12
14
  export { replayEntriesFromAudit, replayResultFromAudit } from "./runtime/replay.js";
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ export { captureMetaPath, configPath, defaultDbPath, eventsPath, loadJsonSync, s
3
3
  export { latestStatusSync } from "./state/status.js";
4
4
  export { configureConnectionSync, databaseHealthSync, initializeDatabaseSync, openDatabaseSync, WorkerctlDatabaseError, } from "./state/database.js";
5
5
  export { taskAuditSync, TaskAuditError } from "./runtime/audit.js";
6
+ export { appHeartbeatPollCommand, appLoopStatusCommand, appLoopStatusSync, appWakeupDispatchPlanSync, appWakeupPlanCommand, appWakeupPlanSync, directInboxPollCommand, } from "./runtime/app-autonomy.js";
6
7
  export { exportTaskAuditSubsetSync, exportTaskSync, TaskExportError } from "./runtime/export.js";
7
8
  export { replayEntriesFromAudit, replayResultFromAudit } from "./runtime/replay.js";
8
9
  export { classifyBusyWait, classifyStartupOutput } from "./runtime/classify.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EACL,eAAe,EACf,UAAU,EACV,aAAa,EACb,UAAU,EACV,YAAY,EACZ,SAAS,EACT,UAAU,EACV,cAAc,EACd,kBAAkB,EAClB,SAAS,EACT,mBAAmB,EACnB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,sBAAsB,EACtB,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AASnE,OAAO,EAAE,yBAAyB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEjG,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEpF,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAEhF,OAAO,EACL,6BAA6B,EAC7B,4BAA4B,EAC5B,iBAAiB,EACjB,wBAAwB,EACxB,uCAAuC,EACvC,8BAA8B,EAC9B,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,mCAAmC,EACnC,0BAA0B,EAC1B,+BAA+B,EAC/B,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,8BAA8B,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAEhG,OAAO,EACL,+BAA+B,EAC/B,yBAAyB,EACzB,wBAAwB,EACxB,2BAA2B,GAC5B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACL,+BAA+B,EAC/B,2CAA2C,EAC3C,gCAAgC,EAChC,4BAA4B,EAC5B,4BAA4B,EAC5B,2CAA2C,EAC3C,uBAAuB,EACvB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AAOpC,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAEhJ,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAEjH,OAAO,EACL,6BAA6B,EAC7B,qBAAqB,EACrB,iCAAiC,EACjC,sBAAsB,EACtB,gCAAgC,EAChC,iBAAiB,GAClB,MAAM,4BAA4B,CAAC;AASpC,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhG,OAAO,EACL,wBAAwB,EACxB,gBAAgB,EAChB,cAAc,EACd,+BAA+B,EAC/B,aAAa,EACb,cAAc,EACd,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,aAAa,EACb,6BAA6B,EAC7B,uBAAuB,EACvB,2BAA2B,EAC3B,kBAAkB,EAClB,iBAAiB,EACjB,aAAa,EACb,yBAAyB,EACzB,0BAA0B,EAC1B,WAAW,EACX,kBAAkB,EAClB,UAAU,GACX,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EACL,eAAe,EACf,UAAU,EACV,aAAa,EACb,UAAU,EACV,YAAY,EACZ,SAAS,EACT,UAAU,EACV,cAAc,EACd,kBAAkB,EAClB,SAAS,EACT,mBAAmB,EACnB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,sBAAsB,EACtB,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AASnE,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,oBAAoB,EACpB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,2BAA2B,CAAC;AAcnC,OAAO,EAAE,yBAAyB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEjG,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEpF,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAEhF,OAAO,EACL,6BAA6B,EAC7B,4BAA4B,EAC5B,iBAAiB,EACjB,wBAAwB,EACxB,uCAAuC,EACvC,8BAA8B,EAC9B,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,mCAAmC,EACnC,0BAA0B,EAC1B,+BAA+B,EAC/B,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,8BAA8B,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAEhG,OAAO,EACL,+BAA+B,EAC/B,yBAAyB,EACzB,wBAAwB,EACxB,2BAA2B,GAC5B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACL,+BAA+B,EAC/B,2CAA2C,EAC3C,gCAAgC,EAChC,4BAA4B,EAC5B,4BAA4B,EAC5B,2CAA2C,EAC3C,uBAAuB,EACvB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AAOpC,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAEhJ,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAEjH,OAAO,EACL,6BAA6B,EAC7B,qBAAqB,EACrB,iCAAiC,EACjC,sBAAsB,EACtB,gCAAgC,EAChC,iBAAiB,GAClB,MAAM,4BAA4B,CAAC;AASpC,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhG,OAAO,EACL,wBAAwB,EACxB,gBAAgB,EAChB,cAAc,EACd,+BAA+B,EAC/B,aAAa,EACb,cAAc,EACd,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,aAAa,EACb,6BAA6B,EAC7B,uBAAuB,EACvB,2BAA2B,EAC3B,kBAAkB,EAClB,iBAAiB,EACjB,aAAa,EACb,yBAAyB,EACzB,0BAA0B,EAC1B,WAAW,EACX,kBAAkB,EAClB,UAAU,GACX,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,109 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+ export type AppLoopLeaseState = "healthy" | "missing" | "stale";
3
+ export type AppLoopDispatchState = "healthy" | "missing" | "stale";
4
+ export type AppLoopRole = "manager" | "worker";
5
+ export interface AppLoopRoleStatus {
6
+ codex_app_thread_id: string | null;
7
+ codex_app_thread_title: string | null;
8
+ direct_inbox_command: string | null;
9
+ lease: {
10
+ age_seconds: number | null;
11
+ last_heartbeat_at: string | null;
12
+ stale_after_seconds: number;
13
+ state: AppLoopLeaseState;
14
+ };
15
+ name: string | null;
16
+ poll_command: string | null;
17
+ receive_style: "pull" | "push" | null;
18
+ session_id: string | null;
19
+ session_kind: "codex_app" | "no_tmux" | "tmux" | null;
20
+ }
21
+ export interface AppLoopNextAction {
22
+ kind: "start_dispatch" | "wake_manager" | "wake_worker";
23
+ prompt?: string;
24
+ reason: string;
25
+ role?: AppLoopRole;
26
+ }
27
+ export interface AppLoopStatus {
28
+ dispatch: {
29
+ dispatcher_id: string;
30
+ last_heartbeat_at: string | null;
31
+ state: AppLoopDispatchState;
32
+ };
33
+ manager: AppLoopRoleStatus;
34
+ next_actions: AppLoopNextAction[];
35
+ ok: boolean;
36
+ task: {
37
+ id: string;
38
+ name: string;
39
+ };
40
+ worker: AppLoopRoleStatus;
41
+ }
42
+ export interface AppWakeupPlan {
43
+ dispatcher: {
44
+ command: string;
45
+ required: boolean;
46
+ state: AppLoopDispatchState;
47
+ };
48
+ status: AppLoopStatus;
49
+ wakeups: AppWakeup[];
50
+ }
51
+ export interface AppWakeup {
52
+ prompt: string;
53
+ reason: string;
54
+ role: AppLoopRole;
55
+ thread: {
56
+ id: string | null;
57
+ title: string | null;
58
+ };
59
+ }
60
+ export type AppWakeupDispatchActionStatus = "blocked_missing_thread" | "ready_to_send" | "skipped_healthy";
61
+ export interface AppWakeupDispatchAction {
62
+ blocker: string | null;
63
+ prompt: string | null;
64
+ reason: string;
65
+ role: AppLoopRole;
66
+ send_ready: boolean;
67
+ status: AppWakeupDispatchActionStatus;
68
+ thread: {
69
+ id: string | null;
70
+ title: string | null;
71
+ };
72
+ }
73
+ export interface AppWakeupDispatchPlan {
74
+ actions: AppWakeupDispatchAction[];
75
+ dispatcher: AppWakeupPlan["dispatcher"];
76
+ status: AppLoopStatus;
77
+ summary: {
78
+ blocked: number;
79
+ dispatcher_required: boolean;
80
+ ready_to_send: number;
81
+ skipped: number;
82
+ total_actions: number;
83
+ };
84
+ }
85
+ export declare function appLoopStatusSync(database: DatabaseSync, options: {
86
+ dbPath?: string | null;
87
+ dispatcherId: string;
88
+ heartbeatStaleSeconds: number;
89
+ now: string;
90
+ taskName: string;
91
+ }): AppLoopStatus;
92
+ export declare function appWakeupPlanSync(database: DatabaseSync, options: {
93
+ dbPath?: string | null;
94
+ dispatcherId: string;
95
+ heartbeatStaleSeconds: number;
96
+ now: string;
97
+ taskName: string;
98
+ }): AppWakeupPlan;
99
+ export declare function appWakeupDispatchPlanSync(database: DatabaseSync, options: {
100
+ dbPath?: string | null;
101
+ dispatcherId: string;
102
+ heartbeatStaleSeconds: number;
103
+ now: string;
104
+ taskName: string;
105
+ }): AppWakeupDispatchPlan;
106
+ export declare function appHeartbeatPollCommand(role: AppLoopRole, taskName: string, dbPath?: string | null): string;
107
+ export declare function appLoopStatusCommand(taskName: string, dbPath?: string | null): string;
108
+ export declare function appWakeupPlanCommand(taskName: string, dbPath?: string | null): string;
109
+ export declare function directInboxPollCommand(role: AppLoopRole, taskName: string, dbPath?: string | null): string;
@@ -0,0 +1,263 @@
1
+ export function appLoopStatusSync(database, options) {
2
+ const task = database.prepare("select id, name from tasks where name = ?").get(options.taskName);
3
+ if (!task) {
4
+ throw new Error(`Task not found: ${options.taskName}`);
5
+ }
6
+ const binding = database.prepare(`
7
+ select ws.id as worker_session_id,
8
+ ws.name as worker_name,
9
+ ws.last_heartbeat_at as worker_last_heartbeat_at,
10
+ ws.codex_app_thread_id as worker_thread_id,
11
+ ws.codex_app_thread_title as worker_thread_title,
12
+ ws.tmux_session as worker_tmux_session,
13
+ ms.id as manager_session_id,
14
+ ms.name as manager_name,
15
+ ms.last_heartbeat_at as manager_last_heartbeat_at,
16
+ ms.codex_app_thread_id as manager_thread_id,
17
+ ms.codex_app_thread_title as manager_thread_title,
18
+ ms.tmux_session as manager_tmux_session
19
+ from bindings
20
+ left join sessions ws on ws.id = bindings.worker_session_id
21
+ left join sessions ms on ms.id = bindings.manager_session_id
22
+ where bindings.task_id = ? and bindings.state in ('active', 'ending')
23
+ order by bindings.created_at desc
24
+ limit 1
25
+ `).get(task.id);
26
+ if (!binding) {
27
+ throw new Error(`No active binding for task: ${options.taskName}`);
28
+ }
29
+ const dispatchHeartbeat = database.prepare(`
30
+ select timestamp
31
+ from telemetry_events
32
+ where actor = 'dispatch'
33
+ and event_type = 'dispatch_watch_heartbeat'
34
+ and json_extract(correlation_json, '$.dispatcher_id') = ?
35
+ order by timestamp desc, id desc
36
+ limit 1
37
+ `).get(options.dispatcherId);
38
+ const manager = roleStatus({
39
+ dbPath: options.dbPath ?? null,
40
+ heartbeatStaleSeconds: options.heartbeatStaleSeconds,
41
+ lastHeartbeatAt: binding.manager_last_heartbeat_at,
42
+ name: binding.manager_name,
43
+ now: options.now,
44
+ role: "manager",
45
+ sessionId: binding.manager_session_id,
46
+ taskName: options.taskName,
47
+ threadId: binding.manager_thread_id,
48
+ threadTitle: binding.manager_thread_title,
49
+ tmuxSession: binding.manager_tmux_session,
50
+ });
51
+ const worker = roleStatus({
52
+ dbPath: options.dbPath ?? null,
53
+ heartbeatStaleSeconds: options.heartbeatStaleSeconds,
54
+ lastHeartbeatAt: binding.worker_last_heartbeat_at,
55
+ name: binding.worker_name,
56
+ now: options.now,
57
+ role: "worker",
58
+ sessionId: binding.worker_session_id,
59
+ taskName: options.taskName,
60
+ threadId: binding.worker_thread_id,
61
+ threadTitle: binding.worker_thread_title,
62
+ tmuxSession: binding.worker_tmux_session,
63
+ });
64
+ const dispatchState = dispatchLeaseState(dispatchHeartbeat?.timestamp ?? null, options.now, options.heartbeatStaleSeconds);
65
+ const dispatch = {
66
+ dispatcher_id: options.dispatcherId,
67
+ last_heartbeat_at: dispatchHeartbeat?.timestamp ?? null,
68
+ state: dispatchState,
69
+ };
70
+ const nextActions = [];
71
+ if (dispatch.state !== "healthy") {
72
+ nextActions.push({
73
+ kind: "start_dispatch",
74
+ prompt: `Run: conveyor dispatch --watch --dispatcher-id ${shellQuote(options.dispatcherId)}${pathFlag(options.dbPath ?? null)}`,
75
+ reason: `Dispatch ${options.dispatcherId} is ${dispatch.state}.`,
76
+ });
77
+ }
78
+ if (manager.lease.state !== "healthy") {
79
+ nextActions.push({
80
+ kind: "wake_manager",
81
+ prompt: manager.poll_command ?? undefined,
82
+ reason: `Manager heartbeat is ${manager.lease.state}.`,
83
+ role: "manager",
84
+ });
85
+ }
86
+ if (worker.lease.state !== "healthy") {
87
+ nextActions.push({
88
+ kind: "wake_worker",
89
+ prompt: worker.poll_command ?? undefined,
90
+ reason: `Worker heartbeat is ${worker.lease.state}.`,
91
+ role: "worker",
92
+ });
93
+ }
94
+ return {
95
+ dispatch,
96
+ manager,
97
+ next_actions: nextActions,
98
+ ok: dispatch.state === "healthy" && manager.lease.state === "healthy" && worker.lease.state === "healthy",
99
+ task,
100
+ worker,
101
+ };
102
+ }
103
+ export function appWakeupPlanSync(database, options) {
104
+ const status = appLoopStatusSync(database, options);
105
+ const wakeups = [];
106
+ if (status.manager.lease.state !== "healthy") {
107
+ wakeups.push(roleWakeup("manager", status.manager, status.task.name, options.dbPath ?? null, status.manager.lease.state));
108
+ }
109
+ if (status.worker.lease.state !== "healthy") {
110
+ wakeups.push(roleWakeup("worker", status.worker, status.task.name, options.dbPath ?? null, status.worker.lease.state));
111
+ }
112
+ return {
113
+ dispatcher: {
114
+ command: `conveyor dispatch --watch --dispatcher-id ${shellQuote(options.dispatcherId)}${pathFlag(options.dbPath ?? null)}`,
115
+ required: status.dispatch.state !== "healthy",
116
+ state: status.dispatch.state,
117
+ },
118
+ status,
119
+ wakeups,
120
+ };
121
+ }
122
+ export function appWakeupDispatchPlanSync(database, options) {
123
+ const plan = appWakeupPlanSync(database, options);
124
+ const wakeupsByRole = new Map(plan.wakeups.map((wakeup) => [wakeup.role, wakeup]));
125
+ const actions = ["manager", "worker"].map((role) => {
126
+ const roleStatus = role === "manager" ? plan.status.manager : plan.status.worker;
127
+ const wakeup = wakeupsByRole.get(role);
128
+ if (!wakeup) {
129
+ return {
130
+ blocker: null,
131
+ prompt: null,
132
+ reason: `${role} heartbeat is healthy.`,
133
+ role,
134
+ send_ready: false,
135
+ status: "skipped_healthy",
136
+ thread: {
137
+ id: roleStatus.codex_app_thread_id,
138
+ title: roleStatus.codex_app_thread_title,
139
+ },
140
+ };
141
+ }
142
+ if (!wakeup.thread.id) {
143
+ return {
144
+ blocker: `No Codex app thread id is registered for the ${role}; use the prompt manually or register app thread metadata before calling send_message_to_thread.`,
145
+ prompt: wakeup.prompt,
146
+ reason: wakeup.reason,
147
+ role,
148
+ send_ready: false,
149
+ status: "blocked_missing_thread",
150
+ thread: wakeup.thread,
151
+ };
152
+ }
153
+ return {
154
+ blocker: null,
155
+ prompt: wakeup.prompt,
156
+ reason: wakeup.reason,
157
+ role,
158
+ send_ready: true,
159
+ status: "ready_to_send",
160
+ thread: wakeup.thread,
161
+ };
162
+ });
163
+ const ready = actions.filter((action) => action.status === "ready_to_send").length;
164
+ const blocked = actions.filter((action) => action.status === "blocked_missing_thread").length;
165
+ const skipped = actions.filter((action) => action.status === "skipped_healthy").length;
166
+ return {
167
+ actions,
168
+ dispatcher: plan.dispatcher,
169
+ status: plan.status,
170
+ summary: {
171
+ blocked,
172
+ dispatcher_required: plan.dispatcher.required,
173
+ ready_to_send: ready,
174
+ skipped,
175
+ total_actions: actions.length,
176
+ },
177
+ };
178
+ }
179
+ export function appHeartbeatPollCommand(role, taskName, dbPath) {
180
+ return `conveyor app-heartbeat ${shellQuote(taskName)} --role ${role}${pathFlag(dbPath ?? null)} --json`;
181
+ }
182
+ export function appLoopStatusCommand(taskName, dbPath) {
183
+ return `conveyor app-loop-status ${shellQuote(taskName)}${pathFlag(dbPath ?? null)} --json`;
184
+ }
185
+ export function appWakeupPlanCommand(taskName, dbPath) {
186
+ return `conveyor app-wakeup-plan ${shellQuote(taskName)}${pathFlag(dbPath ?? null)} --json`;
187
+ }
188
+ export function directInboxPollCommand(role, taskName, dbPath) {
189
+ const inbox = role === "manager" ? "manager-inbox" : "worker-inbox";
190
+ return `conveyor ${inbox} ${shellQuote(taskName)} --consume-next --wait --timeout 60${pathFlag(dbPath ?? null)} --json`;
191
+ }
192
+ function roleStatus(options) {
193
+ const hasTmux = Boolean(options.tmuxSession);
194
+ return {
195
+ codex_app_thread_id: options.threadId,
196
+ codex_app_thread_title: options.threadTitle,
197
+ direct_inbox_command: directInboxPollCommand(options.role, options.taskName, options.dbPath),
198
+ lease: {
199
+ age_seconds: ageSeconds(options.lastHeartbeatAt, options.now),
200
+ last_heartbeat_at: options.lastHeartbeatAt,
201
+ stale_after_seconds: options.heartbeatStaleSeconds,
202
+ state: leaseState(options.lastHeartbeatAt, options.now, options.heartbeatStaleSeconds),
203
+ },
204
+ name: options.name,
205
+ poll_command: appHeartbeatPollCommand(options.role, options.taskName, options.dbPath),
206
+ receive_style: hasTmux ? "push" : "pull",
207
+ session_id: options.sessionId,
208
+ session_kind: hasTmux ? "tmux" : options.threadId ? "codex_app" : "no_tmux",
209
+ };
210
+ }
211
+ function roleWakeup(role, status, taskName, dbPath, state) {
212
+ const pollCommand = appHeartbeatPollCommand(role, taskName, dbPath);
213
+ const directInboxCommand = directInboxPollCommand(role, taskName, dbPath);
214
+ const roleInstruction = role === "manager"
215
+ ? "If an item is consumed, verify worker claims before recording conclusions, require concrete evidence, update Conveyor state as appropriate, and produce exactly one next worker task."
216
+ : "If an item is consumed, execute only that single worker instruction and return exact commands, compact evidence, blockers or residual risk, and exactly one next recommended worker task.";
217
+ return {
218
+ prompt: [
219
+ "Use the manage-codex-workers skill.",
220
+ `This is the ${role} heartbeat wakeup for task ${taskName}.`,
221
+ `Run: ${pollCommand}`,
222
+ `If the heartbeat result asks for direct inbox polling, run: ${directInboxCommand}`,
223
+ roleInstruction,
224
+ "If no item is consumed, stop after a one-line idle receipt.",
225
+ "Idle polling is not completion and does not authorize heartbeat teardown.",
226
+ ].join("\n"),
227
+ reason: `${role} heartbeat is ${state}.`,
228
+ role,
229
+ thread: {
230
+ id: status.codex_app_thread_id,
231
+ title: status.codex_app_thread_title,
232
+ },
233
+ };
234
+ }
235
+ function dispatchLeaseState(value, now, staleSeconds) {
236
+ const state = leaseState(value, now, staleSeconds);
237
+ return state === "healthy" ? "healthy" : state === "stale" ? "stale" : "missing";
238
+ }
239
+ function leaseState(value, now, staleSeconds) {
240
+ if (!value) {
241
+ return "missing";
242
+ }
243
+ const age = ageSeconds(value, now);
244
+ return age !== null && age <= staleSeconds ? "healthy" : "stale";
245
+ }
246
+ function ageSeconds(value, now) {
247
+ if (!value) {
248
+ return null;
249
+ }
250
+ const parsedNow = Date.parse(now);
251
+ const parsedValue = Date.parse(value);
252
+ if (!Number.isFinite(parsedNow) || !Number.isFinite(parsedValue)) {
253
+ return null;
254
+ }
255
+ return Math.max(0, Math.floor((parsedNow - parsedValue) / 1000));
256
+ }
257
+ function pathFlag(dbPath) {
258
+ return dbPath ? ` --path ${shellQuote(dbPath)}` : "";
259
+ }
260
+ function shellQuote(value) {
261
+ return `'${value.replace(/'/g, "'\"'\"'")}'`;
262
+ }
263
+ //# sourceMappingURL=app-autonomy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-autonomy.js","sourceRoot":"","sources":["../../src/runtime/app-autonomy.ts"],"names":[],"mappings":"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"}
@@ -273,17 +273,47 @@ report heartbeat teardown status. If the task or binding still appears active,
273
273
  report that as a control-plane blocker instead of calling the loop complete.
274
274
 
275
275
  When the manager is running in the Codex app and thread tools are available,
276
- create the worker with fresh same-project `create_thread`, set a readable title
277
- with `set_thread_title`, and pass the resulting id/title into
278
- `conveyor create-disposable-binding` with
279
- `--worker-codex-app-thread-id` and `--worker-codex-app-thread-title`. Use
280
- `send_message_to_thread` only to deliver the generated `worker_handoff`
281
- bootstrap prompt; ongoing manager/worker communication should still be routed
276
+ create fresh same-project manager and worker threads with `create_thread`, set
277
+ readable titles, and pass both ids/titles into `conveyor
278
+ create-disposable-binding` with `--manager-codex-app-thread-id`,
279
+ `--manager-codex-app-thread-title`, `--worker-codex-app-thread-id`, and
280
+ `--worker-codex-app-thread-title`. Use `send_message_to_thread` for bootstrap
281
+ prompts only; ongoing manager/worker communication should still be routed
282
282
  through Dispatch and consumed from inboxes. If app thread tools are unavailable,
283
283
  open the worker session manually and paste the same `worker_handoff` prompt.
284
284
  Do not use `fork_thread` for this recipe unless the user explicitly wants a
285
285
  fork of the current conversation.
286
286
 
287
+ ### Codex App Native Manager/Worker Loop
288
+
289
+ ```bash
290
+ conveyor doctor
291
+ conveyor db-doctor
292
+ conveyor create-disposable-binding "$TASK" \
293
+ --worker "$WORKER" \
294
+ --manager "$MANAGER" \
295
+ --worker-codex-app-thread-id "$WORKER_THREAD_ID" \
296
+ --worker-codex-app-thread-title "$WORKER_THREAD_TITLE" \
297
+ --manager-codex-app-thread-id "$MANAGER_THREAD_ID" \
298
+ --manager-codex-app-thread-title "$MANAGER_THREAD_TITLE" \
299
+ --adversarial \
300
+ --json
301
+ conveyor dispatch --watch --dispatcher-id dispatch-local
302
+ conveyor app-heartbeat "$TASK" --role manager --json
303
+ conveyor app-heartbeat "$TASK" --role worker --json
304
+ conveyor app-loop-status "$TASK" --json
305
+ conveyor app-wakeup-plan "$TASK" --json
306
+ conveyor app-wakeup-dispatch "$TASK" --json
307
+ ```
308
+
309
+ Use `app-loop-status` as the operator status surface. If it reports stale
310
+ manager, worker, or Dispatch leases, use `app-wakeup-plan` to get the exact
311
+ thread prompt to send through Codex app automation or `send_message_to_thread`.
312
+ Use `app-wakeup-dispatch` when the manager needs a durable Conveyor receipt for
313
+ which app wake actions were prepared, skipped, or blocked. It only prepares
314
+ adapter-ready actions; direct app-thread delivery remains an app/operator
315
+ action, and Dispatch/inboxes remain the durable task state.
316
+
287
317
  The saved dogfood example is
288
318
  `docs/goals/live-codex-app-inbox-drill/notes/T001-live-drill.md`. It proves
289
319
  manager-to-worker `nudge_worker` delivery and worker-to-manager
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-conveyor",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Local agent manager/worker conveyor control plane for Codex sessions.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -35,25 +35,29 @@ Skill behavior:
35
35
  report blockers.
36
36
  3. Choose concise task, worker, manager, and run names when the user does not
37
37
  provide them. Do not ask the user to invent generated names.
38
- 4. If running in the Codex app and thread tools are available, create a fresh
39
- same-project worker thread with `create_thread`, name it with
40
- `set_thread_title`, and keep the returned thread id/title. Use
41
- `create_thread` for this flow; do not use `fork_thread` unless the user
42
- explicitly asks to fork or resume this exact conversation.
38
+ 4. Preferred fully app-native setup: if running in the Codex app and thread
39
+ tools are available, create fresh same-project manager and worker threads
40
+ with `create_thread`, give both readable titles, and keep both returned
41
+ thread ids/titles. Use `create_thread` for this flow; do not use
42
+ `fork_thread` unless the user explicitly asks to fork or resume this exact
43
+ conversation. If the current thread is explicitly acting as the manager,
44
+ use the current thread as manager and create only the worker thread.
43
45
  5. Create the no-tmux binding with `conveyor create-disposable-binding`
44
46
  using `--template` when a template is known, `--adversarial`, a bounded
45
- `--max-iterations`, and `--json`. When step 4 produced a worker thread id,
46
- pass it through `--worker-codex-app-thread-id` and
47
- `--worker-codex-app-thread-title` so Conveyor can surface the app identity
48
- in `sessions`, `discover`, and setup JSON. If app thread tools are not
49
- available, create the binding without those flags and ask the user to open a
50
- separate Codex app worker session manually.
47
+ `--max-iterations`, and `--json`. When step 4 produced app thread ids, pass
48
+ them through `--manager-codex-app-thread-id`,
49
+ `--manager-codex-app-thread-title`, `--worker-codex-app-thread-id`, and
50
+ `--worker-codex-app-thread-title` so Conveyor can surface app identities in
51
+ `sessions`, `discover`, and setup JSON. If app thread tools are not
52
+ available, create the binding without those flags and ask the user to open
53
+ separate Codex app sessions manually.
51
54
  6. Ensure Dispatch is running or tell the user the single command to start it:
52
55
  `conveyor dispatch --watch --dispatcher-id dispatch-local`.
53
56
  7. Read the returned `communication` blocks. A worker or manager with
54
57
  `session_kind=tmux` and `receive_style=push` can receive direct tmux pushes;
55
58
  one with `session_kind=codex_app` and `receive_style=pull` must poll the
56
- printed inbox command.
59
+ printed `app-heartbeat` command, using direct inbox commands only when the
60
+ heartbeat output or setup JSON provides them as fallbacks.
57
61
  8. Give the worker Codex app session the generated `worker_handoff` prompt.
58
62
  If step 4 created a fresh worker thread, use `send_message_to_thread` only
59
63
  to deliver that bootstrap prompt. Durable manager/worker communication still
@@ -72,10 +76,12 @@ Skill behavior:
72
76
  Idle polling rule for Codex app/no-tmux sessions:
73
77
 
74
78
  - When a worker has `session_kind=codex_app` or `receive_style=pull`, its
75
- idle/check-in command is the returned
76
- `communication.poll_command` or generated `worker_handoff` command.
79
+ default idle/check-in command is the returned `app-heartbeat` worker command.
80
+ Use `communication.poll_command` or the generated `worker_handoff` command as
81
+ fallback/direct inbox polling.
77
82
  - When a manager has `session_kind=codex_app` or `receive_style=pull`, its
78
- idle/check-in command is the returned `communication.poll_command`.
83
+ default idle/check-in command is the returned `app-heartbeat` manager command.
84
+ Use `communication.poll_command` as fallback/direct inbox polling.
79
85
  - The printed command may include a local `PATH=.../bin:$PATH conveyor` prefix;
80
86
  preserve that prefix when giving the command to a Codex app thread.
81
87
  - Repeat the appropriate command whenever the session is idle, after finishing
@@ -83,6 +89,13 @@ Idle polling rule for Codex app/no-tmux sessions:
83
89
  A timeout is not completion; it is only a quiet poll interval.
84
90
  - Keep `conveyor dispatch --watch --dispatcher-id dispatch-local` running so
85
91
  Dispatch can route new messages into those inboxes.
92
+ - Use `conveyor app-loop-status <task> --json` as the operator status surface,
93
+ and `conveyor app-wakeup-plan <task> --json` to produce exact stale-thread
94
+ wake prompts for Codex app automation or `send_message_to_thread`.
95
+ - Use `conveyor app-wakeup-dispatch <task> --json` when the manager needs an
96
+ auditable wake orchestration receipt. It prepares adapter-ready app-thread
97
+ actions, reports skipped and blocked roles, and records telemetry; it does
98
+ not send direct app-thread messages itself.
86
99
  - For bounded Ralph loops, treat `ralph_loop_iteration_advanced` telemetry as
87
100
  the receipt that a worker actually consumed and began the requested
88
101
  iteration.
@@ -727,15 +740,19 @@ etc.) run `conveyor db-doctor --live`.
727
740
  ## Natural-Language Command Mapping
728
741
 
729
742
  - "set up a Codex app Ralph loop": if the current session has Codex app thread
730
- tools, call `create_thread` for a fresh same-project worker, call
731
- `set_thread_title`, run `conveyor create-disposable-binding` with
732
- `--worker-codex-app-thread-id` and `--worker-codex-app-thread-title`, then
733
- send the returned `worker_handoff` using `send_message_to_thread`. If those
734
- tools are unavailable, run `create-disposable-binding` without thread
735
- metadata and give the user the `worker_handoff` prompt to paste into a
736
- manually opened worker. Keep Dispatch as the source of durable communication,
737
- and make both Codex app sessions repeat their role-specific inbox poll while
738
- idle.
743
+ tools, call `create_thread` for fresh same-project manager and worker
744
+ threads unless the current thread is explicitly the manager, set readable
745
+ titles, run `conveyor create-disposable-binding` with both
746
+ `--manager-codex-app-thread-id`/`--manager-codex-app-thread-title` and
747
+ `--worker-codex-app-thread-id`/`--worker-codex-app-thread-title`, then send
748
+ the returned bootstrap prompts using `send_message_to_thread`. If those tools
749
+ are unavailable, run `create-disposable-binding` without thread metadata and
750
+ give the user the handoff prompts to paste into manually opened sessions. Keep
751
+ Dispatch as the source of durable communication, and make both Codex app
752
+ sessions repeat their role-specific `app-heartbeat` command while idle. Use
753
+ `app-loop-status` and `app-wakeup-plan` for operator status and stale-thread
754
+ recovery, and `app-wakeup-dispatch` when the manager needs an auditable
755
+ prepared/skipped/blocked wake receipt.
739
756
  - "register this Codex session as the worker for dashboard setup <CODE>":
740
757
  derive `dashboard-<CODE>-worker`, run `conveyor doctor-self`, then
741
758
  `conveyor register-worker --name dashboard-<CODE>-worker --pid <PID> --cwd <CWD> --tmux-session <SESSION>`.