@pellux/goodvibes-sdk 0.37.2 → 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 (159) 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/core/adaptive-planner.d.ts +50 -0
  23. package/dist/platform/core/adaptive-planner.d.ts.map +1 -1
  24. package/dist/platform/core/adaptive-planner.js +49 -0
  25. package/dist/platform/core/index.d.ts +1 -0
  26. package/dist/platform/core/index.d.ts.map +1 -1
  27. package/dist/platform/core/index.js +1 -0
  28. package/dist/platform/core/orchestrator-runtime.d.ts +11 -0
  29. package/dist/platform/core/orchestrator-runtime.d.ts.map +1 -1
  30. package/dist/platform/core/orchestrator-turn-loop.d.ts +28 -1
  31. package/dist/platform/core/orchestrator-turn-loop.d.ts.map +1 -1
  32. package/dist/platform/core/orchestrator-turn-loop.js +115 -3
  33. package/dist/platform/core/orchestrator.d.ts +61 -0
  34. package/dist/platform/core/orchestrator.d.ts.map +1 -1
  35. package/dist/platform/core/orchestrator.js +74 -1
  36. package/dist/platform/core/plan-proposal.d.ts +165 -0
  37. package/dist/platform/core/plan-proposal.d.ts.map +1 -0
  38. package/dist/platform/core/plan-proposal.js +296 -0
  39. package/dist/platform/knowledge/knowledge-api.d.ts +26 -0
  40. package/dist/platform/knowledge/knowledge-api.d.ts.map +1 -1
  41. package/dist/platform/knowledge/knowledge-api.js +11 -0
  42. package/dist/platform/orchestration/budget.d.ts +22 -0
  43. package/dist/platform/orchestration/budget.d.ts.map +1 -0
  44. package/dist/platform/orchestration/budget.js +35 -0
  45. package/dist/platform/orchestration/cancellation.d.ts +31 -0
  46. package/dist/platform/orchestration/cancellation.d.ts.map +1 -0
  47. package/dist/platform/orchestration/cancellation.js +25 -0
  48. package/dist/platform/orchestration/controller-compat.d.ts +32 -0
  49. package/dist/platform/orchestration/controller-compat.d.ts.map +1 -0
  50. package/dist/platform/orchestration/controller-compat.js +13 -0
  51. package/dist/platform/orchestration/dirty-guard.d.ts +32 -0
  52. package/dist/platform/orchestration/dirty-guard.d.ts.map +1 -0
  53. package/dist/platform/orchestration/dirty-guard.js +147 -0
  54. package/dist/platform/orchestration/engine.d.ts +57 -0
  55. package/dist/platform/orchestration/engine.d.ts.map +1 -0
  56. package/dist/platform/orchestration/engine.js +418 -0
  57. package/dist/platform/orchestration/index.d.ts +18 -0
  58. package/dist/platform/orchestration/index.d.ts.map +1 -0
  59. package/dist/platform/orchestration/index.js +10 -0
  60. package/dist/platform/orchestration/persistence.d.ts +24 -0
  61. package/dist/platform/orchestration/persistence.d.ts.map +1 -0
  62. package/dist/platform/orchestration/persistence.js +176 -0
  63. package/dist/platform/orchestration/phase-runner.d.ts +87 -0
  64. package/dist/platform/orchestration/phase-runner.d.ts.map +1 -0
  65. package/dist/platform/orchestration/phase-runner.js +274 -0
  66. package/dist/platform/orchestration/scheduler.d.ts +45 -0
  67. package/dist/platform/orchestration/scheduler.d.ts.map +1 -0
  68. package/dist/platform/orchestration/scheduler.js +57 -0
  69. package/dist/platform/orchestration/types.d.ts +250 -0
  70. package/dist/platform/orchestration/types.d.ts.map +1 -0
  71. package/dist/platform/orchestration/types.js +15 -0
  72. package/dist/platform/runtime/emitters/communication.d.ts +11 -0
  73. package/dist/platform/runtime/emitters/communication.d.ts.map +1 -1
  74. package/dist/platform/runtime/emitters/communication.js +9 -0
  75. package/dist/platform/runtime/feature-flags/flags.d.ts.map +1 -1
  76. package/dist/platform/runtime/feature-flags/flags.js +17 -0
  77. package/dist/platform/runtime/fleet/adapters/agent.d.ts +56 -0
  78. package/dist/platform/runtime/fleet/adapters/agent.d.ts.map +1 -0
  79. package/dist/platform/runtime/fleet/adapters/agent.js +143 -0
  80. package/dist/platform/runtime/fleet/adapters/automation.d.ts +30 -0
  81. package/dist/platform/runtime/fleet/adapters/automation.d.ts.map +1 -0
  82. package/dist/platform/runtime/fleet/adapters/automation.js +41 -0
  83. package/dist/platform/runtime/fleet/adapters/background-process.d.ts +12 -0
  84. package/dist/platform/runtime/fleet/adapters/background-process.d.ts.map +1 -0
  85. package/dist/platform/runtime/fleet/adapters/background-process.js +46 -0
  86. package/dist/platform/runtime/fleet/adapters/code-index.d.ts +21 -0
  87. package/dist/platform/runtime/fleet/adapters/code-index.d.ts.map +1 -0
  88. package/dist/platform/runtime/fleet/adapters/code-index.js +44 -0
  89. package/dist/platform/runtime/fleet/adapters/orchestration.d.ts +47 -0
  90. package/dist/platform/runtime/fleet/adapters/orchestration.d.ts.map +1 -0
  91. package/dist/platform/runtime/fleet/adapters/orchestration.js +225 -0
  92. package/dist/platform/runtime/fleet/adapters/schedule.d.ts +14 -0
  93. package/dist/platform/runtime/fleet/adapters/schedule.d.ts.map +1 -0
  94. package/dist/platform/runtime/fleet/adapters/schedule.js +28 -0
  95. package/dist/platform/runtime/fleet/adapters/trigger.d.ts +12 -0
  96. package/dist/platform/runtime/fleet/adapters/trigger.d.ts.map +1 -0
  97. package/dist/platform/runtime/fleet/adapters/trigger.js +22 -0
  98. package/dist/platform/runtime/fleet/adapters/watcher.d.ts +13 -0
  99. package/dist/platform/runtime/fleet/adapters/watcher.d.ts.map +1 -0
  100. package/dist/platform/runtime/fleet/adapters/watcher.js +47 -0
  101. package/dist/platform/runtime/fleet/adapters/workflow.d.ts +11 -0
  102. package/dist/platform/runtime/fleet/adapters/workflow.d.ts.map +1 -0
  103. package/dist/platform/runtime/fleet/adapters/workflow.js +34 -0
  104. package/dist/platform/runtime/fleet/adapters/wrfc.d.ts +22 -0
  105. package/dist/platform/runtime/fleet/adapters/wrfc.d.ts.map +1 -0
  106. package/dist/platform/runtime/fleet/adapters/wrfc.js +228 -0
  107. package/dist/platform/runtime/fleet/index.d.ts +10 -0
  108. package/dist/platform/runtime/fleet/index.d.ts.map +1 -0
  109. package/dist/platform/runtime/fleet/index.js +6 -0
  110. package/dist/platform/runtime/fleet/registry.d.ts +113 -0
  111. package/dist/platform/runtime/fleet/registry.d.ts.map +1 -0
  112. package/dist/platform/runtime/fleet/registry.js +630 -0
  113. package/dist/platform/runtime/fleet/types.d.ts +199 -0
  114. package/dist/platform/runtime/fleet/types.d.ts.map +1 -0
  115. package/dist/platform/runtime/fleet/types.js +2 -0
  116. package/dist/platform/runtime/runtime-knowledge-api.d.ts +1 -1
  117. package/dist/platform/runtime/runtime-knowledge-api.d.ts.map +1 -1
  118. package/dist/platform/runtime/runtime-knowledge-api.js +1 -0
  119. package/dist/platform/runtime/services.d.ts +46 -0
  120. package/dist/platform/runtime/services.d.ts.map +1 -1
  121. package/dist/platform/runtime/services.js +78 -0
  122. package/dist/platform/state/code-index-chunking.d.ts +49 -0
  123. package/dist/platform/state/code-index-chunking.d.ts.map +1 -0
  124. package/dist/platform/state/code-index-chunking.js +111 -0
  125. package/dist/platform/state/code-index-db.d.ts +53 -0
  126. package/dist/platform/state/code-index-db.d.ts.map +1 -0
  127. package/dist/platform/state/code-index-db.js +101 -0
  128. package/dist/platform/state/code-index-store.d.ts +175 -0
  129. package/dist/platform/state/code-index-store.d.ts.map +1 -0
  130. package/dist/platform/state/code-index-store.js +612 -0
  131. package/dist/platform/state/index.d.ts +4 -2
  132. package/dist/platform/state/index.d.ts.map +1 -1
  133. package/dist/platform/state/index.js +2 -1
  134. package/dist/platform/state/knowledge-injection.d.ts +23 -0
  135. package/dist/platform/state/knowledge-injection.d.ts.map +1 -1
  136. package/dist/platform/state/knowledge-injection.js +34 -19
  137. package/dist/platform/state/memory-vector-store.d.ts +1 -13
  138. package/dist/platform/state/memory-vector-store.d.ts.map +1 -1
  139. package/dist/platform/state/memory-vector-store.js +6 -38
  140. package/dist/platform/state/sqlite-vec-loader.d.ts +25 -0
  141. package/dist/platform/state/sqlite-vec-loader.d.ts.map +1 -0
  142. package/dist/platform/state/sqlite-vec-loader.js +43 -0
  143. package/dist/platform/tools/agent/manager.d.ts +128 -1
  144. package/dist/platform/tools/agent/manager.d.ts.map +1 -1
  145. package/dist/platform/tools/agent/manager.js +137 -1
  146. package/dist/platform/tools/exec/runtime.d.ts.map +1 -1
  147. package/dist/platform/tools/exec/runtime.js +139 -44
  148. package/dist/platform/tools/exec/schema.d.ts +2 -0
  149. package/dist/platform/tools/exec/schema.d.ts.map +1 -1
  150. package/dist/platform/tools/fetch/runtime.d.ts +7 -0
  151. package/dist/platform/tools/fetch/runtime.d.ts.map +1 -1
  152. package/dist/platform/tools/fetch/runtime.js +8 -6
  153. package/dist/platform/tools/registry.d.ts +9 -3
  154. package/dist/platform/tools/registry.d.ts.map +1 -1
  155. package/dist/platform/tools/registry.js +9 -3
  156. package/dist/platform/types/tools.d.ts +14 -1
  157. package/dist/platform/types/tools.d.ts.map +1 -1
  158. package/dist/platform/version.js +1 -1
  159. package/package.json +17 -9
@@ -0,0 +1,24 @@
1
+ /** SDK-owned platform module. This implementation is maintained in goodvibes-sdk. */
2
+ import { type OrchestrationEvent, type PhaseResult, type SerializedWorkstream, type Workstream, type WorkstreamSnapshot } from './types.js';
3
+ export declare function serializeWorkstream(workstream: Workstream): SerializedWorkstream;
4
+ export declare function deserializeWorkstream(serialized: SerializedWorkstream): Workstream;
5
+ /** Mirrors WrfcController.serializeChain: JSON.stringify a schema-versioned envelope. Returns null on serialization failure rather than throwing. */
6
+ export declare function serializeWorkstreamSnapshot(workstream: Workstream, completedResults: readonly PhaseResult[]): string | null;
7
+ /**
8
+ * Mirrors WrfcController.deserializeChain's future-schemaVersion-reject
9
+ * guard: a snapshot written by a newer runtime is rejected (fail closed)
10
+ * rather than partially trusted.
11
+ */
12
+ export declare function deserializeWorkstreamSnapshot(json: string): WorkstreamSnapshot | null;
13
+ /** Read + quarantine-on-corrupt (never throws, never crashes the caller on a bad file). */
14
+ export declare function loadWorkstreamSnapshot(projectRoot: string, workstreamId: string): WorkstreamSnapshot | null;
15
+ /** List the workstream ids with a snapshot on disk (recognized or not — callers decide via loadWorkstreamSnapshot). */
16
+ export declare function listSnapshotWorkstreamIds(projectRoot: string): string[];
17
+ export declare function writeWorkstreamSnapshot(projectRoot: string, workstream: Workstream, completedResults: readonly PhaseResult[]): void;
18
+ /**
19
+ * Debounced trailing writer (250ms, exactly like wrfc-persistence.ts
20
+ * DEBOUNCE_MS), subscribing to engine lifecycle events. Returns an
21
+ * unsubscribe function that also flushes any pending timers.
22
+ */
23
+ export declare function attachDebouncedWriter(projectRoot: string, getWorkstream: (workstreamId: string) => Workstream | null, getCompletedResults: (workstreamId: string) => readonly PhaseResult[], subscribe: (listener: (event: OrchestrationEvent) => void) => () => void): () => void;
24
+ //# sourceMappingURL=persistence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../../src/platform/orchestration/persistence.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAerF,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAEhB,KAAK,oBAAoB,EAEzB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACxB,MAAM,YAAY,CAAC;AAYpB,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,UAAU,GAAG,oBAAoB,CAEhF;AAED,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,oBAAoB,GAAG,UAAU,CAElF;AAED,qJAAqJ;AACrJ,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,UAAU,EAAE,gBAAgB,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,GAAG,IAAI,CAa3H;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CA6BrF;AAUD,2FAA2F;AAC3F,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAsB3G;AAED,uHAAuH;AACvH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAWvE;AAED,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,gBAAgB,EAAE,SAAS,WAAW,EAAE,GAAG,IAAI,CAUnI;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,UAAU,GAAG,IAAI,EAC1D,mBAAmB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,SAAS,WAAW,EAAE,EACrE,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,KAAK,MAAM,IAAI,GACvE,MAAM,IAAI,CA4BZ"}
@@ -0,0 +1,176 @@
1
+ /** SDK-owned platform module. This implementation is maintained in goodvibes-sdk. */
2
+ /**
3
+ * Persistence (Wave 4, wo701) — mirrors the WrfcController chain seams
4
+ * exactly: serializeChain:323 / deserializeChain:345 (including the
5
+ * future-schemaVersion-reject guard at :364) / importChain:402. Writes to
6
+ * `.goodvibes/orchestration/<workstreamId>.json` — SEPARATE from the TUI's
7
+ * `.goodvibes/tui/wrfc-chains.json` (src/runtime/wrfc-persistence.ts), no
8
+ * path collision. Debounce (250ms) and corrupt-snapshot quarantine
9
+ * (`<path>.unrecognized`) mirror that same TUI module's conventions.
10
+ */
11
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ import { logger } from '../utils/logger.js';
14
+ import { summarizeError } from '../utils/error-display.js';
15
+ import { CURRENT_WORKSTREAM_SCHEMA_VERSION, } from './types.js';
16
+ const DEBOUNCE_MS = 250;
17
+ function serializeWorkItem(item) {
18
+ return { ...item, visits: Object.fromEntries(item.visits) };
19
+ }
20
+ function deserializeWorkItem(raw) {
21
+ return { ...raw, visits: new Map(Object.entries(raw.visits)) };
22
+ }
23
+ export function serializeWorkstream(workstream) {
24
+ return { ...workstream, items: workstream.items.map(serializeWorkItem) };
25
+ }
26
+ export function deserializeWorkstream(serialized) {
27
+ return { ...serialized, items: serialized.items.map(deserializeWorkItem) };
28
+ }
29
+ /** Mirrors WrfcController.serializeChain: JSON.stringify a schema-versioned envelope. Returns null on serialization failure rather than throwing. */
30
+ export function serializeWorkstreamSnapshot(workstream, completedResults) {
31
+ const snapshot = {
32
+ schemaVersion: CURRENT_WORKSTREAM_SCHEMA_VERSION,
33
+ writtenAt: Date.now(),
34
+ workstream: serializeWorkstream(workstream),
35
+ completedResults,
36
+ };
37
+ try {
38
+ return JSON.stringify(snapshot);
39
+ }
40
+ catch (error) {
41
+ logger.error('orchestration persistence: JSON serialization failed', { workstreamId: workstream.id, error: summarizeError(error) });
42
+ return null;
43
+ }
44
+ }
45
+ /**
46
+ * Mirrors WrfcController.deserializeChain's future-schemaVersion-reject
47
+ * guard: a snapshot written by a newer runtime is rejected (fail closed)
48
+ * rather than partially trusted.
49
+ */
50
+ export function deserializeWorkstreamSnapshot(json) {
51
+ let raw;
52
+ try {
53
+ raw = JSON.parse(json);
54
+ }
55
+ catch (error) {
56
+ logger.error('orchestration persistence: JSON parse failed', { error: summarizeError(error) });
57
+ return null;
58
+ }
59
+ if (raw === null || typeof raw !== 'object') {
60
+ logger.warn('orchestration persistence: invalid snapshot JSON — not an object');
61
+ return null;
62
+ }
63
+ const candidate = raw;
64
+ if (typeof candidate.schemaVersion !== 'number') {
65
+ logger.warn('orchestration persistence: invalid snapshot JSON — missing schemaVersion');
66
+ return null;
67
+ }
68
+ if (candidate.schemaVersion > CURRENT_WORKSTREAM_SCHEMA_VERSION) {
69
+ logger.error('orchestration persistence: future schemaVersion rejected — upgrade runtime to read this snapshot', {
70
+ schemaVersion: candidate.schemaVersion,
71
+ supportedVersion: CURRENT_WORKSTREAM_SCHEMA_VERSION,
72
+ });
73
+ return null;
74
+ }
75
+ if (!candidate.workstream || typeof candidate.workstream !== 'object' || !Array.isArray(candidate.completedResults)) {
76
+ logger.warn('orchestration persistence: invalid snapshot JSON — missing workstream/completedResults');
77
+ return null;
78
+ }
79
+ return candidate;
80
+ }
81
+ function orchestrationDir(projectRoot) {
82
+ return join(projectRoot, '.goodvibes', 'orchestration');
83
+ }
84
+ function snapshotPath(projectRoot, workstreamId) {
85
+ return join(orchestrationDir(projectRoot), `${workstreamId}.json`);
86
+ }
87
+ /** Read + quarantine-on-corrupt (never throws, never crashes the caller on a bad file). */
88
+ export function loadWorkstreamSnapshot(projectRoot, workstreamId) {
89
+ const path = snapshotPath(projectRoot, workstreamId);
90
+ if (!existsSync(path))
91
+ return null;
92
+ let text;
93
+ try {
94
+ text = readFileSync(path, 'utf-8');
95
+ }
96
+ catch (error) {
97
+ logger.warn('orchestration persistence: snapshot read failed', { path, error: summarizeError(error) });
98
+ return null;
99
+ }
100
+ const snapshot = deserializeWorkstreamSnapshot(text);
101
+ if (snapshot === null) {
102
+ const quarantinePath = `${path}.unrecognized`;
103
+ try {
104
+ renameSync(path, quarantinePath);
105
+ logger.warn('orchestration persistence: quarantined unrecognized snapshot', { path, quarantinePath });
106
+ }
107
+ catch (error) {
108
+ logger.error('orchestration persistence: failed to quarantine unrecognized snapshot', { path, error: summarizeError(error) });
109
+ }
110
+ return null;
111
+ }
112
+ return snapshot;
113
+ }
114
+ /** List the workstream ids with a snapshot on disk (recognized or not — callers decide via loadWorkstreamSnapshot). */
115
+ export function listSnapshotWorkstreamIds(projectRoot) {
116
+ const dir = orchestrationDir(projectRoot);
117
+ if (!existsSync(dir))
118
+ return [];
119
+ try {
120
+ return readdirSync(dir)
121
+ .filter((entry) => entry.endsWith('.json'))
122
+ .map((entry) => entry.slice(0, -'.json'.length));
123
+ }
124
+ catch (error) {
125
+ logger.warn('orchestration persistence: failed to list snapshot directory', { dir, error: summarizeError(error) });
126
+ return [];
127
+ }
128
+ }
129
+ export function writeWorkstreamSnapshot(projectRoot, workstream, completedResults) {
130
+ const json = serializeWorkstreamSnapshot(workstream, completedResults);
131
+ if (json === null)
132
+ return;
133
+ const path = snapshotPath(projectRoot, workstream.id);
134
+ try {
135
+ mkdirSync(orchestrationDir(projectRoot), { recursive: true });
136
+ writeFileSync(path, json, 'utf-8');
137
+ }
138
+ catch (error) {
139
+ logger.error('orchestration persistence: snapshot write failed', { path, error: summarizeError(error) });
140
+ }
141
+ }
142
+ /**
143
+ * Debounced trailing writer (250ms, exactly like wrfc-persistence.ts
144
+ * DEBOUNCE_MS), subscribing to engine lifecycle events. Returns an
145
+ * unsubscribe function that also flushes any pending timers.
146
+ */
147
+ export function attachDebouncedWriter(projectRoot, getWorkstream, getCompletedResults, subscribe) {
148
+ const timers = new Map();
149
+ function scheduleWrite(workstreamId) {
150
+ const existing = timers.get(workstreamId);
151
+ if (existing)
152
+ clearTimeout(existing);
153
+ const timer = setTimeout(() => {
154
+ timers.delete(workstreamId);
155
+ const workstream = getWorkstream(workstreamId);
156
+ if (!workstream)
157
+ return;
158
+ writeWorkstreamSnapshot(projectRoot, workstream, getCompletedResults(workstreamId));
159
+ }, DEBOUNCE_MS);
160
+ timer.unref?.();
161
+ timers.set(workstreamId, timer);
162
+ }
163
+ const unsubscribe = subscribe((event) => {
164
+ // 'dirty-tree-at-launch' (Wave 6, wo-F item 4) is engine-wide, not
165
+ // workstream-scoped — it has no workstreamId to schedule a write for.
166
+ if (event.type === 'dirty-tree-at-launch')
167
+ return;
168
+ scheduleWrite(event.workstreamId);
169
+ });
170
+ return () => {
171
+ unsubscribe();
172
+ for (const timer of timers.values())
173
+ clearTimeout(timer);
174
+ timers.clear();
175
+ };
176
+ }
@@ -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"}