@roarpeng/graphflow 1.14.0 → 1.15.0
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 +26 -0
- package/README.md +3 -3
- package/README.zh.md +1 -1
- package/dist/config/schema.d.ts +3 -1
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/graph/client-factory.d.ts.map +1 -1
- package/dist/graph/client-factory.js +4 -1
- package/dist/graph/client-factory.js.map +1 -1
- package/dist/graph/graphify-mcp-client.d.ts +33 -0
- package/dist/graph/graphify-mcp-client.d.ts.map +1 -1
- package/dist/graph/graphify-mcp-client.js +66 -5
- package/dist/graph/graphify-mcp-client.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/dsh-harness-installer.d.ts +2 -0
- package/dist/integrations/dsh-harness-installer.d.ts.map +1 -1
- package/dist/integrations/dsh-harness-installer.js +9 -3
- package/dist/integrations/dsh-harness-installer.js.map +1 -1
- package/dist/integrations/host-adapter-install.d.ts +19 -0
- package/dist/integrations/host-adapter-install.d.ts.map +1 -0
- package/dist/integrations/host-adapter-install.js +75 -0
- package/dist/integrations/host-adapter-install.js.map +1 -0
- package/dist/integrations/host-adapter.d.ts +5 -0
- package/dist/integrations/host-adapter.d.ts.map +1 -1
- package/dist/integrations/host-adapter.js.map +1 -1
- package/dist/integrations/skill-installer.d.ts.map +1 -1
- package/dist/integrations/skill-installer.js +3 -1
- package/dist/integrations/skill-installer.js.map +1 -1
- package/dist/security/rbac.d.ts +39 -0
- package/dist/security/rbac.d.ts.map +1 -0
- package/dist/security/rbac.js +213 -0
- package/dist/security/rbac.js.map +1 -0
- package/dist/security/token-auth.d.ts +15 -2
- package/dist/security/token-auth.d.ts.map +1 -1
- package/dist/security/token-auth.js +86 -5
- package/dist/security/token-auth.js.map +1 -1
- package/dist/surfaces/cli/index.js +79 -4
- package/dist/surfaces/cli/index.js.map +1 -1
- package/dist/surfaces/cli/init.d.ts +3 -0
- package/dist/surfaces/cli/init.d.ts.map +1 -1
- package/dist/surfaces/cli/init.js +31 -2
- 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 +7 -3
- package/dist/surfaces/cli/output.js.map +1 -1
- package/dist/surfaces/cli/runtime/graph.d.ts +13 -0
- package/dist/surfaces/cli/runtime/graph.d.ts.map +1 -1
- package/dist/surfaces/cli/runtime/graph.js +97 -9
- package/dist/surfaces/cli/runtime/graph.js.map +1 -1
- package/dist/surfaces/cli/runtime/routing.d.ts +1 -1
- package/dist/surfaces/cli/runtime/routing.d.ts.map +1 -1
- package/dist/surfaces/cli/runtime/routing.js +12 -2
- package/dist/surfaces/cli/runtime/routing.js.map +1 -1
- package/dist/surfaces/cli/runtime/types.d.ts +3 -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 -2
- package/dist/surfaces/cli/runtime.js.map +1 -1
- package/dist/surfaces/mcp/server.d.ts +2 -0
- package/dist/surfaces/mcp/server.d.ts.map +1 -1
- package/dist/surfaces/mcp/server.js +77 -37
- package/dist/surfaces/mcp/server.js.map +1 -1
- package/dist/surfaces/mcp/tool-handlers.d.ts.map +1 -1
- package/dist/surfaces/mcp/tool-handlers.js +2 -0
- package/dist/surfaces/mcp/tool-handlers.js.map +1 -1
- package/dist/surfaces/team/diagnose.d.ts +18 -0
- package/dist/surfaces/team/diagnose.d.ts.map +1 -0
- package/dist/surfaces/team/diagnose.js +57 -0
- package/dist/surfaces/team/diagnose.js.map +1 -0
- package/dist/surfaces/team/ops.d.ts +16 -0
- package/dist/surfaces/team/ops.d.ts.map +1 -0
- package/dist/surfaces/team/ops.js +137 -0
- package/dist/surfaces/team/ops.js.map +1 -0
- package/dist/surfaces/team/server.d.ts +48 -0
- package/dist/surfaces/team/server.d.ts.map +1 -0
- package/dist/surfaces/team/server.js +393 -0
- package/dist/surfaces/team/server.js.map +1 -0
- package/dsh/plugin.mjs +180 -46
- package/package.json +7 -6
- package/plugin.json +1 -1
package/dsh/plugin.mjs
CHANGED
|
@@ -55,7 +55,7 @@ const CAPTURE_OUTPUT_MAX_BYTES = 4_000_000;
|
|
|
55
55
|
|
|
56
56
|
const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
57
57
|
|
|
58
|
-
/** @typedef {{ spawn?: typeof spawn, env?: NodeJS.ProcessEnv, cwd?: string, readSkill?: () => { name: string, description: string, content: string, source: string, path?: string } }} GraphFlowDshPluginConfig */
|
|
58
|
+
/** @typedef {{ spawn?: typeof spawn, env?: NodeJS.ProcessEnv, cwd?: string, log?: { warn?: (msg: string) => void, error?: (msg: string) => void }, readSkill?: () => { name: string, description: string, content: string, source: string, path?: string } }} GraphFlowDshPluginConfig */
|
|
59
59
|
|
|
60
60
|
function envOf(config) {
|
|
61
61
|
return config?.env ?? process.env;
|
|
@@ -154,6 +154,21 @@ export function buildContextHint(cwd = process.cwd()) {
|
|
|
154
154
|
return `GraphFlow: before large code reads, call mcp__graphflow__graphflow_context with rootDir=${cwd}. Do not dump SKILL.md.`;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Same-step first-turn hint message. Tagged as plugin instructions so
|
|
159
|
+
* `isUserOriginatedMessage` does not treat it as a user question.
|
|
160
|
+
* `inject()` is the wrong primitive here — it lands in the *next* step inbox.
|
|
161
|
+
* @param {string} [cwd]
|
|
162
|
+
*/
|
|
163
|
+
export function buildHintMessage(cwd = process.cwd()) {
|
|
164
|
+
return {
|
|
165
|
+
id: crypto.randomUUID(),
|
|
166
|
+
role: "user",
|
|
167
|
+
content: [{ type: "text", text: buildContextHint(cwd) }],
|
|
168
|
+
source: { kind: "plugin", plugin: PLUGIN_ID, form: "instructions" },
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
157
172
|
/**
|
|
158
173
|
* Extract the plain text of a user message (its text ContentBlocks joined).
|
|
159
174
|
* Returns "" when the message carries no text. Never throws.
|
|
@@ -824,23 +839,66 @@ async function rpcNodesHandler(endpoint, payload, signal, config) {
|
|
|
824
839
|
}
|
|
825
840
|
}
|
|
826
841
|
|
|
842
|
+
function logGlue(config, level, message) {
|
|
843
|
+
try {
|
|
844
|
+
const logger = config?.log;
|
|
845
|
+
if (logger && typeof logger[level] === "function") {
|
|
846
|
+
logger[level](`[graphflow-dsh] ${message}`);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
const fallback = level === "error" ? console.error : console.warn;
|
|
850
|
+
fallback(`[graphflow-dsh] ${message}`);
|
|
851
|
+
} catch {
|
|
852
|
+
// logging must never throw into the harness
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
827
856
|
/**
|
|
828
|
-
*
|
|
829
|
-
*
|
|
830
|
-
*
|
|
831
|
-
*
|
|
832
|
-
*
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
857
|
+
* Resolve a Connection service from a Cordis ctx, an inject fork, or the
|
|
858
|
+
* connection object itself. `ctx.get("connection")` may be undefined while
|
|
859
|
+
* `ctx.connection` is already set (or the reverse); try both. Never throws.
|
|
860
|
+
* @param {object|undefined} source
|
|
861
|
+
* @returns {object|undefined}
|
|
862
|
+
*/
|
|
863
|
+
export function resolveConnectionService(source) {
|
|
864
|
+
if (!source || typeof source !== "object") return undefined;
|
|
865
|
+
if (typeof source.get === "function") {
|
|
866
|
+
try {
|
|
867
|
+
const viaGet = source.get("connection");
|
|
868
|
+
if (viaGet && typeof viaGet === "object") return viaGet;
|
|
869
|
+
} catch {
|
|
870
|
+
// service not ready / get threw
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (source.connection && typeof source.connection === "object") return source.connection;
|
|
874
|
+
if (source.rpc && typeof source.rpc.handle === "function") return source;
|
|
875
|
+
return undefined;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function attachRpcEffect(targetCtx, dispose) {
|
|
879
|
+
if (!targetCtx || typeof targetCtx.effect !== "function") return;
|
|
880
|
+
try {
|
|
881
|
+
targetCtx.effect(() => dispose, "graphflow-dsh: /gf nodes rpc channel");
|
|
882
|
+
} catch {
|
|
883
|
+
// effect bus missing — the apply return value still cleans up
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Wire `/gf` on one connection instance. Idempotent per connection.
|
|
889
|
+
* Failures are logged (not swallowed). Never throws.
|
|
837
890
|
* @returns {(() => void)|undefined}
|
|
838
891
|
*/
|
|
839
|
-
function
|
|
840
|
-
|
|
841
|
-
|
|
892
|
+
function registerNodesRpcOnConnection(connection, ctx, config) {
|
|
893
|
+
if (!connection || typeof connection !== "object") {
|
|
894
|
+
logGlue(config, "warn", "/gf RPC: connection service unavailable");
|
|
895
|
+
return undefined;
|
|
896
|
+
}
|
|
842
897
|
const rpc = connection.rpc;
|
|
843
|
-
if (!rpc || typeof rpc.handle !== "function")
|
|
898
|
+
if (!rpc || typeof rpc.handle !== "function") {
|
|
899
|
+
logGlue(config, "warn", "/gf RPC: connection.rpc.handle is missing");
|
|
900
|
+
return undefined;
|
|
901
|
+
}
|
|
844
902
|
if (wiredRpcConnections.has(connection)) return undefined;
|
|
845
903
|
wiredRpcConnections.add(connection);
|
|
846
904
|
let disposer;
|
|
@@ -848,8 +906,13 @@ function registerNodesRpcChannel(ctx, config) {
|
|
|
848
906
|
disposer = rpc.handle(NODES_CHANNEL, (endpoint, payload, signal) => rpcNodesHandler(endpoint, payload, signal, config), {
|
|
849
907
|
authority: "trusted-host",
|
|
850
908
|
});
|
|
851
|
-
} catch {
|
|
909
|
+
} catch (error) {
|
|
852
910
|
wiredRpcConnections.delete(connection);
|
|
911
|
+
logGlue(
|
|
912
|
+
config,
|
|
913
|
+
"error",
|
|
914
|
+
`/gf RPC: rpc.handle("${NODES_CHANNEL}") failed: ${error && error.message ? error.message : error}`
|
|
915
|
+
);
|
|
853
916
|
return undefined;
|
|
854
917
|
}
|
|
855
918
|
const dispose = () => {
|
|
@@ -863,14 +926,83 @@ function registerNodesRpcChannel(ctx, config) {
|
|
|
863
926
|
}
|
|
864
927
|
}
|
|
865
928
|
};
|
|
866
|
-
|
|
929
|
+
attachRpcEffect(ctx, dispose);
|
|
930
|
+
return dispose;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* Best-effort registration of the /gf Connection RPC channel
|
|
935
|
+
* (`ctx.connection.rpc.handle("/gf", handler, { authority: "trusted-host" })`
|
|
936
|
+
* — see dsh-client-connection lib/index.js:219-258: the handler is
|
|
937
|
+
* `(endpoint, payload, signal)` and `handle` returns a disposer).
|
|
938
|
+
*
|
|
939
|
+
* The host `connection` service is often not ready at apply() time
|
|
940
|
+
* (`ctx.get("connection")` is undefined because it injects `webRuntime`).
|
|
941
|
+
* Mirror DSH api-gateway: wait with `ctx.inject(["connection"], cb)` and
|
|
942
|
+
* register when the service appears. Duck-typed, idempotent (one channel
|
|
943
|
+
* per connection), and the returned disposer both unregisters via the
|
|
944
|
+
* handle disposer and is attached to `ctx.effect` when available.
|
|
945
|
+
* Registration failures are logged. Never throws.
|
|
946
|
+
* @param {object} ctx
|
|
947
|
+
* @param {GraphFlowDshPluginConfig} [config]
|
|
948
|
+
* @returns {(() => void)|undefined}
|
|
949
|
+
*/
|
|
950
|
+
function registerNodesRpcChannel(ctx, config) {
|
|
951
|
+
const disposeHolder = { current: undefined };
|
|
952
|
+
let disposed = false;
|
|
953
|
+
|
|
954
|
+
const tryRegister = (sourceCtx) => {
|
|
955
|
+
if (disposed) return undefined;
|
|
956
|
+
const connection = resolveConnectionService(sourceCtx) ?? resolveConnectionService(ctx);
|
|
957
|
+
if (!connection) return undefined;
|
|
958
|
+
const dispose = registerNodesRpcOnConnection(connection, sourceCtx ?? ctx, config);
|
|
959
|
+
if (dispose) {
|
|
960
|
+
disposeHolder.current = dispose;
|
|
961
|
+
attachRpcEffect(ctx, dispose);
|
|
962
|
+
}
|
|
963
|
+
return dispose;
|
|
964
|
+
};
|
|
965
|
+
|
|
966
|
+
tryRegister(ctx);
|
|
967
|
+
|
|
968
|
+
if (!disposeHolder.current && ctx && typeof ctx.inject === "function") {
|
|
969
|
+
logGlue(config, "warn", '/gf RPC: connection not ready; waiting via ctx.inject(["connection"])');
|
|
867
970
|
try {
|
|
868
|
-
ctx.
|
|
869
|
-
|
|
870
|
-
|
|
971
|
+
ctx.inject(["connection"], (injected) => {
|
|
972
|
+
try {
|
|
973
|
+
if (disposed) return;
|
|
974
|
+
const dispose = tryRegister(injected ?? ctx);
|
|
975
|
+
if (!dispose && !disposeHolder.current) {
|
|
976
|
+
logGlue(config, "warn", "/gf RPC: connection inject fired but /gf channel was not registered");
|
|
977
|
+
}
|
|
978
|
+
} catch (error) {
|
|
979
|
+
logGlue(
|
|
980
|
+
config,
|
|
981
|
+
"error",
|
|
982
|
+
`/gf RPC: delayed registration failed: ${error && error.message ? error.message : error}`
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
} catch (error) {
|
|
987
|
+
logGlue(
|
|
988
|
+
config,
|
|
989
|
+
"error",
|
|
990
|
+
`/gf RPC: ctx.inject(["connection"]) failed: ${error && error.message ? error.message : error}`
|
|
991
|
+
);
|
|
871
992
|
}
|
|
872
993
|
}
|
|
873
|
-
|
|
994
|
+
|
|
995
|
+
return () => {
|
|
996
|
+
disposed = true;
|
|
997
|
+
if (typeof disposeHolder.current === "function") {
|
|
998
|
+
try {
|
|
999
|
+
disposeHolder.current();
|
|
1000
|
+
} catch {
|
|
1001
|
+
// teardown is best-effort
|
|
1002
|
+
}
|
|
1003
|
+
disposeHolder.current = undefined;
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
874
1006
|
}
|
|
875
1007
|
|
|
876
1008
|
/**
|
|
@@ -1067,17 +1199,6 @@ export function closePendingEpisodeForCwd(cwd, config = {}) {
|
|
|
1067
1199
|
}
|
|
1068
1200
|
}
|
|
1069
1201
|
|
|
1070
|
-
function safeInjectHint(agent, cwd) {
|
|
1071
|
-
if (!agent || typeof agent.inject !== "function") return;
|
|
1072
|
-
const text = buildContextHint(cwd);
|
|
1073
|
-
agent.inject({
|
|
1074
|
-
id: crypto.randomUUID(),
|
|
1075
|
-
role: "user",
|
|
1076
|
-
content: [{ type: "text", text }],
|
|
1077
|
-
source: { kind: "plugin", plugin: PLUGIN_ID, form: "instructions" },
|
|
1078
|
-
});
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
1202
|
function registerSkill(ctx, config) {
|
|
1082
1203
|
const skills = ctx?.skills;
|
|
1083
1204
|
if (!skills || typeof skills.register !== "function") return;
|
|
@@ -1104,14 +1225,18 @@ export function apply(ctx, config = {}) {
|
|
|
1104
1225
|
}
|
|
1105
1226
|
|
|
1106
1227
|
// Static-panel data channel: /gf Connection RPC (registerNodesRpcChannel).
|
|
1107
|
-
//
|
|
1108
|
-
//
|
|
1109
|
-
//
|
|
1228
|
+
// Registers immediately when connection is already on ctx; otherwise waits
|
|
1229
|
+
// via ctx.inject(["connection"]) so apply() racing webRuntime still works.
|
|
1230
|
+
// Failures are logged. Never throw into the harness.
|
|
1110
1231
|
let disposeNodesRpc;
|
|
1111
1232
|
try {
|
|
1112
1233
|
disposeNodesRpc = registerNodesRpcChannel(ctx, config);
|
|
1113
|
-
} catch {
|
|
1114
|
-
|
|
1234
|
+
} catch (error) {
|
|
1235
|
+
logGlue(
|
|
1236
|
+
config,
|
|
1237
|
+
"error",
|
|
1238
|
+
`/gf RPC: registerNodesRpcChannel threw: ${error && error.message ? error.message : error}`
|
|
1239
|
+
);
|
|
1115
1240
|
}
|
|
1116
1241
|
|
|
1117
1242
|
const hinted = new WeakSet();
|
|
@@ -1259,20 +1384,29 @@ export function apply(ctx, config = {}) {
|
|
|
1259
1384
|
}
|
|
1260
1385
|
|
|
1261
1386
|
try {
|
|
1262
|
-
|
|
1387
|
+
// First-turn hint must ride in the *same* step as the user's first
|
|
1388
|
+
// message. `agent.inject()` from pre-step appends to the *next* step
|
|
1389
|
+
// inbox (and can spawn a trailing step the model refuses). Extend the
|
|
1390
|
+
// enter decision's `messages` after `next()` instead — same WeakSet
|
|
1391
|
+
// gating and source tagging, no driver wake.
|
|
1392
|
+
listen(ctx, "agent/pre-step", async (payload, next) => {
|
|
1393
|
+
let decision;
|
|
1263
1394
|
try {
|
|
1264
|
-
|
|
1265
|
-
if (agent && !hinted.has(agent)) {
|
|
1266
|
-
hinted.add(agent);
|
|
1267
|
-
safeInjectHint(agent, resolveWorkspaceCwd(payload, config.cwd));
|
|
1268
|
-
}
|
|
1395
|
+
decision = typeof next === "function" ? await next() : undefined;
|
|
1269
1396
|
} catch {
|
|
1270
|
-
|
|
1397
|
+
return undefined;
|
|
1271
1398
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1399
|
+
try {
|
|
1400
|
+
if (!decision || decision.kind !== "enter") return decision;
|
|
1401
|
+
const agent = payloadAgent(payload);
|
|
1402
|
+
if (!agent || hinted.has(agent)) return decision;
|
|
1403
|
+
const hint = buildHintMessage(resolveWorkspaceCwd(payload, config.cwd));
|
|
1404
|
+
const messages = Array.isArray(decision.messages) ? decision.messages : [];
|
|
1405
|
+
hinted.add(agent);
|
|
1406
|
+
return { ...decision, messages: [...messages, hint] };
|
|
1407
|
+
} catch {
|
|
1408
|
+
return decision;
|
|
1274
1409
|
}
|
|
1275
|
-
return undefined;
|
|
1276
1410
|
});
|
|
1277
1411
|
} catch {
|
|
1278
1412
|
// event bus missing
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roarpeng/graphflow",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"node": ">=20",
|
|
32
32
|
"npm": ">=10"
|
|
33
33
|
},
|
|
34
|
-
"description": "
|
|
34
|
+
"description": "Local-first memory & context harness for coding agents",
|
|
35
35
|
"main": "dist/index.js",
|
|
36
36
|
"types": "dist/index.d.ts",
|
|
37
37
|
"exports": {
|
|
@@ -98,11 +98,12 @@
|
|
|
98
98
|
"url": "git+https://github.com/Roarpeng/GraphFlow.git"
|
|
99
99
|
},
|
|
100
100
|
"keywords": [
|
|
101
|
-
"
|
|
102
|
-
"
|
|
101
|
+
"memory-harness",
|
|
102
|
+
"context-harness",
|
|
103
|
+
"local-first",
|
|
103
104
|
"mcp",
|
|
104
|
-
"
|
|
105
|
-
"
|
|
105
|
+
"knowledge-graph",
|
|
106
|
+
"coding-agents",
|
|
106
107
|
"cursor",
|
|
107
108
|
"claude-code",
|
|
108
109
|
"dsh-plugin",
|
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.
|
|
4
|
+
"version": "1.15.0",
|
|
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",
|