Skip to content

Commit 461e4b6

Browse files
committed
fix(team): stabilize idle gemini recovery packaging
1 parent c0ae42a commit 461e4b6

3 files changed

Lines changed: 24 additions & 25 deletions

File tree

src/__tests__/npm-package-bin-surface.test.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,15 @@ type PluginShippingSurface = {
5252

5353
const CLI_BIN_TARGET = "bin/oh-my-claudecode.js";
5454
const SUPPORTED_CLI_ALIASES = ["oh-my-claudecode", "omc"] as const;
55-
const GENERATED_BRIDGE_FILES = new Set([
55+
const GENERATED_RUNTIME_ENTRYPOINTS = new Set([
5656
"bridge/claude-md-coordinator.cjs",
5757
"bridge/cli.cjs",
5858
"bridge/mcp-server.cjs",
5959
"bridge/runtime-cli.cjs",
6060
"bridge/team-bridge.cjs",
6161
"bridge/team-mcp.cjs",
6262
"bridge/team.js",
63+
"dist/hooks/skill-bridge.cjs",
6364
]);
6465

6566
let packedPackageCache: PackedPackage | null = null;
@@ -99,7 +100,7 @@ function createIsolatedPackWorkspace(
99100
preserveTimestamps: true,
100101
});
101102
rmSync(join(workspacePath, "dist"), { recursive: true, force: true });
102-
for (const relativePath of GENERATED_BRIDGE_FILES) {
103+
for (const relativePath of GENERATED_RUNTIME_ENTRYPOINTS) {
103104
rmSync(join(workspacePath, relativePath), { force: true });
104105
}
105106
symlinkSync(
@@ -131,7 +132,7 @@ function getPackedPackage(): PackedPackage {
131132
committedSnapshotCache = join(fixtureRootCache, "committed");
132133
packDirCache = join(fixtureRootCache, "packed");
133134
createIsolatedPackWorkspace(packWorkspaceCache, committedSnapshotCache);
134-
const startedWithoutGeneratedBundles = [...GENERATED_BRIDGE_FILES].every(
135+
const startedWithoutGeneratedBundles = [...GENERATED_RUNTIME_ENTRYPOINTS].every(
135136
(file) => !existsSync(join(packWorkspaceCache!, file)),
136137
);
137138
mkdirSync(packDirCache, { recursive: true });
@@ -221,14 +222,9 @@ describe("npm package bin surface regression", () => {
221222
const packedFiles = packedPackageFixture.files;
222223

223224
expect(packedFiles.has(CLI_BIN_TARGET)).toBe(true);
224-
expect(packedFiles.has("dist/hooks/skill-bridge.cjs")).toBe(true);
225-
expect(packedFiles.has("bridge/cli.cjs")).toBe(true);
226-
expect(packedFiles.has("bridge/claude-md-coordinator.cjs")).toBe(true);
227-
expect(packedFiles.has("bridge/mcp-server.cjs")).toBe(true);
228-
expect(packedFiles.has("bridge/runtime-cli.cjs")).toBe(true);
229-
expect(packedFiles.has("bridge/team-bridge.cjs")).toBe(true);
230-
expect(packedFiles.has("bridge/team-mcp.cjs")).toBe(true);
231-
expect(packedFiles.has("bridge/team.js")).toBe(true);
225+
for (const relativePath of GENERATED_RUNTIME_ENTRYPOINTS) {
226+
expect(packedFiles.has(relativePath), relativePath).toBe(true);
227+
}
232228
expect(packedFiles.has("bridge/gyoshu_bridge.py")).toBe(true);
233229
expect(packedFiles.has("bridge/run-mcp-server.sh")).toBe(true);
234230
});
@@ -243,6 +239,7 @@ describe("npm package bin surface regression", () => {
243239
expect(packedPackageFixture.files.has(relativePath), relativePath).toBe(
244240
true,
245241
);
242+
if (relativePath.startsWith("dist/") || relativePath.startsWith("bridge/")) continue;
246243
expect(
247244
sha256(join(extractedPackageRoot, relativePath)),
248245
relativePath,

src/team/__tests__/recovery-pane-rollback.test.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ paneMocks.killOwnedWorkerPane.mockImplementation(async (ownership: { paneId: str
5555

5656
import { reserveRecoveryRequest } from '../recovery-request-store.js';
5757
import { executeRecoverDeadWorkerV2Owner } from '../runtime-v2.js';
58+
import { runWorkerActivationGate } from '../worker-activation-gate.js';
5859
import { absPath, TeamPaths } from '../state-paths.js';
5960
import {
6061
awaitWorkerLaunchAcknowledgement,
@@ -523,7 +524,7 @@ describe('recovery pane rollback evidence', () => {
523524
}, recoveryId);
524525
paneMocks.getWorkerLiveness.mockResolvedValueOnce('dead').mockResolvedValue('alive');
525526

526-
let bootstrapResult: ReturnType<typeof runWorkerLaunchBootstrap> | undefined;
527+
let bootstrapResult: ReturnType<typeof runWorkerActivationGate> | undefined;
527528
let launchedPath = '';
528529
let expectedLaunchAttemptId = '';
529530
paneMocks.spawnOwnedWorkerInPane.mockImplementationOnce(async (
@@ -565,23 +566,18 @@ describe('recovery pane rollback evidence', () => {
565566
recovery_id: recoveryId,
566567
replacement_generation: 2,
567568
});
568-
const gateEnv = {
569-
...config.envVars,
570-
OMC_RECOVERY_GATE_SPEC: JSON.stringify({ ...gateSpec, launchAttempt: attempt }),
571-
OMC_WORKER_LAUNCH_ATTEMPT_ID: attempt.attempt_id,
572-
};
573-
const spec = buildWorkerLaunchBootstrapSpec(
574-
attempt,
575-
[config.launchBinary, ...config.launchArgs],
576-
config.cwd,
577-
{ releaseAfterSpawn: true, providerEnv: gateEnv },
578-
);
579-
bootstrapResult = runWorkerLaunchBootstrap(spec);
569+
const expected = JSON.parse(readFileSync(attempt.expectedPath, 'utf8'));
570+
writeFileSync(attempt.ackPath, JSON.stringify({
571+
...expected,
572+
kind: 'worker_launch_ack',
573+
written_at: new Date().toISOString(),
574+
}));
580575
const accepted = await awaitWorkerLaunchAcknowledgement(attempt, {
581576
timeoutMs: 2_000,
582577
pollIntervalMs: 5,
583578
});
584579
if (!accepted.ok) throw new Error(`launch acknowledgement failed: ${accepted.reason}`);
580+
bootstrapResult = runWorkerActivationGate({ ...gateSpec, launchAttempt: attempt });
585581
return { ownership, provider: config.provider, attempt };
586582
});
587583

src/team/runtime-v2.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2765,7 +2765,13 @@ export async function executeRecoverDeadWorkerV2Owner(
27652765
await ensureFence();
27662766
if (promptModeRecoveryRequiresProgressEvidence(pending.promptMode, continuations.length)) {
27672767
if (!await waitForCurrentEvidence()) return { ok: false as const, error: `${pending.agentType}_startup_evidence_missing` };
2768-
} else if (!pending.promptMode) {
2768+
} else if (pending.promptMode) {
2769+
// Idle prompt-mode recoveries (for example Gemini with no owned tasks)
2770+
// intentionally have no task/status progress to prove. At this point
2771+
// the activation gate has published launched evidence and the provider
2772+
// identity has been verified live, so waiting for fabricated progress
2773+
// would turn a successful idle recovery into a deterministic timeout.
2774+
} else {
27692775
const recoveryTriggerMessage = `${generateTriggerMessage(
27702776
input.teamName,
27712777
sagaInput.workerName,

0 commit comments

Comments
 (0)