@ouro.bot/cli 0.1.0-alpha.726 → 0.1.0-alpha.728

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 (35) hide show
  1. package/README.md +14 -1
  2. package/assets/bluebubbles-host +264 -0
  3. package/changelog.json +18 -0
  4. package/dist/heart/config.js +1 -2
  5. package/dist/heart/core.js +181 -209
  6. package/dist/heart/daemon/bluebubbles-cli-integration.js +71 -0
  7. package/dist/heart/daemon/bluebubbles-host-protocol.js +433 -0
  8. package/dist/heart/daemon/bluebubbles-host.js +273 -0
  9. package/dist/heart/daemon/cli-defaults.js +8 -1
  10. package/dist/heart/daemon/cli-exec.js +239 -34
  11. package/dist/heart/daemon/cli-help.js +17 -6
  12. package/dist/heart/daemon/cli-parse.js +69 -1
  13. package/dist/heart/daemon/cli-render.js +7 -0
  14. package/dist/heart/daemon/daemon.js +79 -14
  15. package/dist/heart/daemon/doctor.js +21 -1
  16. package/dist/heart/daemon/mcp-canary.js +160 -29
  17. package/dist/heart/daemon/process-manager.js +100 -18
  18. package/dist/heart/daemon/socket-client.js +28 -19
  19. package/dist/heart/providers/anthropic.js +17 -12
  20. package/dist/heart/streaming.js +6 -6
  21. package/dist/heart/versioning/ouro-path-installer.js +30 -24
  22. package/dist/heart/versioning/ouro-recovery-launcher.js +76 -0
  23. package/dist/heart/versioning/version-intent.js +114 -0
  24. package/dist/mind/prompt-budget.js +104 -5
  25. package/dist/repertoire/tools.js +0 -10
  26. package/dist/senses/bluebubbles/client.js +43 -57
  27. package/dist/senses/bluebubbles/context-packet.js +104 -19
  28. package/dist/senses/bluebubbles/entry.js +14 -1
  29. package/dist/senses/bluebubbles/index.js +1398 -719
  30. package/dist/senses/bluebubbles/latest-turn.js +311 -0
  31. package/dist/senses/bluebubbles/outbound-state.js +4 -0
  32. package/dist/senses/bluebubbles/reaction-policy.js +4 -12
  33. package/dist/senses/bluebubbles/semantic-receipts.js +178 -10
  34. package/dist/senses/bluebubbles/webhook-registration.js +228 -0
  35. package/package.json +4 -2
@@ -496,14 +496,14 @@ function toResponsesUserContent(content) {
496
496
  return parts.length > 0 ? parts : "";
497
497
  }
498
498
  function toResponsesInput(messages) {
499
- let instructions = "";
499
+ const instructionParts = [];
500
500
  const input = [];
501
501
  for (const msg of messages) {
502
502
  if (msg.role === "system") {
503
- if (!instructions) {
504
- const sys = msg;
505
- instructions = (typeof sys.content === "string" ? sys.content : "") || "";
506
- }
503
+ const sys = msg;
504
+ const content = (typeof sys.content === "string" ? sys.content : "").trim();
505
+ if (content)
506
+ instructionParts.push(content);
507
507
  continue;
508
508
  }
509
509
  if (msg.role === "user") {
@@ -551,7 +551,7 @@ function toResponsesInput(messages) {
551
551
  continue;
552
552
  }
553
553
  }
554
- return { instructions, input };
554
+ return { instructions: instructionParts.join("\n\n"), input };
555
555
  }
556
556
  function toResponsesTools(ccTools) {
557
557
  return ccTools.map((t) => ({
@@ -39,28 +39,20 @@ const fs = __importStar(require("fs"));
39
39
  const os = __importStar(require("os"));
40
40
  const path = __importStar(require("path"));
41
41
  const runtime_1 = require("../../nerves/runtime");
42
- const WRAPPER_SCRIPT = `#!/bin/sh
43
- # Check for dev mode — if dev-config.json exists, dispatch to the dev repo
44
- # Skip dev dispatch for "up" command (explicitly returns to production)
45
- DEV_CONFIG="$HOME/.ouro-cli/dev-config.json"
46
- if [ -f "$DEV_CONFIG" ] && [ "$1" != "up" ]; then
47
- DEV_REPO=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync('$DEV_CONFIG','utf-8')).repoPath)}catch{}" 2>/dev/null)
48
- DEV_ENTRY="$DEV_REPO/dist/heart/daemon/ouro-entry.js"
49
- if [ -n "$DEV_REPO" ] && [ -e "$DEV_ENTRY" ]; then
50
- exec node "$DEV_ENTRY" "$@"
51
- fi
52
- fi
53
- # Fall back to installed version
54
- ENTRY="$HOME/.ouro-cli/CurrentVersion/node_modules/@ouro.bot/cli/dist/heart/daemon/ouro-entry.js"
55
- if [ ! -e "$ENTRY" ]; then
56
- echo "ouro not installed. Run: npx ouro.bot@latest" >&2
57
- exit 1
58
- fi
59
- exec node "$ENTRY" "$@"
60
- `;
61
- function writeWrapperScript(scriptPath, mkdirSync, writeFileSync, chmodSync) {
42
+ const ouro_recovery_launcher_1 = require("./ouro-recovery-launcher");
43
+ const WRAPPER_SCRIPT = ouro_recovery_launcher_1.OURO_WRAPPER_SCRIPT;
44
+ function writeWrapperScript(scriptPath, mkdirSync, writeFileSync, chmodSync, renameSync, existsSync, readFileSync) {
45
+ const assets = (0, ouro_recovery_launcher_1.resolveOuroRecoveryLauncherAssets)();
62
46
  mkdirSync(path.dirname(scriptPath), { recursive: true });
63
- writeFileSync(scriptPath, WRAPPER_SCRIPT, { mode: 0o755 });
47
+ const launcherPath = path.join(path.dirname(scriptPath), "ouro-launcher.js");
48
+ const launcherTempPath = `${launcherPath}.next-${process.pid}`;
49
+ writeFileSync(launcherTempPath, assets.launcherScript, { mode: 0o755 });
50
+ chmodSync(launcherTempPath, 0o755);
51
+ renameSync(launcherTempPath, launcherPath);
52
+ if (existsSync(launcherPath) && readFileSync(launcherPath, "utf8") !== assets.launcherScript) {
53
+ throw new Error("failed to verify installed Ouro recovery launcher");
54
+ }
55
+ writeFileSync(scriptPath, assets.wrapperScript, { mode: 0o755 });
64
56
  chmodSync(scriptPath, 0o755);
65
57
  }
66
58
  function detectShellProfile(homeDir, shell, platform) {
@@ -106,6 +98,16 @@ function isWrapperCurrent(scriptPath, existsSync, readFileSync) {
106
98
  return false;
107
99
  }
108
100
  }
101
+ function isRecoveryLauncherCurrent(scriptPath, existsSync, readFileSync) {
102
+ if (!existsSync(scriptPath))
103
+ return false;
104
+ try {
105
+ return readFileSync(scriptPath, "utf-8") === ouro_recovery_launcher_1.OURO_RECOVERY_LAUNCHER_SCRIPT;
106
+ }
107
+ catch {
108
+ return false;
109
+ }
110
+ }
109
111
  function isOwnedOuroLauncherPath(resolvedPath) {
110
112
  const normalized = path.normalize(resolvedPath);
111
113
  return (normalized.includes(`${path.sep}node_modules${path.sep}@ouro.bot${path.sep}cli${path.sep}`) ||
@@ -116,7 +118,8 @@ function isOwnedOuroLauncherContent(content) {
116
118
  content.includes("ouro.bot@latest") ||
117
119
  content.includes("ouro.bot@alpha") ||
118
120
  content.includes('exec npx --yes ouro.bot "$@"') ||
119
- content.includes("CurrentVersion/node_modules/@ouro.bot/cli"));
121
+ content.includes("CurrentVersion/node_modules/@ouro.bot/cli") ||
122
+ content.includes(".ouro-cli/bin/ouro-launcher.js"));
120
123
  }
121
124
  function canRepairShadowedLauncher(shadowPath, existsSync, readFileSync, realpathSync) {
122
125
  if (!existsSync(shadowPath))
@@ -236,6 +239,7 @@ function installOuroCommand(deps = {}) {
236
239
  const realpathSync = deps.realpathSync ?? fs.realpathSync;
237
240
  const lstatSync = deps.lstatSync ?? fs.lstatSync;
238
241
  const unlinkSync = deps.unlinkSync ?? fs.unlinkSync;
242
+ const renameSync = deps.renameSync ?? fs.renameSync;
239
243
  const envPath = deps.envPath ?? process.env.PATH ?? "";
240
244
  const shell = deps.shell ?? process.env.SHELL;
241
245
  /* v8 ignore stop */
@@ -254,9 +258,11 @@ function installOuroCommand(deps = {}) {
254
258
  }
255
259
  const binDir = path.join(homeDir, ".ouro-cli", "bin");
256
260
  const scriptPath = path.join(binDir, "ouro");
261
+ const recoveryLauncherPath = path.join(binDir, "ouro-launcher.js");
257
262
  const oldScriptPath = path.join(homeDir, ".local", "bin", "ouro");
258
263
  const resolvePath = () => diagnoseOuroPath({ homeDir, envPath, existsSync, readFileSync });
259
- const modernCurrent = isWrapperCurrent(scriptPath, existsSync, readFileSync);
264
+ const modernCurrent = isWrapperCurrent(scriptPath, existsSync, readFileSync)
265
+ && isRecoveryLauncherCurrent(recoveryLauncherPath, existsSync, readFileSync);
260
266
  const oldExists = existsSync(oldScriptPath);
261
267
  const oldCurrent = oldExists && isWrapperCurrent(oldScriptPath, existsSync, readFileSync);
262
268
  // ── Repair old ~/.local/bin/ouro launcher ──
@@ -332,7 +338,7 @@ function installOuroCommand(deps = {}) {
332
338
  });
333
339
  try {
334
340
  if (!modernCurrent) {
335
- writeWrapperScript(scriptPath, mkdirSync, writeFileSync, chmodSync);
341
+ writeWrapperScript(scriptPath, mkdirSync, writeFileSync, chmodSync, renameSync, existsSync, readFileSync);
336
342
  }
337
343
  }
338
344
  catch (error) {
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OURO_WRAPPER_SCRIPT = exports.OURO_RECOVERY_LAUNCHER_SCRIPT = void 0;
4
+ exports.resolveOuroRecoveryLauncherAssets = resolveOuroRecoveryLauncherAssets;
5
+ const runtime_1 = require("../../nerves/runtime");
6
+ exports.OURO_RECOVERY_LAUNCHER_SCRIPT = `#!/usr/bin/env node
7
+ "use strict";
8
+ const fs = require("fs");
9
+ const os = require("os");
10
+ const path = require("path");
11
+ const cp = require("child_process");
12
+ const home = path.join(os.homedir(), ".ouro-cli");
13
+ const current = path.join(home, "CurrentVersion");
14
+ const intentPath = path.join(home, "version-intent.json");
15
+ const entryRel = "node_modules/@ouro.bot/cli/dist/heart/daemon/ouro-entry.js";
16
+ let intent = null;
17
+ try {
18
+ intent = JSON.parse(fs.readFileSync(intentPath, "utf8"));
19
+ if (intent.schemaVersion !== 1 || !["pinned", "latest"].includes(intent.mode) || !intent.targetVersion) throw new Error("invalid fields");
20
+ } catch (error) {
21
+ if (error && error.code !== "ENOENT") {
22
+ process.stderr.write("invalid Ouro version intent: " + error.message + "\\n");
23
+ process.exit(1);
24
+ }
25
+ }
26
+ if (intent) {
27
+ const target = path.join(home, "versions", intent.targetVersion);
28
+ const targetEntry = path.join(target, entryRel);
29
+ if (!fs.existsSync(targetEntry)) {
30
+ process.stderr.write("Ouro version intent target is not installed: " + intent.targetVersion + "\\n");
31
+ process.exit(1);
32
+ }
33
+ let active = null;
34
+ try { active = path.basename(fs.readlinkSync(current)); } catch {}
35
+ if (active !== intent.targetVersion) {
36
+ const next = current + ".next-" + process.pid;
37
+ try { fs.unlinkSync(next); } catch {}
38
+ fs.symlinkSync(target, next);
39
+ fs.renameSync(next, current);
40
+ }
41
+ }
42
+ const entry = path.join(current, entryRel);
43
+ if (!fs.existsSync(entry)) {
44
+ process.stderr.write("ouro not installed. Run: npx ouro.bot@latest\\n");
45
+ process.exit(1);
46
+ }
47
+ const result = cp.spawnSync(process.execPath, [entry, ...process.argv.slice(2)], { stdio: "inherit" });
48
+ if (result.error) throw result.error;
49
+ if (result.signal) process.kill(process.pid, result.signal);
50
+ process.exit(result.status == null ? 1 : result.status);
51
+ `;
52
+ exports.OURO_WRAPPER_SCRIPT = `#!/bin/sh
53
+ # Check for dev mode — if dev-config.json exists, dispatch to the dev repo.
54
+ # Skip dev dispatch for "up" command (explicitly returns to production).
55
+ DEV_CONFIG="$HOME/.ouro-cli/dev-config.json"
56
+ if [ -f "$DEV_CONFIG" ] && [ "$1" != "up" ]; then
57
+ DEV_REPO=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync('$DEV_CONFIG','utf-8')).repoPath)}catch{}" 2>/dev/null)
58
+ DEV_ENTRY="$DEV_REPO/dist/heart/daemon/ouro-entry.js"
59
+ if [ -n "$DEV_REPO" ] && [ -e "$DEV_ENTRY" ]; then
60
+ exec node "$DEV_ENTRY" "$@"
61
+ fi
62
+ fi
63
+ exec node "$HOME/.ouro-cli/bin/ouro-launcher.js" "$@"
64
+ `;
65
+ function resolveOuroRecoveryLauncherAssets() {
66
+ (0, runtime_1.emitNervesEvent)({
67
+ component: "daemon",
68
+ event: "daemon.ouro_recovery_launcher_assets_resolved",
69
+ message: "resolved stable CLI recovery launcher assets",
70
+ meta: { launcher: "ouro-launcher.js", wrapper: "ouro" },
71
+ });
72
+ return {
73
+ launcherScript: exports.OURO_RECOVERY_LAUNCHER_SCRIPT,
74
+ wrapperScript: exports.OURO_WRAPPER_SCRIPT,
75
+ };
76
+ }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.versionIntentPath = versionIntentPath;
37
+ exports.readVersionIntent = readVersionIntent;
38
+ exports.writeVersionIntent = writeVersionIntent;
39
+ const fs = __importStar(require("fs"));
40
+ const os = __importStar(require("os"));
41
+ const path = __importStar(require("path"));
42
+ const runtime_1 = require("../../nerves/runtime");
43
+ function versionIntentPath(homeDir = os.homedir()) {
44
+ return path.join(homeDir, ".ouro-cli", "version-intent.json");
45
+ }
46
+ function parseVersionIntent(raw) {
47
+ let value;
48
+ try {
49
+ value = JSON.parse(raw);
50
+ }
51
+ catch {
52
+ throw new Error("invalid version intent: malformed JSON");
53
+ }
54
+ if (!value
55
+ || typeof value !== "object"
56
+ || value.schemaVersion !== 1
57
+ || !["pinned", "latest"].includes(String(value.mode))
58
+ || typeof value.targetVersion !== "string"
59
+ || !value.targetVersion) {
60
+ throw new Error("invalid version intent: expected schemaVersion=1, mode, and targetVersion");
61
+ }
62
+ return value;
63
+ }
64
+ function readVersionIntent(deps = {}) {
65
+ const readFileSync = deps.readFileSync ?? fs.readFileSync;
66
+ try {
67
+ return parseVersionIntent(readFileSync(versionIntentPath(deps.homeDir), "utf8"));
68
+ }
69
+ catch (error) {
70
+ if (error.code === "ENOENT")
71
+ return null;
72
+ throw error;
73
+ }
74
+ }
75
+ function writeVersionIntent(intent, deps = {}) {
76
+ const mkdirSync = deps.mkdirSync ?? fs.mkdirSync;
77
+ const writeFileSync = deps.writeFileSync ?? fs.writeFileSync;
78
+ const renameSync = deps.renameSync ?? fs.renameSync;
79
+ const unlinkSync = deps.unlinkSync ?? fs.unlinkSync;
80
+ const randomUUID = deps.randomUUID ?? (() => `${process.pid}-${Date.now()}`);
81
+ const destination = versionIntentPath(deps.homeDir);
82
+ const temporary = `${destination}.${randomUUID()}.tmp`;
83
+ (0, runtime_1.emitNervesEvent)({
84
+ component: "daemon",
85
+ event: "daemon.version_intent_write_start",
86
+ message: "writing CLI version intent",
87
+ meta: { mode: intent.mode, targetVersion: intent.targetVersion },
88
+ });
89
+ try {
90
+ mkdirSync(path.dirname(destination), { recursive: true });
91
+ writeFileSync(temporary, `${JSON.stringify(intent, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
92
+ renameSync(temporary, destination);
93
+ (0, runtime_1.emitNervesEvent)({
94
+ component: "daemon",
95
+ event: "daemon.version_intent_write_end",
96
+ message: "CLI version intent written",
97
+ meta: { mode: intent.mode, targetVersion: intent.targetVersion },
98
+ });
99
+ }
100
+ catch (error) {
101
+ try {
102
+ unlinkSync(temporary);
103
+ }
104
+ catch { /* best-effort temporary cleanup */ }
105
+ (0, runtime_1.emitNervesEvent)({
106
+ component: "daemon",
107
+ event: "daemon.version_intent_write_error",
108
+ level: "error",
109
+ message: "failed to write CLI version intent",
110
+ meta: { mode: intent.mode, targetVersion: intent.targetVersion, error: error instanceof Error ? error.message : String(error) },
111
+ });
112
+ throw error;
113
+ }
114
+ }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PROMPT_BUDGET_FALLBACK_CONTEXT_WINDOW_TOKENS = exports.PROMPT_BUDGET_POLICY_VERSION = void 0;
4
4
  exports.estimatePromptBudgetTokensForMessage = estimatePromptBudgetTokensForMessage;
5
5
  exports.estimatePromptBudgetTokens = estimatePromptBudgetTokens;
6
+ exports.assessRequiredPromptEvidenceBudget = assessRequiredPromptEvidenceBudget;
6
7
  exports.applyPromptBudget = applyPromptBudget;
7
8
  const runtime_1 = require("../nerves/runtime");
8
9
  exports.PROMPT_BUDGET_POLICY_VERSION = "prompt-budget/v1";
@@ -89,6 +90,42 @@ function itemTokens(item) {
89
90
  function cloneMessage(message) {
90
91
  return { ...message };
91
92
  }
93
+ function requiredMessagesByIdentity(input) {
94
+ const evidence = input.requiredPromptEvidence;
95
+ if (!evidence)
96
+ return [];
97
+ const current = evidence.currentUserMessage;
98
+ if (current.role !== "user") {
99
+ throw new Error("required current user message must have role=user");
100
+ }
101
+ const currentMatches = input.messages.filter((message) => message === current);
102
+ if (currentMatches.length !== 1) {
103
+ throw new Error(currentMatches.length === 0
104
+ ? "required current user message is not present by identity"
105
+ : "required current user message must appear exactly once by identity");
106
+ }
107
+ const predecessor = evidence.verifiedPredecessorMessage;
108
+ if (predecessor) {
109
+ if (predecessor === current)
110
+ throw new Error("required predecessor and current user message must be distinct objects");
111
+ if (predecessor.role !== "system") {
112
+ throw new Error("required verified predecessor message must have role=system");
113
+ }
114
+ const predecessorMatches = input.messages.filter((message) => message === predecessor);
115
+ if (predecessorMatches.length !== 1) {
116
+ throw new Error(predecessorMatches.length === 0
117
+ ? "required verified predecessor message is not present by identity"
118
+ : "required verified predecessor message must appear exactly once by identity");
119
+ }
120
+ if (input.messages.indexOf(predecessor) !== input.messages.indexOf(current) - 1) {
121
+ throw new Error("required verified predecessor message must be immediately before the current user message");
122
+ }
123
+ }
124
+ return input.messages.filter((message) => message === current || message === predecessor);
125
+ }
126
+ function cloneUnlessRequired(message, requiredMessages) {
127
+ return requiredMessages.has(message) ? message : cloneMessage(message);
128
+ }
92
129
  function messageText(message) {
93
130
  return typeof message.content === "string" ? message.content : "";
94
131
  }
@@ -197,11 +234,35 @@ function nonSystemSourceFor(messages) {
197
234
  return "diagnostics";
198
235
  return "recent-session-tail";
199
236
  }
200
- function buildPromptBudgetItems(messages) {
237
+ function buildPromptBudgetItems(messages, requiredPromptEvidence) {
201
238
  const rawItems = [];
202
239
  let index = 0;
203
240
  while (index < messages.length) {
204
241
  const message = messages[index];
242
+ if (message === requiredPromptEvidence?.currentUserMessage) {
243
+ rawItems.push({
244
+ id: `required:${index}:current`,
245
+ priority: 1,
246
+ source: "current-user-message",
247
+ messages: [message],
248
+ originalMessageCount: 1,
249
+ required: "current-user",
250
+ });
251
+ index += 1;
252
+ continue;
253
+ }
254
+ if (message === requiredPromptEvidence?.verifiedPredecessorMessage) {
255
+ rawItems.push({
256
+ id: `required:${index}:predecessor`,
257
+ priority: 1,
258
+ source: "required-current-turn-state",
259
+ messages: [message],
260
+ originalMessageCount: 1,
261
+ required: "verified-predecessor",
262
+ });
263
+ index += 1;
264
+ continue;
265
+ }
205
266
  if (message.role === "system") {
206
267
  rawItems.push(...systemItems(message, index));
207
268
  index += 1;
@@ -236,6 +297,8 @@ function buildPromptBudgetItems(messages) {
236
297
  const lastUserItemIndex = rawItems.findLastIndex((item) => item.messages.some((message) => message.role === "user"));
237
298
  const latestToolItemIndex = rawItems.findLastIndex((item) => item.messages.some((message) => message.role === "tool" || assistantHasToolCalls(message)));
238
299
  return rawItems.map((item, itemIndex) => {
300
+ if (item.required)
301
+ return item;
239
302
  if (itemIndex === lastUserItemIndex) {
240
303
  return { ...item, priority: 1, source: "current-user-message" };
241
304
  }
@@ -254,7 +317,7 @@ function buildPromptBudgetItems(messages) {
254
317
  });
255
318
  }
256
319
  function flattenItems(items) {
257
- return items.flatMap((item) => item.messages.map(cloneMessage));
320
+ return items.flatMap((item) => item.messages.map((message) => item.required ? message : cloneMessage(message)));
258
321
  }
259
322
  function budgetFor(input) {
260
323
  const contextWindowTokens = typeof input.contextWindowTokens === "number" && Number.isFinite(input.contextWindowTokens) && input.contextWindowTokens > 0
@@ -273,6 +336,19 @@ function budgetFor(input) {
273
336
  inputTokenLimit: Math.max(0, contextWindowTokens - outputReserveTokens - protocolReserveTokens),
274
337
  };
275
338
  }
339
+ function assessRequiredPromptEvidenceBudget(input) {
340
+ const budget = budgetFor(input);
341
+ const messages = requiredMessagesByIdentity(input);
342
+ const estimatedTokens = estimatePromptBudgetTokens(messages);
343
+ return {
344
+ status: messages.length > 0 && estimatedTokens > budget.inputTokenLimit
345
+ ? "required_evidence_over_budget"
346
+ : "within_budget",
347
+ messages,
348
+ estimatedTokens,
349
+ budget,
350
+ };
351
+ }
276
352
  function totalItemTokens(items) {
277
353
  return estimatePromptBudgetTokens(flattenItems(items));
278
354
  }
@@ -304,15 +380,38 @@ function emitPromptBudgetEvent(result) {
304
380
  });
305
381
  }
306
382
  function applyPromptBudget(input) {
307
- const budget = budgetFor(input);
383
+ const requiredAssessment = assessRequiredPromptEvidenceBudget(input);
384
+ const budget = requiredAssessment.budget;
308
385
  const estimatedBeforeTokens = estimatePromptBudgetTokens(input.messages);
309
386
  const truncations = [];
387
+ const requiredMessages = requiredAssessment.messages;
388
+ const requiredMessageSet = new Set(requiredMessages);
310
389
  let droppedMessages = 0;
390
+ const requiredTokens = requiredAssessment.estimatedTokens;
391
+ if (requiredAssessment.status === "required_evidence_over_budget") {
392
+ droppedMessages = input.messages.length - requiredMessages.length;
393
+ const result = {
394
+ policyVersion: exports.PROMPT_BUDGET_POLICY_VERSION,
395
+ status: "required_evidence_over_budget",
396
+ messages: requiredMessages,
397
+ budget,
398
+ stats: {
399
+ estimatedBeforeTokens,
400
+ estimatedAfterTokens: requiredTokens,
401
+ originalMessages: input.messages.length,
402
+ finalMessages: requiredMessages.length,
403
+ droppedMessages,
404
+ truncations,
405
+ },
406
+ };
407
+ emitPromptBudgetEvent(result);
408
+ return result;
409
+ }
311
410
  if (estimatedBeforeTokens <= budget.inputTokenLimit) {
312
411
  const result = {
313
412
  policyVersion: exports.PROMPT_BUDGET_POLICY_VERSION,
314
413
  status: "within_budget",
315
- messages: input.messages.map(cloneMessage),
414
+ messages: input.messages.map((message) => cloneUnlessRequired(message, requiredMessageSet)),
316
415
  budget,
317
416
  stats: {
318
417
  estimatedBeforeTokens,
@@ -326,7 +425,7 @@ function applyPromptBudget(input) {
326
425
  emitPromptBudgetEvent(result);
327
426
  return result;
328
427
  }
329
- let items = buildPromptBudgetItems(input.messages);
428
+ let items = buildPromptBudgetItems(input.messages, input.requiredPromptEvidence);
330
429
  for (const priority of [8, 7, 6, 5, 4, 3]) {
331
430
  if (totalItemTokens(items) <= budget.inputTokenLimit)
332
431
  break;
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.surfaceToolDef = exports.speakTool = exports.restTool = exports.ponderTool = exports.observeTool = exports.settleTool = exports.tools = void 0;
4
4
  exports.resetMcpDefinitions = resetMcpDefinitions;
5
5
  exports.getToolsForChannel = getToolsForChannel;
6
- exports.getRestrictedReactionFeedbackTools = getRestrictedReactionFeedbackTools;
7
6
  exports.resolveToolDefinition = resolveToolDefinition;
8
7
  exports.shellRiskProfile = shellRiskProfile;
9
8
  exports.riskProfileForTool = riskProfileForTool;
@@ -127,15 +126,6 @@ function getToolsForChannel(capabilities, toolPreferences, _context, providerCap
127
126
  }
128
127
  return filterByCapability(result, providerCapabilities);
129
128
  }
130
- /**
131
- * The complete provider-facing tool surface for trusted reaction feedback.
132
- * Keep these schemas sourced from the normal registry so restricted turns
133
- * cannot drift onto lookalike definitions or inherit channel/MCP tools.
134
- */
135
- function getRestrictedReactionFeedbackTools() {
136
- const orientationGetTool = tools_base_1.baseToolDefinitions.find((definition) => definition.tool.function.name === "orientation_get").tool;
137
- return [tools_base_1.settleTool, tools_base_1.observeTool, orientationGetTool];
138
- }
139
129
  // Look up a tool definition from the live combined registry (native + MCP).
140
130
  // The base registry is intentionally consulted at call time so tests and
141
131
  // runtime extensions cannot leave execution metadata behind a stale snapshot.