@alpacakit/agents 0.1.0-beta.39 → 0.2.0-beta.2

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 (53) hide show
  1. package/README.md +121 -1
  2. package/dist/agents/claude-session.d.ts +37 -0
  3. package/dist/agents/claude-session.d.ts.map +1 -0
  4. package/dist/agents/claude-session.js +419 -0
  5. package/dist/agents/claude-stream.d.ts +65 -0
  6. package/dist/agents/claude-stream.d.ts.map +1 -0
  7. package/dist/agents/claude-stream.js +111 -0
  8. package/dist/agents/claude.d.ts +2 -1
  9. package/dist/agents/claude.d.ts.map +1 -1
  10. package/dist/agents/claude.js +6 -107
  11. package/dist/agents/codex-session.d.ts +52 -0
  12. package/dist/agents/codex-session.d.ts.map +1 -0
  13. package/dist/agents/codex-session.js +495 -0
  14. package/dist/agents/codex.d.ts +2 -1
  15. package/dist/agents/codex.d.ts.map +1 -1
  16. package/dist/agents/codex.js +3 -1
  17. package/dist/codex/app-server.d.ts +50 -1
  18. package/dist/codex/app-server.d.ts.map +1 -1
  19. package/dist/codex/app-server.js +130 -27
  20. package/dist/codex/index.d.ts +3 -2
  21. package/dist/codex/index.d.ts.map +1 -1
  22. package/dist/codex/index.js +2 -1
  23. package/dist/codex/protocol.d.ts +101 -0
  24. package/dist/codex/protocol.d.ts.map +1 -1
  25. package/dist/codex/protocol.js +78 -0
  26. package/dist/core/process-close.d.ts +11 -0
  27. package/dist/core/process-close.d.ts.map +1 -0
  28. package/dist/core/process-close.js +27 -0
  29. package/dist/core/session.d.ts +100 -0
  30. package/dist/core/session.d.ts.map +1 -0
  31. package/dist/core/session.js +29 -0
  32. package/dist/host/session-files.d.ts +22 -0
  33. package/dist/host/session-files.d.ts.map +1 -0
  34. package/dist/host/session-files.js +91 -0
  35. package/dist/index.d.ts +7 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +3 -0
  38. package/dist/internal/controlled-process/supervisor-runtime.js +27 -1
  39. package/dist/kernel/definition.d.ts +11 -2
  40. package/dist/kernel/definition.d.ts.map +1 -1
  41. package/dist/kernel/facets.d.ts +38 -1
  42. package/dist/kernel/facets.d.ts.map +1 -1
  43. package/dist/kernel/facets.js +20 -0
  44. package/dist/kernel/runtime.d.ts +10 -6
  45. package/dist/kernel/runtime.d.ts.map +1 -1
  46. package/dist/kernel/runtime.js +4 -1
  47. package/dist/kernel/session.d.ts +97 -0
  48. package/dist/kernel/session.d.ts.map +1 -0
  49. package/dist/kernel/session.js +255 -0
  50. package/dist/testing/fake-exec.d.ts +6 -0
  51. package/dist/testing/fake-exec.d.ts.map +1 -1
  52. package/dist/testing/fake-exec.js +3 -1
  53. package/package.json +2 -2
@@ -0,0 +1,255 @@
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.
7
+ */
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";
12
+ import { OUTPUT_CHANNEL } from "../core/values.js";
13
+ import { EventBroadcast } from "./handle.js";
14
+ const OUTPUT_TAIL_CHARS = 2_000;
15
+ const EVENT_BASE_COST_BYTES = 64;
16
+ /** Both hosts use UUID-shaped conversation ids; anything else never reaches a path. */
17
+ 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;
18
+ export function assertHostSessionId(id, agentId) {
19
+ if (HOST_SESSION_ID.test(id)) {
20
+ return;
21
+ }
22
+ throw new AgentError({
23
+ code: AGENT_ERROR_CODE.invalidInput,
24
+ classification: AgentError.agentFailure(AGENT_ERROR_CODE.invalidInput),
25
+ message: `Agent "${agentId}" session id is not a UUID`,
26
+ data: { reason: "session id must be a UUID" },
27
+ agentId,
28
+ });
29
+ }
30
+ function sessionEventCost(event) {
31
+ return (EVENT_BASE_COST_BYTES +
32
+ (event.type === SESSION_EVENT_TYPE.message ? event.text.length : 0));
33
+ }
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
+ export function sessionStateError(agentId, reason) {
41
+ return new AgentError({
42
+ code: AGENT_ERROR_CODE.invalidInput,
43
+ classification: AgentError.agentFailure(AGENT_ERROR_CODE.invalidInput),
44
+ message: `Agent "${agentId}" session: ${reason}`,
45
+ data: { reason },
46
+ agentId,
47
+ });
48
+ }
49
+ export function sessionExitedError(agentId, exit, stderrTail) {
50
+ const tail = redactSecrets(stderrTail);
51
+ return new AgentError({
52
+ code: AGENT_ERROR_CODE.failed,
53
+ classification: AgentError.agentFailure(AGENT_ERROR_CODE.failed),
54
+ message: `Agent "${agentId}" session process exited`,
55
+ data: {
56
+ exitCode: exit?.code ?? null,
57
+ stdoutTail: null,
58
+ stderrTail: tail === "" ? null : tail,
59
+ },
60
+ agentId,
61
+ });
62
+ }
63
+ /**
64
+ * One interactive child held for the life of a Session: stdout is read as
65
+ * NDJSON lines, stdin stays open for incremental writes, and `close()`
66
+ * ends stdin first (both hosts exit on EOF) before escalating to signals.
67
+ */
68
+ export class SessionProcess {
69
+ process;
70
+ clock;
71
+ exitValue = null;
72
+ stderrTail = "";
73
+ constructor(process, clock) {
74
+ this.process = process;
75
+ this.clock = clock;
76
+ void process.exit.then((exit) => {
77
+ this.exitValue = exit;
78
+ }, () => {
79
+ this.exitValue = { code: null, signal: null };
80
+ });
81
+ }
82
+ 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
+ }
93
+ const process = input.ctx.exec.spawn({
94
+ command,
95
+ args: [...input.args],
96
+ cwd: input.cwd,
97
+ env: input.ctx.env,
98
+ stdin: null,
99
+ keepStdinOpen: true,
100
+ });
101
+ return new SessionProcess(process, input.ctx.clock);
102
+ }
103
+ get exit() {
104
+ return this.process.exit;
105
+ }
106
+ get exited() {
107
+ return this.exitValue;
108
+ }
109
+ get stderr() {
110
+ return this.stderrTail;
111
+ }
112
+ writeLine(line) {
113
+ this.process.writeStdin(`${line}\n`);
114
+ }
115
+ /** Non-empty stdout lines in order; stderr is kept as a tail for errors. */
116
+ async *lines() {
117
+ let pending = "";
118
+ for await (const chunk of this.process.chunks) {
119
+ if (chunk.channel === OUTPUT_CHANNEL.stderr) {
120
+ this.stderrTail = appendTail(this.stderrTail, chunk.text);
121
+ continue;
122
+ }
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;
136
+ }
137
+ }
138
+ close() {
139
+ if (this.exitValue !== null) {
140
+ return Promise.resolve(this.exitValue);
141
+ }
142
+ return endProcessGracefully(this.process, this.clock);
143
+ }
144
+ }
145
+ /**
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.
149
+ */
150
+ export class SessionState {
151
+ agentId;
152
+ broadcast = new EventBroadcast(undefined, sessionEventCost);
153
+ active = null;
154
+ /** A turn whose start the host has not acknowledged yet (Codex `turn/start` in flight). */
155
+ starting = false;
156
+ ended = false;
157
+ constructor(agentId) {
158
+ this.agentId = agentId;
159
+ }
160
+ get events() {
161
+ return this.broadcast.events;
162
+ }
163
+ get activeTurnId() {
164
+ return this.active?.turnId ?? null;
165
+ }
166
+ get isEnded() {
167
+ return this.ended;
168
+ }
169
+ get isStarting() {
170
+ return this.starting;
171
+ }
172
+ emit(event) {
173
+ this.broadcast.push(event);
174
+ }
175
+ /** Record a message for the turn's fallback text and publish it. */
176
+ message(text) {
177
+ this.active?.messages.push(text);
178
+ this.emit({ type: SESSION_EVENT_TYPE.message, text });
179
+ }
180
+ requireOpen() {
181
+ if (this.ended) {
182
+ throw sessionStateError(this.agentId, "session is closed");
183
+ }
184
+ }
185
+ requireIdle() {
186
+ this.requireOpen();
187
+ if (this.active !== null || this.starting) {
188
+ throw sessionStateError(this.agentId, "a turn is in progress; use steer() or interrupt()");
189
+ }
190
+ }
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
+ requireActive() {
200
+ this.requireOpen();
201
+ if (this.active === null) {
202
+ throw sessionStateError(this.agentId, "no turn is in progress");
203
+ }
204
+ return this.active.turnId;
205
+ }
206
+ beginTurn(turnId) {
207
+ this.starting = false;
208
+ return new Promise((resolve, reject) => {
209
+ this.active = { turnId, messages: [], resolve, reject };
210
+ this.emit({ type: SESSION_EVENT_TYPE.turnStarted, turnId });
211
+ });
212
+ }
213
+ /** `text === null` falls back to the turn's collected messages. */
214
+ endTurn(outcome, text) {
215
+ const turn = this.active;
216
+ if (turn === null) {
217
+ return;
218
+ }
219
+ this.active = null;
220
+ this.emit({
221
+ type: SESSION_EVENT_TYPE.turnEnded,
222
+ turnId: turn.turnId,
223
+ outcome,
224
+ });
225
+ turn.resolve({
226
+ turnId: turn.turnId,
227
+ outcome,
228
+ text: text ?? turn.messages.join("\n"),
229
+ });
230
+ }
231
+ /** The process is gone: fail the running turn, publish `exited`, end the stream. */
232
+ end(exit, stderrTail) {
233
+ if (this.ended) {
234
+ return;
235
+ }
236
+ this.ended = true;
237
+ this.starting = false;
238
+ const turn = this.active;
239
+ this.active = null;
240
+ if (turn !== null) {
241
+ this.emit({
242
+ type: SESSION_EVENT_TYPE.turnEnded,
243
+ turnId: turn.turnId,
244
+ outcome: TURN_OUTCOME.failed,
245
+ });
246
+ turn.reject(sessionExitedError(this.agentId, exit, stderrTail));
247
+ }
248
+ this.emit({
249
+ type: SESSION_EVENT_TYPE.exited,
250
+ code: exit.code,
251
+ signal: exit.signal,
252
+ });
253
+ this.broadcast.end();
254
+ }
255
+ }
@@ -13,6 +13,12 @@ export type FakeExecScript = {
13
13
  stderr(text: string): void;
14
14
  exit(exit: ExecExit): void;
15
15
  }) => void;
16
+ /** 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;
16
22
  };
17
23
  export type FakeExec = Exec & {
18
24
  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;CACX,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,CAmFjE"}
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"}
@@ -56,7 +56,9 @@ export function fakeExec(scripts = []) {
56
56
  writes.push(text);
57
57
  script.onWrite?.(text, controls);
58
58
  },
59
- endStdin: () => { },
59
+ endStdin: () => {
60
+ script.onStdinEnd?.(controls);
61
+ },
60
62
  kill: (signal) => {
61
63
  killSignals.push(signal);
62
64
  if (signal === PROCESS_SIGNAL.sigterm &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alpacakit/agents",
3
- "version": "0.1.0-beta.39",
3
+ "version": "0.2.0-beta.2",
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.1.0-beta.39"
48
+ "@alpacakit/core": "0.2.0-beta.2"
49
49
  },
50
50
  "devDependencies": {
51
51
  "typescript": "^5.9.3",