pi-loop-graph-sdk 0.2.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.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README-zh.md +416 -0
  3. package/README.md +414 -0
  4. package/ROADMAP.md +60 -0
  5. package/dist/adapter/complete-tool.d.ts +3 -0
  6. package/dist/adapter/complete-tool.js +53 -0
  7. package/dist/adapter/debug-log.d.ts +38 -0
  8. package/dist/adapter/debug-log.js +151 -0
  9. package/dist/adapter/extension.d.ts +2 -0
  10. package/dist/adapter/extension.js +11 -0
  11. package/dist/adapter/graph-execution-host.d.ts +73 -0
  12. package/dist/adapter/graph-execution-host.js +181 -0
  13. package/dist/adapter/isolated-graph-session.d.ts +75 -0
  14. package/dist/adapter/isolated-graph-session.js +313 -0
  15. package/dist/adapter/loop-graph-extension.d.ts +96 -0
  16. package/dist/adapter/loop-graph-extension.js +487 -0
  17. package/dist/adapter/mechanism-runtime.d.ts +97 -0
  18. package/dist/adapter/mechanism-runtime.js +670 -0
  19. package/dist/adapter/model-messages.d.ts +17 -0
  20. package/dist/adapter/model-messages.js +11 -0
  21. package/dist/adapter/observability.d.ts +88 -0
  22. package/dist/adapter/observability.js +31 -0
  23. package/dist/adapter/output-contract.d.ts +12 -0
  24. package/dist/adapter/output-contract.js +87 -0
  25. package/dist/adapter/pi-node-context.d.ts +132 -0
  26. package/dist/adapter/pi-node-context.js +619 -0
  27. package/dist/adapter/projection.d.ts +121 -0
  28. package/dist/adapter/projection.js +169 -0
  29. package/dist/adapter/skill-content.d.ts +16 -0
  30. package/dist/adapter/skill-content.js +16 -0
  31. package/dist/advanced.d.ts +32 -0
  32. package/dist/advanced.js +17 -0
  33. package/dist/builders/graph.d.ts +27 -0
  34. package/dist/builders/graph.js +39 -0
  35. package/dist/builders/node.d.ts +8 -0
  36. package/dist/builders/node.js +9 -0
  37. package/dist/builders/refs.d.ts +5 -0
  38. package/dist/builders/refs.js +10 -0
  39. package/dist/builders/route.d.ts +11 -0
  40. package/dist/builders/route.js +18 -0
  41. package/dist/core/context.d.ts +73 -0
  42. package/dist/core/context.js +229 -0
  43. package/dist/core/graph.d.ts +172 -0
  44. package/dist/core/graph.js +57 -0
  45. package/dist/core/json.d.ts +8 -0
  46. package/dist/core/json.js +46 -0
  47. package/dist/core/limits.d.ts +7 -0
  48. package/dist/core/limits.js +14 -0
  49. package/dist/core/mechanism.d.ts +88 -0
  50. package/dist/core/mechanism.js +5 -0
  51. package/dist/core/result.d.ts +41 -0
  52. package/dist/core/result.js +1 -0
  53. package/dist/core/schema.d.ts +8 -0
  54. package/dist/core/schema.js +23 -0
  55. package/dist/core/skill.d.ts +12 -0
  56. package/dist/core/skill.js +1 -0
  57. package/dist/host/baseline.d.ts +11 -0
  58. package/dist/host/baseline.js +4 -0
  59. package/dist/host/graph-catalog.d.ts +8 -0
  60. package/dist/host/graph-catalog.js +24 -0
  61. package/dist/host/graph-host.d.ts +53 -0
  62. package/dist/host/graph-host.js +181 -0
  63. package/dist/host/preflight.d.ts +17 -0
  64. package/dist/host/preflight.js +81 -0
  65. package/dist/host/skill-catalog.d.ts +24 -0
  66. package/dist/host/skill-catalog.js +92 -0
  67. package/dist/host/tool-catalog.d.ts +27 -0
  68. package/dist/host/tool-catalog.js +33 -0
  69. package/dist/index.d.ts +21 -0
  70. package/dist/index.js +10 -0
  71. package/dist/replay/checkpoint.d.ts +40 -0
  72. package/dist/replay/checkpoint.js +57 -0
  73. package/dist/replay/events.d.ts +40 -0
  74. package/dist/replay/events.js +1 -0
  75. package/dist/replay/finalizer.d.ts +26 -0
  76. package/dist/replay/finalizer.js +117 -0
  77. package/dist/replay/html.d.ts +3 -0
  78. package/dist/replay/html.js +270 -0
  79. package/dist/replay/index.d.ts +13 -0
  80. package/dist/replay/index.js +7 -0
  81. package/dist/replay/model.d.ts +81 -0
  82. package/dist/replay/model.js +1 -0
  83. package/dist/replay/parser.d.ts +3 -0
  84. package/dist/replay/parser.js +332 -0
  85. package/dist/replay/recorder.d.ts +30 -0
  86. package/dist/replay/recorder.js +195 -0
  87. package/dist/replay/store.d.ts +41 -0
  88. package/dist/replay/store.js +94 -0
  89. package/dist/router.d.ts +4 -0
  90. package/dist/router.js +61 -0
  91. package/dist/runtime/event-bus.d.ts +101 -0
  92. package/dist/runtime/event-bus.js +18 -0
  93. package/dist/runtime/graph-runtime.d.ts +173 -0
  94. package/dist/runtime/graph-runtime.js +1293 -0
  95. package/dist/runtime/invocation-budget.d.ts +22 -0
  96. package/dist/runtime/invocation-budget.js +52 -0
  97. package/dist/runtime/mechanism-runtime.d.ts +92 -0
  98. package/dist/runtime/mechanism-runtime.js +387 -0
  99. package/dist/runtime.d.ts +91 -0
  100. package/dist/runtime.js +258 -0
  101. package/dist/tools-resolve.d.ts +20 -0
  102. package/dist/tools-resolve.js +52 -0
  103. package/dist/type.d.ts +593 -0
  104. package/dist/type.js +30 -0
  105. package/dist/validate.d.ts +25 -0
  106. package/dist/validate.js +203 -0
  107. package/package.json +69 -0
@@ -0,0 +1,258 @@
1
+ // ============================================================
2
+ // GraphRuntime — 图运行时状态机
3
+ // ============================================================
4
+ export class GraphRuntime {
5
+ callStack = [];
6
+ isNodeActive = false;
7
+ /** 当前节点的语义作用域。details 用于匹配,不依赖消息正文。 */
8
+ currentScope = null;
9
+ currentNode = null;
10
+ currentInput = null;
11
+ graphRunId = crypto.randomUUID();
12
+ /** 当前 graph run 已发生的 compaction 次数,仅用于诊断和 checkpoint 观测。 */
13
+ compactionGeneration = 0;
14
+ /**
15
+ * 共享 call/compose 活跃期间异常收到 session_compact 时设为 true。
16
+ * 此后本 session 投影中将持续过滤 compactionSummary,优先保证不泄漏。
17
+ */
18
+ compactionBoundaryViolated = false;
19
+ /** Runtime 控制平面的 frame → NodeScope 对齐表,不进入开发者 frame/LLM。 */
20
+ frameScopes = new Map();
21
+ get top() {
22
+ return this.callStack.length > 0
23
+ ? this.callStack[this.callStack.length - 1]
24
+ : null;
25
+ }
26
+ get topInstance() {
27
+ return this.top?.instance ?? null;
28
+ }
29
+ get topGraph() {
30
+ return this.top?.graph ?? null;
31
+ }
32
+ get currentNodeId() {
33
+ return this.top?.currentNodeId ?? null;
34
+ }
35
+ pushGraph(graph, background, boundary = "root", sharedInstance, parentNodeId) {
36
+ const instance = sharedInstance ?? {
37
+ id: crypto.randomUUID(),
38
+ globalGoal: graph.goal,
39
+ background,
40
+ frames: [],
41
+ mechanisms: graph.mechanisms ?? [],
42
+ scratch: {},
43
+ };
44
+ if (!this.frameScopes.has(instance.id))
45
+ this.frameScopes.set(instance.id, []);
46
+ this.callStack.push({
47
+ instance,
48
+ graph,
49
+ boundary,
50
+ localGoal: graph.goal,
51
+ // root/call 已写入 instance;compose 只在子调用帧叠加,退出即消失。
52
+ localMechanisms: sharedInstance ? (graph.mechanisms ?? []) : [],
53
+ callBackground: background,
54
+ parentNodeId,
55
+ nodeVisits: new Map(),
56
+ currentNodeId: null,
57
+ activeNode: null,
58
+ activeInput: null,
59
+ activeScope: null,
60
+ isNodeActive: false,
61
+ projectedFrameBase: 0,
62
+ });
63
+ return instance;
64
+ }
65
+ popGraph() {
66
+ const popped = this.callStack.pop();
67
+ this.restoreActiveNodeFromTop();
68
+ return popped;
69
+ }
70
+ beginFrameSegment(graphId, parentNodeId) {
71
+ const instance = this.topInstance;
72
+ if (!instance)
73
+ throw new Error("callStack 为空");
74
+ return {
75
+ id: crypto.randomUUID(),
76
+ graphId,
77
+ parentNodeId,
78
+ instanceId: instance.id,
79
+ baseIndex: instance.frames.length,
80
+ depth: this.callStack.length + 1,
81
+ };
82
+ }
83
+ readFrameSegment(scope) {
84
+ const instance = this.assertSegmentOwner(scope);
85
+ // fold 只能看到独立快照。除顶层 frame 外也复制并冻结 result 的可变数据,
86
+ // 以免 folder 修改嵌套字段时反向影响 live frames。
87
+ return Object.freeze(instance.frames.slice(scope.baseIndex).map(snapshotFrame));
88
+ }
89
+ rollbackFrameSegment(scope) {
90
+ const instance = this.assertSegmentOwner(scope);
91
+ instance.frames.splice(scope.baseIndex);
92
+ this.frameScopes.get(instance.id)?.splice(scope.baseIndex);
93
+ }
94
+ closeFrameSegment(scope, completion) {
95
+ this.rollbackFrameSegment(scope);
96
+ return completion;
97
+ }
98
+ nextScope(nodeId) {
99
+ const top = this.top;
100
+ if (!top)
101
+ throw new Error("callStack 为空");
102
+ const visit = (top.nodeVisits.get(nodeId) ?? 0) + 1;
103
+ top.nodeVisits.set(nodeId, visit);
104
+ return {
105
+ protocol: 2,
106
+ graphRunId: this.graphRunId,
107
+ instanceId: top.instance.id,
108
+ scopeId: crypto.randomUUID(),
109
+ graphId: top.graph.id,
110
+ nodeId,
111
+ visit,
112
+ depth: this.callStack.length,
113
+ };
114
+ }
115
+ enterNode(nodeId, scope, input) {
116
+ const graph = this.topGraph;
117
+ if (!graph)
118
+ throw new Error("callStack 为空");
119
+ const node = graph.nodes[nodeId];
120
+ if (!node)
121
+ throw new Error(`节点未找到: ${nodeId}`);
122
+ const top = this.top;
123
+ top.currentNodeId = nodeId;
124
+ this.currentNode = node;
125
+ this.currentInput = input;
126
+ this.currentScope = scope;
127
+ this.isNodeActive = true;
128
+ top.activeNode = node;
129
+ top.activeInput = input;
130
+ top.activeScope = scope;
131
+ top.isNodeActive = true;
132
+ return node;
133
+ }
134
+ exitNode(frame) {
135
+ const instance = this.topInstance;
136
+ if (!instance)
137
+ throw new Error("callStack 为空");
138
+ instance.frames.push(frame);
139
+ if (this.currentScope)
140
+ this.frameScopes.get(instance.id)?.push(this.currentScope);
141
+ this.isNodeActive = false;
142
+ this.currentNode = null;
143
+ this.currentInput = null;
144
+ this.currentScope = null;
145
+ const top = this.top;
146
+ if (top) {
147
+ top.activeNode = null;
148
+ top.activeInput = null;
149
+ top.activeScope = null;
150
+ top.isNodeActive = false;
151
+ }
152
+ }
153
+ /**
154
+ * 记录一次 session compaction。NodeScope 的身份(scopeId)不变。
155
+ * Runtime 只推进 projectedFrameBase;pi 原生 summary 与 recent messages
156
+ * 是压缩历史的权威替代,SDK 不重发 scope,也不遮挡 summary。
157
+ */
158
+ recordCompaction(projectedFrameBase) {
159
+ this.compactionGeneration += 1;
160
+ const top = this.top;
161
+ if (top) {
162
+ const nextBase = projectedFrameBase ?? top.instance.frames.length;
163
+ top.projectedFrameBase = Math.max(top.projectedFrameBase, Math.min(nextBase, top.instance.frames.length));
164
+ }
165
+ return this.compactionGeneration;
166
+ }
167
+ /** 当前 callStack 是否存在嵌套 call/compose(非 root-only)。 */
168
+ get hasActiveSharedCall() {
169
+ return this.callStack.some((frame) => frame.boundary === "call" || frame.boundary === "compose");
170
+ }
171
+ /** 共享调用边界被 compaction 切断后,继续运行会泄漏无法归属的 transcript。 */
172
+ assertNoCompactionBoundaryViolation() {
173
+ if (this.compactionBoundaryViolated) {
174
+ throw new Error("compaction 边界违规:共享 call/compose 已终止,当前 Session 上下文已进入 fail-closed 状态");
175
+ }
176
+ }
177
+ get completedFrameScopes() {
178
+ const instance = this.topInstance;
179
+ return instance ? (this.frameScopes.get(instance.id) ?? []) : [];
180
+ }
181
+ /** 只返回最近一次 compaction 后新生长、仍需单独投影的开发者 frames。 */
182
+ get projectedFrames() {
183
+ const top = this.top;
184
+ return top ? top.instance.frames.slice(top.projectedFrameBase) : [];
185
+ }
186
+ reset() {
187
+ this.callStack = [];
188
+ this.isNodeActive = false;
189
+ this.currentScope = null;
190
+ this.currentNode = null;
191
+ this.currentInput = null;
192
+ this.compactionGeneration = 0;
193
+ this.compactionBoundaryViolated = false;
194
+ this.frameScopes.clear();
195
+ }
196
+ assertSegmentOwner(scope) {
197
+ const instance = this.topInstance;
198
+ if (!instance || instance.id !== scope.instanceId) {
199
+ throw new Error("FrameSegmentScope 不属于当前调用帧");
200
+ }
201
+ if (scope.baseIndex > instance.frames.length) {
202
+ throw new Error("FrameSegmentScope 的 baseIndex 无效");
203
+ }
204
+ return instance;
205
+ }
206
+ restoreActiveNodeFromTop() {
207
+ const top = this.top;
208
+ this.currentNode = top?.activeNode ?? null;
209
+ this.currentInput = top?.activeInput ?? null;
210
+ this.currentScope = top?.activeScope ?? null;
211
+ this.isNodeActive = top?.isNodeActive ?? false;
212
+ }
213
+ }
214
+ /** 复制并冻结 compose fold 可见的帧,不向其暴露 live 引用。 */
215
+ function snapshotFrame(frame) {
216
+ return Object.freeze(snapshotValue(frame));
217
+ }
218
+ /**
219
+ * NodeCompletion.result 是数据对象。这里不用 structuredClone:业务结果可包含
220
+ * 无法被它复制的值(例如函数),而函数本身不可通过普通属性写入改变帧数据。
221
+ * 对常见的对象/数组/Map/Set/Date 递归复制,随后冻结普通对象和数组。
222
+ */
223
+ function snapshotValue(value, seen = new WeakMap()) {
224
+ if (value === null || typeof value !== "object")
225
+ return value;
226
+ const cached = seen.get(value);
227
+ if (cached)
228
+ return cached;
229
+ if (Array.isArray(value)) {
230
+ const copy = [];
231
+ seen.set(value, copy);
232
+ for (const item of value)
233
+ copy.push(snapshotValue(item, seen));
234
+ return Object.freeze(copy);
235
+ }
236
+ if (value instanceof Date)
237
+ return Object.freeze(new Date(value.getTime()));
238
+ if (value instanceof Map) {
239
+ const copy = new Map();
240
+ seen.set(value, copy);
241
+ for (const [key, item] of value)
242
+ copy.set(snapshotValue(key, seen), snapshotValue(item, seen));
243
+ return copy;
244
+ }
245
+ if (value instanceof Set) {
246
+ const copy = new Set();
247
+ seen.set(value, copy);
248
+ for (const item of value)
249
+ copy.add(snapshotValue(item, seen));
250
+ return copy;
251
+ }
252
+ const copy = {};
253
+ seen.set(value, copy);
254
+ for (const [key, item] of Object.entries(value)) {
255
+ copy[key] = snapshotValue(item, seen);
256
+ }
257
+ return Object.freeze(copy);
258
+ }
@@ -0,0 +1,20 @@
1
+ export declare const FRAMEWORK_TOOLS: readonly ["read", "__graph_complete__"];
2
+ export interface ToolResolverInput {
3
+ readonly defaultTools: readonly string[];
4
+ readonly nodeTools: readonly string[];
5
+ readonly frameworkTools: typeof FRAMEWORK_TOOLS;
6
+ readonly graphId?: string;
7
+ readonly nodeId?: string;
8
+ }
9
+ /** 返回候选工具;SDK 随后统一去重并恢复 framework tools 的首尾不变量。 */
10
+ export type ToolResolver = (input: ToolResolverInput) => readonly string[];
11
+ export declare const defaultToolResolver: ToolResolver;
12
+ /**
13
+ * 计算节点的最终工具列表。
14
+ *
15
+ * 规则:
16
+ * [read, ...defaultTools, ...nodeTools, __graph_complete__]
17
+ * 去重(保留首次出现的位置),read 始终在第一位,
18
+ * __graph_complete__ 始终在最后。
19
+ */
20
+ export declare function resolveNodeTools(defaultTools: readonly string[], nodeTools: readonly string[], resolver?: ToolResolver, identity?: Pick<ToolResolverInput, "graphId" | "nodeId">): string[];
@@ -0,0 +1,52 @@
1
+ // ============================================================
2
+ // 工具解析 — 全仓库单一真相源
3
+ // ============================================================
4
+ //
5
+ // 只有一个函数产出最终工具列表。setActiveTools 调它,
6
+ // debug 日志也调它。
7
+ // ============================================================
8
+ export const FRAMEWORK_TOOLS = ["read", "__graph_complete__"];
9
+ export const defaultToolResolver = ({ defaultTools, nodeTools }) => [
10
+ ...defaultTools,
11
+ ...nodeTools,
12
+ ];
13
+ /**
14
+ * 计算节点的最终工具列表。
15
+ *
16
+ * 规则:
17
+ * [read, ...defaultTools, ...nodeTools, __graph_complete__]
18
+ * 去重(保留首次出现的位置),read 始终在第一位,
19
+ * __graph_complete__ 始终在最后。
20
+ */
21
+ export function resolveNodeTools(defaultTools, nodeTools, resolver = defaultToolResolver, identity = {}) {
22
+ const resolved = resolver(Object.freeze({
23
+ defaultTools: Object.freeze([...defaultTools]),
24
+ nodeTools: Object.freeze([...nodeTools]),
25
+ frameworkTools: FRAMEWORK_TOOLS,
26
+ ...identity,
27
+ }));
28
+ if (!Array.isArray(resolved) || resolved.some((name) => typeof name !== "string" || name.length === 0)) {
29
+ throw new TypeError("toolResolver 必须返回非空工具名数组");
30
+ }
31
+ const merged = ["read", ...resolved, "__graph_complete__"];
32
+ const seen = new Set();
33
+ const result = [];
34
+ for (const t of merged) {
35
+ if (!seen.has(t)) {
36
+ seen.add(t);
37
+ result.push(t);
38
+ }
39
+ }
40
+ // 确保 read 在首、__graph_complete__ 在尾
41
+ const readIdx = result.indexOf("read");
42
+ if (readIdx > 0) {
43
+ result.splice(readIdx, 1);
44
+ result.unshift("read");
45
+ }
46
+ const completeIdx = result.indexOf("__graph_complete__");
47
+ if (completeIdx >= 0 && completeIdx !== result.length - 1) {
48
+ result.splice(completeIdx, 1);
49
+ result.push("__graph_complete__");
50
+ }
51
+ return result;
52
+ }