@oisincoveney/pipeline 3.11.15 → 3.11.17
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 +16 -13
- package/defaults/pipeline.yaml +5 -5
- package/dist/argo-workflow.js +3 -3
- package/dist/cli/doctor.js +1 -1
- package/dist/cli/program.js +8 -29
- package/dist/codex-auth-sync.js +1 -1
- package/dist/commands/pipeline-command.js +0 -2
- package/dist/gates.js +1 -1
- package/dist/install-commands/shared.js +1 -1
- package/dist/install-commands.js +1 -13
- package/dist/install-hooks.js +3 -6
- package/dist/install-rules.js +3 -3
- package/dist/mcp/gateway.js +1 -1
- package/dist/moka-global-config.js +1 -1
- package/dist/path-refs.js +1 -1
- package/dist/pipeline-init.js +39 -22
- package/dist/run-state/git-refs.js +1 -1
- package/dist/run-state/opencode-accounts.js +24 -0
- package/dist/runner-command/run.js +10 -0
- package/dist/runner.js +1 -1
- package/dist/runtime/hooks/hooks.js +1 -1
- package/dist/runtime/opencode-session-executor.js +52 -4
- package/dist/runtime/services/runner-command-io-service.js +5 -0
- package/docs/config-architecture.md +6 -6
- package/docs/mcp-gateway.md +3 -3
- package/docs/operator-guide.md +25 -51
- package/docs/slash-command-adapter-contract.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,10 +43,12 @@ Initialize package-owned pipeline support:
|
|
|
43
43
|
moka init
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
`moka init` installs the
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
`moka init` installs or refreshes the whole per-machine harness in one step:
|
|
47
|
+
the package's default skills, generated host command surfaces, the singleton
|
|
48
|
+
`pipeline-gateway` MCP entry, copied hook files from the private
|
|
49
|
+
`oisin-ee/agent-hooks` repository, and global instruction files. OpenCode is the
|
|
50
|
+
package default runtime. The command does not create repo-local `.pipeline`
|
|
51
|
+
config files.
|
|
50
52
|
|
|
51
53
|
The default MCP gateway can run locally or point at the hosted Momokaya gateway.
|
|
52
54
|
Set `PIPELINE_MCP_GATEWAY_AUTHORIZATION` to the full HTTP `Authorization` header
|
|
@@ -56,16 +58,17 @@ value before starting OpenCode when using a protected gateway:
|
|
|
56
58
|
export PIPELINE_MCP_GATEWAY_AUTHORIZATION="Basic $(printf '%s' 'user:password' | base64)"
|
|
57
59
|
```
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
Verify the generated harness (commands, hooks, rules) is current after package
|
|
62
|
+
upgrades or edits to `oisin-ee/agent-hooks`, without writing anything:
|
|
60
63
|
|
|
61
64
|
```shell
|
|
62
|
-
moka
|
|
65
|
+
moka init --check
|
|
63
66
|
```
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
Refresh it, overwriting any locally edited harness files:
|
|
66
69
|
|
|
67
70
|
```shell
|
|
68
|
-
moka
|
|
71
|
+
moka init --force
|
|
69
72
|
```
|
|
70
73
|
|
|
71
74
|
Check local prerequisites and config health:
|
|
@@ -103,10 +106,10 @@ Canonical commands:
|
|
|
103
106
|
- `moka stop <run-id> [node-id]`: abort a run or one active node.
|
|
104
107
|
- `moka export <run-id> --sanitize`: print a portable evidence bundle.
|
|
105
108
|
- `moka doctor`: check local prerequisites and config health.
|
|
106
|
-
- `moka init`: install
|
|
107
|
-
|
|
108
|
-
-
|
|
109
|
-
|
|
109
|
+
- `moka init`: install or refresh the whole per-machine harness (skills,
|
|
110
|
+
command surfaces, hooks, rules). `--check` verifies without writing,
|
|
111
|
+
`--dry-run` previews, `--force` overwrites locally edited files. The harness
|
|
112
|
+
is always installed globally; there is no `--scope`.
|
|
110
113
|
|
|
111
114
|
```shell
|
|
112
115
|
moka run "Implement PIPE-123 user-facing behavior"
|
|
@@ -118,7 +121,7 @@ moka run --effort normal "Implement a standard fix"
|
|
|
118
121
|
moka run --target remote --effort thorough "Submit a full hosted graph run"
|
|
119
122
|
moka run --read-only "Inspect the repository without edits"
|
|
120
123
|
moka run --target remote --command -- opencode run "fix this bug"
|
|
121
|
-
moka
|
|
124
|
+
moka init --force
|
|
122
125
|
```
|
|
123
126
|
|
|
124
127
|
Flag defaults and choices:
|
package/defaults/pipeline.yaml
CHANGED
|
@@ -83,12 +83,12 @@ runner_command:
|
|
|
83
83
|
# Set up package-owned pipeline support + the opencode model registration
|
|
84
84
|
# (.opencode/opencode.json, which declares the gpt-5.5-* reasoning selectors)
|
|
85
85
|
# on every run, so opencode-backed agents in the pod resolve their models
|
|
86
|
-
# instead of failing with "Model not found".
|
|
87
|
-
#
|
|
86
|
+
# instead of failing with "Model not found". `moka init` installs the full
|
|
87
|
+
# per-machine harness (skills + commands + hooks + rules); --force refreshes
|
|
88
|
+
# a pre-baked/version-skewed settings.json instead of refusing it. Idempotent,
|
|
89
|
+
# writes no repo-local pipeline config.
|
|
88
90
|
- command: moka
|
|
89
|
-
args: [init]
|
|
90
|
-
- command: moka
|
|
91
|
-
args: [install-commands]
|
|
91
|
+
args: [init, --force]
|
|
92
92
|
scheduler:
|
|
93
93
|
commands:
|
|
94
94
|
quick:
|
package/dist/argo-workflow.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { compileArgoExecutionGraph } from "./argo-graph.js";
|
|
2
|
+
import { OPENCODE_OPENAI_ACCOUNTS_STAGING_DIR } from "./run-state/opencode-accounts.js";
|
|
2
3
|
import { DEFAULT_RUNNER_TASK_DESCRIPTOR_PATH } from "./runner-command/task-descriptor.js";
|
|
3
4
|
import { stringify } from "yaml";
|
|
4
5
|
import { z } from "zod";
|
|
@@ -322,10 +323,9 @@ function runnerWorkflowStorage(options, tasks) {
|
|
|
322
323
|
}
|
|
323
324
|
});
|
|
324
325
|
volumeMounts.push({
|
|
325
|
-
mountPath:
|
|
326
|
+
mountPath: OPENCODE_OPENAI_ACCOUNTS_STAGING_DIR,
|
|
326
327
|
name: "opencode-openai-accounts",
|
|
327
|
-
readOnly: true
|
|
328
|
-
subPath: "accounts.json"
|
|
328
|
+
readOnly: true
|
|
329
329
|
});
|
|
330
330
|
}
|
|
331
331
|
if (options.gitCredentialsSecretName) {
|
package/dist/cli/doctor.js
CHANGED
|
@@ -5,8 +5,8 @@ import { opencodeAgentName } from "../runtime/opencode-agent-name.js";
|
|
|
5
5
|
import { loadMokaGlobalConfig } from "../moka-global-config.js";
|
|
6
6
|
import { defaultClusterDoctorNamespace, runClusterDoctor } from "../cluster-doctor.js";
|
|
7
7
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
8
|
-
import { execa } from "execa";
|
|
9
8
|
import { join } from "node:path";
|
|
9
|
+
import { execa } from "execa";
|
|
10
10
|
import matter from "gray-matter";
|
|
11
11
|
//#region src/cli/doctor.ts
|
|
12
12
|
const HEADLESS_AGENT_PERMISSION_VALUES = new Set(["ask"]);
|
package/dist/cli/program.js
CHANGED
|
@@ -15,9 +15,7 @@ import { registerRunnerCommandCommand } from "../commands/runner-command-command
|
|
|
15
15
|
import { MOKA_RUN_EFFORTS, MOKA_RUN_TARGETS, resolveMokaRun } from "./run-resolver.js";
|
|
16
16
|
import { registerTicketCommand } from "../commands/ticket-command.js";
|
|
17
17
|
import { formatConfigLintWarning, lintPipelineConfig } from "../config/lint.js";
|
|
18
|
-
import {
|
|
19
|
-
import { formatInstallHooksResult, installHooks } from "../install-hooks.js";
|
|
20
|
-
import { formatPipelineInitResult, formatRefreshAgentHarnessesResult, initPipelineProject, refreshAgentHarnesses } from "../pipeline-init.js";
|
|
18
|
+
import { formatPipelineInitResult, initPipelineProject } from "../pipeline-init.js";
|
|
21
19
|
import { createRun, runControlStatusPaths, updateRunController } from "../run-control/store.js";
|
|
22
20
|
import { registerRunControlCommands } from "../run-control/commands.js";
|
|
23
21
|
import { startDetachedRunController } from "../run-control/detach.js";
|
|
@@ -304,12 +302,7 @@ function createCliProgram(options = {}) {
|
|
|
304
302
|
const config = loadPipelineConfig(process.env.PIPELINE_TARGET_PATH ?? process.cwd(), { allowMissingLintFileReferences: true });
|
|
305
303
|
console.log(renderGatewayConfig(config));
|
|
306
304
|
});
|
|
307
|
-
gatewayCommand.command("configure-host").description("Rewrite host MCP config to the singleton pipeline gateway").addOption(new Option$1("--host <host>", "host config to update").choices([
|
|
308
|
-
"all",
|
|
309
|
-
"opencode",
|
|
310
|
-
"claude-code",
|
|
311
|
-
"codex"
|
|
312
|
-
]).default("all").argParser(parseGatewayHost)).addOption(new Option$1("--scope <scope>", "config scope to update").choices(["project", "global"]).default("project").argParser(parseGatewayHostScope)).action((flags) => {
|
|
305
|
+
gatewayCommand.command("configure-host").description("Rewrite host MCP config to the singleton pipeline gateway").addOption(new Option$1("--host <host>", "host config to update").choices(["all", "opencode"]).default("all").argParser(parseGatewayHost)).addOption(new Option$1("--scope <scope>", "config scope to update").choices(["project", "global"]).default("project").argParser(parseGatewayHostScope)).action((flags) => {
|
|
313
306
|
const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
|
|
314
307
|
const result = configureGatewayHosts(loadPipelineConfig(cwd, { allowMissingLintFileReferences: true }), {
|
|
315
308
|
cwd,
|
|
@@ -337,29 +330,15 @@ function createCliProgram(options = {}) {
|
|
|
337
330
|
const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
|
|
338
331
|
console.log(await localGatewayStatus(cwd));
|
|
339
332
|
});
|
|
340
|
-
program.command("init").description("
|
|
341
|
-
const result = await initPipelineProject({
|
|
342
|
-
console.log(formatPipelineInitResult(result));
|
|
343
|
-
});
|
|
344
|
-
program.command("refresh-harnesses").description("Force-refresh generated agent harnesses (global per-machine install: ~/.claude, ~/.config/opencode, ~/.codex)").action(async () => {
|
|
345
|
-
const result = await refreshAgentHarnesses({ cwd: process.env.PIPELINE_TARGET_PATH ?? process.cwd() });
|
|
346
|
-
console.log(formatRefreshAgentHarnessesResult(result));
|
|
347
|
-
});
|
|
348
|
-
program.command("install-commands").description("Install generated slash-command adapters into per-machine host dirs (~/.claude, ~/.config/opencode, ~/.codex)").addOption(new Option$1("--host <host>", "host command set to install").choices([
|
|
349
|
-
"all",
|
|
350
|
-
"opencode",
|
|
351
|
-
"claude-code",
|
|
352
|
-
"codex"
|
|
353
|
-
]).default("all").argParser(parseCommandHost)).option("--dry-run", "show planned changes without writing files").option("--check", "fail if generated command files are missing or stale").option("--force", "overwrite manually edited command files").action(async (flags) => {
|
|
354
|
-
const result = await installCommands({
|
|
333
|
+
program.command("init").description("Install or refresh package-owned pipeline support: per-machine harness (skills + slash-command adapters + agent hooks + global instruction files) installed globally to ~/.claude, ~/.config/opencode, ~/.codex with no repo-local config").option("--check", "verify the generated harness is current; fail if stale").option("--dry-run", "show planned changes without writing files").option("--force", "overwrite manually edited harness files").action(async (flags) => {
|
|
334
|
+
const result = await initPipelineProject({
|
|
355
335
|
...flags,
|
|
356
336
|
cwd: process.env.PIPELINE_TARGET_PATH ?? process.cwd()
|
|
357
337
|
});
|
|
358
|
-
console.log(
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
console.log(formatInstallHooksResult(result));
|
|
338
|
+
console.log(formatPipelineInitResult(result, {
|
|
339
|
+
check: flags.check,
|
|
340
|
+
dryRun: flags.dryRun
|
|
341
|
+
}));
|
|
363
342
|
});
|
|
364
343
|
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) => {
|
|
365
344
|
const result = syncLocalCodexAuth({
|
package/dist/codex-auth-sync.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mergeOpenCodeProjectConfig } from "./opencode-project-config.js";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
3
|
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
5
|
import { parse } from "jsonc-parser";
|
|
6
6
|
//#region src/codex-auth-sync.ts
|
|
7
7
|
const CODEX_MULTI_AUTH_PLUGIN = "oc-codex-multi-auth@6.3.2";
|
package/dist/gates.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { dirname, join } from "node:path";
|
|
2
1
|
import { homedir } from "node:os";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
3
|
//#region src/install-commands/shared.ts
|
|
4
4
|
const GENERATED_MARKER = "<!-- Generated by @oisincoveney/pipeline. -->";
|
|
5
5
|
const GENERATED_TS_MARKER = "// Generated by @oisincoveney/pipeline.";
|
package/dist/install-commands.js
CHANGED
|
@@ -31,9 +31,6 @@ function selectedInstallHosts(host) {
|
|
|
31
31
|
function isActiveCommandHost(host) {
|
|
32
32
|
return host === "opencode" || host === "claude-code";
|
|
33
33
|
}
|
|
34
|
-
function isInstallHost(host) {
|
|
35
|
-
return INSTALL_HOSTS.some((candidate) => candidate === host);
|
|
36
|
-
}
|
|
37
34
|
function selectedCommandHosts(host) {
|
|
38
35
|
return selectedInstallHosts(host).filter(isActiveCommandHost);
|
|
39
36
|
}
|
|
@@ -274,14 +271,5 @@ async function installCommands(options = {}) {
|
|
|
274
271
|
assertInstallCheckCurrent(options, items);
|
|
275
272
|
return { items };
|
|
276
273
|
}
|
|
277
|
-
function parseCommandHost(value) {
|
|
278
|
-
const host = value ?? "all";
|
|
279
|
-
if (host === "all") return host;
|
|
280
|
-
if (isInstallHost(host)) return host;
|
|
281
|
-
throw new Error(`Unsupported host "${host}". Supported values: all, ${INSTALL_HOSTS.join(", ")}.`);
|
|
282
|
-
}
|
|
283
|
-
function formatInstallCommandsResult(result) {
|
|
284
|
-
return result.items.map((item) => `${item.action} ${item.host}: ${item.path} (${item.invocation})`).join("\n");
|
|
285
|
-
}
|
|
286
274
|
//#endregion
|
|
287
|
-
export {
|
|
275
|
+
export { installCommands };
|
package/dist/install-hooks.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { applyJsonEdit, ensureTrailingNewline, parseJsonRecord } from "./json-config-merge.js";
|
|
2
2
|
import { resolveHarnessTarget } from "./install-commands/shared.js";
|
|
3
3
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
4
|
-
import { execa } from "execa";
|
|
5
|
-
import { dirname, join, relative } from "node:path";
|
|
6
4
|
import { tmpdir } from "node:os";
|
|
5
|
+
import { dirname, join, relative } from "node:path";
|
|
6
|
+
import { execa } from "execa";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
import { mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
|
|
9
9
|
//#region src/install-hooks.ts
|
|
@@ -247,8 +247,5 @@ function installHooks(options = {}) {
|
|
|
247
247
|
};
|
|
248
248
|
});
|
|
249
249
|
}
|
|
250
|
-
function formatInstallHooksResult(result) {
|
|
251
|
-
return result.items.map((item) => `${item.action} ${item.host}: ${item.path}`).join("\n");
|
|
252
|
-
}
|
|
253
250
|
//#endregion
|
|
254
|
-
export {
|
|
251
|
+
export { installHooks };
|
package/dist/install-rules.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { execa } from "execa";
|
|
2
|
-
import { join } from "node:path";
|
|
3
1
|
import { homedir, tmpdir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { execa } from "execa";
|
|
4
4
|
import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
5
5
|
//#region src/install-rules.ts
|
|
6
6
|
const DEFAULT_RULES_INSTALL_SOURCE = "oisin-ee/rules";
|
|
@@ -48,7 +48,7 @@ async function defaultRulesyncRunner(args, opts) {
|
|
|
48
48
|
});
|
|
49
49
|
} catch (error) {
|
|
50
50
|
const cause = error instanceof Error ? `: ${error.message}` : "";
|
|
51
|
-
throw new Error(`Failed to generate global rules from ${DEFAULT_RULES_INSTALL_SOURCE}${cause}. If this is a private repository, authenticate GitHub access with \`gh auth login\` and rerun \`moka
|
|
51
|
+
throw new Error(`Failed to generate global rules from ${DEFAULT_RULES_INSTALL_SOURCE}${cause}. If this is a private repository, authenticate GitHub access with \`gh auth login\` and rerun \`moka init\`.`);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
async function buildRootRule(source) {
|
package/dist/mcp/gateway.js
CHANGED
|
@@ -6,8 +6,8 @@ import { resolveRepoLocalBackendSpecs } from "./repo-local-backends.js";
|
|
|
6
6
|
import { renderToolHiveVmcpInventory } from "./toolhive-vmcp.js";
|
|
7
7
|
import { Effect } from "effect";
|
|
8
8
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
|
-
import { dirname, join } from "node:path";
|
|
10
9
|
import { homedir } from "node:os";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
11
|
//#region src/mcp/gateway.ts
|
|
12
12
|
const PIPELINE_GATEWAY_SERVER_ID = "pipeline-gateway";
|
|
13
13
|
const DEFAULT_LOCAL_GATEWAY_URL = "http://127.0.0.1:4483/mcp";
|
|
@@ -3,8 +3,8 @@ import { ConfigIoService, runConfigIoSync } from "./runtime/services/config-io-s
|
|
|
3
3
|
import "./config.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { Effect } from "effect";
|
|
6
|
-
import { join } from "node:path";
|
|
7
6
|
import { homedir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
8
|
//#region src/moka-global-config.ts
|
|
9
9
|
const MOKA_GLOBAL_CONFIG_PATH = ".config/moka/config.yaml";
|
|
10
10
|
const mokaSubmitGlobalConfigSchema = z.object({
|
package/dist/path-refs.js
CHANGED
package/dist/pipeline-init.js
CHANGED
|
@@ -33,44 +33,61 @@ async function installDefaultSkills(cwd) {
|
|
|
33
33
|
throw new Error(`Failed to install default skills from ${DEFAULT_SKILL_INSTALL_SOURCE}${cause}. If this is a private repository, authenticate GitHub access for npx skills add and rerun \`moka init\`.`);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
function installDefaultHooks() {
|
|
37
|
-
return installHooks({ force: true });
|
|
38
|
-
}
|
|
39
36
|
function hookInstallerFiles(result) {
|
|
40
37
|
return "items" in result ? result.items.map((item) => item.path) : result.files;
|
|
41
38
|
}
|
|
42
39
|
async function initPipelineProject(options = {}) {
|
|
43
40
|
const cwd = options.cwd ?? process.cwd();
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
const { check, dryRun, force } = options;
|
|
42
|
+
const installerFlags = {
|
|
43
|
+
check,
|
|
44
|
+
dryRun,
|
|
45
|
+
force
|
|
46
|
+
};
|
|
47
|
+
if (!(check || dryRun)) await (options.skillInstaller ?? installDefaultSkills)(cwd);
|
|
48
48
|
const result = await installCommands({
|
|
49
49
|
cwd,
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
host: "all",
|
|
51
|
+
...installerFlags
|
|
52
52
|
});
|
|
53
|
-
const hooks = await hookInstaller(cwd);
|
|
54
|
-
const rulesResult = await rulesInstaller(cwd);
|
|
53
|
+
const hooks = await (options.hookInstaller ?? (() => installHooks(installerFlags)))(cwd);
|
|
54
|
+
const rulesResult = await (options.rulesInstaller ?? (() => installRules(installerFlags)))(cwd);
|
|
55
55
|
return { files: [
|
|
56
56
|
...result.items.map((item) => item.path),
|
|
57
57
|
...hookInstallerFiles(hooks),
|
|
58
58
|
...rulesResult.items.map((item) => item.path)
|
|
59
59
|
] };
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
const INIT_RESULT_COPY = {
|
|
62
|
+
install: {
|
|
63
|
+
headline: "Initialized package-owned pipeline support:",
|
|
64
|
+
fileVerb: "generated",
|
|
65
|
+
footer: "no repo-local pipeline config files were created"
|
|
66
|
+
},
|
|
67
|
+
check: {
|
|
68
|
+
headline: "Verified package-owned pipeline support is current:",
|
|
69
|
+
fileVerb: "current",
|
|
70
|
+
footer: "harness verified; no changes written"
|
|
71
|
+
},
|
|
72
|
+
dryRun: {
|
|
73
|
+
headline: "Planned package-owned pipeline support:",
|
|
74
|
+
fileVerb: "would generate",
|
|
75
|
+
footer: "dry run; no changes written"
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
function initResultMode(mode) {
|
|
79
|
+
if (mode.check) return "check";
|
|
80
|
+
if (mode.dryRun) return "dryRun";
|
|
81
|
+
return "install";
|
|
63
82
|
}
|
|
64
|
-
function formatPipelineInitResult(result) {
|
|
83
|
+
function formatPipelineInitResult(result, mode = {}) {
|
|
84
|
+
const copy = INIT_RESULT_COPY[initResultMode(mode)];
|
|
65
85
|
return [
|
|
66
|
-
|
|
67
|
-
"
|
|
68
|
-
...result.files.map((path) =>
|
|
69
|
-
|
|
86
|
+
copy.headline,
|
|
87
|
+
"per-machine harness globally (user/global skills + ~/.claude, ~/.config/opencode, ~/.codex); global instruction files generated via rulesync from oisin-ee/rules; inherited by every repo with no per-repo copy",
|
|
88
|
+
...result.files.map((path) => `${copy.fileVerb} ${path}`),
|
|
89
|
+
copy.footer
|
|
70
90
|
].join("\n");
|
|
71
91
|
}
|
|
72
|
-
function formatRefreshAgentHarnessesResult(result) {
|
|
73
|
-
return [formatPipelineInitResult(result), "global harness refreshed; no repo commit (per-machine install)"].join("\n");
|
|
74
|
-
}
|
|
75
92
|
//#endregion
|
|
76
|
-
export { formatPipelineInitResult,
|
|
93
|
+
export { formatPipelineInitResult, initPipelineProject };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { GitPorcelainService, GitPorcelainServiceLive } from "../runtime/services/git-porcelain-service.js";
|
|
2
2
|
import { Effect } from "effect";
|
|
3
3
|
import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { dirname, resolve } from "node:path";
|
|
5
4
|
import { tmpdir } from "node:os";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
6
6
|
//#region src/run-state/git-refs.ts
|
|
7
7
|
const DEFAULT_WORKSPACE_PATH = "/workspace";
|
|
8
8
|
const DEFAULT_GIT_CREDENTIALS_DIR = "/etc/pipeline/git-credentials";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
//#region src/run-state/opencode-accounts.ts
|
|
5
|
+
const OPENCODE_OPENAI_ACCOUNTS_STAGING_DIR = "/etc/pipeline/opencode-openai-accounts";
|
|
6
|
+
const STAGED_ACCOUNTS_FILE = join(OPENCODE_OPENAI_ACCOUNTS_STAGING_DIR, "accounts.json");
|
|
7
|
+
const ACCOUNTS_FILE_NAME = "oc-codex-multi-auth-accounts.json";
|
|
8
|
+
/**
|
|
9
|
+
* Copy the staged codex-multi-auth accounts secret to the writable plugin path
|
|
10
|
+
* so the plugin can persist rotated tokens. A no-op (copied: false) when no
|
|
11
|
+
* staged secret is mounted — local dev, tests, and configs without the accounts
|
|
12
|
+
* secret keep whatever account store already exists.
|
|
13
|
+
*/
|
|
14
|
+
function prepareOpencodeAccounts(options = {}) {
|
|
15
|
+
const stagedPath = options.stagedPath ?? STAGED_ACCOUNTS_FILE;
|
|
16
|
+
if (!existsSync(stagedPath)) return { copied: false };
|
|
17
|
+
const destPath = options.destPath ?? join(homedir(), ".opencode", ACCOUNTS_FILE_NAME);
|
|
18
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
19
|
+
copyFileSync(stagedPath, destPath);
|
|
20
|
+
chmodSync(destPath, 384);
|
|
21
|
+
return { copied: true };
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { OPENCODE_OPENAI_ACCOUNTS_STAGING_DIR, prepareOpencodeAccounts };
|
|
@@ -100,6 +100,16 @@ function runRunnerCommandEffect(options, runtime) {
|
|
|
100
100
|
phase: "git.workspace.prepare",
|
|
101
101
|
status: "finish"
|
|
102
102
|
}, "git.workspace.prepare finish");
|
|
103
|
+
logger.info({
|
|
104
|
+
phase: "opencode.accounts.prepare",
|
|
105
|
+
status: "start"
|
|
106
|
+
}, "opencode.accounts.prepare start");
|
|
107
|
+
const accountsPrep = yield* io.prepareOpencodeAccounts();
|
|
108
|
+
logger.info({
|
|
109
|
+
copied: accountsPrep.copied,
|
|
110
|
+
phase: "opencode.accounts.prepare",
|
|
111
|
+
status: "finish"
|
|
112
|
+
}, "opencode.accounts.prepare finish");
|
|
103
113
|
logger.info({
|
|
104
114
|
phase: "config.load",
|
|
105
115
|
status: "start"
|
package/dist/runner.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Data } from "effect";
|
|
2
2
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
-
import { execa } from "execa";
|
|
4
3
|
import { join } from "node:path";
|
|
4
|
+
import { execa } from "execa";
|
|
5
5
|
//#region src/runner.ts
|
|
6
6
|
var RunnerCapabilityError = class extends Data.TaggedError("RunnerCapabilityError") {
|
|
7
7
|
constructor(message) {
|
|
@@ -8,8 +8,8 @@ import { CommandExecutor, CommandExecutorLive } from "../services/command-execut
|
|
|
8
8
|
import { parseHookResult } from "../../hooks.js";
|
|
9
9
|
import { Effect } from "effect";
|
|
10
10
|
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
-
import { join, resolve } from "node:path";
|
|
12
11
|
import { tmpdir } from "node:os";
|
|
12
|
+
import { join, resolve } from "node:path";
|
|
13
13
|
import { pathToFileURL } from "node:url";
|
|
14
14
|
//#region src/runtime/hooks/hooks.ts
|
|
15
15
|
async function dispatchHooks(context, event, failure, node, gateId) {
|
|
@@ -310,10 +310,58 @@ function describeMessageError(error) {
|
|
|
310
310
|
const detail = data && typeof data.message === "string" ? `: ${data.message}` : "";
|
|
311
311
|
return `${error.name}${detail}`;
|
|
312
312
|
}
|
|
313
|
-
function unwrap(
|
|
314
|
-
if (
|
|
315
|
-
if (
|
|
316
|
-
return
|
|
313
|
+
function unwrap(result) {
|
|
314
|
+
if (result.error) throw new Error(resultErrorMessage(result));
|
|
315
|
+
if (result.data === void 0) throw new Error(`opencode response contained no data${httpContext(result)}`);
|
|
316
|
+
return result.data;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Build the richest available message for a failed opencode result tuple. The
|
|
320
|
+
* runner reads the result-tuple path (not throwOnError), so the SDK leaves
|
|
321
|
+
* `error` as the raw parsed body — which for a 5xx/timeout is an empty `{}` that
|
|
322
|
+
* stringifies to nothing useful. Walk an ordered precedence (body message →
|
|
323
|
+
* raw string → non-empty JSON body → HTTP status line) so a gateway timeout
|
|
324
|
+
* surfaces as "POST …/prompt → 504 Gateway Timeout" instead of "{}". Mirrors the
|
|
325
|
+
* SDK's own error-interceptor describe().
|
|
326
|
+
*/
|
|
327
|
+
function resultErrorMessage(result) {
|
|
328
|
+
const detail = errorDetail(result.error);
|
|
329
|
+
if (detail) return `${detail}${httpContext(result)}`;
|
|
330
|
+
return httpStatusLine(result) ?? "opencode error with empty response body";
|
|
331
|
+
}
|
|
332
|
+
function errorDetail(error) {
|
|
333
|
+
return bodyMessage(error) ?? nonEmptyString(error) ?? nonEmptyJson(error);
|
|
334
|
+
}
|
|
335
|
+
function bodyMessage(error) {
|
|
336
|
+
if (!isRecord(error)) return;
|
|
337
|
+
return stringField(error.data, "message") ?? stringField(error, "message") ?? stringField(error, "name");
|
|
338
|
+
}
|
|
339
|
+
function nonEmptyString(error) {
|
|
340
|
+
return typeof error === "string" && error.length > 0 ? error : void 0;
|
|
341
|
+
}
|
|
342
|
+
function nonEmptyJson(error) {
|
|
343
|
+
if (error === void 0) return;
|
|
344
|
+
const json = JSON.stringify(error);
|
|
345
|
+
return json && json !== "{}" ? json : void 0;
|
|
346
|
+
}
|
|
347
|
+
function httpContext(result) {
|
|
348
|
+
const status = httpStatusLine(result);
|
|
349
|
+
return status ? ` (${status})` : "";
|
|
350
|
+
}
|
|
351
|
+
function httpStatusLine(result) {
|
|
352
|
+
const status = result.response?.status;
|
|
353
|
+
const { request } = result;
|
|
354
|
+
if (!(request?.method || request?.url || status)) return;
|
|
355
|
+
return `${requestTarget(request)}${statusSuffix(status)}`;
|
|
356
|
+
}
|
|
357
|
+
function requestTarget(request) {
|
|
358
|
+
return `${request?.method ?? "?"} ${request?.url ?? "?"}`;
|
|
359
|
+
}
|
|
360
|
+
function statusSuffix(status) {
|
|
361
|
+
return status ? ` → HTTP ${status}` : "";
|
|
362
|
+
}
|
|
363
|
+
function stringField(value, field) {
|
|
364
|
+
if (isRecord(value) && typeof value[field] === "string" && value[field]) return value[field];
|
|
317
365
|
}
|
|
318
366
|
function errorMessage(error) {
|
|
319
367
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { prepareOpencodeAccounts } from "../../run-state/opencode-accounts.js";
|
|
1
2
|
import { commitAndPushNodeRef, mergeDependencyRefs, prepareRunnerGitWorkspace, promoteFinalRef } from "../../run-state/git-refs.js";
|
|
2
3
|
import { runScheduledWorkflowTask } from "../../pipeline-runtime.js";
|
|
3
4
|
import { resolveRunnerEventSinkAuthToken } from "../../runner-command-contract.js";
|
|
@@ -45,6 +46,10 @@ const RunnerCommandIoServiceLive = Layer.succeed(RunnerCommandIoService, {
|
|
|
45
46
|
try: () => mergeDependencyRefs(options),
|
|
46
47
|
catch: (error) => error
|
|
47
48
|
}),
|
|
49
|
+
prepareOpencodeAccounts: () => Effect.try({
|
|
50
|
+
try: () => prepareOpencodeAccounts(),
|
|
51
|
+
catch: (error) => error
|
|
52
|
+
}),
|
|
48
53
|
prepareRunnerGitWorkspace: (payload, options) => Effect.tryPromise({
|
|
49
54
|
try: () => prepareRunnerGitWorkspace(payload, options),
|
|
50
55
|
catch: (error) => error
|
|
@@ -164,9 +164,9 @@ Project-authored skill and rule paths resolve from the project root and must
|
|
|
164
164
|
exist for runtime use. If default skill files are missing, run `moka init` to
|
|
165
165
|
install them before executing workflows.
|
|
166
166
|
|
|
167
|
-
Default agent hooks are copied by `moka init`
|
|
168
|
-
|
|
169
|
-
|
|
167
|
+
Default agent hooks are copied by `moka init` from the private
|
|
168
|
+
`oisin-ee/agent-hooks` repository. That source repository has one canonical
|
|
169
|
+
host-level layout:
|
|
170
170
|
|
|
171
171
|
```text
|
|
172
172
|
claude-code/
|
|
@@ -215,7 +215,7 @@ OpenCode host resources are generated from the same profile registry:
|
|
|
215
215
|
- `.opencode/skills/*/SKILL.md` is installed by `skills add`; Moka only
|
|
216
216
|
generates agents, commands, plugins, and project config.
|
|
217
217
|
- Additional manually authored OpenCode hook plugins can be copied from
|
|
218
|
-
`oisin-ee/agent-hooks/opencode/` by `moka
|
|
218
|
+
`oisin-ee/agent-hooks/opencode/` by `moka init`.
|
|
219
219
|
- `.opencode/plugins/pipeline-goal-context.ts` projects package-owned
|
|
220
220
|
continuation context into OpenCode compaction.
|
|
221
221
|
- `.opencode/opencode.json` contains the gateway MCP config, enables LSP, and
|
|
@@ -455,8 +455,8 @@ rather than re-emitting it into every project.
|
|
|
455
455
|
|
|
456
456
|
## Troubleshooting
|
|
457
457
|
|
|
458
|
-
- Missing host resources: run `moka
|
|
459
|
-
|
|
458
|
+
- Missing host resources: run `moka init`; `moka run` loads the installed
|
|
459
|
+
package config.
|
|
460
460
|
- Capability error: reduce the profile grants or choose a runner whose declared
|
|
461
461
|
capabilities include the requested tools, filesystem, network, output, rules,
|
|
462
462
|
skills, or MCP access.
|
package/docs/mcp-gateway.md
CHANGED
|
@@ -94,7 +94,7 @@ contains only `pipeline-gateway`.
|
|
|
94
94
|
OpenCode receives that gateway through `.opencode/opencode.json` alongside the
|
|
95
95
|
package-owned runtime projection: `lsp: true`, pinned plugin entries, generated
|
|
96
96
|
agents, projected skills, explicit permissions, and local TypeScript plugins.
|
|
97
|
-
`moka init`
|
|
97
|
+
`moka init` merges this OpenCode project config:
|
|
98
98
|
existing repo-local plugin entries are preserved while missing package defaults
|
|
99
99
|
such as `oc-codex-multi-auth` are appended. Existing `mcp.pipeline-gateway`
|
|
100
100
|
settings are also preserved; use `moka mcp gateway configure-host` when the
|
|
@@ -116,8 +116,8 @@ Qdrant, Fallow, Serena, or Backlog declarations. Use
|
|
|
116
116
|
health, required `tools/list` prefixes, local ToolHive availability for local
|
|
117
117
|
mode, and legacy direct MCP entries. Use `moka init` to install generated
|
|
118
118
|
OpenCode and Claude Code host surfaces with the singleton `pipeline-gateway`
|
|
119
|
-
remote entry
|
|
120
|
-
|
|
119
|
+
remote entry, and `moka init --check` to verify generated host files are current
|
|
120
|
+
after package upgrades. Use
|
|
121
121
|
`moka mcp gateway configure-host` as an explicit migration or repair command
|
|
122
122
|
when existing host MCP config must be rewritten with a backup. The hosted gateway
|
|
123
123
|
requires `PIPELINE_MCP_GATEWAY_AUTHORIZATION` to be set in the OpenCode
|
package/docs/operator-guide.md
CHANGED
|
@@ -175,54 +175,29 @@ OpenBao, publish Secret values, or mutate ESO resources from this package.
|
|
|
175
175
|
|
|
176
176
|
`moka init`
|
|
177
177
|
|
|
178
|
-
Installs the
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
Installs or refreshes the whole per-machine harness in one command: the
|
|
179
|
+
package's default skills, generated host-native command surfaces and MCP
|
|
180
|
+
entries, copied agent hooks from the private `oisin-ee/agent-hooks` repository,
|
|
181
|
+
and global instruction files generated via rulesync from `oisin-ee/rules`.
|
|
182
|
+
OpenCode is the package default runtime. The harness is always installed
|
|
183
|
+
globally (`~/.claude`, `~/.config/opencode`, `~/.codex`); there is no `--scope`.
|
|
184
|
+
`moka init` does not create repo-local `.pipeline` config files.
|
|
182
185
|
|
|
183
186
|
```shell
|
|
184
|
-
moka init
|
|
187
|
+
moka init # install or refresh everything
|
|
188
|
+
moka init --check # verify the generated harness is current; fail if stale
|
|
189
|
+
moka init --dry-run # show planned changes without writing
|
|
190
|
+
moka init --force # overwrite manually edited harness files
|
|
185
191
|
```
|
|
186
192
|
|
|
187
|
-
`
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
package upgrades or manual edits. Initial setup should use `moka init`.
|
|
191
|
-
|
|
192
|
-
```shell
|
|
193
|
-
moka install-commands --host opencode --dry-run
|
|
194
|
-
moka install-commands --host all --force
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
Host choices are `all` and `opencode`.
|
|
198
|
-
|
|
199
|
-
`moka install-hooks`
|
|
200
|
-
|
|
201
|
-
Copies manually authored hook files from the private `oisin-ee/agent-hooks`
|
|
202
|
-
repository into the selected harness scope. The hook source repository has only
|
|
203
|
-
host folders: `claude-code/`, `codex/`, and `opencode/`. Install scope controls
|
|
204
|
-
the destination; it is not represented in the source repository.
|
|
205
|
-
|
|
206
|
-
```shell
|
|
207
|
-
moka install-hooks --scope global --check
|
|
208
|
-
moka install-hooks --scope project --force
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
There is no source override flag and no symlink mode. Moka clones
|
|
193
|
+
`--check` and `--dry-run` write nothing and skip the network skill install.
|
|
194
|
+
By default `moka init` refuses to overwrite manually edited hook or command
|
|
195
|
+
files; `--force` overwrites them. For agent hooks, Moka clones
|
|
212
196
|
`oisin-ee/agent-hooks`, copies files, and tracks installed hashes so later runs
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
Force-refreshes package-owned skills, generated agent harnesses, and copied hook
|
|
219
|
-
files, stages only owned harness/resource paths, and commits them with
|
|
220
|
-
`--no-verify`. The default commit message is `chore: update agent harnesses`.
|
|
221
|
-
|
|
222
|
-
```shell
|
|
223
|
-
moka refresh-harnesses
|
|
224
|
-
moka refresh-harnesses --message "chore: refresh moka harnesses"
|
|
225
|
-
```
|
|
197
|
+
update unchanged owned files, delete removed owned files, and (without `--force`)
|
|
198
|
+
refuse to clobber manual edits. The hook source repository has only host folders
|
|
199
|
+
(`claude-code/`, `codex/`, `opencode/`); there is no source override flag and no
|
|
200
|
+
symlink mode.
|
|
226
201
|
|
|
227
202
|
Use `PIPELINE_TARGET_PATH=/path/to/repo` when invoking `moka` from outside the
|
|
228
203
|
target worktree.
|
|
@@ -399,7 +374,7 @@ OpenCode: /moka-quick, /moka-execute, /moka-inspect
|
|
|
399
374
|
Claude Code: /moka-quick, /moka-execute, /moka-inspect
|
|
400
375
|
```
|
|
401
376
|
|
|
402
|
-
`moka init`
|
|
377
|
+
`moka init` generates, for OpenCode:
|
|
403
378
|
|
|
404
379
|
- `.opencode/commands/moka-<entrypoint>.md` for `/moka-quick`, `/moka-execute`, and `/moka-inspect`
|
|
405
380
|
- `.opencode/agents/*.md` for primary and subagent profiles with explicit
|
|
@@ -409,13 +384,12 @@ Claude Code: /moka-quick, /moka-execute, /moka-inspect
|
|
|
409
384
|
- `.opencode/opencode.json` with LSP, the singleton `pipeline-gateway` MCP
|
|
410
385
|
server, and pinned package-selected plugins
|
|
411
386
|
|
|
412
|
-
`moka init`
|
|
413
|
-
`
|
|
414
|
-
|
|
415
|
-
not generated by Moka.
|
|
387
|
+
`moka init` also copies hook files from `oisin-ee/agent-hooks` by overlaying
|
|
388
|
+
`opencode/`, `claude-code/`, and `codex/` onto the host config roots. Hook files
|
|
389
|
+
are authored in the hook repo, not generated by Moka.
|
|
416
390
|
|
|
417
|
-
|
|
418
|
-
slash commands
|
|
391
|
+
For Claude Code, `moka init` generates `.claude/commands/moka-<entrypoint>.md`
|
|
392
|
+
slash commands.
|
|
419
393
|
|
|
420
394
|
Package defaults select OpenCode for built-in profiles and runner-command
|
|
421
395
|
orchestration. Codex is not a supported runtime host.
|
|
@@ -554,7 +528,7 @@ After changing profile grants or registries, check all three surfaces:
|
|
|
554
528
|
```shell
|
|
555
529
|
moka validate --strict
|
|
556
530
|
moka explain-plan --workflow <workflow-id>
|
|
557
|
-
moka
|
|
531
|
+
moka init --check
|
|
558
532
|
```
|
|
559
533
|
|
|
560
534
|
## Runner Image Verification
|
package/package.json
CHANGED
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"prepack": "bun run build:cli"
|
|
129
129
|
},
|
|
130
130
|
"type": "module",
|
|
131
|
-
"version": "3.11.
|
|
131
|
+
"version": "3.11.17",
|
|
132
132
|
"description": "Config-driven multi-agent pipeline runner for repository work",
|
|
133
133
|
"main": "./dist/index.js",
|
|
134
134
|
"types": "./dist/index.d.ts",
|