@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.
- package/dist/app-catalog.d.ts +162 -0
- package/dist/app-catalog.mjs +250 -0
- package/dist/app-catalog.mjs.map +1 -0
- package/dist/app-registry-B64vZEaO.d.ts +35 -0
- package/dist/app-remote-install.d.ts +97 -0
- package/dist/app-remote-install.mjs +281 -0
- package/dist/app-remote-install.mjs.map +1 -0
- package/dist/first-party-catalog-gen.d.ts +47 -0
- package/dist/first-party-catalog-gen.mjs +116 -0
- package/dist/first-party-catalog-gen.mjs.map +1 -0
- package/dist/index.d.ts +10 -11
- package/dist/index.mjs +389 -30
- package/dist/index.mjs.map +1 -1
- package/dist/{sessions-BdgU9X6P.d.ts → sessions-Cv4XNQXv.d.ts} +19 -2
- package/dist/user-presets.d.ts +1 -1
- package/docs/mcp-tools/agent_start.md +5 -2
- package/package.json +34 -18
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { A as AppSource } from './app-registry-B64vZEaO.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* App catalog — a static, hand-curated `~/.agentproto/app-catalog.json`
|
|
6
|
+
* listing apps a user could `app_install`, independent of whether any are
|
|
7
|
+
* installed yet, plus optional remote catalog `sources` (each an HTTP URL
|
|
8
|
+
* returning `{ entries: AppCatalogEntry[] }`). Read-only from the daemon's
|
|
9
|
+
* side; `app_catalog` (app-tools.ts) merges local entries, remote entries and
|
|
10
|
+
* `AppRegistry.listApps()` to report installed/hasUi status.
|
|
11
|
+
*
|
|
12
|
+
* Sources (see `resolveCatalogSources`): the default public catalog
|
|
13
|
+
* (`DEFAULT_CATALOG_SOURCE_URL`, config `catalog.defaultSource`) comes
|
|
14
|
+
* first, then config `catalog.sources` — which win over (replace) `sources`
|
|
15
|
+
* in the catalog file when set. Extra sources ADD to the default one. Remote
|
|
16
|
+
* catalogs follow the `app-catalog/v1` format; the last good copy of each is
|
|
17
|
+
* cached on disk so `app_catalog` keeps working offline.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
type AppPlacement = "local" | "box" | "any" | "split";
|
|
21
|
+
type AppCatalogTier = "git" | "bundle" | "hosted";
|
|
22
|
+
interface AppCatalogLicense {
|
|
23
|
+
kind: "free" | "paid" | "private";
|
|
24
|
+
url?: string;
|
|
25
|
+
}
|
|
26
|
+
interface AppCatalogRequires {
|
|
27
|
+
browser?: boolean;
|
|
28
|
+
fs?: boolean;
|
|
29
|
+
secrets?: string[];
|
|
30
|
+
}
|
|
31
|
+
/** Remote catalog entry (`app-catalog/v1`). `source` lets a store call
|
|
32
|
+
* `app_install {url, ref, subdir, sha}` (git) or `app_install {url, sha256}`
|
|
33
|
+
* (`.agentapp`). Every field but `appId` + `source` is optional, so
|
|
34
|
+
* pre-v1 catalogs (`{ entries }` only) keep validating. */
|
|
35
|
+
interface AppCatalogEntry {
|
|
36
|
+
appId: string;
|
|
37
|
+
name?: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
category?: string;
|
|
40
|
+
source: AppSource;
|
|
41
|
+
placement?: AppPlacement;
|
|
42
|
+
icon?: string;
|
|
43
|
+
version?: string;
|
|
44
|
+
tier?: AppCatalogTier;
|
|
45
|
+
publisher?: string;
|
|
46
|
+
license?: AppCatalogLicense;
|
|
47
|
+
minAgentprotoVersion?: string;
|
|
48
|
+
requires?: AppCatalogRequires;
|
|
49
|
+
featured?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** Entry of the local `app-catalog.json` `apps` array: a directory on disk. */
|
|
52
|
+
interface AppCatalogFileEntry {
|
|
53
|
+
readonly appId: string;
|
|
54
|
+
readonly name?: string;
|
|
55
|
+
readonly description?: string;
|
|
56
|
+
readonly dir: string;
|
|
57
|
+
readonly category?: string;
|
|
58
|
+
}
|
|
59
|
+
interface AppCatalogSource {
|
|
60
|
+
readonly url: string;
|
|
61
|
+
}
|
|
62
|
+
interface AppCatalogFile {
|
|
63
|
+
readonly apps: readonly AppCatalogFileEntry[];
|
|
64
|
+
readonly sources?: readonly AppCatalogSource[];
|
|
65
|
+
}
|
|
66
|
+
declare const AppCatalogEntrySchema: z.ZodType<AppCatalogEntry>;
|
|
67
|
+
/** `schema` tag of a v1 catalog document. */
|
|
68
|
+
declare const CATALOG_SCHEMA_V1 = "app-catalog/v1";
|
|
69
|
+
/**
|
|
70
|
+
* Default public catalog, queried by `app_catalog` unless config
|
|
71
|
+
* `catalog.defaultSource` is `false` (a URL string replaces it). The file is
|
|
72
|
+
* a static `apps.json` served by the agentproto/site repo
|
|
73
|
+
* (`public/catalog/v1/apps.json`). Bundles it references are GitHub Release
|
|
74
|
+
* assets of the public `agentproto/apps` repo. A failing default source is
|
|
75
|
+
* never fatal: `app_catalog` falls back to the last good copy cached on
|
|
76
|
+
* disk, then to the embedded first-party list (`first-party-catalog.ts`).
|
|
77
|
+
*/
|
|
78
|
+
declare const DEFAULT_CATALOG_SOURCE_URL = "https://agentproto.sh/catalog/v1/apps.json";
|
|
79
|
+
declare function defaultAppCatalogPath(): string;
|
|
80
|
+
/**
|
|
81
|
+
* Read + parse the catalog file at `path` (default `~/.agentproto/app-catalog.json`).
|
|
82
|
+
* Tolerates a missing file (returns an empty catalog) and a malformed one
|
|
83
|
+
* (unreadable JSON, non-array `apps`, entries missing `appId`/`dir`, and
|
|
84
|
+
* `sources` without a string `url` are dropped) — never throws.
|
|
85
|
+
*/
|
|
86
|
+
declare function loadAppCatalogFile(path?: string): Promise<AppCatalogFile>;
|
|
87
|
+
type CatalogSourceOrigin = "default" | "config" | "file";
|
|
88
|
+
interface ResolvedCatalogSource extends AppCatalogSource {
|
|
89
|
+
readonly origin: CatalogSourceOrigin;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Remote sources `app_catalog` queries, in precedence order (first wins on
|
|
93
|
+
* an `appId` collision): the default source (unless `defaultSource: false`;
|
|
94
|
+
* a non-empty string replaces its URL), then config `catalog.sources` when
|
|
95
|
+
* set (even empty), else the catalog file's `sources`. Config/file sources
|
|
96
|
+
* ADD to the default one, they never replace it. Duplicate URLs are dropped.
|
|
97
|
+
*/
|
|
98
|
+
declare function resolveCatalogSources(fileSources: readonly AppCatalogSource[] | undefined, config: {
|
|
99
|
+
sources?: unknown;
|
|
100
|
+
defaultSource?: unknown;
|
|
101
|
+
} | undefined): ResolvedCatalogSource[];
|
|
102
|
+
/** One source's outcome, in the order sources were passed. */
|
|
103
|
+
interface CatalogSourceResult {
|
|
104
|
+
url: string;
|
|
105
|
+
entries: AppCatalogEntry[];
|
|
106
|
+
/** True when `entries` come from the disk cache because the live fetch failed. */
|
|
107
|
+
stale: boolean;
|
|
108
|
+
/** When the entries were fetched (live or cached), ISO-8601. */
|
|
109
|
+
fetchedAt?: string;
|
|
110
|
+
/** False when the source yielded nothing at all (fetch failed, no cache). */
|
|
111
|
+
ok: boolean;
|
|
112
|
+
}
|
|
113
|
+
interface RemoteCatalogResult {
|
|
114
|
+
entries: AppCatalogEntry[];
|
|
115
|
+
warnings: string[];
|
|
116
|
+
bySource: CatalogSourceResult[];
|
|
117
|
+
}
|
|
118
|
+
interface RemoteCatalogClientOptions {
|
|
119
|
+
/** Default 5 minutes. */
|
|
120
|
+
ttlMs?: number;
|
|
121
|
+
/** Per-source request timeout. Default 5000. */
|
|
122
|
+
timeoutMs?: number;
|
|
123
|
+
fetchImpl?: typeof fetch;
|
|
124
|
+
now?: () => number;
|
|
125
|
+
/** Directory for the last-good copy of each source (`<sha1(url)>.json`).
|
|
126
|
+
* Absent = no disk cache (in-memory TTL cache only). */
|
|
127
|
+
cacheDir?: string;
|
|
128
|
+
}
|
|
129
|
+
interface RemoteCatalogClient {
|
|
130
|
+
/** Fetch + validate every source (in order, in parallel). A failing source
|
|
131
|
+
* never throws: it yields a `warnings` line and either its last cached
|
|
132
|
+
* copy (`stale`) or no entries. Live results are cached per URL in memory
|
|
133
|
+
* for `ttlMs` and on disk (when `cacheDir` is set); `refresh` bypasses the
|
|
134
|
+
* in-memory cache. */
|
|
135
|
+
fetchSources(sources: readonly AppCatalogSource[], opts?: {
|
|
136
|
+
refresh?: boolean;
|
|
137
|
+
}): Promise<RemoteCatalogResult>;
|
|
138
|
+
}
|
|
139
|
+
declare const DEFAULT_REMOTE_CATALOG_TTL_MS: number;
|
|
140
|
+
declare const DEFAULT_REMOTE_CATALOG_TIMEOUT_MS = 5000;
|
|
141
|
+
/** Disk-cache file for `url` under `cacheDir`. */
|
|
142
|
+
declare function catalogCachePath(cacheDir: string, url: string): string;
|
|
143
|
+
declare function createRemoteCatalogClient(options?: RemoteCatalogClientOptions): RemoteCatalogClient;
|
|
144
|
+
/**
|
|
145
|
+
* Compare dotted numeric versions (`1.2.10` > `1.2.9`); a pre-release suffix
|
|
146
|
+
* (`1.2.0-beta`) ranks below the same core. Returns -1 | 0 | 1, or
|
|
147
|
+
* `undefined` when either side isn't `N.N.N`-shaped.
|
|
148
|
+
*/
|
|
149
|
+
declare function compareCatalogVersions(a: string, b: string): number | undefined;
|
|
150
|
+
/**
|
|
151
|
+
* Is catalog `entry` an update for an installed app (recorded `source`,
|
|
152
|
+
* APP.md `version`)? Only for the same source kind, only when the pin
|
|
153
|
+
* differs (bundle sha256 / git commit), and never when the entry's version
|
|
154
|
+
* is LOWER than the installed one — a catalog listing an older release is
|
|
155
|
+
* not an update.
|
|
156
|
+
*/
|
|
157
|
+
declare function isCatalogUpdate(installed: {
|
|
158
|
+
readonly source?: AppSource;
|
|
159
|
+
readonly version?: string;
|
|
160
|
+
}, entry: AppCatalogEntry): boolean;
|
|
161
|
+
|
|
162
|
+
export { type AppCatalogEntry, AppCatalogEntrySchema, type AppCatalogFile, type AppCatalogFileEntry, type AppCatalogLicense, type AppCatalogRequires, type AppCatalogSource, type AppCatalogTier, type AppPlacement, AppSource, CATALOG_SCHEMA_V1, type CatalogSourceOrigin, type CatalogSourceResult, DEFAULT_CATALOG_SOURCE_URL, DEFAULT_REMOTE_CATALOG_TIMEOUT_MS, DEFAULT_REMOTE_CATALOG_TTL_MS, type RemoteCatalogClient, type RemoteCatalogClientOptions, type RemoteCatalogResult, type ResolvedCatalogSource, catalogCachePath, compareCatalogVersions, createRemoteCatalogClient, defaultAppCatalogPath, isCatalogUpdate, loadAppCatalogFile, resolveCatalogSources };
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { readFile, mkdir, writeFile, rename } from 'fs/promises';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { join } from '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
|
+
var AppCatalogEntrySchema = 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
|
+
var RemoteCatalogSchema = z.object({
|
|
51
|
+
schema: z.string().optional(),
|
|
52
|
+
generatedAt: z.string().optional(),
|
|
53
|
+
entries: z.array(z.unknown())
|
|
54
|
+
});
|
|
55
|
+
var DEFAULT_CATALOG_SOURCE_URL = "https://agentproto.sh/catalog/v1/apps.json";
|
|
56
|
+
var EMPTY_CATALOG = { apps: [] };
|
|
57
|
+
function defaultAppCatalogPath() {
|
|
58
|
+
return join(homedir(), ".agentproto", "app-catalog.json");
|
|
59
|
+
}
|
|
60
|
+
async function loadAppCatalogFile(path) {
|
|
61
|
+
const catalogPath = path ?? defaultAppCatalogPath();
|
|
62
|
+
let raw;
|
|
63
|
+
try {
|
|
64
|
+
raw = await readFile(catalogPath, "utf8");
|
|
65
|
+
} catch {
|
|
66
|
+
return EMPTY_CATALOG;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(raw);
|
|
70
|
+
const sources = parseSources(parsed.sources);
|
|
71
|
+
const apps = Array.isArray(parsed.apps) ? parsed.apps.filter((e) => {
|
|
72
|
+
if (typeof e !== "object" || e === null) return false;
|
|
73
|
+
const rec = e;
|
|
74
|
+
return typeof rec.appId === "string" && typeof rec.dir === "string";
|
|
75
|
+
}) : [];
|
|
76
|
+
return sources.length > 0 ? { apps, sources } : { apps };
|
|
77
|
+
} catch {
|
|
78
|
+
return EMPTY_CATALOG;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function parseSources(raw) {
|
|
82
|
+
if (!Array.isArray(raw)) return [];
|
|
83
|
+
const out = [];
|
|
84
|
+
for (const s of raw) {
|
|
85
|
+
if (typeof s !== "object" || s === null) continue;
|
|
86
|
+
const url = s.url;
|
|
87
|
+
if (typeof url === "string" && url.length > 0) out.push({ url });
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
function resolveCatalogSources(fileSources, config) {
|
|
92
|
+
const out = [];
|
|
93
|
+
const seen = /* @__PURE__ */ new Set();
|
|
94
|
+
const push = (url, origin) => {
|
|
95
|
+
if (seen.has(url)) return;
|
|
96
|
+
seen.add(url);
|
|
97
|
+
out.push({ url, origin });
|
|
98
|
+
};
|
|
99
|
+
const def = config?.defaultSource;
|
|
100
|
+
if (def !== false) {
|
|
101
|
+
push(typeof def === "string" && def.length > 0 ? def : DEFAULT_CATALOG_SOURCE_URL, "default");
|
|
102
|
+
}
|
|
103
|
+
if (Array.isArray(config?.sources)) {
|
|
104
|
+
for (const s of parseSources(config.sources)) push(s.url, "config");
|
|
105
|
+
} else {
|
|
106
|
+
for (const s of fileSources ?? []) push(s.url, "file");
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
var DEFAULT_REMOTE_CATALOG_TTL_MS = 5 * 6e4;
|
|
111
|
+
var DEFAULT_REMOTE_CATALOG_TIMEOUT_MS = 5e3;
|
|
112
|
+
function catalogCachePath(cacheDir, url) {
|
|
113
|
+
return join(cacheDir, `${createHash("sha1").update(url).digest("hex")}.json`);
|
|
114
|
+
}
|
|
115
|
+
function createRemoteCatalogClient(options = {}) {
|
|
116
|
+
const ttlMs = options.ttlMs ?? DEFAULT_REMOTE_CATALOG_TTL_MS;
|
|
117
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_REMOTE_CATALOG_TIMEOUT_MS;
|
|
118
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
119
|
+
const now = options.now ?? Date.now;
|
|
120
|
+
const cacheDir = options.cacheDir;
|
|
121
|
+
const cache = /* @__PURE__ */ new Map();
|
|
122
|
+
async function readDiskCache(url) {
|
|
123
|
+
if (cacheDir === void 0) return void 0;
|
|
124
|
+
try {
|
|
125
|
+
const raw = JSON.parse(await readFile(catalogCachePath(cacheDir, url), "utf8"));
|
|
126
|
+
if (raw.url !== url || !Array.isArray(raw.entries) || typeof raw.fetchedAt !== "string") return void 0;
|
|
127
|
+
const entries = [];
|
|
128
|
+
for (const e of raw.entries) {
|
|
129
|
+
const parsed = AppCatalogEntrySchema.safeParse(e);
|
|
130
|
+
if (parsed.success) entries.push(parsed.data);
|
|
131
|
+
}
|
|
132
|
+
return { entries, fetchedAt: raw.fetchedAt };
|
|
133
|
+
} catch {
|
|
134
|
+
return void 0;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function writeDiskCache(url, entries, fetchedAt) {
|
|
138
|
+
if (cacheDir === void 0) return;
|
|
139
|
+
try {
|
|
140
|
+
await mkdir(cacheDir, { recursive: true });
|
|
141
|
+
const file = catalogCachePath(cacheDir, url);
|
|
142
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
143
|
+
const body = { url, fetchedAt, entries };
|
|
144
|
+
await writeFile(tmp, JSON.stringify(body, null, 2) + "\n", "utf8");
|
|
145
|
+
await rename(tmp, file);
|
|
146
|
+
} catch {
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function failed(url, warning) {
|
|
150
|
+
const cached = await readDiskCache(url);
|
|
151
|
+
if (cached === void 0) return { url, entries: [], warnings: [warning], stale: false, ok: false };
|
|
152
|
+
return {
|
|
153
|
+
url,
|
|
154
|
+
entries: cached.entries,
|
|
155
|
+
warnings: [`${warning}; using the copy cached at ${cached.fetchedAt}`],
|
|
156
|
+
stale: true,
|
|
157
|
+
fetchedAt: cached.fetchedAt,
|
|
158
|
+
ok: true
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
async function fetchOne(url) {
|
|
162
|
+
let body;
|
|
163
|
+
try {
|
|
164
|
+
const res = await fetchImpl(url, {
|
|
165
|
+
headers: { Accept: "application/json" },
|
|
166
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
167
|
+
});
|
|
168
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
169
|
+
body = await res.json();
|
|
170
|
+
} catch (err) {
|
|
171
|
+
const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
|
|
172
|
+
const reason = timedOut ? `timed out after ${timeoutMs}ms` : err instanceof Error ? err.message : String(err);
|
|
173
|
+
return failed(url, `catalog source ${url}: ${reason}`);
|
|
174
|
+
}
|
|
175
|
+
const shape = RemoteCatalogSchema.safeParse(body);
|
|
176
|
+
if (!shape.success) {
|
|
177
|
+
return failed(url, `catalog source ${url}: expected { entries: [...] }`);
|
|
178
|
+
}
|
|
179
|
+
const entries = [];
|
|
180
|
+
const warnings = [];
|
|
181
|
+
if (shape.data.schema !== void 0 && shape.data.schema !== CATALOG_SCHEMA_V1) {
|
|
182
|
+
warnings.push(
|
|
183
|
+
`catalog source ${url}: unknown schema "${shape.data.schema}" \u2014 reading its entries as ${CATALOG_SCHEMA_V1}`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
shape.data.entries.forEach((raw, i) => {
|
|
187
|
+
const parsed = AppCatalogEntrySchema.safeParse(raw);
|
|
188
|
+
if (parsed.success) entries.push(parsed.data);
|
|
189
|
+
else warnings.push(`catalog source ${url}: entries[${i}] invalid (${parsed.error.issues[0]?.message ?? "schema mismatch"})`);
|
|
190
|
+
});
|
|
191
|
+
const fetchedAt = new Date(now()).toISOString();
|
|
192
|
+
await writeDiskCache(url, entries, fetchedAt);
|
|
193
|
+
return { url, entries, warnings, stale: false, fetchedAt, ok: true };
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
async fetchSources(sources, opts = {}) {
|
|
197
|
+
const results = await Promise.all(
|
|
198
|
+
sources.map(async ({ url }) => {
|
|
199
|
+
const hit = cache.get(url);
|
|
200
|
+
if (!opts.refresh && hit && now() - hit.at < ttlMs) return hit.result;
|
|
201
|
+
const result = await fetchOne(url);
|
|
202
|
+
if (!result.stale && (result.entries.length > 0 || result.warnings.length === 0)) {
|
|
203
|
+
cache.set(url, { at: now(), result });
|
|
204
|
+
} else {
|
|
205
|
+
cache.delete(url);
|
|
206
|
+
}
|
|
207
|
+
return result;
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
return {
|
|
211
|
+
entries: results.flatMap((r) => r.entries),
|
|
212
|
+
warnings: results.flatMap((r) => r.warnings),
|
|
213
|
+
bySource: results.map(({ warnings: _warnings, ...rest }) => rest)
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function compareCatalogVersions(a, b) {
|
|
219
|
+
const parse = (v) => {
|
|
220
|
+
const m = /^v?(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(v.trim());
|
|
221
|
+
return m ? { core: [Number(m[1]), Number(m[2]), Number(m[3])], pre: m[4] !== void 0 } : void 0;
|
|
222
|
+
};
|
|
223
|
+
const pa = parse(a);
|
|
224
|
+
const pb = parse(b);
|
|
225
|
+
if (pa === void 0 || pb === void 0) return void 0;
|
|
226
|
+
for (let i = 0; i < 3; i++) {
|
|
227
|
+
const d = pa.core[i] - pb.core[i];
|
|
228
|
+
if (d !== 0) return d < 0 ? -1 : 1;
|
|
229
|
+
}
|
|
230
|
+
if (pa.pre === pb.pre) return 0;
|
|
231
|
+
return pa.pre ? -1 : 1;
|
|
232
|
+
}
|
|
233
|
+
function isCatalogUpdate(installed, entry) {
|
|
234
|
+
const s = installed.source;
|
|
235
|
+
if (s === void 0 || s.kind === "local" || s.kind !== entry.source.kind) return false;
|
|
236
|
+
const offered = entry.source;
|
|
237
|
+
const differs = s.kind === "agentapp" ? offered.kind === "agentapp" && offered.sha256.toLowerCase() !== s.sha256.toLowerCase() : offered.kind === "git" && offered.sha.toLowerCase() !== s.sha.toLowerCase();
|
|
238
|
+
if (!differs) return false;
|
|
239
|
+
const installedVersion = s.kind === "agentapp" ? s.version : installed.version;
|
|
240
|
+
const offeredVersion = offered.kind === "agentapp" ? offered.version : entry.version;
|
|
241
|
+
if (installedVersion !== void 0 && offeredVersion !== void 0) {
|
|
242
|
+
const cmp = compareCatalogVersions(offeredVersion, installedVersion);
|
|
243
|
+
if (cmp !== void 0 && cmp < 0) return false;
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export { AppCatalogEntrySchema, CATALOG_SCHEMA_V1, DEFAULT_CATALOG_SOURCE_URL, DEFAULT_REMOTE_CATALOG_TIMEOUT_MS, DEFAULT_REMOTE_CATALOG_TTL_MS, catalogCachePath, compareCatalogVersions, createRemoteCatalogClient, defaultAppCatalogPath, isCatalogUpdate, loadAppCatalogFile, resolveCatalogSources };
|
|
249
|
+
//# sourceMappingURL=app-catalog.mjs.map
|
|
250
|
+
//# sourceMappingURL=app-catalog.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/app-catalog.ts"],"names":[],"mappings":"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","file":"app-catalog.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"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persisted installed-app + app-run state for the `app_*` daemon verbs
|
|
3
|
+
* (app-tools.ts). Mirrors workflow-runner.ts's persistence pattern exactly:
|
|
4
|
+
* an in-memory store, write-tmp + rename on every mutation, and persistence
|
|
5
|
+
* OFF by default so unit tests never touch ~/.agentproto/.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** The catalog an app was installed from (`app_install {catalogUrl}`):
|
|
9
|
+
* `app_updates` / `app_resync` follow THAT catalog's entry for `appId`,
|
|
10
|
+
* never another source's. */
|
|
11
|
+
interface AppCatalogRef {
|
|
12
|
+
url: string;
|
|
13
|
+
appId: string;
|
|
14
|
+
}
|
|
15
|
+
/** Where an installed app came from — the pin `app_resync` compares against.
|
|
16
|
+
* Absent on a record (legacy / `{dir}` install) means `{ kind: "local" }`. */
|
|
17
|
+
type AppSource = {
|
|
18
|
+
kind: "local";
|
|
19
|
+
} | {
|
|
20
|
+
kind: "git";
|
|
21
|
+
url: string;
|
|
22
|
+
ref?: string;
|
|
23
|
+
sha: string;
|
|
24
|
+
subdir?: string;
|
|
25
|
+
catalogId?: AppCatalogRef;
|
|
26
|
+
} | {
|
|
27
|
+
kind: "agentapp";
|
|
28
|
+
url: string;
|
|
29
|
+
sha256: string;
|
|
30
|
+
version: string;
|
|
31
|
+
size?: number;
|
|
32
|
+
catalogId?: AppCatalogRef;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type { AppSource as A };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { A as AppSource } from './app-registry-B64vZEaO.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Remote sources for `app_install` / `app_resync`: git URLs and `.agentapp`
|
|
6
|
+
* bundles (URL or local file). Pure staging + swap — no registry, no
|
|
7
|
+
* validation: `app-tools.ts` runs `performInstall` on whatever this hands
|
|
8
|
+
* back, so the same manifest checks apply to every source.
|
|
9
|
+
*
|
|
10
|
+
* Remote-installed apps live under `<appsDir>/<slug>` (the daemon state dir's
|
|
11
|
+
* `apps/`), never in the user's cwd. A (re)install stages into a hidden
|
|
12
|
+
* sibling temp dir first and swaps it in, so a failed clone / digest mismatch
|
|
13
|
+
* / manifest error leaves the previous install untouched.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
declare const DOWNLOAD_TIMEOUT_MS = 30000;
|
|
17
|
+
declare const DOWNLOAD_MAX_BYTES: number;
|
|
18
|
+
/** `app_install` input: exactly one of `{dir}` | `{url, ref?, subdir?, sha?, sha256?, allowBuild?}` | `{file, sha256?}`. */
|
|
19
|
+
declare const appInstallInputSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
20
|
+
dir: z.ZodString;
|
|
21
|
+
dataDir: z.ZodOptional<z.ZodString>;
|
|
22
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
23
|
+
url: z.ZodString;
|
|
24
|
+
ref: z.ZodOptional<z.ZodString>;
|
|
25
|
+
subdir: z.ZodOptional<z.ZodString>;
|
|
26
|
+
sha: z.ZodOptional<z.ZodString>;
|
|
27
|
+
sha256: z.ZodOptional<z.ZodString>;
|
|
28
|
+
allowBuild: z.ZodOptional<z.ZodBoolean>;
|
|
29
|
+
catalogUrl: z.ZodOptional<z.ZodString>;
|
|
30
|
+
dataDir: z.ZodOptional<z.ZodString>;
|
|
31
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
32
|
+
file: z.ZodString;
|
|
33
|
+
sha256: z.ZodOptional<z.ZodString>;
|
|
34
|
+
catalogUrl: z.ZodOptional<z.ZodString>;
|
|
35
|
+
dataDir: z.ZodOptional<z.ZodString>;
|
|
36
|
+
}, z.core.$strict>]>;
|
|
37
|
+
type AppInstallInput = z.infer<typeof appInstallInputSchema>;
|
|
38
|
+
declare const APP_INSTALL_EXCLUSIVE_ERROR: string;
|
|
39
|
+
/** A `{url}` ending in `.agentapp` (ignoring query/fragment) is a bundle, anything else is git. */
|
|
40
|
+
declare function isAgentappUrl(url: string): boolean;
|
|
41
|
+
interface StagedApp {
|
|
42
|
+
/** Directory name under `appsDir` the staged tree will be swapped into. */
|
|
43
|
+
readonly slug: string;
|
|
44
|
+
/** Hidden sibling of the final dir holding the staged tree. */
|
|
45
|
+
readonly tmpDir: string;
|
|
46
|
+
/** Path of the app dir inside the staged tree (`subdir` for git, `""` otherwise). */
|
|
47
|
+
readonly subdir: string;
|
|
48
|
+
readonly source: AppSource;
|
|
49
|
+
/** Remove the staged tree (idempotent) — call when the stage is not swapped in. */
|
|
50
|
+
discard(): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
/** `<repo-name>[-<subdir-slug>]` — repo name is the URL's last path segment minus `.git`. */
|
|
53
|
+
declare function gitSlug(url: string, subdir?: string): string;
|
|
54
|
+
/** Shallow-clone `url` into a temp sibling of `<appsDir>/<slug>` and pin the commit. */
|
|
55
|
+
declare function stageGitApp(input: {
|
|
56
|
+
appsDir: string;
|
|
57
|
+
url: string;
|
|
58
|
+
ref?: string;
|
|
59
|
+
subdir?: string;
|
|
60
|
+
expectedSha?: string;
|
|
61
|
+
}): Promise<StagedApp>;
|
|
62
|
+
/** Stream `url` (http(s):// or file://) to `destFile` under the timeout + size
|
|
63
|
+
* cap. Exported for `agentproto catalog verify`, which applies the same
|
|
64
|
+
* download caps as `app_install`. */
|
|
65
|
+
declare function downloadTo(url: string, destFile: string): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Fetch (or read) a `.agentapp`, verify + unpack it into a temp sibling of
|
|
68
|
+
* `<appsDir>/<safe-id>`. A digest mismatch (or any unpack refusal) throws and
|
|
69
|
+
* leaves nothing behind.
|
|
70
|
+
*/
|
|
71
|
+
declare function stageAgentApp(input: {
|
|
72
|
+
appsDir: string;
|
|
73
|
+
url?: string;
|
|
74
|
+
file?: string;
|
|
75
|
+
expectedSha256?: string;
|
|
76
|
+
}): Promise<StagedApp>;
|
|
77
|
+
interface AppSwap {
|
|
78
|
+
/** Drop the previous tree — the new one is final. */
|
|
79
|
+
commit(): Promise<void>;
|
|
80
|
+
/** Put the previous tree (and its data) back, discarding the new one. */
|
|
81
|
+
rollback(): Promise<void>;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Swap the staged tree in at `target`, keeping the previous tree aside until
|
|
85
|
+
* the caller commits. When the previous install's `dataDir` lived inside the
|
|
86
|
+
* old tree (the `<dir>/data` default), it is carried into the new tree so a
|
|
87
|
+
* re-install never loses app data.
|
|
88
|
+
*/
|
|
89
|
+
declare function swapInStaged(input: {
|
|
90
|
+
tmpDir: string;
|
|
91
|
+
target: string;
|
|
92
|
+
previousDataDir?: string;
|
|
93
|
+
}): Promise<AppSwap>;
|
|
94
|
+
/** The commit `url`'s `ref` (default `HEAD`) currently points at, via `git ls-remote`. */
|
|
95
|
+
declare function remoteGitSha(url: string, ref?: string): Promise<string>;
|
|
96
|
+
|
|
97
|
+
export { APP_INSTALL_EXCLUSIVE_ERROR, type AppInstallInput, type AppSwap, DOWNLOAD_MAX_BYTES, DOWNLOAD_TIMEOUT_MS, type StagedApp, appInstallInputSchema, downloadTo, gitSlug, isAgentappUrl, remoteGitSha, stageAgentApp, stageGitApp, swapInStaged };
|