dsh-thoughtdag 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/README.md +22 -6
  2. package/cordis.patch.yml +5 -0
  3. package/dist-app/assets/{canonical-DbPqsMYz.js → canonical-DLhL4I9r.js} +2 -2
  4. package/dist-app/assets/{canvas-record-C1A8ExlM.js → canvas-record-CkE2kiAl.js} +1 -1
  5. package/dist-app/assets/{claude-code-session-DQRgWy00.js → claude-code-session-C6SC-rR3.js} +1 -1
  6. package/dist-app/assets/{codex-session--KVj3_vb.js → codex-session-BPnTP7aa.js} +1 -1
  7. package/dist-app/assets/{dsh-session-6OCSm4h5.js → dsh-session-DvePmOkr.js} +2 -2
  8. package/dist-app/assets/{experiment-loop-BfxITu2e.js → experiment-loop-Ch2BbUUd.js} +1 -1
  9. package/dist-app/assets/{index-6m02Pb4Q.js → index-BNzCrLOq.js} +147 -145
  10. package/dist-app/assets/index-ByFqFIuH.js +3 -0
  11. package/dist-app/assets/index-CaLqpoXT.css +1 -0
  12. package/dist-app/assets/{index-BL39OXsY.js → index-CyiaXC1W.js} +1 -1
  13. package/dist-app/assets/{index-BH-U0zVI.js → index-DoQT2Wn3.js} +1 -1
  14. package/dist-app/assets/{live-mirror-BElcCk_a.js → live-mirror-Dg0Om8mP.js} +2 -2
  15. package/dist-app/assets/pi-session-BG_vrEOO.js +14 -0
  16. package/dist-app/assets/{sensitive-scan-L0rFzufE.js → sensitive-scan-B7Rgdm8T.js} +1 -1
  17. package/dist-app/assets/{session-handoff-CGrowwZp.js → session-handoff-Bxj5OdHr.js} +2 -2
  18. package/dist-app/assets/{shared-CURSZnXt.js → shared-B6lVzmly.js} +1 -1
  19. package/dist-app/assets/{turndown-plugin-gfm.cjs-p3SpZgsj.js → turndown-plugin-gfm.cjs-DMq7YfJM.js} +1 -1
  20. package/dist-app/assets/{update-check-D5daO3Cz.js → update-check-Cqj9cGM2.js} +1 -1
  21. package/dist-app/index.html +2 -2
  22. package/lib/index.js +96 -2
  23. package/lib/why.mjs +2771 -0
  24. package/package.json +1 -1
  25. package/dist-app/assets/index-D-BTv0n0.js +0 -3
  26. package/dist-app/assets/index-DEHbIrRr.css +0 -1
package/lib/index.js CHANGED
@@ -48,6 +48,14 @@
48
48
  // enter the attachment store and ride the last user message
49
49
  // POST /claude the same call, whole answer as JSON
50
50
  // POST /fetch-url the SPA's link snapshot, through the harness's bounded fetcher
51
+ //
52
+ // The why layer inside the harness: the CLI's library (bundled as ./why.mjs at
53
+ // build time) answers the same four questions here, over the same
54
+ // ~/.thoughtdag index the CLI and the MCP server use — as native harness tools
55
+ // the agent calls like any other (why_check, why_file, why_find, why_recall),
56
+ // as a /why command a person types in the chat, and as a short system-prompt
57
+ // section that says when to ask. Relative paths resolve against the session's
58
+ // working directory.
51
59
  // These are canvas-native calls (summaries, condensing, a canvas that is
52
60
  // not a mirrored session) — they run on the harness's models but do not
53
61
  // enter any session log; a mirrored session's turns go through /followup.
@@ -67,7 +75,7 @@ import { fileURLToPath } from 'node:url'
67
75
  import { zstdDecompressSync } from 'node:zlib'
68
76
 
69
77
  export const name = 'thoughtdag'
70
- export const inject = ['webServer', 'sessions', 'sessionController', 'agents', 'llm', 'attachments', 'web']
78
+ export const inject = ['webServer', 'sessions', 'sessionController', 'agents', 'llm', 'attachments', 'web', 'tools', 'commands', 'systemPrompt']
71
79
 
72
80
  const __dirname = fileURLToPath(new URL('.', import.meta.url))
73
81
  const APP_DIR = resolve(__dirname, '../dist-app')
@@ -360,6 +368,7 @@ const SOURCE = { kind: 'plugin', plugin: 'dsh-thoughtdag' }
360
368
  const FILE_ROOTS = {
361
369
  'claude-projects': join(os.homedir(), '.claude', 'projects'),
362
370
  'codex-sessions': join(os.homedir(), '.codex', 'sessions'),
371
+ 'pi-sessions': join(os.homedir(), '.pi', 'agent', 'sessions'),
363
372
  }
364
373
  const FILE_READ_MAX = 256 * 1024 * 1024
365
374
 
@@ -631,7 +640,90 @@ async function agentOf(ctx, id) {
631
640
  throw new HttpError(404, 'no such session: ' + (r.error?.code ?? r.error?.message ?? 'unknown'))
632
641
  }
633
642
 
634
- export function apply(ctx, config) {
643
+ // ── the why layer ──────────────────────────────────────────────────────
644
+
645
+ /** The working directory a session runs in, as its header records it. */
646
+ const cwdOfSession = (session) => session?.header?.cwd ?? session?.header?.meta?.cwd ?? null
647
+
648
+ /** A path the person or model typed, made absolute against the session's
649
+ * working directory when it is relative; URLs and arxiv ids pass through. */
650
+ function resolveAgainst(cwd, q) {
651
+ const s = String(q ?? '').trim().replace(/^@/, '')
652
+ if (!s || /^(https?:\/\/|arxiv:)/i.test(s) || s.startsWith('/') || !cwd) return s
653
+ return resolve(cwd, s)
654
+ }
655
+
656
+ /** What the why layer managed to register — served at /why/status so a
657
+ * deployment can see it without reading logs. */
658
+ const whyStatus = { loaded: false, tools: [], command: false, prompt: false, error: null }
659
+
660
+ // the harness's tool names: one family, no collision with its own read/find tools
661
+ const TOOL_NAMES = { why_check: 'why_check', why_file: 'why_file', find: 'why_find', recall_turn: 'why_recall' }
662
+ const PATH_ARGS = new Set(['path'])
663
+
664
+ const WHY_PROMPT = `ThoughtDAG why layer. The tools why_check, why_file, why_find and why_recall read the local index of past agent conversations (Claude Code, Codex, this harness) — evidence from session logs, not opinion.
665
+ - Before editing a file that may have history, call why_check(path); if it has history, why_file(path) lists the turns that changed it: when, what was asked, what changed, and what the answer said about it.
666
+ - why_find(phrase) finds where exact words were asked or answered; why_recall(session, turn) reads one turn in full.
667
+ Cite what you learn briefly; do not restate whole turns.`
668
+
669
+ async function installWhyLayer(ctx, config) {
670
+ const wantTools = config?.whyTools !== false
671
+ const wantPrompt = config?.whyPrompt !== false
672
+ if (!wantTools && !wantPrompt) return
673
+ let why
674
+ try {
675
+ why = await import('./why.mjs')
676
+ why.setQuiet?.(true)
677
+ } catch (error) {
678
+ whyStatus.error = error instanceof Error ? error.message : String(error)
679
+ ctx.logger.warn('[dsh-thoughtdag] why layer not loaded: ' + whyStatus.error)
680
+ console.error('[dsh-thoughtdag] why layer not loaded: ' + whyStatus.error)
681
+ return
682
+ }
683
+ whyStatus.loaded = true
684
+ const ask = async (mcpName, args, cwd) => {
685
+ const a = { ...args }
686
+ for (const k of PATH_ARGS) if (typeof a[k] === 'string') a[k] = resolveAgainst(cwd, a[k])
687
+ return why.mcpCall(mcpName, a)
688
+ }
689
+ if (wantTools) {
690
+ // raw definitions: the MCP tool schemas are already JSON Schema, and a
691
+ // bare import of @deepseek-ai/dsh-tools does not resolve from a linked
692
+ // plugin directory — the registry accepts either form
693
+ for (const t of why.MCP_TOOLS) {
694
+ const name = TOOL_NAMES[t.name] ?? t.name
695
+ try {
696
+ ctx.effect(() => ctx.tools.register({
697
+ name,
698
+ description: t.description,
699
+ parameters: t.inputSchema,
700
+ output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: String(value) }] },
701
+ async execute(args, exec) { return ask(t.name, args ?? {}, cwdOfSession(exec?.agent?.session)) },
702
+ }), 'thoughtdag: tool ' + name)
703
+ whyStatus.tools.push(name)
704
+ } catch (error) {
705
+ whyStatus.error = `${name}: ${error instanceof Error ? error.message : String(error)}`
706
+ console.error('[dsh-thoughtdag] tool not registered ' + whyStatus.error)
707
+ }
708
+ }
709
+ ctx.effect(() => ctx.commands.register({
710
+ name: 'why',
711
+ description: 'ThoughtDAG: which past conversations touched this file, URL or paper',
712
+ input: { hint: '<path | url | arxiv:id>' },
713
+ async handler(inv) {
714
+ const q = String(inv.rawInput ?? '').trim()
715
+ if (!q) return { kind: 'error', text: 'usage: /why <path | url | arxiv:id>' }
716
+ try { return { kind: 'success', text: await ask('why_file', { path: q }, cwdOfSession(inv.agent?.session)) } }
717
+ catch (error) { return { kind: 'error', text: error instanceof Error ? error.message : String(error) } }
718
+ },
719
+ }), 'thoughtdag: /why')
720
+ whyStatus.command = true
721
+ }
722
+ if (wantPrompt && wantTools) { ctx.effect(() => ctx.systemPrompt.section({ name: 'thoughtdag-why', order: 900, text: WHY_PROMPT }), 'thoughtdag: why prompt'); whyStatus.prompt = true }
723
+ ctx.logger.info('[dsh-thoughtdag] why layer: ' + (wantTools ? 'why_check why_file why_find why_recall, /why' : 'no tools') + (wantPrompt && wantTools ? ', prompt section' : ''))
724
+ }
725
+
726
+ export async function apply(ctx, config) {
635
727
  const prefix = typeof config?.mountPrefix === 'string' && config.mountPrefix.startsWith('/') && config.mountPrefix.length > 1
636
728
  ? config.mountPrefix.replace(/\/+$/, '')
637
729
  : '/thoughtdag'
@@ -689,6 +781,7 @@ export function apply(ctx, config) {
689
781
  if (one[2] === '/log') return sendFile(res, 'application/x-ndjson; charset=utf-8', sessionToJsonl(session))
690
782
  return sendJson(res, 200, { session: sessionSummary(session) })
691
783
  }
784
+ if (path === '/why/status' && req.method === 'GET') return sendJson(res, 200, whyStatus)
692
785
  // ── the other agents' session files ──
693
786
  if (path === '/roots' && req.method === 'GET') {
694
787
  const roots = []
@@ -842,4 +935,5 @@ export function apply(ctx, config) {
842
935
  ctx.effect(() => ctx.webServer.register({ kind: 'prefix', path: prefix + '/api', handler: api }), 'thoughtdag: api')
843
936
  ctx.effect(() => ctx.webServer.register({ kind: 'prefix', path: prefix, handler: staticHandler }), 'thoughtdag: static')
844
937
  ctx.logger.info('[dsh-thoughtdag] ThoughtDAG mounted at ' + prefix + '/')
938
+ await installWhyLayer(ctx, config)
845
939
  }