nx 23.3.0-beta.2 → 23.3.0-beta.4
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/bin/init-local.js +7 -1
- package/dist/src/command-line/add/add.js +5 -1
- package/dist/src/command-line/init/implementation/utils.js +6 -1
- package/dist/src/command-line/migrate/agentic/capture-generator-output.d.ts +25 -0
- package/dist/src/command-line/migrate/agentic/capture-generator-output.js +121 -5
- package/dist/src/command-line/migrate/agentic/close-agent-session.d.ts +21 -0
- package/dist/src/command-line/migrate/agentic/close-agent-session.js +126 -0
- package/dist/src/command-line/migrate/agentic/definitions.d.ts +24 -0
- package/dist/src/command-line/migrate/agentic/definitions.js +5 -2
- package/dist/src/command-line/migrate/agentic/handoff.d.ts +23 -0
- package/dist/src/command-line/migrate/agentic/handoff.js +62 -3
- package/dist/src/command-line/migrate/agentic/master/invocations.d.ts +12 -0
- package/dist/src/command-line/migrate/agentic/master/invocations.js +54 -0
- package/dist/src/command-line/migrate/agentic/master/run-master-session.d.ts +12 -0
- package/dist/src/command-line/migrate/agentic/master/run-master-session.js +99 -0
- package/dist/src/command-line/migrate/agentic/master/spawn-master.d.ts +31 -0
- package/dist/src/command-line/migrate/agentic/master/spawn-master.js +217 -0
- package/dist/src/command-line/migrate/agentic/prompts/fragments.js +3 -0
- package/dist/src/command-line/migrate/agentic/runner.d.ts +0 -21
- package/dist/src/command-line/migrate/agentic/runner.js +12 -249
- package/dist/src/command-line/migrate/agentic/terminal-repair.d.ts +1 -0
- package/dist/src/command-line/migrate/agentic/terminal-repair.js +27 -0
- package/dist/src/command-line/migrate/agentic/windows-cmd.d.ts +33 -0
- package/dist/src/command-line/migrate/agentic/windows-cmd.js +88 -0
- package/dist/src/command-line/migrate/deferred-output.d.ts +44 -0
- package/dist/src/command-line/migrate/deferred-output.js +92 -0
- package/dist/src/command-line/migrate/execute-migration.d.ts +9 -3
- package/dist/src/command-line/migrate/execute-migration.js +53 -16
- package/dist/src/command-line/migrate/migrate-commits.d.ts +4 -4
- package/dist/src/command-line/migrate/migrate-commits.js +7 -6
- package/dist/src/command-line/migrate/migrate.js +28 -13
- package/dist/src/command-line/migrate/run/broker.d.ts +138 -0
- package/dist/src/command-line/migrate/run/broker.js +507 -0
- package/dist/src/command-line/migrate/run/clean-retry.d.ts +12 -0
- package/dist/src/command-line/migrate/run/clean-retry.js +98 -0
- package/dist/src/command-line/migrate/run/index.d.ts +4 -3
- package/dist/src/command-line/migrate/run/index.js +7 -1
- package/dist/src/command-line/migrate/run/issues.d.ts +8 -1
- package/dist/src/command-line/migrate/run/issues.js +40 -6
- package/dist/src/command-line/migrate/run/orchestrator.d.ts +18 -1
- package/dist/src/command-line/migrate/run/orchestrator.js +409 -377
- package/dist/src/command-line/migrate/run/run-state.d.ts +16 -0
- package/dist/src/command-line/migrate/run/run-state.js +33 -2
- package/dist/src/command-line/migrate/run/runbook.js +8 -4
- package/dist/src/command-line/migrate/run/state-machine.d.ts +24 -1
- package/dist/src/command-line/migrate/run/state-machine.js +112 -21
- package/dist/src/command-line/migrate/run/util.d.ts +2 -1
- package/dist/src/command-line/migrate/run/util.js +3 -3
- package/dist/src/command-line/migrate/run/worker.js +170 -164
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/daemon/server/server.js +11 -8
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/tasks-runner/run-command.js +3 -0
- package/dist/src/utils/git-utils.d.ts +12 -0
- package/dist/src/utils/git-utils.js +76 -30
- package/dist/src/utils/package-json.js +8 -0
- package/package.json +11 -11
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.masterInvariant = masterInvariant;
|
|
4
|
+
exports.masterBootstrapPrompt = masterBootstrapPrompt;
|
|
5
|
+
exports.buildMasterInvocation = buildMasterInvocation;
|
|
6
|
+
const definitions_1 = require("../definitions");
|
|
7
|
+
// One line with no `%`: the Windows shim refuses line breaks and the pinned
|
|
8
|
+
// text is what its command-line budget is measured against.
|
|
9
|
+
function masterInvariant(ctx) {
|
|
10
|
+
return `You are driving Nx migrate run ${ctx.runId}. Its state lives on disk and is owned by nx migrate; never infer its progress from this conversation. Whenever you are unsure what to do, after a compaction or restart, and after you finish any piece of work, run \`${ctx.reconcileCommand}\` and follow the step block it returns. The run's contract is the runbook at ${ctx.runbookPath}; re-read it whenever a step block says so. When a step block reports the run complete, tell the user the outcome, then create the file ${ctx.sentinelPath} as your last action; nx closes this session once it exists.`;
|
|
11
|
+
}
|
|
12
|
+
function masterBootstrapPrompt(ctx) {
|
|
13
|
+
return `Read the runbook at ${ctx.runbookPath} in full, then run \`${ctx.reconcileCommand}\` and follow the step block it returns.`;
|
|
14
|
+
}
|
|
15
|
+
const OPENCODE_AGENT_NAME = 'nx-migrate';
|
|
16
|
+
// Keyed by agent id so a new agent fails to compile until it gets a builder.
|
|
17
|
+
const builders = {
|
|
18
|
+
'claude-code': (ctx) => {
|
|
19
|
+
const allowedTools = (0, definitions_1.claudeCodeHandoffAllowedTools)(ctx.runId);
|
|
20
|
+
return {
|
|
21
|
+
// `--allowedTools` is variadic: the non-variadic flag between it and
|
|
22
|
+
// the positional prompt keeps the prompt from being read as a rule.
|
|
23
|
+
args: [
|
|
24
|
+
...(allowedTools ? ['--allowedTools', allowedTools] : []),
|
|
25
|
+
'--append-system-prompt',
|
|
26
|
+
masterInvariant(ctx),
|
|
27
|
+
masterBootstrapPrompt(ctx),
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
codex: (ctx) => ({
|
|
32
|
+
args: [
|
|
33
|
+
'-c',
|
|
34
|
+
`developer_instructions=${(0, definitions_1.encodeTomlString)(masterInvariant(ctx))}`,
|
|
35
|
+
masterBootstrapPrompt(ctx),
|
|
36
|
+
],
|
|
37
|
+
}),
|
|
38
|
+
opencode: (ctx) => ({
|
|
39
|
+
args: [
|
|
40
|
+
'--agent',
|
|
41
|
+
OPENCODE_AGENT_NAME,
|
|
42
|
+
'--prompt',
|
|
43
|
+
masterBootstrapPrompt(ctx),
|
|
44
|
+
],
|
|
45
|
+
env: {
|
|
46
|
+
OPENCODE_CONFIG_CONTENT: JSON.stringify({
|
|
47
|
+
agent: { [OPENCODE_AGENT_NAME]: { prompt: masterInvariant(ctx) } },
|
|
48
|
+
}),
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
function buildMasterInvocation(agentId, ctx) {
|
|
53
|
+
return builders[agentId](ctx);
|
|
54
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type RunOrchestratorInitInput } from '../../run';
|
|
2
|
+
import type { DetectedInstalledAgent } from '../types';
|
|
3
|
+
export interface RunMasterSessionInput extends Omit<RunOrchestratorInitInput, 'emitAgentInstructions'> {
|
|
4
|
+
agent: DetectedInstalledAgent;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Starts (or resumes) an orchestrated run and hands it to one agent session
|
|
8
|
+
* that drives it through `--run-id` reconciles. Run state is the only
|
|
9
|
+
* authority on the outcome: the exit code follows what run.json says once the
|
|
10
|
+
* session ends, not what the agent process returned.
|
|
11
|
+
*/
|
|
12
|
+
export declare function runMasterSession(input: RunMasterSessionInput): Promise<number | undefined>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runMasterSession = runMasterSession;
|
|
4
|
+
const output_1 = require("../../../../utils/output");
|
|
5
|
+
const migrate_analytics_1 = require("../../migrate-analytics");
|
|
6
|
+
const run_1 = require("../../run");
|
|
7
|
+
const spawn_master_1 = require("./spawn-master");
|
|
8
|
+
/**
|
|
9
|
+
* Starts (or resumes) an orchestrated run and hands it to one agent session
|
|
10
|
+
* that drives it through `--run-id` reconciles. Run state is the only
|
|
11
|
+
* authority on the outcome: the exit code follows what run.json says once the
|
|
12
|
+
* session ends, not what the agent process returned.
|
|
13
|
+
*/
|
|
14
|
+
async function runMasterSession(input) {
|
|
15
|
+
const { agent, ...init } = input;
|
|
16
|
+
const ready = await (0, run_1.runOrchestratorInit)({
|
|
17
|
+
...init,
|
|
18
|
+
emitAgentInstructions: false,
|
|
19
|
+
});
|
|
20
|
+
if (ready.kind === 'refused') {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const { runId, runRoot, runbookPath, reconcileCommand } = ready;
|
|
24
|
+
// Not rendered: the gate env var and the migrations path would have to be
|
|
25
|
+
// reproduced, and the same command carries both.
|
|
26
|
+
const resumeHint = 'Run the same nx migrate command again to resume it.';
|
|
27
|
+
output_1.output.log({
|
|
28
|
+
title: `Starting ${agent.displayName} to drive migrate run ${runId}.`,
|
|
29
|
+
});
|
|
30
|
+
const session = await (0, spawn_master_1.spawnMasterSession)({
|
|
31
|
+
agent,
|
|
32
|
+
runRoot,
|
|
33
|
+
runId,
|
|
34
|
+
runbookPath,
|
|
35
|
+
reconcileCommand,
|
|
36
|
+
policy: {
|
|
37
|
+
createCommits: init.createCommits,
|
|
38
|
+
skipInstall: init.skipInstall,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
if (session.kind === 'spawn-failed') {
|
|
42
|
+
output_1.output.error({
|
|
43
|
+
title: `Could not start ${agent.displayName}: ${session.error.message}`,
|
|
44
|
+
bodyLines: [`Migrate run ${runId} is still active. ${resumeHint}`],
|
|
45
|
+
});
|
|
46
|
+
(0, migrate_analytics_1.reportMigrateRunError)({ code: 'agentic', error: session.error });
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
if (session.kind === 'broker-failed') {
|
|
50
|
+
// The broker itself reads run.json, so its failure can mean the state is
|
|
51
|
+
// unreadable; the read below decides whether a resume is safe to offer.
|
|
52
|
+
output_1.output.error({
|
|
53
|
+
title: `Closed the ${agent.displayName} session: a step's request could not be answered (${session.error.message}).`,
|
|
54
|
+
});
|
|
55
|
+
(0, migrate_analytics_1.reportMigrateRunError)({ code: 'agentic', error: session.error });
|
|
56
|
+
}
|
|
57
|
+
let state;
|
|
58
|
+
try {
|
|
59
|
+
state = (0, run_1.readRunState)((0, run_1.runDir)(runRoot, runId));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
// No resume hint: run discovery skips a directory whose run.json is
|
|
63
|
+
// missing, so a rerun could start a second run over the same plan.
|
|
64
|
+
output_1.output.error({
|
|
65
|
+
title: `Nx could not determine whether migrate run ${runId} completed.`,
|
|
66
|
+
bodyLines: [error instanceof Error ? error.message : String(error)],
|
|
67
|
+
});
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
switch (state.status) {
|
|
71
|
+
case 'active':
|
|
72
|
+
output_1.output.warn({
|
|
73
|
+
title: `Migrate run ${runId} is still active. ${resumeHint}`,
|
|
74
|
+
});
|
|
75
|
+
return 1;
|
|
76
|
+
case 'completed': {
|
|
77
|
+
const applied = state.steps.filter((s) => s.status === 'succeeded').length;
|
|
78
|
+
const skipped = state.steps.filter((s) => s.status === 'skipped').length;
|
|
79
|
+
output_1.output.log({
|
|
80
|
+
title: `Migrate run ${runId} is complete.`,
|
|
81
|
+
bodyLines: [` applied: ${applied}`, ` skipped: ${skipped}`],
|
|
82
|
+
});
|
|
83
|
+
for (const lines of (0, run_1.completionWarnings)(runRoot, runId, state)) {
|
|
84
|
+
output_1.output.warn({ title: lines[0], bodyLines: lines.slice(1) });
|
|
85
|
+
}
|
|
86
|
+
(0, migrate_analytics_1.reportMigrateRunComplete)({
|
|
87
|
+
agenticOutcome: 'enabled',
|
|
88
|
+
agentUsed: agent.id,
|
|
89
|
+
migrationCount: state.steps.length,
|
|
90
|
+
appliedCount: applied,
|
|
91
|
+
});
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
default: {
|
|
95
|
+
const unhandled = state.status;
|
|
96
|
+
throw new Error(`Unhandled migrate run status: ${unhandled}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type MigrateRunPolicy } from '../../run';
|
|
2
|
+
import type { DetectedInstalledAgent } from '../types';
|
|
3
|
+
export interface SpawnMasterSessionInput {
|
|
4
|
+
agent: DetectedInstalledAgent;
|
|
5
|
+
runRoot: string;
|
|
6
|
+
runId: string;
|
|
7
|
+
runbookPath: string;
|
|
8
|
+
reconcileCommand: string;
|
|
9
|
+
policy: MigrateRunPolicy;
|
|
10
|
+
sentinelPollIntervalMs?: number;
|
|
11
|
+
gracefulExitMs?: number;
|
|
12
|
+
forceKillWaitMs?: number;
|
|
13
|
+
}
|
|
14
|
+
export type SpawnMasterSessionResult = {
|
|
15
|
+
kind: 'exited';
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'spawn-failed';
|
|
18
|
+
error: Error;
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'broker-failed';
|
|
21
|
+
error: Error;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Spawns the agent once, with the run's pinned invariant and bootstrap prompt,
|
|
25
|
+
* and waits for the session to end, closing it once the agent writes the
|
|
26
|
+
* session-complete sentinel. Meanwhile it answers the install and commit
|
|
27
|
+
* requests the steps the agent runs hand out of its sandbox. Every failure
|
|
28
|
+
* before the process starts is returned as `spawn-failed`; what the session
|
|
29
|
+
* did is read from run state by the caller, never from the exit code.
|
|
30
|
+
*/
|
|
31
|
+
export declare function spawnMasterSession(input: SpawnMasterSessionInput): Promise<SpawnMasterSessionResult>;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.spawnMasterSession = spawnMasterSession;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const logger_1 = require("../../../../utils/logger");
|
|
8
|
+
const output_1 = require("../../../../utils/output");
|
|
9
|
+
const migrate_output_1 = require("../../migrate-output");
|
|
10
|
+
const run_1 = require("../../run");
|
|
11
|
+
const close_agent_session_1 = require("../close-agent-session");
|
|
12
|
+
const handoff_1 = require("../handoff");
|
|
13
|
+
const terminal_repair_1 = require("../terminal-repair");
|
|
14
|
+
const windows_cmd_1 = require("../windows-cmd");
|
|
15
|
+
const invocations_1 = require("./invocations");
|
|
16
|
+
// The wrapper's local re-exec sets the first two for its own hop and the user
|
|
17
|
+
// sets the third to reach this path; inherited, they would change install or
|
|
18
|
+
// routing behavior for every `nx migrate` the agent runs.
|
|
19
|
+
const STRIPPED_ENV_VARS = [
|
|
20
|
+
'NX_MIGRATE_SKIP_INSTALL',
|
|
21
|
+
'NX_MIGRATE_USE_LOCAL',
|
|
22
|
+
'NX_MIGRATE_ORCHESTRATOR',
|
|
23
|
+
];
|
|
24
|
+
// Under handoffs/ so claude's run-scoped Edit rule already admits the write;
|
|
25
|
+
// nothing lists that directory. The nonce keeps a sentinel left by an earlier
|
|
26
|
+
// session of the same run from closing this one.
|
|
27
|
+
function sessionCompleteSentinel(runRoot, runId, nonce) {
|
|
28
|
+
return (0, path_1.join)((0, run_1.runHandoffsDir)((0, run_1.runDir)(runRoot, runId)), `session-complete-${nonce}`);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Spawns the agent once, with the run's pinned invariant and bootstrap prompt,
|
|
32
|
+
* and waits for the session to end, closing it once the agent writes the
|
|
33
|
+
* session-complete sentinel. Meanwhile it answers the install and commit
|
|
34
|
+
* requests the steps the agent runs hand out of its sandbox. Every failure
|
|
35
|
+
* before the process starts is returned as `spawn-failed`; what the session
|
|
36
|
+
* did is read from run state by the caller, never from the exit code.
|
|
37
|
+
*/
|
|
38
|
+
async function spawnMasterSession(input) {
|
|
39
|
+
const { agent, runRoot, runId, runbookPath, reconcileCommand, policy, sentinelPollIntervalMs = 500, gracefulExitMs = close_agent_session_1.AGENT_GRACEFUL_EXIT_MS, forceKillWaitMs = close_agent_session_1.FORCE_KILL_WAIT_MS, } = input;
|
|
40
|
+
let sentinelPath;
|
|
41
|
+
let child;
|
|
42
|
+
let broker;
|
|
43
|
+
try {
|
|
44
|
+
// Locked before the agent exists: a step must never find the lock free
|
|
45
|
+
// while its parent is alive.
|
|
46
|
+
broker = new run_1.MigrateCommitBroker(runRoot, (0, run_1.runDir)(runRoot, runId), reconcileCommand, policy);
|
|
47
|
+
sentinelPath = sessionCompleteSentinel(runRoot, runId, broker.nonce);
|
|
48
|
+
const spec = (0, invocations_1.buildMasterInvocation)(agent.id, {
|
|
49
|
+
runId,
|
|
50
|
+
reconcileCommand,
|
|
51
|
+
runbookPath: prosePath(runRoot, runbookPath),
|
|
52
|
+
sentinelPath: prosePath(runRoot, sentinelPath),
|
|
53
|
+
});
|
|
54
|
+
const env = { ...process.env, ...spec.env };
|
|
55
|
+
for (const name of STRIPPED_ENV_VARS) {
|
|
56
|
+
delete env[name];
|
|
57
|
+
}
|
|
58
|
+
env[run_1.BROKER_ENV_VAR] = broker.nonce;
|
|
59
|
+
const adapted = (0, windows_cmd_1.adaptSpawnForWindowsShim)(agent.binary, spec.args, {
|
|
60
|
+
stdio: 'inherit',
|
|
61
|
+
cwd: runRoot,
|
|
62
|
+
env,
|
|
63
|
+
windowsHide: true,
|
|
64
|
+
});
|
|
65
|
+
assertWithinWindowsCommandLineBudget(adapted, agent, runId);
|
|
66
|
+
// A symlink here would send the agent's write and the poll below elsewhere.
|
|
67
|
+
const handoffsDir = (0, path_1.dirname)(sentinelPath);
|
|
68
|
+
(0, handoff_1.ensureRunSubdir)(handoffsDir, () => new Error(`Migrate run ${runId} has something other than a directory at ${handoffsDir}; remove it and try again.`));
|
|
69
|
+
// Local alias so `@nx/workspace-require-windows-hide` can track the
|
|
70
|
+
// options as an Identifier.
|
|
71
|
+
const spawnOptions = adapted.options;
|
|
72
|
+
child = (0, child_process_1.spawn)(adapted.binary, adapted.args, spawnOptions);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
broker?.close();
|
|
76
|
+
return { kind: 'spawn-failed', error: toError(error) };
|
|
77
|
+
}
|
|
78
|
+
// Ctrl+C belongs to the agent from the moment it exists.
|
|
79
|
+
const swallowSigint = () => { };
|
|
80
|
+
process.on('SIGINT', swallowSigint);
|
|
81
|
+
const sentinelWatch = new AbortController();
|
|
82
|
+
let brokerFailure;
|
|
83
|
+
// Settles when the poll aborts and the request in flight is answered, or
|
|
84
|
+
// early when a request could not be answered at all.
|
|
85
|
+
const brokerDone = serviceBrokerUntilAborted(broker, sentinelPollIntervalMs, sentinelWatch.signal).catch((error) => {
|
|
86
|
+
brokerFailure = toError(error);
|
|
87
|
+
});
|
|
88
|
+
let started = false;
|
|
89
|
+
try {
|
|
90
|
+
const exitPromise = (0, close_agent_session_1.waitForExit)(child);
|
|
91
|
+
// The usual missing-binary failure is an `error` event with no `exit`
|
|
92
|
+
// (ENOENT after detection); before `spawn` it is a failure to start, not
|
|
93
|
+
// a session that ended.
|
|
94
|
+
const spawnError = await new Promise((resolve) => {
|
|
95
|
+
child.once('spawn', () => resolve(null));
|
|
96
|
+
child.once('error', (error) => resolve(error));
|
|
97
|
+
});
|
|
98
|
+
if (spawnError) {
|
|
99
|
+
return { kind: 'spawn-failed', error: spawnError };
|
|
100
|
+
}
|
|
101
|
+
started = true;
|
|
102
|
+
let exit = {};
|
|
103
|
+
const winner = await Promise.race([
|
|
104
|
+
exitPromise.then((info) => {
|
|
105
|
+
exit = info;
|
|
106
|
+
return 'exit';
|
|
107
|
+
}),
|
|
108
|
+
waitForFile(sentinelPath, sentinelPollIntervalMs, sentinelWatch.signal).then(() => 'sentinel'),
|
|
109
|
+
brokerDone.then(() => 'broker-failed'),
|
|
110
|
+
]);
|
|
111
|
+
if (winner !== 'exit') {
|
|
112
|
+
await (0, close_agent_session_1.closeAgentSession)(child, exitPromise, gracefulExitMs, forceKillWaitMs);
|
|
113
|
+
// The close can return on `exitCode` while `waitForExit` is still in
|
|
114
|
+
// its merge window; bounded so a stuck child cannot hold the run.
|
|
115
|
+
await (0, close_agent_session_1.raceWithTimeout)(exitPromise, forceKillWaitMs);
|
|
116
|
+
}
|
|
117
|
+
if (winner === 'sentinel') {
|
|
118
|
+
// Hygiene only; run state decides the outcome, not this removal.
|
|
119
|
+
try {
|
|
120
|
+
(0, fs_1.rmSync)(sentinelPath, { force: true });
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
logger_1.logger.verbose(`Could not remove ${sentinelPath}: ${toError(error).message}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
logger_1.logger.verbose(`${agent.displayName} session ended (code: ${exit.code ?? 'none'}, signal: ${exit.signal ?? 'none'}${exit.error ? `, error: ${exit.error.message}` : ''}).`);
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
sentinelWatch.abort();
|
|
130
|
+
// With the agent gone, the terminal is restored before the wait: an
|
|
131
|
+
// agent that left it raw would keep a Ctrl+C from reaching the operation
|
|
132
|
+
// in flight as a signal.
|
|
133
|
+
const exited = child.exitCode !== null || child.signalCode !== null;
|
|
134
|
+
if (started && exited) {
|
|
135
|
+
restoreTerminal();
|
|
136
|
+
warnOperationInFlight(broker);
|
|
137
|
+
}
|
|
138
|
+
// The request in flight settles before the lock is released.
|
|
139
|
+
await brokerDone;
|
|
140
|
+
broker.close();
|
|
141
|
+
process.removeListener('SIGINT', swallowSigint);
|
|
142
|
+
if (started && !exited)
|
|
143
|
+
restoreTerminal();
|
|
144
|
+
}
|
|
145
|
+
return brokerFailure
|
|
146
|
+
? { kind: 'broker-failed', error: brokerFailure }
|
|
147
|
+
: { kind: 'exited' };
|
|
148
|
+
}
|
|
149
|
+
function restoreTerminal() {
|
|
150
|
+
(0, terminal_repair_1.restoreTermiosAfterAgent)();
|
|
151
|
+
(0, migrate_output_1.resetSgrAfterAgent)();
|
|
152
|
+
}
|
|
153
|
+
// With the agent gone a Ctrl+C reaches the operation's child process, and
|
|
154
|
+
// nothing else tells the user one is still running.
|
|
155
|
+
function warnOperationInFlight(broker) {
|
|
156
|
+
const request = broker.requestInFlight;
|
|
157
|
+
if (!request)
|
|
158
|
+
return;
|
|
159
|
+
output_1.output.warn({
|
|
160
|
+
title: `Still running ${(0, run_1.treeOperationLabel)(request)} for this migrate run. Press Ctrl+C to end it; the run can be resumed afterwards.`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async function serviceBrokerUntilAborted(broker, intervalMs, signal) {
|
|
164
|
+
while (!signal.aborted) {
|
|
165
|
+
await broker.service();
|
|
166
|
+
await delayUnlessAborted(intervalMs, signal);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function delayUnlessAborted(ms, signal) {
|
|
170
|
+
return new Promise((resolve) => {
|
|
171
|
+
if (signal.aborted) {
|
|
172
|
+
resolve();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const onAbort = () => {
|
|
176
|
+
clearTimeout(timer);
|
|
177
|
+
resolve();
|
|
178
|
+
};
|
|
179
|
+
const timer = setTimeout(() => {
|
|
180
|
+
signal.removeEventListener('abort', onAbort);
|
|
181
|
+
resolve();
|
|
182
|
+
}, ms);
|
|
183
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function toError(error) {
|
|
187
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
188
|
+
}
|
|
189
|
+
// Workspace-relative with forward slashes on every platform: prose the agent
|
|
190
|
+
// reads, not a shell path.
|
|
191
|
+
function prosePath(runRoot, path) {
|
|
192
|
+
return (0, path_1.relative)(runRoot, path).split(path_1.sep).join('/');
|
|
193
|
+
}
|
|
194
|
+
// Never settles after an abort; the race it feeds has settled by then.
|
|
195
|
+
function waitForFile(path, intervalMs, signal) {
|
|
196
|
+
return new Promise((resolve) => {
|
|
197
|
+
let timer;
|
|
198
|
+
const tick = () => {
|
|
199
|
+
if (signal.aborted)
|
|
200
|
+
return;
|
|
201
|
+
if ((0, fs_1.existsSync)(path)) {
|
|
202
|
+
resolve();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
timer = setTimeout(tick, intervalMs);
|
|
206
|
+
};
|
|
207
|
+
signal.addEventListener('abort', () => clearTimeout(timer), { once: true });
|
|
208
|
+
timer = setTimeout(tick, intervalMs);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function assertWithinWindowsCommandLineBudget(adapted, agent, runId) {
|
|
212
|
+
if ((0, windows_cmd_1.withinCommandLineBudget)(adapted)) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
throw new Error(`Launching ${agent.displayName} needs a ${adapted.commandLineLength}-character command line. cmd.exe runs at most ${windows_cmd_1.WINDOWS_COMMAND_LINE_LIMIT} characters, and nx stops at ${windows_cmd_1.WINDOWS_COMMAND_LINE_BUDGET} to leave room for what it cannot measure from here. ` +
|
|
216
|
+
`What varies is the cmd.exe path (${adapted.binary.length} characters), the agent path (${agent.binary.length} characters) and the run id (${runId.length} characters); shorten one of them.`);
|
|
217
|
+
}
|
|
@@ -8,6 +8,7 @@ exports.renderHandoffShapeLines = renderHandoffShapeLines;
|
|
|
8
8
|
exports.renderAuthorScopeRuleLines = renderAuthorScopeRuleLines;
|
|
9
9
|
exports.renderValidationScopeRuleLines = renderValidationScopeRuleLines;
|
|
10
10
|
const format_command_1 = require("../format-command");
|
|
11
|
+
const types_1 = require("../types");
|
|
11
12
|
function renderNxInvocationNote(packageManager, nxInvocation) {
|
|
12
13
|
return `Use \`${packageManager}\` for any package-manager invocation in this workspace. To invoke nx, use \`${nxInvocation} ...\`. Do not default to a different package manager based on your own preference.`;
|
|
13
14
|
}
|
|
@@ -29,6 +30,7 @@ function renderAuthorScopeRuleLines(pmExec, format) {
|
|
|
29
30
|
`- Do not refactor or update dependencies beyond what the migration prompt directs, and do not reformat files you did not change.`,
|
|
30
31
|
renderFormatRule(pmExec, format),
|
|
31
32
|
`- Do not modify files outside the workspace root.`,
|
|
33
|
+
`- Do not edit anything under \`${types_1.MIGRATE_RUNS_RELATIVE_DIR}\` except the handoff file you are asked to write.`,
|
|
32
34
|
`- Do not run \`nx\` commands that mutate workspace state (\`nx migrate\`, \`nx reset\`, \`nx format:write\`, \`nx run-many\`, generators, etc.). Read-only inspection (\`nx show\`, \`nx graph --file\`, reading files) is fine.`,
|
|
33
35
|
`- If the migration instructions are unclear, internally inconsistent, or conflict with the current workspace state, ask the user for direction (see the handoff contract). Do not guess.`,
|
|
34
36
|
];
|
|
@@ -65,6 +67,7 @@ function renderValidationScopeRuleLines() {
|
|
|
65
67
|
`- You may apply minor fixes only when the issue lies within the scope of what this migration intended to accomplish (e.g. a missing import the generator's template should have produced, a type annotation the template missed). Do not refactor, do not modify unrelated functionality, do not extend the migration's scope, do not touch code the migration was not concerned with. If you are unsure whether a fix is in scope, report it in \`summary\` instead of applying.`,
|
|
66
68
|
`- Do not run other \`nx\` commands that mutate workspace state (\`nx migrate\`, \`nx reset\`, generators, etc.).`,
|
|
67
69
|
`- Do not modify files outside the workspace root.`,
|
|
70
|
+
`- Do not edit anything under \`${types_1.MIGRATE_RUNS_RELATIVE_DIR}\` except the handoff file you are asked to write.`,
|
|
68
71
|
`- If validation finds blocking issues you cannot resolve within scope: apply every fix you can within scope, then report the unresolved findings to the user and ask how to proceed (see the handoff contract). Do not guess.`,
|
|
69
72
|
];
|
|
70
73
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { SpawnOptions } from 'child_process';
|
|
2
1
|
import { AgentDefinition, DetectedInstalledAgent, HandoffOutcome, InvocationContext } from './types';
|
|
3
2
|
export interface RunAgenticArgs {
|
|
4
3
|
detected: DetectedInstalledAgent;
|
|
@@ -20,23 +19,3 @@ export interface RunAgenticArgs {
|
|
|
20
19
|
* the handoff file (or the user when the file is missing).
|
|
21
20
|
*/
|
|
22
21
|
export declare function runAgentic(args: RunAgenticArgs): Promise<HandoffOutcome>;
|
|
23
|
-
export declare const WINDOWS_COMMAND_LINE_BUDGET: number;
|
|
24
|
-
export interface AdaptedSpawn {
|
|
25
|
-
binary: string;
|
|
26
|
-
args: string[];
|
|
27
|
-
options: SpawnOptions;
|
|
28
|
-
/**
|
|
29
|
-
* Length of the command line Windows will receive. Set only on the `cmd.exe`
|
|
30
|
-
* wrapper path.
|
|
31
|
-
*/
|
|
32
|
-
commandLineLength?: number;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Node's `spawn` cannot directly execute `.cmd` / `.bat` shims on Windows;
|
|
36
|
-
* `which` resolves to those when an agent was installed via npm. Wrap them in
|
|
37
|
-
* a `cmd.exe /c` invocation with `windowsVerbatimArguments` so quoting follows
|
|
38
|
-
* the cmd.exe convention rather than Node's default cooking.
|
|
39
|
-
*
|
|
40
|
-
* On non-Windows or for non-shim binaries this is a passthrough.
|
|
41
|
-
*/
|
|
42
|
-
export declare function adaptSpawnForWindowsShim(binary: string, args: readonly string[], options: SpawnOptions): AdaptedSpawn;
|