@alpacakit/agents 0.2.0-beta.2 → 0.2.0-beta.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 (130) hide show
  1. package/README.md +21 -0
  2. package/dist/agents/builtin.d.ts +2 -2
  3. package/dist/agents/builtin.d.ts.map +1 -1
  4. package/dist/agents/builtin.js +6 -4
  5. package/dist/agents/claude/cli.d.ts +133 -0
  6. package/dist/agents/claude/cli.d.ts.map +1 -0
  7. package/dist/agents/{claude-stream.js → claude/cli.js} +81 -10
  8. package/dist/agents/claude/host.d.ts +14 -0
  9. package/dist/agents/claude/host.d.ts.map +1 -0
  10. package/dist/agents/{claude.js → claude/host.js} +51 -174
  11. package/dist/agents/{claude.d.ts → claude/index.d.ts} +10 -11
  12. package/dist/agents/claude/index.d.ts.map +1 -0
  13. package/dist/agents/claude/index.js +134 -0
  14. package/dist/agents/{claude-session.d.ts → claude/session.d.ts} +3 -15
  15. package/dist/agents/claude/session.d.ts.map +1 -0
  16. package/dist/agents/claude/session.js +321 -0
  17. package/dist/agents/codex/cli.d.ts +132 -0
  18. package/dist/agents/codex/cli.d.ts.map +1 -0
  19. package/dist/agents/codex/cli.js +110 -0
  20. package/dist/agents/codex/host.d.ts +14 -0
  21. package/dist/agents/codex/host.d.ts.map +1 -0
  22. package/dist/agents/codex/host.js +221 -0
  23. package/dist/agents/codex/index.d.ts +14 -0
  24. package/dist/agents/codex/index.d.ts.map +1 -0
  25. package/dist/agents/codex/index.js +127 -0
  26. package/dist/agents/codex/session.d.ts +21 -0
  27. package/dist/agents/codex/session.d.ts.map +1 -0
  28. package/dist/agents/codex/session.js +390 -0
  29. package/dist/agents/openai-compatible/fetch-engine.d.ts.map +1 -1
  30. package/dist/agents/openai-compatible/fetch-engine.js +3 -2
  31. package/dist/agents/openai-compatible/pi/engine.js +1 -1
  32. package/dist/agents/openai-compatible/pi/{session.d.ts → run.d.ts} +1 -1
  33. package/dist/agents/openai-compatible/pi/run.d.ts.map +1 -0
  34. package/dist/agents/openai-compatible/pi/sandbox.d.ts.map +1 -1
  35. package/dist/auth/login-probe.d.ts +6 -4
  36. package/dist/auth/login-probe.d.ts.map +1 -1
  37. package/dist/auth/login-probe.js +19 -13
  38. package/dist/auth/material.d.ts +1 -1
  39. package/dist/auth/material.d.ts.map +1 -1
  40. package/dist/codex/app-server.d.ts.map +1 -1
  41. package/dist/codex/app-server.js +13 -42
  42. package/dist/codex/index.d.ts +1 -1
  43. package/dist/codex/index.d.ts.map +1 -1
  44. package/dist/config/agent-config-validation.d.ts +1 -1
  45. package/dist/config/agent-config-validation.d.ts.map +1 -1
  46. package/dist/config/agent-configs.d.ts.map +1 -1
  47. package/dist/config/agent-configs.js +3 -3
  48. package/dist/config/compose.d.ts.map +1 -1
  49. package/dist/config/config.d.ts +1 -1
  50. package/dist/config/config.d.ts.map +1 -1
  51. package/dist/config/index.d.ts +2 -2
  52. package/dist/config/index.d.ts.map +1 -1
  53. package/dist/config/index.js +1 -1
  54. package/dist/config/model.d.ts +0 -1
  55. package/dist/config/model.d.ts.map +1 -1
  56. package/dist/config/schema.d.ts.map +1 -1
  57. package/dist/config/schema.js +1 -1
  58. package/dist/config/store.d.ts.map +1 -1
  59. package/dist/config/store.js +6 -24
  60. package/dist/config/support.d.ts +0 -1
  61. package/dist/config/support.d.ts.map +1 -1
  62. package/dist/config/support.js +0 -9
  63. package/dist/core/cli-child.d.ts +56 -0
  64. package/dist/core/cli-child.d.ts.map +1 -0
  65. package/dist/core/cli-child.js +105 -0
  66. package/dist/core/fs.d.ts +1 -1
  67. package/dist/core/fs.d.ts.map +1 -1
  68. package/dist/core/fs.js +10 -2
  69. package/dist/core/node-ports.d.ts.map +1 -1
  70. package/dist/core/node-ports.js +1 -5
  71. package/dist/core/session.d.ts +4 -0
  72. package/dist/core/session.d.ts.map +1 -1
  73. package/dist/fleet/store.d.ts.map +1 -1
  74. package/dist/fleet/store.js +4 -3
  75. package/dist/host/availability.d.ts +2 -1
  76. package/dist/host/availability.d.ts.map +1 -1
  77. package/dist/host/github-dir.d.ts.map +1 -1
  78. package/dist/host/github-dir.js +2 -1
  79. package/dist/host/mcp-list.d.ts.map +1 -1
  80. package/dist/host/mcp-list.js +5 -6
  81. package/dist/host/run-command.d.ts +2 -1
  82. package/dist/host/run-command.d.ts.map +1 -1
  83. package/dist/host/run-command.js +10 -19
  84. package/dist/host/session-files.d.ts +2 -1
  85. package/dist/host/session-files.d.ts.map +1 -1
  86. package/dist/host/session-files.js +2 -1
  87. package/dist/index.d.ts +6 -6
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +5 -5
  90. package/dist/kernel/classify.d.ts +1 -2
  91. package/dist/kernel/classify.d.ts.map +1 -1
  92. package/dist/kernel/classify.js +1 -1
  93. package/dist/kernel/cli-agent.d.ts.map +1 -1
  94. package/dist/kernel/cli-agent.js +26 -85
  95. package/dist/kernel/cli-login.d.ts.map +1 -1
  96. package/dist/kernel/cli-login.js +9 -30
  97. package/dist/kernel/facets.d.ts +1 -1
  98. package/dist/kernel/facets.d.ts.map +1 -1
  99. package/dist/kernel/login.d.ts.map +1 -1
  100. package/dist/kernel/login.js +2 -1
  101. package/dist/kernel/options.d.ts +1 -1
  102. package/dist/kernel/options.d.ts.map +1 -1
  103. package/dist/kernel/run.d.ts.map +1 -1
  104. package/dist/kernel/run.js +2 -1
  105. package/dist/kernel/session.d.ts +46 -39
  106. package/dist/kernel/session.d.ts.map +1 -1
  107. package/dist/kernel/session.js +128 -69
  108. package/dist/testing/fake-exec.d.ts +9 -10
  109. package/dist/testing/fake-exec.d.ts.map +1 -1
  110. package/dist/testing/fake-exec.js +14 -1
  111. package/dist/testing/index.d.ts +1 -1
  112. package/dist/testing/index.d.ts.map +1 -1
  113. package/dist/testing/index.js +1 -1
  114. package/package.json +2 -2
  115. package/dist/agents/claude-session.d.ts.map +0 -1
  116. package/dist/agents/claude-session.js +0 -419
  117. package/dist/agents/claude-stream.d.ts +0 -65
  118. package/dist/agents/claude-stream.d.ts.map +0 -1
  119. package/dist/agents/claude.d.ts.map +0 -1
  120. package/dist/agents/codex-session.d.ts +0 -52
  121. package/dist/agents/codex-session.d.ts.map +0 -1
  122. package/dist/agents/codex-session.js +0 -495
  123. package/dist/agents/codex.d.ts +0 -20
  124. package/dist/agents/codex.d.ts.map +0 -1
  125. package/dist/agents/codex.js +0 -379
  126. package/dist/agents/openai-compatible/pi/session.d.ts.map +0 -1
  127. package/dist/core/process-close.d.ts +0 -11
  128. package/dist/core/process-close.d.ts.map +0 -1
  129. package/dist/core/process-close.js +0 -27
  130. /package/dist/agents/openai-compatible/pi/{session.js → run.js} +0 -0
@@ -1,20 +1,26 @@
1
1
  /**
2
- * Session driver pieces shared by the built-in hosts: the definition-side
3
- * capability contract, the long-lived interactive child (spawn, NDJSON
4
- * lines, EOF-first close), and the turn/event state
5
- * every Session keeps. Protocol knowledge (what to write, how to read a
6
- * line) stays in the adapter modules.
2
+ * The session driver (design "Kernel", session plane): as `RunDriver`
3
+ * drives an adapter's generator, `driveSession` drives a host's
4
+ * `SessionProtocol`. The driver owns the conversation's lifecycle — one
5
+ * turn at a time, the approval registry and its lifetime, the event
6
+ * stream, and the end of it all when the child is gone. A protocol only
7
+ * encodes the five verbs for its host and decodes one incoming item into
8
+ * the neutral session signals; it never keeps turn or approval state.
7
9
  */
8
- import { AGENT_ERROR_CODE, AgentError, NOT_AVAILABLE_CAUSE, } from "../core/error.js";
9
- import { redactSecrets } from "../core/redact.js";
10
- import { SESSION_EVENT_TYPE, TURN_OUTCOME, } from "../core/session.js";
11
- import { endProcessGracefully } from "../core/process-close.js";
10
+ import { endProcessGracefully, failureEvidence, LineBuffer, appendTail, resolveCommand, } from "../core/cli-child.js";
11
+ import { AGENT_ERROR_CODE, AgentError } from "../core/error.js";
12
+ import { APPROVAL_DECISION, SESSION_EVENT_TYPE, TURN_OUTCOME, } from "../core/session.js";
12
13
  import { OUTPUT_CHANNEL } from "../core/values.js";
13
14
  import { EventBroadcast } from "./handle.js";
14
- const OUTPUT_TAIL_CHARS = 2_000;
15
15
  const EVENT_BASE_COST_BYTES = 64;
16
+ const UNKNOWN_EXIT = { code: null, signal: null };
16
17
  /** Both hosts use UUID-shaped conversation ids; anything else never reaches a path. */
17
18
  const HOST_SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
19
+ /** Every host takes both answers (Claude allow/deny; Codex accepts `decline` for every request, T53 h2). */
20
+ const APPROVAL_DECISIONS = [
21
+ APPROVAL_DECISION.accept,
22
+ APPROVAL_DECISION.decline,
23
+ ];
18
24
  export function assertHostSessionId(id, agentId) {
19
25
  if (HOST_SESSION_ID.test(id)) {
20
26
  return;
@@ -31,12 +37,6 @@ function sessionEventCost(event) {
31
37
  return (EVENT_BASE_COST_BYTES +
32
38
  (event.type === SESSION_EVENT_TYPE.message ? event.text.length : 0));
33
39
  }
34
- function appendTail(current, next) {
35
- const combined = current + next;
36
- return combined.length <= OUTPUT_TAIL_CHARS
37
- ? combined
38
- : combined.slice(-OUTPUT_TAIL_CHARS);
39
- }
40
40
  export function sessionStateError(agentId, reason) {
41
41
  return new AgentError({
42
42
  code: AGENT_ERROR_CODE.invalidInput,
@@ -47,16 +47,15 @@ export function sessionStateError(agentId, reason) {
47
47
  });
48
48
  }
49
49
  export function sessionExitedError(agentId, exit, stderrTail) {
50
- const tail = redactSecrets(stderrTail);
51
50
  return new AgentError({
52
51
  code: AGENT_ERROR_CODE.failed,
53
52
  classification: AgentError.agentFailure(AGENT_ERROR_CODE.failed),
54
53
  message: `Agent "${agentId}" session process exited`,
55
- data: {
54
+ data: failureEvidence({
55
+ stdout: "",
56
+ stderr: stderrTail,
56
57
  exitCode: exit?.code ?? null,
57
- stdoutTail: null,
58
- stderrTail: tail === "" ? null : tail,
59
- },
58
+ }),
60
59
  agentId,
61
60
  });
62
61
  }
@@ -76,20 +75,11 @@ export class SessionProcess {
76
75
  void process.exit.then((exit) => {
77
76
  this.exitValue = exit;
78
77
  }, () => {
79
- this.exitValue = { code: null, signal: null };
78
+ this.exitValue = UNKNOWN_EXIT;
80
79
  });
81
80
  }
82
81
  static async spawn(input) {
83
- const command = await input.ctx.exec.which(input.command, input.ctx.env);
84
- if (command === null) {
85
- throw new AgentError({
86
- code: AGENT_ERROR_CODE.notAvailable,
87
- classification: AgentError.agentFailure(AGENT_ERROR_CODE.notAvailable),
88
- message: `Command "${input.command}" was not found on PATH`,
89
- data: { cause: NOT_AVAILABLE_CAUSE.notInstalled },
90
- agentId: input.agentId,
91
- });
92
- }
82
+ const command = await resolveCommand(input.ctx.exec, input.command, input.ctx.env, input.agentId);
93
83
  const process = input.ctx.exec.spawn({
94
84
  command,
95
85
  args: [...input.args],
@@ -114,26 +104,15 @@ export class SessionProcess {
114
104
  }
115
105
  /** Non-empty stdout lines in order; stderr is kept as a tail for errors. */
116
106
  async *lines() {
117
- let pending = "";
107
+ const buffer = new LineBuffer();
118
108
  for await (const chunk of this.process.chunks) {
119
109
  if (chunk.channel === OUTPUT_CHANNEL.stderr) {
120
110
  this.stderrTail = appendTail(this.stderrTail, chunk.text);
121
111
  continue;
122
112
  }
123
- pending += chunk.text;
124
- const lines = pending.split(/\r?\n/u);
125
- pending = lines.pop() ?? "";
126
- for (const line of lines) {
127
- const trimmed = line.trim();
128
- if (trimmed !== "") {
129
- yield trimmed;
130
- }
131
- }
132
- }
133
- const last = pending.trim();
134
- if (last !== "") {
135
- yield last;
113
+ yield* buffer.push(chunk.text);
136
114
  }
115
+ yield* buffer.flush();
137
116
  }
138
117
  close() {
139
118
  if (this.exitValue !== null) {
@@ -143,36 +122,118 @@ export class SessionProcess {
143
122
  }
144
123
  }
145
124
  /**
146
- * Turn bookkeeping and the event stream. Exactly one turn runs at a time:
147
- * `send` needs idle, `steer`/`interrupt` need active — both are typed
148
- * `invalid_input`, never a silent queue.
125
+ * The Session every host gets. Exactly one turn runs at a time: `send`
126
+ * needs idle (including while the host has not yet acknowledged a start),
127
+ * `steer` / `interrupt` need active — both typed `invalid_input`, never a
128
+ * silent queue. Approval requests belong to the turn that raised them and
129
+ * die with it; the child's exit fails the running turn, publishes
130
+ * `exited`, and ends the stream.
149
131
  */
150
- export class SessionState {
132
+ class DrivenSession {
133
+ id;
151
134
  agentId;
135
+ protocol;
136
+ logger;
152
137
  broadcast = new EventBroadcast(undefined, sessionEventCost);
138
+ approvals = new Map();
139
+ /** Items that arrived while the host had not yet acknowledged the turn. */
140
+ held = [];
141
+ reading;
153
142
  active = null;
154
- /** A turn whose start the host has not acknowledged yet (Codex `turn/start` in flight). */
155
143
  starting = false;
156
144
  ended = false;
157
- constructor(agentId) {
145
+ sink;
146
+ constructor(id, agentId, protocol, logger) {
147
+ this.id = id;
158
148
  this.agentId = agentId;
149
+ this.protocol = protocol;
150
+ this.logger = logger;
151
+ const driver = this;
152
+ this.sink = {
153
+ get activeTurnId() {
154
+ return driver.active?.turnId ?? null;
155
+ },
156
+ message: (text) => this.message(text),
157
+ tool: (name) => this.emit({ type: SESSION_EVENT_TYPE.tool, name }),
158
+ approvalRequested: (requestId, summary, payload) => {
159
+ this.approvals.set(requestId, payload);
160
+ this.emit({
161
+ type: SESSION_EVENT_TYPE.approvalRequested,
162
+ requestId,
163
+ summary,
164
+ decisions: APPROVAL_DECISIONS,
165
+ });
166
+ },
167
+ endTurn: (outcome, text) => this.endTurn(outcome, text),
168
+ };
169
+ this.reading = this.read();
159
170
  }
160
171
  get events() {
161
172
  return this.broadcast.events;
162
173
  }
163
- get activeTurnId() {
164
- return this.active?.turnId ?? null;
174
+ async send(text) {
175
+ this.requireIdle();
176
+ // The slot is taken from here: a second send() during the host's
177
+ // round trip is refused, and items that beat the acknowledgement wait.
178
+ this.starting = true;
179
+ let turnId;
180
+ try {
181
+ turnId = await this.protocol.startTurn(text);
182
+ }
183
+ catch (error) {
184
+ this.starting = false;
185
+ this.held.splice(0);
186
+ throw error;
187
+ }
188
+ const turn = this.beginTurn(turnId);
189
+ for (const item of this.held.splice(0)) {
190
+ this.protocol.decode(item, this.sink);
191
+ }
192
+ return turn;
193
+ }
194
+ async steer(text) {
195
+ await this.protocol.steer(this.requireActive(), text);
196
+ }
197
+ async interrupt() {
198
+ await this.protocol.interrupt(this.requireActive());
199
+ }
200
+ async respond(requestId, decision) {
201
+ this.requireOpen();
202
+ if (!this.approvals.has(requestId)) {
203
+ throw sessionStateError(this.agentId, `no pending approval request "${requestId}"`);
204
+ }
205
+ const payload = this.approvals.get(requestId);
206
+ this.approvals.delete(requestId);
207
+ this.protocol.respond(payload, decision);
165
208
  }
166
- get isEnded() {
167
- return this.ended;
209
+ async close() {
210
+ await this.protocol.close();
211
+ await this.reading;
168
212
  }
169
- get isStarting() {
170
- return this.starting;
213
+ async read() {
214
+ try {
215
+ for await (const item of this.protocol.incoming) {
216
+ if (this.starting) {
217
+ this.held.push(item);
218
+ }
219
+ else {
220
+ this.protocol.decode(item, this.sink);
221
+ }
222
+ }
223
+ }
224
+ catch (error) {
225
+ this.logger.log("error", "session reader failed", error);
226
+ }
227
+ finally {
228
+ // Whatever happened above, the stream must end so `events` consumers
229
+ // and `close()` are released. A child whose exit rejects (spawn-time
230
+ // `error`) is gone all the same: no code, no signal.
231
+ this.end(await this.protocol.exit.catch(() => UNKNOWN_EXIT), this.protocol.stderrTail);
232
+ }
171
233
  }
172
234
  emit(event) {
173
235
  this.broadcast.push(event);
174
236
  }
175
- /** Record a message for the turn's fallback text and publish it. */
176
237
  message(text) {
177
238
  this.active?.messages.push(text);
178
239
  this.emit({ type: SESSION_EVENT_TYPE.message, text });
@@ -188,14 +249,6 @@ export class SessionState {
188
249
  throw sessionStateError(this.agentId, "a turn is in progress; use steer() or interrupt()");
189
250
  }
190
251
  }
191
- /** Reserve the idle slot while the host is asked to start the turn. */
192
- markStarting() {
193
- this.starting = true;
194
- }
195
- /** The host refused to start the turn; the slot is idle again. */
196
- clearStarting() {
197
- this.starting = false;
198
- }
199
252
  requireActive() {
200
253
  this.requireOpen();
201
254
  if (this.active === null) {
@@ -210,13 +263,13 @@ export class SessionState {
210
263
  this.emit({ type: SESSION_EVENT_TYPE.turnStarted, turnId });
211
264
  });
212
265
  }
213
- /** `text === null` falls back to the turn's collected messages. */
214
266
  endTurn(outcome, text) {
215
267
  const turn = this.active;
216
268
  if (turn === null) {
217
269
  return;
218
270
  }
219
271
  this.active = null;
272
+ this.approvals.clear();
220
273
  this.emit({
221
274
  type: SESSION_EVENT_TYPE.turnEnded,
222
275
  turnId: turn.turnId,
@@ -228,13 +281,15 @@ export class SessionState {
228
281
  text: text ?? turn.messages.join("\n"),
229
282
  });
230
283
  }
231
- /** The process is gone: fail the running turn, publish `exited`, end the stream. */
284
+ /** The child is gone: fail the running turn, publish `exited`, end the stream. */
232
285
  end(exit, stderrTail) {
233
286
  if (this.ended) {
234
287
  return;
235
288
  }
236
289
  this.ended = true;
237
290
  this.starting = false;
291
+ this.held.splice(0);
292
+ this.approvals.clear();
238
293
  const turn = this.active;
239
294
  this.active = null;
240
295
  if (turn !== null) {
@@ -253,3 +308,7 @@ export class SessionState {
253
308
  this.broadcast.end();
254
309
  }
255
310
  }
311
+ /** Bind a host protocol to the one Session lifecycle. */
312
+ export function driveSession(input) {
313
+ return new DrivenSession(input.id, input.agentId, input.protocol, input.logger);
314
+ }
@@ -1,4 +1,11 @@
1
1
  import type { Exec, ExecChunk, ExecExit, ExecSpawnOptions, InteractiveExecSpawnOptions } from "../core/ports.js";
2
+ export type FakeExecControls = {
3
+ stdout(text: string): void;
4
+ stderr(text: string): void;
5
+ exit(exit: ExecExit): void;
6
+ /** The child never settled normally: `exit` rejects, output ends. */
7
+ fail(error: Error): void;
8
+ };
2
9
  export type FakeExecScript = {
3
10
  chunks?: ExecChunk[];
4
11
  exitCode?: number;
@@ -8,17 +15,9 @@ export type FakeExecScript = {
8
15
  hang?: boolean;
9
16
  /** Survive SIGTERM so SIGKILL escalation can be observed. */
10
17
  ignoreSigterm?: boolean;
11
- onWrite?: (text: string, controls: {
12
- stdout(text: string): void;
13
- stderr(text: string): void;
14
- exit(exit: ExecExit): void;
15
- }) => void;
18
+ onWrite?: (text: string, controls: FakeExecControls) => void;
16
19
  /** Runs when the caller ends stdin — script `controls.exit(...)` here for a child that exits on EOF. */
17
- onStdinEnd?: (controls: {
18
- stdout(text: string): void;
19
- stderr(text: string): void;
20
- exit(exit: ExecExit): void;
21
- }) => void;
20
+ onStdinEnd?: (controls: FakeExecControls) => void;
22
21
  };
23
22
  export type FakeExec = Exec & {
24
23
  readonly spawns: (ExecSpawnOptions | InteractiveExecSpawnOptions)[];
@@ -1 +1 @@
1
- {"version":3,"file":"fake-exec.d.ts","sourceRoot":"","sources":["../../src/testing/fake-exec.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,IAAI,EACJ,SAAS,EACT,QAAQ,EACR,gBAAgB,EAEhB,2BAA2B,EAC5B,MAAM,kBAAkB,CAAC;AAG1B,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,6DAA6D;IAC7D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,CACR,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE;QACR,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;KAC5B,KACE,IAAI,CAAC;IACV,wGAAwG;IACxG,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE;QACtB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;KAC5B,KAAK,IAAI,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG;IAC5B,QAAQ,CAAC,MAAM,EAAE,CAAC,gBAAgB,GAAG,2BAA2B,CAAC,EAAE,CAAC;IACpE,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC;IACjC,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC;IAC3B,iDAAiD;IACjD,QAAQ,CAAC,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACvC,CAAC;AAWF,wBAAgB,QAAQ,CAAC,OAAO,GAAE,cAAc,EAAO,GAAG,QAAQ,CAqFjE"}
1
+ {"version":3,"file":"fake-exec.d.ts","sourceRoot":"","sources":["../../src/testing/fake-exec.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,IAAI,EACJ,SAAS,EACT,QAAQ,EACR,gBAAgB,EAEhB,2BAA2B,EAC5B,MAAM,kBAAkB,CAAC;AAG1B,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3B,qEAAqE;IACrE,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,6DAA6D;IAC7D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC7D,wGAAwG;IACxG,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG;IAC5B,QAAQ,CAAC,MAAM,EAAE,CAAC,gBAAgB,GAAG,2BAA2B,CAAC,EAAE,CAAC;IACpE,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC;IACjC,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC;IAC3B,iDAAiD;IACjD,QAAQ,CAAC,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACvC,CAAC;AAWF,wBAAgB,QAAQ,CAAC,OAAO,GAAE,cAAc,EAAO,GAAG,QAAQ,CAkGjE"}
@@ -18,9 +18,12 @@ export function fakeExec(scripts = []) {
18
18
  stdinWrites.push(writes);
19
19
  const chunks = new AsyncQueue();
20
20
  let settleExit = () => { };
21
- const exit = new Promise((resolve) => {
21
+ let failExit = () => { };
22
+ const exit = new Promise((resolve, reject) => {
22
23
  settleExit = resolve;
24
+ failExit = reject;
23
25
  });
26
+ void exit.catch(() => { });
24
27
  let settleTermination = () => { };
25
28
  const termination = new Promise((resolve) => {
26
29
  settleTermination = resolve;
@@ -35,10 +38,20 @@ export function fakeExec(scripts = []) {
35
38
  settleTermination();
36
39
  settleExit(value);
37
40
  };
41
+ const fail = (error) => {
42
+ if (finished) {
43
+ return;
44
+ }
45
+ finished = true;
46
+ chunks.end();
47
+ settleTermination();
48
+ failExit(error);
49
+ };
38
50
  const controls = {
39
51
  stdout: (text) => chunks.push({ channel: OUTPUT_CHANNEL.stdout, text }),
40
52
  stderr: (text) => chunks.push({ channel: OUTPUT_CHANNEL.stderr, text }),
41
53
  exit: finish,
54
+ fail,
42
55
  };
43
56
  for (const chunk of script.chunks ?? []) {
44
57
  chunks.push(chunk);
@@ -6,6 +6,6 @@
6
6
  export { FAKE_AGENT_BEHAVIOR_KIND, type FakeAgentBehavior, type FakeAgentCall, type FakeAgentDefinition, fakeAgent, } from "./fake-agent.js";
7
7
  export { type FakeClock, fakeClock } from "./fake-clock.js";
8
8
  export { createFakeControlledExec, type FakeControlledExec, type FakeControlledExecScript, } from "./fake-controlled-exec.js";
9
- export { type FakeExec, type FakeExecScript, fakeExec } from "./fake-exec.js";
9
+ export { type FakeExec, type FakeExecControls, type FakeExecScript, fakeExec, } from "./fake-exec.js";
10
10
  export { fakeSecretResolver } from "./fake-secrets.js";
11
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,wBAAwB,EACxB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,SAAS,GACV,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EACL,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,GAC9B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,wBAAwB,EACxB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,SAAS,GACV,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EACL,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,GAC9B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,QAAQ,GACT,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -6,5 +6,5 @@
6
6
  export { FAKE_AGENT_BEHAVIOR_KIND, fakeAgent, } from "./fake-agent.js";
7
7
  export { fakeClock } from "./fake-clock.js";
8
8
  export { createFakeControlledExec, } from "./fake-controlled-exec.js";
9
- export { fakeExec } from "./fake-exec.js";
9
+ export { fakeExec, } from "./fake-exec.js";
10
10
  export { fakeSecretResolver } from "./fake-secrets.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alpacakit/agents",
3
- "version": "0.2.0-beta.2",
3
+ "version": "0.2.0-beta.3",
4
4
  "description": "Run AI coding agents programmatically: execute, stream, classify failures, fail over.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "zod": "^4.3.6",
48
- "@alpacakit/core": "0.2.0-beta.2"
48
+ "@alpacakit/core": "0.2.0-beta.3"
49
49
  },
50
50
  "devDependencies": {
51
51
  "typescript": "^5.9.3",
@@ -1 +0,0 @@
1
- {"version":3,"file":"claude-session.d.ts","sourceRoot":"","sources":["../../src/agents/claude-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAkCH,OAAO,EAGL,KAAK,iBAAiB,EAKvB,MAAM,sBAAsB,CAAC;AAG9B,eAAO,MAAM,sBAAsB;;;;;;;CAOzB,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAC9B,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,OAAO,sBAAsB,CAAC,CAAC;AAEvE,+DAA+D;AAC/D,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;CACrC,CAAC;AA6EF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,cAAc,EAAE,SAAS,GAAG,UAAU,CAAC;CACjD,CAAC;AA4YF,wBAAgB,cAAc,CAC5B,OAAO,EAAE,oBAAoB,GAC5B,iBAAiB,CAAC,wBAAwB,CAAC,CAoB7C"}