@lmzhen/dsh-evolution-learning-graph 0.3.67 → 0.3.69

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/lib/index.js CHANGED
@@ -13,6 +13,9 @@ import { SKILL_NAME_RE, SkillLibrary, contentHash, evolutionIoAdapter, relatedSk
13
13
  * edit/delete can reject a stale index (E-21).
14
14
  * @module @lmzhen/dsh-evolution-learning-graph
15
15
  */
16
+ /** V27 INS-04: how many skill files one `/graph` batch reads concurrently.
17
+ * Bounded so a large library cannot open every file at once (EMFILE). */
18
+ const SKILL_READ_BATCH = 16;
16
19
  function graphDensity(graph) {
17
20
  const linked = /* @__PURE__ */ new Set();
18
21
  let relatedEdges = 0;
@@ -220,12 +223,12 @@ function apply(ctx, rawConfig = {}) {
220
223
  const graphSkillsRoot = resolveSkillsRoot(rawConfig);
221
224
  ctx.inject(["commands"], (commandCtx) => {
222
225
  const commands = commandCtx.commands;
223
- commandCtx.effect(() => commands.register({
226
+ const graphCommand = {
224
227
  name: "graph",
225
228
  description: "Show the learning graph, or act on a node: graph [detail|edit|delete] <nodeId>",
226
229
  recordInput: false,
227
230
  input: { hint: "[detail|edit|delete] <nodeId> [text]" },
228
- handler: async (invocation) => {
231
+ run: async (invocation) => {
229
232
  const ok = (text) => ({
230
233
  kind: "success",
231
234
  text
@@ -241,24 +244,49 @@ function apply(ctx, rawConfig = {}) {
241
244
  const usage = usageService;
242
245
  const memory = memoryService;
243
246
  const io = ioService;
244
- const session = invocation.agent?.session;
245
- const input = (invocation.rawInput ?? "").trim();
247
+ const session = invocation.agent.session;
248
+ const input = invocation.rawInput.trim();
246
249
  const detail = /^detail\s+(\S+)$/.exec(input);
247
250
  if (detail && detail[1]) return await nodeDetail(detail[1]);
248
251
  const edit = /^edit\s+(\S+)\s+([\s\S]+)$/.exec(input);
249
- if (edit && edit[1] && edit[2] !== void 0) return await nodeEdit(edit[1], edit[2]);
252
+ if (edit && edit[1] && edit[2] !== void 0) {
253
+ if (!edit[1].startsWith("memory:")) {
254
+ if (await withSkills().read(edit[1]).catch(() => null) === null) return err(`Skill "${edit[1]}" not found in the live skill library — nothing to edit. (The graph may be showing a stale node.)`);
255
+ }
256
+ return await nodeEdit(edit[1], edit[2]);
257
+ }
250
258
  const remove = /^delete\s+(\S+)$/.exec(input);
251
- if (remove && remove[1]) return await nodeDelete(remove[1]);
259
+ if (remove && remove[1]) {
260
+ if (!remove[1].startsWith("memory:")) {
261
+ if (await withSkills().read(remove[1]).catch(() => null) === null) return err(`Skill "${remove[1]}" not found in the live skill library — nothing to delete.`);
262
+ }
263
+ return await nodeDelete(remove[1]);
264
+ }
252
265
  const directory = await renderGraph();
253
266
  if (input !== "") return err(`Unknown graph subcommand "${input.split(" ")[0]}". ${directory}`);
254
267
  return ok(directory);
255
268
  async function renderGraph() {
256
- const usageMap = await usage.report();
269
+ const usageReport = await usage.report();
270
+ const usageMap = new Map([...usageReport].filter(([, record]) => record.state !== "archived"));
257
271
  const memoryEntries = await memory.read("memory");
258
272
  const userEntries = await memory.read("user");
259
273
  const skills = withSkills();
260
274
  const names = [...usageMap.keys()];
261
- const contents = await Promise.all(names.map((name) => skills.read(name)));
275
+ const contents = [];
276
+ let unreadable = 0;
277
+ for (let offset = 0; offset < names.length; offset += SKILL_READ_BATCH) {
278
+ const batch = names.slice(offset, offset + SKILL_READ_BATCH);
279
+ const batchContents = await Promise.all(batch.map(async (name) => {
280
+ try {
281
+ return await skills.read(name);
282
+ } catch {
283
+ unreadable += 1;
284
+ return null;
285
+ }
286
+ }));
287
+ contents.push(...batchContents);
288
+ }
289
+ if (unreadable > 0) ctx.logger.warn(`evolution-learning-graph: ${unreadable} skill file(s) could not be read; their related_skills edges are missing from this graph`);
262
290
  const related = /* @__PURE__ */ new Map();
263
291
  names.forEach((name, index) => {
264
292
  const content = contents[index];
@@ -291,7 +319,7 @@ function apply(ctx, rawConfig = {}) {
291
319
  if (parsed === null) return err(`Invalid node id "${id}". Skill names or memory:<source>:<index> expected.`);
292
320
  if (parsed.kind === "skill") {
293
321
  const approval = ctx.get("evolutionApproval");
294
- const origins = resolveOrigins(session?.header?.origin);
322
+ const origins = resolveOrigins(session.header.origin);
295
323
  if (approval) {
296
324
  const sessionPolicyEdit = effectiveSessionPolicy(ctx, session);
297
325
  const stagesForeground = approval.stageForeground !== false;
@@ -312,8 +340,8 @@ function apply(ctx, rawConfig = {}) {
312
340
  libraryOrigin: origins.library
313
341
  },
314
342
  origin: origins.approval,
315
- ...session?.id ? { sessionId: session.id } : {},
316
- ...session ? { session } : {},
343
+ ...session.id ? { sessionId: session.id } : {},
344
+ session,
317
345
  ...sessionPolicy !== void 0 ? { sessionPolicy } : {}
318
346
  });
319
347
  if (decision.action === "staged") return ok(decision.message);
@@ -326,7 +354,7 @@ function apply(ctx, rawConfig = {}) {
326
354
  if (!check.ok) return err(check.message ?? "Memory index check failed.");
327
355
  const memoryApproval = ctx.get("evolutionApproval");
328
356
  if (memoryApproval) {
329
- const originsM = resolveOrigins(session?.header?.origin);
357
+ const originsM = resolveOrigins(session.header.origin);
330
358
  const sessionPolicyM = effectiveSessionPolicy(ctx, session);
331
359
  if (memoryApproval.isEnabled !== false && sessionPolicyM !== "never" && (originsM.approval === "background_review" || memoryApproval.stageForeground !== false) && !memoryApproval.hasRunner("memory")) return err("Graph memory write cannot be staged: no memory replay runner is registered — mount the tool-memory row (evolution-all bundle, or the evolution-preset overlay; the host bundle does not carry it) or disable evolution-approval.");
332
360
  const decision = await memoryApproval.request({
@@ -341,8 +369,8 @@ function apply(ctx, rawConfig = {}) {
341
369
  }]
342
370
  },
343
371
  origin: originsM.approval,
344
- ...session?.id ? { sessionId: session.id } : {},
345
- ...session ? { session } : {},
372
+ ...session.id ? { sessionId: session.id } : {},
373
+ session,
346
374
  ...sessionPolicyM !== void 0 ? { sessionPolicy: sessionPolicyM } : {}
347
375
  });
348
376
  if (decision.action === "staged") return ok(decision.message);
@@ -360,7 +388,7 @@ function apply(ctx, rawConfig = {}) {
360
388
  if (parsed.kind === "skill") {
361
389
  const approval = ctx.get("evolutionApproval");
362
390
  if (approval) {
363
- const origins = resolveOrigins(session?.header?.origin);
391
+ const origins = resolveOrigins(session.header.origin);
364
392
  const sessionPolicy = effectiveSessionPolicy(ctx, session);
365
393
  if (approval.isEnabled !== false && sessionPolicy !== "never" && (origins.approval === "background_review" || approval.stageForeground !== false) && !approval.hasRunner("skill")) return err("Graph skill delete cannot be staged: no skill replay runner is registered — mount the tool-skill-manage row (evolution-agent preset) or disable evolution-approval.");
366
394
  const decision = await approval.request({
@@ -375,8 +403,8 @@ function apply(ctx, rawConfig = {}) {
375
403
  libraryOrigin: origins.library
376
404
  },
377
405
  origin: origins.approval,
378
- ...session?.id ? { sessionId: session.id } : {},
379
- ...session ? { session } : {},
406
+ ...session.id ? { sessionId: session.id } : {},
407
+ session,
380
408
  ...sessionPolicy !== void 0 ? { sessionPolicy } : {}
381
409
  });
382
410
  if (decision.action === "staged") return ok(decision.message);
@@ -389,7 +417,7 @@ function apply(ctx, rawConfig = {}) {
389
417
  if (!check.ok) return err(check.message ?? "Memory index check failed.");
390
418
  const memoryApproval = ctx.get("evolutionApproval");
391
419
  if (memoryApproval) {
392
- const origins = resolveOrigins(session?.header?.origin);
420
+ const origins = resolveOrigins(session.header.origin);
393
421
  const sessionPolicy = effectiveSessionPolicy(ctx, session);
394
422
  if (memoryApproval.isEnabled !== false && sessionPolicy !== "never" && (origins.approval === "background_review" || memoryApproval.stageForeground !== false) && !memoryApproval.hasRunner("memory")) return err("Graph memory delete cannot be staged: no memory replay runner is registered — mount the tool-memory row (evolution-all bundle, or the evolution-preset overlay; the host bundle does not carry it) or disable evolution-approval.");
395
423
  const decision = await memoryApproval.request({
@@ -403,8 +431,8 @@ function apply(ctx, rawConfig = {}) {
403
431
  }]
404
432
  },
405
433
  origin: origins.approval,
406
- ...session?.id ? { sessionId: session.id } : {},
407
- ...session ? { session } : {},
434
+ ...session.id ? { sessionId: session.id } : {},
435
+ session,
408
436
  ...sessionPolicy !== void 0 ? { sessionPolicy } : {}
409
437
  });
410
438
  if (decision.action === "staged") return ok(decision.message);
@@ -415,8 +443,13 @@ function apply(ctx, rawConfig = {}) {
415
443
  }]);
416
444
  return result.ok ? ok(result.message) : err(result.message);
417
445
  }
418
- }
419
- }), "evolution-learning-graph.command");
446
+ },
447
+ handler: (invocation) => graphCommand.run(invocation).catch((error) => ({
448
+ kind: "error",
449
+ text: `graph: command failed: ${error instanceof Error ? error.message : String(error)}\nCheck the node id and the skills/memory files under the evolution root, then retry.`
450
+ }))
451
+ };
452
+ commandCtx.effect(() => commands.register(graphCommand), "evolution-learning-graph.command");
420
453
  });
421
454
  }
422
455
  //#endregion
@@ -37,24 +37,6 @@ export interface GraphDensity {
37
37
  edgesPerNode: number;
38
38
  isolatedPct: number;
39
39
  }
40
- /**
41
- * The command invocation contract (N1, v12): the platform command handler
42
- * freezes the invoking agent onto the invocation object — evolution-commands
43
- * `/evolution learn` already reads `invocation.agent` — so the graph can pass
44
- * `agent.session` to the approval service (tool-skill-manage pattern) instead
45
- * of deriving every approval as foreground.
46
- */
47
- export interface GraphInvocation {
48
- rawInput?: string;
49
- agent?: {
50
- session?: {
51
- id?: string;
52
- header?: {
53
- origin?: string;
54
- };
55
- };
56
- };
57
- }
58
40
  export declare function graphDensity(graph: LearningGraph): GraphDensity;
59
41
  /**
60
42
  * Render one node's display line for `/evolution graph`.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-learning-graph",
3
3
  "description": "Learning graph over skills and memory (community build)",
4
- "version": "0.3.67",
4
+ "version": "0.3.69",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -31,14 +31,16 @@
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
33
  "@deepseek-ai/schemastery": "^3.18.1",
34
- "@lmzhen/dsh-evolution-approval": "^0.3.67",
35
- "@lmzhen/dsh-evolution-core": "^0.3.67"
34
+ "@lmzhen/dsh-evolution-approval": "^0.3.69",
35
+ "@lmzhen/dsh-evolution-core": "^0.3.69"
36
36
  },
37
37
  "peerDependencies": {
38
+ "@deepseek-ai/dsh-commands": "^0.1.1-rc.2",
38
39
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
39
40
  "@deepseek-ai/cordis": "^4.0.1"
40
41
  },
41
42
  "devDependencies": {
43
+ "@deepseek-ai/dsh-commands": "^0.1.1-rc.2",
42
44
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2"
43
45
  }
44
46
  }