@oxagen/cli 2.1.4-619 → 2.1.4-629

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 (2) hide show
  1. package/oxagen.mjs +254 -33
  2. package/package.json +1 -1
package/oxagen.mjs CHANGED
@@ -5028,6 +5028,12 @@ var init_envelope = __esm({
5028
5028
  * beside it for readers of the joined form.
5029
5029
  */
5030
5030
  policy_rules: external_exports.array(short).max(64).optional(),
5031
+ /**
5032
+ * The taint sources the decision read (#4666): each third-party read the
5033
+ * run made before the call, by tool, or `web` for a web fetch or search.
5034
+ * Absent when the run was untainted or no Cedar decision read it.
5035
+ */
5036
+ policy_taint: external_exports.array(short).max(32).optional(),
5031
5037
  policy_source: external_exports.enum(POLICY_SOURCES).optional(),
5032
5038
  policy_reason_code: short.optional(),
5033
5039
  policy_reason_digest: digest.optional(),
@@ -7180,6 +7186,70 @@ var init_builtins = __esm({
7180
7186
  }
7181
7187
  });
7182
7188
 
7189
+ // ../../packages/tacho/src/policy/run-facts.ts
7190
+ function taintSourceOf(action, tool) {
7191
+ if (tool.side_effect !== "read" || tool.egress !== "third_party") return null;
7192
+ return WEB_ACTIONS.has(action) ? WEB_TAINT_SOURCE : action;
7193
+ }
7194
+ function emptyRunCallLog() {
7195
+ return { calls: [], reads: [], taint: [] };
7196
+ }
7197
+ function add(list, value, max2) {
7198
+ if (list.length >= max2 || list.includes(value)) return;
7199
+ list.push(value);
7200
+ }
7201
+ function noteRunCall(log, action, tool, targets = []) {
7202
+ if (action === "") return;
7203
+ add(log.calls, action, RUN_CALLS_MAX);
7204
+ if (tool === null) return;
7205
+ const source = taintSourceOf(action, tool);
7206
+ if (source !== null) add(log.taint, source, RUN_TAINT_MAX);
7207
+ if (tool.side_effect !== "read") return;
7208
+ for (const target2 of targets) {
7209
+ if (target2 === "" || target2.length > RUN_READ_MAX_CHARS) continue;
7210
+ add(log.reads, target2, RUN_READS_MAX);
7211
+ }
7212
+ }
7213
+ function runFactsOf(log) {
7214
+ const sources = [...log.taint].sort();
7215
+ return {
7216
+ taint: { tainted: sources.length > 0, sources },
7217
+ run: {
7218
+ prior_calls: [...log.calls].sort(),
7219
+ prior_reads: [...log.reads].sort()
7220
+ }
7221
+ };
7222
+ }
7223
+ function targetsOfArgs(args) {
7224
+ const targets = [];
7225
+ for (const value of Object.values(args ?? {})) {
7226
+ if (typeof value === "string") {
7227
+ if (value !== "" && !targets.includes(value)) targets.push(value);
7228
+ } else if (Array.isArray(value)) {
7229
+ for (const item of value) {
7230
+ if (typeof item === "string" && item !== "" && !targets.includes(item))
7231
+ targets.push(item);
7232
+ }
7233
+ }
7234
+ }
7235
+ return targets;
7236
+ }
7237
+ var WEB_TAINT_SOURCE, WEB_ACTIONS, RUN_TAINT_MAX, RUN_CALLS_MAX, RUN_READS_MAX, RUN_READ_MAX_CHARS;
7238
+ var init_run_facts = __esm({
7239
+ "../../packages/tacho/src/policy/run-facts.ts"() {
7240
+ "use strict";
7241
+ WEB_TAINT_SOURCE = "web";
7242
+ WEB_ACTIONS = /* @__PURE__ */ new Set([
7243
+ "builtin__web_fetch",
7244
+ "builtin__web_search"
7245
+ ]);
7246
+ RUN_TAINT_MAX = 32;
7247
+ RUN_CALLS_MAX = 256;
7248
+ RUN_READS_MAX = 256;
7249
+ RUN_READ_MAX_CHARS = 512;
7250
+ }
7251
+ });
7252
+
7183
7253
  // ../../packages/tacho/src/policy/decision.ts
7184
7254
  function readCedarDecision(answer, approvalIds) {
7185
7255
  if (answer.type === "failure") {
@@ -7299,12 +7369,12 @@ function importedToolRequest(action, tool, input) {
7299
7369
  const { args: _types, ...classification } = tool;
7300
7370
  return { action, tool: classification, argSets: [args], errors };
7301
7371
  }
7302
- function hookRequest(call) {
7372
+ function hookRequest(call, tools) {
7303
7373
  const mcpAction = call.action === void 0 ? mcpActionFor(call.toolName) : void 0;
7304
- if (mcpAction !== void 0 && Object.hasOwn(call.cedar.tools, mcpAction)) {
7374
+ if (mcpAction !== void 0 && Object.hasOwn(tools, mcpAction)) {
7305
7375
  return importedToolRequest(
7306
7376
  mcpAction,
7307
- call.cedar.tools[mcpAction],
7377
+ tools[mcpAction],
7308
7378
  call.toolInput
7309
7379
  );
7310
7380
  }
@@ -7316,9 +7386,20 @@ function hookRequest(call) {
7316
7386
  errors: []
7317
7387
  };
7318
7388
  }
7389
+ function hookCallClass(call, tools) {
7390
+ if (isOxagenTool(call.toolName)) return null;
7391
+ const request3 = hookRequest(call, tools);
7392
+ const targets = [];
7393
+ for (const args of request3.argSets) {
7394
+ for (const target2 of targetsOfArgs(args)) {
7395
+ if (!targets.includes(target2)) targets.push(target2);
7396
+ }
7397
+ }
7398
+ return { action: request3.action, tool: request3.tool, targets };
7399
+ }
7319
7400
  function evaluateHookCall(call) {
7320
7401
  if (isOxagenTool(call.toolName)) return null;
7321
- const request3 = hookRequest(call);
7402
+ const request3 = hookRequest(call, call.cedar.tools);
7322
7403
  const { action } = request3;
7323
7404
  const principals = principalsFor(call.cedar, call.harness, call.agent);
7324
7405
  if (principals.length === 0) {
@@ -7350,7 +7431,12 @@ function evaluateHookCall(call) {
7350
7431
  args,
7351
7432
  now: call.now,
7352
7433
  tier: "harness",
7434
+ ...call.facts !== void 0 ? { taint: call.facts.taint, run: call.facts.run } : {},
7353
7435
  ...principal.operator_role !== void 0 ? { operator_role: principal.operator_role } : {},
7436
+ // The agent's budget from its row in Oxagen, which the bundle
7437
+ // carries on the principal. The hook runs on the machine and cannot
7438
+ // read the run's metered spend, so it does not subtract it: the
7439
+ // gateway's decision of a served call does (#4666).
7354
7440
  ...principal.budget_remaining_cents !== void 0 ? { budget_remaining_cents: principal.budget_remaining_cents } : {},
7355
7441
  harness_tool: call.harnessTool ?? call.toolName,
7356
7442
  ...skill !== void 0 ? { skill } : {}
@@ -7377,6 +7463,7 @@ var init_hook = __esm({
7377
7463
  "use strict";
7378
7464
  init_builtins();
7379
7465
  init_context();
7466
+ init_run_facts();
7380
7467
  init_decision();
7381
7468
  CALL_RESOURCE = { type: "Target", id: "call" };
7382
7469
  MCP_PREFIX = "mcp__";
@@ -7870,6 +7957,7 @@ function evaluatePreToolUse(input) {
7870
7957
  rules: [ask],
7871
7958
  reason_code: "rule_ask",
7872
7959
  reason: `Oxagen policy rule ${ask} requires a permission decision.`,
7960
+ ...taintOf(cedar),
7873
7961
  ...base2,
7874
7962
  stale
7875
7963
  };
@@ -7890,6 +7978,7 @@ function evaluatePreToolUse(input) {
7890
7978
  rules: allowed,
7891
7979
  reason_code: "rule_allow",
7892
7980
  reason: `Allowed by Oxagen policy rule ${rule}.`,
7981
+ ...taintOf(cedar),
7893
7982
  ...base2,
7894
7983
  stale
7895
7984
  };
@@ -7901,6 +7990,7 @@ function evaluatePreToolUse(input) {
7901
7990
  source: "bundle",
7902
7991
  reason_code: cedar.reason_code,
7903
7992
  reason: cedar.reason,
7993
+ ...taintOf(cedar),
7904
7994
  ...base2,
7905
7995
  stale
7906
7996
  };
@@ -7915,6 +8005,9 @@ function evaluatePreToolUse(input) {
7915
8005
  stale
7916
8006
  };
7917
8007
  }
8008
+ function taintOf(cedar) {
8009
+ return cedar?.taint === void 0 ? {} : { taint: cedar.taint };
8010
+ }
7918
8011
  function ruleNames(reasons) {
7919
8012
  return reasons.length > 0 ? { rule: reasons.join(" and "), rules: reasons } : {};
7920
8013
  }
@@ -7930,7 +8023,7 @@ function decideWithCedar(input) {
7930
8023
  const cedarBundle = input.bundle.cedar;
7931
8024
  if (cedarBundle === void 0 || input.cedar === void 0) return void 0;
7932
8025
  if (isOxagenTool(input.toolName)) return void 0;
7933
- const { runtime, harness, agent, skill, action, harness_tool } = input.cedar;
8026
+ const { runtime, harness, agent, skill, action, harness_tool, facts } = input.cedar;
7934
8027
  const readOnly = cedarReadOnly(cedarBundle, input.toolName, harness, action);
7935
8028
  if (runtime === null) {
7936
8029
  if (readOnly) return void 0;
@@ -7966,6 +8059,7 @@ function decideWithCedar(input) {
7966
8059
  ...harness_tool !== void 0 ? { harnessTool: harness_tool } : {},
7967
8060
  ...action !== void 0 ? { action } : {},
7968
8061
  ...skill !== void 0 ? { skill } : {},
8062
+ ...facts !== void 0 ? { facts } : {},
7969
8063
  now: input.now
7970
8064
  });
7971
8065
  } catch (error) {
@@ -7976,10 +8070,12 @@ function decideWithCedar(input) {
7976
8070
  };
7977
8071
  }
7978
8072
  if (verdict === null) return void 0;
8073
+ const taint = facts !== void 0 ? { taint: facts.taint.sources } : {};
7979
8074
  if (verdict.errors.length > 0) {
7980
8075
  return {
7981
8076
  evaluated: "deny",
7982
8077
  ...ruleNames(verdict.reasons),
8078
+ ...taint,
7983
8079
  reason_code: "cedar_error",
7984
8080
  reason: `Cedar could not decide ${verdict.action}: ${verdict.errors[0]}`
7985
8081
  };
@@ -7997,6 +8093,7 @@ function decideWithCedar(input) {
7997
8093
  return {
7998
8094
  evaluated: "allow",
7999
8095
  ...ruleNames(verdict.reasons),
8096
+ ...taint,
8000
8097
  reason_code: "cedar_allow",
8001
8098
  reason: `The steering record's Cedar policy ${names} permits this tool, so the harness's own permission flow decides.`
8002
8099
  };
@@ -8004,6 +8101,7 @@ function decideWithCedar(input) {
8004
8101
  return {
8005
8102
  evaluated: "ask",
8006
8103
  ...ruleNames(verdict.reasons),
8104
+ ...taint,
8007
8105
  reason_code: "cedar_require_approval",
8008
8106
  reason: `The steering record's Cedar policy ${names} requires a person's approval.`
8009
8107
  };
@@ -8011,6 +8109,7 @@ function decideWithCedar(input) {
8011
8109
  return {
8012
8110
  evaluated: "deny",
8013
8111
  ...ruleNames(verdict.reasons),
8112
+ ...taint,
8014
8113
  reason_code: "cedar_deny",
8015
8114
  reason: verdict.reasons.length > 0 ? `Denied by the steering record's Cedar policy ${names}.` : `No Cedar policy in the steering record permits ${verdict.action} for this agent.`
8016
8115
  };
@@ -20235,7 +20334,7 @@ async function isDirectory(path2) {
20235
20334
  async function subagentFiles(sessionDir, tally2) {
20236
20335
  const dir = join11(sessionDir, "subagents");
20237
20336
  const out = [];
20238
- const add = async (folder, name) => {
20337
+ const add2 = async (folder, name) => {
20239
20338
  const match = /^agent-(.+)\.jsonl$/.exec(name);
20240
20339
  if (match === null) return;
20241
20340
  const agentId = match[1];
@@ -20245,7 +20344,7 @@ async function subagentFiles(sessionDir, tally2) {
20245
20344
  meta: await readMeta(join11(folder, `agent-${agentId}.meta.json`))
20246
20345
  });
20247
20346
  };
20248
- for (const name of (await listDir(dir)).sort()) await add(dir, name);
20347
+ for (const name of (await listDir(dir)).sort()) await add2(dir, name);
20249
20348
  const workflows = join11(dir, "workflows");
20250
20349
  for (const workflow of (await listDir(workflows)).sort()) {
20251
20350
  const folder = join11(workflows, workflow);
@@ -20254,7 +20353,7 @@ async function subagentFiles(sessionDir, tally2) {
20254
20353
  tally2.recordsIgnored["workflow-journal"] = (tally2.recordsIgnored["workflow-journal"] ?? 0) + 1;
20255
20354
  continue;
20256
20355
  }
20257
- await add(folder, name);
20356
+ await add2(folder, name);
20258
20357
  }
20259
20358
  }
20260
20359
  return out;
@@ -23365,7 +23464,13 @@ var init_credential_store = __esm({
23365
23464
  /** `OPENAI_API_KEY` in Codex's `auth.json`. */
23366
23465
  "codex:auth.json",
23367
23466
  /** `TACHO_BROKER_<PROVIDER>_API_KEY` in the enrolling shell. */
23368
- "enroll:env"
23467
+ "enroll:env",
23468
+ /**
23469
+ * The run token in an Oxagen Agent Runners sandbox's `launch` message. It
23470
+ * is spent at the cloud gateway, which attaches the operator's model
23471
+ * credential. It is not a model credential itself.
23472
+ */
23473
+ "sandbox:launch"
23369
23474
  ];
23370
23475
  CREDENTIAL_KINDS = ["api_key", "bearer"];
23371
23476
  sealedSchema = external_exports.object({
@@ -23976,7 +24081,7 @@ import { readFileSync as readFileSync11 } from "node:fs";
23976
24081
  import { dirname as dirname9, resolve as resolve3 } from "node:path";
23977
24082
  import { fileURLToPath } from "node:url";
23978
24083
  function readVersion() {
23979
- if (true) return "2.1.4-619";
24084
+ if (true) return "2.1.4-629";
23980
24085
  try {
23981
24086
  const here = dirname9(fileURLToPath(import.meta.url));
23982
24087
  const pkg = JSON.parse(
@@ -35821,8 +35926,18 @@ function refusalMarkOf(result2) {
35821
35926
  return void 0;
35822
35927
  const mark = _meta[REFUSAL_META_KEY];
35823
35928
  if (mark === null || typeof mark !== "object") return void 0;
35824
- const { ruleIds } = mark;
35825
- return { ruleIds: Array.isArray(ruleIds) ? ruleIds : [] };
35929
+ const { ruleIds, taint } = mark;
35930
+ return {
35931
+ ruleIds: Array.isArray(ruleIds) ? ruleIds : [],
35932
+ ...Array.isArray(taint) ? { taint } : {}
35933
+ };
35934
+ }
35935
+ function refusalTaintOf(rpc) {
35936
+ if (rpc?.error !== void 0) return void 0;
35937
+ const listed = refusalMarkOf(rpc?.result)?.taint;
35938
+ if (listed === void 0) return void 0;
35939
+ const taint = listed.filter((source) => typeof source === "string").filter((source) => source !== "").slice(0, REFUSAL_TAINT_MAX).map((source) => source.slice(0, REFUSAL_RULE_MAX));
35940
+ return taint;
35826
35941
  }
35827
35942
  function toolErrorTextOf(result2) {
35828
35943
  if (result2 === null || typeof result2 !== "object") return void 0;
@@ -36067,6 +36182,7 @@ function createMcpGateway(deps) {
36067
36182
  const isCall = request3.method === "tools/call";
36068
36183
  if (!isCall && status2 !== "rejected") return;
36069
36184
  const ruleIds = status2 === "rejected" ? refusalRulesOf(rpc) : void 0;
36185
+ const taint = status2 === "rejected" ? refusalTaintOf(rpc) : void 0;
36070
36186
  const toolUseId = isCall ? toolUseIdOf(request3.params) : void 0;
36071
36187
  deps.record({
36072
36188
  sessionId: context.sessionId,
@@ -36077,6 +36193,7 @@ function createMcpGateway(deps) {
36077
36193
  durationMs,
36078
36194
  ...refusedReason === void 0 ? {} : { refusedReason },
36079
36195
  ...ruleIds === void 0 ? {} : { ruleIds },
36196
+ ...taint === void 0 ? {} : { taint },
36080
36197
  ...exchangeOf(request3, rpc, log)
36081
36198
  });
36082
36199
  }
@@ -36140,7 +36257,7 @@ function toolNameOf(params) {
36140
36257
  const name = params?.["name"];
36141
36258
  return typeof name === "string" && name.length > 0 ? name : "unknown";
36142
36259
  }
36143
- var RPC_INVALID_REQUEST, RPC_INTERNAL_ERROR, RPC_REFUSED, CLAUDE_CODE_TOOL_USE_ID_META, MAX_TOOL_USE_ID_LENGTH, DEFAULT_TIMEOUT_MS3, UPSTREAM_EXCERPT_MAX, KERNEL_REFUSAL_PREFIXES, REFUSAL_RULES_MAX, REFUSAL_RULE_MAX;
36260
+ var RPC_INVALID_REQUEST, RPC_INTERNAL_ERROR, RPC_REFUSED, CLAUDE_CODE_TOOL_USE_ID_META, MAX_TOOL_USE_ID_LENGTH, DEFAULT_TIMEOUT_MS3, UPSTREAM_EXCERPT_MAX, KERNEL_REFUSAL_PREFIXES, REFUSAL_TAINT_MAX, REFUSAL_RULES_MAX, REFUSAL_RULE_MAX;
36144
36261
  var init_mcp_gateway = __esm({
36145
36262
  "../../packages/tacho/src/collector/mcp-gateway.ts"() {
36146
36263
  "use strict";
@@ -36159,6 +36276,7 @@ var init_mcp_gateway = __esm({
36159
36276
  'IAM denied "',
36160
36277
  'IAM requires approval for "'
36161
36278
  ];
36279
+ REFUSAL_TAINT_MAX = 32;
36162
36280
  REFUSAL_RULES_MAX = 64;
36163
36281
  REFUSAL_RULE_MAX = 512;
36164
36282
  }
@@ -40489,6 +40607,34 @@ var init_recall_hints = __esm({
40489
40607
  }
40490
40608
  });
40491
40609
 
40610
+ // ../../packages/tacho/src/collector/policy-run.ts
40611
+ function notePolicyRunCall(record, call) {
40612
+ const counted = hookCallClass(
40613
+ {
40614
+ harness: call.harness,
40615
+ toolName: call.toolName,
40616
+ ...call.toolInput !== void 0 ? { toolInput: call.toolInput } : {}
40617
+ },
40618
+ call.cedar.tools
40619
+ );
40620
+ if (counted === null) return;
40621
+ record.policyRun ??= emptyRunCallLog();
40622
+ noteRunCall(record.policyRun, counted.action, counted.tool, counted.targets);
40623
+ }
40624
+ function policyRunFacts(record) {
40625
+ return runFactsOf(record.policyRun ?? emptyRunCallLog());
40626
+ }
40627
+ function forgetPolicyRun(record) {
40628
+ delete record.policyRun;
40629
+ }
40630
+ var init_policy_run = __esm({
40631
+ "../../packages/tacho/src/collector/policy-run.ts"() {
40632
+ "use strict";
40633
+ init_hook();
40634
+ init_run_facts();
40635
+ }
40636
+ });
40637
+
40492
40638
  // ../../packages/tacho/src/collector/hook-handler.ts
40493
40639
  import { homedir as homedir4 } from "node:os";
40494
40640
  import { dirname as dirname17, isAbsolute as isAbsolute6 } from "node:path";
@@ -40517,6 +40663,10 @@ function policyFacts2(evaluation, view) {
40517
40663
  // The list is what the Run page's Policy tab prints (#3971); the joined
40518
40664
  // `policy_rule` stays for older readers and `tacho.session_commands`.
40519
40665
  ...evaluation.rules !== void 0 && evaluation.rules.length > 0 ? { policy_rules: evaluation.rules.slice(0, 64) } : {},
40666
+ // The taint sources Cedar decided the call with (#4666). An empty list
40667
+ // says the session was assessed and clean; no member says it was not
40668
+ // assessed (ADR-201).
40669
+ ...evaluation.taint !== void 0 ? { policy_taint: evaluation.taint.slice(0, 32) } : {},
40520
40670
  ...evaluation.capability_id !== void 0 ? { capability_id: evaluation.capability_id } : {}
40521
40671
  };
40522
40672
  }
@@ -40746,6 +40896,7 @@ async function handleHookEvent(raw, env, deps, replay, harness, agent, hookId) {
40746
40896
  async function cedarCallFor(view, record, input, deps, action) {
40747
40897
  if (view.bundle.cedar === void 0) return void 0;
40748
40898
  const custom2 = record.customAgent;
40899
+ const facts = policyRunFacts(record);
40749
40900
  return {
40750
40901
  // No loader means no evaluator: a mutating tool is refused rather than
40751
40902
  // decided without the bundle's policies.
@@ -40755,7 +40906,9 @@ async function cedarCallFor(view, record, input, deps, action) {
40755
40906
  ...input.agent_type !== void 0 ? { skill: input.agent_type } : {},
40756
40907
  ...action !== void 0 ? { action } : {},
40757
40908
  // Cursor's adapter renames `Shell` to `Bash` and keeps Cursor's own name here.
40758
- ...typeof input["cursor_tool_name"] === "string" ? { harness_tool: input["cursor_tool_name"] } : {}
40909
+ ...typeof input["cursor_tool_name"] === "string" ? { harness_tool: input["cursor_tool_name"] } : {},
40910
+ // The taint and prior calls of the calls that ran in the session (#4666).
40911
+ facts
40759
40912
  };
40760
40913
  }
40761
40914
  function evaluationRequestFor(view, toolName3, toolInput, record, deps, input, cedar) {
@@ -41316,6 +41469,13 @@ async function routeHook(raw, env, deps, replay, harness, agent, hookId) {
41316
41469
  case "PostToolUse":
41317
41470
  case "PostToolUseFailure": {
41318
41471
  events.push(...record.recorder.ingestHook(payload, env, at, withReplay));
41472
+ if (view.bundle.cedar !== void 0 && input.tool_name !== void 0)
41473
+ notePolicyRunCall(record, {
41474
+ cedar: view.bundle.cedar,
41475
+ harness: record.customAgent !== void 0 ? "custom" : record.harness ?? "claude-code",
41476
+ toolName: input.tool_name,
41477
+ ...input.tool_input !== void 0 ? { toolInput: input.tool_input } : {}
41478
+ });
41319
41479
  if (input.hook_event_name === "PostToolUseFailure")
41320
41480
  noteToolFailure(record, input.tool_name);
41321
41481
  else if (deps.noteMemoryReads !== void 0 && (record.harness ?? "claude-code") === "claude-code")
@@ -41380,6 +41540,7 @@ async function routeHook(raw, env, deps, replay, harness, agent, hookId) {
41380
41540
  events.push(...record.recorder.ingestHook(payload, env, at, withReplay));
41381
41541
  deps.registry.seal(record);
41382
41542
  forgetRecallHints(record);
41543
+ forgetPolicyRun(record);
41383
41544
  const harness2 = skillsHarness(record);
41384
41545
  if (deps.skills !== void 0 && harness2 !== void 0)
41385
41546
  await deps.skills.remove(harness2, record.harnessSessionId, env);
@@ -41455,6 +41616,7 @@ var init_hook_handler = __esm({
41455
41616
  init_reflection_ask();
41456
41617
  init_interjection2();
41457
41618
  init_recall_hints();
41619
+ init_policy_run();
41458
41620
  CONTAINMENT_REQUIRED_REASON = "This agent's mandate requires the contained tier. Start it with `oxagen agent run --contained`.";
41459
41621
  MESSAGE_BOUNDARIES = /* @__PURE__ */ new Set([
41460
41622
  "SessionStart",
@@ -41999,7 +42161,8 @@ function gatewayFrameBody(call) {
41999
42161
  policy_source: "kernel",
42000
42162
  policy_reason_code: "mcp_refused",
42001
42163
  policy_reason_digest: digestText(call.refusedReason ?? "refused"),
42002
- ...call.ruleIds === void 0 || call.ruleIds.length === 0 ? {} : { policy_rules: call.ruleIds }
42164
+ ...call.ruleIds === void 0 || call.ruleIds.length === 0 ? {} : { policy_rules: call.ruleIds },
42165
+ ...call.taint === void 0 ? {} : { policy_taint: call.taint }
42003
42166
  } : {}
42004
42167
  }
42005
42168
  };
@@ -49842,6 +50005,33 @@ import { readdir as readdir3, readFile as readFile3, stat as stat2 } from "node:
49842
50005
  import { homedir as homedir7, hostname as osHostname2 } from "node:os";
49843
50006
  import { dirname as dirname21, join as join39 } from "node:path";
49844
50007
  import { execFile as execFile4, spawnSync as spawnSync7 } from "node:child_process";
50008
+ function cloudGatewayUpstreams(origin = CLOUD_GATEWAY_ORIGIN) {
50009
+ const base2 = new URL(origin).origin;
50010
+ const openai = `${base2}${CLOUD_GATEWAY_BASE_PATH}/openai/v1`;
50011
+ return {
50012
+ anthropic: `${base2}${CLOUD_GATEWAY_BASE_PATH}/anthropic`,
50013
+ openai,
50014
+ chatgpt: openai
50015
+ };
50016
+ }
50017
+ function cloudGatewayCredentialStore(disk, runToken) {
50018
+ const held = (provider) => ({
50019
+ kind: provider === "anthropic" ? "api_key" : "bearer",
50020
+ secret: runToken
50021
+ });
50022
+ return {
50023
+ ...disk,
50024
+ has: () => true,
50025
+ read: (provider) => held(provider),
50026
+ take: () => {
50027
+ throw new Error(
50028
+ "A sandbox's local gateway holds no model credential. The cloud gateway attaches the operator's key."
50029
+ );
50030
+ },
50031
+ release: () => void 0,
50032
+ status: () => []
50033
+ };
50034
+ }
49845
50035
  function defaultExec(command2, args) {
49846
50036
  const result2 = spawnSync7(command2, args, {
49847
50037
  encoding: "utf8",
@@ -50137,10 +50327,14 @@ async function initializeDaemon(options, server, port, ready) {
50137
50327
  const bootId = `tachod-${ulid(now())}`;
50138
50328
  const hostRecord = registry2.ensure(bootId, { pid: process.pid }).record;
50139
50329
  const hostRecorder = hostRecord.recorder;
50140
- const credentialStore = openCredentialStore({
50330
+ const diskCredentialStore = openCredentialStore({
50141
50331
  file: paths.credentials,
50142
50332
  key: paths.credentialsKey
50143
50333
  });
50334
+ const credentialStore = options.cloudGateway !== void 0 ? cloudGatewayCredentialStore(
50335
+ diskCredentialStore,
50336
+ options.cloudGateway.runToken
50337
+ ) : diskCredentialStore;
50144
50338
  const runTokenKey = () => readRunTokenKey(paths.runTokenKey) ?? loadOrCreateRunTokenKey(paths.runTokenKey).key;
50145
50339
  runTokenKey();
50146
50340
  function inCustody(provider) {
@@ -51788,11 +51982,11 @@ async function initializeDaemon(options, server, port, ready) {
51788
51982
  bundle: host.bundle,
51789
51983
  hostStatus: hostStatusInForce(host, bundleVerified)
51790
51984
  }),
51791
- upstreams: () => ({
51985
+ upstreams: () => options.cloudGateway !== void 0 ? cloudGatewayUpstreams(options.cloudGateway.origin) : {
51792
51986
  ...DEFAULT_MODEL_UPSTREAMS,
51793
51987
  ...displacedUpstreams,
51794
51988
  ...options.modelUpstreams
51795
- }),
51989
+ },
51796
51990
  // A restart must not hand the agent's day back either (ADR-160). The day
51797
51991
  // counter asks this once per process (`createDaySpend`), so the figure
51798
51992
  // read at startup is used once. A first call on a later day than
@@ -51971,6 +52165,7 @@ async function initializeDaemon(options, server, port, ready) {
51971
52165
  }
51972
52166
  const api = {
51973
52167
  runContained: contained.run,
52168
+ runSandbox: contained.runSandbox,
51974
52169
  githubLease: (input) => githubProxy.issue(input),
51975
52170
  githubProxy: (req, res) => githubProxy.handle(req, res),
51976
52171
  localToken: host.local_token,
@@ -52375,6 +52570,7 @@ async function initializeDaemon(options, server, port, ready) {
52375
52570
  }
52376
52571
  registry2.settleSwept(candidate);
52377
52572
  forgetRecallHints(candidate.record);
52573
+ forgetPolicyRun(candidate.record);
52378
52574
  closed2.push(candidate);
52379
52575
  }
52380
52576
  if (!queues.hostBusy()) {
@@ -52631,7 +52827,7 @@ async function initializeDaemon(options, server, port, ready) {
52631
52827
  }
52632
52828
  };
52633
52829
  }
52634
- var DEFAULT_TIMERS, STATIC_TOKEN_RENEWAL_CHECK_MS, MEMORY_SCAN_MS, QUEUED_PROMPT_CLASS, RECORDER_EVENTS_KEPT, STOPPING, STOPPED_EXEC, STATE_WRITE_DEFER_MS;
52830
+ var DEFAULT_TIMERS, STATIC_TOKEN_RENEWAL_CHECK_MS, MEMORY_SCAN_MS, CLOUD_GATEWAY_ORIGIN, CLOUD_GATEWAY_BASE_PATH, QUEUED_PROMPT_CLASS, RECORDER_EVENTS_KEPT, STOPPING, STOPPED_EXEC, STATE_WRITE_DEFER_MS;
52635
52831
  var init_daemon = __esm({
52636
52832
  "../../packages/tacho/src/collector/daemon.ts"() {
52637
52833
  "use strict";
@@ -52686,6 +52882,7 @@ var init_daemon = __esm({
52686
52882
  init_machine();
52687
52883
  init_push_basis();
52688
52884
  init_recall_hints();
52885
+ init_policy_run();
52689
52886
  init_session_skills();
52690
52887
  init_credential_issuer();
52691
52888
  init_day_spend();
@@ -52720,6 +52917,8 @@ var init_daemon = __esm({
52720
52917
  };
52721
52918
  STATIC_TOKEN_RENEWAL_CHECK_MS = 60 * 6e4;
52722
52919
  MEMORY_SCAN_MS = 5 * 6e4;
52920
+ CLOUD_GATEWAY_ORIGIN = "https://api.oxagen.sh";
52921
+ CLOUD_GATEWAY_BASE_PATH = "/v1/gateway";
52723
52922
  QUEUED_PROMPT_CLASS = RETENTION_CLASS_BY_KIND["oxagen:message"] ?? "model_call";
52724
52923
  RECORDER_EVENTS_KEPT = 64;
52725
52924
  STOPPING = "tachod is stopping; tacho-hook spools this hook for the next start";
@@ -52868,7 +53067,7 @@ async function stopAll(daemons) {
52868
53067
  const failed3 = results.find((result2) => result2.status === "rejected");
52869
53068
  if (failed3 !== void 0) throw failed3.reason;
52870
53069
  }
52871
- async function runDaemonProcess() {
53070
+ async function runDaemonProcess(options = {}) {
52872
53071
  const paths = tachoHome(process.env);
52873
53072
  const daemons = [];
52874
53073
  let stopping;
@@ -52912,6 +53111,18 @@ async function runDaemonProcess() {
52912
53111
  process.on("SIGHUP", () => {
52913
53112
  for (const daemon of daemons) daemon.refreshBundle().catch(() => void 0);
52914
53113
  });
53114
+ if (options.started !== void 0) {
53115
+ try {
53116
+ await options.started(daemons);
53117
+ } catch (error) {
53118
+ log(
53119
+ `tachod: ${error instanceof Error ? error.message : String(error)}, stopping
53120
+ `
53121
+ );
53122
+ stopWithin(stopOnce, STOP_GRACE_MS, () => exit(1), log);
53123
+ return;
53124
+ }
53125
+ }
52915
53126
  await new Promise(() => void 0);
52916
53127
  }
52917
53128
  var STOP_GRACE_MS;
@@ -54940,7 +55151,7 @@ var init_package = __esm({
54940
55151
  "package.json"() {
54941
55152
  package_default = {
54942
55153
  name: "@oxagen/cli",
54943
- version: "2.1.4-619",
55154
+ version: "2.1.4-629",
54944
55155
  description: "Oxagen governance CLI \u2014 spend, lineage, grounding, credentials, and audit (agentic coding lives in the stella CLI)",
54945
55156
  type: "module",
54946
55157
  main: "./dist/index.js",
@@ -64051,7 +64262,7 @@ var require_YAMLMap = __commonJS({
64051
64262
  static from(schema, obj2, ctx) {
64052
64263
  const { keepUndefined, replacer } = ctx;
64053
64264
  const map = new this(schema);
64054
- const add = (key, value) => {
64265
+ const add2 = (key, value) => {
64055
64266
  if (typeof replacer === "function")
64056
64267
  value = replacer.call(obj2, key, value);
64057
64268
  else if (Array.isArray(replacer) && !replacer.includes(key))
@@ -64061,10 +64272,10 @@ var require_YAMLMap = __commonJS({
64061
64272
  };
64062
64273
  if (obj2 instanceof Map) {
64063
64274
  for (const [key, value] of obj2)
64064
- add(key, value);
64275
+ add2(key, value);
64065
64276
  } else if (obj2 && typeof obj2 === "object") {
64066
64277
  for (const key of Object.keys(obj2))
64067
- add(key, obj2[key]);
64278
+ add2(key, obj2[key]);
64068
64279
  }
64069
64280
  if (typeof schema.sortMapEntries === "function") {
64070
64281
  map.items.sort(schema.sortMapEntries);
@@ -78587,10 +78798,10 @@ var init_esm2 = __esm({
78587
78798
  lpos = pos, lm = null;
78588
78799
  break;
78589
78800
  } else {
78590
- var add = sym - 254;
78801
+ var add2 = sym - 254;
78591
78802
  if (sym > 264) {
78592
78803
  var i2 = sym - 257, b5 = fleb[i2];
78593
- add = bits(dat, pos, (1 << b5) - 1) + fl[i2];
78804
+ add2 = bits(dat, pos, (1 << b5) - 1) + fl[i2];
78594
78805
  pos += b5;
78595
78806
  }
78596
78807
  var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;
@@ -78609,7 +78820,7 @@ var init_esm2 = __esm({
78609
78820
  }
78610
78821
  if (resize)
78611
78822
  cbuf(bt + 131072);
78612
- var end = bt + add;
78823
+ var end = bt + add2;
78613
78824
  if (bt < dt) {
78614
78825
  var shift = dl - dt, dend = Math.min(dt, end);
78615
78826
  if (shift + bt < 0)
@@ -90587,7 +90798,7 @@ var require_umd = __commonJS({
90587
90798
  return this.not().add(ONE);
90588
90799
  };
90589
90800
  LongPrototype.neg = LongPrototype.negate;
90590
- LongPrototype.add = function add(addend) {
90801
+ LongPrototype.add = function add2(addend) {
90591
90802
  if (!isLong3(addend)) addend = fromValue(addend);
90592
90803
  var a48 = this.high >>> 16;
90593
90804
  var a32 = this.high & 65535;
@@ -92076,7 +92287,7 @@ var require_namespace = __commonJS({
92076
92287
  return this.nested[name].values;
92077
92288
  throw Error("no such enum: " + name);
92078
92289
  };
92079
- Namespace.prototype.add = function add(object) {
92290
+ Namespace.prototype.add = function add2(object) {
92080
92291
  if (!(object instanceof Field && object.extend !== void 0 || object instanceof Type || object instanceof OneOf || object instanceof Enum || object instanceof Service || object instanceof Namespace))
92081
92292
  throw TypeError("object must be a valid nested object");
92082
92293
  if (object.name === "__proto__")
@@ -92460,7 +92671,7 @@ var require_service2 = __commonJS({
92460
92671
  });
92461
92672
  return this;
92462
92673
  };
92463
- Service.prototype.add = function add(object) {
92674
+ Service.prototype.add = function add2(object) {
92464
92675
  if (this.get(object.name))
92465
92676
  throw Error("duplicate name '" + object.name + "' in " + this);
92466
92677
  if (object instanceof Method) {
@@ -93187,7 +93398,7 @@ var require_type = __commonJS({
93187
93398
  return this.nested[name];
93188
93399
  return null;
93189
93400
  };
93190
- Type.prototype.add = function add(object) {
93401
+ Type.prototype.add = function add2(object) {
93191
93402
  if (this.get(object.name))
93192
93403
  throw Error("duplicate name '" + object.name + "' in " + this);
93193
93404
  if (object instanceof Field && object.extend === void 0) {
@@ -94114,7 +94325,7 @@ var require_oneof = __commonJS({
94114
94325
  oneof.parent.add(oneof.fieldsArray[i2]);
94115
94326
  }
94116
94327
  }
94117
- OneOf.prototype.add = function add(field) {
94328
+ OneOf.prototype.add = function add2(field) {
94118
94329
  if (!(field instanceof Field))
94119
94330
  throw TypeError("field must be a Field");
94120
94331
  if (field.parent && field.parent !== this.parent)
@@ -94446,7 +94657,7 @@ var require_enum = __commonJS({
94446
94657
  keepComments ? this.comments : void 0
94447
94658
  ]);
94448
94659
  };
94449
- Enum.prototype.add = function add(name, id, comment, options) {
94660
+ Enum.prototype.add = function add2(name, id, comment, options) {
94450
94661
  if (!util2.isString(name))
94451
94662
  throw TypeError("name must be a string");
94452
94663
  if (!util2.isInteger(id))
@@ -122693,6 +122904,7 @@ var init_policy2 = __esm({
122693
122904
  init_decision();
122694
122905
  init_hook();
122695
122906
  init_runtime();
122907
+ init_run_facts();
122696
122908
  }
122697
122909
  });
122698
122910
 
@@ -123498,6 +123710,14 @@ var init_decision_rules = __esm({
123498
123710
  }
123499
123711
  });
123500
123712
 
123713
+ // ../../packages/policy/src/run-facts.ts
123714
+ var init_run_facts2 = __esm({
123715
+ "../../packages/policy/src/run-facts.ts"() {
123716
+ "use strict";
123717
+ init_policy2();
123718
+ }
123719
+ });
123720
+
123501
123721
  // ../../packages/policy/src/visibility.ts
123502
123722
  var init_visibility = __esm({
123503
123723
  "../../packages/policy/src/visibility.ts"() {
@@ -123515,6 +123735,7 @@ var init_src7 = __esm({
123515
123735
  init_compile3();
123516
123736
  init_decision_rules();
123517
123737
  init_evaluate();
123738
+ init_run_facts2();
123518
123739
  init_schema4();
123519
123740
  init_tests();
123520
123741
  init_visibility();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxagen/cli",
3
- "version": "2.1.4-619",
3
+ "version": "2.1.4-629",
4
4
  "description": "Oxagen governance CLI — spend, lineage, grounding, credentials, and audit (agentic coding lives in the stella CLI)",
5
5
  "type": "module",
6
6
  "bin": {