agent-conveyor 0.1.14 → 0.1.16
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 +83 -5
- package/dist/cli/typescript-runtime.js +760 -8
- package/dist/cli/typescript-runtime.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime/campaigns.d.ts +198 -0
- package/dist/runtime/campaigns.js +454 -0
- package/dist/runtime/campaigns.js.map +1 -0
- package/dist/state/schema-v23.js +87 -1
- package/dist/state/schema-v23.js.map +1 -1
- package/dist/state/sqlite-contract.d.ts +1 -1
- package/dist/state/sqlite-contract.js +13 -1
- package/dist/state/sqlite-contract.js.map +1 -1
- package/docs/manager-recipes.md +102 -0
- package/package.json +1 -1
- package/skills/manage-codex-workers/SKILL.md +67 -0
- package/skills/manage-codex-workers/agents/openai.yaml +2 -2
|
@@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import { taskAuditSync } from "../runtime/audit.js";
|
|
8
8
|
import { appAutopilotPlanSync, appLoopStatusSync, appWakeupDispatchPlanSync, appWakeupPlanSync, directInboxPollCommand, visibleSessionProtocolLines, } from "../runtime/app-autonomy.js";
|
|
9
9
|
import { classifyBusyWait, classifyStartupOutput } from "../runtime/classify.js";
|
|
10
|
+
import { addCampaignWorkerSlotSync, campaignDashboardSync, campaignStatusSync, createCampaignAssignmentSync, createCampaignSync, recordCampaignAssetReceiptSync, updateCampaignWorkerSlotLifecycleSync, upsertCampaignChannelBriefSync, } from "../runtime/campaigns.js";
|
|
10
11
|
import { exportTaskSync } from "../runtime/export.js";
|
|
11
12
|
import { ingestSessionSync } from "../runtime/ingest.js";
|
|
12
13
|
import { acceptanceCriteriaForTaskSync, loopEvidenceCriterion, recordAdversarialLoopEvidenceSync, recordLoopEvidenceSync, recordVisualDiffLoopEvidenceSync, } from "../runtime/loop-evidence.js";
|
|
@@ -134,6 +135,12 @@ export function runTypescriptRuntimeCommand(options) {
|
|
|
134
135
|
if (parsed.command === "app-wakeup-record-delivery") {
|
|
135
136
|
return runAppWakeupRecordDeliveryCommand(parsed, options);
|
|
136
137
|
}
|
|
138
|
+
if (parsed.command === "app-worker-rotation-plan") {
|
|
139
|
+
return runAppWorkerRotationPlanCommand(parsed, options);
|
|
140
|
+
}
|
|
141
|
+
if (parsed.command === "app-worker-rotation-record") {
|
|
142
|
+
return runAppWorkerRotationRecordCommand(parsed, options);
|
|
143
|
+
}
|
|
137
144
|
if (parsed.command === "app-autopilot") {
|
|
138
145
|
return runAppAutopilotCommand(parsed, options);
|
|
139
146
|
}
|
|
@@ -146,6 +153,9 @@ export function runTypescriptRuntimeCommand(options) {
|
|
|
146
153
|
if (parsed.command === "tasks") {
|
|
147
154
|
return runTasksCommand(parsed, options);
|
|
148
155
|
}
|
|
156
|
+
if (parsed.command === "campaign") {
|
|
157
|
+
return runCampaignCommand(parsed, options);
|
|
158
|
+
}
|
|
149
159
|
if (parsed.command === "start") {
|
|
150
160
|
return runLegacyStartCommand(parsed, options);
|
|
151
161
|
}
|
|
@@ -373,6 +383,22 @@ function commandHelpText(program, command) {
|
|
|
373
383
|
"Examples:",
|
|
374
384
|
` ${program} finish-task my-task --reason "Accepted criteria satisfied" --require-criteria-audit --path /tmp/work/workerctl.db --json`,
|
|
375
385
|
],
|
|
386
|
+
campaign: [
|
|
387
|
+
`usage: ${program} campaign <create|add-slot|attach-slot|rotate-slot|archive-slot|brief|assign|asset|status|dashboard> --name <campaign> [options] ${path} [--json]`,
|
|
388
|
+
"",
|
|
389
|
+
"Examples:",
|
|
390
|
+
` ${program} campaign create --name launch --objective "Create launch assets" --metadata-json '{"owner":"ops"}' --json`,
|
|
391
|
+
` ${program} campaign add-slot --name launch --slot-key tiktok --role-label "TikTok worker" --channel tiktok --thread-id thread-1 --state active --json`,
|
|
392
|
+
` ${program} campaign attach-slot --name launch --slot campaign-slot-id --session-id session-worker --thread-id thread-1 --state active --json`,
|
|
393
|
+
` ${program} campaign rotate-slot --name launch --slot campaign-slot-id --expected-thread-id thread-1 --thread-id thread-2 --thread-title "TikTok Worker 2" --json`,
|
|
394
|
+
` ${program} campaign archive-slot --name launch --slot campaign-slot-id --expected-thread-id thread-2 --json`,
|
|
395
|
+
` ${program} campaign brief --name launch --channel tiktok --brief-json '{"format":"9:16"}' --json`,
|
|
396
|
+
` ${program} campaign assign --name launch --slot campaign-slot-id --title "Draft hooks" --instructions "Create hooks" --status active --json`,
|
|
397
|
+
` ${program} campaign asset --name launch --slot campaign-slot-id --assignment campaign-assignment-id --asset-type copy --title "Hooks v1" --status needs_review --json`,
|
|
398
|
+
` ${program} campaign asset --name launch --slot campaign-slot-id --assignment campaign-assignment-id --asset-type copy --title "Hooks v2" --allow-additional-receipt --json`,
|
|
399
|
+
` ${program} campaign status --name launch --json`,
|
|
400
|
+
` ${program} campaign dashboard --name launch --json`,
|
|
401
|
+
],
|
|
376
402
|
"manager-ack": [
|
|
377
403
|
`usage: ${program} manager-ack <task> --from-stdin ${path}`,
|
|
378
404
|
`usage: ${program} manager-ack <task> --json ${path}`,
|
|
@@ -399,6 +425,21 @@ function commandHelpText(program, command) {
|
|
|
399
425
|
` ${program} app-autopilot status dogfood --path /tmp/work/workerctl.db`,
|
|
400
426
|
` ${program} app-autopilot stop dogfood --path /tmp/work/workerctl.db --json`,
|
|
401
427
|
],
|
|
428
|
+
"app-worker-rotation-plan": [
|
|
429
|
+
`usage: ${program} app-worker-rotation-plan <task> --old-worker-thread-id ID [--require-handoff] [--reason TEXT] ${path} [--json]`,
|
|
430
|
+
"",
|
|
431
|
+
"Emit Codex app actions for replacing a worker thread with a fresh thread.",
|
|
432
|
+
"The plan fails closed unless the old thread id exactly matches the active bound worker session; it never authorizes archiving a manager or unrelated thread.",
|
|
433
|
+
"",
|
|
434
|
+
"Examples:",
|
|
435
|
+
` ${program} app-worker-rotation-plan dogfood --old-worker-thread-id thread-old --require-handoff --path /tmp/work/workerctl.db --json`,
|
|
436
|
+
],
|
|
437
|
+
"app-worker-rotation-record": [
|
|
438
|
+
`usage: ${program} app-worker-rotation-record <task> --old-worker-thread-id OLD --new-worker-thread-id NEW [--new-worker-thread-title TITLE] --archive-status archived|blocked [--reason TEXT] ${path} [--json]`,
|
|
439
|
+
"",
|
|
440
|
+
"Record the Codex app worker-thread rotation after the app layer creates the new worker thread and archives or blocks on the old one.",
|
|
441
|
+
"The command re-checks active binding ownership before updating the worker session to the new thread id.",
|
|
442
|
+
],
|
|
402
443
|
pair: [
|
|
403
444
|
`usage: ${program} pair --task <task> --worker-name <worker> --manager-name <manager> [options] ${path}`,
|
|
404
445
|
"",
|
|
@@ -431,6 +472,26 @@ function commandHelpText(program, command) {
|
|
|
431
472
|
};
|
|
432
473
|
return linesByCommand[command] ?? [`usage: ${program} ${command} [-h] [options]`];
|
|
433
474
|
}
|
|
475
|
+
const CAMPAIGN_ACTIONS = new Set(["create", "add-slot", "attach-slot", "rotate-slot", "archive-slot", "brief", "assign", "asset", "status", "dashboard"]);
|
|
476
|
+
const CAMPAIGN_STRING_FLAGS = {
|
|
477
|
+
"--artifact-path": "artifactPath",
|
|
478
|
+
"--asset-type": "assetType",
|
|
479
|
+
"--assignment": "assignment",
|
|
480
|
+
"--brief-json": "briefJson",
|
|
481
|
+
"--channel": "channel",
|
|
482
|
+
"--expected-thread-id": "expectedThreadId",
|
|
483
|
+
"--instructions": "instructions",
|
|
484
|
+
"--objective": "objective",
|
|
485
|
+
"--prompt-summary": "promptSummary",
|
|
486
|
+
"--review-notes": "reviewNotes",
|
|
487
|
+
"--role-label": "roleLabel",
|
|
488
|
+
"--session-id": "sessionId",
|
|
489
|
+
"--slot": "slot",
|
|
490
|
+
"--slot-key": "slotKey",
|
|
491
|
+
"--thread-id": "threadId",
|
|
492
|
+
"--thread-title": "threadTitle",
|
|
493
|
+
"--title": "title",
|
|
494
|
+
};
|
|
434
495
|
function parseRuntimeArgs(args, env) {
|
|
435
496
|
const flags = {
|
|
436
497
|
format: "timeline",
|
|
@@ -438,9 +499,14 @@ function parseRuntimeArgs(args, env) {
|
|
|
438
499
|
includeFullTranscripts: false,
|
|
439
500
|
includeTranscripts: false,
|
|
440
501
|
all: false,
|
|
502
|
+
allowAdditionalReceipt: false,
|
|
441
503
|
action: null,
|
|
504
|
+
artifactPath: null,
|
|
505
|
+
assetType: null,
|
|
506
|
+
assignment: null,
|
|
442
507
|
asRole: "all",
|
|
443
508
|
attempts: false,
|
|
509
|
+
briefJson: null,
|
|
444
510
|
json: false,
|
|
445
511
|
activeOnly: false,
|
|
446
512
|
actor: null,
|
|
@@ -454,7 +520,9 @@ function parseRuntimeArgs(args, env) {
|
|
|
454
520
|
blocker: null,
|
|
455
521
|
busyWaitSeconds: DEFAULT_BUSY_WAIT_SECONDS,
|
|
456
522
|
candidate: null,
|
|
523
|
+
campaignName: null,
|
|
457
524
|
check: null,
|
|
525
|
+
channel: null,
|
|
458
526
|
classifyPrompt: null,
|
|
459
527
|
workerCodexAppThreadId: null,
|
|
460
528
|
workerCodexAppThreadTitle: null,
|
|
@@ -480,6 +548,7 @@ function parseRuntimeArgs(args, env) {
|
|
|
480
548
|
epilogueStatus: false,
|
|
481
549
|
epilogueStep: null,
|
|
482
550
|
eventType: null,
|
|
551
|
+
expectedThreadId: null,
|
|
483
552
|
failureMode: null,
|
|
484
553
|
file: null,
|
|
485
554
|
finishRun: null,
|
|
@@ -487,6 +556,7 @@ function parseRuntimeArgs(args, env) {
|
|
|
487
556
|
fromText: null,
|
|
488
557
|
fromWorkerResponse: null,
|
|
489
558
|
goal: null,
|
|
559
|
+
instructions: null,
|
|
490
560
|
keepLatest: 20,
|
|
491
561
|
key: "C-c",
|
|
492
562
|
list: false,
|
|
@@ -512,7 +582,10 @@ function parseRuntimeArgs(args, env) {
|
|
|
512
582
|
require: false,
|
|
513
583
|
requireHandoff: false,
|
|
514
584
|
result: null,
|
|
585
|
+
reviewNotes: null,
|
|
586
|
+
roleLabel: null,
|
|
515
587
|
review: false,
|
|
588
|
+
sessionId: null,
|
|
516
589
|
sessionRole: null,
|
|
517
590
|
sessionState: null,
|
|
518
591
|
show: null,
|
|
@@ -523,9 +596,13 @@ function parseRuntimeArgs(args, env) {
|
|
|
523
596
|
statuses: [],
|
|
524
597
|
statusStaleSeconds: DEFAULT_STATUS_STALE_SECONDS,
|
|
525
598
|
submitRole: null,
|
|
599
|
+
slot: null,
|
|
600
|
+
slotKey: null,
|
|
526
601
|
subtype: null,
|
|
527
602
|
summary: null,
|
|
528
603
|
source: null,
|
|
604
|
+
objective: null,
|
|
605
|
+
promptSummary: null,
|
|
529
606
|
proof: null,
|
|
530
607
|
purpose: null,
|
|
531
608
|
quietAfterCycles: 3,
|
|
@@ -568,8 +645,14 @@ function parseRuntimeArgs(args, env) {
|
|
|
568
645
|
dispatchReceipt: null,
|
|
569
646
|
force: false,
|
|
570
647
|
message: null,
|
|
648
|
+
newWorkerThreadId: null,
|
|
649
|
+
newWorkerThreadTitle: null,
|
|
650
|
+
oldWorkerThreadId: null,
|
|
571
651
|
reason: null,
|
|
572
652
|
threadId: null,
|
|
653
|
+
threadTitle: null,
|
|
654
|
+
title: null,
|
|
655
|
+
archiveStatus: null,
|
|
573
656
|
consumeNext: false,
|
|
574
657
|
wait: false,
|
|
575
658
|
requireAcks: false,
|
|
@@ -686,6 +769,12 @@ function parseRuntimeArgs(args, env) {
|
|
|
686
769
|
else if (arg === "--all") {
|
|
687
770
|
flags.all = true;
|
|
688
771
|
}
|
|
772
|
+
else if (arg === "--allow-additional-receipt") {
|
|
773
|
+
if (command !== "campaign") {
|
|
774
|
+
return { command, enabled, error: "Unsupported TypeScript runtime option: --allow-additional-receipt", explicit, flags, passthroughArgs, task };
|
|
775
|
+
}
|
|
776
|
+
flags.allowAdditionalReceipt = true;
|
|
777
|
+
}
|
|
689
778
|
else if (arg === "--active") {
|
|
690
779
|
flags.active = true;
|
|
691
780
|
}
|
|
@@ -937,7 +1026,7 @@ function parseRuntimeArgs(args, env) {
|
|
|
937
1026
|
}
|
|
938
1027
|
}
|
|
939
1028
|
else if (arg === "--require-handoff") {
|
|
940
|
-
if (command !== "manager-permission") {
|
|
1029
|
+
if (command !== "manager-permission" && command !== "app-worker-rotation-plan") {
|
|
941
1030
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --require-handoff", explicit, flags, task };
|
|
942
1031
|
}
|
|
943
1032
|
flags.requireHandoff = true;
|
|
@@ -1037,6 +1126,17 @@ function parseRuntimeArgs(args, env) {
|
|
|
1037
1126
|
flags.workerctlPath = value.value;
|
|
1038
1127
|
index += 1;
|
|
1039
1128
|
}
|
|
1129
|
+
else if (arg === "--campaign") {
|
|
1130
|
+
if (command !== "dashboard") {
|
|
1131
|
+
return { command, enabled, error: "Unsupported TypeScript runtime option: --campaign", explicit, flags, task };
|
|
1132
|
+
}
|
|
1133
|
+
const value = valueAfter(queue, index, arg);
|
|
1134
|
+
if (value.error) {
|
|
1135
|
+
return { command, enabled, error: value.error, explicit, flags, task };
|
|
1136
|
+
}
|
|
1137
|
+
flags.campaignName = value.value;
|
|
1138
|
+
index += 1;
|
|
1139
|
+
}
|
|
1040
1140
|
else if (arg === "--output") {
|
|
1041
1141
|
const value = valueAfter(queue, index, arg);
|
|
1042
1142
|
if (value.error) {
|
|
@@ -1166,7 +1266,7 @@ function parseRuntimeArgs(args, env) {
|
|
|
1166
1266
|
index += 1;
|
|
1167
1267
|
}
|
|
1168
1268
|
else if (arg === "--metadata-json") {
|
|
1169
|
-
if (command !== "runs" && command !== "loop-evidence") {
|
|
1269
|
+
if (command !== "runs" && command !== "loop-evidence" && command !== "campaign") {
|
|
1170
1270
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --metadata-json", explicit, flags, task };
|
|
1171
1271
|
}
|
|
1172
1272
|
const value = valueAfter(queue, index, arg);
|
|
@@ -1232,6 +1332,14 @@ function parseRuntimeArgs(args, env) {
|
|
|
1232
1332
|
flags.names.push(value.value);
|
|
1233
1333
|
index += 1;
|
|
1234
1334
|
}
|
|
1335
|
+
else if (command === "campaign" && Object.prototype.hasOwnProperty.call(CAMPAIGN_STRING_FLAGS, arg)) {
|
|
1336
|
+
const value = valueAfter(queue, index, arg);
|
|
1337
|
+
if (value.error) {
|
|
1338
|
+
return { command, enabled, error: value.error, explicit, flags, task };
|
|
1339
|
+
}
|
|
1340
|
+
flags[CAMPAIGN_STRING_FLAGS[arg]] = value.value;
|
|
1341
|
+
index += 1;
|
|
1342
|
+
}
|
|
1235
1343
|
else if (arg === "--pid") {
|
|
1236
1344
|
const parsedValue = valueAfter(queue, index, arg);
|
|
1237
1345
|
if (parsedValue.error) {
|
|
@@ -1668,14 +1776,19 @@ function parseRuntimeArgs(args, env) {
|
|
|
1668
1776
|
index += 1;
|
|
1669
1777
|
}
|
|
1670
1778
|
else if (arg === "--objective") {
|
|
1671
|
-
if (command !== "manager-config") {
|
|
1779
|
+
if (command !== "manager-config" && command !== "campaign") {
|
|
1672
1780
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --objective", explicit, flags, task };
|
|
1673
1781
|
}
|
|
1674
1782
|
const value = valueAfter(queue, index, arg);
|
|
1675
1783
|
if (value.error) {
|
|
1676
1784
|
return { command, enabled, error: value.error, explicit, flags, task };
|
|
1677
1785
|
}
|
|
1678
|
-
|
|
1786
|
+
if (command === "manager-config") {
|
|
1787
|
+
flags.managerObjective = value.value;
|
|
1788
|
+
}
|
|
1789
|
+
else {
|
|
1790
|
+
flags.objective = value.value;
|
|
1791
|
+
}
|
|
1679
1792
|
index += 1;
|
|
1680
1793
|
}
|
|
1681
1794
|
else if (arg === "--recipe") {
|
|
@@ -1855,7 +1968,13 @@ function parseRuntimeArgs(args, env) {
|
|
|
1855
1968
|
index = queue.length;
|
|
1856
1969
|
}
|
|
1857
1970
|
else if (arg === "--reason") {
|
|
1858
|
-
if (command !== "finish-task"
|
|
1971
|
+
if (command !== "finish-task"
|
|
1972
|
+
&& command !== "stop-task"
|
|
1973
|
+
&& command !== "record-decision"
|
|
1974
|
+
&& command !== "compact-worker"
|
|
1975
|
+
&& command !== "app-wakeup-record-delivery"
|
|
1976
|
+
&& command !== "app-worker-rotation-plan"
|
|
1977
|
+
&& command !== "app-worker-rotation-record") {
|
|
1859
1978
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --reason", explicit, flags, task };
|
|
1860
1979
|
}
|
|
1861
1980
|
const value = valueAfter(queue, index, arg);
|
|
@@ -1887,8 +2006,19 @@ function parseRuntimeArgs(args, env) {
|
|
|
1887
2006
|
flags.deliveryStatus = value.value;
|
|
1888
2007
|
index += 1;
|
|
1889
2008
|
}
|
|
2009
|
+
else if (arg === "--archive-status") {
|
|
2010
|
+
if (command !== "app-worker-rotation-record") {
|
|
2011
|
+
return { command, enabled, error: "Unsupported TypeScript runtime option: --archive-status", explicit, flags, task };
|
|
2012
|
+
}
|
|
2013
|
+
const value = valueAfter(queue, index, arg);
|
|
2014
|
+
if (value.error) {
|
|
2015
|
+
return { command, enabled, error: value.error, explicit, flags, task };
|
|
2016
|
+
}
|
|
2017
|
+
flags.archiveStatus = value.value;
|
|
2018
|
+
index += 1;
|
|
2019
|
+
}
|
|
1890
2020
|
else if (arg === "--thread-id") {
|
|
1891
|
-
if (command !== "app-wakeup-record-delivery") {
|
|
2021
|
+
if (command !== "app-wakeup-record-delivery" && command !== "campaign") {
|
|
1892
2022
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --thread-id", explicit, flags, task };
|
|
1893
2023
|
}
|
|
1894
2024
|
const value = valueAfter(queue, index, arg);
|
|
@@ -1898,6 +2028,28 @@ function parseRuntimeArgs(args, env) {
|
|
|
1898
2028
|
flags.threadId = value.value;
|
|
1899
2029
|
index += 1;
|
|
1900
2030
|
}
|
|
2031
|
+
else if (arg === "--old-worker-thread-id" || arg === "--new-worker-thread-id" || arg === "--new-worker-thread-title") {
|
|
2032
|
+
if (command !== "app-worker-rotation-plan" && command !== "app-worker-rotation-record") {
|
|
2033
|
+
return { command, enabled, error: `Unsupported TypeScript runtime option: ${arg}`, explicit, flags, task };
|
|
2034
|
+
}
|
|
2035
|
+
if (arg !== "--old-worker-thread-id" && command !== "app-worker-rotation-record") {
|
|
2036
|
+
return { command, enabled, error: `Unsupported TypeScript runtime option: ${arg}`, explicit, flags, task };
|
|
2037
|
+
}
|
|
2038
|
+
const value = valueAfter(queue, index, arg);
|
|
2039
|
+
if (value.error) {
|
|
2040
|
+
return { command, enabled, error: value.error, explicit, flags, task };
|
|
2041
|
+
}
|
|
2042
|
+
if (arg === "--old-worker-thread-id") {
|
|
2043
|
+
flags.oldWorkerThreadId = value.value;
|
|
2044
|
+
}
|
|
2045
|
+
else if (arg === "--new-worker-thread-id") {
|
|
2046
|
+
flags.newWorkerThreadId = value.value;
|
|
2047
|
+
}
|
|
2048
|
+
else {
|
|
2049
|
+
flags.newWorkerThreadTitle = value.value;
|
|
2050
|
+
}
|
|
2051
|
+
index += 1;
|
|
2052
|
+
}
|
|
1901
2053
|
else if (arg === "--message") {
|
|
1902
2054
|
if (command !== "finish-task"
|
|
1903
2055
|
&& command !== "stop-task"
|
|
@@ -2189,6 +2341,9 @@ function parseRuntimeArgs(args, env) {
|
|
|
2189
2341
|
}
|
|
2190
2342
|
flags.statusState = value;
|
|
2191
2343
|
}
|
|
2344
|
+
else if (command === "campaign") {
|
|
2345
|
+
flags.statusState = value;
|
|
2346
|
+
}
|
|
2192
2347
|
else if (!isSessionState(value)) {
|
|
2193
2348
|
return { command, enabled, error: `Unsupported sessions state: ${value}`, explicit, flags, task };
|
|
2194
2349
|
}
|
|
@@ -2202,7 +2357,7 @@ function parseRuntimeArgs(args, env) {
|
|
|
2202
2357
|
flags.epilogueStatus = true;
|
|
2203
2358
|
continue;
|
|
2204
2359
|
}
|
|
2205
|
-
if (command !== "criteria" && command !== "runs" && command !== "loop-evidence") {
|
|
2360
|
+
if (command !== "criteria" && command !== "runs" && command !== "loop-evidence" && command !== "campaign") {
|
|
2206
2361
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --status", explicit, flags, task };
|
|
2207
2362
|
}
|
|
2208
2363
|
const parsedValue = valueAfter(queue, index, arg);
|
|
@@ -2865,6 +3020,12 @@ function parseRuntimeArgs(args, env) {
|
|
|
2865
3020
|
}
|
|
2866
3021
|
flags.action = arg;
|
|
2867
3022
|
}
|
|
3023
|
+
else if (command === "campaign" && flags.action === null) {
|
|
3024
|
+
if (!CAMPAIGN_ACTIONS.has(arg)) {
|
|
3025
|
+
return { command, enabled, error: `Unsupported campaign action: ${arg}`, explicit, flags, task };
|
|
3026
|
+
}
|
|
3027
|
+
flags.action = arg;
|
|
3028
|
+
}
|
|
2868
3029
|
else if ((command === "qa-plan" || command === "qa-run") && flags.subtype === null) {
|
|
2869
3030
|
flags.subtype = arg;
|
|
2870
3031
|
}
|
|
@@ -3752,6 +3913,319 @@ function runAppWakeupRecordDeliveryCommand(parsed, options) {
|
|
|
3752
3913
|
database.close();
|
|
3753
3914
|
}
|
|
3754
3915
|
}
|
|
3916
|
+
function runAppWorkerRotationPlanCommand(parsed, options) {
|
|
3917
|
+
const taskName = requireTask(parsed);
|
|
3918
|
+
if (!parsed.flags.oldWorkerThreadId) {
|
|
3919
|
+
return errorResult("app-worker-rotation-plan requires --old-worker-thread-id.");
|
|
3920
|
+
}
|
|
3921
|
+
const database = openRuntimeDatabase(parsed, options);
|
|
3922
|
+
try {
|
|
3923
|
+
const timestamp = nowIsoSeconds(options);
|
|
3924
|
+
const dbPath = runtimeDbPath(parsed, options);
|
|
3925
|
+
const plan = appWorkerRotationPlanSync(database, {
|
|
3926
|
+
dbPath,
|
|
3927
|
+
now: timestamp,
|
|
3928
|
+
oldWorkerThreadId: parsed.flags.oldWorkerThreadId,
|
|
3929
|
+
reason: parsed.flags.reason,
|
|
3930
|
+
requireHandoff: parsed.flags.requireHandoff,
|
|
3931
|
+
taskName,
|
|
3932
|
+
});
|
|
3933
|
+
const eventId = emitTelemetrySync(database, {
|
|
3934
|
+
actor: "manager",
|
|
3935
|
+
attributes: {
|
|
3936
|
+
actions: plan.actions.map((action) => ({
|
|
3937
|
+
status: action.status,
|
|
3938
|
+
thread_id: action.thread.id,
|
|
3939
|
+
type: action.type,
|
|
3940
|
+
})),
|
|
3941
|
+
blockers: plan.blockers,
|
|
3942
|
+
eligible: plan.eligible,
|
|
3943
|
+
handoff_id: plan.handoff?.id ?? null,
|
|
3944
|
+
old_worker_thread_id: parsed.flags.oldWorkerThreadId,
|
|
3945
|
+
reason: parsed.flags.reason,
|
|
3946
|
+
},
|
|
3947
|
+
correlation: {
|
|
3948
|
+
command: "app-worker-rotation-plan",
|
|
3949
|
+
old_worker_thread_id: parsed.flags.oldWorkerThreadId,
|
|
3950
|
+
},
|
|
3951
|
+
eventType: "app_worker_rotation_planned",
|
|
3952
|
+
severity: plan.eligible ? "info" : "warning",
|
|
3953
|
+
summary: `Codex app worker rotation ${plan.eligible ? "planned" : "blocked"} for ${plan.task.name}.`,
|
|
3954
|
+
taskId: plan.task.id,
|
|
3955
|
+
timestamp,
|
|
3956
|
+
});
|
|
3957
|
+
const output = {
|
|
3958
|
+
...plan,
|
|
3959
|
+
receipt: {
|
|
3960
|
+
event_id: eventId,
|
|
3961
|
+
event_type: "app_worker_rotation_planned",
|
|
3962
|
+
recorded_at: timestamp,
|
|
3963
|
+
},
|
|
3964
|
+
};
|
|
3965
|
+
if (parsed.flags.json) {
|
|
3966
|
+
return jsonResult(output);
|
|
3967
|
+
}
|
|
3968
|
+
return {
|
|
3969
|
+
exitCode: 0,
|
|
3970
|
+
handled: true,
|
|
3971
|
+
stdout: renderAppWorkerRotationPlanText(output),
|
|
3972
|
+
};
|
|
3973
|
+
}
|
|
3974
|
+
finally {
|
|
3975
|
+
database.close();
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
function runAppWorkerRotationRecordCommand(parsed, options) {
|
|
3979
|
+
const taskName = requireTask(parsed);
|
|
3980
|
+
if (!parsed.flags.oldWorkerThreadId) {
|
|
3981
|
+
return errorResult("app-worker-rotation-record requires --old-worker-thread-id.");
|
|
3982
|
+
}
|
|
3983
|
+
if (!parsed.flags.newWorkerThreadId) {
|
|
3984
|
+
return errorResult("app-worker-rotation-record requires --new-worker-thread-id.");
|
|
3985
|
+
}
|
|
3986
|
+
const archiveStatus = parseAppWorkerArchiveStatus(parsed.flags.archiveStatus);
|
|
3987
|
+
if (archiveStatus instanceof Error) {
|
|
3988
|
+
return errorResult(archiveStatus.message);
|
|
3989
|
+
}
|
|
3990
|
+
const database = openRuntimeDatabase(parsed, options);
|
|
3991
|
+
try {
|
|
3992
|
+
const timestamp = nowIsoSeconds(options);
|
|
3993
|
+
const dbPath = runtimeDbPath(parsed, options);
|
|
3994
|
+
const plan = appWorkerRotationPlanSync(database, {
|
|
3995
|
+
dbPath,
|
|
3996
|
+
now: timestamp,
|
|
3997
|
+
oldWorkerThreadId: parsed.flags.oldWorkerThreadId,
|
|
3998
|
+
reason: parsed.flags.reason,
|
|
3999
|
+
requireHandoff: false,
|
|
4000
|
+
taskName,
|
|
4001
|
+
});
|
|
4002
|
+
if (!plan.eligible) {
|
|
4003
|
+
throw new Error(`Cannot record worker rotation; active worker ownership check failed: ${plan.blockers.join(", ")}`);
|
|
4004
|
+
}
|
|
4005
|
+
if (archiveStatus === "archived") {
|
|
4006
|
+
database.prepare(`
|
|
4007
|
+
update sessions
|
|
4008
|
+
set codex_app_thread_id = ?, codex_app_thread_title = ?, last_heartbeat_at = null
|
|
4009
|
+
where id = ? and role = 'worker'
|
|
4010
|
+
`).run(parsed.flags.newWorkerThreadId, parsed.flags.newWorkerThreadTitle, plan.old_worker.session_id);
|
|
4011
|
+
}
|
|
4012
|
+
const eventId = emitTelemetrySync(database, {
|
|
4013
|
+
actor: "manager",
|
|
4014
|
+
attributes: {
|
|
4015
|
+
archive_status: archiveStatus,
|
|
4016
|
+
binding_id: plan.guard.binding_id,
|
|
4017
|
+
handoff_id: plan.handoff?.id ?? null,
|
|
4018
|
+
new_worker_thread_id: parsed.flags.newWorkerThreadId,
|
|
4019
|
+
new_worker_thread_title: parsed.flags.newWorkerThreadTitle,
|
|
4020
|
+
old_worker_session_id: plan.old_worker.session_id,
|
|
4021
|
+
old_worker_thread_id: parsed.flags.oldWorkerThreadId,
|
|
4022
|
+
reason: parsed.flags.reason,
|
|
4023
|
+
session_updated: archiveStatus === "archived",
|
|
4024
|
+
},
|
|
4025
|
+
correlation: {
|
|
4026
|
+
command: "app-worker-rotation-record",
|
|
4027
|
+
new_worker_thread_id: parsed.flags.newWorkerThreadId,
|
|
4028
|
+
old_worker_thread_id: parsed.flags.oldWorkerThreadId,
|
|
4029
|
+
},
|
|
4030
|
+
eventType: "app_worker_rotation_recorded",
|
|
4031
|
+
severity: archiveStatus === "archived" ? "info" : "warning",
|
|
4032
|
+
summary: `Codex app worker rotation ${archiveStatus} for ${plan.task.name}.`,
|
|
4033
|
+
taskId: plan.task.id,
|
|
4034
|
+
timestamp,
|
|
4035
|
+
});
|
|
4036
|
+
const output = {
|
|
4037
|
+
archive: {
|
|
4038
|
+
old_worker_thread_id: parsed.flags.oldWorkerThreadId,
|
|
4039
|
+
status: archiveStatus,
|
|
4040
|
+
},
|
|
4041
|
+
guard: plan.guard,
|
|
4042
|
+
new_worker: {
|
|
4043
|
+
codex_app_thread_id: parsed.flags.newWorkerThreadId,
|
|
4044
|
+
codex_app_thread_title: parsed.flags.newWorkerThreadTitle,
|
|
4045
|
+
session_id: plan.old_worker.session_id,
|
|
4046
|
+
session_updated: archiveStatus === "archived",
|
|
4047
|
+
},
|
|
4048
|
+
old_worker: plan.old_worker,
|
|
4049
|
+
receipt: {
|
|
4050
|
+
event_id: eventId,
|
|
4051
|
+
event_type: "app_worker_rotation_recorded",
|
|
4052
|
+
recorded_at: timestamp,
|
|
4053
|
+
},
|
|
4054
|
+
task: plan.task,
|
|
4055
|
+
};
|
|
4056
|
+
if (parsed.flags.json) {
|
|
4057
|
+
return jsonResult(output);
|
|
4058
|
+
}
|
|
4059
|
+
return {
|
|
4060
|
+
exitCode: 0,
|
|
4061
|
+
handled: true,
|
|
4062
|
+
stdout: [
|
|
4063
|
+
`App worker rotation ${archiveStatus} for ${plan.task.name}.`,
|
|
4064
|
+
`Old worker thread: ${parsed.flags.oldWorkerThreadId}`,
|
|
4065
|
+
`New worker thread: ${parsed.flags.newWorkerThreadId}`,
|
|
4066
|
+
`Receipt: ${eventId}`,
|
|
4067
|
+
].join("\n") + "\n",
|
|
4068
|
+
};
|
|
4069
|
+
}
|
|
4070
|
+
finally {
|
|
4071
|
+
database.close();
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
function parseAppWorkerArchiveStatus(value) {
|
|
4075
|
+
if (value === "archived" || value === "blocked") {
|
|
4076
|
+
return value;
|
|
4077
|
+
}
|
|
4078
|
+
return new Error("app-worker-rotation-record requires --archive-status archived|blocked");
|
|
4079
|
+
}
|
|
4080
|
+
function appWorkerRotationPlanSync(database, options) {
|
|
4081
|
+
const task = taskRowForPair(database, options.taskName);
|
|
4082
|
+
if (task === null) {
|
|
4083
|
+
throw new Error(`Unknown task: ${options.taskName}`);
|
|
4084
|
+
}
|
|
4085
|
+
const binding = activeBindingForTaskSync(database, task.name);
|
|
4086
|
+
const worker = sessionRow(database, binding.worker_session_name, "worker");
|
|
4087
|
+
const manager = sessionRow(database, binding.manager_session_name, "manager");
|
|
4088
|
+
const handoff = latestWorkerHandoffFullSync(database, task.id);
|
|
4089
|
+
const blockers = [];
|
|
4090
|
+
const exactThreadMatch = worker.codex_app_thread_id === options.oldWorkerThreadId;
|
|
4091
|
+
if (binding.state !== "active") {
|
|
4092
|
+
blockers.push("active_binding_not_active");
|
|
4093
|
+
}
|
|
4094
|
+
if (worker.state !== "active") {
|
|
4095
|
+
blockers.push("worker_session_not_active");
|
|
4096
|
+
}
|
|
4097
|
+
if (worker.tmux_session !== null) {
|
|
4098
|
+
blockers.push("worker_session_is_tmux_backed");
|
|
4099
|
+
}
|
|
4100
|
+
if (!worker.codex_app_thread_id) {
|
|
4101
|
+
blockers.push("missing_worker_codex_app_thread_id");
|
|
4102
|
+
}
|
|
4103
|
+
if (!exactThreadMatch) {
|
|
4104
|
+
blockers.push("old_worker_thread_id_mismatch");
|
|
4105
|
+
}
|
|
4106
|
+
if (manager.id === worker.id || manager.codex_app_thread_id === worker.codex_app_thread_id) {
|
|
4107
|
+
blockers.push("manager_worker_thread_not_distinct");
|
|
4108
|
+
}
|
|
4109
|
+
if (options.requireHandoff) {
|
|
4110
|
+
if (handoff === null) {
|
|
4111
|
+
blockers.push("missing_worker_handoff");
|
|
4112
|
+
}
|
|
4113
|
+
else if (handoff.worker_session_id !== worker.id) {
|
|
4114
|
+
blockers.push("handoff_worker_session_mismatch");
|
|
4115
|
+
}
|
|
4116
|
+
}
|
|
4117
|
+
const guard = {
|
|
4118
|
+
active_binding: binding.state === "active",
|
|
4119
|
+
binding_id: binding.binding_id,
|
|
4120
|
+
exact_thread_match: exactThreadMatch,
|
|
4121
|
+
expected_old_worker_thread_id: options.oldWorkerThreadId,
|
|
4122
|
+
manager_session_id: manager.id,
|
|
4123
|
+
manager_thread_id: manager.codex_app_thread_id,
|
|
4124
|
+
require_handoff: options.requireHandoff,
|
|
4125
|
+
role: "worker",
|
|
4126
|
+
task_id: task.id,
|
|
4127
|
+
worker_session_id: worker.id,
|
|
4128
|
+
worker_state: worker.state,
|
|
4129
|
+
};
|
|
4130
|
+
const eligible = blockers.length === 0;
|
|
4131
|
+
const replacementTitle = replacementWorkerThreadTitle(task.name, worker.codex_app_thread_title);
|
|
4132
|
+
const recordCommand = `${conveyorPollInvocation()} app-worker-rotation-record ${shellQuote(task.name)} --old-worker-thread-id ${shellQuote(options.oldWorkerThreadId)} --new-worker-thread-id <new.thread.id> --new-worker-thread-title <new.thread.title> --archive-status archived --path ${shellQuote(options.dbPath)} --json`;
|
|
4133
|
+
return {
|
|
4134
|
+
actions: eligible
|
|
4135
|
+
? [
|
|
4136
|
+
{
|
|
4137
|
+
guard,
|
|
4138
|
+
prompt: replacementWorkerPrompt({
|
|
4139
|
+
dbPath: options.dbPath,
|
|
4140
|
+
handoff,
|
|
4141
|
+
reason: options.reason,
|
|
4142
|
+
taskName: task.name,
|
|
4143
|
+
}),
|
|
4144
|
+
role: "worker",
|
|
4145
|
+
send_ready: true,
|
|
4146
|
+
status: "ready_to_create",
|
|
4147
|
+
thread: { id: null, title: replacementTitle },
|
|
4148
|
+
type: "create_replacement_worker_thread",
|
|
4149
|
+
},
|
|
4150
|
+
{
|
|
4151
|
+
guard,
|
|
4152
|
+
role: "worker",
|
|
4153
|
+
send_ready: true,
|
|
4154
|
+
status: "ready_to_archive",
|
|
4155
|
+
thread: { id: worker.codex_app_thread_id, title: worker.codex_app_thread_title },
|
|
4156
|
+
type: "archive_old_worker_thread",
|
|
4157
|
+
},
|
|
4158
|
+
]
|
|
4159
|
+
: [],
|
|
4160
|
+
blockers,
|
|
4161
|
+
eligible,
|
|
4162
|
+
guard,
|
|
4163
|
+
handoff: handoff === null
|
|
4164
|
+
? null
|
|
4165
|
+
: {
|
|
4166
|
+
created_at: handoff.created_at,
|
|
4167
|
+
id: handoff.id,
|
|
4168
|
+
next_steps: handoff.next_steps,
|
|
4169
|
+
summary: handoff.summary,
|
|
4170
|
+
worker_session_id: handoff.worker_session_id,
|
|
4171
|
+
},
|
|
4172
|
+
old_worker: {
|
|
4173
|
+
codex_app_thread_id: worker.codex_app_thread_id,
|
|
4174
|
+
codex_app_thread_title: worker.codex_app_thread_title,
|
|
4175
|
+
name: worker.name,
|
|
4176
|
+
session_id: worker.id,
|
|
4177
|
+
},
|
|
4178
|
+
record_command: eligible ? recordCommand : null,
|
|
4179
|
+
task: { id: task.id, name: task.name },
|
|
4180
|
+
};
|
|
4181
|
+
}
|
|
4182
|
+
function replacementWorkerThreadTitle(taskName, oldTitle) {
|
|
4183
|
+
const base = oldTitle && oldTitle.trim().length > 0 ? oldTitle.trim() : `${taskName} worker`;
|
|
4184
|
+
return `${base} fresh`;
|
|
4185
|
+
}
|
|
4186
|
+
function replacementWorkerPrompt(options) {
|
|
4187
|
+
const handoffLines = options.handoff === null
|
|
4188
|
+
? ["No saved handoff was required for this rotation plan."]
|
|
4189
|
+
: [
|
|
4190
|
+
`Saved handoff id: ${String(options.handoff.id)}`,
|
|
4191
|
+
`Saved handoff summary: ${String(options.handoff.summary)}`,
|
|
4192
|
+
`Saved handoff next steps: ${JSON.stringify(options.handoff.next_steps ?? [])}`,
|
|
4193
|
+
];
|
|
4194
|
+
return [
|
|
4195
|
+
"Use the manage-codex-workers skill.",
|
|
4196
|
+
`You are the replacement Codex app worker thread for task ${options.taskName}.`,
|
|
4197
|
+
options.reason ? `Rotation reason: ${options.reason}` : "Rotation reason: fresh worker context for Codex app usage.",
|
|
4198
|
+
...handoffLines,
|
|
4199
|
+
"",
|
|
4200
|
+
"Resume from the saved handoff and continue through Conveyor only. Do not rely on the archived worker thread for context beyond the handoff above.",
|
|
4201
|
+
...visibleSessionProtocolLines("worker"),
|
|
4202
|
+
`Run: ${disposableAppHeartbeatCommand("worker", options.taskName, options.dbPath)}`,
|
|
4203
|
+
`If the heartbeat output asks for direct inbox polling, run: ${sessionPollCommand("worker", options.taskName, options.dbPath)}`,
|
|
4204
|
+
"If no item is consumed, stop after a one-line idle receipt.",
|
|
4205
|
+
].join("\n");
|
|
4206
|
+
}
|
|
4207
|
+
function renderAppWorkerRotationPlanText(plan) {
|
|
4208
|
+
const lines = [
|
|
4209
|
+
`App worker rotation for ${plan.task.name}: ${plan.eligible ? "ready" : "blocked"}`,
|
|
4210
|
+
`Old worker thread: ${plan.old_worker.codex_app_thread_id ?? "(missing)"}`,
|
|
4211
|
+
];
|
|
4212
|
+
if (plan.blockers.length > 0) {
|
|
4213
|
+
lines.push(`Blockers: ${plan.blockers.join(", ")}`);
|
|
4214
|
+
}
|
|
4215
|
+
for (const action of plan.actions) {
|
|
4216
|
+
lines.push(`${action.type}: ${action.status}`);
|
|
4217
|
+
if (action.thread.id || action.thread.title) {
|
|
4218
|
+
lines.push(` thread: ${action.thread.title ?? "(untitled)"} ${action.thread.id ?? "(new)"}`);
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
if (plan.record_command) {
|
|
4222
|
+
lines.push(`Record after app tools: ${plan.record_command}`);
|
|
4223
|
+
}
|
|
4224
|
+
if (plan.receipt) {
|
|
4225
|
+
lines.push(`Receipt: ${plan.receipt.event_id}`);
|
|
4226
|
+
}
|
|
4227
|
+
return `${lines.join("\n")}\n`;
|
|
4228
|
+
}
|
|
3755
4229
|
function runAppAutopilotCommand(parsed, options) {
|
|
3756
4230
|
const action = parsed.flags.action;
|
|
3757
4231
|
if (action !== "start" && action !== "stop" && action !== "status") {
|
|
@@ -5596,6 +6070,270 @@ function runTasksCommand(parsed, options) {
|
|
|
5596
6070
|
database.close();
|
|
5597
6071
|
}
|
|
5598
6072
|
}
|
|
6073
|
+
function runCampaignCommand(parsed, options) {
|
|
6074
|
+
if (parsed.task) {
|
|
6075
|
+
return errorResult(`Unexpected argument: ${parsed.task}`);
|
|
6076
|
+
}
|
|
6077
|
+
const action = parsed.flags.action;
|
|
6078
|
+
if (!action) {
|
|
6079
|
+
return errorResult("campaign requires an action: create, add-slot, brief, assign, asset, or status");
|
|
6080
|
+
}
|
|
6081
|
+
const campaign = campaignNameArg(parsed);
|
|
6082
|
+
const database = openRuntimeDatabase(parsed, options);
|
|
6083
|
+
try {
|
|
6084
|
+
if (action === "create") {
|
|
6085
|
+
const objective = requiredStringFlag(parsed.flags.objective, "--objective");
|
|
6086
|
+
const metadata = jsonObjectArg(parsed.flags.metadataJson, "--metadata-json");
|
|
6087
|
+
const campaignId = createCampaignSync(database, {
|
|
6088
|
+
metadata,
|
|
6089
|
+
name: campaign,
|
|
6090
|
+
objective,
|
|
6091
|
+
});
|
|
6092
|
+
return campaignResult(parsed, {
|
|
6093
|
+
action,
|
|
6094
|
+
campaign,
|
|
6095
|
+
campaign_id: campaignId,
|
|
6096
|
+
created: true,
|
|
6097
|
+
}, [`campaign ${campaign} created ${campaignId}`]);
|
|
6098
|
+
}
|
|
6099
|
+
if (action === "add-slot") {
|
|
6100
|
+
const slotKey = requiredStringFlag(parsed.flags.slotKey, "--slot-key");
|
|
6101
|
+
const roleLabel = requiredStringFlag(parsed.flags.roleLabel, "--role-label");
|
|
6102
|
+
const state = campaignSlotStateArg(parsed.flags.statusState);
|
|
6103
|
+
const metadata = jsonObjectArg(parsed.flags.metadataJson, "--metadata-json");
|
|
6104
|
+
const slotId = addCampaignWorkerSlotSync(database, {
|
|
6105
|
+
campaign,
|
|
6106
|
+
channel: parsed.flags.channel,
|
|
6107
|
+
codexAppThreadId: parsed.flags.threadId,
|
|
6108
|
+
codexAppThreadTitle: parsed.flags.threadTitle,
|
|
6109
|
+
metadata,
|
|
6110
|
+
roleLabel,
|
|
6111
|
+
sessionId: parsed.flags.sessionId,
|
|
6112
|
+
slotKey,
|
|
6113
|
+
...(state ? { state } : {}),
|
|
6114
|
+
});
|
|
6115
|
+
return campaignResult(parsed, {
|
|
6116
|
+
action,
|
|
6117
|
+
campaign,
|
|
6118
|
+
created: true,
|
|
6119
|
+
slot_id: slotId,
|
|
6120
|
+
slot_key: slotKey,
|
|
6121
|
+
}, [`campaign ${campaign} slot ${slotKey} created ${slotId}`]);
|
|
6122
|
+
}
|
|
6123
|
+
if (action === "attach-slot") {
|
|
6124
|
+
const slot = requiredStringFlag(parsed.flags.slot, "--slot");
|
|
6125
|
+
const state = campaignSlotStateArg(parsed.flags.statusState) ?? "active";
|
|
6126
|
+
const record = updateCampaignWorkerSlotLifecycleSync(database, {
|
|
6127
|
+
campaign,
|
|
6128
|
+
codexAppThreadId: parsed.flags.threadId,
|
|
6129
|
+
codexAppThreadTitle: parsed.flags.threadTitle,
|
|
6130
|
+
metadata: jsonObjectArg(parsed.flags.metadataJson, "--metadata-json"),
|
|
6131
|
+
sessionId: parsed.flags.sessionId,
|
|
6132
|
+
slot,
|
|
6133
|
+
state,
|
|
6134
|
+
});
|
|
6135
|
+
return campaignResult(parsed, {
|
|
6136
|
+
action,
|
|
6137
|
+
campaign,
|
|
6138
|
+
slot: record,
|
|
6139
|
+
updated: true,
|
|
6140
|
+
}, [`campaign ${campaign} slot ${record.slot_key} attached ${record.id}`]);
|
|
6141
|
+
}
|
|
6142
|
+
if (action === "rotate-slot") {
|
|
6143
|
+
const slot = requiredStringFlag(parsed.flags.slot, "--slot");
|
|
6144
|
+
const expectedThreadId = requiredStringFlag(parsed.flags.expectedThreadId, "--expected-thread-id");
|
|
6145
|
+
const nextThreadId = requiredStringFlag(parsed.flags.threadId, "--thread-id");
|
|
6146
|
+
const record = updateCampaignWorkerSlotLifecycleSync(database, {
|
|
6147
|
+
campaign,
|
|
6148
|
+
codexAppThreadId: nextThreadId,
|
|
6149
|
+
codexAppThreadTitle: parsed.flags.threadTitle,
|
|
6150
|
+
expectedThreadId,
|
|
6151
|
+
sessionId: parsed.flags.sessionId,
|
|
6152
|
+
slot,
|
|
6153
|
+
state: campaignSlotStateArg(parsed.flags.statusState) ?? "active",
|
|
6154
|
+
});
|
|
6155
|
+
return campaignResult(parsed, {
|
|
6156
|
+
action,
|
|
6157
|
+
campaign,
|
|
6158
|
+
expected_thread_id: expectedThreadId,
|
|
6159
|
+
slot: record,
|
|
6160
|
+
updated: true,
|
|
6161
|
+
}, [`campaign ${campaign} slot ${record.slot_key} rotated ${expectedThreadId} -> ${record.codex_app_thread_id ?? "none"}`]);
|
|
6162
|
+
}
|
|
6163
|
+
if (action === "archive-slot") {
|
|
6164
|
+
const slot = requiredStringFlag(parsed.flags.slot, "--slot");
|
|
6165
|
+
const expectedThreadId = requiredStringFlag(parsed.flags.expectedThreadId, "--expected-thread-id");
|
|
6166
|
+
const record = updateCampaignWorkerSlotLifecycleSync(database, {
|
|
6167
|
+
campaign,
|
|
6168
|
+
expectedThreadId,
|
|
6169
|
+
slot,
|
|
6170
|
+
state: "archived",
|
|
6171
|
+
});
|
|
6172
|
+
return campaignResult(parsed, {
|
|
6173
|
+
action,
|
|
6174
|
+
campaign,
|
|
6175
|
+
expected_thread_id: expectedThreadId,
|
|
6176
|
+
slot: record,
|
|
6177
|
+
updated: true,
|
|
6178
|
+
}, [`campaign ${campaign} slot ${record.slot_key} archived ${record.id}`]);
|
|
6179
|
+
}
|
|
6180
|
+
if (action === "brief") {
|
|
6181
|
+
const channel = requiredStringFlag(parsed.flags.channel, "--channel");
|
|
6182
|
+
if (parsed.flags.briefJson === null) {
|
|
6183
|
+
return errorResult("campaign brief requires --brief-json");
|
|
6184
|
+
}
|
|
6185
|
+
const briefId = upsertCampaignChannelBriefSync(database, {
|
|
6186
|
+
brief: jsonObjectArg(parsed.flags.briefJson, "--brief-json"),
|
|
6187
|
+
campaign,
|
|
6188
|
+
channel,
|
|
6189
|
+
});
|
|
6190
|
+
return campaignResult(parsed, {
|
|
6191
|
+
action,
|
|
6192
|
+
brief_id: briefId,
|
|
6193
|
+
campaign,
|
|
6194
|
+
channel,
|
|
6195
|
+
upserted: true,
|
|
6196
|
+
}, [`campaign ${campaign} brief ${channel} upserted ${briefId}`]);
|
|
6197
|
+
}
|
|
6198
|
+
if (action === "assign") {
|
|
6199
|
+
const slot = requiredStringFlag(parsed.flags.slot, "--slot");
|
|
6200
|
+
const title = requiredStringFlag(parsed.flags.title, "--title");
|
|
6201
|
+
const instructions = requiredStringFlag(parsed.flags.instructions, "--instructions");
|
|
6202
|
+
const status = campaignAssignmentStatusArg(parsed.flags.statusState);
|
|
6203
|
+
const metadata = jsonObjectArg(parsed.flags.metadataJson, "--metadata-json");
|
|
6204
|
+
const assignmentId = createCampaignAssignmentSync(database, {
|
|
6205
|
+
campaign,
|
|
6206
|
+
instructions,
|
|
6207
|
+
metadata,
|
|
6208
|
+
slot,
|
|
6209
|
+
title,
|
|
6210
|
+
...(status ? { status } : {}),
|
|
6211
|
+
});
|
|
6212
|
+
return campaignResult(parsed, {
|
|
6213
|
+
action,
|
|
6214
|
+
assignment_id: assignmentId,
|
|
6215
|
+
campaign,
|
|
6216
|
+
created: true,
|
|
6217
|
+
slot_id: slot,
|
|
6218
|
+
}, [`campaign ${campaign} assignment created ${assignmentId}`]);
|
|
6219
|
+
}
|
|
6220
|
+
if (action === "asset") {
|
|
6221
|
+
const slot = requiredStringFlag(parsed.flags.slot, "--slot");
|
|
6222
|
+
const title = requiredStringFlag(parsed.flags.title, "--title");
|
|
6223
|
+
const assetType = campaignAssetTypeArg(requiredStringFlag(parsed.flags.assetType, "--asset-type"));
|
|
6224
|
+
const status = campaignAssetStatusArg(parsed.flags.statusState);
|
|
6225
|
+
const metadata = jsonObjectArg(parsed.flags.metadataJson, "--metadata-json");
|
|
6226
|
+
const assetReceiptId = recordCampaignAssetReceiptSync(database, {
|
|
6227
|
+
allowAdditionalReceipt: parsed.flags.allowAdditionalReceipt,
|
|
6228
|
+
artifactPath: parsed.flags.artifactPath,
|
|
6229
|
+
assetType,
|
|
6230
|
+
assignment: parsed.flags.assignment,
|
|
6231
|
+
campaign,
|
|
6232
|
+
channel: parsed.flags.channel,
|
|
6233
|
+
metadata,
|
|
6234
|
+
promptSummary: parsed.flags.promptSummary,
|
|
6235
|
+
reviewNotes: parsed.flags.reviewNotes,
|
|
6236
|
+
slot,
|
|
6237
|
+
title,
|
|
6238
|
+
...(status ? { status } : {}),
|
|
6239
|
+
});
|
|
6240
|
+
return campaignResult(parsed, {
|
|
6241
|
+
action,
|
|
6242
|
+
asset_receipt_id: assetReceiptId,
|
|
6243
|
+
campaign,
|
|
6244
|
+
created: true,
|
|
6245
|
+
slot_id: slot,
|
|
6246
|
+
}, [`campaign ${campaign} asset receipt created ${assetReceiptId}`]);
|
|
6247
|
+
}
|
|
6248
|
+
if (action === "status") {
|
|
6249
|
+
const status = campaignStatusSync(database, campaign);
|
|
6250
|
+
return campaignResult(parsed, status, renderCampaignStatusText(status));
|
|
6251
|
+
}
|
|
6252
|
+
if (action === "dashboard") {
|
|
6253
|
+
const dashboard = campaignDashboardSync(database, campaign);
|
|
6254
|
+
return campaignResult(parsed, dashboard, renderCampaignDashboardText(dashboard));
|
|
6255
|
+
}
|
|
6256
|
+
return errorResult(`Unsupported campaign action: ${action}`);
|
|
6257
|
+
}
|
|
6258
|
+
finally {
|
|
6259
|
+
database.close();
|
|
6260
|
+
}
|
|
6261
|
+
}
|
|
6262
|
+
function campaignResult(parsed, payload, lines) {
|
|
6263
|
+
return parsed.flags.json ? jsonResult(payload) : textResult(lines);
|
|
6264
|
+
}
|
|
6265
|
+
function campaignNameArg(parsed) {
|
|
6266
|
+
if (parsed.flags.names.length !== 1) {
|
|
6267
|
+
throw new Error("campaign requires exactly one --name <campaign>");
|
|
6268
|
+
}
|
|
6269
|
+
return parsed.flags.names[0];
|
|
6270
|
+
}
|
|
6271
|
+
function requiredStringFlag(value, flag) {
|
|
6272
|
+
if (value === null || value.length === 0) {
|
|
6273
|
+
throw new Error(`campaign requires ${flag}`);
|
|
6274
|
+
}
|
|
6275
|
+
return value;
|
|
6276
|
+
}
|
|
6277
|
+
function campaignSlotStateArg(value) {
|
|
6278
|
+
if (value === null) {
|
|
6279
|
+
return undefined;
|
|
6280
|
+
}
|
|
6281
|
+
if (value === "active" || value === "archived" || value === "blocked" || value === "idle" || value === "planned") {
|
|
6282
|
+
return value;
|
|
6283
|
+
}
|
|
6284
|
+
throw new Error("--state must be one of: active, archived, blocked, idle, planned");
|
|
6285
|
+
}
|
|
6286
|
+
function campaignAssignmentStatusArg(value) {
|
|
6287
|
+
if (value === null) {
|
|
6288
|
+
return undefined;
|
|
6289
|
+
}
|
|
6290
|
+
if (value === "active" || value === "blocked" || value === "cancelled" || value === "done" || value === "queued") {
|
|
6291
|
+
return value;
|
|
6292
|
+
}
|
|
6293
|
+
throw new Error("--status must be one of: active, blocked, cancelled, done, queued");
|
|
6294
|
+
}
|
|
6295
|
+
function campaignAssetStatusArg(value) {
|
|
6296
|
+
if (value === null) {
|
|
6297
|
+
return undefined;
|
|
6298
|
+
}
|
|
6299
|
+
if (value === "approved" || value === "draft" || value === "needs_review" || value === "published" || value === "rejected") {
|
|
6300
|
+
return value;
|
|
6301
|
+
}
|
|
6302
|
+
throw new Error("--status must be one of: approved, draft, needs_review, published, rejected");
|
|
6303
|
+
}
|
|
6304
|
+
function campaignAssetTypeArg(value) {
|
|
6305
|
+
if (value === "audio" || value === "copy" || value === "hyperframes" || value === "image" || value === "other" || value === "video") {
|
|
6306
|
+
return value;
|
|
6307
|
+
}
|
|
6308
|
+
throw new Error("--asset-type must be one of: audio, copy, hyperframes, image, other, video");
|
|
6309
|
+
}
|
|
6310
|
+
function renderCampaignStatusText(status) {
|
|
6311
|
+
return [
|
|
6312
|
+
`campaign ${status.campaign.name} ${status.campaign.status}`,
|
|
6313
|
+
`slots ${status.slots.length}`,
|
|
6314
|
+
`assignments ${statusCountsText(status.assignment_counts)}`,
|
|
6315
|
+
`assets ${statusCountsText(status.asset_counts)}`,
|
|
6316
|
+
`briefs ${status.channel_briefs.length}`,
|
|
6317
|
+
];
|
|
6318
|
+
}
|
|
6319
|
+
function renderCampaignDashboardText(dashboard) {
|
|
6320
|
+
const lines = [
|
|
6321
|
+
`campaign ${dashboard.campaign.name} ${dashboard.campaign.status}`,
|
|
6322
|
+
`next ${dashboard.next_manager_action.action}: ${dashboard.next_manager_action.reason}`,
|
|
6323
|
+
`workers active=${dashboard.summary.active_slots} stale=${dashboard.summary.stale_slots} blocked=${dashboard.summary.blocked_slots} archived=${dashboard.summary.archived_slots}`,
|
|
6324
|
+
`assignments ${statusCountsText(dashboard.assignment_counts)}`,
|
|
6325
|
+
`assets ${statusCountsText(dashboard.asset_counts)}`,
|
|
6326
|
+
`approvals needs_review=${dashboard.approvals.needs_review} approved=${dashboard.approvals.approved} rejected=${dashboard.approvals.rejected} published=${dashboard.approvals.published}`,
|
|
6327
|
+
`blockers ${dashboard.blockers.length}`,
|
|
6328
|
+
];
|
|
6329
|
+
for (const slot of dashboard.slots.slice(0, 8)) {
|
|
6330
|
+
lines.push(`slot ${slot.slot_key} ${slot.state} ${slot.lifecycle.state} assignments=${slot.assignments.length} assets=${slot.assets.length}`);
|
|
6331
|
+
}
|
|
6332
|
+
return lines;
|
|
6333
|
+
}
|
|
6334
|
+
function statusCountsText(counts) {
|
|
6335
|
+
return Object.entries(counts).map(([status, count]) => `${status}=${count}`).join(" ");
|
|
6336
|
+
}
|
|
5599
6337
|
function runLegacyStartCommand(parsed, options) {
|
|
5600
6338
|
if (!parsed.task) {
|
|
5601
6339
|
return errorResult("start requires a session.");
|
|
@@ -5790,7 +6528,14 @@ function runInstallSkillsCommand(parsed, options) {
|
|
|
5790
6528
|
return { exitCode: 0, handled: true, stdout: `${lines.join("\n")}\n` };
|
|
5791
6529
|
}
|
|
5792
6530
|
function dashboardLaunchPayload(parsed) {
|
|
5793
|
-
const
|
|
6531
|
+
const queryParams = new URLSearchParams();
|
|
6532
|
+
if (parsed.flags.taskName) {
|
|
6533
|
+
queryParams.set("task", parsed.flags.taskName);
|
|
6534
|
+
}
|
|
6535
|
+
if (parsed.flags.campaignName) {
|
|
6536
|
+
queryParams.set("campaign", parsed.flags.campaignName);
|
|
6537
|
+
}
|
|
6538
|
+
const query = queryParams.toString() ? `?${queryParams.toString()}` : "";
|
|
5794
6539
|
const command = [
|
|
5795
6540
|
"npm",
|
|
5796
6541
|
"run",
|
|
@@ -5806,6 +6551,9 @@ function dashboardLaunchPayload(parsed) {
|
|
|
5806
6551
|
if (parsed.flags.taskName) {
|
|
5807
6552
|
command.push("--task", parsed.flags.taskName);
|
|
5808
6553
|
}
|
|
6554
|
+
if (parsed.flags.campaignName) {
|
|
6555
|
+
command.push("--campaign", parsed.flags.campaignName);
|
|
6556
|
+
}
|
|
5809
6557
|
if (parsed.flags.path) {
|
|
5810
6558
|
command.push("--db-path", parsed.flags.path);
|
|
5811
6559
|
}
|
|
@@ -5816,6 +6564,7 @@ function dashboardLaunchPayload(parsed) {
|
|
|
5816
6564
|
ensure_dispatch: parsed.flags.require,
|
|
5817
6565
|
host: parsed.flags.host,
|
|
5818
6566
|
port: parsed.flags.port,
|
|
6567
|
+
campaign: parsed.flags.campaignName,
|
|
5819
6568
|
task: parsed.flags.taskName,
|
|
5820
6569
|
url: `http://${parsed.flags.host}:${parsed.flags.port}/${query}`,
|
|
5821
6570
|
};
|
|
@@ -14563,6 +15312,8 @@ function isDefaultRuntimeCommand(command) {
|
|
|
14563
15312
|
|| command === "app-wakeup-plan"
|
|
14564
15313
|
|| command === "app-wakeup-dispatch"
|
|
14565
15314
|
|| command === "app-wakeup-record-delivery"
|
|
15315
|
+
|| command === "app-worker-rotation-plan"
|
|
15316
|
+
|| command === "app-worker-rotation-record"
|
|
14566
15317
|
|| command === "app-autopilot"
|
|
14567
15318
|
|| command === "loop-templates"
|
|
14568
15319
|
|| command === "loop-triggers"
|
|
@@ -14578,6 +15329,7 @@ function isDefaultRuntimeCommand(command) {
|
|
|
14578
15329
|
|| command === "replay"
|
|
14579
15330
|
|| command === "export-task"
|
|
14580
15331
|
|| command === "tasks"
|
|
15332
|
+
|| command === "campaign"
|
|
14581
15333
|
|| command === "bind"
|
|
14582
15334
|
|| command === "unbind"
|
|
14583
15335
|
|| command === "create-disposable-binding"
|