@h-rig/cli 0.0.6-alpha.1 → 0.0.6-alpha.100

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 (120) hide show
  1. package/README.md +18 -19
  2. package/dist/bin/build-rig-binaries.d.ts +2 -0
  3. package/dist/bin/build-rig-binaries.js +22 -10
  4. package/dist/bin/rig.d.ts +72 -0
  5. package/dist/bin/rig.js +15742 -7203
  6. package/dist/config/rig-default-config.yml +5 -0
  7. package/dist/src/app/drone-ui.d.ts +34 -0
  8. package/dist/src/app/drone-ui.js +278 -0
  9. package/dist/src/commands/_async-ui.d.ts +10 -0
  10. package/dist/src/commands/_async-ui.js +121 -0
  11. package/dist/src/commands/_cli-format.d.ts +56 -0
  12. package/dist/src/commands/_cli-format.js +319 -0
  13. package/dist/src/commands/_config-env.d.ts +18 -0
  14. package/dist/src/commands/_config-env.js +22 -0
  15. package/dist/src/commands/_connection-state.d.ts +54 -0
  16. package/dist/src/commands/_connection-state.js +75 -11
  17. package/dist/src/commands/_doctor-checks.d.ts +46 -0
  18. package/dist/src/commands/_doctor-checks.js +95 -244
  19. package/dist/src/commands/_help-catalog.d.ts +29 -0
  20. package/dist/src/commands/_help-catalog.js +146 -0
  21. package/dist/src/commands/_inprocess-services.d.ts +33 -0
  22. package/dist/src/commands/_inprocess-services.js +102 -0
  23. package/dist/src/commands/_json-output.d.ts +11 -0
  24. package/dist/src/commands/_json-output.js +60 -0
  25. package/dist/src/commands/_lazy-reconcile.d.ts +34 -0
  26. package/dist/src/commands/_lazy-reconcile.js +102 -0
  27. package/dist/src/commands/_parsers.d.ts +15 -0
  28. package/dist/src/commands/_parsers.js +18 -11
  29. package/dist/src/commands/_paths.d.ts +11 -0
  30. package/dist/src/commands/_paths.js +1 -1
  31. package/dist/src/commands/_pi-frontend.d.ts +35 -0
  32. package/dist/src/commands/_pi-frontend.js +64 -0
  33. package/dist/src/commands/_pi-install.d.ts +42 -0
  34. package/dist/src/commands/_pi-install.js +19 -36
  35. package/dist/src/commands/_policy.d.ts +8 -0
  36. package/dist/src/commands/_policy.js +67 -19
  37. package/dist/src/commands/_probes.d.ts +1 -0
  38. package/dist/src/commands/_run-bridge.d.ts +114 -0
  39. package/dist/src/commands/_run-bridge.js +387 -0
  40. package/dist/src/commands/_run-diagnostics.d.ts +9 -0
  41. package/dist/src/commands/_run-diagnostics.js +51 -0
  42. package/dist/src/commands/_run-driver-helpers.d.ts +26 -0
  43. package/dist/src/commands/_run-driver-helpers.js +79 -230
  44. package/dist/src/commands/_run-projection.d.ts +50 -0
  45. package/dist/src/commands/_run-projection.js +349 -0
  46. package/dist/src/commands/_run-subcommands.d.ts +3 -0
  47. package/dist/src/commands/_run-subcommands.js +31 -0
  48. package/dist/src/commands/_spinner.d.ts +25 -0
  49. package/dist/src/commands/_spinner.js +65 -0
  50. package/dist/src/commands/agent.d.ts +3 -0
  51. package/dist/src/commands/agent.js +8690 -235
  52. package/dist/src/commands/config.d.ts +3 -0
  53. package/dist/src/commands/config.js +181 -0
  54. package/dist/src/commands/dist.d.ts +28 -0
  55. package/dist/src/commands/dist.js +44 -29
  56. package/dist/src/commands/doctor.d.ts +3 -0
  57. package/dist/src/commands/doctor.js +214 -245
  58. package/dist/src/commands/github.d.ts +3 -0
  59. package/dist/src/commands/github.js +237 -171
  60. package/dist/src/commands/inbox.d.ts +28 -0
  61. package/dist/src/commands/inbox.js +530 -111
  62. package/dist/src/commands/init.d.ts +64 -0
  63. package/dist/src/commands/init.js +941 -654
  64. package/dist/src/commands/inspect.d.ts +20 -0
  65. package/dist/src/commands/inspect.js +709 -119
  66. package/dist/src/commands/pi.d.ts +3 -0
  67. package/dist/src/commands/pi.js +177 -0
  68. package/dist/src/commands/plugin.d.ts +16 -0
  69. package/dist/src/commands/plugin.js +576 -29
  70. package/dist/src/commands/profile-and-review.d.ts +4 -0
  71. package/dist/src/commands/profile-and-review.js +112 -67
  72. package/dist/src/commands/queue.d.ts +3 -0
  73. package/dist/src/commands/queue.js +20 -20
  74. package/dist/src/commands/remote.d.ts +3 -0
  75. package/dist/src/commands/remote.js +883 -50
  76. package/dist/src/commands/repo-git-harness.d.ts +5 -0
  77. package/dist/src/commands/repo-git-harness.js +78 -28
  78. package/dist/src/commands/run.d.ts +21 -0
  79. package/dist/src/commands/run.js +17732 -618
  80. package/dist/src/commands/server.d.ts +3 -0
  81. package/dist/src/commands/server.js +166 -285
  82. package/dist/src/commands/setup.d.ts +16 -0
  83. package/dist/src/commands/setup.js +230 -267
  84. package/dist/src/commands/stats.d.ts +15 -0
  85. package/dist/src/commands/stats.js +632 -0
  86. package/dist/src/commands/task-run-driver.d.ts +94 -0
  87. package/dist/src/commands/task-run-driver.js +120 -1856
  88. package/dist/src/commands/task.d.ts +35 -0
  89. package/dist/src/commands/task.js +676 -1183
  90. package/dist/src/commands/test.d.ts +3 -0
  91. package/dist/src/commands/test.js +15 -8
  92. package/dist/src/commands/triage.d.ts +11 -0
  93. package/dist/src/commands/triage.js +227 -0
  94. package/dist/src/commands/workspace.d.ts +3 -0
  95. package/dist/src/commands/workspace.js +18 -11
  96. package/dist/src/commands.d.ts +13 -0
  97. package/dist/src/commands.js +16760 -7779
  98. package/dist/src/index.d.ts +4 -0
  99. package/dist/src/index.js +16880 -8223
  100. package/dist/src/launcher.d.ts +61 -0
  101. package/dist/src/launcher.js +81 -13
  102. package/dist/src/operator-cli.d.ts +2 -0
  103. package/dist/src/operator-cli.js +17867 -0
  104. package/dist/src/operator-entry.d.ts +1 -0
  105. package/dist/src/operator-entry.js +3 -0
  106. package/dist/src/runner.d.ts +47 -0
  107. package/dist/src/runner.js +19 -24
  108. package/dist/src/withMutedConsole.d.ts +2 -0
  109. package/package.json +21 -7
  110. package/dist/src/commands/_authority-runs.js +0 -110
  111. package/dist/src/commands/_operator-view.js +0 -322
  112. package/dist/src/commands/_preflight.js +0 -460
  113. package/dist/src/commands/_server-client.js +0 -364
  114. package/dist/src/commands/_snapshot-upload.js +0 -313
  115. package/dist/src/commands/_task-picker.js +0 -48
  116. package/dist/src/commands/browser.js +0 -890
  117. package/dist/src/commands/connect.js +0 -180
  118. package/dist/src/commands/inspector.js +0 -256
  119. package/dist/src/commands/task-report-bug.js +0 -1083
  120. package/dist/src/report-bug.js +0 -260
@@ -0,0 +1,64 @@
1
+ // @bun
2
+ // packages/cli/src/commands/_pi-frontend.ts
3
+ import { homedir } from "os";
4
+ import { join } from "path";
5
+ import { assertPathInsideRoot, assertSafeRunId, safePathSegment } from "@rig/shared/safe-identifiers";
6
+ var TERMINAL_RUN_STATUSES = { completed: true, failed: true, stopped: true, cancelled: true, canceled: true, closed: true, merged: true, needs_attention: true, "needs-attention": true };
7
+ function resolveOperatorDroneCwd(runId, homeDir = homedir()) {
8
+ const safeRunId = assertSafeRunId(runId);
9
+ const dronesRoot = join(homeDir, ".rig", "drones");
10
+ return assertPathInsideRoot(dronesRoot, join(dronesRoot, safePathSegment(String(safeRunId).slice(0, 8), { fallback: "run", maxLength: 32 })), "operator drone cwd");
11
+ }
12
+ function buildOperatorPiEnv(input) {
13
+ return {
14
+ PI_CODING_AGENT_SESSION_DIR: input.sessionDir,
15
+ PI_SKIP_VERSION_CHECK: "1",
16
+ PI_HIDDEN_COMMANDS: "import,fork,clone,tree,new,resume,trust",
17
+ RIG_PI_OPERATOR_SESSION: "1",
18
+ RIG_RUN_ID: input.runId,
19
+ RIG_SERVER_URL: input.serverUrl,
20
+ ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
21
+ ...input.serverProjectRoot ? { RIG_PROJECT_ROOT: input.serverProjectRoot } : {}
22
+ };
23
+ }
24
+ function shouldRequireOperatorTranscript(status) {
25
+ return typeof status === "string" && TERMINAL_RUN_STATUSES[status.trim().toLowerCase()] === true;
26
+ }
27
+ function missingOperatorTranscriptMessage(runId, status) {
28
+ const shortRun = runId.trim().slice(0, 8) || "unknown";
29
+ const statusText = typeof status === "string" && status.trim() ? status.trim() : "terminal";
30
+ return `Run ${shortRun} is ${statusText}, but no worker Pi session transcript is recorded for this run. It likely failed before Pi started or predates full-session capture. Use \`rig run show ${runId}\` and \`rig run logs ${runId}\` for the lifecycle details.`;
31
+ }
32
+ async function runWithProcessExitGuard(body) {
33
+ const realExit = process.exit;
34
+ let exitCode = 0;
35
+ let signalQuit = () => {};
36
+ const quit = new Promise((resolve) => {
37
+ signalQuit = resolve;
38
+ });
39
+ const guardedExit = (code) => {
40
+ exitCode = typeof code === "number" ? code : 0;
41
+ signalQuit();
42
+ return;
43
+ };
44
+ process.exit = guardedExit;
45
+ try {
46
+ await Promise.race([Promise.resolve().then(body), quit]);
47
+ } finally {
48
+ process.exit = realExit;
49
+ }
50
+ return exitCode;
51
+ }
52
+ async function attachRunBundledPiFrontend(context, input) {
53
+ input.steered;
54
+ input.returnOnQuit;
55
+ throw new Error(`Interactive Pi attach for run ${input.runId} was removed in the OMP-collab cutover. Run bare \`rig\` and use the OMP cockpit run detail for the live session; legacy automation can request a non-interactive text snapshot with --once or --json.`);
56
+ }
57
+ export {
58
+ shouldRequireOperatorTranscript,
59
+ runWithProcessExitGuard,
60
+ resolveOperatorDroneCwd,
61
+ missingOperatorTranscriptMessage,
62
+ buildOperatorPiEnv,
63
+ attachRunBundledPiFrontend
64
+ };
@@ -0,0 +1,42 @@
1
+ export type PiInstallCheck = {
2
+ readonly ok: boolean;
3
+ readonly label: string;
4
+ readonly detail?: string;
5
+ readonly hint?: string;
6
+ };
7
+ export type CommandRunner = (command: string[], options?: {
8
+ cwd?: string;
9
+ }) => Promise<{
10
+ exitCode: number;
11
+ stdout: string;
12
+ stderr: string;
13
+ }>;
14
+ export type PiRigInstallStatus = {
15
+ readonly pi: PiInstallCheck;
16
+ readonly piRig: PiInstallCheck;
17
+ readonly extensionPath: string;
18
+ };
19
+ export declare function resolvePiRigExtensionPath(homeDir: string): string;
20
+ export declare function resolvePiRigPackageSource(projectRoot: string, exists?: (path: string) => boolean): string;
21
+ export declare function checkPiRigInstall(input?: {
22
+ readonly homeDir?: string;
23
+ readonly commandRunner?: CommandRunner;
24
+ readonly exists?: (path: string) => boolean;
25
+ }): Promise<PiRigInstallStatus>;
26
+ export declare function ensurePiRigInstalled(input: {
27
+ readonly projectRoot: string;
28
+ readonly homeDir?: string;
29
+ readonly commandRunner?: CommandRunner;
30
+ }): Promise<PiRigInstallStatus & {
31
+ installedPath: string;
32
+ }>;
33
+ export declare function ensureRemotePiRigInstalled(input: {
34
+ readonly requestJson: (pathname: string, init?: RequestInit) => Promise<unknown>;
35
+ }): Promise<PiRigInstallStatus & {
36
+ readonly remote: true;
37
+ }>;
38
+ export declare function buildPiSetupChecks(input?: {
39
+ readonly homeDir?: string;
40
+ readonly commandRunner?: CommandRunner;
41
+ readonly exists?: (path: string) => boolean;
42
+ }): Promise<PiInstallCheck[]>;
@@ -1,12 +1,9 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/_pi-install.ts
3
- import { existsSync, readFileSync, rmSync } from "fs";
3
+ import { existsSync } from "fs";
4
4
  import { homedir } from "os";
5
5
  import { resolve } from "path";
6
- var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
7
- var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
8
- export { default } from '@rig/pi-rig';
9
- `;
6
+ var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
10
7
  async function defaultCommandRunner(command, options = {}) {
11
8
  const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
12
9
  const [stdout, stderr, exitCode] = await Promise.all([
@@ -55,7 +52,13 @@ async function ensurePiBinaryAvailable(input) {
55
52
  }
56
53
  return { ok: true, detail: (current.stdout || current.stderr).trim() || undefined };
57
54
  }
58
- const installCommand = process.env.RIG_PI_INSTALL_COMMAND?.trim() || "bunx @earendil-works/pi@latest install";
55
+ const installCommand = process.env.RIG_PI_INSTALL_COMMAND?.trim();
56
+ if (!installCommand) {
57
+ return {
58
+ ok: false,
59
+ error: `${(current.stderr || current.stdout).trim() || "pi --version failed"}. Set RIG_PI_INSTALL_COMMAND to a supported installer or install Pi manually.`
60
+ };
61
+ }
59
62
  const parts = splitInstallCommand(installCommand);
60
63
  if (parts.length === 0) {
61
64
  return { ok: false, error: (current.stderr || current.stdout).trim() || "pi --version failed" };
@@ -72,18 +75,6 @@ async function ensurePiBinaryAvailable(input) {
72
75
  ...next.exitCode === 0 ? {} : { error: (next.stderr || next.stdout).trim() || "pi --version failed after install" }
73
76
  };
74
77
  }
75
- function removeManagedLegacyPiRigBridge(homeDir, exists = existsSync) {
76
- const extensionPath = resolvePiRigExtensionPath(homeDir);
77
- const indexPath = resolve(extensionPath, "index.ts");
78
- if (!exists(indexPath))
79
- return;
80
- try {
81
- const content = readFileSync(indexPath, "utf8");
82
- if (content === LEGACY_PI_RIG_MARKER || content.includes("Managed by Rig. Source package: @rig/pi-rig")) {
83
- rmSync(extensionPath, { recursive: true, force: true });
84
- }
85
- } catch {}
86
- }
87
78
  async function checkPiRigInstall(input = {}) {
88
79
  const home = resolvePiHomeDir(input.homeDir);
89
80
  const extensionPath = resolvePiRigExtensionPath(home);
@@ -94,48 +85,40 @@ async function checkPiRigInstall(input = {}) {
94
85
  piRig: { ok: true, label: "pi-rig global extension", detail: extensionPath }
95
86
  };
96
87
  }
97
- const exists = input.exists ?? existsSync;
98
88
  const runner = input.commandRunner ?? defaultCommandRunner;
99
89
  const piResult = await safeRun(runner, ["pi", "--version"]);
100
90
  const piListResult = piResult.exitCode === 0 ? await safeRun(runner, ["pi", "list"]) : { exitCode: 1, stdout: "", stderr: "" };
101
- const listedPiRig = piListResult.exitCode === 0 && piListContainsPiRig(`${piListResult.stdout}
91
+ const hasPiRig = piListResult.exitCode === 0 && piListContainsPiRig(`${piListResult.stdout}
102
92
  ${piListResult.stderr}`);
103
- const legacyBridge = exists(resolve(extensionPath, "index.ts"));
104
- const hasPiRig = listedPiRig;
93
+ const hasLegacyBridgeScaffold = !hasPiRig && (input.exists ?? existsSync)(extensionPath);
105
94
  return {
106
95
  extensionPath,
107
96
  pi: {
108
97
  ok: piResult.exitCode === 0,
109
98
  label: "pi",
110
99
  detail: (piResult.stdout || piResult.stderr).trim() || undefined,
111
- hint: piResult.exitCode === 0 ? undefined : "Install Pi or run `rig init --yes` to install/update the Pi runtime."
100
+ hint: piResult.exitCode === 0 ? undefined : "Install Pi/OMP manually or set RIG_PI_INSTALL_COMMAND before verifying with bare `rig` / Cockpit \u2192 Doctor."
112
101
  },
113
102
  piRig: {
114
103
  ok: hasPiRig,
115
104
  label: "pi-rig global extension",
116
- detail: hasPiRig ? piListResult.stdout.trim() || PI_RIG_PACKAGE_NAME : legacyBridge ? `${extensionPath} (legacy bridge; reinstall required)` : undefined,
117
- hint: hasPiRig ? undefined : "Run `rig init --yes` to install/enable the global pi-rig package with `pi install`."
105
+ detail: hasPiRig ? piListResult.stdout.trim() || PI_RIG_PACKAGE_NAME : hasLegacyBridgeScaffold ? `legacy bridge scaffold at ${extensionPath}` : undefined,
106
+ hint: hasPiRig ? undefined : `Install the Rig OMP extension with \`pi install ${PI_RIG_PACKAGE_NAME}\`, then verify with bare \`rig\` / Cockpit \u2192 Doctor.`
118
107
  }
119
108
  };
120
109
  }
121
110
  async function ensurePiRigInstalled(input) {
122
111
  const home = resolvePiHomeDir(input.homeDir);
123
112
  if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
124
- const status2 = await checkPiRigInstall({ homeDir: home, commandRunner: input.commandRunner });
125
- return { ...status2, installedPath: status2.extensionPath };
113
+ const status = await checkPiRigInstall({ homeDir: home, commandRunner: input.commandRunner });
114
+ return { ...status, installedPath: status.extensionPath };
126
115
  }
127
116
  const runner = input.commandRunner ?? defaultCommandRunner;
128
117
  const piAvailable = await ensurePiBinaryAvailable({ runner, projectRoot: input.projectRoot });
129
- const status = piAvailable.ok ? await checkPiRigInstall({ homeDir: home, commandRunner: runner }) : {
130
- extensionPath: resolvePiRigExtensionPath(home),
131
- pi: { ok: false, label: "pi", detail: piAvailable.error, hint: "Install/update Pi with RIG_PI_INSTALL_COMMAND or install Pi manually." },
132
- piRig: { ok: false, label: "pi-rig global extension", hint: "Pi is required before pi-rig can be installed." }
133
- };
134
118
  if (!piAvailable.ok) {
135
119
  throw new Error(`Pi install/update failed: ${piAvailable.error ?? "pi unavailable"}`);
136
120
  }
137
121
  const packageSource = resolvePiRigPackageSource(input.projectRoot);
138
- removeManagedLegacyPiRigBridge(home);
139
122
  const install = await runner(["pi", "install", packageSource], { cwd: input.projectRoot });
140
123
  if (install.exitCode !== 0) {
141
124
  throw new Error(`pi-rig install failed: ${(install.stderr || install.stdout).trim() || `exit ${install.exitCode}`}`);
@@ -147,7 +130,7 @@ async function ensureRemotePiRigInstalled(input) {
147
130
  const payload = await input.requestJson("/api/pi-rig/install", {
148
131
  method: "POST",
149
132
  headers: { "content-type": "application/json" },
150
- body: JSON.stringify({ package: "@rig/pi-rig", scope: "global" })
133
+ body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
151
134
  });
152
135
  const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
153
136
  const piOk = record.piOk === true || record.ok === true;
@@ -160,13 +143,13 @@ async function ensureRemotePiRigInstalled(input) {
160
143
  ok: piOk,
161
144
  label: "pi",
162
145
  detail: typeof record.piVersion === "string" ? record.piVersion : undefined,
163
- hint: piOk ? undefined : "Install/update Pi on the selected remote Rig server."
146
+ hint: piOk ? undefined : "Install/update Pi on the selected remote control host, then verify with bare `rig` / Cockpit \u2192 Doctor."
164
147
  },
165
148
  piRig: {
166
149
  ok: piRigOk,
167
150
  label: "pi-rig global extension",
168
151
  detail: extensionPath,
169
- hint: piRigOk ? undefined : "Install/enable pi-rig on the selected remote Rig server."
152
+ hint: piRigOk ? undefined : `Install/enable the Rig OMP extension (${PI_RIG_PACKAGE_NAME}) on the selected remote control host, then verify with bare \`rig\` / Cockpit \u2192 Doctor.`
170
153
  }
171
154
  };
172
155
  }
@@ -0,0 +1,8 @@
1
+ import { type RunnerContext } from "../runner";
2
+ export type PolicyMode = "off" | "observe" | "enforce";
3
+ export declare function resolveEffectivePolicyMode(context: RunnerContext): PolicyMode;
4
+ export declare function appendControlledBashAudit(context: RunnerContext, mode: PolicyMode, command: string, args: string[], matchedRuleIds: string[], action?: "warn" | "blocked"): void;
5
+ export declare function enforceNativeCommandPolicy(context: RunnerContext, args: string[], options: {
6
+ commandPrefix: string;
7
+ writeAuditLog: boolean;
8
+ }): Promise<void>;
@@ -1,36 +1,85 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/_policy.ts
3
- import { appendFileSync, mkdirSync } from "fs";
4
- import { resolve } from "path";
3
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "fs";
4
+ import { resolve as resolve2 } from "path";
5
5
 
6
6
  // packages/cli/src/runner.ts
7
7
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
8
- import { CliError } from "@rig/runtime/control-plane/errors";
8
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
9
9
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
10
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
11
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
12
10
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
13
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
11
+
12
+ class CliError extends RuntimeCliError {
13
+ hint;
14
+ constructor(message, exitCode = 1, options = {}) {
15
+ super(message, exitCode);
16
+ if (options.hint?.trim()) {
17
+ this.hint = options.hint.trim();
18
+ }
19
+ }
20
+ }
14
21
  function formatCommand(parts) {
15
22
  return parts.map((part) => /[^a-zA-Z0-9_./:-]/.test(part) ? JSON.stringify(part) : part).join(" ");
16
23
  }
17
24
 
25
+ // packages/cli/src/commands/_paths.ts
26
+ import { resolve } from "path";
27
+ import { resolveMonorepoRoot } from "@rig/runtime/layout";
28
+ function resolveControlPlaneHostStateRoot(projectRoot) {
29
+ return resolve(projectRoot, ".rig");
30
+ }
31
+ function resolveControlPlaneHostLogsDir(projectRoot) {
32
+ return resolve(resolveControlPlaneHostStateRoot(projectRoot), "logs");
33
+ }
34
+ function resolveControlPlaneDefinitionRoot(projectRoot) {
35
+ return resolve(projectRoot, "rig");
36
+ }
37
+
18
38
  // packages/cli/src/commands/_policy.ts
19
- import { evaluate as evaluate2, loadPolicy as loadPolicy2, resolveAction as resolveAction2 } from "@rig/runtime/control-plane/runtime/guard";
20
- import { resolveHarnessPaths } from "@rig/runtime/control-plane/native/utils";
39
+ function loadPolicyFile(projectRoot) {
40
+ const policyPath = resolve2(resolveControlPlaneDefinitionRoot(projectRoot), "policy", "policy.json");
41
+ if (!existsSync(policyPath)) {
42
+ return { mode: "observe", rules: [] };
43
+ }
44
+ try {
45
+ const parsed = JSON.parse(readFileSync(policyPath, "utf8"));
46
+ const mode = parsed.mode === "off" || parsed.mode === "observe" || parsed.mode === "enforce" ? parsed.mode : "observe";
47
+ const rules = Array.isArray(parsed.rules) ? parsed.rules.filter((value) => Boolean(value && typeof value === "object" && !Array.isArray(value))).map((value, index) => ({
48
+ id: typeof value.id === "string" && value.id.trim() ? value.id.trim() : `rule-${index + 1}`,
49
+ reason: typeof value.reason === "string" && value.reason.trim() ? value.reason.trim() : undefined,
50
+ match: typeof value.match === "string" && value.match.trim() ? value.match.trim() : undefined,
51
+ command: typeof value.command === "string" && value.command.trim() ? value.command.trim() : undefined
52
+ })) : [];
53
+ return { mode, rules };
54
+ } catch {
55
+ return { mode: "observe", rules: [] };
56
+ }
57
+ }
58
+ function matchingRules(command, rules) {
59
+ return rules.filter((rule) => {
60
+ const rawPattern = rule.command ?? rule.match;
61
+ if (!rawPattern)
62
+ return false;
63
+ try {
64
+ return new RegExp(rawPattern, "i").test(command);
65
+ } catch {
66
+ return command.includes(rawPattern);
67
+ }
68
+ });
69
+ }
21
70
  function resolveEffectivePolicyMode(context) {
22
71
  const envMode = process.env.RIG_BASH_MODE;
23
72
  if (envMode === "off" || envMode === "observe" || envMode === "enforce") {
24
73
  return envMode;
25
74
  }
26
- return context.policyMode || loadPolicy2(context.projectRoot).mode;
75
+ return context.policyMode ?? loadPolicyFile(context.projectRoot).mode;
27
76
  }
28
77
  function appendControlledBashAudit(context, mode, command, args, matchedRuleIds, action) {
29
78
  if (mode === "off") {
30
79
  return;
31
80
  }
32
- const logsDir = resolveHarnessPaths(context.projectRoot).logsDir;
33
- const logFile = resolve(logsDir, "controlled-bash.jsonl");
81
+ const logsDir = resolveControlPlaneHostLogsDir(context.projectRoot);
82
+ const logFile = resolve2(logsDir, "controlled-bash.jsonl");
34
83
  mkdirSync(logsDir, { recursive: true });
35
84
  const payload = {
36
85
  timestamp: new Date().toISOString(),
@@ -51,25 +100,24 @@ function appendControlledBashAudit(context, mode, command, args, matchedRuleIds,
51
100
  async function enforceNativeCommandPolicy(context, args, options) {
52
101
  const mode = resolveEffectivePolicyMode(context);
53
102
  const command = formatCommand([options.commandPrefix, ...args]);
54
- const decision = evaluate2({
55
- projectRoot: context.projectRoot,
56
- evaluation: { type: "command", command }
57
- });
58
- const action = resolveAction2(mode, decision.matchedRules);
59
- const matchedRuleIds = decision.matchedRules.map((rule) => rule.id);
103
+ const rules = matchingRules(command, loadPolicyFile(context.projectRoot).rules);
104
+ const matchedRuleIds = rules.map((rule) => rule.id);
105
+ const action = rules.length === 0 ? "allow" : mode === "enforce" ? "block" : "warn";
60
106
  await context.emitEvent("policy.decision", {
61
107
  target: "command",
62
108
  command,
63
109
  mode,
64
110
  allowed: action !== "block",
65
111
  matchedRuleIds,
66
- reasons: decision.matchedRules.map((rule) => rule.reason)
112
+ reasons: rules.map((rule) => rule.reason).filter(Boolean)
67
113
  });
68
114
  if (options.writeAuditLog) {
69
115
  appendControlledBashAudit(context, mode, command, args, matchedRuleIds, action === "block" ? "blocked" : action === "warn" ? "warn" : undefined);
70
116
  }
71
117
  if (action === "block") {
72
- throw new CliError2(`Policy blocked command: ${command}`, 126);
118
+ throw new CliError(`Policy blocked command: ${command}`, 126, {
119
+ hint: "Review rig/policy/policy.json, or re-run with `--policy-mode observe` to log instead of block."
120
+ });
73
121
  }
74
122
  }
75
123
  export {
@@ -0,0 +1 @@
1
+ export declare function runQuietBinaryProbe(binaryPath: string, args: string[], cwd: string): Promise<boolean>;
@@ -0,0 +1,114 @@
1
+ import type { ChildProcess, SpawnOptions } from "node:child_process";
2
+ import { type RegisteredTaskSource } from "@rig/contracts";
3
+ import { type CollabRegistryFilter, type LiveCollabProjection } from "@oh-my-pi/pi-coding-agent/collab/api";
4
+ export type RigExtensionTask = {
5
+ readonly id: string;
6
+ readonly title?: string | null;
7
+ readonly status?: string | null;
8
+ readonly source?: string | null;
9
+ readonly url?: string | null;
10
+ readonly body?: string | null;
11
+ };
12
+ export type RigExtensionServerState = {
13
+ readonly alias: string;
14
+ readonly kind: "local" | "remote";
15
+ readonly baseUrl?: string | null;
16
+ readonly projectRoot?: string | null;
17
+ readonly status?: string | null;
18
+ readonly taskSource?: string | null;
19
+ };
20
+ export type RigExtensionAttachResult = {
21
+ readonly sessionPath?: string | null;
22
+ readonly joinLink?: string | null;
23
+ readonly collabSessionId?: string | null;
24
+ };
25
+ export type RunRunProcessInput = {
26
+ readonly projectRoot?: string;
27
+ readonly taskId: string;
28
+ readonly runId?: string;
29
+ readonly title?: string | null;
30
+ readonly model?: string | null;
31
+ readonly prompt?: string | null;
32
+ readonly rigRunBin?: string;
33
+ readonly writeStdout?: (text: string) => void;
34
+ };
35
+ export type RunRunProcessResult = {
36
+ readonly runId: string;
37
+ readonly pid: number;
38
+ readonly sessionName: string;
39
+ readonly runtime: unknown;
40
+ };
41
+ export type RunRunProcess = (input: RunRunProcessInput) => Promise<RunRunProcessResult>;
42
+ type SpawnFunction = (command: string, args: readonly string[], options: SpawnOptions) => ChildProcess;
43
+ type ListTasksDeps = {
44
+ readonly loadTaskSource?: (projectRoot: string) => Promise<{
45
+ readonly kind: string;
46
+ readonly source: RegisteredTaskSource;
47
+ }>;
48
+ };
49
+ type SpawnRunProcessDeps = {
50
+ readonly runRunProcess?: RunRunProcess;
51
+ readonly spawn?: SpawnFunction;
52
+ readonly uuid?: () => string;
53
+ readonly resolveRigRunBin?: () => string;
54
+ readonly env?: NodeJS.ProcessEnv;
55
+ };
56
+ type AttachViaRelayDeps = {
57
+ readonly listActiveCollabSessions?: (filter: CollabRegistryFilter) => Promise<readonly LiveCollabProjection[]>;
58
+ readonly sleep?: (ms: number) => Promise<void>;
59
+ readonly now?: () => number;
60
+ };
61
+ export type SpawnRunProcessInput = {
62
+ readonly projectRoot: string;
63
+ readonly taskId: string;
64
+ readonly title?: string | null;
65
+ };
66
+ export type AttachViaRelayInput = {
67
+ readonly runId: string;
68
+ readonly taskId?: string | null;
69
+ readonly identityFilter: CollabRegistryFilter;
70
+ readonly timeoutMs?: number;
71
+ readonly pollIntervalMs?: number;
72
+ };
73
+ export declare function listTasksFromSource(projectRoot: string, deps?: ListTasksDeps): Promise<readonly RigExtensionTask[]>;
74
+ export declare function getTaskFromSource(projectRoot: string, taskId: string, deps?: ListTasksDeps): Promise<RigExtensionTask | null>;
75
+ export declare function readSelectedTarget(projectRoot: string, env?: NodeJS.ProcessEnv): Promise<RigExtensionServerState>;
76
+ export declare function resolveRigRunBin(): string;
77
+ export declare function spawnRunProcess(input: SpawnRunProcessInput, deps?: SpawnRunProcessDeps): Promise<{
78
+ readonly runId: string;
79
+ }>;
80
+ export type RunControl = {
81
+ readonly kind: "steer";
82
+ readonly message: string;
83
+ } | {
84
+ readonly kind: "stop";
85
+ readonly reason: string;
86
+ };
87
+ export interface DeliverRemoteControlDeps {
88
+ readonly env?: NodeJS.ProcessEnv;
89
+ readonly spawn?: SpawnFunction;
90
+ }
91
+ interface RemoteControlContext {
92
+ readonly checkout?: string | null;
93
+ readonly registryBaseUrl?: string | null;
94
+ readonly namespaceKey?: string | null;
95
+ }
96
+ /**
97
+ * Build the remote shell command that re-runs the operator's control VERB on the
98
+ * remote host. The run is LOCAL there (its session journal lives on the remote
99
+ * disk), so `rig run steer/stop <runId>` resolves it locally and the remote
100
+ * worker's own localRunChanges consumer applies it — fully symmetric with local
101
+ * placement. We SSH the verb (not a pre-built sentinel) so the remote rebuilds via
102
+ * its canonical stop path. Identity is passed inline (SSH does not forward local
103
+ * env); the registry HMAC secret is read from the remote env, never sent over argv.
104
+ */
105
+ export declare function buildRemoteControlCommand(runId: string, control: RunControl, ctx: RemoteControlContext): string;
106
+ /**
107
+ * Deliver a steer/stop control to a REMOTE run by running the verb on the remote
108
+ * host over SSH (login shell, identical to spawnRunProcess). Awaits the remote
109
+ * exit code so the operator gets an honest success/failure — not fire-and-forget.
110
+ */
111
+ export declare function deliverRemoteRunControl(projectRoot: string, runId: string, control: RunControl, deps?: DeliverRemoteControlDeps): Promise<void>;
112
+ export declare function listActiveRunCollab(filter: CollabRegistryFilter): Promise<readonly LiveCollabProjection[]>;
113
+ export declare function attachViaRelay(input: AttachViaRelayInput, deps?: AttachViaRelayDeps): Promise<RigExtensionAttachResult | null>;
114
+ export {};