@navels/neal 0.6.3 → 0.6.5
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.
|
@@ -556,6 +556,7 @@ export async function runCoderPlanRound(args) {
|
|
|
556
556
|
sessionHandle,
|
|
557
557
|
finalResponse,
|
|
558
558
|
marker,
|
|
559
|
+
blockedReason: normalizedPayload.action === 'blocked' ? normalizedPayload.blockedReason.trim() || null : null,
|
|
559
560
|
};
|
|
560
561
|
}
|
|
561
562
|
const coder = getCoderAdapter(args.coder);
|
|
@@ -603,6 +604,7 @@ export async function runCoderPlanRound(args) {
|
|
|
603
604
|
sessionHandle,
|
|
604
605
|
finalResponse,
|
|
605
606
|
marker,
|
|
607
|
+
blockedReason: null,
|
|
606
608
|
};
|
|
607
609
|
}
|
|
608
610
|
function renderCoderPlanPayload(payload, marker) {
|
|
@@ -9,6 +9,7 @@ import { runOnePass } from '../orchestrator.js';
|
|
|
9
9
|
import { writeExecutionArtifacts } from '../orchestrator/artifacts.js';
|
|
10
10
|
import { countOpenNonBlockingFindings, determinePlanRefinementConvergence, formatPlanRefinementSummary, isPlanRefinementState, } from '../plan-refinement.js';
|
|
11
11
|
import { acquireActiveRunLock, releaseActiveRunLockSync } from '../run-lock.js';
|
|
12
|
+
import { planResumeActions } from '../resume-planner.js';
|
|
12
13
|
import { formatPublicRunStatus, getRunDisplayStatus } from '../run-status.js';
|
|
13
14
|
import { getCurrentScopeLabel } from '../scopes.js';
|
|
14
15
|
import { resolveRunStatePath } from '../run-registry.js';
|
|
@@ -200,6 +201,12 @@ function armShutdownWatchdog(finalState, logger) {
|
|
|
200
201
|
});
|
|
201
202
|
};
|
|
202
203
|
}
|
|
204
|
+
// Keep a crash message readable on the console; `neal status` carries the
|
|
205
|
+
// full text from the `phase.error` event.
|
|
206
|
+
function truncateFailureMessage(message) {
|
|
207
|
+
const trimmed = message.trim();
|
|
208
|
+
return trimmed.length > 1000 ? `${trimmed.slice(0, 1000)}…` : trimmed;
|
|
209
|
+
}
|
|
203
210
|
function formatFinalSummaryLine(finalState, publicStatus) {
|
|
204
211
|
if (publicStatus === 'waiting_for_manual_gate') {
|
|
205
212
|
const gate = finalState.manualGate;
|
|
@@ -255,6 +262,9 @@ function formatFinalNextAction(finalState, displayStatus, runId, guidance) {
|
|
|
255
262
|
return `Inspect failure: neal status --run ${runId}; resume when ready: neal resume --run ${runId}`;
|
|
256
263
|
}
|
|
257
264
|
if (finalState.status === 'blocked') {
|
|
265
|
+
if (planResumeActions(finalState).some((action) => action.kind === 'restore_resumable_blocked_phase')) {
|
|
266
|
+
return `Address the reason above, then re-run ${formatPublicPhase(finalState.blockedFromPhase ?? finalState.phase)}: neal resume --run ${runId}`;
|
|
267
|
+
}
|
|
258
268
|
return `Inspect blocked run: neal status --run ${runId}; provide guidance with neal resume --run ${runId} --message "..." only if the run is waiting for operator guidance`;
|
|
259
269
|
}
|
|
260
270
|
if (displayStatus.effectiveStatus === 'paused') {
|
|
@@ -265,7 +275,7 @@ function formatFinalNextAction(finalState, displayStatus, runId, guidance) {
|
|
|
265
275
|
}
|
|
266
276
|
return `Continue this run: neal resume --run ${runId}`;
|
|
267
277
|
}
|
|
268
|
-
export function renderFinalRunOutput(finalState, statePath, displayStatus, autoSquashResult = null) {
|
|
278
|
+
export function renderFinalRunOutput(finalState, statePath, displayStatus, autoSquashResult = null, failureMessage = null) {
|
|
269
279
|
const runId = basename(finalState.runDir);
|
|
270
280
|
const publicStatus = formatPublicRunStatus(displayStatus);
|
|
271
281
|
const findings = countFinalFindings(finalState);
|
|
@@ -312,6 +322,18 @@ export function renderFinalRunOutput(finalState, statePath, displayStatus, autoS
|
|
|
312
322
|
if (guidance) {
|
|
313
323
|
lines.push('', ...renderBlockedGuidanceSections(guidance));
|
|
314
324
|
}
|
|
325
|
+
else if (finalState.blockerReason) {
|
|
326
|
+
// A block that is not waiting for `--message` guidance has no guidance
|
|
327
|
+
// section, so print the stored reason under the same heading.
|
|
328
|
+
lines.push('', '## Why Neal Stopped', finalState.blockerReason);
|
|
329
|
+
}
|
|
330
|
+
else if (finalState.status === 'failed' && failureMessage) {
|
|
331
|
+
// A crashed round is caught above and rendered as a failed result instead
|
|
332
|
+
// of being rethrown, so the error text has to be printed here or the
|
|
333
|
+
// console never shows it. `neal status` reads the same message from the
|
|
334
|
+
// `phase.error` event.
|
|
335
|
+
lines.push('', '## Why Neal Stopped', truncateFailureMessage(failureMessage));
|
|
336
|
+
}
|
|
315
337
|
lines.push('', '## Next Action', `- ${formatFinalNextAction(finalState, displayStatus, runId, guidance)}`);
|
|
316
338
|
return lines.join('\n');
|
|
317
339
|
}
|
|
@@ -411,6 +433,7 @@ export async function executeRun(state, statePath, logger, options = {}) {
|
|
|
411
433
|
writeDiagnostic(renderInteractiveKeyHint(allowStopRequest));
|
|
412
434
|
}
|
|
413
435
|
let finalState;
|
|
436
|
+
let runFailureMessage = null;
|
|
414
437
|
try {
|
|
415
438
|
try {
|
|
416
439
|
finalState = await runOnePass(state, statePath, logger, {
|
|
@@ -435,6 +458,7 @@ export async function executeRun(state, statePath, logger, options = {}) {
|
|
|
435
458
|
if (!finalState) {
|
|
436
459
|
throw error;
|
|
437
460
|
}
|
|
461
|
+
runFailureMessage = error instanceof Error ? error.message : String(error);
|
|
438
462
|
}
|
|
439
463
|
}
|
|
440
464
|
finally {
|
|
@@ -477,7 +501,7 @@ export async function executeRun(state, statePath, logger, options = {}) {
|
|
|
477
501
|
status: finalState.status,
|
|
478
502
|
runDir: finalState.runDir,
|
|
479
503
|
});
|
|
480
|
-
const finalOutput = renderFinalRunOutput(finalState, statePath, displayStatus, autoSquashResult);
|
|
504
|
+
const finalOutput = renderFinalRunOutput(finalState, statePath, displayStatus, autoSquashResult, runFailureMessage);
|
|
481
505
|
process.stdout.write(finalOutput.trimEnd() + '\n');
|
|
482
506
|
await logger.event('shutdown.final_output_written', {
|
|
483
507
|
phase: finalState.phase,
|
|
@@ -211,17 +211,23 @@ export async function runCoderPlanPhase(state, statePath, logger) {
|
|
|
211
211
|
}
|
|
212
212
|
const completionProblem = getPlanningCompletionProblem(codex.marker);
|
|
213
213
|
const dirtyWorktreeBlocker = await getPlanPhaseDirtyWorktreeBlocker(workingState, 'coder_plan');
|
|
214
|
+
const blocked = codex.marker === 'AUTONOMY_BLOCKED' || Boolean(completionProblem) || Boolean(dirtyWorktreeBlocker);
|
|
215
|
+
// Persist why the planner stopped so `neal status` and the run result can show
|
|
216
|
+
// it. A `coder_plan` block is never answerable via `neal resume --message`
|
|
217
|
+
// (that eligibility keys on `blockedFromPhase`), so a non-null reason here
|
|
218
|
+
// only informs the operator; it does not change resume behavior.
|
|
219
|
+
const reason = blocked
|
|
220
|
+
? dirtyWorktreeBlocker ?? completionProblem ?? codex.blockedReason ?? 'The planner reported a blocker during plan refinement'
|
|
221
|
+
: null;
|
|
214
222
|
const nextState = await saveState(statePath, {
|
|
215
223
|
...workingState,
|
|
216
224
|
plannerSessionHandle: codex.sessionHandle,
|
|
217
225
|
plannerSessionProtocol: codex.sessionHandle ? activePlannerSessionProtocol : null,
|
|
218
226
|
lastScopeMarker: codex.marker,
|
|
219
|
-
phase:
|
|
220
|
-
status:
|
|
221
|
-
blockedFromPhase:
|
|
222
|
-
|
|
223
|
-
// coder-response blocker reason; keep it cleared whether we advance or block.
|
|
224
|
-
blockerReason: null,
|
|
227
|
+
phase: blocked ? 'blocked' : 'reviewer_plan',
|
|
228
|
+
status: blocked ? 'blocked' : 'running',
|
|
229
|
+
blockedFromPhase: blocked ? 'coder_plan' : null,
|
|
230
|
+
blockerReason: reason,
|
|
225
231
|
});
|
|
226
232
|
await writeExecutionArtifacts(nextState);
|
|
227
233
|
await logger?.event('phase.complete', {
|
|
@@ -230,8 +236,7 @@ export async function runCoderPlanPhase(state, statePath, logger) {
|
|
|
230
236
|
sessionHandle: codex.sessionHandle,
|
|
231
237
|
nextPhase: nextState.phase,
|
|
232
238
|
});
|
|
233
|
-
if (nextState.status === 'blocked') {
|
|
234
|
-
const reason = dirtyWorktreeBlocker ?? completionProblem ?? 'The coder reported a blocker during plan revision';
|
|
239
|
+
if (nextState.status === 'blocked' && reason !== null) {
|
|
235
240
|
if (nextState.topLevelMode !== 'execute') {
|
|
236
241
|
await notifyBlocked(nextState, reason, logger);
|
|
237
242
|
return nextState;
|
|
@@ -325,14 +325,16 @@ function renderLifecycleEvents(state, scopeDescriptor, options) {
|
|
|
325
325
|
}
|
|
326
326
|
if (state.status === 'blocked') {
|
|
327
327
|
const blockedPhase = state.blockedFromPhase ?? state.phase;
|
|
328
|
+
const blockLine = formatScopeRelatedPhase(blockedPhase)
|
|
329
|
+
? options.scopeIntroEstablished
|
|
330
|
+
? `Run is blocked during ${formatPublicPhase(blockedPhase)}.`
|
|
331
|
+
: `Run is blocked during ${formatPublicPhase(blockedPhase)} for ${scopeReference}.`
|
|
332
|
+
: `Run is blocked during ${formatPublicPhase(blockedPhase)}.`;
|
|
333
|
+
const reason = (state.blockerReason ?? state.interactiveBlockedRecovery?.blockedReason ?? '').replace(/\s+/g, ' ').trim();
|
|
328
334
|
return [
|
|
329
335
|
{
|
|
330
336
|
signature: `lifecycle:${runId}:blocked:${blockedPhase}`,
|
|
331
|
-
line:
|
|
332
|
-
? options.scopeIntroEstablished
|
|
333
|
-
? `Run is blocked during ${formatPublicPhase(blockedPhase)}.`
|
|
334
|
-
: `Run is blocked during ${formatPublicPhase(blockedPhase)} for ${scopeReference}.`
|
|
335
|
-
: `Run is blocked during ${formatPublicPhase(blockedPhase)}.`,
|
|
337
|
+
line: reason ? `${blockLine} ${reason}` : blockLine,
|
|
336
338
|
},
|
|
337
339
|
];
|
|
338
340
|
}
|
package/docs/maintenance.md
CHANGED
|
@@ -52,7 +52,9 @@ is gated on it as a whole.
|
|
|
52
52
|
opens and merges the release-preparation PR (version bump + changelog), and
|
|
53
53
|
runs the Publish workflow through the npm 2FA approval. See
|
|
54
54
|
[docs/release.md](release.md). Urgent bumps (a fix neal needs immediately)
|
|
55
|
-
may skip the Renovate soak with a manual PR
|
|
55
|
+
may skip the Renovate soak with a manual PR: `scripts/bump-native-sdks.sh`
|
|
56
|
+
opens one at the current latest releases. Qualify it the same way. Leave
|
|
57
|
+
Renovate's own SDK PR open; it closes itself once the manual bump merges.
|
|
56
58
|
|
|
57
59
|
## TypeScript 6 and 7 side by side
|
|
58
60
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@navels/neal",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "A source-first multi-agent CLI for planning, executing, reviewing, and resuming scoped code changes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@ai-sdk/openai-compatible": "3.0.39",
|
|
59
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.
|
|
60
|
-
"@openai/codex-sdk": "0.
|
|
59
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.260",
|
|
60
|
+
"@openai/codex-sdk": "0.153.2",
|
|
61
61
|
"ai": "7.0.83",
|
|
62
62
|
"dotenv": "^17.4.2",
|
|
63
63
|
"yaml": "^2.9.0",
|