oh-my-codex 0.8.11 → 0.8.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -35
- package/dist/agents/__tests__/definitions.test.js +1 -0
- package/dist/agents/__tests__/definitions.test.js.map +1 -1
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +11 -0
- package/dist/agents/definitions.js.map +1 -1
- package/dist/cli/__tests__/doctor-invalid-config.test.d.ts +2 -0
- package/dist/cli/__tests__/doctor-invalid-config.test.d.ts.map +1 -0
- package/dist/cli/__tests__/doctor-invalid-config.test.js +52 -0
- package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -0
- package/dist/cli/__tests__/index.test.js +35 -3
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.d.ts +2 -0
- package/dist/cli/__tests__/launch-fallback.test.d.ts.map +1 -0
- package/dist/cli/__tests__/launch-fallback.test.js +60 -0
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -0
- package/dist/cli/__tests__/resume.test.d.ts +2 -0
- package/dist/cli/__tests__/resume.test.d.ts.map +1 -0
- package/dist/cli/__tests__/resume.test.js +78 -0
- package/dist/cli/__tests__/resume.test.js.map +1 -0
- package/dist/cli/__tests__/session-search-help.test.d.ts +2 -0
- package/dist/cli/__tests__/session-search-help.test.d.ts.map +1 -0
- package/dist/cli/__tests__/session-search-help.test.js +36 -0
- package/dist/cli/__tests__/session-search-help.test.js.map +1 -0
- package/dist/cli/__tests__/session-search.test.d.ts +2 -0
- package/dist/cli/__tests__/session-search.test.d.ts.map +1 -0
- package/dist/cli/__tests__/session-search.test.js +77 -0
- package/dist/cli/__tests__/session-search.test.js.map +1 -0
- package/dist/cli/__tests__/setup-prompts-overwrite.test.js +2 -0
- package/dist/cli/__tests__/setup-prompts-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/team-decompose.test.js +41 -15
- package/dist/cli/__tests__/team-decompose.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +208 -3
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +26 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +4 -3
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +73 -27
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/session-search.d.ts +8 -0
- package/dist/cli/session-search.d.ts.map +1 -0
- package/dist/cli/session-search.js +133 -0
- package/dist/cli/session-search.js.map +1 -0
- package/dist/cli/team.d.ts +13 -12
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +123 -39
- package/dist/cli/team.js.map +1 -1
- package/dist/hooks/__tests__/agents-overlay.test.js +33 -1
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +219 -0
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +1 -0
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +64 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-modules.test.js +7 -0
- package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +2 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +420 -5
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +95 -0
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +3 -0
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/tmux-hook-engine.test.js +39 -1
- package/dist/hooks/__tests__/tmux-hook-engine.test.js.map +1 -1
- package/dist/hooks/agents-overlay.d.ts +6 -1
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +45 -4
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/mcp/team-server.js +1 -1
- package/dist/mcp/team-server.js.map +1 -1
- package/dist/session-history/__tests__/search.test.d.ts +2 -0
- package/dist/session-history/__tests__/search.test.d.ts.map +1 -0
- package/dist/session-history/__tests__/search.test.js +150 -0
- package/dist/session-history/__tests__/search.test.js.map +1 -0
- package/dist/session-history/search.d.ts +31 -0
- package/dist/session-history/search.d.ts.map +1 -0
- package/dist/session-history/search.js +326 -0
- package/dist/session-history/search.js.map +1 -0
- package/dist/team/__tests__/allocation-policy.test.d.ts +2 -0
- package/dist/team/__tests__/allocation-policy.test.d.ts.map +1 -0
- package/dist/team/__tests__/allocation-policy.test.js +39 -0
- package/dist/team/__tests__/allocation-policy.test.js.map +1 -0
- package/dist/team/__tests__/api-interop.test.js +140 -4
- package/dist/team/__tests__/api-interop.test.js.map +1 -1
- package/dist/team/__tests__/followup-planner.test.js +12 -0
- package/dist/team/__tests__/followup-planner.test.js.map +1 -1
- package/dist/team/__tests__/idle-nudge.test.js +6 -1
- package/dist/team/__tests__/idle-nudge.test.js.map +1 -1
- package/dist/team/__tests__/rebalance-policy.test.d.ts +2 -0
- package/dist/team/__tests__/rebalance-policy.test.d.ts.map +1 -0
- package/dist/team/__tests__/rebalance-policy.test.js +125 -0
- package/dist/team/__tests__/rebalance-policy.test.js.map +1 -0
- package/dist/team/__tests__/runtime.test.js +315 -12
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +20 -1
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/team-ops-contract.test.js +1 -0
- package/dist/team/__tests__/team-ops-contract.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +20 -3
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/allocation-policy.d.ts +23 -0
- package/dist/team/allocation-policy.d.ts.map +1 -0
- package/dist/team/allocation-policy.js +71 -0
- package/dist/team/allocation-policy.js.map +1 -0
- package/dist/team/api-interop.d.ts +1 -1
- package/dist/team/api-interop.d.ts.map +1 -1
- package/dist/team/api-interop.js +159 -0
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/idle-nudge.js +1 -1
- package/dist/team/idle-nudge.js.map +1 -1
- package/dist/team/rebalance-policy.d.ts +19 -0
- package/dist/team/rebalance-policy.d.ts.map +1 -0
- package/dist/team/rebalance-policy.js +48 -0
- package/dist/team/rebalance-policy.js.map +1 -0
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +132 -17
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/state/types.d.ts +3 -0
- package/dist/team/state/types.d.ts.map +1 -1
- package/dist/team/state/types.js.map +1 -1
- package/dist/team/state.d.ts +8 -0
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +28 -12
- package/dist/team/state.js.map +1 -1
- package/dist/team/team-ops.d.ts +2 -1
- package/dist/team/team-ops.d.ts.map +1 -1
- package/dist/team/team-ops.js +1 -0
- package/dist/team/team-ops.js.map +1 -1
- package/dist/team/tmux-session.d.ts +5 -4
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +5 -67
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts +1 -0
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +9 -2
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/package.json +2 -1
- package/prompts/team-executor.md +57 -0
- package/prompts/team-orchestrator.md +8 -0
- package/scripts/notify-fallback-watcher.js +295 -1
- package/scripts/notify-hook/auto-nudge.js +20 -4
- package/scripts/notify-hook/team-dispatch.js +11 -58
- package/scripts/notify-hook/team-leader-nudge.js +59 -12
- package/scripts/notify-hook/team-tmux-guard.js +28 -11
- package/scripts/notify-hook/team-worker.js +3 -1
- package/scripts/notify-hook/tmux-injection.js +12 -13
- package/scripts/tmux-hook-engine.js +56 -0
- package/skills/team/SKILL.md +14 -0
- package/templates/catalog-manifest.json +5 -0
- package/dist/rtk/__tests__/index.test.d.ts +0 -2
- package/dist/rtk/__tests__/index.test.d.ts.map +0 -1
- package/dist/rtk/__tests__/index.test.js +0 -104
- package/dist/rtk/__tests__/index.test.js.map +0 -1
- package/dist/rtk/index.d.ts +0 -130
- package/dist/rtk/index.d.ts.map +0 -1
- package/dist/rtk/index.js +0 -257
- package/dist/rtk/index.js.map +0 -1
package/dist/team/runtime.js
CHANGED
|
@@ -4,9 +4,9 @@ import { readdir, readFile } from 'fs/promises';
|
|
|
4
4
|
import { performance } from 'perf_hooks';
|
|
5
5
|
import { spawn } from 'child_process';
|
|
6
6
|
import { sanitizeTeamName, isTmuxAvailable, createTeamSession, buildWorkerProcessLaunchSpec, resolveTeamWorkerCli, resolveTeamWorkerCliPlan, resolveTeamWorkerLaunchMode, waitForWorkerReady, dismissTrustPromptIfPresent, sleepFractionalSeconds, sendToWorker, sendToLeaderPane, sendToWorkerStdin, isWorkerAlive, getWorkerPanePid, killWorkerByPaneIdAsync, teardownWorkerPanes, unregisterResizeHook, destroyTeamSession, listTeamSessions, } from './tmux-session.js';
|
|
7
|
-
import { teamInit as initTeamState, DEFAULT_MAX_WORKERS, teamReadConfig as readTeamConfig, teamWriteWorkerIdentity as writeWorkerIdentity, teamReadWorkerHeartbeat as readWorkerHeartbeat, teamReadWorkerStatus as readWorkerStatus, teamWriteWorkerInbox as writeWorkerInbox, teamCreateTask as createStateTask, teamReadTask as readTask, teamListTasks as listTasks, teamReadManifest as readTeamManifestV2, teamNormalizePolicy as normalizeTeamPolicy, teamClaimTask as claimTask, teamReleaseTaskClaim as releaseTaskClaim, teamReclaimExpiredTaskClaim as reclaimExpiredTaskClaim, teamAppendEvent as appendTeamEvent, teamReadTaskApproval as readTaskApproval, teamListMailbox as listMailboxMessages, teamMarkMessageDelivered as markMessageDelivered, teamMarkMessageNotified as markMessageNotified, teamEnqueueDispatchRequest as enqueueDispatchRequest, teamMarkDispatchRequestNotified as markDispatchRequestNotified, teamTransitionDispatchRequest as transitionDispatchRequest, teamReadDispatchRequest as readDispatchRequest, teamCleanup as cleanupTeamState, teamSaveConfig as saveTeamConfig, teamWriteShutdownRequest as writeShutdownRequest, teamReadShutdownAck as readShutdownAck, teamReadMonitorSnapshot as readMonitorSnapshot, teamWriteMonitorSnapshot as writeMonitorSnapshot, teamReadPhase as readTeamPhaseState, teamWritePhase as writeTeamPhaseState, } from './team-ops.js';
|
|
7
|
+
import { teamInit as initTeamState, DEFAULT_MAX_WORKERS, teamReadConfig as readTeamConfig, teamWriteWorkerIdentity as writeWorkerIdentity, teamReadWorkerHeartbeat as readWorkerHeartbeat, teamReadWorkerStatus as readWorkerStatus, teamWriteWorkerInbox as writeWorkerInbox, teamCreateTask as createStateTask, teamReadTask as readTask, teamListTasks as listTasks, teamReadManifest as readTeamManifestV2, teamNormalizeGovernance as normalizeTeamGovernance, teamNormalizePolicy as normalizeTeamPolicy, teamClaimTask as claimTask, teamReleaseTaskClaim as releaseTaskClaim, teamReclaimExpiredTaskClaim as reclaimExpiredTaskClaim, teamAppendEvent as appendTeamEvent, teamReadTaskApproval as readTaskApproval, teamListMailbox as listMailboxMessages, teamMarkMessageDelivered as markMessageDelivered, teamMarkMessageNotified as markMessageNotified, teamEnqueueDispatchRequest as enqueueDispatchRequest, teamMarkDispatchRequestNotified as markDispatchRequestNotified, teamTransitionDispatchRequest as transitionDispatchRequest, teamReadDispatchRequest as readDispatchRequest, teamCleanup as cleanupTeamState, teamSaveConfig as saveTeamConfig, teamWriteShutdownRequest as writeShutdownRequest, teamReadShutdownAck as readShutdownAck, teamReadMonitorSnapshot as readMonitorSnapshot, teamWriteMonitorSnapshot as writeMonitorSnapshot, teamReadPhase as readTeamPhaseState, teamWritePhase as writeTeamPhaseState, } from './team-ops.js';
|
|
8
8
|
import { queueInboxInstruction, queueDirectMailboxMessage, queueBroadcastMailboxMessage, waitForDispatchReceipt, } from './mcp-comm.js';
|
|
9
|
-
import { generateWorkerOverlay, writeTeamWorkerInstructionsFile, removeTeamWorkerInstructionsFile, generateInitialInbox, generateTaskAssignmentInbox, generateShutdownInbox, generateTriggerMessage, generateMailboxTriggerMessage, writeWorkerRoleInstructionsFile, } from './worker-bootstrap.js';
|
|
9
|
+
import { generateWorkerOverlay, writeTeamWorkerInstructionsFile, removeTeamWorkerInstructionsFile, generateInitialInbox, generateTaskAssignmentInbox, generateShutdownInbox, generateTriggerMessage, generateMailboxTriggerMessage, generateLeaderMailboxTriggerMessage, writeWorkerRoleInstructionsFile, } from './worker-bootstrap.js';
|
|
10
10
|
import { loadRolePrompt } from './role-router.js';
|
|
11
11
|
import { codexPromptsDir } from '../utils/paths.js';
|
|
12
12
|
import { isLowComplexityAgentType, resolveTeamWorkerLaunchArgs, TEAM_LOW_COMPLEXITY_DEFAULT_MODEL, resolveTeamLowComplexityDefaultModel, parseTeamWorkerLaunchArgs, splitWorkerLaunchArgs, resolveAgentReasoningEffort, } from './model-contract.js';
|
|
@@ -14,6 +14,7 @@ import { resolveCanonicalTeamStateRoot } from './state-root.js';
|
|
|
14
14
|
import { inferPhaseTargetFromTaskCounts, reconcilePhaseStateForMonitor } from './phase-controller.js';
|
|
15
15
|
import { getTeamTmuxSessions } from '../notifications/tmux.js';
|
|
16
16
|
import { hasStructuredVerificationEvidence } from '../verification/verifier.js';
|
|
17
|
+
import { buildRebalanceDecisions } from './rebalance-policy.js';
|
|
17
18
|
import { readModeState, updateModeState } from '../modes/base.js';
|
|
18
19
|
import { ensureWorktree, planWorktreeTarget, rollbackProvisionedWorktrees, } from './worktree.js';
|
|
19
20
|
async function syncRootTeamModeStateOnTerminalPhase(teamName, phase, cwd) {
|
|
@@ -46,6 +47,46 @@ async function syncRootTeamModeStateOnTerminalPhase(teamName, phase, cwd) {
|
|
|
46
47
|
// Best-effort compatibility sync only.
|
|
47
48
|
}
|
|
48
49
|
}
|
|
50
|
+
async function syncLinkedRalphModeStateOnTerminalPhase(teamName, phase, cwd, nowIso = new Date().toISOString()) {
|
|
51
|
+
if (phase !== 'complete' && phase !== 'failed' && phase !== 'cancelled')
|
|
52
|
+
return;
|
|
53
|
+
try {
|
|
54
|
+
const [teamState, ralphState] = await Promise.all([
|
|
55
|
+
readModeState('team', cwd),
|
|
56
|
+
readModeState('ralph', cwd),
|
|
57
|
+
]);
|
|
58
|
+
if (!teamState || !ralphState)
|
|
59
|
+
return;
|
|
60
|
+
const stateTeamName = typeof teamState.team_name === 'string' ? teamState.team_name.trim() : '';
|
|
61
|
+
if (stateTeamName && stateTeamName !== teamName)
|
|
62
|
+
return;
|
|
63
|
+
if (teamState.linked_ralph !== true || ralphState.linked_team !== true)
|
|
64
|
+
return;
|
|
65
|
+
const terminalAt = typeof teamState.completed_at === 'string' && teamState.completed_at
|
|
66
|
+
? teamState.completed_at
|
|
67
|
+
: nowIso;
|
|
68
|
+
const alreadySynced = ralphState.active === false
|
|
69
|
+
&& ralphState.current_phase === phase
|
|
70
|
+
&& ralphState.linked_team_terminal_phase === phase
|
|
71
|
+
&& ralphState.linked_team_terminal_at === terminalAt
|
|
72
|
+
&& ralphState.completed_at === terminalAt;
|
|
73
|
+
if (alreadySynced)
|
|
74
|
+
return;
|
|
75
|
+
await updateModeState('ralph', {
|
|
76
|
+
active: false,
|
|
77
|
+
current_phase: phase,
|
|
78
|
+
linked_mode: 'team',
|
|
79
|
+
linked_team: true,
|
|
80
|
+
linked_team_terminal_phase: phase,
|
|
81
|
+
linked_team_terminal_at: terminalAt,
|
|
82
|
+
completed_at: terminalAt,
|
|
83
|
+
last_turn_at: nowIso,
|
|
84
|
+
}, cwd);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Best-effort compatibility sync only.
|
|
88
|
+
}
|
|
89
|
+
}
|
|
49
90
|
function collectProvisionedShutdownWorktrees(config) {
|
|
50
91
|
const seenWorktreePaths = new Set();
|
|
51
92
|
const worktrees = [];
|
|
@@ -96,6 +137,43 @@ function resolveWorkerReadyTimeoutMs(env) {
|
|
|
96
137
|
return parsed;
|
|
97
138
|
return 45_000;
|
|
98
139
|
}
|
|
140
|
+
function parseTeamWorkerContext(raw) {
|
|
141
|
+
if (typeof raw !== 'string' || raw.trim() === '')
|
|
142
|
+
return null;
|
|
143
|
+
const [teamName, workerName] = raw.trim().split('/');
|
|
144
|
+
if (!teamName || !workerName)
|
|
145
|
+
return null;
|
|
146
|
+
return { teamName, workerName };
|
|
147
|
+
}
|
|
148
|
+
function resolveManifestLookupCwds(cwd) {
|
|
149
|
+
const candidates = new Set([resolve(cwd)]);
|
|
150
|
+
const leaderCwd = process.env[TEAM_LEADER_CWD_ENV];
|
|
151
|
+
if (typeof leaderCwd === 'string' && leaderCwd.trim() !== '') {
|
|
152
|
+
candidates.add(resolve(leaderCwd));
|
|
153
|
+
}
|
|
154
|
+
const teamStateRoot = process.env[TEAM_STATE_ROOT_ENV];
|
|
155
|
+
if (typeof teamStateRoot === 'string' && teamStateRoot.trim() !== '') {
|
|
156
|
+
candidates.add(resolve(teamStateRoot, '..', '..'));
|
|
157
|
+
}
|
|
158
|
+
return [...candidates];
|
|
159
|
+
}
|
|
160
|
+
function resolveGovernancePolicy(governance, legacyPolicy) {
|
|
161
|
+
return normalizeTeamGovernance(governance, legacyPolicy);
|
|
162
|
+
}
|
|
163
|
+
async function assertNestedTeamAllowed(cwd) {
|
|
164
|
+
const workerContext = parseTeamWorkerContext(process.env.OMX_TEAM_WORKER);
|
|
165
|
+
if (!workerContext)
|
|
166
|
+
return;
|
|
167
|
+
for (const candidateCwd of resolveManifestLookupCwds(cwd)) {
|
|
168
|
+
const manifest = await readTeamManifestV2(workerContext.teamName, candidateCwd);
|
|
169
|
+
const governance = resolveGovernancePolicy(manifest?.governance, manifest?.policy);
|
|
170
|
+
if (governance.nested_teams_allowed)
|
|
171
|
+
return;
|
|
172
|
+
if (manifest)
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
throw new Error('nested_team_disallowed');
|
|
176
|
+
}
|
|
99
177
|
async function readWorkerStartupEvidence(teamName, workerName, cwd) {
|
|
100
178
|
const status = await readWorkerStatus(teamName, workerName, cwd);
|
|
101
179
|
if (typeof status.current_task_id === 'string' && status.current_task_id.trim() !== '') {
|
|
@@ -360,9 +438,8 @@ function resolveEffectiveWorkerCliForStartupLog(resolvedLaunchArgs, env) {
|
|
|
360
438
|
* Start a new team: init state, create tmux session, bootstrap workers.
|
|
361
439
|
*/
|
|
362
440
|
export async function startTeam(teamName, task, agentType, workerCount, tasks, cwd, options = {}) {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
441
|
+
const leaderCwd = resolve(cwd);
|
|
442
|
+
await assertNestedTeamAllowed(leaderCwd);
|
|
366
443
|
const workerLaunchMode = resolveTeamWorkerLaunchMode(process.env);
|
|
367
444
|
const displayMode = workerLaunchMode === 'interactive' ? 'split_pane' : 'auto';
|
|
368
445
|
if (workerLaunchMode === 'interactive') {
|
|
@@ -373,7 +450,6 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
|
|
|
373
450
|
throw new Error('Team mode requires running inside tmux current leader pane');
|
|
374
451
|
}
|
|
375
452
|
}
|
|
376
|
-
const leaderCwd = resolve(cwd);
|
|
377
453
|
const sanitized = sanitizeTeamName(teamName);
|
|
378
454
|
const teamStateRoot = resolveCanonicalTeamStateRoot(leaderCwd);
|
|
379
455
|
const activeWorktreeMode = options.worktreeMode?.enabled
|
|
@@ -802,7 +878,7 @@ export async function monitorTeam(teamName, cwd) {
|
|
|
802
878
|
if (reclaimed.ok && reclaimed.reclaimed)
|
|
803
879
|
reclaimedTaskIds.push(task.id);
|
|
804
880
|
}
|
|
805
|
-
|
|
881
|
+
let taskView = reclaimedTaskIds.length > 0 ? await listTasks(sanitized, cwd) : allTasks;
|
|
806
882
|
const taskById = new Map(taskView.map((task) => [task.id, task]));
|
|
807
883
|
const inProgressByOwner = new Map();
|
|
808
884
|
for (const task of taskView) {
|
|
@@ -863,6 +939,39 @@ export async function monitorTeam(teamName, cwd) {
|
|
|
863
939
|
recommendations.push(`Send reminder to non-reporting ${w.name}`);
|
|
864
940
|
}
|
|
865
941
|
}
|
|
942
|
+
for (const taskId of reclaimedTaskIds) {
|
|
943
|
+
recommendations.push(`Reclaimed expired claim for task-${taskId}`);
|
|
944
|
+
}
|
|
945
|
+
const rebalanceDecisions = buildRebalanceDecisions({
|
|
946
|
+
tasks: taskView,
|
|
947
|
+
workers: workers.map((worker) => ({
|
|
948
|
+
name: worker.name,
|
|
949
|
+
role: config.workers.find((entry) => entry.name === worker.name)?.role,
|
|
950
|
+
alive: worker.alive,
|
|
951
|
+
status: worker.status,
|
|
952
|
+
})),
|
|
953
|
+
reclaimedTaskIds,
|
|
954
|
+
});
|
|
955
|
+
let assignedDuringMonitor = false;
|
|
956
|
+
for (const decision of rebalanceDecisions) {
|
|
957
|
+
if (decision.type === 'assign' && decision.taskId && decision.workerName) {
|
|
958
|
+
try {
|
|
959
|
+
await assignTask(sanitized, decision.workerName, decision.taskId, cwd);
|
|
960
|
+
recommendations.push(`Assigned task-${decision.taskId} to ${decision.workerName}: ${decision.reason}`);
|
|
961
|
+
assignedDuringMonitor = true;
|
|
962
|
+
}
|
|
963
|
+
catch (error) {
|
|
964
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
965
|
+
recommendations.push(`Unable to assign task-${decision.taskId} to ${decision.workerName}: ${message}`);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
else {
|
|
969
|
+
recommendations.push(decision.reason);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
if (assignedDuringMonitor) {
|
|
973
|
+
taskView = await listTasks(sanitized, cwd);
|
|
974
|
+
}
|
|
866
975
|
// Count tasks
|
|
867
976
|
const taskCounts = {
|
|
868
977
|
total: taskView.length,
|
|
@@ -895,9 +1004,7 @@ export async function monitorTeam(teamName, cwd) {
|
|
|
895
1004
|
await writeTeamPhaseState(sanitized, phaseState, cwd);
|
|
896
1005
|
const phase = phaseState.current_phase;
|
|
897
1006
|
await syncRootTeamModeStateOnTerminalPhase(sanitized, phase, cwd);
|
|
898
|
-
|
|
899
|
-
recommendations.push(`Reclaimed expired claim for task-${taskId}`);
|
|
900
|
-
}
|
|
1007
|
+
await syncLinkedRalphModeStateOnTerminalPhase(sanitized, phase, cwd);
|
|
901
1008
|
if (deadWorkerStall) {
|
|
902
1009
|
recommendations.push('All workers are dead while work remains; mark the team failed or restart with fresh workers.');
|
|
903
1010
|
}
|
|
@@ -969,10 +1076,11 @@ export async function assignTask(teamName, workerName, taskId, cwd) {
|
|
|
969
1076
|
if (!task)
|
|
970
1077
|
throw new Error(`Task ${taskId} not found`);
|
|
971
1078
|
const manifest = await readTeamManifestV2(sanitized, cwd);
|
|
972
|
-
|
|
1079
|
+
const governance = resolveGovernancePolicy(manifest?.governance, manifest?.policy);
|
|
1080
|
+
if (governance.delegation_only && workerName === 'leader-fixed') {
|
|
973
1081
|
throw new Error('delegation_only_violation');
|
|
974
1082
|
}
|
|
975
|
-
if (
|
|
1083
|
+
if (governance.plan_approval_required && task.requires_code_change === true) {
|
|
976
1084
|
const approved = await isTaskApprovedForExecution(sanitized, taskId, cwd);
|
|
977
1085
|
if (!approved) {
|
|
978
1086
|
throw new Error('plan_approval_required');
|
|
@@ -1073,6 +1181,8 @@ export async function shutdownTeam(teamName, cwd, options = {}) {
|
|
|
1073
1181
|
restoreTeamModelInstructionsFile(sanitized);
|
|
1074
1182
|
return;
|
|
1075
1183
|
}
|
|
1184
|
+
const manifest = await readTeamManifestV2(sanitized, cwd);
|
|
1185
|
+
const governance = resolveGovernancePolicy(manifest?.governance, manifest?.policy);
|
|
1076
1186
|
if (!force) {
|
|
1077
1187
|
const allTasks = await listTasks(sanitized, cwd);
|
|
1078
1188
|
const gate = {
|
|
@@ -1084,11 +1194,12 @@ export async function shutdownTeam(teamName, cwd, options = {}) {
|
|
|
1084
1194
|
failed: allTasks.filter((t) => t.status === 'failed').length,
|
|
1085
1195
|
allowed: false,
|
|
1086
1196
|
};
|
|
1087
|
-
gate.allowed =
|
|
1197
|
+
gate.allowed = governance.cleanup_requires_all_workers_inactive !== true
|
|
1198
|
+
|| (gate.pending === 0 && gate.blocked === 0 && gate.in_progress === 0 && gate.failed === 0);
|
|
1088
1199
|
await appendTeamEvent(sanitized, {
|
|
1089
1200
|
type: 'shutdown_gate',
|
|
1090
1201
|
worker: 'leader-fixed',
|
|
1091
|
-
reason: `allowed=${gate.allowed} total=${gate.total} pending=${gate.pending} blocked=${gate.blocked} in_progress=${gate.in_progress} completed=${gate.completed} failed=${gate.failed}${ralph ? ' policy=ralph' : ''}`,
|
|
1202
|
+
reason: `allowed=${gate.allowed} total=${gate.total} pending=${gate.pending} blocked=${gate.blocked} in_progress=${gate.in_progress} completed=${gate.completed} failed=${gate.failed} cleanup_requires_all_workers_inactive=${governance.cleanup_requires_all_workers_inactive}${ralph ? ' policy=ralph' : ''}`,
|
|
1092
1203
|
}, cwd).catch(() => { });
|
|
1093
1204
|
if (!gate.allowed) {
|
|
1094
1205
|
const hasActiveWork = gate.pending > 0 || gate.blocked > 0 || gate.in_progress > 0;
|
|
@@ -1114,7 +1225,6 @@ export async function shutdownTeam(teamName, cwd, options = {}) {
|
|
|
1114
1225
|
}, cwd).catch(() => { });
|
|
1115
1226
|
}
|
|
1116
1227
|
const sessionName = config.tmux_session;
|
|
1117
|
-
const manifest = await readTeamManifestV2(sanitized, cwd);
|
|
1118
1228
|
const dispatchPolicy = resolveDispatchPolicy(manifest?.policy, config.worker_launch_mode);
|
|
1119
1229
|
const shutdownRequestTimes = new Map();
|
|
1120
1230
|
// 1. Send shutdown inbox to each worker
|
|
@@ -1209,6 +1319,9 @@ export async function shutdownTeam(teamName, cwd, options = {}) {
|
|
|
1209
1319
|
leaderPaneId,
|
|
1210
1320
|
hudPaneId,
|
|
1211
1321
|
});
|
|
1322
|
+
if (hudPaneId) {
|
|
1323
|
+
await killWorkerByPaneIdAsync(hudPaneId, leaderPaneId ?? undefined);
|
|
1324
|
+
}
|
|
1212
1325
|
// 4. Destroy tmux session
|
|
1213
1326
|
if (!sessionName.includes(':')) {
|
|
1214
1327
|
try {
|
|
@@ -1250,6 +1363,7 @@ export async function shutdownTeam(teamName, cwd, options = {}) {
|
|
|
1250
1363
|
worker: 'leader-fixed',
|
|
1251
1364
|
reason: `total=${finalTasks.length} completed=${completed} failed=${failed} pending=${pending} force=${force}`,
|
|
1252
1365
|
}, cwd).catch(() => { });
|
|
1366
|
+
await syncLinkedRalphModeStateOnTerminalPhase(sanitized, 'cancelled', cwd);
|
|
1253
1367
|
}
|
|
1254
1368
|
const cleanupErrors = [];
|
|
1255
1369
|
const provisionedWorktrees = collectProvisionedShutdownWorktrees(config);
|
|
@@ -1331,7 +1445,8 @@ async function findActiveTeams(cwd, leaderSessionId) {
|
|
|
1331
1445
|
const teamName = e.name;
|
|
1332
1446
|
const cfg = await readTeamConfig(teamName, cwd);
|
|
1333
1447
|
const manifest = await readTeamManifestV2(teamName, cwd);
|
|
1334
|
-
|
|
1448
|
+
const governance = resolveGovernancePolicy(manifest?.governance, manifest?.policy);
|
|
1449
|
+
if (governance.one_team_per_leader_session === false)
|
|
1335
1450
|
continue;
|
|
1336
1451
|
const workerLaunchMode = cfg?.worker_launch_mode
|
|
1337
1452
|
?? manifest?.policy?.worker_launch_mode
|
|
@@ -1807,7 +1922,7 @@ export async function sendWorkerMessage(teamName, fromWorker, toWorker, body, cw
|
|
|
1807
1922
|
const manifest = await readTeamManifestV2(sanitized, cwd);
|
|
1808
1923
|
const dispatchPolicy = resolveDispatchPolicy(manifest?.policy, config.worker_launch_mode);
|
|
1809
1924
|
if (toWorker === 'leader-fixed') {
|
|
1810
|
-
const leaderTriggerMessage =
|
|
1925
|
+
const leaderTriggerMessage = generateLeaderMailboxTriggerMessage(sanitized, fromWorker);
|
|
1811
1926
|
const leaderTransportPreference = dispatchPolicy.dispatch_mode === 'transport_direct'
|
|
1812
1927
|
? 'transport_direct'
|
|
1813
1928
|
: 'hook_preferred_with_fallback';
|