@ekkos/cli 0.2.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/dist/cache/LocalSessionStore.d.ts +129 -0
- package/dist/cache/LocalSessionStore.js +688 -0
- package/dist/cache/capture.d.ts +26 -0
- package/dist/cache/capture.js +461 -0
- package/dist/cache/index.d.ts +7 -0
- package/dist/cache/index.js +23 -0
- package/dist/cache/types.d.ts +147 -0
- package/dist/cache/types.js +40 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.js +478 -0
- package/dist/commands/run.d.ts +12 -0
- package/dist/commands/run.js +829 -0
- package/dist/commands/setup.d.ts +6 -0
- package/dist/commands/setup.js +658 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +109 -0
- package/dist/commands/test.d.ts +1 -0
- package/dist/commands/test.js +157 -0
- package/dist/deploy/agents.d.ts +15 -0
- package/dist/deploy/agents.js +72 -0
- package/dist/deploy/hooks.d.ts +16 -0
- package/dist/deploy/hooks.js +121 -0
- package/dist/deploy/index.d.ts +7 -0
- package/dist/deploy/index.js +24 -0
- package/dist/deploy/instructions.d.ts +12 -0
- package/dist/deploy/instructions.js +36 -0
- package/dist/deploy/mcp.d.ts +19 -0
- package/dist/deploy/mcp.js +109 -0
- package/dist/deploy/plugins.d.ts +19 -0
- package/dist/deploy/plugins.js +62 -0
- package/dist/deploy/settings.d.ts +8 -0
- package/dist/deploy/settings.js +84 -0
- package/dist/deploy/skills.d.ts +19 -0
- package/dist/deploy/skills.js +60 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +71 -0
- package/dist/restore/RestoreOrchestrator.d.ts +48 -0
- package/dist/restore/RestoreOrchestrator.js +481 -0
- package/dist/restore/index.d.ts +4 -0
- package/dist/restore/index.js +20 -0
- package/dist/utils/platform.d.ts +29 -0
- package/dist/utils/platform.js +65 -0
- package/dist/utils/session-words.json +119 -0
- package/dist/utils/state.d.ts +57 -0
- package/dist/utils/state.js +186 -0
- package/dist/utils/templates.d.ts +24 -0
- package/dist/utils/templates.js +118 -0
- package/package.json +48 -0
- package/templates/CLAUDE.md +287 -0
- package/templates/README.md +378 -0
- package/templates/agents/README.md +182 -0
- package/templates/agents/code-reviewer.md +166 -0
- package/templates/agents/debug-detective.md +169 -0
- package/templates/agents/ekkOS_Vercel.md +99 -0
- package/templates/agents/extension-manager.md +229 -0
- package/templates/agents/git-companion.md +185 -0
- package/templates/agents/github-test-agent.md +321 -0
- package/templates/agents/railway-manager.md +179 -0
- package/templates/claude-plugins/PHASE2_COMPLETION.md +346 -0
- package/templates/claude-plugins/PLUGIN_PROPOSALS.md +1776 -0
- package/templates/claude-plugins/README.md +587 -0
- package/templates/claude-plugins/agents/code-reviewer.json +14 -0
- package/templates/claude-plugins/agents/debug-detective.json +15 -0
- package/templates/claude-plugins/agents/git-companion.json +14 -0
- package/templates/claude-plugins/blog-manager/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/blog-manager/commands/blog.md +691 -0
- package/templates/claude-plugins/golden-loop-monitor/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/golden-loop-monitor/commands/loop-status.md +434 -0
- package/templates/claude-plugins/learning-tracker/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/learning-tracker/commands/my-patterns.md +282 -0
- package/templates/claude-plugins/memory-lens/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/memory-lens/commands/memory-search.md +181 -0
- package/templates/claude-plugins/pattern-coach/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/pattern-coach/commands/forge.md +365 -0
- package/templates/claude-plugins/project-schema-validator/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/project-schema-validator/commands/validate-schema.md +582 -0
- package/templates/claude-plugins-admin/AGENT_TEAM_PROPOSALS.md +819 -0
- package/templates/claude-plugins-admin/README.md +446 -0
- package/templates/claude-plugins-admin/autonomous-admin-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/autonomous-admin-agent/commands/agent.md +595 -0
- package/templates/claude-plugins-admin/backend-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/backend-agent/commands/backend.md +798 -0
- package/templates/claude-plugins-admin/deploy-guardian/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/deploy-guardian/commands/deploy.md +554 -0
- package/templates/claude-plugins-admin/frontend-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/frontend-agent/commands/frontend.md +881 -0
- package/templates/claude-plugins-admin/mcp-server-manager/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/mcp-server-manager/commands/mcp.md +85 -0
- package/templates/claude-plugins-admin/memory-system-monitor/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/memory-system-monitor/commands/memory-health.md +569 -0
- package/templates/claude-plugins-admin/qa-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/qa-agent/commands/qa.md +863 -0
- package/templates/claude-plugins-admin/tech-lead-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/tech-lead-agent/commands/lead.md +732 -0
- package/templates/commands/continue.md +47 -0
- package/templates/cursor-hooks/after-agent-response.sh +117 -0
- package/templates/cursor-hooks/before-submit-prompt.sh +419 -0
- package/templates/cursor-hooks/hooks.json +20 -0
- package/templates/cursor-hooks/lib/contract.sh +320 -0
- package/templates/cursor-hooks/stop.sh +75 -0
- package/templates/cursor-rules/ekkos-memory.md +187 -0
- package/templates/hooks/assistant-response.sh +96 -0
- package/templates/hooks/hooks.json +28 -0
- package/templates/hooks/lib/contract.sh +320 -0
- package/templates/hooks/lib/state.sh +158 -0
- package/templates/hooks/session-start.ps1 +41 -0
- package/templates/hooks/session-start.sh +318 -0
- package/templates/hooks/stop.ps1 +16 -0
- package/templates/hooks/stop.sh +989 -0
- package/templates/hooks/user-prompt-submit.ps1 +174 -0
- package/templates/hooks/user-prompt-submit.sh +587 -0
- package/templates/hooks-node/lib/state.js +187 -0
- package/templates/hooks-node/stop.js +416 -0
- package/templates/hooks-node/user-prompt-submit.js +337 -0
- package/templates/plan-template.md +306 -0
- package/templates/rules/00-hooks-contract.mdc +89 -0
- package/templates/rules/30-ekkos-core.mdc +188 -0
- package/templates/rules/31-ekkos-messages.mdc +78 -0
- package/templates/skills/continue/SKILL.md +169 -0
- package/templates/skills/ekkOS_Deep_Recall/Skill.md +282 -0
- package/templates/skills/ekkOS_Learn/Skill.md +265 -0
- package/templates/skills/ekkOS_Memory_First/Skill.md +206 -0
- package/templates/skills/ekkOS_Plan_Assist/Skill.md +302 -0
- package/templates/skills/ekkOS_Preferences/Skill.md +247 -0
- package/templates/skills/ekkOS_Reflect/Skill.md +257 -0
- package/templates/skills/ekkOS_Safety/Skill.md +265 -0
- package/templates/skills/ekkOS_Schema/Skill.md +251 -0
- package/templates/skills/ekkOS_Summary/Skill.md +257 -0
- package/templates/skills/ekkOS_Vault/Skill.md +287 -0
- package/templates/skills/permissions/Skill.md +322 -0
- package/templates/spec-template.md +159 -0
- package/templates/windsurf-hooks/before-submit-prompt.sh +238 -0
- package/templates/windsurf-hooks/hooks.json +10 -0
- package/templates/windsurf-hooks/lib/contract.sh +320 -0
- package/templates/windsurf-rules/ekkos-memory.md +129 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ekkOS Fast /continue - Restore Orchestrator
|
|
4
|
+
*
|
|
5
|
+
* Implements the 3-tier restore chain for near-zero context loss:
|
|
6
|
+
* - Tier 0: Local JSONL cache (~20ms)
|
|
7
|
+
* - Tier 1: Redis hot cache (~150ms)
|
|
8
|
+
* - Tier 2: Supabase cold store (~500ms)
|
|
9
|
+
*
|
|
10
|
+
* Falls back through tiers on miss, tracks which tier succeeded.
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.restoreOrchestrator = exports.RestoreOrchestrator = void 0;
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const os = __importStar(require("os"));
|
|
50
|
+
const LocalSessionStore_js_1 = require("../cache/LocalSessionStore.js");
|
|
51
|
+
const types_js_1 = require("../cache/types.js");
|
|
52
|
+
// API configuration
|
|
53
|
+
const MEMORY_API_URL = process.env.EKKOS_API_URL || 'https://api.ekkos.dev';
|
|
54
|
+
const CONFIG_PATH = path.join(os.homedir(), '.ekkos', 'config.json');
|
|
55
|
+
/**
|
|
56
|
+
* Load auth token from config
|
|
57
|
+
*/
|
|
58
|
+
function loadAuthToken() {
|
|
59
|
+
try {
|
|
60
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
61
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
|
|
62
|
+
return config.hookApiKey || config.apiKey || '';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Ignore config errors
|
|
67
|
+
}
|
|
68
|
+
return '';
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* RestoreOrchestrator - Tiered restore for /continue
|
|
72
|
+
*/
|
|
73
|
+
class RestoreOrchestrator {
|
|
74
|
+
constructor() {
|
|
75
|
+
this.localStore = new LocalSessionStore_js_1.LocalSessionStore();
|
|
76
|
+
this.authToken = loadAuthToken();
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Main restore function - attempts tiers in order
|
|
80
|
+
*/
|
|
81
|
+
async restore(options = {}) {
|
|
82
|
+
const startTime = Date.now();
|
|
83
|
+
const lastN = options.last_n || 10;
|
|
84
|
+
// Resolve session
|
|
85
|
+
let sessionId = options.session_id;
|
|
86
|
+
let sessionName = options.session_name;
|
|
87
|
+
if (sessionName && !sessionId) {
|
|
88
|
+
sessionId = this.localStore.getSessionId(sessionName) || undefined;
|
|
89
|
+
}
|
|
90
|
+
if (!sessionId && !sessionName) {
|
|
91
|
+
// Get most recent session
|
|
92
|
+
const sessions = this.localStore.listSessions();
|
|
93
|
+
if (sessions.length > 0) {
|
|
94
|
+
sessionId = sessions[0].session_id;
|
|
95
|
+
sessionName = sessions[0].session_name;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!sessionId) {
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
error: 'No session specified and no recent sessions found',
|
|
102
|
+
latency_ms: Date.now() - startTime,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// Try Tier 0: Local cache
|
|
106
|
+
const localResult = await this.restoreFromLocal(sessionId, sessionName || '', lastN);
|
|
107
|
+
if (localResult.success && localResult.data) {
|
|
108
|
+
return {
|
|
109
|
+
...localResult,
|
|
110
|
+
latency_ms: Date.now() - startTime,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// Try Tier 1: Redis
|
|
114
|
+
const redisResult = await this.restoreFromRedis(sessionName || sessionId, lastN);
|
|
115
|
+
if (redisResult.success && redisResult.data) {
|
|
116
|
+
return {
|
|
117
|
+
...redisResult,
|
|
118
|
+
latency_ms: Date.now() - startTime,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// Try Tier 2: Supabase
|
|
122
|
+
const supabaseResult = await this.restoreFromSupabase(sessionId, lastN);
|
|
123
|
+
if (supabaseResult.success && supabaseResult.data) {
|
|
124
|
+
return {
|
|
125
|
+
...supabaseResult,
|
|
126
|
+
latency_ms: Date.now() - startTime,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// All tiers failed
|
|
130
|
+
return {
|
|
131
|
+
success: false,
|
|
132
|
+
error: `All restore tiers failed. Local: ${localResult.error}, Redis: ${redisResult.error}, Supabase: ${supabaseResult.error}`,
|
|
133
|
+
latency_ms: Date.now() - startTime,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Tier 0: Restore from local JSONL cache
|
|
138
|
+
*/
|
|
139
|
+
async restoreFromLocal(sessionId, sessionName, lastN) {
|
|
140
|
+
const startTime = Date.now();
|
|
141
|
+
const turnsResult = this.localStore.getLastTurns(sessionId, lastN);
|
|
142
|
+
if (!turnsResult.success || !turnsResult.data || turnsResult.data.length === 0) {
|
|
143
|
+
return {
|
|
144
|
+
success: false,
|
|
145
|
+
error: turnsResult.error || 'No turns found in local cache',
|
|
146
|
+
source: 'local',
|
|
147
|
+
latency_ms: Date.now() - startTime,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
// Mark each turn as complete or incomplete using stricter validation
|
|
151
|
+
const allTurns = turnsResult.data.map((t) => ({
|
|
152
|
+
...t,
|
|
153
|
+
is_complete: (0, types_js_1.isValidAssistantResponse)(t.assistant_response),
|
|
154
|
+
}));
|
|
155
|
+
// Separate complete turns from incomplete (pending) turns
|
|
156
|
+
const completeTurns = allTurns.filter((t) => t.is_complete);
|
|
157
|
+
const pendingTurns = allTurns.filter((t) => !t.is_complete);
|
|
158
|
+
// Get the most recent pending turn (user query not yet answered)
|
|
159
|
+
const pendingTurn = pendingTurns.length > 0 ? pendingTurns[pendingTurns.length - 1] : undefined;
|
|
160
|
+
if (completeTurns.length === 0) {
|
|
161
|
+
// No complete turns, but might have pending user query
|
|
162
|
+
if (pendingTurn) {
|
|
163
|
+
return {
|
|
164
|
+
success: false,
|
|
165
|
+
error: `No complete turns found. Pending user query: "${pendingTurn.user_query.slice(0, 50)}..."`,
|
|
166
|
+
source: 'local',
|
|
167
|
+
latency_ms: Date.now() - startTime,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
success: false,
|
|
172
|
+
error: 'No complete turns found (all turns missing assistant_response)',
|
|
173
|
+
source: 'local',
|
|
174
|
+
latency_ms: Date.now() - startTime,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// Use complete turns for context, but include pending for awareness
|
|
178
|
+
const turns = completeTurns;
|
|
179
|
+
const latest = turns[turns.length - 1]; // Guaranteed to be complete
|
|
180
|
+
const meta = this.localStore.getSessionMeta(sessionId);
|
|
181
|
+
// Estimate tokens (rough: 4 chars per token)
|
|
182
|
+
const totalChars = turns.reduce((sum, t) => sum + t.user_query.length + t.assistant_response.length, 0);
|
|
183
|
+
const payload = {
|
|
184
|
+
session_id: sessionId,
|
|
185
|
+
session_name: sessionName || this.localStore.getSessionName(sessionId) || 'unknown',
|
|
186
|
+
source: 'local',
|
|
187
|
+
restored_turns: turns,
|
|
188
|
+
latest: {
|
|
189
|
+
user_query: latest.user_query,
|
|
190
|
+
assistant_response: latest.assistant_response,
|
|
191
|
+
},
|
|
192
|
+
pending_turn: pendingTurn
|
|
193
|
+
? {
|
|
194
|
+
turn_id: pendingTurn.turn_id,
|
|
195
|
+
user_query: pendingTurn.user_query,
|
|
196
|
+
ts: pendingTurn.ts,
|
|
197
|
+
}
|
|
198
|
+
: undefined,
|
|
199
|
+
directives: [], // Will be fetched separately if needed
|
|
200
|
+
patterns: [],
|
|
201
|
+
metadata: {
|
|
202
|
+
acked_turn_id: meta?.acked_turn_id || 0,
|
|
203
|
+
last_flush_ts: meta?.last_flush_ts || new Date().toISOString(),
|
|
204
|
+
token_estimate: Math.ceil(totalChars / 4),
|
|
205
|
+
complete_turn_count: completeTurns.length,
|
|
206
|
+
has_pending: !!pendingTurn,
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
return {
|
|
210
|
+
success: true,
|
|
211
|
+
data: payload,
|
|
212
|
+
source: 'local',
|
|
213
|
+
latency_ms: Date.now() - startTime,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Tier 1: Restore from Redis via API
|
|
218
|
+
*/
|
|
219
|
+
async restoreFromRedis(sessionName, lastN) {
|
|
220
|
+
const startTime = Date.now();
|
|
221
|
+
if (!this.authToken) {
|
|
222
|
+
return {
|
|
223
|
+
success: false,
|
|
224
|
+
error: 'No auth token configured',
|
|
225
|
+
source: 'redis',
|
|
226
|
+
latency_ms: Date.now() - startTime,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
const url = `${MEMORY_API_URL}/api/v1/working/restore?session=${encodeURIComponent(sessionName)}&full=true&limit=${lastN}`;
|
|
231
|
+
const response = await fetch(url, {
|
|
232
|
+
method: 'GET',
|
|
233
|
+
headers: {
|
|
234
|
+
Authorization: `Bearer ${this.authToken}`,
|
|
235
|
+
'Content-Type': 'application/json',
|
|
236
|
+
},
|
|
237
|
+
signal: AbortSignal.timeout(5000), // 5 second timeout
|
|
238
|
+
});
|
|
239
|
+
if (!response.ok) {
|
|
240
|
+
return {
|
|
241
|
+
success: false,
|
|
242
|
+
error: `Redis API returned ${response.status}`,
|
|
243
|
+
source: 'redis',
|
|
244
|
+
latency_ms: Date.now() - startTime,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const data = await response.json();
|
|
248
|
+
if (data.mode === 'full_restore' && data.turns && data.turns.length > 0) {
|
|
249
|
+
const allTurns = data.turns.map((t, idx) => ({
|
|
250
|
+
turn_id: t.turn_number || idx + 1,
|
|
251
|
+
ts: t.timestamp || new Date().toISOString(),
|
|
252
|
+
user_query: t.user?.query || '',
|
|
253
|
+
assistant_response: t.agent?.response || '',
|
|
254
|
+
tools_used: t.agent?.tools_used || [],
|
|
255
|
+
files_referenced: t.user?.files_referenced || [],
|
|
256
|
+
is_complete: (0, types_js_1.isValidAssistantResponse)(t.agent?.response),
|
|
257
|
+
}));
|
|
258
|
+
// Separate complete turns from pending
|
|
259
|
+
const completeTurns = allTurns.filter((t) => t.is_complete);
|
|
260
|
+
const pendingTurns = allTurns.filter((t) => !t.is_complete);
|
|
261
|
+
const pendingTurn = pendingTurns.length > 0 ? pendingTurns[pendingTurns.length - 1] : undefined;
|
|
262
|
+
if (completeTurns.length === 0) {
|
|
263
|
+
return {
|
|
264
|
+
success: false,
|
|
265
|
+
error: pendingTurn
|
|
266
|
+
? `No complete turns in Redis. Pending: "${pendingTurn.user_query.slice(0, 50)}..."`
|
|
267
|
+
: 'No complete turns found in Redis',
|
|
268
|
+
source: 'redis',
|
|
269
|
+
latency_ms: Date.now() - startTime,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const turns = completeTurns;
|
|
273
|
+
const latest = turns[turns.length - 1]; // Guaranteed to be complete
|
|
274
|
+
const payload = {
|
|
275
|
+
session_id: data.session_id || sessionName,
|
|
276
|
+
session_name: data.session_name || sessionName,
|
|
277
|
+
source: 'redis',
|
|
278
|
+
restored_turns: turns,
|
|
279
|
+
latest: {
|
|
280
|
+
user_query: latest.user_query,
|
|
281
|
+
assistant_response: latest.assistant_response,
|
|
282
|
+
},
|
|
283
|
+
pending_turn: pendingTurn
|
|
284
|
+
? {
|
|
285
|
+
turn_id: pendingTurn.turn_id,
|
|
286
|
+
user_query: pendingTurn.user_query,
|
|
287
|
+
ts: pendingTurn.ts,
|
|
288
|
+
}
|
|
289
|
+
: undefined,
|
|
290
|
+
directives: [],
|
|
291
|
+
patterns: [],
|
|
292
|
+
metadata: {
|
|
293
|
+
acked_turn_id: turns.length,
|
|
294
|
+
last_flush_ts: new Date().toISOString(),
|
|
295
|
+
token_estimate: data.token_estimate || 0,
|
|
296
|
+
complete_turn_count: completeTurns.length,
|
|
297
|
+
has_pending: !!pendingTurn,
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
return {
|
|
301
|
+
success: true,
|
|
302
|
+
data: payload,
|
|
303
|
+
source: 'redis',
|
|
304
|
+
latency_ms: Date.now() - startTime,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
success: false,
|
|
309
|
+
error: 'No turns found in Redis',
|
|
310
|
+
source: 'redis',
|
|
311
|
+
latency_ms: Date.now() - startTime,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
return {
|
|
316
|
+
success: false,
|
|
317
|
+
error: err instanceof Error ? err.message : String(err),
|
|
318
|
+
source: 'redis',
|
|
319
|
+
latency_ms: Date.now() - startTime,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Tier 2: Restore from Supabase via API
|
|
325
|
+
*/
|
|
326
|
+
async restoreFromSupabase(sessionId, lastN) {
|
|
327
|
+
const startTime = Date.now();
|
|
328
|
+
if (!this.authToken) {
|
|
329
|
+
return {
|
|
330
|
+
success: false,
|
|
331
|
+
error: 'No auth token configured',
|
|
332
|
+
source: 'supabase',
|
|
333
|
+
latency_ms: Date.now() - startTime,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
// Use the episodic memory API to get conversations
|
|
338
|
+
const url = `${MEMORY_API_URL}/api/v1/memory/recall?session_id=${encodeURIComponent(sessionId)}&limit=${lastN}`;
|
|
339
|
+
const response = await fetch(url, {
|
|
340
|
+
method: 'GET',
|
|
341
|
+
headers: {
|
|
342
|
+
Authorization: `Bearer ${this.authToken}`,
|
|
343
|
+
'Content-Type': 'application/json',
|
|
344
|
+
},
|
|
345
|
+
signal: AbortSignal.timeout(10000), // 10 second timeout for cold storage
|
|
346
|
+
});
|
|
347
|
+
if (!response.ok) {
|
|
348
|
+
return {
|
|
349
|
+
success: false,
|
|
350
|
+
error: `Supabase API returned ${response.status}`,
|
|
351
|
+
source: 'supabase',
|
|
352
|
+
latency_ms: Date.now() - startTime,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
const data = await response.json();
|
|
356
|
+
if (data.conversations && data.conversations.length > 0) {
|
|
357
|
+
const allTurns = data.conversations.map((c, idx) => ({
|
|
358
|
+
turn_id: idx + 1,
|
|
359
|
+
ts: c.processed_at || c.created_at || new Date().toISOString(),
|
|
360
|
+
user_query: c.user_query || '',
|
|
361
|
+
assistant_response: c.assistant_response || '',
|
|
362
|
+
tools_used: [],
|
|
363
|
+
files_referenced: c.metadata?.file_changes?.map((f) => f.path) || [],
|
|
364
|
+
is_complete: (0, types_js_1.isValidAssistantResponse)(c.assistant_response),
|
|
365
|
+
}));
|
|
366
|
+
// Separate complete turns from pending
|
|
367
|
+
const completeTurns = allTurns.filter((t) => t.is_complete);
|
|
368
|
+
const pendingTurns = allTurns.filter((t) => !t.is_complete);
|
|
369
|
+
const pendingTurn = pendingTurns.length > 0 ? pendingTurns[pendingTurns.length - 1] : undefined;
|
|
370
|
+
if (completeTurns.length === 0) {
|
|
371
|
+
return {
|
|
372
|
+
success: false,
|
|
373
|
+
error: pendingTurn
|
|
374
|
+
? `No complete turns in Supabase. Pending: "${pendingTurn.user_query.slice(0, 50)}..."`
|
|
375
|
+
: 'No complete turns found in Supabase',
|
|
376
|
+
source: 'supabase',
|
|
377
|
+
latency_ms: Date.now() - startTime,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
const turns = completeTurns;
|
|
381
|
+
const latest = turns[turns.length - 1]; // Guaranteed to be complete
|
|
382
|
+
const payload = {
|
|
383
|
+
session_id: sessionId,
|
|
384
|
+
session_name: data.session_name || 'unknown',
|
|
385
|
+
source: 'supabase',
|
|
386
|
+
restored_turns: turns,
|
|
387
|
+
latest: {
|
|
388
|
+
user_query: latest.user_query,
|
|
389
|
+
assistant_response: latest.assistant_response,
|
|
390
|
+
},
|
|
391
|
+
pending_turn: pendingTurn
|
|
392
|
+
? {
|
|
393
|
+
turn_id: pendingTurn.turn_id,
|
|
394
|
+
user_query: pendingTurn.user_query,
|
|
395
|
+
ts: pendingTurn.ts,
|
|
396
|
+
}
|
|
397
|
+
: undefined,
|
|
398
|
+
directives: [],
|
|
399
|
+
patterns: [],
|
|
400
|
+
metadata: {
|
|
401
|
+
acked_turn_id: turns.length,
|
|
402
|
+
last_flush_ts: new Date().toISOString(),
|
|
403
|
+
token_estimate: 0,
|
|
404
|
+
complete_turn_count: completeTurns.length,
|
|
405
|
+
has_pending: !!pendingTurn,
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
return {
|
|
409
|
+
success: true,
|
|
410
|
+
data: payload,
|
|
411
|
+
source: 'supabase',
|
|
412
|
+
latency_ms: Date.now() - startTime,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
success: false,
|
|
417
|
+
error: 'No conversations found in Supabase',
|
|
418
|
+
source: 'supabase',
|
|
419
|
+
latency_ms: Date.now() - startTime,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
catch (err) {
|
|
423
|
+
return {
|
|
424
|
+
success: false,
|
|
425
|
+
error: err instanceof Error ? err.message : String(err),
|
|
426
|
+
source: 'supabase',
|
|
427
|
+
latency_ms: Date.now() - startTime,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Format RestorePayload as system-reminder markdown
|
|
433
|
+
*/
|
|
434
|
+
formatAsSystemReminder(payload) {
|
|
435
|
+
const lines = [
|
|
436
|
+
'<system-reminder>',
|
|
437
|
+
'CONTEXT RESTORED (ekkOS /continue)',
|
|
438
|
+
`Session: ${payload.session_name} (${payload.session_id})`,
|
|
439
|
+
`Source: ${payload.source}`,
|
|
440
|
+
`Turns restored: ${payload.restored_turns.length}`,
|
|
441
|
+
'',
|
|
442
|
+
'## Last User Request',
|
|
443
|
+
payload.latest.user_query,
|
|
444
|
+
'',
|
|
445
|
+
'## Last Assistant Response',
|
|
446
|
+
payload.latest.assistant_response.slice(0, 2000),
|
|
447
|
+
payload.latest.assistant_response.length > 2000 ? '\n[...truncated...]' : '',
|
|
448
|
+
'',
|
|
449
|
+
'## Recent Turns (older → newer)',
|
|
450
|
+
];
|
|
451
|
+
// Add turn summaries (skip last one since it's shown in detail above)
|
|
452
|
+
const turnsToShow = payload.restored_turns.slice(0, -1);
|
|
453
|
+
for (let i = 0; i < turnsToShow.length; i++) {
|
|
454
|
+
const turn = turnsToShow[i];
|
|
455
|
+
const userSnippet = turn.user_query.slice(0, 100) + (turn.user_query.length > 100 ? '...' : '');
|
|
456
|
+
const assistantSnippet = turn.assistant_response.slice(0, 100) + (turn.assistant_response.length > 100 ? '...' : '');
|
|
457
|
+
lines.push(`${i + 1}) U: ${userSnippet}`);
|
|
458
|
+
lines.push(` A: ${assistantSnippet}`);
|
|
459
|
+
}
|
|
460
|
+
lines.push('');
|
|
461
|
+
lines.push('INSTRUCTION: Resume seamlessly where you left off.');
|
|
462
|
+
lines.push('Do not ask "what were we doing?"');
|
|
463
|
+
lines.push('Start your response with: "✓ Continuing -"');
|
|
464
|
+
lines.push('</system-reminder>');
|
|
465
|
+
return lines.join('\n');
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Get restore statistics
|
|
469
|
+
*/
|
|
470
|
+
getStats() {
|
|
471
|
+
const stats = this.localStore.getStats();
|
|
472
|
+
return {
|
|
473
|
+
local_sessions: stats.session_count,
|
|
474
|
+
local_turns: stats.total_turns,
|
|
475
|
+
local_size_bytes: stats.cache_size_bytes,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
exports.RestoreOrchestrator = RestoreOrchestrator;
|
|
480
|
+
// Export singleton instance
|
|
481
|
+
exports.restoreOrchestrator = new RestoreOrchestrator();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ekkOS Fast /continue - Restore Module
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
17
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
__exportStar(require("./RestoreOrchestrator.js"), exports);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const isWindows: boolean;
|
|
2
|
+
export declare const isMac: boolean;
|
|
3
|
+
export declare const isLinux: boolean;
|
|
4
|
+
export declare const PLATFORM_URL = "https://platform.ekkos.dev";
|
|
5
|
+
export declare const MCP_API_URL = "https://mcp.ekkos.dev";
|
|
6
|
+
export declare const HOME_DIR: string;
|
|
7
|
+
export declare const EKKOS_DIR: string;
|
|
8
|
+
export declare const EKKOS_CONFIG: string;
|
|
9
|
+
export declare const CLAUDE_DIR: string;
|
|
10
|
+
export declare const CLAUDE_CONFIG: string;
|
|
11
|
+
export declare const CLAUDE_SETTINGS: string;
|
|
12
|
+
export declare const CLAUDE_HOOKS_DIR: string;
|
|
13
|
+
export declare const CLAUDE_SKILLS_DIR: string;
|
|
14
|
+
export declare const CLAUDE_AGENTS_DIR: string;
|
|
15
|
+
export declare const CLAUDE_PLUGINS_DIR: string;
|
|
16
|
+
export declare const CLAUDE_STATE_DIR: string;
|
|
17
|
+
export declare const CLAUDE_MD: string;
|
|
18
|
+
export declare const CURSOR_DIR: string;
|
|
19
|
+
export declare const CURSOR_MCP: string;
|
|
20
|
+
export declare const WINDSURF_DIR: string;
|
|
21
|
+
export declare const WINDSURF_MCP: string;
|
|
22
|
+
/**
|
|
23
|
+
* Detect which IDEs are installed on this system
|
|
24
|
+
*/
|
|
25
|
+
export declare function detectInstalledIDEs(): string[];
|
|
26
|
+
/**
|
|
27
|
+
* Get the current running IDE from environment
|
|
28
|
+
*/
|
|
29
|
+
export declare function detectCurrentIDE(): string | null;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WINDSURF_MCP = exports.WINDSURF_DIR = exports.CURSOR_MCP = exports.CURSOR_DIR = exports.CLAUDE_MD = exports.CLAUDE_STATE_DIR = exports.CLAUDE_PLUGINS_DIR = exports.CLAUDE_AGENTS_DIR = exports.CLAUDE_SKILLS_DIR = exports.CLAUDE_HOOKS_DIR = exports.CLAUDE_SETTINGS = exports.CLAUDE_CONFIG = exports.CLAUDE_DIR = exports.EKKOS_CONFIG = exports.EKKOS_DIR = exports.HOME_DIR = exports.MCP_API_URL = exports.PLATFORM_URL = exports.isLinux = exports.isMac = exports.isWindows = void 0;
|
|
4
|
+
exports.detectInstalledIDEs = detectInstalledIDEs;
|
|
5
|
+
exports.detectCurrentIDE = detectCurrentIDE;
|
|
6
|
+
const os_1 = require("os");
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
exports.isWindows = (0, os_1.platform)() === 'win32';
|
|
10
|
+
exports.isMac = (0, os_1.platform)() === 'darwin';
|
|
11
|
+
exports.isLinux = (0, os_1.platform)() === 'linux';
|
|
12
|
+
exports.PLATFORM_URL = 'https://platform.ekkos.dev';
|
|
13
|
+
exports.MCP_API_URL = 'https://mcp.ekkos.dev';
|
|
14
|
+
// Common directories
|
|
15
|
+
exports.HOME_DIR = (0, os_1.homedir)();
|
|
16
|
+
exports.EKKOS_DIR = (0, path_1.join)(exports.HOME_DIR, '.ekkos');
|
|
17
|
+
exports.EKKOS_CONFIG = (0, path_1.join)(exports.EKKOS_DIR, 'config.json');
|
|
18
|
+
exports.CLAUDE_DIR = (0, path_1.join)(exports.HOME_DIR, '.claude');
|
|
19
|
+
exports.CLAUDE_CONFIG = (0, path_1.join)(exports.HOME_DIR, '.claude.json');
|
|
20
|
+
exports.CLAUDE_SETTINGS = (0, path_1.join)(exports.CLAUDE_DIR, 'settings.json');
|
|
21
|
+
exports.CLAUDE_HOOKS_DIR = (0, path_1.join)(exports.CLAUDE_DIR, 'hooks');
|
|
22
|
+
exports.CLAUDE_SKILLS_DIR = (0, path_1.join)(exports.CLAUDE_DIR, 'skills');
|
|
23
|
+
exports.CLAUDE_AGENTS_DIR = (0, path_1.join)(exports.CLAUDE_DIR, 'agents');
|
|
24
|
+
exports.CLAUDE_PLUGINS_DIR = (0, path_1.join)(exports.CLAUDE_DIR, 'plugins', 'ekkos');
|
|
25
|
+
exports.CLAUDE_STATE_DIR = (0, path_1.join)(exports.CLAUDE_DIR, 'state');
|
|
26
|
+
exports.CLAUDE_MD = (0, path_1.join)(exports.CLAUDE_DIR, 'CLAUDE.md');
|
|
27
|
+
exports.CURSOR_DIR = (0, path_1.join)(exports.HOME_DIR, '.cursor');
|
|
28
|
+
exports.CURSOR_MCP = (0, path_1.join)(exports.CURSOR_DIR, 'mcp.json');
|
|
29
|
+
exports.WINDSURF_DIR = (0, path_1.join)(exports.HOME_DIR, '.codeium', 'windsurf');
|
|
30
|
+
exports.WINDSURF_MCP = (0, path_1.join)(exports.WINDSURF_DIR, 'mcp_config.json');
|
|
31
|
+
/**
|
|
32
|
+
* Detect which IDEs are installed on this system
|
|
33
|
+
*/
|
|
34
|
+
function detectInstalledIDEs() {
|
|
35
|
+
const ides = [];
|
|
36
|
+
// Check Claude Code
|
|
37
|
+
if ((0, fs_1.existsSync)(exports.CLAUDE_DIR) || process.env.CLAUDE_CODE) {
|
|
38
|
+
ides.push('claude');
|
|
39
|
+
}
|
|
40
|
+
// Check Cursor
|
|
41
|
+
if ((0, fs_1.existsSync)(exports.CURSOR_DIR)) {
|
|
42
|
+
ides.push('cursor');
|
|
43
|
+
}
|
|
44
|
+
// Check Windsurf
|
|
45
|
+
if ((0, fs_1.existsSync)(exports.WINDSURF_DIR)) {
|
|
46
|
+
ides.push('windsurf');
|
|
47
|
+
}
|
|
48
|
+
return ides;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get the current running IDE from environment
|
|
52
|
+
*/
|
|
53
|
+
function detectCurrentIDE() {
|
|
54
|
+
const termProgram = process.env.TERM_PROGRAM?.toLowerCase() || '';
|
|
55
|
+
const termEmulator = process.env.TERMINAL_EMULATOR?.toLowerCase() || '';
|
|
56
|
+
if (termProgram.includes('cursor'))
|
|
57
|
+
return 'cursor';
|
|
58
|
+
if (termProgram.includes('vscode') || termEmulator.includes('vscode'))
|
|
59
|
+
return 'vscode';
|
|
60
|
+
if (termProgram.includes('windsurf') || termProgram.includes('codeium'))
|
|
61
|
+
return 'windsurf';
|
|
62
|
+
if (process.env.CLAUDE_CODE)
|
|
63
|
+
return 'claude';
|
|
64
|
+
return null;
|
|
65
|
+
}
|