@ekkos/cli 0.2.9 → 0.2.11
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/agent/daemon.d.ts +86 -0
- package/dist/agent/daemon.js +297 -0
- package/dist/agent/pty-runner.d.ts +51 -0
- package/dist/agent/pty-runner.js +184 -0
- package/dist/cache/LocalSessionStore.d.ts +34 -21
- package/dist/cache/LocalSessionStore.js +169 -53
- package/dist/cache/capture.d.ts +19 -11
- package/dist/cache/capture.js +243 -76
- package/dist/cache/types.d.ts +14 -1
- package/dist/commands/agent.d.ts +44 -0
- package/dist/commands/agent.js +300 -0
- package/dist/commands/doctor.d.ts +10 -0
- package/dist/commands/doctor.js +175 -87
- package/dist/commands/hooks.d.ts +109 -0
- package/dist/commands/hooks.js +668 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +357 -85
- package/dist/commands/setup-remote.d.ts +20 -0
- package/dist/commands/setup-remote.js +467 -0
- package/dist/index.js +116 -1
- package/dist/restore/RestoreOrchestrator.d.ts +17 -3
- package/dist/restore/RestoreOrchestrator.js +64 -22
- package/dist/utils/paths.d.ts +125 -0
- package/dist/utils/paths.js +283 -0
- package/dist/utils/state.d.ts +2 -0
- package/package.json +1 -1
- package/templates/ekkos-manifest.json +223 -0
- package/templates/helpers/json-parse.cjs +101 -0
- package/templates/hooks/assistant-response.ps1 +256 -0
- package/templates/hooks/assistant-response.sh +124 -64
- package/templates/hooks/session-start.ps1 +107 -2
- package/templates/hooks/session-start.sh +201 -166
- package/templates/hooks/stop.ps1 +124 -3
- package/templates/hooks/stop.sh +470 -843
- package/templates/hooks/user-prompt-submit.ps1 +107 -22
- package/templates/hooks/user-prompt-submit.sh +403 -393
- package/templates/project-stubs/session-start.ps1 +63 -0
- package/templates/project-stubs/session-start.sh +55 -0
- package/templates/project-stubs/stop.ps1 +63 -0
- package/templates/project-stubs/stop.sh +55 -0
- package/templates/project-stubs/user-prompt-submit.ps1 +63 -0
- package/templates/project-stubs/user-prompt-submit.sh +55 -0
- package/templates/shared/hooks-enabled.json +22 -0
- package/templates/shared/session-words.json +45 -0
package/dist/cache/capture.js
CHANGED
|
@@ -1,25 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
/**
|
|
4
|
-
* ekkOS Fast Capture & Restore - CLI for local cache operations
|
|
4
|
+
* ekkOS Fast Capture & Restore - CLI for local cache operations (Instance-Aware)
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Per ekkOS Onboarding Spec v1.2 FINAL + ADDENDUM:
|
|
7
|
+
* - All Tier 0 cache paths MUST be: ~/.ekkos/cache/sessions/{instanceId}/{sessionId}.jsonl
|
|
8
|
+
* - All persisted records MUST include: instanceId, sessionId, sessionName
|
|
9
|
+
*
|
|
10
|
+
* Capture Commands (NEW format with instanceId):
|
|
11
|
+
* capture user <instance_id> <session_id> <session_name> <turn_id> <query> [project_path]
|
|
12
|
+
* capture response <instance_id> <session_id> <turn_id> <response> [tools] [files]
|
|
13
|
+
*
|
|
14
|
+
* Capture Commands (LEGACY format - backward compatible, uses default instanceId):
|
|
7
15
|
* capture user <session_id> <session_name> <turn_id> <query> [project_path]
|
|
8
16
|
* capture response <session_id> <turn_id> <response> [tools] [files]
|
|
9
17
|
*
|
|
10
18
|
* Restore Commands:
|
|
11
|
-
* capture restore [session_name] [--json|--markdown|--n=N]
|
|
19
|
+
* capture restore [session_name] [--json|--markdown|--n=N] [--instance=ID]
|
|
12
20
|
*
|
|
13
|
-
* ACK & Sync Commands
|
|
14
|
-
* capture ack <session_id> <turn_id>
|
|
15
|
-
* capture sync [session_id]
|
|
16
|
-
* capture prune <session_id>
|
|
17
|
-
* capture cleanup
|
|
21
|
+
* ACK & Sync Commands:
|
|
22
|
+
* capture ack <session_id> <turn_id> [--instance=ID]
|
|
23
|
+
* capture sync [session_id] [--instance=ID]
|
|
24
|
+
* capture prune <session_id> [--instance=ID]
|
|
25
|
+
* capture cleanup [--instance=ID]
|
|
18
26
|
*
|
|
19
27
|
* Query Commands:
|
|
20
|
-
* capture list
|
|
21
|
-
* capture get <session_id> [n]
|
|
22
|
-
* capture stats
|
|
28
|
+
* capture list [--instance=ID|--all]
|
|
29
|
+
* capture get <session_id> [n] [--instance=ID]
|
|
30
|
+
* capture stats [--instance=ID]
|
|
23
31
|
*
|
|
24
32
|
* This is a lightweight script designed for hook integration.
|
|
25
33
|
* Writes to local JSONL cache with minimal latency.
|
|
@@ -63,7 +71,7 @@ const path = __importStar(require("path"));
|
|
|
63
71
|
const os = __importStar(require("os"));
|
|
64
72
|
const LocalSessionStore_js_1 = require("./LocalSessionStore.js");
|
|
65
73
|
const RestoreOrchestrator_js_1 = require("../restore/RestoreOrchestrator.js");
|
|
66
|
-
const
|
|
74
|
+
const paths_js_1 = require("../utils/paths.js");
|
|
67
75
|
// API configuration
|
|
68
76
|
const MEMORY_API_URL = process.env.EKKOS_API_URL || 'https://api.ekkos.dev';
|
|
69
77
|
const CONFIG_PATH = path.join(os.homedir(), '.ekkos', 'config.json');
|
|
@@ -82,75 +90,236 @@ function loadAuthToken() {
|
|
|
82
90
|
}
|
|
83
91
|
return '';
|
|
84
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Parse --instance=ID flag from args
|
|
95
|
+
*/
|
|
96
|
+
function parseInstanceFlag(args) {
|
|
97
|
+
let instanceId = process.env.EKKOS_INSTANCE_ID || paths_js_1.DEFAULT_INSTANCE_ID;
|
|
98
|
+
const cleanArgs = [];
|
|
99
|
+
for (const arg of args) {
|
|
100
|
+
if (arg.startsWith('--instance=')) {
|
|
101
|
+
instanceId = arg.slice('--instance='.length);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
cleanArgs.push(arg);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { instanceId: (0, paths_js_1.normalizeInstanceId)(instanceId), cleanArgs };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Detect if args match the NEW format (with instanceId as first positional arg)
|
|
111
|
+
*
|
|
112
|
+
* NEW user format: <instance_id> <session_id> <session_name> <turn_id> <query> [project_path]
|
|
113
|
+
* OLD user format: <session_id> <session_name> <turn_id> <query> [project_path]
|
|
114
|
+
*
|
|
115
|
+
* Detection heuristic:
|
|
116
|
+
* - If arg[3] is a number (turn_id), it's NEW format
|
|
117
|
+
* - If arg[2] is a number (turn_id), it's OLD format
|
|
118
|
+
*/
|
|
119
|
+
function isNewUserFormat(args) {
|
|
120
|
+
// NEW format has at least 5 args: instanceId, sessionId, sessionName, turnId, query
|
|
121
|
+
if (args.length < 5)
|
|
122
|
+
return false;
|
|
123
|
+
// In NEW format, arg[3] should be turn_id (a number)
|
|
124
|
+
// In OLD format, arg[2] should be turn_id (a number)
|
|
125
|
+
const arg3 = args[3];
|
|
126
|
+
const arg2 = args[2];
|
|
127
|
+
// If arg[3] is numeric but arg[2] is not, it's NEW format
|
|
128
|
+
if (/^\d+$/.test(arg3) && !/^\d+$/.test(arg2)) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
// If arg[2] is numeric, it's OLD format
|
|
132
|
+
if (/^\d+$/.test(arg2)) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
// Default to NEW format if ambiguous and we have enough args
|
|
136
|
+
return args.length >= 5;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Similar detection for response command
|
|
140
|
+
*
|
|
141
|
+
* NEW response format: <instance_id> <session_id> <turn_id> <response> [tools] [files]
|
|
142
|
+
* OLD response format: <session_id> <turn_id> <response> [tools] [files]
|
|
143
|
+
*/
|
|
144
|
+
function isNewResponseFormat(args) {
|
|
145
|
+
// NEW format has at least 4 args: instanceId, sessionId, turnId, response
|
|
146
|
+
if (args.length < 4)
|
|
147
|
+
return false;
|
|
148
|
+
// In NEW format, arg[2] should be turn_id (a number)
|
|
149
|
+
// In OLD format, arg[1] should be turn_id (a number)
|
|
150
|
+
const arg2 = args[2];
|
|
151
|
+
const arg1 = args[1];
|
|
152
|
+
// If arg[2] is numeric but arg[1] is not, it's NEW format
|
|
153
|
+
if (/^\d+$/.test(arg2) && !/^\d+$/.test(arg1)) {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
// If arg[1] is numeric, it's OLD format
|
|
157
|
+
if (/^\d+$/.test(arg1)) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
// Default to NEW format if ambiguous
|
|
161
|
+
return args.length >= 4;
|
|
162
|
+
}
|
|
85
163
|
async function main() {
|
|
86
164
|
const args = process.argv.slice(2);
|
|
87
165
|
const command = args[0];
|
|
88
166
|
if (!command) {
|
|
89
|
-
console.error('
|
|
167
|
+
console.error('ekkOS Capture CLI - Instance-Aware Local Cache');
|
|
168
|
+
console.error('');
|
|
169
|
+
console.error('Usage: ekkos-capture <command> [args...]');
|
|
170
|
+
console.error('');
|
|
171
|
+
console.error('Commands:');
|
|
172
|
+
console.error(' user Capture user query (start of turn)');
|
|
173
|
+
console.error(' response Capture assistant response (end of turn)');
|
|
174
|
+
console.error(' restore Restore session context');
|
|
175
|
+
console.error(' ack Acknowledge turn synced to Redis');
|
|
176
|
+
console.error(' sync Sync unACKed turns to Redis');
|
|
177
|
+
console.error(' prune Remove safely ACKed turns');
|
|
178
|
+
console.error(' cleanup Prune all + evict old sessions');
|
|
179
|
+
console.error(' list List cached sessions');
|
|
180
|
+
console.error(' get Get turns from a session');
|
|
181
|
+
console.error(' stats Show cache statistics');
|
|
182
|
+
console.error('');
|
|
183
|
+
console.error('Global options:');
|
|
184
|
+
console.error(' --instance=ID Override instance ID (default: $EKKOS_INSTANCE_ID or "default")');
|
|
90
185
|
process.exit(1);
|
|
91
186
|
}
|
|
92
187
|
try {
|
|
188
|
+
// Parse global --instance flag
|
|
189
|
+
const { instanceId, cleanArgs } = parseInstanceFlag(args.slice(1));
|
|
190
|
+
const store = (0, LocalSessionStore_js_1.createLocalSessionStore)(instanceId);
|
|
93
191
|
switch (command) {
|
|
94
192
|
case 'user': {
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
193
|
+
// Detect format and parse accordingly
|
|
194
|
+
if (isNewUserFormat(cleanArgs)) {
|
|
195
|
+
// NEW format: <instance_id> <session_id> <session_name> <turn_id> <query> [project_path]
|
|
196
|
+
const [instId, sessionId, sessionName, turnIdStr, query, projectPath] = cleanArgs;
|
|
197
|
+
if (!instId || !sessionId || !sessionName || !turnIdStr || !query) {
|
|
198
|
+
console.error('Usage (new): capture user <instance_id> <session_id> <session_name> <turn_id> <query> [project_path]');
|
|
199
|
+
console.error('Usage (old): capture user <session_id> <session_name> <turn_id> <query> [project_path]');
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
// Use the provided instanceId
|
|
203
|
+
store.setInstanceId(instId);
|
|
204
|
+
const turnId = parseInt(turnIdStr, 10);
|
|
205
|
+
const turn = {
|
|
206
|
+
turn_id: turnId,
|
|
207
|
+
ts: new Date().toISOString(),
|
|
208
|
+
user_query: query,
|
|
209
|
+
assistant_response: '', // Will be filled by response capture
|
|
210
|
+
tools_used: [],
|
|
211
|
+
files_referenced: [],
|
|
212
|
+
instance_id: instId,
|
|
213
|
+
session_id: sessionId,
|
|
214
|
+
session_name: sessionName,
|
|
215
|
+
};
|
|
216
|
+
const result = store.appendTurn(sessionId, sessionName, turn, projectPath);
|
|
217
|
+
if (result.success) {
|
|
218
|
+
console.log(JSON.stringify({ success: true, instance_id: instId, latency_ms: result.latency_ms }));
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
console.error(JSON.stringify({ success: false, error: result.error }));
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
113
224
|
}
|
|
114
225
|
else {
|
|
115
|
-
|
|
116
|
-
|
|
226
|
+
// OLD format: <session_id> <session_name> <turn_id> <query> [project_path]
|
|
227
|
+
const [sessionId, sessionName, turnIdStr, query, projectPath] = cleanArgs;
|
|
228
|
+
if (!sessionId || !sessionName || !turnIdStr || !query) {
|
|
229
|
+
console.error('Usage (new): capture user <instance_id> <session_id> <session_name> <turn_id> <query> [project_path]');
|
|
230
|
+
console.error('Usage (old): capture user <session_id> <session_name> <turn_id> <query> [project_path]');
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
const turnId = parseInt(turnIdStr, 10);
|
|
234
|
+
const turn = {
|
|
235
|
+
turn_id: turnId,
|
|
236
|
+
ts: new Date().toISOString(),
|
|
237
|
+
user_query: query,
|
|
238
|
+
assistant_response: '', // Will be filled by response capture
|
|
239
|
+
tools_used: [],
|
|
240
|
+
files_referenced: [],
|
|
241
|
+
instance_id: instanceId, // Use parsed/default instanceId
|
|
242
|
+
session_id: sessionId,
|
|
243
|
+
session_name: sessionName,
|
|
244
|
+
};
|
|
245
|
+
const result = store.appendTurn(sessionId, sessionName, turn, projectPath);
|
|
246
|
+
if (result.success) {
|
|
247
|
+
console.log(JSON.stringify({ success: true, instance_id: instanceId, latency_ms: result.latency_ms }));
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
console.error(JSON.stringify({ success: false, error: result.error }));
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
117
253
|
}
|
|
118
254
|
break;
|
|
119
255
|
}
|
|
120
256
|
case 'response': {
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
257
|
+
// Detect format and parse accordingly
|
|
258
|
+
if (isNewResponseFormat(cleanArgs)) {
|
|
259
|
+
// NEW format: <instance_id> <session_id> <turn_id> <response> [tools_json] [files_json]
|
|
260
|
+
const [instId, sessionId, turnIdStr, response, toolsJson, filesJson] = cleanArgs;
|
|
261
|
+
if (!instId || !sessionId || !turnIdStr || !response) {
|
|
262
|
+
console.error('Usage (new): capture response <instance_id> <session_id> <turn_id> <response> [tools_json] [files_json]');
|
|
263
|
+
console.error('Usage (old): capture response <session_id> <turn_id> <response> [tools_json] [files_json]');
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
// Use the provided instanceId
|
|
267
|
+
store.setInstanceId(instId);
|
|
268
|
+
const turnId = parseInt(turnIdStr, 10);
|
|
269
|
+
const tools = toolsJson ? JSON.parse(toolsJson) : [];
|
|
270
|
+
const files = filesJson ? JSON.parse(filesJson) : [];
|
|
271
|
+
const result = store.updateTurnResponse(sessionId, turnId, response, tools, files);
|
|
272
|
+
if (result.success) {
|
|
273
|
+
console.log(JSON.stringify({ success: true, instance_id: instId, latency_ms: result.latency_ms }));
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
console.error(JSON.stringify({ success: false, error: result.error }));
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
133
279
|
}
|
|
134
280
|
else {
|
|
135
|
-
|
|
136
|
-
|
|
281
|
+
// OLD format: <session_id> <turn_id> <response> [tools_json] [files_json]
|
|
282
|
+
const [sessionId, turnIdStr, response, toolsJson, filesJson] = cleanArgs;
|
|
283
|
+
if (!sessionId || !turnIdStr || !response) {
|
|
284
|
+
console.error('Usage (new): capture response <instance_id> <session_id> <turn_id> <response> [tools_json] [files_json]');
|
|
285
|
+
console.error('Usage (old): capture response <session_id> <turn_id> <response> [tools_json] [files_json]');
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
const turnId = parseInt(turnIdStr, 10);
|
|
289
|
+
const tools = toolsJson ? JSON.parse(toolsJson) : [];
|
|
290
|
+
const files = filesJson ? JSON.parse(filesJson) : [];
|
|
291
|
+
const result = store.updateTurnResponse(sessionId, turnId, response, tools, files);
|
|
292
|
+
if (result.success) {
|
|
293
|
+
console.log(JSON.stringify({ success: true, instance_id: instanceId, latency_ms: result.latency_ms }));
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
console.error(JSON.stringify({ success: false, error: result.error }));
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
137
299
|
}
|
|
138
300
|
break;
|
|
139
301
|
}
|
|
140
302
|
case 'list': {
|
|
141
|
-
//
|
|
142
|
-
const
|
|
143
|
-
|
|
303
|
+
// Check for --all flag
|
|
304
|
+
const showAll = cleanArgs.includes('--all');
|
|
305
|
+
if (showAll) {
|
|
306
|
+
const sessions = store.listAllSessions();
|
|
307
|
+
console.log(JSON.stringify(sessions, null, 2));
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
const sessions = store.listSessions();
|
|
311
|
+
console.log(JSON.stringify(sessions, null, 2));
|
|
312
|
+
}
|
|
144
313
|
break;
|
|
145
314
|
}
|
|
146
315
|
case 'get': {
|
|
147
|
-
|
|
148
|
-
const
|
|
316
|
+
const sessionId = cleanArgs[0];
|
|
317
|
+
const nStr = cleanArgs[1];
|
|
149
318
|
if (!sessionId) {
|
|
150
|
-
console.error('Usage: capture get <session_id> [n]');
|
|
319
|
+
console.error('Usage: capture get <session_id> [n] [--instance=ID]');
|
|
151
320
|
process.exit(1);
|
|
152
321
|
}
|
|
153
|
-
const n = nStr ? parseInt(nStr, 10) : 10;
|
|
322
|
+
const n = nStr && /^\d+$/.test(nStr) ? parseInt(nStr, 10) : 10;
|
|
154
323
|
const result = store.getLastTurns(sessionId, n);
|
|
155
324
|
if (result.success) {
|
|
156
325
|
console.log(JSON.stringify(result.data, null, 2));
|
|
@@ -167,13 +336,12 @@ async function main() {
|
|
|
167
336
|
break;
|
|
168
337
|
}
|
|
169
338
|
case 'restore': {
|
|
170
|
-
|
|
171
|
-
const orchestrator = new RestoreOrchestrator_js_1.RestoreOrchestrator();
|
|
339
|
+
const orchestrator = new RestoreOrchestrator_js_1.RestoreOrchestrator(instanceId);
|
|
172
340
|
let sessionName;
|
|
173
341
|
let outputFormat = 'json';
|
|
174
342
|
let lastN = 10;
|
|
175
|
-
for (let i =
|
|
176
|
-
const arg =
|
|
343
|
+
for (let i = 0; i < cleanArgs.length; i++) {
|
|
344
|
+
const arg = cleanArgs[i];
|
|
177
345
|
if (arg === '--json') {
|
|
178
346
|
outputFormat = 'json';
|
|
179
347
|
}
|
|
@@ -189,6 +357,7 @@ async function main() {
|
|
|
189
357
|
}
|
|
190
358
|
const result = await orchestrator.restore({
|
|
191
359
|
session_name: sessionName,
|
|
360
|
+
instance_id: instanceId,
|
|
192
361
|
last_n: lastN,
|
|
193
362
|
});
|
|
194
363
|
if (result.success && result.data) {
|
|
@@ -199,6 +368,7 @@ async function main() {
|
|
|
199
368
|
console.log(JSON.stringify({
|
|
200
369
|
success: true,
|
|
201
370
|
source: result.data.source,
|
|
371
|
+
instance_id: instanceId,
|
|
202
372
|
latency_ms: result.latency_ms,
|
|
203
373
|
session_name: result.data.session_name,
|
|
204
374
|
session_id: result.data.session_id,
|
|
@@ -211,6 +381,7 @@ async function main() {
|
|
|
211
381
|
console.error(JSON.stringify({
|
|
212
382
|
success: false,
|
|
213
383
|
error: result.error,
|
|
384
|
+
instance_id: instanceId,
|
|
214
385
|
latency_ms: result.latency_ms,
|
|
215
386
|
}));
|
|
216
387
|
process.exit(1);
|
|
@@ -218,17 +389,16 @@ async function main() {
|
|
|
218
389
|
break;
|
|
219
390
|
}
|
|
220
391
|
case 'ack': {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const [, sessionId, turnIdStr] = args;
|
|
392
|
+
const sessionId = cleanArgs[0];
|
|
393
|
+
const turnIdStr = cleanArgs[1];
|
|
224
394
|
if (!sessionId || !turnIdStr) {
|
|
225
|
-
console.error('Usage: capture ack <session_id> <turn_id>');
|
|
395
|
+
console.error('Usage: capture ack <session_id> <turn_id> [--instance=ID]');
|
|
226
396
|
process.exit(1);
|
|
227
397
|
}
|
|
228
398
|
const turnId = parseInt(turnIdStr, 10);
|
|
229
399
|
const result = store.ack(sessionId, turnId);
|
|
230
400
|
if (result.success) {
|
|
231
|
-
console.log(JSON.stringify({ success: true, acked_turn_id: turnId, latency_ms: result.latency_ms }));
|
|
401
|
+
console.log(JSON.stringify({ success: true, acked_turn_id: turnId, instance_id: instanceId, latency_ms: result.latency_ms }));
|
|
232
402
|
}
|
|
233
403
|
else {
|
|
234
404
|
console.error(JSON.stringify({ success: false, error: result.error }));
|
|
@@ -237,11 +407,9 @@ async function main() {
|
|
|
237
407
|
break;
|
|
238
408
|
}
|
|
239
409
|
case 'prune': {
|
|
240
|
-
|
|
241
|
-
// Remove turns safely below ACK threshold
|
|
242
|
-
const [, sessionId] = args;
|
|
410
|
+
const sessionId = cleanArgs[0];
|
|
243
411
|
if (!sessionId) {
|
|
244
|
-
console.error('Usage: capture prune <session_id>');
|
|
412
|
+
console.error('Usage: capture prune <session_id> [--instance=ID]');
|
|
245
413
|
process.exit(1);
|
|
246
414
|
}
|
|
247
415
|
const meta = store.getSessionMeta(sessionId);
|
|
@@ -255,6 +423,7 @@ async function main() {
|
|
|
255
423
|
success: true,
|
|
256
424
|
pruned_turns: result.data,
|
|
257
425
|
acked_turn_id: meta.acked_turn_id,
|
|
426
|
+
instance_id: instanceId,
|
|
258
427
|
latency_ms: result.latency_ms,
|
|
259
428
|
}));
|
|
260
429
|
}
|
|
@@ -265,9 +434,7 @@ async function main() {
|
|
|
265
434
|
break;
|
|
266
435
|
}
|
|
267
436
|
case 'sync': {
|
|
268
|
-
|
|
269
|
-
// Sync unACKed turns to Redis, update ACK on success
|
|
270
|
-
const [, sessionIdArg] = args;
|
|
437
|
+
const sessionIdArg = cleanArgs[0];
|
|
271
438
|
const authToken = loadAuthToken();
|
|
272
439
|
if (!authToken) {
|
|
273
440
|
console.error(JSON.stringify({ success: false, error: 'No auth token configured' }));
|
|
@@ -297,6 +464,7 @@ async function main() {
|
|
|
297
464
|
try {
|
|
298
465
|
const payload = {
|
|
299
466
|
session_name: meta.session_name || session.session_name,
|
|
467
|
+
instance_id: instanceId,
|
|
300
468
|
turn_number: turn.turn_id,
|
|
301
469
|
user_query: turn.user_query,
|
|
302
470
|
agent_response: turn.assistant_response,
|
|
@@ -334,14 +502,12 @@ async function main() {
|
|
|
334
502
|
turns_synced: totalSynced,
|
|
335
503
|
turns_failed: totalFailed,
|
|
336
504
|
sessions_processed: sessions.length,
|
|
505
|
+
instance_id: instanceId,
|
|
337
506
|
}));
|
|
338
507
|
break;
|
|
339
508
|
}
|
|
340
509
|
case 'sync-supabase': {
|
|
341
|
-
|
|
342
|
-
// Sync ALL local cache turns to Supabase (episodic memory)
|
|
343
|
-
// This backfills any missing turns that failed during capture
|
|
344
|
-
const [, sessionIdArg] = args;
|
|
510
|
+
const sessionIdArg = cleanArgs[0];
|
|
345
511
|
const authToken = loadAuthToken();
|
|
346
512
|
if (!authToken) {
|
|
347
513
|
console.error(JSON.stringify({ success: false, error: 'No auth token configured' }));
|
|
@@ -380,6 +546,7 @@ async function main() {
|
|
|
380
546
|
session_id: session.session_id,
|
|
381
547
|
metadata: {
|
|
382
548
|
source: 'claude-code',
|
|
549
|
+
instance_id: instanceId,
|
|
383
550
|
turn_number: turn.turn_id,
|
|
384
551
|
session_name: meta.session_name,
|
|
385
552
|
tools_used: turn.tools_used || [],
|
|
@@ -422,12 +589,11 @@ async function main() {
|
|
|
422
589
|
turns_failed: totalFailed,
|
|
423
590
|
turns_skipped: totalSkipped,
|
|
424
591
|
sessions_processed: sessions.length,
|
|
592
|
+
instance_id: instanceId,
|
|
425
593
|
}));
|
|
426
594
|
break;
|
|
427
595
|
}
|
|
428
596
|
case 'cleanup': {
|
|
429
|
-
// capture cleanup
|
|
430
|
-
// Evict old sessions and prune all sessions
|
|
431
597
|
const sessions = store.listSessions();
|
|
432
598
|
let totalPruned = 0;
|
|
433
599
|
// Prune all sessions
|
|
@@ -444,6 +610,7 @@ async function main() {
|
|
|
444
610
|
turns_pruned: totalPruned,
|
|
445
611
|
sessions_evicted: evicted,
|
|
446
612
|
remaining_sessions: store.listSessions().length,
|
|
613
|
+
instance_id: instanceId,
|
|
447
614
|
}));
|
|
448
615
|
break;
|
|
449
616
|
}
|
package/dist/cache/types.d.ts
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
/**
|
|
10
10
|
* A single conversation turn (user + assistant pair)
|
|
11
|
+
*
|
|
12
|
+
* Per ekkOS Onboarding Spec v1.2 ADDENDUM:
|
|
13
|
+
* All persisted records MUST include: instanceId, sessionId, sessionName
|
|
11
14
|
*/
|
|
12
15
|
export interface Turn {
|
|
13
16
|
turn_id: number;
|
|
@@ -19,6 +22,9 @@ export interface Turn {
|
|
|
19
22
|
diffs?: string[];
|
|
20
23
|
token_estimate?: number;
|
|
21
24
|
is_complete?: boolean;
|
|
25
|
+
instance_id?: string;
|
|
26
|
+
session_id?: string;
|
|
27
|
+
session_name?: string;
|
|
22
28
|
}
|
|
23
29
|
/**
|
|
24
30
|
* Known placeholder strings that indicate incomplete/invalid responses
|
|
@@ -37,17 +43,22 @@ export interface SessionIndex {
|
|
|
37
43
|
}
|
|
38
44
|
export interface SessionIndexEntry {
|
|
39
45
|
session_id: string;
|
|
46
|
+
instance_id?: string;
|
|
40
47
|
last_active_ts: string;
|
|
41
48
|
last_turn_id: number;
|
|
42
49
|
acked_turn_id: number;
|
|
43
50
|
project_path?: string;
|
|
44
51
|
}
|
|
45
52
|
/**
|
|
46
|
-
* Per-session metadata stored in {session_id}.meta.json
|
|
53
|
+
* Per-session metadata stored in {instanceId}/{session_id}.meta.json
|
|
54
|
+
*
|
|
55
|
+
* Per ekkOS Onboarding Spec v1.2 ADDENDUM:
|
|
56
|
+
* All persisted records MUST include: instanceId, sessionId, sessionName
|
|
47
57
|
*/
|
|
48
58
|
export interface SessionMeta {
|
|
49
59
|
session_id: string;
|
|
50
60
|
session_name: string;
|
|
61
|
+
instance_id?: string;
|
|
51
62
|
acked_turn_id: number;
|
|
52
63
|
supabase_acked_turn_id?: number;
|
|
53
64
|
last_flush_ts: string;
|
|
@@ -68,6 +79,7 @@ export interface OpenLoop {
|
|
|
68
79
|
export interface RestorePayload {
|
|
69
80
|
session_id: string;
|
|
70
81
|
session_name: string;
|
|
82
|
+
instance_id?: string;
|
|
71
83
|
source: 'local' | 'redis' | 'supabase' | 'stream';
|
|
72
84
|
restored_turns: Turn[];
|
|
73
85
|
latest: {
|
|
@@ -118,6 +130,7 @@ export interface Pattern {
|
|
|
118
130
|
export interface RestoreOptions {
|
|
119
131
|
session_name?: string;
|
|
120
132
|
session_id?: string;
|
|
133
|
+
instance_id?: string;
|
|
121
134
|
last_n?: number;
|
|
122
135
|
include_directives?: boolean;
|
|
123
136
|
include_patterns?: boolean;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ekkos agent - Agent management commands for remote terminal
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* - daemon: Run the agent daemon (used by service)
|
|
6
|
+
* - start: Start the agent service
|
|
7
|
+
* - stop: Stop the agent service
|
|
8
|
+
* - restart: Restart the agent service
|
|
9
|
+
* - status: Check agent status
|
|
10
|
+
* - uninstall: Remove agent service
|
|
11
|
+
*/
|
|
12
|
+
export interface AgentOptions {
|
|
13
|
+
verbose?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Run the agent daemon (foreground)
|
|
17
|
+
*/
|
|
18
|
+
export declare function agentDaemon(options?: AgentOptions): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Start the agent service
|
|
21
|
+
*/
|
|
22
|
+
export declare function agentStart(options?: AgentOptions): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Stop the agent service
|
|
25
|
+
*/
|
|
26
|
+
export declare function agentStop(options?: AgentOptions): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Restart the agent service
|
|
29
|
+
*/
|
|
30
|
+
export declare function agentRestart(options?: AgentOptions): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Check agent status
|
|
33
|
+
*/
|
|
34
|
+
export declare function agentStatus(options?: AgentOptions): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Uninstall the agent service
|
|
37
|
+
*/
|
|
38
|
+
export declare function agentUninstall(options?: AgentOptions): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Show agent logs
|
|
41
|
+
*/
|
|
42
|
+
export declare function agentLogs(options?: {
|
|
43
|
+
follow?: boolean;
|
|
44
|
+
}): Promise<void>;
|