Merge remote-tracking branch 'origin/master' into fix/agc-recent-project-status-retry
# Conflicts: # docs/project-memory/shared-memory/decision-log.md
This commit is contained in:
@@ -194,6 +194,14 @@ _Avoid_: 进度通知、快照轮询、第二套历史
|
||||
把项目对话历史条目与运行态事件转换成消息气泡和工具卡片的读取期转换;不持久化,也不构成事实源。
|
||||
_Avoid_: 投影缓存文件、已脱敏卡片库、第二套 reducer
|
||||
|
||||
**项目对话输入**:
|
||||
AGC 项目对话的输入只有自然语言回合(含 `@` 素材引用与附件);需要动作时由 Runtime 工具与确认卡承接,不从输入文本解析控制词。
|
||||
_Avoid_: 斜杠命令、聊天命令草稿、命令发现列表
|
||||
|
||||
**项目命令 id**:
|
||||
AGC 运行期工具与项目权限策略使用的稳定标识(`GAME_CREATION_APP_COMMANDS` 与 `GameCreationAppPermission`),由 Rust 运行期策略校验与 App 权限审计 / 项目前置条件判定消费,不是用户输入语法。
|
||||
_Avoid_: 把命令 id 当作可输入的聊天命令、为权限位补聊天入口
|
||||
|
||||
**引用候选**:
|
||||
输入区可以命中的对象集合(`@` 素材、`$` Skill),由宿主按种类注入;输入区不判断候选属于哪一类。
|
||||
_Avoid_: 输入区自己读项目清单或应用目录、把候选取值写死在组件里
|
||||
|
||||
@@ -15,20 +15,10 @@
|
||||
"skill-pack:test": "node --test scripts/check-skill-pack.test.mjs",
|
||||
"llm-status": "node scripts/run-cli-with-config.mjs --llm-status",
|
||||
"agent-task": "node scripts/run-cli-with-config.mjs --agent-task",
|
||||
"chat": "node scripts/run-cli-with-config.mjs --swarm-chat",
|
||||
"swarm": "node scripts/run-cli-with-config.mjs --swarm-chat",
|
||||
"config": "node scripts/game-creator-config-wizard.mjs",
|
||||
"test:chat": "node scripts/agent-swarm-test-chat.mjs --task \"制作一个可直接试玩的原创植物塔防小游戏:玩家选择并放置原创守卫阻挡敌人,完成波次后可以进入下一关并重新开始。主题、单位名称与视觉语言必须原创,不使用任何现有游戏角色、单位名、Logo 或受保护视觉语言。请自主完成正式产物、静态检查和双视口试玩验证。\" --no-open",
|
||||
"test:chat:manual": "node scripts/agent-swarm-test-chat.mjs",
|
||||
"agent-run": "node scripts/run-cli-with-config.mjs --agent-run",
|
||||
"agent-run:smoke": "node scripts/smoke-agent-run-local-provider.mjs",
|
||||
"agent-runtime:real-e2e": "node scripts/agent-runtime-real-e2e.mjs",
|
||||
"agent-runtime:collaboration-policy-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-collaboration-policy-mixed-recovery",
|
||||
"agent-runtime:mixed-swarm-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-static-isolated-autonomous-chat",
|
||||
"agent-runtime:supervisor-swarm-autonomous-chat-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-autonomous-chat",
|
||||
"agent-runtime:supervisor-autonomous-playable-lane-defense-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-autonomous-playable-lane-defense",
|
||||
"agent-runtime:supervisor-autonomous-playable-lane-defense-deterministic-e2e": "node scripts/agent-runtime-deterministic-playable-e2e.mjs",
|
||||
"agent-runtime:supervisor-autonomous-playable-lane-defense-deterministic-self-test": "node scripts/agent-runtime-deterministic-playable-e2e.mjs --self-test",
|
||||
"agent-runtime:supervisor-swarm-transient-retry-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-transient-retry",
|
||||
"agent-runtime:supervisor-swarm-final-reply-transient-retry-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-final-reply-transient-retry",
|
||||
"agent-runtime:supervisor-swarm-tool-plan-handoff-runner-kill-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-tool-plan-handoff-runner-kill",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,69 +1,8 @@
|
||||
import { createHash } from '../dependencies.mjs';
|
||||
import { isPlainObject } from '../harness/config.mjs';
|
||||
import {
|
||||
interactiveCliOutput,
|
||||
writeInteractiveCliLine,
|
||||
} from '../harness/process.mjs';
|
||||
import {
|
||||
shutdownWaiters,
|
||||
state,
|
||||
userInputAnswerText,
|
||||
} from '../runtime-state.mjs';
|
||||
import { shutdownWaiters, state } from '../runtime-state.mjs';
|
||||
import { disposableProjectPathVariants } from './runtime.mjs';
|
||||
|
||||
export async function answerRemainingInteractiveQuestions(session) {
|
||||
let answeredPromptCount = 1;
|
||||
const deadline = Date.now() + 60_000;
|
||||
while (Date.now() < deadline) {
|
||||
const output = interactiveCliOutput(session);
|
||||
if (output.includes(`[\u5df2\u56de\u7b54] ${state.userInput.requestId}`))
|
||||
return;
|
||||
const promptCount = output.split('或直接输入其他答案:').length - 1;
|
||||
while (answeredPromptCount < promptCount && answeredPromptCount < 3) {
|
||||
writeInteractiveCliLine(session, userInputAnswerText);
|
||||
answeredPromptCount += 1;
|
||||
}
|
||||
if (output.includes('[待确认]')) {
|
||||
throw codedError('user-input-unexpected-tool-confirmation');
|
||||
}
|
||||
if (session.closed) throw codedError('user-input-cli-closed-before-answer');
|
||||
await sleep(100);
|
||||
}
|
||||
throw codedError('user-input-answer-timeout');
|
||||
}
|
||||
|
||||
export function parseSingleSwarmTurnReport(output, codePrefix) {
|
||||
const reportLines = output
|
||||
.split(/\r?\n/u)
|
||||
.filter((line) => line.startsWith('[turn.report] '));
|
||||
assert(reportLines.length === 1, `${codePrefix}-turn-report-count-invalid`);
|
||||
const report = JSON.parse(reportLines[0].slice('[turn.report] '.length));
|
||||
assert(
|
||||
isPlainObject(report) &&
|
||||
JSON.stringify(Object.keys(report).sort()) ===
|
||||
JSON.stringify(
|
||||
[
|
||||
'schemaVersion',
|
||||
'outcome',
|
||||
'parentAgentId',
|
||||
'sessionId',
|
||||
'parentRunId',
|
||||
'runtimeCount',
|
||||
'busyRuntimeCount',
|
||||
'pendingTaskCount',
|
||||
'runningTaskCount',
|
||||
'waitingForConfirmationCount',
|
||||
'waitingForUserInputCount',
|
||||
'newAssistantMessageCount',
|
||||
'finalReplyChars',
|
||||
'reconciliationAgentCount',
|
||||
].sort(),
|
||||
),
|
||||
`${codePrefix}-turn-report-shape-invalid`,
|
||||
);
|
||||
return report;
|
||||
}
|
||||
|
||||
export function isFailedTask(task) {
|
||||
return (
|
||||
['failed', 'cancelled', 'budget-exhausted'].includes(task.status) ||
|
||||
|
||||
@@ -2419,24 +2419,6 @@ export function finalMessageId(agentId, sessionId, runId) {
|
||||
).slice(0, 32)}`;
|
||||
}
|
||||
|
||||
export function runtimePublicStatusMessageId(
|
||||
agentId,
|
||||
sessionId,
|
||||
runId,
|
||||
status,
|
||||
) {
|
||||
const correlationId = runtimeMessageCorrelationId(
|
||||
agentId,
|
||||
sessionId,
|
||||
runId,
|
||||
).slice(0, 32);
|
||||
const statusFingerprint = createHash('sha256')
|
||||
.update(status)
|
||||
.digest('hex')
|
||||
.slice(0, 16);
|
||||
return `runtime-public-status-${correlationId}-${statusFingerprint}`;
|
||||
}
|
||||
|
||||
export function backgroundTaskMessageId(agentId, sessionId, runId, source) {
|
||||
const fingerprint = createHash('sha256')
|
||||
.update(`${agentId}\n${sessionId}\n${runId}\n${source}`)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -37,26 +37,16 @@ import {
|
||||
state,
|
||||
steerRunnerKillAppDataSentinelFileName,
|
||||
steerRunnerKillAppDataSentinelSchema,
|
||||
supervisorAutonomousPlayableAppDataSentinelFileName,
|
||||
supervisorAutonomousPlayableAppDataSentinelSchema,
|
||||
supervisorSwarmAppDataSentinelFileName,
|
||||
supervisorSwarmAppDataSentinelSchema,
|
||||
supervisorSwarmAutonomousChatAppDataSentinelFileName,
|
||||
supervisorSwarmAutonomousChatAppDataSentinelSchema,
|
||||
supervisorSwarmCollaborationPolicyAppDataSentinelFileName,
|
||||
supervisorSwarmCollaborationPolicyAppDataSentinelSchema,
|
||||
supervisorSwarmFinalReplyTransientRetryAppDataSentinelFileName,
|
||||
supervisorSwarmFinalReplyTransientRetryAppDataSentinelSchema,
|
||||
supervisorSwarmRequiredAgentIds,
|
||||
supervisorSwarmStaticIsolatedAutonomousChatAppDataSentinelFileName,
|
||||
supervisorSwarmStaticIsolatedAutonomousChatAppDataSentinelSchema,
|
||||
supervisorSwarmToolPlanHandoffRunnerKillAppDataSentinelFileName,
|
||||
supervisorSwarmToolPlanHandoffRunnerKillAppDataSentinelSchema,
|
||||
supervisorSwarmTransientRetryAppDataSentinelFileName,
|
||||
supervisorSwarmTransientRetryAppDataSentinelSchema,
|
||||
supervisorSwarmTransientRetryTargetAgentId,
|
||||
userInputAppDataSentinelFileName,
|
||||
userInputAppDataSentinelSchema,
|
||||
webSearchAppDataSentinelFileName,
|
||||
webSearchAppDataSentinelSchema,
|
||||
windowsProcessHandleHelperSource,
|
||||
@@ -68,21 +58,14 @@ import { isProjectSkillSuite } from '../suites/project-skill.mjs';
|
||||
import { isResponseStreamSuite } from '../suites/response-stream.mjs';
|
||||
import { isScopedAgentsSuite } from '../suites/scoped-agents.mjs';
|
||||
import { isSteerRunnerKillSuite } from '../suites/steer-runner-kill.mjs';
|
||||
import { isSupervisorAutonomousPlayableLaneDefenseSuite } from '../suites/supervisor-autonomous-playable.mjs';
|
||||
import {
|
||||
isSupervisorSwarmAutonomousChatSuite,
|
||||
isSupervisorSwarmCollaborationPolicyMixedRecoverySuite,
|
||||
isSupervisorSwarmFinalReplyTransientRetrySuite,
|
||||
isSupervisorSwarmInitialTransientRetrySuite,
|
||||
isSupervisorSwarmInteractiveChatSuite,
|
||||
isSupervisorSwarmMixedHarnessSuite,
|
||||
isSupervisorSwarmStaticIsolatedAutonomousChatSuite,
|
||||
isSupervisorSwarmSuite,
|
||||
isSupervisorSwarmToolPlanHandoffRunnerKillSuite,
|
||||
isSupervisorSwarmTransientRetrySuite,
|
||||
rebuildSupervisorSwarmTranscriptScanner,
|
||||
} from '../suites/supervisor-swarm.mjs';
|
||||
import { isUserInputRuntimeSuite } from '../suites/user-input.mjs';
|
||||
import {
|
||||
isWebSearchSuite,
|
||||
sameEffectiveAgentLlmWithoutWebSearch,
|
||||
@@ -100,19 +83,10 @@ import { appendBounded, runProcess } from './process.mjs';
|
||||
import { decodeUtf8Fatal, isIsolatedRunnerSuite } from './reporting.mjs';
|
||||
import { killRunnerOnce, readRunnerStatus, runnerBootId } from './runtime.mjs';
|
||||
|
||||
const platformSessionFixtureEnv = 'GENARRATIVE_AGC_PLATFORM_SESSION_FIXTURE';
|
||||
const platformSessionFixtureMaxBytes = 16 * 1024;
|
||||
const isolatedPlatformSessionFixtureName =
|
||||
'.deterministic-platform-session.json';
|
||||
const platformSessionFixtureSchema =
|
||||
'genarrative-agc-platform-session-fixture.v1';
|
||||
|
||||
export function isolatedSuiteProtectsSourceAppData() {
|
||||
return (
|
||||
isSupervisorSwarmTransientRetrySuite() ||
|
||||
isSupervisorSwarmToolPlanHandoffRunnerKillSuite() ||
|
||||
isSupervisorAutonomousPlayableLaneDefenseSuite() ||
|
||||
isSupervisorSwarmInteractiveChatSuite()
|
||||
isSupervisorSwarmToolPlanHandoffRunnerKillSuite()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -120,27 +94,6 @@ export function isolatedSuiteUsesSiblingAppData() {
|
||||
return isWebSearchSuite() || isolatedSuiteProtectsSourceAppData();
|
||||
}
|
||||
|
||||
export function sameSupervisorPlayableProviderBinding(left, right) {
|
||||
const scalarFields = [
|
||||
'providerAgentMode',
|
||||
'providerModel',
|
||||
'providerApiKind',
|
||||
'providerReasoningEffort',
|
||||
'providerBaseUrlSha256',
|
||||
];
|
||||
return (
|
||||
left != null &&
|
||||
right != null &&
|
||||
scalarFields.every((field) => left[field] === right[field]) &&
|
||||
Array.isArray(left.boundAgentIds) &&
|
||||
Array.isArray(right.boundAgentIds) &&
|
||||
left.boundAgentIds.length === right.boundAgentIds.length &&
|
||||
left.boundAgentIds.every(
|
||||
(agentId, index) => agentId === right.boundAgentIds[index],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function isolatedSuiteAppDataProfile() {
|
||||
if (isSteerRunnerKillSuite()) {
|
||||
return {
|
||||
@@ -174,14 +127,6 @@ export function isolatedSuiteAppDataProfile() {
|
||||
codePrefix: 'context-compaction-appdata',
|
||||
};
|
||||
}
|
||||
if (isUserInputRuntimeSuite()) {
|
||||
return {
|
||||
prefix: '.agent-runtime-real-e2e-user-input-',
|
||||
sentinelName: userInputAppDataSentinelFileName,
|
||||
sentinelSchema: userInputAppDataSentinelSchema,
|
||||
codePrefix: 'user-input-appdata',
|
||||
};
|
||||
}
|
||||
if (isScopedAgentsSuite()) {
|
||||
return {
|
||||
prefix: '.agent-runtime-real-e2e-scoped-agents-',
|
||||
@@ -236,41 +181,6 @@ export function isolatedSuiteAppDataProfile() {
|
||||
codePrefix: 'supervisor-swarm-transient-retry-appdata',
|
||||
};
|
||||
}
|
||||
if (isSupervisorAutonomousPlayableLaneDefenseSuite()) {
|
||||
return {
|
||||
prefix: '.agent-runtime-real-e2e-supervisor-autonomous-playable-',
|
||||
sentinelName: supervisorAutonomousPlayableAppDataSentinelFileName,
|
||||
sentinelSchema: supervisorAutonomousPlayableAppDataSentinelSchema,
|
||||
codePrefix: 'supervisor-autonomous-playable-appdata',
|
||||
};
|
||||
}
|
||||
if (isSupervisorSwarmAutonomousChatSuite()) {
|
||||
return {
|
||||
prefix: '.agent-runtime-real-e2e-supervisor-swarm-autonomous-chat-',
|
||||
sentinelName: supervisorSwarmAutonomousChatAppDataSentinelFileName,
|
||||
sentinelSchema: supervisorSwarmAutonomousChatAppDataSentinelSchema,
|
||||
codePrefix: 'supervisor-swarm-autonomous-chat-appdata',
|
||||
};
|
||||
}
|
||||
if (isSupervisorSwarmStaticIsolatedAutonomousChatSuite()) {
|
||||
return {
|
||||
prefix:
|
||||
'.agent-runtime-real-e2e-supervisor-swarm-static-isolated-autonomous-chat-',
|
||||
sentinelName:
|
||||
supervisorSwarmStaticIsolatedAutonomousChatAppDataSentinelFileName,
|
||||
sentinelSchema:
|
||||
supervisorSwarmStaticIsolatedAutonomousChatAppDataSentinelSchema,
|
||||
codePrefix: 'supervisor-swarm-static-isolated-autonomous-chat-appdata',
|
||||
};
|
||||
}
|
||||
if (isSupervisorSwarmCollaborationPolicyMixedRecoverySuite()) {
|
||||
return {
|
||||
prefix: '.agent-runtime-real-e2e-supervisor-swarm-collaboration-policy-',
|
||||
sentinelName: supervisorSwarmCollaborationPolicyAppDataSentinelFileName,
|
||||
sentinelSchema: supervisorSwarmCollaborationPolicyAppDataSentinelSchema,
|
||||
codePrefix: 'supervisor-swarm-collaboration-policy-appdata',
|
||||
};
|
||||
}
|
||||
if (isSupervisorSwarmSuite()) {
|
||||
return {
|
||||
prefix: '.agent-runtime-real-e2e-supervisor-swarm-',
|
||||
@@ -505,120 +415,6 @@ export async function verifySourceAppDataDirectoryUntouched() {
|
||||
state.isolatedRunner.sourceAppDataDirectoryUntouched = true;
|
||||
}
|
||||
|
||||
async function readPlatformSessionFixtureForIsolatedSuite(sourceConfigDir) {
|
||||
const rawPath = process.env[platformSessionFixtureEnv];
|
||||
assert(
|
||||
isNonEmptyString(rawPath) && path.isAbsolute(rawPath),
|
||||
'supervisor-autonomous-playable-platform-session-fixture-missing',
|
||||
);
|
||||
const sourceRealPath = await fs.realpath(sourceConfigDir);
|
||||
const requestedPath = path.resolve(rawPath);
|
||||
const requestedMetadata = await fs.lstat(requestedPath).catch((error) => {
|
||||
if (error?.code === 'ENOENT') return null;
|
||||
throw error;
|
||||
});
|
||||
assert(
|
||||
requestedMetadata?.isFile() && !requestedMetadata.isSymbolicLink(),
|
||||
'supervisor-autonomous-playable-platform-session-fixture-not-regular',
|
||||
);
|
||||
assert(
|
||||
requestedMetadata.size <= platformSessionFixtureMaxBytes,
|
||||
'supervisor-autonomous-playable-platform-session-fixture-too-large',
|
||||
);
|
||||
const realPath = await fs.realpath(requestedPath);
|
||||
assert(
|
||||
isPathInside(sourceRealPath, realPath),
|
||||
'supervisor-autonomous-playable-platform-session-fixture-outside-config',
|
||||
);
|
||||
const bytes = await fs.readFile(realPath);
|
||||
assert(
|
||||
bytes.length <= platformSessionFixtureMaxBytes,
|
||||
'supervisor-autonomous-playable-platform-session-fixture-too-large',
|
||||
);
|
||||
let fixture;
|
||||
try {
|
||||
fixture = JSON.parse(
|
||||
decodeUtf8Fatal(bytes, 'platform-session-fixture-invalid-utf8'),
|
||||
);
|
||||
} catch (error) {
|
||||
throw codedError(
|
||||
'supervisor-autonomous-playable-platform-session-fixture-invalid',
|
||||
error,
|
||||
);
|
||||
}
|
||||
const expectedKeys = [
|
||||
'schemaVersion',
|
||||
'userId',
|
||||
'accessToken',
|
||||
'apiBaseUrl',
|
||||
'generation',
|
||||
];
|
||||
assert(
|
||||
isPlainObject(fixture) &&
|
||||
JSON.stringify(Object.keys(fixture).sort()) ===
|
||||
JSON.stringify([...expectedKeys].sort()) &&
|
||||
fixture.schemaVersion === platformSessionFixtureSchema &&
|
||||
isNonEmptyString(fixture.userId) &&
|
||||
isNonEmptyString(fixture.accessToken) &&
|
||||
isNonEmptyString(fixture.apiBaseUrl) &&
|
||||
Number.isSafeInteger(fixture.generation) &&
|
||||
fixture.generation > 0,
|
||||
'supervisor-autonomous-playable-platform-session-fixture-invalid',
|
||||
);
|
||||
return {
|
||||
sourcePath: realPath,
|
||||
bytes,
|
||||
fixture,
|
||||
sha256: createHash('sha256').update(bytes).digest('hex'),
|
||||
};
|
||||
}
|
||||
|
||||
async function installPlatformSessionFixtureIntoIsolatedAppData(
|
||||
sourceConfigDir,
|
||||
appDataDir,
|
||||
) {
|
||||
if (!isSupervisorAutonomousPlayableLaneDefenseSuite()) return;
|
||||
const source =
|
||||
await readPlatformSessionFixtureForIsolatedSuite(sourceConfigDir);
|
||||
const isolatedPath = path.join(
|
||||
appDataDir,
|
||||
isolatedPlatformSessionFixtureName,
|
||||
);
|
||||
await fs.copyFile(
|
||||
source.sourcePath,
|
||||
isolatedPath,
|
||||
fsConstants.COPYFILE_EXCL | fsConstants.COPYFILE_FICLONE,
|
||||
);
|
||||
await fs.chmod(isolatedPath, 0o600).catch(() => {});
|
||||
const isolatedMetadata = await fs.lstat(isolatedPath);
|
||||
assert(
|
||||
isolatedMetadata.isFile() &&
|
||||
!isolatedMetadata.isSymbolicLink() &&
|
||||
isolatedMetadata.size === source.bytes.length,
|
||||
'supervisor-autonomous-playable-platform-session-fixture-copy-invalid',
|
||||
);
|
||||
const isolatedBytes = await fs.readFile(isolatedPath);
|
||||
assert(
|
||||
createHash('sha256').update(isolatedBytes).digest('hex') === source.sha256,
|
||||
'supervisor-autonomous-playable-platform-session-fixture-copy-mismatch',
|
||||
);
|
||||
state.isolatedRunner.platformSessionFixtureSourcePath = source.sourcePath;
|
||||
state.isolatedRunner.platformSessionFixturePath = isolatedPath;
|
||||
state.isolatedRunner.platformSessionFixtureSha256 = source.sha256;
|
||||
state.isolatedRunner.platformSessionFixturePreviousEnv =
|
||||
Object.prototype.hasOwnProperty.call(process.env, platformSessionFixtureEnv)
|
||||
? process.env[platformSessionFixtureEnv]
|
||||
: undefined;
|
||||
process.env[platformSessionFixtureEnv] = isolatedPath;
|
||||
state.formalConfigPathTranscriptScanner?.addSecrets(
|
||||
absolutePathVariants(source.sourcePath, isolatedPath),
|
||||
);
|
||||
const previousLeakCount = state.transcriptScanner?.count ?? 0;
|
||||
state.secrets = [...new Set([...state.secrets, source.fixture.accessToken])];
|
||||
rebuildSupervisorSwarmTranscriptScanner();
|
||||
state.transcriptScanner.count = previousLeakCount;
|
||||
}
|
||||
|
||||
export async function prepareIsolatedSuiteAppData({
|
||||
streamAgentId = null,
|
||||
webSearchAgentId = null,
|
||||
@@ -773,8 +569,7 @@ export async function prepareIsolatedSuiteAppData({
|
||||
isScopedAgentsSuite() ||
|
||||
isProjectSkillSuite() ||
|
||||
isParallelReadSuite() ||
|
||||
isSupervisorSwarmSuite() ||
|
||||
isSupervisorAutonomousPlayableLaneDefenseSuite()
|
||||
isSupervisorSwarmSuite()
|
||||
? 'private-copy'
|
||||
: 'hardlink';
|
||||
try {
|
||||
@@ -891,10 +686,6 @@ export async function prepareIsolatedSuiteAppData({
|
||||
state.secrets = [...suiteSecrets];
|
||||
rebuildSupervisorSwarmTranscriptScanner();
|
||||
state.transcriptScanner.count = previousLeakCount;
|
||||
await installPlatformSessionFixtureIntoIsolatedAppData(
|
||||
sourceConfigDir,
|
||||
appDataDir,
|
||||
);
|
||||
const unexpectedEndpoint = await fs
|
||||
.lstat(path.join(appDataDir, runnerEndpointFileName))
|
||||
.catch((error) => {
|
||||
@@ -903,46 +694,6 @@ export async function prepareIsolatedSuiteAppData({
|
||||
});
|
||||
assert(!unexpectedEndpoint, `${profile.codePrefix}-endpoint-preexisted`);
|
||||
state.runtimeConfigDir = appDataDir;
|
||||
if (isSupervisorAutonomousPlayableLaneDefenseSuite()) {
|
||||
const isolatedConfig = await loadConfig(appDataDir);
|
||||
const expectedBinding = state.config.providerBinding;
|
||||
const agentIds = [projectSupervisorAgentId];
|
||||
const bindings = agentIds.map((agentId) => {
|
||||
const effective = effectiveAgentLlmConfig(isolatedConfig.config, agentId);
|
||||
assert(
|
||||
['apiKey', 'baseUrl', 'model', 'apiKind', 'reasoningEffort'].every(
|
||||
(key) => isNonEmptyString(effective[key]),
|
||||
),
|
||||
'supervisor-autonomous-playable-effective-provider-incomplete',
|
||||
);
|
||||
return {
|
||||
providerModel: effective.model.trim(),
|
||||
providerApiKind: effective.apiKind.trim(),
|
||||
providerReasoningEffort: effective.reasoningEffort.trim(),
|
||||
providerBaseUrlSha256: hashValue(effective.baseUrl.trim()),
|
||||
};
|
||||
});
|
||||
const effectiveBinding = {
|
||||
...bindings[0],
|
||||
boundAgentIds: [...agentIds].sort(),
|
||||
};
|
||||
assert(
|
||||
expectedBinding &&
|
||||
bindings.every(
|
||||
(binding) => JSON.stringify(binding) === JSON.stringify(bindings[0]),
|
||||
) &&
|
||||
sameSupervisorPlayableProviderBinding(
|
||||
effectiveBinding,
|
||||
expectedBinding,
|
||||
),
|
||||
'supervisor-autonomous-playable-effective-provider-binding-mismatch',
|
||||
);
|
||||
state.supervisorAutonomousPlayable.expectedProviderBinding = {
|
||||
...expectedBinding,
|
||||
};
|
||||
state.supervisorAutonomousPlayable.effectiveProviderBinding =
|
||||
effectiveBinding;
|
||||
}
|
||||
if (streamAgentId) {
|
||||
const isolatedConfig = await loadConfig(appDataDir);
|
||||
const isolatedEffective = effectiveAgentLlmConfig(
|
||||
@@ -981,22 +732,6 @@ export async function prepareIsolatedSuiteAppData({
|
||||
);
|
||||
state.webSearch.effectiveEnabled = true;
|
||||
}
|
||||
if (isUserInputRuntimeSuite()) {
|
||||
const isolatedConfig = await loadConfig(appDataDir);
|
||||
const isolatedEffective = effectiveAgentLlmConfig(
|
||||
isolatedConfig.config,
|
||||
projectSupervisorAgentId,
|
||||
);
|
||||
assert(
|
||||
isolatedEffective.model === 'gpt-5.5' &&
|
||||
['apiKey', 'baseUrl', 'model'].every(
|
||||
(key) =>
|
||||
typeof isolatedEffective[key] === 'string' &&
|
||||
isolatedEffective[key].trim().length > 0,
|
||||
),
|
||||
'user-input-effective-gpt-5-5-config-invalid',
|
||||
);
|
||||
}
|
||||
if (isScopedAgentsSuite()) {
|
||||
const isolatedConfig = await loadConfig(appDataDir);
|
||||
const isolatedEffective = effectiveAgentLlmConfig(
|
||||
@@ -1073,9 +808,8 @@ export async function prepareIsolatedSuiteAppData({
|
||||
effective.requestTimeoutMs > 0 &&
|
||||
Number.isSafeInteger(effective.maxRetries) &&
|
||||
effective.maxRetries >=
|
||||
(isSupervisorSwarmInteractiveChatSuite() ||
|
||||
(isSupervisorSwarmTransientRetrySuite() &&
|
||||
agentId !== supervisorSwarmTransientRetryTargetAgentId)
|
||||
agentId !== supervisorSwarmTransientRetryTargetAgentId
|
||||
? 0
|
||||
: 1) &&
|
||||
effective.maxRetries <= 3 &&
|
||||
@@ -1095,24 +829,6 @@ export async function prepareIsolatedSuiteAppData({
|
||||
{ llm: isolatedConfig.config.llm },
|
||||
'',
|
||||
);
|
||||
if (isSupervisorSwarmMixedHarnessSuite()) {
|
||||
assert(
|
||||
globalEffective.model === 'gpt-5.5' &&
|
||||
globalEffective.apiKind === 'openai_chat' &&
|
||||
isNonEmptyString(globalEffective.reasoningEffort) &&
|
||||
Number.isSafeInteger(globalEffective.requestTimeoutMs) &&
|
||||
globalEffective.requestTimeoutMs > 0 &&
|
||||
Number.isSafeInteger(globalEffective.maxRetries) &&
|
||||
globalEffective.maxRetries >= 0 &&
|
||||
globalEffective.maxRetries <= 3 &&
|
||||
Number.isSafeInteger(globalEffective.retryBackoffMs) &&
|
||||
globalEffective.retryBackoffMs > 0 &&
|
||||
['apiKey', 'baseUrl', 'model'].every((key) =>
|
||||
isNonEmptyString(globalEffective[key]),
|
||||
),
|
||||
'supervisor-swarm-mixed-default-provider-policy-invalid',
|
||||
);
|
||||
}
|
||||
const configuredAgentIds = Object.keys(
|
||||
isPlainObject(isolatedConfig.config.agentLlm)
|
||||
? isolatedConfig.config.agentLlm
|
||||
@@ -2000,31 +1716,6 @@ export async function verifyIsolatedSuiteConfigLinksUnchanged() {
|
||||
'isolated-source-config-changed-during-suite',
|
||||
);
|
||||
}
|
||||
const fixture = state.isolatedRunner;
|
||||
if (
|
||||
fixture.platformSessionFixtureSourcePath &&
|
||||
fixture.platformSessionFixturePath &&
|
||||
fixture.platformSessionFixtureSha256
|
||||
) {
|
||||
const [sourceMetadata, isolatedMetadata, sourceBytes, isolatedBytes] =
|
||||
await Promise.all([
|
||||
fs.lstat(fixture.platformSessionFixtureSourcePath),
|
||||
fs.lstat(fixture.platformSessionFixturePath),
|
||||
fs.readFile(fixture.platformSessionFixtureSourcePath),
|
||||
fs.readFile(fixture.platformSessionFixturePath),
|
||||
]);
|
||||
assert(
|
||||
sourceMetadata.isFile() &&
|
||||
!sourceMetadata.isSymbolicLink() &&
|
||||
isolatedMetadata.isFile() &&
|
||||
!isolatedMetadata.isSymbolicLink() &&
|
||||
createHash('sha256').update(sourceBytes).digest('hex') ===
|
||||
fixture.platformSessionFixtureSha256 &&
|
||||
createHash('sha256').update(isolatedBytes).digest('hex') ===
|
||||
fixture.platformSessionFixtureSha256,
|
||||
'supervisor-autonomous-playable-platform-session-fixture-changed',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function verifySourceConfigLinkCountsRestored() {
|
||||
@@ -2064,22 +1755,7 @@ export async function removeIsolatedSuiteAppData() {
|
||||
} catch (error) {
|
||||
ownershipError = error;
|
||||
}
|
||||
try {
|
||||
await fs.rm(appDataDir, { recursive: true, force: false });
|
||||
} finally {
|
||||
if (state.isolatedRunner.platformSessionFixtureSourcePath) {
|
||||
const previous = state.isolatedRunner.platformSessionFixturePreviousEnv;
|
||||
if (previous === undefined) {
|
||||
delete process.env[platformSessionFixtureEnv];
|
||||
} else {
|
||||
process.env[platformSessionFixtureEnv] = previous;
|
||||
}
|
||||
}
|
||||
state.isolatedRunner.platformSessionFixturePath = null;
|
||||
state.isolatedRunner.platformSessionFixtureSourcePath = null;
|
||||
state.isolatedRunner.platformSessionFixtureSha256 = null;
|
||||
state.isolatedRunner.platformSessionFixturePreviousEnv = undefined;
|
||||
}
|
||||
await fs.rm(appDataDir, { recursive: true, force: false });
|
||||
state.runtimeConfigDir = state.options.configDir;
|
||||
try {
|
||||
await verifySourceConfigLinkCountsRestored();
|
||||
|
||||
@@ -13,15 +13,10 @@ import {
|
||||
responseStreamSuite,
|
||||
scopedAgentsSuite,
|
||||
steerRunnerKillSuite,
|
||||
supervisorAutonomousPlayableLaneDefenseSuite,
|
||||
supervisorSwarmAutonomousChatSuite,
|
||||
supervisorSwarmCollaborationPolicyMixedRecoverySuite,
|
||||
supervisorSwarmFinalReplyTransientRetrySuite,
|
||||
supervisorSwarmStaticIsolatedAutonomousChatSuite,
|
||||
supervisorSwarmSuite,
|
||||
supervisorSwarmToolPlanHandoffRunnerKillSuite,
|
||||
supervisorSwarmTransientRetrySuite,
|
||||
userInputRuntimeSuite,
|
||||
webSearchSuite,
|
||||
} from '../runtime-state.mjs';
|
||||
import { collectApiKeys, isPathInside } from './io.mjs';
|
||||
@@ -58,7 +53,6 @@ export function parseArguments(args) {
|
||||
suite === responseStreamSuite ||
|
||||
suite === webSearchSuite ||
|
||||
suite === contextCompactionSuite ||
|
||||
suite === userInputRuntimeSuite ||
|
||||
suite === scopedAgentsSuite ||
|
||||
suite === projectSkillSuite ||
|
||||
suite === parallelReadSuite ||
|
||||
@@ -66,10 +60,6 @@ export function parseArguments(args) {
|
||||
suite === supervisorSwarmTransientRetrySuite ||
|
||||
suite === supervisorSwarmFinalReplyTransientRetrySuite ||
|
||||
suite === supervisorSwarmToolPlanHandoffRunnerKillSuite ||
|
||||
suite === supervisorSwarmAutonomousChatSuite ||
|
||||
suite === supervisorAutonomousPlayableLaneDefenseSuite ||
|
||||
suite === supervisorSwarmStaticIsolatedAutonomousChatSuite ||
|
||||
suite === supervisorSwarmCollaborationPolicyMixedRecoverySuite ||
|
||||
suite === steerRunnerKillSuite ||
|
||||
processSessionSuites.has(suite),
|
||||
'unsupported-suite',
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
assert,
|
||||
codedError,
|
||||
hashValue,
|
||||
sleep,
|
||||
throwIfShutdownRequested,
|
||||
} from '../assertions/core.mjs';
|
||||
import { fs, path, spawn, withLoopbackNoProxy } from '../dependencies.mjs';
|
||||
@@ -13,14 +12,9 @@ import {
|
||||
manifestPath,
|
||||
state,
|
||||
} from '../runtime-state.mjs';
|
||||
import {
|
||||
isSupervisorSwarmTransientRetrySuite,
|
||||
recordSupervisorSwarmChatSessionFailureDiagnostic,
|
||||
} from '../suites/supervisor-swarm.mjs';
|
||||
import { isSupervisorSwarmTransientRetrySuite } from '../suites/supervisor-swarm.mjs';
|
||||
import { isIsolatedRunnerSuite } from './reporting.mjs';
|
||||
|
||||
export const activeInteractiveCliSessions = new Set();
|
||||
|
||||
export async function prepareCliBinary() {
|
||||
const cargo = process.platform === 'win32' ? 'cargo.exe' : 'cargo';
|
||||
await runProcess(
|
||||
@@ -84,8 +78,8 @@ export function buildCliChildEnvironment() {
|
||||
NO_COLOR: '1',
|
||||
RUST_BACKTRACE: '0',
|
||||
};
|
||||
// The deterministic playable suite copies its account fixture into the
|
||||
// sibling isolated AppData directory. Set the path explicitly here so
|
||||
// The isolated runner copies its account fixture into the sibling isolated
|
||||
// AppData directory. Set the path explicitly here so
|
||||
// every CLI and the Runner it launches use the isolated copy, even if the
|
||||
// parent harness environment was restored or changed after setup.
|
||||
if (state.isolatedRunner.platformSessionFixturePath) {
|
||||
@@ -144,197 +138,6 @@ export function codedProcessError(code, details) {
|
||||
return error;
|
||||
}
|
||||
|
||||
export function startInteractiveCli(args) {
|
||||
assert(Boolean(state.cliBinary), 'interactive-cli-binary-not-ready');
|
||||
assert(Boolean(state.runtimeConfigDir), 'interactive-config-dir-not-ready');
|
||||
if (
|
||||
isIsolatedRunnerSuite() &&
|
||||
state.options?.configDir &&
|
||||
path.resolve(state.runtimeConfigDir) ===
|
||||
path.resolve(state.options.configDir)
|
||||
) {
|
||||
state.isolatedRunner.sourceConfigCliCallCount += 1;
|
||||
}
|
||||
const child = spawn(
|
||||
state.cliBinary,
|
||||
[...args, '--config-dir', state.runtimeConfigDir],
|
||||
{
|
||||
cwd: appRoot,
|
||||
env: buildCliChildEnvironment(),
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
},
|
||||
);
|
||||
return createInteractiveCliSession(child);
|
||||
}
|
||||
|
||||
export function createInteractiveCliSession(child) {
|
||||
activeCommandChildren.add(child);
|
||||
const session = {
|
||||
child,
|
||||
stdout: Buffer.alloc(0),
|
||||
stderr: Buffer.alloc(0),
|
||||
exited: false,
|
||||
exitInfo: null,
|
||||
exitPromise: null,
|
||||
closed: false,
|
||||
closeInfo: null,
|
||||
closePromise: null,
|
||||
stdioClosed: false,
|
||||
stdioCloseInfo: null,
|
||||
spawnError: null,
|
||||
stdinError: null,
|
||||
};
|
||||
activeInteractiveCliSessions.add(session);
|
||||
session.exitPromise = new Promise((resolve) => {
|
||||
const settle = (result) => {
|
||||
if (session.exited) return;
|
||||
activeCommandChildren.delete(child);
|
||||
session.exited = true;
|
||||
session.closed = true;
|
||||
session.exitInfo = result;
|
||||
session.closeInfo = result;
|
||||
resolve(result);
|
||||
};
|
||||
child.once('error', (error) => {
|
||||
session.spawnError = error;
|
||||
settle({ code: null, signal: null, error });
|
||||
});
|
||||
child.once('exit', (code, signal) => {
|
||||
settle({ code, signal, error: null });
|
||||
});
|
||||
});
|
||||
session.closePromise = new Promise((resolve) => {
|
||||
child.once('close', (code, signal) => {
|
||||
activeCommandChildren.delete(child);
|
||||
activeInteractiveCliSessions.delete(session);
|
||||
session.stdioClosed = true;
|
||||
session.stdioCloseInfo = {
|
||||
code,
|
||||
signal,
|
||||
error: session.spawnError,
|
||||
};
|
||||
resolve(session.stdioCloseInfo);
|
||||
});
|
||||
});
|
||||
child.stdin?.on('error', (error) => {
|
||||
session.stdinError ??= error;
|
||||
});
|
||||
child.stdout.on('data', (chunk) => {
|
||||
state.transcriptScanner?.scan('interactive-stdout', chunk);
|
||||
state.projectPathTranscriptScanner?.scan('interactive-stdout', chunk);
|
||||
state.formalConfigPathTranscriptScanner?.scan('interactive-stdout', chunk);
|
||||
session.stdout = appendBounded(session.stdout, chunk, commandOutputLimit);
|
||||
});
|
||||
child.stderr.on('data', (chunk) => {
|
||||
state.transcriptScanner?.scan('interactive-stderr', chunk);
|
||||
state.projectPathTranscriptScanner?.scan('interactive-stderr', chunk);
|
||||
state.formalConfigPathTranscriptScanner?.scan('interactive-stderr', chunk);
|
||||
session.stderr = appendBounded(session.stderr, chunk, commandOutputLimit);
|
||||
});
|
||||
return session;
|
||||
}
|
||||
|
||||
export function interactiveCliOutput(session) {
|
||||
return `${session.stdout.toString('utf8')}\n${session.stderr.toString('utf8')}`;
|
||||
}
|
||||
|
||||
export function writeInteractiveCliLine(session, line) {
|
||||
assert(!session.closed, 'interactive-cli-already-closed');
|
||||
assert(session.child.stdin.writable, 'interactive-cli-stdin-not-writable');
|
||||
session.child.stdin.write(`${line}\n`);
|
||||
}
|
||||
|
||||
export async function waitForInteractiveCliOutput(
|
||||
session,
|
||||
predicate,
|
||||
code,
|
||||
timeoutMs,
|
||||
{ allowAfterProcessExit = false } = {},
|
||||
) {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
while (Date.now() < deadline) {
|
||||
const output = interactiveCliOutput(session);
|
||||
if (predicate(output)) return output;
|
||||
if (session.exited && !allowAfterProcessExit) {
|
||||
if (session === state.supervisorSwarmCliSession) {
|
||||
recordSupervisorSwarmChatSessionFailureDiagnostic(session);
|
||||
}
|
||||
throw codedError(`${code}-cli-exited`);
|
||||
}
|
||||
if (session.stdioClosed) {
|
||||
if (session === state.supervisorSwarmCliSession) {
|
||||
recordSupervisorSwarmChatSessionFailureDiagnostic(session);
|
||||
}
|
||||
throw codedError(`${code}-cli-stdio-closed`);
|
||||
}
|
||||
await sleep(50);
|
||||
}
|
||||
throw codedError(code);
|
||||
}
|
||||
|
||||
export async function waitForInteractiveCliExit(session, timeoutMs) {
|
||||
const result = await Promise.race([
|
||||
session.exitPromise,
|
||||
sleep(timeoutMs).then(() => null),
|
||||
]);
|
||||
if (!result) throw codedError('interactive-cli-exit-timeout');
|
||||
if (result.error) throw codedError('interactive-cli-process-error');
|
||||
assert(
|
||||
result.code === 0 && result.signal === null,
|
||||
'interactive-cli-exit-invalid',
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function closeInteractiveCli(session) {
|
||||
if (!session) return null;
|
||||
if (session.exited) return session.exitInfo;
|
||||
if (
|
||||
session.child.stdin.writable &&
|
||||
!session.child.stdin.writableEnded &&
|
||||
!session.child.stdin.destroyed
|
||||
) {
|
||||
session.child.stdin.write('/quit\n');
|
||||
}
|
||||
let result = await Promise.race([
|
||||
session.exitPromise,
|
||||
sleep(3_000).then(() => null),
|
||||
]);
|
||||
if (!result && !session.exited) {
|
||||
session.child.kill('SIGTERM');
|
||||
result = await Promise.race([
|
||||
session.exitPromise,
|
||||
sleep(2_000).then(() => null),
|
||||
]);
|
||||
}
|
||||
if (!result && !session.exited) {
|
||||
session.child.kill('SIGKILL');
|
||||
result = await Promise.race([
|
||||
session.exitPromise,
|
||||
sleep(5_000).then(() => null),
|
||||
]);
|
||||
}
|
||||
assert(Boolean(result), 'interactive-cli-cleanup-timeout');
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function waitForInteractiveCliStdioClose(session, timeoutMs) {
|
||||
if (!session || session.stdioClosed) return session?.stdioCloseInfo ?? null;
|
||||
const result = await Promise.race([
|
||||
session.closePromise,
|
||||
sleep(timeoutMs).then(() => null),
|
||||
]);
|
||||
if (!result) throw codedError('interactive-cli-stdio-close-timeout');
|
||||
return result;
|
||||
}
|
||||
|
||||
export function destroyInteractiveCliOutputStreams(session) {
|
||||
if (!session) return;
|
||||
for (const stream of [session.child.stdout, session.child.stderr]) {
|
||||
if (stream && !stream.destroyed) stream.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export async function runProcess(
|
||||
program,
|
||||
args,
|
||||
@@ -406,214 +209,6 @@ export async function runProcess(
|
||||
});
|
||||
}
|
||||
|
||||
export async function listSystemProcessIdentities() {
|
||||
if (process.platform === 'win32') {
|
||||
const systemRoot = process.env.SystemRoot ?? process.env.SYSTEMROOT;
|
||||
assert(
|
||||
typeof systemRoot === 'string' && path.isAbsolute(systemRoot),
|
||||
'owned-process-snapshot-system-root-invalid',
|
||||
);
|
||||
const powershell = path.join(
|
||||
systemRoot,
|
||||
'System32/WindowsPowerShell/v1.0/powershell.exe',
|
||||
);
|
||||
const metadata = await fs.lstat(powershell);
|
||||
assert(
|
||||
metadata.isFile() && !metadata.isSymbolicLink(),
|
||||
'owned-process-snapshot-powershell-invalid',
|
||||
);
|
||||
const result = await runProcess(
|
||||
powershell,
|
||||
[
|
||||
'-NoProfile',
|
||||
'-NonInteractive',
|
||||
'-Command',
|
||||
'$processes = @(Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CreationDate,Name); $processes | ConvertTo-Json -Compress',
|
||||
],
|
||||
{
|
||||
cwd: appRoot,
|
||||
timeoutMs: 30_000,
|
||||
env: { ...process.env, NO_COLOR: '1', RUST_BACKTRACE: '0' },
|
||||
},
|
||||
);
|
||||
const parsed = JSON.parse(result.stdout);
|
||||
return (Array.isArray(parsed) ? parsed : [parsed])
|
||||
.map((record) => ({
|
||||
pid: Number(record?.ProcessId),
|
||||
parentPid: Number(record?.ParentProcessId),
|
||||
startedAt: String(record?.CreationDate ?? ''),
|
||||
name: String(record?.Name ?? ''),
|
||||
}))
|
||||
.filter(validSystemProcessIdentity);
|
||||
}
|
||||
assert(
|
||||
process.platform === 'linux' || process.platform === 'darwin',
|
||||
'owned-process-snapshot-platform-unsupported',
|
||||
);
|
||||
const result = await runProcess(
|
||||
'ps',
|
||||
['-A', '-o', 'pid=', '-o', 'ppid=', '-o', 'lstart=', '-o', 'comm='],
|
||||
{ cwd: appRoot, timeoutMs: 30_000 },
|
||||
);
|
||||
return result.stdout
|
||||
.split(/\r?\n/u)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
.map((line) => {
|
||||
const fields = line.split(/\s+/u);
|
||||
return {
|
||||
pid: Number(fields[0]),
|
||||
parentPid: Number(fields[1]),
|
||||
startedAt: fields.slice(2, 7).join(' '),
|
||||
name: fields.slice(7).join(' '),
|
||||
};
|
||||
})
|
||||
.filter(validSystemProcessIdentity);
|
||||
}
|
||||
|
||||
function validSystemProcessIdentity(record) {
|
||||
return (
|
||||
Number.isSafeInteger(record?.pid) &&
|
||||
record.pid > 0 &&
|
||||
Number.isSafeInteger(record.parentPid) &&
|
||||
record.parentPid >= 0 &&
|
||||
typeof record.startedAt === 'string' &&
|
||||
record.startedAt.length > 0 &&
|
||||
typeof record.name === 'string' &&
|
||||
record.name.length > 0
|
||||
);
|
||||
}
|
||||
|
||||
export function buildOwnedProcessCleanupSnapshot(
|
||||
processRecords,
|
||||
{ rootPids = [], runnerPid = null, helperPids = [] } = {},
|
||||
) {
|
||||
assert(
|
||||
Array.isArray(processRecords) &&
|
||||
Array.isArray(rootPids) &&
|
||||
Array.isArray(helperPids),
|
||||
'owned-process-snapshot-input-invalid',
|
||||
);
|
||||
const records = processRecords.filter(validSystemProcessIdentity);
|
||||
const byPid = new Map(records.map((record) => [record.pid, record]));
|
||||
const childrenByParent = new Map();
|
||||
for (const record of records) {
|
||||
const children = childrenByParent.get(record.parentPid) ?? [];
|
||||
children.push(record.pid);
|
||||
childrenByParent.set(record.parentPid, children);
|
||||
}
|
||||
const normalizedRunnerPid = Number.isSafeInteger(runnerPid)
|
||||
? runnerPid
|
||||
: null;
|
||||
const helperPidSet = new Set(
|
||||
helperPids.filter((pid) => Number.isSafeInteger(pid) && pid > 0),
|
||||
);
|
||||
const roots = [
|
||||
...new Set(
|
||||
[...rootPids, normalizedRunnerPid, ...helperPidSet].filter(
|
||||
(pid) => Number.isSafeInteger(pid) && pid > 0,
|
||||
),
|
||||
),
|
||||
];
|
||||
assert(roots.length > 0, 'owned-process-snapshot-root-missing');
|
||||
const ownedPids = new Set();
|
||||
const queue = [...roots];
|
||||
while (queue.length > 0) {
|
||||
const pid = queue.shift();
|
||||
if (ownedPids.has(pid)) continue;
|
||||
ownedPids.add(pid);
|
||||
queue.push(...(childrenByParent.get(pid) ?? []));
|
||||
}
|
||||
const identities = [...ownedPids]
|
||||
.map((pid) => byPid.get(pid))
|
||||
.filter(Boolean)
|
||||
.map((record) => ({
|
||||
pid: record.pid,
|
||||
startedAt: record.startedAt,
|
||||
name: record.name,
|
||||
kind: ownedProcessKind(record, normalizedRunnerPid, helperPidSet),
|
||||
}))
|
||||
.sort((left, right) => left.pid - right.pid);
|
||||
return {
|
||||
identities,
|
||||
observedCounts: countOwnedProcessKinds(identities),
|
||||
};
|
||||
}
|
||||
|
||||
function ownedProcessKind(record, runnerPid, helperPids) {
|
||||
if (record.pid === runnerPid) return 'runner';
|
||||
if (helperPids.has(record.pid)) return 'helper';
|
||||
const name = path.basename(record.name).toLowerCase();
|
||||
if (/^node(?:\.exe)?$/u.test(name)) return 'node';
|
||||
if (/^(?:chrome|chromium|msedge|google-chrome)(?:\.exe)?$/u.test(name)) {
|
||||
return 'browser';
|
||||
}
|
||||
return 'command';
|
||||
}
|
||||
|
||||
function countOwnedProcessKinds(identities) {
|
||||
const counts = {
|
||||
runner: 0,
|
||||
helper: 0,
|
||||
node: 0,
|
||||
browser: 0,
|
||||
command: 0,
|
||||
total: identities.length,
|
||||
};
|
||||
for (const identity of identities) counts[identity.kind] += 1;
|
||||
return counts;
|
||||
}
|
||||
|
||||
export function inspectOwnedProcessCleanupResiduals(
|
||||
snapshot,
|
||||
processRecords,
|
||||
{ activeCommandChildCount = 0, activeInteractiveCliSessionCount = 0 } = {},
|
||||
) {
|
||||
assert(
|
||||
Array.isArray(snapshot?.identities) && Array.isArray(processRecords),
|
||||
'owned-process-residual-input-invalid',
|
||||
);
|
||||
const currentByPid = new Map(
|
||||
processRecords
|
||||
.filter(validSystemProcessIdentity)
|
||||
.map((record) => [record.pid, record]),
|
||||
);
|
||||
const residualIdentities = snapshot.identities.filter((identity) => {
|
||||
const current = currentByPid.get(identity.pid);
|
||||
return (
|
||||
current?.startedAt === identity.startedAt &&
|
||||
current?.name === identity.name
|
||||
);
|
||||
});
|
||||
return {
|
||||
residualCounts: countOwnedProcessKinds(residualIdentities),
|
||||
activeCommandChildCount,
|
||||
activeInteractiveCliSessionCount,
|
||||
clean:
|
||||
residualIdentities.length === 0 &&
|
||||
activeCommandChildCount === 0 &&
|
||||
activeInteractiveCliSessionCount === 0,
|
||||
};
|
||||
}
|
||||
|
||||
export async function captureOwnedProcessCleanupSnapshot(options) {
|
||||
return buildOwnedProcessCleanupSnapshot(
|
||||
await listSystemProcessIdentities(),
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
export async function verifyOwnedProcessCleanupSnapshot(snapshot) {
|
||||
return inspectOwnedProcessCleanupResiduals(
|
||||
snapshot,
|
||||
await listSystemProcessIdentities(),
|
||||
{
|
||||
activeCommandChildCount: activeCommandChildren.size,
|
||||
activeInteractiveCliSessionCount: activeInteractiveCliSessions.size,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function appendBounded(current, chunk, limit) {
|
||||
const combined = Buffer.concat([current, chunk]);
|
||||
return combined.length <= limit
|
||||
|
||||
@@ -28,12 +28,10 @@ import {
|
||||
isGoalRuntimeSuite,
|
||||
} from '../suites/goal.mjs';
|
||||
import { isResponseStreamSuite } from '../suites/response-stream.mjs';
|
||||
import { isSupervisorAutonomousPlayableLaneDefenseSuite } from '../suites/supervisor-autonomous-playable.mjs';
|
||||
import {
|
||||
isSupervisorSwarmSuite,
|
||||
supervisorSwarmVerificationFixtureSource,
|
||||
} from '../suites/supervisor-swarm.mjs';
|
||||
import { isUserInputRuntimeSuite } from '../suites/user-input.mjs';
|
||||
import { isWebSearchSuite } from '../suites/web-search.mjs';
|
||||
import { createSentinelOwnedTempDirectory } from './app-data.mjs';
|
||||
import { effectiveAgentLlmConfig } from './config.mjs';
|
||||
@@ -41,19 +39,15 @@ import { runProcess } from './process.mjs';
|
||||
import { isIsolatedRunnerSuite } from './reporting.mjs';
|
||||
|
||||
export function requiredAgentIdsForSuite() {
|
||||
return isUserInputRuntimeSuite()
|
||||
? [projectSupervisorAgentId]
|
||||
: isSupervisorAutonomousPlayableLaneDefenseSuite()
|
||||
? [projectSupervisorAgentId]
|
||||
: isSupervisorSwarmSuite()
|
||||
? [
|
||||
projectSupervisorAgentId,
|
||||
supervisorSwarmDesignAgentId,
|
||||
supervisorSwarmQualityAgentId,
|
||||
]
|
||||
: isIsolatedRunnerSuite()
|
||||
? [mainAgentId]
|
||||
: [mainAgentId, 'quality-review'];
|
||||
return isSupervisorSwarmSuite()
|
||||
? [
|
||||
projectSupervisorAgentId,
|
||||
supervisorSwarmDesignAgentId,
|
||||
supervisorSwarmQualityAgentId,
|
||||
]
|
||||
: isIsolatedRunnerSuite()
|
||||
? [mainAgentId]
|
||||
: [mainAgentId, 'quality-review'];
|
||||
}
|
||||
|
||||
export function expectedProviderBindingForSuite(config) {
|
||||
@@ -120,11 +114,9 @@ export async function checkPrerequisites(config) {
|
||||
return {
|
||||
llmConfigured,
|
||||
providerBinding,
|
||||
chromeAvailable:
|
||||
!isIsolatedRunnerSuite() ||
|
||||
isSupervisorAutonomousPlayableLaneDefenseSuite()
|
||||
? Boolean(await findSupportedBrowser())
|
||||
: false,
|
||||
chromeAvailable: !isIsolatedRunnerSuite()
|
||||
? Boolean(await findSupportedBrowser())
|
||||
: false,
|
||||
editorApiConfigured,
|
||||
};
|
||||
}
|
||||
@@ -267,8 +259,7 @@ export async function seedDisposableProject({
|
||||
? supervisorSwarmVerificationFixtureSource()
|
||||
: isGoalRuntimeSuite() ||
|
||||
isResponseStreamSuite() ||
|
||||
isWebSearchSuite() ||
|
||||
isSupervisorAutonomousPlayableLaneDefenseSuite()
|
||||
isWebSearchSuite()
|
||||
? goalRevisionOneVerificationFixtureSource()
|
||||
: goalRevisionTwoVerificationFixtureSource(),
|
||||
),
|
||||
@@ -405,23 +396,6 @@ export function seededGameHtml() {
|
||||
`;
|
||||
}
|
||||
|
||||
export function productionDefaultGameIndexHtml() {
|
||||
return `<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Genarrative Game Draft</title>
|
||||
<style>
|
||||
body { margin: 0; display: grid; min-height: 100vh; place-items: center; background: #101827; color: #d9e7ff; font: 16px system-ui, sans-serif; }
|
||||
main { width: min(720px, calc(100vw - 32px)); }
|
||||
</style>
|
||||
</head>
|
||||
<body><main>还没有生成游戏。回到聊天输入创意并确认生成后,这里会写入可试玩原型。</main></body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
export function buildTaskPrompt(suite) {
|
||||
const editorAssetOutcome =
|
||||
suite === 'full'
|
||||
|
||||
@@ -20,9 +20,7 @@ import { isProjectSkillSuite } from '../suites/project-skill.mjs';
|
||||
import { isResponseStreamSuite } from '../suites/response-stream.mjs';
|
||||
import { isScopedAgentsSuite } from '../suites/scoped-agents.mjs';
|
||||
import { isSteerRunnerKillSuite } from '../suites/steer-runner-kill.mjs';
|
||||
import { isSupervisorAutonomousPlayableLaneDefenseSuite } from '../suites/supervisor-autonomous-playable.mjs';
|
||||
import { isSupervisorSwarmSuite } from '../suites/supervisor-swarm.mjs';
|
||||
import { isUserInputRuntimeSuite } from '../suites/user-input.mjs';
|
||||
import { isWebSearchSuite } from '../suites/web-search.mjs';
|
||||
import { isPathInside, readJson } from './io.mjs';
|
||||
|
||||
@@ -88,17 +86,12 @@ export function buildSummary() {
|
||||
config: state.config,
|
||||
blocked: state.blocked,
|
||||
run: {
|
||||
agentId:
|
||||
isUserInputRuntimeSuite() ||
|
||||
isSupervisorAutonomousPlayableLaneDefenseSuite() ||
|
||||
isSupervisorSwarmSuite()
|
||||
? projectSupervisorAgentId
|
||||
: mainAgentId,
|
||||
agentId: isSupervisorSwarmSuite()
|
||||
? projectSupervisorAgentId
|
||||
: mainAgentId,
|
||||
runIdHash: hashValue(state.initialRunId),
|
||||
sessionIdHash: hashValue(state.initialSessionId),
|
||||
runnerKilled: isSupervisorAutonomousPlayableLaneDefenseSuite()
|
||||
? false
|
||||
: state.runnerKilled,
|
||||
runnerKilled: state.runnerKilled,
|
||||
resumed: state.resumed,
|
||||
identityStable: state.identityStable,
|
||||
},
|
||||
@@ -110,11 +103,9 @@ export function buildSummary() {
|
||||
projectPathReportLeakCount: state.projectPathReportLeakCount,
|
||||
...(isWebSearchSuite() ||
|
||||
isContextCompactionSuite() ||
|
||||
isUserInputRuntimeSuite() ||
|
||||
isScopedAgentsSuite() ||
|
||||
isProjectSkillSuite() ||
|
||||
isParallelReadSuite() ||
|
||||
isSupervisorAutonomousPlayableLaneDefenseSuite() ||
|
||||
isSupervisorSwarmSuite() ||
|
||||
isSteerRunnerKillSuite()
|
||||
? {
|
||||
@@ -408,11 +399,9 @@ export function isIsolatedRunnerSuite() {
|
||||
isResponseStreamSuite() ||
|
||||
isWebSearchSuite() ||
|
||||
isContextCompactionSuite() ||
|
||||
isUserInputRuntimeSuite() ||
|
||||
isScopedAgentsSuite() ||
|
||||
isProjectSkillSuite() ||
|
||||
isParallelReadSuite() ||
|
||||
isSupervisorAutonomousPlayableLaneDefenseSuite() ||
|
||||
isSupervisorSwarmSuite()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -87,11 +87,7 @@ import {
|
||||
import { isProjectSkillSuite } from '../suites/project-skill.mjs';
|
||||
import { isScopedAgentsSuite } from '../suites/scoped-agents.mjs';
|
||||
import { isSteerRunnerKillSuite } from '../suites/steer-runner-kill.mjs';
|
||||
import {
|
||||
confirmSupervisorSwarmPendingActionsInChat,
|
||||
isSupervisorSwarmInteractiveChatSuite,
|
||||
isSupervisorSwarmSuite,
|
||||
} from '../suites/supervisor-swarm.mjs';
|
||||
import { isSupervisorSwarmSuite } from '../suites/supervisor-swarm.mjs';
|
||||
import { killRunnerPidOnce, verifyOwnedRunnerForKill } from './app-data.mjs';
|
||||
import { isPlainObject } from './config.mjs';
|
||||
import {
|
||||
@@ -1038,13 +1034,6 @@ export async function confirmPendingActions(
|
||||
allowedTools = null,
|
||||
shouldConfirm = () => true,
|
||||
) {
|
||||
if (isSupervisorSwarmInteractiveChatSuite()) {
|
||||
await confirmSupervisorSwarmPendingActionsInChat(
|
||||
allowedTools,
|
||||
shouldConfirm,
|
||||
);
|
||||
return;
|
||||
}
|
||||
for (const pending of await findPendingActions()) {
|
||||
if (state.confirmedActionIds.has(pending.actionId)) continue;
|
||||
if (!shouldConfirm(pending)) continue;
|
||||
|
||||
@@ -43,12 +43,6 @@ export const contextCompactionAppDataSentinelFileName =
|
||||
export const contextCompactionAppDataSentinelSchema =
|
||||
'genarrative-agent-runtime-real-e2e-context-compaction-appdata.v1';
|
||||
|
||||
export const userInputAppDataSentinelFileName =
|
||||
'.agent-runtime-real-e2e-user-input-appdata.json';
|
||||
|
||||
export const userInputAppDataSentinelSchema =
|
||||
'genarrative-agent-runtime-real-e2e-user-input-appdata.v1';
|
||||
|
||||
export const scopedAgentsAppDataSentinelFileName =
|
||||
'.agent-runtime-real-e2e-scoped-agents-appdata.json';
|
||||
|
||||
@@ -109,30 +103,6 @@ export const supervisorSwarmToolPlanHandoffCheckpointReachedFileName =
|
||||
export const supervisorSwarmToolPlanHandoffCheckpointReachedSchema =
|
||||
'game-creator-tool-plan-handoff-runner-kill-checkpoint-reached.v1';
|
||||
|
||||
export const supervisorSwarmAutonomousChatAppDataSentinelFileName =
|
||||
'.agent-runtime-real-e2e-supervisor-swarm-autonomous-chat-appdata.json';
|
||||
|
||||
export const supervisorSwarmAutonomousChatAppDataSentinelSchema =
|
||||
'genarrative-agent-runtime-real-e2e-supervisor-swarm-autonomous-chat-appdata.v1';
|
||||
|
||||
export const supervisorAutonomousPlayableAppDataSentinelFileName =
|
||||
'.agent-runtime-real-e2e-supervisor-autonomous-playable-appdata.json';
|
||||
|
||||
export const supervisorAutonomousPlayableAppDataSentinelSchema =
|
||||
'genarrative-agent-runtime-real-e2e-supervisor-autonomous-playable-appdata.v1';
|
||||
|
||||
export const supervisorSwarmStaticIsolatedAutonomousChatAppDataSentinelFileName =
|
||||
'.agent-runtime-real-e2e-supervisor-swarm-static-isolated-autonomous-chat-appdata.json';
|
||||
|
||||
export const supervisorSwarmStaticIsolatedAutonomousChatAppDataSentinelSchema =
|
||||
'genarrative-agent-runtime-real-e2e-supervisor-swarm-static-isolated-autonomous-chat-appdata.v1';
|
||||
|
||||
export const supervisorSwarmCollaborationPolicyAppDataSentinelFileName =
|
||||
'.agent-runtime-real-e2e-supervisor-swarm-collaboration-policy-appdata.json';
|
||||
|
||||
export const supervisorSwarmCollaborationPolicyAppDataSentinelSchema =
|
||||
'genarrative-agent-runtime-real-e2e-supervisor-swarm-collaboration-policy-appdata.v1';
|
||||
|
||||
export const mainAgentId = 'code-prototype';
|
||||
|
||||
export const projectSupervisorAgentId = 'project-supervisor';
|
||||
@@ -179,8 +149,6 @@ export const webSearchSuite = 'web-search';
|
||||
|
||||
export const contextCompactionSuite = 'context-compaction';
|
||||
|
||||
export const userInputRuntimeSuite = 'user-input-runtime';
|
||||
|
||||
export const scopedAgentsSuite = 'scoped-agents';
|
||||
|
||||
export const projectSkillSuite = 'project-skill';
|
||||
@@ -213,18 +181,6 @@ export const supervisorSwarmTransientRetryBackoffMs = 30_000;
|
||||
|
||||
export const supervisorSwarmTransientRetryPreDueGuardMs = 1_500;
|
||||
|
||||
export const supervisorSwarmAutonomousChatSuite =
|
||||
'supervisor-swarm-autonomous-chat';
|
||||
|
||||
export const supervisorAutonomousPlayableLaneDefenseSuite =
|
||||
'supervisor-autonomous-playable-lane-defense';
|
||||
|
||||
export const supervisorSwarmStaticIsolatedAutonomousChatSuite =
|
||||
'supervisor-swarm-static-isolated-autonomous-chat';
|
||||
|
||||
export const supervisorSwarmCollaborationPolicyMixedRecoverySuite =
|
||||
'supervisor-swarm-collaboration-policy-mixed-recovery';
|
||||
|
||||
export const runtimeContextBundleSchemaVersion =
|
||||
'game-creator-runtime-context-bundle.v5';
|
||||
|
||||
@@ -234,17 +190,6 @@ export const providerRequestLifecycleSchemaVersion =
|
||||
export const providerActionBatchSchemaVersion =
|
||||
'game-creator-provider-action-batch.v2';
|
||||
|
||||
export const runProfileBindingSchemaVersion =
|
||||
'game-creator-run-profile-binding.v1';
|
||||
|
||||
export const autonomousCompletionContractSchemaVersion =
|
||||
'game-creator-autonomous-completion-contract.v2';
|
||||
|
||||
export const autonomousPlaytestReceiptSchemaVersion =
|
||||
'game-creator-autonomous-playtest-receipt.v2';
|
||||
|
||||
export const autonomousGameBuildRunProfile = 'autonomous-game-build';
|
||||
|
||||
export const supervisorCollaborationPolicySchemaVersion =
|
||||
'game-creator-supervisor-collaboration-policy.v1';
|
||||
|
||||
@@ -287,12 +232,6 @@ export const contextCompactionTriggerTurns = new Set([4, 8]);
|
||||
|
||||
export const contextCompactionConstraintCanary = `GENARRATIVE_CONTEXT_CONSTRAINT_${randomUUID().replaceAll('-', '').slice(0, 20)}`;
|
||||
|
||||
export const userInputAnswerCanary = `GENARRATIVE_USER_CHOICE_${randomUUID()
|
||||
.replaceAll('-', '')
|
||||
.slice(0, 20)}`;
|
||||
|
||||
export const userInputAnswerText = `选择轻量像素风,优先保证移动端轮廓和动作可读性;确认标记 ${userInputAnswerCanary}`;
|
||||
|
||||
export const scopedAgentsRootCanary = `GENARRATIVE_SCOPE_ROOT_${randomUUID()
|
||||
.replaceAll('-', '')
|
||||
.slice(0, 20)}`;
|
||||
@@ -461,143 +400,6 @@ export const supervisorSwarmConfirmedTools = [
|
||||
'project.verify',
|
||||
];
|
||||
|
||||
export const supervisorSwarmAutonomousTask =
|
||||
'请把这个试玩项目推进到可以交给首批玩家体验的状态。以仓库现有的正式交付要求、临时检查要求和实际验证结果为准;按阶段生效的临时检查必须在各自生效后全部完成,不能把后续检查并入前置检查或漏掉。完成后简短说明交付内容、验证结论和仍需关注的问题。';
|
||||
|
||||
export const supervisorAutonomousPlayableLaneDefenseTask =
|
||||
'做一个植物大战僵尸式的塔防游戏,要能选择植物、阻挡敌人、正常闯关,并且生成后可以直接试玩。';
|
||||
|
||||
export const supervisorAutonomousPlayableSourceFieldMaxChars = 8_000;
|
||||
|
||||
export const supervisorAutonomousPlayableSourceTotalMaxChars = 10_000;
|
||||
|
||||
export const supervisorAutonomousPlayableSafeStatuses = new Set([
|
||||
'cancelling',
|
||||
'budget-exhausted',
|
||||
'cancelled',
|
||||
'completed',
|
||||
'failed',
|
||||
'idle',
|
||||
'needs-reconciliation',
|
||||
'paused',
|
||||
'pausing',
|
||||
'pending',
|
||||
'running',
|
||||
'waiting-for-confirmation',
|
||||
'waiting-for-user-input',
|
||||
]);
|
||||
|
||||
export const supervisorAutonomousPlayableSafePhases = new Set([
|
||||
'action',
|
||||
'budget-exhausted',
|
||||
'cancelled',
|
||||
'cancelling',
|
||||
'completion-contract-failed',
|
||||
'completed',
|
||||
'conversation-write-failed',
|
||||
'executing',
|
||||
'failed',
|
||||
'finalizing',
|
||||
'idle',
|
||||
'needs-reconciliation',
|
||||
'observation',
|
||||
'parent-terminal',
|
||||
'paused',
|
||||
'pausing',
|
||||
'planning',
|
||||
'provider-action-batch',
|
||||
'queued',
|
||||
'response',
|
||||
'running',
|
||||
'waiting-for-agent',
|
||||
'waiting-for-confirmation',
|
||||
'waiting-for-delegate-receipts',
|
||||
'waiting-for-isolated-join',
|
||||
'waiting-for-process-session',
|
||||
'waiting-for-provider-retry',
|
||||
'waiting-for-user-input',
|
||||
]);
|
||||
|
||||
export const supervisorAutonomousPlayableSafeDeliveryStatuses = new Set([
|
||||
'claimed-by-parent',
|
||||
'dispatched',
|
||||
'ready',
|
||||
'suppressed',
|
||||
]);
|
||||
|
||||
export const supervisorAutonomousPlayableSafeTerminalStatuses = new Set([
|
||||
'budget-exhausted',
|
||||
'cancelled',
|
||||
'completed',
|
||||
'failed',
|
||||
]);
|
||||
|
||||
export const supervisorAutonomousPlayableSafeTurnOutcomes = new Set([
|
||||
'failed',
|
||||
'incomplete',
|
||||
'needs-reconciliation',
|
||||
'settled',
|
||||
]);
|
||||
|
||||
export const laneDefensePlaytestRequiredAssertions = [
|
||||
'state-surface-valid',
|
||||
'level-positive',
|
||||
'start-control-clicked',
|
||||
'start-sequence-advanced',
|
||||
'start-phase-playing',
|
||||
'defender-option-control-clicked',
|
||||
'defender-selection-sequence-advanced',
|
||||
'defender-selection-recorded',
|
||||
'lane-cell-control-clicked',
|
||||
'defender-placement-sequence-advanced',
|
||||
'defender-count-increased',
|
||||
'enemies-present-after-placement',
|
||||
'speed-up-control-clicked',
|
||||
'battle-sequence-advanced',
|
||||
'battle-sequence-monotonic',
|
||||
'enemy-position-changed',
|
||||
'enemy-health-decreased',
|
||||
'phase-won',
|
||||
'next-level-control-clicked',
|
||||
'next-level-sequence-advanced',
|
||||
'level-increased',
|
||||
'restart-control-clicked',
|
||||
'restart-sequence-advanced',
|
||||
'restart-phase-ready-or-playing',
|
||||
];
|
||||
|
||||
export const supervisorSwarmAutonomousRoutingTerms = [
|
||||
supervisorSwarmDesignAgentId,
|
||||
supervisorSwarmQualityAgentId,
|
||||
'agent.delegate',
|
||||
'agent.run_status',
|
||||
'同一个 planning 轮次',
|
||||
'同轮',
|
||||
'并行',
|
||||
'返工',
|
||||
'repair',
|
||||
'一次',
|
||||
'两个 Agent',
|
||||
'两个专业',
|
||||
'Runner',
|
||||
'pidfd',
|
||||
'actionId',
|
||||
'delegationId',
|
||||
];
|
||||
|
||||
export const supervisorSwarmCollaborationPolicyControlTerms = [
|
||||
'.agent/collaboration-policy.json',
|
||||
supervisorCollaborationPolicySchemaVersion,
|
||||
'requiredInitialWave',
|
||||
'minStaticDelegates',
|
||||
'requiredStaticAgentIds',
|
||||
'minIsolatedChildren',
|
||||
'minIsolatedGroupsBeforeClaim',
|
||||
'orchestratorOnlyAfterDelegation',
|
||||
'policyFingerprint',
|
||||
'contractFingerprint',
|
||||
];
|
||||
|
||||
export const supervisorSwarmIsolatedReviews = [
|
||||
{
|
||||
path: 'e2e/isolated-a/evidence.txt',
|
||||
@@ -625,17 +427,6 @@ export const supervisorSwarmIsolatedReviews = [
|
||||
},
|
||||
];
|
||||
|
||||
export const supervisorSwarmIsolatedReviewGroups = [
|
||||
supervisorSwarmIsolatedReviews.slice(0, 2),
|
||||
supervisorSwarmIsolatedReviews.slice(2),
|
||||
];
|
||||
|
||||
export const supervisorSwarmInitialIsolatedReviews =
|
||||
supervisorSwarmIsolatedReviewGroups[0];
|
||||
|
||||
export const supervisorSwarmFollowupIsolatedReviews =
|
||||
supervisorSwarmIsolatedReviewGroups[1];
|
||||
|
||||
export const webSearchBaselineApiUrl =
|
||||
'https://api.github.com/repos/nodejs/node/releases/latest';
|
||||
|
||||
@@ -703,8 +494,6 @@ export const pollIntervalMs = 750;
|
||||
|
||||
export const runTimeoutMs = 30 * 60 * 1000;
|
||||
|
||||
export const supervisorAutonomousPlayableRunTimeoutMs = 60 * 60 * 1000;
|
||||
|
||||
export const supervisorSwarmTerminalSidecarCleanupTimeoutMs = 10_000;
|
||||
|
||||
export const processRunnerKillStartTimeoutMs = 5 * 60 * 1000;
|
||||
@@ -938,10 +727,6 @@ export class BlockedError extends Error {
|
||||
|
||||
export const isolatedRunnerState = {
|
||||
appDataDir: null,
|
||||
platformSessionFixturePath: null,
|
||||
platformSessionFixtureSourcePath: null,
|
||||
platformSessionFixtureSha256: null,
|
||||
platformSessionFixturePreviousEnv: undefined,
|
||||
ownerToken: null,
|
||||
createdAt: 0,
|
||||
current: null,
|
||||
@@ -968,9 +753,6 @@ export const state = {
|
||||
linuxPidfdPythonPath: null,
|
||||
windowsProcessHandlePowerShellPath: null,
|
||||
cleanupInProgress: false,
|
||||
userInputCliSession: null,
|
||||
supervisorSwarmCliSession: null,
|
||||
supervisorAutonomousPlayableCliSession: null,
|
||||
status: 'FAIL',
|
||||
suite: null,
|
||||
options: null,
|
||||
@@ -1082,23 +864,6 @@ export const state = {
|
||||
finalReplyFingerprint: null,
|
||||
reportLeakCount: 0,
|
||||
},
|
||||
userInput: {
|
||||
requestId: null,
|
||||
responseId: null,
|
||||
actionId: null,
|
||||
questionMessageId: null,
|
||||
answerMessageId: null,
|
||||
questionCount: 0,
|
||||
optionCount: 0,
|
||||
providerStartedBeforeKill: 0,
|
||||
providerStartedAfterRestart: 0,
|
||||
conversationCountBeforeKill: 0,
|
||||
conversationCountAfterRestart: 0,
|
||||
oldRunnerBootId: null,
|
||||
newRunnerBootId: null,
|
||||
privateValues: [],
|
||||
reportLeakCount: 0,
|
||||
},
|
||||
scopedAgents: {
|
||||
effectiveModel: null,
|
||||
effectiveApiKind: null,
|
||||
@@ -1120,20 +885,6 @@ export const state = {
|
||||
privateValues: [],
|
||||
reportLeakCount: 0,
|
||||
},
|
||||
supervisorAutonomousPlayable: {
|
||||
freshInitBaselineUsed: false,
|
||||
initialGameIndexSha256: null,
|
||||
expectedProviderBinding: null,
|
||||
effectiveProviderBinding: null,
|
||||
stdinWriteCount: 0,
|
||||
stdinEnded: false,
|
||||
stdinBytes: 0,
|
||||
turnReport: null,
|
||||
cliOutput: '',
|
||||
privateValues: [],
|
||||
reportLeakCount: 0,
|
||||
ownedProcessCleanupSnapshot: null,
|
||||
},
|
||||
supervisorSwarm: {
|
||||
effectiveModel: null,
|
||||
effectiveApiKind: null,
|
||||
@@ -1215,47 +966,7 @@ export const state = {
|
||||
toolPlanHandoffLifecycleClosedExactlyOnce: false,
|
||||
toolPlanHandoffAuditIdempotent: false,
|
||||
toolPlanHandoffRecoveredPlanFingerprintMatched: false,
|
||||
autonomousTaskRecipeFree: false,
|
||||
autonomousRepositoryRecipeFree: false,
|
||||
interactiveCliUsed: false,
|
||||
chatSessionUnexpectedlyClosed: false,
|
||||
chatSessionFailureKind: 'none',
|
||||
chatSessionExitCode: 'none',
|
||||
chatSessionCloseSignal: 'none',
|
||||
chatSessionProcessErrorCode: 'none',
|
||||
chatSessionStderrChars: 0,
|
||||
chatSessionStderrSha256: 'none',
|
||||
turnReport: null,
|
||||
mixedSpawnActionId: null,
|
||||
mixedSpawnRequestHash: null,
|
||||
mixedFollowupSpawnActionId: null,
|
||||
mixedFollowupSpawnRequestHash: null,
|
||||
staticIsolatedProviderRequestIds: [],
|
||||
staticIsolatedProviderOverlapObserved: false,
|
||||
preKillMixedIdentity: null,
|
||||
collaborationPolicyWritten: false,
|
||||
collaborationPolicySnapshotInitialRecord: null,
|
||||
collaborationPolicySnapshotInitialBytes: null,
|
||||
collaborationPolicySnapshotInitialBytesSha256: null,
|
||||
collaborationPolicySnapshotInitialIdentityHash: null,
|
||||
collaborationPolicySnapshotBindingInitialRecord: null,
|
||||
collaborationPolicySnapshotBindingInitialBytes: null,
|
||||
collaborationPolicySnapshotBindingInitialBytesSha256: null,
|
||||
chatSessionFailureDiagnostic: null,
|
||||
collaborationPolicyDriftFixtureWritten: false,
|
||||
collaborationPolicyDriftFixturePolicyFingerprint: null,
|
||||
collaborationPolicyDriftedMinIsolatedGroupsBeforeClaim: null,
|
||||
initialBatchRecoveryBoundaryObserved: false,
|
||||
initialBatchRecoveryOldRunnerBootId: null,
|
||||
initialBatchRecoveryNewRunnerBootId: null,
|
||||
initialBatchRecoveryPreKillIdentity: null,
|
||||
initialBatchRecoveryPostRecoveryIdentity: null,
|
||||
initialBatchRecoveryPreKillSideEffects: null,
|
||||
initialBatchRecoveryPostRecoverySideEffects: null,
|
||||
initialBatchRecoveryIdentityStable: false,
|
||||
initialBatchRecoveryConfirmationRestoredCount: 0,
|
||||
initialBatchRecoveryPidfdClaimCount: 0,
|
||||
initialBatchRecoveryPidfdSignalCount: 0,
|
||||
},
|
||||
confirmedActionIds: new Set(),
|
||||
cleanupPerformed: false,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
-1890
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,3 @@
|
||||
export * from './supervisor-swarm/chat-session.mjs';
|
||||
export * from './supervisor-swarm/collaboration-assertions.mjs';
|
||||
export * from './supervisor-swarm/collaboration-policy.mjs';
|
||||
export * from './supervisor-swarm/evidence-schema.mjs';
|
||||
|
||||
-108
@@ -1,108 +0,0 @@
|
||||
import {
|
||||
assert,
|
||||
codedError,
|
||||
commandPassedMarker,
|
||||
findPendingActions,
|
||||
readRuntime,
|
||||
safeProcessFailureDiagnostic,
|
||||
state,
|
||||
supervisorSwarmConfirmedTools,
|
||||
supervisorSwarmDesignContent,
|
||||
supervisorSwarmDesignPath,
|
||||
supervisorSwarmQualityContent,
|
||||
supervisorSwarmQualityPath,
|
||||
supervisorSwarmWeakQualityContent,
|
||||
visibleText,
|
||||
waitForInteractiveCliOutput,
|
||||
writeInteractiveCliLine,
|
||||
} from './shared.mjs';
|
||||
|
||||
export function supervisorSwarmVerificationFixtureSource() {
|
||||
return `import fs from 'node:fs';\nconst html = fs.readFileSync('game/index.html', 'utf8');\nconst agents = fs.readFileSync('AGENTS.md', 'utf8');\nconst optionalExact = (file, expected) => !fs.existsSync(file) || fs.readFileSync(file, 'utf8') === expected;\nconst passed = html.includes(${JSON.stringify(visibleText)}) && html.includes('<canvas') && html.includes('requestAnimationFrame') && agents.includes('SUPERVISOR_SWARM_REPOSITORY_CONTEXT') && optionalExact(${JSON.stringify(supervisorSwarmDesignPath)}, ${JSON.stringify(supervisorSwarmDesignContent)}) && [${JSON.stringify(supervisorSwarmWeakQualityContent)}, ${JSON.stringify(supervisorSwarmQualityContent)}].includes(fs.readFileSync(${JSON.stringify(supervisorSwarmQualityPath)}, 'utf8'));\nif (!passed) process.exit(1);\nconsole.log(${JSON.stringify(commandPassedMarker)});\n`;
|
||||
}
|
||||
|
||||
export function recordSupervisorSwarmChatSessionFailureDiagnostic(session) {
|
||||
const diagnostic = session
|
||||
? safeProcessFailureDiagnostic({
|
||||
...session.closeInfo,
|
||||
stdout: session.stdout,
|
||||
stderr: session.stderr,
|
||||
})
|
||||
: {
|
||||
failureKind: 'missing',
|
||||
exitCode: 'none',
|
||||
signal: 'none',
|
||||
processErrorCode: 'none',
|
||||
stderrChars: 0,
|
||||
stderrSha256: 'none',
|
||||
};
|
||||
state.supervisorSwarm.chatSessionFailureDiagnostic = diagnostic;
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
export function supervisorSwarmChatSessionFailureEvidence() {
|
||||
const diagnostic = state.supervisorSwarm.chatSessionFailureDiagnostic;
|
||||
return {
|
||||
chatSessionUnexpectedlyClosed: Boolean(diagnostic),
|
||||
chatSessionFailureKind: diagnostic?.failureKind ?? 'none',
|
||||
chatSessionExitCode: diagnostic?.exitCode ?? 'none',
|
||||
chatSessionCloseSignal: diagnostic?.signal ?? 'none',
|
||||
chatSessionProcessErrorCode: diagnostic?.processErrorCode ?? 'none',
|
||||
chatSessionStderrChars: diagnostic?.stderrChars ?? 0,
|
||||
chatSessionStderrSha256: diagnostic?.stderrSha256 ?? 'none',
|
||||
};
|
||||
}
|
||||
|
||||
export function requireOpenSupervisorSwarmChatSession() {
|
||||
const session = state.supervisorSwarmCliSession;
|
||||
if (session && !session.closed) return session;
|
||||
recordSupervisorSwarmChatSessionFailureDiagnostic(session);
|
||||
throw codedError('supervisor-swarm-chat-session-missing');
|
||||
}
|
||||
|
||||
export async function confirmSupervisorSwarmPendingActionsInChat(
|
||||
allowedTools,
|
||||
shouldConfirm,
|
||||
) {
|
||||
const session = requireOpenSupervisorSwarmChatSession();
|
||||
const allowed = new Set(
|
||||
allowedTools ? [...allowedTools] : supervisorSwarmConfirmedTools,
|
||||
);
|
||||
const candidates = (await findPendingActions()).filter((pending) => {
|
||||
if (state.confirmedActionIds.has(pending.actionId)) return false;
|
||||
if (!shouldConfirm(pending)) return false;
|
||||
assert(
|
||||
allowed.has(pending.tool),
|
||||
`pending-tool-not-allowed-in-scenario:${pending.tool}`,
|
||||
);
|
||||
return true;
|
||||
});
|
||||
if (candidates.length === 0) return;
|
||||
const output = await waitForInteractiveCliOutput(
|
||||
session,
|
||||
(value) =>
|
||||
candidates.some((pending) =>
|
||||
value.includes(
|
||||
`[待确认] agent=${pending.agentId} run=${pending.runId} action=${pending.actionId} tool=${pending.tool}`,
|
||||
),
|
||||
),
|
||||
'supervisor-swarm-chat-confirmation-prompt-timeout',
|
||||
120_000,
|
||||
);
|
||||
const pending = candidates.find((candidate) =>
|
||||
output.includes(
|
||||
`[待确认] agent=${candidate.agentId} run=${candidate.runId} action=${candidate.actionId} tool=${candidate.tool}`,
|
||||
),
|
||||
);
|
||||
assert(pending, 'supervisor-swarm-chat-prompted-action-missing');
|
||||
const runtime = await readRuntime(pending.agentId);
|
||||
assert(runtime.runId === pending.runId, 'pending-run-mismatch');
|
||||
writeInteractiveCliLine(session, 'approve');
|
||||
await waitForInteractiveCliOutput(
|
||||
session,
|
||||
(output) => output.includes(`[已批准] ${pending.actionId}`),
|
||||
'supervisor-swarm-chat-confirmation-result-timeout',
|
||||
120_000,
|
||||
);
|
||||
state.confirmedActionIds.add(pending.actionId);
|
||||
}
|
||||
-606
File diff suppressed because it is too large
Load Diff
+29
-652
File diff suppressed because it is too large
Load Diff
-63
@@ -8,30 +8,6 @@ export function supervisorSwarmEvidenceFieldTemplate() {
|
||||
return {
|
||||
scenario: 'project-supervisor-dual-delegate-single-repair-runner-recovery',
|
||||
targetAgentId: projectSupervisorAgentId,
|
||||
autonomousModeEnabled: false,
|
||||
autonomousTaskRecipeFree: false,
|
||||
autonomousRepositoryRecipeFree: false,
|
||||
interactiveCliUsed: false,
|
||||
chatSessionUnexpectedlyClosed: false,
|
||||
chatSessionFailureKind: 'none',
|
||||
chatSessionExitCode: 'none',
|
||||
chatSessionCloseSignal: 'none',
|
||||
chatSessionProcessErrorCode: 'none',
|
||||
chatSessionStderrChars: 0,
|
||||
chatSessionStderrSha256: 'none',
|
||||
turnReportCaptured: false,
|
||||
turnReportOutcome: 'not-requested',
|
||||
turnReportParentIdentityStable: false,
|
||||
turnReportRuntimeCount: 0,
|
||||
turnReportBusyRuntimeCount: 0,
|
||||
turnReportPendingTaskCount: 0,
|
||||
turnReportRunningTaskCount: 0,
|
||||
turnReportWaitingForConfirmationCount: 0,
|
||||
turnReportWaitingForUserInputCount: 0,
|
||||
turnReportNewAssistantMessageCount: 0,
|
||||
turnReportFinalReplyChars: 0,
|
||||
turnReportReconciliationAgentCount: 0,
|
||||
turnReportPrivateLeakCount: 0,
|
||||
providerModel: null,
|
||||
providerApiKind: null,
|
||||
providerReasoningEffort: null,
|
||||
@@ -124,46 +100,7 @@ export function supervisorSwarmEvidenceFieldTemplate() {
|
||||
duplicateCollaborationPolicySnapshotBindingCount: 0,
|
||||
collaborationPolicySnapshotResidualArtifactCount: 0,
|
||||
collaborationPolicySnapshotBindingResidualArtifactCount: 0,
|
||||
initialBatchRecoveryRequired: false,
|
||||
initialBatchRecoveryBoundaryObserved: false,
|
||||
initialBatchRecoveryBatchIdStable: false,
|
||||
initialBatchRecoveryPolicyFingerprintStable: false,
|
||||
initialBatchRecoveryContractFingerprintStable: false,
|
||||
initialBatchRecoveryAllActionIdsStable: false,
|
||||
initialBatchRecoveryProviderStartedIdentitySetStable: false,
|
||||
initialBatchRecoveryWaitingConfirmationStable: false,
|
||||
initialBatchRecoveryZeroSideEffects: false,
|
||||
initialBatchRecoveryPreKillDeliveryCount: 0,
|
||||
initialBatchRecoveryPostRecoveryDeliveryCount: 0,
|
||||
initialBatchRecoveryPreKillGroupCount: 0,
|
||||
initialBatchRecoveryPostRecoveryGroupCount: 0,
|
||||
initialBatchRecoveryPreKillChildCount: 0,
|
||||
initialBatchRecoveryPostRecoveryChildCount: 0,
|
||||
initialBatchRecoveryPreKillProjectRevision: 0,
|
||||
initialBatchRecoveryPostRecoveryProjectRevision: 0,
|
||||
initialBatchRecoveryPreKillProjectModificationCount: 0,
|
||||
initialBatchRecoveryPostRecoveryProjectModificationCount: 0,
|
||||
initialBatchRecoveryPreKillActionExecutionCount: 0,
|
||||
initialBatchRecoveryPostRecoveryActionExecutionCount: 0,
|
||||
initialBatchRecoveryPreKillActionReceiptCount: 0,
|
||||
initialBatchRecoveryPostRecoveryActionReceiptCount: 0,
|
||||
initialBatchRecoveryConfirmationRestoredCount: 0,
|
||||
initialBatchRecoveryRunnerBootChanged: false,
|
||||
initialBatchRecoveryPidfdClaimCount: 0,
|
||||
initialBatchRecoveryPidfdSignalCount: 0,
|
||||
nativeDualDelegatePlanCount: 0,
|
||||
mixedModeEnabled: false,
|
||||
mixedSpawnActionCaptured: false,
|
||||
mixedFollowupSpawnActionCaptured: false,
|
||||
mixedSpawnRequestHashStable: false,
|
||||
mixedSpawnConfirmationRequiredCount: 0,
|
||||
mixedSpawnApprovalCount: 0,
|
||||
mixedSpawnConfirmationOrderValid: false,
|
||||
nativeMixedCollaborationPlanCount: 0,
|
||||
nativeFollowupIsolatedPlanCount: 0,
|
||||
staticIsolatedProviderOverlapObserved: false,
|
||||
staticIsolatedProviderRequestIdentityCount: 0,
|
||||
mixedParentIdentityStable: false,
|
||||
initialDeliveryCount: 0,
|
||||
repairDeliveryCount: 0,
|
||||
repairDeliveryStatus: 'absent',
|
||||
|
||||
+57
-548
File diff suppressed because it is too large
Load Diff
+13
-127
@@ -1,12 +1,4 @@
|
||||
import {
|
||||
recordSupervisorSwarmChatSessionFailureDiagnostic,
|
||||
requireOpenSupervisorSwarmChatSession,
|
||||
} from './chat-session.mjs';
|
||||
import {
|
||||
captureSupervisorSwarmCollaborationPolicySnapshotAndDrift,
|
||||
captureSupervisorSwarmInitialProviderBatch,
|
||||
restartSupervisorSwarmRunnerAtInitialBatchBoundary,
|
||||
} from './collaboration-policy.mjs';
|
||||
import { captureSupervisorSwarmInitialProviderBatch } from './collaboration-policy.mjs';
|
||||
import { validateSupervisorSwarmEvidence } from './evidence-validation.mjs';
|
||||
import {
|
||||
driveSupervisorSwarmRuntimeToCompletion,
|
||||
@@ -21,28 +13,19 @@ import {
|
||||
import {
|
||||
assert,
|
||||
claimOwnedRunner,
|
||||
codedError,
|
||||
ensureOwnedRunnerStableKillSupport,
|
||||
hashValue,
|
||||
isNonEmptyString,
|
||||
isPlainObject,
|
||||
prepareCliBinary,
|
||||
projectSupervisorAgentId,
|
||||
readRuntime,
|
||||
requestedRunId,
|
||||
runCli,
|
||||
sleep,
|
||||
startInteractiveCli,
|
||||
state,
|
||||
supervisorSwarmSessionId,
|
||||
waitForInteractiveCliExit,
|
||||
waitForInteractiveCliOutput,
|
||||
writeInteractiveCliLine,
|
||||
} from './shared.mjs';
|
||||
import {
|
||||
isSupervisorSwarmFinalReplyTransientRetrySuite,
|
||||
isSupervisorSwarmInitialTransientRetrySuite,
|
||||
isSupervisorSwarmInteractiveChatSuite,
|
||||
isSupervisorSwarmToolPlanHandoffRunnerKillSuite,
|
||||
} from './suite-selection.mjs';
|
||||
import { captureSupervisorSwarmToolPlanHandoffRunnerKillCheckpoint } from './tool-plan-handoff.mjs';
|
||||
@@ -62,42 +45,20 @@ export async function runSupervisorSwarmE2e() {
|
||||
chars: [...task].length,
|
||||
sha256: hashValue(task),
|
||||
};
|
||||
if (isSupervisorSwarmInteractiveChatSuite()) {
|
||||
state.isolatedRunner.launchAttempted = true;
|
||||
state.supervisorSwarmCliSession = startInteractiveCli([
|
||||
'--swarm-chat',
|
||||
state.initialRunId = requestedRunId;
|
||||
state.initialSessionId = supervisorSwarmSessionId;
|
||||
await runCli(
|
||||
[
|
||||
'--agent-enqueue',
|
||||
'--init',
|
||||
state.projectRoot,
|
||||
]);
|
||||
await waitForInteractiveCliOutput(
|
||||
state.supervisorSwarmCliSession,
|
||||
(output) =>
|
||||
output.includes('Agent Swarm Chat') &&
|
||||
output.includes(`父 Agent:${projectSupervisorAgentId}`),
|
||||
'supervisor-swarm-autonomous-chat-banner-timeout',
|
||||
30_000,
|
||||
);
|
||||
state.supervisorSwarm.interactiveCliUsed = true;
|
||||
writeInteractiveCliLine(state.supervisorSwarmCliSession, task);
|
||||
const started = await waitForSupervisorSwarmAutonomousParentRuntime(task);
|
||||
state.initialRunId = started.runId;
|
||||
state.initialSessionId = started.sessionId;
|
||||
} else {
|
||||
state.initialRunId = requestedRunId;
|
||||
state.initialSessionId = supervisorSwarmSessionId;
|
||||
await runCli(
|
||||
[
|
||||
'--agent-enqueue',
|
||||
'--init',
|
||||
state.projectRoot,
|
||||
projectSupervisorAgentId,
|
||||
state.initialRunId,
|
||||
task,
|
||||
],
|
||||
{ timeoutMs: 120_000 },
|
||||
);
|
||||
state.isolatedRunner.launchAttempted = true;
|
||||
}
|
||||
projectSupervisorAgentId,
|
||||
state.initialRunId,
|
||||
task,
|
||||
],
|
||||
{ timeoutMs: 120_000 },
|
||||
);
|
||||
state.isolatedRunner.launchAttempted = true;
|
||||
await claimOwnedRunner();
|
||||
const runtime = await readRuntime(projectSupervisorAgentId);
|
||||
assert(
|
||||
@@ -114,86 +75,11 @@ export async function runSupervisorSwarmE2e() {
|
||||
await captureSupervisorSwarmToolPlanHandoffRunnerKillCheckpoint();
|
||||
}
|
||||
await captureSupervisorSwarmInitialProviderBatch();
|
||||
await captureSupervisorSwarmCollaborationPolicySnapshotAndDrift();
|
||||
await restartSupervisorSwarmRunnerAtInitialBatchBoundary();
|
||||
await driveSupervisorSwarmToRepairKillBoundary();
|
||||
if (isSupervisorSwarmFinalReplyTransientRetrySuite()) {
|
||||
await captureSupervisorSwarmTransientRetryCheckpoint();
|
||||
}
|
||||
await driveSupervisorSwarmRuntimeToCompletion();
|
||||
if (isSupervisorSwarmInteractiveChatSuite()) {
|
||||
await captureSupervisorSwarmAutonomousTurnReport();
|
||||
writeInteractiveCliLine(state.supervisorSwarmCliSession, '/quit');
|
||||
await waitForInteractiveCliExit(state.supervisorSwarmCliSession, 30_000);
|
||||
state.supervisorSwarmCliSession = null;
|
||||
}
|
||||
state.evidence = await validateSupervisorSwarmEvidence();
|
||||
assert(state.evidence.secretLeakCount === 0, 'loaded-key-leak-detected');
|
||||
}
|
||||
|
||||
export async function waitForSupervisorSwarmAutonomousParentRuntime(task) {
|
||||
const deadline = Date.now() + 120_000;
|
||||
while (Date.now() < deadline) {
|
||||
const runtime = await readRuntime(projectSupervisorAgentId).catch(
|
||||
() => null,
|
||||
);
|
||||
if (
|
||||
runtime?.agentId === projectSupervisorAgentId &&
|
||||
runtime.sessionId === supervisorSwarmSessionId &&
|
||||
isNonEmptyString(runtime.runId) &&
|
||||
runtime.currentTask === task
|
||||
) {
|
||||
return runtime;
|
||||
}
|
||||
if (state.supervisorSwarmCliSession?.closed) {
|
||||
recordSupervisorSwarmChatSessionFailureDiagnostic(
|
||||
state.supervisorSwarmCliSession,
|
||||
);
|
||||
throw codedError('supervisor-swarm-autonomous-chat-closed-before-run');
|
||||
}
|
||||
await sleep(50);
|
||||
}
|
||||
throw codedError('supervisor-swarm-autonomous-parent-runtime-timeout');
|
||||
}
|
||||
|
||||
export async function captureSupervisorSwarmAutonomousTurnReport() {
|
||||
const session = requireOpenSupervisorSwarmChatSession();
|
||||
const output = await waitForInteractiveCliOutput(
|
||||
session,
|
||||
(value) => value.includes('[turn.report] '),
|
||||
'supervisor-swarm-autonomous-turn-report-timeout',
|
||||
60_000,
|
||||
);
|
||||
const reportLines = output
|
||||
.split(/\r?\n/u)
|
||||
.filter((line) => line.startsWith('[turn.report] '));
|
||||
assert(
|
||||
reportLines.length === 1,
|
||||
'supervisor-swarm-autonomous-turn-report-count-invalid',
|
||||
);
|
||||
const report = JSON.parse(reportLines[0].slice('[turn.report] '.length));
|
||||
assert(
|
||||
isPlainObject(report) &&
|
||||
JSON.stringify(Object.keys(report).sort()) ===
|
||||
JSON.stringify(
|
||||
[
|
||||
'schemaVersion',
|
||||
'outcome',
|
||||
'parentAgentId',
|
||||
'sessionId',
|
||||
'parentRunId',
|
||||
'runtimeCount',
|
||||
'busyRuntimeCount',
|
||||
'pendingTaskCount',
|
||||
'runningTaskCount',
|
||||
'waitingForConfirmationCount',
|
||||
'waitingForUserInputCount',
|
||||
'newAssistantMessageCount',
|
||||
'finalReplyChars',
|
||||
'reconciliationAgentCount',
|
||||
].sort(),
|
||||
),
|
||||
'supervisor-swarm-autonomous-turn-report-shape-invalid',
|
||||
);
|
||||
state.supervisorSwarm.turnReport = report;
|
||||
}
|
||||
|
||||
+4
-411
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
supervisorSwarmMixedSpawnRequestHashesStable,
|
||||
supervisorSwarmParentDeliveries,
|
||||
supervisorSwarmRelevantRunKeys,
|
||||
} from './collaboration-assertions.mjs';
|
||||
@@ -8,7 +7,6 @@ import {
|
||||
duplicateSupervisorSwarmCollaborationPolicySnapshotCount,
|
||||
inspectSupervisorSwarmCollaborationPolicySnapshot,
|
||||
inspectSupervisorSwarmCollaborationPolicySnapshotBinding,
|
||||
readSupervisorSwarmCollaborationPolicySnapshotBindingFile,
|
||||
readSupervisorSwarmCollaborationPolicySnapshotFile,
|
||||
supervisorSwarmCollaborationPolicyDriftObservationCount,
|
||||
supervisorSwarmExpectedCollaborationPolicySnapshot,
|
||||
@@ -59,17 +57,9 @@ import {
|
||||
supervisorSwarmQualityAgentId,
|
||||
supervisorSwarmQualityContent,
|
||||
supervisorSwarmQualityPath,
|
||||
supervisorSwarmSessionId,
|
||||
supervisorSwarmWeakQualityContent,
|
||||
} from './shared.mjs';
|
||||
import {
|
||||
isSupervisorSwarmCollaborationPolicyMixedRecoverySuite,
|
||||
isSupervisorSwarmFinalReplyTransientRetrySuite,
|
||||
isSupervisorSwarmMixedHarnessSuite,
|
||||
isSupervisorSwarmMultiIsolatedHarnessSuite,
|
||||
supervisorSwarmExpectedIsolatedReviewGroups,
|
||||
supervisorSwarmInitialIsolatedReviewsForSuite,
|
||||
} from './suite-selection.mjs';
|
||||
import { isSupervisorSwarmFinalReplyTransientRetrySuite } from './suite-selection.mjs';
|
||||
|
||||
export async function collectPartialSupervisorSwarmEvidence(baseEvidence) {
|
||||
const persistence = await readSupervisorSwarmPersistence({
|
||||
@@ -125,127 +115,6 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) {
|
||||
record.recordType === 'agent.runtime.provider_request.lifecycle' &&
|
||||
relevantRuns.has(`${record.agentId}\0${record.runId}`),
|
||||
);
|
||||
const indexedAgentDb = persistence.agentDb.map((record, index) => ({
|
||||
record,
|
||||
index,
|
||||
}));
|
||||
const initialBatchActions =
|
||||
state.supervisorSwarm.initialProviderBatch?.actions ?? [];
|
||||
const mixedSpawnAction = initialBatchActions.find(
|
||||
(action) => action.actionId === state.supervisorSwarm.mixedSpawnActionId,
|
||||
);
|
||||
const matchesMixedInitialAction = (record, action) =>
|
||||
action != null &&
|
||||
record.agentId === projectSupervisorAgentId &&
|
||||
record.runId === state.initialRunId &&
|
||||
record.actionId === action.actionId &&
|
||||
record.actionFingerprint === action.actionFingerprint &&
|
||||
record.tool === action.tool;
|
||||
const mixedSpawnConfirmationRequirements = indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType ===
|
||||
'agent.runtime.provider_action_batch.confirmation_required' &&
|
||||
record.sessionId === supervisorSwarmSessionId &&
|
||||
record.batchId === state.supervisorSwarm.initialProviderBatch?.batchId &&
|
||||
matchesMixedInitialAction(record, mixedSpawnAction),
|
||||
);
|
||||
const mixedSpawnApprovals = indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.tool_confirmation.approved' &&
|
||||
record.sessionId === supervisorSwarmSessionId &&
|
||||
record.confirmedRunId === state.initialRunId &&
|
||||
matchesMixedInitialAction(record, mixedSpawnAction),
|
||||
);
|
||||
const mixedDelegateExecutionIndexes = initialBatchActions
|
||||
.filter((action) => action.tool === 'agent.delegate')
|
||||
.flatMap((action) =>
|
||||
indexedAgentDb
|
||||
.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.tool_action.executing' &&
|
||||
matchesMixedInitialAction(record, action),
|
||||
)
|
||||
.map(({ index }) => index),
|
||||
);
|
||||
const mixedSpawnReceiptIndexes = indexedAgentDb
|
||||
.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.action_receipt' &&
|
||||
record.sessionId === supervisorSwarmSessionId &&
|
||||
record.status === 'ok' &&
|
||||
matchesMixedInitialAction(record, mixedSpawnAction),
|
||||
)
|
||||
.map(({ index }) => index);
|
||||
const mixedInitialSpawnConfirmationOrderValid =
|
||||
mixedSpawnConfirmationRequirements.length === 1 &&
|
||||
mixedSpawnApprovals.length === 1 &&
|
||||
mixedDelegateExecutionIndexes.length === 2 &&
|
||||
mixedSpawnReceiptIndexes.length === 1 &&
|
||||
mixedSpawnConfirmationRequirements[0].index <
|
||||
mixedSpawnApprovals[0].index &&
|
||||
mixedDelegateExecutionIndexes.every(
|
||||
(index) => mixedSpawnApprovals[0].index < index,
|
||||
) &&
|
||||
mixedSpawnApprovals[0].index < mixedSpawnReceiptIndexes[0];
|
||||
const matchesMixedFollowupIdentity = (record) =>
|
||||
isNonEmptyString(state.supervisorSwarm.mixedFollowupSpawnActionId) &&
|
||||
record.agentId === projectSupervisorAgentId &&
|
||||
(record.runId === state.initialRunId ||
|
||||
record.confirmedRunId === state.initialRunId) &&
|
||||
record.actionId === state.supervisorSwarm.mixedFollowupSpawnActionId;
|
||||
const matchesMixedFollowupAction = (record) =>
|
||||
matchesMixedFollowupIdentity(record) &&
|
||||
(record.tool === 'agent.spawn_isolated' ||
|
||||
record.commandId === 'agent.spawn_isolated');
|
||||
const mixedFollowupConfirmationRequirements = indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType ===
|
||||
'agent.runtime.provider_action_batch.confirmation_required' &&
|
||||
record.sessionId === supervisorSwarmSessionId &&
|
||||
matchesMixedFollowupAction(record),
|
||||
);
|
||||
const mixedFollowupApprovals = indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.tool_confirmation.approved' &&
|
||||
record.sessionId === supervisorSwarmSessionId &&
|
||||
matchesMixedFollowupAction(record),
|
||||
);
|
||||
const mixedFollowupSideEffects = indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.agent.spawn_isolated' &&
|
||||
matchesMixedFollowupIdentity(record),
|
||||
);
|
||||
const mixedFollowupReceiptIndexes = indexedAgentDb
|
||||
.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.action_receipt' &&
|
||||
record.status === 'ok' &&
|
||||
matchesMixedFollowupAction(record),
|
||||
)
|
||||
.map(({ index }) => index);
|
||||
const mixedFollowupObservationIndexes = indexedAgentDb
|
||||
.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.tool_observation' &&
|
||||
record.status === 'ok' &&
|
||||
matchesMixedFollowupAction(record),
|
||||
)
|
||||
.map(({ index }) => index);
|
||||
const mixedFollowupSpawnConfirmationOrderValid =
|
||||
mixedFollowupConfirmationRequirements.length === 1 &&
|
||||
mixedFollowupApprovals.length === 1 &&
|
||||
mixedFollowupSideEffects.length === 1 &&
|
||||
mixedFollowupReceiptIndexes.length === 1 &&
|
||||
mixedFollowupObservationIndexes.length === 1 &&
|
||||
mixedFollowupConfirmationRequirements[0].index <
|
||||
mixedFollowupApprovals[0].index &&
|
||||
mixedFollowupApprovals[0].index < mixedFollowupSideEffects[0].index &&
|
||||
mixedFollowupSideEffects[0].index < mixedFollowupReceiptIndexes[0] &&
|
||||
mixedFollowupReceiptIndexes[0] < mixedFollowupObservationIndexes[0];
|
||||
const mixedSpawnConfirmationOrderValid =
|
||||
mixedInitialSpawnConfirmationOrderValid &&
|
||||
(!isSupervisorSwarmMultiIsolatedHarnessSuite() ||
|
||||
mixedFollowupSpawnConfirmationOrderValid);
|
||||
const toleratePartialRead = async (
|
||||
surface,
|
||||
reader,
|
||||
@@ -278,7 +147,6 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) {
|
||||
isolatedEvidenceContents,
|
||||
changedFiles,
|
||||
collaborationPolicySnapshotFile,
|
||||
collaborationPolicySnapshotBindingFile,
|
||||
collaborationPolicySidecar,
|
||||
finalReplyResponseStream,
|
||||
] = await Promise.all([
|
||||
@@ -359,14 +227,6 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) {
|
||||
: null,
|
||||
null,
|
||||
),
|
||||
toleratePartialRead(
|
||||
'collaboration-policy-snapshot-binding',
|
||||
() =>
|
||||
supervisorSwarmInitialBatchBindsPolicySnapshot()
|
||||
? readSupervisorSwarmCollaborationPolicySnapshotBindingFile()
|
||||
: null,
|
||||
null,
|
||||
),
|
||||
toleratePartialRead(
|
||||
'collaboration-policy-sidecar',
|
||||
() => readJson(supervisorSwarmCollaborationPolicyPath()),
|
||||
@@ -469,63 +329,12 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) {
|
||||
partialCollaborationPolicySnapshots[0],
|
||||
supervisorSwarmExpectedCollaborationPolicySnapshot(),
|
||||
);
|
||||
const partialCollaborationPolicySnapshotBytesStable =
|
||||
isNonEmptyString(
|
||||
state.supervisorSwarm.collaborationPolicySnapshotInitialBytes,
|
||||
) &&
|
||||
collaborationPolicySnapshotFile?.bytes ===
|
||||
state.supervisorSwarm.collaborationPolicySnapshotInitialBytes &&
|
||||
hashValue(collaborationPolicySnapshotFile.bytes) ===
|
||||
state.supervisorSwarm.collaborationPolicySnapshotInitialBytesSha256;
|
||||
const partialCollaborationPolicySnapshotFieldsStable =
|
||||
isPlainObject(
|
||||
state.supervisorSwarm.collaborationPolicySnapshotInitialRecord,
|
||||
) &&
|
||||
isPlainObject(collaborationPolicySnapshotFile?.snapshot) &&
|
||||
JSON.stringify(collaborationPolicySnapshotFile.snapshot) ===
|
||||
JSON.stringify(
|
||||
state.supervisorSwarm.collaborationPolicySnapshotInitialRecord,
|
||||
) &&
|
||||
JSON.stringify(collaborationPolicySnapshotFile.snapshot) ===
|
||||
JSON.stringify(partialCollaborationPolicySnapshots[0]);
|
||||
const partialCollaborationPolicySnapshotFingerprintStable =
|
||||
partialCollaborationPolicySnapshotFieldsStable &&
|
||||
collaborationPolicySnapshotFile.snapshot.snapshotFingerprint ===
|
||||
state.supervisorSwarm.collaborationPolicySnapshotInitialRecord
|
||||
.snapshotFingerprint &&
|
||||
partialCollaborationPolicySnapshotInspection.identityHash ===
|
||||
state.supervisorSwarm.collaborationPolicySnapshotInitialIdentityHash;
|
||||
const partialCollaborationPolicySnapshotPolicyFingerprintStable =
|
||||
partialCollaborationPolicySnapshotFieldsStable &&
|
||||
collaborationPolicySnapshotFile.snapshot.policyFingerprint ===
|
||||
state.supervisorSwarm.collaborationPolicySnapshotInitialRecord
|
||||
.policyFingerprint;
|
||||
const partialCollaborationPolicySnapshotBindingInspection =
|
||||
inspectSupervisorSwarmCollaborationPolicySnapshotBinding(
|
||||
partialCollaborationPolicySnapshotBindings[0],
|
||||
collaborationPolicySnapshotFile?.snapshot ??
|
||||
partialCollaborationPolicySnapshots[0],
|
||||
);
|
||||
const partialCollaborationPolicySnapshotBindingBytesStable =
|
||||
isNonEmptyString(
|
||||
state.supervisorSwarm.collaborationPolicySnapshotBindingInitialBytes,
|
||||
) &&
|
||||
collaborationPolicySnapshotBindingFile?.bytes ===
|
||||
state.supervisorSwarm.collaborationPolicySnapshotBindingInitialBytes &&
|
||||
hashValue(collaborationPolicySnapshotBindingFile.bytes) ===
|
||||
state.supervisorSwarm
|
||||
.collaborationPolicySnapshotBindingInitialBytesSha256;
|
||||
const partialCollaborationPolicySnapshotBindingFieldsStable =
|
||||
isPlainObject(
|
||||
state.supervisorSwarm.collaborationPolicySnapshotBindingInitialRecord,
|
||||
) &&
|
||||
isPlainObject(collaborationPolicySnapshotBindingFile?.binding) &&
|
||||
JSON.stringify(collaborationPolicySnapshotBindingFile.binding) ===
|
||||
JSON.stringify(
|
||||
state.supervisorSwarm.collaborationPolicySnapshotBindingInitialRecord,
|
||||
) &&
|
||||
JSON.stringify(collaborationPolicySnapshotBindingFile.binding) ===
|
||||
JSON.stringify(partialCollaborationPolicySnapshotBindings[0]);
|
||||
const partialCollaborationPolicyDriftObservationCount =
|
||||
supervisorSwarmCollaborationPolicyDriftObservationCount(
|
||||
persistence.agentDb,
|
||||
@@ -538,62 +347,9 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) {
|
||||
duplicateSupervisorSwarmCollaborationPolicySnapshotBindingCount(
|
||||
partialCollaborationPolicySnapshotBindings,
|
||||
);
|
||||
const partialChatSessionFailureDiagnostic =
|
||||
state.supervisorSwarm.chatSessionFailureDiagnostic;
|
||||
const partialIdentityBefore =
|
||||
state.supervisorSwarm.initialBatchRecoveryPreKillIdentity;
|
||||
const partialIdentityAfter =
|
||||
state.supervisorSwarm.initialBatchRecoveryPostRecoveryIdentity;
|
||||
const partialSideEffectsBefore =
|
||||
state.supervisorSwarm.initialBatchRecoveryPreKillSideEffects;
|
||||
const partialSideEffectsAfter =
|
||||
state.supervisorSwarm.initialBatchRecoveryPostRecoverySideEffects;
|
||||
const partialRecoveryRequired =
|
||||
isSupervisorSwarmCollaborationPolicyMixedRecoverySuite();
|
||||
const zeroSideEffectFields = [
|
||||
'deliveryCount',
|
||||
'claimCount',
|
||||
'isolatedGroupCount',
|
||||
'isolatedChildCount',
|
||||
'isolatedResultCount',
|
||||
'isolatedJoinDeliveryCount',
|
||||
'delegatedChildTaskCount',
|
||||
'projectRevision',
|
||||
'projectModifiedPathCount',
|
||||
'projectMutationActionCount',
|
||||
'initialActionExecutionCount',
|
||||
'initialActionReceiptCount',
|
||||
'initialActionSideEffectCount',
|
||||
'recoveryFailedCount',
|
||||
];
|
||||
const partialRecoveryZeroSideEffects =
|
||||
partialRecoveryRequired &&
|
||||
partialSideEffectsBefore != null &&
|
||||
partialSideEffectsAfter != null &&
|
||||
zeroSideEffectFields.every(
|
||||
(field) =>
|
||||
partialSideEffectsBefore[field] === 0 &&
|
||||
partialSideEffectsAfter[field] === 0,
|
||||
);
|
||||
return buildSupervisorSwarmEvidence({
|
||||
...baseEvidence,
|
||||
scenario: partialRecoveryRequired
|
||||
? 'project-supervisor-collaboration-policy-mixed-initial-batch-recovery'
|
||||
: baseEvidence.scenario,
|
||||
interactiveCliUsed: state.supervisorSwarm.interactiveCliUsed,
|
||||
chatSessionUnexpectedlyClosed: Boolean(partialChatSessionFailureDiagnostic),
|
||||
chatSessionFailureKind:
|
||||
partialChatSessionFailureDiagnostic?.failureKind ?? 'none',
|
||||
chatSessionExitCode:
|
||||
partialChatSessionFailureDiagnostic?.exitCode ?? 'none',
|
||||
chatSessionCloseSignal:
|
||||
partialChatSessionFailureDiagnostic?.signal ?? 'none',
|
||||
chatSessionProcessErrorCode:
|
||||
partialChatSessionFailureDiagnostic?.processErrorCode ?? 'none',
|
||||
chatSessionStderrChars:
|
||||
partialChatSessionFailureDiagnostic?.stderrChars ?? 0,
|
||||
chatSessionStderrSha256:
|
||||
partialChatSessionFailureDiagnostic?.stderrSha256 ?? 'none',
|
||||
scenario: baseEvidence.scenario,
|
||||
providerModel: state.supervisorSwarm.effectiveModel,
|
||||
providerApiKind: state.supervisorSwarm.effectiveApiKind,
|
||||
providerReasoningEffort: state.supervisorSwarm.effectiveReasoningEffort,
|
||||
@@ -701,12 +457,8 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) {
|
||||
?.orchestratorOnlyAfterDelegation ?? false,
|
||||
initialCollaborationContractCountsMatched:
|
||||
partialCollaborationContract?.initialStaticAgentIds?.length === 2 &&
|
||||
partialCollaborationContract?.isolatedSpawnCount ===
|
||||
(isSupervisorSwarmMixedHarnessSuite() ? 1 : 0) &&
|
||||
partialCollaborationContract?.isolatedChildCount ===
|
||||
(isSupervisorSwarmMixedHarnessSuite()
|
||||
? supervisorSwarmInitialIsolatedReviewsForSuite().length
|
||||
: 0),
|
||||
partialCollaborationContract?.isolatedSpawnCount === 0 &&
|
||||
partialCollaborationContract?.isolatedChildCount === 0,
|
||||
initialCollaborationPolicyFingerprint:
|
||||
partialCollaborationContract?.policyFingerprint ?? null,
|
||||
initialCollaborationContractFingerprint:
|
||||
@@ -750,19 +502,6 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) {
|
||||
partialCollaborationPolicySnapshotInspection.identityHashMatched,
|
||||
collaborationPolicySnapshotBoundAtValid:
|
||||
partialCollaborationPolicySnapshotInspection.boundAtValid,
|
||||
collaborationPolicySnapshotBytesStable:
|
||||
partialCollaborationPolicySnapshotBytesStable,
|
||||
collaborationPolicySnapshotFieldsStable:
|
||||
partialCollaborationPolicySnapshotFieldsStable,
|
||||
collaborationPolicySnapshotFingerprintStable:
|
||||
partialCollaborationPolicySnapshotFingerprintStable,
|
||||
collaborationPolicySnapshotPolicyFingerprintStable:
|
||||
partialCollaborationPolicySnapshotPolicyFingerprintStable,
|
||||
collaborationPolicySnapshotStable:
|
||||
partialCollaborationPolicySnapshotBytesStable &&
|
||||
partialCollaborationPolicySnapshotFieldsStable &&
|
||||
partialCollaborationPolicySnapshotFingerprintStable &&
|
||||
partialCollaborationPolicySnapshotPolicyFingerprintStable,
|
||||
collaborationPolicySnapshotBindingCaptured:
|
||||
partialCollaborationPolicySnapshotRequired &&
|
||||
partialCollaborationPolicySnapshotBindingInspection.exactShape,
|
||||
@@ -781,16 +520,6 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) {
|
||||
partialCollaborationPolicySnapshotBindingInspection.boundAtMatched,
|
||||
collaborationPolicySnapshotBindingSnapshotMatched:
|
||||
partialCollaborationPolicySnapshotBindingInspection.snapshotMatched,
|
||||
collaborationPolicySnapshotBindingBytesStable:
|
||||
partialCollaborationPolicySnapshotBindingBytesStable,
|
||||
collaborationPolicySnapshotBindingFieldsStable:
|
||||
partialCollaborationPolicySnapshotBindingFieldsStable,
|
||||
collaborationPolicySnapshotBindingStable:
|
||||
partialCollaborationPolicySnapshotBindingBytesStable &&
|
||||
partialCollaborationPolicySnapshotBindingFieldsStable &&
|
||||
partialCollaborationPolicySnapshotBindingInspection.snapshotMatched,
|
||||
collaborationPolicyDriftFixtureWritten:
|
||||
state.supervisorSwarm.collaborationPolicyDriftFixtureWritten,
|
||||
collaborationPolicySidecarMinIsolatedGroupsBeforeClaim:
|
||||
collaborationPolicySidecar?.minIsolatedGroupsBeforeClaim ?? 0,
|
||||
collaborationPolicyDriftStatusObserved:
|
||||
@@ -805,142 +534,6 @@ export async function collectPartialSupervisorSwarmEvidence(baseEvidence) {
|
||||
residualSidecars.collaborationPolicySnapshotArtifacts,
|
||||
collaborationPolicySnapshotBindingResidualArtifactCount:
|
||||
residualSidecars.collaborationPolicySnapshotBindingArtifacts,
|
||||
initialBatchRecoveryRequired: partialRecoveryRequired,
|
||||
initialBatchRecoveryBoundaryObserved:
|
||||
state.supervisorSwarm.initialBatchRecoveryBoundaryObserved,
|
||||
initialBatchRecoveryBatchIdStable:
|
||||
partialRecoveryRequired &&
|
||||
partialIdentityBefore?.batchId === partialIdentityAfter?.batchId,
|
||||
initialBatchRecoveryPolicyFingerprintStable:
|
||||
partialRecoveryRequired &&
|
||||
partialIdentityBefore?.policyFingerprint ===
|
||||
partialIdentityAfter?.policyFingerprint,
|
||||
initialBatchRecoveryContractFingerprintStable:
|
||||
partialRecoveryRequired &&
|
||||
partialIdentityBefore?.contractFingerprint ===
|
||||
partialIdentityAfter?.contractFingerprint,
|
||||
initialBatchRecoveryAllActionIdsStable:
|
||||
partialRecoveryRequired &&
|
||||
JSON.stringify(
|
||||
partialIdentityBefore?.actions?.map((action) => action.actionId),
|
||||
) ===
|
||||
JSON.stringify(
|
||||
partialIdentityAfter?.actions?.map((action) => action.actionId),
|
||||
),
|
||||
initialBatchRecoveryProviderStartedIdentitySetStable:
|
||||
partialRecoveryRequired &&
|
||||
JSON.stringify(partialSideEffectsBefore?.providerStartedIdentities) ===
|
||||
JSON.stringify(partialSideEffectsAfter?.providerStartedIdentities),
|
||||
initialBatchRecoveryWaitingConfirmationStable:
|
||||
partialRecoveryRequired &&
|
||||
partialIdentityBefore?.status === 'waiting-confirmation' &&
|
||||
partialIdentityBefore?.nextActionIndex === 0 &&
|
||||
partialIdentityAfter?.status === 'waiting-confirmation' &&
|
||||
partialIdentityAfter?.nextActionIndex === 0,
|
||||
initialBatchRecoveryZeroSideEffects: partialRecoveryZeroSideEffects,
|
||||
initialBatchRecoveryPreKillDeliveryCount:
|
||||
partialSideEffectsBefore?.deliveryCount ?? 0,
|
||||
initialBatchRecoveryPostRecoveryDeliveryCount:
|
||||
partialSideEffectsAfter?.deliveryCount ?? 0,
|
||||
initialBatchRecoveryPreKillGroupCount:
|
||||
partialSideEffectsBefore?.isolatedGroupCount ?? 0,
|
||||
initialBatchRecoveryPostRecoveryGroupCount:
|
||||
partialSideEffectsAfter?.isolatedGroupCount ?? 0,
|
||||
initialBatchRecoveryPreKillChildCount:
|
||||
partialSideEffectsBefore?.isolatedChildCount ?? 0,
|
||||
initialBatchRecoveryPostRecoveryChildCount:
|
||||
partialSideEffectsAfter?.isolatedChildCount ?? 0,
|
||||
initialBatchRecoveryPreKillProjectRevision:
|
||||
partialSideEffectsBefore?.projectRevision ?? 0,
|
||||
initialBatchRecoveryPostRecoveryProjectRevision:
|
||||
partialSideEffectsAfter?.projectRevision ?? 0,
|
||||
initialBatchRecoveryPreKillProjectModificationCount:
|
||||
partialSideEffectsBefore?.projectModifiedPathCount ?? 0,
|
||||
initialBatchRecoveryPostRecoveryProjectModificationCount:
|
||||
partialSideEffectsAfter?.projectModifiedPathCount ?? 0,
|
||||
initialBatchRecoveryPreKillActionExecutionCount:
|
||||
partialSideEffectsBefore?.initialActionExecutionCount ?? 0,
|
||||
initialBatchRecoveryPostRecoveryActionExecutionCount:
|
||||
partialSideEffectsAfter?.initialActionExecutionCount ?? 0,
|
||||
initialBatchRecoveryPreKillActionReceiptCount:
|
||||
partialSideEffectsBefore?.initialActionReceiptCount ?? 0,
|
||||
initialBatchRecoveryPostRecoveryActionReceiptCount:
|
||||
partialSideEffectsAfter?.initialActionReceiptCount ?? 0,
|
||||
initialBatchRecoveryConfirmationRestoredCount:
|
||||
state.supervisorSwarm.initialBatchRecoveryConfirmationRestoredCount,
|
||||
initialBatchRecoveryRunnerBootChanged:
|
||||
partialRecoveryRequired &&
|
||||
isNonEmptyString(
|
||||
state.supervisorSwarm.initialBatchRecoveryOldRunnerBootId,
|
||||
) &&
|
||||
isNonEmptyString(
|
||||
state.supervisorSwarm.initialBatchRecoveryNewRunnerBootId,
|
||||
) &&
|
||||
state.supervisorSwarm.initialBatchRecoveryOldRunnerBootId !==
|
||||
state.supervisorSwarm.initialBatchRecoveryNewRunnerBootId,
|
||||
initialBatchRecoveryPidfdClaimCount:
|
||||
state.supervisorSwarm.initialBatchRecoveryPidfdClaimCount,
|
||||
initialBatchRecoveryPidfdSignalCount:
|
||||
state.supervisorSwarm.initialBatchRecoveryPidfdSignalCount,
|
||||
mixedModeEnabled: isSupervisorSwarmMixedHarnessSuite(),
|
||||
mixedSpawnActionCaptured: isNonEmptyString(
|
||||
state.supervisorSwarm.mixedSpawnActionId,
|
||||
),
|
||||
mixedFollowupSpawnActionCaptured: isNonEmptyString(
|
||||
state.supervisorSwarm.mixedFollowupSpawnActionId,
|
||||
),
|
||||
mixedSpawnRequestHashStable: supervisorSwarmMixedSpawnRequestHashesStable(
|
||||
isolatedRecords.groups,
|
||||
),
|
||||
mixedSpawnConfirmationRequiredCount:
|
||||
mixedSpawnConfirmationRequirements.length +
|
||||
mixedFollowupConfirmationRequirements.length,
|
||||
mixedSpawnApprovalCount:
|
||||
mixedSpawnApprovals.length + mixedFollowupApprovals.length,
|
||||
mixedSpawnConfirmationOrderValid,
|
||||
nativeMixedCollaborationPlanCount: persistence.agentDb.filter(
|
||||
(record) =>
|
||||
record.recordType === 'agent.runtime.tool_plan.protocol' &&
|
||||
record.agentId === projectSupervisorAgentId &&
|
||||
record.runId === state.initialRunId &&
|
||||
Array.isArray(record.functionNames) &&
|
||||
record.functionNames.filter(
|
||||
(name) => name === 'runtime_tool_agent_delegate',
|
||||
).length === 2 &&
|
||||
record.functionNames.filter(
|
||||
(name) => name === 'runtime_tool_agent_spawn_isolated',
|
||||
).length === 1,
|
||||
).length,
|
||||
nativeFollowupIsolatedPlanCount:
|
||||
isSupervisorSwarmMultiIsolatedHarnessSuite()
|
||||
? persistence.agentDb.filter(
|
||||
(record) =>
|
||||
record.recordType === 'agent.runtime.tool_plan.protocol' &&
|
||||
record.agentId === projectSupervisorAgentId &&
|
||||
record.runId === state.initialRunId &&
|
||||
Array.isArray(record.functionNames) &&
|
||||
record.functionNames.filter(
|
||||
(name) => name === 'runtime_tool_agent_delegate',
|
||||
).length === 0 &&
|
||||
record.functionNames.filter(
|
||||
(name) => name === 'runtime_tool_agent_spawn_isolated',
|
||||
).length === 1,
|
||||
).length
|
||||
: 0,
|
||||
staticIsolatedProviderOverlapObserved:
|
||||
state.supervisorSwarm.staticIsolatedProviderOverlapObserved,
|
||||
staticIsolatedProviderRequestIdentityCount: new Set(
|
||||
state.supervisorSwarm.staticIsolatedProviderRequestIds,
|
||||
).size,
|
||||
mixedParentIdentityStable:
|
||||
isolatedRecords.groups.length ===
|
||||
supervisorSwarmExpectedIsolatedReviewGroups().length &&
|
||||
isolatedRecords.groups.every(
|
||||
(group) =>
|
||||
group.parentAgentId === projectSupervisorAgentId &&
|
||||
group.parentSessionId === supervisorSwarmSessionId &&
|
||||
group.parentRunId === state.initialRunId,
|
||||
),
|
||||
initialDeliveryCount: initial.length,
|
||||
repairDeliveryCount: repairs.length,
|
||||
repairDeliveryStatus: observedRepairStatus,
|
||||
|
||||
-100
@@ -6,10 +6,8 @@ import {
|
||||
codedError,
|
||||
collectPartialRuntimeJsonlSurface,
|
||||
fs,
|
||||
hashJsonValue,
|
||||
hashValue,
|
||||
isNonEmptyString,
|
||||
isolatedJoinDeliveryTarget,
|
||||
isPlainObject,
|
||||
listFiles,
|
||||
path,
|
||||
@@ -586,104 +584,6 @@ export function supervisorSwarmTaskIdentity(task) {
|
||||
};
|
||||
}
|
||||
|
||||
export function supervisorSwarmIsolatedGroupIdentity(group) {
|
||||
return {
|
||||
schemaVersion: group.schemaVersion,
|
||||
parentAgentId: group.parentAgentId,
|
||||
parentSessionId: group.parentSessionId,
|
||||
parentRunId: group.parentRunId,
|
||||
parentActionId: group.parentActionId,
|
||||
delegationGroupId: group.delegationGroupId,
|
||||
joinRunId: group.joinRunId,
|
||||
depth: group.depth,
|
||||
joinMode: group.joinMode,
|
||||
requestSha256: hashJsonValue(group.request ?? null),
|
||||
instanceIds: [...(group.instanceIds ?? [])].sort(),
|
||||
createdAt: group.createdAt,
|
||||
};
|
||||
}
|
||||
|
||||
export function supervisorSwarmIsolatedInstanceIdentity(instance) {
|
||||
return {
|
||||
schemaVersion: instance.schemaVersion,
|
||||
parentAgentId: instance.parentAgentId,
|
||||
parentSessionId: instance.parentSessionId,
|
||||
parentRunId: instance.parentRunId,
|
||||
parentActionId: instance.parentActionId,
|
||||
delegationGroupId: instance.delegationGroupId,
|
||||
delegationId: instance.delegationId,
|
||||
childIndex: instance.childIndex,
|
||||
instanceId: instance.instanceId,
|
||||
templateAgentId: instance.templateAgentId,
|
||||
sessionId: instance.sessionId,
|
||||
runId: instance.runId,
|
||||
depth: instance.depth,
|
||||
taskSha256: hashValue(instance.task ?? ''),
|
||||
acceptanceCriteriaSha256: hashValue(
|
||||
JSON.stringify(instance.acceptanceCriteria ?? []),
|
||||
),
|
||||
expectedArtifacts: instance.expectedArtifacts,
|
||||
writeScopes: instance.writeScopes,
|
||||
createdAt: instance.createdAt,
|
||||
};
|
||||
}
|
||||
|
||||
export function supervisorSwarmIsolatedResultIdentity(record) {
|
||||
const result = record.result ?? {};
|
||||
return {
|
||||
schemaVersion: record.schemaVersion,
|
||||
delegationGroupId: record.delegationGroupId,
|
||||
childIndex: record.childIndex,
|
||||
delegationId: result.delegationId,
|
||||
instanceId: result.instanceId,
|
||||
templateAgentId: result.templateAgentId,
|
||||
runId: result.runId,
|
||||
status: result.status,
|
||||
summarySha256: hashValue(result.summary ?? ''),
|
||||
artifacts: result.artifacts,
|
||||
evidenceSha256: hashValue(JSON.stringify(result.evidence ?? [])),
|
||||
verifiedRevision: result.verifiedRevision ?? null,
|
||||
errorSha256: result.error == null ? null : hashValue(result.error),
|
||||
recordedAt: record.recordedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export function supervisorSwarmIsolatedJoinIdentity(delivery) {
|
||||
return {
|
||||
schemaVersion: delivery.schemaVersion,
|
||||
parentAgentId: delivery.parentAgentId,
|
||||
parentRunId: delivery.parentRunId,
|
||||
delegationGroupId: delivery.delegationGroupId,
|
||||
joinRunId: delivery.joinRunId,
|
||||
status: delivery.status,
|
||||
deliveryTarget: isolatedJoinDeliveryTarget(delivery),
|
||||
queuedRunId: delivery.queuedRunId ?? null,
|
||||
claimedByActionId: delivery.claimedByActionId ?? null,
|
||||
updatedAt: delivery.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export function supervisorSwarmIsolatedJoinClaimIdentity(claim) {
|
||||
return {
|
||||
schemaVersion: claim.schemaVersion,
|
||||
parentAgentId: claim.parentAgentId,
|
||||
parentRunId: claim.parentRunId,
|
||||
actionId: claim.actionId,
|
||||
status: claim.status,
|
||||
joins: (claim.joins ?? [])
|
||||
.map((join) => ({
|
||||
parentActionId: join.parentActionId,
|
||||
delegationGroupId: join.delegationGroupId,
|
||||
joinRunId: join.joinRunId,
|
||||
source: join.source,
|
||||
}))
|
||||
.sort((left, right) =>
|
||||
left.delegationGroupId.localeCompare(right.delegationGroupId),
|
||||
),
|
||||
updatedAt: claim.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export function supervisorSwarmParentIsolatedRecords(persistence) {
|
||||
const groups = (persistence.isolatedGroups ?? []).filter(
|
||||
(group) =>
|
||||
|
||||
+14
-325
@@ -22,8 +22,6 @@ import {
|
||||
} from './shared.mjs';
|
||||
import {
|
||||
isSupervisorSwarmFinalReplyTransientRetrySuite,
|
||||
isSupervisorSwarmMixedHarnessSuite,
|
||||
isSupervisorSwarmMultiIsolatedHarnessSuite,
|
||||
isSupervisorSwarmTransientRetrySuite,
|
||||
} from './suite-selection.mjs';
|
||||
|
||||
@@ -403,12 +401,10 @@ export function validateSupervisorSwarmNativeProtocol(
|
||||
toolPlanRepairEvidenceHasNoFatalLocalRepair(repairEvidence),
|
||||
'supervisor-swarm-native-tool-protocol-required',
|
||||
);
|
||||
const mixed = isSupervisorSwarmMixedHarnessSuite();
|
||||
const expectedActionFunctionCount = mixed ? 3 : 2;
|
||||
const expectedActionFunctionCount = 2;
|
||||
const allowedInitialFunctionNames = new Set([
|
||||
'update_agent_plan',
|
||||
'runtime_tool_agent_delegate',
|
||||
...(mixed ? ['runtime_tool_agent_spawn_isolated'] : []),
|
||||
]);
|
||||
const initialMultiCall = protocols.filter((record) => {
|
||||
if (!Array.isArray(record.functionNames)) return false;
|
||||
@@ -428,7 +424,7 @@ export function validateSupervisorSwarmNativeProtocol(
|
||||
).length === 2 &&
|
||||
actionFunctionNames.filter(
|
||||
(name) => name === 'runtime_tool_agent_spawn_isolated',
|
||||
).length === (mixed ? 1 : 0) &&
|
||||
).length === 0 &&
|
||||
record.functionNames.every((name) =>
|
||||
allowedInitialFunctionNames.has(name),
|
||||
) &&
|
||||
@@ -445,40 +441,6 @@ export function validateSupervisorSwarmNativeProtocol(
|
||||
initialMultiCall.length === 1,
|
||||
'supervisor-swarm-native-collaboration-plan-count-invalid',
|
||||
);
|
||||
const followupIsolatedPlans = isSupervisorSwarmMultiIsolatedHarnessSuite()
|
||||
? protocols.filter((record) => {
|
||||
if (!Array.isArray(record.functionNames)) return false;
|
||||
const actionFunctionNames = record.functionNames.filter((name) =>
|
||||
name.startsWith('runtime_tool_'),
|
||||
);
|
||||
return (
|
||||
record.agentId === projectSupervisorAgentId &&
|
||||
record.sessionId === supervisorSwarmSessionId &&
|
||||
record.runId === state.initialRunId &&
|
||||
record.loopIteration >
|
||||
state.supervisorSwarm.initialProviderBatch.loopIteration &&
|
||||
actionFunctionNames.length === 1 &&
|
||||
actionFunctionNames[0] === 'runtime_tool_agent_spawn_isolated' &&
|
||||
record.functionNames.every((name) =>
|
||||
['update_agent_plan', 'runtime_tool_agent_spawn_isolated'].includes(
|
||||
name,
|
||||
),
|
||||
) &&
|
||||
record.functionNames.filter((name) => name === 'update_agent_plan')
|
||||
.length <= 1 &&
|
||||
hasValidToolPlanProtocolCallProjection(record) &&
|
||||
record.functionCallCount === record.functionNames.length &&
|
||||
Array.isArray(record.callIdSha256s) &&
|
||||
record.callIdSha256s.length === record.functionCallCount &&
|
||||
new Set(record.callIdSha256s).size === record.callIdSha256s.length
|
||||
);
|
||||
})
|
||||
: [];
|
||||
assert(
|
||||
!isSupervisorSwarmMultiIsolatedHarnessSuite() ||
|
||||
followupIsolatedPlans.length === 1,
|
||||
'supervisor-swarm-native-followup-isolated-plan-count-invalid',
|
||||
);
|
||||
return {
|
||||
toolPlanProtocolCount: relevant.filter(
|
||||
(record) => record.recordType === 'agent.runtime.tool_plan.protocol',
|
||||
@@ -496,10 +458,6 @@ export function validateSupervisorSwarmNativeProtocol(
|
||||
(record) => record.protocol === 'text_json',
|
||||
).length,
|
||||
nativeDualDelegatePlanCount: initialMultiCall.length,
|
||||
nativeMixedCollaborationPlanCount: isSupervisorSwarmMixedHarnessSuite()
|
||||
? initialMultiCall.length
|
||||
: 0,
|
||||
nativeFollowupIsolatedPlanCount: followupIsolatedPlans.length,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -637,9 +595,7 @@ export function validateSupervisorSwarmActionPersistence(
|
||||
}
|
||||
const initialBatchActions =
|
||||
state.supervisorSwarm.initialProviderBatch?.actions ?? [];
|
||||
const expectedInitialActionCount = isSupervisorSwarmMixedHarnessSuite()
|
||||
? 3
|
||||
: 2;
|
||||
const expectedInitialActionCount = 2;
|
||||
assert(
|
||||
initialBatchActions.length === expectedInitialActionCount,
|
||||
'supervisor-swarm-initial-batch-action-count-invalid',
|
||||
@@ -651,284 +607,20 @@ export function validateSupervisorSwarmActionPersistence(
|
||||
record.actionId === action.actionId &&
|
||||
record.actionFingerprint === action.actionFingerprint &&
|
||||
record.tool === action.tool;
|
||||
let mixedSpawnConfirmationRequiredCount = 0;
|
||||
let mixedSpawnApprovalCount = 0;
|
||||
let mixedSpawnConfirmationOrderValid = false;
|
||||
let mixedInitialSpawnTerminalObservationIndex = -1;
|
||||
if (isSupervisorSwarmMixedHarnessSuite()) {
|
||||
const delegateActions = initialBatchActions.filter(
|
||||
(action) => action.tool === 'agent.delegate',
|
||||
);
|
||||
const spawnActions = initialBatchActions.filter(
|
||||
assert(
|
||||
initialBatchActions.every(
|
||||
(action) =>
|
||||
action.tool === 'agent.spawn_isolated' &&
|
||||
action.actionId === state.supervisorSwarm.mixedSpawnActionId,
|
||||
);
|
||||
const spawnAction = spawnActions[0];
|
||||
const indexedAgentDb = agentDb.map((record, index) => ({ record, index }));
|
||||
const initialActionTimeline = (action) => {
|
||||
const auto = action.tool === 'agent.delegate';
|
||||
const executions = indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.tool_action.executing' &&
|
||||
record.executionMode === 'auto' &&
|
||||
matchesInitialBatchAction(record, action),
|
||||
);
|
||||
const autoObserved = indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.tool_action.observed' &&
|
||||
record.executionMode === 'auto' &&
|
||||
record.observationStatus === 'ok' &&
|
||||
matchesInitialBatchAction(record, action),
|
||||
);
|
||||
const receipts = indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.action_receipt' &&
|
||||
record.sessionId === supervisorSwarmSessionId &&
|
||||
record.executionMode === (auto ? 'auto' : 'confirmation') &&
|
||||
record.status === 'ok' &&
|
||||
matchesInitialBatchAction(record, action),
|
||||
);
|
||||
const terminalObservations = indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.tool_observation' &&
|
||||
record.status === 'ok' &&
|
||||
record.decision === (auto ? 'auto' : 'approved') &&
|
||||
matchesInitialBatchAction(record, action),
|
||||
);
|
||||
let sideEffects = [];
|
||||
if (auto) {
|
||||
const actionDeliveries = deliveries.filter(
|
||||
(delivery) =>
|
||||
delivery.parentAgentId === projectSupervisorAgentId &&
|
||||
delivery.parentSessionId === supervisorSwarmSessionId &&
|
||||
delivery.parentRunId === state.initialRunId &&
|
||||
delivery.parentActionId === action.actionId &&
|
||||
delivery.repairOfDelegationId == null,
|
||||
);
|
||||
sideEffects =
|
||||
actionDeliveries.length === 1
|
||||
? indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.agent.delegate' &&
|
||||
record.parentRunId === state.initialRunId &&
|
||||
record.delegationId === actionDeliveries[0].delegationId &&
|
||||
record.targetAgentId === actionDeliveries[0].targetAgentId &&
|
||||
record.runId === actionDeliveries[0].targetRunId,
|
||||
)
|
||||
: [];
|
||||
} else {
|
||||
sideEffects = indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.agent.spawn_isolated' &&
|
||||
record.agentId === projectSupervisorAgentId &&
|
||||
executing.filter((record) => matchesInitialBatchAction(record, action))
|
||||
.length === 1 &&
|
||||
receipts.filter(
|
||||
(record) =>
|
||||
record.sessionId === supervisorSwarmSessionId &&
|
||||
record.runId === state.initialRunId &&
|
||||
record.actionId === action.actionId,
|
||||
);
|
||||
}
|
||||
return {
|
||||
action,
|
||||
auto,
|
||||
executions,
|
||||
autoObserved,
|
||||
receipts,
|
||||
terminalObservations,
|
||||
sideEffects,
|
||||
};
|
||||
};
|
||||
const actionTimelines = initialBatchActions
|
||||
.map(initialActionTimeline)
|
||||
.sort(
|
||||
(left, right) => left.action.actionIndex - right.action.actionIndex,
|
||||
);
|
||||
const delegateTimelines = actionTimelines.filter(({ auto }) => auto);
|
||||
const spawnTimeline = actionTimelines.find(({ auto }) => !auto);
|
||||
const confirmationRequired = spawnAction
|
||||
? indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType ===
|
||||
'agent.runtime.provider_action_batch.confirmation_required' &&
|
||||
record.agentId === projectSupervisorAgentId &&
|
||||
record.sessionId === supervisorSwarmSessionId &&
|
||||
record.runId === state.initialRunId &&
|
||||
record.batchId ===
|
||||
state.supervisorSwarm.initialProviderBatch.batchId &&
|
||||
record.actionCount === expectedInitialActionCount &&
|
||||
record.actionIndex === spawnAction.actionIndex &&
|
||||
matchesInitialBatchAction(record, spawnAction),
|
||||
)
|
||||
: [];
|
||||
const approvals = spawnAction
|
||||
? indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.tool_confirmation.approved' &&
|
||||
record.sessionId === supervisorSwarmSessionId &&
|
||||
record.confirmedRunId === state.initialRunId &&
|
||||
record.commandId === 'agent.spawn_isolated' &&
|
||||
matchesInitialBatchAction(record, spawnAction),
|
||||
)
|
||||
: [];
|
||||
const actionLifecycleOrderValid = actionTimelines.every((timeline) => {
|
||||
if (
|
||||
timeline.sideEffects.length !== 1 ||
|
||||
timeline.receipts.length !== 1 ||
|
||||
timeline.terminalObservations.length !== 1
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (timeline.auto) {
|
||||
return (
|
||||
timeline.executions.length === 1 &&
|
||||
timeline.autoObserved.length === 1 &&
|
||||
timeline.executions[0].index < timeline.sideEffects[0].index &&
|
||||
timeline.sideEffects[0].index < timeline.autoObserved[0].index &&
|
||||
timeline.autoObserved[0].index < timeline.receipts[0].index &&
|
||||
timeline.receipts[0].index < timeline.terminalObservations[0].index
|
||||
);
|
||||
}
|
||||
return (
|
||||
timeline.executions.length === 0 &&
|
||||
timeline.autoObserved.length === 0 &&
|
||||
approvals.length === 1 &&
|
||||
approvals[0].index < timeline.sideEffects[0].index &&
|
||||
timeline.sideEffects[0].index < timeline.receipts[0].index &&
|
||||
timeline.receipts[0].index < timeline.terminalObservations[0].index
|
||||
);
|
||||
});
|
||||
const batchActionOrderValid = actionTimelines.every((timeline, index) => {
|
||||
if (timeline.action.actionIndex !== index) return false;
|
||||
if (index === 0) return true;
|
||||
const previousEnd =
|
||||
actionTimelines[index - 1].terminalObservations[0]?.index;
|
||||
const currentStart = timeline.auto
|
||||
? timeline.executions[0]?.index
|
||||
: timeline.sideEffects[0]?.index;
|
||||
return (
|
||||
Number.isSafeInteger(previousEnd) &&
|
||||
Number.isSafeInteger(currentStart) &&
|
||||
previousEnd < currentStart
|
||||
);
|
||||
});
|
||||
mixedSpawnConfirmationRequiredCount = confirmationRequired.length;
|
||||
mixedSpawnApprovalCount = approvals.length;
|
||||
mixedInitialSpawnTerminalObservationIndex =
|
||||
spawnTimeline?.terminalObservations[0]?.index ?? -1;
|
||||
mixedSpawnConfirmationOrderValid =
|
||||
confirmationRequired.length === 1 &&
|
||||
approvals.length === 1 &&
|
||||
confirmationRequired[0].index < approvals[0].index &&
|
||||
actionTimelines.every(
|
||||
(timeline) =>
|
||||
approvals[0].index <
|
||||
(timeline.auto
|
||||
? timeline.executions[0]?.index
|
||||
: timeline.sideEffects[0]?.index),
|
||||
) &&
|
||||
actionLifecycleOrderValid &&
|
||||
batchActionOrderValid;
|
||||
assert(
|
||||
delegateActions.length === 2 &&
|
||||
new Set(delegateActions.map((action) => action.actionId)).size === 2 &&
|
||||
spawnActions.length === 1 &&
|
||||
spawnAction.executionMode === 'confirmation' &&
|
||||
delegateActions.every((action) => action.executionMode === 'auto') &&
|
||||
delegateTimelines.length === 2 &&
|
||||
spawnTimeline != null &&
|
||||
mixedSpawnConfirmationRequiredCount === 1 &&
|
||||
mixedSpawnApprovalCount === 1 &&
|
||||
mixedSpawnConfirmationOrderValid,
|
||||
'supervisor-swarm-mixed-initial-batch-action-persistence-invalid',
|
||||
);
|
||||
} else {
|
||||
assert(
|
||||
initialBatchActions.every(
|
||||
(action) =>
|
||||
executing.filter((record) =>
|
||||
record.status === 'ok' &&
|
||||
matchesInitialBatchAction(record, action),
|
||||
).length === 1 &&
|
||||
receipts.filter(
|
||||
(record) =>
|
||||
record.sessionId === supervisorSwarmSessionId &&
|
||||
record.status === 'ok' &&
|
||||
matchesInitialBatchAction(record, action),
|
||||
).length === 1,
|
||||
),
|
||||
'supervisor-swarm-initial-batch-action-persistence-invalid',
|
||||
);
|
||||
}
|
||||
if (isSupervisorSwarmMultiIsolatedHarnessSuite()) {
|
||||
const followupActionId = state.supervisorSwarm.mixedFollowupSpawnActionId;
|
||||
assert(
|
||||
isNonEmptyString(followupActionId) &&
|
||||
followupActionId !== state.supervisorSwarm.mixedSpawnActionId,
|
||||
'supervisor-swarm-followup-spawn-action-missing',
|
||||
);
|
||||
const indexedAgentDb = agentDb.map((record, index) => ({ record, index }));
|
||||
const matchesFollowupIdentity = (record) =>
|
||||
record.agentId === projectSupervisorAgentId &&
|
||||
(record.runId === state.initialRunId ||
|
||||
record.confirmedRunId === state.initialRunId) &&
|
||||
record.actionId === followupActionId;
|
||||
const matchesFollowupToolRecord = (record) =>
|
||||
matchesFollowupIdentity(record) &&
|
||||
(record.tool === 'agent.spawn_isolated' ||
|
||||
record.commandId === 'agent.spawn_isolated');
|
||||
const confirmationRequired = indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType ===
|
||||
'agent.runtime.provider_action_batch.confirmation_required' &&
|
||||
record.sessionId === supervisorSwarmSessionId &&
|
||||
matchesFollowupToolRecord(record),
|
||||
);
|
||||
const approvals = indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.tool_confirmation.approved' &&
|
||||
record.sessionId === supervisorSwarmSessionId &&
|
||||
matchesFollowupToolRecord(record),
|
||||
);
|
||||
const sideEffects = indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.agent.spawn_isolated' &&
|
||||
record.sessionId === supervisorSwarmSessionId &&
|
||||
matchesFollowupIdentity(record),
|
||||
);
|
||||
const followupReceipts = indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.action_receipt' &&
|
||||
record.sessionId === supervisorSwarmSessionId &&
|
||||
record.executionMode === 'confirmation' &&
|
||||
record.status === 'ok' &&
|
||||
matchesFollowupToolRecord(record),
|
||||
);
|
||||
const terminalObservations = indexedAgentDb.filter(
|
||||
({ record }) =>
|
||||
record.recordType === 'agent.runtime.tool_observation' &&
|
||||
record.status === 'ok' &&
|
||||
record.decision === 'approved' &&
|
||||
matchesFollowupToolRecord(record),
|
||||
);
|
||||
const followupOrderValid =
|
||||
confirmationRequired.length === 1 &&
|
||||
approvals.length === 1 &&
|
||||
sideEffects.length === 1 &&
|
||||
followupReceipts.length === 1 &&
|
||||
terminalObservations.length === 1 &&
|
||||
mixedInitialSpawnTerminalObservationIndex >= 0 &&
|
||||
mixedInitialSpawnTerminalObservationIndex <
|
||||
confirmationRequired[0].index &&
|
||||
confirmationRequired[0].index < approvals[0].index &&
|
||||
approvals[0].index < sideEffects[0].index &&
|
||||
sideEffects[0].index < followupReceipts[0].index &&
|
||||
followupReceipts[0].index < terminalObservations[0].index;
|
||||
assert(
|
||||
followupOrderValid,
|
||||
'supervisor-swarm-followup-spawn-action-persistence-invalid',
|
||||
);
|
||||
mixedSpawnConfirmationRequiredCount += confirmationRequired.length;
|
||||
mixedSpawnApprovalCount += approvals.length;
|
||||
mixedSpawnConfirmationOrderValid &&= followupOrderValid;
|
||||
}
|
||||
).length === 1,
|
||||
),
|
||||
'supervisor-swarm-initial-batch-action-persistence-invalid',
|
||||
);
|
||||
const matchesRecoveredRepairPending = (record) =>
|
||||
record.agentId === state.supervisorSwarm.repairTargetAgentId &&
|
||||
record.runId === state.supervisorSwarm.repairTargetRunId &&
|
||||
@@ -957,9 +649,6 @@ export function validateSupervisorSwarmActionPersistence(
|
||||
duplicateReceiptCount,
|
||||
duplicateExecutingActionIdCount,
|
||||
isolatedMutationActionCount: isolatedMutationActions.length,
|
||||
mixedSpawnConfirmationRequiredCount,
|
||||
mixedSpawnApprovalCount,
|
||||
mixedSpawnConfirmationOrderValid,
|
||||
recoveredRepairPendingActionCount:
|
||||
recoveredRepairConfirmationLifecycle.receipts.length,
|
||||
failedRepairActionCount: repairAttempts.failedRepairActions.length,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user