oh-my-codex 0.15.2 → 0.15.3
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/Cargo.lock +5 -5
- package/Cargo.toml +1 -1
- package/dist/agents/__tests__/native-config.test.js +33 -0
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/catalog/__tests__/plugin-bundle-ssot.test.js +9 -1
- package/dist/catalog/__tests__/plugin-bundle-ssot.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-context-window-warning.test.d.ts +2 -0
- package/dist/cli/__tests__/doctor-context-window-warning.test.d.ts.map +1 -0
- package/dist/cli/__tests__/doctor-context-window-warning.test.js +122 -0
- package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js +2 -2
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/exec.test.js +1 -0
- package/dist/cli/__tests__/exec.test.js.map +1 -1
- package/dist/cli/__tests__/explore.test.js +40 -17
- package/dist/cli/__tests__/explore.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +141 -8
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/mcp-serve.test.js +27 -1
- package/dist/cli/__tests__/mcp-serve.test.js.map +1 -1
- package/dist/cli/__tests__/ralph.test.js +59 -1
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +2 -1
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +55 -10
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +46 -3
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +16 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +126 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-serve.d.ts +1 -0
- package/dist/cli/mcp-serve.d.ts.map +1 -1
- package/dist/cli/mcp-serve.js +8 -0
- package/dist/cli/mcp-serve.js.map +1 -1
- package/dist/cli/ralph.d.ts +2 -0
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +17 -1
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/team.d.ts +4 -0
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +47 -22
- package/dist/cli/team.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +27 -5
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/generator.d.ts +11 -2
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +114 -58
- package/dist/config/generator.js.map +1 -1
- package/dist/hooks/__tests__/agents-overlay.test.js +59 -0
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/anti-slop-workflow.test.js +109 -18
- package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +21 -0
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hud/__tests__/index.test.js +30 -14
- package/dist/hud/__tests__/index.test.js.map +1 -1
- package/dist/openclaw/__tests__/dispatcher.test.js +1 -1
- package/dist/openclaw/__tests__/dispatcher.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +398 -14
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/stages/team-exec.d.ts +8 -4
- package/dist/pipeline/stages/team-exec.d.ts.map +1 -1
- package/dist/pipeline/stages/team-exec.js +198 -13
- package/dist/pipeline/stages/team-exec.js.map +1 -1
- package/dist/planning/__tests__/artifacts.test.js +246 -1
- package/dist/planning/__tests__/artifacts.test.js.map +1 -1
- package/dist/planning/artifact-names.d.ts +13 -0
- package/dist/planning/artifact-names.d.ts.map +1 -0
- package/dist/planning/artifact-names.js +108 -0
- package/dist/planning/artifact-names.js.map +1 -0
- package/dist/planning/artifacts.d.ts +22 -1
- package/dist/planning/artifacts.d.ts.map +1 -1
- package/dist/planning/artifacts.js +165 -50
- package/dist/planning/artifacts.js.map +1 -1
- package/dist/ralph/__tests__/persistence.test.js +21 -1
- package/dist/ralph/__tests__/persistence.test.js.map +1 -1
- package/dist/ralph/persistence.d.ts.map +1 -1
- package/dist/ralph/persistence.js +6 -4
- package/dist/ralph/persistence.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +352 -2
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +85 -6
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +123 -0
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker-posttooluse.js +1 -1
- package/dist/scripts/notify-hook/team-worker-posttooluse.js.map +1 -1
- package/dist/scripts/notify-hook.js +1 -1
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/sync-plugin-mirror.d.ts +1 -0
- package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js +8 -2
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +41 -0
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/team/__tests__/api-interop.test.js +220 -0
- package/dist/team/__tests__/api-interop.test.js.map +1 -1
- package/dist/team/__tests__/model-contract.test.js +40 -9
- package/dist/team/__tests__/model-contract.test.js.map +1 -1
- package/dist/team/__tests__/repo-aware-decomposition.test.js +41 -0
- package/dist/team/__tests__/repo-aware-decomposition.test.js.map +1 -1
- package/dist/team/__tests__/runtime-cli.test.js +24 -0
- package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +446 -67
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +13 -0
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/team-identity.test.d.ts +2 -0
- package/dist/team/__tests__/team-identity.test.d.ts.map +1 -0
- package/dist/team/__tests__/team-identity.test.js +166 -0
- package/dist/team/__tests__/team-identity.test.js.map +1 -0
- package/dist/team/__tests__/tmux-session.test.js +55 -1
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +12 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/api-interop.d.ts +1 -0
- package/dist/team/api-interop.d.ts.map +1 -1
- package/dist/team/api-interop.js +159 -129
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/delivery-log.d.ts +1 -1
- package/dist/team/delivery-log.d.ts.map +1 -1
- package/dist/team/delivery-log.js.map +1 -1
- package/dist/team/repo-aware-decomposition.d.ts +3 -0
- package/dist/team/repo-aware-decomposition.d.ts.map +1 -1
- package/dist/team/repo-aware-decomposition.js +2 -0
- package/dist/team/repo-aware-decomposition.js.map +1 -1
- package/dist/team/runtime-cli.d.ts +32 -2
- package/dist/team/runtime-cli.d.ts.map +1 -1
- package/dist/team/runtime-cli.js +78 -26
- package/dist/team/runtime-cli.js.map +1 -1
- package/dist/team/runtime.d.ts +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +338 -35
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/state.d.ts +9 -0
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +21 -0
- package/dist/team/state.js.map +1 -1
- package/dist/team/team-identity.d.ts +26 -0
- package/dist/team/team-identity.d.ts.map +1 -0
- package/dist/team/team-identity.js +169 -0
- package/dist/team/team-identity.js.map +1 -0
- package/dist/team/tmux-session.d.ts +18 -0
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +61 -1
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts +2 -0
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +10 -1
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/skills/ai-slop-cleaner/SKILL.md +30 -5
- package/skills/ai-slop-cleaner/SKILL.md +30 -5
- package/src/scripts/__tests__/codex-native-hook.test.ts +398 -2
- package/src/scripts/codex-native-hook.ts +115 -5
- package/src/scripts/codex-native-pre-post.ts +121 -0
- package/src/scripts/notify-hook/team-worker-posttooluse.ts +1 -1
- package/src/scripts/notify-hook.ts +1 -1
- package/src/scripts/sync-plugin-mirror.ts +11 -2
package/dist/team/runtime.js
CHANGED
|
@@ -3,8 +3,8 @@ import { existsSync, appendFileSync, mkdirSync } from 'fs';
|
|
|
3
3
|
import { mkdir, readdir, readFile, writeFile } from 'fs/promises';
|
|
4
4
|
import { performance } from 'perf_hooks';
|
|
5
5
|
import { spawn, spawnSync } from 'child_process';
|
|
6
|
-
import { sanitizeTeamName, isTmuxAvailable, hasCurrentTmuxClientContext, createTeamSession, buildWorkerProcessLaunchSpec, resolveTeamWorkerCli, resolveTeamWorkerCliPlan, resolveTeamWorkerLaunchMode, waitForWorkerReady, waitForWorkerReadyAsync, dismissTrustPromptIfPresent, sendToWorker, sendToWorkerStdin, isWorkerAlive, isWorkerPaneOpen, getWorkerPanePid, killWorkerByPaneIdAsync, restoreStandaloneHudPane, teardownWorkerPanes, unregisterResizeHook, destroyTeamSession, listPaneIds, listTeamSessions, resolveSharedSessionShutdownTopology, } 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, teamUpdateTask as updateTask, 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, teamWriteWorkerStatus as writeWorkerStatus, } from './team-ops.js';
|
|
6
|
+
import { sanitizeTeamName, isTmuxAvailable, hasCurrentTmuxClientContext, createTeamSession, buildWorkerProcessLaunchSpec, resolveTeamWorkerCli, resolveTeamWorkerCliPlan, resolveTeamWorkerLaunchMode, waitForWorkerReady, waitForWorkerReadyAsync, dismissTrustPromptIfPresent, evaluateStartupDirectTriggerSafety, sendToWorker, sendToWorkerStdin, isWorkerAlive, isWorkerPaneOpen, getWorkerPanePid, killWorkerByPaneIdAsync, restoreStandaloneHudPane, teardownWorkerPanes, unregisterResizeHook, destroyTeamSession, listPaneIds, listTeamSessions, resolveSharedSessionShutdownTopology, } 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, teamUpdateTask as updateTask, 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, teamWriteWorkerStatus as writeWorkerStatus, writeAtomic, } from './team-ops.js';
|
|
8
8
|
import { queueInboxInstruction, queueDirectMailboxMessage, queueBroadcastMailboxMessage, waitForDispatchReceipt, } from './mcp-comm.js';
|
|
9
9
|
import { appendTeamDeliveryLogForCwd } from './delivery-log.js';
|
|
10
10
|
import { remapRepoAwareDecompositionMetadataToCreatedTasks, } from './repo-aware-decomposition.js';
|
|
@@ -16,6 +16,7 @@ import { codexPromptsDir } from '../utils/paths.js';
|
|
|
16
16
|
import { isTerminalPhase } from './orchestrator.js';
|
|
17
17
|
import { resolveTeamWorkerLaunchArgs, TEAM_LOW_COMPLEXITY_DEFAULT_MODEL, parseTeamWorkerLaunchArgs, splitWorkerLaunchArgs, resolveAgentDefaultModel, resolveAgentReasoningEffort, } from './model-contract.js';
|
|
18
18
|
import { resolveCanonicalTeamStateRoot } from './state-root.js';
|
|
19
|
+
import { buildInternalTeamName, resolveTeamIdentityScope, resolveTeamNameForCurrentContext } from './team-identity.js';
|
|
19
20
|
import { inferPhaseTargetFromTaskCounts, reconcilePhaseStateForMonitor } from './phase-controller.js';
|
|
20
21
|
import { getTeamTmuxSessions } from '../notifications/tmux.js';
|
|
21
22
|
import { hasStructuredVerificationEvidence } from '../verification/verifier.js';
|
|
@@ -188,6 +189,21 @@ async function logRuntimeDispatchOutcome(params) {
|
|
|
188
189
|
reason: outcome.reason,
|
|
189
190
|
});
|
|
190
191
|
}
|
|
192
|
+
async function logStartupTiming(params) {
|
|
193
|
+
const { cwd, teamName, workerName, event, paneId, elapsedMs, reason, requestId, transport } = params;
|
|
194
|
+
await appendTeamDeliveryLogForCwd(cwd, {
|
|
195
|
+
event: 'startup_timing',
|
|
196
|
+
source: 'team.runtime',
|
|
197
|
+
team: teamName,
|
|
198
|
+
to_worker: workerName,
|
|
199
|
+
startup_event: event,
|
|
200
|
+
pane_id: paneId,
|
|
201
|
+
elapsed_ms: typeof elapsedMs === 'number' ? Math.round(elapsedMs) : undefined,
|
|
202
|
+
reason,
|
|
203
|
+
request_id: requestId,
|
|
204
|
+
transport,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
191
207
|
function collectProvisionedShutdownWorktrees(config) {
|
|
192
208
|
const seenWorktreePaths = new Set();
|
|
193
209
|
const worktrees = [];
|
|
@@ -932,6 +948,40 @@ const WORKTREE_TRIGGER_STATE_ROOT = '$OMX_TEAM_STATE_ROOT';
|
|
|
932
948
|
const STARTUP_EVIDENCE_TIMEOUT_MS = 15_000;
|
|
933
949
|
const STARTUP_EVIDENCE_POLL_MS = 100;
|
|
934
950
|
const STARTUP_EVIDENCE_LAUNCH_TIMEOUT_MS = 45_000;
|
|
951
|
+
const STARTUP_TIMING_LOG_VERSION = 1;
|
|
952
|
+
function createStartupTimingRecorder(teamName, cwd) {
|
|
953
|
+
const startedAt = performance.now();
|
|
954
|
+
const events = [];
|
|
955
|
+
return {
|
|
956
|
+
mark: (phase, details = {}) => {
|
|
957
|
+
events.push({
|
|
958
|
+
phase,
|
|
959
|
+
at: new Date().toISOString(),
|
|
960
|
+
elapsed_ms: Math.round((performance.now() - startedAt) * 1000) / 1000,
|
|
961
|
+
...details,
|
|
962
|
+
});
|
|
963
|
+
},
|
|
964
|
+
flush: async () => {
|
|
965
|
+
const timingPath = join(cwd, '.omx', 'state', 'team', teamName, 'startup-timing.json');
|
|
966
|
+
await writeAtomic(timingPath, JSON.stringify({ schema_version: STARTUP_TIMING_LOG_VERSION, team_name: teamName, events }, null, 2)).catch(() => { });
|
|
967
|
+
for (const event of events) {
|
|
968
|
+
await appendTeamDeliveryLogForCwd(cwd, {
|
|
969
|
+
event: 'dispatch_result',
|
|
970
|
+
source: 'team.runtime.startup-timing',
|
|
971
|
+
team: teamName,
|
|
972
|
+
result: event.ok === false ? 'failed' : 'ok',
|
|
973
|
+
phase: event.phase,
|
|
974
|
+
elapsed_ms: event.elapsed_ms,
|
|
975
|
+
to_worker: event.worker,
|
|
976
|
+
pane_id: event.pane_id,
|
|
977
|
+
reason: event.reason,
|
|
978
|
+
transport: event.transport,
|
|
979
|
+
request_id: event.request_id,
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
},
|
|
983
|
+
};
|
|
984
|
+
}
|
|
935
985
|
const promptWorkerRegistry = new Map();
|
|
936
986
|
const previousModelInstructionsFileByTeam = new Map();
|
|
937
987
|
const PROMPT_WORKER_SIGTERM_WAIT_MS = 3_000;
|
|
@@ -1000,7 +1050,7 @@ function resolveGovernancePolicy(governance, legacyPolicy) {
|
|
|
1000
1050
|
return normalizeTeamGovernance(governance, legacyPolicy);
|
|
1001
1051
|
}
|
|
1002
1052
|
async function assertNestedTeamAllowed(cwd) {
|
|
1003
|
-
const workerContext = parseTeamWorkerContext(process.env.OMX_TEAM_WORKER);
|
|
1053
|
+
const workerContext = parseTeamWorkerContext(process.env.OMX_TEAM_INTERNAL_WORKER || process.env.OMX_TEAM_WORKER);
|
|
1004
1054
|
if (!workerContext)
|
|
1005
1055
|
return;
|
|
1006
1056
|
for (const candidateCwd of resolveManifestLookupCwds(cwd)) {
|
|
@@ -1077,6 +1127,22 @@ async function recordRecoverableStartupIssue(params) {
|
|
|
1077
1127
|
reason,
|
|
1078
1128
|
}, cwd).catch(() => { });
|
|
1079
1129
|
}
|
|
1130
|
+
async function recordPromptStartupWorkerStopped(params) {
|
|
1131
|
+
const { teamName, workerName, taskIds, reason, cwd } = params;
|
|
1132
|
+
const updatedAt = new Date().toISOString();
|
|
1133
|
+
await writeWorkerStatus(teamName, workerName, {
|
|
1134
|
+
state: 'failed',
|
|
1135
|
+
current_task_id: taskIds[0],
|
|
1136
|
+
reason,
|
|
1137
|
+
updated_at: updatedAt,
|
|
1138
|
+
}, cwd).catch(() => { });
|
|
1139
|
+
await appendTeamEvent(teamName, {
|
|
1140
|
+
type: 'worker_stopped',
|
|
1141
|
+
worker: workerName,
|
|
1142
|
+
task_id: taskIds[0],
|
|
1143
|
+
reason,
|
|
1144
|
+
}, cwd).catch(() => { });
|
|
1145
|
+
}
|
|
1080
1146
|
function setTeamModelInstructionsFile(teamName, filePath) {
|
|
1081
1147
|
if (!previousModelInstructionsFileByTeam.has(teamName)) {
|
|
1082
1148
|
previousModelInstructionsFileByTeam.set(teamName, process.env[MODEL_INSTRUCTIONS_FILE_ENV]);
|
|
@@ -1481,11 +1547,28 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
|
|
|
1481
1547
|
const leaderCwd = resolve(cwd);
|
|
1482
1548
|
await assertNestedTeamAllowed(leaderCwd);
|
|
1483
1549
|
const effectiveWorktreeMode = resolveEffectiveTeamWorktreeMode(leaderCwd, options.worktreeMode);
|
|
1484
|
-
const
|
|
1485
|
-
const leaderSessionId = await resolveLeaderSessionId(leaderCwd);
|
|
1486
|
-
await assertTeamStartupIsNonDestructive(sanitized, leaderCwd, leaderSessionId);
|
|
1550
|
+
const displayName = sanitizeTeamName(teamName);
|
|
1487
1551
|
const workerLaunchMode = resolveTeamWorkerLaunchMode(process.env);
|
|
1488
1552
|
const displayMode = workerLaunchMode === 'interactive' ? 'split_pane' : 'auto';
|
|
1553
|
+
const rawIdentityScope = resolveTeamIdentityScope(process.env);
|
|
1554
|
+
const resolvedLeaderSessionId = await resolveLeaderSessionId(leaderCwd);
|
|
1555
|
+
const identityScope = rawIdentityScope.source === 'run-id'
|
|
1556
|
+
? (resolvedLeaderSessionId
|
|
1557
|
+
? { ...rawIdentityScope, sessionId: resolvedLeaderSessionId, runId: '' }
|
|
1558
|
+
: {
|
|
1559
|
+
...rawIdentityScope,
|
|
1560
|
+
// Prompt-mode starts can run outside tmux/native session metadata. A fresh
|
|
1561
|
+
// random run id would give every start a new leader identity and bypass
|
|
1562
|
+
// one-active-team protection, so scope that fallback to the leader cwd.
|
|
1563
|
+
runId: `cwd:${leaderCwd}`,
|
|
1564
|
+
})
|
|
1565
|
+
: rawIdentityScope;
|
|
1566
|
+
const sanitized = buildInternalTeamName(displayName, identityScope);
|
|
1567
|
+
const leaderSessionId = identityScope.sessionId || identityScope.paneId || identityScope.tmuxTarget || identityScope.runId;
|
|
1568
|
+
await assertTeamStartupIsNonDestructive(sanitized, leaderCwd, leaderSessionId);
|
|
1569
|
+
if (displayName !== sanitized) {
|
|
1570
|
+
await assertTeamStartupIsNonDestructive(displayName, leaderCwd, leaderSessionId);
|
|
1571
|
+
}
|
|
1489
1572
|
if (workerLaunchMode === 'interactive') {
|
|
1490
1573
|
if (!isTmuxAvailable()) {
|
|
1491
1574
|
throw new Error('Team mode requires tmux. Install with: apt install tmux / brew install tmux');
|
|
@@ -1553,10 +1636,18 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
|
|
|
1553
1636
|
const skipWorkerReadyWait = shouldSkipWorkerReadyWait(process.env);
|
|
1554
1637
|
try {
|
|
1555
1638
|
// 3. Init state directory + config
|
|
1556
|
-
config = await initTeamState(sanitized, task, agentType, workerCount, leaderCwd, DEFAULT_MAX_WORKERS, {
|
|
1639
|
+
config = await initTeamState(sanitized, task, agentType, workerCount, leaderCwd, DEFAULT_MAX_WORKERS, {
|
|
1640
|
+
...process.env,
|
|
1641
|
+
OMX_SESSION_ID: leaderSessionId,
|
|
1642
|
+
OMX_TEAM_DISPLAY_MODE: displayMode,
|
|
1643
|
+
OMX_TEAM_WORKER_LAUNCH_MODE: workerLaunchMode,
|
|
1644
|
+
}, {
|
|
1557
1645
|
leader_cwd: leaderCwd,
|
|
1558
1646
|
team_state_root: teamStateRoot,
|
|
1559
1647
|
workspace_mode: workspaceMode,
|
|
1648
|
+
display_name: displayName,
|
|
1649
|
+
requested_name: displayName,
|
|
1650
|
+
identity_source: identityScope.source,
|
|
1560
1651
|
worktree_mode: effectiveWorktreeMode,
|
|
1561
1652
|
}, 'default');
|
|
1562
1653
|
if (!config) {
|
|
@@ -1565,6 +1656,9 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
|
|
|
1565
1656
|
config.leader_cwd = leaderCwd;
|
|
1566
1657
|
config.team_state_root = teamStateRoot;
|
|
1567
1658
|
config.workspace_mode = workspaceMode;
|
|
1659
|
+
config.display_name = displayName;
|
|
1660
|
+
config.requested_name = displayName;
|
|
1661
|
+
config.identity_source = identityScope.source;
|
|
1568
1662
|
config.worktree_mode = effectiveWorktreeMode;
|
|
1569
1663
|
// 4. Create tasks. Repo-aware DAG dependencies are symbolic until the
|
|
1570
1664
|
// state layer returns concrete task IDs, so create those tasks dependency
|
|
@@ -1661,6 +1755,7 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
|
|
|
1661
1755
|
rolePromptContent: rawRolePromptContent ?? undefined,
|
|
1662
1756
|
worktreeRootAgentsCanonical: Boolean(workerWorkspace.worktreePath),
|
|
1663
1757
|
taskHints: effectiveDecompositionMetadata?.task_hints,
|
|
1758
|
+
approvedContextSummary: effectiveDecompositionMetadata?.approved_context_summary,
|
|
1664
1759
|
});
|
|
1665
1760
|
const triggerDirective = buildTriggerDirective(workerName, sanitized, resolveInstructionStateRoot(workerWorkspace.worktreePath));
|
|
1666
1761
|
const trigger = triggerDirective.text;
|
|
@@ -1688,6 +1783,7 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
|
|
|
1688
1783
|
[TEAM_STATE_ROOT_ENV]: teamStateRoot,
|
|
1689
1784
|
[TEAM_LEADER_CWD_ENV]: leaderCwd,
|
|
1690
1785
|
[MODEL_INSTRUCTIONS_FILE_ENV]: plan.instructionsFilePath,
|
|
1786
|
+
OMX_TEAM_DISPLAY_NAME: displayName,
|
|
1691
1787
|
};
|
|
1692
1788
|
if (plan.workerWorkspace.worktreePath) {
|
|
1693
1789
|
env.OMX_TEAM_WORKTREE_PATH = plan.workerWorkspace.worktreePath;
|
|
@@ -1756,6 +1852,7 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
|
|
|
1756
1852
|
cleanup: options.cleanupLaunchOrphanedMcpProcesses,
|
|
1757
1853
|
writeWarning: options.writeCleanupWarning,
|
|
1758
1854
|
});
|
|
1855
|
+
const startupTiming = createStartupTimingRecorder(sanitized, leaderCwd);
|
|
1759
1856
|
if (workerLaunchMode === 'interactive') {
|
|
1760
1857
|
const createdSession = createTeamSession(sanitized, workerCount, leaderCwd, sharedWorkerLaunchArgs, workerStartups);
|
|
1761
1858
|
sessionName = createdSession.name;
|
|
@@ -1763,6 +1860,9 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
|
|
|
1763
1860
|
createdWorkerPaneIds.push(...createdSession.workerPaneIds);
|
|
1764
1861
|
createdLeaderPaneId = createdSession.leaderPaneId;
|
|
1765
1862
|
applyCreatedInteractiveSessionToConfig(config, createdSession, workerPaneIds);
|
|
1863
|
+
for (const [index, paneId] of createdSession.workerPaneIds.entries()) {
|
|
1864
|
+
startupTiming.mark('split_returned', { worker: `worker-${index + 1}`, pane_id: paneId });
|
|
1865
|
+
}
|
|
1766
1866
|
}
|
|
1767
1867
|
else {
|
|
1768
1868
|
config.tmux_session = `prompt-${sanitized}`;
|
|
@@ -1787,6 +1887,7 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
|
|
|
1787
1887
|
throw new Error(`missing bootstrap plan for worker-${i}`);
|
|
1788
1888
|
}
|
|
1789
1889
|
await materializeWorkerStartupState(bootstrapPlan, i, workerPaneIds[i - 1]);
|
|
1890
|
+
startupTiming.mark('identity_inbox_written', { worker: bootstrapPlan.workerName, pane_id: workerPaneIds[i - 1] });
|
|
1790
1891
|
}
|
|
1791
1892
|
await saveTeamConfig(config, leaderCwd);
|
|
1792
1893
|
// 7. Start all safe per-worker readiness/dispatch attempts concurrently.
|
|
@@ -1812,6 +1913,7 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
|
|
|
1812
1913
|
const trigger = bootstrapPlan.trigger;
|
|
1813
1914
|
const triggerIntent = bootstrapPlan.triggerIntent;
|
|
1814
1915
|
const initialPrompt = bootstrapPlan.initialPrompt;
|
|
1916
|
+
const startupStartedAt = performance.now();
|
|
1815
1917
|
const taskRoles = workerTasks
|
|
1816
1918
|
.map((task) => task.role)
|
|
1817
1919
|
.filter((role) => Boolean(role));
|
|
@@ -1819,8 +1921,26 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
|
|
|
1819
1921
|
if (uniqueTaskRoles.length > 1) {
|
|
1820
1922
|
console.log(`[omx:team] ${workerName}: mixed task roles [${uniqueTaskRoles.join(', ')}], falling back to ${agentType}`);
|
|
1821
1923
|
}
|
|
1822
|
-
|
|
1924
|
+
const startupDirectOutcome = workerLaunchMode === 'interactive' && !initialPrompt
|
|
1925
|
+
? await attemptStartupDirectTrigger({
|
|
1926
|
+
teamName: sanitized,
|
|
1927
|
+
config: config,
|
|
1928
|
+
workerName,
|
|
1929
|
+
workerIndex,
|
|
1930
|
+
paneId,
|
|
1931
|
+
workerCli: workerCliPlan[workerIndex - 1],
|
|
1932
|
+
inbox,
|
|
1933
|
+
triggerMessage: trigger,
|
|
1934
|
+
intent: triggerIntent,
|
|
1935
|
+
taskIds: workerTasks.map((task) => task.id),
|
|
1936
|
+
cwd: leaderCwd,
|
|
1937
|
+
timing: startupTiming,
|
|
1938
|
+
})
|
|
1939
|
+
: null;
|
|
1940
|
+
if (workerLaunchMode === 'interactive' && !skipWorkerReadyWait && !initialPrompt && !startupDirectOutcome?.ok) {
|
|
1941
|
+
startupTiming.mark('ready_wait_start', { worker: workerName, pane_id: paneId });
|
|
1823
1942
|
const ready = await waitForWorkerReadyAsync(sessionName, workerIndex, workerReadyTimeoutMs, paneId);
|
|
1943
|
+
startupTiming.mark('ready_wait_end', { worker: workerName, pane_id: paneId, ok: ready });
|
|
1824
1944
|
if (!ready) {
|
|
1825
1945
|
const workerAlive = isWorkerPaneOpen(sessionName, workerIndex, paneId);
|
|
1826
1946
|
if (workerAlive) {
|
|
@@ -1841,10 +1961,11 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
|
|
|
1841
1961
|
};
|
|
1842
1962
|
}
|
|
1843
1963
|
}
|
|
1964
|
+
const startupReadyPromptObserved = workerLaunchMode === 'interactive' && !skipWorkerReadyWait && !initialPrompt;
|
|
1844
1965
|
let dispatchOutcome = initialPrompt
|
|
1845
1966
|
? { ok: true, transport: 'none', reason: 'startup_prompt_delivered_at_launch' }
|
|
1846
|
-
: { ok: false, transport: 'none', reason: 'not_attempted' };
|
|
1847
|
-
if (!initialPrompt) {
|
|
1967
|
+
: (startupDirectOutcome ?? { ok: false, transport: 'none', reason: 'not_attempted' });
|
|
1968
|
+
if (!initialPrompt && !startupDirectOutcome?.ok) {
|
|
1848
1969
|
for (let attempt = 1; attempt <= startupDispatchRetries; attempt++) {
|
|
1849
1970
|
dispatchOutcome = await dispatchCriticalInboxInstruction({
|
|
1850
1971
|
teamName: sanitized,
|
|
@@ -1861,7 +1982,20 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
|
|
|
1861
1982
|
inboxCorrelationKey: `startup:${workerName}`,
|
|
1862
1983
|
requireWorkerStartupEvidence: true,
|
|
1863
1984
|
startupEvidenceTimeoutMs: workerStartupEvidenceTimeoutMs,
|
|
1985
|
+
startupReadyPromptObserved,
|
|
1986
|
+
startupTiming,
|
|
1864
1987
|
});
|
|
1988
|
+
await logStartupTiming({
|
|
1989
|
+
cwd: leaderCwd,
|
|
1990
|
+
teamName: sanitized,
|
|
1991
|
+
workerName,
|
|
1992
|
+
event: dispatchOutcome.ok ? 'startup_evidence' : 'startup_attempt_failed',
|
|
1993
|
+
paneId,
|
|
1994
|
+
elapsedMs: performance.now() - startupStartedAt,
|
|
1995
|
+
reason: dispatchOutcome.reason,
|
|
1996
|
+
requestId: dispatchOutcome.request_id,
|
|
1997
|
+
transport: dispatchOutcome.transport,
|
|
1998
|
+
}).catch(() => { });
|
|
1865
1999
|
if (dispatchOutcome.ok)
|
|
1866
2000
|
break;
|
|
1867
2001
|
if (attempt < startupDispatchRetries) {
|
|
@@ -1895,6 +2029,16 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
|
|
|
1895
2029
|
});
|
|
1896
2030
|
return { ok: true, workerIndex, workerName };
|
|
1897
2031
|
}
|
|
2032
|
+
if (workerLaunchMode === 'prompt' && !workerAlive) {
|
|
2033
|
+
await recordPromptStartupWorkerStopped({
|
|
2034
|
+
teamName: sanitized,
|
|
2035
|
+
workerName,
|
|
2036
|
+
taskIds: workerTasks.map((task) => task.id),
|
|
2037
|
+
reason: dispatchOutcome.reason,
|
|
2038
|
+
cwd: leaderCwd,
|
|
2039
|
+
});
|
|
2040
|
+
return { ok: true, workerIndex, workerName };
|
|
2041
|
+
}
|
|
1898
2042
|
return {
|
|
1899
2043
|
ok: false,
|
|
1900
2044
|
workerIndex,
|
|
@@ -1921,6 +2065,7 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
|
|
|
1921
2065
|
throw firstStartupError.error;
|
|
1922
2066
|
}
|
|
1923
2067
|
await saveTeamConfig(config, leaderCwd);
|
|
2068
|
+
await startupTiming.flush();
|
|
1924
2069
|
return {
|
|
1925
2070
|
teamName: sanitized,
|
|
1926
2071
|
sanitizedName: sanitized,
|
|
@@ -2046,7 +2191,7 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
|
|
|
2046
2191
|
*/
|
|
2047
2192
|
export async function monitorTeam(teamName, cwd) {
|
|
2048
2193
|
const monitorStartMs = performance.now();
|
|
2049
|
-
const sanitized =
|
|
2194
|
+
const sanitized = resolveTeamNameForCurrentContext(teamName, cwd);
|
|
2050
2195
|
const config = await readTeamConfig(sanitized, cwd);
|
|
2051
2196
|
if (!config)
|
|
2052
2197
|
return null;
|
|
@@ -2361,6 +2506,25 @@ export async function assignTask(teamName, workerName, taskId, cwd) {
|
|
|
2361
2506
|
export async function reassignTask(teamName, taskId, _fromWorker, toWorker, cwd) {
|
|
2362
2507
|
await assignTask(teamName, toWorker, taskId, cwd);
|
|
2363
2508
|
}
|
|
2509
|
+
function resolveCommitHygieneArtifactTeamNames(config, internalTeamName) {
|
|
2510
|
+
const names = [];
|
|
2511
|
+
for (const value of [config.requested_name, config.display_name, internalTeamName]) {
|
|
2512
|
+
if (typeof value !== 'string' || value.trim() === '')
|
|
2513
|
+
continue;
|
|
2514
|
+
try {
|
|
2515
|
+
const sanitized = sanitizeTeamName(value);
|
|
2516
|
+
if (!names.includes(sanitized))
|
|
2517
|
+
names.push(sanitized);
|
|
2518
|
+
}
|
|
2519
|
+
catch {
|
|
2520
|
+
// Persisted display/request names are best-effort aliases. If an older
|
|
2521
|
+
// state file contains an invalid value, fall back to the internal name.
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
if (!names.includes(internalTeamName))
|
|
2525
|
+
names.push(internalTeamName);
|
|
2526
|
+
return names;
|
|
2527
|
+
}
|
|
2364
2528
|
/**
|
|
2365
2529
|
* Graceful shutdown: send shutdown inbox to all workers, wait, force kill, cleanup.
|
|
2366
2530
|
*/
|
|
@@ -2368,7 +2532,7 @@ export async function shutdownTeam(teamName, cwd, options = {}) {
|
|
|
2368
2532
|
const force = options.force === true;
|
|
2369
2533
|
const confirmIssues = options.confirmIssues === true;
|
|
2370
2534
|
let skipWorkerAcks = false;
|
|
2371
|
-
const sanitized =
|
|
2535
|
+
const sanitized = resolveTeamNameForCurrentContext(teamName, cwd);
|
|
2372
2536
|
const config = await readTeamConfig(sanitized, cwd);
|
|
2373
2537
|
if (!config) {
|
|
2374
2538
|
// No config -- just try to kill tmux session and clean up
|
|
@@ -2616,14 +2780,22 @@ export async function shutdownTeam(teamName, cwd, options = {}) {
|
|
|
2616
2780
|
}
|
|
2617
2781
|
}
|
|
2618
2782
|
const artifactCwd = resolveTeamCommitHygieneArtifactCwd(config, cwd);
|
|
2619
|
-
const ledger = await appendTeamCommitHygieneEntries(sanitized, commitHygieneEntries, artifactCwd);
|
|
2620
2783
|
const taskView = await listTasks(sanitized, cwd).catch(() => []);
|
|
2621
|
-
const
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2784
|
+
const internalLedger = await appendTeamCommitHygieneEntries(sanitized, commitHygieneEntries, artifactCwd);
|
|
2785
|
+
const commitHygieneArtifactTeamNames = resolveCommitHygieneArtifactTeamNames(config, sanitized);
|
|
2786
|
+
let commitHygieneArtifacts = null;
|
|
2787
|
+
for (const artifactTeamName of commitHygieneArtifactTeamNames) {
|
|
2788
|
+
const ledger = artifactTeamName === sanitized
|
|
2789
|
+
? internalLedger
|
|
2790
|
+
: await appendTeamCommitHygieneEntries(artifactTeamName, internalLedger.entries, artifactCwd);
|
|
2791
|
+
const commitHygieneContext = buildTeamCommitHygieneContext({
|
|
2792
|
+
teamName: artifactTeamName,
|
|
2793
|
+
tasks: taskView,
|
|
2794
|
+
ledger,
|
|
2795
|
+
});
|
|
2796
|
+
const writtenArtifacts = await writeTeamCommitHygieneContext(artifactTeamName, commitHygieneContext, artifactCwd);
|
|
2797
|
+
commitHygieneArtifacts ??= writtenArtifacts;
|
|
2798
|
+
}
|
|
2627
2799
|
// 5. Remove worker worktree-root instructions and team-scoped fallback instructions.
|
|
2628
2800
|
for (const worker of config.workers) {
|
|
2629
2801
|
if (!worker.worktree_path || !worker.team_state_root)
|
|
@@ -2675,7 +2847,7 @@ export async function shutdownTeam(teamName, cwd, options = {}) {
|
|
|
2675
2847
|
* Resume monitoring an existing team.
|
|
2676
2848
|
*/
|
|
2677
2849
|
export async function resumeTeam(teamName, cwd) {
|
|
2678
|
-
const sanitized =
|
|
2850
|
+
const sanitized = resolveTeamNameForCurrentContext(teamName, cwd);
|
|
2679
2851
|
const config = await readTeamConfig(sanitized, cwd);
|
|
2680
2852
|
if (!config)
|
|
2681
2853
|
return null;
|
|
@@ -2951,8 +3123,89 @@ async function markDispatchRequestLeaderPaneMissingDeferred(params) {
|
|
|
2951
3123
|
last_reason: 'leader_pane_missing_deferred',
|
|
2952
3124
|
}, cwd).catch(() => { });
|
|
2953
3125
|
}
|
|
3126
|
+
async function attemptStartupDirectTrigger(params) {
|
|
3127
|
+
const { teamName, config, workerName, workerIndex, paneId, workerCli, inbox, triggerMessage, intent, taskIds, cwd, timing, } = params;
|
|
3128
|
+
const safety = await evaluateStartupDirectTriggerSafety(config.tmux_session, workerIndex, paneId, workerCli);
|
|
3129
|
+
if (!safety.safe) {
|
|
3130
|
+
timing.mark('startup_direct_bypass', {
|
|
3131
|
+
worker: workerName,
|
|
3132
|
+
pane_id: paneId,
|
|
3133
|
+
ok: false,
|
|
3134
|
+
reason: `startup_direct_unsafe:${safety.reason}`,
|
|
3135
|
+
});
|
|
3136
|
+
return null;
|
|
3137
|
+
}
|
|
3138
|
+
const queued = await queueInboxInstruction({
|
|
3139
|
+
teamName,
|
|
3140
|
+
workerName,
|
|
3141
|
+
workerIndex,
|
|
3142
|
+
paneId,
|
|
3143
|
+
inbox,
|
|
3144
|
+
triggerMessage,
|
|
3145
|
+
intent,
|
|
3146
|
+
cwd,
|
|
3147
|
+
transportPreference: 'transport_direct',
|
|
3148
|
+
fallbackAllowed: false,
|
|
3149
|
+
inboxCorrelationKey: `startup-direct:${workerName}`,
|
|
3150
|
+
notify: (_target, message) => notifyWorkerOutcome(config, workerIndex, message, paneId),
|
|
3151
|
+
});
|
|
3152
|
+
timing.mark('dispatch_queued', {
|
|
3153
|
+
worker: workerName,
|
|
3154
|
+
pane_id: paneId,
|
|
3155
|
+
ok: queued.ok,
|
|
3156
|
+
reason: queued.reason,
|
|
3157
|
+
transport: queued.transport,
|
|
3158
|
+
request_id: queued.request_id,
|
|
3159
|
+
});
|
|
3160
|
+
timing.mark('direct_fallback', {
|
|
3161
|
+
worker: workerName,
|
|
3162
|
+
pane_id: paneId,
|
|
3163
|
+
ok: queued.ok,
|
|
3164
|
+
reason: queued.ok ? `startup_direct_trigger_sent:${safety.reason}` : queued.reason,
|
|
3165
|
+
transport: queued.transport,
|
|
3166
|
+
request_id: queued.request_id,
|
|
3167
|
+
});
|
|
3168
|
+
if (!queued.ok)
|
|
3169
|
+
return queued;
|
|
3170
|
+
const effectiveWorkerCli = workerCli ?? 'codex';
|
|
3171
|
+
const workerStartupEvidence = await waitForWorkerStartupEvidence({
|
|
3172
|
+
teamName,
|
|
3173
|
+
workerName,
|
|
3174
|
+
workerCli: effectiveWorkerCli,
|
|
3175
|
+
cwd,
|
|
3176
|
+
timeoutMs: 0,
|
|
3177
|
+
pollMs: STARTUP_EVIDENCE_POLL_MS,
|
|
3178
|
+
});
|
|
3179
|
+
timing.mark('startup_evidence', {
|
|
3180
|
+
worker: workerName,
|
|
3181
|
+
pane_id: paneId,
|
|
3182
|
+
ok: workerStartupEvidence !== 'none',
|
|
3183
|
+
reason: workerStartupEvidence,
|
|
3184
|
+
transport: queued.transport,
|
|
3185
|
+
request_id: queued.request_id,
|
|
3186
|
+
});
|
|
3187
|
+
const reason = workerStartupEvidence === 'none'
|
|
3188
|
+
? `${effectiveWorkerCli}_startup_direct_no_evidence:${safety.reason}`
|
|
3189
|
+
: `startup_direct_trigger_sent:${safety.reason}`;
|
|
3190
|
+
if ((effectiveWorkerCli === 'codex' || effectiveWorkerCli === 'claude') && workerStartupEvidence === 'none') {
|
|
3191
|
+
await recordRecoverableStartupIssue({
|
|
3192
|
+
teamName,
|
|
3193
|
+
workerName,
|
|
3194
|
+
taskIds,
|
|
3195
|
+
reason,
|
|
3196
|
+
cwd,
|
|
3197
|
+
});
|
|
3198
|
+
}
|
|
3199
|
+
return {
|
|
3200
|
+
...queued,
|
|
3201
|
+
reason,
|
|
3202
|
+
};
|
|
3203
|
+
}
|
|
2954
3204
|
async function dispatchCriticalInboxInstruction(params) {
|
|
2955
|
-
const { teamName, config, workerName, workerIndex, paneId, workerCli, inbox, triggerMessage, intent, cwd, dispatchPolicy, inboxCorrelationKey, requireWorkerStartupEvidence, startupEvidenceTimeoutMs, } = params;
|
|
3205
|
+
const { teamName, config, workerName, workerIndex, paneId, workerCli, inbox, triggerMessage, intent, cwd, dispatchPolicy, inboxCorrelationKey, requireWorkerStartupEvidence, startupEvidenceTimeoutMs, startupReadyPromptObserved = false, startupTiming, } = params;
|
|
3206
|
+
const noteTiming = (phase, details) => {
|
|
3207
|
+
startupTiming?.mark(phase, { worker: workerName, pane_id: paneId, ...details });
|
|
3208
|
+
};
|
|
2956
3209
|
if (config.worker_launch_mode === 'prompt') {
|
|
2957
3210
|
return await queueInboxInstruction({
|
|
2958
3211
|
teamName,
|
|
@@ -2999,12 +3252,26 @@ async function dispatchCriticalInboxInstruction(params) {
|
|
|
2999
3252
|
inboxCorrelationKey,
|
|
3000
3253
|
notify: () => ({ ok: true, transport: 'hook', reason: 'queued_for_hook_dispatch' }),
|
|
3001
3254
|
});
|
|
3255
|
+
noteTiming('dispatch_queued', {
|
|
3256
|
+
ok: queued.ok,
|
|
3257
|
+
reason: queued.reason,
|
|
3258
|
+
transport: queued.transport,
|
|
3259
|
+
request_id: queued.request_id,
|
|
3260
|
+
});
|
|
3002
3261
|
if (!queued.request_id)
|
|
3003
3262
|
return { ...queued, ok: false, reason: 'dispatch_request_missing_id' };
|
|
3004
3263
|
const receipt = await waitForDispatchReceipt(teamName, queued.request_id, cwd, {
|
|
3005
3264
|
timeoutMs: dispatchPolicy.dispatch_ack_timeout_ms,
|
|
3006
3265
|
pollMs: 50,
|
|
3007
3266
|
});
|
|
3267
|
+
if (receipt) {
|
|
3268
|
+
noteTiming('hook_receipt', {
|
|
3269
|
+
ok: receipt.status === 'delivered' || receipt.status === 'notified',
|
|
3270
|
+
reason: receipt.status,
|
|
3271
|
+
transport: 'hook',
|
|
3272
|
+
request_id: queued.request_id,
|
|
3273
|
+
});
|
|
3274
|
+
}
|
|
3008
3275
|
if (receipt?.status === 'delivered') {
|
|
3009
3276
|
return { ok: true, transport: 'hook', reason: 'hook_receipt_delivered', request_id: queued.request_id };
|
|
3010
3277
|
}
|
|
@@ -3015,6 +3282,14 @@ async function dispatchCriticalInboxInstruction(params) {
|
|
|
3015
3282
|
if (!requiresObservedStartupEvidence) {
|
|
3016
3283
|
return { ok: true, transport: 'hook', reason: 'hook_receipt_notified', request_id: queued.request_id };
|
|
3017
3284
|
}
|
|
3285
|
+
if (startupReadyPromptObserved) {
|
|
3286
|
+
return {
|
|
3287
|
+
ok: true,
|
|
3288
|
+
transport: 'hook',
|
|
3289
|
+
reason: 'hook_receipt_notified_with_ready_prompt',
|
|
3290
|
+
request_id: queued.request_id,
|
|
3291
|
+
};
|
|
3292
|
+
}
|
|
3018
3293
|
startupEvidence = await waitForWorkerStartupEvidence({
|
|
3019
3294
|
teamName,
|
|
3020
3295
|
workerName,
|
|
@@ -3022,6 +3297,12 @@ async function dispatchCriticalInboxInstruction(params) {
|
|
|
3022
3297
|
cwd,
|
|
3023
3298
|
timeoutMs: startupEvidenceTimeoutMs,
|
|
3024
3299
|
});
|
|
3300
|
+
noteTiming('startup_evidence', {
|
|
3301
|
+
ok: startupEvidence !== 'none',
|
|
3302
|
+
reason: startupEvidence,
|
|
3303
|
+
transport: 'hook',
|
|
3304
|
+
request_id: queued.request_id,
|
|
3305
|
+
});
|
|
3025
3306
|
if (startupEvidence !== 'none') {
|
|
3026
3307
|
return {
|
|
3027
3308
|
ok: true,
|
|
@@ -3034,13 +3315,21 @@ async function dispatchCriticalInboxInstruction(params) {
|
|
|
3034
3315
|
if (receipt?.status === 'failed') {
|
|
3035
3316
|
const fallback = await notifyWorkerOutcome(config, workerIndex, triggerMessage, paneId);
|
|
3036
3317
|
if (fallback.ok) {
|
|
3037
|
-
const fallbackStartupEvidence =
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3318
|
+
const fallbackStartupEvidence = startupReadyPromptObserved
|
|
3319
|
+
? 'ready_prompt'
|
|
3320
|
+
: await waitForRequiredStartupEvidenceAfterDirectFallback({
|
|
3321
|
+
requireWorkerStartupEvidence,
|
|
3322
|
+
workerCli,
|
|
3323
|
+
teamName,
|
|
3324
|
+
workerName,
|
|
3325
|
+
cwd,
|
|
3326
|
+
timeoutMs: startupEvidenceTimeoutMs,
|
|
3327
|
+
});
|
|
3328
|
+
noteTiming('startup_evidence', {
|
|
3329
|
+
ok: fallbackStartupEvidence !== 'none',
|
|
3330
|
+
reason: fallbackStartupEvidence,
|
|
3331
|
+
transport: fallback.transport,
|
|
3332
|
+
request_id: queued.request_id,
|
|
3044
3333
|
});
|
|
3045
3334
|
if (requiresObservedStartupEvidence && fallbackStartupEvidence === 'none') {
|
|
3046
3335
|
await transitionDispatchRequest(teamName, queued.request_id, 'failed', 'failed', { last_reason: `${workerCli}_startup_no_evidence_after_fallback:${fallback.reason}` }, cwd).catch(() => { });
|
|
@@ -3071,6 +3360,12 @@ async function dispatchCriticalInboxInstruction(params) {
|
|
|
3071
3360
|
};
|
|
3072
3361
|
}
|
|
3073
3362
|
const fallback = await notifyWorkerOutcome(config, workerIndex, triggerMessage, paneId);
|
|
3363
|
+
noteTiming('direct_fallback', {
|
|
3364
|
+
ok: fallback.ok,
|
|
3365
|
+
reason: fallback.reason,
|
|
3366
|
+
transport: fallback.transport,
|
|
3367
|
+
request_id: queued.request_id,
|
|
3368
|
+
});
|
|
3074
3369
|
const startupFallbackLabel = receipt?.status === 'notified' && requiresObservedStartupEvidence
|
|
3075
3370
|
? `${workerCli}_startup_no_evidence`
|
|
3076
3371
|
: null;
|
|
@@ -3078,13 +3373,21 @@ async function dispatchCriticalInboxInstruction(params) {
|
|
|
3078
3373
|
? `${startupFallbackLabel}_fallback_failed:${fallback.reason}`
|
|
3079
3374
|
: `fallback_attempted_but_unconfirmed:${fallback.reason}`;
|
|
3080
3375
|
if (fallback.ok) {
|
|
3081
|
-
const fallbackStartupEvidence =
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3376
|
+
const fallbackStartupEvidence = startupReadyPromptObserved
|
|
3377
|
+
? 'ready_prompt'
|
|
3378
|
+
: await waitForRequiredStartupEvidenceAfterDirectFallback({
|
|
3379
|
+
requireWorkerStartupEvidence,
|
|
3380
|
+
workerCli,
|
|
3381
|
+
teamName,
|
|
3382
|
+
workerName,
|
|
3383
|
+
cwd,
|
|
3384
|
+
timeoutMs: startupEvidenceTimeoutMs,
|
|
3385
|
+
});
|
|
3386
|
+
noteTiming('startup_evidence', {
|
|
3387
|
+
ok: fallbackStartupEvidence !== 'none',
|
|
3388
|
+
reason: fallbackStartupEvidence,
|
|
3389
|
+
transport: fallback.transport,
|
|
3390
|
+
request_id: queued.request_id,
|
|
3088
3391
|
});
|
|
3089
3392
|
if (requiresObservedStartupEvidence && fallbackStartupEvidence === 'none') {
|
|
3090
3393
|
const current = await readDispatchRequest(teamName, queued.request_id, cwd);
|