@oxagen/cli 2.1.6-143 → 2.1.6-152

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 (3) hide show
  1. package/README.md +3 -1
  2. package/oxagen.mjs +531 -93
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -123,7 +123,7 @@ node apps/cli/dist-standalone/oxagen.mjs --version
123
123
  ```bash
124
124
  oxagen login # opens a browser, OAuth + org/workspace picker
125
125
  oxagen login --token oxk_live_… --org acme --workspace main # CI / headless
126
- oxagen logout # clear the saved session
126
+ oxagen logout # revoke this machine's sign-in and clear the saved session
127
127
  oxagen graph search -q "workspace context" --limit 1 # confirm credentials work
128
128
  ```
129
129
 
@@ -147,6 +147,8 @@ token needs no login either.
147
147
  ```bash
148
148
  oxagen budget show # spend ceilings with their live burn
149
149
  oxagen budget set --scope org --period month --limit 500
150
+ oxagen budget no-progress show # the no-progress limit and the runs that reached it
151
+ oxagen budget no-progress set --repeats 30 --mode enforced # opens a steering PR
150
152
  oxagen cost --in 200000 --out 40000 # project cost from the baked-in rate card
151
153
  oxagen cost --rates # print the rate card
152
154
  oxagen router preview|policy # the verified-outcome market router
package/oxagen.mjs CHANGED
@@ -234,7 +234,7 @@ function claudeSettingsFacts(env, cwd, model, readText3) {
234
234
  `${project2}/.claude/settings.local.json`
235
235
  );
236
236
  const facts = {};
237
- const word = (value) => typeof value === "string" && value.trim() !== "" ? value.trim().slice(0, EFFORT_SETTING_MAX) : void 0;
237
+ const word2 = (value) => typeof value === "string" && value.trim() !== "" ? value.trim().slice(0, EFFORT_SETTING_MAX) : void 0;
238
238
  for (const path2 of paths) {
239
239
  const text5 = readText3(path2);
240
240
  if (text5 === void 0) continue;
@@ -247,7 +247,7 @@ function claudeSettingsFacts(env, cwd, model, readText3) {
247
247
  if (parsed2 === null || typeof parsed2 !== "object" || Array.isArray(parsed2))
248
248
  continue;
249
249
  const settings = parsed2;
250
- const effort = modelEffortOf(settings["modelSettings"], model ?? word(settings["model"])) ?? word(settings["effortLevel"]);
250
+ const effort = modelEffortOf(settings["modelSettings"], model ?? word2(settings["model"])) ?? word2(settings["effortLevel"]);
251
251
  if (effort !== void 0) facts.effort_level_setting = effort;
252
252
  const thinking = settings["alwaysThinkingEnabled"];
253
253
  if (typeof thinking === "boolean") facts.always_thinking_enabled = thinking;
@@ -6928,7 +6928,7 @@ function opensPullRequest(tool) {
6928
6928
  return last === "create_pull_request" || last === "pull_request_create";
6929
6929
  }
6930
6930
  function nameWords(tool) {
6931
- return tool.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length > 0);
6931
+ return tool.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().split(/[^a-z0-9]+/).filter((word2) => word2.length > 0);
6932
6932
  }
6933
6933
  function mcpToolName(toolName3) {
6934
6934
  const mcp = /^mcp__([^_]+(?:_[^_]+)*?)__(.+)$/.exec(toolName3);
@@ -6939,7 +6939,7 @@ function mcpToolName(toolName3) {
6939
6939
  }
6940
6940
  function mcpToolMutates(tool) {
6941
6941
  const verb = nameWords(tool).find(
6942
- (word) => READ_VERBS.has(word) || WRITE_VERBS.has(word)
6942
+ (word2) => READ_VERBS.has(word2) || WRITE_VERBS.has(word2)
6943
6943
  );
6944
6944
  return verb === void 0 || WRITE_VERBS.has(verb);
6945
6945
  }
@@ -7096,7 +7096,7 @@ function classifySimpleEffect(command2) {
7096
7096
  if (words2 === void 0) {
7097
7097
  return void 0;
7098
7098
  }
7099
- const start = words2.findIndex((word) => !ASSIGNMENT.test(word));
7099
+ const start = words2.findIndex((word2) => !ASSIGNMENT.test(word2));
7100
7100
  if (start === -1) {
7101
7101
  return void 0;
7102
7102
  }
@@ -7236,7 +7236,7 @@ function classifyTool(toolName3, toolInput) {
7236
7236
  function shellLineOf(input) {
7237
7237
  const command2 = input["command"];
7238
7238
  if (typeof command2 === "string") return command2;
7239
- if (!Array.isArray(command2) || !command2.every((word) => typeof word === "string"))
7239
+ if (!Array.isArray(command2) || !command2.every((word2) => typeof word2 === "string"))
7240
7240
  return void 0;
7241
7241
  const [program2, flag2, script] = command2;
7242
7242
  if (command2.length === 3 && program2 !== void 0 && ARGV_SHELLS.has(posix.basename(program2)) && /^-[a-z]*c$/.test(flag2 ?? ""))
@@ -7526,9 +7526,9 @@ function leadingWords(piece) {
7526
7526
  function ghSubcommand(words2) {
7527
7527
  const found = [];
7528
7528
  for (let j = 1; j < words2.length && found.length < 2; j += 1) {
7529
- const word = words2[j];
7530
- if (GH_VALUE_FLAGS.has(word)) j += 1;
7531
- else if (!word.startsWith("-")) found.push(word);
7529
+ const word2 = words2[j];
7530
+ if (GH_VALUE_FLAGS.has(word2)) j += 1;
7531
+ else if (!word2.startsWith("-")) found.push(word2);
7532
7532
  }
7533
7533
  return [found[0], found[1]];
7534
7534
  }
@@ -7538,15 +7538,15 @@ function ghApiCall(words2) {
7538
7538
  let path2;
7539
7539
  const start = words2.indexOf("api");
7540
7540
  for (let j = start + 1; j < words2.length; j += 1) {
7541
- const word = words2[j];
7542
- const eq = word.indexOf("=");
7543
- const flag2 = word.startsWith("--") && eq > 0 ? word.slice(0, eq) : word;
7544
- const inline = flag2 !== word ? word.slice(eq + 1) : void 0;
7545
- if (/^-X[A-Za-z]+$/.test(word)) {
7546
- method = word.slice(2);
7541
+ const word2 = words2[j];
7542
+ const eq = word2.indexOf("=");
7543
+ const flag2 = word2.startsWith("--") && eq > 0 ? word2.slice(0, eq) : word2;
7544
+ const inline = flag2 !== word2 ? word2.slice(eq + 1) : void 0;
7545
+ if (/^-X[A-Za-z]+$/.test(word2)) {
7546
+ method = word2.slice(2);
7547
7547
  continue;
7548
7548
  }
7549
- if (/^-[fF]./.test(word)) {
7549
+ if (/^-[fF]./.test(word2)) {
7550
7550
  body2 = true;
7551
7551
  continue;
7552
7552
  }
@@ -7557,8 +7557,8 @@ function ghApiCall(words2) {
7557
7557
  if (API_BODY_FLAGS.has(flag2)) body2 = true;
7558
7558
  continue;
7559
7559
  }
7560
- if (word.startsWith("-")) continue;
7561
- path2 ??= word;
7560
+ if (word2.startsWith("-")) continue;
7561
+ path2 ??= word2;
7562
7562
  }
7563
7563
  return {
7564
7564
  method: (method ?? (body2 ? "POST" : "GET")).toUpperCase(),
@@ -7567,7 +7567,7 @@ function ghApiCall(words2) {
7567
7567
  }
7568
7568
  function hasBody(words2) {
7569
7569
  return words2.some(
7570
- (word) => word === "-b" || word === "--body" || word === "-F" || word === "--body-file" || word.startsWith("--body=") || word.startsWith("--body-file=")
7570
+ (word2) => word2 === "-b" || word2 === "--body" || word2 === "-F" || word2 === "--body-file" || word2.startsWith("--body=") || word2.startsWith("--body-file=")
7571
7571
  );
7572
7572
  }
7573
7573
  function shellWritesComment(command2) {
@@ -7653,7 +7653,7 @@ function githubTextWrites(command2) {
7653
7653
  }).map(({ text: text5 }) => text5);
7654
7654
  }
7655
7655
  function mcpWritesGithubText(tool) {
7656
- return nameWords(tool).some((word) => GITHUB_TEXT_WORDS.has(word)) && mcpToolMutates(tool);
7656
+ return nameWords(tool).some((word2) => GITHUB_TEXT_WORDS.has(word2)) && mcpToolMutates(tool);
7657
7657
  }
7658
7658
  function strings(value, out = [], depth = 0) {
7659
7659
  if (depth > 8) return out;
@@ -8394,13 +8394,13 @@ function shellSegments(command2, options = {}) {
8394
8394
  }
8395
8395
  function heredocBodiesEnd(command2, start, heredocs) {
8396
8396
  let at2 = start;
8397
- for (const { word, tabs } of heredocs) {
8397
+ for (const { word: word2, tabs } of heredocs) {
8398
8398
  while (at2 < command2.length) {
8399
8399
  const newline2 = command2.indexOf("\n", at2);
8400
8400
  const end = newline2 === -1 ? command2.length : newline2;
8401
8401
  const line = command2.slice(at2, end);
8402
8402
  at2 = newline2 === -1 ? command2.length : newline2 + 1;
8403
- if ((tabs ? line.replace(/^\t+/, "") : line) === word) break;
8403
+ if ((tabs ? line.replace(/^\t+/, "") : line) === word2) break;
8404
8404
  }
8405
8405
  }
8406
8406
  return at2;
@@ -9423,7 +9423,7 @@ function posixWords(line) {
9423
9423
  }
9424
9424
  function collectorFiles(hookCommand2) {
9425
9425
  return (posixWords(hookCommand2) ?? []).filter(
9426
- (word) => word.value.startsWith("/")
9426
+ (word2) => word2.value.startsWith("/")
9427
9427
  );
9428
9428
  }
9429
9429
  function skipWhenCollectorAbsent(hookCommand2, command2, answer) {
@@ -9940,6 +9940,18 @@ function cursorToolName(name, mcpServer) {
9940
9940
  }
9941
9941
  return CURSOR_TOOL_NAMES[name] ?? name;
9942
9942
  }
9943
+ function cursorEffort(modelParams) {
9944
+ if (!Array.isArray(modelParams)) return void 0;
9945
+ const setting2 = modelParams.find(
9946
+ (entry2) => isRecord(entry2) && entry2["id"] === "effort"
9947
+ );
9948
+ const value = isRecord(setting2) ? setting2["value"] : void 0;
9949
+ const word2 = typeof value === "string" ? value.trim().toLowerCase() : "";
9950
+ return EFFORT_WORDS.has(word2) ? word2 : null;
9951
+ }
9952
+ function followsStopHook(loopCount) {
9953
+ return typeof loopCount === "number" && Number.isInteger(loopCount) && loopCount > 0;
9954
+ }
9943
9955
  function translateCursorPayload(raw) {
9944
9956
  if (!isRecord(raw)) return raw;
9945
9957
  const event = nonEmptyString(raw["hook_event_name"]);
@@ -9950,7 +9962,9 @@ function translateCursorPayload(raw) {
9950
9962
  session_id: cursorSessionId,
9951
9963
  generation_id: generationId,
9952
9964
  user_email: _email,
9953
- model_params: _modelParams,
9965
+ model,
9966
+ model_id: modelId2,
9967
+ model_params: modelParams,
9954
9968
  cwd,
9955
9969
  tool_name: toolName3,
9956
9970
  tool_output: toolOutput,
@@ -9983,6 +9997,20 @@ function translateCursorPayload(raw) {
9983
9997
  if (workspaceRoots !== void 0) out["workspace_roots"] = workspaceRoots;
9984
9998
  if (nonEmptyString(cursorSessionId) !== void 0)
9985
9999
  out["cursor_session_id"] = cursorSessionId;
10000
+ const modelIdText = nonEmptyString(modelId2);
10001
+ const modelName = nonEmptyString(model);
10002
+ if (modelIdText !== void 0) {
10003
+ out["model"] = modelIdText;
10004
+ if (modelName !== void 0 && modelName !== modelIdText)
10005
+ out["cursor_model"] = modelName;
10006
+ } else if (model !== void 0) out["model"] = model;
10007
+ const effort = cursorEffort(modelParams);
10008
+ if (effort !== void 0) out["effort"] = effort;
10009
+ if (event === "stop" && followsStopHook(raw["loop_count"]) && raw["prompt_source"] === void 0 && raw["prompt_origin"] === void 0) {
10010
+ out["prompt_source"] = "system";
10011
+ out["prompt_origin"] = { kind: "stop_hook" };
10012
+ out["prompt_source_derived"] = "loop_count";
10013
+ }
9986
10014
  if (typeof toolName3 === "string") {
9987
10015
  out["tool_name"] = cursorToolName(toolName3, raw["mcp_server_name"]);
9988
10016
  out["cursor_tool_name"] = toolName3;
@@ -10081,7 +10109,7 @@ function cursorAnswer(response, claudeEvent) {
10081
10109
  return emit({});
10082
10110
  }
10083
10111
  }
10084
- var CURSOR_TO_CLAUDE_EVENT, CURSOR_HOOK_EVENTS, CURSOR_ENFORCEMENT_EVENTS, CURSOR_TOOL_NAMES, ASK_SUFFIX;
10112
+ var CURSOR_TO_CLAUDE_EVENT, CURSOR_HOOK_EVENTS, CURSOR_ENFORCEMENT_EVENTS, CURSOR_TOOL_NAMES, EFFORT_WORDS, ASK_SUFFIX;
10085
10113
  var init_cursor_adapter = __esm({
10086
10114
  "../../packages/tacho/src/claude-code/cursor-adapter.ts"() {
10087
10115
  "use strict";
@@ -10117,6 +10145,15 @@ var init_cursor_adapter = __esm({
10117
10145
  Grep: "Grep",
10118
10146
  Task: "Task"
10119
10147
  };
10148
+ EFFORT_WORDS = /* @__PURE__ */ new Set([
10149
+ "none",
10150
+ "minimal",
10151
+ "low",
10152
+ "medium",
10153
+ "high",
10154
+ "xhigh",
10155
+ "max"
10156
+ ]);
10120
10157
  ASK_SUFFIX = "A person has to approve this in Oxagen before it can run; Cursor does not enforce an ask at preToolUse.";
10121
10158
  }
10122
10159
  });
@@ -11473,6 +11510,68 @@ var init_process_scan = __esm({
11473
11510
  }
11474
11511
  });
11475
11512
 
11513
+ // ../../packages/tacho/src/host/stella-prompt-source.ts
11514
+ function withoutSenderMembers(payload) {
11515
+ const out = {};
11516
+ for (const [member3, value] of Object.entries(payload))
11517
+ if (!SENDER_MEMBERS.has(member3)) out[member3] = value;
11518
+ return out;
11519
+ }
11520
+ function word(value) {
11521
+ if (typeof value !== "string") return void 0;
11522
+ const text5 = value.trim();
11523
+ return text5.length > 0 ? text5 : void 0;
11524
+ }
11525
+ function originKindOf(value) {
11526
+ return isRecord(value) ? word(value["kind"]) : word(value);
11527
+ }
11528
+ function isSelfDriving(text5) {
11529
+ return text5 !== void 0 && text5.toLowerCase().replace(/[-_]/g, "") === "selfdriving";
11530
+ }
11531
+ function either(payload, tacho, claudeCode) {
11532
+ return payload[tacho] !== void 0 ? payload[tacho] : payload[claudeCode];
11533
+ }
11534
+ function stellaPromptSource(payload) {
11535
+ const source = word(either(payload, "prompt_source", "promptSource"));
11536
+ const kind = originKindOf(either(payload, "prompt_origin", "origin"));
11537
+ if (isSelfDriving(source) || isSelfDriving(kind))
11538
+ return { prompt_source: "system", prompt_origin: { kind: "heartbeat" } };
11539
+ const sender = {};
11540
+ if (source !== void 0 && PROMPT_SOURCES.has(source))
11541
+ sender.prompt_source = source;
11542
+ if (kind !== void 0 && PROMPT_ORIGIN_KINDS.has(kind))
11543
+ sender.prompt_origin = { kind };
11544
+ return sender.prompt_source !== void 0 || sender.prompt_origin !== void 0 ? sender : void 0;
11545
+ }
11546
+ var PROMPT_SOURCES, PROMPT_ORIGIN_KINDS, SENDER_MEMBERS;
11547
+ var init_stella_prompt_source = __esm({
11548
+ "../../packages/tacho/src/host/stella-prompt-source.ts"() {
11549
+ "use strict";
11550
+ init_context();
11551
+ PROMPT_SOURCES = /* @__PURE__ */ new Set([
11552
+ "typed",
11553
+ "queued",
11554
+ "system",
11555
+ "sdk"
11556
+ ]);
11557
+ PROMPT_ORIGIN_KINDS = /* @__PURE__ */ new Set([
11558
+ "human",
11559
+ "peer",
11560
+ "coordinator",
11561
+ "task-notification",
11562
+ "auto-continuation",
11563
+ "heartbeat",
11564
+ "stop_hook"
11565
+ ]);
11566
+ SENDER_MEMBERS = /* @__PURE__ */ new Set([
11567
+ "prompt_source",
11568
+ "promptSource",
11569
+ "prompt_origin",
11570
+ "origin"
11571
+ ]);
11572
+ }
11573
+ });
11574
+
11476
11575
  // ../../packages/tacho/src/claude-code/payload-members.ts
11477
11576
  function isRecord2(value) {
11478
11577
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -11764,10 +11863,14 @@ function stellaSessionId(pid, instance) {
11764
11863
  function translateStellaPayload(raw, pid, instance) {
11765
11864
  if (!isRecord2(raw) || typeof raw["event"] !== "string") return raw;
11766
11865
  const { event, tool, toolResult: toolResult2, finalText, subagentResult, ...rest } = raw;
11866
+ const prompt = event === "UserPromptSubmit";
11867
+ const sender = prompt ? stellaPromptSource(raw) : void 0;
11767
11868
  const out = {
11768
- ...rest,
11869
+ ...prompt ? withoutSenderMembers(rest) : rest,
11769
11870
  session_id: stellaSessionId(pid, instance),
11770
- hook_event_name: event
11871
+ hook_event_name: event,
11872
+ ...sender?.prompt_source !== void 0 ? { prompt_source: sender.prompt_source } : {},
11873
+ ...sender?.prompt_origin !== void 0 ? { prompt_origin: { ...sender.prompt_origin } } : {}
11771
11874
  };
11772
11875
  if (isRecord2(tool)) {
11773
11876
  if (typeof tool["name"] === "string") {
@@ -11859,6 +11962,7 @@ var init_stella_adapter = __esm({
11859
11962
  init_digest();
11860
11963
  init_fs();
11861
11964
  init_process_scan();
11965
+ init_stella_prompt_source();
11862
11966
  init_context();
11863
11967
  init_payload_members();
11864
11968
  SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash"]);
@@ -12659,6 +12763,24 @@ function toolCallContent(input) {
12659
12763
  )
12660
12764
  };
12661
12765
  }
12766
+ function promptSenderOf(input) {
12767
+ const source = str2(input["prompt_source"]);
12768
+ const origin = promptOriginOf(input["prompt_origin"]);
12769
+ const body2 = {
12770
+ ...source !== void 0 && source.length <= 512 ? { prompt_source: source } : {},
12771
+ ...origin !== void 0 ? { prompt_origin: origin } : {}
12772
+ };
12773
+ const derived = str2(input["prompt_source_derived"]);
12774
+ return {
12775
+ body: body2,
12776
+ attrs: derived !== void 0 && Object.keys(body2).length > 0 ? { [PROMPT_SOURCE_DERIVED_ATTR]: attrValue(derived) } : {}
12777
+ };
12778
+ }
12779
+ function effortFactOf(value) {
12780
+ if (value === null) return { effort: void 0 };
12781
+ const effort = str2(value);
12782
+ return effort !== void 0 ? { effort } : {};
12783
+ }
12662
12784
  function normalizeHook(raw, env, options) {
12663
12785
  const input = hookInputSchema.parse(raw);
12664
12786
  const repairs = payloadRepairs(raw);
@@ -12680,6 +12802,7 @@ function normalizeHook(raw, env, options) {
12680
12802
  },
12681
12803
  context: {
12682
12804
  ...contextFactsFromEnv(env),
12805
+ ...effortFactOf(input["effort"]),
12683
12806
  ...input.cwd !== void 0 ? { cwd: input.cwd } : {},
12684
12807
  ...input.permission_mode !== void 0 ? { permission_mode: input.permission_mode } : {},
12685
12808
  ...str2(input["model"]) !== void 0 ? { model: str2(input["model"]) } : {}
@@ -12737,12 +12860,10 @@ function normalizeHook(raw, env, options) {
12737
12860
  case "UserPromptSubmit":
12738
12861
  case "UserPromptExpansion": {
12739
12862
  const prompt = str2(input["prompt"]) ?? str2(input["user_input"]);
12740
- const promptSource = str2(input["prompt_source"]);
12741
- const promptOrigin = promptOriginOf(input["prompt_origin"]);
12863
+ const sender = promptSenderOf(input);
12742
12864
  const body2 = {
12743
12865
  ...prompt !== void 0 ? { prompt_digest: digestText(prompt), prompt_length: prompt.length } : {},
12744
- ...promptSource !== void 0 && promptSource.length <= 512 ? { prompt_source: promptSource } : {},
12745
- ...promptOrigin !== void 0 ? { prompt_origin: promptOrigin } : {},
12866
+ ...sender.body,
12746
12867
  ...str2(input["command_name"]) !== void 0 ? { command_name: str2(input["command_name"]) } : {},
12747
12868
  ...str2(input["command_input"]) !== void 0 ? {
12748
12869
  command_input_digest: digestText(
@@ -12752,15 +12873,14 @@ function normalizeHook(raw, env, options) {
12752
12873
  };
12753
12874
  const kind = input.hook_event_name === "UserPromptSubmit" ? "turn_start" : "oxagen:message";
12754
12875
  const issueRefs = kind === "turn_start" && prompt !== void 0 && workOrderIdOf(env[WORK_ORDER_ENV]) === void 0 ? promptIssueRefs(prompt) : [];
12876
+ const attrs2 = {
12877
+ ...sender.attrs,
12878
+ ...issueRefs.length > 0 ? { [PROMPT_ISSUE_REFS_ATTR]: issueRefs.join(" ") } : {}
12879
+ };
12755
12880
  return [
12756
12881
  draft(kind, body2, {
12757
12882
  ...prompt !== void 0 ? { content: textContent(prompt) } : {},
12758
- ...issueRefs.length > 0 ? {
12759
- attrs: {
12760
- ...base2.attrs,
12761
- [PROMPT_ISSUE_REFS_ATTR]: issueRefs.join(" ")
12762
- }
12763
- } : {}
12883
+ ...Object.keys(attrs2).length > 0 ? { attrs: { ...base2.attrs, ...attrs2 } } : {}
12764
12884
  })
12765
12885
  ];
12766
12886
  }
@@ -12902,11 +13022,15 @@ function normalizeHook(raw, env, options) {
12902
13022
  ...sessionCrons !== void 0 ? { session_crons: sessionCrons } : {}
12903
13023
  };
12904
13024
  if (input.hook_event_name === "Stop") {
13025
+ const sender = promptSenderOf(input);
12905
13026
  return [
12906
13027
  draft(
12907
13028
  "turn_end",
12908
- common,
12909
- last !== void 0 ? { content: textContent(last) } : {}
13029
+ { ...common, ...sender.body },
13030
+ {
13031
+ ...last !== void 0 ? { content: textContent(last) } : {},
13032
+ ...Object.keys(sender.attrs).length > 0 ? { attrs: { ...base2.attrs, ...sender.attrs } } : {}
13033
+ }
12910
13034
  )
12911
13035
  ];
12912
13036
  }
@@ -13136,7 +13260,7 @@ function normalizeHook(raw, env, options) {
13136
13260
  }
13137
13261
  }
13138
13262
  }
13139
- var textMember, pathMember, hookInputSchema, PAYLOAD_REPAIRS_ATTR, TYPED_MEMBERS, TURN_END_REASON_ATTR, PROMOTED;
13263
+ var textMember, pathMember, hookInputSchema, PAYLOAD_REPAIRS_ATTR, TYPED_MEMBERS, TURN_END_REASON_ATTR, PROMPT_SOURCE_DERIVED_ATTR, PROMOTED;
13140
13264
  var init_hooks = __esm({
13141
13265
  "../../packages/tacho/src/claude-code/hooks.ts"() {
13142
13266
  "use strict";
@@ -13196,6 +13320,7 @@ var init_hooks = __esm({
13196
13320
  ["transcript_path", "path"]
13197
13321
  ];
13198
13322
  TURN_END_REASON_ATTR = "oxagen.turn_end_reason";
13323
+ PROMPT_SOURCE_DERIVED_ATTR = "oxagen.prompt_source_derived";
13199
13324
  PROMOTED = /* @__PURE__ */ new Set([
13200
13325
  "session_id",
13201
13326
  "hook_event_name",
@@ -13216,6 +13341,8 @@ var init_hooks = __esm({
13216
13341
  "user_input",
13217
13342
  "prompt_source",
13218
13343
  "prompt_origin",
13344
+ "prompt_source_derived",
13345
+ "effort",
13219
13346
  "source",
13220
13347
  "trigger",
13221
13348
  "reason",
@@ -27199,7 +27326,7 @@ import { readFileSync as readFileSync10 } from "node:fs";
27199
27326
  import { dirname as dirname9, resolve as resolve3 } from "node:path";
27200
27327
  import { fileURLToPath } from "node:url";
27201
27328
  function readVersion() {
27202
- if (true) return "2.1.6-143";
27329
+ if (true) return "2.1.6-152";
27203
27330
  try {
27204
27331
  const here = dirname9(fileURLToPath(import.meta.url));
27205
27332
  const pkg = JSON.parse(
@@ -27380,7 +27507,7 @@ function oxagenRuntimeCommands(entry2 = process.argv[1], env = process.env, node
27380
27507
  } else {
27381
27508
  return runtimeCommands(void 0, env, nodePath, platform, false, exists);
27382
27509
  }
27383
- const line = prefix.map((word) => shellQuote2(word, platform)).join(" ");
27510
+ const line = prefix.map((word2) => shellQuote2(word2, platform)).join(" ");
27384
27511
  const binDir = P.dirname(prefix[prefix.length - 1]);
27385
27512
  const transient = transientBinDir(binDir, env);
27386
27513
  return {
@@ -43242,11 +43369,11 @@ function lastCommandName(command2) {
43242
43369
  if (segment.trim() !== "") last = segment;
43243
43370
  const words2 = last.trim().split(/\s+/);
43244
43371
  const at2 = words2.findIndex(
43245
- (word2) => !/^[A-Za-z_]\w*=/.test(word2) && !COMMAND_PREFIXES.has(word2)
43372
+ (word3) => !/^[A-Za-z_]\w*=/.test(word3) && !COMMAND_PREFIXES.has(word3)
43246
43373
  );
43247
- const [word, next] = at2 < 0 ? [] : words2.slice(at2);
43248
- if (word === void 0 || word === "") return void 0;
43249
- const name = word.slice(word.lastIndexOf("/") + 1);
43374
+ const [word2, next] = at2 < 0 ? [] : words2.slice(at2);
43375
+ if (word2 === void 0 || word2 === "") return void 0;
43376
+ const name = word2.slice(word2.lastIndexOf("/") + 1);
43250
43377
  return name === "git" && next === "grep" ? "git grep" : name;
43251
43378
  }
43252
43379
  function isSearchNoMatch(toolInput, error) {
@@ -47503,8 +47630,8 @@ function memoryFileOf(path2, locations, home) {
47503
47630
  }
47504
47631
  return void 0;
47505
47632
  }
47506
- function pathOf(word, cwd, home) {
47507
- const expanded = /^(?:~|\$HOME|\$\{HOME\})\//.test(word) ? join42(home, word.slice(word.indexOf("/") + 1)) : word;
47633
+ function pathOf(word2, cwd, home) {
47634
+ const expanded = /^(?:~|\$HOME|\$\{HOME\})\//.test(word2) ? join42(home, word2.slice(word2.indexOf("/") + 1)) : word2;
47508
47635
  if (isAbsolute10(expanded)) return expanded;
47509
47636
  if (cwd !== void 0 && isAbsolute10(cwd)) return resolve15(cwd, expanded);
47510
47637
  return void 0;
@@ -47521,9 +47648,9 @@ function memoryReadsOf(toolName3, toolInput, cwd, home, locations) {
47521
47648
  words2 = (stringMember(toolInput, "command") ?? "").slice(0, BASH_COMMAND_MAX_CHARS).split(/[\s;|&<>()'"`=]+/);
47522
47649
  else return [];
47523
47650
  const files2 = /* @__PURE__ */ new Map();
47524
- for (const word of words2) {
47525
- if (word.length === 0 || /[*?[]/.test(word)) continue;
47526
- const path2 = pathOf(word, cwd, home);
47651
+ for (const word2 of words2) {
47652
+ if (word2.length === 0 || /[*?[]/.test(word2)) continue;
47653
+ const path2 = pathOf(word2, cwd, home);
47527
47654
  if (path2 === void 0) continue;
47528
47655
  const file = memoryFileOf(path2, locations, home);
47529
47656
  if (file !== void 0) files2.set(`${file.harness}
@@ -49040,9 +49167,9 @@ var init_envelope2 = __esm({
49040
49167
  });
49041
49168
 
49042
49169
  // ../../packages/tacho/src/collector/local-servers/launch.ts
49043
- function flagOf(word) {
49044
- const equals2 = word.indexOf("=");
49045
- return equals2 < 0 ? word : word.slice(0, equals2);
49170
+ function flagOf(word2) {
49171
+ const equals2 = word2.indexOf("=");
49172
+ return equals2 < 0 ? word2 : word2.slice(0, equals2);
49046
49173
  }
49047
49174
  function mountSource(flag2, value) {
49048
49175
  if (flag2 !== "--mount") return value.split(":")[0];
@@ -49053,10 +49180,10 @@ function mountSource(flag2, value) {
49053
49180
  return void 0;
49054
49181
  }
49055
49182
  function hostRootMount(words2) {
49056
- for (const [index, word] of words2.entries()) {
49057
- const flag2 = flagOf(word);
49183
+ for (const [index, word2] of words2.entries()) {
49184
+ const flag2 = flagOf(word2);
49058
49185
  if (!OCI_MOUNT_FLAGS.has(flag2)) continue;
49059
- const value = word.length > flag2.length ? word.slice(flag2.length + 1) : words2[index + 1];
49186
+ const value = word2.length > flag2.length ? word2.slice(flag2.length + 1) : words2[index + 1];
49060
49187
  const source = value === void 0 ? void 0 : mountSource(flag2, value);
49061
49188
  if (source !== void 0 && /^\/+$/.test(source)) return flag2;
49062
49189
  }
@@ -49076,12 +49203,12 @@ function launchShapeProblem(spec) {
49076
49203
  ...runner.flags,
49077
49204
  ...type === "oci" ? spec.env.flatMap((name) => ["-e", name]) : []
49078
49205
  ];
49079
- if (lead.some((word, index) => spec.args[index] !== word)) {
49206
+ if (lead.some((word2, index) => spec.args[index] !== word2)) {
49080
49207
  return `a ${type} launch starts with ${lead.join(" ")}`;
49081
49208
  }
49082
49209
  const rest = spec.args.slice(lead.length);
49083
49210
  const forbidden = new Set(FORBIDDEN_RUNNER_FLAGS[type]);
49084
- const runtimeFlag = (end) => rest.slice(0, end).find((word) => forbidden.has(flagOf(word)));
49211
+ const runtimeFlag = (end) => rest.slice(0, end).find((word2) => forbidden.has(flagOf(word2)));
49085
49212
  if (runner.pin === "file") {
49086
49213
  const at3 = rest.indexOf("--from");
49087
49214
  const url = at3 < 0 ? void 0 : rest[at3 + 1];
@@ -50140,8 +50267,8 @@ function codegraphDownloadUrls(version2, asset, host = DOWNLOADS_HOST) {
50140
50267
  ];
50141
50268
  }
50142
50269
  function digestOf2(text5) {
50143
- const word = text5.trim().split(/\s+/)[0]?.toLowerCase();
50144
- return word !== void 0 && /^[0-9a-f]{64}$/.test(word) ? word : void 0;
50270
+ const word2 = text5.trim().split(/\s+/)[0]?.toLowerCase();
50271
+ return word2 !== void 0 && /^[0-9a-f]{64}$/.test(word2) ? word2 : void 0;
50145
50272
  }
50146
50273
  async function fetchOne(input, url) {
50147
50274
  const timeoutMs = input.timeoutMs ?? DOWNLOAD_TIMEOUT_MS;
@@ -61870,8 +61997,8 @@ var require_option = __commonJS({
61870
61997
  }
61871
61998
  };
61872
61999
  function camelcase(str3) {
61873
- return str3.split("-").reduce((str4, word) => {
61874
- return str4 + word[0].toUpperCase() + word.slice(1);
62000
+ return str3.split("-").reduce((str4, word2) => {
62001
+ return str4 + word2[0].toUpperCase() + word2.slice(1);
61875
62002
  });
61876
62003
  }
61877
62004
  function splitOptionFlags(flags) {
@@ -61929,12 +62056,12 @@ var require_suggestSimilar = __commonJS({
61929
62056
  }
61930
62057
  return d[a.length][b3.length];
61931
62058
  }
61932
- function suggestSimilar(word, candidates) {
62059
+ function suggestSimilar(word2, candidates) {
61933
62060
  if (!candidates || candidates.length === 0) return "";
61934
62061
  candidates = Array.from(new Set(candidates));
61935
- const searchingOptions = word.startsWith("--");
62062
+ const searchingOptions = word2.startsWith("--");
61936
62063
  if (searchingOptions) {
61937
- word = word.slice(2);
62064
+ word2 = word2.slice(2);
61938
62065
  candidates = candidates.map((candidate) => candidate.slice(2));
61939
62066
  }
61940
62067
  let similar = [];
@@ -61942,8 +62069,8 @@ var require_suggestSimilar = __commonJS({
61942
62069
  const minSimilarity = 0.4;
61943
62070
  candidates.forEach((candidate) => {
61944
62071
  if (candidate.length <= 1) return;
61945
- const distance2 = editDistance(word, candidate);
61946
- const length = Math.max(word.length, candidate.length);
62072
+ const distance2 = editDistance(word2, candidate);
62073
+ const length = Math.max(word2.length, candidate.length);
61947
62074
  const similarity = (length - distance2) / length;
61948
62075
  if (similarity > minSimilarity) {
61949
62076
  if (distance2 < bestDistance) {
@@ -66320,7 +66447,7 @@ var init_package = __esm({
66320
66447
  "package.json"() {
66321
66448
  package_default = {
66322
66449
  name: "@oxagen/cli",
66323
- version: "2.1.6-143",
66450
+ version: "2.1.6-152",
66324
66451
  description: "Oxagen governance CLI \u2014 spend, lineage, grounding, credentials, and audit (agentic coding lives in the stella CLI)",
66325
66452
  type: "module",
66326
66453
  main: "./dist/index.js",
@@ -68243,6 +68370,175 @@ var init_budget2 = __esm({
68243
68370
  }
68244
68371
  });
68245
68372
 
68373
+ // src/commands/no-progress.ts
68374
+ var no_progress_exports = {};
68375
+ __export(no_progress_exports, {
68376
+ NO_PROGRESS_CAPABILITIES: () => NO_PROGRESS_CAPABILITIES,
68377
+ monthToDate: () => monthToDate,
68378
+ noProgressSet: () => noProgressSet,
68379
+ noProgressShow: () => noProgressShow
68380
+ });
68381
+ function monthToDate(now) {
68382
+ const to = now.toISOString().slice(0, 10);
68383
+ return { from: `${to.slice(0, 7)}-01`, to };
68384
+ }
68385
+ function limitLine(limit) {
68386
+ return `${limit.repeats} identical calls in a row, ${limit.mode} mode`;
68387
+ }
68388
+ async function noProgressShow(opts = {}, writer = stdoutWriter, now = /* @__PURE__ */ new Date()) {
68389
+ const out = createOutput({ json: opts.json }, writer);
68390
+ if (opts.from === void 0 !== (opts.to === void 0)) {
68391
+ process.exitCode = 2;
68392
+ out.error("Name both --from and --to, or neither.", "usage");
68393
+ return;
68394
+ }
68395
+ const period = opts.from !== void 0 && opts.to !== void 0 ? { from: opts.from, to: opts.to } : monthToDate(now);
68396
+ if (!DAY.test(period.from) || !DAY.test(period.to)) {
68397
+ process.exitCode = 2;
68398
+ out.error("Give --from and --to as YYYY-MM-DD days.", "usage");
68399
+ return;
68400
+ }
68401
+ let result2;
68402
+ try {
68403
+ result2 = await apiPostOrThrow(
68404
+ "spend/no-progress-limit",
68405
+ { period }
68406
+ );
68407
+ } catch (err2) {
68408
+ out.error(err2, "api");
68409
+ return;
68410
+ }
68411
+ if (out.isJson) {
68412
+ out.data(result2);
68413
+ return;
68414
+ }
68415
+ writer.write(
68416
+ result2.limit === null ? "No-progress limit: not set. The check does not run in this workspace." : `No-progress limit: ${limitLine(result2.limit)}${result2.source === "default" ? " (the default)" : ""}.`
68417
+ );
68418
+ if (result2.pendingPullRequest !== null) {
68419
+ writer.write(
68420
+ `Steering PR #${result2.pendingPullRequest.number} is open: ${result2.pendingPullRequest.url}`
68421
+ );
68422
+ }
68423
+ writer.write("");
68424
+ if (result2.hits.length === 0) {
68425
+ writer.write(
68426
+ `No run reached the limit from ${result2.period.from} to ${result2.period.to}.`
68427
+ );
68428
+ return;
68429
+ }
68430
+ writer.write(
68431
+ `Runs that reached it from ${result2.period.from} to ${result2.period.to}:`
68432
+ );
68433
+ writer.write("");
68434
+ printTable(
68435
+ ["RUN", "AGENT", "TOOL", "CALLS", "OUTCOME", "DETECTED"],
68436
+ result2.hits.map((hit) => [
68437
+ hit.runId,
68438
+ hit.agentKey ?? "-",
68439
+ hit.tool,
68440
+ String(hit.repeats),
68441
+ hit.outcome === "paused" ? "paused" : "would pause",
68442
+ hit.detectedAt
68443
+ ]),
68444
+ writer
68445
+ );
68446
+ if (result2.more) {
68447
+ writer.write("");
68448
+ writer.write("More runs reached it. Narrow the range to see them.");
68449
+ }
68450
+ }
68451
+ async function noProgressSet(opts, writer = stdoutWriter) {
68452
+ const out = createOutput({ json: opts.json }, writer);
68453
+ const named = opts.repeats !== void 0 || opts.mode !== void 0;
68454
+ if (opts.clear === true && named) {
68455
+ process.exitCode = 2;
68456
+ out.error("--clear takes no --repeats or --mode. Send it alone.", "usage");
68457
+ return;
68458
+ }
68459
+ if (opts.clear !== true && !named) {
68460
+ process.exitCode = 2;
68461
+ out.error(
68462
+ "Name --repeats, --mode, or both. To return to the default, use --clear.",
68463
+ "usage"
68464
+ );
68465
+ return;
68466
+ }
68467
+ let body2;
68468
+ if (opts.clear === true) {
68469
+ body2 = { clear: true };
68470
+ } else {
68471
+ body2 = {};
68472
+ if (opts.repeats !== void 0) {
68473
+ const repeats = Number(opts.repeats);
68474
+ if (!Number.isInteger(repeats) || repeats < 2) {
68475
+ process.exitCode = 2;
68476
+ out.error(
68477
+ `Invalid --repeats "${opts.repeats}". Give a whole number of 2 or more.`,
68478
+ "usage"
68479
+ );
68480
+ return;
68481
+ }
68482
+ body2.repeats = repeats;
68483
+ }
68484
+ if (opts.mode !== void 0) {
68485
+ if (opts.mode !== "observe" && opts.mode !== "enforced") {
68486
+ process.exitCode = 2;
68487
+ out.error(
68488
+ `Invalid --mode "${opts.mode}". Use "observe" or "enforced".`,
68489
+ "usage"
68490
+ );
68491
+ return;
68492
+ }
68493
+ body2.mode = opts.mode;
68494
+ }
68495
+ }
68496
+ let result2;
68497
+ try {
68498
+ result2 = await apiPutOrThrow(
68499
+ "spend/no-progress-limit",
68500
+ body2
68501
+ );
68502
+ } catch (err2) {
68503
+ out.error(err2, "api");
68504
+ return;
68505
+ }
68506
+ if (out.isJson) {
68507
+ out.data(result2);
68508
+ return;
68509
+ }
68510
+ if (result2.status === "withdrawn" && result2.withdrawnPullRequest !== null) {
68511
+ const closed2 = result2.withdrawnPullRequest;
68512
+ writer.write(`\u2713 Closed steering PR #${closed2.number}: ${closed2.url}`);
68513
+ writer.write(" The limit in force is already the one you asked for.");
68514
+ return;
68515
+ }
68516
+ if (result2.status !== "proposed" || result2.steeringPullRequest === null) {
68517
+ writer.write("Nothing changed. The limit in force is already that one.");
68518
+ return;
68519
+ }
68520
+ const pr = result2.steeringPullRequest;
68521
+ writer.write(
68522
+ `\u2713 ${pr.reused ? "Updated" : "Opened"} steering PR #${pr.number}: ${pr.url}`
68523
+ );
68524
+ writer.write(
68525
+ result2.limit === null ? " When it merges, the default of 20 identical calls in observe mode applies again." : ` When it merges, the limit becomes ${limitLine(result2.limit)}.`
68526
+ );
68527
+ }
68528
+ var NO_PROGRESS_CAPABILITIES, DAY;
68529
+ var init_no_progress = __esm({
68530
+ "src/commands/no-progress.ts"() {
68531
+ init_api();
68532
+ init_capture_writer();
68533
+ init_output();
68534
+ NO_PROGRESS_CAPABILITIES = {
68535
+ get: "get_no_progress_limit",
68536
+ set: "set_no_progress_limit"
68537
+ };
68538
+ DAY = /^\d{4}-\d{2}-\d{2}$/;
68539
+ }
68540
+ });
68541
+
68246
68542
  // src/commands/price.ts
68247
68543
  var price_exports = {};
68248
68544
  __export(price_exports, {
@@ -85418,10 +85714,16 @@ var init_digest_shared = __esm({
85418
85714
  });
85419
85715
 
85420
85716
  // ../../packages/oxagen/src/contracts/run.shared.ts
85421
- var LATEST_ACTIVITY_MESSAGES, LATEST_ACTIVITY_TEXT_MAX;
85717
+ var IN_APP_AGENT_SURFACES, MODEL_CLASS_REPLAY_SURFACE, TACHO_SESSION_SURFACES, LATEST_ACTIVITY_MESSAGES, LATEST_ACTIVITY_TEXT_MAX;
85422
85718
  var init_run_shared = __esm({
85423
85719
  "../../packages/oxagen/src/contracts/run.shared.ts"() {
85424
85720
  "use strict";
85721
+ IN_APP_AGENT_SURFACES = ["chat", "api-chat"];
85722
+ MODEL_CLASS_REPLAY_SURFACE = "model_class_replay";
85723
+ TACHO_SESSION_SURFACES = [
85724
+ ...IN_APP_AGENT_SURFACES,
85725
+ MODEL_CLASS_REPLAY_SURFACE
85726
+ ];
85425
85727
  LATEST_ACTIVITY_MESSAGES = 5;
85426
85728
  LATEST_ACTIVITY_TEXT_MAX = 300;
85427
85729
  }
@@ -86256,7 +86558,7 @@ var init_toolbelt = __esm({
86256
86558
  });
86257
86559
 
86258
86560
  // ../../packages/oxagen/src/steering-repo/workspace.ts
86259
- var embeddingProviderSchema, embeddingsSchema, STELLA_ARCHIVE_AFTER_DAYS_MIN, STELLA_ARCHIVE_AFTER_DAYS_MAX, workspaceSchema;
86561
+ var embeddingProviderSchema, embeddingsSchema, STELLA_ARCHIVE_AFTER_DAYS_MIN, STELLA_ARCHIVE_AFTER_DAYS_MAX, noProgressModeSchema, NO_PROGRESS_REPEATS_MIN, NO_PROGRESS_REPEATS_MAX, noProgressLimitSchema, workspaceSchema;
86260
86562
  var init_workspace2 = __esm({
86261
86563
  "../../packages/oxagen/src/steering-repo/workspace.ts"() {
86262
86564
  "use strict";
@@ -86288,6 +86590,17 @@ var init_workspace2 = __esm({
86288
86590
  );
86289
86591
  STELLA_ARCHIVE_AFTER_DAYS_MIN = 1;
86290
86592
  STELLA_ARCHIVE_AFTER_DAYS_MAX = 365;
86593
+ noProgressModeSchema = external_exports.enum(["observe", "enforced"]);
86594
+ NO_PROGRESS_REPEATS_MIN = 2;
86595
+ NO_PROGRESS_REPEATS_MAX = 2147483647;
86596
+ noProgressLimitSchema = external_exports.object({
86597
+ repeats: external_exports.number().int().min(NO_PROGRESS_REPEATS_MIN).max(NO_PROGRESS_REPEATS_MAX).describe(
86598
+ "Identical calls in a row that stop a run counting as progress. 2 or more."
86599
+ ),
86600
+ mode: noProgressModeSchema.describe(
86601
+ "observe records each run that reaches the limit, and runs keep going. enforced also pauses the run at its next checkpoint."
86602
+ )
86603
+ }).strict();
86291
86604
  workspaceSchema = external_exports.object({
86292
86605
  schema: external_exports.literal("workspace/v1"),
86293
86606
  organization: organizationSlugSchema,
@@ -86314,6 +86627,11 @@ var init_workspace2 = __esm({
86314
86627
  )
86315
86628
  }).strict().optional(),
86316
86629
  embeddings: embeddingsSchema.optional(),
86630
+ limits: external_exports.object({
86631
+ no_progress: noProgressLimitSchema.optional().describe(
86632
+ "The no-progress limit. 20 identical calls in observe mode when unset."
86633
+ )
86634
+ }).strict().optional(),
86317
86635
  stella: external_exports.object({
86318
86636
  archive_after_days: external_exports.number().int().min(STELLA_ARCHIVE_AFTER_DAYS_MIN).max(STELLA_ARCHIVE_AFTER_DAYS_MAX).optional().describe(
86319
86637
  "Archive a Stella thread after this many days with no activity. 7 when unset."
@@ -87261,7 +87579,7 @@ var init_compile = __esm({
87261
87579
 
87262
87580
  // ../../packages/steering-check/src/checks/conflicts.ts
87263
87581
  function wordsOf(statement) {
87264
- return statement.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim().split(" ").filter((word) => word !== "");
87582
+ return statement.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim().split(" ").filter((word2) => word2 !== "");
87265
87583
  }
87266
87584
  function compared(entry2) {
87267
87585
  const words2 = wordsOf(entry2.statement);
@@ -87273,8 +87591,8 @@ function alike(a, b3) {
87273
87591
  return false;
87274
87592
  const [small, large] = a.set.size <= b3.set.size ? [a.set, b3.set] : [b3.set, a.set];
87275
87593
  let missed = 0;
87276
- for (const word of small) {
87277
- if (large.has(word)) continue;
87594
+ for (const word2 of small) {
87595
+ if (large.has(word2)) continue;
87278
87596
  missed += 1;
87279
87597
  if ((small.size - missed) / (large.size + missed) < NEAR_DUPLICATE_SIMILARITY)
87280
87598
  return false;
@@ -88074,6 +88392,7 @@ function ruleOf(issue, family, raw) {
88074
88392
  return "toolbelt-skill-only";
88075
88393
  if (field !== null && /^tools\.\d+$/.test(field)) return "tool-target";
88076
88394
  if (field === "stella.archive_after_days") return "stella-archive-days";
88395
+ if (field === "limits.no_progress.repeats") return "no-progress-repeats";
88077
88396
  if (message.startsWith("Invalid enum value")) return "enum-value";
88078
88397
  if (family === "record" && field === null) return "strict-yaml";
88079
88398
  return "file-schema";
@@ -88266,7 +88585,7 @@ function recordShape2(file) {
88266
88585
  return findings;
88267
88586
  }
88268
88587
  function wordCount(text5) {
88269
- return text5.split(/\s+/).filter((word) => word !== "").length;
88588
+ return text5.split(/\s+/).filter((word2) => word2 !== "").length;
88270
88589
  }
88271
88590
  function lineCount(text5) {
88272
88591
  return text5.replace(/\n$/, "").split("\n").length;
@@ -88442,6 +88761,10 @@ var init_schema2 = __esm({
88442
88761
  expected: "A whole number of days from 1 to 365.",
88443
88762
  fix: "Set archive_after_days to a whole number from 1 to 365, or remove it for 7."
88444
88763
  },
88764
+ "no-progress-repeats": {
88765
+ expected: "A whole number of identical calls, 2 or more.",
88766
+ fix: "Set repeats to a whole number of 2 or more, or remove [limits.no_progress] for the default of 20."
88767
+ },
88445
88768
  "file-schema": {
88446
88769
  expected: "A file that matches its JSON Schema.",
88447
88770
  fix: "Correct the field to match the schema."
@@ -90837,8 +91160,8 @@ async function runTurns(runId, opts = {}, writer = stdoutWriter) {
90837
91160
  }
90838
91161
  }
90839
91162
  function parseKinds(value) {
90840
- const words2 = (value ?? "").split(",").map((word) => word.trim()).filter((word) => word !== "");
90841
- const unknown2 = words2.find((word) => !isTranscriptKind(word));
91163
+ const words2 = (value ?? "").split(",").map((word2) => word2.trim()).filter((word2) => word2 !== "");
91164
+ const unknown2 = words2.find((word2) => !isTranscriptKind(word2));
90842
91165
  if (unknown2 !== void 0) return { unknown: unknown2 };
90843
91166
  return { kinds: [...new Set(words2)] };
90844
91167
  }
@@ -92490,7 +92813,7 @@ function dismissalText(dismissal) {
92490
92813
  return `${dismissal.at}${who}${why}`;
92491
92814
  }
92492
92815
  function formatWorkspaceMemory(result2, now = /* @__PURE__ */ new Date()) {
92493
- const { memory, uses, uses_total, memory_pr } = result2;
92816
+ const { memory, uses, uses_total, index_runs, memory_pr } = result2;
92494
92817
  const lines = [
92495
92818
  `Memory ${memory.id}`,
92496
92819
  ` Name: ${memoryName(memory)}`,
@@ -92501,6 +92824,9 @@ function formatWorkspaceMemory(result2, now = /* @__PURE__ */ new Date()) {
92501
92824
  ` Type: ${memory.memory_type ?? "None"}`,
92502
92825
  ` Kind: ${memory.kind}`,
92503
92826
  ` Uses: ${usesText(memory.use_count, memory.use_signal)}`,
92827
+ // A session that only loaded the memory's MEMORY.md line is counted
92828
+ // apart from its uses (ADR-6940).
92829
+ ...index_runs > 0 ? [` Index: loaded from MEMORY.md in ${index_runs} sessions`] : [],
92504
92830
  ` Last used: ${ago(memory.last_used_at, now)}`,
92505
92831
  ` Captured: ${memory.created_at}`
92506
92832
  ];
@@ -92519,11 +92845,11 @@ function formatWorkspaceMemory(result2, now = /* @__PURE__ */ new Date()) {
92519
92845
  if (uses.length > 0) {
92520
92846
  lines.push(
92521
92847
  "",
92522
- `Uses (${uses.length < uses_total ? `${uses.length} of ${uses_total}` : uses_total}):`
92848
+ `Runs (${uses.length < uses_total ? `${uses.length} of ${uses_total}` : uses_total}):`
92523
92849
  );
92524
92850
  for (const use of uses)
92525
92851
  lines.push(
92526
- ` ${use.used_at} ${use.signal} ${use.run ?? "no run"}${use.count > 1 ? ` x${use.count}` : ""}`
92852
+ ` ${use.used_at} ${use.signals.join("+")} ${use.run ?? "no run"}${use.count > 1 ? ` x${use.count}` : ""}`
92527
92853
  );
92528
92854
  }
92529
92855
  return lines.join("\n");
@@ -96885,7 +97211,7 @@ function registryLaunch({
96885
97211
  return {
96886
97212
  ok: true,
96887
97213
  command: runner.command,
96888
- args: launch.map((word) => word.text),
97214
+ args: launch.map((word2) => word2.text),
96889
97215
  package: {
96890
97216
  name: pkg.identifier,
96891
97217
  version: version2,
@@ -113082,10 +113408,10 @@ var init_suggest = __esm({
113082
113408
  });
113083
113409
 
113084
113410
  // ../../packages/mcp-studio/src/lint/report.ts
113085
- function joinList2(items, word) {
113086
- if (items.length <= 2) return items.join(` ${word} `);
113411
+ function joinList2(items, word2) {
113412
+ if (items.length <= 2) return items.join(` ${word2} `);
113087
113413
  const last = items.at(-1) ?? "";
113088
- return `${items.slice(0, -1).join(", ")}, ${word} ${last}`;
113414
+ return `${items.slice(0, -1).join(", ")}, ${word2} ${last}`;
113089
113415
  }
113090
113416
  function orList2(items) {
113091
113417
  return joinList2(items, "or");
@@ -122158,9 +122484,9 @@ var require_lodash = __commonJS({
122158
122484
  function toString(value) {
122159
122485
  return value == null ? "" : baseToString(value);
122160
122486
  }
122161
- var camelCase = createCompounder(function(result2, word, index) {
122162
- word = word.toLowerCase();
122163
- return result2 + (index ? capitalize(word) : word);
122487
+ var camelCase = createCompounder(function(result2, word2, index) {
122488
+ word2 = word2.toLowerCase();
122489
+ return result2 + (index ? capitalize(word2) : word2);
122164
122490
  });
122165
122491
  function capitalize(string) {
122166
122492
  return upperFirst(toString(string).toLowerCase());
@@ -143901,6 +144227,7 @@ var init_loopback_login = __esm({
143901
144227
  var auth_exports = {};
143902
144228
  __export(auth_exports, {
143903
144229
  apiKeysUrl: () => apiKeysUrl,
144230
+ endServerSession: () => endServerSession,
143904
144231
  handleLogin: () => handleLogin,
143905
144232
  handleLogout: () => handleLogout,
143906
144233
  runBrowserLogin: () => runBrowserLogin,
@@ -144142,26 +144469,117 @@ Note: this key authenticated, but the API currently refuses it (HTTP 403).
144142
144469
  );
144143
144470
  }
144144
144471
  }
144145
- function handleLogout() {
144472
+ async function endServerSession(token, apiUrl) {
144473
+ let res;
144474
+ try {
144475
+ res = await fetch(`${apiUrl}/v1/auth/cli/session`, {
144476
+ method: "DELETE",
144477
+ headers: {
144478
+ Authorization: `Bearer ${token}`,
144479
+ Accept: "application/json"
144480
+ },
144481
+ signal: AbortSignal.timeout(LOGOUT_TIMEOUT_MS)
144482
+ });
144483
+ } catch (err2) {
144484
+ if (err2 instanceof Error && err2.name === "TimeoutError") {
144485
+ return {
144486
+ kind: "failed",
144487
+ detail: `no answer from ${apiUrl} within ${LOGOUT_TIMEOUT_MS / 1e3} seconds`
144488
+ };
144489
+ }
144490
+ return {
144491
+ kind: "failed",
144492
+ detail: `could not reach ${apiUrl}: ${err2 instanceof Error ? err2.message : String(err2)}`
144493
+ };
144494
+ }
144495
+ if (res.ok) return { kind: "ended" };
144496
+ if (res.status === 401) return { kind: "gone" };
144497
+ const refusal = await refusalOf4(res);
144498
+ if (res.status === 403 && refusal.reason === "not_a_cli_session") {
144499
+ return { kind: "not_a_session" };
144500
+ }
144501
+ return {
144502
+ kind: "failed",
144503
+ detail: refusal.message ? `HTTP ${res.status}: ${refusal.message}` : `HTTP ${res.status}`
144504
+ };
144505
+ }
144506
+ async function refusalOf4(res) {
144507
+ try {
144508
+ const body2 = await res.json();
144509
+ const error = body2?.error;
144510
+ return {
144511
+ ...typeof error?.reason === "string" ? { reason: error.reason } : {},
144512
+ ...typeof error?.message === "string" ? { message: error.message } : {}
144513
+ };
144514
+ } catch {
144515
+ return {};
144516
+ }
144517
+ }
144518
+ async function handleLogout() {
144146
144519
  const config = readConfig();
144147
144520
  if (!config.token && !config.orgSlug && !config.workspaceSlug) {
144148
144521
  process.stdout.write(`Not logged in.
144149
144522
  `);
144150
144523
  return;
144151
144524
  }
144525
+ const end = config.token ? await endServerSession(config.token, getApiUrl()) : null;
144152
144526
  clearConfig();
144527
+ const appUrl = getAppUrl();
144528
+ switch (end?.kind) {
144529
+ case "ended":
144530
+ process.stdout.write(
144531
+ `Logged out. Revoked this machine's sign-in on the server and cleared ~/.config/oxagen/config.json.
144532
+ `
144533
+ );
144534
+ break;
144535
+ case "gone":
144536
+ process.stdout.write(
144537
+ `Logged out. Cleared ~/.config/oxagen/config.json.
144538
+ `
144539
+ );
144540
+ process.stderr.write(
144541
+ `Note: the server no longer accepts this machine's sign-in. It may be revoked, or only suspended until its workspace or your access to it returns. To end it for good, revoke it in Oxagen: open Account settings, choose Security, and find it under Command-line sign-ins. ${appUrl}
144542
+ `
144543
+ );
144544
+ break;
144545
+ case "not_a_session":
144546
+ process.stdout.write(
144547
+ `Logged out. Cleared ~/.config/oxagen/config.json.
144548
+ `
144549
+ );
144550
+ process.stderr.write(
144551
+ `Note: the saved token is an API key, not a command-line sign-in, so it still works. Revoke it on the API keys page if no one else uses it: ${apiKeysUrl(appUrl, config.orgSlug)}
144552
+ `
144553
+ );
144554
+ break;
144555
+ case "failed":
144556
+ process.stdout.write(
144557
+ `Logged out. Cleared ~/.config/oxagen/config.json.
144558
+ `
144559
+ );
144560
+ process.stderr.write(
144561
+ `Warning: the server did not revoke this machine's sign-in (${end.detail}), so its key may still work. Revoke it in Oxagen: open Account settings, choose Security, and find it under Command-line sign-ins. ${appUrl}
144562
+ `
144563
+ );
144564
+ break;
144565
+ default:
144566
+ process.stdout.write(
144567
+ `Logged out. Cleared ~/.config/oxagen/config.json.
144568
+ `
144569
+ );
144570
+ }
144153
144571
  process.stdout.write(
144154
- `Logged out. Session cleared from ~/.config/oxagen/config.json.
144155
- Note: per-project links (.oxagen/workspace.json) are left intact.
144572
+ `Note: per-project links (.oxagen/workspace.json) are left intact.
144156
144573
  `
144157
144574
  );
144158
144575
  }
144159
- var WHOAMI_TIMEOUT_MS;
144576
+ var WHOAMI_TIMEOUT_MS, LOGOUT_TIMEOUT_MS;
144160
144577
  var init_auth2 = __esm({
144161
144578
  "src/commands/auth.ts"() {
144162
144579
  init_config();
144163
144580
  init_linker();
144164
144581
  WHOAMI_TIMEOUT_MS = 15e3;
144582
+ LOGOUT_TIMEOUT_MS = 15e3;
144165
144583
  }
144166
144584
  });
144167
144585
 
@@ -145207,6 +145625,26 @@ function buildProgram() {
145207
145625
  await budgetSet2(opts);
145208
145626
  }
145209
145627
  );
145628
+ const noProgressCmd = budgetCmd.command("no-progress").description(
145629
+ "The identical calls in a row that stop a run counting as progress"
145630
+ );
145631
+ noProgressCmd.command("show").description(
145632
+ "Show the limit in force, its open steering PR, and the runs that reached it"
145633
+ ).option(
145634
+ "--from <day>",
145635
+ "First day of the range, YYYY-MM-DD (this month by default)"
145636
+ ).option("--to <day>", "Last day of the range, YYYY-MM-DD").option("--json", "Output JSON").action(async (opts) => {
145637
+ const { noProgressShow: noProgressShow2 } = await Promise.resolve().then(() => (init_no_progress(), no_progress_exports));
145638
+ await noProgressShow2(opts);
145639
+ });
145640
+ noProgressCmd.command("set").description(
145641
+ "Open a steering PR that changes the limit (org Owner or workspace Admin)"
145642
+ ).option("--repeats <n>", "Identical calls in a row, 2 or more").option("--mode <mode>", "observe | enforced").option("--clear", "Return to the default of 20 calls in observe mode").option("--json", "Output JSON").action(
145643
+ async (opts) => {
145644
+ const { noProgressSet: noProgressSet2 } = await Promise.resolve().then(() => (init_no_progress(), no_progress_exports));
145645
+ await noProgressSet2(opts);
145646
+ }
145647
+ );
145210
145648
  const priceCmd = program2.command("price").description(
145211
145649
  "The organization's negotiated rates in the price book \u2014 what runs are billed at"
145212
145650
  );
@@ -146012,10 +146450,10 @@ function buildProgram() {
146012
146450
  }
146013
146451
  );
146014
146452
  program2.command("logout").description(
146015
- "Clear the stored Oxagen session from ~/.config/oxagen/config.json."
146453
+ "Revoke this machine's sign-in on the server and clear it from ~/.config/oxagen/config.json."
146016
146454
  ).action(async () => {
146017
146455
  const { handleLogout: handleLogout2 } = await Promise.resolve().then(() => (init_auth2(), auth_exports));
146018
- handleLogout2();
146456
+ await handleLogout2();
146019
146457
  });
146020
146458
  const agent = program2.command("agent").description("Govern the workspace's registered agents");
146021
146459
  agent.command("register").description(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxagen/cli",
3
- "version": "2.1.6-143",
3
+ "version": "2.1.6-152",
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": {