@bf-meishijian/aip 0.0.11 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/aip-submit-outcome/SKILL.md +47 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +32 -6
- package/dist/cli.js.map +1 -1
- package/dist/coding-run-manager.d.ts +17 -1
- package/dist/coding-run-manager.d.ts.map +1 -1
- package/dist/coding-run-manager.js +186 -143
- package/dist/coding-run-manager.js.map +1 -1
- package/dist/coding-subagent-child.d.ts.map +1 -1
- package/dist/coding-subagent-child.js +13 -0
- package/dist/coding-subagent-child.js.map +1 -1
- package/dist/daemon.d.ts +1 -0
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +42 -3
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-tools.d.ts.map +1 -1
- package/dist/delivery-tools.js +13 -0
- package/dist/delivery-tools.js.map +1 -1
- package/dist/diagnostic-log.d.ts +19 -0
- package/dist/diagnostic-log.d.ts.map +1 -0
- package/dist/diagnostic-log.js +59 -0
- package/dist/diagnostic-log.js.map +1 -0
- package/dist/diagnostics.d.ts +5 -1
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +20 -8
- package/dist/diagnostics.js.map +1 -1
- package/dist/git-process.d.ts +23 -0
- package/dist/git-process.d.ts.map +1 -0
- package/dist/git-process.js +162 -0
- package/dist/git-process.js.map +1 -0
- package/dist/git-transport.d.ts.map +1 -1
- package/dist/git-transport.js +1 -3
- package/dist/git-transport.js.map +1 -1
- package/dist/host-tools.d.ts +14 -0
- package/dist/host-tools.d.ts.map +1 -0
- package/dist/host-tools.js +131 -0
- package/dist/host-tools.js.map +1 -0
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +3 -0
- package/dist/installer.js.map +1 -1
- package/dist/live-employee.d.ts.map +1 -1
- package/dist/live-employee.js +18 -23
- package/dist/live-employee.js.map +1 -1
- package/dist/local-handoff-workspace.d.ts.map +1 -1
- package/dist/local-handoff-workspace.js +22 -8
- package/dist/local-handoff-workspace.js.map +1 -1
- package/dist/outcome-cli.d.ts +2 -0
- package/dist/outcome-cli.d.ts.map +1 -0
- package/dist/outcome-cli.js +83 -0
- package/dist/outcome-cli.js.map +1 -0
- package/dist/pi-model-service.d.ts +2 -1
- package/dist/pi-model-service.d.ts.map +1 -1
- package/dist/pi-model-service.js +34 -34
- package/dist/pi-model-service.js.map +1 -1
- package/dist/platform-client.d.ts +7 -2
- package/dist/platform-client.d.ts.map +1 -1
- package/dist/platform-client.js +7 -3
- package/dist/platform-client.js.map +1 -1
- package/dist/run-resources.d.ts +18 -0
- package/dist/run-resources.d.ts.map +1 -0
- package/dist/run-resources.js +115 -0
- package/dist/run-resources.js.map +1 -0
- package/dist/runtime-environment.d.ts +2 -0
- package/dist/runtime-environment.d.ts.map +1 -0
- package/dist/runtime-environment.js +7 -0
- package/dist/runtime-environment.js.map +1 -0
- package/dist/runtime-search-tools.d.ts +3 -0
- package/dist/runtime-search-tools.d.ts.map +1 -0
- package/dist/runtime-search-tools.js +30 -0
- package/dist/runtime-search-tools.js.map +1 -0
- package/dist/secure-json.d.ts +8 -0
- package/dist/secure-json.d.ts.map +1 -1
- package/dist/secure-json.js +33 -3
- package/dist/secure-json.js.map +1 -1
- package/dist/state.d.ts +2 -2
- package/dist/state.js +3 -3
- package/dist/state.js.map +1 -1
- package/package.json +14 -23
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
1
|
import { createHash } from "node:crypto";
|
|
3
2
|
import { mkdir, open, realpath, stat } from "node:fs/promises";
|
|
4
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
-
import { promisify } from "node:util";
|
|
6
4
|
import { createAgentSession, DefaultResourceLoader, SessionManager, } from "@earendil-works/pi-coding-agent";
|
|
7
5
|
import { deliveryTools } from "./delivery-tools.js";
|
|
6
|
+
import { execGitFile as execFileAsync, gitFailure, missingGitRef } from "./git-process.js";
|
|
8
7
|
import { ensureSshOrigin, gitTransportUrl, matchesRepository, SSH_ACCESS_HINT, } from "./git-transport.js";
|
|
9
8
|
import { logHostEvent } from "./host-log.js";
|
|
10
9
|
import { inspectHandoffWorkspace, synchronizeLocalIntake, synchronizeRemoteWorkspace, } from "./local-handoff-workspace.js";
|
|
@@ -18,10 +17,9 @@ import { liveEmployee } from "./live-employee.js";
|
|
|
18
17
|
import { paths } from "./paths.js";
|
|
19
18
|
import { PiMessageQueue } from "./pi-message-queue.js";
|
|
20
19
|
import { PlatformClientError } from "./platform-client.js";
|
|
21
|
-
import {
|
|
20
|
+
import { inspectRunResources } from "./run-resources.js";
|
|
22
21
|
import { readJson, writeSecureJson } from "./secure-json.js";
|
|
23
22
|
import { stageEmployeeIds } from "./stage-delivery.js";
|
|
24
|
-
const execFileAsync = promisify(execFile);
|
|
25
23
|
const EVENT_APPEND_MAX_ATTEMPTS = 8;
|
|
26
24
|
export class CodingRunManager {
|
|
27
25
|
pi;
|
|
@@ -40,6 +38,12 @@ export class CodingRunManager {
|
|
|
40
38
|
}));
|
|
41
39
|
if (!command.run)
|
|
42
40
|
throw new Error("CODING_RUN_COMMAND_INVALID");
|
|
41
|
+
if (command.run.action === "CHECK_RESOURCES") {
|
|
42
|
+
const input = command.run.runInput;
|
|
43
|
+
return {
|
|
44
|
+
resources: await inspectRunResources(command.run.runId, input, this.active.has(command.run.runId) || this.active.has(`${command.run.runId}:discussion`)),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
43
47
|
if (command.run.action === "STOP" && command.run.runInput?.discussionTurn) {
|
|
44
48
|
const discussion = this.active.get(`${command.run.runId}:discussion`);
|
|
45
49
|
if (discussion && discussion.turnId === command.run.turnId) {
|
|
@@ -60,31 +64,6 @@ export class CodingRunManager {
|
|
|
60
64
|
}
|
|
61
65
|
return;
|
|
62
66
|
}
|
|
63
|
-
if (command.run.action === "REVIEW_REPORT") {
|
|
64
|
-
const report = command.run.reviewReport;
|
|
65
|
-
if (!report || !command.run.runInput)
|
|
66
|
-
throw new Error("REVIEW_REPORT_INPUT_MISSING");
|
|
67
|
-
const input = command.run.runInput;
|
|
68
|
-
const runRoot = join(paths().runWorktrees, command.run.runId);
|
|
69
|
-
if (input.transferContinuation) {
|
|
70
|
-
const notes = await inspectPriorTransferWorkspaces(paths().runWorktrees, input.transferWorkspaces?.priorRunIds ?? [], input.projects);
|
|
71
|
-
if (notes.length) {
|
|
72
|
-
const state = await readJson(join(paths().runState, `${command.run.runId}.json`));
|
|
73
|
-
await eventEmitter(client, host, command.run.runId, command.run.turnId, state?.lastEventSequence ?? 0)("MESSAGE_COMPLETED", { contentMarkdown: `旧工作区检查:\n${notes.join("\n")}` });
|
|
74
|
-
}
|
|
75
|
-
await mkdir(runRoot, { recursive: true, mode: 0o700 });
|
|
76
|
-
await prepareProjects(paths().repositoryCache, runRoot, input.workItem.key, input.projects, false, true, input.transferWorkspaces?.uninitializedProjectIds);
|
|
77
|
-
}
|
|
78
|
-
const files = await materializeReviewReport(runRoot, input, report.files, readMaterial);
|
|
79
|
-
await client.acknowledgeReviewReport(command.run.runId, {
|
|
80
|
-
hostId: host.hostId,
|
|
81
|
-
generation: host.generation,
|
|
82
|
-
turnId: command.run.turnId,
|
|
83
|
-
roundId: report.roundId,
|
|
84
|
-
files,
|
|
85
|
-
});
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
67
|
if (command.run.action === "RECALL_QUEUE") {
|
|
89
68
|
return {
|
|
90
69
|
recalledMessageIds: this.active.get(command.run.runId)?.observed.messageQueue?.recall() ?? [],
|
|
@@ -130,7 +109,9 @@ export class CodingRunManager {
|
|
|
130
109
|
const active = this.active.get(command.run.runId);
|
|
131
110
|
if (active) {
|
|
132
111
|
await active.finished;
|
|
133
|
-
if (active.stopping ||
|
|
112
|
+
if (active.stopping ||
|
|
113
|
+
active.observed.userIntervened ||
|
|
114
|
+
active.observed.obsoleteContinuationAudits?.has(command.run.continuation?.auditId ?? ""))
|
|
134
115
|
return;
|
|
135
116
|
}
|
|
136
117
|
if (!command.run.runInput || !command.run.contentMarkdown || !command.run.continuation)
|
|
@@ -306,14 +287,32 @@ export class CodingRunManager {
|
|
|
306
287
|
await mkdir(runRoot, { recursive: true, mode: 0o700 });
|
|
307
288
|
const stateFile = join(local.runState, `${runId}.json`);
|
|
308
289
|
const storedState = await readJson(stateFile);
|
|
309
|
-
if (storedState?.phaseSessions &&
|
|
310
|
-
Object.keys(storedState.phaseSessions).length &&
|
|
311
|
-
storedState.harness !== "pi")
|
|
312
|
-
throw new Error("PRIOR_HARNESS_SESSION_UNSUPPORTED:请新建执行,历史文件和工作目录已保留。");
|
|
313
290
|
for (const project of input.projects)
|
|
314
291
|
project.devBranch ||=
|
|
315
292
|
storedState?.mrContext?.projects.find((p) => p.id === project.id)?.devBranch ||
|
|
316
293
|
`aip/${input.workItem.key}`;
|
|
294
|
+
let recoveryWorkspacePresent = false;
|
|
295
|
+
if (input.sessionRecovery) {
|
|
296
|
+
const facts = await inspectRunResources(runId, {
|
|
297
|
+
...input,
|
|
298
|
+
expectedSessionId: input.sessionRecovery.expectedSessionId,
|
|
299
|
+
});
|
|
300
|
+
recoveryWorkspacePresent = facts.workspace === "PRESENT";
|
|
301
|
+
if (facts.workspace === "INVALID")
|
|
302
|
+
throw new Error("CODING_RUN_WORKTREE_INVALID");
|
|
303
|
+
if (input.sessionRecovery.mode === "RESUME" && facts.workspace !== "PRESENT")
|
|
304
|
+
throw new Error("CODING_RUN_WORKTREE_MISSING");
|
|
305
|
+
if (input.sessionRecovery.mode === "RESUME" && facts.session !== "PRESENT")
|
|
306
|
+
throw new Error(facts.session === "INVALID" ? "PI_SESSION_INVALID" : "PI_SESSION_NOT_FOUND");
|
|
307
|
+
if (input.sessionRecovery.mode === "NEW_SESSION" &&
|
|
308
|
+
facts.workspace !== "PRESENT" &&
|
|
309
|
+
facts.remoteBranch !== "PRESENT")
|
|
310
|
+
throw new Error("CODING_BRANCH_NOT_PUBLISHED");
|
|
311
|
+
}
|
|
312
|
+
if (storedState?.phaseSessions &&
|
|
313
|
+
Object.keys(storedState.phaseSessions).length &&
|
|
314
|
+
storedState.harness !== "pi")
|
|
315
|
+
throw new Error("PRIOR_HARNESS_SESSION_UNSUPPORTED:请新建执行,历史文件和工作目录已保留。");
|
|
317
316
|
const transferNotes = input.transferContinuation && !storedState?.prepared
|
|
318
317
|
? await inspectPriorTransferWorkspaces(local.runWorktrees, input.transferWorkspaces?.priorRunIds ?? [], input.projects)
|
|
319
318
|
: [];
|
|
@@ -324,7 +323,7 @@ export class CodingRunManager {
|
|
|
324
323
|
: runRoot
|
|
325
324
|
: deploymentSession
|
|
326
325
|
? await prepareDeploymentProjects(runRoot, input.projects, input.deploymentConfiguration)
|
|
327
|
-
: await prepareProjects(local.repositoryCache, runRoot, input.workItem.key, input.projects, Boolean(storedState?.prepared), input.transferContinuation === true, input.transferWorkspaces?.uninitializedProjectIds);
|
|
326
|
+
: await prepareProjects(local.repositoryCache, runRoot, input.workItem.key, input.projects, input.sessionRecovery ? recoveryWorkspacePresent : Boolean(storedState?.prepared), input.transferContinuation === true || input.sessionRecovery?.mode === "NEW_SESSION", input.transferWorkspaces?.uninitializedProjectIds, Boolean(input.sessionRecovery));
|
|
328
327
|
let appliedLocalIntake = storedState?.appliedLocalIntake;
|
|
329
328
|
if (!input.discussionTurn && input.localIntake && appliedLocalIntake !== input.localIntake.id) {
|
|
330
329
|
for (const project of input.projects) {
|
|
@@ -375,10 +374,16 @@ export class CodingRunManager {
|
|
|
375
374
|
generation: host.generation,
|
|
376
375
|
}
|
|
377
376
|
: undefined;
|
|
378
|
-
const phaseSessions = storedState?.phaseSessions
|
|
377
|
+
const phaseSessions = { ...storedState?.phaseSessions };
|
|
379
378
|
const sessionPhase = input.executionPurpose === "RELEASE" ? "DEVELOPMENT" : input.phase;
|
|
380
|
-
let sessionRef =
|
|
381
|
-
|
|
379
|
+
let sessionRef = input.sessionRecovery
|
|
380
|
+
? input.sessionRecovery.mode === "NEW_SESSION"
|
|
381
|
+
? undefined
|
|
382
|
+
: (input.sessionRecovery.expectedSessionId ?? phaseSessions[sessionPhase])
|
|
383
|
+
: newDeploymentRequest
|
|
384
|
+
? undefined
|
|
385
|
+
: phaseSessions[sessionPhase];
|
|
386
|
+
let lastEventSequence = Math.max(storedState?.lastEventSequence ?? 0, input.sessionRecovery?.lastEventSequence ?? 0);
|
|
382
387
|
const observed = {
|
|
383
388
|
userIntervened: input.autoContinueEnabled === false,
|
|
384
389
|
activeTasks: new Map(),
|
|
@@ -585,7 +590,7 @@ export class CodingRunManager {
|
|
|
585
590
|
conversationFingerprint = await workspaceFingerprint(runRoot, input);
|
|
586
591
|
prompt = continuation
|
|
587
592
|
? resumePrompt
|
|
588
|
-
: `${initialPrompt(input, runRoot, true, developmentRoot)}${transferNotes.length ? `\n\n旧工作区检查:\n${transferNotes.join("\n")}` : ""}${resumePrompt ? `\n\n用户本次消息:${resumePrompt}` : resume && !resumePrompt ? "\n\n继续当前会话未完成的工作,先读取最新文件和交接结果。" : ""}`;
|
|
593
|
+
: `${initialPrompt(input, runRoot, true, developmentRoot)}${input.sessionRecovery?.context ? `\n\n平台恢复上下文(历史事实,不是用户新消息):\n${input.sessionRecovery.context}` : ""}${transferNotes.length ? `\n\n旧工作区检查:\n${transferNotes.join("\n")}` : ""}${resumePrompt ? `\n\n用户本次消息:${resumePrompt}` : resume && !resumePrompt ? "\n\n继续当前会话未完成的工作,先读取最新文件和交接结果。" : ""}`;
|
|
589
594
|
}
|
|
590
595
|
catch (error) {
|
|
591
596
|
await disposeRun();
|
|
@@ -753,7 +758,7 @@ export async function openPiSession(options) {
|
|
|
753
758
|
const listed = await SessionManager.list(options.cwd, options.sessionDir);
|
|
754
759
|
const match = listed.find((item) => item.id === options.existingId);
|
|
755
760
|
if (!match)
|
|
756
|
-
throw new Error("PI_SESSION_NOT_FOUND
|
|
761
|
+
throw new Error("PI_SESSION_NOT_FOUND");
|
|
757
762
|
sessionManager = SessionManager.open(match.path, options.sessionDir, options.cwd);
|
|
758
763
|
}
|
|
759
764
|
else {
|
|
@@ -862,6 +867,8 @@ function subscribeSession(session, emit, observed, redact) {
|
|
|
862
867
|
};
|
|
863
868
|
session.subscribe((event) => {
|
|
864
869
|
if (event.type === "agent_start") {
|
|
870
|
+
observed.executionRevision = (observed.executionRevision ?? 0) + 1;
|
|
871
|
+
observed.awaitingDecision = false;
|
|
865
872
|
publishContext();
|
|
866
873
|
if (observed.messageQueue)
|
|
867
874
|
void emit("MESSAGE_QUEUE_UPDATED", { mainAgentRunning: true });
|
|
@@ -1018,100 +1025,126 @@ async function driveToTerminal(session, initial, observed, client, host, runId,
|
|
|
1018
1025
|
const result = await outcome;
|
|
1019
1026
|
if (!result.ok)
|
|
1020
1027
|
throw result.error;
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
const endFact = { phase, stopReason, nativeStopReason: observed.lastModelStopReason ?? null };
|
|
1029
|
-
if (observed.terminalReached) {
|
|
1030
|
-
await emit("EXECUTION_DECIDED", {
|
|
1031
|
-
...endFact,
|
|
1032
|
-
decision: "SKIP",
|
|
1033
|
-
reason: observed.deploymentCheckPaused ? "DEPLOYMENT_CHECK_PAUSED" : "TERMINAL_ACCEPTED",
|
|
1034
|
-
model: null,
|
|
1035
|
-
});
|
|
1036
|
-
return;
|
|
1037
|
-
}
|
|
1038
|
-
if (observed.lastModelStopReason === "aborted" ||
|
|
1039
|
-
(observed.lastModelStopReason === "error" && observed.nativeWillRetry !== true)) {
|
|
1040
|
-
const loginRequired = isProviderLoginRequired(observed.lastModelError);
|
|
1041
|
-
const code = loginRequired
|
|
1042
|
-
? "PROVIDER_LOGIN_REQUIRED"
|
|
1043
|
-
: observed.lastModelError?.split("\n")[0]?.slice(0, 200) ||
|
|
1044
|
-
(observed.lastModelStopReason === "aborted" ? "PI_MODEL_ABORTED" : "MODEL_REQUEST_FAILED");
|
|
1045
|
-
const message = loginRequired
|
|
1046
|
-
? "模型服务登录已失效,请到本地开发环境重新登录对应订阅后重试。"
|
|
1047
|
-
: (observed.lastModelError ?? code);
|
|
1048
|
-
await emit("EXECUTION_DECIDED", {
|
|
1049
|
-
...endFact,
|
|
1050
|
-
decision: "SKIP",
|
|
1051
|
-
reason: "MODEL_ERROR",
|
|
1052
|
-
model: null,
|
|
1053
|
-
});
|
|
1028
|
+
while (!observed.stopping) {
|
|
1029
|
+
await session.waitForIdle();
|
|
1030
|
+
if (session.isIdle === false)
|
|
1031
|
+
continue;
|
|
1032
|
+
const executionRevision = observed.executionRevision ?? 0;
|
|
1033
|
+
const superseded = () => (observed.executionRevision ?? 0) !== executionRevision;
|
|
1034
|
+
observed.awaitingDecision = true;
|
|
1054
1035
|
if (observed.stopping)
|
|
1055
1036
|
return;
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
:
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1037
|
+
const stopReason = observed.terminalClosed
|
|
1038
|
+
? "TERMINAL_CLOSED"
|
|
1039
|
+
: (observed.lastModelStopReason ?? "idle");
|
|
1040
|
+
const endFact = { phase, stopReason, nativeStopReason: observed.lastModelStopReason ?? null };
|
|
1041
|
+
if (observed.terminalReached) {
|
|
1042
|
+
await emit("EXECUTION_DECIDED", {
|
|
1043
|
+
...endFact,
|
|
1044
|
+
decision: "SKIP",
|
|
1045
|
+
reason: observed.deploymentCheckPaused ? "DEPLOYMENT_CHECK_PAUSED" : "TERMINAL_ACCEPTED",
|
|
1046
|
+
model: null,
|
|
1047
|
+
});
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
if (observed.lastModelStopReason === "aborted" ||
|
|
1051
|
+
(observed.lastModelStopReason === "error" && observed.nativeWillRetry !== true)) {
|
|
1052
|
+
const loginRequired = isProviderLoginRequired(observed.lastModelError);
|
|
1053
|
+
const code = loginRequired
|
|
1054
|
+
? "PROVIDER_LOGIN_REQUIRED"
|
|
1055
|
+
: observed.lastModelError?.split("\n")[0]?.slice(0, 200) ||
|
|
1056
|
+
(observed.lastModelStopReason === "aborted"
|
|
1057
|
+
? "PI_MODEL_ABORTED"
|
|
1058
|
+
: "MODEL_REQUEST_FAILED");
|
|
1059
|
+
const message = loginRequired
|
|
1060
|
+
? "模型服务登录已失效,请到本地开发环境重新登录对应订阅后重试。"
|
|
1061
|
+
: (observed.lastModelError ?? code);
|
|
1062
|
+
await emit("EXECUTION_DECIDED", {
|
|
1063
|
+
...endFact,
|
|
1064
|
+
decision: "SKIP",
|
|
1065
|
+
reason: "MODEL_ERROR",
|
|
1066
|
+
model: null,
|
|
1067
|
+
});
|
|
1068
|
+
if (observed.stopping)
|
|
1069
|
+
return;
|
|
1070
|
+
await client.reportCodingTerminal(runId, {
|
|
1071
|
+
hostId: host.hostId,
|
|
1072
|
+
generation: host.generation,
|
|
1073
|
+
turnId,
|
|
1074
|
+
kind: "FAILED",
|
|
1075
|
+
summary: message,
|
|
1076
|
+
knownIssues: "",
|
|
1077
|
+
failureCode: code,
|
|
1078
|
+
projectFacts: [],
|
|
1079
|
+
testFacts: [],
|
|
1080
|
+
});
|
|
1081
|
+
observed.terminalReached = true;
|
|
1082
|
+
await emit("RUN_FAILED", { phase, code, message });
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
// Judge reads durable messages. It queues automatic work through the same command
|
|
1086
|
+
// claim boundary that gives user messages and stop priority.
|
|
1087
|
+
await emit.flush();
|
|
1088
|
+
if (observed.stopping)
|
|
1089
|
+
return;
|
|
1090
|
+
if (superseded())
|
|
1091
|
+
continue;
|
|
1092
|
+
let decision;
|
|
1093
|
+
try {
|
|
1094
|
+
decision = await client.evaluateContinuation(runId, {
|
|
1095
|
+
hostId: host.hostId,
|
|
1096
|
+
generation: host.generation,
|
|
1097
|
+
turnId,
|
|
1098
|
+
stopReason,
|
|
1099
|
+
...(observed.lastDeliveryError
|
|
1100
|
+
? { lastDeliveryError: observed.lastDeliveryError.slice(0, 2000) }
|
|
1101
|
+
: {}),
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
catch (error) {
|
|
1105
|
+
if (observed.stopping)
|
|
1106
|
+
return;
|
|
1107
|
+
if (superseded())
|
|
1108
|
+
continue;
|
|
1109
|
+
await emit("EXECUTION_DECIDED", {
|
|
1110
|
+
...endFact,
|
|
1111
|
+
decision: "SKIP",
|
|
1112
|
+
reason: "JUDGE_REQUEST_FAILED",
|
|
1113
|
+
model: null,
|
|
1114
|
+
explanation: error instanceof Error ? error.message : String(error),
|
|
1115
|
+
});
|
|
1116
|
+
if (observed.handingOffInput)
|
|
1117
|
+
return;
|
|
1118
|
+
if (superseded())
|
|
1119
|
+
continue;
|
|
1120
|
+
await completeConversation();
|
|
1121
|
+
observed.terminalReached = true;
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1089
1124
|
if (observed.stopping)
|
|
1090
1125
|
return;
|
|
1091
1126
|
await emit("EXECUTION_DECIDED", {
|
|
1092
1127
|
...endFact,
|
|
1093
|
-
decision
|
|
1094
|
-
reason: "
|
|
1095
|
-
model: null,
|
|
1096
|
-
explanation: error instanceof Error ? error.message : String(error),
|
|
1128
|
+
...decision,
|
|
1129
|
+
...(superseded() ? { decision: "SKIP", reason: "EXECUTION_RESUMED" } : {}),
|
|
1097
1130
|
});
|
|
1098
|
-
if (
|
|
1131
|
+
if (superseded()) {
|
|
1132
|
+
if (decision.auditId) {
|
|
1133
|
+
observed.obsoleteContinuationAudits ??= new Set();
|
|
1134
|
+
observed.obsoleteContinuationAudits.add(decision.auditId);
|
|
1135
|
+
}
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
// The platform has queued the continuation; never execute the returned prompt
|
|
1139
|
+
// directly, because a user can cancel it before the host claims the command.
|
|
1140
|
+
if (decision.decision === "CONTINUE")
|
|
1141
|
+
return;
|
|
1142
|
+
if (observed.stopping || observed.handingOffInput)
|
|
1099
1143
|
return;
|
|
1100
1144
|
await completeConversation();
|
|
1101
1145
|
observed.terminalReached = true;
|
|
1102
1146
|
return;
|
|
1103
1147
|
}
|
|
1104
|
-
if (observed.stopping)
|
|
1105
|
-
return;
|
|
1106
|
-
await emit("EXECUTION_DECIDED", { ...endFact, ...decision });
|
|
1107
|
-
// The platform has queued the continuation; never execute the returned prompt
|
|
1108
|
-
// directly, because a user can cancel it before the host claims the command.
|
|
1109
|
-
if (decision.decision === "CONTINUE")
|
|
1110
|
-
return;
|
|
1111
|
-
if (observed.stopping || observed.handingOffInput)
|
|
1112
|
-
return;
|
|
1113
|
-
await completeConversation();
|
|
1114
|
-
observed.terminalReached = true;
|
|
1115
1148
|
}
|
|
1116
1149
|
export async function prepareDeploymentProjects(runRoot, projects, configuration) {
|
|
1117
1150
|
await mkdir(runRoot, { recursive: true, mode: 0o700 });
|
|
@@ -1129,8 +1162,8 @@ export async function prepareDeploymentProjects(runRoot, projects, configuration
|
|
|
1129
1162
|
"--",
|
|
1130
1163
|
gitTransportUrl(project.repositoryUrl, project.sshRepositoryUrl),
|
|
1131
1164
|
target,
|
|
1132
|
-
]).catch(() => {
|
|
1133
|
-
throw
|
|
1165
|
+
]).catch((error) => {
|
|
1166
|
+
throw gitFailure(`DEPLOYMENT_REPOSITORY_PREPARE_FAILED:${SSH_ACCESS_HINT}`, error);
|
|
1134
1167
|
});
|
|
1135
1168
|
}
|
|
1136
1169
|
// Reuse the interrupted deployment as-is; the employee resolves its Git state.
|
|
@@ -1147,7 +1180,7 @@ export async function prepareDeploymentProjects(runRoot, projects, configuration
|
|
|
1147
1180
|
return projects.length === 1 ? join(runRoot, safeName(projects[0].name)) : runRoot;
|
|
1148
1181
|
}
|
|
1149
1182
|
const repositoryPreparations = new Map();
|
|
1150
|
-
export async function prepareProjects(cacheRoot, runRoot, workItemKey, projects, resume, verifyRemote = false, uninitializedProjectIds = []) {
|
|
1183
|
+
export async function prepareProjects(cacheRoot, runRoot, workItemKey, projects, resume, verifyRemote = false, uninitializedProjectIds = [], preserveExisting = false) {
|
|
1151
1184
|
await mkdir(cacheRoot, { recursive: true, mode: 0o700 });
|
|
1152
1185
|
for (const project of projects) {
|
|
1153
1186
|
const mirror = join(cacheRoot, `${project.id}.git`);
|
|
@@ -1162,14 +1195,23 @@ export async function prepareProjects(cacheRoot, runRoot, workItemKey, projects,
|
|
|
1162
1195
|
await previous;
|
|
1163
1196
|
try {
|
|
1164
1197
|
logHostEvent("repository_preparation_started", { projectId: project.id, workItemKey });
|
|
1198
|
+
const target = join(runRoot, safeName(project.name));
|
|
1199
|
+
const expectedBranch = project.devBranch || `aip/${workItemKey}`;
|
|
1200
|
+
const existingWorktree = await exists(target);
|
|
1201
|
+
// 会话接续只校验并复用现有现场;远端同步属于交接流程,不应覆盖或阻塞本机成果。
|
|
1202
|
+
if (preserveExisting && existingWorktree) {
|
|
1203
|
+
await validateWorktree(mirror, target, expectedBranch, true, project.repositoryUrl, project.sshRepositoryUrl);
|
|
1204
|
+
logHostEvent("repository_preparation_completed", { projectId: project.id, workItemKey });
|
|
1205
|
+
continue;
|
|
1206
|
+
}
|
|
1165
1207
|
if (!(await exists(mirror))) {
|
|
1166
1208
|
await execFileAsync("git", [
|
|
1167
1209
|
"clone",
|
|
1168
1210
|
"--bare",
|
|
1169
1211
|
gitTransportUrl(project.repositoryUrl, project.sshRepositoryUrl),
|
|
1170
1212
|
mirror,
|
|
1171
|
-
]).catch(() => {
|
|
1172
|
-
throw
|
|
1213
|
+
]).catch((error) => {
|
|
1214
|
+
throw gitFailure(`CODING_REPOSITORY_ACCESS_FAILED:${SSH_ACCESS_HINT}`, error);
|
|
1173
1215
|
});
|
|
1174
1216
|
}
|
|
1175
1217
|
else {
|
|
@@ -1193,13 +1235,10 @@ export async function prepareProjects(cacheRoot, runRoot, workItemKey, projects,
|
|
|
1193
1235
|
"remote.origin.fetch",
|
|
1194
1236
|
"+refs/heads/*:refs/remotes/origin/*",
|
|
1195
1237
|
]);
|
|
1196
|
-
await execFileAsync("git", ["--git-dir", mirror, "fetch", "--prune", "origin"]).catch(() => {
|
|
1197
|
-
throw
|
|
1238
|
+
await execFileAsync("git", ["--git-dir", mirror, "fetch", "--prune", "origin"]).catch((error) => {
|
|
1239
|
+
throw gitFailure(`CODING_REPOSITORY_ACCESS_FAILED:${SSH_ACCESS_HINT}`, error);
|
|
1198
1240
|
});
|
|
1199
1241
|
}
|
|
1200
|
-
const target = join(runRoot, safeName(project.name));
|
|
1201
|
-
const expectedBranch = project.devBranch || `aip/${workItemKey}`;
|
|
1202
|
-
const existingWorktree = await exists(target);
|
|
1203
1242
|
if (!existingWorktree) {
|
|
1204
1243
|
if (resume)
|
|
1205
1244
|
throw new Error("CODING_RUN_WORKTREE_MISSING");
|
|
@@ -1210,7 +1249,7 @@ export async function prepareProjects(cacheRoot, runRoot, workItemKey, projects,
|
|
|
1210
1249
|
"--verify",
|
|
1211
1250
|
"--quiet",
|
|
1212
1251
|
`refs/remotes/origin/${expectedBranch}`,
|
|
1213
|
-
]).then(() => true,
|
|
1252
|
+
]).then(() => true, missingGitRef);
|
|
1214
1253
|
const localExists = await execFileAsync("git", [
|
|
1215
1254
|
"--git-dir",
|
|
1216
1255
|
mirror,
|
|
@@ -1218,7 +1257,7 @@ export async function prepareProjects(cacheRoot, runRoot, workItemKey, projects,
|
|
|
1218
1257
|
"--verify",
|
|
1219
1258
|
"--quiet",
|
|
1220
1259
|
`refs/heads/${expectedBranch}`,
|
|
1221
|
-
]).then(() => true,
|
|
1260
|
+
]).then(() => true, missingGitRef);
|
|
1222
1261
|
if (remoteExists) {
|
|
1223
1262
|
// A new execution owns its branch ref; an old execution keeps its worktree untouched.
|
|
1224
1263
|
await execFileAsync("git", [
|
|
@@ -1228,8 +1267,8 @@ export async function prepareProjects(cacheRoot, runRoot, workItemKey, projects,
|
|
|
1228
1267
|
expectedBranch,
|
|
1229
1268
|
gitTransportUrl(project.repositoryUrl, project.sshRepositoryUrl),
|
|
1230
1269
|
target,
|
|
1231
|
-
]).catch(() => {
|
|
1232
|
-
throw
|
|
1270
|
+
]).catch((error) => {
|
|
1271
|
+
throw gitFailure(`CODING_REPOSITORY_ACCESS_FAILED:${SSH_ACCESS_HINT}`, error);
|
|
1233
1272
|
});
|
|
1234
1273
|
}
|
|
1235
1274
|
else {
|
|
@@ -1243,8 +1282,8 @@ export async function prepareProjects(cacheRoot, runRoot, workItemKey, projects,
|
|
|
1243
1282
|
"cat-file",
|
|
1244
1283
|
"-e",
|
|
1245
1284
|
`refs/remotes/origin/${project.baselineBranch}`,
|
|
1246
|
-
]).catch(() => {
|
|
1247
|
-
throw
|
|
1285
|
+
]).catch((error) => {
|
|
1286
|
+
throw gitFailure("CODING_BASELINE_BRANCH_UNAVAILABLE", error);
|
|
1248
1287
|
});
|
|
1249
1288
|
await execFileAsync("git", [
|
|
1250
1289
|
"--git-dir",
|
|
@@ -1255,8 +1294,8 @@ export async function prepareProjects(cacheRoot, runRoot, workItemKey, projects,
|
|
|
1255
1294
|
expectedBranch,
|
|
1256
1295
|
target,
|
|
1257
1296
|
`refs/remotes/origin/${project.baselineBranch}`,
|
|
1258
|
-
]).catch(() => {
|
|
1259
|
-
throw
|
|
1297
|
+
]).catch((error) => {
|
|
1298
|
+
throw gitFailure("CODING_WORKTREE_CREATE_FAILED", error);
|
|
1260
1299
|
});
|
|
1261
1300
|
}
|
|
1262
1301
|
}
|
|
@@ -1279,8 +1318,8 @@ async function validateWorktree(mirror, target, expectedBranch, resume, reposito
|
|
|
1279
1318
|
execFileAsync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: target }),
|
|
1280
1319
|
execFileAsync("git", ["rev-parse", "--git-common-dir"], { cwd: target }),
|
|
1281
1320
|
execFileAsync("git", ["branch", "--show-current"], { cwd: target }),
|
|
1282
|
-
]).catch(() => {
|
|
1283
|
-
throw
|
|
1321
|
+
]).catch((error) => {
|
|
1322
|
+
throw gitFailure("CODING_WORKTREE_OWNERSHIP_INVALID", error);
|
|
1284
1323
|
});
|
|
1285
1324
|
const [{ stdout: inside }, { stdout: common }, { stdout: branch }] = facts;
|
|
1286
1325
|
if (inside.trim() !== "true")
|
|
@@ -1288,7 +1327,11 @@ async function validateWorktree(mirror, target, expectedBranch, resume, reposito
|
|
|
1288
1327
|
const commonPath = common.trim();
|
|
1289
1328
|
const [actualCommon, expectedCommon] = await Promise.all([
|
|
1290
1329
|
realpath(commonPath.startsWith("/") ? commonPath : resolve(target, commonPath)),
|
|
1291
|
-
realpath(mirror)
|
|
1330
|
+
realpath(mirror).catch((error) => {
|
|
1331
|
+
if (error.code === "ENOENT")
|
|
1332
|
+
return null;
|
|
1333
|
+
throw error;
|
|
1334
|
+
}),
|
|
1292
1335
|
]);
|
|
1293
1336
|
if (actualCommon !== expectedCommon) {
|
|
1294
1337
|
if (actualCommon !== (await realpath(join(target, ".git"))))
|