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.
Files changed (75) hide show
  1. package/dist/bridge/adapters.d.mts +33 -0
  2. package/dist/bridge/adapters.d.mts.map +1 -0
  3. package/dist/bridge/adapters.mjs +159 -0
  4. package/dist/bridge/adapters.mjs.map +1 -0
  5. package/dist/bridge/bridge-auth.d.mts +5 -0
  6. package/dist/bridge/bridge-auth.d.mts.map +1 -0
  7. package/dist/bridge/bridge-auth.mjs +24 -0
  8. package/dist/bridge/bridge-auth.mjs.map +1 -0
  9. package/dist/bridge/codex-app-server-adapter.d.mts +37 -0
  10. package/dist/bridge/codex-app-server-adapter.d.mts.map +1 -0
  11. package/dist/bridge/codex-app-server-adapter.mjs +401 -0
  12. package/dist/bridge/codex-app-server-adapter.mjs.map +1 -0
  13. package/dist/bridge/daemon.mjs +190 -0
  14. package/dist/bridge/foundr-codex-reply.d.mts +96 -0
  15. package/dist/bridge/foundr-codex-reply.d.mts.map +1 -0
  16. package/dist/bridge/foundr-codex-reply.mjs +542 -0
  17. package/dist/bridge/foundr-codex-reply.mjs.map +1 -0
  18. package/dist/bridge/listener-flags.d.mts +5 -0
  19. package/dist/bridge/listener-flags.d.mts.map +1 -0
  20. package/dist/bridge/listener-flags.mjs +18 -0
  21. package/dist/bridge/listener-flags.mjs.map +1 -0
  22. package/dist/bridge/listener-loop.d.mts +58 -0
  23. package/dist/bridge/listener-loop.d.mts.map +1 -0
  24. package/dist/bridge/listener-loop.mjs +424 -0
  25. package/dist/bridge/listener-loop.mjs.map +1 -0
  26. package/dist/bridge/logout.d.mts +8 -0
  27. package/dist/bridge/logout.d.mts.map +1 -0
  28. package/dist/bridge/logout.mjs +35 -0
  29. package/dist/bridge/logout.mjs.map +1 -0
  30. package/dist/bridge/mcp-client.d.mts +97 -0
  31. package/dist/bridge/mcp-client.d.mts.map +1 -0
  32. package/dist/bridge/mcp-client.mjs +290 -0
  33. package/dist/bridge/mcp-client.mjs.map +1 -0
  34. package/dist/bridge/oauth-provider.d.mts +32 -0
  35. package/dist/bridge/oauth-provider.d.mts.map +1 -0
  36. package/dist/bridge/oauth-provider.mjs +94 -0
  37. package/dist/bridge/oauth-provider.mjs.map +1 -0
  38. package/dist/bridge/public-room-mention-listener-loop.d.mts +120 -0
  39. package/dist/bridge/public-room-mention-listener-loop.d.mts.map +1 -0
  40. package/dist/bridge/public-room-mention-listener-loop.mjs +225 -0
  41. package/dist/bridge/public-room-mention-listener-loop.mjs.map +1 -0
  42. package/dist/bridge/realtime-wake.d.mts +11 -0
  43. package/dist/bridge/realtime-wake.d.mts.map +1 -0
  44. package/dist/bridge/realtime-wake.mjs +134 -0
  45. package/dist/bridge/realtime-wake.mjs.map +1 -0
  46. package/dist/bridge/work-mission-listener-loop.d.mts +100 -0
  47. package/dist/bridge/work-mission-listener-loop.d.mts.map +1 -0
  48. package/dist/bridge/work-mission-listener-loop.mjs +737 -0
  49. package/dist/bridge/work-mission-listener-loop.mjs.map +1 -0
  50. package/dist/cli-parse.d.ts +27 -0
  51. package/dist/cli-parse.d.ts.map +1 -0
  52. package/dist/cli-parse.js +47 -0
  53. package/dist/cli-parse.js.map +1 -0
  54. package/dist/cli.d.ts +3 -0
  55. package/dist/cli.d.ts.map +1 -0
  56. package/dist/cli.js +232 -0
  57. package/dist/cli.js.map +1 -0
  58. package/dist/codex-preflight.d.ts +44 -0
  59. package/dist/codex-preflight.d.ts.map +1 -0
  60. package/dist/codex-preflight.js +74 -0
  61. package/dist/codex-preflight.js.map +1 -0
  62. package/dist/index.d.ts +5 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +5 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/launchd.d.ts +78 -0
  67. package/dist/launchd.d.ts.map +1 -0
  68. package/dist/launchd.js +118 -0
  69. package/dist/launchd.js.map +1 -0
  70. package/dist/paths.d.ts +30 -0
  71. package/dist/paths.d.ts.map +1 -0
  72. package/dist/paths.js +26 -0
  73. package/dist/paths.js.map +1 -0
  74. package/install.sh +117 -0
  75. 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":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAC1C,OAAO,QAAQ,MAAM,wBAAwB,CAAA;AAE7C,MAAM,eAAe,GAAG;IACtB,mBAAmB;IACnB,sBAAsB;IACtB,yBAAyB;CAC1B,CAAA;AAED,SAAS,kBAAkB,CAAC,MAAM;IAChC,KAAK,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,CAAC;QACzD,MAAM,KAAK,GAAG,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAG,GAAG,CAAC,CAAA;QAC3B,IAAI,KAAK,IAAI,IAAI;YAAE,SAAQ;QAC3B,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAA;QACjC,IAAI,IAAI;YAAE,OAAO,IAAI,CAAA;IACvB,CAAC;IACD,OAAO,EAAE,CAAA;AACX,CAAC;AAED,SAAS,eAAe,CAAC,MAAM;;IAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,MAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,IAAI,mCAAI,EAAE,CAAC,CAAA;IACvC,OAAO,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,aAAa,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,cAAc,CAAA;AACnG,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,IAAI;;IAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,aAAJ,IAAI,cAAJ,IAAI,GAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACtC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IAEtB,IAAI,MAAM,CAAA;IACV,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAC3B,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,IAAI,mCAAI,EAAE,CAAC,CAAA;IACvC,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,aAAa,EAAE,CAAC;QAC/C,MAAM,KAAK,GAAG,MAAM,CAAC,MAAA,MAAA,MAAM,CAAC,IAAI,mCAAI,MAAM,CAAC,KAAK,mCAAI,EAAE,CAAC,CAAA;QACvD,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IACtD,CAAC;IACD,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,cAAc,EAAE,CAAC;QACjD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAA,MAAM,CAAC,MAAM,mCAAI,EAAE,CAAC,CAAA;QAC1C,OAAO,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;YACvD,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE;YAC5B,CAAC,CAAC,IAAI,CAAA;IACV,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAM;IACnC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,aAAN,MAAM,cAAN,MAAM,GAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACxC,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAA;IAEpB,MAAM,cAAc,GAAG,CAAC,SAAS,EAAE,EAAE;QACnC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QACpC,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,MAAM,CAAC,IAAI,EAAE,CAAA;QACpD,IAAI,eAAe,CAAC,MAAM,CAAC;YAAE,OAAO,EAAE,CAAA;QACtC,OAAO,kBAAkB,CAAC,MAAM,CAAC,CAAA;IACnC,CAAC,CAAA;IAED,IAAI,CAAC;QACH,OAAO,cAAc,CAAC,IAAI,CAAC,CAAA;IAC7B,CAAC;IAAC,WAAM,CAAC,CAAA,CAAC;IAEV,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAC5E,IAAI,CAAC,IAAI;YAAE,SAAQ;QACnB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;YACnC,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAA;QAC3B,CAAC;QAAC,WAAM,CAAC,CAAA,CAAC;IACZ,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAO,EAAE,QAAQ;;IAClD,MAAM,GAAG,qBAAQ,OAAO,CAAE,CAAA;IAC1B,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QAClC,OAAO,GAAG,CAAC,GAAG,CAAC,CAAA;IACjB,CAAC;IAED,GAAG,CAAC,gBAAgB,GAAG,MAAA,MAAA,QAAQ,CAAC,MAAM,0CAAE,EAAE,mCAAI,EAAE,CAAA;IAChD,GAAG,CAAC,iBAAiB,GAAG,MAAA,MAAA,QAAQ,CAAC,OAAO,0CAAE,EAAE,mCAAI,EAAE,CAAA;IAClD,GAAG,CAAC,mBAAmB,GAAG,MAAA,MAAA,QAAQ,CAAC,QAAQ,0CAAE,YAAY,mCAAI,EAAE,CAAA;IAC/D,GAAG,CAAC,mBAAmB,GAAG,MAAA,MAAA,QAAQ,CAAC,OAAO,0CAAE,IAAI,mCAAI,EAAE,CAAA;IACtD,GAAG,CAAC,mBAAmB,GAAG,MAAA,QAAQ,CAAC,IAAI,mCAAI,cAAc,CAAA;IACzD,GAAG,CAAC,iBAAiB,GAAG,MAAA,MAAA,QAAQ,CAAC,OAAO,0CAAE,EAAE,mCAAI,EAAE,CAAA;IAClD,GAAG,CAAC,oBAAoB,GAAG,MAAA,MAAA,QAAQ,CAAC,OAAO,0CAAE,KAAK,mCAAI,EAAE,CAAA;IACxD,GAAG,CAAC,sBAAsB,GAAG,MAAA,MAAA,QAAQ,CAAC,QAAQ,0CAAE,eAAe,mCAAI,EAAE,CAAA;IAErE,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,EAAE;IACxF,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;IAEtD,OAAO;QACL,IAAI,EAAE,OAAO;QACb,KAAK,CAAC,KAAK,CAAC,QAAQ;YAClB,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;YAC7C,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;YAClC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAA;QAC9B,CAAC;QACD,KAAK;YACH,EAAE,CAAC,KAAK,EAAE,CAAA;QACZ,CAAC;KACF,CAAA;AACH,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,EAAE,OAAO,EAAE;IAC9C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAA;IAC9C,CAAC;IAED,OAAO;QACL,IAAI,EAAE,SAAS;QACf,KAAK,CAAC,QAAQ,EAAE,OAAO,GAAG,EAAE;YAC1B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBACrC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE;oBAC3B,KAAK,EAAE,IAAI;oBACX,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;oBAC/B,GAAG,EAAE,kBAAkB,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC;iBAC/C,CAAC,CAAA;gBACF,IAAI,MAAM,GAAG,EAAE,CAAA;gBACf,IAAI,MAAM,GAAG,EAAE,CAAA;gBACf,IAAI,UAAU,GAAG,EAAE,CAAA;gBAEnB,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;;oBAChC,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;oBAC/B,MAAM,IAAI,SAAS,CAAA;oBACnB,UAAU,IAAI,SAAS,CAAA;oBACvB,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;oBACvC,UAAU,GAAG,MAAA,KAAK,CAAC,GAAG,EAAE,mCAAI,EAAE,CAAA;oBAC9B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;wBACzB,MAAM,KAAK,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAA;wBAC3C,IAAI,KAAK;4BAAE,MAAA,OAAO,CAAC,UAAU,wDAAG,KAAK,CAAC,CAAA;oBACxC,CAAC;gBACH,CAAC,CAAC,CAAA;gBACF,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;oBAChC,MAAM,IAAI,KAAK,CAAA;gBACjB,CAAC,CAAC,CAAA;gBACF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;gBACzB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;;oBACzB,IAAI,UAAU,EAAE,CAAC;wBACf,MAAM,KAAK,GAAG,uBAAuB,CAAC,UAAU,CAAC,CAAA;wBACjD,IAAI,KAAK;4BAAE,MAAA,OAAO,CAAC,UAAU,wDAAG,KAAK,CAAC,CAAA;oBACxC,CAAC;oBACD,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;wBACf,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,wBAAwB,IAAI,EAAE,CAAC,CAAC,CAAA;wBAClE,OAAM;oBACR,CAAC;oBACD,OAAO,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;gBAC3C,CAAC,CAAC,CAAA;gBAEF,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;YAClD,CAAC,CAAC,CAAA;QACJ,CAAC;QACD,KAAK,KAAI,CAAC;KACX,CAAA;AACH,CAAC"}
@@ -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
+ }