@oisincoveney/pipeline 3.13.0 → 3.14.0

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.
Files changed (107) hide show
  1. package/defaults/opencode-ecosystem.yaml +2 -12
  2. package/defaults/pipeline.yaml +0 -4
  3. package/dist/argo-submit.d.ts +2 -4
  4. package/dist/argo-submit.js +1 -5
  5. package/dist/argo-workflow.d.ts +2 -7
  6. package/dist/argo-workflow.js +16 -48
  7. package/dist/broker-auth.js +4 -3
  8. package/dist/cli/doctor.js +2 -2
  9. package/dist/cli/program.d.ts +1 -1
  10. package/dist/cli/program.js +58 -55
  11. package/dist/cli/submit-options.js +7 -4
  12. package/dist/cluster-doctor.js +4 -5
  13. package/dist/codex-auth-sync.js +16 -51
  14. package/dist/commands/ticket/complete.js +61 -0
  15. package/dist/commands/ticket/create.js +115 -0
  16. package/dist/commands/ticket/graph-check.js +16 -0
  17. package/dist/commands/ticket/next.js +43 -0
  18. package/dist/commands/ticket/sequence.js +18 -0
  19. package/dist/commands/ticket/shared.d.ts +14 -0
  20. package/dist/commands/ticket/shared.js +77 -0
  21. package/dist/commands/ticket/start.js +94 -0
  22. package/dist/commands/ticket-command.d.ts +2 -15
  23. package/dist/commands/ticket-command.js +14 -319
  24. package/dist/config/load.js +28 -7
  25. package/dist/config/schemas.d.ts +18 -21
  26. package/dist/config/schemas.js +2 -7
  27. package/dist/gates.js +1 -1
  28. package/dist/install-commands/claude-code.js +1 -1
  29. package/dist/install-commands/opencode.js +19 -1
  30. package/dist/install-commands.js +2 -2
  31. package/dist/install-hooks.js +1 -1
  32. package/dist/install-rules.js +1 -1
  33. package/dist/loop/controller-deps.js +1 -2
  34. package/dist/loop/loop-command.js +0 -2
  35. package/dist/loop/loop-controller-entrypoint.js +7 -4
  36. package/dist/moka-global-config.d.ts +19 -5
  37. package/dist/moka-global-config.js +43 -6
  38. package/dist/moka-submit.d.ts +12 -18
  39. package/dist/moka-submit.js +1 -5
  40. package/dist/pipeline-runtime.d.ts +22 -1
  41. package/dist/pipeline-runtime.js +38 -20
  42. package/dist/planning/generate.d.ts +20 -20
  43. package/dist/run-control/commands.js +78 -40
  44. package/dist/run-control/next-node.js +112 -0
  45. package/dist/run-control/postgres/postgres-run-control-store.js +243 -0
  46. package/dist/run-control/postgres/schema.js +52 -0
  47. package/dist/run-control/run-control-store.js +62 -0
  48. package/dist/run-control/runtime-reporter.js +10 -12
  49. package/dist/run-control/store.js +19 -8
  50. package/dist/run-control/submit-result.js +59 -0
  51. package/dist/run-control/supervisor.js +20 -16
  52. package/dist/run-state/opencode-accounts.js +8 -94
  53. package/dist/runner-command/run.js +29 -13
  54. package/dist/runner-command-contract.d.ts +2 -2
  55. package/dist/runner-event-schema.d.ts +10 -10
  56. package/dist/runner.d.ts +7 -0
  57. package/dist/runner.js +53 -19
  58. package/dist/runtime/contracts/contracts.d.ts +20 -1
  59. package/dist/runtime/contracts/index.d.ts +1 -1
  60. package/dist/runtime/durable-store/durable-store.js +53 -0
  61. package/dist/runtime/durable-store/postgres/postgres-store.js +133 -0
  62. package/dist/runtime/durable-store/postgres/schema.js +31 -0
  63. package/dist/runtime/events/events.js +1 -1
  64. package/dist/runtime/gates/adjudication/llm-judge.js +56 -0
  65. package/dist/runtime/gates/adjudication/structured-claim.js +52 -0
  66. package/dist/runtime/gates/adjudicator/adjudicator.js +69 -0
  67. package/dist/runtime/gates/adjudicator/index.js +39 -0
  68. package/dist/runtime/gates/contract.js +25 -0
  69. package/dist/runtime/gates/gates.js +15 -368
  70. package/dist/runtime/gates/index.js +3 -0
  71. package/dist/runtime/gates/kinds/acceptance/acceptance.js +109 -0
  72. package/dist/runtime/gates/kinds/acceptance/index.js +9 -0
  73. package/dist/runtime/gates/kinds/artifact/artifact.js +20 -0
  74. package/dist/runtime/gates/kinds/artifact/index.js +9 -0
  75. package/dist/runtime/gates/kinds/builtin/builtin.js +21 -0
  76. package/dist/runtime/gates/kinds/builtin/index.js +9 -0
  77. package/dist/runtime/gates/kinds/changed-files/changed-files.js +61 -0
  78. package/dist/runtime/gates/kinds/changed-files/index.js +9 -0
  79. package/dist/runtime/gates/kinds/command/command.js +21 -0
  80. package/dist/runtime/gates/kinds/command/index.js +9 -0
  81. package/dist/runtime/gates/kinds/json-schema/index.js +9 -0
  82. package/dist/runtime/gates/kinds/json-schema/json-schema.js +32 -0
  83. package/dist/runtime/gates/kinds/verdict/index.js +9 -0
  84. package/dist/runtime/gates/kinds/verdict/verdict.js +32 -0
  85. package/dist/runtime/gates/orchestrator.js +161 -0
  86. package/dist/runtime/gates/registry.js +47 -0
  87. package/dist/runtime/json-validation/json-validation.js +1 -1
  88. package/dist/runtime/node-protocol/node-protocol.js +83 -0
  89. package/dist/runtime/opencode-session-executor.js +48 -8
  90. package/dist/runtime/protected-paths/protected-paths.js +97 -0
  91. package/dist/runtime/run-journal.d.ts +21 -0
  92. package/dist/runtime/scheduler.js +13 -8
  93. package/dist/runtime/services/opencode-sdk-service.js +4 -0
  94. package/dist/runtime/services/repo-io-service.d.ts +2 -0
  95. package/dist/runtime/services/runner-command-io-service.js +1 -1
  96. package/dist/tickets/backlog-task-store.d.ts +2 -0
  97. package/dist/tickets/completion/complete-ticket.js +96 -0
  98. package/dist/tickets/ticket-graph.d.ts +2 -0
  99. package/dist/tickets/ticket-selection.d.ts +1 -0
  100. package/docs/mcp-gateway.md +6 -6
  101. package/docs/moka-orchestrator-design.md +79 -0
  102. package/docs/okteto-runner.md +1 -1
  103. package/docs/operator-guide.md +8 -5
  104. package/docs/pipeline-console-runner-contract.md +8 -7
  105. package/package.json +5 -2
  106. package/dist/runtime/run-journal.js +0 -28
  107. package/dist/runtime/services/run-journal-file-service.js +0 -20
@@ -59,15 +59,6 @@ ecosystem_code:
59
59
  role: long-running goal mode with /goal slash command, persistent state, evidence-gated completion, and TUI sidebar
60
60
  default_stack: true
61
61
  source: https://www.npmjs.com/package/@prevalentware/opencode-goal-plugin
62
- - id: oc-codex-multi-auth
63
- name: oc-codex-multi-auth
64
- package: oc-codex-multi-auth
65
- plugin:
66
- kind: npm
67
- package: oc-codex-multi-auth@6.3.2
68
- role: ChatGPT Plus/Pro OAuth with multi-account rotation, health checks, and Codex/GPT-5 routing for OpenCode
69
- default_stack: true
70
- source: https://github.com/ndycode/oc-codex-multi-auth
71
62
  - id: opencode-snip
72
63
  name: opencode-snip
73
64
  role: prompt snippet and command-discipline helper patterns
@@ -86,9 +77,8 @@ ecosystem_code:
86
77
  # Reasoning effort is no longer expressed as synthetic per-effort model ids.
87
78
  # Pipeline config carries `reasoning_effort` as data on each node/profile and the
88
79
  # runner applies it as the opencode model variant (`--variant` / prompt body
89
- # `variant`) for the selected GPT-5 model. The oc-codex-multi-auth plugin owns
90
- # the base-model templates and the Codex request options (store:false,
91
- # reasoning.encrypted_content), so moka registers no provider models here.
80
+ # `variant`) for the selected GPT-5 model. Broker auth owns Codex request
81
+ # options, so moka registers no provider models here.
92
82
  mcp_backends:
93
83
  - id: pipeline-gateway
94
84
  locality: project-remote
@@ -11,10 +11,6 @@ context_handoff:
11
11
  # seeded by the node's task + handoff artifacts, within token_budget.
12
12
  repo_map:
13
13
  enabled: true
14
- # durability: journal each terminal node result so a killed run resumes from the
15
- # last passed node without re-running (or re-spending tokens on) finished work.
16
- durability:
17
- enabled: true
18
14
  # parallel_worktrees: opt-in git-worktree isolation for parallel child nodes —
19
15
  # each parallel child runs on its own branch with its own per-worktree opencode
20
16
  # server so concurrent edits and sessions can't collide. OFF by default; enable
@@ -20,13 +20,11 @@ declare const submitRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
20
20
  kubeconfigPath: z.ZodOptional<z.ZodString>;
21
21
  name: z.ZodOptional<z.ZodString>;
22
22
  namespace: z.ZodString;
23
- brokerAuth: z.ZodOptional<z.ZodObject<{
23
+ brokerAuth: z.ZodObject<{
24
24
  secretKey: z.ZodDefault<z.ZodString>;
25
25
  secretName: z.ZodString;
26
26
  url: z.ZodDefault<z.ZodString>;
27
- }, z.core.$strict>>;
28
- opencodeAuthSecretName: z.ZodOptional<z.ZodString>;
29
- opencodeOpenaiAccountsSecretName: z.ZodOptional<z.ZodString>;
27
+ }, z.core.$strict>;
30
28
  payloadJson: z.ZodString;
31
29
  scheduleYaml: z.ZodString;
32
30
  serviceAccountName: z.ZodOptional<z.ZodString>;
@@ -39,9 +39,7 @@ const submitRunnerArgoWorkflowOptionsSchema = z.object({
39
39
  kubeconfigPath: z.string().min(1).optional(),
40
40
  name: z.string().min(1).optional(),
41
41
  namespace: z.string().min(1),
42
- brokerAuth: brokerAuthOptionSchema.optional(),
43
- opencodeAuthSecretName: z.string().min(1).optional(),
44
- opencodeOpenaiAccountsSecretName: z.string().min(1).optional(),
42
+ brokerAuth: brokerAuthOptionSchema,
45
43
  payloadJson: z.string().min(1),
46
44
  scheduleYaml: z.string().min(1),
47
45
  serviceAccountName: z.string().min(1).optional()
@@ -92,8 +90,6 @@ function submitRunnerArgoWorkflowEffect(rawOptions, dependencies) {
92
90
  name: options.name,
93
91
  namespace: options.namespace,
94
92
  brokerAuth: options.brokerAuth,
95
- opencodeAuthSecretName: options.opencodeAuthSecretName,
96
- opencodeOpenaiAccountsSecret: options.opencodeOpenaiAccountsSecretName ? { name: options.opencodeOpenaiAccountsSecretName } : void 0,
97
93
  payloadConfigMapName,
98
94
  plan: compiled.plan,
99
95
  scheduleConfigMapName: scheduleArtifactConfigMapName,
@@ -145,16 +145,11 @@ declare const buildRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
145
145
  labels: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodString>>>;
146
146
  name: z.ZodOptional<z.ZodString>;
147
147
  namespace: z.ZodString;
148
- brokerAuth: z.ZodOptional<z.ZodObject<{
148
+ brokerAuth: z.ZodObject<{
149
149
  secretKey: z.ZodDefault<z.ZodString>;
150
150
  secretName: z.ZodString;
151
151
  url: z.ZodDefault<z.ZodString>;
152
- }, z.core.$strict>>;
153
- opencodeAuthSecretName: z.ZodOptional<z.ZodString>;
154
- opencodeOpenaiAccountsSecret: z.ZodOptional<z.ZodObject<{
155
- key: z.ZodOptional<z.ZodString>;
156
- name: z.ZodString;
157
- }, z.core.$strict>>;
152
+ }, z.core.$strict>;
158
153
  payloadConfigMapKey: z.ZodDefault<z.ZodString>;
159
154
  payloadConfigMapName: z.ZodString;
160
155
  resources: z.ZodOptional<z.ZodObject<{
@@ -1,5 +1,4 @@
1
1
  import { compileArgoExecutionGraph } from "./argo-graph.js";
2
- import { OPENCODE_AUTH_STAGING_DIR, OPENCODE_OPENAI_ACCOUNTS_STAGING_DIR } from "./run-state/opencode-accounts.js";
3
2
  import { DEFAULT_RUNNER_TASK_DESCRIPTOR_PATH } from "./runner-command/task-descriptor.js";
4
3
  import { stringify } from "yaml";
5
4
  import { z } from "zod";
@@ -27,6 +26,10 @@ const RUNNER_OPENCODE_ENV = [
27
26
  name: "PIPELINE_AGENT_TIMEOUT_MS",
28
27
  value: "600000"
29
28
  },
29
+ {
30
+ name: "PIPELINE_AGENT_IDLE_TIMEOUT_MS",
31
+ value: "180000"
32
+ },
30
33
  {
31
34
  name: "PIPELINE_DISABLED_MODELS",
32
35
  value: "opencode-go/qwen3.7-max"
@@ -180,12 +183,7 @@ const buildRunnerArgoWorkflowOptionsSchema = z.object({
180
183
  secretKey: z.string().min(1).default("api-key"),
181
184
  secretName: kubernetesNameSchema,
182
185
  url: z.string().min(1).default("https://cliproxy.momokaya.ee")
183
- }).strict().optional(),
184
- opencodeAuthSecretName: kubernetesNameSchema.optional(),
185
- opencodeOpenaiAccountsSecret: z.object({
186
- key: z.string().min(1).optional(),
187
- name: kubernetesNameSchema
188
- }).strict().optional(),
186
+ }).strict(),
189
187
  payloadConfigMapKey: z.string().min(1).default("payload.json"),
190
188
  payloadConfigMapName: kubernetesNameSchema,
191
189
  resources: argoWorkflowResourceRequirementsSchema.optional(),
@@ -312,43 +310,6 @@ function runnerWorkflowStorage(options, tasks) {
312
310
  readOnly: true
313
311
  });
314
312
  }
315
- if (options.opencodeAuthSecretName) {
316
- volumes.push({
317
- name: "opencode-auth",
318
- secret: {
319
- defaultMode: 256,
320
- items: [{
321
- key: "auth.json",
322
- path: "auth.json"
323
- }],
324
- secretName: options.opencodeAuthSecretName
325
- }
326
- });
327
- volumeMounts.push({
328
- mountPath: OPENCODE_AUTH_STAGING_DIR,
329
- name: "opencode-auth",
330
- readOnly: true
331
- });
332
- }
333
- if (options.opencodeOpenaiAccountsSecret) {
334
- const accountsKey = options.opencodeOpenaiAccountsSecret.key ?? "accounts.json";
335
- volumes.push({
336
- name: "opencode-openai-accounts",
337
- secret: {
338
- defaultMode: 256,
339
- items: [{
340
- key: accountsKey,
341
- path: "accounts.json"
342
- }],
343
- secretName: options.opencodeOpenaiAccountsSecret.name
344
- }
345
- });
346
- volumeMounts.push({
347
- mountPath: OPENCODE_OPENAI_ACCOUNTS_STAGING_DIR,
348
- name: "opencode-openai-accounts",
349
- readOnly: true
350
- });
351
- }
352
313
  if (options.gitCredentialsSecretName) {
353
314
  volumes.push({
354
315
  name: "runner-git-credentials",
@@ -387,18 +348,25 @@ function runnerWorkflowStorage(options, tasks) {
387
348
  };
388
349
  }
389
350
  /**
390
- * The runner container env: the static opencode/agent tuning plus, when broker
391
- * auth is configured, BROKER_URL (literal) and BROKER_API_KEY (sourced from the
392
- * broker secret key, never inlined into the manifest).
351
+ * The runner container env: static opencode/agent tuning plus BROKER_URL
352
+ * (literal) and BROKER_API_KEY (sourced from the broker secret key, never
353
+ * inlined into the manifest).
393
354
  */
394
355
  function runnerContainerEnv(options) {
395
- if (!options.brokerAuth) return [...RUNNER_OPENCODE_ENV];
396
356
  return [
397
357
  ...RUNNER_OPENCODE_ENV,
398
358
  {
399
359
  name: "BROKER_URL",
400
360
  value: options.brokerAuth.url
401
361
  },
362
+ {
363
+ name: "PIPELINE_BROKER_SECRET_NAME",
364
+ value: options.brokerAuth.secretName
365
+ },
366
+ {
367
+ name: "PIPELINE_BROKER_SECRET_KEY",
368
+ value: options.brokerAuth.secretKey
369
+ },
402
370
  {
403
371
  name: "BROKER_API_KEY",
404
372
  valueFrom: { secretKeyRef: {
@@ -39,9 +39,10 @@ const brokerAuthOptionSchema = z.object({
39
39
  url: z.string().min(1).default(DEFAULT_BROKER_URL)
40
40
  }).strict();
41
41
  /**
42
- * Resolve broker credentials from the environment, or `undefined` when the
43
- * runner is not broker-authenticated (local dev, non-broker fallback). The
44
- * `BROKER_URL` env is optional and defaults to the production broker origin.
42
+ * Resolve broker credentials from the environment, or `undefined` when a
43
+ * non-runner caller has not supplied broker auth. Remote runner manifests always
44
+ * inject BROKER_API_KEY. The `BROKER_URL` env is optional and defaults to the
45
+ * production broker origin.
45
46
  */
46
47
  function resolveBrokerCredentials(env = process.env) {
47
48
  const apiKey = env[BROKER_API_KEY_ENV];
@@ -1,12 +1,12 @@
1
1
  import { PipelineConfigError } from "../config/schemas.js";
2
2
  import { loadPipelineConfig } from "../config/load.js";
3
3
  import "../config.js";
4
- import { opencodeAgentName } from "../runtime/opencode-agent-name.js";
5
4
  import { loadMokaGlobalConfig } from "../moka-global-config.js";
5
+ import { opencodeAgentName } from "../runtime/opencode-agent-name.js";
6
6
  import { defaultClusterDoctorNamespace, runClusterDoctor } from "../cluster-doctor.js";
7
7
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
8
- import { join } from "node:path";
9
8
  import { execa } from "execa";
9
+ import { join } from "node:path";
10
10
  import matter from "gray-matter";
11
11
  //#region src/cli/doctor.ts
12
12
  const HEADLESS_AGENT_PERMISSION_VALUES = new Set(["ask"]);
@@ -1,6 +1,6 @@
1
1
  import { RunEffort, RunMode, RunTarget } from "../run-control/contracts.js";
2
2
  import { RunCommand } from "./run-command.js";
3
- import { TicketCommandOptions } from "../commands/ticket-command.js";
3
+ import { TicketCommandOptions } from "../commands/ticket/shared.js";
4
4
  import { runPipelineFromConfig } from "../pipeline-runtime.js";
5
5
  import { Effect } from "effect";
6
6
  import { Command } from "commander";
@@ -1,5 +1,6 @@
1
1
  import { loadPipelineConfig } from "../config/load.js";
2
2
  import "../config.js";
3
+ import { loadMokaGlobalConfig } from "../moka-global-config.js";
3
4
  import { flattenNodes } from "../planning/graph.js";
4
5
  import { configureGatewayHosts, localGatewayStatus, reconcileGateway, renderGatewayConfig, runGatewayDoctor, startLocalGateway } from "../mcp/gateway.js";
5
6
  import { createOrchestratorLaunchPlan, createRunnerLaunchPlan } from "../runner.js";
@@ -17,9 +18,8 @@ import { registerTicketCommand } from "../commands/ticket-command.js";
17
18
  import { formatConfigLintWarning, lintPipelineConfig } from "../config/lint.js";
18
19
  import { parseLoopFlags, runLoopSubmit } from "../loop/loop-command.js";
19
20
  import { runLoopControllerEntrypoint } from "../loop/loop-controller-entrypoint.js";
20
- import { loadMokaGlobalConfig } from "../moka-global-config.js";
21
21
  import { formatPipelineInitResult, initPipelineProject } from "../pipeline-init.js";
22
- import { createRun, runControlStatusPaths, updateRunController } from "../run-control/store.js";
22
+ import { withRunControlStoreScoped } from "../run-control/run-control-store.js";
23
23
  import { registerRunControlCommands } from "../run-control/commands.js";
24
24
  import { startDetachedRunController } from "../run-control/detach.js";
25
25
  import { createRunStoreRuntimeReporter } from "../run-control/runtime-reporter.js";
@@ -117,8 +117,14 @@ async function runConfiguredPipeline(rawInputs) {
117
117
  });
118
118
  }
119
119
  async function runAndPrintPipeline(inputs) {
120
+ await Effect.runPromise(withRunControlStoreScoped(inputs.worktreePath, (store) => Effect.tryPromise({
121
+ catch: (error) => error,
122
+ try: () => runAndPrintPipelineWithStore(inputs, store)
123
+ })));
124
+ }
125
+ async function runAndPrintPipelineWithStore(inputs, store) {
120
126
  const runner = inputs.pipelineRunner ?? runPipelineFromConfig;
121
- const runStoreReporter = await createRunStoreReporter(inputs, createTerminalRuntimeReporter());
127
+ const runStoreReporter = await createRunStoreReporter(inputs, createTerminalRuntimeReporter(), store);
122
128
  if (inputs.supervised) console.log(formatSupervisedRunFollowUp(requireRunId(inputs.runId)));
123
129
  let result;
124
130
  try {
@@ -144,27 +150,28 @@ async function runWithFlushedReporter(flush, run) {
144
150
  await flush();
145
151
  }
146
152
  }
147
- async function createLocalRunStoreRuntimeReporter(inputs, reporter) {
153
+ async function createLocalRunStoreRuntimeReporter(inputs, reporter, store) {
148
154
  const runId = requireRunId(inputs.runId);
149
- await createRun({
155
+ await Effect.runPromise(store.createRun({
150
156
  ...resolvedRunControlOptions(inputs.runControl),
151
157
  nodeIds: plannedRunStoreNodeIds(inputs),
152
- runId,
153
- workspaceRoot: inputs.worktreePath
154
- });
158
+ runId
159
+ }));
155
160
  return createRunStoreRuntimeReporter({
156
161
  reporter,
157
162
  runId,
163
+ store,
158
164
  workspaceRoot: inputs.worktreePath
159
165
  });
160
166
  }
161
- function createRunStoreReporter(inputs, reporter) {
167
+ function createRunStoreReporter(inputs, reporter, store) {
162
168
  if (inputs.runStoreMode === "reuse") {
163
169
  const runId = requireRunId(inputs.runId);
164
170
  if (inputs.supervisor) {
165
171
  const supervisor = createRunControlSupervisor({
166
172
  reporter,
167
173
  runId,
174
+ store,
168
175
  workspaceRoot: inputs.worktreePath
169
176
  });
170
177
  supervisor.start();
@@ -176,10 +183,11 @@ function createRunStoreReporter(inputs, reporter) {
176
183
  return createRunStoreRuntimeReporter({
177
184
  reporter,
178
185
  runId,
186
+ store,
179
187
  workspaceRoot: inputs.worktreePath
180
188
  });
181
189
  }
182
- return createLocalRunStoreRuntimeReporter(inputs, reporter);
190
+ return createLocalRunStoreRuntimeReporter(inputs, reporter, store);
183
191
  }
184
192
  function requireRunId(runId) {
185
193
  if (!runId) throw new Error("Run id is required for local run-control persistence.");
@@ -343,7 +351,7 @@ function createCliProgram(options = {}) {
343
351
  dryRun: flags.dryRun
344
352
  }));
345
353
  });
346
- program.command("codex-auth").description("Manage local Codex multi-auth integration").command("sync-local").description("Use one local oc-codex account pool and declare the plugin in dev repos").option("--root <path>", "directory containing repositories to sync").option("--dry-run", "show planned changes without writing files").option("--check", "fail if local Codex auth config is not synced").action((flags) => {
354
+ program.command("codex-auth").description("Manage local Codex broker auth integration").command("sync-local").description("Point local dev repos' opencode openai provider at the central CLIProxyAPI broker").option("--root <path>", "directory containing repositories to sync").option("--dry-run", "show planned changes without writing files").option("--check", "fail if local Codex auth config is not synced").action((flags) => {
347
355
  const result = syncLocalCodexAuth({
348
356
  check: flags.check,
349
357
  dryRun: flags.dryRun,
@@ -352,12 +360,7 @@ function createCliProgram(options = {}) {
352
360
  console.log(formatCodexAuthSyncResult(result));
353
361
  if (!result.ok) process.exitCode = 1;
354
362
  });
355
- addMokaSubmitOptions(program.command("submit").description([
356
- "Submit work to Momokaya as an Argo Workflow.",
357
- "Compatibility alias for `moka run \"<task>\" --target remote --effort thorough`.",
358
- "Quick equivalent: `moka run \"<task>\" --target remote --effort quick`.",
359
- "Command equivalent: `moka run --target remote --command -- <argv...>`."
360
- ].join("\n")).argument("[input...]", "task description, or command argv with --command")).action(async (input, flags) => {
363
+ addMokaSubmitOptions(program.command("submit").description("Submit work to Momokaya as an Argo Workflow.").argument("[input...]", "task description, or command argv with --command")).action(async (input, flags) => {
361
364
  printMokaSubmitResult(await runMokaSubmitFromCli(input, flags));
362
365
  });
363
366
  registerLoopCommand(program);
@@ -407,7 +410,10 @@ function buildLoopSubmitInput(options) {
407
410
  const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
408
411
  const config = loadPipelineConfig(cwd, { allowMissingLintFileReferences: true });
409
412
  const momokaya = loadMokaGlobalConfig()?.momokaya;
413
+ const brokerAuth = momokaya?.submit.brokerAuth;
414
+ if (!brokerAuth) throw new Error("momokaya.submit.brokerAuth is required for moka loop submit");
410
415
  return {
416
+ brokerAuth,
411
417
  config,
412
418
  eventUrl: momokaya?.submit.eventUrl,
413
419
  flags: parseLoopFlags(options),
@@ -415,9 +421,6 @@ function buildLoopSubmitInput(options) {
415
421
  githubAuthSecretName: momokaya?.submit.githubAuthSecretName,
416
422
  kubeconfigPath: momokaya?.kubernetes.kubeconfig,
417
423
  namespace: momokaya?.kubernetes.namespace,
418
- brokerAuth: momokaya?.submit.brokerAuth,
419
- opencodeAuthSecretName: momokaya?.submit.opencodeAuthSecretName,
420
- opencodeOpenaiAccountsSecretName: momokaya?.submit.opencodeOpenaiAccountsSecretName,
421
424
  serviceAccountName: momokaya?.submit.serviceAccountName,
422
425
  worktreePath: cwd
423
426
  };
@@ -441,43 +444,43 @@ async function runDetachedResolvedTask(task, execution, runControl) {
441
444
  task,
442
445
  worktreePath
443
446
  });
444
- await createRun({
445
- ...resolvedRunControlOptions(runControl),
446
- nodeIds: plannedRunStoreNodeIds({
447
- config: prepared.config,
448
- entrypoint: prepared.entrypoint,
449
- runId,
450
- runControl,
451
- schedule: prepared.schedule,
452
- task,
453
- workflow: prepared.workflow,
454
- worktreePath
455
- }),
456
- runId,
457
- workspaceRoot: worktreePath
458
- });
459
- const launch = await startDetachedRunController({
460
- entrypoint: prepared.entrypoint,
461
- runId,
462
- schedule: prepared.schedule,
463
- task,
464
- workflow: prepared.workflow,
465
- workspaceRoot: worktreePath
466
- });
467
- await updateRunController({
468
- controller: {
469
- argv: launch.argv,
470
- cwd: worktreePath,
471
- paths: runControlStatusPaths({
447
+ await Effect.runPromise(withRunControlStoreScoped(worktreePath, (store) => Effect.gen(function* () {
448
+ yield* store.createRun({
449
+ ...resolvedRunControlOptions(runControl),
450
+ nodeIds: plannedRunStoreNodeIds({
451
+ config: prepared.config,
452
+ entrypoint: prepared.entrypoint,
472
453
  runId,
473
- workspaceRoot: worktreePath
454
+ runControl,
455
+ schedule: prepared.schedule,
456
+ task,
457
+ workflow: prepared.workflow,
458
+ worktreePath
474
459
  }),
475
- pid: launch.pid,
476
- startedAt: launch.startedAt
477
- },
478
- runId,
479
- workspaceRoot: worktreePath
480
- });
460
+ runId
461
+ });
462
+ const launch = yield* Effect.tryPromise({
463
+ catch: (error) => error,
464
+ try: () => startDetachedRunController({
465
+ entrypoint: prepared.entrypoint,
466
+ runId,
467
+ schedule: prepared.schedule,
468
+ task,
469
+ workflow: prepared.workflow,
470
+ workspaceRoot: worktreePath
471
+ })
472
+ });
473
+ yield* store.updateRunController({
474
+ controller: {
475
+ argv: launch.argv,
476
+ cwd: worktreePath,
477
+ paths: store.statusPaths({ runId }),
478
+ pid: launch.pid,
479
+ startedAt: launch.startedAt
480
+ },
481
+ runId
482
+ });
483
+ })));
481
484
  console.log(formatDetachedRunFollowUp(runId));
482
485
  }
483
486
  async function prepareDetachedRun(input) {
@@ -1,7 +1,7 @@
1
1
  import { loadPipelineConfig } from "../config/load.js";
2
2
  import "../config.js";
3
- import { submitMoka } from "../moka-submit.js";
4
3
  import { loadMokaGlobalConfig } from "../moka-global-config.js";
4
+ import { submitMoka } from "../moka-submit.js";
5
5
  import { Option } from "commander";
6
6
  //#region src/cli/submit-options.ts
7
7
  function addMokaSubmitOptions(command) {
@@ -34,6 +34,7 @@ function resolveMokaEventUrl(flags, globalConfig) {
34
34
  function mokaCommonSubmitOptions(input) {
35
35
  const momokaya = input.globalConfig?.momokaya;
36
36
  return {
37
+ brokerAuth: resolveMokaBrokerAuth(input.globalConfig),
37
38
  config: input.config,
38
39
  delivery: { pullRequest: input.flags.openPr === true },
39
40
  eventUrl: input.eventUrl,
@@ -48,13 +49,15 @@ function mokaCommonSubmitOptions(input) {
48
49
  kubeconfigPath: input.flags.kubeconfig ?? momokaya?.kubernetes.kubeconfig,
49
50
  name: input.flags.name,
50
51
  namespace: input.flags.namespace ?? momokaya?.kubernetes.namespace,
51
- brokerAuth: momokaya?.submit.brokerAuth,
52
- opencodeAuthSecretName: momokaya?.submit.opencodeAuthSecretName,
53
- opencodeOpenaiAccountsSecretName: momokaya?.submit.opencodeOpenaiAccountsSecretName,
54
52
  serviceAccountName: input.flags.serviceAccount ?? momokaya?.submit.serviceAccountName,
55
53
  worktreePath: input.cwd
56
54
  };
57
55
  }
56
+ function resolveMokaBrokerAuth(globalConfig) {
57
+ const brokerAuth = globalConfig?.momokaya.submit.brokerAuth;
58
+ if (!brokerAuth) throw new Error("momokaya.submit.brokerAuth is required for remote submit");
59
+ return brokerAuth;
60
+ }
58
61
  function submitMokaCommandInput(input, flags, commonOptions) {
59
62
  if (flags.quick || flags.schedule) throw new Error("--command cannot be combined with --quick or --schedule");
60
63
  if (input.length === 0) throw new Error("Command argv is required when --command is set");
@@ -1,16 +1,16 @@
1
- import { KubernetesArgoService, KubernetesArgoServiceLive } from "./runtime/services/kubernetes-argo-service.js";
2
1
  import { loadMokaGlobalConfig } from "./moka-global-config.js";
2
+ import { KubernetesArgoService, KubernetesArgoServiceLive } from "./runtime/services/kubernetes-argo-service.js";
3
3
  import { Effect } from "effect";
4
4
  //#region src/cluster-doctor.ts
5
5
  const DEFAULT_NAMESPACE = "momokaya-pipeline";
6
6
  const DEFAULT_RESOURCES = {
7
+ brokerAuthSecretName: "broker-api-key",
7
8
  eventAuthSecretName: "pipeline-runner-event-auth",
8
9
  eventAuthExternalSecretName: "pipeline-runner-event-auth",
9
10
  externalSecretRemoteRef: "agent-runtime/pipeline-runner/event-auth",
10
11
  gitCredentialsSecretName: "oisin-bot-git-credentials",
11
12
  githubAuthSecretName: "oisin-bot-github-auth",
12
13
  imagePullSecretName: "ghcr-pull-secret",
13
- opencodeAuthSecretName: "opencode-auth-1",
14
14
  serviceAccountName: "pipeline-runner"
15
15
  };
16
16
  const FORBIDDEN_RE = /forbidden/i;
@@ -62,17 +62,16 @@ function clusterResources() {
62
62
  const configured = loadMokaGlobalConfig()?.momokaya.submit;
63
63
  return configured ? {
64
64
  ...DEFAULT_RESOURCES,
65
- brokerAuthSecretName: configured.brokerAuth?.secretName,
65
+ brokerAuthSecretName: configured.brokerAuth?.secretName ?? DEFAULT_RESOURCES.brokerAuthSecretName,
66
66
  eventAuthSecretName: configured.eventAuthSecretName,
67
67
  gitCredentialsSecretName: configured.gitCredentialsSecretName,
68
68
  githubAuthSecretName: configured.githubAuthSecretName,
69
69
  imagePullSecretName: configured.imagePullSecretName,
70
- opencodeAuthSecretName: configured.opencodeAuthSecretName ?? DEFAULT_RESOURCES.opencodeAuthSecretName,
71
70
  serviceAccountName: configured.serviceAccountName
72
71
  } : DEFAULT_RESOURCES;
73
72
  }
74
73
  function secretChecks(namespace, kubectlOptions, resources) {
75
- const authSecretCheck = resources.brokerAuthSecretName ? [resources.brokerAuthSecretName, `Secret ${resources.brokerAuthSecretName} missing in ${namespace}; expected broker api-key mount by name.`] : [resources.opencodeAuthSecretName, `Secret ${resources.opencodeAuthSecretName} missing in ${namespace}; expected OpenCode auth mount by name.`];
74
+ const authSecretCheck = [resources.brokerAuthSecretName, `Secret ${resources.brokerAuthSecretName} missing in ${namespace}; expected broker api-key mount by name.`];
76
75
  return [
77
76
  [resources.eventAuthSecretName, eventAuthMissingDetail(namespace)],
78
77
  [resources.imagePullSecretName, `Secret ${resources.imagePullSecretName} missing in ${namespace}; expected imagePullSecret for ghcr.io/oisin-ee/pipeline-runner.`],
@@ -1,15 +1,25 @@
1
1
  import { applyOpencodeBrokerProvider, resolveBrokerCredentials } from "./broker-auth.js";
2
- import { mergeOpenCodeProjectConfig } from "./opencode-project-config.js";
3
2
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
4
- import { homedir } from "node:os";
5
3
  import { dirname, join } from "node:path";
6
- import { parse } from "jsonc-parser";
7
4
  //#region src/codex-auth-sync.ts
8
- const CODEX_MULTI_AUTH_PLUGIN = "oc-codex-multi-auth@6.3.2";
9
- const GLOBAL_CODEX_AUTH_CONFIG_PATH = join(homedir(), ".opencode/openai-codex-auth-config.json");
5
+ /**
6
+ * Point each local dev repo's opencode openai provider at the central
7
+ * CLIProxyAPI broker. codex + opencode authenticate through the broker
8
+ * (which owns OAuth refresh / rotation / failover), so there is no per-project
9
+ * account pool to declare. Requires BROKER_API_KEY in the env (or an explicit
10
+ * `broker` override).
11
+ */
10
12
  function syncLocalCodexAuth(options) {
11
13
  const broker = options.broker === void 0 ? resolveBrokerCredentials() : options.broker;
12
- const items = broker ? discoverGitRepositories(options.root).map((repo) => syncProjectBrokerConfig(repo, broker, options)) : [syncGlobalPluginConfig(options.globalConfigPath ?? GLOBAL_CODEX_AUTH_CONFIG_PATH, options), ...discoverGitRepositories(options.root).map((repo) => syncProjectOpenCodeConfig(repo, options))];
14
+ if (!broker) return {
15
+ items: [{
16
+ action: "error",
17
+ message: "BROKER_API_KEY is required: codex + opencode authenticate through the central CLIProxyAPI broker.",
18
+ path: options.root
19
+ }],
20
+ ok: false
21
+ };
22
+ const items = discoverGitRepositories(options.root).map((repo) => syncProjectBrokerConfig(repo, broker, options));
13
23
  const hasRequiredChanges = items.some((item) => item.action === "create" || item.action === "update");
14
24
  const hasErrors = items.some((item) => item.action === "error");
15
25
  const checkFailed = options.check === true && hasRequiredChanges;
@@ -26,30 +36,6 @@ function formatCodexAuthSyncResult(result) {
26
36
  if (!result.ok) lines.push("codex-auth sync-local check failed");
27
37
  return lines.join("\n");
28
38
  }
29
- function syncGlobalPluginConfig(path, options) {
30
- const currentText = existsSync(path) ? readFileSync(path, "utf8") : void 0;
31
- const parsed = parseJsonObject(currentText ?? "{}");
32
- if (!parsed.ok) return {
33
- action: "error",
34
- message: formatParseErrors(parsed.errors),
35
- path
36
- };
37
- return writeIfChanged(path, currentText, `${JSON.stringify({
38
- ...parsed.value,
39
- perProjectAccounts: false
40
- }, null, 2)}\n`, options);
41
- }
42
- function syncProjectOpenCodeConfig(repo, options) {
43
- const path = join(repo, ".opencode/opencode.json");
44
- const currentText = existsSync(path) ? readFileSync(path, "utf8") : void 0;
45
- const merged = mergeOpenCodeProjectConfig(currentText, { plugin: [CODEX_MULTI_AUTH_PLUGIN] });
46
- if (!merged.ok) return {
47
- action: "error",
48
- message: formatParseErrors(merged.errors),
49
- path
50
- };
51
- return writeIfChanged(path, currentText, merged.content, options);
52
- }
53
39
  function syncProjectBrokerConfig(repo, broker, options) {
54
40
  const path = join(repo, ".opencode/opencode.json");
55
41
  const currentText = existsSync(path) ? readFileSync(path, "utf8") : void 0;
@@ -86,26 +72,5 @@ function isDirectory(path) {
86
72
  return false;
87
73
  }
88
74
  }
89
- function parseJsonObject(content) {
90
- const errors = [];
91
- const value = parse(content, errors, {
92
- allowTrailingComma: true,
93
- disallowComments: false
94
- });
95
- if (errors.length > 0 || !isRecord(value)) return {
96
- errors,
97
- ok: false
98
- };
99
- return {
100
- ok: true,
101
- value
102
- };
103
- }
104
- function formatParseErrors(errors) {
105
- return errors.length > 0 ? `invalid JSONC (${errors.length} parse error${errors.length === 1 ? "" : "s"})` : "expected a JSON object";
106
- }
107
- function isRecord(value) {
108
- return typeof value === "object" && value !== null && !Array.isArray(value);
109
- }
110
75
  //#endregion
111
76
  export { formatCodexAuthSyncResult, syncLocalCodexAuth };