pi-tau-web-server 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +303 -0
- package/bin/auth.js +96 -0
- package/bin/config.js +99 -0
- package/bin/model-utils.js +134 -0
- package/bin/server-main.js +1189 -0
- package/bin/sessions.js +492 -0
- package/bin/tau.js +8 -0
- package/bin/tree.js +248 -0
- package/bin/types.js +2 -0
- package/package.json +74 -0
- package/public/app-main.js +2025 -0
- package/public/app-types.js +1 -0
- package/public/app.js +1 -0
- package/public/command-palette.js +42 -0
- package/public/dialogs.js +199 -0
- package/public/file-browser.js +196 -0
- package/public/icons/apple-touch-icon.png +0 -0
- package/public/icons/favicon-16.png +0 -0
- package/public/icons/favicon-32.png +0 -0
- package/public/icons/tau-192.png +0 -0
- package/public/icons/tau-512.png +0 -0
- package/public/icons/tau-logo.png +0 -0
- package/public/icons/tau-logo.svg +13 -0
- package/public/icons/tau-maskable-512.png +0 -0
- package/public/icons/tau-new.png +0 -0
- package/public/index.html +264 -0
- package/public/launcher-panel.js +54 -0
- package/public/launcher.js +84 -0
- package/public/manifest.json +28 -0
- package/public/markdown.js +336 -0
- package/public/message-renderer.js +268 -0
- package/public/model-picker.js +478 -0
- package/public/session-sidebar.js +460 -0
- package/public/session-stats-card.js +123 -0
- package/public/state.js +81 -0
- package/public/style.css +3864 -0
- package/public/sw.js +66 -0
- package/public/themes.js +72 -0
- package/public/tool-card.js +317 -0
- package/public/tree-view.js +474 -0
- package/public/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/public/vendor/katex/katex.min.css +1 -0
- package/public/vendor/katex/katex.min.js +1 -0
- package/public/voice-input.js +74 -0
- package/public/websocket-client.js +156 -0
- package/scripts/copy-katex.mjs +32 -0
- package/src/pi-extension/tau-tree.ts +71 -0
- package/src/public/app-main.ts +2190 -0
- package/src/public/app-types.ts +96 -0
- package/src/public/app.ts +1 -0
- package/src/public/command-palette.ts +53 -0
- package/src/public/dialogs.ts +251 -0
- package/src/public/file-browser.ts +224 -0
- package/src/public/launcher-panel.ts +68 -0
- package/src/public/launcher.ts +101 -0
- package/src/public/legacy-dom.d.ts +29 -0
- package/src/public/markdown.ts +372 -0
- package/src/public/message-renderer.ts +311 -0
- package/src/public/model-picker.ts +500 -0
- package/src/public/session-sidebar.ts +522 -0
- package/src/public/session-stats-card.ts +176 -0
- package/src/public/state.ts +96 -0
- package/src/public/sw.ts +79 -0
- package/src/public/themes.ts +73 -0
- package/src/public/tool-card.ts +375 -0
- package/src/public/tree-view.ts +527 -0
- package/src/public/voice-input.ts +98 -0
- package/src/public/websocket-client.ts +165 -0
- package/src/server/auth.ts +88 -0
- package/src/server/config.ts +88 -0
- package/src/server/model-utils.ts +122 -0
- package/src/server/server-main.ts +1004 -0
- package/src/server/sessions.ts +481 -0
- package/src/server/tau.ts +9 -0
- package/src/server/tree.ts +288 -0
- package/src/server/types.ts +68 -0
- package/tsconfig.json +3 -0
- package/tsconfig.public.json +15 -0
- package/tsconfig.server.json +16 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Session-tree navigation for the backend-local `navigate_tree` RPC command.
|
|
3
|
+
*
|
|
4
|
+
* pi stores a session as an append-only JSONL tree (entries have id/parentId;
|
|
5
|
+
* the current position is the "leaf"). pi's native RPC protocol only exposes
|
|
6
|
+
* READ-ONLY tree commands (get_tree / get_entries) — there is no command that
|
|
7
|
+
* moves the leaf — so tau bundles a small pi extension
|
|
8
|
+
* (src/pi-extension/tau-tree.ts, loaded into every child via `--extension`)
|
|
9
|
+
* that registers a `/tau-tree-navigate <entryId>` command. The handler calls
|
|
10
|
+
* pi's own ctx.navigateTree() — the same API the TUI /tree uses — so the move
|
|
11
|
+
* happens in-process, respects other extensions' session_before_tree hooks,
|
|
12
|
+
* and persists itself by appending a `custom` marker entry (pi derives the
|
|
13
|
+
* leaf from the LAST entry on load, so a bare in-memory move would not
|
|
14
|
+
* survive a restart). Matched extension commands never reach the LLM and
|
|
15
|
+
* never append a user message, so neither the command text nor the marker is
|
|
16
|
+
* visible to the model.
|
|
17
|
+
*
|
|
18
|
+
* navigateTree below drives that command over RPC: it resolves the target
|
|
19
|
+
* from a preflight get_tree, sends the prompt, then re-fetches the tree to
|
|
20
|
+
* refresh session.entries and VERIFY the move — a handler error does not fail
|
|
21
|
+
* the prompt response (pi reports it as an `extension_error` event instead),
|
|
22
|
+
* so the follow-up get_tree is the real success check.
|
|
23
|
+
*
|
|
24
|
+
* Selection semantics mirror pi's /tree (docs/sessions.md "Selection
|
|
25
|
+
* Behavior"); the extension delegates them to ctx.navigateTree, and tau
|
|
26
|
+
* re-derives them here (selectNavigationTarget) only to detect no-ops, verify
|
|
27
|
+
* the new leaf, and hand the client its editorText — RPC-mode extensions
|
|
28
|
+
* cannot fill an editor:
|
|
29
|
+
* - user (or custom) MESSAGE entry → the leaf moves to the entry's PARENT
|
|
30
|
+
* and the message text goes back as editorText so the client can put it
|
|
31
|
+
* in the input box for edit + resubmit (root user message → reset to an
|
|
32
|
+
* empty conversation).
|
|
33
|
+
* - any other entry → the leaf moves TO that entry, no editorText.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import type { JsonRecord, RpcCommand, RpcResponse } from './types.js';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Name of the slash command registered by tau's bundled pi extension.
|
|
40
|
+
* Keep in sync with NAVIGATE_COMMAND in src/pi-extension/tau-tree.ts.
|
|
41
|
+
*/
|
|
42
|
+
export const NAVIGATE_COMMAND = 'tau-tree-navigate';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* customType of the marker entries the extension appends to persist a leaf
|
|
46
|
+
* move. Keep in sync with src/pi-extension/tau-tree.ts (and the frontend
|
|
47
|
+
* constant in src/public/tree-view.ts, which hides markers from the tree).
|
|
48
|
+
*/
|
|
49
|
+
export const NAVIGATION_MARKER_TYPE = 'tau:navigate-tree';
|
|
50
|
+
|
|
51
|
+
/** Loosely-typed session entry as stored in the JSONL file / returned by get_tree. */
|
|
52
|
+
export type TreeEntry = {
|
|
53
|
+
id: string;
|
|
54
|
+
parentId: string | null;
|
|
55
|
+
type?: string;
|
|
56
|
+
message?: { role?: string; content?: unknown };
|
|
57
|
+
content?: unknown;
|
|
58
|
+
customType?: string;
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type SessionTreeNodeLike = { entry: TreeEntry; children?: SessionTreeNodeLike[] };
|
|
63
|
+
|
|
64
|
+
export type NavigationTarget = {
|
|
65
|
+
/** Entry id the leaf should move to; null means reset to an empty conversation. */
|
|
66
|
+
leafTargetId: string | null;
|
|
67
|
+
/** Message text for the client's input box (user/custom message targets only). */
|
|
68
|
+
editorText?: string;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
function textFromContent(content: unknown): string {
|
|
72
|
+
if (typeof content === 'string') return content;
|
|
73
|
+
if (!Array.isArray(content)) return '';
|
|
74
|
+
return content
|
|
75
|
+
.filter((b): b is { type?: unknown; text?: unknown } => !!b && typeof b === 'object')
|
|
76
|
+
.filter((b) => b.type === 'text' && typeof b.text === 'string')
|
|
77
|
+
.map((b) => b.text as string)
|
|
78
|
+
.join('\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Decide where the leaf should move for a navigation target, mirroring pi's
|
|
83
|
+
* /tree selection behavior. Pure function — exported for tests.
|
|
84
|
+
*/
|
|
85
|
+
export function selectNavigationTarget(entry: TreeEntry): NavigationTarget {
|
|
86
|
+
if (entry.type === 'message' && entry.message?.role === 'user') {
|
|
87
|
+
return { leafTargetId: entry.parentId ?? null, editorText: textFromContent(entry.message.content) };
|
|
88
|
+
}
|
|
89
|
+
if (entry.type === 'custom_message') {
|
|
90
|
+
return { leafTargetId: entry.parentId ?? null, editorText: textFromContent(entry.content) };
|
|
91
|
+
}
|
|
92
|
+
return { leafTargetId: entry.id };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Flatten get_tree nodes (possibly multiple roots) into an id → entry map. */
|
|
96
|
+
export function flattenTree(nodes: SessionTreeNodeLike[] | undefined | null): Map<string, TreeEntry> {
|
|
97
|
+
const byId = new Map<string, TreeEntry>();
|
|
98
|
+
const stack = [...(nodes || [])];
|
|
99
|
+
while (stack.length) {
|
|
100
|
+
const node = stack.pop();
|
|
101
|
+
if (!node || !node.entry || typeof node.entry.id !== 'string') continue;
|
|
102
|
+
byId.set(node.entry.id, node.entry);
|
|
103
|
+
if (node.children && node.children.length) stack.push(...node.children);
|
|
104
|
+
}
|
|
105
|
+
return byId;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Walk root→leaf along parentId links; returns [] for a null/unknown leaf. */
|
|
109
|
+
export function pathFromRoot(byId: Map<string, TreeEntry>, leafId: string | null | undefined): TreeEntry[] {
|
|
110
|
+
const path: TreeEntry[] = [];
|
|
111
|
+
const seen = new Set<string>();
|
|
112
|
+
let current = leafId ? byId.get(leafId) : undefined;
|
|
113
|
+
while (current && !seen.has(current.id)) {
|
|
114
|
+
seen.add(current.id);
|
|
115
|
+
path.push(current);
|
|
116
|
+
current = current.parentId ? byId.get(current.parentId) : undefined;
|
|
117
|
+
}
|
|
118
|
+
path.reverse();
|
|
119
|
+
return path;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** True when `leafId` is `ancestorId` or one of its descendants. */
|
|
123
|
+
export function leafDescendsFrom(byId: Map<string, TreeEntry>, leafId: string | null | undefined, ancestorId: string): boolean {
|
|
124
|
+
const seen = new Set<string>();
|
|
125
|
+
let current = leafId ? byId.get(leafId) : undefined;
|
|
126
|
+
while (current && !seen.has(current.id)) {
|
|
127
|
+
if (current.id === ancestorId) return true;
|
|
128
|
+
seen.add(current.id);
|
|
129
|
+
current = current.parentId ? byId.get(current.parentId) : undefined;
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Structural view of PiRpcSession that navigateTree needs. Kept structural so
|
|
136
|
+
* tests can drive the flow with a fake session (no LLM, no child process).
|
|
137
|
+
*/
|
|
138
|
+
export type NavigableSession = {
|
|
139
|
+
id: string;
|
|
140
|
+
isStreaming: boolean;
|
|
141
|
+
entries: JsonRecord[];
|
|
142
|
+
send: (command: RpcCommand, opts?: { timeoutMs?: number }) => Promise<RpcResponse>;
|
|
143
|
+
snapshot: () => JsonRecord;
|
|
144
|
+
manager: { broadcast: (data: unknown) => void; broadcastUpdated: (id: string) => void };
|
|
145
|
+
/**
|
|
146
|
+
* Last `extension_error` event text reported by the pi child (set by
|
|
147
|
+
* PiRpcSession.handleEvent). The extension command's errors surface here
|
|
148
|
+
* instead of failing the prompt response, so navigateTree clears it before
|
|
149
|
+
* prompting and reports it when the leaf verification fails.
|
|
150
|
+
*/
|
|
151
|
+
lastExtensionError?: string | null;
|
|
152
|
+
/** Set once navigateTree has confirmed the child loaded tau's extension. */
|
|
153
|
+
navigateCommandChecked?: boolean;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Session ids with a navigate_tree currently in flight. navigateTree runs a
|
|
157
|
+
// multi-await check→navigate→verify sequence, so without this a second
|
|
158
|
+
// navigation (or a prompt — see the guard in server-main's handleRpcCommand)
|
|
159
|
+
// could interleave with it and land its entries on the wrong branch.
|
|
160
|
+
const navigationsInFlight = new Set<string>();
|
|
161
|
+
|
|
162
|
+
/** True while a navigate_tree is moving this session's leaf. */
|
|
163
|
+
export function isTreeNavigationInProgress(sessionId: string): boolean {
|
|
164
|
+
return navigationsInFlight.has(sessionId);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function assertNotStreaming(session: NavigableSession): void {
|
|
168
|
+
if (session.isStreaming) {
|
|
169
|
+
throw new Error('Cannot navigate the session tree while the agent is streaming; abort or wait for the turn to finish');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Once per child process, confirm pi actually registered tau's extension
|
|
175
|
+
* command. Without this check a broken install (extension file missing or
|
|
176
|
+
* failing to load) would make the `/tau-tree-navigate …` text fall through
|
|
177
|
+
* pi's command matching and be sent to the LLM as a literal user prompt.
|
|
178
|
+
*/
|
|
179
|
+
async function assertNavigateCommandAvailable(session: NavigableSession): Promise<void> {
|
|
180
|
+
if (session.navigateCommandChecked) return;
|
|
181
|
+
const resp = await session.send({ type: 'get_commands' }, { timeoutMs: 15000 });
|
|
182
|
+
if (resp.success === false) {
|
|
183
|
+
throw new Error(`pi could not list its commands: ${resp.error || 'unknown error'}`);
|
|
184
|
+
}
|
|
185
|
+
const commands = ((resp.data || {}) as { commands?: Array<{ name?: string; source?: string }> }).commands || [];
|
|
186
|
+
const found = commands.some((c) => c.name === NAVIGATE_COMMAND && c.source === 'extension');
|
|
187
|
+
if (!found) {
|
|
188
|
+
throw new Error(`The pi child did not load tau's ${NAVIGATE_COMMAND} extension; cannot navigate the session tree`);
|
|
189
|
+
}
|
|
190
|
+
session.navigateCommandChecked = true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Full navigate_tree flow for a live session: resolve the target from a
|
|
195
|
+
* preflight get_tree, have the child move its leaf in-process via the bundled
|
|
196
|
+
* extension's /tau-tree-navigate command, re-fetch the tree to confirm the
|
|
197
|
+
* move, refresh session.entries to the new active path, and broadcast a fresh
|
|
198
|
+
* `live_session_snapshot` (plus `live_session_updated`) so every connected
|
|
199
|
+
* browser re-renders the conversation.
|
|
200
|
+
*
|
|
201
|
+
* The streaming guard cannot be made fully airtight from tau's side (a prompt
|
|
202
|
+
* whose agent_start has not yet round-tripped through the child is invisible
|
|
203
|
+
* here), so the flow is defensive instead: one navigation per session at a
|
|
204
|
+
* time, the extension re-checks idleness in-process (ctx.isIdle()) right
|
|
205
|
+
* before moving the leaf, prompts are refused by handleRpcCommand while a
|
|
206
|
+
* navigation is in flight, and if the child still turns out to have moved on
|
|
207
|
+
* (leaf verification fails) the clients are re-synced to the child's real
|
|
208
|
+
* state before the error is reported.
|
|
209
|
+
*/
|
|
210
|
+
export async function navigateTree(session: NavigableSession, entryId: string): Promise<{ editorText?: string }> {
|
|
211
|
+
if (navigationsInFlight.has(session.id)) {
|
|
212
|
+
throw new Error('Another tree navigation for this session is already in progress');
|
|
213
|
+
}
|
|
214
|
+
navigationsInFlight.add(session.id);
|
|
215
|
+
try {
|
|
216
|
+
assertNotStreaming(session);
|
|
217
|
+
// Preflight: fetch the tree to resolve the target entry (clear error for
|
|
218
|
+
// unknown ids), detect no-ops, and derive editorText locally — RPC-mode
|
|
219
|
+
// extensions cannot fill the client's editor, and the /tau-tree-navigate
|
|
220
|
+
// prompt response cannot carry handler data.
|
|
221
|
+
let treeResp = await session.send({ type: 'get_tree' }, { timeoutMs: 15000 });
|
|
222
|
+
if (treeResp.success === false) {
|
|
223
|
+
throw new Error(`pi did not answer the pre-navigation tree check: ${treeResp.error || 'unknown error'}`);
|
|
224
|
+
}
|
|
225
|
+
let treeData = (treeResp.data || {}) as { tree?: SessionTreeNodeLike[]; leafId?: string | null };
|
|
226
|
+
let byId = flattenTree(treeData.tree);
|
|
227
|
+
const entry = byId.get(entryId);
|
|
228
|
+
if (!entry) throw new Error(`Entry ${entryId} not found in the session tree`);
|
|
229
|
+
const target = selectNavigationTarget(entry);
|
|
230
|
+
const previousLeafId = treeData.leafId ?? null;
|
|
231
|
+
// Already there: pi's navigateTree no-ops when the selected entry IS the
|
|
232
|
+
// current leaf, and moving to the current position would be pure churn —
|
|
233
|
+
// skip the round-trip (but still hand back the editorText, so selecting
|
|
234
|
+
// the leaf user message keeps prefilling the composer).
|
|
235
|
+
if (entryId === previousLeafId || (target.leafTargetId !== null && target.leafTargetId === previousLeafId)) {
|
|
236
|
+
return target.editorText !== undefined ? { editorText: target.editorText } : {};
|
|
237
|
+
}
|
|
238
|
+
await assertNavigateCommandAvailable(session);
|
|
239
|
+
// A turn may have started while the preflight round-tripped; re-check
|
|
240
|
+
// here for a fast, precise error (the extension re-checks in-process via
|
|
241
|
+
// ctx.isIdle() as the authoritative guard).
|
|
242
|
+
assertNotStreaming(session);
|
|
243
|
+
session.lastExtensionError = null;
|
|
244
|
+
const prompted = await session.send({ type: 'prompt', message: `/${NAVIGATE_COMMAND} ${entryId}` }, { timeoutMs: 30000 });
|
|
245
|
+
if (prompted.success === false) {
|
|
246
|
+
throw new Error(`pi refused the tree-navigation command: ${prompted.error || 'unknown error'}`);
|
|
247
|
+
}
|
|
248
|
+
// The prompt ack only means the handler ran — its errors surface as
|
|
249
|
+
// `extension_error` events, not as a failed response — so the follow-up
|
|
250
|
+
// get_tree is the real success check.
|
|
251
|
+
treeResp = await session.send({ type: 'get_tree' }, { timeoutMs: 30000 });
|
|
252
|
+
if (treeResp.success === false) {
|
|
253
|
+
// The child MAY have moved its leaf; we just cannot re-derive the
|
|
254
|
+
// entries. At least nudge clients so they refetch metadata instead of
|
|
255
|
+
// silently rendering the old branch forever.
|
|
256
|
+
session.manager.broadcastUpdated(session.id);
|
|
257
|
+
throw new Error(`pi could not report the session tree after navigating: ${treeResp.error || 'unknown error'}`);
|
|
258
|
+
}
|
|
259
|
+
treeData = (treeResp.data || {}) as { tree?: SessionTreeNodeLike[]; leafId?: string | null };
|
|
260
|
+
byId = flattenTree(treeData.tree);
|
|
261
|
+
const newLeafId = treeData.leafId ?? null;
|
|
262
|
+
// Refresh entries and broadcast BEFORE verifying the leaf: at this point
|
|
263
|
+
// the child is authoritative, so even when verification fails below every
|
|
264
|
+
// client must be re-rendered onto whatever the child actually has.
|
|
265
|
+
session.entries = pathFromRoot(byId, newLeafId) as unknown as JsonRecord[];
|
|
266
|
+
session.manager.broadcast({ type: 'live_session_snapshot', sessionId: session.id, ...session.snapshot() });
|
|
267
|
+
session.manager.broadcastUpdated(session.id);
|
|
268
|
+
// On success the extension appended a marker at the navigation target and
|
|
269
|
+
// that marker IS the child's new leaf (no reload happens, so nothing else
|
|
270
|
+
// gets appended in between). Requiring the leaf to have changed AND to be
|
|
271
|
+
// a marker parented at the target rejects both a silent no-move (handler
|
|
272
|
+
// error) and a leaf that moved somewhere unexpected.
|
|
273
|
+
const newLeaf = newLeafId ? byId.get(newLeafId) : undefined;
|
|
274
|
+
const moved =
|
|
275
|
+
newLeafId !== previousLeafId &&
|
|
276
|
+
newLeaf !== undefined &&
|
|
277
|
+
newLeaf.type === 'custom' &&
|
|
278
|
+
newLeaf.customType === NAVIGATION_MARKER_TYPE &&
|
|
279
|
+
(newLeaf.parentId ?? null) === target.leafTargetId;
|
|
280
|
+
if (!moved) {
|
|
281
|
+
const detail = session.lastExtensionError ? `: ${session.lastExtensionError}` : '';
|
|
282
|
+
throw new Error(`pi did not move the session leaf to the expected entry${detail}`);
|
|
283
|
+
}
|
|
284
|
+
return target.editorText !== undefined ? { editorText: target.editorText } : {};
|
|
285
|
+
} finally {
|
|
286
|
+
navigationsInFlight.delete(session.id);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export type JsonRecord = Record<string, unknown>;
|
|
2
|
+
export type TauArgs = Record<string, string | boolean | undefined> & {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
port?: string;
|
|
5
|
+
host?: string;
|
|
6
|
+
'projects-dir'?: string;
|
|
7
|
+
};
|
|
8
|
+
export type TauSettingsFile = {
|
|
9
|
+
tau?: {
|
|
10
|
+
port?: string | number;
|
|
11
|
+
host?: string;
|
|
12
|
+
user?: string;
|
|
13
|
+
pass?: string;
|
|
14
|
+
authEnabled?: boolean;
|
|
15
|
+
cookieSecret?: string;
|
|
16
|
+
projectsDir?: string;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export type TauSettings = {
|
|
21
|
+
port: number;
|
|
22
|
+
host: string;
|
|
23
|
+
user: string;
|
|
24
|
+
pass: string;
|
|
25
|
+
authEnabled?: boolean;
|
|
26
|
+
cookieSecret?: string;
|
|
27
|
+
projectsDir: string;
|
|
28
|
+
};
|
|
29
|
+
export type ModelIdentity = {
|
|
30
|
+
provider?: string;
|
|
31
|
+
id?: string;
|
|
32
|
+
name?: string;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
};
|
|
35
|
+
export type ParsedModelSpec = { model: ModelIdentity | null; level: string | null };
|
|
36
|
+
export type RpcCommand = {
|
|
37
|
+
id?: string;
|
|
38
|
+
type?: string;
|
|
39
|
+
sessionId?: string;
|
|
40
|
+
filePath?: string;
|
|
41
|
+
outputPath?: string;
|
|
42
|
+
cwd?: string;
|
|
43
|
+
model?: string;
|
|
44
|
+
provider?: string;
|
|
45
|
+
modelId?: string;
|
|
46
|
+
level?: string;
|
|
47
|
+
name?: string;
|
|
48
|
+
enabled?: boolean;
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
};
|
|
51
|
+
export type RpcResponse = JsonRecord;
|
|
52
|
+
export type PendingCommand = {
|
|
53
|
+
resolve: (value: RpcResponse) => void;
|
|
54
|
+
reject: (reason: unknown) => void;
|
|
55
|
+
timer: ReturnType<typeof setTimeout>;
|
|
56
|
+
command?: string;
|
|
57
|
+
};
|
|
58
|
+
export type LiveClient = {
|
|
59
|
+
readyState: number;
|
|
60
|
+
send(payload: string): void;
|
|
61
|
+
close(code?: number, reason?: string): void;
|
|
62
|
+
terminate(): void;
|
|
63
|
+
ping(): void;
|
|
64
|
+
isAlive?: boolean;
|
|
65
|
+
};
|
|
66
|
+
export type StatusError = Error & { status?: number; stderr?: string };
|
|
67
|
+
|
|
68
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"rootDir": "src/public",
|
|
7
|
+
"outDir": "public",
|
|
8
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
9
|
+
"strict": true,
|
|
10
|
+
"declaration": false,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/public/**/*.ts"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"rootDir": "src/server",
|
|
7
|
+
"outDir": "bin",
|
|
8
|
+
"types": ["node"],
|
|
9
|
+
"strict": true,
|
|
10
|
+
"declaration": false,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/server/**/*.ts"]
|
|
16
|
+
}
|