@adhdev/daemon-standalone 1.0.57 → 1.0.58-rc.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-standalone",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.58-rc.1",
|
|
4
4
|
"description": "ADHDev standalone daemon — embedded HTTP/WS server for local dashboard",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"license": "AGPL-3.0-or-later",
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@adhdev/ghostty-vt-node": "*",
|
|
40
|
-
"@adhdev/session-host-core": "
|
|
40
|
+
"@adhdev/session-host-core": "1.0.58-rc.1",
|
|
41
41
|
"@xterm/addon-serialize": "^0.14.0",
|
|
42
42
|
"@xterm/xterm": "^6.0.0",
|
|
43
43
|
"chalk": "^5.3.0",
|
|
@@ -1413,7 +1413,23 @@ var MESH_SEND_TASK_TOOL = {
|
|
|
1413
1413
|
enum: ["when_idle", "interrupt"],
|
|
1414
1414
|
description: "How to deliver when the target session is BUSY. Default 'when_idle': never disturbs the running turn \u2014 the task is queued and auto-delivered the moment the session goes idle. \u2605'interrupt' ABORTS the turn currently in flight by pressing the provider's own stop control (Ctrl-C, or ESC on antigravity-cli), then delivers this task once the session settles. THE WORK IN PROGRESS IS DISCARDED \u2014 whatever the agent had not yet finished is lost, and any partial edits it was mid-way through are left as they are. Use it only when the running turn is genuinely going the wrong way and finishing it is worse than losing it. If the target provider cannot interrupt (no stop control declared, or an empty stop key), the dispatch is REJECTED rather than quietly falling back to when_idle \u2014 so a steering attempt never reports success while the session actually runs on to completion under the old instructions. Re-send with 'when_idle' if delivery-after-completion is acceptable. Has no effect on an idle session (delivered immediately either way)."
|
|
1415
1415
|
},
|
|
1416
|
-
deliveryMode: { type: "string", enum: ["when_idle", "interrupt"], description: "CamelCase alias for delivery_mode." }
|
|
1416
|
+
deliveryMode: { type: "string", enum: ["when_idle", "interrupt"], description: "CamelCase alias for delivery_mode." },
|
|
1417
|
+
// GRAPH-MEASUREMENT-DIRECT — the decision record for the DIRECT surface.
|
|
1418
|
+
//
|
|
1419
|
+
// ★ WHY IT IS HERE AT ALL. This tool is the MAJORITY dispatch surface (~67%
|
|
1420
|
+
// of dispatches in the graph-adoption investigation) and it carried no
|
|
1421
|
+
// decision field, so two thirds of all routing judgements were structurally
|
|
1422
|
+
// unmeasurable. `decision_missing` on mesh_enqueue_task described only the
|
|
1423
|
+
// enqueue minority and was silent — not negative — about the rest.
|
|
1424
|
+
//
|
|
1425
|
+
// ★ OPTIONAL, exactly like mesh_enqueue_task's (see the note there): phase E
|
|
1426
|
+
// measures and does not enforce, and a required field would reject every
|
|
1427
|
+
// existing caller. Omission degrades to decision_missing, never an error.
|
|
1428
|
+
orchestration_decision: {
|
|
1429
|
+
type: "object",
|
|
1430
|
+
description: "Record of your dispatch decision, for adoption measurement: {decision, direct_reason, ready_worker_tasks, known_graph_steps, capability_blockers}. On this DIRECT surface, direct_reason says why dispatching into an existing session beat queueing a task \u2014 one of same_subject_continuation, investigation_handoff, idle_session_reuse, queue_bypass_urgent, new_subject, legacy_client, operator_override. These are the cases the operating rules name: same-subject continuation, the investigate\u2192fix handoff, reusing an idle session for a follow-up/retry/cleanup delta, or a deliberate queue bypass. new_subject is the one the rules do NOT endorse \u2014 a genuinely new topic should get its own task even when a session sits idle \u2014 and reporting it returns an unsanctioned_direct_dispatch advisory. Report it honestly anyway: it is recorded, never refused. Optional and never rejected: omitting it is recorded as decision_missing. Provenance only \u2014 it never changes execution."
|
|
1431
|
+
},
|
|
1432
|
+
orchestrationDecision: { type: "object", description: "CamelCase alias for orchestration_decision." }
|
|
1417
1433
|
},
|
|
1418
1434
|
required: ["node_id", "session_id", "message", "difficulty"]
|
|
1419
1435
|
}
|
|
@@ -1535,7 +1551,7 @@ var MESH_FAST_FORWARD_NODE_TOOL = {
|
|
|
1535
1551
|
mode: { type: "string", enum: ["merge", "push"], description: "merge (default): git merge --ff-only to absorb upstream. push: strict ff-only push of local commits to origin/<branch>; refuses any non-fast-forward." },
|
|
1536
1552
|
branch: { type: "string", description: "Optional guard: require the node's current branch to match this branch before planning/executing." },
|
|
1537
1553
|
execute: { type: "boolean", description: "When true, apply the fast-forward/push if all safety gates pass. Defaults false/dry-run." },
|
|
1538
|
-
dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute." },
|
|
1554
|
+
dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute. dry_run=false is NOT an execute trigger \u2014 it only declines to veto, so passing it alone is rejected with dry_run_false_requires_execute rather than silently previewing. Use execute=true to apply." },
|
|
1539
1555
|
update_submodules: { type: "boolean", description: 'mode="merge" only: when true, if the root fast-forward changes gitlinks, run only git submodule update --init --recursive and verify submodules clean.' },
|
|
1540
1556
|
push_submodules: { type: "boolean", description: 'mode="push" only: also ff-only push submodule HEADs to their origin main. Gated by mesh policy allowAutoPublishSubmoduleMainCommits \u2014 skipped unless that policy is enabled. Defaults false (root push only).' }
|
|
1541
1557
|
},
|
|
@@ -1898,7 +1914,7 @@ var MESH_PRUNE_STALE_DIRECT_TOOL = {
|
|
|
1898
1914
|
type: "object",
|
|
1899
1915
|
properties: {
|
|
1900
1916
|
execute: { type: "boolean", description: "When true, actually delete the orphaned records. Defaults false (dry run). Ignored when dry_run=true." },
|
|
1901
|
-
dry_run: { type: "boolean", description: "Force a preview without mutation even if execute=true. Defaults to dry-run behavior when execute is not set." },
|
|
1917
|
+
dry_run: { type: "boolean", description: "Force a preview without mutation even if execute=true. Defaults to dry-run behavior when execute is not set. dry_run=false is NOT an execute trigger \u2014 passing it alone is rejected with dry_run_false_requires_execute rather than silently previewing. Use execute=true to prune." },
|
|
1902
1918
|
include_terminal: { type: "boolean", description: "Also prune terminal (completed/failed) direct dispatch store rows in addition to orphans. Defaults false." }
|
|
1903
1919
|
}
|
|
1904
1920
|
}
|
|
@@ -2028,7 +2044,7 @@ var MESH_MAGI_REVIEW_TOOL = {
|
|
|
2028
2044
|
require_independent_evidence: { type: "boolean", description: "Default true \u2014 high-impact claims with no file:line/source evidence are routed to needs_verification." },
|
|
2029
2045
|
include_stale: { type: "boolean", description: "Default false. By default, panel slots whose node HEAD commit differs from the coordinator reference commit are EXCLUDED (they would investigate different code). Set true to fan out to them anyway \u2014 results will be git-skewed and a warning is surfaced. If exclusion drops the panel below 2 independent targets the call errors rather than degrading to N=1; include_stale=true is one way to recover." },
|
|
2030
2046
|
wait: { type: "boolean", description: "Default true \u2014 collect replica outputs and return the synthesis. Set false to dispatch async and return a consensusGroupId handle; collect later with mesh_magi_collect." },
|
|
2031
|
-
wait_timeout_ms: { type: "number", description: 'Max time to wait for replica completion before returning a partial "missing K of N" synthesis. Default
|
|
2047
|
+
wait_timeout_ms: { type: "number", description: 'Max time to wait for replica completion before returning a partial "missing K of N" synthesis. Default 8 min, max 20 min.' },
|
|
2032
2048
|
auto_cleanup: { type: "boolean", description: "Default = mesh policy magiSessionCleanup (ON / stop_and_delete unless overridden). Once all replicas are terminal, stop+delete ONLY the worker sessions THIS fan-out auto-launched (marker-verified) so repeated reviews don't accumulate idle worker sessions. Reused/coordinator/other sessions are never touched. Set false to preserve auto-launched worker sessions for inspection. No effect on a partial (non-terminal) collection." }
|
|
2033
2049
|
},
|
|
2034
2050
|
required: ["question", "task_kind"]
|
|
@@ -2044,7 +2060,7 @@ var MESH_MAGI_COLLECT_TOOL = {
|
|
|
2044
2060
|
task_kind: { type: "string", enum: ["claim_audit", "rca", "design", "freeform"], description: "Optional override of the task_kind used to parse replica answers. Normally recovered automatically from the original dispatch \u2014 only set this if the dispatched ledger entry was pruned and auto-recovery falls back to claim_audit incorrectly." },
|
|
2045
2061
|
require_independent_evidence: { type: "boolean", description: "Default true \u2014 high-impact claims with no file:line/source evidence are routed to needs_verification." },
|
|
2046
2062
|
wait: { type: "boolean", description: "Default false (snapshot). Set true to block for outstanding replicas up to wait_timeout_ms before synthesizing." },
|
|
2047
|
-
wait_timeout_ms: { type: "number", description: "When wait=true, max time to wait for remaining replica completion. Default
|
|
2063
|
+
wait_timeout_ms: { type: "number", description: "When wait=true, max time to wait for remaining replica completion. Default 8 min, max 20 min." },
|
|
2048
2064
|
auto_cleanup: { type: "boolean", description: "Default = mesh policy magiSessionCleanup (ON / stop_and_delete). When the collection is terminal, stop+delete ONLY the worker sessions THIS fan-out auto-launched (marker-verified). Reused/coordinator/other sessions are never touched. Set false to preserve them. No effect on a partial (non-terminal) snapshot." },
|
|
2049
2065
|
verbose: { type: "boolean", description: "Default false. When true, each synthesis.replicas[] entry also carries rawAnswer \u2014 the replica's raw end-user answer text (capped). Omitted by default to keep the payload small; the structured clusters already carry the parsed claims." }
|
|
2050
2066
|
},
|
|
@@ -6515,9 +6531,12 @@ var import_daemon_core7 = require("@adhdev/daemon-core");
|
|
|
6515
6531
|
var MAGI_MAX_REPLICAS = 12;
|
|
6516
6532
|
var MAGI_MIN_TARGETS = 2;
|
|
6517
6533
|
var MAGI_CLUSTER_JACCARD = 0.4;
|
|
6518
|
-
var MAGI_DEFAULT_WAIT_MS =
|
|
6519
|
-
var MAGI_MAX_WAIT_MS =
|
|
6534
|
+
var MAGI_DEFAULT_WAIT_MS = 48e4;
|
|
6535
|
+
var MAGI_MAX_WAIT_MS = 12e5;
|
|
6520
6536
|
var MAGI_POLL_INTERVAL_MS = 5e3;
|
|
6537
|
+
function resolveMagiWaitTimeoutMs(raw) {
|
|
6538
|
+
return Math.min(MAGI_MAX_WAIT_MS, Math.max(MAGI_POLL_INTERVAL_MS, Number(raw) || MAGI_DEFAULT_WAIT_MS));
|
|
6539
|
+
}
|
|
6521
6540
|
var VALID_TASK_KINDS = ["claim_audit", "rca", "design", "freeform"];
|
|
6522
6541
|
var DEFAULT_TASK_KIND = "claim_audit";
|
|
6523
6542
|
function normalizeMagiTaskKind(raw) {
|
|
@@ -6898,7 +6917,11 @@ function synthesizeMagiResponses(responses, opts = {}) {
|
|
|
6898
6917
|
const replicasMissing = Math.max(0, replicasExpected - replicasAnswered);
|
|
6899
6918
|
const lossDominated = replicasMissing > 0 && replicasMissing >= replicasAnswered;
|
|
6900
6919
|
if (lossDominated) {
|
|
6901
|
-
|
|
6920
|
+
const notAnswered = responses.filter((r) => !(r.source.ok && r.response));
|
|
6921
|
+
const pendingCount = notAnswered.filter((r) => r.source.error === "replica_deadline_exceeded").length;
|
|
6922
|
+
const failedCount = notAnswered.length - pendingCount;
|
|
6923
|
+
const dropBreakdown = pendingCount > 0 && failedCount > 0 ? ` (${pendingCount} still pending past the deadline \u2014 re-collect may recover them; ${failedCount} genuinely failed/unparseable/stale \u2014 those need a panel swap)` : pendingCount > 0 ? ` (all ${pendingCount} still pending past the deadline \u2014 re-collect with mesh_magi_collect may recover them, this is NOT a failed panel)` : ` (all ${failedCount} genuinely failed/unparseable/stale \u2014 re-collecting will not recover them, consider a panel swap)`;
|
|
6924
|
+
independenceBanner = `independence not achieved \u2014 only ${replicasAnswered} of ${replicasExpected} replica(s) answered (${replicasMissing} missing/dropped), collapsing the answering set to ${distinctProviders} provider(s) and ${distinctNodes} machine(s). This is a replica-loss/collection failure, not a low-diversity panel${dropBreakdown}. Agreements are routed to needs_verification.`;
|
|
6902
6925
|
} else {
|
|
6903
6926
|
independenceBanner = `independence not achieved \u2014 the answering replicas span ${distinctProviders} provider(s) and ${distinctNodes} machine(s); their agreements are source-coupled and routed to needs_verification.`;
|
|
6904
6927
|
}
|
|
@@ -7431,7 +7454,7 @@ async function meshMagiReview(ctx, args) {
|
|
|
7431
7454
|
const mode = readString(args.mode);
|
|
7432
7455
|
const requireIndependentEvidence = (args.require_independent_evidence ?? args.requireIndependentEvidence) !== false;
|
|
7433
7456
|
const wait = args.wait !== false;
|
|
7434
|
-
const waitTimeoutMs =
|
|
7457
|
+
const waitTimeoutMs = resolveMagiWaitTimeoutMs(args.wait_timeout_ms ?? args.waitTimeoutMs);
|
|
7435
7458
|
const consensusGroupId = `magi_${(0, import_node_crypto.randomUUID)().replace(/-/g, "")}`;
|
|
7436
7459
|
const titleQ = question.length > 80 ? `${question.slice(0, 77)}...` : question;
|
|
7437
7460
|
const mission = (0, import_daemon_core6.upsertMeshMission)(ctx.mesh.id, {
|
|
@@ -7606,7 +7629,7 @@ async function meshMagiCollect(ctx, args) {
|
|
|
7606
7629
|
}
|
|
7607
7630
|
const requireIndependentEvidence = (args.require_independent_evidence ?? args.requireIndependentEvidence) !== false;
|
|
7608
7631
|
const wait = args.wait === true;
|
|
7609
|
-
const timeoutMs = wait ?
|
|
7632
|
+
const timeoutMs = wait ? resolveMagiWaitTimeoutMs(args.wait_timeout_ms ?? args.waitTimeoutMs) : 0;
|
|
7610
7633
|
const replicaTaskIds = replicaTasks.map((t) => readString(t.id)).filter(Boolean);
|
|
7611
7634
|
const collected = await collectMagiResponses(ctx, { replicaTaskIds, timeoutMs, taskKind });
|
|
7612
7635
|
const synthesis = synthesizeMagiResponses(collected.responses, {
|
|
@@ -8050,7 +8073,7 @@ ${magiOutputContractFor(kind)}`;
|
|
|
8050
8073
|
finalized.set(taskId, prov);
|
|
8051
8074
|
return true;
|
|
8052
8075
|
}
|
|
8053
|
-
source.error = isSchemaFailure ? `schema_invalid: ${kindResult.failReason}` : "
|
|
8076
|
+
source.error = isSchemaFailure ? `schema_invalid: ${kindResult.failReason}` : "replica_deadline_exceeded";
|
|
8054
8077
|
finalized.set(taskId, { source, response: emptyResponse() });
|
|
8055
8078
|
return true;
|
|
8056
8079
|
}
|
|
@@ -8277,6 +8300,15 @@ function computeIdleDispatchAckRisk(sessionWasIdle, dispatchPreRecorded, session
|
|
|
8277
8300
|
}
|
|
8278
8301
|
async function meshPruneStaleDirect(ctx, args = {}) {
|
|
8279
8302
|
await refreshMeshFromDaemon(ctx);
|
|
8303
|
+
if (args.dry_run === false && args.execute !== true) {
|
|
8304
|
+
return JSON.stringify({
|
|
8305
|
+
success: false,
|
|
8306
|
+
code: "dry_run_false_requires_execute",
|
|
8307
|
+
executed: false,
|
|
8308
|
+
error: "dry_run:false alone does not execute \u2014 it only declines to veto. Pass execute:true to actually prune.",
|
|
8309
|
+
nextAction: "Re-run mesh_prune_stale_direct(execute: true) to prune, or omit dry_run to preview."
|
|
8310
|
+
}, null, 2);
|
|
8311
|
+
}
|
|
8280
8312
|
const execute = args.execute === true && args.dry_run !== true;
|
|
8281
8313
|
const includeTerminal = args.include_terminal === true;
|
|
8282
8314
|
const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
|
|
@@ -8363,6 +8395,16 @@ async function meshSendTask(ctx, args) {
|
|
|
8363
8395
|
});
|
|
8364
8396
|
}
|
|
8365
8397
|
const difficulty = difficultyRaw;
|
|
8398
|
+
const rawDecision = args.orchestration_decision ?? args.orchestrationDecision;
|
|
8399
|
+
const decisionMissing = rawDecision === void 0 || rawDecision === null;
|
|
8400
|
+
const orchestration = (0, import_daemon_core6.normalizeOrchestrationDecision)(rawDecision, "direct");
|
|
8401
|
+
const orchestrationWarning = {
|
|
8402
|
+
...orchestration.unsanctionedDirect ? {
|
|
8403
|
+
unsanctionedDirect: orchestration.unsanctionedDirect,
|
|
8404
|
+
unsanctionedDirectHint: import_daemon_core6.MESH_UNSANCTIONED_DIRECT_HINT
|
|
8405
|
+
} : {},
|
|
8406
|
+
...decisionMissing ? { orchestrationDecisionMissing: true } : {}
|
|
8407
|
+
};
|
|
8366
8408
|
const modeValidation = (0, import_daemon_core6.validateMeshTaskModeRequest)(requestedTaskMode, message, readonly);
|
|
8367
8409
|
if (!modeValidation.valid) {
|
|
8368
8410
|
return JSON.stringify({
|
|
@@ -8519,6 +8561,17 @@ async function meshSendTask(ctx, args) {
|
|
|
8519
8561
|
...readonly ? { readonly: true } : {},
|
|
8520
8562
|
dispatchedAt
|
|
8521
8563
|
});
|
|
8564
|
+
(0, import_daemon_core6.recordDirectDispatchDecision)(ctx.mesh.id, {
|
|
8565
|
+
taskId,
|
|
8566
|
+
via: "p2p_direct",
|
|
8567
|
+
nodeId: args.node_id,
|
|
8568
|
+
...dispatchedSessionId ? { sessionId: dispatchedSessionId } : {},
|
|
8569
|
+
...missionId ? { missionId } : {},
|
|
8570
|
+
...ctx.coordinatorSessionId ? { coordinatorSessionId: ctx.coordinatorSessionId } : {},
|
|
8571
|
+
decision: orchestration.decision,
|
|
8572
|
+
...decisionMissing ? { decisionMissing: true } : {},
|
|
8573
|
+
...orchestration.unsanctionedDirect ? { unsanctionedDirect: orchestration.unsanctionedDirect.reportedReason } : {}
|
|
8574
|
+
});
|
|
8522
8575
|
} catch {
|
|
8523
8576
|
}
|
|
8524
8577
|
}
|
|
@@ -8531,7 +8584,10 @@ async function meshSendTask(ctx, args) {
|
|
|
8531
8584
|
taskMode,
|
|
8532
8585
|
...result2.success && result2.providerType ? { providerType: result2.providerType } : {},
|
|
8533
8586
|
dispatched: result2.success === true,
|
|
8534
|
-
...result2.success ? buildMissionInactiveWarning(ctx, missionId) ?? {} : {}
|
|
8587
|
+
...result2.success ? buildMissionInactiveWarning(ctx, missionId) ?? {} : {},
|
|
8588
|
+
// GRAPH-MEASUREMENT-DIRECT: advisory only, and only on a dispatch that
|
|
8589
|
+
// actually happened — a failed dispatch made no routing decision to report on.
|
|
8590
|
+
...result2.success ? orchestrationWarning : {}
|
|
8535
8591
|
});
|
|
8536
8592
|
}
|
|
8537
8593
|
if (args.session_id) {
|
|
@@ -8836,6 +8892,17 @@ async function meshSendTask(ctx, args) {
|
|
|
8836
8892
|
...readonly ? { readonly: true } : {},
|
|
8837
8893
|
dispatchedAt
|
|
8838
8894
|
});
|
|
8895
|
+
(0, import_daemon_core6.recordDirectDispatchDecision)(ctx.mesh.id, {
|
|
8896
|
+
taskId,
|
|
8897
|
+
via: "local_direct",
|
|
8898
|
+
nodeId: args.node_id,
|
|
8899
|
+
sessionId: args.session_id,
|
|
8900
|
+
...missionId ? { missionId } : {},
|
|
8901
|
+
...ctx.coordinatorSessionId ? { coordinatorSessionId: ctx.coordinatorSessionId } : {},
|
|
8902
|
+
decision: orchestration.decision,
|
|
8903
|
+
...decisionMissing ? { decisionMissing: true } : {},
|
|
8904
|
+
...orchestration.unsanctionedDirect ? { unsanctionedDirect: orchestration.unsanctionedDirect.reportedReason } : {}
|
|
8905
|
+
});
|
|
8839
8906
|
} catch {
|
|
8840
8907
|
}
|
|
8841
8908
|
let deliveryId;
|
|
@@ -8869,7 +8936,9 @@ async function meshSendTask(ctx, args) {
|
|
|
8869
8936
|
// session whose dispatch row did NOT survive pre-record. A successfully
|
|
8870
8937
|
// pre-recorded idle dispatch (the NOTIF-DROP / CANON-A path) is not at risk.
|
|
8871
8938
|
...computeIdleDispatchAckRisk(sessionWasIdle, dispatchPreRecorded, args.session_id),
|
|
8872
|
-
...buildMissionInactiveWarning(ctx, missionId) ?? {}
|
|
8939
|
+
...buildMissionInactiveWarning(ctx, missionId) ?? {},
|
|
8940
|
+
// GRAPH-MEASUREMENT-DIRECT: advisory only — never blocks, never re-routes.
|
|
8941
|
+
...orchestrationWarning
|
|
8873
8942
|
});
|
|
8874
8943
|
}
|
|
8875
8944
|
const task = (0, import_daemon_core6.enqueueTask)(ctx.mesh.id, message, {
|
|
@@ -9423,6 +9492,20 @@ async function meshFastForwardNode(ctx, args) {
|
|
|
9423
9492
|
blockingReasons: ["node_read_only"]
|
|
9424
9493
|
}, null, 2);
|
|
9425
9494
|
}
|
|
9495
|
+
if (args.dry_run === false && args.execute !== true) {
|
|
9496
|
+
return JSON.stringify({
|
|
9497
|
+
success: false,
|
|
9498
|
+
code: "dry_run_false_requires_execute",
|
|
9499
|
+
nodeId: args.node_id,
|
|
9500
|
+
workspace: node.workspace,
|
|
9501
|
+
allowed: false,
|
|
9502
|
+
willRun: false,
|
|
9503
|
+
executed: false,
|
|
9504
|
+
blockingReasons: ["dry_run_false_requires_execute"],
|
|
9505
|
+
error: "dry_run:false alone does not execute \u2014 it only declines to veto. Pass execute:true to actually apply the fast-forward.",
|
|
9506
|
+
nextAction: `Re-run mesh_fast_forward_node(node_id: "${args.node_id}", execute: true) to apply, or omit dry_run to preview.`
|
|
9507
|
+
}, null, 2);
|
|
9508
|
+
}
|
|
9426
9509
|
try {
|
|
9427
9510
|
const dryRun = args.dry_run === true || args.execute !== true;
|
|
9428
9511
|
const result = await commandForNode(ctx, node, "fast_forward_mesh_node", {
|