dsh-thoughtdag 0.4.4 → 0.4.5

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 (22) hide show
  1. package/README.md +22 -6
  2. package/cordis.patch.yml +5 -0
  3. package/dist-app/assets/{canonical-DbPqsMYz.js → canonical-DDfsxGbh.js} +2 -2
  4. package/dist-app/assets/{canvas-record-C1A8ExlM.js → canvas-record-vunI4XSG.js} +1 -1
  5. package/dist-app/assets/{claude-code-session-DQRgWy00.js → claude-code-session-w7XeYCbv.js} +1 -1
  6. package/dist-app/assets/{codex-session--KVj3_vb.js → codex-session-BteJdhnJ.js} +1 -1
  7. package/dist-app/assets/{dsh-session-6OCSm4h5.js → dsh-session-DtVkAUjy.js} +1 -1
  8. package/dist-app/assets/{experiment-loop-BfxITu2e.js → experiment-loop-cKs-OaUA.js} +1 -1
  9. package/dist-app/assets/{index-6m02Pb4Q.js → index-BgFLKHxw.js} +10 -10
  10. package/dist-app/assets/{index-BH-U0zVI.js → index-CZgf0TxH.js} +1 -1
  11. package/dist-app/assets/{index-D-BTv0n0.js → index-DbmziCje.js} +3 -3
  12. package/dist-app/assets/{index-BL39OXsY.js → index-ZY9zV6mj.js} +1 -1
  13. package/dist-app/assets/{live-mirror-BElcCk_a.js → live-mirror-Cil__utB.js} +2 -2
  14. package/dist-app/assets/{sensitive-scan-L0rFzufE.js → sensitive-scan-NBqP2Srd.js} +1 -1
  15. package/dist-app/assets/{session-handoff-CGrowwZp.js → session-handoff-C_88Qy1y.js} +2 -2
  16. package/dist-app/assets/{shared-CURSZnXt.js → shared-0Cyt1YSM.js} +1 -1
  17. package/dist-app/assets/{turndown-plugin-gfm.cjs-p3SpZgsj.js → turndown-plugin-gfm.cjs-BkQZDlXD.js} +1 -1
  18. package/dist-app/assets/{update-check-D5daO3Cz.js → update-check-Ca6SiYKC.js} +1 -1
  19. package/dist-app/index.html +1 -1
  20. package/lib/index.js +95 -2
  21. package/lib/why.mjs +2588 -0
  22. package/package.json +1 -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')
@@ -631,7 +639,90 @@ async function agentOf(ctx, id) {
631
639
  throw new HttpError(404, 'no such session: ' + (r.error?.code ?? r.error?.message ?? 'unknown'))
632
640
  }
633
641
 
634
- export function apply(ctx, config) {
642
+ // ── the why layer ──────────────────────────────────────────────────────
643
+
644
+ /** The working directory a session runs in, as its header records it. */
645
+ const cwdOfSession = (session) => session?.header?.cwd ?? session?.header?.meta?.cwd ?? null
646
+
647
+ /** A path the person or model typed, made absolute against the session's
648
+ * working directory when it is relative; URLs and arxiv ids pass through. */
649
+ function resolveAgainst(cwd, q) {
650
+ const s = String(q ?? '').trim().replace(/^@/, '')
651
+ if (!s || /^(https?:\/\/|arxiv:)/i.test(s) || s.startsWith('/') || !cwd) return s
652
+ return resolve(cwd, s)
653
+ }
654
+
655
+ /** What the why layer managed to register — served at /why/status so a
656
+ * deployment can see it without reading logs. */
657
+ const whyStatus = { loaded: false, tools: [], command: false, prompt: false, error: null }
658
+
659
+ // the harness's tool names: one family, no collision with its own read/find tools
660
+ const TOOL_NAMES = { why_check: 'why_check', why_file: 'why_file', find: 'why_find', recall_turn: 'why_recall' }
661
+ const PATH_ARGS = new Set(['path'])
662
+
663
+ 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.
664
+ - 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.
665
+ - why_find(phrase) finds where exact words were asked or answered; why_recall(session, turn) reads one turn in full.
666
+ Cite what you learn briefly; do not restate whole turns.`
667
+
668
+ async function installWhyLayer(ctx, config) {
669
+ const wantTools = config?.whyTools !== false
670
+ const wantPrompt = config?.whyPrompt !== false
671
+ if (!wantTools && !wantPrompt) return
672
+ let why
673
+ try {
674
+ why = await import('./why.mjs')
675
+ why.setQuiet?.(true)
676
+ } catch (error) {
677
+ whyStatus.error = error instanceof Error ? error.message : String(error)
678
+ ctx.logger.warn('[dsh-thoughtdag] why layer not loaded: ' + whyStatus.error)
679
+ console.error('[dsh-thoughtdag] why layer not loaded: ' + whyStatus.error)
680
+ return
681
+ }
682
+ whyStatus.loaded = true
683
+ const ask = async (mcpName, args, cwd) => {
684
+ const a = { ...args }
685
+ for (const k of PATH_ARGS) if (typeof a[k] === 'string') a[k] = resolveAgainst(cwd, a[k])
686
+ return why.mcpCall(mcpName, a)
687
+ }
688
+ if (wantTools) {
689
+ // raw definitions: the MCP tool schemas are already JSON Schema, and a
690
+ // bare import of @deepseek-ai/dsh-tools does not resolve from a linked
691
+ // plugin directory — the registry accepts either form
692
+ for (const t of why.MCP_TOOLS) {
693
+ const name = TOOL_NAMES[t.name] ?? t.name
694
+ try {
695
+ ctx.effect(() => ctx.tools.register({
696
+ name,
697
+ description: t.description,
698
+ parameters: t.inputSchema,
699
+ output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: String(value) }] },
700
+ async execute(args, exec) { return ask(t.name, args ?? {}, cwdOfSession(exec?.agent?.session)) },
701
+ }), 'thoughtdag: tool ' + name)
702
+ whyStatus.tools.push(name)
703
+ } catch (error) {
704
+ whyStatus.error = `${name}: ${error instanceof Error ? error.message : String(error)}`
705
+ console.error('[dsh-thoughtdag] tool not registered ' + whyStatus.error)
706
+ }
707
+ }
708
+ ctx.effect(() => ctx.commands.register({
709
+ name: 'why',
710
+ description: 'ThoughtDAG: which past conversations touched this file, URL or paper',
711
+ input: { hint: '<path | url | arxiv:id>' },
712
+ async handler(inv) {
713
+ const q = String(inv.rawInput ?? '').trim()
714
+ if (!q) return { kind: 'error', text: 'usage: /why <path | url | arxiv:id>' }
715
+ try { return { kind: 'success', text: await ask('why_file', { path: q }, cwdOfSession(inv.agent?.session)) } }
716
+ catch (error) { return { kind: 'error', text: error instanceof Error ? error.message : String(error) } }
717
+ },
718
+ }), 'thoughtdag: /why')
719
+ whyStatus.command = true
720
+ }
721
+ if (wantPrompt && wantTools) { ctx.effect(() => ctx.systemPrompt.section({ name: 'thoughtdag-why', order: 900, text: WHY_PROMPT }), 'thoughtdag: why prompt'); whyStatus.prompt = true }
722
+ ctx.logger.info('[dsh-thoughtdag] why layer: ' + (wantTools ? 'why_check why_file why_find why_recall, /why' : 'no tools') + (wantPrompt && wantTools ? ', prompt section' : ''))
723
+ }
724
+
725
+ export async function apply(ctx, config) {
635
726
  const prefix = typeof config?.mountPrefix === 'string' && config.mountPrefix.startsWith('/') && config.mountPrefix.length > 1
636
727
  ? config.mountPrefix.replace(/\/+$/, '')
637
728
  : '/thoughtdag'
@@ -689,6 +780,7 @@ export function apply(ctx, config) {
689
780
  if (one[2] === '/log') return sendFile(res, 'application/x-ndjson; charset=utf-8', sessionToJsonl(session))
690
781
  return sendJson(res, 200, { session: sessionSummary(session) })
691
782
  }
783
+ if (path === '/why/status' && req.method === 'GET') return sendJson(res, 200, whyStatus)
692
784
  // ── the other agents' session files ──
693
785
  if (path === '/roots' && req.method === 'GET') {
694
786
  const roots = []
@@ -842,4 +934,5 @@ export function apply(ctx, config) {
842
934
  ctx.effect(() => ctx.webServer.register({ kind: 'prefix', path: prefix + '/api', handler: api }), 'thoughtdag: api')
843
935
  ctx.effect(() => ctx.webServer.register({ kind: 'prefix', path: prefix, handler: staticHandler }), 'thoughtdag: static')
844
936
  ctx.logger.info('[dsh-thoughtdag] ThoughtDAG mounted at ' + prefix + '/')
937
+ await installWhyLayer(ctx, config)
845
938
  }