@quantiya/codevibe-core 2.0.1 → 2.0.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/dist/__tests__/prompt-parser.test.d.ts +1 -0
- package/dist/adapter/registry.d.ts +69 -2
- package/dist/appsync/__tests__/appsync-client-event-source-filter.test.d.ts +1 -0
- package/dist/appsync/__tests__/appsync-client-task-group.test.d.ts +1 -0
- package/dist/appsync/__tests__/cleanup-subscription.test.d.ts +1 -0
- package/dist/appsync/__tests__/coerce-awsjson-and-review-summary.test.d.ts +1 -0
- package/dist/appsync/__tests__/cp1f-quorum-sdk.test.d.ts +1 -0
- package/dist/appsync/__tests__/event-throttle.test.d.ts +1 -0
- package/dist/appsync/appsync-client.d.ts +621 -10
- package/dist/appsync/event-throttle.d.ts +28 -0
- package/dist/appsync/index.d.ts +3 -0
- package/dist/appsync/queries.d.ts +19 -0
- package/dist/audit-keys/index.d.ts +32 -0
- package/dist/auth/auth-service.d.ts +32 -2
- package/dist/auth/auth-telemetry.d.ts +19 -1
- package/dist/auth/index.d.ts +1 -0
- package/dist/companion-mode/__tests__/detect-parallel.test.d.ts +1 -0
- package/dist/companion-mode/index.d.ts +4 -3
- package/dist/companion-mode/persist-preference.d.ts +1 -1
- package/dist/config/config.d.ts +8 -1
- package/dist/continuation/__tests__/dirty-state-collector.test.d.ts +1 -0
- package/dist/continuation/__tests__/integration.test.d.ts +1 -0
- package/dist/continuation/__tests__/packet-reader.test.d.ts +1 -0
- package/dist/continuation/__tests__/packet-writer.test.d.ts +1 -0
- package/dist/continuation/__tests__/parity.test.d.ts +1 -0
- package/dist/continuation/__tests__/status-cli.test.d.ts +1 -0
- package/dist/continuation/dirty-state-collector.d.ts +46 -0
- package/dist/continuation/index.d.ts +8 -0
- package/dist/continuation/packet-reader.d.ts +33 -0
- package/dist/continuation/packet-writer.d.ts +86 -0
- package/dist/continuation/types.d.ts +239 -0
- package/dist/credential-broker/__tests__/broker.test.d.ts +1 -0
- package/dist/credential-broker/__tests__/canonical.test.d.ts +1 -0
- package/dist/credential-broker/__tests__/scrubber.test.d.ts +1 -0
- package/dist/credential-broker/__tests__/upstream-client.test.d.ts +1 -0
- package/dist/credential-broker/__tests__/vendor-key-store.test.d.ts +1 -0
- package/dist/credential-broker/audit-sink.d.ts +69 -0
- package/dist/credential-broker/broker-config.d.ts +39 -0
- package/dist/credential-broker/broker-token.d.ts +123 -0
- package/dist/credential-broker/broker.d.ts +165 -0
- package/dist/credential-broker/canonical.d.ts +119 -0
- package/dist/credential-broker/index.d.ts +15 -0
- package/dist/credential-broker/scrubber.d.ts +45 -0
- package/dist/credential-broker/types.d.ts +141 -0
- package/dist/credential-broker/upstream-client.d.ts +20 -0
- package/dist/credential-broker/vendor-key-store.d.ts +54 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +427 -124
- package/dist/keychain/__tests__/keychain-backend.test.d.ts +1 -0
- package/dist/keychain/keychain-backend.d.ts +82 -0
- package/dist/local-executor/__tests__/agentic-disposable-repo.test.d.ts +1 -0
- package/dist/local-executor/__tests__/agentic-resolve.test.d.ts +1 -0
- package/dist/local-executor/__tests__/agentic-resolver-flag.test.d.ts +1 -0
- package/dist/local-executor/__tests__/class-a-sign.test.d.ts +1 -0
- package/dist/local-executor/__tests__/class1-reconcilers.test.d.ts +1 -0
- package/dist/local-executor/__tests__/class2-resolver.test.d.ts +1 -0
- package/dist/local-executor/__tests__/confined-model-caller.test.d.ts +1 -0
- package/dist/local-executor/__tests__/conflict-classifier.test.d.ts +1 -0
- package/dist/local-executor/__tests__/conflict-resolution-durable-store.test.d.ts +1 -0
- package/dist/local-executor/__tests__/conflict-resolution-store.test.d.ts +1 -0
- package/dist/local-executor/__tests__/cp1f-quorum-consumer.test.d.ts +1 -0
- package/dist/local-executor/__tests__/cp7-audit-writer.test.d.ts +1 -0
- package/dist/local-executor/__tests__/durable-store.test.d.ts +1 -0
- package/dist/local-executor/__tests__/git-lock-retry.test.d.ts +1 -0
- package/dist/local-executor/__tests__/implementor-argv.test.d.ts +1 -0
- package/dist/local-executor/__tests__/local-executor-teams.test.d.ts +1 -0
- package/dist/local-executor/__tests__/merge-conflict-extract.test.d.ts +1 -0
- package/dist/local-executor/__tests__/merge-conflict-git.integration.test.d.ts +1 -0
- package/dist/local-executor/__tests__/merge-conflict-resolve-agentic.integration.test.d.ts +1 -0
- package/dist/local-executor/__tests__/merge-conflict-resolve.integration.test.d.ts +1 -0
- package/dist/local-executor/__tests__/merge-journal-store.test.d.ts +1 -0
- package/dist/local-executor/__tests__/merge-train-agentic-recovery.integration.test.d.ts +1 -0
- package/dist/local-executor/__tests__/merge-train-w3.integration.test.d.ts +1 -0
- package/dist/local-executor/__tests__/merge-train-waveb.integration.test.d.ts +1 -0
- package/dist/local-executor/__tests__/merge-train.test.d.ts +1 -0
- package/dist/local-executor/__tests__/promote-wal-store.test.d.ts +1 -0
- package/dist/local-executor/__tests__/revert-reconcile.test.d.ts +1 -0
- package/dist/local-executor/__tests__/revert-runner.test.d.ts +1 -0
- package/dist/local-executor/__tests__/shared-contract-detector.test.d.ts +1 -0
- package/dist/local-executor/__tests__/substrate-engage.test.d.ts +1 -0
- package/dist/local-executor/__tests__/team-execution-flag.test.d.ts +1 -0
- package/dist/local-executor/__tests__/track-registry.test.d.ts +1 -0
- package/dist/local-executor/__tests__/wb2-review-bundle.test.d.ts +1 -0
- package/dist/local-executor/__tests__/wb2-substrate-spawn.live.test.d.ts +1 -0
- package/dist/local-executor/__tests__/workspace-shadow.test.d.ts +1 -0
- package/dist/local-executor/__tests__/worktree-merge.test.d.ts +1 -0
- package/dist/local-executor/agentic-disposable-repo.d.ts +52 -0
- package/dist/local-executor/agentic-resolve.d.ts +54 -0
- package/dist/local-executor/agentic-resolver-flag.d.ts +9 -0
- package/dist/local-executor/authority.d.ts +33 -1
- package/dist/local-executor/class-a-emit.d.ts +3 -1
- package/dist/local-executor/class-a-sign.d.ts +57 -0
- package/dist/local-executor/class-b-consumer.d.ts +159 -4
- package/dist/local-executor/class-b-verify.d.ts +30 -0
- package/dist/local-executor/class1-reconcilers.d.ts +58 -0
- package/dist/local-executor/class2-resolver.d.ts +74 -0
- package/dist/local-executor/confined-model-caller.d.ts +39 -0
- package/dist/local-executor/conflict-classifier.d.ts +21 -0
- package/dist/local-executor/conflict-resolution-durable-store.d.ts +61 -0
- package/dist/local-executor/conflict-resolution-store.d.ts +210 -0
- package/dist/local-executor/conflict-types.d.ts +86 -0
- package/dist/local-executor/cp7-audit-writer.d.ts +152 -0
- package/dist/local-executor/durable-store.d.ts +334 -0
- package/dist/local-executor/git-lock-retry.d.ts +40 -0
- package/dist/local-executor/hook-bridge.d.ts +1 -1
- package/dist/local-executor/implementor-argv.d.ts +183 -0
- package/dist/local-executor/index.d.ts +44 -3
- package/dist/local-executor/local-executor-impl.d.ts +645 -4
- package/dist/local-executor/merge-conflict-extract.d.ts +34 -0
- package/dist/local-executor/merge-conflict-resolve.d.ts +101 -0
- package/dist/local-executor/merge-journal-store.d.ts +311 -0
- package/dist/local-executor/merge-train.d.ts +713 -0
- package/dist/local-executor/promote-wal-store.d.ts +301 -0
- package/dist/local-executor/resolution-set-index.d.ts +51 -0
- package/dist/local-executor/revert-reconcile.d.ts +89 -0
- package/dist/local-executor/revert-runner.d.ts +17 -0
- package/dist/local-executor/shared-contract-detector.d.ts +57 -0
- package/dist/local-executor/spawn.d.ts +29 -1
- package/dist/local-executor/task-group-types.d.ts +346 -0
- package/dist/local-executor/team-execution-flag.d.ts +21 -0
- package/dist/local-executor/track-registry.d.ts +50 -0
- package/dist/local-executor/types.d.ts +238 -4
- package/dist/local-executor/wb2-review-bundle.d.ts +59 -0
- package/dist/local-executor/workspace-shadow.d.ts +290 -0
- package/dist/local-executor/worktree-merge.d.ts +601 -0
- package/dist/local-model/__tests__/installer.test.d.ts +1 -0
- package/dist/local-model/__tests__/manager.test.d.ts +1 -0
- package/dist/local-model/__tests__/ollama.test.d.ts +1 -0
- package/dist/local-model/__tests__/runtime.test.d.ts +1 -0
- package/dist/local-model/__tests__/state.test.d.ts +1 -0
- package/dist/local-model/manager.d.ts +119 -0
- package/dist/local-model/ollama.d.ts +27 -0
- package/dist/local-model/runtime.d.ts +27 -0
- package/dist/local-model/state.d.ts +32 -0
- package/dist/orchestration/detect-agents.d.ts +15 -2
- package/dist/orchestration/setup-types.d.ts +2 -1
- package/dist/orchestration-shell/__tests__/attachments.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/audit-browser.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/audited-path-fixes.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/bracketed-paste.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/cli-group-halt-wiring.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/cli-orchestration-session-bootstrap.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/cli-probe-group-status.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/cli-quorum-loop-tier.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/cli-verdict-classifier-wiring.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/context-store.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/cp10-585-accept-echo-target.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/cp10-585-loop.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/cp10-585-prompts.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/cp12-w2b-team-loop.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/cp12-w4-team-recovery.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/cp1f-quorum-loop.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/cp606-team-track-user-resolved.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/cp7-reviewer-substrate.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/declared-test-runner.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/familiarize-routing.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/gate-decision-submit.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/gate-details-panel.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/gate-verdict-details-dispatch.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/group-decision-submit.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/image-attach-e2e.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/input-bar-autocomplete.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/live-region-cap.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/live-static-order.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/model-cli.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/non-gate-prompt-routing.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/orchestration-app-group-decision-transport.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/orchestration-app-layout.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/reissue-team-retained-spec.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/review-summary-render.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/reviewer-wizard.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/reviewers-format.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/root-discovery.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/serialize-submissions.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/slash-suggest.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/status-format.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/task-progress.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/team-decompose-routing.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/team-decompose-types.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/team-decompose.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/team-e2e-smoke.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/tier2-reviewer-selection.test.d.ts +1 -0
- package/dist/orchestration-shell/__tests__/web-browsing.test.d.ts +1 -0
- package/dist/orchestration-shell/attachments.d.ts +152 -0
- package/dist/orchestration-shell/audit-browser.d.ts +115 -0
- package/dist/orchestration-shell/audit-runner.d.ts +59 -0
- package/dist/orchestration-shell/bracketed-paste.d.ts +26 -0
- package/dist/orchestration-shell/cli.d.ts +336 -1
- package/dist/orchestration-shell/cli.js +40580 -4692
- package/dist/orchestration-shell/components/AuditLogPanel.d.ts +11 -0
- package/dist/orchestration-shell/components/ConversationPane.d.ts +7 -1
- package/dist/orchestration-shell/components/GateDetailsPanel.d.ts +57 -0
- package/dist/orchestration-shell/components/GatePanelEntry.d.ts +9 -0
- package/dist/orchestration-shell/components/GatePromptEntry.d.ts +7 -0
- package/dist/orchestration-shell/components/InputBar.d.ts +17 -3
- package/dist/orchestration-shell/components/LiveProgressLine.d.ts +8 -0
- package/dist/orchestration-shell/components/MultiTrackStatusNode.d.ts +8 -0
- package/dist/orchestration-shell/components/OrchestrationApp.d.ts +81 -15
- package/dist/orchestration-shell/components/ReviewerSetupWizard.d.ts +24 -0
- package/dist/orchestration-shell/components/StatusBar.d.ts +15 -2
- package/dist/orchestration-shell/context-store.d.ts +389 -0
- package/dist/orchestration-shell/declared-test-runner.d.ts +84 -0
- package/dist/orchestration-shell/emit-shell-event.d.ts +1 -1
- package/dist/orchestration-shell/gate-decision-submit.d.ts +245 -0
- package/dist/orchestration-shell/gate-prompts.d.ts +361 -3
- package/dist/orchestration-shell/index.d.ts +613 -1
- package/dist/orchestration-shell/ink-runtime.d.ts +21 -1
- package/dist/orchestration-shell/live-region-cap.d.ts +70 -0
- package/dist/orchestration-shell/live-static-order.d.ts +22 -0
- package/dist/orchestration-shell/non-tty-fallback.d.ts +21 -0
- package/dist/orchestration-shell/quorum-loop.d.ts +1900 -0
- package/dist/orchestration-shell/root-discovery.d.ts +25 -0
- package/dist/orchestration-shell/route-browse.d.ts +20 -0
- package/dist/orchestration-shell/slash-router.d.ts +20 -1
- package/dist/orchestration-shell/slash-routes/continuation.d.ts +71 -0
- package/dist/orchestration-shell/slash-routes/reviewers.d.ts +41 -0
- package/dist/orchestration-shell/slash-suggest.d.ts +29 -0
- package/dist/orchestration-shell/status-format.d.ts +59 -0
- package/dist/orchestration-shell/task-progress.d.ts +122 -0
- package/dist/orchestration-shell/team-decompose.d.ts +196 -0
- package/dist/orchestration-shell/types.d.ts +363 -3
- package/dist/orchestration-shell/vendor/ink-parse-keypress.d.ts +42 -0
- package/dist/orchestration-shell/web/extract.d.ts +11 -0
- package/dist/orchestration-shell/web/fetch.d.ts +15 -0
- package/dist/orchestration-shell/web/ip.d.ts +26 -0
- package/dist/orchestration-shell/web/sanitize.d.ts +14 -0
- package/dist/orchestration-shell/web/search.d.ts +16 -0
- package/dist/planner/__tests__/local-gemma.test.d.ts +1 -0
- package/dist/planner/index.d.ts +2 -0
- package/dist/planner/local-advisory.d.ts +84 -0
- package/dist/planner/local-gemma.d.ts +23 -0
- package/dist/planner/types.d.ts +16 -6
- package/dist/reviewer/__tests__/antigravity-provider.test.d.ts +1 -0
- package/dist/reviewer/__tests__/claude-provider-json.test.d.ts +1 -0
- package/dist/reviewer/__tests__/cp7-reviewer-sandbox.test.d.ts +1 -0
- package/dist/reviewer/__tests__/output-parser-classifier-helpers.test.d.ts +1 -0
- package/dist/reviewer/__tests__/verdict-classifier.test.d.ts +1 -0
- package/dist/reviewer/index.d.ts +2 -0
- package/dist/reviewer/output-parser.d.ts +48 -12
- package/dist/reviewer/provider.d.ts +107 -3
- package/dist/reviewer/providers/__tests__/failure-classifier.test.d.ts +1 -0
- package/dist/reviewer/providers/antigravity.d.ts +49 -0
- package/dist/reviewer/providers/claude.d.ts +19 -4
- package/dist/reviewer/providers/codex.d.ts +47 -5
- package/dist/reviewer/providers/failure-classifier.d.ts +26 -0
- package/dist/reviewer/providers/gemini.d.ts +5 -4
- package/dist/reviewer/registry.d.ts +5 -5
- package/dist/reviewer/subprocess.d.ts +37 -0
- package/dist/reviewer/token-usage.d.ts +32 -0
- package/dist/reviewer/types.d.ts +5 -5
- package/dist/reviewer/verdict-classifier.d.ts +62 -0
- package/dist/session/__tests__/prepare-event-timestamp.test.d.ts +1 -0
- package/dist/session/index.d.ts +1 -0
- package/dist/session/prepare-event-timestamp.d.ts +30 -0
- package/dist/substrate/__tests__/docker.test.d.ts +1 -0
- package/dist/substrate/__tests__/forwarder.test.d.ts +1 -0
- package/dist/substrate/__tests__/git-proxy.test.d.ts +1 -0
- package/dist/substrate/__tests__/sandbox-exec.test.d.ts +1 -0
- package/dist/substrate/__tests__/substrate-e2e.integration.test.d.ts +1 -0
- package/dist/substrate/command-runner.d.ts +19 -0
- package/dist/substrate/docker.d.ts +134 -0
- package/dist/substrate/errors.d.ts +33 -0
- package/dist/substrate/forwarder.d.ts +67 -0
- package/dist/substrate/git-proxy.d.ts +35 -0
- package/dist/substrate/index.d.ts +12 -0
- package/dist/substrate/relay-script.d.ts +27 -0
- package/dist/substrate/sandbox-exec.d.ts +116 -0
- package/dist/substrate/types.d.ts +218 -0
- package/dist/substrate-launch/__tests__/apikey-bootstrap.test.d.ts +1 -0
- package/dist/substrate-launch/__tests__/creditless-loop.integration.test.d.ts +1 -0
- package/dist/substrate-launch/__tests__/engage-substrate.test.d.ts +1 -0
- package/dist/substrate-launch/__tests__/fallback-ladder.test.d.ts +1 -0
- package/dist/substrate-launch/__tests__/le-env-scrub.test.d.ts +1 -0
- package/dist/substrate-launch/__tests__/sanitized-env.test.d.ts +1 -0
- package/dist/substrate-launch/apikey-bootstrap.d.ts +84 -0
- package/dist/substrate-launch/engage-substrate.d.ts +102 -0
- package/dist/substrate-launch/fallback-ladder.d.ts +49 -0
- package/dist/substrate-launch/index.d.ts +12 -0
- package/dist/substrate-launch/le-env-scrub.d.ts +77 -0
- package/dist/substrate-launch/sanitized-env.d.ts +73 -0
- package/dist/types/auth.d.ts +6 -1
- package/dist/types/events.d.ts +3 -1
- package/dist/types/reviewer.d.ts +6 -6
- package/package.json +7 -3
|
@@ -0,0 +1,1900 @@
|
|
|
1
|
+
import type { AppSyncClient, TeamTrackRecoveryRow } from '../appsync/appsync-client';
|
|
2
|
+
import type { Session } from '../types';
|
|
3
|
+
import type { LocalExecutor } from '../local-executor';
|
|
4
|
+
import type { ImageAttachment } from './types';
|
|
5
|
+
import { type ImplementorMode } from '../local-executor/implementor-argv';
|
|
6
|
+
import type { ConflictModelCaller } from '../local-executor/class2-resolver';
|
|
7
|
+
import type { ConflictResolverContext } from '../local-executor/merge-conflict-resolve';
|
|
8
|
+
import type { AgentKind } from '../local-executor/types';
|
|
9
|
+
import type { GateDispatchPayload, ReviewerDispatchPayload, ReviseFeedbackPayload } from '../local-executor/types';
|
|
10
|
+
import type { TeamTrackFailureReason } from '../local-executor/task-group-types';
|
|
11
|
+
import { WorkspaceShadow, type ShadowEnv } from '../local-executor/workspace-shadow';
|
|
12
|
+
import type { RevertManifestStore } from '../local-executor/durable-store';
|
|
13
|
+
import { type TeamExecution } from '../local-executor/team-execution-flag';
|
|
14
|
+
import { type RenderedConflict, type TrackIntentDiff } from '../local-executor/wb2-review-bundle';
|
|
15
|
+
import { ReviewerRegistry } from '../reviewer/registry';
|
|
16
|
+
import { type ReviewerFailureReason } from '../reviewer/provider';
|
|
17
|
+
import type { SubstrateHandle } from '../substrate/types';
|
|
18
|
+
import type { VerdictClassifier } from '../reviewer/verdict-classifier';
|
|
19
|
+
import type { AgentKind as ReviewerAgentKind, ReviewerRole } from '../reviewer/types';
|
|
20
|
+
import type { OnProgress } from './task-progress';
|
|
21
|
+
import { runShellCommand } from '../local-executor/verification-gates/shell-runner';
|
|
22
|
+
import type { PostDecisionAction, Tier } from './types';
|
|
23
|
+
import type { ContinuationPacketWriter } from '../continuation/packet-writer';
|
|
24
|
+
import type { ContinuationPacketReader } from '../continuation/packet-reader';
|
|
25
|
+
import type { HandoffReason } from '../continuation/types';
|
|
26
|
+
/**
|
|
27
|
+
* CP-1.f — the clean, user-facing one-liner for a synthesized ESCALATE verdict
|
|
28
|
+
* whose origin is a reviewer subprocess failure. Built ONLY from the closed
|
|
29
|
+
* `ReviewerFailureReason` + the agent display name via the fixed template
|
|
30
|
+
* table. Never derived from raw provider output. Exported for the
|
|
31
|
+
* sanitization test.
|
|
32
|
+
*/
|
|
33
|
+
export declare function failureReasonTemplate(agentKind: ReviewerAgentKind, reason: ReviewerFailureReason): string;
|
|
34
|
+
/**
|
|
35
|
+
* CP-1.f implementor-round-failure surfacing (dogfood fix 2026-06-10) — classify
|
|
36
|
+
* a thrown implementor-round error into a SAFE, classified label. NEVER returns
|
|
37
|
+
* the raw error message / stderr (the wire-safety rule shared with progress
|
|
38
|
+
* events + synthesized reviewer reasoning).
|
|
39
|
+
*
|
|
40
|
+
* - `AuthorityError` → the closed-category label (`authority_scope_conflict` →
|
|
41
|
+
* "blocked by the local authority allowlist", etc.).
|
|
42
|
+
* - A spawn/start failure (ENOENT / "spawn" / "not found" on the agent binary)
|
|
43
|
+
* → a generic "the agent could not be started" label.
|
|
44
|
+
* - Anything else → a generic "an unexpected error occurred" label.
|
|
45
|
+
*
|
|
46
|
+
* Exported for the sanitization test (proves no raw message leaks).
|
|
47
|
+
*/
|
|
48
|
+
export declare function classifyImplementorRoundFailure(err: unknown): string;
|
|
49
|
+
/**
|
|
50
|
+
* PHASE-CP-10-MIN (#585) §3.6 H-C / L1 — classify an implementor failure as a
|
|
51
|
+
* QUOTA exhaustion (a usage-limit / rate-limit error from the coding-agent CLI).
|
|
52
|
+
* Returns ONLY a boolean (the `quota_exhausted` label); `resetAt` extraction is
|
|
53
|
+
* CP-13's, not #585's (Non-goal §1.3).
|
|
54
|
+
*
|
|
55
|
+
* The discriminator is the `text` (`exitCode` is informational context — a
|
|
56
|
+
* usage-limit exit is typically nonzero, but the stderr text is authoritative).
|
|
57
|
+
* Reads the raw stderr / error message LOCALLY ONLY — never on the wire
|
|
58
|
+
* (wire-safety; only the closed `quota_exhausted` LABEL crosses the wire).
|
|
59
|
+
*
|
|
60
|
+
* Fixtures (L1):
|
|
61
|
+
* - Codex (PINNED — CP-13 §0/§2-D1 live sample): `You've hit your usage limit
|
|
62
|
+
* … try again at <date>` → match on `usage limit` (case-insensitive).
|
|
63
|
+
* - Claude / Gemini: TBD — CP-13 has not captured real samples. Inventing
|
|
64
|
+
* strings risks a FALSE-NEGATIVE classifier that misses a real quota error,
|
|
65
|
+
* so they are left as additive one-liners to fill once real samples land.
|
|
66
|
+
* An UNMATCHED quota error degrades FAIL-SAFE to the existing born-halt
|
|
67
|
+
* `surfaceHalt` (the user just doesn't get auto-continuation; identical to
|
|
68
|
+
* today) — NOT fail-open.
|
|
69
|
+
*
|
|
70
|
+
* Exported for the L1 fixture test (I5).
|
|
71
|
+
*/
|
|
72
|
+
export declare function classifyImplementorQuotaFailure(exitCode: number | undefined, text: string): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Audited-path fix (Fix 1) — build the SAFE, no-plaintext desktop advisory text
|
|
75
|
+
* for a hosted-side PolicyRejection. Uses ONLY the closed `category` classifier
|
|
76
|
+
* label (a hosted-side enum, NOT user prose — already carried on the existing
|
|
77
|
+
* mobile NOTIFICATION) + the closed `recommendedRecovery` enum's recovery hint.
|
|
78
|
+
* The raw `detail` (potential plaintext) is NEVER included. Exported for the
|
|
79
|
+
* sanitization test. An unknown recovery value falls back to the generic
|
|
80
|
+
* `ask_user` hint so the advisory always reads cleanly.
|
|
81
|
+
*/
|
|
82
|
+
export declare function policyRejectionAdvisoryText(info: {
|
|
83
|
+
category: string;
|
|
84
|
+
recommendedRecovery: string;
|
|
85
|
+
}): string;
|
|
86
|
+
/**
|
|
87
|
+
* Audited-path fix (Fix 5) — select the implementor `AgentKind` for a START.
|
|
88
|
+
*
|
|
89
|
+
* - An explicit `override` (from `/task --agent codex|claude`) wins, BUT only
|
|
90
|
+
* when that agent was actually detected on this host (else fail-closed to the
|
|
91
|
+
* default — we never spawn a binary the host doesn't have).
|
|
92
|
+
* - Otherwise: CLAUDE when it is among the detected agents (the documented
|
|
93
|
+
* default), else the FIRST detected agent, else CLAUDE as the universal
|
|
94
|
+
* fail-safe (matches the engine's Claude-only fallback when the host detected
|
|
95
|
+
* nothing). The implementor argv allowlist already covers all three agents,
|
|
96
|
+
* so any returned kind is spawnable without widening it.
|
|
97
|
+
*
|
|
98
|
+
* Returns `{ agent, note }` — `note` is a non-null user-facing string ONLY when
|
|
99
|
+
* an explicit override could not be honored (so the shell can surface it); null
|
|
100
|
+
* when the selection was clean. NEVER throws.
|
|
101
|
+
*/
|
|
102
|
+
export declare function selectImplementorAgent(detected: readonly AgentKind[], override?: AgentKind | null): {
|
|
103
|
+
agent: AgentKind;
|
|
104
|
+
note: string | null;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* CP-14 WB2 §2.4 (LOCK #C14-WB2-TIER2-INDEPENDENT / #C14-MOAT output-review half) — pick the
|
|
108
|
+
* final-tip Tier-2 reviewer agent: the strongest HOST-DETECTED reviewer, best-effort INDEPENDENT
|
|
109
|
+
* of the class-2 resolver agent(s) this gate.
|
|
110
|
+
*
|
|
111
|
+
* - `override` (forward-compat; no desktop surface today) wins, but ONLY when host-detected
|
|
112
|
+
* (never spawn an undetected binary — same posture as {@link selectImplementorAgent}).
|
|
113
|
+
* - else the strongest detected agent (lowest {@link TIER2_REVIEWER_RANK} index) whose kind is
|
|
114
|
+
* NOT in `resolverAgents` (independence). If every detected agent also resolved (single-agent
|
|
115
|
+
* host, or ≥2 resolver agents covering all detected), fall back to the strongest detected
|
|
116
|
+
* overall — a same-agent Tier-2 is still a FRESH adversarial review of the output.
|
|
117
|
+
* - empty detected → `null` → the gate NO-PROMOTEs (the moat's output-review half).
|
|
118
|
+
*
|
|
119
|
+
* Inputs/outputs are LE-UPPERCASE `AgentKind`; the dispatcher bridges the result to the lowercase
|
|
120
|
+
* reviewer wire kind via {@link leAgentToReviewerAgent}. `resolverAgents` is a set of raw strings
|
|
121
|
+
* (the §4.5 record's `implementorAgent`); non-AgentKind values are simply absent from `.has`.
|
|
122
|
+
* Pure; NEVER throws.
|
|
123
|
+
*/
|
|
124
|
+
export declare function selectFinalTier2Reviewer(detected: readonly AgentKind[], resolverAgents: ReadonlySet<string>, override?: AgentKind | null): AgentKind | null;
|
|
125
|
+
/**
|
|
126
|
+
* CP-14 WB2 §2.4 fallback (dogfood-pinned 2026-07-03) — the FULL ordered Tier-2
|
|
127
|
+
* candidate list, same selection rules as {@link selectFinalTier2Reviewer}
|
|
128
|
+
* (which is now its head): an explicit host-detected override first, then the
|
|
129
|
+
* independent candidates by rank, then the resolver-agent candidates by rank
|
|
130
|
+
* (a same-agent Tier-2 is still a fresh adversarial review of the output — the
|
|
131
|
+
* documented last resort). The dispatcher walks this list when a candidate
|
|
132
|
+
* fails to SPAWN/confine (host run 16: the top-ranked independent reviewer
|
|
133
|
+
* (Codex) died in-sandbox at boot and the merge REJECTED with two healthy
|
|
134
|
+
* reviewers standing by). A real VERDICT — pass or reject — is authoritative
|
|
135
|
+
* and never falls through. Pure; NEVER throws.
|
|
136
|
+
*/
|
|
137
|
+
export declare function selectFinalTier2Candidates(detected: readonly AgentKind[], resolverAgents: ReadonlySet<string>, override?: AgentKind | null): AgentKind[];
|
|
138
|
+
/**
|
|
139
|
+
* CP-7 §8 (Stage-1-resolved) — the narrow result the QuorumLoop consumes from
|
|
140
|
+
* the reviewer substrate engager. Mirrors `SubstrateLaunch.EngageResult` /
|
|
141
|
+
* `LocalExecutorImpl.SubstrateEngageResult` but is declared STRUCTURALLY here
|
|
142
|
+
* (not importing the concrete `engageSubstrate`) so the loop module stays
|
|
143
|
+
* decoupled and tests inject a stub. Two variants:
|
|
144
|
+
* - `substrate` (the moat is ON for this seat — the loop threads
|
|
145
|
+
* `substrateHandle` into `evaluate` + calls `teardown()` when the seat
|
|
146
|
+
* completes, on EVERY path).
|
|
147
|
+
* - `reduced_trust` (the moat is OFF for this seat — A5 coarse host with no
|
|
148
|
+
* runtime / Gemini non-Trusted: the loop runs the reviewer on the LEGACY
|
|
149
|
+
* unsandboxed path + surfaces the reduced-trust badge).
|
|
150
|
+
*
|
|
151
|
+
* THROW semantics (fail-closed): the engager THROWS for a Trusted agent when
|
|
152
|
+
* the broker cannot start (or any non-`reduced_trust` engage failure). The
|
|
153
|
+
* loop treats a throw as FAIL-CLOSED — it does NOT silently fall back to the
|
|
154
|
+
* ambient-cred legacy path (that would defeat the moat); it synthesizes a
|
|
155
|
+
* clean ESCALATE so the gate resolves rather than hangs.
|
|
156
|
+
*/
|
|
157
|
+
export type ReviewerSubstrateEngageResult = {
|
|
158
|
+
mode: 'substrate';
|
|
159
|
+
tier: 'docker' | 'sandbox_exec';
|
|
160
|
+
reducedTrust: boolean;
|
|
161
|
+
reducedTrustReason?: string;
|
|
162
|
+
substrateHandle: SubstrateHandle;
|
|
163
|
+
teardown(): Promise<void>;
|
|
164
|
+
} | {
|
|
165
|
+
mode: 'reduced_trust';
|
|
166
|
+
reducedTrust: true;
|
|
167
|
+
reducedTrustReason: string;
|
|
168
|
+
teardown(): Promise<void>;
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* CP-7 §8 (Stage-1-resolved) — engage the CP-7 trusted-execution substrate +
|
|
172
|
+
* broker for ONE reviewer seat (mirrors the implementor's per-spawn engager).
|
|
173
|
+
* The orchestration shell wires this to `SubstrateLaunch.engageSubstrate`
|
|
174
|
+
* (with `role: 'reviewer'`), translating the reviewer's lowercase `agent`
|
|
175
|
+
* (`claude`/`codex`/`gemini`) into the engager's UPPERCASE `agentKind`.
|
|
176
|
+
*
|
|
177
|
+
* ABSENT on `QuorumLoopDeps` (legacy / companion / tests) → reviewers take the
|
|
178
|
+
* legacy unsandboxed path UNCHANGED (byte-identical non-regression). PRESENT →
|
|
179
|
+
* each Trusted-agent reviewer seat engages the moat (no ambient vendor creds;
|
|
180
|
+
* model call via the broker), with the §6 Q4 GEMINI / reduced-trust handling
|
|
181
|
+
* the engager performs internally (returns `reduced_trust`).
|
|
182
|
+
*/
|
|
183
|
+
export type ReviewerSubstrateEngager = (input: {
|
|
184
|
+
taskId: string;
|
|
185
|
+
/** The reviewer's lowercase agent (the loop maps it to UPPERCASE inside the wiring). */
|
|
186
|
+
agent: ReviewerAgentKind;
|
|
187
|
+
/** Canonical host workdir the sandbox mounts (the loop's working dir). */
|
|
188
|
+
workdir: string;
|
|
189
|
+
}) => Promise<ReviewerSubstrateEngageResult>;
|
|
190
|
+
export interface QuorumLoopDeps {
|
|
191
|
+
appsyncClient: AppSyncClient;
|
|
192
|
+
session: Session;
|
|
193
|
+
/** The team-mode LE (used for spawnImplementor + spawnReviewer). */
|
|
194
|
+
localExecutor: LocalExecutor;
|
|
195
|
+
/** Reviewer registry (production: createSubprocessReviewerRegistry()). */
|
|
196
|
+
reviewerRegistry: ReviewerRegistry;
|
|
197
|
+
/**
|
|
198
|
+
* REVIEWER-VERDICT-GEMMA-CLASSIFIER-DESIGN.md (D1/§Wiring) — the local-Gemma
|
|
199
|
+
* verdict classifier, forwarded on the `evaluate` opts at BOTH reviewer sites
|
|
200
|
+
* (per-seat + Wave-B Tier-2) so every reviewer verdict is determined by the
|
|
201
|
+
* fail-closed classifier instead of the regex `parseVerdictOutput`. Under the
|
|
202
|
+
* default `gemma` mode this is ALWAYS present (the real classifier, or a dummy
|
|
203
|
+
* always-ESCALATE classifier when the Gemma runner is null at boot). ABSENT ⇒
|
|
204
|
+
* the providers use the regex parser — reachable ONLY when the operator sets
|
|
205
|
+
* `CODEVIBE_REVIEWER_VERDICT_CLASSIFIER=regex` (a deliberate `undefined`,
|
|
206
|
+
* NEVER an accidental missing-classifier fallback).
|
|
207
|
+
*/
|
|
208
|
+
classifyVerdict?: VerdictClassifier;
|
|
209
|
+
/** Resolve the session key for E2E encrypt/decrypt. */
|
|
210
|
+
getSessionKey: (sessionId: string) => Promise<string | null>;
|
|
211
|
+
/** Working dir for spawned subprocesses (canonicalized). */
|
|
212
|
+
workingDir: string;
|
|
213
|
+
/**
|
|
214
|
+
* CP-14 Wave A — the team execution model. `worktree_merge` makes each team
|
|
215
|
+
* track commit to a persistent branch (the merge-before-push spine) instead of
|
|
216
|
+
* the merge-train promote (the only model since the §10 retirement). Omitted keeps the
|
|
217
|
+
* shipped path byte-identical. Resolved from `CODEVIBE_TEAM_EXECUTION` when
|
|
218
|
+
* unset (see {@link resolveTeamExecution}).
|
|
219
|
+
*/
|
|
220
|
+
teamExecution?: TeamExecution;
|
|
221
|
+
/**
|
|
222
|
+
* CP-7 Stage-2 r3 (Codex) HIGH — ARM the LE's current/authorized task id so
|
|
223
|
+
* the round-0 (and revise) implementor spawn engages the CP-7 trusted-execution
|
|
224
|
+
* substrate instead of the pre-TaskAuthorized reduced-trust path. `startTask`
|
|
225
|
+
* threads the minted taskId here BEFORE the round-0 spawn drains (and rolls it
|
|
226
|
+
* back to `null` on a START rejection); production wires it to
|
|
227
|
+
* `LocalExecutorImpl.setActiveTaskId`. ABSENT (legacy/tests) → the LE's task id
|
|
228
|
+
* is unchanged (the pre-fix behavior); production MUST wire it so the
|
|
229
|
+
* implementor never spawns unbadged/unsandboxed. The same state
|
|
230
|
+
* (`LocalExecutorImpl.taskId`) drives `engageSubstrateForSpawn` (local-executor-
|
|
231
|
+
* impl.ts:566) to call `substrateEngager` rather than emit a `tier:'none'` badge.
|
|
232
|
+
*/
|
|
233
|
+
armLeTaskId?: (taskId: string | null) => void;
|
|
234
|
+
/**
|
|
235
|
+
* CP-7 §8 (Stage-1-resolved) — engage the CP-7 trusted-execution substrate +
|
|
236
|
+
* broker for ONE reviewer seat. When wired (production via cli.ts →
|
|
237
|
+
* `SubstrateLaunch.engageSubstrate` with `role: 'reviewer'`), each Trusted-
|
|
238
|
+
* agent reviewer runs INSIDE the sandbox with no ambient vendor creds and a
|
|
239
|
+
* broker-routed model call — closing the CP-14 §8/§13 reviewer-sandbox gap.
|
|
240
|
+
* ABSENT (legacy / companion / tests) → reviewers take the legacy unsandboxed
|
|
241
|
+
* path BYTE-IDENTICAL (§7 non-regression). Reviewer-sandboxing is ALSO gated
|
|
242
|
+
* behind an opt-in env flag (`CODEVIBE_SANDBOX_REVIEWERS=1`, read in
|
|
243
|
+
* `engageSeatSubstrate` on the `spawnOneSeat` path) that DEFAULTS OFF — so
|
|
244
|
+
* even when this engager is wired,
|
|
245
|
+
* reviewers stay on the legacy path until the broker round-trip is observed
|
|
246
|
+
* green in a configured env (see `reviewerSandboxingEnabled`). The flag is
|
|
247
|
+
* independent of implementor sandboxing (which has its own engager).
|
|
248
|
+
*/
|
|
249
|
+
engageReviewerSubstrate?: ReviewerSubstrateEngager;
|
|
250
|
+
/**
|
|
251
|
+
* CP-1.f LOCK RP-6 — this orchestration host's detected coding agents
|
|
252
|
+
* (UPPERCASE `CLAUDE` / `CODEX` / `GEMINI`), computed ONCE by `cli.ts` via
|
|
253
|
+
* `detectInstalledAgents` (the SAME UPPERCASE string form
|
|
254
|
+
* `pushDetectedAgents` sends). Threaded here so `startTask` carries it IN
|
|
255
|
+
* the request and the gate row's availability is stamped race-free from
|
|
256
|
+
* THIS host — closing the cross-device shared-`User.availableAgents`
|
|
257
|
+
* TOCTOU (a legacy companion daemon on another device can no longer union
|
|
258
|
+
* a stale agent back in between the host's write and the engine's read).
|
|
259
|
+
*
|
|
260
|
+
* The `[]` vs ABSENT distinction is load-bearing engine-side: an EMPTY
|
|
261
|
+
* array (`[]` — host detected nothing) is sent VERBATIM (→ Claude-only
|
|
262
|
+
* fail-safe, no Users read). ONLY a fully-absent dep (legacy/tests →
|
|
263
|
+
* `undefined`) omits the field (→ engine Users-table fallback). So
|
|
264
|
+
* production wires this to a (possibly empty) array; it does NOT leave it
|
|
265
|
+
* `undefined`.
|
|
266
|
+
*/
|
|
267
|
+
detectedAgents?: string[];
|
|
268
|
+
/**
|
|
269
|
+
* #585 auto-continuation (Max) — the resolved subscription tier (from
|
|
270
|
+
* `resumeOrCreateSession`: env override → getSubscriptionStatus → FREE).
|
|
271
|
+
* MAX → a quota-exhausted continuation handoff AUTO-ACCEPTS on the strongest
|
|
272
|
+
* available agent (no prompt). Pro/Free/unresolved/absent → the existing
|
|
273
|
+
* `continuation_offer_handoff` prompt (fail-closed). Auto-continuation only
|
|
274
|
+
* affects the user's OWN agents, so the resolved tier is sufficient (no
|
|
275
|
+
* separate per-request entitlement fetch).
|
|
276
|
+
*/
|
|
277
|
+
tier?: Tier;
|
|
278
|
+
/** Sleep helper (injected for deterministic tests). */
|
|
279
|
+
sleep?: (ms: number) => Promise<void>;
|
|
280
|
+
/**
|
|
281
|
+
* CP-1.f execute-first (§4) — shadow lifecycle environment (state/shadow dir
|
|
282
|
+
* roots, TTL, child-kill hook, sleep). Injected for deterministic tests;
|
|
283
|
+
* production uses the defaults (`~/.codevibe/shadow-state`, `os.tmpdir()`).
|
|
284
|
+
*/
|
|
285
|
+
shadowEnv?: ShadowEnv;
|
|
286
|
+
/**
|
|
287
|
+
* CP-1.f execute-first (#EF-1 / #SR-2) — surface a user-visible halt /
|
|
288
|
+
* fail-closed message to the orchestration timeline. Wired by the shell to an
|
|
289
|
+
* `emitShellEvent` NOTIFICATION. ABSENT (tests) → logged only.
|
|
290
|
+
*/
|
|
291
|
+
surfaceHalt?: (message: string) => void;
|
|
292
|
+
/**
|
|
293
|
+
* PHASE-589/469 W1 (LOCK #589-E) — flip the desktop's OWN team track to
|
|
294
|
+
* `Failed` + render the group-terminal from LOCAL state. Wired by the TEAM
|
|
295
|
+
* shell to emit a `team_track_terminal` (state `Failed`) shell event through
|
|
296
|
+
* the SAME store-tap chokepoint the MergeGate convergence uses, then evaluate
|
|
297
|
+
* local `all-terminal && any-failed` → render "Team halted — track N
|
|
298
|
+
* failed". TEAM-only — ABSENT on the single-impl path (and in tests) → no-op.
|
|
299
|
+
* The QuorumLoop reports the engine marker (`submitTrackVerificationOutcome`)
|
|
300
|
+
* separately; this callback drives the DESKTOP-side render only.
|
|
301
|
+
*/
|
|
302
|
+
onTeamTrackFailed?: (args: {
|
|
303
|
+
taskGroupId: string;
|
|
304
|
+
trackIndex: number;
|
|
305
|
+
reason: TeamTrackFailureReason;
|
|
306
|
+
}) => void;
|
|
307
|
+
/**
|
|
308
|
+
* PHASE-589/469 W2 — flip the desktop's OWN team track to `Revising` while an
|
|
309
|
+
* auto-revise round is in flight. Wired by the TEAM shell to emit a
|
|
310
|
+
* `team_track_terminal` (state `Revising`) shell event through the SAME
|
|
311
|
+
* store-tap chokepoint the convergence + W1-fail render use. Fired ONLY when
|
|
312
|
+
* the engine's `submitTrackVerificationOutcome` marker AUTO-REVISED (the
|
|
313
|
+
* returned `trackState` is NOT `Failed`) — the track stays InFlight and the
|
|
314
|
+
* desktop re-spawns its implementor on the inbound `REVISE_FEEDBACK`. On
|
|
315
|
+
* cap-exhaust the marker returns `Failed` instead and `onTeamTrackFailed`
|
|
316
|
+
* fires (W1's terminal render). TEAM-only — ABSENT on single-impl (and in
|
|
317
|
+
* tests) → no-op.
|
|
318
|
+
*/
|
|
319
|
+
onTeamTrackRevising?: (args: {
|
|
320
|
+
taskGroupId: string;
|
|
321
|
+
trackIndex: number;
|
|
322
|
+
}) => void;
|
|
323
|
+
/**
|
|
324
|
+
* #606 (FACE A) — flip the desktop's OWN team track terminal the moment the
|
|
325
|
+
* USER resolves that track's per-track gate (final_approval Approve/Cancel, or
|
|
326
|
+
* the escalation 5-option Accept/Abort). Closes the #469 "track stuck
|
|
327
|
+
* InFlight" gap: the per-track USER decision reached NONE of the render-
|
|
328
|
+
* terminal writers (only `emitGroupConvergence` + the W1 marker path did), so a
|
|
329
|
+
* user-resolved track stayed `InFlight` in the render until the slow engine
|
|
330
|
+
* convergence/sweeper. Emits a `team_track_terminal` (state Passed/Failed)
|
|
331
|
+
* shell event through the SAME `leEmitShellEvent` chokepoint the convergence +
|
|
332
|
+
* W1/W2 callbacks use — but withOUT a `reason` label, so `routeTeamShell
|
|
333
|
+
* EventToStore` does the track flip ONLY (it does NOT take the W1 local group-
|
|
334
|
+
* terminal render branch, which is `reason`-gated; the all-or-nothing GROUP
|
|
335
|
+
* gate is driven authoritatively by the engine's `TaskGroupHalted` emit, #606
|
|
336
|
+
* FACE B). TEAM-only — ABSENT on the single-impl path (and in tests) → no-op.
|
|
337
|
+
*/
|
|
338
|
+
onTeamTrackUserResolved?: (args: {
|
|
339
|
+
taskGroupId: string;
|
|
340
|
+
trackIndex: number;
|
|
341
|
+
state: 'Passed' | 'Failed';
|
|
342
|
+
}) => void;
|
|
343
|
+
/**
|
|
344
|
+
* PHASE-589/469 W3.1 (C1) — the durable revert-manifest store. For a TEAM
|
|
345
|
+
* track promote, the pre-image manifest is captured + persisted BEFORE the
|
|
346
|
+
* tree mutates (LOCK #589-I), so a later group reject can revert every applied
|
|
347
|
+
* track. TEAM-only — ABSENT on single-impl (and in tests) → promote runs the
|
|
348
|
+
* legacy no-capture path (byte-identical). Wired by the TEAM shell to a single
|
|
349
|
+
* process-wide `RevertManifestStore`.
|
|
350
|
+
*/
|
|
351
|
+
durableStore?: RevertManifestStore;
|
|
352
|
+
/**
|
|
353
|
+
* CP-1.f live progress surfacing — desktop-LOCAL progress phases (shadow
|
|
354
|
+
* created, implementor running + periodic tick, diff captured, submitting,
|
|
355
|
+
* reviewers dispatched, per-seat + verdict progress, revise, promote/discard,
|
|
356
|
+
* waiting-on-user). Wired by the shell to the Ink store (an ephemeral spinner
|
|
357
|
+
* line + persisted milestone lines). ABSENT (tests / non-loop sessions) →
|
|
358
|
+
* no-op. NO backend/wire/schema change: progress NEVER becomes a createEvent.
|
|
359
|
+
*
|
|
360
|
+
* SECURITY INVARIANT: events carry ONLY counts/labels/paths/elapsed — never
|
|
361
|
+
* diff content, file contents, reviewer reasoning, prompts, or any decrypted
|
|
362
|
+
* plaintext (the `TaskProgressEvent` union is the allowlist). The loop wraps
|
|
363
|
+
* every emit so a throwing listener can NEVER break the loop.
|
|
364
|
+
*/
|
|
365
|
+
onProgress?: OnProgress;
|
|
366
|
+
/**
|
|
367
|
+
* PHASE-598 — the declared-test EXECUTOR subprocess seam. The per-track
|
|
368
|
+
* declared-`test_surfaces` runner spawns `node <file>` through this primitive
|
|
369
|
+
* (default `runShellCommand`, the SAME primitive the CP-3 verification gates
|
|
370
|
+
* use). Injected so integration tests can stub the executor's `node` runs
|
|
371
|
+
* without a live `node` subprocess. ABSENT → the real `runShellCommand`.
|
|
372
|
+
*/
|
|
373
|
+
runShell?: typeof runShellCommand;
|
|
374
|
+
/**
|
|
375
|
+
* PHASE-CP-10-MIN (#585) §3.5 — the continuation packet WRITER (the trigger
|
|
376
|
+
* transport). The `user_requested` (`/continue request`) + `quota_exhausted`
|
|
377
|
+
* (born-halt fork) triggers call `write()` directly (local CONTINUATION.md +
|
|
378
|
+
* the `recordContinuationPacketWritten` mutation → server stamps + emits the
|
|
379
|
+
* handoff prompt). ABSENT (tests / legacy) → the continuation triggers are
|
|
380
|
+
* unavailable (the quota fork degrades to the generic `surfaceHalt`).
|
|
381
|
+
*/
|
|
382
|
+
continuationWriter?: ContinuationPacketWriter;
|
|
383
|
+
/**
|
|
384
|
+
* PHASE-CP-10-MIN (#585) §3.5 — the continuation packet READER. The resume
|
|
385
|
+
* (`resumeFromContinuation`) reads the CP-6 `CONTINUATION.md` via this reader,
|
|
386
|
+
* which hash-verifies + backend-audit-row-verifies (refusing a
|
|
387
|
+
* `PacketUnverified` packet — the pre-spawn fidelity gate). ABSENT → resume
|
|
388
|
+
* is unavailable.
|
|
389
|
+
*/
|
|
390
|
+
continuationReader?: ContinuationPacketReader;
|
|
391
|
+
/**
|
|
392
|
+
* PHASE-CP-10-MIN (#585) L-A — clock for minting a fresh per-request
|
|
393
|
+
* `expiresAt` (`now + TTL`, millisecond precision). Injected for deterministic
|
|
394
|
+
* tests; production uses `Date.now`.
|
|
395
|
+
*/
|
|
396
|
+
clock?: () => number;
|
|
397
|
+
/**
|
|
398
|
+
* PHASE-CP-10-MIN (#585) §3.5 — the continuation packet TTL (ms) used to mint
|
|
399
|
+
* `expiresAt = now + TTL`. ABSENT → the default `CONTINUATION_EXPIRES_TTL_MS`.
|
|
400
|
+
*/
|
|
401
|
+
continuationTtlMs?: number;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Map the lowercase wire `AgentKind` (`"claude"`) used by the engine dispatch
|
|
405
|
+
* + the reviewer registry to the UPPERCASE LE `AgentKind` (`"CLAUDE"`) the
|
|
406
|
+
* spawn path uses. Append-only.
|
|
407
|
+
*/
|
|
408
|
+
declare function reviewerAgentToLeAgent(agent: ReviewerAgentKind): AgentKind;
|
|
409
|
+
/**
|
|
410
|
+
* Extract the NUMERIC seat-id from the resolved seat-id STRING the engine
|
|
411
|
+
* dispatch carries (`"1::codex"` → 1, bare `"1"` → 1). Returns null when the
|
|
412
|
+
* leading token isn't a non-negative integer.
|
|
413
|
+
*/
|
|
414
|
+
export declare function numericSeatId(resolvedSeatId: string): number | null;
|
|
415
|
+
export declare class QuorumLoop {
|
|
416
|
+
private readonly deps;
|
|
417
|
+
private readonly sleep;
|
|
418
|
+
/** The active task this desktop is driving (set at start_task). */
|
|
419
|
+
private activeTaskId;
|
|
420
|
+
private taskEpoch;
|
|
421
|
+
private readonly epochByTaskId;
|
|
422
|
+
private readonly tokensByTaskId;
|
|
423
|
+
private readonly tokensCountedGates;
|
|
424
|
+
/**
|
|
425
|
+
* The implementor brief for the active task (set at start_task). The
|
|
426
|
+
* GATE_DISPATCH consumer reads this to drive the round-0 implementor spawn —
|
|
427
|
+
* the brief is no longer threaded inline through the spawn call (the spawn
|
|
428
|
+
* moved out of `startTask` into the GATE_DISPATCH consumer, §3.C.20 FIX).
|
|
429
|
+
*/
|
|
430
|
+
private activeBrief;
|
|
431
|
+
/**
|
|
432
|
+
* IMAGE-ATTACHMENT-DESIGN.md §5 — TASK-SCOPED image attachments (parallel to
|
|
433
|
+
* `activeBrief`/`activeBriefByTask`). The single-impl path uses `activeAttachments`;
|
|
434
|
+
* the team path keys by the child `taskId` in `activeAttachmentsByTask` (armed at
|
|
435
|
+
* `registerTeamTaskBrief`, before its buffered GATE_DISPATCH drains). Re-copied
|
|
436
|
+
* into EACH round's shadow at `runImplementorRound` (round-0, revise, continuation,
|
|
437
|
+
* per track). A LOCAL-file reference (sourcePath + metadata), never bytes/wire.
|
|
438
|
+
*/
|
|
439
|
+
private activeAttachments;
|
|
440
|
+
private readonly activeAttachmentsByTask;
|
|
441
|
+
/** IMAGE-ATTACHMENT-DESIGN.md §5 — GROUP-keyed team attachments, armed at launch, fanned per-track at `registerTeamTaskBrief`. */
|
|
442
|
+
private readonly teamAttachmentsByGroup;
|
|
443
|
+
/**
|
|
444
|
+
* CP-12 W2.b (§3.C.2 / §3.C.3 / M5) — per-task brief + agent + trackIndex for
|
|
445
|
+
* the TEAM path. Replaces the single `activeBrief` singleton for multi-task
|
|
446
|
+
* routing: a `GATE_DISPATCH` whose OUTER envelope `task_id` is a known team
|
|
447
|
+
* task resolves `{ brief, agent }` HERE (keyed by the track's child `task_id`),
|
|
448
|
+
* rather than from the single `activeTaskId`/`activeBrief`. Populated by the
|
|
449
|
+
* brief→task_id bridge at `TrackAssigned` (the shell's `launchTrack` looks up
|
|
450
|
+
* the parse-time `briefsByTrackIndex.get(track_index)` and inserts here via the
|
|
451
|
+
* `registerTeamTaskBrief` callback). The single-impl path is UNAFFECTED — its
|
|
452
|
+
* dispatch carries `envelopeTaskId === this.activeTaskId` (the engine stamps the
|
|
453
|
+
* owning task on EVERY dispatch), so `handleGateDispatch`'s
|
|
454
|
+
* `envelopeTaskId !== this.activeTaskId` discriminator falls it through to the
|
|
455
|
+
* single `activeTaskId`/`activeBrief` spawn (byte-identical to today).
|
|
456
|
+
*/
|
|
457
|
+
/**
|
|
458
|
+
* CP-12 W2.b (§3.C.2 / §3.C.4) — TRUE once a `/team` has been issued THIS shell
|
|
459
|
+
* session (the shell calls `markTeamSessionActive()` at `/team` parse time).
|
|
460
|
+
* Disambiguates the two cases where a team `GATE_DISPATCH` arrives for a task
|
|
461
|
+
* NOT yet in `activeBriefByTask`:
|
|
462
|
+
* - active (a `/team` ran this session) → an EARLY-PACKET race: the
|
|
463
|
+
* `GATE_DISPATCH` beat its track's `TrackAssigned`; BUFFER + drain on
|
|
464
|
+
* registration (§3.C.2).
|
|
465
|
+
* - NOT active (post-restart: the in-memory brief map is gone, no `/team`
|
|
466
|
+
* re-issued) → FAIL LOUD with the §3.C.4 remediation (never spawn an
|
|
467
|
+
* empty-brief implementor).
|
|
468
|
+
*/
|
|
469
|
+
private teamSessionActive;
|
|
470
|
+
/**
|
|
471
|
+
* CP-12 W2.b (§3.C.3 / M4) — the per-track briefs, keyed by the COMPOSITE
|
|
472
|
+
* `teamBriefKey(taskGroupId, trackIndex)`. Captured at `/team` time via
|
|
473
|
+
* `setTeamBriefs(taskGroupId, …)` (after `createTaskGroup` returns the id) AND
|
|
474
|
+
* seeded by recovery PASS 1. The `registerTeamTask` bridge + recovery launch read
|
|
475
|
+
* `getTeamBrief(taskGroupId, trackIndex)` when `TrackAssigned` correlates a track.
|
|
476
|
+
* Desktop-local — NEVER on the wire.
|
|
477
|
+
*
|
|
478
|
+
* CP-12 W4 (#593 Stage-2 r2): keyed by `(taskGroupId, trackIndex)`, NOT bare
|
|
479
|
+
* `trackIndex` — B.2 (`getInFlightTeamTracks`) aggregates rows across ALL
|
|
480
|
+
* in-flight groups for the session, so a bare-index key let two groups that
|
|
481
|
+
* share a `trackIndex` overwrite each other's brief.
|
|
482
|
+
*/
|
|
483
|
+
private teamBriefsByKey;
|
|
484
|
+
private readonly activeBriefByTask;
|
|
485
|
+
/**
|
|
486
|
+
* CP-1.f revise-context fix (dogfood task 2ac68708, 2026-06-11) — the BINDING
|
|
487
|
+
* user-requested changes for a task, accumulated across ALL revise rounds.
|
|
488
|
+
*
|
|
489
|
+
* Each Reject-with-notes the user submits (desktop OR mobile) is delivered by
|
|
490
|
+
* the engine on a single synthetic `seat_id === 'user'` finding inside the
|
|
491
|
+
* `REVISE_FEEDBACK` payload (engine `workflow.rs::open_user_revise_round`).
|
|
492
|
+
* `handleReviseFeedback` records it here via `recordUserNote` BEFORE composing
|
|
493
|
+
* the round's brief — a SINGLE capture point that covers both surfaces (the
|
|
494
|
+
* old `index.ts onTerminalDecision` capture fired only for DESKTOP decisions,
|
|
495
|
+
* silently dropping mobile-originated notes). The revise brief then re-states
|
|
496
|
+
* EVERY accumulated note as a BINDING requirement so a later round can never
|
|
497
|
+
* silently drop an earlier user requirement (the live bug: round-1's JSDoc
|
|
498
|
+
* requirement evaporated by round 2 because the round-2 implementor brief
|
|
499
|
+
* carried ONLY round-1's reviewer findings — no original task, no prior user
|
|
500
|
+
* note). Keyed by taskId so an overlapping terminal signal resolves the right
|
|
501
|
+
* task's notes.
|
|
502
|
+
*/
|
|
503
|
+
private readonly userNotesByTask;
|
|
504
|
+
/** Seats with a reviewer subprocess currently running, keyed `gateId::seatId`. */
|
|
505
|
+
private readonly runningSeats;
|
|
506
|
+
/** Seats whose verdict has been submitted, keyed `gateId::seatId`. */
|
|
507
|
+
private readonly submittedSeats;
|
|
508
|
+
/**
|
|
509
|
+
* CP-1.f live progress surfacing — per-gate progress bookkeeping. The
|
|
510
|
+
* single-task loop receives REVIEWER_DISPATCH packets ONE seat at a time
|
|
511
|
+
* (no upfront total on the wire), so we count seats DISPATCHED on this desktop
|
|
512
|
+
* as the "expected" denominator and verdicts SUBMITTED as the numerator. Keyed
|
|
513
|
+
* by gateId → set of seatIds (idempotent under re-delivery / recovery).
|
|
514
|
+
*/
|
|
515
|
+
private readonly dispatchedSeatsByGate;
|
|
516
|
+
private readonly verdictedSeatsByGate;
|
|
517
|
+
/** Gates with an implementor round currently running. */
|
|
518
|
+
private readonly inFlightImplementorGates;
|
|
519
|
+
/** Gates whose implementor output has been submitted. */
|
|
520
|
+
private readonly submittedOutputGates;
|
|
521
|
+
/**
|
|
522
|
+
* PHASE-CP-10-MIN (#585) #C10M-16 / Codex-H1 — the per-task ACTIVE
|
|
523
|
+
* implementor context. `runImplementorRound` populates it at spawn (wiring
|
|
524
|
+
* the abort `signal`) + clears it in `finally`. A mid-flight `/continue
|
|
525
|
+
* request` — or the foreign-accept consumer — sets `handoffClaimed = true` +
|
|
526
|
+
* calls `handle.abort()` so the still-running original implementor is
|
|
527
|
+
* suppressed: the post-`await handle.done` path then SKIPS
|
|
528
|
+
* captureDiff/submit/surfaceHalt/round_failed and just `discardShadow`s. The
|
|
529
|
+
* DDB mutual exclusion (#C10M-14) only blocks a late `mark_resolved`; this
|
|
530
|
+
* desktop suppression closes the late-LOCAL-submit race. Keyed by taskId.
|
|
531
|
+
*/
|
|
532
|
+
private readonly activeImplementorByTask;
|
|
533
|
+
/**
|
|
534
|
+
* PHASE-CP-10-MIN (#585) §2.3 / #C10M-16 — offers already resumed (the
|
|
535
|
+
* dedupe set; keyed on `offer_id`, the same axis as the audit slot #C10M-4).
|
|
536
|
+
* A LOCAL accept adds the offerId + resumes inline; the `onApplyUserDecision`
|
|
537
|
+
* consumer suppresses an own-echo whose `offer_id` is already in the set and
|
|
538
|
+
* drives the resume only for a FOREIGN (mobile-originated) accept. First-add
|
|
539
|
+
* wins; the second observation is a no-op. `offer_id`-keyed (NOT
|
|
540
|
+
* `gate_id`-only) because a CANCEL→re-request puts two offers on one gate.
|
|
541
|
+
*/
|
|
542
|
+
private readonly resumedOffers;
|
|
543
|
+
/**
|
|
544
|
+
* PHASE-CP-10-MIN (#585) #C10M-12 / M6 — agents that have quota-failed for
|
|
545
|
+
* the active task (bounds the quota-substitution loop). Before firing a
|
|
546
|
+
* quota offer, if EVERY distinct configured agent is already in the set →
|
|
547
|
+
* terminal `surfaceHalt` instead of another offer. Keyed by taskId; a desktop
|
|
548
|
+
* restart resets it (acceptable — the user re-decides).
|
|
549
|
+
*/
|
|
550
|
+
private readonly quotaFailedAgentsByTask;
|
|
551
|
+
/**
|
|
552
|
+
* #585 auto-continuation (Max) — offerIds minted by the QUOTA auto-fork
|
|
553
|
+
* (`handleQuotaExhaustedFork → requestContinuation`), as opposed to the
|
|
554
|
+
* user-requested `/continue request` path (which mints its OWN distinct
|
|
555
|
+
* offerId). Max auto-continuation MUST fire ONLY for quota-origin offers; a
|
|
556
|
+
* user-requested handoff is the user's explicit choice to pick a target and
|
|
557
|
+
* must NEVER be auto-accepted (Stage-2 Codex+agy HIGH — gate on quota ORIGIN,
|
|
558
|
+
* not prompt KIND). Bounded by the quota loop (≤ one offer per agent per task).
|
|
559
|
+
* A desktop restart empties this → a pre-restart quota offer falls back to the
|
|
560
|
+
* manual prompt (fail-closed; the Max user is prompted instead of auto-resumed).
|
|
561
|
+
*/
|
|
562
|
+
private readonly quotaContinuationOffers;
|
|
563
|
+
/** REVISE_FEEDBACK ids already consumed (deterministic dedup). */
|
|
564
|
+
private readonly seenReviseFeedbackIds;
|
|
565
|
+
/**
|
|
566
|
+
* CP-12 W2.b (dogfood team-revise fix, 2026-06-13) — `gateId → taskId` for every
|
|
567
|
+
* dispatched implementor round. A `ReviseFeedback` packet carries ONLY gate ids
|
|
568
|
+
* (`ReviseFeedbackPayload` has no per-track `taskId`/`trackIndex`), so the revise
|
|
569
|
+
* handler has no other way to learn WHICH track (child `task_id`) the revise is
|
|
570
|
+
* for. Without this the team revise round fell back to the single-impl globals
|
|
571
|
+
* (`activeTaskId`/`activeImplementorAgent`/`activeBrief`) — empty/wrong in a team
|
|
572
|
+
* session — and re-spawned the implementor against an empty taskId, throwing
|
|
573
|
+
* "an unexpected error occurred". Populated at EVERY `runImplementorRound`
|
|
574
|
+
* (round-0 + revise, single-impl + team) so `payload.gateId` (the parent gate
|
|
575
|
+
* that produced the REVISE) resolves to the right track. The single-impl path
|
|
576
|
+
* resolves to `activeTaskId` here too (so NO single-vs-team discriminator — the
|
|
577
|
+
* downstream `?? this.activeX` fallbacks keep single-impl byte-identical).
|
|
578
|
+
* Set-only + unbounded, mirroring the sibling `seenReviseFeedbackIds` /
|
|
579
|
+
* `seenGateDispatchRunIds` / `activeBriefByTask` per-round desktop maps (gate
|
|
580
|
+
* cardinality is bounded by those same dedup sets).
|
|
581
|
+
*/
|
|
582
|
+
private readonly taskByGateId;
|
|
583
|
+
/**
|
|
584
|
+
* GATE_DISPATCH `gateRunId`s already consumed (idempotent re-delivery dedup).
|
|
585
|
+
* A sweeper / WS re-deliver of the SAME round-0 dispatch must NOT double-spawn
|
|
586
|
+
* the implementor. Symmetric to `seenReviseFeedbackIds` (§3.C.20 FIX). The
|
|
587
|
+
* `runImplementorRound` in-flight/submitted double-guard is the second layer.
|
|
588
|
+
*/
|
|
589
|
+
private readonly seenGateDispatchRunIds;
|
|
590
|
+
/**
|
|
591
|
+
* §4.2 (team recovery double-launch fix — dogfood 2026-06-15) — gateRunIds
|
|
592
|
+
* whose track `launchRecoveredTrack` has CLAIMED for recovery, stamped
|
|
593
|
+
* SYNCHRONOUSLY at the dedup point BEFORE the `await recoverLaunch`. Distinct
|
|
594
|
+
* from `seenGateDispatchRunIds`, which `handleTeamGateDispatch` stamps only
|
|
595
|
+
* AFTER its async work — leaving a window where a concurrent B.2 recovery
|
|
596
|
+
* passes the dedup, runs `recoverLaunch` → the `'duplicate'` repair →
|
|
597
|
+
* `registerTeamTaskBrief` a SECOND time → duplicate gate/verdict/applied/
|
|
598
|
+
* convergence emits (the dogfood "Applied 1 file…" ×3 / duplicate final_approval;
|
|
599
|
+
* no data impact, exactly 2 files). Claiming here, before any await, makes the
|
|
600
|
+
* "recover once" dedup actually atomic. Kept SEPARATE from
|
|
601
|
+
* `seenGateDispatchRunIds` so the legitimate round-0 gate dispatch still runs
|
|
602
|
+
* (stamping that set here would suppress the implementor entirely).
|
|
603
|
+
*/
|
|
604
|
+
private readonly recoveredGateRunIds;
|
|
605
|
+
/**
|
|
606
|
+
* §4.1 — DURABLE per-track tombstone for a present-but-CORRUPT brief
|
|
607
|
+
* ciphertext (decrypt threw during recovery PASS 1). Blocks BOTH recovery
|
|
608
|
+
* relaunch (PASS 2) AND any future LIVE `TrackAssigned`/drain for that track
|
|
609
|
+
* (§4.3) — a corrupt brief must NEVER spawn an empty-brief implementor on
|
|
610
|
+
* EITHER path. The track times out via the engine sweeper (bounded). Distinct
|
|
611
|
+
* from a `null` `encryptedBrief` (a legit empty brief).
|
|
612
|
+
*
|
|
613
|
+
* CP-12 W4 (#593 Stage-2 r2): keyed by the COMPOSITE `teamBriefKey(taskGroupId,
|
|
614
|
+
* trackIndex)`, NOT bare `trackIndex` — B.2 aggregates across groups, so a
|
|
615
|
+
* tombstone on group A's track 0 must not block group B's (valid) track 0.
|
|
616
|
+
*/
|
|
617
|
+
private readonly corruptBriefTracks;
|
|
618
|
+
/**
|
|
619
|
+
* #592 PROVENANCE GUARD (2026-06-26) — the task-group ids THIS desktop
|
|
620
|
+
* ORIGINATED (recorded in `armB1Recovery`, right after a successful
|
|
621
|
+
* `createTaskGroup`). Class B team packets broadcast PER-USER, and B.2 recovery
|
|
622
|
+
* (`recoverTeamTracks`) discovers EVERY in-flight group session-wide — so a
|
|
623
|
+
* SECOND desktop/container on the same account receives + processes this
|
|
624
|
+
* desktop's `TrackAssigned`/`GateDispatch`. WITHOUT this guard a desktop that
|
|
625
|
+
* already owns a group pins a FOREIGN group's base on its OWN workspace and
|
|
626
|
+
* submits an empty round-0 → born-halts the REAL originator's gate (dogfood
|
|
627
|
+
* 2026-06-26, proven: `bold_lewin` killed another run's gate `7681a432`). When
|
|
628
|
+
* NON-EMPTY, recovery + live team dispatch DROP any group not in this set. EMPTY
|
|
629
|
+
* (fresh process / post-crash) → no filter, so legitimate crash-recovery of an
|
|
630
|
+
* own in-flight group is preserved (the rare two-cold-desktops edge is unchanged).
|
|
631
|
+
*/
|
|
632
|
+
private readonly ownedGroupIds;
|
|
633
|
+
/**
|
|
634
|
+
* CP-12 W4 (#593 Stage-2 r2) — the composite key for `teamBriefsByKey` +
|
|
635
|
+
* `corruptBriefTracks`. `(taskGroupId, trackIndex)` so two in-flight groups
|
|
636
|
+
* that share a `trackIndex` (B.2 aggregates session-wide) never collide.
|
|
637
|
+
*/
|
|
638
|
+
private teamBriefKey;
|
|
639
|
+
/**
|
|
640
|
+
* PHASE-589/469 W3.1 (C1) — the GROUP-WIDE monotonic promote-order counter,
|
|
641
|
+
* keyed by `taskGroupId`. Each team track promote reserves
|
|
642
|
+
* `[base, base + appliedCount)` so the durable manifest's `promoteOrder` is a
|
|
643
|
+
* single increasing sequence ACROSS every track in the group — the reverse of
|
|
644
|
+
* which drives reverse-apply (last-applied reverted first, LOCK #589-I).
|
|
645
|
+
* In-memory: a crash loses the counter, but the durable manifest already
|
|
646
|
+
* persists each entry's `promoteOrder`, so the FIRST reservation per group in
|
|
647
|
+
* this process re-seeds the counter from the manifest's high-water mark
|
|
648
|
+
* (see {@link reservePromoteOrder}), and a recovery sorts off the persisted
|
|
649
|
+
* values.
|
|
650
|
+
*/
|
|
651
|
+
private readonly groupPromoteOrder;
|
|
652
|
+
/**
|
|
653
|
+
* PHASE-589/469 (M1) — groups whose `groupPromoteOrder` has already been
|
|
654
|
+
* seeded from the durable manifest's high-water mark IN THIS PROCESS. The
|
|
655
|
+
* seed runs ONCE per group per process (the first `reservePromoteOrder`);
|
|
656
|
+
* thereafter the in-memory counter is authoritative.
|
|
657
|
+
*/
|
|
658
|
+
private readonly promoteOrderSeeded;
|
|
659
|
+
/**
|
|
660
|
+
* PHASE-589/469 W3-H1 (Stage-2 Codex r2) — per-GROUP async MUTEX for
|
|
661
|
+
* `reservePromoteOrder`. The seed-then-reserve body is a check-then-`await`-then-
|
|
662
|
+
* act sequence: it marks `promoteOrderSeeded` SYNCHRONOUSLY, then `await`s the
|
|
663
|
+
* durable manifest high-water read BEFORE setting `groupPromoteOrder`. A SECOND
|
|
664
|
+
* same-group reservation entering during that await would see `seeded === true`
|
|
665
|
+
* yet `groupPromoteOrder === undefined`, skip the seed, and reserve `base = 0` —
|
|
666
|
+
* so two same-group tracks could BOTH reserve base 0 (and the slow first reserve
|
|
667
|
+
* then clobbers the second's counter write). Two tracks at the same
|
|
668
|
+
* `(promoteOrder, path)` make `appendPromote` overwrite the older manifest entry
|
|
669
|
+
* (it dedups idempotently on `(promoteOrder, path)`), losing the original
|
|
670
|
+
* pre-image so the all-or-nothing revert can't restore baseline.
|
|
671
|
+
*
|
|
672
|
+
* The shadow promote mutex (`promoteShadowLocked` / {@link runExclusiveShadowOp})
|
|
673
|
+
* is keyed per-TASK, but `promoteOrder` is per-GROUP, so this needs its OWN
|
|
674
|
+
* per-group serialization. Same promise-chain shape as `runExclusiveShadowOp`
|
|
675
|
+
* (predecessor-rejection-isolated, entry GC'd when the tail it stored settles).
|
|
676
|
+
*/
|
|
677
|
+
private readonly promoteOrderOpsByGroup;
|
|
678
|
+
/**
|
|
679
|
+
* Serialize one `reservePromoteOrder` body for `taskGroupId` behind any in-flight
|
|
680
|
+
* reservation for the SAME group (mirror of {@link runExclusiveShadowOp}, keyed
|
|
681
|
+
* per-group not per-task). A predecessor's rejection is isolated at the
|
|
682
|
+
* sequencing layer; the map entry is GC'd once the tail we appended is still the
|
|
683
|
+
* current tail (an idle group leaks no entry).
|
|
684
|
+
*/
|
|
685
|
+
private runExclusivePromoteOrderOp;
|
|
686
|
+
/**
|
|
687
|
+
* Reserve `count` contiguous group-wide promote-order slots; returns the base.
|
|
688
|
+
*
|
|
689
|
+
* PHASE-589/469 (M1) — on the FIRST reservation for a given `taskGroupId` in
|
|
690
|
+
* this process, seed the in-memory counter from the durable manifest's
|
|
691
|
+
* high-water mark (`max(promoteOrder) + 1`) so a post-crash second track (or a
|
|
692
|
+
* re-promote after the in-memory Map was emptied by a restart) continues ABOVE
|
|
693
|
+
* the persisted orders and NEVER reuses one. Without this, a crash that empties
|
|
694
|
+
* `groupPromoteOrder` would let a recovered second track restart at base 0 and
|
|
695
|
+
* collide with track 0's already-persisted orders — leaving two manifest
|
|
696
|
+
* entries at the same `promoteOrder` for different paths, whose reverse-apply
|
|
697
|
+
* order is then undefined (a shared file could be deleted-before-restored).
|
|
698
|
+
*
|
|
699
|
+
* TEAM-only + best-effort: the seed reads the durable store only when one is
|
|
700
|
+
* wired (team tracks); if the manifest read throws, fall back to the current
|
|
701
|
+
* in-memory base (log a warning, never block the promote).
|
|
702
|
+
*
|
|
703
|
+
* Serialized PER-GROUP ({@link runExclusivePromoteOrderOp}, W3-H1) so the whole
|
|
704
|
+
* seed-then-reserve body is atomic w.r.t. concurrent same-group calls: a second
|
|
705
|
+
* concurrent reservation WAITS, then reads the now-SEEDED counter and reserves
|
|
706
|
+
* the next contiguous slot (distinct, non-overlapping bases — never duplicate 0).
|
|
707
|
+
*/
|
|
708
|
+
private reservePromoteOrder;
|
|
709
|
+
private reservePromoteOrderLocked;
|
|
710
|
+
/**
|
|
711
|
+
* §4.3 — the startup ORDERING BARRIER as a REFCOUNT (Stage-2 r1 Codex HIGH).
|
|
712
|
+
* `0` ⇒ no recovery seeding in flight ⇒ barrier UP (live TEAM packets consume
|
|
713
|
+
* normally; single-impl / no-recovery path byte-identical). `> 0` ⇒ at least
|
|
714
|
+
* one recovery op (B.1 / a B.2 attempt sequence) is seeding ⇒ barrier DOWN
|
|
715
|
+
* (live TEAM packets BUFFER). It is a refcount, not a boolean, so the barrier
|
|
716
|
+
* spans the ENTIRE recovery window — incremented SYNCHRONOUSLY at the start of
|
|
717
|
+
* each recovery op (before its first GSI query, so a live packet arriving
|
|
718
|
+
* DURING the query is buffered, not consumed empty) and decremented in a
|
|
719
|
+
* `finally`; the buffered queue is drained ONCE when the count returns to 0.
|
|
720
|
+
* This also makes concurrent B.1+B.2 safe (no per-op toggle race).
|
|
721
|
+
*/
|
|
722
|
+
private recoverySeedingDepth;
|
|
723
|
+
/**
|
|
724
|
+
* §4.3 — TEAM Class B packets buffered while the recovery seed barrier is up
|
|
725
|
+
* (`recoverySeedingDepth > 0`). Drained through the normal live handlers AFTER
|
|
726
|
+
* seeding completes (which now read the seeded brief / hit the corrupt
|
|
727
|
+
* tombstone). Single-impl / non-team packets are NEVER buffered here.
|
|
728
|
+
*/
|
|
729
|
+
private readonly bufferedTeamPackets;
|
|
730
|
+
/**
|
|
731
|
+
* §4.2 B.1 two-condition latch — TRUE once the Class B subscription has been
|
|
732
|
+
* established (the `onSubscribed` signal). B.1 fires only when BOTH this AND a
|
|
733
|
+
* pending `taskGroupId` are set (covers the common case where the subscription
|
|
734
|
+
* was ALREADY up before `createTaskGroup` returned).
|
|
735
|
+
*/
|
|
736
|
+
private subscriptionReady;
|
|
737
|
+
/**
|
|
738
|
+
* §4.2 B.1 two-condition latch — the `taskGroupId` `createTaskGroup` just
|
|
739
|
+
* returned, awaiting the subscription-ready condition. CLEARED immediately on
|
|
740
|
+
* B.1 fire so a later reconnect's `onSubscribed` does not re-run B.1 for an
|
|
741
|
+
* already-handled group (reconnect recovery is B.2's job).
|
|
742
|
+
*/
|
|
743
|
+
private pendingB1TaskGroupId;
|
|
744
|
+
/**
|
|
745
|
+
* Stage-2 r2 (Codex) FIX A — START in-flight flag. The engine emits
|
|
746
|
+
* GATE_DISPATCH from INSIDE the `startTask` handler (workflow.rs:3097), BEFORE
|
|
747
|
+
* the mutation response returns, so the live `onClassBPacket` WS can deliver
|
|
748
|
+
* GATE_DISPATCH to the desktop BEFORE `await appsyncClient.startTask(...)`
|
|
749
|
+
* resolves. `startTask` arms the active task + sets this flag BEFORE the
|
|
750
|
+
* await; while it is set, `handleGateDispatch` BUFFERS the packet (into
|
|
751
|
+
* `pendingGateDispatches`) instead of spawning, so we never spawn an
|
|
752
|
+
* implementor for a gate whose START is not yet CONFIRMED. The buffer is
|
|
753
|
+
* DRAINED only after `startTask` resolves successfully (run the spawn then);
|
|
754
|
+
* on `startTask` rejection the active context is rolled back + the buffer
|
|
755
|
+
* discarded (a buffered packet whose START failed never spawns).
|
|
756
|
+
*/
|
|
757
|
+
private startTaskInFlight;
|
|
758
|
+
/**
|
|
759
|
+
* Stage-2 r2 (Codex) FIX A — GATE_DISPATCH payloads that arrived while
|
|
760
|
+
* `startTask` was in flight (single-impl early-packet race), OR (CP-12 W2.b,
|
|
761
|
+
* §3.C.2 / agy MEDIUM-2) a TEAM GATE_DISPATCH that arrived BEFORE its track's
|
|
762
|
+
* `TrackAssigned` registered the brief in `activeBriefByTask`.
|
|
763
|
+
*
|
|
764
|
+
* Each entry retains `envelopeTaskId` (the OUTER envelope's `task_id`) so a
|
|
765
|
+
* buffered TEAM dispatch keeps its routing key — the payload carries no
|
|
766
|
+
* `taskId`, and the `TrackAssigned` that registers the brief may land AFTER the
|
|
767
|
+
* `GATE_DISPATCH` under fire-and-forget consumption. The replay-on-registration
|
|
768
|
+
* drain routes each entry by its buffered `envelopeTaskId`
|
|
769
|
+
* (`handleGateDispatch(entry.payload, entry.envelopeTaskId)`). For the
|
|
770
|
+
* single-impl early-packet race the buffered `envelopeTaskId` equals the active
|
|
771
|
+
* task (the engine stamps the owning task on every dispatch), so the re-invoked
|
|
772
|
+
* `handleGateDispatch` sees `envelopeTaskId === this.activeTaskId` and falls
|
|
773
|
+
* through to the single-impl spawn (byte-identical to today).
|
|
774
|
+
*/
|
|
775
|
+
private readonly pendingGateDispatches;
|
|
776
|
+
/**
|
|
777
|
+
* CP-1.f execute-first (§4) — the per-task SHADOW workspace. Created BEFORE the
|
|
778
|
+
* round-0 implementor spawn, retained across all revise rounds + the review
|
|
779
|
+
* window, and PROMOTED on `TaskAuthorized` (#PR-1/#PR-3) or DISCARDED on every
|
|
780
|
+
* other terminal. Keyed by taskId so the class-B consumer + terminal handler
|
|
781
|
+
* resolve it without threading it through the packet path. One active task per
|
|
782
|
+
* loop, but a Map keeps the lookup robust across overlapping terminal signals.
|
|
783
|
+
*/
|
|
784
|
+
private readonly shadowsByTask;
|
|
785
|
+
/**
|
|
786
|
+
* PHASE-598 (Codex impl-r5 / LOCK #598-11) — the REVIEWED diff snapshot per TEAM
|
|
787
|
+
* task, stored when a round's gate goes live (reviewers dispatched). `promoteShadow`
|
|
788
|
+
* fails-closed if the live shadow drifts from it before promote — the airtight
|
|
789
|
+
* backstop for a declared test that spawns a DETACHED writer (the test runner waits
|
|
790
|
+
* only on the direct child, so such a writer can mutate the shadow AFTER the
|
|
791
|
+
* post-test re-capture passes but BEFORE promote). Presence ⇔ team task: single-impl
|
|
792
|
+
* never stores, so its promote path is unchanged. Cleared on promote/discard.
|
|
793
|
+
*/
|
|
794
|
+
private readonly reviewedSnapshotByTask;
|
|
795
|
+
/** Guards concurrent shadow-create for the same task (round-0 + an early re-deliver). */
|
|
796
|
+
private readonly shadowCreateInFlight;
|
|
797
|
+
/**
|
|
798
|
+
* CP-14 Wave A — the ONE pinned base SHA per task group (`worktree_merge`),
|
|
799
|
+
* captured (memoized) at the first track of the group BEFORE any shadow is
|
|
800
|
+
* created, so EVERY track branches from the same base → a deterministic
|
|
801
|
+
* merge-train input (Stage-1 plan-review MED-3). Promise-memoized so concurrent
|
|
802
|
+
* track registrations share a single `git rev-parse HEAD`.
|
|
803
|
+
*/
|
|
804
|
+
private readonly groupBaseShaInFlight;
|
|
805
|
+
/**
|
|
806
|
+
* Per-task async MUTEX for shadow LIFECYCLE mutations (restart-race fix,
|
|
807
|
+
* 2026-06-11 Stage-2 Codex r2). A promise-chain serializes EVERY map/stateDir-
|
|
808
|
+
* touching shadow op for a task — `ensureShadow`'s create-or-get section,
|
|
809
|
+
* `discardShadow`, `replaceShadowForRound0`, and `promoteShadow`'s
|
|
810
|
+
* map/stateDir section — so a check-then-act guard's snapshot (mapped vs the
|
|
811
|
+
* captured instance) stays TRUE for the whole critical section. Eliminates the
|
|
812
|
+
* "matched at entry, then replaced while awaiting" class of interleaving (the
|
|
813
|
+
* third found one-at-a-time: orphan double-discard → unmapped window → full-A-
|
|
814
|
+
* discard overlapping a restart replacement deleting B's #PR-4 marker).
|
|
815
|
+
*
|
|
816
|
+
* The chain holds the LAST queued op's settle promise; `runExclusiveShadowOp`
|
|
817
|
+
* appends `fn` to run after it, swallows the predecessor's rejection at the
|
|
818
|
+
* sequencing layer (errors propagate to the OWNING caller, never poison the
|
|
819
|
+
* chain for the next op), and clears the entry once the tail it stored is the
|
|
820
|
+
* one that settled (so an idle task leaks no entry). The serialized region is
|
|
821
|
+
* fs/git-only with NO nested `runExclusiveShadowOp` call (the wrapped methods
|
|
822
|
+
* never call each other), so it cannot self-deadlock.
|
|
823
|
+
*/
|
|
824
|
+
private readonly shadowOpsByTask;
|
|
825
|
+
/**
|
|
826
|
+
* Serialize one shadow lifecycle op for `taskId` behind any in-flight op for
|
|
827
|
+
* the same task. The op's result/rejection is returned to ITS caller; a
|
|
828
|
+
* predecessor's rejection is isolated (caught at the sequencing layer) so it
|
|
829
|
+
* never poisons a later op. The map entry is cleared only when the tail we
|
|
830
|
+
* appended is still the current tail (no later op queued behind us).
|
|
831
|
+
*/
|
|
832
|
+
private runExclusiveShadowOp;
|
|
833
|
+
/**
|
|
834
|
+
* The last terminal workspace outcome (promote / discard) this loop applied,
|
|
835
|
+
* for the `summarize_current_status` shell answer (Fix 2). RELATIVE file paths
|
|
836
|
+
* only — never any diff content (the user owns the workspace, so paths are
|
|
837
|
+
* permitted per the promote/discard surface exception; content is not). `null`
|
|
838
|
+
* until the first promote/discard lands. Updated ONLY when a promote actually
|
|
839
|
+
* MATERIALIZES files (a re-delivered no-op packet does NOT overwrite it).
|
|
840
|
+
*/
|
|
841
|
+
private lastWorkspaceOutcome;
|
|
842
|
+
constructor(deps: QuorumLoopDeps);
|
|
843
|
+
/** The current active task id (for the recovery poll). */
|
|
844
|
+
get activeTask(): string | null;
|
|
845
|
+
/**
|
|
846
|
+
* Audited-path fix (Fix 5) — the host's detected implementor agents
|
|
847
|
+
* (UPPERCASE `AgentKind`), as threaded into the loop at construction. Used by
|
|
848
|
+
* the `/task` deterministic shortcut + the planner `start_task` path to select
|
|
849
|
+
* which implementor to spawn (default CLAUDE when present, else the first
|
|
850
|
+
* detected). Returns a shallow copy so callers can't mutate the loop's record.
|
|
851
|
+
* Empty when the host passed none (a definitive "nothing detected" →
|
|
852
|
+
* `selectImplementorAgent` falls back to CLAUDE).
|
|
853
|
+
*/
|
|
854
|
+
/**
|
|
855
|
+
* IMAGE-ATTACHMENT-DESIGN.md §5 (Stage-2 R3 HIGH): expose the loop's durable
|
|
856
|
+
* store so the LIVE team-launch sites can persist the RetainedTeamLaunchSpec
|
|
857
|
+
* (incl. attachments) — otherwise `setTeamLaunchSpec` never runs on a live launch
|
|
858
|
+
* and `recoverTeamTracks`'s restore has nothing to read. Same instance the loop
|
|
859
|
+
* uses for reissue/recovery/promote.
|
|
860
|
+
*/
|
|
861
|
+
getDurableStore(): RevertManifestStore | undefined;
|
|
862
|
+
getDetectedAgents(): AgentKind[];
|
|
863
|
+
/**
|
|
864
|
+
* #585 auto-continuation (Max) — pick the agent to AUTO-CONTINUE on when the
|
|
865
|
+
* implementor for `taskId` hit a quota wall, or `null` to fall through to the
|
|
866
|
+
* existing `continuation_offer_handoff` prompt. Returns a target ONLY when:
|
|
867
|
+
* (a) tier === MAX (the "don't bother me" automation tier — mirrors auto-revise), AND
|
|
868
|
+
* (b) ≥1 host-detected agent is NOT already quota-failed for this task.
|
|
869
|
+
* The pick is the STRONGEST such agent (`TIER2_REVIEWER_RANK`), deterministic. The
|
|
870
|
+
* quota-failed set (populated by `handleQuotaExhaustedFork` BEFORE the offer) already
|
|
871
|
+
* contains the just-failed source agent, so the result is never the agent that just
|
|
872
|
+
* hit quota. All detected agents quota-failed → `null` (the existing terminal halt
|
|
873
|
+
* fires). Non-MAX / unresolved / absent tier → `null` (fail-closed to the prompt).
|
|
874
|
+
* Pure read; never throws.
|
|
875
|
+
*/
|
|
876
|
+
pickAutoContinuationTarget(taskId: string): AgentKind | null;
|
|
877
|
+
/**
|
|
878
|
+
* #585 auto-continuation (Max) — true iff `offerId` was minted by the QUOTA
|
|
879
|
+
* auto-fork (`handleQuotaExhaustedFork`). Max auto-continuation gates on THIS,
|
|
880
|
+
* not the prompt kind, so a user-requested `/continue request` handoff (a
|
|
881
|
+
* distinct offerId) is NEVER auto-accepted (Stage-2 Codex+agy HIGH). Empty
|
|
882
|
+
* after a desktop restart → returns false → the manual prompt (fail-closed).
|
|
883
|
+
* Pure read; never throws.
|
|
884
|
+
*/
|
|
885
|
+
isQuotaContinuationOffer(offerId: string): boolean;
|
|
886
|
+
/**
|
|
887
|
+
* PHASE-590 §3.B — the canonicalized real working dir (the repo). The local
|
|
888
|
+
* team decomposer (`runLocalDecomposer`) spawns a read-only `plan`-mode
|
|
889
|
+
* implementor against THIS dir so it can read the tree before emitting the
|
|
890
|
+
* `WorkItemInput[]` decomposition. Read-only accessor (the decomposer never
|
|
891
|
+
* writes here — `plan` mode).
|
|
892
|
+
*/
|
|
893
|
+
getWorkingDir(): string;
|
|
894
|
+
/**
|
|
895
|
+
* Read-only accessor for the last terminal workspace outcome (promote with its
|
|
896
|
+
* RELATIVE file paths, or discard). Used by the shell's
|
|
897
|
+
* `summarize_current_status` answer to name the files last applied. Returns a
|
|
898
|
+
* shallow copy so a caller can't mutate the loop's record. `null` until the
|
|
899
|
+
* first promote/discard.
|
|
900
|
+
*/
|
|
901
|
+
getLastWorkspaceOutcome(): {
|
|
902
|
+
kind: 'promoted';
|
|
903
|
+
files: string[];
|
|
904
|
+
} | {
|
|
905
|
+
kind: 'discarded';
|
|
906
|
+
} | null;
|
|
907
|
+
/** The DI callbacks the shell wires into its ClassBConsumer. */
|
|
908
|
+
classBConsumerDeps(): {
|
|
909
|
+
classBSignatureVerifier: (packet: import('../local-executor/types').ClassBInbound) => Promise<boolean>;
|
|
910
|
+
gateDispatch: (payload: GateDispatchPayload, envelopeTaskId?: string) => Promise<void>;
|
|
911
|
+
spawnReviewers: (payload: ReviewerDispatchPayload) => Promise<void>;
|
|
912
|
+
runReviseRound: (payload: ReviseFeedbackPayload) => Promise<void>;
|
|
913
|
+
};
|
|
914
|
+
private cachedSigningKey;
|
|
915
|
+
private verifySignature;
|
|
916
|
+
/**
|
|
917
|
+
* Drive the engine START from a planner `start_task` decision. Mints the
|
|
918
|
+
* taskId, ARMS the active task (taskId/brief/agent), calls the `startTask` SDK
|
|
919
|
+
* mutation, then DRAINS any GATE_DISPATCH buffered during the START window.
|
|
920
|
+
*
|
|
921
|
+
* CP-1.f Wave 3 FIX — the implementor is NOT spawned here. The engine creates
|
|
922
|
+
* the round-0 `reviewer_quorum` gate with a deterministic gate_id and emits a
|
|
923
|
+
* GATE_DISPATCH packet carrying it as `gateRunId`; the `startTask` mutation
|
|
924
|
+
* response carries ONLY the workflow-state LABEL string ("gate_plan_in_flight")
|
|
925
|
+
* — NOT the gate_id. So the desktop spawns the implementor + submits its
|
|
926
|
+
* output against `gateRunId` in `handleGateDispatch`. Using `taskId` as the
|
|
927
|
+
* gate correlation (the prior wiring) is rejected by `submitImplementorOutput`
|
|
928
|
+
* — it needs the engine's gate_id.
|
|
929
|
+
*
|
|
930
|
+
* Stage-2 r2 (Codex) FIX A — the engine emits GATE_DISPATCH from INSIDE the
|
|
931
|
+
* `startTask` handler (workflow.rs:3097), BEFORE its mutation response returns,
|
|
932
|
+
* so the live WS can deliver that round-0 GATE_DISPATCH to the desktop BEFORE
|
|
933
|
+
* the SDK `await` below resolves. To avoid spawning before the START is
|
|
934
|
+
* confirmed (and to avoid the Stage-1 FIX-3 "drop forever" no-op that hangs the
|
|
935
|
+
* loop), we ARM the active context + set `startTaskInFlight` BEFORE the await;
|
|
936
|
+
* an early GATE_DISPATCH is then BUFFERED (not spawned) and DRAINED here once
|
|
937
|
+
* the START resolves successfully. On rejection the armed context is rolled
|
|
938
|
+
* back + the buffer discarded (a buffered packet whose START failed never
|
|
939
|
+
* spawns). Invariant: an early GATE_DISPATCH is never dropped, and the
|
|
940
|
+
* implementor is never spawned until `startTask` succeeds.
|
|
941
|
+
*
|
|
942
|
+
* Best-effort: a backend error is logged + surfaced advisory by the caller
|
|
943
|
+
* (returns the taskId on success, null on failure so the caller can render the
|
|
944
|
+
* error). No `gateId` is returned — it is not known until GATE_DISPATCH.
|
|
945
|
+
*/
|
|
946
|
+
startTask(args: {
|
|
947
|
+
decision: import('../planner/types').PlannerDecision;
|
|
948
|
+
brief: string;
|
|
949
|
+
agent: AgentKind;
|
|
950
|
+
/** IMAGE-ATTACHMENT-DESIGN.md §5 — task-scoped image attachments, armed before the drain. */
|
|
951
|
+
attachments?: ImageAttachment[];
|
|
952
|
+
}): Promise<{
|
|
953
|
+
taskId: string;
|
|
954
|
+
} | null>;
|
|
955
|
+
/**
|
|
956
|
+
* CP-1.f Wave 3 FIX — consume a GATE_DISPATCH packet. The engine emits this
|
|
957
|
+
* after `startTask` creates the round-0 `reviewer_quorum` gate; its
|
|
958
|
+
* `gateRunId` is the round-0 gate_id the desktop submits the implementor
|
|
959
|
+
* output against (the `startTask` mutation response does NOT carry it).
|
|
960
|
+
*
|
|
961
|
+
* Only `gateType === "reviewer_quorum"` spawns the implementor (at START
|
|
962
|
+
* `reviewerSeats` is absent — the proposal hasn't arrived; the per-seat
|
|
963
|
+
* fan-out is driven later by REVIEWER_DISPATCH once `submitImplementorOutput`
|
|
964
|
+
* lands). `verification` gates are CP-3 (out of scope for CP-1.f single-task)
|
|
965
|
+
* and `final_approval` surfaces via `onEventCreated` INTERACTIVE_PROMPT (§2),
|
|
966
|
+
* NOT GATE_DISPATCH — both are logged + ignored (no spawn).
|
|
967
|
+
*
|
|
968
|
+
* Idempotent: deduped on `gateRunId` (a sweeper / WS re-deliver must not
|
|
969
|
+
* double-spawn), and `runImplementorRound` is itself double-guarded
|
|
970
|
+
* (in-flight / already-submitted per gate). Best-effort: errors are logged,
|
|
971
|
+
* never thrown upward.
|
|
972
|
+
*/
|
|
973
|
+
handleGateDispatch(payload: GateDispatchPayload, envelopeTaskId?: string): Promise<void>;
|
|
974
|
+
/**
|
|
975
|
+
* CP-12 W2.b (§3.C.2 / §3.C.3) — mark a `/team` issued THIS shell session, so
|
|
976
|
+
* a team `GATE_DISPATCH` for a not-yet-registered track is BUFFERED (early
|
|
977
|
+
* race) rather than failed-loud (post-restart). The shell calls this at `/team`
|
|
978
|
+
* parse time (the same parse that captures `briefsByTrackIndex`).
|
|
979
|
+
*/
|
|
980
|
+
markTeamSessionActive(): void;
|
|
981
|
+
/**
|
|
982
|
+
* CP-12 W2.b (§3.C.3 / M4) — bind the parse-time per-track briefs to their
|
|
983
|
+
* task group + mark the team session active. The shell calls this from the
|
|
984
|
+
* `/team` command dispatch AFTER `createTaskGroup` returns the `taskGroupId`
|
|
985
|
+
* (the parse-time briefs are keyed by array `trackIndex`; this re-keys them by
|
|
986
|
+
* the composite `(taskGroupId, trackIndex)`). Two synchronous statements after
|
|
987
|
+
* the `createTaskGroup` await, BEFORE `armB1Recovery` — no live `TrackAssigned`
|
|
988
|
+
* can interleave (single-threaded), so the bridge always finds the brief.
|
|
989
|
+
*
|
|
990
|
+
* CP-12 W4 (#593 Stage-2 r2): composite-keyed so two in-flight groups that
|
|
991
|
+
* share a `trackIndex` never overwrite each other's brief. Desktop-local
|
|
992
|
+
* (NEVER on the wire). A subsequent `/team` for a NEW group adds its OWN keys.
|
|
993
|
+
*/
|
|
994
|
+
setTeamBriefs(taskGroupId: string, briefsByTrackIndex: Map<number, string>): void;
|
|
995
|
+
/**
|
|
996
|
+
* IMAGE-ATTACHMENT-DESIGN.md §5 — arm the GROUP's image attachments at team
|
|
997
|
+
* launch (alongside `setTeamBriefs`), BEFORE any `TrackAssigned`.
|
|
998
|
+
* `registerTeamTaskBrief` fans them per-track into `activeAttachmentsByTask` before
|
|
999
|
+
* that track's buffered GATE_DISPATCH drains, so the round-0 spawn copies them in.
|
|
1000
|
+
*/
|
|
1001
|
+
setTeamAttachments(taskGroupId: string, attachments: ImageAttachment[]): void;
|
|
1002
|
+
/**
|
|
1003
|
+
* CP-12 W2.b (§3.C.3) — read the brief for `(taskGroupId, trackIndex)`. The
|
|
1004
|
+
* `registerTeamTask` bridge (wired in the shell) reads this when `TrackAssigned`
|
|
1005
|
+
* correlates a track to its child `task_id`; recovery PASS 2 reads it for a
|
|
1006
|
+
* relaunched track. Empty string when the user issued no `description` for the
|
|
1007
|
+
* track (a track with no brief runs an empty-brief round-0; the engine's #EF-1
|
|
1008
|
+
* born-halt surfaces it). Composite-keyed (#593 Stage-2 r2) — see `teamBriefKey`.
|
|
1009
|
+
*/
|
|
1010
|
+
getTeamBrief(taskGroupId: string, trackIndex: number): string;
|
|
1011
|
+
/**
|
|
1012
|
+
* CP-12 W2.b (§3.C.3 / M5) — the brief→task_id bridge. `launchTrack` calls this
|
|
1013
|
+
* at `TrackAssigned` (the FIRST surface carrying both `track_index` AND the
|
|
1014
|
+
* track's child `task_id`), having resolved the parse-time
|
|
1015
|
+
* `briefsByTrackIndex.get(track_index)` for the brief. Inserts the per-task
|
|
1016
|
+
* brief/agent/scope into `activeBriefByTask`, THEN drains any team
|
|
1017
|
+
* `GATE_DISPATCH` for this `task_id` that arrived BEFORE the track registered
|
|
1018
|
+
* (the §3.C.2 early-packet race). Idempotent: a duplicate `TrackAssigned`
|
|
1019
|
+
* (same generation) re-registers the same entry + the buffered dispatch (if
|
|
1020
|
+
* any) is already drained, so the drain is a no-op.
|
|
1021
|
+
*/
|
|
1022
|
+
registerTeamTaskBrief(taskId: string, entry: {
|
|
1023
|
+
brief: string;
|
|
1024
|
+
agent: AgentKind;
|
|
1025
|
+
taskGroupId: string;
|
|
1026
|
+
trackIndex: number;
|
|
1027
|
+
writePaths: string[];
|
|
1028
|
+
autoModifiedFiles: string[];
|
|
1029
|
+
testSurfaces: string[];
|
|
1030
|
+
}): Promise<void>;
|
|
1031
|
+
/**
|
|
1032
|
+
* CP-12 W2.b (§3.C.2) — the TEAM analogue of the single-impl round-0 spawn.
|
|
1033
|
+
* Routes by the OUTER envelope's `task_id` (the payload has none): resolves
|
|
1034
|
+
* `{ brief, agent, write scope }` from `activeBriefByTask`, runs the EXISTING
|
|
1035
|
+
* `runImplementorRound` machinery against the track's child `task_id` + the
|
|
1036
|
+
* scheduler-minted round-0 `gateRunId`, and validates the captured diff against
|
|
1037
|
+
* the track's declared write scope (§3.C.2b option a) before submit.
|
|
1038
|
+
*
|
|
1039
|
+
* - NOT-yet-registered + a `/team` ran this session → BUFFER (early race,
|
|
1040
|
+
* drained by `registerTeamTaskBrief`).
|
|
1041
|
+
* - NOT-yet-registered + NO `/team` this session (post-restart) → FAIL LOUD
|
|
1042
|
+
* (§3.C.4) — never spawn an empty-brief implementor.
|
|
1043
|
+
*/
|
|
1044
|
+
private handleTeamGateDispatch;
|
|
1045
|
+
/**
|
|
1046
|
+
* INVARIANT enforcement (live dogfood "Reject and restart" race, 2026-06-11):
|
|
1047
|
+
* before a NEW round-0 spawn, drop any STALE shadow still mapped for this task
|
|
1048
|
+
* so `ensureShadow` re-creates a FRESH one. A restart reuses the SAME taskId;
|
|
1049
|
+
* the rejected attempt's shadow may still be mapped here. Tear it down (its
|
|
1050
|
+
* uuid-suffixed dir is owned solely by that instance, so this never affects a
|
|
1051
|
+
* sibling) and clear the task's accumulated binding user notes — a restart is a
|
|
1052
|
+
* fresh round-0 of the ORIGINAL task, so the rejected attempt's revise-context
|
|
1053
|
+
* notes must not bleed into the restart. Clearing notes HERE (the dispatch side)
|
|
1054
|
+
* AND in the matching-instance `discardShadow` path makes BOTH race orders
|
|
1055
|
+
* converge on "restart starts clean." No-op when nothing is mapped (the normal
|
|
1056
|
+
* start_task → first round-0, where the shadow does not yet exist).
|
|
1057
|
+
*/
|
|
1058
|
+
private replaceShadowForRound0;
|
|
1059
|
+
private replaceShadowForRound0Locked;
|
|
1060
|
+
/**
|
|
1061
|
+
* CP-14 P1 — the team execution model this session SIGNALS on
|
|
1062
|
+
* `createTaskGroup` (`executionModel`). Always `worktree_merge` since the
|
|
1063
|
+
* §10 retirement (2026-07-03): overlapping-track dispatch with the
|
|
1064
|
+
* merge-train + the server contract gate as the authority.
|
|
1065
|
+
*/
|
|
1066
|
+
teamExecutionModel(): TeamExecution;
|
|
1067
|
+
/**
|
|
1068
|
+
* CP-14 Wave A — the ONE pinned group base SHA (memoized): `git rev-parse HEAD`
|
|
1069
|
+
* in the workspace, captured at the FIRST track of the group, BEFORE any track
|
|
1070
|
+
* shadow/branch is created, so every track roots at the same base (Stage-1
|
|
1071
|
+
* MED-3). Concurrent callers share one rev-parse via the in-flight promise.
|
|
1072
|
+
* Throws if HEAD can't be resolved (a non-git workspace is ineligible upstream).
|
|
1073
|
+
*/
|
|
1074
|
+
private ensureGroupBaseSha;
|
|
1075
|
+
/**
|
|
1076
|
+
* CP-1.f execute-first (§4) — ensure the per-task SHADOW exists, creating it
|
|
1077
|
+
* BEFORE the round-0 spawn. Idempotent: round-0 + an early GATE_DISPATCH
|
|
1078
|
+
* re-deliver, and revise rounds, all reuse the SAME shadow (the diff is
|
|
1079
|
+
* cumulative shadow-vs-real across revise rounds, D6). Fail-closed (#SR-2): a
|
|
1080
|
+
* create failure throws so the caller never spawns against a half-built shadow.
|
|
1081
|
+
*/
|
|
1082
|
+
private ensureShadow;
|
|
1083
|
+
private ensureShadowLocked;
|
|
1084
|
+
/** The shadow for `taskId` (introspection / the terminal promote/discard handlers). */
|
|
1085
|
+
shadowForTask(taskId: string): WorkspaceShadow | undefined;
|
|
1086
|
+
/**
|
|
1087
|
+
* Spawn the EXECUTE-mode implementor for one round in the task's SHADOW,
|
|
1088
|
+
* capture the shadow-vs-real diff, and submit it as the execute-first
|
|
1089
|
+
* artifact (§E1). Factored so round-0 (start_task) + round-N+1 (revise) share
|
|
1090
|
+
* the path; the shadow is reused across revise rounds (D6). The submit return
|
|
1091
|
+
* carries `workflowState` — on `"halted"`/`"idle"` (empty diff / born-halt)
|
|
1092
|
+
* the halt is surfaced SYNCHRONOUSLY (#EF-1), not via the ≤5min sweeper.
|
|
1093
|
+
*
|
|
1094
|
+
* Double-guarded: a gate already in-flight OR already submitted is a no-op.
|
|
1095
|
+
*/
|
|
1096
|
+
runImplementorRound(args: {
|
|
1097
|
+
taskId: string;
|
|
1098
|
+
gateId: string;
|
|
1099
|
+
roundNumber: number;
|
|
1100
|
+
brief: string;
|
|
1101
|
+
agent: AgentKind;
|
|
1102
|
+
sessionKey: string;
|
|
1103
|
+
mode?: ImplementorMode;
|
|
1104
|
+
/**
|
|
1105
|
+
* CP-12 W2.b (§3.C.2b option a / H3) — TEAM only. After `captureDiff` and
|
|
1106
|
+
* BEFORE submit, validate every changed file against the track's declared
|
|
1107
|
+
* write scope (`writePaths ∪ autoModifiedFiles ∪ testSurfaces`); an
|
|
1108
|
+
* out-of-scope write FAILS the round LOUDLY (no submit, no promote).
|
|
1109
|
+
* `testSurfaces` is the track's own-module test files (#590), disjointness-
|
|
1110
|
+
* validated by the engine. Absent for the single-impl path (no per-track
|
|
1111
|
+
* confinement).
|
|
1112
|
+
*/
|
|
1113
|
+
validateDiffScope?: {
|
|
1114
|
+
writePaths: string[];
|
|
1115
|
+
autoModifiedFiles: string[];
|
|
1116
|
+
testSurfaces: string[];
|
|
1117
|
+
trackIndex: number;
|
|
1118
|
+
};
|
|
1119
|
+
}): Promise<void>;
|
|
1120
|
+
/**
|
|
1121
|
+
* Extract the agent's informational stdout summary (audit-only; reviewers
|
|
1122
|
+
* never see it — they review the captured diff). Best-effort: a failure to
|
|
1123
|
+
* read the summary is NON-fatal (the diff is the source of truth). For Codex
|
|
1124
|
+
* the clean reply is the `--output-last-message` temp file; for Gemini it is
|
|
1125
|
+
* the JSON envelope's `.response`; Claude `--print` is clean stdout.
|
|
1126
|
+
*/
|
|
1127
|
+
private extractAgentSummary;
|
|
1128
|
+
/**
|
|
1129
|
+
* [token counter — Phase 2 · P2-2] Best-effort per-round IMPLEMENTOR token count
|
|
1130
|
+
* (input+output). Mirrors `extractAgentSummary`'s capture split: Codex emits its
|
|
1131
|
+
* `--json` usage on stdout even though the SUMMARY is file-captured, so sum that
|
|
1132
|
+
* JSONL stream; Claude parses the `--output-format json` envelope's `usage`.
|
|
1133
|
+
* Gemini/Antigravity are out of scope (undefined). NEVER throws — a read/parse
|
|
1134
|
+
* failure yields `undefined` (segment simply omitted), never breaks the round.
|
|
1135
|
+
*/
|
|
1136
|
+
private extractImplementorTokens;
|
|
1137
|
+
/**
|
|
1138
|
+
* PHASE-589/469 W1+W2 (LOCK #589-E/F/H) — on a TEAM track local hard-fail (any
|
|
1139
|
+
* of the 4 closed reasons: `verification_failure` / `out_of_scope_write` /
|
|
1140
|
+
* `review_integrity_violation` / `promote_failure`), report the engine marker
|
|
1141
|
+
* + render the desktop's OWN track from the AUTHORITATIVE returned state:
|
|
1142
|
+
* (a) report the engine marker (`submitTrackVerificationOutcome`). For a
|
|
1143
|
+
* REVISABLE reason (`verification_failure` / `out_of_scope_write`) thread
|
|
1144
|
+
* the round-N `gateId` so the engine can mint the round-N+1 revise round
|
|
1145
|
+
* (cap permitting). **BEST-EFFORT** — on any failure log a warn and STILL
|
|
1146
|
+
* proceed (the engine lease sweeper backstops); a marker failure must
|
|
1147
|
+
* NEVER crash the loop;
|
|
1148
|
+
* (b) render the desktop's OWN track from the marker's RETURNED `trackState`
|
|
1149
|
+
* (authoritative, no optimistic divergence):
|
|
1150
|
+
* - W2 auto-revise (returned state NOT `Failed`, track stays InFlight)
|
|
1151
|
+
* → `onTeamTrackRevising` (render `Revising`); the engine emits
|
|
1152
|
+
* `REVISE_FEEDBACK` → `handleReviseFeedback` re-spawns the implementor;
|
|
1153
|
+
* - terminal-fail (returned `Failed`: a TERMINAL reason, OR a revisable
|
|
1154
|
+
* reason at cap-exhaust) → `onTeamTrackFailed` (render `Failed` +
|
|
1155
|
+
* group-terminal from LOCAL state);
|
|
1156
|
+
* - a thrown marker (state unknown) → `onTeamTrackFailed` (the honest
|
|
1157
|
+
* "round failed, couldn't drive a revise" render; the lease sweeper
|
|
1158
|
+
* eventually terminal-fails the engine track to match).
|
|
1159
|
+
* TEAM-only — caller guards on `validateDiffScope`; single-impl never calls
|
|
1160
|
+
* this. The `taskGroupId` is resolved from the per-task team registry
|
|
1161
|
+
* (`activeBriefByTask`); if it is absent (a non-team task) this is a no-op.
|
|
1162
|
+
* W2 scope: NO revert, NO engine halt-emit (those are W3).
|
|
1163
|
+
*
|
|
1164
|
+
* `gateId` is the track's CURRENT round-N reviewer_quorum gate; REQUIRED for a
|
|
1165
|
+
* revisable reason (the engine rejects a revisable marker without it). The
|
|
1166
|
+
* TERMINAL-reason callers omit it.
|
|
1167
|
+
*/
|
|
1168
|
+
private reportTeamTrackFailure;
|
|
1169
|
+
/**
|
|
1170
|
+
* No-op UX (#1) — report the BENIGN no-change terminal for a TEAM track that
|
|
1171
|
+
* produced an empty diff. Sends the engine the `no_change` outcome (→ the engine
|
|
1172
|
+
* marks the track `TrackState::Passed` via `pass_track_and_schedule`; the
|
|
1173
|
+
* scheduler fires the MergeGate on all-terminal-no-failure — NEVER the all-or-
|
|
1174
|
+
* nothing revert) and flips the desktop's OWN track render out of `InFlight` to
|
|
1175
|
+
* `Passed` via the no-`reason` `onTeamTrackUserResolved` chokepoint (track flip
|
|
1176
|
+
* ONLY; the GROUP terminal stays engine-authoritative). Best-effort: a marker
|
|
1177
|
+
* failure is backstopped by the engine lease sweeper. The PARALLEL of
|
|
1178
|
+
* `reportTeamTrackFailure`, but for the benign no-change outcome (no `gateId` —
|
|
1179
|
+
* `no_change` is non-revisable). TEAM-only.
|
|
1180
|
+
*/
|
|
1181
|
+
private reportTeamTrackNoChange;
|
|
1182
|
+
/**
|
|
1183
|
+
* #606 (FACE A) — flip the desktop's OWN team track render-terminal from a
|
|
1184
|
+
* per-track USER decision. Called by the shell's `onTerminalDecision` (the
|
|
1185
|
+
* shared TTY + non-TTY gate-resolution chokepoint) AFTER a successful
|
|
1186
|
+
* `applyUserDecision`, with the task's id + the engine's returned `postAction`.
|
|
1187
|
+
*
|
|
1188
|
+
* Resolves the track via the EXISTING `(taskId → taskGroupId, trackIndex)`
|
|
1189
|
+
* correlation (`activeBriefByTask`, registered at TrackAssigned), maps the
|
|
1190
|
+
* `postAction` to the render-terminal state via the locked
|
|
1191
|
+
* `postActionTeamTrackState` (which MIRRORS the engine's
|
|
1192
|
+
* `fire_deferred_track_terminal` disposition), and fires
|
|
1193
|
+
* `onTeamTrackUserResolved` so the shell emits a `team_track_terminal`
|
|
1194
|
+
* (Passed/Failed) through the SAME chokepoint the W1/W2 callbacks use.
|
|
1195
|
+
*
|
|
1196
|
+
* NO-OP (single-impl byte-identical) when:
|
|
1197
|
+
* - the task is not in `activeBriefByTask` (single-impl / unknown task), or
|
|
1198
|
+
* - its `taskGroupId` is empty (single-impl registration), or
|
|
1199
|
+
* - the `postAction` is non-terminal (`open_new_round` / `restart_proposal` /
|
|
1200
|
+
* `continuation_switch_*` → a new gate follows; the track keeps working).
|
|
1201
|
+
*
|
|
1202
|
+
* Best-effort: a throwing listener can NEVER break the decision path (the
|
|
1203
|
+
* desktop never fails a user's decision on a render-flip emit error; the engine
|
|
1204
|
+
* `TaskGroupHalted` convergence is the authoritative group resolution).
|
|
1205
|
+
*/
|
|
1206
|
+
reportTeamTrackUserResolved(taskId: string, postAction: PostDecisionAction): void;
|
|
1207
|
+
/**
|
|
1208
|
+
* CP-1.f execute-first — discard the task's shadow (a non-TaskAuthorized
|
|
1209
|
+
* terminal). Best-effort: a teardown failure is logged + leaves the durable
|
|
1210
|
+
* marker for the next-startup GC (#GC-2 lazy cleanup). Forgets the in-memory
|
|
1211
|
+
* handle so a later promote can't run against a torn-down shadow.
|
|
1212
|
+
*
|
|
1213
|
+
* INSTANCE-PINNED (live dogfood race 2026-06-11, option-4 "Reject and restart"):
|
|
1214
|
+
* a RESTART reuses the SAME taskId. The decision-time discard fires
|
|
1215
|
+
* asynchronously (after the `applyUserDecision` SDK await) while the engine
|
|
1216
|
+
* ALSO emits a fresh round-0 GATE_DISPATCH for the restart — which replaces
|
|
1217
|
+
* the task's map entry with the NEW round's shadow. An instance-BLIND
|
|
1218
|
+
* `shadowsByTask.get(taskId)` here would then discard the NEW shadow out from
|
|
1219
|
+
* under the starting implementor ("Implementor round 0 failed — could not be
|
|
1220
|
+
* started"). So the caller captures the shadow INSTANCE at decision time (BEFORE
|
|
1221
|
+
* the SDK await, before the restart dispatch can win the race) and threads it as
|
|
1222
|
+
* `expected`. When `expected` is given:
|
|
1223
|
+
* • it MATCHES the currently-mapped shadow → discard it as before (this round
|
|
1224
|
+
* is genuinely terminal); clear the notes (terminal, no more revise rounds).
|
|
1225
|
+
* • it does NOT match (a NEWER shadow replaced it — the restart dispatch won)
|
|
1226
|
+
* → the captured instance is ORPHANED: tear down ITS directory directly
|
|
1227
|
+
* (each shadow owns a uuid-suffixed dir, so this never touches the live
|
|
1228
|
+
* sibling), but LEAVE the map + the new shadow + the notes untouched (the
|
|
1229
|
+
* restart's fresh-shadow dispatch already cleared + now owns the notes), and
|
|
1230
|
+
* do NOT print the "discarded — tree unchanged" milestone (a debug log only).
|
|
1231
|
+
*
|
|
1232
|
+
* When `expected` is omitted (the internal born-halt / round-failure callers at
|
|
1233
|
+
* runImplementorRound) the behavior is exactly as before — discard whatever is
|
|
1234
|
+
* currently mapped for the task.
|
|
1235
|
+
*/
|
|
1236
|
+
discardShadow(taskId: string, expected?: WorkspaceShadow): Promise<void>;
|
|
1237
|
+
private discardShadowLocked;
|
|
1238
|
+
/**
|
|
1239
|
+
* CP-1.f execute-first (#PR-1/#PR-3) — promote the task's shadow onto the real
|
|
1240
|
+
* tree. Driven ONLY by the `TaskAuthorized` Class-B edge (covers all 3
|
|
1241
|
+
* authorize paths). Idempotent (#PR-4): a second call (packet + recovery
|
|
1242
|
+
* double-fire) is a safe no-op via the durable `promoted` marker. Fail-closed
|
|
1243
|
+
* (#SR-2): conflicts/errors are surfaced to the user, never a silent
|
|
1244
|
+
* half-applied tree.
|
|
1245
|
+
*/
|
|
1246
|
+
promoteShadow(taskId: string): Promise<void>;
|
|
1247
|
+
/**
|
|
1248
|
+
* CP-12 W2.b (§3.C.2b (d) / H3-4) — promote-quiescence BARRIER for `taskId`.
|
|
1249
|
+
* Enqueues an EMPTY critical section behind any in-flight shadow lifecycle op
|
|
1250
|
+
* (notably `promoteShadow`) for this task on the SAME per-task
|
|
1251
|
+
* `runExclusiveShadowOp` mutex, so it resolves only once that task's shadow-op
|
|
1252
|
+
* chain has fully drained. `runMergeGate` (on `LocalExecutorImpl`) cannot call
|
|
1253
|
+
* the private `runExclusiveShadowOp` directly, so this PUBLIC delegator is the
|
|
1254
|
+
* surface the team LE dep `awaitTaskShadowQuiescent` wires to — mirroring how
|
|
1255
|
+
* `promoteShadow` (above) is the public delegator over the same private mutex.
|
|
1256
|
+
* It awaits any in-flight promote for the task before MergeGate's integration
|
|
1257
|
+
* check reads the real tree, with NO new locking primitive.
|
|
1258
|
+
*/
|
|
1259
|
+
awaitTaskShadowQuiescent(taskId: string): Promise<void>;
|
|
1260
|
+
private promoteShadowLocked;
|
|
1261
|
+
private promoteShadowLockedInner;
|
|
1262
|
+
/**
|
|
1263
|
+
* [live task status timer §3] The promote TAIL — (re)opens the busy window with
|
|
1264
|
+
* `promoting`, applies the reviewed `diff`, then emits either the `promoted`
|
|
1265
|
+
* milestone (success) or returns silently (marker-flip no-op / apply-conflict).
|
|
1266
|
+
* Extracted from `promoteShadowLockedInner` so that method's `try/finally` wraps
|
|
1267
|
+
* EXACTLY the post-`promoting` strand exits — never the pre-`promoting`
|
|
1268
|
+
* no-op/re-delivery/discard returns (which must stay silent). All emits carry the
|
|
1269
|
+
* caller-resolved `originEpoch`.
|
|
1270
|
+
*/
|
|
1271
|
+
private promoteApplyLocked;
|
|
1272
|
+
/** #SR-2 — surface a halt/fail-closed message to the user (or log if unwired). */
|
|
1273
|
+
private surfaceHalt;
|
|
1274
|
+
/**
|
|
1275
|
+
* PHASE-598 — the thin wiring that supplies the real fs/shell deps to the pure
|
|
1276
|
+
* `runDeclaredTestSurfaces` executor (unit tests inject mocks into the free
|
|
1277
|
+
* function; integration tests stub the executor's `node` runs via
|
|
1278
|
+
* `QuorumLoopDeps.runShell`). `readdir` is wrapped to `{ withFileTypes: true }`
|
|
1279
|
+
* so the executor sees `Dirent`s (the directory-expansion symlink discriminate).
|
|
1280
|
+
*/
|
|
1281
|
+
private runDeclaredTestSurfaces;
|
|
1282
|
+
/**
|
|
1283
|
+
* [live task status timer §3] Origin epoch for an emit context. Resolves from
|
|
1284
|
+
* the ORIGINATING task's id (never `this.taskEpoch` read at emit time — an async
|
|
1285
|
+
* re-delivery may run after a LATER `startTask`). Callers pass their in-scope
|
|
1286
|
+
* taskId (or the `taskByGateId[gateId]` bridge). `?? this.taskEpoch` defends a
|
|
1287
|
+
* post-restart entry (a bounded residual, per design).
|
|
1288
|
+
*/
|
|
1289
|
+
private originEpochFor;
|
|
1290
|
+
/**
|
|
1291
|
+
* CP-1.f live progress surfacing — emit ONE desktop-local progress phase.
|
|
1292
|
+
* Failure-safe: a throwing listener is caught + logged, NEVER propagated into
|
|
1293
|
+
* the loop (a render bug must not break the quorum). Synchronous — never
|
|
1294
|
+
* awaited, never blocks the loop. No-op when unwired (tests / non-loop).
|
|
1295
|
+
*/
|
|
1296
|
+
private emitProgress;
|
|
1297
|
+
/**
|
|
1298
|
+
* [token counter — Phase 2 · P2-2] Strictly-ADDITIVE per-task token accrual.
|
|
1299
|
+
* `n` is clamped by `finiteToken` (P2-0) so a malformed CLI field contributes 0,
|
|
1300
|
+
* never poisons the accumulator (→ `NaN`/regression). Additive-only means the
|
|
1301
|
+
* reducer's `Math.max` tick-refresh carry is idempotent (a re-delivery never
|
|
1302
|
+
* decreases). No-op for a null taskId or a 0 clamp.
|
|
1303
|
+
*/
|
|
1304
|
+
private addTaskTokens;
|
|
1305
|
+
/**
|
|
1306
|
+
* [token counter — Phase 2 · P2-2] The per-task CUMULATIVE total for stamping.
|
|
1307
|
+
* Null-tolerant (MED-1: `seat_update`/`verdicts_progress` resolve taskId via
|
|
1308
|
+
* `taskByGateId[gateId]`, `undefined` after a restart). The `|| undefined`
|
|
1309
|
+
* (falsy-collapse, NOT `??`) is load-bearing: a `0`/absent entry OMITS the
|
|
1310
|
+
* ` · ↓ <tokens>` segment rather than rendering `↓ 0`. Mirrors `originEpochFor`.
|
|
1311
|
+
*/
|
|
1312
|
+
private tokensFor;
|
|
1313
|
+
/**
|
|
1314
|
+
* [token counter — Phase 2 · P2-3] Emit a progress phase with BOTH the entry-
|
|
1315
|
+
* cached `originEpoch` AND the LIVE token total injected at CALL time. CRITICAL
|
|
1316
|
+
* (Codex-H3): `tokensFor(taskId)` is read HERE, per emit — NOT cached at function
|
|
1317
|
+
* entry — because `addTaskTokens` (implementor exit, reviewer success) runs BEFORE
|
|
1318
|
+
* the same function's later `diff_captured`/`submitting_diff`/`verdicts_progress`
|
|
1319
|
+
* emits; caching at entry would stamp the OLD total and the last reviewer's tokens
|
|
1320
|
+
* would vanish when `waiting_user` closes the window. Only `originEpoch` is entry-
|
|
1321
|
+
* cached (correct — the epoch is fixed for the task); tokens are read fresh.
|
|
1322
|
+
*/
|
|
1323
|
+
private emitProgressForTask;
|
|
1324
|
+
/**
|
|
1325
|
+
* CP-1.f live progress surfacing — cheap, best-effort, time-bounded count of
|
|
1326
|
+
* the files changed so far in the shadow for the per-tick status line. Bounded
|
|
1327
|
+
* to the tracked set; SKIPPED (returns undefined) when the workspace is large
|
|
1328
|
+
* (`TICK_SCAN_MAX_TRACKED`) or the scan exceeds `TICK_SCAN_TIMEOUT_MS`. NEVER
|
|
1329
|
+
* throws + NEVER blocks the loop (the tick fire-and-forgets it). Returns ONLY
|
|
1330
|
+
* a count — never any file content or path payload.
|
|
1331
|
+
*/
|
|
1332
|
+
private tickFileChangeCount;
|
|
1333
|
+
/**
|
|
1334
|
+
* Submit the execute-first artifact with NotFound retry-with-backoff, and
|
|
1335
|
+
* RETURN the engine's `{ gateId, workflowState }` so the caller can read the
|
|
1336
|
+
* SYNCHRONOUS born-halt label (#EF-1 / agy M4). On exhausted retries the last
|
|
1337
|
+
* error propagates (the caller surfaces it fail-closed).
|
|
1338
|
+
*/
|
|
1339
|
+
private submitImplementorOutputWithRetry;
|
|
1340
|
+
private handleReviewerDispatch;
|
|
1341
|
+
/**
|
|
1342
|
+
* §3.C.21 — spawn + run ONE reviewer seat, then submit its verdict.
|
|
1343
|
+
* Double-guarded (§3.C.21b LOCK): a seat already running-in-memory OR
|
|
1344
|
+
* already verdict-submitted is a no-op, so a live packet + a recovery
|
|
1345
|
+
* re-spawn never double-run the same seat. On a reviewer error/timeout, a
|
|
1346
|
+
* synthesized ESCALATE verdict is submitted (#C1F-8) so the gate never hangs.
|
|
1347
|
+
*/
|
|
1348
|
+
spawnOneSeat(args: {
|
|
1349
|
+
gateId: string;
|
|
1350
|
+
seatId: number;
|
|
1351
|
+
role: ReviewerRole;
|
|
1352
|
+
agentKind: ReviewerAgentKind;
|
|
1353
|
+
}): Promise<void>;
|
|
1354
|
+
/**
|
|
1355
|
+
* Audited-path fix (Fix 9) — run `reviewerRegistry.evaluate` under the
|
|
1356
|
+
* DEFENSE-IN-DEPTH per-seat wall-clock guard. Returns the verdict if `evaluate`
|
|
1357
|
+
* settles within `SEAT_EVALUATE_TIMEOUT_MS`; throws `SeatTimeoutError` if the
|
|
1358
|
+
* guard fires first. The abandoned `evaluate` promise is given a no-op `.catch`
|
|
1359
|
+
* so a late rejection (the subprocess `runReviewer` self-kill at 5 min throws a
|
|
1360
|
+
* `timeout` ReviewerError) does NOT surface as an unhandled rejection. The guard
|
|
1361
|
+
* never touches any process — `runReviewer` owns + kills its own child.
|
|
1362
|
+
*/
|
|
1363
|
+
private evaluateSeatWithTimeout;
|
|
1364
|
+
/**
|
|
1365
|
+
* CP-7 §8 (Stage-1-resolved) — engage the trusted-execution substrate +
|
|
1366
|
+
* broker for ONE reviewer seat (mirrors the implementor's per-spawn engage).
|
|
1367
|
+
* Returns `{ handle?, teardown }`:
|
|
1368
|
+
* - `handle` PRESENT → sandboxed; the seat's model call routes through the
|
|
1369
|
+
* broker (no ambient vendor creds reach the reviewer subprocess).
|
|
1370
|
+
* - `handle` ABSENT → the seat runs the LEGACY unsandboxed path.
|
|
1371
|
+
* - THROWS `ReviewerErrorClass` (spawn_failed) → FAIL-CLOSED: a Trusted-
|
|
1372
|
+
* agent broker-engage failure. The caller turns this into a clean
|
|
1373
|
+
* synthesized ESCALATE — it does NOT fall back to the ambient-cred legacy
|
|
1374
|
+
* path (that defeats the moat).
|
|
1375
|
+
*
|
|
1376
|
+
* TRI-STATE (decided here, mirroring the implementor LE):
|
|
1377
|
+
* 1. No engager wired OR `CODEVIBE_SANDBOX_REVIEWERS` not opted in (DEFAULT
|
|
1378
|
+
* OFF; opt in with `=1`/`=true`) → LEGACY (byte-identical non-regression).
|
|
1379
|
+
* No badge.
|
|
1380
|
+
* 1b. No resolvable taskId (cannot scope a per-task broker) → LEGACY + a
|
|
1381
|
+
* reduced-trust badge (Stage-2 r1 Codex LOW — a trust-downgrade must be
|
|
1382
|
+
* VISIBLE, never a silent ambient-cred fallback; see
|
|
1383
|
+
* `surfaceReviewerReducedTrust` at the no-taskId branch below).
|
|
1384
|
+
* 2. GEMINI (non-Trusted, §6 Q4) → LEGACY + reduced-trust badge. We do NOT
|
|
1385
|
+
* call the engager for Gemini (it would throw the non-Trusted error);
|
|
1386
|
+
* pre-checking the agent kind avoids string-matching that throw and keeps
|
|
1387
|
+
* a Gemini seat RUNNING (just unsandboxed + badged), never escalated.
|
|
1388
|
+
* 3. Engager returns `reduced_trust` (no runtime / coarse host) → LEGACY +
|
|
1389
|
+
* reduced-trust badge.
|
|
1390
|
+
* 4. Engager returns `substrate` → SANDBOXED. A `reducedTrust` (A5 coarse)
|
|
1391
|
+
* result still runs sandboxed but surfaces the coarse badge.
|
|
1392
|
+
* 5. Engager THROWS for a Trusted agent (broker-start failure, etc.) →
|
|
1393
|
+
* FAIL-CLOSED (re-thrown as ReviewerErrorClass spawn_failed).
|
|
1394
|
+
*
|
|
1395
|
+
* `teardown()` is ALWAYS safe to call (a no-op on the legacy path); the caller
|
|
1396
|
+
* invokes it in a `finally` so a sandbox never leaks.
|
|
1397
|
+
*/
|
|
1398
|
+
private engageSeatSubstrate;
|
|
1399
|
+
/**
|
|
1400
|
+
* CP-7 §8 (Stage-1-resolved) — surface the PROMINENT reduced-trust badge for a
|
|
1401
|
+
* reviewer seat (mirrors the implementor's badge, scoped to the loop's
|
|
1402
|
+
* available NOTIFICATION channel). The moat being OFF/coarse for a reviewer is
|
|
1403
|
+
* VISIBLE + auditable, never silent: a user-visible NOTIFICATION (via
|
|
1404
|
+
* `surfaceHalt`, the loop's NOTIFICATION surface) + a WARN log. Best-effort —
|
|
1405
|
+
* unlike the implementor's `tier:'none'` path this is NOT delivery-gated,
|
|
1406
|
+
* because a reviewer reduced-trust badge does not change whether the seat
|
|
1407
|
+
* RUNS (it always runs; the badge only informs the user).
|
|
1408
|
+
*/
|
|
1409
|
+
private surfaceReviewerReducedTrust;
|
|
1410
|
+
/**
|
|
1411
|
+
* Wave B §2.1 confinement TRI-STATE for a merge-gate model/reviewer invocation
|
|
1412
|
+
* (LOCK #C14-MOAT). Path A: the CP-7 substrate/broker (opted in + a Trusted agent
|
|
1413
|
+
* + engage succeeds → a handle; no ambient vendor cred in the child). Path B: a
|
|
1414
|
+
* trusted-container boundary (legacy spawn confined by the OUTER container — the
|
|
1415
|
+
* dogfood/E2E path). Path C: neither → NOT confined. A path-A engage that
|
|
1416
|
+
* throws/reduced-trusts falls through to B/C (both safe: B confined, C refuses),
|
|
1417
|
+
* so a broker hiccup never silently runs unconfined.
|
|
1418
|
+
*/
|
|
1419
|
+
private engageWaveBConfinement;
|
|
1420
|
+
/**
|
|
1421
|
+
* Wave B §2.1 — build the CONFINED class-2 model caller for a merge gate, or
|
|
1422
|
+
* `null` on path C (no confinement → the gate runs without a resolver; conflicts
|
|
1423
|
+
* halt to the human gate; no unconfined LLM resolver ever runs — LOCK #C14-MOAT).
|
|
1424
|
+
* The model call reuses the proven reviewer subprocess (`runReviewer`, `claude
|
|
1425
|
+
* --print`, read-only allowlist), routed through the substrate broker on path A
|
|
1426
|
+
* or the trusted-container legacy spawn on path B.
|
|
1427
|
+
*/
|
|
1428
|
+
makeConflictModelCaller(args: {
|
|
1429
|
+
ownerTaskId: string;
|
|
1430
|
+
}): Promise<{
|
|
1431
|
+
caller: ConflictModelCaller;
|
|
1432
|
+
teardown: () => Promise<void>;
|
|
1433
|
+
} | null>;
|
|
1434
|
+
/**
|
|
1435
|
+
* CP-14 WB2 §2.1/§2.2/§2.3 — build the AGENTIC class-2 resolver, or `null` on
|
|
1436
|
+
* path C (no confinement). When wired, a slot's class-2 conflicts are resolved
|
|
1437
|
+
* by re-dispatching the INCOMING track's implementor (read+write) inside a
|
|
1438
|
+
* disposable repo, then fd-safe selective-importing ONLY the conflicted files.
|
|
1439
|
+
* SECURITY-CRITICAL — fail-closed on BOTH layers:
|
|
1440
|
+
*
|
|
1441
|
+
* 1. CONSTRUCTION-time (mirrors `makeConflictModelCaller` returning null): probe
|
|
1442
|
+
* `engageWaveBConfinement` ONCE against the loop workdir; path C → return
|
|
1443
|
+
* `null` → `resolveAgentic` is NOT set on the context → a fresh class-2
|
|
1444
|
+
* conflict takes the legacy `caller` path (which on path C is the
|
|
1445
|
+
* CONFINEMENT-UNAVAILABLE caller → human-gate). Byte-identical to today.
|
|
1446
|
+
* 2. DISPATCH-time (Stage-2 Codex HIGH — confinement BOUND TO THE ACTUAL SPAWN):
|
|
1447
|
+
* `dispatchImplementor` spawns with `requireConfined: true`, so
|
|
1448
|
+
* `LocalExecutor.spawnImplementor` → `engageSubstrateForSpawn` THROWS
|
|
1449
|
+
* `SpawnConfinementUnavailable` on EVERY non-substrate-engaged outcome (no
|
|
1450
|
+
* engager / pre-task / `reduced_trust`) instead of returning the legacy
|
|
1451
|
+
* unsandboxed args. The closure maps that throw → `AgenticDispatchUnconfinedError`
|
|
1452
|
+
* → `resolveAgenticSlot` → a clean `human_gate`. NEVER an unconfined spawn.
|
|
1453
|
+
*
|
|
1454
|
+
* The confining substrate is established by `spawnImplementor` itself
|
|
1455
|
+
* (`engageSubstrateForSpawn`, keyed on the real `taskId` + `workingDir =
|
|
1456
|
+
* realpath(disposableRepo)`), which mounts ONLY the disposable repo. There is NO
|
|
1457
|
+
* separate per-resolve probe (the prior "prove then tear down then spawn" design
|
|
1458
|
+
* proved confinement on a handle the LE discarded — Codex correctly flagged that the
|
|
1459
|
+
* separate engage could still yield `reduced_trust`; the `requireConfined` spawn IS
|
|
1460
|
+
* now the sole, correctly-bound dispatch-time gate). The construction probe (layer 1)
|
|
1461
|
+
* is only the coarse wire-vs-legacy decision. On a path-B host with no FS-scoping
|
|
1462
|
+
* substrate (the dogfood — bwrap can't namespace a nested container) WB2 fail-closes
|
|
1463
|
+
* here; a successful resolve requires a real path-A substrate (production).
|
|
1464
|
+
*/
|
|
1465
|
+
makeAgenticResolver(args: {
|
|
1466
|
+
ownerTaskId: string;
|
|
1467
|
+
}): Promise<{
|
|
1468
|
+
resolveAgentic: NonNullable<ConflictResolverContext['resolveAgentic']>;
|
|
1469
|
+
teardown: () => Promise<void>;
|
|
1470
|
+
} | null>;
|
|
1471
|
+
/**
|
|
1472
|
+
* Wave B §2.3 (LOCK #C14-FINAL-TIER2 / #C14-MOAT) — dispatch the mandatory
|
|
1473
|
+
* final-tip Tier-2 reviewer over the integration diff (base → tip), CONFINED via
|
|
1474
|
+
* the §2.1 tri-state. Returns `confined: false` on path C (the gate fails closed,
|
|
1475
|
+
* NO-PROMOTE). A non-APPROVE verdict OR a reviewer error → `reject` (fail-safe: an
|
|
1476
|
+
* LLM byte never promotes on an unclear Tier-2). Reuses the per-gate reviewer
|
|
1477
|
+
* registry + the read-only reviewer allowlist over the resolved review surface.
|
|
1478
|
+
*/
|
|
1479
|
+
dispatchFinalTier2Review(args: {
|
|
1480
|
+
ownerTaskId: string;
|
|
1481
|
+
repoDir: string;
|
|
1482
|
+
baseSha: string;
|
|
1483
|
+
integrationTip: string;
|
|
1484
|
+
/** CP-14 WB2 §2.4 — the class-2 resolver agent(s) this gate (for independent selection). */
|
|
1485
|
+
resolverAgents?: ReadonlySet<string>;
|
|
1486
|
+
/** Forward-compat desktop override (no surface today; honored only when host-detected). */
|
|
1487
|
+
tier2ReviewerOverride?: AgentKind | null;
|
|
1488
|
+
/**
|
|
1489
|
+
* CP-14 WB2 §2.6 (additive) — the per-conflict 3-way CONTENT + per-track INTENT
|
|
1490
|
+
* diffs to fold into the reviewer prompt. ALL OPTIONAL: absent ⇒ today's bare
|
|
1491
|
+
* base→tip diff (graceful degrade). The aggregate bundle is byte-bounded with
|
|
1492
|
+
* fail-closed overflow (over `WAVE_B_TIER2_MAX_BUNDLE_BYTES` → reject/NO-PROMOTE).
|
|
1493
|
+
*/
|
|
1494
|
+
conflictBundle?: readonly RenderedConflict[];
|
|
1495
|
+
trackIntentDiffs?: readonly TrackIntentDiff[];
|
|
1496
|
+
}): Promise<{
|
|
1497
|
+
verdict: 'pass' | 'reject';
|
|
1498
|
+
confined: boolean;
|
|
1499
|
+
reason?: string;
|
|
1500
|
+
}>;
|
|
1501
|
+
/**
|
|
1502
|
+
* Synthesize an ESCALATE verdict for a seat that could NOT produce a real
|
|
1503
|
+
* verdict, so the gate resolves instead of hanging (#C1F-8).
|
|
1504
|
+
*
|
|
1505
|
+
* CP-1.f sanitization invariant (LOAD-BEARING): the wire `reasoning` is a
|
|
1506
|
+
* CLEAN, classified one-liner from the fixed template table — NEVER raw
|
|
1507
|
+
* stderr / stdout / the raw `spawn_failed.reason`. The `synthesized ESCALATE
|
|
1508
|
+
* (#C1F-8): …` audit prefix lives ONLY in logs (the caller logs it), never
|
|
1509
|
+
* on the wire.
|
|
1510
|
+
*
|
|
1511
|
+
* @param reasoning the final clean wire `reasoning` (already a fixed template).
|
|
1512
|
+
*/
|
|
1513
|
+
private synthesizeEscalate;
|
|
1514
|
+
private submitVerdict;
|
|
1515
|
+
/**
|
|
1516
|
+
* On WS (re)connect/startup, poll for the seats assigned to this desktop for
|
|
1517
|
+
* any in_review gate of the active task and spawn any seat NEITHER already
|
|
1518
|
+
* running-in-memory NOR already verdict-submitted (double-guard). Without
|
|
1519
|
+
* this, a REVIEWER_DISPATCH dropped during a WS gap hangs the gate forever.
|
|
1520
|
+
*/
|
|
1521
|
+
recoverInReviewAssignments(): Promise<void>;
|
|
1522
|
+
/**
|
|
1523
|
+
* CP-1.f execute-first (#GC-1 / #PR-4) — on session-start/WS-resync, GC
|
|
1524
|
+
* orphaned shadow dirs (un-signalled auto-halts leave NO terminal edge, so
|
|
1525
|
+
* their temp dirs would leak) while NEVER deleting an authorized-but-pending
|
|
1526
|
+
* shadow. The active task's shadow is KEPT (it may still promote/discard); a
|
|
1527
|
+
* `promoted`-or-expired orphan is reaped.
|
|
1528
|
+
*
|
|
1529
|
+
* Promote-on-recovery (#PR-4) of a dropped `TaskAuthorized`: the durable
|
|
1530
|
+
* `pending`→`promoted` marker makes promote idempotent, so a re-delivered
|
|
1531
|
+
* `TaskAuthorized` packet after reconnect promotes safely (the live edge is
|
|
1532
|
+
* the recovery path). A `getTaskState` query for an eager promote of an
|
|
1533
|
+
* authorized-but-pending shadow whose packet was permanently lost is the only
|
|
1534
|
+
* residual — until then the GC reaper KEEPS such a shadow (it is never reaped
|
|
1535
|
+
* while `pending` and un-expired), so the approved code is never lost; a
|
|
1536
|
+
* re-delivered packet (or the next live edge) promotes it.
|
|
1537
|
+
*/
|
|
1538
|
+
recoverShadows(): Promise<void>;
|
|
1539
|
+
/**
|
|
1540
|
+
* §4.3 — true while a team recovery is seeding briefs (the startup barrier is
|
|
1541
|
+
* down). The Class B consume path checks this to BUFFER a live TEAM packet
|
|
1542
|
+
* instead of consuming it before PASS-1 seeding finishes. Single-impl /
|
|
1543
|
+
* non-team packets are unaffected (the barrier is team-scoped — the caller
|
|
1544
|
+
* only buffers TEAM packets).
|
|
1545
|
+
*/
|
|
1546
|
+
isRecoverySeeding(): boolean;
|
|
1547
|
+
/**
|
|
1548
|
+
* §4.3 (Stage-2 r1 Codex MED) — true if `(taskGroupId, trackIndex)` carries a
|
|
1549
|
+
* corrupt-brief TOMBSTONE. The LE consults this at the TOP of `launchTrack`
|
|
1550
|
+
* (before any register / spawn / lease-renewing progress emit) so a tombstoned
|
|
1551
|
+
* track is refused on the LIVE path as early as on the recovery path — never
|
|
1552
|
+
* spawning an empty-brief implementor or mutating registry state for it. The
|
|
1553
|
+
* engine sweeper times the un-launched track out (bounded).
|
|
1554
|
+
*
|
|
1555
|
+
* CP-12 W4 (#593 Stage-2 r2): keyed by `(taskGroupId, trackIndex)` so a corrupt
|
|
1556
|
+
* brief in group A never blocks a different group B's track with the same index.
|
|
1557
|
+
*/
|
|
1558
|
+
isTrackTombstoned(taskGroupId: string, trackIndex: number): boolean;
|
|
1559
|
+
/**
|
|
1560
|
+
* #592 PROVENANCE GUARD (live path) — true if `taskGroupId` is a team group this
|
|
1561
|
+
* desktop did NOT originate AND this desktop owns ≥1 group. Wired to the LE's
|
|
1562
|
+
* `isForeignTeamGroup` team-dep, consulted at the TOP of `launchTrack` (right
|
|
1563
|
+
* after the tombstone gate) so a foreign group's LIVE `TrackAssigned` is refused
|
|
1564
|
+
* before any spawn — the symmetric counterpart to the recovery-path filter in
|
|
1565
|
+
* `recoverTeamTracks` / `launchRecoveredTrack`. Returns FALSE when the owned-set
|
|
1566
|
+
* is empty (fresh process / post-crash) so own-group launch + crash-recovery are
|
|
1567
|
+
* unaffected. See `ownedGroupIds`.
|
|
1568
|
+
*/
|
|
1569
|
+
isForeignTeamGroup(taskGroupId: string): boolean;
|
|
1570
|
+
/**
|
|
1571
|
+
* §4.3 — buffer a live TEAM Class B packet while the recovery seed barrier is
|
|
1572
|
+
* down. Returns true if buffered (the caller must NOT consume it live). The
|
|
1573
|
+
* caller (`index.ts` onPacket / the LE consume path) only routes TEAM packets
|
|
1574
|
+
* here; single-impl / non-team packets bypass this entirely.
|
|
1575
|
+
*/
|
|
1576
|
+
bufferTeamPacketDuringSeeding(kind: 'trackAssigned' | 'gateDispatch', payload: unknown, envelopeTaskId?: string): boolean;
|
|
1577
|
+
/**
|
|
1578
|
+
* §4.3 — drain the buffered live TEAM packets through the normal live handlers
|
|
1579
|
+
* AFTER the recovery seed barrier lifts (`recoverySeedingDepth === 0`). Each
|
|
1580
|
+
* handler now reads the SEEDED brief (or hits the corrupt tombstone and refuses
|
|
1581
|
+
* to launch). `seenGateDispatchRunIds` dedups a packet both recovery and the
|
|
1582
|
+
* drained-live path would drive. Best-effort — a per-packet throw is logged.
|
|
1583
|
+
*/
|
|
1584
|
+
private drainBufferedTeamPackets;
|
|
1585
|
+
/**
|
|
1586
|
+
* §4.3 (Stage-2 r1 Codex HIGH) — run a recovery op UNDER the refcount barrier.
|
|
1587
|
+
* Increments `recoverySeedingDepth` SYNCHRONOUSLY (before `fn`'s first GSI
|
|
1588
|
+
* query, so a live TEAM packet arriving during that query is buffered, not
|
|
1589
|
+
* consumed with an empty brief), then decrements in `finally`; the buffered
|
|
1590
|
+
* queue drains ONCE when the count returns to 0 (so concurrent B.1+B.2 share
|
|
1591
|
+
* one barrier window and drain exactly once after BOTH finish). `fn` is
|
|
1592
|
+
* already fail-open internally; a throw still releases the barrier + drains.
|
|
1593
|
+
*/
|
|
1594
|
+
private withRecoveryBarrier;
|
|
1595
|
+
/**
|
|
1596
|
+
* §4.2 / §4.3 (Stage-2 r2 Stage-1 M1) — run the `/team` CREATE + brief-bind under
|
|
1597
|
+
* the SAME refcount seed barrier the recovery path uses. The parse-time briefs
|
|
1598
|
+
* can only be bound by `(taskGroupId, trackIndex)` AFTER `createTaskGroup`
|
|
1599
|
+
* returns the group id; the engine emits the per-track `TrackAssigned`/round-0
|
|
1600
|
+
* `GateDispatch` BEFORE that response, so when the Class B subscription is
|
|
1601
|
+
* already up a live packet can be delivered DURING the create round-trip. Running
|
|
1602
|
+
* the whole create+bind under this barrier makes such an in-window packet BUFFER
|
|
1603
|
+
* (`isRecoverySeeding()` true → `bufferTeamPacketDuringSeeding`) instead of
|
|
1604
|
+
* registering with an empty brief; the barrier releases + drains AFTER the briefs
|
|
1605
|
+
* are bound, so the buffered packet re-consumes with the correct brief. The
|
|
1606
|
+
* `seenGateDispatchRunIds` dedup makes the subsequent B.1 a no-op for an
|
|
1607
|
+
* already-launched track. Generic — returns the fn's value (the new group id).
|
|
1608
|
+
*/
|
|
1609
|
+
runTeamCreateUnderSeedBarrier<T>(fn: () => Promise<T>): Promise<T>;
|
|
1610
|
+
/**
|
|
1611
|
+
* §4.2 B.1 — arm the B.1 two-condition latch with the just-created
|
|
1612
|
+
* `taskGroupId`. If the subscription is already established, fire B.1
|
|
1613
|
+
* immediately (the common case where the subscription was up before
|
|
1614
|
+
* `createTaskGroup` returned).
|
|
1615
|
+
*/
|
|
1616
|
+
armB1Recovery(taskGroupId: string): void;
|
|
1617
|
+
/**
|
|
1618
|
+
* §4.2 — the subscription-establishment signal (`onSubscribed`). Sets the
|
|
1619
|
+
* `subscriptionReady` latch (firing a pending B.1) and ALWAYS runs B.2
|
|
1620
|
+
* discovery. Fires on the INITIAL connect (`isReconnect=false`, the restart
|
|
1621
|
+
* case B.2 exists for) AND a reconnect (`isReconnect=true`). B.1 and B.2 each
|
|
1622
|
+
* take the refcount barrier (`withRecoveryBarrier`) which is incremented
|
|
1623
|
+
* SYNCHRONOUSLY here (before this returns), so any live TEAM packet processed
|
|
1624
|
+
* after this point buffers until BOTH recovery ops finish seeding.
|
|
1625
|
+
*/
|
|
1626
|
+
onSubscriptionReady(isReconnect: boolean): void;
|
|
1627
|
+
/**
|
|
1628
|
+
* §4.2 B.1 — strong-read the just-created group's in-flight tracks and recover
|
|
1629
|
+
* them UNDER the barrier. Capture + CLEAR `pendingB1TaskGroupId` immediately
|
|
1630
|
+
* so a later reconnect's `onSubscribed` does not re-run B.1 for an
|
|
1631
|
+
* already-handled group. Fail-open: a query throw warns + returns (the barrier
|
|
1632
|
+
* still releases + drains via `withRecoveryBarrier`'s `finally`).
|
|
1633
|
+
*/
|
|
1634
|
+
private fireB1;
|
|
1635
|
+
/**
|
|
1636
|
+
* §4.2 B.2 — discovery on subscription establishment, with a bounded GSI-lag
|
|
1637
|
+
* retry. Each attempt queries the GSI then IMMEDIATELY recovers that attempt's
|
|
1638
|
+
* rows (NOT accumulate-then-recover — `relaunch` is decided from the lease live
|
|
1639
|
+
* at query time, so recovering each attempt's rows immediately avoids a
|
|
1640
|
+
* near-expiring lease lapsing during backoff). Keeps querying across the
|
|
1641
|
+
* bounded window to catch GSI-lagged groups (does NOT stop on first non-empty).
|
|
1642
|
+
* Idempotent via `seenGateDispatchRunIds`. Retry-skip optimization (§4.2): on a
|
|
1643
|
+
* RECONNECT where no team work ran this session AND the first query is empty,
|
|
1644
|
+
* skip the remaining retries (no GSI lag possible). On the INITIAL connect the
|
|
1645
|
+
* full retry ALWAYS runs (a restarted process has `teamSessionActive`
|
|
1646
|
+
* stale-false yet may have orphaned team work). Fail-open per attempt.
|
|
1647
|
+
*/
|
|
1648
|
+
runB2Recovery(isReconnect: boolean): Promise<void>;
|
|
1649
|
+
private runB2RecoveryAttempts;
|
|
1650
|
+
/**
|
|
1651
|
+
* §4.1 — the TWO-PASS recovery core. PASS 1 decrypts + seeds EVERY brief for
|
|
1652
|
+
* all non-terminal tracks (the SEED set, incl. `Pending`) BEFORE any launch, so
|
|
1653
|
+
* the live launch plumbing (bridge → getTeamBrief → registerTeamTaskBrief →
|
|
1654
|
+
* drain), any buffered-dispatch drain, AND a LATER live dispatch of a
|
|
1655
|
+
* capacity-held `Pending` track all read the CORRECT brief — no empty window.
|
|
1656
|
+
* PASS 2 relaunches ONLY the relaunch subset. The ordering BARRIER (§4.3) is
|
|
1657
|
+
* owned by the CALLER (`fireB1` / `runB2Recovery` via `withRecoveryBarrier`),
|
|
1658
|
+
* which holds it across the WHOLE recovery window + drains once at the end —
|
|
1659
|
+
* so this method just seeds + relaunches (it is always invoked under a barrier
|
|
1660
|
+
* that is already down, and may run multiple times within one barrier window).
|
|
1661
|
+
*/
|
|
1662
|
+
recoverTeamTracks(rows: TeamTrackRecoveryRow[]): Promise<void>;
|
|
1663
|
+
/**
|
|
1664
|
+
* §4.1 PASS 2 (one row) — validate → recoverLaunchTrack → repair-on-duplicate →
|
|
1665
|
+
* drive the round-0 gate. Honors the corrupt-brief tombstone + the
|
|
1666
|
+
* `seenGateDispatchRunIds` dedup + the #592 provenance guard. Called ONLY from
|
|
1667
|
+
* `recoverTeamTracks` PASS 2 (the recovery dispatch chokepoint); the buffered
|
|
1668
|
+
* live-TrackAssigned drain (§4.3) routes through `consumeClassB` →
|
|
1669
|
+
* `LocalExecutorImpl.launchTrack` (NOT here), where the LE's `isForeignTeamGroup`
|
|
1670
|
+
* gate is the live-path counterpart (Stage-1 r1 HIGH / Stage-2 advisory). Pre: the
|
|
1671
|
+
* brief is already seeded (PASS 1) or legitimately empty.
|
|
1672
|
+
*/
|
|
1673
|
+
private launchRecoveredTrack;
|
|
1674
|
+
/**
|
|
1675
|
+
* §4.1 — reconstruct the round-0 `GateDispatchPayload` from a recovery row.
|
|
1676
|
+
* Round-0 is SEAT-LESS (the proposal hasn't arrived, no reviewer seats yet), so
|
|
1677
|
+
* `verificationChecks` / `reviewerSeats` / `finalApprovalSurface` are absent —
|
|
1678
|
+
* matching the live round-0 emit (`start_ports.rs` sets `reviewer_seats: None`).
|
|
1679
|
+
*/
|
|
1680
|
+
private reconstructRound0GateDispatch;
|
|
1681
|
+
private handleReviseFeedback;
|
|
1682
|
+
/** The implementor agent the loop spawns (set at start_task). */
|
|
1683
|
+
private activeImplementorAgent;
|
|
1684
|
+
setActiveImplementorAgent(agent: AgentKind): void;
|
|
1685
|
+
/**
|
|
1686
|
+
* #585 #C10M-9 — resolve the active task's request context for `/continue
|
|
1687
|
+
* request` (the entrypoint wires this to the slash route's
|
|
1688
|
+
* `getActiveRequestContext`). Returns `null` when no implementor round is
|
|
1689
|
+
* active (no gate to hand off from). The `gateId`/`roundNumber` come from the
|
|
1690
|
+
* ACTIVE implementor registry (the round currently running); the brief +
|
|
1691
|
+
* source agent come from the loop's active state.
|
|
1692
|
+
*/
|
|
1693
|
+
getActiveContinuationRequestContext(): {
|
|
1694
|
+
taskId: string;
|
|
1695
|
+
gateId: string;
|
|
1696
|
+
roundNumber: number;
|
|
1697
|
+
brief: string;
|
|
1698
|
+
sourceAgent: AgentKind;
|
|
1699
|
+
} | null;
|
|
1700
|
+
/**
|
|
1701
|
+
* #585 L-A — mint a fresh `expiresAt = now + TTL` at millisecond precision
|
|
1702
|
+
* (RFC-3339). Fresh per request so a CANCEL→re-request produces a distinct
|
|
1703
|
+
* hash preimage → distinct `packet_hash` → the FRESH-write path re-fires the
|
|
1704
|
+
* prompt (#C10M-12). NEW behavior #585 adds (no production minting site
|
|
1705
|
+
* existed; `expiresAt` is a caller-supplied `PacketWriteInput` field).
|
|
1706
|
+
*/
|
|
1707
|
+
private mintExpiresAt;
|
|
1708
|
+
/**
|
|
1709
|
+
* #585 L2 — resolve the text used to classify a quota failure. In TEST mode
|
|
1710
|
+
* ONLY (`NODE_ENV === 'test'`), the force-quota env var (assembled from
|
|
1711
|
+
* fragments below, see the inline comment) injects a synthetic usage-limit
|
|
1712
|
+
* stderr for the named agent so the `continuation-handoff-quota` E2E scenario
|
|
1713
|
+
* is reproducible in CI (the real quota error is not naturally reproducible).
|
|
1714
|
+
* The force-quota literal is asserted ABSENT from the published esbuild bundle
|
|
1715
|
+
* by the `verify-no-test-hooks` build step (§5.5 / dimension-8
|
|
1716
|
+
* prod-unreachability) — hence the fragment-assembled key + the verbatim
|
|
1717
|
+
* literal NEVER appearing in this file. The branch is `NODE_ENV==='test'`-gated
|
|
1718
|
+
* so it is never evaluated in prod regardless.
|
|
1719
|
+
*/
|
|
1720
|
+
/**
|
|
1721
|
+
* #585 §5.5 / L2 — the SINGLE source of the force-quota test gate. Returns true
|
|
1722
|
+
* when (a) `NODE_ENV === 'test'` AND (b) the fragment-assembled force-quota env
|
|
1723
|
+
* var names `agent`. The env-var KEY is assembled from fragments so the
|
|
1724
|
+
* force-quota literal NEVER appears verbatim in the published bundle (the
|
|
1725
|
+
* `verify-no-test-hooks` CI grep asserts its absence);
|
|
1726
|
+
* the whole gate is ALSO `NODE_ENV==='test'`-gated so it is never evaluated in
|
|
1727
|
+
* prod. Drives BOTH the synthetic quota stderr (`resolveQuotaClassificationText`)
|
|
1728
|
+
* AND the forced empty-diff born-halt (`runImplementorRound` capture site) — the
|
|
1729
|
+
* two halves that make the `continuation-handoff-quota` E2E reproducible (a real
|
|
1730
|
+
* model will not deterministically exit with NO diff, which the quota fork
|
|
1731
|
+
* requires).
|
|
1732
|
+
*/
|
|
1733
|
+
private isForcedQuotaAgent;
|
|
1734
|
+
/**
|
|
1735
|
+
* Test seam — force the ROUND-0 implementor agent (overriding the session
|
|
1736
|
+
* default). `NODE_ENV==='test'`-gated; the env key is fragment-assembled so the
|
|
1737
|
+
* literal never appears in a published bundle (`verify-no-test-hooks` greps for
|
|
1738
|
+
* it). Makes the `codex-implementor-edits` E2E deterministic: a Codex
|
|
1739
|
+
* implementor exercises `codex exec` end-to-end (the substrate-aware sandbox
|
|
1740
|
+
* rewrite) where the session default would otherwise pick Claude. Returns the
|
|
1741
|
+
* forced agent, or null when unset / in prod.
|
|
1742
|
+
*/
|
|
1743
|
+
private resolveForcedImplementorAgent;
|
|
1744
|
+
private resolveQuotaClassificationText;
|
|
1745
|
+
/**
|
|
1746
|
+
* #585 §3.5 / §3.6 — the `quota_exhausted` fork. The implementor halted on a
|
|
1747
|
+
* quota error (born-halt or, defensively, a throw). Bound-check the
|
|
1748
|
+
* quota-substitution loop (#C10M-12), discard the shadow (the real tree was
|
|
1749
|
+
* never touched), emit the `continuation_offered` milestone (NOT
|
|
1750
|
+
* `round_failed`, L-B), and fire `writer.write()` with
|
|
1751
|
+
* `blockedReason:'quota_exhausted'` + the dead round's gateId + a fresh ms
|
|
1752
|
+
* `expiresAt` — the server stamps the gate + emits the handoff prompt.
|
|
1753
|
+
*/
|
|
1754
|
+
private handleQuotaExhaustedFork;
|
|
1755
|
+
/**
|
|
1756
|
+
* #585 §3.5 / #C10M-3/9/16 — the trigger transport. Builds a
|
|
1757
|
+
* `PacketWriteInput` (fresh ULID `offerId`, the trigger's `blockedReason`,
|
|
1758
|
+
* the round's `gateId`, a fresh ms `expiresAt`) and calls
|
|
1759
|
+
* `ContinuationPacketWriter.write()` DIRECTLY (local CONTINUATION.md + the
|
|
1760
|
+
* already-wired `recordContinuationPacketWritten` mutation → the server
|
|
1761
|
+
* stamps `handoff_pending` + emits the prompt). For the `user_requested`
|
|
1762
|
+
* trigger (`abortActive: true`) it FIRST claims the handoff + aborts the
|
|
1763
|
+
* running implementor (Codex-H1/#C10M-16) so the original implementor is
|
|
1764
|
+
* suppressed. Returns the minted `offerId` (or null on failure).
|
|
1765
|
+
*/
|
|
1766
|
+
requestContinuation(input: {
|
|
1767
|
+
taskId: string;
|
|
1768
|
+
gateId: string;
|
|
1769
|
+
sourceAgent: AgentKind;
|
|
1770
|
+
blockedReason: HandoffReason;
|
|
1771
|
+
brief: string;
|
|
1772
|
+
abortActive: boolean;
|
|
1773
|
+
targetAgent?: AgentKind;
|
|
1774
|
+
onOfferMinted?: (offerId: string) => void;
|
|
1775
|
+
}): Promise<{
|
|
1776
|
+
offerId: string;
|
|
1777
|
+
} | null>;
|
|
1778
|
+
/**
|
|
1779
|
+
* #585 #C10M-16 — claim the handoff for the active task: set
|
|
1780
|
+
* `handoffClaimed = true` + abort the running implementor's spawn so the
|
|
1781
|
+
* post-`await handle.done` path suppresses the late capture/submit/surfaceHalt.
|
|
1782
|
+
* Idempotent; a no-op when no implementor is active for the task.
|
|
1783
|
+
*/
|
|
1784
|
+
private claimHandoffForActiveTask;
|
|
1785
|
+
/**
|
|
1786
|
+
* #585 §2.3 / #C10M-16 — the `onApplyUserDecision` consumer for the
|
|
1787
|
+
* orchestration shell. Routes a `continuation_switch_accepted` action to
|
|
1788
|
+
* `resumeFromContinuation`, deduped against the local-accept path via
|
|
1789
|
+
* `resumedOffers` keyed on `action.offer_id`. A LOCAL accept already added
|
|
1790
|
+
* its `offer_id` (so its own echo is SUPPRESSED here); a FOREIGN
|
|
1791
|
+
* (mobile-originated) accept drives EXACTLY ONE resume. A `declined` echo
|
|
1792
|
+
* clears the prompt state (handled by the reducer via the SUBMIT path; here
|
|
1793
|
+
* it is a no-op for the resume side). Wired by the shell to the
|
|
1794
|
+
* `AppSyncClient.subscribeToApplyUserDecision` callback.
|
|
1795
|
+
*/
|
|
1796
|
+
onApplyUserDecisionEcho(event: {
|
|
1797
|
+
taskId: string;
|
|
1798
|
+
gateId: string;
|
|
1799
|
+
decision: string;
|
|
1800
|
+
action: PostDecisionAction;
|
|
1801
|
+
}): Promise<void>;
|
|
1802
|
+
/**
|
|
1803
|
+
* #585 §3.5 / #C10M-11/13 — resume the task with the user's chosen agent.
|
|
1804
|
+
* The SERVER has ALREADY minted the `nextGateId` row + advanced to
|
|
1805
|
+
* `GatePlanInFlight{nextGateId}` (agy-H1) — the desktop does NOT mint a gate;
|
|
1806
|
+
* it spawns into the server-created gate. Reads the CP-6 `CONTINUATION.md`
|
|
1807
|
+
* (hash + backend-audit verified — refuses a `PacketUnverified` packet, the
|
|
1808
|
+
* pre-spawn fidelity gate), builds the resume brief from the packet's
|
|
1809
|
+
* WORK-CONTEXT fields ONLY (NEVER `packet.targetAgent` — the user's choice is
|
|
1810
|
+
* authoritative, #C10M-13), `setActiveImplementorAgent(targetAgent)`, and
|
|
1811
|
+
* re-enters `runImplementorRound` with the SERVER-supplied `nextGateId`/
|
|
1812
|
+
* `nextRound`. Idempotent on the deterministic `nextGateId` via the
|
|
1813
|
+
* `inFlightImplementorGates`/`submittedOutputGates` double-guard.
|
|
1814
|
+
*/
|
|
1815
|
+
resumeFromContinuation(input: {
|
|
1816
|
+
taskId: string;
|
|
1817
|
+
targetAgent: AgentKind;
|
|
1818
|
+
nextGateId: string;
|
|
1819
|
+
nextRound: number;
|
|
1820
|
+
}): Promise<boolean>;
|
|
1821
|
+
/**
|
|
1822
|
+
* #585 #C10M-13 — build the resume brief from the packet's WORK-CONTEXT
|
|
1823
|
+
* fields ONLY. MUST NOT include `packet.targetAgent` or any agent-name
|
|
1824
|
+
* directive (so it can never contradict the user's chosen agent). Pure +
|
|
1825
|
+
* deterministic.
|
|
1826
|
+
*/
|
|
1827
|
+
private buildResumeBrief;
|
|
1828
|
+
/**
|
|
1829
|
+
* #585 #C10M-16 — record that a LOCAL accept (typed-digit or `/continue
|
|
1830
|
+
* accept`) drove `resumeFromContinuation` inline, so the `onApplyUserDecision`
|
|
1831
|
+
* own-echo for that SAME offer is suppressed (exactly ONE resume per offer).
|
|
1832
|
+
* Called by the local-accept path BEFORE driving the resume.
|
|
1833
|
+
*/
|
|
1834
|
+
markOfferResumed(offerId: string): void;
|
|
1835
|
+
/** @internal — test introspection for the active-implementor registry. */
|
|
1836
|
+
get _activeImplementorForTests(): ReadonlyMap<string, {
|
|
1837
|
+
gateId: string;
|
|
1838
|
+
handoffClaimed: boolean;
|
|
1839
|
+
}>;
|
|
1840
|
+
/** @internal — test introspection for the resume dedupe set. */
|
|
1841
|
+
get _resumedOffersForTests(): ReadonlySet<string>;
|
|
1842
|
+
/**
|
|
1843
|
+
* CP-1.f revise-context fix (dogfood task 2ac68708, 2026-06-11) — record a
|
|
1844
|
+
* BINDING user-requested change for a task. Called from `handleReviseFeedback`
|
|
1845
|
+
* when the `REVISE_FEEDBACK` payload carries the engine's synthetic
|
|
1846
|
+
* `seat_id === 'user'` finding (a Reject-with-notes, desktop OR mobile); its
|
|
1847
|
+
* `findings` text becomes a binding requirement re-stated in EVERY subsequent
|
|
1848
|
+
* revise round so no round can silently drop an earlier user requirement.
|
|
1849
|
+
*
|
|
1850
|
+
* Empty / whitespace-only notes are ignored (a defensive guard — the engine
|
|
1851
|
+
* already fails closed on empty notes upstream). Idempotent against an exact
|
|
1852
|
+
* consecutive repeat (a re-delivered packet must not duplicate the binding
|
|
1853
|
+
* requirement). Public so tests can drive the capture directly.
|
|
1854
|
+
*/
|
|
1855
|
+
recordUserNote(taskId: string, note: string): void;
|
|
1856
|
+
/**
|
|
1857
|
+
* CP-1.f revise-context fix (dogfood task 2ac68708, 2026-06-11) — build the
|
|
1858
|
+
* CUMULATIVE round-N+1 revise brief from the original task brief
|
|
1859
|
+
* (`activeBrief`, set at start_task), every BINDING user note recorded for
|
|
1860
|
+
* this task, and the current round's reviewer findings. The SAME brief is
|
|
1861
|
+
* used as the implementor stdin AND the execute-first `task_brief` rendered
|
|
1862
|
+
* into the reviewer prompts, so reviewers and the implementor share one
|
|
1863
|
+
* source of truth (the REAL requested task) and no round drops an earlier
|
|
1864
|
+
* user requirement.
|
|
1865
|
+
*/
|
|
1866
|
+
private buildReviseBrief;
|
|
1867
|
+
/** @internal — test introspection. */
|
|
1868
|
+
get _runningSeatsForTests(): ReadonlySet<string>;
|
|
1869
|
+
/** @internal — test introspection. */
|
|
1870
|
+
get _submittedSeatsForTests(): ReadonlySet<string>;
|
|
1871
|
+
/** @internal — test seam to set the active task without start_task. */
|
|
1872
|
+
_setActiveTaskForTests(taskId: string, brief?: string): void;
|
|
1873
|
+
/** @internal — read a task's accumulated binding user notes. */
|
|
1874
|
+
_userNotesForTests(taskId: string): readonly string[];
|
|
1875
|
+
/** @internal — read the consumed REVISE_FEEDBACK dedup ids (Stage-2 r1 guard). */
|
|
1876
|
+
get _seenReviseFeedbackIdsForTests(): ReadonlySet<string>;
|
|
1877
|
+
/** @internal — read the live in-memory shadow handle for a task (or undefined). */
|
|
1878
|
+
_getShadowForTests(taskId: string): WorkspaceShadow | undefined;
|
|
1879
|
+
/** @internal — re-arm a (recovered) shadow handle so a double-fire hits the marker short-circuit. */
|
|
1880
|
+
_armShadowForTests(taskId: string, shadow: WorkspaceShadow): void;
|
|
1881
|
+
/**
|
|
1882
|
+
* @internal — drop a task's shadow-map entry WITHOUT touching its dir/marker.
|
|
1883
|
+
* Reproduces the transient UNMAPPED window inside `replaceShadowForRound0`:
|
|
1884
|
+
* the map entry is `delete`d before the (slow) stale teardown awaits and before
|
|
1885
|
+
* the replacement shadow B is created, so `discardShadow` can observe
|
|
1886
|
+
* `mapped === undefined`.
|
|
1887
|
+
*/
|
|
1888
|
+
_unmapShadowForTests(taskId: string): void;
|
|
1889
|
+
/**
|
|
1890
|
+
* @internal — NEGATIVE-CONTROL seams (restart-race mutex, 2026-06-11 Codex r2):
|
|
1891
|
+
* invoke the UNLOCKED `*Locked` bodies directly, BYPASSING `runExclusiveShadowOp`.
|
|
1892
|
+
* Used ONLY by the mutex regression test to prove that without serialization a
|
|
1893
|
+
* matched discard overlapping a restart replacement destroys the #PR-4 marker.
|
|
1894
|
+
* Production code MUST always go through the locked public wrappers.
|
|
1895
|
+
*/
|
|
1896
|
+
_discardShadowUnlockedForTests(taskId: string, expected?: WorkspaceShadow): Promise<void>;
|
|
1897
|
+
_replaceShadowForRound0UnlockedForTests(taskId: string): Promise<void>;
|
|
1898
|
+
_ensureShadowUnlockedForTests(taskId: string): Promise<WorkspaceShadow>;
|
|
1899
|
+
}
|
|
1900
|
+
export { reviewerAgentToLeAgent };
|