@backburner/cli 0.1.0
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/LICENSE +21 -0
- package/README.md +329 -0
- package/dist/src/agents/antigravity-launch.js +162 -0
- package/dist/src/agents/antigravity.js +251 -0
- package/dist/src/agents/claude-launch.js +117 -0
- package/dist/src/agents/claude.js +271 -0
- package/dist/src/agents/codex-launch.js +82 -0
- package/dist/src/agents/codex.js +261 -0
- package/dist/src/agents/composite-runner.js +13 -0
- package/dist/src/agents/extraction.js +442 -0
- package/dist/src/agents/failure-feedback.js +132 -0
- package/dist/src/agents/gemini-launch.js +96 -0
- package/dist/src/agents/gemini.js +392 -0
- package/dist/src/agents/job-agent-assignments.js +180 -0
- package/dist/src/agents/lifecycle-feedback.js +147 -0
- package/dist/src/agents/limit-detector.js +83 -0
- package/dist/src/agents/opencode-launch.js +138 -0
- package/dist/src/agents/opencode.js +156 -0
- package/dist/src/agents/plan-breakdown-feedback.js +97 -0
- package/dist/src/agents/pool.js +168 -0
- package/dist/src/agents/prompt.js +462 -0
- package/dist/src/agents/provider-sessions.js +105 -0
- package/dist/src/agents/review-feedback.js +255 -0
- package/dist/src/agents/selection.js +113 -0
- package/dist/src/agents/service.js +1087 -0
- package/dist/src/agents/types.js +1 -0
- package/dist/src/agents/wave-assessment-feedback.js +69 -0
- package/dist/src/attention/derive.js +337 -0
- package/dist/src/capabilities/index.js +2 -0
- package/dist/src/capabilities/projection.js +183 -0
- package/dist/src/capabilities/types.js +1 -0
- package/dist/src/capabilities/validator.js +87 -0
- package/dist/src/cli/commands/broker-smoke.js +96 -0
- package/dist/src/cli/commands/broker.js +56 -0
- package/dist/src/cli/commands/init.js +4 -0
- package/dist/src/cli/commands/journal.js +43 -0
- package/dist/src/cli/commands/run-loop.js +56 -0
- package/dist/src/cli/commands/run.js +1241 -0
- package/dist/src/cli/commands/tui.js +187 -0
- package/dist/src/cli/dispatcher.js +51 -0
- package/dist/src/cli/errors.js +2 -0
- package/dist/src/cli/init.js +92 -0
- package/dist/src/cli/options.js +86 -0
- package/dist/src/cli/output.js +231 -0
- package/dist/src/cli/run-journal.js +342 -0
- package/dist/src/cli/run-types.js +1 -0
- package/dist/src/cli/run.js +32 -0
- package/dist/src/cli/runtime/dispatch-lifecycle.js +772 -0
- package/dist/src/cli/runtime/provider-runtime-types.js +1 -0
- package/dist/src/cli/runtime/provider-runtime.js +152 -0
- package/dist/src/cli/runtime/provider-tools.js +73 -0
- package/dist/src/cli/runtime/providers/antigravity.js +26 -0
- package/dist/src/cli/runtime/providers/claude.js +20 -0
- package/dist/src/cli/runtime/providers/codex.js +20 -0
- package/dist/src/cli/runtime/providers/gemini.js +20 -0
- package/dist/src/cli/runtime/providers/opencode.js +20 -0
- package/dist/src/cli/runtime/run-context.js +131 -0
- package/dist/src/cli/tui/index.js +3 -0
- package/dist/src/cli/tui/screen.js +388 -0
- package/dist/src/cli/tui/tracker.js +558 -0
- package/dist/src/cli/tui/types.js +1 -0
- package/dist/src/cli/tui/views.js +1006 -0
- package/dist/src/config/loader.js +136 -0
- package/dist/src/config/schemas.js +403 -0
- package/dist/src/config/types.js +1 -0
- package/dist/src/context/types.js +331 -0
- package/dist/src/domain/entities.js +1 -0
- package/dist/src/git/backburner-git-tool-service.js +680 -0
- package/dist/src/git/gitops-lifecycle.js +56 -0
- package/dist/src/git/merge-prep-workspace.js +357 -0
- package/dist/src/git/types.js +484 -0
- package/dist/src/github/broker-model-content.js +215 -0
- package/dist/src/github/broker-server.js +237 -0
- package/dist/src/github/broker.js +638 -0
- package/dist/src/github/composed.js +53 -0
- package/dist/src/github/gateway.js +478 -0
- package/dist/src/github/normalize.js +222 -0
- package/dist/src/github/pr-classification.js +73 -0
- package/dist/src/github/scoped-broker.js +35 -0
- package/dist/src/github/security.js +126 -0
- package/dist/src/github/service.js +332 -0
- package/dist/src/github/types.js +1 -0
- package/dist/src/github/utils.js +16 -0
- package/dist/src/journal/index.js +4 -0
- package/dist/src/journal/reader.js +62 -0
- package/dist/src/journal/renderer.js +50 -0
- package/dist/src/journal/service.js +72 -0
- package/dist/src/journal/types.js +1 -0
- package/dist/src/journal/writer.js +19 -0
- package/dist/src/mcps/bridge.js +166 -0
- package/dist/src/mcps/index.js +5 -0
- package/dist/src/mcps/reconcile.js +113 -0
- package/dist/src/mcps/smithery-client.js +186 -0
- package/dist/src/mcps/smithery.js +164 -0
- package/dist/src/mcps/types.js +1 -0
- package/dist/src/memory/json-store.js +71 -0
- package/dist/src/memory/prompt.js +16 -0
- package/dist/src/memory/scope.js +31 -0
- package/dist/src/memory/store.js +1 -0
- package/dist/src/memory/tool-service.js +142 -0
- package/dist/src/memory/types.js +12 -0
- package/dist/src/onboarding/config-writer.js +96 -0
- package/dist/src/onboarding/engine.js +86 -0
- package/dist/src/onboarding/prompt.js +102 -0
- package/dist/src/onboarding/services/provider-discovery.js +58 -0
- package/dist/src/onboarding/services/repo-discovery.js +246 -0
- package/dist/src/onboarding/steps/check-github-auth.js +32 -0
- package/dist/src/onboarding/steps/check-requirements.js +34 -0
- package/dist/src/onboarding/steps/configure-models.js +105 -0
- package/dist/src/onboarding/steps/configure-paths.js +33 -0
- package/dist/src/onboarding/steps/discover-providers.js +38 -0
- package/dist/src/onboarding/steps/discover-repos.js +39 -0
- package/dist/src/onboarding/steps/generate-config.js +106 -0
- package/dist/src/onboarding/steps/readiness-validation.js +158 -0
- package/dist/src/onboarding/steps/select-repos.js +76 -0
- package/dist/src/onboarding/steps/show-summary.js +97 -0
- package/dist/src/onboarding/steps/welcome.js +27 -0
- package/dist/src/onboarding/types.js +1 -0
- package/dist/src/onboarding/validators/git-access.js +63 -0
- package/dist/src/onboarding/validators/github-auth.js +85 -0
- package/dist/src/onboarding/validators/tool-checker.js +153 -0
- package/dist/src/onboarding/validators/workspace.js +83 -0
- package/dist/src/prompts/resolver.js +106 -0
- package/dist/src/retrospectives/candidates.js +224 -0
- package/dist/src/retrospectives/derive.js +169 -0
- package/dist/src/retrospectives/ingest.js +321 -0
- package/dist/src/retrospectives/management-prs.js +380 -0
- package/dist/src/retrospectives/proposals.js +199 -0
- package/dist/src/retrospectives/scopes.js +272 -0
- package/dist/src/retrospectives/scoring.js +171 -0
- package/dist/src/retrospectives/types.js +1 -0
- package/dist/src/state/loader.js +473 -0
- package/dist/src/state/types.js +15 -0
- package/dist/src/tasks/derivation/builders.js +70 -0
- package/dist/src/tasks/derivation/handlers/comment-response.js +169 -0
- package/dist/src/tasks/derivation/handlers/implement-plan.js +103 -0
- package/dist/src/tasks/derivation/handlers/index.js +54 -0
- package/dist/src/tasks/derivation/handlers/packet-pr-control.js +92 -0
- package/dist/src/tasks/derivation/handlers/packet-worktree.js +29 -0
- package/dist/src/tasks/derivation/handlers/plan-breakdown.js +45 -0
- package/dist/src/tasks/derivation/handlers/prepare-for-merge.js +91 -0
- package/dist/src/tasks/derivation/handlers/product-discovery.js +36 -0
- package/dist/src/tasks/derivation/handlers/retry-failed-task.js +125 -0
- package/dist/src/tasks/derivation/handlers/review-pr.js +28 -0
- package/dist/src/tasks/derivation/handlers/shared.js +24 -0
- package/dist/src/tasks/derivation/handlers/sync-parent-branch.js +100 -0
- package/dist/src/tasks/derivation/handlers/wave-assessment.js +223 -0
- package/dist/src/tasks/derivation/handlers/write-plan.js +101 -0
- package/dist/src/tasks/derivation/handlers/write-product-spec.js +49 -0
- package/dist/src/tasks/derivation/runner.js +18 -0
- package/dist/src/tasks/derivation/types.js +1 -0
- package/dist/src/tasks/derive.js +810 -0
- package/dist/src/tasks/mcp-skip.js +10 -0
- package/dist/src/tasks/retry.js +10 -0
- package/dist/src/tasks/task.js +184 -0
- package/dist/src/tasks/types.js +1 -0
- package/dist/src/utils/command-runner.js +192 -0
- package/dist/src/utils/json.js +38 -0
- package/dist/src/utils/logger.js +43 -0
- package/dist/src/utils/paths.js +38 -0
- package/dist/src/utils/slug.js +10 -0
- package/dist/src/workflows/registry.js +174 -0
- package/dist/src/workflows/triage.js +90 -0
- package/dist/src/workflows/workstream-machine/derive-events.js +121 -0
- package/dist/src/workflows/workstream-machine/evaluate.js +24 -0
- package/dist/src/workflows/workstream-machine/events.js +1 -0
- package/dist/src/workflows/workstream-machine/machine.js +355 -0
- package/dist/src/workflows/workstream-machine/selectors.js +19 -0
- package/dist/src/workflows/workstream-machine/types.js +1 -0
- package/dist/src/workstreams/branch-sync.js +87 -0
- package/dist/src/workstreams/derive.js +629 -0
- package/dist/src/workstreams/mcp-blockers.js +420 -0
- package/dist/src/workstreams/packet-candidates.js +42 -0
- package/dist/src/workstreams/packet-lifecycle-feedback.js +60 -0
- package/dist/src/workstreams/packet-lifecycle-report.js +199 -0
- package/dist/src/workstreams/packet-plan.js +597 -0
- package/dist/src/workstreams/packet-projections.js +190 -0
- package/dist/src/workstreams/parent-branch-sync-action.js +312 -0
- package/dist/src/workstreams/parent-branch-sync.js +148 -0
- package/dist/src/workstreams/phases.js +4 -0
- package/dist/src/workstreams/plan-breakdown-generator/export.js +19 -0
- package/dist/src/workstreams/plan-breakdown-generator/index.js +6 -0
- package/dist/src/workstreams/plan-breakdown-generator/inspect.js +50 -0
- package/dist/src/workstreams/plan-breakdown-generator/patch.js +139 -0
- package/dist/src/workstreams/plan-breakdown-generator/session.js +19 -0
- package/dist/src/workstreams/plan-breakdown-generator/store.js +27 -0
- package/dist/src/workstreams/plan-breakdown-generator/tools.js +407 -0
- package/dist/src/workstreams/plan-breakdown-generator/types.js +6 -0
- package/dist/src/workstreams/plan-breakdown-generator/validate.js +212 -0
- package/dist/src/workstreams/plan-breakdown-schema.js +175 -0
- package/dist/src/workstreams/utils.js +15 -0
- package/dist/src/workstreams/wave-assessment/checkpoint.js +172 -0
- package/dist/src/workstreams/wave-assessment/index.js +3 -0
- package/dist/src/workstreams/wave-assessment/session.js +84 -0
- package/dist/src/workstreams/wave-assessment/store.js +51 -0
- package/dist/src/workstreams/wave-assessment/tools.js +419 -0
- package/dist/src/workstreams/wave-assessment/types.js +1 -0
- package/dist/src/workstreams/wave-assessment/validate.js +78 -0
- package/dist/src/worktrees/service.js +839 -0
- package/dist/src/worktrees/workspace-ownership.js +34 -0
- package/dist/src/worktrees/workspace-recovery.js +177 -0
- package/package.json +61 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { mkdir, readFile, realpath, writeFile } from "node:fs/promises";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { writeJsonFileAtomic } from "../utils/json.js";
|
|
5
|
+
import { buildAntigravityCliArgs, ensureAntigravityMcpConfig, getAntigravityEnvOverrides, mapPermissionToAntigravityMode, prependGlobalAntigravityLaunchInstructions, writeAntigravityMcpConfig, writeAntigravitySettings } from "./antigravity-launch.js";
|
|
6
|
+
import { buildExecutionId, extractAndExportPlanBreakdownResult, extractAndExportWaveAssessmentResult, extractExitCode, extractReviewResult, extractStderr, extractStdout, extractTriageResult, detectProviderLimit } from "./extraction.js";
|
|
7
|
+
import { buildPromptPacket, renderPrompt } from "./prompt.js";
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 20 * 60 * 1000;
|
|
9
|
+
const DEFAULT_PRINT_TIMEOUT = "20m";
|
|
10
|
+
export class AntigravityCliAgent {
|
|
11
|
+
options;
|
|
12
|
+
now;
|
|
13
|
+
timeoutMs;
|
|
14
|
+
printTimeout;
|
|
15
|
+
logger;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.options = options;
|
|
18
|
+
this.now = options.now ?? (() => new Date().toISOString());
|
|
19
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
20
|
+
this.printTimeout = options.printTimeout ?? DEFAULT_PRINT_TIMEOUT;
|
|
21
|
+
this.logger = options.logger;
|
|
22
|
+
}
|
|
23
|
+
async dispatch(task, context, agentConfig, _sessionDecision, promptContent) {
|
|
24
|
+
const promptPacket = buildPromptPacket(task, context);
|
|
25
|
+
const promptText = prependGlobalAntigravityLaunchInstructions(renderPrompt(task, promptPacket, {
|
|
26
|
+
providerIdentity: "Antigravity CLI",
|
|
27
|
+
customConstraints: promptContent?.content,
|
|
28
|
+
learningOverlays: promptContent?.learningOverlays
|
|
29
|
+
}), { ...(this.options.githubBroker ? { mcp: this.options.githubBroker } : {}) });
|
|
30
|
+
const startedAt = this.now();
|
|
31
|
+
const executionId = buildExecutionId(task.id, startedAt);
|
|
32
|
+
const artifactDir = path.join(this.options.logDir, "artifacts", executionId);
|
|
33
|
+
const promptPacketPath = path.join(artifactDir, "prompt-packet.json");
|
|
34
|
+
const promptArtifactPath = path.join(artifactDir, "prompt.md");
|
|
35
|
+
const stdoutArtifactPath = path.join(artifactDir, "stdout.txt");
|
|
36
|
+
const stderrArtifactPath = path.join(artifactDir, "stderr.txt");
|
|
37
|
+
const streamArtifactPath = path.join(artifactDir, "stdout-raw.txt");
|
|
38
|
+
const settingsArtifactPath = path.join(artifactDir, "antigravity-settings.json");
|
|
39
|
+
const mcpConfigArtifactPath = path.join(artifactDir, "antigravity-mcp-config.json");
|
|
40
|
+
const logFilePath = path.join(artifactDir, "antigravity.log");
|
|
41
|
+
const antigravityHome = this.options.configHome ?? path.join(os.homedir(), ".gemini", "antigravity-cli");
|
|
42
|
+
const legacyGeminiConfigHome = this.options.legacyGeminiConfigHome ??
|
|
43
|
+
(this.options.configHome ? undefined : path.join(os.homedir(), ".gemini", "config"));
|
|
44
|
+
const cwd = context.localRepoPath ?? process.cwd();
|
|
45
|
+
const permissionMode = mapPermissionToAntigravityMode(task.executionPermission);
|
|
46
|
+
const resumeConversationId = _sessionDecision?.mode === "native_resume_candidate"
|
|
47
|
+
? _sessionDecision.record.providerSessionId
|
|
48
|
+
: undefined;
|
|
49
|
+
await mkdir(artifactDir, { recursive: true });
|
|
50
|
+
await Promise.all([
|
|
51
|
+
writeJsonFileAtomic(promptPacketPath, promptPacket),
|
|
52
|
+
writeFile(promptArtifactPath, promptText, "utf8"),
|
|
53
|
+
writeAntigravitySettings(settingsArtifactPath, {
|
|
54
|
+
permissionMode,
|
|
55
|
+
cwd,
|
|
56
|
+
...(agentConfig.model ? { model: agentConfig.model } : {})
|
|
57
|
+
}),
|
|
58
|
+
writeAntigravityMcpConfig(mcpConfigArtifactPath, {
|
|
59
|
+
...(this.options.githubBroker ? { mcp: this.options.githubBroker } : {})
|
|
60
|
+
})
|
|
61
|
+
]);
|
|
62
|
+
const args = buildAntigravityCliArgs({
|
|
63
|
+
permissionMode,
|
|
64
|
+
...(agentConfig.model ? { model: agentConfig.model } : {}),
|
|
65
|
+
...(resumeConversationId ? { conversationId: resumeConversationId } : {}),
|
|
66
|
+
printTimeout: this.printTimeout,
|
|
67
|
+
logFilePath
|
|
68
|
+
});
|
|
69
|
+
this.logger?.info(JSON.stringify({
|
|
70
|
+
component: "antigravity-launch",
|
|
71
|
+
taskKind: task.kind,
|
|
72
|
+
targetRepoPath: cwd,
|
|
73
|
+
antigravityHome,
|
|
74
|
+
settingsPath: settingsArtifactPath,
|
|
75
|
+
mcpConfigPath: mcpConfigArtifactPath,
|
|
76
|
+
legacyGeminiMcpConfigPath: legacyGeminiConfigHome
|
|
77
|
+
? path.join(legacyGeminiConfigHome, "mcp_config.json")
|
|
78
|
+
: undefined,
|
|
79
|
+
mcpServerUrl: this.options.githubBroker?.serverUrl
|
|
80
|
+
}));
|
|
81
|
+
let exitCode = 0;
|
|
82
|
+
let rawStdout = "";
|
|
83
|
+
let rawStderr = "";
|
|
84
|
+
try {
|
|
85
|
+
if (this.options.githubBroker) {
|
|
86
|
+
const setup = await ensureAntigravityMcpConfig(antigravityHome, this.options.githubBroker);
|
|
87
|
+
this.logger?.info(JSON.stringify({
|
|
88
|
+
component: "antigravity-mcp-bootstrap",
|
|
89
|
+
configPath: setup.configPath,
|
|
90
|
+
changed: setup.changed,
|
|
91
|
+
backupPath: setup.backupPath
|
|
92
|
+
}));
|
|
93
|
+
if (legacyGeminiConfigHome) {
|
|
94
|
+
const legacySetup = await ensureAntigravityMcpConfig(legacyGeminiConfigHome, this.options.githubBroker);
|
|
95
|
+
this.logger?.info(JSON.stringify({
|
|
96
|
+
component: "antigravity-legacy-gemini-mcp-bootstrap",
|
|
97
|
+
configPath: legacySetup.configPath,
|
|
98
|
+
changed: legacySetup.changed,
|
|
99
|
+
backupPath: legacySetup.backupPath
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const result = await this.options.commandRunner.run(agentConfig.command, args, {
|
|
104
|
+
cwd,
|
|
105
|
+
input: promptText,
|
|
106
|
+
env: getAntigravityEnvOverrides(),
|
|
107
|
+
timeoutMs: this.timeoutMs
|
|
108
|
+
});
|
|
109
|
+
exitCode = result.exitCode;
|
|
110
|
+
rawStdout = result.stdout;
|
|
111
|
+
rawStderr = result.stderr;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
exitCode = extractExitCode(error);
|
|
115
|
+
rawStdout = extractStdout(error);
|
|
116
|
+
rawStderr = extractStderr(error) || (error instanceof Error ? error.message : String(error));
|
|
117
|
+
}
|
|
118
|
+
const stdout = normalizeAntigravityStdout(rawStdout);
|
|
119
|
+
const endedAt = this.now();
|
|
120
|
+
const observedConversationId = await readAntigravityConversationIdForCwd(antigravityHome, cwd)
|
|
121
|
+
.catch((err) => {
|
|
122
|
+
this.logger?.warn(`[antigravity-session] Failed to read conversation id for ${cwd}: ${err instanceof Error ? err.message : String(err)}`);
|
|
123
|
+
return undefined;
|
|
124
|
+
});
|
|
125
|
+
const finalConversationId = observedConversationId ?? resumeConversationId;
|
|
126
|
+
await Promise.all([
|
|
127
|
+
writeFile(streamArtifactPath, rawStdout, "utf8"),
|
|
128
|
+
writeFile(stdoutArtifactPath, stdout, "utf8"),
|
|
129
|
+
writeFile(stderrArtifactPath, rawStderr, "utf8")
|
|
130
|
+
]);
|
|
131
|
+
const triageResult = extractTriageResult(task, stdout);
|
|
132
|
+
const reviewResult = extractReviewResult(task, stdout);
|
|
133
|
+
const planBreakdownExtraction = extractAndExportPlanBreakdownResult(task, stdout, this.options.planBreakdownSessionStore);
|
|
134
|
+
const waveAssessmentExtraction = extractAndExportWaveAssessmentResult(task, stdout, this.options.waveAssessmentSessionStore);
|
|
135
|
+
let taskStatus = exitCode === 0 ? "completed" : "failed";
|
|
136
|
+
let errorMessage = exitCode === 0 ? undefined : rawStderr.trim() || "Antigravity CLI exited with a non-zero status.";
|
|
137
|
+
if (task.kind === "review_pr" && exitCode === 0 && !reviewResult) {
|
|
138
|
+
taskStatus = "failed";
|
|
139
|
+
errorMessage = "Agent output did not contain a valid structured review result.";
|
|
140
|
+
}
|
|
141
|
+
if ((task.kind === "plan_breakdown" || task.kind === "wave_plan_replanning") &&
|
|
142
|
+
exitCode === 0 &&
|
|
143
|
+
!planBreakdownExtraction.ok) {
|
|
144
|
+
taskStatus = "failed";
|
|
145
|
+
errorMessage = planBreakdownExtraction.message;
|
|
146
|
+
}
|
|
147
|
+
if (task.kind === "wave_plan_assessment" && exitCode === 0 && !waveAssessmentExtraction.ok) {
|
|
148
|
+
taskStatus = "failed";
|
|
149
|
+
errorMessage = waveAssessmentExtraction.message;
|
|
150
|
+
}
|
|
151
|
+
const execution = {
|
|
152
|
+
id: executionId,
|
|
153
|
+
taskId: task.id,
|
|
154
|
+
taskKind: task.kind,
|
|
155
|
+
taskStatus,
|
|
156
|
+
repoId: task.repoId,
|
|
157
|
+
repoSlug: task.repoSlug,
|
|
158
|
+
agentId: agentConfig.id,
|
|
159
|
+
agentProvider: "antigravity",
|
|
160
|
+
...(agentConfig.model ? { agentModel: agentConfig.model } : {}),
|
|
161
|
+
command: {
|
|
162
|
+
executable: agentConfig.command,
|
|
163
|
+
args,
|
|
164
|
+
cwd,
|
|
165
|
+
timeoutMs: this.timeoutMs
|
|
166
|
+
},
|
|
167
|
+
promptArtifactPath,
|
|
168
|
+
stdoutArtifactPath,
|
|
169
|
+
stderrArtifactPath,
|
|
170
|
+
streamArtifactPath,
|
|
171
|
+
promptPacketPath,
|
|
172
|
+
settingsArtifactPath,
|
|
173
|
+
startedAt,
|
|
174
|
+
endedAt,
|
|
175
|
+
exitCode,
|
|
176
|
+
...(errorMessage ? { errorMessage } : {}),
|
|
177
|
+
...(triageResult || reviewResult || planBreakdownExtraction.ok || waveAssessmentExtraction.ok
|
|
178
|
+
? {
|
|
179
|
+
result: {
|
|
180
|
+
...(triageResult ? { triage: triageResult } : {}),
|
|
181
|
+
...(reviewResult ? { review: reviewResult } : {}),
|
|
182
|
+
...(planBreakdownExtraction.ok
|
|
183
|
+
? {
|
|
184
|
+
planBreakdown: planBreakdownExtraction.planBreakdown,
|
|
185
|
+
planBreakdownSessionId: planBreakdownExtraction.sessionId
|
|
186
|
+
}
|
|
187
|
+
: {}),
|
|
188
|
+
...(waveAssessmentExtraction.ok
|
|
189
|
+
? {
|
|
190
|
+
waveAssessment: waveAssessmentExtraction.waveAssessment
|
|
191
|
+
}
|
|
192
|
+
: {})
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
: {})
|
|
196
|
+
};
|
|
197
|
+
const providerLimitSignal = detectProviderLimit("antigravity", exitCode, rawStdout, rawStderr);
|
|
198
|
+
return {
|
|
199
|
+
taskStatus: execution.taskStatus,
|
|
200
|
+
execution,
|
|
201
|
+
...(providerLimitSignal ? { providerLimitSignal } : {}),
|
|
202
|
+
...(finalConversationId
|
|
203
|
+
? {
|
|
204
|
+
providerSession: {
|
|
205
|
+
providerSessionId: finalConversationId,
|
|
206
|
+
status: "active"
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
: {})
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
export function normalizeAntigravityStdout(stdout) {
|
|
214
|
+
return stdout
|
|
215
|
+
.split(/\r?\n/)
|
|
216
|
+
.filter((line) => line.trim() !== "--print-timeout")
|
|
217
|
+
.join("\n")
|
|
218
|
+
.trimEnd();
|
|
219
|
+
}
|
|
220
|
+
export async function readAntigravityConversationIdForCwd(antigravityHome, cwd) {
|
|
221
|
+
const cachePath = path.join(antigravityHome, "cache", "last_conversations.json");
|
|
222
|
+
let raw;
|
|
223
|
+
try {
|
|
224
|
+
raw = await readFile(cachePath, "utf8");
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
throw err;
|
|
231
|
+
}
|
|
232
|
+
const parsed = JSON.parse(raw);
|
|
233
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
const conversations = parsed;
|
|
237
|
+
const candidates = new Set([cwd]);
|
|
238
|
+
try {
|
|
239
|
+
candidates.add(await realpath(cwd));
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
// The cwd may have been removed after the run; fall back to the literal path.
|
|
243
|
+
}
|
|
244
|
+
for (const candidate of candidates) {
|
|
245
|
+
const value = conversations[candidate];
|
|
246
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
247
|
+
return value;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
/**
|
|
3
|
+
* Maps orchestrator permissions to Claude Code CLI permission modes.
|
|
4
|
+
* - read-only -> dontAsk
|
|
5
|
+
* - workspace-write -> bypassPermissions (allows running local bash commands like npm, docker-compose)
|
|
6
|
+
*/
|
|
7
|
+
export function mapPermissionToClaudePermissionMode(permission) {
|
|
8
|
+
return permission === "workspace-write" ? "bypassPermissions" : "dontAsk";
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Builds CLI arguments for Claude Code CLI.
|
|
12
|
+
* Uses -p for print mode (non-interactive).
|
|
13
|
+
*/
|
|
14
|
+
export function buildClaudeCliArgs(options) {
|
|
15
|
+
const args = ["-p", "--permission-mode", options.permissionMode];
|
|
16
|
+
if (options.resumeSessionId) {
|
|
17
|
+
args.push("--resume", options.resumeSessionId);
|
|
18
|
+
}
|
|
19
|
+
else if (options.plannedSessionId) {
|
|
20
|
+
args.push("--session-id", options.plannedSessionId);
|
|
21
|
+
if (options.sessionName) {
|
|
22
|
+
args.push("--name", options.sessionName);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (options.model) {
|
|
26
|
+
args.push("--model", options.model);
|
|
27
|
+
}
|
|
28
|
+
if (options.settingsPath) {
|
|
29
|
+
args.push("--settings", options.settingsPath);
|
|
30
|
+
}
|
|
31
|
+
if (options.mcpConfigPath) {
|
|
32
|
+
args.push("--mcp-config", options.mcpConfigPath, "--strict-mcp-config");
|
|
33
|
+
}
|
|
34
|
+
if (options.allowedTools && options.allowedTools.length > 0) {
|
|
35
|
+
args.push("--allowedTools", options.allowedTools.join(","));
|
|
36
|
+
}
|
|
37
|
+
return args;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Builds the MCP configuration object for Claude.
|
|
41
|
+
*/
|
|
42
|
+
export function buildClaudeMcpConfig(mcp) {
|
|
43
|
+
return {
|
|
44
|
+
mcpServers: {
|
|
45
|
+
[mcp.serverName]: {
|
|
46
|
+
type: "http",
|
|
47
|
+
url: mcp.serverUrl
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Builds the settings configuration object for Claude.
|
|
54
|
+
*/
|
|
55
|
+
export function buildClaudeSettings(options) {
|
|
56
|
+
if (options.workspacePath) {
|
|
57
|
+
return { trustedWorkspaces: [options.workspacePath] };
|
|
58
|
+
}
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Writes the MCP configuration to a file.
|
|
63
|
+
*/
|
|
64
|
+
export async function writeClaudeMcpConfig(path, mcp) {
|
|
65
|
+
const config = buildClaudeMcpConfig(mcp);
|
|
66
|
+
await fs.writeFile(path, JSON.stringify(config, null, 2));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Writes the settings configuration to a file.
|
|
70
|
+
*/
|
|
71
|
+
export async function writeClaudeSettings(path, options) {
|
|
72
|
+
const settings = buildClaudeSettings(options);
|
|
73
|
+
await fs.writeFile(path, JSON.stringify(settings, null, 2));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Generates the allow-list of tool names for Claude's --allowedTools flag.
|
|
77
|
+
* Names follow the mcp__<serverName>__<toolName> pattern.
|
|
78
|
+
*/
|
|
79
|
+
export function buildClaudeAllowedTools(mcp) {
|
|
80
|
+
return mcp.toolAllowList.map((tool) => {
|
|
81
|
+
const normalizedToolName = tool.replaceAll(".", "_");
|
|
82
|
+
return `mcp__${mcp.serverName}__${normalizedToolName}`;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Provides global launch instructions for Claude agents.
|
|
87
|
+
*/
|
|
88
|
+
export function buildGlobalClaudeLaunchInstructions(input) {
|
|
89
|
+
const tools = buildClaudeAllowedTools(input.mcp).join(", ");
|
|
90
|
+
return `
|
|
91
|
+
# Claude Code CLI Launch Instructions
|
|
92
|
+
|
|
93
|
+
You are running inside a managed orchestrator.
|
|
94
|
+
A local MCP server named "${input.mcp.serverName}" is available and pre-authorized.
|
|
95
|
+
The following tools are allowed: ${tools}.
|
|
96
|
+
|
|
97
|
+
## Operational Rules
|
|
98
|
+
|
|
99
|
+
1. Use the local MCP server for all GitHub operations (issues, PRs, comments, code search).
|
|
100
|
+
2. Do not use the "gh" CLI or other external GitHub tools directly for these tasks.
|
|
101
|
+
3. If a required GitHub capability is missing from the MCP server, report it as a limitation rather than attempting a bypass.
|
|
102
|
+
4. Adhere to the repository-local AGENTS.md instructions if present.
|
|
103
|
+
5. Any message you post to GitHub (via comments or PR descriptions) MUST begin with the "🤖" emoji.
|
|
104
|
+
|
|
105
|
+
Ready for task execution.
|
|
106
|
+
`.trim();
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Prepends global instructions to a prompt if MCP is configured.
|
|
110
|
+
*/
|
|
111
|
+
export function prependGlobalClaudeLaunchInstructions(promptText, options) {
|
|
112
|
+
if (!options.mcp) {
|
|
113
|
+
return promptText;
|
|
114
|
+
}
|
|
115
|
+
const instructions = buildGlobalClaudeLaunchInstructions({ mcp: options.mcp });
|
|
116
|
+
return `${instructions}\n\n${promptText}`;
|
|
117
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { copyFile, mkdir, readdir, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { writeJsonFileAtomic } from "../utils/json.js";
|
|
5
|
+
import { buildClaudeAllowedTools, buildClaudeCliArgs, mapPermissionToClaudePermissionMode, prependGlobalClaudeLaunchInstructions, writeClaudeMcpConfig, writeClaudeSettings } from "./claude-launch.js";
|
|
6
|
+
import { buildExecutionId, extractExitCode, extractAndExportPlanBreakdownResult, extractAndExportWaveAssessmentResult, extractReviewResult, extractStderr, extractStdout, extractTriageResult, detectProviderLimit } from "./extraction.js";
|
|
7
|
+
import { buildPromptPacket, renderPrompt } from "./prompt.js";
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 20 * 60 * 1000;
|
|
9
|
+
export class ClaudeCodeCliAgent {
|
|
10
|
+
options;
|
|
11
|
+
now;
|
|
12
|
+
timeoutMs;
|
|
13
|
+
logger;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
this.now = options.now ?? (() => new Date().toISOString());
|
|
17
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
18
|
+
this.logger = options.logger;
|
|
19
|
+
}
|
|
20
|
+
async dispatch(task, context, agentConfig, sessionDecision, promptContent) {
|
|
21
|
+
const promptPacket = buildPromptPacket(task, context);
|
|
22
|
+
const promptText = prependGlobalClaudeLaunchInstructions(renderPrompt(task, promptPacket, {
|
|
23
|
+
providerIdentity: "Claude Code CLI",
|
|
24
|
+
customConstraints: promptContent?.content,
|
|
25
|
+
learningOverlays: promptContent?.learningOverlays
|
|
26
|
+
}), { ...(this.options.githubBroker ? { mcp: this.options.githubBroker } : {}) });
|
|
27
|
+
const startedAt = this.now();
|
|
28
|
+
const executionId = buildExecutionId(task.id, startedAt);
|
|
29
|
+
const artifactDir = path.join(this.options.logDir, "artifacts", executionId);
|
|
30
|
+
const promptPacketPath = path.join(artifactDir, "prompt-packet.json");
|
|
31
|
+
const promptArtifactPath = path.join(artifactDir, "prompt.md");
|
|
32
|
+
const stdoutArtifactPath = path.join(artifactDir, "stdout.txt");
|
|
33
|
+
const stderrArtifactPath = path.join(artifactDir, "stderr.txt");
|
|
34
|
+
const providerTranscriptArtifactPath = path.join(artifactDir, "claude-project-transcript.jsonl");
|
|
35
|
+
const mcpConfigPath = path.join(artifactDir, "claude-mcp-config.json");
|
|
36
|
+
const settingsArtifactPath = path.join(artifactDir, "claude-settings.json");
|
|
37
|
+
await mkdir(artifactDir, { recursive: true });
|
|
38
|
+
const cwd = context.localRepoPath ?? process.cwd();
|
|
39
|
+
const writeTasks = [
|
|
40
|
+
writeJsonFileAtomic(promptPacketPath, promptPacket),
|
|
41
|
+
writeFile(promptArtifactPath, promptText, "utf8")
|
|
42
|
+
];
|
|
43
|
+
if (this.options.githubBroker) {
|
|
44
|
+
writeTasks.push(writeClaudeMcpConfig(mcpConfigPath, this.options.githubBroker));
|
|
45
|
+
writeTasks.push(writeClaudeSettings(settingsArtifactPath, {
|
|
46
|
+
mcp: this.options.githubBroker,
|
|
47
|
+
workspacePath: cwd
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
await Promise.all(writeTasks);
|
|
51
|
+
const permissionMode = mapPermissionToClaudePermissionMode(task.executionPermission);
|
|
52
|
+
const resumeSessionId = sessionDecision?.mode === "native_resume_candidate"
|
|
53
|
+
? sessionDecision.record.providerSessionId
|
|
54
|
+
: undefined;
|
|
55
|
+
const plannedSessionId = sessionDecision?.mode === "new_session"
|
|
56
|
+
? sessionDecision.providerSessionId
|
|
57
|
+
: undefined;
|
|
58
|
+
const sessionName = sessionDecision?.mode === "new_session"
|
|
59
|
+
? sessionDecision.providerSessionName
|
|
60
|
+
: undefined;
|
|
61
|
+
const args = buildClaudeCliArgs({
|
|
62
|
+
permissionMode,
|
|
63
|
+
resumeSessionId,
|
|
64
|
+
plannedSessionId,
|
|
65
|
+
sessionName,
|
|
66
|
+
...(agentConfig.model ? { model: agentConfig.model } : {}),
|
|
67
|
+
...(this.options.githubBroker
|
|
68
|
+
? {
|
|
69
|
+
mcpConfigPath,
|
|
70
|
+
settingsPath: settingsArtifactPath,
|
|
71
|
+
allowedTools: buildClaudeAllowedTools(this.options.githubBroker)
|
|
72
|
+
}
|
|
73
|
+
: {})
|
|
74
|
+
});
|
|
75
|
+
this.logger?.info(JSON.stringify({
|
|
76
|
+
component: "claude-launch",
|
|
77
|
+
taskKind: task.kind,
|
|
78
|
+
targetRepoPath: cwd,
|
|
79
|
+
mcpConfigPath: this.options.githubBroker ? mcpConfigPath : undefined,
|
|
80
|
+
mcpServerUrl: this.options.githubBroker?.serverUrl
|
|
81
|
+
}));
|
|
82
|
+
let exitCode = 0;
|
|
83
|
+
let stdout = "";
|
|
84
|
+
let stderr = "";
|
|
85
|
+
try {
|
|
86
|
+
const result = await this.options.commandRunner.run(agentConfig.command, args, {
|
|
87
|
+
cwd,
|
|
88
|
+
input: promptText,
|
|
89
|
+
timeoutMs: this.timeoutMs
|
|
90
|
+
});
|
|
91
|
+
exitCode = result.exitCode;
|
|
92
|
+
stdout = result.stdout;
|
|
93
|
+
stderr = result.stderr;
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
exitCode = extractExitCode(error);
|
|
97
|
+
stdout = extractStdout(error);
|
|
98
|
+
stderr = extractStderr(error);
|
|
99
|
+
}
|
|
100
|
+
const endedAt = this.now();
|
|
101
|
+
const originalStderr = stderr;
|
|
102
|
+
const transcriptCapture = await captureClaudeProjectTranscript({
|
|
103
|
+
cwd,
|
|
104
|
+
startedAt,
|
|
105
|
+
artifactPath: providerTranscriptArtifactPath,
|
|
106
|
+
...(this.options.claudeProjectsDir ? { projectsDir: this.options.claudeProjectsDir } : {}),
|
|
107
|
+
...(this.logger ? { logger: this.logger } : {})
|
|
108
|
+
});
|
|
109
|
+
if (transcriptCapture && exitCode !== 0 && stderr.trim() === "") {
|
|
110
|
+
stderr = [
|
|
111
|
+
`Claude project transcript captured at ${providerTranscriptArtifactPath}`,
|
|
112
|
+
`Source: ${transcriptCapture.sourcePath}`
|
|
113
|
+
].join("\n");
|
|
114
|
+
}
|
|
115
|
+
await Promise.all([
|
|
116
|
+
writeFile(stdoutArtifactPath, stdout, "utf8"),
|
|
117
|
+
writeFile(stderrArtifactPath, stderr, "utf8")
|
|
118
|
+
]);
|
|
119
|
+
const triageResult = extractTriageResult(task, stdout);
|
|
120
|
+
const reviewResult = extractReviewResult(task, stdout);
|
|
121
|
+
const planBreakdownExtraction = extractAndExportPlanBreakdownResult(task, stdout, this.options.planBreakdownSessionStore);
|
|
122
|
+
const waveAssessmentExtraction = extractAndExportWaveAssessmentResult(task, stdout, this.options.waveAssessmentSessionStore);
|
|
123
|
+
let taskStatus = exitCode === 0 ? "completed" : "failed";
|
|
124
|
+
let errorMessage = exitCode === 0 ? undefined : originalStderr.trim() || "Claude Code CLI exited with a non-zero status.";
|
|
125
|
+
if (task.kind === "review_pr" && exitCode === 0 && !reviewResult) {
|
|
126
|
+
taskStatus = "failed";
|
|
127
|
+
errorMessage = "Agent output did not contain a valid structured review result.";
|
|
128
|
+
}
|
|
129
|
+
if ((task.kind === "plan_breakdown" || task.kind === "wave_plan_replanning") && exitCode === 0) {
|
|
130
|
+
if (!planBreakdownExtraction.ok) {
|
|
131
|
+
taskStatus = "failed";
|
|
132
|
+
errorMessage = planBreakdownExtraction.message;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (task.kind === "wave_plan_assessment" && exitCode === 0) {
|
|
136
|
+
if (!waveAssessmentExtraction.ok) {
|
|
137
|
+
taskStatus = "failed";
|
|
138
|
+
errorMessage = waveAssessmentExtraction.message;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const execution = {
|
|
142
|
+
id: executionId,
|
|
143
|
+
taskId: task.id,
|
|
144
|
+
taskKind: task.kind,
|
|
145
|
+
taskStatus,
|
|
146
|
+
repoId: task.repoId,
|
|
147
|
+
repoSlug: task.repoSlug,
|
|
148
|
+
agentId: agentConfig.id,
|
|
149
|
+
agentProvider: "claude",
|
|
150
|
+
...(agentConfig.model ? { agentModel: agentConfig.model } : {}),
|
|
151
|
+
command: {
|
|
152
|
+
executable: agentConfig.command,
|
|
153
|
+
args,
|
|
154
|
+
cwd,
|
|
155
|
+
timeoutMs: this.timeoutMs
|
|
156
|
+
},
|
|
157
|
+
promptArtifactPath,
|
|
158
|
+
stdoutArtifactPath,
|
|
159
|
+
stderrArtifactPath,
|
|
160
|
+
...(transcriptCapture ? { providerTranscriptArtifactPath } : {}),
|
|
161
|
+
promptPacketPath,
|
|
162
|
+
...(this.options.githubBroker ? { settingsArtifactPath } : {}),
|
|
163
|
+
startedAt,
|
|
164
|
+
endedAt,
|
|
165
|
+
exitCode,
|
|
166
|
+
...(errorMessage ? { errorMessage } : {}),
|
|
167
|
+
...(triageResult || reviewResult || planBreakdownExtraction.ok || waveAssessmentExtraction.ok
|
|
168
|
+
? {
|
|
169
|
+
result: {
|
|
170
|
+
...(triageResult ? { triage: triageResult } : {}),
|
|
171
|
+
...(reviewResult ? { review: reviewResult } : {}),
|
|
172
|
+
...(planBreakdownExtraction.ok
|
|
173
|
+
? {
|
|
174
|
+
planBreakdown: planBreakdownExtraction.planBreakdown,
|
|
175
|
+
planBreakdownSessionId: planBreakdownExtraction.sessionId
|
|
176
|
+
}
|
|
177
|
+
: {}),
|
|
178
|
+
...(waveAssessmentExtraction.ok
|
|
179
|
+
? {
|
|
180
|
+
waveAssessment: waveAssessmentExtraction.waveAssessment
|
|
181
|
+
}
|
|
182
|
+
: {})
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
: {})
|
|
186
|
+
};
|
|
187
|
+
const parsedSessionId = extractSessionIdFromClaudeOutput(stdout);
|
|
188
|
+
const finalSessionId = parsedSessionId || plannedSessionId || resumeSessionId;
|
|
189
|
+
const finalSessionName = parsedSessionId ? undefined : (sessionName || (sessionDecision?.mode === "native_resume_candidate" ? sessionDecision.record.providerSessionName : undefined));
|
|
190
|
+
const providerLimitSignal = detectProviderLimit("claude", exitCode, stdout, stderr);
|
|
191
|
+
return {
|
|
192
|
+
taskStatus: execution.taskStatus,
|
|
193
|
+
execution,
|
|
194
|
+
...(providerLimitSignal ? { providerLimitSignal } : {}),
|
|
195
|
+
...(finalSessionId
|
|
196
|
+
? {
|
|
197
|
+
providerSession: {
|
|
198
|
+
providerSessionId: finalSessionId,
|
|
199
|
+
status: "active",
|
|
200
|
+
...(finalSessionName ? { providerSessionName: finalSessionName } : {})
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
: {})
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async function captureClaudeProjectTranscript(input) {
|
|
208
|
+
const projectsDir = input.projectsDir ?? path.join(os.homedir(), ".claude", "projects");
|
|
209
|
+
const projectDir = path.join(projectsDir, encodeClaudeProjectPath(input.cwd));
|
|
210
|
+
const startedAtMs = Date.parse(input.startedAt);
|
|
211
|
+
const minMtimeMs = Number.isFinite(startedAtMs) ? startedAtMs - 5_000 : 0;
|
|
212
|
+
try {
|
|
213
|
+
const entries = await readdir(projectDir, { withFileTypes: true });
|
|
214
|
+
const candidates = [];
|
|
215
|
+
for (const entry of entries) {
|
|
216
|
+
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const filePath = path.join(projectDir, entry.name);
|
|
220
|
+
const fileStat = await stat(filePath);
|
|
221
|
+
if (fileStat.mtimeMs >= minMtimeMs) {
|
|
222
|
+
candidates.push({ filePath, mtimeMs: fileStat.mtimeMs });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
226
|
+
const latest = candidates[0];
|
|
227
|
+
if (!latest) {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
await copyFile(latest.filePath, input.artifactPath);
|
|
231
|
+
return { sourcePath: latest.filePath };
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
const code = error.code;
|
|
235
|
+
if (code !== "ENOENT") {
|
|
236
|
+
input.logger?.warn(`[claude-transcript] Failed to capture Claude project transcript for ${input.cwd}: ${error instanceof Error ? error.message : String(error)}`);
|
|
237
|
+
}
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function encodeClaudeProjectPath(cwd) {
|
|
242
|
+
return path.resolve(cwd).replace(/[^A-Za-z0-9]/g, "-");
|
|
243
|
+
}
|
|
244
|
+
function extractSessionIdFromClaudeOutput(stdoutText) {
|
|
245
|
+
if (!stdoutText.trim()) {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
const parsed = JSON.parse(stdoutText);
|
|
250
|
+
if (parsed && typeof parsed === "object" && typeof parsed.session_id === "string") {
|
|
251
|
+
return parsed.session_id;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
// Substring parse candidate
|
|
256
|
+
const start = stdoutText.indexOf("{");
|
|
257
|
+
const end = stdoutText.lastIndexOf("}");
|
|
258
|
+
if (start !== -1 && end !== -1 && end > start) {
|
|
259
|
+
try {
|
|
260
|
+
const parsed = JSON.parse(stdoutText.substring(start, end + 1));
|
|
261
|
+
if (parsed && typeof parsed === "object" && typeof parsed.session_id === "string") {
|
|
262
|
+
return parsed.session_id;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// ignore
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|