@dinoxx/dinox-cli 2.3.0 → 2.5.2
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 +17 -3
- package/dist/cli.js +2 -0
- package/dist/commands/gateway/index.d.ts +4 -0
- package/dist/commands/gateway/index.js +300 -0
- package/dist/commands/schema/renderSkills.js +4 -0
- package/dist/commands/schema/spec.js +2 -0
- package/dist/config/paths.d.ts +1 -0
- package/dist/config/paths.js +1 -1
- package/dist/daemon/server.d.ts +1 -0
- package/dist/daemon/server.js +41 -0
- package/dist/gateway/adapters/acp.d.ts +47 -0
- package/dist/gateway/adapters/acp.js +302 -0
- package/dist/gateway/adapters/echo.d.ts +12 -0
- package/dist/gateway/adapters/echo.js +51 -0
- package/dist/gateway/adapters/index.d.ts +9 -0
- package/dist/gateway/adapters/index.js +31 -0
- package/dist/gateway/assistantProjection.d.ts +32 -0
- package/dist/gateway/assistantProjection.js +193 -0
- package/dist/gateway/config.d.ts +60 -0
- package/dist/gateway/config.js +291 -0
- package/dist/gateway/conversationContext.d.ts +44 -0
- package/dist/gateway/conversationContext.js +75 -0
- package/dist/gateway/detect.d.ts +40 -0
- package/dist/gateway/detect.js +145 -0
- package/dist/gateway/deviceHost.d.ts +91 -0
- package/dist/gateway/deviceHost.js +641 -0
- package/dist/gateway/keepAwake.d.ts +24 -0
- package/dist/gateway/keepAwake.js +64 -0
- package/dist/gateway/mcpServer.d.ts +4 -0
- package/dist/gateway/mcpServer.js +138 -0
- package/dist/gateway/protocol.d.ts +116 -0
- package/dist/gateway/protocol.js +182 -0
- package/dist/gateway/runExecutor.d.ts +91 -0
- package/dist/gateway/runExecutor.js +422 -0
- package/dist/gateway/service.d.ts +30 -0
- package/dist/gateway/service.js +112 -0
- package/dist/gateway/streamPublisher.d.ts +33 -0
- package/dist/gateway/streamPublisher.js +93 -0
- package/dist/gateway/threadStore.d.ts +222 -0
- package/dist/gateway/threadStore.js +597 -0
- package/dist/gateway/types.d.ts +136 -0
- package/dist/gateway/types.js +10 -0
- package/dist/gateway/workspaceFolders.d.ts +33 -0
- package/dist/gateway/workspaceFolders.js +129 -0
- package/dist/vendor/electron/domain/properties.js +12 -1
- package/dist/vendor/electron/domain/saved-views.js +4 -1
- package/dist/vendor/electron/shared/external-types.d.ts +18 -0
- package/dist/vendor/electron/shared/note-properties.d.ts +16 -0
- package/dist/vendor/electron/shared/note-properties.js +45 -0
- package/dist/vendor/electron/shared/powersync-read-models.d.ts +7 -1
- package/dist/vendor/electron/shared/remote-device-agent.d.ts +373 -0
- package/dist/vendor/electron/shared/remote-device-agent.js +191 -0
- package/package.json +5 -2
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { REMOTE_DEVICE_MAX_EVENT_BYTES, REMOTE_DEVICE_PROTOCOL_VERSION, } from '../vendor/electron/shared/remote-device-agent.js';
|
|
2
|
+
import { TERMINAL_RUN_STATUSES } from './threadStore.js';
|
|
3
|
+
const COALESCE_MS = 100;
|
|
4
|
+
const MAX_APPROVALS = 32;
|
|
5
|
+
function byteLength(value) {
|
|
6
|
+
return Buffer.byteLength(JSON.stringify(value), 'utf8');
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Keeps an event under the NATS payload budget: drop parts (the snapshot is
|
|
10
|
+
* self-correcting and `run.status` recovers the rest), then drop extras.
|
|
11
|
+
*/
|
|
12
|
+
export function boundRunEvent(event) {
|
|
13
|
+
if (byteLength(event) <= REMOTE_DEVICE_MAX_EVENT_BYTES)
|
|
14
|
+
return event;
|
|
15
|
+
const metadata = event.assistant.message.metadata ?? {};
|
|
16
|
+
const stripped = {
|
|
17
|
+
...event,
|
|
18
|
+
assistant: {
|
|
19
|
+
message: {
|
|
20
|
+
...event.assistant.message,
|
|
21
|
+
parts: [],
|
|
22
|
+
metadata: { status: metadata.status, runId: metadata.runId },
|
|
23
|
+
},
|
|
24
|
+
truncated: true,
|
|
25
|
+
},
|
|
26
|
+
pendingApprovals: event.pendingApprovals?.slice(0, MAX_APPROVALS),
|
|
27
|
+
};
|
|
28
|
+
if (byteLength(stripped) <= REMOTE_DEVICE_MAX_EVENT_BYTES)
|
|
29
|
+
return stripped;
|
|
30
|
+
const minimal = { ...stripped, pendingApprovals: [], usage: null };
|
|
31
|
+
if (byteLength(minimal) <= REMOTE_DEVICE_MAX_EVENT_BYTES)
|
|
32
|
+
return minimal;
|
|
33
|
+
throw new Error('run.event exceeds the remote-device payload limit');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* One per run. Streaming snapshots are coalesced to the latest per 100ms;
|
|
37
|
+
* approval and terminal states go out immediately (and supersede a pending one).
|
|
38
|
+
*/
|
|
39
|
+
export class RunStreamPublisher {
|
|
40
|
+
ids;
|
|
41
|
+
publish;
|
|
42
|
+
sequence = 0;
|
|
43
|
+
pending = null;
|
|
44
|
+
timer = null;
|
|
45
|
+
disposed = false;
|
|
46
|
+
constructor(ids, publish) {
|
|
47
|
+
this.ids = ids;
|
|
48
|
+
this.publish = publish;
|
|
49
|
+
}
|
|
50
|
+
push(snapshot) {
|
|
51
|
+
if (this.disposed)
|
|
52
|
+
return;
|
|
53
|
+
if (TERMINAL_RUN_STATUSES.has(snapshot.status) || snapshot.status === 'waiting_approval') {
|
|
54
|
+
this.clearTimer();
|
|
55
|
+
this.pending = null;
|
|
56
|
+
this.emit(snapshot);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this.pending = snapshot;
|
|
60
|
+
this.timer ??= setTimeout(() => {
|
|
61
|
+
this.timer = null;
|
|
62
|
+
const latest = this.pending;
|
|
63
|
+
this.pending = null;
|
|
64
|
+
if (latest)
|
|
65
|
+
this.emit(latest);
|
|
66
|
+
}, COALESCE_MS);
|
|
67
|
+
}
|
|
68
|
+
dispose() {
|
|
69
|
+
this.disposed = true;
|
|
70
|
+
this.clearTimer();
|
|
71
|
+
this.pending = null;
|
|
72
|
+
}
|
|
73
|
+
clearTimer() {
|
|
74
|
+
if (this.timer)
|
|
75
|
+
clearTimeout(this.timer);
|
|
76
|
+
this.timer = null;
|
|
77
|
+
}
|
|
78
|
+
emit(snapshot) {
|
|
79
|
+
this.sequence += 1;
|
|
80
|
+
this.publish(boundRunEvent({
|
|
81
|
+
protocolVersion: REMOTE_DEVICE_PROTOCOL_VERSION,
|
|
82
|
+
type: 'run.event',
|
|
83
|
+
runId: this.ids.runId,
|
|
84
|
+
threadId: this.ids.threadId,
|
|
85
|
+
sequence: this.sequence,
|
|
86
|
+
emittedAt: new Date().toISOString(),
|
|
87
|
+
revision: snapshot.revision,
|
|
88
|
+
status: snapshot.status,
|
|
89
|
+
assistant: { message: snapshot.assistant },
|
|
90
|
+
pendingApprovals: snapshot.pendingApprovals,
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import type { AbstractPowerSyncDatabase } from '@powersync/common';
|
|
2
|
+
import type { ChatUIMessage, ThreadRunCreateThreadRequest, ThreadRunEventStatus } from '../vendor/electron/shared/external-types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Persistence for gateway runs, written exactly like Dinox desktop's remote host
|
|
5
|
+
* (dinox-electron ThreadRunQueueService / ThreadRunCoordinatorHelpers) so every
|
|
6
|
+
* client reads identical rows:
|
|
7
|
+
* - the user message is written when the run is accepted;
|
|
8
|
+
* - live progress stays in the local-only `local_thread_run` snapshot;
|
|
9
|
+
* - the assistant message lands in `c_chat_message` once, when the run ends.
|
|
10
|
+
*/
|
|
11
|
+
type Tx = Pick<AbstractPowerSyncDatabase, 'execute' | 'getOptional' | 'getAll'>;
|
|
12
|
+
export declare const TERMINAL_RUN_STATUSES: ReadonlySet<ThreadRunEventStatus>;
|
|
13
|
+
export type StoredRun = {
|
|
14
|
+
id: string;
|
|
15
|
+
threadId: string;
|
|
16
|
+
status: ThreadRunEventStatus;
|
|
17
|
+
requestId: string | null;
|
|
18
|
+
assistant: ChatUIMessage | null;
|
|
19
|
+
updatedAt: string | null;
|
|
20
|
+
};
|
|
21
|
+
/** Mirrors desktop `withRunMetadata`: the UI reads `metadata.status` for streaming state. */
|
|
22
|
+
export declare function withRunMetadata(message: ChatUIMessage, runId: string, status: ThreadRunEventStatus): ChatUIMessage;
|
|
23
|
+
/**
|
|
24
|
+
* Synced queue marker on a user message (`metadata.queue`), shared by every
|
|
25
|
+
* client: the run that answers it has not started yet. It lives on the synced
|
|
26
|
+
* message because run rows (`local_thread_run`) are local to one process, so
|
|
27
|
+
* other devices could not otherwise show the queue. Removed when the run starts.
|
|
28
|
+
*/
|
|
29
|
+
export type MessageQueueMarker = {
|
|
30
|
+
state: 'queued' | 'withdrawn';
|
|
31
|
+
runId: string;
|
|
32
|
+
executor: 'gateway';
|
|
33
|
+
queuedAt: string;
|
|
34
|
+
};
|
|
35
|
+
/** The answering run merged this turn into a later one; it never gets its own reply. */
|
|
36
|
+
export declare function withMergedInto(message: ChatUIMessage, mergedIntoRunId: string): ChatUIMessage;
|
|
37
|
+
/** Next `order_index` after `parentId` (midpoint before its next sibling) or at the end. */
|
|
38
|
+
export declare function resolveNextOrderIndex(tx: Tx, input: {
|
|
39
|
+
threadId: string;
|
|
40
|
+
parentId: string | null;
|
|
41
|
+
}): Promise<number>;
|
|
42
|
+
export declare function threadExists(db: Tx, threadId: string, userId: string): Promise<boolean>;
|
|
43
|
+
export type EnqueueRunInput = {
|
|
44
|
+
runId: string;
|
|
45
|
+
threadId: string;
|
|
46
|
+
userId: string;
|
|
47
|
+
requestId: string;
|
|
48
|
+
userMessage: ChatUIMessage;
|
|
49
|
+
assistantMessage: ChatUIMessage;
|
|
50
|
+
parentMessageId: string | null;
|
|
51
|
+
createThread: ThreadRunCreateThreadRequest | null;
|
|
52
|
+
requestPayload: Record<string, unknown>;
|
|
53
|
+
/** Waits behind another turn of the same thread: marks the user message as queued. */
|
|
54
|
+
queued: boolean;
|
|
55
|
+
/** FIFO order within the thread. */
|
|
56
|
+
queueIndex: number;
|
|
57
|
+
/**
|
|
58
|
+
* Parent of a queued turn: the reply still being written, or the turn queued
|
|
59
|
+
* just before it. Absent for a turn that runs now, which follows the thread's
|
|
60
|
+
* last message.
|
|
61
|
+
*/
|
|
62
|
+
queuedAfterMessageId?: string;
|
|
63
|
+
};
|
|
64
|
+
/** Persists the accepted turn atomically: thread (if new), user message, queued run. */
|
|
65
|
+
export declare function enqueueRun(db: AbstractPowerSyncDatabase, input: EnqueueRunInput): Promise<{
|
|
66
|
+
threadCreated: boolean;
|
|
67
|
+
}>;
|
|
68
|
+
/** Live progress for `run.status` recovery; never touches synced tables. */
|
|
69
|
+
export declare function updateRunSnapshot(db: Tx, input: {
|
|
70
|
+
runId: string;
|
|
71
|
+
status: ThreadRunEventStatus;
|
|
72
|
+
assistant: ChatUIMessage;
|
|
73
|
+
}): Promise<void>;
|
|
74
|
+
/** Ends the run and writes the final assistant message into the synced transcript. */
|
|
75
|
+
export declare function commitRunTerminal(db: AbstractPowerSyncDatabase, input: {
|
|
76
|
+
runId: string;
|
|
77
|
+
threadId: string;
|
|
78
|
+
userId: string;
|
|
79
|
+
userMessageId: string;
|
|
80
|
+
status: ThreadRunEventStatus;
|
|
81
|
+
assistant: ChatUIMessage;
|
|
82
|
+
errorText: string | null;
|
|
83
|
+
}): Promise<void>;
|
|
84
|
+
export declare function findRun(db: Tx, input: {
|
|
85
|
+
runId: string;
|
|
86
|
+
userId: string;
|
|
87
|
+
}): Promise<StoredRun | null>;
|
|
88
|
+
/** The queued turns start now: their messages stop showing as queued. */
|
|
89
|
+
export declare function startQueuedRuns(db: AbstractPowerSyncDatabase, userMessageIds: string[]): Promise<void>;
|
|
90
|
+
/** Ends a queued turn that was merged into a later one: no reply of its own is written. */
|
|
91
|
+
export declare function finishMergedRun(db: Tx, input: {
|
|
92
|
+
runId: string;
|
|
93
|
+
mergedIntoRunId: string;
|
|
94
|
+
assistant: ChatUIMessage;
|
|
95
|
+
}): Promise<ChatUIMessage>;
|
|
96
|
+
/**
|
|
97
|
+
* The user took a queued turn back before it ran: the run is cancelled and its
|
|
98
|
+
* message removed from the transcript (marked `withdrawn` so other devices can
|
|
99
|
+
* tell a withdrawal from a deletion).
|
|
100
|
+
*/
|
|
101
|
+
export declare function withdrawQueuedRun(db: AbstractPowerSyncDatabase, input: {
|
|
102
|
+
runId: string;
|
|
103
|
+
userMessageId: string;
|
|
104
|
+
assistant: ChatUIMessage;
|
|
105
|
+
}): Promise<ChatUIMessage>;
|
|
106
|
+
/** Runs left non-terminal by a previous process can never finish; mark them interrupted. */
|
|
107
|
+
export declare function interruptOrphanedRuns(db: Tx, userId: string): Promise<number>;
|
|
108
|
+
/**
|
|
109
|
+
* One gateway's agent session on a thread, scoped to the thread's topic when it
|
|
110
|
+
* started. The thread can switch agents, so every gateway keeps its own session
|
|
111
|
+
* and remembers the last transcript message it saw.
|
|
112
|
+
*/
|
|
113
|
+
export type GatewaySessionBinding = {
|
|
114
|
+
sessionId: string;
|
|
115
|
+
topicId: string | null;
|
|
116
|
+
/** Newest transcript message the session has seen; null when it has seen nothing of the topic yet. */
|
|
117
|
+
lastSeenMessageId: string | null;
|
|
118
|
+
/** Directory the session works in; absent for bindings written before it was recorded. */
|
|
119
|
+
cwd?: string;
|
|
120
|
+
/** Written before seen messages were tracked: the session is assumed to be up to date. */
|
|
121
|
+
legacy?: true;
|
|
122
|
+
};
|
|
123
|
+
export declare function readSessionBinding(db: Tx, threadId: string, gatewayId: string): Promise<GatewaySessionBinding | null>;
|
|
124
|
+
/** Replaces this gateway's binding and keeps every other gateway's. */
|
|
125
|
+
export declare function writeSessionBinding(db: AbstractPowerSyncDatabase, threadId: string, gatewayId: string, binding: GatewaySessionBinding): Promise<void>;
|
|
126
|
+
/** Directories this gateway's sessions worked in, most recently used first. */
|
|
127
|
+
export declare function readRecentSessionFolders(db: Tx, gatewayId: string, limit?: number): Promise<string[]>;
|
|
128
|
+
/** The instructions the user wrote for the thread's agent (`c_project.agent_md`); empty when none. */
|
|
129
|
+
export declare function readAgentInstructions(db: Tx, threadId: string): Promise<string>;
|
|
130
|
+
/**
|
|
131
|
+
* The thread's current topic: the latest "new topic" marker the app wrote
|
|
132
|
+
* (`entry_type = 'internal'`, `meta_data.kind = 'context_reset'`). A new topic
|
|
133
|
+
* starts a fresh agent session; like the app, it covers rows written after the marker.
|
|
134
|
+
*/
|
|
135
|
+
export declare function readCurrentTopic(db: Tx, threadId: string): Promise<{
|
|
136
|
+
id: string;
|
|
137
|
+
createdAt: string | null;
|
|
138
|
+
} | null>;
|
|
139
|
+
/** Marks a reply with the agent session that wrote it (`metadata.gateway`). */
|
|
140
|
+
export declare function withGatewaySession(message: ChatUIMessage, gateway: {
|
|
141
|
+
gatewayId: string;
|
|
142
|
+
sessionId: string;
|
|
143
|
+
}): ChatUIMessage;
|
|
144
|
+
/**
|
|
145
|
+
* Waits until a message exists in the local replica (deleted or not), e.g. the
|
|
146
|
+
* last message the client saw before sending a turn. False when it has not
|
|
147
|
+
* synced within the timeout or the wait was aborted.
|
|
148
|
+
*/
|
|
149
|
+
export declare function waitForMessage(db: Tx, messageId: string, options: {
|
|
150
|
+
timeoutMs: number;
|
|
151
|
+
signal?: AbortSignal;
|
|
152
|
+
}): Promise<boolean>;
|
|
153
|
+
/** A transcript message reduced to what another agent needs: who said what. */
|
|
154
|
+
export type TranscriptMessage = {
|
|
155
|
+
id: string;
|
|
156
|
+
role: 'user' | 'assistant';
|
|
157
|
+
/** The answering agent's name; null for the user or an unknown agent. */
|
|
158
|
+
speaker: string | null;
|
|
159
|
+
text: string;
|
|
160
|
+
createdAt: string | null;
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* The newest transcript messages in a window, oldest first, with how many
|
|
164
|
+
* matching messages lie before the page. Bounds are exclusive: `since` is a
|
|
165
|
+
* topic start (by `created_at`, as the app scopes topics), `afterMessageId` and
|
|
166
|
+
* `beforeMessageId` are transcript positions; an id that no longer exists is no bound.
|
|
167
|
+
*/
|
|
168
|
+
export declare function readTranscriptPage(db: Tx, input: {
|
|
169
|
+
threadId: string;
|
|
170
|
+
since?: string | null;
|
|
171
|
+
afterMessageId?: string | null;
|
|
172
|
+
beforeMessageId?: string | null;
|
|
173
|
+
/** Leaves out the turns this agent session answered itself (its replies and their prompts). */
|
|
174
|
+
excludeSessionId?: string | null;
|
|
175
|
+
limit: number;
|
|
176
|
+
}): Promise<{
|
|
177
|
+
messages: TranscriptMessage[];
|
|
178
|
+
earlier: number;
|
|
179
|
+
}>;
|
|
180
|
+
/** File parts resolve only to resources this user owns with an https storage URL. */
|
|
181
|
+
export declare function resolveTrustedResource(db: Tx, input: {
|
|
182
|
+
resourceId: string;
|
|
183
|
+
userId: string;
|
|
184
|
+
}): Promise<{
|
|
185
|
+
url: string;
|
|
186
|
+
name: string;
|
|
187
|
+
mediaType: string | null;
|
|
188
|
+
} | null>;
|
|
189
|
+
/** Registers the gateway as a `c_robot` the app can list and bind agents to (no secrets). */
|
|
190
|
+
export type RobotRegistration = {
|
|
191
|
+
id: string;
|
|
192
|
+
userId: string;
|
|
193
|
+
name: string;
|
|
194
|
+
deviceId: string;
|
|
195
|
+
agentType: string;
|
|
196
|
+
cwd: string | null;
|
|
197
|
+
/** This machine's hostname, shown with the robot. */
|
|
198
|
+
deviceName: string;
|
|
199
|
+
/** Marks the robot as hosted by this installation, for reconciliation. */
|
|
200
|
+
machineId: string;
|
|
201
|
+
agentId?: string;
|
|
202
|
+
/** Owner-facing settings, synced so the app can show them while the computer is offline. */
|
|
203
|
+
settings?: RobotSettings;
|
|
204
|
+
};
|
|
205
|
+
export type RobotSettings = {
|
|
206
|
+
approval: 'ask' | 'auto_read';
|
|
207
|
+
paused: boolean;
|
|
208
|
+
dinoxTools: boolean;
|
|
209
|
+
};
|
|
210
|
+
export declare function upsertRobot(db: Tx, input: RobotRegistration): Promise<void>;
|
|
211
|
+
/**
|
|
212
|
+
* Soft-deletes this machine's gateway robots that are no longer configured
|
|
213
|
+
* (removed, or replaced before ids were deterministic), so the app never
|
|
214
|
+
* offers an agent that nothing hosts. Robots from other machines are untouched.
|
|
215
|
+
*/
|
|
216
|
+
export declare function retireRemovedRobots(db: Tx, input: {
|
|
217
|
+
userId: string;
|
|
218
|
+
machineId: string;
|
|
219
|
+
keepIds: string[];
|
|
220
|
+
}): Promise<string[]>;
|
|
221
|
+
export declare function softDeleteRobot(db: Tx, robotId: string): Promise<void>;
|
|
222
|
+
export {};
|