@duckmind/dm-windows-x64 0.63.0 → 0.63.6

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.
Files changed (101) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +200 -4
  3. package/extensions/dm-context/package.json +1 -1
  4. package/extensions/dm-context/skills/context-management/SKILL.md +173 -223
  5. package/extensions/dm-context/skills/context-management/references/development-and-troubleshooting.md +75 -0
  6. package/extensions/dm-context/skills/context-management/references/interleaved-async-work.md +143 -0
  7. package/extensions/dm-context/skills/context-management/references/planning-and-execution.md +81 -0
  8. package/extensions/dm-context/skills/context-management/references/repeated-items-and-batch-work.md +69 -0
  9. package/extensions/dm-context/skills/context-management/references/retry-branch-and-pivot.md +80 -0
  10. package/extensions/dm-context/skills/context-management/references/search-research-and-reading.md +103 -0
  11. package/extensions/dm-context/skills/context-management/references/task-switching-and-cleanup.md +73 -0
  12. package/extensions/dm-context/src/context.js +3 -2
  13. package/extensions/dm-context/src/index.js +168 -84
  14. package/extensions/dm-skills-manager/THIRD_PARTY_NOTICES.md +27 -0
  15. package/extensions/dm-skills-manager/extensions/skills-manager/components.js +265 -0
  16. package/extensions/dm-skills-manager/extensions/skills-manager/constants.js +31 -0
  17. package/extensions/dm-skills-manager/extensions/skills-manager/creation-fallback.js +20 -0
  18. package/extensions/dm-skills-manager/extensions/skills-manager/creation.js +145 -0
  19. package/extensions/dm-skills-manager/extensions/skills-manager/dialog.js +738 -0
  20. package/extensions/dm-skills-manager/extensions/skills-manager/dm-ai-compat.js +15 -0
  21. package/extensions/dm-skills-manager/extensions/skills-manager/format.js +125 -0
  22. package/extensions/dm-skills-manager/extensions/skills-manager/glyphs.js +142 -0
  23. package/extensions/dm-skills-manager/extensions/skills-manager/layout.js +93 -0
  24. package/extensions/dm-skills-manager/extensions/skills-manager/paths.js +76 -0
  25. package/extensions/dm-skills-manager/extensions/skills-manager/registry.js +95 -0
  26. package/extensions/dm-skills-manager/extensions/skills-manager/settings.js +93 -0
  27. package/extensions/dm-skills-manager/extensions/skills-manager/startup.js +32 -0
  28. package/extensions/dm-skills-manager/extensions/skills-manager/toggle.js +54 -0
  29. package/extensions/dm-skills-manager/extensions/skills-manager/types.js +14 -0
  30. package/extensions/dm-skills-manager/extensions/skills-manager/ui.js +121 -0
  31. package/extensions/dm-skills-manager/extensions/skills-manager.js +111 -0
  32. package/extensions/dm-skills-manager/package.json +121 -0
  33. package/extensions/dm-web-access/SECURITY.md +5 -0
  34. package/extensions/dm-web-access/activity.js +65 -0
  35. package/extensions/dm-web-access/anysearch.js +158 -0
  36. package/extensions/dm-web-access/auth-fetch.js +131 -0
  37. package/extensions/dm-web-access/bocha.js +214 -0
  38. package/extensions/dm-web-access/brave.js +196 -0
  39. package/extensions/dm-web-access/brightdata-unlocker.js +202 -0
  40. package/extensions/dm-web-access/brightdata.js +334 -0
  41. package/extensions/dm-web-access/chrome-cookies.js +627 -0
  42. package/extensions/dm-web-access/content-find.js +114 -0
  43. package/extensions/dm-web-access/credential-source.js +150 -0
  44. package/extensions/dm-web-access/curator-page.js +3559 -0
  45. package/extensions/dm-web-access/curator-server.js +691 -0
  46. package/extensions/dm-web-access/data-uri-sanitize.js +312 -0
  47. package/extensions/dm-web-access/datalab-pdf-extract.js +346 -0
  48. package/extensions/dm-web-access/declared-web-links.js +167 -0
  49. package/extensions/dm-web-access/dm-web-fetch-demo.mp4 +0 -0
  50. package/extensions/dm-web-access/duckduckgo.js +118 -0
  51. package/extensions/dm-web-access/exa.js +401 -0
  52. package/extensions/dm-web-access/extract.js +1220 -0
  53. package/extensions/dm-web-access/feature-config.js +24 -0
  54. package/extensions/dm-web-access/fetch-params.js +81 -0
  55. package/extensions/dm-web-access/firecrawl.js +378 -0
  56. package/extensions/dm-web-access/gemini-adc.js +241 -0
  57. package/extensions/dm-web-access/gemini-api.js +258 -0
  58. package/extensions/dm-web-access/gemini-pdf-extract.js +74 -0
  59. package/extensions/dm-web-access/gemini-search.js +889 -0
  60. package/extensions/dm-web-access/gemini-url-context.js +97 -0
  61. package/extensions/dm-web-access/gemini-web-config.js +84 -0
  62. package/extensions/dm-web-access/gemini-web.js +351 -0
  63. package/extensions/dm-web-access/github-api.js +166 -0
  64. package/extensions/dm-web-access/github-extract.js +991 -0
  65. package/extensions/dm-web-access/github-issue-pr.js +750 -0
  66. package/extensions/dm-web-access/index.js +3117 -0
  67. package/extensions/dm-web-access/jina-search.js +242 -0
  68. package/extensions/dm-web-access/kagi.js +255 -0
  69. package/extensions/dm-web-access/kimi-search.js +214 -0
  70. package/extensions/dm-web-access/ollama.js +209 -0
  71. package/extensions/dm-web-access/openai-search.js +510 -0
  72. package/extensions/dm-web-access/package.json +34 -0
  73. package/extensions/dm-web-access/page-query.js +124 -0
  74. package/extensions/dm-web-access/parallel-mcp.js +223 -0
  75. package/extensions/dm-web-access/parallel.js +345 -0
  76. package/extensions/dm-web-access/pdf-extract.js +257 -0
  77. package/extensions/dm-web-access/perplexity.js +151 -0
  78. package/extensions/dm-web-access/querit.js +327 -0
  79. package/extensions/dm-web-access/query-rewrite.js +40 -0
  80. package/extensions/dm-web-access/render-search-error.js +80 -0
  81. package/extensions/dm-web-access/rsc-extract.js +347 -0
  82. package/extensions/dm-web-access/search1api.js +245 -0
  83. package/extensions/dm-web-access/searchinfinity.js +221 -0
  84. package/extensions/dm-web-access/searxng.js +223 -0
  85. package/extensions/dm-web-access/serpbase.js +205 -0
  86. package/extensions/dm-web-access/serpdive.js +238 -0
  87. package/extensions/dm-web-access/serper.js +200 -0
  88. package/extensions/dm-web-access/source-check.js +198 -0
  89. package/extensions/dm-web-access/ssrf-protection.js +436 -0
  90. package/extensions/dm-web-access/storage.js +451 -0
  91. package/extensions/dm-web-access/summary-model-scope.js +83 -0
  92. package/extensions/dm-web-access/summary-review.js +364 -0
  93. package/extensions/dm-web-access/tavily.js +199 -0
  94. package/extensions/dm-web-access/tinyfish.js +325 -0
  95. package/extensions/dm-web-access/utils.js +476 -0
  96. package/extensions/dm-web-access/valyu.js +189 -0
  97. package/extensions/dm-web-access/video-extract.js +336 -0
  98. package/extensions/dm-web-access/xai-search.js +285 -0
  99. package/extensions/dm-web-access/xcrawl.js +221 -0
  100. package/extensions/dm-web-access/youtube-extract.js +279 -0
  101. package/package.json +9 -1
@@ -1,9 +1,36 @@
1
- import { Type } from "@sinclair/typebox";
1
+ import {
2
+ Type
3
+ } from "@duckmind/dm-ai";
2
4
  import { formatTokens } from "./utils.js";
3
- const InternalTools = ["context_tag", "context_log", "context_checkout"];
5
+ const InternalTools = ["context_checkpoint", "context_timeline", "context_compact"];
6
+ const DmContextCustomMessageType = "dm-context";
7
+ const AcmEnableFollowUp = "Agentic context management is now enabled";
4
8
  let CommandCtx = null;
5
- let CheckoutParams = null;
9
+ let CompactParams = null;
6
10
  const isInternal = (name) => InternalTools.includes(name);
11
+ const formatContextUsage = (usage, includeTokens = false) => {
12
+ if (usage?.percent == null)
13
+ return "Unknown";
14
+ const percent = `${usage.percent.toFixed(1)}%`;
15
+ if (!includeTokens || usage.tokens == null)
16
+ return percent;
17
+ return `${percent} (${formatTokens(usage.tokens)}/${formatTokens(usage.contextWindow)})`;
18
+ };
19
+ const PassiveCompactionEntryTypes = new Set([
20
+ "custom",
21
+ "label",
22
+ "session_info",
23
+ "model_change",
24
+ "thinking_level_change"
25
+ ]);
26
+ export const didConversationAdvance = (branch, compactTurnLeaf) => {
27
+ if (!compactTurnLeaf)
28
+ return true;
29
+ const compactTurnIndex = branch.findIndex((entry) => entry.id === compactTurnLeaf);
30
+ if (compactTurnIndex === -1)
31
+ return true;
32
+ return branch.slice(compactTurnIndex + 1).some((entry) => !PassiveCompactionEntryTypes.has(entry.type));
33
+ };
7
34
  const resolveTargetId = (sm, target) => {
8
35
  if (target.toLowerCase() === "root") {
9
36
  const tree = sm.getTree();
@@ -21,18 +48,21 @@ const resolveTargetId = (sm, target) => {
21
48
  }
22
49
  return target;
23
50
  };
24
- const ContextLogParams = Type.Object({
25
- limit: Type.Optional(Type.Number({ description: "History limit for visible entries (default: 50)." })),
26
- verbose: Type.Optional(Type.Boolean({ description: "If true, show ALL messages. If false (default), collapses intermediate AI steps and only shows 'milestones': User messages, Tags, Branch Points, and Summaries." }))
51
+ const ContextTimelineDescription = "Inspect the active conversation path as a structural map: checkpoints, summaries/compactions, branch points, user turns, and current position. Use when orientation or compact target selection depends on the shape of history.";
52
+ const ContextTimelineParams = Type.Object({
53
+ limit: Type.Optional(Type.Number({ description: "Maximum visible timeline entries (default: 50)." })),
54
+ verbose: Type.Optional(Type.Boolean({ description: "If true, show all messages including internal context-tool traffic. If false (default), collapse to structural milestones." }))
27
55
  });
28
- const ContextCheckoutParams = Type.Object({
29
- target: Type.String({ description: "Where to jump/squash to. Can be a tag name (e.g., 'task-start'), a commit ID, or 'root'. This is the base for your new branch." }),
30
- message: Type.String({ description: "The 'Carryover Message' for the new branch. A summary of your *current* progress/lessons that you want to bring with you to the new state. This ensures you don't lose key information when switching contexts. Good summary message: '[Status] + [Reason] + [Important Changes] + [Carryover Data]'" }),
31
- backupTag: Type.Optional(Type.String({ description: "Optional tag name to apply to the CURRENT state before checking out. Use this to create an automatic backup of the history you are about to leave/squash." }))
56
+ const ContextCompactDescription = "Create a summarized continuation branch from an earlier checkpoint or history node. The selected target is the branch point; the summary must restore the useful state from the compacted path after that target. This changes conversation history only; it does not modify or roll back disk files or external systems.";
57
+ const ContextCompactParams = Type.Object({
58
+ target: Type.String({ description: "Checkpoint name, history node ID, or root to use as the branch point for the summarized continuation." }),
59
+ summary: Type.String({ description: "Handoff summary injected into the new continuation branch. Restore current task/state, decisions/constraints, important external side effects (changed files, processes, browser/tickets/remote state), validation status, source anchors/evidence/open questions likely needed soon, and explicit next step. Do not rely on backupCheckpoint for details needed in the next phase." }),
60
+ backupCheckpoint: Type.Optional(Type.String({ description: "Optional checkpoint name to label the current conversation state before branching. This is only a recovery pointer; the summary must still contain the state needed to continue." }))
32
61
  });
33
- const ContextTagParams = Type.Object({
34
- name: Type.String({ description: "The tag/milestone name. Use meaningful names." }),
35
- target: Type.Optional(Type.String({ description: "The commit ID to tag. Defaults to HEAD (current state)." }))
62
+ const ContextCheckpointDescription = "Create a named anchor by labeling a conversation history node. This does not branch, summarize, or affect external state; it only makes the point easy to find later in timeline or compact target selection.";
63
+ const ContextCheckpointParams = Type.Object({
64
+ name: Type.String({ description: "Unique semantic anchor name that encodes the task and phase/purpose, e.g. parser-fix-start or timeout-investigation-search. Avoid generic names like start, checkpoint-1, or retry." }),
65
+ target: Type.Optional(Type.String({ description: "Optional history node ID or checkpoint name to label. Defaults to the current meaningful position near the conversation head." }))
36
66
  });
37
67
  export default function src_default(pi) {
38
68
  pi.registerCommand("acm", {
@@ -40,41 +70,38 @@ export default function src_default(pi) {
40
70
  handler: async (args, ctx) => {
41
71
  CommandCtx = ctx;
42
72
  ctx.ui.notify("Agentic Context Management enabled.", "info");
43
- pi.sendMessage({
44
- customType: "dm-context",
45
- content: "use context-management skill",
46
- display: false
47
- }, {
48
- deliverAs: "followUp"
49
- });
50
73
  if (args) {
51
- pi.sendUserMessage(args);
74
+ pi.sendUserMessage(args, { deliverAs: "followUp" });
52
75
  }
53
76
  }
54
77
  });
55
- const findTagInTree = (sm, nodes, tagName) => {
56
- for (const n of nodes) {
57
- if (sm.getLabel(n.entry.id) === tagName)
78
+ const findCheckpointInTree = (sm, nodes, checkpointName) => {
79
+ const stack = [...nodes].reverse();
80
+ while (stack.length > 0) {
81
+ const n = stack.pop();
82
+ if (sm.getLabel(n.entry.id) === checkpointName)
58
83
  return n.entry.id;
59
- const r = findTagInTree(sm, n.children, tagName);
60
- if (r)
61
- return r;
84
+ if (n.children?.length) {
85
+ for (let i = n.children.length - 1;i >= 0; i--) {
86
+ stack.push(n.children[i]);
87
+ }
88
+ }
62
89
  }
63
90
  return null;
64
91
  };
65
92
  pi.registerTool({
66
- name: "context_tag",
67
- label: "Context Tag",
68
- description: "Creates a 'Save Point' (Bookmark) in the history. Use this before trying risky changes or when a feature is stable. 'Untagged progress is risky'.",
69
- parameters: ContextTagParams,
93
+ name: "context_checkpoint",
94
+ label: "Context Checkpoint",
95
+ description: ContextCheckpointDescription,
96
+ parameters: ContextCheckpointParams,
70
97
  async execute(_id, params, _signal, _onUpdate, ctx) {
71
98
  const sm = ctx.sessionManager;
72
- const existingTagId = findTagInTree(sm, sm.getTree(), params.name);
73
- if (existingTagId) {
99
+ const existingCheckpointId = findCheckpointInTree(sm, sm.getTree(), params.name);
100
+ if (existingCheckpointId) {
74
101
  return {
75
102
  content: [{
76
103
  type: "text",
77
- text: `Error: Tag '${params.name}' already exists at ${existingTagId}. Tag names must be unique. Use a different name or delete the existing tag first.`
104
+ text: `Error: Checkpoint '${params.name}' already exists at ${existingCheckpointId}. Checkpoint names must be unique. Use a different name or remove the existing one first.`
78
105
  }],
79
106
  details: {}
80
107
  };
@@ -106,14 +133,20 @@ export default function src_default(pi) {
106
133
  id = sm.getLeafId() ?? "";
107
134
  }
108
135
  pi.setLabel(id, params.name);
109
- return { content: [{ type: "text", text: `Created tag '${params.name}' at ${id}` }], details: {} };
136
+ return {
137
+ content: [{
138
+ type: "text",
139
+ text: `Created checkpoint '${params.name}' at ${id}.`
140
+ }],
141
+ details: {}
142
+ };
110
143
  }
111
144
  });
112
145
  pi.registerTool({
113
- name: "context_log",
114
- label: "Context Log",
115
- description: "Show the entire history structure (status, message, tags, milestones). Analogous to 'git log --graph --oneline --decorate'",
116
- parameters: ContextLogParams,
146
+ name: "context_timeline",
147
+ label: "Context Timeline",
148
+ description: ContextTimelineDescription,
149
+ parameters: ContextTimelineParams,
117
150
  async execute(_id, params, _signal, _onUpdate, ctx) {
118
151
  const sm = ctx.sessionManager;
119
152
  const branch = sm.getBranch();
@@ -137,7 +170,7 @@ export default function src_default(pi) {
137
170
  return e.summary || "[No summary provided]";
138
171
  }
139
172
  if (entry.type === "label") {
140
- return `tag: ${entry.label}`;
173
+ return `checkpoint: ${entry.label}`;
141
174
  }
142
175
  if (entry.type === "message") {
143
176
  const msg = entry.message;
@@ -234,7 +267,7 @@ export default function src_default(pi) {
234
267
  }
235
268
  const id = entry.id;
236
269
  const isRoot = branch.length > 0 && entry.id === branch[0].id;
237
- const meta = [isRoot ? "ROOT" : null, isHead ? "HEAD" : null, label ? `tag: ${label}` : null].filter(Boolean).join(", ");
270
+ const meta = [isRoot ? "ROOT" : null, isHead ? "HEAD" : null, label ? `checkpoint: ${label}` : null].filter(Boolean).join(", ");
238
271
  const body = content.length > 100 ? content.slice(0, 100) + "..." : content;
239
272
  const marker = isHead ? "*" : role === "USER" ? "•" : "|";
240
273
  lines.push(`${marker} ${id}${meta ? ` (${meta})` : ""} [${role}] ${body}`);
@@ -242,26 +275,24 @@ export default function src_default(pi) {
242
275
  if (hiddenCount > 0) {
243
276
  lines.push(` : ... (${hiddenCount} hidden messages) ...`);
244
277
  }
245
- const usage = await ctx.getContextUsage();
246
- let usageStr = "Unknown";
247
- if (usage) {
248
- usageStr = `${usage.percent.toFixed(1)}% (${formatTokens(usage.tokens)}/${formatTokens(usage.contextWindow)})`;
249
- }
250
- let stepsSinceTag = 0;
251
- let nearestTagName = "None";
278
+ const usageStr = formatContextUsage(ctx.getContextUsage(), true);
279
+ let stepsSinceCheckpoint = 0;
280
+ let nearestCheckpointName = "None";
252
281
  for (let i = branch.length - 1;i >= 0; i--) {
253
282
  const id = branch[i].id;
254
283
  const label = sm.getLabel(id);
255
284
  if (label) {
256
- nearestTagName = label;
285
+ nearestCheckpointName = label;
257
286
  break;
258
287
  }
259
- stepsSinceTag++;
288
+ stepsSinceCheckpoint++;
260
289
  }
290
+ const compactCue = nearestCheckpointName === "None" ? "create a checkpoint before the next noisy phase" : `if this segment has produced a stable result and another phase remains, compact to '${nearestCheckpointName}' with a handoff summary before continuing`;
261
291
  const hud = [
262
292
  `[Context Dashboard]`,
263
293
  `• Context Usage: ${usageStr}`,
264
- `• Segment Size: ${stepsSinceTag} steps since last tag '${nearestTagName}'`,
294
+ `• Segment Size: ${stepsSinceCheckpoint} steps since last checkpoint '${nearestCheckpointName}'`,
295
+ `• Compact Cue: ${compactCue}`,
265
296
  `---------------------------------------------------`
266
297
  ].join(`
267
298
  `);
@@ -271,13 +302,16 @@ export default function src_default(pi) {
271
302
  }
272
303
  });
273
304
  pi.registerTool({
274
- name: "context_checkout",
275
- label: "Context Checkout",
276
- description: "Navigate to ANY point in the conversation history. This checkout only resets *conversation history*, NOT disk files. ALWAYS provide a detailed 'message' to bridge context.",
277
- parameters: ContextCheckoutParams,
305
+ name: "context_compact",
306
+ label: "Context Compact",
307
+ description: ContextCompactDescription,
308
+ parameters: ContextCompactParams,
278
309
  async execute(_id, params, _signal, _onUpdate, ctx) {
279
310
  if (!CommandCtx) {
280
- ctx.ui.setEditorText(`/acm ${ctx.ui.getEditorText() || "continue"}`);
311
+ const editorText = ctx.ui.getEditorText();
312
+ const followUp = editorText ? `${AcmEnableFollowUp}
313
+ ${editorText}` : AcmEnableFollowUp;
314
+ ctx.ui.setEditorText(`/acm ${followUp}`);
281
315
  return {
282
316
  content: [{
283
317
  type: "text",
@@ -287,52 +321,102 @@ export default function src_default(pi) {
287
321
  };
288
322
  }
289
323
  const sm = ctx.sessionManager;
324
+ const usageBeforeText = formatContextUsage(ctx.getContextUsage());
290
325
  const tid = resolveTargetId(sm, params.target);
291
326
  const currentLeaf = sm.getLeafId();
292
327
  if (currentLeaf === tid) {
293
328
  return { content: [{ type: "text", text: `Already at target ${tid}` }], details: {} };
294
329
  }
295
- if (params.backupTag && currentLeaf) {
296
- pi.setLabel(currentLeaf, params.backupTag);
330
+ if (params.backupCheckpoint && currentLeaf) {
331
+ pi.setLabel(currentLeaf, params.backupCheckpoint);
297
332
  }
298
333
  const currentLabel = currentLeaf ? sm.getLabel(currentLeaf) : undefined;
299
- const origin = currentLabel ? `tag: ${currentLabel}` : currentLeaf || "unknown";
300
- const enrichedMessage = `(summary from ${origin})
301
- ${params.message}`;
302
- const nid = await sm.branchWithSummary(tid, enrichedMessage);
303
- CheckoutParams = params;
304
- CheckoutParams.nid = nid;
305
- CheckoutParams.tid = tid;
306
- CheckoutParams.enrichedMessage = enrichedMessage;
307
- return { content: [{ type: "text", text: "checkout start" }], details: {} };
334
+ const origin = currentLabel ? `checkpoint: ${currentLabel}` : currentLeaf || "unknown";
335
+ const enrichedMessage = `(handoff summary from ${origin})
336
+ ${params.summary}`;
337
+ CompactParams = params;
338
+ CompactParams.tid = tid;
339
+ CompactParams.enrichedMessage = enrichedMessage;
340
+ CompactParams.usageBeforeText = usageBeforeText;
341
+ return { content: [{ type: "text", text: "compact start" }], details: {} };
308
342
  }
309
343
  });
310
- pi.on("turn_end", async (event, ctx) => {
311
- if (!CheckoutParams) {
344
+ pi.on("turn_end", async (_event, ctx) => {
345
+ if (!CompactParams) {
312
346
  return;
313
347
  }
314
348
  ctx.abort();
315
349
  });
316
350
  pi.on("agent_end", async (_event, ctx) => {
317
- if (!CheckoutParams) {
351
+ if (!CompactParams) {
318
352
  return;
319
353
  }
320
354
  if (!CommandCtx) {
321
355
  return;
322
356
  }
323
- await CommandCtx.navigateTree(CheckoutParams.nid, {
324
- summarize: false
325
- });
326
- ctx.ui.notify(`Checked out ${CheckoutParams.target}${CheckoutParams.target === CheckoutParams.tid ? "" : `(${CheckoutParams.tid})`}
327
- Backup tag created: ${CheckoutParams.backupTag || "none"}
328
- message: ${CheckoutParams.enrichedMessage}`, "info");
329
- CheckoutParams = null;
330
- pi.sendMessage({
331
- customType: "dm-context",
332
- content: "context_checkout complete. A summary of your previous branch was injected above. Read it to understand your new state. Execute the 'Next Step' from the summary",
333
- display: false
334
- }, {
335
- triggerTurn: true
336
- });
357
+ const sm = ctx.sessionManager;
358
+ const compactParams = CompactParams;
359
+ const commandCtx = CommandCtx;
360
+ CompactParams = null;
361
+ const compactTurnLeaf = sm.getLeafId();
362
+ setTimeout(async () => {
363
+ try {
364
+ await commandCtx.waitForIdle();
365
+ const branch = sm.getBranch();
366
+ if (didConversationAdvance(branch, compactTurnLeaf)) {
367
+ commandCtx.ui.notify("context_compact cancelled: conversation advanced before compaction completed.", "warning");
368
+ pi.sendMessage({
369
+ customType: DmContextCustomMessageType,
370
+ content: [
371
+ "context_compact cancelled: conversation advanced before the summary branch was created.",
372
+ "No compaction was applied; continue from the current path. If still useful, inspect timeline and retry with an updated summary."
373
+ ].join(`
374
+ `),
375
+ display: false
376
+ }, {
377
+ triggerTurn: true,
378
+ deliverAs: "followUp"
379
+ });
380
+ return;
381
+ }
382
+ const nid = sm.branchWithSummary(compactParams.tid, compactParams.enrichedMessage);
383
+ compactParams.nid = nid;
384
+ sm.branch(compactParams.tid);
385
+ await commandCtx.navigateTree(compactParams.nid, {
386
+ summarize: false
387
+ });
388
+ const usageAfter = commandCtx.getContextUsage();
389
+ commandCtx.ui.notify([
390
+ `Compacted to ${compactParams.target}${compactParams.target === compactParams.tid ? "" : `(${compactParams.tid})`}`,
391
+ `Context Usage: ${compactParams.usageBeforeText} -> ${formatContextUsage(usageAfter)}`,
392
+ `Backup checkpoint created: ${compactParams.backupCheckpoint || "none"}`,
393
+ `Summary: ${compactParams.enrichedMessage}`
394
+ ].join(`
395
+ `), "info");
396
+ pi.sendMessage({
397
+ customType: DmContextCustomMessageType,
398
+ content: "context_compact complete. A handoff summary of your previous conversation path was injected above. Read it to understand your new state. Execute the Next Step from the summary",
399
+ display: false
400
+ }, {
401
+ triggerTurn: true,
402
+ deliverAs: "followUp"
403
+ });
404
+ } catch (err) {
405
+ const message = err instanceof Error ? err.message : String(err);
406
+ commandCtx.ui.notify(`context_compact failed: ${message}`, "error");
407
+ pi.sendMessage({
408
+ customType: DmContextCustomMessageType,
409
+ content: [
410
+ `context_compact failed: ${message}`,
411
+ "No compaction was applied; continue from the current path. Retry only with a fresh timeline/summary."
412
+ ].join(`
413
+ `),
414
+ display: false
415
+ }, {
416
+ triggerTurn: true,
417
+ deliverAs: "followUp"
418
+ });
419
+ }
420
+ }, 0);
337
421
  });
338
422
  }
@@ -0,0 +1,27 @@
1
+ # Third-party notices
2
+
3
+ This package is based on ideas and portions of [`@kmiyh/pi-skills-menu`](https://github.com/Kmiyh/pi-skills-menu), which is licensed under the MIT License.
4
+
5
+ ```text
6
+ MIT License
7
+
8
+ Copyright (c) 2025 Kmiyh
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+ ```
@@ -0,0 +1,265 @@
1
+ import {
2
+ Container,
3
+ Editor,
4
+ Key,
5
+ Markdown,
6
+ matchesKey,
7
+ Spacer,
8
+ Text,
9
+ truncateToWidth,
10
+ visibleWidth
11
+ } from "@duckmind/dm-tui";
12
+ import { getMarkdownTheme } from "@duckmind/dm-coding-agent";
13
+ import { buildFrontmatterBlock } from "./format.js";
14
+ import { glyphs } from "./glyphs.js";
15
+ import { isDeletableSkill } from "./registry.js";
16
+ import {
17
+ getEditorTheme,
18
+ inlineLine,
19
+ packageLabel,
20
+ padAnsi,
21
+ scopeLabel,
22
+ skillEntityTitle,
23
+ skillKeyHints,
24
+ skillLocation,
25
+ skillSelectedLine,
26
+ toneText,
27
+ renderFrame
28
+ } from "./ui.js";
29
+
30
+ export class SingleLineText {
31
+ text;
32
+ ellipsis;
33
+ constructor(text, ellipsis = "...") {
34
+ this.text = text;
35
+ this.ellipsis = ellipsis;
36
+ }
37
+ render(width) {
38
+ return [truncateToWidth(inlineLine(this.text), width, this.ellipsis)];
39
+ }
40
+ invalidate() {}
41
+ }
42
+
43
+ export class ListLineText {
44
+ text;
45
+ selected;
46
+ theme;
47
+ ellipsis;
48
+ constructor(text, selected, theme, ellipsis = "...") {
49
+ this.text = text;
50
+ this.selected = selected;
51
+ this.theme = theme;
52
+ this.ellipsis = ellipsis;
53
+ }
54
+ render(width) {
55
+ const line = truncateToWidth(inlineLine(this.text), width, this.ellipsis);
56
+ return [this.selected ? skillSelectedLine(this.theme, line, width) : line];
57
+ }
58
+ invalidate() {}
59
+ }
60
+
61
+ export class PrefixedEditor {
62
+ editor;
63
+ prefix;
64
+ constructor(editor, prefix = "> ") {
65
+ this.editor = editor;
66
+ this.prefix = prefix;
67
+ }
68
+ render(width) {
69
+ const rendered = this.editor.render(Math.max(1, width - this.prefix.length));
70
+ const lines = rendered.length >= 2 ? rendered.slice(1, -1) : rendered;
71
+ return lines.length === 0 ? [this.prefix] : lines.map((line, index) => `${index === 0 ? this.prefix : " "}${line}`);
72
+ }
73
+ invalidate() {
74
+ this.editor.invalidate();
75
+ }
76
+ }
77
+
78
+ export class SearchInputLine {
79
+ input;
80
+ theme;
81
+ prefix;
82
+ constructor(input, theme, prefix = " ") {
83
+ this.input = input;
84
+ this.theme = theme;
85
+ this.prefix = prefix;
86
+ }
87
+ render(width) {
88
+ const inputWidth = Math.max(1, width - visibleWidth(this.prefix));
89
+ const line = truncateToWidth(`${this.prefix}${this.input.render(inputWidth)[0] ?? ""}`, width, "");
90
+ return [this.theme.bg("toolPendingBg", padAnsi(line, width))];
91
+ }
92
+ invalidate() {
93
+ this.input.invalidate();
94
+ }
95
+ }
96
+
97
+ export class ScrollableSkillPreview {
98
+ scrollOffset = 0;
99
+ lastInnerWidth = 1;
100
+ lastContentLines = [];
101
+ skill;
102
+ theme;
103
+ getTerminalRows;
104
+ constructor(skill, theme, getTerminalRows) {
105
+ this.skill = skill;
106
+ this.theme = theme;
107
+ this.getTerminalRows = getTerminalRows;
108
+ }
109
+ setSkill(skill) {
110
+ this.skill = skill;
111
+ this.scrollOffset = 0;
112
+ this.lastContentLines = [];
113
+ }
114
+ invalidate() {
115
+ this.lastContentLines = [];
116
+ }
117
+ maxHeight() {
118
+ return Math.max(10, Math.floor(this.getTerminalRows() * 0.78));
119
+ }
120
+ buildContentLines(innerWidth) {
121
+ const content = new Container;
122
+ const status = this.skill.enabled ? this.theme.fg("success", "enabled") : this.theme.fg("warning", "disabled");
123
+ const source = packageLabel(this.skill) ? `${packageLabel(this.skill)}` : this.skill.path;
124
+ content.addChild(new Text(skillEntityTitle(this.theme, this.skill.name), 0, 0));
125
+ content.addChild(new Text(`${this.theme.fg("muted", scopeLabel(this.skill))}${this.theme.fg("dim", ` ${glyphs().bullet.trim()} `)}${this.theme.fg("muted", source)}${this.theme.fg("dim", ` ${glyphs().bullet.trim()} `)}${status}`, 0, 0));
126
+ content.addChild(new Spacer(1));
127
+ content.addChild(new Text(this.theme.fg("muted", this.theme.bold("Description")), 0, 0));
128
+ content.addChild(new Text(this.skill.description, 0, 0));
129
+ content.addChild(new Spacer(1));
130
+ content.addChild(new Text(this.theme.fg("muted", this.theme.bold("Metadata")), 0, 0));
131
+ content.addChild(new Text(this.theme.fg("dim", buildFrontmatterBlock(this.skill)), 0, 0));
132
+ content.addChild(new Spacer(1));
133
+ content.addChild(new Text(this.theme.fg("muted", this.theme.bold("Content")), 0, 0));
134
+ content.addChild(new Spacer(1));
135
+ content.addChild(new Markdown(this.skill.content || this.theme.fg("dim", "(empty skill body)"), 0, 0, getMarkdownTheme()));
136
+ const lines = content.render(innerWidth);
137
+ this.lastInnerWidth = innerWidth;
138
+ this.lastContentLines = lines;
139
+ return lines;
140
+ }
141
+ footer(innerWidth, visibleHeight, totalLines) {
142
+ const maxScroll = Math.max(0, totalLines - visibleHeight);
143
+ const scroll = maxScroll > 0 ? this.theme.fg("dim", ` ${glyphs().bullet.trim()} ${this.scrollOffset + 1}-${Math.min(totalLines, this.scrollOffset + visibleHeight)}/${totalLines}`) : "";
144
+ const hints = [["-/=", "page"]];
145
+ hints.push(["ctrl+x", "enable/disable"]);
146
+ if (isDeletableSkill(this.skill))
147
+ hints.push(["alt+e", "edit"], ["alt+r", "rename"], ["backspace", "delete"]);
148
+ return truncateToWidth(`${skillKeyHints(this.theme, hints)}${scroll}`, innerWidth, this.theme.fg("dim", "..."));
149
+ }
150
+ render(width) {
151
+ if (width < 8)
152
+ return [];
153
+ const innerWidth = Math.max(1, width - 4);
154
+ const visibleHeight = Math.max(1, this.maxHeight() - 3);
155
+ const contentLines = this.buildContentLines(innerWidth);
156
+ const maxScroll = Math.max(0, contentLines.length - visibleHeight);
157
+ this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, maxScroll));
158
+ const visible = contentLines.slice(this.scrollOffset, this.scrollOffset + visibleHeight);
159
+ return renderFrame(this.theme, width, [...visible, this.footer(innerWidth, visibleHeight, contentLines.length)]);
160
+ }
161
+ handleInput(data) {
162
+ const visibleHeight = Math.max(1, this.maxHeight() - 3);
163
+ const total = this.lastContentLines.length || this.buildContentLines(this.lastInnerWidth).length;
164
+ const maxScroll = Math.max(0, total - visibleHeight);
165
+ if (matchesKey(data, Key.up))
166
+ this.scrollOffset = Math.max(0, this.scrollOffset - 1);
167
+ else if (matchesKey(data, Key.down))
168
+ this.scrollOffset = Math.min(maxScroll, this.scrollOffset + 1);
169
+ else if (matchesKey(data, "-") || matchesKey(data, Key.pageUp))
170
+ this.scrollOffset = Math.max(0, this.scrollOffset - visibleHeight);
171
+ else if (matchesKey(data, "=") || matchesKey(data, Key.pageDown))
172
+ this.scrollOffset = Math.min(maxScroll, this.scrollOffset + visibleHeight);
173
+ else if (matchesKey(data, Key.home))
174
+ this.scrollOffset = 0;
175
+ else if (matchesKey(data, Key.end))
176
+ this.scrollOffset = maxScroll;
177
+ }
178
+ }
179
+
180
+ export class SkillEditorView {
181
+ editor;
182
+ initialText;
183
+ proxyTui;
184
+ skill;
185
+ theme;
186
+ realTui;
187
+ onSave;
188
+ onCancel;
189
+ virtualRows = 24;
190
+ _focused = false;
191
+ message;
192
+ get focused() {
193
+ return this._focused;
194
+ }
195
+ set focused(value) {
196
+ this._focused = value;
197
+ this.editor.focused = value;
198
+ }
199
+ constructor(skill, theme, realTui, initialText, onSave, onCancel) {
200
+ this.skill = skill;
201
+ this.theme = theme;
202
+ this.realTui = realTui;
203
+ this.onSave = onSave;
204
+ this.onCancel = onCancel;
205
+ this.initialText = initialText;
206
+ const self = this;
207
+ this.proxyTui = { requestRender: () => realTui.requestRender(), get terminal() {
208
+ return { ...realTui.terminal, rows: Math.max(1, self.virtualRows) };
209
+ } };
210
+ this.editor = new Editor(this.proxyTui, getEditorTheme(theme), { autocompleteMaxVisible: 6 });
211
+ this.editor.setText(initialText);
212
+ }
213
+ setSkill(skill) {
214
+ this.skill = skill;
215
+ }
216
+ setMessage(text, tone) {
217
+ this.message = { text, tone };
218
+ }
219
+ isDirty() {
220
+ return this.editor.getText() !== this.initialText;
221
+ }
222
+ invalidate() {
223
+ this.editor.invalidate();
224
+ }
225
+ targetHeight() {
226
+ return Math.max(10, Math.floor(this.realTui.terminal.rows * 0.78));
227
+ }
228
+ rowsForVisibleEditorLines(targetVisibleLines) {
229
+ let rows = 5;
230
+ while (Math.max(5, Math.floor(rows * 0.3)) < targetVisibleLines && rows < 1000)
231
+ rows += 1;
232
+ return rows;
233
+ }
234
+ render(width) {
235
+ const innerWidth = Math.max(1, width - 4);
236
+ const lines = [
237
+ skillEntityTitle(this.theme, `Edit ${this.skill.name}`),
238
+ this.theme.fg("muted", skillLocation(this.skill)),
239
+ this.theme.fg("dim", `Name is immutable here: ${this.skill.name}`)
240
+ ];
241
+ if (this.message)
242
+ lines.push("", toneText(this.theme, this.message.tone, this.message.text));
243
+ const targetInnerLines = Math.max(1, this.targetHeight() - 2);
244
+ const staticLineCount = lines.length + 3;
245
+ const editorBlockLines = Math.max(7, targetInnerLines - staticLineCount);
246
+ this.virtualRows = this.rowsForVisibleEditorLines(Math.max(5, editorBlockLines - 2));
247
+ lines.push("", ...this.editor.render(innerWidth), "", truncateToWidth(skillKeyHints(this.theme, [["alt+s", "save"]]), innerWidth, this.theme.fg("dim", "...")));
248
+ while (lines.length < targetInnerLines)
249
+ lines.splice(Math.max(0, lines.length - 1), 0, "");
250
+ return renderFrame(this.theme, width, lines.slice(0, targetInnerLines));
251
+ }
252
+ handleInput(data) {
253
+ if (matchesKey(data, Key.escape)) {
254
+ this.onCancel();
255
+ return;
256
+ }
257
+ if (matchesKey(data, Key.alt("s")) || matchesKey(data, Key.ctrl("s"))) {
258
+ this.onSave(this.editor.getText());
259
+ return;
260
+ }
261
+ if (this.message?.tone === "error")
262
+ this.message = undefined;
263
+ this.editor.handleInput(data);
264
+ }
265
+ }