@chenmk/superflow 0.4.0 → 0.5.1
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/INSTALL.en.md +58 -12
- package/INSTALL.md +62 -33
- package/README.md +181 -54
- package/README.zh-CN.md +158 -51
- package/assets/brand/managed-review-loop.png +0 -0
- package/assets/manifest.json +10 -5
- package/assets/scripts/superflow-coding-ready.mjs +135 -0
- package/assets/scripts/superflow-dependency-update-hook.sh +81 -8
- package/assets/scripts/superflow-document-audit.mjs +240 -0
- package/assets/scripts/superflow-environment-preflight.mjs +398 -0
- package/assets/scripts/superflow-hook-guard.sh +40 -1
- package/assets/scripts/superflow-managed-command-guard.py +309 -0
- package/assets/scripts/superflow-managed-executor-preflight.mjs +421 -0
- package/assets/scripts/superflow-managed-owner-verification.sh +117 -0
- package/assets/scripts/superflow-managed-work-check.mjs +287 -26
- package/assets/scripts/superflow-managed-work-guard.sh +6 -92
- package/assets/skills/superflow-clarify/SKILL.md +14 -0
- package/assets/skills/superflow-design/SKILL.md +38 -13
- package/assets/skills/superflow-docs/SKILL.md +93 -2
- package/assets/skills/superflow-pipeline/SKILL.md +167 -10
- package/assets/skills/superflow-pipeline/references/batch-prompt-template.md +17 -9
- package/assets/skills/superflow-pipeline/references/document-readiness.md +157 -0
- package/assets/skills/superflow-pipeline/references/document-templates.md +9 -5
- package/assets/skills/superflow-pipeline/references/feature-gated-workflow.md +14 -0
- package/assets/skills/superflow-pipeline/references/implementation-prompt-template.md +20 -11
- package/assets/skills/superflow-pipeline/references/managed-work.md +123 -15
- package/assets/skills/superflow-pipeline/references/orchestration.md +2 -2
- package/assets/skills/superflow-pipeline/references/p0-baseline-template.md +3 -2
- package/assets/skills/superflow-pipeline/references/quality-gate.md +7 -5
- package/assets/skills/superflow-pipeline/references/reviewer-checklist.md +7 -0
- package/assets/skills/superflow-pipeline/scripts/superflow-guard.sh +205 -9
- package/assets/skills/superflow-pipeline/scripts/superflow-handoff.sh +7 -4
- package/assets/skills/superflow-requirement-review/SKILL.md +178 -0
- package/assets/skills/superflow-requirement-review/agents/openai.yaml +4 -0
- package/assets/skills-en/superflow-clarify/SKILL.md +17 -0
- package/assets/skills-en/superflow-design/SKILL.md +31 -12
- package/assets/skills-en/superflow-docs/SKILL.md +99 -0
- package/assets/skills-en/superflow-pipeline/SKILL.md +163 -9
- package/assets/skills-en/superflow-pipeline/references/batch-prompt-template.md +4 -0
- package/assets/skills-en/superflow-pipeline/references/document-readiness.md +125 -0
- package/assets/skills-en/superflow-pipeline/references/document-templates.md +10 -0
- package/assets/skills-en/superflow-pipeline/references/feature-gated-workflow.md +7 -0
- package/assets/skills-en/superflow-pipeline/references/implementation-prompt-template.md +7 -0
- package/assets/skills-en/superflow-pipeline/references/managed-work.md +160 -10
- package/assets/skills-en/superflow-pipeline/references/quality-gate.md +12 -0
- package/assets/skills-en/superflow-pipeline/references/reviewer-checklist.md +7 -0
- package/assets/skills-en/superflow-pipeline/scripts/superflow-guard.sh +205 -9
- package/assets/skills-en/superflow-pipeline/scripts/superflow-handoff.sh +7 -4
- package/assets/skills-en/superflow-requirement-review/SKILL.md +99 -0
- package/assets/skills-en/superflow-requirement-review/agents/openai.yaml +4 -0
- package/dist/app/cli.js +64 -9
- package/dist/app/cli.js.map +1 -1
- package/dist/app/commands/change-guard.js +19 -2
- package/dist/app/commands/change-guard.js.map +1 -1
- package/dist/app/commands/check.js +107 -45
- package/dist/app/commands/check.js.map +1 -1
- package/dist/app/commands/docs.js +7 -3
- package/dist/app/commands/docs.js.map +1 -1
- package/dist/app/commands/doctor.js +174 -198
- package/dist/app/commands/doctor.js.map +1 -1
- package/dist/app/commands/eval.js +64 -0
- package/dist/app/commands/eval.js.map +1 -0
- package/dist/app/commands/implement.js +7 -3
- package/dist/app/commands/implement.js.map +1 -1
- package/dist/app/commands/init.js +12 -24
- package/dist/app/commands/init.js.map +1 -1
- package/dist/app/commands/mcp.js +167 -0
- package/dist/app/commands/mcp.js.map +1 -0
- package/dist/app/commands/pipeline.js +652 -18
- package/dist/app/commands/pipeline.js.map +1 -1
- package/dist/app/commands/requirement-review.js +6 -0
- package/dist/app/commands/requirement-review.js.map +1 -0
- package/dist/app/commands/status.js +66 -3
- package/dist/app/commands/status.js.map +1 -1
- package/dist/app/commands/uninstall.js +3 -3
- package/dist/app/commands/uninstall.js.map +1 -1
- package/dist/app/commands/update.js +83 -9
- package/dist/app/commands/update.js.map +1 -1
- package/dist/domains/agent.js +3 -11
- package/dist/domains/agent.js.map +1 -1
- package/dist/domains/config/cli-help.js +54 -12
- package/dist/domains/config/cli-help.js.map +1 -1
- package/dist/domains/config/i18n.js +6 -8
- package/dist/domains/config/i18n.js.map +1 -1
- package/dist/domains/config/manifest.js +0 -3
- package/dist/domains/config/manifest.js.map +1 -1
- package/dist/domains/deps.js +68 -7
- package/dist/domains/deps.js.map +1 -1
- package/dist/domains/managed-work/completion-policy.js +174 -0
- package/dist/domains/managed-work/completion-policy.js.map +1 -0
- package/dist/domains/managed-work/context-manifest.js +208 -0
- package/dist/domains/managed-work/context-manifest.js.map +1 -0
- package/dist/domains/managed-work/contract.js +135 -4
- package/dist/domains/managed-work/contract.js.map +1 -1
- package/dist/domains/managed-work/control-signals.js +34 -0
- package/dist/domains/managed-work/control-signals.js.map +1 -0
- package/dist/domains/managed-work/control.js +628 -0
- package/dist/domains/managed-work/control.js.map +1 -0
- package/dist/domains/managed-work/evaluation.js +497 -0
- package/dist/domains/managed-work/evaluation.js.map +1 -0
- package/dist/domains/managed-work/evidence-path.js +14 -0
- package/dist/domains/managed-work/evidence-path.js.map +1 -0
- package/dist/domains/managed-work/execution-contract.js +167 -0
- package/dist/domains/managed-work/execution-contract.js.map +1 -0
- package/dist/domains/managed-work/executor-policy.js +65 -0
- package/dist/domains/managed-work/executor-policy.js.map +1 -0
- package/dist/domains/managed-work/failure.js +65 -0
- package/dist/domains/managed-work/failure.js.map +1 -0
- package/dist/domains/managed-work/host-review.js +77 -0
- package/dist/domains/managed-work/host-review.js.map +1 -0
- package/dist/domains/managed-work/human-messages.js +31 -0
- package/dist/domains/managed-work/human-messages.js.map +1 -0
- package/dist/domains/managed-work/input.js +29 -3
- package/dist/domains/managed-work/input.js.map +1 -1
- package/dist/domains/managed-work/journal.js +77 -3
- package/dist/domains/managed-work/journal.js.map +1 -1
- package/dist/domains/managed-work/lock.js +67 -1
- package/dist/domains/managed-work/lock.js.map +1 -1
- package/dist/domains/managed-work/notifications.js +0 -15
- package/dist/domains/managed-work/notifications.js.map +1 -1
- package/dist/domains/managed-work/pair-admission.js +108 -0
- package/dist/domains/managed-work/pair-admission.js.map +1 -0
- package/dist/domains/managed-work/prompts.js +257 -22
- package/dist/domains/managed-work/prompts.js.map +1 -1
- package/dist/domains/managed-work/redaction.js +90 -0
- package/dist/domains/managed-work/redaction.js.map +1 -0
- package/dist/domains/managed-work/review-convergence.js +41 -0
- package/dist/domains/managed-work/review-convergence.js.map +1 -0
- package/dist/domains/managed-work/review-facts.js +181 -0
- package/dist/domains/managed-work/review-facts.js.map +1 -0
- package/dist/domains/managed-work/rule-selection.js +84 -0
- package/dist/domains/managed-work/rule-selection.js.map +1 -0
- package/dist/domains/managed-work/runner.js +1967 -175
- package/dist/domains/managed-work/runner.js.map +1 -1
- package/dist/domains/managed-work/schemas.js +72 -2
- package/dist/domains/managed-work/schemas.js.map +1 -1
- package/dist/domains/managed-work/service.js +82 -13
- package/dist/domains/managed-work/service.js.map +1 -1
- package/dist/domains/managed-work/state.js +195 -5
- package/dist/domains/managed-work/state.js.map +1 -1
- package/dist/domains/managed-work/storage.js +66 -24
- package/dist/domains/managed-work/storage.js.map +1 -1
- package/dist/domains/managed-work/types.js +12 -9
- package/dist/domains/managed-work/types.js.map +1 -1
- package/dist/domains/managed-work/verification-categories.js +39 -0
- package/dist/domains/managed-work/verification-categories.js.map +1 -0
- package/dist/domains/managed-work/verification-policy.js +116 -0
- package/dist/domains/managed-work/verification-policy.js.map +1 -0
- package/dist/domains/managed-work/wait.js +21 -3
- package/dist/domains/managed-work/wait.js.map +1 -1
- package/dist/domains/managed-work/workspace-binding.js +133 -0
- package/dist/domains/managed-work/workspace-binding.js.map +1 -0
- package/dist/domains/sdd-readiness.js +44 -0
- package/dist/domains/sdd-readiness.js.map +1 -0
- package/dist/domains/skill/deploy.js +0 -6
- package/dist/domains/skill/deploy.js.map +1 -1
- package/dist/domains/skill/scripts.js +0 -8
- package/dist/domains/skill/scripts.js.map +1 -1
- package/dist/domains/state.js +1 -2
- package/dist/domains/state.js.map +1 -1
- package/dist/mcp/runtime-identity.js +21 -0
- package/dist/mcp/runtime-identity.js.map +1 -0
- package/dist/mcp/server.js +369 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/platform/agent-process.js +499 -38
- package/dist/platform/agent-process.js.map +1 -1
- package/dist/platform/executable.js +28 -0
- package/dist/platform/executable.js.map +1 -0
- package/dist/platform/paths.js +1 -17
- package/dist/platform/paths.js.map +1 -1
- package/dist/platform/process-tree.js +44 -0
- package/dist/platform/process-tree.js.map +1 -0
- package/dist/platform/process.js +1 -1
- package/dist/platform/process.js.map +1 -1
- package/package.json +17 -7
- package/scripts/check-design-principles.mjs +107 -0
- package/scripts/check-managed-work-design.mjs +507 -0
- package/scripts/clean-dist.js +9 -0
- package/scripts/ensure-bin-modes.js +11 -0
- package/scripts/preinstall.js +57 -0
- package/dist/cli/index.js +0 -193
- package/dist/cli/index.js.map +0 -1
- package/dist/commands/archive.js +0 -6
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/clarify.js +0 -6
- package/dist/commands/clarify.js.map +0 -1
- package/dist/commands/design.js +0 -28
- package/dist/commands/design.js.map +0 -1
- package/dist/commands/docs.js +0 -6
- package/dist/commands/docs.js.map +0 -1
- package/dist/commands/doctor.js +0 -494
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/implement.js +0 -6
- package/dist/commands/implement.js.map +0 -1
- package/dist/commands/init.js +0 -520
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/pipeline.js +0 -6
- package/dist/commands/pipeline.js.map +0 -1
- package/dist/commands/scan.js +0 -63
- package/dist/commands/scan.js.map +0 -1
- package/dist/commands/status.js +0 -173
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/uninstall.js +0 -214
- package/dist/commands/uninstall.js.map +0 -1
- package/dist/commands/update.js +0 -193
- package/dist/commands/update.js.map +0 -1
- package/dist/commands/verify.js +0 -6
- package/dist/commands/verify.js.map +0 -1
- package/dist/core/assets.js +0 -27
- package/dist/core/assets.js.map +0 -1
- package/dist/core/cli-help.js +0 -98
- package/dist/core/cli-help.js.map +0 -1
- package/dist/core/context.js +0 -123
- package/dist/core/context.js.map +0 -1
- package/dist/core/dependencies.js +0 -146
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/detect.js +0 -64
- package/dist/core/detect.js.map +0 -1
- package/dist/core/i18n.js +0 -105
- package/dist/core/i18n.js.map +0 -1
- package/dist/core/integrity.js +0 -46
- package/dist/core/integrity.js.map +0 -1
- package/dist/core/manifest.js +0 -21
- package/dist/core/manifest.js.map +0 -1
- package/dist/core/prompts.js +0 -20
- package/dist/core/prompts.js.map +0 -1
- package/dist/core/registry.js +0 -134
- package/dist/core/registry.js.map +0 -1
- package/dist/core/rules.js +0 -17
- package/dist/core/rules.js.map +0 -1
- package/dist/core/scripts.js +0 -48
- package/dist/core/scripts.js.map +0 -1
- package/dist/core/skill-check.js +0 -32
- package/dist/core/skill-check.js.map +0 -1
- package/dist/core/skills.js +0 -62
- package/dist/core/skills.js.map +0 -1
- package/dist/core/state.js +0 -54
- package/dist/core/state.js.map +0 -1
- package/dist/utils/path.js +0 -11
- package/dist/utils/path.js.map +0 -1
- package/dist/utils/shell.js +0 -29
- package/dist/utils/shell.js.map +0 -1
|
@@ -2,30 +2,97 @@ import { existsSync, readFileSync, unlinkSync } from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import spawn from "cross-spawn";
|
|
4
4
|
import { managedText } from "../domains/managed-work/i18n.js";
|
|
5
|
+
import { resolveExecutableShim } from "./executable.js";
|
|
6
|
+
import { stopProcessTree } from "./process-tree.js";
|
|
7
|
+
const HOST_SUPERVISION_INTERVAL_MS = 10 * 60_000;
|
|
8
|
+
const CLAUDE_MANAGED_AUTO_COMPACT_WINDOW = "200000";
|
|
9
|
+
const CLAUDE_MANAGED_AUTO_COMPACT_PERCENT = "70";
|
|
10
|
+
/**
|
|
11
|
+
* GUI-launched MCP servers do not reliably inherit an interactive shell PATH.
|
|
12
|
+
* Supplement it only with standard executable locations, never a user's home
|
|
13
|
+
* directory or a tool-specific private shim.
|
|
14
|
+
*/
|
|
15
|
+
export function managedExecutablePath(basePath, platform = process.platform, directoryExists = existsSync) {
|
|
16
|
+
const delimiter = platform === "win32" ? ";" : ":";
|
|
17
|
+
const standardDirectories = platform === "darwin"
|
|
18
|
+
? ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"]
|
|
19
|
+
: platform === "win32"
|
|
20
|
+
? []
|
|
21
|
+
: ["/usr/local/bin", "/usr/bin", "/bin"];
|
|
22
|
+
const existing = (basePath ?? "").split(delimiter).filter(Boolean);
|
|
23
|
+
const known = new Set(existing);
|
|
24
|
+
for (const directory of standardDirectories) {
|
|
25
|
+
if (directoryExists(directory) && !known.has(directory)) {
|
|
26
|
+
existing.push(directory);
|
|
27
|
+
known.add(directory);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return existing.join(delimiter);
|
|
31
|
+
}
|
|
5
32
|
export class LocalAgentInvoker {
|
|
6
33
|
async invoke(invocation) {
|
|
7
34
|
return runAgentProcess(invocation);
|
|
8
35
|
}
|
|
9
36
|
}
|
|
37
|
+
export class AgentInvocationFailure extends Error {
|
|
38
|
+
stdout;
|
|
39
|
+
stderr;
|
|
40
|
+
sessionId;
|
|
41
|
+
usage;
|
|
42
|
+
telemetry;
|
|
43
|
+
constructor(message, stdout, stderr, sessionId, usage, telemetry) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.stdout = stdout;
|
|
46
|
+
this.stderr = stderr;
|
|
47
|
+
this.sessionId = sessionId;
|
|
48
|
+
this.usage = usage;
|
|
49
|
+
this.telemetry = telemetry;
|
|
50
|
+
this.name = "AgentInvocationFailure";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function classifyUnresumableSessionFailure(input) {
|
|
54
|
+
const evidence = input.message.toLowerCase();
|
|
55
|
+
const resumeFailure = /\b(?:failed|failure|error|rejected|unable|cannot|can't)\b.{0,80}\b(?:resume|resuming)\b/.test(evidence) ||
|
|
56
|
+
/\b(?:resume|resuming)\b.{0,80}\b(?:failed|failure|error|rejected|unable|cannot|can't|history exceeds)\b/.test(evidence);
|
|
57
|
+
if (!resumeFailure)
|
|
58
|
+
return null;
|
|
59
|
+
if (/(?:(?:session|conversation)(?:\s+id)?\s+(?:not found|does not exist|expired)|unknown\s+(?:session|conversation))/.test(evidence)) {
|
|
60
|
+
return "session_not_found";
|
|
61
|
+
}
|
|
62
|
+
if (/(?:(?:invalid|malformed)\s+(?:session|conversation)(?:\s+id)?|(?:session|conversation)\s+id\s+is\s+invalid)/.test(evidence)) {
|
|
63
|
+
return "session_invalid";
|
|
64
|
+
}
|
|
65
|
+
if (/context window|context limit|too large|too long/.test(evidence)) {
|
|
66
|
+
return "resume_context_limit";
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
10
70
|
export function buildAgentCommand(invocation) {
|
|
11
71
|
if (invocation.agent === "codex")
|
|
12
72
|
return buildCodexCommand(invocation);
|
|
13
|
-
return
|
|
73
|
+
return invocation.agent === "claude"
|
|
74
|
+
? buildClaudeCommand(invocation)
|
|
75
|
+
: buildCodexCommand(invocation);
|
|
76
|
+
}
|
|
77
|
+
export function wrapWithSleepPrevention(command, args, platform = process.platform, caffeinateAvailable = existsSync("/usr/bin/caffeinate")) {
|
|
78
|
+
if (platform !== "darwin" || !caffeinateAvailable)
|
|
79
|
+
return { command, args };
|
|
80
|
+
return {
|
|
81
|
+
command: "/usr/bin/caffeinate",
|
|
82
|
+
args: ["-im", command, ...args],
|
|
83
|
+
};
|
|
14
84
|
}
|
|
15
85
|
async function runAgentProcess(invocation) {
|
|
16
86
|
const command = buildAgentCommand(invocation);
|
|
87
|
+
const environment = buildAgentEnvironment(invocation);
|
|
88
|
+
const executable = resolveExecutableShim(command.command, environment);
|
|
89
|
+
const processCommand = wrapWithSleepPrevention(executable, command.args);
|
|
17
90
|
if (command.outputFile && existsSync(command.outputFile))
|
|
18
91
|
unlinkSync(command.outputFile);
|
|
19
92
|
return new Promise((resolve, reject) => {
|
|
20
|
-
const child = spawn(
|
|
93
|
+
const child = spawn(processCommand.command, processCommand.args, {
|
|
21
94
|
cwd: invocation.projectRoot,
|
|
22
|
-
env:
|
|
23
|
-
...process.env,
|
|
24
|
-
SUPERFLOW_MANAGED_TASK_ID: invocation.taskId,
|
|
25
|
-
SUPERFLOW_MANAGED_RUN_ID: invocation.runId,
|
|
26
|
-
SUPERFLOW_MANAGED_ROLE: invocation.role,
|
|
27
|
-
SUPERFLOW_MANAGED_PROJECT_ROOT: invocation.projectRoot,
|
|
28
|
-
},
|
|
95
|
+
env: environment,
|
|
29
96
|
detached: process.platform !== "win32",
|
|
30
97
|
stdio: ["pipe", "pipe", "pipe"],
|
|
31
98
|
shell: false,
|
|
@@ -35,18 +102,53 @@ async function runAgentProcess(invocation) {
|
|
|
35
102
|
let lineBuffer = "";
|
|
36
103
|
let sessionId = invocation.sessionId;
|
|
37
104
|
let lastOutputAt = Date.now();
|
|
105
|
+
let lastClockCheckAt = lastOutputAt;
|
|
106
|
+
let activeInvocationMs = 0;
|
|
38
107
|
let finished = false;
|
|
108
|
+
let telemetry = initialTelemetry();
|
|
109
|
+
let usage = emptyUsage();
|
|
110
|
+
invocation.onTelemetry?.(telemetry);
|
|
111
|
+
invocation.onProcess?.(child.pid ?? null);
|
|
39
112
|
const warningTimer = setTimeout(() => {
|
|
113
|
+
if (finished)
|
|
114
|
+
return;
|
|
115
|
+
accountClockGap();
|
|
40
116
|
invocation.onProgress?.(managedText(invocation.language, "连续一段时间没有新进展,后台仍在观察进程", "No new progress for a while; the background service is still observing the process"));
|
|
41
117
|
}, invocation.timeout.warningMs);
|
|
118
|
+
const heartbeatTimer = setInterval(() => {
|
|
119
|
+
if (finished)
|
|
120
|
+
return;
|
|
121
|
+
accountClockGap();
|
|
122
|
+
const elapsed = Date.now() - lastOutputAt;
|
|
123
|
+
invocation.onProgress?.(managedText(invocation.language, `Agent 心跳:阶段 ${telemetry.phase},距最近输出 ${Math.round(elapsed / 1_000)} 秒,最近进展原因 ${telemetry.lastProgressReason}`, `Agent heartbeat: phase ${telemetry.phase}, ${Math.round(elapsed / 1_000)} seconds since output, last progress reason ${telemetry.lastProgressReason}`));
|
|
124
|
+
}, 60_000);
|
|
125
|
+
const supervisionTimer = setInterval(() => {
|
|
126
|
+
if (finished)
|
|
127
|
+
return;
|
|
128
|
+
accountClockGap();
|
|
129
|
+
invocation.onProgress?.(`superflow-supervision:${JSON.stringify({
|
|
130
|
+
phase: telemetry.phase,
|
|
131
|
+
lastProgressAt: telemetry.lastProgressAt,
|
|
132
|
+
lastProgressReason: telemetry.lastProgressReason,
|
|
133
|
+
secondsSinceOutput: Math.round((Date.now() - lastOutputAt) / 1_000),
|
|
134
|
+
})}`);
|
|
135
|
+
}, HOST_SUPERVISION_INTERVAL_MS);
|
|
42
136
|
let stalledTimer = setTimeout(onStalled, invocation.timeout.stalledMs);
|
|
43
|
-
const hardTimer =
|
|
137
|
+
const hardTimer = setInterval(() => {
|
|
138
|
+
if (finished)
|
|
139
|
+
return;
|
|
140
|
+
accountClockGap();
|
|
141
|
+
if (activeInvocationMs < invocation.timeout.hardMs)
|
|
142
|
+
return;
|
|
44
143
|
stopChild(child.pid);
|
|
45
144
|
finishError(new Error(managedText(invocation.language, `单次 Agent 调用超过 ${invocation.timeout.hardMs}ms`, `Single Agent invocation exceeded ${invocation.timeout.hardMs}ms`)));
|
|
46
|
-
},
|
|
145
|
+
}, 250);
|
|
47
146
|
if (invocation.sessionId)
|
|
48
147
|
invocation.onSession?.(invocation.sessionId);
|
|
49
148
|
child.stdout?.on("data", (chunk) => {
|
|
149
|
+
if (finished)
|
|
150
|
+
return;
|
|
151
|
+
accountClockGap();
|
|
50
152
|
const text = chunk.toString();
|
|
51
153
|
stdout += text;
|
|
52
154
|
lastOutputAt = Date.now();
|
|
@@ -57,6 +159,16 @@ async function runAgentProcess(invocation) {
|
|
|
57
159
|
lineBuffer = lines.pop() ?? "";
|
|
58
160
|
for (const line of lines) {
|
|
59
161
|
const event = safeJson(line);
|
|
162
|
+
if (event) {
|
|
163
|
+
telemetry = updateTelemetry(telemetry, event);
|
|
164
|
+
usage = updateAgentUsage(usage, event);
|
|
165
|
+
invocation.onTelemetry?.(telemetry);
|
|
166
|
+
}
|
|
167
|
+
if (invocation.agent === "claude" && shouldStopClaudeRetry(event)) {
|
|
168
|
+
stopChild(child.pid);
|
|
169
|
+
finishError(new Error(managedText(invocation.language, "Claude 底层模型连续出现 3 次临时 429/529/overloaded,已熔断本次调用", "Claude's provider returned 3 consecutive transient 429/529/overloaded retries; this invocation was stopped")));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
60
172
|
const discoveredSession = extractSessionId(event);
|
|
61
173
|
if (!sessionId && discoveredSession) {
|
|
62
174
|
sessionId = discoveredSession;
|
|
@@ -68,6 +180,9 @@ async function runAgentProcess(invocation) {
|
|
|
68
180
|
}
|
|
69
181
|
});
|
|
70
182
|
child.stderr?.on("data", (chunk) => {
|
|
183
|
+
if (finished)
|
|
184
|
+
return;
|
|
185
|
+
accountClockGap();
|
|
71
186
|
stderr += chunk.toString();
|
|
72
187
|
lastOutputAt = Date.now();
|
|
73
188
|
});
|
|
@@ -78,7 +193,7 @@ async function runAgentProcess(invocation) {
|
|
|
78
193
|
cleanupTimers();
|
|
79
194
|
finished = true;
|
|
80
195
|
if ((code ?? 1) !== 0) {
|
|
81
|
-
reject(new
|
|
196
|
+
reject(new AgentInvocationFailure(formatAgentFailure(invocation.agent, code, stdout, stderr, invocation.language), stdout, stderr, sessionId, usage, telemetry));
|
|
82
197
|
return;
|
|
83
198
|
}
|
|
84
199
|
try {
|
|
@@ -94,14 +209,24 @@ async function runAgentProcess(invocation) {
|
|
|
94
209
|
stdout,
|
|
95
210
|
stderr,
|
|
96
211
|
exitCode: code ?? 0,
|
|
212
|
+
usage,
|
|
213
|
+
telemetry,
|
|
97
214
|
});
|
|
98
215
|
}
|
|
99
216
|
catch (error) {
|
|
100
|
-
reject(error);
|
|
217
|
+
reject(new AgentInvocationFailure(error.message, stdout, stderr, sessionId, usage, telemetry));
|
|
101
218
|
}
|
|
102
219
|
});
|
|
103
|
-
child.stdin?.end(invocation
|
|
220
|
+
child.stdin?.end(agentProcessInput(invocation));
|
|
104
221
|
function onStalled() {
|
|
222
|
+
if (finished)
|
|
223
|
+
return;
|
|
224
|
+
const slept = accountClockGap();
|
|
225
|
+
if (slept) {
|
|
226
|
+
clearTimeout(stalledTimer);
|
|
227
|
+
stalledTimer = setTimeout(onStalled, invocation.timeout.stalledMs);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
105
230
|
if (Date.now() - lastOutputAt < invocation.timeout.stalledMs)
|
|
106
231
|
return;
|
|
107
232
|
stopChild(child.pid);
|
|
@@ -112,15 +237,167 @@ async function runAgentProcess(invocation) {
|
|
|
112
237
|
return;
|
|
113
238
|
finished = true;
|
|
114
239
|
cleanupTimers();
|
|
115
|
-
reject(error
|
|
240
|
+
reject(error instanceof AgentInvocationFailure
|
|
241
|
+
? error
|
|
242
|
+
: new AgentInvocationFailure(error.message, stdout, stderr, sessionId, usage, telemetry));
|
|
116
243
|
}
|
|
117
244
|
function cleanupTimers() {
|
|
118
245
|
clearTimeout(warningTimer);
|
|
119
246
|
clearTimeout(stalledTimer);
|
|
120
|
-
|
|
247
|
+
clearInterval(hardTimer);
|
|
248
|
+
clearInterval(heartbeatTimer);
|
|
249
|
+
clearInterval(supervisionTimer);
|
|
250
|
+
invocation.onProcess?.(null);
|
|
251
|
+
}
|
|
252
|
+
function accountClockGap() {
|
|
253
|
+
const now = Date.now();
|
|
254
|
+
const delta = Math.max(0, now - lastClockCheckAt);
|
|
255
|
+
lastClockCheckAt = now;
|
|
256
|
+
const slept = delta > 5_000;
|
|
257
|
+
if (slept) {
|
|
258
|
+
lastOutputAt += delta;
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
activeInvocationMs += delta;
|
|
262
|
+
return false;
|
|
121
263
|
}
|
|
122
264
|
});
|
|
123
265
|
}
|
|
266
|
+
export function buildAgentEnvironment(invocation, baseEnv = process.env) {
|
|
267
|
+
return {
|
|
268
|
+
...baseEnv,
|
|
269
|
+
PATH: managedExecutablePath(baseEnv.PATH),
|
|
270
|
+
SUPERFLOW_MANAGED_TASK_ID: invocation.taskId,
|
|
271
|
+
SUPERFLOW_MANAGED_RUN_ID: invocation.runId,
|
|
272
|
+
SUPERFLOW_MANAGED_ROLE: invocation.role,
|
|
273
|
+
SUPERFLOW_MANAGED_PROJECT_ROOT: invocation.projectRoot,
|
|
274
|
+
SUPERFLOW_MANAGED_CONTEXT_MANIFEST: path.join(invocation.projectRoot, ".superflow", "tasks", invocation.taskId, "context-manifest.json"),
|
|
275
|
+
...(invocation.agent === "claude" && invocation.role === "executor"
|
|
276
|
+
? {
|
|
277
|
+
CLAUDE_CODE_AUTO_COMPACT_WINDOW: baseEnv.CLAUDE_CODE_AUTO_COMPACT_WINDOW ??
|
|
278
|
+
CLAUDE_MANAGED_AUTO_COMPACT_WINDOW,
|
|
279
|
+
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE: baseEnv.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE ??
|
|
280
|
+
CLAUDE_MANAGED_AUTO_COMPACT_PERCENT,
|
|
281
|
+
}
|
|
282
|
+
: {}),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
export function initialTelemetry() {
|
|
286
|
+
const now = new Date().toISOString();
|
|
287
|
+
return {
|
|
288
|
+
phase: "model_waiting",
|
|
289
|
+
model: null,
|
|
290
|
+
tools: [],
|
|
291
|
+
toolUses: [],
|
|
292
|
+
plugins: [],
|
|
293
|
+
lastOutputAt: now,
|
|
294
|
+
lastProgressAt: now,
|
|
295
|
+
lastProgressReason: "invocation_started",
|
|
296
|
+
permissionDenials: 0,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function emptyUsage() {
|
|
300
|
+
return {
|
|
301
|
+
inputTokens: null,
|
|
302
|
+
outputTokens: null,
|
|
303
|
+
cacheReadTokens: null,
|
|
304
|
+
cacheWriteTokens: null,
|
|
305
|
+
costUsd: null,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
export function updateTelemetry(current, event) {
|
|
309
|
+
const now = new Date().toISOString();
|
|
310
|
+
const next = { ...current, lastOutputAt: now };
|
|
311
|
+
if (Array.isArray(event.permission_denials)) {
|
|
312
|
+
next.permissionDenials = Math.max(next.permissionDenials ?? 0, event.permission_denials.length);
|
|
313
|
+
}
|
|
314
|
+
if (event.type === "system" && event.subtype === "init") {
|
|
315
|
+
next.model = stringValue(event.model) ?? next.model;
|
|
316
|
+
next.tools = stringArray(event.tools);
|
|
317
|
+
next.plugins = stringArray(event.plugins);
|
|
318
|
+
next.lastProgressAt = now;
|
|
319
|
+
next.lastProgressReason = "system_init";
|
|
320
|
+
}
|
|
321
|
+
const toolNames = extractToolNames(event);
|
|
322
|
+
if (event.type === "result") {
|
|
323
|
+
next.phase = "model_waiting";
|
|
324
|
+
next.lastProgressAt = now;
|
|
325
|
+
next.lastProgressReason = "agent_result";
|
|
326
|
+
}
|
|
327
|
+
else if (toolNames.length > 0) {
|
|
328
|
+
next.toolUses = [...new Set([...next.toolUses, ...toolNames])];
|
|
329
|
+
next.phase = toolNames.some((name) => /^(write|edit|apply_patch)$/i.test(name))
|
|
330
|
+
? "workspace_writing"
|
|
331
|
+
: "tool_running";
|
|
332
|
+
next.lastProgressAt = now;
|
|
333
|
+
next.lastProgressReason = `tool_use:${toolNames.join(",")}`;
|
|
334
|
+
}
|
|
335
|
+
else if (/permission|approval/i.test(`${event.type} ${event.subtype}`)) {
|
|
336
|
+
next.phase = "awaiting_local_approval";
|
|
337
|
+
next.lastProgressReason = "local_approval";
|
|
338
|
+
}
|
|
339
|
+
else if (event.type === "assistant" || event.type === "message") {
|
|
340
|
+
next.phase = "model_waiting";
|
|
341
|
+
next.lastProgressAt = now;
|
|
342
|
+
next.lastProgressReason = "model_output";
|
|
343
|
+
}
|
|
344
|
+
return next;
|
|
345
|
+
}
|
|
346
|
+
function extractToolNames(value) {
|
|
347
|
+
if (!value || typeof value !== "object")
|
|
348
|
+
return [];
|
|
349
|
+
const object = value;
|
|
350
|
+
const names = [];
|
|
351
|
+
if ((object.type === "tool_use" || object.type === "tool_call") &&
|
|
352
|
+
typeof object.name === "string") {
|
|
353
|
+
names.push(object.name);
|
|
354
|
+
}
|
|
355
|
+
for (const child of Object.values(object)) {
|
|
356
|
+
if (Array.isArray(child)) {
|
|
357
|
+
for (const item of child)
|
|
358
|
+
names.push(...extractToolNames(item));
|
|
359
|
+
}
|
|
360
|
+
else if (child && typeof child === "object") {
|
|
361
|
+
names.push(...extractToolNames(child));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return names;
|
|
365
|
+
}
|
|
366
|
+
function extractUsage(event) {
|
|
367
|
+
const usage = event.usage && typeof event.usage === "object"
|
|
368
|
+
? event.usage
|
|
369
|
+
: {};
|
|
370
|
+
return {
|
|
371
|
+
inputTokens: numberValue(usage.input_tokens ?? usage.inputTokens),
|
|
372
|
+
outputTokens: numberValue(usage.output_tokens ?? usage.outputTokens),
|
|
373
|
+
cacheReadTokens: numberValue(usage.cache_read_input_tokens ?? usage.cacheReadTokens),
|
|
374
|
+
cacheWriteTokens: numberValue(usage.cache_creation_input_tokens ?? usage.cacheWriteTokens),
|
|
375
|
+
costUsd: numberValue(event.total_cost_usd ?? event.cost_usd),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
export function updateAgentUsage(current, event) {
|
|
379
|
+
return mergeUsage(current, extractUsage(event));
|
|
380
|
+
}
|
|
381
|
+
function mergeUsage(current, next) {
|
|
382
|
+
return {
|
|
383
|
+
inputTokens: next.inputTokens ?? current.inputTokens,
|
|
384
|
+
outputTokens: next.outputTokens ?? current.outputTokens,
|
|
385
|
+
cacheReadTokens: next.cacheReadTokens ?? current.cacheReadTokens,
|
|
386
|
+
cacheWriteTokens: next.cacheWriteTokens ?? current.cacheWriteTokens,
|
|
387
|
+
costUsd: next.costUsd ?? current.costUsd,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function stringValue(value) {
|
|
391
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
392
|
+
}
|
|
393
|
+
function stringArray(value) {
|
|
394
|
+
if (!Array.isArray(value))
|
|
395
|
+
return [];
|
|
396
|
+
return value.filter((item) => typeof item === "string");
|
|
397
|
+
}
|
|
398
|
+
function numberValue(value) {
|
|
399
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
400
|
+
}
|
|
124
401
|
export function formatAgentFailure(agent, code, stdout, stderr, language) {
|
|
125
402
|
const details = [stderr.trim(), extractFailureFromEvents(stdout)]
|
|
126
403
|
.filter(Boolean)
|
|
@@ -135,11 +412,26 @@ function extractFailureFromEvents(stdout) {
|
|
|
135
412
|
const event = safeJson(lines[index]);
|
|
136
413
|
if (!event)
|
|
137
414
|
continue;
|
|
138
|
-
for (const key of [
|
|
415
|
+
for (const key of [
|
|
416
|
+
"error",
|
|
417
|
+
"message",
|
|
418
|
+
"result",
|
|
419
|
+
"reason",
|
|
420
|
+
"terminal_reason",
|
|
421
|
+
"subtype",
|
|
422
|
+
"stop_reason",
|
|
423
|
+
]) {
|
|
139
424
|
const value = event[key];
|
|
140
425
|
if (typeof value === "string" && value.trim())
|
|
141
426
|
messages.push(value.trim());
|
|
142
427
|
}
|
|
428
|
+
if (Array.isArray(event.errors)) {
|
|
429
|
+
for (const value of event.errors) {
|
|
430
|
+
if (typeof value === "string" && value.trim()) {
|
|
431
|
+
messages.push(value.trim());
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
143
435
|
}
|
|
144
436
|
return messages.join("\n");
|
|
145
437
|
}
|
|
@@ -156,6 +448,7 @@ function buildCodexCommand(invocation) {
|
|
|
156
448
|
args: [
|
|
157
449
|
"exec",
|
|
158
450
|
"resume",
|
|
451
|
+
...(invocation.role === "executor" ? ["--disable", "memories"] : []),
|
|
159
452
|
invocation.sessionId,
|
|
160
453
|
"--json",
|
|
161
454
|
"--output-schema",
|
|
@@ -176,6 +469,7 @@ function buildCodexCommand(invocation) {
|
|
|
176
469
|
command: "codex",
|
|
177
470
|
args: [
|
|
178
471
|
"exec",
|
|
472
|
+
...(invocation.role === "executor" ? ["--disable", "memories"] : []),
|
|
179
473
|
"-C",
|
|
180
474
|
invocation.projectRoot,
|
|
181
475
|
"--sandbox",
|
|
@@ -194,7 +488,7 @@ function buildCodexCommand(invocation) {
|
|
|
194
488
|
}
|
|
195
489
|
function buildClaudeCommand(invocation) {
|
|
196
490
|
const schema = readFileSync(invocation.schemaPath, "utf-8");
|
|
197
|
-
const args = ["-p"];
|
|
491
|
+
const args = ["--bare", "-p"];
|
|
198
492
|
let initialSessionId = invocation.sessionId;
|
|
199
493
|
if (invocation.sessionId)
|
|
200
494
|
args.push("--resume", invocation.sessionId);
|
|
@@ -202,11 +496,98 @@ function buildClaudeCommand(invocation) {
|
|
|
202
496
|
initialSessionId = cryptoRandomUuid();
|
|
203
497
|
args.push("--session-id", initialSessionId);
|
|
204
498
|
}
|
|
205
|
-
args.push(
|
|
499
|
+
args.push(...(invocation.systemPromptPath
|
|
500
|
+
? ["--append-system-prompt-file", invocation.systemPromptPath]
|
|
501
|
+
: []), "--output-format", "stream-json", "--verbose", "--include-partial-messages", "--json-schema", schema, "--tools", claudeTools(invocation.role));
|
|
502
|
+
if (invocation.role === "executor") {
|
|
503
|
+
args.push("--dangerously-skip-permissions");
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
args.push("--permission-mode", "dontAsk", "--allowedTools", ...claudeAllowedTools(invocation.role));
|
|
507
|
+
}
|
|
508
|
+
if (invocation.role === "supervisor") {
|
|
509
|
+
args.push("--max-turns", "20");
|
|
510
|
+
}
|
|
206
511
|
for (const root of invocation.writableRoots)
|
|
207
512
|
args.push("--add-dir", root);
|
|
208
513
|
return { command: "claude", args, outputFile: null, initialSessionId };
|
|
209
514
|
}
|
|
515
|
+
export function agentProcessInput(invocation) {
|
|
516
|
+
if (invocation.agent === "codex")
|
|
517
|
+
return invocation.prompt;
|
|
518
|
+
if (invocation.agent === "claude" && invocation.promptPath) {
|
|
519
|
+
return `Read ${invocation.promptPath} completely and execute it.`;
|
|
520
|
+
}
|
|
521
|
+
return invocation.prompt;
|
|
522
|
+
}
|
|
523
|
+
function claudeTools(role) {
|
|
524
|
+
return role === "supervisor"
|
|
525
|
+
? "Read,Grep,Glob,Bash"
|
|
526
|
+
: "Read,Write,Edit,Grep,Glob,Bash";
|
|
527
|
+
}
|
|
528
|
+
function claudeAllowedTools(role) {
|
|
529
|
+
const readOnly = [
|
|
530
|
+
"Read",
|
|
531
|
+
"Grep",
|
|
532
|
+
"Glob",
|
|
533
|
+
"Bash(git diff *)",
|
|
534
|
+
"Bash(git status *)",
|
|
535
|
+
"Bash(git log *)",
|
|
536
|
+
"Bash(git show *)",
|
|
537
|
+
"Bash(xmllint *)",
|
|
538
|
+
];
|
|
539
|
+
if (role === "supervisor")
|
|
540
|
+
return readOnly;
|
|
541
|
+
return [
|
|
542
|
+
...readOnly,
|
|
543
|
+
"Write",
|
|
544
|
+
"Edit",
|
|
545
|
+
"Bash(mvn *)",
|
|
546
|
+
"Bash(JAVA_HOME=* mvn *)",
|
|
547
|
+
"Bash(JAVA_HOME=* */mvn *)",
|
|
548
|
+
"Bash(java *)",
|
|
549
|
+
"Bash(JAVA_HOME=* java *)",
|
|
550
|
+
"Bash(/usr/libexec/java_home *)",
|
|
551
|
+
"Bash(command -v java*)",
|
|
552
|
+
"Bash(command -v mvn*)",
|
|
553
|
+
"Bash(which java*)",
|
|
554
|
+
"Bash(which mvn*)",
|
|
555
|
+
"Bash(npm *)",
|
|
556
|
+
"Bash(npx *)",
|
|
557
|
+
"Bash(pnpm *)",
|
|
558
|
+
"Bash(yarn *)",
|
|
559
|
+
"Bash(gradle *)",
|
|
560
|
+
"Bash(./gradlew *)",
|
|
561
|
+
"Bash(openspec *)",
|
|
562
|
+
"Bash(bash .claude/scripts/superflow-*.sh)",
|
|
563
|
+
"Bash(bash .claude/scripts/superflow-*.sh *)",
|
|
564
|
+
"Bash(python3 .claude/scripts/superflow-*.py)",
|
|
565
|
+
"Bash(python3 .claude/scripts/superflow-*.py *)",
|
|
566
|
+
"Bash(node .claude/scripts/superflow-*.mjs)",
|
|
567
|
+
"Bash(node .claude/scripts/superflow-*.mjs *)",
|
|
568
|
+
"Bash(PORT=* npm *)",
|
|
569
|
+
"Bash(node --version*)",
|
|
570
|
+
"Bash(npm --version*)",
|
|
571
|
+
"Bash(curl --version*)",
|
|
572
|
+
"Bash(which curl*)",
|
|
573
|
+
"Bash(command -v curl*)",
|
|
574
|
+
"Bash(curl http://localhost*)",
|
|
575
|
+
"Bash(curl http://127.0.0.1*)",
|
|
576
|
+
"Bash(lsof *)",
|
|
577
|
+
"Bash(ps *)",
|
|
578
|
+
"Bash(kill *)",
|
|
579
|
+
];
|
|
580
|
+
}
|
|
581
|
+
export function shouldStopClaudeRetry(event) {
|
|
582
|
+
if (event?.type !== "system" ||
|
|
583
|
+
event.subtype !== "api_retry" ||
|
|
584
|
+
typeof event.attempt !== "number" ||
|
|
585
|
+
event.attempt < 3) {
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
const detail = `${String(event.error_status ?? "")} ${String(event.error ?? "")}`;
|
|
589
|
+
return /\b(?:429|529)\b|overloaded|rate.?limit|访问量过大|暂时.*限流/i.test(detail);
|
|
590
|
+
}
|
|
210
591
|
function extractStructuredOutput(agent, stdout, outputFile, language) {
|
|
211
592
|
if (agent === "codex" && outputFile && existsSync(outputFile)) {
|
|
212
593
|
return parseStructuredValue(readFileSync(outputFile, "utf-8"), language);
|
|
@@ -218,9 +599,40 @@ function extractStructuredOutput(agent, stdout, outputFile, language) {
|
|
|
218
599
|
if (event[key] !== undefined)
|
|
219
600
|
return parseStructuredValue(event[key], language);
|
|
220
601
|
}
|
|
602
|
+
const text = extractEventText(event);
|
|
603
|
+
if (text) {
|
|
604
|
+
try {
|
|
605
|
+
return parseStructuredValue(text, language);
|
|
606
|
+
}
|
|
607
|
+
catch {
|
|
608
|
+
// Continue to earlier events before falling back to the whole stream.
|
|
609
|
+
}
|
|
610
|
+
}
|
|
221
611
|
}
|
|
222
612
|
return parseStructuredValue(stdout, language);
|
|
223
613
|
}
|
|
614
|
+
function extractEventText(value) {
|
|
615
|
+
if (!value || typeof value !== "object")
|
|
616
|
+
return null;
|
|
617
|
+
const object = value;
|
|
618
|
+
if (typeof object.text === "string" && object.text.trim())
|
|
619
|
+
return object.text;
|
|
620
|
+
for (const child of Object.values(object)) {
|
|
621
|
+
if (Array.isArray(child)) {
|
|
622
|
+
for (let index = child.length - 1; index >= 0; index--) {
|
|
623
|
+
const text = extractEventText(child[index]);
|
|
624
|
+
if (text)
|
|
625
|
+
return text;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
else if (child && typeof child === "object") {
|
|
629
|
+
const text = extractEventText(child);
|
|
630
|
+
if (text)
|
|
631
|
+
return text;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
224
636
|
function parseStructuredValue(value, language) {
|
|
225
637
|
if (typeof value === "object" && value !== null)
|
|
226
638
|
return value;
|
|
@@ -251,7 +663,13 @@ function extractSessionId(value) {
|
|
|
251
663
|
if (!value || typeof value !== "object")
|
|
252
664
|
return null;
|
|
253
665
|
const object = value;
|
|
254
|
-
for (const key of [
|
|
666
|
+
for (const key of [
|
|
667
|
+
"session_id",
|
|
668
|
+
"sessionId",
|
|
669
|
+
"sessionID",
|
|
670
|
+
"thread_id",
|
|
671
|
+
"threadId",
|
|
672
|
+
]) {
|
|
255
673
|
if (typeof object[key] === "string" && isUuid(object[key]))
|
|
256
674
|
return object[key];
|
|
257
675
|
}
|
|
@@ -262,17 +680,75 @@ function extractSessionId(value) {
|
|
|
262
680
|
}
|
|
263
681
|
return null;
|
|
264
682
|
}
|
|
265
|
-
function extractProgress(value) {
|
|
683
|
+
export function extractProgress(value) {
|
|
266
684
|
if (!value || typeof value !== "object")
|
|
267
685
|
return null;
|
|
686
|
+
const stageHint = extractToolStageHint(value);
|
|
687
|
+
if (stageHint)
|
|
688
|
+
return `superflow-stage:${stageHint}`;
|
|
268
689
|
const object = value;
|
|
269
690
|
for (const key of ["message", "summary", "text"]) {
|
|
270
691
|
if (typeof object[key] === "string" && object[key].length > 0) {
|
|
271
|
-
return object[key]
|
|
692
|
+
return object[key];
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
697
|
+
function extractToolStageHint(value) {
|
|
698
|
+
if (!value || typeof value !== "object")
|
|
699
|
+
return null;
|
|
700
|
+
const object = value;
|
|
701
|
+
const type = stringValue(object.type);
|
|
702
|
+
const name = stringValue(object.name);
|
|
703
|
+
if (["tool_use", "tool_call"].includes(type ?? "") &&
|
|
704
|
+
/^(?:write|edit|apply_patch)$/i.test(name ?? "")) {
|
|
705
|
+
return "implementation";
|
|
706
|
+
}
|
|
707
|
+
if (["tool_use", "tool_call"].includes(type ?? "") &&
|
|
708
|
+
/^(?:bash|shell|terminal)$/i.test(name ?? "")) {
|
|
709
|
+
const input = object.input;
|
|
710
|
+
const command = input && typeof input === "object"
|
|
711
|
+
? stringValue(input.command)
|
|
712
|
+
: null;
|
|
713
|
+
if (command)
|
|
714
|
+
return stageFromCommand(command);
|
|
715
|
+
}
|
|
716
|
+
for (const child of Object.values(object)) {
|
|
717
|
+
if (Array.isArray(child)) {
|
|
718
|
+
for (const item of child) {
|
|
719
|
+
const stage = extractToolStageHint(item);
|
|
720
|
+
if (stage)
|
|
721
|
+
return stage;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
else if (child && typeof child === "object") {
|
|
725
|
+
const stage = extractToolStageHint(child);
|
|
726
|
+
if (stage)
|
|
727
|
+
return stage;
|
|
272
728
|
}
|
|
273
729
|
}
|
|
274
730
|
return null;
|
|
275
731
|
}
|
|
732
|
+
function stageFromCommand(command) {
|
|
733
|
+
const value = command.toLowerCase();
|
|
734
|
+
if (/preflight|diff --check/.test(value))
|
|
735
|
+
return "delivery_self_check";
|
|
736
|
+
if (/drop table|redis.*(?:del|exists)|kill|pkill|cleanup/.test(value)) {
|
|
737
|
+
return "cleanup";
|
|
738
|
+
}
|
|
739
|
+
if (/\bcurl\b|\bwget\b|http:\/\/|https:\/\//.test(value)) {
|
|
740
|
+
return "http_e2e";
|
|
741
|
+
}
|
|
742
|
+
if (/java -jar|spring-boot:run|bootrun|npm run (?:dev|start)/.test(value)) {
|
|
743
|
+
return "application_startup";
|
|
744
|
+
}
|
|
745
|
+
if (/mvn.*package|gradle.*build|npm.*build/.test(value))
|
|
746
|
+
return "package";
|
|
747
|
+
if (/mvn.*test|gradle.*test|vitest|jest|pytest/.test(value)) {
|
|
748
|
+
return "unit_test";
|
|
749
|
+
}
|
|
750
|
+
return null;
|
|
751
|
+
}
|
|
276
752
|
function safeJson(value) {
|
|
277
753
|
try {
|
|
278
754
|
return JSON.parse(value);
|
|
@@ -282,22 +758,7 @@ function safeJson(value) {
|
|
|
282
758
|
}
|
|
283
759
|
}
|
|
284
760
|
function stopChild(pid) {
|
|
285
|
-
|
|
286
|
-
return;
|
|
287
|
-
try {
|
|
288
|
-
if (process.platform === "win32")
|
|
289
|
-
process.kill(pid, "SIGTERM");
|
|
290
|
-
else
|
|
291
|
-
process.kill(-pid, "SIGTERM");
|
|
292
|
-
}
|
|
293
|
-
catch {
|
|
294
|
-
try {
|
|
295
|
-
process.kill(pid, "SIGTERM");
|
|
296
|
-
}
|
|
297
|
-
catch {
|
|
298
|
-
// Process already exited.
|
|
299
|
-
}
|
|
300
|
-
}
|
|
761
|
+
stopProcessTree(pid);
|
|
301
762
|
}
|
|
302
763
|
function isUuid(value) {
|
|
303
764
|
return (typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f-]{27}$/i.test(value));
|