@duckmind/dm-darwin-x64 0.49.9 → 0.50.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.
Files changed (38) hide show
  1. package/dm +0 -0
  2. package/extensions/.dm-extensions.json +4 -185
  3. package/extensions/dm-cliproxy/index.js +2 -2
  4. package/extensions/dm-cliproxy/scripts/check-config-migration.js +8 -10
  5. package/extensions/dm-cliproxy/src/apply.js +1 -1
  6. package/extensions/dm-cliproxy/src/cache.js +1 -1
  7. package/extensions/dm-cliproxy/src/commands.js +2 -2
  8. package/extensions/dm-cliproxy/src/config.js +2 -2
  9. package/extensions/dm-cliproxy/src/fetch-models.js +1 -1
  10. package/extensions/dm-cliproxy/src/fetch-usage.js +1 -1
  11. package/extensions/dm-cliproxy/src/ui-hub/hub.js +2 -2
  12. package/extensions/dm-cliproxy/src/ui-hub/index.js +2 -2
  13. package/extensions/dm-cliproxy/src/ui-hub/view-diagnostics.js +1 -1
  14. package/extensions/dm-cliproxy/src/ui-hub/view-usage.js +1 -1
  15. package/extensions/dm-cliproxy/src/ui-setup.js +2 -2
  16. package/extensions/dm-cliproxy/src/usage-shared-cache.js +1 -1
  17. package/extensions/dm-image2/index.js +7 -7
  18. package/extensions/dm-image2/src/image-lib.mjs +1 -1
  19. package/package.json +1 -1
  20. package/CHANGELOG.md +0 -4796
  21. package/extensions/dm-ask-user/index.js +0 -10
  22. package/extensions/dm-ask-user/package.json +0 -18
  23. package/extensions/dm-cli-anything/index.js +0 -5
  24. package/extensions/dm-cli-anything/package.json +0 -22
  25. package/extensions/dm-cli-anything/skills/cli-anything/SKILL.md +0 -21
  26. package/extensions/dm-cli-anything/vendor/public_registry.json +0 -367
  27. package/extensions/dm-cli-anything/vendor/registry.json +0 -1232
  28. package/extensions/dm-grill-me/index.js +0 -174
  29. package/extensions/dm-grill-me/package.json +0 -23
  30. package/extensions/dm-tasks/package.json +0 -20
  31. package/extensions/dm-tasks/src/auto-clear.js +0 -1
  32. package/extensions/dm-tasks/src/index.js +0 -212
  33. package/extensions/dm-tasks/src/process-tracker.js +0 -1
  34. package/extensions/dm-tasks/src/task-store.js +0 -1
  35. package/extensions/dm-tasks/src/tasks-config.js +0 -1
  36. package/extensions/dm-tasks/src/types.js +0 -0
  37. package/extensions/dm-tasks/src/ui/settings-menu.js +0 -1
  38. package/extensions/dm-tasks/src/ui/task-widget.js +0 -1
@@ -1,174 +0,0 @@
1
- import{CustomEditor as c,DynamicBorder as r,getMarkdownTheme as s}from"@duckmind/dm-coding-agent";import{Markdown as a,matchesKey as N,Text as k,truncateToWidth as P}from"@duckmind/dm-tui";import{Type as H}from"typebox";var v="grill-me-state",e="design-doc by default; adapt/recommend near readiness",U={active:!1,topic:"",intent:"auto",outputPreference:"",researchMode:"auto",checkpoint:"",phase:"interview",outputPhase:!1,outputSelection:void 0,approvedOutputPlan:void 0,alternatives:[],currentQuestion:void 0,updatedAt:Date.now()},p=["auto","plan","learn","research","content","decide"],l=["off","ask","auto"],g=[{label:"GitHub issues",value:"github-issues",description:"Issue titles/bodies/labels for implementation slices, research tasks, tutorial chapters, or milestones."},{label:"Design doc",value:"design-doc",description:"A structured design proposal with goals, constraints, architecture, tradeoffs, risks, and rollout."},{label:"README.md",value:"readme",description:"A README or README update covering setup, usage, behavior, examples, and caveats."},{label:"ADR",value:"adr",description:"Architecture Decision Record(s) documenting decision context, options, choice, and consequences."},{label:"PRD",value:"prd",description:"Product requirements, user stories, scope, acceptance criteria, and non-goals."},{label:"Implementation plan",value:"implementation-plan",description:"Step-by-step engineering plan, milestones, sequencing, dependencies, and validation."},{label:"Research brief",value:"research-brief",description:"Open questions, investigation plan, evidence to gather, and decision criteria."},{label:"Summary / decision memo",value:"summary",description:"Concise summary of the checkpoint, decisions, assumptions, and next actions."},{label:"Tutorial / content outline",value:"content-outline",description:"Chapters, lesson flow, examples, exercises, or publishing outline."},{label:"Test plan / QA checklist",value:"test-plan",description:"Acceptance tests, manual QA steps, edge cases, and regression coverage."},{label:"Changelog / release notes",value:"release-notes",description:"User-facing change summary, migration notes, and release caveats."}],b="If approved GitHub issue output has no repo/remote, ask to initialize, create, or select one before creating the previewed issues; use drafts only if the user chooses.";function C(J){return{...J}}function E(J){return(typeof J.outputPreference==="string"?J.outputPreference.trim():"")||"(none set; explicitly ask for one or more outputs before production)"}function O(){return g.map((J)=>`- ${J.label} (${J.value}): ${J.description}`).join(`
2
- `)}function f(){return g.map((J)=>J.label).join(", ")}function A(J){if(J.outputPhase)return"output";return J.phase??"interview"}function d(J){let j=A(J);if(j==="output")return"output production; approved mutations allowed";if(j==="output-selection")return"mandatory output selection; choose final outputs/continue/stop";return"interview; read-only enforcement active"}function t(J,j){return`# Shared Understanding
3
-
4
- ## Topic
5
-
6
- ${J}
7
-
8
- ## Current Understanding
9
-
10
- We are starting a grill-me session to reach shared understanding before producing outputs or implementation work.
11
-
12
- ## Working Configuration
13
-
14
- - Intent: ${j.intent}
15
- - Grilling style: thorough Socratic interview
16
- - Research mode: ${j.researchMode}
17
- - Output preference: ${E(j)}
18
-
19
- ## Decisions
20
-
21
- - Grill mode uses a single thorough default style.
22
- - Grill mode should adapt to the subject rather than force hardcoded interview phases.
23
- - A hardcoded output-selection phase is mandatory at the end of the interview before output production or stopping.
24
- - Grill mode must not assume a default output. The assistant must explicitly ask which output(s) to produce.
25
-
26
- ## Assumptions
27
-
28
- - The checkpoint should evolve as meaningful understanding changes.
29
- - The assistant should ask enough follow-up questions to resolve the decision tree instead of rushing to readiness.
30
-
31
- ## Risks / Unknowns
32
-
33
- - The user's desired outcome mode and output set may still be ambiguous.
34
- - Some branches may need to be explicitly deferred if they are not worth resolving now.
35
-
36
- ## Coverage Checklist
37
-
38
- Use this as an adaptive checklist, not a rigid phase order. Mark each branch resolved, intentionally deferred, or still open.
39
-
40
- - [ ] Desired outcome and success criteria
41
- - [ ] Scope boundaries and non-goals
42
- - [ ] User/audience/stakeholder context
43
- - [ ] Constraints, dependencies, and available resources
44
- - [ ] Alternatives, tradeoffs, and decision criteria
45
- - [ ] Risks, failure modes, edge cases, and open unknowns
46
- - [ ] Validation, testing, or evidence plan
47
- - [ ] Rollout/next steps and ownership
48
- - [ ] Output artifact selection (only in the mandatory terminal phase)
49
-
50
- ## Decision Branches
51
-
52
- - Root: clarify the user's desired outcome and success criteria, then follow dependent branches one at a time.
53
-
54
- ## Open Questions
55
-
56
- - What outcome is the user ultimately trying to achieve with this topic?
57
- - What constraints or risks should shape the next branch of questioning?
58
- - Which output artifact(s) should be produced, if any, once shared understanding is sufficient?
59
-
60
- ## Explicit Output Destination Options
61
-
62
- ${O()}
63
- `}function jj(J){return`# Grill Status
64
-
65
- - Active: ${J.active?"yes":"no"}
66
- - Topic: ${J.topic||"(none)"}
67
- - Intent: ${J.intent}
68
- - Style: thorough default
69
- - Research: ${J.researchMode}
70
- - Phase: ${d(J)}
71
- - Output preference: ${E(J)}
72
- ${J.outputSelection?`- Output selection rationale: ${J.outputSelection.readinessRationale}
73
- - Recommended outputs: ${J.outputSelection.recommendedOutputs}
74
- - Recommended strategy: ${J.outputSelection.recommendedStrategy}
75
- `:""}${J.approvedOutputPlan?`- Approved output plan: ${J.approvedOutputPlan}
76
- `:""}- Current question: ${J.currentQuestion||"(none)"}
77
- - Tab alternatives: ${J.alternatives.length?J.alternatives.map((j)=>j.label).join(" | "):"(none set)"}
78
- - Checkpoint last updated: ${J.updatedAt?new Date(J.updatedAt).toLocaleString():"never"}
79
- ${J.lastChangeSummary?`- Last checkpoint change: ${J.lastChangeSummary}
80
- `:""}`}function T(J){return J.map((j)=>({value:String(j.value??"").trim(),label:String(j.label??j.value??"").trim(),description:j.description?String(j.description).trim():void 0})).filter((j)=>j.value&&j.label).slice(0,5)}function y(J){return J.replace(/\r\n/g,`
81
- `).trim()}function Jj(J,j,X){if(j.length===0)return-1;let z=y(J);if(!z)return X>0?0:j.length-1;let F=j.findIndex((q)=>y(q.value)===z);if(F>=0)return(F+X+j.length)%j.length;if(/\s/.test(z))return-1;let K=z.toLowerCase();return j.findIndex((q)=>q.label.toLowerCase().includes(K)||q.value.toLowerCase().includes(K))}class m extends c{grillKeybindings;getGrillState;constructor(J,j,X,z,F){super(J,j,X,F);this.grillKeybindings=X;this.getGrillState=z}handleInput(J){if(!this.isShowingAutocomplete()){if(this.grillKeybindings.matches(J,"tui.input.tab")&&this.cycleGrillAlternative(1))return;if(N(J,"shift+tab")&&this.cycleGrillAlternative(-1))return}super.handleInput(J)}cycleGrillAlternative(J){let j=this.getGrillState();if(!j.active||j.alternatives.length===0)return!1;let X=Jj(this.getText(),j.alternatives,J);if(X<0)return!1;return this.setText(j.alternatives[X].value),this.tui.requestRender(),!0}}function Qj(J,j){return{async getSuggestions(X,z,F,K){let q=j();if(!q.active||q.alternatives.length===0)return J.getSuggestions(X,z,F,K);let V=X[z]??"",Q=V.slice(0,F),Y=V.slice(F),Z=Q.match(/([^\s]*)$/)?.[1]??"",W=Q.slice(0,Q.length-Z.length);if(!(X.slice(0,z).join(`
82
- `).trim()===""&&W.trim()===""&&Y.trim()===""&&X.slice(z+1).join(`
83
- `).trim()===""))return J.getSuggestions(X,z,F,K);let B=Z.toLowerCase(),w=q.alternatives.filter((G)=>!B||G.label.toLowerCase().includes(B)||G.value.toLowerCase().includes(B)).map((G)=>({value:G.value,label:G.label,description:G.description}));if(w.length===0)return J.getSuggestions(X,z,F,K);return{prefix:Z,items:w}},applyCompletion(X,z,F,K,q){return J.applyCompletion(X,z,F,K,q)},shouldTriggerFileCompletion(X,z,F){return J.shouldTriggerFileCompletion?.(X,z,F)??!0}}}function Vj(J){let j=J?.content;if(typeof j==="string")return j;if(Array.isArray(j))return j.filter((X)=>X?.type==="text"&&typeof X.text==="string").map((X)=>X.text).join(`
84
- `);return""}function h(J){let j=J.sessionManager.getBranch(),X=[];for(let F=j.length-1;F>=0&&X.join(`
85
- `).length<1600;F--){let K=j[F];if(K?.type!=="message")continue;let q=K.message?.role;if(q!=="user"&&q!=="assistant")continue;let V=Vj(K.message).trim();if(!V||V.startsWith("/grill"))continue;X.unshift(`${q}: ${V}`)}let z=X.join(`
86
-
87
- `).trim();return z?`Current conversation context:
88
-
89
- ${z}`:""}function Xj(J){let j=J.match(/(?:[^\s"]+|"[^"]*")+/g)??[],X={},z=[];for(let F=0;F<j.length;F++){let K=j[F].replace(/^"|"$/g,"");if(K.startsWith("--")){let q=K.indexOf("=");if(q>2)X[K.slice(2,q)]=K.slice(q+1);else{let V=K.slice(2),Q=j[F+1]?.replace(/^"|"$/g,"");if(Q&&!Q.startsWith("--"))X[V]=Q,F++;else X[V]=!0}}else z.push(K)}return{flags:X,rest:z.join(" ").trim()}}function x(J){return typeof J==="string"&&p.includes(J)?J:void 0}function u(J){return typeof J==="string"&&l.includes(J)?J:void 0}function Zj(J){return J.trim().split(/\s+/,1)[0]?.toLowerCase()??""}function $j(J){return J.split(/&&|\|\||;|\n/).map((j)=>j.trim()).filter(Boolean)}function zj(J){let j=J[1];return["status","log","diff","show","branch","grep","ls-files","remote","rev-parse","describe"].includes(j)}function Wj(J){let j=J[1],X=J[2];if(["status","auth","repo","pr","issue","label","milestone"].includes(j)===!1)return!1;if(j==="repo")return[void 0,"view","list"].includes(X);if(j==="issue")return[void 0,"list","view","status"].includes(X);if(j==="pr")return[void 0,"list","view","status","diff","checks"].includes(X);if(j==="label"||j==="milestone")return[void 0,"list","view"].includes(X);return!0}function Yj(J){let j=J.trim();if(!j)return!0;if(/(^|[^<])>(>|&)?\s*\S/.test(j)||/\btee\b/.test(j))return!1;if(/\b(rm|mv|cp|mkdir|rmdir|touch|chmod|chown|sudo|kill|pkill|reboot|shutdown|curl\s+.*\|\s*(sh|bash)|wget\s+.*\|\s*(sh|bash))\b/.test(j))return!1;let z=["git add","git commit","git push","git checkout","git switch","git reset","git merge","git rebase","npm install","npm i","npm add","pnpm install","pnpm add","yarn add","yarn install","pip install","cargo install","cargo add","gh issue create","gh issue edit","gh issue close","gh pr create","gh pr edit"],F=j.toLowerCase();if(z.some((K)=>F.includes(K)))return!1;for(let K of $j(j)){let q=K.split(/\s+/),V=q[0];if(!V)continue;if(["cat","head","tail","less","more","grep","rg","find","fd","ls","pwd","tree","wc","sort","uniq","cut","awk","sed","date","whoami","uname","which","where","echo"].includes(V))continue;if(["npm","pnpm","yarn"].includes(V)){if(["list","outdated","view","info","why"].includes(q[1]))continue;return!1}if(V==="git"){if(zj(q))continue;return!1}if(V==="gh"){if(Wj(q))continue;return!1}return!1}return!0}function Hj(J){let j=C(U);function X(){j.updatedAt=Date.now(),J.appendEntry(v,C(j))}function z(V){if(!j.active){V.ui.setStatus("grill-me",void 0),V.ui.setWidget("grill-me",void 0);return}let Q=A(j),Y=Q==="output"?"\uD83D\uDD25 grill: output":Q==="output-selection"?"\uD83D\uDD25 grill: select output":"\uD83D\uDD25 grill";V.ui.setStatus("grill-me",V.ui.theme.fg(Q==="output"?"warning":Q==="output-selection"?"success":"accent",Y));let $=j.topic.length>90?`${j.topic.slice(0,87)}...`:j.topic,Z=[V.ui.theme.fg("accent",`\uD83D\uDD25 Grill Me: ${$||"active"}`),V.ui.theme.fg("muted",`intent=${j.intent} style=thorough research=${j.researchMode}`),V.ui.theme.fg("dim",`Phase: ${d(j)}`)];if(j.alternatives.length>0){Z.push(V.ui.theme.fg("accent","Tab: fill/cycle replies • Shift+Tab previous • Enter sends"));for(let W of j.alternatives){let R=W.description?` — ${W.description}`:"";Z.push(V.ui.theme.fg("muted",` • ${W.label}${R}`))}}V.ui.setWidget("grill-me",Z,{placement:"belowEditor"})}function F(V,Q,Y={},$="Grill Me"){j={...C(U),...Y,active:!0,topic:V,phase:"interview",outputPhase:!1,outputSelection:void 0,approvedOutputPlan:void 0},j.checkpoint=t(V,j),j.lastChangeSummary=`Started ${$} session`,X(),z(Q),J.sendUserMessage(`Start a ${$} session for this topic:
90
-
91
- ${V}
92
-
93
- Begin by updating the checkpoint if needed, seed or maintain the coverage checklist, clarity ledger, and decision branches, then call grill_set_alternatives with 2-5 concrete answer choices and ask the first focused Socratic question. Route repo/docs facts to read-only inspection when available, ask the user for judgments and tradeoffs, and run a light pressure pass over assumptions/risks before closing. Mention that Tab fills/cycles suggested replies and Enter sends the selected or edited reply. Use the single thorough grilling style. When the interview is ready to end, the mandatory hardcoded output-selection phase must be entered with grill_enter_output_selection_phase before producing outputs or stopping.`)}async function K(V){if(!V.hasUI){J.sendMessage({customType:"grill-me-checkpoint",content:j.checkpoint,display:!0});return}return await V.ui.custom((Q,Y,$,Z)=>{let W=new r((M)=>Y.fg("accent",M)),R=new a(j.checkpoint,1,0,s()),B=0,w=0,G=[],D=16;function L(M){if(w!==M||G.length===0)w=M,G=R.render(M);return G}function S(){return Math.max(0,G.length-D)}function _(M){B=Math.max(0,Math.min(S(),B+M)),Q.requestRender()}return{render(M){let I=L(M);B=Math.min(B,S());let o=I.slice(B,B+D),n=I.length>D?`lines ${B+1}-${Math.min(B+D,I.length)} of ${I.length}`:"full checkpoint";return[...W.render(M),P(Y.fg("accent",Y.bold("\uD83D\uDD25 Grill Me Checkpoint")),M),P(Y.fg("dim",`${n} • ↑↓/PgUp/PgDn scroll • e edit • Enter/Esc close`),M),...o.map((i)=>P(i,M,"")),...W.render(M)]},invalidate(){W.invalidate(),R.invalidate(),w=0,G=[]},handleInput(M){if(N(M,"escape")||N(M,"enter"))Z(void 0);else if(N(M,"e"))Z("edit");else if(N(M,"up"))_(-1);else if(N(M,"down"))_(1);else if(N(M,"pageUp"))_(-D);else if(N(M,"pageDown"))_(D)}}},{overlay:!0,overlayOptions:{anchor:"center",width:"80%",minWidth:50,maxHeight:"80%",margin:2}})}async function q(V,Q){if(!j.checkpoint.trim()){V.ui.notify("No grill checkpoint yet.","warning");return}let Y=Q?.trim().toLowerCase()||"overlay";if(Y.includes("edit")){let Z=await V.ui.editor("Edit Grill Me checkpoint",j.checkpoint);if(Z!==void 0)j.checkpoint=Z.trim()||j.checkpoint,j.lastChangeSummary="Checkpoint edited by user",X(),z(V),V.ui.notify("Grill checkpoint updated.","info");return}if(Y.includes("chat")){J.sendMessage({customType:"grill-me-checkpoint",content:j.checkpoint,display:!0});return}if(await K(V)==="edit")await q(V,"edit")}J.registerCommand("checkpoint",{description:"Show the current Grill Me checkpoint in an overlay",handler:async(V,Q)=>{await q(Q,V.trim())}}),J.registerCommand("grill",{description:"Start or control a Socratic Grill Me planning session",handler:async(V,Q)=>{let Y=V.trim(),$=Zj(Y),Z=Y.slice($.length).trim();if($==="help"){J.sendMessage({customType:"grill-me-help",content:`# Grill Me commands
94
-
95
- - /grill <topic>
96
- - /grill stop
97
- - /checkpoint [edit|chat]
98
- - /grill checkpoint [edit|chat]
99
- - /grill status
100
- - /grill intent auto|plan|learn|research|content|decide
101
- - /grill output <one or more outputs> (preference only; approval still required)
102
- - /grill research off|ask|auto
103
-
104
- Grill Me uses one thorough default Socratic style. The assistant must use the hardcoded output-selection phase before ending the interview, producing outputs, or stopping without outputs.`,display:!0});return}if($==="stop"){j.active=!1,j.phase="interview",j.outputPhase=!1,j.outputSelection=void 0,j.approvedOutputPlan=void 0,j.currentQuestion=void 0,j.alternatives=[],j.lastChangeSummary="Stopped grill session",X(),z(Q),Q.ui.notify("Grill mode stopped.","info");return}if($==="status"){J.sendMessage({customType:"grill-me-status",content:jj(j),display:!0});return}if($==="checkpoint"){await q(Q,Z);return}if($==="intent"){let D=x(Z);if(!D){Q.ui.notify(`Usage: /grill intent ${p.join("|")}`,"warning");return}j.intent=D,j.lastChangeSummary=`Intent set to ${D}`,X(),z(Q),Q.ui.notify(`Grill intent: ${D}`,"info");return}if($==="output"){if(!Z){Q.ui.notify("Usage: /grill output <one or more outputs, e.g. design-doc,issues>","warning");return}j.outputPreference=Z,j.lastChangeSummary=`Output preference set to ${Z}`,X(),z(Q),Q.ui.notify(`Grill output preference: ${Z}. This is not approval; Grill Me will still ask/confirm before producing outputs.`,"info");return}if($==="research"){let D=u(Z);if(!D){Q.ui.notify(`Usage: /grill research ${l.join("|")}`,"warning");return}j.researchMode=D,j.lastChangeSummary=`Research mode set to ${D}`,X(),z(Q),Q.ui.notify(`Grill research mode: ${D}`,"info");return}let W=Xj(Y),R={},B=x(W.flags.intent),w=u(W.flags.research);if(B)R.intent=B;if(w)R.researchMode=w;if(typeof W.flags.output==="string")R.outputPreference=W.flags.output;let G=W.rest;if(!G){let D=h(Q);if(!Q.hasUI)G=D||"Current conversation";else{let L=await Q.ui.editor("What should I grill you about?",D||"");if(!L?.trim()){Q.ui.notify("Cancelled grill start.","info");return}G=L.trim()}}F(G,Q,R)}}),J.registerCommand("deep-interview",{description:"Start a DM Deep Interview session",handler:async(V,Q)=>{let Y=V.trim()||h(Q)||"Current conversation";F(Y,Q,{researchMode:"auto"},"DM Deep Interview")}}),J.registerTool({name:"grill_update_checkpoint",label:"Update Grill Checkpoint",description:"Replace the Grill Me shared-understanding checkpoint. Use before asking the next grill question whenever meaningful understanding changes.",promptSnippet:"Persist the evolving Grill Me shared-understanding Markdown checkpoint",promptGuidelines:["Use grill_update_checkpoint before asking the next question whenever an active Grill Me session reaches a meaningful new decision, clarification, assumption, risk, or open question.","Maintain a clarity ledger for intent, outcome, scope, constraints, evidence, success criteria, decisions, and risks; use it to choose the next high-leverage question.","Route missing repo/docs facts to read-only inspection when available, and ask the user for judgments, priorities, tradeoffs, risk tolerance, taste, and decision boundaries.","Before closing the interview, run a light pressure pass over assumptions, counterexamples, failure modes, and unresolved branches."],parameters:H.Object({markdown:H.String({description:"The full replacement Markdown checkpoint."}),changeSummary:H.String({description:"Brief visible summary of what changed."})}),async execute(V,Q){if(!j.active)return{content:[{type:"text",text:"No active Grill Me session. Start one with /grill <topic>."}],details:{checkpoint:j.checkpoint,changeSummary:"No active session",updatedAt:j.updatedAt}};return j.checkpoint=Q.markdown,j.lastChangeSummary=Q.changeSummary,X(),{content:[{type:"text",text:`Recorded checkpoint update: ${Q.changeSummary}`}],details:{checkpoint:j.checkpoint,changeSummary:Q.changeSummary,updatedAt:j.updatedAt}}},renderCall(V,Q){return new k(Q.fg("toolTitle",Q.bold("grill_update_checkpoint "))+Q.fg("muted",V.changeSummary??""),0,0)},renderResult(V,Q,Y){let $=V.details?.changeSummary,Z=$?`✓ ${$}`:V.content[0]?.type==="text"?V.content[0].text:"Checkpoint updated";return new k(Y.fg("success",Z),0,0)}}),J.registerTool({name:"grill_set_alternatives",label:"Set Grill Alternatives",description:"Set the visible Grill Me answer alternatives offered to the user via Tab autocomplete for the next question or readiness choice.",promptSnippet:"Present answer alternatives through the Grill Me Tab autocomplete UX",promptGuidelines:["Before asking each grill question, call grill_set_alternatives with 2-5 concise, concrete alternatives the user can accept or edit with Tab autocomplete.","Include one recommended alternative and make it clear in the label or description.","Use alternatives that are useful defaults, not exhaustive menus; the user can still type a custom answer."],parameters:H.Object({question:H.String({description:"The question these alternatives answer."}),alternatives:H.Array(H.Object({value:H.String({description:"The exact reply inserted into the user's editor when selected."}),label:H.String({description:"Short visible label for the alternative."}),description:H.Optional(H.String({description:"Brief explanation or recommendation note."}))}),{description:"2-5 suggested replies. Include a recommended/default option."})}),async execute(V,Q,Y,$,Z){if(!j.active)return{content:[{type:"text",text:"No active Grill Me session. Start one with /grill <topic>."}],details:{alternatives:[]}};if(j.currentQuestion=Q.question,j.alternatives=T(Q.alternatives),j.lastChangeSummary=`Set ${j.alternatives.length} Tab alternatives`,X(),Z)z(Z);return{content:[{type:"text",text:`Tab alternatives updated: ${j.alternatives.map((W)=>W.label).join(", ")}`}],details:{question:j.currentQuestion,alternatives:j.alternatives}}},renderCall(V,Q){return new k(Q.fg("toolTitle",Q.bold("grill_set_alternatives "))+Q.fg("muted",V.question??""),0,0)},renderResult(V,Q,Y){let $=V.details?.alternatives??[],Z=$.length?`✓ Tab alternatives: ${$.map((W)=>W.label).join(" | ")}`:"No alternatives set";return new k(Y.fg($.length?"success":"warning",Z),0,0)}}),J.registerTool({name:"grill_enter_output_selection_phase",label:"Enter Grill Output Selection",description:`Enter the mandatory hardcoded output-selection phase at the end of the Grill Me interview before stopping or producing outputs. The phase must explicitly mention available output destinations: ${f()}.`,promptSnippet:"Start the mandatory Grill Me output-selection phase",promptGuidelines:["Use grill_enter_output_selection_phase after the final checkpoint update when the Grill Me interview is ready to end.","Do not stop a Grill Me interview, claim the work is complete, or enter output production until grill_enter_output_selection_phase has been called and the user has selected what happens next.",`In the output-selection chat response, explicitly list these output destination options before asking for a choice: ${f()}.`],parameters:H.Object({readinessRationale:H.String({description:"Why shared understanding is sufficient to leave interview mode."}),recommendedOutputs:H.String({description:`One or more recommended output destinations/formats from the explicit catalog (${f()}), or 'none' if no artifact is recommended.`}),recommendedStrategy:H.String({description:"Recommended output strategy, distinct from destination/format."}),question:H.String({description:"The explicit output-selection question to ask the user. It should name concrete output options rather than saying only 'outputs'."}),alternatives:H.Array(H.Object({value:H.String({description:"The exact reply inserted into the user's editor when selected."}),label:H.String({description:"Short visible label for the alternative."}),description:H.Optional(H.String({description:"Brief explanation or recommendation note."}))}),{description:"2-5 choices covering produce output(s), continue grilling, review checkpoint, or stop with no output as appropriate."})}),async execute(V,Q,Y,$,Z){if(!j.active)return{content:[{type:"text",text:"No active Grill Me session. Start one with /grill <topic>."}],details:{phase:A(j)}};if(j.phase="output-selection",j.outputPhase=!1,j.outputSelection={readinessRationale:Q.readinessRationale,recommendedOutputs:Q.recommendedOutputs,recommendedStrategy:Q.recommendedStrategy,question:Q.question},j.approvedOutputPlan=void 0,j.currentQuestion=Q.question,j.alternatives=T(Q.alternatives),j.lastChangeSummary="Entered mandatory output-selection phase",X(),Z)z(Z);return{content:[{type:"text",text:`Output-selection phase is active. In the chat response, explicitly show these output destination options:
105
- ${O()}
106
-
107
- Then ask the user to choose one or more, customize the set, continue grilling, review the checkpoint, or stop without output. Alternatives: ${j.alternatives.map((W)=>W.label).join(", ")}`}],details:{phase:A(j),outputSelection:j.outputSelection,outputDestinationOptions:g,alternatives:j.alternatives}}},renderCall(V,Q){return new k(Q.fg("toolTitle",Q.bold("grill_enter_output_selection_phase "))+Q.fg("muted",V.question??""),0,0)},renderResult(V,Q,Y){let $=V.details?.outputSelection,Z=$?`✓ Output selection: ${$.recommendedOutputs}`:V.content[0]?.type==="text"?V.content[0].text:"Output selection phase updated";return new k(Y.fg($?"success":"warning",Z),0,0)}}),J.registerTool({name:"grill_finish_output_selection_phase",label:"Finish Grill Output Selection",description:"Resolve the mandatory output-selection phase without entering output production, either by continuing the interview or stopping with no outputs.",promptSnippet:"Resolve Grill Me output selection without output production",promptGuidelines:["Use grill_finish_output_selection_phase when the user responds to the mandatory output-selection phase by choosing to continue grilling or stop without producing outputs.","If the user approves concrete output production, use grill_enter_output_phase instead."],parameters:H.Object({outcome:H.String({description:"Either 'continue-grilling' or 'stop-without-output'."}),summary:H.Optional(H.String({description:"Brief summary of the user's output-selection decision."}))}),async execute(V,Q,Y,$,Z){if(!j.active)return{content:[{type:"text",text:"No active Grill Me session."}],details:{phase:A(j),active:j.active}};if(A(j)!=="output-selection")return{content:[{type:"text",text:"No active output-selection phase. Call grill_enter_output_selection_phase before resolving output selection."}],details:{phase:A(j),active:j.active}};let W=String(Q.outcome??"").trim().toLowerCase();if(W==="continue-grilling"||W==="continue"||W==="grill"){if(j.phase="interview",j.outputPhase=!1,j.outputSelection=void 0,j.currentQuestion=void 0,j.alternatives=[],j.lastChangeSummary=Q.summary?`Output selection resolved: ${Q.summary}`:"Output selection resolved: continue grilling",X(),Z)z(Z);return{content:[{type:"text",text:"Returned to Grill Me interview mode. Ask the next Socratic question with grill_set_alternatives."}],details:{phase:A(j),active:j.active,outcome:W}}}if(W==="stop-without-output"||W==="stop"||W==="no-output"||W==="none"){if(j.active=!1,j.phase="interview",j.outputPhase=!1,j.outputSelection=void 0,j.approvedOutputPlan=void 0,j.currentQuestion=void 0,j.alternatives=[],j.lastChangeSummary=Q.summary?`Stopped after output selection: ${Q.summary}`:"Stopped after output selection without outputs",X(),Z)z(Z);return{content:[{type:"text",text:j.lastChangeSummary}],details:{phase:A(j),active:j.active,outcome:W}}}return{content:[{type:"text",text:"Unsupported outcome. Use 'continue-grilling' or 'stop-without-output'. If outputs were approved, call grill_enter_output_phase instead."}],details:{phase:A(j),active:j.active,outcome:W}}}}),J.registerTool({name:"grill_enter_output_phase",label:"Enter Grill Output Phase",description:"Mark that the user approved output production after mandatory output selection, allowing the assistant to use tools required to create the approved artifacts.",promptSnippet:"Enter approved Grill Me output-production phase",promptGuidelines:["Use grill_enter_output_phase only after grill_enter_output_selection_phase has run and the user explicitly approves a concrete output plan or preview during an active Grill Me session.","During output phase, do not refuse approved mutating output work merely because it mutates state, such as creating GitHub issues. If a tool, CLI, platform, or DM permission/authentication gate blocks the mutation, stop and ask the user for the needed permission, confirmation, credentials, or plan change; do not bypass it or broaden scope.",b],parameters:H.Object({outputPlan:H.String({description:"The approved output plan, including one or more outputs/artifacts/files/issues and intended tool use."})}),async execute(V,Q,Y,$,Z){if(!j.active)return{content:[{type:"text",text:"No active Grill Me session."}],details:{phase:A(j),outputPhase:!1,outputPlan:Q.outputPlan}};if(A(j)!=="output-selection"&&!j.outputPhase)return{content:[{type:"text",text:"Output production requires the mandatory output-selection phase first. Call grill_enter_output_selection_phase, ask the user to choose/approve outputs, then call grill_enter_output_phase."}],details:{phase:A(j),outputPhase:!1,outputPlan:Q.outputPlan}};if(j.phase="output",j.outputPhase=!0,j.approvedOutputPlan=Q.outputPlan,j.lastChangeSummary="Entered approved output phase",X(),Z)z(Z);return{content:[{type:"text",text:`Output phase enabled for approved plan:
108
- ${Q.outputPlan}
109
-
110
- ${b}`}],details:{phase:A(j),outputPhase:!0,outputPlan:Q.outputPlan}}}}),J.registerTool({name:"grill_finish_output_phase",label:"Finish Grill Output Phase",description:"Return an active Grill Me session to read-only interview/planning enforcement after output production.",promptSnippet:"Return Grill Me to read-only interview mode after output production",parameters:H.Object({summary:H.Optional(H.String({description:"Brief summary of outputs created."}))}),async execute(V,Q,Y,$,Z){if(j.phase="interview",j.outputPhase=!1,j.outputSelection=void 0,j.approvedOutputPlan=void 0,j.lastChangeSummary=Q.summary?`Finished output phase: ${Q.summary}`:"Finished output phase",X(),Z)z(Z);return{content:[{type:"text",text:j.lastChangeSummary}],details:{phase:A(j),outputPhase:!1,summary:Q.summary}}}}),J.on("tool_call",async(V)=>{if(!j.active||j.outputPhase)return;if(V.toolName==="edit"||V.toolName==="write")return{block:!0,reason:"Grill Me is read-only until the mandatory output-selection phase runs and the user approves a concrete output plan. Call grill_enter_output_selection_phase first, then grill_enter_output_phase before writing artifacts."};if(V.toolName==="bash"){let Q=String(V.input?.command??"");if(!Yj(Q))return{block:!0,reason:`Grill Me read-only mode blocked a potentially mutating command. Run the mandatory output-selection phase with grill_enter_output_selection_phase, get output approval, then call grill_enter_output_phase first.
111
- Command: ${Q}`}}}),J.on("before_agent_start",async(V)=>{if(!j.active)return;let Q=["Use one thorough default style throughout the interview.","Be relentlessly curious but collaborative: challenge vague answers, surface contradictions, and test assumptions without changing persona.","Ask enough follow-up questions to resolve the decision tree. Prefer one more high-value question over premature readiness.","Maintain a coverage checklist and decision-branch ledger in the checkpoint; mark branches resolved, deferred, or still open as understanding evolves.","Walk dependent branches one at a time. If an answer changes upstream assumptions, revisit affected downstream decisions before moving on.","Do not enter output selection until major objective, scope, constraints, dependencies, risks, validation, and output branches are resolved or explicitly deferred."].join(`
112
- - `),Y={off:"Do not proactively inspect files or research. Ask the user instead unless they explicitly provide context.",ask:"If a question could be answered by inspecting files/code/research, ask permission before doing so.",auto:"For coding/project contexts, if a question can be answered by inspecting available files/code, inspect instead of asking. Use read-only tools during interview mode."},$=A(j),Z=$==="output"?`Approved output phase: produce only the approved outputs. If a required mutation is blocked by permissions, auth, or repo setup, ask the user instead of bypassing or faking success. ${b} When done, call grill_finish_output_phase.`:$==="output-selection"?"You are in the mandatory output-selection phase. Do not ask new interview questions unless the user chooses to continue grilling. Ask the user to choose outputs/continue/review/stop from the active output-selection alternatives. If they approve concrete output production, call grill_enter_output_phase. If they choose to continue or stop without output, call grill_finish_output_selection_phase.":"You are in read-only interview mode. Do not implement, write files, create issues, install packages, run mutating commands, or stop the Grill Me work. When ready to end the interview, first update the checkpoint if needed, then call grill_enter_output_selection_phase to enter the mandatory hardcoded output-selection phase before output production or stopping.",W=j.outputSelection?`
113
-
114
- Active output selection:
115
- - Rationale: ${j.outputSelection.readinessRationale}
116
- - Recommended outputs: ${j.outputSelection.recommendedOutputs}
117
- - Recommended strategy: ${j.outputSelection.recommendedStrategy}
118
- - Question: ${j.outputSelection.question}`:"",R=`
119
-
120
- [GRILL ME EXTENSION ACTIVE]
121
- Topic:
122
- ${j.topic}
123
-
124
- Configuration:
125
- - Intent preset: ${j.intent}
126
- - Grilling style: thorough default
127
- - Research mode: ${j.researchMode}
128
- - Output preference: ${E(j)}
129
- - Phase: ${$}
130
- - Output phase: ${j.outputPhase?"yes":"no"}${W}
131
-
132
- Current checkpoint:
133
- ${j.checkpoint||"(No checkpoint yet.)"}
134
-
135
- Current Tab alternatives:
136
- ${j.alternatives.length?j.alternatives.map((B)=>`- ${B.label}: ${B.value}${B.description?` (${B.description})`:""}`).join(`
137
- `):"(None set.)"}
138
-
139
- Behavior:
140
- - Apply the Socratic method to reach shared understanding of the topic.
141
- - Avoid hardcoded interview phases. Adapt the dimensions you explore to the subject and to the user's expertise.
142
- - The output-selection phase is the one hardcoded terminal phase: it is mandatory before stopping the Grill Me work, stopping without outputs, or producing outputs.
143
- - Treat desired outcome mode as important: learning, building, researching, content/tutorial creation, decision review, etc.
144
- - Do not set or assume a default output mode for the session. A missing output preference means no output has been chosen yet, not design-doc or any other default.
145
- - Treat /grill output as a preference only, not production approval. Always explicitly ask/confirm which output(s) to produce before output production.
146
- - Support 1..n outputs in one approved output plan; for example, a design doc AND uploaded GitHub issues.
147
- - The output-selection phase must explicitly mention concrete output destinations by name. Use this catalog and allow custom combinations:
148
- ${O()}
149
- - Ask mostly one focused question at a time. Small grouped questions are allowed only when inseparable.
150
- - Ask more than the minimum needed for a shallow summary: keep drilling until the meaningful dependency branches are resolved, contradicted, or intentionally deferred.
151
- - Every grill question must present 2-5 concrete answer alternatives. Before asking the question, call grill_set_alternatives so the user can fill/cycle those alternatives with Tab and send the selected or edited reply with Enter. Also show the same alternatives briefly in chat.
152
- - Include your recommended answer by default with each grill question and mark it as recommended.
153
- - ${Q}
154
- - ${Y[j.researchMode]}
155
- - ${Z}
156
-
157
- Checkpoint rule:
158
- - The checkpoint is the source of durable shared understanding.
159
- - Whenever the user's answer meaningfully changes shared understanding, call grill_update_checkpoint with a full replacement Markdown checkpoint and a concise changeSummary BEFORE asking the next grill question.
160
- - The checkpoint should be adaptive Markdown. Add/remove sections as appropriate for the topic.
161
- - Keep a coverage checklist and decision-branch ledger in the checkpoint when useful; update branch status as resolved, open, contradicted, or intentionally deferred.
162
- - If there is no meaningful change, you may ask the next question without updating.
163
-
164
- Readiness/output rule:
165
- - When you think shared understanding is good enough, do not merely present a prompt-only readiness gate. First verify that the major coverage branches are resolved or explicitly deferred, then call grill_enter_output_selection_phase with the rationale, recommended output destination(s), recommended strategy, explicit output-selection question, and 2-5 alternatives.
166
- - The mandatory output-selection phase must explicitly ask the user which output(s) to produce, even if you have a recommendation or /grill output preference. In the chat response, name the concrete options from the catalog above, including GitHub issues, design doc, README.md, ADR, PRD, implementation plan, research brief, summary/decision memo, tutorial/content outline, test plan/QA checklist, and changelog/release notes.
167
- - Offer useful single-output and multi-output alternatives where appropriate, and make clear the user can choose 1..n outputs or customize the list.
168
- - Output-selection alternatives should include continue grilling and/or review checkpoint when useful, and stop-without-output when producing no artifact is a reasonable choice.
169
- - Output destination and strategy are separate. For example, GitHub issues can be implementation slices, tutorial chapters, research investigations, content installments, or prototype experiments.
170
- - For file outputs, draft before writing. For GitHub issues, preview titles/bodies/labels before creating. For multiple outputs, preview the full set and dependencies/order before creation.
171
- - Mutating output actions require explicit user approval of the concrete output set/plan, an active output-selection phase, and grill_enter_output_phase first.
172
- - During approved output phase, perform only approved mutations, and do not refuse approved mutating output actions merely because they mutate state. If a permission/authentication/tool/repo setup gate blocks an approved mutation (for example gh issue create), ask the user for permission, confirmation, credentials, or a revised plan instead of bypassing or faking success. ${b}
173
- - If the user chooses to continue grilling or stop without output during output selection, call grill_finish_output_selection_phase with that outcome.
174
- [/GRILL ME EXTENSION ACTIVE]`;return{systemPrompt:V.systemPrompt+R}}),J.on("session_start",async(V,Q)=>{Q.ui.setEditorComponent(($,Z,W)=>new m($,Z,W,()=>j)),Q.ui.addAutocompleteProvider(($)=>Qj($,()=>j)),j=C(U);let Y=Q.sessionManager.getBranch();for(let $ of Y)if($?.type==="custom"&&$.customType===v&&$.data){if(j={...C(U),...$.data},j.outputPreference===e)j.outputPreference="";if(!j.phase)j.phase=j.outputPhase?"output":"interview";if(j.phase!=="output")j.outputPhase=!1}z(Q)})}export{Hj as default};
@@ -1,23 +0,0 @@
1
- {
2
- "name": "dm-grill-me",
3
- "version": "0.3.0",
4
- "description": "Socratic planning and shared-understanding sessions for DM",
5
- "type": "module",
6
- "license": "MIT",
7
- "keywords": [
8
- "duckmind",
9
- "dm",
10
- "dm-package",
11
- "dm-extension",
12
- "grill",
13
- "deep-interview",
14
- "interview",
15
- "planning",
16
- "socratic"
17
- ],
18
- "dm": {
19
- "extensions": [
20
- "./index.js"
21
- ]
22
- }
23
- }
@@ -1,20 +0,0 @@
1
- {
2
- "name": "dm-tasks",
3
- "version": "0.5.0",
4
- "description": "DM extension for Claude Code-style task tracking and coordination",
5
- "author": "tintinweb",
6
- "license": "MIT",
7
- "type": "module",
8
- "keywords": [
9
- "duckmind",
10
- "dm",
11
- "tasks",
12
- "todo",
13
- "coordination"
14
- ],
15
- "dm": {
16
- "extensions": [
17
- "./src/index.js"
18
- ]
19
- }
20
- }
@@ -1 +0,0 @@
1
- class z{getStore;getMode;clearDelayTurns;completedAtTurn=new Map;allCompletedAtTurn=null;constructor(h,f,b=4){this.getStore=h;this.getMode=f;this.clearDelayTurns=b}trackCompletion(h,f){let b=this.getMode();if(b==="never")return;if(b==="on_task_complete")this.completedAtTurn.set(h,f);else if(b==="on_list_complete")this.checkAllCompleted(f)}checkAllCompleted(h){let f=this.getStore().list();if(f.length>0&&f.every((b)=>b.status==="completed")){if(this.allCompletedAtTurn===null)this.allCompletedAtTurn=h}else this.allCompletedAtTurn=null}resetBatchCountdown(){this.allCompletedAtTurn=null}reset(){this.completedAtTurn.clear(),this.allCompletedAtTurn=null}onTurnStart(h){let f=this.getMode(),b=!1;if(f==="on_task_complete")for(let[j,x]of this.completedAtTurn){let q=this.getStore().get(j);if(!q||q.status!=="completed")this.completedAtTurn.delete(j);else if(h-x>=this.clearDelayTurns)this.getStore().delete(j),this.completedAtTurn.delete(j),b=!0}else if(f==="on_list_complete"&&this.allCompletedAtTurn!==null){if(h-this.allCompletedAtTurn>=this.clearDelayTurns)this.getStore().clearCompleted(),this.allCompletedAtTurn=null,b=!0}return b}}export{z as AutoClearManager};
@@ -1,212 +0,0 @@
1
- import{join as D0,resolve as m0}from"node:path";import{Type as D}from"typebox";class s{getStore;getMode;clearDelayTurns;completedAtTurn=new Map;allCompletedAtTurn=null;constructor(J,H,Q=4){this.getStore=J,this.getMode=H,this.clearDelayTurns=Q}trackCompletion(J,H){let Q=this.getMode();if(Q==="never")return;if(Q==="on_task_complete")this.completedAtTurn.set(J,H);else if(Q==="on_list_complete")this.checkAllCompleted(H)}checkAllCompleted(J){let H=this.getStore().list();if(H.length>0&&H.every((Q)=>Q.status==="completed")){if(this.allCompletedAtTurn===null)this.allCompletedAtTurn=J}else this.allCompletedAtTurn=null}resetBatchCountdown(){this.allCompletedAtTurn=null}reset(){this.completedAtTurn.clear(),this.allCompletedAtTurn=null}onTurnStart(J){let H=this.getMode(),Q=!1;if(H==="on_task_complete")for(let[$,V]of this.completedAtTurn){let z=this.getStore().get($);if(!z||z.status!=="completed")this.completedAtTurn.delete($);else if(J-V>=this.clearDelayTurns)this.getStore().delete($),this.completedAtTurn.delete($),Q=!0}else if(H==="on_list_complete"&&this.allCompletedAtTurn!==null){if(J-this.allCompletedAtTurn>=this.clearDelayTurns)this.getStore().clearCompleted(),this.allCompletedAtTurn=null,Q=!0}return Q}}class t{processes=new Map;track(J,H,Q){let $={taskId:J,pid:H.pid,command:Q,output:[],status:"running",startedAt:Date.now(),proc:H,abortController:new AbortController,waiters:[]};H.stdout?.on("data",(V)=>{$.output.push(V.toString())}),H.stderr?.on("data",(V)=>{$.output.push(V.toString())}),H.on("close",(V,z)=>{if($.status==="running")$.status=V===0?"completed":"error";$.exitCode=V??void 0,$.completedAt=Date.now();for(let B of $.waiters)B();$.waiters=[]}),H.on("error",(V)=>{if($.status==="running"){$.status="error",$.output.push(`Process error: ${V.message}`),$.completedAt=Date.now();for(let z of $.waiters)z();$.waiters=[]}}),this.processes.set(J,$)}getOutput(J){let H=this.processes.get(J);if(!H)return;return{output:H.output.join(""),status:H.status,exitCode:H.exitCode,startedAt:H.startedAt,completedAt:H.completedAt,command:H.command}}waitForCompletion(J,H,Q){let $=this.processes.get(J);if(!$)return Promise.resolve(void 0);if($.status!=="running")return Promise.resolve(this.getOutput(J));return new Promise((V)=>{let z=!1,B=setTimeout(A,H);function A(){if(z)return;z=!0,clearTimeout(B),V(P.getOutput(J))}let P=this;$.waiters.push(A),Q?.addEventListener("abort",A,{once:!0})})}async stop(J){let H=this.processes.get(J);if(!H||H.status!=="running")return!1;H.status="stopped",H.proc.kill("SIGTERM"),await new Promise((Q)=>{let $=setTimeout(()=>{try{H.proc.kill("SIGKILL")}catch{}Q()},5000);H.proc.on("close",()=>{clearTimeout($),Q()})}),H.completedAt=Date.now();for(let Q of H.waiters)Q();return H.waiters=[],!0}getProcess(J){return this.processes.get(J)}}import{existsSync as A0,mkdirSync as N0,readFileSync as Z0,renameSync as U0,unlinkSync as e,writeFileSync as $0}from"node:fs";import{homedir as V0}from"node:os";import{dirname as j0,isAbsolute as L0,join as q0}from"node:path";var _0=q0(V0(),".dm","tasks"),B0=50,E0=100;function F0(J){for(let H=0;H<E0;H++)try{$0(J,`${process.pid}`,{flag:"wx"});return}catch(Q){if(Q.code==="EEXIST"){try{let V=parseInt(Z0(J,"utf-8"),10);if(V&&!C0(V)){e(J);continue}}catch{}let $=Date.now();while(Date.now()-$<B0);continue}throw Q}throw Error(`Failed to acquire lock: ${J}`)}function M0(J){try{e(J)}catch{}}function C0(J){try{return process.kill(J,0),!0}catch{return!1}}class i{filePath;lockPath;nextId=1;tasks=new Map;constructor(J){if(!J)return;let Q=L0(J)?J:q0(_0,`${J}.json`);N0(j0(Q),{recursive:!0}),this.filePath=Q,this.lockPath=Q+".lock",this.load()}load(){if(!this.filePath)return;if(!A0(this.filePath))return;try{let J=JSON.parse(Z0(this.filePath,"utf-8"));this.nextId=J.nextId,this.tasks.clear();for(let H of J.tasks)this.tasks.set(H.id,H)}catch{}}save(){if(!this.filePath)return;let J={nextId:this.nextId,tasks:Array.from(this.tasks.values())},H=this.filePath+".tmp";$0(H,JSON.stringify(J,null,2)),U0(H,this.filePath)}withLock(J){if(!this.lockPath)return J();F0(this.lockPath);try{this.load();let H=J();return this.save(),H}finally{M0(this.lockPath)}}create(J,H,Q,$){return this.withLock(()=>{let V=Date.now(),z={id:String(this.nextId++),subject:J,description:H,status:"pending",activeForm:Q,owner:void 0,metadata:$??{},blocks:[],blockedBy:[],createdAt:V,updatedAt:V};return this.tasks.set(z.id,z),z})}get(J){if(this.filePath)this.load();return this.tasks.get(J)}list(){if(this.filePath)this.load();return Array.from(this.tasks.values()).sort((J,H)=>Number(J.id)-Number(H.id))}update(J,H){return this.withLock(()=>{let Q=this.tasks.get(J);if(!Q)return{task:void 0,changedFields:[],warnings:[]};let $=[],V=[];if(H.status==="deleted"){this.tasks.delete(J);for(let z of this.tasks.values())z.blocks=z.blocks.filter((B)=>B!==J),z.blockedBy=z.blockedBy.filter((B)=>B!==J);return{task:void 0,changedFields:["deleted"],warnings:[]}}if(H.status!==void 0)Q.status=H.status,$.push("status");if(H.subject!==void 0)Q.subject=H.subject,$.push("subject");if(H.description!==void 0)Q.description=H.description,$.push("description");if(H.activeForm!==void 0)Q.activeForm=H.activeForm,$.push("activeForm");if(H.owner!==void 0)Q.owner=H.owner,$.push("owner");if(H.metadata!==void 0){for(let[z,B]of Object.entries(H.metadata))if(B===null)delete Q.metadata[z];else Q.metadata[z]=B;$.push("metadata")}if(H.addBlocks&&H.addBlocks.length>0){for(let z of H.addBlocks){if(!Q.blocks.includes(z))Q.blocks.push(z);let B=this.tasks.get(z);if(B&&!B.blockedBy.includes(J))B.blockedBy.push(J),B.updatedAt=Date.now();if(z===J)V.push(`#${J} blocks itself`);else if(!B)V.push(`#${z} does not exist`);else if(B.blocks.includes(J))V.push(`cycle: #${J} and #${z} block each other`)}$.push("blocks")}if(H.addBlockedBy&&H.addBlockedBy.length>0){for(let z of H.addBlockedBy){if(!Q.blockedBy.includes(z))Q.blockedBy.push(z);let B=this.tasks.get(z);if(B&&!B.blocks.includes(J))B.blocks.push(J),B.updatedAt=Date.now();if(z===J)V.push(`#${J} blocks itself`);else if(!B)V.push(`#${z} does not exist`);else if(Q.blocks.includes(z))V.push(`cycle: #${J} and #${z} block each other`)}$.push("blockedBy")}return Q.updatedAt=Date.now(),{task:Q,changedFields:$,warnings:V}})}delete(J){return this.withLock(()=>{if(!this.tasks.has(J))return!1;this.tasks.delete(J);for(let H of this.tasks.values())H.blocks=H.blocks.filter((Q)=>Q!==J),H.blockedBy=H.blockedBy.filter((Q)=>Q!==J);return!0})}clearAll(){return this.withLock(()=>{let J=this.tasks.size;return this.tasks.clear(),J})}deleteFileIfEmpty(){if(!this.filePath||this.tasks.size>0)return!1;try{e(this.filePath)}catch{}return!0}clearCompleted(){return this.withLock(()=>{let J=0;for(let[H,Q]of this.tasks)if(Q.status==="completed")this.tasks.delete(H),J++;if(J>0){let H=new Set(this.tasks.keys());for(let Q of this.tasks.values())Q.blocks=Q.blocks.filter(($)=>H.has($)),Q.blockedBy=Q.blockedBy.filter(($)=>H.has($))}return J})}}import{mkdirSync as O0,readFileSync as R0,writeFileSync as P0}from"node:fs";import{dirname as w0,join as v0}from"node:path";var J0=v0(process.cwd(),".dm","tasks-config.json");function G0(){try{return JSON.parse(R0(J0,"utf-8"))}catch{return{}}}function d(J){O0(w0(J0),{recursive:!0}),P0(J0,JSON.stringify(J,null,2))}import{getSettingsListTheme as S0}from"@duckmind/dm-coding-agent";import{Container as y0,SettingsList as x0,Spacer as f0,Text as h0}from"@duckmind/dm-tui";async function W0(J,H,Q,$){return await J.custom((V,z,B,A)=>{let P=[{id:"taskScope",label:"Task storage",description:"memory: tasks live only in memory, lost when session ends. session: persisted per session (tasks-<sessionId>.json), survives resume. project: shared across all sessions (tasks.json). Takes effect on next session start.",currentValue:H.taskScope??"session",values:["memory","session","project"]},{id:"autoCascade",label:"Auto-execute with agents",description:"When ON: pending agent tasks start automatically once their dependencies complete. When OFF: use TaskExecute to launch them manually.",currentValue:H.autoCascade??!1?"on":"off",values:["on","off"]},{id:"autoClearCompleted",label:"Auto-clear completed tasks",description:`never: completed tasks stay visible until manually cleared. on_list_complete: cleared automatically after all tasks are done. on_task_complete: each task cleared shortly after it completes. Clearing lags ~${$} turns.`,currentValue:H.autoClearCompleted??"on_list_complete",values:["never","on_list_complete","on_task_complete"]}],x=new x0(P,10,S0(),(f,M)=>{if(f==="autoCascade")H.autoCascade=M==="on",d(H);if(f==="taskScope")H.taskScope=M,d(H);if(f==="autoClearCompleted")H.autoClearCompleted=M,d(H)},()=>A(void 0));class S extends y0{handleInput(f){x.handleInput(f)}}let y=new S;return y.addChild(new h0(z.bold(z.fg("accent","⚙ Task Settings")),0,0)),y.addChild(new f0(1)),y.addChild(x),y}),Q()}import{truncateToWidth as b0}from"@duckmind/dm-tui";var X0=["✳","✴","✵","✶","✷","✸","✹","✺","✻","✼","✽"],H0=10;function T0(J){let H=Math.floor(J/1000);if(H<60)return`${H}s`;let Q=Math.floor(H/60),$=H%60;if(Q<60)return $>0?`${Q}m ${$}s`:`${Q}m`;let V=Math.floor(Q/60),z=Q%60;return z>0?`${V}h ${z}m`:`${V}h`}function Y0(J){if(J<1000)return String(J);return(J/1000).toFixed(1).replace(/\.0$/,"")+"k"}class z0{store;uiCtx;widgetFrame=0;widgetInterval;activeTaskIds=new Set;metrics=new Map;tui;widgetRegistered=!1;constructor(J){this.store=J}setStore(J){this.store=J}setUICtx(J){this.uiCtx=J}setActiveTask(J,H=!0){if(J&&H){if(this.activeTaskIds.add(J),!this.metrics.has(J))this.metrics.set(J,{startedAt:Date.now(),inputTokens:0,outputTokens:0});this.ensureTimer()}else if(J)this.activeTaskIds.delete(J);this.update()}addTokenUsage(J,H){for(let Q of this.activeTaskIds){let $=this.metrics.get(Q);if($)$.inputTokens+=J,$.outputTokens+=H}}ensureTimer(){if(!this.widgetInterval)this.widgetInterval=setInterval(()=>this.update(),150)}renderWidget(J,H){let Q=this.store.list(),$=J.terminal.columns,V=(M)=>b0(M,$);if(Q.length===0)return[];let z=Q.filter((M)=>M.status==="completed"),B=Q.filter((M)=>M.status==="in_progress"),A=Q.filter((M)=>M.status==="pending"),P=[];if(z.length>0)P.push(`${z.length} done`);if(B.length>0)P.push(`${B.length} in progress`);if(A.length>0)P.push(`${A.length} open`);let x=`${Q.length} tasks (${P.join(", ")})`,S=X0[this.widgetFrame%X0.length],y=[V(H.fg("accent","●")+" "+H.fg("accent",x))],f=Q.slice(0,H0);for(let M=0;M<f.length;M++){let E=f[M],g=this.activeTaskIds.has(E.id)&&E.status==="in_progress",b;if(g)b=H.fg("accent",S);else if(E.status==="completed")b=H.fg("success","✔");else if(E.status==="in_progress")b=H.fg("accent","◼");else b="◻";let u="";if(E.status==="pending"&&E.blockedBy.length>0){let T=E.blockedBy.filter((w)=>{let m=this.store.get(w);return m&&m.status!=="completed"});if(T.length>0)u=H.fg("dim",` › blocked by ${T.map((w)=>"#"+w).join(", ")}`)}let C;if(g){let T=E.activeForm||E.subject,w=E.metadata?.agentId,m=w?` (agent ${w.slice(0,5)})`:"",h=this.metrics.get(E.id),I="";if(h){let p=T0(Date.now()-h.startedAt),O=[];if(h.inputTokens>0)O.push(`↑ ${Y0(h.inputTokens)}`);if(h.outputTokens>0)O.push(`↓ ${Y0(h.outputTokens)}`);I=O.length>0?` ${H.fg("dim",`(${p} · ${O.join(" ")})`)}`:` ${H.fg("dim",`(${p})`)}`}C=` ${b} ${H.fg("dim","#"+E.id)} ${H.fg("accent",T+m+"…")}${I}`}else if(E.status==="completed")C=` ${b} ${H.fg("dim",H.strikethrough("#"+E.id+" "+E.subject))}`;else{let T=E.status==="in_progress"&&E.metadata?.agentId?H.fg("dim",` (agent ${E.metadata.agentId.slice(0,5)})`):"";C=` ${b} ${H.fg("dim","#"+E.id)} ${E.subject}${T}`}y.push(V(C+u))}if(Q.length>H0)y.push(V(H.fg("dim",` … and ${Q.length-H0} more`)));return y}update(){if(!this.uiCtx)return;let J=this.store.list();if(J.length===0){if(this.widgetRegistered)this.uiCtx.setWidget("tasks",void 0),this.widgetRegistered=!1;if(this.widgetInterval)clearInterval(this.widgetInterval),this.widgetInterval=void 0;return}for(let Q of this.activeTaskIds){let $=this.store.get(Q);if(!$||$.status!=="in_progress")this.activeTaskIds.delete(Q),this.metrics.delete(Q)}let H=J.some((Q)=>this.activeTaskIds.has(Q.id)&&Q.status==="in_progress");if(H)this.ensureTimer();else if(!H&&this.widgetInterval)clearInterval(this.widgetInterval),this.widgetInterval=void 0;if(this.widgetFrame++,!this.widgetRegistered)this.uiCtx.setWidget("tasks",(Q,$)=>{return this.tui=Q,{render:()=>this.renderWidget(Q,$),invalidate:()=>{}}},{placement:"aboveEditor"}),this.widgetRegistered=!0;else if(this.tui)this.tui.requestRender()}dispose(){if(this.widgetInterval)clearInterval(this.widgetInterval),this.widgetInterval=void 0;if(this.uiCtx)this.uiCtx.setWidget("tasks",void 0);this.widgetRegistered=!1,this.tui=void 0}}var I0=!!(process.env.DM_TASKS_DEBUG||process.env.PI_TASKS_DEBUG);function l(...J){if(I0)console.error("[dm-tasks]",...J)}function v(J){return{content:[{type:"text",text:J}],details:void 0}}var l0=Symbol.for("dm-subagents:service"),g0=new Set(["TaskCreate","TaskList","TaskGet","TaskUpdate","TaskOutput","TaskStop","TaskExecute"]),u0=4,K0=4,p0=`<system-reminder>
2
- The task tools haven't been used recently. If you're working on tasks that would benefit from tracking progress, consider using TaskCreate to add new tasks and TaskUpdate to update task status (set to in_progress when starting, completed when done). Also consider cleaning up the task list if it has become stale. Only use these if relevant to the current work. This is just a gentle reminder - ignore if not applicable. Make sure that you NEVER mention this reminder to the user
3
- </system-reminder>`;function c0(J){let H=G0(),Q=process.env.DM_TASKS??process.env.PI_TASKS,$=H.taskScope??"session";function V(X){if(Q==="off")return;if(Q?.startsWith("/"))return Q;if(Q?.startsWith("."))return m0(Q);if(Q)return Q;if($==="memory")return;if($==="session"&&X)return D0(process.cwd(),".dm","tasks",`tasks-${X}.json`);if($==="session")return;return D0(process.cwd(),".dm","tasks","tasks.json")}let z=new i(V()),B=new t,A=new z0(z),P,x,S=new Map;function y(){return globalThis[l0]}function f(){let X=y();return typeof X?.spawn==="function"?X:void 0}function M(X,Z=!1){let G=S.get(X);if(G)return G;return z.list().find((N)=>{if(!Z&&N.status!=="in_progress")return!1;return N.metadata?.agentId===X})?.id}function E(X,Z=!1){let G=M(X,Z);if(G)return G;for(let[N,U]of S)if(N.startsWith(X))return U;return z.list().find((N)=>{if(!Z&&N.status!=="in_progress")return!1;let U=N.metadata?.agentId;return typeof U==="string"&&U.startsWith(X)})?.id}async function g(X,Z,G){let N=f();if(!N?.spawn)throw Error("Bundled dm-subagents is not loaded yet; restart dm or check bundled extensions.");l("spawn:call",{type:X,options:{...G,prompt:void 0}});let U=await N.spawn(X,Z,G);if(typeof U!=="string"||U.length===0)throw Error("dm-subagents did not return an agent id");return l("spawn:ok",{id:U}),U}async function b(X){let Z=y();if(typeof Z?.abort!=="function")return;try{await Z.abort(X)}catch(G){l("stop:error",{agentId:X,err:G})}}function u(X,Z){let G=`You are executing task #${X.id}: "${X.subject}"
4
-
5
- ${X.description}`;if(X.blockedBy&&X.blockedBy.length>0){let N=[];for(let U of X.blockedBy){let q=z.get(U);if(q?.metadata?.result){let K=q.metadata.result.length>4000?q.metadata.result.slice(0,4000)+`
6
-
7
- [... truncated — use TaskGet for full output]`:q.metadata.result;N.push(`### Task #${U}: ${q.subject}
8
- ${K}`)}}if(N.length>0)G+=`
9
-
10
- ## Prerequisite task results
11
-
12
- ${N.join(`
13
-
14
- `)}`}if(Z)G+=`
15
-
16
- ${Z}`;return G+=`
17
-
18
- Complete this task fully. Do not attempt to manage tasks yourself.`,G}let C=new s(()=>z,()=>H.autoClearCompleted??"on_list_complete",K0);function T(X,Z){let G=z.get(X);if(!G||G.status!=="in_progress")return!1;if(Z.status==="completed"||Z.status==="steered"||Z.status==="stopped"){S.delete(Z.id);let N={...G.metadata};if(Z.result!==void 0)N.result=Z.result;return z.update(G.id,{status:"completed",metadata:N}),A.setActiveTask(G.id,!1),C.trackCompletion(G.id,O),l("reconciled terminal subagent",{taskId:G.id,agentId:Z.id,status:Z.status}),!0}if(Z.status==="error"||Z.status==="aborted")return S.delete(Z.id),z.update(G.id,{status:"pending",metadata:{...G.metadata,lastError:Z.error??Z.status}}),A.setActiveTask(G.id,!1),C.resetBatchCountdown(),l("reconciled failed subagent",{taskId:G.id,agentId:Z.id,status:Z.status}),!0;return!1}function w(){let X=y();if(typeof X?.getRecord!=="function")return;let Z=!1;for(let G of z.list()){if(G.status!=="in_progress")continue;let N=G.metadata?.agentId;if(typeof N!=="string"||N.length===0)continue;let U=X.getRecord(N);if(!U)continue;if(T(G.id,U))Z=!0}if(Z)A.update()}J.events.on("subagents:completed",async(X)=>{let{id:Z,result:G}=X,N=M(Z);if(!N)return;S.delete(Z);let U=z.get(N);if(!U)return;if(z.update(U.id,{status:"completed",metadata:{...U.metadata,result:G}}),A.setActiveTask(U.id,!1),(H.autoCascade??!1)&&x&&P){let q=z.list().filter((K)=>K.status==="pending"&&K.metadata?.agentType&&K.blockedBy.includes(U.id)&&K.blockedBy.every((L)=>z.get(L)?.status==="completed"));for(let K of q){z.update(K.id,{status:"in_progress"});let L=u(K,x.additionalContext);try{let Y=await g(K.metadata.agentType,L,{description:K.subject,isBackground:!0,maxTurns:x.maxTurns,...x.model?{model:x.model}:{}});S.set(Y,K.id),z.update(K.id,{owner:Y,metadata:{...K.metadata,agentId:Y}}),A.setActiveTask(K.id)}catch(Y){z.update(K.id,{status:"pending",metadata:{...K.metadata,lastError:Y.message}})}}}C.trackCompletion(U.id,O),A.update()}),J.events.on("subagents:failed",(X)=>{let{id:Z,error:G,result:N,status:U}=X,q=M(Z);if(!q)return;S.delete(Z);let K=z.get(q);if(!K)return;if(U==="stopped")z.update(K.id,{status:"completed",metadata:{...K.metadata,result:N||K.metadata?.result}}),C.trackCompletion(K.id,O);else z.update(K.id,{status:"pending",metadata:{...K.metadata,lastError:G||U}}),C.resetBatchCountdown();A.setActiveTask(K.id,!1),A.update()});let m=!1,h=!1;function I(X){if(m)return;if($==="session"&&!Q){let Z=X.sessionManager.getSessionId(),G=V(Z);z=new i(G),A.setStore(z)}m=!0}function p(X=!1){if(h)return;h=!0;let Z=z.list();if(Z.length>0)if(!X&&Z.every((G)=>G.status==="completed")){if(z.clearCompleted(),$==="session")z.deleteFileIfEmpty()}else A.update()}let O=0,n=0,k=!1;J.on("turn_start",async(X,Z)=>{if(O++,P=Z,A.setUICtx(Z.ui),I(Z),C.onTurnStart(O))A.update()}),J.on("turn_end",async(X)=>{let Z=X.message;if(Z?.role==="assistant"&&Z.usage)A.addTokenUsage(Z.usage.input??0,Z.usage.output??0)}),J.on("tool_result",async(X)=>{if(g0.has(X.toolName))return n=O,k=!1,{};if(O-n<u0)return{};if(k)return{};if(z.list().length===0)return{};return k=!0,n=O,{content:[...X.content,{type:"text",text:p0}]}}),J.on("before_agent_start",async(X,Z)=>{P=Z,A.setUICtx(Z.ui),I(Z),p()}),J.on("session_switch",async(X,Z)=>{P=Z,A.setUICtx(Z.ui);let G=X?.reason==="resume";if(m=!1,h=!1,O=0,n=0,k=!1,C.reset(),!G&&$==="memory")z.clearAll();I(Z),p(G)}),J.on("tool_execution_start",async(X,Z)=>{P=Z,A.setUICtx(Z.ui),I(Z),A.update()}),J.registerTool({name:"TaskCreate",label:"TaskCreate",description:`Use this tool to create a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
19
- It also helps the user understand the progress of the task and overall progress of their requests.
20
-
21
- ## When to Use This Tool
22
-
23
- Use this tool proactively in these scenarios:
24
-
25
- - Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
26
- - Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
27
- - Plan mode - When using plan mode, create a task list to track the work
28
- - User explicitly requests todo list - When the user directly asks you to use the todo list
29
- - User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
30
- - After receiving new instructions - Immediately capture user requirements as tasks
31
- - When you start working on a task - Mark it as in_progress BEFORE beginning work
32
- - After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation
33
-
34
- ## When NOT to Use This Tool
35
-
36
- Skip using this tool when:
37
- - There is only a single, straightforward task
38
- - The task is trivial and tracking it provides no organizational benefit
39
- - The task can be completed in less than 3 trivial steps
40
- - The task is purely conversational or informational
41
-
42
- NOTE that you should not use this tool if there is only one trivial task to do. In this case you are better off just doing the task directly.
43
-
44
- ## Task Fields
45
-
46
- - **subject**: A brief, actionable title in imperative form (e.g., "Fix authentication bug in login flow")
47
- - **description**: Detailed description of what needs to be done, including context and acceptance criteria
48
- - **activeForm** (optional): Present continuous form shown in the spinner when the task is in_progress (e.g., "Fixing authentication bug"). If omitted, the spinner shows the subject instead.
49
-
50
- All tasks are created with status \`pending\`.
51
-
52
- ## Tips
53
-
54
- - Create tasks with clear, specific subjects that describe the outcome
55
- - Include enough detail in the description for another agent to understand and complete the task
56
- - After creating tasks, use TaskUpdate to set up dependencies (blocks/blockedBy) if needed
57
- - Check TaskList first to avoid creating duplicate tasks
58
- - Include \`agentType\` (e.g., "general-purpose", "Explore") to mark tasks for subagent execution via TaskExecute`,promptGuidelines:["When working on complex multi-step tasks, use TaskCreate to track progress and TaskUpdate to update status.","Mark tasks as in_progress before starting work and completed when done.","Use TaskList to check for available work after completing a task."],parameters:D.Object({subject:D.String({description:"A brief title for the task"}),description:D.String({description:"A detailed description of what needs to be done"}),activeForm:D.Optional(D.String({description:"Present continuous form shown in spinner when in_progress (e.g., 'Running tests')"})),agentType:D.Optional(D.String({description:"Agent type for subagent execution (e.g., 'general-purpose', 'Explore'). Tasks with agentType can be started via TaskExecute."})),metadata:D.Optional(D.Record(D.String(),D.Any(),{description:"Arbitrary metadata to attach to the task"}))}),execute(X,Z,G,N,U){C.resetBatchCountdown();let q=Z.metadata??{};if(Z.agentType)q.agentType=Z.agentType;let K=z.create(Z.subject,Z.description,Z.activeForm,Object.keys(q).length>0?q:void 0);return A.update(),Promise.resolve(v(`Task #${K.id} created successfully: ${K.subject}`))}}),J.registerTool({name:"TaskList",label:"TaskList",description:`Use this tool to list all tasks in the task list.
59
-
60
- ## When to Use This Tool
61
-
62
- - To see what tasks are available to work on (status: 'pending', no owner, not blocked)
63
- - To check overall progress on the project
64
- - To find tasks that are blocked and need dependencies resolved
65
- - After completing a task, to check for newly unblocked work or claim the next available task
66
- - **Prefer working on tasks in ID order** (lowest ID first) when multiple tasks are available, as earlier tasks often set up context for later ones
67
-
68
- ## Output
69
-
70
- Returns a summary of each task:
71
- - **id**: Task identifier (use with TaskGet, TaskUpdate)
72
- - **subject**: Brief description of the task
73
- - **status**: 'pending', 'in_progress', or 'completed'
74
- - **owner**: Agent ID if assigned, empty if available
75
- - **blockedBy**: List of open task IDs that must be resolved first (tasks with blockedBy cannot be claimed until dependencies resolve)
76
-
77
- Use TaskGet with a specific task ID to view full details including description and comments.`,parameters:D.Object({}),execute(X,Z,G,N,U){w();let q=z.list();if(q.length===0)return Promise.resolve(v("No tasks found"));let K={pending:0,in_progress:1,completed:2},Y=[...q].sort((W,j)=>{let F=(K[W.status]??0)-(K[j.status]??0);if(F!==0)return F;return Number(W.id)-Number(j.id)}).map((W)=>{let j=`#${W.id} [${W.status}] ${W.subject}`;if(W.owner)j+=` (${W.owner})`;if(W.blockedBy.length>0){let F=W.blockedBy.filter((_)=>{let R=z.get(_);return R&&R.status!=="completed"});if(F.length>0)j+=` [blocked by ${F.map((_)=>"#"+_).join(", ")}]`}return j});return Promise.resolve(v(Y.join(`
78
- `)))}}),J.registerTool({name:"TaskGet",label:"TaskGet",description:`Use this tool to retrieve a task by its ID from the task list.
79
-
80
- ## When to Use This Tool
81
-
82
- - When you need the full description and context before starting work on a task
83
- - To understand task dependencies (what it blocks, what blocks it)
84
- - After being assigned a task, to get complete requirements
85
-
86
- ## Output
87
-
88
- Returns full task details:
89
- - **subject**: Task title
90
- - **description**: Detailed requirements and context
91
- - **status**: 'pending', 'in_progress', or 'completed'
92
- - **blocks**: Tasks waiting on this one to complete
93
- - **blockedBy**: Tasks that must complete before this one can start
94
-
95
- ## Tips
96
-
97
- - After fetching a task, verify its blockedBy list is empty before beginning work.
98
- - Use TaskList to see all tasks in summary form.`,parameters:D.Object({taskId:D.String({description:"The ID of the task to retrieve"})}),execute(X,Z,G,N,U){w();let q=z.get(Z.taskId);if(!q)return Promise.resolve(v("Task not found"));let K=q.description.replace(/\\n/g,`
99
- `),L=[`Task #${q.id}: ${q.subject}`,`Status: ${q.status}`];if(q.owner)L.push(`Owner: ${q.owner}`);if(L.push(`Description: ${K}`),q.blockedBy.length>0){let W=q.blockedBy.filter((j)=>{let F=z.get(j);return F&&F.status!=="completed"});if(W.length>0)L.push(`Blocked by: ${W.map((j)=>"#"+j).join(", ")}`)}if(q.blocks.length>0)L.push(`Blocks: ${q.blocks.map((W)=>"#"+W).join(", ")}`);if(Object.keys(q.metadata).length>0)L.push(`Metadata: ${JSON.stringify(q.metadata)}`);return Promise.resolve(v(L.join(`
100
- `)))}}),J.registerTool({name:"TaskUpdate",label:"TaskUpdate",description:`Use this tool to update a task in the task list.
101
-
102
- ## When to Use This Tool
103
-
104
- **Before starting work on a task:**
105
- - Mark it in_progress BEFORE beginning — do not start work without updating status first
106
- - After resolving, call TaskList to find your next task
107
-
108
- **Mark tasks as resolved:**
109
- - When you have completed the work described in a task
110
- - When a task is no longer needed or has been superseded
111
- - IMPORTANT: Always mark your assigned tasks as resolved when you finish them
112
- - After resolving, call TaskList to find your next task
113
-
114
- - ONLY mark a task as completed when you have FULLY accomplished it
115
- - If you encounter errors, blockers, or cannot finish, keep the task as in_progress
116
- - When blocked, create a new task describing what needs to be resolved
117
- - Never mark a task as completed if:
118
- - Tests are failing
119
- - Implementation is partial
120
- - You encountered unresolved errors
121
- - You couldn't find necessary files or dependencies
122
-
123
- **Delete tasks:**
124
- - When a task is no longer relevant or was created in error
125
- - Setting status to \`deleted\` permanently removes the task
126
-
127
- **Update task details:**
128
- - When requirements change or become clearer
129
- - When establishing dependencies between tasks
130
-
131
- ## Fields You Can Update
132
-
133
- - **status**: The task status (see Status Workflow below)
134
- - **subject**: Change the task title (imperative form, e.g., "Run tests")
135
- - **description**: Change the task description
136
- - **activeForm**: Present continuous form shown in spinner when in_progress (e.g., "Running tests")
137
- - **owner**: Change the task owner (agent name)
138
- - **metadata**: Merge metadata keys into the task (set a key to null to delete it)
139
- - **addBlocks**: Mark tasks that cannot start until this one completes
140
- - **addBlockedBy**: Mark tasks that must complete before this one can start
141
-
142
- ## Status Workflow
143
-
144
- Status progresses: \`pending\` → \`in_progress\` → \`completed\`
145
-
146
- Use \`deleted\` to permanently remove a task.
147
-
148
- ## Staleness
149
-
150
- Make sure to read a task's latest state using \`TaskGet\` before updating it.
151
-
152
- ## Examples
153
-
154
- Mark task as in progress when starting work:
155
- \`\`\`json
156
- {"taskId": "1", "status": "in_progress"}
157
- \`\`\`
158
-
159
- Mark task as completed after finishing work:
160
- \`\`\`json
161
- {"taskId": "1", "status": "completed"}
162
- \`\`\`
163
-
164
- Delete a task:
165
- \`\`\`json
166
- {"taskId": "1", "status": "deleted"}
167
- \`\`\`
168
-
169
- Claim a task by setting owner:
170
- \`\`\`json
171
- {"taskId": "1", "owner": "my-name"}
172
- \`\`\`
173
-
174
- Set up task dependencies:
175
- \`\`\`json
176
- {"taskId": "2", "addBlockedBy": ["1"]}
177
- \`\`\``,parameters:D.Object({taskId:D.String({description:"The ID of the task to update"}),status:D.Optional(D.Unsafe({type:"string",enum:["pending","in_progress","completed","deleted"],description:"New status for the task"})),subject:D.Optional(D.String({description:"New subject for the task"})),description:D.Optional(D.String({description:"New description for the task"})),activeForm:D.Optional(D.String({description:"Present continuous form shown in spinner when in_progress"})),owner:D.Optional(D.String({description:"New owner for the task"})),metadata:D.Optional(D.Record(D.String(),D.Any(),{description:"Metadata keys to merge into the task. Set a key to null to delete it."})),addBlocks:D.Optional(D.Array(D.String(),{description:"Task IDs that this task blocks"})),addBlockedBy:D.Optional(D.Array(D.String(),{description:"Task IDs that block this task"}))}),execute(X,Z,G,N,U){w();let{taskId:q,...K}=Z,{task:L,changedFields:Y,warnings:W}=z.update(q,K);if(Y.length===0&&!L)return Promise.resolve(v(`Task #${q} not found`));if(K.status==="in_progress")A.setActiveTask(q),C.resetBatchCountdown();else if(K.status==="pending")C.resetBatchCountdown();else if(K.status==="completed"||K.status==="deleted"){if(A.setActiveTask(q,!1),K.status==="completed")C.trackCompletion(q,O)}A.update();let j=`Updated task #${q} ${Y.join(", ")}`;if(W.length>0)j+=` (warning: ${W.join("; ")})`;return Promise.resolve(v(j))}}),J.registerTool({name:"TaskOutput",label:"TaskOutput",description:`- Retrieves output from a running or completed task (background shell, agent, or remote session)
178
- - Takes a task_id parameter identifying the task
179
- - Returns the task output along with status information
180
- - Use block=true (default) to wait for task completion
181
- - Use block=false for non-blocking check of current status
182
- - Task IDs can be found using the /tasks command
183
- - Works with all task types: background shells, async agents, and remote sessions`,parameters:D.Object({task_id:D.String({description:"The task ID to get output from"}),block:D.Boolean({description:"Whether to wait for completion",default:!0}),timeout:D.Number({description:"Max wait time in ms",default:30000,minimum:0,maximum:600000})}),async execute(X,Z,G,N,U){w();let{task_id:q,block:K,timeout:L}=Z,Y=B.getOutput(q);if(!Y){let W=q;if(!z.get(W))W=E(q,!0)??W;let j=z.get(W);if(!j)throw Error(`No task found with ID ${q}`);if(j.metadata?.agentId){if(K&&j.status==="in_progress")await new Promise((_)=>{let R=setTimeout(()=>{c(),o(),_()},L??30000),r=()=>{clearTimeout(R),_()},c=J.events.on("subagents:completed",(a)=>{if(a.id===j.metadata?.agentId)c(),o(),r()}),o=J.events.on("subagents:failed",(a)=>{if(a.id===j.metadata?.agentId)c(),o(),r()}),Q0=z.get(W);if(Q0&&Q0.status!=="in_progress")c(),o(),r();G?.addEventListener("abort",()=>{c(),o(),r()},{once:!0})});w();let F=z.get(W)??j;return v(`Task #${W} [${F.status}] — subagent ${j.metadata.agentId}`)}throw Error(`No background process for task ${q}`)}if(K&&Y.status==="running"){let W=await B.waitForCompletion(q,L??30000,G??void 0);if(W)return v(`Task #${q} (${W.status})${W.exitCode!==void 0?` exit code: ${W.exitCode}`:""}
184
-
185
- ${W.output}`)}return v(`Task #${q} (${Y.status})${Y.exitCode!==void 0?` exit code: ${Y.exitCode}`:""}
186
-
187
- ${Y.output}`)}}),J.registerTool({name:"TaskStop",label:"TaskStop",description:`
188
- - Stops a running background task by its ID
189
- - Takes a task_id parameter identifying the task to stop
190
- - Returns a success or failure status
191
- - Use this tool when you need to terminate a long-running task`,parameters:D.Object({task_id:D.Optional(D.String({description:"The ID of the background task to stop"})),shell_id:D.Optional(D.String({description:"Deprecated: use task_id instead"}))}),async execute(X,Z,G,N,U){w();let q=Z.task_id??Z.shell_id;if(!q)throw Error("task_id is required");if(!await B.stop(q)){let L=q;if(!z.get(L))L=E(q,!0)??L;let Y=z.get(L);if(Y?.metadata?.agentId&&Y.status==="in_progress")return z.update(L,{status:"completed"}),C.trackCompletion(L,O),await b(Y.metadata.agentId),A.setActiveTask(L,!1),A.update(),v(`Task #${L} stopped successfully`);throw Error(`No running background process for task ${q}`)}return z.update(q,{status:"completed"}),C.trackCompletion(q,O),A.setActiveTask(q,!1),A.update(),v(`Task #${q} stopped successfully`)}}),J.registerTool({name:"TaskExecute",label:"TaskExecute",description:`Execute one or more tasks as subagents.
192
-
193
- ## When to Use This Tool
194
-
195
- - To start execution of tasks that have \`agentType\` set (created via TaskCreate with agentType parameter)
196
- - Tasks must be \`pending\` with all blockedBy dependencies \`completed\`
197
- - Each task runs as an independent background subagent
198
-
199
- ## Parameters
200
-
201
- - **task_ids**: Array of task IDs to execute
202
- - **additional_context**: Extra context appended to each agent's prompt
203
- - **model**: Model override for agents (e.g., "sonnet", "haiku")
204
- - **max_turns**: Maximum turns per agent`,promptGuidelines:["Never use the Agent tool for tasks launched via TaskExecute — agents are already running."],parameters:D.Object({task_ids:D.Array(D.String(),{description:"Task IDs to execute as subagents"}),additional_context:D.Optional(D.String({description:"Extra context for agent prompts"})),model:D.Optional(D.String({description:"Model override for agents"})),max_turns:D.Optional(D.Number({description:"Max turns per agent",minimum:1}))}),async execute(X,Z,G,N,U){if(w(),!f())return v("Subagent execution is currently unavailable. Bundled dm-subagents is not loaded yet; restart dm or check bundled extensions.");let q=[],K=[];for(let Y of Z.task_ids){let W=z.get(Y);if(!W){q.push(`#${Y}: not found`);continue}if(W.status!=="pending"){q.push(`#${Y}: not pending (status: ${W.status})`);continue}if(!W.metadata?.agentType){q.push(`#${Y}: no agentType set — create with agentType parameter or update metadata`);continue}let j=W.blockedBy.filter((_)=>{let R=z.get(_);return!R||R.status!=="completed"});if(j.length>0){q.push(`#${Y}: blocked by ${j.map((_)=>"#"+_).join(", ")}`);continue}z.update(Y,{status:"in_progress"});let F=u(W,Z.additional_context);try{let _=await g(W.metadata.agentType,F,{description:W.subject,isBackground:!0,maxTurns:Z.max_turns,...Z.model?{model:Z.model}:{}});S.set(_,Y),z.update(Y,{owner:_,metadata:{...W.metadata,agentId:_}}),A.setActiveTask(Y),K.push(`#${Y} → agent ${_}`)}catch(_){l(`spawn:error task=#${Y}`,_),z.update(Y,{status:"pending"}),q.push(`#${Y}: spawn failed — ${_.message}`)}}x={additionalContext:Z.additional_context,model:Z.model,maxTurns:Z.max_turns},A.update();let L=[];if(K.length>0)L.push(`Launched ${K.length} agent(s):
205
- ${K.join(`
206
- `)}
207
- Use TaskOutput to check progress. Do not spawn additional agents for these tasks.`);if(q.length>0)L.push(`Skipped:
208
- ${q.join(`
209
- `)}`);if(L.length===0)L.push("No tasks to execute.");return v(L.join(`
210
-
211
- `))}}),J.registerCommand("tasks",{description:"Manage tasks — view, create, clear completed",handler:async(X,Z)=>{let G=Z.ui,N=async()=>{let Y=z.list(),W=Y.length,j=Y.filter((R)=>R.status==="completed").length,F=[`View all tasks (${W})`,"Create task"];if(j>0)F.push(`Clear completed (${j})`);if(W>0)F.push(`Clear all (${W})`);F.push("Settings");let _=await G.select("Tasks",F);if(!_)return;if(_.startsWith("View"))await U();else if(_==="Create task")await L();else if(_==="Settings")await K();else if(_.startsWith("Clear completed")){if(z.clearCompleted(),$==="session")z.deleteFileIfEmpty();A.update(),await N()}else if(_.startsWith("Clear all")){if(z.clearAll(),$==="session")z.deleteFileIfEmpty();A.update(),await N()}},U=async()=>{let Y=z.list();if(Y.length===0)return await G.select("No tasks",["← Back"]),N();let W=(R)=>{switch(R){case"completed":return"✔";case"in_progress":return"◼";default:return"◻"}},j=Y.map((R)=>`${W(R.status)} #${R.id} [${R.status}] ${R.subject}`);j.push("← Back");let F=await G.select("Tasks",j);if(!F||F==="← Back")return N();let _=F.match(/#(\d+)/);if(_)await q(_[1]);else return U()},q=async(Y)=>{let W=z.get(Y);if(!W)return U();let j=[];if(W.status==="pending")j.push("▸ Start (in_progress)");if(W.status==="in_progress")j.push("✓ Complete");j.push("✗ Delete"),j.push("← Back");let F=`#${W.id} [${W.status}] ${W.subject}
212
- ${W.description}`,_=await G.select(F,j);if(_==="▸ Start (in_progress)")return z.update(Y,{status:"in_progress"}),A.setActiveTask(Y),A.update(),U();else if(_==="✓ Complete")return z.update(Y,{status:"completed"}),C.trackCompletion(Y,O),A.setActiveTask(Y,!1),A.update(),U();else if(_==="✗ Delete")return z.update(Y,{status:"deleted"}),A.setActiveTask(Y,!1),A.update(),U();return U()},K=()=>W0(G,H,N,K0),L=async()=>{let Y=await G.input("Task subject");if(!Y)return N();let W=await G.input("Task description");if(!W)return N();return z.create(Y,W),A.update(),N()};await N()}})}export{c0 as default};
@@ -1 +0,0 @@
1
- class D{processes=new Map;track(w,j,y){let q={taskId:w,pid:j.pid,command:y,output:[],status:"running",startedAt:Date.now(),proc:j,abortController:new AbortController,waiters:[]};j.stdout?.on("data",(x)=>{q.output.push(x.toString())}),j.stderr?.on("data",(x)=>{q.output.push(x.toString())}),j.on("close",(x,z)=>{if(q.status==="running")q.status=x===0?"completed":"error";q.exitCode=x??void 0,q.completedAt=Date.now();for(let A of q.waiters)A();q.waiters=[]}),j.on("error",(x)=>{if(q.status==="running"){q.status="error",q.output.push(`Process error: ${x.message}`),q.completedAt=Date.now();for(let z of q.waiters)z();q.waiters=[]}}),this.processes.set(w,q)}getOutput(w){let j=this.processes.get(w);if(!j)return;return{output:j.output.join(""),status:j.status,exitCode:j.exitCode,startedAt:j.startedAt,completedAt:j.completedAt,command:j.command}}waitForCompletion(w,j,y){let q=this.processes.get(w);if(!q)return Promise.resolve(void 0);if(q.status!=="running")return Promise.resolve(this.getOutput(w));return new Promise((x)=>{let z=!1,A=setTimeout(B,j);function B(){if(z)return;z=!0,clearTimeout(A),x(C.getOutput(w))}let C=this;q.waiters.push(B),y?.addEventListener("abort",B,{once:!0})})}async stop(w){let j=this.processes.get(w);if(!j||j.status!=="running")return!1;j.status="stopped",j.proc.kill("SIGTERM"),await new Promise((y)=>{let q=setTimeout(()=>{try{j.proc.kill("SIGKILL")}catch{}y()},5000);j.proc.on("close",()=>{clearTimeout(q),y()})}),j.completedAt=Date.now();for(let y of j.waiters)y();return j.waiters=[],!0}getProcess(w){return this.processes.get(w)}}export{D as ProcessTracker};
@@ -1 +0,0 @@
1
- import{existsSync as $,mkdirSync as q,readFileSync as V,renameSync as x,unlinkSync as U,writeFileSync as W}from"node:fs";import{homedir as D}from"node:os";import{dirname as X,isAbsolute as Y,join as Z}from"node:path";var M=Z(D(),".dm","tasks"),A=50,C=100;function E(z){for(let B=0;B<C;B++)try{W(z,`${process.pid}`,{flag:"wx"});return}catch(G){if(G.code==="EEXIST"){try{let Q=parseInt(V(z,"utf-8"),10);if(Q&&!T(Q)){U(z);continue}}catch{}let N=Date.now();while(Date.now()-N<A);continue}throw G}throw Error(`Failed to acquire lock: ${z}`)}function K(z){try{U(z)}catch{}}function T(z){try{return process.kill(z,0),!0}catch{return!1}}class j{filePath;lockPath;nextId=1;tasks=new Map;constructor(z){if(!z)return;let G=Y(z)?z:Z(M,`${z}.json`);q(X(G),{recursive:!0}),this.filePath=G,this.lockPath=G+".lock",this.load()}load(){if(!this.filePath)return;if(!$(this.filePath))return;try{let z=JSON.parse(V(this.filePath,"utf-8"));this.nextId=z.nextId,this.tasks.clear();for(let B of z.tasks)this.tasks.set(B.id,B)}catch{}}save(){if(!this.filePath)return;let z={nextId:this.nextId,tasks:Array.from(this.tasks.values())},B=this.filePath+".tmp";W(B,JSON.stringify(z,null,2)),x(B,this.filePath)}withLock(z){if(!this.lockPath)return z();E(this.lockPath);try{this.load();let B=z();return this.save(),B}finally{K(this.lockPath)}}create(z,B,G,N){return this.withLock(()=>{let Q=Date.now(),H={id:String(this.nextId++),subject:z,description:B,status:"pending",activeForm:G,owner:void 0,metadata:N??{},blocks:[],blockedBy:[],createdAt:Q,updatedAt:Q};return this.tasks.set(H.id,H),H})}get(z){if(this.filePath)this.load();return this.tasks.get(z)}list(){if(this.filePath)this.load();return Array.from(this.tasks.values()).sort((z,B)=>Number(z.id)-Number(B.id))}update(z,B){return this.withLock(()=>{let G=this.tasks.get(z);if(!G)return{task:void 0,changedFields:[],warnings:[]};let N=[],Q=[];if(B.status==="deleted"){this.tasks.delete(z);for(let H of this.tasks.values())H.blocks=H.blocks.filter((J)=>J!==z),H.blockedBy=H.blockedBy.filter((J)=>J!==z);return{task:void 0,changedFields:["deleted"],warnings:[]}}if(B.status!==void 0)G.status=B.status,N.push("status");if(B.subject!==void 0)G.subject=B.subject,N.push("subject");if(B.description!==void 0)G.description=B.description,N.push("description");if(B.activeForm!==void 0)G.activeForm=B.activeForm,N.push("activeForm");if(B.owner!==void 0)G.owner=B.owner,N.push("owner");if(B.metadata!==void 0){for(let[H,J]of Object.entries(B.metadata))if(J===null)delete G.metadata[H];else G.metadata[H]=J;N.push("metadata")}if(B.addBlocks&&B.addBlocks.length>0){for(let H of B.addBlocks){if(!G.blocks.includes(H))G.blocks.push(H);let J=this.tasks.get(H);if(J&&!J.blockedBy.includes(z))J.blockedBy.push(z),J.updatedAt=Date.now();if(H===z)Q.push(`#${z} blocks itself`);else if(!J)Q.push(`#${H} does not exist`);else if(J.blocks.includes(z))Q.push(`cycle: #${z} and #${H} block each other`)}N.push("blocks")}if(B.addBlockedBy&&B.addBlockedBy.length>0){for(let H of B.addBlockedBy){if(!G.blockedBy.includes(H))G.blockedBy.push(H);let J=this.tasks.get(H);if(J&&!J.blocks.includes(z))J.blocks.push(z),J.updatedAt=Date.now();if(H===z)Q.push(`#${z} blocks itself`);else if(!J)Q.push(`#${H} does not exist`);else if(G.blocks.includes(H))Q.push(`cycle: #${z} and #${H} block each other`)}N.push("blockedBy")}return G.updatedAt=Date.now(),{task:G,changedFields:N,warnings:Q}})}delete(z){return this.withLock(()=>{if(!this.tasks.has(z))return!1;this.tasks.delete(z);for(let B of this.tasks.values())B.blocks=B.blocks.filter((G)=>G!==z),B.blockedBy=B.blockedBy.filter((G)=>G!==z);return!0})}clearAll(){return this.withLock(()=>{let z=this.tasks.size;return this.tasks.clear(),z})}deleteFileIfEmpty(){if(!this.filePath||this.tasks.size>0)return!1;try{U(this.filePath)}catch{}return!0}clearCompleted(){return this.withLock(()=>{let z=0;for(let[B,G]of this.tasks)if(G.status==="completed")this.tasks.delete(B),z++;if(z>0){let B=new Set(this.tasks.keys());for(let G of this.tasks.values())G.blocks=G.blocks.filter((N)=>B.has(N)),G.blockedBy=G.blockedBy.filter((N)=>B.has(N))}return z})}}export{j as TaskStore};
@@ -1 +0,0 @@
1
- import{mkdirSync as e,readFileSync as n,writeFileSync as t}from"node:fs";import{dirname as r,join as i}from"node:path";var s=i(process.cwd(),".dm","tasks-config.json");function f(){try{return JSON.parse(n(s,"utf-8"))}catch{return{}}}function m(o){e(r(s),{recursive:!0}),t(s,JSON.stringify(o,null,2))}export{m as saveTasksConfig,f as loadTasksConfig};
File without changes
@@ -1 +0,0 @@
1
- import{getSettingsListTheme as R}from"@duckmind/dm-coding-agent";import{Container as S,SettingsList as U,Spacer as W,Text as X}from"@duckmind/dm-tui";import{mkdirSync as H,readFileSync as $,writeFileSync as K}from"node:fs";import{dirname as M,join as Q}from"node:path";var v=Q(process.cwd(),".dm","tasks-config.json");function F(J){H(M(v),{recursive:!0}),K(v,JSON.stringify(J,null,2))}async function D(J,r,z,A){return await J.custom((Y,O,Z,E)=>{let G=[{id:"taskScope",label:"Task storage",description:"memory: tasks live only in memory, lost when session ends. session: persisted per session (tasks-<sessionId>.json), survives resume. project: shared across all sessions (tasks.json). Takes effect on next session start.",currentValue:r.taskScope??"session",values:["memory","session","project"]},{id:"autoCascade",label:"Auto-execute with agents",description:"When ON: pending agent tasks start automatically once their dependencies complete. When OFF: use TaskExecute to launch them manually.",currentValue:r.autoCascade??!1?"on":"off",values:["on","off"]},{id:"autoClearCompleted",label:"Auto-clear completed tasks",description:`never: completed tasks stay visible until manually cleared. on_list_complete: cleared automatically after all tasks are done. on_task_complete: each task cleared shortly after it completes. Clearing lags ~${A} turns.`,currentValue:r.autoClearCompleted??"on_list_complete",values:["never","on_list_complete","on_task_complete"]}],j=new U(G,10,R(),(p,N)=>{if(p==="autoCascade")r.autoCascade=N==="on",F(r);if(p==="taskScope")r.taskScope=N,F(r);if(p==="autoClearCompleted")r.autoClearCompleted=N,F(r)},()=>E(void 0));class q extends S{handleInput(p){j.handleInput(p)}}let y=new q;return y.addChild(new X(O.bold(O.fg("accent","⚙ Task Settings")),0,0)),y.addChild(new W(1)),y.addChild(j),y}),z()}export{D as openSettingsMenu};
@@ -1 +0,0 @@
1
- import{truncateToWidth as v}from"@duckmind/dm-tui";var E=["✳","✴","✵","✶","✷","✸","✹","✺","✻","✼","✽"],R=10;function P(G){let z=Math.floor(G/1000);if(z<60)return`${z}s`;let F=Math.floor(z/60),J=z%60;if(F<60)return J>0?`${F}m ${J}s`:`${F}m`;let U=Math.floor(F/60),j=F%60;return j>0?`${U}h ${j}m`:`${U}h`}function M(G){if(G<1000)return String(G);return(G/1000).toFixed(1).replace(/\.0$/,"")+"k"}class f{store;uiCtx;widgetFrame=0;widgetInterval;activeTaskIds=new Set;metrics=new Map;tui;widgetRegistered=!1;constructor(G){this.store=G}setStore(G){this.store=G}setUICtx(G){this.uiCtx=G}setActiveTask(G,z=!0){if(G&&z){if(this.activeTaskIds.add(G),!this.metrics.has(G))this.metrics.set(G,{startedAt:Date.now(),inputTokens:0,outputTokens:0});this.ensureTimer()}else if(G)this.activeTaskIds.delete(G);this.update()}addTokenUsage(G,z){for(let F of this.activeTaskIds){let J=this.metrics.get(F);if(J)J.inputTokens+=G,J.outputTokens+=z}}ensureTimer(){if(!this.widgetInterval)this.widgetInterval=setInterval(()=>this.update(),150)}renderWidget(G,z){let F=this.store.list(),J=G.terminal.columns,U=(O)=>v(O,J);if(F.length===0)return[];let j=F.filter((O)=>O.status==="completed"),W=F.filter((O)=>O.status==="in_progress"),K=F.filter((O)=>O.status==="pending"),q=[];if(j.length>0)q.push(`${j.length} done`);if(W.length>0)q.push(`${W.length} in progress`);if(K.length>0)q.push(`${K.length} open`);let _=`${F.length} tasks (${q.join(", ")})`,b=E[this.widgetFrame%E.length],D=[U(z.fg("accent","●")+" "+z.fg("accent",_))],V=F.slice(0,R);for(let O=0;O<V.length;O++){let H=V[O],X=this.activeTaskIds.has(H.id)&&H.status==="in_progress",Q;if(X)Q=z.fg("accent",b);else if(H.status==="completed")Q=z.fg("success","✔");else if(H.status==="in_progress")Q=z.fg("accent","◼");else Q="◻";let L="";if(H.status==="pending"&&H.blockedBy.length>0){let Y=H.blockedBy.filter((Z)=>{let y=this.store.get(Z);return y&&y.status!=="completed"});if(Y.length>0)L=z.fg("dim",` › blocked by ${Y.map((Z)=>"#"+Z).join(", ")}`)}let w;if(X){let Y=H.activeForm||H.subject,Z=H.metadata?.agentId,y=Z?` (agent ${Z.slice(0,5)})`:"",$=this.metrics.get(H.id),N="";if($){let B=P(Date.now()-$.startedAt),C=[];if($.inputTokens>0)C.push(`↑ ${M($.inputTokens)}`);if($.outputTokens>0)C.push(`↓ ${M($.outputTokens)}`);N=C.length>0?` ${z.fg("dim",`(${B} · ${C.join(" ")})`)}`:` ${z.fg("dim",`(${B})`)}`}w=` ${Q} ${z.fg("dim","#"+H.id)} ${z.fg("accent",Y+y+"…")}${N}`}else if(H.status==="completed")w=` ${Q} ${z.fg("dim",z.strikethrough("#"+H.id+" "+H.subject))}`;else{let Y=H.status==="in_progress"&&H.metadata?.agentId?z.fg("dim",` (agent ${H.metadata.agentId.slice(0,5)})`):"";w=` ${Q} ${z.fg("dim","#"+H.id)} ${H.subject}${Y}`}D.push(U(w+L))}if(F.length>R)D.push(U(z.fg("dim",` … and ${F.length-R} more`)));return D}update(){if(!this.uiCtx)return;let G=this.store.list();if(G.length===0){if(this.widgetRegistered)this.uiCtx.setWidget("tasks",void 0),this.widgetRegistered=!1;if(this.widgetInterval)clearInterval(this.widgetInterval),this.widgetInterval=void 0;return}for(let F of this.activeTaskIds){let J=this.store.get(F);if(!J||J.status!=="in_progress")this.activeTaskIds.delete(F),this.metrics.delete(F)}let z=G.some((F)=>this.activeTaskIds.has(F.id)&&F.status==="in_progress");if(z)this.ensureTimer();else if(!z&&this.widgetInterval)clearInterval(this.widgetInterval),this.widgetInterval=void 0;if(this.widgetFrame++,!this.widgetRegistered)this.uiCtx.setWidget("tasks",(F,J)=>{return this.tui=F,{render:()=>this.renderWidget(F,J),invalidate:()=>{}}},{placement:"aboveEditor"}),this.widgetRegistered=!0;else if(this.tui)this.tui.requestRender()}dispose(){if(this.widgetInterval)clearInterval(this.widgetInterval),this.widgetInterval=void 0;if(this.uiCtx)this.uiCtx.setWidget("tasks",void 0);this.widgetRegistered=!1,this.tui=void 0}}export{f as TaskWidget};