@0xmaxma/claude-gateway 1.5.8 → 1.6.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.
Files changed (65) hide show
  1. package/README.md +22 -0
  2. package/dist/agent/runner.d.ts +30 -0
  3. package/dist/agent/runner.d.ts.map +1 -1
  4. package/dist/agent/runner.js +223 -16
  5. package/dist/agent/runner.js.map +1 -1
  6. package/dist/api/apps-router.d.ts.map +1 -1
  7. package/dist/api/apps-router.js +7 -2
  8. package/dist/api/apps-router.js.map +1 -1
  9. package/dist/api/gateway-router.d.ts.map +1 -1
  10. package/dist/api/gateway-router.js +27 -0
  11. package/dist/api/gateway-router.js.map +1 -1
  12. package/dist/api/line-webhook-router.d.ts.map +1 -1
  13. package/dist/api/line-webhook-router.js +68 -0
  14. package/dist/api/line-webhook-router.js.map +1 -1
  15. package/dist/api/router.d.ts +2 -0
  16. package/dist/api/router.d.ts.map +1 -1
  17. package/dist/api/router.js +59 -3
  18. package/dist/api/router.js.map +1 -1
  19. package/dist/api/share-router.d.ts +11 -0
  20. package/dist/api/share-router.d.ts.map +1 -0
  21. package/dist/api/share-router.js +402 -0
  22. package/dist/api/share-router.js.map +1 -0
  23. package/dist/apps/installer.d.ts +55 -0
  24. package/dist/apps/installer.d.ts.map +1 -1
  25. package/dist/apps/installer.js +130 -0
  26. package/dist/apps/installer.js.map +1 -1
  27. package/dist/config/loader.d.ts.map +1 -1
  28. package/dist/config/loader.js +9 -0
  29. package/dist/config/loader.js.map +1 -1
  30. package/dist/config/public-url.d.ts +11 -0
  31. package/dist/config/public-url.d.ts.map +1 -0
  32. package/dist/config/public-url.js +41 -0
  33. package/dist/config/public-url.js.map +1 -0
  34. package/dist/config/watcher.d.ts.map +1 -1
  35. package/dist/config/watcher.js +1 -0
  36. package/dist/config/watcher.js.map +1 -1
  37. package/dist/history/db.d.ts +14 -0
  38. package/dist/history/db.d.ts.map +1 -1
  39. package/dist/history/db.js +48 -4
  40. package/dist/history/db.js.map +1 -1
  41. package/dist/history/types.d.ts +5 -0
  42. package/dist/history/types.d.ts.map +1 -1
  43. package/dist/session/process.d.ts.map +1 -1
  44. package/dist/session/process.js +17 -1
  45. package/dist/session/process.js.map +1 -1
  46. package/dist/session/store.d.ts.map +1 -1
  47. package/dist/session/store.js +9 -1
  48. package/dist/session/store.js.map +1 -1
  49. package/dist/share/session-image-catalog.d.ts +22 -0
  50. package/dist/share/session-image-catalog.d.ts.map +1 -0
  51. package/dist/share/session-image-catalog.js +151 -0
  52. package/dist/share/session-image-catalog.js.map +1 -0
  53. package/dist/share/share-store.d.ts +137 -0
  54. package/dist/share/share-store.d.ts.map +1 -0
  55. package/dist/share/share-store.js +340 -0
  56. package/dist/share/share-store.js.map +1 -0
  57. package/dist/types.d.ts +17 -7
  58. package/dist/types.d.ts.map +1 -1
  59. package/mcp/instructions.ts +1 -0
  60. package/mcp/server.ts +2 -0
  61. package/mcp/tools/image/module.ts +240 -26
  62. package/mcp/tools/line/module.ts +186 -0
  63. package/mcp/tools/share-image/module.ts +120 -0
  64. package/mcp/tools/shared/share-client.ts +197 -0
  65. package/package.json +3 -1
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.computeSessionImageCatalog = computeSessionImageCatalog;
37
+ const fs = __importStar(require("fs"));
38
+ const db_1 = require("../history/db");
39
+ const media_store_1 = require("../history/media-store");
40
+ const share_store_1 = require("./share-store");
41
+ /**
42
+ * Session image catalog (#72) — the single deterministic answer to
43
+ * "which image is image N in this chat?".
44
+ *
45
+ * The agent must never count images from its own transcript: after a context
46
+ * compaction or a resumed session that count silently drifts, and the number
47
+ * the user sees has no shared source with the number the agent quotes. This
48
+ * module derives the list from data that is already persisted and append-only
49
+ * (message media_files), so every consumer that calls it agrees by construction.
50
+ *
51
+ * Computed fresh on every call — no new table, no reconciliation job. Ordinals
52
+ * are stable because history only ever grows; the one case that renumbers is
53
+ * retention pruning, which deletes the files too (they were unreferenceable
54
+ * anyway) and renumbers every consumer at once.
55
+ */
56
+ /** Phase-1 catalog scope: raster images the share/reference path can carry. */
57
+ const IMAGE_EXT_RE = /\.(png|jpe?g|webp|gif)$/i;
58
+ /** Legacy uploads whose original name had no extension were stored as .bin
59
+ * (clipboard pastes, sanitized non-ASCII filenames). Their extension says
60
+ * nothing, so ask the bytes: read just enough header to run the same magic-
61
+ * bytes sniff the share layer validates with. Anything unreadable is not an
62
+ * image for catalog purposes. */
63
+ function binFileIsImage(agentsBaseDir, agentId, relativePath) {
64
+ try {
65
+ const abs = media_store_1.MediaStore.resolvePath(agentsBaseDir, agentId, relativePath);
66
+ const fd = fs.openSync(abs, 'r');
67
+ try {
68
+ const header = Buffer.alloc(12);
69
+ const read = fs.readSync(fd, header, 0, 12, 0);
70
+ return (0, share_store_1.detectImageMime)(header.subarray(0, read)) !== null;
71
+ }
72
+ finally {
73
+ fs.closeSync(fd);
74
+ }
75
+ }
76
+ catch {
77
+ return false;
78
+ }
79
+ }
80
+ /** Cap catalog descriptions — they describe, they are not a transcript. */
81
+ const MAX_DESC_CHARS = 200;
82
+ /** Channel receivers persist bare placeholders ("(photo)") for captionless
83
+ * media — those describe nothing, so they don't become a desc. */
84
+ const PLACEHOLDER_CONTENT_RE = /^\((?:photo|image|video|file|sticker|audio|voice)\)$/i;
85
+ function toDesc(text) {
86
+ const t = (text ?? '').trim();
87
+ if (!t || PLACEHOLDER_CONTENT_RE.test(t))
88
+ return undefined;
89
+ return t.length > MAX_DESC_CHARS ? `${t.slice(0, MAX_DESC_CHARS)}…` : t;
90
+ }
91
+ /**
92
+ * History stores media as `media/<chat>/<file>` while image_artifacts (and
93
+ * validateShareFile) store the media-ROOT-relative `<chat>/<file>`. Normalise to
94
+ * the root-relative form so the artifact join can match, and so the same file
95
+ * recorded in either form dedupes to ONE ordinal. MediaStore.resolvePath accepts
96
+ * both, so the emitted path stays usable as a ref.
97
+ */
98
+ function toMediaRootRelative(p) {
99
+ return p.startsWith('media/') ? p.slice(6) : p;
100
+ }
101
+ /** available = the bytes are still on disk. A path that cannot even be resolved
102
+ * (traversal, escaped symlink) is not addressable, so it is unavailable too. */
103
+ function isOnDisk(agentsBaseDir, agentId, relativePath) {
104
+ try {
105
+ return fs.existsSync(media_store_1.MediaStore.resolvePath(agentsBaseDir, agentId, relativePath));
106
+ }
107
+ catch {
108
+ return false;
109
+ }
110
+ }
111
+ function computeSessionImageCatalog(opts) {
112
+ const { agentsBaseDir, store, agentId, sessionId } = opts;
113
+ const rows = db_1.HistoryDB.forAgent(agentsBaseDir, agentId).listSessionMedia(sessionId);
114
+ const items = [];
115
+ const seen = new Set();
116
+ for (const row of rows) {
117
+ // Anything the agent produced is a generation; everything else (user turn,
118
+ // system note) is an upload from the caller's side.
119
+ const origin = row.role === 'assistant' ? 'generated' : 'upload';
120
+ for (const raw of row.mediaFiles) {
121
+ if (typeof raw !== 'string' || !raw.trim())
122
+ continue;
123
+ const relativePath = toMediaRootRelative(raw.trim());
124
+ if (!IMAGE_EXT_RE.test(relativePath) &&
125
+ !(/\.bin$/i.test(relativePath) && binFileIsImage(agentsBaseDir, agentId, relativePath)))
126
+ continue;
127
+ // First appearance owns the ordinal forever — re-sending the same file
128
+ // later must not create a second entry or shift the numbers after it.
129
+ if (seen.has(relativePath))
130
+ continue;
131
+ seen.add(relativePath);
132
+ const artifact = store.findArtifactByPath(agentId, sessionId, relativePath);
133
+ // Generated → the generation prompt (persisted on the artifact); upload →
134
+ // whatever the user said in the message that carried the file.
135
+ const desc = origin === 'generated' ? toDesc(artifact?.prompt) : toDesc(row.content);
136
+ items.push({
137
+ index: items.length + 1,
138
+ ref: artifact ? `artifact:${artifact.id}` : relativePath,
139
+ relative_path: relativePath,
140
+ origin,
141
+ ts: row.ts,
142
+ // A missing file STAYS listed (available:false) so the ordinals of the
143
+ // images after it do not shift under the agent mid-conversation.
144
+ available: isOnDisk(agentsBaseDir, agentId, relativePath),
145
+ ...(desc ? { desc } : {}),
146
+ });
147
+ }
148
+ }
149
+ return items;
150
+ }
151
+ //# sourceMappingURL=session-image-catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-image-catalog.js","sourceRoot":"","sources":["../../src/share/session-image-catalog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Share bridge — SQLite-backed store for short-lived public image shares
3
+ * and private image artifacts (#70, plan §8/§9).
4
+ *
5
+ * The gateway MAIN process is the only owner of this DB: MCP subprocesses go
6
+ * through the authenticated HTTP API (share-router.ts) and never open
7
+ * SQLite themselves. Tokens are bearer capabilities: 24 random bytes,
8
+ * base64url, and ONLY the SHA-256 hash is persisted — the plaintext token
9
+ * exists in the mint response (and a short-lived in-memory dedupe cache) only.
10
+ */
11
+ /** randomBytes(24) → base64url without padding = 32 chars. */
12
+ export declare const SHARE_TOKEN_RE: RegExp;
13
+ /** Idempotent-mint window (§17.4): a re-mint for the same (agent, session,
14
+ * ref, purpose) within this window returns the SAME token/URL so the
15
+ * provider's duplicate-submit guard (which hashes the image URLs) still
16
+ * dedupes agent retries instead of double-billing. */
17
+ export declare const MINT_DEDUPE_WINDOW_MS = 60000;
18
+ export declare const DEFAULT_SHARE_TTL_SECONDS = 1800;
19
+ export declare const DEFAULT_MAX_REFS = 5;
20
+ export declare const DEFAULT_MAX_FILE_BYTES: number;
21
+ export declare const DEFAULT_MAX_TOTAL_BYTES: number;
22
+ /** Artifacts older than this are pruned opportunistically — far beyond any share
23
+ * TTL (max 24h) and any live session, so removal never breaks an active ref. */
24
+ export declare const ARTIFACT_RETENTION_MS: number;
25
+ export type ShareLimits = {
26
+ maxRefs: number;
27
+ maxFileBytes: number;
28
+ maxTotalBytes: number;
29
+ };
30
+ export declare function shareLimitsFromEnv(): ShareLimits;
31
+ /** Typed error with a stable machine-readable code (used for HTTP mapping). */
32
+ export declare class ShareError extends Error {
33
+ readonly code: string;
34
+ constructor(code: string, message: string);
35
+ }
36
+ /** Phase-1 formats: PNG / JPEG / WebP only (§11). GIF/SVG/PDF are rejected. */
37
+ export declare function detectImageMime(header: Buffer): 'image/png' | 'image/jpeg' | 'image/webp' | null;
38
+ export type ValidatedShareFile = {
39
+ /** Path relative to the agent's media root — the ONLY path form persisted (§12.8). */
40
+ relativePath: string;
41
+ size: number;
42
+ mime: string;
43
+ };
44
+ /**
45
+ * Validate a local ref (absolute or media-relative path) for sharing (§12):
46
+ * canonicalize, require containment in the agent's media root, reject symlink
47
+ * escapes, require a regular file, validate magic bytes and the per-file cap.
48
+ * Returns only the media-root-relative path — client-supplied absolute paths
49
+ * are never persisted. Throws ShareError with a stable code.
50
+ */
51
+ export declare function validateShareFile(agentsBaseDir: string, agentId: string, ref: string, maxFileBytes?: number): ValidatedShareFile;
52
+ export type MintedShare = {
53
+ shareId: string;
54
+ /** Plaintext token — returned to the caller, NEVER persisted or logged. */
55
+ token: string;
56
+ expiresAtMs: number;
57
+ /** true when this mint was served from the idempotency window (§17.4). */
58
+ deduped: boolean;
59
+ };
60
+ export type ShareRow = {
61
+ shareId: string;
62
+ agentId: string;
63
+ sessionId: string;
64
+ relativePath: string;
65
+ purpose: string;
66
+ };
67
+ export type ArtifactRow = {
68
+ artifactId: string;
69
+ agentId: string;
70
+ sessionId: string;
71
+ relativePath: string;
72
+ provider: string;
73
+ model: string;
74
+ };
75
+ export declare class ShareStore {
76
+ private readonly db;
77
+ /** In-memory idempotent-mint cache: dedupe key → last mint. Plaintext tokens
78
+ * live here only for MINT_DEDUPE_WINDOW_MS, never on disk (§9/§17.4). */
79
+ private readonly mintCache;
80
+ /** Last opportunistic artifact-prune timestamp (throttle, see registerArtifact). */
81
+ private lastArtifactPruneMs;
82
+ constructor(dbPath: string);
83
+ registerArtifact(a: {
84
+ agentId: string;
85
+ sessionId: string;
86
+ relativePath: string;
87
+ provider: string;
88
+ model: string;
89
+ taskId?: string;
90
+ imageIndex?: number;
91
+ prompt?: string;
92
+ }): string;
93
+ /** Opportunistic, throttled retention sweep — delete artifacts older than
94
+ * ARTIFACT_RETENTION_MS. Best-effort: a failure here must not fail the insert. */
95
+ private pruneOldArtifacts;
96
+ /** Resolve an artifact bound to the SAME agent AND session — cross-agent or
97
+ * cross-session lookups return null (uniform "not found", §8). */
98
+ resolveArtifact(agentId: string, sessionId: string, artifactId: string): ArtifactRow | null;
99
+ /** Newest artifact id registered for (agent, session, relative_path), or null.
100
+ * Read-only reverse lookup for the session image catalog (#72): a path that
101
+ * was produced by generate_image gets the stable `artifact:<id>` ref instead
102
+ * of a raw path. Same agent+session binding as resolveArtifact — a path
103
+ * registered by another agent/session is invisible here. */
104
+ findArtifactByPath(agentId: string, sessionId: string, relativePath: string): {
105
+ id: string;
106
+ prompt: string | null;
107
+ } | null;
108
+ /**
109
+ * Mint a share. `dedupeRef` is the caller-facing identity of the ref
110
+ * (e.g. "artifact:img_x" or "path:<relative>") — an identical mint for the
111
+ * same (agent, session, ref, purpose) within MINT_DEDUPE_WINDOW_MS returns
112
+ * the SAME token (§17.4) so provider-side dedupe keeps working.
113
+ */
114
+ mintShare(a: {
115
+ agentId: string;
116
+ sessionId: string;
117
+ relativePath: string;
118
+ dedupeRef: string;
119
+ purpose: string;
120
+ ttlSeconds: number;
121
+ }): MintedShare;
122
+ /** Look up a live (unexpired, unrevoked) share by plaintext token. */
123
+ lookupByToken(token: string): ShareRow | null;
124
+ getShareOwner(shareId: string): {
125
+ agentId: string;
126
+ sessionId: string;
127
+ purpose: string;
128
+ } | null;
129
+ /** Revoke a share by id. Also evicts any idempotency-cache entry pointing at
130
+ * it so a post-revoke mint issues a FRESH token. Returns false when unknown. */
131
+ revokeShare(shareId: string): boolean;
132
+ /** Lazy cleanup — delete expired rows. Safe to call opportunistically. */
133
+ cleanupExpired(): void;
134
+ close(): void;
135
+ private pruneMintCache;
136
+ }
137
+ //# sourceMappingURL=share-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"share-store.d.ts","sourceRoot":"","sources":["../../src/share/share-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,340 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ShareStore = exports.ShareError = exports.ARTIFACT_RETENTION_MS = exports.DEFAULT_MAX_TOTAL_BYTES = exports.DEFAULT_MAX_FILE_BYTES = exports.DEFAULT_MAX_REFS = exports.DEFAULT_SHARE_TTL_SECONDS = exports.MINT_DEDUPE_WINDOW_MS = exports.SHARE_TOKEN_RE = void 0;
37
+ exports.shareLimitsFromEnv = shareLimitsFromEnv;
38
+ exports.detectImageMime = detectImageMime;
39
+ exports.validateShareFile = validateShareFile;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const crypto_1 = require("crypto");
43
+ const node_sqlite_1 = require("node:sqlite");
44
+ const media_store_1 = require("../history/media-store");
45
+ /**
46
+ * Share bridge — SQLite-backed store for short-lived public image shares
47
+ * and private image artifacts (#70, plan §8/§9).
48
+ *
49
+ * The gateway MAIN process is the only owner of this DB: MCP subprocesses go
50
+ * through the authenticated HTTP API (share-router.ts) and never open
51
+ * SQLite themselves. Tokens are bearer capabilities: 24 random bytes,
52
+ * base64url, and ONLY the SHA-256 hash is persisted — the plaintext token
53
+ * exists in the mint response (and a short-lived in-memory dedupe cache) only.
54
+ */
55
+ /** randomBytes(24) → base64url without padding = 32 chars. */
56
+ exports.SHARE_TOKEN_RE = /^[A-Za-z0-9_-]{32}$/;
57
+ /** Idempotent-mint window (§17.4): a re-mint for the same (agent, session,
58
+ * ref, purpose) within this window returns the SAME token/URL so the
59
+ * provider's duplicate-submit guard (which hashes the image URLs) still
60
+ * dedupes agent retries instead of double-billing. */
61
+ exports.MINT_DEDUPE_WINDOW_MS = 60000;
62
+ exports.DEFAULT_SHARE_TTL_SECONDS = 1800; // 30 min (§3.15)
63
+ exports.DEFAULT_MAX_REFS = 5;
64
+ exports.DEFAULT_MAX_FILE_BYTES = 20 * 1024 * 1024; // 20 MiB
65
+ exports.DEFAULT_MAX_TOTAL_BYTES = 50 * 1024 * 1024; // 50 MiB
66
+ /** Artifacts older than this are pruned opportunistically — far beyond any share
67
+ * TTL (max 24h) and any live session, so removal never breaks an active ref. */
68
+ exports.ARTIFACT_RETENTION_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
69
+ const ARTIFACT_PRUNE_THROTTLE_MS = 60 * 60 * 1000; // sweep at most once/hour
70
+ function shareLimitsFromEnv() {
71
+ const num = (v, dflt) => {
72
+ const n = Number(v);
73
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : dflt;
74
+ };
75
+ return {
76
+ maxRefs: num(process.env.IMAGE_SHARE_MAX_REFS, exports.DEFAULT_MAX_REFS),
77
+ maxFileBytes: num(process.env.IMAGE_SHARE_MAX_FILE_BYTES, exports.DEFAULT_MAX_FILE_BYTES),
78
+ maxTotalBytes: num(process.env.IMAGE_SHARE_MAX_TOTAL_BYTES, exports.DEFAULT_MAX_TOTAL_BYTES),
79
+ };
80
+ }
81
+ /** Typed error with a stable machine-readable code (used for HTTP mapping). */
82
+ class ShareError extends Error {
83
+ constructor(code, message) {
84
+ super(message);
85
+ this.code = code;
86
+ }
87
+ }
88
+ exports.ShareError = ShareError;
89
+ /** Phase-1 formats: PNG / JPEG / WebP only (§11). GIF/SVG/PDF are rejected. */
90
+ function detectImageMime(header) {
91
+ if (header.length >= 3 && header[0] === 0xff && header[1] === 0xd8 && header[2] === 0xff)
92
+ return 'image/jpeg';
93
+ if (header.length >= 8 && header[0] === 0x89 && header[1] === 0x50 && header[2] === 0x4e && header[3] === 0x47)
94
+ return 'image/png';
95
+ if (header.length >= 12 &&
96
+ header[0] === 0x52 && header[1] === 0x49 && header[2] === 0x46 && header[3] === 0x46 &&
97
+ header[8] === 0x57 && header[9] === 0x45 && header[10] === 0x42 && header[11] === 0x50)
98
+ return 'image/webp';
99
+ return null;
100
+ }
101
+ /**
102
+ * Validate a local ref (absolute or media-relative path) for sharing (§12):
103
+ * canonicalize, require containment in the agent's media root, reject symlink
104
+ * escapes, require a regular file, validate magic bytes and the per-file cap.
105
+ * Returns only the media-root-relative path — client-supplied absolute paths
106
+ * are never persisted. Throws ShareError with a stable code.
107
+ */
108
+ function validateShareFile(agentsBaseDir, agentId, ref, maxFileBytes = exports.DEFAULT_MAX_FILE_BYTES) {
109
+ const root = media_store_1.MediaStore.agentMediaRoot(agentsBaseDir, agentId);
110
+ let resolved;
111
+ try {
112
+ if (path.isAbsolute(ref)) {
113
+ // Absolute input (e.g. the path generate_image returned): containment +
114
+ // symlink checks mirror MediaStore.resolvePath.
115
+ const abs = path.resolve(ref);
116
+ if (!abs.startsWith(root + path.sep))
117
+ throw new Error('outside media root');
118
+ resolved = fs.existsSync(abs) ? fs.realpathSync(abs) : abs;
119
+ if (!resolved.startsWith(root + path.sep))
120
+ throw new Error('outside media root');
121
+ }
122
+ else {
123
+ resolved = media_store_1.MediaStore.resolvePath(agentsBaseDir, agentId, ref);
124
+ }
125
+ }
126
+ catch {
127
+ throw new ShareError('invalid_path', 'path is outside the agent media root');
128
+ }
129
+ let stat;
130
+ try {
131
+ stat = fs.lstatSync(resolved); // realpath already followed links; lstat guards devices/FIFOs
132
+ }
133
+ catch {
134
+ throw new ShareError('image_ref_not_found', 'referenced image file does not exist');
135
+ }
136
+ if (!stat.isFile()) {
137
+ throw new ShareError('invalid_path', 'referenced path is not a regular file');
138
+ }
139
+ if (stat.size > maxFileBytes) {
140
+ throw new ShareError('image_too_large', `image exceeds ${maxFileBytes} bytes`);
141
+ }
142
+ const header = Buffer.alloc(12);
143
+ const fd = fs.openSync(resolved, 'r');
144
+ try {
145
+ fs.readSync(fd, header, 0, 12, 0);
146
+ }
147
+ finally {
148
+ fs.closeSync(fd);
149
+ }
150
+ const mime = detectImageMime(header);
151
+ if (!mime) {
152
+ throw new ShareError('unsupported_image_type', 'only PNG, JPEG and WebP can be shared');
153
+ }
154
+ const relativePath = path.relative(root, resolved);
155
+ if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
156
+ throw new ShareError('invalid_path', 'path is outside the agent media root');
157
+ }
158
+ return { relativePath, size: stat.size, mime };
159
+ }
160
+ class ShareStore {
161
+ constructor(dbPath) {
162
+ /** In-memory idempotent-mint cache: dedupe key → last mint. Plaintext tokens
163
+ * live here only for MINT_DEDUPE_WINDOW_MS, never on disk (§9/§17.4). */
164
+ this.mintCache = new Map();
165
+ /** Last opportunistic artifact-prune timestamp (throttle, see registerArtifact). */
166
+ this.lastArtifactPruneMs = 0;
167
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
168
+ this.db = new node_sqlite_1.DatabaseSync(dbPath);
169
+ this.db.exec('PRAGMA journal_mode=WAL');
170
+ this.db.exec('PRAGMA busy_timeout=5000');
171
+ this.db.exec('PRAGMA foreign_keys=ON');
172
+ this.db.exec(`
173
+ CREATE TABLE IF NOT EXISTS image_shares (
174
+ id TEXT PRIMARY KEY,
175
+ token_hash BLOB NOT NULL UNIQUE,
176
+ agent_id TEXT NOT NULL,
177
+ session_id TEXT NOT NULL,
178
+ relative_path TEXT NOT NULL,
179
+ purpose TEXT NOT NULL,
180
+ created_at INTEGER NOT NULL,
181
+ expires_at INTEGER NOT NULL,
182
+ revoked_at INTEGER
183
+ );
184
+ CREATE INDEX IF NOT EXISTS image_shares_expiry ON image_shares(expires_at);
185
+
186
+ CREATE TABLE IF NOT EXISTS image_artifacts (
187
+ id TEXT PRIMARY KEY,
188
+ agent_id TEXT NOT NULL,
189
+ session_id TEXT NOT NULL,
190
+ relative_path TEXT NOT NULL,
191
+ provider TEXT NOT NULL,
192
+ model TEXT NOT NULL,
193
+ task_id TEXT,
194
+ image_index INTEGER NOT NULL DEFAULT 0,
195
+ created_at INTEGER NOT NULL,
196
+ prompt TEXT
197
+ );
198
+ CREATE INDEX IF NOT EXISTS image_artifacts_session_created
199
+ ON image_artifacts(agent_id, session_id, created_at DESC);
200
+ `);
201
+ // DBs created before the `prompt` column existed: CREATE TABLE IF NOT
202
+ // EXISTS leaves them untouched, so add the column in place. Additive and
203
+ // nullable — older rows simply have no prompt.
204
+ const cols = this.db.prepare(`PRAGMA table_info(image_artifacts)`).all();
205
+ if (!cols.some((c) => c.name === 'prompt')) {
206
+ this.db.exec('ALTER TABLE image_artifacts ADD COLUMN prompt TEXT');
207
+ }
208
+ }
209
+ // ── artifacts (§8) ────────────────────────────────────────────────────────
210
+ registerArtifact(a) {
211
+ const id = `img_${(0, crypto_1.randomBytes)(9).toString('base64url')}`;
212
+ this.db.prepare(`INSERT INTO image_artifacts (id, agent_id, session_id, relative_path, provider, model, task_id, image_index, created_at, prompt)
213
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, a.agentId, a.sessionId, a.relativePath, a.provider, a.model, a.taskId ?? null, a.imageIndex ?? 0, Date.now(), a.prompt ?? null);
214
+ this.pruneOldArtifacts();
215
+ return id;
216
+ }
217
+ /** Opportunistic, throttled retention sweep — delete artifacts older than
218
+ * ARTIFACT_RETENTION_MS. Best-effort: a failure here must not fail the insert. */
219
+ pruneOldArtifacts() {
220
+ const now = Date.now();
221
+ if (now - this.lastArtifactPruneMs < ARTIFACT_PRUNE_THROTTLE_MS)
222
+ return;
223
+ this.lastArtifactPruneMs = now;
224
+ try {
225
+ this.db.prepare('DELETE FROM image_artifacts WHERE created_at <= ?').run(now - exports.ARTIFACT_RETENTION_MS);
226
+ }
227
+ catch {
228
+ /* best-effort */
229
+ }
230
+ }
231
+ /** Resolve an artifact bound to the SAME agent AND session — cross-agent or
232
+ * cross-session lookups return null (uniform "not found", §8). */
233
+ resolveArtifact(agentId, sessionId, artifactId) {
234
+ const row = this.db.prepare(`SELECT id, agent_id, session_id, relative_path, provider, model
235
+ FROM image_artifacts WHERE id = ? AND agent_id = ? AND session_id = ?`).get(artifactId, agentId, sessionId);
236
+ if (!row)
237
+ return null;
238
+ return {
239
+ artifactId: row.id,
240
+ agentId: row.agent_id,
241
+ sessionId: row.session_id,
242
+ relativePath: row.relative_path,
243
+ provider: row.provider,
244
+ model: row.model,
245
+ };
246
+ }
247
+ /** Newest artifact id registered for (agent, session, relative_path), or null.
248
+ * Read-only reverse lookup for the session image catalog (#72): a path that
249
+ * was produced by generate_image gets the stable `artifact:<id>` ref instead
250
+ * of a raw path. Same agent+session binding as resolveArtifact — a path
251
+ * registered by another agent/session is invisible here. */
252
+ findArtifactByPath(agentId, sessionId, relativePath) {
253
+ const row = this.db.prepare(`SELECT id, prompt FROM image_artifacts
254
+ WHERE agent_id = ? AND session_id = ? AND relative_path = ?
255
+ ORDER BY created_at DESC, rowid DESC LIMIT 1`).get(agentId, sessionId, relativePath);
256
+ return row ? { id: row.id, prompt: row.prompt } : null;
257
+ }
258
+ // ── shares (§9) ───────────────────────────────────────────────────────────
259
+ /**
260
+ * Mint a share. `dedupeRef` is the caller-facing identity of the ref
261
+ * (e.g. "artifact:img_x" or "path:<relative>") — an identical mint for the
262
+ * same (agent, session, ref, purpose) within MINT_DEDUPE_WINDOW_MS returns
263
+ * the SAME token (§17.4) so provider-side dedupe keeps working.
264
+ */
265
+ mintShare(a) {
266
+ const key = [a.agentId, a.sessionId, a.dedupeRef, a.purpose].join('');
267
+ const now = Date.now();
268
+ const cached = this.mintCache.get(key);
269
+ if (cached && now - cached.mintedAtMs < exports.MINT_DEDUPE_WINDOW_MS && cached.expiresAtMs > now) {
270
+ return { shareId: cached.shareId, token: cached.token, expiresAtMs: cached.expiresAtMs, deduped: true };
271
+ }
272
+ const token = (0, crypto_1.randomBytes)(24).toString('base64url');
273
+ const tokenHash = (0, crypto_1.createHash)('sha256').update(token).digest();
274
+ const shareId = `shr_${(0, crypto_1.randomBytes)(9).toString('base64url')}`;
275
+ const expiresAtMs = now + a.ttlSeconds * 1000;
276
+ this.db.prepare(`INSERT INTO image_shares (id, token_hash, agent_id, session_id, relative_path, purpose, created_at, expires_at)
277
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(shareId, tokenHash, a.agentId, a.sessionId, a.relativePath, a.purpose, now, expiresAtMs);
278
+ this.mintCache.set(key, { shareId, token, expiresAtMs, deduped: false, mintedAtMs: now });
279
+ this.pruneMintCache(now);
280
+ return { shareId, token, expiresAtMs, deduped: false };
281
+ }
282
+ /** Look up a live (unexpired, unrevoked) share by plaintext token. */
283
+ lookupByToken(token) {
284
+ if (!exports.SHARE_TOKEN_RE.test(token))
285
+ return null;
286
+ const tokenHash = (0, crypto_1.createHash)('sha256').update(token).digest();
287
+ const row = this.db.prepare(`SELECT id, agent_id, session_id, relative_path, purpose
288
+ FROM image_shares
289
+ WHERE token_hash = ? AND revoked_at IS NULL AND expires_at > ?`).get(tokenHash, Date.now());
290
+ if (!row)
291
+ return null;
292
+ return {
293
+ shareId: row.id,
294
+ agentId: row.agent_id,
295
+ sessionId: row.session_id,
296
+ relativePath: row.relative_path,
297
+ purpose: row.purpose,
298
+ };
299
+ }
300
+ getShareOwner(shareId) {
301
+ const row = this.db.prepare(`SELECT agent_id, session_id, purpose FROM image_shares WHERE id = ? AND revoked_at IS NULL`).get(shareId);
302
+ return row ? { agentId: row.agent_id, sessionId: row.session_id, purpose: row.purpose } : null;
303
+ }
304
+ /** Revoke a share by id. Also evicts any idempotency-cache entry pointing at
305
+ * it so a post-revoke mint issues a FRESH token. Returns false when unknown. */
306
+ revokeShare(shareId) {
307
+ const res = this.db.prepare(`UPDATE image_shares SET revoked_at = ? WHERE id = ? AND revoked_at IS NULL`).run(Date.now(), shareId);
308
+ for (const [key, entry] of this.mintCache) {
309
+ if (entry.shareId === shareId)
310
+ this.mintCache.delete(key);
311
+ }
312
+ return Number(res.changes) > 0;
313
+ }
314
+ /** Lazy cleanup — delete expired rows. Safe to call opportunistically. */
315
+ cleanupExpired() {
316
+ try {
317
+ this.db.prepare(`DELETE FROM image_shares WHERE expires_at <= ?`).run(Date.now());
318
+ }
319
+ catch {
320
+ /* best-effort */
321
+ }
322
+ }
323
+ close() {
324
+ try {
325
+ this.db.close();
326
+ }
327
+ catch {
328
+ /* already closed */
329
+ }
330
+ }
331
+ pruneMintCache(now) {
332
+ for (const [key, entry] of this.mintCache) {
333
+ if (now - entry.mintedAtMs >= exports.MINT_DEDUPE_WINDOW_MS || entry.expiresAtMs <= now) {
334
+ this.mintCache.delete(key);
335
+ }
336
+ }
337
+ }
338
+ }
339
+ exports.ShareStore = ShareStore;
340
+ //# sourceMappingURL=share-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"share-store.js","sourceRoot":"","sources":["../../src/share/share-store.ts"],"names":[],"mappings":"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"}