@baique/pi-web-sky 0.1.35 → 0.1.36
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/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +16 -16
- package/.next/build-manifest.json +3 -3
- package/.next/react-loadable-manifest.json +1 -1
- package/.next/required-server-files.js +1 -1
- package/.next/required-server-files.json +1 -1
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +3 -3
- package/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/agent/[id]/bash-output/route.js +1 -1
- package/.next/server/app/api/agent/[id]/events/route.js +1 -1
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/app-update/route.js +1 -1
- package/.next/server/app/api/auth/login/[provider]/route.js +1 -1
- package/.next/server/app/api/cwd/validate/route.js +1 -1
- package/.next/server/app/api/default-cwd/route.js +1 -1
- package/.next/server/app/api/file-index/route.js +1 -1
- package/.next/server/app/api/files/[...path]/route.js +1 -1
- package/.next/server/app/api/mcp/route.js +1 -1
- package/.next/server/app/api/models/route.js +1 -1
- package/.next/server/app/api/plugins/route.js +1 -1
- package/.next/server/app/api/project-trust/route.js +1 -1
- package/.next/server/app/api/search/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/auto-name/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/todos/route.js +1 -1
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/skills/check/route.js +1 -1
- package/.next/server/app/api/skills/check/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/install/route.js +1 -1
- package/.next/server/app/api/skills/route.js +2 -31
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/update/route.js +1 -1
- package/.next/server/app/api/skills/update/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +5 -5
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +5 -5
- package/.next/server/app/index.segments/_full.segment.rsc +5 -5
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/page.js +2 -2
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +16 -16
- package/.next/server/chunks/2404.js +1 -1
- package/.next/server/chunks/3500.js +30 -0
- package/.next/server/chunks/4226.js +2 -2
- package/.next/server/chunks/542.js +10 -5
- package/.next/server/chunks/5794.js +1 -1
- package/.next/server/chunks/6429.js +1 -1
- package/.next/server/chunks/898.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/5315.7e72d26a3ad3bd9a.js +4 -0
- package/.next/static/chunks/app/{layout-1d9a7a44fbc92d2e.js → layout-ebbc0d287267b07b.js} +1 -1
- package/.next/static/chunks/app/{page-6d6779a81a0a7639.js → page-e568623a44c67606.js} +2 -2
- package/.next/static/chunks/{webpack-fa4f40085b1ec1ac.js → webpack-2e4e348867a555cd.js} +1 -1
- package/.next/static/css/422c7dc0faa04d89.css +1 -0
- package/.next/trace +8 -9
- package/.next/trace-build +1 -1
- package/bin/pi-web-sky.js +15 -20
- package/lib/agent-client.test.mjs +64 -0
- package/lib/agent-client.ts +53 -0
- package/lib/agent-event-connection.test.mjs +193 -0
- package/lib/agent-event-connection.ts +202 -0
- package/lib/agent-event-stream.test.mjs +155 -0
- package/lib/agent-event-stream.ts +132 -0
- package/lib/agent-event-wire.test.mjs +278 -0
- package/lib/agent-event-wire.ts +107 -0
- package/lib/agent-phase.ts +24 -0
- package/lib/allowed-roots.ts +31 -0
- package/lib/ansi.test.mjs +52 -0
- package/lib/ansi.ts +211 -0
- package/lib/api-types.ts +139 -0
- package/lib/app-update.test.mjs +27 -0
- package/lib/app-update.ts +28 -0
- package/lib/atomic-file.test.mjs +37 -0
- package/lib/atomic-file.ts +34 -0
- package/lib/audit-session.ts +235 -0
- package/lib/bash-output.test.mjs +63 -0
- package/lib/bash-output.ts +53 -0
- package/lib/bg-image.ts +129 -0
- package/lib/board-events.ts +83 -0
- package/lib/board-purge.test.mjs +66 -0
- package/lib/board-purge.ts +65 -0
- package/lib/board-search.test.mjs +110 -0
- package/lib/board-search.ts +63 -0
- package/lib/board-store.test.mjs +542 -0
- package/lib/board-store.ts +794 -0
- package/lib/board-types.ts +96 -0
- package/lib/board-utils.ts +17 -0
- package/lib/bounded-form-data.test.mjs +54 -0
- package/lib/bounded-form-data.ts +49 -0
- package/lib/browser-notifications.test.mjs +167 -0
- package/lib/browser-notifications.ts +104 -0
- package/lib/chat-lazy-load.test.mjs +140 -0
- package/lib/chat-lazy-load.ts +64 -0
- package/lib/clipboard-paths.test.mjs +140 -0
- package/lib/clipboard-paths.ts +192 -0
- package/lib/clipboard.ts +18 -0
- package/lib/compaction-summary.test.mjs +44 -0
- package/lib/compaction-summary.ts +34 -0
- package/lib/custom-ui-terminal.test.mjs +32 -0
- package/lib/custom-ui-terminal.ts +27 -0
- package/lib/deepseek-pricing.test.mjs +120 -0
- package/lib/deepseek-pricing.ts +71 -0
- package/lib/directory-browser.test.mjs +60 -0
- package/lib/directory-browser.ts +83 -0
- package/lib/draft-stash.test.mjs +91 -0
- package/lib/draft-stash.ts +102 -0
- package/lib/draft-store.ts +105 -0
- package/lib/dropdown-direction.ts +27 -0
- package/lib/extension-command.test.mjs +63 -0
- package/lib/extension-command.ts +81 -0
- package/lib/file-access.test.mjs +31 -0
- package/lib/file-access.ts +59 -0
- package/lib/file-dirent.test.mjs +56 -0
- package/lib/file-dirent.ts +17 -0
- package/lib/file-explorer-state.test.mjs +63 -0
- package/lib/file-explorer-state.ts +36 -0
- package/lib/file-fuzzy.test.mjs +29 -0
- package/lib/file-fuzzy.ts +189 -0
- package/lib/file-links.test.mjs +92 -0
- package/lib/file-links.ts +148 -0
- package/lib/file-paths.ts +43 -0
- package/lib/file-types.test.mjs +34 -0
- package/lib/file-types.ts +73 -0
- package/lib/file-upload.test.mjs +70 -0
- package/lib/file-upload.ts +59 -0
- package/lib/file-viewer-state.test.mjs +20 -0
- package/lib/file-viewer-state.ts +15 -0
- package/lib/frontmatter.test.mjs +55 -0
- package/lib/frontmatter.ts +87 -0
- package/lib/git-changes.test.mjs +52 -0
- package/lib/git-changes.ts +232 -0
- package/lib/git-status.ts +47 -0
- package/lib/git-types.ts +29 -0
- package/lib/http-dispatcher.test.mjs +83 -0
- package/lib/http-dispatcher.ts +86 -0
- package/lib/i18n/format.test.mjs +19 -0
- package/lib/i18n/format.ts +60 -0
- package/lib/i18n/messages/en.ts +662 -0
- package/lib/i18n/messages/zh-CN.ts +661 -0
- package/lib/i18n/registry.test.mjs +32 -0
- package/lib/i18n/registry.ts +43 -0
- package/lib/i18n/types.ts +15 -0
- package/lib/image-attachments.test.mjs +27 -0
- package/lib/image-attachments.ts +56 -0
- package/lib/initial-navigation.test.mjs +65 -0
- package/lib/initial-navigation.ts +15 -0
- package/lib/markdown.test.mjs +104 -0
- package/lib/markdown.ts +347 -0
- package/lib/message-display.test.mjs +130 -0
- package/lib/message-display.ts +47 -0
- package/lib/model-catalog.test.mjs +327 -0
- package/lib/model-catalog.ts +414 -0
- package/lib/model-discovery-auth.ts +58 -0
- package/lib/model-discovery.test.mjs +50 -0
- package/lib/model-discovery.ts +75 -0
- package/lib/model-id-preference.ts +46 -0
- package/lib/model-scope.test.mjs +193 -0
- package/lib/model-scope.ts +198 -0
- package/lib/models-cache.test.mjs +122 -0
- package/lib/models-cache.ts +90 -0
- package/lib/models-config-store.test.mjs +200 -0
- package/lib/models-config-store.ts +84 -0
- package/lib/next-config-esm.test.mjs +14 -0
- package/lib/next-config.test.mjs +13 -0
- package/lib/node-version.test.mjs +33 -0
- package/lib/normalize.test.mjs +54 -0
- package/lib/normalize.ts +63 -0
- package/lib/npx.ts +62 -0
- package/lib/panel-layout.test.mjs +56 -0
- package/lib/panel-layout.ts +52 -0
- package/lib/patch.test.mjs +110 -0
- package/lib/patch.ts +134 -0
- package/lib/path-security.ts +42 -0
- package/lib/paths.test.mjs +58 -0
- package/lib/paths.ts +71 -0
- package/lib/pi-types.ts +183 -0
- package/lib/pi-web-options.test.mjs +63 -0
- package/lib/process-lifecycle.test.mjs +83 -0
- package/lib/project-command-env.test.mjs +298 -0
- package/lib/project-command-env.ts +129 -0
- package/lib/project-groups.test.mjs +60 -0
- package/lib/project-groups.ts +53 -0
- package/lib/project-identity.test.mjs +37 -0
- package/lib/project-identity.ts +26 -0
- package/lib/project-tree.test.mjs +115 -0
- package/lib/project-tree.ts +176 -0
- package/lib/project-trust.test.mjs +117 -0
- package/lib/project-trust.ts +48 -0
- package/lib/prompt-recovery.test.mjs +33 -0
- package/lib/prompt-recovery.ts +47 -0
- package/lib/provider-api-key-route.test.mjs +12 -0
- package/lib/provider-credential-store.test.mjs +126 -0
- package/lib/provider-credential-store.ts +114 -0
- package/lib/provider-listing-runtime.ts +37 -0
- package/lib/provider-listing.test.mjs +114 -0
- package/lib/provider-listing.ts +118 -0
- package/lib/provider-services.ts +64 -0
- package/lib/request-security.test.mjs +256 -0
- package/lib/request-security.ts +136 -0
- package/lib/rpc-manager-shutdown.test.mjs +372 -0
- package/lib/rpc-manager-widgets.test.mjs +342 -0
- package/lib/rpc-manager.test.mjs +139 -0
- package/lib/rpc-manager.ts +1703 -0
- package/lib/rpc-session-info.test.mjs +93 -0
- package/lib/session-delete.test.mjs +53 -0
- package/lib/session-delete.ts +86 -0
- package/lib/session-file-references-core.ts +82 -0
- package/lib/session-file-references.test.mjs +91 -0
- package/lib/session-file-references.ts +29 -0
- package/lib/session-path.test.mjs +24 -0
- package/lib/session-path.ts +11 -0
- package/lib/session-reader.pagination.test.mjs +115 -0
- package/lib/session-reader.test.mjs +446 -0
- package/lib/session-reader.ts +548 -0
- package/lib/session-resolve.test.mjs +65 -0
- package/lib/session-row-context-menu.test.mjs +46 -0
- package/lib/session-row-context-menu.ts +29 -0
- package/lib/session-scanner.test.mjs +151 -0
- package/lib/session-scanner.ts +238 -0
- package/lib/session-search.test.mjs +173 -0
- package/lib/session-search.ts +284 -0
- package/lib/session-stats.ts +26 -0
- package/lib/session-timing.test.mjs +107 -0
- package/lib/session-timing.ts +45 -0
- package/lib/session-title.test.mjs +284 -0
- package/lib/session-title.ts +255 -0
- package/lib/skill-frontmatter.test.mjs +75 -0
- package/lib/skill-frontmatter.ts +40 -0
- package/lib/skill-lock.test.mjs +182 -0
- package/lib/skill-lock.ts +146 -0
- package/lib/skill-updates.test.mjs +182 -0
- package/lib/skill-updates.ts +263 -0
- package/lib/skills-service.ts +16 -0
- package/lib/slash-display.test.mjs +122 -0
- package/lib/slash-display.ts +30 -0
- package/lib/sqlite-db.test.mjs +140 -0
- package/lib/sqlite-db.ts +205 -0
- package/lib/startup-preferences.test.mjs +130 -0
- package/lib/startup-preferences.ts +65 -0
- package/lib/streaming-message.test.mjs +212 -0
- package/lib/streaming-message.ts +151 -0
- package/lib/subagent-widget.test.mjs +87 -0
- package/lib/subagent-widget.ts +109 -0
- package/lib/sync-room-server.mjs +137 -0
- package/lib/task-card-store.test.mjs +309 -0
- package/lib/task-card-store.ts +559 -0
- package/lib/task-scheduler.ts +498 -0
- package/lib/task-store.test.mjs +216 -0
- package/lib/task-store.ts +271 -0
- package/lib/terminal-input.test.mjs +46 -0
- package/lib/terminal-input.ts +63 -0
- package/lib/terminal-manager.test.mjs +71 -0
- package/lib/terminal-manager.ts +245 -0
- package/lib/tool-execution-progress.test.mjs +32 -0
- package/lib/tool-execution-progress.ts +29 -0
- package/lib/tool-names.ts +25 -0
- package/lib/tool-preset-preference.test.mjs +72 -0
- package/lib/tool-preset-preference.ts +41 -0
- package/lib/tool-presets.test.mjs +56 -0
- package/lib/tool-presets.ts +42 -0
- package/lib/turn-written-files.test.mjs +137 -0
- package/lib/turn-written-files.ts +60 -0
- package/lib/types.ts +349 -0
- package/lib/wallpaper-settings.ts +191 -0
- package/lib/web-auth.test.mjs +50 -0
- package/lib/web-auth.ts +45 -0
- package/lib/workspace-memory.test.mjs +129 -0
- package/lib/workspace-memory.ts +98 -0
- package/lib/worktree.test.mjs +50 -0
- package/lib/worktree.ts +250 -0
- package/package.json +3 -1
- package/server.mjs +34 -0
- package/.next/server/chunks/7513.js +0 -30
- package/.next/static/chunks/5315.03de3c6abd8b283e.js +0 -4
- package/.next/static/css/cf25164cb7c04d95.css +0 -1
- /package/.next/static/{Ud6Y2ONkGuPVFbo1xZQvk → EoBw3VYlygpr1EqlVOa5b}/_buildManifest.js +0 -0
- /package/.next/static/{Ud6Y2ONkGuPVFbo1xZQvk → EoBw3VYlygpr1EqlVOa5b}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { ClientAssistantMessageEvent } from "./agent-event-wire";
|
|
2
|
+
import { normalizeStreamingToolCalls } from "./normalize";
|
|
3
|
+
import type {
|
|
4
|
+
AgentMessage,
|
|
5
|
+
AssistantContentBlock,
|
|
6
|
+
AssistantMessage,
|
|
7
|
+
} from "./types";
|
|
8
|
+
|
|
9
|
+
export type { ClientAssistantMessageEvent } from "./agent-event-wire";
|
|
10
|
+
|
|
11
|
+
export interface StreamingState {
|
|
12
|
+
isStreaming: boolean;
|
|
13
|
+
streamingMessage: AssistantMessage | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type StreamAction =
|
|
17
|
+
| { type: "start" }
|
|
18
|
+
| { type: "snapshot"; message: AgentMessage }
|
|
19
|
+
| { type: "delta"; event: ClientAssistantMessageEvent }
|
|
20
|
+
| { type: "end" };
|
|
21
|
+
|
|
22
|
+
export const INITIAL_STREAMING_STATE: StreamingState = {
|
|
23
|
+
isStreaming: false,
|
|
24
|
+
streamingMessage: null,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function updateContentBlock(
|
|
28
|
+
state: StreamingState,
|
|
29
|
+
contentIndex: number,
|
|
30
|
+
update: (current: AssistantContentBlock | undefined) => AssistantContentBlock | null,
|
|
31
|
+
): StreamingState {
|
|
32
|
+
const message = state.streamingMessage;
|
|
33
|
+
if (!message || !Number.isInteger(contentIndex) || contentIndex < 0) return state;
|
|
34
|
+
|
|
35
|
+
const content = [...message.content];
|
|
36
|
+
const nextBlock = update(content[contentIndex]);
|
|
37
|
+
if (!nextBlock) return state;
|
|
38
|
+
content[contentIndex] = nextBlock;
|
|
39
|
+
return {
|
|
40
|
+
isStreaming: true,
|
|
41
|
+
streamingMessage: { ...message, content },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function applyDelta(
|
|
46
|
+
state: StreamingState,
|
|
47
|
+
event: ClientAssistantMessageEvent,
|
|
48
|
+
): StreamingState {
|
|
49
|
+
switch (event.type) {
|
|
50
|
+
case "text_start":
|
|
51
|
+
return updateContentBlock(state, event.contentIndex, (current) => (
|
|
52
|
+
current?.type === "text" ? current : { type: "text", text: "" }
|
|
53
|
+
));
|
|
54
|
+
case "text_delta":
|
|
55
|
+
return updateContentBlock(state, event.contentIndex, (current) => (
|
|
56
|
+
current?.type === "text"
|
|
57
|
+
? { ...current, text: current.text + event.delta }
|
|
58
|
+
: null
|
|
59
|
+
));
|
|
60
|
+
case "text_end":
|
|
61
|
+
return updateContentBlock(state, event.contentIndex, (current) => ({
|
|
62
|
+
...(current?.type === "text" ? current : {}),
|
|
63
|
+
type: "text",
|
|
64
|
+
text: event.content,
|
|
65
|
+
}));
|
|
66
|
+
case "thinking_start":
|
|
67
|
+
return updateContentBlock(state, event.contentIndex, (current) => (
|
|
68
|
+
current?.type === "thinking" ? current : { type: "thinking", thinking: "" }
|
|
69
|
+
));
|
|
70
|
+
case "thinking_delta":
|
|
71
|
+
return updateContentBlock(state, event.contentIndex, (current) => (
|
|
72
|
+
current?.type === "thinking"
|
|
73
|
+
? { ...current, thinking: current.thinking + event.delta }
|
|
74
|
+
: null
|
|
75
|
+
));
|
|
76
|
+
case "thinking_end":
|
|
77
|
+
return updateContentBlock(state, event.contentIndex, (current) => ({
|
|
78
|
+
...(current?.type === "thinking" ? current : {}),
|
|
79
|
+
type: "thinking",
|
|
80
|
+
thinking: event.content,
|
|
81
|
+
}));
|
|
82
|
+
case "toolcall_start":
|
|
83
|
+
return updateContentBlock(state, event.contentIndex, (current) => {
|
|
84
|
+
if (current?.type === "toolCall") {
|
|
85
|
+
return {
|
|
86
|
+
...current,
|
|
87
|
+
toolCallId: event.id ?? current.toolCallId,
|
|
88
|
+
toolName: event.toolName ?? current.toolName,
|
|
89
|
+
rawInput: current.rawInput ?? "",
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (typeof event.toolName !== "string") return null;
|
|
93
|
+
return {
|
|
94
|
+
type: "toolCall",
|
|
95
|
+
toolCallId: event.id ?? "",
|
|
96
|
+
toolName: event.toolName,
|
|
97
|
+
input: {},
|
|
98
|
+
rawInput: "",
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
case "toolcall_delta":
|
|
102
|
+
return updateContentBlock(state, event.contentIndex, (current) => (
|
|
103
|
+
current?.type === "toolCall"
|
|
104
|
+
? {
|
|
105
|
+
...current,
|
|
106
|
+
toolCallId: event.id || current.toolCallId,
|
|
107
|
+
toolName: event.toolName || current.toolName,
|
|
108
|
+
rawInput: (current.rawInput ?? "") + event.delta,
|
|
109
|
+
}
|
|
110
|
+
: null
|
|
111
|
+
));
|
|
112
|
+
case "toolcall_end":
|
|
113
|
+
return updateContentBlock(state, event.contentIndex, () => ({
|
|
114
|
+
type: "toolCall",
|
|
115
|
+
toolCallId: event.toolCall.id,
|
|
116
|
+
toolName: event.toolCall.name,
|
|
117
|
+
input: event.toolCall.arguments,
|
|
118
|
+
}));
|
|
119
|
+
default:
|
|
120
|
+
return state;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function streamReducer(
|
|
125
|
+
state: StreamingState,
|
|
126
|
+
action: StreamAction,
|
|
127
|
+
): StreamingState {
|
|
128
|
+
switch (action.type) {
|
|
129
|
+
case "start":
|
|
130
|
+
// A late `start` (e.g. the mount-time state restore finishing after the
|
|
131
|
+
// SSE reconnect already replayed the partial message via `snapshot`)
|
|
132
|
+
// must not wipe an in-flight streaming message. Clearing it here would
|
|
133
|
+
// drop every subsequent text delta (applyDelta needs an existing block)
|
|
134
|
+
// and freeze the typewriter until the whole message lands at message_end.
|
|
135
|
+
return state.streamingMessage
|
|
136
|
+
? { ...state, isStreaming: true }
|
|
137
|
+
: { isStreaming: true, streamingMessage: null };
|
|
138
|
+
case "snapshot": {
|
|
139
|
+
const message = normalizeStreamingToolCalls(action.message);
|
|
140
|
+
return message.role === "assistant"
|
|
141
|
+
? { isStreaming: true, streamingMessage: message }
|
|
142
|
+
: state;
|
|
143
|
+
}
|
|
144
|
+
case "delta":
|
|
145
|
+
return applyDelta(state, action.event);
|
|
146
|
+
case "end":
|
|
147
|
+
return INITIAL_STREAMING_STATE;
|
|
148
|
+
default:
|
|
149
|
+
return state;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
async function loadSubject() {
|
|
5
|
+
try {
|
|
6
|
+
const { createJiti } = await import("jiti");
|
|
7
|
+
return createJiti(import.meta.url).import("./subagent-widget.ts");
|
|
8
|
+
} catch {
|
|
9
|
+
return import("./subagent-widget.ts");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
parseSubagentSnapshot,
|
|
15
|
+
parseSubagentInspectReply,
|
|
16
|
+
widgetParsers,
|
|
17
|
+
SUBAGENT_ASYNC_JSON_PREFIX,
|
|
18
|
+
SUBAGENT_INSPECT_JSON_PREFIX,
|
|
19
|
+
} = await loadSubject();
|
|
20
|
+
|
|
21
|
+
const SNAPSHOT = {
|
|
22
|
+
kind: "pi-subagents.async-status-snapshot",
|
|
23
|
+
version: 1,
|
|
24
|
+
generatedAt: 1787999999000,
|
|
25
|
+
caps: { maxRuns: 20, maxChildrenPerNode: 8, maxDepth: 3, maxStringLength: 160, maxSerializedBytes: 32768 },
|
|
26
|
+
omitted: { runs: 0, children: 0, byteLimitExceeded: false },
|
|
27
|
+
runs: [
|
|
28
|
+
{
|
|
29
|
+
id: "run-1",
|
|
30
|
+
kind: "subagent",
|
|
31
|
+
label: "worker",
|
|
32
|
+
state: "running",
|
|
33
|
+
startedAt: 1787999998000,
|
|
34
|
+
activity: { currentTool: "read", toolCount: 3 },
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
test("解析正常快照,字段映射正确", () => {
|
|
40
|
+
const lines = [`${SUBAGENT_ASYNC_JSON_PREFIX}${JSON.stringify(SNAPSHOT)}`];
|
|
41
|
+
const parsed = parseSubagentSnapshot(lines);
|
|
42
|
+
assert.ok(parsed);
|
|
43
|
+
assert.equal(parsed.kind, "pi-subagents.async-status-snapshot");
|
|
44
|
+
assert.equal(parsed.runs.length, 1);
|
|
45
|
+
assert.equal(parsed.runs[0].label, "worker");
|
|
46
|
+
assert.equal(parsed.runs[0].state, "running");
|
|
47
|
+
assert.equal(parsed.runs[0].activity?.currentTool, "read");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("非 JSON 前缀返回 null", () => {
|
|
51
|
+
assert.equal(parseSubagentSnapshot(["plain text line"]), null);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("前缀对但 JSON 非法返回 null", () => {
|
|
55
|
+
assert.equal(parseSubagentSnapshot([`${SUBAGENT_ASYNC_JSON_PREFIX}{not-json`]), null);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("前缀对但 kind 不符返回 null", () => {
|
|
59
|
+
const lines = [`${SUBAGENT_ASYNC_JSON_PREFIX}${JSON.stringify({ kind: "other.kind", runs: [] })}`];
|
|
60
|
+
assert.equal(parseSubagentSnapshot(lines), null);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("runs 非数组返回 null", () => {
|
|
64
|
+
const lines = [
|
|
65
|
+
`${SUBAGENT_ASYNC_JSON_PREFIX}${JSON.stringify({ kind: "pi-subagents.async-status-snapshot", runs: "nope" })}`,
|
|
66
|
+
];
|
|
67
|
+
assert.equal(parseSubagentSnapshot(lines), null);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("解析 inspect 回包", () => {
|
|
71
|
+
const reply = {
|
|
72
|
+
kind: "pi-subagents.inspect-reply",
|
|
73
|
+
version: 1,
|
|
74
|
+
requestId: "req-1",
|
|
75
|
+
asyncId: "run-1",
|
|
76
|
+
status: "running",
|
|
77
|
+
messages: [{ role: "assistant", kind: "toolCall", name: "read", text: "read src/a.ts" }],
|
|
78
|
+
};
|
|
79
|
+
const parsed = parseSubagentInspectReply([`${SUBAGENT_INSPECT_JSON_PREFIX}${JSON.stringify(reply)}`]);
|
|
80
|
+
assert.ok(parsed);
|
|
81
|
+
assert.equal(parsed.requestId, "req-1");
|
|
82
|
+
assert.equal(parsed.messages?.[0].kind, "toolCall");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("注册表骨架含 subagent-async 解析器", () => {
|
|
86
|
+
assert.equal(typeof widgetParsers["subagent-async"], "function");
|
|
87
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* subagent widget 快照解析 + 注册表骨架。
|
|
3
|
+
*
|
|
4
|
+
* pi-subagents 在 RPC 模式下把异步子任务状态压成单行 JSON 推给 pi-web:
|
|
5
|
+
* widget key: "subagent-async"
|
|
6
|
+
* 首行前缀: "PI_SUBAGENT_ASYNC_JSON:" + JSON.stringify(AsyncStatusSnapshotV1)
|
|
7
|
+
*
|
|
8
|
+
* 这里只做「识别 + 解析」,渲染在 components/subagent/*。
|
|
9
|
+
* 注册表骨架:未来其他插件若也用「带标记的 JSON 行」推结构化内容,
|
|
10
|
+
* 在 widgetParsers 里加一条即可,pi-web 无需改渲染主流程。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const SUBAGENT_ASYNC_WIDGET_KEY = "subagent-async";
|
|
14
|
+
export const SUBAGENT_INSPECT_WIDGET_KEY = "subagent-inspect";
|
|
15
|
+
export const SUBAGENT_FLEET_STATUS_WIDGET_KEY = "subagent-fleet-status";
|
|
16
|
+
export const SUBAGENT_ASYNC_JSON_PREFIX = "PI_SUBAGENT_ASYNC_JSON:";
|
|
17
|
+
export const SUBAGENT_INSPECT_JSON_PREFIX = "PI_SUBAGENT_INSPECT_JSON:";
|
|
18
|
+
|
|
19
|
+
/** pi-subagents 推的、在 pi-web 里不应作为可见 widget 渲染的 key(纯数据传输 / TUI 专属) */
|
|
20
|
+
export const SUBAGENT_HIDDEN_WIDGET_KEYS = new Set([
|
|
21
|
+
SUBAGENT_ASYNC_WIDGET_KEY,
|
|
22
|
+
SUBAGENT_INSPECT_WIDGET_KEY,
|
|
23
|
+
SUBAGENT_FLEET_STATUS_WIDGET_KEY,
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
/** pi-subagents AsyncStatusSnapshotV1 的节点结构(对齐插件侧,只取展示所需字段) */
|
|
27
|
+
export interface SubagentSnapshotNode {
|
|
28
|
+
id: string;
|
|
29
|
+
kind: "subagent" | "workflow" | "step" | "host-step";
|
|
30
|
+
label: string;
|
|
31
|
+
state:
|
|
32
|
+
| "queued"
|
|
33
|
+
| "running"
|
|
34
|
+
| "complete"
|
|
35
|
+
| "failed"
|
|
36
|
+
| "partial"
|
|
37
|
+
| "paused"
|
|
38
|
+
| "stopped"
|
|
39
|
+
| "rejected";
|
|
40
|
+
startedAt?: number;
|
|
41
|
+
endedAt?: number;
|
|
42
|
+
activity?: {
|
|
43
|
+
state?: string;
|
|
44
|
+
currentTool?: string;
|
|
45
|
+
turnCount?: number;
|
|
46
|
+
toolCount?: number;
|
|
47
|
+
};
|
|
48
|
+
children?: SubagentSnapshotNode[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface SubagentSnapshot {
|
|
52
|
+
kind: string;
|
|
53
|
+
version: number;
|
|
54
|
+
generatedAt: number;
|
|
55
|
+
runs: SubagentSnapshotNode[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface SubagentInspectMessage {
|
|
59
|
+
role: string;
|
|
60
|
+
kind: "text" | "toolCall" | "toolResult";
|
|
61
|
+
text: string;
|
|
62
|
+
name?: string;
|
|
63
|
+
isError?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** pi-subagents.inspect-reply v1 */
|
|
67
|
+
export interface SubagentInspectReply {
|
|
68
|
+
kind: string;
|
|
69
|
+
version: number;
|
|
70
|
+
requestId: string;
|
|
71
|
+
asyncId?: string;
|
|
72
|
+
childId?: string;
|
|
73
|
+
status?: string;
|
|
74
|
+
label?: string;
|
|
75
|
+
task?: string;
|
|
76
|
+
messages?: SubagentInspectMessage[];
|
|
77
|
+
finalOutput?: string;
|
|
78
|
+
error?: { code: string; message: string };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parsePrefixedJson<T>(line: string | undefined, prefix: string): T | null {
|
|
82
|
+
if (!line || !line.startsWith(prefix)) return null;
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(line.slice(prefix.length)) as T;
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** 解析 subagent-async widget 快照;非本插件内容或解析失败返回 null */
|
|
91
|
+
export function parseSubagentSnapshot(lines: string[]): SubagentSnapshot | null {
|
|
92
|
+
const raw = parsePrefixedJson<SubagentSnapshot>(lines[0], SUBAGENT_ASYNC_JSON_PREFIX);
|
|
93
|
+
if (!raw || raw.kind !== "pi-subagents.async-status-snapshot" || !Array.isArray(raw.runs)) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return raw;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** 解析 subagent-inspect 回包(emit-then-retract 的 emit 帧) */
|
|
100
|
+
export function parseSubagentInspectReply(lines: string[]): SubagentInspectReply | null {
|
|
101
|
+
const raw = parsePrefixedJson<SubagentInspectReply>(lines[0], SUBAGENT_INSPECT_JSON_PREFIX);
|
|
102
|
+
if (!raw || raw.kind !== "pi-subagents.inspect-reply") return null;
|
|
103
|
+
return raw;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** 注册表骨架:widget key → 解析器。现在只注册 subagent,未来插件加条目即可。 */
|
|
107
|
+
export const widgetParsers: Record<string, (lines: string[]) => unknown | null> = {
|
|
108
|
+
[SUBAGENT_ASYNC_WIDGET_KEY]: parseSubagentSnapshot,
|
|
109
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// tldraw sync 房间服务器核心(可内嵌 Next.js server,也可独立进程)
|
|
3
|
+
// - 每看板一个 TLSocketRoom + SQLiteSyncStorage(sync.db,tablePrefix 按 board 隔离)
|
|
4
|
+
// - 纯文档同步:不懂业务,派生边/补卡由前端 reconcile(读业务数据 → CRDT 写文档)
|
|
5
|
+
// - 自定义 shape(session-card / task-card / sticky-note)两端注册
|
|
6
|
+
//
|
|
7
|
+
// 用法(内嵌):server.on("upgrade", handleSyncUpgrade) —— 返回 false 表示未处理,调用方转交 Next
|
|
8
|
+
// 用法(独立):见 scripts/sync-server.mjs
|
|
9
|
+
// ============================================================================
|
|
10
|
+
import { createRequire } from "node:module";
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
import { randomUUID } from "node:crypto";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { join, normalize } from "node:path";
|
|
15
|
+
|
|
16
|
+
const { DatabaseSync } = require("node:sqlite");
|
|
17
|
+
const { WebSocketServer } = require("ws");
|
|
18
|
+
const core = require("@tldraw/sync-core");
|
|
19
|
+
const tlschema = require("@tldraw/tlschema");
|
|
20
|
+
const T = require("@tldraw/validate").T;
|
|
21
|
+
|
|
22
|
+
// ---- agent dir(与 lib/sqlite-db.ts 同语义)----
|
|
23
|
+
export function getAgentDir() {
|
|
24
|
+
const envDir = process.env.PI_CODING_AGENT_DIR;
|
|
25
|
+
if (envDir) {
|
|
26
|
+
const expanded = envDir.startsWith("~") ? join(homedir(), envDir.slice(1)) : envDir;
|
|
27
|
+
return normalize(expanded);
|
|
28
|
+
}
|
|
29
|
+
return join(homedir(), ".pi", "agent");
|
|
30
|
+
}
|
|
31
|
+
export const SYNC_DB_FILE = join(getAgentDir(), "sync.db");
|
|
32
|
+
|
|
33
|
+
// ---- 自定义 shape props 校验器(与 components/canvas/{SessionCardShape,StickyNoteShape,TaskCardShape}.tsx 保持一致)----
|
|
34
|
+
const sessionCardProps = {
|
|
35
|
+
sessionId: T.string, title: T.string, projectName: T.string,
|
|
36
|
+
messageCount: T.number, lastReply: T.string, phase: T.string,
|
|
37
|
+
runningMs: T.number, endedAt: T.number, lastActivityAt: T.number,
|
|
38
|
+
stale: T.boolean, expanded: T.boolean, cwd: T.string, taskId: T.string,
|
|
39
|
+
w: T.number, h: T.number, expandedW: T.number, expandedH: T.number,
|
|
40
|
+
collapsedW: T.number, collapsedH: T.number,
|
|
41
|
+
};
|
|
42
|
+
const stickyNoteProps = {
|
|
43
|
+
text: T.string, w: T.number, h: T.number,
|
|
44
|
+
badge: T.string.optional(), createdAt: T.number.optional(),
|
|
45
|
+
};
|
|
46
|
+
const taskCardProps = {
|
|
47
|
+
cardId: T.string, number: T.number, name: T.string,
|
|
48
|
+
description: T.string.optional(), readyStatus: T.string, execStatus: T.string, priority: T.number,
|
|
49
|
+
// due:业务层允许 number | null(无截止=null)。shape 写入可能带 null(草稿初始化/API 回读),
|
|
50
|
+
// 必须 nullable 否则 sync 校验失败 → INVALID_RECORD → 画布永远 loading(派发后看板死掉根因)
|
|
51
|
+
due: T.number.nullable().optional(), expanded: T.boolean,
|
|
52
|
+
w: T.number, h: T.number, expandedW: T.number, expandedH: T.number,
|
|
53
|
+
collapsedW: T.number, collapsedH: T.number,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const syncSchema = tlschema.createTLSchema({
|
|
57
|
+
shapes: {
|
|
58
|
+
...tlschema.defaultShapeSchemas,
|
|
59
|
+
"session-card": { props: sessionCardProps },
|
|
60
|
+
"sticky-note": { props: stickyNoteProps },
|
|
61
|
+
"task-card": { props: taskCardProps },
|
|
62
|
+
},
|
|
63
|
+
bindings: { ...tlschema.defaultBindingSchemas },
|
|
64
|
+
assets: { ...tlschema.defaultAssetSchemas },
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// ---- 房间管理:每 boardId 一个 TLSocketRoom;SQLite 持久化(同一 sync.db,tablePrefix 隔离)----
|
|
68
|
+
// 单一 DatabaseSync 连接(node:sqlite 同步,单连接线程安全)
|
|
69
|
+
const db = new DatabaseSync(SYNC_DB_FILE);
|
|
70
|
+
const rooms = new Map();
|
|
71
|
+
|
|
72
|
+
function safePrefix(boardId) {
|
|
73
|
+
return `board_${boardId.replace(/[^a-zA-Z0-9_]/g, "_")}_`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getSyncRoom(boardId) {
|
|
77
|
+
let room = rooms.get(boardId);
|
|
78
|
+
if (room) return room;
|
|
79
|
+
const storage = new core.SQLiteSyncStorage({
|
|
80
|
+
sql: new core.NodeSqliteWrapper(db, { tablePrefix: safePrefix(boardId) }),
|
|
81
|
+
});
|
|
82
|
+
room = new core.TLSocketRoom({
|
|
83
|
+
// clientTimeout 不能传 0:0 会让 pruneSessions 把所有 Connected 会话当超时踢掉
|
|
84
|
+
// (timeSince > 0 恒真)→ 客户端重连风暴。传 undefined 用默认 20s 空闲超时,
|
|
85
|
+
// ping 每 5s 保活,正常不会触发。
|
|
86
|
+
storage, schema: syncSchema, clientTimeout: undefined,
|
|
87
|
+
// INVALID_RECORD 等致命错误会走 log.error(带 TLSyncError 对象)——hook 它打印详情
|
|
88
|
+
log: {
|
|
89
|
+
warn: (...a) => console.warn("[sync:room]", ...a),
|
|
90
|
+
error: (...a) => {
|
|
91
|
+
const err = a[0];
|
|
92
|
+
const detail = err instanceof Error ? `${err.message}${err.reason ? ` [reason=${err.reason}]` : ""}` : JSON.stringify(a).slice(0, 500);
|
|
93
|
+
console.error("[sync:room]", detail);
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
rooms.set(boardId, room);
|
|
98
|
+
return room;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getSyncRoomCount() {
|
|
102
|
+
return rooms.size;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// noServer WSS:不做端口监听,只负责把 upgrade 的 net.Socket 握手成 ws 实例
|
|
106
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 处理一条 WebSocket upgrade 请求(挂在 Node http server 的 "upgrade" 事件)。
|
|
110
|
+
* 路径匹配 /connect/:boardId → 房间接管;否则返回 false(调用方转交 Next 自身 handler,如 HMR)。
|
|
111
|
+
* @returns {boolean} 是否已处理
|
|
112
|
+
*/
|
|
113
|
+
export function handleSyncUpgrade(req, socket, head) {
|
|
114
|
+
let url;
|
|
115
|
+
try {
|
|
116
|
+
url = new URL(req.url, "http://localhost");
|
|
117
|
+
} catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
const m = url.pathname.match(/^\/connect\/([^/]+)$/);
|
|
121
|
+
if (!m) return false;
|
|
122
|
+
|
|
123
|
+
const boardId = decodeURIComponent(m[1]);
|
|
124
|
+
const room = getSyncRoom(boardId);
|
|
125
|
+
// sessionId 必须复用客户端提供的(useSync 每次重连带固定 TAB_ID)——
|
|
126
|
+
// 若随机生成,重连被当成新会话,服务器端 session 堆积、协议错乱。
|
|
127
|
+
const sessionId = url.searchParams.get("sessionId") ?? randomUUID();
|
|
128
|
+
|
|
129
|
+
// 握手成功后 ws 即为 EventTarget 兼容实例;handleSocketConnect 自动挂
|
|
130
|
+
// message/close/error 监听。绝不手动再 ws.on("message")——否则每条消息处理
|
|
131
|
+
// 两次 → push_result 双发 → 重连死循环。
|
|
132
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
133
|
+
room.handleSocketConnect({ sessionId, socket: ws, isReadonly: false });
|
|
134
|
+
console.log(`[sync] ${boardId}: client ${sessionId.slice(0, 8)} connected (${room.getNumActiveSessions()})`);
|
|
135
|
+
});
|
|
136
|
+
return true;
|
|
137
|
+
}
|