@oisincoveney/pipeline 3.11.14 → 3.11.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -13
- package/defaults/pipeline.yaml +5 -5
- package/dist/cli/program.js +8 -29
- package/dist/commands/pipeline-command.js +0 -2
- package/dist/config/schemas.d.ts +4 -4
- package/dist/hooks.d.ts +1 -1
- package/dist/install-commands.js +1 -13
- package/dist/install-hooks.js +1 -4
- package/dist/install-rules.js +1 -1
- package/dist/model-resolver.js +25 -33
- package/dist/moka-submit.d.ts +6 -6
- package/dist/pipeline-init.js +39 -22
- package/dist/runner-event-schema.d.ts +8 -8
- package/dist/runtime/agent-node/agent-node.js +91 -24
- package/dist/runtime/opencode-session-executor.js +56 -17
- 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/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/config/schemas.d.ts
CHANGED
|
@@ -233,8 +233,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
233
233
|
policy: z.ZodOptional<z.ZodObject<{
|
|
234
234
|
commands: z.ZodOptional<z.ZodEnum<{
|
|
235
235
|
allow: "allow";
|
|
236
|
-
deny: "deny";
|
|
237
236
|
"trusted-only": "trusted-only";
|
|
237
|
+
deny: "deny";
|
|
238
238
|
}>>;
|
|
239
239
|
modules: z.ZodOptional<z.ZodEnum<{
|
|
240
240
|
allow: "allow";
|
|
@@ -262,8 +262,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
262
262
|
global: "global";
|
|
263
263
|
}>>;
|
|
264
264
|
mode: z.ZodEnum<{
|
|
265
|
-
local: "local";
|
|
266
265
|
hosted: "hosted";
|
|
266
|
+
local: "local";
|
|
267
267
|
}>;
|
|
268
268
|
provider: z.ZodLiteral<"toolhive">;
|
|
269
269
|
authorization_env: z.ZodDefault<z.ZodString>;
|
|
@@ -306,10 +306,10 @@ declare const configSchema: z.ZodObject<{
|
|
|
306
306
|
}, z.core.$strict>>;
|
|
307
307
|
output: z.ZodOptional<z.ZodObject<{
|
|
308
308
|
format: z.ZodEnum<{
|
|
309
|
-
json_schema: "json_schema";
|
|
310
309
|
text: "text";
|
|
311
310
|
json: "json";
|
|
312
311
|
jsonl: "jsonl";
|
|
312
|
+
json_schema: "json_schema";
|
|
313
313
|
}>;
|
|
314
314
|
repair: z.ZodOptional<z.ZodObject<{
|
|
315
315
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -385,10 +385,10 @@ declare const configSchema: z.ZodObject<{
|
|
|
385
385
|
disabled: "disabled";
|
|
386
386
|
}>>>;
|
|
387
387
|
output_formats: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
388
|
-
json_schema: "json_schema";
|
|
389
388
|
text: "text";
|
|
390
389
|
json: "json";
|
|
391
390
|
jsonl: "jsonl";
|
|
391
|
+
json_schema: "json_schema";
|
|
392
392
|
}>>>;
|
|
393
393
|
rules: z.ZodOptional<z.ZodBoolean>;
|
|
394
394
|
skills: z.ZodOptional<z.ZodBoolean>;
|
package/dist/hooks.d.ts
CHANGED
|
@@ -13,8 +13,8 @@ declare const hookResultSchema: z.ZodObject<{
|
|
|
13
13
|
taskContext: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
14
14
|
}, z.core.$strict>>;
|
|
15
15
|
status: z.ZodEnum<{
|
|
16
|
-
fail: "fail";
|
|
17
16
|
pass: "pass";
|
|
17
|
+
fail: "fail";
|
|
18
18
|
skip: "skip";
|
|
19
19
|
}>;
|
|
20
20
|
summary: z.ZodOptional<z.ZodString>;
|
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
|
@@ -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
|
@@ -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/model-resolver.js
CHANGED
|
@@ -1,27 +1,37 @@
|
|
|
1
1
|
//#region src/model-resolver.ts
|
|
2
2
|
const DISABLED_MODELS_ENV = "PIPELINE_DISABLED_MODELS";
|
|
3
|
-
function
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
function fallbackModelSelection(models, options) {
|
|
3
|
+
function selectNodeModelCandidates(node, options) {
|
|
4
|
+
const models = node.models ?? [];
|
|
7
5
|
if (models.length === 0) return {
|
|
6
|
+
models: [],
|
|
8
7
|
reason: "node declares no model fallback array",
|
|
9
8
|
skipped: []
|
|
10
9
|
};
|
|
11
10
|
const disabled = disabledModels();
|
|
12
11
|
const available = options?.available;
|
|
13
12
|
const enabled = models.filter((candidate) => !disabled.has(candidate) && isAvailable(candidate, available));
|
|
14
|
-
const
|
|
13
|
+
const baseSkipped = models.filter((candidate) => disabled.has(candidate) || !isAvailable(candidate, available));
|
|
15
14
|
const sizing = sizingFromOptions(options);
|
|
16
|
-
if (!sizing) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
if (!sizing) return {
|
|
16
|
+
models: enabled,
|
|
17
|
+
reason: selectionReason(enabled[0]),
|
|
18
|
+
skipped: baseSkipped
|
|
19
|
+
};
|
|
20
|
+
return sizedCandidates(enabled, baseSkipped, sizing);
|
|
21
|
+
}
|
|
22
|
+
function sizedCandidates(enabled, baseSkipped, options) {
|
|
23
|
+
const { estimatedTokens, budget } = options;
|
|
24
|
+
const required = estimatedTokens / (budget.max_context_pct / 100);
|
|
25
|
+
const fits = [];
|
|
26
|
+
const tooSmall = [];
|
|
27
|
+
for (const candidate of enabled) if ((budget.model_context_windows[candidate] ?? budget.default_context_window) >= required) fits.push(candidate);
|
|
28
|
+
else tooSmall.push(candidate);
|
|
29
|
+
const head = fits[0];
|
|
30
|
+
return {
|
|
31
|
+
models: fits,
|
|
32
|
+
reason: head ? `selected '${head}' (window ${budget.model_context_windows[head] ?? budget.default_context_window}) — holds estimated ${estimatedTokens} tokens within the ${budget.max_context_pct}% context cap` : `estimated context ${estimatedTokens} tokens exceeds ${budget.max_context_pct}% of every available model window`,
|
|
33
|
+
skipped: [...baseSkipped, ...tooSmall]
|
|
34
|
+
};
|
|
25
35
|
}
|
|
26
36
|
function isAvailable(candidate, available) {
|
|
27
37
|
return available ? available.has(candidate) : true;
|
|
@@ -32,24 +42,6 @@ function sizingFromOptions(options) {
|
|
|
32
42
|
estimatedTokens: options.estimatedTokens
|
|
33
43
|
};
|
|
34
44
|
}
|
|
35
|
-
function sizedSelection(enabled, disabledSkipped, options) {
|
|
36
|
-
const { estimatedTokens, budget } = options;
|
|
37
|
-
const required = estimatedTokens / (budget.max_context_pct / 100);
|
|
38
|
-
const tooSmall = [];
|
|
39
|
-
for (const candidate of enabled) {
|
|
40
|
-
const window = budget.model_context_windows[candidate] ?? budget.default_context_window;
|
|
41
|
-
if (window >= required) return {
|
|
42
|
-
model: candidate,
|
|
43
|
-
reason: `selected '${candidate}' (window ${window}) — holds estimated ${estimatedTokens} tokens within the ${budget.max_context_pct}% context cap`,
|
|
44
|
-
skipped: [...disabledSkipped, ...tooSmall]
|
|
45
|
-
};
|
|
46
|
-
tooSmall.push(candidate);
|
|
47
|
-
}
|
|
48
|
-
return {
|
|
49
|
-
reason: `estimated context ${estimatedTokens} tokens exceeds ${budget.max_context_pct}% of every available model window`,
|
|
50
|
-
skipped: [...disabledSkipped, ...tooSmall]
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
45
|
function selectionReason(model) {
|
|
54
46
|
if (model) return "selected first enabled model from node fallback array";
|
|
55
47
|
return `all configured node models are disabled by ${DISABLED_MODELS_ENV}`;
|
|
@@ -58,4 +50,4 @@ function disabledModels() {
|
|
|
58
50
|
return new Set((process.env[DISABLED_MODELS_ENV] ?? "").split(",").map((value) => value.trim()).filter(Boolean));
|
|
59
51
|
}
|
|
60
52
|
//#endregion
|
|
61
|
-
export {
|
|
53
|
+
export { selectNodeModelCandidates };
|
package/dist/moka-submit.d.ts
CHANGED
|
@@ -5,13 +5,13 @@ import { z } from "zod";
|
|
|
5
5
|
//#region src/moka-submit.d.ts
|
|
6
6
|
declare const mokaSubmitDirectHooksSchema: z.ZodRecord<z.ZodEnum<{
|
|
7
7
|
"workflow.start": "workflow.start";
|
|
8
|
+
"node.finish": "node.finish";
|
|
9
|
+
"node.start": "node.start";
|
|
8
10
|
"workflow.success": "workflow.success";
|
|
9
11
|
"workflow.failure": "workflow.failure";
|
|
10
12
|
"workflow.complete": "workflow.complete";
|
|
11
|
-
"node.start": "node.start";
|
|
12
13
|
"node.success": "node.success";
|
|
13
14
|
"node.error": "node.error";
|
|
14
|
-
"node.finish": "node.finish";
|
|
15
15
|
"gate.failure": "gate.failure";
|
|
16
16
|
}> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
17
17
|
failure: z.ZodDefault<z.ZodEnum<{
|
|
@@ -94,13 +94,13 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
94
94
|
}, z.core.$strict>>;
|
|
95
95
|
hooks: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
96
96
|
"workflow.start": "workflow.start";
|
|
97
|
+
"node.finish": "node.finish";
|
|
98
|
+
"node.start": "node.start";
|
|
97
99
|
"workflow.success": "workflow.success";
|
|
98
100
|
"workflow.failure": "workflow.failure";
|
|
99
101
|
"workflow.complete": "workflow.complete";
|
|
100
|
-
"node.start": "node.start";
|
|
101
102
|
"node.success": "node.success";
|
|
102
103
|
"node.error": "node.error";
|
|
103
|
-
"node.finish": "node.finish";
|
|
104
104
|
"gate.failure": "gate.failure";
|
|
105
105
|
}> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
106
106
|
failure: z.ZodDefault<z.ZodEnum<{
|
|
@@ -207,13 +207,13 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
207
207
|
}, z.core.$strict>>;
|
|
208
208
|
hooks: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
209
209
|
"workflow.start": "workflow.start";
|
|
210
|
+
"node.finish": "node.finish";
|
|
211
|
+
"node.start": "node.start";
|
|
210
212
|
"workflow.success": "workflow.success";
|
|
211
213
|
"workflow.failure": "workflow.failure";
|
|
212
214
|
"workflow.complete": "workflow.complete";
|
|
213
|
-
"node.start": "node.start";
|
|
214
215
|
"node.success": "node.success";
|
|
215
216
|
"node.error": "node.error";
|
|
216
|
-
"node.finish": "node.finish";
|
|
217
217
|
"gate.failure": "gate.failure";
|
|
218
218
|
}> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
219
219
|
failure: z.ZodDefault<z.ZodEnum<{
|
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 };
|
|
@@ -11,8 +11,8 @@ declare const runnerEventRecordSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
11
11
|
runId: z.ZodString;
|
|
12
12
|
sequence: z.ZodNumber;
|
|
13
13
|
type: z.ZodEnum<{
|
|
14
|
-
"workflow.start": "workflow.start";
|
|
15
14
|
"workflow.planned": "workflow.planned";
|
|
15
|
+
"workflow.start": "workflow.start";
|
|
16
16
|
}>;
|
|
17
17
|
workflowPlan: z.ZodObject<{
|
|
18
18
|
edges: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -58,10 +58,10 @@ declare const runnerEventRecordSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
58
58
|
}>;
|
|
59
59
|
}, z.core.$strip>;
|
|
60
60
|
type: z.ZodEnum<{
|
|
61
|
-
"node.start": "node.start";
|
|
62
|
-
"node.finish": "node.finish";
|
|
63
61
|
"agent.finish": "agent.finish";
|
|
64
62
|
"agent.start": "agent.start";
|
|
63
|
+
"node.finish": "node.finish";
|
|
64
|
+
"node.start": "node.start";
|
|
65
65
|
}>;
|
|
66
66
|
}, z.core.$strip>, z.ZodObject<{
|
|
67
67
|
at: z.ZodOptional<z.ZodString>;
|
|
@@ -108,8 +108,8 @@ declare const runnerEventRecordSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
108
108
|
nodeId: z.ZodOptional<z.ZodString>;
|
|
109
109
|
outputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
110
110
|
status: z.ZodEnum<{
|
|
111
|
-
fail: "fail";
|
|
112
111
|
pass: "pass";
|
|
112
|
+
fail: "fail";
|
|
113
113
|
skip: "skip";
|
|
114
114
|
}>;
|
|
115
115
|
summary: z.ZodOptional<z.ZodString>;
|
|
@@ -189,8 +189,8 @@ declare const runnerEventBatchSchema: z.ZodObject<{
|
|
|
189
189
|
runId: z.ZodString;
|
|
190
190
|
sequence: z.ZodNumber;
|
|
191
191
|
type: z.ZodEnum<{
|
|
192
|
-
"workflow.start": "workflow.start";
|
|
193
192
|
"workflow.planned": "workflow.planned";
|
|
193
|
+
"workflow.start": "workflow.start";
|
|
194
194
|
}>;
|
|
195
195
|
workflowPlan: z.ZodObject<{
|
|
196
196
|
edges: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -236,10 +236,10 @@ declare const runnerEventBatchSchema: z.ZodObject<{
|
|
|
236
236
|
}>;
|
|
237
237
|
}, z.core.$strip>;
|
|
238
238
|
type: z.ZodEnum<{
|
|
239
|
-
"node.start": "node.start";
|
|
240
|
-
"node.finish": "node.finish";
|
|
241
239
|
"agent.finish": "agent.finish";
|
|
242
240
|
"agent.start": "agent.start";
|
|
241
|
+
"node.finish": "node.finish";
|
|
242
|
+
"node.start": "node.start";
|
|
243
243
|
}>;
|
|
244
244
|
}, z.core.$strip>, z.ZodObject<{
|
|
245
245
|
at: z.ZodOptional<z.ZodString>;
|
|
@@ -286,8 +286,8 @@ declare const runnerEventBatchSchema: z.ZodObject<{
|
|
|
286
286
|
nodeId: z.ZodOptional<z.ZodString>;
|
|
287
287
|
outputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
288
288
|
status: z.ZodEnum<{
|
|
289
|
-
fail: "fail";
|
|
290
289
|
pass: "pass";
|
|
290
|
+
fail: "fail";
|
|
291
291
|
skip: "skip";
|
|
292
292
|
}>;
|
|
293
293
|
summary: z.ZodOptional<z.ZodString>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolvePackageAssetPath } from "../../package-assets.js";
|
|
2
2
|
import { resolveFileReference } from "../../path-refs.js";
|
|
3
3
|
import { gatewayServerForProfile } from "../../mcp/gateway.js";
|
|
4
|
-
import {
|
|
4
|
+
import { selectNodeModelCandidates } from "../../model-resolver.js";
|
|
5
5
|
import { createRunnerLaunchPlan } from "../../runner.js";
|
|
6
6
|
import { normalizeRunnerOutput, runnerTextCandidates } from "../../runner-output.js";
|
|
7
7
|
import { estimateTokens } from "../../token-estimator.js";
|
|
@@ -29,17 +29,72 @@ function executeAgentNodeEffect(node, context, attempt) {
|
|
|
29
29
|
if (decision.overBudget) return {
|
|
30
30
|
evidence: [
|
|
31
31
|
`agent boundary node=${node.id} profile=${node.profile}`,
|
|
32
|
-
`over token budget: ${decision.
|
|
33
|
-
...decision.
|
|
32
|
+
`over token budget: ${decision.reason}`,
|
|
33
|
+
...decision.skipped.length ? [`model fallbacks skipped: ${decision.skipped.join(", ")}`] : []
|
|
34
34
|
],
|
|
35
35
|
exitCode: 1,
|
|
36
36
|
output: ""
|
|
37
37
|
};
|
|
38
|
-
const
|
|
38
|
+
const profileId = node.profile;
|
|
39
|
+
const fallbackEvidence = [];
|
|
40
|
+
const lastIndex = decision.candidates.length - 1;
|
|
41
|
+
for (let index = 0; index < lastIndex; index += 1) {
|
|
42
|
+
const model = decision.candidates[index];
|
|
43
|
+
const attemptOutcome = yield* runModelAttemptEffect({
|
|
44
|
+
attempt,
|
|
45
|
+
context,
|
|
46
|
+
model,
|
|
47
|
+
node,
|
|
48
|
+
profileId,
|
|
49
|
+
prompt
|
|
50
|
+
});
|
|
51
|
+
if (attemptOutcome.result.exitCode !== 70) return yield* buildAgentAttemptResultEffect({
|
|
52
|
+
attempt,
|
|
53
|
+
context,
|
|
54
|
+
decision,
|
|
55
|
+
fallbackEvidence,
|
|
56
|
+
model,
|
|
57
|
+
node,
|
|
58
|
+
outcome: attemptOutcome,
|
|
59
|
+
profileId
|
|
60
|
+
});
|
|
61
|
+
fallbackEvidence.push(fallbackNote(model, decision.candidates[index + 1], attemptOutcome.result));
|
|
62
|
+
}
|
|
63
|
+
const lastModel = decision.candidates[lastIndex];
|
|
64
|
+
return yield* buildAgentAttemptResultEffect({
|
|
65
|
+
attempt,
|
|
66
|
+
context,
|
|
67
|
+
decision,
|
|
68
|
+
fallbackEvidence,
|
|
69
|
+
model: lastModel,
|
|
70
|
+
node,
|
|
71
|
+
outcome: yield* runModelAttemptEffect({
|
|
72
|
+
attempt,
|
|
73
|
+
context,
|
|
74
|
+
model: lastModel,
|
|
75
|
+
node,
|
|
76
|
+
profileId,
|
|
77
|
+
prompt
|
|
78
|
+
}),
|
|
79
|
+
profileId
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function modelLabel(model) {
|
|
84
|
+
return model ?? "profile/default";
|
|
85
|
+
}
|
|
86
|
+
function fallbackNote(failed, next, result) {
|
|
87
|
+
const detail = result.stderr ? `: ${result.stderr}` : "";
|
|
88
|
+
return `model ${modelLabel(failed)} failed (infra exit ${result.exitCode}${detail}); falling back to ${modelLabel(next)}`;
|
|
89
|
+
}
|
|
90
|
+
function runModelAttemptEffect(inputs) {
|
|
91
|
+
return Effect.gen(function* () {
|
|
92
|
+
const { attempt, context, model, node, profileId, prompt } = inputs;
|
|
93
|
+
const service = yield* AgentNodeRuntimeService;
|
|
39
94
|
const plan = createRunnerLaunchPlan(context.config, {
|
|
40
|
-
model
|
|
95
|
+
model,
|
|
41
96
|
nodeId: node.id,
|
|
42
|
-
profileId
|
|
97
|
+
profileId,
|
|
43
98
|
prompt,
|
|
44
99
|
reasoningEffort: node.reasoning_effort,
|
|
45
100
|
worktreePath: context.worktreePath
|
|
@@ -47,12 +102,22 @@ function executeAgentNodeEffect(node, context, attempt) {
|
|
|
47
102
|
if (node.timeoutMs) plan.timeoutMs = node.timeoutMs;
|
|
48
103
|
context.agentInvocations.push(plan);
|
|
49
104
|
emitAgentStart(context, plan, attempt);
|
|
50
|
-
const result = yield*
|
|
105
|
+
const result = yield* service.executeRunner(context.executor, plan, {
|
|
51
106
|
onOutput: agentOutputRecorder(context, node, attempt),
|
|
52
107
|
signal: context.signal
|
|
53
108
|
});
|
|
54
109
|
emitAgentFinish(context, plan, attempt, result);
|
|
55
110
|
if (result.sessionId) context.nodeStateStore.recordSessionId(node.id, result.sessionId);
|
|
111
|
+
return {
|
|
112
|
+
plan,
|
|
113
|
+
result
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function buildAgentAttemptResultEffect(inputs) {
|
|
118
|
+
return Effect.gen(function* () {
|
|
119
|
+
const { attempt, context, decision, fallbackEvidence, model, node, outcome, profileId } = inputs;
|
|
120
|
+
const { plan, result } = outcome;
|
|
56
121
|
const finalized = yield* finalizeAgentOutputEffect({
|
|
57
122
|
context,
|
|
58
123
|
node,
|
|
@@ -64,10 +129,11 @@ function executeAgentNodeEffect(node, context, attempt) {
|
|
|
64
129
|
const handoff = yield* maybeDeriveHandoffEffect(context, node, finalized.output, attempt);
|
|
65
130
|
return withOptionalHandoff({
|
|
66
131
|
evidence: [
|
|
67
|
-
`agent boundary node=${node.id} profile=${
|
|
132
|
+
`agent boundary node=${node.id} profile=${profileId} runner=${plan.runnerId}`,
|
|
68
133
|
`estimated context tokens: ${decision.estimatedTokens}`,
|
|
69
|
-
`model selection: ${
|
|
70
|
-
...
|
|
134
|
+
`model selection: ${modelLabel(model)} (${decision.reason})`,
|
|
135
|
+
...decision.skipped.length ? [`model fallbacks skipped: ${decision.skipped.join(", ")}`] : [],
|
|
136
|
+
...fallbackEvidence,
|
|
71
137
|
...finalized.evidence,
|
|
72
138
|
...result.stderr ? [`stderr: ${result.stderr}`] : [],
|
|
73
139
|
...result.timedOut ? ["agent timed out"] : []
|
|
@@ -151,27 +217,28 @@ function createHandoffFinalizerPlan(context, node, runner, rawOutput) {
|
|
|
151
217
|
}
|
|
152
218
|
/**
|
|
153
219
|
* Pure model-routing decision for a node: estimate the assembled prompt size and
|
|
154
|
-
*
|
|
155
|
-
* A node with no fallback array
|
|
156
|
-
*
|
|
157
|
-
*
|
|
220
|
+
* resolve the ordered fallback set of models whose window holds it within the
|
|
221
|
+
* context cap. A node with no fallback array (or none surviving the filters)
|
|
222
|
+
* falls back to the profile default (`undefined`). A node with a fallback array
|
|
223
|
+
* but no fitting model under budget is `overBudget` — the caller fails it fast
|
|
224
|
+
* rather than truncating.
|
|
158
225
|
*/
|
|
159
226
|
function decideNodeModel(prompt, node, budget, availableModels) {
|
|
160
227
|
const estimatedTokens = estimateTokens(prompt);
|
|
161
|
-
|
|
162
|
-
estimatedTokens,
|
|
163
|
-
overBudget: false,
|
|
164
|
-
selection: selectNodeModel(node, { available: availableModels })
|
|
165
|
-
};
|
|
166
|
-
const selection = selectNodeModel(node, {
|
|
228
|
+
const candidates = selectNodeModelCandidates(node, {
|
|
167
229
|
available: availableModels,
|
|
168
|
-
budget
|
|
169
|
-
|
|
230
|
+
...budget ? {
|
|
231
|
+
budget,
|
|
232
|
+
estimatedTokens
|
|
233
|
+
} : {}
|
|
170
234
|
});
|
|
235
|
+
const overBudget = Boolean(budget && node.models?.length) && candidates.models.length === 0;
|
|
171
236
|
return {
|
|
237
|
+
candidates: candidates.models.length ? candidates.models : [void 0],
|
|
172
238
|
estimatedTokens,
|
|
173
|
-
overBudget
|
|
174
|
-
|
|
239
|
+
overBudget,
|
|
240
|
+
reason: candidates.reason,
|
|
241
|
+
skipped: candidates.skipped
|
|
175
242
|
};
|
|
176
243
|
}
|
|
177
244
|
function finalizeAgentOutputEffect(inputs) {
|
|
@@ -7,15 +7,6 @@ function createOpencodeSessionRegistry() {
|
|
|
7
7
|
return { sessions: /* @__PURE__ */ new Map() };
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* Distinguish infra failure (server/session error -> retry-eligible exit 70)
|
|
11
|
-
* from a normal agent completion (the agent may still have produced a wrong
|
|
12
|
-
* answer; gates decide that, exit 0). This mirrors the EXIT_STARTUP convention
|
|
13
|
-
* in runner-command/run.ts and feeds retry.ts via the node's retry policy.
|
|
14
|
-
*/
|
|
15
|
-
const EXIT_OK = 0;
|
|
16
|
-
const EXIT_AGENT_ERROR = 1;
|
|
17
|
-
const EXIT_INFRA = 70;
|
|
18
|
-
/**
|
|
19
10
|
* SDK-backed replacement for the subprocess `runLaunchPlan`. Conforms to the
|
|
20
11
|
* RuntimeContext.executor seam so agent-node never learns the transport.
|
|
21
12
|
*/
|
|
@@ -288,7 +279,7 @@ function successResult(plan, drive) {
|
|
|
288
279
|
};
|
|
289
280
|
return {
|
|
290
281
|
argv: plan.args,
|
|
291
|
-
exitCode:
|
|
282
|
+
exitCode: 0,
|
|
292
283
|
sessionId: drive.sessionId,
|
|
293
284
|
stdout
|
|
294
285
|
};
|
|
@@ -296,7 +287,7 @@ function successResult(plan, drive) {
|
|
|
296
287
|
function failureResult(plan, error) {
|
|
297
288
|
return {
|
|
298
289
|
argv: plan.args,
|
|
299
|
-
exitCode:
|
|
290
|
+
exitCode: 70,
|
|
300
291
|
stderr: `opencode session failed: ${errorMessage(error)}`,
|
|
301
292
|
stdout: ""
|
|
302
293
|
};
|
|
@@ -309,8 +300,8 @@ function failureResult(plan, error) {
|
|
|
309
300
|
function infraErrorExitCode(error) {
|
|
310
301
|
switch (error.name) {
|
|
311
302
|
case "MessageOutputLengthError":
|
|
312
|
-
case "MessageAbortedError": return
|
|
313
|
-
default: return
|
|
303
|
+
case "MessageAbortedError": return 1;
|
|
304
|
+
default: return 70;
|
|
314
305
|
}
|
|
315
306
|
}
|
|
316
307
|
function describeMessageError(error) {
|
|
@@ -319,10 +310,58 @@ function describeMessageError(error) {
|
|
|
319
310
|
const detail = data && typeof data.message === "string" ? `: ${data.message}` : "";
|
|
320
311
|
return `${error.name}${detail}`;
|
|
321
312
|
}
|
|
322
|
-
function unwrap(
|
|
323
|
-
if (
|
|
324
|
-
if (
|
|
325
|
-
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];
|
|
326
365
|
}
|
|
327
366
|
function errorMessage(error) {
|
|
328
367
|
return error instanceof Error ? error.message : String(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.16",
|
|
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",
|