@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
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
1
|
import { AssistantProjection, projectPendingApprovals, toTranscriptMessage } from './assistantProjection.js';
|
|
3
2
|
import { CONTEXT_MESSAGE_LIMIT, withConversationContext } from './conversationContext.js';
|
|
4
3
|
import { commitRunTerminal, enqueueRun, finishMergedRun, startQueuedRuns, TERMINAL_RUN_STATUSES, withdrawQueuedRun, readAgentInstructions, readCurrentTopic, readSessionBinding, readTranscriptPage, resolveTrustedResource, updateRunSnapshot, waitForMessage, withGatewaySession, writeSessionBinding, } from './threadStore.js';
|
|
5
|
-
import { readAgentConfig } from './protocol.js';
|
|
4
|
+
import { AGENT_CWD_CONFIG_KEY, readAgentConfig } from './protocol.js';
|
|
5
|
+
import { resolveTurnWorkspace, queuedTurnPrompt } from './workspace.js';
|
|
6
6
|
const SNAPSHOT_PERSIST_INTERVAL_MS = 1_000;
|
|
7
7
|
/** Turns waiting behind a thread's active one; more are refused as `busy`. */
|
|
8
8
|
export const MAX_QUEUED_TURNS = 10;
|
|
@@ -313,24 +313,35 @@ export class GatewayRunExecutor {
|
|
|
313
313
|
readSessionBinding(db, active.threadId, gateway.id),
|
|
314
314
|
]);
|
|
315
315
|
const topicId = topic?.id ?? null;
|
|
316
|
-
|
|
316
|
+
let continued = binding?.topicId === topicId ? binding : null;
|
|
317
317
|
const turns = [...merged, request];
|
|
318
|
-
const
|
|
318
|
+
const tailSynced = this.clientTailSynced(turns[0], active.controller.signal);
|
|
319
|
+
const [resources, initialContext, instructions] = await Promise.all([
|
|
319
320
|
Promise.all(turns.map((turn) => this.resolveResources(turn.userMessage))).then((lists) => lists.flat()),
|
|
320
|
-
|
|
321
|
+
tailSynced.then((synced) => this.unseenContext(active.threadId, turns[0].userMessage.id, topic?.createdAt ?? null, continued, synced)),
|
|
321
322
|
readAgentInstructions(db, active.threadId).catch(() => ''),
|
|
322
323
|
]);
|
|
323
|
-
|
|
324
|
-
const cwd = continued ? (continued.cwd ?? gateway.previousCwd ?? gateway.cwd ?? os.homedir()) : (gateway.cwd ?? os.homedir());
|
|
325
|
-
// Validated on receipt; a malformed value never reaches here.
|
|
324
|
+
let context = initialContext;
|
|
326
325
|
const requestedConfig = readAgentConfig(request);
|
|
327
|
-
const agentConfig = requestedConfig && requestedConfig !== 'invalid' ? requestedConfig : undefined;
|
|
328
|
-
const
|
|
326
|
+
const agentConfig = requestedConfig && requestedConfig !== 'invalid' ? { ...requestedConfig } : undefined;
|
|
327
|
+
const cwd = await resolveTurnWorkspace(gateway, binding, !!continued, agentConfig);
|
|
328
|
+
if (agentConfig)
|
|
329
|
+
delete agentConfig[AGENT_CWD_CONFIG_KEY];
|
|
330
|
+
const prompt = queuedTurnPrompt(turns.map((turn) => turn.prompt));
|
|
329
331
|
const result = await adapter.runTurn({
|
|
330
332
|
sessionId: continued?.sessionId ?? null,
|
|
331
333
|
cwd,
|
|
332
|
-
|
|
333
|
-
|
|
334
|
+
prompt: async ({ created }) => {
|
|
335
|
+
if (created && continued) {
|
|
336
|
+
continued = null;
|
|
337
|
+
// A failed load has no retained history, including this gateway's own replies.
|
|
338
|
+
const synced = await tailSynced;
|
|
339
|
+
context = await this.unseenContext(active.threadId, turns[0].userMessage.id, topic?.createdAt ?? null, null, synced);
|
|
340
|
+
}
|
|
341
|
+
const text = withConversationContext(prompt, context, { canReadConversation: gateway.dinoxTools !== false });
|
|
342
|
+
const preamble = created && sessionPreamble(instructions, { delivery: !!this.deps.uploadFile, knowledge: gateway.dinoxTools !== false });
|
|
343
|
+
return preamble ? `${preamble}\n\n${text}` : text;
|
|
344
|
+
},
|
|
334
345
|
resources,
|
|
335
346
|
signal: active.controller.signal,
|
|
336
347
|
config: agentConfig,
|
|
@@ -15,11 +15,13 @@ export type GatewayServiceDeps = {
|
|
|
15
15
|
*/
|
|
16
16
|
export declare class GatewayService {
|
|
17
17
|
private readonly deps;
|
|
18
|
+
private readonly definitions;
|
|
18
19
|
private hosts;
|
|
19
20
|
private readonly log;
|
|
20
21
|
private readonly keepAwake;
|
|
21
22
|
constructor(deps: GatewayServiceDeps);
|
|
22
23
|
start(): Promise<void>;
|
|
24
|
+
private reconcile;
|
|
23
25
|
stop(): Promise<void>;
|
|
24
26
|
/** Re-reads the account's gateways (after add/remove) without restarting the daemon. */
|
|
25
27
|
reload(): Promise<DeviceHostStatus[]>;
|
package/dist/gateway/service.js
CHANGED
|
@@ -23,6 +23,7 @@ const defaultLog = (message, details) => writeDaemonLogLine('dinox-gateway', mes
|
|
|
23
23
|
*/
|
|
24
24
|
export class GatewayService {
|
|
25
25
|
deps;
|
|
26
|
+
definitions = new Map();
|
|
26
27
|
hosts = [];
|
|
27
28
|
log;
|
|
28
29
|
keepAwake;
|
|
@@ -32,7 +33,9 @@ export class GatewayService {
|
|
|
32
33
|
this.keepAwake = new KeepAwake(this.log);
|
|
33
34
|
}
|
|
34
35
|
async start() {
|
|
35
|
-
|
|
36
|
+
await this.reconcile(await loadGateways(this.deps.userId), true);
|
|
37
|
+
}
|
|
38
|
+
async reconcile(configured, recoverOrphans) {
|
|
36
39
|
const machineId = await getMachineId();
|
|
37
40
|
// Reconcile before anything else, also when the last gateway was removed.
|
|
38
41
|
const retired = await retireRemovedRobots(this.deps.db, {
|
|
@@ -48,10 +51,17 @@ export class GatewayService {
|
|
|
48
51
|
const gateways = configured.filter((gateway) => gateway.enabled);
|
|
49
52
|
if (gateways.length === 0)
|
|
50
53
|
return;
|
|
51
|
-
const interrupted = await interruptOrphanedRuns(this.deps.db, this.deps.userId);
|
|
54
|
+
const interrupted = recoverOrphans && this.hosts.length === 0 ? await interruptOrphanedRuns(this.deps.db, this.deps.userId) : 0;
|
|
52
55
|
if (interrupted > 0)
|
|
53
56
|
this.log('marked runs from a previous host as interrupted', { count: interrupted });
|
|
54
57
|
for (const gateway of gateways) {
|
|
58
|
+
const existing = this.hosts.find((host) => this.definitions.get(host)?.id === gateway.id);
|
|
59
|
+
if (existing) {
|
|
60
|
+
const definition = this.definitions.get(existing);
|
|
61
|
+
applyLiveSettings(definition, gateway);
|
|
62
|
+
await upsertRobot(this.deps.db, this.robotRegistration(definition, machineId)).catch((error) => this.log('register robot failed', { gateway: gateway.name, error: String(error) }));
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
55
65
|
await upsertRobot(this.deps.db, this.robotRegistration(gateway, machineId)).catch((error) => this.log('register robot failed', { gateway: gateway.name, error: String(error) }));
|
|
56
66
|
const host = new GatewayDeviceHost({
|
|
57
67
|
gateway,
|
|
@@ -67,18 +77,33 @@ export class GatewayService {
|
|
|
67
77
|
});
|
|
68
78
|
host.start();
|
|
69
79
|
this.hosts.push(host);
|
|
80
|
+
this.definitions.set(host, gateway);
|
|
70
81
|
}
|
|
71
82
|
}
|
|
72
83
|
async stop() {
|
|
73
84
|
const hosts = this.hosts;
|
|
74
85
|
this.hosts = [];
|
|
86
|
+
this.definitions.clear();
|
|
75
87
|
await Promise.allSettled(hosts.map((host) => host.stop()));
|
|
76
88
|
this.keepAwake.dispose();
|
|
77
89
|
}
|
|
78
90
|
/** Re-reads the account's gateways (after add/remove) without restarting the daemon. */
|
|
79
91
|
async reload() {
|
|
80
|
-
await this.
|
|
81
|
-
|
|
92
|
+
const configured = await loadGateways(this.deps.userId);
|
|
93
|
+
const identity = (gateway) => JSON.stringify([
|
|
94
|
+
gateway.id, gateway.deviceId, gateway.name, gateway.kind, gateway.command, gateway.args ?? [],
|
|
95
|
+
Object.entries(gateway.env ?? {}).sort(([a], [b]) => a.localeCompare(b)), gateway.enabled,
|
|
96
|
+
]);
|
|
97
|
+
for (const host of [...this.hosts]) {
|
|
98
|
+
const previous = this.definitions.get(host);
|
|
99
|
+
const next = configured.find((gateway) => gateway.id === previous.id);
|
|
100
|
+
if (next && identity(previous) === identity(next))
|
|
101
|
+
continue;
|
|
102
|
+
await host.stop();
|
|
103
|
+
this.hosts = this.hosts.filter((candidate) => candidate !== host);
|
|
104
|
+
this.definitions.delete(host);
|
|
105
|
+
}
|
|
106
|
+
await this.reconcile(configured, false);
|
|
82
107
|
return this.status();
|
|
83
108
|
}
|
|
84
109
|
/** Saves a change made in the app, applies it to the running gateway and syncs the robot row. */
|
|
@@ -219,4 +219,6 @@ export declare function retireRemovedRobots(db: Tx, input: {
|
|
|
219
219
|
keepIds: string[];
|
|
220
220
|
}): Promise<string[]>;
|
|
221
221
|
export declare function softDeleteRobot(db: Tx, robotId: string): Promise<void>;
|
|
222
|
+
/** Only the settings for this robot may choose its agent's workspace. */
|
|
223
|
+
export declare function readAgentWorkspace(db: Tx, threadId: string, gatewayId: string): Promise<string | null>;
|
|
222
224
|
export {};
|
|
@@ -595,3 +595,16 @@ function robotMachineId(metaData) {
|
|
|
595
595
|
export async function softDeleteRobot(db, robotId) {
|
|
596
596
|
await db.execute('UPDATE c_robot SET is_del = 1, updated_at = ? WHERE id = ?', [new Date().toISOString(), robotId]);
|
|
597
597
|
}
|
|
598
|
+
/** Only the settings for this robot may choose its agent's workspace. */
|
|
599
|
+
export async function readAgentWorkspace(db, threadId, gatewayId) {
|
|
600
|
+
const row = await db.getOptional(`SELECT p.meta_data FROM c_thread t JOIN c_project p ON p.id = t.project_id
|
|
601
|
+
WHERE t.id = ? AND (p.is_del IS NULL OR p.is_del = 0) LIMIT 1`, [threadId]);
|
|
602
|
+
try {
|
|
603
|
+
const settings = JSON.parse(row?.meta_data ?? '{}').runSettings;
|
|
604
|
+
const value = settings?.robotId === gatewayId ? settings.values?.['dinox.cwd'] : null;
|
|
605
|
+
return typeof value === 'string' && value.length > 0 && value.length <= 4096 ? value : null;
|
|
606
|
+
}
|
|
607
|
+
catch {
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
}
|
package/dist/gateway/types.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export type GatewayDefinition = {
|
|
|
26
26
|
/** ACP: executable started over stdio, e.g. `hermes` with args `["acp"]`. */
|
|
27
27
|
command?: string;
|
|
28
28
|
args?: string[];
|
|
29
|
-
/**
|
|
29
|
+
/** Last configured fallback after the conversation's and agent's working folders. */
|
|
30
30
|
cwd?: string;
|
|
31
31
|
/**
|
|
32
32
|
* Directory of sessions bound before sessions recorded their own: the
|
|
@@ -118,9 +118,10 @@ export type GatewayTurnInput = {
|
|
|
118
118
|
sessionId: string | null;
|
|
119
119
|
/** Working directory of the session (a new one starts here; a continued one is loaded with it). */
|
|
120
120
|
cwd: string;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
/** Build context only after the adapter knows whether history survived. */
|
|
122
|
+
prompt: (session: {
|
|
123
|
+
created: boolean;
|
|
124
|
+
}) => Promise<string>;
|
|
124
125
|
/** Attachments the user sent with this turn (trusted, https). */
|
|
125
126
|
resources: Array<{
|
|
126
127
|
uri: string;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { GatewayDefinition } from './types.js';
|
|
2
|
+
import type { GatewaySessionBinding } from './threadStore.js';
|
|
3
|
+
/** A topic reset changes context, while the conversation keeps its folder. */
|
|
4
|
+
export declare function resolveTurnWorkspace(gateway: GatewayDefinition, binding: GatewaySessionBinding | null, continued: boolean, config?: Record<string, string>): Promise<string>;
|
|
5
|
+
export declare function queuedTurnPrompt(prompts: string[]): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import { AGENT_CWD_CONFIG_KEY } from './protocol.js';
|
|
3
|
+
import { resolveWorkspaceFolder } from './workspaceFolders.js';
|
|
4
|
+
/** A topic reset changes context, while the conversation keeps its folder. */
|
|
5
|
+
export async function resolveTurnWorkspace(gateway, binding, continued, config) {
|
|
6
|
+
if (continued)
|
|
7
|
+
return binding?.cwd ?? gateway.previousCwd ?? gateway.cwd ?? os.homedir();
|
|
8
|
+
if (binding?.cwd)
|
|
9
|
+
return binding.cwd;
|
|
10
|
+
const requested = config?.[AGENT_CWD_CONFIG_KEY];
|
|
11
|
+
if (requested) {
|
|
12
|
+
try {
|
|
13
|
+
return await resolveWorkspaceFolder(requested);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
throw new Error(`Working folder is unavailable: ${requested} (${error instanceof Error ? error.message : String(error)})`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return gateway.cwd ?? os.homedir();
|
|
20
|
+
}
|
|
21
|
+
export function queuedTurnPrompt(prompts) {
|
|
22
|
+
if (prompts.length === 1)
|
|
23
|
+
return prompts[0].trim();
|
|
24
|
+
return [
|
|
25
|
+
'<dinox_queued_messages>',
|
|
26
|
+
'The user sent these messages one after another while you were busy. Answer them together.',
|
|
27
|
+
...prompts.map((text, index) => `[Message ${index + 1}] ${text.trim().replaceAll('</dinox_queued_messages>', '<\\/dinox_queued_messages>')}`),
|
|
28
|
+
'</dinox_queued_messages>',
|
|
29
|
+
].join('\n');
|
|
30
|
+
}
|
|
@@ -18,10 +18,12 @@ export type WorkspaceFolder = {
|
|
|
18
18
|
};
|
|
19
19
|
/** The folder an agent may work in, resolved to its real path. */
|
|
20
20
|
export declare function resolveWorkspaceFolder(input: string, home?: string): Promise<string>;
|
|
21
|
-
/**
|
|
22
|
-
export declare function
|
|
21
|
+
/** A portable folder name, optionally distinguished by the agent ID's last six characters. */
|
|
22
|
+
export declare function agentFolderName(name: string, agentId?: string | null): string;
|
|
23
|
+
/** `~/dinox-agents/<name>`: a folder of the agent's own, offered as the default choice. */
|
|
24
|
+
export declare function agentHomeFolder(agentName: string, home?: string, agentId?: string | null): string;
|
|
23
25
|
/** Creates the agent's own folder when missing; the only folder the app can create. */
|
|
24
|
-
export declare function ensureAgentHomeFolder(agentName: string, home?: string): Promise<string>;
|
|
26
|
+
export declare function ensureAgentHomeFolder(agentName: string, home?: string, agentId?: string | null): Promise<string>;
|
|
25
27
|
/** The visible subfolders of a folder (the home folder included), for browsing. */
|
|
26
28
|
export declare function listWorkspaceFolders(input: string | null | undefined, home?: string): Promise<{
|
|
27
29
|
path: string;
|
|
@@ -13,7 +13,7 @@ const MAX_FOLDERS = 200;
|
|
|
13
13
|
/** Home-level folders that hold system data rather than the user's work. */
|
|
14
14
|
const SYSTEM_FOLDERS = new Set(['Library', 'Applications']);
|
|
15
15
|
const SKIPPED_FOLDERS = new Set(['node_modules']);
|
|
16
|
-
const AGENT_HOME_PARENT = '
|
|
16
|
+
const AGENT_HOME_PARENT = 'dinox-agents';
|
|
17
17
|
async function realHome(home) {
|
|
18
18
|
return fs.realpath(home).catch(() => path.resolve(home));
|
|
19
19
|
}
|
|
@@ -73,14 +73,27 @@ export async function resolveWorkspaceFolder(input, home = os.homedir()) {
|
|
|
73
73
|
}
|
|
74
74
|
return real;
|
|
75
75
|
}
|
|
76
|
-
/**
|
|
77
|
-
export function
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
/** A portable folder name, optionally distinguished by the agent ID's last six characters. */
|
|
77
|
+
export function agentFolderName(name, agentId) {
|
|
78
|
+
let base = name.normalize('NFC')
|
|
79
|
+
.replace(/[\u0000-\u001f\u007f/\\:*?"<>|]/g, '')
|
|
80
|
+
.replace(/\s+/g, '-')
|
|
81
|
+
.toLowerCase()
|
|
82
|
+
.replace(/-+/g, '-')
|
|
83
|
+
.replace(/^[-.]+|[-.]+$/g, '');
|
|
84
|
+
base = Array.from(base).slice(0, 48).join('').replace(/[-.]+$/g, '') || 'agent';
|
|
85
|
+
if (/^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/.test(base))
|
|
86
|
+
base += '-agent';
|
|
87
|
+
const suffix = agentId?.replace(/[^a-z0-9]/gi, '').toLowerCase().slice(-6);
|
|
88
|
+
return suffix ? `${base}-${suffix}` : base;
|
|
89
|
+
}
|
|
90
|
+
/** `~/dinox-agents/<name>`: a folder of the agent's own, offered as the default choice. */
|
|
91
|
+
export function agentHomeFolder(agentName, home = os.homedir(), agentId) {
|
|
92
|
+
return path.join(home, AGENT_HOME_PARENT, agentFolderName(agentName, agentId));
|
|
80
93
|
}
|
|
81
94
|
/** Creates the agent's own folder when missing; the only folder the app can create. */
|
|
82
|
-
export async function ensureAgentHomeFolder(agentName, home = os.homedir()) {
|
|
83
|
-
const folder = agentHomeFolder(agentName, home);
|
|
95
|
+
export async function ensureAgentHomeFolder(agentName, home = os.homedir(), agentId) {
|
|
96
|
+
const folder = agentHomeFolder(agentName, home, agentId);
|
|
84
97
|
await fs.mkdir(folder, { recursive: true, mode: 0o755 });
|
|
85
98
|
return resolveWorkspaceFolder(folder, home);
|
|
86
99
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AbstractPowerSyncDatabase } from '@powersync/common';
|
|
2
|
+
import { type RemoteDeviceWorkspaceListRequest, type RemoteDeviceWorkspaceListResponse } from '../vendor/electron/shared/remote-device-agent.js';
|
|
3
|
+
import type { GatewayDefinition } from './types.js';
|
|
4
|
+
/** Wire paths stay relative even when a directory on disk is a symlink. */
|
|
5
|
+
export declare function listGatewayWorkspace(db: AbstractPowerSyncDatabase, userId: string, gateway: GatewayDefinition, request: RemoteDeviceWorkspaceListRequest): Promise<RemoteDeviceWorkspaceListResponse>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { REMOTE_DEVICE_PROTOCOL_VERSION, } from '../vendor/electron/shared/remote-device-agent.js';
|
|
4
|
+
import { readAgentWorkspace, readSessionBinding, threadExists } from './threadStore.js';
|
|
5
|
+
import { resolveWorkspaceFolder } from './workspaceFolders.js';
|
|
6
|
+
const SUBJECT_ID = /^[a-z0-9][a-z0-9_-]{0,127}$/;
|
|
7
|
+
const DEVICE_ID = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
8
|
+
const IGNORED_NAMES = new Set(['node_modules', 'build', 'coverage', 'dist']);
|
|
9
|
+
function withinRoot(root, target) {
|
|
10
|
+
const relative = path.relative(root, target);
|
|
11
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
12
|
+
}
|
|
13
|
+
/** Wire paths stay relative even when a directory on disk is a symlink. */
|
|
14
|
+
export async function listGatewayWorkspace(db, userId, gateway, request) {
|
|
15
|
+
const base = {
|
|
16
|
+
protocolVersion: REMOTE_DEVICE_PROTOCOL_VERSION, type: 'workspace.list.result', requestId: request.requestId,
|
|
17
|
+
threadId: request.threadId, parentPath: '', ok: false, entries: [], nextCursor: null, truncated: false, fetchedAt: new Date().toISOString(),
|
|
18
|
+
};
|
|
19
|
+
const fail = (code, message) => ({ ...base, error: { code, message } });
|
|
20
|
+
const parent = request.parentPath ?? '';
|
|
21
|
+
if (request.protocolVersion !== REMOTE_DEVICE_PROTOCOL_VERSION ||
|
|
22
|
+
typeof request.requestId !== 'string' || !SUBJECT_ID.test(request.requestId) ||
|
|
23
|
+
typeof request.threadId !== 'string' || !SUBJECT_ID.test(request.threadId) ||
|
|
24
|
+
typeof request.clientDeviceId !== 'string' || !DEVICE_ID.test(request.clientDeviceId))
|
|
25
|
+
return fail('invalid_request', 'Invalid query identity');
|
|
26
|
+
if (typeof parent !== 'string' || parent.length > 2048 || parent.includes('\\') ||
|
|
27
|
+
path.win32.isAbsolute(parent) || parent.startsWith('/') ||
|
|
28
|
+
(parent && parent.split('/').some((part) => !part || part === '.' || part === '..' || part === '.git' || part === 'node_modules')))
|
|
29
|
+
return fail('invalid_request', 'Invalid workspace path');
|
|
30
|
+
base.parentPath = parent;
|
|
31
|
+
const cursor = request.cursor;
|
|
32
|
+
const offset = cursor === undefined || cursor === null || cursor === '' ? 0 : typeof cursor === 'string' && /^\d{1,8}$/.test(cursor) ? Number(cursor) : null;
|
|
33
|
+
if (offset === null)
|
|
34
|
+
return fail('invalid_request', 'Invalid workspace cursor');
|
|
35
|
+
try {
|
|
36
|
+
if (!(await threadExists(db, request.threadId, userId)))
|
|
37
|
+
return fail('thread_not_found', 'Conversation not found');
|
|
38
|
+
const binding = await readSessionBinding(db, request.threadId, gateway.id);
|
|
39
|
+
const agentFolder = binding?.cwd ? null : await readAgentWorkspace(db, request.threadId, gateway.id);
|
|
40
|
+
const root = binding?.cwd ?? (agentFolder ? await resolveWorkspaceFolder(agentFolder) : gateway.cwd);
|
|
41
|
+
if (!root)
|
|
42
|
+
return { ...base, ok: true };
|
|
43
|
+
const rootReal = await fs.realpath(root);
|
|
44
|
+
const target = await fs.realpath(path.resolve(rootReal, ...parent.split('/')));
|
|
45
|
+
if (path.relative(rootReal, target).split(path.sep).some((part) => part === '.git' || part === 'node_modules'))
|
|
46
|
+
return fail('invalid_request', 'Directory contents are unavailable');
|
|
47
|
+
if (!withinRoot(rootReal, target))
|
|
48
|
+
return fail('invalid_request', 'Workspace path escapes its root');
|
|
49
|
+
if (!(await fs.stat(target)).isDirectory())
|
|
50
|
+
return fail('invalid_request', 'Workspace path is not a directory');
|
|
51
|
+
const rank = (entry) => entry.isDirectory() ? 0 : entry.isSymbolicLink() ? 2 : 1;
|
|
52
|
+
const all = (await fs.readdir(target, { withFileTypes: true }))
|
|
53
|
+
.filter((entry) => !!entry.name && !entry.name.startsWith('.') && !entry.name.includes('\\') && !IGNORED_NAMES.has(entry.name))
|
|
54
|
+
.sort((a, b) => rank(a) - rank(b) || a.name.localeCompare(b.name, undefined, { numeric: true }));
|
|
55
|
+
const bounded = all.slice(0, 5000);
|
|
56
|
+
if (offset > bounded.length)
|
|
57
|
+
return fail('invalid_request', 'Invalid workspace cursor');
|
|
58
|
+
const limit = typeof request.limit === 'number' && Number.isFinite(request.limit) ? Math.min(200, Math.max(1, Math.trunc(request.limit))) : 100;
|
|
59
|
+
const page = bounded.slice(offset, offset + limit);
|
|
60
|
+
const entries = await Promise.all(page.map(async (entry) => {
|
|
61
|
+
const kind = entry.isSymbolicLink() ? 'symlink' : entry.isDirectory() ? 'directory' : 'file';
|
|
62
|
+
const stat = await fs.lstat(path.join(target, entry.name)).catch(() => null);
|
|
63
|
+
return {
|
|
64
|
+
path: parent ? `${parent}/${entry.name}` : entry.name,
|
|
65
|
+
name: entry.name,
|
|
66
|
+
kind,
|
|
67
|
+
hasChildren: kind === 'directory',
|
|
68
|
+
...(stat?.isFile() ? { size: stat.size } : {}),
|
|
69
|
+
...(stat ? { modifiedAt: stat.mtime.toISOString() } : {}),
|
|
70
|
+
};
|
|
71
|
+
}));
|
|
72
|
+
const next = offset + page.length;
|
|
73
|
+
return { ...base, ok: true, entries, nextCursor: next < bounded.length ? String(next) : null, truncated: all.length > bounded.length };
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
return fail('query_failed', error instanceof Error ? error.message : String(error));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
import { createTimeoutSignal } from '../utils/abort.js';
|
|
2
|
+
import { reportProgress } from '../utils/progress.js';
|
|
2
3
|
import { getStatusSnapshot, waitForDownloadIdle } from './runtime.js';
|
|
3
4
|
import { syncNoteTokenIndex } from './tokenIndex.js';
|
|
4
5
|
export async function runSingleSyncPass(db, timeoutMs) {
|
|
5
6
|
const deadline = Date.now() + Math.max(1, timeoutMs);
|
|
6
7
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
|
7
|
-
|
|
8
|
+
// PowerSync reports operation counts while a checkpoint downloads.
|
|
9
|
+
const stopDownloadProgress = db.registerListener({
|
|
10
|
+
statusChanged: (status) => {
|
|
11
|
+
const progress = status.downloadProgress;
|
|
12
|
+
if (progress)
|
|
13
|
+
reportProgress({ phase: 'download', current: progress.downloadedOperations, total: progress.totalOperations });
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
reportProgress({ phase: 'download' });
|
|
17
|
+
const downloadIdle = await waitForDownloadIdle(db, Math.max(1, Math.min(remainingMs(), 60_000)))
|
|
18
|
+
.finally(stopDownloadProgress);
|
|
8
19
|
const indexTimeoutMs = remainingMs();
|
|
9
20
|
const timeout = indexTimeoutMs > 0 ? createTimeoutSignal(indexTimeoutMs) : null;
|
|
10
21
|
const tokenIndex = await syncNoteTokenIndex(db, {
|
|
11
22
|
signal: timeout?.signal ?? AbortSignal.abort(),
|
|
23
|
+
onBatch: (progress) => reportProgress({ phase: 'index', current: progress.scanned }),
|
|
12
24
|
}).finally(() => timeout?.cancel());
|
|
13
25
|
const status = getStatusSnapshot(db);
|
|
14
26
|
const uploadIdle = !status.dataFlowStatus.uploading;
|
|
@@ -25,6 +25,8 @@ export declare function syncNoteTokenIndex(db: AbstractPowerSyncDatabase, option
|
|
|
25
25
|
nowIso?: () => string;
|
|
26
26
|
tokenize?: (text: string, limit?: number) => string[];
|
|
27
27
|
signal?: AbortSignal;
|
|
28
|
+
/** Called after each batch with the running counters. */
|
|
29
|
+
onBatch?: (result: NoteTokenIndexResult) => void;
|
|
28
30
|
}): Promise<NoteTokenIndexResult>;
|
|
29
31
|
export declare function inspectNoteTokenIndex(db: AbstractPowerSyncDatabase): Promise<NoteTokenIndexInspection>;
|
|
30
32
|
export declare function rebuildNoteTokenIndex(db: AbstractPowerSyncDatabase, options?: Parameters<typeof syncNoteTokenIndex>[1]): Promise<NoteTokenIndexResult>;
|
|
@@ -67,6 +67,7 @@ export async function syncNoteTokenIndex(db, options) {
|
|
|
67
67
|
}
|
|
68
68
|
await updateIndexCursor(db, cursorUpdatedAt, cursorId, nowIso);
|
|
69
69
|
result.cursor = { updatedAt: cursorUpdatedAt, id: cursorId };
|
|
70
|
+
options?.onBatch?.(result);
|
|
70
71
|
}
|
|
71
72
|
result.complete = await reconcileSkippedRows(db, result, batchSize, nowIso, tokenize, signal);
|
|
72
73
|
result.cursor = {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CommandExecuteResult } from '../daemon/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs a command line in this process with its stdout/stderr captured instead
|
|
4
|
+
* of written, the same way the daemon runs a forwarded command.
|
|
5
|
+
*/
|
|
6
|
+
export declare function runCapturedInProcess(argv: string[], onFatalError?: (error: unknown) => void): Promise<CommandExecuteResult>;
|
|
7
|
+
/**
|
|
8
|
+
* Runs one data command and returns its captured output: inside the daemon
|
|
9
|
+
* when it is available, otherwise in this process (honoring `--request-id`).
|
|
10
|
+
*/
|
|
11
|
+
export declare function executeCommandLine(argv: string[]): Promise<CommandExecuteResult>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { tryForwardToDaemon } from '../daemon/forward.js';
|
|
2
|
+
import { localCommandExecution, requestContext } from '../utils/requestContext.js';
|
|
3
|
+
import { requestIdFromArgv, runWithRequestId } from './store.js';
|
|
4
|
+
/**
|
|
5
|
+
* Runs a command line in this process with its stdout/stderr captured instead
|
|
6
|
+
* of written, the same way the daemon runs a forwarded command.
|
|
7
|
+
*/
|
|
8
|
+
export async function runCapturedInProcess(argv, onFatalError) {
|
|
9
|
+
const { runCli } = await import('../runCli.js');
|
|
10
|
+
const context = {
|
|
11
|
+
stdout: [], stderr: [], format: null, noticeProvider: null, cwd: process.cwd(),
|
|
12
|
+
};
|
|
13
|
+
const exitCode = await requestContext.run(context, () => localCommandExecution.run(true, () => runCli(argv, onFatalError)));
|
|
14
|
+
return { stdout: context.stdout.join(''), stderr: context.stderr.join(''), exitCode };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Runs one data command and returns its captured output: inside the daemon
|
|
18
|
+
* when it is available, otherwise in this process (honoring `--request-id`).
|
|
19
|
+
*/
|
|
20
|
+
export async function executeCommandLine(argv) {
|
|
21
|
+
const forwarded = await tryForwardToDaemon(argv);
|
|
22
|
+
if (forwarded)
|
|
23
|
+
return forwarded;
|
|
24
|
+
const key = requestIdFromArgv(argv);
|
|
25
|
+
return key ? runWithRequestId(key, argv, () => runCapturedInProcess(argv)) : runCapturedInProcess(argv);
|
|
26
|
+
}
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { GLOBAL_OPTION_NAMES } from '../commands/schema/globalOptions.js';
|
|
3
4
|
import { listCommandSchemas } from '../commands/schema/spec.js';
|
|
4
5
|
// Global flags accepted by every command (see src/cli.ts) plus Commander builtins.
|
|
5
|
-
const GLOBAL_FLAGS = new Set([
|
|
6
|
-
'--json',
|
|
7
|
-
'--format',
|
|
8
|
-
'--offline',
|
|
9
|
-
'--require-sync',
|
|
10
|
-
'--sync-timeout',
|
|
11
|
-
'--verbose',
|
|
12
|
-
'--help',
|
|
13
|
-
'--version',
|
|
14
|
-
]);
|
|
6
|
+
const GLOBAL_FLAGS = new Set([...GLOBAL_OPTION_NAMES, '--help', '--version']);
|
|
15
7
|
const SHELL_BOUNDARY = /\s(?:\||&&|\|\||;|>|2>|#)\s/;
|
|
16
8
|
function buildCommandIndex() {
|
|
17
9
|
const byCommand = new Map();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function encodeCursor(command: string, offset: number, query: unknown): string;
|
|
2
|
+
/** Returns the offset a cursor points at; throws when it is malformed or belongs to another query. */
|
|
3
|
+
export declare function decodeCursor(token: string, command: string, query: unknown): number;
|
|
4
|
+
/**
|
|
5
|
+
* Resolves the start offset from `--cursor` or `--offset`; the two are
|
|
6
|
+
* mutually exclusive (an offset of 0, the default, does not count).
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveCursorOffset(cursor: unknown, offset: number, command: string, query: unknown): number;
|
|
9
|
+
export declare function nextCursor(command: string, query: unknown, nextOffset: number, hasMore: boolean): string | null;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { DinoxError } from './errors.js';
|
|
3
|
+
function stableJson(value) {
|
|
4
|
+
if (Array.isArray(value))
|
|
5
|
+
return `[${value.map(stableJson).join(',')}]`;
|
|
6
|
+
if (value && typeof value === 'object') {
|
|
7
|
+
const entries = Object.entries(value)
|
|
8
|
+
.filter(([, item]) => item !== undefined)
|
|
9
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
10
|
+
return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(',')}}`;
|
|
11
|
+
}
|
|
12
|
+
return JSON.stringify(value ?? null);
|
|
13
|
+
}
|
|
14
|
+
function queryHash(command, query) {
|
|
15
|
+
return createHash('sha256').update(`${command}\n${stableJson(query)}`).digest('base64url').slice(0, 16);
|
|
16
|
+
}
|
|
17
|
+
export function encodeCursor(command, offset, query) {
|
|
18
|
+
const payload = { v: 1, c: command, o: offset, q: queryHash(command, query) };
|
|
19
|
+
return Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
20
|
+
}
|
|
21
|
+
function invalidCursor(message) {
|
|
22
|
+
return new DinoxError(message, {
|
|
23
|
+
code: 'INVALID_ARGUMENT',
|
|
24
|
+
recoverable: true,
|
|
25
|
+
suggestion: 'Pass the nextCursor from the previous page of the same command and query, or drop --cursor to start over.',
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** Returns the offset a cursor points at; throws when it is malformed or belongs to another query. */
|
|
29
|
+
export function decodeCursor(token, command, query) {
|
|
30
|
+
let payload;
|
|
31
|
+
try {
|
|
32
|
+
payload = JSON.parse(Buffer.from(token.trim(), 'base64url').toString('utf8'));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
throw invalidCursor('--cursor is not a valid pagination cursor');
|
|
36
|
+
}
|
|
37
|
+
if (!payload || payload.v !== 1 || typeof payload.o !== 'number' || !Number.isInteger(payload.o) || payload.o < 0) {
|
|
38
|
+
throw invalidCursor('--cursor is not a valid pagination cursor');
|
|
39
|
+
}
|
|
40
|
+
if (payload.c !== command || payload.q !== queryHash(command, query)) {
|
|
41
|
+
throw invalidCursor(`--cursor belongs to a different ${payload.c === command ? 'query' : 'command'}`);
|
|
42
|
+
}
|
|
43
|
+
return payload.o;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolves the start offset from `--cursor` or `--offset`; the two are
|
|
47
|
+
* mutually exclusive (an offset of 0, the default, does not count).
|
|
48
|
+
*/
|
|
49
|
+
export function resolveCursorOffset(cursor, offset, command, query) {
|
|
50
|
+
if (cursor === undefined || cursor === null || cursor === '')
|
|
51
|
+
return offset;
|
|
52
|
+
if (typeof cursor !== 'string')
|
|
53
|
+
throw invalidCursor('--cursor must be a string');
|
|
54
|
+
if (offset > 0) {
|
|
55
|
+
throw new DinoxError('--cursor and --offset cannot be used together', { code: 'INVALID_ARGUMENT', recoverable: true });
|
|
56
|
+
}
|
|
57
|
+
return decodeCursor(cursor, command, query);
|
|
58
|
+
}
|
|
59
|
+
export function nextCursor(command, query, nextOffset, hasMore) {
|
|
60
|
+
return hasMore ? encodeCursor(command, nextOffset, query) : null;
|
|
61
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const DEFAULT_FORMAT_ENV = "DINOX_FORMAT";
|
|
2
|
+
/**
|
|
3
|
+
* Applies DINOX_FORMAT (json, yaml, or auto: json when stdout is not a
|
|
4
|
+
* terminal) by adding `--format` when the command line has none. Agents set
|
|
5
|
+
* it once instead of passing `--format json` on every call. Help, version and
|
|
6
|
+
* protocol commands are left alone.
|
|
7
|
+
*/
|
|
8
|
+
export declare function applyDefaultFormat(argv: string[], env?: NodeJS.ProcessEnv, stdoutIsTTY?: boolean): string[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { DinoxError } from './errors.js';
|
|
2
|
+
export const DEFAULT_FORMAT_ENV = 'DINOX_FORMAT';
|
|
3
|
+
/** Commands whose stdout is a protocol, never a command result. */
|
|
4
|
+
const PROTOCOL_COMMANDS = [['gateway', 'mcp'], ['mcp'], ['daemon', 'supervise']];
|
|
5
|
+
function commandWords(argv) {
|
|
6
|
+
return argv.filter((arg) => !arg.startsWith('-')).slice(0, 2);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Applies DINOX_FORMAT (json, yaml, or auto: json when stdout is not a
|
|
10
|
+
* terminal) by adding `--format` when the command line has none. Agents set
|
|
11
|
+
* it once instead of passing `--format json` on every call. Help, version and
|
|
12
|
+
* protocol commands are left alone.
|
|
13
|
+
*/
|
|
14
|
+
export function applyDefaultFormat(argv, env = process.env, stdoutIsTTY = Boolean(process.stdout.isTTY)) {
|
|
15
|
+
const raw = env[DEFAULT_FORMAT_ENV]?.trim().toLowerCase();
|
|
16
|
+
if (!raw)
|
|
17
|
+
return argv;
|
|
18
|
+
if (raw !== 'json' && raw !== 'yaml' && raw !== 'auto') {
|
|
19
|
+
throw new DinoxError(`${DEFAULT_FORMAT_ENV} must be one of: json, yaml, auto`, {
|
|
20
|
+
code: 'INVALID_ARGUMENT',
|
|
21
|
+
recoverable: true,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const format = raw === 'auto' ? (stdoutIsTTY ? null : 'json') : raw;
|
|
25
|
+
if (!format || argv.length === 0)
|
|
26
|
+
return argv;
|
|
27
|
+
const terminator = argv.indexOf('--');
|
|
28
|
+
const options = terminator === -1 ? argv : argv.slice(0, terminator);
|
|
29
|
+
const explicit = options.some((arg) => arg === '--format' || arg.startsWith('--format=') || arg === '--json');
|
|
30
|
+
const informational = options.some((arg) => ['--help', '-h', '--version', '-V'].includes(arg)) || options[0] === 'help';
|
|
31
|
+
const words = commandWords(options);
|
|
32
|
+
const protocol = PROTOCOL_COMMANDS.some((command) => command.every((word, index) => words[index] === word));
|
|
33
|
+
if (explicit || informational || protocol)
|
|
34
|
+
return argv;
|
|
35
|
+
return terminator === -1
|
|
36
|
+
? [...argv, '--format', format]
|
|
37
|
+
: [...argv.slice(0, terminator), '--format', format, ...argv.slice(terminator)];
|
|
38
|
+
}
|
|
@@ -471,6 +471,10 @@ export declare const ERROR_CODES: {
|
|
|
471
471
|
readonly exitCode: 5;
|
|
472
472
|
readonly description: "No command schema exists for the requested path.";
|
|
473
473
|
};
|
|
474
|
+
readonly SEMANTIC_SEARCH_UNAVAILABLE: {
|
|
475
|
+
readonly exitCode: 1;
|
|
476
|
+
readonly description: "The semantic search service could not be reached or failed; keyword search (note search) still works.";
|
|
477
|
+
};
|
|
474
478
|
readonly SKILLS_LOCK_INVALID: {
|
|
475
479
|
readonly exitCode: 1;
|
|
476
480
|
readonly description: "The skills installer lock file is malformed.";
|
package/dist/utils/errorCodes.js
CHANGED
|
@@ -123,6 +123,7 @@ export const ERROR_CODES = {
|
|
|
123
123
|
SAVED_VIEW_UNSUPPORTED_LAYOUT: { exitCode: 1, description: 'The saved view layout is unsupported.' },
|
|
124
124
|
SAVED_VIEW_VALUE_TYPE_MISMATCH: { exitCode: 1, description: 'A filter value does not match the field type.' },
|
|
125
125
|
SCHEMA_PATH_NOT_FOUND: { exitCode: 5, description: 'No command schema exists for the requested path.' },
|
|
126
|
+
SEMANTIC_SEARCH_UNAVAILABLE: { exitCode: 1, description: 'The semantic search service could not be reached or failed; keyword search (note search) still works.' },
|
|
126
127
|
SKILLS_LOCK_INVALID: { exitCode: 1, description: 'The skills installer lock file is malformed.' },
|
|
127
128
|
SKILLS_LOCK_READ_FAILED: { exitCode: 1, description: 'The skills installer lock file could not be read.' },
|
|
128
129
|
SKILLS_MIGRATION_FAILED: { exitCode: 1, description: 'The skills installer failed during migration.' },
|