@0xmaxma/claude-gateway 1.8.4 → 1.8.6

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 (71) hide show
  1. package/README.md +11 -2
  2. package/config.template.json +5 -3
  3. package/dist/agent/model-catalog.d.ts.map +1 -1
  4. package/dist/agent/model-catalog.js +5 -13
  5. package/dist/agent/model-catalog.js.map +1 -1
  6. package/dist/agent/runner.d.ts +1 -1
  7. package/dist/agent/runner.d.ts.map +1 -1
  8. package/dist/agent/runner.js +22 -3
  9. package/dist/agent/runner.js.map +1 -1
  10. package/dist/api/apps-router.d.ts.map +1 -1
  11. package/dist/api/apps-router.js +31 -9
  12. package/dist/api/apps-router.js.map +1 -1
  13. package/dist/api/gateway-router.js +1 -1
  14. package/dist/api/gateway-router.js.map +1 -1
  15. package/dist/api/packages.d.ts.map +1 -1
  16. package/dist/api/packages.js +17 -1
  17. package/dist/api/packages.js.map +1 -1
  18. package/dist/api/share-router.d.ts +8 -0
  19. package/dist/api/share-router.d.ts.map +1 -1
  20. package/dist/api/share-router.js +100 -10
  21. package/dist/api/share-router.js.map +1 -1
  22. package/dist/apps/agent-manager.d.ts +35 -2
  23. package/dist/apps/agent-manager.d.ts.map +1 -1
  24. package/dist/apps/agent-manager.js +131 -15
  25. package/dist/apps/agent-manager.js.map +1 -1
  26. package/dist/apps/installer.d.ts +49 -1
  27. package/dist/apps/installer.d.ts.map +1 -1
  28. package/dist/apps/installer.js +74 -17
  29. package/dist/apps/installer.js.map +1 -1
  30. package/dist/cli/args.d.ts +6 -0
  31. package/dist/cli/args.d.ts.map +1 -1
  32. package/dist/cli/args.js +9 -1
  33. package/dist/cli/args.js.map +1 -1
  34. package/dist/cli/commands/logs.d.ts.map +1 -1
  35. package/dist/cli/commands/logs.js +91 -18
  36. package/dist/cli/commands/logs.js.map +1 -1
  37. package/dist/cli/commands/service.d.ts.map +1 -1
  38. package/dist/cli/commands/service.js +61 -7
  39. package/dist/cli/commands/service.js.map +1 -1
  40. package/dist/cli/index.d.ts.map +1 -1
  41. package/dist/cli/index.js +12 -3
  42. package/dist/cli/index.js.map +1 -1
  43. package/dist/cli/output.d.ts +34 -0
  44. package/dist/cli/output.d.ts.map +1 -1
  45. package/dist/cli/output.js +56 -10
  46. package/dist/cli/output.js.map +1 -1
  47. package/dist/config/claude-settings.d.ts +41 -0
  48. package/dist/config/claude-settings.d.ts.map +1 -0
  49. package/dist/config/claude-settings.js +102 -0
  50. package/dist/config/claude-settings.js.map +1 -0
  51. package/dist/index.js +10 -1
  52. package/dist/index.js.map +1 -1
  53. package/dist/session/process.d.ts +43 -1
  54. package/dist/session/process.d.ts.map +1 -1
  55. package/dist/session/process.js +154 -9
  56. package/dist/session/process.js.map +1 -1
  57. package/dist/share/share-store.d.ts +45 -4
  58. package/dist/share/share-store.d.ts.map +1 -1
  59. package/dist/share/share-store.js +127 -23
  60. package/dist/share/share-store.js.map +1 -1
  61. package/dist/shutdown-signals.d.ts +18 -0
  62. package/dist/shutdown-signals.d.ts.map +1 -1
  63. package/dist/shutdown-signals.js +33 -2
  64. package/dist/shutdown-signals.js.map +1 -1
  65. package/mcp/server.ts +2 -2
  66. package/mcp/tools/image/module.ts +10 -3
  67. package/mcp/tools/share-file/module.ts +169 -0
  68. package/mcp/tools/shared/share-client.ts +8 -4
  69. package/mcp/tools/telegram/receiver-server.ts +12 -2
  70. package/package.json +1 -1
  71. package/mcp/tools/share-image/module.ts +0 -120
@@ -0,0 +1,169 @@
1
+ import type { ToolModule, McpToolDefinition, McpToolResult, ToolVisibility } from '../../types';
2
+ import {
3
+ ShareClientError,
4
+ createShares,
5
+ revokeShare,
6
+ shareBridgeEnabled,
7
+ type ShareRef,
8
+ } from '../shared/share-client';
9
+
10
+ /**
11
+ * Standalone share_file MCP tool (#70, plan §15) — explicit create/revoke of
12
+ * short-lived public URLs for a file in the agent's media directory. There is
13
+ * deliberately NO list action (shares are unenumerable, §10). generate_image
14
+ * auto-normalizes refs through the same gateway API, so normal image editing
15
+ * never needs this tool; it exists for explicit workflows (e.g. handing a
16
+ * temporary URL to an external service).
17
+ *
18
+ * #444: was `share_image` back when the bridge could only carry images. The
19
+ * old name stays registered as a thin alias — it is baked into agent
20
+ * workspaces (AGENTS.md, skills, memories) that we do not get to rewrite.
21
+ */
22
+ export class ShareFileModule implements ToolModule {
23
+ id = 'share-file';
24
+ toolVisibility: ToolVisibility = 'all-configured';
25
+
26
+ isEnabled(): boolean {
27
+ return shareBridgeEnabled();
28
+ }
29
+
30
+ getTools(): McpToolDefinition[] {
31
+ return shareFileToolDefs;
32
+ }
33
+
34
+ async handleTool(name: string, args: Record<string, unknown>): Promise<McpToolResult> {
35
+ if (name !== 'share_file' && name !== LEGACY_TOOL_NAME) {
36
+ return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
37
+ }
38
+ const legacy = name === LEGACY_TOOL_NAME;
39
+ const action = typeof args.action === 'string' ? args.action : 'create';
40
+ switch (action) {
41
+ case 'create':
42
+ return this.handleCreate(args, legacy);
43
+ case 'revoke':
44
+ return this.handleRevoke(args);
45
+ default:
46
+ return {
47
+ content: [{ type: 'text', text: `share_file: unknown action "${action}" (expected create | revoke)` }],
48
+ isError: true,
49
+ };
50
+ }
51
+ }
52
+
53
+ private async handleCreate(args: Record<string, unknown>, legacy: boolean): Promise<McpToolResult> {
54
+ const single = typeof args.path === 'string' && args.path.trim() ? [args.path.trim()] : [];
55
+ const many = Array.isArray(args.paths)
56
+ ? (args.paths.filter((p) => typeof p === 'string' && p.trim()) as string[])
57
+ : [];
58
+ if (single.length && many.length) {
59
+ return { content: [{ type: 'text', text: 'share_file: pass either "path" or "paths", not both.' }], isError: true };
60
+ }
61
+ const paths = single.length ? single : many;
62
+ if (!paths.length) {
63
+ return { content: [{ type: 'text', text: 'share_file: action="create" requires "path" or "paths".' }], isError: true };
64
+ }
65
+ const refs: ShareRef[] = paths.map((p) =>
66
+ p.startsWith('artifact:') ? { artifact_id: p.slice('artifact:'.length) } : { path: p },
67
+ );
68
+ const opts: { purpose?: string; ttlSeconds?: number; allowDocuments: boolean } = {
69
+ // share_file is the agent's explicit "publish this file" verb, so it opts
70
+ // into the full share allowlist (images + PDF). The legacy share_image
71
+ // name does NOT: an instruction written against it meant "share an
72
+ // image", and widening it would retroactively change what an existing
73
+ // call site's live token can serve — a share minted on chart.png would
74
+ // start serving a PDF if the file were later overwritten, where before it
75
+ // failed closed with a 404. The narrow image consumers (line_image,
76
+ // generate_image ref normalization) stay strict for the same reason.
77
+ allowDocuments: !legacy,
78
+ };
79
+ if (typeof args.purpose === 'string' && args.purpose.trim()) opts.purpose = args.purpose.trim();
80
+ if (typeof args.ttl_seconds === 'number' && args.ttl_seconds > 0) opts.ttlSeconds = args.ttl_seconds;
81
+ try {
82
+ const items = await createShares(refs, opts);
83
+ return { content: [{ type: 'text', text: JSON.stringify({ items }, null, 2) }] };
84
+ } catch (err) {
85
+ return this.mapError(err, legacy);
86
+ }
87
+ }
88
+
89
+ private async handleRevoke(args: Record<string, unknown>): Promise<McpToolResult> {
90
+ const shareId = typeof args.share_id === 'string' ? args.share_id.trim() : '';
91
+ if (!shareId) {
92
+ return { content: [{ type: 'text', text: 'share_file: action="revoke" requires "share_id".' }], isError: true };
93
+ }
94
+ try {
95
+ await revokeShare(shareId);
96
+ return { content: [{ type: 'text', text: JSON.stringify({ revoked: true, share_id: shareId }) }] };
97
+ } catch (err) {
98
+ return this.mapError(err);
99
+ }
100
+ }
101
+
102
+ private mapError(err: unknown, legacy = false): McpToolResult {
103
+ if (err instanceof ShareClientError) {
104
+ // Without this the agent hits a dead end: it asked the image-only name to
105
+ // share a PDF and gets a bare 415 with no hint that a wider tool exists.
106
+ const hint =
107
+ legacy && err.code === 'unsupported_file_type'
108
+ ? ' — share_image is image-only; call share_file for documents.'
109
+ : '';
110
+ return { content: [{ type: 'text', text: `share_file: ${err.code}: ${err.message}${hint}` }], isError: true };
111
+ }
112
+ return {
113
+ content: [{ type: 'text', text: `share_file: gateway unavailable: ${(err as Error).message}` }],
114
+ isError: true,
115
+ };
116
+ }
117
+ }
118
+
119
+ const LEGACY_TOOL_NAME = 'share_image';
120
+
121
+ const shareFileInputSchema = {
122
+ type: 'object',
123
+ properties: {
124
+ action: { type: 'string', enum: ['create', 'revoke'], description: 'create (default) | revoke' },
125
+ path: { type: 'string', description: 'One media path (e.g. "media/session-1/report.pdf") or "artifact:<id>" to share.' },
126
+ paths: { type: 'array', items: { type: 'string' }, description: 'Multiple media paths / artifact refs (max 5).' },
127
+ ttl_seconds: { type: 'number', description: 'Optional lifetime in seconds (default 1800).' },
128
+ purpose: { type: 'string', description: 'Optional purpose tag (default "codex_ref").' },
129
+ share_id: { type: 'string', description: 'Share id to revoke (required for action="revoke").' },
130
+ },
131
+ required: [],
132
+ };
133
+
134
+ // The alias is image-only, so it must not advertise the PDF example: an agent
135
+ // reading its schema would be told a document is acceptable and get a 415.
136
+ // Same shape, image wording — a separate object costs nothing, since both defs
137
+ // are serialized in full either way.
138
+ const shareImageInputSchema = {
139
+ ...shareFileInputSchema,
140
+ properties: {
141
+ ...shareFileInputSchema.properties,
142
+ path: { type: 'string', description: 'One media path (e.g. "media/session-1/chart.png") or "artifact:<id>" to share.' },
143
+ paths: { type: 'array', items: { type: 'string' }, description: 'Multiple media image paths / artifact refs (max 5).' },
144
+ },
145
+ };
146
+
147
+ const shareFileToolDefs: McpToolDefinition[] = [
148
+ {
149
+ name: 'share_file',
150
+ description:
151
+ 'Create or revoke a SHORT-LIVED public URL for a file in this agent\'s media directory (PNG, JPEG, WebP or PDF). ' +
152
+ 'action="create" takes "path" (one media path or artifact:<id>) or "paths" (up to 5) and returns ' +
153
+ '{ share_id, url, expires_at } per file — the URL needs no auth and expires automatically (default 30 min). ' +
154
+ 'action="revoke" takes "share_id" and invalidates the URL immediately. ' +
155
+ 'You normally do NOT need this for image editing: generate_image converts local/artifact references ' +
156
+ 'to share URLs automatically. There is no list action.',
157
+ inputSchema: shareFileInputSchema,
158
+ },
159
+ {
160
+ // Deprecated #444 alias, kept so agent instructions written against the old
161
+ // name keep working — and kept IMAGE-ONLY, which is what those instructions
162
+ // meant and what the name still promises. Description is deliberately one
163
+ // line: it must not cost a second copy of the real tool's context budget.
164
+ name: LEGACY_TOOL_NAME,
165
+ description:
166
+ 'Deprecated image-only alias for share_file — use share_file instead, which also shares PDFs.',
167
+ inputSchema: shareImageInputSchema,
168
+ },
169
+ ];
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Image share bridge client (#70) — the ONLY way MCP subprocesses touch the
3
- * share/artifact store. They call the authenticated local Gateway HTTP API
2
+ * File share bridge client (#70, widened past images in #444) — the ONLY way
3
+ * MCP subprocesses touch the share/artifact store. They call the authenticated
4
+ * local Gateway HTTP API
4
5
  * (GATEWAY_API_URL + GATEWAY_API_KEY, both already injected into the session
5
6
  * subprocess env) and NEVER open the SQLite DB themselves (plan §9/§10).
6
7
  *
@@ -53,7 +54,7 @@ export type CatalogItem = {
53
54
  desc?: string;
54
55
  };
55
56
 
56
- /** Error carrying the gateway's stable machine-readable code (e.g. image_ref_not_found). */
57
+ /** Error carrying the gateway's stable machine-readable code (e.g. share_ref_not_found). */
57
58
  export class ShareClientError extends Error {
58
59
  readonly code: string;
59
60
  readonly status: number;
@@ -109,7 +110,7 @@ async function callGateway(
109
110
  /** Mint short-lived share URLs for local/artifact refs — order preserved. */
110
111
  export async function createShares(
111
112
  refs: ShareRef[],
112
- opts: { purpose?: string; ttlSeconds?: number } = {},
113
+ opts: { purpose?: string; ttlSeconds?: number; allowDocuments?: boolean } = {},
113
114
  ): Promise<ShareItem[]> {
114
115
  const body: Record<string, unknown> = {
115
116
  agent_id: process.env.GATEWAY_AGENT_ID,
@@ -118,6 +119,9 @@ export async function createShares(
118
119
  refs,
119
120
  };
120
121
  if (opts.ttlSeconds !== undefined) body.ttl_seconds = opts.ttlSeconds;
122
+ // Opt-in only: image callers (line_image, generate_image refs) must keep
123
+ // getting a hard rejection for a non-image ref.
124
+ if (opts.allowDocuments) body.allow_documents = true;
121
125
  const { status, json } = await callGateway('POST', '/api/v1/shares', body);
122
126
  if (status !== 201) {
123
127
  const code = typeof json.code === 'string' ? json.code : 'share_failed';
@@ -884,12 +884,22 @@ const BOT_COMMANDS = [
884
884
  // reached only when the gateway call itself fails, so it is two fallbacks deep
885
885
  // and will drift — keep it roughly in step with DEFAULT_MODELS
886
886
  // (src/agent/runner.ts) and config.template.json gateway.models.
887
+ //
888
+ // `alias` is never read in this process: validModelRows() keeps only id and
889
+ // label, and the picker's callback data is `model:<id>`. It is carried anyway
890
+ // so this list stays diffable against the two registries it mirrors, and so
891
+ // the cross-registry sync test can compare alias mappings — that test is the
892
+ // only thing standing between this copy and the silent drift that already lost
893
+ // it Opus 5 once. Do not drop the column to "clean up"; it would blind the
894
+ // guard, not simplify it.
887
895
  const AVAILABLE_MODELS = [
888
- { id: 'claude-fable-5[1m]', label: 'Fable 5 (1M)', alias: 'fable[1m]' },
896
+ { id: 'claude-fable-5-1[1m]', label: 'Fable 5.1 (1M)', alias: 'fable[1m]' },
897
+ { id: 'claude-fable-5[1m]', label: 'Fable 5 (1M)', alias: 'fable5[1m]' },
889
898
  { id: 'claude-opus-5[1m]', label: 'Opus 5 (1M)', alias: 'opus[1m]' },
890
899
  { id: 'claude-opus-4-8[1m]', label: 'Opus 4.8 (1M)', alias: 'opus48[1m]' },
891
900
  { id: 'claude-sonnet-5[1m]', label: 'Sonnet 5 (1M)', alias: 'sonnet[1m]' },
892
- { id: 'claude-fable-5', label: 'Fable 5', alias: 'fable' },
901
+ { id: 'claude-fable-5-1', label: 'Fable 5.1', alias: 'fable' },
902
+ { id: 'claude-fable-5', label: 'Fable 5', alias: 'fable5' },
893
903
  { id: 'claude-opus-5', label: 'Opus 5', alias: 'opus' },
894
904
  { id: 'claude-opus-4-8', label: 'Opus 4.8', alias: 'opus48' },
895
905
  { id: 'claude-opus-4-6', label: 'Opus 4.6', alias: 'opus46' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xmaxma/claude-gateway",
3
- "version": "1.8.4",
3
+ "version": "1.8.6",
4
4
  "description": "Multi-agent gateway for Claude",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,120 +0,0 @@
1
- import type { ToolModule, McpToolDefinition, McpToolResult, ToolVisibility } from '../../types';
2
- import {
3
- ShareClientError,
4
- createShares,
5
- revokeShare,
6
- shareBridgeEnabled,
7
- type ShareRef,
8
- } from '../shared/share-client';
9
-
10
- /**
11
- * Standalone share_image MCP tool (#70, plan §15) — explicit create/revoke of
12
- * short-lived public image URLs. There is deliberately NO list action (shares
13
- * are unenumerable, §10). generate_image auto-normalizes refs through the same
14
- * gateway API, so normal image editing never needs this tool; it exists for
15
- * explicit workflows (e.g. handing a temporary URL to an external service).
16
- */
17
- export class ShareImageModule implements ToolModule {
18
- id = 'share-image';
19
- toolVisibility: ToolVisibility = 'all-configured';
20
-
21
- isEnabled(): boolean {
22
- return shareBridgeEnabled();
23
- }
24
-
25
- getTools(): McpToolDefinition[] {
26
- return shareImageToolDefs;
27
- }
28
-
29
- async handleTool(name: string, args: Record<string, unknown>): Promise<McpToolResult> {
30
- if (name !== 'share_image') {
31
- return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
32
- }
33
- const action = typeof args.action === 'string' ? args.action : 'create';
34
- switch (action) {
35
- case 'create':
36
- return this.handleCreate(args);
37
- case 'revoke':
38
- return this.handleRevoke(args);
39
- default:
40
- return {
41
- content: [{ type: 'text', text: `share_image: unknown action "${action}" (expected create | revoke)` }],
42
- isError: true,
43
- };
44
- }
45
- }
46
-
47
- private async handleCreate(args: Record<string, unknown>): Promise<McpToolResult> {
48
- const single = typeof args.path === 'string' && args.path.trim() ? [args.path.trim()] : [];
49
- const many = Array.isArray(args.paths)
50
- ? (args.paths.filter((p) => typeof p === 'string' && p.trim()) as string[])
51
- : [];
52
- if (single.length && many.length) {
53
- return { content: [{ type: 'text', text: 'share_image: pass either "path" or "paths", not both.' }], isError: true };
54
- }
55
- const paths = single.length ? single : many;
56
- if (!paths.length) {
57
- return { content: [{ type: 'text', text: 'share_image: action="create" requires "path" or "paths".' }], isError: true };
58
- }
59
- const refs: ShareRef[] = paths.map((p) =>
60
- p.startsWith('artifact:') ? { artifact_id: p.slice('artifact:'.length) } : { path: p },
61
- );
62
- const opts: { purpose?: string; ttlSeconds?: number } = {};
63
- if (typeof args.purpose === 'string' && args.purpose.trim()) opts.purpose = args.purpose.trim();
64
- if (typeof args.ttl_seconds === 'number' && args.ttl_seconds > 0) opts.ttlSeconds = args.ttl_seconds;
65
- try {
66
- const items = await createShares(refs, opts);
67
- return { content: [{ type: 'text', text: JSON.stringify({ items }, null, 2) }] };
68
- } catch (err) {
69
- return this.mapError(err);
70
- }
71
- }
72
-
73
- private async handleRevoke(args: Record<string, unknown>): Promise<McpToolResult> {
74
- const shareId = typeof args.share_id === 'string' ? args.share_id.trim() : '';
75
- if (!shareId) {
76
- return { content: [{ type: 'text', text: 'share_image: action="revoke" requires "share_id".' }], isError: true };
77
- }
78
- try {
79
- await revokeShare(shareId);
80
- return { content: [{ type: 'text', text: JSON.stringify({ revoked: true, share_id: shareId }) }] };
81
- } catch (err) {
82
- return this.mapError(err);
83
- }
84
- }
85
-
86
- private mapError(err: unknown): McpToolResult {
87
- if (err instanceof ShareClientError) {
88
- return { content: [{ type: 'text', text: `share_image: ${err.code}: ${err.message}` }], isError: true };
89
- }
90
- return {
91
- content: [{ type: 'text', text: `share_image: gateway unavailable: ${(err as Error).message}` }],
92
- isError: true,
93
- };
94
- }
95
- }
96
-
97
- const shareImageToolDefs: McpToolDefinition[] = [
98
- {
99
- name: 'share_image',
100
- description:
101
- 'Create or revoke a SHORT-LIVED public URL for an image in this agent\'s media directory. ' +
102
- 'action="create" takes "path" (one media path or artifact:<id>) or "paths" (up to 5) and returns ' +
103
- '{ share_id, url, expires_at } per image — the URL needs no auth and expires automatically (default 30 min). ' +
104
- 'action="revoke" takes "share_id" and invalidates the URL immediately. ' +
105
- 'You normally do NOT need this for image editing: generate_image converts local/artifact references ' +
106
- 'to share URLs automatically. There is no list action.',
107
- inputSchema: {
108
- type: 'object',
109
- properties: {
110
- action: { type: 'string', enum: ['create', 'revoke'], description: 'create (default) | revoke' },
111
- path: { type: 'string', description: 'One media path (e.g. "media/session-1/image.png") or "artifact:<id>" to share.' },
112
- paths: { type: 'array', items: { type: 'string' }, description: 'Multiple media paths / artifact refs (max 5).' },
113
- ttl_seconds: { type: 'number', description: 'Optional lifetime in seconds (default 1800).' },
114
- purpose: { type: 'string', description: 'Optional purpose tag (default "codex_ref").' },
115
- share_id: { type: 'string', description: 'Share id to revoke (required for action="revoke").' },
116
- },
117
- required: [],
118
- },
119
- },
120
- ];