@agentproto/runtime 1.1.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,115 @@
1
+ import { createServer, request } from 'http';
2
+
3
+ /**
4
+ * @agentproto/runtime v0.1.0-alpha
5
+ * Long-running gateway: MCP server + HTTP transport + HEARTBEAT autonomy + conversation persistence over a workspace dir.
6
+ */
7
+
8
+ function createTelegramProxy(opts) {
9
+ const log = opts.log ?? (() => {
10
+ });
11
+ let server = null;
12
+ return {
13
+ async start() {
14
+ server = createServer((req, res) => {
15
+ handleProxyRequest(req, res, opts, log);
16
+ });
17
+ return new Promise((resolve, reject) => {
18
+ server.once("error", reject);
19
+ server.listen(opts.listenPort, "127.0.0.1", () => {
20
+ const addr = server.address();
21
+ const port = typeof addr === "object" && addr !== null ? addr.port : opts.listenPort;
22
+ const url = `http://127.0.0.1:${port}`;
23
+ log(`[telegram-proxy] listening on ${url}`);
24
+ resolve({ url, port });
25
+ });
26
+ });
27
+ },
28
+ async stop() {
29
+ if (!server) return;
30
+ await new Promise((resolve, reject) => {
31
+ server.close((err) => {
32
+ if (err) reject(err);
33
+ else resolve();
34
+ });
35
+ });
36
+ server = null;
37
+ }
38
+ };
39
+ }
40
+ function handleProxyRequest(req, res, opts, log) {
41
+ if (req.method !== "POST") {
42
+ res.writeHead(405, { "content-type": "application/json" });
43
+ res.end(JSON.stringify({ error: "method_not_allowed" }));
44
+ return;
45
+ }
46
+ const rawUrl = req.url ?? "/";
47
+ const normalized = normalizePath(rawUrl);
48
+ if (!normalized.startsWith("/inbound/")) {
49
+ res.writeHead(404, { "content-type": "application/json" });
50
+ res.end(JSON.stringify({ error: "not_found" }));
51
+ return;
52
+ }
53
+ const targetUrl = new URL(normalized, opts.targetBaseUrl);
54
+ const queryIdx = rawUrl.indexOf("?");
55
+ if (queryIdx >= 0) {
56
+ targetUrl.search = rawUrl.slice(queryIdx);
57
+ }
58
+ log(`[telegram-proxy] ${req.method} ${rawUrl} -> ${targetUrl.toString()}`);
59
+ const headers = {};
60
+ for (const [key, value] of Object.entries(req.headers)) {
61
+ if (value !== void 0) {
62
+ headers[key] = value;
63
+ }
64
+ }
65
+ headers.host = targetUrl.host;
66
+ const proxyReq = request(
67
+ {
68
+ hostname: targetUrl.hostname,
69
+ port: targetUrl.port || (targetUrl.protocol === "https:" ? 443 : 80),
70
+ path: targetUrl.pathname + targetUrl.search,
71
+ method: req.method,
72
+ headers
73
+ },
74
+ (proxyRes) => {
75
+ res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
76
+ proxyRes.pipe(res);
77
+ }
78
+ );
79
+ proxyReq.on("error", (err) => {
80
+ log(`[telegram-proxy] upstream error: ${err.message}`);
81
+ if (!res.headersSent) {
82
+ res.writeHead(502, { "content-type": "application/json" });
83
+ res.end(JSON.stringify({ error: "bad_gateway" }));
84
+ }
85
+ });
86
+ req.pipe(proxyReq);
87
+ }
88
+ function normalizePath(urlPath) {
89
+ const pathPart = urlPath.split("?")[0] ?? urlPath;
90
+ let decoded;
91
+ try {
92
+ decoded = decodeURIComponent(pathPart);
93
+ } catch {
94
+ return pathPart;
95
+ }
96
+ const hasTrailingSlash = decoded.endsWith("/") && decoded !== "/";
97
+ const segments = decoded.split("/").filter((s) => s !== "" && s !== ".");
98
+ const normalized = [];
99
+ for (const seg of segments) {
100
+ if (seg === "..") {
101
+ normalized.pop();
102
+ } else {
103
+ normalized.push(seg);
104
+ }
105
+ }
106
+ let result = "/" + normalized.join("/");
107
+ if (hasTrailingSlash && !result.endsWith("/")) {
108
+ result += "/";
109
+ }
110
+ return result;
111
+ }
112
+
113
+ export { createTelegramProxy };
114
+ //# sourceMappingURL=telegram-proxy.mjs.map
115
+ //# sourceMappingURL=telegram-proxy.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/telegram-proxy.ts"],"names":[],"mappings":"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","file":"telegram-proxy.mjs","sourcesContent":["/**\n * Narrow public ingress proxy for Telegram (and other) bot webhooks.\n *\n * SECURITY: the daemon's port (default 18790) exposes the entire control\n * plane — `session_list`, `agent_start`, `agent_kill`, etc. A bare public\n * tunnel to that port would expose full daemon control to anyone who finds\n * the URL. This proxy listens on its own port and forwards ONLY\n * `POST /inbound/*` to the daemon. Every other path or method is rejected\n * with 404/405.\n */\n\nimport { createServer, request, type IncomingMessage, type Server, type ServerResponse } from \"node:http\"\n\nexport interface TelegramProxyOptions {\n targetBaseUrl: string\n listenPort: number\n log?: (msg: string) => void\n}\n\nexport interface TelegramProxy {\n start(): Promise<{ url: string; port: number }>\n stop(): Promise<void>\n}\n\n/**\n * Create a narrow reverse-proxy that forwards ONLY `POST /inbound/*`\n * to a target base URL. Every other path or method is rejected.\n *\n * SECURITY: the URL path is decoded, normalized (resolving `.` and `..`),\n * and then checked. Only paths whose normalized form literally begins with\n * `/inbound/` are forwarded. Path-traversal attempts such as\n * `/inbound/../sessions/foo` decode and normalize to `/sessions/foo`, which\n * does not start with `/inbound/` and is rejected with 404.\n */\nexport function createTelegramProxy(opts: TelegramProxyOptions): TelegramProxy {\n const log = opts.log ?? (() => {})\n let server: Server | null = null\n\n return {\n async start() {\n server = createServer((req, res) => {\n handleProxyRequest(req, res, opts, log)\n })\n\n return new Promise<{ url: string; port: number }>((resolve, reject) => {\n server!.once(\"error\", reject)\n server!.listen(opts.listenPort, \"127.0.0.1\", () => {\n const addr = server!.address()\n const port = typeof addr === \"object\" && addr !== null ? addr.port : opts.listenPort\n const url = `http://127.0.0.1:${port}`\n log(`[telegram-proxy] listening on ${url}`)\n resolve({ url, port })\n })\n })\n },\n\n async stop() {\n if (!server) return\n await new Promise<void>((resolve, reject) => {\n server!.close(err => {\n if (err) reject(err)\n else resolve()\n })\n })\n server = null\n },\n }\n}\n\nfunction handleProxyRequest(\n req: IncomingMessage,\n res: ServerResponse,\n opts: TelegramProxyOptions,\n log: (msg: string) => void,\n): void {\n // Only POST is allowed.\n if (req.method !== \"POST\") {\n res.writeHead(405, { \"content-type\": \"application/json\" })\n res.end(JSON.stringify({ error: \"method_not_allowed\" }))\n return\n }\n\n const rawUrl = req.url ?? \"/\"\n const normalized = normalizePath(rawUrl)\n\n // SECURITY: only forward paths whose normalized form literally begins with /inbound/\n if (!normalized.startsWith(\"/inbound/\")) {\n res.writeHead(404, { \"content-type\": \"application/json\" })\n res.end(JSON.stringify({ error: \"not_found\" }))\n return\n }\n\n const targetUrl = new URL(normalized, opts.targetBaseUrl)\n // Preserve query string.\n const queryIdx = rawUrl.indexOf(\"?\")\n if (queryIdx >= 0) {\n targetUrl.search = rawUrl.slice(queryIdx)\n }\n\n log(`[telegram-proxy] ${req.method} ${rawUrl} -> ${targetUrl.toString()}`)\n\n // Build headers: forward everything except Host, which we rewrite.\n const headers: Record<string, string | string[]> = {}\n for (const [key, value] of Object.entries(req.headers)) {\n if (value !== undefined) {\n headers[key] = value\n }\n }\n headers.host = targetUrl.host\n\n const proxyReq = request(\n {\n hostname: targetUrl.hostname,\n port: targetUrl.port || (targetUrl.protocol === \"https:\" ? 443 : 80),\n path: targetUrl.pathname + targetUrl.search,\n method: req.method,\n headers,\n },\n proxyRes => {\n res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers)\n proxyRes.pipe(res)\n },\n )\n\n proxyReq.on(\"error\", err => {\n log(`[telegram-proxy] upstream error: ${err.message}`)\n if (!res.headersSent) {\n res.writeHead(502, { \"content-type\": \"application/json\" })\n res.end(JSON.stringify({ error: \"bad_gateway\" }))\n }\n })\n\n // The proxy does not follow redirects; it passes the upstream response\n // (including 301/302) straight through to the caller. This is the default\n // behaviour of node:http.request.\n\n req.pipe(proxyReq)\n}\n\n/**\n * Decode percent-encoding and collapse `.` / `..` segments.\n * Returns the raw path part on malformed percent-encoding so it fails the\n * `/inbound/` prefix check downstream.\n */\nfunction normalizePath(urlPath: string): string {\n const pathPart = urlPath.split(\"?\")[0] ?? urlPath\n\n let decoded: string\n try {\n decoded = decodeURIComponent(pathPart)\n } catch {\n // Malformed percent-encoding — return raw so it fails the allowlist.\n return pathPart\n }\n\n const hasTrailingSlash = decoded.endsWith(\"/\") && decoded !== \"/\"\n\n const segments = decoded.split(\"/\").filter(s => s !== \"\" && s !== \".\")\n const normalized: string[] = []\n for (const seg of segments) {\n if (seg === \"..\") {\n normalized.pop()\n } else {\n normalized.push(seg)\n }\n }\n\n let result = \"/\" + normalized.join(\"/\")\n if (hasTrailingSlash && !result.endsWith(\"/\")) {\n result += \"/\"\n }\n return result\n}\n"]}
@@ -0,0 +1,91 @@
1
+ import { z } from 'zod';
2
+ import { S as SessionConfig, R as RouteSpec, P as Posture, E as EffortLevel, C as ContextProfile } from './session-config-DIf6wYYP.js';
3
+ import '@agentproto/auth';
4
+ import './context-continuity-B9n0t0v-.js';
5
+
6
+ /**
7
+ * User-owned spawn presets.
8
+ *
9
+ * This is deliberately separate from `preset-tools.ts`: provider presets are
10
+ * static gateway definitions shipped by packages, while a UserPreset is a
11
+ * private saved combination of the orthogonal session-config axes.
12
+ */
13
+
14
+ /** A reusable, user-scoped subset of the spawn/session config axes. */
15
+ interface UserPreset extends Partial<SessionConfig> {
16
+ /** Stable machine-local id, e.g. `fast-deepseek`. */
17
+ id: string;
18
+ /** Human-readable name shown by CLI and editor pickers. */
19
+ label: string;
20
+ /** Adapter harness to use. Omitted means the caller selects one. */
21
+ adapter?: string;
22
+ /** Canonical harness slug — alias for `adapter`. */
23
+ harness?: string;
24
+ model?: string;
25
+ route?: RouteSpec;
26
+ access?: {
27
+ profileRef?: string;
28
+ };
29
+ posture?: Posture;
30
+ effort?: EffortLevel;
31
+ contextProfile?: ContextProfile;
32
+ /** Working directory the favorite pins to. When set, a spawn from this
33
+ * preset lands here regardless of the caller's active folder — the axis
34
+ * that makes a favorite fully location-pinned (true zero-input). Omitted
35
+ * means the caller's cwd ladder resolves it as before. */
36
+ cwd?: string;
37
+ /** Skills to preload for a spawn from this preset — the same axis as
38
+ * `SpawnAgentSessionInput.skills`. Omitted means the adapter/defaults
39
+ * decide. */
40
+ skills?: string[];
41
+ }
42
+ declare const userPresetsFileSchema: z.ZodObject<{
43
+ version: z.ZodLiteral<1>;
44
+ presets: z.ZodArray<z.ZodObject<{
45
+ id: z.ZodString;
46
+ label: z.ZodString;
47
+ adapter: z.ZodOptional<z.ZodString>;
48
+ harness: z.ZodOptional<z.ZodString>;
49
+ model: z.ZodOptional<z.ZodString>;
50
+ route: z.ZodOptional<z.ZodObject<{
51
+ gateway: z.ZodString;
52
+ baseUrl: z.ZodOptional<z.ZodString>;
53
+ }, z.core.$strip>>;
54
+ access: z.ZodOptional<z.ZodObject<{
55
+ profileRef: z.ZodOptional<z.ZodString>;
56
+ }, z.core.$strip>>;
57
+ posture: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
58
+ default: "default";
59
+ plan: "plan";
60
+ "accept-edits": "accept-edits";
61
+ bypass: "bypass";
62
+ "read-only": "read-only";
63
+ }>, z.ZodObject<{
64
+ harnessModeId: z.ZodString;
65
+ }, z.core.$strip>]>>;
66
+ effort: z.ZodOptional<z.ZodEnum<{
67
+ low: "low";
68
+ medium: "medium";
69
+ high: "high";
70
+ xhigh: "xhigh";
71
+ max: "max";
72
+ ultracode: "ultracode";
73
+ }>>;
74
+ contextProfile: z.ZodOptional<z.ZodString>;
75
+ cwd: z.ZodOptional<z.ZodString>;
76
+ skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
77
+ }, z.core.$strip>>;
78
+ }, z.core.$strip>;
79
+ type UserPresetsFile = z.infer<typeof userPresetsFileSchema>;
80
+ declare function userPresetsPath(): string;
81
+ /** Missing or malformed user config is treated as empty — a bad preset must
82
+ * never prevent the daemon from starting. Writes always restore valid JSON. */
83
+ declare function loadUserPresets(): Promise<UserPresetsFile>;
84
+ declare function listUserPresets(): Promise<UserPreset[]>;
85
+ declare function getUserPreset(id: string): Promise<UserPreset | undefined>;
86
+ /** Add or replace a preset by id. The parser makes this the single validation
87
+ * boundary for CLI, MCP and editor callers. */
88
+ declare function saveUserPreset(preset: UserPreset): Promise<void>;
89
+ declare function deleteUserPreset(id: string): Promise<boolean>;
90
+
91
+ export { type UserPreset, type UserPresetsFile, deleteUserPreset, getUserPreset, listUserPresets, loadUserPresets, saveUserPreset, userPresetsPath };
@@ -0,0 +1,84 @@
1
+ import { readFile, mkdir, writeFile } from 'fs/promises';
2
+ import { homedir } from 'os';
3
+ import { resolve, join } from 'path';
4
+ import { z } from 'zod';
5
+
6
+ /**
7
+ * @agentproto/runtime v0.1.0-alpha
8
+ * Long-running gateway: MCP server + HTTP transport + HEARTBEAT autonomy + conversation persistence over a workspace dir.
9
+ */
10
+
11
+ var effortSchema = z.enum(["low", "medium", "high", "xhigh", "max", "ultracode"]);
12
+ var postureSchema = z.union([
13
+ z.enum(["default", "plan", "accept-edits", "bypass", "read-only"]),
14
+ z.object({ harnessModeId: z.string().min(1) })
15
+ ]);
16
+ var routeSchema = z.object({
17
+ gateway: z.string().min(1),
18
+ baseUrl: z.string().url().optional()
19
+ });
20
+ var userPresetSchema = z.object({
21
+ id: z.string().regex(/^[a-z0-9][a-z0-9-]*$/),
22
+ label: z.string().min(1),
23
+ adapter: z.string().min(1).optional(),
24
+ harness: z.string().min(1).optional(),
25
+ model: z.string().min(1).optional(),
26
+ route: routeSchema.optional(),
27
+ access: z.object({ profileRef: z.string().min(1).optional() }).optional(),
28
+ posture: postureSchema.optional(),
29
+ effort: effortSchema.optional(),
30
+ contextProfile: z.string().min(1).optional(),
31
+ cwd: z.string().min(1).optional(),
32
+ skills: z.array(z.string().min(1)).optional()
33
+ });
34
+ var userPresetsFileSchema = z.object({
35
+ version: z.literal(1),
36
+ presets: z.array(userPresetSchema)
37
+ });
38
+ function emptyFile() {
39
+ return { version: 1, presets: [] };
40
+ }
41
+ function userPresetsPath() {
42
+ return resolve(homedir(), ".agentproto", "presets.json");
43
+ }
44
+ async function loadUserPresets() {
45
+ try {
46
+ return userPresetsFileSchema.parse(JSON.parse(await readFile(userPresetsPath(), "utf8")));
47
+ } catch {
48
+ return emptyFile();
49
+ }
50
+ }
51
+ async function writeUserPresets(file) {
52
+ const dir = join(homedir(), ".agentproto");
53
+ await mkdir(dir, { recursive: true });
54
+ await writeFile(userPresetsPath(), JSON.stringify(file, null, 2) + "\n", {
55
+ encoding: "utf8",
56
+ mode: 384
57
+ });
58
+ }
59
+ async function listUserPresets() {
60
+ return (await loadUserPresets()).presets;
61
+ }
62
+ async function getUserPreset(id) {
63
+ return (await loadUserPresets()).presets.find((preset) => preset.id === id);
64
+ }
65
+ async function saveUserPreset(preset) {
66
+ const validated = userPresetSchema.parse(preset);
67
+ const file = await loadUserPresets();
68
+ const index = file.presets.findIndex((existing) => existing.id === validated.id);
69
+ if (index === -1) file.presets.push(validated);
70
+ else file.presets[index] = validated;
71
+ await writeUserPresets(file);
72
+ }
73
+ async function deleteUserPreset(id) {
74
+ const file = await loadUserPresets();
75
+ const index = file.presets.findIndex((preset) => preset.id === id);
76
+ if (index === -1) return false;
77
+ file.presets.splice(index, 1);
78
+ await writeUserPresets(file);
79
+ return true;
80
+ }
81
+
82
+ export { deleteUserPreset, getUserPreset, listUserPresets, loadUserPresets, saveUserPreset, userPresetsPath };
83
+ //# sourceMappingURL=user-presets.mjs.map
84
+ //# sourceMappingURL=user-presets.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/user-presets.ts"],"names":[],"mappings":"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","file":"user-presets.mjs","sourcesContent":["/**\n * User-owned spawn presets.\n *\n * This is deliberately separate from `preset-tools.ts`: provider presets are\n * static gateway definitions shipped by packages, while a UserPreset is a\n * private saved combination of the orthogonal session-config axes.\n */\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\"\nimport { homedir } from \"node:os\"\nimport { join, resolve } from \"node:path\"\nimport { z } from \"zod\"\nimport type {\n ContextProfile,\n EffortLevel,\n Posture,\n RouteSpec,\n SessionConfig,\n} from \"./session-config.js\"\n\nconst effortSchema = z.enum([\"low\", \"medium\", \"high\", \"xhigh\", \"max\", \"ultracode\"])\nconst postureSchema = z.union([\n z.enum([\"default\", \"plan\", \"accept-edits\", \"bypass\", \"read-only\"]),\n z.object({ harnessModeId: z.string().min(1) }),\n])\nconst routeSchema = z.object({\n gateway: z.string().min(1),\n baseUrl: z.string().url().optional(),\n})\n\n/** A reusable, user-scoped subset of the spawn/session config axes. */\nexport interface UserPreset extends Partial<SessionConfig> {\n /** Stable machine-local id, e.g. `fast-deepseek`. */\n id: string\n /** Human-readable name shown by CLI and editor pickers. */\n label: string\n /** Adapter harness to use. Omitted means the caller selects one. */\n adapter?: string\n /** Canonical harness slug — alias for `adapter`. */\n harness?: string\n model?: string\n route?: RouteSpec\n access?: { profileRef?: string }\n posture?: Posture\n effort?: EffortLevel\n contextProfile?: ContextProfile\n /** Working directory the favorite pins to. When set, a spawn from this\n * preset lands here regardless of the caller's active folder — the axis\n * that makes a favorite fully location-pinned (true zero-input). Omitted\n * means the caller's cwd ladder resolves it as before. */\n cwd?: string\n /** Skills to preload for a spawn from this preset — the same axis as\n * `SpawnAgentSessionInput.skills`. Omitted means the adapter/defaults\n * decide. */\n skills?: string[]\n}\n\nconst userPresetSchema = z.object({\n id: z.string().regex(/^[a-z0-9][a-z0-9-]*$/),\n label: z.string().min(1),\n adapter: z.string().min(1).optional(),\n harness: z.string().min(1).optional(),\n model: z.string().min(1).optional(),\n route: routeSchema.optional(),\n access: z.object({ profileRef: z.string().min(1).optional() }).optional(),\n posture: postureSchema.optional(),\n effort: effortSchema.optional(),\n contextProfile: z.string().min(1).optional(),\n cwd: z.string().min(1).optional(),\n skills: z.array(z.string().min(1)).optional(),\n}) satisfies z.ZodType<UserPreset>\n\nconst userPresetsFileSchema = z.object({\n version: z.literal(1),\n presets: z.array(userPresetSchema),\n})\n\nexport type UserPresetsFile = z.infer<typeof userPresetsFileSchema>\n\nfunction emptyFile(): UserPresetsFile {\n return { version: 1, presets: [] }\n}\n\nexport function userPresetsPath(): string {\n return resolve(homedir(), \".agentproto\", \"presets.json\")\n}\n\n/** Missing or malformed user config is treated as empty — a bad preset must\n * never prevent the daemon from starting. Writes always restore valid JSON. */\nexport async function loadUserPresets(): Promise<UserPresetsFile> {\n try {\n return userPresetsFileSchema.parse(JSON.parse(await readFile(userPresetsPath(), \"utf8\")))\n } catch {\n return emptyFile()\n }\n}\n\nasync function writeUserPresets(file: UserPresetsFile): Promise<void> {\n const dir = join(homedir(), \".agentproto\")\n await mkdir(dir, { recursive: true })\n await writeFile(userPresetsPath(), JSON.stringify(file, null, 2) + \"\\n\", {\n encoding: \"utf8\",\n mode: 0o600,\n })\n}\n\nexport async function listUserPresets(): Promise<UserPreset[]> {\n return (await loadUserPresets()).presets\n}\n\nexport async function getUserPreset(id: string): Promise<UserPreset | undefined> {\n return (await loadUserPresets()).presets.find(preset => preset.id === id)\n}\n\n/** Add or replace a preset by id. The parser makes this the single validation\n * boundary for CLI, MCP and editor callers. */\nexport async function saveUserPreset(preset: UserPreset): Promise<void> {\n const validated = userPresetSchema.parse(preset)\n const file = await loadUserPresets()\n const index = file.presets.findIndex(existing => existing.id === validated.id)\n if (index === -1) file.presets.push(validated)\n else file.presets[index] = validated\n await writeUserPresets(file)\n}\n\nexport async function deleteUserPreset(id: string): Promise<boolean> {\n const file = await loadUserPresets()\n const index = file.presets.findIndex(preset => preset.id === id)\n if (index === -1) return false\n file.presets.splice(index, 1)\n await writeUserPresets(file)\n return true\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentproto/runtime",
3
- "version": "1.1.0",
3
+ "version": "2.1.0",
4
4
  "description": "@agentproto/runtime — long-running gateway that turns an agentproto workspace into a live runtime. Composes @agentproto/mcp-server (CRUD verbs) with HTTP transport, HEARTBEAT.md autonomy loop, and append-only conversation persistence. Drop a workspace dir, point your MCP client at it, and the agent ticks on its own.",
5
5
  "keywords": [
6
6
  "agentproto",
@@ -30,6 +30,11 @@
30
30
  "import": "./dist/index.mjs",
31
31
  "default": "./dist/index.mjs"
32
32
  },
33
+ "./catalog-models": {
34
+ "types": "./dist/catalog-models.d.ts",
35
+ "import": "./dist/catalog-models.mjs",
36
+ "default": "./dist/catalog-models.mjs"
37
+ },
33
38
  "./conversations": {
34
39
  "types": "./dist/conversations.d.ts",
35
40
  "import": "./dist/conversations.mjs",
@@ -46,6 +51,11 @@
46
51
  "default": "./dist/workspace-fs.mjs"
47
52
  },
48
53
  "./package.json": "./package.json",
54
+ "./pr-provenance": {
55
+ "types": "./dist/pr-provenance.d.ts",
56
+ "import": "./dist/pr-provenance.mjs",
57
+ "default": "./dist/pr-provenance.mjs"
58
+ },
49
59
  "./workspaces-config": {
50
60
  "types": "./dist/workspaces-config.d.ts",
51
61
  "import": "./dist/workspaces-config.mjs",
@@ -75,6 +85,16 @@
75
85
  "types": "./dist/session-story.d.ts",
76
86
  "import": "./dist/session-story.mjs",
77
87
  "default": "./dist/session-story.mjs"
88
+ },
89
+ "./session-story-panel": {
90
+ "types": "./dist/session-story-panel.d.ts",
91
+ "import": "./dist/session-story-panel.mjs",
92
+ "default": "./dist/session-story-panel.mjs"
93
+ },
94
+ "./user-presets": {
95
+ "types": "./dist/user-presets.d.ts",
96
+ "import": "./dist/user-presets.mjs",
97
+ "default": "./dist/user-presets.mjs"
78
98
  }
79
99
  },
80
100
  "files": [
@@ -86,42 +106,45 @@
86
106
  "access": "public"
87
107
  },
88
108
  "dependencies": {
109
+ "@modelcontextprotocol/sdk": "^1.30.0",
89
110
  "croner": "^9.0.0",
90
- "@modelcontextprotocol/sdk": "^1.29.0",
91
111
  "gray-matter": "^4.0.3",
92
- "ws": "^8.18.0",
112
+ "ws": "^8.21.1",
93
113
  "zod": "^4.4.3",
94
- "@agentproto/acp": "0.6.0",
95
- "@agentproto/auth": "0.2.0",
96
- "@agentproto/driver-agent-cli": "2.0.1",
97
- "@agentproto/workflow": "0.1.0",
98
- "@agentproto/workflow-loader": "0.1.1",
99
- "@agentproto/workflow-runtime": "0.5.0",
100
- "@agentproto/provider-kit": "0.3.0",
101
- "@agentproto/provider-presets": "0.4.1",
102
- "@agentproto/eval-reporters": "0.2.3",
114
+ "@agentproto/acp": "0.7.0",
103
115
  "@agentproto/agent": "0.2.1",
116
+ "@agentproto/auth": "1.0.0",
117
+ "@agentproto/command-sandbox": "0.2.0",
118
+ "@agentproto/driver": "0.2.0",
119
+ "@agentproto/driver-agent-cli": "2.1.0",
120
+ "@agentproto/eval-reporters": "0.2.5",
104
121
  "@agentproto/manifest": "0.2.1",
105
- "@agentproto/mcp-server": "0.2.3",
106
- "@agentproto/model-catalog": "0.6.0",
107
- "@agentproto/providers-store": "0.3.1",
122
+ "@agentproto/mcp-server": "0.2.5",
123
+ "@agentproto/model-catalog": "0.8.0",
124
+ "@agentproto/provider-kit": "0.4.1",
125
+ "@agentproto/provider-presets": "0.5.1",
126
+ "@agentproto/providers-store": "0.3.3",
108
127
  "@agentproto/redaction": "0.2.1",
109
- "@agentproto/sandbox": "0.1.5",
110
- "@agentproto/secrets": "0.2.1",
111
- "@agentproto/telemetry-langfuse": "0.2.2"
128
+ "@agentproto/routine": "0.2.0",
129
+ "@agentproto/sandbox": "0.2.1",
130
+ "@agentproto/secrets": "0.2.2",
131
+ "@agentproto/telemetry-langfuse": "0.2.3",
132
+ "@agentproto/tool": "0.2.1",
133
+ "@agentproto/workflow": "0.1.1",
134
+ "@agentproto/workflow-loader": "0.1.2",
135
+ "@agentproto/workflow-runtime": "0.6.0"
112
136
  },
113
137
  "optionalDependencies": {
114
- "@agentproto/sandbox-e2b": "0.2.1"
138
+ "@agentproto/sandbox-e2b": "0.3.1"
115
139
  },
116
140
  "devDependencies": {
117
141
  "@types/node": "^25.6.2",
118
142
  "@types/ws": "^8.5.13",
119
143
  "tsup": "^8.5.1",
120
144
  "typescript": "^5.9.3",
145
+ "vite-node": "^3.2.4",
121
146
  "vitest": "^3.2.4",
122
- "@agentproto/driver": "0.1.3",
123
- "@agentproto/rendezvous": "0.2.0",
124
- "@agentproto/tool": "0.2.1",
147
+ "@agentproto/rendezvous": "0.2.1",
125
148
  "@agentproto/tooling": "0.1.0-alpha.0"
126
149
  },
127
150
  "scripts": {
@@ -130,6 +153,8 @@
130
153
  "clean": "rm -rf dist",
131
154
  "check-types": "tsc --noEmit",
132
155
  "test": "vitest run --passWithNoTests",
133
- "test:watch": "vitest"
156
+ "test:watch": "vitest",
157
+ "sim:inbound": "vite-node scripts/simulate-inbound.mjs",
158
+ "telegram:proxy": "vite-node scripts/telegram-proxy.mjs"
134
159
  }
135
160
  }