@exaudeus/workrail 3.40.0 → 3.42.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.
- package/dist/cli/commands/init.js +0 -3
- package/dist/cli-worktrain.js +48 -11
- package/dist/cli.js +0 -18
- package/dist/config/app-config.d.ts +0 -16
- package/dist/config/app-config.js +0 -14
- package/dist/config/config-file.js +0 -3
- package/dist/console-ui/assets/index-DGj8EsFR.css +1 -0
- package/dist/console-ui/assets/index-DwfWMKvv.js +28 -0
- package/dist/console-ui/index.html +2 -2
- package/dist/context-assembly/deps.d.ts +8 -0
- package/dist/context-assembly/deps.js +2 -0
- package/dist/context-assembly/index.d.ts +6 -0
- package/dist/context-assembly/index.js +50 -0
- package/dist/context-assembly/infra.d.ts +3 -0
- package/dist/context-assembly/infra.js +154 -0
- package/dist/context-assembly/types.d.ts +30 -0
- package/dist/context-assembly/types.js +2 -0
- package/dist/coordinators/pr-review.d.ts +20 -1
- package/dist/coordinators/pr-review.js +189 -4
- package/dist/daemon/daemon-events.d.ts +9 -1
- package/dist/daemon/soul-template.d.ts +2 -2
- package/dist/daemon/soul-template.js +11 -1
- package/dist/daemon/workflow-runner.d.ts +14 -1
- package/dist/daemon/workflow-runner.js +406 -25
- package/dist/di/container.js +1 -25
- package/dist/di/tokens.d.ts +0 -3
- package/dist/di/tokens.js +0 -3
- package/dist/domain/execution/state.d.ts +6 -6
- package/dist/engine/engine-factory.js +0 -1
- package/dist/infrastructure/console-defaults.d.ts +1 -0
- package/dist/infrastructure/console-defaults.js +4 -0
- package/dist/infrastructure/session/index.d.ts +0 -1
- package/dist/infrastructure/session/index.js +1 -3
- package/dist/manifest.json +138 -122
- package/dist/mcp/handlers/session.d.ts +1 -0
- package/dist/mcp/handlers/session.js +61 -13
- package/dist/mcp/handlers/v2-workflow.d.ts +2 -2
- package/dist/mcp/output-schemas.d.ts +234 -234
- package/dist/mcp/server.js +1 -18
- package/dist/mcp/tools.d.ts +2 -2
- package/dist/mcp/transports/http-entry.js +0 -2
- package/dist/mcp/transports/stdio-entry.js +1 -2
- package/dist/mcp/types.d.ts +0 -2
- package/dist/mcp/v2/tools.d.ts +24 -24
- package/dist/trigger/daemon-console.d.ts +2 -0
- package/dist/trigger/daemon-console.js +1 -1
- package/dist/trigger/trigger-listener.d.ts +2 -0
- package/dist/trigger/trigger-listener.js +3 -1
- package/dist/trigger/trigger-router.d.ts +4 -3
- package/dist/trigger/trigger-router.js +4 -3
- package/dist/trigger/trigger-store.js +17 -4
- package/dist/v2/durable-core/schemas/artifacts/assessment.d.ts +2 -2
- package/dist/v2/durable-core/schemas/artifacts/coordinator-signal.d.ts +2 -2
- package/dist/v2/durable-core/schemas/artifacts/loop-control.d.ts +6 -6
- package/dist/v2/durable-core/schemas/artifacts/review-verdict.d.ts +6 -6
- package/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +56 -56
- package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.d.ts +83 -83
- package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +1024 -1024
- package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +2336 -2336
- package/dist/v2/durable-core/schemas/session/dag-topology.d.ts +6 -6
- package/dist/v2/durable-core/schemas/session/events.d.ts +339 -339
- package/dist/v2/durable-core/schemas/session/gaps.d.ts +30 -30
- package/dist/v2/durable-core/schemas/session/manifest.d.ts +6 -6
- package/dist/v2/durable-core/schemas/session/outputs.d.ts +8 -8
- package/dist/v2/durable-core/schemas/session/validation-event.d.ts +3 -3
- package/dist/v2/usecases/console-routes.d.ts +2 -1
- package/dist/v2/usecases/console-routes.js +29 -5
- package/dist/v2/usecases/console-service.js +14 -0
- package/dist/v2/usecases/console-types.d.ts +1 -0
- package/docs/authoring.md +16 -16
- package/docs/design/context-assembly-design-candidates.md +199 -0
- package/docs/design/context-assembly-implementation-plan.md +211 -0
- package/docs/design/context-assembly-review-findings.md +112 -0
- package/docs/design/coordinator-message-queue-drain-plan.md +241 -0
- package/docs/design/coordinator-message-queue-drain-review.md +120 -0
- package/docs/design/coordinator-message-queue-drain.md +289 -0
- package/docs/design/shaping-workflow-external-research.md +119 -0
- package/docs/discovery/late-bound-goals-impl-plan.md +147 -0
- package/docs/discovery/late-bound-goals-review.md +82 -0
- package/docs/discovery/late-bound-goals.md +118 -0
- package/docs/discovery/steer-endpoint-design-candidates.md +288 -0
- package/docs/discovery/steer-endpoint-design-review-findings.md +104 -0
- package/docs/discovery/steer-endpoint-implementation-plan.md +284 -0
- package/docs/ideas/backlog.md +356 -0
- package/docs/ideas/design-candidates-console-session-tree-impl.md +64 -0
- package/docs/ideas/design-candidates-session-tree-view.md +196 -0
- package/docs/ideas/design-review-findings-console-session-tree-impl.md +75 -0
- package/docs/ideas/design-review-findings-session-tree-view.md +88 -0
- package/docs/ideas/implementation_plan_session_tree_view.md +238 -0
- package/package.json +2 -1
- package/spec/authoring-spec.json +16 -16
- package/spec/shape.schema.json +178 -0
- package/spec/workflow-tags.json +232 -47
- package/workflows/coding-task-workflow-agentic.json +491 -480
- package/workflows/wr.shaping.json +182 -0
- package/dist/console-ui/assets/index-8dh0Psu-.css +0 -1
- package/dist/console-ui/assets/index-CXWCAonr.js +0 -28
- package/dist/infrastructure/session/DashboardHeartbeat.d.ts +0 -8
- package/dist/infrastructure/session/DashboardHeartbeat.js +0 -39
- package/dist/infrastructure/session/DashboardLockRelease.d.ts +0 -2
- package/dist/infrastructure/session/DashboardLockRelease.js +0 -29
- package/dist/infrastructure/session/HttpServer.d.ts +0 -60
- package/dist/infrastructure/session/HttpServer.js +0 -912
- package/workflows/coding-task-workflow-agentic.lean.v2.json +0 -648
- package/workflows/coding-task-workflow-agentic.v2.json +0 -324
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>WorkRail Console</title>
|
|
7
|
-
<script type="module" crossorigin src="/console/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/console/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/console/assets/index-DwfWMKvv.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/console/assets/index-DGj8EsFR.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Result } from '../runtime/result.js';
|
|
2
|
+
import type { SessionNote } from './types.js';
|
|
3
|
+
export interface ContextAssemblerDeps {
|
|
4
|
+
readonly execGit: (args: readonly string[], cwd: string) => Promise<Result<string, string>>;
|
|
5
|
+
readonly execGh: (args: readonly string[], cwd: string) => Promise<Result<string, string>>;
|
|
6
|
+
readonly listRecentSessions: (workspacePath: string, limit: number) => Promise<Result<readonly SessionNote[], string>>;
|
|
7
|
+
readonly nowIso: () => string;
|
|
8
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ContextAssemblerDeps } from './deps.js';
|
|
2
|
+
import type { ContextAssembler, ContextBundle, RenderOpts } from './types.js';
|
|
3
|
+
export declare function createContextAssembler(deps: ContextAssemblerDeps): ContextAssembler;
|
|
4
|
+
export declare function renderContextBundle(bundle: ContextBundle, _opts?: RenderOpts): string;
|
|
5
|
+
export type { AssemblyTask, ContextBundle, ContextAssembler, SessionNote, RenderOpts } from './types.js';
|
|
6
|
+
export type { ContextAssemblerDeps } from './deps.js';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createContextAssembler = createContextAssembler;
|
|
4
|
+
exports.renderContextBundle = renderContextBundle;
|
|
5
|
+
function createContextAssembler(deps) {
|
|
6
|
+
return {
|
|
7
|
+
async assemble(task) {
|
|
8
|
+
const [gitDiff, priorSessionNotes] = await Promise.all([
|
|
9
|
+
assembleGitDiff(deps, task),
|
|
10
|
+
assemblePriorNotes(deps, task.workspacePath),
|
|
11
|
+
]);
|
|
12
|
+
return {
|
|
13
|
+
task,
|
|
14
|
+
gitDiff,
|
|
15
|
+
priorSessionNotes,
|
|
16
|
+
assembledAt: deps.nowIso(),
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
async function assembleGitDiff(deps, task) {
|
|
22
|
+
if (task.kind === 'pr_review') {
|
|
23
|
+
const ghResult = await deps.execGh(['pr', 'diff', String(task.prNumber), '--name-only'], task.workspacePath);
|
|
24
|
+
if (ghResult.kind === 'ok' && ghResult.value.trim().length > 0) {
|
|
25
|
+
return { kind: 'ok', value: ghResult.value.trim() };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return deps.execGit(['diff', 'HEAD~1', '--stat'], task.workspacePath);
|
|
29
|
+
}
|
|
30
|
+
async function assemblePriorNotes(deps, workspacePath) {
|
|
31
|
+
const PRIOR_SESSION_LIMIT = 3;
|
|
32
|
+
return deps.listRecentSessions(workspacePath, PRIOR_SESSION_LIMIT);
|
|
33
|
+
}
|
|
34
|
+
function renderContextBundle(bundle, _opts) {
|
|
35
|
+
const parts = [];
|
|
36
|
+
if (bundle.priorSessionNotes.kind === 'ok' && bundle.priorSessionNotes.value.length > 0) {
|
|
37
|
+
parts.push('### Recent session notes for this workspace\n');
|
|
38
|
+
for (const note of bundle.priorSessionNotes.value) {
|
|
39
|
+
const title = note.sessionTitle ?? note.sessionId.slice(0, 12);
|
|
40
|
+
const branch = note.gitBranch ? ` (branch: ${note.gitBranch})` : '';
|
|
41
|
+
const recap = note.recapSnippet ?? '(no recap available)';
|
|
42
|
+
parts.push(`**${title}**${branch}\n${recap}\n`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (bundle.gitDiff.kind === 'ok' && bundle.gitDiff.value.trim().length > 0) {
|
|
46
|
+
parts.push('### Changed files\n');
|
|
47
|
+
parts.push('```\n' + bundle.gitDiff.value.trim() + '\n```\n');
|
|
48
|
+
}
|
|
49
|
+
return parts.join('\n');
|
|
50
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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;
|
|
@@ -48,6 +50,15 @@ export interface CoordinatorDeps {
|
|
|
48
50
|
readonly stderr: (line: string) => void;
|
|
49
51
|
readonly now: () => number;
|
|
50
52
|
readonly port: number;
|
|
53
|
+
readonly readFile: (path: string) => Promise<string>;
|
|
54
|
+
readonly appendFile: (path: string, content: string) => Promise<void>;
|
|
55
|
+
readonly mkdir: (path: string, options: {
|
|
56
|
+
recursive: boolean;
|
|
57
|
+
}) => Promise<string | undefined>;
|
|
58
|
+
readonly homedir: () => string;
|
|
59
|
+
readonly joinPath: (...paths: string[]) => string;
|
|
60
|
+
readonly nowIso: () => string;
|
|
61
|
+
readonly generateId: () => string;
|
|
51
62
|
}
|
|
52
63
|
export declare function parseFindingsFromNotes(notes: string | null): Result<ReviewFindings, string>;
|
|
53
64
|
export declare function readVerdictArtifact(artifacts: readonly unknown[], sessionHandle?: string): ReviewFindings | null;
|
|
@@ -59,4 +70,12 @@ export interface CoordinatorPortDiscoveryDeps {
|
|
|
59
70
|
readonly homedir: () => string;
|
|
60
71
|
readonly joinPath: (...paths: string[]) => string;
|
|
61
72
|
}
|
|
73
|
+
export interface DrainResult {
|
|
74
|
+
readonly stop: boolean;
|
|
75
|
+
readonly stopReason: string | null;
|
|
76
|
+
readonly skipPrNumbers: readonly number[];
|
|
77
|
+
readonly addPrNumbers: readonly number[];
|
|
78
|
+
readonly messagesProcessed: number;
|
|
79
|
+
}
|
|
80
|
+
export declare function drainMessageQueue(deps: Pick<CoordinatorDeps, 'readFile' | 'appendFile' | 'writeFile' | 'mkdir' | 'homedir' | 'joinPath' | 'nowIso' | 'generateId' | 'stderr'>): Promise<DrainResult>;
|
|
62
81
|
export declare function runPrReviewCoordinator(deps: CoordinatorDeps, opts: PrReviewOpts): Promise<CoordinatorResult>;
|
|
@@ -5,9 +5,11 @@ exports.readVerdictArtifact = readVerdictArtifact;
|
|
|
5
5
|
exports.buildFixGoal = buildFixGoal;
|
|
6
6
|
exports.formatElapsed = formatElapsed;
|
|
7
7
|
exports.discoverConsolePort = discoverConsolePort;
|
|
8
|
+
exports.drainMessageQueue = drainMessageQueue;
|
|
8
9
|
exports.runPrReviewCoordinator = runPrReviewCoordinator;
|
|
9
10
|
const result_js_1 = require("../runtime/result.js");
|
|
10
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");
|
|
11
13
|
const MAX_FIX_PASSES = 3;
|
|
12
14
|
const CHILD_SESSION_TIMEOUT_MS = 15 * 60 * 1000;
|
|
13
15
|
const COORDINATOR_MAX_MS = 90 * 60 * 1000;
|
|
@@ -161,6 +163,126 @@ async function discoverConsolePort(deps, portOverride) {
|
|
|
161
163
|
}
|
|
162
164
|
return DEFAULT_CONSOLE_PORT;
|
|
163
165
|
}
|
|
166
|
+
async function drainMessageQueue(deps) {
|
|
167
|
+
const workrailDir = deps.joinPath(deps.homedir(), '.workrail');
|
|
168
|
+
const queuePath = deps.joinPath(workrailDir, 'message-queue.jsonl');
|
|
169
|
+
const cursorPath = deps.joinPath(workrailDir, 'message-queue-cursor.json');
|
|
170
|
+
const outboxPath = deps.joinPath(workrailDir, 'outbox.jsonl');
|
|
171
|
+
let queueContent;
|
|
172
|
+
try {
|
|
173
|
+
queueContent = await deps.readFile(queuePath);
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
if (isEnoentError(err)) {
|
|
177
|
+
return { stop: false, stopReason: null, skipPrNumbers: [], addPrNumbers: [], messagesProcessed: 0 };
|
|
178
|
+
}
|
|
179
|
+
deps.stderr(`[WARN coord:drain reason=read_failed] drainMessageQueue: could not read message queue: ${err instanceof Error ? err.message : String(err)}`);
|
|
180
|
+
return { stop: false, stopReason: null, skipPrNumbers: [], addPrNumbers: [], messagesProcessed: 0 };
|
|
181
|
+
}
|
|
182
|
+
const allLines = queueContent.split('\n').filter((line) => line.trim() !== '');
|
|
183
|
+
const parsedMessages = [];
|
|
184
|
+
for (const line of allLines) {
|
|
185
|
+
try {
|
|
186
|
+
const msg = JSON.parse(line);
|
|
187
|
+
parsedMessages.push(msg);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
deps.stderr(`[WARN coord:drain reason=malformed_line] drainMessageQueue: skipped malformed JSONL line`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const totalLines = parsedMessages.length;
|
|
194
|
+
let lastReadCount = 0;
|
|
195
|
+
try {
|
|
196
|
+
const cursorContent = await deps.readFile(cursorPath);
|
|
197
|
+
const cursor = JSON.parse(cursorContent);
|
|
198
|
+
if (typeof cursor.lastReadCount === 'number' && cursor.lastReadCount >= 0) {
|
|
199
|
+
lastReadCount = cursor.lastReadCount;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
lastReadCount = 0;
|
|
204
|
+
}
|
|
205
|
+
if (lastReadCount > totalLines) {
|
|
206
|
+
lastReadCount = 0;
|
|
207
|
+
}
|
|
208
|
+
const newMessages = parsedMessages.slice(lastReadCount);
|
|
209
|
+
let stop = false;
|
|
210
|
+
let stopReason = null;
|
|
211
|
+
const skipSet = new Set();
|
|
212
|
+
const addSet = new Set();
|
|
213
|
+
const outboxEntries = [];
|
|
214
|
+
const STOP_RE = /^\s*stop\b/i;
|
|
215
|
+
const SKIP_PR_RE = /\bskip[- ]pr[\s#]+([0-9]+)/i;
|
|
216
|
+
const ADD_PR_RE = /\badd[- ]pr[\s#]+([0-9]+)/i;
|
|
217
|
+
for (const msg of newMessages) {
|
|
218
|
+
const text = msg.message;
|
|
219
|
+
if (STOP_RE.test(text)) {
|
|
220
|
+
stop = true;
|
|
221
|
+
stopReason = text;
|
|
222
|
+
deps.stderr(`[INFO coord:drain kind=stop ts=${msg.timestamp}] drainMessageQueue: stop signal received -- message: "${text}"`);
|
|
223
|
+
outboxEntries.push(JSON.stringify({
|
|
224
|
+
id: deps.generateId(),
|
|
225
|
+
message: `WorkTrain coordinator stopped by queued message: "${text}" (queued at ${msg.timestamp})`,
|
|
226
|
+
timestamp: deps.nowIso(),
|
|
227
|
+
}) + '\n');
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const skipMatch = SKIP_PR_RE.exec(text);
|
|
231
|
+
if (skipMatch !== null) {
|
|
232
|
+
const prNum = parseInt(skipMatch[1], 10);
|
|
233
|
+
skipSet.add(prNum);
|
|
234
|
+
deps.stderr(`[INFO coord:drain kind=skip-pr prNumber=${prNum} ts=${msg.timestamp}] drainMessageQueue: skip-pr signal received -- message: "${text}"`);
|
|
235
|
+
outboxEntries.push(JSON.stringify({
|
|
236
|
+
id: deps.generateId(),
|
|
237
|
+
message: `WorkTrain coordinator skipping PR #${prNum} per queued message: "${text}" (queued at ${msg.timestamp})`,
|
|
238
|
+
timestamp: deps.nowIso(),
|
|
239
|
+
}) + '\n');
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const addMatch = ADD_PR_RE.exec(text);
|
|
243
|
+
if (addMatch !== null) {
|
|
244
|
+
const prNum = parseInt(addMatch[1], 10);
|
|
245
|
+
addSet.add(prNum);
|
|
246
|
+
deps.stderr(`[INFO coord:drain kind=add-pr prNumber=${prNum} ts=${msg.timestamp}] drainMessageQueue: add-pr signal received -- message: "${text}"`);
|
|
247
|
+
outboxEntries.push(JSON.stringify({
|
|
248
|
+
id: deps.generateId(),
|
|
249
|
+
message: `WorkTrain coordinator adding PR #${prNum} per queued message: "${text}" (queued at ${msg.timestamp})`,
|
|
250
|
+
timestamp: deps.nowIso(),
|
|
251
|
+
}) + '\n');
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (outboxEntries.length > 0) {
|
|
256
|
+
try {
|
|
257
|
+
await deps.mkdir(workrailDir, { recursive: true });
|
|
258
|
+
await deps.appendFile(outboxPath, outboxEntries.join(''));
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
deps.stderr(`[WARN coord:drain reason=outbox_write_failed] drainMessageQueue: could not write outbox notifications: ${err instanceof Error ? err.message : String(err)}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const newCursor = JSON.stringify({ lastReadCount: totalLines }, null, 2) + '\n';
|
|
265
|
+
try {
|
|
266
|
+
await deps.mkdir(workrailDir, { recursive: true });
|
|
267
|
+
await deps.writeFile(cursorPath, newCursor);
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
deps.stderr(`[WARN coord:drain reason=cursor_write_failed] drainMessageQueue: could not update cursor: ${err instanceof Error ? err.message : String(err)}`);
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
stop,
|
|
274
|
+
stopReason,
|
|
275
|
+
skipPrNumbers: [...skipSet],
|
|
276
|
+
addPrNumbers: [...addSet],
|
|
277
|
+
messagesProcessed: newMessages.length,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function isEnoentError(err) {
|
|
281
|
+
return (err !== null &&
|
|
282
|
+
typeof err === 'object' &&
|
|
283
|
+
'code' in err &&
|
|
284
|
+
err.code === 'ENOENT');
|
|
285
|
+
}
|
|
164
286
|
async function runPrReviewCoordinator(deps, opts) {
|
|
165
287
|
const coordinatorStartMs = deps.now();
|
|
166
288
|
const today = new Date(deps.now()).toISOString().slice(0, 10);
|
|
@@ -170,6 +292,30 @@ async function runPrReviewCoordinator(deps, opts) {
|
|
|
170
292
|
deps.stderr(line);
|
|
171
293
|
reportLines.push(line);
|
|
172
294
|
}
|
|
295
|
+
const drainResult = await drainMessageQueue(deps);
|
|
296
|
+
if (drainResult.messagesProcessed > 0) {
|
|
297
|
+
const skipStr = drainResult.skipPrNumbers.length > 0
|
|
298
|
+
? `, skip=[${drainResult.skipPrNumbers.join(',')}]`
|
|
299
|
+
: '';
|
|
300
|
+
const addStr = drainResult.addPrNumbers.length > 0
|
|
301
|
+
? `, add=[${drainResult.addPrNumbers.join(',')}]`
|
|
302
|
+
: '';
|
|
303
|
+
log(`[drain] processed ${drainResult.messagesProcessed} message(s)${skipStr}${addStr}${drainResult.stop ? ', STOP SIGNAL' : ''}`);
|
|
304
|
+
}
|
|
305
|
+
if (drainResult.stop) {
|
|
306
|
+
const stopMsg = drainResult.stopReason ?? 'stop signal in message queue';
|
|
307
|
+
log(` STOP: coordinator halted by queued message: "${stopMsg}"`);
|
|
308
|
+
const result = {
|
|
309
|
+
reviewed: 0,
|
|
310
|
+
approved: 0,
|
|
311
|
+
escalated: 0,
|
|
312
|
+
mergedPrs: [],
|
|
313
|
+
reportPath,
|
|
314
|
+
hasErrors: false,
|
|
315
|
+
};
|
|
316
|
+
await writeReport(deps, reportPath, reportLines, result);
|
|
317
|
+
return result;
|
|
318
|
+
}
|
|
173
319
|
log('[1/3] Gathering open PRs...');
|
|
174
320
|
const stageStart = deps.now();
|
|
175
321
|
let prs;
|
|
@@ -183,6 +329,25 @@ async function runPrReviewCoordinator(deps, opts) {
|
|
|
183
329
|
else {
|
|
184
330
|
prs = await deps.listOpenPRs(opts.workspace);
|
|
185
331
|
}
|
|
332
|
+
if (drainResult.addPrNumbers.length > 0) {
|
|
333
|
+
const existingNums = new Set(prs.map((p) => p.number));
|
|
334
|
+
for (const addNum of drainResult.addPrNumbers) {
|
|
335
|
+
if (!existingNums.has(addNum)) {
|
|
336
|
+
prs = [...prs, { number: addNum, title: `PR #${addNum}`, headRef: '' }];
|
|
337
|
+
existingNums.add(addNum);
|
|
338
|
+
log(` [drain] added PR #${addNum} from message queue`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (drainResult.skipPrNumbers.length > 0) {
|
|
343
|
+
const skipSet = new Set(drainResult.skipPrNumbers);
|
|
344
|
+
const prsBefore = prs.length;
|
|
345
|
+
prs = prs.filter((p) => !skipSet.has(p.number));
|
|
346
|
+
const skippedCount = prsBefore - prs.length;
|
|
347
|
+
if (skippedCount > 0) {
|
|
348
|
+
log(` [drain] skipped ${skippedCount} PR(s) from message queue: [${[...skipSet].join(',')}]`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
186
351
|
log(` done (${formatElapsed(deps.now() - stageStart)}) -- ${prs.length} PR(s) found`);
|
|
187
352
|
if (prs.length === 0) {
|
|
188
353
|
const result = {
|
|
@@ -213,13 +378,33 @@ async function runPrReviewCoordinator(deps, opts) {
|
|
|
213
378
|
}
|
|
214
379
|
const reviewHandles = new Map();
|
|
215
380
|
const spawnErrors = new Map();
|
|
381
|
+
const spawnContexts = new Map();
|
|
216
382
|
for (const pr of prs) {
|
|
217
383
|
const goal = `Review PR #${pr.number} "${pr.title}" before merge`;
|
|
218
384
|
if (opts.dryRun) {
|
|
219
385
|
log(` PR #${pr.number} [dry-run] would spawn mr-review-workflow-agentic`);
|
|
220
386
|
continue;
|
|
221
387
|
}
|
|
222
|
-
|
|
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);
|
|
223
408
|
if (spawnResult.kind === 'err') {
|
|
224
409
|
spawnErrors.set(pr.number, spawnResult.error);
|
|
225
410
|
log(` PR #${pr.number} spawn failed: ${spawnResult.error}`);
|
|
@@ -298,7 +483,7 @@ async function runPrReviewCoordinator(deps, opts) {
|
|
|
298
483
|
sessionHandles: [handle],
|
|
299
484
|
};
|
|
300
485
|
if (severity === 'minor' && findings && sessionResult.outcome === 'success') {
|
|
301
|
-
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));
|
|
302
487
|
outcomes.set(prNum, processedOutcome);
|
|
303
488
|
}
|
|
304
489
|
else {
|
|
@@ -373,7 +558,7 @@ async function runPrReviewCoordinator(deps, opts) {
|
|
|
373
558
|
await writeReport(deps, reportPath, reportLines, result);
|
|
374
559
|
return result;
|
|
375
560
|
}
|
|
376
|
-
async function runFixAgentLoop(deps, opts, pr, initialFindings, initialOutcome, coordinatorStartMs, log) {
|
|
561
|
+
async function runFixAgentLoop(deps, opts, pr, initialFindings, initialOutcome, coordinatorStartMs, log, reviewSpawnContext) {
|
|
377
562
|
let passCount = 0;
|
|
378
563
|
let currentFindings = initialFindings;
|
|
379
564
|
let sessionHandles = [...initialOutcome.sessionHandles];
|
|
@@ -450,7 +635,7 @@ async function runFixAgentLoop(deps, opts, pr, initialFindings, initialOutcome,
|
|
|
450
635
|
};
|
|
451
636
|
}
|
|
452
637
|
const reReviewGoal = `Re-review PR #${pr.number} after fixes (pass ${passCount})`;
|
|
453
|
-
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);
|
|
454
639
|
if (reReviewSpawnResult.kind === 'err') {
|
|
455
640
|
log(` PR #${pr.number} -> re-review spawn failed: ${reReviewSpawnResult.error}`);
|
|
456
641
|
return {
|
|
@@ -101,6 +101,14 @@ export interface ToolCallFailedEvent {
|
|
|
101
101
|
readonly errorMessage: string;
|
|
102
102
|
readonly workrailSessionId?: string;
|
|
103
103
|
}
|
|
104
|
+
export interface SignalEmittedEvent {
|
|
105
|
+
readonly kind: 'signal_emitted';
|
|
106
|
+
readonly sessionId: string;
|
|
107
|
+
readonly signalKind: string;
|
|
108
|
+
readonly signalId: string;
|
|
109
|
+
readonly payload: Readonly<Record<string, unknown>>;
|
|
110
|
+
readonly workrailSessionId?: string;
|
|
111
|
+
}
|
|
104
112
|
export interface AgentStuckEvent {
|
|
105
113
|
readonly kind: 'agent_stuck';
|
|
106
114
|
readonly sessionId: string;
|
|
@@ -110,7 +118,7 @@ export interface AgentStuckEvent {
|
|
|
110
118
|
readonly argsSummary?: string;
|
|
111
119
|
readonly workrailSessionId?: string;
|
|
112
120
|
}
|
|
113
|
-
export type DaemonEvent = DaemonStartedEvent | TriggerFiredEvent | SessionQueuedEvent | SessionStartedEvent | ToolCalledEvent | ToolErrorEvent | StepAdvancedEvent | SessionCompletedEvent | DeliveryAttemptedEvent | IssueReportedEvent | LlmTurnStartedEvent | LlmTurnCompletedEvent | ToolCallStartedEvent | ToolCallCompletedEvent | ToolCallFailedEvent | AgentStuckEvent;
|
|
121
|
+
export type DaemonEvent = DaemonStartedEvent | TriggerFiredEvent | SessionQueuedEvent | SessionStartedEvent | ToolCalledEvent | ToolErrorEvent | StepAdvancedEvent | SessionCompletedEvent | DeliveryAttemptedEvent | IssueReportedEvent | LlmTurnStartedEvent | LlmTurnCompletedEvent | ToolCallStartedEvent | ToolCallCompletedEvent | ToolCallFailedEvent | AgentStuckEvent | SignalEmittedEvent;
|
|
114
122
|
export declare class DaemonEventEmitter {
|
|
115
123
|
private readonly _dir;
|
|
116
124
|
constructor(dirOverride?: string);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const DAEMON_SOUL_DEFAULT = "- Write code that follows the patterns already established in the codebase\n- Never skip tests. Run existing tests before and after changes\n- Prefer small, focused changes over large rewrites\n- If a step asks you to write code, write actual code -- do not write pseudocode or placeholders\n- Commit your work when you complete a logical unit";
|
|
2
|
-
export declare const DAEMON_SOUL_TEMPLATE = "# WorkRail Daemon Soul\n#\n# This file is injected into every WorkRail Auto daemon session system prompt under\n# \"## Agent Rules and Philosophy\". Edit it to customize the agent's behavior for\n# your environment: coding conventions, commit style, tool preferences, etc.\n#\n# Changes take effect on the next daemon session -- no restart required.\n#\n# The defaults below reflect general best practices. Override them freely.\n\n- Write code that follows the patterns already established in the codebase\n- Never skip tests. Run existing tests before and after changes\n- Prefer small, focused changes over large rewrites\n- If a step asks you to write code, write actual code -- do not write pseudocode or placeholders\n- Commit your work when you complete a logical unit\n";
|
|
1
|
+
export declare const DAEMON_SOUL_DEFAULT = "- Write code that follows the patterns already established in the codebase\n- Never skip tests. Run existing tests before and after changes\n- Prefer small, focused changes over large rewrites\n- If a step asks you to write code, write actual code -- do not write pseudocode or placeholders\n- Commit your work when you complete a logical unit\n\n## File work\n- File search: Use Glob (NOT find or ls)\n- Content search: Use Grep (NOT grep or rg)\n- Read files: Use Read (NOT cat/head/tail)\n- Edit files: Use Edit (NOT sed/awk)\n- Write files: Use Write (NOT echo >/cat <<EOF)\n- Always Read a file before Edit. Edit requires the file to have been read in this session.\n- Use Edit for targeted in-place changes. Use Write only for new files or full rewrites.\n- Grep output_mode: use \"files_with_matches\" to find which files, then Read the relevant ones.";
|
|
2
|
+
export declare const DAEMON_SOUL_TEMPLATE = "# WorkRail Daemon Soul\n#\n# This file is injected into every WorkRail Auto daemon session system prompt under\n# \"## Agent Rules and Philosophy\". Edit it to customize the agent's behavior for\n# your environment: coding conventions, commit style, tool preferences, etc.\n#\n# Changes take effect on the next daemon session -- no restart required.\n#\n# The defaults below reflect general best practices. Override them freely.\n\n- Write code that follows the patterns already established in the codebase\n- Never skip tests. Run existing tests before and after changes\n- Prefer small, focused changes over large rewrites\n- If a step asks you to write code, write actual code -- do not write pseudocode or placeholders\n- Commit your work when you complete a logical unit\n\n## File work\n- File search: Use Glob (NOT find or ls)\n- Content search: Use Grep (NOT grep or rg)\n- Read files: Use Read (NOT cat/head/tail)\n- Edit files: Use Edit (NOT sed/awk)\n- Write files: Use Write (NOT echo >/cat <<EOF)\n- Always Read a file before Edit. Edit requires the file to have been read in this session.\n- Use Edit for targeted in-place changes. Use Write only for new files or full rewrites.\n- Grep output_mode: use \"files_with_matches\" to find which files, then Read the relevant ones.\n";
|
|
@@ -6,7 +6,17 @@ exports.DAEMON_SOUL_DEFAULT = `\
|
|
|
6
6
|
- Never skip tests. Run existing tests before and after changes
|
|
7
7
|
- Prefer small, focused changes over large rewrites
|
|
8
8
|
- If a step asks you to write code, write actual code -- do not write pseudocode or placeholders
|
|
9
|
-
- Commit your work when you complete a logical unit
|
|
9
|
+
- Commit your work when you complete a logical unit
|
|
10
|
+
|
|
11
|
+
## File work
|
|
12
|
+
- File search: Use Glob (NOT find or ls)
|
|
13
|
+
- Content search: Use Grep (NOT grep or rg)
|
|
14
|
+
- Read files: Use Read (NOT cat/head/tail)
|
|
15
|
+
- Edit files: Use Edit (NOT sed/awk)
|
|
16
|
+
- Write files: Use Write (NOT echo >/cat <<EOF)
|
|
17
|
+
- Always Read a file before Edit. Edit requires the file to have been read in this session.
|
|
18
|
+
- Use Edit for targeted in-place changes. Use Write only for new files or full rewrites.
|
|
19
|
+
- Grep output_mode: use "files_with_matches" to find which files, then Read the relevant ones.`;
|
|
10
20
|
exports.DAEMON_SOUL_TEMPLATE = `\
|
|
11
21
|
# WorkRail Daemon Soul
|
|
12
22
|
#
|