@pimote/pimote 0.1.0 → 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 (37) hide show
  1. package/README.md +3 -1
  2. package/client/build/_app/immutable/assets/0.DBrr7n4n.css +2 -0
  3. package/client/build/_app/immutable/assets/2.DE6k3bQj.css +1 -0
  4. package/client/build/_app/immutable/chunks/5vSSf6qG.js +5 -0
  5. package/client/build/_app/immutable/chunks/{BTSGQ0LP.js → B8lQCytv.js} +1 -1
  6. package/client/build/_app/immutable/chunks/CT6ckxpD.js +1 -0
  7. package/client/build/_app/immutable/chunks/DlJOVoUQ.js +1 -0
  8. package/client/build/_app/immutable/chunks/YxmLwfhj.js +1 -0
  9. package/client/build/_app/immutable/chunks/{L5t1qIFa.js → uZO1iyJZ.js} +2 -2
  10. package/client/build/_app/immutable/chunks/yWVx3W2o.js +1 -0
  11. package/client/build/_app/immutable/entry/app.CNzpBgAg.js +2 -0
  12. package/client/build/_app/immutable/entry/start.DYkTAHh1.js +1 -0
  13. package/client/build/_app/immutable/nodes/0.DNlQhEb_.js +10 -0
  14. package/client/build/_app/immutable/nodes/1.B8zmHMre.js +1 -0
  15. package/client/build/_app/immutable/nodes/2.W9yV4-x2.js +54 -0
  16. package/client/build/_app/version.json +1 -1
  17. package/client/build/index.html +8 -8
  18. package/package.json +3 -3
  19. package/patches/{@mariozechner+pi-coding-agent+0.65.0.patch → @mariozechner+pi-coding-agent+0.67.6.patch} +4 -4
  20. package/server/dist/folder-index.js +8 -4
  21. package/server/dist/git-branch.js +32 -0
  22. package/server/dist/message-mapper.js +65 -2
  23. package/server/dist/server.js +3 -0
  24. package/server/dist/session-manager.js +35 -4
  25. package/server/dist/ws-handler.js +271 -31
  26. package/client/build/_app/immutable/assets/0.CsjXJ2oE.css +0 -2
  27. package/client/build/_app/immutable/assets/2.CIRqqeIr.css +0 -1
  28. package/client/build/_app/immutable/chunks/BN18Mjoo.js +0 -1
  29. package/client/build/_app/immutable/chunks/BTW4yCoz.js +0 -1
  30. package/client/build/_app/immutable/chunks/CnuZs6QA.js +0 -1
  31. package/client/build/_app/immutable/chunks/CvWR-ThL.js +0 -1
  32. package/client/build/_app/immutable/chunks/D5m3x_L9.js +0 -5
  33. package/client/build/_app/immutable/entry/app.BjHwmkZK.js +0 -2
  34. package/client/build/_app/immutable/entry/start.CZeUhs5D.js +0 -1
  35. package/client/build/_app/immutable/nodes/0.HHf1ps7Y.js +0 -5
  36. package/client/build/_app/immutable/nodes/1.CjbUSBAL.js +0 -1
  37. package/client/build/_app/immutable/nodes/2.C22f_gRz.js +0 -49
@@ -12,12 +12,12 @@
12
12
  <link rel="icon" type="image/png" sizes="192x192" href="/pwa/icon-192.png" />
13
13
  <link rel="icon" type="image/png" sizes="512x512" href="/pwa/icon-512.png" />
14
14
  <link rel="apple-touch-icon" href="/pwa/icon-192.png" />
15
- <link href="/_app/immutable/entry/start.CZeUhs5D.js" rel="modulepreload">
16
- <link href="/_app/immutable/chunks/BN18Mjoo.js" rel="modulepreload">
17
- <link href="/_app/immutable/chunks/CnuZs6QA.js" rel="modulepreload">
18
- <link href="/_app/immutable/chunks/BTSGQ0LP.js" rel="modulepreload">
15
+ <link href="/_app/immutable/entry/start.DYkTAHh1.js" rel="modulepreload">
16
+ <link href="/_app/immutable/chunks/CT6ckxpD.js" rel="modulepreload">
17
+ <link href="/_app/immutable/chunks/DlJOVoUQ.js" rel="modulepreload">
18
+ <link href="/_app/immutable/chunks/B8lQCytv.js" rel="modulepreload">
19
19
  <link href="/_app/immutable/chunks/5FogVG_p.js" rel="modulepreload">
20
- <link href="/_app/immutable/entry/app.BjHwmkZK.js" rel="modulepreload">
20
+ <link href="/_app/immutable/entry/app.CNzpBgAg.js" rel="modulepreload">
21
21
  <link href="/_app/immutable/chunks/CHncfsjL.js" rel="modulepreload">
22
22
  <link href="/_app/immutable/chunks/D1hYfEew.js" rel="modulepreload">
23
23
 
@@ -26,15 +26,15 @@
26
26
  <div style="display: contents">
27
27
  <script>
28
28
  {
29
- __sveltekit_1otbek3 = {
29
+ __sveltekit_brd6po = {
30
30
  base: ""
31
31
  };
32
32
 
33
33
  const element = document.currentScript.parentElement;
34
34
 
35
35
  Promise.all([
36
- import("/_app/immutable/entry/start.CZeUhs5D.js"),
37
- import("/_app/immutable/entry/app.BjHwmkZK.js")
36
+ import("/_app/immutable/entry/start.DYkTAHh1.js"),
37
+ import("/_app/immutable/entry/app.CNzpBgAg.js")
38
38
  ]).then(([kit, app]) => {
39
39
  kit.start(app, element);
40
40
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pimote/pimote",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Web client and embedded server for pi with multi-session browser access, streaming, and extension UI support",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  "client/build/**",
31
31
  "server/dist/**/*.js",
32
32
  "scripts/postinstall-patches.mjs",
33
- "patches/@mariozechner+pi-coding-agent+0.65.0.patch",
33
+ "patches/@mariozechner+pi-coding-agent+0.67.6.patch",
34
34
  "README.md",
35
35
  "LICENSE"
36
36
  ],
@@ -73,7 +73,7 @@
73
73
  },
74
74
  "dependencies": {
75
75
  "@fontsource-variable/jetbrains-mono": "^5.2.8",
76
- "@mariozechner/pi-coding-agent": "0.65.0",
76
+ "@mariozechner/pi-coding-agent": "0.67.6",
77
77
  "patch-package": "^8.0.1",
78
78
  "web-push": "^3.6.7",
79
79
  "ws": "^8.20.0"
@@ -1,8 +1,8 @@
1
1
  diff --git a/node_modules/@mariozechner/pi-coding-agent/dist/core/agent-session-runtime.js b/node_modules/@mariozechner/pi-coding-agent/dist/core/agent-session-runtime.js
2
- index 9ab786e..6f89829 100644
2
+ index fcb3466..20ee304 100644
3
3
  --- a/node_modules/@mariozechner/pi-coding-agent/dist/core/agent-session-runtime.js
4
4
  +++ b/node_modules/@mariozechner/pi-coding-agent/dist/core/agent-session-runtime.js
5
- @@ -74,9 +74,6 @@ export class AgentSessionRuntime {
5
+ @@ -75,9 +75,6 @@ export class AgentSessionRuntime {
6
6
  this.session.dispose();
7
7
  }
8
8
  apply(result) {
@@ -12,9 +12,9 @@ index 9ab786e..6f89829 100644
12
12
  this._session = result.session;
13
13
  this._services = result.services;
14
14
  this._diagnostics = result.diagnostics;
15
- @@ -223,9 +220,6 @@ export class AgentSessionRuntime {
16
- */
15
+ @@ -227,9 +224,6 @@ export class AgentSessionRuntime {
17
16
  export async function createAgentSessionRuntime(createRuntime, options) {
17
+ assertSessionCwdExists(options.sessionManager, options.cwd);
18
18
  const result = await createRuntime(options);
19
19
  - if (process.cwd() !== result.services.cwd) {
20
20
  - process.chdir(result.services.cwd);
@@ -7,9 +7,13 @@ const PROJECT_MARKERS = ['.git', 'package.json'];
7
7
  * Scans configured root directories for project folders and lists their sessions.
8
8
  */
9
9
  export class FolderIndex {
10
- roots;
11
- constructor(roots) {
12
- this.roots = roots;
10
+ _roots;
11
+ constructor(_roots) {
12
+ this._roots = _roots;
13
+ }
14
+ /** Returns the configured root directories. */
15
+ get roots() {
16
+ return this._roots;
13
17
  }
14
18
  /**
15
19
  * Scan all roots one level deep for project directories.
@@ -17,7 +21,7 @@ export class FolderIndex {
17
21
  */
18
22
  async scan() {
19
23
  const folders = [];
20
- for (const root of this.roots) {
24
+ for (const root of this._roots) {
21
25
  let entries;
22
26
  try {
23
27
  entries = await readdir(root);
@@ -0,0 +1,32 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ /** Resolve the current git branch for a directory. Returns null if not a git repo or detached. */
3
+ export function getGitBranch(cwd) {
4
+ // Guard against inherited Git env vars forcing resolution to another repo.
5
+ const env = { ...process.env };
6
+ delete env.GIT_DIR;
7
+ delete env.GIT_WORK_TREE;
8
+ const runGit = (args) => {
9
+ try {
10
+ const value = execFileSync('git', args, {
11
+ cwd,
12
+ env,
13
+ encoding: 'utf-8',
14
+ timeout: 2000,
15
+ stdio: ['ignore', 'pipe', 'ignore'],
16
+ }).trim();
17
+ return value || null;
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ };
23
+ // Best signal for the checked-out branch (works with linked worktrees).
24
+ const current = runGit(['branch', '--show-current']);
25
+ if (current)
26
+ return current;
27
+ // Fallback for older Git versions / unusual setups.
28
+ const abbrevRef = runGit(['rev-parse', '--abbrev-ref', 'HEAD']);
29
+ if (!abbrevRef || abbrevRef === 'HEAD')
30
+ return null;
31
+ return abbrevRef;
32
+ }
@@ -6,6 +6,64 @@
6
6
  export function mapAgentMessages(messages) {
7
7
  return messages.map(mapAgentMessage);
8
8
  }
9
+ /**
10
+ * Extract entry IDs from branch entries in the same order that
11
+ * buildSessionContext produces messages. This mirrors the SDK's
12
+ * compaction/branch-summary logic so IDs can be zipped 1:1 with
13
+ * the mapped PimoteAgentMessage array.
14
+ */
15
+ export function extractMessageEntryIds(branch) {
16
+ // Find the last compaction entry on the path
17
+ let compaction = null;
18
+ for (const entry of branch) {
19
+ if (entry.type === 'compaction')
20
+ compaction = entry;
21
+ }
22
+ const ids = [];
23
+ const appendId = (entry) => {
24
+ if (entry.type === 'message') {
25
+ ids.push(entry.id);
26
+ }
27
+ else if (entry.type === 'custom_message') {
28
+ ids.push(entry.id);
29
+ }
30
+ else if (entry.type === 'branch_summary' && entry.summary) {
31
+ ids.push(entry.id);
32
+ }
33
+ };
34
+ if (compaction) {
35
+ // Compaction summary message maps to the compaction entry
36
+ ids.push(compaction.id);
37
+ const compactionIdx = branch.findIndex((e) => e.type === 'compaction' && e.id === compaction.id);
38
+ // Kept messages before the compaction entry
39
+ let foundFirstKept = false;
40
+ for (let i = 0; i < compactionIdx; i++) {
41
+ if (branch[i].id === compaction.firstKeptEntryId)
42
+ foundFirstKept = true;
43
+ if (foundFirstKept)
44
+ appendId(branch[i]);
45
+ }
46
+ // Messages after the compaction entry
47
+ for (let i = compactionIdx + 1; i < branch.length; i++) {
48
+ appendId(branch[i]);
49
+ }
50
+ }
51
+ else {
52
+ for (const entry of branch) {
53
+ appendId(entry);
54
+ }
55
+ }
56
+ return ids;
57
+ }
58
+ /**
59
+ * Apply entry IDs from the session manager onto mapped messages.
60
+ * Zips by index — both arrays must correspond 1:1.
61
+ */
62
+ export function applyEntryIds(messages, entryIds) {
63
+ for (let i = 0; i < messages.length && i < entryIds.length; i++) {
64
+ messages[i].entryId = entryIds[i];
65
+ }
66
+ }
9
67
  export function mapAgentMessage(msg) {
10
68
  const role = msg.role ?? 'unknown';
11
69
  const content = [];
@@ -52,7 +110,7 @@ export function mapAgentMessage(msg) {
52
110
  }
53
111
  // Handle custom messages — preserve customType and display flag for the client
54
112
  if (role === 'custom') {
55
- return { role, content, customType: msg.customType, display: msg.display ?? true };
113
+ return { role, content, entryId: msg.id, customType: msg.customType, display: msg.display ?? true };
56
114
  }
57
115
  // Handle tool result messages
58
116
  if (role === 'toolResult') {
@@ -72,9 +130,14 @@ export function mapAgentMessage(msg) {
72
130
  toolCallId: msg.toolCallId,
73
131
  toolName: msg.toolName,
74
132
  result: resultContent.length > 0 ? resultContent[0].text : undefined,
133
+ isError: msg.isError || undefined,
75
134
  },
76
135
  ],
136
+ entryId: msg.id,
77
137
  };
78
138
  }
79
- return { role, content };
139
+ // Note: msg.id is typically undefined for standard SDK messages (UserMessage,
140
+ // AssistantMessage, ToolResultMessage). Entry IDs are applied separately via
141
+ // applyEntryIds() using the session manager's branch entries.
142
+ return { role, content, ...(msg.id ? { entryId: msg.id } : {}) };
80
143
  }
@@ -121,6 +121,9 @@ export async function createServer(config, sessionManager, folderIndex, pushNoti
121
121
  sessionManager.onSessionClosed = (sessionId, folderPath) => {
122
122
  WsHandler.broadcastSidebarUpdate(sessionId, folderPath, sessionManager, clientRegistry);
123
123
  };
124
+ sessionManager.onGitBranchChange = (sessionId, folderPath) => {
125
+ WsHandler.broadcastSidebarUpdate(sessionId, folderPath, sessionManager, clientRegistry);
126
+ };
124
127
  const wss = new WebSocketServer({ noServer: true });
125
128
  const clientRegistry = new Map();
126
129
  httpServer.on('upgrade', (req, socket, head) => {
@@ -1,6 +1,7 @@
1
1
  import { createAgentSessionRuntime, createAgentSessionServices, createAgentSessionFromServices, createEventBus, AuthStorage, ModelRegistry, getAgentDir, SessionManager as PiSessionManager, } from '@mariozechner/pi-coding-agent';
2
2
  import { EventBuffer } from './event-buffer.js';
3
3
  import { applyPanelMessage, getMergedPanelCards } from './panel-state.js';
4
+ import { getGitBranch } from './git-branch.js';
4
5
  // ---- Slot-based helpers (operate on ManagedSlot) ----
5
6
  /** Send an event to the client connected to this slot. No-op if disconnected. */
6
7
  export function sendSlotEvent(slot, event) {
@@ -59,6 +60,7 @@ function createSessionState(session, eventBus, config, callbacks, slotRef, folde
59
60
  panelState: new Map(),
60
61
  panelListenerUnsubs: [],
61
62
  panelThrottleTimer: null,
63
+ treeNavigationInProgress: false,
62
64
  };
63
65
  // Subscribe to session events
64
66
  const unsubscribe = session.subscribe((event) => {
@@ -125,8 +127,11 @@ export class PimoteSessionManager {
125
127
  modelRegistry;
126
128
  sessions = new Map();
127
129
  idleCheckHandle = null;
130
+ gitBranchCheckHandle = null;
131
+ lastKnownGitBranchBySession = new Map();
128
132
  onStatusChange;
129
133
  onSessionClosed;
134
+ onGitBranchChange;
130
135
  constructor(config, pushNotificationService) {
131
136
  this.config = config;
132
137
  this.pushNotificationService = pushNotificationService;
@@ -137,6 +142,8 @@ export class PimoteSessionManager {
137
142
  const eventBusRef = { current: null };
138
143
  const sharedAuthStorage = this.authStorage;
139
144
  const sharedModelRegistry = this.modelRegistry;
145
+ const sessionManager = sessionFilePath ? PiSessionManager.open(sessionFilePath) : PiSessionManager.create(folderPath);
146
+ const effectiveFolderPath = sessionFilePath ? sessionManager.getCwd() : folderPath;
140
147
  const factory = async ({ cwd, agentDir, sessionManager, sessionStartEvent }) => {
141
148
  const eventBus = createEventBus();
142
149
  eventBusRef.current = eventBus;
@@ -154,9 +161,9 @@ export class PimoteSessionManager {
154
161
  };
155
162
  };
156
163
  const runtime = await createAgentSessionRuntime(factory, {
157
- cwd: folderPath,
164
+ cwd: effectiveFolderPath,
158
165
  agentDir: getAgentDir(),
159
- sessionManager: sessionFilePath ? PiSessionManager.open(sessionFilePath) : PiSessionManager.create(folderPath),
166
+ sessionManager,
160
167
  });
161
168
  const session = runtime.session;
162
169
  const sessionId = session.sessionId;
@@ -186,10 +193,10 @@ export class PimoteSessionManager {
186
193
  onStatusChange: (sid, fp) => this.onStatusChange?.(sid, fp),
187
194
  onAgentEnd: (sid, s) => this.handleAgentEnd(sid, s),
188
195
  sendEvent: (e) => sendSlotEvent(slot, e),
189
- }, slotRef, folderPath);
196
+ }, slotRef, effectiveFolderPath);
190
197
  const slot = {
191
198
  runtime,
192
- folderPath,
199
+ folderPath: effectiveFolderPath,
193
200
  eventBusRef,
194
201
  connection: null,
195
202
  sessionState,
@@ -199,6 +206,7 @@ export class PimoteSessionManager {
199
206
  };
200
207
  slotRef.slot = slot;
201
208
  this.sessions.set(sessionId, slot);
209
+ this.lastKnownGitBranchBySession.set(sessionId, getGitBranch(effectiveFolderPath));
202
210
  return sessionId;
203
211
  }
204
212
  handleAgentEnd(sessionId, slot) {
@@ -261,12 +269,16 @@ export class PimoteSessionManager {
261
269
  const folderPath = slot.folderPath;
262
270
  await slot.runtime.dispose();
263
271
  this.sessions.delete(sessionId);
272
+ this.lastKnownGitBranchBySession.delete(sessionId);
264
273
  this.onSessionClosed?.(sessionId, folderPath);
265
274
  }
266
275
  /** Re-key a slot in the session map after session replacement. */
267
276
  reKeySession(slot, oldId, newId) {
268
277
  this.sessions.delete(oldId);
269
278
  this.sessions.set(newId, slot);
279
+ const lastKnown = this.lastKnownGitBranchBySession.get(oldId) ?? null;
280
+ this.lastKnownGitBranchBySession.delete(oldId);
281
+ this.lastKnownGitBranchBySession.set(newId, lastKnown);
270
282
  }
271
283
  /** Rebuild a slot's SessionState after session replacement.
272
284
  * Tears down the old state and creates a new one from the current runtime.session. */
@@ -289,6 +301,9 @@ export class PimoteSessionManager {
289
301
  this.stopIdleCheck();
290
302
  this.idleCheckHandle = setInterval(() => {
291
303
  for (const [sessionId, slot] of this.sessions) {
304
+ if (slot.sessionState.treeNavigationInProgress) {
305
+ continue;
306
+ }
292
307
  const clientId = slot.connection?.connectedClientId ?? null;
293
308
  const hasConnectedClient = clientId !== null && (isClientConnected?.(clientId) ?? false);
294
309
  if (!hasConnectedClient && Date.now() - slot.sessionState.lastActivity > idleTimeout) {
@@ -298,12 +313,28 @@ export class PimoteSessionManager {
298
313
  }
299
314
  }
300
315
  }, 60_000);
316
+ this.gitBranchCheckHandle = setInterval(() => {
317
+ for (const [sessionId, slot] of this.sessions) {
318
+ if (!slot.connection?.connectedClientId)
319
+ continue;
320
+ const next = getGitBranch(slot.folderPath);
321
+ const prev = this.lastKnownGitBranchBySession.get(sessionId) ?? null;
322
+ if (next !== prev) {
323
+ this.lastKnownGitBranchBySession.set(sessionId, next);
324
+ this.onGitBranchChange?.(sessionId, slot.folderPath);
325
+ }
326
+ }
327
+ }, 3000);
301
328
  }
302
329
  stopIdleCheck() {
303
330
  if (this.idleCheckHandle !== null) {
304
331
  clearInterval(this.idleCheckHandle);
305
332
  this.idleCheckHandle = null;
306
333
  }
334
+ if (this.gitBranchCheckHandle !== null) {
335
+ clearInterval(this.gitBranchCheckHandle);
336
+ this.gitBranchCheckHandle = null;
337
+ }
307
338
  }
308
339
  async dispose() {
309
340
  this.stopIdleCheck();