patchwork-os 1.2.0-beta.2.canary.781 → 1.2.0-beta.2.canary.783

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 (135) hide show
  1. package/dist/activityLog.js +6 -1
  2. package/dist/activityLog.js.map +1 -1
  3. package/dist/approvalQueue.js +15 -0
  4. package/dist/approvalQueue.js.map +1 -1
  5. package/dist/bridge.js +32 -1
  6. package/dist/bridge.js.map +1 -1
  7. package/dist/claudeDriver.d.ts +10 -0
  8. package/dist/claudeDriver.js +27 -4
  9. package/dist/claudeDriver.js.map +1 -1
  10. package/dist/claudeOrchestrator.d.ts +5 -0
  11. package/dist/claudeOrchestrator.js +94 -6
  12. package/dist/claudeOrchestrator.js.map +1 -1
  13. package/dist/commands/patchworkInit.js +10 -0
  14. package/dist/commands/patchworkInit.js.map +1 -1
  15. package/dist/commands/policyExplain.d.ts +50 -0
  16. package/dist/commands/policyExplain.js +221 -0
  17. package/dist/commands/policyExplain.js.map +1 -0
  18. package/dist/commands/profile.d.ts +17 -0
  19. package/dist/commands/profile.js +85 -0
  20. package/dist/commands/profile.js.map +1 -0
  21. package/dist/commands/recipe.d.ts +8 -0
  22. package/dist/commands/recipe.js +64 -0
  23. package/dist/commands/recipe.js.map +1 -1
  24. package/dist/commands/recipeInstall.js +19 -0
  25. package/dist/commands/recipeInstall.js.map +1 -1
  26. package/dist/connectors/baseConnector.js +5 -0
  27. package/dist/connectors/baseConnector.js.map +1 -1
  28. package/dist/connectors/secrets.js +10 -2
  29. package/dist/connectors/secrets.js.map +1 -1
  30. package/dist/connectors/tokenStorage.d.ts +9 -0
  31. package/dist/connectors/tokenStorage.js +27 -0
  32. package/dist/connectors/tokenStorage.js.map +1 -1
  33. package/dist/decisionTraceLog.js +4 -3
  34. package/dist/decisionTraceLog.js.map +1 -1
  35. package/dist/drivers/claude/envSanitizer.d.ts +39 -0
  36. package/dist/drivers/claude/envSanitizer.js +114 -0
  37. package/dist/drivers/claude/envSanitizer.js.map +1 -1
  38. package/dist/drivers/claude/subprocess.d.ts +12 -0
  39. package/dist/drivers/claude/subprocess.js +114 -25
  40. package/dist/drivers/claude/subprocess.js.map +1 -1
  41. package/dist/drivers/claude/subprocessSettings.d.ts +4 -1
  42. package/dist/drivers/claude/subprocessSettings.js +24 -7
  43. package/dist/drivers/claude/subprocessSettings.js.map +1 -1
  44. package/dist/drivers/codex/subprocess.d.ts +19 -0
  45. package/dist/drivers/codex/subprocess.js +40 -8
  46. package/dist/drivers/codex/subprocess.js.map +1 -1
  47. package/dist/drivers/gemini/index.d.ts +24 -0
  48. package/dist/drivers/gemini/index.js +76 -13
  49. package/dist/drivers/gemini/index.js.map +1 -1
  50. package/dist/drivers/types.d.ts +14 -0
  51. package/dist/drivers/types.js.map +1 -1
  52. package/dist/errors.d.ts +1 -0
  53. package/dist/errors.js +1 -0
  54. package/dist/errors.js.map +1 -1
  55. package/dist/governance/doctorReport.d.ts +45 -0
  56. package/dist/governance/doctorReport.js +251 -0
  57. package/dist/governance/doctorReport.js.map +1 -0
  58. package/dist/governance/effectivePolicy.d.ts +134 -0
  59. package/dist/governance/effectivePolicy.js +322 -0
  60. package/dist/governance/effectivePolicy.js.map +1 -0
  61. package/dist/governance/killSwitchPolicy.d.ts +30 -0
  62. package/dist/governance/killSwitchPolicy.js +52 -0
  63. package/dist/governance/killSwitchPolicy.js.map +1 -0
  64. package/dist/governance/pluginPolicy.d.ts +111 -0
  65. package/dist/governance/pluginPolicy.js +247 -0
  66. package/dist/governance/pluginPolicy.js.map +1 -0
  67. package/dist/governance/profile.d.ts +153 -0
  68. package/dist/governance/profile.js +181 -0
  69. package/dist/governance/profile.js.map +1 -0
  70. package/dist/governance/secretValues.d.ts +89 -0
  71. package/dist/governance/secretValues.js +286 -0
  72. package/dist/governance/secretValues.js.map +1 -0
  73. package/dist/governance/toolFacts.d.ts +11 -0
  74. package/dist/governance/toolFacts.js +46 -0
  75. package/dist/governance/toolFacts.js.map +1 -0
  76. package/dist/governance/untrustedContent.d.ts +43 -0
  77. package/dist/governance/untrustedContent.js +81 -0
  78. package/dist/governance/untrustedContent.js.map +1 -0
  79. package/dist/index.js +73 -5
  80. package/dist/index.js.map +1 -1
  81. package/dist/logger.js +7 -6
  82. package/dist/logger.js.map +1 -1
  83. package/dist/patchworkConfig.d.ts +22 -0
  84. package/dist/patchworkConfig.js.map +1 -1
  85. package/dist/pluginLoader.d.ts +10 -2
  86. package/dist/pluginLoader.js +12 -8
  87. package/dist/pluginLoader.js.map +1 -1
  88. package/dist/recipeOrchestration.d.ts +0 -6
  89. package/dist/recipeOrchestration.js +80 -23
  90. package/dist/recipeOrchestration.js.map +1 -1
  91. package/dist/recipeRoutes.js +52 -0
  92. package/dist/recipeRoutes.js.map +1 -1
  93. package/dist/recipes/agentExecutor.d.ts +43 -3
  94. package/dist/recipes/agentExecutor.js +55 -7
  95. package/dist/recipes/agentExecutor.js.map +1 -1
  96. package/dist/recipes/approvalRequest.d.ts +8 -0
  97. package/dist/recipes/approvalRequest.js.map +1 -1
  98. package/dist/recipes/chainedRunner.d.ts +18 -5
  99. package/dist/recipes/chainedRunner.js +107 -12
  100. package/dist/recipes/chainedRunner.js.map +1 -1
  101. package/dist/recipes/haltCategory.d.ts +4 -0
  102. package/dist/recipes/haltCategory.js +4 -0
  103. package/dist/recipes/haltCategory.js.map +1 -1
  104. package/dist/recipes/schema.d.ts +5 -1
  105. package/dist/recipes/schemaGenerator.js +12 -1
  106. package/dist/recipes/schemaGenerator.js.map +1 -1
  107. package/dist/recipes/stepObservation.d.ts +15 -0
  108. package/dist/recipes/stepObservation.js +27 -22
  109. package/dist/recipes/stepObservation.js.map +1 -1
  110. package/dist/recipes/templateEngine.d.ts +11 -1
  111. package/dist/recipes/templateEngine.js +12 -2
  112. package/dist/recipes/templateEngine.js.map +1 -1
  113. package/dist/recipes/toolRegistry.d.ts +3 -1
  114. package/dist/recipes/toolRegistry.js +9 -3
  115. package/dist/recipes/toolRegistry.js.map +1 -1
  116. package/dist/recipes/tools/http.d.ts +5 -2
  117. package/dist/recipes/tools/http.js +29 -10
  118. package/dist/recipes/tools/http.js.map +1 -1
  119. package/dist/recipes/validation.d.ts +10 -1
  120. package/dist/recipes/validation.js +51 -1
  121. package/dist/recipes/validation.js.map +1 -1
  122. package/dist/recipes/yamlRunner.d.ts +46 -5
  123. package/dist/recipes/yamlRunner.js +173 -21
  124. package/dist/recipes/yamlRunner.js.map +1 -1
  125. package/dist/schemas/recipe.v1.json +60 -3
  126. package/dist/server.js +15 -0
  127. package/dist/server.js.map +1 -1
  128. package/dist/ssrfGuard.d.ts +89 -5
  129. package/dist/ssrfGuard.js +250 -5
  130. package/dist/ssrfGuard.js.map +1 -1
  131. package/dist/tools/httpClient.js +23 -165
  132. package/dist/tools/httpClient.js.map +1 -1
  133. package/dist/transport.js +41 -0
  134. package/dist/transport.js.map +1 -1
  135. package/package.json +1 -1
@@ -0,0 +1,221 @@
1
+ /**
2
+ * `patchwork policy explain <recipe> [tool]` — what can this recipe / worker /
3
+ * tool actually do right now, and why?
4
+ *
5
+ * Uses the SAME functions the runtime enforces with:
6
+ * - `resolveProfile` (config → profile) profile.ts
7
+ * - `toolFactsFor` (registry → tool facts) toolFacts.ts
8
+ * - `resolveAgentContainment` + `stepSandboxRequest` (agent steps)
9
+ * - `decideWorkerAction` + `resolveGateOutcome` (worker gate)
10
+ * - `parseDataPolicy` + `resolveDestination` + `decideBoundary` (privacy)
11
+ * - `readKillSwitch`
12
+ * - `computeEffectivePolicy` (composition) effectivePolicy.ts
13
+ *
14
+ * It never re-implements a rule. `effectivePolicy.test.ts` pins the runner
15
+ * to the same calculation, and this command is the human-readable face of it.
16
+ *
17
+ * Reads config and ledgers; writes nothing.
18
+ */
19
+ import { existsSync, readFileSync } from "node:fs";
20
+ import { parse as parseYaml } from "yaml";
21
+ import { computeEffectivePolicy, formatEffectivePolicy, } from "../governance/effectivePolicy.js";
22
+ import { readKillSwitch } from "../governance/killSwitchPolicy.js";
23
+ import { explainPluginPolicy, pluginSpecsOf, policyInputFromConfig, } from "../governance/pluginPolicy.js";
24
+ import { resolveAgentContainment, resolveProfile, } from "../governance/profile.js";
25
+ import { toolFactsFor } from "../governance/toolFacts.js";
26
+ import { loadConfig } from "../patchworkConfig.js";
27
+ import { patchworkPath } from "../patchworkHome.js";
28
+ import { stepSandboxRequest } from "../recipes/agentExecutor.js";
29
+ import { findYamlRecipePath } from "../recipesHttp.js";
30
+ import "../recipes/tools/index.js";
31
+ function resolveRecipeFile(ref, recipesDir) {
32
+ if (existsSync(ref) && /\.(ya?ml|json)$/.test(ref))
33
+ return ref;
34
+ return findYamlRecipePath(recipesDir, ref);
35
+ }
36
+ export async function explainRecipePolicy(ref, opts = {}) {
37
+ const recipesDir = opts.recipesDir ?? patchworkPath("recipes");
38
+ const file = resolveRecipeFile(ref, recipesDir);
39
+ if (!file) {
40
+ throw new Error(`recipe not found: ${ref} (looked in ${recipesDir})`);
41
+ }
42
+ const raw = parseYaml(readFileSync(file, "utf-8"));
43
+ const name = typeof raw.name === "string" ? raw.name : ref;
44
+ const trigger = (raw.trigger?.type ??
45
+ "manual");
46
+ let cfg = opts.config;
47
+ if (!cfg) {
48
+ try {
49
+ cfg = loadConfig();
50
+ }
51
+ catch {
52
+ cfg = undefined;
53
+ }
54
+ }
55
+ const profile = resolveProfile(cfg ? { profile: cfg.profile, approvalGate: cfg.approvalGate } : undefined);
56
+ // Worker facts — the same loader + decision the gate uses. Only when the
57
+ // profile (or the live flag) makes the worker gate active; otherwise the
58
+ // manifest is inert and the report says so by omitting the worker.
59
+ let workerId;
60
+ let workerCtx;
61
+ {
62
+ const { resolveWorkerIdForRecipe } = await import("../recipeOrchestration.js");
63
+ workerId = await resolveWorkerIdForRecipe(name, opts.workersDir);
64
+ const { isEnabled, FLAG_WORKER_AUTONOMY } = await import("../featureFlags.js");
65
+ const gateActive = profile.workerAuthority || isEnabled(FLAG_WORKER_AUTONOMY);
66
+ if (workerId && gateActive) {
67
+ const { loadWorkerTrustForRecipe } = await import("../workers/runWorkerShadow.js");
68
+ const trust = loadWorkerTrustForRecipe(name, {
69
+ ...(opts.patchworkDir && { patchworkDir: opts.patchworkDir }),
70
+ ...(opts.workersDir && { workersDir: opts.workersDir }),
71
+ });
72
+ if (trust) {
73
+ const { parseForbidRules } = await import("../workers/forbidPolicy.js");
74
+ workerCtx = {
75
+ worker: trust.worker,
76
+ store: trust.store,
77
+ forbidRules: parseForbidRules(trust.worker.forbids).rules,
78
+ };
79
+ }
80
+ }
81
+ }
82
+ // Privacy registry — parsed the way executeAgent parses it.
83
+ const { parseRegistry, resolveDestination } = await import("../privacy/destinationRegistry.js");
84
+ const { decideBoundary, parseDataPolicy, DEFAULT_CLASSIFICATION } = await import("../privacy/dataPolicy.js");
85
+ const registry = parseRegistry(cfg?.privacy);
86
+ const { decideWorkerAction, resolveGateOutcome } = await import("../workers/workerGate.js");
87
+ const plugins = explainPluginPolicy(pluginSpecsOf(raw), policyInputFromConfig(profile, cfg)).map((v) => ({ spec: v.spec, allowed: v.allowed, reason: v.reason }));
88
+ const steps = [];
89
+ const rawSteps = Array.isArray(raw.steps)
90
+ ? raw.steps
91
+ : [];
92
+ let n = 0;
93
+ for (const step of rawSteps) {
94
+ const agent = step.agent;
95
+ const toolId = agent
96
+ ? "agent"
97
+ : typeof step.tool === "string"
98
+ ? step.tool
99
+ : undefined;
100
+ const stepId = (typeof step.id === "string" && step.id) ||
101
+ (typeof step.into === "string" && step.into) ||
102
+ (agent && typeof agent.into === "string" && agent.into) ||
103
+ `step_${n}`;
104
+ n++;
105
+ if (!toolId)
106
+ continue; // compound / nested steps are governed by their children
107
+ if (opts.tool && opts.tool !== toolId)
108
+ continue;
109
+ const containment = agent
110
+ ? resolveAgentContainment(profile, stepSandboxRequest({
111
+ ...(agent.sandbox !== undefined && {
112
+ sandbox: agent.sandbox,
113
+ }),
114
+ ...(Array.isArray(agent.tools) && {
115
+ allowedTools: agent.tools,
116
+ }),
117
+ ...(Array.isArray(agent.disallowedTools) && {
118
+ disallowedTools: agent.disallowedTools,
119
+ }),
120
+ ...(typeof agent.mcpAccess === "boolean" && {
121
+ mcpAccess: agent.mcpAccess,
122
+ }),
123
+ }))
124
+ : undefined;
125
+ const tool = toolFactsFor(toolId, containment ? { containment } : undefined);
126
+ let worker;
127
+ if (workerCtx && workerId) {
128
+ const decision = decideWorkerAction(workerCtx.worker, toolId, undefined, workerCtx.store, workerCtx.forbidRules.length > 0
129
+ ? { forbidRules: workerCtx.forbidRules }
130
+ : undefined);
131
+ const resolved = resolveGateOutcome(decision, undefined);
132
+ worker = {
133
+ id: workerId,
134
+ action: decision.action,
135
+ ...(decision.ruleId !== undefined && {
136
+ ruleId: String(decision.ruleId),
137
+ }),
138
+ ...(resolved.standingPermissionId !== undefined && {
139
+ standingPermissionId: resolved.standingPermissionId,
140
+ }),
141
+ };
142
+ }
143
+ let privacy;
144
+ if (agent && registry.destinations.length > 0) {
145
+ const declared = agent.data_policy;
146
+ const policy = declared === undefined
147
+ ? { classification: DEFAULT_CLASSIFICATION }
148
+ : parseDataPolicy(declared);
149
+ const driver = typeof agent.driver === "string" ? agent.driver : cfg?.driver;
150
+ const dest = resolveDestination(registry, driver, policy?.classification ?? DEFAULT_CLASSIFICATION, cfg?.localEndpoint ? { endpoint: cfg.localEndpoint } : {});
151
+ if (dest) {
152
+ const outcome = policy
153
+ ? decideBoundary(policy, dest.destination, {
154
+ localDestinationAccepts: dest.localDestinationAccepts,
155
+ })
156
+ : {
157
+ decision: "DENY",
158
+ reason: "unrecognised classification",
159
+ };
160
+ privacy = {
161
+ classification: policy?.classification ?? "invalid",
162
+ destination: dest.destination.id,
163
+ decision: outcome.decision,
164
+ ...(outcome.reason !== undefined && { reason: outcome.reason }),
165
+ };
166
+ }
167
+ }
168
+ const input = {
169
+ profile,
170
+ recipe: {
171
+ name,
172
+ ...(typeof raw.requireApproval === "boolean" && {
173
+ requireApproval: raw.requireApproval,
174
+ }),
175
+ },
176
+ trigger,
177
+ tool,
178
+ ...(worker && { worker }),
179
+ ...(privacy && { privacy }),
180
+ killSwitch: readKillSwitch(profile),
181
+ };
182
+ steps.push({
183
+ stepId,
184
+ toolId,
185
+ input,
186
+ result: computeEffectivePolicy(input),
187
+ });
188
+ }
189
+ return {
190
+ recipe: name,
191
+ file,
192
+ trigger,
193
+ profileMode: profile.mode,
194
+ ...(workerId && { workerId }),
195
+ plugins,
196
+ steps,
197
+ };
198
+ }
199
+ export function formatExplainReport(r) {
200
+ const out = [];
201
+ if (r.plugins.length > 0) {
202
+ out.push(`PLUGINS (servers:)`);
203
+ for (const p of r.plugins) {
204
+ out.push(` ${p.allowed ? "ALLOWED" : "REFUSED"} ${p.spec} — ${p.reason}`);
205
+ }
206
+ out.push("");
207
+ }
208
+ if (r.steps.length === 0) {
209
+ out.push(`RECIPE: ${r.recipe}`);
210
+ out.push(`PROFILE: ${r.profileMode.toUpperCase()}`);
211
+ out.push("No tool or agent steps to explain.");
212
+ return out.join("\n");
213
+ }
214
+ for (const s of r.steps) {
215
+ out.push(`STEP: ${s.stepId}`);
216
+ out.push(formatEffectivePolicy(s.input, s.result));
217
+ out.push("");
218
+ }
219
+ return out.join("\n").trimEnd();
220
+ }
221
+ //# sourceMappingURL=policyExplain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policyExplain.js","sourceRoot":"","sources":["../../src/commands/policyExplain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `patchwork profile [show|governed|compat]` — read or set the governance
3
+ * profile in config.json.
4
+ *
5
+ * Setting writes ONE key. Everything the profile implies is resolved at
6
+ * bridge startup from that key (src/governance/profile.ts), so there is no
7
+ * second place the posture can be recorded and drift. A running bridge must
8
+ * be restarted for the change to take effect; the command says so.
9
+ */
10
+ export interface ProfileCommandResult {
11
+ ok: boolean;
12
+ text: string;
13
+ exitCode: number;
14
+ }
15
+ export declare function runProfileCommand(args: string[], opts?: {
16
+ configPath?: string;
17
+ }): ProfileCommandResult;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * `patchwork profile [show|governed|compat]` — read or set the governance
3
+ * profile in config.json.
4
+ *
5
+ * Setting writes ONE key. Everything the profile implies is resolved at
6
+ * bridge startup from that key (src/governance/profile.ts), so there is no
7
+ * second place the posture can be recorded and drift. A running bridge must
8
+ * be restarted for the change to take effect; the command says so.
9
+ */
10
+ import { formatGovernanceReport, governanceReport, } from "../governance/doctorReport.js";
11
+ import { PROFILE_MODES, resolveProfile, } from "../governance/profile.js";
12
+ import { loadConfig, saveConfig } from "../patchworkConfig.js";
13
+ export function runProfileCommand(args, opts = {}) {
14
+ const verb = args[0] ?? "show";
15
+ if (verb === "--help" || verb === "-h") {
16
+ return {
17
+ ok: true,
18
+ exitCode: 0,
19
+ text: "Usage: patchwork profile [show|governed|compat] [--json]\n\n" +
20
+ " show print the resolved governance posture (same lines as `patchwork doctor`)\n" +
21
+ " governed set profile: governed in config.json — approval gate ≥ high, automated\n" +
22
+ " triggers gated, worker authority + policy matrix enforced, agent steps\n" +
23
+ " contained, recipe plugins allowlisted, kill switch fails closed\n" +
24
+ " compat set profile: compat — byte-identical to pre-profile behaviour\n\n" +
25
+ "A running bridge must be restarted (launchctl kickstart …) to pick up a change.\n",
26
+ };
27
+ }
28
+ if (verb === "show") {
29
+ const json = args.includes("--json");
30
+ let cfg;
31
+ try {
32
+ cfg = loadConfig(opts.configPath);
33
+ }
34
+ catch {
35
+ cfg = undefined;
36
+ }
37
+ const report = governanceReport(cfg ? { config: cfg } : {});
38
+ return {
39
+ ok: true,
40
+ exitCode: report.governed ? 0 : 1,
41
+ text: json
42
+ ? JSON.stringify({
43
+ profile: report.profile.mode,
44
+ governed: report.governed,
45
+ lines: report.lines,
46
+ reasons: report.reasons,
47
+ }, null, 2)
48
+ : formatGovernanceReport(report),
49
+ };
50
+ }
51
+ if (!PROFILE_MODES.includes(verb)) {
52
+ return {
53
+ ok: false,
54
+ exitCode: 2,
55
+ text: `[profile] unknown verb "${verb}"; expected show | governed | compat\n`,
56
+ };
57
+ }
58
+ const mode = verb;
59
+ let cfg;
60
+ try {
61
+ cfg = loadConfig(opts.configPath);
62
+ }
63
+ catch (err) {
64
+ return {
65
+ ok: false,
66
+ exitCode: 1,
67
+ text: `[profile] cannot read config.json: ${err instanceof Error ? err.message : String(err)}\n`,
68
+ };
69
+ }
70
+ const before = cfg.profile;
71
+ saveConfig({ ...cfg, profile: mode }, opts.configPath);
72
+ const resolved = resolveProfile({
73
+ profile: mode,
74
+ approvalGate: cfg.approvalGate,
75
+ });
76
+ const lines = [
77
+ `[profile] ${before === undefined ? "(unset)" : before} → ${mode}`,
78
+ mode === "governed"
79
+ ? `[profile] governed: approval gate ${resolved.approvalGate}, automated triggers gated, worker authority + policy matrix enforced, agent steps contained, plugins allowlisted, kill switch fails closed`
80
+ : "[profile] compat: behaviour is byte-identical to a pre-profile install",
81
+ "[profile] restart the bridge for this to take effect; `patchwork doctor` shows the live posture",
82
+ ];
83
+ return { ok: true, exitCode: 0, text: `${lines.join("\n")}\n` };
84
+ }
85
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/commands/profile.ts"],"names":[],"mappings":"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"}
@@ -141,6 +141,14 @@ export interface WatchedRecipeRunResult {
141
141
  run?: RunRecipeResult;
142
142
  summary?: RecipeExecutionSummary;
143
143
  }
144
+ /**
145
+ * Resolve the governance deps for a bridge-less (`--local`) run. Exported for
146
+ * tests. Returns `{}` under compat or when the caller already supplied them.
147
+ */
148
+ export declare function resolveLocalGovernance(provided: Partial<RunnerDeps> | undefined, io?: {
149
+ isTTY?: boolean;
150
+ ask?: (question: string) => Promise<string>;
151
+ }): Promise<Partial<RunnerDeps>>;
144
152
  export declare function runRecipe(recipeRef: string, options?: RunRecipeOptions): Promise<RunRecipeResult>;
145
153
  export declare function summarizeRecipeExecution(result: RunRecipeResult["result"]): RecipeExecutionSummary;
146
154
  /**
@@ -818,6 +818,62 @@ function extractSchemaHeader(content) {
818
818
  }
819
819
  return {};
820
820
  }
821
+ /**
822
+ * Resolve the governance deps for a bridge-less (`--local`) run. Exported for
823
+ * tests. Returns `{}` under compat or when the caller already supplied them.
824
+ */
825
+ export async function resolveLocalGovernance(provided, io = {}) {
826
+ if (provided?.governance !== undefined)
827
+ return {};
828
+ const { loadConfig } = await import("../patchworkConfig.js");
829
+ const { resolveProfile } = await import("../governance/profile.js");
830
+ let cfg = {};
831
+ try {
832
+ cfg = loadConfig();
833
+ }
834
+ catch {
835
+ cfg = {};
836
+ }
837
+ const profile = resolveProfile(cfg);
838
+ // Publish it process-wide too: executeAgent (containment) and
839
+ // loadRecipeServers (plugin allowlist) read `activeProfile()`, not the
840
+ // runner deps. A local run that set only `deps.governance` would gate
841
+ // approvals as governed while containing agents as compat.
842
+ const { setActiveProfile } = await import("../governance/profile.js");
843
+ setActiveProfile(profile);
844
+ if (profile.mode !== "governed")
845
+ return {};
846
+ if (provided?.requireApprovalFn !== undefined)
847
+ return { governance: profile };
848
+ const isTTY = io.isTTY ?? (process.stdin.isTTY === true && process.stdout.isTTY === true);
849
+ const ask = io.ask ??
850
+ (async (question) => {
851
+ const readline = await import("node:readline/promises");
852
+ const rl = readline.createInterface({
853
+ input: process.stdin,
854
+ output: process.stdout,
855
+ });
856
+ try {
857
+ return await rl.question(question);
858
+ }
859
+ finally {
860
+ rl.close();
861
+ }
862
+ });
863
+ const requireApprovalFn = async (input) => {
864
+ if (input.effective === "ALLOW")
865
+ return true;
866
+ if (!isTTY) {
867
+ process.stderr.write(`[recipe run] step "${input.summary ?? input.toolId}" needs human approval under the governed profile and there is no terminal to ask on — refused. Run from a terminal, or run through the bridge so the approval reaches the dashboard.\n`);
868
+ return { approved: false, refusal: "rejected" };
869
+ }
870
+ const answer = await ask(`[recipe run] approve ${input.summary ?? input.toolId} (${input.tier} tier)? [y/N] `);
871
+ return /^y(es)?$/i.test(answer.trim())
872
+ ? true
873
+ : { approved: false, refusal: "rejected" };
874
+ };
875
+ return { governance: profile, requireApprovalFn };
876
+ }
821
877
  export async function runRecipe(recipeRef, options = {}) {
822
878
  const recipePath = resolveRecipePath(recipeRef);
823
879
  const recipe = loadYamlRecipe(recipePath);
@@ -831,11 +887,19 @@ export async function runRecipe(recipeRef, options = {}) {
831
887
  const recipeToRun = selection
832
888
  ? { ...recipe, steps: [selection.step] }
833
889
  : recipe;
890
+ // Phase 0: a local run is governed by the SAME profile as a bridge run.
891
+ // Under governed, a step the effective policy says needs a human is put to
892
+ // the operator on the terminal; with no TTY it is refused (fail closed)
893
+ // rather than executed. Under compat nothing is injected — byte-identical
894
+ // to before. Tests that pass their own `deps.requireApprovalFn` /
895
+ // `deps.governance` keep them.
896
+ const localGovernance = await resolveLocalGovernance(options.deps);
834
897
  const runnerDeps = {
835
898
  ...options.deps,
836
899
  workdir: options.workdir ?? options.deps?.workdir ?? process.cwd(),
837
900
  ...(options.manualRunId && { manualRunId: options.manualRunId }),
838
901
  ...(options.ledgerDir && { ledgerDir: options.ledgerDir }),
902
+ ...localGovernance,
839
903
  };
840
904
  if (options.dryRun) {
841
905
  throw new Error("runRecipeDryPlan must be used for dry-run execution");