@koda-sl/baker-bridge 0.71.0-dev.be88d2a8c.37a228cd → 0.71.2-dev.5e66c2900
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/README.md +43 -14
- package/dist/hono/bridge-job.d.ts +32 -1
- package/dist/hono/bridge-job.d.ts.map +1 -1
- package/dist/hono/bridge-job.js +109 -26
- package/dist/hono/bridge-job.js.map +1 -1
- package/dist/hono/bridge-job.test.js +156 -4
- package/dist/hono/bridge-job.test.js.map +1 -1
- package/dist/hono/connection-input.d.ts +4 -52
- package/dist/hono/connection-input.d.ts.map +1 -1
- package/dist/hono/connection-input.js +4 -22
- package/dist/hono/connection-input.js.map +1 -1
- package/dist/hono/connection-input.test.js +1 -20
- package/dist/hono/connection-input.test.js.map +1 -1
- package/dist/hono/context-usage.d.ts +18 -0
- package/dist/hono/context-usage.d.ts.map +1 -1
- package/dist/hono/context-usage.js +51 -0
- package/dist/hono/context-usage.js.map +1 -1
- package/dist/hono/context-usage.test.js +23 -1
- package/dist/hono/context-usage.test.js.map +1 -1
- package/dist/hono/convex.d.ts +1 -1
- package/dist/hono/convex.d.ts.map +1 -1
- package/dist/hono/convex.js.map +1 -1
- package/dist/hono/relay.d.ts +1 -1
- package/dist/hono/relay.d.ts.map +1 -1
- package/dist/hono/relay.js.map +1 -1
- package/dist/hono/session-continuity.d.ts +4 -3
- package/dist/hono/session-continuity.d.ts.map +1 -1
- package/dist/hono/session-continuity.js +6 -5
- package/dist/hono/session-continuity.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -176,7 +176,7 @@ Every turn also carries an in-process SDK MCP server named `baker_ui` hosting in
|
|
|
176
176
|
|
|
177
177
|
Its second tool, `request_flow_input`, is the **blocking form-configuration form**: the agent invokes it to configure the confidential parts of a flow ("Form") it cannot author itself — side-effect credentials/connections (`encryptedConfig`, `oauthProviderId`) and widget third-party field definitions (`nodeData.form.external` for HubSpot/Calendly/HighLevel/SavvyCal). The agent authors the surrounding structure in `_data.json` and references it by `nodeId`/`sideEffectId`. Same `canUseTool` park pattern: the bridge POSTs `/api/chat/flow-input-request`, the dashboard renders a tabbed form (secret inputs / OAuth connect+account picker / live resource picker), and on submit Convex delivers the resolved values to `POST /flow-input-decision`. Unlike tags, the resolved values are written **into the branch flow file** through the encrypting `writeFlow` (read → patch node/side-effect → write); the model only ever sees the redacted `{ status, nodeId, sideEffectId?, secretFieldsSet, note }`. Leaked secret values in `prefilledConfig` are stripped bridge-side (and again backend-side). Live wait matches `AskUserQuestion` (15 min); like all blocking prompts, the form itself never expires — the thread parks and a later submission recovers the turn.
|
|
178
178
|
|
|
179
|
-
Its third tool, `request_connection`, is the **blocking connection request**: the agent invokes it when a data platform the task needs — Google Ads, LinkedIn Ads, Google Analytics 4, Google Search Console, Google Tag Manager
|
|
179
|
+
Its third tool, `request_connection`, is the **blocking connection request**: the agent invokes it when a data platform the task needs — Google Ads, LinkedIn Ads, Google Analytics 4, Google Search Console, or Google Tag Manager — is not connected yet, instead of telling the user to go to Settings. Same `canUseTool` park pattern: the bridge POSTs `/api/chat/connection-input-request`, the dashboard renders one tab per platform with the OAuth connect button and the account/property/site/container picker, and on submit Convex delivers the outcome to `POST /connection-input-decision`. Nothing confidential passes through the bridge or the model: the dashboard writes the selection onto the company's connection, and the resolution carries only `{ status, platform, accountLabel?, selected[] }`. Meta and X Ads are deliberately not offered — they are still coming soon. Live wait matches `AskUserQuestion` (15 min).
|
|
180
180
|
|
|
181
181
|
## MCP servers (Composio)
|
|
182
182
|
|
|
@@ -298,8 +298,19 @@ Convex action ──POST /message/async──▶ Bridge AgentSession
|
|
|
298
298
|
- **Context window usage** is relayed after every turn to `POST
|
|
299
299
|
/api/chat/context-usage` as `{ tokens, window }`. Occupied `tokens` come from
|
|
300
300
|
the **last main-conversation request's own usage** (`input_tokens +
|
|
301
|
-
cache_read_input_tokens + cache_creation_input_tokens`
|
|
302
|
-
|
|
301
|
+
cache_read_input_tokens + cache_creation_input_tokens`) — the real fill of the
|
|
302
|
+
window at that request. It is read from **two** places, because neither is
|
|
303
|
+
reliable alone: the assembled assistant message, and the raw
|
|
304
|
+
`message_delta` stream event (`streamEventContextUsage`). Anthropic reports a
|
|
305
|
+
request's input counts on `message_start`, which is where the runtime builds
|
|
306
|
+
the message's `usage` from — but the OpenRouter passthrough sends
|
|
307
|
+
`message_start` empty and puts the real numbers on `message_delta`, so the
|
|
308
|
+
assistant message arrives all zeros. Reading only the message meant the meter
|
|
309
|
+
never had a single reading in production: 3,910 assistant messages in one day,
|
|
310
|
+
every one reporting zero input tokens, so no chat ever stored a
|
|
311
|
+
`contextUsage` and the meter never rendered. A zero from either source is
|
|
312
|
+
ignored rather than written, so the meter holds its last real value instead of
|
|
313
|
+
resetting. The bridge does *not*
|
|
303
314
|
use the turn `result`'s per-model token totals: those aggregate every API
|
|
304
315
|
round-trip in the turn (one per tool call, plus any same-model subagent work),
|
|
305
316
|
so summing them inflates the reading by roughly the number of tool calls and
|
|
@@ -377,17 +388,34 @@ Convex action ──POST /message/async──▶ Bridge AgentSession
|
|
|
377
388
|
for a summary before anything happened — the user watched "Summarizing this chat
|
|
378
389
|
to free up space…" instead of a publish starting. A `/publish` turn therefore
|
|
379
390
|
runs with `settings.autoCompactEnabled: false`: same session, same context, it
|
|
380
|
-
just starts working.
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
391
|
+
just starts working.
|
|
392
|
+
- **An overflowed publish restarts itself instead of failing.** When a request is
|
|
393
|
+
refused for size (`isContextOverflowError` — Anthropic's `prompt is too long`,
|
|
394
|
+
OpenRouter's `maximum context length`), `contextOverflowRecovery` abandons the
|
|
395
|
+
oversized session, clears the stored boundary, and re-runs the turn on a fresh
|
|
396
|
+
one. Summarizing is *not* the first move and usually not a move at all: once
|
|
397
|
+
the conversation is past the window, the summary request has to send that same
|
|
398
|
+
conversation and is refused identically — in production the compaction attempt
|
|
399
|
+
died 0.9s after it started, leaving a spinner that never resolved and a failed
|
|
400
|
+
publish, while resending `/publish` (which lands on a fresh session, because
|
|
401
|
+
the failed turn already cleared the dirty one) worked every time.
|
|
402
|
+
- **Only `/publish` restarts.** The refusal lands pre-flight, before the agent
|
|
403
|
+
has done anything, and publish works from the state of the workspace rather
|
|
404
|
+
than from the conversation — so it re-enters a fresh session as the bare
|
|
405
|
+
command it already is, having lost nothing. An ordinary message is answered
|
|
406
|
+
*from* the conversation; restarting it would trade a live session for a short
|
|
407
|
+
rehydrated history and quietly give a worse answer, so it is left to fail and
|
|
408
|
+
say so. It gives up no context by failing — `clearIfDirty` drops the dirty
|
|
409
|
+
session either way, so resending lands on the same rehydrated history.
|
|
410
|
+
- **Compaction** is kept for the one case where the restart would cost
|
|
411
|
+
something: the agent has already produced work this turn (`resumeSessionAt`
|
|
412
|
+
set), so it retries once with compaction allowed, resuming the in-flight
|
|
413
|
+
session (forked). One restart per turn; it has already dropped everything a
|
|
414
|
+
second one could drop.
|
|
386
415
|
- **The refusal itself is never shown.** The CLI renders a refused request as an
|
|
387
416
|
assistant message *before* the query throws, and relayed text is never
|
|
388
|
-
retracted — so
|
|
389
|
-
|
|
390
|
-
owed its compaction retry, that message is dropped outright
|
|
417
|
+
retracted — so recovery used to leave a raw `Prompt is too long` in the chat,
|
|
418
|
+
above the publish that then succeeded. That message is dropped outright
|
|
391
419
|
(`isContextOverflowRefusal`: a single text block, ≤200 chars, leading with the
|
|
392
420
|
provider's error *and* matching `isContextOverflowError` — so `API Error: 400 …
|
|
393
421
|
prompt is too long` is dropped, `API Error: 500` and an answer that merely
|
|
@@ -397,8 +425,9 @@ Convex action ──POST /message/async──▶ Bridge AgentSession
|
|
|
397
425
|
changes the leftover cannot come back as the turn's end-of-turn post. Dropping
|
|
398
426
|
it before the session observer also stops it from counting as
|
|
399
427
|
work done, which would otherwise make the retry resume with "continue where you
|
|
400
|
-
left off" instead of delivering the `/publish` the turn still owes.
|
|
401
|
-
|
|
428
|
+
left off" instead of delivering the `/publish` the turn still owes. A turn the
|
|
429
|
+
restart cannot save reports the failure once, in product language — the
|
|
430
|
+
provider's wording never reaches the chat.
|
|
402
431
|
- **Compaction progress + confirmation.** A compaction summarizes the session to
|
|
403
432
|
free context. A manual `/compact` turn emits no assistant text, so it would
|
|
404
433
|
otherwise complete with nothing visible; an automatic compaction happens
|
|
@@ -133,7 +133,7 @@ export type BridgeJobRelay = {
|
|
|
133
133
|
heartbeat(target: CallbackTarget): Promise<void>;
|
|
134
134
|
compaction(target: CallbackTarget, state: "compacting" | "done"): Promise<void>;
|
|
135
135
|
contextUsage(target: CallbackTarget, usage: ContextUsage): Promise<void>;
|
|
136
|
-
contextSource(target: CallbackTarget, source: "local_resume" | "rehydrated_missing_session" | "rehydrated_invalid_resume" | "rehydrated_repaired_tool_state" | "rehydration_failed", metadata: Record<string, unknown>): Promise<void>;
|
|
136
|
+
contextSource(target: CallbackTarget, source: "local_resume" | "rehydrated_missing_session" | "rehydrated_invalid_resume" | "rehydrated_repaired_tool_state" | "rehydrated_context_overflow" | "rehydration_failed", metadata: Record<string, unknown>): Promise<void>;
|
|
137
137
|
complete(target: CallbackTarget, outcome: BridgeJobOutcome): Promise<void>;
|
|
138
138
|
};
|
|
139
139
|
export type AgentSdkQuery = AsyncIterable<SDKMessage> & {
|
|
@@ -239,6 +239,37 @@ export declare class BridgeJobExecutor {
|
|
|
239
239
|
private loadTrustedBoundary;
|
|
240
240
|
private prepareRequestPrompt;
|
|
241
241
|
private runAttempt;
|
|
242
|
+
/**
|
|
243
|
+
* The next attempt for a request the provider refused for size, or null when
|
|
244
|
+
* the turn has no move left.
|
|
245
|
+
*
|
|
246
|
+
* Restarting on a fresh session is the FIRST choice, not the last. Once the
|
|
247
|
+
* conversation itself is past the window, summarizing it cannot rescue the
|
|
248
|
+
* turn: the summary request has to send that same conversation and is refused
|
|
249
|
+
* the same way. Production bore this out — the compaction attempt died 0.9s
|
|
250
|
+
* after it started, leaving a spinner that never resolved and a raw provider
|
|
251
|
+
* error above a failed publish, while the user's own fix (send `/publish`
|
|
252
|
+
* again, which lands on a fresh session because the failed turn cleared the
|
|
253
|
+
* dirty one) worked every time. This does that fix automatically.
|
|
254
|
+
*
|
|
255
|
+
* Abandoning the session is free *for publish specifically*: the refusal lands
|
|
256
|
+
* pre-flight, before the agent has done anything, and `/publish` re-enters a
|
|
257
|
+
* fresh session as the bare command it already is — it publishes from the state
|
|
258
|
+
* of the workspace, not from the conversation, so nothing it needs was in the
|
|
259
|
+
* session that got dropped. An ordinary message is answered *from* that
|
|
260
|
+
* context, so restarting it would quietly swap a live session for a short
|
|
261
|
+
* rehydrated history and give a worse answer with no explanation. That turn is
|
|
262
|
+
* left to fail and say so. It loses nothing by failing: a failed turn clears
|
|
263
|
+
* the dirty session anyway (`clearIfDirty`), so resending lands on exactly the
|
|
264
|
+
* rehydrated context the restart would have used — the user just gets told.
|
|
265
|
+
*
|
|
266
|
+
* Compaction is kept for the one case where a restart WOULD cost something —
|
|
267
|
+
* the agent already produced work this turn, so a fresh session would make it
|
|
268
|
+
* redo the steps it just took. `resumeSessionAt` is that test: it is set only
|
|
269
|
+
* by an assistant message, whereas `sessionId` arrives with the CLI's `init`,
|
|
270
|
+
* before the first model request.
|
|
271
|
+
*/
|
|
272
|
+
private contextOverflowRecovery;
|
|
242
273
|
private finishSuccessfulAttempt;
|
|
243
274
|
private errorOutcome;
|
|
244
275
|
private resultWithObservedCost;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-job.d.ts","sourceRoot":"","sources":["../../src/hono/bridge-job.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAoB,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC1G,OAAO,EAAE,KAAK,SAAS,EAAwE,MAAM,kBAAkB,CAAC;AACxH,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAE/B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,YAAY,
|
|
1
|
+
{"version":3,"file":"bridge-job.d.ts","sourceRoot":"","sources":["../../src/hono/bridge-job.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAoB,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC1G,OAAO,EAAE,KAAK,SAAS,EAAwE,MAAM,kBAAkB,CAAC;AACxH,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAE/B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,YAAY,EAMlB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EAGzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAIL,KAAK,kBAAkB,EAExB,MAAM,kBAAkB,CAAC;AAK1B,OAAO,EAKL,KAAK,kBAAkB,EASxB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAEvF,OAAO,EAKL,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAuC,KAAK,cAAc,EAAsB,MAAM,uBAAuB,CAAC;AACrH,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5G,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;IAC/B,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAClC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,aAAa,CAAC,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEzG,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAarE;AAED,MAAM,MAAM,gBAAgB,GACxB;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,qBAAqB,CAAC;CACvC,GACD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,cAAc,EAAE,CAAA;CAAE,GACrF;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE1G,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IACnD,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,KAAK,IAAI,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;IAC1C,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,KAAK,IAAI,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,CAAC,UAAU,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACnD,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,KAAK,IAAI,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,CAAC,UAAU,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACnD,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,KAAK,IAAI,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,CAAC,UAAU,EAAE,yBAAyB,KAAK,IAAI,CAAC;IACzD,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,KAAK,IAAI,CAAC;CACjC,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,OAAO,EAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBxB;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC/C,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACvC,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC/C,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACjD,uBAAuB,EAAE,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IAC7D,SAAS,CAAC,IAAI,IAAI,CAAC;IACnB,QAAQ,IAAI,OAAO,CAAC;CACrB,CAAC;AAEF,KAAK,aAAa,GAAG;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,KAAK,cAAc,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AA8B3D,MAAM,MAAM,cAAc,GAAG;IAC3B,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC3C,WAAW,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACzD,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC;IACnE,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzF,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3G,aAAa,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,eAAe,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,cAAc,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrH,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,gBAAgB,CACd,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,qBAAqB,EAAE,CAAA;KAAE,GAChE,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,sBAAsB,CACpB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,sBAAsB,EAAE,CAAA;KAAE,GACpE,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,uBAAuB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,UAAU,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnG,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,aAAa,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,UAAU,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChF,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE,aAAa,CACX,MAAM,EAAE,cAAc,EACtB,MAAM,EACF,cAAc,GACd,4BAA4B,GAC5B,2BAA2B,GAC3B,gCAAgC,GAChC,6BAA6B,GAC7B,oBAAoB,EACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,QAAQ,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5E,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,aAAa,CAAC,UAAU,CAAC,GAAG;IACtD,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,KAAK,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,GAAG,aAAa,CAAC;IAClE,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;CACnD,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,GAAG,EAAE,eAAe,CAAC;IACrB,KAAK,EAAE,cAAc,CAAC;IACtB,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE;QACZ,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAAC;QACnE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,qBAAqB,EAAE,OAAO,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QACtG,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,kBAAkB,CAAA;SAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACnG,CAAC;IACF,kBAAkB,CAAC,EAAE,CAAC,WAAW,EAAE,cAAc,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACxE,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,CAAC;AA6DF,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,cAGC;CACF;AA8JD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAM3D;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAE1D;AAkED,2FAA2F;AAC3F,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAgB7D;AA8BD;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAcjE;AA8BD,MAAM,WAAW,aAAa;IAC5B,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;IACtC,OAAO,IAAI,OAAO,CAAC;IACnB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gBAAgB,EAAE,MAAM,OAAO,CAAC;IAChC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,GAAG,aAAa,CAiChB;AAkMD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkB;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgD;IAC7E,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAmE;IACtG,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAqB;IACzD,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAS;IAClD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IAEnC,YAAY,YAAY,EAAE,6BAA6B,EAatD;IAEK,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA+CpF;YAEa,mBAAmB;YAanB,oBAAoB;YAkBpB,UAAU;IAqCxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;YACW,uBAAuB;YAsCvB,uBAAuB;IA2BrC,OAAO,CAAC,YAAY;IA6BpB,OAAO,CAAC,sBAAsB;IAI9B,OAAO,CAAC,0BAA0B;YAkBpB,4BAA4B;YAgC5B,iBAAiB;IAY/B,OAAO,CAAC,iBAAiB;IASzB;;;;OAIG;IACH,OAAO,CAAC,YAAY;YA+BN,YAAY;YA8KZ,UAAU;IA0ExB,OAAO,CAAC,mBAAmB;YAoBb,iBAAiB;YA6FjB,aAAa;YA6Cb,mBAAmB;IAkCjC,OAAO,CAAC,uBAAuB;YAqBjB,2BAA2B;YAmB3B,YAAY;IAgB1B,OAAO,CAAC,mBAAmB;YAsCb,qBAAqB;YAoCrB,qBAAqB;YAkDrB,sBAAsB;YAkDtB,4BAA4B;YAyC5B,gBAAgB;CAwC/B"}
|
package/dist/hono/bridge-job.js
CHANGED
|
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { applyInFlightTask, applyTaskLifecycleMessage, backgroundTaskSnapshot } from "./agent-tasks.js";
|
|
5
5
|
import { CONNECTION_INPUT_TOOL_NAME, normalizeConnectionInputToolInput, } from "./connection-input.js";
|
|
6
|
-
import { messageContextTokens, pickContextWindow, turnContextUsage, } from "./context-usage.js";
|
|
6
|
+
import { messageContextTokens, pickContextWindow, streamEventContextUsage, turnContextUsage, } from "./context-usage.js";
|
|
7
7
|
import { FLOW_INPUT_TOOL_NAME, normalizeFlowInputToolInput, sanitizeFlowRequests, } from "./flow-input.js";
|
|
8
8
|
import { composioScopesToInvalidate, failedCustomServers, reportMcpInvalid, } from "./mcp-servers.js";
|
|
9
9
|
import { autoCompactWindowForModel, betasForModel, contextWindowForModel } from "./model-capabilities.js";
|
|
@@ -12,7 +12,7 @@ import { appendCurrentReferenceContext } from "./prompt-context.js";
|
|
|
12
12
|
import { bridgeRuntimeFiles } from "./runtime-files.js";
|
|
13
13
|
import { applyCurrentSenderAttribution, buildCleanBoundary, buildRehydratedPrompt, compactionRetryAttempt, emptyInProgressSession, isContextOverflowError, isResumeSessionError, observeInProgressSession, prepareSessionAttempt, stallContinuationAttempt, } from "./session-continuity.js";
|
|
14
14
|
import { readSessionSkills } from "./session-skills.js";
|
|
15
|
-
import { verbatimSlashCommand } from "./slash-commands.js";
|
|
15
|
+
import { isPublishCommandPrompt, verbatimSlashCommand } from "./slash-commands.js";
|
|
16
16
|
import { createBakerUiServer, normalizeTagInputToolInput, sanitizeTagChanges, TAG_INPUT_TOOL_NAME, } from "./tag-input.js";
|
|
17
17
|
import { addQueryModelUsage, queryModelUsage, turnModelUsageList } from "./turn-model-usage.js";
|
|
18
18
|
export function parsePlanDecision(value) {
|
|
@@ -126,7 +126,7 @@ function createBridgeJobState() {
|
|
|
126
126
|
inProgressSession: emptyInProgressSession(),
|
|
127
127
|
observedCostUsd: 0,
|
|
128
128
|
controlChannelDenies: 0,
|
|
129
|
-
|
|
129
|
+
restartedOnFreshSession: false,
|
|
130
130
|
modelUsageTotals: new Map(),
|
|
131
131
|
queryModelUsage: new Map(),
|
|
132
132
|
};
|
|
@@ -152,6 +152,18 @@ function isUserVisibleSdkMessage(msg) {
|
|
|
152
152
|
const delta = event.delta;
|
|
153
153
|
return delta?.type === "text_delta" && typeof delta.text === "string" && delta.text.length > 0;
|
|
154
154
|
}
|
|
155
|
+
// What the user reads when a turn ends on a size refusal the restart could not
|
|
156
|
+
// save. "Prompt is too long" is the provider talking to us, not the product
|
|
157
|
+
// talking to a marketer: it names nothing they did and nothing they can do. The
|
|
158
|
+
// failed turn clears the session, so sending again genuinely does start from the
|
|
159
|
+
// shorter rehydrated history — which is the action this asks for.
|
|
160
|
+
const CONTEXT_OVERFLOW_MESSAGE = "This chat has grown too long to continue in one step. Send your message again — it will pick up from a shorter version of this conversation.";
|
|
161
|
+
function userFacingTurnError(message) {
|
|
162
|
+
if (isContextOverflowError(message)) {
|
|
163
|
+
return CONTEXT_OVERFLOW_MESSAGE;
|
|
164
|
+
}
|
|
165
|
+
return enrichImageError(message);
|
|
166
|
+
}
|
|
155
167
|
function enrichImageError(message) {
|
|
156
168
|
if (!message.toLowerCase().includes("could not process image")) {
|
|
157
169
|
return message;
|
|
@@ -586,15 +598,32 @@ function createTurnEndController(job, query, closeQuery, timings) {
|
|
|
586
598
|
}
|
|
587
599
|
// Track the occupancy of the last main-conversation request. The meter reads a
|
|
588
600
|
// single request's usage, not the turn's aggregate totals — and only from the
|
|
589
|
-
// main conversation, so subagent work never pollutes it.
|
|
590
|
-
//
|
|
591
|
-
//
|
|
592
|
-
//
|
|
601
|
+
// main conversation, so subagent work never pollutes it.
|
|
602
|
+
//
|
|
603
|
+
// Two sources, because neither is reliable alone. The assembled assistant
|
|
604
|
+
// message is the natural one, but through the OpenRouter passthrough every
|
|
605
|
+
// provider route we run reports it as all zeros: the runtime builds that usage
|
|
606
|
+
// from `message_start`, which the passthrough sends empty, and puts the real
|
|
607
|
+
// numbers on `message_delta` instead. Production showed the cost of trusting
|
|
608
|
+
// only the message — 3,910 assistant messages in a day, every one reporting zero
|
|
609
|
+
// input tokens, so the meter had no reading to show and never appeared at all.
|
|
610
|
+
//
|
|
611
|
+
// Zero is never a real occupancy, so a zero from either source keeps the
|
|
612
|
+
// previous reading rather than resetting the meter.
|
|
593
613
|
function trackMainRequestUsage(state, msg) {
|
|
614
|
+
if (msg.type === "stream_event") {
|
|
615
|
+
if (msg.parent_tool_use_id) {
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
recordMainRequestUsage(state, streamEventContextUsage(msg.event));
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
594
621
|
if (msg.type !== "assistant" || msg.parent_tool_use_id || msg.subagent_type) {
|
|
595
622
|
return;
|
|
596
623
|
}
|
|
597
|
-
|
|
624
|
+
recordMainRequestUsage(state, msg.message.usage);
|
|
625
|
+
}
|
|
626
|
+
function recordMainRequestUsage(state, usage) {
|
|
598
627
|
if (usage && messageContextTokens(usage) > 0) {
|
|
599
628
|
state.lastMainRequestUsage = usage;
|
|
600
629
|
}
|
|
@@ -710,14 +739,12 @@ export class BridgeJobExecutor {
|
|
|
710
739
|
break;
|
|
711
740
|
}
|
|
712
741
|
catch (error) {
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
current = compactionRetryAttempt(current, state.inProgressSession);
|
|
720
|
-
continue;
|
|
742
|
+
if (isContextOverflowError(error) && controls.isActive()) {
|
|
743
|
+
const recovered = await this.contextOverflowRecovery(job, controls, state, attempt, current);
|
|
744
|
+
if (recovered) {
|
|
745
|
+
current = recovered;
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
721
748
|
}
|
|
722
749
|
if (!(error instanceof StreamStallError) || stallRetries >= MAX_STALL_RETRIES || !controls.isActive()) {
|
|
723
750
|
throw error;
|
|
@@ -729,6 +756,62 @@ export class BridgeJobExecutor {
|
|
|
729
756
|
}
|
|
730
757
|
return { costUsd, attemptedRehydration: attempt.attemptedRehydration };
|
|
731
758
|
}
|
|
759
|
+
/**
|
|
760
|
+
* The next attempt for a request the provider refused for size, or null when
|
|
761
|
+
* the turn has no move left.
|
|
762
|
+
*
|
|
763
|
+
* Restarting on a fresh session is the FIRST choice, not the last. Once the
|
|
764
|
+
* conversation itself is past the window, summarizing it cannot rescue the
|
|
765
|
+
* turn: the summary request has to send that same conversation and is refused
|
|
766
|
+
* the same way. Production bore this out — the compaction attempt died 0.9s
|
|
767
|
+
* after it started, leaving a spinner that never resolved and a raw provider
|
|
768
|
+
* error above a failed publish, while the user's own fix (send `/publish`
|
|
769
|
+
* again, which lands on a fresh session because the failed turn cleared the
|
|
770
|
+
* dirty one) worked every time. This does that fix automatically.
|
|
771
|
+
*
|
|
772
|
+
* Abandoning the session is free *for publish specifically*: the refusal lands
|
|
773
|
+
* pre-flight, before the agent has done anything, and `/publish` re-enters a
|
|
774
|
+
* fresh session as the bare command it already is — it publishes from the state
|
|
775
|
+
* of the workspace, not from the conversation, so nothing it needs was in the
|
|
776
|
+
* session that got dropped. An ordinary message is answered *from* that
|
|
777
|
+
* context, so restarting it would quietly swap a live session for a short
|
|
778
|
+
* rehydrated history and give a worse answer with no explanation. That turn is
|
|
779
|
+
* left to fail and say so. It loses nothing by failing: a failed turn clears
|
|
780
|
+
* the dirty session anyway (`clearIfDirty`), so resending lands on exactly the
|
|
781
|
+
* rehydrated context the restart would have used — the user just gets told.
|
|
782
|
+
*
|
|
783
|
+
* Compaction is kept for the one case where a restart WOULD cost something —
|
|
784
|
+
* the agent already produced work this turn, so a fresh session would make it
|
|
785
|
+
* redo the steps it just took. `resumeSessionAt` is that test: it is set only
|
|
786
|
+
* by an assistant message, whereas `sessionId` arrives with the CLI's `init`,
|
|
787
|
+
* before the first model request.
|
|
788
|
+
*/
|
|
789
|
+
async contextOverflowRecovery(job, controls, state, original, current) {
|
|
790
|
+
const producedWork = state.inProgressSession.resumeSessionAt !== undefined;
|
|
791
|
+
if (isPublishCommandPrompt(job.prompt) &&
|
|
792
|
+
!producedWork &&
|
|
793
|
+
original.usedBoundary &&
|
|
794
|
+
!state.restartedOnFreshSession) {
|
|
795
|
+
state.restartedOnFreshSession = true;
|
|
796
|
+
console.error(`[BridgeJob] conversation no longer fits the context window — restarting thread ${job.threadId}`);
|
|
797
|
+
await this.sessionStore.clear(job.threadId, { runToken: controls.runToken, reason: "sdk_error" });
|
|
798
|
+
void this.relay.contextSource({ threadId: job.threadId, turnId: job.turnId }, "rehydrated_context_overflow", {
|
|
799
|
+
historyEntryCount: job.history?.entries.length ?? 0,
|
|
800
|
+
omittedEntryCount: job.history?.omittedEntryCount ?? 0,
|
|
801
|
+
historyTruncated: job.history?.truncated ?? false,
|
|
802
|
+
reason: "context_overflow",
|
|
803
|
+
});
|
|
804
|
+
return prepareSessionAttempt({ requestPrompt: original.prompt, history: job.history });
|
|
805
|
+
}
|
|
806
|
+
// Work to preserve (or a restart already spent): summarizing is the only
|
|
807
|
+
// move left. Once per turn — the retry runs with compaction on, so a second
|
|
808
|
+
// overflow is a real failure rather than something to loop on.
|
|
809
|
+
if (current.autoCompact === false) {
|
|
810
|
+
console.error(`[BridgeJob] publish no longer fits the context window — compacting for thread ${job.threadId}`);
|
|
811
|
+
return compactionRetryAttempt(current, state.inProgressSession);
|
|
812
|
+
}
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
732
815
|
async finishSuccessfulAttempt(job, controls, state, costUsd) {
|
|
733
816
|
const modelUsage = turnModelUsageList(state.modelUsageTotals);
|
|
734
817
|
if (!controls.isActive()) {
|
|
@@ -764,7 +847,7 @@ export class BridgeJobExecutor {
|
|
|
764
847
|
costUsd: result.costUsd,
|
|
765
848
|
model,
|
|
766
849
|
modelUsage,
|
|
767
|
-
errors: [
|
|
850
|
+
errors: [userFacingTurnError(errorMessage(error))],
|
|
768
851
|
};
|
|
769
852
|
}
|
|
770
853
|
resultWithObservedCost(result, state) {
|
|
@@ -861,7 +944,6 @@ export class BridgeJobExecutor {
|
|
|
861
944
|
// it (they are cumulative); it is folded into the turn totals in `finally`,
|
|
862
945
|
// so a stall that lands after a clean result still contributes its snapshot.
|
|
863
946
|
state.queryModelUsage = new Map();
|
|
864
|
-
state.compactionRetryOwed = attempt.autoCompact === false;
|
|
865
947
|
const mcpServers = await this.resolveMcpServers(job);
|
|
866
948
|
const skills = await readSessionSkills(bridgeRuntimeFiles.projectRoot());
|
|
867
949
|
const autoCompactWindow = autoCompactWindowForModel(model);
|
|
@@ -1108,14 +1190,15 @@ export class BridgeJobExecutor {
|
|
|
1108
1190
|
return costUsd;
|
|
1109
1191
|
}
|
|
1110
1192
|
controls.markEvent?.();
|
|
1111
|
-
// Drop the size refusal outright
|
|
1112
|
-
//
|
|
1113
|
-
//
|
|
1114
|
-
//
|
|
1115
|
-
//
|
|
1116
|
-
//
|
|
1117
|
-
//
|
|
1118
|
-
|
|
1193
|
+
// Drop the size refusal outright. A recovered turn must not leave a raw
|
|
1194
|
+
// provider error sitting above the work that then succeeded, and a turn that
|
|
1195
|
+
// fails anyway says so once, in product language, through the error outcome —
|
|
1196
|
+
// never twice, and never in the provider's words. Dropping it before
|
|
1197
|
+
// `observeInProgressSession` also keeps it from setting `resumeSessionAt` —
|
|
1198
|
+
// the refusal is not work the agent did, and counting it as such would send
|
|
1199
|
+
// the retry a "continue where you left off" note instead of the `/publish`
|
|
1200
|
+
// the turn still owes.
|
|
1201
|
+
if (isContextOverflowRefusal(msg)) {
|
|
1119
1202
|
// Today the refusal is synthetic — the request is rejected pre-flight, so
|
|
1120
1203
|
// no deltas precede it and `turnText` is empty. Clearing it anyway is what
|
|
1121
1204
|
// makes the drop hold if that ever changes: streamed text survives the
|