@ouro.bot/cli 0.1.0-alpha.887 → 0.1.0-alpha.888

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.
package/changelog.json CHANGED
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.888",
6
+ "changes": [
7
+ "Answer gates (source grounding and brevity) now hold a plain-text reply as well as a settle answer, so a reply with no settle call can no longer name an unsourced cast or ignore a brevity request."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.887",
6
12
  "changes": [
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.887",
2
+ "runtimeVersion": "0.1.0-alpha.888",
3
3
  "bundleSchemaVersion": 3,
4
4
  "lastUpdated": "2026-09-03T00:00:00.000Z"
5
5
  }
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0"?>
2
2
  <Container version="2">
3
3
  <Name>ouro-butler</Name>
4
- <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.887</Repository>
4
+ <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.888</Repository>
5
5
  <Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
6
6
  <Network>host</Network>
7
7
  <Shell>sh</Shell>
@@ -1208,6 +1208,38 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1208
1208
  rejectedAttempt = [structuredClone(assistant)];
1209
1209
  nextAttemptControls = controls;
1210
1210
  };
1211
+ // Answer gates run only for agents that opt in (ToolContext.answerGates), on every reply the person would receive: a settle answer
1212
+ // and a plain-text reply with no tool call alike (alpha.886 replay: a plain-text reply skipped them and an invented cast went out).
1213
+ // A request-specific contract (options.requiredToolCalls) already validates its own terminal answer. Each gate has its own bounded
1214
+ // retry, and none spends the last provider iteration.
1215
+ const answerGateVerdict = (answer, intent) => {
1216
+ const gates = options?.requiredToolCalls ? undefined : options?.toolContext?.answerGates;
1217
+ const retriesLeft = providerIterations < stepBudget - 1;
1218
+ let groundingError = null;
1219
+ let unverifiedNames = null;
1220
+ const grounding = gates?.sourceGrounding ? (0, source_grounding_1.sourceGroundingFinding)({ answer, userText: userTexts(messages).join("\n"), latestUserText: latestUserMessageText(messages.slice(0, turnStartLength)), knownText: priorTurnsText(messages.slice(0, turnStartLength)), tools: turnToolRecords }) : null;
1221
+ if (grounding) {
1222
+ if (groundingRejections < ANSWER_GATE_MAX_REJECTIONS && retriesLeft) {
1223
+ groundingRejections += 1;
1224
+ groundingError = grounding.message;
1225
+ (0, runtime_1.emitNervesEvent)({ level: "warn", component: "engine", event: "engine.answer_gate_rejected", message: "answer named things from a work that the turn did not look up; sending the model back", meta: { gate: "source_grounding", rejection: groundingRejections, cap: ANSWER_GATE_MAX_REJECTIONS, lookupsRun: grounding.lookups } });
1226
+ }
1227
+ else {
1228
+ unverifiedNames = grounding.names;
1229
+ (0, runtime_1.emitNervesEvent)({ level: "warn", component: "engine", event: "engine.answer_gate_disclosed", message: "unsourced names survived the retries; delivering with a disclosure", meta: { gate: "source_grounding", names: grounding.names.length } });
1230
+ }
1231
+ }
1232
+ let brevityError = null;
1233
+ if (!groundingError && gates?.brevity && !options?.toolContext?.context?.isGroupChat && brevityRejections < ANSWER_GATE_MAX_REJECTIONS && retriesLeft
1234
+ && (0, reply_style_1.briefStyleRequested)(userTexts(messages), (0, reply_style_1.savedCommunicationPreference)(options?.toolContext?.context?.friend))) {
1235
+ brevityError = (0, reply_style_1.briefStyleViolation)(answer, intent);
1236
+ if (brevityError) {
1237
+ brevityRejections += 1;
1238
+ (0, runtime_1.emitNervesEvent)({ level: "warn", component: "engine", event: "engine.answer_gate_rejected", message: "answer broke the person's brevity request; sending the model back", meta: { gate: "brevity", rejection: brevityRejections, cap: ANSWER_GATE_MAX_REJECTIONS } });
1239
+ }
1240
+ }
1241
+ return { error: groundingError ?? brevityError, unverifiedNames };
1242
+ };
1211
1243
  const rejectToolBatch = (assistant, reasons) => {
1212
1244
  rejectAttempt(assistant, assistant.tool_calls.map((call, index) => ({
1213
1245
  role: "tool", tool_call_id: call.id, content: reasons[index],
@@ -1774,6 +1806,24 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1774
1806
  }]);
1775
1807
  continue;
1776
1808
  }
1809
+ // A plain-text reply reaches the person just as a settle answer does, so the answer gates hold it too.
1810
+ const textAnswer = isPrivateRuntimeChannel ? "" : stripThinkBlocksForViolationCheck(result.content).trim();
1811
+ const textVerdict = textAnswer ? answerGateVerdict(textAnswer) : null;
1812
+ if (textVerdict?.error) {
1813
+ streamCallbackBuffer?.discard();
1814
+ callbacks.onClearText?.();
1815
+ rejectAttempt(msg, [{ role: "user", content: textVerdict.error }]);
1816
+ continue;
1817
+ }
1818
+ if (textVerdict?.unverifiedNames) {
1819
+ const disclosed = (0, source_grounding_1.withUnverifiedDisclosure)(textAnswer, textVerdict.unverifiedNames);
1820
+ streamCallbackBuffer?.discard();
1821
+ callbacks.onClearText?.();
1822
+ callbacks.onTextChunk(disclosed);
1823
+ pushGenerated({ ...msg, content: disclosed });
1824
+ done = true;
1825
+ continue;
1826
+ }
1777
1827
  // Legitimate text-only response, or cap reached — accept as-is.
1778
1828
  await streamCallbackBuffer?.flush();
1779
1829
  pushGenerated(msg);
@@ -1976,34 +2026,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1976
2026
  sawSettleContentMismatch = true;
1977
2027
  (0, runtime_1.emitNervesEvent)({ level: "warn", component: "engine", event: "engine.settle_content_mismatch", message: "settle answer differs from the reply text written beside it; asking once for the reply as the answer", meta: { answerLength: deliveredAnswer.length, contentLength: msg.content.length } });
1978
2028
  }
1979
- // Answer gates run only for agents that opt in (ToolContext.answerGates). A request-specific contract (options.requiredToolCalls)
1980
- // already validates its own terminal answer. Each gate has its own bounded retry, and none spends the last provider iteration.
1981
- const gates = options?.requiredToolCalls ? undefined : options?.toolContext?.answerGates;
1982
- const retriesLeft = providerIterations < stepBudget - 1;
1983
- let groundingError = null;
1984
- let unverifiedNames = null;
1985
- const grounding = gates?.sourceGrounding ? (0, source_grounding_1.sourceGroundingFinding)({ answer: deliveredAnswer, userText: userTexts(messages).join("\n"), latestUserText: latestUserMessageText(messages.slice(0, turnStartLength)), knownText: priorTurnsText(messages.slice(0, turnStartLength)), tools: turnToolRecords }) : null;
1986
- if (grounding) {
1987
- if (groundingRejections < ANSWER_GATE_MAX_REJECTIONS && retriesLeft) {
1988
- groundingRejections += 1;
1989
- groundingError = grounding.message;
1990
- (0, runtime_1.emitNervesEvent)({ level: "warn", component: "engine", event: "engine.answer_gate_rejected", message: "settle answer named things from a work that the turn did not look up; sending the model back", meta: { gate: "source_grounding", rejection: groundingRejections, cap: ANSWER_GATE_MAX_REJECTIONS, lookupsRun: grounding.lookups } });
1991
- }
1992
- else {
1993
- unverifiedNames = grounding.names;
1994
- (0, runtime_1.emitNervesEvent)({ level: "warn", component: "engine", event: "engine.answer_gate_disclosed", message: "unsourced names survived the retries; delivering with a disclosure", meta: { gate: "source_grounding", names: grounding.names.length } });
1995
- }
1996
- }
1997
- let brevityError = null;
1998
- if (!groundingError && gates?.brevity && !options?.toolContext?.context?.isGroupChat && brevityRejections < ANSWER_GATE_MAX_REJECTIONS && retriesLeft
1999
- && (0, reply_style_1.briefStyleRequested)(userTexts(messages), (0, reply_style_1.savedCommunicationPreference)(options?.toolContext?.context?.friend))) {
2000
- brevityError = (0, reply_style_1.briefStyleViolation)(deliveredAnswer, intent);
2001
- if (brevityError) {
2002
- brevityRejections += 1;
2003
- (0, runtime_1.emitNervesEvent)({ level: "warn", component: "engine", event: "engine.answer_gate_rejected", message: "settle answer broke the person's brevity request; sending the model back", meta: { gate: "brevity", rejection: brevityRejections, cap: ANSWER_GATE_MAX_REJECTIONS } });
2004
- }
2005
- }
2006
- const answerGateError = groundingError ?? brevityError;
2029
+ const { error: answerGateError, unverifiedNames } = answerGateVerdict(deliveredAnswer, intent);
2007
2030
  const retryError = contentMismatch
2008
2031
  ?? answerGateError
2009
2032
  ?? privateReturnAckLeakError(deliveredAnswer, privateReturnHeldTokens)
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.887",
3
+ "version": "0.1.0-alpha.888",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@ouro.bot/cli",
9
- "version": "0.1.0-alpha.887",
9
+ "version": "0.1.0-alpha.888",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.78.0",
12
12
  "@azure/identity": "^4.13.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.887",
3
+ "version": "0.1.0-alpha.888",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },