@roarpeng/graphflow 1.9.14 → 1.9.16
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/CHANGELOG.md +45 -0
- package/README.md +21 -8
- package/README.zh.md +14 -6
- package/cordis.patch.yml +16 -4
- package/dist/graph/context-slicer-types.d.ts +7 -1
- package/dist/graph/context-slicer-types.d.ts.map +1 -1
- package/dist/graph/context-slicer-types.js.map +1 -1
- package/dist/graph/context-slicer.d.ts +6 -1
- package/dist/graph/context-slicer.d.ts.map +1 -1
- package/dist/graph/context-slicer.js +120 -39
- package/dist/graph/context-slicer.js.map +1 -1
- package/dist/graph/token-savings.d.ts +13 -0
- package/dist/graph/token-savings.d.ts.map +1 -1
- package/dist/graph/token-savings.js +36 -6
- package/dist/graph/token-savings.js.map +1 -1
- package/dist/hooks/auto-capture.d.ts +3 -2
- package/dist/hooks/auto-capture.d.ts.map +1 -1
- package/dist/hooks/auto-capture.js +3 -2
- package/dist/hooks/auto-capture.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/integrations/claude-code-hooks.d.ts +6 -3
- package/dist/integrations/claude-code-hooks.d.ts.map +1 -1
- package/dist/integrations/claude-code-hooks.js +20 -8
- package/dist/integrations/claude-code-hooks.js.map +1 -1
- package/dist/integrations/dsh-harness-installer.d.ts +5 -1
- package/dist/integrations/dsh-harness-installer.d.ts.map +1 -1
- package/dist/integrations/dsh-harness-installer.js +24 -6
- package/dist/integrations/dsh-harness-installer.js.map +1 -1
- package/dist/integrations/skill-installer.d.ts.map +1 -1
- package/dist/integrations/skill-installer.js +9 -2
- package/dist/integrations/skill-installer.js.map +1 -1
- package/dist/learning/canary-gate.d.ts +5 -2
- package/dist/learning/canary-gate.d.ts.map +1 -1
- package/dist/learning/canary-gate.js +11 -3
- package/dist/learning/canary-gate.js.map +1 -1
- package/dist/learning/dialogue-thread.d.ts +15 -0
- package/dist/learning/dialogue-thread.d.ts.map +1 -1
- package/dist/learning/dialogue-thread.js +58 -1
- package/dist/learning/dialogue-thread.js.map +1 -1
- package/dist/learning/episodic-memory.d.ts +16 -0
- package/dist/learning/episodic-memory.d.ts.map +1 -1
- package/dist/learning/episodic-memory.js +61 -1
- package/dist/learning/episodic-memory.js.map +1 -1
- package/dist/learning/skill-admission.d.ts +38 -0
- package/dist/learning/skill-admission.d.ts.map +1 -0
- package/dist/learning/skill-admission.js +195 -0
- package/dist/learning/skill-admission.js.map +1 -0
- package/dist/learning/skill-consolidate.d.ts +4 -1
- package/dist/learning/skill-consolidate.d.ts.map +1 -1
- package/dist/learning/skill-consolidate.js +56 -6
- package/dist/learning/skill-consolidate.js.map +1 -1
- package/dist/learning/skill-flywheel.d.ts +8 -2
- package/dist/learning/skill-flywheel.d.ts.map +1 -1
- package/dist/learning/skill-flywheel.js +65 -38
- package/dist/learning/skill-flywheel.js.map +1 -1
- package/dist/learning/skill-opt-lite.d.ts +17 -0
- package/dist/learning/skill-opt-lite.d.ts.map +1 -1
- package/dist/learning/skill-opt-lite.js +108 -0
- package/dist/learning/skill-opt-lite.js.map +1 -1
- package/dist/learning/skill-store.d.ts.map +1 -1
- package/dist/learning/skill-store.js +44 -4
- package/dist/learning/skill-store.js.map +1 -1
- package/dist/learning/skill-types.d.ts +15 -0
- package/dist/learning/skill-types.d.ts.map +1 -1
- package/dist/learning/skill-types.js +11 -0
- package/dist/learning/skill-types.js.map +1 -1
- package/dist/learning/turn-distillation.d.ts +16 -0
- package/dist/learning/turn-distillation.d.ts.map +1 -0
- package/dist/learning/turn-distillation.js +161 -0
- package/dist/learning/turn-distillation.js.map +1 -0
- package/dist/learning/workflow-skill.d.ts +25 -0
- package/dist/learning/workflow-skill.d.ts.map +1 -0
- package/dist/learning/workflow-skill.js +91 -0
- package/dist/learning/workflow-skill.js.map +1 -0
- package/dist/surfaces/cli/index.js +40 -32
- package/dist/surfaces/cli/index.js.map +1 -1
- package/dist/surfaces/cli/init.d.ts.map +1 -1
- package/dist/surfaces/cli/init.js +9 -1
- package/dist/surfaces/cli/init.js.map +1 -1
- package/dist/surfaces/cli/output.d.ts.map +1 -1
- package/dist/surfaces/cli/output.js +2 -1
- package/dist/surfaces/cli/output.js.map +1 -1
- package/dist/surfaces/cli/runtime/dialogue.d.ts +36 -0
- package/dist/surfaces/cli/runtime/dialogue.d.ts.map +1 -1
- package/dist/surfaces/cli/runtime/dialogue.js +78 -0
- package/dist/surfaces/cli/runtime/dialogue.js.map +1 -1
- package/dist/surfaces/cli/runtime/graph.d.ts +14 -21
- package/dist/surfaces/cli/runtime/graph.d.ts.map +1 -1
- package/dist/surfaces/cli/runtime/graph.js +59 -21
- package/dist/surfaces/cli/runtime/graph.js.map +1 -1
- package/dist/surfaces/cli/runtime/memory.d.ts +7 -6
- package/dist/surfaces/cli/runtime/memory.d.ts.map +1 -1
- package/dist/surfaces/cli/runtime/memory.js +12 -21
- package/dist/surfaces/cli/runtime/memory.js.map +1 -1
- package/dist/surfaces/cli/runtime/routing.d.ts +4 -1
- package/dist/surfaces/cli/runtime/routing.d.ts.map +1 -1
- package/dist/surfaces/cli/runtime/routing.js +10 -4
- package/dist/surfaces/cli/runtime/routing.js.map +1 -1
- package/dist/surfaces/cli/runtime/types.d.ts +15 -0
- package/dist/surfaces/cli/runtime/types.d.ts.map +1 -1
- package/dist/surfaces/cli/runtime.d.ts +1 -1
- package/dist/surfaces/cli/runtime.d.ts.map +1 -1
- package/dist/surfaces/cli/runtime.js +3 -1
- package/dist/surfaces/cli/runtime.js.map +1 -1
- package/dist/surfaces/mcp/tool-definitions.js +1 -1
- package/dist/surfaces/mcp/tool-definitions.js.map +1 -1
- package/dist/surfaces/trae-skill/graphflow/SKILL.md +5 -4
- package/dsh/plugin.mjs +454 -0
- package/package.json +12 -1
- package/plugin.json +2 -2
- package/skills/graphflow/SKILL.md +5 -4
- package/src/surfaces/trae-skill/graphflow/SKILL.md +5 -4
- package/web/README.md +36 -0
- package/web/plugin.mjs +448 -0
package/dsh/plugin.mjs
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphFlow DeepSeek Harness (dsh) glue plugin.
|
|
3
|
+
*
|
|
4
|
+
* DSH plugins are ESM (`type: module`); GraphFlow's package root is CJS.
|
|
5
|
+
* This file is the `exports["./dsh"]` entry. Cordis loads it via
|
|
6
|
+
* `name: '@roarpeng/graphflow/dsh'` in cordis.patch.yml.
|
|
7
|
+
*
|
|
8
|
+
* Duck-types `ctx` — no hard dependency on `@deepseek-ai/cordis`.
|
|
9
|
+
* Missing `ctx.skills` / events → no-op. Never throw into the harness loop.
|
|
10
|
+
*/
|
|
11
|
+
import { spawn } from "node:child_process";
|
|
12
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
export const name = "graphflow-dsh";
|
|
17
|
+
|
|
18
|
+
const PLUGIN_ID = "graphflow-dsh";
|
|
19
|
+
const AUTO_CAPTURE_ENV = "GRAPHFLOW_AUTO_CAPTURE";
|
|
20
|
+
const JOURNAL_RELATIVE = join(".graphflow", "session-journal.jsonl");
|
|
21
|
+
const DEFAULT_SKILL_DESCRIPTION =
|
|
22
|
+
"图谱上下文压缩、任务规划与知识图谱编排(10 个 MCP 工具)。任何读代码、改代码、排错、中文问题之前必须先调 graphflow_context。DeepSeek Harness 下工具名为 mcp__graphflow__graphflow_*。";
|
|
23
|
+
|
|
24
|
+
const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
25
|
+
|
|
26
|
+
/** @typedef {{ spawn?: typeof spawn, env?: NodeJS.ProcessEnv, cwd?: string, readSkill?: () => { name: string, description: string, content: string, source: string, path?: string } }} GraphFlowDshPluginConfig */
|
|
27
|
+
|
|
28
|
+
function envOf(config) {
|
|
29
|
+
return config?.env ?? process.env;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isAutoCaptureEnabled(env = process.env) {
|
|
33
|
+
const raw = env[AUTO_CAPTURE_ENV]?.trim().toLowerCase();
|
|
34
|
+
return !(raw === "0" || raw === "false" || raw === "off" || raw === "no" || raw === "disabled");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Explicit success only — same contract as Claude Code SessionEnd `$2`.
|
|
39
|
+
* Missing GRAPHFLOW_HOOK_SUCCESS leaves the episode pending.
|
|
40
|
+
* @returns {boolean|undefined}
|
|
41
|
+
*/
|
|
42
|
+
export function resolveExplicitSuccess(env = process.env) {
|
|
43
|
+
const raw = env.GRAPHFLOW_HOOK_SUCCESS?.trim().toLowerCase();
|
|
44
|
+
if (raw === "true" || raw === "1" || raw === "yes") return true;
|
|
45
|
+
if (raw === "false" || raw === "0" || raw === "no") return false;
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function resolveSessionJournalPath(workspaceRoot) {
|
|
50
|
+
const override = process.env.GRAPHFLOW_HOOK_JOURNAL?.trim();
|
|
51
|
+
if (override) return override;
|
|
52
|
+
return join(workspaceRoot ?? process.cwd(), JOURNAL_RELATIVE);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function latestPendingEpisodeId(journalPath) {
|
|
56
|
+
if (!journalPath || !existsSync(journalPath)) return undefined;
|
|
57
|
+
let text;
|
|
58
|
+
try {
|
|
59
|
+
text = readFileSync(journalPath, "utf8");
|
|
60
|
+
} catch {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const lines = text.split(/\r?\n/);
|
|
64
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
65
|
+
const trimmed = lines[i]?.trim();
|
|
66
|
+
if (!trimmed) continue;
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(trimmed);
|
|
69
|
+
if (typeof parsed?.episodeId === "string" && parsed.episodeId) {
|
|
70
|
+
return parsed.episodeId;
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
// skip damaged lines
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function buildContextHint(cwd = process.cwd()) {
|
|
80
|
+
return `GraphFlow: before large code reads, call mcp__graphflow__graphflow_context with rootDir=${cwd}. Do not dump SKILL.md.`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Extract the plain text of a user message (its text ContentBlocks joined).
|
|
85
|
+
* Returns "" when the message carries no text. Never throws.
|
|
86
|
+
* @param {object|undefined} message - a UserMessage (agent/inbox/inserted payload).
|
|
87
|
+
* @returns {string}
|
|
88
|
+
*/
|
|
89
|
+
export function extractMessageText(message) {
|
|
90
|
+
if (!message || typeof message !== "object") return "";
|
|
91
|
+
const content = message.content;
|
|
92
|
+
if (!Array.isArray(content)) return "";
|
|
93
|
+
const parts = [];
|
|
94
|
+
for (const block of content) {
|
|
95
|
+
if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
|
|
96
|
+
parts.push(block.text);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return parts.join("\n").trim();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The local GraphFlow CLI entry (this package's own dist build). Falls back to
|
|
104
|
+
* `graphflow` from PATH when dist is absent (source checkout without build).
|
|
105
|
+
* @returns {string}
|
|
106
|
+
*/
|
|
107
|
+
export function resolveCliCommand(packageRoot = PACKAGE_ROOT) {
|
|
108
|
+
const distCli = join(packageRoot, "dist", "surfaces", "cli", "index.js");
|
|
109
|
+
return existsSync(distCli) ? distCli : "graphflow";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Whether a message originates from the human user rather than the harness.
|
|
114
|
+
* Only `source.kind === "user"` qualifies; harness/system injections
|
|
115
|
+
* (`plugin`/`tool`/`model`) must not become dialogue turns (job finished
|
|
116
|
+
* notices, subagent reports, tool results, …). Unknown/missing source falls
|
|
117
|
+
* back to the role check so nothing legitimate is dropped.
|
|
118
|
+
* @param {object|undefined} message
|
|
119
|
+
* @returns {boolean}
|
|
120
|
+
*/
|
|
121
|
+
export function isUserOriginatedMessage(message) {
|
|
122
|
+
if (!message || typeof message !== "object") return false;
|
|
123
|
+
const kind =
|
|
124
|
+
message.source && typeof message.source === "object" ? message.source.kind : undefined;
|
|
125
|
+
if (kind === "user") return true;
|
|
126
|
+
if (kind === "plugin" || kind === "tool" || kind === "model") return false;
|
|
127
|
+
return message.role !== "system";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Best-effort auto-record of one user turn as a dialogue node.
|
|
132
|
+
* Spawns `graphflow dialogue record --query "<text>" [--session <sessionId>]`
|
|
133
|
+
* in the agent's workspace (cwd = session header cwd) so every web/agent
|
|
134
|
+
* question becomes a dialogue-turn graph node without relying on the agent
|
|
135
|
+
* calling graphflow_context itself. Only human-originated messages are
|
|
136
|
+
* recorded (see isUserOriginatedMessage) — harness/system injections must not
|
|
137
|
+
* pollute the dialogue graph. `sessionId` scopes the turn to one dsh session
|
|
138
|
+
* (omitted → default "main" dialogue session) so question recording and reply
|
|
139
|
+
* filling land in the same dialogue session. Never throws; the spawned child
|
|
140
|
+
* is detached.
|
|
141
|
+
* @param {object|undefined} message - a UserMessage (agent/inbox/inserted payload).
|
|
142
|
+
* @param {string} [cwd]
|
|
143
|
+
* @param {GraphFlowDshPluginConfig} [config]
|
|
144
|
+
* @param {string} [sessionId] - dialogue session name/id; omitted → "main".
|
|
145
|
+
* @returns {{attempted: boolean, reason?: string, workspace?: string}}
|
|
146
|
+
*/
|
|
147
|
+
export function recordDialogueFromInbox(message, cwd, config = {}, sessionId) {
|
|
148
|
+
try {
|
|
149
|
+
if (!isUserOriginatedMessage(message)) {
|
|
150
|
+
return { attempted: false, reason: "not-user-message" };
|
|
151
|
+
}
|
|
152
|
+
const env = envOf(config);
|
|
153
|
+
if (!isAutoCaptureEnabled(env)) {
|
|
154
|
+
return { attempted: false, reason: "auto-capture-off" };
|
|
155
|
+
}
|
|
156
|
+
const text = extractMessageText(message);
|
|
157
|
+
if (text.length < 4) {
|
|
158
|
+
return { attempted: false, reason: "query-too-short" };
|
|
159
|
+
}
|
|
160
|
+
const workspace = typeof cwd === "string" && cwd.trim() ? cwd : process.cwd();
|
|
161
|
+
const spawnFn = typeof config.spawn === "function" ? config.spawn : spawn;
|
|
162
|
+
const cli = resolveCliCommand(config.packageRoot);
|
|
163
|
+
const args = [cli, "dialogue", "record", "--query", text];
|
|
164
|
+
if (typeof sessionId === "string" && sessionId.trim()) {
|
|
165
|
+
args.push("--session", sessionId);
|
|
166
|
+
}
|
|
167
|
+
const bin = cli === "graphflow" ? (env.GRAPHFLOW_HOOK_BIN?.trim() || "npx") : process.execPath;
|
|
168
|
+
const fullArgs = cli === "graphflow" ? ["-y", "--package=@roarpeng/graphflow", ...args] : args;
|
|
169
|
+
spawnFn(bin, fullArgs, {
|
|
170
|
+
cwd: workspace,
|
|
171
|
+
env,
|
|
172
|
+
stdio: "ignore",
|
|
173
|
+
detached: true,
|
|
174
|
+
windowsHide: true,
|
|
175
|
+
})?.unref?.();
|
|
176
|
+
return { attempted: true, workspace };
|
|
177
|
+
} catch {
|
|
178
|
+
return { attempted: false, reason: "error" };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Best-effort fill of one assistant reply into the latest pending dialogue
|
|
184
|
+
* turn for `sessionId`. Spawns `graphflow dialogue record --reply "<text>"
|
|
185
|
+
* [--session <sessionId>]` (reply-only mode) in the workspace; the runtime
|
|
186
|
+
* fills the latest pending turn's assistantReply and safely skips when none is
|
|
187
|
+
* pending. Half-written (interrupted) replies are not filled. Never throws.
|
|
188
|
+
* @param {object|undefined} event - an `assistant/message` session event.
|
|
189
|
+
* @param {string} [sessionId] - dialogue session name/id.
|
|
190
|
+
* @param {string} [cwd]
|
|
191
|
+
* @param {GraphFlowDshPluginConfig} [config]
|
|
192
|
+
* @returns {{attempted: boolean, reason?: string, workspace?: string}}
|
|
193
|
+
*/
|
|
194
|
+
export function recordReplyFromTurn(event, sessionId, cwd, config = {}) {
|
|
195
|
+
try {
|
|
196
|
+
const text = extractMessageText(event?.message);
|
|
197
|
+
if (!text) {
|
|
198
|
+
return { attempted: false, reason: "no-text" };
|
|
199
|
+
}
|
|
200
|
+
if (event?.interrupted) {
|
|
201
|
+
return { attempted: false, reason: "interrupted" };
|
|
202
|
+
}
|
|
203
|
+
const env = envOf(config);
|
|
204
|
+
if (!isAutoCaptureEnabled(env)) {
|
|
205
|
+
return { attempted: false, reason: "auto-capture-off" };
|
|
206
|
+
}
|
|
207
|
+
const workspace = typeof cwd === "string" && cwd.trim() ? cwd : process.cwd();
|
|
208
|
+
const spawnFn = typeof config.spawn === "function" ? config.spawn : spawn;
|
|
209
|
+
const cli = resolveCliCommand(config.packageRoot);
|
|
210
|
+
const args = [cli, "dialogue", "record", "--reply", text];
|
|
211
|
+
if (typeof sessionId === "string" && sessionId.trim()) {
|
|
212
|
+
args.push("--session", sessionId);
|
|
213
|
+
}
|
|
214
|
+
const bin = cli === "graphflow" ? (env.GRAPHFLOW_HOOK_BIN?.trim() || "npx") : process.execPath;
|
|
215
|
+
const fullArgs = cli === "graphflow" ? ["-y", "--package=@roarpeng/graphflow", ...args] : args;
|
|
216
|
+
spawnFn(bin, fullArgs, {
|
|
217
|
+
cwd: workspace,
|
|
218
|
+
env,
|
|
219
|
+
stdio: "ignore",
|
|
220
|
+
detached: true,
|
|
221
|
+
windowsHide: true,
|
|
222
|
+
})?.unref?.();
|
|
223
|
+
return { attempted: true, workspace };
|
|
224
|
+
} catch {
|
|
225
|
+
return { attempted: false, reason: "error" };
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function loadGraphFlowSkillRegistration(packageRoot = PACKAGE_ROOT) {
|
|
230
|
+
const skillPath = join(packageRoot, "skills", "graphflow", "SKILL.md");
|
|
231
|
+
let content = "";
|
|
232
|
+
let description = DEFAULT_SKILL_DESCRIPTION;
|
|
233
|
+
if (existsSync(skillPath)) {
|
|
234
|
+
try {
|
|
235
|
+
content = readFileSync(skillPath, "utf8");
|
|
236
|
+
const fm = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n?/);
|
|
237
|
+
const descLine = fm?.[1]?.match(/^description:\s*["']?(.+?)["']?\s*$/m);
|
|
238
|
+
if (descLine?.[1]) {
|
|
239
|
+
description = descLine[1].replace(/\\"/g, '"').trim();
|
|
240
|
+
}
|
|
241
|
+
} catch {
|
|
242
|
+
content = "";
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
name: "graphflow",
|
|
247
|
+
description,
|
|
248
|
+
content,
|
|
249
|
+
source: "runtime",
|
|
250
|
+
path: skillPath,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function payloadAgent(payload) {
|
|
255
|
+
if (!payload || typeof payload !== "object") return undefined;
|
|
256
|
+
if (payload.agent && typeof payload.agent === "object") return payload.agent;
|
|
257
|
+
return payload;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function resolveWorkspaceCwd(payload, fallbackCwd = process.cwd()) {
|
|
261
|
+
const agent = payloadAgent(payload);
|
|
262
|
+
const session = payload?.session ?? agent?.session;
|
|
263
|
+
const candidates = [
|
|
264
|
+
payload?.cwd,
|
|
265
|
+
agent?.cwd,
|
|
266
|
+
session?.cwd,
|
|
267
|
+
typeof fallbackCwd === "string" ? fallbackCwd : undefined,
|
|
268
|
+
];
|
|
269
|
+
for (const value of candidates) {
|
|
270
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
271
|
+
}
|
|
272
|
+
return process.cwd();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function buildOutcomeArgs(episodeId, success) {
|
|
276
|
+
return ["-y", "--package=@roarpeng/graphflow", "graphflow", "outcome", "report", episodeId, success ? "true" : "false"];
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Best-effort close of the latest pending episode for `cwd`.
|
|
281
|
+
* Reuses `graphflow outcome report` (same flywheel as Claude Code SessionEnd).
|
|
282
|
+
* Does not default pending episodes to success: GRAPHFLOW_HOOK_SUCCESS must be
|
|
283
|
+
* an explicit true/false. Never throws.
|
|
284
|
+
*/
|
|
285
|
+
export function closePendingEpisodeForCwd(cwd, config = {}) {
|
|
286
|
+
try {
|
|
287
|
+
const env = envOf(config);
|
|
288
|
+
if (!isAutoCaptureEnabled(env)) {
|
|
289
|
+
return { attempted: false, reason: "auto-capture-off" };
|
|
290
|
+
}
|
|
291
|
+
const workspace = typeof cwd === "string" && cwd.trim() ? cwd : process.cwd();
|
|
292
|
+
const journalPath = resolveSessionJournalPath(workspace);
|
|
293
|
+
const episodeId = latestPendingEpisodeId(journalPath);
|
|
294
|
+
if (!episodeId) {
|
|
295
|
+
return { attempted: false, reason: "no-pending-episode" };
|
|
296
|
+
}
|
|
297
|
+
const success = resolveExplicitSuccess(env);
|
|
298
|
+
if (success === undefined) {
|
|
299
|
+
return { attempted: false, reason: "no-explicit-success", episodeId };
|
|
300
|
+
}
|
|
301
|
+
const spawnFn = typeof config.spawn === "function" ? config.spawn : spawn;
|
|
302
|
+
const bin = env.GRAPHFLOW_HOOK_BIN?.trim() || "npx";
|
|
303
|
+
spawnFn(bin, buildOutcomeArgs(episodeId, success), {
|
|
304
|
+
cwd: workspace,
|
|
305
|
+
env,
|
|
306
|
+
stdio: "ignore",
|
|
307
|
+
detached: true,
|
|
308
|
+
windowsHide: true,
|
|
309
|
+
})?.unref?.();
|
|
310
|
+
return { attempted: true, episodeId };
|
|
311
|
+
} catch {
|
|
312
|
+
return { attempted: false, reason: "error" };
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function safeInjectHint(agent, cwd) {
|
|
317
|
+
if (!agent || typeof agent.inject !== "function") return;
|
|
318
|
+
const text = buildContextHint(cwd);
|
|
319
|
+
agent.inject({
|
|
320
|
+
id: crypto.randomUUID(),
|
|
321
|
+
role: "user",
|
|
322
|
+
content: [{ type: "text", text }],
|
|
323
|
+
source: { kind: "plugin", plugin: PLUGIN_ID, form: "instructions" },
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function registerSkill(ctx, config) {
|
|
328
|
+
const skills = ctx?.skills;
|
|
329
|
+
if (!skills || typeof skills.register !== "function") return;
|
|
330
|
+
const skill =
|
|
331
|
+
typeof config.readSkill === "function" ? config.readSkill() : loadGraphFlowSkillRegistration();
|
|
332
|
+
skills.register(skill);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function listen(ctx, event, handler) {
|
|
336
|
+
if (!ctx || typeof ctx.on !== "function") return;
|
|
337
|
+
ctx.on(event, handler);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Cordis plugin entry. DSH mounts this row as `id: graphflow-dsh`.
|
|
342
|
+
* @param {object} ctx duck-typed Cordis context
|
|
343
|
+
* @param {GraphFlowDshPluginConfig} [config]
|
|
344
|
+
*/
|
|
345
|
+
export function apply(ctx, config = {}) {
|
|
346
|
+
try {
|
|
347
|
+
registerSkill(ctx, config);
|
|
348
|
+
} catch {
|
|
349
|
+
// missing or duplicate skill registry — ignore
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const hinted = new WeakSet();
|
|
353
|
+
const closed = new Set();
|
|
354
|
+
|
|
355
|
+
const closeFromPayload = (payload) => {
|
|
356
|
+
try {
|
|
357
|
+
const cwd = resolveWorkspaceCwd(payload, config.cwd);
|
|
358
|
+
const journalPath = resolveSessionJournalPath(cwd);
|
|
359
|
+
const episodeId = latestPendingEpisodeId(journalPath);
|
|
360
|
+
if (episodeId && closed.has(episodeId)) {
|
|
361
|
+
return { attempted: false, reason: "already-closed" };
|
|
362
|
+
}
|
|
363
|
+
const result = closePendingEpisodeForCwd(cwd, config);
|
|
364
|
+
if (result.episodeId) closed.add(result.episodeId);
|
|
365
|
+
return result;
|
|
366
|
+
} catch {
|
|
367
|
+
return { attempted: false, reason: "error" };
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
// Claude Code SessionEnd analog. Do not close on session/flush — that is a
|
|
373
|
+
// live durability checkpoint, not session end; reporting true there would
|
|
374
|
+
// mark the flywheel successful while the agent is still working.
|
|
375
|
+
listen(ctx, "agent/disposed", (payload) => {
|
|
376
|
+
closeFromPayload(payload);
|
|
377
|
+
});
|
|
378
|
+
} catch {
|
|
379
|
+
// event bus missing
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const recordedInboxMessages = new WeakSet();
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
// Auto record every user question as a dialogue-turn node (the dsh analog
|
|
386
|
+
// of Claude Code's PostToolUse capture): each web/agent question becomes a
|
|
387
|
+
// graph node with session/workspace lineage, without relying on the agent
|
|
388
|
+
// calling graphflow_context itself. Turns are scoped to the dsh session id
|
|
389
|
+
// so question recording and reply filling stay in the same dialogue
|
|
390
|
+
// session (never crossing same-workspace sessions).
|
|
391
|
+
listen(ctx, "agent/inbox/inserted", (payload) => {
|
|
392
|
+
try {
|
|
393
|
+
const message = payload?.message;
|
|
394
|
+
if (!message || typeof message !== "object") return;
|
|
395
|
+
if (recordedInboxMessages.has(message)) return;
|
|
396
|
+
recordedInboxMessages.add(message);
|
|
397
|
+
const agent = payloadAgent(payload);
|
|
398
|
+
const cwd =
|
|
399
|
+
agent?.session?.header?.cwd ??
|
|
400
|
+
resolveWorkspaceCwd(payload, config.cwd);
|
|
401
|
+
recordDialogueFromInbox(message, cwd, config, agent?.session?.id);
|
|
402
|
+
} catch {
|
|
403
|
+
// recording is optional
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
} catch {
|
|
407
|
+
// event bus missing
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const recordedReplies = new WeakSet();
|
|
411
|
+
|
|
412
|
+
try {
|
|
413
|
+
// Auto fill every assistant reply into the latest pending dialogue turn of
|
|
414
|
+
// the same dsh session: closes the question→answer loop so dialogue-turn
|
|
415
|
+
// nodes record a complete exchange without the agent calling
|
|
416
|
+
// graphflow_context({ assistantReply }) itself. Deduped per message object;
|
|
417
|
+
// interrupted (half-written) replies are skipped.
|
|
418
|
+
listen(ctx, "session/event", (session, event) => {
|
|
419
|
+
try {
|
|
420
|
+
if (event?.type !== "assistant/message") return;
|
|
421
|
+
if (event.interrupted) return;
|
|
422
|
+
const message = event.message;
|
|
423
|
+
if (!message || typeof message !== "object") return;
|
|
424
|
+
if (recordedReplies.has(message)) return;
|
|
425
|
+
recordedReplies.add(message);
|
|
426
|
+
recordReplyFromTurn(event, session?.id, session?.header?.cwd, config);
|
|
427
|
+
} catch {
|
|
428
|
+
// reply fill is optional
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
} catch {
|
|
432
|
+
// event bus missing
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
try {
|
|
436
|
+
listen(ctx, "agent/pre-step", (payload, next) => {
|
|
437
|
+
try {
|
|
438
|
+
const agent = payloadAgent(payload);
|
|
439
|
+
if (agent && !hinted.has(agent)) {
|
|
440
|
+
hinted.add(agent);
|
|
441
|
+
safeInjectHint(agent, resolveWorkspaceCwd(payload, config.cwd));
|
|
442
|
+
}
|
|
443
|
+
} catch {
|
|
444
|
+
// hint is optional
|
|
445
|
+
}
|
|
446
|
+
if (typeof next === "function") {
|
|
447
|
+
return next();
|
|
448
|
+
}
|
|
449
|
+
return undefined;
|
|
450
|
+
});
|
|
451
|
+
} catch {
|
|
452
|
+
// event bus missing
|
|
453
|
+
}
|
|
454
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roarpeng/graphflow",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.16",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
"plugin.json",
|
|
12
12
|
"mcp.json",
|
|
13
13
|
"cordis.patch.yml",
|
|
14
|
+
"dsh",
|
|
15
|
+
"web",
|
|
14
16
|
"skills",
|
|
15
17
|
"src/surfaces/trae-skill",
|
|
16
18
|
"src/surfaces/trae-rules",
|
|
@@ -32,6 +34,15 @@
|
|
|
32
34
|
"description": "A Context-Aware Multi-Agent Orchestration Engine",
|
|
33
35
|
"main": "dist/index.js",
|
|
34
36
|
"types": "dist/index.d.ts",
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"default": "./dist/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./dsh": "./dsh/plugin.mjs",
|
|
43
|
+
"./web": "./web/plugin.mjs",
|
|
44
|
+
"./package.json": "./package.json"
|
|
45
|
+
},
|
|
35
46
|
"bin": {
|
|
36
47
|
"graphflow": "dist/surfaces/cli/index.js",
|
|
37
48
|
"graphflow-mcp": "dist/surfaces/mcp/server.js"
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "graphflow",
|
|
4
|
-
"version": "1.9.
|
|
4
|
+
"version": "1.9.16",
|
|
5
5
|
"description": "图谱上下文压缩与任务规划插件(10 个 MCP 工具)。能力:压缩上下文、DAG 规划、增量建图、技能飞轮、诊断与产物导入导出。DeepSeek Harness:dsh plugin --profile web add @roarpeng/graphflow;工具名为 mcp__graphflow__graphflow_*。先调 graphflow_context(传 rootDir),复杂任务再 plan,改完代码后 index。",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Roarpeng",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"install": "dsh plugin --profile web add @roarpeng/graphflow",
|
|
27
27
|
"installHome": "npx @roarpeng/graphflow install",
|
|
28
28
|
"toolsPrefix": "mcp__graphflow__",
|
|
29
|
-
"usage": "先 mcp__graphflow__graphflow_context(rootDir=仓库绝对路径),复杂任务 graphflow_plan,改完 graphflow_index;graphflow_run 之后必须 graphflow_report_outcome。",
|
|
29
|
+
"usage": "先 mcp__graphflow__graphflow_context(rootDir=仓库绝对路径),复杂任务 graphflow_plan,改完 graphflow_index;graphflow_run 之后必须 graphflow_report_outcome。启动:npx @deepseek-ai/dsh web。",
|
|
30
30
|
"capabilities": [
|
|
31
31
|
"graphflow_context",
|
|
32
32
|
"graphflow_plan",
|
|
@@ -114,17 +114,18 @@ GraphFlow 是 DeepSeek Harness 的 `dsh-plugin`。装入后模型看到的工具
|
|
|
114
114
|
|
|
115
115
|
```sh
|
|
116
116
|
dsh plugin --profile web add @roarpeng/graphflow
|
|
117
|
-
|
|
117
|
+
npx @deepseek-ai/dsh web
|
|
118
|
+
# 或已有 ~/.dsh 时写 home overlay + skill 文件:
|
|
118
119
|
npx @roarpeng/graphflow install
|
|
119
120
|
```
|
|
120
121
|
|
|
121
122
|
| 路径 | 作用 |
|
|
122
123
|
|------|------|
|
|
123
|
-
| 包内 `cordis.patch.yml` | `dsh plugin add` 插入的 bundle
|
|
124
|
+
| 包内 `cordis.patch.yml` | `dsh plugin add` 插入的 bundle 层:MCP(`cwd: process.cwd()`)+ `@roarpeng/graphflow/dsh` glue |
|
|
124
125
|
| `$DSH_HOME/cordis.patch.yml`(默认 `~/.dsh`) | `graphflow install` 写的 home overlay |
|
|
125
|
-
| `$DSH_HOME/skills/graphflow/SKILL.md` | 本 Skill |
|
|
126
|
+
| `$DSH_HOME/skills/graphflow/SKILL.md` | 本 Skill(install 复制);glue 也会在运行时 `ctx.skills.register` |
|
|
126
127
|
|
|
127
|
-
**用法:**
|
|
128
|
+
**用法:** 第一轮先 `mcp__graphflow__graphflow_context`(`rootDir` = 仓库绝对路径)。不要在 patch 里写死 `GRAPHFLOW_WORKSPACE_ROOT`。走了 `graphflow_run` 后必须 `graphflow_report_outcome`。会话结束时 glue 会 best-effort 关闭 pending episode(`GRAPHFLOW_AUTO_CAPTURE=0` 可关)。VS Code 图谱面板 / Workbench Tree 不在 dsh 上。
|
|
128
129
|
|
|
129
130
|
---
|
|
130
131
|
|
|
@@ -114,17 +114,18 @@ GraphFlow 是 DeepSeek Harness 的 `dsh-plugin`。装入后模型看到的工具
|
|
|
114
114
|
|
|
115
115
|
```sh
|
|
116
116
|
dsh plugin --profile web add @roarpeng/graphflow
|
|
117
|
-
|
|
117
|
+
npx @deepseek-ai/dsh web
|
|
118
|
+
# 或已有 ~/.dsh 时写 home overlay + skill 文件:
|
|
118
119
|
npx @roarpeng/graphflow install
|
|
119
120
|
```
|
|
120
121
|
|
|
121
122
|
| 路径 | 作用 |
|
|
122
123
|
|------|------|
|
|
123
|
-
| 包内 `cordis.patch.yml` | `dsh plugin add` 插入的 bundle
|
|
124
|
+
| 包内 `cordis.patch.yml` | `dsh plugin add` 插入的 bundle 层:MCP(`cwd: process.cwd()`)+ `@roarpeng/graphflow/dsh` glue |
|
|
124
125
|
| `$DSH_HOME/cordis.patch.yml`(默认 `~/.dsh`) | `graphflow install` 写的 home overlay |
|
|
125
|
-
| `$DSH_HOME/skills/graphflow/SKILL.md` | 本 Skill |
|
|
126
|
+
| `$DSH_HOME/skills/graphflow/SKILL.md` | 本 Skill(install 复制);glue 也会在运行时 `ctx.skills.register` |
|
|
126
127
|
|
|
127
|
-
**用法:**
|
|
128
|
+
**用法:** 第一轮先 `mcp__graphflow__graphflow_context`(`rootDir` = 仓库绝对路径)。不要在 patch 里写死 `GRAPHFLOW_WORKSPACE_ROOT`。走了 `graphflow_run` 后必须 `graphflow_report_outcome`。会话结束时 glue 会 best-effort 关闭 pending episode(`GRAPHFLOW_AUTO_CAPTURE=0` 可关)。VS Code 图谱面板 / Workbench Tree 不在 dsh 上。
|
|
128
129
|
|
|
129
130
|
---
|
|
130
131
|
|
package/web/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# GraphFlow Web 知识节点栏
|
|
2
|
+
|
|
3
|
+
DeepSeek Harness web 右侧"知识节点"面板的源码种子。
|
|
4
|
+
|
|
5
|
+
## 现状
|
|
6
|
+
|
|
7
|
+
- **自动按轮记录**(持久、随包生效):`dsh/plugin.mjs`(glue)监听 `agent/inbox/inserted`,
|
|
8
|
+
自动把每一轮用户提问写成 `dialogue-turn` 图节点(写入 agent 会话工作区的 `.graphflow/`)。
|
|
9
|
+
回复补全沿用既有机制:agent 调用 `graphflow_context({ assistantReply })` 时填充(与
|
|
10
|
+
Claude Code SessionEnd 同一契约)。可用 `GRAPHFLOW_AUTO_CAPTURE=0` 关闭。
|
|
11
|
+
- **右侧知识节点栏**(本会话生效):以动态 Cordis 插件(Host + Client 双半部)挂载:
|
|
12
|
+
- `conversation.session.header.utilities`:会话头部"知识节点"开关按钮
|
|
13
|
+
- `shell.overlay`:右缘浮动面板(additive,不替换任何出厂 UI),展示
|
|
14
|
+
Workbench 主题(主线/旁支、活跃、待回复、消息数)+ 对话轮次(Q/A、跳转标记)
|
|
15
|
+
- 点击节点:通过会话 binding 的 `prompt('queue')` 通道提交续聊指令,agent 自动调用
|
|
16
|
+
`graphflow_context({ topicId | resumeFromTurnId })` 恢复上下文继续
|
|
17
|
+
|
|
18
|
+
## 数据来源
|
|
19
|
+
|
|
20
|
+
面板数据由动态插件的 host 半部经 `harness.handle("gf.nodes.list")` 提供,
|
|
21
|
+
内部调用(当前工作区下):
|
|
22
|
+
- `graphflow workbench tree --json`
|
|
23
|
+
- `graphflow dialogue list --json --limit 50`
|
|
24
|
+
|
|
25
|
+
## 打包为静态客户端插件(未来)
|
|
26
|
+
|
|
27
|
+
dsh web 的静态客户端包必须预编译为 `window.__ModuleLoader__.load({id, factory})`
|
|
28
|
+
工厂格式并经 `/plugins/<id>/client.js` 提供;纯 ESM 文件无法直接加载。
|
|
29
|
+
`plugin.mjs` 中的 `hostHalf()` / `clientHalf()` 即未来 `code.host` / `code.client`
|
|
30
|
+
的源码。届时需:
|
|
31
|
+
1. 在 `package.json` 增加 `dsh.client` 声明(`platform: "web"` + `inject` 列表)
|
|
32
|
+
2. 用 dsh monorepo 的 rolldown 管线把 `./web` 编译为客户端 bundle
|
|
33
|
+
3. 在 `cordis.patch.yml` 增加第三行(客户端插件行)
|
|
34
|
+
|
|
35
|
+
在完成上述打包前,**不要**在 `cordis.patch.yml` 添加该行(client-modules 扫描到
|
|
36
|
+
缺失 bundle 会使 profile 组合失败)。
|