@oisincoveney/pipeline 3.12.4 → 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 +5 -2
  4. package/dist/argo-submit.js +3 -4
  5. package/dist/argo-workflow.d.ts +15 -7
  6. package/dist/argo-workflow.js +52 -50
  7. package/dist/broker-auth.d.ts +16 -0
  8. package/dist/broker-auth.js +173 -0
  9. package/dist/cli/doctor.js +2 -2
  10. package/dist/cli/program.d.ts +1 -1
  11. package/dist/cli/program.js +58 -54
  12. package/dist/cli/submit-options.js +7 -3
  13. package/dist/cluster-doctor.js +5 -4
  14. package/dist/codex-auth-sync.js +23 -45
  15. package/dist/commands/ticket/complete.js +61 -0
  16. package/dist/commands/ticket/create.js +115 -0
  17. package/dist/commands/ticket/graph-check.js +16 -0
  18. package/dist/commands/ticket/next.js +43 -0
  19. package/dist/commands/ticket/sequence.js +18 -0
  20. package/dist/commands/ticket/shared.d.ts +14 -0
  21. package/dist/commands/ticket/shared.js +77 -0
  22. package/dist/commands/ticket/start.js +94 -0
  23. package/dist/commands/ticket-command.d.ts +2 -15
  24. package/dist/commands/ticket-command.js +14 -319
  25. package/dist/config/load.js +28 -7
  26. package/dist/config/schemas.d.ts +18 -21
  27. package/dist/config/schemas.js +2 -7
  28. package/dist/gates.js +1 -1
  29. package/dist/install-commands/claude-code.js +1 -1
  30. package/dist/install-commands/opencode.js +19 -1
  31. package/dist/install-hooks.js +2 -2
  32. package/dist/install-rules.js +1 -1
  33. package/dist/loop/controller-deps.js +1 -2
  34. package/dist/loop/loop-command.js +1 -2
  35. package/dist/loop/loop-controller-entrypoint.js +7 -4
  36. package/dist/moka-global-config.d.ts +22 -3
  37. package/dist/moka-global-config.js +43 -4
  38. package/dist/moka-submit.d.ts +19 -13
  39. package/dist/moka-submit.js +3 -4
  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 +33 -78
  53. package/dist/runner-command/run.js +29 -12
  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,8 +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
- opencodeAuthSecretName: z.ZodOptional<z.ZodString>;
24
- opencodeOpenaiAccountsSecretName: z.ZodOptional<z.ZodString>;
23
+ brokerAuth: z.ZodObject<{
24
+ secretKey: z.ZodDefault<z.ZodString>;
25
+ secretName: z.ZodString;
26
+ url: z.ZodDefault<z.ZodString>;
27
+ }, z.core.$strict>;
25
28
  payloadJson: z.ZodString;
26
29
  scheduleYaml: z.ZodString;
27
30
  serviceAccountName: z.ZodOptional<z.ZodString>;
@@ -1,4 +1,5 @@
1
1
  import { ArgoGraphCompilerError, compileArgoExecutionGraph } from "./argo-graph.js";
2
+ import { brokerAuthOptionSchema } from "./broker-auth.js";
2
3
  import { parseRunnerCommandPayload, runnerCommandPayloadSchema } from "./runner-command-contract.js";
3
4
  import { buildRunnerTaskDescriptor } from "./runner-command/task-descriptor.js";
4
5
  import { buildRunnerArgoWorkflowManifest, runnerArgoWorkflowManifestSchema } from "./argo-workflow.js";
@@ -38,8 +39,7 @@ const submitRunnerArgoWorkflowOptionsSchema = z.object({
38
39
  kubeconfigPath: z.string().min(1).optional(),
39
40
  name: z.string().min(1).optional(),
40
41
  namespace: z.string().min(1),
41
- opencodeAuthSecretName: z.string().min(1).optional(),
42
- opencodeOpenaiAccountsSecretName: z.string().min(1).optional(),
42
+ brokerAuth: brokerAuthOptionSchema,
43
43
  payloadJson: z.string().min(1),
44
44
  scheduleYaml: z.string().min(1),
45
45
  serviceAccountName: z.string().min(1).optional()
@@ -89,8 +89,7 @@ function submitRunnerArgoWorkflowEffect(rawOptions, dependencies) {
89
89
  labels,
90
90
  name: options.name,
91
91
  namespace: options.namespace,
92
- opencodeAuthSecretName: options.opencodeAuthSecretName,
93
- opencodeOpenaiAccountsSecret: options.opencodeOpenaiAccountsSecretName ? { name: options.opencodeOpenaiAccountsSecretName } : void 0,
92
+ brokerAuth: options.brokerAuth,
94
93
  payloadConfigMapName,
95
94
  plan: compiled.plan,
96
95
  scheduleConfigMapName: scheduleArtifactConfigMapName,
@@ -27,10 +27,18 @@ declare const runnerArgoWorkflowManifestSchema: z.ZodObject<{
27
27
  container: z.ZodOptional<z.ZodObject<{
28
28
  args: z.ZodArray<z.ZodString>;
29
29
  command: z.ZodOptional<z.ZodArray<z.ZodString>>;
30
- env: z.ZodOptional<z.ZodArray<z.ZodObject<{
30
+ env: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
31
31
  name: z.ZodString;
32
32
  value: z.ZodString;
33
- }, z.core.$strict>>>;
33
+ }, z.core.$strict>, z.ZodObject<{
34
+ name: z.ZodString;
35
+ valueFrom: z.ZodObject<{
36
+ secretKeyRef: z.ZodObject<{
37
+ key: z.ZodString;
38
+ name: z.ZodString;
39
+ }, z.core.$strict>;
40
+ }, z.core.$strict>;
41
+ }, z.core.$strict>]>>>;
34
42
  image: z.ZodString;
35
43
  imagePullPolicy: z.ZodEnum<{
36
44
  Always: "Always";
@@ -137,11 +145,11 @@ declare const buildRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
137
145
  labels: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodString>>>;
138
146
  name: z.ZodOptional<z.ZodString>;
139
147
  namespace: z.ZodString;
140
- opencodeAuthSecretName: z.ZodOptional<z.ZodString>;
141
- opencodeOpenaiAccountsSecret: z.ZodOptional<z.ZodObject<{
142
- key: z.ZodOptional<z.ZodString>;
143
- name: z.ZodString;
144
- }, z.core.$strict>>;
148
+ brokerAuth: z.ZodObject<{
149
+ secretKey: z.ZodDefault<z.ZodString>;
150
+ secretName: z.ZodString;
151
+ url: z.ZodDefault<z.ZodString>;
152
+ }, z.core.$strict>;
145
153
  payloadConfigMapKey: z.ZodDefault<z.ZodString>;
146
154
  payloadConfigMapName: z.ZodString;
147
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"
@@ -92,14 +95,21 @@ const argoWorkflowRetryStrategySchema = z.object({
92
95
  "OnTransientError"
93
96
  ])
94
97
  }).strict();
98
+ const argoWorkflowEnvVarSchema = z.union([z.object({
99
+ name: z.string().min(1),
100
+ value: z.string()
101
+ }).strict(), z.object({
102
+ name: z.string().min(1),
103
+ valueFrom: z.object({ secretKeyRef: z.object({
104
+ key: z.string().min(1),
105
+ name: kubernetesNameSchema
106
+ }).strict() }).strict()
107
+ }).strict()]);
95
108
  const argoWorkflowTemplateSchema = z.object({
96
109
  container: z.object({
97
110
  args: z.array(z.string().min(1)).min(1),
98
111
  command: z.array(z.string().min(1)).min(1).optional(),
99
- env: z.array(z.object({
100
- name: z.string().min(1),
101
- value: z.string()
102
- }).strict()).optional(),
112
+ env: z.array(argoWorkflowEnvVarSchema).optional(),
103
113
  image: z.string().min(1),
104
114
  imagePullPolicy: z.enum([
105
115
  "Always",
@@ -169,11 +179,11 @@ const buildRunnerArgoWorkflowOptionsSchema = z.object({
169
179
  labels: z.record(z.string().min(1), z.string().min(1).optional()).default({}),
170
180
  name: z.string().min(1).optional(),
171
181
  namespace: kubernetesNameSchema,
172
- opencodeAuthSecretName: kubernetesNameSchema.optional(),
173
- opencodeOpenaiAccountsSecret: z.object({
174
- key: z.string().min(1).optional(),
175
- name: kubernetesNameSchema
176
- }).strict().optional(),
182
+ brokerAuth: z.object({
183
+ secretKey: z.string().min(1).default("api-key"),
184
+ secretName: kubernetesNameSchema,
185
+ url: z.string().min(1).default("https://cliproxy.momokaya.ee")
186
+ }).strict(),
177
187
  payloadConfigMapKey: z.string().min(1).default("payload.json"),
178
188
  payloadConfigMapName: kubernetesNameSchema,
179
189
  resources: argoWorkflowResourceRequirementsSchema.optional(),
@@ -300,43 +310,6 @@ function runnerWorkflowStorage(options, tasks) {
300
310
  readOnly: true
301
311
  });
302
312
  }
303
- if (options.opencodeAuthSecretName) {
304
- volumes.push({
305
- name: "opencode-auth",
306
- secret: {
307
- defaultMode: 256,
308
- items: [{
309
- key: "auth.json",
310
- path: "auth.json"
311
- }],
312
- secretName: options.opencodeAuthSecretName
313
- }
314
- });
315
- volumeMounts.push({
316
- mountPath: OPENCODE_AUTH_STAGING_DIR,
317
- name: "opencode-auth",
318
- readOnly: true
319
- });
320
- }
321
- if (options.opencodeOpenaiAccountsSecret) {
322
- const accountsKey = options.opencodeOpenaiAccountsSecret.key ?? "accounts.json";
323
- volumes.push({
324
- name: "opencode-openai-accounts",
325
- secret: {
326
- defaultMode: 256,
327
- items: [{
328
- key: accountsKey,
329
- path: "accounts.json"
330
- }],
331
- secretName: options.opencodeOpenaiAccountsSecret.name
332
- }
333
- });
334
- volumeMounts.push({
335
- mountPath: OPENCODE_OPENAI_ACCOUNTS_STAGING_DIR,
336
- name: "opencode-openai-accounts",
337
- readOnly: true
338
- });
339
- }
340
313
  if (options.gitCredentialsSecretName) {
341
314
  volumes.push({
342
315
  name: "runner-git-credentials",
@@ -374,6 +347,35 @@ function runnerWorkflowStorage(options, tasks) {
374
347
  volumes: z.array(argoWorkflowVolumeSchema).parse(volumes)
375
348
  };
376
349
  }
350
+ /**
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).
354
+ */
355
+ function runnerContainerEnv(options) {
356
+ return [
357
+ ...RUNNER_OPENCODE_ENV,
358
+ {
359
+ name: "BROKER_URL",
360
+ value: options.brokerAuth.url
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
+ },
370
+ {
371
+ name: "BROKER_API_KEY",
372
+ valueFrom: { secretKeyRef: {
373
+ key: options.brokerAuth.secretKey,
374
+ name: options.brokerAuth.secretName
375
+ } }
376
+ }
377
+ ];
378
+ }
377
379
  function runnerLifecycleTemplate(options, volumeMounts) {
378
380
  return {
379
381
  container: {
@@ -387,7 +389,7 @@ function runnerLifecycleTemplate(options, volumeMounts) {
387
389
  RUNNER_WORKFLOW_SCHEDULE_PATH
388
390
  ],
389
391
  command: ["moka"],
390
- env: [...RUNNER_OPENCODE_ENV],
392
+ env: runnerContainerEnv(options),
391
393
  image: options.image,
392
394
  imagePullPolicy: options.imagePullPolicy,
393
395
  name: "runner",
@@ -416,7 +418,7 @@ function runnerCommandTemplate(task, options, volumeMounts) {
416
418
  RUNNER_WORKFLOW_SCHEDULE_PATH
417
419
  ],
418
420
  command: ["moka"],
419
- env: [...RUNNER_OPENCODE_ENV],
421
+ env: runnerContainerEnv(options),
420
422
  image: options.image,
421
423
  imagePullPolicy: options.imagePullPolicy,
422
424
  name: "runner",
@@ -441,7 +443,7 @@ function runnerFinalizerTemplate(options, volumeMounts) {
441
443
  "{{workflow.status}}"
442
444
  ],
443
445
  command: ["moka"],
444
- env: [...RUNNER_OPENCODE_ENV],
446
+ env: runnerContainerEnv(options),
445
447
  image: options.image,
446
448
  imagePullPolicy: options.imagePullPolicy,
447
449
  name: "runner",
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/broker-auth.d.ts
4
+ /**
5
+ * Submit-time broker auth options: the runner sources BROKER_API_KEY from
6
+ * `secretName[secretKey]` and BROKER_URL from `url`. Shared by every submit
7
+ * entrypoint so the broker wiring is declared in exactly one place.
8
+ */
9
+ declare const brokerAuthOptionSchema: z.ZodObject<{
10
+ secretKey: z.ZodDefault<z.ZodString>;
11
+ secretName: z.ZodString;
12
+ url: z.ZodDefault<z.ZodString>;
13
+ }, z.core.$strict>;
14
+ type BrokerAuthOption = z.input<typeof brokerAuthOptionSchema>;
15
+ //#endregion
16
+ export { BrokerAuthOption };
@@ -0,0 +1,173 @@
1
+ import { applyJsonEdit, ensureTrailingNewline, formatJson, parseJsonRecord } from "./json-config-merge.js";
2
+ import { z } from "zod";
3
+ //#region src/broker-auth.ts
4
+ /**
5
+ * Central CLIProxyAPI broker auth for codex + opencode.
6
+ *
7
+ * When `BROKER_API_KEY` is present, codex and opencode authenticate through the
8
+ * central broker (an OpenAI-compatible `/v1` endpoint) instead of materializing
9
+ * the bespoke multi-auth account pool. The broker owns OAuth refresh / rotation
10
+ * / failover, so the runner no longer stages `oc-codex-multi-auth` accounts, the
11
+ * mounted `~/.codex/auth.json`, or the multi-auth opencode plugin.
12
+ *
13
+ * This mirrors the proven coder dev-workspace template
14
+ * (infra: coder-templates/dev-workspace/main.tf):
15
+ * - codex ~/.codex/config.toml: `model_provider = "broker"` +
16
+ * `[model_providers.broker]` base_url=<broker>/v1 env_key=BROKER_API_KEY
17
+ * wire_api="responses".
18
+ * - opencode global config: `provider.openai.options.baseURL=<broker>/v1`
19
+ * plus `store=false` and `include=["reasoning.encrypted_content"]` (required
20
+ * by the Codex/Responses backend the broker fronts).
21
+ * - opencode auth store: `{"openai":{"type":"api","key":<BROKER_API_KEY>}}`.
22
+ */
23
+ const BROKER_API_KEY_ENV = "BROKER_API_KEY";
24
+ const BROKER_URL_ENV = "BROKER_URL";
25
+ const DEFAULT_BROKER_URL = "https://cliproxy.momokaya.ee";
26
+ const TRAILING_SLASH_RE = /\/+$/;
27
+ const CODEX_BROKER_PROVIDER_ID = "broker";
28
+ const OPENCODE_OPENAI_PROVIDER_ID = "openai";
29
+ /** Plugin removed from opencode config in broker mode (broker fronts auth). */
30
+ const OC_CODEX_MULTI_AUTH_PLUGIN_NAME = "oc-codex-multi-auth";
31
+ /**
32
+ * Submit-time broker auth options: the runner sources BROKER_API_KEY from
33
+ * `secretName[secretKey]` and BROKER_URL from `url`. Shared by every submit
34
+ * entrypoint so the broker wiring is declared in exactly one place.
35
+ */
36
+ const brokerAuthOptionSchema = z.object({
37
+ secretKey: z.string().min(1).default("api-key"),
38
+ secretName: z.string().min(1),
39
+ url: z.string().min(1).default(DEFAULT_BROKER_URL)
40
+ }).strict();
41
+ /**
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.
46
+ */
47
+ function resolveBrokerCredentials(env = process.env) {
48
+ const apiKey = env[BROKER_API_KEY_ENV];
49
+ if (apiKey === void 0 || apiKey.length === 0) return;
50
+ const rawUrl = env[BROKER_URL_ENV];
51
+ return {
52
+ apiKey,
53
+ baseUrl: rawUrl !== void 0 && rawUrl.length > 0 ? rawUrl.replace(TRAILING_SLASH_RE, "") : DEFAULT_BROKER_URL
54
+ };
55
+ }
56
+ /** The broker's OpenAI-compatible endpoint (`<baseUrl>/v1`). */
57
+ function brokerV1Url(credentials) {
58
+ return `${credentials.baseUrl}/v1`;
59
+ }
60
+ /**
61
+ * opencode host auth store contents for broker mode — the `openai` provider
62
+ * authenticates with the broker api-key. Other providers in an existing store
63
+ * are intentionally NOT preserved here: in broker mode the runner owns this
64
+ * file outright (the multi-auth pool that previously populated it is gone).
65
+ */
66
+ function renderOpencodeBrokerAuthJson(credentials) {
67
+ return formatJson({ [OPENCODE_OPENAI_PROVIDER_ID]: {
68
+ key: credentials.apiKey,
69
+ type: "api"
70
+ } });
71
+ }
72
+ /**
73
+ * Inject the codex broker model provider into an existing `config.toml`,
74
+ * preserving every other section. Idempotent: re-running replaces the
75
+ * `[model_providers.broker]` block and the top-level `model_provider` key.
76
+ */
77
+ function applyCodexBrokerProvider(currentText, credentials) {
78
+ const withoutProvider = removeCodexBrokerSections(currentText ?? "");
79
+ const projection = renderCodexBrokerProvider(credentials);
80
+ return ensureTrailingNewline([withoutProvider.trimEnd(), projection.trimEnd()].filter(Boolean).join("\n\n"));
81
+ }
82
+ function renderCodexBrokerProvider(credentials) {
83
+ return [
84
+ `model_provider = "${CODEX_BROKER_PROVIDER_ID}"`,
85
+ "",
86
+ `[model_providers.${CODEX_BROKER_PROVIDER_ID}]`,
87
+ `name = "${CODEX_BROKER_PROVIDER_ID}"`,
88
+ `base_url = "${brokerV1Url(credentials)}"`,
89
+ `env_key = "${BROKER_API_KEY_ENV}"`,
90
+ "wire_api = \"responses\""
91
+ ].join("\n");
92
+ }
93
+ const CODEX_BROKER_SECTION_HEADER = `[model_providers.${CODEX_BROKER_PROVIDER_ID}]`;
94
+ const CODEX_MODEL_PROVIDER_KEY_RE = /^\s*model_provider\s*=/;
95
+ function removeCodexBrokerSections(content) {
96
+ const lines = content.split("\n");
97
+ const kept = [];
98
+ let removing = false;
99
+ for (const line of lines) {
100
+ if (line.trim() === CODEX_BROKER_SECTION_HEADER) {
101
+ removing = true;
102
+ continue;
103
+ }
104
+ if (removing && isTomlSectionHeader(line)) removing = false;
105
+ if (removing) continue;
106
+ if (CODEX_MODEL_PROVIDER_KEY_RE.test(line)) continue;
107
+ kept.push(line);
108
+ }
109
+ return kept.join("\n");
110
+ }
111
+ function isTomlSectionHeader(line) {
112
+ const trimmed = line.trim();
113
+ return trimmed.startsWith("[") && trimmed.endsWith("]");
114
+ }
115
+ /**
116
+ * Point an opencode config (global or project `opencode.json`) at the broker:
117
+ * set `provider.openai.options.{baseURL,store,include}` and drop the
118
+ * `oc-codex-multi-auth` plugin entry. Preserves all other config (models, mcp,
119
+ * other plugins). Creates a minimal config when `currentText` is undefined.
120
+ */
121
+ function applyOpencodeBrokerProvider(currentText, credentials) {
122
+ const options = {
123
+ baseURL: brokerV1Url(credentials),
124
+ include: ["reasoning.encrypted_content"],
125
+ store: false
126
+ };
127
+ if (currentText === void 0) return { content: formatJson({
128
+ $schema: "https://opencode.ai/config.json",
129
+ provider: { [OPENCODE_OPENAI_PROVIDER_ID]: { options } }
130
+ }) };
131
+ const parsed = parseJsonRecord(currentText);
132
+ if (!parsed.ok) return { error: "invalid opencode config JSON" };
133
+ const withProvider = applyJsonEdit(currentText, [
134
+ "provider",
135
+ OPENCODE_OPENAI_PROVIDER_ID,
136
+ "options"
137
+ ], mergeOpenaiOptions(parsed.value, options));
138
+ const nextPlugins = pluginsWithoutMultiAuth(parsed.value.plugin);
139
+ return { content: ensureTrailingNewline(nextPlugins === void 0 ? withProvider : applyJsonEdit(withProvider, ["plugin"], nextPlugins)) };
140
+ }
141
+ function mergeOpenaiOptions(parsed, options) {
142
+ return {
143
+ ...currentOpenaiOptions(parsed),
144
+ ...options
145
+ };
146
+ }
147
+ function currentOpenaiOptions(parsed) {
148
+ const provider = parsed.provider;
149
+ if (!isRecord(provider)) return {};
150
+ const openai = provider[OPENCODE_OPENAI_PROVIDER_ID];
151
+ if (!isRecord(openai)) return {};
152
+ return isRecord(openai.options) ? openai.options : {};
153
+ }
154
+ /**
155
+ * Return the plugin array with every `oc-codex-multi-auth` entry removed, or
156
+ * `undefined` when there was nothing to remove (so the caller can skip the
157
+ * edit). Handles bare-string and `[name, opts]` tuple plugin specifiers.
158
+ */
159
+ function pluginsWithoutMultiAuth(plugin) {
160
+ if (!Array.isArray(plugin)) return;
161
+ const filtered = plugin.filter((entry) => !isMultiAuthPlugin(entry));
162
+ return filtered.length === plugin.length ? void 0 : filtered;
163
+ }
164
+ function isMultiAuthPlugin(entry) {
165
+ const specifier = Array.isArray(entry) ? entry[0] : entry;
166
+ if (typeof specifier !== "string") return false;
167
+ return (specifier.indexOf("@", 1) === -1 ? specifier : specifier.slice(0, specifier.indexOf("@", 1))) === OC_CODEX_MULTI_AUTH_PLUGIN_NAME;
168
+ }
169
+ function isRecord(value) {
170
+ return typeof value === "object" && value !== null && !Array.isArray(value);
171
+ }
172
+ //#endregion
173
+ export { applyCodexBrokerProvider, applyOpencodeBrokerProvider, brokerAuthOptionSchema, renderOpencodeBrokerAuthJson, resolveBrokerCredentials };
@@ -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";