@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.
- package/README.md +22 -0
- package/dist/agent/runner.d.ts +30 -0
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +223 -16
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/apps-router.d.ts.map +1 -1
- package/dist/api/apps-router.js +7 -2
- package/dist/api/apps-router.js.map +1 -1
- package/dist/api/gateway-router.d.ts.map +1 -1
- package/dist/api/gateway-router.js +27 -0
- package/dist/api/gateway-router.js.map +1 -1
- package/dist/api/line-webhook-router.d.ts.map +1 -1
- package/dist/api/line-webhook-router.js +68 -0
- package/dist/api/line-webhook-router.js.map +1 -1
- package/dist/api/router.d.ts +2 -0
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +59 -3
- package/dist/api/router.js.map +1 -1
- package/dist/api/share-router.d.ts +11 -0
- package/dist/api/share-router.d.ts.map +1 -0
- package/dist/api/share-router.js +402 -0
- package/dist/api/share-router.js.map +1 -0
- package/dist/apps/installer.d.ts +55 -0
- package/dist/apps/installer.d.ts.map +1 -1
- package/dist/apps/installer.js +130 -0
- package/dist/apps/installer.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +9 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/public-url.d.ts +11 -0
- package/dist/config/public-url.d.ts.map +1 -0
- package/dist/config/public-url.js +41 -0
- package/dist/config/public-url.js.map +1 -0
- package/dist/config/watcher.d.ts.map +1 -1
- package/dist/config/watcher.js +1 -0
- package/dist/config/watcher.js.map +1 -1
- package/dist/history/db.d.ts +14 -0
- package/dist/history/db.d.ts.map +1 -1
- package/dist/history/db.js +48 -4
- package/dist/history/db.js.map +1 -1
- package/dist/history/types.d.ts +5 -0
- package/dist/history/types.d.ts.map +1 -1
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +17 -1
- package/dist/session/process.js.map +1 -1
- package/dist/session/store.d.ts.map +1 -1
- package/dist/session/store.js +9 -1
- package/dist/session/store.js.map +1 -1
- package/dist/share/session-image-catalog.d.ts +22 -0
- package/dist/share/session-image-catalog.d.ts.map +1 -0
- package/dist/share/session-image-catalog.js +151 -0
- package/dist/share/session-image-catalog.js.map +1 -0
- package/dist/share/share-store.d.ts +137 -0
- package/dist/share/share-store.d.ts.map +1 -0
- package/dist/share/share-store.js +340 -0
- package/dist/share/share-store.js.map +1 -0
- package/dist/types.d.ts +17 -7
- package/dist/types.d.ts.map +1 -1
- package/mcp/instructions.ts +1 -0
- package/mcp/server.ts +2 -0
- package/mcp/tools/image/module.ts +240 -26
- package/mcp/tools/line/module.ts +186 -0
- package/mcp/tools/share-image/module.ts +120 -0
- package/mcp/tools/shared/share-client.ts +197 -0
- 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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"}
|