@pellux/goodvibes-sdk 0.37.1 → 0.38.0

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.
Files changed (165) hide show
  1. package/dist/contracts/artifacts/operator-contract.json +1 -1
  2. package/dist/events/communication.d.ts +21 -1
  3. package/dist/events/communication.d.ts.map +1 -1
  4. package/dist/platform/agents/message-bus-core.d.ts +7 -0
  5. package/dist/platform/agents/message-bus-core.d.ts.map +1 -1
  6. package/dist/platform/agents/message-bus-core.js +2 -1
  7. package/dist/platform/agents/orchestrator-runner.d.ts +37 -1
  8. package/dist/platform/agents/orchestrator-runner.d.ts.map +1 -1
  9. package/dist/platform/agents/orchestrator-runner.js +158 -7
  10. package/dist/platform/agents/orchestrator.d.ts +40 -0
  11. package/dist/platform/agents/orchestrator.d.ts.map +1 -1
  12. package/dist/platform/agents/orchestrator.js +29 -0
  13. package/dist/platform/agents/turn-knowledge-injection.d.ts +107 -0
  14. package/dist/platform/agents/turn-knowledge-injection.d.ts.map +1 -0
  15. package/dist/platform/agents/turn-knowledge-injection.js +152 -0
  16. package/dist/platform/agents/wrfc-controller.d.ts +23 -0
  17. package/dist/platform/agents/wrfc-controller.d.ts.map +1 -1
  18. package/dist/platform/agents/wrfc-controller.js +55 -0
  19. package/dist/platform/config/manager.d.ts +9 -0
  20. package/dist/platform/config/manager.d.ts.map +1 -1
  21. package/dist/platform/config/manager.js +15 -0
  22. package/dist/platform/control-plane/approval-broker.d.ts +1 -0
  23. package/dist/platform/control-plane/approval-broker.d.ts.map +1 -1
  24. package/dist/platform/control-plane/approval-broker.js +2 -0
  25. package/dist/platform/core/adaptive-planner.d.ts +50 -0
  26. package/dist/platform/core/adaptive-planner.d.ts.map +1 -1
  27. package/dist/platform/core/adaptive-planner.js +49 -0
  28. package/dist/platform/core/index.d.ts +1 -0
  29. package/dist/platform/core/index.d.ts.map +1 -1
  30. package/dist/platform/core/index.js +1 -0
  31. package/dist/platform/core/orchestrator-runtime.d.ts +11 -0
  32. package/dist/platform/core/orchestrator-runtime.d.ts.map +1 -1
  33. package/dist/platform/core/orchestrator-turn-loop.d.ts +28 -1
  34. package/dist/platform/core/orchestrator-turn-loop.d.ts.map +1 -1
  35. package/dist/platform/core/orchestrator-turn-loop.js +115 -3
  36. package/dist/platform/core/orchestrator.d.ts +61 -0
  37. package/dist/platform/core/orchestrator.d.ts.map +1 -1
  38. package/dist/platform/core/orchestrator.js +74 -1
  39. package/dist/platform/core/plan-proposal.d.ts +165 -0
  40. package/dist/platform/core/plan-proposal.d.ts.map +1 -0
  41. package/dist/platform/core/plan-proposal.js +296 -0
  42. package/dist/platform/knowledge/knowledge-api.d.ts +26 -0
  43. package/dist/platform/knowledge/knowledge-api.d.ts.map +1 -1
  44. package/dist/platform/knowledge/knowledge-api.js +11 -0
  45. package/dist/platform/orchestration/budget.d.ts +22 -0
  46. package/dist/platform/orchestration/budget.d.ts.map +1 -0
  47. package/dist/platform/orchestration/budget.js +35 -0
  48. package/dist/platform/orchestration/cancellation.d.ts +31 -0
  49. package/dist/platform/orchestration/cancellation.d.ts.map +1 -0
  50. package/dist/platform/orchestration/cancellation.js +25 -0
  51. package/dist/platform/orchestration/controller-compat.d.ts +32 -0
  52. package/dist/platform/orchestration/controller-compat.d.ts.map +1 -0
  53. package/dist/platform/orchestration/controller-compat.js +13 -0
  54. package/dist/platform/orchestration/dirty-guard.d.ts +32 -0
  55. package/dist/platform/orchestration/dirty-guard.d.ts.map +1 -0
  56. package/dist/platform/orchestration/dirty-guard.js +147 -0
  57. package/dist/platform/orchestration/engine.d.ts +57 -0
  58. package/dist/platform/orchestration/engine.d.ts.map +1 -0
  59. package/dist/platform/orchestration/engine.js +418 -0
  60. package/dist/platform/orchestration/index.d.ts +18 -0
  61. package/dist/platform/orchestration/index.d.ts.map +1 -0
  62. package/dist/platform/orchestration/index.js +10 -0
  63. package/dist/platform/orchestration/persistence.d.ts +24 -0
  64. package/dist/platform/orchestration/persistence.d.ts.map +1 -0
  65. package/dist/platform/orchestration/persistence.js +176 -0
  66. package/dist/platform/orchestration/phase-runner.d.ts +87 -0
  67. package/dist/platform/orchestration/phase-runner.d.ts.map +1 -0
  68. package/dist/platform/orchestration/phase-runner.js +274 -0
  69. package/dist/platform/orchestration/scheduler.d.ts +45 -0
  70. package/dist/platform/orchestration/scheduler.d.ts.map +1 -0
  71. package/dist/platform/orchestration/scheduler.js +57 -0
  72. package/dist/platform/orchestration/types.d.ts +250 -0
  73. package/dist/platform/orchestration/types.d.ts.map +1 -0
  74. package/dist/platform/orchestration/types.js +15 -0
  75. package/dist/platform/runtime/emitters/communication.d.ts +11 -0
  76. package/dist/platform/runtime/emitters/communication.d.ts.map +1 -1
  77. package/dist/platform/runtime/emitters/communication.js +9 -0
  78. package/dist/platform/runtime/feature-flags/flags.d.ts.map +1 -1
  79. package/dist/platform/runtime/feature-flags/flags.js +17 -0
  80. package/dist/platform/runtime/fleet/adapters/agent.d.ts +56 -0
  81. package/dist/platform/runtime/fleet/adapters/agent.d.ts.map +1 -0
  82. package/dist/platform/runtime/fleet/adapters/agent.js +143 -0
  83. package/dist/platform/runtime/fleet/adapters/automation.d.ts +30 -0
  84. package/dist/platform/runtime/fleet/adapters/automation.d.ts.map +1 -0
  85. package/dist/platform/runtime/fleet/adapters/automation.js +41 -0
  86. package/dist/platform/runtime/fleet/adapters/background-process.d.ts +12 -0
  87. package/dist/platform/runtime/fleet/adapters/background-process.d.ts.map +1 -0
  88. package/dist/platform/runtime/fleet/adapters/background-process.js +46 -0
  89. package/dist/platform/runtime/fleet/adapters/code-index.d.ts +21 -0
  90. package/dist/platform/runtime/fleet/adapters/code-index.d.ts.map +1 -0
  91. package/dist/platform/runtime/fleet/adapters/code-index.js +44 -0
  92. package/dist/platform/runtime/fleet/adapters/orchestration.d.ts +47 -0
  93. package/dist/platform/runtime/fleet/adapters/orchestration.d.ts.map +1 -0
  94. package/dist/platform/runtime/fleet/adapters/orchestration.js +225 -0
  95. package/dist/platform/runtime/fleet/adapters/schedule.d.ts +14 -0
  96. package/dist/platform/runtime/fleet/adapters/schedule.d.ts.map +1 -0
  97. package/dist/platform/runtime/fleet/adapters/schedule.js +28 -0
  98. package/dist/platform/runtime/fleet/adapters/trigger.d.ts +12 -0
  99. package/dist/platform/runtime/fleet/adapters/trigger.d.ts.map +1 -0
  100. package/dist/platform/runtime/fleet/adapters/trigger.js +22 -0
  101. package/dist/platform/runtime/fleet/adapters/watcher.d.ts +13 -0
  102. package/dist/platform/runtime/fleet/adapters/watcher.d.ts.map +1 -0
  103. package/dist/platform/runtime/fleet/adapters/watcher.js +47 -0
  104. package/dist/platform/runtime/fleet/adapters/workflow.d.ts +11 -0
  105. package/dist/platform/runtime/fleet/adapters/workflow.d.ts.map +1 -0
  106. package/dist/platform/runtime/fleet/adapters/workflow.js +34 -0
  107. package/dist/platform/runtime/fleet/adapters/wrfc.d.ts +22 -0
  108. package/dist/platform/runtime/fleet/adapters/wrfc.d.ts.map +1 -0
  109. package/dist/platform/runtime/fleet/adapters/wrfc.js +228 -0
  110. package/dist/platform/runtime/fleet/index.d.ts +10 -0
  111. package/dist/platform/runtime/fleet/index.d.ts.map +1 -0
  112. package/dist/platform/runtime/fleet/index.js +6 -0
  113. package/dist/platform/runtime/fleet/registry.d.ts +113 -0
  114. package/dist/platform/runtime/fleet/registry.d.ts.map +1 -0
  115. package/dist/platform/runtime/fleet/registry.js +630 -0
  116. package/dist/platform/runtime/fleet/types.d.ts +199 -0
  117. package/dist/platform/runtime/fleet/types.d.ts.map +1 -0
  118. package/dist/platform/runtime/fleet/types.js +2 -0
  119. package/dist/platform/runtime/runtime-knowledge-api.d.ts +1 -1
  120. package/dist/platform/runtime/runtime-knowledge-api.d.ts.map +1 -1
  121. package/dist/platform/runtime/runtime-knowledge-api.js +1 -0
  122. package/dist/platform/runtime/services.d.ts +46 -0
  123. package/dist/platform/runtime/services.d.ts.map +1 -1
  124. package/dist/platform/runtime/services.js +78 -0
  125. package/dist/platform/state/code-index-chunking.d.ts +49 -0
  126. package/dist/platform/state/code-index-chunking.d.ts.map +1 -0
  127. package/dist/platform/state/code-index-chunking.js +111 -0
  128. package/dist/platform/state/code-index-db.d.ts +53 -0
  129. package/dist/platform/state/code-index-db.d.ts.map +1 -0
  130. package/dist/platform/state/code-index-db.js +101 -0
  131. package/dist/platform/state/code-index-store.d.ts +175 -0
  132. package/dist/platform/state/code-index-store.d.ts.map +1 -0
  133. package/dist/platform/state/code-index-store.js +612 -0
  134. package/dist/platform/state/index.d.ts +4 -2
  135. package/dist/platform/state/index.d.ts.map +1 -1
  136. package/dist/platform/state/index.js +2 -1
  137. package/dist/platform/state/knowledge-injection.d.ts +23 -0
  138. package/dist/platform/state/knowledge-injection.d.ts.map +1 -1
  139. package/dist/platform/state/knowledge-injection.js +34 -19
  140. package/dist/platform/state/memory-vector-store.d.ts +1 -13
  141. package/dist/platform/state/memory-vector-store.d.ts.map +1 -1
  142. package/dist/platform/state/memory-vector-store.js +6 -38
  143. package/dist/platform/state/sqlite-vec-loader.d.ts +25 -0
  144. package/dist/platform/state/sqlite-vec-loader.d.ts.map +1 -0
  145. package/dist/platform/state/sqlite-vec-loader.js +43 -0
  146. package/dist/platform/tools/agent/manager.d.ts +128 -1
  147. package/dist/platform/tools/agent/manager.d.ts.map +1 -1
  148. package/dist/platform/tools/agent/manager.js +137 -1
  149. package/dist/platform/tools/exec/runtime.d.ts.map +1 -1
  150. package/dist/platform/tools/exec/runtime.js +139 -44
  151. package/dist/platform/tools/exec/schema.d.ts +2 -0
  152. package/dist/platform/tools/exec/schema.d.ts.map +1 -1
  153. package/dist/platform/tools/fetch/runtime.d.ts +7 -0
  154. package/dist/platform/tools/fetch/runtime.d.ts.map +1 -1
  155. package/dist/platform/tools/fetch/runtime.js +8 -6
  156. package/dist/platform/tools/registry.d.ts +9 -3
  157. package/dist/platform/tools/registry.d.ts.map +1 -1
  158. package/dist/platform/tools/registry.js +9 -3
  159. package/dist/platform/types/tools.d.ts +14 -1
  160. package/dist/platform/types/tools.d.ts.map +1 -1
  161. package/dist/platform/version.js +1 -1
  162. package/dist/platform/workspace/checkpoint/side-git.d.ts +20 -8
  163. package/dist/platform/workspace/checkpoint/side-git.d.ts.map +1 -1
  164. package/dist/platform/workspace/checkpoint/side-git.js +21 -11
  165. package/package.json +17 -9
@@ -0,0 +1,87 @@
1
+ /** SDK-owned platform module. This implementation is maintained in goodvibes-sdk. */
2
+ /**
3
+ * Phase-runner (Wave 4, wo701) — runs one WorkItem through one Phase: spawn
4
+ * agent, await completion, verify claims, run gates, commit, cleanup.
5
+ *
6
+ * REUSES the hardened WRFC primitives verbatim (same functions WrfcController
7
+ * itself calls, so behavior can't fork): verifyEngineerClaims
8
+ * (wrfc-reporting.ts) for the phantom-work guard, runWrfcGateChecks
9
+ * (wrfc-gate-runtime.ts) for quality gates, AgentWorktree.commitWorkingTree
10
+ * for scoped commits, and the transport-retry / WrfcChainFailureKind pattern
11
+ * (isTransportFailureMessage + getWrfcTransportRetryLimit/DelayMs) for
12
+ * bounded respawn-on-transport-blip.
13
+ *
14
+ * REALITY-WINS DIVERGENCE from the brief's design (c): WrfcController itself
15
+ * (wrfc-controller.ts, verified) never calls AgentWorktree.create() for its
16
+ * role agents — engineer/reviewer/fixer/integrator all run in the SAME
17
+ * shared `projectRoot` working directory; AgentWorktree is used ONLY for its
18
+ * commitWorkingTree/merge/cleanup surface (merge/cleanup are safe no-ops
19
+ * when no isolated worktree dir exists, which is always, today). There is no
20
+ * per-agent `workingDirectory` override anywhere in AgentInput /
21
+ * AgentOrchestratorRunContext.createRunContext() (verified: the latter is
22
+ * fixed per AgentOrchestrator instance, not per-spawn), so a spawned agent
23
+ * cannot actually be pointed at an isolated worktree directory without new
24
+ * cross-cutting plumbing through AgentManager/AgentOrchestrator construction
25
+ * — well beyond this module's boundary, and not something WrfcController
26
+ * itself has either. This module therefore mirrors WrfcController's ACTUAL
27
+ * (shared-directory) behavior rather than the brief's aspirational
28
+ * per-item-isolated-worktree fan-out; true fan-out isolation is a valuable,
29
+ * separately-scoped follow-up (see the work-order report).
30
+ *
31
+ * SECOND REALITY-WINS DIVERGENCE: AgentManager.spawn()'s root-spawn
32
+ * normalization (tools/agent/wrfc-batch-policy.ts isRootReviewRoleTask) force
33
+ * -rewrites any PARENTLESS spawn whose template is literally
34
+ * reviewer/tester/verifier/qa/review/test, OR whose task text matches
35
+ * ROLE_ACTION_RE/ROLE_PREFIX_RE (e.g. "review the diff"), into an
36
+ * 'engineer'-templated WRFC-owner chain with `dangerously_disable_wrfc`
37
+ * forced back to `false` — REGARDLESS of what this module passes in.
38
+ * Phase-runner spawns are always parentless (a workstream has no owning
39
+ * AgentRecord), so it must dodge that heuristic by construction: never
40
+ * literally template review/test-flavored phases as one of those role
41
+ * strings (use 'general' instead — see templateForPhase), and phrase
42
+ * review-phase prompts with "assess/evaluate" rather than "review/test/
43
+ * verify" (see buildPhaseTask). This is load-bearing: changing this wording
44
+ * without checking wrfc-batch-policy.ts's regexes again risks silently
45
+ * re-activating the WRFC hijack for review-kind phases.
46
+ */
47
+ import type { AgentManager } from '../tools/agent/manager.js';
48
+ import type { ConfigManager } from '../config/manager.js';
49
+ import type { RuntimeEventBus } from '../runtime/events/index.js';
50
+ import type { CancellationRegistry } from './cancellation.js';
51
+ import { type DirtyLaunchSnapshot } from './dirty-guard.js';
52
+ import type { Phase, PhaseResult, WorkItem, WorkItemUsage, Workstream } from './types.js';
53
+ /** Narrow structural pick — testable with stubs, mirrors AgentManagerLike (wrfc-config.ts). */
54
+ export type PhaseRunnerAgentManagerLike = Pick<AgentManager, 'spawn' | 'getStatus' | 'cancel' | 'registerCancellationSignal' | 'releaseCancellationSignal'>;
55
+ /** Structural pick of AgentWorktree's surface — matches WrfcController's WrfcWorktreeOps injection seam exactly, so the same test doubles work for both. */
56
+ export interface WrfcWorktreeOps {
57
+ merge(agentId: string): Promise<boolean>;
58
+ cleanup(agentId: string): Promise<void>;
59
+ commitWorkingTree(message: string, paths?: string[]): Promise<string | null>;
60
+ currentHead(): Promise<string | null>;
61
+ }
62
+ export interface PhaseRunnerDeps {
63
+ readonly agentManager: PhaseRunnerAgentManagerLike;
64
+ readonly configManager: Pick<ConfigManager, 'get' | 'getCategory'>;
65
+ readonly runtimeBus: RuntimeEventBus;
66
+ readonly projectRoot: string;
67
+ readonly sessionId: string;
68
+ readonly createWorktree?: (() => WrfcWorktreeOps) | undefined;
69
+ readonly cancellation: CancellationRegistry;
70
+ readonly priceUsage?: ((model: string | undefined, usage: WorkItemUsage) => number | null) | undefined;
71
+ readonly skipClaimVerification?: boolean | undefined;
72
+ /**
73
+ * The dirty-tree snapshot taken synchronously at engine launch (Wave 6,
74
+ * wo-F item 4 — see dirty-guard.ts). Absent (undefined) degrades to
75
+ * today's behavior: no exclusion, every candidate path is committed.
76
+ */
77
+ readonly launchDirtySnapshot?: DirtyLaunchSnapshot | undefined;
78
+ }
79
+ export interface PhaseRunOutcome {
80
+ readonly result: PhaseResult;
81
+ readonly agentStatus: 'completed' | 'failed' | 'cancelled';
82
+ }
83
+ /** Combines a new phase's usage into a work item's running total. Single-source cost (never independently re-priced here). */
84
+ export declare function mergeUsage(a: WorkItemUsage, b: WorkItemUsage): WorkItemUsage;
85
+ /** Runs one WorkItem through one Phase to completion (or cancellation/failure). Recurses (bounded by transportRetryLimit) on a transport-classified spawn failure. */
86
+ export declare function runPhase(workstream: Workstream, item: WorkItem, phase: Phase, priorReports: readonly PhaseResult[], deps: PhaseRunnerDeps): Promise<PhaseRunOutcome>;
87
+ //# sourceMappingURL=phase-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phase-runner.d.ts","sourceRoot":"","sources":["../../../src/platform/orchestration/phase-runner.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAErF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,2BAA2B,CAAC;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAelE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAiC,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC3F,OAAO,KAAK,EAAgC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExH,+FAA+F;AAC/F,MAAM,MAAM,2BAA2B,GAAG,IAAI,CAC5C,YAAY,EACZ,OAAO,GAAG,WAAW,GAAG,QAAQ,GAAG,4BAA4B,GAAG,2BAA2B,CAC9F,CAAC;AAEF,4JAA4J;AAC5J,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7E,WAAW,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,YAAY,EAAE,2BAA2B,CAAC;IACnD,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,KAAK,GAAG,aAAa,CAAC,CAAC;IACnE,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,MAAM,eAAe,CAAC,GAAG,SAAS,CAAC;IAC9D,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;IAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,aAAa,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,SAAS,CAAC;IACvG,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACrD;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;CAChE;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;CAC5D;AA8ED,8HAA8H;AAC9H,wBAAgB,UAAU,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,GAAG,aAAa,CAgB5E;AAyGD,sKAAsK;AACtK,wBAAsB,QAAQ,CAC5B,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,KAAK,EACZ,YAAY,EAAE,SAAS,WAAW,EAAE,EACpC,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,eAAe,CAAC,CAuG1B"}
@@ -0,0 +1,274 @@
1
+ /** SDK-owned platform module. This implementation is maintained in goodvibes-sdk. */
2
+ import { AgentWorktree } from '../agents/worktree.js';
3
+ import { parseCompletionReport, } from '../agents/completion-report.js';
4
+ import { verifyEngineerClaims } from '../agents/wrfc-reporting.js';
5
+ import { runWrfcGateChecks } from '../agents/wrfc-gate-runtime.js';
6
+ import { getWrfcTransportRetryDelayMs, getWrfcTransportRetryLimit } from '../agents/wrfc-config.js';
7
+ import { isTransportFailureMessage } from '../types/errors.js';
8
+ import { logger } from '../utils/logger.js';
9
+ import { summarizeError } from '../utils/error-display.js';
10
+ import { excludeUntouchedLaunchResidue } from './dirty-guard.js';
11
+ function templateForPhase(phase) {
12
+ return phase.kind === 'review' || phase.kind === 'gate' ? 'general' : 'engineer';
13
+ }
14
+ function buildPhaseTask(item, phase, priorReports) {
15
+ const priorContext = priorReports.length > 0
16
+ ? `\n\nPrior phase reports for this work item:\n${priorReports.map((r) => `- ${r.phaseId}: ${r.report.summary}`).join('\n')}`
17
+ : '';
18
+ if (phase.kind === 'review' || phase.kind === 'gate') {
19
+ return `Assess the following work item's changes against its constraints and report findings. Do not modify files.\n\nWork item: ${item.title}\n${item.task}${priorContext}`;
20
+ }
21
+ if (phase.kind === 'fix') {
22
+ return `Address the following findings for this work item.\n\nWork item: ${item.title}\n${item.task}${priorContext}`;
23
+ }
24
+ return `${item.task}${priorContext}`;
25
+ }
26
+ function genericReport(summary) {
27
+ return { version: 1, archetype: 'generic', summary, result: summary };
28
+ }
29
+ function sleep(ms) {
30
+ return new Promise((resolve) => {
31
+ const timer = setTimeout(resolve, ms);
32
+ timer.unref?.();
33
+ });
34
+ }
35
+ function awaitAgentTermination(runtimeBus, agentManager, agentId) {
36
+ return new Promise((resolve) => {
37
+ const unsubscribe = runtimeBus.onDomain('agents', (envelope) => {
38
+ const event = envelope.payload;
39
+ if (event.agentId !== agentId)
40
+ return;
41
+ if (event.type !== 'AGENT_COMPLETED' && event.type !== 'AGENT_FAILED' && event.type !== 'AGENT_CANCELLED')
42
+ return;
43
+ unsubscribe();
44
+ const status = event.type === 'AGENT_COMPLETED' ? 'completed' : event.type === 'AGENT_CANCELLED' ? 'cancelled' : 'failed';
45
+ resolve({ status, record: agentManager.getStatus(agentId) });
46
+ });
47
+ });
48
+ }
49
+ function usageFromRecord(record, priceUsage) {
50
+ const u = record?.usage;
51
+ const base = {
52
+ inputTokens: u?.inputTokens ?? 0,
53
+ outputTokens: u?.outputTokens ?? 0,
54
+ cacheReadTokens: u?.cacheReadTokens ?? 0,
55
+ cacheWriteTokens: u?.cacheWriteTokens ?? 0,
56
+ reasoningTokens: u?.reasoningTokens,
57
+ llmCallCount: u?.llmCallCount ?? 0,
58
+ turnCount: u?.turnCount ?? 0,
59
+ toolCallCount: record?.toolCallCount ?? 0,
60
+ };
61
+ let costUsd = null;
62
+ let costState = 'unpriced';
63
+ if (u && priceUsage) {
64
+ try {
65
+ const priced = priceUsage(record?.model, { ...base, costUsd: null, costState: 'unpriced' });
66
+ if (priced !== null) {
67
+ costUsd = priced;
68
+ costState = 'priced';
69
+ }
70
+ }
71
+ catch {
72
+ // stays unpriced — never fabricate a cost from a throwing pricer.
73
+ }
74
+ }
75
+ return { ...base, costUsd, costState };
76
+ }
77
+ /** Combines a new phase's usage into a work item's running total. Single-source cost (never independently re-priced here). */
78
+ export function mergeUsage(a, b) {
79
+ const sawReasoning = a.reasoningTokens !== undefined || b.reasoningTokens !== undefined;
80
+ const bothPriced = a.costState === 'priced' && b.costState === 'priced';
81
+ const neitherPriced = a.costUsd === null && b.costUsd === null;
82
+ return {
83
+ inputTokens: a.inputTokens + b.inputTokens,
84
+ outputTokens: a.outputTokens + b.outputTokens,
85
+ cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens,
86
+ cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens,
87
+ reasoningTokens: sawReasoning ? (a.reasoningTokens ?? 0) + (b.reasoningTokens ?? 0) : undefined,
88
+ llmCallCount: a.llmCallCount + b.llmCallCount,
89
+ turnCount: a.turnCount + b.turnCount,
90
+ toolCallCount: a.toolCallCount + b.toolCallCount,
91
+ costUsd: a.costUsd !== null && b.costUsd !== null ? a.costUsd + b.costUsd : (a.costUsd ?? b.costUsd),
92
+ costState: bothPriced ? 'priced' : neitherPriced ? 'unpriced' : 'estimated',
93
+ };
94
+ }
95
+ /** Quality gates (global-config-driven, reused VERBATIM) + phase-required-gate assertion + phantom guard + reviewer constraint findings. */
96
+ async function evaluateGate(workstream, phase, report, deps) {
97
+ const results = [...await runWrfcGateChecks({
98
+ configManager: deps.configManager,
99
+ projectRoot: deps.projectRoot,
100
+ runtimeBus: deps.runtimeBus,
101
+ sessionId: deps.sessionId,
102
+ chainId: workstream.id,
103
+ })];
104
+ const ranNames = new Set(results.map((r) => r.gate));
105
+ const missingRequired = phase.gate.gates.filter((name) => !ranNames.has(name));
106
+ for (const name of missingRequired) {
107
+ results.push({ gate: name, passed: false, output: 'required gate is not configured/enabled', durationMs: 0 });
108
+ }
109
+ if (report.archetype === 'engineer' && !deps.skipClaimVerification) {
110
+ const verification = verifyEngineerClaims(report, deps.projectRoot);
111
+ if (verification.kind === 'unverified' || verification.kind === 'unverifiable_no_claims') {
112
+ results.push({ gate: 'phantom-work-guard', passed: false, output: verification.summary, durationMs: 0 });
113
+ }
114
+ }
115
+ let constraintFindings;
116
+ let unsatisfiedConstraintIds;
117
+ if (report.archetype === 'reviewer') {
118
+ const reviewer = report;
119
+ constraintFindings = reviewer.constraintFindings ?? [];
120
+ const unsatisfied = constraintFindings.filter((f) => !f.satisfied);
121
+ unsatisfiedConstraintIds = unsatisfied.map((f) => f.constraintId);
122
+ if (!reviewer.passed || unsatisfied.length > 0) {
123
+ results.push({
124
+ gate: 'reviewer-verdict',
125
+ passed: false,
126
+ output: unsatisfied.length > 0
127
+ ? `${unsatisfied.length} unsatisfied constraint(s): ${unsatisfied.map((f) => f.constraintId).join(', ')}`
128
+ : 'reviewer did not pass',
129
+ durationMs: 0,
130
+ });
131
+ }
132
+ }
133
+ return {
134
+ passed: results.every((r) => r.passed),
135
+ results,
136
+ constraintFindings,
137
+ unsatisfiedConstraintIds,
138
+ };
139
+ }
140
+ async function commitPhaseWork(item, phase, agentId, worktree, deps) {
141
+ if (phase.gate.scope === 'off')
142
+ return undefined;
143
+ let paths = phase.gate.scope === 'scoped' ? item.touchedPaths : undefined;
144
+ let exclusion;
145
+ if (paths && paths.length > 0 && deps.launchDirtySnapshot) {
146
+ const launchSnapshot = deps.launchDirtySnapshot;
147
+ if (launchSnapshot.size > 0) {
148
+ const { included, excluded } = excludeUntouchedLaunchResidue(deps.projectRoot, paths, launchSnapshot);
149
+ if (excluded.length > 0) {
150
+ exclusion = { excludedPaths: excluded, skipped: included.length === 0 };
151
+ if (included.length === 0) {
152
+ // Every candidate path is untouched launch-dirty residue — an
153
+ // honest "nothing this phase did needs committing", not a silent
154
+ // no-op AND not a fallback to sweeping the whole working tree.
155
+ logger.info('orchestration phase-runner: scoped commit skipped — every candidate path is untouched launch-dirty residue', {
156
+ itemId: item.id,
157
+ phaseId: phase.id,
158
+ excludedPaths: excluded,
159
+ });
160
+ return exclusion;
161
+ }
162
+ logger.info('orchestration phase-runner: excluded untouched launch-dirty residue from scoped commit', {
163
+ itemId: item.id,
164
+ phaseId: phase.id,
165
+ excludedPaths: excluded,
166
+ });
167
+ paths = [...included];
168
+ }
169
+ }
170
+ }
171
+ try {
172
+ await worktree.commitWorkingTree(`orchestration: ${item.title} — ${phase.kind} phase`, paths);
173
+ await worktree.merge(agentId);
174
+ }
175
+ catch (error) {
176
+ logger.warn('orchestration phase-runner: commit/merge failed', { itemId: item.id, phaseId: phase.id, error: summarizeError(error) });
177
+ }
178
+ return exclusion;
179
+ }
180
+ /** Runs one WorkItem through one Phase to completion (or cancellation/failure). Recurses (bounded by transportRetryLimit) on a transport-classified spawn failure. */
181
+ export async function runPhase(workstream, item, phase, priorReports, deps) {
182
+ const startedAt = Date.now();
183
+ const createWorktree = deps.createWorktree ?? (() => new AgentWorktree(deps.projectRoot));
184
+ const worktree = createWorktree();
185
+ const record = deps.agentManager.spawn({
186
+ mode: 'spawn',
187
+ task: buildPhaseTask(item, phase, priorReports),
188
+ template: templateForPhase(phase),
189
+ dangerously_disable_wrfc: true,
190
+ });
191
+ record.workItemId = item.id;
192
+ item.agentId = record.id;
193
+ item.allAgentIds.push(record.id);
194
+ item.branch ??= `agent/${item.id}`;
195
+ const signal = deps.cancellation.start(item.id);
196
+ deps.agentManager.registerCancellationSignal(record.id, signal);
197
+ let outcome;
198
+ try {
199
+ outcome = await awaitAgentTermination(deps.runtimeBus, deps.agentManager, record.id);
200
+ }
201
+ finally {
202
+ deps.agentManager.releaseCancellationSignal(record.id);
203
+ deps.cancellation.release(item.id);
204
+ }
205
+ const usage = usageFromRecord(outcome.record, deps.priceUsage);
206
+ if (outcome.status === 'cancelled') {
207
+ await worktree.cleanup(record.id).catch(() => undefined);
208
+ return {
209
+ agentStatus: 'cancelled',
210
+ result: {
211
+ itemId: item.id,
212
+ phaseId: phase.id,
213
+ agentId: record.id,
214
+ report: genericReport('cancelled by operator'),
215
+ gate: { passed: false, results: [] },
216
+ startedAt,
217
+ completedAt: Date.now(),
218
+ usage,
219
+ },
220
+ };
221
+ }
222
+ if (outcome.status === 'failed') {
223
+ const transportFailure = isTransportFailureMessage(outcome.record?.error ?? '');
224
+ const retryLimit = getWrfcTransportRetryLimit(deps.configManager);
225
+ if (transportFailure && item.transportRetryCount < retryLimit) {
226
+ item.transportRetryCount += 1;
227
+ await worktree.cleanup(record.id).catch(() => undefined);
228
+ await sleep(getWrfcTransportRetryDelayMs(deps.configManager));
229
+ return runPhase(workstream, item, phase, priorReports, deps);
230
+ }
231
+ await worktree.cleanup(record.id).catch(() => undefined);
232
+ return {
233
+ agentStatus: 'failed',
234
+ result: {
235
+ itemId: item.id,
236
+ phaseId: phase.id,
237
+ agentId: record.id,
238
+ report: genericReport(outcome.record?.error ?? 'agent failed'),
239
+ gate: { passed: false, results: [] },
240
+ startedAt,
241
+ completedAt: Date.now(),
242
+ usage,
243
+ },
244
+ };
245
+ }
246
+ const report = parseCompletionReport(outcome.record?.fullOutput ?? '') ?? genericReport(outcome.record?.fullOutput ?? '');
247
+ if (report.archetype === 'engineer') {
248
+ const engineerReport = report;
249
+ for (const path of [...engineerReport.filesCreated, ...engineerReport.filesModified, ...engineerReport.filesDeleted]) {
250
+ if (!item.touchedPaths.includes(path))
251
+ item.touchedPaths.push(path);
252
+ }
253
+ }
254
+ const gate = await evaluateGate(workstream, phase, report, deps);
255
+ let commitExclusion;
256
+ if (gate.passed) {
257
+ commitExclusion = await commitPhaseWork(item, phase, record.id, worktree, deps);
258
+ }
259
+ await worktree.cleanup(record.id).catch(() => undefined);
260
+ return {
261
+ agentStatus: 'completed',
262
+ result: {
263
+ itemId: item.id,
264
+ phaseId: phase.id,
265
+ agentId: record.id,
266
+ report,
267
+ gate,
268
+ startedAt,
269
+ completedAt: Date.now(),
270
+ usage,
271
+ ...(commitExclusion ? { commitExclusion } : {}),
272
+ },
273
+ };
274
+ }
@@ -0,0 +1,45 @@
1
+ /** SDK-owned platform module. This implementation is maintained in goodvibes-sdk. */
2
+ /**
3
+ * Scheduler (Wave 4, wo701) — pure capacity-matching helpers, no side
4
+ * effects. The hard departure from WrfcController's pairwise
5
+ * engineer<->reviewer binding (startReview:883/startFix:1042): each tick,
6
+ * for every phase in ordinal order, free capacity slots (capacity minus
7
+ * in-flight) are filled from whichever waiting items are queued for that
8
+ * phase — an item advances the instant ITS gate passes, claimed by
9
+ * whatever slot happens to be free, never bound to a specific sibling item.
10
+ */
11
+ import type { Phase, WorkItem, Workstream } from './types.js';
12
+ export declare function sortedPhases(workstream: Workstream): Phase[];
13
+ export declare function firstPhase(workstream: Workstream): Phase | undefined;
14
+ /**
15
+ * The next phase in ORDINARY forward progression. Deliberately skips
16
+ * 'fix'-kind phases: a dynamically-inserted fix phase sits at an ordinal
17
+ * after its review (see engine.ts findOrInsertFixPhase) but is reachable
18
+ * ONLY via the explicit review-failure re-route, never as "what comes next"
19
+ * for an item whose review already passed — otherwise a later item that
20
+ * never needed fixing would wrongly detour through it.
21
+ */
22
+ export declare function nextPhaseAfter(workstream: Workstream, ordinal: number): Phase | undefined;
23
+ export declare function phaseById(workstream: Workstream, phaseId: string): Phase | undefined;
24
+ /** The nearest preceding review-kind phase — the return target after a dynamically-inserted fix phase's gate passes. Purely structural (survives serialization with zero extra bookkeeping). */
25
+ export declare function reviewPhaseBefore(workstream: Workstream, phase: Phase): Phase | undefined;
26
+ export interface PhaseClaim {
27
+ readonly item: WorkItem;
28
+ readonly phase: Phase;
29
+ }
30
+ /**
31
+ * Which (item, phase) pairs have free capacity to claim RIGHT NOW. Pure —
32
+ * no side effects, no budget check (the caller applies budget.checkBudget
33
+ * before actually claiming, since budget is a *decision*, not a capacity
34
+ * fact this function should own).
35
+ *
36
+ * 'blocked-budget' items are deliberately included in the waiting set, not
37
+ * just 'pending'/'awaiting-capacity': a budget block is a recoverable
38
+ * decision (see BudgetCeiling/WorkItemState docs, types.ts), never a
39
+ * capacity fact, so a previously-blocked item must be reconsidered on every
40
+ * tick the instant a slot is free — the caller's budget.checkBudget call
41
+ * re-decides it fresh each time, honestly re-blocking it if the ceiling
42
+ * still refuses.
43
+ */
44
+ export declare function computeClaims(workstream: Workstream): PhaseClaim[];
45
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../../src/platform/orchestration/scheduler.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAErF;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE9D,wBAAgB,YAAY,CAAC,UAAU,EAAE,UAAU,GAAG,KAAK,EAAE,CAE5D;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,UAAU,GAAG,KAAK,GAAG,SAAS,CAEpE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAEzF;AAED,wBAAgB,SAAS,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAEpF;AAED,gMAAgM;AAChM,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAEzF;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACvB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,EAAE,CAiBlE"}
@@ -0,0 +1,57 @@
1
+ /** SDK-owned platform module. This implementation is maintained in goodvibes-sdk. */
2
+ export function sortedPhases(workstream) {
3
+ return [...workstream.phases].sort((a, b) => a.ordinal - b.ordinal);
4
+ }
5
+ export function firstPhase(workstream) {
6
+ return sortedPhases(workstream)[0];
7
+ }
8
+ /**
9
+ * The next phase in ORDINARY forward progression. Deliberately skips
10
+ * 'fix'-kind phases: a dynamically-inserted fix phase sits at an ordinal
11
+ * after its review (see engine.ts findOrInsertFixPhase) but is reachable
12
+ * ONLY via the explicit review-failure re-route, never as "what comes next"
13
+ * for an item whose review already passed — otherwise a later item that
14
+ * never needed fixing would wrongly detour through it.
15
+ */
16
+ export function nextPhaseAfter(workstream, ordinal) {
17
+ return sortedPhases(workstream).find((p) => p.ordinal > ordinal && p.kind !== 'fix');
18
+ }
19
+ export function phaseById(workstream, phaseId) {
20
+ return workstream.phases.find((p) => p.id === phaseId);
21
+ }
22
+ /** The nearest preceding review-kind phase — the return target after a dynamically-inserted fix phase's gate passes. Purely structural (survives serialization with zero extra bookkeeping). */
23
+ export function reviewPhaseBefore(workstream, phase) {
24
+ return sortedPhases(workstream).filter((p) => p.ordinal < phase.ordinal && p.kind === 'review').pop();
25
+ }
26
+ /**
27
+ * Which (item, phase) pairs have free capacity to claim RIGHT NOW. Pure —
28
+ * no side effects, no budget check (the caller applies budget.checkBudget
29
+ * before actually claiming, since budget is a *decision*, not a capacity
30
+ * fact this function should own).
31
+ *
32
+ * 'blocked-budget' items are deliberately included in the waiting set, not
33
+ * just 'pending'/'awaiting-capacity': a budget block is a recoverable
34
+ * decision (see BudgetCeiling/WorkItemState docs, types.ts), never a
35
+ * capacity fact, so a previously-blocked item must be reconsidered on every
36
+ * tick the instant a slot is free — the caller's budget.checkBudget call
37
+ * re-decides it fresh each time, honestly re-blocking it if the ceiling
38
+ * still refuses.
39
+ */
40
+ export function computeClaims(workstream) {
41
+ const claims = [];
42
+ for (const phase of sortedPhases(workstream)) {
43
+ const inFlight = workstream.items.filter((item) => item.currentPhaseId === phase.id && item.state === 'in-phase').length;
44
+ let free = phase.capacity - inFlight;
45
+ if (free <= 0)
46
+ continue;
47
+ const waiting = workstream.items.filter((item) => item.currentPhaseId === phase.id
48
+ && (item.state === 'pending' || item.state === 'awaiting-capacity' || item.state === 'blocked-budget'));
49
+ for (const item of waiting) {
50
+ if (free <= 0)
51
+ break;
52
+ claims.push({ item, phase });
53
+ free -= 1;
54
+ }
55
+ }
56
+ return claims;
57
+ }