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.
- package/README.md +22 -6
- package/cordis.patch.yml +5 -0
- package/dist-app/assets/{canonical-DbPqsMYz.js → canonical-DLhL4I9r.js} +2 -2
- package/dist-app/assets/{canvas-record-C1A8ExlM.js → canvas-record-CkE2kiAl.js} +1 -1
- package/dist-app/assets/{claude-code-session-DQRgWy00.js → claude-code-session-C6SC-rR3.js} +1 -1
- package/dist-app/assets/{codex-session--KVj3_vb.js → codex-session-BPnTP7aa.js} +1 -1
- package/dist-app/assets/{dsh-session-6OCSm4h5.js → dsh-session-DvePmOkr.js} +2 -2
- package/dist-app/assets/{experiment-loop-BfxITu2e.js → experiment-loop-Ch2BbUUd.js} +1 -1
- package/dist-app/assets/{index-6m02Pb4Q.js → index-BNzCrLOq.js} +147 -145
- package/dist-app/assets/index-ByFqFIuH.js +3 -0
- package/dist-app/assets/index-CaLqpoXT.css +1 -0
- package/dist-app/assets/{index-BL39OXsY.js → index-CyiaXC1W.js} +1 -1
- package/dist-app/assets/{index-BH-U0zVI.js → index-DoQT2Wn3.js} +1 -1
- package/dist-app/assets/{live-mirror-BElcCk_a.js → live-mirror-Dg0Om8mP.js} +2 -2
- package/dist-app/assets/pi-session-BG_vrEOO.js +14 -0
- package/dist-app/assets/{sensitive-scan-L0rFzufE.js → sensitive-scan-B7Rgdm8T.js} +1 -1
- package/dist-app/assets/{session-handoff-CGrowwZp.js → session-handoff-Bxj5OdHr.js} +2 -2
- package/dist-app/assets/{shared-CURSZnXt.js → shared-B6lVzmly.js} +1 -1
- package/dist-app/assets/{turndown-plugin-gfm.cjs-p3SpZgsj.js → turndown-plugin-gfm.cjs-DMq7YfJM.js} +1 -1
- package/dist-app/assets/{update-check-D5daO3Cz.js → update-check-Cqj9cGM2.js} +1 -1
- package/dist-app/index.html +2 -2
- package/lib/index.js +96 -2
- package/lib/why.mjs +2771 -0
- package/package.json +1 -1
- package/dist-app/assets/index-D-BTv0n0.js +0 -3
- 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
|
-
|
|
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
|
}
|