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.
@@ -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
- flags.managerObjective = value.value;
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" && command !== "stop-task" && command !== "record-decision" && command !== "compact-worker" && command !== "app-wakeup-record-delivery") {
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 query = parsed.flags.taskName ? `?task=${encodeURIComponent(parsed.flags.taskName)}` : "";
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"