@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,31 @@
1
+ /** SDK-owned platform module. This implementation is maintained in goodvibes-sdk. */
2
+ /**
3
+ * Per-work-item cancellation registry (Wave 4, wo701) — the deferred W0.1
4
+ * cooperative-cancellation piece.
5
+ *
6
+ * One AbortController per in-flight WorkItem. `engine.kill(itemId)` aborts
7
+ * the controller here AND calls `agentManager.cancel(agentId, 'kill')` — the
8
+ * same mechanism every agent in the system already uses to stop at its next
9
+ * turn boundary (orchestrator-runner.ts's `record.status === 'cancelled'`
10
+ * poll). That covers "no orphaned AGENT" unconditionally.
11
+ *
12
+ * The AbortSignal this registry hands out is ALSO registered on AgentManager
13
+ * (registerCancellationSignal) so opted-in tools (exec, fetch) can abort an
14
+ * in-flight child process/request immediately instead of waiting for that
15
+ * turn boundary — see AgentManager.getCancellationSignal /
16
+ * AgentOrchestrator.setCancellationSource / orchestrator-runner.ts's
17
+ * `context.getCancellationSignal?.(record.id)` threaded into
18
+ * `toolRegistry.execute` opts.
19
+ */
20
+ export interface CancellationRegistry {
21
+ /** Create (replacing any prior registration) the controller for a work item and return its signal. */
22
+ start(itemId: string): AbortSignal;
23
+ /** Abort the item's controller, if one is registered. Returns false when nothing was registered (already terminal, or never started). */
24
+ abort(itemId: string): boolean;
25
+ /** Drop the registration once the item's phase run has ended (success, failure, or cancel). Safe to call unconditionally. */
26
+ release(itemId: string): void;
27
+ /** True when this item currently has a live (non-aborted) registration. */
28
+ isActive(itemId: string): boolean;
29
+ }
30
+ export declare function createCancellationRegistry(): CancellationRegistry;
31
+ //# sourceMappingURL=cancellation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cancellation.d.ts","sourceRoot":"","sources":["../../../src/platform/orchestration/cancellation.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAErF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,oBAAoB;IACnC,sGAAsG;IACtG,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC;IACnC,yIAAyI;IACzI,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,6HAA6H;IAC7H,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;CACnC;AAED,wBAAgB,0BAA0B,IAAI,oBAAoB,CA2BjE"}
@@ -0,0 +1,25 @@
1
+ /** SDK-owned platform module. This implementation is maintained in goodvibes-sdk. */
2
+ export function createCancellationRegistry() {
3
+ const controllers = new Map();
4
+ function start(itemId) {
5
+ controllers.get(itemId)?.abort();
6
+ const controller = new AbortController();
7
+ controllers.set(itemId, controller);
8
+ return controller.signal;
9
+ }
10
+ function abort(itemId) {
11
+ const controller = controllers.get(itemId);
12
+ if (!controller)
13
+ return false;
14
+ controller.abort();
15
+ return true;
16
+ }
17
+ function release(itemId) {
18
+ controllers.delete(itemId);
19
+ }
20
+ function isActive(itemId) {
21
+ const controller = controllers.get(itemId);
22
+ return controller !== undefined && !controller.signal.aborted;
23
+ }
24
+ return { start, abort, release, isActive };
25
+ }
@@ -0,0 +1,32 @@
1
+ /** SDK-owned platform module. This implementation is maintained in goodvibes-sdk. */
2
+ /**
3
+ * Controller-compat (Wave 4, wo701) — stage 1 of the 3-stage WrfcController
4
+ * migration (see design doc (g) and the work-order report's design-
5
+ * divergences section).
6
+ *
7
+ * `fromChainSpec()` produces the canned two-phase engineer->review
8
+ * WORKSTREAM SPEC that a `WrfcController.createChain(ownerRecord)` call
9
+ * would otherwise start — a NEW opt-in surface for callers that want the
10
+ * engine-backed pipeline. It returns a `CreateWorkstreamInput` (a spec), not
11
+ * a live `Workstream`: realizing one (generated ids, initialized item
12
+ * state) is the engine's own responsibility via
13
+ * `engine.createWorkstream(fromChainSpec(...))`, mirroring how the type
14
+ * model already separates PhaseSpec/WorkItemSpec (input) from Phase/WorkItem
15
+ * (engine-owned state).
16
+ *
17
+ * STAGE 1 (this wave, unchanged): WrfcController itself is NOT touched.
18
+ * spawn-forced-wrfc, /teamwork, and archetypes all keep hitting
19
+ * WrfcController.createChain via AgentManager.manager.ts:565 exactly as
20
+ * before. This function is additive only.
21
+ * STAGE 2 (later, feature-flagged): WrfcController.createChain internally
22
+ * builds a workstream via this same helper and delegates execution to the
23
+ * engine — the public seam (createChain's signature and the three entry
24
+ * points) stays byte-for-byte unchanged.
25
+ * STAGE 3 (future wave): retire the standalone WrfcController state machine
26
+ * once parity is proven.
27
+ */
28
+ import type { AgentRecord } from '../tools/agent/manager.js';
29
+ import type { ConfigManager } from '../config/manager.js';
30
+ import type { CreateWorkstreamInput } from './engine.js';
31
+ export declare function fromChainSpec(ownerRecord: Pick<AgentRecord, 'id' | 'task'>, configManager: Pick<ConfigManager, 'get' | 'getCategory'>): CreateWorkstreamInput;
32
+ //# sourceMappingURL=controller-compat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller-compat.d.ts","sourceRoot":"","sources":["../../../src/platform/orchestration/controller-compat.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAErF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,wBAAgB,aAAa,CAC3B,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,MAAM,CAAC,EAC7C,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,KAAK,GAAG,aAAa,CAAC,GACxD,qBAAqB,CAUvB"}
@@ -0,0 +1,13 @@
1
+ /** SDK-owned platform module. This implementation is maintained in goodvibes-sdk. */
2
+ import { getWrfcCommitScope } from '../agents/wrfc-config.js';
3
+ export function fromChainSpec(ownerRecord, configManager) {
4
+ const commitScope = getWrfcCommitScope(configManager);
5
+ return {
6
+ title: ownerRecord.task,
7
+ items: [{ id: ownerRecord.id, title: ownerRecord.task, task: ownerRecord.task }],
8
+ phases: [
9
+ { role: 'engineer', capacity: 1, kind: 'engineer', gate: { scope: commitScope, gates: [] } },
10
+ { role: 'reviewer', capacity: 1, kind: 'review', gate: { scope: commitScope, gates: [] } },
11
+ ],
12
+ };
13
+ }
@@ -0,0 +1,32 @@
1
+ /** SDK-owned platform module. This implementation is maintained in goodvibes-sdk. */
2
+ /** Content hash of a file's current working-tree bytes. Binary-safe (raw bytes, not text). Returns null when the path does not exist on disk (a working-tree deletion). */
3
+ export declare function hashWorkingTreeFile(cwd: string, relativePath: string): string | null;
4
+ /** path -> content hash at launch (null = the dirty path did not exist on disk, e.g. an unstaged deletion). */
5
+ export type DirtyLaunchSnapshot = ReadonlyMap<string, string | null>;
6
+ /**
7
+ * Snapshot every dirty path (staged + unstaged + untracked) in `cwd`'s
8
+ * working tree right now, each with a content hash. Call once at engine
9
+ * launch, before any phase of this run has had a chance to touch anything.
10
+ * Never throws: a git failure (e.g. `cwd` is not a repo) degrades to an
11
+ * empty snapshot, which makes the exclusion guard a no-op (today's
12
+ * behavior) rather than blocking commits.
13
+ */
14
+ export declare function snapshotDirtyTree(cwd: string): DirtyLaunchSnapshot;
15
+ /** Result of partitioning a scoped commit's candidate paths against the launch snapshot. */
16
+ export interface ScopedCommitExclusion {
17
+ readonly included: readonly string[];
18
+ readonly excluded: readonly string[];
19
+ }
20
+ /**
21
+ * Partition `candidatePaths` into paths safe to include in a scoped commit
22
+ * vs. paths that must be excluded because they are untouched launch-dirty
23
+ * residue. A path is excluded only when BOTH:
24
+ * - it appears in `launchSnapshot` (it was already dirty before this run), AND
25
+ * - its current content hash equals the hash recorded at launch (this run
26
+ * never actually changed it).
27
+ * Everything else is included — a path absent from the snapshot was clean
28
+ * at launch (this run is what dirtied it), and a path whose hash changed
29
+ * was genuinely touched by this run even though it started out dirty.
30
+ */
31
+ export declare function excludeUntouchedLaunchResidue(cwd: string, candidatePaths: readonly string[], launchSnapshot: DirtyLaunchSnapshot): ScopedCommitExclusion;
32
+ //# sourceMappingURL=dirty-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dirty-guard.d.ts","sourceRoot":"","sources":["../../../src/platform/orchestration/dirty-guard.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAsCrF,2KAA2K;AAC3K,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYpF;AAED,+GAA+G;AAC/G,MAAM,MAAM,mBAAmB,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;AAyCrE;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAalE;AAED,4FAA4F;AAC5F,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,cAAc,EAAE,mBAAmB,GAClC,qBAAqB,CAiBvB"}
@@ -0,0 +1,147 @@
1
+ /** SDK-owned platform module. This implementation is maintained in goodvibes-sdk. */
2
+ /**
3
+ * Dirty-residue guard (Wave 6, wo-F item 4) — prevents an orchestration
4
+ * engine run's scoped commit from sweeping in uncommitted changes that were
5
+ * ALREADY sitting in the working tree before this engine launched (typically
6
+ * residue left behind by a previously killed run sharing the same
7
+ * `projectRoot`, since phase-runner.ts's WorkItem.touchedPaths accumulates
8
+ * across phases/retries and never resets — see phase-runner.ts runPhase).
9
+ *
10
+ * Mechanism: snapshot every dirty path + a binary-safe content hash at
11
+ * engine-launch time (`snapshotDirtyTree`). At scoped-commit time, a
12
+ * candidate path is excluded only when it was ALREADY dirty at launch AND
13
+ * its content hash is unchanged since launch — i.e. this run never touched
14
+ * it. A path that was clean at launch, or whose hash changed, is included:
15
+ * this run is the one that dirtied (or re-dirtied) it.
16
+ *
17
+ * `snapshotDirtyTree` is deliberately SYNCHRONOUS (Bun.spawnSync, mirroring
18
+ * GitService's own sync helpers — initRepo/isGitRepo/getRepoRoot,
19
+ * platform/git/service.ts) rather than shelling out through simple-git's
20
+ * async API: the engine factory (engine.ts) is a synchronous function that
21
+ * must have a settled snapshot before its first phase can possibly reach a
22
+ * commit, and a promise backed by real child-process I/O cannot be relied
23
+ * on to resolve within a caller's microtask-only wait (subprocess exit is
24
+ * delivered via the event loop's macrotask phases, not microtasks) — a real
25
+ * regression seen against tests that only drain microtasks. One blocking
26
+ * `git status` call at launch (paid once, exactly like GitService.initRepo)
27
+ * avoids that whole class of timing hazard entirely.
28
+ */
29
+ import { createHash } from 'node:crypto';
30
+ import { readFileSync, existsSync } from 'node:fs';
31
+ import { join } from 'node:path';
32
+ import { logger } from '../utils/logger.js';
33
+ import { summarizeError } from '../utils/error-display.js';
34
+ /** Dirty paths under this prefix are GoodVibes' own internal state, never a candidate for a scoped commit — excluded from the snapshot so they never pollute the 'dirty-tree-at-launch' event. */
35
+ const INTERNAL_STATE_PREFIX = '.goodvibes/';
36
+ /** Content hash of a file's current working-tree bytes. Binary-safe (raw bytes, not text). Returns null when the path does not exist on disk (a working-tree deletion). */
37
+ export function hashWorkingTreeFile(cwd, relativePath) {
38
+ const absolutePath = join(cwd, relativePath);
39
+ if (!existsSync(absolutePath))
40
+ return null;
41
+ try {
42
+ return createHash('sha256').update(readFileSync(absolutePath)).digest('hex');
43
+ }
44
+ catch (error) {
45
+ logger.warn('orchestration dirty-guard: failed to hash working-tree file', {
46
+ path: relativePath,
47
+ error: summarizeError(error),
48
+ });
49
+ return null;
50
+ }
51
+ }
52
+ /**
53
+ * Parse `git status --porcelain -z --untracked-files=all` output into a set of
54
+ * paths. The `-z` (NUL-terminated) format is used deliberately because it emits
55
+ * paths VERBATIM: git's default porcelain C-quotes any path containing
56
+ * non-ASCII or special bytes (core.quotePath defaults to true), rendering
57
+ * `café.txt` as `"caf\303\251.txt"`. Keying the launch snapshot on that quoted
58
+ * string while excludeUntouchedLaunchResidue / hashWorkingTreeFile receive the
59
+ * RAW path would silently bypass exclusion (residue leaks into scoped commits)
60
+ * and make the content hash read the wrong (nonexistent, quoted) filename. With
61
+ * `-z` there is no quoting, so snapshot keys match the raw paths used
62
+ * everywhere else.
63
+ *
64
+ * Record shape: each NUL-terminated record is `XY<space><path>`. A rename or
65
+ * copy (X or Y is 'R'/'C') is split across TWO NUL-terminated fields — the
66
+ * destination first, then the origin. We keep the destination (the actual
67
+ * scoped-commit candidate, already in this record) and consume the following
68
+ * origin field so it is never mis-parsed as its own dirty path.
69
+ */
70
+ function parsePorcelainPaths(output) {
71
+ const paths = new Set();
72
+ const records = output.split('\0');
73
+ for (let i = 0; i < records.length; i++) {
74
+ const record = records[i];
75
+ // Skips the trailing empty field left by the final NUL, plus any record too
76
+ // short to hold a 2-char status pair + separator + a 1-char path.
77
+ if (record.length < 4)
78
+ continue;
79
+ const x = record[0];
80
+ const y = record[1];
81
+ // A rename/copy record is followed by a second NUL-separated field (the
82
+ // origin path). Consume it so the next iteration does not treat it as a
83
+ // standalone status record — done before any early-continue below.
84
+ if (x === 'R' || y === 'R' || x === 'C' || y === 'C')
85
+ i++;
86
+ const path = record.slice(3);
87
+ if (path.startsWith(INTERNAL_STATE_PREFIX))
88
+ continue;
89
+ paths.add(path);
90
+ }
91
+ return paths;
92
+ }
93
+ /**
94
+ * Snapshot every dirty path (staged + unstaged + untracked) in `cwd`'s
95
+ * working tree right now, each with a content hash. Call once at engine
96
+ * launch, before any phase of this run has had a chance to touch anything.
97
+ * Never throws: a git failure (e.g. `cwd` is not a repo) degrades to an
98
+ * empty snapshot, which makes the exclusion guard a no-op (today's
99
+ * behavior) rather than blocking commits.
100
+ */
101
+ export function snapshotDirtyTree(cwd) {
102
+ try {
103
+ const result = Bun.spawnSync(['git', '-C', cwd, 'status', '--porcelain', '-z', '--untracked-files=all']);
104
+ if (result.exitCode !== 0)
105
+ return new Map();
106
+ const output = new TextDecoder().decode(result.stdout);
107
+ const paths = parsePorcelainPaths(output);
108
+ const snapshot = new Map();
109
+ for (const path of paths)
110
+ snapshot.set(path, hashWorkingTreeFile(cwd, path));
111
+ return snapshot;
112
+ }
113
+ catch (error) {
114
+ logger.warn('orchestration dirty-guard: failed to snapshot dirty tree at launch', { error: summarizeError(error) });
115
+ return new Map();
116
+ }
117
+ }
118
+ /**
119
+ * Partition `candidatePaths` into paths safe to include in a scoped commit
120
+ * vs. paths that must be excluded because they are untouched launch-dirty
121
+ * residue. A path is excluded only when BOTH:
122
+ * - it appears in `launchSnapshot` (it was already dirty before this run), AND
123
+ * - its current content hash equals the hash recorded at launch (this run
124
+ * never actually changed it).
125
+ * Everything else is included — a path absent from the snapshot was clean
126
+ * at launch (this run is what dirtied it), and a path whose hash changed
127
+ * was genuinely touched by this run even though it started out dirty.
128
+ */
129
+ export function excludeUntouchedLaunchResidue(cwd, candidatePaths, launchSnapshot) {
130
+ const included = [];
131
+ const excluded = [];
132
+ for (const path of candidatePaths) {
133
+ if (!launchSnapshot.has(path)) {
134
+ included.push(path);
135
+ continue;
136
+ }
137
+ const hashAtLaunch = launchSnapshot.get(path) ?? null;
138
+ const hashNow = hashWorkingTreeFile(cwd, path);
139
+ if (hashAtLaunch === hashNow) {
140
+ excluded.push(path);
141
+ }
142
+ else {
143
+ included.push(path);
144
+ }
145
+ }
146
+ return { included, excluded };
147
+ }
@@ -0,0 +1,57 @@
1
+ /** SDK-owned platform module. This implementation is maintained in goodvibes-sdk. */
2
+ import type { PhaseRunnerAgentManagerLike, WrfcWorktreeOps } from './phase-runner.js';
3
+ import { type BudgetCeiling, type OrchestrationEventListener, type Phase, type PhaseResult, type PhaseSpec, type WorkItemSpec, type WorkItemUsage, type Workstream } from './types.js';
4
+ import type { ConfigManager } from '../config/manager.js';
5
+ import type { RuntimeEventBus } from '../runtime/events/index.js';
6
+ export interface OrchestrationEngineDeps {
7
+ readonly agentManager: PhaseRunnerAgentManagerLike;
8
+ readonly configManager: Pick<ConfigManager, 'get' | 'getCategory'>;
9
+ readonly runtimeBus: RuntimeEventBus;
10
+ readonly projectRoot: string;
11
+ readonly sessionId?: string | undefined;
12
+ readonly createWorktree?: (() => WrfcWorktreeOps) | undefined;
13
+ readonly priceUsage?: ((model: string | undefined, usage: WorkItemUsage) => number | null) | undefined;
14
+ readonly skipClaimVerification?: boolean | undefined;
15
+ /** Bounds re-review cycles through a dynamically-inserted fix phase. Default 5 (mirrors WrfcController's default maxFixAttempts). */
16
+ readonly maxPhaseVisits?: number | undefined;
17
+ readonly now?: (() => number) | undefined;
18
+ /** Set false to skip wiring the debounced disk writer (tests that don't want filesystem side effects). Default true. */
19
+ readonly persist?: boolean | undefined;
20
+ }
21
+ export interface CreateWorkstreamInput {
22
+ readonly id?: string | undefined;
23
+ readonly title: string;
24
+ readonly phases: readonly PhaseSpec[];
25
+ readonly items: readonly WorkItemSpec[];
26
+ readonly budget?: BudgetCeiling | undefined;
27
+ }
28
+ export interface OrchestrationEngine {
29
+ createWorkstream(input: CreateWorkstreamInput): Workstream;
30
+ getWorkstream(id: string): Workstream | null;
31
+ listWorkstreams(): Workstream[];
32
+ insertPhase(workstreamId: string, afterOrdinal: number, spec: PhaseSpec): Phase | null;
33
+ /** Begin (or resume ticking) a workstream's pipeline. Idempotent — safe to call on an already-running workstream. */
34
+ start(workstreamId: string): void;
35
+ /** Abort a work item's in-flight agent (if any) and mark it terminally failed. Never affects sibling items. */
36
+ kill(itemId: string): boolean;
37
+ /**
38
+ * Replace (or clear, via `undefined`) a workstream's budget ceiling and
39
+ * immediately re-tick it, so any item already sitting in 'blocked-budget'
40
+ * gets reconsidered right away — the only recovery path for that state
41
+ * (see WorkItemState's 'blocked-budget' doc, types.ts). Returns false if
42
+ * the workstream doesn't exist.
43
+ */
44
+ updateBudget(workstreamId: string, ceiling: BudgetCeiling | undefined): boolean;
45
+ getPhaseResults(workstreamId: string): readonly PhaseResult[];
46
+ serializeWorkstream(workstreamId: string): string | null;
47
+ /** Import a serialized snapshot (a full WorkstreamSnapshot JSON string, see persistence.ts). Refuses to overwrite a non-terminal in-memory workstream unless force=true. */
48
+ importWorkstream(snapshotJson: string, force?: boolean): boolean;
49
+ /** Import + start from the on-disk snapshot for one workstream id. */
50
+ resumeWorkstream(workstreamId: string): boolean;
51
+ /** Import + start every snapshot under .goodvibes/orchestration/. Returns the count resumed. */
52
+ resumeAllFromDisk(): number;
53
+ on(listener: OrchestrationEventListener): () => void;
54
+ dispose(): void;
55
+ }
56
+ export declare function createOrchestrationEngine(deps: OrchestrationEngineDeps): OrchestrationEngine;
57
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/platform/orchestration/engine.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAkBrF,OAAO,KAAK,EAAE,2BAA2B,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAYtF,OAAO,EAGL,KAAK,aAAa,EAElB,KAAK,0BAA0B,EAC/B,KAAK,KAAK,EACV,KAAK,WAAW,EAChB,KAAK,SAAS,EAEd,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,UAAU,EAChB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAIlE,MAAM,WAAW,uBAAuB;IACtC,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,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,MAAM,eAAe,CAAC,GAAG,SAAS,CAAC;IAC9D,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,qIAAqI;IACrI,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;IAC1C,wHAAwH;IACxH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CAC7C;AAED,MAAM,WAAW,mBAAmB;IAClC,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,UAAU,CAAC;IAC3D,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IAC7C,eAAe,IAAI,UAAU,EAAE,CAAC;IAChC,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,KAAK,GAAG,IAAI,CAAC;IACvF,qHAAqH;IACrH,KAAK,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,+GAA+G;IAC/G,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B;;;;;;OAMG;IACH,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,SAAS,GAAG,OAAO,CAAC;IAChF,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,WAAW,EAAE,CAAC;IAC9D,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACzD,4KAA4K;IAC5K,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IACjE,sEAAsE;IACtE,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;IAChD,gGAAgG;IAChG,iBAAiB,IAAI,MAAM,CAAC;IAC5B,EAAE,CAAC,QAAQ,EAAE,0BAA0B,GAAG,MAAM,IAAI,CAAC;IACrD,OAAO,IAAI,IAAI,CAAC;CACjB;AAMD,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,uBAAuB,GAAG,mBAAmB,CAga5F"}