@tom2012/cc-web 2026.5.24-j → 2026.6.8-a

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 (35) hide show
  1. package/backend/dist/mcp/daemon-client.d.ts +16 -0
  2. package/backend/dist/mcp/daemon-client.d.ts.map +1 -0
  3. package/backend/dist/mcp/daemon-client.js +136 -0
  4. package/backend/dist/mcp/daemon-client.js.map +1 -0
  5. package/backend/dist/mcp/server.d.ts +2 -0
  6. package/backend/dist/mcp/server.d.ts.map +1 -0
  7. package/backend/dist/mcp/server.js +33 -0
  8. package/backend/dist/mcp/server.js.map +1 -0
  9. package/backend/dist/mcp/tools.d.ts +12 -0
  10. package/backend/dist/mcp/tools.d.ts.map +1 -0
  11. package/backend/dist/mcp/tools.js +215 -0
  12. package/backend/dist/mcp/tools.js.map +1 -0
  13. package/backend/dist/routes/projects.d.ts.map +1 -1
  14. package/backend/dist/routes/projects.js +79 -0
  15. package/backend/dist/routes/projects.js.map +1 -1
  16. package/backend/package-lock.json +594 -8
  17. package/backend/package.json +1 -0
  18. package/bin/ccweb.js +16 -0
  19. package/frontend/dist/assets/{ChatOverlay-BRgnwGwh.js → ChatOverlay-BCmamNeY.js} +1 -1
  20. package/frontend/dist/assets/{GraphPreview-BVM9gfmS.js → GraphPreview-DDfW4Ga1.js} +1 -1
  21. package/frontend/dist/assets/{MobilePage-B9a2MiNi.js → MobilePage-DlrSEbwf.js} +3 -3
  22. package/frontend/dist/assets/{OfficePreview-kRfFYNJL.js → OfficePreview-BAyVAKCq.js} +2 -2
  23. package/frontend/dist/assets/{PdfPreview-BoQe2z7m.js → PdfPreview-JZP_GIo5.js} +1 -1
  24. package/frontend/dist/assets/{ProjectPage-0A4qihgf.js → ProjectPage-D6XI_syc.js} +3 -3
  25. package/frontend/dist/assets/{SettingsPage-CKzoera_.js → SettingsPage-Cesy5VBi.js} +1 -1
  26. package/frontend/dist/assets/{SkillHubPage-Cm6IFJEd.js → SkillHubPage-D-LrJx4v.js} +1 -1
  27. package/frontend/dist/assets/{chevron-down-BKjdR_9i.js → chevron-down-BNy_ld5o.js} +1 -1
  28. package/frontend/dist/assets/{index-W_XJ77yr.js → index-BUBRc674.js} +2 -2
  29. package/frontend/dist/assets/{index-BvT4ZzpF.js → index-Cs28h9_C.js} +1 -1
  30. package/frontend/dist/assets/{index-CEbDU5Rb.js → index-DxDu5WkP.js} +1 -1
  31. package/frontend/dist/assets/{jszip.min-nwjCmvsr.js → jszip.min--CjHKRwc.js} +1 -1
  32. package/frontend/dist/assets/{search-DCMeG4M-.js → search-BUgyHPYV.js} +1 -1
  33. package/frontend/dist/assets/{select-B98b-7vv.js → select-opuUXbEP.js} +1 -1
  34. package/frontend/dist/index.html +1 -1
  35. package/package.json +1 -1
@@ -0,0 +1,16 @@
1
+ export declare class DaemonClient {
2
+ private port;
3
+ private token;
4
+ private baseUrl;
5
+ constructor();
6
+ /** Fetch a fresh local token. Throws if daemon is unreachable or rejects. */
7
+ private fetchToken;
8
+ private ensureToken;
9
+ /**
10
+ * JSON-in / JSON-out request. Retries once on 401 (token expired between
11
+ * MCP-server start and the call).
12
+ */
13
+ request<T = unknown>(method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', path: string, body?: unknown): Promise<T>;
14
+ }
15
+ export declare function getDaemonClient(): DaemonClient;
16
+ //# sourceMappingURL=daemon-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-client.d.ts","sourceRoot":"","sources":["../../src/mcp/daemon-client.ts"],"names":[],"mappings":"AAgCA,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,OAAO,CAAS;;IAOxB,6EAA6E;YAC/D,UAAU;YAUV,WAAW;IAMzB;;;OAGG;IACG,OAAO,CAAC,CAAC,GAAG,OAAO,EACvB,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,EACnD,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,GACb,OAAO,CAAC,CAAC,CAAC;CA4Bd;AAGD,wBAAgB,eAAe,IAAI,YAAY,CAG9C"}
@@ -0,0 +1,136 @@
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.DaemonClient = void 0;
37
+ exports.getDaemonClient = getDaemonClient;
38
+ /**
39
+ * HTTP client for the running ccweb daemon, used by the MCP server.
40
+ *
41
+ * Token acquisition:
42
+ * The MCP server runs as a child of the user's CLI on the same machine, so
43
+ * it can hit /api/auth/local-token without credentials (isLocalRequest gate).
44
+ * Token TTL = 30d (matches generateLocalToken), refresh on 401.
45
+ *
46
+ * Port discovery:
47
+ * Reads ~/.ccweb/ccweb.port (written by daemon on listen). Falls back to
48
+ * CCWEB_PORT env then 3001. The .port file gets refreshed on every daemon
49
+ * start so the value is reliable as long as daemon is running.
50
+ */
51
+ const fs = __importStar(require("fs"));
52
+ const os = __importStar(require("os"));
53
+ const path = __importStar(require("path"));
54
+ function dataDir() {
55
+ return process.env.CCWEB_DATA_DIR || path.join(os.homedir(), '.ccweb');
56
+ }
57
+ function resolvePort() {
58
+ const envPort = parseInt(process.env.CCWEB_PORT || '', 10);
59
+ if (Number.isFinite(envPort) && envPort > 0)
60
+ return envPort;
61
+ try {
62
+ const raw = fs.readFileSync(path.join(dataDir(), 'ccweb.port'), 'utf8').trim();
63
+ const n = parseInt(raw, 10);
64
+ if (Number.isFinite(n) && n > 0)
65
+ return n;
66
+ }
67
+ catch { /* ignore */ }
68
+ return 3001;
69
+ }
70
+ class DaemonClient {
71
+ constructor() {
72
+ this.token = null;
73
+ this.port = resolvePort();
74
+ this.baseUrl = `http://127.0.0.1:${this.port}`;
75
+ }
76
+ /** Fetch a fresh local token. Throws if daemon is unreachable or rejects. */
77
+ async fetchToken() {
78
+ const res = await fetch(`${this.baseUrl}/api/auth/local-token`);
79
+ if (!res.ok) {
80
+ throw new Error(`Failed to obtain local token: HTTP ${res.status} ${await res.text().catch(() => '')}`);
81
+ }
82
+ const data = (await res.json());
83
+ if (!data.token)
84
+ throw new Error('Daemon returned no token');
85
+ return data.token;
86
+ }
87
+ async ensureToken() {
88
+ if (this.token)
89
+ return this.token;
90
+ this.token = await this.fetchToken();
91
+ return this.token;
92
+ }
93
+ /**
94
+ * JSON-in / JSON-out request. Retries once on 401 (token expired between
95
+ * MCP-server start and the call).
96
+ */
97
+ async request(method, path, body) {
98
+ const exec = async () => {
99
+ const token = await this.ensureToken();
100
+ return fetch(`${this.baseUrl}${path}`, {
101
+ method,
102
+ headers: {
103
+ Authorization: `Bearer ${token}`,
104
+ ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),
105
+ },
106
+ body: body !== undefined ? JSON.stringify(body) : undefined,
107
+ });
108
+ };
109
+ let res = await exec();
110
+ if (res.status === 401) {
111
+ this.token = null;
112
+ res = await exec();
113
+ }
114
+ if (!res.ok) {
115
+ let detail = '';
116
+ try {
117
+ detail = (await res.json()).error || '';
118
+ }
119
+ catch { /* ignore */ }
120
+ throw new Error(`HTTP ${res.status}${detail ? `: ${detail}` : ''}`);
121
+ }
122
+ // 204 / empty body
123
+ const text = await res.text();
124
+ if (!text)
125
+ return undefined;
126
+ return JSON.parse(text);
127
+ }
128
+ }
129
+ exports.DaemonClient = DaemonClient;
130
+ let _instance = null;
131
+ function getDaemonClient() {
132
+ if (!_instance)
133
+ _instance = new DaemonClient();
134
+ return _instance;
135
+ }
136
+ //# sourceMappingURL=daemon-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-client.js","sourceRoot":"","sources":["../../src/mcp/daemon-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":""}
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * Entry point for `ccweb mcp` — a stdio MCP server that proxies calls to the
5
+ * running ccweb daemon on the same host.
6
+ *
7
+ * Why a separate process: MCP clients (Claude Code / Codex / Cursor) launch
8
+ * the server as a stdio child. Keeping it out of the daemon means:
9
+ * - daemon never needs to be MCP-aware
10
+ * - server lifecycle = MCP client lifecycle (clean shutdown when client exits)
11
+ * - one daemon serves multiple concurrent MCP clients without contention
12
+ *
13
+ * stdout is reserved for the MCP JSON-RPC framing. All diagnostics MUST go
14
+ * to stderr (console.error). console.log would corrupt the protocol stream.
15
+ */
16
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
17
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
18
+ const tools_js_1 = require("./tools.js");
19
+ async function main() {
20
+ const server = new mcp_js_1.McpServer({
21
+ name: 'ccweb',
22
+ version: '1.0.0',
23
+ });
24
+ (0, tools_js_1.registerTools)(server);
25
+ const transport = new stdio_js_1.StdioServerTransport();
26
+ await server.connect(transport);
27
+ console.error('[ccweb-mcp] ready (stdio)');
28
+ }
29
+ main().catch((err) => {
30
+ console.error('[ccweb-mcp] fatal:', err);
31
+ process.exit(1);
32
+ });
33
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":";;AAAA;;;;;;;;;;;;GAYG;AACH,oEAAoE;AACpE,wEAAiF;AACjF,yCAA2C;AAE3C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,IAAI,kBAAS,CAAC;QAC3B,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,IAAA,wBAAa,EAAC,MAAM,CAAC,CAAC;IAEtB,MAAM,SAAS,GAAG,IAAI,+BAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;AAC7C,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC;IACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * MCP tool registry. Each tool wraps one daemon-side REST endpoint with a
3
+ * Zod input schema (validated by McpServer) + a JSON response payload.
4
+ *
5
+ * Tools return `{ content: [{ type: 'text', text: string }] }` per the MCP
6
+ * spec. For structured data, we serialize JSON into the text block — most
7
+ * MCP clients (Claude Code / Codex) display it raw or feed it back to the
8
+ * model. We also set isError=true on failures so the model sees the failure.
9
+ */
10
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
11
+ export declare function registerTools(server: McpServer): void;
12
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAsCpE,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2QrD"}
@@ -0,0 +1,215 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerTools = registerTools;
4
+ const zod_1 = require("zod");
5
+ const daemon_client_js_1 = require("./daemon-client.js");
6
+ const PROJECT_ID = zod_1.z
7
+ .string()
8
+ .regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, 'projectId must be a UUID');
9
+ function ok(payload) {
10
+ return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
11
+ }
12
+ function err(message) {
13
+ return { content: [{ type: 'text', text: message }], isError: true };
14
+ }
15
+ async function safe(fn) {
16
+ try {
17
+ return await fn();
18
+ }
19
+ catch (e) {
20
+ return err(e instanceof Error ? e.message : String(e));
21
+ }
22
+ }
23
+ function registerTools(server) {
24
+ const client = (0, daemon_client_js_1.getDaemonClient)();
25
+ // ─── projects ─────────────────────────────────────────────────────────────
26
+ server.registerTool('list_projects', {
27
+ title: 'List projects',
28
+ description: 'List all projects visible to the caller. Filter by `archived: true|false`. Omitting `archived` returns every project (active + archived).',
29
+ inputSchema: { archived: zod_1.z.boolean().optional() },
30
+ }, async ({ archived }) => safe(async () => {
31
+ const projects = await client.request('GET', '/api/projects');
32
+ const filtered = archived === undefined
33
+ ? projects
34
+ : projects.filter((p) => !!p.archived === archived);
35
+ return ok(filtered.map((p) => ({
36
+ id: p.id,
37
+ name: p.name,
38
+ folderPath: p.folderPath,
39
+ cliTool: p.cliTool,
40
+ archived: !!p.archived,
41
+ status: p.status,
42
+ tags: p.tags ?? [],
43
+ })));
44
+ }));
45
+ server.registerTool('archive_project', {
46
+ title: 'Archive a project',
47
+ description: 'Move a project to the archived list. Stops its terminal first.',
48
+ inputSchema: { projectId: PROJECT_ID },
49
+ }, async ({ projectId }) => safe(async () => {
50
+ const result = await client.request('PATCH', `/api/projects/${projectId}/archive`);
51
+ return ok({ id: result.id, name: result.name, archived: !!result.archived });
52
+ }));
53
+ server.registerTool('unarchive_project', {
54
+ title: 'Unarchive a project',
55
+ description: 'Move a project back from the archived list. Does not auto-start its terminal.',
56
+ inputSchema: { projectId: PROJECT_ID },
57
+ }, async ({ projectId }) => safe(async () => {
58
+ const result = await client.request('PATCH', `/api/projects/${projectId}/unarchive`);
59
+ return ok({ id: result.id, name: result.name, archived: !!result.archived });
60
+ }));
61
+ // ─── filesystem ───────────────────────────────────────────────────────────
62
+ server.registerTool('list_files', {
63
+ title: 'List a project directory',
64
+ description: 'List files and directories inside the project folder. `subPath` is relative to the project root; omit to list root.',
65
+ inputSchema: { projectId: PROJECT_ID, subPath: zod_1.z.string().optional() },
66
+ }, async ({ projectId, subPath }) => safe(async () => {
67
+ const projects = await client.request('GET', '/api/projects');
68
+ const project = projects.find((p) => p.id === projectId);
69
+ if (!project)
70
+ return err(`Project ${projectId} not found`);
71
+ const target = subPath
72
+ ? `${project.folderPath}/${subPath}`.replace(/\/+/g, '/')
73
+ : project.folderPath;
74
+ const result = await client.request('GET', `/api/filesystem?path=${encodeURIComponent(target)}`);
75
+ return ok({
76
+ path: result.path,
77
+ entries: result.entries.map((e) => ({
78
+ name: e.name,
79
+ type: e.type,
80
+ relativePath: e.path.startsWith(project.folderPath + '/')
81
+ ? e.path.slice(project.folderPath.length + 1)
82
+ : e.path,
83
+ })),
84
+ });
85
+ }));
86
+ server.registerTool('read_file', {
87
+ title: 'Read a file from a project',
88
+ description: 'Read a UTF-8 text file from the project. `path` may be absolute or relative to the project root. Files > 5 MB or binary return metadata only (no content).',
89
+ inputSchema: { projectId: PROJECT_ID, path: zod_1.z.string() },
90
+ }, async ({ projectId, path: filePath }) => safe(async () => {
91
+ const projects = await client.request('GET', '/api/projects');
92
+ const project = projects.find((p) => p.id === projectId);
93
+ if (!project)
94
+ return err(`Project ${projectId} not found`);
95
+ const target = filePath.startsWith('/')
96
+ ? filePath
97
+ : `${project.folderPath}/${filePath}`;
98
+ const result = await client.request('GET', `/api/filesystem/file?path=${encodeURIComponent(target)}`);
99
+ if (result.tooLarge) {
100
+ return ok({
101
+ path: result.path,
102
+ size: result.size,
103
+ tooLarge: true,
104
+ note: 'File exceeds the 5 MB inline read limit. Read it from disk directly or have the user split it before retrying.',
105
+ });
106
+ }
107
+ if (result.binary) {
108
+ return ok({
109
+ path: result.path,
110
+ size: result.size,
111
+ binary: true,
112
+ note: 'File contains binary data (null bytes in first 8KB). Cannot return as text.',
113
+ });
114
+ }
115
+ return ok({ path: result.path, size: result.size, content: result.content });
116
+ }));
117
+ // ─── memory ───────────────────────────────────────────────────────────────
118
+ server.registerTool('read_memory', {
119
+ title: 'Read project memory prompts',
120
+ description: 'List all memory prompts (CLAUDE.md / AGENTS.md fragments) for a project, including which are currently inserted into the instructions file.',
121
+ inputSchema: { projectId: PROJECT_ID },
122
+ }, async ({ projectId }) => safe(async () => {
123
+ const result = await client.request('GET', `/api/memory/project/${projectId}`);
124
+ return ok({
125
+ instructionsFilename: result.instructionsFilename,
126
+ instructionsFileLineCount: result.claudeMdLineCount,
127
+ prompts: result.items.map((p) => ({
128
+ filename: p.filename,
129
+ inserted: p.inserted,
130
+ body: p.body,
131
+ })),
132
+ });
133
+ }));
134
+ // ─── LLM I/O ──────────────────────────────────────────────────────────────
135
+ server.registerTool('send_to_llm', {
136
+ title: 'Send a prompt to a project\'s LLM',
137
+ description: 'Send text into the project\'s CLI session (paste-mode by default, which submits via Enter). Returns `{ ok, sentAt }` immediately — does NOT wait for the LLM to respond. Pass the returned `sentAt` to `wait_for_llm` so it can recognize the new assistant turn even if it completes very quickly. Use mode="raw" for slash commands like "/help" that need to be typed line-start.',
138
+ inputSchema: {
139
+ projectId: PROJECT_ID,
140
+ text: zod_1.z.string(),
141
+ mode: zod_1.z.enum(['paste', 'raw']).optional(),
142
+ },
143
+ }, async ({ projectId, text, mode }) => safe(async () => {
144
+ const result = await client.request('POST', `/api/projects/${projectId}/send-input`, { text, mode });
145
+ return ok({ ok: result.ok, sentAt: result.sentAt });
146
+ }));
147
+ server.registerTool('wait_for_llm', {
148
+ title: 'Wait for the LLM to finish a turn',
149
+ description: 'Wait for the project\'s LLM turn to complete, then return the chat blocks the LLM produced. Pass the `sentAt` returned by `send_to_llm` (recommended) — wait_for_llm will return as soon as a NEW assistant ChatBlock with timestamp > sentAt has arrived AND the PTY has been quiet for ~30s. Without sentAt the tool falls back to active→idle edge detection, which can miss turns shorter than the 1s poll interval. `timeoutMs` defaults to 600000 (10 min).',
150
+ inputSchema: {
151
+ projectId: PROJECT_ID,
152
+ sentAt: zod_1.z.string().datetime().optional(),
153
+ timeoutMs: zod_1.z.number().int().positive().max(3600000).optional(),
154
+ limit: zod_1.z.number().int().positive().max(200).optional(),
155
+ },
156
+ }, async ({ projectId, sentAt, timeoutMs, limit }) => safe(async () => {
157
+ const deadline = Date.now() + (timeoutMs ?? 600000);
158
+ const POLL_MS = 1000;
159
+ const sentAtMs = sentAt ? Date.parse(sentAt) : null;
160
+ const historyLimit = limit ?? 10;
161
+ let observedActive = false;
162
+ let sawIdle = false;
163
+ let sawNewAssistant = false;
164
+ // Returns true once we believe the turn the caller anchored is done.
165
+ // Two-conditions when sentAt is provided:
166
+ // 1. there is at least one assistant ChatBlock with timestamp > sentAt
167
+ // 2. semantic-status is currently idle (active=false) — guards against
168
+ // returning between two assistant blocks in the same turn (Claude
169
+ // can emit multiple blocks if it uses tools mid-turn)
170
+ // Falls back to active-edge detection when sentAt is absent.
171
+ const checkDone = async () => {
172
+ const status = await client.request('GET', `/api/projects/${projectId}/semantic-status`);
173
+ if (status.active) {
174
+ observedActive = true;
175
+ sawIdle = false;
176
+ return false;
177
+ }
178
+ // active=false from here on
179
+ if (sentAtMs !== null) {
180
+ // Cheap check first: only fetch history if we haven't yet observed
181
+ // a new assistant block.
182
+ if (!sawNewAssistant) {
183
+ const recent = await client.request('GET', `/api/projects/${projectId}/chat-history?limit=${historyLimit}`);
184
+ sawNewAssistant = recent.blocks.some((b) => b.role === 'assistant' && b.timestamp && Date.parse(b.timestamp) > sentAtMs);
185
+ }
186
+ if (sawNewAssistant) {
187
+ sawIdle = true;
188
+ return true;
189
+ }
190
+ return false;
191
+ }
192
+ // Legacy path: rely on active→idle edge.
193
+ if (observedActive) {
194
+ sawIdle = true;
195
+ return true;
196
+ }
197
+ return false;
198
+ };
199
+ while (Date.now() < deadline) {
200
+ if (await checkDone())
201
+ break;
202
+ await new Promise((r) => setTimeout(r, POLL_MS));
203
+ }
204
+ const history = await client.request('GET', `/api/projects/${projectId}/chat-history?limit=${historyLimit}`);
205
+ const deadlineHit = Date.now() >= deadline;
206
+ return ok({
207
+ observedActive,
208
+ idle: sawIdle,
209
+ sawNewAssistant: sentAtMs !== null ? sawNewAssistant : undefined,
210
+ deadlineHit,
211
+ blocks: history.blocks,
212
+ });
213
+ }));
214
+ }
215
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/routes/projects.ts"],"names":[],"mappings":"AAwCA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAsqBxB,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/routes/projects.ts"],"names":[],"mappings":"AAyCA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA0uBxB,eAAe,MAAM,CAAC"}
@@ -41,6 +41,7 @@ const config_1 = require("../config");
41
41
  const cli_prompt_detector_1 = require("../cli-prompt-detector");
42
42
  const user_prefs_1 = require("../user-prefs");
43
43
  const terminal_manager_1 = require("../terminal-manager");
44
+ const terminal_paste_1 = require("../terminal-paste");
44
45
  const session_manager_1 = require("../session-manager");
45
46
  const adapters_1 = require("../adapters");
46
47
  const chat_backup_1 = require("../chat-backup");
@@ -760,5 +761,83 @@ router.post('/:id/cli-prompt-respond', (req, res) => {
760
761
  res.status(500).json({ error: err.message });
761
762
  }
762
763
  });
764
+ /**
765
+ * POST /api/projects/:id/send-input { text: string, mode?: 'paste' | 'raw' }
766
+ *
767
+ * MCP-friendly entry to inject text into the CLI's PTY. Default 'paste' mode
768
+ * wraps body in bracketed-paste sequence + trailing CR (writeTerminalInputSplit
769
+ * handles the Ink TUI paste-submit timing). 'raw' mode writes text + '\r' as-is
770
+ * — use for slash commands like '/help' which only trigger when typed line-start.
771
+ *
772
+ * Write permission gate matches cli-prompt-respond: owner / admin / edit-share.
773
+ */
774
+ router.post('/:id/send-input', (req, res) => {
775
+ const project = (0, config_1.getProject)(req.params.id);
776
+ if (!project) {
777
+ res.status(404).json({ error: 'Not found' });
778
+ return;
779
+ }
780
+ const username = req.user?.username;
781
+ const isOwner = (0, config_1.isProjectOwner)(project, username);
782
+ const share = project.shares?.find((s) => s.username === username);
783
+ const canWrite = isOwner || (0, config_1.isAdminUser)(username) || share?.permission === 'edit';
784
+ if (!canWrite) {
785
+ res.status(403).json({ error: 'Forbidden' });
786
+ return;
787
+ }
788
+ const body = req.body;
789
+ if (typeof body.text !== 'string') {
790
+ res.status(400).json({ error: 'text must be a string' });
791
+ return;
792
+ }
793
+ const mode = body.mode === 'raw' ? 'raw' : 'paste';
794
+ if (!terminal_manager_1.terminalManager.hasTerminal(req.params.id)) {
795
+ res.status(503).json({ error: 'Terminal is not running for this project' });
796
+ return;
797
+ }
798
+ try {
799
+ if (mode === 'raw') {
800
+ // Strip trailing whitespace + \r\n, append a single \r for submission.
801
+ const trimmed = body.text.replace(/[\r\n\s]+$/, '');
802
+ terminal_manager_1.terminalManager.writeRaw(req.params.id, trimmed + '\r');
803
+ }
804
+ else {
805
+ (0, terminal_paste_1.writeTerminalInputSplit)(req.params.id, (0, terminal_paste_1.buildPaste)(body.text));
806
+ }
807
+ // sentAt anchors `wait_for_llm` — it can match the new assistant block
808
+ // by ChatBlock.timestamp > sentAt, which is robust against turns that
809
+ // finish faster than the 1s semantic-status poll interval.
810
+ res.json({ ok: true, sentAt: new Date().toISOString() });
811
+ }
812
+ catch (err) {
813
+ res.status(500).json({ error: err.message });
814
+ }
815
+ });
816
+ /**
817
+ * GET /api/projects/:id/semantic-status
818
+ *
819
+ * Returns the current semantic snapshot { active, semantic? }. MCP wait_for_llm
820
+ * polls this; `active: false` means the LLM has been quiet for at least
821
+ * SEMANTIC_STALE_MS (30s) — close-enough signal that the current turn finished.
822
+ */
823
+ router.get('/:id/semantic-status', (req, res) => {
824
+ const project = (0, config_1.getProject)(req.params.id);
825
+ if (!project) {
826
+ res.status(404).json({ error: 'Not found' });
827
+ return;
828
+ }
829
+ if (!(0, config_1.isProjectOwner)(project, req.user?.username) &&
830
+ !project.shares?.some((s) => s.username === req.user?.username) &&
831
+ !(0, config_1.isAdminUser)(req.user?.username)) {
832
+ res.status(403).json({ error: 'Forbidden' });
833
+ return;
834
+ }
835
+ const semantic = session_manager_1.sessionManager.getSemanticStatus(req.params.id);
836
+ const fresh = semantic && Date.now() - semantic.updatedAt <= 30000;
837
+ res.json({
838
+ active: !!(semantic && fresh),
839
+ semantic: fresh ? semantic : undefined,
840
+ });
841
+ });
763
842
  exports.default = router;
764
843
  //# sourceMappingURL=projects.js.map