@adhdev/daemon-core 0.9.76-rc.36 → 0.9.76-rc.38

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.
@@ -21,6 +21,9 @@ export interface SessionHostControlPlane {
21
21
  }): Promise<any>;
22
22
  listSessions(): Promise<any[]>;
23
23
  stopSession(sessionId: string): Promise<any>;
24
+ deleteSession(sessionId: string, opts?: {
25
+ force?: boolean;
26
+ }): Promise<any>;
24
27
  resumeSession(sessionId: string): Promise<any>;
25
28
  restartSession(sessionId: string): Promise<any>;
26
29
  sendSignal(sessionId: string, signal: string): Promise<any>;
@@ -85,6 +88,10 @@ export declare class DaemonCommandRouter {
85
88
  private getMeshForCommand;
86
89
  private updateInlineMeshNode;
87
90
  private removeInlineMeshNode;
91
+ private normalizeMeshSessionCleanupMode;
92
+ private sessionMatchesMeshNode;
93
+ private isCompletedHostedSession;
94
+ private cleanupMeshSessions;
88
95
  private traceSessionHostAction;
89
96
  /**
90
97
  * Unified command routing.
package/dist/index.js CHANGED
@@ -41,7 +41,8 @@ var init_repo_mesh_types = __esm({
41
41
  requireApprovalForPush: true,
42
42
  requireApprovalForDestructiveGit: true,
43
43
  dirtyWorkspaceBehavior: "warn",
44
- maxParallelTasks: 2
44
+ maxParallelTasks: 2,
45
+ sessionCleanupOnNodeRemove: "preserve"
45
46
  };
46
47
  }
47
48
  });
@@ -460,6 +461,18 @@ function normalizeRepoIdentity(remoteUrl) {
460
461
  if (sshMatch) return `${sshMatch[1]}/${sshMatch[2]}`;
461
462
  return identity;
462
463
  }
464
+ function mergeMeshPolicy(base, patch) {
465
+ const policy = { ...DEFAULT_MESH_POLICY, ...base || {}, ...patch || {} };
466
+ if (!["block", "warn", "checkpoint_then_continue"].includes(policy.dirtyWorkspaceBehavior)) {
467
+ policy.dirtyWorkspaceBehavior = "warn";
468
+ }
469
+ const maxParallelTasks = Number(policy.maxParallelTasks);
470
+ policy.maxParallelTasks = Number.isFinite(maxParallelTasks) ? Math.max(1, Math.min(8, Math.floor(maxParallelTasks))) : 2;
471
+ if (!SESSION_CLEANUP_MODES.has(String(policy.sessionCleanupOnNodeRemove))) {
472
+ policy.sessionCleanupOnNodeRemove = "preserve";
473
+ }
474
+ return policy;
475
+ }
463
476
  function listMeshes() {
464
477
  return loadMeshConfig().meshes;
465
478
  }
@@ -483,7 +496,7 @@ function createMesh(opts) {
483
496
  repoIdentity,
484
497
  repoRemoteUrl: opts.repoRemoteUrl,
485
498
  defaultBranch: opts.defaultBranch,
486
- policy: { ...DEFAULT_MESH_POLICY, ...opts.policy },
499
+ policy: mergeMeshPolicy(void 0, opts.policy),
487
500
  coordinator: opts.coordinator || {},
488
501
  nodes: [],
489
502
  createdAt: now,
@@ -499,7 +512,7 @@ function updateMesh(meshId, opts) {
499
512
  if (!mesh) return void 0;
500
513
  if (opts.name !== void 0) mesh.name = opts.name.trim().slice(0, 100);
501
514
  if (opts.defaultBranch !== void 0) mesh.defaultBranch = opts.defaultBranch;
502
- if (opts.policy) mesh.policy = { ...mesh.policy, ...opts.policy };
515
+ if (opts.policy) mesh.policy = mergeMeshPolicy(mesh.policy, opts.policy);
503
516
  if (opts.coordinator) mesh.coordinator = opts.coordinator;
504
517
  mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
505
518
  saveMeshConfig(config);
@@ -562,7 +575,7 @@ function updateNode(meshId, nodeId, opts) {
562
575
  saveMeshConfig(config);
563
576
  return node;
564
577
  }
565
- var import_fs2, import_path2, import_crypto3;
578
+ var import_fs2, import_path2, import_crypto3, SESSION_CLEANUP_MODES;
566
579
  var init_mesh_config = __esm({
567
580
  "src/config/mesh-config.ts"() {
568
581
  "use strict";
@@ -571,6 +584,7 @@ var init_mesh_config = __esm({
571
584
  import_crypto3 = require("crypto");
572
585
  init_config();
573
586
  init_repo_mesh_types();
587
+ SESSION_CLEANUP_MODES = /* @__PURE__ */ new Set(["preserve", "stop", "delete_stopped", "stop_and_delete"]);
574
588
  }
575
589
  });
576
590
 
@@ -21491,6 +21505,75 @@ var DaemonCommandRouter = class {
21491
21505
  this.inlineMeshCache.set(meshId, mesh);
21492
21506
  return true;
21493
21507
  }
21508
+ normalizeMeshSessionCleanupMode(value) {
21509
+ return value === "stop" || value === "delete_stopped" || value === "stop_and_delete" || value === "preserve" ? value : "preserve";
21510
+ }
21511
+ sessionMatchesMeshNode(record, node, nodeId, sessionIds) {
21512
+ const sessionId = typeof record?.sessionId === "string" ? record.sessionId : "";
21513
+ if (!sessionId) return false;
21514
+ if (sessionIds?.size) return sessionIds.has(sessionId);
21515
+ const workspace = typeof node?.workspace === "string" ? node.workspace : "";
21516
+ if (workspace && record?.workspace === workspace) return true;
21517
+ if (record?.meta?.meshNodeId === nodeId) return true;
21518
+ return false;
21519
+ }
21520
+ isCompletedHostedSession(record) {
21521
+ return record?.lifecycle === "stopped" || record?.lifecycle === "failed" || record?.lifecycle === "interrupted";
21522
+ }
21523
+ async cleanupMeshSessions(args) {
21524
+ if (args.mode === "preserve") {
21525
+ return { success: true, mode: "preserve", matchedCount: 0, stoppedSessionIds: [], deletedSessionIds: [], skippedSessionIds: [] };
21526
+ }
21527
+ if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
21528
+ const requestedSessionIds = Array.isArray(args.sessionIds) ? new Set(args.sessionIds.map((id) => typeof id === "string" ? id.trim() : "").filter(Boolean)) : void 0;
21529
+ const sessions = await this.deps.sessionHostControl.listSessions();
21530
+ const matched = sessions.filter((record) => this.sessionMatchesMeshNode(record, args.node, args.nodeId, requestedSessionIds));
21531
+ const stoppedSessionIds = [];
21532
+ const deletedSessionIds = [];
21533
+ const skippedSessionIds = [];
21534
+ const errors = [];
21535
+ for (const record of matched) {
21536
+ const sessionId = String(record.sessionId);
21537
+ const completed = this.isCompletedHostedSession(record);
21538
+ try {
21539
+ if (args.mode === "stop") {
21540
+ if (!completed) {
21541
+ if (!args.dryRun) await this.deps.sessionHostControl.stopSession(sessionId);
21542
+ stoppedSessionIds.push(sessionId);
21543
+ } else {
21544
+ skippedSessionIds.push(sessionId);
21545
+ }
21546
+ continue;
21547
+ }
21548
+ if (args.mode === "delete_stopped") {
21549
+ if (completed) {
21550
+ if (!args.dryRun) await this.deps.sessionHostControl.deleteSession(sessionId, { force: false });
21551
+ deletedSessionIds.push(sessionId);
21552
+ } else {
21553
+ skippedSessionIds.push(sessionId);
21554
+ }
21555
+ continue;
21556
+ }
21557
+ if (args.mode === "stop_and_delete") {
21558
+ if (!args.dryRun) await this.deps.sessionHostControl.deleteSession(sessionId, { force: true });
21559
+ deletedSessionIds.push(sessionId);
21560
+ continue;
21561
+ }
21562
+ } catch (e) {
21563
+ errors.push({ sessionId, error: e?.message || String(e) });
21564
+ }
21565
+ }
21566
+ return {
21567
+ success: errors.length === 0,
21568
+ mode: args.mode,
21569
+ dryRun: args.dryRun === true,
21570
+ matchedCount: matched.length,
21571
+ stoppedSessionIds,
21572
+ deletedSessionIds,
21573
+ skippedSessionIds,
21574
+ ...errors.length ? { errors } : {}
21575
+ };
21576
+ }
21494
21577
  async traceSessionHostAction(action, args, run, summarizeResult) {
21495
21578
  const interactionId = typeof args?._interactionId === "string" ? args._interactionId : void 0;
21496
21579
  const sessionId = typeof args?.sessionId === "string" ? args.sessionId : void 0;
@@ -22157,7 +22240,26 @@ var DaemonCommandRouter = class {
22157
22240
  if (!name) return { success: false, error: "name required" };
22158
22241
  try {
22159
22242
  const { createMesh: createMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
22160
- const mesh = createMesh2({ name, repoIdentity, repoRemoteUrl, defaultBranch });
22243
+ const mesh = createMesh2({ name, repoIdentity, repoRemoteUrl, defaultBranch, policy: args?.policy });
22244
+ return { success: true, mesh };
22245
+ } catch (e) {
22246
+ return { success: false, error: e.message };
22247
+ }
22248
+ }
22249
+ case "update_mesh": {
22250
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
22251
+ if (!meshId) return { success: false, error: "meshId required" };
22252
+ try {
22253
+ const { updateMesh: updateMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
22254
+ const patch = {};
22255
+ if (typeof args?.name === "string") patch.name = args.name;
22256
+ if (typeof args?.defaultBranch === "string") patch.defaultBranch = args.defaultBranch;
22257
+ if (args?.policy && typeof args.policy === "object" && !Array.isArray(args.policy)) patch.policy = args.policy;
22258
+ if (args?.coordinator && typeof args.coordinator === "object" && !Array.isArray(args.coordinator)) patch.coordinator = args.coordinator;
22259
+ if (!Object.keys(patch).length) return { success: false, error: "No updates provided" };
22260
+ const mesh = updateMesh2(meshId, patch);
22261
+ if (!mesh) return { success: false, error: "Mesh not found" };
22262
+ this.inlineMeshCache.set(meshId, mesh);
22161
22263
  return { success: true, mesh };
22162
22264
  } catch (e) {
22163
22265
  return { success: false, error: e.message };
@@ -22194,6 +22296,54 @@ var DaemonCommandRouter = class {
22194
22296
  return { success: false, error: e.message };
22195
22297
  }
22196
22298
  }
22299
+ case "update_mesh_node": {
22300
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
22301
+ const nodeId = typeof args?.nodeId === "string" ? args.nodeId.trim() : "";
22302
+ if (!meshId || !nodeId) return { success: false, error: "meshId and nodeId required" };
22303
+ try {
22304
+ const { updateNode: updateNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
22305
+ const policy = args?.policy && typeof args.policy === "object" && !Array.isArray(args.policy) ? { ...args.policy } : {};
22306
+ if (Array.isArray(args?.providerPriority)) {
22307
+ const providerPriority = args.providerPriority.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean);
22308
+ delete policy.provider_priority;
22309
+ if (providerPriority.length) {
22310
+ policy.providerPriority = providerPriority;
22311
+ } else {
22312
+ delete policy.providerPriority;
22313
+ }
22314
+ }
22315
+ const node = updateNode2(meshId, nodeId, { policy });
22316
+ if (!node) return { success: false, error: "Mesh node not found" };
22317
+ return { success: true, node };
22318
+ } catch (e) {
22319
+ return { success: false, error: e.message };
22320
+ }
22321
+ }
22322
+ case "cleanup_mesh_sessions": {
22323
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
22324
+ const nodeId = typeof args?.nodeId === "string" ? args.nodeId.trim() : "";
22325
+ if (!meshId || !nodeId) return { success: false, error: "meshId and nodeId required" };
22326
+ try {
22327
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
22328
+ const mesh = meshRecord?.mesh;
22329
+ if (!mesh) return { success: false, error: "Mesh not found" };
22330
+ const node = mesh?.nodes?.find((n) => n.id === nodeId || n.nodeId === nodeId);
22331
+ if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh` };
22332
+ const mode = this.normalizeMeshSessionCleanupMode(args?.mode ?? mesh?.policy?.sessionCleanupOnNodeRemove);
22333
+ const sessionIds = Array.isArray(args?.sessionIds) ? args.sessionIds.map((id) => typeof id === "string" ? id.trim() : "").filter(Boolean) : void 0;
22334
+ const result = await this.cleanupMeshSessions({
22335
+ meshId,
22336
+ nodeId,
22337
+ node,
22338
+ mode,
22339
+ sessionIds,
22340
+ dryRun: args?.dryRun === true
22341
+ });
22342
+ return result;
22343
+ } catch (e) {
22344
+ return { success: false, error: e.message };
22345
+ }
22346
+ }
22197
22347
  case "remove_mesh_node": {
22198
22348
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
22199
22349
  const nodeId = typeof args?.nodeId === "string" ? args.nodeId.trim() : "";
@@ -22202,6 +22352,14 @@ var DaemonCommandRouter = class {
22202
22352
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
22203
22353
  const mesh = meshRecord?.mesh;
22204
22354
  const node = mesh?.nodes?.find((n) => n.id === nodeId || n.nodeId === nodeId);
22355
+ const sessionCleanupMode = this.normalizeMeshSessionCleanupMode(
22356
+ args?.sessionCleanupMode ?? args?.session_cleanup_mode ?? mesh?.policy?.sessionCleanupOnNodeRemove
22357
+ );
22358
+ let sessionCleanup;
22359
+ if (node && sessionCleanupMode !== "preserve") {
22360
+ sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode });
22361
+ if (sessionCleanup.success === false) return { success: false, removed: false, sessionCleanup };
22362
+ }
22205
22363
  if (node?.isLocalWorktree && node.workspace) {
22206
22364
  try {
22207
22365
  const sourceNode = node.clonedFromNodeId ? mesh?.nodes.find((n) => n.id === node.clonedFromNodeId || n.nodeId === node.clonedFromNodeId) : mesh?.nodes.find((n) => !n.isLocalWorktree);
@@ -22221,7 +22379,7 @@ var DaemonCommandRouter = class {
22221
22379
  const { removeNode: removeNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
22222
22380
  removed = removeNode3(meshId, nodeId);
22223
22381
  }
22224
- return { success: true, removed };
22382
+ return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {} };
22225
22383
  } catch (e) {
22226
22384
  return { success: false, error: e.message };
22227
22385
  }