@dinoxx/dinox-cli 2.14.0 → 2.16.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 +2 -0
- package/dist/ai/ragxClient.d.ts +15 -0
- package/dist/ai/ragxClient.js +71 -0
- package/dist/cli.js +27 -3
- package/dist/commands/batch.d.ts +35 -0
- package/dist/commands/batch.js +202 -0
- package/dist/commands/boxes/index.js +21 -21
- package/dist/commands/mcp.d.ts +6 -0
- package/dist/commands/mcp.js +47 -0
- package/dist/commands/notes/actions.js +18 -2
- package/dist/commands/notes/descriptors.js +111 -5
- package/dist/commands/notes/detailContent.d.ts +19 -0
- package/dist/commands/notes/detailContent.js +62 -0
- package/dist/commands/notes/export.js +4 -0
- package/dist/commands/notes/repo/search.d.ts +2 -1
- package/dist/commands/notes/repo/search.js +1 -1
- package/dist/commands/notes/repo/semantic.d.ts +14 -0
- package/dist/commands/notes/repo/semantic.js +31 -0
- package/dist/commands/notes/searchResponse.d.ts +3 -0
- package/dist/commands/notes/searchResponse.js +1 -0
- package/dist/commands/notes/semantic.d.ts +116 -0
- package/dist/commands/notes/semantic.js +332 -0
- package/dist/commands/prompt/index.js +7 -5
- package/dist/commands/savedViews/index.js +12 -3
- package/dist/commands/schema/globalOptions.d.ts +6 -0
- package/dist/commands/schema/globalOptions.js +6 -0
- package/dist/commands/schema/renderSkills.js +5 -2
- package/dist/commands/schema/spec.js +18 -7
- package/dist/commands/storage/index.js +7 -5
- package/dist/commands/sync.js +3 -3
- package/dist/commands/tags/index.js +21 -21
- package/dist/commands/todo/index.js +22 -2
- package/dist/commands/todo/repo.d.ts +5 -0
- package/dist/commands/todo/repo.js +7 -2
- package/dist/commands/writeReceipt.d.ts +13 -0
- package/dist/commands/writeReceipt.js +12 -0
- package/dist/config/serviceEndpoints.d.ts +2 -0
- package/dist/config/serviceEndpoints.js +2 -0
- package/dist/daemon/client.d.ts +2 -0
- package/dist/daemon/client.js +22 -2
- package/dist/daemon/forward.d.ts +4 -1
- package/dist/daemon/forward.js +13 -4
- package/dist/daemon/server.d.ts +1 -1
- package/dist/daemon/server.js +17 -1
- package/dist/daemon/types.d.ts +6 -0
- package/dist/dinox.js +12 -10
- package/dist/gateway/adapters/acp.d.ts +5 -1
- package/dist/gateway/adapters/acp.js +21 -9
- package/dist/gateway/adapters/echo.js +7 -4
- package/dist/gateway/adapters/index.d.ts +1 -0
- package/dist/gateway/adapters/index.js +5 -3
- package/dist/gateway/deviceHost.d.ts +3 -1
- package/dist/gateway/deviceHost.js +75 -5
- package/dist/gateway/mcpCommandTools.d.ts +15 -5
- package/dist/gateway/mcpCommandTools.js +30 -19
- package/dist/gateway/mcpEnv.d.ts +4 -0
- package/dist/gateway/mcpEnv.js +4 -0
- package/dist/gateway/mcpGroups.d.ts +3 -0
- package/dist/gateway/mcpGroups.js +4 -0
- package/dist/gateway/mcpServer.d.ts +10 -1
- package/dist/gateway/mcpServer.js +26 -5
- package/dist/gateway/protocol.d.ts +31 -2
- package/dist/gateway/protocol.js +15 -3
- package/dist/gateway/runExecutor.js +23 -12
- package/dist/gateway/service.d.ts +2 -0
- package/dist/gateway/service.js +29 -4
- package/dist/gateway/threadStore.d.ts +2 -0
- package/dist/gateway/threadStore.js +13 -0
- package/dist/gateway/types.d.ts +5 -4
- package/dist/gateway/workspace.d.ts +5 -0
- package/dist/gateway/workspace.js +30 -0
- package/dist/gateway/workspaceFolders.d.ts +5 -3
- package/dist/gateway/workspaceFolders.js +20 -7
- package/dist/gateway/workspaceListing.d.ts +5 -0
- package/dist/gateway/workspaceListing.js +78 -0
- package/dist/powersync/syncPass.js +13 -1
- package/dist/powersync/tokenIndex.d.ts +2 -0
- package/dist/powersync/tokenIndex.js +1 -0
- package/dist/requests/execute.d.ts +11 -0
- package/dist/requests/execute.js +26 -0
- package/dist/skills/commandRefs.js +2 -10
- package/dist/utils/cursor.d.ts +9 -0
- package/dist/utils/cursor.js +61 -0
- package/dist/utils/defaultFormat.d.ts +8 -0
- package/dist/utils/defaultFormat.js +38 -0
- package/dist/utils/errorCodes.d.ts +4 -0
- package/dist/utils/errorCodes.js +1 -0
- package/dist/utils/output.d.ts +2 -0
- package/dist/utils/output.js +41 -3
- package/dist/utils/progress.d.ts +25 -0
- package/dist/utils/progress.js +59 -0
- package/dist/utils/requestContext.d.ts +8 -0
- package/dist/utils/select.d.ts +20 -0
- package/dist/utils/select.js +92 -0
- package/dist/utils/usageError.d.ts +37 -0
- package/dist/utils/usageError.js +127 -0
- package/package.json +1 -1
|
@@ -140,7 +140,7 @@ export class AcpGatewayAdapter {
|
|
|
140
140
|
};
|
|
141
141
|
input.signal.addEventListener('abort', onAbort, { once: true });
|
|
142
142
|
try {
|
|
143
|
-
const text =
|
|
143
|
+
const text = await input.prompt({ created });
|
|
144
144
|
const prompt = [{ type: 'text', text }];
|
|
145
145
|
for (const resource of input.resources) {
|
|
146
146
|
prompt.push({ type: 'resource_link', uri: resource.uri, name: resource.name, mimeType: resource.mediaType ?? null });
|
|
@@ -170,16 +170,23 @@ export class AcpGatewayAdapter {
|
|
|
170
170
|
turn.sent += 1;
|
|
171
171
|
return turn.sendFile(file);
|
|
172
172
|
}
|
|
173
|
+
catalogExpiresAt = 0;
|
|
174
|
+
catalogRevision = 0;
|
|
175
|
+
probing = null;
|
|
173
176
|
async describeConfig() {
|
|
174
|
-
if (this.catalog)
|
|
177
|
+
if (this.catalog && Date.now() < this.catalogExpiresAt)
|
|
175
178
|
return this.catalog;
|
|
179
|
+
if (!this.probing)
|
|
180
|
+
this.probing = this.probeConfig().finally(() => { this.probing = null; });
|
|
181
|
+
return this.probing;
|
|
182
|
+
}
|
|
183
|
+
async probeConfig() {
|
|
176
184
|
const agent = await this.ensureProcess();
|
|
177
|
-
if (this.catalog)
|
|
178
|
-
return this.catalog;
|
|
179
185
|
// No session yet: open one only to read the agent's choices. It never runs a turn,
|
|
180
186
|
// so close it where the agent allows, or it shows up in the user's session list.
|
|
187
|
+
const revision = this.catalogRevision;
|
|
181
188
|
const created = await agent.connection.newSession({ cwd: this.gateway.cwd ?? os.homedir(), mcpServers: [] });
|
|
182
|
-
this.rememberConfig(agent, created.sessionId, created.configOptions);
|
|
189
|
+
this.rememberConfig(agent, created.sessionId, created.configOptions, revision === this.catalogRevision);
|
|
183
190
|
if (agent.init.agentCapabilities?.sessionCapabilities?.close) {
|
|
184
191
|
await agent.connection.closeSession({ sessionId: created.sessionId }).catch((error) => this.log('closing the settings session failed', { error: String(error) }));
|
|
185
192
|
}
|
|
@@ -221,7 +228,7 @@ export class AcpGatewayAdapter {
|
|
|
221
228
|
try {
|
|
222
229
|
// No turn is registered yet, so the history the agent replays is ignored.
|
|
223
230
|
const token = crypto.randomBytes(18).toString('base64url');
|
|
224
|
-
const loaded = await agent.connection.loadSession({ sessionId, cwd, mcpServers: this.mcpServers(token) });
|
|
231
|
+
const loaded = await agent.connection.loadSession({ sessionId, cwd, mcpServers: this.mcpServers(token, cwd) });
|
|
225
232
|
this.sessionTokens.set(token, sessionId);
|
|
226
233
|
agent.liveSessions.add(sessionId);
|
|
227
234
|
this.rememberConfig(agent, sessionId, loaded.configOptions);
|
|
@@ -232,7 +239,7 @@ export class AcpGatewayAdapter {
|
|
|
232
239
|
}
|
|
233
240
|
}
|
|
234
241
|
const token = crypto.randomBytes(18).toString('base64url');
|
|
235
|
-
const created = await agent.connection.newSession({ cwd, mcpServers: this.mcpServers(token) });
|
|
242
|
+
const created = await agent.connection.newSession({ cwd, mcpServers: this.mcpServers(token, cwd) });
|
|
236
243
|
this.sessionTokens.set(token, created.sessionId);
|
|
237
244
|
agent.liveSessions.add(created.sessionId);
|
|
238
245
|
this.rememberConfig(agent, created.sessionId, created.configOptions);
|
|
@@ -260,12 +267,17 @@ export class AcpGatewayAdapter {
|
|
|
260
267
|
}
|
|
261
268
|
}
|
|
262
269
|
}
|
|
263
|
-
rememberConfig(agent, sessionId, options) {
|
|
270
|
+
rememberConfig(agent, sessionId, options, updateCatalog = true) {
|
|
264
271
|
if (!options)
|
|
265
272
|
return;
|
|
266
273
|
const normalized = normalizeConfigOptions(options);
|
|
267
274
|
agent.sessionConfig.set(sessionId, normalized);
|
|
268
|
-
|
|
275
|
+
// A slow probe must not overwrite a newer live session update.
|
|
276
|
+
if (updateCatalog) {
|
|
277
|
+
this.catalog = normalized;
|
|
278
|
+
this.catalogExpiresAt = Date.now() + 5 * 60_000;
|
|
279
|
+
this.catalogRevision += 1;
|
|
280
|
+
}
|
|
269
281
|
}
|
|
270
282
|
ensureProcess() {
|
|
271
283
|
if (this.process)
|
|
@@ -24,18 +24,21 @@ export class EchoGatewayAdapter {
|
|
|
24
24
|
async runTurn(input) {
|
|
25
25
|
const sessionId = input.sessionId ?? `echo-${generateId()}`;
|
|
26
26
|
try {
|
|
27
|
-
|
|
27
|
+
const text = await input.prompt({ created: input.sessionId === null });
|
|
28
|
+
// Echo the user text while exercising the full prompt factory.
|
|
29
|
+
const prompt = text.replace(/<dinox_(delivery|knowledge|agent_instructions)>[\s\S]*?<\/dinox_\1>\n\n/g, '');
|
|
30
|
+
if (prompt.includes('/approve')) {
|
|
28
31
|
const toolCallId = `tool-${generateId()}`;
|
|
29
32
|
const approvalId = `approval-${generateId()}`;
|
|
30
|
-
input.onEvent({ type: 'tool', toolCallId, title: 'echo.write', status: 'pending', input: { text:
|
|
31
|
-
input.onEvent({ type: 'approval-request', approvalId, toolCallId, toolName: 'echo.write', input: { text:
|
|
33
|
+
input.onEvent({ type: 'tool', toolCallId, title: 'echo.write', status: 'pending', input: { text: prompt } });
|
|
34
|
+
input.onEvent({ type: 'approval-request', approvalId, toolCallId, toolName: 'echo.write', input: { text: prompt } });
|
|
32
35
|
const approved = await input.awaitApproval(approvalId);
|
|
33
36
|
input.onEvent({ type: 'approval-resolved', approvalId, approved });
|
|
34
37
|
input.onEvent(approved
|
|
35
38
|
? { type: 'tool', toolCallId, status: 'completed', output: 'approved' }
|
|
36
39
|
: { type: 'tool', toolCallId, status: 'failed', output: 'denied by user' });
|
|
37
40
|
}
|
|
38
|
-
for (const word of `Echo: ${
|
|
41
|
+
for (const word of `Echo: ${prompt}`.split(/(\s+)/)) {
|
|
39
42
|
await delay(CHUNK_DELAY_MS, input.signal);
|
|
40
43
|
input.onEvent({ type: 'text-delta', text: word });
|
|
41
44
|
}
|
|
@@ -8,5 +8,6 @@ import type { GatewayAdapter, GatewayDefinition } from '../types.js';
|
|
|
8
8
|
export declare function dinoxMcpServerConfig(options: {
|
|
9
9
|
sessionToken: string;
|
|
10
10
|
knowledgeTools: boolean;
|
|
11
|
+
cwd?: string;
|
|
11
12
|
}): McpServer | null;
|
|
12
13
|
export declare function createGatewayAdapter(gateway: GatewayDefinition, log: (message: string, details?: Record<string, unknown>) => void): GatewayAdapter;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DINOX_MCP_SERVER_NAME, GATEWAY_KNOWLEDGE_TOOLS_ENV, GATEWAY_SESSION_TOKEN_ENV } from '../mcpEnv.js';
|
|
1
|
+
import { DINOX_MCP_SERVER_NAME, GATEWAY_SESSION_CWD_ENV, GATEWAY_KNOWLEDGE_TOOLS_ENV, GATEWAY_SESSION_TOKEN_ENV } from '../mcpEnv.js';
|
|
2
2
|
import { AcpGatewayAdapter } from './acp.js';
|
|
3
3
|
import { EchoGatewayAdapter } from './echo.js';
|
|
4
4
|
/**
|
|
@@ -12,6 +12,7 @@ export function dinoxMcpServerConfig(options) {
|
|
|
12
12
|
return null;
|
|
13
13
|
const inherited = Object.entries(process.env).filter((entry) => entry[0].startsWith('DINOX_') &&
|
|
14
14
|
entry[0] !== GATEWAY_SESSION_TOKEN_ENV &&
|
|
15
|
+
entry[0] !== GATEWAY_SESSION_CWD_ENV &&
|
|
15
16
|
entry[0] !== GATEWAY_KNOWLEDGE_TOOLS_ENV &&
|
|
16
17
|
typeof entry[1] === 'string');
|
|
17
18
|
return {
|
|
@@ -20,6 +21,7 @@ export function dinoxMcpServerConfig(options) {
|
|
|
20
21
|
args: [...process.execArgv, entrypoint, 'gateway', 'mcp'],
|
|
21
22
|
env: [
|
|
22
23
|
...inherited,
|
|
24
|
+
[GATEWAY_SESSION_CWD_ENV, options.cwd ?? process.cwd()],
|
|
23
25
|
[GATEWAY_SESSION_TOKEN_ENV, options.sessionToken],
|
|
24
26
|
[GATEWAY_KNOWLEDGE_TOOLS_ENV, options.knowledgeTools ? '1' : '0'],
|
|
25
27
|
].map(([name, value]) => ({ name, value })),
|
|
@@ -30,8 +32,8 @@ export function createGatewayAdapter(gateway, log) {
|
|
|
30
32
|
case 'acp':
|
|
31
33
|
// Read per session, so turning the knowledge tools off applies to the next new conversation.
|
|
32
34
|
// `send_file` stays offered either way: it is how the agent hands the user a file.
|
|
33
|
-
return new AcpGatewayAdapter(gateway, log, (sessionToken) => {
|
|
34
|
-
const tools = dinoxMcpServerConfig({ sessionToken, knowledgeTools: gateway.dinoxTools !== false });
|
|
35
|
+
return new AcpGatewayAdapter(gateway, log, (sessionToken, cwd) => {
|
|
36
|
+
const tools = dinoxMcpServerConfig({ sessionToken, cwd, knowledgeTools: gateway.dinoxTools !== false });
|
|
35
37
|
return tools ? [tools] : [];
|
|
36
38
|
});
|
|
37
39
|
case 'echo':
|
|
@@ -77,8 +77,10 @@ export declare class GatewayDeviceHost {
|
|
|
77
77
|
private publishRunEvent;
|
|
78
78
|
private flushPendingEvents;
|
|
79
79
|
private handleDiscover;
|
|
80
|
-
/**
|
|
80
|
+
/** Every reply-bearing query gets a result, including unsupported extensions. */
|
|
81
81
|
private handleQuery;
|
|
82
|
+
private dispatchQuery;
|
|
83
|
+
private prepareFolder;
|
|
82
84
|
private settingsResult;
|
|
83
85
|
private changeSettings;
|
|
84
86
|
private listFolders;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
|
+
import { listGatewayWorkspace } from './workspaceListing.js';
|
|
2
3
|
import { credsAuthenticator, wsconnect, } from '@nats-io/nats-core';
|
|
4
|
+
import { AI_SERVICE_URL } from '../config/serviceEndpoints.js';
|
|
3
5
|
import { toAuthorizationHeaderValue } from '../utils/authToken.js';
|
|
4
6
|
import { REMOTE_DEVICE_PRESENCE_INTERVAL_MS, REMOTE_DEVICE_PROTOCOL_NAME, REMOTE_DEVICE_PROTOCOL_VERSION, remoteDeviceSubject, remoteDiscoverSubject, remotePresenceSubject, remoteRunStreamSubject, } from '../vendor/electron/shared/remote-device-agent.js';
|
|
5
7
|
import { projectPendingApprovals } from './assistantProjection.js';
|
|
6
|
-
import { isAgentConfigQuery, isAgentFoldersQuery, isAgentSettingsQuery, isCancelRequest, isRunStatusRequest, readAgentSettingsUpdate, reconnectDelayMs, validateApprovalRequest, validateRunRequest, } from './protocol.js';
|
|
8
|
+
import { isAgentConfigQuery, isAgentFolderPrepareQuery, isAgentFoldersQuery, isAgentSettingsQuery, isCancelRequest, isRunStatusRequest, readAgentSettingsUpdate, reconnectDelayMs, validateApprovalRequest, validateRunRequest, } from './protocol.js';
|
|
7
9
|
import { GatewayRunExecutor, MAX_QUEUED_TURNS, QueueFullError } from './runExecutor.js';
|
|
8
10
|
import { RunStreamPublisher } from './streamPublisher.js';
|
|
9
|
-
import { findRun, readRecentSessionFolders, resolveTrustedResource, TERMINAL_RUN_STATUSES, threadExists, } from './threadStore.js';
|
|
11
|
+
import { findRun, readRecentSessionFolders, readSessionBinding, resolveTrustedResource, TERMINAL_RUN_STATUSES, threadExists, } from './threadStore.js';
|
|
10
12
|
import { uploadToOfficialStorage } from './fileUpload.js';
|
|
11
13
|
import { agentHomeFolder, ensureAgentHomeFolder, existingWorkspaceFolders, listWorkspaceFolders, resolveWorkspaceFolder, WorkspaceFolderError, } from './workspaceFolders.js';
|
|
12
|
-
export const DEFAULT_NATS_CREDENTIALS_URL =
|
|
14
|
+
export const DEFAULT_NATS_CREDENTIALS_URL = `${AI_SERVICE_URL}/api/nats/credentials`;
|
|
13
15
|
const CREDENTIALS_TIMEOUT_MS = 10_000;
|
|
14
16
|
const CREDENTIALS_REFRESH_LEAD_MS = 60_000;
|
|
15
17
|
const ACK_CACHE_TTL_MS = 10 * 60 * 1000;
|
|
@@ -239,6 +241,7 @@ export class GatewayDeviceHost {
|
|
|
239
241
|
connectedAt: this.connectedAt ?? new Date().toISOString(),
|
|
240
242
|
busyThreadIds: this.executor.busyThreadIds(),
|
|
241
243
|
capabilities: {
|
|
244
|
+
workspace: { list: true, scope: 'thread', filePreview: false },
|
|
242
245
|
threadRun: true,
|
|
243
246
|
cancel: true,
|
|
244
247
|
stream: 'assistant-snapshot',
|
|
@@ -249,6 +252,7 @@ export class GatewayDeviceHost {
|
|
|
249
252
|
// Additive v2 extension: tells clients this device is an external agent
|
|
250
253
|
// (the agent owns context; Dinox model/tool settings do not apply).
|
|
251
254
|
info.capabilities.executor = {
|
|
255
|
+
agentWorkspace: true,
|
|
252
256
|
kind: 'gateway',
|
|
253
257
|
gatewayId: this.deps.gateway.id,
|
|
254
258
|
runtime: this.deps.gateway.kind,
|
|
@@ -333,17 +337,55 @@ export class GatewayDeviceHost {
|
|
|
333
337
|
return;
|
|
334
338
|
msg.respond(encode({ protocolVersion: REMOTE_DEVICE_PROTOCOL_VERSION, type: 'device.discovered', requestId, device: this.deviceInfo() }));
|
|
335
339
|
}
|
|
336
|
-
/**
|
|
340
|
+
/** Every reply-bearing query gets a result, including unsupported extensions. */
|
|
337
341
|
async handleQuery(msg) {
|
|
338
342
|
const { value, byteLength } = decode(msg);
|
|
339
343
|
if (!msg.reply)
|
|
340
344
|
return;
|
|
345
|
+
try {
|
|
346
|
+
await this.dispatchQuery(msg, value, byteLength);
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
const requestId = value?.requestId;
|
|
350
|
+
if (typeof requestId !== 'string')
|
|
351
|
+
return;
|
|
352
|
+
msg.respond(encode({
|
|
353
|
+
protocolVersion: REMOTE_DEVICE_PROTOCOL_VERSION,
|
|
354
|
+
type: 'query.error', requestId, ok: false,
|
|
355
|
+
error: { code: 'query_failed', message: error instanceof Error ? error.message : String(error) },
|
|
356
|
+
}));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async dispatchQuery(msg, value, byteLength) {
|
|
360
|
+
const raw = value;
|
|
361
|
+
if (raw?.type === 'query.cancel')
|
|
362
|
+
return;
|
|
363
|
+
if (raw?.type === 'agent.folder.prepare' && typeof raw.requestId === 'string') {
|
|
364
|
+
msg.respond(encode(await this.prepareFolder(value, raw.requestId, byteLength)));
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (raw?.type === 'workspace.list' && typeof raw.requestId === 'string') {
|
|
368
|
+
msg.respond(encode(await listGatewayWorkspace(this.deps.db, this.deps.userId, this.deps.gateway, value)));
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if ((raw?.type === 'workspace.file.read' || raw?.type === 'skills.list') && typeof raw.requestId === 'string') {
|
|
372
|
+
const base = { protocolVersion: REMOTE_DEVICE_PROTOCOL_VERSION, requestId: raw.requestId, threadId: String(raw.threadId ?? ''), ok: false, fetchedAt: new Date().toISOString(), error: { code: 'unsupported', message: 'This gateway does not support this query' } };
|
|
373
|
+
const result = raw.type === 'skills.list'
|
|
374
|
+
? { ...base, type: 'skills.list.result', skills: [] }
|
|
375
|
+
: { ...base, type: 'workspace.file.read.result', path: String(raw.path ?? ''), preview: null, size: null };
|
|
376
|
+
msg.respond(encode(result));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
341
379
|
if (isAgentConfigQuery(value)) {
|
|
342
380
|
msg.respond(encode(await this.agentConfig(value)));
|
|
343
381
|
return;
|
|
344
382
|
}
|
|
345
383
|
if (isAgentSettingsQuery(value)) {
|
|
346
|
-
|
|
384
|
+
const result = this.settingsResult(value.requestId);
|
|
385
|
+
if (value.threadId && await threadExists(this.deps.db, value.threadId, this.deps.userId)) {
|
|
386
|
+
result.threadCwd = (await readSessionBinding(this.deps.db, value.threadId, this.deps.gateway.id))?.cwd ?? null;
|
|
387
|
+
}
|
|
388
|
+
msg.respond(encode(result));
|
|
347
389
|
return;
|
|
348
390
|
}
|
|
349
391
|
if (isAgentFoldersQuery(value)) {
|
|
@@ -356,7 +398,34 @@ export class GatewayDeviceHost {
|
|
|
356
398
|
msg.respond(encode('error' in update
|
|
357
399
|
? this.settingsResult(requestId, { code: 'invalid_request', message: update.error })
|
|
358
400
|
: await this.changeSettings(update.update)));
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (typeof raw?.requestId === 'string')
|
|
404
|
+
msg.respond(encode({ protocolVersion: REMOTE_DEVICE_PROTOCOL_VERSION, type: 'query.error', requestId: raw.requestId, ok: false, error: { code: 'unsupported', message: 'Unsupported gateway query' } }));
|
|
405
|
+
}
|
|
406
|
+
async prepareFolder(value, requestId, byteLength) {
|
|
407
|
+
const result = {
|
|
408
|
+
protocolVersion: REMOTE_DEVICE_PROTOCOL_VERSION,
|
|
409
|
+
type: 'agent.folder.result',
|
|
410
|
+
requestId,
|
|
411
|
+
ok: false,
|
|
412
|
+
path: null,
|
|
413
|
+
home: os.homedir(),
|
|
414
|
+
fetchedAt: new Date().toISOString(),
|
|
415
|
+
};
|
|
416
|
+
if (!isAgentFolderPrepareQuery(value, byteLength)) {
|
|
417
|
+
return { ...result, error: { code: 'invalid_request', message: 'Choose exactly one path or agent name' } };
|
|
418
|
+
}
|
|
419
|
+
try {
|
|
420
|
+
result.path = value.path !== undefined
|
|
421
|
+
? await resolveWorkspaceFolder(value.path)
|
|
422
|
+
: await ensureAgentHomeFolder(value.agentName, undefined, value.agentId);
|
|
423
|
+
result.ok = true;
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
result.error = settingsError(error);
|
|
359
427
|
}
|
|
428
|
+
return result;
|
|
360
429
|
}
|
|
361
430
|
settingsResult(requestId, error) {
|
|
362
431
|
const gateway = this.deps.gateway;
|
|
@@ -365,6 +434,7 @@ export class GatewayDeviceHost {
|
|
|
365
434
|
type: 'agent.settings.result',
|
|
366
435
|
requestId,
|
|
367
436
|
ok: !error,
|
|
437
|
+
threadCwd: null,
|
|
368
438
|
settings: {
|
|
369
439
|
cwd: gateway.cwd ?? null,
|
|
370
440
|
approval: gateway.approval ?? 'ask',
|
|
@@ -3,16 +3,26 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import type { CommandSchema } from '../commands/schema/types.js';
|
|
5
5
|
import { tryForwardToDaemon } from '../daemon/forward.js';
|
|
6
|
+
import { MCP_COMMAND_GROUPS, type McpCommandGroup } from './mcpGroups.js';
|
|
6
7
|
/**
|
|
7
8
|
* One MCP tool per command group, generated from the command schemas. Each runs
|
|
8
9
|
* the real CLI as a child process without a shell, preserving the CLI's own
|
|
9
10
|
* validation, dry-run and confirmation guards. Approval belongs to the agent's
|
|
10
11
|
* permission flow, guided by the tool annotations.
|
|
11
12
|
*/
|
|
12
|
-
|
|
13
|
-
export declare const MCP_COMMAND_GROUPS: readonly ["note", "tag", "box", "todo", "view", "prop", "template", "prompt", "graph", "storage", "meta.stats"];
|
|
13
|
+
export { MCP_COMMAND_GROUPS, type McpCommandGroup };
|
|
14
14
|
/** Commands deliberately excluded from the MCP surface. */
|
|
15
|
-
export declare const MCP_COMMAND_EXCLUSIONS: readonly ["auth.*", "config.*", "daemon.*", "gateway.*", "doctor", "skills.*", "update", "sync", "info", "schema", "meta.schema"];
|
|
15
|
+
export declare const MCP_COMMAND_EXCLUSIONS: readonly ["auth.*", "config.*", "daemon.*", "gateway.*", "doctor", "skills.*", "update", "sync", "info", "schema", "meta.schema", "batch", "mcp"];
|
|
16
|
+
export type CommandToolOptions = {
|
|
17
|
+
/** Only commands whose risk is `read`; groups left without any are dropped. */
|
|
18
|
+
readOnly?: boolean;
|
|
19
|
+
/** Subset of MCP_COMMAND_GROUPS to expose; default all. */
|
|
20
|
+
groups?: readonly McpCommandGroup[];
|
|
21
|
+
/** The curated search_notes/read_note/... tools are registered alongside. */
|
|
22
|
+
knowledgeTools?: boolean;
|
|
23
|
+
};
|
|
24
|
+
/** The tool name a command group is published under. */
|
|
25
|
+
export declare function commandToolName(group: McpCommandGroup): string;
|
|
16
26
|
/** Runs a CLI invocation and captures its output. */
|
|
17
27
|
export type CommandRunner = (argv: string[]) => Promise<{
|
|
18
28
|
stdout: string;
|
|
@@ -30,7 +40,7 @@ export declare const runCliCommand: CommandRunner;
|
|
|
30
40
|
/** Validates tool inputs and builds CLI arguments in schema order. */
|
|
31
41
|
export declare function commandArgv(schema: CommandSchema, input: Record<string, unknown>): string[];
|
|
32
42
|
/** Builds grouped MCP tool definitions from the command schemas. */
|
|
33
|
-
export declare function commandToolDefinitions(): {
|
|
43
|
+
export declare function commandToolDefinitions(options?: CommandToolOptions): {
|
|
34
44
|
name: string;
|
|
35
45
|
commands: CommandSchema[];
|
|
36
46
|
config: {
|
|
@@ -49,4 +59,4 @@ export declare function commandToolDefinitions(): {
|
|
|
49
59
|
};
|
|
50
60
|
}[];
|
|
51
61
|
/** Registers command tools using the real CLI or an injected runner. */
|
|
52
|
-
export declare function registerCommandTools(server: McpServer, runner?: CommandRunner): void;
|
|
62
|
+
export declare function registerCommandTools(server: McpServer, runner?: CommandRunner, options?: CommandToolOptions): void;
|
|
@@ -3,6 +3,7 @@ import { promisify } from 'node:util';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { listCommandSchemas } from '../commands/schema/spec.js';
|
|
5
5
|
import { tryForwardToDaemon } from '../daemon/forward.js';
|
|
6
|
+
import { MCP_COMMAND_GROUPS } from './mcpGroups.js';
|
|
6
7
|
import { requestContext } from '../utils/requestContext.js';
|
|
7
8
|
/**
|
|
8
9
|
* One MCP tool per command group, generated from the command schemas. Each runs
|
|
@@ -10,14 +11,19 @@ import { requestContext } from '../utils/requestContext.js';
|
|
|
10
11
|
* validation, dry-run and confirmation guards. Approval belongs to the agent's
|
|
11
12
|
* permission flow, guided by the tool annotations.
|
|
12
13
|
*/
|
|
13
|
-
|
|
14
|
-
export const MCP_COMMAND_GROUPS = [
|
|
15
|
-
'note', 'tag', 'box', 'todo', 'view', 'prop', 'template', 'prompt', 'graph', 'storage', 'meta.stats',
|
|
16
|
-
];
|
|
14
|
+
export { MCP_COMMAND_GROUPS };
|
|
17
15
|
/** Commands deliberately excluded from the MCP surface. */
|
|
18
16
|
export const MCP_COMMAND_EXCLUSIONS = [
|
|
19
17
|
'auth.*', 'config.*', 'daemon.*', 'gateway.*', 'doctor', 'skills.*', 'update', 'sync', 'info', 'schema', 'meta.schema',
|
|
18
|
+
// MCP clients issue several tool calls themselves; batch is the shell's equivalent.
|
|
19
|
+
'batch',
|
|
20
|
+
// The MCP server itself.
|
|
21
|
+
'mcp',
|
|
20
22
|
];
|
|
23
|
+
/** The tool name a command group is published under. */
|
|
24
|
+
export function commandToolName(group) {
|
|
25
|
+
return group === 'meta.stats' ? 'dinox_stats' : `dinox_${group}`;
|
|
26
|
+
}
|
|
21
27
|
const execute = promisify(execFile);
|
|
22
28
|
/** Runs the current CLI entry point without a shell. */
|
|
23
29
|
export function createCliCommandRunner(overrides = {}) {
|
|
@@ -169,28 +175,33 @@ export function commandArgv(schema, input) {
|
|
|
169
175
|
return [...schema.path.split('.'), ...flags, '--format', 'json', '--', ...positionals];
|
|
170
176
|
}
|
|
171
177
|
/** Builds grouped MCP tool definitions from the command schemas. */
|
|
172
|
-
export function commandToolDefinitions() {
|
|
173
|
-
|
|
174
|
-
|
|
178
|
+
export function commandToolDefinitions(options = {}) {
|
|
179
|
+
const { readOnly = false, groups = MCP_COMMAND_GROUPS, knowledgeTools = true } = options;
|
|
180
|
+
return MCP_COMMAND_GROUPS.filter((group) => groups.includes(group)).flatMap((group) => {
|
|
181
|
+
const commands = listCommandSchemas().filter((schema) => (group === 'meta.stats' ? schema.path === group : schema.path.startsWith(`${group}.`))
|
|
182
|
+
&& (!readOnly || schema.risk === 'read'));
|
|
183
|
+
if (commands.length === 0)
|
|
184
|
+
return [];
|
|
175
185
|
const actions = commands.map(actionName);
|
|
176
|
-
const description = 'Dinox CLI commands.
|
|
186
|
+
const description = 'Dinox CLI commands. ' +
|
|
187
|
+
(knowledgeTools ? 'search_notes/read_note/search_todos/related_notes are the fast daemon-backed path for plain search/read. ' : '') +
|
|
177
188
|
'High-risk writes need dryRun: true first, then the CLI confirmation options listed below (e.g. confirm/expectedCount); ' +
|
|
178
189
|
'CLI guards enforce them. Input keys are camelCase.\n' +
|
|
179
190
|
commands.map(describeAction).join('\n');
|
|
180
|
-
return {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
191
|
+
return [{
|
|
192
|
+
name: commandToolName(group),
|
|
193
|
+
commands,
|
|
194
|
+
config: {
|
|
195
|
+
description,
|
|
196
|
+
annotations: { ...toolAnnotations(commands), openWorldHint: group === 'storage' },
|
|
197
|
+
inputSchema: { action: z.enum(actions), input: z.record(z.string(), z.unknown()).optional() },
|
|
198
|
+
},
|
|
199
|
+
}];
|
|
189
200
|
});
|
|
190
201
|
}
|
|
191
202
|
/** Registers command tools using the real CLI or an injected runner. */
|
|
192
|
-
export function registerCommandTools(server, runner = runCliCommand) {
|
|
193
|
-
for (const tool of commandToolDefinitions()) {
|
|
203
|
+
export function registerCommandTools(server, runner = runCliCommand, options = {}) {
|
|
204
|
+
for (const tool of commandToolDefinitions(options)) {
|
|
194
205
|
server.registerTool(tool.name, tool.config, async ({ action, input }) => {
|
|
195
206
|
try {
|
|
196
207
|
const schema = tool.commands.find((command) => actionName(command) === action);
|
package/dist/gateway/mcpEnv.d.ts
CHANGED
|
@@ -5,3 +5,7 @@ export declare const GATEWAY_SESSION_TOKEN_ENV = "DINOX_GATEWAY_SESSION_TOKEN";
|
|
|
5
5
|
export declare const GATEWAY_KNOWLEDGE_TOOLS_ENV = "DINOX_GATEWAY_KNOWLEDGE_TOOLS";
|
|
6
6
|
/** Must not collide with MCP servers the user already registered with the agent. */
|
|
7
7
|
export declare const DINOX_MCP_SERVER_NAME = "dinox-gateway";
|
|
8
|
+
/** `dino mcp`, the server users register with their own MCP clients. */
|
|
9
|
+
export declare const DINOX_STANDALONE_MCP_SERVER_NAME = "dinox";
|
|
10
|
+
/** Session folder for resolving relative send_file paths. */
|
|
11
|
+
export declare const GATEWAY_SESSION_CWD_ENV = "DINOX_GATEWAY_SESSION_CWD";
|
package/dist/gateway/mcpEnv.js
CHANGED
|
@@ -5,3 +5,7 @@ export const GATEWAY_SESSION_TOKEN_ENV = 'DINOX_GATEWAY_SESSION_TOKEN';
|
|
|
5
5
|
export const GATEWAY_KNOWLEDGE_TOOLS_ENV = 'DINOX_GATEWAY_KNOWLEDGE_TOOLS';
|
|
6
6
|
/** Must not collide with MCP servers the user already registered with the agent. */
|
|
7
7
|
export const DINOX_MCP_SERVER_NAME = 'dinox-gateway';
|
|
8
|
+
/** `dino mcp`, the server users register with their own MCP clients. */
|
|
9
|
+
export const DINOX_STANDALONE_MCP_SERVER_NAME = 'dinox';
|
|
10
|
+
/** Session folder for resolving relative send_file paths. */
|
|
11
|
+
export const GATEWAY_SESSION_CWD_ENV = 'DINOX_GATEWAY_SESSION_CWD';
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Command groups exposed as MCP tools. Kept free of imports: command descriptors read it at load time. */
|
|
2
|
+
export declare const MCP_COMMAND_GROUPS: readonly ["note", "tag", "box", "todo", "view", "prop", "template", "prompt", "graph", "storage", "meta.stats"];
|
|
3
|
+
export type McpCommandGroup = (typeof MCP_COMMAND_GROUPS)[number];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CommandRunner } from './mcpCommandTools.js';
|
|
1
|
+
import { type CommandRunner, type CommandToolOptions } from './mcpCommandTools.js';
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
export type DaemonCall = (method: string, params?: unknown, options?: {
|
|
4
4
|
timeoutMs?: number;
|
|
@@ -13,4 +13,13 @@ export type DinoxMcpOptions = {
|
|
|
13
13
|
cwd?: string;
|
|
14
14
|
};
|
|
15
15
|
export declare function createDinoxMcpServer(call?: DaemonCall, options?: DinoxMcpOptions): McpServer;
|
|
16
|
+
/**
|
|
17
|
+
* The Dinox MCP server for any MCP client (`dino mcp`). Only the generated
|
|
18
|
+
* command tools: they reach the daemon when it is available and otherwise run
|
|
19
|
+
* the CLI in a child process, so no daemon is required.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createStandaloneMcpServer(options?: Omit<CommandToolOptions, 'knowledgeTools'> & {
|
|
22
|
+
commandRunner?: CommandRunner;
|
|
23
|
+
}): McpServer;
|
|
16
24
|
export declare function serveDinoxMcpOverStdio(): Promise<void>;
|
|
25
|
+
export declare function serveStandaloneMcpOverStdio(options: Omit<CommandToolOptions, 'knowledgeTools'>): Promise<void>;
|
|
@@ -4,7 +4,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
4
4
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { sendDaemonRequest } from '../daemon/client.js';
|
|
7
|
-
import { DINOX_MCP_SERVER_NAME, GATEWAY_KNOWLEDGE_TOOLS_ENV, GATEWAY_SESSION_TOKEN_ENV } from './mcpEnv.js';
|
|
7
|
+
import { DINOX_MCP_SERVER_NAME, DINOX_STANDALONE_MCP_SERVER_NAME, GATEWAY_SESSION_CWD_ENV, GATEWAY_KNOWLEDGE_TOOLS_ENV, GATEWAY_SESSION_TOKEN_ENV } from './mcpEnv.js';
|
|
8
8
|
import { READ_CONVERSATION_MAX_LIMIT } from './conversationContext.js';
|
|
9
9
|
import { getPackageVersion } from '../utils/version.js';
|
|
10
10
|
/**
|
|
@@ -183,11 +183,32 @@ function registerKnowledgeTools(server, call) {
|
|
|
183
183
|
}
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
|
+
const STANDALONE_INSTRUCTIONS = "Tools for the user's Dinox knowledge base (notes, todos, tags, card boxes, views, properties, templates, prompts, " +
|
|
187
|
+
'the link graph and storage). Each dinox_* tool groups the CLI commands of one area: pick an `action` and pass its ' +
|
|
188
|
+
'camelCase `input`. Prefer note search for keywords and note semantic/context for meaning-based questions. Results ' +
|
|
189
|
+
'carry `_notice` (stale cache, pending upload) worth relaying. High-risk writes need dryRun: true first, then the ' +
|
|
190
|
+
'confirmation options the action lists.';
|
|
191
|
+
/**
|
|
192
|
+
* The Dinox MCP server for any MCP client (`dino mcp`). Only the generated
|
|
193
|
+
* command tools: they reach the daemon when it is available and otherwise run
|
|
194
|
+
* the CLI in a child process, so no daemon is required.
|
|
195
|
+
*/
|
|
196
|
+
export function createStandaloneMcpServer(options = {}) {
|
|
197
|
+
const server = new McpServer({ name: DINOX_STANDALONE_MCP_SERVER_NAME, version: getPackageVersion() }, { instructions: STANDALONE_INSTRUCTIONS + (options.readOnly ? ' This server is read-only.' : '') });
|
|
198
|
+
registerCommandTools(server, options.commandRunner, { ...options, knowledgeTools: false });
|
|
199
|
+
return server;
|
|
200
|
+
}
|
|
201
|
+
async function serveOverStdio(server) {
|
|
202
|
+
await server.connect(new StdioServerTransport());
|
|
203
|
+
await new Promise((resolve) => process.stdin.once('close', resolve));
|
|
204
|
+
}
|
|
186
205
|
export async function serveDinoxMcpOverStdio() {
|
|
187
|
-
|
|
206
|
+
await serveOverStdio(createDinoxMcpServer(undefined, {
|
|
207
|
+
cwd: process.env[GATEWAY_SESSION_CWD_ENV] || process.cwd(),
|
|
188
208
|
knowledgeTools: process.env[GATEWAY_KNOWLEDGE_TOOLS_ENV] !== '0',
|
|
189
209
|
sessionToken: process.env[GATEWAY_SESSION_TOKEN_ENV]?.trim() || undefined,
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
export async function serveStandaloneMcpOverStdio(options) {
|
|
213
|
+
await serveOverStdio(createStandaloneMcpServer(options));
|
|
193
214
|
}
|
|
@@ -20,8 +20,11 @@ export type AgentConfigResult = {
|
|
|
20
20
|
};
|
|
21
21
|
};
|
|
22
22
|
export declare function isAgentConfigQuery(value: unknown): value is AgentConfigQuery;
|
|
23
|
+
export declare const MAX_PATH_LENGTH = 4096;
|
|
24
|
+
/** Host-side workspace choice, never forwarded as an ACP session option. */
|
|
25
|
+
export declare const AGENT_CWD_CONFIG_KEY = "dinox.cwd";
|
|
23
26
|
export type AgentHostSettings = {
|
|
24
|
-
/**
|
|
27
|
+
/** Fallback when neither the conversation nor its agent has chosen a folder. */
|
|
25
28
|
cwd: string | null;
|
|
26
29
|
approval: GatewayApprovalMode;
|
|
27
30
|
/** Online but refusing new turns. */
|
|
@@ -32,7 +35,7 @@ export type AgentHostSettings = {
|
|
|
32
35
|
export type AgentSettingsChange = {
|
|
33
36
|
/** An existing folder (absolute or `~/...`), checked on this computer. */
|
|
34
37
|
cwd?: string;
|
|
35
|
-
/** Creates and uses the agent's own folder (`~/
|
|
38
|
+
/** Creates and uses the agent's own folder (`~/dinox-agents/<name>`). */
|
|
36
39
|
useAgentFolder?: true;
|
|
37
40
|
approval?: GatewayApprovalMode;
|
|
38
41
|
paused?: boolean;
|
|
@@ -41,6 +44,7 @@ export type AgentSettingsChange = {
|
|
|
41
44
|
export type AgentSettingsQuery = {
|
|
42
45
|
protocolVersion: typeof REMOTE_DEVICE_PROTOCOL_VERSION;
|
|
43
46
|
type: 'agent.settings.get';
|
|
47
|
+
threadId?: string;
|
|
44
48
|
requestId: string;
|
|
45
49
|
clientDeviceId: string;
|
|
46
50
|
};
|
|
@@ -58,6 +62,8 @@ export type AgentSettingsResult = {
|
|
|
58
62
|
requestId: string;
|
|
59
63
|
ok: boolean;
|
|
60
64
|
settings: AgentHostSettings;
|
|
65
|
+
/** The conversation's folder, even after its topic has changed. */
|
|
66
|
+
threadCwd: string | null;
|
|
61
67
|
/** For showing paths as `~/...`. */
|
|
62
68
|
home: string;
|
|
63
69
|
/** Where `useAgentFolder` puts the agent's own folder. */
|
|
@@ -99,6 +105,29 @@ export type AgentFoldersResult = {
|
|
|
99
105
|
message: string;
|
|
100
106
|
};
|
|
101
107
|
};
|
|
108
|
+
export type AgentFolderPrepareQuery = {
|
|
109
|
+
protocolVersion: typeof REMOTE_DEVICE_PROTOCOL_VERSION;
|
|
110
|
+
type: 'agent.folder.prepare';
|
|
111
|
+
requestId: string;
|
|
112
|
+
clientDeviceId: string;
|
|
113
|
+
path?: string;
|
|
114
|
+
agentName?: string;
|
|
115
|
+
agentId?: string;
|
|
116
|
+
};
|
|
117
|
+
export type AgentFolderPrepareResult = {
|
|
118
|
+
protocolVersion: typeof REMOTE_DEVICE_PROTOCOL_VERSION;
|
|
119
|
+
type: 'agent.folder.result';
|
|
120
|
+
requestId: string;
|
|
121
|
+
ok: boolean;
|
|
122
|
+
path: string | null;
|
|
123
|
+
home: string;
|
|
124
|
+
fetchedAt: string;
|
|
125
|
+
error?: {
|
|
126
|
+
code: AgentSettingsErrorCode;
|
|
127
|
+
message: string;
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
export declare function isAgentFolderPrepareQuery(value: unknown, byteLength: number): value is AgentFolderPrepareQuery;
|
|
102
131
|
export declare function isAgentSettingsQuery(value: unknown): value is AgentSettingsQuery;
|
|
103
132
|
export declare function isAgentFoldersQuery(value: unknown): value is AgentFoldersQuery;
|
|
104
133
|
/** The change an `agent.settings.set` asks for, or why it is malformed. */
|
package/dist/gateway/protocol.js
CHANGED
|
@@ -76,9 +76,21 @@ export function isAgentConfigQuery(value) {
|
|
|
76
76
|
* `agent.settings.set`, and picks a working folder with `agent.folders.list`.
|
|
77
77
|
*/
|
|
78
78
|
const MAX_SETTINGS_REQUEST_BYTES = 16 * 1024;
|
|
79
|
-
const MAX_PATH_LENGTH = 4096;
|
|
79
|
+
export const MAX_PATH_LENGTH = 4096;
|
|
80
|
+
/** Host-side workspace choice, never forwarded as an ACP session option. */
|
|
81
|
+
export const AGENT_CWD_CONFIG_KEY = 'dinox.cwd';
|
|
82
|
+
export function isAgentFolderPrepareQuery(value, byteLength) {
|
|
83
|
+
if (!isMessageOfType(value, 'agent.folder.prepare') || !isDeviceId(value.clientDeviceId) || byteLength > MAX_SETTINGS_REQUEST_BYTES)
|
|
84
|
+
return false;
|
|
85
|
+
if ((value.path === undefined) === (value.agentName === undefined))
|
|
86
|
+
return false;
|
|
87
|
+
if (value.agentId !== undefined && (value.agentName === undefined || typeof value.agentId !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(value.agentId)))
|
|
88
|
+
return false;
|
|
89
|
+
const text = value.path ?? value.agentName;
|
|
90
|
+
return typeof text === 'string' && !!text.trim() && text.length <= (value.path === undefined ? MAX_AGENT_CONFIG_TEXT : MAX_PATH_LENGTH);
|
|
91
|
+
}
|
|
80
92
|
export function isAgentSettingsQuery(value) {
|
|
81
|
-
return isMessageOfType(value, 'agent.settings.get') && isDeviceId(value.clientDeviceId);
|
|
93
|
+
return isMessageOfType(value, 'agent.settings.get') && isDeviceId(value.clientDeviceId) && (value.threadId === undefined || isSubjectId(value.threadId));
|
|
82
94
|
}
|
|
83
95
|
export function isAgentFoldersQuery(value) {
|
|
84
96
|
if (!isMessageOfType(value, 'agent.folders.list') || !isDeviceId(value.clientDeviceId))
|
|
@@ -139,7 +151,7 @@ export function readAgentConfig(request) {
|
|
|
139
151
|
for (const [id, value] of entries) {
|
|
140
152
|
if (!id || id.length > MAX_AGENT_CONFIG_TEXT)
|
|
141
153
|
return 'invalid';
|
|
142
|
-
if (typeof value !== 'string' || !value || value.length > MAX_AGENT_CONFIG_TEXT)
|
|
154
|
+
if (typeof value !== 'string' || !value || value.length > (id === AGENT_CWD_CONFIG_KEY ? MAX_PATH_LENGTH : MAX_AGENT_CONFIG_TEXT))
|
|
143
155
|
return 'invalid';
|
|
144
156
|
config[id] = value;
|
|
145
157
|
}
|