@h-rig/cli-surface-plugin 0.0.6-alpha.156 → 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
|
@@ -1,22 +1,661 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/cli-surface-plugin/src/commands/inbox.ts
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { RUN_READ_MODEL } from "@rig/contracts";
|
|
4
|
+
import { defineCapability } from "@rig/core/capability";
|
|
5
|
+
import { requireCapabilityForRoot } from "@rig/core/capability-loaders";
|
|
6
6
|
|
|
7
7
|
// packages/cli-surface-plugin/src/runner.ts
|
|
8
|
-
import { EventBus } from "@rig/runtime
|
|
9
|
-
import { CliError as RuntimeCliError } from "@rig/
|
|
10
|
-
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
11
|
-
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
8
|
+
import { EventBus } from "@rig/core/runtime-events";
|
|
9
|
+
import { CliError as RuntimeCliError } from "@rig/contracts";
|
|
12
10
|
|
|
11
|
+
// packages/cli-surface-plugin/src/control-plane/guard.ts
|
|
12
|
+
import { optimizeNextInvocation } from "bun:jsc";
|
|
13
|
+
import { existsSync, readFileSync, statSync } from "fs";
|
|
14
|
+
import { resolve } from "path";
|
|
15
|
+
|
|
16
|
+
// packages/cli-surface-plugin/src/control-plane/scope.ts
|
|
17
|
+
import { getScopeRules } from "@rig/core/scope-rules";
|
|
18
|
+
var scopeRegexCache = new Map;
|
|
19
|
+
function unique(values) {
|
|
20
|
+
return [...new Set(values)];
|
|
21
|
+
}
|
|
22
|
+
function normalizeRelativeScopePath(inputPath) {
|
|
23
|
+
let normalized = inputPath.replace(/^\.\//, "");
|
|
24
|
+
const rules = getScopeRules();
|
|
25
|
+
if (rules?.stripPrefixes) {
|
|
26
|
+
for (const prefix of rules.stripPrefixes) {
|
|
27
|
+
if (normalized.startsWith(prefix)) {
|
|
28
|
+
normalized = normalized.slice(prefix.length);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return normalized;
|
|
33
|
+
}
|
|
34
|
+
function normalizePathToScope(projectRoot, monorepoRoot, inputPath) {
|
|
35
|
+
let normalized = inputPath.replace(/^\.\//, "");
|
|
36
|
+
if (normalized.startsWith(projectRoot + "/")) {
|
|
37
|
+
normalized = normalized.slice(projectRoot.length + 1);
|
|
38
|
+
}
|
|
39
|
+
if (normalized.startsWith(monorepoRoot + "/")) {
|
|
40
|
+
normalized = normalized.slice(monorepoRoot.length + 1);
|
|
41
|
+
}
|
|
42
|
+
return normalizeRelativeScopePath(normalized);
|
|
43
|
+
}
|
|
44
|
+
function scopeGlobToRegex(glob) {
|
|
45
|
+
const cached = scopeRegexCache.get(glob);
|
|
46
|
+
if (cached) {
|
|
47
|
+
return cached;
|
|
48
|
+
}
|
|
49
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "__GLOBSTAR__").replace(/\*/g, "[^/]*").replace(/__GLOBSTAR__/g, ".*");
|
|
50
|
+
const compiled = new RegExp(`^${escaped}$`);
|
|
51
|
+
scopeRegexCache.set(glob, compiled);
|
|
52
|
+
return compiled;
|
|
53
|
+
}
|
|
54
|
+
function scopeMatches(path, scopes) {
|
|
55
|
+
const pathVariants = unique([path, normalizeRelativeScopePath(path)]);
|
|
56
|
+
for (const scope of scopes) {
|
|
57
|
+
const scopeVariants = unique([scope, normalizeRelativeScopePath(scope)]);
|
|
58
|
+
for (const candidatePath of pathVariants) {
|
|
59
|
+
for (const candidateScope of scopeVariants) {
|
|
60
|
+
if (candidatePath === candidateScope || scopeGlobToRegex(candidateScope).test(candidatePath)) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// packages/cli-surface-plugin/src/control-plane/guard.ts
|
|
70
|
+
import {
|
|
71
|
+
POLICY_VERSION
|
|
72
|
+
} from "@rig/contracts";
|
|
73
|
+
var DEFAULT_SCOPE = {
|
|
74
|
+
fail_closed: true,
|
|
75
|
+
harness_paths_exempt: true,
|
|
76
|
+
runtime_paths_exempt: true
|
|
77
|
+
};
|
|
78
|
+
var DEFAULT_SANDBOX = {
|
|
79
|
+
mode: "enforce",
|
|
80
|
+
network: true,
|
|
81
|
+
read_deny: [],
|
|
82
|
+
write_allow_from_runtime: true
|
|
83
|
+
};
|
|
84
|
+
var DEFAULT_ISOLATION = {
|
|
85
|
+
default_mode: "worktree",
|
|
86
|
+
repo_symlink_fallback: false,
|
|
87
|
+
strict_provisioning: true,
|
|
88
|
+
fail_closed_on_provision_error: true
|
|
89
|
+
};
|
|
90
|
+
var DEFAULT_COMPLETION = {
|
|
91
|
+
derive_checks_from_scope: true,
|
|
92
|
+
checks: [],
|
|
93
|
+
typescript_config_probe: ["tsconfig.json"],
|
|
94
|
+
eslint_config_probe: [".eslintrc.js", ".eslintrc.json", "eslint.config.js"]
|
|
95
|
+
};
|
|
96
|
+
var DEFAULT_RUNTIME_IMAGE = {
|
|
97
|
+
deps: {
|
|
98
|
+
monorepo_install: false,
|
|
99
|
+
hp_next_install: false
|
|
100
|
+
},
|
|
101
|
+
plugins_require_binaries: true
|
|
102
|
+
};
|
|
103
|
+
var DEFAULT_RUNTIME_SNAPSHOT = {
|
|
104
|
+
enabled: true
|
|
105
|
+
};
|
|
106
|
+
function defaultPolicy() {
|
|
107
|
+
return {
|
|
108
|
+
version: POLICY_VERSION,
|
|
109
|
+
mode: "enforce",
|
|
110
|
+
scope: { ...DEFAULT_SCOPE },
|
|
111
|
+
rules: [],
|
|
112
|
+
sandbox: { ...DEFAULT_SANDBOX },
|
|
113
|
+
isolation: { ...DEFAULT_ISOLATION },
|
|
114
|
+
completion: { ...DEFAULT_COMPLETION },
|
|
115
|
+
runtime_image: {
|
|
116
|
+
deps: { ...DEFAULT_RUNTIME_IMAGE.deps },
|
|
117
|
+
plugins_require_binaries: DEFAULT_RUNTIME_IMAGE.plugins_require_binaries
|
|
118
|
+
},
|
|
119
|
+
runtime_snapshot: { ...DEFAULT_RUNTIME_SNAPSHOT }
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
var policyCache = null;
|
|
123
|
+
var policyCachePath = null;
|
|
124
|
+
var seededPolicyConfig = null;
|
|
125
|
+
var compiledRegexCache = new Map;
|
|
126
|
+
function loadPolicy(projectRoot) {
|
|
127
|
+
if (seededPolicyConfig) {
|
|
128
|
+
return seededPolicyConfig;
|
|
129
|
+
}
|
|
130
|
+
const configPath = resolve(projectRoot, "rig/policy/policy.json");
|
|
131
|
+
if (!existsSync(configPath)) {
|
|
132
|
+
return defaultPolicy();
|
|
133
|
+
}
|
|
134
|
+
let mtimeMs;
|
|
135
|
+
try {
|
|
136
|
+
mtimeMs = statSync(configPath).mtimeMs;
|
|
137
|
+
} catch {
|
|
138
|
+
return defaultPolicy();
|
|
139
|
+
}
|
|
140
|
+
if (policyCache && policyCachePath === configPath && policyCache.mtimeMs === mtimeMs) {
|
|
141
|
+
return policyCache.config;
|
|
142
|
+
}
|
|
143
|
+
let parsed;
|
|
144
|
+
try {
|
|
145
|
+
parsed = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
146
|
+
} catch {
|
|
147
|
+
return defaultPolicy();
|
|
148
|
+
}
|
|
149
|
+
const config = mergeWithDefaults(parsed);
|
|
150
|
+
policyCache = { mtimeMs, config };
|
|
151
|
+
policyCachePath = configPath;
|
|
152
|
+
return config;
|
|
153
|
+
}
|
|
154
|
+
function mergeWithDefaults(parsed) {
|
|
155
|
+
const base = defaultPolicy();
|
|
156
|
+
if (typeof parsed.mode === "string" && isValidMode(parsed.mode)) {
|
|
157
|
+
base.mode = parsed.mode;
|
|
158
|
+
}
|
|
159
|
+
if (parsed.scope && typeof parsed.scope === "object" && !Array.isArray(parsed.scope)) {
|
|
160
|
+
const s = parsed.scope;
|
|
161
|
+
if (typeof s.fail_closed === "boolean")
|
|
162
|
+
base.scope.fail_closed = s.fail_closed;
|
|
163
|
+
if (typeof s.harness_paths_exempt === "boolean")
|
|
164
|
+
base.scope.harness_paths_exempt = s.harness_paths_exempt;
|
|
165
|
+
if (typeof s.runtime_paths_exempt === "boolean")
|
|
166
|
+
base.scope.runtime_paths_exempt = s.runtime_paths_exempt;
|
|
167
|
+
}
|
|
168
|
+
if (Array.isArray(parsed.rules)) {
|
|
169
|
+
base.rules = precompilePolicyRuleRegexes(parsed.rules.filter(isValidRule));
|
|
170
|
+
}
|
|
171
|
+
if (Array.isArray(parsed.deny) && base.rules.length === 0) {
|
|
172
|
+
base.rules = precompilePolicyRuleRegexes(migrateLegacyDeny(parsed.deny));
|
|
173
|
+
}
|
|
174
|
+
if (parsed.sandbox && typeof parsed.sandbox === "object" && !Array.isArray(parsed.sandbox)) {
|
|
175
|
+
const sb = parsed.sandbox;
|
|
176
|
+
if (typeof sb.mode === "string" && isValidMode(sb.mode))
|
|
177
|
+
base.sandbox.mode = sb.mode;
|
|
178
|
+
if (typeof sb.network === "boolean")
|
|
179
|
+
base.sandbox.network = sb.network;
|
|
180
|
+
if (Array.isArray(sb.read_deny))
|
|
181
|
+
base.sandbox.read_deny = sb.read_deny.filter((v) => typeof v === "string");
|
|
182
|
+
if (typeof sb.write_allow_from_runtime === "boolean")
|
|
183
|
+
base.sandbox.write_allow_from_runtime = sb.write_allow_from_runtime;
|
|
184
|
+
}
|
|
185
|
+
if (parsed.isolation && typeof parsed.isolation === "object" && !Array.isArray(parsed.isolation)) {
|
|
186
|
+
const iso = parsed.isolation;
|
|
187
|
+
if (iso.default_mode === "worktree")
|
|
188
|
+
base.isolation.default_mode = iso.default_mode;
|
|
189
|
+
if (typeof iso.repo_symlink_fallback === "boolean")
|
|
190
|
+
base.isolation.repo_symlink_fallback = iso.repo_symlink_fallback;
|
|
191
|
+
if (typeof iso.strict_provisioning === "boolean")
|
|
192
|
+
base.isolation.strict_provisioning = iso.strict_provisioning;
|
|
193
|
+
if (typeof iso.fail_closed_on_provision_error === "boolean")
|
|
194
|
+
base.isolation.fail_closed_on_provision_error = iso.fail_closed_on_provision_error;
|
|
195
|
+
}
|
|
196
|
+
if (parsed.completion && typeof parsed.completion === "object" && !Array.isArray(parsed.completion)) {
|
|
197
|
+
const comp = parsed.completion;
|
|
198
|
+
if (typeof comp.derive_checks_from_scope === "boolean")
|
|
199
|
+
base.completion.derive_checks_from_scope = comp.derive_checks_from_scope;
|
|
200
|
+
if (Array.isArray(comp.checks))
|
|
201
|
+
base.completion.checks = comp.checks.filter((v) => typeof v === "string");
|
|
202
|
+
if (Array.isArray(comp.typescript_config_probe))
|
|
203
|
+
base.completion.typescript_config_probe = comp.typescript_config_probe.filter((v) => typeof v === "string");
|
|
204
|
+
if (Array.isArray(comp.eslint_config_probe))
|
|
205
|
+
base.completion.eslint_config_probe = comp.eslint_config_probe.filter((v) => typeof v === "string");
|
|
206
|
+
}
|
|
207
|
+
if (parsed.runtime_image && typeof parsed.runtime_image === "object" && !Array.isArray(parsed.runtime_image)) {
|
|
208
|
+
const runtimeImage = parsed.runtime_image;
|
|
209
|
+
if (runtimeImage.deps && typeof runtimeImage.deps === "object" && !Array.isArray(runtimeImage.deps)) {
|
|
210
|
+
const deps = runtimeImage.deps;
|
|
211
|
+
if (typeof deps.monorepo_install === "boolean") {
|
|
212
|
+
base.runtime_image.deps.monorepo_install = deps.monorepo_install;
|
|
213
|
+
}
|
|
214
|
+
if (typeof deps.hp_next_install === "boolean") {
|
|
215
|
+
base.runtime_image.deps.hp_next_install = deps.hp_next_install;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (typeof runtimeImage.plugins_require_binaries === "boolean") {
|
|
219
|
+
base.runtime_image.plugins_require_binaries = runtimeImage.plugins_require_binaries;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (parsed.runtime_snapshot && typeof parsed.runtime_snapshot === "object" && !Array.isArray(parsed.runtime_snapshot)) {
|
|
223
|
+
const runtimeSnapshot = parsed.runtime_snapshot;
|
|
224
|
+
if (typeof runtimeSnapshot.enabled === "boolean") {
|
|
225
|
+
base.runtime_snapshot.enabled = runtimeSnapshot.enabled;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return base;
|
|
229
|
+
}
|
|
230
|
+
function isValidMode(value) {
|
|
231
|
+
return value === "off" || value === "observe" || value === "enforce";
|
|
232
|
+
}
|
|
233
|
+
function isValidRule(value) {
|
|
234
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
235
|
+
return false;
|
|
236
|
+
const r = value;
|
|
237
|
+
return typeof r.id === "string" && typeof r.category === "string" && r.match != null && typeof r.match === "object";
|
|
238
|
+
}
|
|
239
|
+
function migrateLegacyDeny(deny) {
|
|
240
|
+
const rules = [];
|
|
241
|
+
for (const entry of deny) {
|
|
242
|
+
if (typeof entry.id !== "string")
|
|
243
|
+
continue;
|
|
244
|
+
const match = {};
|
|
245
|
+
if (typeof entry.pattern === "string")
|
|
246
|
+
match.pattern = entry.pattern;
|
|
247
|
+
if (typeof entry.regex === "string")
|
|
248
|
+
match.regex = entry.regex;
|
|
249
|
+
if (!match.pattern && !match.regex)
|
|
250
|
+
continue;
|
|
251
|
+
rules.push({
|
|
252
|
+
id: entry.id,
|
|
253
|
+
category: "command",
|
|
254
|
+
match,
|
|
255
|
+
action: "block",
|
|
256
|
+
...typeof entry.description === "string" ? { description: entry.description } : {}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return rules;
|
|
260
|
+
}
|
|
261
|
+
function precompilePolicyRuleRegexes(rules) {
|
|
262
|
+
return rules.map((rule) => {
|
|
263
|
+
const compiledRegex = rule.match.regex ? compileSafeRegex(rule.match.regex, `rules.${rule.id}.match.regex`, true) : undefined;
|
|
264
|
+
const compiledUnlessRegex = rule.unless?.regex ? compileSafeRegex(rule.unless.regex, `rules.${rule.id}.unless.regex`, true) : undefined;
|
|
265
|
+
return {
|
|
266
|
+
...rule,
|
|
267
|
+
...compiledRegex ? { compiledRegex } : {},
|
|
268
|
+
...compiledUnlessRegex ? { compiledUnlessRegex } : {}
|
|
269
|
+
};
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
function getRegexUnsafeReason(pattern) {
|
|
273
|
+
if (pattern.length > 512) {
|
|
274
|
+
return "pattern exceeds max safe length (512 chars)";
|
|
275
|
+
}
|
|
276
|
+
if (/\\[1-9]/.test(pattern)) {
|
|
277
|
+
return "pattern uses backreferences";
|
|
278
|
+
}
|
|
279
|
+
if (/\((?:[^()\\]|\\.)*[+*](?:[^()\\]|\\.)*\)\s*[*+{]/.test(pattern)) {
|
|
280
|
+
return "pattern contains nested quantifiers";
|
|
281
|
+
}
|
|
282
|
+
if (/\((?:[^()\\]|\\.)*\.\\?[+*](?:[^()\\]|\\.)*\)\s*[*+{]/.test(pattern)) {
|
|
283
|
+
return "pattern contains nested broad quantifiers";
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
function compileSafeRegex(pattern, sourceLabel, logOnFailure) {
|
|
288
|
+
const cached = compiledRegexCache.get(pattern);
|
|
289
|
+
if (cached !== undefined) {
|
|
290
|
+
return cached ?? undefined;
|
|
291
|
+
}
|
|
292
|
+
const unsafeReason = getRegexUnsafeReason(pattern);
|
|
293
|
+
if (unsafeReason) {
|
|
294
|
+
if (logOnFailure) {
|
|
295
|
+
console.warn(`[policy] Skipping unsafe regex in ${sourceLabel}: ${unsafeReason}`);
|
|
296
|
+
}
|
|
297
|
+
compiledRegexCache.set(pattern, null);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
const compiled = new RegExp(pattern);
|
|
302
|
+
compiledRegexCache.set(pattern, compiled);
|
|
303
|
+
return compiled;
|
|
304
|
+
} catch (error) {
|
|
305
|
+
if (logOnFailure) {
|
|
306
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
307
|
+
console.warn(`[policy] Skipping invalid regex in ${sourceLabel}: ${message}`);
|
|
308
|
+
}
|
|
309
|
+
compiledRegexCache.set(pattern, null);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function matchRule(rule, input) {
|
|
314
|
+
const { match } = rule;
|
|
315
|
+
if (match.pattern && input.includes(match.pattern)) {
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
if (match.regex) {
|
|
319
|
+
const compiled = rule.compiledRegex || compileSafeRegex(match.regex, `rules.${rule.id}.match.regex`, false);
|
|
320
|
+
if (!compiled) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
return compiled.test(input);
|
|
325
|
+
} catch {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
function matchRuleUnless(rule, command, taskId) {
|
|
332
|
+
if (!rule.unless)
|
|
333
|
+
return false;
|
|
334
|
+
if (rule.unless.regex) {
|
|
335
|
+
const compiled = rule.compiledUnlessRegex || compileSafeRegex(rule.unless.regex, `rules.${rule.id}.unless.regex`, false);
|
|
336
|
+
if (!compiled) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
if (compiled.test(command))
|
|
341
|
+
return true;
|
|
342
|
+
} catch {}
|
|
343
|
+
}
|
|
344
|
+
if (rule.unless.task_in && taskId) {
|
|
345
|
+
if (rule.unless.task_in.includes(taskId))
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
function resolveAction(mode, matched) {
|
|
351
|
+
if (matched.length === 0)
|
|
352
|
+
return "allow";
|
|
353
|
+
if (mode === "off")
|
|
354
|
+
return "allow";
|
|
355
|
+
if (mode === "observe")
|
|
356
|
+
return "warn";
|
|
357
|
+
return "block";
|
|
358
|
+
}
|
|
359
|
+
function resolveAbsolutePath(projectRoot, rawPath) {
|
|
360
|
+
if (rawPath.startsWith("/"))
|
|
361
|
+
return resolve(rawPath);
|
|
362
|
+
return resolve(projectRoot, rawPath);
|
|
363
|
+
}
|
|
364
|
+
function isHarnessPath(projectRoot, rawPath) {
|
|
365
|
+
const absPath = resolveAbsolutePath(projectRoot, rawPath);
|
|
366
|
+
const managedRoots = [
|
|
367
|
+
resolve(projectRoot, "rig"),
|
|
368
|
+
resolve(projectRoot, ".rig"),
|
|
369
|
+
resolve(projectRoot, "artifacts")
|
|
370
|
+
];
|
|
371
|
+
return managedRoots.some((root) => absPath === root || absPath.startsWith(root + "/"));
|
|
372
|
+
}
|
|
373
|
+
function isRuntimePath(projectRoot, rawPath, taskWorkspace) {
|
|
374
|
+
const absPath = resolveAbsolutePath(projectRoot, rawPath);
|
|
375
|
+
if (taskWorkspace) {
|
|
376
|
+
const workspaceRigRoot = resolve(taskWorkspace, ".rig");
|
|
377
|
+
const workspaceArtifactsRoot = resolve(taskWorkspace, "artifacts");
|
|
378
|
+
if (absPath === workspaceRigRoot || absPath.startsWith(workspaceRigRoot + "/") || absPath === workspaceArtifactsRoot || absPath.startsWith(workspaceArtifactsRoot + "/")) {
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const runtimeRoot = resolve(projectRoot, ".rig/runtime/agents");
|
|
383
|
+
return absPath === runtimeRoot || absPath.startsWith(runtimeRoot + "/");
|
|
384
|
+
}
|
|
385
|
+
function isTestFile(path) {
|
|
386
|
+
return /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(path) || /\/(__tests__|tests|test)\//.test(path);
|
|
387
|
+
}
|
|
388
|
+
function evaluate(context) {
|
|
389
|
+
const policy = loadPolicy(context.projectRoot);
|
|
390
|
+
switch (context.evaluation.type) {
|
|
391
|
+
case "tool-call":
|
|
392
|
+
return evaluateToolCall(policy, context);
|
|
393
|
+
case "command":
|
|
394
|
+
return evaluateCommand(policy, context);
|
|
395
|
+
case "content-write":
|
|
396
|
+
return evaluateContent(policy, context);
|
|
397
|
+
case "file-access":
|
|
398
|
+
return evaluateScope(policy, context, context.evaluation.file_path, context.evaluation.access);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
function evaluateScope(policy, context, filePath, access) {
|
|
402
|
+
const allowed = () => ({
|
|
403
|
+
allowed: true,
|
|
404
|
+
matchedRules: [],
|
|
405
|
+
action: "allow",
|
|
406
|
+
failClosed: false
|
|
407
|
+
});
|
|
408
|
+
if (policy.scope.harness_paths_exempt && isHarnessPath(context.projectRoot, filePath)) {
|
|
409
|
+
return allowed();
|
|
410
|
+
}
|
|
411
|
+
if (policy.scope.runtime_paths_exempt && isRuntimePath(context.projectRoot, filePath, context.taskWorkspace)) {
|
|
412
|
+
return allowed();
|
|
413
|
+
}
|
|
414
|
+
if (!context.taskId) {
|
|
415
|
+
if (access === "write" && policy.scope.fail_closed) {
|
|
416
|
+
return {
|
|
417
|
+
allowed: false,
|
|
418
|
+
matchedRules: [],
|
|
419
|
+
action: resolveAction(policy.mode, [{ id: "scope:no-task", category: "command", reason: "No active task; fail-closed for write operations" }]),
|
|
420
|
+
failClosed: true
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
return allowed();
|
|
424
|
+
}
|
|
425
|
+
const scopes = context.taskScopes || [];
|
|
426
|
+
if (scopes.length === 0) {
|
|
427
|
+
return allowed();
|
|
428
|
+
}
|
|
429
|
+
if (context.taskWorkspace && context.taskWorkspace !== context.projectRoot && filePath.startsWith("/")) {
|
|
430
|
+
const absPath = resolve(filePath);
|
|
431
|
+
if (!absPath.startsWith(context.taskWorkspace + "/") && !isHarnessPath(context.projectRoot, filePath)) {
|
|
432
|
+
const reason2 = `Absolute path '${filePath}' is outside task runtime boundary. Allowed root: ${context.taskWorkspace}`;
|
|
433
|
+
const matched2 = [{ id: "scope:workspace-boundary", category: "command", reason: reason2 }];
|
|
434
|
+
return {
|
|
435
|
+
allowed: policy.mode !== "enforce",
|
|
436
|
+
matchedRules: matched2,
|
|
437
|
+
action: resolveAction(policy.mode, matched2),
|
|
438
|
+
failClosed: false
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
const monorepoRoot = context.monorepoRoot || process.env.MONOREPO_ROOT?.trim() || context.taskWorkspace || context.projectRoot;
|
|
443
|
+
let normalizedPath = filePath;
|
|
444
|
+
if (context.taskWorkspace && context.taskWorkspace !== context.projectRoot && filePath.startsWith(context.taskWorkspace + "/")) {
|
|
445
|
+
normalizedPath = filePath.slice(context.taskWorkspace.length + 1);
|
|
446
|
+
}
|
|
447
|
+
normalizedPath = normalizePathToScope(context.projectRoot, monorepoRoot, normalizedPath);
|
|
448
|
+
if (scopeMatches(filePath, scopes) || scopeMatches(normalizedPath, scopes)) {
|
|
449
|
+
return allowed();
|
|
450
|
+
}
|
|
451
|
+
const reason = `File '${filePath}' (normalized: '${normalizedPath}') is outside scope of task ${context.taskId}`;
|
|
452
|
+
const matched = [{ id: "scope:out-of-scope", category: "command", reason }];
|
|
453
|
+
return {
|
|
454
|
+
allowed: policy.mode !== "enforce",
|
|
455
|
+
matchedRules: matched,
|
|
456
|
+
action: resolveAction(policy.mode, matched),
|
|
457
|
+
failClosed: false
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function evaluateCommand(policy, context) {
|
|
461
|
+
const evaluation = context.evaluation;
|
|
462
|
+
if (evaluation.type !== "command") {
|
|
463
|
+
return { allowed: true, matchedRules: [], action: "allow", failClosed: false };
|
|
464
|
+
}
|
|
465
|
+
const command = evaluation.command;
|
|
466
|
+
const matchedRules = [];
|
|
467
|
+
for (const rule of policy.rules) {
|
|
468
|
+
if (rule.category !== "command")
|
|
469
|
+
continue;
|
|
470
|
+
if (!matchRule(rule, command))
|
|
471
|
+
continue;
|
|
472
|
+
if (matchRuleUnless(rule, command, context.taskId))
|
|
473
|
+
continue;
|
|
474
|
+
matchedRules.push({
|
|
475
|
+
id: rule.id,
|
|
476
|
+
category: rule.category,
|
|
477
|
+
...rule.description !== undefined ? { description: rule.description } : {},
|
|
478
|
+
reason: rule.description || `Matched rule ${rule.id}`
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
const writeTarget = extractWriteTarget(command);
|
|
482
|
+
if (writeTarget && !/^\/dev\//.test(writeTarget) && !/^\/proc\//.test(writeTarget)) {
|
|
483
|
+
const scopeResult = evaluateScope(policy, context, writeTarget, "write");
|
|
484
|
+
if (!scopeResult.allowed || scopeResult.matchedRules.length > 0) {
|
|
485
|
+
matchedRules.push(...scopeResult.matchedRules);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
const action = resolveAction(policy.mode, matchedRules);
|
|
489
|
+
return {
|
|
490
|
+
allowed: action !== "block",
|
|
491
|
+
matchedRules,
|
|
492
|
+
action,
|
|
493
|
+
failClosed: false
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
function extractWriteTarget(command) {
|
|
497
|
+
const redirect = command.match(/>>?\s+([^\s;|&]+)/);
|
|
498
|
+
if (redirect?.[1])
|
|
499
|
+
return redirect[1];
|
|
500
|
+
const tee = command.match(/tee\s+(-a\s+)?([^\s;|&]+)/);
|
|
501
|
+
if (tee?.[2])
|
|
502
|
+
return tee[2];
|
|
503
|
+
return "";
|
|
504
|
+
}
|
|
505
|
+
function evaluateContent(policy, context) {
|
|
506
|
+
const evaluation = context.evaluation;
|
|
507
|
+
if (evaluation.type !== "content-write") {
|
|
508
|
+
return { allowed: true, matchedRules: [], action: "allow", failClosed: false };
|
|
509
|
+
}
|
|
510
|
+
const { content, file_path } = evaluation;
|
|
511
|
+
const matchedRules = [];
|
|
512
|
+
const scopeResult = evaluateScope(policy, context, file_path, "write");
|
|
513
|
+
if (scopeResult.matchedRules.length > 0) {
|
|
514
|
+
matchedRules.push(...scopeResult.matchedRules);
|
|
515
|
+
}
|
|
516
|
+
for (const rule of policy.rules) {
|
|
517
|
+
if (rule.category !== "content" && rule.category !== "import" && rule.category !== "test-integrity")
|
|
518
|
+
continue;
|
|
519
|
+
if (rule.applies_to === "test-files" && !isTestFile(file_path))
|
|
520
|
+
continue;
|
|
521
|
+
if (!matchRule(rule, content))
|
|
522
|
+
continue;
|
|
523
|
+
if (matchRuleUnless(rule, content, context.taskId))
|
|
524
|
+
continue;
|
|
525
|
+
matchedRules.push({
|
|
526
|
+
id: rule.id,
|
|
527
|
+
category: rule.category,
|
|
528
|
+
...rule.description !== undefined ? { description: rule.description } : {},
|
|
529
|
+
reason: rule.description || `Matched rule ${rule.id}`
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
const action = resolveAction(policy.mode, matchedRules);
|
|
533
|
+
return {
|
|
534
|
+
allowed: action !== "block",
|
|
535
|
+
matchedRules,
|
|
536
|
+
action,
|
|
537
|
+
failClosed: false
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
function evaluateToolCall(policy, context) {
|
|
541
|
+
const evaluation = context.evaluation;
|
|
542
|
+
if (evaluation.type !== "tool-call") {
|
|
543
|
+
return { allowed: true, matchedRules: [], action: "allow", failClosed: false };
|
|
544
|
+
}
|
|
545
|
+
const { tool_name, tool_input } = evaluation;
|
|
546
|
+
const allMatched = [];
|
|
547
|
+
const filePaths = extractFilePathsFromToolInput(tool_name, tool_input);
|
|
548
|
+
for (const fp of filePaths) {
|
|
549
|
+
const access = isWriteTool(tool_name) ? "write" : "read";
|
|
550
|
+
const scopeResult = evaluateScope(policy, context, fp, access);
|
|
551
|
+
if (scopeResult.matchedRules.length > 0) {
|
|
552
|
+
allMatched.push(...scopeResult.matchedRules);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
const content = extractContentFromToolInput(tool_input);
|
|
556
|
+
if (content) {
|
|
557
|
+
const filePath = filePaths[0] || "";
|
|
558
|
+
const contentContext = {
|
|
559
|
+
...context,
|
|
560
|
+
evaluation: { type: "content-write", file_path: filePath, content }
|
|
561
|
+
};
|
|
562
|
+
const contentPolicy = loadPolicy(context.projectRoot);
|
|
563
|
+
for (const rule of contentPolicy.rules) {
|
|
564
|
+
if (rule.category !== "content" && rule.category !== "import" && rule.category !== "test-integrity")
|
|
565
|
+
continue;
|
|
566
|
+
if (rule.applies_to === "test-files" && !isTestFile(filePath))
|
|
567
|
+
continue;
|
|
568
|
+
if (!matchRule(rule, content))
|
|
569
|
+
continue;
|
|
570
|
+
if (matchRuleUnless(rule, content, context.taskId))
|
|
571
|
+
continue;
|
|
572
|
+
allMatched.push({
|
|
573
|
+
id: rule.id,
|
|
574
|
+
category: rule.category,
|
|
575
|
+
...rule.description !== undefined ? { description: rule.description } : {},
|
|
576
|
+
reason: rule.description || `Matched rule ${rule.id}`
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (tool_name === "Bash") {
|
|
581
|
+
const command = String(tool_input.command || tool_input.cmd || "");
|
|
582
|
+
if (command) {
|
|
583
|
+
const cmdContext = {
|
|
584
|
+
...context,
|
|
585
|
+
evaluation: { type: "command", command }
|
|
586
|
+
};
|
|
587
|
+
const cmdResult = evaluateCommand(policy, cmdContext);
|
|
588
|
+
if (cmdResult.matchedRules.length > 0) {
|
|
589
|
+
allMatched.push(...cmdResult.matchedRules);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
const seen = new Set;
|
|
594
|
+
const deduplicated = [];
|
|
595
|
+
for (const rule of allMatched) {
|
|
596
|
+
if (!seen.has(rule.id)) {
|
|
597
|
+
seen.add(rule.id);
|
|
598
|
+
deduplicated.push(rule);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
const action = resolveAction(policy.mode, deduplicated);
|
|
602
|
+
return {
|
|
603
|
+
allowed: action !== "block",
|
|
604
|
+
matchedRules: deduplicated,
|
|
605
|
+
action,
|
|
606
|
+
failClosed: false
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
function isWriteTool(toolName) {
|
|
610
|
+
return toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit";
|
|
611
|
+
}
|
|
612
|
+
function extractFilePathsFromToolInput(toolName, input) {
|
|
613
|
+
const paths = [];
|
|
614
|
+
const add = (value) => {
|
|
615
|
+
if (typeof value === "string" && value.trim()) {
|
|
616
|
+
paths.push(value.trim());
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
if (toolName === "Read" || toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit") {
|
|
620
|
+
add(input.file_path);
|
|
621
|
+
add(input.path);
|
|
622
|
+
} else if (toolName === "Glob") {
|
|
623
|
+
add(input.path);
|
|
624
|
+
} else if (toolName === "Grep") {
|
|
625
|
+
add(input.path);
|
|
626
|
+
} else {
|
|
627
|
+
add(input.file_path);
|
|
628
|
+
add(input.path);
|
|
629
|
+
}
|
|
630
|
+
return paths;
|
|
631
|
+
}
|
|
632
|
+
function extractContentFromToolInput(input) {
|
|
633
|
+
if (typeof input.content === "string")
|
|
634
|
+
return input.content;
|
|
635
|
+
if (typeof input.new_string === "string")
|
|
636
|
+
return input.new_string;
|
|
637
|
+
return "";
|
|
638
|
+
}
|
|
639
|
+
var guardHotPathPrimed = false;
|
|
640
|
+
function primeGuardHotPaths() {
|
|
641
|
+
if (guardHotPathPrimed) {
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
guardHotPathPrimed = true;
|
|
645
|
+
try {
|
|
646
|
+
optimizeNextInvocation(matchRule);
|
|
647
|
+
optimizeNextInvocation(evaluate);
|
|
648
|
+
} catch {}
|
|
649
|
+
}
|
|
650
|
+
primeGuardHotPaths();
|
|
651
|
+
|
|
652
|
+
// packages/cli-surface-plugin/src/control-plane/agent-binary-build.ts
|
|
653
|
+
import { runtimeProvisioningEnv } from "@rig/core/runtime-provisioning-env";
|
|
654
|
+
|
|
655
|
+
// packages/cli-surface-plugin/src/runner.ts
|
|
13
656
|
class CliError extends RuntimeCliError {
|
|
14
|
-
hint;
|
|
15
657
|
constructor(message, exitCode = 1, options = {}) {
|
|
16
|
-
super(message, exitCode);
|
|
17
|
-
if (options.hint?.trim()) {
|
|
18
|
-
this.hint = options.hint.trim();
|
|
19
|
-
}
|
|
658
|
+
super(message, exitCode, options);
|
|
20
659
|
}
|
|
21
660
|
}
|
|
22
661
|
function takeOption(args, option) {
|
|
@@ -48,7 +687,6 @@ Usage: ${usage}`);
|
|
|
48
687
|
|
|
49
688
|
// packages/cli-surface-plugin/src/commands/_cli-format.ts
|
|
50
689
|
import pc from "picocolors";
|
|
51
|
-
import { runStatusColorRole, runStatusText, statusColorRole } from "@rig/run-worker/runs";
|
|
52
690
|
var dim = pc.dim;
|
|
53
691
|
var faintBar = pc.dim("\u2502");
|
|
54
692
|
var accent = pc.cyan;
|
|
@@ -67,6 +705,52 @@ function colorForRole(role) {
|
|
|
67
705
|
return pc.dim;
|
|
68
706
|
}
|
|
69
707
|
}
|
|
708
|
+
function normalizeStatus(status) {
|
|
709
|
+
const normalized = String(status ?? "").trim().toLowerCase().replace(/[\s_]+/g, "-");
|
|
710
|
+
return normalized === "waiting-input" ? "waiting-user-input" : normalized;
|
|
711
|
+
}
|
|
712
|
+
function statusColorRole(status) {
|
|
713
|
+
switch (normalizeStatus(status)) {
|
|
714
|
+
case "done":
|
|
715
|
+
case "completed":
|
|
716
|
+
case "ready":
|
|
717
|
+
case "healthy":
|
|
718
|
+
case "approved":
|
|
719
|
+
case "merged":
|
|
720
|
+
return "success";
|
|
721
|
+
case "needs-attention":
|
|
722
|
+
case "waiting-approval":
|
|
723
|
+
case "waiting-user-input":
|
|
724
|
+
case "blocked":
|
|
725
|
+
case "paused":
|
|
726
|
+
return "action-yellow";
|
|
727
|
+
case "running":
|
|
728
|
+
case "adopted":
|
|
729
|
+
case "preparing":
|
|
730
|
+
case "created":
|
|
731
|
+
case "queued":
|
|
732
|
+
case "starting":
|
|
733
|
+
case "pending":
|
|
734
|
+
case "in-progress":
|
|
735
|
+
case "active":
|
|
736
|
+
case "booting":
|
|
737
|
+
case "validating":
|
|
738
|
+
case "reviewing":
|
|
739
|
+
case "closing-out":
|
|
740
|
+
return "active-cyan";
|
|
741
|
+
case "failed":
|
|
742
|
+
case "error":
|
|
743
|
+
case "rejected":
|
|
744
|
+
return "failure";
|
|
745
|
+
case "stopped":
|
|
746
|
+
case "cancelled":
|
|
747
|
+
case "canceled":
|
|
748
|
+
case "stale":
|
|
749
|
+
return "muted";
|
|
750
|
+
default:
|
|
751
|
+
return "neutral";
|
|
752
|
+
}
|
|
753
|
+
}
|
|
70
754
|
function statusColor(status) {
|
|
71
755
|
return colorForRole(statusColorRole(status));
|
|
72
756
|
}
|
|
@@ -125,15 +809,36 @@ function formatInboxList(kind, entries) {
|
|
|
125
809
|
}
|
|
126
810
|
|
|
127
811
|
// packages/cli-surface-plugin/src/commands/inbox.ts
|
|
812
|
+
var RunReadModelCap = defineCapability(RUN_READ_MODEL);
|
|
813
|
+
async function loadRunReadModel(projectRoot) {
|
|
814
|
+
return requireCapabilityForRoot(projectRoot, RunReadModelCap, "No run-worker plugin provides run read model for this project root.");
|
|
815
|
+
}
|
|
816
|
+
function readModelKind(kind) {
|
|
817
|
+
return kind === "approvals" ? "approval" : "input";
|
|
818
|
+
}
|
|
128
819
|
async function listInboxRecords(context, kind, filters = {}, deps = {}) {
|
|
129
|
-
|
|
130
|
-
|
|
820
|
+
if (deps.listInbox)
|
|
821
|
+
return deps.listInbox(context.projectRoot, kind, filters);
|
|
822
|
+
const readModel = await loadRunReadModel(context.projectRoot);
|
|
823
|
+
return readModel.listInboxRecords({
|
|
824
|
+
projectRoot: context.projectRoot,
|
|
825
|
+
kind: readModelKind(kind),
|
|
826
|
+
...filters.run ? { runId: filters.run } : {},
|
|
827
|
+
...filters.task ? { taskId: filters.task } : {}
|
|
828
|
+
});
|
|
131
829
|
}
|
|
132
830
|
async function listInbox(context, kind, filters = {}, deps = {}) {
|
|
133
831
|
return listInboxRecords(context, kind, filters, deps);
|
|
134
832
|
}
|
|
135
|
-
async function readPendingInboxCounts(context) {
|
|
136
|
-
|
|
833
|
+
async function readPendingInboxCounts(context, deps = {}) {
|
|
834
|
+
if (deps.getInboxCounts)
|
|
835
|
+
return deps.getInboxCounts(context.projectRoot);
|
|
836
|
+
try {
|
|
837
|
+
const counts = await (await loadRunReadModel(context.projectRoot)).getInboxCounts({ projectRoot: context.projectRoot, kind: "all" });
|
|
838
|
+
return { approvals: counts.approvals, inputs: counts.inputs };
|
|
839
|
+
} catch {
|
|
840
|
+
return null;
|
|
841
|
+
}
|
|
137
842
|
}
|
|
138
843
|
function resolutionAttachError(runId, verb, error) {
|
|
139
844
|
const target = runId ?? "<run-id>";
|
|
@@ -158,13 +863,26 @@ function renderInboxSnapshot(snapshot) {
|
|
|
158
863
|
if (snapshot.approvals.length === 0 && snapshot.inputs.length === 0)
|
|
159
864
|
console.log("Nothing pending.");
|
|
160
865
|
}
|
|
866
|
+
function sleep(ms) {
|
|
867
|
+
const { promise, resolve: resolve2 } = Promise.withResolvers();
|
|
868
|
+
setTimeout(resolve2, ms);
|
|
869
|
+
return promise;
|
|
870
|
+
}
|
|
161
871
|
async function watchInbox(context, filters, deps) {
|
|
162
872
|
const snapshot = await readInboxSnapshot(context, filters, deps);
|
|
163
873
|
if (context.outputMode !== "text")
|
|
164
874
|
return { ok: true, group: "inbox", command: "watch", details: snapshot };
|
|
165
875
|
renderInboxSnapshot(snapshot);
|
|
166
|
-
|
|
167
|
-
|
|
876
|
+
while (true) {
|
|
877
|
+
await sleep(500);
|
|
878
|
+
renderInboxSnapshot(await readInboxSnapshot(context, filters, deps));
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
async function deliverInboxResolution(context, runId, resolution, deps) {
|
|
882
|
+
const result = deps.resolveInboxRequest ? await deps.resolveInboxRequest(context.projectRoot, runId, resolution) : await (await loadRunReadModel(context.projectRoot)).resolveInboxRequest({ projectRoot: context.projectRoot, runId, resolution });
|
|
883
|
+
if (!result.delivered)
|
|
884
|
+
throw new Error(result.detail ?? `Inbox request ${resolution.requestId} was not delivered.`);
|
|
885
|
+
return result;
|
|
168
886
|
}
|
|
169
887
|
async function executeInbox(context, args, deps = {}) {
|
|
170
888
|
const [command = "approvals", ...rest] = args;
|
|
@@ -192,14 +910,15 @@ async function executeInbox(context, args, deps = {}) {
|
|
|
192
910
|
const rawDecision = (decision.value ?? "approve").trim().toLowerCase();
|
|
193
911
|
if (rawDecision !== "approve" && rawDecision !== "approved" && rawDecision !== "reject" && rawDecision !== "rejected")
|
|
194
912
|
throw new CliError("--decision must be approve or reject.", 2);
|
|
913
|
+
const normalizedDecision = rawDecision === "approved" ? "approve" : rawDecision === "rejected" ? "reject" : rawDecision;
|
|
195
914
|
try {
|
|
196
|
-
await
|
|
915
|
+
await deliverInboxResolution(context, run.value, { kind: "approval", requestId: request.value, decision: normalizedDecision }, deps);
|
|
197
916
|
} catch (error) {
|
|
198
917
|
resolutionAttachError(run.value, "approve", error);
|
|
199
918
|
}
|
|
200
919
|
if (text)
|
|
201
920
|
printFormattedOutput(`Resolved approval ${request.value} for run ${run.value}.`);
|
|
202
|
-
return { ok: true, group: "inbox", command, details: { runId: run.value, requestId: request.value, decision:
|
|
921
|
+
return { ok: true, group: "inbox", command, details: { runId: run.value, requestId: request.value, decision: normalizedDecision } };
|
|
203
922
|
}
|
|
204
923
|
case "respond":
|
|
205
924
|
case "answer": {
|
|
@@ -214,7 +933,7 @@ async function executeInbox(context, args, deps = {}) {
|
|
|
214
933
|
if (!run.value || !request.value || !answer)
|
|
215
934
|
resolutionAttachError(run.value, "respond", new Error(`rig inbox ${command} requires --run, --request, and --text/--answer.`));
|
|
216
935
|
try {
|
|
217
|
-
await
|
|
936
|
+
await deliverInboxResolution(context, run.value, { kind: "input", requestId: request.value, answers: { answer } }, deps);
|
|
218
937
|
} catch (error) {
|
|
219
938
|
resolutionAttachError(run.value, "respond", error);
|
|
220
939
|
}
|