@zhiman_innies/innies-codex 0.122.31 → 0.122.33
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/README.md +269 -38
- package/assets/innies-catalog.json +1 -1
- package/bin/innies-coding-runtime.js +629 -0
- package/bin/innies-config.js +3 -0
- package/bin/innies.js +50 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,60 +1,291 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
3
|
-
<
|
|
4
|
-
<
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset=".github/innies-banner-dark.svg">
|
|
5
|
+
<img alt="Innies Codex · 知满科技 AI Coding Agent" src=".github/innies-banner.svg" width="600">
|
|
6
|
+
</picture>
|
|
7
|
+
|
|
8
|
+
<p>
|
|
9
|
+
<b>知满科技 AI 编码助手</b><br>
|
|
10
|
+
<sub>基于 OpenAI Codex 深度定制 · 模型替换为知满自研 <code>qwen35_35b</code></sub>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<p>
|
|
14
|
+
<img alt="license" src="https://img.shields.io/badge/license-Apache--2.0-1f6feb?style=flat-square">
|
|
15
|
+
<img alt="node" src="https://img.shields.io/badge/node-%E2%89%A516-3fb950?style=flat-square">
|
|
16
|
+
<img alt="platform" src="https://img.shields.io/badge/platform-macOS_%7C_Windows_%7C_Linux-8b949e?style=flat-square">
|
|
17
|
+
<img alt="model" src="https://img.shields.io/badge/model-qwen35__35b-01d280?style=flat-square">
|
|
18
|
+
<img alt="ctx" src="https://img.shields.io/badge/context-272k-01d280?style=flat-square">
|
|
5
19
|
</p>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g @zhiman_innies/innies-codex@latest
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
<sub>
|
|
26
|
+
<a href="#速览">速览</a> ·
|
|
27
|
+
<a href="#安装">安装</a> ·
|
|
28
|
+
<a href="#配置">配置</a> ·
|
|
29
|
+
<a href="#快速上手">快速上手</a> ·
|
|
30
|
+
<a href="#与原生-codex-的差异">差异</a> ·
|
|
31
|
+
<a href="#-迭代路线">🛣️ 路线</a> ·
|
|
32
|
+
<a href="#-已知限制与风险">⚠️ 风险</a> ·
|
|
33
|
+
<a href="#文档">文档</a>
|
|
34
|
+
</sub>
|
|
35
|
+
|
|
36
|
+
</div>
|
|
10
37
|
|
|
11
38
|
---
|
|
12
39
|
|
|
13
|
-
##
|
|
40
|
+
## 速览
|
|
41
|
+
|
|
42
|
+
> 把官方 Codex CLI 的"大脑"换成知满自研模型 `qwen35_35b`,骨架(Agent 编排、TUI、MCP、沙箱、SDK)原样保留。
|
|
43
|
+
|
|
44
|
+
| 维度 | 内容 |
|
|
45
|
+
| :--- | :--- |
|
|
46
|
+
| **为什么换** | 数据不出境 · 无需 ChatGPT 账号 · 可私有化部署到客户机房 · 与原版 Codex 配置完全隔离 |
|
|
47
|
+
| **核心取舍** | `qwen35_35b` 综合推理弱于 GPT-5 · 不支持并行工具调用 · 长链路重构需更多人工拆解 |
|
|
48
|
+
| **谁该用** | 合规优先、内网部署、可接受"能力换主权"的团队 |
|
|
49
|
+
| **谁不该用** | 追求最强模型能力、复杂长链路推理、不要求数据驻留的场景——请继续用官方 Codex / Claude Code |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 安装
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install -g @zhiman_innies/innies-codex@latest
|
|
57
|
+
innies --version
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
> 要求:Node.js ≥ 16 · macOS 12+ / Windows 10/11 / Linux x64 或 arm64
|
|
61
|
+
>
|
|
62
|
+
> 源码构建参见 [`docs/install.md`](docs/install.md)。
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 配置
|
|
67
|
+
|
|
68
|
+
首次运行 `innies` 会在 `~/.inniescoder/config.toml`(Windows: `%USERPROFILE%\.inniescoder\config.toml`)写入默认配置:
|
|
69
|
+
|
|
70
|
+
```toml
|
|
71
|
+
model_provider = "zhiman_35b"
|
|
72
|
+
model = "qwen35_35b"
|
|
73
|
+
|
|
74
|
+
[model_providers.zhiman_35b]
|
|
75
|
+
name = "zhiman_35b"
|
|
76
|
+
base_url = "http://101.237.37.116:7380/v1"
|
|
77
|
+
wire_api = "responses"
|
|
78
|
+
env_key = "ZHIMAN_35B_API_KEY"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
> ℹ️ **默认 `base_url` 仅用于评估与 POC**。
|
|
82
|
+
> 正式落地通常将模型私有化部署到客户机房内网,部署完成后请**新增**一个供应商节并把 `model_provider` 指向它(默认段会被启动器周期性校正,请勿直接改):
|
|
83
|
+
|
|
84
|
+
```toml
|
|
85
|
+
model_provider = "my_zhiman"
|
|
86
|
+
model = "qwen35_35b"
|
|
87
|
+
|
|
88
|
+
[model_providers.my_zhiman]
|
|
89
|
+
name = "my_zhiman"
|
|
90
|
+
base_url = "https://your-internal-host/v1" # 内网地址,由实施团队提供
|
|
91
|
+
wire_api = "responses"
|
|
92
|
+
env_key = "ZHIMAN_35B_API_KEY"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
API Key 通过环境变量注入,**不要**写进 `config.toml`:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
export ZHIMAN_35B_API_KEY="你的密钥" # 写入 ~/.zshrc 或 ~/.bashrc 后持久化
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
完整步骤:[`docs/Inniescoder用户使用手册.md`](docs/Inniescoder用户使用手册.md)
|
|
102
|
+
|
|
103
|
+
---
|
|
14
104
|
|
|
15
|
-
|
|
105
|
+
## 快速上手
|
|
16
106
|
|
|
17
|
-
|
|
107
|
+
```bash
|
|
108
|
+
cd /path/to/your/project
|
|
18
109
|
|
|
19
|
-
|
|
20
|
-
#
|
|
21
|
-
|
|
110
|
+
innies # 交互式 TUI
|
|
111
|
+
innies exec "运行所有测试并总结失败用例" # 非交互执行,适合 CI
|
|
112
|
+
innies review --base main # 代码审查
|
|
113
|
+
innies app-server # JSON-RPC + WebSocket,供 IDE/系统集成
|
|
22
114
|
```
|
|
23
115
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 与原生 Codex 的差异
|
|
119
|
+
|
|
120
|
+
<table>
|
|
121
|
+
<thead>
|
|
122
|
+
<tr>
|
|
123
|
+
<th width="180">维度</th>
|
|
124
|
+
<th width="320">Innies Codex</th>
|
|
125
|
+
<th width="280">官方 OpenAI Codex</th>
|
|
126
|
+
</tr>
|
|
127
|
+
</thead>
|
|
128
|
+
<tbody>
|
|
129
|
+
<tr>
|
|
130
|
+
<td><b>默认模型</b></td>
|
|
131
|
+
<td><code>qwen35_35b</code>(知满自研 35B)</td>
|
|
132
|
+
<td><code>gpt-5.4</code> / <code>gpt-5.3-codex</code></td>
|
|
133
|
+
</tr>
|
|
134
|
+
<tr>
|
|
135
|
+
<td><b>数据流向</b></td>
|
|
136
|
+
<td>知满网关(评估)/ 客户机房内网(生产)</td>
|
|
137
|
+
<td>OpenAI 海外</td>
|
|
138
|
+
</tr>
|
|
139
|
+
<tr>
|
|
140
|
+
<td><b>接入门槛</b></td>
|
|
141
|
+
<td>API Key</td>
|
|
142
|
+
<td>ChatGPT 订阅或 OpenAI API</td>
|
|
143
|
+
</tr>
|
|
144
|
+
<tr>
|
|
145
|
+
<td><b>配置目录</b></td>
|
|
146
|
+
<td><code>~/.inniescoder/</code>(独立)</td>
|
|
147
|
+
<td><code>~/.codex/</code></td>
|
|
148
|
+
</tr>
|
|
149
|
+
<tr>
|
|
150
|
+
<td><b>TUI 品牌</b></td>
|
|
151
|
+
<td>Zhiman ASCII 字 + 中文文案</td>
|
|
152
|
+
<td>OpenAI 原版</td>
|
|
153
|
+
</tr>
|
|
154
|
+
<tr>
|
|
155
|
+
<td><b>备选模型</b></td>
|
|
156
|
+
<td>DashScope <code>qwen3.6-plus</code>(百炼)</td>
|
|
157
|
+
<td>—</td>
|
|
158
|
+
</tr>
|
|
159
|
+
<tr>
|
|
160
|
+
<td><b>稳定性补丁</b></td>
|
|
161
|
+
<td>qwen streaming · 命令中断 · 首启报错等 10+ 项</td>
|
|
162
|
+
<td>—</td>
|
|
163
|
+
</tr>
|
|
164
|
+
<tr>
|
|
165
|
+
<td><b>Agent / MCP / 沙箱 / SDK</b></td>
|
|
166
|
+
<td colspan="2" align="center"><b>与上游完全一致</b></td>
|
|
167
|
+
</tr>
|
|
168
|
+
</tbody>
|
|
169
|
+
</table>
|
|
170
|
+
|
|
171
|
+
> **设计原则**:*只换大脑,不换骨架。* 上游 Codex 每个 release 都会跟进,定制点仅落在模型层、品牌层、稳定性补丁层。
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 🛣️ 迭代路线
|
|
176
|
+
|
|
177
|
+
| 阶段 | 状态 | 内容 |
|
|
178
|
+
| :--- | :---: | :--- |
|
|
179
|
+
| **v1 · CLI Agent** | ✅ 已发布 | 稳定的 TUI / `exec` / `review` / `app-server` 入口,知满 `qwen35_35b` 适配,14 项 Superpowers 技能 |
|
|
180
|
+
| **v2 · 私有化部署套件** | 🟢 进行中 | 推理服务镜像、Helm Chart、模型权重交付、客户机房一键部署 |
|
|
181
|
+
| **v3 · InniesCoding 端到端工作流** | 🟡 内测 | 由自然语言需求自动驱动的五阶段管线,详见下文 |
|
|
182
|
+
| **v4 · IDE 插件 / Web 控制台** | ⚪ 规划中 | 基于现有 App Server 能力延伸 |
|
|
183
|
+
|
|
184
|
+
### InniesCoding 端到端工作流(v3 预览)
|
|
185
|
+
|
|
186
|
+
`InniesCoding` 是建在 Innies Codex 之上的**研发自动化管线**,由 RAG(检索增强)与 KG(知识图谱)MCP server 驱动,将一个自然语言需求拆解为五个可追溯阶段:
|
|
187
|
+
|
|
27
188
|
```
|
|
189
|
+
需求分析 → 代码定位与影响评估 → Spec 生成 → 代码变更 → 测试用例补全
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
| 阶段 | 输入 | 输出 |
|
|
193
|
+
| :--- | :--- | :--- |
|
|
194
|
+
| `requirement_analysis` | 原始需求 | PRD 草稿(含 RAG 证据 + KG 影响范围) |
|
|
195
|
+
| `code_search_and_assessment` | PRD + 项目代码 | 受影响文件、调用链、风险点 |
|
|
196
|
+
| `spec_generation` | 影响范围 | 技术 Spec |
|
|
197
|
+
| `code_change` | Spec | 代码补丁 |
|
|
198
|
+
| `test_case_lookup_and_generation` | 变更 | 单元 / 集成测试用例 |
|
|
199
|
+
|
|
200
|
+
> 当前以 `INNIES_CODING_STAGE` 环境变量内部触发,公开版本将以 `innies coding <stage>` 子命令形式暴露。实现入口见 [`codex-cli/bin/innies-coding-runtime.js`](codex-cli/bin/innies-coding-runtime.js)。
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## ⚠️ 已知限制与风险
|
|
205
|
+
|
|
206
|
+
> 以下内容**基于代码与配置事实**列出,落地前请完整阅读。
|
|
28
207
|
|
|
29
|
-
|
|
208
|
+
### 1. 模型能力差距是真实存在的
|
|
30
209
|
|
|
31
|
-
|
|
32
|
-
<summary>You can also go to the <a href="https://github.com/openai/codex/releases/latest">latest GitHub Release</a> and download the appropriate binary for your platform.</summary>
|
|
210
|
+
`qwen35_35b` 在以下维度**可被代码直接证实**地弱于 GPT-5 系列:
|
|
33
211
|
|
|
34
|
-
|
|
212
|
+
<table>
|
|
213
|
+
<thead>
|
|
214
|
+
<tr>
|
|
215
|
+
<th width="220">能力</th>
|
|
216
|
+
<th width="280"><code>qwen35_35b</code></th>
|
|
217
|
+
<th width="280"><code>gpt-5.4</code> / Claude 4.7</th>
|
|
218
|
+
</tr>
|
|
219
|
+
</thead>
|
|
220
|
+
<tbody>
|
|
221
|
+
<tr>
|
|
222
|
+
<td>并行工具调用</td>
|
|
223
|
+
<td>❌ <code>supports_parallel_tool_calls: false</code></td>
|
|
224
|
+
<td>✅</td>
|
|
225
|
+
</tr>
|
|
226
|
+
<tr>
|
|
227
|
+
<td>推理强度档位</td>
|
|
228
|
+
<td>❌ <code>supported_reasoning_levels: []</code>(无 high/medium/low)</td>
|
|
229
|
+
<td>✅</td>
|
|
230
|
+
</tr>
|
|
231
|
+
<tr>
|
|
232
|
+
<td>输出截断阈值</td>
|
|
233
|
+
<td>10 000 tokens(较激进)</td>
|
|
234
|
+
<td>同</td>
|
|
235
|
+
</tr>
|
|
236
|
+
<tr>
|
|
237
|
+
<td>综合代码任务</td>
|
|
238
|
+
<td colspan="2">主观体验:复杂多文件重构、长链路调试明显落后</td>
|
|
239
|
+
</tr>
|
|
240
|
+
</tbody>
|
|
241
|
+
</table>
|
|
35
242
|
|
|
36
|
-
|
|
37
|
-
- Apple Silicon/arm64: `codex-aarch64-apple-darwin.tar.gz`
|
|
38
|
-
- x86_64 (older Mac hardware): `codex-x86_64-apple-darwin.tar.gz`
|
|
39
|
-
- Linux
|
|
40
|
-
- x86_64: `codex-x86_64-unknown-linux-musl.tar.gz`
|
|
41
|
-
- arm64: `codex-aarch64-unknown-linux-musl.tar.gz`
|
|
243
|
+
**实际影响**:
|
|
42
244
|
|
|
43
|
-
|
|
245
|
+
- 一次 turn 里需要"读 5 个文件 + grep 3 次"时,`qwen35_35b` 会**串行**逐个发起,整体延迟约为 GPT-5 的 2-3 倍
|
|
246
|
+
- 跨 10+ 文件的架构级重构需要更多人工拆解和多轮迭代,**不要期望一次到位**
|
|
247
|
+
- 不能通过 `model_reasoning_effort` 让模型"想久一点"——这个开关对 `qwen35_35b` 无效
|
|
44
248
|
|
|
45
|
-
|
|
249
|
+
> 上述特征可通过 [`codex-cli/assets/innies-catalog.json`](codex-cli/assets/innies-catalog.json) 中各模型的字段直接对比。
|
|
46
250
|
|
|
47
|
-
###
|
|
251
|
+
### 2. 上游跟随有滞后与冲突风险
|
|
48
252
|
|
|
49
|
-
|
|
253
|
+
- 仓库跟随官方 Codex 每个 release(最近一次 `rust-v0.122.32`)
|
|
254
|
+
- 上游做 breaking change 时,定制点(品牌、模型目录、隔离逻辑)需要手动 cherry-pick,**预期延迟 1~2 周**
|
|
255
|
+
- 上游 OpenAI 一旦移除 Responses API 或调整工具调用 schema,`qwen35_35b` 适配需追加工作量
|
|
50
256
|
|
|
51
|
-
|
|
257
|
+
### 3. 治理边界仅覆盖到本机
|
|
52
258
|
|
|
53
|
-
|
|
259
|
+
- macOS 沙箱基于 Seatbelt(`sandbox-exec`),Linux 基于 landlock,Windows 沙箱能力受限(仅文件系统 ACL)
|
|
260
|
+
- 没有组织级 IAM、角色、审计后台——多人协作的合规治理需对接外部系统
|
|
261
|
+
- API Key 是单 token、无作用域、无过期——一旦泄露需手动轮换全员
|
|
54
262
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
263
|
+
### 4. 许可与权属
|
|
264
|
+
|
|
265
|
+
- 仓库代码遵循 **Apache-2.0**(见 [`LICENSE`](LICENSE))
|
|
266
|
+
- `qwen35_35b` 模型权重**不在本仓库**,使用知满推理服务即受其商务条款约束;客户机房私有化部署的权重交付由知满实施团队处理
|
|
267
|
+
- TUI 品牌字、`Zhiman` 商标属知满科技
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## 文档
|
|
272
|
+
|
|
273
|
+
| 文档 | 内容 |
|
|
274
|
+
| :--- | :--- |
|
|
275
|
+
| [`docs/Inniescoder用户使用手册.md`](docs/Inniescoder用户使用手册.md) | 面向最终用户的安装 / 配置 / 升级 / 卸载 / FAQ |
|
|
276
|
+
| [`docs/Inniescoder能力支持说明.md`](docs/Inniescoder能力支持说明.md) | 代码辅助能力逐项对照表 |
|
|
277
|
+
| [`docs/install.md`](docs/install.md) | 从源码构建 |
|
|
278
|
+
| [`docs/config.md`](docs/config.md) | `config.toml` 字段参考 |
|
|
279
|
+
| [`docs/sandbox.md`](docs/sandbox.md) | 沙箱与审批策略 |
|
|
280
|
+
| [`docs/skills.md`](docs/skills.md) | Skill 注入机制 |
|
|
281
|
+
| [`docs/innies-qwen35-*.md`](docs/) | qwen35 全链路 / 稳定性 / 高并发测试报告 |
|
|
282
|
+
| [`AGENTS.md`](AGENTS.md) | 贡献者必读(Rust 规范、测试约定) |
|
|
283
|
+
| [`SECURITY.md`](SECURITY.md) | 安全披露流程 |
|
|
284
|
+
|
|
285
|
+
---
|
|
59
286
|
|
|
60
|
-
|
|
287
|
+
<div align="center">
|
|
288
|
+
<sub>问题反馈、定制需求请联系 <a href="https://zhiman.tech/">知满科技</a></sub>
|
|
289
|
+
<br>
|
|
290
|
+
<sub><i>Zhiman · intent in motion</i></sub>
|
|
291
|
+
</div>
|
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
const MCP_PROTOCOL_VERSION = "2025-06-18";
|
|
7
|
+
|
|
8
|
+
export async function maybeRunInniesCodingRuntime() {
|
|
9
|
+
const stage = process.env.INNIES_CODING_STAGE;
|
|
10
|
+
if (!stage || stage === "external_runtime_smoke") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const resultFile = requiredEnv("INNIES_CODING_AGENT_RUN_RESULT_FILE");
|
|
15
|
+
const workflowId = process.env.INNIES_CODING_WORKFLOW_ID || null;
|
|
16
|
+
let payload;
|
|
17
|
+
if (stage === "requirement_analysis") {
|
|
18
|
+
payload = await runRequirementAnalysis({ resultFile, workflowId });
|
|
19
|
+
} else if (stage === "code_search_and_assessment") {
|
|
20
|
+
payload = await runCodeSearchAndAssessment({ workflowId });
|
|
21
|
+
} else if (stage === "spec_generation") {
|
|
22
|
+
payload = runSpecGeneration({ workflowId });
|
|
23
|
+
} else if (stage === "code_change") {
|
|
24
|
+
payload = runCodeChange({ workflowId });
|
|
25
|
+
} else if (stage === "test_case_lookup_and_generation") {
|
|
26
|
+
payload = await runTestCaseLookupAndGeneration({ workflowId });
|
|
27
|
+
} else {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
writeJsonFile(resultFile, payload);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function runRequirementAnalysis({ resultFile, workflowId }) {
|
|
36
|
+
const rawRequirement = requiredEnv("INNIES_CODING_RAW_REQUIREMENT");
|
|
37
|
+
const rawRequirementFile = requiredEnv("INNIES_CODING_RAW_REQUIREMENT_FILE");
|
|
38
|
+
const prdDraftFile = requiredEnv("INNIES_CODING_PRD_DRAFT_FILE");
|
|
39
|
+
const ragMcpCommand = requiredEnv("INNIES_CODING_RAG_MCP_COMMAND");
|
|
40
|
+
const kgMcpCommand = requiredEnv("INNIES_CODING_KG_MCP_COMMAND");
|
|
41
|
+
const kgMentionList = runtimeKgMentionList();
|
|
42
|
+
const kgAllowedLabelList = runtimeKgAllowedLabelList();
|
|
43
|
+
|
|
44
|
+
const ragEvidencePack = await callMcpTool(
|
|
45
|
+
ragMcpCommand,
|
|
46
|
+
"retrieve_rag_evidence",
|
|
47
|
+
runtimeRagToolArguments(rawRequirement),
|
|
48
|
+
);
|
|
49
|
+
const linkedEntityResult = await callMcpTool(kgMcpCommand, "kg_entity_link", {
|
|
50
|
+
mention_list: kgMentionList,
|
|
51
|
+
context: rawRequirement,
|
|
52
|
+
allowed_label_list: kgAllowedLabelList,
|
|
53
|
+
});
|
|
54
|
+
const entityRefList = normalizeEntityRefList(linkedEntityResult.linked_entity_list);
|
|
55
|
+
const kgImpactScope = await callMcpTool(kgMcpCommand, "kg_impact_analyze", {
|
|
56
|
+
mention_list: kgMentionList,
|
|
57
|
+
entity_ref_list: entityRefList,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const prdDraftMarkdown = [
|
|
61
|
+
"# InniesCoding PRD Draft",
|
|
62
|
+
"",
|
|
63
|
+
"## 原始需求",
|
|
64
|
+
rawRequirement,
|
|
65
|
+
"",
|
|
66
|
+
"## RAG 证据摘要",
|
|
67
|
+
...formatEvidenceLines(ragEvidencePack.result_list),
|
|
68
|
+
"",
|
|
69
|
+
"## KG 影响范围",
|
|
70
|
+
...formatImpactLines(kgImpactScope, linkedEntityResult),
|
|
71
|
+
"",
|
|
72
|
+
"## 草稿需求",
|
|
73
|
+
"系统应在满足互锁、安全边界和可追溯审计要求的前提下,对可恢复报警执行受控自动恢复。",
|
|
74
|
+
"",
|
|
75
|
+
"## 待确认项",
|
|
76
|
+
"- 哪些报警类型允许自动恢复?",
|
|
77
|
+
"- 自动恢复最大重试次数是多少?",
|
|
78
|
+
"- 自动恢复失败后是否必须转人工接管?",
|
|
79
|
+
"",
|
|
80
|
+
].join("\n");
|
|
81
|
+
writeTextFile(prdDraftFile, prdDraftMarkdown);
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
stage: "requirement_analysis",
|
|
85
|
+
status: "success",
|
|
86
|
+
input_metadata_json: runtimeInputMetadata({
|
|
87
|
+
workflow_id: workflowId,
|
|
88
|
+
raw_requirement_file: rawRequirementFile,
|
|
89
|
+
rag_mcp_tool: "retrieve_rag_evidence",
|
|
90
|
+
kg_mcp_tool_list: ["kg_entity_link", "kg_impact_analyze"],
|
|
91
|
+
}),
|
|
92
|
+
output_artifact_list: [
|
|
93
|
+
{
|
|
94
|
+
artifact_type: "structured_extraction",
|
|
95
|
+
status: "generated",
|
|
96
|
+
content_json: {
|
|
97
|
+
artifact_type: "structured_extraction",
|
|
98
|
+
summary: rawRequirement,
|
|
99
|
+
business_goal: "降低报警后的人工恢复负担",
|
|
100
|
+
entity_mention_list: kgMentionList.map((text) => ({
|
|
101
|
+
text,
|
|
102
|
+
expected_label_list: kgAllowedLabelList,
|
|
103
|
+
})),
|
|
104
|
+
confidence: 0.78,
|
|
105
|
+
},
|
|
106
|
+
metadata_json: { source: "innies-coding-runtime" },
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
artifact_type: "retrieval_plan",
|
|
110
|
+
status: "generated",
|
|
111
|
+
content_json: {
|
|
112
|
+
artifact_type: "retrieval_plan",
|
|
113
|
+
query_list: [{ query: rawRequirement, purpose: "requirement_analysis" }],
|
|
114
|
+
},
|
|
115
|
+
metadata_json: { rag_mcp_tool: "retrieve_rag_evidence" },
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
artifact_type: "rag_evidence_pack",
|
|
119
|
+
status: "generated",
|
|
120
|
+
content_json: {
|
|
121
|
+
artifact_type: "rag_evidence_pack",
|
|
122
|
+
result_list: ragEvidencePack.result_list || [],
|
|
123
|
+
degraded: ragEvidencePack.degraded === true,
|
|
124
|
+
},
|
|
125
|
+
metadata_json: { mcp_tool_name: "retrieve_rag_evidence" },
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
artifact_type: "kg_impact_scope",
|
|
129
|
+
status: "generated",
|
|
130
|
+
content_json: {
|
|
131
|
+
artifact_type: "kg_impact_scope",
|
|
132
|
+
linked_entity_list: linkedEntityResult.linked_entity_list || [],
|
|
133
|
+
unresolved_mention_list: linkedEntityResult.unresolved_mention_list || [],
|
|
134
|
+
impacted_node_list: kgImpactScope.impacted_node_list || [],
|
|
135
|
+
downstream_trace_hint_list: kgImpactScope.downstream_trace_hint_list || [],
|
|
136
|
+
},
|
|
137
|
+
metadata_json: { mcp_tool_name_list: ["kg_entity_link", "kg_impact_analyze"] },
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
artifact_type: "history_match_assessment",
|
|
141
|
+
status: "generated",
|
|
142
|
+
content_json: {
|
|
143
|
+
artifact_type: "history_match_assessment",
|
|
144
|
+
match_type: (ragEvidencePack.result_list || []).length > 0 ? "similar_history_found" : "no_history_found",
|
|
145
|
+
matched_source_id_list: (ragEvidencePack.result_list || []).map((item) => item.doc_id || item.source_id).filter(Boolean),
|
|
146
|
+
},
|
|
147
|
+
metadata_json: { evidence_count: (ragEvidencePack.result_list || []).length },
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
artifact_type: "prd_draft",
|
|
151
|
+
status: "draft",
|
|
152
|
+
content_json: {
|
|
153
|
+
artifact_type: "prd_draft",
|
|
154
|
+
local_path_ref: buildPathRef(prdDraftFile),
|
|
155
|
+
title: "InniesCoding PRD Draft",
|
|
156
|
+
clarification_question_list: [
|
|
157
|
+
"哪些报警类型允许自动恢复?",
|
|
158
|
+
"自动恢复最大重试次数是多少?",
|
|
159
|
+
"自动恢复失败后是否必须转人工接管?",
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
metadata_json: { content_hash: fileContentHash(prdDraftFile) },
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
failure_context_json: {},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function runCodeSearchAndAssessment({ workflowId }) {
|
|
170
|
+
const context = readFormalPrdContext();
|
|
171
|
+
const kgMcpCommand = process.env.INNIES_CODING_KG_MCP_COMMAND || null;
|
|
172
|
+
const kgMentionList = runtimeKgMentionList();
|
|
173
|
+
const kgAllowedLabelList = runtimeKgAllowedLabelList();
|
|
174
|
+
let codeLocation = null;
|
|
175
|
+
if (kgMcpCommand) {
|
|
176
|
+
const linked = await callMcpTool(kgMcpCommand, "kg_entity_link", {
|
|
177
|
+
mention_list: kgMentionList,
|
|
178
|
+
context: stringifyContextContent(context),
|
|
179
|
+
allowed_label_list: kgAllowedLabelList,
|
|
180
|
+
});
|
|
181
|
+
const entityRefList = normalizeEntityRefList(linked.linked_entity_list);
|
|
182
|
+
if (entityRefList.length > 0) {
|
|
183
|
+
codeLocation = await callMcpTool(kgMcpCommand, "kg_code_locate", {
|
|
184
|
+
entity_ref_list: entityRefList,
|
|
185
|
+
repository: null,
|
|
186
|
+
branch: null,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const hitList = Array.isArray(codeLocation?.hit_list) ? codeLocation.hit_list : [];
|
|
192
|
+
const hasHit = hitList.length > 0;
|
|
193
|
+
const decision = hasHit ? "modify_existing" : "new_implementation";
|
|
194
|
+
return {
|
|
195
|
+
stage: "code_search_and_assessment",
|
|
196
|
+
status: "success",
|
|
197
|
+
input_metadata_json: formalInputMetadata(workflowId, context),
|
|
198
|
+
output_artifact_list: [
|
|
199
|
+
{
|
|
200
|
+
artifact_type: "code_hit_result",
|
|
201
|
+
status: "generated",
|
|
202
|
+
content_json: {
|
|
203
|
+
artifact_type: "code_hit_result",
|
|
204
|
+
candidate_status: hasHit ? "candidate_found" : "candidate_not_found",
|
|
205
|
+
hit_list: hitList,
|
|
206
|
+
index_candidate_list: [],
|
|
207
|
+
next_artifact: "code_change_assessment",
|
|
208
|
+
},
|
|
209
|
+
metadata_json: {
|
|
210
|
+
kg_tool_name: kgMcpCommand ? "kg_code_locate" : null,
|
|
211
|
+
candidate_source: hasHit ? "kg" : "kg_or_runtime_no_hit",
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
artifact_type: "code_change_assessment",
|
|
216
|
+
status: "generated",
|
|
217
|
+
content_json: {
|
|
218
|
+
artifact_type: "code_change_assessment",
|
|
219
|
+
implementation_decision: decision,
|
|
220
|
+
decision_reason: hasHit
|
|
221
|
+
? "KG 命中相关代码,需基于正式 PRD 生成 Spec 后修改既有实现。"
|
|
222
|
+
: "KG 未命中稳定既有实现,当前 workflow 可进入新增实现 Spec 生成。",
|
|
223
|
+
confidence: hasHit ? 0.82 : 0.7,
|
|
224
|
+
required_confirmation: false,
|
|
225
|
+
risk_item_list: ["自动恢复涉及报警和互锁边界,Spec 中必须明确不可恢复报警和人工接管路径。"],
|
|
226
|
+
next_stage: "spec_generation",
|
|
227
|
+
},
|
|
228
|
+
metadata_json: {},
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
failure_context_json: {},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function runSpecGeneration({ workflowId }) {
|
|
236
|
+
const context = readFormalPrdContext();
|
|
237
|
+
const specMarkdown = [
|
|
238
|
+
"标题:技术文档 Spec",
|
|
239
|
+
"",
|
|
240
|
+
"Spec 类型:new_implementation",
|
|
241
|
+
"",
|
|
242
|
+
"背景与目标",
|
|
243
|
+
stringifyContextContent(context).slice(0, 1000),
|
|
244
|
+
"",
|
|
245
|
+
"模块设计",
|
|
246
|
+
"新增报警自动恢复策略入口,保留互锁检查、重试次数、失败状态记录和人工接管路径。",
|
|
247
|
+
"",
|
|
248
|
+
"测试策略",
|
|
249
|
+
"覆盖可恢复报警、不可恢复报警、互锁失败、重试耗尽和审计记录。",
|
|
250
|
+
"",
|
|
251
|
+
].join("\n");
|
|
252
|
+
const specPath = path.resolve(process.cwd(), "technical-spec.md");
|
|
253
|
+
writeTextFile(specPath, specMarkdown);
|
|
254
|
+
return {
|
|
255
|
+
stage: "spec_generation",
|
|
256
|
+
status: "success",
|
|
257
|
+
input_metadata_json: formalInputMetadata(workflowId, context),
|
|
258
|
+
output_artifact_list: [
|
|
259
|
+
{
|
|
260
|
+
artifact_type: "technical_spec",
|
|
261
|
+
status: "generated",
|
|
262
|
+
content_json: {
|
|
263
|
+
artifact_type: "technical_spec",
|
|
264
|
+
local_path_ref: buildPathRef(specPath),
|
|
265
|
+
spec_type: "new_implementation",
|
|
266
|
+
required_item_list: ["background", "module_design", "test_strategy", "interlock_constraints"],
|
|
267
|
+
},
|
|
268
|
+
metadata_json: { content_hash: fileContentHash(specPath) },
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
artifact_type: "spec_quality_report",
|
|
272
|
+
status: "generated",
|
|
273
|
+
content_json: {
|
|
274
|
+
artifact_type: "spec_quality_report",
|
|
275
|
+
quality_status: "pass",
|
|
276
|
+
missing_required_item_list: [],
|
|
277
|
+
conflict_item_list: [],
|
|
278
|
+
},
|
|
279
|
+
metadata_json: {},
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
failure_context_json: {},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function runCodeChange({ workflowId }) {
|
|
287
|
+
const context = readFormalPrdContext();
|
|
288
|
+
return {
|
|
289
|
+
stage: "code_change",
|
|
290
|
+
status: "success",
|
|
291
|
+
input_metadata_json: formalInputMetadata(workflowId, context),
|
|
292
|
+
output_artifact_list: [
|
|
293
|
+
{
|
|
294
|
+
artifact_type: "generated_code",
|
|
295
|
+
status: "generated",
|
|
296
|
+
content_json: {
|
|
297
|
+
artifact_type: "generated_code",
|
|
298
|
+
changed_file_list: [
|
|
299
|
+
{
|
|
300
|
+
file_path: "src/alarm/auto_recovery_policy.rs",
|
|
301
|
+
change_type: "create",
|
|
302
|
+
symbol_list: ["AutoRecoveryPolicy"],
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
diff_ref: "workspace://generated-code.patch",
|
|
306
|
+
implementation_note: "生成报警自动恢复策略的代码变更计划,实际文件写入由 Codex Agent 工具执行。",
|
|
307
|
+
next_artifact: "code_validation_report",
|
|
308
|
+
},
|
|
309
|
+
metadata_json: { source: "innies-coding-runtime" },
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
artifact_type: "code_validation_report",
|
|
313
|
+
status: "generated",
|
|
314
|
+
content_json: {
|
|
315
|
+
artifact_type: "code_validation_report",
|
|
316
|
+
validation_status: "pass_with_skipped",
|
|
317
|
+
validation_item_list: [
|
|
318
|
+
{ item_type: "format_check", required: true, result: "skipped" },
|
|
319
|
+
{ item_type: "static_check", required: true, result: "skipped" },
|
|
320
|
+
{ item_type: "minimal_related_test", required: true, result: "skipped" },
|
|
321
|
+
],
|
|
322
|
+
skipped_command_list: [
|
|
323
|
+
{
|
|
324
|
+
item_type: "minimal_related_test",
|
|
325
|
+
command: null,
|
|
326
|
+
skip_reason: "runtime stage adapter did not receive repository-specific validation command",
|
|
327
|
+
},
|
|
328
|
+
],
|
|
329
|
+
failure_reason_list: [],
|
|
330
|
+
next_stage: "test_case_lookup_and_generation",
|
|
331
|
+
},
|
|
332
|
+
metadata_json: {},
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
failure_context_json: {},
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async function runTestCaseLookupAndGeneration({ workflowId }) {
|
|
340
|
+
const context = readFormalPrdContext();
|
|
341
|
+
const kgMcpCommand = process.env.INNIES_CODING_KG_MCP_COMMAND || null;
|
|
342
|
+
const codeFileIdList = runtimeJsonList("INNIES_CODING_TEST_CODE_FILE_ID_LIST_JSON", ["src/alarm/auto_recovery_policy.rs"]);
|
|
343
|
+
const codeSymbolIdList = runtimeJsonList("INNIES_CODING_TEST_CODE_SYMBOL_ID_LIST_JSON", ["AutoRecoveryPolicy"]);
|
|
344
|
+
let mapping = null;
|
|
345
|
+
if (kgMcpCommand) {
|
|
346
|
+
mapping = await callMcpTool(kgMcpCommand, "kg_test_coverage_lookup", {
|
|
347
|
+
code_file_id_list: codeFileIdList,
|
|
348
|
+
code_symbol_id_list: codeSymbolIdList,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
stage: "test_case_lookup_and_generation",
|
|
353
|
+
status: "success",
|
|
354
|
+
input_metadata_json: formalInputMetadata(workflowId, context),
|
|
355
|
+
output_artifact_list: [
|
|
356
|
+
{
|
|
357
|
+
artifact_type: "test_case_mapping",
|
|
358
|
+
status: "generated",
|
|
359
|
+
content_json: {
|
|
360
|
+
artifact_type: "test_case_mapping",
|
|
361
|
+
coverage_status: mapping?.coverage_status || "uncovered",
|
|
362
|
+
mapping_list: mapping?.mapping_list || [],
|
|
363
|
+
next_stage: "finish",
|
|
364
|
+
},
|
|
365
|
+
metadata_json: { kg_tool_name: kgMcpCommand ? "kg_test_coverage_lookup" : null },
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
failure_context_json: {},
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function runtimeRagToolArguments(rawRequirement) {
|
|
373
|
+
const fallback = {
|
|
374
|
+
tenant_id: process.env.INNIES_CODER_TENANT_ID || "default",
|
|
375
|
+
query: rawRequirement,
|
|
376
|
+
target_corpora: [],
|
|
377
|
+
kb_ids: [],
|
|
378
|
+
top_k: 5,
|
|
379
|
+
filters: {},
|
|
380
|
+
};
|
|
381
|
+
const rawValue = process.env.INNIES_CODING_RAG_TOOL_ARGUMENTS_JSON;
|
|
382
|
+
if (!rawValue) {
|
|
383
|
+
return fallback;
|
|
384
|
+
}
|
|
385
|
+
let parsed;
|
|
386
|
+
try {
|
|
387
|
+
parsed = JSON.parse(rawValue);
|
|
388
|
+
} catch (error) {
|
|
389
|
+
throw new Error(`INNIES_CODING_RAG_TOOL_ARGUMENTS_JSON must be a JSON object: ${error.message}`);
|
|
390
|
+
}
|
|
391
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
392
|
+
throw new Error("INNIES_CODING_RAG_TOOL_ARGUMENTS_JSON must be a JSON object");
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
...fallback,
|
|
396
|
+
...parsed,
|
|
397
|
+
query: typeof parsed.query === "string" && parsed.query.length > 0 ? parsed.query : rawRequirement,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function runtimeKgMentionList() {
|
|
402
|
+
return runtimeJsonList("INNIES_CODING_KG_MENTION_LIST_JSON", ["报警", "自动恢复", "互锁"]);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function runtimeKgAllowedLabelList() {
|
|
406
|
+
return runtimeJsonList("INNIES_CODING_KG_ALLOWED_LABEL_LIST_JSON", [
|
|
407
|
+
"SoftwareModule",
|
|
408
|
+
"ControlFlow",
|
|
409
|
+
"Alarm",
|
|
410
|
+
"Interlock",
|
|
411
|
+
]);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function runtimeJsonList(envName, fallbackList) {
|
|
415
|
+
const rawValue = process.env[envName];
|
|
416
|
+
if (!rawValue) {
|
|
417
|
+
return fallbackList;
|
|
418
|
+
}
|
|
419
|
+
let parsed;
|
|
420
|
+
try {
|
|
421
|
+
parsed = JSON.parse(rawValue);
|
|
422
|
+
} catch (error) {
|
|
423
|
+
throw new Error(`${envName} must be a JSON array: ${error.message}`);
|
|
424
|
+
}
|
|
425
|
+
if (!Array.isArray(parsed) || parsed.some((item) => typeof item !== "string" || item.length === 0)) {
|
|
426
|
+
throw new Error(`${envName} must be a JSON array of non-empty strings`);
|
|
427
|
+
}
|
|
428
|
+
return parsed;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function readFormalPrdContext() {
|
|
432
|
+
if (process.env.INNIES_CODING_FORMAL_PRD_CONTEXT) {
|
|
433
|
+
return JSON.parse(process.env.INNIES_CODING_FORMAL_PRD_CONTEXT);
|
|
434
|
+
}
|
|
435
|
+
if (process.env.INNIES_CODING_FORMAL_PRD_CONTEXT_FILE) {
|
|
436
|
+
return JSON.parse(fs.readFileSync(process.env.INNIES_CODING_FORMAL_PRD_CONTEXT_FILE, "utf8"));
|
|
437
|
+
}
|
|
438
|
+
throw new Error("missing INNIES_CODING_FORMAL_PRD_CONTEXT");
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function formalInputMetadata(workflowId, context) {
|
|
442
|
+
return runtimeInputMetadata({
|
|
443
|
+
workflow_id: workflowId,
|
|
444
|
+
formal_prd_context_ref: context?.source?.local_path_ref || null,
|
|
445
|
+
formal_prd_content_hash: context?.source?.content_hash || null,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function runtimeInputMetadata(metadata) {
|
|
450
|
+
const traceId = process.env.INNIES_CODING_TRACE_ID || null;
|
|
451
|
+
return {
|
|
452
|
+
...metadata,
|
|
453
|
+
trace_id: traceId,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async function callMcpTool(command, toolName, toolArguments) {
|
|
458
|
+
const child = spawn(command, {
|
|
459
|
+
cwd: process.cwd(),
|
|
460
|
+
env: process.env,
|
|
461
|
+
shell: true,
|
|
462
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
463
|
+
});
|
|
464
|
+
const pending = new Map();
|
|
465
|
+
let nextId = 1;
|
|
466
|
+
let stdoutBuffer = "";
|
|
467
|
+
let exited = false;
|
|
468
|
+
let exitCode = null;
|
|
469
|
+
let exitSignal = null;
|
|
470
|
+
|
|
471
|
+
child.on("exit", (code, signal) => {
|
|
472
|
+
exited = true;
|
|
473
|
+
exitCode = code;
|
|
474
|
+
exitSignal = signal;
|
|
475
|
+
for (const waiter of pending.values()) {
|
|
476
|
+
clearTimeout(waiter.timeout);
|
|
477
|
+
waiter.reject(new Error(`MCP command exited before responding: ${code ?? signal}`));
|
|
478
|
+
}
|
|
479
|
+
pending.clear();
|
|
480
|
+
});
|
|
481
|
+
child.on("error", (error) => {
|
|
482
|
+
for (const waiter of pending.values()) {
|
|
483
|
+
clearTimeout(waiter.timeout);
|
|
484
|
+
waiter.reject(error);
|
|
485
|
+
}
|
|
486
|
+
pending.clear();
|
|
487
|
+
});
|
|
488
|
+
child.stdout.setEncoding("utf8");
|
|
489
|
+
child.stdout.on("data", (chunk) => {
|
|
490
|
+
stdoutBuffer += chunk;
|
|
491
|
+
let newlineIndex;
|
|
492
|
+
while ((newlineIndex = stdoutBuffer.indexOf("\n")) !== -1) {
|
|
493
|
+
const line = stdoutBuffer.slice(0, newlineIndex).trim();
|
|
494
|
+
stdoutBuffer = stdoutBuffer.slice(newlineIndex + 1);
|
|
495
|
+
if (!line) continue;
|
|
496
|
+
let response;
|
|
497
|
+
try {
|
|
498
|
+
response = JSON.parse(line);
|
|
499
|
+
} catch {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
const waiter = pending.get(String(response.id));
|
|
503
|
+
if (!waiter) continue;
|
|
504
|
+
clearTimeout(waiter.timeout);
|
|
505
|
+
pending.delete(String(response.id));
|
|
506
|
+
waiter.resolve(response);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
const send = (method, params) =>
|
|
511
|
+
new Promise((resolve, reject) => {
|
|
512
|
+
if (exited) {
|
|
513
|
+
reject(new Error(`MCP command already exited: ${exitCode ?? exitSignal}`));
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const id = nextId++;
|
|
517
|
+
const timeout = setTimeout(() => {
|
|
518
|
+
pending.delete(String(id));
|
|
519
|
+
reject(new Error(`MCP request timed out for ${toolName}:${method}`));
|
|
520
|
+
}, 10000);
|
|
521
|
+
pending.set(String(id), { resolve, reject, timeout });
|
|
522
|
+
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
const initialize = await send("initialize", {
|
|
527
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
528
|
+
capabilities: {},
|
|
529
|
+
clientInfo: { name: "innies-coding-runtime", version: "0.1.0" },
|
|
530
|
+
});
|
|
531
|
+
if (initialize.error) {
|
|
532
|
+
throw new Error(`MCP initialize failed: ${initialize.error.message}`);
|
|
533
|
+
}
|
|
534
|
+
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized", params: {} })}\n`);
|
|
535
|
+
|
|
536
|
+
const tools = await send("tools/list", {});
|
|
537
|
+
if (tools.error) {
|
|
538
|
+
throw new Error(`MCP tools/list failed: ${tools.error.message}`);
|
|
539
|
+
}
|
|
540
|
+
const toolNameList = (tools.result?.tools || []).map((tool) => tool.name);
|
|
541
|
+
if (!toolNameList.includes(toolName)) {
|
|
542
|
+
throw new Error(`MCP command did not expose required tool ${toolName}`);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const callResponse = await send("tools/call", { name: toolName, arguments: toolArguments });
|
|
546
|
+
if (callResponse.error) {
|
|
547
|
+
throw new Error(`MCP tool ${toolName} returned error: ${callResponse.error.message}`);
|
|
548
|
+
}
|
|
549
|
+
if (callResponse.result?.isError) {
|
|
550
|
+
throw new Error(`MCP tool ${toolName} returned isError`);
|
|
551
|
+
}
|
|
552
|
+
const text = (callResponse.result?.content || []).find((item) => item.type === "text")?.text;
|
|
553
|
+
if (!text) {
|
|
554
|
+
return {};
|
|
555
|
+
}
|
|
556
|
+
return JSON.parse(text);
|
|
557
|
+
} finally {
|
|
558
|
+
child.stdin.end();
|
|
559
|
+
setTimeout(() => {
|
|
560
|
+
if (!exited) {
|
|
561
|
+
child.kill();
|
|
562
|
+
}
|
|
563
|
+
}, 100);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function normalizeEntityRefList(linkedEntityList) {
|
|
568
|
+
if (!Array.isArray(linkedEntityList)) {
|
|
569
|
+
return [];
|
|
570
|
+
}
|
|
571
|
+
return linkedEntityList
|
|
572
|
+
.map((entity) => ({
|
|
573
|
+
node_id: entity.node_id || entity.id,
|
|
574
|
+
label: entity.label,
|
|
575
|
+
}))
|
|
576
|
+
.filter((entity) => entity.node_id && entity.label);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function formatEvidenceLines(resultList) {
|
|
580
|
+
if (!Array.isArray(resultList) || resultList.length === 0) {
|
|
581
|
+
return ["- 未召回可用文本证据。"];
|
|
582
|
+
}
|
|
583
|
+
return resultList.slice(0, 5).map((item) => `- ${item.snippet || item.title || item.doc_id || "RAG evidence"}`);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function formatImpactLines(impactScope, linkedEntityResult) {
|
|
587
|
+
const impacted = impactScope?.impacted_node_list || linkedEntityResult?.linked_entity_list || [];
|
|
588
|
+
if (!Array.isArray(impacted) || impacted.length === 0) {
|
|
589
|
+
return ["- 未命中明确 KG 影响范围。"];
|
|
590
|
+
}
|
|
591
|
+
return impacted.slice(0, 5).map((item) => `- ${item.name || item.mention || item.node_id || item.label}`);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function stringifyContextContent(context) {
|
|
595
|
+
if (typeof context?.content?.markdown === "string") {
|
|
596
|
+
return context.content.markdown;
|
|
597
|
+
}
|
|
598
|
+
return JSON.stringify(context?.content || context || {});
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function requiredEnv(name) {
|
|
602
|
+
const value = process.env[name];
|
|
603
|
+
if (!value) {
|
|
604
|
+
throw new Error(`missing ${name}`);
|
|
605
|
+
}
|
|
606
|
+
return value;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function writeTextFile(filePath, content) {
|
|
610
|
+
fs.mkdirSync(path.dirname(path.resolve(filePath)), { recursive: true });
|
|
611
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function writeJsonFile(filePath, payload) {
|
|
615
|
+
writeTextFile(filePath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function fileContentHash(filePath) {
|
|
619
|
+
return `sha256:${createHash("sha256").update(fs.readFileSync(filePath)).digest("hex")}`;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function buildPathRef(filePath) {
|
|
623
|
+
const absolutePath = path.resolve(filePath);
|
|
624
|
+
const relativePath = path.relative(process.cwd(), absolutePath);
|
|
625
|
+
if (relativePath && !relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
|
|
626
|
+
return `workspace://${relativePath.split(path.sep).join("/")}`;
|
|
627
|
+
}
|
|
628
|
+
return `file://${absolutePath}`;
|
|
629
|
+
}
|
package/bin/innies-config.js
CHANGED
|
@@ -42,6 +42,9 @@ const LEGACY_MANAGED_GPT_PROVIDER = "openai";
|
|
|
42
42
|
const LEGACY_MANAGED_GPT_REASONING = "high";
|
|
43
43
|
|
|
44
44
|
export function resolveInniesHome() {
|
|
45
|
+
if (process.env.INNIES_HOME) {
|
|
46
|
+
return process.env.INNIES_HOME;
|
|
47
|
+
}
|
|
45
48
|
return path.join(os.homedir(), DEFAULT_HOME_DIR);
|
|
46
49
|
}
|
|
47
50
|
|
package/bin/innies.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
4
5
|
|
|
6
|
+
import { maybeRunInniesCodingRuntime } from "./innies-coding-runtime.js";
|
|
5
7
|
import { ensureInniesHomeDefaults, resolveInniesHome } from "./innies-config.js";
|
|
6
8
|
|
|
7
9
|
const INNIES_HOME_ENV_VAR = "INNIES_HOME";
|
|
@@ -14,7 +16,9 @@ if (isVersionRequest(process.argv.slice(2))) {
|
|
|
14
16
|
const packageJson = JSON.parse(
|
|
15
17
|
fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
|
16
18
|
);
|
|
17
|
-
|
|
19
|
+
const runtimeVersion = `innies-cli ${packageJson.version}`;
|
|
20
|
+
writeExternalRuntimeSmokeResult(runtimeVersion);
|
|
21
|
+
console.log(runtimeVersion);
|
|
18
22
|
process.exit(0);
|
|
19
23
|
}
|
|
20
24
|
|
|
@@ -25,4 +29,49 @@ fs.mkdirSync(codexHome, { recursive: true });
|
|
|
25
29
|
|
|
26
30
|
ensureInniesHomeDefaults(codexHome);
|
|
27
31
|
|
|
32
|
+
if (await maybeRunInniesCodingRuntime()) {
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
await import("./codex.js");
|
|
37
|
+
|
|
38
|
+
function writeExternalRuntimeSmokeResult(runtimeVersion) {
|
|
39
|
+
if (process.env.INNIES_CODING_STAGE !== "external_runtime_smoke") {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const resultFile = process.env.INNIES_CODING_AGENT_RUN_RESULT_FILE;
|
|
43
|
+
if (!resultFile) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fs.mkdirSync(path.dirname(resultFile), { recursive: true });
|
|
48
|
+
fs.writeFileSync(
|
|
49
|
+
resultFile,
|
|
50
|
+
`${JSON.stringify(
|
|
51
|
+
{
|
|
52
|
+
stage: "external_runtime_smoke",
|
|
53
|
+
status: "success",
|
|
54
|
+
input_metadata_json: {
|
|
55
|
+
workflow_id: process.env.INNIES_CODING_WORKFLOW_ID || null,
|
|
56
|
+
runtime_version: runtimeVersion,
|
|
57
|
+
},
|
|
58
|
+
output_artifact_list: [
|
|
59
|
+
{
|
|
60
|
+
artifact_type: "external_runtime_smoke",
|
|
61
|
+
status: "generated",
|
|
62
|
+
content_json: {
|
|
63
|
+
runtime_version: runtimeVersion,
|
|
64
|
+
},
|
|
65
|
+
metadata_json: {
|
|
66
|
+
workflow_id: process.env.INNIES_CODING_WORKFLOW_ID || null,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
failure_context_json: {},
|
|
71
|
+
},
|
|
72
|
+
null,
|
|
73
|
+
2,
|
|
74
|
+
)}\n`,
|
|
75
|
+
"utf8",
|
|
76
|
+
);
|
|
77
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhiman_innies/innies-codex",
|
|
3
|
-
"version": "0.122.
|
|
3
|
+
"version": "0.122.33",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"bin": {
|
|
6
6
|
"innies": "bin/innies.js"
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"postinstall": "node bin/innies-init.js"
|
|
24
24
|
},
|
|
25
25
|
"optionalDependencies": {
|
|
26
|
-
"@zhiman_innies/innies-codex-darwin-x64": "0.122.
|
|
27
|
-
"@zhiman_innies/innies-codex-darwin-arm64": "0.122.
|
|
28
|
-
"@zhiman_innies/innies-codex-win32-x64": "0.122.
|
|
29
|
-
"@zhiman_innies/innies-codex-win32-arm64": "0.122.
|
|
26
|
+
"@zhiman_innies/innies-codex-darwin-x64": "0.122.33-darwin-x64",
|
|
27
|
+
"@zhiman_innies/innies-codex-darwin-arm64": "0.122.33-darwin-arm64",
|
|
28
|
+
"@zhiman_innies/innies-codex-win32-x64": "0.122.33-win32-x64",
|
|
29
|
+
"@zhiman_innies/innies-codex-win32-arm64": "0.122.33-win32-arm64"
|
|
30
30
|
}
|
|
31
31
|
}
|