@smartmemory/compose 0.1.0 → 0.1.2-beta

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 (124) hide show
  1. package/.claude/skills/bug-fix/SKILL.md +143 -0
  2. package/.claude/skills/compose/SKILL.md +604 -0
  3. package/.compose-deps.json +89 -0
  4. package/README.md +14 -3
  5. package/bin/compose.js +473 -0
  6. package/contracts/comp-obs-contract.schema.json +362 -0
  7. package/contracts/cross-model-review-result.json +78 -0
  8. package/contracts/review-result.json +126 -0
  9. package/dist/assets/{_baseUniq-CQwX6VLz.js → _baseUniq-D-avYfn5.js} +1 -1
  10. package/dist/assets/{arc-SxJ2J1sh.js → arc-BC4dfQ-X.js} +1 -1
  11. package/dist/assets/{architectureDiagram-Q4EWVU46-BykunY1F.js → architectureDiagram-Q4EWVU46-BZmFXnGI.js} +1 -1
  12. package/dist/assets/{blockDiagram-DXYQGD6D-ohAKBOUw.js → blockDiagram-DXYQGD6D-DlfWSuux.js} +1 -1
  13. package/dist/assets/{c4Diagram-AHTNJAMY-DBDC3ENB.js → c4Diagram-AHTNJAMY-Y__uJrRx.js} +1 -1
  14. package/dist/assets/channel-LRG9kHqJ.js +1 -0
  15. package/dist/assets/{chunk-4BX2VUAB-Cv93Z7uM.js → chunk-4BX2VUAB-BfMePfTp.js} +1 -1
  16. package/dist/assets/{chunk-4TB4RGXK-DE0WBDkj.js → chunk-4TB4RGXK-BdlMSdEA.js} +1 -1
  17. package/dist/assets/{chunk-55IACEB6-CE1EXenG.js → chunk-55IACEB6-vrQHZTdv.js} +1 -1
  18. package/dist/assets/{chunk-EDXVE4YY-DA7Ana6H.js → chunk-EDXVE4YY-B8wioVlW.js} +1 -1
  19. package/dist/assets/{chunk-FMBD7UC4-CTDIPA3p.js → chunk-FMBD7UC4-Cd6Hrux2.js} +1 -1
  20. package/dist/assets/{chunk-OYMX7WX6-uGBaPaTX.js → chunk-OYMX7WX6-CfrhdQXY.js} +1 -1
  21. package/dist/assets/{chunk-QZHKN3VN-CYlnXuUO.js → chunk-QZHKN3VN-B9JQerOU.js} +1 -1
  22. package/dist/assets/{chunk-YZCP3GAM-ojGkzcZK.js → chunk-YZCP3GAM-DFN9X99H.js} +1 -1
  23. package/dist/assets/classDiagram-6PBFFD2Q-BC9a6pDE.js +1 -0
  24. package/dist/assets/classDiagram-v2-HSJHXN6E-BC9a6pDE.js +1 -0
  25. package/dist/assets/clone-dRxgFrBv.js +1 -0
  26. package/dist/assets/{cose-bilkent-S5V4N54A-Bktn9hL-.js → cose-bilkent-S5V4N54A-BAn0ap_E.js} +1 -1
  27. package/dist/assets/{dagre-KV5264BT-DFaSzuRF.js → dagre-KV5264BT-DyxnVq1g.js} +1 -1
  28. package/dist/assets/{diagram-5BDNPKRD-DnfmDzEm.js → diagram-5BDNPKRD-XCrzqski.js} +1 -1
  29. package/dist/assets/{diagram-G4DWMVQ6-Bm8W9YnG.js → diagram-G4DWMVQ6-MBCAXft_.js} +1 -1
  30. package/dist/assets/{diagram-MMDJMWI5-B5-TSKvp.js → diagram-MMDJMWI5-DbtB2yS6.js} +1 -1
  31. package/dist/assets/{diagram-TYMM5635-ls4rqlky.js → diagram-TYMM5635-Bb5NzX61.js} +1 -1
  32. package/dist/assets/{erDiagram-SMLLAGMA-giG6WO-r.js → erDiagram-SMLLAGMA-CpIeCOh2.js} +1 -1
  33. package/dist/assets/{flowDiagram-DWJPFMVM-XvlUuz-7.js → flowDiagram-DWJPFMVM-CHyoKnhW.js} +1 -1
  34. package/dist/assets/{ganttDiagram-T4ZO3ILL-hLBV57oV.js → ganttDiagram-T4ZO3ILL-DErKteO_.js} +1 -1
  35. package/dist/assets/{gitGraphDiagram-UUTBAWPF-BHu3s_Gn.js → gitGraphDiagram-UUTBAWPF-KFVAtj2F.js} +1 -1
  36. package/dist/assets/{graph-D0Cfv00Y.js → graph-CRnO_ifT.js} +1 -1
  37. package/dist/assets/index-DKBsEUJ-.css +1 -0
  38. package/dist/assets/index-DkRKLuNr.js +1144 -0
  39. package/dist/assets/{infoDiagram-42DDH7IO-DbqRsOo3.js → infoDiagram-42DDH7IO-BZFnuSp5.js} +1 -1
  40. package/dist/assets/{ishikawaDiagram-UXIWVN3A-DnCdx7zb.js → ishikawaDiagram-UXIWVN3A-4Xe2Szde.js} +1 -1
  41. package/dist/assets/{journeyDiagram-VCZTEJTY-CfD7eNcP.js → journeyDiagram-VCZTEJTY-CZRByfS-.js} +1 -1
  42. package/dist/assets/{kanban-definition-6JOO6SKY-BYaO9-mK.js → kanban-definition-6JOO6SKY-B95sk6Fk.js} +1 -1
  43. package/dist/assets/{layout-Bj72wOEB.js → layout-BqNQzxWT.js} +1 -1
  44. package/dist/assets/{linear-BRFo114D.js → linear-CUh7qb64.js} +1 -1
  45. package/dist/assets/{min-GCHnKlJS.js → min-wXgOS3ig.js} +1 -1
  46. package/dist/assets/{mindmap-definition-QFDTVHPH-n0PMebY4.js → mindmap-definition-QFDTVHPH-DB6iaAbO.js} +1 -1
  47. package/dist/assets/{pieDiagram-DEJITSTG-pN4CljHF.js → pieDiagram-DEJITSTG-CHkZHrTW.js} +1 -1
  48. package/dist/assets/{quadrantDiagram-34T5L4WZ-DNoAy8-D.js → quadrantDiagram-34T5L4WZ-DoTEO8e3.js} +1 -1
  49. package/dist/assets/{requirementDiagram-MS252O5E-BhtY05PT.js → requirementDiagram-MS252O5E-Dn8peXYp.js} +1 -1
  50. package/dist/assets/{sankeyDiagram-XADWPNL6-B6AD-16A.js → sankeyDiagram-XADWPNL6-DRXs6Ipb.js} +1 -1
  51. package/dist/assets/{sequenceDiagram-FGHM5R23-DShHM-uk.js → sequenceDiagram-FGHM5R23-wBBYZ0aq.js} +1 -1
  52. package/dist/assets/{stateDiagram-FHFEXIEX-DMxn7HTo.js → stateDiagram-FHFEXIEX-DPlBNGmf.js} +1 -1
  53. package/dist/assets/stateDiagram-v2-QKLJ7IA2-BW0ezXb4.js +1 -0
  54. package/dist/assets/{timeline-definition-GMOUNBTQ-Cdu6uq52.js → timeline-definition-GMOUNBTQ-CbbyTlHk.js} +1 -1
  55. package/dist/assets/{vennDiagram-DHZGUBPP-CpK29iRe.js → vennDiagram-DHZGUBPP-Bj4GaFfj.js} +1 -1
  56. package/dist/assets/{wardley-RL74JXVD-BQgSkdcO.js → wardley-RL74JXVD-RtNzq8KU.js} +55 -55
  57. package/dist/assets/{wardleyDiagram-NUSXRM2D-DJHYev6O.js → wardleyDiagram-NUSXRM2D-CDfE3zSj.js} +1 -1
  58. package/dist/assets/{xychartDiagram-5P7HB3ND-1d75pbaO.js → xychartDiagram-5P7HB3ND-CZXHHYD5.js} +1 -1
  59. package/dist/index.html +2 -2
  60. package/lib/budget-ledger.js +45 -0
  61. package/lib/bug-bisect.js +292 -0
  62. package/lib/bug-checkpoint.js +191 -0
  63. package/lib/bug-escalation.js +306 -0
  64. package/lib/bug-index-gen.js +136 -0
  65. package/lib/bug-ledger.js +126 -0
  66. package/lib/build-stream-schema.js +176 -0
  67. package/lib/build-stream-writer.js +3 -1
  68. package/lib/build.js +854 -284
  69. package/lib/connector-factory-shim.js +167 -0
  70. package/lib/constants.js +18 -0
  71. package/lib/debug-discipline.js +176 -27
  72. package/lib/deps.js +205 -0
  73. package/lib/health-score.js +4 -4
  74. package/lib/import.js +26 -13
  75. package/lib/inject-schema.js +21 -0
  76. package/lib/new.js +27 -53
  77. package/lib/result-normalizer.js +160 -144
  78. package/lib/review-lenses.js +5 -5
  79. package/lib/review-normalize.js +413 -0
  80. package/lib/review-prompt.js +163 -0
  81. package/lib/sections.js +325 -0
  82. package/lib/step-prompt.js +21 -1
  83. package/lib/step-validator.js +5 -3
  84. package/lib/stratum-mcp-client.js +172 -7
  85. package/package.json +14 -3
  86. package/pipelines/bug-fix.stratum.yaml +39 -1
  87. package/pipelines/build.stratum.yaml +28 -45
  88. package/pipelines/review-fix.stratum.yaml +1 -1
  89. package/presets/team-review.stratum.yaml +21 -14
  90. package/server/build-stream-bridge.js +28 -0
  91. package/server/cc-session-feature-resolver.js +111 -0
  92. package/server/cc-session-reader.js +327 -0
  93. package/server/cc-session-watcher.js +318 -0
  94. package/server/compose-mcp-tools.js +0 -125
  95. package/server/compose-mcp.js +2 -4
  96. package/server/contract-diff.js +192 -0
  97. package/server/decision-event-emit.js +175 -0
  98. package/server/decision-event-id.js +64 -0
  99. package/server/decision-events-snapshot.js +166 -0
  100. package/server/design-routes.js +92 -49
  101. package/server/drift-axes.js +365 -0
  102. package/server/drift-emit.js +121 -0
  103. package/server/gate-log-store.js +102 -0
  104. package/server/lifecycle-phase-history.js +44 -0
  105. package/server/open-loops-store.js +102 -0
  106. package/server/schema-validator.js +49 -0
  107. package/server/status-emit.js +27 -0
  108. package/server/status-snapshot.js +218 -0
  109. package/server/vision-routes.js +332 -4
  110. package/server/vision-server.js +104 -12
  111. package/server/vision-store.js +21 -0
  112. package/dist/assets/channel-DGElom1e.js +0 -1
  113. package/dist/assets/classDiagram-6PBFFD2Q-KqWP9wWZ.js +0 -1
  114. package/dist/assets/classDiagram-v2-HSJHXN6E-KqWP9wWZ.js +0 -1
  115. package/dist/assets/clone-DUJKJXd7.js +0 -1
  116. package/dist/assets/index-CUd6pFGF.css +0 -1
  117. package/dist/assets/index-DReRlzZI.js +0 -1144
  118. package/dist/assets/stateDiagram-v2-QKLJ7IA2-o6PnCs4e.js +0 -1
  119. package/server/connectors/agent-connector.js +0 -78
  120. package/server/connectors/claude-sdk-connector.js +0 -198
  121. package/server/connectors/codex-connector.js +0 -240
  122. package/server/connectors/connector-discovery.js +0 -18
  123. package/server/connectors/connector-runtime.js +0 -13
  124. package/server/connectors/opencode-connector.js +0 -200
@@ -1 +0,0 @@
1
- import{s as t,b as r,a,S as s}from"./chunk-OYMX7WX6-uGBaPaTX.js";import{_ as i}from"./index-DReRlzZI.js";import"./chunk-55IACEB6-CE1EXenG.js";import"./chunk-EDXVE4YY-DA7Ana6H.js";var l={parser:a,get db(){return new s(2)},renderer:r,styles:t,init:i(e=>{e.state||(e.state={}),e.state.arrowMarkerAbsolute=e.arrowMarkerAbsolute},"init")};export{l as diagram};
@@ -1,78 +0,0 @@
1
- /**
2
- * AgentConnector — base class for all agent connectors.
3
- *
4
- * @implements {ConnectorDiscovery} — see connector-discovery.js
5
- * @implements {ConnectorRuntime} — see connector-runtime.js
6
- *
7
- * Subclasses implement run(), interrupt(), and isRunning.
8
- * Duck typing — no enforcement at runtime beyond the throw in run().
9
- *
10
- * Message envelope (yielded by run()):
11
- * { type: 'system', subtype: 'init' | 'complete', agent: string, model?: string }
12
- * { type: 'assistant', content: string }
13
- * { type: 'tool_use', tool: string, input: object }
14
- * { type: 'tool_use_summary', summary: string, output?: string }
15
- * { type: 'error', message: string }
16
- *
17
- * Schema mode: if opts.schema is provided, the connector injects it into the
18
- * prompt as instructions and yields text output. JSON.parse() happens at the
19
- * MCP layer (agent-mcp.js), never inside connectors.
20
- */
21
-
22
- export class AgentConnector {
23
- // ── Discovery ──────────────────────────────────────────────────────────────
24
-
25
- /** @returns {string[]} */
26
- listModels() { return []; }
27
-
28
- /** @param {string} _modelId @returns {boolean} */
29
- supportsModel(_modelId) { return false; }
30
-
31
- /** @param {string} _sessionId @returns {Promise<object[]>} */
32
- async loadHistory(_sessionId) { return []; }
33
-
34
- // ── Runtime ────────────────────────────────────────────────────────────────
35
-
36
- /**
37
- * Run a prompt against the agent.
38
- *
39
- * @param {string} prompt
40
- * @param {object} [opts]
41
- * @param {object} [opts.schema] — JSON Schema → structured output mode
42
- * @param {string} [opts.modelID] — override model for this run
43
- * @param {string} [opts.providerID] — provider ID (OpenCode subclasses only)
44
- * @param {string} [opts.cwd] — working directory
45
- * @param {string[]} [opts.tools] — restrict available tools
46
- * @returns {AsyncGenerator}
47
- */
48
- // eslint-disable-next-line require-yield
49
- async *run(_prompt, _opts = {}) {
50
- throw new Error(`${this.constructor.name}.run() not implemented`);
51
- }
52
-
53
- /** Stop the active run cleanly. No-op if not running. */
54
- interrupt() {}
55
-
56
- /** Whether a run is currently active. */
57
- get isRunning() { return false; }
58
- }
59
-
60
- /**
61
- * Inject a JSON schema into a prompt so the agent knows to return structured JSON.
62
- * Used by both ClaudeSDKConnector and OpencodeConnector — parsing happens at call site.
63
- *
64
- * @param {string} prompt
65
- * @param {object} schema JSON Schema object
66
- * @returns {string}
67
- */
68
- export function injectSchema(prompt, schema) {
69
- return (
70
- `${prompt}\n\n` +
71
- `IMPORTANT: After completing the task, include a JSON code block at the very end ` +
72
- `of your response matching this schema:\n` +
73
- '```json\n' +
74
- `${JSON.stringify(schema, null, 2)}\n` +
75
- '```\n' +
76
- `The JSON block must be the last thing in your response.`
77
- );
78
- }
@@ -1,198 +0,0 @@
1
- /**
2
- * ClaudeSDKConnector — wraps @anthropic-ai/claude-agent-sdk query().
3
- *
4
- * All Anthropic model execution goes through this connector.
5
- * Yields the same typed message envelope as the other connectors.
6
- */
7
-
8
- import { query } from '@anthropic-ai/claude-agent-sdk';
9
- import { AgentConnector, injectSchema } from './agent-connector.js';
10
-
11
- const DEFAULT_MODEL = process.env.CLAUDE_MODEL || 'claude-sonnet-4-6';
12
-
13
- export class ClaudeSDKConnector extends AgentConnector {
14
- // ── Discovery ──────────────────────────────────────────────────────────────
15
- // No overrides — inherits stubs from AgentConnector. See agent-connector.js.
16
-
17
- #model;
18
- #cwd;
19
- #query = null;
20
-
21
- #allowedTools;
22
- #disallowedTools;
23
- #thinking;
24
- #effort;
25
-
26
- /**
27
- * @param {object} [opts]
28
- * @param {string} [opts.model] — default model (env CLAUDE_MODEL or claude-sonnet-4-6)
29
- * @param {string} [opts.cwd] — default working directory
30
- * @param {string[]} [opts.allowedTools] — restrict to these tools (overrides preset)
31
- * @param {string[]} [opts.disallowedTools] — deny these tools (used alongside allowedTools or preset)
32
- * @param {object|null} [opts.thinking] — Claude thinking config, e.g. { type: 'adaptive' } or { type: 'disabled' }
33
- * @param {string|null} [opts.effort] — effort level: 'low' | 'medium' | 'high' | 'xhigh' | 'max'
34
- */
35
- constructor({ model = DEFAULT_MODEL, cwd = process.cwd(), allowedTools, disallowedTools, thinking, effort } = {}) {
36
- super();
37
- this.#model = model;
38
- this.#cwd = cwd;
39
- this.#allowedTools = allowedTools ?? null;
40
- this.#disallowedTools = disallowedTools ?? null;
41
- this.#thinking = thinking ?? null;
42
- this.#effort = effort ?? null;
43
- }
44
-
45
- // ── Runtime ────────────────────────────────────────────────────────────────
46
-
47
- async *run(prompt, { schema, modelID, cwd, thinking, effort } = {}) {
48
- if (this.#query) {
49
- throw new Error('ClaudeSDKConnector: run() already active. Call interrupt() first.');
50
- }
51
-
52
- const actualPrompt = schema ? injectSchema(prompt, schema) : prompt;
53
-
54
- // Strip CLAUDECODE env var to allow spawning inside a Claude Code session
55
- const cleanEnv = { ...process.env };
56
- delete cleanEnv.CLAUDECODE;
57
-
58
- // Build tools config: prefer explicit allow/deny lists if provided,
59
- // otherwise fall back to the default claude_code preset (backward compat).
60
- let toolsConfig;
61
- if (this.#allowedTools !== null) {
62
- toolsConfig = { type: 'allowed', allowedTools: this.#allowedTools };
63
- if (this.#disallowedTools !== null) {
64
- toolsConfig.disallowedTools = this.#disallowedTools;
65
- }
66
- } else if (this.#disallowedTools !== null) {
67
- toolsConfig = { type: 'preset', preset: 'claude_code', disallowedTools: this.#disallowedTools };
68
- } else {
69
- toolsConfig = { type: 'preset', preset: 'claude_code' };
70
- }
71
-
72
- // Resolve thinking/effort: per-run override beats constructor default.
73
- const resolvedThinking = thinking !== undefined ? thinking : this.#thinking;
74
- const resolvedEffort = effort !== undefined ? effort : this.#effort;
75
-
76
- const sdkOptions = {
77
- cwd: cwd ?? this.#cwd,
78
- model: modelID ?? this.#model,
79
- permissionMode: 'acceptEdits',
80
- tools: toolsConfig,
81
- env: cleanEnv,
82
- };
83
- if (resolvedThinking !== null && resolvedThinking !== undefined) {
84
- sdkOptions.thinking = resolvedThinking;
85
- }
86
- if (resolvedEffort !== null && resolvedEffort !== undefined) {
87
- sdkOptions.effort = resolvedEffort;
88
- }
89
-
90
- const q = query({
91
- prompt: actualPrompt,
92
- options: sdkOptions,
93
- });
94
- this.#query = q;
95
-
96
- yield { type: 'system', subtype: 'init', agent: 'claude', model: modelID ?? this.#model };
97
-
98
- const activeModel = modelID ?? this.#model;
99
- try {
100
- for await (const msg of q) {
101
- if (process.env.COMPOSE_DEBUG) {
102
- process.stderr.write(` [sdk] ${msg?.type ?? typeof msg}\n`);
103
- }
104
- for (const event of _normalizeAll(msg)) {
105
- // Inject model into usage events extracted from result messages
106
- if (event.type === 'usage' && event._from_result) {
107
- const { _from_result: _, ...usageEvent } = event;
108
- yield { ...usageEvent, model: activeModel };
109
- } else {
110
- yield event;
111
- }
112
- }
113
- }
114
- yield { type: 'system', subtype: 'complete', agent: 'claude' };
115
- } catch (err) {
116
- if (err?.name !== 'AbortError') {
117
- yield { type: 'error', message: err.message || String(err) };
118
- }
119
- } finally {
120
- this.#query = null;
121
- }
122
- }
123
-
124
- interrupt() {
125
- if (this.#query) {
126
- try { this.#query.interrupt(); } catch { /* already done */ }
127
- this.#query = null;
128
- }
129
- }
130
-
131
- get isRunning() {
132
- return this.#query !== null;
133
- }
134
- }
135
-
136
- // ---------------------------------------------------------------------------
137
- // Normalize Claude SDK message → shared envelope
138
- // ---------------------------------------------------------------------------
139
-
140
- /**
141
- * Normalize an SDK message into one or more envelope events.
142
- * Returns an array because a single assistant message can contain
143
- * multiple content blocks (text + tool_use).
144
- */
145
- function _normalizeAll(msg) {
146
- if (!msg || typeof msg !== 'object') {
147
- return [{ type: 'assistant', content: String(msg) }];
148
- }
149
- if (msg.type === 'system' || msg.type === 'error') return [msg];
150
-
151
- // SDK assistant message — extract content blocks from msg.message
152
- if (msg.type === 'assistant' && msg.message?.content) {
153
- const events = [];
154
- for (const block of msg.message.content) {
155
- if (block.type === 'text' && block.text) {
156
- events.push({ type: 'assistant', content: block.text });
157
- } else if (block.type === 'tool_use') {
158
- events.push({ type: 'tool_use', tool: block.name, input: block.input ?? {} });
159
- }
160
- }
161
- return events.length > 0 ? events : [msg];
162
- }
163
-
164
- // SDK result message — contains the final aggregated text and usage metadata
165
- if (msg.type === 'result' && msg.result) {
166
- const events = [{ type: 'result', content: msg.result }];
167
- // Extract usage from msg.usage or msg.message.usage
168
- const usage = msg.usage ?? msg.message?.usage;
169
- if (usage) {
170
- events.push({
171
- type: 'usage',
172
- input_tokens: usage.input_tokens ?? 0,
173
- output_tokens: usage.output_tokens ?? 0,
174
- cache_creation_input_tokens: usage.cache_creation_input_tokens ?? 0,
175
- cache_read_input_tokens: usage.cache_read_input_tokens ?? 0,
176
- // model is injected by the run() loop via _modelForUsage tag on the event
177
- _from_result: true,
178
- });
179
- }
180
- return events;
181
- }
182
-
183
- if (msg.type === 'tool_use') {
184
- return [{ type: 'tool_use', tool: msg.name ?? msg.tool, input: msg.input ?? {} }];
185
- }
186
- if (msg.type === 'tool_use_summary') {
187
- const output = (msg.result ?? msg.output ?? '');
188
- return [{ type: 'tool_use_summary', summary: msg.summary, output: output ? output.slice(0, 2048) : undefined }];
189
- }
190
- if (msg.type === 'tool_progress') {
191
- return [{ type: 'tool_progress', tool: msg.tool_name, elapsed: msg.elapsed_time_seconds }];
192
- }
193
- // Delta or text content
194
- if (msg.delta?.text) return [{ type: 'assistant', content: msg.delta.text }];
195
- if (msg.content && typeof msg.content === 'string') return [{ type: 'assistant', content: msg.content }];
196
- // Pass through unknown message types as-is
197
- return [msg];
198
- }
@@ -1,240 +0,0 @@
1
- /**
2
- * CodexConnector — spawns the official `codex exec --json` CLI for each prompt.
3
- *
4
- * Replaces the previous opencode-backed implementation. Uses the OpenAI Codex
5
- * CLI (`codex`, installed via `npm i -g @openai/codex` or `brew install codex`)
6
- * which streams structured JSONL events to stdout.
7
- *
8
- * Auth: run `codex login` once (ChatGPT OAuth), or set OPENAI_API_KEY.
9
- *
10
- * Model IDs use the form `<model>` or `<model>/<effort>` where effort is one
11
- * of `minimal|low|medium|high|xhigh`. The effort suffix is split off and
12
- * passed as `-c model_reasoning_effort=<effort>`.
13
- */
14
-
15
- import { spawn } from 'node:child_process';
16
- import { createInterface } from 'node:readline';
17
- import { AgentConnector, injectSchema } from './agent-connector.js';
18
-
19
- // ---------------------------------------------------------------------------
20
- // Supported Codex model IDs (model + optional /effort suffix)
21
- // ---------------------------------------------------------------------------
22
-
23
- export const CODEX_MODEL_IDS = new Set([
24
- 'gpt-5.4',
25
- 'gpt-5.4/low',
26
- 'gpt-5.4/medium',
27
- 'gpt-5.4/high',
28
- 'gpt-5.4/xhigh',
29
- 'gpt-5.2-codex',
30
- 'gpt-5.2-codex/low',
31
- 'gpt-5.2-codex/medium',
32
- 'gpt-5.2-codex/high',
33
- 'gpt-5.2-codex/xhigh',
34
- 'gpt-5.1-codex-max',
35
- 'gpt-5.1-codex-max/low',
36
- 'gpt-5.1-codex-max/medium',
37
- 'gpt-5.1-codex-max/high',
38
- 'gpt-5.1-codex-max/xhigh',
39
- 'gpt-5.1-codex',
40
- 'gpt-5.1-codex/low',
41
- 'gpt-5.1-codex/medium',
42
- 'gpt-5.1-codex/high',
43
- 'gpt-5.1-codex-mini',
44
- 'gpt-5.1-codex-mini/medium',
45
- 'gpt-5.1-codex-mini/high',
46
- ]);
47
-
48
- const DEFAULT_MODEL_ID = process.env.CODEX_MODEL || 'gpt-5.4';
49
- const AGENT_NAME = 'codex';
50
-
51
- // ---------------------------------------------------------------------------
52
- // CodexConnector
53
- // ---------------------------------------------------------------------------
54
-
55
- export class CodexConnector extends AgentConnector {
56
- _defaultModelID;
57
- _cwd;
58
- #proc = null;
59
-
60
- /**
61
- * @param {object} [opts]
62
- * @param {string} [opts.modelID] — Codex model ID; must be in CODEX_MODEL_IDS
63
- * @param {string} [opts.cwd] — default working directory
64
- * @throws {Error} if modelID is not a recognized Codex model
65
- */
66
- constructor({ modelID = DEFAULT_MODEL_ID, cwd = process.cwd() } = {}) {
67
- super();
68
- _assertCodexModel(modelID);
69
- this._defaultModelID = modelID;
70
- this._cwd = cwd;
71
- }
72
-
73
- // ── Runtime ────────────────────────────────────────────────────────────────
74
-
75
- async *run(prompt, { schema, modelID, cwd } = {}) {
76
- if (this.#proc) {
77
- throw new Error(`${AGENT_NAME}: run() already active. Call interrupt() first.`);
78
- }
79
-
80
- const resolvedModelID = modelID ?? this._defaultModelID;
81
- _assertCodexModel(resolvedModelID);
82
- const resolvedCwd = cwd ?? this._cwd;
83
- const actualPrompt = schema ? injectSchema(prompt, schema) : prompt;
84
-
85
- const [baseModel, effort] = resolvedModelID.split('/');
86
-
87
- yield {
88
- type: 'system', subtype: 'init',
89
- agent: AGENT_NAME, model: resolvedModelID,
90
- };
91
-
92
- const args = [
93
- 'exec',
94
- '--json',
95
- '--skip-git-repo-check',
96
- '--sandbox', 'read-only',
97
- '-m', baseModel,
98
- '-C', resolvedCwd,
99
- ];
100
- if (effort) {
101
- args.push('-c', `model_reasoning_effort="${effort}"`);
102
- }
103
- args.push('-'); // read prompt from stdin
104
-
105
- const proc = spawn('codex', args, {
106
- cwd: resolvedCwd,
107
- stdio: ['pipe', 'pipe', 'pipe'],
108
- env: process.env,
109
- });
110
- this.#proc = proc;
111
-
112
- // Write prompt via stdin to avoid argv length and quoting issues
113
- proc.stdin.end(actualPrompt);
114
-
115
- const rl = createInterface({ input: proc.stdout, crlfDelay: Infinity });
116
- const textParts = [];
117
- const stderrChunks = [];
118
-
119
- // Stream stderr — surface auth/rate-limit errors immediately
120
- proc.stderr.on('data', chunk => {
121
- stderrChunks.push(chunk);
122
- const text = chunk.toString();
123
- const lower = text.toLowerCase();
124
- if (lower.includes('rate limit') || lower.includes('rate_limit') ||
125
- lower.includes('quota') || lower.includes('insufficient_quota') ||
126
- lower.includes('unauthorized') || lower.includes('401') ||
127
- lower.includes('403') || lower.includes('authentication') ||
128
- lower.includes('not logged in') || lower.includes('login required') ||
129
- lower.includes('billing') || lower.includes('exceeded')) {
130
- process.stderr.write(`\n⚠ ${AGENT_NAME}: ${text.trim()}\n`);
131
- process.stderr.write(` → Check login: codex login status\n`);
132
- process.stderr.write(` → Re-auth: codex login\n\n`);
133
- }
134
- });
135
-
136
- // Stall detection — warn if no stdout events for 120s
137
- let lastEventAt = Date.now();
138
- const stallTimer = setInterval(() => {
139
- const silent = Math.round((Date.now() - lastEventAt) / 1000);
140
- if (silent >= 120) {
141
- process.stderr.write(`\n⚠ ${AGENT_NAME}: no response for ${silent}s — may be stalled or rate-limited\n`);
142
- process.stderr.write(` → Press s to skip, or Ctrl+C to abort\n\n`);
143
- }
144
- }, 30_000);
145
-
146
- try {
147
- for await (const line of rl) {
148
- if (!line.trim()) continue;
149
- lastEventAt = Date.now();
150
-
151
- let event;
152
- try { event = JSON.parse(line); } catch { continue; }
153
-
154
- // codex exec --json event shapes:
155
- // { type: 'thread.started', thread_id }
156
- // { type: 'turn.started' }
157
- // { type: 'item.started' | 'item.updated' | 'item.completed', item: {...} }
158
- // { type: 'turn.completed', usage: { input_tokens, cached_input_tokens, output_tokens } }
159
- // { type: 'error', message }
160
- const t = event.type;
161
-
162
- if (t === 'item.completed' && event.item) {
163
- const item = event.item;
164
- if (item.type === 'agent_message' && item.text) {
165
- textParts.push(item.text);
166
- yield { type: 'assistant', content: item.text };
167
- } else if (item.type === 'command_execution') {
168
- const cmd = item.command ?? item.input?.command ?? '';
169
- yield { type: 'tool_use', tool: 'bash', input: { command: cmd } };
170
- const out = item.aggregated_output ?? item.output ?? '';
171
- if (out) {
172
- const short = out.length > 80 ? out.slice(0, 77) + '...' : out;
173
- yield { type: 'tool_use_summary', summary: short, output: String(out).slice(0, 2048) };
174
- }
175
- } else if (item.type === 'file_change') {
176
- yield { type: 'tool_use', tool: 'edit', input: { path: item.path ?? '' } };
177
- } else if (item.type === 'reasoning' && item.text) {
178
- // Surface reasoning as assistant content for visibility
179
- yield { type: 'assistant', content: item.text };
180
- }
181
- } else if (t === 'turn.completed' && event.usage) {
182
- const u = event.usage;
183
- yield {
184
- type: 'usage',
185
- input_tokens: u.input_tokens ?? 0,
186
- output_tokens: u.output_tokens ?? 0,
187
- cache_creation_input_tokens: 0,
188
- cache_read_input_tokens: u.cached_input_tokens ?? 0,
189
- cost_usd: 0,
190
- model: resolvedModelID,
191
- };
192
- } else if (t === 'error') {
193
- yield { type: 'error', message: event.message || 'codex error' };
194
- }
195
- }
196
-
197
- const exitCode = await new Promise(resolve => proc.on('close', resolve));
198
-
199
- if (exitCode !== 0 && textParts.length === 0) {
200
- const stderr = Buffer.concat(stderrChunks).toString();
201
- yield { type: 'error', message: stderr || `codex exited with code ${exitCode}` };
202
- } else {
203
- const fullText = textParts.join('');
204
- if (fullText) yield { type: 'result', content: fullText };
205
- yield { type: 'system', subtype: 'complete', agent: AGENT_NAME };
206
- }
207
- } catch (err) {
208
- if (err?.name !== 'AbortError') {
209
- yield { type: 'error', message: err.message || String(err) };
210
- }
211
- } finally {
212
- clearInterval(stallTimer);
213
- this.#proc = null;
214
- }
215
- }
216
-
217
- interrupt() {
218
- if (this.#proc) {
219
- try { this.#proc.kill('SIGTERM'); } catch { /* ignore */ }
220
- this.#proc = null;
221
- }
222
- }
223
-
224
- get isRunning() {
225
- return this.#proc !== null;
226
- }
227
- }
228
-
229
- // ---------------------------------------------------------------------------
230
- // Guard
231
- // ---------------------------------------------------------------------------
232
-
233
- function _assertCodexModel(modelID) {
234
- if (!CODEX_MODEL_IDS.has(modelID)) {
235
- throw new Error(
236
- `CodexConnector: '${modelID}' is not a supported Codex model.\n` +
237
- `Supported models: ${[...CODEX_MODEL_IDS].join(', ')}`
238
- );
239
- }
240
- }
@@ -1,18 +0,0 @@
1
- // compose/server/connectors/connector-discovery.js
2
- /**
3
- * @interface ConnectorDiscovery
4
- *
5
- * Stateless vendor capability contract.
6
- * Implementations must not hold execution state.
7
- *
8
- * All three concrete connectors (ClaudeSDKConnector, CodexConnector, OpencodeConnector)
9
- * satisfy this interface. Shape verified by test/connector-shape.test.js.
10
- */
11
- export const ConnectorDiscoveryInterface = {
12
- /** @returns {string[]} model IDs available for this vendor */
13
- listModels() {},
14
- /** @param {string} modelId @returns {boolean} */
15
- supportsModel(_modelId) {},
16
- /** @param {string} sessionId @returns {Promise<object[]>} message history */
17
- async loadHistory(_sessionId) { return []; },
18
- };
@@ -1,13 +0,0 @@
1
- // compose/server/connectors/connector-runtime.js
2
- /**
3
- * @interface ConnectorRuntime
4
- *
5
- * Stateful execution contract.
6
- * See agent-connector.js for the message envelope spec.
7
- */
8
- export const ConnectorRuntimeInterface = {
9
- /** @yields typed message envelopes */
10
- async *run(_prompt, _opts) {},
11
- interrupt() {},
12
- get isRunning() { return false; },
13
- };