@pi-chamber/web 0.1.8 → 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.
- package/bin/cli.test.js +48 -0
- package/bin/lib/DOCUMENTATION.md +2 -2
- package/bin/lib/cli-args.js +38 -11
- package/bin/lib/cli-startup.js +60 -13
- package/bin/lib/commands-startup.js +20 -5
- package/bin/lib/commands-update.js +91 -34
- package/bin/lib/commands-update.test.js +57 -42
- package/dist/assets/CommitSection-LewzHfDH.js +1 -0
- package/dist/assets/DiagramEditor-BOIJVnh3.js +1 -0
- package/dist/assets/DiagramView-CdNHezna.js +1 -0
- package/dist/assets/{DiffView-BHS4m_K7.js → DiffView-Cmw-RHIH.js} +2 -2
- package/dist/assets/{FilesView-DmZcdwl-.js → FilesView-BODbL0Xm.js} +6 -6
- package/dist/assets/GitView-DqYF-n_h.js +6 -0
- package/dist/assets/JsonTreeView-DzEjA1ht.js +1 -0
- package/dist/assets/{MarkdownRendererImpl-B07lHhBl.js → MarkdownRendererImpl-Bfq0bOSM.js} +2 -2
- package/dist/assets/PatchDiff-CtVByTC4.js +1 -0
- package/dist/assets/{PierreDiffViewer-Bo0_BCT7.js → PierreDiffViewer-Cwoewmiu.js} +2 -2
- package/dist/assets/{useAppFontEffects-DXASDavv.js → SessionSwitcherDropdown-DNjCJjzF.js} +97 -97
- package/dist/assets/{SettingsView-CtsS4oDI.js → SettingsView-BtLvHlj4.js} +3 -3
- package/dist/assets/SettingsWindow-CPd7JmJs.js +1 -0
- package/dist/assets/{ToolOutputDialog-iZG2ey4T.js → ToolOutputDialog-CFFC5eI-.js} +6 -6
- package/dist/assets/ToolPartDiffPreview-yFq1DKSA.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-DD5IznIe.js → VirtualizedFileDiff-CC5lTPJS.js} +4 -4
- package/dist/assets/collapsible-D_555Hw_.js +1 -0
- package/dist/assets/computeVirtualFileMetrics-BcVuV0Ic.js +3 -0
- package/dist/assets/contextFileOpenGuard-BygvjHuE.js +1 -0
- package/dist/assets/index-C9CLY-is.js +1 -0
- package/dist/assets/index-DQr8-8RF.js +8 -0
- package/dist/assets/index-DYrJXRkU.css +1 -0
- package/dist/assets/isDiffPlainText-Bher0UxW.js +1 -0
- package/dist/assets/isFilePlainText-EIViqOlI.js +1 -0
- package/dist/assets/main-1bIgjP48.js +2 -0
- package/dist/assets/main-JDtt2bQS.js +133 -0
- package/dist/assets/{markdownCore-BULtt6Xa.js → markdownCore-Bm4FxtK1.js} +2 -2
- package/dist/assets/miniChat-B7kKqKHH.js +2 -0
- package/dist/assets/mobile-DZPRce5z.js +2 -0
- package/dist/assets/renderElectronMiniChatApp-Dq0GdThJ.js +1 -0
- package/dist/assets/renderMobileApp-DlKnjObC.js +23 -0
- package/dist/assets/runtimeSurface-B6sKTjo7.js +1 -0
- package/dist/assets/{useInlineCommentController-KJFe98HG.js → useInlineCommentController-CSR_Ju0a.js} +2 -2
- package/dist/assets/usePanelSlide-BkonU4LS.js +47 -0
- package/dist/assets/{useWindowTitle-BcwmWC0M.js → useWindowTitle-rt_8xbd9.js} +3 -3
- package/dist/assets/{vendor-codemirror-language-data-CFVH0pe8.js → vendor-codemirror-language-data-Be-zDX0u.js} +2 -2
- package/dist/assets/{web-components-CNPQSzpa.js → web-components-DgFD1Jms.js} +4 -4
- package/dist/index.html +4 -4
- package/dist/mini-chat.html +3 -3
- package/dist/mobile.html +3 -3
- package/package.json +1 -1
- package/server/lib/package-manager.js +44 -65
- package/server/lib/package-manager.test.js +27 -0
- package/server/lib/pi/routes.js +6 -0
- package/server/lib/pi/routes.test.js +32 -0
- package/server/lib/pi/session-daemon/DOCUMENTATION.md +1 -1
- package/server/lib/pi/session-daemon/session-daemon.js +11 -4
- package/server/lib/pi/session-daemon/session-daemon.test.js +24 -0
- package/dist/assets/CommitSection-DGGArAb2.js +0 -1
- package/dist/assets/DiagramEditor-ig5nYV6t.js +0 -1
- package/dist/assets/DiagramView-8Qr6pwwI.js +0 -1
- package/dist/assets/GitView-gfHxmc66.js +0 -6
- package/dist/assets/JsonTreeView-DbFqka3P.js +0 -1
- package/dist/assets/PatchDiff-DD9pAs47.js +0 -1
- package/dist/assets/SessionSwitcherDropdown-t7WZX24f.js +0 -1
- package/dist/assets/SettingsWindow-DIRGn3eQ.js +0 -1
- package/dist/assets/TerminalView-DUwO1CCY.js +0 -46
- package/dist/assets/ToolPartDiffPreview-FTexlZSz.js +0 -8
- package/dist/assets/collapsible-ODWWxk4f.js +0 -1
- package/dist/assets/computeVirtualFileMetrics-DdXkLFAD.js +0 -3
- package/dist/assets/contextFileOpenGuard-uC4OerzL.js +0 -1
- package/dist/assets/index-CRB7i1S2.js +0 -8
- package/dist/assets/index-C_Mo87uS.css +0 -1
- package/dist/assets/index-MSKFS67y.js +0 -1
- package/dist/assets/isDiffPlainText-CeCQwHA2.js +0 -1
- package/dist/assets/isFilePlainText-BcfhE9FH.js +0 -1
- package/dist/assets/main-CPygRDd9.js +0 -134
- package/dist/assets/main-DZg4qzOe.js +0 -2
- package/dist/assets/miniChat-BFotDgpO.js +0 -2
- package/dist/assets/mobile-3LLzhN5Q.js +0 -2
- package/dist/assets/renderElectronMiniChatApp-DGmmT9Wf.js +0 -1
- package/dist/assets/renderMobileApp-Dd8P9hvM.js +0 -23
- package/dist/assets/runtimeSurface-Cnqjhb6Y.js +0 -1
- package/dist/assets/useOpenInAppsStore-DHAeT9zx.js +0 -1
- /package/dist/assets/{TerminalView-BV3KOtdA.css → usePanelSlide-BV3KOtdA.css} +0 -0
package/dist/mini-chat.html
CHANGED
|
@@ -89,14 +89,14 @@
|
|
|
89
89
|
}
|
|
90
90
|
</style>
|
|
91
91
|
|
|
92
|
-
<script type="module" crossorigin src="/assets/miniChat-
|
|
92
|
+
<script type="module" crossorigin src="/assets/miniChat-B7kKqKHH.js"></script>
|
|
93
93
|
<link rel="modulepreload" crossorigin href="/assets/vendor-vite-runtime-DD1OZmLC.js">
|
|
94
94
|
<link rel="modulepreload" crossorigin href="/assets/vendor-scheduler-7OC5HNn7.js">
|
|
95
95
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-DYyjh7Q0.js">
|
|
96
96
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-CnsBKjP5.js">
|
|
97
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
97
|
+
<link rel="modulepreload" crossorigin href="/assets/index-DQr8-8RF.js">
|
|
98
98
|
<link rel="modulepreload" crossorigin href="/assets/vendor-katex-CHD9BdDO.js">
|
|
99
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
99
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DYrJXRkU.css">
|
|
100
100
|
<link rel="stylesheet" crossorigin href="/assets/vendor-katex-Cp579UoJ.css">
|
|
101
101
|
</head>
|
|
102
102
|
<body class="h-full bg-background text-foreground">
|
package/dist/mobile.html
CHANGED
|
@@ -63,14 +63,14 @@
|
|
|
63
63
|
})();
|
|
64
64
|
</script>
|
|
65
65
|
|
|
66
|
-
<script type="module" crossorigin src="/assets/mobile-
|
|
66
|
+
<script type="module" crossorigin src="/assets/mobile-DZPRce5z.js"></script>
|
|
67
67
|
<link rel="modulepreload" crossorigin href="/assets/vendor-vite-runtime-DD1OZmLC.js">
|
|
68
68
|
<link rel="modulepreload" crossorigin href="/assets/vendor-scheduler-7OC5HNn7.js">
|
|
69
69
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-DYyjh7Q0.js">
|
|
70
70
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-CnsBKjP5.js">
|
|
71
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
71
|
+
<link rel="modulepreload" crossorigin href="/assets/index-DQr8-8RF.js">
|
|
72
72
|
<link rel="modulepreload" crossorigin href="/assets/vendor-katex-CHD9BdDO.js">
|
|
73
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
73
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DYrJXRkU.css">
|
|
74
74
|
<link rel="stylesheet" crossorigin href="/assets/vendor-katex-Cp579UoJ.css">
|
|
75
75
|
</head>
|
|
76
76
|
<body class="h-full bg-background text-foreground">
|
package/package.json
CHANGED
|
@@ -17,6 +17,13 @@ const CHANGELOG_URL = 'https://raw.githubusercontent.com/RyderAsKing/PiChamber/m
|
|
|
17
17
|
const GITHUB_RELEASES_URL = 'https://github.com/RyderAsKing/PiChamber/releases';
|
|
18
18
|
const GITHUB_RELEASES_API_URL = 'https://api.github.com/repos/RyderAsKing/PiChamber/releases';
|
|
19
19
|
let cachedDetectedPm = null;
|
|
20
|
+
const TRUSTED_UPDATE_REASONS = new Set([
|
|
21
|
+
'forced-env',
|
|
22
|
+
'install-path-owner',
|
|
23
|
+
'global-root-owner',
|
|
24
|
+
'cached',
|
|
25
|
+
]);
|
|
26
|
+
const UPDATE_PACKAGE_MANAGERS = new Set(['npm', 'pnpm', 'yarn', 'bun']);
|
|
20
27
|
|
|
21
28
|
function getSpawnSyncBaseOptions() {
|
|
22
29
|
return process.platform === 'win32' ? { windowsHide: true } : {};
|
|
@@ -373,6 +380,21 @@ function packageManagerOwnsCurrentInstall(pm) {
|
|
|
373
380
|
return false;
|
|
374
381
|
}
|
|
375
382
|
|
|
383
|
+
function detectionResult(packageManager, reason) {
|
|
384
|
+
return {
|
|
385
|
+
packageManager,
|
|
386
|
+
reason,
|
|
387
|
+
packagePath: getCurrentPackagePath(),
|
|
388
|
+
packageManagerCommand: resolvePackageManagerCommand(packageManager),
|
|
389
|
+
globalNodeModulesRoot: getGlobalNodeModulesRoots(packageManager)[0] || null,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function cacheTrustedDetection(packageManager, reason) {
|
|
394
|
+
cachedDetectedPm = packageManager;
|
|
395
|
+
return detectionResult(packageManager, reason);
|
|
396
|
+
}
|
|
397
|
+
|
|
376
398
|
export function detectPackageManagerDetails() {
|
|
377
399
|
// In desktop (Electron) runtime, package-manager detection is worthless —
|
|
378
400
|
// the app ships as a .app bundle, not installed via npm/pnpm/yarn/bun, and
|
|
@@ -401,44 +423,23 @@ export function detectPackageManagerDetails() {
|
|
|
401
423
|
}
|
|
402
424
|
|
|
403
425
|
const forcedPm = process.env.PICHAMBER_PACKAGE_MANAGER?.trim();
|
|
404
|
-
if (forcedPm &&
|
|
426
|
+
if (forcedPm && UPDATE_PACKAGE_MANAGERS.has(forcedPm)) {
|
|
405
427
|
const forcedPmCommand = resolvePackageManagerCommand(forcedPm);
|
|
406
428
|
if (isCommandAvailable(forcedPmCommand)) {
|
|
407
|
-
|
|
408
|
-
return {
|
|
409
|
-
packageManager: cachedDetectedPm,
|
|
410
|
-
reason: 'forced-env',
|
|
411
|
-
packagePath: getCurrentPackagePath(),
|
|
412
|
-
packageManagerCommand: forcedPmCommand,
|
|
413
|
-
globalNodeModulesRoot: getGlobalNodeModulesRoots(cachedDetectedPm)[0] || null,
|
|
414
|
-
};
|
|
429
|
+
return cacheTrustedDetection(forcedPm, 'forced-env');
|
|
415
430
|
}
|
|
416
431
|
}
|
|
417
432
|
|
|
418
433
|
// First prefer the package manager that demonstrably owns the current install.
|
|
419
434
|
const installPathPm = detectPackageManagerFromCurrentInstallPath();
|
|
420
435
|
if (installPathPm && packageManagerOwnsCurrentInstall(installPathPm)) {
|
|
421
|
-
|
|
422
|
-
return {
|
|
423
|
-
packageManager: cachedDetectedPm,
|
|
424
|
-
reason: 'install-path-owner',
|
|
425
|
-
packagePath: getCurrentPackagePath(),
|
|
426
|
-
packageManagerCommand: resolvePackageManagerCommand(cachedDetectedPm),
|
|
427
|
-
globalNodeModulesRoot: getGlobalNodeModulesRoots(cachedDetectedPm)[0] || null,
|
|
428
|
-
};
|
|
436
|
+
return cacheTrustedDetection(installPathPm, 'install-path-owner');
|
|
429
437
|
}
|
|
430
438
|
|
|
431
439
|
const ownershipCandidates = ['pnpm', 'yarn', 'bun', 'npm'];
|
|
432
440
|
for (const candidate of ownershipCandidates) {
|
|
433
441
|
if (packageManagerOwnsCurrentInstall(candidate)) {
|
|
434
|
-
|
|
435
|
-
return {
|
|
436
|
-
packageManager: cachedDetectedPm,
|
|
437
|
-
reason: 'global-root-owner',
|
|
438
|
-
packagePath: getCurrentPackagePath(),
|
|
439
|
-
packageManagerCommand: resolvePackageManagerCommand(cachedDetectedPm),
|
|
440
|
-
globalNodeModulesRoot: getGlobalNodeModulesRoots(cachedDetectedPm)[0] || null,
|
|
441
|
-
};
|
|
442
|
+
return cacheTrustedDetection(candidate, 'global-root-owner');
|
|
442
443
|
}
|
|
443
444
|
}
|
|
444
445
|
|
|
@@ -471,29 +472,14 @@ export function detectPackageManagerDetails() {
|
|
|
471
472
|
|
|
472
473
|
// Validate the hint against package visibility, but only after ownership checks failed.
|
|
473
474
|
if (hintedPm && isCommandAvailable(resolvePackageManagerCommand(hintedPm)) && isPackageInstalledWith(hintedPm)) {
|
|
474
|
-
|
|
475
|
-
return {
|
|
476
|
-
packageManager: cachedDetectedPm,
|
|
477
|
-
reason: 'hinted-visible-install',
|
|
478
|
-
packagePath: getCurrentPackagePath(),
|
|
479
|
-
packageManagerCommand: resolvePackageManagerCommand(cachedDetectedPm),
|
|
480
|
-
globalNodeModulesRoot: getGlobalNodeModulesRoots(cachedDetectedPm)[0] || null,
|
|
481
|
-
};
|
|
475
|
+
return detectionResult(hintedPm, 'hinted-visible-install');
|
|
482
476
|
}
|
|
483
477
|
|
|
484
478
|
const runtimePm = detectPackageManagerFromRuntimePath(process.execPath);
|
|
485
479
|
if (runtimePm && isCommandAvailable(resolvePackageManagerCommand(runtimePm)) && isPackageInstalledWith(runtimePm)) {
|
|
486
|
-
|
|
487
|
-
return {
|
|
488
|
-
packageManager: cachedDetectedPm,
|
|
489
|
-
reason: 'runtime-visible-install',
|
|
490
|
-
packagePath: getCurrentPackagePath(),
|
|
491
|
-
packageManagerCommand: resolvePackageManagerCommand(cachedDetectedPm),
|
|
492
|
-
globalNodeModulesRoot: getGlobalNodeModulesRoots(cachedDetectedPm)[0] || null,
|
|
493
|
-
};
|
|
480
|
+
return detectionResult(runtimePm, 'runtime-visible-install');
|
|
494
481
|
}
|
|
495
482
|
|
|
496
|
-
// Last resort: pick a PM that can at least see the package.
|
|
497
483
|
const pmChecks = [
|
|
498
484
|
{ name: 'pnpm', check: () => isCommandAvailable(resolvePackageManagerCommand('pnpm')) },
|
|
499
485
|
{ name: 'yarn', check: () => isCommandAvailable(resolvePackageManagerCommand('yarn')) },
|
|
@@ -502,35 +488,29 @@ export function detectPackageManagerDetails() {
|
|
|
502
488
|
];
|
|
503
489
|
|
|
504
490
|
for (const { name, check } of pmChecks) {
|
|
505
|
-
if (check()) {
|
|
506
|
-
|
|
507
|
-
if (isPackageInstalledWith(name)) {
|
|
508
|
-
cachedDetectedPm = name;
|
|
509
|
-
return {
|
|
510
|
-
packageManager: cachedDetectedPm,
|
|
511
|
-
reason: 'last-resort-visible-install',
|
|
512
|
-
packagePath: getCurrentPackagePath(),
|
|
513
|
-
packageManagerCommand: resolvePackageManagerCommand(cachedDetectedPm),
|
|
514
|
-
globalNodeModulesRoot: getGlobalNodeModulesRoots(cachedDetectedPm)[0] || null,
|
|
515
|
-
};
|
|
516
|
-
}
|
|
491
|
+
if (check() && isPackageInstalledWith(name)) {
|
|
492
|
+
return detectionResult(name, 'last-resort-visible-install');
|
|
517
493
|
}
|
|
518
494
|
}
|
|
519
495
|
|
|
520
|
-
|
|
521
|
-
return {
|
|
522
|
-
packageManager: cachedDetectedPm,
|
|
523
|
-
reason: 'default-fallback',
|
|
524
|
-
packagePath: getCurrentPackagePath(),
|
|
525
|
-
packageManagerCommand: resolvePackageManagerCommand(cachedDetectedPm),
|
|
526
|
-
globalNodeModulesRoot: getGlobalNodeModulesRoots(cachedDetectedPm)[0] || null,
|
|
527
|
-
};
|
|
496
|
+
return detectionResult('npm', 'default-fallback');
|
|
528
497
|
}
|
|
529
498
|
|
|
530
499
|
export function detectPackageManager() {
|
|
531
500
|
return detectPackageManagerDetails().packageManager;
|
|
532
501
|
}
|
|
533
502
|
|
|
503
|
+
export function resolveTrustedUpdatePackageManager(details = detectPackageManagerDetails()) {
|
|
504
|
+
if (
|
|
505
|
+
details
|
|
506
|
+
&& TRUSTED_UPDATE_REASONS.has(details.reason)
|
|
507
|
+
&& UPDATE_PACKAGE_MANAGERS.has(details.packageManager)
|
|
508
|
+
) {
|
|
509
|
+
return details.packageManager;
|
|
510
|
+
}
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
|
|
534
514
|
function detectPackageManagerFromInstallPath(pkgPath) {
|
|
535
515
|
if (!pkgPath) return null;
|
|
536
516
|
const normalized = pkgPath.replace(/\\/g, '/').toLowerCase();
|
|
@@ -547,9 +527,8 @@ function detectPackageManagerFromRuntimePath(runtimePath) {
|
|
|
547
527
|
if (normalized.includes('/.bun/bin/bun') || normalized.endsWith('/bun') || normalized.endsWith('/bun.exe')) {
|
|
548
528
|
return 'bun';
|
|
549
529
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
if (normalized.includes('/node') || normalized.endsWith('/node.exe')) return 'npm';
|
|
530
|
+
// `node` is the runtime for npm, pnpm, and yarn global bins. It is not an
|
|
531
|
+
// install owner.
|
|
553
532
|
return null;
|
|
554
533
|
}
|
|
555
534
|
|
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
detectPackageManager,
|
|
12
12
|
executeUpdate,
|
|
13
13
|
getCurrentVersion,
|
|
14
|
+
resolveTrustedUpdatePackageManager,
|
|
14
15
|
} = await import('./package-manager.js');
|
|
15
16
|
|
|
16
17
|
function officialRegistryPackage(latest) {
|
|
@@ -366,5 +367,31 @@ describe('CLI update exports', () => {
|
|
|
366
367
|
it('exports package-manager helpers used by the update command', () => {
|
|
367
368
|
expect(typeof detectPackageManager).toBe('function');
|
|
368
369
|
expect(typeof executeUpdate).toBe('function');
|
|
370
|
+
expect(typeof resolveTrustedUpdatePackageManager).toBe('function');
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
describe('resolveTrustedUpdatePackageManager', () => {
|
|
375
|
+
it('accepts only installs that this process can prove it owns', () => {
|
|
376
|
+
expect(resolveTrustedUpdatePackageManager({
|
|
377
|
+
packageManager: 'pnpm',
|
|
378
|
+
reason: 'install-path-owner',
|
|
379
|
+
})).toBe('pnpm');
|
|
380
|
+
expect(resolveTrustedUpdatePackageManager({
|
|
381
|
+
packageManager: 'bun',
|
|
382
|
+
reason: 'forced-env',
|
|
383
|
+
})).toBe('bun');
|
|
384
|
+
expect(resolveTrustedUpdatePackageManager({
|
|
385
|
+
packageManager: 'npm',
|
|
386
|
+
reason: 'default-fallback',
|
|
387
|
+
})).toBeNull();
|
|
388
|
+
expect(resolveTrustedUpdatePackageManager({
|
|
389
|
+
packageManager: 'npm',
|
|
390
|
+
reason: 'runtime-visible-install',
|
|
391
|
+
})).toBeNull();
|
|
392
|
+
expect(resolveTrustedUpdatePackageManager({
|
|
393
|
+
packageManager: 'pnpm',
|
|
394
|
+
reason: 'last-resort-visible-install',
|
|
395
|
+
})).toBeNull();
|
|
369
396
|
});
|
|
370
397
|
});
|
package/server/lib/pi/routes.js
CHANGED
|
@@ -343,6 +343,12 @@ const projectEventFrame = (frame) => {
|
|
|
343
343
|
} } };
|
|
344
344
|
}
|
|
345
345
|
case 'session.lifecycle': return { ...common, payload: { state: frame.payload.state } };
|
|
346
|
+
case 'session.updated': {
|
|
347
|
+
if (typeof frame.payload.title !== 'string') return null;
|
|
348
|
+
const title = frame.payload.title.trim();
|
|
349
|
+
if (title.length === 0 || title.length > 256) return null;
|
|
350
|
+
return { ...common, payload: { title } };
|
|
351
|
+
}
|
|
346
352
|
case 'assistant.message.start': return { ...common, payload: { messageId: frame.payload.messageId, role: frame.payload.role, startedAt: frame.payload.startedAt, ...(typeof frame.payload.parentId === 'string' ? { parentId: frame.payload.parentId } : {}), ...(typeof frame.payload.text === 'string' ? { text: frame.payload.text } : {}), ...(frame.payload.model ? { model: frame.payload.model } : {}) } };
|
|
347
353
|
case 'assistant.message.delta':
|
|
348
354
|
case 'assistant.thinking.delta': return { ...common, payload: { messageId: frame.payload.messageId, contentIndex: frame.payload.contentIndex, delta: frame.payload.delta, ...(typeof frame.payload.partId === 'string' ? { partId: frame.payload.partId } : {}) } };
|
|
@@ -563,6 +563,38 @@ describe('Pi runtime route', () => {
|
|
|
563
563
|
expect(text).not.toContain('/private/socket');
|
|
564
564
|
});
|
|
565
565
|
|
|
566
|
+
it('projects session.updated titles onto the public event stream', async () => {
|
|
567
|
+
const runtime = {
|
|
568
|
+
health: async () => ({ state: 'ready', protocolVersion: 1, capabilities: [] }),
|
|
569
|
+
subscribe: async ({ onEvent }) => {
|
|
570
|
+
onEvent({
|
|
571
|
+
protocolVersion: 1,
|
|
572
|
+
kind: 'event',
|
|
573
|
+
event: 'session.updated',
|
|
574
|
+
sequence: 8,
|
|
575
|
+
payload: { sessionId: 'pi-session-5', directory: '/workspace', title: ' Fix the parser ', endpoint: '/private/socket' },
|
|
576
|
+
});
|
|
577
|
+
return () => {};
|
|
578
|
+
},
|
|
579
|
+
};
|
|
580
|
+
const app = express();
|
|
581
|
+
registerPiRuntimeRoutes(app, { getPiSessionDaemonRuntime: () => runtime });
|
|
582
|
+
server = await listen(app);
|
|
583
|
+
const response = await fetch(`http://127.0.0.1:${server.address().port}/api/pi/events?sessionId=pi-session-5&fromSequence=7`);
|
|
584
|
+
const reader = response.body.getReader();
|
|
585
|
+
const first = await reader.read();
|
|
586
|
+
let text = new TextDecoder().decode(first.value);
|
|
587
|
+
while (!text.includes('"session.updated"')) {
|
|
588
|
+
const next = await reader.read();
|
|
589
|
+
if (next.done) break;
|
|
590
|
+
text += new TextDecoder().decode(next.value);
|
|
591
|
+
}
|
|
592
|
+
await reader.cancel();
|
|
593
|
+
expect(text).toContain('"name":"session.updated"');
|
|
594
|
+
expect(text).toContain('"title":"Fix the parser"');
|
|
595
|
+
expect(text).not.toContain('/private/socket');
|
|
596
|
+
});
|
|
597
|
+
|
|
566
598
|
it('projects Pi usage through the public session and event envelopes', async () => {
|
|
567
599
|
const sampleUsage = {
|
|
568
600
|
input: 120, output: 80, cacheRead: 30, cacheWrite: 5, totalTokens: 235,
|
|
@@ -25,7 +25,7 @@ This module is the Pi-owned session-daemon foundation. A detached local daemon p
|
|
|
25
25
|
- The public health route deliberately whitelists its response fields. It never returns a daemon endpoint, credential, PID, cwd, agent directory, or transcript data. Public transcript and event projections preserve daemon-redacted assistant error messages so the UI can distinguish provider/runtime failures instead of reducing every failure to `ASSISTANT_ERROR`.
|
|
26
26
|
- A client must send an `authenticate` frame carrying the host-private daemon credential before it can issue a request. Credentials are never logged or returned.
|
|
27
27
|
- Frames are LF-delimited JSON. They have a 1 MiB maximum buffered size. A malformed, oversized, unauthenticated, or invalid request closes the connection.
|
|
28
|
-
- The private core commands are `runtime.health`, `projects.list/select`, `providers.list/config.get/models.set/status/login/login.respond/login.status/logout`, `settings.get/set`, `resources.list/update/prompts.create/prompts.delete`, and session list/create/open/rename/delete/tree/navigate/fork/clone/prompt/steer/followUp/abort/model/thinking/compact operations. Resource lists use opaque IDs and never disclose server paths. Only top-level Pi prompt templates and applicable AGENTS.md sources are editable; package/path resources and skills are browse-only. Resource writes are rejected while a session streams, use an atomic replace, then recreate the idle runtime. Project trust changes are persisted in Pi trust.json and likewise recreate an idle runtime; extensions remain disabled regardless of trust. Provider login submits API keys only across authenticated private IPC or brokers Pi's OAuth/device/manual-code interaction; public projections contain only opaque login IDs, prompts, URLs/device codes, model catalog/configuration, and stable states, never credentials or write-only headers. `providers.models.set` is rejected while a session streams, atomically persists Pi's `models.json`, then rehydrates the idle runtime so the catalog is authoritative immediately. `providers.list` projects only catalog and authentication-status metadata, including Pi-supported `thinkingLevels` (`null` map entries hidden, `xhigh`/`max` opt-in). Session thinking overrides are clamped to the live model's supported levels. `sessions.list` revalidates the selected cwd's JSONL directory before SDK discovery and returns no JSONL paths. The browser explicitly selects an existing user-chosen project directory before listing it; the daemon's startup cwd is an internal fallback, not a user-visible default project. Unnamed persisted sessions use their first prompt as a readable list title when available. `sessions.open` and `getSession` project the last assistant `provider`/`model`/`thinkingLevel` onto the session record so reopening a chat keeps that cache identity. Session selection validates a persisted JSONL path before Pi opens it; inactive mutations are never directed by a browser-supplied path. Attachment paths originate in the server-local upload map and cross only private IPC into Pi's tool context. Pi may persist or echo those paths in its authoritative JSONL transcript, so every public projection redacts them recursively: session titles/previews, user and assistant text/thinking, tool arguments, errors, snapshots, and live message events never expose the server-local path. The daemon maps Pi message lifecycle, deltas, tools, queue changes, model/thinking, compaction,
|
|
28
|
+
- The private core commands are `runtime.health`, `projects.list/select`, `providers.list/config.get/models.set/status/login/login.respond/login.status/logout`, `settings.get/set`, `resources.list/update/prompts.create/prompts.delete`, and session list/create/open/rename/delete/tree/navigate/fork/clone/prompt/steer/followUp/abort/model/thinking/compact operations. Resource lists use opaque IDs and never disclose server paths. Only top-level Pi prompt templates and applicable AGENTS.md sources are editable; package/path resources and skills are browse-only. Resource writes are rejected while a session streams, use an atomic replace, then recreate the idle runtime. Project trust changes are persisted in Pi trust.json and likewise recreate an idle runtime; extensions remain disabled regardless of trust. Provider login submits API keys only across authenticated private IPC or brokers Pi's OAuth/device/manual-code interaction; public projections contain only opaque login IDs, prompts, URLs/device codes, model catalog/configuration, and stable states, never credentials or write-only headers. `providers.models.set` is rejected while a session streams, atomically persists Pi's `models.json`, then rehydrates the idle runtime so the catalog is authoritative immediately. `providers.list` projects only catalog and authentication-status metadata, including Pi-supported `thinkingLevels` (`null` map entries hidden, `xhigh`/`max` opt-in). Session thinking overrides are clamped to the live model's supported levels. `sessions.list` revalidates the selected cwd's JSONL directory before SDK discovery and returns no JSONL paths. The browser explicitly selects an existing user-chosen project directory before listing it; the daemon's startup cwd is an internal fallback, not a user-visible default project. Unnamed persisted sessions use their first prompt as a readable list title when available. `sessions.open` and `getSession` project the last assistant `provider`/`model`/`thinkingLevel` onto the session record so reopening a chat keeps that cache identity. Session selection validates a persisted JSONL path before Pi opens it; inactive mutations are never directed by a browser-supplied path. Attachment paths originate in the server-local upload map and cross only private IPC into Pi's tool context. Pi may persist or echo those paths in its authoritative JSONL transcript, so every public projection redacts them recursively: session titles/previews, user and assistant text/thinking, tool arguments, errors, snapshots, and live message events never expose the server-local path. The daemon maps Pi message lifecycle, deltas, tools, queue changes, model/thinking, compaction, interruption state, and session title updates (`session.updated` on create-with-title, first-prompt auto-title, and rename) to canonical events. Tool events carry redacted input arguments, cumulative partial-output snapshots, final output, error text, renderer metadata (edit diffs, truncation notes), and start/end timestamps. Persisted `toolResult` entries merge into their assistant `toolCall` parts as output/error/end time so hydrated sessions render complete tool cards; tool-result entries never surface as standalone messages. Assistant message projections and start events carry the owning user-message id so the restored turn renderer can group and display replies; all assistant entries in a tool loop remain attached to the latest user turn. Assistant message projections also pass through Pi's per-turn `Usage` (input, output, cacheRead, cacheWrite, totalTokens, and the same five numeric cost fields). The daemon sanitizes the raw payload through `projectUsage` before publication: every numeric must be finite and non-negative, the whole object is omitted when any field is missing or wrong type, and unknown keys are never copied. The `assistant.message.end` event carries the same sanitized usage so the last turn updates without waiting for a full `getSession` refetch. The public route whitelist re-applies the same sanitizer on each projection so a stale or malformed payload from a different daemon version cannot surface as `NaN` or `Infinity` in the reducer.
|
|
29
29
|
- Each event and reconnect snapshot has a monotonic sequence. A reconnect receives a bounded contiguous replay after its cursor when available, otherwise an authoritative `session.snapshot`; unavailable runtimes are not represented as empty sessions.
|
|
30
30
|
- Prompt, steer, and follow-up requests acknowledge acceptance immediately after Pi accepts the call; the daemon owns the still-running send promise and publishes any asynchronous rejection as `session.error`. A later prompt on the same session invalidates that rejection so it cannot error the new turn. If the rejected send left Pi streaming, the daemon aborts it. Steer/follow-up against an idle session starts a new prompt instead of `SESSION_NOT_RUNNING`. Agent completion is never held behind the 30-second public/private request budgets.
|
|
31
31
|
- Registry keys combine the SDK session ID with its cwd. A replacement subscription is rebound to the replacement session so old-session events cannot be published under the new identity. A collision is a visible error; it never replaces or disposes an unrelated runtime.
|
|
@@ -409,7 +409,7 @@ export function createSessionDaemon({
|
|
|
409
409
|
throw new SessionDaemonProtocolError('INVALID_SESSION', 'Pi returned an invalid active session.');
|
|
410
410
|
}
|
|
411
411
|
manager.appendSessionInfo(title);
|
|
412
|
-
publish('session.updated', { title }, sessionId, targetDir);
|
|
412
|
+
publish('session.updated', { title: redactAttachmentPaths(title) }, sessionId, targetDir);
|
|
413
413
|
return;
|
|
414
414
|
}
|
|
415
415
|
|
|
@@ -421,7 +421,7 @@ export function createSessionDaemon({
|
|
|
421
421
|
}
|
|
422
422
|
await validatePiSessionJsonlFile(target.path);
|
|
423
423
|
renamePersistedSession({ sessionFile: target.path, title, cwd: targetDir });
|
|
424
|
-
publish('session.updated', { title }, sessionId, targetDir);
|
|
424
|
+
publish('session.updated', { title: redactAttachmentPaths(title) }, sessionId, targetDir);
|
|
425
425
|
};
|
|
426
426
|
|
|
427
427
|
const findPersistedSession = async (sessionId, requestedDirectory) => {
|
|
@@ -664,7 +664,14 @@ export function createSessionDaemon({
|
|
|
664
664
|
runtime = newRuntime;
|
|
665
665
|
activeDirectory = targetCwd;
|
|
666
666
|
rememberRuntimeSession();
|
|
667
|
-
|
|
667
|
+
const created = projectActiveSession(newRuntime, targetCwd);
|
|
668
|
+
const createdTitle = created.session.title
|
|
669
|
+
|| (typeof payload.title === 'string' ? payload.title.trim() : '')
|
|
670
|
+
|| newRuntime.session?.sessionManager?.getSessionName?.();
|
|
671
|
+
if (createdTitle) {
|
|
672
|
+
publish('session.updated', { title: redactAttachmentPaths(createdTitle) }, created.session.id, targetCwd);
|
|
673
|
+
}
|
|
674
|
+
return created;
|
|
668
675
|
};
|
|
669
676
|
|
|
670
677
|
const listProviders = async (requestedDirectory) => {
|
|
@@ -1320,7 +1327,7 @@ export function createSessionDaemon({
|
|
|
1320
1327
|
const derived = deriveSessionTitle(payload.text);
|
|
1321
1328
|
if (derived && typeof manager?.appendSessionInfo === 'function') {
|
|
1322
1329
|
manager.appendSessionInfo(derived);
|
|
1323
|
-
publish('session.updated', { title: derived }, payload.sessionId, activeRuntime.cwd);
|
|
1330
|
+
publish('session.updated', { title: redactAttachmentPaths(derived) }, payload.sessionId, activeRuntime.cwd);
|
|
1324
1331
|
}
|
|
1325
1332
|
}
|
|
1326
1333
|
|
|
@@ -45,6 +45,7 @@ class FakeSession {
|
|
|
45
45
|
getLeafId: () => 'fake-entry',
|
|
46
46
|
getTree: () => [{ entry: { id: 'fake-entry', parentId: undefined, timestamp: '2026-01-01T00:00:00.000Z' }, children: [] }],
|
|
47
47
|
appendSessionInfo: (name) => this.names.push(name),
|
|
48
|
+
getSessionName: () => this.names[this.names.length - 1],
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
51
|
|
|
@@ -465,6 +466,29 @@ describe('Pi session daemon spike', () => {
|
|
|
465
466
|
await client.close();
|
|
466
467
|
});
|
|
467
468
|
|
|
469
|
+
it('publishes session.updated when a session is first prompted', async () => {
|
|
470
|
+
const root = await mkdtemp(join(tmpdir(), 'pichamber-pi-daemon-'));
|
|
471
|
+
const endpoint = testDaemonEndpoint(root);
|
|
472
|
+
daemon = createSessionDaemon({
|
|
473
|
+
endpoint,
|
|
474
|
+
credential,
|
|
475
|
+
cwd: root,
|
|
476
|
+
createRuntime: async () => new FakeRuntime({ cwd: root, session: new FakeSession('pi-session-old') }),
|
|
477
|
+
});
|
|
478
|
+
await daemon.start();
|
|
479
|
+
|
|
480
|
+
const client = connectClient(endpoint);
|
|
481
|
+
await client.authenticate();
|
|
482
|
+
await client.request('sessions.create', { cwd: root });
|
|
483
|
+
const promptedUpdated = client.next((frame) => frame.event === 'session.updated' && frame.payload?.title === 'Inspect this report');
|
|
484
|
+
await client.request('sessions.prompt', { sessionId: 'pi-session-new', text: 'Inspect this report\n\nDetails' });
|
|
485
|
+
await expect(promptedUpdated).resolves.toMatchObject({
|
|
486
|
+
event: 'session.updated',
|
|
487
|
+
payload: { sessionId: 'pi-session-new', title: 'Inspect this report' },
|
|
488
|
+
});
|
|
489
|
+
await client.close();
|
|
490
|
+
});
|
|
491
|
+
|
|
468
492
|
it('creates and selects a persisted Pi session with supported creation metadata', async () => {
|
|
469
493
|
const root = await mkdtemp(join(tmpdir(), 'pichamber-pi-daemon-'));
|
|
470
494
|
const endpoint = testDaemonEndpoint(root);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{R as C,j as e}from"./vendor-react-DYyjh7Q0.js";import{f as z,T as y,y as _,B as c,I as a,z as v,aG as I}from"./useAppFontEffects-DXASDavv.js";import{u as H}from"./index-CRB7i1S2.js";const N=38,h=200,k=({value:t,onChange:o,placeholder:m,disabled:i=!1,hasTouchInput:r=!1,isMobile:u=!1})=>{const n=C.useRef(null),j=H(s=>s.inputSpellcheckEnabled);return C.useLayoutEffect(()=>{const s=n.current;if(!s)return;const l=document.activeElement===s,d=()=>{s.style.height=`${N}px`;const p=s.scrollHeight,x=Math.min(Math.max(p,N),h);s.style.height=`${x}px`,s.style.overflowY=p>h?"auto":"hidden",p>h&&!l&&(s.scrollTop=s.scrollHeight)};d();const f=window.requestAnimationFrame(d);return()=>{window.cancelAnimationFrame(f)}},[t]),e.jsx("textarea",{ref:n,value:t,onChange:s=>o(s.target.value),placeholder:m??"Commit message",rows:1,disabled:i,autoCorrect:r?"on":"off",autoCapitalize:r?"sentences":"off",spellCheck:u||j,className:z("w-full rounded-lg border border-border/60 bg-surface-elevated px-3 py-2 typography-ui-label text-foreground placeholder:text-muted-foreground","resize-none outline-none transition-[border-color,box-shadow] duration-150","focus-visible:ring-2 focus-visible:ring-[var(--interactive-focus-ring)]",i&&"opacity-50"),style:{minHeight:N,maxHeight:h}})},F=({highlights:t,onInsert:o})=>{if(t.length===0)return null;const m=()=>{o(t)};return e.jsxs("div",{className:"space-y-2 rounded-xl border border-border/60 bg-transparent px-3 py-2",children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("p",{className:"typography-micro text-muted-foreground",children:"Highlights"}),e.jsxs(y,{children:[e.jsx(_,{asChild:!0,children:e.jsx(c,{variant:"ghost",size:"icon",className:"size-6",onClick:m,"aria-label":"Insert aria label",children:e.jsx(a,{name:"arrow-down",className:"size-4"})})}),e.jsx(v,{sideOffset:8,children:"Insert tooltip"})]})]}),e.jsx("ul",{className:"space-y-1",children:t.map((i,r)=>e.jsx("li",{className:"typography-meta text-foreground",children:i},`${i}-${r}`))})]})},q=({stagedCount:t,commitMessage:o,onCommitMessageChange:m,generatedHighlights:i,onInsertHighlights:r,onGenerateMessage:u,isGeneratingMessage:n,onCommit:j,onCommitAndPush:s,commitAction:l,hasPendingIndexMutation:d=!1,gitmojiEnabled:f,onOpenGitmojiPicker:p})=>{const x=t>0,b=o.trim()&&x&&l===null&&!d,{isMobile:g,hasTouchInput:w}=I();return e.jsxs("section",{className:"border-0 bg-transparent rounded-none",children:[e.jsxs("div",{className:"flex w-full items-baseline gap-2 px-0 pt-2 pb-1",children:[e.jsx("h3",{className:"typography-ui-header font-semibold text-foreground",children:"Commit"}),x?null:e.jsx("span",{className:"min-w-0 truncate typography-meta text-muted-foreground",children:"Stage files to enable commit."})]}),e.jsxs("div",{className:"flex flex-col gap-3 px-0 pt-1 pb-3",children:[e.jsx(F,{highlights:i,onInsert:r}),e.jsx(k,{value:o,onChange:m,placeholder:"Commit message",disabled:l!==null,hasTouchInput:w,isMobile:g}),f&&e.jsxs(c,{variant:"outline",size:"sm",onClick:p,className:"w-fit",type:"button",children:[e.jsx(a,{name:"emotion-happy",className:"size-4"}),"Add gitmoji"]}),e.jsxs("div",{className:"@container/commit-actions flex items-center gap-2 min-w-0",children:[e.jsxs(c,{variant:"outline",size:"sm",onClick:u,disabled:n||l!==null||d||t===0,type:"button","aria-label":"Generate commit message",className:"commit-actions__btn",children:[n?e.jsx(a,{name:"loader-4",className:"size-4 animate-spin"}):e.jsx(a,{name:"ai-generate-2",className:"size-4 text-primary"}),e.jsx("span",{className:"commit-actions__label",children:"Generate"})]}),e.jsx("div",{className:"flex-1"}),e.jsx(c,{size:"sm",variant:"outline",onClick:j,disabled:!b||n,className:"commit-actions__btn whitespace-nowrap","aria-label":"Commit aria label",children:l==="commit"?e.jsxs(e.Fragment,{children:[e.jsx(a,{name:"loader-4",className:"size-4 animate-spin"}),e.jsx("span",{className:"commit-actions__label",children:"Committing..."})]}):e.jsxs(e.Fragment,{children:[e.jsx(a,{name:"git-commit",className:"size-4"}),e.jsx("span",{className:"commit-actions__label",children:"Commit"})]})}),g?e.jsxs(y,{children:[e.jsx(_,{asChild:!0,children:e.jsx(c,{variant:"default",size:"sm",onClick:()=>s(),disabled:!b||n,className:"h-7 w-7 p-0","aria-label":"Commit and sync",children:l==="commitAndPush"?e.jsx(a,{name:"loader-4",className:"size-4 animate-spin"}):e.jsx(a,{name:"arrow-up",className:"size-3.5"})})}),e.jsx(v,{side:"top",children:e.jsx("p",{children:"Commit & sync"})})]}):e.jsx(c,{size:"sm",variant:"default",onClick:()=>s(),disabled:!b||n,className:"commit-actions__btn","aria-label":"Commit and sync",children:l==="commitAndPush"?e.jsxs(e.Fragment,{children:[e.jsx(a,{name:"loader-4",className:"size-4 animate-spin"}),e.jsx("span",{className:"commit-actions__label commit-actions__label--push",children:"Syncing..."})]}):e.jsxs(e.Fragment,{children:[e.jsx(a,{name:"arrow-up",className:"size-3.5"}),e.jsx("span",{className:"commit-actions__label commit-actions__label--push",children:"Commit & sync"})]})})]})]})]})};export{q as C};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{R as e,j as h}from"./vendor-react-DYyjh7Q0.js";import{D as E}from"./vendor-react-drawio-Boqanm0j.js";import{f as x}from"./useAppFontEffects-DXASDavv.js";const D='<mxfile><diagram id="new" name="Page-1"><mxGraphModel dx="0" dy="0" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0"><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel></diagram></mxfile>';function g(){if(typeof document>"u")return!1;const o=document.documentElement.getAttribute("data-theme");return o==="dark"||!o&&window.matchMedia("(prefers-color-scheme: dark)").matches}const X=e.forwardRef(function({xml:t,readOnly:a,className:R,onChange:c},v){const s=e.useRef(t),i=e.useRef(null),m=e.useRef(!1),[n,k]=e.useState(g),u=e.useRef(t),d=e.useRef(t);d.current!==t&&(d.current=t,u.current=t,s.current=t);const f=e.useRef(n);f.current!==n&&(f.current=n,u.current=s.current);const l=e.useRef(null);e.useEffect(()=>{const r=()=>k(g());r();const p=new MutationObserver(r);return p.observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme"]}),()=>p.disconnect()},[]),e.useEffect(()=>{const r=setTimeout(()=>{l.current?.querySelector(".diagrams-iframe")?.focus()},600);return()=>clearTimeout(r)},[]),e.useImperativeHandle(v,()=>({getXml:()=>s.current}));const b=e.useCallback(()=>{!t&&!m.current&&(m.current=!0,setTimeout(()=>{i.current?.template({})},500))},[t]),w=e.useCallback(r=>{s.current=r.xml,c?.(r.xml)},[c]);return h.jsx("div",{ref:l,className:x("h-full w-full",R),children:h.jsx(E,{ref:i,xml:u.current||D,autosave:!0,urlParameters:{ui:a?"simple":n?"dark":"kennedy",spin:!0,libraries:!a,chrome:a,nav:a,layers:a,noSaveBtn:!0,noExitBtn:!0,saveAndExit:!1},onLoad:b,onAutoSave:w},n?"dark":"light")})});export{X as D};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{R as r,j as t}from"./vendor-react-DYyjh7Q0.js";import{u as a}from"./index-CRB7i1S2.js";import{K as v,I as m}from"./useAppFontEffects-DXASDavv.js";import{D as j}from"./DiagramEditor-ig5nYV6t.js";import"./vendor-scheduler-7OC5HNn7.js";import"./vendor-zustand-CnsBKjP5.js";import"./vendor-simplewebauthn-browser-VDA66rT_.js";import"./vendor-vite-runtime-DD1OZmLC.js";import"./vendor-base-ui-DwfTg5wD.js";import"./vendor-floating-ui-utils-BzXdqYpP.js";import"./vendor-floating-ui-react-dom-sVFnuruh.js";import"./vendor-floating-ui-dom-Bkw82kL9.js";import"./vendor-floating-ui-core-CXmKXOXc.js";import"./vendor-use-sync-external-store-B7OScDIw.js";import"./runtimeSurface-Cnqjhb6Y.js";import"./vendor-dnd-kit-core-GipZW2tY.js";import"./vendor-dnd-kit-utilities-CrOcp0Bp.js";import"./vendor-dnd-kit-accessibility-Cv52zwWT.js";import"./vendor-dnd-kit-sortable-Ct2QxIHb.js";import"./vendor-cmdk-BCEUzcHE.js";import"./vendor-radix-ui-react-dialog-jX3Dld8c.js";import"./vendor-radix-ui-primitive-Dc_FVRD7.js";import"./vendor-radix-ui-react-compose-refs-D1GnwnP9.js";import"./vendor-radix-ui-react-context-CM4yFQ2I.js";import"./vendor-radix-ui-react-id-BcxRjnbX.js";import"./vendor-radix-ui-react-use-layout-effect-0fyGwS2_.js";import"./vendor-radix-ui-react-use-controllable-state-C_ecoh35.js";import"./vendor-radix-ui-react-dismissable-layer-M7LsH3yL.js";import"./vendor-radix-ui-react-primitive-CfpW6_IT.js";import"./vendor-radix-ui-react-slot-BxUc3Pvz.js";import"./vendor-radix-ui-react-use-callback-ref-UKEWHtoP.js";import"./vendor-radix-ui-react-use-escape-keydown-Co13lHGj.js";import"./vendor-radix-ui-react-focus-scope-C2K7lNPv.js";import"./vendor-radix-ui-react-portal-xlJWBfDg.js";import"./vendor-radix-ui-react-presence-Btn-Dp7L.js";import"./vendor-radix-ui-react-focus-guards-D5MZ0nQs.js";import"./vendor-react-remove-scroll-CfeMe0DL.js";import"./vendor-tslib-De9GV7Vy.js";import"./vendor-react-remove-scroll-bar-C9YRs2nU.js";import"./vendor-react-style-singleton-B1H4bWSG.js";import"./vendor-get-nonce-C-Z93AgS.js";import"./vendor-use-sidecar-C-AuTczP.js";import"./vendor-use-callback-ref-CJB0R85I.js";import"./vendor-aria-hidden-DvXkyWUv.js";import"./vendor-codemirror-language-BuDklWi8.js";import"./vendor-lezer-common-B3-cUCR_.js";import"./vendor-codemirror-state-Wdz6Qsow.js";import"./vendor-marijn-find-cluster-break-DXwl3gUT.js";import"./vendor-codemirror-view-pXtfpqbT.js";import"./vendor-style-mod-NZ5MEPvj.js";import"./vendor-w3c-keyname-Vcq4gwWv.js";import"./vendor-crelt-C8TCjufn.js";import"./vendor-lezer-highlight-CDMHUZ74.js";import"./vendor-codemirror-lang-javascript-CrbeaEAU.js";import"./vendor-lezer-javascript-D7Tj24ot.js";import"./vendor-lezer-lr-bhwg1HMg.js";import"./vendor-codemirror-autocomplete-3xMyV7a2.js";import"./vendor-codemirror-lang-json-BTvAjwkJ.js";import"./vendor-lezer-json-Iaex0UN-.js";import"./vendor-codemirror-lang-css-C4jEScp7.js";import"./vendor-lezer-css-C5xB6jxd.js";import"./vendor-codemirror-lang-html-C1Svvdey.js";import"./vendor-lezer-html-C0I1p52t.js";import"./vendor-codemirror-lang-markdown-BloD9GN-.js";import"./vendor-lezer-markdown-C6g-d6P3.js";import"./vendor-codemirror-language-data-CFVH0pe8.js";import"./vendor-codemirror-lang-python-CR2g6ukK.js";import"./vendor-lezer-python-CvuFxeZl.js";import"./vendor-codemirror-commands-Bzf12ODH.js";import"./vendor-class-variance-authority-5VPnzWs2.js";import"./vendor-clsx-B-dksMZM.js";import"./vendor-tanstack-react-virtual-IrbCBGJ4.js";import"./vendor-tanstack-virtual-core-Dubg10g5.js";import"./vendor-framer-motion-DJASDzNt.js";import"./vendor-motion-dom-DJLgZh4Q.js";import"./vendor-motion-utils-BGh8QBgx.js";import"./vendor-sonner-dWA7qvjr.js";import"./vendor-tailwind-merge-CPcsbTWB.js";import"./vendor-fuse.js-Dw8P0qyA.js";import"./vendor-react-drawio-Boqanm0j.js";function Jt(){const{files:o}=v(),[e,d]=r.useState(null),[s,p]=r.useState(""),[x,n]=r.useState(!0),l=r.useRef(null),c=a(i=>i.pendingDiagramFile),u=r.useCallback(async i=>{n(!0),d(i);try{const f=await o?.readFile?.(i);f&&p(f.content)}catch{p("")}finally{n(!1)}},[o]);r.useEffect(()=>{if(!c)return;const i=a.getState().consumePendingDiagramFile();i&&u(i)},[u,c]);const g=r.useCallback(async()=>{const i=l.current?.getXml();e&&o?.writeFile&&i&&i!==s&&(await o.writeFile(e,i),p(i))},[e,o,s]),h=e?e.split("/").pop()||e:"";return e?x?t.jsx("div",{className:"flex h-full items-center justify-center p-3",children:t.jsx(m,{name:"loader-4",className:"size-4 animate-spin"})}):t.jsxs("div",{className:"flex h-full flex-col",children:[t.jsxs("div",{className:"flex items-center gap-2 border-b border-border/30 px-3 py-1.5",children:[t.jsx(m,{name:"file",className:"size-4 shrink-0 text-muted-foreground"}),t.jsx("span",{className:"typography-ui text-muted-foreground truncate flex-1",children:h}),t.jsx("button",{type:"button",onClick:()=>{g()},className:"size-6 flex items-center justify-center rounded-md text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",title:"Save diagram",children:t.jsx(m,{name:"save-3",className:"size-4"})}),t.jsx("button",{type:"button",onClick:()=>a.getState().setActiveMainTab("chat"),className:"size-6 flex items-center justify-center rounded-md text-muted-foreground hover:bg-interactive-hover/50 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",title:"Close diagram view",children:t.jsx(m,{name:"close",className:"size-4"})})]}),t.jsx("div",{className:"flex-1 min-h-0",children:t.jsx(j,{ref:l,xml:s,className:"h-full"})})]}):t.jsx("div",{className:"flex h-full items-center justify-center p-3",children:t.jsx("div",{className:"typography-ui text-muted-foreground",children:"Pick a file from the tree."})})}export{Jt as DiagramView};
|