@ekkos/cli 1.3.1 → 1.3.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/dist/commands/dashboard.js +147 -57
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +54 -16
- package/dist/commands/run.js +163 -44
- package/dist/commands/status.d.ts +4 -1
- package/dist/commands/status.js +165 -27
- package/dist/commands/synk.d.ts +7 -0
- package/dist/commands/synk.js +339 -0
- package/dist/deploy/settings.d.ts +6 -5
- package/dist/deploy/settings.js +27 -17
- package/dist/index.js +12 -82
- package/dist/lib/usage-parser.d.ts +1 -1
- package/dist/lib/usage-parser.js +5 -3
- package/dist/local/index.d.ts +14 -0
- package/dist/local/index.js +28 -0
- package/dist/local/local-embeddings.d.ts +49 -0
- package/dist/local/local-embeddings.js +232 -0
- package/dist/local/offline-fallback.d.ts +44 -0
- package/dist/local/offline-fallback.js +159 -0
- package/dist/local/sqlite-store.d.ts +126 -0
- package/dist/local/sqlite-store.js +393 -0
- package/dist/local/sync-engine.d.ts +42 -0
- package/dist/local/sync-engine.js +223 -0
- package/dist/synk/api.d.ts +22 -0
- package/dist/synk/api.js +133 -0
- package/dist/synk/auth.d.ts +7 -0
- package/dist/synk/auth.js +30 -0
- package/dist/synk/config.d.ts +18 -0
- package/dist/synk/config.js +37 -0
- package/dist/synk/daemon/control-client.d.ts +11 -0
- package/dist/synk/daemon/control-client.js +101 -0
- package/dist/synk/daemon/control-server.d.ts +24 -0
- package/dist/synk/daemon/control-server.js +91 -0
- package/dist/synk/daemon/run.d.ts +14 -0
- package/dist/synk/daemon/run.js +338 -0
- package/dist/synk/encryption.d.ts +17 -0
- package/dist/synk/encryption.js +133 -0
- package/dist/synk/index.d.ts +13 -0
- package/dist/synk/index.js +36 -0
- package/dist/synk/machine-client.d.ts +42 -0
- package/dist/synk/machine-client.js +218 -0
- package/dist/synk/persistence.d.ts +51 -0
- package/dist/synk/persistence.js +211 -0
- package/dist/synk/qr.d.ts +5 -0
- package/dist/synk/qr.js +33 -0
- package/dist/synk/session-bridge.d.ts +58 -0
- package/dist/synk/session-bridge.js +171 -0
- package/dist/synk/session-client.d.ts +46 -0
- package/dist/synk/session-client.js +240 -0
- package/dist/synk/types.d.ts +574 -0
- package/dist/synk/types.js +74 -0
- package/dist/utils/platform.d.ts +5 -1
- package/dist/utils/platform.js +24 -4
- package/dist/utils/proxy-url.d.ts +10 -0
- package/dist/utils/proxy-url.js +19 -0
- package/dist/utils/state.d.ts +1 -1
- package/dist/utils/state.js +11 -3
- package/package.json +13 -4
- package/templates/claude-plugins-admin/AGENT_TEAM_PROPOSALS.md +0 -819
- package/templates/claude-plugins-admin/README.md +0 -446
- package/templates/claude-plugins-admin/autonomous-admin-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/autonomous-admin-agent/commands/agent.md +0 -595
- package/templates/claude-plugins-admin/backend-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/backend-agent/commands/backend.md +0 -798
- package/templates/claude-plugins-admin/deploy-guardian/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/deploy-guardian/commands/deploy.md +0 -554
- package/templates/claude-plugins-admin/frontend-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/frontend-agent/commands/frontend.md +0 -881
- package/templates/claude-plugins-admin/mcp-server-manager/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/mcp-server-manager/commands/mcp.md +0 -85
- package/templates/claude-plugins-admin/memory-system-monitor/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/memory-system-monitor/commands/memory-health.md +0 -569
- package/templates/claude-plugins-admin/qa-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/qa-agent/commands/qa.md +0 -863
- package/templates/claude-plugins-admin/tech-lead-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/tech-lead-agent/commands/lead.md +0 -732
- package/templates/hooks-node/lib/state.js +0 -187
- package/templates/hooks-node/stop.js +0 -416
- package/templates/hooks-node/user-prompt-submit.js +0 -337
- package/templates/rules/00-hooks-contract.mdc +0 -89
- package/templates/rules/30-ekkos-core.mdc +0 -188
- package/templates/rules/31-ekkos-messages.mdc +0 -78
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SynkSessionBridge — links `ekkos run` to synk real-time sync
|
|
4
|
+
*
|
|
5
|
+
* Lifecycle:
|
|
6
|
+
* create() → reads creds, returns null if synk disabled
|
|
7
|
+
* onSessionEstablished → creates synk session + WS client
|
|
8
|
+
* onAgentOutput → batched forwarding of PTY output
|
|
9
|
+
* onIdleChange → keepalive / thinking state
|
|
10
|
+
* onTurnEnd → flush output buffer
|
|
11
|
+
* shutdown → graceful close
|
|
12
|
+
*/
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.SynkSessionBridge = void 0;
|
|
18
|
+
const node_events_1 = require("node:events");
|
|
19
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
20
|
+
const persistence_1 = require("./persistence");
|
|
21
|
+
const api_1 = require("./api");
|
|
22
|
+
const session_client_1 = require("./session-client");
|
|
23
|
+
const config_1 = require("./config");
|
|
24
|
+
const control_client_1 = require("./daemon/control-client");
|
|
25
|
+
class SynkSessionBridge extends node_events_1.EventEmitter {
|
|
26
|
+
constructor(credential, apiClient, opts) {
|
|
27
|
+
super();
|
|
28
|
+
this.sessionClient = null;
|
|
29
|
+
this.synkSession = null;
|
|
30
|
+
this.outputBuffer = '';
|
|
31
|
+
this.flushTimer = null;
|
|
32
|
+
this.established = false;
|
|
33
|
+
this.closed = false;
|
|
34
|
+
this.credential = credential;
|
|
35
|
+
this.apiClient = apiClient;
|
|
36
|
+
this.cwd = opts.cwd;
|
|
37
|
+
this.verbose = opts.verbose ?? false;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Factory — returns null when synk is not configured (zero-cost path).
|
|
41
|
+
*/
|
|
42
|
+
static async create(opts) {
|
|
43
|
+
try {
|
|
44
|
+
const credential = await (0, persistence_1.readCredentials)();
|
|
45
|
+
if (!credential)
|
|
46
|
+
return null;
|
|
47
|
+
const apiClient = await api_1.ApiClient.create(credential);
|
|
48
|
+
return new SynkSessionBridge(credential, apiClient, opts);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Called once after Claude Code's session name is detected from the status line.
|
|
56
|
+
* Creates the synk session via REST, then opens a SessionClient WebSocket.
|
|
57
|
+
*/
|
|
58
|
+
async onSessionEstablished(sessionName, sessionId, extra) {
|
|
59
|
+
if (this.established || this.closed)
|
|
60
|
+
return;
|
|
61
|
+
this.established = true;
|
|
62
|
+
try {
|
|
63
|
+
const settings = await (0, persistence_1.readSettings)();
|
|
64
|
+
const metadata = {
|
|
65
|
+
path: this.cwd,
|
|
66
|
+
host: node_os_1.default.hostname(),
|
|
67
|
+
name: sessionName,
|
|
68
|
+
os: node_os_1.default.platform(),
|
|
69
|
+
homeDir: node_os_1.default.homedir(),
|
|
70
|
+
synkHomeDir: config_1.synkConfig.synkHomeDir,
|
|
71
|
+
machineId: settings.machineId,
|
|
72
|
+
claudeSessionId: sessionId,
|
|
73
|
+
hostPid: extra?.hostPid ?? process.pid,
|
|
74
|
+
startedBy: 'terminal',
|
|
75
|
+
lifecycleState: 'running',
|
|
76
|
+
lifecycleStateSince: Date.now(),
|
|
77
|
+
};
|
|
78
|
+
const session = await this.apiClient.getOrCreateSession({
|
|
79
|
+
tag: sessionId,
|
|
80
|
+
metadata,
|
|
81
|
+
state: null,
|
|
82
|
+
});
|
|
83
|
+
if (!session) {
|
|
84
|
+
this.log('synk session creation failed (server unreachable)');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.synkSession = session;
|
|
88
|
+
this.sessionClient = new session_client_1.SessionClient(this.credential.token, session);
|
|
89
|
+
// Pipe incoming user messages to run.ts via EventEmitter
|
|
90
|
+
this.sessionClient.onUserMessage((msg) => {
|
|
91
|
+
this.emit('remote-message', { text: msg.content.text, meta: msg.meta });
|
|
92
|
+
});
|
|
93
|
+
this.sessionClient.on('connected', () => this.log('synk WS connected'));
|
|
94
|
+
this.sessionClient.on('disconnected', (reason) => this.log(`synk WS disconnected: ${reason}`));
|
|
95
|
+
this.sessionClient.on('error', (err) => this.log(`synk WS error: ${err.message}`));
|
|
96
|
+
// Notify daemon (best-effort, fire-and-forget)
|
|
97
|
+
(0, control_client_1.notifyDaemonSessionStarted)(session.id, metadata).catch(() => { });
|
|
98
|
+
this.log(`synk session established: ${session.id}`);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
this.log(`synk session establish error: ${err instanceof Error ? err.message : err}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Called on every PTY data chunk. Buffers and flushes every 200ms.
|
|
106
|
+
*/
|
|
107
|
+
onAgentOutput(data) {
|
|
108
|
+
if (!this.sessionClient || this.closed)
|
|
109
|
+
return;
|
|
110
|
+
this.outputBuffer += data;
|
|
111
|
+
if (!this.flushTimer) {
|
|
112
|
+
this.flushTimer = setTimeout(() => {
|
|
113
|
+
this.flushTimer = null;
|
|
114
|
+
this.flushOutputBuffer();
|
|
115
|
+
}, 200);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Called when Claude transitions between idle <-> active.
|
|
120
|
+
*/
|
|
121
|
+
onIdleChange(idle) {
|
|
122
|
+
if (!this.sessionClient || this.closed)
|
|
123
|
+
return;
|
|
124
|
+
this.sessionClient.keepAlive(!idle, 'local');
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Called at turn boundaries — flush any pending output immediately.
|
|
128
|
+
*/
|
|
129
|
+
onTurnEnd() {
|
|
130
|
+
this.flushOutputBuffer();
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Graceful shutdown — send death event, flush, close socket.
|
|
134
|
+
*/
|
|
135
|
+
async shutdown() {
|
|
136
|
+
if (this.closed)
|
|
137
|
+
return;
|
|
138
|
+
this.closed = true;
|
|
139
|
+
if (this.flushTimer) {
|
|
140
|
+
clearTimeout(this.flushTimer);
|
|
141
|
+
this.flushTimer = null;
|
|
142
|
+
}
|
|
143
|
+
this.flushOutputBuffer();
|
|
144
|
+
if (this.sessionClient) {
|
|
145
|
+
try {
|
|
146
|
+
this.sessionClient.sendSessionDeath();
|
|
147
|
+
await Promise.race([
|
|
148
|
+
this.sessionClient.flush(),
|
|
149
|
+
new Promise(resolve => setTimeout(resolve, 3000)),
|
|
150
|
+
]);
|
|
151
|
+
}
|
|
152
|
+
catch { }
|
|
153
|
+
await this.sessionClient.close().catch(() => { });
|
|
154
|
+
this.sessionClient = null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// ── internals ──
|
|
158
|
+
flushOutputBuffer() {
|
|
159
|
+
if (!this.sessionClient || this.outputBuffer.length === 0)
|
|
160
|
+
return;
|
|
161
|
+
const chunk = this.outputBuffer;
|
|
162
|
+
this.outputBuffer = '';
|
|
163
|
+
this.sessionClient.sendAgentMessage(chunk);
|
|
164
|
+
}
|
|
165
|
+
log(msg) {
|
|
166
|
+
if (this.verbose) {
|
|
167
|
+
process.stderr.write(`[synk-bridge] ${msg}\n`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
exports.SynkSessionBridge = SynkSessionBridge;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real-time session client for ekkOS_synk
|
|
3
|
+
* Manages Socket.IO connection and message sync with synk-server
|
|
4
|
+
*/
|
|
5
|
+
import { EventEmitter } from 'node:events';
|
|
6
|
+
import type { Session, Metadata, UserMessage } from './types';
|
|
7
|
+
export declare class SessionClient extends EventEmitter {
|
|
8
|
+
private readonly token;
|
|
9
|
+
readonly sessionId: string;
|
|
10
|
+
private metadata;
|
|
11
|
+
private metadataVersion;
|
|
12
|
+
private agentState;
|
|
13
|
+
private agentStateVersion;
|
|
14
|
+
private socket;
|
|
15
|
+
private pendingMessages;
|
|
16
|
+
private pendingMessageCallback;
|
|
17
|
+
private encryptionKey;
|
|
18
|
+
private encryptionVariant;
|
|
19
|
+
private lastSeq;
|
|
20
|
+
private pendingOutbox;
|
|
21
|
+
private flushTimer;
|
|
22
|
+
constructor(token: string, session: Session);
|
|
23
|
+
onUserMessage(callback: (data: UserMessage) => void): void;
|
|
24
|
+
private routeIncomingMessage;
|
|
25
|
+
private fetchMessages;
|
|
26
|
+
private scheduleFlush;
|
|
27
|
+
private flushOutbox;
|
|
28
|
+
/** Send encrypted message to session */
|
|
29
|
+
sendMessage(content: unknown): void;
|
|
30
|
+
/** Send agent output message */
|
|
31
|
+
sendAgentMessage(data: any): void;
|
|
32
|
+
/** Send session event */
|
|
33
|
+
sendSessionEvent(event: {
|
|
34
|
+
type: string;
|
|
35
|
+
[key: string]: any;
|
|
36
|
+
}): void;
|
|
37
|
+
/** Send keepalive ping */
|
|
38
|
+
keepAlive(thinking: boolean, mode: 'local' | 'remote'): void;
|
|
39
|
+
/** Notify server of session end */
|
|
40
|
+
sendSessionDeath(): void;
|
|
41
|
+
/** Update session metadata on server */
|
|
42
|
+
updateMetadata(handler: (metadata: Metadata) => Metadata): void;
|
|
43
|
+
/** Wait for pending messages to flush */
|
|
44
|
+
flush(): Promise<void>;
|
|
45
|
+
close(): Promise<void>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Real-time session client for ekkOS_synk
|
|
4
|
+
* Manages Socket.IO connection and message sync with synk-server
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.SessionClient = void 0;
|
|
11
|
+
const node_events_1 = require("node:events");
|
|
12
|
+
const socket_io_client_1 = require("socket.io-client");
|
|
13
|
+
const node_crypto_1 = require("node:crypto");
|
|
14
|
+
const axios_1 = __importDefault(require("axios"));
|
|
15
|
+
const types_1 = require("./types");
|
|
16
|
+
const encryption_1 = require("./encryption");
|
|
17
|
+
const config_1 = require("./config");
|
|
18
|
+
class SessionClient extends node_events_1.EventEmitter {
|
|
19
|
+
constructor(token, session) {
|
|
20
|
+
super();
|
|
21
|
+
this.pendingMessages = [];
|
|
22
|
+
this.pendingMessageCallback = null;
|
|
23
|
+
this.lastSeq = 0;
|
|
24
|
+
this.pendingOutbox = [];
|
|
25
|
+
this.flushTimer = null;
|
|
26
|
+
this.token = token;
|
|
27
|
+
this.sessionId = session.id;
|
|
28
|
+
this.metadata = session.metadata;
|
|
29
|
+
this.metadataVersion = session.metadataVersion;
|
|
30
|
+
this.agentState = session.agentState;
|
|
31
|
+
this.agentStateVersion = session.agentStateVersion;
|
|
32
|
+
this.encryptionKey = session.encryptionKey;
|
|
33
|
+
this.encryptionVariant = session.encryptionVariant;
|
|
34
|
+
this.socket = (0, socket_io_client_1.io)(config_1.synkConfig.serverUrl, {
|
|
35
|
+
auth: {
|
|
36
|
+
token: this.token,
|
|
37
|
+
clientType: 'session-scoped',
|
|
38
|
+
sessionId: this.sessionId,
|
|
39
|
+
},
|
|
40
|
+
path: '/v1/updates',
|
|
41
|
+
reconnection: true,
|
|
42
|
+
reconnectionAttempts: Infinity,
|
|
43
|
+
reconnectionDelay: 1000,
|
|
44
|
+
reconnectionDelayMax: 5000,
|
|
45
|
+
transports: ['websocket'],
|
|
46
|
+
withCredentials: true,
|
|
47
|
+
autoConnect: false,
|
|
48
|
+
});
|
|
49
|
+
this.socket.on('connect', () => {
|
|
50
|
+
this.emit('connected');
|
|
51
|
+
this.fetchMessages().catch(() => { });
|
|
52
|
+
});
|
|
53
|
+
this.socket.on('disconnect', (reason) => {
|
|
54
|
+
this.emit('disconnected', reason);
|
|
55
|
+
});
|
|
56
|
+
this.socket.on('connect_error', (error) => {
|
|
57
|
+
this.emit('error', error);
|
|
58
|
+
});
|
|
59
|
+
this.socket.on('update', (data) => {
|
|
60
|
+
try {
|
|
61
|
+
if (!data.body)
|
|
62
|
+
return;
|
|
63
|
+
if (data.body.t === 'new-message') {
|
|
64
|
+
const messageSeq = data.body.message?.seq;
|
|
65
|
+
if (this.lastSeq === 0 || typeof messageSeq !== 'number' || messageSeq !== this.lastSeq + 1 || data.body.message.content.t !== 'encrypted') {
|
|
66
|
+
this.fetchMessages().catch(() => { });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const body = (0, encryption_1.decrypt)(this.encryptionKey, this.encryptionVariant, (0, encryption_1.decodeBase64)(data.body.message.content.c));
|
|
70
|
+
this.routeIncomingMessage(body);
|
|
71
|
+
this.lastSeq = messageSeq;
|
|
72
|
+
}
|
|
73
|
+
else if (data.body.t === 'update-session') {
|
|
74
|
+
if (data.body.metadata && data.body.metadata.version > this.metadataVersion) {
|
|
75
|
+
this.metadata = (0, encryption_1.decrypt)(this.encryptionKey, this.encryptionVariant, (0, encryption_1.decodeBase64)(data.body.metadata.value));
|
|
76
|
+
this.metadataVersion = data.body.metadata.version;
|
|
77
|
+
}
|
|
78
|
+
if (data.body.agentState && data.body.agentState.version > this.agentStateVersion) {
|
|
79
|
+
this.agentState = data.body.agentState.value
|
|
80
|
+
? (0, encryption_1.decrypt)(this.encryptionKey, this.encryptionVariant, (0, encryption_1.decodeBase64)(data.body.agentState.value))
|
|
81
|
+
: null;
|
|
82
|
+
this.agentStateVersion = data.body.agentState.version;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
this.emit('message', data.body);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
this.emit('error', error);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
this.socket.on('rpc-request', async (data, callback) => {
|
|
94
|
+
this.emit('rpc-request', data, callback);
|
|
95
|
+
});
|
|
96
|
+
this.socket.connect();
|
|
97
|
+
}
|
|
98
|
+
onUserMessage(callback) {
|
|
99
|
+
this.pendingMessageCallback = callback;
|
|
100
|
+
while (this.pendingMessages.length > 0) {
|
|
101
|
+
callback(this.pendingMessages.shift());
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
routeIncomingMessage(message) {
|
|
105
|
+
const userResult = types_1.UserMessageSchema.safeParse(message);
|
|
106
|
+
if (userResult.success) {
|
|
107
|
+
if (this.pendingMessageCallback) {
|
|
108
|
+
this.pendingMessageCallback(userResult.data);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
this.pendingMessages.push(userResult.data);
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
this.emit('message', message);
|
|
116
|
+
}
|
|
117
|
+
async fetchMessages() {
|
|
118
|
+
let afterSeq = this.lastSeq;
|
|
119
|
+
while (true) {
|
|
120
|
+
const response = await axios_1.default.get(`${config_1.synkConfig.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`, {
|
|
121
|
+
params: { after_seq: afterSeq, limit: 100 },
|
|
122
|
+
headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' },
|
|
123
|
+
timeout: 60000,
|
|
124
|
+
});
|
|
125
|
+
const messages = Array.isArray(response.data.messages) ? response.data.messages : [];
|
|
126
|
+
let maxSeq = afterSeq;
|
|
127
|
+
for (const message of messages) {
|
|
128
|
+
if (message.seq > maxSeq)
|
|
129
|
+
maxSeq = message.seq;
|
|
130
|
+
if (message.content?.t !== 'encrypted')
|
|
131
|
+
continue;
|
|
132
|
+
try {
|
|
133
|
+
const body = (0, encryption_1.decrypt)(this.encryptionKey, this.encryptionVariant, (0, encryption_1.decodeBase64)(message.content.c));
|
|
134
|
+
this.routeIncomingMessage(body);
|
|
135
|
+
}
|
|
136
|
+
catch { }
|
|
137
|
+
}
|
|
138
|
+
this.lastSeq = Math.max(this.lastSeq, maxSeq);
|
|
139
|
+
if (!response.data.hasMore || maxSeq === afterSeq)
|
|
140
|
+
break;
|
|
141
|
+
afterSeq = maxSeq;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
scheduleFlush() {
|
|
145
|
+
if (this.flushTimer)
|
|
146
|
+
return;
|
|
147
|
+
this.flushTimer = setTimeout(async () => {
|
|
148
|
+
this.flushTimer = null;
|
|
149
|
+
await this.flushOutbox().catch(() => { });
|
|
150
|
+
}, 50);
|
|
151
|
+
}
|
|
152
|
+
async flushOutbox() {
|
|
153
|
+
if (this.pendingOutbox.length === 0)
|
|
154
|
+
return;
|
|
155
|
+
const batch = this.pendingOutbox.slice();
|
|
156
|
+
try {
|
|
157
|
+
const response = await axios_1.default.post(`${config_1.synkConfig.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`, { messages: batch }, {
|
|
158
|
+
headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' },
|
|
159
|
+
timeout: 60000,
|
|
160
|
+
});
|
|
161
|
+
this.pendingOutbox.splice(0, batch.length);
|
|
162
|
+
const messages = Array.isArray(response.data.messages) ? response.data.messages : [];
|
|
163
|
+
const maxSeq = messages.reduce((acc, m) => (m.seq > acc ? m.seq : acc), this.lastSeq);
|
|
164
|
+
this.lastSeq = maxSeq;
|
|
165
|
+
}
|
|
166
|
+
catch { }
|
|
167
|
+
}
|
|
168
|
+
/** Send encrypted message to session */
|
|
169
|
+
sendMessage(content) {
|
|
170
|
+
const encrypted = (0, encryption_1.encodeBase64)((0, encryption_1.encrypt)(this.encryptionKey, this.encryptionVariant, content));
|
|
171
|
+
this.pendingOutbox.push({ content: encrypted, localId: (0, node_crypto_1.randomUUID)() });
|
|
172
|
+
this.scheduleFlush();
|
|
173
|
+
}
|
|
174
|
+
/** Send agent output message */
|
|
175
|
+
sendAgentMessage(data) {
|
|
176
|
+
this.sendMessage({
|
|
177
|
+
role: 'agent',
|
|
178
|
+
content: { type: 'output', data },
|
|
179
|
+
meta: { sentFrom: 'cli' },
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/** Send session event */
|
|
183
|
+
sendSessionEvent(event) {
|
|
184
|
+
this.sendMessage({
|
|
185
|
+
role: 'agent',
|
|
186
|
+
content: { id: (0, node_crypto_1.randomUUID)(), type: 'event', data: event },
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/** Send keepalive ping */
|
|
190
|
+
keepAlive(thinking, mode) {
|
|
191
|
+
this.socket.volatile.emit('session-alive', {
|
|
192
|
+
sid: this.sessionId,
|
|
193
|
+
time: Date.now(),
|
|
194
|
+
thinking,
|
|
195
|
+
mode,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/** Notify server of session end */
|
|
199
|
+
sendSessionDeath() {
|
|
200
|
+
this.socket.emit('session-end', { sid: this.sessionId, time: Date.now() });
|
|
201
|
+
}
|
|
202
|
+
/** Update session metadata on server */
|
|
203
|
+
updateMetadata(handler) {
|
|
204
|
+
if (!this.metadata)
|
|
205
|
+
return;
|
|
206
|
+
const updated = handler(this.metadata);
|
|
207
|
+
this.socket.emit('update-metadata', {
|
|
208
|
+
sid: this.sessionId,
|
|
209
|
+
expectedVersion: this.metadataVersion,
|
|
210
|
+
metadata: (0, encryption_1.encodeBase64)((0, encryption_1.encrypt)(this.encryptionKey, this.encryptionVariant, updated)),
|
|
211
|
+
}, (answer) => {
|
|
212
|
+
if (answer.result === 'success') {
|
|
213
|
+
this.metadata = (0, encryption_1.decrypt)(this.encryptionKey, this.encryptionVariant, (0, encryption_1.decodeBase64)(answer.metadata));
|
|
214
|
+
this.metadataVersion = answer.version;
|
|
215
|
+
}
|
|
216
|
+
else if (answer.result === 'version-mismatch' && answer.version > this.metadataVersion) {
|
|
217
|
+
this.metadataVersion = answer.version;
|
|
218
|
+
this.metadata = (0, encryption_1.decrypt)(this.encryptionKey, this.encryptionVariant, (0, encryption_1.decodeBase64)(answer.metadata));
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/** Wait for pending messages to flush */
|
|
223
|
+
async flush() {
|
|
224
|
+
await this.flushOutbox();
|
|
225
|
+
if (!this.socket.connected)
|
|
226
|
+
return;
|
|
227
|
+
return new Promise((resolve) => {
|
|
228
|
+
this.socket.emit('ping', () => resolve());
|
|
229
|
+
setTimeout(() => resolve(), 10000);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
async close() {
|
|
233
|
+
if (this.flushTimer) {
|
|
234
|
+
clearTimeout(this.flushTimer);
|
|
235
|
+
this.flushTimer = null;
|
|
236
|
+
}
|
|
237
|
+
this.socket.close();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
exports.SessionClient = SessionClient;
|