@agentproto/runtime 5.10.0 → 5.11.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.
@@ -0,0 +1,281 @@
1
+ import { execFile } from 'child_process';
2
+ import { randomUUID } from 'crypto';
3
+ import { mkdir, realpath, stat, copyFile, open, mkdtemp, rm, rename } from 'fs/promises';
4
+ import { tmpdir } from 'os';
5
+ import { join, sep, resolve, relative, isAbsolute, dirname } from 'path';
6
+ import { fileURLToPath, pathToFileURL } from 'url';
7
+ import { z } from 'zod';
8
+ import { unpackApp, safeId } from '@agentproto/app-kit';
9
+
10
+ /**
11
+ * @agentproto/runtime v0.1.0-alpha
12
+ * Long-running gateway: MCP server + HTTP transport + HEARTBEAT autonomy + conversation persistence over a workspace dir.
13
+ */
14
+
15
+ var DOWNLOAD_TIMEOUT_MS = 3e4;
16
+ var DOWNLOAD_MAX_BYTES = 200 * 1024 * 1024;
17
+ var GIT_TIMEOUT_MS = 5 * 6e4;
18
+ var dataDirField = z.string().optional();
19
+ var appInstallInputSchema = z.union([
20
+ z.object({ dir: z.string(), dataDir: dataDirField }).strict(),
21
+ z.object({
22
+ url: z.string(),
23
+ ref: z.string().optional(),
24
+ subdir: z.string().optional(),
25
+ sha: z.string().optional(),
26
+ sha256: z.string().optional(),
27
+ allowBuild: z.boolean().optional(),
28
+ catalogUrl: z.string().optional(),
29
+ dataDir: dataDirField
30
+ }).strict(),
31
+ z.object({ file: z.string(), sha256: z.string().optional(), catalogUrl: z.string().optional(), dataDir: dataDirField }).strict()
32
+ ]);
33
+ var APP_INSTALL_EXCLUSIVE_ERROR = "pass exactly one source: {dir} (local app dir), {url, ref?, subdir?, sha?, allowBuild?} (git repo), {url, sha256?} (a .agentapp URL), or {file, sha256?} (local .agentapp path); `dataDir` may accompany any of them, `catalogUrl` any remote one.";
34
+ function isAgentappUrl(url) {
35
+ return /\.agentapp$/i.test(url.split(/[?#]/)[0]);
36
+ }
37
+ function tmpSibling(appsDir, slug) {
38
+ return join(appsDir, `.tmp-${slug}-${randomUUID().slice(0, 8)}`);
39
+ }
40
+ function sanitizeSlug(raw) {
41
+ const s = raw.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[.-]+/, "").replace(/-+$/, "");
42
+ return s === "" ? "app" : s;
43
+ }
44
+ function gitSlug(url, subdir) {
45
+ const tail = url.replace(/[?#].*$/, "").replace(/[\\/]+$/, "").split(/[\\/:]/).pop() ?? "";
46
+ const repo = sanitizeSlug(tail.replace(/\.git$/i, ""));
47
+ if (subdir === void 0 || subdir === "") return repo;
48
+ return sanitizeSlug(`${repo}-${subdir.split(/[\\/]+/).filter(Boolean).join("-")}`);
49
+ }
50
+ function assertGitUrl(url) {
51
+ if (url.trim() === "" || url.startsWith("-") || url.includes("::")) {
52
+ throw new Error(`"${url}" is not a usable git URL.`);
53
+ }
54
+ }
55
+ function git(args, cwd) {
56
+ return new Promise((done, fail) => {
57
+ execFile(
58
+ "git",
59
+ [...args],
60
+ {
61
+ ...cwd !== void 0 ? { cwd } : {},
62
+ timeout: GIT_TIMEOUT_MS,
63
+ maxBuffer: 16 * 1024 * 1024,
64
+ env: {
65
+ ...process.env,
66
+ GIT_TERMINAL_PROMPT: "0",
67
+ GIT_ALLOW_PROTOCOL: "file:git:http:https:ssh"
68
+ }
69
+ },
70
+ (err, stdout, stderr) => {
71
+ if (err) {
72
+ const detail = String(stderr).trim() || err.message;
73
+ fail(new Error(`git ${args[0]} failed: ${detail}`));
74
+ } else done(String(stdout));
75
+ }
76
+ );
77
+ });
78
+ }
79
+ function normalizeSubdir(subdir) {
80
+ if (subdir === void 0) return "";
81
+ const trimmed = subdir.trim();
82
+ if (trimmed === "" || trimmed === ".") return "";
83
+ if (isAbsolute(trimmed) || trimmed.split(/[\\/]/).includes("..")) {
84
+ throw new Error(`subdir "${subdir}" must be a relative path inside the repo.`);
85
+ }
86
+ return trimmed.replace(/^\.[\\/]/, "").replace(/[\\/]+$/, "");
87
+ }
88
+ async function stageGitApp(input) {
89
+ assertGitUrl(input.url);
90
+ const subdir = normalizeSubdir(input.subdir);
91
+ const slug = gitSlug(input.url, subdir);
92
+ await mkdir(input.appsDir, { recursive: true });
93
+ const tmpDir = tmpSibling(input.appsDir, slug);
94
+ const discard = () => rm(tmpDir, { recursive: true, force: true });
95
+ try {
96
+ await git([
97
+ "clone",
98
+ "--depth",
99
+ "1",
100
+ ...input.ref !== void 0 ? [`--branch=${input.ref}`] : [],
101
+ "--",
102
+ input.url,
103
+ tmpDir
104
+ ]);
105
+ const sha = (await git(["rev-parse", "HEAD"], tmpDir)).trim();
106
+ if (input.expectedSha !== void 0 && sha.toLowerCase() !== input.expectedSha.trim().toLowerCase()) {
107
+ throw new Error(
108
+ `git commit mismatch for ${input.url}: expected ${input.expectedSha.trim()}, got ${sha}. Nothing was installed.`
109
+ );
110
+ }
111
+ if (subdir !== "") {
112
+ let real;
113
+ try {
114
+ real = await realpath(join(tmpDir, subdir));
115
+ } catch {
116
+ throw new Error(`subdir "${subdir}" does not exist in ${input.url}.`);
117
+ }
118
+ const root = await realpath(tmpDir);
119
+ if (real !== root && !real.startsWith(root + sep)) {
120
+ throw new Error(`subdir "${subdir}" resolves outside the repo.`);
121
+ }
122
+ if (!(await stat(real)).isDirectory()) throw new Error(`subdir "${subdir}" is not a directory.`);
123
+ }
124
+ return {
125
+ slug,
126
+ tmpDir,
127
+ subdir,
128
+ source: {
129
+ kind: "git",
130
+ url: input.url,
131
+ ...input.ref !== void 0 ? { ref: input.ref } : {},
132
+ sha,
133
+ ...subdir !== "" ? { subdir } : {}
134
+ },
135
+ discard
136
+ };
137
+ } catch (err) {
138
+ await discard();
139
+ throw err;
140
+ }
141
+ }
142
+ async function downloadTo(url, destFile) {
143
+ if (url.startsWith("file:")) {
144
+ const src = fileURLToPath(url);
145
+ if ((await stat(src)).size > DOWNLOAD_MAX_BYTES) {
146
+ throw new Error(`${url} exceeds the ${DOWNLOAD_MAX_BYTES / 1024 / 1024} MB cap.`);
147
+ }
148
+ await copyFile(src, destFile);
149
+ return;
150
+ }
151
+ const res = await fetch(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS), redirect: "follow" });
152
+ if (!res.ok || res.body === null) throw new Error(`download of ${url} failed: HTTP ${res.status}`);
153
+ const declared = Number(res.headers.get("content-length") ?? "0");
154
+ if (declared > DOWNLOAD_MAX_BYTES) {
155
+ throw new Error(`${url} is ${declared} bytes, over the ${DOWNLOAD_MAX_BYTES / 1024 / 1024} MB cap.`);
156
+ }
157
+ const out = await open(destFile, "w");
158
+ try {
159
+ let total = 0;
160
+ const reader = res.body.getReader();
161
+ for (; ; ) {
162
+ const { done, value } = await reader.read();
163
+ if (done) break;
164
+ total += value.byteLength;
165
+ if (total > DOWNLOAD_MAX_BYTES) {
166
+ await reader.cancel();
167
+ throw new Error(`${url} exceeds the ${DOWNLOAD_MAX_BYTES / 1024 / 1024} MB cap.`);
168
+ }
169
+ await out.write(value);
170
+ }
171
+ } finally {
172
+ await out.close();
173
+ }
174
+ }
175
+ async function stageAgentApp(input) {
176
+ await mkdir(input.appsDir, { recursive: true });
177
+ const scratch = await mkdtemp(join(tmpdir(), "agentapp-dl-"));
178
+ const tmpDir = join(input.appsDir, `.tmp-agentapp-${randomUUID().slice(0, 8)}`);
179
+ const discard = () => rm(tmpDir, { recursive: true, force: true });
180
+ try {
181
+ let bundle;
182
+ let url;
183
+ if (input.file !== void 0) {
184
+ bundle = resolve(input.file);
185
+ url = pathToFileURL(bundle).href;
186
+ } else if (input.url !== void 0) {
187
+ url = input.url;
188
+ if (!/^(https?|file):\/\//i.test(url)) {
189
+ throw new Error(`.agentapp url must be https://, http:// or file:// (got "${url}").`);
190
+ }
191
+ bundle = join(scratch, "download.agentapp");
192
+ await downloadTo(url, bundle);
193
+ } else {
194
+ throw new Error("stageAgentApp needs a url or a file.");
195
+ }
196
+ const { manifest } = await unpackApp({ file: bundle, dest: tmpDir });
197
+ if (input.expectedSha256 !== void 0 && manifest.sha256.toLowerCase() !== input.expectedSha256.trim().toLowerCase()) {
198
+ throw new Error(
199
+ `.agentapp digest mismatch for ${url}: expected sha256 ${input.expectedSha256.trim()}, bundle declares ${manifest.sha256}. Nothing was installed.`
200
+ );
201
+ }
202
+ return {
203
+ slug: sanitizeSlug(safeId(manifest.id)),
204
+ tmpDir,
205
+ subdir: "",
206
+ source: { kind: "agentapp", url, sha256: manifest.sha256, version: manifest.version },
207
+ discard
208
+ };
209
+ } catch (err) {
210
+ await discard();
211
+ throw err;
212
+ } finally {
213
+ await rm(scratch, { recursive: true, force: true });
214
+ }
215
+ }
216
+ async function swapInStaged(input) {
217
+ const { tmpDir, target } = input;
218
+ let backup;
219
+ try {
220
+ await stat(target);
221
+ backup = `${target}.old-${randomUUID().slice(0, 8)}`;
222
+ } catch {
223
+ }
224
+ if (backup !== void 0) await rename(target, backup);
225
+ try {
226
+ await rename(tmpDir, target);
227
+ } catch (err) {
228
+ if (backup !== void 0) await rename(backup, target);
229
+ throw err;
230
+ }
231
+ let carriedRel;
232
+ if (backup !== void 0 && input.previousDataDir !== void 0) {
233
+ const rel = relative(target, input.previousDataDir);
234
+ if (rel !== "" && !rel.startsWith("..") && !isAbsolute(rel)) {
235
+ const from = join(backup, rel);
236
+ try {
237
+ await stat(from);
238
+ await rm(join(target, rel), { recursive: true, force: true });
239
+ await mkdir(dirname(join(target, rel)), { recursive: true });
240
+ await rename(from, join(target, rel));
241
+ carriedRel = rel;
242
+ } catch {
243
+ }
244
+ }
245
+ }
246
+ return {
247
+ async commit() {
248
+ if (backup !== void 0) await rm(backup, { recursive: true, force: true });
249
+ },
250
+ async rollback() {
251
+ if (carriedRel !== void 0 && backup !== void 0) {
252
+ await mkdir(dirname(join(backup, carriedRel)), { recursive: true });
253
+ await rename(join(target, carriedRel), join(backup, carriedRel));
254
+ }
255
+ await rm(target, { recursive: true, force: true });
256
+ if (backup !== void 0) await rename(backup, target);
257
+ }
258
+ };
259
+ }
260
+ async function remoteGitSha(url, ref) {
261
+ assertGitUrl(url);
262
+ if (ref === void 0) {
263
+ const line = (await git(["ls-remote", "--", url, "HEAD"])).split("\n")[0] ?? "";
264
+ const sha2 = line.split(/\s+/)[0];
265
+ if (!sha2) throw new Error(`${url} has no HEAD.`);
266
+ return sha2;
267
+ }
268
+ const out = await git(["ls-remote", "--", url, `refs/heads/${ref}`, `refs/tags/${ref}`, `refs/tags/${ref}^{}`]);
269
+ const byRef = /* @__PURE__ */ new Map();
270
+ for (const line of out.split("\n")) {
271
+ const [sha2, name] = line.split(/\s+/);
272
+ if (sha2 && name) byRef.set(name, sha2);
273
+ }
274
+ const sha = byRef.get(`refs/heads/${ref}`) ?? byRef.get(`refs/tags/${ref}^{}`) ?? byRef.get(`refs/tags/${ref}`);
275
+ if (!sha) throw new Error(`ref "${ref}" not found in ${url}.`);
276
+ return sha;
277
+ }
278
+
279
+ export { APP_INSTALL_EXCLUSIVE_ERROR, DOWNLOAD_MAX_BYTES, DOWNLOAD_TIMEOUT_MS, appInstallInputSchema, downloadTo, gitSlug, isAgentappUrl, remoteGitSha, stageAgentApp, stageGitApp, swapInStaged };
280
+ //# sourceMappingURL=app-remote-install.mjs.map
281
+ //# sourceMappingURL=app-remote-install.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/app-remote-install.ts"],"names":["sha"],"mappings":";;;;;;;;;;;;;;AAsBO,IAAM,mBAAA,GAAsB;AAC5B,IAAM,kBAAA,GAAqB,MAAM,IAAA,GAAO;AAC/C,IAAM,iBAAiB,CAAA,GAAI,GAAA;AAI3B,IAAM,YAAA,GAAe,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAGlC,IAAM,qBAAA,GAAwB,EAAE,KAAA,CAAM;AAAA,EAC3C,CAAA,CAAE,MAAA,CAAO,EAAE,GAAA,EAAK,CAAA,CAAE,MAAA,EAAO,EAAG,OAAA,EAAS,YAAA,EAAc,CAAA,CAAE,MAAA,EAAO;AAAA,EAC5D,EACG,MAAA,CAAO;AAAA,IACN,GAAA,EAAK,EAAE,MAAA,EAAO;AAAA,IACd,GAAA,EAAK,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,IACzB,MAAA,EAAQ,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,IAC5B,GAAA,EAAK,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,IACzB,MAAA,EAAQ,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,IAC5B,UAAA,EAAY,CAAA,CAAE,OAAA,EAAQ,CAAE,QAAA,EAAS;AAAA,IACjC,UAAA,EAAY,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,IAChC,OAAA,EAAS;AAAA,GACV,EACA,MAAA,EAAO;AAAA,EACV,CAAA,CAAE,OAAO,EAAE,IAAA,EAAM,EAAE,MAAA,EAAO,EAAG,MAAA,EAAQ,CAAA,CAAE,MAAA,EAAO,CAAE,UAAS,EAAG,UAAA,EAAY,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,IAAY,OAAA,EAAS,YAAA,EAAc,CAAA,CAAE,MAAA;AAC1H,CAAC;AAGM,IAAM,2BAAA,GACX;AAIK,SAAS,cAAc,GAAA,EAAsB;AAClD,EAAA,OAAO,eAAe,IAAA,CAAK,GAAA,CAAI,MAAM,MAAM,CAAA,CAAE,CAAC,CAAE,CAAA;AAClD;AAgBA,SAAS,UAAA,CAAW,SAAiB,IAAA,EAAsB;AACzD,EAAA,OAAO,IAAA,CAAK,OAAA,EAAS,CAAA,KAAA,EAAQ,IAAI,CAAA,CAAA,EAAI,UAAA,EAAW,CAAE,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAE,CAAA;AACjE;AAEA,SAAS,aAAa,GAAA,EAAqB;AACzC,EAAA,MAAM,CAAA,GAAI,GAAA,CAAI,OAAA,CAAQ,mBAAA,EAAqB,GAAG,CAAA,CAAE,OAAA,CAAQ,QAAA,EAAU,EAAE,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AACvF,EAAA,OAAO,CAAA,KAAM,KAAK,KAAA,GAAQ,CAAA;AAC5B;AAGO,SAAS,OAAA,CAAQ,KAAa,MAAA,EAAyB;AAC5D,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAA,CAAE,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAA,CAAE,KAAA,CAAM,QAAQ,CAAA,CAAE,KAAI,IAAK,EAAA;AACxF,EAAA,MAAM,OAAO,YAAA,CAAa,IAAA,CAAK,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAC,CAAA;AACrD,EAAA,IAAI,MAAA,KAAW,MAAA,IAAa,MAAA,KAAW,EAAA,EAAI,OAAO,IAAA;AAClD,EAAA,OAAO,YAAA,CAAa,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,OAAO,KAAA,CAAM,QAAQ,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG,CAAC,CAAA,CAAE,CAAA;AACnF;AAEA,SAAS,aAAa,GAAA,EAAmB;AACvC,EAAA,IAAI,GAAA,CAAI,IAAA,EAAK,KAAM,EAAA,IAAM,GAAA,CAAI,UAAA,CAAW,GAAG,CAAA,IAAK,GAAA,CAAI,QAAA,CAAS,IAAI,CAAA,EAAG;AAClE,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,CAAA,EAAI,GAAG,CAAA,0BAAA,CAA4B,CAAA;AAAA,EACrD;AACF;AAEA,SAAS,GAAA,CAAI,MAAyB,GAAA,EAA+B;AACnE,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,IAAA,EAAM,IAAA,KAAS;AACjC,IAAA,QAAA;AAAA,MACE,KAAA;AAAA,MACA,CAAC,GAAG,IAAI,CAAA;AAAA,MACR;AAAA,QACE,GAAI,GAAA,KAAQ,MAAA,GAAY,EAAE,GAAA,KAAQ,EAAC;AAAA,QACnC,OAAA,EAAS,cAAA;AAAA,QACT,SAAA,EAAW,KAAK,IAAA,GAAO,IAAA;AAAA,QACvB,GAAA,EAAK;AAAA,UACH,GAAG,OAAA,CAAQ,GAAA;AAAA,UACX,mBAAA,EAAqB,GAAA;AAAA,UACrB,kBAAA,EAAoB;AAAA;AACtB,OACF;AAAA,MACA,CAAC,GAAA,EAAK,MAAA,EAAQ,MAAA,KAAW;AACvB,QAAA,IAAI,GAAA,EAAK;AACP,UAAA,MAAM,SAAS,MAAA,CAAO,MAAM,CAAA,CAAE,IAAA,MAAU,GAAA,CAAI,OAAA;AAC5C,UAAA,IAAA,CAAK,IAAI,MAAM,CAAA,IAAA,EAAO,IAAA,CAAK,CAAC,CAAC,CAAA,SAAA,EAAY,MAAM,CAAA,CAAE,CAAC,CAAA;AAAA,QACpD,CAAA,MAAO,IAAA,CAAK,MAAA,CAAO,MAAM,CAAC,CAAA;AAAA,MAC5B;AAAA,KACF;AAAA,EACF,CAAC,CAAA;AACH;AAEA,SAAS,gBAAgB,MAAA,EAAoC;AAC3D,EAAA,IAAI,MAAA,KAAW,QAAW,OAAO,EAAA;AACjC,EAAA,MAAM,OAAA,GAAU,OAAO,IAAA,EAAK;AAC5B,EAAA,IAAI,OAAA,KAAY,EAAA,IAAM,OAAA,KAAY,GAAA,EAAK,OAAO,EAAA;AAC9C,EAAA,IAAI,UAAA,CAAW,OAAO,CAAA,IAAK,OAAA,CAAQ,MAAM,OAAO,CAAA,CAAE,QAAA,CAAS,IAAI,CAAA,EAAG;AAChE,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,QAAA,EAAW,MAAM,CAAA,0CAAA,CAA4C,CAAA;AAAA,EAC/E;AACA,EAAA,OAAO,QAAQ,OAAA,CAAQ,UAAA,EAAY,EAAE,CAAA,CAAE,OAAA,CAAQ,WAAW,EAAE,CAAA;AAC9D;AAGA,eAAsB,YAAY,KAAA,EAMX;AACrB,EAAA,YAAA,CAAa,MAAM,GAAG,CAAA;AACtB,EAAA,MAAM,MAAA,GAAS,eAAA,CAAgB,KAAA,CAAM,MAAM,CAAA;AAC3C,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,KAAA,CAAM,GAAA,EAAK,MAAM,CAAA;AACtC,EAAA,MAAM,MAAM,KAAA,CAAM,OAAA,EAAS,EAAE,SAAA,EAAW,MAAM,CAAA;AAC9C,EAAA,MAAM,MAAA,GAAS,UAAA,CAAW,KAAA,CAAM,OAAA,EAAS,IAAI,CAAA;AAC7C,EAAA,MAAM,OAAA,GAAU,MAAqB,EAAA,CAAG,MAAA,EAAQ,EAAE,SAAA,EAAW,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,CAAA;AAChF,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,CAAI;AAAA,MACR,OAAA;AAAA,MACA,SAAA;AAAA,MACA,GAAA;AAAA,MACA,GAAI,KAAA,CAAM,GAAA,KAAQ,KAAA,CAAA,GAAY,CAAC,YAAY,KAAA,CAAM,GAAG,CAAA,CAAE,CAAA,GAAI,EAAC;AAAA,MAC3D,IAAA;AAAA,MACA,KAAA,CAAM,GAAA;AAAA,MACN;AAAA,KACD,CAAA;AACD,IAAA,MAAM,GAAA,GAAA,CAAO,MAAM,GAAA,CAAI,CAAC,aAAa,MAAM,CAAA,EAAG,MAAM,CAAA,EAAG,IAAA,EAAK;AAC5D,IAAA,IAAI,KAAA,CAAM,WAAA,KAAgB,KAAA,CAAA,IAAa,GAAA,CAAI,WAAA,EAAY,KAAM,KAAA,CAAM,WAAA,CAAY,IAAA,EAAK,CAAE,WAAA,EAAY,EAAG;AACnG,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,wBAAA,EAA2B,MAAM,GAAG,CAAA,WAAA,EAAc,MAAM,WAAA,CAAY,IAAA,EAAM,CAAA,MAAA,EAAS,GAAG,CAAA,wBAAA;AAAA,OACxF;AAAA,IACF;AACA,IAAA,IAAI,WAAW,EAAA,EAAI;AACjB,MAAA,IAAI,IAAA;AACJ,MAAA,IAAI;AACF,QAAA,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,CAAK,MAAA,EAAQ,MAAM,CAAC,CAAA;AAAA,MAC5C,CAAA,CAAA,MAAQ;AACN,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,QAAA,EAAW,MAAM,CAAA,oBAAA,EAAuB,KAAA,CAAM,GAAG,CAAA,CAAA,CAAG,CAAA;AAAA,MACtE;AACA,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,MAAM,CAAA;AAClC,MAAA,IAAI,SAAS,IAAA,IAAQ,CAAC,KAAK,UAAA,CAAW,IAAA,GAAO,GAAG,CAAA,EAAG;AACjD,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,QAAA,EAAW,MAAM,CAAA,4BAAA,CAA8B,CAAA;AAAA,MACjE;AACA,MAAA,IAAI,CAAA,CAAE,MAAM,IAAA,CAAK,IAAI,CAAA,EAAG,WAAA,EAAY,EAAG,MAAM,IAAI,KAAA,CAAM,CAAA,QAAA,EAAW,MAAM,CAAA,qBAAA,CAAuB,CAAA;AAAA,IACjG;AACA,IAAA,OAAO;AAAA,MACL,IAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA,EAAQ;AAAA,QACN,IAAA,EAAM,KAAA;AAAA,QACN,KAAK,KAAA,CAAM,GAAA;AAAA,QACX,GAAI,MAAM,GAAA,KAAQ,KAAA,CAAA,GAAY,EAAE,GAAA,EAAK,KAAA,CAAM,GAAA,EAAI,GAAI,EAAC;AAAA,QACpD,GAAA;AAAA,QACA,GAAI,MAAA,KAAW,EAAA,GAAK,EAAE,MAAA,KAAW;AAAC,OACpC;AAAA,MACA;AAAA,KACF;AAAA,EACF,SAAS,GAAA,EAAK;AACZ,IAAA,MAAM,OAAA,EAAQ;AACd,IAAA,MAAM,GAAA;AAAA,EACR;AACF;AAKA,eAAsB,UAAA,CAAW,KAAa,QAAA,EAAiC;AAC7E,EAAA,IAAI,GAAA,CAAI,UAAA,CAAW,OAAO,CAAA,EAAG;AAC3B,IAAA,MAAM,GAAA,GAAM,cAAc,GAAG,CAAA;AAC7B,IAAA,IAAA,CAAK,MAAM,IAAA,CAAK,GAAG,CAAA,EAAG,OAAO,kBAAA,EAAoB;AAC/C,MAAA,MAAM,IAAI,MAAM,CAAA,EAAG,GAAG,gBAAgB,kBAAA,GAAqB,IAAA,GAAO,IAAI,CAAA,QAAA,CAAU,CAAA;AAAA,IAClF;AACA,IAAA,MAAM,QAAA,CAAS,KAAK,QAAQ,CAAA;AAC5B,IAAA;AAAA,EACF;AACA,EAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK,EAAE,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,mBAAmB,CAAA,EAAG,QAAA,EAAU,QAAA,EAAU,CAAA;AACrG,EAAA,IAAI,CAAC,GAAA,CAAI,EAAA,IAAM,GAAA,CAAI,SAAS,IAAA,EAAM,MAAM,IAAI,KAAA,CAAM,CAAA,YAAA,EAAe,GAAG,CAAA,cAAA,EAAiB,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AACjG,EAAA,MAAM,WAAW,MAAA,CAAO,GAAA,CAAI,QAAQ,GAAA,CAAI,gBAAgB,KAAK,GAAG,CAAA;AAChE,EAAA,IAAI,WAAW,kBAAA,EAAoB;AACjC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA,IAAA,EAAO,QAAQ,CAAA,iBAAA,EAAoB,kBAAA,GAAqB,IAAA,GAAO,IAAI,CAAA,QAAA,CAAU,CAAA;AAAA,EACrG;AACA,EAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,QAAA,EAAU,GAAG,CAAA;AACpC,EAAA,IAAI;AACF,IAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,IAAA,MAAM,MAAA,GAAS,GAAA,CAAI,IAAA,CAAK,SAAA,EAAU;AAClC,IAAA,WAAS;AACP,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,OAAO,IAAA,EAAK;AAC1C,MAAA,IAAI,IAAA,EAAM;AACV,MAAA,KAAA,IAAS,KAAA,CAAM,UAAA;AACf,MAAA,IAAI,QAAQ,kBAAA,EAAoB;AAC9B,QAAA,MAAM,OAAO,MAAA,EAAO;AACpB,QAAA,MAAM,IAAI,MAAM,CAAA,EAAG,GAAG,gBAAgB,kBAAA,GAAqB,IAAA,GAAO,IAAI,CAAA,QAAA,CAAU,CAAA;AAAA,MAClF;AACA,MAAA,MAAM,GAAA,CAAI,MAAM,KAAK,CAAA;AAAA,IACvB;AAAA,EACF,CAAA,SAAE;AACA,IAAA,MAAM,IAAI,KAAA,EAAM;AAAA,EAClB;AACF;AAOA,eAAsB,cAAc,KAAA,EAKb;AACrB,EAAA,MAAM,MAAM,KAAA,CAAM,OAAA,EAAS,EAAE,SAAA,EAAW,MAAM,CAAA;AAC9C,EAAA,MAAM,UAAU,MAAM,OAAA,CAAQ,KAAK,MAAA,EAAO,EAAG,cAAc,CAAC,CAAA;AAC5D,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,OAAA,EAAS,CAAA,cAAA,EAAiB,UAAA,EAAW,CAAE,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAE,CAAA;AAC9E,EAAA,MAAM,OAAA,GAAU,MAAqB,EAAA,CAAG,MAAA,EAAQ,EAAE,SAAA,EAAW,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,CAAA;AAChF,EAAA,IAAI;AACF,IAAA,IAAI,MAAA;AACJ,IAAA,IAAI,GAAA;AACJ,IAAA,IAAI,KAAA,CAAM,SAAS,KAAA,CAAA,EAAW;AAC5B,MAAA,MAAA,GAAS,OAAA,CAAQ,MAAM,IAAI,CAAA;AAC3B,MAAA,GAAA,GAAM,aAAA,CAAc,MAAM,CAAA,CAAE,IAAA;AAAA,IAC9B,CAAA,MAAA,IAAW,KAAA,CAAM,GAAA,KAAQ,KAAA,CAAA,EAAW;AAClC,MAAA,GAAA,GAAM,KAAA,CAAM,GAAA;AACZ,MAAA,IAAI,CAAC,sBAAA,CAAuB,IAAA,CAAK,GAAG,CAAA,EAAG;AACrC,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,yDAAA,EAA4D,GAAG,CAAA,GAAA,CAAK,CAAA;AAAA,MACtF;AACA,MAAA,MAAA,GAAS,IAAA,CAAK,SAAS,mBAAmB,CAAA;AAC1C,MAAA,MAAM,UAAA,CAAW,KAAK,MAAM,CAAA;AAAA,IAC9B,CAAA,MAAO;AACL,MAAA,MAAM,IAAI,MAAM,sCAAsC,CAAA;AAAA,IACxD;AACA,IAAA,MAAM,EAAE,QAAA,EAAS,GAAI,MAAM,SAAA,CAAU,EAAE,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,MAAA,EAAQ,CAAA;AACnE,IAAA,IACE,KAAA,CAAM,cAAA,KAAmB,KAAA,CAAA,IACzB,QAAA,CAAS,MAAA,CAAO,WAAA,EAAY,KAAM,KAAA,CAAM,cAAA,CAAe,IAAA,EAAK,CAAE,WAAA,EAAY,EAC1E;AACA,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,8BAAA,EAAiC,GAAG,CAAA,kBAAA,EAAqB,KAAA,CAAM,eAAe,IAAA,EAAM,CAAA,kBAAA,EAC/D,QAAA,CAAS,MAAM,CAAA,wBAAA;AAAA,OACtC;AAAA,IACF;AACA,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,YAAA,CAAa,MAAA,CAAO,QAAA,CAAS,EAAE,CAAC,CAAA;AAAA,MACtC,MAAA;AAAA,MACA,MAAA,EAAQ,EAAA;AAAA,MACR,MAAA,EAAQ,EAAE,IAAA,EAAM,UAAA,EAAY,GAAA,EAAK,QAAQ,QAAA,CAAS,MAAA,EAAQ,OAAA,EAAS,QAAA,CAAS,OAAA,EAAQ;AAAA,MACpF;AAAA,KACF;AAAA,EACF,SAAS,GAAA,EAAK;AACZ,IAAA,MAAM,OAAA,EAAQ;AACd,IAAA,MAAM,GAAA;AAAA,EACR,CAAA,SAAE;AACA,IAAA,MAAM,GAAG,OAAA,EAAS,EAAE,WAAW,IAAA,EAAM,KAAA,EAAO,MAAM,CAAA;AAAA,EACpD;AACF;AAiBA,eAAsB,aAAa,KAAA,EAId;AACnB,EAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAO,GAAI,KAAA;AAC3B,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAM,KAAK,MAAM,CAAA;AACjB,IAAA,MAAA,GAAS,CAAA,EAAG,MAAM,CAAA,KAAA,EAAQ,UAAA,GAAa,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA;AAAA,EACpD,CAAA,CAAA,MAAQ;AAAA,EAER;AACA,EAAA,IAAI,MAAA,KAAW,MAAA,EAAW,MAAM,MAAA,CAAO,QAAQ,MAAM,CAAA;AACrD,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,CAAO,QAAQ,MAAM,CAAA;AAAA,EAC7B,SAAS,GAAA,EAAK;AACZ,IAAA,IAAI,MAAA,KAAW,MAAA,EAAW,MAAM,MAAA,CAAO,QAAQ,MAAM,CAAA;AACrD,IAAA,MAAM,GAAA;AAAA,EACR;AAEA,EAAA,IAAI,UAAA;AACJ,EAAA,IAAI,MAAA,KAAW,MAAA,IAAa,KAAA,CAAM,eAAA,KAAoB,MAAA,EAAW;AAC/D,IAAA,MAAM,GAAA,GAAM,QAAA,CAAS,MAAA,EAAQ,KAAA,CAAM,eAAe,CAAA;AAClD,IAAA,IAAI,GAAA,KAAQ,EAAA,IAAM,CAAC,GAAA,CAAI,UAAA,CAAW,IAAI,CAAA,IAAK,CAAC,UAAA,CAAW,GAAG,CAAA,EAAG;AAC3D,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,MAAA,EAAQ,GAAG,CAAA;AAC7B,MAAA,IAAI;AACF,QAAA,MAAM,KAAK,IAAI,CAAA;AACf,QAAA,MAAM,EAAA,CAAG,IAAA,CAAK,MAAA,EAAQ,GAAG,CAAA,EAAG,EAAE,SAAA,EAAW,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,CAAA;AAC5D,QAAA,MAAM,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,GAAG,CAAC,CAAA,EAAG,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AAC3D,QAAA,MAAM,MAAA,CAAO,IAAA,EAAM,IAAA,CAAK,MAAA,EAAQ,GAAG,CAAC,CAAA;AACpC,QAAA,UAAA,GAAa,GAAA;AAAA,MACf,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,MAAM,MAAA,GAAS;AACb,MAAA,IAAI,MAAA,KAAW,MAAA,EAAW,MAAM,EAAA,CAAG,MAAA,EAAQ,EAAE,SAAA,EAAW,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,CAAA;AAAA,IAC7E,CAAA;AAAA,IACA,MAAM,QAAA,GAAW;AACf,MAAA,IAAI,UAAA,KAAe,MAAA,IAAa,MAAA,KAAW,MAAA,EAAW;AACpD,QAAA,MAAM,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,UAAU,CAAC,CAAA,EAAG,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AAClE,QAAA,MAAM,MAAA,CAAO,KAAK,MAAA,EAAQ,UAAU,GAAG,IAAA,CAAK,MAAA,EAAQ,UAAU,CAAC,CAAA;AAAA,MACjE;AACA,MAAA,MAAM,GAAG,MAAA,EAAQ,EAAE,WAAW,IAAA,EAAM,KAAA,EAAO,MAAM,CAAA;AACjD,MAAA,IAAI,MAAA,KAAW,MAAA,EAAW,MAAM,MAAA,CAAO,QAAQ,MAAM,CAAA;AAAA,IACvD;AAAA,GACF;AACF;AAKA,eAAsB,YAAA,CAAa,KAAa,GAAA,EAA+B;AAC7E,EAAA,YAAA,CAAa,GAAG,CAAA;AAChB,EAAA,IAAI,QAAQ,MAAA,EAAW;AACrB,IAAA,MAAM,IAAA,GAAA,CAAQ,MAAM,GAAA,CAAI,CAAC,aAAa,IAAA,EAAM,GAAA,EAAK,MAAM,CAAC,CAAA,EAAG,KAAA,CAAM,IAAI,CAAA,CAAE,CAAC,CAAA,IAAK,EAAA;AAC7E,IAAA,MAAMA,IAAAA,GAAM,IAAA,CAAK,KAAA,CAAM,KAAK,EAAE,CAAC,CAAA;AAC/B,IAAA,IAAI,CAACA,IAAAA,EAAK,MAAM,IAAI,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA,aAAA,CAAe,CAAA;AAC/C,IAAA,OAAOA,IAAAA;AAAA,EACT;AACA,EAAA,MAAM,MAAM,MAAM,GAAA,CAAI,CAAC,WAAA,EAAa,MAAM,GAAA,EAAK,CAAA,WAAA,EAAc,GAAG,CAAA,CAAA,EAAI,aAAa,GAAG,CAAA,CAAA,EAAI,CAAA,UAAA,EAAa,GAAG,KAAK,CAAC,CAAA;AAC9G,EAAA,MAAM,KAAA,uBAAY,GAAA,EAAoB;AACtC,EAAA,KAAA,MAAW,IAAA,IAAQ,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA,EAAG;AAClC,IAAA,MAAM,CAACA,IAAAA,EAAK,IAAI,CAAA,GAAI,IAAA,CAAK,MAAM,KAAK,CAAA;AACpC,IAAA,IAAIA,IAAAA,IAAO,IAAA,EAAM,KAAA,CAAM,GAAA,CAAI,MAAMA,IAAG,CAAA;AAAA,EACtC;AAEA,EAAA,MAAM,MAAM,KAAA,CAAM,GAAA,CAAI,CAAA,WAAA,EAAc,GAAG,EAAE,CAAA,IAAK,KAAA,CAAM,GAAA,CAAI,CAAA,UAAA,EAAa,GAAG,CAAA,GAAA,CAAK,CAAA,IAAK,MAAM,GAAA,CAAI,CAAA,UAAA,EAAa,GAAG,CAAA,CAAE,CAAA;AAC9G,EAAA,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,CAAA,KAAA,EAAQ,GAAG,CAAA,eAAA,EAAkB,GAAG,CAAA,CAAA,CAAG,CAAA;AAC7D,EAAA,OAAO,GAAA;AACT","file":"app-remote-install.mjs","sourcesContent":["/**\n * Remote sources for `app_install` / `app_resync`: git URLs and `.agentapp`\n * bundles (URL or local file). Pure staging + swap — no registry, no\n * validation: `app-tools.ts` runs `performInstall` on whatever this hands\n * back, so the same manifest checks apply to every source.\n *\n * Remote-installed apps live under `<appsDir>/<slug>` (the daemon state dir's\n * `apps/`), never in the user's cwd. A (re)install stages into a hidden\n * sibling temp dir first and swaps it in, so a failed clone / digest mismatch\n * / manifest error leaves the previous install untouched.\n */\n\nimport { execFile } from \"node:child_process\"\nimport { randomUUID } from \"node:crypto\"\nimport { copyFile, mkdir, mkdtemp, open, realpath, rename, rm, stat } from \"node:fs/promises\"\nimport { tmpdir } from \"node:os\"\nimport { dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\"\nimport { fileURLToPath, pathToFileURL } from \"node:url\"\nimport { z } from \"zod\"\nimport { safeId, unpackApp } from \"@agentproto/app-kit\"\nimport type { AppSource } from \"./app-registry.js\"\n\nexport const DOWNLOAD_TIMEOUT_MS = 30_000\nexport const DOWNLOAD_MAX_BYTES = 200 * 1024 * 1024\nconst GIT_TIMEOUT_MS = 5 * 60_000\n\n// ── input schema ────────────────────────────────────────────────────────\n\nconst dataDirField = z.string().optional()\n\n/** `app_install` input: exactly one of `{dir}` | `{url, ref?, subdir?, sha?, sha256?, allowBuild?}` | `{file, sha256?}`. */\nexport const appInstallInputSchema = z.union([\n z.object({ dir: z.string(), dataDir: dataDirField }).strict(),\n z\n .object({\n url: z.string(),\n ref: z.string().optional(),\n subdir: z.string().optional(),\n sha: z.string().optional(),\n sha256: z.string().optional(),\n allowBuild: z.boolean().optional(),\n catalogUrl: z.string().optional(),\n dataDir: dataDirField,\n })\n .strict(),\n z.object({ file: z.string(), sha256: z.string().optional(), catalogUrl: z.string().optional(), dataDir: dataDirField }).strict(),\n])\nexport type AppInstallInput = z.infer<typeof appInstallInputSchema>\n\nexport const APP_INSTALL_EXCLUSIVE_ERROR =\n \"pass exactly one source: {dir} (local app dir), {url, ref?, subdir?, sha?, allowBuild?} (git repo), \" +\n \"{url, sha256?} (a .agentapp URL), or {file, sha256?} (local .agentapp path); `dataDir` may accompany any of them, `catalogUrl` any remote one.\"\n\n/** A `{url}` ending in `.agentapp` (ignoring query/fragment) is a bundle, anything else is git. */\nexport function isAgentappUrl(url: string): boolean {\n return /\\.agentapp$/i.test(url.split(/[?#]/)[0]!)\n}\n\n// ── staging ─────────────────────────────────────────────────────────────\n\nexport interface StagedApp {\n /** Directory name under `appsDir` the staged tree will be swapped into. */\n readonly slug: string\n /** Hidden sibling of the final dir holding the staged tree. */\n readonly tmpDir: string\n /** Path of the app dir inside the staged tree (`subdir` for git, `\"\"` otherwise). */\n readonly subdir: string\n readonly source: AppSource\n /** Remove the staged tree (idempotent) — call when the stage is not swapped in. */\n discard(): Promise<void>\n}\n\nfunction tmpSibling(appsDir: string, slug: string): string {\n return join(appsDir, `.tmp-${slug}-${randomUUID().slice(0, 8)}`)\n}\n\nfunction sanitizeSlug(raw: string): string {\n const s = raw.replace(/[^A-Za-z0-9._-]+/g, \"-\").replace(/^[.-]+/, \"\").replace(/-+$/, \"\")\n return s === \"\" ? \"app\" : s\n}\n\n/** `<repo-name>[-<subdir-slug>]` — repo name is the URL's last path segment minus `.git`. */\nexport function gitSlug(url: string, subdir?: string): string {\n const tail = url.replace(/[?#].*$/, \"\").replace(/[\\\\/]+$/, \"\").split(/[\\\\/:]/).pop() ?? \"\"\n const repo = sanitizeSlug(tail.replace(/\\.git$/i, \"\"))\n if (subdir === undefined || subdir === \"\") return repo\n return sanitizeSlug(`${repo}-${subdir.split(/[\\\\/]+/).filter(Boolean).join(\"-\")}`)\n}\n\nfunction assertGitUrl(url: string): void {\n if (url.trim() === \"\" || url.startsWith(\"-\") || url.includes(\"::\")) {\n throw new Error(`\"${url}\" is not a usable git URL.`)\n }\n}\n\nfunction git(args: readonly string[], cwd?: string): Promise<string> {\n return new Promise((done, fail) => {\n execFile(\n \"git\",\n [...args],\n {\n ...(cwd !== undefined ? { cwd } : {}),\n timeout: GIT_TIMEOUT_MS,\n maxBuffer: 16 * 1024 * 1024,\n env: {\n ...process.env,\n GIT_TERMINAL_PROMPT: \"0\",\n GIT_ALLOW_PROTOCOL: \"file:git:http:https:ssh\",\n },\n },\n (err, stdout, stderr) => {\n if (err) {\n const detail = String(stderr).trim() || err.message\n fail(new Error(`git ${args[0]} failed: ${detail}`))\n } else done(String(stdout))\n },\n )\n })\n}\n\nfunction normalizeSubdir(subdir: string | undefined): string {\n if (subdir === undefined) return \"\"\n const trimmed = subdir.trim()\n if (trimmed === \"\" || trimmed === \".\") return \"\"\n if (isAbsolute(trimmed) || trimmed.split(/[\\\\/]/).includes(\"..\")) {\n throw new Error(`subdir \"${subdir}\" must be a relative path inside the repo.`)\n }\n return trimmed.replace(/^\\.[\\\\/]/, \"\").replace(/[\\\\/]+$/, \"\")\n}\n\n/** Shallow-clone `url` into a temp sibling of `<appsDir>/<slug>` and pin the commit. */\nexport async function stageGitApp(input: {\n appsDir: string\n url: string\n ref?: string\n subdir?: string\n expectedSha?: string\n}): Promise<StagedApp> {\n assertGitUrl(input.url)\n const subdir = normalizeSubdir(input.subdir)\n const slug = gitSlug(input.url, subdir)\n await mkdir(input.appsDir, { recursive: true })\n const tmpDir = tmpSibling(input.appsDir, slug)\n const discard = (): Promise<void> => rm(tmpDir, { recursive: true, force: true })\n try {\n await git([\n \"clone\",\n \"--depth\",\n \"1\",\n ...(input.ref !== undefined ? [`--branch=${input.ref}`] : []),\n \"--\",\n input.url,\n tmpDir,\n ])\n const sha = (await git([\"rev-parse\", \"HEAD\"], tmpDir)).trim()\n if (input.expectedSha !== undefined && sha.toLowerCase() !== input.expectedSha.trim().toLowerCase()) {\n throw new Error(\n `git commit mismatch for ${input.url}: expected ${input.expectedSha.trim()}, got ${sha}. Nothing was installed.`,\n )\n }\n if (subdir !== \"\") {\n let real: string\n try {\n real = await realpath(join(tmpDir, subdir))\n } catch {\n throw new Error(`subdir \"${subdir}\" does not exist in ${input.url}.`)\n }\n const root = await realpath(tmpDir)\n if (real !== root && !real.startsWith(root + sep)) {\n throw new Error(`subdir \"${subdir}\" resolves outside the repo.`)\n }\n if (!(await stat(real)).isDirectory()) throw new Error(`subdir \"${subdir}\" is not a directory.`)\n }\n return {\n slug,\n tmpDir,\n subdir,\n source: {\n kind: \"git\",\n url: input.url,\n ...(input.ref !== undefined ? { ref: input.ref } : {}),\n sha,\n ...(subdir !== \"\" ? { subdir } : {}),\n },\n discard,\n }\n } catch (err) {\n await discard()\n throw err\n }\n}\n\n/** Stream `url` (http(s):// or file://) to `destFile` under the timeout + size\n * cap. Exported for `agentproto catalog verify`, which applies the same\n * download caps as `app_install`. */\nexport async function downloadTo(url: string, destFile: string): Promise<void> {\n if (url.startsWith(\"file:\")) {\n const src = fileURLToPath(url)\n if ((await stat(src)).size > DOWNLOAD_MAX_BYTES) {\n throw new Error(`${url} exceeds the ${DOWNLOAD_MAX_BYTES / 1024 / 1024} MB cap.`)\n }\n await copyFile(src, destFile)\n return\n }\n const res = await fetch(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS), redirect: \"follow\" })\n if (!res.ok || res.body === null) throw new Error(`download of ${url} failed: HTTP ${res.status}`)\n const declared = Number(res.headers.get(\"content-length\") ?? \"0\")\n if (declared > DOWNLOAD_MAX_BYTES) {\n throw new Error(`${url} is ${declared} bytes, over the ${DOWNLOAD_MAX_BYTES / 1024 / 1024} MB cap.`)\n }\n const out = await open(destFile, \"w\")\n try {\n let total = 0\n const reader = res.body.getReader()\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n total += value.byteLength\n if (total > DOWNLOAD_MAX_BYTES) {\n await reader.cancel()\n throw new Error(`${url} exceeds the ${DOWNLOAD_MAX_BYTES / 1024 / 1024} MB cap.`)\n }\n await out.write(value)\n }\n } finally {\n await out.close()\n }\n}\n\n/**\n * Fetch (or read) a `.agentapp`, verify + unpack it into a temp sibling of\n * `<appsDir>/<safe-id>`. A digest mismatch (or any unpack refusal) throws and\n * leaves nothing behind.\n */\nexport async function stageAgentApp(input: {\n appsDir: string\n url?: string\n file?: string\n expectedSha256?: string\n}): Promise<StagedApp> {\n await mkdir(input.appsDir, { recursive: true })\n const scratch = await mkdtemp(join(tmpdir(), \"agentapp-dl-\"))\n const tmpDir = join(input.appsDir, `.tmp-agentapp-${randomUUID().slice(0, 8)}`)\n const discard = (): Promise<void> => rm(tmpDir, { recursive: true, force: true })\n try {\n let bundle: string\n let url: string\n if (input.file !== undefined) {\n bundle = resolve(input.file)\n url = pathToFileURL(bundle).href\n } else if (input.url !== undefined) {\n url = input.url\n if (!/^(https?|file):\\/\\//i.test(url)) {\n throw new Error(`.agentapp url must be https://, http:// or file:// (got \"${url}\").`)\n }\n bundle = join(scratch, \"download.agentapp\")\n await downloadTo(url, bundle)\n } else {\n throw new Error(\"stageAgentApp needs a url or a file.\")\n }\n const { manifest } = await unpackApp({ file: bundle, dest: tmpDir })\n if (\n input.expectedSha256 !== undefined &&\n manifest.sha256.toLowerCase() !== input.expectedSha256.trim().toLowerCase()\n ) {\n throw new Error(\n `.agentapp digest mismatch for ${url}: expected sha256 ${input.expectedSha256.trim()}, ` +\n `bundle declares ${manifest.sha256}. Nothing was installed.`,\n )\n }\n return {\n slug: sanitizeSlug(safeId(manifest.id)),\n tmpDir,\n subdir: \"\",\n source: { kind: \"agentapp\", url, sha256: manifest.sha256, version: manifest.version },\n discard,\n }\n } catch (err) {\n await discard()\n throw err\n } finally {\n await rm(scratch, { recursive: true, force: true })\n }\n}\n\n// ── swap ────────────────────────────────────────────────────────────────\n\nexport interface AppSwap {\n /** Drop the previous tree — the new one is final. */\n commit(): Promise<void>\n /** Put the previous tree (and its data) back, discarding the new one. */\n rollback(): Promise<void>\n}\n\n/**\n * Swap the staged tree in at `target`, keeping the previous tree aside until\n * the caller commits. When the previous install's `dataDir` lived inside the\n * old tree (the `<dir>/data` default), it is carried into the new tree so a\n * re-install never loses app data.\n */\nexport async function swapInStaged(input: {\n tmpDir: string\n target: string\n previousDataDir?: string\n}): Promise<AppSwap> {\n const { tmpDir, target } = input\n let backup: string | undefined\n try {\n await stat(target)\n backup = `${target}.old-${randomUUID().slice(0, 8)}`\n } catch {\n // first install — nothing to keep aside\n }\n if (backup !== undefined) await rename(target, backup)\n try {\n await rename(tmpDir, target)\n } catch (err) {\n if (backup !== undefined) await rename(backup, target)\n throw err\n }\n\n let carriedRel: string | undefined\n if (backup !== undefined && input.previousDataDir !== undefined) {\n const rel = relative(target, input.previousDataDir)\n if (rel !== \"\" && !rel.startsWith(\"..\") && !isAbsolute(rel)) {\n const from = join(backup, rel)\n try {\n await stat(from)\n await rm(join(target, rel), { recursive: true, force: true })\n await mkdir(dirname(join(target, rel)), { recursive: true })\n await rename(from, join(target, rel))\n carriedRel = rel\n } catch {\n // no data dir on disk yet — nothing to carry\n }\n }\n }\n\n return {\n async commit() {\n if (backup !== undefined) await rm(backup, { recursive: true, force: true })\n },\n async rollback() {\n if (carriedRel !== undefined && backup !== undefined) {\n await mkdir(dirname(join(backup, carriedRel)), { recursive: true })\n await rename(join(target, carriedRel), join(backup, carriedRel))\n }\n await rm(target, { recursive: true, force: true })\n if (backup !== undefined) await rename(backup, target)\n },\n }\n}\n\n// ── resync probe ────────────────────────────────────────────────────────\n\n/** The commit `url`'s `ref` (default `HEAD`) currently points at, via `git ls-remote`. */\nexport async function remoteGitSha(url: string, ref?: string): Promise<string> {\n assertGitUrl(url)\n if (ref === undefined) {\n const line = (await git([\"ls-remote\", \"--\", url, \"HEAD\"])).split(\"\\n\")[0] ?? \"\"\n const sha = line.split(/\\s+/)[0]\n if (!sha) throw new Error(`${url} has no HEAD.`)\n return sha\n }\n const out = await git([\"ls-remote\", \"--\", url, `refs/heads/${ref}`, `refs/tags/${ref}`, `refs/tags/${ref}^{}`])\n const byRef = new Map<string, string>()\n for (const line of out.split(\"\\n\")) {\n const [sha, name] = line.split(/\\s+/)\n if (sha && name) byRef.set(name, sha)\n }\n // Peeled tag (`^{}`) is the commit an annotated tag points at — what a clone checks out.\n const sha = byRef.get(`refs/heads/${ref}`) ?? byRef.get(`refs/tags/${ref}^{}`) ?? byRef.get(`refs/tags/${ref}`)\n if (!sha) throw new Error(`ref \"${ref}\" not found in ${url}.`)\n return sha\n}\n"]}
@@ -0,0 +1,47 @@
1
+ import { AppCatalogEntry } from './app-catalog.js';
2
+ import 'zod';
3
+ import './app-registry-B64vZEaO.js';
4
+
5
+ /**
6
+ * Publishing-pipeline helpers shared by `agentproto catalog build`
7
+ * (`packages/cli`) and the `catalog:first-party` sync script
8
+ * (`packages/runtime/scripts/sync-first-party-catalog.mjs`): sorting +
9
+ * merging catalog entries, and rendering the embedded first-party
10
+ * catalog TypeScript file. Single source of truth -- no copies.
11
+ */
12
+
13
+ /** A v1 catalog document as published at `DEFAULT_CATALOG_SOURCE_URL`. */
14
+ interface CatalogDocument {
15
+ schema: string;
16
+ generatedAt: string;
17
+ entries: AppCatalogEntry[];
18
+ }
19
+ /** Entries sorted by `appId` (stable order for deterministic output). */
20
+ declare function sortCatalogEntries(entries: readonly AppCatalogEntry[]): AppCatalogEntry[];
21
+ /** Build a v1 catalog document with deterministic entry order. */
22
+ declare function buildCatalogDocument(entries: readonly AppCatalogEntry[], generatedAt: string): CatalogDocument;
23
+ /** JSON serialization used for every published catalog file (2-space
24
+ * indent, trailing newline) so diffs stay clean. */
25
+ declare function serializeCatalogDocument(doc: CatalogDocument): string;
26
+ interface MergeCatalogResult {
27
+ entries: AppCatalogEntry[];
28
+ /** `appId -> reason` for incoming entries that were NOT applied. */
29
+ skipped: Map<string, string>;
30
+ }
31
+ /**
32
+ * Merge incoming entries over `base` by `appId`: the incoming entry replaces
33
+ * the existing one when its version is >= (per `compareCatalogVersions`;
34
+ * unparseable versions always let the incoming entry win). An incoming
35
+ * entry with a LOWER version is skipped with a warning and the existing
36
+ * entry stays. Incoming entries with a version that ties or is unparseable
37
+ * replace too -- publishing the same version twice is a no-op rewrite.
38
+ */
39
+ declare function mergeCatalogEntries(base: readonly AppCatalogEntry[], incoming: readonly AppCatalogEntry[], compare: (a: string, b: string) => number | undefined): MergeCatalogResult;
40
+ /**
41
+ * Render the embedded first-party catalog TypeScript file. The emitted
42
+ * source must pass typecheck and lint as-is: entries are serialized as
43
+ * plain literals (key order preserved from the validated entry).
44
+ */
45
+ declare function renderFirstPartyCatalogTs(entries: readonly AppCatalogEntry[]): string;
46
+
47
+ export { type CatalogDocument, type MergeCatalogResult, buildCatalogDocument, mergeCatalogEntries, renderFirstPartyCatalogTs, serializeCatalogDocument, sortCatalogEntries };
@@ -0,0 +1,116 @@
1
+ import 'crypto';
2
+ import 'fs/promises';
3
+ import 'os';
4
+ import 'path';
5
+ import { z } from 'zod';
6
+
7
+ /**
8
+ * @agentproto/runtime v0.1.0-alpha
9
+ * Long-running gateway: MCP server + HTTP transport + HEARTBEAT autonomy + conversation persistence over a workspace dir.
10
+ */
11
+
12
+ var AppSourceSchema = z.discriminatedUnion("kind", [
13
+ z.object({ kind: z.literal("local") }),
14
+ z.object({
15
+ kind: z.literal("git"),
16
+ url: z.string().min(1),
17
+ ref: z.string().optional(),
18
+ sha: z.string().min(1),
19
+ subdir: z.string().optional()
20
+ }),
21
+ z.object({
22
+ kind: z.literal("agentapp"),
23
+ url: z.string().min(1),
24
+ sha256: z.string().min(1),
25
+ version: z.string().min(1),
26
+ size: z.number().int().nonnegative().optional()
27
+ })
28
+ ]);
29
+ z.object({
30
+ appId: z.string().min(1),
31
+ name: z.string().optional(),
32
+ description: z.string().optional(),
33
+ category: z.string().optional(),
34
+ source: AppSourceSchema,
35
+ placement: z.enum(["local", "box", "any", "split"]).optional(),
36
+ icon: z.string().optional(),
37
+ version: z.string().optional(),
38
+ tier: z.enum(["git", "bundle", "hosted"]).optional(),
39
+ publisher: z.string().optional(),
40
+ license: z.object({ kind: z.enum(["free", "paid", "private"]), url: z.string().optional() }).optional(),
41
+ minAgentprotoVersion: z.string().optional(),
42
+ requires: z.object({
43
+ browser: z.boolean().optional(),
44
+ fs: z.boolean().optional(),
45
+ secrets: z.array(z.string()).optional()
46
+ }).optional(),
47
+ featured: z.boolean().optional()
48
+ });
49
+ var CATALOG_SCHEMA_V1 = "app-catalog/v1";
50
+ z.object({
51
+ schema: z.string().optional(),
52
+ generatedAt: z.string().optional(),
53
+ entries: z.array(z.unknown())
54
+ });
55
+
56
+ // src/first-party-catalog-gen.ts
57
+ function sortCatalogEntries(entries) {
58
+ return [...entries].sort((a, b) => a.appId < b.appId ? -1 : a.appId > b.appId ? 1 : 0);
59
+ }
60
+ function buildCatalogDocument(entries, generatedAt) {
61
+ return { schema: CATALOG_SCHEMA_V1, generatedAt, entries: sortCatalogEntries(entries) };
62
+ }
63
+ function serializeCatalogDocument(doc) {
64
+ return JSON.stringify(doc, null, 2) + "\n";
65
+ }
66
+ function mergeCatalogEntries(base, incoming, compare) {
67
+ const byAppId = /* @__PURE__ */ new Map();
68
+ for (const e of base) byAppId.set(e.appId, e);
69
+ const skipped = /* @__PURE__ */ new Map();
70
+ for (const e of incoming) {
71
+ const existing = byAppId.get(e.appId);
72
+ if (existing === void 0) {
73
+ byAppId.set(e.appId, e);
74
+ continue;
75
+ }
76
+ const ev = existing.version;
77
+ const nv = e.version;
78
+ if (ev !== void 0 && nv !== void 0) {
79
+ const cmp = compare(nv, ev);
80
+ if (cmp !== void 0 && cmp < 0) {
81
+ skipped.set(
82
+ e.appId,
83
+ `version ${nv} is older than the existing ${ev}; keeping the existing entry`
84
+ );
85
+ continue;
86
+ }
87
+ }
88
+ byAppId.set(e.appId, e);
89
+ }
90
+ return { entries: [...byAppId.values()], skipped };
91
+ }
92
+ function renderFirstPartyCatalogTs(entries) {
93
+ const body = sortCatalogEntries(entries).map((e) => " " + JSON.stringify(e, null, 2).replace(/\n/g, "\n ") + ",").join("\n");
94
+ return `/**
95
+ * Embedded first-party catalog -- the offline fallback for the DEFAULT
96
+ * catalog source (\`DEFAULT_CATALOG_SOURCE_URL\`, app-catalog.ts) when it is
97
+ * unreachable and no copy of it is cached on disk, so a fresh offline
98
+ * daemon's \`app_catalog\` still lists first-party apps. Entries carry
99
+ * metadata + URLs: they install once the network is back.
100
+ *
101
+ * GENERATED by \`pnpm --filter @agentproto/runtime catalog:first-party\`
102
+ * from the published catalog -- do not hand-edit. \`app_install {sha256}\`
103
+ * refuses any digest mismatch.
104
+ */
105
+
106
+ import type { AppCatalogEntry } from "./app-catalog.js"
107
+
108
+ export const FIRST_PARTY_CATALOG_ENTRIES: readonly AppCatalogEntry[] = [
109
+ ${body}
110
+ ]
111
+ `;
112
+ }
113
+
114
+ export { buildCatalogDocument, mergeCatalogEntries, renderFirstPartyCatalogTs, serializeCatalogDocument, sortCatalogEntries };
115
+ //# sourceMappingURL=first-party-catalog-gen.mjs.map
116
+ //# sourceMappingURL=first-party-catalog-gen.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/app-catalog.ts","../src/first-party-catalog-gen.ts"],"names":[],"mappings":"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","file":"first-party-catalog-gen.mjs","sourcesContent":["/**\n * App catalog — a static, hand-curated `~/.agentproto/app-catalog.json`\n * listing apps a user could `app_install`, independent of whether any are\n * installed yet, plus optional remote catalog `sources` (each an HTTP URL\n * returning `{ entries: AppCatalogEntry[] }`). Read-only from the daemon's\n * side; `app_catalog` (app-tools.ts) merges local entries, remote entries and\n * `AppRegistry.listApps()` to report installed/hasUi status.\n *\n * Sources (see `resolveCatalogSources`): the default public catalog\n * (`DEFAULT_CATALOG_SOURCE_URL`, config `catalog.defaultSource`) comes\n * first, then config `catalog.sources` — which win over (replace) `sources`\n * in the catalog file when set. Extra sources ADD to the default one. Remote\n * catalogs follow the `app-catalog/v1` format; the last good copy of each is\n * cached on disk so `app_catalog` keeps working offline.\n */\n\nimport { createHash } from \"node:crypto\"\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\"\nimport { homedir } from \"node:os\"\nimport { join } from \"node:path\"\nimport { z } from \"zod\"\nimport type { AppSource } from \"./app-registry.js\"\n\nexport type { AppSource }\n\nexport type AppPlacement = \"local\" | \"box\" | \"any\" | \"split\"\n\nexport type AppCatalogTier = \"git\" | \"bundle\" | \"hosted\"\n\nexport interface AppCatalogLicense {\n kind: \"free\" | \"paid\" | \"private\"\n url?: string\n}\n\nexport interface AppCatalogRequires {\n browser?: boolean\n fs?: boolean\n secrets?: string[]\n}\n\n/** Remote catalog entry (`app-catalog/v1`). `source` lets a store call\n * `app_install {url, ref, subdir, sha}` (git) or `app_install {url, sha256}`\n * (`.agentapp`). Every field but `appId` + `source` is optional, so\n * pre-v1 catalogs (`{ entries }` only) keep validating. */\nexport interface AppCatalogEntry {\n appId: string\n name?: string\n description?: string\n category?: string\n source: AppSource\n placement?: AppPlacement\n icon?: string\n version?: string\n tier?: AppCatalogTier\n publisher?: string\n license?: AppCatalogLicense\n minAgentprotoVersion?: string\n requires?: AppCatalogRequires\n featured?: boolean\n}\n\n/** Entry of the local `app-catalog.json` `apps` array: a directory on disk. */\nexport interface AppCatalogFileEntry {\n readonly appId: string\n readonly name?: string\n readonly description?: string\n readonly dir: string\n readonly category?: string\n}\n\nexport interface AppCatalogSource {\n readonly url: string\n}\n\nexport interface AppCatalogFile {\n readonly apps: readonly AppCatalogFileEntry[]\n readonly sources?: readonly AppCatalogSource[]\n}\n\nconst AppSourceSchema: z.ZodType<AppSource> = z.discriminatedUnion(\"kind\", [\n z.object({ kind: z.literal(\"local\") }),\n z.object({\n kind: z.literal(\"git\"),\n url: z.string().min(1),\n ref: z.string().optional(),\n sha: z.string().min(1),\n subdir: z.string().optional(),\n }),\n z.object({\n kind: z.literal(\"agentapp\"),\n url: z.string().min(1),\n sha256: z.string().min(1),\n version: z.string().min(1),\n size: z.number().int().nonnegative().optional(),\n }),\n])\n\nexport const AppCatalogEntrySchema: z.ZodType<AppCatalogEntry> = z.object({\n appId: z.string().min(1),\n name: z.string().optional(),\n description: z.string().optional(),\n category: z.string().optional(),\n source: AppSourceSchema,\n placement: z.enum([\"local\", \"box\", \"any\", \"split\"]).optional(),\n icon: z.string().optional(),\n version: z.string().optional(),\n tier: z.enum([\"git\", \"bundle\", \"hosted\"]).optional(),\n publisher: z.string().optional(),\n license: z\n .object({ kind: z.enum([\"free\", \"paid\", \"private\"]), url: z.string().optional() })\n .optional(),\n minAgentprotoVersion: z.string().optional(),\n requires: z\n .object({\n browser: z.boolean().optional(),\n fs: z.boolean().optional(),\n secrets: z.array(z.string()).optional(),\n })\n .optional(),\n featured: z.boolean().optional(),\n})\n\n/** `schema` tag of a v1 catalog document. */\nexport const CATALOG_SCHEMA_V1 = \"app-catalog/v1\"\n\nconst RemoteCatalogSchema = z.object({\n schema: z.string().optional(),\n generatedAt: z.string().optional(),\n entries: z.array(z.unknown()),\n})\n\n/**\n * Default public catalog, queried by `app_catalog` unless config\n * `catalog.defaultSource` is `false` (a URL string replaces it). The file is\n * a static `apps.json` served by the agentproto/site repo\n * (`public/catalog/v1/apps.json`). Bundles it references are GitHub Release\n * assets of the public `agentproto/apps` repo. A failing default source is\n * never fatal: `app_catalog` falls back to the last good copy cached on\n * disk, then to the embedded first-party list (`first-party-catalog.ts`).\n */\nexport const DEFAULT_CATALOG_SOURCE_URL = \"https://agentproto.sh/catalog/v1/apps.json\"\n\nconst EMPTY_CATALOG: AppCatalogFile = { apps: [] }\n\nexport function defaultAppCatalogPath(): string {\n return join(homedir(), \".agentproto\", \"app-catalog.json\")\n}\n\n/**\n * Read + parse the catalog file at `path` (default `~/.agentproto/app-catalog.json`).\n * Tolerates a missing file (returns an empty catalog) and a malformed one\n * (unreadable JSON, non-array `apps`, entries missing `appId`/`dir`, and\n * `sources` without a string `url` are dropped) — never throws.\n */\nexport async function loadAppCatalogFile(path?: string): Promise<AppCatalogFile> {\n const catalogPath = path ?? defaultAppCatalogPath()\n let raw: string\n try {\n raw = await readFile(catalogPath, \"utf8\")\n } catch {\n return EMPTY_CATALOG\n }\n try {\n const parsed = JSON.parse(raw) as { apps?: unknown; sources?: unknown }\n const sources = parseSources(parsed.sources)\n const apps = Array.isArray(parsed.apps)\n ? parsed.apps.filter((e): e is AppCatalogFileEntry => {\n if (typeof e !== \"object\" || e === null) return false\n const rec = e as Record<string, unknown>\n return typeof rec.appId === \"string\" && typeof rec.dir === \"string\"\n })\n : []\n return sources.length > 0 ? { apps, sources } : { apps }\n } catch {\n return EMPTY_CATALOG\n }\n}\n\nfunction parseSources(raw: unknown): AppCatalogSource[] {\n if (!Array.isArray(raw)) return []\n const out: AppCatalogSource[] = []\n for (const s of raw) {\n if (typeof s !== \"object\" || s === null) continue\n const url = (s as Record<string, unknown>).url\n if (typeof url === \"string\" && url.length > 0) out.push({ url })\n }\n return out\n}\n\nexport type CatalogSourceOrigin = \"default\" | \"config\" | \"file\"\n\nexport interface ResolvedCatalogSource extends AppCatalogSource {\n readonly origin: CatalogSourceOrigin\n}\n\n/**\n * Remote sources `app_catalog` queries, in precedence order (first wins on\n * an `appId` collision): the default source (unless `defaultSource: false`;\n * a non-empty string replaces its URL), then config `catalog.sources` when\n * set (even empty), else the catalog file's `sources`. Config/file sources\n * ADD to the default one, they never replace it. Duplicate URLs are dropped.\n */\nexport function resolveCatalogSources(\n fileSources: readonly AppCatalogSource[] | undefined,\n config: { sources?: unknown; defaultSource?: unknown } | undefined,\n): ResolvedCatalogSource[] {\n const out: ResolvedCatalogSource[] = []\n const seen = new Set<string>()\n const push = (url: string, origin: CatalogSourceOrigin): void => {\n if (seen.has(url)) return\n seen.add(url)\n out.push({ url, origin })\n }\n const def = config?.defaultSource\n if (def !== false) {\n push(typeof def === \"string\" && def.length > 0 ? def : DEFAULT_CATALOG_SOURCE_URL, \"default\")\n }\n if (Array.isArray(config?.sources)) {\n for (const s of parseSources(config.sources)) push(s.url, \"config\")\n } else {\n for (const s of fileSources ?? []) push(s.url, \"file\")\n }\n return out\n}\n\n/** One source's outcome, in the order sources were passed. */\nexport interface CatalogSourceResult {\n url: string\n entries: AppCatalogEntry[]\n /** True when `entries` come from the disk cache because the live fetch failed. */\n stale: boolean\n /** When the entries were fetched (live or cached), ISO-8601. */\n fetchedAt?: string\n /** False when the source yielded nothing at all (fetch failed, no cache). */\n ok: boolean\n}\n\nexport interface RemoteCatalogResult {\n entries: AppCatalogEntry[]\n warnings: string[]\n bySource: CatalogSourceResult[]\n}\n\nexport interface RemoteCatalogClientOptions {\n /** Default 5 minutes. */\n ttlMs?: number\n /** Per-source request timeout. Default 5000. */\n timeoutMs?: number\n fetchImpl?: typeof fetch\n now?: () => number\n /** Directory for the last-good copy of each source (`<sha1(url)>.json`).\n * Absent = no disk cache (in-memory TTL cache only). */\n cacheDir?: string\n}\n\nexport interface RemoteCatalogClient {\n /** Fetch + validate every source (in order, in parallel). A failing source\n * never throws: it yields a `warnings` line and either its last cached\n * copy (`stale`) or no entries. Live results are cached per URL in memory\n * for `ttlMs` and on disk (when `cacheDir` is set); `refresh` bypasses the\n * in-memory cache. */\n fetchSources(\n sources: readonly AppCatalogSource[],\n opts?: { refresh?: boolean },\n ): Promise<RemoteCatalogResult>\n}\n\nexport const DEFAULT_REMOTE_CATALOG_TTL_MS = 5 * 60_000\nexport const DEFAULT_REMOTE_CATALOG_TIMEOUT_MS = 5_000\n\n/** Disk-cache file for `url` under `cacheDir`. */\nexport function catalogCachePath(cacheDir: string, url: string): string {\n return join(cacheDir, `${createHash(\"sha1\").update(url).digest(\"hex\")}.json`)\n}\n\ninterface CatalogDiskCache {\n url: string\n fetchedAt: string\n entries: unknown[]\n}\n\ntype SourceFetch = CatalogSourceResult & { warnings: string[] }\n\nexport function createRemoteCatalogClient(\n options: RemoteCatalogClientOptions = {},\n): RemoteCatalogClient {\n const ttlMs = options.ttlMs ?? DEFAULT_REMOTE_CATALOG_TTL_MS\n const timeoutMs = options.timeoutMs ?? DEFAULT_REMOTE_CATALOG_TIMEOUT_MS\n const fetchImpl = options.fetchImpl ?? fetch\n const now = options.now ?? Date.now\n const cacheDir = options.cacheDir\n const cache = new Map<string, { at: number; result: SourceFetch }>()\n\n async function readDiskCache(url: string): Promise<{ entries: AppCatalogEntry[]; fetchedAt: string } | undefined> {\n if (cacheDir === undefined) return undefined\n try {\n const raw = JSON.parse(await readFile(catalogCachePath(cacheDir, url), \"utf8\")) as Partial<CatalogDiskCache>\n if (raw.url !== url || !Array.isArray(raw.entries) || typeof raw.fetchedAt !== \"string\") return undefined\n const entries: AppCatalogEntry[] = []\n for (const e of raw.entries) {\n const parsed = AppCatalogEntrySchema.safeParse(e)\n if (parsed.success) entries.push(parsed.data)\n }\n return { entries, fetchedAt: raw.fetchedAt }\n } catch {\n return undefined\n }\n }\n\n async function writeDiskCache(url: string, entries: AppCatalogEntry[], fetchedAt: string): Promise<void> {\n if (cacheDir === undefined) return\n try {\n await mkdir(cacheDir, { recursive: true })\n const file = catalogCachePath(cacheDir, url)\n const tmp = `${file}.tmp-${process.pid}`\n const body: CatalogDiskCache = { url, fetchedAt, entries }\n await writeFile(tmp, JSON.stringify(body, null, 2) + \"\\n\", \"utf8\")\n await rename(tmp, file)\n } catch {\n // Best-effort — a cache write failure must not fail the listing.\n }\n }\n\n async function failed(url: string, warning: string): Promise<SourceFetch> {\n const cached = await readDiskCache(url)\n if (cached === undefined) return { url, entries: [], warnings: [warning], stale: false, ok: false }\n return {\n url,\n entries: cached.entries,\n warnings: [`${warning}; using the copy cached at ${cached.fetchedAt}`],\n stale: true,\n fetchedAt: cached.fetchedAt,\n ok: true,\n }\n }\n\n async function fetchOne(url: string): Promise<SourceFetch> {\n let body: unknown\n try {\n const res = await fetchImpl(url, {\n headers: { Accept: \"application/json\" },\n signal: AbortSignal.timeout(timeoutMs),\n })\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n body = await res.json()\n } catch (err) {\n const timedOut = err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\")\n const reason = timedOut\n ? `timed out after ${timeoutMs}ms`\n : err instanceof Error\n ? err.message\n : String(err)\n return failed(url, `catalog source ${url}: ${reason}`)\n }\n const shape = RemoteCatalogSchema.safeParse(body)\n if (!shape.success) {\n return failed(url, `catalog source ${url}: expected { entries: [...] }`)\n }\n const entries: AppCatalogEntry[] = []\n const warnings: string[] = []\n if (shape.data.schema !== undefined && shape.data.schema !== CATALOG_SCHEMA_V1) {\n warnings.push(\n `catalog source ${url}: unknown schema \"${shape.data.schema}\" — reading its entries as ${CATALOG_SCHEMA_V1}`,\n )\n }\n shape.data.entries.forEach((raw, i) => {\n const parsed = AppCatalogEntrySchema.safeParse(raw)\n if (parsed.success) entries.push(parsed.data)\n else warnings.push(`catalog source ${url}: entries[${i}] invalid (${parsed.error.issues[0]?.message ?? \"schema mismatch\"})`)\n })\n const fetchedAt = new Date(now()).toISOString()\n await writeDiskCache(url, entries, fetchedAt)\n return { url, entries, warnings, stale: false, fetchedAt, ok: true }\n }\n\n return {\n async fetchSources(sources, opts = {}) {\n const results = await Promise.all(\n sources.map(async ({ url }) => {\n const hit = cache.get(url)\n if (!opts.refresh && hit && now() - hit.at < ttlMs) return hit.result\n const result = await fetchOne(url)\n // Neither failures nor stale disk fallbacks are cached in memory,\n // so a flaky source retries on the next call.\n if (!result.stale && (result.entries.length > 0 || result.warnings.length === 0)) {\n cache.set(url, { at: now(), result })\n } else {\n cache.delete(url)\n }\n return result\n }),\n )\n return {\n entries: results.flatMap(r => r.entries),\n warnings: results.flatMap(r => r.warnings),\n bySource: results.map(({ warnings: _warnings, ...rest }) => rest),\n }\n },\n }\n}\n\n/**\n * Compare dotted numeric versions (`1.2.10` > `1.2.9`); a pre-release suffix\n * (`1.2.0-beta`) ranks below the same core. Returns -1 | 0 | 1, or\n * `undefined` when either side isn't `N.N.N`-shaped.\n */\nexport function compareCatalogVersions(a: string, b: string): number | undefined {\n const parse = (v: string): { core: number[]; pre: boolean } | undefined => {\n const m = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(-.+)?$/.exec(v.trim())\n return m ? { core: [Number(m[1]), Number(m[2]), Number(m[3])], pre: m[4] !== undefined } : undefined\n }\n const pa = parse(a)\n const pb = parse(b)\n if (pa === undefined || pb === undefined) return undefined\n for (let i = 0; i < 3; i++) {\n const d = pa.core[i]! - pb.core[i]!\n if (d !== 0) return d < 0 ? -1 : 1\n }\n if (pa.pre === pb.pre) return 0\n return pa.pre ? -1 : 1\n}\n\n/**\n * Is catalog `entry` an update for an installed app (recorded `source`,\n * APP.md `version`)? Only for the same source kind, only when the pin\n * differs (bundle sha256 / git commit), and never when the entry's version\n * is LOWER than the installed one — a catalog listing an older release is\n * not an update.\n */\nexport function isCatalogUpdate(\n installed: { readonly source?: AppSource; readonly version?: string },\n entry: AppCatalogEntry,\n): boolean {\n const s = installed.source\n if (s === undefined || s.kind === \"local\" || s.kind !== entry.source.kind) return false\n const offered = entry.source\n const differs =\n s.kind === \"agentapp\"\n ? offered.kind === \"agentapp\" && offered.sha256.toLowerCase() !== s.sha256.toLowerCase()\n : offered.kind === \"git\" && offered.sha.toLowerCase() !== s.sha.toLowerCase()\n if (!differs) return false\n const installedVersion = s.kind === \"agentapp\" ? s.version : installed.version\n const offeredVersion = offered.kind === \"agentapp\" ? offered.version : entry.version\n if (installedVersion !== undefined && offeredVersion !== undefined) {\n const cmp = compareCatalogVersions(offeredVersion, installedVersion)\n if (cmp !== undefined && cmp < 0) return false\n }\n return true\n}\n","/**\n * Publishing-pipeline helpers shared by `agentproto catalog build`\n * (`packages/cli`) and the `catalog:first-party` sync script\n * (`packages/runtime/scripts/sync-first-party-catalog.mjs`): sorting +\n * merging catalog entries, and rendering the embedded first-party\n * catalog TypeScript file. Single source of truth -- no copies.\n */\n\nimport { CATALOG_SCHEMA_V1, type AppCatalogEntry } from \"./app-catalog.js\"\n\n/** A v1 catalog document as published at `DEFAULT_CATALOG_SOURCE_URL`. */\nexport interface CatalogDocument {\n schema: string\n generatedAt: string\n entries: AppCatalogEntry[]\n}\n\n/** Entries sorted by `appId` (stable order for deterministic output). */\nexport function sortCatalogEntries(\n entries: readonly AppCatalogEntry[],\n): AppCatalogEntry[] {\n return [...entries].sort((a, b) => (a.appId < b.appId ? -1 : a.appId > b.appId ? 1 : 0))\n}\n\n/** Build a v1 catalog document with deterministic entry order. */\nexport function buildCatalogDocument(\n entries: readonly AppCatalogEntry[],\n generatedAt: string,\n): CatalogDocument {\n return { schema: CATALOG_SCHEMA_V1, generatedAt, entries: sortCatalogEntries(entries) }\n}\n\n/** JSON serialization used for every published catalog file (2-space\n * indent, trailing newline) so diffs stay clean. */\nexport function serializeCatalogDocument(doc: CatalogDocument): string {\n return JSON.stringify(doc, null, 2) + \"\\n\"\n}\n\nexport interface MergeCatalogResult {\n entries: AppCatalogEntry[]\n /** `appId -> reason` for incoming entries that were NOT applied. */\n skipped: Map<string, string>\n}\n\n/**\n * Merge incoming entries over `base` by `appId`: the incoming entry replaces\n * the existing one when its version is >= (per `compareCatalogVersions`;\n * unparseable versions always let the incoming entry win). An incoming\n * entry with a LOWER version is skipped with a warning and the existing\n * entry stays. Incoming entries with a version that ties or is unparseable\n * replace too -- publishing the same version twice is a no-op rewrite.\n */\nexport function mergeCatalogEntries(\n base: readonly AppCatalogEntry[],\n incoming: readonly AppCatalogEntry[],\n compare: (a: string, b: string) => number | undefined,\n): MergeCatalogResult {\n const byAppId = new Map<string, AppCatalogEntry>()\n for (const e of base) byAppId.set(e.appId, e)\n const skipped = new Map<string, string>()\n for (const e of incoming) {\n const existing = byAppId.get(e.appId)\n if (existing === undefined) {\n byAppId.set(e.appId, e)\n continue\n }\n const ev = existing.version\n const nv = e.version\n if (ev !== undefined && nv !== undefined) {\n const cmp = compare(nv, ev)\n if (cmp !== undefined && cmp < 0) {\n skipped.set(\n e.appId,\n `version ${nv} is older than the existing ${ev}; keeping the existing entry`,\n )\n continue\n }\n }\n byAppId.set(e.appId, e)\n }\n return { entries: [...byAppId.values()], skipped }\n}\n\n/**\n * Render the embedded first-party catalog TypeScript file. The emitted\n * source must pass typecheck and lint as-is: entries are serialized as\n * plain literals (key order preserved from the validated entry).\n */\nexport function renderFirstPartyCatalogTs(\n entries: readonly AppCatalogEntry[],\n): string {\n const body = sortCatalogEntries(entries)\n .map((e) => \" \" + JSON.stringify(e, null, 2).replace(/\\n/g, \"\\n \") + \",\")\n .join(\"\\n\")\n return `/**\n * Embedded first-party catalog -- the offline fallback for the DEFAULT\n * catalog source (\\`DEFAULT_CATALOG_SOURCE_URL\\`, app-catalog.ts) when it is\n * unreachable and no copy of it is cached on disk, so a fresh offline\n * daemon's \\`app_catalog\\` still lists first-party apps. Entries carry\n * metadata + URLs: they install once the network is back.\n *\n * GENERATED by \\`pnpm --filter @agentproto/runtime catalog:first-party\\`\n * from the published catalog -- do not hand-edit. \\`app_install {sha256}\\`\n * refuses any digest mismatch.\n */\n\nimport type { AppCatalogEntry } from \"./app-catalog.js\"\n\nexport const FIRST_PARTY_CATALOG_ENTRIES: readonly AppCatalogEntry[] = [\n${body}\n]\n`\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -3,8 +3,8 @@ import { h as WorktreeIsolationMode, i as SpawnAttachMode, j as SpawnDedupeMode,
3
3
  export { k as DEFAULT_MODEL_ROLES, M as MODEL_ROLE_REF_PREFIX, l as ModelRoleContext, m as ModelRoleEntry, n as ModelRoleSource, o as ModelRoleValue, p as ModelRolesConfig, q as listModelRoles, r as parseModelRoleRef, s as resolveModelRole } from './config-Dsmv-jri.js';
4
4
  import { B as BuildHeartbeatAgent } from './heartbeat-BJtxbDb9.js';
5
5
  export { H as HeartbeatAgent, a as HeartbeatRunner, R as RuntimeEvent, b as RuntimeEvents, p as parseDuration } from './heartbeat-BJtxbDb9.js';
6
- import { M as MessageUrgency, S as SessionEvent, a as SessionEventType, b as SessionEventBus, c as SessionsRegistry, C as CompletionPolicySupervisor, T as TaskLedger, d as SessionDescriptor, e as SessionEndReason, A as ActivityPolicySlice, f as ActivityListFilter, g as ActivityRecord, h as ActivityTurnSession, i as ActivityPrSession, j as ActivityWorkflowRunSlice, P as PrResolvedState, k as ActivityTaskSlice, W as WorktreeProvisioner, l as AgentsMdResolution, U as UserPreset, R as RestartPolicy, m as SandboxAppServeSpec, n as WorktreeField, o as AgentSessionLike, p as UsageReadContext, q as UsageReadResult, r as MessageKind, s as SessionMessage, t as AgentStreamEvent, u as SessionUsage, v as ToolCallRecord, w as PendingPromptView, x as StatsSessionDescriptor, y as ProcRow, z as ProvisionInFlight, B as ProvisionTracker, D as SessionCrashReason, E as UsageRollup, F as RemainingQuotaReader, Q as QuotaReadableProfile, G as UsageSnapshotRecord, H as SessionAwaitingQuestion, I as PolicyRunState, J as PtyFactory, K as WorktreeAutoReclaimer } from './sessions-BdgU9X6P.js';
7
- export { L as ActivityKind, N as ActivitySource, O as ActivityState, V as ActivityWaitingOn, X as AnthropicReaderConfig, Y as AnthropicRemainingQuotaReader, Z as AttachPolicyInput, _ as CommandGroupStats, $ as CommitSpec, a0 as DEFAULT_WORKTREE_ISOLATION, a1 as DEFAULT_WORKTREE_PROVISION_CONCURRENCY, a2 as EagerResumeFailReason, a3 as EagerResumeOutcome, a4 as EagerResumeSkipReason, a5 as GateSpec, a6 as HostInfo, a7 as JudgeGateSpec, a8 as LabeledProcessStatsReport, a9 as LabeledSessionStats, aa as MAX_MESSAGE_DATA_BYTES, ab as MESSAGE_KINDS, ac as MESSAGE_PREAMBLE, ad as MESSAGE_TAG, ae as MESSAGE_URGENCIES, af as MessageDeliveryVia, ag as MessageFilter, ah as MessageRelation, ai as MessageTreeNode, aj as NO_OUTPUT_STALL_TURN_ERROR, ak as OnFailSpec, al as OrphanGroup, am as PendingPermission, an as PermissionRespondInput, ao as PermissionRespondResult, ap as PolicyRunStatus, aq as PricingResolver, ar as ProcessDetail, as as ProcessStatsReport, at as ProcessStatsService, au as QuotaStoreFile, av as QuotaStoreOptions, aw as RegisterBrowserInput, ax as RegisterSessionInput, ay as RemainingQuota, az as ResourceStats, aA as ResumeDisabledError, aB as SESSION_END_REASONS, aC as SESSION_ID_ENV, aD as SessionCurrentPhase, aE as SessionKind, aF as SessionMessageFrom, aG as SessionOutcome, aH as SessionOutcomeArtifact, aI as SessionOutcomeCompact, aJ as SessionOutcomeLink, aK as SessionResourceStats, aL as SessionSnapshots, aM as SessionStatus, aN as ShellGateSpec, aO as SpawnAgentInput, aP as SpawnSessionInput, aQ as StatsDetail, aR as StoredProfileQuota, aS as TASK_STATUSES, aT as TaskCaller, aU as TaskChangedEvent, aV as TaskClaimInput, aW as TaskCreateInput, aX as TaskGateOutcome, aY as TaskGateRunner, aZ as TaskLedgerRegistry, a_ as TaskLedgerSessionSlice, a$ as TaskListFilter, b0 as TaskRecord, b1 as TaskStatus, b2 as TaskUpdateInput, b3 as TaskVerification, b4 as TaskVerifySupervisor, b5 as TaskWriteResult, b6 as TokenPricing, b7 as UsageBucket, b8 as UsageComputeInput, b9 as UsageDetail, ba as UsageSource, bb as UserPresetsFile, bc as WORKSPACE_SLUG_ENV, bd as WORKTREE_ISOLATION_ENV, be as WORKTREE_PROVISION_CONCURRENCY_ENV, bf as WorktreeDecision, bg as WorktreeProvisionOutcome, bh as WorktreeProvisionPhase, bi as WorktreeProvisionProgress, bj as WorktreeProvisionRequest, bk as WorktreeRequest, bl as activityCounts, bm as compactOutcome, bn as createProcessStatsService, bo as createSessionMessage, bp as createSupervisorTaskGateRunner, bq as createTaskLedger, br as decideWorktreeIsolation, bs as deleteUserPreset, bt as deriveSessionOutcome, bu as deriveSessionUsage, bv as escapeHumanPrompt, bw as escapeMessageBody, bx as filterActivities, by as getProcessStatsService, bz as getUserPreset, bA as isClosedTaskStatus, bB as isKnownSessionEndReason, bC as isMessageAllowed, bD as isProviderLimitError, bE as isTerminalActivityState, bF as listUserPresets, bG as loadQuotaStore, bH as loadUserPresets, bI as loadWorktreeIsolation, bJ as matchesMessageFilter, bK as messageFrom, bL as mintMessageId, bM as normalizeCommand, bN as normalizeWorktreeField, bO as parseAnthropicRateLimitHeaders, bP as parseTaskStatus, bQ as parseWindow, bR as parseWorktreeIsolationMode, bS as pickUsageDetail, bT as policyToActivities, bU as policyWatchesSession, bV as prToActivities, bW as projectSessionUsage, bX as readProfileQuota, bY as recordProfileQuota, bZ as renderInboxDigest, b_ as renderSessionMessage, b$ as renderSessionMessages, c0 as resolveRelation, c1 as rollupUsage, c2 as sanitizeLabel, c3 as saveUserPreset, c4 as trackWorktreeProvision, c5 as turnToActivities, c6 as userPresetsPath, c7 as workflowToActivities } from './sessions-BdgU9X6P.js';
6
+ import { M as MessageUrgency, S as SessionEvent, a as SessionEventType, b as SessionEventBus, c as SessionsRegistry, C as CompletionPolicySupervisor, T as TaskLedger, d as SessionDescriptor, e as SessionEndReason, A as ActivityPolicySlice, f as ActivityListFilter, g as ActivityRecord, h as ActivityTurnSession, i as ActivityPrSession, j as ActivityWorkflowRunSlice, P as PrResolvedState, k as ActivityTaskSlice, W as WorktreeProvisioner, l as AgentsMdResolution, U as UserPreset, R as RestartPolicy, m as SandboxAppServeSpec, n as WorktreeField, o as AgentSessionLike, p as UsageReadContext, q as UsageReadResult, r as MessageKind, s as SessionMessage, t as AgentStreamEvent, u as SessionUsage, v as ToolCallRecord, w as PendingPromptView, x as StatsSessionDescriptor, y as ProcRow, z as ProvisionInFlight, B as ProvisionTracker, D as SessionCrashReason, E as UsageRollup, F as RemainingQuotaReader, Q as QuotaReadableProfile, G as UsageSnapshotRecord, H as SessionAwaitingQuestion, I as PolicyRunState, J as PtyFactory, K as WorktreeAutoReclaimer } from './sessions-Cv4XNQXv.js';
7
+ export { L as ActivityKind, N as ActivitySource, O as ActivityState, V as ActivityWaitingOn, X as AnthropicReaderConfig, Y as AnthropicRemainingQuotaReader, Z as AttachPolicyInput, _ as CommandGroupStats, $ as CommitSpec, a0 as DEFAULT_WORKTREE_ISOLATION, a1 as DEFAULT_WORKTREE_PROVISION_CONCURRENCY, a2 as EagerResumeFailReason, a3 as EagerResumeOutcome, a4 as EagerResumeSkipReason, a5 as GateSpec, a6 as HostInfo, a7 as JudgeGateSpec, a8 as LabeledProcessStatsReport, a9 as LabeledSessionStats, aa as MAX_MESSAGE_DATA_BYTES, ab as MESSAGE_KINDS, ac as MESSAGE_PREAMBLE, ad as MESSAGE_TAG, ae as MESSAGE_URGENCIES, af as MessageDeliveryVia, ag as MessageFilter, ah as MessageRelation, ai as MessageTreeNode, aj as NO_OUTPUT_STALL_TURN_ERROR, ak as OnFailSpec, al as OrphanGroup, am as PendingPermission, an as PermissionRespondInput, ao as PermissionRespondResult, ap as PolicyRunStatus, aq as PricingResolver, ar as ProcessDetail, as as ProcessStatsReport, at as ProcessStatsService, au as QuotaStoreFile, av as QuotaStoreOptions, aw as RegisterBrowserInput, ax as RegisterSessionInput, ay as RemainingQuota, az as ResourceStats, aA as ResumeDisabledError, aB as SESSION_END_REASONS, aC as SESSION_ID_ENV, aD as SessionCurrentPhase, aE as SessionKind, aF as SessionMessageFrom, aG as SessionOutcome, aH as SessionOutcomeArtifact, aI as SessionOutcomeCompact, aJ as SessionOutcomeLink, aK as SessionResourceStats, aL as SessionSnapshots, aM as SessionStatus, aN as ShellGateSpec, aO as SpawnAgentInput, aP as SpawnSessionInput, aQ as StatsDetail, aR as StoredProfileQuota, aS as TASK_STATUSES, aT as TaskCaller, aU as TaskChangedEvent, aV as TaskClaimInput, aW as TaskCreateInput, aX as TaskGateOutcome, aY as TaskGateRunner, aZ as TaskLedgerRegistry, a_ as TaskLedgerSessionSlice, a$ as TaskListFilter, b0 as TaskRecord, b1 as TaskStatus, b2 as TaskUpdateInput, b3 as TaskVerification, b4 as TaskVerifySupervisor, b5 as TaskWriteResult, b6 as TokenPricing, b7 as UsageBucket, b8 as UsageComputeInput, b9 as UsageDetail, ba as UsageSource, bb as UserPresetsFile, bc as WORKSPACE_SLUG_ENV, bd as WORKTREE_ISOLATION_ENV, be as WORKTREE_PROVISION_CONCURRENCY_ENV, bf as WorktreeDecision, bg as WorktreeProvisionOutcome, bh as WorktreeProvisionPhase, bi as WorktreeProvisionProgress, bj as WorktreeProvisionRequest, bk as WorktreeRequest, bl as activityCounts, bm as compactOutcome, bn as createProcessStatsService, bo as createSessionMessage, bp as createSupervisorTaskGateRunner, bq as createTaskLedger, br as decideWorktreeIsolation, bs as deleteUserPreset, bt as deriveSessionOutcome, bu as deriveSessionUsage, bv as escapeHumanPrompt, bw as escapeMessageBody, bx as filterActivities, by as getProcessStatsService, bz as getUserPreset, bA as isClosedTaskStatus, bB as isKnownSessionEndReason, bC as isMessageAllowed, bD as isProviderLimitError, bE as isTerminalActivityState, bF as listUserPresets, bG as loadQuotaStore, bH as loadUserPresets, bI as loadWorktreeIsolation, bJ as matchesMessageFilter, bK as messageFrom, bL as mintMessageId, bM as normalizeCommand, bN as normalizeWorktreeField, bO as parseAnthropicRateLimitHeaders, bP as parseTaskStatus, bQ as parseWindow, bR as parseWorktreeIsolationMode, bS as pickUsageDetail, bT as policyToActivities, bU as policyWatchesSession, bV as prToActivities, bW as projectSessionUsage, bX as readProfileQuota, bY as recordProfileQuota, bZ as renderInboxDigest, b_ as renderSessionMessage, b$ as renderSessionMessages, c0 as resolveRelation, c1 as rollupUsage, c2 as sanitizeLabel, c3 as saveUserPreset, c4 as trackWorktreeProvision, c5 as turnToActivities, c6 as userPresetsPath, c7 as workflowToActivities } from './sessions-Cv4XNQXv.js';
8
8
  import { R as RouteSpec, P as Posture, C as ContextProfile, a as CanonicalPosture } from './session-config-BUDM7T1B.js';
9
9
  export { A as AuthMethod, D as DeclaredAdapterMode, E as EffortLevel, S as SessionConfig, c as composeMode, d as decomposeMode } from './session-config-BUDM7T1B.js';
10
10
  import { AcpMcpServer } from '@agentproto/acp';
@@ -936,15 +936,14 @@ interface EnableResult {
936
936
  * A single link to open on a phone (PHONE-PLAN.md P1.2) — the token rides
937
937
  * in a URL *fragment* (`#token=`), never a `?` query string, since
938
938
  * fragments never reach a server (no Referer, no access/proxy log, no CDN
939
- * cache key). Two shapes, chosen by whether this daemon has the
940
- * `@agentik/session-chat` app installed with a UI:
941
- * - installed: `<publicUrl>/apps/@agentik/session-chat/ui#token=<t>` —
942
- * the phone lands directly in Control Center.
943
- * - not installed: `https://cli.agentproto.sh/panel#daemon=<publicUrl>&token=<t>`
944
- * — the hosted panel, which speaks the same fragment shape (see
945
- * agentproto/cli-site's `readFragmentConnection`).
946
- * Only emitted when exposing the gateway (same condition as `bearerToken`)
947
- * — a passthrough tunnel has no daemon UI to link to.
939
+ * cache key): `<publicUrl>/apps/@agentik/session-chat/ui#token=<t>`, so
940
+ * the phone lands directly in Control Center. Only emitted when exposing
941
+ * the gateway (same condition as `bearerToken`) — a passthrough tunnel has
942
+ * no daemon UI to link to — AND when this daemon has the
943
+ * `@agentik/session-chat` app installed with a UI: it is the only daemon
944
+ * page that reads the `#token=` fragment, and there is no hosted panel to
945
+ * fall back to (the hosted cli-site panel is retired; phones pair
946
+ * through rendezvous, `agentproto pair offer`).
948
947
  */
949
948
  phoneUrl?: string;
950
949
  provider: "quick";