nx 23.3.0-beta.0 → 23.3.0-beta.1
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/src/command-line/format/format.js +2 -18
- package/dist/src/command-line/graph/graph.js +42 -3
- package/dist/src/command-line/init/implementation/format.js +2 -3
- package/dist/src/command-line/migrate/agentic/definitions.js +75 -19
- package/dist/src/command-line/migrate/agentic/format-command.d.ts +14 -0
- package/dist/src/command-line/migrate/agentic/format-command.js +44 -0
- package/dist/src/command-line/migrate/agentic/handoff-gitignore.d.ts +3 -3
- package/dist/src/command-line/migrate/agentic/handoff-gitignore.js +3 -3
- package/dist/src/command-line/migrate/agentic/handoff.d.ts +27 -17
- package/dist/src/command-line/migrate/agentic/handoff.js +107 -24
- package/dist/src/command-line/migrate/agentic/instruction-files.d.ts +21 -0
- package/dist/src/command-line/migrate/agentic/instruction-files.js +36 -0
- package/dist/src/command-line/migrate/agentic/prompts/fragments.d.ts +16 -0
- package/dist/src/command-line/migrate/agentic/prompts/fragments.js +70 -0
- package/dist/src/command-line/migrate/agentic/prompts/generic-validation.js +4 -3
- package/dist/src/command-line/migrate/agentic/prompts/system-prompt.d.ts +25 -1
- package/dist/src/command-line/migrate/agentic/prompts/system-prompt.js +57 -34
- package/dist/src/command-line/migrate/agentic/run-step.js +28 -4
- package/dist/src/command-line/migrate/agentic/runner.d.ts +17 -8
- package/dist/src/command-line/migrate/agentic/runner.js +110 -55
- package/dist/src/command-line/migrate/agentic/types.d.ts +23 -8
- package/dist/src/command-line/migrate/agentic/types.js +10 -7
- package/dist/src/command-line/migrate/migrate-commits.d.ts +1 -0
- package/dist/src/command-line/migrate/migrate-commits.js +9 -0
- package/dist/src/command-line/migrate/migrate-output.d.ts +3 -7
- package/dist/src/command-line/migrate/migrate-output.js +3 -7
- package/dist/src/command-line/migrate/migrate.js +21 -8
- package/dist/src/command-line/migrate/run/agent-output.d.ts +10 -0
- package/dist/src/command-line/migrate/run/agent-output.js +16 -2
- package/dist/src/command-line/migrate/run/agent-work-payload.d.ts +37 -0
- package/dist/src/command-line/migrate/run/agent-work-payload.js +142 -0
- package/dist/src/command-line/migrate/run/atomic-write.d.ts +8 -0
- package/dist/src/command-line/migrate/run/atomic-write.js +17 -0
- package/dist/src/command-line/migrate/run/index.d.ts +1 -1
- package/dist/src/command-line/migrate/run/issues.d.ts +106 -0
- package/dist/src/command-line/migrate/run/issues.js +980 -0
- package/dist/src/command-line/migrate/run/orchestrator.d.ts +1 -0
- package/dist/src/command-line/migrate/run/orchestrator.js +662 -105
- package/dist/src/command-line/migrate/run/run-state.d.ts +43 -8
- package/dist/src/command-line/migrate/run/run-state.js +184 -20
- package/dist/src/command-line/migrate/run/runbook.d.ts +17 -0
- package/dist/src/command-line/migrate/run/runbook.js +97 -0
- package/dist/src/command-line/migrate/run/state-lock.d.ts +2 -2
- package/dist/src/command-line/migrate/run/state-lock.js +2 -2
- package/dist/src/command-line/migrate/run/state-machine.d.ts +5 -1
- package/dist/src/command-line/migrate/run/state-machine.js +27 -1
- package/dist/src/command-line/migrate/run/worker.js +206 -56
- package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +5 -1
- package/dist/src/command-line/release/publish.js +11 -4
- package/dist/src/command-line/release/utils/remote-release-clients/github.js +3 -3
- package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +3 -3
- package/dist/src/command-line/release/utils/remote-release-clients/remote-release-client.d.ts +2 -0
- package/dist/src/command-line/release/utils/remote-release-clients/remote-release-client.js +35 -0
- package/dist/src/command-line/report/report.d.ts +2 -0
- package/dist/src/command-line/report/report.js +28 -1
- package/dist/src/command-line/yargs-utils/shared-options.d.ts +1 -1
- package/dist/src/command-line/yargs-utils/shared-options.js +22 -4
- package/dist/src/config/workspace-json-project-json.d.ts +2 -0
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/daemon/client/client.d.ts +4 -2
- package/dist/src/daemon/client/client.js +20 -11
- package/dist/src/daemon/message-types/run-tasks-execution-hooks.d.ts +3 -2
- package/dist/src/daemon/server/handle-hash-tasks.d.ts +8 -2
- package/dist/src/daemon/server/handle-hash-tasks.js +16 -3
- package/dist/src/daemon/server/handle-outputs-changes.d.ts +2 -2
- package/dist/src/daemon/server/handle-outputs-changes.js +21 -6
- package/dist/src/daemon/server/handle-tasks-execution-hooks.d.ts +3 -2
- package/dist/src/daemon/server/outputs-tracking.d.ts +6 -0
- package/dist/src/daemon/server/outputs-tracking.js +18 -0
- package/dist/src/daemon/server/project-graph-incremental-recomputation.d.ts +22 -7
- package/dist/src/daemon/server/project-graph-incremental-recomputation.js +99 -69
- package/dist/src/daemon/server/restart-checks.d.ts +22 -0
- package/dist/src/daemon/server/restart-checks.js +77 -0
- package/dist/src/daemon/server/server.js +28 -18
- package/dist/src/daemon/server/shutdown-utils.d.ts +0 -5
- package/dist/src/daemon/server/shutdown-utils.js +3 -26
- package/dist/src/hasher/hash-task.js +20 -16
- package/dist/src/hasher/native-task-hasher-impl.d.ts +8 -0
- package/dist/src/hasher/native-task-hasher-impl.js +46 -3
- package/dist/src/hasher/task-hasher.d.ts +9 -0
- package/dist/src/hasher/task-hasher.js +14 -0
- package/dist/src/native/index.d.ts +202 -21
- package/dist/src/native/index.js +3 -3
- package/dist/src/native/native-bindings.js +3 -1
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/plugins/js/lock-file/pnpm-parser.js +1 -1
- package/dist/src/plugins/js/lock-file/utils/pnpm-normalizer.d.ts +2 -0
- package/dist/src/plugins/js/lock-file/utils/pnpm-normalizer.js +22 -0
- package/dist/src/plugins/js/utils/register.js +2 -1
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +2 -1
- package/dist/src/project-graph/plugins/isolation/messaging.d.ts +3 -2
- package/dist/src/project-graph/plugins/isolation/plugin-worker.js +5 -1
- package/dist/src/project-graph/plugins/loaded-nx-plugin.d.ts +3 -2
- package/dist/src/project-graph/plugins/loaded-nx-plugin.js +5 -1
- package/dist/src/project-graph/plugins/task-results-stub.d.ts +50 -0
- package/dist/src/project-graph/plugins/task-results-stub.js +111 -0
- package/dist/src/project-graph/plugins/tasks-execution-hooks.d.ts +3 -2
- package/dist/src/project-graph/plugins/tasks-execution-hooks.js +2 -1
- package/dist/src/project-graph/project-graph.js +2 -0
- package/dist/src/tasks-runner/batch/run-batch.js +15 -0
- package/dist/src/tasks-runner/cache.d.ts +35 -0
- package/dist/src/tasks-runner/cache.js +40 -0
- package/dist/src/tasks-runner/create-task-graph.js +1 -0
- package/dist/src/tasks-runner/default-tasks-runner.js +1 -1
- package/dist/src/tasks-runner/forked-process-task-runner.d.ts +1 -1
- package/dist/src/tasks-runner/forked-process-task-runner.js +8 -7
- package/dist/src/tasks-runner/init-tasks-runner.js +5 -1
- package/dist/src/tasks-runner/is-tui-enabled.js +4 -3
- package/dist/src/tasks-runner/life-cycle.d.ts +7 -0
- package/dist/src/tasks-runner/life-cycle.js +7 -0
- package/dist/src/tasks-runner/life-cycles/summary-terminal-output-life-cycle.d.ts +35 -0
- package/dist/src/tasks-runner/life-cycles/summary-terminal-output-life-cycle.js +146 -0
- package/dist/src/tasks-runner/prune-task-graph.d.ts +17 -0
- package/dist/src/tasks-runner/prune-task-graph.js +45 -0
- package/dist/src/tasks-runner/run-command.js +14 -3
- package/dist/src/tasks-runner/running-tasks/batch-process.d.ts +93 -19
- package/dist/src/tasks-runner/running-tasks/batch-process.js +184 -88
- package/dist/src/tasks-runner/task-orchestrator.d.ts +64 -2
- package/dist/src/tasks-runner/task-orchestrator.js +232 -46
- package/dist/src/tasks-runner/terminal-output-path.d.ts +11 -0
- package/dist/src/tasks-runner/terminal-output-path.js +18 -0
- package/dist/src/tasks-runner/utils.js +5 -2
- package/dist/src/utils/ab-testing.d.ts +3 -0
- package/dist/src/utils/ab-testing.js +11 -3
- package/dist/src/utils/chunkify.d.ts +1 -7
- package/dist/src/utils/chunkify.js +2 -8
- package/dist/src/utils/command-line-utils.d.ts +19 -0
- package/dist/src/utils/command-line-utils.js +10 -3
- package/dist/src/utils/formatters/index.d.ts +6 -0
- package/dist/src/utils/formatters/index.js +5 -0
- package/dist/src/utils/formatters/prettier.d.ts +0 -8
- package/dist/src/utils/formatters/prettier.js +4 -26
- package/dist/src/utils/installed-nx-version.js +2 -48
- package/dist/src/utils/min-release-age/packument.js +1 -1
- package/dist/src/utils/output.d.ts +22 -3
- package/dist/src/utils/output.js +28 -5
- package/dist/src/utils/package-json.d.ts +25 -0
- package/dist/src/utils/package-json.js +41 -6
- package/dist/src/utils/package-manager.d.ts +6 -0
- package/dist/src/utils/package-manager.js +10 -0
- package/dist/src/utils/provenance.js +20 -4
- package/dist/src/utils/workspace-context.d.ts +47 -3
- package/dist/src/utils/workspace-context.js +162 -10
- package/package.json +12 -12
- package/schemas/nx-schema.json +4 -0
- package/schemas/project-schema.json +4 -0
- package/dist/src/daemon/server/file-watching/route-workspace-changes.d.ts +0 -9
- package/dist/src/daemon/server/file-watching/route-workspace-changes.js +0 -76
- package/dist/src/daemon/server/watcher.d.ts +0 -18
- package/dist/src/daemon/server/watcher.js +0 -126
|
@@ -18,15 +18,6 @@ const ignore_1 = require("../../utils/ignore");
|
|
|
18
18
|
const object_sort_1 = require("../../utils/object-sort");
|
|
19
19
|
const output_1 = require("../../utils/output");
|
|
20
20
|
const workspace_root_1 = require("../../utils/workspace-root");
|
|
21
|
-
/**
|
|
22
|
-
* A table, not a `switch`: this lookup sits inside a `try` whose `catch`
|
|
23
|
-
* reports "configured but not installed", and a `never` arm would throw into
|
|
24
|
-
* that catch and be misreported. A missing member is a compile error here.
|
|
25
|
-
*/
|
|
26
|
-
const resolveFormatterBin = {
|
|
27
|
-
oxfmt: oxfmt_1.getOxfmtBinPath,
|
|
28
|
-
prettier: prettier_1.getPrettierPath,
|
|
29
|
-
};
|
|
30
21
|
async function format(command, args) {
|
|
31
22
|
const formatterType = (0, formatters_1.detectFormatter)(workspace_root_1.workspaceRoot);
|
|
32
23
|
if (!formatterType) {
|
|
@@ -41,7 +32,7 @@ async function format(command, args) {
|
|
|
41
32
|
// node_modules. Resolving now turns a raw MODULE_NOT_FOUND into something
|
|
42
33
|
// actionable.
|
|
43
34
|
try {
|
|
44
|
-
resolveFormatterBin[formatterType]();
|
|
35
|
+
formatters_1.resolveFormatterBin[formatterType]();
|
|
45
36
|
}
|
|
46
37
|
catch {
|
|
47
38
|
output_1.output.error({
|
|
@@ -53,19 +44,12 @@ async function format(command, args) {
|
|
|
53
44
|
process.exit(1);
|
|
54
45
|
}
|
|
55
46
|
const { nxArgs } = (0, command_line_utils_1.splitArgsIntoNxArgsAndOverrides)(args, 'affected', { printWarnings: false }, (0, configuration_1.readNxJson)());
|
|
56
|
-
// Patterns are kept raw here. Prettier is invoked through a shell so it
|
|
57
|
-
// quotes them at the call site; oxfmt is invoked with execFile and needs
|
|
58
|
-
// the unquoted paths.
|
|
59
47
|
const patterns = await getPatterns(formatterType, {
|
|
60
48
|
...args,
|
|
61
49
|
...nxArgs,
|
|
62
50
|
});
|
|
63
51
|
// Chunkify the patterns array to prevent crashing the windows terminal.
|
|
64
|
-
|
|
65
|
-
// chunks against that; oxfmt goes through execFile and gets them raw.
|
|
66
|
-
const chunkList = (0, chunkify_1.chunkify)(patterns, undefined, formatterType === 'prettier'
|
|
67
|
-
? (pattern) => (0, prettier_1.quoteForShell)(pattern).length
|
|
68
|
-
: undefined);
|
|
52
|
+
const chunkList = (0, chunkify_1.chunkify)(patterns);
|
|
69
53
|
switch (command) {
|
|
70
54
|
case 'write':
|
|
71
55
|
if (nxArgs.sortRootTsconfigPaths) {
|
|
@@ -850,17 +850,51 @@ async function getExpandedTaskInputs(depGraphClientResponse, expandedTaskInputsC
|
|
|
850
850
|
expandedTaskInputsCache.set(taskId, result);
|
|
851
851
|
return result;
|
|
852
852
|
}
|
|
853
|
+
// A brace group ({a,b}.json) carries commas, so a glob list joined with
|
|
854
|
+
// commas can only be split at brace depth zero.
|
|
855
|
+
function splitGlobGroup(group) {
|
|
856
|
+
const globs = [];
|
|
857
|
+
let current = '';
|
|
858
|
+
let depth = 0;
|
|
859
|
+
for (const char of group) {
|
|
860
|
+
if (char === '{') {
|
|
861
|
+
depth++;
|
|
862
|
+
}
|
|
863
|
+
else if (char === '}' && depth > 0) {
|
|
864
|
+
depth--;
|
|
865
|
+
}
|
|
866
|
+
if (char === ',' && depth === 0) {
|
|
867
|
+
globs.push(current);
|
|
868
|
+
current = '';
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
current += char;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
globs.push(current);
|
|
875
|
+
// An unbalanced `{` would swallow the rest of the list; a plain split is
|
|
876
|
+
// the better guess then.
|
|
877
|
+
return depth === 0 ? globs : group.split(',');
|
|
878
|
+
}
|
|
853
879
|
function expandInputs(inputs, project, allWorkspaceFiles, depGraphClientResponse) {
|
|
854
880
|
const projectNames = depGraphClientResponse.projects.map((p) => p.name);
|
|
855
881
|
const workspaceRootInputs = [];
|
|
856
882
|
const projectRootInputs = [];
|
|
857
883
|
const externalInputs = [];
|
|
858
884
|
const otherInputs = [];
|
|
885
|
+
const filesInputs = [];
|
|
859
886
|
inputs.forEach((input) => {
|
|
860
887
|
// grouped workspace inputs look like workspace:[pattern,otherPattern]
|
|
861
888
|
if (input.startsWith('workspace:[')) {
|
|
862
|
-
|
|
863
|
-
|
|
889
|
+
workspaceRootInputs.push(...splitGlobGroup(input.substring(11, input.length - 1)));
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
// Disk-backed groups look like files:[glob,!otherGlob]. They expand on
|
|
893
|
+
// disk, so they must be matched before the `:` catch-all below classifies
|
|
894
|
+
// them as external dependencies.
|
|
895
|
+
const diskBacked = /^files:\[(.*)\]$/.exec(input);
|
|
896
|
+
if (diskBacked) {
|
|
897
|
+
filesInputs.push(splitGlobGroup(diskBacked[1]));
|
|
864
898
|
return;
|
|
865
899
|
}
|
|
866
900
|
const maybeProjectName = input.split(':')[0];
|
|
@@ -881,6 +915,7 @@ function expandInputs(inputs, project, allWorkspaceFiles, depGraphClientResponse
|
|
|
881
915
|
}
|
|
882
916
|
});
|
|
883
917
|
const workspaceRootsExpanded = getExpandedWorkspaceRoots(workspaceRootInputs, allWorkspaceFiles);
|
|
918
|
+
const filesExpanded = filesInputs.flatMap((globs) => (0, native_1.expandFilesInput)(workspace_root_1.workspaceRoot, globs));
|
|
884
919
|
const otherInputsExpanded = otherInputs.map((input) => {
|
|
885
920
|
if (input === 'TsConfig') {
|
|
886
921
|
return (0, node_path_1.relative)(workspace_root_1.workspaceRoot, (0, typescript_1.getRootTsConfigPath)());
|
|
@@ -908,7 +943,11 @@ function expandInputs(inputs, project, allWorkspaceFiles, depGraphClientResponse
|
|
|
908
943
|
return acc;
|
|
909
944
|
}, {});
|
|
910
945
|
return {
|
|
911
|
-
general: [
|
|
946
|
+
general: [
|
|
947
|
+
...workspaceRootsExpanded,
|
|
948
|
+
...filesExpanded,
|
|
949
|
+
...otherInputsExpanded,
|
|
950
|
+
],
|
|
912
951
|
...projectRootsExpanded,
|
|
913
952
|
external: externalInputs,
|
|
914
953
|
};
|
|
@@ -72,8 +72,7 @@ command = 'nx init') {
|
|
|
72
72
|
// already finished.
|
|
73
73
|
//
|
|
74
74
|
// Chunked as `nx format` chunks: the Angular flow records a `project.json`
|
|
75
|
-
// per project.
|
|
76
|
-
// goes through execFile and gets raw paths.
|
|
75
|
+
// per project.
|
|
77
76
|
let formatter = null;
|
|
78
77
|
try {
|
|
79
78
|
formatter = (0, formatters_1.detectFormatter)(repoRoot);
|
|
@@ -93,7 +92,7 @@ command = 'nx init') {
|
|
|
93
92
|
// batch, but exits 2 on one unsupported file, which would end a
|
|
94
93
|
// successful init with a spurious warning and prettier's own stderr.
|
|
95
94
|
const supported = await (0, prettier_1.filterToPrettierSupportedFiles)(files);
|
|
96
|
-
for (const chunk of (0, chunkify_1.chunkify)(supported
|
|
95
|
+
for (const chunk of (0, chunkify_1.chunkify)(supported)) {
|
|
97
96
|
if (chunk.length) {
|
|
98
97
|
(0, prettier_1.writeWithPrettier)(chunk, repoRoot);
|
|
99
98
|
}
|
|
@@ -4,6 +4,7 @@ exports.AGENT_DEFINITIONS = exports.opencodeDefinition = exports.codexDefinition
|
|
|
4
4
|
exports.getAgentDefinition = getAgentDefinition;
|
|
5
5
|
const os_1 = require("os");
|
|
6
6
|
const path_1 = require("path");
|
|
7
|
+
const smol_toml_1 = require("smol-toml");
|
|
7
8
|
const types_1 = require("./types");
|
|
8
9
|
// --- Claude Code ---------------------------------------------------------
|
|
9
10
|
function claudeCodeWellKnownPaths() {
|
|
@@ -49,15 +50,14 @@ function claudeCodeHandoffAllowedTools(runDirName) {
|
|
|
49
50
|
function claudeCodeBuildInteractive(ctx) {
|
|
50
51
|
const allowedTools = claudeCodeHandoffAllowedTools(ctx.runDirName);
|
|
51
52
|
return {
|
|
52
|
-
// `--allowedTools` is variadic
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
// (`--system-prompt`) between them and the user prompt.
|
|
53
|
+
// `--allowedTools` is variadic, so a positional right after it is read as
|
|
54
|
+
// another rule: keep the rules in one comma-joined element and a flag
|
|
55
|
+
// between them and the instructions pointer.
|
|
56
56
|
args: [
|
|
57
57
|
...(allowedTools ? ['--allowedTools', allowedTools] : []),
|
|
58
|
-
'--system-prompt',
|
|
59
|
-
ctx.
|
|
60
|
-
ctx.
|
|
58
|
+
'--system-prompt-file',
|
|
59
|
+
ctx.systemPromptFilePath,
|
|
60
|
+
ctx.instructionsPointer,
|
|
61
61
|
],
|
|
62
62
|
cwd: ctx.workspaceRoot,
|
|
63
63
|
};
|
|
@@ -74,14 +74,41 @@ function codexWellKnownPaths() {
|
|
|
74
74
|
return [];
|
|
75
75
|
}
|
|
76
76
|
// No handoff permission flag: codex's default sandbox already allows writes
|
|
77
|
-
// inside the cwd tree
|
|
78
|
-
//
|
|
77
|
+
// inside the cwd tree, and a user-hardened read-only config is theirs to keep.
|
|
78
|
+
//
|
|
79
|
+
// `-c model_instructions_file` replaces codex's built-in instructions rather
|
|
80
|
+
// than adding this context to them, so the context stays on the command line,
|
|
81
|
+
// reduced rather than the full prompt.
|
|
79
82
|
function codexBuildInteractive(ctx) {
|
|
80
83
|
return {
|
|
81
|
-
args: [
|
|
84
|
+
args: [
|
|
85
|
+
'-c',
|
|
86
|
+
`developer_instructions=${encodeTomlString(ctx.inlineSystemContext)}`,
|
|
87
|
+
ctx.instructionsPointer,
|
|
88
|
+
],
|
|
82
89
|
cwd: ctx.workspaceRoot,
|
|
83
90
|
};
|
|
84
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Encodes a single-line TOML string for codex overrides. Codex treats a parse
|
|
94
|
+
* failure as literal text instead of reporting it, so the result is parsed
|
|
95
|
+
* back before it reaches the command line.
|
|
96
|
+
*/
|
|
97
|
+
function encodeTomlString(value) {
|
|
98
|
+
// JSON escapes every control TOML rejects except DEL, which it leaves raw.
|
|
99
|
+
const encoded = JSON.stringify(value).replace(/\x7f/g, '\\u007F');
|
|
100
|
+
let decoded;
|
|
101
|
+
try {
|
|
102
|
+
decoded = (0, smol_toml_1.parse)(`value = ${encoded}`).value;
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
throw new Error(`Could not encode the agent's system context as TOML for OpenAI Codex: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
106
|
+
}
|
|
107
|
+
if (decoded !== value) {
|
|
108
|
+
throw new Error(`Encoding the agent's system context as TOML for OpenAI Codex did not round-trip; refusing to run the agent on altered instructions.`);
|
|
109
|
+
}
|
|
110
|
+
return encoded;
|
|
111
|
+
}
|
|
85
112
|
exports.codexDefinition = {
|
|
86
113
|
id: 'codex',
|
|
87
114
|
displayName: 'OpenAI Codex',
|
|
@@ -110,25 +137,54 @@ function opencodeWellKnownPaths() {
|
|
|
110
137
|
return candidates;
|
|
111
138
|
}
|
|
112
139
|
// No handoff permission config: opencode's `edit` permission defaults to
|
|
113
|
-
// allow, and injecting one would clobber
|
|
114
|
-
//
|
|
140
|
+
// allow, and injecting one would clobber a user's own patterns rather than
|
|
141
|
+
// merge with them.
|
|
142
|
+
//
|
|
143
|
+
// The config travels through OPENCODE_CONFIG_CONTENT rather than
|
|
144
|
+
// OPENCODE_CONFIG. Both merge with the user's own config, but the runner
|
|
145
|
+
// spreads `env` over `process.env`, so the latter would overwrite a value the
|
|
146
|
+
// user had set.
|
|
115
147
|
function opencodeBuildInteractive(ctx) {
|
|
116
|
-
const config = {
|
|
117
|
-
agent: {
|
|
118
|
-
[OPENCODE_TRANSIENT_AGENT_NAME]: { prompt: ctx.systemContext },
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
148
|
return {
|
|
122
149
|
args: [
|
|
123
150
|
'--agent',
|
|
124
151
|
OPENCODE_TRANSIENT_AGENT_NAME,
|
|
125
152
|
'--prompt',
|
|
126
|
-
ctx.
|
|
153
|
+
ctx.instructionsPointer,
|
|
127
154
|
],
|
|
128
|
-
env: { OPENCODE_CONFIG_CONTENT:
|
|
155
|
+
env: { OPENCODE_CONFIG_CONTENT: opencodeConfigContent(ctx) },
|
|
129
156
|
cwd: ctx.workspaceRoot,
|
|
130
157
|
};
|
|
131
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Escapes inline prompt patterns before opencode's pre-JSON substitution pass.
|
|
161
|
+
* Excludes generator instructions from the config; cmd.exe drops inherited
|
|
162
|
+
* variables longer than 8191 characters.
|
|
163
|
+
*/
|
|
164
|
+
function opencodeConfigContent(ctx) {
|
|
165
|
+
// Windows separators become `/`, which its APIs accept just as well.
|
|
166
|
+
// Elsewhere a `\` belongs to the file name.
|
|
167
|
+
const filePath = process.platform === 'win32'
|
|
168
|
+
? ctx.systemPromptFilePath.replace(/\\/g, '/')
|
|
169
|
+
: ctx.systemPromptFilePath;
|
|
170
|
+
const prompt = isSubstitutionSafePath(filePath)
|
|
171
|
+
? JSON.stringify(`{file:${filePath}}`)
|
|
172
|
+
: // `\u007b` starts no pattern and JSON decodes it back to `{`, so the
|
|
173
|
+
// prompt survives expansion whatever the workspace path put in it.
|
|
174
|
+
JSON.stringify(ctx.systemPrompt).replace(/\{/g, '\\u007b');
|
|
175
|
+
// Assembled rather than serialized whole: `JSON.stringify` over the object
|
|
176
|
+
// would re-escape the backslashes the `\u007b` encoding introduced.
|
|
177
|
+
return `{"agent":{${JSON.stringify(OPENCODE_TRANSIENT_AGENT_NAME)}:{"prompt":${prompt}}}}`;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Whether opencode would read back the path nx wrote. A `}` closes the
|
|
181
|
+
* reference early, and a character JSON escapes arrives escaped. Braces are
|
|
182
|
+
* rejected wholesale rather than matched against `{env:` and `{file:`, since a
|
|
183
|
+
* pattern opening inside the path swallows the reference's closing brace.
|
|
184
|
+
*/
|
|
185
|
+
function isSubstitutionSafePath(filePath) {
|
|
186
|
+
return (!/[{}]/.test(filePath) && JSON.stringify(filePath).slice(1, -1) === filePath);
|
|
187
|
+
}
|
|
132
188
|
exports.opencodeDefinition = {
|
|
133
189
|
id: 'opencode',
|
|
134
190
|
displayName: 'OpenCode',
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { FormatterType } from '../../../utils/formatters';
|
|
2
|
+
/**
|
|
3
|
+
* The command the agent runs over the files it changed, with a `<paths>`
|
|
4
|
+
* placeholder. `--` keeps a path that starts with `-` from being read as an
|
|
5
|
+
* option.
|
|
6
|
+
*/
|
|
7
|
+
export declare function formatCommandFor(formatter: FormatterType, pmExec: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Resolved per dispense so a migration's formatter change takes effect; the
|
|
10
|
+
* scope rules name the replacement commands for the step that made it. `null`
|
|
11
|
+
* when no formatter is configured or its binary cannot be resolved: under npm
|
|
12
|
+
* the command would otherwise make `npx` download it.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveFormatCommand(root: string, pmExec: string): string | null;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatCommandFor = formatCommandFor;
|
|
4
|
+
exports.resolveFormatCommand = resolveFormatCommand;
|
|
5
|
+
const formatters_1 = require("../../../utils/formatters");
|
|
6
|
+
/**
|
|
7
|
+
* The command the agent runs over the files it changed, with a `<paths>`
|
|
8
|
+
* placeholder. `--` keeps a path that starts with `-` from being read as an
|
|
9
|
+
* option.
|
|
10
|
+
*/
|
|
11
|
+
function formatCommandFor(formatter, pmExec) {
|
|
12
|
+
switch (formatter) {
|
|
13
|
+
case 'prettier':
|
|
14
|
+
// --ignore-unknown skips files prettier has no parser for.
|
|
15
|
+
return `${pmExec} prettier --write --ignore-unknown -- <paths>`;
|
|
16
|
+
case 'oxfmt':
|
|
17
|
+
// oxfmt exits 2 when every path was skipped; the flag makes that
|
|
18
|
+
// success. Unlike prettier it skips unparseable files on its own.
|
|
19
|
+
return `${pmExec} oxfmt --no-error-on-unmatched-pattern -- <paths>`;
|
|
20
|
+
default: {
|
|
21
|
+
const unhandled = formatter;
|
|
22
|
+
throw new Error(`Unhandled formatter: ${unhandled}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolved per dispense so a migration's formatter change takes effect; the
|
|
28
|
+
* scope rules name the replacement commands for the step that made it. `null`
|
|
29
|
+
* when no formatter is configured or its binary cannot be resolved: under npm
|
|
30
|
+
* the command would otherwise make `npx` download it.
|
|
31
|
+
*/
|
|
32
|
+
function resolveFormatCommand(root, pmExec) {
|
|
33
|
+
const formatter = (0, formatters_1.detectFormatter)(root);
|
|
34
|
+
if (formatter === null) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
formatters_1.resolveFormatterBin[formatter]();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return formatCommandFor(formatter, pmExec);
|
|
44
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Both the agentic runner and the orchestrator write per-run scratch under
|
|
3
|
-
* `.nx/migrate-runs/<run-id>/`: handoff files in both cases, plus
|
|
4
|
-
* run state and its plan snapshots for the orchestrator. The v23
|
|
5
|
-
* `23-0-0-add-migrate-runs-to-git-ignore` adds `.nx/migrate-runs` to
|
|
3
|
+
* `.nx/migrate-runs/<run-id>/`: handoff and prompt files in both cases, plus
|
|
4
|
+
* the durable run state and its plan snapshots for the orchestrator. The v23
|
|
5
|
+
* migration `23-0-0-add-migrate-runs-to-git-ignore` adds `.nx/migrate-runs` to
|
|
6
6
|
* `.gitignore`; in its declared slot (typically late) earlier per-migration
|
|
7
7
|
* commits would absorb the scratch into the user-visible diff.
|
|
8
8
|
*
|
|
@@ -11,9 +11,9 @@ const logger_1 = require("../../../utils/logger");
|
|
|
11
11
|
const types_1 = require("./types");
|
|
12
12
|
/**
|
|
13
13
|
* Both the agentic runner and the orchestrator write per-run scratch under
|
|
14
|
-
* `.nx/migrate-runs/<run-id>/`: handoff files in both cases, plus
|
|
15
|
-
* run state and its plan snapshots for the orchestrator. The v23
|
|
16
|
-
* `23-0-0-add-migrate-runs-to-git-ignore` adds `.nx/migrate-runs` to
|
|
14
|
+
* `.nx/migrate-runs/<run-id>/`: handoff and prompt files in both cases, plus
|
|
15
|
+
* the durable run state and its plan snapshots for the orchestrator. The v23
|
|
16
|
+
* migration `23-0-0-add-migrate-runs-to-git-ignore` adds `.nx/migrate-runs` to
|
|
17
17
|
* `.gitignore`; in its declared slot (typically late) earlier per-migration
|
|
18
18
|
* commits would absorb the scratch into the user-visible diff.
|
|
19
19
|
*
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type BigIntStats } from 'fs';
|
|
1
2
|
import { HandoffFile } from './types';
|
|
2
3
|
/** Returns the run directory for a given workspace + run id (target version). */
|
|
3
4
|
export declare function runDirPath(workspaceRoot: string, runId: string): string;
|
|
@@ -19,19 +20,16 @@ export declare function mkdirSafely(dir: string, purpose: string): void;
|
|
|
19
20
|
* inspection.
|
|
20
21
|
*/
|
|
21
22
|
export declare function initRunDir(workspaceRoot: string, runId: string): string;
|
|
22
|
-
/**
|
|
23
|
-
* Absolute path of the handoff file for a migration step within a run, under
|
|
24
|
-
* the run directory's `handoffs/` subtree: the pre-authorized write scope
|
|
25
|
-
* stops there, so a handoff placed anywhere else costs an approval prompt.
|
|
26
|
-
* The package's scope (if any) becomes a real subdirectory so the package name
|
|
27
|
-
* stays readable; two packages can ship a migration with the same name without
|
|
28
|
-
* colliding because they land in different package subdirectories. Each
|
|
29
|
-
* segment is sanitized so the path is always writable on every platform.
|
|
30
|
-
*/
|
|
31
23
|
export declare function stepHandoffPath(runDir: string, migration: {
|
|
32
24
|
package: string;
|
|
33
25
|
name: string;
|
|
34
26
|
}): string;
|
|
27
|
+
/** Handoff path for a run step. No hash needed: step ids are unique within the run. */
|
|
28
|
+
export declare function runStepHandoffPath(runDir: string, stepId: string): string;
|
|
29
|
+
export declare function stepPromptsDir(runDir: string, migration: {
|
|
30
|
+
package: string;
|
|
31
|
+
name: string;
|
|
32
|
+
}): string;
|
|
35
33
|
export type HandoffReadFailureReason = 'missing' | 'read-error' | 'parse-error' | 'shape-mismatch';
|
|
36
34
|
export type HandoffReadResult = {
|
|
37
35
|
ok: true;
|
|
@@ -42,19 +40,31 @@ export type HandoffReadResult = {
|
|
|
42
40
|
detail?: string;
|
|
43
41
|
};
|
|
44
42
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
* `lstat`, not `stat`: a symlink in the handoffs dir's place would send every
|
|
44
|
+
* handoff read and removal wherever it points.
|
|
45
|
+
*/
|
|
46
|
+
export declare function handoffsDirState(handoffsDir: string): 'directory' | 'missing' | 'other';
|
|
47
|
+
/**
|
|
48
|
+
* Reads the file `stat` describes, refusing a symlink swapped in after the
|
|
49
|
+
* caller's lstat: O_NOFOLLOW fails the open with ELOOP, and O_NONBLOCK keeps a
|
|
50
|
+
* planted FIFO from blocking it. Windows has neither flag, so there the inode
|
|
51
|
+
* comparison is what catches a followed symlink. It does not guard against an
|
|
52
|
+
* unlink and recreate reusing the inode number. Read errors propagate: a file
|
|
53
|
+
* the agent cannot read must not pass.
|
|
54
|
+
*/
|
|
55
|
+
export declare function readInspectedFile(filePath: string, stat: BigIntStats, replacedMessage: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Splits "not written yet" from "written but garbage" so callers can surface a
|
|
58
|
+
* malformed handoff instead of collapsing it into the generic
|
|
59
|
+
* ambiguous-outcome prompt.
|
|
50
60
|
*/
|
|
51
|
-
export declare function readHandoffWithReason(filePath: string): HandoffReadResult;
|
|
61
|
+
export declare function readHandoffWithReason(filePath: string, handoffsDir: string): HandoffReadResult;
|
|
52
62
|
/**
|
|
53
63
|
* Convenience wrapper preserving the original null-on-any-failure contract.
|
|
54
64
|
* Used by the polling loop (`waitForValidHandoff`) where every failure mode
|
|
55
65
|
* is "keep waiting" — the file may be missing, mid-write, or being rewritten.
|
|
56
66
|
*/
|
|
57
|
-
export declare function readHandoff(filePath: string): HandoffFile | null;
|
|
67
|
+
export declare function readHandoff(filePath: string, handoffsDir: string): HandoffFile | null;
|
|
58
68
|
/**
|
|
59
69
|
* Polls for a valid handoff file. Resolves once `readHandoff` accepts the
|
|
60
70
|
* file's contents. Used to detect when the agent has finished its work so the
|
|
@@ -63,7 +73,7 @@ export declare function readHandoff(filePath: string): HandoffFile | null;
|
|
|
63
73
|
*
|
|
64
74
|
* Rejects with the abort reason when `options.signal` is aborted.
|
|
65
75
|
*/
|
|
66
|
-
export declare function waitForValidHandoff(handoffFilePath: string, options?: {
|
|
76
|
+
export declare function waitForValidHandoff(handoffFilePath: string, handoffsDir: string, options?: {
|
|
67
77
|
intervalMs?: number;
|
|
68
78
|
signal?: AbortSignal;
|
|
69
79
|
}): Promise<void>;
|
|
@@ -5,9 +5,14 @@ exports.resolveAgenticRunId = resolveAgenticRunId;
|
|
|
5
5
|
exports.mkdirSafely = mkdirSafely;
|
|
6
6
|
exports.initRunDir = initRunDir;
|
|
7
7
|
exports.stepHandoffPath = stepHandoffPath;
|
|
8
|
+
exports.runStepHandoffPath = runStepHandoffPath;
|
|
9
|
+
exports.stepPromptsDir = stepPromptsDir;
|
|
10
|
+
exports.handoffsDirState = handoffsDirState;
|
|
11
|
+
exports.readInspectedFile = readInspectedFile;
|
|
8
12
|
exports.readHandoffWithReason = readHandoffWithReason;
|
|
9
13
|
exports.readHandoff = readHandoff;
|
|
10
14
|
exports.waitForValidHandoff = waitForValidHandoff;
|
|
15
|
+
const crypto_1 = require("crypto");
|
|
11
16
|
const fs_1 = require("fs");
|
|
12
17
|
const path_1 = require("path");
|
|
13
18
|
const semver_1 = require("semver");
|
|
@@ -56,14 +61,10 @@ function initRunDir(workspaceRoot, runId) {
|
|
|
56
61
|
// a migration named `CON` would otherwise produce a `CON.json` that the
|
|
57
62
|
// agent can't write to.
|
|
58
63
|
const WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\..*)?$/i;
|
|
59
|
-
/**
|
|
60
|
-
* The bare `.` / `..` check must come first — otherwise a malformed migration
|
|
61
|
-
* name of exactly `..` would let the handoff write escape the run directory.
|
|
62
|
-
*/
|
|
63
64
|
function sanitizeSegment(value) {
|
|
64
65
|
if (value === '.' || value === '..')
|
|
65
66
|
return '_';
|
|
66
|
-
let sanitized = value.replace(/[\x00-\x1f<>:"
|
|
67
|
+
let sanitized = value.replace(/[\x00-\x1f<>:"/\\|?*+]/g, '_');
|
|
67
68
|
// Windows forbids trailing dots/spaces on file/directory names.
|
|
68
69
|
sanitized = sanitized.replace(/[. ]+$/, '');
|
|
69
70
|
if (WINDOWS_RESERVED_NAMES.test(sanitized)) {
|
|
@@ -71,29 +72,111 @@ function sanitizeSegment(value) {
|
|
|
71
72
|
}
|
|
72
73
|
return sanitized || '_';
|
|
73
74
|
}
|
|
75
|
+
const STEP_NAME_PREFIX_MAX_BYTES = 64;
|
|
76
|
+
// Cuts on code points so a multibyte character is never split, and counts
|
|
77
|
+
// UTF-8 bytes because the filesystem limit is per byte, not per character.
|
|
78
|
+
function truncateUtf8(value, maxBytes) {
|
|
79
|
+
let out = '';
|
|
80
|
+
let used = 0;
|
|
81
|
+
for (const ch of value) {
|
|
82
|
+
const bytes = Buffer.byteLength(ch);
|
|
83
|
+
if (used + bytes > maxBytes)
|
|
84
|
+
break;
|
|
85
|
+
out += ch;
|
|
86
|
+
used += bytes;
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
74
90
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* The package's scope (if any) becomes a real subdirectory so the package name
|
|
79
|
-
* stays readable; two packages can ship a migration with the same name without
|
|
80
|
-
* colliding because they land in different package subdirectories. Each
|
|
81
|
-
* segment is sanitized so the path is always writable on every platform.
|
|
91
|
+
* The name a step's own files are keyed by, bounded to fit one path component.
|
|
92
|
+
* Sanitizing folds many characters to `_` and the prefix is cut to fit, so the
|
|
93
|
+
* SHA-256 of the raw package and name is what keeps two migrations apart.
|
|
82
94
|
*/
|
|
95
|
+
function stepStem(migration) {
|
|
96
|
+
const prefix = truncateUtf8([...migration.package.split('/'), migration.name]
|
|
97
|
+
.map(sanitizeSegment)
|
|
98
|
+
.join('+'), STEP_NAME_PREFIX_MAX_BYTES);
|
|
99
|
+
const hash = (0, crypto_1.createHash)('sha256')
|
|
100
|
+
.update(JSON.stringify([migration.package, migration.name]))
|
|
101
|
+
.digest('hex');
|
|
102
|
+
return `${prefix}-${hash}`;
|
|
103
|
+
}
|
|
83
104
|
function stepHandoffPath(runDir, migration) {
|
|
84
|
-
return (0, path_1.join)(runDir, types_1.HANDOFFS_DIR_NAME,
|
|
105
|
+
return (0, path_1.join)(runDir, types_1.HANDOFFS_DIR_NAME, `${stepStem(migration)}.json`);
|
|
106
|
+
}
|
|
107
|
+
/** Handoff path for a run step. No hash needed: step ids are unique within the run. */
|
|
108
|
+
function runStepHandoffPath(runDir, stepId) {
|
|
109
|
+
return (0, path_1.join)(runDir, types_1.HANDOFFS_DIR_NAME, `${sanitizeSegment(stepId)}.json`);
|
|
110
|
+
}
|
|
111
|
+
function stepPromptsDir(runDir, migration) {
|
|
112
|
+
return (0, path_1.join)(runDir, types_1.PROMPTS_DIR_NAME, stepStem(migration));
|
|
85
113
|
}
|
|
86
114
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* distinguish "file not yet written" from "file written but garbage" — the
|
|
90
|
-
* latter is surfaced to the user instead of being collapsed into the same
|
|
91
|
-
* generic ambiguous-outcome prompt.
|
|
115
|
+
* `lstat`, not `stat`: a symlink in the handoffs dir's place would send every
|
|
116
|
+
* handoff read and removal wherever it points.
|
|
92
117
|
*/
|
|
93
|
-
function
|
|
118
|
+
function handoffsDirState(handoffsDir) {
|
|
119
|
+
try {
|
|
120
|
+
return (0, fs_1.lstatSync)(handoffsDir).isDirectory() ? 'directory' : 'other';
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
if (err?.code === 'ENOENT')
|
|
124
|
+
return 'missing';
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Reads the file `stat` describes, refusing a symlink swapped in after the
|
|
130
|
+
* caller's lstat: O_NOFOLLOW fails the open with ELOOP, and O_NONBLOCK keeps a
|
|
131
|
+
* planted FIFO from blocking it. Windows has neither flag, so there the inode
|
|
132
|
+
* comparison is what catches a followed symlink. It does not guard against an
|
|
133
|
+
* unlink and recreate reusing the inode number. Read errors propagate: a file
|
|
134
|
+
* the agent cannot read must not pass.
|
|
135
|
+
*/
|
|
136
|
+
function readInspectedFile(filePath, stat, replacedMessage) {
|
|
137
|
+
const fd = (0, fs_1.openSync)(filePath, fs_1.constants.O_RDONLY |
|
|
138
|
+
(fs_1.constants.O_NOFOLLOW ?? 0) |
|
|
139
|
+
(fs_1.constants.O_NONBLOCK ?? 0));
|
|
140
|
+
try {
|
|
141
|
+
const fdStat = (0, fs_1.fstatSync)(fd, { bigint: true });
|
|
142
|
+
if (!fdStat.isFile() ||
|
|
143
|
+
fdStat.dev !== stat.dev ||
|
|
144
|
+
fdStat.ino !== stat.ino) {
|
|
145
|
+
throw new Error(replacedMessage);
|
|
146
|
+
}
|
|
147
|
+
return (0, fs_1.readFileSync)(fd, 'utf-8');
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
(0, fs_1.closeSync)(fd);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Splits "not written yet" from "written but garbage" so callers can surface a
|
|
155
|
+
* malformed handoff instead of collapsing it into the generic
|
|
156
|
+
* ambiguous-outcome prompt.
|
|
157
|
+
*/
|
|
158
|
+
function readHandoffWithReason(filePath, handoffsDir) {
|
|
94
159
|
let raw;
|
|
95
160
|
try {
|
|
96
|
-
|
|
161
|
+
switch (handoffsDirState(handoffsDir)) {
|
|
162
|
+
case 'missing':
|
|
163
|
+
return { ok: false, reason: 'missing' };
|
|
164
|
+
case 'other':
|
|
165
|
+
return {
|
|
166
|
+
ok: false,
|
|
167
|
+
reason: 'read-error',
|
|
168
|
+
detail: `${handoffsDir} is not a directory`,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const stat = (0, fs_1.lstatSync)(filePath, { bigint: true });
|
|
172
|
+
if (!stat.isFile()) {
|
|
173
|
+
return {
|
|
174
|
+
ok: false,
|
|
175
|
+
reason: 'read-error',
|
|
176
|
+
detail: `${filePath} is not a regular file`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
raw = readInspectedFile(filePath, stat, `${filePath} was replaced while being read`);
|
|
97
180
|
}
|
|
98
181
|
catch (err) {
|
|
99
182
|
const code = err?.code;
|
|
@@ -148,8 +231,8 @@ function readHandoffWithReason(filePath) {
|
|
|
148
231
|
* Used by the polling loop (`waitForValidHandoff`) where every failure mode
|
|
149
232
|
* is "keep waiting" — the file may be missing, mid-write, or being rewritten.
|
|
150
233
|
*/
|
|
151
|
-
function readHandoff(filePath) {
|
|
152
|
-
const result = readHandoffWithReason(filePath);
|
|
234
|
+
function readHandoff(filePath, handoffsDir) {
|
|
235
|
+
const result = readHandoffWithReason(filePath, handoffsDir);
|
|
153
236
|
return result.ok ? result.handoff : null;
|
|
154
237
|
}
|
|
155
238
|
/**
|
|
@@ -160,7 +243,7 @@ function readHandoff(filePath) {
|
|
|
160
243
|
*
|
|
161
244
|
* Rejects with the abort reason when `options.signal` is aborted.
|
|
162
245
|
*/
|
|
163
|
-
function waitForValidHandoff(handoffFilePath, options = {}) {
|
|
246
|
+
function waitForValidHandoff(handoffFilePath, handoffsDir, options = {}) {
|
|
164
247
|
const intervalMs = options.intervalMs ?? 500;
|
|
165
248
|
const { signal } = options;
|
|
166
249
|
return new Promise((resolve, reject) => {
|
|
@@ -180,7 +263,7 @@ function waitForValidHandoff(handoffFilePath, options = {}) {
|
|
|
180
263
|
onAbort();
|
|
181
264
|
return;
|
|
182
265
|
}
|
|
183
|
-
if (readHandoff(handoffFilePath) !== null) {
|
|
266
|
+
if (readHandoff(handoffFilePath, handoffsDir) !== null) {
|
|
184
267
|
signal?.removeEventListener('abort', onAbort);
|
|
185
268
|
resolve();
|
|
186
269
|
return;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface StepInstructionFiles {
|
|
2
|
+
/** Absolute path: config loaders resolve it, not the agent from its cwd. */
|
|
3
|
+
systemPromptFilePath: string;
|
|
4
|
+
/** Single-line command-line text pointing the agent at its instructions. */
|
|
5
|
+
instructionsPointer: string;
|
|
6
|
+
}
|
|
7
|
+
export interface WriteStepInstructionFilesArgs {
|
|
8
|
+
workspaceRoot: string;
|
|
9
|
+
runDir: string;
|
|
10
|
+
migration: {
|
|
11
|
+
package: string;
|
|
12
|
+
name: string;
|
|
13
|
+
};
|
|
14
|
+
systemPrompt: string;
|
|
15
|
+
instructions: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Writes prompts as files on every platform to keep one delivery path.
|
|
19
|
+
* Windows npm shims cannot carry multi-line prompts within cmd.exe's limit.
|
|
20
|
+
*/
|
|
21
|
+
export declare function writeStepInstructionFiles(args: WriteStepInstructionFilesArgs): StepInstructionFiles;
|