@exaudeus/workrail 3.41.0 → 3.43.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 (63) hide show
  1. package/dist/cli-worktrain.js +40 -11
  2. package/dist/console-ui/assets/{index-CQt4UhPB.js → index-Sb57DW4B.js} +1 -1
  3. package/dist/console-ui/index.html +1 -1
  4. package/dist/context-assembly/deps.d.ts +8 -0
  5. package/dist/context-assembly/deps.js +2 -0
  6. package/dist/context-assembly/index.d.ts +6 -0
  7. package/dist/context-assembly/index.js +50 -0
  8. package/dist/context-assembly/infra.d.ts +3 -0
  9. package/dist/context-assembly/infra.js +154 -0
  10. package/dist/context-assembly/types.d.ts +30 -0
  11. package/dist/context-assembly/types.js +2 -0
  12. package/dist/coordinators/pr-review.d.ts +3 -1
  13. package/dist/coordinators/pr-review.js +25 -4
  14. package/dist/daemon/workflow-runner.d.ts +11 -1
  15. package/dist/daemon/workflow-runner.js +82 -9
  16. package/dist/domain/execution/state.d.ts +6 -6
  17. package/dist/manifest.json +76 -44
  18. package/dist/mcp/handlers/v2-workflow.d.ts +2 -2
  19. package/dist/mcp/output-schemas.d.ts +234 -234
  20. package/dist/mcp/tools.d.ts +2 -2
  21. package/dist/mcp/v2/tools.d.ts +24 -24
  22. package/dist/trigger/delivery-action.d.ts +2 -0
  23. package/dist/trigger/delivery-action.js +24 -0
  24. package/dist/trigger/trigger-router.js +24 -1
  25. package/dist/trigger/trigger-store.js +42 -0
  26. package/dist/trigger/types.d.ts +3 -0
  27. package/dist/v2/durable-core/schemas/artifacts/assessment.d.ts +2 -2
  28. package/dist/v2/durable-core/schemas/artifacts/coordinator-signal.d.ts +2 -2
  29. package/dist/v2/durable-core/schemas/artifacts/loop-control.d.ts +6 -6
  30. package/dist/v2/durable-core/schemas/artifacts/review-verdict.d.ts +6 -6
  31. package/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +56 -56
  32. package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.d.ts +83 -83
  33. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +1024 -1024
  34. package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +2336 -2336
  35. package/dist/v2/durable-core/schemas/session/dag-topology.d.ts +6 -6
  36. package/dist/v2/durable-core/schemas/session/events.d.ts +339 -339
  37. package/dist/v2/durable-core/schemas/session/gaps.d.ts +30 -30
  38. package/dist/v2/durable-core/schemas/session/manifest.d.ts +6 -6
  39. package/dist/v2/durable-core/schemas/session/outputs.d.ts +8 -8
  40. package/dist/v2/durable-core/schemas/session/validation-event.d.ts +3 -3
  41. package/docs/design/adaptive-coordinator-context-candidates.md +265 -0
  42. package/docs/design/adaptive-coordinator-context-review.md +101 -0
  43. package/docs/design/adaptive-coordinator-context.md +504 -0
  44. package/docs/design/adaptive-coordinator-routing-candidates.md +340 -0
  45. package/docs/design/adaptive-coordinator-routing-design-review.md +135 -0
  46. package/docs/design/adaptive-coordinator-routing-review.md +156 -0
  47. package/docs/design/adaptive-coordinator-routing.md +660 -0
  48. package/docs/design/context-assembly-design-candidates.md +199 -0
  49. package/docs/design/context-assembly-implementation-plan.md +211 -0
  50. package/docs/design/context-assembly-layer-design-review.md +110 -0
  51. package/docs/design/context-assembly-layer.md +622 -0
  52. package/docs/design/context-assembly-review-findings.md +112 -0
  53. package/docs/design/stuck-escalation-candidates.md +176 -0
  54. package/docs/design/stuck-escalation-design-review.md +70 -0
  55. package/docs/design/stuck-escalation.md +326 -0
  56. package/docs/design/worktrain-task-queue-candidates.md +252 -0
  57. package/docs/design/worktrain-task-queue-design-review.md +109 -0
  58. package/docs/design/worktrain-task-queue.md +443 -0
  59. package/docs/design/worktree-review-findings-candidates.md +101 -0
  60. package/docs/design/worktree-review-findings-design-review.md +65 -0
  61. package/docs/design/worktree-review-findings-implementation-plan.md +153 -0
  62. package/docs/ideas/backlog.md +212 -0
  63. package/package.json +3 -3
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createListRecentSessions = createListRecentSessions;
37
+ const fs = __importStar(require("node:fs/promises"));
38
+ const nodePath = __importStar(require("node:path"));
39
+ const node_crypto_1 = require("node:crypto");
40
+ const neverthrow_1 = require("neverthrow");
41
+ const result_js_1 = require("../runtime/result.js");
42
+ const index_js_1 = require("../v2/infra/local/session-summary-provider/index.js");
43
+ const index_js_2 = require("../v2/infra/local/data-dir/index.js");
44
+ const index_js_3 = require("../v2/infra/local/directory-listing/index.js");
45
+ const index_js_4 = require("../v2/infra/local/session-store/index.js");
46
+ const index_js_5 = require("../v2/infra/local/sha256/index.js");
47
+ const WRITE_NOT_SUPPORTED = 'context-assembly: write ops not supported in read-only session store';
48
+ function makeReadOnlyFsPort() {
49
+ return {
50
+ readFileUtf8(filePath) {
51
+ return (0, neverthrow_1.fromPromise)(fs.readFile(filePath, 'utf-8'), (e) => {
52
+ const nodeErr = e;
53
+ if (nodeErr.code === 'ENOENT')
54
+ return { code: 'FS_NOT_FOUND', message: nodeErr.message ?? 'not found' };
55
+ return { code: 'FS_IO_ERROR', message: nodeErr.message ?? String(e) };
56
+ });
57
+ },
58
+ readFileBytes(filePath) {
59
+ return (0, neverthrow_1.fromPromise)(fs.readFile(filePath).then((buf) => new Uint8Array(buf)), (e) => {
60
+ const nodeErr = e;
61
+ if (nodeErr.code === 'ENOENT')
62
+ return { code: 'FS_NOT_FOUND', message: nodeErr.message ?? 'not found' };
63
+ return { code: 'FS_IO_ERROR', message: nodeErr.message ?? String(e) };
64
+ });
65
+ },
66
+ stat(_filePath) {
67
+ throw new Error(WRITE_NOT_SUPPORTED);
68
+ },
69
+ mkdirp(_dirPath) { throw new Error(WRITE_NOT_SUPPORTED); },
70
+ fsyncDir(_dirPath) { throw new Error(WRITE_NOT_SUPPORTED); },
71
+ openWriteTruncate(_filePath) { throw new Error(WRITE_NOT_SUPPORTED); },
72
+ openAppend(_filePath) { throw new Error(WRITE_NOT_SUPPORTED); },
73
+ openExclusive(_filePath, _bytes) { throw new Error(WRITE_NOT_SUPPORTED); },
74
+ writeAll(_fd, _bytes) { throw new Error(WRITE_NOT_SUPPORTED); },
75
+ fsyncFile(_fd) { throw new Error(WRITE_NOT_SUPPORTED); },
76
+ closeFile(_fd) { throw new Error(WRITE_NOT_SUPPORTED); },
77
+ rename(_from, _to) { throw new Error(WRITE_NOT_SUPPORTED); },
78
+ unlink(_filePath) { throw new Error(WRITE_NOT_SUPPORTED); },
79
+ writeFileBytes(_filePath, _bytes) { throw new Error(WRITE_NOT_SUPPORTED); },
80
+ readdir(_dirPath) { throw new Error(WRITE_NOT_SUPPORTED); },
81
+ readdirWithMtime(_dirPath) { throw new Error(WRITE_NOT_SUPPORTED); },
82
+ };
83
+ }
84
+ function makeDirectoryListingOpsPort() {
85
+ return {
86
+ readdir(dirPath) {
87
+ return (0, neverthrow_1.fromPromise)(fs.readdir(dirPath), (e) => {
88
+ const nodeErr = e;
89
+ if (nodeErr.code === 'ENOENT')
90
+ return { code: 'FS_NOT_FOUND', message: nodeErr.message ?? 'not found' };
91
+ return { code: 'FS_IO_ERROR', message: nodeErr.message ?? String(e) };
92
+ });
93
+ },
94
+ readdirWithMtime(dirPath) {
95
+ return (0, neverthrow_1.fromPromise)((async () => {
96
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
97
+ const withMtimes = await Promise.all(entries.map(async (entry) => {
98
+ try {
99
+ const stat = await fs.stat(nodePath.join(dirPath, entry.name));
100
+ return { name: entry.name, mtimeMs: stat.mtimeMs };
101
+ }
102
+ catch {
103
+ return null;
104
+ }
105
+ }));
106
+ return withMtimes.filter((e) => e !== null);
107
+ })(), (e) => {
108
+ const nodeErr = e;
109
+ if (nodeErr.code === 'ENOENT')
110
+ return { code: 'FS_NOT_FOUND', message: nodeErr.message ?? 'not found' };
111
+ return { code: 'FS_IO_ERROR', message: nodeErr.message ?? String(e) };
112
+ });
113
+ },
114
+ };
115
+ }
116
+ function createListRecentSessions() {
117
+ return async (workspacePath, limit) => {
118
+ try {
119
+ const dataDir = new index_js_2.LocalDataDirV2(process.env);
120
+ const directoryListingOps = makeDirectoryListingOpsPort();
121
+ const directoryListing = new index_js_3.LocalDirectoryListingV2(directoryListingOps);
122
+ const fsPort = makeReadOnlyFsPort();
123
+ const sha256 = new index_js_5.NodeSha256V2();
124
+ const sessionStore = new index_js_4.LocalSessionEventLogStoreV2(dataDir, fsPort, sha256);
125
+ const provider = new index_js_1.LocalSessionSummaryProviderV2({
126
+ directoryListing,
127
+ dataDir,
128
+ sessionStore,
129
+ });
130
+ const result = await provider.loadHealthySummaries();
131
+ if (result.isErr()) {
132
+ return (0, result_js_1.err)(`listRecentSessions: ${result.error.message}`);
133
+ }
134
+ const workspaceHash = `sha256:${(0, node_crypto_1.createHash)('sha256').update(workspacePath).digest('hex')}`;
135
+ const notes = result.value
136
+ .filter((s) => s.observations.repoRootHash === null ||
137
+ s.observations.repoRootHash === workspaceHash)
138
+ .slice()
139
+ .sort((a, b) => (b.lastModifiedMs ?? Date.now()) - (a.lastModifiedMs ?? Date.now()))
140
+ .slice(0, limit)
141
+ .map((s) => ({
142
+ sessionId: String(s.sessionId),
143
+ recapSnippet: s.recapSnippet != null ? String(s.recapSnippet) : null,
144
+ sessionTitle: s.sessionTitle,
145
+ gitBranch: s.observations.gitBranch,
146
+ lastModifiedMs: s.lastModifiedMs ?? Date.now(),
147
+ }));
148
+ return (0, result_js_1.ok)(notes);
149
+ }
150
+ catch (e) {
151
+ return (0, result_js_1.err)(`listRecentSessions error: ${e instanceof Error ? e.message : String(e)}`);
152
+ }
153
+ };
154
+ }
@@ -0,0 +1,30 @@
1
+ import type { Result } from '../runtime/result.js';
2
+ export type AssemblyTask = {
3
+ readonly kind: 'pr_review';
4
+ readonly prNumber: number;
5
+ readonly workspacePath: string;
6
+ readonly payloadBody?: string;
7
+ } | {
8
+ readonly kind: 'coding_task';
9
+ readonly issueNumber?: number;
10
+ readonly workspacePath: string;
11
+ readonly payloadBody?: string;
12
+ };
13
+ export interface SessionNote {
14
+ readonly sessionId: string;
15
+ readonly recapSnippet: string | null;
16
+ readonly sessionTitle: string | null;
17
+ readonly gitBranch: string | null;
18
+ readonly lastModifiedMs: number;
19
+ }
20
+ export interface ContextBundle {
21
+ readonly task: AssemblyTask;
22
+ readonly gitDiff: Result<string, string>;
23
+ readonly priorSessionNotes: Result<readonly SessionNote[], string>;
24
+ readonly assembledAt: string;
25
+ }
26
+ export interface RenderOpts {
27
+ }
28
+ export interface ContextAssembler {
29
+ assemble(task: AssemblyTask): Promise<ContextBundle>;
30
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,5 +1,6 @@
1
1
  import type { Result } from '../runtime/result.js';
2
2
  import type { AwaitResult } from '../cli/commands/worktrain-await.js';
3
+ import type { ContextAssembler } from '../context-assembly/types.js';
3
4
  export type ReviewSeverity = 'clean' | 'minor' | 'blocking' | 'unknown';
4
5
  export interface ReviewFindings {
5
6
  readonly severity: ReviewSeverity;
@@ -36,7 +37,8 @@ export interface PrReviewOpts {
36
37
  readonly port?: number;
37
38
  }
38
39
  export interface CoordinatorDeps {
39
- readonly spawnSession: (workflowId: string, goal: string, workspace: string) => Promise<Result<string, string>>;
40
+ readonly spawnSession: (workflowId: string, goal: string, workspace: string, context?: Readonly<Record<string, unknown>>) => Promise<Result<string, string>>;
41
+ readonly contextAssembler?: ContextAssembler;
40
42
  readonly awaitSessions: (handles: readonly string[], timeoutMs: number) => Promise<AwaitResult>;
41
43
  readonly getAgentResult: (sessionHandle: string) => Promise<{
42
44
  recapMarkdown: string | null;
@@ -9,6 +9,7 @@ exports.drainMessageQueue = drainMessageQueue;
9
9
  exports.runPrReviewCoordinator = runPrReviewCoordinator;
10
10
  const result_js_1 = require("../runtime/result.js");
11
11
  const review_verdict_js_1 = require("../v2/durable-core/schemas/artifacts/review-verdict.js");
12
+ const index_js_1 = require("../context-assembly/index.js");
12
13
  const MAX_FIX_PASSES = 3;
13
14
  const CHILD_SESSION_TIMEOUT_MS = 15 * 60 * 1000;
14
15
  const COORDINATOR_MAX_MS = 90 * 60 * 1000;
@@ -377,13 +378,33 @@ async function runPrReviewCoordinator(deps, opts) {
377
378
  }
378
379
  const reviewHandles = new Map();
379
380
  const spawnErrors = new Map();
381
+ const spawnContexts = new Map();
380
382
  for (const pr of prs) {
381
383
  const goal = `Review PR #${pr.number} "${pr.title}" before merge`;
382
384
  if (opts.dryRun) {
383
385
  log(` PR #${pr.number} [dry-run] would spawn mr-review-workflow-agentic`);
384
386
  continue;
385
387
  }
386
- const spawnResult = await deps.spawnSession('mr-review-workflow-agentic', goal, opts.workspace);
388
+ let spawnContext;
389
+ if (deps.contextAssembler) {
390
+ const bundle = await deps.contextAssembler.assemble({
391
+ kind: 'pr_review',
392
+ prNumber: pr.number,
393
+ workspacePath: opts.workspace,
394
+ });
395
+ const rendered = (0, index_js_1.renderContextBundle)(bundle);
396
+ if (rendered.trim().length > 0) {
397
+ spawnContext = { assembledContextSummary: rendered };
398
+ spawnContexts.set(pr.number, spawnContext);
399
+ }
400
+ if (bundle.gitDiff.kind === 'err') {
401
+ deps.stderr(`[WARN coord:context prNumber=${pr.number}] gitDiff failed: ${bundle.gitDiff.error}`);
402
+ }
403
+ if (bundle.priorSessionNotes.kind === 'err') {
404
+ deps.stderr(`[WARN coord:context prNumber=${pr.number}] priorSessionNotes failed: ${bundle.priorSessionNotes.error}`);
405
+ }
406
+ }
407
+ const spawnResult = await deps.spawnSession('mr-review-workflow-agentic', goal, opts.workspace, spawnContext);
387
408
  if (spawnResult.kind === 'err') {
388
409
  spawnErrors.set(pr.number, spawnResult.error);
389
410
  log(` PR #${pr.number} spawn failed: ${spawnResult.error}`);
@@ -462,7 +483,7 @@ async function runPrReviewCoordinator(deps, opts) {
462
483
  sessionHandles: [handle],
463
484
  };
464
485
  if (severity === 'minor' && findings && sessionResult.outcome === 'success') {
465
- const processedOutcome = await runFixAgentLoop(deps, opts, pr, findings, outcome, coordinatorStartMs, log);
486
+ const processedOutcome = await runFixAgentLoop(deps, opts, pr, findings, outcome, coordinatorStartMs, log, spawnContexts.get(prNum));
466
487
  outcomes.set(prNum, processedOutcome);
467
488
  }
468
489
  else {
@@ -537,7 +558,7 @@ async function runPrReviewCoordinator(deps, opts) {
537
558
  await writeReport(deps, reportPath, reportLines, result);
538
559
  return result;
539
560
  }
540
- async function runFixAgentLoop(deps, opts, pr, initialFindings, initialOutcome, coordinatorStartMs, log) {
561
+ async function runFixAgentLoop(deps, opts, pr, initialFindings, initialOutcome, coordinatorStartMs, log, reviewSpawnContext) {
541
562
  let passCount = 0;
542
563
  let currentFindings = initialFindings;
543
564
  let sessionHandles = [...initialOutcome.sessionHandles];
@@ -614,7 +635,7 @@ async function runFixAgentLoop(deps, opts, pr, initialFindings, initialOutcome,
614
635
  };
615
636
  }
616
637
  const reReviewGoal = `Re-review PR #${pr.number} after fixes (pass ${passCount})`;
617
- const reReviewSpawnResult = await deps.spawnSession('mr-review-workflow-agentic', reReviewGoal, opts.workspace);
638
+ const reReviewSpawnResult = await deps.spawnSession('mr-review-workflow-agentic', reReviewGoal, opts.workspace, reviewSpawnContext);
618
639
  if (reReviewSpawnResult.kind === 'err') {
619
640
  log(` PR #${pr.number} -> re-review spawn failed: ${reReviewSpawnResult.error}`);
620
641
  return {
@@ -6,6 +6,7 @@ import type { DaemonRegistry } from '../v2/infra/in-memory/daemon-registry/index
6
6
  import type { V2StartWorkflowOutputSchema } from '../mcp/output-schemas.js';
7
7
  import type { DaemonEventEmitter } from './daemon-events.js';
8
8
  export declare const DAEMON_SESSIONS_DIR: string;
9
+ export declare const WORKTREES_DIR: string;
9
10
  export { DAEMON_SOUL_DEFAULT, DAEMON_SOUL_TEMPLATE } from './soul-template.js';
10
11
  export type ReadFileState = {
11
12
  content: string;
@@ -28,6 +29,9 @@ export interface WorkflowTrigger {
28
29
  readonly parentSessionId?: string;
29
30
  readonly spawnDepth?: number;
30
31
  readonly soulFile?: string;
32
+ readonly branchStrategy?: 'worktree' | 'none';
33
+ readonly baseBranch?: string;
34
+ readonly branchPrefix?: string;
31
35
  }
32
36
  export interface WorkflowRunSuccess {
33
37
  readonly _tag: 'success';
@@ -35,6 +39,8 @@ export interface WorkflowRunSuccess {
35
39
  readonly stopReason: string;
36
40
  readonly lastStepNotes?: string;
37
41
  readonly lastStepArtifacts?: readonly unknown[];
42
+ readonly sessionWorkspacePath?: string;
43
+ readonly sessionId?: string;
38
44
  }
39
45
  export interface WorkflowRunError {
40
46
  readonly _tag: 'error';
@@ -64,13 +70,17 @@ export interface OrphanedSession {
64
70
  readonly continueToken: string;
65
71
  readonly checkpointToken: string | null;
66
72
  readonly ts: number;
73
+ readonly worktreePath?: string;
67
74
  }
68
75
  export declare function readDaemonSessionState(sessionId: string): Promise<{
69
76
  continueToken: string;
70
77
  checkpointToken: string | null;
71
78
  } | null>;
72
79
  export declare function readAllDaemonSessions(sessionsDir?: string): Promise<OrphanedSession[]>;
73
- export declare function runStartupRecovery(sessionsDir?: string): Promise<void>;
80
+ export declare function runStartupRecovery(sessionsDir?: string, execFn?: (file: string, args: string[]) => Promise<{
81
+ stdout: string;
82
+ stderr: string;
83
+ }>): Promise<void>;
74
84
  export declare function makeContinueWorkflowTool(sessionId: string, ctx: V2ToolContext, onAdvance: (nextStepText: string, continueToken: string) => void, onComplete: (notes: string | undefined, artifacts?: readonly unknown[]) => void, schemas: Record<string, any>, _executeContinueWorkflowFn?: typeof executeContinueWorkflow, emitter?: DaemonEventEmitter, workrailSessionId?: string | null): AgentTool;
75
85
  export declare function makeCompleteStepTool(sessionId: string, ctx: V2ToolContext, getCurrentToken: () => string, onAdvance: (nextStepText: string, continueToken: string) => void, onComplete: (notes: string | undefined, artifacts?: readonly unknown[]) => void, onTokenUpdate: (t: string) => void, schemas: Record<string, any>, _executeContinueWorkflowFn?: typeof executeContinueWorkflow, emitter?: DaemonEventEmitter, workrailSessionId?: string | null): AgentTool;
76
86
  export declare function makeBashTool(workspacePath: string, schemas: Record<string, any>, sessionId?: string, emitter?: DaemonEventEmitter, workrailSessionId?: string | null): AgentTool;
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.DAEMON_SIGNALS_DIR = exports.DAEMON_SOUL_TEMPLATE = exports.DAEMON_SOUL_DEFAULT = exports.DAEMON_SESSIONS_DIR = void 0;
39
+ exports.DAEMON_SIGNALS_DIR = exports.DAEMON_SOUL_TEMPLATE = exports.DAEMON_SOUL_DEFAULT = exports.WORKTREES_DIR = exports.DAEMON_SESSIONS_DIR = void 0;
40
40
  exports.readDaemonSessionState = readDaemonSessionState;
41
41
  exports.readAllDaemonSessions = readAllDaemonSessions;
42
42
  exports.runStartupRecovery = runStartupRecovery;
@@ -83,8 +83,11 @@ function withWorkrailSession(sid) {
83
83
  }
84
84
  exports.DAEMON_SESSIONS_DIR = path.join(os.homedir(), '.workrail', 'daemon-sessions');
85
85
  const MAX_ORPHAN_AGE_MS = 2 * 60 * 60 * 1000;
86
+ const MAX_WORKTREE_ORPHAN_AGE_MS = 24 * 60 * 60 * 1000;
86
87
  const WORKRAIL_DIR = path.join(os.homedir(), '.workrail');
88
+ exports.WORKTREES_DIR = path.join(os.homedir(), '.workrail', 'worktrees');
87
89
  const WORKSPACE_CONTEXT_MAX_BYTES = 32 * 1024;
90
+ const MAX_ASSEMBLED_CONTEXT_BYTES = 8192;
88
91
  const WORKSPACE_CONTEXT_CANDIDATE_PATHS = [
89
92
  '.claude/CLAUDE.md',
90
93
  'CLAUDE.md',
@@ -95,10 +98,10 @@ const soul_template_js_1 = require("./soul-template.js");
95
98
  var soul_template_js_2 = require("./soul-template.js");
96
99
  Object.defineProperty(exports, "DAEMON_SOUL_DEFAULT", { enumerable: true, get: function () { return soul_template_js_2.DAEMON_SOUL_DEFAULT; } });
97
100
  Object.defineProperty(exports, "DAEMON_SOUL_TEMPLATE", { enumerable: true, get: function () { return soul_template_js_2.DAEMON_SOUL_TEMPLATE; } });
98
- async function persistTokens(sessionId, continueToken, checkpointToken) {
101
+ async function persistTokens(sessionId, continueToken, checkpointToken, worktreePath) {
99
102
  await fs.mkdir(exports.DAEMON_SESSIONS_DIR, { recursive: true });
100
103
  const sessionPath = path.join(exports.DAEMON_SESSIONS_DIR, `${sessionId}.json`);
101
- const state = JSON.stringify({ continueToken, checkpointToken, ts: Date.now() }, null, 2);
104
+ const state = JSON.stringify({ continueToken, checkpointToken, ts: Date.now(), ...(worktreePath !== undefined ? { worktreePath } : {}) }, null, 2);
102
105
  const tmp = `${sessionPath}.tmp`;
103
106
  await fs.writeFile(tmp, state, 'utf8');
104
107
  await fs.rename(tmp, sessionPath);
@@ -144,6 +147,7 @@ async function readAllDaemonSessions(sessionsDir = exports.DAEMON_SESSIONS_DIR)
144
147
  continueToken: parsed.continueToken,
145
148
  checkpointToken: typeof parsed.checkpointToken === 'string' ? parsed.checkpointToken : null,
146
149
  ts: parsed.ts,
150
+ ...(typeof parsed.worktreePath === 'string' ? { worktreePath: parsed.worktreePath } : {}),
147
151
  });
148
152
  }
149
153
  catch (err) {
@@ -152,7 +156,7 @@ async function readAllDaemonSessions(sessionsDir = exports.DAEMON_SESSIONS_DIR)
152
156
  }
153
157
  return sessions;
154
158
  }
155
- async function runStartupRecovery(sessionsDir = exports.DAEMON_SESSIONS_DIR) {
159
+ async function runStartupRecovery(sessionsDir = exports.DAEMON_SESSIONS_DIR, execFn = execFileAsync) {
156
160
  const sessions = await readAllDaemonSessions(sessionsDir);
157
161
  if (sessions.length === 0) {
158
162
  await clearStrayTmpFiles(sessionsDir);
@@ -167,6 +171,22 @@ async function runStartupRecovery(sessionsDir = exports.DAEMON_SESSIONS_DIR) {
167
171
  const ageSec = Math.round(ageMs / 1000);
168
172
  const label = isStale ? 'stale orphaned session' : 'orphaned session';
169
173
  console.log(`[WorkflowRunner] Clearing ${label}: sessionId=${session.sessionId} age=${ageSec}s`);
174
+ if (session.worktreePath && ageMs > MAX_WORKTREE_ORPHAN_AGE_MS) {
175
+ console.log(`[WorkflowRunner] Removing orphan worktree: sessionId=${session.sessionId} worktreePath=${session.worktreePath}`);
176
+ try {
177
+ await execFn('git', ['worktree', 'remove', '--force', session.worktreePath]);
178
+ console.log(`[WorkflowRunner] Removed orphan worktree: ${session.worktreePath}`);
179
+ }
180
+ catch (err) {
181
+ console.warn(`[WorkflowRunner] Could not remove orphan worktree ${session.worktreePath}: ` +
182
+ `${err instanceof Error ? err.message : String(err)}`);
183
+ }
184
+ }
185
+ else if (session.worktreePath && ageMs <= MAX_WORKTREE_ORPHAN_AGE_MS) {
186
+ const ageHours = (ageMs / (60 * 60 * 1000)).toFixed(1);
187
+ console.log(`[WorkflowRunner] Keeping recent orphan worktree: sessionId=${session.sessionId} ` +
188
+ `age=${ageHours}h (threshold=24h) worktreePath=${session.worktreePath}`);
189
+ }
170
190
  try {
171
191
  await fs.unlink(path.join(sessionsDir, `${session.sessionId}.json`));
172
192
  cleared++;
@@ -1307,6 +1327,16 @@ function buildSystemPrompt(trigger, sessionState, soulContent, workspaceContext)
1307
1327
  lines.push('## Workspace Context (from AGENTS.md / CLAUDE.md)');
1308
1328
  lines.push(workspaceContext);
1309
1329
  }
1330
+ const assembledContextSummary = trigger.context?.['assembledContextSummary'];
1331
+ if (typeof assembledContextSummary === 'string' && assembledContextSummary.trim().length > 0) {
1332
+ let ctxStr = assembledContextSummary;
1333
+ if (Buffer.byteLength(ctxStr, 'utf8') > MAX_ASSEMBLED_CONTEXT_BYTES) {
1334
+ ctxStr = ctxStr.slice(0, MAX_ASSEMBLED_CONTEXT_BYTES) + '\n[Prior context truncated at 8KB]';
1335
+ }
1336
+ lines.push('');
1337
+ lines.push('## Prior Context');
1338
+ lines.push(ctxStr.trim());
1339
+ }
1310
1340
  if (trigger.referenceUrls && trigger.referenceUrls.length > 0) {
1311
1341
  lines.push('');
1312
1342
  lines.push('## Reference documents');
@@ -1419,12 +1449,53 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerR
1419
1449
  if (startContinueToken) {
1420
1450
  await persistTokens(sessionId, startContinueToken, startCheckpointToken);
1421
1451
  }
1452
+ let sessionWorkspacePath = trigger.workspacePath;
1453
+ let sessionWorktreePath;
1454
+ if (trigger.branchStrategy === 'worktree') {
1455
+ const branchPrefix = trigger.branchPrefix ?? 'worktrain/';
1456
+ const baseBranch = trigger.baseBranch ?? 'main';
1457
+ sessionWorkspacePath = path.join(exports.WORKTREES_DIR, sessionId);
1458
+ sessionWorktreePath = sessionWorkspacePath;
1459
+ try {
1460
+ await fs.mkdir(exports.WORKTREES_DIR, { recursive: true });
1461
+ await execFileAsync('git', ['-C', trigger.workspacePath, 'fetch', 'origin', baseBranch]);
1462
+ await execFileAsync('git', [
1463
+ '-C', trigger.workspacePath,
1464
+ 'worktree', 'add',
1465
+ sessionWorkspacePath,
1466
+ '-b', `${branchPrefix}${sessionId}`,
1467
+ `origin/${baseBranch}`,
1468
+ ]);
1469
+ await persistTokens(sessionId, startContinueToken ?? currentContinueToken, startCheckpointToken, sessionWorktreePath);
1470
+ console.log(`[WorkflowRunner] Worktree created: sessionId=${sessionId} ` +
1471
+ `branch=${branchPrefix}${sessionId} path=${sessionWorkspacePath}`);
1472
+ }
1473
+ catch (err) {
1474
+ const errMsg = err instanceof Error ? err.message : String(err);
1475
+ console.error(`[WorkflowRunner] Worktree creation failed: sessionId=${sessionId} error=${errMsg}`);
1476
+ emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'error', detail: errMsg.slice(0, 200), ...withWorkrailSession(workrailSessionId) });
1477
+ if (workrailSessionId !== null)
1478
+ daemonRegistry?.unregister(workrailSessionId, 'failed');
1479
+ return {
1480
+ _tag: 'error',
1481
+ workflowId: trigger.workflowId,
1482
+ message: `Worktree creation failed: ${errMsg}`,
1483
+ stopReason: 'error',
1484
+ };
1485
+ }
1486
+ }
1422
1487
  if (firstStep.isComplete) {
1423
1488
  await fs.unlink(path.join(exports.DAEMON_SESSIONS_DIR, `${sessionId}.json`)).catch(() => { });
1424
1489
  emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'success', detail: 'stop', ...withWorkrailSession(workrailSessionId) });
1425
1490
  if (workrailSessionId !== null)
1426
1491
  daemonRegistry?.unregister(workrailSessionId, 'completed');
1427
- return { _tag: 'success', workflowId: trigger.workflowId, stopReason: 'stop' };
1492
+ return {
1493
+ _tag: 'success',
1494
+ workflowId: trigger.workflowId,
1495
+ stopReason: 'stop',
1496
+ ...(sessionWorktreePath !== undefined ? { sessionWorkspacePath: sessionWorktreePath } : {}),
1497
+ ...(sessionWorktreePath !== undefined ? { sessionId } : {}),
1498
+ };
1428
1499
  }
1429
1500
  const schemas = getSchemas();
1430
1501
  const spawnCurrentDepth = trigger.spawnDepth ?? 0;
@@ -1433,12 +1504,12 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerR
1433
1504
  const tools = [
1434
1505
  makeCompleteStepTool(sessionId, ctx, () => currentContinueToken, onAdvance, onComplete, (t) => { currentContinueToken = t; }, schemas, index_js_1.executeContinueWorkflow, emitter, workrailSessionId),
1435
1506
  makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas, index_js_1.executeContinueWorkflow, emitter, workrailSessionId),
1436
- makeBashTool(trigger.workspacePath, schemas, sessionId, emitter, workrailSessionId),
1507
+ makeBashTool(sessionWorkspacePath, schemas, sessionId, emitter, workrailSessionId),
1437
1508
  makeReadTool(readFileState, schemas, sessionId, emitter, workrailSessionId),
1438
1509
  makeWriteTool(readFileState, schemas, sessionId, emitter, workrailSessionId),
1439
- makeGlobTool(trigger.workspacePath, schemas, sessionId, emitter, workrailSessionId),
1440
- makeGrepTool(trigger.workspacePath, schemas, sessionId, emitter, workrailSessionId),
1441
- makeEditTool(trigger.workspacePath, readFileState, schemas, sessionId, emitter, workrailSessionId),
1510
+ makeGlobTool(sessionWorkspacePath, schemas, sessionId, emitter, workrailSessionId),
1511
+ makeGrepTool(sessionWorkspacePath, schemas, sessionId, emitter, workrailSessionId),
1512
+ makeEditTool(sessionWorkspacePath, readFileState, schemas, sessionId, emitter, workrailSessionId),
1442
1513
  makeReportIssueTool(sessionId, emitter, workrailSessionId, undefined, (summary) => {
1443
1514
  if (issueSummaries.length < MAX_ISSUE_SUMMARIES) {
1444
1515
  issueSummaries.push(summary);
@@ -1659,5 +1730,7 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerR
1659
1730
  stopReason,
1660
1731
  ...(lastStepNotes !== undefined ? { lastStepNotes } : {}),
1661
1732
  ...(lastStepArtifacts !== undefined ? { lastStepArtifacts } : {}),
1733
+ ...(sessionWorktreePath !== undefined ? { sessionWorkspacePath: sessionWorktreePath } : {}),
1734
+ ...(sessionWorktreePath !== undefined ? { sessionId } : {}),
1662
1735
  };
1663
1736
  }
@@ -20,12 +20,12 @@ export declare const LoopFrameSchema: z.ZodObject<{
20
20
  iteration: z.ZodNumber;
21
21
  bodyIndex: z.ZodNumber;
22
22
  }, "strip", z.ZodTypeAny, {
23
- loopId: string;
24
23
  iteration: number;
24
+ loopId: string;
25
25
  bodyIndex: number;
26
26
  }, {
27
- loopId: string;
28
27
  iteration: number;
28
+ loopId: string;
29
29
  bodyIndex: number;
30
30
  }>;
31
31
  export declare const StepInstanceIdSchema: z.ZodObject<{
@@ -34,23 +34,23 @@ export declare const StepInstanceIdSchema: z.ZodObject<{
34
34
  loopId: z.ZodString;
35
35
  iteration: z.ZodNumber;
36
36
  }, "strip", z.ZodTypeAny, {
37
- loopId: string;
38
37
  iteration: number;
39
- }, {
40
38
  loopId: string;
39
+ }, {
41
40
  iteration: number;
41
+ loopId: string;
42
42
  }>, "many">;
43
43
  }, "strip", z.ZodTypeAny, {
44
44
  stepId: string;
45
45
  loopPath: {
46
- loopId: string;
47
46
  iteration: number;
47
+ loopId: string;
48
48
  }[];
49
49
  }, {
50
50
  stepId: string;
51
51
  loopPath: {
52
- loopId: string;
53
52
  iteration: number;
53
+ loopId: string;
54
54
  }[];
55
55
  }>;
56
56
  export declare const ExecutionStateSchema: z.ZodType<ExecutionState>;