@telora/daemon 0.19.3 → 0.19.6
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/build-info.json +4 -3
- package/dist/backends/codex/codex-mcp-proxy.d.ts +128 -0
- package/dist/backends/codex/codex-mcp-proxy.d.ts.map +1 -0
- package/dist/backends/codex/codex-mcp-proxy.js +297 -0
- package/dist/backends/codex/codex-mcp-proxy.js.map +1 -0
- package/dist/backends/codex/mcp-preflight.d.ts +73 -0
- package/dist/backends/codex/mcp-preflight.d.ts.map +1 -0
- package/dist/backends/codex/mcp-preflight.js +188 -0
- package/dist/backends/codex/mcp-preflight.js.map +1 -0
- package/dist/backends/codex/namespace-bridge.d.ts +131 -0
- package/dist/backends/codex/namespace-bridge.d.ts.map +1 -0
- package/dist/backends/codex/namespace-bridge.js +149 -0
- package/dist/backends/codex/namespace-bridge.js.map +1 -0
- package/dist/focus-executor.d.ts +12 -1
- package/dist/focus-executor.d.ts.map +1 -1
- package/dist/focus-executor.js +71 -8
- package/dist/focus-executor.js.map +1 -1
- package/dist/spawner/helpers.d.ts.map +1 -1
- package/dist/spawner/helpers.js +28 -0
- package/dist/spawner/helpers.js.map +1 -1
- package/dist/types/focus.d.ts +8 -0
- package/dist/types/focus.d.ts.map +1 -1
- package/package.json +1 -1
package/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commitSha": "
|
|
3
|
-
"builtAt": "2026-06-
|
|
2
|
+
"commitSha": "4523841b3",
|
|
3
|
+
"builtAt": "2026-06-28T15:29:31.449Z",
|
|
4
4
|
"expectedMigrations": [
|
|
5
5
|
"20250829113330_create_org_nodes_table.sql",
|
|
6
6
|
"20250829113402_fix_function_security_search_path.sql",
|
|
@@ -687,6 +687,7 @@
|
|
|
687
687
|
"20260626201457_seed_builtin_stage_models_opus.sql",
|
|
688
688
|
"20260626204927_local_model_sizing_metadata.sql",
|
|
689
689
|
"20260627124000_local_model_sizing_metadata_writes.sql",
|
|
690
|
-
"20260627231555_normalize_focus_coding_mcp_tools.sql"
|
|
690
|
+
"20260627231555_normalize_focus_coding_mcp_tools.sql",
|
|
691
|
+
"20260628140456_loop_review_watermark_and_proposals.sql"
|
|
691
692
|
]
|
|
692
693
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Localhost translating proxy for local-model (OSS) Codex Responses traffic.
|
|
3
|
+
*
|
|
4
|
+
* Codex serializes each MCP server as one `type:"namespace"` Responses tool;
|
|
5
|
+
* OSS inference servers (llama-server) drop that tool type, so the model never
|
|
6
|
+
* sees Telora MCP tools (see namespace-bridge.ts + the runbook). This proxy is
|
|
7
|
+
* the localhost shim that bridges the gap for registered local-model passes
|
|
8
|
+
* ONLY -- hosted/cloud and Claude passes never route through it.
|
|
9
|
+
*
|
|
10
|
+
* Request path (model-bound, CERTAIN): the proxy buffers each `/responses`
|
|
11
|
+
* request, flattens `mcp__*` namespace tools into ordinary `function` tools via
|
|
12
|
+
* flattenNamespaceTools(), and forwards the rewritten body upstream so a
|
|
13
|
+
* namespace-incompatible server receives the Telora tool schemas.
|
|
14
|
+
*
|
|
15
|
+
* Response path (Codex-bound, CAPTURE-GATED): the upstream SSE stream is parsed
|
|
16
|
+
* and any flat `mcp__`-named function call is rewritten toward Codex's canonical
|
|
17
|
+
* namespace identity via normalizeToolCall(). The EXACT envelope Codex accepts
|
|
18
|
+
* is confirmed against a live OSS capture before routing is flipped on by
|
|
19
|
+
* default (runbook step 3); until then this stays opt-in and the rewrite is the
|
|
20
|
+
* single isolated place to pin once the capture lands.
|
|
21
|
+
*
|
|
22
|
+
* Reception guard: the proxy records whether any tools-bearing request it
|
|
23
|
+
* forwarded actually carried the required Telora control tools (after
|
|
24
|
+
* flattening). The spawn path reads getReceptionState() to fail a tool-less
|
|
25
|
+
* pass loud (durable session) instead of letting it silently loop.
|
|
26
|
+
*
|
|
27
|
+
* Binds 127.0.0.1 on an ephemeral port. Logs are redacted (no prompt content).
|
|
28
|
+
*/
|
|
29
|
+
/** Tools a local-model coding pass must be able to call to sync Telora state. */
|
|
30
|
+
export declare const PROXY_REQUIRED_TOOLS: readonly ["telora_product_issue", "telora_product_delivery"];
|
|
31
|
+
/**
|
|
32
|
+
* Env flag that opts a daemon into routing local-model passes through this
|
|
33
|
+
* proxy. OPT-IN (default off) until the response envelope is confirmed against a
|
|
34
|
+
* live OSS capture (runbook step 3) -- unlike the opt-OUT loop flags.
|
|
35
|
+
*/
|
|
36
|
+
export declare const LOCAL_MODEL_MCP_PROXY_FLAG = "TELORA_LOCAL_MODEL_MCP_PROXY";
|
|
37
|
+
/**
|
|
38
|
+
* Pure decision: should this pass route through the translating proxy? True only
|
|
39
|
+
* for a registered local-model pass when the opt-in flag is truthy. A non-local
|
|
40
|
+
* pass (hosted/cloud/Claude) NEVER routes -- regardless of the flag -- so the
|
|
41
|
+
* hosted path is provably untouched.
|
|
42
|
+
*
|
|
43
|
+
* Opt-in semantics (inverse of shouldRunLoop): undefined/''/'0'/'false' -> off;
|
|
44
|
+
* any other value -> on.
|
|
45
|
+
*/
|
|
46
|
+
export declare function shouldRouteLocalModelThroughProxy(flagValue: string | undefined, isLocalModelPass: boolean): boolean;
|
|
47
|
+
export interface ProxyReceptionState {
|
|
48
|
+
/** A request carrying a non-empty tools array was forwarded at least once. */
|
|
49
|
+
sawToolsRequest: boolean;
|
|
50
|
+
/** Every required Telora tool was present (after flattening) in such a request. */
|
|
51
|
+
requiredToolsDelivered: boolean;
|
|
52
|
+
/** Required tools observed missing from a tools-bearing request. */
|
|
53
|
+
missingTools: string[];
|
|
54
|
+
}
|
|
55
|
+
export interface CodexMcpProxyHandle {
|
|
56
|
+
/** Base URL to advertise to Codex as the provider base_url (mirrors upstream path). */
|
|
57
|
+
url: string;
|
|
58
|
+
port: number;
|
|
59
|
+
getReceptionState(): ProxyReceptionState;
|
|
60
|
+
close(): Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
export interface StartProxyOptions {
|
|
63
|
+
/** The real OSS inference server base URL, e.g. http://10.1.1.x:8888/v1. */
|
|
64
|
+
upstreamBaseUrl: string;
|
|
65
|
+
requiredTools?: readonly string[];
|
|
66
|
+
/** Injected fetch (tests); defaults to global fetch. */
|
|
67
|
+
fetchImpl?: typeof fetch;
|
|
68
|
+
/** Injected logger (tests); defaults to console. */
|
|
69
|
+
logger?: {
|
|
70
|
+
log: (msg: string) => void;
|
|
71
|
+
warn: (msg: string) => void;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Decode the set of MCP sub-tools present in a (already-flattened) tools array.
|
|
76
|
+
* Plain built-in functions (non-mcp__ names) are ignored.
|
|
77
|
+
*/
|
|
78
|
+
export declare function decodeDeliveredTools(tools: unknown): Set<string>;
|
|
79
|
+
/** Exit category for a pass whose required Telora tools never reached the model. */
|
|
80
|
+
export declare const MCP_TOOLS_UNDELIVERED_EXIT_CATEGORY = "mcp_tools_undelivered";
|
|
81
|
+
/**
|
|
82
|
+
* Durable terminal-session payload for a pass that ran without its Telora
|
|
83
|
+
* control surface. Same shape as the other session-update builders; kept in this
|
|
84
|
+
* neutral module so the close handler (spawner/helpers) can use it without a
|
|
85
|
+
* circular import on focus-executor.
|
|
86
|
+
*/
|
|
87
|
+
export declare function mcpToolsUndeliveredSessionUpdate(missingTools: readonly string[]): {
|
|
88
|
+
status: string;
|
|
89
|
+
exit_reason: string;
|
|
90
|
+
exit_category: string;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Pure decision: given a proxy's final reception state, should this pass be
|
|
94
|
+
* failed closed (durable) because the model never received its required Telora
|
|
95
|
+
* control tools? True only when a tools-bearing request was seen AND a required
|
|
96
|
+
* tool was missing -- a pass that simply never sent tools (no model turn) is not
|
|
97
|
+
* penalized. Drives mcpToolsUndeliveredSessionUpdate at team close.
|
|
98
|
+
*/
|
|
99
|
+
export declare function shouldFailClosedForUndeliveredTools(reception: ProxyReceptionState): {
|
|
100
|
+
fail: boolean;
|
|
101
|
+
missing: string[];
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Update a reception state given the tools array of a freshly-flattened request.
|
|
105
|
+
* Only requests that actually carry tools count toward delivery (the first model
|
|
106
|
+
* turn carries the tool list; later turns may omit it). Pure; returned as a new
|
|
107
|
+
* object.
|
|
108
|
+
*/
|
|
109
|
+
export declare function updateReceptionState(prev: ProxyReceptionState, flattenedTools: unknown, requiredTools: readonly string[]): ProxyReceptionState;
|
|
110
|
+
/**
|
|
111
|
+
* Rewrite a single decoded SSE event object: if it carries a flat `mcp__`
|
|
112
|
+
* function call, normalize it toward Codex's canonical namespace/tool identity.
|
|
113
|
+
* Returns the (possibly) rewritten object. CAPTURE-GATED shape -- see module
|
|
114
|
+
* header; pin here once the live envelope is confirmed.
|
|
115
|
+
*/
|
|
116
|
+
export declare function rewriteResponseEvent(event: unknown): unknown;
|
|
117
|
+
/**
|
|
118
|
+
* Transform an SSE response chunk stream. Stateful over `data:` lines: each
|
|
119
|
+
* complete `data: {json}` line is parsed, run through rewriteResponseEvent, and
|
|
120
|
+
* re-serialized. Non-data lines and unparseable payloads pass through verbatim.
|
|
121
|
+
* Returns a function that consumes raw text chunks and returns transformed text.
|
|
122
|
+
*/
|
|
123
|
+
export declare function createSseTransformer(): (chunk: string) => string;
|
|
124
|
+
/**
|
|
125
|
+
* Start the localhost translating proxy. Resolves once it is listening.
|
|
126
|
+
*/
|
|
127
|
+
export declare function startCodexMcpProxy(opts: StartProxyOptions): Promise<CodexMcpProxyHandle>;
|
|
128
|
+
//# sourceMappingURL=codex-mcp-proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-mcp-proxy.d.ts","sourceRoot":"","sources":["../../../src/backends/codex/codex-mcp-proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAWH,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,8DAA+D,CAAC;AAEjG;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,iCAAiC,CAAC;AAEzE;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,CAC/C,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,gBAAgB,EAAE,OAAO,GACxB,OAAO,CAIT;AAED,MAAM,WAAW,mBAAmB;IAClC,8EAA8E;IAC9E,eAAe,EAAE,OAAO,CAAC;IACzB,mFAAmF;IACnF,sBAAsB,EAAE,OAAO,CAAC;IAChC,oEAAoE;IACpE,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,uFAAuF;IACvF,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,IAAI,mBAAmB,CAAC;IACzC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,wDAAwD;IACxD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,oDAAoD;IACpD,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CACtE;AAKD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,CAUhE;AAED,oFAAoF;AACpF,eAAO,MAAM,mCAAmC,0BAA0B,CAAC;AAE3E;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG;IACjF,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB,CASA;AAED;;;;;;GAMG;AACH,wBAAgB,mCAAmC,CACjD,SAAS,EAAE,mBAAmB,GAC7B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAGtC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,mBAAmB,EACzB,cAAc,EAAE,OAAO,EACvB,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,mBAAmB,CASrB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CA4B5D;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAahE;AA0BD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA8FxF"}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Localhost translating proxy for local-model (OSS) Codex Responses traffic.
|
|
3
|
+
*
|
|
4
|
+
* Codex serializes each MCP server as one `type:"namespace"` Responses tool;
|
|
5
|
+
* OSS inference servers (llama-server) drop that tool type, so the model never
|
|
6
|
+
* sees Telora MCP tools (see namespace-bridge.ts + the runbook). This proxy is
|
|
7
|
+
* the localhost shim that bridges the gap for registered local-model passes
|
|
8
|
+
* ONLY -- hosted/cloud and Claude passes never route through it.
|
|
9
|
+
*
|
|
10
|
+
* Request path (model-bound, CERTAIN): the proxy buffers each `/responses`
|
|
11
|
+
* request, flattens `mcp__*` namespace tools into ordinary `function` tools via
|
|
12
|
+
* flattenNamespaceTools(), and forwards the rewritten body upstream so a
|
|
13
|
+
* namespace-incompatible server receives the Telora tool schemas.
|
|
14
|
+
*
|
|
15
|
+
* Response path (Codex-bound, CAPTURE-GATED): the upstream SSE stream is parsed
|
|
16
|
+
* and any flat `mcp__`-named function call is rewritten toward Codex's canonical
|
|
17
|
+
* namespace identity via normalizeToolCall(). The EXACT envelope Codex accepts
|
|
18
|
+
* is confirmed against a live OSS capture before routing is flipped on by
|
|
19
|
+
* default (runbook step 3); until then this stays opt-in and the rewrite is the
|
|
20
|
+
* single isolated place to pin once the capture lands.
|
|
21
|
+
*
|
|
22
|
+
* Reception guard: the proxy records whether any tools-bearing request it
|
|
23
|
+
* forwarded actually carried the required Telora control tools (after
|
|
24
|
+
* flattening). The spawn path reads getReceptionState() to fail a tool-less
|
|
25
|
+
* pass loud (durable session) instead of letting it silently loop.
|
|
26
|
+
*
|
|
27
|
+
* Binds 127.0.0.1 on an ephemeral port. Logs are redacted (no prompt content).
|
|
28
|
+
*/
|
|
29
|
+
import { createServer } from 'node:http';
|
|
30
|
+
import { flattenNamespaceTools, normalizeToolCall, decodeFlatToolName, redactRequestForLog, } from './namespace-bridge.js';
|
|
31
|
+
/** Tools a local-model coding pass must be able to call to sync Telora state. */
|
|
32
|
+
export const PROXY_REQUIRED_TOOLS = ['telora_product_issue', 'telora_product_delivery'];
|
|
33
|
+
/**
|
|
34
|
+
* Env flag that opts a daemon into routing local-model passes through this
|
|
35
|
+
* proxy. OPT-IN (default off) until the response envelope is confirmed against a
|
|
36
|
+
* live OSS capture (runbook step 3) -- unlike the opt-OUT loop flags.
|
|
37
|
+
*/
|
|
38
|
+
export const LOCAL_MODEL_MCP_PROXY_FLAG = 'TELORA_LOCAL_MODEL_MCP_PROXY';
|
|
39
|
+
/**
|
|
40
|
+
* Pure decision: should this pass route through the translating proxy? True only
|
|
41
|
+
* for a registered local-model pass when the opt-in flag is truthy. A non-local
|
|
42
|
+
* pass (hosted/cloud/Claude) NEVER routes -- regardless of the flag -- so the
|
|
43
|
+
* hosted path is provably untouched.
|
|
44
|
+
*
|
|
45
|
+
* Opt-in semantics (inverse of shouldRunLoop): undefined/''/'0'/'false' -> off;
|
|
46
|
+
* any other value -> on.
|
|
47
|
+
*/
|
|
48
|
+
export function shouldRouteLocalModelThroughProxy(flagValue, isLocalModelPass) {
|
|
49
|
+
if (!isLocalModelPass)
|
|
50
|
+
return false;
|
|
51
|
+
const v = (flagValue ?? '').trim().toLowerCase();
|
|
52
|
+
return v !== '' && v !== '0' && v !== 'false' && v !== 'off' && v !== 'no';
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Decode the set of MCP sub-tools present in a (already-flattened) tools array.
|
|
56
|
+
* Plain built-in functions (non-mcp__ names) are ignored.
|
|
57
|
+
*/
|
|
58
|
+
export function decodeDeliveredTools(tools) {
|
|
59
|
+
const out = new Set();
|
|
60
|
+
if (!Array.isArray(tools))
|
|
61
|
+
return out;
|
|
62
|
+
for (const t of tools) {
|
|
63
|
+
if (t?.type === 'function' && typeof t.name === 'string') {
|
|
64
|
+
const decoded = decodeFlatToolName(t.name);
|
|
65
|
+
if (decoded)
|
|
66
|
+
out.add(decoded.tool);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
/** Exit category for a pass whose required Telora tools never reached the model. */
|
|
72
|
+
export const MCP_TOOLS_UNDELIVERED_EXIT_CATEGORY = 'mcp_tools_undelivered';
|
|
73
|
+
/**
|
|
74
|
+
* Durable terminal-session payload for a pass that ran without its Telora
|
|
75
|
+
* control surface. Same shape as the other session-update builders; kept in this
|
|
76
|
+
* neutral module so the close handler (spawner/helpers) can use it without a
|
|
77
|
+
* circular import on focus-executor.
|
|
78
|
+
*/
|
|
79
|
+
export function mcpToolsUndeliveredSessionUpdate(missingTools) {
|
|
80
|
+
const missing = missingTools.length > 0 ? missingTools.join(', ') : '(unknown)';
|
|
81
|
+
return {
|
|
82
|
+
status: 'failed',
|
|
83
|
+
exit_reason: `Local-model pass ran without its Telora control surface: required tool(s) not delivered to the model: ${missing}. ` +
|
|
84
|
+
`Failing the pass durably instead of re-spawning into the same tool-less loop.`,
|
|
85
|
+
exit_category: MCP_TOOLS_UNDELIVERED_EXIT_CATEGORY,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Pure decision: given a proxy's final reception state, should this pass be
|
|
90
|
+
* failed closed (durable) because the model never received its required Telora
|
|
91
|
+
* control tools? True only when a tools-bearing request was seen AND a required
|
|
92
|
+
* tool was missing -- a pass that simply never sent tools (no model turn) is not
|
|
93
|
+
* penalized. Drives mcpToolsUndeliveredSessionUpdate at team close.
|
|
94
|
+
*/
|
|
95
|
+
export function shouldFailClosedForUndeliveredTools(reception) {
|
|
96
|
+
const fail = reception.sawToolsRequest && !reception.requiredToolsDelivered;
|
|
97
|
+
return { fail, missing: fail ? reception.missingTools : [] };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Update a reception state given the tools array of a freshly-flattened request.
|
|
101
|
+
* Only requests that actually carry tools count toward delivery (the first model
|
|
102
|
+
* turn carries the tool list; later turns may omit it). Pure; returned as a new
|
|
103
|
+
* object.
|
|
104
|
+
*/
|
|
105
|
+
export function updateReceptionState(prev, flattenedTools, requiredTools) {
|
|
106
|
+
if (!Array.isArray(flattenedTools) || flattenedTools.length === 0)
|
|
107
|
+
return prev;
|
|
108
|
+
const delivered = decodeDeliveredTools(flattenedTools);
|
|
109
|
+
const missing = requiredTools.filter(t => !delivered.has(t));
|
|
110
|
+
return {
|
|
111
|
+
sawToolsRequest: true,
|
|
112
|
+
requiredToolsDelivered: prev.requiredToolsDelivered || missing.length === 0,
|
|
113
|
+
missingTools: missing,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Rewrite a single decoded SSE event object: if it carries a flat `mcp__`
|
|
118
|
+
* function call, normalize it toward Codex's canonical namespace/tool identity.
|
|
119
|
+
* Returns the (possibly) rewritten object. CAPTURE-GATED shape -- see module
|
|
120
|
+
* header; pin here once the live envelope is confirmed.
|
|
121
|
+
*/
|
|
122
|
+
export function rewriteResponseEvent(event) {
|
|
123
|
+
if (!event || typeof event !== 'object')
|
|
124
|
+
return event;
|
|
125
|
+
const ev = event;
|
|
126
|
+
// Codex emits function calls as output items; rewrite any item whose name is
|
|
127
|
+
// a flat mcp__ name. Handle both a bare function_call item and a response that
|
|
128
|
+
// nests output items.
|
|
129
|
+
const rewriteItem = (item) => {
|
|
130
|
+
if (item.type === 'function_call' && typeof item.name === 'string') {
|
|
131
|
+
const canonical = normalizeToolCall({
|
|
132
|
+
name: item.name,
|
|
133
|
+
arguments: item.arguments,
|
|
134
|
+
call_id: typeof item.call_id === 'string' ? item.call_id : undefined,
|
|
135
|
+
});
|
|
136
|
+
if (canonical) {
|
|
137
|
+
item.name = canonical.name;
|
|
138
|
+
item.namespace = canonical.namespace;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
if (ev.type === 'function_call')
|
|
143
|
+
rewriteItem(ev);
|
|
144
|
+
if (ev.item && typeof ev.item === 'object')
|
|
145
|
+
rewriteItem(ev.item);
|
|
146
|
+
const response = ev.response;
|
|
147
|
+
if (response && Array.isArray(response.output)) {
|
|
148
|
+
for (const item of response.output) {
|
|
149
|
+
if (item && typeof item === 'object')
|
|
150
|
+
rewriteItem(item);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return ev;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Transform an SSE response chunk stream. Stateful over `data:` lines: each
|
|
157
|
+
* complete `data: {json}` line is parsed, run through rewriteResponseEvent, and
|
|
158
|
+
* re-serialized. Non-data lines and unparseable payloads pass through verbatim.
|
|
159
|
+
* Returns a function that consumes raw text chunks and returns transformed text.
|
|
160
|
+
*/
|
|
161
|
+
export function createSseTransformer() {
|
|
162
|
+
let buffer = '';
|
|
163
|
+
return (chunk) => {
|
|
164
|
+
buffer += chunk;
|
|
165
|
+
let out = '';
|
|
166
|
+
let idx;
|
|
167
|
+
while ((idx = buffer.indexOf('\n')) !== -1) {
|
|
168
|
+
const line = buffer.slice(0, idx);
|
|
169
|
+
buffer = buffer.slice(idx + 1);
|
|
170
|
+
out += transformSseLine(line) + '\n';
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function transformSseLine(line) {
|
|
176
|
+
const prefix = 'data:';
|
|
177
|
+
if (!line.startsWith(prefix))
|
|
178
|
+
return line;
|
|
179
|
+
const payload = line.slice(prefix.length).trimStart();
|
|
180
|
+
if (!payload || payload === '[DONE]')
|
|
181
|
+
return line;
|
|
182
|
+
let parsed;
|
|
183
|
+
try {
|
|
184
|
+
parsed = JSON.parse(payload);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return line; // not JSON we understand -- leave untouched
|
|
188
|
+
}
|
|
189
|
+
const rewritten = rewriteResponseEvent(parsed);
|
|
190
|
+
return `data: ${JSON.stringify(rewritten)}`;
|
|
191
|
+
}
|
|
192
|
+
function readBody(req) {
|
|
193
|
+
return new Promise((resolve, reject) => {
|
|
194
|
+
const chunks = [];
|
|
195
|
+
req.on('data', (c) => chunks.push(c));
|
|
196
|
+
req.on('end', () => resolve(Buffer.concat(chunks)));
|
|
197
|
+
req.on('error', reject);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Start the localhost translating proxy. Resolves once it is listening.
|
|
202
|
+
*/
|
|
203
|
+
export function startCodexMcpProxy(opts) {
|
|
204
|
+
const requiredTools = opts.requiredTools ?? PROXY_REQUIRED_TOOLS;
|
|
205
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
206
|
+
const logger = opts.logger ?? { log: (m) => console.log(m), warn: (m) => console.warn(m) };
|
|
207
|
+
const upstream = new URL(opts.upstreamBaseUrl);
|
|
208
|
+
let reception = {
|
|
209
|
+
sawToolsRequest: false,
|
|
210
|
+
requiredToolsDelivered: false,
|
|
211
|
+
missingTools: [],
|
|
212
|
+
};
|
|
213
|
+
const server = createServer((req, res) => {
|
|
214
|
+
void handleRequest(req, res);
|
|
215
|
+
});
|
|
216
|
+
async function handleRequest(req, res) {
|
|
217
|
+
try {
|
|
218
|
+
const raw = await readBody(req);
|
|
219
|
+
const isResponses = (req.url ?? '').includes('/responses') && (req.method ?? 'GET').toUpperCase() === 'POST';
|
|
220
|
+
let forwardBody = raw;
|
|
221
|
+
if (isResponses && raw.length > 0) {
|
|
222
|
+
try {
|
|
223
|
+
const parsed = JSON.parse(raw.toString('utf8'));
|
|
224
|
+
const flattened = flattenNamespaceTools(parsed);
|
|
225
|
+
reception = updateReceptionState(reception, flattened.tools, requiredTools);
|
|
226
|
+
forwardBody = JSON.stringify(flattened);
|
|
227
|
+
logger.log(`[codex-mcp-proxy] forwarded responses request ${JSON.stringify(redactRequestForLog(flattened))}`);
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// Non-JSON body -- forward verbatim.
|
|
231
|
+
forwardBody = raw;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Forward to upstream origin + the original request path.
|
|
235
|
+
const target = `${upstream.origin}${req.url ?? ''}`;
|
|
236
|
+
const headers = {};
|
|
237
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
238
|
+
if (v === undefined)
|
|
239
|
+
continue;
|
|
240
|
+
if (k.toLowerCase() === 'host' || k.toLowerCase() === 'content-length')
|
|
241
|
+
continue;
|
|
242
|
+
headers[k] = Array.isArray(v) ? v.join(',') : v;
|
|
243
|
+
}
|
|
244
|
+
const upstreamResp = await doFetch(target, {
|
|
245
|
+
method: req.method,
|
|
246
|
+
headers,
|
|
247
|
+
body: req.method === 'GET' || req.method === 'HEAD' ? undefined : forwardBody,
|
|
248
|
+
});
|
|
249
|
+
const respHeaders = {};
|
|
250
|
+
upstreamResp.headers.forEach((value, key) => {
|
|
251
|
+
if (key.toLowerCase() === 'content-length')
|
|
252
|
+
return; // transformed length differs
|
|
253
|
+
respHeaders[key] = value;
|
|
254
|
+
});
|
|
255
|
+
res.writeHead(upstreamResp.status, respHeaders);
|
|
256
|
+
const contentType = upstreamResp.headers.get('content-type') ?? '';
|
|
257
|
+
if (contentType.includes('text/event-stream') && upstreamResp.body) {
|
|
258
|
+
const transform = createSseTransformer();
|
|
259
|
+
const reader = upstreamResp.body.getReader();
|
|
260
|
+
const decoder = new TextDecoder();
|
|
261
|
+
for (;;) {
|
|
262
|
+
const { done, value } = await reader.read();
|
|
263
|
+
if (done)
|
|
264
|
+
break;
|
|
265
|
+
res.write(transform(decoder.decode(value, { stream: true })));
|
|
266
|
+
}
|
|
267
|
+
res.end();
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
const buf = Buffer.from(await upstreamResp.arrayBuffer());
|
|
271
|
+
res.end(buf);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
logger.warn(`[codex-mcp-proxy] upstream error: ${err.message}`);
|
|
276
|
+
if (!res.headersSent)
|
|
277
|
+
res.writeHead(502, { 'content-type': 'text/plain' });
|
|
278
|
+
res.end('proxy upstream error');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return new Promise((resolve, reject) => {
|
|
282
|
+
server.on('error', reject);
|
|
283
|
+
server.listen(0, '127.0.0.1', () => {
|
|
284
|
+
const port = server.address().port;
|
|
285
|
+
// Advertise the proxy with the SAME path as upstream so Codex's
|
|
286
|
+
// base_url + "/responses" reconstructs the upstream path on forward.
|
|
287
|
+
const url = `http://127.0.0.1:${port}${upstream.pathname.replace(/\/$/, '')}`;
|
|
288
|
+
resolve({
|
|
289
|
+
url,
|
|
290
|
+
port,
|
|
291
|
+
getReceptionState: () => reception,
|
|
292
|
+
close: () => new Promise((res) => server.close(() => res())),
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
//# sourceMappingURL=codex-mcp-proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-mcp-proxy.js","sourceRoot":"","sources":["../../../src/backends/codex/codex-mcp-proxy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only Telora MCP preflight for local-model passes.
|
|
3
|
+
*
|
|
4
|
+
* A local-model coding team is only useful if the agent can actually reach the
|
|
5
|
+
* Telora issue/delivery control surface through its MCP server. When that
|
|
6
|
+
* provisioning is broken -- the MCP package cannot be resolved, the server fails
|
|
7
|
+
* to start, or it does not expose the required tools -- a spawned team produces
|
|
8
|
+
* no useful work and no diagnostic: the focus sits "ready" and the daemon
|
|
9
|
+
* re-spawns into the same wall every poll (UDE #3, the silent spawn-loop wedge).
|
|
10
|
+
*
|
|
11
|
+
* This preflight proves, BEFORE the coding team spawns, that the required Telora
|
|
12
|
+
* tools are present and callable, by running a read-only JSON-RPC handshake
|
|
13
|
+
* (`initialize` + `tools/list`) against the provisioned Telora MCP server over
|
|
14
|
+
* stdio. On failure it returns a non-allowed gate result carrying a precise
|
|
15
|
+
* reason; the caller turns that into a durable terminal session and skips the
|
|
16
|
+
* spawn (the same fail-closed contract as gateLocalModelBackendSpawn). It NEVER
|
|
17
|
+
* mutates Telora -- the handshake reads the tool list only.
|
|
18
|
+
*
|
|
19
|
+
* The probe is an injectable seam so the gate is unit-testable without spawning
|
|
20
|
+
* a real subprocess.
|
|
21
|
+
*/
|
|
22
|
+
import type { DaemonConfig } from '../../types.js';
|
|
23
|
+
/** Exit category stamped on the session when the MCP preflight refuses a spawn. */
|
|
24
|
+
export declare const MCP_PREFLIGHT_EXIT_CATEGORY = "mcp_preflight_failed";
|
|
25
|
+
/**
|
|
26
|
+
* The Telora control-surface tools a coding team must be able to call. A local
|
|
27
|
+
* pass that cannot see these cannot keep issue/delivery state synchronized, so
|
|
28
|
+
* their absence is terminal for the spawn.
|
|
29
|
+
*/
|
|
30
|
+
export declare const MCP_PREFLIGHT_REQUIRED_TOOLS: readonly ["telora_product_issue", "telora_product_delivery"];
|
|
31
|
+
/** How long the handshake may take before the probe gives up (ms). */
|
|
32
|
+
export declare const MCP_PREFLIGHT_TIMEOUT_MS = 20000;
|
|
33
|
+
export interface McpPreflightResult {
|
|
34
|
+
allowed: boolean;
|
|
35
|
+
reason?: string;
|
|
36
|
+
/** Exit category for the durable terminal session when !allowed. */
|
|
37
|
+
category?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Probe seam: resolve the tool names the provisioned Telora MCP server exposes.
|
|
41
|
+
* Production implementation spawns the server and runs the stdio handshake;
|
|
42
|
+
* tests inject a fake.
|
|
43
|
+
*/
|
|
44
|
+
export type McpToolProbe = (config: DaemonConfig) => Promise<string[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Real probe: spawn `node <telora-mcp-server>` with the execution profile env,
|
|
47
|
+
* perform the MCP `initialize` + `tools/list` handshake over stdio, and return
|
|
48
|
+
* the exposed tool names. Throws on resolution failure, spawn error, timeout,
|
|
49
|
+
* or a malformed/absent tools list. Read-only: it lists tools and never calls
|
|
50
|
+
* one.
|
|
51
|
+
*
|
|
52
|
+
* Identity (TELORA_URL / TELORA_TRACKER_ID) is intentionally NOT injected: the
|
|
53
|
+
* MCP server resolves it from the single source `~/.telora/daemon.json` via
|
|
54
|
+
* loadCredentials() (inherited process.env still propagates when present). The
|
|
55
|
+
* `config` parameter is part of the McpToolProbe seam (used by injected test
|
|
56
|
+
* fakes) but unused by this server-handshake implementation.
|
|
57
|
+
*/
|
|
58
|
+
export declare function probeMcpToolsViaHandshake(_config: DaemonConfig): Promise<string[]>;
|
|
59
|
+
/**
|
|
60
|
+
* Gate a local-model spawn on a read-only proof that the required Telora MCP
|
|
61
|
+
* tools are callable. Returns {allowed:true} for every non-local pass (hosted /
|
|
62
|
+
* claude) without probing -- their MCP wiring is unchanged and out of scope.
|
|
63
|
+
*
|
|
64
|
+
* For a local pass: run the probe; if every required tool is present the gate
|
|
65
|
+
* allows the spawn, otherwise it refuses with a precise, actionable reason
|
|
66
|
+
* (resolution/spawn/timeout failure, or the specific missing tool names). The
|
|
67
|
+
* caller fails the spawn closed (durable terminal session, no team launched).
|
|
68
|
+
*/
|
|
69
|
+
export declare function preflightLocalModelMcp(config: DaemonConfig, isLocalModelPass: boolean, opts?: {
|
|
70
|
+
probe?: McpToolProbe;
|
|
71
|
+
requiredTools?: readonly string[];
|
|
72
|
+
}): Promise<McpPreflightResult>;
|
|
73
|
+
//# sourceMappingURL=mcp-preflight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-preflight.d.ts","sourceRoot":"","sources":["../../../src/backends/codex/mcp-preflight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,mFAAmF;AACnF,eAAO,MAAM,2BAA2B,yBAAyB,CAAC;AAElE;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,8DAG/B,CAAC;AAEX,sEAAsE;AACtE,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAE/C,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AAUvE;;;;;;;;;;;;GAYG;AACH,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAgGxF;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,YAAY,EACpB,gBAAgB,EAAE,OAAO,EACzB,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,YAAY,CAAC;IAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAO,GACrE,OAAO,CAAC,kBAAkB,CAAC,CAgC7B"}
|