foundr-companion 0.1.0
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/dist/bridge/adapters.d.mts +33 -0
- package/dist/bridge/adapters.d.mts.map +1 -0
- package/dist/bridge/adapters.mjs +159 -0
- package/dist/bridge/adapters.mjs.map +1 -0
- package/dist/bridge/bridge-auth.d.mts +5 -0
- package/dist/bridge/bridge-auth.d.mts.map +1 -0
- package/dist/bridge/bridge-auth.mjs +24 -0
- package/dist/bridge/bridge-auth.mjs.map +1 -0
- package/dist/bridge/codex-app-server-adapter.d.mts +37 -0
- package/dist/bridge/codex-app-server-adapter.d.mts.map +1 -0
- package/dist/bridge/codex-app-server-adapter.mjs +401 -0
- package/dist/bridge/codex-app-server-adapter.mjs.map +1 -0
- package/dist/bridge/daemon.mjs +190 -0
- package/dist/bridge/foundr-codex-reply.d.mts +96 -0
- package/dist/bridge/foundr-codex-reply.d.mts.map +1 -0
- package/dist/bridge/foundr-codex-reply.mjs +542 -0
- package/dist/bridge/foundr-codex-reply.mjs.map +1 -0
- package/dist/bridge/listener-flags.d.mts +5 -0
- package/dist/bridge/listener-flags.d.mts.map +1 -0
- package/dist/bridge/listener-flags.mjs +18 -0
- package/dist/bridge/listener-flags.mjs.map +1 -0
- package/dist/bridge/listener-loop.d.mts +58 -0
- package/dist/bridge/listener-loop.d.mts.map +1 -0
- package/dist/bridge/listener-loop.mjs +424 -0
- package/dist/bridge/listener-loop.mjs.map +1 -0
- package/dist/bridge/logout.d.mts +8 -0
- package/dist/bridge/logout.d.mts.map +1 -0
- package/dist/bridge/logout.mjs +35 -0
- package/dist/bridge/logout.mjs.map +1 -0
- package/dist/bridge/mcp-client.d.mts +97 -0
- package/dist/bridge/mcp-client.d.mts.map +1 -0
- package/dist/bridge/mcp-client.mjs +290 -0
- package/dist/bridge/mcp-client.mjs.map +1 -0
- package/dist/bridge/oauth-provider.d.mts +32 -0
- package/dist/bridge/oauth-provider.d.mts.map +1 -0
- package/dist/bridge/oauth-provider.mjs +94 -0
- package/dist/bridge/oauth-provider.mjs.map +1 -0
- package/dist/bridge/public-room-mention-listener-loop.d.mts +120 -0
- package/dist/bridge/public-room-mention-listener-loop.d.mts.map +1 -0
- package/dist/bridge/public-room-mention-listener-loop.mjs +225 -0
- package/dist/bridge/public-room-mention-listener-loop.mjs.map +1 -0
- package/dist/bridge/realtime-wake.d.mts +11 -0
- package/dist/bridge/realtime-wake.d.mts.map +1 -0
- package/dist/bridge/realtime-wake.mjs +134 -0
- package/dist/bridge/realtime-wake.mjs.map +1 -0
- package/dist/bridge/work-mission-listener-loop.d.mts +100 -0
- package/dist/bridge/work-mission-listener-loop.d.mts.map +1 -0
- package/dist/bridge/work-mission-listener-loop.mjs +737 -0
- package/dist/bridge/work-mission-listener-loop.mjs.map +1 -0
- package/dist/cli-parse.d.ts +27 -0
- package/dist/cli-parse.d.ts.map +1 -0
- package/dist/cli-parse.js +47 -0
- package/dist/cli-parse.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +232 -0
- package/dist/cli.js.map +1 -0
- package/dist/codex-preflight.d.ts +44 -0
- package/dist/codex-preflight.d.ts.map +1 -0
- package/dist/codex-preflight.js +74 -0
- package/dist/codex-preflight.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/launchd.d.ts +78 -0
- package/dist/launchd.d.ts.map +1 -0
- package/dist/launchd.js +118 -0
- package/dist/launchd.js.map +1 -0
- package/dist/paths.d.ts +30 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +26 -0
- package/dist/paths.js.map +1 -0
- package/install.sh +117 -0
- package/package.json +29 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function parseReplyProgressEvent(line: any): {
|
|
2
|
+
type: string;
|
|
3
|
+
text: string;
|
|
4
|
+
status?: undefined;
|
|
5
|
+
} | {
|
|
6
|
+
type: string;
|
|
7
|
+
status: string;
|
|
8
|
+
text?: undefined;
|
|
9
|
+
} | null;
|
|
10
|
+
export function parseReplyText(stdout: any): string;
|
|
11
|
+
export function commandEnvironment(baseEnv: any, envelope: any): any;
|
|
12
|
+
export function createStdioAdapter({ input, output }?: {
|
|
13
|
+
input?: (NodeJS.ReadStream & {
|
|
14
|
+
fd: 0;
|
|
15
|
+
}) | undefined;
|
|
16
|
+
output?: (NodeJS.WriteStream & {
|
|
17
|
+
fd: 1;
|
|
18
|
+
}) | undefined;
|
|
19
|
+
}): {
|
|
20
|
+
name: string;
|
|
21
|
+
reply(envelope: any): Promise<{
|
|
22
|
+
text: string;
|
|
23
|
+
}>;
|
|
24
|
+
close(): void;
|
|
25
|
+
};
|
|
26
|
+
export function createCommandAdapter({ command }: {
|
|
27
|
+
command: any;
|
|
28
|
+
}): {
|
|
29
|
+
name: string;
|
|
30
|
+
reply(envelope: any, options?: {}): Promise<any>;
|
|
31
|
+
close(): void;
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=adapters.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapters.d.mts","sourceRoot":"","sources":["../../src/bridge/adapters.mjs"],"names":[],"mappings":"AAwBA;;;;;;;;SAsBC;AAED,oDAwBC;AAED,qEAgBC;AAED;;;;;;;;;;;;;EAcC;AAED;;;;;;EAkDC"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import readline from 'node:readline/promises'
|
|
3
|
+
|
|
4
|
+
const SECRET_ENV_KEYS = [
|
|
5
|
+
'FOUNDR_MCP_BEARER',
|
|
6
|
+
'FOUNDR_REFRESH_TOKEN',
|
|
7
|
+
'FOUNDR_BRIDGE_AUTH_FILE',
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
function firstNonEmptyField(parsed) {
|
|
11
|
+
for (const key of ['text', 'reply', 'message', 'output']) {
|
|
12
|
+
const value = parsed?.[key]
|
|
13
|
+
if (value == null) continue
|
|
14
|
+
const text = String(value).trim()
|
|
15
|
+
if (text) return text
|
|
16
|
+
}
|
|
17
|
+
return ''
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isProgressReply(parsed) {
|
|
21
|
+
const type = String(parsed?.type ?? '')
|
|
22
|
+
return type === 'delta' || type === 'reply.delta' || type === 'status' || type === 'reply.status'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function parseReplyProgressEvent(line) {
|
|
26
|
+
const text = String(line ?? '').trim()
|
|
27
|
+
if (!text) return null
|
|
28
|
+
|
|
29
|
+
let parsed
|
|
30
|
+
try {
|
|
31
|
+
parsed = JSON.parse(text)
|
|
32
|
+
} catch {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
const type = String(parsed?.type ?? '')
|
|
36
|
+
if (type === 'delta' || type === 'reply.delta') {
|
|
37
|
+
const delta = String(parsed.text ?? parsed.delta ?? '')
|
|
38
|
+
return delta ? { type: 'delta', text: delta } : null
|
|
39
|
+
}
|
|
40
|
+
if (type === 'status' || type === 'reply.status') {
|
|
41
|
+
const status = String(parsed.status ?? '')
|
|
42
|
+
return ['thinking', 'writing', 'failed'].includes(status)
|
|
43
|
+
? { type: 'status', status }
|
|
44
|
+
: null
|
|
45
|
+
}
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function parseReplyText(stdout) {
|
|
50
|
+
const text = String(stdout ?? '').trim()
|
|
51
|
+
if (!text) return ''
|
|
52
|
+
|
|
53
|
+
const parseJsonReply = (candidate) => {
|
|
54
|
+
const parsed = JSON.parse(candidate)
|
|
55
|
+
if (typeof parsed === 'string') return parsed.trim()
|
|
56
|
+
if (isProgressReply(parsed)) return ''
|
|
57
|
+
return firstNonEmptyField(parsed)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
return parseJsonReply(text)
|
|
62
|
+
} catch {}
|
|
63
|
+
|
|
64
|
+
for (const line of text.split(/\r?\n/).map((item) => item.trim()).reverse()) {
|
|
65
|
+
if (!line) continue
|
|
66
|
+
try {
|
|
67
|
+
const parsed = parseJsonReply(line)
|
|
68
|
+
if (parsed) return parsed
|
|
69
|
+
} catch {}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return text
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function commandEnvironment(baseEnv, envelope) {
|
|
76
|
+
const env = { ...baseEnv }
|
|
77
|
+
for (const key of SECRET_ENV_KEYS) {
|
|
78
|
+
delete env[key]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
env.FOUNDR_THREAD_ID = envelope.thread?.id ?? ''
|
|
82
|
+
env.FOUNDR_MESSAGE_ID = envelope.message?.id ?? ''
|
|
83
|
+
env.FOUNDR_AGENT_KEY_ID = envelope.delivery?.agent_key_id ?? ''
|
|
84
|
+
env.FOUNDR_MESSAGE_TEXT = envelope.message?.body ?? ''
|
|
85
|
+
env.FOUNDR_CHANNEL_KIND = envelope.kind ?? 'private_chat'
|
|
86
|
+
env.FOUNDR_MISSION_ID = envelope.mission?.id ?? ''
|
|
87
|
+
env.FOUNDR_MISSION_TITLE = envelope.mission?.title ?? ''
|
|
88
|
+
env.FOUNDR_REQUESTED_MODEL = envelope.delivery?.requested_model ?? ''
|
|
89
|
+
|
|
90
|
+
return env
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function createStdioAdapter({ input = process.stdin, output = process.stdout } = {}) {
|
|
94
|
+
const rl = readline.createInterface({ input, output })
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
name: 'stdio',
|
|
98
|
+
async reply(envelope) {
|
|
99
|
+
output.write(`${JSON.stringify(envelope)}\n`)
|
|
100
|
+
const line = await rl.question('')
|
|
101
|
+
return { text: line.trim() }
|
|
102
|
+
},
|
|
103
|
+
close() {
|
|
104
|
+
rl.close()
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function createCommandAdapter({ command }) {
|
|
110
|
+
if (!command) {
|
|
111
|
+
throw new Error('reply command is required')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
name: 'command',
|
|
116
|
+
reply(envelope, options = {}) {
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
const child = spawn(command, {
|
|
119
|
+
shell: true,
|
|
120
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
121
|
+
env: commandEnvironment(process.env, envelope),
|
|
122
|
+
})
|
|
123
|
+
let stdout = ''
|
|
124
|
+
let stderr = ''
|
|
125
|
+
let lineBuffer = ''
|
|
126
|
+
|
|
127
|
+
child.stdout.on('data', (chunk) => {
|
|
128
|
+
const chunkText = String(chunk)
|
|
129
|
+
stdout += chunkText
|
|
130
|
+
lineBuffer += chunkText
|
|
131
|
+
const lines = lineBuffer.split(/\r?\n/)
|
|
132
|
+
lineBuffer = lines.pop() ?? ''
|
|
133
|
+
for (const line of lines) {
|
|
134
|
+
const event = parseReplyProgressEvent(line)
|
|
135
|
+
if (event) options.onProgress?.(event)
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
child.stderr.on('data', (chunk) => {
|
|
139
|
+
stderr += chunk
|
|
140
|
+
})
|
|
141
|
+
child.on('error', reject)
|
|
142
|
+
child.on('close', (code) => {
|
|
143
|
+
if (lineBuffer) {
|
|
144
|
+
const event = parseReplyProgressEvent(lineBuffer)
|
|
145
|
+
if (event) options.onProgress?.(event)
|
|
146
|
+
}
|
|
147
|
+
if (code !== 0) {
|
|
148
|
+
reject(new Error(stderr.trim() || `reply command exited ${code}`))
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
resolve({ text: parseReplyText(stdout) })
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
child.stdin.end(`${JSON.stringify(envelope)}\n`)
|
|
155
|
+
})
|
|
156
|
+
},
|
|
157
|
+
close() {},
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapters.mjs","sourceRoot":"","sources":["../../src/bridge/adapters.mjs"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export function authFilePath(): string;
|
|
2
|
+
export function loadBridgeAuth(file?: string): Promise<any>;
|
|
3
|
+
export function saveBridgeAuth(auth: any, file?: string): Promise<void>;
|
|
4
|
+
export function deleteBridgeAuth(file?: string): Promise<void>;
|
|
5
|
+
//# sourceMappingURL=bridge-auth.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge-auth.d.mts","sourceRoot":"","sources":["../../src/bridge/bridge-auth.mjs"],"names":[],"mappings":"AAIA,uCAEC;AAED,4DAGC;AAED,wEAMC;AAED,+DAEC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
export function authFilePath() {
|
|
6
|
+
return path.join(os.homedir(), '.foundr-world', 'bridge-auth.json')
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function loadBridgeAuth(file = authFilePath()) {
|
|
10
|
+
const raw = await fs.readFile(file, 'utf8')
|
|
11
|
+
return JSON.parse(raw)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function saveBridgeAuth(auth, file = authFilePath()) {
|
|
15
|
+
const dir = path.dirname(file)
|
|
16
|
+
await fs.mkdir(dir, { recursive: true, mode: 0o700 })
|
|
17
|
+
await fs.chmod(dir, 0o700)
|
|
18
|
+
await fs.writeFile(file, `${JSON.stringify(auth, null, 2)}\n`, { mode: 0o600 })
|
|
19
|
+
await fs.chmod(file, 0o600)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function deleteBridgeAuth(file = authFilePath()) {
|
|
23
|
+
await fs.rm(file, { force: true })
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge-auth.mjs","sourceRoot":"","sources":["../../src/bridge/bridge-auth.mjs"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACjC,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,MAAM,UAAU,YAAY;IAC1B,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,eAAe,EAAE,kBAAkB,CAAC,CAAA;AACrE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAI,GAAG,YAAY,EAAE;IACxD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,YAAY,EAAE;IAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9B,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACrD,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IAC1B,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAC/E,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;AAC7B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAI,GAAG,YAAY,EAAE;IAC1D,MAAM,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AACpC,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function createCodexAppServerAdapter(options?: {}): {
|
|
2
|
+
name: string;
|
|
3
|
+
start(): Promise<void>;
|
|
4
|
+
reply(envelope: any, replyOptions?: {}): Promise<{
|
|
5
|
+
text: string;
|
|
6
|
+
}>;
|
|
7
|
+
close(): void;
|
|
8
|
+
};
|
|
9
|
+
export class CodexAppServerSession {
|
|
10
|
+
constructor({ env, spawnFn, requestTimeoutMs, turnTimeoutMs, }?: {
|
|
11
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
12
|
+
spawnFn?: typeof spawn | undefined;
|
|
13
|
+
requestTimeoutMs?: number | undefined;
|
|
14
|
+
turnTimeoutMs?: number | undefined;
|
|
15
|
+
});
|
|
16
|
+
env: NodeJS.ProcessEnv;
|
|
17
|
+
spawnFn: typeof spawn;
|
|
18
|
+
requestTimeoutMs: number;
|
|
19
|
+
turnTimeoutMs: number;
|
|
20
|
+
child: null;
|
|
21
|
+
lineBuffer: string;
|
|
22
|
+
stderrText: string;
|
|
23
|
+
nextRequestId: number;
|
|
24
|
+
pending: Map<any, any>;
|
|
25
|
+
startPromise: any;
|
|
26
|
+
threadId: any;
|
|
27
|
+
currentTurn: any;
|
|
28
|
+
closed: boolean;
|
|
29
|
+
start(): Promise<void>;
|
|
30
|
+
reply(envelope: any, options?: {}): Promise<{
|
|
31
|
+
text: string;
|
|
32
|
+
}>;
|
|
33
|
+
close(): void;
|
|
34
|
+
#private;
|
|
35
|
+
}
|
|
36
|
+
import { spawn } from 'node:child_process';
|
|
37
|
+
//# sourceMappingURL=codex-app-server-adapter.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-app-server-adapter.d.mts","sourceRoot":"","sources":["../../src/bridge/codex-app-server-adapter.mjs"],"names":[],"mappings":"AAkYA;;;;;;;EAcC;AA7SD;IACE;;;;;OAmBC;IAbC,uBAAc;IACd,sBAAsB;IACtB,yBAAwC;IACxC,sBAAkC;IAClC,YAAiB;IACjB,mBAAoB;IACpB,mBAAoB;IACpB,sBAAsB;IACtB,uBAAwB;IACxB,kBAAwB;IACxB,cAAoB;IACpB,iBAAuB;IACvB,gBAAmB;IAGrB,uBAYC;IAED;;OAIC;IAED,cAYC;;CAuOF;sBAhYqB,oBAAoB"}
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import fs from 'node:fs/promises'
|
|
3
|
+
import os from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { parseReplyText } from './adapters.mjs'
|
|
6
|
+
import {
|
|
7
|
+
appServerErrorMessage,
|
|
8
|
+
appServerBridgeInstructions,
|
|
9
|
+
appServerRequestParams,
|
|
10
|
+
appServerResumeParams,
|
|
11
|
+
appServerServerRequestResponse,
|
|
12
|
+
appServerThreadStartParams,
|
|
13
|
+
appServerTurnStartParams,
|
|
14
|
+
codexArgs,
|
|
15
|
+
codexSessionId,
|
|
16
|
+
codexThreadMode,
|
|
17
|
+
extractCodexProgressEvent,
|
|
18
|
+
extractCompletedCodexText,
|
|
19
|
+
parseJsonLine,
|
|
20
|
+
safeCodexEnv,
|
|
21
|
+
} from './foundr-codex-reply.mjs'
|
|
22
|
+
|
|
23
|
+
const APP_SERVER_REQUEST_TIMEOUT_MS = 120_000
|
|
24
|
+
const APP_SERVER_TURN_TIMEOUT_MS = 180_000
|
|
25
|
+
|
|
26
|
+
function eventThreadId(event) {
|
|
27
|
+
return event?.params?.threadId ?? event?.threadId ?? event?.params?.turn?.threadId ?? null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function eventTurnId(event) {
|
|
31
|
+
return event?.params?.turnId ?? event?.turnId ?? event?.params?.turn?.id ?? null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isTerminalTurnEvent(event) {
|
|
35
|
+
return event?.method === 'turn/completed' || event?.type === 'turn.completed'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function appServerExitedError(code, stderrText) {
|
|
39
|
+
return new Error(stderrText.trim() || `Codex app-server exited ${code}`)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function nowMs() {
|
|
43
|
+
return performance.now()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function timingEnabled(env) {
|
|
47
|
+
return env.FOUNDR_PRIVATE_CHAT_TIMING === '1'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function timingLog(env, startedAt, message) {
|
|
51
|
+
if (!timingEnabled(env)) return
|
|
52
|
+
console.error(`[foundr:codex:timing +${Math.round(nowMs() - startedAt)}ms] ${message}`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function traceEnabled(env) {
|
|
56
|
+
return env.FOUNDR_CODEX_TRACE === '1'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function traceLog(env, event) {
|
|
60
|
+
if (!traceEnabled(env)) return
|
|
61
|
+
const method = event?.method ?? event?.type ?? 'unknown'
|
|
62
|
+
const item = event?.params?.item ?? event?.item
|
|
63
|
+
const suffix = item?.type ? ` item=${item.type}` : ''
|
|
64
|
+
console.error(`[foundr:codex:trace] ${method}${suffix}`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function bridgeThreadStorePath(env) {
|
|
68
|
+
const configured = String(env.FOUNDR_CODEX_BRIDGE_STORE || '').trim()
|
|
69
|
+
if (configured) return configured
|
|
70
|
+
return path.join(os.homedir(), '.foundr-world', 'codex-bridge-thread.json')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function readBridgeThreadStore(env) {
|
|
74
|
+
const configured = String(env.FOUNDR_CODEX_BRIDGE_THREAD_ID || '').trim()
|
|
75
|
+
if (configured) return { threadId: configured, source: 'env' }
|
|
76
|
+
if (env.FOUNDR_CODEX_BRIDGE_EPHEMERAL === '1') return null
|
|
77
|
+
if (env.FOUNDR_CODEX_BRIDGE_RESET === '1') return null
|
|
78
|
+
|
|
79
|
+
const storePath = bridgeThreadStorePath(env)
|
|
80
|
+
const data = await fs.readFile(storePath, 'utf8')
|
|
81
|
+
.then(text => JSON.parse(text))
|
|
82
|
+
.catch(() => null)
|
|
83
|
+
const threadId = String(data?.thread_id || data?.threadId || '').trim()
|
|
84
|
+
return threadId ? { threadId, source: 'store' } : null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function writeBridgeThreadStore(env, threadId) {
|
|
88
|
+
if (!threadId || env.FOUNDR_CODEX_BRIDGE_EPHEMERAL === '1') return
|
|
89
|
+
if (String(env.FOUNDR_CODEX_BRIDGE_THREAD_ID || '').trim()) return
|
|
90
|
+
|
|
91
|
+
const storePath = bridgeThreadStorePath(env)
|
|
92
|
+
await fs.mkdir(path.dirname(storePath), { recursive: true })
|
|
93
|
+
await fs.writeFile(storePath, JSON.stringify({
|
|
94
|
+
thread_id: threadId,
|
|
95
|
+
instructions_hash_hint: appServerBridgeInstructions(env).slice(0, 160),
|
|
96
|
+
updated_at: new Date().toISOString(),
|
|
97
|
+
}, null, 2))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export class CodexAppServerSession {
|
|
101
|
+
constructor({
|
|
102
|
+
env = process.env,
|
|
103
|
+
spawnFn = spawn,
|
|
104
|
+
requestTimeoutMs = APP_SERVER_REQUEST_TIMEOUT_MS,
|
|
105
|
+
turnTimeoutMs = APP_SERVER_TURN_TIMEOUT_MS,
|
|
106
|
+
} = {}) {
|
|
107
|
+
this.env = env
|
|
108
|
+
this.spawnFn = spawnFn
|
|
109
|
+
this.requestTimeoutMs = requestTimeoutMs
|
|
110
|
+
this.turnTimeoutMs = turnTimeoutMs
|
|
111
|
+
this.child = null
|
|
112
|
+
this.lineBuffer = ''
|
|
113
|
+
this.stderrText = ''
|
|
114
|
+
this.nextRequestId = 1
|
|
115
|
+
this.pending = new Map()
|
|
116
|
+
this.startPromise = null
|
|
117
|
+
this.threadId = null
|
|
118
|
+
this.currentTurn = null
|
|
119
|
+
this.closed = false
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async start() {
|
|
123
|
+
if (this.closed) {
|
|
124
|
+
throw new Error('Codex app-server adapter is closed')
|
|
125
|
+
}
|
|
126
|
+
if (this.threadId && this.child) return
|
|
127
|
+
this.startPromise ??= this.#start()
|
|
128
|
+
try {
|
|
129
|
+
await this.startPromise
|
|
130
|
+
} catch (error) {
|
|
131
|
+
this.startPromise = null
|
|
132
|
+
throw error
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async reply(envelope, options = {}) {
|
|
137
|
+
await this.start()
|
|
138
|
+
const text = await this.#runTurn(envelope, options)
|
|
139
|
+
return { text: parseReplyText(text) }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
close() {
|
|
143
|
+
this.closed = true
|
|
144
|
+
if (this.child) {
|
|
145
|
+
try {
|
|
146
|
+
this.child.kill?.('SIGTERM')
|
|
147
|
+
} catch {}
|
|
148
|
+
}
|
|
149
|
+
this.#failPending(new Error('Codex app-server adapter closed'))
|
|
150
|
+
this.#failCurrentTurn(new Error('Codex app-server adapter closed'))
|
|
151
|
+
this.child = null
|
|
152
|
+
this.threadId = null
|
|
153
|
+
this.startPromise = null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async #start() {
|
|
157
|
+
const startedAt = nowMs()
|
|
158
|
+
const threadMode = codexThreadMode(this.env)
|
|
159
|
+
const sessionId = codexSessionId(this.env)
|
|
160
|
+
if (threadMode === 'resume' && !sessionId) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
'CODEX_THREAD_ID is unavailable. Start the listener from an active Codex shell, set FOUNDR_CODEX_SESSION_ID, or use FOUNDR_CODEX_THREAD_MODE=bridge.',
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.child = this.spawnFn('codex', codexArgs(), {
|
|
167
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
168
|
+
env: safeCodexEnv(this.env),
|
|
169
|
+
})
|
|
170
|
+
this.lineBuffer = ''
|
|
171
|
+
this.stderrText = ''
|
|
172
|
+
this.child.stdout.on('data', (chunk) => this.#handleStdout(chunk))
|
|
173
|
+
this.child.stderr.on('data', (chunk) => {
|
|
174
|
+
this.stderrText += chunk
|
|
175
|
+
})
|
|
176
|
+
this.child.on('error', (error) => {
|
|
177
|
+
this.#handleExit(error)
|
|
178
|
+
})
|
|
179
|
+
this.child.on('close', (code) => {
|
|
180
|
+
this.#handleExit(appServerExitedError(code, this.stderrText))
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
await this.#send('initialize')
|
|
184
|
+
timingLog(this.env, startedAt, 'initialized app-server')
|
|
185
|
+
this.child.stdin.write(`${JSON.stringify({ method: 'initialized' })}\n`)
|
|
186
|
+
if (threadMode === 'bridge') {
|
|
187
|
+
this.threadId = await this.#ensureBridgeThread(startedAt)
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const resumed = await this.#send('thread/resume', appServerResumeParams(sessionId, this.env))
|
|
192
|
+
this.threadId = resumed?.thread?.id ?? sessionId
|
|
193
|
+
timingLog(this.env, startedAt, `resumed thread=${this.threadId}`)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async #ensureBridgeThread(startedAt) {
|
|
197
|
+
const stored = await readBridgeThreadStore(this.env)
|
|
198
|
+
if (stored?.threadId) {
|
|
199
|
+
try {
|
|
200
|
+
const resumed = await this.#send('thread/resume', appServerResumeParams(stored.threadId, this.env))
|
|
201
|
+
const threadId = resumed?.thread?.id ?? stored.threadId
|
|
202
|
+
timingLog(this.env, startedAt, `resumed bridge thread=${threadId} source=${stored.source}`)
|
|
203
|
+
return threadId
|
|
204
|
+
} catch (error) {
|
|
205
|
+
timingLog(this.env, startedAt, `bridge resume failed; starting new thread: ${appServerErrorMessage(error)}`)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const started = await this.#send('thread/start', appServerThreadStartParams(this.env))
|
|
210
|
+
const threadId = started?.thread?.id
|
|
211
|
+
if (!threadId) {
|
|
212
|
+
throw new Error('Codex app-server did not return a thread id for bridge mode')
|
|
213
|
+
}
|
|
214
|
+
await writeBridgeThreadStore(this.env, threadId).catch(() => {})
|
|
215
|
+
timingLog(this.env, startedAt, `started bridge thread=${threadId}`)
|
|
216
|
+
return threadId
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#runTurn(envelope, { onProgress } = {}) {
|
|
220
|
+
if (this.currentTurn) {
|
|
221
|
+
throw new Error('Codex app-server already has an active turn')
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let timer
|
|
225
|
+
let turnState
|
|
226
|
+
const startedAt = nowMs()
|
|
227
|
+
const done = new Promise((resolve, reject) => {
|
|
228
|
+
timer = setTimeout(() => {
|
|
229
|
+
this.#failCurrentTurn(new Error('Codex app-server turn timed out'))
|
|
230
|
+
}, this.turnTimeoutMs)
|
|
231
|
+
timer.unref?.()
|
|
232
|
+
|
|
233
|
+
this.currentTurn = {
|
|
234
|
+
threadId: this.threadId,
|
|
235
|
+
turnId: null,
|
|
236
|
+
streamedText: '',
|
|
237
|
+
finalText: '',
|
|
238
|
+
firstDeltaAt: null,
|
|
239
|
+
startedAt,
|
|
240
|
+
onProgress,
|
|
241
|
+
resolve: (text) => {
|
|
242
|
+
timingLog(this.env, startedAt, `turn completed chars=${String(text ?? '').length}`)
|
|
243
|
+
clearTimeout(timer)
|
|
244
|
+
this.currentTurn = null
|
|
245
|
+
resolve(text)
|
|
246
|
+
},
|
|
247
|
+
reject: (error) => {
|
|
248
|
+
clearTimeout(timer)
|
|
249
|
+
this.currentTurn = null
|
|
250
|
+
reject(error)
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
turnState = this.currentTurn
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
void this.#send('turn/start', appServerTurnStartParams(this.threadId, envelope, this.env))
|
|
257
|
+
.then((started) => {
|
|
258
|
+
timingLog(this.env, startedAt, `turn/start accepted turn=${started?.turn?.id ?? 'unknown'}`)
|
|
259
|
+
if (this.currentTurn === turnState && !this.currentTurn.turnId && started?.turn?.id) {
|
|
260
|
+
this.currentTurn.turnId = started.turn.id
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
.catch((error) => {
|
|
264
|
+
if (this.currentTurn === turnState) {
|
|
265
|
+
this.#failCurrentTurn(error)
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
return done
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
#send(method, params) {
|
|
273
|
+
if (!this.child) {
|
|
274
|
+
return Promise.reject(new Error('Codex app-server is not running'))
|
|
275
|
+
}
|
|
276
|
+
const id = this.nextRequestId++
|
|
277
|
+
return new Promise((resolve, reject) => {
|
|
278
|
+
const timer = setTimeout(() => {
|
|
279
|
+
this.pending.delete(id)
|
|
280
|
+
reject(new Error(`Codex app-server request timed out: ${method}`))
|
|
281
|
+
}, this.requestTimeoutMs)
|
|
282
|
+
timer.unref?.()
|
|
283
|
+
this.pending.set(id, {
|
|
284
|
+
method,
|
|
285
|
+
timer,
|
|
286
|
+
resolve,
|
|
287
|
+
reject,
|
|
288
|
+
})
|
|
289
|
+
this.child.stdin.write(`${JSON.stringify({ id, method, params: appServerRequestParams(method, params) })}\n`)
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
#handleStdout(chunk) {
|
|
294
|
+
this.lineBuffer += String(chunk)
|
|
295
|
+
const lines = this.lineBuffer.split(/\r?\n/)
|
|
296
|
+
this.lineBuffer = lines.pop() ?? ''
|
|
297
|
+
for (const line of lines) {
|
|
298
|
+
this.#handleMessage(parseJsonLine(line))
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
#handleMessage(event) {
|
|
303
|
+
if (!event) return
|
|
304
|
+
traceLog(this.env, event)
|
|
305
|
+
|
|
306
|
+
if (event.id != null && event.method) {
|
|
307
|
+
const response = appServerServerRequestResponse(String(event.method))
|
|
308
|
+
if ('error' in response) {
|
|
309
|
+
this.child?.stdin.write(`${JSON.stringify({ id: event.id, error: response.error })}\n`)
|
|
310
|
+
} else {
|
|
311
|
+
this.child?.stdin.write(`${JSON.stringify({ id: event.id, result: response })}\n`)
|
|
312
|
+
}
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (event.id != null) {
|
|
317
|
+
const request = this.pending.get(event.id)
|
|
318
|
+
if (!request) return
|
|
319
|
+
this.pending.delete(event.id)
|
|
320
|
+
clearTimeout(request.timer)
|
|
321
|
+
if (event.error) {
|
|
322
|
+
request.reject(new Error(appServerErrorMessage(event.error)))
|
|
323
|
+
} else {
|
|
324
|
+
request.resolve(event.result)
|
|
325
|
+
}
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
this.#handleNotification(event)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
#handleNotification(event) {
|
|
333
|
+
const turn = this.currentTurn
|
|
334
|
+
if (!turn) return
|
|
335
|
+
|
|
336
|
+
const threadId = eventThreadId(event)
|
|
337
|
+
if (threadId && turn.threadId && threadId !== turn.threadId) return
|
|
338
|
+
|
|
339
|
+
if (event?.method === 'turn/started' && event?.params?.turn?.id) {
|
|
340
|
+
turn.turnId = event.params.turn.id
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const turnId = eventTurnId(event)
|
|
344
|
+
if (turn.turnId && turnId && turnId !== turn.turnId) return
|
|
345
|
+
|
|
346
|
+
const completedText = extractCompletedCodexText(event)
|
|
347
|
+
if (completedText) turn.finalText = completedText
|
|
348
|
+
|
|
349
|
+
const progressEvent = extractCodexProgressEvent(event)
|
|
350
|
+
if (progressEvent?.type === 'delta') turn.streamedText += progressEvent.text
|
|
351
|
+
if (progressEvent) {
|
|
352
|
+
if (progressEvent.type === 'delta' && !turn.firstDeltaAt) {
|
|
353
|
+
turn.firstDeltaAt = nowMs()
|
|
354
|
+
timingLog(this.env, turn.startedAt, 'first agent delta')
|
|
355
|
+
}
|
|
356
|
+
turn.onProgress?.(progressEvent)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (isTerminalTurnEvent(event) && (!turn.turnId || !turnId || turnId === turn.turnId)) {
|
|
360
|
+
turn.resolve(turn.finalText || turn.streamedText)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
#handleExit(error) {
|
|
365
|
+
if (this.closed) return
|
|
366
|
+
this.child = null
|
|
367
|
+
this.threadId = null
|
|
368
|
+
this.startPromise = null
|
|
369
|
+
this.#failPending(error)
|
|
370
|
+
this.#failCurrentTurn(error)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
#failPending(error) {
|
|
374
|
+
for (const request of this.pending.values()) {
|
|
375
|
+
clearTimeout(request.timer)
|
|
376
|
+
request.reject(error)
|
|
377
|
+
}
|
|
378
|
+
this.pending.clear()
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
#failCurrentTurn(error) {
|
|
382
|
+
if (!this.currentTurn) return
|
|
383
|
+
this.currentTurn.reject(error)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function createCodexAppServerAdapter(options = {}) {
|
|
388
|
+
const session = new CodexAppServerSession(options)
|
|
389
|
+
return {
|
|
390
|
+
name: 'codex-app-server',
|
|
391
|
+
start() {
|
|
392
|
+
return session.start()
|
|
393
|
+
},
|
|
394
|
+
reply(envelope, replyOptions = {}) {
|
|
395
|
+
return session.reply(envelope, replyOptions)
|
|
396
|
+
},
|
|
397
|
+
close() {
|
|
398
|
+
session.close()
|
|
399
|
+
},
|
|
400
|
+
}
|
|
401
|
+
}
|