@fixy/codex-adapter 0.0.2 → 0.0.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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "tool_name": "Bash",
3
+ "tool_input_preview": "{\"command\":\"pnpm tsc --noEmit 2>&1\",\"description\":\"Typecheck codex-adapter directly\"}",
4
+ "error": "Exit code 2\nsrc/index.ts(51,14): error TS2591: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.\n[rerun: b14]",
5
+ "timestamp": "2026-04-12T11:48:20.179Z",
6
+ "retry_count": 1
7
+ }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,4 @@
1
- export {};
1
+ import type { FixyAdapter } from '@fixy/core';
2
+ export declare function createCodexAdapter(): FixyAdapter;
3
+ export { parseCodexStreamJson } from './parse.js';
2
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,WAAW,EAIZ,MAAM,YAAY,CAAC;AA6LpB,wBAAgB,kBAAkB,IAAI,WAAW,CAEhD;AAED,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,172 @@
1
- export {};
1
+ // packages/codex-adapter/src/index.ts
2
+ import { buildInheritedEnv, redactEnvForLogs, resolveCommand, runChildProcess, } from '@fixy/adapter-utils';
3
+ import { parseCodexStreamJson } from './parse.js';
4
+ // Codex CLI writes skill-loader errors to stderr on startup — suppress them.
5
+ const CODEX_NOISE_RE = /^[0-9TZ:.+-]+ (ERROR|WARN) codex_/;
6
+ function filterCodexNoise(stderr) {
7
+ return stderr
8
+ .split('\n')
9
+ .filter((line) => !CODEX_NOISE_RE.test(line))
10
+ .join('\n')
11
+ .trim();
12
+ }
13
+ class CodexAdapter {
14
+ id = 'codex';
15
+ name = 'Codex CLI';
16
+ async probe() {
17
+ let resolvedCommand;
18
+ try {
19
+ resolvedCommand = await resolveCommand('codex');
20
+ }
21
+ catch {
22
+ return {
23
+ available: false,
24
+ version: null,
25
+ authStatus: 'unknown',
26
+ detail: 'codex CLI not found in PATH',
27
+ };
28
+ }
29
+ try {
30
+ const result = await runChildProcess({
31
+ command: resolvedCommand,
32
+ args: ['--version'],
33
+ cwd: process.cwd(),
34
+ env: buildInheritedEnv(),
35
+ timeoutMs: 10_000,
36
+ });
37
+ const version = result.stdout.trim() || null;
38
+ return {
39
+ available: true,
40
+ version,
41
+ authStatus: 'unknown',
42
+ detail: null,
43
+ };
44
+ }
45
+ catch (err) {
46
+ const detail = err instanceof Error ? err.message : String(err);
47
+ return {
48
+ available: false,
49
+ version: null,
50
+ authStatus: 'unknown',
51
+ detail,
52
+ };
53
+ }
54
+ }
55
+ async execute(ctx) {
56
+ const resolvedCommand = await resolveCommand('codex');
57
+ const env = buildInheritedEnv({});
58
+ let args;
59
+ if (ctx.session) {
60
+ // Resume existing session: codex exec resume <thread_id> --json ...
61
+ args = [
62
+ 'exec',
63
+ 'resume',
64
+ ctx.session.sessionId,
65
+ '--json',
66
+ '--skip-git-repo-check',
67
+ '--full-auto',
68
+ ctx.prompt,
69
+ ];
70
+ }
71
+ else {
72
+ // New session: codex exec --json --skip-git-repo-check --full-auto <prompt>
73
+ args = [
74
+ 'exec',
75
+ '--json',
76
+ '--skip-git-repo-check',
77
+ '--full-auto',
78
+ ctx.prompt,
79
+ ];
80
+ }
81
+ ctx.onMeta({
82
+ resolvedCommand,
83
+ args,
84
+ cwd: ctx.threadContext.worktreePath,
85
+ env: redactEnvForLogs(env),
86
+ });
87
+ // Buffer partial stdout lines so we can parse JSONL events and forward
88
+ // only the agent text instead of raw JSON to the terminal.
89
+ let stdoutLineBuffer = '';
90
+ const forwardJsonLine = (line) => {
91
+ try {
92
+ const obj = JSON.parse(line);
93
+ if (typeof obj === 'object' &&
94
+ obj !== null &&
95
+ obj['type'] === 'item.completed') {
96
+ const item = obj['item'];
97
+ if (typeof item === 'object' &&
98
+ item !== null &&
99
+ item['type'] === 'agent_message') {
100
+ const text = item['text'];
101
+ if (typeof text === 'string' && text.length > 0) {
102
+ ctx.onLog('stdout', text + '\n');
103
+ }
104
+ }
105
+ }
106
+ }
107
+ catch {
108
+ // Not JSON — forward as-is (shouldn't happen with --json flag)
109
+ if (line.length > 0)
110
+ ctx.onLog('stdout', line + '\n');
111
+ }
112
+ };
113
+ const result = await runChildProcess({
114
+ command: resolvedCommand,
115
+ args,
116
+ cwd: ctx.threadContext.worktreePath,
117
+ env,
118
+ signal: ctx.signal,
119
+ onLog: (stream, chunk) => {
120
+ if (stream === 'stderr') {
121
+ const filtered = filterCodexNoise(chunk);
122
+ if (filtered.length > 0)
123
+ ctx.onLog('stderr', filtered);
124
+ return;
125
+ }
126
+ // stdout: buffer and parse JSONL line-by-line
127
+ stdoutLineBuffer += chunk;
128
+ const lines = stdoutLineBuffer.split('\n');
129
+ stdoutLineBuffer = lines.pop() ?? '';
130
+ for (const line of lines) {
131
+ const trimmed = line.trim();
132
+ if (trimmed.length > 0)
133
+ forwardJsonLine(trimmed);
134
+ }
135
+ },
136
+ onSpawn: ctx.onSpawn,
137
+ });
138
+ // Flush any remaining buffered line
139
+ if (stdoutLineBuffer.trim().length > 0) {
140
+ forwardJsonLine(stdoutLineBuffer.trim());
141
+ }
142
+ const parsed = parseCodexStreamJson(result.stdout);
143
+ const warnings = [];
144
+ const filteredStderr = filterCodexNoise(result.stderr ?? '');
145
+ if (filteredStderr.length > 0) {
146
+ warnings.push(filteredStderr);
147
+ }
148
+ let errorMessage = null;
149
+ if (result.exitCode !== 0 && !result.timedOut) {
150
+ const stderrMsg = filteredStderr;
151
+ errorMessage =
152
+ stderrMsg.length > 0
153
+ ? `codex exited with code ${result.exitCode}: ${stderrMsg}`
154
+ : `codex exited with code ${result.exitCode}`;
155
+ }
156
+ return {
157
+ exitCode: result.exitCode,
158
+ signal: result.signal,
159
+ timedOut: result.timedOut,
160
+ summary: parsed.summary,
161
+ session: parsed.sessionId ? { sessionId: parsed.sessionId, params: {} } : null,
162
+ patches: [],
163
+ warnings,
164
+ errorMessage,
165
+ };
166
+ }
167
+ }
168
+ export function createCodexAdapter() {
169
+ return new CodexAdapter();
170
+ }
171
+ export { parseCodexStreamJson } from './parse.js';
2
172
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export interface CodexStreamResult {
2
+ sessionId: string | null;
3
+ summary: string;
4
+ }
5
+ export declare function parseCodexStreamJson(stdout: string): CodexStreamResult;
6
+ //# sourceMappingURL=parse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB;AAkBD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CA2CtE"}
package/dist/parse.js ADDED
@@ -0,0 +1,56 @@
1
+ function tryParseJson(line) {
2
+ try {
3
+ const parsed = JSON.parse(line);
4
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
5
+ return parsed;
6
+ }
7
+ return null;
8
+ }
9
+ catch {
10
+ return null;
11
+ }
12
+ }
13
+ function asString(value, fallback) {
14
+ return typeof value === 'string' && value.length > 0 ? value : fallback;
15
+ }
16
+ export function parseCodexStreamJson(stdout) {
17
+ if (stdout.length === 0) {
18
+ return { sessionId: null, summary: '' };
19
+ }
20
+ let sessionId = null;
21
+ const agentTexts = [];
22
+ const lines = stdout.split('\n');
23
+ for (const rawLine of lines) {
24
+ const line = rawLine.trim();
25
+ if (line.length === 0)
26
+ continue;
27
+ const obj = tryParseJson(line);
28
+ if (obj === null)
29
+ continue;
30
+ const type = obj['type'];
31
+ if (type === 'thread.started') {
32
+ // {"type":"thread.started","thread_id":"<uuid>"}
33
+ const tid = asString(obj['thread_id'], '');
34
+ if (tid !== '')
35
+ sessionId = tid;
36
+ }
37
+ else if (type === 'item.completed') {
38
+ // {"type":"item.completed","item":{"id":"...","type":"agent_message","text":"..."}}
39
+ const item = obj['item'];
40
+ if (typeof item === 'object' && item !== null && !Array.isArray(item)) {
41
+ const it = item;
42
+ if (it['type'] === 'agent_message') {
43
+ const text = asString(it['text'], '');
44
+ if (text !== '')
45
+ agentTexts.push(text);
46
+ }
47
+ }
48
+ }
49
+ }
50
+ const summary = agentTexts.join('\n\n').trim();
51
+ if (summary === '' && sessionId === null) {
52
+ return { sessionId: null, summary: stdout };
53
+ }
54
+ return { sessionId, summary };
55
+ }
56
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAKA,SAAS,YAAY,CAAC,IAAY;IAChC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5E,OAAO,MAAiC,CAAC;QAC3C,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc,EAAE,QAAgB;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAc;IACjD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC1C,CAAC;IAED,IAAI,SAAS,GAAkB,IAAI,CAAC;IACpC,MAAM,UAAU,GAAa,EAAE,CAAC;IAEhC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAEjC,KAAK,MAAM,OAAO,IAAI,KAAK,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAEhC,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,GAAG,KAAK,IAAI;YAAE,SAAS;QAE3B,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;QAEzB,IAAI,IAAI,KAAK,gBAAgB,EAAE,CAAC;YAC9B,iDAAiD;YACjD,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC;YAC3C,IAAI,GAAG,KAAK,EAAE;gBAAE,SAAS,GAAG,GAAG,CAAC;QAClC,CAAC;aAAM,IAAI,IAAI,KAAK,gBAAgB,EAAE,CAAC;YACrC,oFAAoF;YACpF,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;YACzB,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBACtE,MAAM,EAAE,GAAG,IAA+B,CAAC;gBAC3C,IAAI,EAAE,CAAC,MAAM,CAAC,KAAK,eAAe,EAAE,CAAC;oBACnC,MAAM,IAAI,GAAG,QAAQ,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;oBACtC,IAAI,IAAI,KAAK,EAAE;wBAAE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACzC,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IAE/C,IAAI,OAAO,KAAK,EAAE,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACzC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAC9C,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AAChC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fixy/codex-adapter",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -11,7 +11,11 @@
11
11
  }
12
12
  },
13
13
  "dependencies": {
14
- "@fixy/adapter-utils": "0.0.2"
14
+ "@fixy/core": "0.0.3",
15
+ "@fixy/adapter-utils": "0.0.3"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^20.0.0"
15
19
  },
16
20
  "license": "MIT",
17
21
  "scripts": {
package/src/index.ts CHANGED
@@ -1 +1,201 @@
1
- export {};
1
+ // packages/codex-adapter/src/index.ts
2
+
3
+ import type {
4
+ FixyAdapter,
5
+ FixyProbeResult,
6
+ FixyExecutionContext,
7
+ FixyExecutionResult,
8
+ } from '@fixy/core';
9
+
10
+ import {
11
+ buildInheritedEnv,
12
+ redactEnvForLogs,
13
+ resolveCommand,
14
+ runChildProcess,
15
+ } from '@fixy/adapter-utils';
16
+
17
+ import { parseCodexStreamJson } from './parse.js';
18
+
19
+ // Codex CLI writes skill-loader errors to stderr on startup — suppress them.
20
+ const CODEX_NOISE_RE = /^[0-9TZ:.+-]+ (ERROR|WARN) codex_/;
21
+
22
+ function filterCodexNoise(stderr: string): string {
23
+ return stderr
24
+ .split('\n')
25
+ .filter((line) => !CODEX_NOISE_RE.test(line))
26
+ .join('\n')
27
+ .trim();
28
+ }
29
+
30
+ class CodexAdapter implements FixyAdapter {
31
+ readonly id = 'codex';
32
+ readonly name = 'Codex CLI';
33
+
34
+ async probe(): Promise<FixyProbeResult> {
35
+ let resolvedCommand: string;
36
+ try {
37
+ resolvedCommand = await resolveCommand('codex');
38
+ } catch {
39
+ return {
40
+ available: false,
41
+ version: null,
42
+ authStatus: 'unknown',
43
+ detail: 'codex CLI not found in PATH',
44
+ };
45
+ }
46
+
47
+ try {
48
+ const result = await runChildProcess({
49
+ command: resolvedCommand,
50
+ args: ['--version'],
51
+ cwd: process.cwd(),
52
+ env: buildInheritedEnv(),
53
+ timeoutMs: 10_000,
54
+ });
55
+ const version = result.stdout.trim() || null;
56
+ return {
57
+ available: true,
58
+ version,
59
+ authStatus: 'unknown',
60
+ detail: null,
61
+ };
62
+ } catch (err) {
63
+ const detail = err instanceof Error ? err.message : String(err);
64
+ return {
65
+ available: false,
66
+ version: null,
67
+ authStatus: 'unknown',
68
+ detail,
69
+ };
70
+ }
71
+ }
72
+
73
+ async execute(ctx: FixyExecutionContext): Promise<FixyExecutionResult> {
74
+ const resolvedCommand = await resolveCommand('codex');
75
+ const env = buildInheritedEnv({});
76
+
77
+ let args: string[];
78
+
79
+ if (ctx.session) {
80
+ // Resume existing session: codex exec resume <thread_id> --json ...
81
+ args = [
82
+ 'exec',
83
+ 'resume',
84
+ ctx.session.sessionId,
85
+ '--json',
86
+ '--skip-git-repo-check',
87
+ '--full-auto',
88
+ ctx.prompt,
89
+ ];
90
+ } else {
91
+ // New session: codex exec --json --skip-git-repo-check --full-auto <prompt>
92
+ args = [
93
+ 'exec',
94
+ '--json',
95
+ '--skip-git-repo-check',
96
+ '--full-auto',
97
+ ctx.prompt,
98
+ ];
99
+ }
100
+
101
+ ctx.onMeta({
102
+ resolvedCommand,
103
+ args,
104
+ cwd: ctx.threadContext.worktreePath,
105
+ env: redactEnvForLogs(env),
106
+ });
107
+
108
+ // Buffer partial stdout lines so we can parse JSONL events and forward
109
+ // only the agent text instead of raw JSON to the terminal.
110
+ let stdoutLineBuffer = '';
111
+
112
+ const forwardJsonLine = (line: string): void => {
113
+ try {
114
+ const obj = JSON.parse(line);
115
+ if (
116
+ typeof obj === 'object' &&
117
+ obj !== null &&
118
+ obj['type'] === 'item.completed'
119
+ ) {
120
+ const item = obj['item'];
121
+ if (
122
+ typeof item === 'object' &&
123
+ item !== null &&
124
+ item['type'] === 'agent_message'
125
+ ) {
126
+ const text = item['text'];
127
+ if (typeof text === 'string' && text.length > 0) {
128
+ ctx.onLog('stdout', text + '\n');
129
+ }
130
+ }
131
+ }
132
+ } catch {
133
+ // Not JSON — forward as-is (shouldn't happen with --json flag)
134
+ if (line.length > 0) ctx.onLog('stdout', line + '\n');
135
+ }
136
+ };
137
+
138
+ const result = await runChildProcess({
139
+ command: resolvedCommand,
140
+ args,
141
+ cwd: ctx.threadContext.worktreePath,
142
+ env,
143
+ signal: ctx.signal,
144
+ onLog: (stream, chunk) => {
145
+ if (stream === 'stderr') {
146
+ const filtered = filterCodexNoise(chunk);
147
+ if (filtered.length > 0) ctx.onLog('stderr', filtered);
148
+ return;
149
+ }
150
+ // stdout: buffer and parse JSONL line-by-line
151
+ stdoutLineBuffer += chunk;
152
+ const lines = stdoutLineBuffer.split('\n');
153
+ stdoutLineBuffer = lines.pop() ?? '';
154
+ for (const line of lines) {
155
+ const trimmed = line.trim();
156
+ if (trimmed.length > 0) forwardJsonLine(trimmed);
157
+ }
158
+ },
159
+ onSpawn: ctx.onSpawn,
160
+ });
161
+
162
+ // Flush any remaining buffered line
163
+ if (stdoutLineBuffer.trim().length > 0) {
164
+ forwardJsonLine(stdoutLineBuffer.trim());
165
+ }
166
+
167
+ const parsed = parseCodexStreamJson(result.stdout);
168
+
169
+ const warnings: string[] = [];
170
+ const filteredStderr = filterCodexNoise(result.stderr ?? '');
171
+ if (filteredStderr.length > 0) {
172
+ warnings.push(filteredStderr);
173
+ }
174
+
175
+ let errorMessage: string | null = null;
176
+ if (result.exitCode !== 0 && !result.timedOut) {
177
+ const stderrMsg = filteredStderr;
178
+ errorMessage =
179
+ stderrMsg.length > 0
180
+ ? `codex exited with code ${result.exitCode}: ${stderrMsg}`
181
+ : `codex exited with code ${result.exitCode}`;
182
+ }
183
+
184
+ return {
185
+ exitCode: result.exitCode,
186
+ signal: result.signal,
187
+ timedOut: result.timedOut,
188
+ summary: parsed.summary,
189
+ session: parsed.sessionId ? { sessionId: parsed.sessionId, params: {} } : null,
190
+ patches: [],
191
+ warnings,
192
+ errorMessage,
193
+ };
194
+ }
195
+ }
196
+
197
+ export function createCodexAdapter(): FixyAdapter {
198
+ return new CodexAdapter();
199
+ }
200
+
201
+ export { parseCodexStreamJson } from './parse.js';
package/src/parse.ts ADDED
@@ -0,0 +1,65 @@
1
+ export interface CodexStreamResult {
2
+ sessionId: string | null;
3
+ summary: string;
4
+ }
5
+
6
+ function tryParseJson(line: string): Record<string, unknown> | null {
7
+ try {
8
+ const parsed = JSON.parse(line);
9
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
10
+ return parsed as Record<string, unknown>;
11
+ }
12
+ return null;
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
17
+
18
+ function asString(value: unknown, fallback: string): string {
19
+ return typeof value === 'string' && value.length > 0 ? value : fallback;
20
+ }
21
+
22
+ export function parseCodexStreamJson(stdout: string): CodexStreamResult {
23
+ if (stdout.length === 0) {
24
+ return { sessionId: null, summary: '' };
25
+ }
26
+
27
+ let sessionId: string | null = null;
28
+ const agentTexts: string[] = [];
29
+
30
+ const lines = stdout.split('\n');
31
+
32
+ for (const rawLine of lines) {
33
+ const line = rawLine.trim();
34
+ if (line.length === 0) continue;
35
+
36
+ const obj = tryParseJson(line);
37
+ if (obj === null) continue;
38
+
39
+ const type = obj['type'];
40
+
41
+ if (type === 'thread.started') {
42
+ // {"type":"thread.started","thread_id":"<uuid>"}
43
+ const tid = asString(obj['thread_id'], '');
44
+ if (tid !== '') sessionId = tid;
45
+ } else if (type === 'item.completed') {
46
+ // {"type":"item.completed","item":{"id":"...","type":"agent_message","text":"..."}}
47
+ const item = obj['item'];
48
+ if (typeof item === 'object' && item !== null && !Array.isArray(item)) {
49
+ const it = item as Record<string, unknown>;
50
+ if (it['type'] === 'agent_message') {
51
+ const text = asString(it['text'], '');
52
+ if (text !== '') agentTexts.push(text);
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ const summary = agentTexts.join('\n\n').trim();
59
+
60
+ if (summary === '' && sessionId === null) {
61
+ return { sessionId: null, summary: stdout };
62
+ }
63
+
64
+ return { sessionId, summary };
65
+ }
package/tsconfig.json CHANGED
@@ -2,7 +2,8 @@
2
2
  "extends": "../../tsconfig.base.json",
3
3
  "compilerOptions": {
4
4
  "outDir": "dist",
5
- "rootDir": "src"
5
+ "rootDir": "src",
6
+ "types": ["node"]
6
7
  },
7
8
  "include": ["src"]
8
9
  }