oh-my-copilot 5.6.1 → 5.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/bridge/__pycache__/gyoshu_bridge.cpython-312.pyc +0 -0
- package/bridge/claude-md-coordinator.cjs +2 -2
- package/bridge/cli.cjs +88 -4
- package/bridge/runtime-cli.cjs +31 -0
- package/bridge/team.js +122 -7
- package/dist/__tests__/run-cjs-graceful-fallback.test.js +2 -0
- package/dist/__tests__/run-cjs-graceful-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +170 -0
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/commands/team.d.ts.map +1 -1
- package/dist/cli/commands/team.js +25 -5
- package/dist/cli/commands/team.js.map +1 -1
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +61 -7
- package/dist/cli/team.js.map +1 -1
- package/dist/team/runtime-v2.d.ts +12 -0
- package/dist/team/runtime-v2.d.ts.map +1 -1
- package/dist/team/runtime-v2.js +103 -1
- package/dist/team/runtime-v2.js.map +1 -1
- package/docs/CLAUDE.md +1 -1
- package/docs/MIGRATION.md +17 -0
- package/docs/REFERENCE.md +1 -1
- package/package.json +6 -2
- package/plugin.json +1 -1
- package/scripts/build-contained-fs.mjs +24 -2
- package/scripts/git-guardrails.mjs +10 -9
- package/scripts/postinstall-contained-fs.mjs +56 -0
- package/scripts/run.cjs +18 -0
package/dist/team/runtime-v2.js
CHANGED
|
@@ -19,7 +19,7 @@ import { join, resolve } from 'path';
|
|
|
19
19
|
import { existsSync } from 'fs';
|
|
20
20
|
import { link, lstat, mkdir, open, readdir, readFile, rm, unlink, writeFile } from 'fs/promises';
|
|
21
21
|
import { performance } from 'perf_hooks';
|
|
22
|
-
import { TeamPaths, absPath, teamStateRoot } from './state-paths.js';
|
|
22
|
+
import { TeamPaths, absPath, canonicalTeamStatePath, teamStateRoot, teamWorkspaceHash } from './state-paths.js';
|
|
23
23
|
import { getOmcRoot, validateSessionId } from '../lib/worktree-paths.js';
|
|
24
24
|
import { allocateTasksToWorkers } from './allocation-policy.js';
|
|
25
25
|
import { readTeamConfig, readWorkerStatus, readWorkerHeartbeat, writeShutdownRequest, readShutdownAck, writeWorkerInbox, saveTeamConfig, commitInitialTeamConfigUnderLock, readRevisionedTeamConfig, saveTeamConfigAtRevision, migrateTeamConfigRevision, withTeamConfigMutationLock, readTeamManifest, } from './monitor.js';
|
|
@@ -3412,6 +3412,14 @@ export async function startTeamV2(config) {
|
|
|
3412
3412
|
const missing = missingBinaryReasons.map(({ agentType, reason }) => `${agentType}:${reason}`).join(';');
|
|
3413
3413
|
throw new Error(`cli_binary_preflight_failed:${missing}`);
|
|
3414
3414
|
}
|
|
3415
|
+
// Clean up any stale reservations from dead processes BEFORE acquiring the lifecycle lock.
|
|
3416
|
+
// This prevents nested lock acquisition and ensures old dead-owner reservations don't block startup.
|
|
3417
|
+
try {
|
|
3418
|
+
await cleanupStaleReservations(sanitized, leaderCwd);
|
|
3419
|
+
}
|
|
3420
|
+
catch {
|
|
3421
|
+
// Best-effort; proceed even if cleanup fails
|
|
3422
|
+
}
|
|
3415
3423
|
return withTeamInstanceLifecycleLock(leaderCwd, sanitized, async () => {
|
|
3416
3424
|
// Reserve the name before creating any state, worktree, pane, or provider
|
|
3417
3425
|
// effect. The reservation remains external and blocks same-name startup
|
|
@@ -4938,6 +4946,100 @@ export async function monitorTeamV2(teamName, cwd, expectedInstanceId) {
|
|
|
4938
4946
|
};
|
|
4939
4947
|
}
|
|
4940
4948
|
// ---------------------------------------------------------------------------
|
|
4949
|
+
// cleanupStaleReservations — remove reservations owned by dead processes
|
|
4950
|
+
// ---------------------------------------------------------------------------
|
|
4951
|
+
/**
|
|
4952
|
+
* Best-effort cleanup of stale team reservations owned by dead processes.
|
|
4953
|
+
* Acquires the lifecycle lock to ensure race-safe removal.
|
|
4954
|
+
* This allows new teams to be created even if a previous team's reservation
|
|
4955
|
+
* file was left behind due to process death during startup.
|
|
4956
|
+
*/
|
|
4957
|
+
export async function cleanupStaleReservations(teamName, cwd) {
|
|
4958
|
+
const sanitized = sanitizeTeamName(teamName);
|
|
4959
|
+
try {
|
|
4960
|
+
await withTeamInstanceLifecycleLock(cwd, sanitized, async () => {
|
|
4961
|
+
const workspaceHash = teamWorkspaceHash(cwd, sanitized);
|
|
4962
|
+
const reservationPath = canonicalTeamStatePath(cwd, TeamPaths.teamInstanceReservation(workspaceHash, sanitized));
|
|
4963
|
+
if (!existsSync(reservationPath))
|
|
4964
|
+
return;
|
|
4965
|
+
const contentStr = await readFile(reservationPath, 'utf-8');
|
|
4966
|
+
const reservation = JSON.parse(contentStr);
|
|
4967
|
+
// Validate basic shape
|
|
4968
|
+
if (!(reservation &&
|
|
4969
|
+
typeof reservation === 'object' &&
|
|
4970
|
+
!Array.isArray(reservation) &&
|
|
4971
|
+
'owner' in reservation &&
|
|
4972
|
+
typeof reservation.owner === 'object')) {
|
|
4973
|
+
return; // Malformed, leave alone
|
|
4974
|
+
}
|
|
4975
|
+
const owner = reservation.owner;
|
|
4976
|
+
if (!(owner &&
|
|
4977
|
+
typeof owner === 'object' &&
|
|
4978
|
+
!Array.isArray(owner) &&
|
|
4979
|
+
'pid' in owner &&
|
|
4980
|
+
'process_started_at' in owner)) {
|
|
4981
|
+
return; // Malformed owner, leave alone
|
|
4982
|
+
}
|
|
4983
|
+
// Check if the process is dead
|
|
4984
|
+
const ownerRecord = owner;
|
|
4985
|
+
if (isProcessIdentityDead(ownerRecord)) {
|
|
4986
|
+
// Owner process is dead; clean up this reservation
|
|
4987
|
+
await unlink(reservationPath);
|
|
4988
|
+
}
|
|
4989
|
+
}, 5_000);
|
|
4990
|
+
}
|
|
4991
|
+
catch {
|
|
4992
|
+
// Best-effort; silently proceed on any error
|
|
4993
|
+
}
|
|
4994
|
+
}
|
|
4995
|
+
/**
|
|
4996
|
+
* Clean up abandoned team state when config exists but has no valid instance_id.
|
|
4997
|
+
* This handles partial startup failures and ensures no state is left behind.
|
|
4998
|
+
*/
|
|
4999
|
+
export async function cleanupAbandonedTeamState(teamName, cwd) {
|
|
5000
|
+
const sanitized = sanitizeTeamName(teamName);
|
|
5001
|
+
await withTeamInstanceLifecycleLock(cwd, sanitized, async () => {
|
|
5002
|
+
// Clean up the reservation if it exists and owner is dead or missing
|
|
5003
|
+
const workspaceHash = teamWorkspaceHash(cwd, sanitized);
|
|
5004
|
+
const reservationPath = canonicalTeamStatePath(cwd, TeamPaths.teamInstanceReservation(workspaceHash, sanitized));
|
|
5005
|
+
if (existsSync(reservationPath)) {
|
|
5006
|
+
try {
|
|
5007
|
+
const contentStr = await readFile(reservationPath, 'utf-8');
|
|
5008
|
+
const reservation = JSON.parse(contentStr);
|
|
5009
|
+
const owner = reservation.owner;
|
|
5010
|
+
// Remove if owner is dead or if this process owns it
|
|
5011
|
+
if (!owner || isProcessIdentityDead(owner) || ownerMatchesCurrent(owner)) {
|
|
5012
|
+
await unlink(reservationPath);
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
catch {
|
|
5016
|
+
// If reservation is malformed, try to remove it anyway
|
|
5017
|
+
try {
|
|
5018
|
+
await unlink(reservationPath);
|
|
5019
|
+
}
|
|
5020
|
+
catch {
|
|
5021
|
+
// Reservation removal failed; continue with state dir cleanup
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
// Clean up the team state directory
|
|
5026
|
+
const teamRoot = teamStateRoot(cwd, sanitized);
|
|
5027
|
+
if (existsSync(teamRoot)) {
|
|
5028
|
+
try {
|
|
5029
|
+
await rm(teamRoot, { recursive: true, force: true });
|
|
5030
|
+
}
|
|
5031
|
+
catch (error) {
|
|
5032
|
+
throw new Error(`Failed to remove team state directory at ${teamRoot}: ${error instanceof Error ? error.message : String(error)}`);
|
|
5033
|
+
}
|
|
5034
|
+
}
|
|
5035
|
+
}, 10_000);
|
|
5036
|
+
}
|
|
5037
|
+
// Helper to check if owner matches current process
|
|
5038
|
+
function ownerMatchesCurrent(owner) {
|
|
5039
|
+
const processStartedAt = currentProcessStartIdentity();
|
|
5040
|
+
return processStartedAt !== null && owner.pid === process.pid && processStartedAt === owner.process_started_at;
|
|
5041
|
+
}
|
|
5042
|
+
// ---------------------------------------------------------------------------
|
|
4941
5043
|
// shutdownTeam — graceful shutdown with gate, ack, force kill
|
|
4942
5044
|
// ---------------------------------------------------------------------------
|
|
4943
5045
|
/**
|