pim-agent 0.16.0 → 0.17.1
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/package.json +2 -2
- package/packages/core/src/attachments/AttachmentSweep.ts +155 -0
- package/packages/core/src/session/SessionHost.ts +11 -0
- package/packages/core/src/shared/SendFile.ts +6 -6
- package/packages/daemon/src/WebSurface.ts +8 -0
- package/packages/protocol/src/ServerEvent.ts +8 -0
- package/packages/server/src/AttachmentEndpoint.ts +13 -0
- package/packages/server/src/SendFileTool.ts +2 -1
- package/packages/server/src/SessionProjection.ts +3 -14
- package/packages/server/src/SessionStream.ts +18 -0
- package/packages/telegram/src/SendFileSchema.ts +2 -0
- package/packages/telegram/src/SendFileTool.ts +7 -2
- package/packages/web/dist/client/assets/{abnfDiagram-O67JEVCF-DjiNWKul.js → abnfDiagram-O67JEVCF-S70BZyPB.js} +1 -1
- package/packages/web/dist/client/assets/{arc-L-qgUA9f.js → arc-D0p0HLMb.js} +1 -1
- package/packages/web/dist/client/assets/architecture-7GRP2DOG-yQf0WKdB.js +1 -0
- package/packages/web/dist/client/assets/{architectureDiagram-NJMV4G6O-BSgBjV0C.js → architectureDiagram-NJMV4G6O-6GuZb687.js} +1 -1
- package/packages/web/dist/client/assets/{blockDiagram-BEXU5L5S-D93Ll9Dw.js → blockDiagram-BEXU5L5S-DzywmoiJ.js} +1 -1
- package/packages/web/dist/client/assets/{c4Diagram-YGBWAQC7-Cso9YWgM.js → c4Diagram-YGBWAQC7-D-MpQ0W3.js} +1 -1
- package/packages/web/dist/client/assets/channel-A9lu_dxi.js +1 -0
- package/packages/web/dist/client/assets/{chunk-3FUC2YCW-DBEKoKZF.js → chunk-3FUC2YCW-DF2B3IxV.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-5DYCD2WN-Cbw5WlJE.js → chunk-5DYCD2WN-DMceIjqt.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-742MDFTN-CbGMGJSz.js → chunk-742MDFTN-BOZ7Pdth.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-7INBJB4K-SPvc4azf.js → chunk-7INBJB4K-BvpoYrYB.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-7M6MHVWA-C7fjnYS0.js → chunk-7M6MHVWA-ByOdpY-Y.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-7PRAP22T-uozWTkjM.js → chunk-7PRAP22T-h7j33vs7.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-GTNCS2PH-B0QMH9-t.js → chunk-GTNCS2PH-Bg1KOR7I.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-GWA4HPMP-hz0kwYNQ.js → chunk-GWA4HPMP-CA2QZz2h.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-MBY4JIJT-C15Cngrs.js → chunk-MBY4JIJT-BE61C_Va.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-NETBCI7D-DBtUA5X-.js → chunk-NETBCI7D-CEsickh0.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-O7XYJQB3-CgAoHDzj.js → chunk-O7XYJQB3-CYYv2jJO.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-UA2S7LBM-DFxDogqX.js → chunk-UA2S7LBM-B8-QOH6L.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-WEXAMYUT-CWZQAlYC.js → chunk-WEXAMYUT-CyMRJQp6.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-XXDRQBXY-C5xGs6Rc.js → chunk-XXDRQBXY-Cmd7vLTd.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-Z7XXMR3K-CHcw5vr_.js → chunk-Z7XXMR3K-WhtXBeDl.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-ZIGJFQKS-BtHD8Zy8.js → chunk-ZIGJFQKS-CaEojzzE.js} +1 -1
- package/packages/web/dist/client/assets/{classDiagram-v2-NBCMYWYE-Dw3ROQG-.js → classDiagram-v2-NBCMYWYE-Dv2qUp29.js} +1 -1
- package/packages/web/dist/client/assets/{core-CeOhpnl2.js → core-Cb9gUTo5.js} +1 -1
- package/packages/web/dist/client/assets/{cose-bilkent-JH36ORCC-BM6rvX_q.js → cose-bilkent-JH36ORCC-DQEDZfC5.js} +1 -1
- package/packages/web/dist/client/assets/{cynefin-OW5HDTMX-smBcMKQm.js → cynefin-OW5HDTMX-DOQlVGX6.js} +1 -1
- package/packages/web/dist/client/assets/{cynefinDiagram-VND7K2PF-Xo8uWpc_.js → cynefinDiagram-VND7K2PF-C4W1RUFd.js} +1 -1
- package/packages/web/dist/client/assets/{dagre-6A5THRUB-BFbnP-g2.js → dagre-6A5THRUB-CRs0De0G.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-22UHCM2B-C-IfNBdK.js → diagram-22UHCM2B-DxFDjO43.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-3UASUU5V-B0c7MTxl.js → diagram-3UASUU5V-CegEAUrZ.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-ATOU4E4O-B000aNmc.js → diagram-ATOU4E4O-2aB_SB1o.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-CDSNMT55-DIlix5yU.js → diagram-CDSNMT55-CsarcWtX.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-MLGK6HIB-BZi63Dr-.js → diagram-MLGK6HIB-DQzJp4eN.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-MPIPVDR6-Bbeeq0Au.js → diagram-MPIPVDR6-DQl3_PIf.js} +1 -1
- package/packages/web/dist/client/assets/{dist-BRnpNqza.js → dist-CNCbCG-8.js} +1 -1
- package/packages/web/dist/client/assets/{ebnfDiagram-ZINNZB2B-BsXd4VIv.js → ebnfDiagram-ZINNZB2B-DKc6meXW.js} +1 -1
- package/packages/web/dist/client/assets/{elk-276RUBZZ-D4QDfO3S.js → elk-276RUBZZ-_NGIsb93.js} +1 -1
- package/packages/web/dist/client/assets/{erDiagram-OPXOYQCR-C6arxXyP.js → erDiagram-OPXOYQCR-C3gwh24V.js} +1 -1
- package/packages/web/dist/client/assets/eventmodeling-NTZA5JFV-Bo93YedS.js +1 -0
- package/packages/web/dist/client/assets/flowDiagram-KWPJA3E3-0A15mPte.js +1 -0
- package/packages/web/dist/client/assets/{ganttDiagram-FUAMR5RP-DpfbQ0_7.js → ganttDiagram-FUAMR5RP-Df_VTt0y.js} +1 -1
- package/packages/web/dist/client/assets/{gitGraph-4MIJSDKK-D69loz0z.js → gitGraph-4MIJSDKK-BPHlByEr.js} +1 -1
- package/packages/web/dist/client/assets/{gitGraphDiagram-X574FWY7-DPTNIBdx.js → gitGraphDiagram-X574FWY7-BDdaNY3g.js} +1 -1
- package/packages/web/dist/client/assets/{index-BIRI2JUA.css → index-BkWuqvcO.css} +1 -1
- package/packages/web/dist/client/assets/{index-CY4YRMsv.js → index-D-QPbUKB.js} +28 -27
- package/packages/web/dist/client/assets/{info-A6RAGUB7-0ypbrrGR.js → info-A6RAGUB7-DVDHVz3I.js} +1 -1
- package/packages/web/dist/client/assets/{infoDiagram-VRGFBTTK-CT4qjd2X.js → infoDiagram-VRGFBTTK-CDDMeu-r.js} +1 -1
- package/packages/web/dist/client/assets/{ishikawaDiagram-OU5B5YK6-Ds-pffvh.js → ishikawaDiagram-OU5B5YK6-DMDXKIyQ.js} +1 -1
- package/packages/web/dist/client/assets/{journeyDiagram-ZHPQQLJL-fNWD9i15.js → journeyDiagram-ZHPQQLJL-UKMXUlz_.js} +1 -1
- package/packages/web/dist/client/assets/{kanban-definition-PNTS6WVX-DoA1ow-i.js → kanban-definition-PNTS6WVX-DWgqq59e.js} +1 -1
- package/packages/web/dist/client/assets/{line-3HHp4UkM.js → line-vDf4ObKd.js} +1 -1
- package/packages/web/dist/client/assets/{linear-DIzOXsHI.js → linear-BBVij_3S.js} +1 -1
- package/packages/web/dist/client/assets/{mermaid-parser.core-B9OBpC9v.js → mermaid-parser.core-CDKMCqjM.js} +3 -3
- package/packages/web/dist/client/assets/{mermaid.core-CkifwY3w.js → mermaid.core-BjpOh806.js} +4 -4
- package/packages/web/dist/client/assets/{mindmap-definition-NLK3R4M7-Cnar9-Py.js → mindmap-definition-NLK3R4M7-BMipQZeS.js} +1 -1
- package/packages/web/dist/client/assets/{packet-AYTQ26CC-Jns1LUBO.js → packet-AYTQ26CC-DImL0XPD.js} +1 -1
- package/packages/web/dist/client/assets/{pegDiagram-GJSIUBJH-nFP7dozy.js → pegDiagram-GJSIUBJH-BJD0IXTL.js} +1 -1
- package/packages/web/dist/client/assets/{pie-WAS4IAKB-BjmmQ763.js → pie-WAS4IAKB-CgC6B0rZ.js} +1 -1
- package/packages/web/dist/client/assets/{pieDiagram-5QR66LMP-_Ol4YM7C.js → pieDiagram-5QR66LMP-jae3zN89.js} +1 -1
- package/packages/web/dist/client/assets/{quadrantDiagram-O4NWA36T-CSfIDrCe.js → quadrantDiagram-O4NWA36T-BhTx0jk2.js} +1 -1
- package/packages/web/dist/client/assets/{radar-RG4KPBEZ-DeDV4Ozw.js → radar-RG4KPBEZ-B_ltf9AY.js} +1 -1
- package/packages/web/dist/client/assets/{railroad-74A4TZTK-BjYITqPl.js → railroad-74A4TZTK-By3fDd_H.js} +1 -1
- package/packages/web/dist/client/assets/railroad-abnf-HS5TGJTU-BX_Mf2eZ.js +1 -0
- package/packages/web/dist/client/assets/railroad-ebnf-LZEXJU2U-DLNVANLW.js +1 -0
- package/packages/web/dist/client/assets/railroad-peg-WCYAUIDC-DP4QW8Jl.js +1 -0
- package/packages/web/dist/client/assets/{railroadDiagram-XR7U4H2S-cQsJHXYd.js → railroadDiagram-XR7U4H2S-CPt1wtLY.js} +1 -1
- package/packages/web/dist/client/assets/{requirementDiagram-PLB6GJNP-D8iL2cTl.js → requirementDiagram-PLB6GJNP-CfE8UBJz.js} +1 -1
- package/packages/web/dist/client/assets/{sankeyDiagram-IPEJSGJF-DHbIjJtD.js → sankeyDiagram-IPEJSGJF-Dhx4GKLx.js} +1 -1
- package/packages/web/dist/client/assets/{sequenceDiagram-PO4LG4MO-IgI9FWmD.js → sequenceDiagram-PO4LG4MO-gBkD37HF.js} +1 -1
- package/packages/web/dist/client/assets/{src-CvUW7nT-.js → src-BIB5suEH.js} +1 -1
- package/packages/web/dist/client/assets/{stateDiagram-v2-GCMORJYK-CUdbTd8Z.js → stateDiagram-v2-GCMORJYK-D83j6rTL.js} +1 -1
- package/packages/web/dist/client/assets/{swimlanes-2SLR337P-DjGv-iIk.js → swimlanes-2SLR337P-fycZYHpf.js} +1 -1
- package/packages/web/dist/client/assets/swimlanesDiagram-TC7HE7FX-CFtvwr_x.js +8 -0
- package/packages/web/dist/client/assets/{timeline-definition-EJHVYXUP-D3--MIhr.js → timeline-definition-EJHVYXUP-Cvi1YHnr.js} +1 -1
- package/packages/web/dist/client/assets/{treeView-Q6P3EWNA-Bp6MU1OS.js → treeView-Q6P3EWNA-29MJkv6b.js} +1 -1
- package/packages/web/dist/client/assets/{treemap-WGGIJYW6-_oQZ7eAW.js → treemap-WGGIJYW6-C40lMWhu.js} +1 -1
- package/packages/web/dist/client/assets/{usecaseDiagram-POWQR4AR-fBSgWqU6.js → usecaseDiagram-POWQR4AR-B5Rx1Px3.js} +1 -1
- package/packages/web/dist/client/assets/{vennDiagram-UO4OBE2U-DDlGnf8X.js → vennDiagram-UO4OBE2U-BYfkzvKQ.js} +1 -1
- package/packages/web/dist/client/assets/{wardley-WFR3VGLG-CQOVh6Bi.js → wardley-WFR3VGLG-D-FLNXpG.js} +1 -1
- package/packages/web/dist/client/assets/{wardleyDiagram-VNRHLVJA-CCe1EWES.js → wardleyDiagram-VNRHLVJA-Bw9x86aC.js} +1 -1
- package/packages/web/dist/client/assets/{xychartDiagram-PMCCYNJV-BD2EkyDp.js → xychartDiagram-PMCCYNJV-LULIohD6.js} +1 -1
- package/packages/web/dist/client/index.html +2 -2
- package/packages/web/dist/client/assets/architecture-7GRP2DOG-C-Cc8ZGz.js +0 -1
- package/packages/web/dist/client/assets/channel-BT3dJIQT.js +0 -1
- package/packages/web/dist/client/assets/eventmodeling-NTZA5JFV-BfxrKIKV.js +0 -1
- package/packages/web/dist/client/assets/flowDiagram-KWPJA3E3-KhkDuqWr.js +0 -1
- package/packages/web/dist/client/assets/railroad-abnf-HS5TGJTU-Bko8bqdm.js +0 -1
- package/packages/web/dist/client/assets/railroad-ebnf-LZEXJU2U-Bctn0Z-b.js +0 -1
- package/packages/web/dist/client/assets/railroad-peg-WCYAUIDC-9IigqOyn.js +0 -1
- package/packages/web/dist/client/assets/swimlanesDiagram-TC7HE7FX-3fthS15x.js +0 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pim-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.1",
|
|
4
4
|
"description": "A batteries-included distro of Pi, accessible in your terminal, browser and Telegram.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"vite": "^8.2.2"
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@earendil-works/pi-coding-agent": "1.
|
|
79
|
+
"@earendil-works/pi-coding-agent": "1.4.0",
|
|
80
80
|
"diff": "^9.0.0",
|
|
81
81
|
"fzf": "^0.5.2",
|
|
82
82
|
"grammy": "^1.46.0",
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { readdir, rm, rmdir, stat } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { SessionMeta } from "../session/SessionMeta";
|
|
5
|
+
import type { SessionRegistry } from "../session/SessionRegistry";
|
|
6
|
+
|
|
7
|
+
export type StoredEntry = {
|
|
8
|
+
readonly scope: string;
|
|
9
|
+
readonly path: string;
|
|
10
|
+
readonly size: number;
|
|
11
|
+
readonly modifiedAt: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type SweepPolicy = {
|
|
15
|
+
/** Session ids with a session file; an empty set disables the orphan rule. */
|
|
16
|
+
readonly alive: ReadonlySet<string>;
|
|
17
|
+
readonly archived: ReadonlySet<string>;
|
|
18
|
+
readonly now: number;
|
|
19
|
+
readonly budgetBytes?: number;
|
|
20
|
+
readonly graceMs?: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type SweepResult = {
|
|
24
|
+
readonly removed: number;
|
|
25
|
+
readonly freedBytes: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const BUDGET_BYTES = 5 * 1024 * 1024 * 1024;
|
|
29
|
+
const GRACE_MS = 24 * 60 * 60 * 1000;
|
|
30
|
+
const INTERVAL_MS = 60 * 60 * 1000;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Files younger than the grace period are untouched and uncounted. Of the
|
|
34
|
+
* rest: orphans and files bigger than the whole budget go, then the oldest —
|
|
35
|
+
* archived sessions first — until the remainder fits the budget.
|
|
36
|
+
*/
|
|
37
|
+
function doomed(
|
|
38
|
+
entries: readonly StoredEntry[],
|
|
39
|
+
policy: SweepPolicy
|
|
40
|
+
): readonly StoredEntry[] {
|
|
41
|
+
const budget = policy.budgetBytes ?? BUDGET_BYTES;
|
|
42
|
+
const cutoff = policy.now - (policy.graceMs ?? GRACE_MS);
|
|
43
|
+
const trustsListing = policy.alive.size > 0;
|
|
44
|
+
const removed: StoredEntry[] = [];
|
|
45
|
+
const kept: StoredEntry[] = [];
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
if (entry.modifiedAt >= cutoff) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const orphaned = trustsListing && !policy.alive.has(entry.scope);
|
|
51
|
+
(orphaned || entry.size > budget ? removed : kept).push(entry);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const rank = (entry: StoredEntry) =>
|
|
55
|
+
policy.archived.has(entry.scope) ? 0 : 1;
|
|
56
|
+
kept.sort((a, b) => rank(a) - rank(b) || a.modifiedAt - b.modifiedAt);
|
|
57
|
+
let total = kept.reduce((sum, entry) => sum + entry.size, 0);
|
|
58
|
+
for (const entry of kept) {
|
|
59
|
+
if (total <= budget) {
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
removed.push(entry);
|
|
63
|
+
total -= entry.size;
|
|
64
|
+
}
|
|
65
|
+
return removed;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function scan(root: string): Promise<readonly StoredEntry[]> {
|
|
69
|
+
const scopes = await readdir(root, { withFileTypes: true }).catch(() => []);
|
|
70
|
+
const perScope = await Promise.all(
|
|
71
|
+
scopes
|
|
72
|
+
.filter((scope) => scope.isDirectory())
|
|
73
|
+
.map(async (scope) => {
|
|
74
|
+
const dir = join(root, scope.name);
|
|
75
|
+
const names = await readdir(dir).catch(() => []);
|
|
76
|
+
const entries = await Promise.all(
|
|
77
|
+
names.map(async (name) => {
|
|
78
|
+
const path = join(dir, name);
|
|
79
|
+
const st = await stat(path).catch(() => undefined);
|
|
80
|
+
return st?.isFile()
|
|
81
|
+
? {
|
|
82
|
+
scope: scope.name,
|
|
83
|
+
path,
|
|
84
|
+
size: st.size,
|
|
85
|
+
modifiedAt: st.mtimeMs,
|
|
86
|
+
}
|
|
87
|
+
: undefined;
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
return entries.filter((entry) => entry !== undefined);
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
return perScope.flat();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function sweep(root: string, policy: SweepPolicy): Promise<SweepResult> {
|
|
97
|
+
const victims = doomed(await scan(root), policy);
|
|
98
|
+
await Promise.all(victims.map((entry) => rm(entry.path, { force: true })));
|
|
99
|
+
const scopes = new Set(victims.map((entry) => entry.scope));
|
|
100
|
+
await Promise.all(
|
|
101
|
+
[...scopes].map((scope) => rmdir(join(root, scope)).catch(() => {}))
|
|
102
|
+
);
|
|
103
|
+
return {
|
|
104
|
+
removed: victims.length,
|
|
105
|
+
freedBytes: victims.reduce((sum, entry) => sum + entry.size, 0),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Sweeps now and hourly; returns the stop function. */
|
|
110
|
+
function install(deps: {
|
|
111
|
+
readonly root: string;
|
|
112
|
+
readonly registry: SessionRegistry;
|
|
113
|
+
readonly meta: SessionMeta;
|
|
114
|
+
}): () => void {
|
|
115
|
+
let running: Promise<void> | undefined;
|
|
116
|
+
const run = (): void => {
|
|
117
|
+
running ??= (async () => {
|
|
118
|
+
const [summaries, entries] = await Promise.all([
|
|
119
|
+
deps.registry.list(),
|
|
120
|
+
deps.meta.sessions(),
|
|
121
|
+
]);
|
|
122
|
+
await sweep(deps.root, {
|
|
123
|
+
alive: new Set(summaries.map((summary) => summary.sessionId)),
|
|
124
|
+
archived: new Set(
|
|
125
|
+
[...entries]
|
|
126
|
+
.filter(([, entry]) => entry.archived === true)
|
|
127
|
+
.map(([sessionId]) => sessionId)
|
|
128
|
+
),
|
|
129
|
+
now: Date.now(),
|
|
130
|
+
});
|
|
131
|
+
})()
|
|
132
|
+
.catch((error: unknown) => {
|
|
133
|
+
process.stderr.write(
|
|
134
|
+
`attachment sweep failed: ${error instanceof Error ? error.message : String(error)}\n`
|
|
135
|
+
);
|
|
136
|
+
})
|
|
137
|
+
.finally(() => {
|
|
138
|
+
running = undefined;
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
run();
|
|
142
|
+
const timer = setInterval(run, INTERVAL_MS);
|
|
143
|
+
timer.unref?.();
|
|
144
|
+
return () => {
|
|
145
|
+
clearInterval(timer);
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const AttachmentSweep = {
|
|
150
|
+
BUDGET_BYTES,
|
|
151
|
+
GRACE_MS,
|
|
152
|
+
doomed,
|
|
153
|
+
sweep,
|
|
154
|
+
install,
|
|
155
|
+
};
|
|
@@ -341,6 +341,17 @@ export class SessionHost {
|
|
|
341
341
|
return { cancelled: true, restored };
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
+
/** Messages queued behind the running turn, oldest first. */
|
|
345
|
+
public get queued(): readonly string[] {
|
|
346
|
+
if (!this.cached || !this.cached.isStreaming) {
|
|
347
|
+
return [];
|
|
348
|
+
}
|
|
349
|
+
return [
|
|
350
|
+
...this.cached.getSteeringMessages(),
|
|
351
|
+
...this.cached.getFollowUpMessages(),
|
|
352
|
+
];
|
|
353
|
+
}
|
|
354
|
+
|
|
344
355
|
/** Takes back messages queued behind the running turn. Not queued: the turn holds the queue. */
|
|
345
356
|
public takeBack(): readonly string[] {
|
|
346
357
|
if (!this.cached || !this.cached.isStreaming) {
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
+
import { Format } from "./Format";
|
|
1
2
|
import { FsErrors } from "./FsErrors";
|
|
2
3
|
import { Paths } from "./Paths";
|
|
3
4
|
|
|
4
|
-
const MAX_BYTES = 50 * 1024 * 1024;
|
|
5
|
-
|
|
6
5
|
async function validate(
|
|
7
6
|
rawPath: string,
|
|
8
|
-
cwd: string
|
|
7
|
+
cwd: string,
|
|
8
|
+
maxBytes = Number.POSITIVE_INFINITY
|
|
9
9
|
): Promise<{ readonly path: string; readonly size: number }> {
|
|
10
10
|
const path = Paths.resolve(rawPath, cwd);
|
|
11
11
|
const st = await FsErrors.statOrThrow(path);
|
|
12
12
|
if (!st.isFile()) {
|
|
13
13
|
throw new Error(`${rawPath} is not a regular file.`);
|
|
14
14
|
}
|
|
15
|
-
if (st.size >
|
|
15
|
+
if (st.size > maxBytes) {
|
|
16
16
|
throw new Error(
|
|
17
|
-
`${rawPath} is ${st.size}
|
|
17
|
+
`${rawPath} is ${Format.bytes(st.size)}; max allowed is ${Format.bytes(maxBytes)}.`
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
20
|
return { path, size: st.size };
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export const SendFile = {
|
|
23
|
+
export const SendFile = { validate };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
import { AttachmentStore } from "#core/attachments/AttachmentStore";
|
|
4
|
+
import { AttachmentSweep } from "#core/attachments/AttachmentSweep";
|
|
4
5
|
import type { AgentRuntime } from "#core/session/AgentRuntime";
|
|
6
|
+
import { SessionMeta } from "#core/session/SessionMeta";
|
|
5
7
|
import { SessionRegistry } from "#core/session/SessionRegistry";
|
|
6
8
|
import { Tools } from "#core/shared/Tools";
|
|
7
9
|
import { defaultAttachmentsRoot } from "#server/AttachmentEndpoint";
|
|
@@ -39,12 +41,18 @@ function create(args: ReadonlyArray<string>, runtime: AgentRuntime): Surface {
|
|
|
39
41
|
: { clientDir: options.clientDir }),
|
|
40
42
|
});
|
|
41
43
|
gateway.start();
|
|
44
|
+
const stopSweep = AttachmentSweep.install({
|
|
45
|
+
root: attachmentsRoot,
|
|
46
|
+
registry,
|
|
47
|
+
meta: new SessionMeta(),
|
|
48
|
+
});
|
|
42
49
|
process.stderr.write(
|
|
43
50
|
`pim-server listening on ${gateway.url} (web UI: http://${options.hostname}:${gateway.port})\n`
|
|
44
51
|
);
|
|
45
52
|
|
|
46
53
|
return {
|
|
47
54
|
stop: async () => {
|
|
55
|
+
stopSweep();
|
|
48
56
|
await gateway.stop();
|
|
49
57
|
await registry.disposeAll();
|
|
50
58
|
},
|
|
@@ -32,6 +32,12 @@ export type AttachmentView = {
|
|
|
32
32
|
readonly isImage: boolean;
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
/** What pi holds queued behind the running turn, merged into one message. */
|
|
36
|
+
export type QueuedView = {
|
|
37
|
+
readonly text: string;
|
|
38
|
+
readonly attachments?: readonly AttachmentView[];
|
|
39
|
+
};
|
|
40
|
+
|
|
35
41
|
/** Projected from pi's session JSONL. `seq` is the line ordinal, so each line emits at most one durable event. */
|
|
36
42
|
export type DurableEvent =
|
|
37
43
|
| {
|
|
@@ -249,6 +255,8 @@ export type EphemeralEvent =
|
|
|
249
255
|
readonly behind?: number;
|
|
250
256
|
/** Changes whenever the working copy does, including dirty file content. */
|
|
251
257
|
readonly repoRevision?: string;
|
|
258
|
+
/** Absent when nothing is queued. */
|
|
259
|
+
readonly queued?: QueuedView;
|
|
252
260
|
}
|
|
253
261
|
/** A frame the server could not attribute to any command. */
|
|
254
262
|
| { readonly type: "error"; readonly message: string };
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { basename, dirname, join } from "node:path";
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
Attachments,
|
|
5
|
+
type PromptAttachment,
|
|
6
|
+
} from "#core/attachments/Attachments";
|
|
3
7
|
import {
|
|
4
8
|
AttachmentStore,
|
|
5
9
|
type StoredAttachment,
|
|
6
10
|
} from "#core/attachments/AttachmentStore";
|
|
7
11
|
import { Paths } from "#core/shared/Paths";
|
|
12
|
+
import type { AttachmentView } from "#protocol/ServerEvent";
|
|
8
13
|
import { IMMUTABLE, serveFile } from "./StaticClient";
|
|
9
14
|
|
|
10
15
|
export type AttachmentEndpointDeps = {
|
|
@@ -41,6 +46,14 @@ export function attachmentUrl(path: string): string {
|
|
|
41
46
|
return `${PREFIX}${scope}/${encodeURIComponent(basename(path))}`;
|
|
42
47
|
}
|
|
43
48
|
|
|
49
|
+
export function attachmentView(file: PromptAttachment): AttachmentView {
|
|
50
|
+
return {
|
|
51
|
+
name: Attachments.nameOf(file.path),
|
|
52
|
+
url: attachmentUrl(file.path),
|
|
53
|
+
isImage: file.isImage,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
44
57
|
/** `POST /upload?session=<id>` with a multipart `file` field; `GET /attachment/<session>/<file>` reads it back. */
|
|
45
58
|
export class AttachmentEndpoint {
|
|
46
59
|
private readonly store: AttachmentStore;
|
|
@@ -38,7 +38,8 @@ function build(
|
|
|
38
38
|
...defineTool({
|
|
39
39
|
name: "send_file",
|
|
40
40
|
label: "send_file",
|
|
41
|
-
description:
|
|
41
|
+
description:
|
|
42
|
+
"Send a local file to the user's web browser. Images appear inline. No size limit.",
|
|
42
43
|
parameters: sendFileSchema,
|
|
43
44
|
async execute(_id, params) {
|
|
44
45
|
const { path: rawPath } = params as SendFileInput;
|
|
@@ -4,17 +4,12 @@ import type {
|
|
|
4
4
|
} from "@earendil-works/pi-agent-core";
|
|
5
5
|
import type { FileEntry } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
|
|
7
|
-
import { Attachments } from "#core/attachments/Attachments";
|
|
8
7
|
import { EventLog, type LoggedEntry } from "#core/session/EventLog";
|
|
9
8
|
import { MessageText } from "#core/session/MessageText";
|
|
10
9
|
import { UserPrompt } from "#core/session/UserPrompt";
|
|
11
10
|
import { Tools } from "#core/shared/Tools";
|
|
12
|
-
import type {
|
|
13
|
-
|
|
14
|
-
DurableEvent,
|
|
15
|
-
ToolCallView,
|
|
16
|
-
} from "#protocol/ServerEvent";
|
|
17
|
-
import { attachmentUrl } from "./AttachmentEndpoint";
|
|
11
|
+
import type { DurableEvent, ToolCallView } from "#protocol/ServerEvent";
|
|
12
|
+
import { attachmentView } from "./AttachmentEndpoint";
|
|
18
13
|
|
|
19
14
|
/** Projects pi's session file into durable events: at most one per line, tagged with the line ordinal. */
|
|
20
15
|
export class SessionProjection {
|
|
@@ -85,13 +80,7 @@ export class SessionProjection {
|
|
|
85
80
|
switch (message.role) {
|
|
86
81
|
case "user": {
|
|
87
82
|
const said = UserPrompt.of(message.content);
|
|
88
|
-
const attachments
|
|
89
|
-
(file) => ({
|
|
90
|
-
name: Attachments.nameOf(file.path),
|
|
91
|
-
url: attachmentUrl(file.path),
|
|
92
|
-
isImage: file.isImage,
|
|
93
|
-
})
|
|
94
|
-
);
|
|
83
|
+
const attachments = said.files.map(attachmentView);
|
|
95
84
|
return {
|
|
96
85
|
seq,
|
|
97
86
|
type: "message",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentSessionEvent } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
|
+
import { Attachments } from "#core/attachments/Attachments";
|
|
3
4
|
import { PickerService } from "#core/picker/PickerService";
|
|
4
5
|
import { MessageText } from "#core/session/MessageText";
|
|
5
6
|
import type { LeaseState, SessionHost } from "#core/session/SessionHost";
|
|
@@ -12,9 +13,11 @@ import type { NoticeSeverity, ToolView } from "#core/view/ViewBlock";
|
|
|
12
13
|
import type { Command } from "#protocol/Command";
|
|
13
14
|
import type {
|
|
14
15
|
EphemeralEvent,
|
|
16
|
+
QueuedView,
|
|
15
17
|
ServerEvent,
|
|
16
18
|
StreamEvent,
|
|
17
19
|
} from "#protocol/ServerEvent";
|
|
20
|
+
import { attachmentView } from "./AttachmentEndpoint";
|
|
18
21
|
import { SessionProjection } from "./SessionProjection";
|
|
19
22
|
|
|
20
23
|
export type StreamListener = (event: ServerEvent) => void;
|
|
@@ -69,6 +72,16 @@ const REQUEST_CEILING_MS = 180_000;
|
|
|
69
72
|
/** How long a pending dialog survives its last client detaching (reloads, reconnects). */
|
|
70
73
|
const DETACH_GRACE_MS = 15_000;
|
|
71
74
|
|
|
75
|
+
function queuedView(queued: readonly string[]): QueuedView | undefined {
|
|
76
|
+
if (queued.length === 0) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
const { text, files } = Attachments.parse(queued.join("\n\n"));
|
|
80
|
+
return files.length === 0
|
|
81
|
+
? { text }
|
|
82
|
+
: { text, attachments: files.map(attachmentView) };
|
|
83
|
+
}
|
|
84
|
+
|
|
72
85
|
function sameLease(a: LeaseState, b: LeaseState): boolean {
|
|
73
86
|
return (
|
|
74
87
|
a.writable === b.writable &&
|
|
@@ -452,6 +465,7 @@ export class SessionStream implements SessionUi {
|
|
|
452
465
|
const { branch, dirtyCount, ahead, behind, revision } =
|
|
453
466
|
this.git.stateOf(cwd);
|
|
454
467
|
const modelLabel = this.host.currentModelLabel;
|
|
468
|
+
const queued = queuedView(this.host.queued);
|
|
455
469
|
const turnElapsedMs =
|
|
456
470
|
this.host.status === "idle" || this.turnStartedAt === 0
|
|
457
471
|
? undefined
|
|
@@ -478,6 +492,7 @@ export class SessionStream implements SessionUi {
|
|
|
478
492
|
...(branch === null
|
|
479
493
|
? {}
|
|
480
494
|
: { branch, dirtyCount, ahead, behind, repoRevision: revision }),
|
|
495
|
+
...(queued === undefined ? {} : { queued }),
|
|
481
496
|
};
|
|
482
497
|
}
|
|
483
498
|
|
|
@@ -634,6 +649,9 @@ export class SessionStream implements SessionUi {
|
|
|
634
649
|
case "entry_appended":
|
|
635
650
|
void this.flushDurable();
|
|
636
651
|
return;
|
|
652
|
+
case "queue_update":
|
|
653
|
+
this.emit(this.sessionState());
|
|
654
|
+
return;
|
|
637
655
|
// Don't await: pi appends the entry only after this listener returns.
|
|
638
656
|
case "message_end": {
|
|
639
657
|
if (event.message.role === "assistant") {
|
|
@@ -7,6 +7,7 @@ import type { PimToolDefinition } from "#core/shared/Tools";
|
|
|
7
7
|
import type { ToolView } from "#core/view/ViewBlock";
|
|
8
8
|
import {
|
|
9
9
|
MAX_CAPTION_CHARS,
|
|
10
|
+
MAX_DOCUMENT_BYTES,
|
|
10
11
|
sendFileSchema,
|
|
11
12
|
type SendFileInput,
|
|
12
13
|
} from "./SendFileSchema";
|
|
@@ -23,11 +24,15 @@ function build(deps: SendFileDeps): PimToolDefinition<typeof sendFileSchema> {
|
|
|
23
24
|
...defineTool({
|
|
24
25
|
name: "send_file",
|
|
25
26
|
label: "send_file",
|
|
26
|
-
description: `Send a local file to the current Telegram chat/thread as a document. Max ${
|
|
27
|
+
description: `Send a local file to the current Telegram chat/thread as a document. Max ${MAX_DOCUMENT_BYTES / (1024 * 1024)} MB.`,
|
|
27
28
|
parameters: sendFileSchema,
|
|
28
29
|
async execute(_id, params) {
|
|
29
30
|
const { path: rawPath, caption } = params as SendFileInput;
|
|
30
|
-
const resolved = await SendFile.validate(
|
|
31
|
+
const resolved = await SendFile.validate(
|
|
32
|
+
rawPath,
|
|
33
|
+
deps.cwd,
|
|
34
|
+
MAX_DOCUMENT_BYTES
|
|
35
|
+
);
|
|
31
36
|
const trimmedCaption = caption?.slice(0, MAX_CAPTION_CHARS);
|
|
32
37
|
await send(deps.api, deps.sessionId, resolved.path, trimmedCaption);
|
|
33
38
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{m as e,t}from"./mermaid-parser.core-
|
|
1
|
+
import{m as e,t}from"./mermaid-parser.core-CDKMCqjM.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-BIB5suEH.js";import"./chunk-O7XYJQB3-CYYv2jJO.js";import{n as i,r as a,t as o}from"./chunk-GTNCS2PH-Bg1KOR7I.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-BjpOh806.js";var c=e().RailroadAbnf.parser.LangiumParser,l=n(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=n(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=n(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=n(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=n(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=n(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=n(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:n(e=>{o.clear(),r.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new t(n);let i=n.value;r.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),r.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e,t}from"./path-fybaL0A-.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-
|
|
1
|
+
import{n as e,t}from"./path-fybaL0A-.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-CNCbCG-8.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i){if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./chunk-FOHPRMQF-CoA4358r.js";import{M as e}from"./mermaid-parser.core-CDKMCqjM.js";export{e as createArchitectureServices};
|