@h-rig/cli-surface-plugin 0.0.6-alpha.157 → 0.0.6-alpha.158
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/dist/src/app/drone-ui.d.ts +0 -11
- package/dist/src/app/drone-ui.js +0 -114
- package/dist/src/commands/_async-ui.d.ts +1 -1
- package/dist/src/commands/_cli-format.d.ts +0 -29
- package/dist/src/commands/_cli-format.js +59 -113
- package/dist/src/commands/_connection-state.d.ts +6 -33
- package/dist/src/commands/_connection-state.js +654 -138
- package/dist/src/commands/_doctor-checks.d.ts +2 -5
- package/dist/src/commands/_doctor-checks.js +10 -9
- package/dist/src/commands/_help-catalog.d.ts +2 -1
- package/dist/src/commands/_help-catalog.js +654 -7
- package/dist/src/commands/_inprocess-services.d.ts +5 -5
- package/dist/src/commands/_inprocess-services.js +1 -1
- package/dist/src/commands/_parsers.js +651 -12
- package/dist/src/commands/_paths.d.ts +0 -2
- package/dist/src/commands/_paths.js +2 -10
- package/dist/src/commands/_pi-install.d.ts +2 -12
- package/dist/src/commands/_pi-install.js +3 -36
- package/dist/src/commands/_policy.js +659 -20
- package/dist/src/commands/agent.d.ts +1 -1
- package/dist/src/commands/agent.js +675 -24
- package/dist/src/commands/config.d.ts +1 -1
- package/dist/src/commands/config.js +656 -21
- package/dist/src/commands/dist.d.ts +1 -1
- package/dist/src/commands/dist.js +828 -102
- package/dist/src/commands/doctor.d.ts +1 -1
- package/dist/src/commands/doctor.js +658 -12
- package/dist/src/commands/github.d.ts +1 -1
- package/dist/src/commands/github.js +658 -19
- package/dist/src/commands/inbox.d.ts +12 -8
- package/dist/src/commands/inbox.js +741 -22
- package/dist/src/commands/init.d.ts +17 -19
- package/dist/src/commands/init.js +836 -306
- package/dist/src/commands/inspect.d.ts +5 -6
- package/dist/src/commands/inspect.js +754 -42
- package/dist/src/commands/pi.d.ts +1 -1
- package/dist/src/commands/pi.js +655 -16
- package/dist/src/commands/plugin.d.ts +9 -9
- package/dist/src/commands/plugin.js +652 -13
- package/dist/src/commands/profile-and-review.d.ts +1 -1
- package/dist/src/commands/profile-and-review.js +655 -16
- package/dist/src/commands/queue.d.ts +1 -1
- package/dist/src/commands/queue.js +871 -12
- package/dist/src/commands/remote-client.d.ts +152 -0
- package/dist/src/commands/remote-client.js +475 -0
- package/dist/src/commands/remote.d.ts +1 -1
- package/dist/src/commands/remote.js +1100 -29
- package/dist/src/commands/repo-git-harness.d.ts +1 -1
- package/dist/src/commands/repo-git-harness.js +2321 -47
- package/dist/src/commands/run.d.ts +10 -6
- package/dist/src/commands/run.js +830 -50
- package/dist/src/commands/server.d.ts +1 -1
- package/dist/src/commands/server.js +649 -11
- package/dist/src/commands/setup.d.ts +2 -2
- package/dist/src/commands/setup.js +829 -18
- package/dist/src/commands/stats.d.ts +2 -4
- package/dist/src/commands/stats.js +1299 -20
- package/dist/src/commands/test.d.ts +1 -1
- package/dist/src/commands/test.js +648 -9
- package/dist/src/commands/triage.d.ts +2 -3
- package/dist/src/commands/triage.js +657 -11
- package/dist/src/commands/workspace.d.ts +1 -1
- package/dist/src/commands/workspace.js +1280 -15
- package/dist/src/control-plane/agent-binary-build.d.ts +9 -0
- package/dist/src/control-plane/agent-binary-build.js +88 -0
- package/dist/src/control-plane/embedded-native-assets.d.ts +7 -0
- package/dist/src/control-plane/embedded-native-assets.js +6 -0
- package/dist/src/control-plane/guard.d.ts +17 -0
- package/dist/src/control-plane/guard.js +684 -0
- package/dist/src/control-plane/harness-cli.d.ts +12 -0
- package/dist/src/control-plane/harness-cli.js +1623 -0
- package/dist/src/control-plane/native/git-ops.d.ts +67 -0
- package/dist/src/control-plane/native/git-ops.js +1381 -0
- package/dist/src/control-plane/native/github-auth-env.d.ts +1 -0
- package/dist/src/control-plane/native/github-auth-env.js +21 -0
- package/dist/src/control-plane/native/host-git.d.ts +4 -0
- package/dist/src/control-plane/native/host-git.js +51 -0
- package/dist/src/control-plane/priority-queue.d.ts +22 -0
- package/dist/src/control-plane/priority-queue.js +212 -0
- package/dist/src/control-plane/rigfig.d.ts +9 -0
- package/dist/src/control-plane/rigfig.js +70 -0
- package/dist/src/control-plane/scope.d.ts +3 -0
- package/dist/src/control-plane/scope.js +58 -0
- package/dist/src/control-plane/setup-status.d.ts +44 -0
- package/dist/src/control-plane/setup-status.js +164 -0
- package/dist/src/control-plane/task-data.d.ts +2 -0
- package/dist/src/control-plane/task-data.js +12 -0
- package/dist/src/control-plane/workspace-ops.d.ts +79 -0
- package/dist/src/control-plane/workspace-ops.js +639 -0
- package/dist/src/help-catalog-data.d.ts +7 -0
- package/dist/src/help-catalog-data.js +660 -0
- package/dist/src/kernel-dispatch.js +1 -3
- package/dist/src/plugin.js +10072 -30
- package/dist/src/runner.d.ts +7 -9
- package/dist/src/runner.js +750 -30
- package/package.json +12 -13
- package/dist/src/commands/_json-output.d.ts +0 -11
- package/dist/src/commands/_json-output.js +0 -54
- package/dist/src/commands/_pi-frontend.d.ts +0 -35
- package/dist/src/commands/_pi-frontend.js +0 -64
- package/dist/src/commands/_run-driver-helpers.d.ts +0 -26
- package/dist/src/commands/_run-driver-helpers.js +0 -132
- package/dist/src/commands/task-run-driver.d.ts +0 -93
- package/dist/src/commands/task-run-driver.js +0 -136
- package/dist/src/commands/task.d.ts +0 -46
- package/dist/src/commands/task.js +0 -555
- package/dist/src/provider-model.d.ts +0 -34
- package/dist/src/provider-model.js +0 -56
- package/dist/src/rig-config-package-deps.d.ts +0 -10
- package/dist/src/rig-config-package-deps.js +0 -272
- package/dist/src/version.d.ts +0 -8
- package/dist/src/version.js +0 -47
package/dist/src/commands/run.js
CHANGED
|
@@ -1,39 +1,665 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var __require = import.meta.require;
|
|
3
|
-
|
|
4
2
|
// packages/cli-surface-plugin/src/commands/run.ts
|
|
5
|
-
import { TERMINAL_RUN_STATUSES } from "@rig/contracts";
|
|
6
|
-
import {
|
|
7
|
-
listTasks,
|
|
8
|
-
normalizeTaskId,
|
|
9
|
-
readTaskTitle
|
|
10
|
-
} from "@rig/core/task-io";
|
|
11
|
-
import { removeRegistryRoom } from "@rig/runtime/control-plane/registry-room";
|
|
12
|
-
import { isReadyTask, resolveStartTask, selectNextReadyTask } from "@rig/dependency-graph-plugin";
|
|
13
3
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
readSessionRunEntries,
|
|
21
|
-
resolveJoinTarget
|
|
22
|
-
} from "@rig/run-worker/runs";
|
|
4
|
+
RUN_READ_MODEL,
|
|
5
|
+
TASK_IO_SERVICE_CAPABILITY,
|
|
6
|
+
TASK_SELECTION
|
|
7
|
+
} from "@rig/contracts";
|
|
8
|
+
import { defineCapability } from "@rig/core/capability";
|
|
9
|
+
import { requireCapabilityForRoot } from "@rig/core/capability-loaders";
|
|
23
10
|
|
|
24
11
|
// packages/cli-surface-plugin/src/runner.ts
|
|
25
|
-
import { EventBus } from "@rig/runtime
|
|
26
|
-
import { CliError as RuntimeCliError } from "@rig/
|
|
27
|
-
|
|
28
|
-
|
|
12
|
+
import { EventBus } from "@rig/core/runtime-events";
|
|
13
|
+
import { CliError as RuntimeCliError } from "@rig/contracts";
|
|
14
|
+
|
|
15
|
+
// packages/cli-surface-plugin/src/control-plane/guard.ts
|
|
16
|
+
import { optimizeNextInvocation } from "bun:jsc";
|
|
17
|
+
import { existsSync, readFileSync, statSync } from "fs";
|
|
18
|
+
import { resolve } from "path";
|
|
29
19
|
|
|
20
|
+
// packages/cli-surface-plugin/src/control-plane/scope.ts
|
|
21
|
+
import { getScopeRules } from "@rig/core/scope-rules";
|
|
22
|
+
var scopeRegexCache = new Map;
|
|
23
|
+
function unique(values) {
|
|
24
|
+
return [...new Set(values)];
|
|
25
|
+
}
|
|
26
|
+
function normalizeRelativeScopePath(inputPath) {
|
|
27
|
+
let normalized = inputPath.replace(/^\.\//, "");
|
|
28
|
+
const rules = getScopeRules();
|
|
29
|
+
if (rules?.stripPrefixes) {
|
|
30
|
+
for (const prefix of rules.stripPrefixes) {
|
|
31
|
+
if (normalized.startsWith(prefix)) {
|
|
32
|
+
normalized = normalized.slice(prefix.length);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return normalized;
|
|
37
|
+
}
|
|
38
|
+
function normalizePathToScope(projectRoot, monorepoRoot, inputPath) {
|
|
39
|
+
let normalized = inputPath.replace(/^\.\//, "");
|
|
40
|
+
if (normalized.startsWith(projectRoot + "/")) {
|
|
41
|
+
normalized = normalized.slice(projectRoot.length + 1);
|
|
42
|
+
}
|
|
43
|
+
if (normalized.startsWith(monorepoRoot + "/")) {
|
|
44
|
+
normalized = normalized.slice(monorepoRoot.length + 1);
|
|
45
|
+
}
|
|
46
|
+
return normalizeRelativeScopePath(normalized);
|
|
47
|
+
}
|
|
48
|
+
function scopeGlobToRegex(glob) {
|
|
49
|
+
const cached = scopeRegexCache.get(glob);
|
|
50
|
+
if (cached) {
|
|
51
|
+
return cached;
|
|
52
|
+
}
|
|
53
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "__GLOBSTAR__").replace(/\*/g, "[^/]*").replace(/__GLOBSTAR__/g, ".*");
|
|
54
|
+
const compiled = new RegExp(`^${escaped}$`);
|
|
55
|
+
scopeRegexCache.set(glob, compiled);
|
|
56
|
+
return compiled;
|
|
57
|
+
}
|
|
58
|
+
function scopeMatches(path, scopes) {
|
|
59
|
+
const pathVariants = unique([path, normalizeRelativeScopePath(path)]);
|
|
60
|
+
for (const scope of scopes) {
|
|
61
|
+
const scopeVariants = unique([scope, normalizeRelativeScopePath(scope)]);
|
|
62
|
+
for (const candidatePath of pathVariants) {
|
|
63
|
+
for (const candidateScope of scopeVariants) {
|
|
64
|
+
if (candidatePath === candidateScope || scopeGlobToRegex(candidateScope).test(candidatePath)) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// packages/cli-surface-plugin/src/control-plane/guard.ts
|
|
74
|
+
import {
|
|
75
|
+
POLICY_VERSION
|
|
76
|
+
} from "@rig/contracts";
|
|
77
|
+
var DEFAULT_SCOPE = {
|
|
78
|
+
fail_closed: true,
|
|
79
|
+
harness_paths_exempt: true,
|
|
80
|
+
runtime_paths_exempt: true
|
|
81
|
+
};
|
|
82
|
+
var DEFAULT_SANDBOX = {
|
|
83
|
+
mode: "enforce",
|
|
84
|
+
network: true,
|
|
85
|
+
read_deny: [],
|
|
86
|
+
write_allow_from_runtime: true
|
|
87
|
+
};
|
|
88
|
+
var DEFAULT_ISOLATION = {
|
|
89
|
+
default_mode: "worktree",
|
|
90
|
+
repo_symlink_fallback: false,
|
|
91
|
+
strict_provisioning: true,
|
|
92
|
+
fail_closed_on_provision_error: true
|
|
93
|
+
};
|
|
94
|
+
var DEFAULT_COMPLETION = {
|
|
95
|
+
derive_checks_from_scope: true,
|
|
96
|
+
checks: [],
|
|
97
|
+
typescript_config_probe: ["tsconfig.json"],
|
|
98
|
+
eslint_config_probe: [".eslintrc.js", ".eslintrc.json", "eslint.config.js"]
|
|
99
|
+
};
|
|
100
|
+
var DEFAULT_RUNTIME_IMAGE = {
|
|
101
|
+
deps: {
|
|
102
|
+
monorepo_install: false,
|
|
103
|
+
hp_next_install: false
|
|
104
|
+
},
|
|
105
|
+
plugins_require_binaries: true
|
|
106
|
+
};
|
|
107
|
+
var DEFAULT_RUNTIME_SNAPSHOT = {
|
|
108
|
+
enabled: true
|
|
109
|
+
};
|
|
110
|
+
function defaultPolicy() {
|
|
111
|
+
return {
|
|
112
|
+
version: POLICY_VERSION,
|
|
113
|
+
mode: "enforce",
|
|
114
|
+
scope: { ...DEFAULT_SCOPE },
|
|
115
|
+
rules: [],
|
|
116
|
+
sandbox: { ...DEFAULT_SANDBOX },
|
|
117
|
+
isolation: { ...DEFAULT_ISOLATION },
|
|
118
|
+
completion: { ...DEFAULT_COMPLETION },
|
|
119
|
+
runtime_image: {
|
|
120
|
+
deps: { ...DEFAULT_RUNTIME_IMAGE.deps },
|
|
121
|
+
plugins_require_binaries: DEFAULT_RUNTIME_IMAGE.plugins_require_binaries
|
|
122
|
+
},
|
|
123
|
+
runtime_snapshot: { ...DEFAULT_RUNTIME_SNAPSHOT }
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
var policyCache = null;
|
|
127
|
+
var policyCachePath = null;
|
|
128
|
+
var seededPolicyConfig = null;
|
|
129
|
+
var compiledRegexCache = new Map;
|
|
130
|
+
function loadPolicy(projectRoot) {
|
|
131
|
+
if (seededPolicyConfig) {
|
|
132
|
+
return seededPolicyConfig;
|
|
133
|
+
}
|
|
134
|
+
const configPath = resolve(projectRoot, "rig/policy/policy.json");
|
|
135
|
+
if (!existsSync(configPath)) {
|
|
136
|
+
return defaultPolicy();
|
|
137
|
+
}
|
|
138
|
+
let mtimeMs;
|
|
139
|
+
try {
|
|
140
|
+
mtimeMs = statSync(configPath).mtimeMs;
|
|
141
|
+
} catch {
|
|
142
|
+
return defaultPolicy();
|
|
143
|
+
}
|
|
144
|
+
if (policyCache && policyCachePath === configPath && policyCache.mtimeMs === mtimeMs) {
|
|
145
|
+
return policyCache.config;
|
|
146
|
+
}
|
|
147
|
+
let parsed;
|
|
148
|
+
try {
|
|
149
|
+
parsed = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
150
|
+
} catch {
|
|
151
|
+
return defaultPolicy();
|
|
152
|
+
}
|
|
153
|
+
const config = mergeWithDefaults(parsed);
|
|
154
|
+
policyCache = { mtimeMs, config };
|
|
155
|
+
policyCachePath = configPath;
|
|
156
|
+
return config;
|
|
157
|
+
}
|
|
158
|
+
function mergeWithDefaults(parsed) {
|
|
159
|
+
const base = defaultPolicy();
|
|
160
|
+
if (typeof parsed.mode === "string" && isValidMode(parsed.mode)) {
|
|
161
|
+
base.mode = parsed.mode;
|
|
162
|
+
}
|
|
163
|
+
if (parsed.scope && typeof parsed.scope === "object" && !Array.isArray(parsed.scope)) {
|
|
164
|
+
const s = parsed.scope;
|
|
165
|
+
if (typeof s.fail_closed === "boolean")
|
|
166
|
+
base.scope.fail_closed = s.fail_closed;
|
|
167
|
+
if (typeof s.harness_paths_exempt === "boolean")
|
|
168
|
+
base.scope.harness_paths_exempt = s.harness_paths_exempt;
|
|
169
|
+
if (typeof s.runtime_paths_exempt === "boolean")
|
|
170
|
+
base.scope.runtime_paths_exempt = s.runtime_paths_exempt;
|
|
171
|
+
}
|
|
172
|
+
if (Array.isArray(parsed.rules)) {
|
|
173
|
+
base.rules = precompilePolicyRuleRegexes(parsed.rules.filter(isValidRule));
|
|
174
|
+
}
|
|
175
|
+
if (Array.isArray(parsed.deny) && base.rules.length === 0) {
|
|
176
|
+
base.rules = precompilePolicyRuleRegexes(migrateLegacyDeny(parsed.deny));
|
|
177
|
+
}
|
|
178
|
+
if (parsed.sandbox && typeof parsed.sandbox === "object" && !Array.isArray(parsed.sandbox)) {
|
|
179
|
+
const sb = parsed.sandbox;
|
|
180
|
+
if (typeof sb.mode === "string" && isValidMode(sb.mode))
|
|
181
|
+
base.sandbox.mode = sb.mode;
|
|
182
|
+
if (typeof sb.network === "boolean")
|
|
183
|
+
base.sandbox.network = sb.network;
|
|
184
|
+
if (Array.isArray(sb.read_deny))
|
|
185
|
+
base.sandbox.read_deny = sb.read_deny.filter((v) => typeof v === "string");
|
|
186
|
+
if (typeof sb.write_allow_from_runtime === "boolean")
|
|
187
|
+
base.sandbox.write_allow_from_runtime = sb.write_allow_from_runtime;
|
|
188
|
+
}
|
|
189
|
+
if (parsed.isolation && typeof parsed.isolation === "object" && !Array.isArray(parsed.isolation)) {
|
|
190
|
+
const iso = parsed.isolation;
|
|
191
|
+
if (iso.default_mode === "worktree")
|
|
192
|
+
base.isolation.default_mode = iso.default_mode;
|
|
193
|
+
if (typeof iso.repo_symlink_fallback === "boolean")
|
|
194
|
+
base.isolation.repo_symlink_fallback = iso.repo_symlink_fallback;
|
|
195
|
+
if (typeof iso.strict_provisioning === "boolean")
|
|
196
|
+
base.isolation.strict_provisioning = iso.strict_provisioning;
|
|
197
|
+
if (typeof iso.fail_closed_on_provision_error === "boolean")
|
|
198
|
+
base.isolation.fail_closed_on_provision_error = iso.fail_closed_on_provision_error;
|
|
199
|
+
}
|
|
200
|
+
if (parsed.completion && typeof parsed.completion === "object" && !Array.isArray(parsed.completion)) {
|
|
201
|
+
const comp = parsed.completion;
|
|
202
|
+
if (typeof comp.derive_checks_from_scope === "boolean")
|
|
203
|
+
base.completion.derive_checks_from_scope = comp.derive_checks_from_scope;
|
|
204
|
+
if (Array.isArray(comp.checks))
|
|
205
|
+
base.completion.checks = comp.checks.filter((v) => typeof v === "string");
|
|
206
|
+
if (Array.isArray(comp.typescript_config_probe))
|
|
207
|
+
base.completion.typescript_config_probe = comp.typescript_config_probe.filter((v) => typeof v === "string");
|
|
208
|
+
if (Array.isArray(comp.eslint_config_probe))
|
|
209
|
+
base.completion.eslint_config_probe = comp.eslint_config_probe.filter((v) => typeof v === "string");
|
|
210
|
+
}
|
|
211
|
+
if (parsed.runtime_image && typeof parsed.runtime_image === "object" && !Array.isArray(parsed.runtime_image)) {
|
|
212
|
+
const runtimeImage = parsed.runtime_image;
|
|
213
|
+
if (runtimeImage.deps && typeof runtimeImage.deps === "object" && !Array.isArray(runtimeImage.deps)) {
|
|
214
|
+
const deps = runtimeImage.deps;
|
|
215
|
+
if (typeof deps.monorepo_install === "boolean") {
|
|
216
|
+
base.runtime_image.deps.monorepo_install = deps.monorepo_install;
|
|
217
|
+
}
|
|
218
|
+
if (typeof deps.hp_next_install === "boolean") {
|
|
219
|
+
base.runtime_image.deps.hp_next_install = deps.hp_next_install;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (typeof runtimeImage.plugins_require_binaries === "boolean") {
|
|
223
|
+
base.runtime_image.plugins_require_binaries = runtimeImage.plugins_require_binaries;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (parsed.runtime_snapshot && typeof parsed.runtime_snapshot === "object" && !Array.isArray(parsed.runtime_snapshot)) {
|
|
227
|
+
const runtimeSnapshot = parsed.runtime_snapshot;
|
|
228
|
+
if (typeof runtimeSnapshot.enabled === "boolean") {
|
|
229
|
+
base.runtime_snapshot.enabled = runtimeSnapshot.enabled;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return base;
|
|
233
|
+
}
|
|
234
|
+
function isValidMode(value) {
|
|
235
|
+
return value === "off" || value === "observe" || value === "enforce";
|
|
236
|
+
}
|
|
237
|
+
function isValidRule(value) {
|
|
238
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
239
|
+
return false;
|
|
240
|
+
const r = value;
|
|
241
|
+
return typeof r.id === "string" && typeof r.category === "string" && r.match != null && typeof r.match === "object";
|
|
242
|
+
}
|
|
243
|
+
function migrateLegacyDeny(deny) {
|
|
244
|
+
const rules = [];
|
|
245
|
+
for (const entry of deny) {
|
|
246
|
+
if (typeof entry.id !== "string")
|
|
247
|
+
continue;
|
|
248
|
+
const match = {};
|
|
249
|
+
if (typeof entry.pattern === "string")
|
|
250
|
+
match.pattern = entry.pattern;
|
|
251
|
+
if (typeof entry.regex === "string")
|
|
252
|
+
match.regex = entry.regex;
|
|
253
|
+
if (!match.pattern && !match.regex)
|
|
254
|
+
continue;
|
|
255
|
+
rules.push({
|
|
256
|
+
id: entry.id,
|
|
257
|
+
category: "command",
|
|
258
|
+
match,
|
|
259
|
+
action: "block",
|
|
260
|
+
...typeof entry.description === "string" ? { description: entry.description } : {}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
return rules;
|
|
264
|
+
}
|
|
265
|
+
function precompilePolicyRuleRegexes(rules) {
|
|
266
|
+
return rules.map((rule) => {
|
|
267
|
+
const compiledRegex = rule.match.regex ? compileSafeRegex(rule.match.regex, `rules.${rule.id}.match.regex`, true) : undefined;
|
|
268
|
+
const compiledUnlessRegex = rule.unless?.regex ? compileSafeRegex(rule.unless.regex, `rules.${rule.id}.unless.regex`, true) : undefined;
|
|
269
|
+
return {
|
|
270
|
+
...rule,
|
|
271
|
+
...compiledRegex ? { compiledRegex } : {},
|
|
272
|
+
...compiledUnlessRegex ? { compiledUnlessRegex } : {}
|
|
273
|
+
};
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
function getRegexUnsafeReason(pattern) {
|
|
277
|
+
if (pattern.length > 512) {
|
|
278
|
+
return "pattern exceeds max safe length (512 chars)";
|
|
279
|
+
}
|
|
280
|
+
if (/\\[1-9]/.test(pattern)) {
|
|
281
|
+
return "pattern uses backreferences";
|
|
282
|
+
}
|
|
283
|
+
if (/\((?:[^()\\]|\\.)*[+*](?:[^()\\]|\\.)*\)\s*[*+{]/.test(pattern)) {
|
|
284
|
+
return "pattern contains nested quantifiers";
|
|
285
|
+
}
|
|
286
|
+
if (/\((?:[^()\\]|\\.)*\.\\?[+*](?:[^()\\]|\\.)*\)\s*[*+{]/.test(pattern)) {
|
|
287
|
+
return "pattern contains nested broad quantifiers";
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
function compileSafeRegex(pattern, sourceLabel, logOnFailure) {
|
|
292
|
+
const cached = compiledRegexCache.get(pattern);
|
|
293
|
+
if (cached !== undefined) {
|
|
294
|
+
return cached ?? undefined;
|
|
295
|
+
}
|
|
296
|
+
const unsafeReason = getRegexUnsafeReason(pattern);
|
|
297
|
+
if (unsafeReason) {
|
|
298
|
+
if (logOnFailure) {
|
|
299
|
+
console.warn(`[policy] Skipping unsafe regex in ${sourceLabel}: ${unsafeReason}`);
|
|
300
|
+
}
|
|
301
|
+
compiledRegexCache.set(pattern, null);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
const compiled = new RegExp(pattern);
|
|
306
|
+
compiledRegexCache.set(pattern, compiled);
|
|
307
|
+
return compiled;
|
|
308
|
+
} catch (error) {
|
|
309
|
+
if (logOnFailure) {
|
|
310
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
311
|
+
console.warn(`[policy] Skipping invalid regex in ${sourceLabel}: ${message}`);
|
|
312
|
+
}
|
|
313
|
+
compiledRegexCache.set(pattern, null);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function matchRule(rule, input) {
|
|
318
|
+
const { match } = rule;
|
|
319
|
+
if (match.pattern && input.includes(match.pattern)) {
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
if (match.regex) {
|
|
323
|
+
const compiled = rule.compiledRegex || compileSafeRegex(match.regex, `rules.${rule.id}.match.regex`, false);
|
|
324
|
+
if (!compiled) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
try {
|
|
328
|
+
return compiled.test(input);
|
|
329
|
+
} catch {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
function matchRuleUnless(rule, command, taskId) {
|
|
336
|
+
if (!rule.unless)
|
|
337
|
+
return false;
|
|
338
|
+
if (rule.unless.regex) {
|
|
339
|
+
const compiled = rule.compiledUnlessRegex || compileSafeRegex(rule.unless.regex, `rules.${rule.id}.unless.regex`, false);
|
|
340
|
+
if (!compiled) {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
if (compiled.test(command))
|
|
345
|
+
return true;
|
|
346
|
+
} catch {}
|
|
347
|
+
}
|
|
348
|
+
if (rule.unless.task_in && taskId) {
|
|
349
|
+
if (rule.unless.task_in.includes(taskId))
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
function resolveAction(mode, matched) {
|
|
355
|
+
if (matched.length === 0)
|
|
356
|
+
return "allow";
|
|
357
|
+
if (mode === "off")
|
|
358
|
+
return "allow";
|
|
359
|
+
if (mode === "observe")
|
|
360
|
+
return "warn";
|
|
361
|
+
return "block";
|
|
362
|
+
}
|
|
363
|
+
function resolveAbsolutePath(projectRoot, rawPath) {
|
|
364
|
+
if (rawPath.startsWith("/"))
|
|
365
|
+
return resolve(rawPath);
|
|
366
|
+
return resolve(projectRoot, rawPath);
|
|
367
|
+
}
|
|
368
|
+
function isHarnessPath(projectRoot, rawPath) {
|
|
369
|
+
const absPath = resolveAbsolutePath(projectRoot, rawPath);
|
|
370
|
+
const managedRoots = [
|
|
371
|
+
resolve(projectRoot, "rig"),
|
|
372
|
+
resolve(projectRoot, ".rig"),
|
|
373
|
+
resolve(projectRoot, "artifacts")
|
|
374
|
+
];
|
|
375
|
+
return managedRoots.some((root) => absPath === root || absPath.startsWith(root + "/"));
|
|
376
|
+
}
|
|
377
|
+
function isRuntimePath(projectRoot, rawPath, taskWorkspace) {
|
|
378
|
+
const absPath = resolveAbsolutePath(projectRoot, rawPath);
|
|
379
|
+
if (taskWorkspace) {
|
|
380
|
+
const workspaceRigRoot = resolve(taskWorkspace, ".rig");
|
|
381
|
+
const workspaceArtifactsRoot = resolve(taskWorkspace, "artifacts");
|
|
382
|
+
if (absPath === workspaceRigRoot || absPath.startsWith(workspaceRigRoot + "/") || absPath === workspaceArtifactsRoot || absPath.startsWith(workspaceArtifactsRoot + "/")) {
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const runtimeRoot = resolve(projectRoot, ".rig/runtime/agents");
|
|
387
|
+
return absPath === runtimeRoot || absPath.startsWith(runtimeRoot + "/");
|
|
388
|
+
}
|
|
389
|
+
function isTestFile(path) {
|
|
390
|
+
return /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(path) || /\/(__tests__|tests|test)\//.test(path);
|
|
391
|
+
}
|
|
392
|
+
function evaluate(context) {
|
|
393
|
+
const policy = loadPolicy(context.projectRoot);
|
|
394
|
+
switch (context.evaluation.type) {
|
|
395
|
+
case "tool-call":
|
|
396
|
+
return evaluateToolCall(policy, context);
|
|
397
|
+
case "command":
|
|
398
|
+
return evaluateCommand(policy, context);
|
|
399
|
+
case "content-write":
|
|
400
|
+
return evaluateContent(policy, context);
|
|
401
|
+
case "file-access":
|
|
402
|
+
return evaluateScope(policy, context, context.evaluation.file_path, context.evaluation.access);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function evaluateScope(policy, context, filePath, access) {
|
|
406
|
+
const allowed = () => ({
|
|
407
|
+
allowed: true,
|
|
408
|
+
matchedRules: [],
|
|
409
|
+
action: "allow",
|
|
410
|
+
failClosed: false
|
|
411
|
+
});
|
|
412
|
+
if (policy.scope.harness_paths_exempt && isHarnessPath(context.projectRoot, filePath)) {
|
|
413
|
+
return allowed();
|
|
414
|
+
}
|
|
415
|
+
if (policy.scope.runtime_paths_exempt && isRuntimePath(context.projectRoot, filePath, context.taskWorkspace)) {
|
|
416
|
+
return allowed();
|
|
417
|
+
}
|
|
418
|
+
if (!context.taskId) {
|
|
419
|
+
if (access === "write" && policy.scope.fail_closed) {
|
|
420
|
+
return {
|
|
421
|
+
allowed: false,
|
|
422
|
+
matchedRules: [],
|
|
423
|
+
action: resolveAction(policy.mode, [{ id: "scope:no-task", category: "command", reason: "No active task; fail-closed for write operations" }]),
|
|
424
|
+
failClosed: true
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
return allowed();
|
|
428
|
+
}
|
|
429
|
+
const scopes = context.taskScopes || [];
|
|
430
|
+
if (scopes.length === 0) {
|
|
431
|
+
return allowed();
|
|
432
|
+
}
|
|
433
|
+
if (context.taskWorkspace && context.taskWorkspace !== context.projectRoot && filePath.startsWith("/")) {
|
|
434
|
+
const absPath = resolve(filePath);
|
|
435
|
+
if (!absPath.startsWith(context.taskWorkspace + "/") && !isHarnessPath(context.projectRoot, filePath)) {
|
|
436
|
+
const reason2 = `Absolute path '${filePath}' is outside task runtime boundary. Allowed root: ${context.taskWorkspace}`;
|
|
437
|
+
const matched2 = [{ id: "scope:workspace-boundary", category: "command", reason: reason2 }];
|
|
438
|
+
return {
|
|
439
|
+
allowed: policy.mode !== "enforce",
|
|
440
|
+
matchedRules: matched2,
|
|
441
|
+
action: resolveAction(policy.mode, matched2),
|
|
442
|
+
failClosed: false
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const monorepoRoot = context.monorepoRoot || process.env.MONOREPO_ROOT?.trim() || context.taskWorkspace || context.projectRoot;
|
|
447
|
+
let normalizedPath = filePath;
|
|
448
|
+
if (context.taskWorkspace && context.taskWorkspace !== context.projectRoot && filePath.startsWith(context.taskWorkspace + "/")) {
|
|
449
|
+
normalizedPath = filePath.slice(context.taskWorkspace.length + 1);
|
|
450
|
+
}
|
|
451
|
+
normalizedPath = normalizePathToScope(context.projectRoot, monorepoRoot, normalizedPath);
|
|
452
|
+
if (scopeMatches(filePath, scopes) || scopeMatches(normalizedPath, scopes)) {
|
|
453
|
+
return allowed();
|
|
454
|
+
}
|
|
455
|
+
const reason = `File '${filePath}' (normalized: '${normalizedPath}') is outside scope of task ${context.taskId}`;
|
|
456
|
+
const matched = [{ id: "scope:out-of-scope", category: "command", reason }];
|
|
457
|
+
return {
|
|
458
|
+
allowed: policy.mode !== "enforce",
|
|
459
|
+
matchedRules: matched,
|
|
460
|
+
action: resolveAction(policy.mode, matched),
|
|
461
|
+
failClosed: false
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
function evaluateCommand(policy, context) {
|
|
465
|
+
const evaluation = context.evaluation;
|
|
466
|
+
if (evaluation.type !== "command") {
|
|
467
|
+
return { allowed: true, matchedRules: [], action: "allow", failClosed: false };
|
|
468
|
+
}
|
|
469
|
+
const command = evaluation.command;
|
|
470
|
+
const matchedRules = [];
|
|
471
|
+
for (const rule of policy.rules) {
|
|
472
|
+
if (rule.category !== "command")
|
|
473
|
+
continue;
|
|
474
|
+
if (!matchRule(rule, command))
|
|
475
|
+
continue;
|
|
476
|
+
if (matchRuleUnless(rule, command, context.taskId))
|
|
477
|
+
continue;
|
|
478
|
+
matchedRules.push({
|
|
479
|
+
id: rule.id,
|
|
480
|
+
category: rule.category,
|
|
481
|
+
...rule.description !== undefined ? { description: rule.description } : {},
|
|
482
|
+
reason: rule.description || `Matched rule ${rule.id}`
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
const writeTarget = extractWriteTarget(command);
|
|
486
|
+
if (writeTarget && !/^\/dev\//.test(writeTarget) && !/^\/proc\//.test(writeTarget)) {
|
|
487
|
+
const scopeResult = evaluateScope(policy, context, writeTarget, "write");
|
|
488
|
+
if (!scopeResult.allowed || scopeResult.matchedRules.length > 0) {
|
|
489
|
+
matchedRules.push(...scopeResult.matchedRules);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const action = resolveAction(policy.mode, matchedRules);
|
|
493
|
+
return {
|
|
494
|
+
allowed: action !== "block",
|
|
495
|
+
matchedRules,
|
|
496
|
+
action,
|
|
497
|
+
failClosed: false
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
function extractWriteTarget(command) {
|
|
501
|
+
const redirect = command.match(/>>?\s+([^\s;|&]+)/);
|
|
502
|
+
if (redirect?.[1])
|
|
503
|
+
return redirect[1];
|
|
504
|
+
const tee = command.match(/tee\s+(-a\s+)?([^\s;|&]+)/);
|
|
505
|
+
if (tee?.[2])
|
|
506
|
+
return tee[2];
|
|
507
|
+
return "";
|
|
508
|
+
}
|
|
509
|
+
function evaluateContent(policy, context) {
|
|
510
|
+
const evaluation = context.evaluation;
|
|
511
|
+
if (evaluation.type !== "content-write") {
|
|
512
|
+
return { allowed: true, matchedRules: [], action: "allow", failClosed: false };
|
|
513
|
+
}
|
|
514
|
+
const { content, file_path } = evaluation;
|
|
515
|
+
const matchedRules = [];
|
|
516
|
+
const scopeResult = evaluateScope(policy, context, file_path, "write");
|
|
517
|
+
if (scopeResult.matchedRules.length > 0) {
|
|
518
|
+
matchedRules.push(...scopeResult.matchedRules);
|
|
519
|
+
}
|
|
520
|
+
for (const rule of policy.rules) {
|
|
521
|
+
if (rule.category !== "content" && rule.category !== "import" && rule.category !== "test-integrity")
|
|
522
|
+
continue;
|
|
523
|
+
if (rule.applies_to === "test-files" && !isTestFile(file_path))
|
|
524
|
+
continue;
|
|
525
|
+
if (!matchRule(rule, content))
|
|
526
|
+
continue;
|
|
527
|
+
if (matchRuleUnless(rule, content, context.taskId))
|
|
528
|
+
continue;
|
|
529
|
+
matchedRules.push({
|
|
530
|
+
id: rule.id,
|
|
531
|
+
category: rule.category,
|
|
532
|
+
...rule.description !== undefined ? { description: rule.description } : {},
|
|
533
|
+
reason: rule.description || `Matched rule ${rule.id}`
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
const action = resolveAction(policy.mode, matchedRules);
|
|
537
|
+
return {
|
|
538
|
+
allowed: action !== "block",
|
|
539
|
+
matchedRules,
|
|
540
|
+
action,
|
|
541
|
+
failClosed: false
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
function evaluateToolCall(policy, context) {
|
|
545
|
+
const evaluation = context.evaluation;
|
|
546
|
+
if (evaluation.type !== "tool-call") {
|
|
547
|
+
return { allowed: true, matchedRules: [], action: "allow", failClosed: false };
|
|
548
|
+
}
|
|
549
|
+
const { tool_name, tool_input } = evaluation;
|
|
550
|
+
const allMatched = [];
|
|
551
|
+
const filePaths = extractFilePathsFromToolInput(tool_name, tool_input);
|
|
552
|
+
for (const fp of filePaths) {
|
|
553
|
+
const access = isWriteTool(tool_name) ? "write" : "read";
|
|
554
|
+
const scopeResult = evaluateScope(policy, context, fp, access);
|
|
555
|
+
if (scopeResult.matchedRules.length > 0) {
|
|
556
|
+
allMatched.push(...scopeResult.matchedRules);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
const content = extractContentFromToolInput(tool_input);
|
|
560
|
+
if (content) {
|
|
561
|
+
const filePath = filePaths[0] || "";
|
|
562
|
+
const contentContext = {
|
|
563
|
+
...context,
|
|
564
|
+
evaluation: { type: "content-write", file_path: filePath, content }
|
|
565
|
+
};
|
|
566
|
+
const contentPolicy = loadPolicy(context.projectRoot);
|
|
567
|
+
for (const rule of contentPolicy.rules) {
|
|
568
|
+
if (rule.category !== "content" && rule.category !== "import" && rule.category !== "test-integrity")
|
|
569
|
+
continue;
|
|
570
|
+
if (rule.applies_to === "test-files" && !isTestFile(filePath))
|
|
571
|
+
continue;
|
|
572
|
+
if (!matchRule(rule, content))
|
|
573
|
+
continue;
|
|
574
|
+
if (matchRuleUnless(rule, content, context.taskId))
|
|
575
|
+
continue;
|
|
576
|
+
allMatched.push({
|
|
577
|
+
id: rule.id,
|
|
578
|
+
category: rule.category,
|
|
579
|
+
...rule.description !== undefined ? { description: rule.description } : {},
|
|
580
|
+
reason: rule.description || `Matched rule ${rule.id}`
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (tool_name === "Bash") {
|
|
585
|
+
const command = String(tool_input.command || tool_input.cmd || "");
|
|
586
|
+
if (command) {
|
|
587
|
+
const cmdContext = {
|
|
588
|
+
...context,
|
|
589
|
+
evaluation: { type: "command", command }
|
|
590
|
+
};
|
|
591
|
+
const cmdResult = evaluateCommand(policy, cmdContext);
|
|
592
|
+
if (cmdResult.matchedRules.length > 0) {
|
|
593
|
+
allMatched.push(...cmdResult.matchedRules);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const seen = new Set;
|
|
598
|
+
const deduplicated = [];
|
|
599
|
+
for (const rule of allMatched) {
|
|
600
|
+
if (!seen.has(rule.id)) {
|
|
601
|
+
seen.add(rule.id);
|
|
602
|
+
deduplicated.push(rule);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
const action = resolveAction(policy.mode, deduplicated);
|
|
606
|
+
return {
|
|
607
|
+
allowed: action !== "block",
|
|
608
|
+
matchedRules: deduplicated,
|
|
609
|
+
action,
|
|
610
|
+
failClosed: false
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
function isWriteTool(toolName) {
|
|
614
|
+
return toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit";
|
|
615
|
+
}
|
|
616
|
+
function extractFilePathsFromToolInput(toolName, input) {
|
|
617
|
+
const paths = [];
|
|
618
|
+
const add = (value) => {
|
|
619
|
+
if (typeof value === "string" && value.trim()) {
|
|
620
|
+
paths.push(value.trim());
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
if (toolName === "Read" || toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit") {
|
|
624
|
+
add(input.file_path);
|
|
625
|
+
add(input.path);
|
|
626
|
+
} else if (toolName === "Glob") {
|
|
627
|
+
add(input.path);
|
|
628
|
+
} else if (toolName === "Grep") {
|
|
629
|
+
add(input.path);
|
|
630
|
+
} else {
|
|
631
|
+
add(input.file_path);
|
|
632
|
+
add(input.path);
|
|
633
|
+
}
|
|
634
|
+
return paths;
|
|
635
|
+
}
|
|
636
|
+
function extractContentFromToolInput(input) {
|
|
637
|
+
if (typeof input.content === "string")
|
|
638
|
+
return input.content;
|
|
639
|
+
if (typeof input.new_string === "string")
|
|
640
|
+
return input.new_string;
|
|
641
|
+
return "";
|
|
642
|
+
}
|
|
643
|
+
var guardHotPathPrimed = false;
|
|
644
|
+
function primeGuardHotPaths() {
|
|
645
|
+
if (guardHotPathPrimed) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
guardHotPathPrimed = true;
|
|
649
|
+
try {
|
|
650
|
+
optimizeNextInvocation(matchRule);
|
|
651
|
+
optimizeNextInvocation(evaluate);
|
|
652
|
+
} catch {}
|
|
653
|
+
}
|
|
654
|
+
primeGuardHotPaths();
|
|
655
|
+
|
|
656
|
+
// packages/cli-surface-plugin/src/control-plane/agent-binary-build.ts
|
|
657
|
+
import { runtimeProvisioningEnv } from "@rig/core/runtime-provisioning-env";
|
|
658
|
+
|
|
659
|
+
// packages/cli-surface-plugin/src/runner.ts
|
|
30
660
|
class CliError extends RuntimeCliError {
|
|
31
|
-
hint;
|
|
32
661
|
constructor(message, exitCode = 1, options = {}) {
|
|
33
|
-
super(message, exitCode);
|
|
34
|
-
if (options.hint?.trim()) {
|
|
35
|
-
this.hint = options.hint.trim();
|
|
36
|
-
}
|
|
662
|
+
super(message, exitCode, options);
|
|
37
663
|
}
|
|
38
664
|
}
|
|
39
665
|
function takeFlag(args, flag) {
|
|
@@ -76,8 +702,8 @@ Usage: ${usage}`);
|
|
|
76
702
|
}
|
|
77
703
|
|
|
78
704
|
// packages/cli-surface-plugin/src/kernel-dispatch.ts
|
|
705
|
+
import { getProcessKernel } from "@rig/kernel-seed/boot-default";
|
|
79
706
|
async function dispatchThroughKernel(input, options) {
|
|
80
|
-
const { getProcessKernel } = await import("@rig/kernel/boot-default");
|
|
81
707
|
const kernel = getProcessKernel();
|
|
82
708
|
if (!kernel) {
|
|
83
709
|
throw new Error("Kernel not booted: cannot dispatch a run through the transport capability.");
|
|
@@ -88,10 +714,13 @@ async function dispatchThroughKernel(input, options) {
|
|
|
88
714
|
|
|
89
715
|
// packages/cli-surface-plugin/src/commands/_cli-format.ts
|
|
90
716
|
import pc from "picocolors";
|
|
91
|
-
import { runStatusColorRole, runStatusText, statusColorRole } from "@rig/run-worker/runs";
|
|
92
717
|
var dim = pc.dim;
|
|
93
718
|
var faintBar = pc.dim("\u2502");
|
|
94
719
|
var accent = pc.cyan;
|
|
720
|
+
function numberField(record, key) {
|
|
721
|
+
const value = record[key];
|
|
722
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
723
|
+
}
|
|
95
724
|
function truncate(value, width) {
|
|
96
725
|
return value.length <= width ? value : `${value.slice(0, Math.max(0, width - 1))}\u2026`;
|
|
97
726
|
}
|
|
@@ -113,6 +742,52 @@ function colorForRole(role) {
|
|
|
113
742
|
return pc.dim;
|
|
114
743
|
}
|
|
115
744
|
}
|
|
745
|
+
function normalizeStatus(status) {
|
|
746
|
+
const normalized = String(status ?? "").trim().toLowerCase().replace(/[\s_]+/g, "-");
|
|
747
|
+
return normalized === "waiting-input" ? "waiting-user-input" : normalized;
|
|
748
|
+
}
|
|
749
|
+
function statusColorRole(status) {
|
|
750
|
+
switch (normalizeStatus(status)) {
|
|
751
|
+
case "done":
|
|
752
|
+
case "completed":
|
|
753
|
+
case "ready":
|
|
754
|
+
case "healthy":
|
|
755
|
+
case "approved":
|
|
756
|
+
case "merged":
|
|
757
|
+
return "success";
|
|
758
|
+
case "needs-attention":
|
|
759
|
+
case "waiting-approval":
|
|
760
|
+
case "waiting-user-input":
|
|
761
|
+
case "blocked":
|
|
762
|
+
case "paused":
|
|
763
|
+
return "action-yellow";
|
|
764
|
+
case "running":
|
|
765
|
+
case "adopted":
|
|
766
|
+
case "preparing":
|
|
767
|
+
case "created":
|
|
768
|
+
case "queued":
|
|
769
|
+
case "starting":
|
|
770
|
+
case "pending":
|
|
771
|
+
case "in-progress":
|
|
772
|
+
case "active":
|
|
773
|
+
case "booting":
|
|
774
|
+
case "validating":
|
|
775
|
+
case "reviewing":
|
|
776
|
+
case "closing-out":
|
|
777
|
+
return "active-cyan";
|
|
778
|
+
case "failed":
|
|
779
|
+
case "error":
|
|
780
|
+
case "rejected":
|
|
781
|
+
return "failure";
|
|
782
|
+
case "stopped":
|
|
783
|
+
case "cancelled":
|
|
784
|
+
case "canceled":
|
|
785
|
+
case "stale":
|
|
786
|
+
return "muted";
|
|
787
|
+
default:
|
|
788
|
+
return "neutral";
|
|
789
|
+
}
|
|
790
|
+
}
|
|
116
791
|
function compactDate(value) {
|
|
117
792
|
const parsed = Date.parse(value);
|
|
118
793
|
if (!Number.isFinite(parsed))
|
|
@@ -137,10 +812,20 @@ function runTitleOf(run) {
|
|
|
137
812
|
return firstString(run, ["title", "summary", "name"], taskIdOf(run) || "(untitled)");
|
|
138
813
|
}
|
|
139
814
|
function runLikeStatusText(run) {
|
|
140
|
-
|
|
815
|
+
const record = run;
|
|
816
|
+
const normalized = normalizeStatus(record.status);
|
|
817
|
+
return normalized || (record.live === true && record.stale !== true ? "starting" : "unknown");
|
|
141
818
|
}
|
|
142
819
|
function runLikeStatusColor(run) {
|
|
143
|
-
|
|
820
|
+
const record = run;
|
|
821
|
+
const pendingApprovals = numberField(record, "pendingApprovals") ?? 0;
|
|
822
|
+
const pendingInputs = numberField(record, "pendingInputs") ?? 0;
|
|
823
|
+
const stallCount = numberField(record, "stallCount") ?? 0;
|
|
824
|
+
const status = runLikeStatusText(run);
|
|
825
|
+
if (status !== "completed" && status !== "failed" && status !== "stopped" && (status === "needs-attention" || pendingApprovals + pendingInputs > 0 || stallCount > 0)) {
|
|
826
|
+
return colorForRole("action-yellow");
|
|
827
|
+
}
|
|
828
|
+
return colorForRole(statusColorRole(status));
|
|
144
829
|
}
|
|
145
830
|
function printFormattedOutput(message) {
|
|
146
831
|
console.log(message);
|
|
@@ -241,7 +926,10 @@ function formatRunSummaryLine(run) {
|
|
|
241
926
|
}
|
|
242
927
|
|
|
243
928
|
// packages/cli-surface-plugin/src/commands/run.ts
|
|
244
|
-
var
|
|
929
|
+
var RunReadModelCap = defineCapability(RUN_READ_MODEL);
|
|
930
|
+
var TaskIoCap = defineCapability(TASK_IO_SERVICE_CAPABILITY);
|
|
931
|
+
var TaskSelectionCap = defineCapability(TASK_SELECTION);
|
|
932
|
+
var NOT_READY_TASK_STATUSES = { closed: true, done: true, completed: true, merged: true, blocked: true, "in-progress": true, running: true };
|
|
245
933
|
var RUN_COMMANDS = new Set([
|
|
246
934
|
"list",
|
|
247
935
|
"status",
|
|
@@ -280,6 +968,31 @@ function parsePromptOverride(prompt, initialPrompt) {
|
|
|
280
968
|
}
|
|
281
969
|
return promptText || initialPromptText || null;
|
|
282
970
|
}
|
|
971
|
+
function normalizeTaskId(value) {
|
|
972
|
+
const trimmed = value?.trim();
|
|
973
|
+
if (!trimmed)
|
|
974
|
+
return;
|
|
975
|
+
return trimmed.startsWith("#") && /^#\d+$/.test(trimmed) ? trimmed.slice(1) : trimmed;
|
|
976
|
+
}
|
|
977
|
+
function readTaskTitle(task) {
|
|
978
|
+
if (!task)
|
|
979
|
+
return null;
|
|
980
|
+
for (const key of ["title", "summary", "name"]) {
|
|
981
|
+
const value = task[key];
|
|
982
|
+
if (typeof value === "string" && value.trim())
|
|
983
|
+
return value.trim();
|
|
984
|
+
}
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
async function loadTaskIo(projectRoot) {
|
|
988
|
+
return requireCapabilityForRoot(projectRoot, TaskIoCap, "No task-sources plugin provides task IO for this project root.");
|
|
989
|
+
}
|
|
990
|
+
async function loadRunReadModel(projectRoot) {
|
|
991
|
+
return requireCapabilityForRoot(projectRoot, RunReadModelCap, "No run-worker plugin provides run read model for this project root.");
|
|
992
|
+
}
|
|
993
|
+
async function loadTaskSelection(projectRoot) {
|
|
994
|
+
return requireCapabilityForRoot(projectRoot, TaskSelectionCap, "No dependency-graph plugin provides task selection for this project root.");
|
|
995
|
+
}
|
|
283
996
|
function parsePositiveLimit(value) {
|
|
284
997
|
if (value === undefined)
|
|
285
998
|
return;
|
|
@@ -325,12 +1038,78 @@ function requireRunId(value, usage) {
|
|
|
325
1038
|
}
|
|
326
1039
|
return value.trim();
|
|
327
1040
|
}
|
|
1041
|
+
function taskStatus(task) {
|
|
1042
|
+
const value = task.status;
|
|
1043
|
+
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
1044
|
+
}
|
|
1045
|
+
function isReadyTask(task) {
|
|
1046
|
+
const status = taskStatus(task);
|
|
1047
|
+
return status.length === 0 || NOT_READY_TASK_STATUSES[status] !== true;
|
|
1048
|
+
}
|
|
1049
|
+
function selectNextReadyTaskLocal(tasks) {
|
|
1050
|
+
return tasks.find(isReadyTask) ?? null;
|
|
1051
|
+
}
|
|
1052
|
+
async function selectNextReadyTaskForRun(projectRoot, deps) {
|
|
1053
|
+
if (deps.listTasks)
|
|
1054
|
+
return selectNextReadyTaskLocal(await deps.listTasks(projectRoot));
|
|
1055
|
+
const selection = await (await loadTaskSelection(projectRoot)).selectNextReadyTask({ projectRoot });
|
|
1056
|
+
return selection.selected;
|
|
1057
|
+
}
|
|
1058
|
+
async function readyTasksForRun(projectRoot, deps) {
|
|
1059
|
+
if (deps.listTasks)
|
|
1060
|
+
return (await deps.listTasks(projectRoot)).filter(isReadyTask);
|
|
1061
|
+
const selection = await (await loadTaskSelection(projectRoot)).selectNextReadyTask({ projectRoot });
|
|
1062
|
+
return selection.ready;
|
|
1063
|
+
}
|
|
1064
|
+
async function resolveStartTaskForRun(projectRoot, taskRef, next, deps) {
|
|
1065
|
+
if (next) {
|
|
1066
|
+
const task2 = await selectNextReadyTaskForRun(projectRoot, deps);
|
|
1067
|
+
const taskId2 = normalizeTaskId(task2?.id);
|
|
1068
|
+
if (!task2 || !taskId2)
|
|
1069
|
+
throw new Error("No ready task found.");
|
|
1070
|
+
return { taskId: taskId2, task: task2 };
|
|
1071
|
+
}
|
|
1072
|
+
const taskId = normalizeTaskId(taskRef);
|
|
1073
|
+
if (!taskId)
|
|
1074
|
+
throw new Error("Missing task id.");
|
|
1075
|
+
const task = deps.listTasks ? (await deps.listTasks(projectRoot).catch(() => [])).find((candidate) => normalizeTaskId(candidate.id) === taskId) ?? null : await (await loadTaskIo(projectRoot)).getTask(projectRoot, taskId).catch(() => null);
|
|
1076
|
+
return { taskId, task };
|
|
1077
|
+
}
|
|
1078
|
+
async function activeRunByTaskId(listRunsForRoot, readModel, projectRoot) {
|
|
1079
|
+
const active = new Map;
|
|
1080
|
+
for (const run of await listRunsForRoot(projectRoot)) {
|
|
1081
|
+
const classification = readModel.classifyRun(run);
|
|
1082
|
+
if (!run.taskId || !run.live || run.stale || classification.isTerminal || classification.status === "needs-attention" || classification.status === "needs_attention")
|
|
1083
|
+
continue;
|
|
1084
|
+
active.set(run.taskId, run);
|
|
1085
|
+
}
|
|
1086
|
+
return active;
|
|
1087
|
+
}
|
|
1088
|
+
async function assertNoActiveRunForTask(listRunsForRoot, readModel, projectRoot, taskId) {
|
|
1089
|
+
const active = await activeRunByTaskId(listRunsForRoot, readModel, projectRoot);
|
|
1090
|
+
if (active.has(taskId))
|
|
1091
|
+
throw new CliError(`Task ${taskId} already has an active run.`, 2, { hint: "Use --force to dispatch another run anyway." });
|
|
1092
|
+
}
|
|
1093
|
+
async function deliverRunControl(projectRoot, run, control, deps) {
|
|
1094
|
+
if (deps.deliverControl)
|
|
1095
|
+
return deps.deliverControl(projectRoot, run.runId, control);
|
|
1096
|
+
const result = await (await loadRunReadModel(projectRoot)).deliverControl({ projectRoot, runId: run.runId, control });
|
|
1097
|
+
if (!result.delivered)
|
|
1098
|
+
throw new CliError(result.detail ?? `Run ${run.runId} did not accept ${control.kind}.`, 2);
|
|
1099
|
+
}
|
|
1100
|
+
async function removeRegistryRoom(projectRoot, runId) {
|
|
1101
|
+
const result = await (await loadRunReadModel(projectRoot)).removeRunRegistryEntry({ projectRoot, runId });
|
|
1102
|
+
return result.removed;
|
|
1103
|
+
}
|
|
328
1104
|
async function executeRun(context, args, deps = {}) {
|
|
329
|
-
const listRunRecords = deps.listRuns ?? ((root) => listRuns(root));
|
|
330
|
-
const getRunRecord = deps.getRun ?? ((root, id) => getRun(root, id));
|
|
331
|
-
const joinTarget = deps.resolveJoin ?? ((root, id) =>
|
|
1105
|
+
const listRunRecords = deps.listRuns ?? (async (root) => (await loadRunReadModel(root)).listRuns({ projectRoot: root }));
|
|
1106
|
+
const getRunRecord = deps.getRun ?? (async (root, id) => (await loadRunReadModel(root)).getRun({ projectRoot: root, selector: { id, kind: "any" } }));
|
|
1107
|
+
const joinTarget = deps.resolveJoin ?? (async (root, id) => {
|
|
1108
|
+
const target = await (await loadRunReadModel(root)).resolveControlTarget({ projectRoot: root, runId: id, purpose: "attach" });
|
|
1109
|
+
return target?.joinLink ? { joinLink: target.joinLink, stale: target.stale } : null;
|
|
1110
|
+
});
|
|
332
1111
|
const dispatch = deps.dispatch ?? ((input) => dispatchThroughKernel(input));
|
|
333
|
-
const
|
|
1112
|
+
const runReadModel = () => loadRunReadModel(context.projectRoot);
|
|
334
1113
|
const [command = "status", ...rest] = normalizeRunArgs(args);
|
|
335
1114
|
const text = context.outputMode === "text";
|
|
336
1115
|
switch (command) {
|
|
@@ -344,8 +1123,9 @@ async function executeRun(context, args, deps = {}) {
|
|
|
344
1123
|
case "status": {
|
|
345
1124
|
requireNoExtraArgs(rest, "rig run status");
|
|
346
1125
|
const runs = await listRunRecords(context.projectRoot);
|
|
347
|
-
const
|
|
348
|
-
const
|
|
1126
|
+
const readModel = await runReadModel();
|
|
1127
|
+
const activeRuns = runs.filter((run) => !readModel.classifyRun(run).isTerminal).map(toRunLike);
|
|
1128
|
+
const recentRuns = runs.filter((run) => readModel.classifyRun(run).isTerminal).map(toRunLike);
|
|
349
1129
|
if (text)
|
|
350
1130
|
printFormattedOutput(formatRunStatus({ activeRuns, recentRuns, runs: [...activeRuns, ...recentRuns] }));
|
|
351
1131
|
return { ok: true, group: "run", command, details: { activeRuns, recentRuns } };
|
|
@@ -368,14 +1148,14 @@ async function executeRun(context, args, deps = {}) {
|
|
|
368
1148
|
pending = initialPromptResult.rest;
|
|
369
1149
|
const taskRef = pending[0]?.startsWith("-") ? undefined : pending[0];
|
|
370
1150
|
requireNoExtraArgs(taskRef ? pending.slice(1) : pending, "rig run [start] [#<issue>|<task-id>|--next|--task <id>] [--title <t>] [--model <m>] [--prompt <p>|--initial-prompt <p>] [--force]");
|
|
371
|
-
const { taskId, task } = await
|
|
1151
|
+
const { taskId, task } = await resolveStartTaskForRun(context.projectRoot, taskResult.value ?? taskRef, nextResult.value, deps);
|
|
372
1152
|
const title = titleResult.value ?? readTaskTitle(task);
|
|
373
1153
|
const model = modelResult.value?.trim() || null;
|
|
374
1154
|
const prompt = parsePromptOverride(promptResult.value, initialPromptResult.value);
|
|
375
1155
|
if (context.dryRun)
|
|
376
1156
|
return { ok: true, group: "run", command, details: { taskId, title: title ?? null, model, prompt, dryRun: true } };
|
|
377
1157
|
if (!forceResult.value)
|
|
378
|
-
await assertNoActiveRunForTask(listRunRecords, context.projectRoot, taskId);
|
|
1158
|
+
await assertNoActiveRunForTask(listRunRecords, await runReadModel(), context.projectRoot, taskId);
|
|
379
1159
|
const submitted = await dispatch({ projectRoot: context.projectRoot, taskId, title, force: forceResult.value, model, prompt });
|
|
380
1160
|
printText(context, `${formatSubmittedRun({ runId: submitted.runId, taskId, title })}
|
|
381
1161
|
|
|
@@ -387,10 +1167,10 @@ Next: rig run attach ${submitted.runId}`);
|
|
|
387
1167
|
const limitResult = takeOption(forceResult.rest, "--limit");
|
|
388
1168
|
requireNoExtraArgs(limitResult.rest, "rig run start-parallel [--limit <n>] [--force]");
|
|
389
1169
|
const limit = parsePositiveLimit(limitResult.value);
|
|
390
|
-
let readyTasks =
|
|
1170
|
+
let readyTasks = [...await readyTasksForRun(context.projectRoot, deps)];
|
|
391
1171
|
let skipped = 0;
|
|
392
1172
|
if (!forceResult.value) {
|
|
393
|
-
const active = await activeRunByTaskId(listRunRecords, context.projectRoot);
|
|
1173
|
+
const active = await activeRunByTaskId(listRunRecords, await runReadModel(), context.projectRoot);
|
|
394
1174
|
const before = readyTasks.length;
|
|
395
1175
|
readyTasks = readyTasks.filter((task) => {
|
|
396
1176
|
const id = normalizeTaskId(task.id);
|
|
@@ -429,7 +1209,7 @@ Next: rig run attach ${submitted.runId}`);
|
|
|
429
1209
|
case "start-serial": {
|
|
430
1210
|
const force = takeFlag(rest, "--force");
|
|
431
1211
|
requireNoExtraArgs(force.rest, "rig run start-serial [--force]");
|
|
432
|
-
const task =
|
|
1212
|
+
const task = await selectNextReadyTaskForRun(context.projectRoot, deps);
|
|
433
1213
|
const taskId = normalizeTaskId(task?.id);
|
|
434
1214
|
if (!task || !taskId)
|
|
435
1215
|
throw new CliError("No ready task found.", 1, { hint: "Run `rig task list` to inspect available tasks." });
|
|
@@ -437,7 +1217,7 @@ Next: rig run attach ${submitted.runId}`);
|
|
|
437
1217
|
if (context.dryRun)
|
|
438
1218
|
return { ok: true, group: "run", command, details: { taskId, title: title ?? null, dryRun: true, serialStepOnly: true } };
|
|
439
1219
|
if (!force.value)
|
|
440
|
-
await assertNoActiveRunForTask(listRunRecords, context.projectRoot, taskId);
|
|
1220
|
+
await assertNoActiveRunForTask(listRunRecords, await runReadModel(), context.projectRoot, taskId);
|
|
441
1221
|
const submitted = await dispatch({ projectRoot: context.projectRoot, taskId, title });
|
|
442
1222
|
printText(context, `${formatSubmittedRun({ runId: submitted.runId, taskId, title })}
|
|
443
1223
|
|
|
@@ -469,7 +1249,7 @@ Next: rig run attach ${submitted.runId}`);
|
|
|
469
1249
|
const record = await getRunRecord(context.projectRoot, runId);
|
|
470
1250
|
if (!record)
|
|
471
1251
|
throw new CliError(`Run not found: ${runId}`, 2, { hint: "Run `rig run list` to see runs." });
|
|
472
|
-
const entries =
|
|
1252
|
+
const entries = ((await (await runReadModel()).getRunDetails({ projectRoot: context.projectRoot, selector: { id: record.runId, kind: "run-id" } }))?.sessionEntries ?? []).filter((entry) => typeof entry.customType === "string" && entry.customType.startsWith("rig.run."));
|
|
473
1253
|
if (text) {
|
|
474
1254
|
printFormattedOutput(formatRunCard(toRunLike(record)));
|
|
475
1255
|
if (entries.length === 0) {
|
|
@@ -527,7 +1307,7 @@ Next: rig run attach ${submitted.runId}`);
|
|
|
527
1307
|
const record = await getRunRecord(context.projectRoot, runId);
|
|
528
1308
|
if (!record)
|
|
529
1309
|
throw new CliError(`Run not found: ${runId}`, 2, { hint: "Run `rig run list` to see runs." });
|
|
530
|
-
await deliverRunControl(context.projectRoot, record, { kind: "steer", message: messageText },
|
|
1310
|
+
await deliverRunControl(context.projectRoot, record, { kind: "steer", message: messageText }, deps);
|
|
531
1311
|
if (text)
|
|
532
1312
|
printFormattedOutput(`Steered run ${record.runId}.`);
|
|
533
1313
|
return { ok: true, group: "run", command, details: { runId: record.runId, steered: true } };
|
|
@@ -541,7 +1321,7 @@ Next: rig run attach ${submitted.runId}`);
|
|
|
541
1321
|
const record = await getRunRecord(context.projectRoot, runId);
|
|
542
1322
|
if (!record)
|
|
543
1323
|
throw new CliError(`Run not found: ${runId}`, 2, { hint: "Run `rig run list` to see runs." });
|
|
544
|
-
await deliverRunControl(context.projectRoot, record, { kind: "stop", reason: reasonOpt.value ?? "operator requested stop" },
|
|
1324
|
+
await deliverRunControl(context.projectRoot, record, { kind: "stop", reason: reasonOpt.value ?? "operator requested stop" }, deps);
|
|
545
1325
|
if (text)
|
|
546
1326
|
printFormattedOutput(`Requested stop for run ${record.runId}.`);
|
|
547
1327
|
return { ok: true, group: "run", command, details: { runId: record.runId, stopRequested: true } };
|
|
@@ -558,7 +1338,7 @@ Next: rig run attach ${submitted.runId}`);
|
|
|
558
1338
|
throw new CliError(`Run ${runId} is not live; only a running run can be paused.`, 2, { hint: `Re-dispatch it instead: rig run restart ${runId}` });
|
|
559
1339
|
if (context.dryRun)
|
|
560
1340
|
return { ok: true, group: "run", command, details: { runId: record.runId, dryRun: true } };
|
|
561
|
-
await deliverRunControl(context.projectRoot, record, { kind: "pause" },
|
|
1341
|
+
await deliverRunControl(context.projectRoot, record, { kind: "pause" }, deps);
|
|
562
1342
|
if (text)
|
|
563
1343
|
printFormattedOutput(`Requested pause for run ${record.runId}. Resume it with \`rig run resume ${record.runId}\`.`);
|
|
564
1344
|
return { ok: true, group: "run", command, details: { runId: record.runId, pauseRequested: true } };
|
|
@@ -574,7 +1354,7 @@ Next: rig run attach ${submitted.runId}`);
|
|
|
574
1354
|
if (record.live && !record.stale) {
|
|
575
1355
|
if (context.dryRun)
|
|
576
1356
|
return { ok: true, group: "run", command, details: { runId: record.runId, dryRun: true } };
|
|
577
|
-
await deliverRunControl(context.projectRoot, record, { kind: "resume" },
|
|
1357
|
+
await deliverRunControl(context.projectRoot, record, { kind: "resume" }, deps);
|
|
578
1358
|
if (text)
|
|
579
1359
|
printFormattedOutput(`Requested resume for run ${record.runId}.`);
|
|
580
1360
|
return { ok: true, group: "run", command, details: { runId: record.runId, resumeRequested: true } };
|