@adhdev/daemon-standalone 1.0.56-rc.8 → 1.0.56-rc.9

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.56-rc.8",
3
+ "version": "1.0.56-rc.9",
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": "1.0.56-rc.8",
40
+ "@adhdev/session-host-core": "1.0.56-rc.9",
41
41
  "@xterm/addon-serialize": "^0.14.0",
42
42
  "@xterm/xterm": "^6.0.0",
43
43
  "chalk": "^5.3.0",
@@ -635,6 +635,7 @@ var CANONICAL_MESH_TOOL_NAMES = [
635
635
  "mesh_graph_view",
636
636
  "mesh_graph_gate_claim",
637
637
  "mesh_graph_gate_release",
638
+ "mesh_graph_gate_abandon",
638
639
  "mesh_queue_cancel",
639
640
  "mesh_queue_requeue",
640
641
  "mesh_send_task",
@@ -1272,6 +1273,22 @@ var MESH_GRAPH_GATE_RELEASE_TOOL = {
1272
1273
  required: ["gate_id", "fencing_token", "lease_generation", "idempotency_key", "outcome"]
1273
1274
  }
1274
1275
  };
1276
+ var MESH_GRAPH_GATE_ABANDON_TOOL = {
1277
+ name: "mesh_graph_gate_abandon",
1278
+ description: "Give up on a coordinator gate that can never be opened, so its graph can reach a terminal state. Use this when the work behind a gate was cancelled or is no longer wanted \u2014 e.g. you cancelled the implementation tasks and the refinery/land gate is now stranded with nothing to land. Without it that gate stays awaiting_coordinator forever and the graph can reach NO terminal state at all, not even cancelled. \u2605 ABANDON IS NOT A PASS. It materializes NOTHING: every downstream task this gate was holding is CANCELLED, not opened, and no outcome/result/evidence is produced for downstream run_if or inputs_from. If you actually want the downstream work to run, use mesh_graph_gate_release instead \u2014 that is the only way through a gate, and this tool is deliberately not a shortcut around it. Needs no fencing token (it grants no passage), but a gate whose lease is still LIVE under another coordinator is refused unless you pass force=true \u2014 that holder may be mid-action on a real external side effect (a merge, a publish, a deploy). Abandoning an already-abandoned gate is a safe no-op; a RELEASED gate can never be abandoned, because its downstream already ran.",
1279
+ inputSchema: {
1280
+ type: "object",
1281
+ properties: {
1282
+ gate_id: { type: "string", description: "The gate to abandon (from mesh_graph_view)." },
1283
+ gateId: { type: "string", description: "CamelCase alias for gate_id." },
1284
+ reason: { type: "string", description: "Why this gate is being given up \u2014 recorded on the gate node, on every cancelled downstream row, and in the provenance ledger. Required: an abandon with no stated reason is indistinguishable from a mistake later." },
1285
+ force: { type: "boolean", description: "Abandon even though another coordinator holds a LIVE lease. Only when you know that holder is dead \u2014 otherwise you may strand an external side effect it is mid-way through." },
1286
+ coordinator_session_id: { type: "string", description: "Recorded as who abandoned the gate. Defaults to this coordinator session." },
1287
+ coordinatorSessionId: { type: "string", description: "CamelCase alias for coordinator_session_id." }
1288
+ },
1289
+ required: ["gate_id", "reason"]
1290
+ }
1291
+ };
1275
1292
  var MESH_GRAPH_VIEW_TOOL = {
1276
1293
  name: "mesh_graph_view",
1277
1294
  description: "Inspect orchestration GRAPHS on this mesh: node states and refs, active edges, materialization receipts, coordinator gates (with who holds the lease and what is blocked), delayed workspace sagas, derived dependency failures, and the next action a coordinator actually has to take. Read-only. Defaults to in-flight graphs; pass include_terminal=true for completed ones. Only mesh_enqueue_batch requests that use graph features (gates, inputs_from, run_if, workspace_ref) create a graph \u2014 a plain depends_on batch runs on the ordinary queue and appears in mesh_view_queue instead. Do not poll this waiting for generating work; use it when you need to know why something is blocked or which gate is waiting on you.",
@@ -2134,6 +2151,7 @@ var ALL_MESH_TOOLS = [
2134
2151
  MESH_GRAPH_VIEW_TOOL,
2135
2152
  MESH_GRAPH_GATE_CLAIM_TOOL,
2136
2153
  MESH_GRAPH_GATE_RELEASE_TOOL,
2154
+ MESH_GRAPH_GATE_ABANDON_TOOL,
2137
2155
  MESH_QUEUE_CANCEL_TOOL,
2138
2156
  MESH_QUEUE_REQUEUE_TOOL,
2139
2157
  MESH_SEND_TASK_TOOL,
@@ -5096,6 +5114,95 @@ async function meshGraphGateRelease(ctx, args) {
5096
5114
  });
5097
5115
  }
5098
5116
  }
5117
+ async function meshGraphGateAbandon(ctx, args) {
5118
+ recordMeshCoordinatorToolCall(ctx, "mesh_graph_gate_abandon");
5119
+ const gateId = readString(args.gate_id) || readString(args.gateId);
5120
+ const reason = readString(args.reason);
5121
+ const missing = [
5122
+ !gateId ? "gate_id" : null,
5123
+ !reason ? "reason" : null
5124
+ ].filter((f) => f !== null);
5125
+ if (missing.length > 0) {
5126
+ return JSON.stringify({
5127
+ success: false,
5128
+ code: "missing_abandon_fields",
5129
+ missing,
5130
+ error: `mesh_graph_gate_abandon requires ${missing.join(", ")}. The reason is recorded on the gate, on every cancelled downstream row, and in the provenance ledger \u2014 an abandon with no stated reason is indistinguishable from a mistake when someone reads it back later.`
5131
+ });
5132
+ }
5133
+ const coordinatorSessionId = resolveGateSession(ctx, args.coordinator_session_id ?? args.coordinatorSessionId);
5134
+ try {
5135
+ const result = (0, import_daemon_core6.abandonMeshGraphGate)({
5136
+ meshId: ctx.mesh.id,
5137
+ gateId,
5138
+ reason,
5139
+ ...coordinatorSessionId ? { coordinatorSessionId } : {},
5140
+ ...args.force === true ? { force: true } : {}
5141
+ });
5142
+ if (!result.abandoned) {
5143
+ return JSON.stringify({
5144
+ success: false,
5145
+ abandoned: false,
5146
+ code: result.reason ?? "gate_not_abandonable",
5147
+ gateId,
5148
+ ...result.gate ? { gateState: result.gate.state, action: result.gate.action } : {},
5149
+ error: describeAbandonRefusal(result.reason, result.gate?.state)
5150
+ });
5151
+ }
5152
+ const duplicate = result.reason === "gate_already_abandoned";
5153
+ if (!duplicate) {
5154
+ (0, import_daemon_core6.recordGraphGateAbandoned)(ctx.mesh.id, {
5155
+ graphId: result.gate.graphId,
5156
+ gateId,
5157
+ ref: result.gate.ref,
5158
+ action: result.gate.action,
5159
+ // The gate row already carries `cancelled`; the PRIOR state is what
5160
+ // says whether this closed an open gate or a stranded expired one.
5161
+ priorState: result.gate.state,
5162
+ reason,
5163
+ ...coordinatorSessionId ? { coordinatorSessionId } : {},
5164
+ ...args.force === true ? { force: true } : {},
5165
+ cancelledNodeIds: result.cancelledNodeIds,
5166
+ ...result.graphStatus ? { graphStatus: result.graphStatus } : {}
5167
+ });
5168
+ }
5169
+ return JSON.stringify({
5170
+ success: true,
5171
+ abandoned: true,
5172
+ // Re-abandoning an abandoned gate is a safe no-op, so a retried cleanup
5173
+ // never has to tell "I did it" from "it was already done".
5174
+ duplicate,
5175
+ gateId,
5176
+ graphId: result.gate.graphId,
5177
+ ...result.gate.ref ? { ref: result.gate.ref } : {},
5178
+ gateState: result.gate.state,
5179
+ cancelledNodeIds: result.cancelledNodeIds,
5180
+ cancelledCount: result.cancelledNodeIds.length,
5181
+ ...result.cancelledTaskIds.length > 0 ? { cancelledTaskIds: result.cancelledTaskIds } : {},
5182
+ ...result.graphStatus ? { graphStatus: result.graphStatus } : {},
5183
+ materializedNodeIds: [],
5184
+ note: "Abandon is not a pass: nothing was materialized and no gate outcome was produced. Everything this gate was holding is cancelled. If the graph still shows a non-terminal status, another gate or an in-flight worker is still outstanding \u2014 check mesh_graph_view."
5185
+ });
5186
+ } catch (e) {
5187
+ const message = e?.message || String(e);
5188
+ return JSON.stringify({ success: false, abandoned: false, gateId, error: message });
5189
+ }
5190
+ }
5191
+ function describeAbandonRefusal(reason, state) {
5192
+ switch (reason) {
5193
+ case "gate_not_found":
5194
+ return "No such gate on this mesh. Use mesh_graph_view to list gates.";
5195
+ case "gate_lease_held":
5196
+ return "Another coordinator holds a LIVE lease on this gate, and it may be mid-action on a real external side effect (a merge, a publish, a deploy). Wait for the lease to lapse, or pass force=true if you know that holder is dead.";
5197
+ case "gate_abandon_race":
5198
+ return "The gate moved (a concurrent claim or release) while this abandon was committing. Re-read it with mesh_graph_view and decide again \u2014 it may no longer need abandoning.";
5199
+ default:
5200
+ if (reason?.startsWith("gate_terminal:")) {
5201
+ return `The gate is already terminal (${state ?? reason.slice("gate_terminal:".length)}) and cannot be abandoned. A RELEASED gate already let its downstream run, so abandoning it would claim closure over work that is in flight or finished \u2014 cancel that work directly instead.`;
5202
+ }
5203
+ return `The gate could not be abandoned (${reason ?? "unknown reason"}).`;
5204
+ }
5205
+ }
5099
5206
  async function meshGraphView(ctx, args) {
5100
5207
  recordMeshCoordinatorToolCall(ctx, "mesh_graph_view");
5101
5208
  try {
@@ -11080,6 +11187,9 @@ async function startMcpServer(opts) {
11080
11187
  case "mesh_graph_gate_release":
11081
11188
  text = await meshGraphGateRelease(meshCtx, a);
11082
11189
  break;
11190
+ case "mesh_graph_gate_abandon":
11191
+ text = await meshGraphGateAbandon(meshCtx, a);
11192
+ break;
11083
11193
  case "mesh_queue_cancel":
11084
11194
  text = await meshQueueCancel(meshCtx, a);
11085
11195
  break;