@openprd/cli 0.1.18 → 0.1.21
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/.openprd/changes/openprd-control-plane-v020/.openprd.yaml +2 -0
- package/.openprd/changes/openprd-control-plane-v020/design.md +78 -0
- package/.openprd/changes/openprd-control-plane-v020/proposal.md +54 -0
- package/.openprd/changes/openprd-control-plane-v020/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/openprd-control-plane-v020/task-events.jsonl +27 -0
- package/.openprd/changes/openprd-control-plane-v020/tasks-002.md +35 -0
- package/.openprd/changes/openprd-control-plane-v020/tasks.md +427 -0
- package/.openprd/changes/openprd-generated-change/.openprd.yaml +2 -0
- package/.openprd/changes/openprd-generated-change/design.md +50 -0
- package/.openprd/changes/openprd-generated-change/proposal.md +37 -0
- package/.openprd/changes/openprd-generated-change/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/openprd-generated-change/task-events.jsonl +21 -0
- package/.openprd/changes/openprd-generated-change/tasks.md +357 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/.openprd.yaml +2 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/design.md +52 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/proposal.md +35 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/task-events.jsonl +1 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/tasks.md +170 -0
- package/.openprd/design/active/asset-spec.md +19 -14
- package/.openprd/design/active/direction-plan.md +19 -3
- package/.openprd/design/active/facts-sheet.md +16 -7
- package/.openprd/design/active/image-preflight.md +6 -5
- package/.openprd/design/active/review-studio-v020-directions/compare-plan.json +34 -0
- package/.openprd/design/active/review-studio-v020-directions/contact-sheet.jpg +0 -0
- package/.openprd/design/active/review-studio-v020-directions/crops/01.png +0 -0
- package/.openprd/design/active/review-studio-v020-directions/crops/02.png +0 -0
- package/.openprd/design/active/review-studio-v020-directions/crops/03.png +0 -0
- package/.openprd/design/active/review-studio-v020-directions/focus-board.template.json +69 -0
- package/.openprd/design/active/review-studio-v020-directions/parallel-board.template.json +45 -0
- package/.openprd/design/active/review-studio-v020-directions/reference-set.json +143 -0
- package/.openprd/design/active/review-studio-v020-directions/source.png +0 -0
- package/.openprd/design/active/selected-direction.md +23 -9
- package/.openprd/engagements/active/control-plane-architecture.json +203 -0
- package/.openprd/engagements/active/control-plane-intake.json +418 -0
- package/.openprd/engagements/active/prd.md +186 -100
- package/.openprd/engagements/active/review-presentation-v0018.json +176 -0
- package/.openprd/i18n-config.json +12 -0
- package/.openprd/ledger/events.jsonl +6 -0
- package/.openprd/ledger/heads/ebea1a71a9daa566f1c91b53.json +10 -0
- package/.openprd/manifest.json +21 -0
- package/AGENTS.md +1 -0
- package/README.md +22 -0
- package/README_EN.md +27 -0
- package/package.json +1 -1
- package/skills/openprd-frontend-design/SKILL.md +16 -0
- package/skills/openprd-harness/SKILL.md +1 -1
- package/skills/openprd-quality/SKILL.md +1 -1
- package/skills/openprd-requirement-intake/SKILL.md +1 -1
- package/skills/openprd-shared/SKILL.md +1 -1
- package/src/adapters/adapter-spi.js +193 -0
- package/src/adapters/capability-envelope.js +98 -0
- package/src/adapters/event-normalizer.js +55 -0
- package/src/adapters/index.js +4 -0
- package/src/adapters/install-safety.js +249 -0
- package/src/agent-canonical-content.js +6 -2
- package/src/agent-integration.js +147 -42
- package/src/canvas-app-client-app.js +838 -0
- package/src/canvas-app-client-helpers.js +1138 -0
- package/src/canvas-app-styles.js +898 -0
- package/src/canvas-app.html.js +6 -2781
- package/src/canvas-binding.js +414 -0
- package/src/canvas-codex-relay.js +430 -0
- package/src/canvas-i18n.js +8 -0
- package/src/canvas-scene.js +377 -0
- package/src/canvas-session-store.js +148 -0
- package/src/canvas-workspace.js +59 -1066
- package/src/cli/args.js +63 -4
- package/src/cli/gate-print.js +17 -0
- package/src/cli/quality-commands.js +18 -0
- package/src/cli/quality-print.js +10 -0
- package/src/cli/runtime-print.js +24 -0
- package/src/codex-hook-runner-template.mjs +87 -77
- package/src/codex-runtime.js +48 -5
- package/src/context/cache.js +245 -0
- package/src/context/compiler.js +438 -0
- package/src/context/constants.js +30 -0
- package/src/context/index.js +39 -0
- package/src/context/redaction.js +84 -0
- package/src/context/stable.js +69 -0
- package/src/context/telemetry.js +42 -0
- package/src/dev-standards.js +57 -0
- package/src/fleet.js +112 -95
- package/src/gates/index.js +2 -0
- package/src/gates/scoped-gates.js +256 -0
- package/src/gates/store.js +126 -0
- package/src/gates/workspace.js +41 -0
- package/src/html-artifacts.js +725 -28
- package/src/kernel/atomic-store.js +299 -0
- package/src/kernel/event-envelope.js +166 -0
- package/src/kernel/index.js +4 -0
- package/src/kernel/project-ledger.js +467 -0
- package/src/kernel/project-manifest.js +205 -0
- package/src/knowledge-v3/index.js +1 -0
- package/src/knowledge-v3/lifecycle.js +290 -0
- package/src/knowledge.js +14 -7
- package/src/openprd.js +71 -2
- package/src/review-model.js +413 -0
- package/src/review-presentation.js +1 -1
- package/src/run-harness.js +431 -37
- package/src/runtime/cli_runtime_README.md +28 -0
- package/src/runtime/errors.js +66 -0
- package/src/runtime/index.js +44 -0
- package/src/runtime/lane-schema.js +141 -0
- package/src/runtime/lane-store.js +279 -0
- package/src/runtime/task-runtime.js +449 -0
- package/src/runtime/workspace.js +179 -0
- package/src/runtime/write-set.js +206 -0
- package/src/session-binding.js +16 -3
- package/src/session-registry.js +59 -1
- package/src/upgrade/fleet-mutation.js +166 -0
- package/src/upgrade/fleet-transaction.js +398 -0
- package/src/upgrade/transaction-store.js +416 -0
- package/src/visual-compare-core.js +66 -27
- package/src/visual-compare.js +18 -12
- package/src/workspace-core.js +109 -7
- package/src/workspace-registry.js +39 -1
- package/src/workspace-workflow.js +18 -15
package/src/cli/args.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
function parseCommandArgs(argv) {
|
|
21
21
|
const args = [...argv];
|
|
22
|
-
const flags = { json: false, force: false, fix: false, open: false, append: false, init: false, check: false, review: false, reject: false, resume: false, advance: false, verify: false, evidenceRequired: false, next: false, generate: false, validate: false, apply: false, archive: false, activate: false, close: false, keep: false, write: false, dryRun: false, repairAgent: false, allowDirtyMain: false, fleet: false, updateOpenprd: false, backfillWorkUnits: false, syncRegistry: false, setupMissing: false, doctor: false, context: false, recordHook: false, hookInject: false, plan: false, prompt: false, loopRun: false, finish: false, commit: false, html: false, template: false, failOnViolation: false, noExternalFacts: false, noBrandAssets: false, noRealImages: false, daemon: false, stop: false, bridgeOutbox: false, bridgeSent: false, bridgeFailed: false, mark: null, direction: null, autoRefactor: null, type: 'architecture', mode: 'auto', input: null, field: null, value: null, set: null, jsonFile: null, artifactMarkdown: null, contentJson: null, presentation: null, source: null, reference: null, actual: null, before: null, after: null, board: null, grid: null, boxes: null, out: null, format: null, quality: null, maxPanelWidth: null, referenceLabel: null, actualLabel: null, locale: null, classifyExternal: null, maxIterations: null, maxDepth: null, include: null, exclude: null, report: null, item: null, id: null, status: null, claim: null, notes: null, reason: null, confidence: null, threshold: null, change: null, starter: null, sections: null, brief: null, tools: 'all', hookProfile: null, templatePack: null, target: 'openprd', targetRoot: null, path: null, productType: null, title: null, owner: null, problem: null, whyNow: null, evidence: null, from: null, to: null, version: null, digest: null, workUnit: null, event: null, risk: null, outcome: null, preview: null, learn: null, genre: null, style: null, topic: null, enable: false, disable: false, agent: 'codex', agentCommand: null, worktree: null, branch: null, message: null, thread: null, threadTitle: null, conversationTitle: null, session: null, sessionTitle: null, host: null, port: null, openTarget: 'auto' };
|
|
22
|
+
const flags = { json: false, force: false, fix: false, open: false, append: false, init: false, check: false, review: false, reject: false, resume: false, advance: false, verify: false, evidenceRequired: false, next: false, generate: false, validate: false, apply: false, archive: false, activate: false, close: false, keep: false, write: false, dryRun: false, repairAgent: false, allowDirtyMain: false, fleet: false, updateOpenprd: false, backfillWorkUnits: false, syncRegistry: false, setupMissing: false, doctor: false, context: false, recordHook: false, hookInject: false, plan: false, prompt: false, loopRun: false, finish: false, commit: false, html: false, template: false, failOnViolation: false, noExternalFacts: false, noBrandAssets: false, noRealImages: false, daemon: false, stop: false, bridgeOutbox: false, bridgeSent: false, bridgeFailed: false, mark: null, direction: null, autoRefactor: null, type: 'architecture', mode: 'auto', input: null, field: null, value: null, set: null, jsonFile: null, artifactMarkdown: null, contentJson: null, presentation: null, source: null, reference: null, actual: null, before: null, after: null, board: null, grid: null, boxes: null, out: null, format: null, quality: null, maxPanelWidth: null, referenceLabel: null, actualLabel: null, locale: null, classifyExternal: null, maxIterations: null, maxDepth: null, include: null, exclude: null, report: null, item: null, id: null, status: null, claim: null, notes: null, reason: null, confidence: null, threshold: null, change: null, starter: null, sections: null, brief: null, tools: 'all', hookProfile: null, templatePack: null, target: 'openprd', targetRoot: null, path: null, productType: null, title: null, owner: null, problem: null, whyNow: null, evidence: null, from: null, to: null, version: null, digest: null, workUnit: null, event: null, risk: null, outcome: null, preview: null, learn: null, genre: null, style: null, topic: null, enable: false, disable: false, agent: 'codex', agentCommand: null, worktree: null, branch: null, message: null, thread: null, threadTitle: null, conversationTitle: null, session: null, sessionTitle: null, host: null, port: null, openTarget: 'auto', runtimeAction: null, lane: null, task: null, claimId: null, client: null, leaseMs: null, expectedRevision: null, expectedLaneRevision: null, writeSet: null, baseCommit: null, gateId: null, approvalId: null, symlinkStrategy: null, installSafetyBackupRoot: null };
|
|
23
23
|
const positionals = [];
|
|
24
24
|
|
|
25
25
|
while (args.length > 0) {
|
|
@@ -28,6 +28,62 @@ function parseCommandArgs(argv) {
|
|
|
28
28
|
flags.json = true;
|
|
29
29
|
continue;
|
|
30
30
|
}
|
|
31
|
+
if (arg === '--runtime-action' || arg === '--action') {
|
|
32
|
+
flags.runtimeAction = args.shift() ?? null;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (arg === '--lane') {
|
|
36
|
+
flags.lane = args.shift() ?? null;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (arg === '--task') {
|
|
40
|
+
flags.task = args.shift() ?? null;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (arg === '--claim-id') {
|
|
44
|
+
flags.claimId = args.shift() ?? null;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (arg === '--client') {
|
|
48
|
+
flags.client = args.shift() ?? null;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (arg === '--lease-ms') {
|
|
52
|
+
flags.leaseMs = args.shift() ?? null;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (arg === '--expected-revision') {
|
|
56
|
+
flags.expectedRevision = args.shift() ?? null;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (arg === '--expected-lane-revision') {
|
|
60
|
+
flags.expectedLaneRevision = args.shift() ?? null;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (arg === '--write-set') {
|
|
64
|
+
flags.writeSet = args.shift() ?? null;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (arg === '--base-commit') {
|
|
68
|
+
flags.baseCommit = args.shift() ?? null;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (arg === '--gate' || arg === '--gate-id') {
|
|
72
|
+
flags.gateId = args.shift() ?? null;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (arg === '--approval-id') {
|
|
76
|
+
flags.approvalId = args.shift() ?? null;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (arg === '--symlink-strategy') {
|
|
80
|
+
flags.symlinkStrategy = args.shift() ?? null;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (arg === '--install-safety-backup-root') {
|
|
84
|
+
flags.installSafetyBackupRoot = args.shift() ?? null;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
31
87
|
if (arg === '--force') {
|
|
32
88
|
flags.force = true;
|
|
33
89
|
continue;
|
|
@@ -581,14 +637,16 @@ function usage() {
|
|
|
581
637
|
'OpenPrd CLI',
|
|
582
638
|
'',
|
|
583
639
|
'Usage:',
|
|
584
|
-
' openprd init [path] [--template-pack <base|consumer|b2b|agent>] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--force]',
|
|
585
|
-
' openprd setup [path] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--force] [--json]',
|
|
586
|
-
' openprd update [path] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--force] [--json]',
|
|
640
|
+
' openprd init [path] [--template-pack <base|consumer|b2b|agent>] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--symlink-strategy <block|materialize>] [--install-safety-backup-root <path>] [--force]',
|
|
641
|
+
' openprd setup [path] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--symlink-strategy <block|materialize>] [--install-safety-backup-root <path>] [--force] [--json]',
|
|
642
|
+
' openprd update [path] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--symlink-strategy <block|materialize>] [--install-safety-backup-root <path>] [--force] [--json]',
|
|
587
643
|
' openprd self-update [--check] [--dry-run] [--json]',
|
|
588
644
|
' openprd upgrade [path] [--fleet] [--dry-run] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--max-depth <n>] [--include <csv>] [--exclude <csv>] [--report <file>] [--force] [--json]',
|
|
589
645
|
' openprd doctor [path] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--fix] [--json]',
|
|
590
646
|
' openprd fleet <root> [--dry-run|--doctor|--update-openprd|--backfill-work-units|--sync-registry|--setup-missing] [--hook-profile <lite|guarded|full>] [--max-depth <n>] [--include <csv>] [--exclude <csv>] [--report <file>] [--json]',
|
|
591
647
|
' openprd run [path] [--context|--verify|--record-hook --event <name> --risk <level> --outcome <text> --preview <text>] [--message <text>] [--json]',
|
|
648
|
+
' openprd runtime [path] --action <status|create-lane|ensure-lane|add-task|claim|heartbeat|release|finish|recover> [--lane <id>] [--task <id>] [--claim-id <id>] [--write-set <csv>] [--lease-ms <n>] [--expected-revision <n>] [--expected-lane-revision <n>] [--client <codex|claude|cursor>] [--session <id>] [--thread <id>] [--worktree <path>] [--branch <name>] [--base-commit <sha>] [--evidence <ref>] [--json]',
|
|
649
|
+
' openprd gate [path] --action <status|define|evidence|approve|evaluate> [--json-file <record.json>] [--gate <id>] [--approval-id <id>] [--json]',
|
|
592
650
|
' openprd loop [path] [--init|--plan|--next|--prompt|--run|--verify|--finish] [--change <id>] [--item <task-id-or-handle>] [--agent <codex|claude>] [--agent-command <cmd>] [--repair-agent] [--worktree <path>] [--branch <name>] [--allow-dirty-main] [--commit] [--dry-run] [--message <text>] [--json]',
|
|
593
651
|
' openprd canvas [path] [--thread <id> [--thread-title <name>]|--session <id> [--session-title <name>]] [--host <host>] [--port <number>] [--daemon|--stop] [--open] [--open-target <auto|system|codex-browser>] [--dry-run] [--json]',
|
|
594
652
|
' openprd canvas [path] --bridge-outbox [--thread <id>|--session <id>] [--claim <next|id>] [--json]',
|
|
@@ -607,6 +665,7 @@ function usage() {
|
|
|
607
665
|
' --board modes: focus-board, parallel-board, verification-board, alignment-board, centering-board',
|
|
608
666
|
' openprd design-starter [path] [--starter <content-home|product-launch|ops-dashboard>] [--out <index.html>] [--title <name>] [--brief <text>] [--sections <a|b|c>] [--no-external-facts] [--no-brand-assets] [--no-real-images] [--force] [--json]',
|
|
609
667
|
' openprd dev-check [path] <file...> [--auto-refactor <on|off>] [--json]',
|
|
668
|
+
' openprd i18n-init [path] [--locale <zh-Hans|en|...>] [--json]',
|
|
610
669
|
' openprd grow [path] [--review|--apply --id <candidate-id>|--reject --id <candidate-id>|--init|--check] [--notes <text>] [--json]',
|
|
611
670
|
' openprd benchmark <add|observe|list|approve|verify> [target-or-id] [path-for-list-or-verify] [--path <project>] [--notes <text>] [--threshold <n>] [--id <benchmark-id>] [--json]',
|
|
612
671
|
' openprd synthesize [path] [--title <text>] [--owner <text>] [--problem <text>] [--why-now <text>] [--work-unit <id>] [--target-root <path>] [--open] [--json]',
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
function printGateResult(result, json) {
|
|
2
|
+
if (json) {
|
|
3
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
if (!result.ok) {
|
|
7
|
+
console.log(`OpenPrd 操作门禁:未完成(${result.error?.code ?? 'unknown'})`);
|
|
8
|
+
console.log(result.error?.message ?? '门禁操作失败。');
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const status = result.result?.status;
|
|
12
|
+
console.log(`OpenPrd 操作门禁:${status === 'pass' ? '通过' : status === 'blocked' ? '已阻断' : '已记录'}`);
|
|
13
|
+
if (result.result?.decisionId) console.log(`决策编号:${result.result.decisionId}`);
|
|
14
|
+
for (const reason of result.result?.reasons ?? []) console.log(`- ${reason.code}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { printGateResult };
|
|
@@ -218,5 +218,23 @@ export async function runQualityCommand(command, { projectPath, flags, positiona
|
|
|
218
218
|
return result.ok ? 0 : 1;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
if (command === 'i18n-init') {
|
|
222
|
+
const initProjectPath = path.resolve(flags.path ?? positionals[0] ?? process.cwd());
|
|
223
|
+
const { initI18nConfig } = await import('../dev-standards.js');
|
|
224
|
+
const result = await initI18nConfig(initProjectPath, {
|
|
225
|
+
defaultLanguage: flags.locale ?? undefined,
|
|
226
|
+
});
|
|
227
|
+
if (flags.json) {
|
|
228
|
+
console.log(JSON.stringify(result, null, 2));
|
|
229
|
+
} else {
|
|
230
|
+
console.log(`OpenPrd i18n: ${result.created ? '已创建' : '配置已存在'} ${result.configPath}`);
|
|
231
|
+
console.log(`基准语言: ${result.config.defaultLanguage}`);
|
|
232
|
+
console.log(`支持语种: ${result.config.supportedLanguages.join(', ')}`);
|
|
233
|
+
console.log(`dev-check 检查: ${result.config.enforceOnDevCheck ? '开启' : '关闭'}`);
|
|
234
|
+
console.log(`最佳实践文档: docs/basic/i18n-guidelines.md`);
|
|
235
|
+
}
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
238
|
+
|
|
221
239
|
return null;
|
|
222
240
|
}
|
package/src/cli/quality-print.js
CHANGED
|
@@ -123,6 +123,16 @@ function printDevelopmentStandardsResult(result, json) {
|
|
|
123
123
|
} else if (result.visualEvidence?.uiFiles?.length > 0) {
|
|
124
124
|
console.log(`- 视觉证据: 最近 24 小时已有证据板(${result.visualEvidence.freshEvidence.join(', ')});确认覆盖本轮改动并把证据图嵌入对话流。`);
|
|
125
125
|
}
|
|
126
|
+
if (result.i18nReminder?.required) {
|
|
127
|
+
console.log(`- 多语言提醒: ${result.i18nReminder.reason}`);
|
|
128
|
+
console.log(` 涉及文件: ${result.i18nReminder.textFiles.join(', ')}`);
|
|
129
|
+
if (result.i18nReminder.guideline) {
|
|
130
|
+
console.log(` 最佳实践: ${result.i18nReminder.guideline}`);
|
|
131
|
+
}
|
|
132
|
+
if (!result.i18nReminder.configExists) {
|
|
133
|
+
console.log(' 建议: 创建 .openprd/i18n-config.json 启用项目级多语言检查。');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
126
136
|
printKnowledgeReview(result.knowledgeReview);
|
|
127
137
|
for (const error of result.errors ?? []) {
|
|
128
138
|
console.log(`- ${error}`);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
function printTaskRuntimeResult(result, json) {
|
|
2
|
+
if (json) {
|
|
3
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
if (!result.ok) {
|
|
7
|
+
console.log(`OpenPrd 并行任务运行时:未完成(${result.error?.code ?? 'unknown'})`);
|
|
8
|
+
console.log(result.error?.message ?? '运行时操作失败。');
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
console.log(`OpenPrd 并行任务运行时:${result.action.replace(/^runtime-/, '')}`);
|
|
12
|
+
if (result.laneId) console.log(`处理通道:${result.laneId}`);
|
|
13
|
+
if (result.taskId) console.log(`任务:${result.taskId}`);
|
|
14
|
+
const task = result.result?.task;
|
|
15
|
+
if (task) {
|
|
16
|
+
console.log(`状态:${task.status}`);
|
|
17
|
+
console.log(`修订:${task.revision}`);
|
|
18
|
+
if (task.claim?.leaseExpiresAt) console.log(`占用有效期至:${task.claim.leaseExpiresAt}`);
|
|
19
|
+
} else if (Array.isArray(result.result)) {
|
|
20
|
+
console.log(`处理通道数:${result.result.length}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { printTaskRuntimeResult };
|
|
@@ -765,45 +765,22 @@ function detectExecutionSignals(eventName, payload) {
|
|
|
765
765
|
};
|
|
766
766
|
}
|
|
767
767
|
|
|
768
|
-
function latestSessionRegistryEntry(root, sessionId) {
|
|
769
|
-
if (!sessionId) {
|
|
770
|
-
return null;
|
|
771
|
-
}
|
|
772
|
-
const filePath = sessionRegistryPath();
|
|
773
|
-
if (!fs.existsSync(filePath)) {
|
|
774
|
-
return null;
|
|
775
|
-
}
|
|
776
|
-
const workspaceRoot = path.resolve(root);
|
|
777
|
-
let latest = null;
|
|
778
|
-
for (const line of fs.readFileSync(filePath, 'utf8').split(/\r?\n/)) {
|
|
779
|
-
if (!line.trim()) {
|
|
780
|
-
continue;
|
|
781
|
-
}
|
|
782
|
-
try {
|
|
783
|
-
const entry = JSON.parse(line);
|
|
784
|
-
if (entry?.sessionId !== sessionId) {
|
|
785
|
-
continue;
|
|
786
|
-
}
|
|
787
|
-
const entryRoot = entry?.workspaceRoot ? path.resolve(String(entry.workspaceRoot)) : null;
|
|
788
|
-
if (entryRoot && entryRoot !== workspaceRoot) {
|
|
789
|
-
continue;
|
|
790
|
-
}
|
|
791
|
-
latest = entry;
|
|
792
|
-
} catch {}
|
|
793
|
-
}
|
|
794
|
-
return latest;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
768
|
function rememberAutomationMode(root, sessionId, payload, signals, laneKind) {
|
|
798
769
|
if (!sessionId) {
|
|
799
770
|
return null;
|
|
800
771
|
}
|
|
801
|
-
|
|
772
|
+
const gateStatus = laneKind === 'automation' ? 'automation-safe-mode' : 'openprd-opt-in';
|
|
773
|
+
const existing = readSessionBinding(root, sessionId);
|
|
774
|
+
// 每个 hook 事件都会经过这里;同一会话的 lane 记忆只需要落一次盘。
|
|
775
|
+
if (existing?.laneKind === laneKind && existing?.gateStatus === gateStatus) {
|
|
776
|
+
return existing;
|
|
777
|
+
}
|
|
778
|
+
return writeSessionBinding(root, sessionId, {
|
|
802
779
|
laneKind,
|
|
803
780
|
tool: 'openprd-hook',
|
|
804
781
|
promptPreview: preview(promptText(payload) || payloadText(payload), 500),
|
|
805
782
|
gateActive: false,
|
|
806
|
-
gateStatus
|
|
783
|
+
gateStatus,
|
|
807
784
|
title: laneKind === 'automation'
|
|
808
785
|
? 'Unattended automation bypassed OpenPrd'
|
|
809
786
|
: 'Unattended automation explicitly enabled OpenPrd',
|
|
@@ -820,7 +797,7 @@ function automationBypassDecision(root, eventName, payload, sessionId) {
|
|
|
820
797
|
if (signals.isAutomation) {
|
|
821
798
|
return { bypass: true, optIn: false, remembered: false, signals };
|
|
822
799
|
}
|
|
823
|
-
const remembered = eventName === 'UserPromptSubmit' ? null :
|
|
800
|
+
const remembered = eventName === 'UserPromptSubmit' ? null : readSessionBinding(root, sessionId);
|
|
824
801
|
if (remembered?.laneKind === 'automation' && remembered?.gateStatus === 'automation-safe-mode') {
|
|
825
802
|
return { bypass: true, optIn: false, remembered: true, signals };
|
|
826
803
|
}
|
|
@@ -912,44 +889,68 @@ function readSessionBinding(root, sessionId = null) {
|
|
|
912
889
|
return readJsonSync(sessionBindingPath(root, sessionId), null);
|
|
913
890
|
}
|
|
914
891
|
|
|
915
|
-
function
|
|
892
|
+
function appendJsonLineAtomic(filePath, value) {
|
|
916
893
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
917
|
-
fs.
|
|
894
|
+
const fd = fs.openSync(filePath, 'a', 0o600);
|
|
895
|
+
try {
|
|
896
|
+
fs.writeSync(fd, `${JSON.stringify(value)}\n`);
|
|
897
|
+
fs.fsyncSync(fd);
|
|
898
|
+
} finally {
|
|
899
|
+
fs.closeSync(fd);
|
|
900
|
+
}
|
|
918
901
|
}
|
|
919
902
|
|
|
920
|
-
function
|
|
903
|
+
function sessionRegistryProjection(entry = {}) {
|
|
904
|
+
return {
|
|
905
|
+
sessionId: entry.sessionId ?? null,
|
|
906
|
+
workspaceRoot: entry.workspaceRoot ?? null,
|
|
907
|
+
laneKind: entry.laneKind ?? 'requirement',
|
|
908
|
+
tool: entry.tool ?? 'codex',
|
|
909
|
+
threadId: entry.threadId ?? null,
|
|
910
|
+
changeId: entry.changeId ?? null,
|
|
911
|
+
taskHandle: entry.taskHandle ?? null,
|
|
912
|
+
workUnitId: entry.workUnitId ?? null,
|
|
913
|
+
versionId: entry.versionId ?? null,
|
|
914
|
+
digest: entry.digest ?? null,
|
|
915
|
+
title: entry.title ?? null,
|
|
916
|
+
targetRoot: entry.targetRoot ?? null,
|
|
917
|
+
promptPreview: entry.promptPreview ?? null,
|
|
918
|
+
reviewStatus: entry.reviewStatus ?? null,
|
|
919
|
+
gateStatus: entry.gateStatus ?? null,
|
|
920
|
+
gateActive: entry.gateActive === true,
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function writeSessionRegistryEntry(root, sessionId, binding, previous = null) {
|
|
921
925
|
if (!sessionId) {
|
|
922
926
|
return null;
|
|
923
927
|
}
|
|
924
928
|
const workspaceRoot = path.resolve(root);
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
version: 1,
|
|
929
|
+
const projection = sessionRegistryProjection({
|
|
930
|
+
...binding,
|
|
928
931
|
sessionId,
|
|
929
932
|
workspaceRoot,
|
|
933
|
+
});
|
|
934
|
+
const previousProjection = previous
|
|
935
|
+
? sessionRegistryProjection({ ...previous, sessionId, workspaceRoot })
|
|
936
|
+
: null;
|
|
937
|
+
if (previousProjection && JSON.stringify(previousProjection) === JSON.stringify(projection)) {
|
|
938
|
+
return null;
|
|
939
|
+
}
|
|
940
|
+
const recordedAt = binding.updatedAt || now();
|
|
941
|
+
const entry = {
|
|
942
|
+
version: 1,
|
|
943
|
+
...projection,
|
|
930
944
|
realpath: workspaceRoot,
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
taskHandle: patch.taskHandle || null,
|
|
936
|
-
workUnitId: patch.workUnitId || null,
|
|
937
|
-
versionId: patch.versionId || null,
|
|
938
|
-
digest: patch.digest || null,
|
|
939
|
-
title: patch.title || null,
|
|
940
|
-
targetRoot: patch.targetRoot ? path.resolve(patch.targetRoot) : null,
|
|
941
|
-
promptPreview: patch.promptPreview || null,
|
|
942
|
-
reviewStatus: patch.reviewStatus || null,
|
|
943
|
-
gateStatus: patch.gateStatus || null,
|
|
944
|
-
gateActive: patch.gateActive === true,
|
|
945
|
-
statePath: patch.statePath || sessionStatePath(root, sessionId),
|
|
946
|
-
bindingPath: patch.bindingPath || sessionBindingPath(root, sessionId),
|
|
947
|
-
firstRegisteredAt: patch.firstRegisteredAt || recordedAt,
|
|
945
|
+
targetRoot: projection.targetRoot ? path.resolve(projection.targetRoot) : null,
|
|
946
|
+
statePath: sessionStatePath(root, sessionId),
|
|
947
|
+
bindingPath: sessionBindingPath(root, sessionId),
|
|
948
|
+
firstRegisteredAt: previous?.createdAt || binding.createdAt || recordedAt,
|
|
948
949
|
lastRegisteredAt: recordedAt,
|
|
949
|
-
lastUpdatedAt:
|
|
950
|
+
lastUpdatedAt: recordedAt,
|
|
950
951
|
recordedAt,
|
|
951
952
|
};
|
|
952
|
-
|
|
953
|
+
appendJsonLineAtomic(sessionRegistryPath(), entry);
|
|
953
954
|
return entry;
|
|
954
955
|
}
|
|
955
956
|
|
|
@@ -968,25 +969,7 @@ function writeSessionBinding(root, sessionId, patch = {}) {
|
|
|
968
969
|
updatedAt: patch.updatedAt ?? now(),
|
|
969
970
|
};
|
|
970
971
|
writeJsonSync(filePath, next);
|
|
971
|
-
writeSessionRegistryEntry(root, sessionId,
|
|
972
|
-
laneKind: patch.laneKind ?? previous?.laneKind ?? 'requirement',
|
|
973
|
-
tool: patch.tool ?? previous?.tool ?? 'codex',
|
|
974
|
-
threadId: patch.threadId ?? previous?.threadId ?? null,
|
|
975
|
-
changeId: patch.changeId ?? next.changeId ?? null,
|
|
976
|
-
taskHandle: patch.taskHandle ?? next.taskHandle ?? null,
|
|
977
|
-
workUnitId: patch.workUnitId ?? next.workUnitId ?? null,
|
|
978
|
-
versionId: patch.versionId ?? next.versionId ?? null,
|
|
979
|
-
digest: patch.digest ?? next.digest ?? null,
|
|
980
|
-
title: patch.title ?? next.title ?? null,
|
|
981
|
-
targetRoot: patch.targetRoot ?? next.targetRoot ?? null,
|
|
982
|
-
promptPreview: patch.promptPreview ?? next.promptPreview ?? null,
|
|
983
|
-
reviewStatus: patch.reviewStatus ?? next.reviewStatus ?? null,
|
|
984
|
-
gateStatus: patch.gateStatus ?? next.gateStatus ?? null,
|
|
985
|
-
gateActive: patch.gateActive ?? next.gateActive ?? false,
|
|
986
|
-
bindingPath: filePath,
|
|
987
|
-
statePath: sessionStatePath(root, sessionId),
|
|
988
|
-
updatedAt: next.updatedAt,
|
|
989
|
-
});
|
|
972
|
+
writeSessionRegistryEntry(root, sessionId, next, previous);
|
|
990
973
|
return next;
|
|
991
974
|
}
|
|
992
975
|
|
|
@@ -1561,6 +1544,9 @@ function knowledgeSkillContextLines(knowledgeSkills) {
|
|
|
1561
1544
|
}
|
|
1562
1545
|
|
|
1563
1546
|
function renderRunContextText(result) {
|
|
1547
|
+
if (String(result?.contextText || '').trim()) {
|
|
1548
|
+
return String(result.contextText).trim();
|
|
1549
|
+
}
|
|
1564
1550
|
const lines = [
|
|
1565
1551
|
'当前进展参考',
|
|
1566
1552
|
'当前项目: ' + result.projectRoot,
|
|
@@ -1935,6 +1921,8 @@ function analyzePromptIntent(prompt) {
|
|
|
1935
1921
|
&& !codeVisualArtifactRequested;
|
|
1936
1922
|
const learningMaterialIntent = detectLearningMaterialIntent(text);
|
|
1937
1923
|
const learningMaterialRequest = learningMaterialIntent.requested;
|
|
1924
|
+
const canvasCollaborationRequest = /(画布|canvas|excalidraw)/i.test(text)
|
|
1925
|
+
&& /(标注|批注|协同|改图|回填|贴到画布|放到画布|插入画布|插到画布|画布评审|画布反馈|一起看|openprd\s*canvas|annotation)/i.test(text);
|
|
1938
1926
|
const visualReview = /效果图|实现截图|视觉对比|视觉评审|对标效果图|复刻|不一致|好丑|没对齐|对不上|不像/i.test(text);
|
|
1939
1927
|
const directBugfixExecution = explicitExecution && bugfixOrDiagnostic;
|
|
1940
1928
|
const newFeatureVerbMatched = /(新增|增加|新建)/.test(text);
|
|
@@ -2043,6 +2031,7 @@ function analyzePromptIntent(prompt) {
|
|
|
2043
2031
|
learningMaterialRequest,
|
|
2044
2032
|
learningGenre: learningMaterialIntent.genre,
|
|
2045
2033
|
learningMaterialReason: learningMaterialIntent.reason,
|
|
2034
|
+
canvasCollaborationRequest,
|
|
2046
2035
|
shouldInject: requiresIntake
|
|
2047
2036
|
|| requirementTier === 'l1'
|
|
2048
2037
|
|| explicitExecution
|
|
@@ -2059,6 +2048,7 @@ function analyzePromptIntent(prompt) {
|
|
|
2059
2048
|
|| browserSafetyRequest
|
|
2060
2049
|
|| productCopyRequest
|
|
2061
2050
|
|| learningMaterialRequest
|
|
2051
|
+
|| canvasCollaborationRequest
|
|
2062
2052
|
|| /openprd/i.test(normalized)
|
|
2063
2053
|
|| /\bprd\b/i.test(normalized),
|
|
2064
2054
|
};
|
|
@@ -3461,6 +3451,21 @@ function visualMockupMessage(intent, root = null) {
|
|
|
3461
3451
|
].join('\n');
|
|
3462
3452
|
}
|
|
3463
3453
|
|
|
3454
|
+
function canvasCollaborationMessage(intent) {
|
|
3455
|
+
if (!intent?.canvasCollaborationRequest) {
|
|
3456
|
+
return null;
|
|
3457
|
+
}
|
|
3458
|
+
return [
|
|
3459
|
+
'当前请求涉及 OpenPrd 画布协同(生图上画布、人在画布上标注、标注结果回到对话继续改)。',
|
|
3460
|
+
'打开或复用画布:运行 `openprd canvas .`;无法自动识别当前对话时用 `--thread <id> --thread-title <name>` 或 `--session <id> --session-title <name>` 显式绑定,画布必须绑定当前对话。',
|
|
3461
|
+
'结构化读画布:需要知道用户正在圈哪些元素时,优先 `GET /api/selection` 读取选中元素的 ID、类型、坐标、尺寸和文本,不要只靠截图猜;需要全量场景再读 `GET /api/scene`。',
|
|
3462
|
+
'结构化写画布:把生成的新图放上画布时,优先 `POST /api/insert-image`,用 `anchorElementId` + `placement`(right/left/below/above)把新图放到指定元素旁边,不要覆盖原图;回填 AI 生图占位卡时继续用 `POST /api/images` 带 `placeholderId`。',
|
|
3463
|
+
'标注改图闭环:如果交接记录(handoff)的 `selection` 是 `annotation-edit`,或带有 `sourceImageElementIds` / `annotationElementIds`,说明用户在原图上画了标注要求改图。把截图里的标注当修改指令,生成修改后的新图,再用 `POST /api/insert-image` 以原图为 anchor 放到旁边,方便用户对比原图和新版。',
|
|
3464
|
+
'生图尺寸契约:handoff 或占位卡带 `sizeContract`(width/height/aspectRatio)时,生成图片必须按这个宽高比出图,避免回填时变形拉伸。',
|
|
3465
|
+
'每轮改图后主动确认:新图是否符合标注意图、是否继续下一轮标注迭代;用户可以继续在新图上标注,形成多轮改图闭环。',
|
|
3466
|
+
].join('\n');
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3464
3469
|
function learningGenreFlag(intent = null) {
|
|
3465
3470
|
return intent?.learningGenre ? ` --genre ${intent.learningGenre}` : '';
|
|
3466
3471
|
}
|
|
@@ -3488,7 +3493,9 @@ function largeUiVisualDirectionMessage() {
|
|
|
3488
3493
|
'OpenPrd 大界面改动视觉方案评审:',
|
|
3489
3494
|
'位置: 需求分流之后、PRD 定稿或实现开工之前;它不同于 review.html,也不同于实现后的 visual-compare。',
|
|
3490
3495
|
'判断: 先从用户目标、信息架构变化、视觉决策成本和验收风险判断是否需要方向评审,不用关键词触发。会决定首屏、核心布局、主视觉、关键路径、组件层级/密度,或用户需要先选设计方向时触发。',
|
|
3491
|
-
'
|
|
3496
|
+
'自动性: 用户不需要额外提出“请生图”。只要语义判断命中大界面改动或视觉决策成本高,就主动完成 3 方向生成;卡片宽度、间距、对齐、颜色、圆角、字号等轻量微调不触发。',
|
|
3497
|
+
'步骤: 已有界面时,Codex 原生 App 用 Computer Use,Codex 网页环境用 Chrome 插件的任务专用窗口,Cursor 用当前可用的浏览器或项目预览能力,进入对应功能并截当前真实界面。三个方向必须把同一张截图作为 image-to-image 参考;除非用户明确要求换风格,否则锁定现有字体角色、色彩系统、明暗模式、表面语言、圆角/边框/阴影、图标语言、平台框架和信息密度,只探索布局、信息层级、导航、关键路径和空间构图。冷启动没有现有界面时,基于已确认 PRD、用户画像、第一版切片和视觉目标写 design brief,不伪造修改前截图。brief 必须写清用途、受众、气质端点、约束和记忆点,3 个方向要分别说明审美主张和主动避开的模板味。然后按生图路由调用内置生图工具(Codex=`imagegen`/Image 2,Cursor=`GenerateImage`;都不可用时按 `.openprd/harness/image-generation-preference.json` 的用户已确认偏好,没有偏好先问用户)生成至少 3 个不同设计思想方向;把效果图横向拼成一张带 1/2/3 序号的大图,先作为候选效果图给用户评审,不要默认写入 `.openprd/harness/visual-reviews/` 当验收参考。',
|
|
3498
|
+
'质量: 每个方向都要从用途、用户、业务约束、核心动作和唯一记忆点出发,同时检查字体角色、色彩、空间构图、信息层级、表面/图标语言和动效含义;拒绝无依据蓝紫渐变、默认字体、等权白卡堆叠、过量大圆角、无语境装饰和只换颜色的伪方向。',
|
|
3492
3499
|
'交互: 主动追问三件事:是否符合预期、是否纳入后续效果图/实现截图对比、是否按此继续后续实现。只有用户确认纳入后续对比或继续实现后,才把选定方向、整张图或其中子图整理成 reference-set 并写入 `.openprd/harness/visual-reviews/`;用户确认前,不进入大 UI 实现,也不要声称界面方案已定。',
|
|
3493
3500
|
'多对象参考处理: 如果候选效果图是一张图里包含多个子图、网格、多对象或多方向,不要拿整板直接硬比;先运行 `openprd visual-prepare . --reference <效果图> --grid <列>x<行>` 或 `--boxes <plan.json>`,检查 contact sheet,确认 reference-set 后再逐项进入实现和验收。',
|
|
3494
3501
|
].join('\n');
|
|
@@ -3651,6 +3658,7 @@ function contextMessage(cwd, intent = null, gate = null, progress = null) {
|
|
|
3651
3658
|
'OpenPrd 上下文只是建议,不是自动执行指令。请先判断用户当前意图。',
|
|
3652
3659
|
lightweightRequirementMessage(intent),
|
|
3653
3660
|
visualMockupMessage(intent, cwd),
|
|
3661
|
+
canvasCollaborationMessage(intent),
|
|
3654
3662
|
learningReviewMessage(intent),
|
|
3655
3663
|
largeUiVisualDirectionMessage(),
|
|
3656
3664
|
visualLocalAdjustmentMessage(),
|
|
@@ -3673,6 +3681,7 @@ function contextMessage(cwd, intent = null, gate = null, progress = null) {
|
|
|
3673
3681
|
'OpenPrd 上下文只是建议,不是自动执行指令。请先判断用户当前意图。',
|
|
3674
3682
|
lightweightRequirementMessage(intent),
|
|
3675
3683
|
visualMockupMessage(intent, cwd),
|
|
3684
|
+
canvasCollaborationMessage(intent),
|
|
3676
3685
|
learningReviewMessage(intent),
|
|
3677
3686
|
largeUiVisualDirectionMessage(),
|
|
3678
3687
|
visualLocalAdjustmentMessage(),
|
|
@@ -3700,6 +3709,7 @@ function contextMessage(cwd, intent = null, gate = null, progress = null) {
|
|
|
3700
3709
|
gateMessage,
|
|
3701
3710
|
lightweightRequirementMessage(intent),
|
|
3702
3711
|
visualMockupMessage(intent, cwd),
|
|
3712
|
+
canvasCollaborationMessage(intent),
|
|
3703
3713
|
learningReviewMessage(intent),
|
|
3704
3714
|
largeUiVisualDirectionMessage(intent),
|
|
3705
3715
|
requirementRoutingSummary(),
|
package/src/codex-runtime.js
CHANGED
|
@@ -89,6 +89,29 @@ function runProcess(command, args = [], options = {}) {
|
|
|
89
89
|
});
|
|
90
90
|
let stdout = '';
|
|
91
91
|
let stderr = '';
|
|
92
|
+
let settled = false;
|
|
93
|
+
let timedOut = false;
|
|
94
|
+
const timeoutMs = Number.isFinite(options.timeoutMs)
|
|
95
|
+
? Math.max(1, Number(options.timeoutMs))
|
|
96
|
+
: 15000;
|
|
97
|
+
const killGraceMs = Number.isFinite(options.killGraceMs)
|
|
98
|
+
? Math.max(1, Number(options.killGraceMs))
|
|
99
|
+
: 1000;
|
|
100
|
+
let killTimer = null;
|
|
101
|
+
const timeoutTimer = setTimeout(() => {
|
|
102
|
+
timedOut = true;
|
|
103
|
+
child.kill('SIGTERM');
|
|
104
|
+
killTimer = setTimeout(() => child.kill('SIGKILL'), killGraceMs);
|
|
105
|
+
killTimer.unref?.();
|
|
106
|
+
}, timeoutMs);
|
|
107
|
+
timeoutTimer.unref?.();
|
|
108
|
+
const finish = (payload) => {
|
|
109
|
+
if (settled) return;
|
|
110
|
+
settled = true;
|
|
111
|
+
clearTimeout(timeoutTimer);
|
|
112
|
+
if (killTimer) clearTimeout(killTimer);
|
|
113
|
+
resolve(payload);
|
|
114
|
+
};
|
|
92
115
|
child.stdout.on('data', (chunk) => {
|
|
93
116
|
stdout = `${stdout}${chunk}`.slice(-64000);
|
|
94
117
|
});
|
|
@@ -96,7 +119,7 @@ function runProcess(command, args = [], options = {}) {
|
|
|
96
119
|
stderr = `${stderr}${chunk}`.slice(-64000);
|
|
97
120
|
});
|
|
98
121
|
child.on('error', (error) => {
|
|
99
|
-
|
|
122
|
+
finish({
|
|
100
123
|
ok: false,
|
|
101
124
|
command,
|
|
102
125
|
args,
|
|
@@ -104,19 +127,22 @@ function runProcess(command, args = [], options = {}) {
|
|
|
104
127
|
exitCode: null,
|
|
105
128
|
stdout,
|
|
106
129
|
stderr,
|
|
130
|
+
timedOut,
|
|
107
131
|
error: error.message,
|
|
108
132
|
});
|
|
109
133
|
});
|
|
110
|
-
child.on('close', (exitCode) => {
|
|
111
|
-
|
|
112
|
-
ok: exitCode === 0,
|
|
134
|
+
child.on('close', (exitCode, signal) => {
|
|
135
|
+
finish({
|
|
136
|
+
ok: !timedOut && exitCode === 0,
|
|
113
137
|
command,
|
|
114
138
|
args,
|
|
115
139
|
display: invocation.display,
|
|
116
140
|
exitCode,
|
|
141
|
+
signal,
|
|
117
142
|
stdout,
|
|
118
143
|
stderr,
|
|
119
|
-
|
|
144
|
+
timedOut,
|
|
145
|
+
error: timedOut ? `Command timed out after ${timeoutMs}ms.` : null,
|
|
120
146
|
});
|
|
121
147
|
});
|
|
122
148
|
});
|
|
@@ -131,6 +157,8 @@ function normalizeRunResult(command, args, result = {}) {
|
|
|
131
157
|
exitCode: result.exitCode ?? result.status ?? null,
|
|
132
158
|
stdout: String(result.stdout ?? ''),
|
|
133
159
|
stderr: String(result.stderr ?? ''),
|
|
160
|
+
timedOut: Boolean(result.timedOut),
|
|
161
|
+
signal: result.signal ? String(result.signal) : null,
|
|
134
162
|
error: result.error ? String(result.error) : null,
|
|
135
163
|
};
|
|
136
164
|
}
|
|
@@ -178,6 +206,17 @@ export function diagnoseCodexVersionFailure(result, options = {}) {
|
|
|
178
206
|
};
|
|
179
207
|
}
|
|
180
208
|
|
|
209
|
+
if (result.timedOut) {
|
|
210
|
+
return {
|
|
211
|
+
type: 'version-check-timeout',
|
|
212
|
+
summary: `Codex CLI 健康检查超时:codex --version 在 ${options.timeoutMs ?? 15000}ms 内没有返回。`,
|
|
213
|
+
missingPackage: null,
|
|
214
|
+
repairCommand,
|
|
215
|
+
manualCommand: repairCommand.display,
|
|
216
|
+
output,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
181
220
|
if (
|
|
182
221
|
result.exitCode === null
|
|
183
222
|
&& /(ENOENT|not found|command not found|no such file|spawn .*enoent)/i.test(output)
|
|
@@ -212,6 +251,8 @@ export async function checkCodexCliHealth(options = {}) {
|
|
|
212
251
|
await runner(command, args, {
|
|
213
252
|
cwd: options.cwd,
|
|
214
253
|
env: options.env ?? process.env,
|
|
254
|
+
timeoutMs: options.timeoutMs ?? 15000,
|
|
255
|
+
killGraceMs: options.killGraceMs,
|
|
215
256
|
}),
|
|
216
257
|
);
|
|
217
258
|
const repairCommand = buildCodexRepairCommand(options);
|
|
@@ -257,6 +298,8 @@ export async function repairCodexCli(options = {}) {
|
|
|
257
298
|
await runner(commandInfo.command, commandInfo.args, {
|
|
258
299
|
cwd: options.cwd,
|
|
259
300
|
env: options.env ?? process.env,
|
|
301
|
+
timeoutMs: options.repairTimeoutMs ?? 300000,
|
|
302
|
+
killGraceMs: options.killGraceMs,
|
|
260
303
|
}),
|
|
261
304
|
);
|
|
262
305
|
const recheck = result.ok ? await checkCodexCliHealth(options) : null;
|