@adhdev/daemon-standalone 1.0.57 → 1.0.58-rc.2
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.2",
|
|
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.2",
|
|
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
|
}
|
|
@@ -7295,6 +7318,8 @@ async function meshMagiKindPanelSet(ctx, args) {
|
|
|
7295
7318
|
const scope = magiPanelScope(meshId, ctx.mesh.name);
|
|
7296
7319
|
try {
|
|
7297
7320
|
const current = (0, import_daemon_core6.getMagiKindPanel)(kind, meshId) ?? [];
|
|
7321
|
+
const ignoredFields = (0, import_daemon_core6.collectIgnoredMagiSlotFields)(args.slots);
|
|
7322
|
+
const ignoredNote = ignoredFields.length ? { ignoredFields, ignoredFieldsNote: "These keys are not part of the MAGI slot schema and were DROPPED (the panel was still saved without them). A MAGI panel decides WHO answers independently; per-slot routing axes like thinkingLevel/difficulty/maxParallel belong on the node capability slots (mesh_node_slots_set)." } : {};
|
|
7298
7323
|
if (!write) {
|
|
7299
7324
|
const preview = (0, import_daemon_core6.normalizeMagiSlots)(args.slots, ctx.mesh.nodes.map((n) => n.id));
|
|
7300
7325
|
return JSON.stringify({
|
|
@@ -7305,6 +7330,7 @@ async function meshMagiKindPanelSet(ctx, args) {
|
|
|
7305
7330
|
replacement: true,
|
|
7306
7331
|
currentSlots: current,
|
|
7307
7332
|
slots: preview,
|
|
7333
|
+
...ignoredNote,
|
|
7308
7334
|
note: `Dry-run only \u2014 no file written. This is a WHOLESALE replacement of the kind's slot list for mesh '${meshId}' (machine-local ~/.adhdev/meshes.json); the currentSlots would be fully replaced. Other meshes on this machine are unaffected. Re-run with write=true after explicit user approval.`
|
|
7309
7335
|
}, null, 2);
|
|
7310
7336
|
}
|
|
@@ -7317,6 +7343,7 @@ async function meshMagiKindPanelSet(ctx, args) {
|
|
|
7317
7343
|
replacement: true,
|
|
7318
7344
|
previousSlots: current,
|
|
7319
7345
|
slots,
|
|
7346
|
+
...ignoredNote,
|
|
7320
7347
|
nextAction: "Verify with mesh_magi_kind_panel_list, then mesh_magi_review({ task_kind }) resolves this binding."
|
|
7321
7348
|
}, null, 2);
|
|
7322
7349
|
} catch (e) {
|
|
@@ -7431,7 +7458,7 @@ async function meshMagiReview(ctx, args) {
|
|
|
7431
7458
|
const mode = readString(args.mode);
|
|
7432
7459
|
const requireIndependentEvidence = (args.require_independent_evidence ?? args.requireIndependentEvidence) !== false;
|
|
7433
7460
|
const wait = args.wait !== false;
|
|
7434
|
-
const waitTimeoutMs =
|
|
7461
|
+
const waitTimeoutMs = resolveMagiWaitTimeoutMs(args.wait_timeout_ms ?? args.waitTimeoutMs);
|
|
7435
7462
|
const consensusGroupId = `magi_${(0, import_node_crypto.randomUUID)().replace(/-/g, "")}`;
|
|
7436
7463
|
const titleQ = question.length > 80 ? `${question.slice(0, 77)}...` : question;
|
|
7437
7464
|
const mission = (0, import_daemon_core6.upsertMeshMission)(ctx.mesh.id, {
|
|
@@ -7606,7 +7633,7 @@ async function meshMagiCollect(ctx, args) {
|
|
|
7606
7633
|
}
|
|
7607
7634
|
const requireIndependentEvidence = (args.require_independent_evidence ?? args.requireIndependentEvidence) !== false;
|
|
7608
7635
|
const wait = args.wait === true;
|
|
7609
|
-
const timeoutMs = wait ?
|
|
7636
|
+
const timeoutMs = wait ? resolveMagiWaitTimeoutMs(args.wait_timeout_ms ?? args.waitTimeoutMs) : 0;
|
|
7610
7637
|
const replicaTaskIds = replicaTasks.map((t) => readString(t.id)).filter(Boolean);
|
|
7611
7638
|
const collected = await collectMagiResponses(ctx, { replicaTaskIds, timeoutMs, taskKind });
|
|
7612
7639
|
const synthesis = synthesizeMagiResponses(collected.responses, {
|
|
@@ -8050,7 +8077,7 @@ ${magiOutputContractFor(kind)}`;
|
|
|
8050
8077
|
finalized.set(taskId, prov);
|
|
8051
8078
|
return true;
|
|
8052
8079
|
}
|
|
8053
|
-
source.error = isSchemaFailure ? `schema_invalid: ${kindResult.failReason}` : "
|
|
8080
|
+
source.error = isSchemaFailure ? `schema_invalid: ${kindResult.failReason}` : "replica_deadline_exceeded";
|
|
8054
8081
|
finalized.set(taskId, { source, response: emptyResponse() });
|
|
8055
8082
|
return true;
|
|
8056
8083
|
}
|
|
@@ -8277,6 +8304,15 @@ function computeIdleDispatchAckRisk(sessionWasIdle, dispatchPreRecorded, session
|
|
|
8277
8304
|
}
|
|
8278
8305
|
async function meshPruneStaleDirect(ctx, args = {}) {
|
|
8279
8306
|
await refreshMeshFromDaemon(ctx);
|
|
8307
|
+
if (args.dry_run === false && args.execute !== true) {
|
|
8308
|
+
return JSON.stringify({
|
|
8309
|
+
success: false,
|
|
8310
|
+
code: "dry_run_false_requires_execute",
|
|
8311
|
+
executed: false,
|
|
8312
|
+
error: "dry_run:false alone does not execute \u2014 it only declines to veto. Pass execute:true to actually prune.",
|
|
8313
|
+
nextAction: "Re-run mesh_prune_stale_direct(execute: true) to prune, or omit dry_run to preview."
|
|
8314
|
+
}, null, 2);
|
|
8315
|
+
}
|
|
8280
8316
|
const execute = args.execute === true && args.dry_run !== true;
|
|
8281
8317
|
const includeTerminal = args.include_terminal === true;
|
|
8282
8318
|
const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
|
|
@@ -8363,6 +8399,16 @@ async function meshSendTask(ctx, args) {
|
|
|
8363
8399
|
});
|
|
8364
8400
|
}
|
|
8365
8401
|
const difficulty = difficultyRaw;
|
|
8402
|
+
const rawDecision = args.orchestration_decision ?? args.orchestrationDecision;
|
|
8403
|
+
const decisionMissing = rawDecision === void 0 || rawDecision === null;
|
|
8404
|
+
const orchestration = (0, import_daemon_core6.normalizeOrchestrationDecision)(rawDecision, "direct");
|
|
8405
|
+
const orchestrationWarning = {
|
|
8406
|
+
...orchestration.unsanctionedDirect ? {
|
|
8407
|
+
unsanctionedDirect: orchestration.unsanctionedDirect,
|
|
8408
|
+
unsanctionedDirectHint: import_daemon_core6.MESH_UNSANCTIONED_DIRECT_HINT
|
|
8409
|
+
} : {},
|
|
8410
|
+
...decisionMissing ? { orchestrationDecisionMissing: true } : {}
|
|
8411
|
+
};
|
|
8366
8412
|
const modeValidation = (0, import_daemon_core6.validateMeshTaskModeRequest)(requestedTaskMode, message, readonly);
|
|
8367
8413
|
if (!modeValidation.valid) {
|
|
8368
8414
|
return JSON.stringify({
|
|
@@ -8519,6 +8565,17 @@ async function meshSendTask(ctx, args) {
|
|
|
8519
8565
|
...readonly ? { readonly: true } : {},
|
|
8520
8566
|
dispatchedAt
|
|
8521
8567
|
});
|
|
8568
|
+
(0, import_daemon_core6.recordDirectDispatchDecision)(ctx.mesh.id, {
|
|
8569
|
+
taskId,
|
|
8570
|
+
via: "p2p_direct",
|
|
8571
|
+
nodeId: args.node_id,
|
|
8572
|
+
...dispatchedSessionId ? { sessionId: dispatchedSessionId } : {},
|
|
8573
|
+
...missionId ? { missionId } : {},
|
|
8574
|
+
...ctx.coordinatorSessionId ? { coordinatorSessionId: ctx.coordinatorSessionId } : {},
|
|
8575
|
+
decision: orchestration.decision,
|
|
8576
|
+
...decisionMissing ? { decisionMissing: true } : {},
|
|
8577
|
+
...orchestration.unsanctionedDirect ? { unsanctionedDirect: orchestration.unsanctionedDirect.reportedReason } : {}
|
|
8578
|
+
});
|
|
8522
8579
|
} catch {
|
|
8523
8580
|
}
|
|
8524
8581
|
}
|
|
@@ -8531,7 +8588,10 @@ async function meshSendTask(ctx, args) {
|
|
|
8531
8588
|
taskMode,
|
|
8532
8589
|
...result2.success && result2.providerType ? { providerType: result2.providerType } : {},
|
|
8533
8590
|
dispatched: result2.success === true,
|
|
8534
|
-
...result2.success ? buildMissionInactiveWarning(ctx, missionId) ?? {} : {}
|
|
8591
|
+
...result2.success ? buildMissionInactiveWarning(ctx, missionId) ?? {} : {},
|
|
8592
|
+
// GRAPH-MEASUREMENT-DIRECT: advisory only, and only on a dispatch that
|
|
8593
|
+
// actually happened — a failed dispatch made no routing decision to report on.
|
|
8594
|
+
...result2.success ? orchestrationWarning : {}
|
|
8535
8595
|
});
|
|
8536
8596
|
}
|
|
8537
8597
|
if (args.session_id) {
|
|
@@ -8836,6 +8896,17 @@ async function meshSendTask(ctx, args) {
|
|
|
8836
8896
|
...readonly ? { readonly: true } : {},
|
|
8837
8897
|
dispatchedAt
|
|
8838
8898
|
});
|
|
8899
|
+
(0, import_daemon_core6.recordDirectDispatchDecision)(ctx.mesh.id, {
|
|
8900
|
+
taskId,
|
|
8901
|
+
via: "local_direct",
|
|
8902
|
+
nodeId: args.node_id,
|
|
8903
|
+
sessionId: args.session_id,
|
|
8904
|
+
...missionId ? { missionId } : {},
|
|
8905
|
+
...ctx.coordinatorSessionId ? { coordinatorSessionId: ctx.coordinatorSessionId } : {},
|
|
8906
|
+
decision: orchestration.decision,
|
|
8907
|
+
...decisionMissing ? { decisionMissing: true } : {},
|
|
8908
|
+
...orchestration.unsanctionedDirect ? { unsanctionedDirect: orchestration.unsanctionedDirect.reportedReason } : {}
|
|
8909
|
+
});
|
|
8839
8910
|
} catch {
|
|
8840
8911
|
}
|
|
8841
8912
|
let deliveryId;
|
|
@@ -8869,7 +8940,9 @@ async function meshSendTask(ctx, args) {
|
|
|
8869
8940
|
// session whose dispatch row did NOT survive pre-record. A successfully
|
|
8870
8941
|
// pre-recorded idle dispatch (the NOTIF-DROP / CANON-A path) is not at risk.
|
|
8871
8942
|
...computeIdleDispatchAckRisk(sessionWasIdle, dispatchPreRecorded, args.session_id),
|
|
8872
|
-
...buildMissionInactiveWarning(ctx, missionId) ?? {}
|
|
8943
|
+
...buildMissionInactiveWarning(ctx, missionId) ?? {},
|
|
8944
|
+
// GRAPH-MEASUREMENT-DIRECT: advisory only — never blocks, never re-routes.
|
|
8945
|
+
...orchestrationWarning
|
|
8873
8946
|
});
|
|
8874
8947
|
}
|
|
8875
8948
|
const task = (0, import_daemon_core6.enqueueTask)(ctx.mesh.id, message, {
|
|
@@ -9423,6 +9496,20 @@ async function meshFastForwardNode(ctx, args) {
|
|
|
9423
9496
|
blockingReasons: ["node_read_only"]
|
|
9424
9497
|
}, null, 2);
|
|
9425
9498
|
}
|
|
9499
|
+
if (args.dry_run === false && args.execute !== true) {
|
|
9500
|
+
return JSON.stringify({
|
|
9501
|
+
success: false,
|
|
9502
|
+
code: "dry_run_false_requires_execute",
|
|
9503
|
+
nodeId: args.node_id,
|
|
9504
|
+
workspace: node.workspace,
|
|
9505
|
+
allowed: false,
|
|
9506
|
+
willRun: false,
|
|
9507
|
+
executed: false,
|
|
9508
|
+
blockingReasons: ["dry_run_false_requires_execute"],
|
|
9509
|
+
error: "dry_run:false alone does not execute \u2014 it only declines to veto. Pass execute:true to actually apply the fast-forward.",
|
|
9510
|
+
nextAction: `Re-run mesh_fast_forward_node(node_id: "${args.node_id}", execute: true) to apply, or omit dry_run to preview.`
|
|
9511
|
+
}, null, 2);
|
|
9512
|
+
}
|
|
9426
9513
|
try {
|
|
9427
9514
|
const dryRun = args.dry_run === true || args.execute !== true;
|
|
9428
9515
|
const result = await commandForNode(ctx, node, "fast_forward_mesh_node", {
|