@gajae-code/coding-agent 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/types/async/job-manager.d.ts +25 -0
  3. package/dist/types/commit/model-selection.d.ts +1 -1
  4. package/dist/types/config/model-registry.d.ts +3 -1
  5. package/dist/types/config/model-resolver.d.ts +1 -19
  6. package/dist/types/config/models-config-schema.d.ts +12 -0
  7. package/dist/types/config/settings-schema.d.ts +15 -1
  8. package/dist/types/gjc-runtime/goal-mode-request.d.ts +8 -1
  9. package/dist/types/harness-control-plane/types.d.ts +7 -2
  10. package/dist/types/modes/acp/acp-event-mapper.d.ts +2 -0
  11. package/dist/types/modes/components/custom-editor.d.ts +7 -0
  12. package/dist/types/modes/shared/agent-wire/command-contract.d.ts +18 -0
  13. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +84 -0
  14. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +14 -7
  15. package/dist/types/modes/shared/agent-wire/event-observation.d.ts +37 -0
  16. package/dist/types/modes/shared/agent-wire/protocol.d.ts +13 -34
  17. package/dist/types/session/agent-session.d.ts +12 -1
  18. package/dist/types/session/session-manager.d.ts +1 -1
  19. package/dist/types/tools/bash.d.ts +2 -0
  20. package/dist/types/tools/browser/actions.d.ts +54 -0
  21. package/dist/types/tools/browser.d.ts +80 -0
  22. package/dist/types/tools/image-gen.d.ts +1 -0
  23. package/dist/types/tools/index.d.ts +3 -1
  24. package/dist/types/tools/job.d.ts +1 -1
  25. package/package.json +7 -7
  26. package/src/async/job-manager.ts +120 -1
  27. package/src/commands/ultragoal.ts +7 -1
  28. package/src/commit/agentic/index.ts +2 -2
  29. package/src/commit/model-selection.ts +7 -22
  30. package/src/commit/pipeline.ts +2 -2
  31. package/src/config/model-registry.ts +17 -9
  32. package/src/config/model-resolver.ts +14 -84
  33. package/src/config/models-config-schema.ts +2 -0
  34. package/src/config/settings-schema.ts +14 -1
  35. package/src/gjc-runtime/goal-mode-request.ts +21 -1
  36. package/src/harness-control-plane/owner.ts +3 -3
  37. package/src/harness-control-plane/rpc-adapter.ts +7 -1
  38. package/src/harness-control-plane/types.ts +8 -11
  39. package/src/internal-urls/docs-index.generated.ts +3 -3
  40. package/src/memories/index.ts +1 -1
  41. package/src/modes/acp/acp-agent.ts +17 -9
  42. package/src/modes/acp/acp-event-mapper.ts +33 -1
  43. package/src/modes/components/custom-editor.ts +19 -3
  44. package/src/modes/controllers/input-controller.ts +27 -7
  45. package/src/modes/controllers/selector-controller.ts +7 -1
  46. package/src/modes/interactive-mode.ts +3 -1
  47. package/src/modes/rpc/rpc-client.ts +16 -3
  48. package/src/modes/rpc/rpc-mode.ts +5 -2
  49. package/src/modes/shared/agent-wire/command-contract.ts +18 -0
  50. package/src/modes/shared/agent-wire/event-contract.ts +147 -0
  51. package/src/modes/shared/agent-wire/event-envelope.ts +35 -16
  52. package/src/modes/shared/agent-wire/event-observation.ts +397 -0
  53. package/src/modes/shared/agent-wire/protocol.ts +24 -81
  54. package/src/modes/utils/context-usage.ts +2 -2
  55. package/src/prompts/agents/explore.md +1 -1
  56. package/src/prompts/agents/plan.md +1 -1
  57. package/src/prompts/agents/reviewer.md +1 -1
  58. package/src/prompts/tools/browser.md +3 -2
  59. package/src/runtime-mcp/manager.ts +15 -2
  60. package/src/sdk.ts +3 -1
  61. package/src/session/agent-session.ts +60 -4
  62. package/src/session/session-manager.ts +1 -1
  63. package/src/task/agents.ts +1 -1
  64. package/src/tools/bash.ts +6 -1
  65. package/src/tools/browser/actions.ts +189 -0
  66. package/src/tools/browser.ts +91 -1
  67. package/src/tools/image-gen.ts +42 -15
  68. package/src/tools/index.ts +7 -1
  69. package/src/tools/inspect-image.ts +10 -8
  70. package/src/tools/job.ts +12 -2
  71. package/src/tools/monitor.ts +98 -17
  72. package/src/utils/commit-message-generator.ts +6 -13
  73. package/src/utils/title-generator.ts +1 -1
  74. package/dist/types/harness-control-plane/frame-mapper.d.ts +0 -29
  75. package/src/harness-control-plane/frame-mapper.ts +0 -286
  76. package/src/priority.json +0 -37
@@ -0,0 +1,397 @@
1
+ /**
2
+ * Canonical AgentSession event observation: the single semantic mapping from
3
+ * `AgentSessionEvent` (and non-event wire frames) to bounded owner observations.
4
+ *
5
+ * This is the one place that derives `AgentWireOwnerObservation`s. Harness (and
6
+ * any other owner control plane) consumes these instead of re-parsing the wire
7
+ * protocol with private knowledge.
8
+ *
9
+ * Hard rule: evidence is BOUNDED — only ids, names, categories, statuses,
10
+ * cursors, timestamps, and short codes/messages. Never assistant text, message
11
+ * deltas, command output, raw args, or raw tool results.
12
+ */
13
+ import type { AgentSessionEvent } from "../../../session/agent-session";
14
+ import type { AgentWireEventPayload, AgentWireOwnerObservation } from "./event-contract";
15
+ import { toAgentWireEventPayload } from "./event-envelope";
16
+
17
+ const TEST_RE = /\b(bun test|npm test|yarn test|pnpm test|jest|vitest|pytest|go test|cargo test|mocha|ava)\b/i;
18
+ const TOOL_STATUS_CODES = new Set([
19
+ "aborted",
20
+ "blocked",
21
+ "cancelled",
22
+ "complete",
23
+ "completed",
24
+ "error",
25
+ "failed",
26
+ "ok",
27
+ "pending",
28
+ "running",
29
+ "skipped",
30
+ "success",
31
+ "timeout",
32
+ ]);
33
+
34
+ /** True when a tool name or command indicates a test-runner invocation. */
35
+ export function isTestRunnerTool(toolName?: unknown, command?: unknown): boolean {
36
+ const name = typeof toolName === "string" ? toolName : "";
37
+ const cmd = typeof command === "string" ? command : "";
38
+ if (/test/i.test(name) && name !== "edit" && name !== "read") return true;
39
+ return TEST_RE.test(cmd);
40
+ }
41
+
42
+ function str(v: unknown): string | undefined {
43
+ return typeof v === "string" ? v : undefined;
44
+ }
45
+ function num(v: unknown): number | undefined {
46
+ return typeof v === "number" ? v : undefined;
47
+ }
48
+ /** Only accept a known closed-vocabulary tool status; reject arbitrary strings. */
49
+ export function boundedStatus(v: unknown): string | undefined {
50
+ if (typeof v !== "string") return undefined;
51
+ const status = v.trim().toLowerCase();
52
+ return TOOL_STATUS_CODES.has(status) ? status : undefined;
53
+ }
54
+ /** Accept only identifier-shaped tokens (e.g. RPC command names); reject free text. */
55
+ export function boundedToken(v: unknown): string | undefined {
56
+ if (typeof v !== "string") return undefined;
57
+ return /^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(v) ? v : undefined;
58
+ }
59
+ function recordObject(v: unknown): Record<string, unknown> | undefined {
60
+ return v && typeof v === "object" && !Array.isArray(v) ? (v as Record<string, unknown>) : undefined;
61
+ }
62
+ function idOf(v: unknown): string | null {
63
+ const record = recordObject(v);
64
+ return str(record?.id) ?? null;
65
+ }
66
+ /** Extract a bounded tool command for test detection only — never persisted. */
67
+ function toolCommand(args: unknown): string | undefined {
68
+ const record = recordObject(args);
69
+ const c = record?.command ?? record?.cmd ?? record?.commandLine;
70
+ return typeof c === "string" ? c : undefined;
71
+ }
72
+ /** Derive a bounded tool status from a result/partialResult/isError shape. */
73
+ function resultStatus(result: unknown, isError?: boolean): string | undefined {
74
+ if (isError === true) return "error";
75
+ const record = recordObject(result);
76
+ if (!record) return undefined;
77
+ if (record.isError === true) return "error";
78
+ return boundedStatus(record.status) ?? boundedStatus(recordObject(record.details)?.status);
79
+ }
80
+
81
+ function obs(
82
+ event: AgentSessionEvent,
83
+ partial: Omit<AgentWireOwnerObservation, "eventType">,
84
+ ): AgentWireOwnerObservation {
85
+ return { eventType: event.type, ...partial };
86
+ }
87
+
88
+ /**
89
+ * Map a single `AgentSessionEvent` to its bounded owner observation, or null
90
+ * when the event carries no owner-facing signal.
91
+ */
92
+ export function observeAgentSessionEvent(event: AgentSessionEvent): AgentWireOwnerObservation | null {
93
+ switch (event.type) {
94
+ case "agent_start":
95
+ return obs(event, {
96
+ kind: "rpc_agent_started",
97
+ signal: "SessionStart",
98
+ evidence: {},
99
+ severity: "info",
100
+ semantic: true,
101
+ coalesceKey: null,
102
+ });
103
+ case "turn_start":
104
+ return obs(event, {
105
+ kind: "rpc_turn_started",
106
+ signal: "prompt-accepted",
107
+ evidence: {},
108
+ severity: "info",
109
+ semantic: true,
110
+ coalesceKey: null,
111
+ });
112
+ case "turn_end":
113
+ return obs(event, {
114
+ kind: "rpc_turn_ended",
115
+ signal: null,
116
+ evidence: {},
117
+ severity: "info",
118
+ semantic: false,
119
+ coalesceKey: null,
120
+ });
121
+ case "message_start":
122
+ case "message_update":
123
+ case "message_end": {
124
+ const messageId = idOf(event.message);
125
+ return obs(event, {
126
+ kind: "rpc_message_activity",
127
+ signal: null,
128
+ evidence: { phase: event.type, messageId },
129
+ severity: "info",
130
+ semantic: false,
131
+ coalesceKey: `message:${messageId ?? "msg"}`,
132
+ });
133
+ }
134
+ case "tool_execution_start": {
135
+ const test = isTestRunnerTool(event.toolName, toolCommand(event.args));
136
+ return obs(event, {
137
+ kind: "rpc_tool_started",
138
+ signal: test ? "test-running" : "tool-call",
139
+ evidence: { toolId: str(event.toolCallId) ?? null, toolName: str(event.toolName) ?? null },
140
+ severity: "info",
141
+ semantic: true,
142
+ coalesceKey: null,
143
+ });
144
+ }
145
+ case "tool_execution_update": {
146
+ const test = isTestRunnerTool(event.toolName, toolCommand(event.args));
147
+ return obs(event, {
148
+ kind: "rpc_tool_updated",
149
+ signal: test ? "test-running" : null,
150
+ evidence: { toolId: str(event.toolCallId) ?? null, status: resultStatus(event.partialResult) ?? null },
151
+ severity: "info",
152
+ semantic: false,
153
+ coalesceKey: `tool:${str(event.toolCallId) ?? "tool"}`,
154
+ });
155
+ }
156
+ case "tool_execution_end": {
157
+ const test = isTestRunnerTool(event.toolName);
158
+ const status = resultStatus(event.result, event.isError);
159
+ return obs(event, {
160
+ kind: "rpc_tool_ended",
161
+ signal: test ? "test-running" : "tool-call",
162
+ evidence: {
163
+ toolId: str(event.toolCallId) ?? null,
164
+ toolName: str(event.toolName) ?? null,
165
+ status: status ?? null,
166
+ },
167
+ severity: status === "error" ? "warn" : "info",
168
+ semantic: true,
169
+ coalesceKey: null,
170
+ });
171
+ }
172
+ case "auto_compaction_start":
173
+ case "auto_compaction_end":
174
+ return obs(event, {
175
+ kind: "rpc_compaction",
176
+ signal: null,
177
+ evidence: { phase: event.type },
178
+ severity: "info",
179
+ semantic: false,
180
+ coalesceKey: null,
181
+ });
182
+ case "auto_retry_start":
183
+ return obs(event, {
184
+ kind: "rpc_retry",
185
+ signal: null,
186
+ evidence: { phase: event.type, attempt: num(event.attempt) ?? null },
187
+ severity: "warn",
188
+ semantic: false,
189
+ coalesceKey: null,
190
+ });
191
+ case "auto_retry_end":
192
+ return obs(event, {
193
+ kind: "rpc_retry",
194
+ signal: null,
195
+ evidence: { phase: event.type, success: event.success === true },
196
+ severity: "warn",
197
+ semantic: false,
198
+ coalesceKey: null,
199
+ });
200
+ case "retry_fallback_applied":
201
+ case "retry_fallback_succeeded":
202
+ return obs(event, {
203
+ kind: "rpc_retry_fallback",
204
+ signal: null,
205
+ evidence: { phase: event.type, role: str(event.role) ?? null },
206
+ severity: "warn",
207
+ semantic: false,
208
+ coalesceKey: null,
209
+ });
210
+ case "ttsr_triggered":
211
+ return obs(event, {
212
+ kind: "rpc_ttsr",
213
+ signal: "error",
214
+ evidence: { ruleCount: Array.isArray(event.rules) ? event.rules.length : 0 },
215
+ severity: "warn",
216
+ semantic: true,
217
+ coalesceKey: null,
218
+ });
219
+ case "todo_reminder":
220
+ case "todo_auto_clear":
221
+ return obs(event, {
222
+ kind: "rpc_todo",
223
+ signal: null,
224
+ evidence: { phase: event.type },
225
+ severity: "info",
226
+ semantic: false,
227
+ coalesceKey: null,
228
+ });
229
+ case "irc_message":
230
+ return obs(event, {
231
+ kind: "rpc_irc",
232
+ signal: null,
233
+ evidence: {},
234
+ severity: "info",
235
+ semantic: false,
236
+ coalesceKey: null,
237
+ });
238
+ case "notice": {
239
+ const level = event.level;
240
+ return obs(event, {
241
+ kind: "rpc_notice",
242
+ signal: level === "error" ? "error" : null,
243
+ evidence: { level },
244
+ severity: level === "info" ? "info" : "warn",
245
+ semantic: level === "error",
246
+ coalesceKey: null,
247
+ });
248
+ }
249
+ case "thinking_level_changed":
250
+ return obs(event, {
251
+ kind: "rpc_thinking",
252
+ signal: null,
253
+ evidence: { thinkingLevel: str(event.thinkingLevel) ?? null },
254
+ severity: "info",
255
+ semantic: false,
256
+ coalesceKey: null,
257
+ });
258
+ case "goal_updated":
259
+ return obs(event, {
260
+ kind: "rpc_goal",
261
+ signal: null,
262
+ evidence: { hasGoal: event.goal != null },
263
+ severity: "info",
264
+ semantic: false,
265
+ coalesceKey: null,
266
+ });
267
+ case "agent_end":
268
+ return obs(event, {
269
+ kind: "rpc_agent_completed",
270
+ signal: "completed",
271
+ evidence: { stopReason: str(event.stopReason) ?? "completed" },
272
+ severity: "info",
273
+ semantic: true,
274
+ coalesceKey: null,
275
+ });
276
+ default:
277
+ return assertNeverEvent(event);
278
+ }
279
+ }
280
+
281
+ function assertNeverEvent(event: never): null {
282
+ void (event as AgentSessionEvent);
283
+ return null;
284
+ }
285
+
286
+ /** Build the rich event payload (renderer-facing) for an `AgentSessionEvent`. */
287
+ export { toAgentWireEventPayload };
288
+
289
+ /** Observe the bounded owner signal carried by a rich event payload. */
290
+ export function observeAgentWireEventPayload(payload: AgentWireEventPayload): AgentWireOwnerObservation | null {
291
+ return observeAgentSessionEvent(payload.event);
292
+ }
293
+
294
+ function ownerFrame(
295
+ frameType: string,
296
+ partial: Omit<AgentWireOwnerObservation, "frameType">,
297
+ ): AgentWireOwnerObservation {
298
+ return { frameType, ...partial };
299
+ }
300
+
301
+ /**
302
+ * Map a single outbound RPC wire frame (docs/rpc.md) to a bounded owner
303
+ * observation, or null when the frame carries no owner-facing signal. Event
304
+ * frames delegate to {@link observeAgentWireEventPayload}; non-event frames are
305
+ * mapped here so owners never re-parse protocol semantics privately.
306
+ */
307
+ export function observeRpcOutboundFrame(frame: Record<string, unknown>): AgentWireOwnerObservation | null {
308
+ const type = str(frame.type);
309
+ if (!type || type === "ready") return null;
310
+
311
+ switch (type) {
312
+ case "response": {
313
+ if (frame.success === false) {
314
+ const error = recordObject(frame.error);
315
+ return ownerFrame(type, {
316
+ kind: "rpc_response_failed",
317
+ signal: "error",
318
+ evidence: {
319
+ command: boundedToken(frame.command) ?? null,
320
+ id: boundedToken(frame.id) ?? null,
321
+ code: boundedToken(error?.code) ?? null,
322
+ },
323
+ severity: "warn",
324
+ semantic: false,
325
+ coalesceKey: null,
326
+ });
327
+ }
328
+ return null;
329
+ }
330
+ case "event": {
331
+ const payload = recordObject(frame.payload);
332
+ const event = recordObject(payload?.event);
333
+ if (!event) return null;
334
+ return observeAgentSessionEvent(event as unknown as AgentSessionEvent);
335
+ }
336
+ case "workflow_gate":
337
+ return ownerFrame(type, {
338
+ kind: "rpc_workflow_gate",
339
+ signal: null,
340
+ evidence: {
341
+ gate_id: str(frame.gate_id) ?? null,
342
+ kind: str(frame.kind) ?? null,
343
+ stage: str(frame.stage) ?? null,
344
+ },
345
+ severity: "info",
346
+ semantic: true,
347
+ coalesceKey: null,
348
+ });
349
+ case "extension_ui_request":
350
+ return ownerFrame(type, {
351
+ kind: "rpc_extension_request",
352
+ signal: "tool-call",
353
+ evidence: { id: str(frame.id) ?? null, method: str(frame.method) ?? null },
354
+ severity: "info",
355
+ semantic: false,
356
+ coalesceKey: null,
357
+ });
358
+ case "extension_error":
359
+ return ownerFrame(type, {
360
+ kind: "rpc_extension_error",
361
+ signal: "error",
362
+ evidence: {
363
+ extensionPath: str(frame.extensionPath) ?? null,
364
+ event: boundedToken(frame.event) ?? null,
365
+ },
366
+ severity: "critical",
367
+ semantic: true,
368
+ coalesceKey: null,
369
+ });
370
+ case "host_tool_call":
371
+ case "host_tool_cancel":
372
+ return ownerFrame(type, {
373
+ kind: type === "host_tool_cancel" ? "rpc_host_tool_cancel" : "rpc_host_tool_call",
374
+ signal: "tool-call",
375
+ evidence: { id: str(frame.id) ?? null, toolName: str(frame.toolName) ?? null },
376
+ severity: "info",
377
+ semantic: false,
378
+ coalesceKey: null,
379
+ });
380
+ case "host_uri_request":
381
+ case "host_uri_cancel":
382
+ return ownerFrame(type, {
383
+ kind: type === "host_uri_cancel" ? "rpc_host_uri_cancel" : "rpc_host_uri_request",
384
+ signal: "tool-call",
385
+ evidence: {
386
+ id: str(frame.id) ?? null,
387
+ operation: str(frame.operation) ?? null,
388
+ scheme: str(frame.scheme) ?? null,
389
+ },
390
+ severity: "info",
391
+ semantic: false,
392
+ coalesceKey: null,
393
+ });
394
+ default:
395
+ return null;
396
+ }
397
+ }
@@ -1,100 +1,43 @@
1
1
  /**
2
2
  * Shared agent-wire protocol primitives for GJC bridge surfaces.
3
3
  *
4
- * This module is the transport-agnostic, versioned frame contract that the
5
- * RPC mode and the (in-progress) `--mode bridge` wiring site both build on.
6
- * It carries the SEMANTIC agent surface events, responses, and UI/permission
7
- * requests — never pixels. See `.gjc/specs/deep-interview-gjc-backend-bridge.md`
8
- * and `.gjc/plans/ralplan/gjc-backend-bridge/pending-approval.md`.
4
+ * The canonical event/frame contract now lives in `event-contract.ts`. This
5
+ * module re-exports it under the historical `Bridge*` names so existing RPC and
6
+ * Bridge code keeps compiling while the adapters migrate to the canonical
7
+ * `AgentWire*` names. See `.gjc/specs/deep-interview-reconcile-rpc-adapters.md`.
9
8
  */
10
- import type { AgentSessionEvent } from "../../../session/agent-session";
9
+ import type {
10
+ AgentWireEventFrame,
11
+ AgentWireEventPayload,
12
+ AgentWireEventType,
13
+ AgentWireFrameEnvelope,
14
+ AgentWireFrameType,
15
+ } from "./event-contract";
16
+ import { AGENT_WIRE_EVENT_TYPES, AGENT_WIRE_PROTOCOL_VERSION } from "./event-contract";
11
17
 
12
18
  /** Wire protocol version. Bump on breaking envelope/semantic changes. */
13
- export const BRIDGE_PROTOCOL_VERSION = 1 as const;
19
+ export const BRIDGE_PROTOCOL_VERSION = AGENT_WIRE_PROTOCOL_VERSION;
14
20
 
15
21
  /** The discriminant of every `AgentSessionEvent` the agent can emit. */
16
- export type AgentSessionEventType = AgentSessionEvent["type"];
17
-
18
- /**
19
- * Compile-time exhaustive registry of every `AgentSessionEvent` variant.
20
- *
21
- * Adding a new variant to `AgentSessionEvent` without registering it here is a
22
- * type error. This keeps the bridge wire surface in lockstep with the agent
23
- * event union — the "event/element drift → silent incompleteness" mitigation
24
- * from the plan's pre-mortem.
25
- */
26
- const AGENT_SESSION_EVENT_TYPE_REGISTRY: Record<AgentSessionEventType, true> = {
27
- agent_start: true,
28
- agent_end: true,
29
- turn_start: true,
30
- turn_end: true,
31
- message_start: true,
32
- message_update: true,
33
- message_end: true,
34
- tool_execution_start: true,
35
- tool_execution_update: true,
36
- tool_execution_end: true,
37
- auto_compaction_start: true,
38
- auto_compaction_end: true,
39
- auto_retry_start: true,
40
- auto_retry_end: true,
41
- retry_fallback_applied: true,
42
- retry_fallback_succeeded: true,
43
- ttsr_triggered: true,
44
- todo_reminder: true,
45
- todo_auto_clear: true,
46
- irc_message: true,
47
- notice: true,
48
- thinking_level_changed: true,
49
- goal_updated: true,
50
- };
22
+ export type AgentSessionEventType = AgentWireEventType;
51
23
 
52
24
  /** Every agent-session event type, derived from the exhaustive registry. */
53
- export const AGENT_SESSION_EVENT_TYPES: readonly AgentSessionEventType[] = Object.keys(
54
- AGENT_SESSION_EVENT_TYPE_REGISTRY,
55
- ) as AgentSessionEventType[];
25
+ export const AGENT_SESSION_EVENT_TYPES: readonly AgentSessionEventType[] = AGENT_WIRE_EVENT_TYPES;
56
26
 
57
27
  /** Top-level frame categories carried over any bridge transport. */
58
- export type BridgeFrameType =
59
- | "ready"
60
- | "event"
61
- | "response"
62
- | "ui_request"
63
- | "permission_request"
64
- | "host_tool_call"
65
- | "host_uri_request"
66
- | "reset"
67
- | "workflow_gate"
68
- | "error";
28
+ export type BridgeFrameType = AgentWireFrameType;
69
29
 
70
- /**
71
- * Universal frame envelope. Every frame on every transport carries these
72
- * fields so clients can order (`seq`), resume (`seq` cursor), and correlate
73
- * request/response pairs (`correlation_id`). `session_id` is present from v1
74
- * even though v1 runs one session per process, so in-process multiplexing is
75
- * an additive, non-breaking change later.
76
- */
77
- export interface BridgeFrameEnvelope<TType extends BridgeFrameType = BridgeFrameType, TPayload = unknown> {
78
- protocol_version: typeof BRIDGE_PROTOCOL_VERSION;
79
- session_id: string;
80
- /** Monotonic per-session sequence number, starting at 1. */
81
- seq: number;
82
- /** Unique id for this frame. */
83
- frame_id: string;
84
- /** Ties a request frame to its response frame, when applicable. */
85
- correlation_id?: string;
86
- type: TType;
87
- payload: TPayload;
88
- }
30
+ /** Universal frame envelope. See {@link AgentWireFrameEnvelope}. */
31
+ export type BridgeFrameEnvelope<
32
+ TType extends BridgeFrameType = BridgeFrameType,
33
+ TPayload = unknown,
34
+ > = AgentWireFrameEnvelope<TType, TPayload>;
89
35
 
90
- /** Payload carried by an `event` frame. */
91
- export interface BridgeEventPayload {
92
- event_type: AgentSessionEventType;
93
- event: AgentSessionEvent;
94
- }
36
+ /** Payload carried by an `event` frame. See {@link AgentWireEventPayload}. */
37
+ export type BridgeEventPayload = AgentWireEventPayload;
95
38
 
96
39
  /** An `AgentSessionEvent` serialized into a versioned wire frame. */
97
- export type BridgeEventFrame = BridgeFrameEnvelope<"event", BridgeEventPayload>;
40
+ export type BridgeEventFrame = AgentWireEventFrame;
98
41
 
99
42
  /** A `workflow_gate` event serialized into a versioned wire frame (#321). */
100
43
  export type BridgeWorkflowGateFrame = BridgeFrameEnvelope<
@@ -197,14 +197,14 @@ export function computeContextBreakdown(
197
197
  if (contextWindow > 0) {
198
198
  const compactionSettings = session.settings.getGroup("compaction") as CompactionSettings;
199
199
  if (compactionSettings.enabled && compactionSettings.strategy !== "off") {
200
- const threshold = resolveThresholdTokens(contextWindow, compactionSettings);
200
+ const threshold = resolveThresholdTokens(contextWindow, compactionSettings, model?.maxTokens ?? 0);
201
201
  autoCompactBufferTokens = Math.max(0, contextWindow - threshold);
202
202
  } else {
203
203
  autoCompactBufferTokens = 0;
204
204
  }
205
205
  // Even when fully disabled, fall back to a sensible reserve floor for display.
206
206
  if (autoCompactBufferTokens === 0 && compactionSettings.enabled) {
207
- autoCompactBufferTokens = effectiveReserveTokens(contextWindow, compactionSettings);
207
+ autoCompactBufferTokens = effectiveReserveTokens(contextWindow, compactionSettings, model?.maxTokens ?? 0);
208
208
  }
209
209
  }
210
210
  autoCompactBufferTokens = Math.min(autoCompactBufferTokens, Math.max(0, contextWindow - usedTokens));
@@ -2,7 +2,7 @@
2
2
  name: explore
3
3
  description: Fast read-only codebase scout returning compressed context for handoff
4
4
  tools: read, search, find, web_search
5
- model: pi/smol
5
+ model: pi/default
6
6
  thinking-level: med
7
7
  output:
8
8
  properties:
@@ -3,7 +3,7 @@ name: plan
3
3
  description: Software architect for complex multi-file architectural decisions. NOT for simple tasks, single-file changes, or tasks completable in <5 tool calls.
4
4
  tools: read, search, find, bash, lsp, web_search, ast_grep
5
5
  spawns: explore
6
- model: pi/plan, pi/slow
6
+ model: pi/default
7
7
  thinking-level: high
8
8
  hide: true
9
9
  ---
@@ -3,7 +3,7 @@ name: reviewer
3
3
  description: "Code review specialist for quality/security analysis"
4
4
  tools: read, search, find, bash, lsp, web_search, ast_grep, report_finding
5
5
  spawns: explore
6
- model: pi/slow
6
+ model: pi/default
7
7
  thinking-level: high
8
8
  blocking: true
9
9
  output:
@@ -2,10 +2,11 @@ Drives a real Chromium tab with full puppeteer access via JS execution.
2
2
 
3
3
  <instruction>
4
4
  - For static web content (articles, docs, issues/PRs, JSON, PDFs, feeds), prefer the `read` tool with a URL — reader-mode text without spinning up a browser. Use this tool when you need JS execution, authentication, or interactive actions.
5
- - Three actions only:
5
+ - Four actions:
6
6
  - `open` — acquire (or reuse) a named tab. `name` defaults to `"main"`. Optional `url` navigates after the tab is ready. Optional `viewport` sets dimensions. Optional `dialogs: "accept" | "dismiss"` auto-handles `alert`/`confirm`/`beforeunload` so navigation/clicks don't hang (default: leave dialogs unhandled — page hangs until caller wires `page.on('dialog', …)`).
7
7
  - `close` — release a tab by `name`, or every tab with `all: true`. For spawned-app browsers, set `kill: true` to terminate the process tree (default leaves it running).
8
8
  - `run` — execute JS against an existing tab. `code` is the body of an async function with `page`, `browser`, `tab`, `display`, `assert`, `wait` in scope. The function's return value is JSON-stringified into the tool result; multiple `display(value)` calls accumulate text/images.
9
+ - `act` — run a list of structured `actions` against an existing tab without writing JS (preferred for routine navigation/interaction). Each step is `{ verb, … }`; verbs: `navigate {url, wait_until?}`, `click {id|selector}`, `type {id|selector, text}`, `fill {selector, value}`, `select {selector, values}`, `press {key, selector?}`, `scroll {dx?, dy?}`, `back`, `wait {selector?|ms?}`, `observe {viewport_only?, include_all?}`, `extract {format?}`, `screenshot`. Address elements by the numeric `id` from a prior `observe` (preferred) or a selector. Steps run in order; the tool returns an array of per-step results (observations/extracted content included). Use `run` only when a verb does not cover what you need.
9
10
  - Tabs survive across `run` calls and across in-process subagents. Open once, reuse many times.
10
11
  - Browser kinds, selected by the `app` field on `open`:
11
12
  - default (no `app`) → headless Chromium with stealth patches.
@@ -32,7 +33,7 @@ Drives a real Chromium tab with full puppeteer access via JS execution.
32
33
  </instruction>
33
34
 
34
35
  <critical>
35
- - You MUST call `open` before `run`. `run` does not implicitly create a tab.
36
+ - You MUST call `open` before `run` or `act`. Neither implicitly creates a tab.
36
37
  - You NEVER screenshot just to "see what's on the page" — `tab.observe()` returns structured data with element ids you can act on immediately.
37
38
  - After a `tab.goto()` or any navigation, prior element ids from `tab.observe()` are invalidated. Re-observe before referencing them.
38
39
  - `code` runs with full Node access. Treat it as your code, not sandboxed code.
@@ -304,6 +304,7 @@ export class MCPManager {
304
304
  config: MCPServerConfig;
305
305
  tracked: TrackedPromise<ToolLoadResult>;
306
306
  toolsPromise: Promise<ToolLoadResult>;
307
+ connectionAbort: AbortController;
307
308
  };
308
309
 
309
310
  const errors = new Map<string, string>();
@@ -424,7 +425,7 @@ export class MCPManager {
424
425
  this.#pendingToolLoads.set(name, toolsPromise);
425
426
 
426
427
  const tracked = trackPromise(toolsPromise);
427
- connectionTasks.push({ name, config, tracked, toolsPromise });
428
+ connectionTasks.push({ name, config, tracked, toolsPromise, connectionAbort });
428
429
 
429
430
  void toolsPromise
430
431
  .then(async ({ connection, serverTools }) => {
@@ -475,7 +476,19 @@ export class MCPManager {
475
476
 
476
477
  const pendingWithoutCache = pendingTasks.filter(task => !cachedTools.has(task.name));
477
478
  if (pendingWithoutCache.length > 0) {
478
- await Promise.allSettled(pendingWithoutCache.map(task => task.tracked.promise));
479
+ for (const task of pendingWithoutCache) {
480
+ const message = `MCP server connection timed out during startup: ${task.name}`;
481
+ errors.set(task.name, message);
482
+ reportedErrors.add(task.name);
483
+ task.connectionAbort.abort(new Error(message));
484
+ if (this.#pendingConnections.has(task.name)) this.#pendingConnections.delete(task.name);
485
+ if (this.#pendingToolLoads.get(task.name) === task.toolsPromise)
486
+ this.#pendingToolLoads.delete(task.name);
487
+ this.#pendingConnectionControllers.delete(task.name);
488
+ }
489
+ // Do not await these promises here: a misbehaving stdio/MCP transport can ignore
490
+ // AbortSignal and keep startup blocked indefinitely. The background toolsPromise
491
+ // handler will clean up if it eventually settles.
479
492
  }
480
493
  }
481
494
 
package/src/sdk.ts CHANGED
@@ -868,7 +868,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
868
868
  imageProvider === "auto" ||
869
869
  imageProvider === "openai" ||
870
870
  imageProvider === "gemini" ||
871
- imageProvider === "openrouter"
871
+ imageProvider === "openrouter" ||
872
+ imageProvider === "antigravity"
872
873
  ) {
873
874
  setPreferredImageProvider(imageProvider);
874
875
  }
@@ -1222,6 +1223,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1222
1223
  timestamp: Date.now(),
1223
1224
  }),
1224
1225
  sendCustomMessage: (msg, opts) => session.sendCustomMessage(msg, opts),
1226
+ purgeQueuedCustomMessages: predicate => session.purgeQueuedCustomMessages(predicate),
1225
1227
  peekQueueInvoker: () => session.peekQueueInvoker(),
1226
1228
  peekStandingResolveHandler: () => session.peekStandingResolveHandler(),
1227
1229
  setStandingResolveHandler: handler => session.setStandingResolveHandler(handler),