@noopolis/daimon 0.2.2 → 0.2.4

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.
@@ -64,6 +64,13 @@ export declare function createGrokBrokerProxyHandler(authority: GrokBrokerCreden
64
64
  */
65
65
  export declare class GrokBrokerProxyRefusal extends Error {
66
66
  readonly reason: string;
67
- constructor(reason: string);
67
+ readonly check?: string | undefined;
68
+ readonly detail?: string | undefined;
69
+ /**
70
+ * `check` names which body-gate check failed and `detail` is the refused
71
+ * body's content-free shape ({@link grokBrokerProxyBodyShape}); both are for
72
+ * the broker's stderr line, and `check` also rides the 400 body.
73
+ */
74
+ constructor(reason: string, check?: string | undefined, detail?: string | undefined);
68
75
  }
69
76
  //# sourceMappingURL=grokBrokerProxy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"grokBrokerProxy.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerProxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAExF,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAIpF,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAEzE,OAAO,EAAgE,KAAK,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACtI,OAAO,EAAE,+BAA+B,EAAE,MAAM,6BAA6B,CAAC;AAE9E,OAAO,EAA2E,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAG7I,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAkC,KAAK,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAE5G,2FAA2F;AAC3F,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,MAAM,EAAE,qBAAqB,CAAC;IAC9B,KAAK,EAAE,mBAAmB,CAAC;IAC3B,KAAK,CAAC,EAAE,uBAAuB,CAAC;CACjC,CAAC,CAAC;AAEH,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IAAE,WAAW,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAAC,qBAAqB,CAAC,CAAC,mBAAmB,EAAC,MAAM,GAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAAC,YAAY,CAAC,mBAAmB,CAAC,EAAC,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,OAAO,CAAC,IAAI,OAAO,CAAA;CAAE,CAAC,CAAC;AACjQ,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,+BAA+B,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC,CAAC;AAE3N;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,6BAA6B,EAAE,QAAQ,GAAE,kBAAoC,EACxF,MAAM,GAAE,qBAAwD,EAAE,UAAU,SAAS,EACrF,MAAM,CAAC,EAAE,mBAAmB,EAAE,SAAS,CAAC,EAAE,mBAAmB;;;;mCAoC5B,MAAM,SAAS,MAAM,OAAO,CAAC,IAAI,CAAC;iCACpC,MAAM;yBACd,MAAM,QAAQ,mBAAmB;uBAMnC,MAAM;qBACR,eAAe,YAAY,cAAc;GA5B7D;AAED,iFAAiF;AACjF,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,6BAA6B,EAAE,QAAQ,GAAE,kBAAoC,EACxF,MAAM,GAAE,qBAAwD,EAAE,MAAM,CAAC,EAAE,mBAAmB,EAAE,SAAS,CAAC,EAAE,mBAAmB;;mCAc9F,MAAM,SAAS,MAAM,OAAO,CAAC,IAAI,CAAC;iCACpC,MAAM;yBACd,MAAM,QAAQ,mBAAmB;uBAMnC,MAAM;qBACR,eAAe,YAAY,cAAc;EAI7D;AAED;;;;;;;;GAQG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CACpC"}
1
+ {"version":3,"file":"grokBrokerProxy.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerProxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAExF,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAIpF,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAEzE,OAAO,EAAgE,KAAK,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACtI,OAAO,EAAE,+BAA+B,EAAmC,MAAM,6BAA6B,CAAC;AAG/G,OAAO,EAA2E,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAG7I,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAkC,KAAK,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAE5G,2FAA2F;AAC3F,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,MAAM,EAAE,qBAAqB,CAAC;IAC9B,KAAK,EAAE,mBAAmB,CAAC;IAC3B,KAAK,CAAC,EAAE,uBAAuB,CAAC;CACjC,CAAC,CAAC;AAEH,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IAAE,WAAW,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAAC,qBAAqB,CAAC,CAAC,mBAAmB,EAAC,MAAM,GAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAAC,YAAY,CAAC,mBAAmB,CAAC,EAAC,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,OAAO,CAAC,IAAI,OAAO,CAAA;CAAE,CAAC,CAAC;AACjQ,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,+BAA+B,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC,CAAC;AAE3N;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,6BAA6B,EAAE,QAAQ,GAAE,kBAAoC,EACxF,MAAM,GAAE,qBAAwD,EAAE,UAAU,SAAS,EACrF,MAAM,CAAC,EAAE,mBAAmB,EAAE,SAAS,CAAC,EAAE,mBAAmB;;;;mCAoC5B,MAAM,SAAS,MAAM,OAAO,CAAC,IAAI,CAAC;iCACpC,MAAM;yBACd,MAAM,QAAQ,mBAAmB;uBAMnC,MAAM;qBACR,eAAe,YAAY,cAAc;GA5B7D;AAED,iFAAiF;AACjF,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,6BAA6B,EAAE,QAAQ,GAAE,kBAAoC,EACxF,MAAM,GAAE,qBAAwD,EAAE,MAAM,CAAC,EAAE,mBAAmB,EAAE,SAAS,CAAC,EAAE,mBAAmB;;mCAc9F,MAAM,SAAS,MAAM,OAAO,CAAC,IAAI,CAAC;iCACpC,MAAM;yBACd,MAAM,QAAQ,mBAAmB;uBAMnC,MAAM;qBACR,eAAe,YAAY,cAAc;EAI7D;AAED;;;;;;;;GAQG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAMnC,QAAQ,CAAC,MAAM,EAAE,MAAM;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;IALtF;;;;OAIG;gBACkB,MAAM,EAAE,MAAM,EAAW,KAAK,CAAC,EAAE,MAAM,YAAA,EAAW,MAAM,CAAC,EAAE,MAAM,YAAA;CACvF"}
@@ -6,7 +6,8 @@ import { CLI_ENGINE_MAX_DIAGNOSTIC_BYTES } from "../pi/cliChildOutput.js";
6
6
  import { EngineBrokerCapabilities } from "./engineBrokerCapabilities.js";
7
7
  import { ENGINE_BROKER_AUTH_STALE } from "./engineBrokerProtocol.js";
8
8
  import { DEFAULT_GROK_BROKER_MODEL_POLICY, parseGrokBrokerModelPolicy } from "./grokBrokerModelPolicy.js";
9
- import { authorizeGrokBrokerProxyRequest } from "./grokBrokerProxyRequest.js";
9
+ import { authorizeGrokBrokerProxyRequest, GrokBrokerProxyRequestRejection } from "./grokBrokerProxyRequest.js";
10
+ import { formatGrokBrokerProxyBodyShape, grokBrokerProxyBodyShape } from "./grokBrokerProxyBodyShape.js";
10
11
  import { GROK_SESSION_TITLE_SINK_KEY } from "./grokBrokerWorkerConfig.js";
11
12
  import { parseGrokFinalReply, parseGrokResponseToolNames, parseGrokUpstreamUsage } from "./grokBrokerTurnMeter.js";
12
13
  import { GROK_ENGINE_BROKER } from "../contracts/runtimeContractManifest.js";
@@ -77,9 +78,18 @@ export function createGrokBrokerProxyHandler(authority, upstream = defaultUpstre
77
78
  */
78
79
  export class GrokBrokerProxyRefusal extends Error {
79
80
  reason;
80
- constructor(reason) {
81
+ check;
82
+ detail;
83
+ /**
84
+ * `check` names which body-gate check failed and `detail` is the refused
85
+ * body's content-free shape ({@link grokBrokerProxyBodyShape}); both are for
86
+ * the broker's stderr line, and `check` also rides the 400 body.
87
+ */
88
+ constructor(reason, check, detail) {
81
89
  super(`grok broker proxy refused: ${reason}`);
82
90
  this.reason = reason;
91
+ this.check = check;
92
+ this.detail = detail;
83
93
  }
84
94
  }
85
95
  async function serve(request, response, authority, upstream, capabilities, guards, turns, fallback, grants) {
@@ -137,7 +147,7 @@ async function serve(request, response, authority, upstream, capabilities, guard
137
147
  token = "";
138
148
  // The spend gate runs after the body is proven a real lean worker request
139
149
  // (a refused session-title body never counts) and before any upstream call.
140
- const admission = turn.meter.admit();
150
+ const admission = turn.meter.admit({ keepFinal: prepared.compaction === true });
141
151
  if ("refused" in admission) {
142
152
  response.writeHead(429, { "content-type": "application/json", "cache-control": "no-store" });
143
153
  response.end(JSON.stringify({ error: "turn limit reached", limit: admission.refused }));
@@ -167,8 +177,9 @@ async function serve(request, response, authority, upstream, capabilities, guard
167
177
  // A successful answer that called nothing and stopped is the turn's final
168
178
  // reply: the broker ends a worker that finished but did not exit on it
169
179
  // (`grokEngineBrokerTurnEnd.ts`), and reports it when the launcher refused
170
- // the worker's stdout. Observation only, exactly like the tool names.
171
- settle?.(usageOrEstimate(result.body, result.headers["content-type"]), toolCallsOrNothing(result.body, result.headers["content-type"]), result.status === 200 ? finalReplyOrNothing(result.body, result.headers["content-type"]) : undefined);
180
+ // the worker's stdout. Observation only, exactly like the tool names. A
181
+ // compaction summary also calls nothing and stops, and is never the reply.
182
+ settle?.(usageOrEstimate(result.body, result.headers["content-type"]), toolCallsOrNothing(result.body, result.headers["content-type"]), result.status === 200 && prepared.compaction !== true ? finalReplyOrNothing(result.body, result.headers["content-type"]) : undefined);
172
183
  response.writeHead(result.status, { "content-type": result.headers["content-type"] ?? "application/json", "cache-control": "no-store" });
173
184
  response.end(result.body);
174
185
  }
@@ -191,7 +202,8 @@ async function serve(request, response, authority, upstream, capabilities, guard
191
202
  // A named refusal is its own account; anything else used to reach the log as
192
203
  // the bare word `broker_unavailable`, which names nothing — so it carries the
193
204
  // fault's own class and message, and nothing else, beside it.
194
- const named = refused ? refusal : `${refusal} (${brokerFaultCause(error, secrets)})`;
205
+ const named = !refused ? `${refusal} (${brokerFaultCause(error, secrets)})`
206
+ : error instanceof GrokBrokerProxyRefusal && error.check !== undefined ? `${refusal} check=${error.check}${error.detail === undefined ? "" : ` ${error.detail}`}` : refusal;
195
207
  if (!titleSink && !expectedWorkerProbe(request))
196
208
  process.stderr.write(`[grok-proxy] refused: ${named}\n`);
197
209
  // Grok's own session-title call is refused by design, and keeps the transient
@@ -206,7 +218,7 @@ async function serve(request, response, authority, upstream, capabilities, guard
206
218
  }
207
219
  if (refused) {
208
220
  response.writeHead(400, { "content-type": "application/json", "cache-control": "no-store" });
209
- response.end(JSON.stringify({ error: "broker refused this request", reason: refusal }));
221
+ response.end(JSON.stringify({ error: "broker refused this request", reason: refusal, ...(error instanceof GrokBrokerProxyRefusal && error.check !== undefined ? { check: error.check } : {}) }));
210
222
  return;
211
223
  }
212
224
  response.writeHead(503, { "content-type": "application/json", "cache-control": "no-store" });
@@ -307,13 +319,19 @@ const finalReplyOrNothing = (body, contentType) => {
307
319
  return undefined;
308
320
  }
309
321
  };
310
- /** The body gate, refused non-retryably: a rejected body is a policy miss, never a transient fault. */
322
+ /**
323
+ * The body gate, refused non-retryably: a rejected body is a policy miss, never
324
+ * a transient fault. The refusal names the failed check and the body's
325
+ * content-free shape, because a bare `request_body_rejected` sent a live
326
+ * training pilot to a stub harness to learn which of a dozen checks fired.
327
+ */
311
328
  function authorizeRequestOrRefuse(...args) {
312
329
  try {
313
330
  return authorizeGrokBrokerProxyRequest(...args);
314
331
  }
315
- catch {
316
- throw new GrokBrokerProxyRefusal("request_body_rejected");
332
+ catch (error) {
333
+ const check = error instanceof GrokBrokerProxyRequestRejection ? error.check : "credential";
334
+ throw new GrokBrokerProxyRefusal("request_body_rejected", check, formatGrokBrokerProxyBodyShape(grokBrokerProxyBodyShape(args[0].body)));
317
335
  }
318
336
  }
319
337
  async function readBody(request) { const chunks = []; let bytes = 0; for await (const chunk of request) {
@@ -0,0 +1,24 @@
1
+ /**
2
+ * A content-free description of one refused worker body, for the broker's own
3
+ * stderr: byte length, top-level member names, offered tool names, message
4
+ * count and roles, and `tool_choice`. Never a message, a parameter schema, a
5
+ * value of any other member, a header or a token.
6
+ *
7
+ * Every name comes from a body the worker wrote, so each is admitted only as a
8
+ * plain short identifier (anything else is `<invalid>`) and every list is
9
+ * bounded with a `<truncated>` last entry. A body that is not a JSON object
10
+ * reports its byte length alone.
11
+ */
12
+ export type GrokBrokerProxyBodyShape = Readonly<{
13
+ bytes: number;
14
+ keys?: readonly string[];
15
+ tools?: readonly string[] | "absent" | "invalid";
16
+ messages?: number | "invalid";
17
+ roles?: readonly string[];
18
+ tool_choice?: string;
19
+ }>;
20
+ export declare const GROK_PROXY_BODY_SHAPE_LIST_MAX = 32;
21
+ export declare function grokBrokerProxyBodyShape(body: Uint8Array): GrokBrokerProxyBodyShape;
22
+ /** One line, `key=value` pairs, for the `[grok-proxy] refused:` log. */
23
+ export declare const formatGrokBrokerProxyBodyShape: (shape: GrokBrokerProxyBodyShape) => string;
24
+ //# sourceMappingURL=grokBrokerProxyBodyShape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grokBrokerProxyBodyShape.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerProxyBodyShape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,QAAQ,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC,CAAC;AAGH,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAqBjD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,UAAU,GAAG,wBAAwB,CAmBnF;AAED,wEAAwE;AACxE,eAAO,MAAM,8BAA8B,GAAI,OAAO,wBAAwB,KAAG,MAOtE,CAAC"}
@@ -0,0 +1,60 @@
1
+ const NAME = /^[A-Za-z_][A-Za-z0-9_.-]{0,63}$/u;
2
+ export const GROK_PROXY_BODY_SHAPE_LIST_MAX = 32;
3
+ const safeName = (value) => typeof value === "string" && NAME.test(value) ? value : "<invalid>";
4
+ const bounded = (values) => values.length <= GROK_PROXY_BODY_SHAPE_LIST_MAX ? values : [...values.slice(0, GROK_PROXY_BODY_SHAPE_LIST_MAX - 1), "<truncated>"];
5
+ const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
6
+ /** Roles in order, with consecutive repeats collapsed to `role*n` so a long history stays short. */
7
+ const roleRuns = (messages) => {
8
+ const runs = [];
9
+ let last;
10
+ let count = 0;
11
+ const flush = () => { if (last !== undefined)
12
+ runs.push(count > 1 ? `${last}*${count}` : last); };
13
+ for (const message of messages) {
14
+ const role = isObject(message) ? safeName(message.role) : "<invalid>";
15
+ if (role === last) {
16
+ count++;
17
+ continue;
18
+ }
19
+ flush();
20
+ last = role;
21
+ count = 1;
22
+ }
23
+ flush();
24
+ return bounded(runs);
25
+ };
26
+ export function grokBrokerProxyBodyShape(body) {
27
+ let parsed;
28
+ try {
29
+ parsed = JSON.parse(new TextDecoder("utf-8", { fatal: false }).decode(body));
30
+ }
31
+ catch {
32
+ return { bytes: body.byteLength };
33
+ }
34
+ if (!isObject(parsed))
35
+ return { bytes: body.byteLength };
36
+ const tools = parsed.tools === undefined ? "absent" : Array.isArray(parsed.tools)
37
+ ? bounded(parsed.tools.map((tool) => isObject(tool) && isObject(tool.function) ? safeName(tool.function.name) : "<invalid>"))
38
+ : "invalid";
39
+ const choice = parsed.tool_choice;
40
+ return {
41
+ bytes: body.byteLength,
42
+ keys: bounded(Object.keys(parsed).map(safeName).sort()),
43
+ tools,
44
+ messages: Array.isArray(parsed.messages) ? parsed.messages.length : "invalid",
45
+ ...(Array.isArray(parsed.messages) ? { roles: roleRuns(parsed.messages) } : {}),
46
+ ...(choice === undefined ? {} : {
47
+ tool_choice: typeof choice === "string" ? safeName(choice)
48
+ : isObject(choice) ? `object:${isObject(choice.function) ? safeName(choice.function.name) : safeName(choice.type)}` : "<invalid>"
49
+ })
50
+ };
51
+ }
52
+ /** One line, `key=value` pairs, for the `[grok-proxy] refused:` log. */
53
+ export const formatGrokBrokerProxyBodyShape = (shape) => [
54
+ `bytes=${shape.bytes}`,
55
+ ...(shape.keys === undefined ? [] : [`keys=${shape.keys.join(",")}`]),
56
+ ...(shape.tools === undefined ? [] : [`tools=${Array.isArray(shape.tools) ? shape.tools.join(",") : shape.tools}`]),
57
+ ...(shape.messages === undefined ? [] : [`messages=${shape.messages}`]),
58
+ ...(shape.roles === undefined ? [] : [`roles=${shape.roles.join(",")}`]),
59
+ ...(shape.tool_choice === undefined ? [] : [`tool_choice=${shape.tool_choice}`])
60
+ ].join(" ");
@@ -1,5 +1,15 @@
1
1
  import type { EngineBrokerCapabilities } from "./engineBrokerCapabilities.js";
2
2
  import { type GrokBrokerModelPolicy } from "./grokBrokerModelPolicy.js";
3
+ /**
4
+ * Which body-gate check refused a request: a closed, content-free code, so a
5
+ * refusal on the broker's stderr names its cause without a stub harness.
6
+ */
7
+ export type GrokBrokerProxyRejectionCheck = "route" | "body_size" | "bearer" | "scope" | "client_version" | "json" | "envelope" | "model" | "reasoning_effort" | "tools" | "members" | "stream_options";
8
+ /** A body-gate refusal. The message is the historic one; `check` names the failed check. */
9
+ export declare class GrokBrokerProxyRequestRejection extends Error {
10
+ readonly check: GrokBrokerProxyRejectionCheck;
11
+ constructor(check: GrokBrokerProxyRejectionCheck);
12
+ }
3
13
  export type GrokBrokerProxyInput = Readonly<{
4
14
  method: string;
5
15
  pathname: string;
@@ -8,10 +18,12 @@ export type GrokBrokerProxyInput = Readonly<{
8
18
  agentId?: string;
9
19
  turnId?: string;
10
20
  }>;
21
+ /** `compaction` marks a normalized auto-compaction summary request: its answer is a summary, never the turn's final reply. */
11
22
  export type GrokBrokerUpstreamRequest = Readonly<{
12
23
  url: "https://cli-chat-proxy.grok.com/v1/chat/completions";
13
24
  headers: Readonly<Record<string, string>>;
14
25
  body: Uint8Array;
26
+ compaction?: true;
15
27
  }>;
16
28
  /**
17
29
  * Authorizes one worker request and rebuilds it for the provider.
@@ -24,7 +36,24 @@ export type GrokBrokerUpstreamRequest = Readonly<{
24
36
  * - the offered tool names are exactly the lean visible set. Grok 1.0.34 turns
25
37
  * an unmappable `--tools` entry into its full 19-tool set, and its per-turn
26
38
  * `session_title` request carries a single forced tool; both are refused.
39
+ *
40
+ * One other shape is admitted, and only by normalizing it into the lean one:
41
+ * Grok 1.0.34's auto-compaction summary request ({@link normalizeGrokCompactionRequest}).
27
42
  */
28
43
  export declare function authorizeGrokBrokerProxyRequest(input: GrokBrokerProxyInput, capabilities: EngineBrokerCapabilities, bearer: string, policy?: GrokBrokerModelPolicy): GrokBrokerUpstreamRequest;
44
+ /**
45
+ * Rewrites, in place, exactly the captured compaction shape into the lean
46
+ * worker shape, and reports whether it did. The two extra members are each
47
+ * the provider's default value (`tool_choice: "auto"` with tools offered,
48
+ * `temperature: 1`), so removing them changes nothing the model is asked; the
49
+ * missing effort is set to the declared one, so the request cannot fall back
50
+ * to a provider default effort. Every other check — model, tools, stream
51
+ * options — then runs on the result exactly as for any worker request, and
52
+ * the forwarded body is indistinguishable from an ordinary lean request: the
53
+ * spend envelope is unchanged. Any other value of either member (a forced
54
+ * `tool_choice`, another temperature) or any other member set is left as is,
55
+ * and refused by the ordinary checks.
56
+ */
57
+ export declare function normalizeGrokCompactionRequest(parsed: Record<string, unknown>, reasoningEffort: string): boolean;
29
58
  export declare function exactLeanTools(tools: unknown): boolean;
30
59
  //# sourceMappingURL=grokBrokerProxyRequest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"grokBrokerProxyRequest.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerProxyRequest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,EAAgE,KAAK,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAGtI,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAAC,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC9L,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,qDAAqD,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC;AAE9K;;;;;;;;;;;GAWG;AACH,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,wBAAwB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,qBAAwD,GAAG,yBAAyB,CAmChO;AASD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAStD"}
1
+ {"version":3,"file":"grokBrokerProxyRequest.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerProxyRequest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,EAAgE,KAAK,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAItI;;;GAGG;AACH,MAAM,MAAM,6BAA6B,GAAG,OAAO,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,gBAAgB,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,kBAAkB,GAAG,OAAO,GAAG,SAAS,GAAG,gBAAgB,CAAC;AAExM,4FAA4F;AAC5F,qBAAa,+BAAgC,SAAQ,KAAK;IAC5C,QAAQ,CAAC,KAAK,EAAE,6BAA6B;gBAApC,KAAK,EAAE,6BAA6B;CAC1D;AAED,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAAC,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC9L,8HAA8H;AAC9H,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,qDAAqD,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAAC,IAAI,EAAE,UAAU,CAAC;IAAC,UAAU,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC;AAEjM;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,wBAAwB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,qBAAwD,GAAG,yBAAyB,CAwChO;AAcD;;;;;;;;;;;;GAYG;AACH,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAOhH;AASD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAStD"}
@@ -2,6 +2,15 @@ import { GROK_ENGINE_BROKER } from "../contracts/runtimeContractManifest.js";
2
2
  import { GROK_WORKER_VISIBLE_TOOLS } from "../contracts/grokWorkerContract.js";
3
3
  import { DEFAULT_GROK_BROKER_MODEL_POLICY, parseGrokBrokerModelPolicy } from "./grokBrokerModelPolicy.js";
4
4
  const MAX_BODY = 2 * 1024 * 1024;
5
+ /** A body-gate refusal. The message is the historic one; `check` names the failed check. */
6
+ export class GrokBrokerProxyRequestRejection extends Error {
7
+ check;
8
+ constructor(check) {
9
+ super("broker proxy request rejected");
10
+ this.check = check;
11
+ }
12
+ }
13
+ function reject(check) { throw new GrokBrokerProxyRequestRejection(check); }
5
14
  /**
6
15
  * Authorizes one worker request and rebuilds it for the provider.
7
16
  *
@@ -13,23 +22,28 @@ const MAX_BODY = 2 * 1024 * 1024;
13
22
  * - the offered tool names are exactly the lean visible set. Grok 1.0.34 turns
14
23
  * an unmappable `--tools` entry into its full 19-tool set, and its per-turn
15
24
  * `session_title` request carries a single forced tool; both are refused.
25
+ *
26
+ * One other shape is admitted, and only by normalizing it into the lean one:
27
+ * Grok 1.0.34's auto-compaction summary request ({@link normalizeGrokCompactionRequest}).
16
28
  */
17
29
  export function authorizeGrokBrokerProxyRequest(input, capabilities, bearer, policy = DEFAULT_GROK_BROKER_MODEL_POLICY) {
18
30
  const declared = parseGrokBrokerModelPolicy(policy);
19
- if (input.method !== "POST" || input.pathname !== "/v1/chat/completions" || input.body.byteLength < 2 || input.body.byteLength > MAX_BODY)
20
- throw new Error("broker proxy request rejected");
31
+ if (input.method !== "POST" || input.pathname !== "/v1/chat/completions")
32
+ reject("route");
33
+ if (input.body.byteLength < 2 || input.body.byteLength > MAX_BODY)
34
+ reject("body_size");
21
35
  const authorization = input.headers.authorization;
22
36
  const match = authorization?.match(/^Bearer ([A-Za-z0-9_-]{40,})$/u);
23
37
  if (match === null || match === undefined)
24
- throw new Error("broker proxy request rejected");
38
+ return reject("bearer");
25
39
  const scope = capabilities.authorizeToken(match[1]);
26
40
  if (scope === undefined || (input.agentId !== undefined && scope.agentId !== input.agentId) || (input.turnId !== undefined && scope.turnId !== input.turnId))
27
- throw new Error("broker proxy request rejected");
41
+ reject("scope");
28
42
  if (!bearer || /[\r\n]/u.test(bearer))
29
43
  throw new Error("broker credential authority unavailable");
30
44
  const clientVersion = input.headers["x-grok-client-version"];
31
45
  if (clientVersion !== GROK_ENGINE_BROKER.grokCliVersion)
32
- throw new Error("broker proxy request rejected");
46
+ reject("client_version");
33
47
  let parsed;
34
48
  // `__proto__` is refused at any depth: JSON.parse makes it an ordinary own
35
49
  // member, but an upstream JavaScript parser may treat it as a prototype.
@@ -41,16 +55,21 @@ export function authorizeGrokBrokerProxyRequest(input, capabilities, bearer, pol
41
55
  });
42
56
  }
43
57
  catch {
44
- throw new Error("broker proxy request rejected");
58
+ return reject("json");
45
59
  }
46
60
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed) || parsed.stream !== true || !Array.isArray(parsed.messages))
47
- throw new Error("broker proxy request rejected");
48
- if (parsed.model !== declared.model || parsed.reasoning_effort !== declared.reasoningEffort)
49
- throw new Error("broker proxy request rejected");
61
+ reject("envelope");
62
+ const compaction = normalizeGrokCompactionRequest(parsed, declared.reasoningEffort);
63
+ if (parsed.model !== declared.model)
64
+ reject("model");
65
+ if (parsed.reasoning_effort !== declared.reasoningEffort)
66
+ reject("reasoning_effort");
50
67
  if (!exactLeanTools(parsed.tools))
51
- throw new Error("broker proxy request rejected");
52
- if (Object.keys(parsed).some((key) => !LEAN_BODY_MEMBERS.has(key)) || !validStreamOptions(parsed.stream_options))
53
- throw new Error("broker proxy request rejected");
68
+ reject("tools");
69
+ if (Object.keys(parsed).some((key) => !LEAN_BODY_MEMBERS.has(key)))
70
+ reject("members");
71
+ if (!validStreamOptions(parsed.stream_options))
72
+ reject("stream_options");
54
73
  // Keep origin/main's production handling: UTF-8 replacement decoding, then
55
74
  // serialize the validated object (including last-member-wins duplicates).
56
75
  // Only a clocked turn takes the stricter date rewrite before this gate.
@@ -61,9 +80,45 @@ export function authorizeGrokBrokerProxyRequest(input, capabilities, bearer, pol
61
80
  "x-xai-token-auth": "xai-grok-cli", "x-grok-model-override": declared.model,
62
81
  "x-grok-client-version": clientVersion, "x-grok-client-identifier": "grok-shell"
63
82
  },
64
- body: Buffer.from(JSON.stringify(parsed))
83
+ body: Buffer.from(JSON.stringify(parsed)),
84
+ ...(compaction ? { compaction: true } : {})
65
85
  };
66
86
  }
87
+ /**
88
+ * Top-level members of Grok 1.0.34's auto-compaction summary request (live stub
89
+ * capture against the pinned binary in the training image, 2026-10-21). When a
90
+ * session nears the model's `context_window`, Grok asks the same model for a
91
+ * summary — single-pass, or a second "two-pass" request — and sends it with
92
+ * the lean tool set, `tool_choice: "auto"` and `temperature: 1`, and *without*
93
+ * `reasoning_effort`. Refusing it does not stop the session: Grok gives up on
94
+ * compaction and carries the whole history forward, so every refusal was a
95
+ * wasted round trip and the context kept growing.
96
+ */
97
+ const COMPACTION_BODY_MEMBERS = new Set(["messages", "model", "stream", "stream_options", "temperature", "tool_choice", "tools"]);
98
+ /**
99
+ * Rewrites, in place, exactly the captured compaction shape into the lean
100
+ * worker shape, and reports whether it did. The two extra members are each
101
+ * the provider's default value (`tool_choice: "auto"` with tools offered,
102
+ * `temperature: 1`), so removing them changes nothing the model is asked; the
103
+ * missing effort is set to the declared one, so the request cannot fall back
104
+ * to a provider default effort. Every other check — model, tools, stream
105
+ * options — then runs on the result exactly as for any worker request, and
106
+ * the forwarded body is indistinguishable from an ordinary lean request: the
107
+ * spend envelope is unchanged. Any other value of either member (a forced
108
+ * `tool_choice`, another temperature) or any other member set is left as is,
109
+ * and refused by the ordinary checks.
110
+ */
111
+ export function normalizeGrokCompactionRequest(parsed, reasoningEffort) {
112
+ const keys = Object.keys(parsed);
113
+ if (keys.length !== COMPACTION_BODY_MEMBERS.size || keys.some((key) => !COMPACTION_BODY_MEMBERS.has(key)))
114
+ return false;
115
+ if (parsed.temperature !== 1 || parsed.tool_choice !== "auto")
116
+ return false;
117
+ delete parsed.temperature;
118
+ delete parsed.tool_choice;
119
+ parsed.reasoning_effort = reasoningEffort;
120
+ return true;
121
+ }
67
122
  /** Top-level members of a Grok 1.0.34 lean worker chat-completions body (live stub capture). */
68
123
  const LEAN_BODY_MEMBERS = new Set(["messages", "model", "reasoning_effort", "stream", "stream_options", "tools"]);
69
124
  /** Members of each lean tool `function` entry (live stub capture). */
@@ -83,8 +83,13 @@ export declare class GrokBrokerTurnMeter {
83
83
  private final;
84
84
  private activityAt;
85
85
  constructor(limits: EngineBrokerTurnLimits, onLimit?: (reason: Exclude<EngineBrokerLimitReason, "none">) => void, now?: () => number);
86
- /** Returns the request index and its upstream abort signal when admitted, the limit that refused it, or `busy` while another request is in flight. */
87
- admit(): Readonly<{
86
+ /**
87
+ * Returns the request index and its upstream abort signal when admitted, the limit that refused it, or `busy` while another request is in flight.
88
+ * `keepFinal` admits a request that cannot replace the answer (a compaction summary), so a final reply already recorded survives it.
89
+ */
90
+ admit(options?: Readonly<{
91
+ keepFinal?: boolean;
92
+ }>): Readonly<{
88
93
  index: number;
89
94
  signal: AbortSignal;
90
95
  } | {
@@ -1 +1 @@
1
- {"version":3,"file":"grokBrokerTurnMeter.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerTurnMeter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"grokBrokerTurnMeter.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerTurnMeter.ts"],"names":[],"mappings":"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"}
@@ -63,8 +63,11 @@ export class GrokBrokerTurnMeter {
63
63
  this.startedAt = now();
64
64
  this.activityAt = this.startedAt;
65
65
  }
66
- /** Returns the request index and its upstream abort signal when admitted, the limit that refused it, or `busy` while another request is in flight. */
67
- admit() {
66
+ /**
67
+ * Returns the request index and its upstream abort signal when admitted, the limit that refused it, or `busy` while another request is in flight.
68
+ * `keepFinal` admits a request that cannot replace the answer (a compaction summary), so a final reply already recorded survives it.
69
+ */
70
+ admit(options = {}) {
68
71
  if (this.reason === "none") {
69
72
  if (this.now() - this.startedAt >= this.limits.timeoutMs)
70
73
  this.trip("timeout");
@@ -78,8 +81,9 @@ export class GrokBrokerTurnMeter {
78
81
  if (this.inFlight !== undefined)
79
82
  return { busy: true };
80
83
  this.timings.push({ startedAt: new Date(this.now()).toISOString() });
81
- // A new request means the previous answer was not the last one.
82
- this.final = undefined;
84
+ // A new sampling request means the previous answer was not the last one.
85
+ if (options.keepFinal !== true)
86
+ this.final = undefined;
83
87
  this.activityAt = this.now();
84
88
  const controller = new AbortController();
85
89
  this.inFlight = { index: this.timings.length - 1, controller };
@@ -58,6 +58,8 @@ export type GrokEngineBrokerTurnDependencies = Readonly<{
58
58
  runNative(input: NativeBrokerTurn, signal: AbortSignal): Promise<Readonly<NativeBrokerTurnResult>>;
59
59
  /** The worker the launcher started for this identity (`/proc` by default); only read when the broker ends a turn itself. */
60
60
  observeWorker?(workerUid: number): Promise<GrokWorkerProcess | undefined>;
61
+ /** Whether the worker identity has a live local tool process (default: `grokWorkerToolProcessLive` over `/proc`). */
62
+ observeToolProcess?(workerUid: number): Promise<boolean>;
61
63
  /** Overrides the turn-end grace and idle bounds (tests). */
62
64
  turnEnd?: GrokBrokerTurnEndTiming;
63
65
  /** Whether no live process holds the worker identity (`/proc` by default); polled, bounded, before an aborted turn is sealed. */
@@ -1 +1 @@
1
- {"version":3,"file":"grokEngineBrokerTurn.d.ts","sourceRoot":"","sources":["../../src/runtime/grokEngineBrokerTurn.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"grokEngineBrokerTurn.d.ts","sourceRoot":"","sources":["../../src/runtime/grokEngineBrokerTurn.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,7 @@ import { finishBrokerTurnWithUsage } from "./grokEngineBrokerMetering.js";
14
14
  import { GrokBrokerTurnMeter } from "./grokBrokerTurnMeter.js";
15
15
  import { awaitGrokWorkerIdentityEmpty, findGrokWorkerProcess, GROK_WORKER_REAP_WAIT_MS, grokWorkerIdentityEmpty, watchGrokBrokerTurnEnd } from "./grokEngineBrokerTurnEnd.js";
16
16
  import { GrokWorkerAttestationFailure } from "./grokWorkerAttestation.js";
17
+ import { grokWorkerToolProcessLive, grokWorkerToolProcessSampler } from "./grokWorkerToolProcess.js";
17
18
  export class EngineBrokerTurnFailure extends Error {
18
19
  code;
19
20
  diagnostic;
@@ -119,12 +120,14 @@ export async function runGrokEngineBrokerTurn(deps, registration, wakeId, prompt
119
120
  deps.proxy.registerTurn(turnId, { policy: registration.model, meter, clock });
120
121
  const capabilityTtlMs = engineBrokerCapabilityTtlMs(limits.timeoutMs);
121
122
  const providerCapability = deps.proxy.capabilities.issue(agentId, turnId, capabilityTtlMs), mcpCapability = deps.mcp.register(agentId, turnId, mcpEndpoint, capabilityTtlMs);
123
+ // A shell command Grok runs itself is work the proxy and facade never see.
124
+ const toolProcess = grokWorkerToolProcessSampler(() => (deps.observeToolProcess ?? grokWorkerToolProcessLive)(registration.workerUid));
122
125
  watchdog = watchGrokBrokerTurnEnd({
123
- meter, activity: () => deps.mcp.activity?.(turnId), signal: controller.signal, ...(deps.turnEnd === undefined ? {} : { timing: deps.turnEnd }),
126
+ meter, activity: () => deps.mcp.activity?.(turnId), toolProcess: toolProcess.live, signal: controller.signal, ...(deps.turnEnd === undefined ? {} : { timing: deps.turnEnd }),
124
127
  // The worker is read once, as soon as it has made a request — long before
125
128
  // anything can end the turn and reap it — so a final reply is never left
126
129
  // without the worker that gave it.
127
- onPoll: () => { if (worker === undefined && meter.snapshot().requests > 0)
130
+ onPoll: () => { toolProcess.poll(); if (worker === undefined && meter.snapshot().requests > 0)
128
131
  worker = (deps.observeWorker ?? findGrokWorkerProcess)(registration.workerUid).catch(() => undefined); },
129
132
  onFinalReply: () => { worker ??= (deps.observeWorker ?? findGrokWorkerProcess)(registration.workerUid).catch(() => undefined); },
130
133
  // An idle stall is a deadline of its own: it trips the meter as a timeout, so it seals like one.
@@ -30,8 +30,12 @@ import type { GrokBrokerTurnMeter } from "./grokBrokerTurnMeter.js";
30
30
  * so a model that keeps going is never cut off. Independently, a turn with no
31
31
  * model request and no tool call in flight and no activity of either kind for
32
32
  * `idleMs` ends as `onEnd("idle")` — the backstop for a worker that stalls
33
- * without ever answering. Shell tools Grok runs itself are invisible here, so
34
- * the idle bound is minutes, not seconds.
33
+ * without ever answering. Shell tools Grok runs itself never reach the proxy
34
+ * or the facade, and Grok lets the model give one a timeout of up to ten
35
+ * hours, so a worker with a live tool process (`toolProcess`,
36
+ * `grokWorkerToolProcess.ts`) is working too: it is never idle while one runs,
37
+ * and the quiet clock restarts from the last poll that saw one. The idle bound
38
+ * stays minutes, not seconds, because that observation is a poll.
35
39
  */
36
40
  export type GrokBrokerTurnEndTiming = Readonly<{
37
41
  finalGraceMs: number;
@@ -45,6 +49,8 @@ export type GrokBrokerTurnEndReason = "final_reply" | "idle";
45
49
  export declare function watchGrokBrokerTurnEnd(input: Readonly<{
46
50
  meter: Pick<GrokBrokerTurnMeter, "finalReply" | "busy" | "lastActivityAt">;
47
51
  activity: () => EngineBrokerMcpActivity | undefined;
52
+ /** Whether the worker has a local tool process alive, as last observed; absent means never observed. */
53
+ toolProcess?: () => boolean;
48
54
  onFinalReply: () => void;
49
55
  /** Every poll, before any decision: the broker records the worker while it is certainly alive. */
50
56
  onPoll?: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"grokEngineBrokerTurnEnd.d.ts","sourceRoot":"","sources":["../../src/runtime/grokEngineBrokerTurnEnd.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAC3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACzG,eAAO,MAAM,oBAAoB,EAAE,uBAAqG,CAAC;AACzI,qGAAqG;AACrG,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,MAAM,MAAM,uBAAuB,GAAG,aAAa,GAAG,MAAM,CAAC;AAE7D,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,QAAQ,CAAC;IACrD,KAAK,EAAE,IAAI,CAAC,mBAAmB,EAAE,YAAY,GAAG,MAAM,GAAG,gBAAgB,CAAC,CAAC;IAC3E,QAAQ,EAAE,MAAM,uBAAuB,GAAG,SAAS,CAAC;IACpD,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,kGAAkG;IAClG,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,CAAC,MAAM,EAAE,uBAAuB,KAAK,IAAI,CAAC;IACjD,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC,GAAG,QAAQ,CAAC;IAAE,IAAI,IAAI,IAAI,CAAA;CAAE,CAAC,CAyB9B;AAED,oJAAoJ;AACpJ,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE9E;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAU,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAqBnH;AAED,2DAA2D;AAC3D,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAU,EAAE,eAAe,UAAQ,GACvD,OAAO,CAAC,OAAO,CAAC,CAgBlB;AAED;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAA2B,EAAE,MAAM,SAAM,EAAE,QAAQ,SAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAarM"}
1
+ {"version":3,"file":"grokEngineBrokerTurnEnd.d.ts","sourceRoot":"","sources":["../../src/runtime/grokEngineBrokerTurnEnd.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAC3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACzG,eAAO,MAAM,oBAAoB,EAAE,uBAAqG,CAAC;AACzI,qGAAqG;AACrG,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,MAAM,MAAM,uBAAuB,GAAG,aAAa,GAAG,MAAM,CAAC;AAE7D,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,QAAQ,CAAC;IACrD,KAAK,EAAE,IAAI,CAAC,mBAAmB,EAAE,YAAY,GAAG,MAAM,GAAG,gBAAgB,CAAC,CAAC;IAC3E,QAAQ,EAAE,MAAM,uBAAuB,GAAG,SAAS,CAAC;IACpD,wGAAwG;IACxG,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;IAC5B,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,kGAAkG;IAClG,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,CAAC,MAAM,EAAE,uBAAuB,KAAK,IAAI,CAAC;IACjD,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC,GAAG,QAAQ,CAAC;IAAE,IAAI,IAAI,IAAI,CAAA;CAAE,CAAC,CA8B9B;AAED,oJAAoJ;AACpJ,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE9E;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAU,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAqBnH;AAED,2DAA2D;AAC3D,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAU,EAAE,eAAe,UAAQ,GACvD,OAAO,CAAC,OAAO,CAAC,CAgBlB;AAED;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAA2B,EAAE,MAAM,SAAM,EAAE,QAAQ,SAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAarM"}
@@ -5,7 +5,7 @@ export const GROK_BROKER_TURN_END = Object.freeze({ finalGraceMs: 15_000, idleMs
5
5
  export const GROK_WORKER_REAP_WAIT_MS = 10_000;
6
6
  export function watchGrokBrokerTurnEnd(input) {
7
7
  const timing = input.timing ?? GROK_BROKER_TURN_END, now = input.now ?? Date.now;
8
- let notified, done = false;
8
+ let notified, done = false, toolSeenAt = 0;
9
9
  const check = () => {
10
10
  if (done)
11
11
  return;
@@ -32,11 +32,22 @@ export function watchGrokBrokerTurnEnd(input) {
32
32
  catch {
33
33
  activity = undefined;
34
34
  }
35
+ let tool = false;
36
+ try {
37
+ tool = input.toolProcess?.() === true;
38
+ }
39
+ catch {
40
+ tool = false;
41
+ }
42
+ if (tool)
43
+ toolSeenAt = now();
35
44
  if (input.meter.busy() || (activity?.inFlight ?? 0) > 0)
36
45
  return;
37
46
  const quietSince = Math.max(input.meter.lastActivityAt(), activity?.lastActivityAt ?? 0);
47
+ // A tool process defers only the idle verdict. A final reply called no tool, so a child still
48
+ // alive beside it is not the model's work and must not hold a finished worker open.
38
49
  const reason = final !== undefined && now() - Math.max(final.at, quietSince) >= timing.finalGraceMs ? "final_reply"
39
- : now() - quietSince >= timing.idleMs ? "idle" : undefined;
50
+ : now() - Math.max(quietSince, toolSeenAt) >= timing.idleMs ? "idle" : undefined;
40
51
  if (reason === undefined)
41
52
  return;
42
53
  stop();
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Whether a Grok worker is running one of its own local tools right now,
3
+ * read-only from `/proc`.
4
+ *
5
+ * The broker sees every model request (the proxy) and every MCP tool call (the
6
+ * facade), but not the tools Grok runs in-process: `run_terminal_command`
7
+ * executes as a child of the worker. Grok 1.0.34 advertises that tool with a
8
+ * model-chosen timeout of up to 36 000 000 ms (default 120 000 ms), so a
9
+ * command the model gave a long timeout is real work the broker cannot see,
10
+ * and the idle watchdog (`grokEngineBrokerTurnEnd.ts`) used to end such a turn
11
+ * as stalled ten minutes in.
12
+ *
13
+ * Every engine process carries the argv the launcher compiled — the outer
14
+ * bubblewrap wrapper Grok re-executes into and the Grok process inside it —
15
+ * so `--output-format streaming-messages-json` marks them. A live (non-zombie)
16
+ * process owned by `uid` whose parent is such an engine process, and which is
17
+ * not one itself, is a tool the worker started and is waiting on. Short-lived
18
+ * helpers count too, which only delays an idle verdict by a poll.
19
+ *
20
+ * An unreadable `/proc` (or one mounted `hidepid`) answers `false`: the
21
+ * watchdog then behaves exactly as it did without this signal.
22
+ */
23
+ export declare function grokWorkerToolProcessLive(uid: number, procRoot?: string): Promise<boolean>;
24
+ /**
25
+ * The watchdog's poll is synchronous and `/proc` is not, so the turn keeps the
26
+ * last answer: `poll()` starts one read when none is pending, `live()` returns
27
+ * the latest completed one. A failed read answers `false`, as an unreadable
28
+ * `/proc` does.
29
+ */
30
+ export declare function grokWorkerToolProcessSampler(read: () => Promise<boolean>): Readonly<{
31
+ poll(): void;
32
+ live(): boolean;
33
+ }>;
34
+ //# sourceMappingURL=grokWorkerToolProcess.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grokWorkerToolProcess.d.ts","sourceRoot":"","sources":["../../src/runtime/grokWorkerToolProcess.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAoBjG;AAQD;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC;IAAE,IAAI,IAAI,IAAI,CAAC;IAAC,IAAI,IAAI,OAAO,CAAA;CAAE,CAAC,CAUtH"}
@@ -0,0 +1,78 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ /**
3
+ * Whether a Grok worker is running one of its own local tools right now,
4
+ * read-only from `/proc`.
5
+ *
6
+ * The broker sees every model request (the proxy) and every MCP tool call (the
7
+ * facade), but not the tools Grok runs in-process: `run_terminal_command`
8
+ * executes as a child of the worker. Grok 1.0.34 advertises that tool with a
9
+ * model-chosen timeout of up to 36 000 000 ms (default 120 000 ms), so a
10
+ * command the model gave a long timeout is real work the broker cannot see,
11
+ * and the idle watchdog (`grokEngineBrokerTurnEnd.ts`) used to end such a turn
12
+ * as stalled ten minutes in.
13
+ *
14
+ * Every engine process carries the argv the launcher compiled — the outer
15
+ * bubblewrap wrapper Grok re-executes into and the Grok process inside it —
16
+ * so `--output-format streaming-messages-json` marks them. A live (non-zombie)
17
+ * process owned by `uid` whose parent is such an engine process, and which is
18
+ * not one itself, is a tool the worker started and is waiting on. Short-lived
19
+ * helpers count too, which only delays an idle verdict by a poll.
20
+ *
21
+ * An unreadable `/proc` (or one mounted `hidepid`) answers `false`: the
22
+ * watchdog then behaves exactly as it did without this signal.
23
+ */
24
+ export async function grokWorkerToolProcessLive(uid, procRoot = "/proc") {
25
+ let entries;
26
+ try {
27
+ entries = await readdir(procRoot);
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ const owned = new Map();
33
+ for (const entry of entries) {
34
+ if (!/^\d+$/u.test(entry))
35
+ continue;
36
+ try {
37
+ const status = await readFile(`${procRoot}/${entry}/status`, "utf8");
38
+ if (Number(/^Uid:\s+(\d+)/mu.exec(status)?.[1]) !== uid)
39
+ continue;
40
+ if (/^State:\s+[ZX]/mu.test(status))
41
+ continue;
42
+ const parent = Number(/^PPid:\s+(\d+)/mu.exec(status)?.[1]);
43
+ if (!Number.isSafeInteger(parent))
44
+ continue;
45
+ const argv = (await readFile(`${procRoot}/${entry}/cmdline`, "utf8")).split("\0");
46
+ owned.set(Number(entry), { parent, engine: isGrokEngineArgv(argv) });
47
+ }
48
+ catch { /* a process exiting mid-scan is not part of the answer */ }
49
+ }
50
+ for (const process of owned.values()) {
51
+ if (!process.engine && owned.get(process.parent)?.engine === true)
52
+ return true;
53
+ }
54
+ return false;
55
+ }
56
+ /** The compiled worker argv pair every engine process of a brokered turn carries (`grokBrokerWorkerConfig.ts`). */
57
+ function isGrokEngineArgv(argv) {
58
+ const flag = argv.indexOf("--output-format");
59
+ return flag !== -1 && argv[flag + 1] === "streaming-messages-json";
60
+ }
61
+ /**
62
+ * The watchdog's poll is synchronous and `/proc` is not, so the turn keeps the
63
+ * last answer: `poll()` starts one read when none is pending, `live()` returns
64
+ * the latest completed one. A failed read answers `false`, as an unreadable
65
+ * `/proc` does.
66
+ */
67
+ export function grokWorkerToolProcessSampler(read) {
68
+ let live = false, pending = false;
69
+ return {
70
+ poll: () => {
71
+ if (pending)
72
+ return;
73
+ pending = true;
74
+ void Promise.resolve().then(read).then((value) => { live = value; }, () => { live = false; }).finally(() => { pending = false; });
75
+ },
76
+ live: () => live
77
+ };
78
+ }
@@ -226,7 +226,7 @@ function moltnetTool(agent, wakeContext) {
226
226
  }
227
227
  async function connect(agent, server) {
228
228
  taskClockChildEnvironment(server.env);
229
- const client = new Client({ name: "daimon-production", version: "0.2.2" });
229
+ const client = new Client({ name: "daimon-production", version: "0.2.4" });
230
230
  const headers = server.authSecretEnv === undefined ? {} : { authorization: `Bearer ${requiredSecret(server.authSecretEnv)}` };
231
231
  const transport = server.transport === "stdio"
232
232
  ? new StdioClientTransport({ command: server.command, args: [...server.args], env: stringEnvironment(readyTaskClockEnvironment({ ...server.env, ...cliChildEnvironment([], agent.runtimeHomePath, { executablePath: server.command }) })) })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noopolis/daimon",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Noopolis native per-agent runtime harness built on Pi.",
5
5
  "license": "MIT",
6
6
  "type": "module",