@zhiman_innies/innies-codex 0.122.31 → 0.122.32
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/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
|
@@ -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.32",
|
|
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.32-darwin-x64",
|
|
27
|
+
"@zhiman_innies/innies-codex-darwin-arm64": "0.122.32-darwin-arm64",
|
|
28
|
+
"@zhiman_innies/innies-codex-win32-x64": "0.122.32-win32-x64",
|
|
29
|
+
"@zhiman_innies/innies-codex-win32-arm64": "0.122.32-win32-arm64"
|
|
30
30
|
}
|
|
31
31
|
}
|