oh-my-codex 0.11.11 → 0.11.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/Cargo.lock +5 -5
- package/Cargo.toml +1 -1
- package/README.de.md +12 -6
- package/README.el.md +223 -0
- package/README.es.md +12 -6
- package/README.fr.md +11 -5
- package/README.it.md +12 -6
- package/README.ja.md +12 -6
- package/README.ko.md +12 -6
- package/README.md +56 -28
- package/README.pl.md +216 -0
- package/README.pt.md +12 -6
- package/README.ru.md +12 -6
- package/README.tr.md +12 -6
- package/README.vi.md +148 -183
- package/README.zh-TW.md +14 -17
- package/README.zh.md +12 -6
- package/crates/omx-runtime-core/src/engine.rs +122 -4
- package/crates/omx-runtime-core/src/lib.rs +17 -0
- package/dist/autoresearch/contracts.d.ts.map +1 -1
- package/dist/autoresearch/contracts.js +1 -0
- package/dist/autoresearch/contracts.js.map +1 -1
- package/dist/autoresearch/runtime.d.ts.map +1 -1
- package/dist/autoresearch/runtime.js +7 -1
- package/dist/autoresearch/runtime.js.map +1 -1
- package/dist/cli/__tests__/agents.test.js +24 -1
- package/dist/cli/__tests__/agents.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch.test.js +11 -0
- package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
- package/dist/cli/__tests__/cleanup.test.js +117 -4
- package/dist/cli/__tests__/cleanup.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +33 -3
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/error-handling-warnings.test.js +13 -0
- package/dist/cli/__tests__/error-handling-warnings.test.js.map +1 -1
- package/dist/cli/__tests__/exec.test.js +6 -0
- package/dist/cli/__tests__/exec.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +101 -1
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +3 -0
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +10 -0
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/packaged-script-resolution.test.js +4 -3
- package/dist/cli/__tests__/packaged-script-resolution.test.js.map +1 -1
- package/dist/cli/__tests__/resume.test.js +6 -0
- package/dist/cli/__tests__/resume.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +29 -12
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +1 -1
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/star-prompt.test.js +16 -0
- package/dist/cli/__tests__/star-prompt.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +112 -1
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/__tests__/windows-popup-loop-contract.test.d.ts +2 -0
- package/dist/cli/__tests__/windows-popup-loop-contract.test.d.ts.map +1 -0
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js +30 -0
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -0
- package/dist/cli/agents.d.ts.map +1 -1
- package/dist/cli/agents.js +9 -3
- package/dist/cli/agents.js.map +1 -1
- package/dist/cli/autoresearch-guided.d.ts.map +1 -1
- package/dist/cli/autoresearch-guided.js +9 -3
- package/dist/cli/autoresearch-guided.js.map +1 -1
- package/dist/cli/autoresearch.d.ts.map +1 -1
- package/dist/cli/autoresearch.js +8 -2
- package/dist/cli/autoresearch.js.map +1 -1
- package/dist/cli/cleanup.d.ts +2 -0
- package/dist/cli/cleanup.d.ts.map +1 -1
- package/dist/cli/cleanup.js +27 -1
- package/dist/cli/cleanup.js.map +1 -1
- package/dist/cli/doctor.js +7 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +9 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +171 -55
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +18 -15
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/star-prompt.d.ts.map +1 -1
- package/dist/cli/star-prompt.js +2 -0
- package/dist/cli/star-prompt.js.map +1 -1
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +5 -1
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/tmux-hook.d.ts.map +1 -1
- package/dist/cli/tmux-hook.js +4 -1
- package/dist/cli/tmux-hook.js.map +1 -1
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +26 -0
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +1 -0
- package/dist/cli/update.js.map +1 -1
- package/dist/compat/__tests__/rust-runtime-compat.test.js +84 -1
- package/dist/compat/__tests__/rust-runtime-compat.test.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +4 -4
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/mcp-registry.test.js +13 -16
- package/dist/config/__tests__/mcp-registry.test.js.map +1 -1
- package/dist/config/mcp-registry.d.ts +1 -0
- package/dist/config/mcp-registry.d.ts.map +1 -1
- package/dist/config/mcp-registry.js +4 -4
- package/dist/config/mcp-registry.js.map +1 -1
- package/dist/config/models.d.ts +1 -0
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +39 -1
- package/dist/config/models.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +12 -1
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +554 -18
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +347 -16
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-modules.test.js +5 -0
- package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.d.ts +2 -0
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +597 -0
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -0
- package/dist/hooks/__tests__/notify-hook-regression-205.test.js +19 -1
- package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js +73 -53
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +193 -2
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +183 -0
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +255 -97
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +0 -0
- package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +46 -0
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-team-routing.test.js +34 -0
- package/dist/hooks/__tests__/prompt-team-routing.test.js.map +1 -1
- package/dist/hooks/__tests__/tmux-hook-engine.test.js +32 -1
- package/dist/hooks/__tests__/tmux-hook-engine.test.js.map +1 -1
- package/dist/hooks/code-simplifier/index.d.ts.map +1 -1
- package/dist/hooks/code-simplifier/index.js +1 -0
- package/dist/hooks/code-simplifier/index.js.map +1 -1
- package/dist/hooks/codebase-map.d.ts.map +1 -1
- package/dist/hooks/codebase-map.js +1 -0
- package/dist/hooks/codebase-map.js.map +1 -1
- package/dist/hooks/extensibility/sdk/tmux.d.ts.map +1 -1
- package/dist/hooks/extensibility/sdk/tmux.js +3 -1
- package/dist/hooks/extensibility/sdk/tmux.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +1 -0
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +48 -0
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +6 -0
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +1 -0
- package/dist/hooks/session.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +70 -1
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/authority.d.ts.map +1 -1
- package/dist/hud/authority.js +1 -0
- package/dist/hud/authority.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +52 -0
- package/dist/hud/state.js.map +1 -1
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/mcp/state-server.js +5 -0
- package/dist/mcp/state-server.js.map +1 -1
- package/dist/modes/__tests__/base-session-scope.test.js +46 -0
- package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +4 -0
- package/dist/modes/base.js.map +1 -1
- package/dist/notifications/__tests__/custom-alias-enablement.test.d.ts +2 -0
- package/dist/notifications/__tests__/custom-alias-enablement.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/custom-alias-enablement.test.js +84 -0
- package/dist/notifications/__tests__/custom-alias-enablement.test.js.map +1 -0
- package/dist/notifications/__tests__/idle-cooldown.test.js +55 -0
- package/dist/notifications/__tests__/idle-cooldown.test.js.map +1 -1
- package/dist/notifications/idle-cooldown.d.ts +8 -6
- package/dist/notifications/idle-cooldown.d.ts.map +1 -1
- package/dist/notifications/idle-cooldown.js +53 -22
- package/dist/notifications/idle-cooldown.js.map +1 -1
- package/dist/notifications/notifier.js +1 -1
- package/dist/notifications/notifier.js.map +1 -1
- package/dist/notifications/reply-listener.d.ts.map +1 -1
- package/dist/notifications/reply-listener.js +1 -0
- package/dist/notifications/reply-listener.js.map +1 -1
- package/dist/notifications/tmux.d.ts.map +1 -1
- package/dist/notifications/tmux.js +4 -0
- package/dist/notifications/tmux.js.map +1 -1
- package/dist/openclaw/config.js +2 -2
- package/dist/openclaw/config.js.map +1 -1
- package/dist/runtime/bridge.d.ts +2 -0
- package/dist/runtime/bridge.d.ts.map +1 -1
- package/dist/runtime/bridge.js +8 -0
- package/dist/runtime/bridge.js.map +1 -1
- package/dist/scripts/notify-fallback-watcher.js +103 -53
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.d.ts +2 -1
- package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.js +90 -104
- package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.d.ts +19 -0
- package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -0
- package/dist/scripts/notify-hook/managed-tmux.js +320 -0
- package/dist/scripts/notify-hook/managed-tmux.js.map +1 -0
- package/dist/scripts/notify-hook/operational-events.d.ts.map +1 -1
- package/dist/scripts/notify-hook/operational-events.js +2 -0
- package/dist/scripts/notify-hook/operational-events.js.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts +22 -0
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -0
- package/dist/scripts/notify-hook/ralph-session-resume.js +277 -0
- package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -0
- package/dist/scripts/notify-hook/state-io.d.ts +1 -1
- package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
- package/dist/scripts/notify-hook/state-io.js +2 -10
- package/dist/scripts/notify-hook/state-io.js.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts +1 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js +123 -72
- package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.js +13 -5
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.js +1 -19
- package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker.js +4 -4
- package/dist/scripts/notify-hook/team-worker.js.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts +1 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +102 -35
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/notify-hook.js +144 -20
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/run-provider-advisor.js +2 -0
- package/dist/scripts/run-provider-advisor.js.map +1 -1
- package/dist/scripts/run-test-files.d.ts +2 -0
- package/dist/scripts/run-test-files.d.ts.map +1 -0
- package/dist/scripts/run-test-files.js +41 -0
- package/dist/scripts/run-test-files.js.map +1 -0
- package/dist/scripts/tmux-hook-engine.d.ts +2 -0
- package/dist/scripts/tmux-hook-engine.d.ts.map +1 -1
- package/dist/scripts/tmux-hook-engine.js +15 -0
- package/dist/scripts/tmux-hook-engine.js.map +1 -1
- package/dist/team/__tests__/api-interop.test.js +136 -4
- package/dist/team/__tests__/api-interop.test.js.map +1 -1
- package/dist/team/__tests__/leader-activity.test.js +107 -2
- package/dist/team/__tests__/leader-activity.test.js.map +1 -1
- package/dist/team/__tests__/runtime-cli.test.js +32 -0
- package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +148 -0
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/shutdown-fallback.test.js +13 -0
- package/dist/team/__tests__/shutdown-fallback.test.js.map +1 -1
- package/dist/team/__tests__/state-root.test.js +11 -1
- package/dist/team/__tests__/state-root.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +237 -0
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +521 -2
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/api-interop.d.ts.map +1 -1
- package/dist/team/api-interop.js +41 -31
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/commit-hygiene.d.ts +60 -0
- package/dist/team/commit-hygiene.d.ts.map +1 -0
- package/dist/team/commit-hygiene.js +232 -0
- package/dist/team/commit-hygiene.js.map +1 -0
- package/dist/team/leader-activity.d.ts.map +1 -1
- package/dist/team/leader-activity.js +56 -4
- package/dist/team/leader-activity.js.map +1 -1
- package/dist/team/runtime-cli.d.ts +9 -1
- package/dist/team/runtime-cli.d.ts.map +1 -1
- package/dist/team/runtime-cli.js +15 -6
- package/dist/team/runtime-cli.js.map +1 -1
- package/dist/team/runtime.d.ts +7 -2
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +392 -171
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +6 -2
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state/dispatch.d.ts +2 -0
- package/dist/team/state/dispatch.d.ts.map +1 -1
- package/dist/team/state/dispatch.js +86 -40
- package/dist/team/state/dispatch.js.map +1 -1
- package/dist/team/state/mailbox.d.ts +3 -0
- package/dist/team/state/mailbox.d.ts.map +1 -1
- package/dist/team/state/mailbox.js +93 -19
- package/dist/team/state/mailbox.js.map +1 -1
- package/dist/team/state-root.d.ts +1 -1
- package/dist/team/state-root.d.ts.map +1 -1
- package/dist/team/state-root.js +8 -3
- package/dist/team/state-root.js.map +1 -1
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +96 -2
- package/dist/team/state.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +81 -29
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +4 -0
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/team/worktree.d.ts.map +1 -1
- package/dist/team/worktree.js +9 -0
- package/dist/team/worktree.js.map +1 -1
- package/dist/utils/__tests__/paths.test.js +98 -11
- package/dist/utils/__tests__/paths.test.js.map +1 -1
- package/dist/utils/__tests__/platform-command.test.js +101 -2
- package/dist/utils/__tests__/platform-command.test.js.map +1 -1
- package/dist/utils/git-layout.d.ts +8 -0
- package/dist/utils/git-layout.d.ts.map +1 -0
- package/dist/utils/git-layout.js +58 -0
- package/dist/utils/git-layout.js.map +1 -0
- package/dist/utils/paths.d.ts +3 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +14 -4
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/platform-command.d.ts.map +1 -1
- package/dist/utils/platform-command.js +35 -3
- package/dist/utils/platform-command.js.map +1 -1
- package/package.json +9 -5
- package/src/scripts/notify-fallback-watcher.ts +103 -53
- package/src/scripts/notify-hook/auto-nudge.ts +97 -103
- package/src/scripts/notify-hook/managed-tmux.ts +324 -0
- package/src/scripts/notify-hook/operational-events.ts +2 -0
- package/src/scripts/notify-hook/ralph-session-resume.ts +337 -0
- package/src/scripts/notify-hook/state-io.ts +2 -10
- package/src/scripts/notify-hook/team-dispatch.ts +131 -66
- package/src/scripts/notify-hook/team-leader-nudge.ts +19 -5
- package/src/scripts/notify-hook/team-tmux-guard.ts +0 -20
- package/src/scripts/notify-hook/team-worker.ts +4 -4
- package/src/scripts/notify-hook/tmux-injection.ts +103 -33
- package/src/scripts/notify-hook.ts +150 -21
- package/src/scripts/run-provider-advisor.ts +4 -2
- package/src/scripts/run-test-files.ts +48 -0
- package/src/scripts/tmux-hook-engine.ts +16 -0
- package/templates/AGENTS.md +51 -43
package/dist/team/runtime.js
CHANGED
|
@@ -12,12 +12,12 @@ import { composeRoleInstructionsForRole } from '../agents/native-config.js';
|
|
|
12
12
|
import { codexPromptsDir } from '../utils/paths.js';
|
|
13
13
|
import { resolveTeamWorkerLaunchArgs, TEAM_LOW_COMPLEXITY_DEFAULT_MODEL, parseTeamWorkerLaunchArgs, splitWorkerLaunchArgs, resolveAgentDefaultModel, resolveAgentReasoningEffort, } from './model-contract.js';
|
|
14
14
|
import { resolveCanonicalTeamStateRoot } from './state-root.js';
|
|
15
|
-
import { isBridgeEnabled, getDefaultBridge } from '../runtime/bridge.js';
|
|
16
15
|
import { inferPhaseTargetFromTaskCounts, reconcilePhaseStateForMonitor } from './phase-controller.js';
|
|
17
16
|
import { getTeamTmuxSessions } from '../notifications/tmux.js';
|
|
18
17
|
import { hasStructuredVerificationEvidence } from '../verification/verifier.js';
|
|
19
18
|
import { buildRebalanceDecisions } from './rebalance-policy.js';
|
|
20
19
|
import { readModeState, updateModeState } from '../modes/base.js';
|
|
20
|
+
import { appendTeamCommitHygieneEntries, buildTeamCommitHygieneContext, writeTeamCommitHygieneContext, } from './commit-hygiene.js';
|
|
21
21
|
import { assertCleanLeaderWorkspaceForWorkerWorktrees, ensureWorktree, isGitRepository, planWorktreeTarget, rollbackProvisionedWorktrees, } from './worktree.js';
|
|
22
22
|
async function syncRootTeamModeStateOnTerminalPhase(teamName, phase, cwd) {
|
|
23
23
|
if (phase !== 'complete' && phase !== 'failed' && phase !== 'cancelled')
|
|
@@ -82,6 +82,7 @@ function runCommand(command, args, cwd) {
|
|
|
82
82
|
const result = spawnSync(command, args, {
|
|
83
83
|
cwd,
|
|
84
84
|
encoding: 'utf-8',
|
|
85
|
+
windowsHide: true,
|
|
85
86
|
});
|
|
86
87
|
return {
|
|
87
88
|
ok: result.status === 0,
|
|
@@ -173,11 +174,22 @@ function appendIntegrationReport(teamName, entry, cwd) {
|
|
|
173
174
|
const line = `- [${timestamp}] ${entry.workerName}: ${entry.operation} conflict auto-resolved (${entry.strategy}) on files: ${entry.files.join(', ') || 'unknown'}. ${entry.detail}\n`;
|
|
174
175
|
appendFileSync(reportPath, existsSync(reportPath) ? line : `# Integration Report\n\n${line}`);
|
|
175
176
|
}
|
|
177
|
+
function resolveWorkerMergeRef(branchResult, workerHead) {
|
|
178
|
+
const branchRef = branchResult.ok ? branchResult.stdout.trim() : '';
|
|
179
|
+
if (!branchRef || branchRef === 'HEAD')
|
|
180
|
+
return workerHead;
|
|
181
|
+
return branchRef;
|
|
182
|
+
}
|
|
183
|
+
function leaderContainsCommit(repoRoot, cwd, commit) {
|
|
184
|
+
return runGitCommand(repoRoot, ['merge-base', '--is-ancestor', commit, 'HEAD'], cwd).ok;
|
|
185
|
+
}
|
|
176
186
|
async function integrateWorkerCommitsIntoLeader(params) {
|
|
177
187
|
const { teamName, config, previous, cwd } = params;
|
|
178
188
|
const next = { ...(previous?.integrationByWorker ?? {}) };
|
|
179
189
|
const leaderHeadAtCycleStart = resolveLeaderHead(resolve(config.workers[0]?.worktree_repo_root ?? cwd), cwd);
|
|
180
190
|
const integratedWorkerNames = new Set();
|
|
191
|
+
const commitHygieneEntries = [];
|
|
192
|
+
const artifactCwd = config.leader_cwd ?? cwd;
|
|
181
193
|
// ── Phase A: Auto-commit dirty worktrees ──
|
|
182
194
|
for (const worker of config.workers) {
|
|
183
195
|
if (!worker.worktree_repo_root || !worker.worktree_path || !existsSync(worker.worktree_path))
|
|
@@ -190,6 +202,16 @@ async function integrateWorkerCommitsIntoLeader(params) {
|
|
|
190
202
|
worktree_path: resolve(worker.worktree_path),
|
|
191
203
|
summary: `auto-committed dirty worktree for ${worker.name}`,
|
|
192
204
|
}, cwd);
|
|
205
|
+
commitHygieneEntries.push({
|
|
206
|
+
recorded_at: new Date().toISOString(),
|
|
207
|
+
operation: 'auto_checkpoint',
|
|
208
|
+
worker_name: worker.name,
|
|
209
|
+
task_id: worker.assigned_tasks[0],
|
|
210
|
+
status: 'applied',
|
|
211
|
+
operational_commit: commitHash,
|
|
212
|
+
worktree_path: resolve(worker.worktree_path),
|
|
213
|
+
detail: 'Dirty worker worktree checkpointed before runtime integration.',
|
|
214
|
+
});
|
|
193
215
|
}
|
|
194
216
|
}
|
|
195
217
|
// ── Phase B: Integrate worker commits to leader (hybrid strategy) ──
|
|
@@ -220,26 +242,69 @@ async function integrateWorkerCommitsIntoLeader(params) {
|
|
|
220
242
|
if (workerIsAheadOfLeader) {
|
|
221
243
|
// Worker is cleanly ahead → merge --no-ff -X theirs
|
|
222
244
|
const workerBranch = runGitCommand(repoRoot, ['rev-parse', '--abbrev-ref', 'HEAD'], worktreePath);
|
|
223
|
-
const branchRef = workerBranch
|
|
245
|
+
const branchRef = resolveWorkerMergeRef(workerBranch, workerHead);
|
|
224
246
|
const merge = runGitCommand(repoRoot, ['merge', '--no-ff', '-X', 'theirs', '-m', `omx(team): merge ${worker.name}`, branchRef], cwd);
|
|
225
247
|
if (merge.ok) {
|
|
226
248
|
const newLeaderHead = resolveLeaderHead(repoRoot, cwd) ?? leaderHead;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
249
|
+
const workerIntegrated = leaderContainsCommit(repoRoot, cwd, workerHead);
|
|
250
|
+
const leaderAdvanced = newLeaderHead !== leaderHead;
|
|
251
|
+
if (workerIntegrated && leaderAdvanced) {
|
|
252
|
+
state.last_integrated_head = workerHead;
|
|
253
|
+
state.last_leader_head = newLeaderHead;
|
|
254
|
+
state.status = 'integrated';
|
|
255
|
+
state.conflict_commit = undefined;
|
|
256
|
+
state.conflict_files = undefined;
|
|
257
|
+
state.updated_at = new Date().toISOString();
|
|
258
|
+
integratedWorkerNames.add(worker.name);
|
|
259
|
+
await appendIntegrationEvent(teamName, 'worker_merge_applied', worker, {
|
|
260
|
+
worker_name: worker.name,
|
|
261
|
+
worker_head: workerHead,
|
|
262
|
+
leader_head_before: leaderHead,
|
|
263
|
+
leader_head_after: newLeaderHead,
|
|
264
|
+
worktree_path: worktreePath,
|
|
265
|
+
summary: `merged ${worker.name} into leader via --no-ff -X theirs`,
|
|
266
|
+
}, cwd);
|
|
267
|
+
await sendIntegrationMessageToLeader(teamName, worker, `INTEGRATED: merged ${worker.name} (${workerHead.slice(0, 12)}) into leader HEAD ${newLeaderHead.slice(0, 12)} via merge --no-ff.`, cwd);
|
|
268
|
+
commitHygieneEntries.push({
|
|
269
|
+
recorded_at: new Date().toISOString(),
|
|
270
|
+
operation: 'integration_merge',
|
|
271
|
+
worker_name: worker.name,
|
|
272
|
+
task_id: worker.assigned_tasks[0],
|
|
273
|
+
status: 'applied',
|
|
274
|
+
operational_commit: newLeaderHead,
|
|
275
|
+
source_commit: workerHead,
|
|
276
|
+
leader_head_before: leaderHead,
|
|
277
|
+
leader_head_after: newLeaderHead,
|
|
278
|
+
worktree_path: worktreePath,
|
|
279
|
+
detail: 'Leader created a runtime merge commit to integrate worker history.',
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
state.last_leader_head = newLeaderHead;
|
|
284
|
+
state.status = 'idle';
|
|
285
|
+
state.updated_at = new Date().toISOString();
|
|
286
|
+
appendIntegrationReport(teamName, {
|
|
287
|
+
workerName: worker.name,
|
|
288
|
+
operation: 'merge',
|
|
289
|
+
strategy: '-X theirs',
|
|
290
|
+
files: [],
|
|
291
|
+
detail: `merge reported success but leader HEAD did not advance cleanly (leader_before=${leaderHead.slice(0, 12)}, leader_after=${newLeaderHead.slice(0, 12)}, worker_integrated=${workerIntegrated}, merge_ref=${branchRef}).`,
|
|
292
|
+
}, cwd);
|
|
293
|
+
await sendIntegrationMessageToLeader(teamName, worker, `INTEGRATION NO-OP: merge for ${worker.name} using ${branchRef.slice(0, 12)} reported success but leader HEAD stayed ${newLeaderHead.slice(0, 12)}. Inspect ${worktreePath}.`, cwd);
|
|
294
|
+
commitHygieneEntries.push({
|
|
295
|
+
recorded_at: new Date().toISOString(),
|
|
296
|
+
operation: 'integration_merge',
|
|
297
|
+
worker_name: worker.name,
|
|
298
|
+
task_id: worker.assigned_tasks[0],
|
|
299
|
+
status: 'skipped',
|
|
300
|
+
operational_commit: newLeaderHead,
|
|
301
|
+
source_commit: workerHead,
|
|
302
|
+
leader_head_before: leaderHead,
|
|
303
|
+
leader_head_after: newLeaderHead,
|
|
304
|
+
worktree_path: worktreePath,
|
|
305
|
+
detail: 'Merge command reported success but leader HEAD did not advance or contain the worker commit; runtime refused to report false integration.',
|
|
306
|
+
});
|
|
307
|
+
}
|
|
243
308
|
}
|
|
244
309
|
else {
|
|
245
310
|
// Merge failed even with -X theirs (e.g. binary conflict) — abort and log
|
|
@@ -333,6 +398,19 @@ async function integrateWorkerCommitsIntoLeader(params) {
|
|
|
333
398
|
summary: `cherry-picked ${commit.slice(0, 12)} from ${worker.name} with -X theirs`,
|
|
334
399
|
}, cwd);
|
|
335
400
|
await sendIntegrationMessageToLeader(teamName, worker, `INTEGRATED: cherry-picked ${commit.slice(0, 12)} from ${worker.name} into leader HEAD ${newLeaderHead.slice(0, 12)} (-X theirs).`, cwd);
|
|
401
|
+
commitHygieneEntries.push({
|
|
402
|
+
recorded_at: new Date().toISOString(),
|
|
403
|
+
operation: 'integration_cherry_pick',
|
|
404
|
+
worker_name: worker.name,
|
|
405
|
+
task_id: worker.assigned_tasks[0],
|
|
406
|
+
status: 'applied',
|
|
407
|
+
operational_commit: newLeaderHead,
|
|
408
|
+
source_commit: commit,
|
|
409
|
+
leader_head_before: leaderHead,
|
|
410
|
+
leader_head_after: newLeaderHead,
|
|
411
|
+
worktree_path: worktreePath,
|
|
412
|
+
detail: 'Leader created a runtime cherry-pick commit while integrating diverged worker history.',
|
|
413
|
+
});
|
|
336
414
|
}
|
|
337
415
|
if (allPicked) {
|
|
338
416
|
integratedWorkerNames.add(worker.name);
|
|
@@ -376,8 +454,10 @@ async function integrateWorkerCommitsIntoLeader(params) {
|
|
|
376
454
|
continue;
|
|
377
455
|
}
|
|
378
456
|
// Rebase with -X ours (in rebase context, "ours" = upstream = leader wins)
|
|
457
|
+
const workerHeadBeforeRebase = resolveWorkerHead(worktreePath);
|
|
379
458
|
const rebase = runGitCommand(repoRoot, ['rebase', '-X', 'ours', newLeaderHead], worktreePath);
|
|
380
459
|
if (rebase.ok) {
|
|
460
|
+
const workerHeadAfterRebase = resolveWorkerHead(worktreePath);
|
|
381
461
|
const state = next[worker.name] ?? {};
|
|
382
462
|
state.last_rebased_leader_head = newLeaderHead;
|
|
383
463
|
state.status = 'idle';
|
|
@@ -391,6 +471,19 @@ async function integrateWorkerCommitsIntoLeader(params) {
|
|
|
391
471
|
worktree_path: worktreePath,
|
|
392
472
|
summary: `cross-rebased ${worker.name} onto ${newLeaderHead.slice(0, 12)} (-X ours)`,
|
|
393
473
|
}, cwd);
|
|
474
|
+
commitHygieneEntries.push({
|
|
475
|
+
recorded_at: new Date().toISOString(),
|
|
476
|
+
operation: 'cross_rebase',
|
|
477
|
+
worker_name: worker.name,
|
|
478
|
+
task_id: worker.assigned_tasks[0],
|
|
479
|
+
status: 'applied',
|
|
480
|
+
operational_commit: workerHeadAfterRebase,
|
|
481
|
+
leader_head_after: newLeaderHead,
|
|
482
|
+
worker_head_before: workerHeadBeforeRebase,
|
|
483
|
+
worker_head_after: workerHeadAfterRebase,
|
|
484
|
+
worktree_path: worktreePath,
|
|
485
|
+
detail: 'Runtime rebase rewrote worker history onto the updated leader head.',
|
|
486
|
+
});
|
|
394
487
|
}
|
|
395
488
|
else {
|
|
396
489
|
// Rebase failed — abort to restore worktree, log for retry next cycle
|
|
@@ -415,6 +508,9 @@ async function integrateWorkerCommitsIntoLeader(params) {
|
|
|
415
508
|
}
|
|
416
509
|
}
|
|
417
510
|
}
|
|
511
|
+
if (commitHygieneEntries.length > 0) {
|
|
512
|
+
await appendTeamCommitHygieneEntries(teamName, commitHygieneEntries, artifactCwd);
|
|
513
|
+
}
|
|
418
514
|
return next;
|
|
419
515
|
}
|
|
420
516
|
function renderWorktreeMergeReport(report) {
|
|
@@ -427,6 +523,8 @@ function renderWorktreeMergeReport(report) {
|
|
|
427
523
|
`- synthetic_commit: ${report.syntheticCommit ?? 'none'}`,
|
|
428
524
|
`- merge_outcome: ${report.mergeOutcome}`,
|
|
429
525
|
`- merge_detail: ${report.mergeDetail}`,
|
|
526
|
+
`- leader_head_before: ${report.leaderHeadBefore ?? 'none'}`,
|
|
527
|
+
`- leader_head_after: ${report.leaderHeadAfter ?? 'none'}`,
|
|
430
528
|
'',
|
|
431
529
|
'## Summary',
|
|
432
530
|
report.summaryText ?? 'sparkshell summary unavailable; using raw diff fallback.',
|
|
@@ -459,6 +557,8 @@ async function prepareShutdownMergeReport(worker, leaderCwd) {
|
|
|
459
557
|
summaryText: null,
|
|
460
558
|
mergeOutcome: 'skipped',
|
|
461
559
|
mergeDetail: addResult.stderr || 'git add -A failed',
|
|
560
|
+
leaderHeadBefore: resolveLeaderHead(repoRoot, leaderCwd),
|
|
561
|
+
leaderHeadAfter: resolveLeaderHead(repoRoot, leaderCwd),
|
|
462
562
|
};
|
|
463
563
|
}
|
|
464
564
|
const commitResult = runGitCommand(repoRoot, ['commit', '--no-verify', '-m', `omx(team): checkpoint ${worker.name} shutdown changes`], worktreePath);
|
|
@@ -477,6 +577,8 @@ async function prepareShutdownMergeReport(worker, leaderCwd) {
|
|
|
477
577
|
summaryText: null,
|
|
478
578
|
mergeOutcome: 'skipped',
|
|
479
579
|
mergeDetail: commitResult.stderr || 'git commit failed',
|
|
580
|
+
leaderHeadBefore: resolveLeaderHead(repoRoot, leaderCwd),
|
|
581
|
+
leaderHeadAfter: resolveLeaderHead(repoRoot, leaderCwd),
|
|
480
582
|
};
|
|
481
583
|
}
|
|
482
584
|
}
|
|
@@ -485,8 +587,10 @@ async function prepareShutdownMergeReport(worker, leaderCwd) {
|
|
|
485
587
|
const diffText = getWorktreeDiffText(worktreePath);
|
|
486
588
|
const summaryText = summarizeWorktreeDiffWithSparkShell(worktreePath);
|
|
487
589
|
const reportPath = join(worktreePath, '.omx', 'diff.md');
|
|
590
|
+
const leaderHeadBefore = resolveLeaderHead(repoRoot, leaderCwd);
|
|
488
591
|
let mergeOutcome = 'skipped';
|
|
489
592
|
let mergeDetail = 'worktree merge skipped';
|
|
593
|
+
let leaderHeadAfter = leaderHeadBefore;
|
|
490
594
|
if (sourceRef) {
|
|
491
595
|
const alreadyMerged = runGitCommand(repoRoot, ['merge-base', '--is-ancestor', sourceRef, 'HEAD'], leaderCwd);
|
|
492
596
|
if (alreadyMerged.ok) {
|
|
@@ -498,11 +602,13 @@ async function prepareShutdownMergeReport(worker, leaderCwd) {
|
|
|
498
602
|
if (mergeResult.ok) {
|
|
499
603
|
mergeOutcome = 'merged';
|
|
500
604
|
mergeDetail = mergeResult.stdout || 'merged successfully';
|
|
605
|
+
leaderHeadAfter = resolveLeaderHead(repoRoot, leaderCwd) ?? leaderHeadBefore;
|
|
501
606
|
}
|
|
502
607
|
else {
|
|
503
608
|
mergeOutcome = 'conflict';
|
|
504
609
|
mergeDetail = mergeResult.stderr || mergeResult.stdout || 'merge failed';
|
|
505
610
|
runGitCommand(repoRoot, ['merge', '--abort'], leaderCwd);
|
|
611
|
+
leaderHeadAfter = resolveLeaderHead(repoRoot, leaderCwd) ?? leaderHeadBefore;
|
|
506
612
|
}
|
|
507
613
|
}
|
|
508
614
|
}
|
|
@@ -516,6 +622,8 @@ async function prepareShutdownMergeReport(worker, leaderCwd) {
|
|
|
516
622
|
summaryText,
|
|
517
623
|
mergeOutcome,
|
|
518
624
|
mergeDetail,
|
|
625
|
+
leaderHeadBefore,
|
|
626
|
+
leaderHeadAfter,
|
|
519
627
|
};
|
|
520
628
|
await mkdir(join(worktreePath, '.omx'), { recursive: true });
|
|
521
629
|
await writeFile(reportPath, renderWorktreeMergeReport(report), 'utf-8');
|
|
@@ -523,11 +631,14 @@ async function prepareShutdownMergeReport(worker, leaderCwd) {
|
|
|
523
631
|
return report;
|
|
524
632
|
}
|
|
525
633
|
async function prepareWorkerWorktreeShutdownReports(config, leaderCwd) {
|
|
634
|
+
const reports = [];
|
|
526
635
|
for (const worker of config.workers) {
|
|
527
636
|
if (!worker.worktree_path || !worker.worktree_repo_root)
|
|
528
637
|
continue;
|
|
529
638
|
try {
|
|
530
|
-
await prepareShutdownMergeReport(worker, leaderCwd);
|
|
639
|
+
const report = await prepareShutdownMergeReport(worker, leaderCwd);
|
|
640
|
+
if (report)
|
|
641
|
+
reports.push(report);
|
|
531
642
|
}
|
|
532
643
|
catch (error) {
|
|
533
644
|
const worktreePath = resolve(worker.worktree_path);
|
|
@@ -546,6 +657,7 @@ async function prepareWorkerWorktreeShutdownReports(config, leaderCwd) {
|
|
|
546
657
|
process.stdout.write(`${fallback}\n`);
|
|
547
658
|
}
|
|
548
659
|
}
|
|
660
|
+
return reports;
|
|
549
661
|
}
|
|
550
662
|
function resolveEffectiveTeamWorktreeMode(leaderCwd, requestedMode) {
|
|
551
663
|
if (!isGitRepository(leaderCwd)) {
|
|
@@ -692,12 +804,20 @@ function registerPromptWorkerHandle(teamName, workerName, child) {
|
|
|
692
804
|
}
|
|
693
805
|
const processPid = pid;
|
|
694
806
|
const existingTeamHandles = promptWorkerRegistry.get(teamName) ?? new Map();
|
|
695
|
-
existingTeamHandles.set(workerName, {
|
|
807
|
+
existingTeamHandles.set(workerName, {
|
|
808
|
+
child,
|
|
809
|
+
pid: processPid,
|
|
810
|
+
processGroupId: process.platform !== 'win32' ? processPid : null,
|
|
811
|
+
});
|
|
696
812
|
promptWorkerRegistry.set(teamName, existingTeamHandles);
|
|
697
813
|
child.on('exit', () => {
|
|
698
814
|
const teamHandles = promptWorkerRegistry.get(teamName);
|
|
699
815
|
if (!teamHandles)
|
|
700
816
|
return;
|
|
817
|
+
const handle = teamHandles.get(workerName);
|
|
818
|
+
if (handle?.processGroupId && isProcessGroupAlive(handle.processGroupId)) {
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
701
821
|
teamHandles.delete(workerName);
|
|
702
822
|
if (teamHandles.size === 0)
|
|
703
823
|
promptWorkerRegistry.delete(teamName);
|
|
@@ -728,67 +848,215 @@ function isPidAlive(pid) {
|
|
|
728
848
|
return false;
|
|
729
849
|
}
|
|
730
850
|
}
|
|
731
|
-
|
|
732
|
-
if (
|
|
851
|
+
function isProcessGroupAlive(processGroupId) {
|
|
852
|
+
if (process.platform === 'win32')
|
|
853
|
+
return false;
|
|
854
|
+
if (!Number.isFinite(processGroupId) || processGroupId <= 0)
|
|
855
|
+
return false;
|
|
856
|
+
try {
|
|
857
|
+
process.kill(-processGroupId, 0);
|
|
858
|
+
return true;
|
|
859
|
+
}
|
|
860
|
+
catch (err) {
|
|
861
|
+
if (err.code === 'ESRCH')
|
|
862
|
+
return false;
|
|
863
|
+
process.stderr.write(`[team/runtime] operation failed: ${err}\n`);
|
|
864
|
+
return false;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
function listProcessTreeEntries() {
|
|
868
|
+
if (process.platform === 'win32')
|
|
869
|
+
return [];
|
|
870
|
+
const result = spawnSync('ps', ['axww', '-o', 'pid=,ppid='], {
|
|
871
|
+
encoding: 'utf-8',
|
|
872
|
+
windowsHide: true,
|
|
873
|
+
});
|
|
874
|
+
if (result.status !== 0 || typeof result.stdout !== 'string')
|
|
875
|
+
return [];
|
|
876
|
+
return result.stdout
|
|
877
|
+
.split(/\r?\n/)
|
|
878
|
+
.map((line) => line.trim())
|
|
879
|
+
.filter(Boolean)
|
|
880
|
+
.map((line) => {
|
|
881
|
+
const match = line.match(/^(\d+)\s+(\d+)$/);
|
|
882
|
+
if (!match)
|
|
883
|
+
return null;
|
|
884
|
+
const pid = Number.parseInt(match[1], 10);
|
|
885
|
+
const ppid = Number.parseInt(match[2], 10);
|
|
886
|
+
if (!Number.isFinite(pid) || pid <= 0)
|
|
887
|
+
return null;
|
|
888
|
+
if (!Number.isFinite(ppid) || ppid < 0)
|
|
889
|
+
return null;
|
|
890
|
+
return { pid, ppid };
|
|
891
|
+
})
|
|
892
|
+
.filter((entry) => entry !== null);
|
|
893
|
+
}
|
|
894
|
+
function collectProcessTreePids(rootPid) {
|
|
895
|
+
if (!Number.isFinite(rootPid) || rootPid <= 0)
|
|
896
|
+
return [];
|
|
897
|
+
const childrenByPid = new Map();
|
|
898
|
+
for (const entry of listProcessTreeEntries()) {
|
|
899
|
+
const siblings = childrenByPid.get(entry.ppid) ?? [];
|
|
900
|
+
siblings.push(entry.pid);
|
|
901
|
+
childrenByPid.set(entry.ppid, siblings);
|
|
902
|
+
}
|
|
903
|
+
const ordered = [];
|
|
904
|
+
const stack = [rootPid];
|
|
905
|
+
const seen = new Set();
|
|
906
|
+
while (stack.length > 0) {
|
|
907
|
+
const pid = stack.pop();
|
|
908
|
+
if (seen.has(pid))
|
|
909
|
+
continue;
|
|
910
|
+
seen.add(pid);
|
|
911
|
+
ordered.push(pid);
|
|
912
|
+
for (const childPid of childrenByPid.get(pid) ?? []) {
|
|
913
|
+
if (!seen.has(childPid))
|
|
914
|
+
stack.push(childPid);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
return ordered.reverse();
|
|
918
|
+
}
|
|
919
|
+
async function waitForTrackedPidsExit(pids, timeoutMs) {
|
|
920
|
+
const tracked = [...new Set(pids.filter((pid) => Number.isFinite(pid) && pid > 0))];
|
|
921
|
+
if (tracked.length === 0)
|
|
733
922
|
return true;
|
|
734
923
|
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
735
924
|
while (Date.now() < deadline) {
|
|
925
|
+
if (tracked.every((pid) => !isPidAlive(pid)))
|
|
926
|
+
return true;
|
|
736
927
|
await new Promise((resolve) => setTimeout(resolve, PROMPT_WORKER_EXIT_POLL_MS));
|
|
928
|
+
}
|
|
929
|
+
return tracked.every((pid) => !isPidAlive(pid));
|
|
930
|
+
}
|
|
931
|
+
async function terminateTrackedProcessTree(rootPid, processGroupId = null, graceMs = PROMPT_WORKER_SIGTERM_WAIT_MS, killWaitMs = PROMPT_WORKER_SIGKILL_WAIT_MS) {
|
|
932
|
+
if (processGroupId && process.platform !== 'win32') {
|
|
933
|
+
const trackedPids = collectProcessTreePids(rootPid);
|
|
934
|
+
try {
|
|
935
|
+
process.kill(-processGroupId, 'SIGTERM');
|
|
936
|
+
}
|
|
937
|
+
catch (err) {
|
|
938
|
+
if (err.code !== 'ESRCH') {
|
|
939
|
+
process.stderr.write(`[team/runtime] operation failed: ${err}\n`);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
for (const pid of trackedPids) {
|
|
943
|
+
if (pid === rootPid)
|
|
944
|
+
continue;
|
|
945
|
+
try {
|
|
946
|
+
process.kill(pid, 'SIGTERM');
|
|
947
|
+
}
|
|
948
|
+
catch (err) {
|
|
949
|
+
if (err.code !== 'ESRCH') {
|
|
950
|
+
process.stderr.write(`[team/runtime] operation failed: ${err}\n`);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
const groupDeadline = Date.now() + Math.max(0, graceMs);
|
|
955
|
+
while (Date.now() < groupDeadline) {
|
|
956
|
+
const groupAlive = isProcessGroupAlive(processGroupId);
|
|
957
|
+
const descendantsAlive = trackedPids.some((pid) => isPidAlive(pid));
|
|
958
|
+
if (!groupAlive && !descendantsAlive) {
|
|
959
|
+
return { terminated: true, forcedKill: false, trackedPids };
|
|
960
|
+
}
|
|
961
|
+
await new Promise((resolve) => setTimeout(resolve, PROMPT_WORKER_EXIT_POLL_MS));
|
|
962
|
+
}
|
|
963
|
+
try {
|
|
964
|
+
process.kill(-processGroupId, 'SIGKILL');
|
|
965
|
+
}
|
|
966
|
+
catch (err) {
|
|
967
|
+
if (err.code !== 'ESRCH') {
|
|
968
|
+
process.stderr.write(`[team/runtime] operation failed: ${err}\n`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
for (const pid of trackedPids) {
|
|
972
|
+
if (!isPidAlive(pid))
|
|
973
|
+
continue;
|
|
974
|
+
try {
|
|
975
|
+
process.kill(pid, 'SIGKILL');
|
|
976
|
+
}
|
|
977
|
+
catch (err) {
|
|
978
|
+
if (err.code !== 'ESRCH') {
|
|
979
|
+
process.stderr.write(`[team/runtime] operation failed: ${err}\n`);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
const killDeadline = Date.now() + Math.max(0, killWaitMs);
|
|
984
|
+
while (Date.now() < killDeadline) {
|
|
985
|
+
const groupAlive = isProcessGroupAlive(processGroupId);
|
|
986
|
+
const descendantsAlive = trackedPids.some((pid) => isPidAlive(pid));
|
|
987
|
+
if (!groupAlive && !descendantsAlive) {
|
|
988
|
+
return { terminated: true, forcedKill: true, trackedPids };
|
|
989
|
+
}
|
|
990
|
+
await new Promise((resolve) => setTimeout(resolve, PROMPT_WORKER_EXIT_POLL_MS));
|
|
991
|
+
}
|
|
992
|
+
return {
|
|
993
|
+
terminated: !isProcessGroupAlive(processGroupId) && trackedPids.every((pid) => !isPidAlive(pid)),
|
|
994
|
+
forcedKill: true,
|
|
995
|
+
trackedPids,
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
const trackedPids = collectProcessTreePids(rootPid);
|
|
999
|
+
if (trackedPids.length === 0) {
|
|
1000
|
+
return {
|
|
1001
|
+
terminated: !isPidAlive(rootPid),
|
|
1002
|
+
forcedKill: false,
|
|
1003
|
+
trackedPids: [],
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
for (const pid of trackedPids) {
|
|
1007
|
+
try {
|
|
1008
|
+
process.kill(pid, 'SIGTERM');
|
|
1009
|
+
}
|
|
1010
|
+
catch (err) {
|
|
1011
|
+
if (err.code !== 'ESRCH') {
|
|
1012
|
+
process.stderr.write(`[team/runtime] operation failed: ${err}\n`);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
if (await waitForTrackedPidsExit(trackedPids, graceMs)) {
|
|
1017
|
+
return { terminated: true, forcedKill: false, trackedPids };
|
|
1018
|
+
}
|
|
1019
|
+
for (const pid of trackedPids) {
|
|
737
1020
|
if (!isPidAlive(pid))
|
|
738
|
-
|
|
1021
|
+
continue;
|
|
1022
|
+
try {
|
|
1023
|
+
process.kill(pid, 'SIGKILL');
|
|
1024
|
+
}
|
|
1025
|
+
catch (err) {
|
|
1026
|
+
if (err.code !== 'ESRCH') {
|
|
1027
|
+
process.stderr.write(`[team/runtime] operation failed: ${err}\n`);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
739
1030
|
}
|
|
740
|
-
return
|
|
1031
|
+
return {
|
|
1032
|
+
terminated: await waitForTrackedPidsExit(trackedPids, killWaitMs),
|
|
1033
|
+
forcedKill: true,
|
|
1034
|
+
trackedPids,
|
|
1035
|
+
};
|
|
741
1036
|
}
|
|
742
1037
|
async function teardownPromptWorker(teamName, workerName, fallbackPid, cwd, context) {
|
|
743
1038
|
const handle = getPromptWorkerHandle(teamName, workerName);
|
|
744
1039
|
const handlePid = handle?.pid;
|
|
1040
|
+
const processGroupId = handle?.processGroupId ?? null;
|
|
745
1041
|
const pid = (typeof handlePid === 'number' && Number.isFinite(handlePid))
|
|
746
1042
|
? handlePid
|
|
747
1043
|
: (Number.isFinite(fallbackPid) && (fallbackPid ?? 0) > 0 ? fallbackPid : null);
|
|
748
|
-
if (pid === null) {
|
|
1044
|
+
if (pid === null && processGroupId === null) {
|
|
749
1045
|
removePromptWorkerHandle(teamName, workerName);
|
|
750
1046
|
return { terminated: true, forcedKill: false, pid: null };
|
|
751
1047
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
}
|
|
756
|
-
else {
|
|
757
|
-
process.kill(pid, 'SIGTERM');
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
catch (err) {
|
|
761
|
-
if (err.code !== 'ESRCH') {
|
|
762
|
-
process.stderr.write(`[team/runtime] operation failed: ${err}\n`);
|
|
763
|
-
}
|
|
764
|
-
// Best effort.
|
|
765
|
-
}
|
|
766
|
-
const exitedOnTerm = await waitForPidExit(pid, PROMPT_WORKER_SIGTERM_WAIT_MS);
|
|
767
|
-
if (exitedOnTerm) {
|
|
1048
|
+
const teardown = await terminateTrackedProcessTree(pid ?? 0, processGroupId);
|
|
1049
|
+
const processGone = processGroupId ? !isProcessGroupAlive(processGroupId) : !isPidAlive(pid);
|
|
1050
|
+
if (teardown.terminated && processGone) {
|
|
768
1051
|
removePromptWorkerHandle(teamName, workerName);
|
|
769
|
-
return { terminated: true, forcedKill:
|
|
1052
|
+
return { terminated: true, forcedKill: teardown.forcedKill, pid };
|
|
770
1053
|
}
|
|
771
1054
|
await appendTeamEvent(teamName, {
|
|
772
1055
|
type: 'worker_stopped',
|
|
773
1056
|
worker: workerName,
|
|
774
1057
|
reason: `prompt_force_kill:${context}:pid=${pid}`,
|
|
775
1058
|
}, cwd).catch(() => { });
|
|
776
|
-
|
|
777
|
-
if (handle && handle.child.exitCode === null) {
|
|
778
|
-
handle.child.kill('SIGKILL');
|
|
779
|
-
}
|
|
780
|
-
else {
|
|
781
|
-
process.kill(pid, 'SIGKILL');
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
catch (err) {
|
|
785
|
-
if (err.code !== 'ESRCH') {
|
|
786
|
-
process.stderr.write(`[team/runtime] operation failed: ${err}\n`);
|
|
787
|
-
}
|
|
788
|
-
// Best effort.
|
|
789
|
-
}
|
|
790
|
-
const exitedOnKill = await waitForPidExit(pid, PROMPT_WORKER_SIGKILL_WAIT_MS);
|
|
791
|
-
if (!exitedOnKill) {
|
|
1059
|
+
if (!teardown.terminated) {
|
|
792
1060
|
await appendTeamEvent(teamName, {
|
|
793
1061
|
type: 'worker_stopped',
|
|
794
1062
|
worker: workerName,
|
|
@@ -796,18 +1064,22 @@ async function teardownPromptWorker(teamName, workerName, fallbackPid, cwd, cont
|
|
|
796
1064
|
}, cwd).catch(() => { });
|
|
797
1065
|
return {
|
|
798
1066
|
terminated: false,
|
|
799
|
-
forcedKill:
|
|
1067
|
+
forcedKill: teardown.forcedKill,
|
|
800
1068
|
pid,
|
|
801
1069
|
error: 'still_alive_after_sigkill',
|
|
802
1070
|
};
|
|
803
1071
|
}
|
|
804
1072
|
removePromptWorkerHandle(teamName, workerName);
|
|
805
|
-
return { terminated: true, forcedKill:
|
|
1073
|
+
return { terminated: true, forcedKill: teardown.forcedKill, pid };
|
|
806
1074
|
}
|
|
807
1075
|
function isPromptWorkerAlive(config, worker) {
|
|
808
1076
|
const handle = getPromptWorkerHandle(config.name, worker.name);
|
|
809
1077
|
if (handle?.child.exitCode === null && !handle.child.killed)
|
|
810
1078
|
return true;
|
|
1079
|
+
if (handle?.processGroupId && isProcessGroupAlive(handle.processGroupId))
|
|
1080
|
+
return true;
|
|
1081
|
+
if (process.platform !== 'win32' && isProcessGroupAlive(worker.pid))
|
|
1082
|
+
return true;
|
|
811
1083
|
return isPidAlive(worker.pid);
|
|
812
1084
|
}
|
|
813
1085
|
export { TEAM_LOW_COMPLEXITY_DEFAULT_MODEL };
|
|
@@ -816,6 +1088,7 @@ function spawnPromptWorker(teamName, workerName, workerIndex, workerCwd, launchA
|
|
|
816
1088
|
const processSpec = buildWorkerProcessLaunchSpec(teamName, workerIndex, launchArgs, workerCwd, workerEnv, workerCli, initialPrompt);
|
|
817
1089
|
const child = spawn(processSpec.command, processSpec.args, {
|
|
818
1090
|
cwd: workerCwd,
|
|
1091
|
+
detached: process.platform !== 'win32',
|
|
819
1092
|
env: { ...process.env, ...processSpec.env },
|
|
820
1093
|
stdio: ['pipe', 'ignore', 'ignore'],
|
|
821
1094
|
});
|
|
@@ -1255,7 +1528,11 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
|
|
|
1255
1528
|
}
|
|
1256
1529
|
// In split-pane topology, we must not kill the entire tmux session; kill only created panes.
|
|
1257
1530
|
if (sessionName.includes(':')) {
|
|
1258
|
-
for (const paneId of createdWorkerPaneIds) {
|
|
1531
|
+
for (const [index, paneId] of createdWorkerPaneIds.entries()) {
|
|
1532
|
+
const panePid = getWorkerPanePid(sessionName, index + 1, paneId);
|
|
1533
|
+
if (panePid) {
|
|
1534
|
+
await terminateTrackedProcessTree(panePid);
|
|
1535
|
+
}
|
|
1259
1536
|
try {
|
|
1260
1537
|
await killWorkerByPaneIdAsync(paneId, createdLeaderPaneId);
|
|
1261
1538
|
}
|
|
@@ -1669,7 +1946,7 @@ export async function shutdownTeam(teamName, cwd, options = {}) {
|
|
|
1669
1946
|
}
|
|
1670
1947
|
await cleanupTeamState(sanitized, cwd);
|
|
1671
1948
|
restoreTeamModelInstructionsFile(sanitized);
|
|
1672
|
-
return;
|
|
1949
|
+
return { commitHygieneArtifacts: null };
|
|
1673
1950
|
}
|
|
1674
1951
|
const manifest = await readTeamManifestV2(sanitized, cwd);
|
|
1675
1952
|
const governance = resolveGovernancePolicy(manifest?.governance, manifest?.policy);
|
|
@@ -1772,6 +2049,12 @@ export async function shutdownTeam(teamName, cwd, options = {}) {
|
|
|
1772
2049
|
const leaderPaneId = config.leader_pane_id;
|
|
1773
2050
|
const hudPaneId = config.hud_pane_id;
|
|
1774
2051
|
if (config.worker_launch_mode === 'interactive') {
|
|
2052
|
+
const workerPanePids = config.workers
|
|
2053
|
+
.map((w) => getWorkerPanePid(sessionName, w.index, w.pane_id))
|
|
2054
|
+
.filter((pid) => typeof pid === 'number' && Number.isFinite(pid) && pid > 0);
|
|
2055
|
+
for (const panePid of workerPanePids) {
|
|
2056
|
+
await terminateTrackedProcessTree(panePid);
|
|
2057
|
+
}
|
|
1775
2058
|
let resizeHookWarning = null;
|
|
1776
2059
|
if (config.resize_hook_name && config.resize_hook_target) {
|
|
1777
2060
|
const resizeHookName = config.resize_hook_name;
|
|
@@ -1828,7 +2111,50 @@ export async function shutdownTeam(teamName, cwd, options = {}) {
|
|
|
1828
2111
|
throw new Error(`shutdown_prompt_teardown_failed:${promptTeardownFailures.join(',')}`);
|
|
1829
2112
|
}
|
|
1830
2113
|
}
|
|
1831
|
-
await prepareWorkerWorktreeShutdownReports(config, cwd);
|
|
2114
|
+
const shutdownReports = await prepareWorkerWorktreeShutdownReports(config, cwd);
|
|
2115
|
+
const commitHygieneEntries = [];
|
|
2116
|
+
for (const report of shutdownReports) {
|
|
2117
|
+
const worker = config.workers.find((entry) => entry.name === report.workerName);
|
|
2118
|
+
if (report.syntheticCommit) {
|
|
2119
|
+
commitHygieneEntries.push({
|
|
2120
|
+
recorded_at: new Date().toISOString(),
|
|
2121
|
+
operation: 'shutdown_checkpoint',
|
|
2122
|
+
worker_name: report.workerName,
|
|
2123
|
+
task_id: worker?.assigned_tasks[0],
|
|
2124
|
+
status: 'applied',
|
|
2125
|
+
operational_commit: report.syntheticCommit,
|
|
2126
|
+
source_commit: report.sourceRef,
|
|
2127
|
+
worktree_path: report.worktreePath,
|
|
2128
|
+
report_path: report.reportPath,
|
|
2129
|
+
detail: 'Runtime created a shutdown checkpoint commit to preserve worker worktree changes.',
|
|
2130
|
+
});
|
|
2131
|
+
}
|
|
2132
|
+
if (report.sourceRef && report.mergeOutcome !== 'skipped') {
|
|
2133
|
+
commitHygieneEntries.push({
|
|
2134
|
+
recorded_at: new Date().toISOString(),
|
|
2135
|
+
operation: 'shutdown_merge',
|
|
2136
|
+
worker_name: report.workerName,
|
|
2137
|
+
task_id: worker?.assigned_tasks[0],
|
|
2138
|
+
status: report.mergeOutcome === 'merged' ? 'applied' : report.mergeOutcome,
|
|
2139
|
+
operational_commit: report.mergeOutcome === 'merged' ? report.leaderHeadAfter : null,
|
|
2140
|
+
source_commit: report.sourceRef,
|
|
2141
|
+
leader_head_before: report.leaderHeadBefore,
|
|
2142
|
+
leader_head_after: report.leaderHeadAfter,
|
|
2143
|
+
worktree_path: report.worktreePath,
|
|
2144
|
+
report_path: report.reportPath,
|
|
2145
|
+
detail: report.mergeDetail,
|
|
2146
|
+
});
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
const artifactCwd = config.leader_cwd ?? cwd;
|
|
2150
|
+
const ledger = await appendTeamCommitHygieneEntries(sanitized, commitHygieneEntries, artifactCwd);
|
|
2151
|
+
const taskView = await listTasks(sanitized, cwd).catch(() => []);
|
|
2152
|
+
const commitHygieneContext = buildTeamCommitHygieneContext({
|
|
2153
|
+
teamName: sanitized,
|
|
2154
|
+
tasks: taskView,
|
|
2155
|
+
ledger,
|
|
2156
|
+
});
|
|
2157
|
+
const commitHygieneArtifacts = await writeTeamCommitHygieneContext(sanitized, commitHygieneContext, artifactCwd);
|
|
1832
2158
|
// 5. Remove worker worktree-root instructions and team-scoped fallback instructions.
|
|
1833
2159
|
for (const worker of config.workers) {
|
|
1834
2160
|
if (!worker.worktree_path || !worker.team_state_root)
|
|
@@ -1869,6 +2195,7 @@ export async function shutdownTeam(teamName, cwd, options = {}) {
|
|
|
1869
2195
|
if (cleanupErrors.length > 0) {
|
|
1870
2196
|
throw new Error(cleanupErrors.join(' | '));
|
|
1871
2197
|
}
|
|
2198
|
+
return { commitHygieneArtifacts };
|
|
1872
2199
|
}
|
|
1873
2200
|
/**
|
|
1874
2201
|
* Resume monitoring an existing team.
|
|
@@ -2090,22 +2417,6 @@ async function markDispatchRequestLeaderPaneMissingDeferred(params) {
|
|
|
2090
2417
|
}
|
|
2091
2418
|
async function dispatchCriticalInboxInstruction(params) {
|
|
2092
2419
|
const { teamName, config, workerName, workerIndex, paneId, workerCli, inbox, triggerMessage, cwd, dispatchPolicy, inboxCorrelationKey, requireWorkerStartupEvidence, } = params;
|
|
2093
|
-
// --- Rust runtime bridge: dual-write dispatch mutations ---
|
|
2094
|
-
if (isBridgeEnabled()) {
|
|
2095
|
-
try {
|
|
2096
|
-
const bridge = getDefaultBridge(cwd);
|
|
2097
|
-
const bridgeRequestId = `dispatch-${workerName}-${Date.now()}`;
|
|
2098
|
-
bridge.execCommand({
|
|
2099
|
-
command: 'QueueDispatch',
|
|
2100
|
-
request_id: bridgeRequestId,
|
|
2101
|
-
target: workerName,
|
|
2102
|
-
metadata: { kind: 'inbox', inbox_correlation_key: inboxCorrelationKey },
|
|
2103
|
-
});
|
|
2104
|
-
}
|
|
2105
|
-
catch (_bridgeErr) {
|
|
2106
|
-
// Bridge failure is non-fatal — fall through to existing JS logic
|
|
2107
|
-
}
|
|
2108
|
-
}
|
|
2109
2420
|
if (config.worker_launch_mode === 'prompt') {
|
|
2110
2421
|
return await queueInboxInstruction({
|
|
2111
2422
|
teamName,
|
|
@@ -2183,12 +2494,6 @@ async function dispatchCriticalInboxInstruction(params) {
|
|
|
2183
2494
|
if (receipt?.status === 'failed') {
|
|
2184
2495
|
const fallback = await notifyWorkerOutcome(config, workerIndex, triggerMessage, paneId);
|
|
2185
2496
|
if (fallback.ok) {
|
|
2186
|
-
if (isBridgeEnabled()) {
|
|
2187
|
-
try {
|
|
2188
|
-
getDefaultBridge(cwd).execCommand({ command: 'MarkNotified', request_id: queued.request_id, channel: `fallback_confirmed_after_failed_receipt:${fallback.reason}` });
|
|
2189
|
-
}
|
|
2190
|
-
catch (_) { /* non-fatal */ }
|
|
2191
|
-
}
|
|
2192
2497
|
await markDispatchRequestNotified(teamName, queued.request_id, { last_reason: `fallback_confirmed_after_failed_receipt:${fallback.reason}`, failed_at: undefined }, cwd).catch(() => null);
|
|
2193
2498
|
return {
|
|
2194
2499
|
ok: true,
|
|
@@ -2197,12 +2502,6 @@ async function dispatchCriticalInboxInstruction(params) {
|
|
|
2197
2502
|
request_id: queued.request_id,
|
|
2198
2503
|
};
|
|
2199
2504
|
}
|
|
2200
|
-
if (isBridgeEnabled()) {
|
|
2201
|
-
try {
|
|
2202
|
-
getDefaultBridge(cwd).execCommand({ command: 'MarkFailed', request_id: queued.request_id, reason: `fallback_attempted_but_unconfirmed:${fallback.reason}` });
|
|
2203
|
-
}
|
|
2204
|
-
catch (_) { /* non-fatal */ }
|
|
2205
|
-
}
|
|
2206
2505
|
await transitionDispatchRequest(teamName, queued.request_id, receipt.status, 'failed', { last_reason: `fallback_attempted_but_unconfirmed:${fallback.reason}` }, cwd).catch(() => { });
|
|
2207
2506
|
return {
|
|
2208
2507
|
ok: false,
|
|
@@ -2219,20 +2518,8 @@ async function dispatchCriticalInboxInstruction(params) {
|
|
|
2219
2518
|
? `${startupFallbackLabel}_fallback_failed:${fallback.reason}`
|
|
2220
2519
|
: `fallback_attempted_but_unconfirmed:${fallback.reason}`;
|
|
2221
2520
|
if (fallback.ok) {
|
|
2222
|
-
if (isBridgeEnabled()) {
|
|
2223
|
-
try {
|
|
2224
|
-
getDefaultBridge(cwd).execCommand({ command: 'MarkNotified', request_id: queued.request_id, channel: `fallback_confirmed:${fallback.reason}` });
|
|
2225
|
-
}
|
|
2226
|
-
catch (_) { /* non-fatal */ }
|
|
2227
|
-
}
|
|
2228
2521
|
const marked = await markDispatchRequestNotified(teamName, queued.request_id, { last_reason: `fallback_confirmed:${fallback.reason}` }, cwd);
|
|
2229
2522
|
if (!marked) {
|
|
2230
|
-
if (isBridgeEnabled()) {
|
|
2231
|
-
try {
|
|
2232
|
-
getDefaultBridge(cwd).execCommand({ command: 'MarkFailed', request_id: queued.request_id, reason: `fallback_confirmed_after_failed_receipt:${fallback.reason}` });
|
|
2233
|
-
}
|
|
2234
|
-
catch (_) { /* non-fatal */ }
|
|
2235
|
-
}
|
|
2236
2523
|
await transitionDispatchRequest(teamName, queued.request_id, 'failed', 'failed', { last_reason: `fallback_confirmed_after_failed_receipt:${fallback.reason}` }, cwd).catch(() => { });
|
|
2237
2524
|
}
|
|
2238
2525
|
return {
|
|
@@ -2246,12 +2533,6 @@ async function dispatchCriticalInboxInstruction(params) {
|
|
|
2246
2533
|
}
|
|
2247
2534
|
const current = await readDispatchRequest(teamName, queued.request_id, cwd);
|
|
2248
2535
|
if (current && current.status !== 'failed') {
|
|
2249
|
-
if (isBridgeEnabled()) {
|
|
2250
|
-
try {
|
|
2251
|
-
getDefaultBridge(cwd).execCommand({ command: 'MarkFailed', request_id: queued.request_id, reason: fallbackFailureReason });
|
|
2252
|
-
}
|
|
2253
|
-
catch (_) { /* non-fatal */ }
|
|
2254
|
-
}
|
|
2255
2536
|
await transitionDispatchRequest(teamName, queued.request_id, current.status, 'failed', { last_reason: fallbackFailureReason }, cwd).catch(() => { });
|
|
2256
2537
|
}
|
|
2257
2538
|
return {
|
|
@@ -2383,21 +2664,6 @@ async function deliverPendingMailboxMessages(teamName, config, workers, previous
|
|
|
2383
2664
|
continue;
|
|
2384
2665
|
for (const msg of unnotified) {
|
|
2385
2666
|
const triggerMessage = generateMailboxTriggerMessage(worker.name, teamName, 1, resolveInstructionStateRoot(workerInfo.worktree_path));
|
|
2386
|
-
// --- Rust runtime bridge: dual-write mailbox dispatch ---
|
|
2387
|
-
if (isBridgeEnabled()) {
|
|
2388
|
-
try {
|
|
2389
|
-
const bridge = getDefaultBridge(cwd);
|
|
2390
|
-
bridge.execCommand({
|
|
2391
|
-
command: 'QueueDispatch',
|
|
2392
|
-
request_id: `mailbox-${msg.message_id}-${Date.now()}`,
|
|
2393
|
-
target: worker.name,
|
|
2394
|
-
metadata: { kind: 'mailbox', message_id: msg.message_id },
|
|
2395
|
-
});
|
|
2396
|
-
}
|
|
2397
|
-
catch (_bridgeErr) {
|
|
2398
|
-
// Bridge failure is non-fatal — fall through to existing JS logic
|
|
2399
|
-
}
|
|
2400
|
-
}
|
|
2401
2667
|
const transportPreference = config.worker_launch_mode === 'prompt'
|
|
2402
2668
|
? 'prompt_stdin'
|
|
2403
2669
|
: (dispatchPolicy.dispatch_mode === 'transport_direct' ? 'transport_direct' : 'hook_preferred_with_fallback');
|
|
@@ -2431,16 +2697,6 @@ async function deliverPendingMailboxMessages(teamName, config, workers, previous
|
|
|
2431
2697
|
const direct = await notifyWorkerOutcome(config, workerInfo.index, triggerMessage, workerInfo.pane_id);
|
|
2432
2698
|
outcome = { ...direct, request_id: queued.request.request_id, message_id: msg.message_id };
|
|
2433
2699
|
if (outcome.ok) {
|
|
2434
|
-
if (isBridgeEnabled()) {
|
|
2435
|
-
try {
|
|
2436
|
-
getDefaultBridge(cwd).execCommand({ command: 'MarkNotified', request_id: queued.request.request_id, channel: `direct:${outcome.reason}` });
|
|
2437
|
-
}
|
|
2438
|
-
catch (_) { /* non-fatal */ }
|
|
2439
|
-
try {
|
|
2440
|
-
getDefaultBridge(cwd).execCommand({ command: 'MarkMailboxNotified', message_id: msg.message_id });
|
|
2441
|
-
}
|
|
2442
|
-
catch (_) { /* non-fatal */ }
|
|
2443
|
-
}
|
|
2444
2700
|
await markMessageNotified(teamName, worker.name, msg.message_id, cwd).catch(() => false);
|
|
2445
2701
|
await markDispatchRequestNotified(teamName, queued.request.request_id, { message_id: msg.message_id, last_reason: outcome.reason }, cwd).catch(() => null);
|
|
2446
2702
|
}
|
|
@@ -2464,23 +2720,6 @@ export async function sendWorkerMessage(teamName, fromWorker, toWorker, body, cw
|
|
|
2464
2720
|
throw new Error(`Team ${sanitized} not found`);
|
|
2465
2721
|
const manifest = await readTeamManifestV2(sanitized, cwd);
|
|
2466
2722
|
const dispatchPolicy = resolveDispatchPolicy(manifest?.policy, config.worker_launch_mode);
|
|
2467
|
-
// --- Rust runtime bridge: dual-write mailbox message creation ---
|
|
2468
|
-
if (isBridgeEnabled()) {
|
|
2469
|
-
try {
|
|
2470
|
-
const bridge = getDefaultBridge(cwd);
|
|
2471
|
-
const bridgeMessageId = `msg-${fromWorker}-${toWorker}-${Date.now()}`;
|
|
2472
|
-
bridge.execCommand({
|
|
2473
|
-
command: 'CreateMailboxMessage',
|
|
2474
|
-
message_id: bridgeMessageId,
|
|
2475
|
-
from_worker: fromWorker,
|
|
2476
|
-
to_worker: toWorker,
|
|
2477
|
-
body,
|
|
2478
|
-
});
|
|
2479
|
-
}
|
|
2480
|
-
catch (_bridgeErr) {
|
|
2481
|
-
// Bridge failure is non-fatal — fall through to existing JS logic
|
|
2482
|
-
}
|
|
2483
|
-
}
|
|
2484
2723
|
if (toWorker === 'leader-fixed') {
|
|
2485
2724
|
const leaderTriggerMessage = generateLeaderMailboxTriggerMessage(sanitized, fromWorker);
|
|
2486
2725
|
const leaderTransportPreference = dispatchPolicy.dispatch_mode === 'transport_direct'
|
|
@@ -2538,7 +2777,7 @@ export async function sendWorkerMessage(teamName, fromWorker, toWorker, body, cw
|
|
|
2538
2777
|
}
|
|
2539
2778
|
if (!finalOutcome.ok)
|
|
2540
2779
|
throw new Error(`mailbox_notify_failed:${finalOutcome.reason}`);
|
|
2541
|
-
return;
|
|
2780
|
+
return finalOutcome;
|
|
2542
2781
|
}
|
|
2543
2782
|
const recipient = config.workers.find((w) => w.name === toWorker);
|
|
2544
2783
|
if (!recipient)
|
|
@@ -2583,6 +2822,7 @@ export async function sendWorkerMessage(teamName, fromWorker, toWorker, body, cw
|
|
|
2583
2822
|
}
|
|
2584
2823
|
if (!finalOutcome.ok)
|
|
2585
2824
|
throw new Error(`mailbox_notify_failed:${finalOutcome.reason}`);
|
|
2825
|
+
return finalOutcome;
|
|
2586
2826
|
}
|
|
2587
2827
|
export async function broadcastWorkerMessage(teamName, fromWorker, body, cwd) {
|
|
2588
2828
|
const sanitized = sanitizeTeamName(teamName);
|
|
@@ -2594,25 +2834,6 @@ export async function broadcastWorkerMessage(teamName, fromWorker, body, cwd) {
|
|
|
2594
2834
|
const transportPreference = config.worker_launch_mode === 'prompt'
|
|
2595
2835
|
? 'prompt_stdin'
|
|
2596
2836
|
: (dispatchPolicy.dispatch_mode === 'transport_direct' ? 'transport_direct' : 'hook_preferred_with_fallback');
|
|
2597
|
-
// --- Rust runtime bridge: dual-write broadcast mailbox messages ---
|
|
2598
|
-
if (isBridgeEnabled()) {
|
|
2599
|
-
try {
|
|
2600
|
-
const bridge = getDefaultBridge(cwd);
|
|
2601
|
-
for (const w of config.workers) {
|
|
2602
|
-
const bridgeMessageId = `bcast-${fromWorker}-${w.name}-${Date.now()}`;
|
|
2603
|
-
bridge.execCommand({
|
|
2604
|
-
command: 'CreateMailboxMessage',
|
|
2605
|
-
message_id: bridgeMessageId,
|
|
2606
|
-
from_worker: fromWorker,
|
|
2607
|
-
to_worker: w.name,
|
|
2608
|
-
body,
|
|
2609
|
-
});
|
|
2610
|
-
}
|
|
2611
|
-
}
|
|
2612
|
-
catch (_bridgeErr) {
|
|
2613
|
-
// Bridge failure is non-fatal — fall through to existing JS logic
|
|
2614
|
-
}
|
|
2615
|
-
}
|
|
2616
2837
|
const outcomes = await queueBroadcastMailboxMessage({
|
|
2617
2838
|
teamName: sanitized,
|
|
2618
2839
|
fromWorker,
|