agent-conveyor 0.1.18 → 0.1.20

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 CHANGED
@@ -154,18 +154,36 @@ The GitHub Pages version lives at
154
154
  Use `node scripts/check-landing-page.mjs` for a docs-only desktop/mobile
155
155
  screenshot gate; this does not run the full package release smoke.
156
156
 
157
+ ### Codex Operator Plugin
158
+
159
+ Agent Conveyor also ships Codex-app-only operator scaffolding for visible
160
+ manager/worker sessions from any project. Install the package, then install
161
+ and inspect the plugin:
162
+
163
+ ```bash
164
+ npm install -g agent-conveyor
165
+ conveyor install-plugin
166
+ conveyor plugin-status
167
+ ```
168
+
169
+ The per-project default ledger for operator sessions is
170
+ `.codex-workers/workerctl.db`. The initial included skills are
171
+ `conveyor-create-pair`, `conveyor-create-worker-set`, and
172
+ `conveyor-check-status`.
173
+
157
174
  After install, the intended Codex app entry point is natural language. Open a
158
175
  new Codex app session in the target repo and say:
159
176
 
160
177
  ```text
161
- Use the manage-codex-workers skill.
178
+ Use the conveyor-create-pair skill.
162
179
 
163
180
  Set up a Codex app Ralph loop for issue CTL.
164
181
  Require adversarial proof before another worker iteration.
165
182
  ```
166
183
 
167
- The installed skill should call the `conveyor` CLI, choose names, create the
168
- no-tmux binding with `create-disposable-binding`, point the worker at
184
+ For multiple workers, start with `Use the conveyor-create-worker-set skill`.
185
+ The installed plugin skill should call the `conveyor` CLI, choose names, create
186
+ the no-tmux binding with `create-disposable-binding`, point the worker at
169
187
  `worker-inbox`, and use `loop-status` plus telemetry receipts before reporting
170
188
  that the loop is ready. When the manager is itself running in the Codex app and
171
189
  thread tools are available, the skill should first call `create_thread` for a
@@ -25,6 +25,15 @@ interface SpawnedCodexSessionDiscoveryOptions {
25
25
  }
26
26
  type TypescriptRuntimeOptions = {
27
27
  args: readonly string[];
28
+ campaignReadbackBeforeVerify?: (context: {
29
+ databasePath: string;
30
+ readback: {
31
+ assignment?: string;
32
+ campaign: string;
33
+ channel?: string;
34
+ slot?: string;
35
+ };
36
+ }) => void;
28
37
  codexCommandResolver?: (name: string) => string | null;
29
38
  cwd?: string;
30
39
  discoverSpawnedCodexSession?: (options: SpawnedCodexSessionDiscoveryOptions) => SpawnedCodexSessionDiscovery;
@@ -7,7 +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
+ import { addCampaignWorkerSlotSync, campaignDashboardSync, campaignSetupReadbackProofSync, campaignStatusSync, createCampaignAssignmentSync, createCampaignSync, recordCampaignAssetReceiptSync, updateCampaignWorkerSlotLifecycleSync, upsertCampaignChannelBriefSync, } from "../runtime/campaigns.js";
11
11
  import { exportTaskSync } from "../runtime/export.js";
12
12
  import { ingestSessionSync } from "../runtime/ingest.js";
13
13
  import { acceptanceCriteriaForTaskSync, loopEvidenceCriterion, recordAdversarialLoopEvidenceSync, recordLoopEvidenceSync, recordVisualDiffLoopEvidenceSync, } from "../runtime/loop-evidence.js";
@@ -171,6 +171,15 @@ export function runTypescriptRuntimeCommand(options) {
171
171
  if (parsed.command === "install-skills") {
172
172
  return runInstallSkillsCommand(parsed, options);
173
173
  }
174
+ if (parsed.command === "plugin-path") {
175
+ return runPluginPathCommand(parsed, options);
176
+ }
177
+ if (parsed.command === "plugin-status") {
178
+ return runPluginStatusCommand(parsed, options);
179
+ }
180
+ if (parsed.command === "install-plugin") {
181
+ return runInstallPluginCommand(parsed, options);
182
+ }
174
183
  if (parsed.command === "bind") {
175
184
  return runBindCommand(parsed, options);
176
185
  }
@@ -1086,7 +1095,7 @@ function parseRuntimeArgs(args, env) {
1086
1095
  index += 1;
1087
1096
  }
1088
1097
  else if (arg === "--codex-home") {
1089
- if (command !== "install-skills") {
1098
+ if (command !== "install-skills" && command !== "install-plugin" && command !== "plugin-status" && command !== "plugin-path") {
1090
1099
  return { command, enabled, error: "Unsupported TypeScript runtime option: --codex-home", explicit, flags, task };
1091
1100
  }
1092
1101
  const value = valueAfter(queue, index, arg);
@@ -6096,12 +6105,16 @@ function runCampaignCommand(parsed, options) {
6096
6105
  name: campaign,
6097
6106
  objective,
6098
6107
  });
6108
+ const readback = campaignSetupReadbackProofFromConfiguredDatabase(parsed, options, {
6109
+ campaign: campaignId,
6110
+ });
6099
6111
  return campaignResult(parsed, {
6100
6112
  action,
6101
6113
  campaign,
6102
6114
  campaign_id: campaignId,
6103
6115
  created: true,
6104
- }, [`campaign ${campaign} created ${campaignId}`]);
6116
+ ledger_readback: readback,
6117
+ }, [`campaign ${campaign} created ${campaignId}`, campaignLedgerReadbackText(readback)]);
6105
6118
  }
6106
6119
  if (action === "add-slot") {
6107
6120
  const slotKey = requiredStringFlag(parsed.flags.slotKey, "--slot-key");
@@ -6119,13 +6132,18 @@ function runCampaignCommand(parsed, options) {
6119
6132
  slotKey,
6120
6133
  ...(state ? { state } : {}),
6121
6134
  });
6135
+ const readback = campaignSetupReadbackProofFromConfiguredDatabase(parsed, options, {
6136
+ campaign,
6137
+ slot: slotId,
6138
+ });
6122
6139
  return campaignResult(parsed, {
6123
6140
  action,
6124
6141
  campaign,
6125
6142
  created: true,
6143
+ ledger_readback: readback,
6126
6144
  slot_id: slotId,
6127
6145
  slot_key: slotKey,
6128
- }, [`campaign ${campaign} slot ${slotKey} created ${slotId}`]);
6146
+ }, [`campaign ${campaign} slot ${slotKey} created ${slotId}`, campaignLedgerReadbackText(readback)]);
6129
6147
  }
6130
6148
  if (action === "attach-slot") {
6131
6149
  const slot = requiredStringFlag(parsed.flags.slot, "--slot");
@@ -6194,13 +6212,18 @@ function runCampaignCommand(parsed, options) {
6194
6212
  campaign,
6195
6213
  channel,
6196
6214
  });
6215
+ const readback = campaignSetupReadbackProofFromConfiguredDatabase(parsed, options, {
6216
+ campaign,
6217
+ channel,
6218
+ });
6197
6219
  return campaignResult(parsed, {
6198
6220
  action,
6199
6221
  brief_id: briefId,
6200
6222
  campaign,
6201
6223
  channel,
6224
+ ledger_readback: readback,
6202
6225
  upserted: true,
6203
- }, [`campaign ${campaign} brief ${channel} upserted ${briefId}`]);
6226
+ }, [`campaign ${campaign} brief ${channel} upserted ${briefId}`, campaignLedgerReadbackText(readback)]);
6204
6227
  }
6205
6228
  if (action === "assign") {
6206
6229
  const slot = requiredStringFlag(parsed.flags.slot, "--slot");
@@ -6216,13 +6239,19 @@ function runCampaignCommand(parsed, options) {
6216
6239
  title,
6217
6240
  ...(status ? { status } : {}),
6218
6241
  });
6242
+ const readback = campaignSetupReadbackProofFromConfiguredDatabase(parsed, options, {
6243
+ assignment: assignmentId,
6244
+ campaign,
6245
+ slot,
6246
+ });
6219
6247
  return campaignResult(parsed, {
6220
6248
  action,
6221
6249
  assignment_id: assignmentId,
6222
6250
  campaign,
6223
6251
  created: true,
6252
+ ledger_readback: readback,
6224
6253
  slot_id: slot,
6225
- }, [`campaign ${campaign} assignment created ${assignmentId}`]);
6254
+ }, [`campaign ${campaign} assignment created ${assignmentId}`, campaignLedgerReadbackText(readback)]);
6226
6255
  }
6227
6256
  if (action === "asset") {
6228
6257
  const slot = requiredStringFlag(parsed.flags.slot, "--slot");
@@ -6276,6 +6305,25 @@ function runCampaignCommand(parsed, options) {
6276
6305
  function campaignActionsUsage() {
6277
6306
  return CAMPAIGN_ACTION_NAMES.join("|");
6278
6307
  }
6308
+ function campaignSetupReadbackProofFromConfiguredDatabase(parsed, options, readbackOptions) {
6309
+ const databasePath = runtimeDbPath(parsed, options);
6310
+ options.campaignReadbackBeforeVerify?.({ databasePath, readback: readbackOptions });
6311
+ const database = openDatabaseSync(databasePath);
6312
+ initializeDatabaseSync(database);
6313
+ try {
6314
+ return campaignSetupReadbackProofSync(database, readbackOptions);
6315
+ }
6316
+ catch (error) {
6317
+ const message = error instanceof Error ? error.message : String(error);
6318
+ throw new Error(`campaign ledger readback failed after setup write: ${message}`, { cause: error });
6319
+ }
6320
+ finally {
6321
+ database.close();
6322
+ }
6323
+ }
6324
+ function campaignLedgerReadbackText(proof) {
6325
+ return `ledger_readback ok campaign=${proof.campaign_id} checks=${proof.checks.map((check) => check.entity).join(",")}`;
6326
+ }
6279
6327
  function unsupportedCampaignActionMessage(action) {
6280
6328
  return `Unsupported campaign action: ${action ?? "<missing>"}; expected one of: ${CAMPAIGN_ACTION_NAMES.join(", ")}. Use \`conveyor campaign dashboard --name <campaign> --json\` to list assets and receipt counts.`;
6281
6329
  }
@@ -6616,7 +6664,7 @@ function runInstallSkillsCommand(parsed, options) {
6616
6664
  if (unsupported) {
6617
6665
  return unsupportedRuntimeResult(parsed, unsupported);
6618
6666
  }
6619
- const codexHome = resolve(expandUserPath(parsed.flags.codexHome ?? options.env?.CODEX_HOME ?? join(homedir(), ".codex")));
6667
+ const codexHome = resolveCodexHome(parsed, options);
6620
6668
  const skills = installableSkillSources();
6621
6669
  const targets = skills.map((skill) => ({
6622
6670
  name: skill.name,
@@ -6648,6 +6696,194 @@ function runInstallSkillsCommand(parsed, options) {
6648
6696
  const lines = targets.map((target) => `${parsed.flags.dryRun ? "would install" : "installed"} ${target.name} skill in ${target.target}`);
6649
6697
  return { exitCode: 0, handled: true, stdout: `${lines.join("\n")}\n` };
6650
6698
  }
6699
+ const AGENT_CONVEYOR_PLUGIN_NAME = "agent-conveyor";
6700
+ const AGENT_CONVEYOR_PLUGIN_SKILLS = ["conveyor-create-pair", "conveyor-create-worker-set", "conveyor-check-status"];
6701
+ function resolveCodexHome(parsed, options) {
6702
+ return resolve(expandUserPath(parsed.flags.codexHome ?? options.env?.CODEX_HOME ?? join(homedir(), ".codex")));
6703
+ }
6704
+ function packageVersionFromRoot(packageRoot) {
6705
+ const packageJsonPath = join(packageRoot, "package.json");
6706
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
6707
+ if (!isPlainRecord(parsed) || typeof parsed.version !== "string" || parsed.version.length === 0) {
6708
+ throw new Error(`Package version not found in ${packageJsonPath}.`);
6709
+ }
6710
+ return parsed.version;
6711
+ }
6712
+ function pluginPaths(parsed, options) {
6713
+ const codexHome = resolveCodexHome(parsed, options);
6714
+ const packageRoot = packageRootFromRuntimeModule();
6715
+ const packageVersion = packageVersionFromRoot(packageRoot);
6716
+ const pluginCacheRoot = join(codexHome, "plugins", "cache", AGENT_CONVEYOR_PLUGIN_NAME, AGENT_CONVEYOR_PLUGIN_NAME);
6717
+ return {
6718
+ codex_home: codexHome,
6719
+ package_root: packageRoot,
6720
+ plugin_cache_root: pluginCacheRoot,
6721
+ plugin_install_root: join(pluginCacheRoot, packageVersion),
6722
+ plugin_source: join(packageRoot, "plugin", AGENT_CONVEYOR_PLUGIN_NAME),
6723
+ skills_install_root: join(codexHome, "skills"),
6724
+ };
6725
+ }
6726
+ function readAgentConveyorPluginManifest(source) {
6727
+ const manifestPath = join(source, "plugin.json");
6728
+ const parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
6729
+ if (!isPlainRecord(parsed)) {
6730
+ throw new Error(`Agent Conveyor plugin manifest must be a JSON object: ${manifestPath}`);
6731
+ }
6732
+ if (parsed.name !== AGENT_CONVEYOR_PLUGIN_NAME) {
6733
+ throw new Error(`Agent Conveyor plugin manifest name must be "${AGENT_CONVEYOR_PLUGIN_NAME}": ${manifestPath}`);
6734
+ }
6735
+ if (typeof parsed.version !== "string") {
6736
+ throw new Error(`Agent Conveyor plugin manifest is missing a string version: ${manifestPath}`);
6737
+ }
6738
+ return {
6739
+ name: parsed.name,
6740
+ version: parsed.version,
6741
+ skills: Array.isArray(parsed.skills) ? parsed.skills.filter((skill) => typeof skill === "string") : undefined,
6742
+ };
6743
+ }
6744
+ function assertPluginVersionMatchesPackage(manifest, packageVersion) {
6745
+ if (manifest.version !== packageVersion) {
6746
+ throw new Error(`Agent Conveyor plugin version ${manifest.version} does not match package version ${packageVersion}.`);
6747
+ }
6748
+ }
6749
+ function pluginSkillTargets(paths) {
6750
+ return AGENT_CONVEYOR_PLUGIN_SKILLS.map((name) => {
6751
+ const target = join(paths.skills_install_root, name);
6752
+ return {
6753
+ installed: existsSync(join(target, "SKILL.md")),
6754
+ name,
6755
+ source: join(paths.plugin_source, "skills", name),
6756
+ target,
6757
+ };
6758
+ });
6759
+ }
6760
+ function installedAgentConveyorPluginManifest(paths) {
6761
+ const manifestPath = join(paths.plugin_install_root, "plugin.json");
6762
+ if (!existsSync(manifestPath)) {
6763
+ return null;
6764
+ }
6765
+ const parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
6766
+ if (!isPlainRecord(parsed) || parsed.name !== AGENT_CONVEYOR_PLUGIN_NAME || typeof parsed.version !== "string") {
6767
+ return null;
6768
+ }
6769
+ return {
6770
+ name: parsed.name,
6771
+ version: parsed.version,
6772
+ skills: Array.isArray(parsed.skills) ? parsed.skills.filter((skill) => typeof skill === "string") : undefined,
6773
+ };
6774
+ }
6775
+ function agentConveyorPluginStatus(parsed, options) {
6776
+ const paths = pluginPaths(parsed, options);
6777
+ const packageVersion = packageVersionFromRoot(paths.package_root);
6778
+ const manifest = readAgentConveyorPluginManifest(paths.plugin_source);
6779
+ const installedManifest = installedAgentConveyorPluginManifest(paths);
6780
+ const installedVersion = installedManifest?.version ?? null;
6781
+ const skills = pluginSkillTargets(paths);
6782
+ const installed = installedManifest !== null && skills.every((skill) => skill.installed);
6783
+ return {
6784
+ installed,
6785
+ installed_version: installedVersion,
6786
+ package_version: packageVersion,
6787
+ paths,
6788
+ plugin_version: manifest.version,
6789
+ skills,
6790
+ version_matches: installed && installedVersion === packageVersion && manifest.version === packageVersion,
6791
+ };
6792
+ }
6793
+ function unsupportedPluginOptions(parsed) {
6794
+ if (parsed.task !== null) {
6795
+ return `Unexpected argument: ${parsed.task}`;
6796
+ }
6797
+ return null;
6798
+ }
6799
+ function runPluginPathCommand(parsed, options) {
6800
+ const unsupported = unsupportedPluginOptions(parsed);
6801
+ if (unsupported) {
6802
+ return unsupportedRuntimeResult(parsed, unsupported);
6803
+ }
6804
+ const paths = pluginPaths(parsed, options);
6805
+ if (parsed.flags.json) {
6806
+ return jsonResult(paths);
6807
+ }
6808
+ return {
6809
+ exitCode: 0,
6810
+ handled: true,
6811
+ stdout: [
6812
+ `codex_home: ${paths.codex_home}`,
6813
+ `package_root: ${paths.package_root}`,
6814
+ `plugin_cache_root: ${paths.plugin_cache_root}`,
6815
+ `plugin_install_root: ${paths.plugin_install_root}`,
6816
+ `plugin_source: ${paths.plugin_source}`,
6817
+ `skills_install_root: ${paths.skills_install_root}`,
6818
+ "",
6819
+ ].join("\n"),
6820
+ };
6821
+ }
6822
+ function runPluginStatusCommand(parsed, options) {
6823
+ const unsupported = unsupportedPluginOptions(parsed);
6824
+ if (unsupported) {
6825
+ return unsupportedRuntimeResult(parsed, unsupported);
6826
+ }
6827
+ const status = agentConveyorPluginStatus(parsed, options);
6828
+ if (parsed.flags.json) {
6829
+ return jsonResult(status);
6830
+ }
6831
+ const skillLines = status.skills.map((skill) => `skill ${skill.name}: ${skill.installed ? "installed" : "missing"}`);
6832
+ return {
6833
+ exitCode: 0,
6834
+ handled: true,
6835
+ stdout: [
6836
+ `installed: ${status.installed}`,
6837
+ `installed_version: ${status.installed_version ?? "none"}`,
6838
+ `package_version: ${status.package_version}`,
6839
+ `plugin_version: ${status.plugin_version}`,
6840
+ `version_matches: ${status.version_matches}`,
6841
+ ...skillLines,
6842
+ "",
6843
+ ].join("\n"),
6844
+ };
6845
+ }
6846
+ function runInstallPluginCommand(parsed, options) {
6847
+ const unsupported = unsupportedPluginOptions(parsed);
6848
+ if (unsupported) {
6849
+ return unsupportedRuntimeResult(parsed, unsupported);
6850
+ }
6851
+ const paths = pluginPaths(parsed, options);
6852
+ const packageVersion = packageVersionFromRoot(paths.package_root);
6853
+ const manifest = readAgentConveyorPluginManifest(paths.plugin_source);
6854
+ assertPluginVersionMatchesPackage(manifest, packageVersion);
6855
+ const skillTargets = pluginSkillTargets(paths);
6856
+ for (const target of skillTargets) {
6857
+ if (!existsSync(join(target.source, "SKILL.md"))) {
6858
+ throw new Error(`Agent Conveyor plugin skill not found: ${target.source}`);
6859
+ }
6860
+ }
6861
+ if (!parsed.flags.dryRun) {
6862
+ rmSync(paths.plugin_install_root, { force: true, recursive: true });
6863
+ mkdirSync(dirname(paths.plugin_install_root), { recursive: true });
6864
+ cpSync(paths.plugin_source, paths.plugin_install_root, { recursive: true });
6865
+ for (const target of skillTargets) {
6866
+ rmSync(target.target, { force: true, recursive: true });
6867
+ mkdirSync(dirname(target.target), { recursive: true });
6868
+ cpSync(target.source, target.target, { recursive: true });
6869
+ }
6870
+ }
6871
+ const status = agentConveyorPluginStatus(parsed, options);
6872
+ const payload = {
6873
+ ...status,
6874
+ dry_run: parsed.flags.dryRun,
6875
+ installed: status.installed,
6876
+ installed_skills: parsed.flags.dryRun ? [] : skillTargets.map((target) => target.name),
6877
+ };
6878
+ if (parsed.flags.json) {
6879
+ return jsonResult(payload);
6880
+ }
6881
+ const lines = [
6882
+ `${parsed.flags.dryRun ? "would install" : "installed"} ${AGENT_CONVEYOR_PLUGIN_NAME} plugin in ${paths.plugin_install_root}`,
6883
+ ...skillTargets.map((target) => `${parsed.flags.dryRun ? "would install" : "installed"} ${target.name} skill in ${target.target}`),
6884
+ ];
6885
+ return { exitCode: 0, handled: true, stdout: `${lines.join("\n")}\n` };
6886
+ }
6651
6887
  function dashboardLaunchPayload(parsed) {
6652
6888
  const queryParams = new URLSearchParams();
6653
6889
  if (parsed.flags.taskName) {
@@ -15447,6 +15683,9 @@ function isDefaultRuntimeCommand(command) {
15447
15683
  || command === "start-test"
15448
15684
  || command === "dashboard"
15449
15685
  || command === "install-skills"
15686
+ || command === "install-plugin"
15687
+ || command === "plugin-status"
15688
+ || command === "plugin-path"
15450
15689
  || command === "replay"
15451
15690
  || command === "export-task"
15452
15691
  || command === "tasks"