@h-rig/cli-surface-plugin 0.0.6-alpha.157 → 0.0.6-alpha.159

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/src/app/drone-ui.d.ts +0 -11
  2. package/dist/src/app/drone-ui.js +0 -114
  3. package/dist/src/commands/_async-ui.d.ts +1 -1
  4. package/dist/src/commands/_cli-format.d.ts +0 -29
  5. package/dist/src/commands/_cli-format.js +59 -113
  6. package/dist/src/commands/_connection-state.d.ts +6 -33
  7. package/dist/src/commands/_connection-state.js +654 -138
  8. package/dist/src/commands/_doctor-checks.d.ts +2 -5
  9. package/dist/src/commands/_doctor-checks.js +10 -9
  10. package/dist/src/commands/_help-catalog.d.ts +2 -1
  11. package/dist/src/commands/_help-catalog.js +654 -7
  12. package/dist/src/commands/_inprocess-services.d.ts +5 -5
  13. package/dist/src/commands/_inprocess-services.js +1 -1
  14. package/dist/src/commands/_parsers.js +651 -12
  15. package/dist/src/commands/_paths.d.ts +0 -2
  16. package/dist/src/commands/_paths.js +2 -10
  17. package/dist/src/commands/_pi-install.d.ts +2 -12
  18. package/dist/src/commands/_pi-install.js +3 -36
  19. package/dist/src/commands/_policy.js +659 -20
  20. package/dist/src/commands/agent.d.ts +1 -1
  21. package/dist/src/commands/agent.js +675 -24
  22. package/dist/src/commands/config.d.ts +1 -1
  23. package/dist/src/commands/config.js +656 -21
  24. package/dist/src/commands/dist.d.ts +1 -1
  25. package/dist/src/commands/dist.js +828 -102
  26. package/dist/src/commands/doctor.d.ts +1 -1
  27. package/dist/src/commands/doctor.js +658 -12
  28. package/dist/src/commands/github.d.ts +1 -1
  29. package/dist/src/commands/github.js +658 -19
  30. package/dist/src/commands/inbox.d.ts +12 -8
  31. package/dist/src/commands/inbox.js +741 -22
  32. package/dist/src/commands/init.d.ts +17 -19
  33. package/dist/src/commands/init.js +836 -306
  34. package/dist/src/commands/inspect.d.ts +5 -6
  35. package/dist/src/commands/inspect.js +754 -42
  36. package/dist/src/commands/pi.d.ts +1 -1
  37. package/dist/src/commands/pi.js +655 -16
  38. package/dist/src/commands/plugin.d.ts +9 -9
  39. package/dist/src/commands/plugin.js +652 -13
  40. package/dist/src/commands/profile-and-review.d.ts +1 -1
  41. package/dist/src/commands/profile-and-review.js +655 -16
  42. package/dist/src/commands/queue.d.ts +1 -1
  43. package/dist/src/commands/queue.js +871 -12
  44. package/dist/src/commands/remote-client.d.ts +152 -0
  45. package/dist/src/commands/remote-client.js +475 -0
  46. package/dist/src/commands/remote.d.ts +1 -1
  47. package/dist/src/commands/remote.js +1100 -29
  48. package/dist/src/commands/repo-git-harness.d.ts +1 -1
  49. package/dist/src/commands/repo-git-harness.js +2321 -47
  50. package/dist/src/commands/run.d.ts +10 -6
  51. package/dist/src/commands/run.js +830 -50
  52. package/dist/src/commands/server.d.ts +1 -1
  53. package/dist/src/commands/server.js +649 -11
  54. package/dist/src/commands/setup.d.ts +2 -2
  55. package/dist/src/commands/setup.js +829 -18
  56. package/dist/src/commands/stats.d.ts +2 -4
  57. package/dist/src/commands/stats.js +1299 -20
  58. package/dist/src/commands/test.d.ts +1 -1
  59. package/dist/src/commands/test.js +648 -9
  60. package/dist/src/commands/triage.d.ts +2 -3
  61. package/dist/src/commands/triage.js +657 -11
  62. package/dist/src/commands/workspace.d.ts +1 -1
  63. package/dist/src/commands/workspace.js +1280 -15
  64. package/dist/src/control-plane/agent-binary-build.d.ts +9 -0
  65. package/dist/src/control-plane/agent-binary-build.js +88 -0
  66. package/dist/src/control-plane/embedded-native-assets.d.ts +7 -0
  67. package/dist/src/control-plane/embedded-native-assets.js +6 -0
  68. package/dist/src/control-plane/guard.d.ts +17 -0
  69. package/dist/src/control-plane/guard.js +684 -0
  70. package/dist/src/control-plane/harness-cli.d.ts +12 -0
  71. package/dist/src/control-plane/harness-cli.js +1623 -0
  72. package/dist/src/control-plane/native/git-ops.d.ts +67 -0
  73. package/dist/src/control-plane/native/git-ops.js +1381 -0
  74. package/dist/src/control-plane/native/github-auth-env.d.ts +1 -0
  75. package/dist/src/control-plane/native/github-auth-env.js +21 -0
  76. package/dist/src/control-plane/native/host-git.d.ts +4 -0
  77. package/dist/src/control-plane/native/host-git.js +51 -0
  78. package/dist/src/control-plane/priority-queue.d.ts +22 -0
  79. package/dist/src/control-plane/priority-queue.js +212 -0
  80. package/dist/src/control-plane/rigfig.d.ts +9 -0
  81. package/dist/src/control-plane/rigfig.js +70 -0
  82. package/dist/src/control-plane/scope.d.ts +3 -0
  83. package/dist/src/control-plane/scope.js +58 -0
  84. package/dist/src/control-plane/setup-status.d.ts +44 -0
  85. package/dist/src/control-plane/setup-status.js +164 -0
  86. package/dist/src/control-plane/task-data.d.ts +2 -0
  87. package/dist/src/control-plane/task-data.js +12 -0
  88. package/dist/src/control-plane/workspace-ops.d.ts +79 -0
  89. package/dist/src/control-plane/workspace-ops.js +639 -0
  90. package/dist/src/help-catalog-data.d.ts +7 -0
  91. package/dist/src/help-catalog-data.js +660 -0
  92. package/dist/src/kernel-dispatch.js +1 -3
  93. package/dist/src/plugin.js +10072 -30
  94. package/dist/src/runner.d.ts +7 -9
  95. package/dist/src/runner.js +750 -30
  96. package/package.json +12 -13
  97. package/dist/src/commands/_json-output.d.ts +0 -11
  98. package/dist/src/commands/_json-output.js +0 -54
  99. package/dist/src/commands/_pi-frontend.d.ts +0 -35
  100. package/dist/src/commands/_pi-frontend.js +0 -64
  101. package/dist/src/commands/_run-driver-helpers.d.ts +0 -26
  102. package/dist/src/commands/_run-driver-helpers.js +0 -132
  103. package/dist/src/commands/task-run-driver.d.ts +0 -93
  104. package/dist/src/commands/task-run-driver.js +0 -136
  105. package/dist/src/commands/task.d.ts +0 -46
  106. package/dist/src/commands/task.js +0 -555
  107. package/dist/src/provider-model.d.ts +0 -34
  108. package/dist/src/provider-model.js +0 -56
  109. package/dist/src/rig-config-package-deps.d.ts +0 -10
  110. package/dist/src/rig-config-package-deps.js +0 -272
  111. package/dist/src/version.d.ts +0 -8
  112. package/dist/src/version.js +0 -47
@@ -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
- activeRunByTaskId,
15
- assertNoActiveRunForTask,
16
- deliverRemoteRunControl,
17
- deliverRunControl,
18
- getRun,
19
- listRuns,
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/control-plane/runtime/events";
26
- import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
27
- import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
28
- import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
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
- return runStatusText(run);
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
- return colorForRole(runStatusColorRole(run));
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 TERMINAL = new Set(TERMINAL_RUN_STATUSES);
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) => resolveJoinTarget(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 listTaskRecords = deps.listTasks ?? ((root) => listTasks(root));
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 activeRuns = runs.filter((run) => !TERMINAL.has(run.status)).map(toRunLike);
348
- const recentRuns = runs.filter((run) => TERMINAL.has(run.status)).map(toRunLike);
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 resolveStartTask({ projectRoot: context.projectRoot, taskId: taskResult.value ?? taskRef, next: nextResult.value, listTasks: listTaskRecords });
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 = (await listTaskRecords(context.projectRoot)).filter(isReadyTask);
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 = selectNextReadyTask(await listTaskRecords(context.projectRoot));
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 = readSessionRunEntries(record.sessionPath).filter((entry) => typeof entry.customType === "string" && entry.customType.startsWith("rig.run."));
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 }, { deliverRemote: deps.deliverRemote ?? deliverRemoteRunControl });
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" }, { deliverRemote: deps.deliverRemote ?? deliverRemoteRunControl });
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" }, { deliverRemote: deps.deliverRemote ?? deliverRemoteRunControl });
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" }, { deliverRemote: deps.deliverRemote ?? deliverRemoteRunControl });
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 } };