@ekkos/cli 1.2.14 โ 1.2.16
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/capture.js +0 -0
- package/dist/commands/run.js +29 -17
- package/dist/cron/promoter.js +0 -0
- package/dist/index.js +68 -5
- package/dist/utils/session-binding.d.ts +9 -2
- package/dist/utils/session-binding.js +41 -16
- package/dist/utils/state.d.ts +1 -1
- package/dist/utils/state.js +2 -2
- package/package.json +1 -1
- package/templates/CLAUDE.md +6 -5
- package/templates/hooks/stop.ps1 +43 -29
- package/templates/hooks/stop.sh +18 -14
- package/templates/hooks/user-prompt-submit.ps1 +121 -33
- package/templates/hooks/user-prompt-submit.sh +97 -94
package/dist/cache/capture.js
CHANGED
|
File without changes
|
package/dist/commands/run.js
CHANGED
|
@@ -439,7 +439,7 @@ let proxyModeEnabled = true;
|
|
|
439
439
|
/**
|
|
440
440
|
* Generate a unique session UUID and convert to human-readable name
|
|
441
441
|
* Each CLI invocation gets a NEW session (not tied to project path)
|
|
442
|
-
* Uses uuidToWords from state.ts for consistency
|
|
442
|
+
* Uses uuidToWords from state.ts for consistency across ekkOS components
|
|
443
443
|
*/
|
|
444
444
|
function generateCliSessionName() {
|
|
445
445
|
const sessionUuid = crypto.randomUUID();
|
|
@@ -469,12 +469,12 @@ function getEkkosEnv() {
|
|
|
469
469
|
env.EKKOS_PROXY_MODE = '1';
|
|
470
470
|
// Enable ultra-minimal mode by default (30%โ20% eviction for constant-cost infinite context)
|
|
471
471
|
env.EKKOS_ULTRA_MINIMAL = '1';
|
|
472
|
-
// Use
|
|
473
|
-
//
|
|
474
|
-
// The hook calls POST /proxy/session/bind with Claude's actual session name
|
|
472
|
+
// Use a unique pending scope until Claude exposes its real session UUID.
|
|
473
|
+
// run.ts binds this scope to the real session immediately on transcript detect.
|
|
475
474
|
if (!cliSessionName) {
|
|
476
|
-
|
|
477
|
-
|
|
475
|
+
const pendingToken = crypto.randomBytes(8).toString('hex');
|
|
476
|
+
cliSessionName = `_pending-${pendingToken}`;
|
|
477
|
+
cliSessionId = `pending-${pendingToken}`;
|
|
478
478
|
console.log(chalk_1.default.gray(` ๐ Session: pending (will bind to Claude session)`));
|
|
479
479
|
}
|
|
480
480
|
// Get full userId from config (NOT the truncated version from auth token)
|
|
@@ -1085,7 +1085,7 @@ async function run(options) {
|
|
|
1085
1085
|
}
|
|
1086
1086
|
// Check PTY availability early (deterministic, no async race)
|
|
1087
1087
|
// Windows: SKIP node-pty entirely. ConPTY corrupts Ink's cursor management sequences
|
|
1088
|
-
// (hide/show/move), causing ghost
|
|
1088
|
+
// (hide/show/move), causing ghost block cursor artifacts. Use stdio:'inherit' spawn
|
|
1089
1089
|
// instead so Ink gets direct terminal access. Hooks still handle session tracking.
|
|
1090
1090
|
const loadedPty = isWindows ? null : await loadPty();
|
|
1091
1091
|
const usePty = loadedPty !== null;
|
|
@@ -1496,13 +1496,17 @@ async function run(options) {
|
|
|
1496
1496
|
const sessionId = file.replace('.jsonl', '');
|
|
1497
1497
|
transcriptPath = fullPath;
|
|
1498
1498
|
currentSessionId = sessionId;
|
|
1499
|
+
currentSession = (0, state_1.uuidToWords)(sessionId);
|
|
1500
|
+
(0, state_1.updateCurrentProcessSession)(currentSessionId, currentSession);
|
|
1501
|
+
(0, state_1.updateState)({ sessionId: currentSessionId, sessionName: currentSession });
|
|
1502
|
+
bindRealSessionToProxy(currentSession, 'fast-transcript', currentSessionId);
|
|
1499
1503
|
dlog(`[TRANSCRIPT] FAST DETECT: New transcript found! ${fullPath}`);
|
|
1500
1504
|
evictionDebugLog('TRANSCRIPT_SET', 'Fast poll detected new file', {
|
|
1501
1505
|
transcriptPath,
|
|
1502
1506
|
currentSessionId,
|
|
1503
1507
|
elapsedMs: Date.now() - launchTime
|
|
1504
1508
|
});
|
|
1505
|
-
startStreamTailer(transcriptPath, currentSessionId);
|
|
1509
|
+
startStreamTailer(transcriptPath, currentSessionId, currentSession || undefined);
|
|
1506
1510
|
// Stop polling
|
|
1507
1511
|
if (transcriptPollInterval) {
|
|
1508
1512
|
clearInterval(transcriptPollInterval);
|
|
@@ -1516,10 +1520,6 @@ async function run(options) {
|
|
|
1516
1520
|
// Project dir doesn't exist yet, keep polling
|
|
1517
1521
|
}
|
|
1518
1522
|
}
|
|
1519
|
-
// Start polling immediately
|
|
1520
|
-
transcriptPollInterval = setInterval(pollForNewTranscript, 500);
|
|
1521
|
-
pollForNewTranscript(); // Also run once immediately
|
|
1522
|
-
dlog('[TRANSCRIPT] Fast polling started - looking for new jsonl files');
|
|
1523
1523
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
1524
1524
|
// SESSION NAME TRACKING (from live TUI output)
|
|
1525
1525
|
// Claude prints: "ยท Turn N ยท groovy-koala-saves ยท ๐
"
|
|
@@ -1561,6 +1561,10 @@ async function run(options) {
|
|
|
1561
1561
|
let turnEndTimeout = null;
|
|
1562
1562
|
const TURN_END_STABLE_MS = 500; // Must see idle prompt for 500ms
|
|
1563
1563
|
let pendingClearAfterEviction = false; // Flag to trigger /clear after eviction
|
|
1564
|
+
// Start polling after session-tracking state is initialized.
|
|
1565
|
+
transcriptPollInterval = setInterval(pollForNewTranscript, 500);
|
|
1566
|
+
pollForNewTranscript(); // Also run once immediately
|
|
1567
|
+
dlog('[TRANSCRIPT] Fast polling started - looking for new jsonl files');
|
|
1564
1568
|
// Debug log to eviction-debug.log for 400 error diagnosis
|
|
1565
1569
|
function evictionDebugLog(category, msg, data) {
|
|
1566
1570
|
try {
|
|
@@ -1615,22 +1619,30 @@ async function run(options) {
|
|
|
1615
1619
|
dlog(`[TRANSCRIPT] Resolved by session ID (${source}): ${candidate}`);
|
|
1616
1620
|
startStreamTailer(transcriptPath, currentSessionId, currentSession || undefined);
|
|
1617
1621
|
}
|
|
1618
|
-
function bindRealSessionToProxy(sessionName, source) {
|
|
1622
|
+
function bindRealSessionToProxy(sessionName, source, sessionIdHint) {
|
|
1619
1623
|
if (!proxyModeEnabled)
|
|
1620
1624
|
return;
|
|
1621
1625
|
if (!sessionName || sessionName === '_pending')
|
|
1622
1626
|
return;
|
|
1623
1627
|
if (boundProxySession === sessionName || bindingSessionInFlight === sessionName)
|
|
1624
1628
|
return;
|
|
1629
|
+
const pendingSession = cliSessionName?.startsWith('_pending') ? cliSessionName : undefined;
|
|
1630
|
+
const bindSessionId = sessionIdHint || currentSessionId || undefined;
|
|
1625
1631
|
bindingSessionInFlight = sessionName;
|
|
1626
1632
|
void (async () => {
|
|
1627
1633
|
const maxAttempts = 3;
|
|
1628
1634
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1629
|
-
const success = await (0, session_binding_1.bindSession)(
|
|
1635
|
+
const success = await (0, session_binding_1.bindSession)({
|
|
1636
|
+
realSession: sessionName,
|
|
1637
|
+
projectPath: process.cwd(),
|
|
1638
|
+
pendingSession,
|
|
1639
|
+
sessionId: bindSessionId,
|
|
1640
|
+
});
|
|
1630
1641
|
if (success) {
|
|
1631
1642
|
boundProxySession = sessionName;
|
|
1632
1643
|
bindingSessionInFlight = null;
|
|
1633
1644
|
cliSessionName = sessionName;
|
|
1645
|
+
cliSessionId = bindSessionId || cliSessionId;
|
|
1634
1646
|
dlog(`[SESSION_BIND] Bound ${sessionName} from ${source} (attempt ${attempt}/${maxAttempts})`);
|
|
1635
1647
|
return;
|
|
1636
1648
|
}
|
|
@@ -2329,7 +2341,7 @@ async function run(options) {
|
|
|
2329
2341
|
(0, state_1.updateState)({ sessionId: currentSessionId, sessionName: currentSession });
|
|
2330
2342
|
dlog(`Session detected from UUID: ${currentSession}`);
|
|
2331
2343
|
resolveTranscriptFromSessionId('session-id-from-output');
|
|
2332
|
-
bindRealSessionToProxy(currentSession, 'session-id-from-output');
|
|
2344
|
+
bindRealSessionToProxy(currentSession, 'session-id-from-output', currentSessionId || undefined);
|
|
2333
2345
|
}
|
|
2334
2346
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
2335
2347
|
// SESSION NAME DETECTION (PRIMARY METHOD)
|
|
@@ -2363,7 +2375,7 @@ async function run(options) {
|
|
|
2363
2375
|
// Also update global state for backwards compatibility
|
|
2364
2376
|
(0, state_1.updateState)({ sessionName: currentSession });
|
|
2365
2377
|
dlog(`Session detected from status line: ${currentSession} (observedSessionThisRun=true)`);
|
|
2366
|
-
bindRealSessionToProxy(currentSession, 'status-line');
|
|
2378
|
+
bindRealSessionToProxy(currentSession, 'status-line', currentSessionId || undefined);
|
|
2367
2379
|
resolveTranscriptFromSessionId('status-line');
|
|
2368
2380
|
}
|
|
2369
2381
|
}
|
|
@@ -2371,7 +2383,7 @@ async function run(options) {
|
|
|
2371
2383
|
// Same session, just update timestamp
|
|
2372
2384
|
lastSeenSessionAt = Date.now();
|
|
2373
2385
|
if (boundProxySession !== detectedSession) {
|
|
2374
|
-
bindRealSessionToProxy(detectedSession, 'status-line-refresh');
|
|
2386
|
+
bindRealSessionToProxy(detectedSession, 'status-line-refresh', currentSessionId || undefined);
|
|
2375
2387
|
}
|
|
2376
2388
|
}
|
|
2377
2389
|
}
|
package/dist/cron/promoter.js
CHANGED
|
File without changes
|
package/dist/index.js
CHANGED
|
@@ -51,6 +51,9 @@ const agent_1 = require("./commands/agent");
|
|
|
51
51
|
const state_1 = require("./utils/state");
|
|
52
52
|
const index_1 = require("./commands/usage/index");
|
|
53
53
|
const dashboard_1 = require("./commands/dashboard");
|
|
54
|
+
const swarm_1 = require("./commands/swarm");
|
|
55
|
+
const swarm_dashboard_1 = require("./commands/swarm-dashboard");
|
|
56
|
+
const swarm_setup_1 = require("./commands/swarm-setup");
|
|
54
57
|
const chalk_1 = __importDefault(require("chalk"));
|
|
55
58
|
const fs = __importStar(require("fs"));
|
|
56
59
|
const path = __importStar(require("path"));
|
|
@@ -76,7 +79,7 @@ commander_1.program
|
|
|
76
79
|
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos run -b')} ${chalk_1.default.gray('Launch with bypass permissions mode')}`,
|
|
77
80
|
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos doctor --fix')} ${chalk_1.default.gray('Check and auto-fix system prerequisites')}`,
|
|
78
81
|
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos usage daily')} ${chalk_1.default.gray("View today's token usage and costs")}`,
|
|
79
|
-
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos
|
|
82
|
+
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos swarm launch -t "build X"')} ${chalk_1.default.gray('Launch parallel workers on a task')}`,
|
|
80
83
|
'',
|
|
81
84
|
chalk_1.default.gray(' Run ') + chalk_1.default.white('ekkos <command> --help') + chalk_1.default.gray(' for detailed options on any command.'),
|
|
82
85
|
'',
|
|
@@ -154,7 +157,7 @@ commander_1.program
|
|
|
154
157
|
title: 'Running',
|
|
155
158
|
icon: 'โธ',
|
|
156
159
|
commands: [
|
|
157
|
-
{ name: 'run', desc: '
|
|
160
|
+
{ name: 'run', desc: 'Launch Claude Code with ekkOS memory + auto-continue', note: 'default' },
|
|
158
161
|
{ name: 'test-claude', desc: 'Launch Claude with proxy only (no ccDNA/PTY) for debugging' },
|
|
159
162
|
{ name: 'sessions', desc: 'List active Claude Code sessions' },
|
|
160
163
|
],
|
|
@@ -177,6 +180,13 @@ commander_1.program
|
|
|
177
180
|
{ name: 'agent', desc: 'Manage the remote terminal agent (start, stop, status, logs)' },
|
|
178
181
|
],
|
|
179
182
|
},
|
|
183
|
+
{
|
|
184
|
+
title: 'Swarm (Multi-Agent)',
|
|
185
|
+
icon: 'โธ',
|
|
186
|
+
commands: [
|
|
187
|
+
{ name: 'swarm', desc: 'Parallel workers, Q-learning routing, swarm dashboard' },
|
|
188
|
+
],
|
|
189
|
+
},
|
|
180
190
|
];
|
|
181
191
|
const padCmd = 18;
|
|
182
192
|
let output = '';
|
|
@@ -229,7 +239,7 @@ commander_1.program
|
|
|
229
239
|
// Run command - launches Claude with auto-continue wrapper
|
|
230
240
|
commander_1.program
|
|
231
241
|
.command('run')
|
|
232
|
-
.description('
|
|
242
|
+
.description('Launch Claude Code with auto-continue (auto /clear + /continue when context is high)')
|
|
233
243
|
.option('-s, --session <name>', 'Session name to restore on clear')
|
|
234
244
|
.option('-b, --bypass', 'Enable bypass permissions mode (dangerously skip all permission checks)')
|
|
235
245
|
.option('-v, --verbose', 'Show debug output')
|
|
@@ -342,10 +352,10 @@ hooksCmd
|
|
|
342
352
|
(0, index_1.registerUsageCommand)(commander_1.program);
|
|
343
353
|
// Dashboard command - live TUI for monitoring session usage
|
|
344
354
|
commander_1.program.addCommand(dashboard_1.dashboardCommand);
|
|
345
|
-
// Sessions command - list active Claude Code sessions
|
|
355
|
+
// Sessions command - list active Claude Code sessions (swarm support)
|
|
346
356
|
commander_1.program
|
|
347
357
|
.command('sessions')
|
|
348
|
-
.description('List active Claude Code sessions')
|
|
358
|
+
.description('List active Claude Code sessions (for swarm/multi-session support)')
|
|
349
359
|
.option('-j, --json', 'Output machine-readable JSON')
|
|
350
360
|
.action((options) => {
|
|
351
361
|
const sessions = (0, state_1.getActiveSessions)();
|
|
@@ -470,6 +480,59 @@ agentCmd
|
|
|
470
480
|
.action((options) => {
|
|
471
481
|
(0, agent_1.agentHealth)({ json: options.json });
|
|
472
482
|
});
|
|
483
|
+
// Swarm command - manage Q-learning routing
|
|
484
|
+
const swarmCmd = commander_1.program
|
|
485
|
+
.command('swarm')
|
|
486
|
+
.description('Manage Swarm Q-learning model routing');
|
|
487
|
+
swarmCmd
|
|
488
|
+
.command('status')
|
|
489
|
+
.description('Show Q-table stats (states, visits, epsilon, top actions)')
|
|
490
|
+
.action(swarm_1.swarmStatus);
|
|
491
|
+
swarmCmd
|
|
492
|
+
.command('reset')
|
|
493
|
+
.description('Clear Q-table from Redis (routing reverts to static rules)')
|
|
494
|
+
.action(swarm_1.swarmReset);
|
|
495
|
+
swarmCmd
|
|
496
|
+
.command('export')
|
|
497
|
+
.description('Export Q-table to .swarm/q-learning-model.json')
|
|
498
|
+
.action(swarm_1.swarmExport);
|
|
499
|
+
swarmCmd
|
|
500
|
+
.command('import')
|
|
501
|
+
.description('Import Q-table from .swarm/q-learning-model.json into Redis')
|
|
502
|
+
.action(swarm_1.swarmImport);
|
|
503
|
+
swarmCmd
|
|
504
|
+
.command('launch')
|
|
505
|
+
.description('Launch parallel workers on a decomposed task (opens wizard if --task is omitted)')
|
|
506
|
+
.option('-w, --workers <count>', 'Number of parallel workers (2-8)', parseInt)
|
|
507
|
+
.option('-t, --task <task>', 'Task description to decompose and execute')
|
|
508
|
+
.option('--no-bypass', 'Disable bypass permissions mode')
|
|
509
|
+
.option('--no-decompose', 'Skip AI decomposition (send same task to all workers)')
|
|
510
|
+
.option('--no-queen', 'Skip launching the Python Queen coordinator')
|
|
511
|
+
.option('--queen-strategy <strategy>', 'Queen strategy (adaptive-default, hierarchical-cascade, mesh-consensus)')
|
|
512
|
+
.option('-v, --verbose', 'Show debug output')
|
|
513
|
+
.action((options) => {
|
|
514
|
+
// Auto-open wizard when --task is missing
|
|
515
|
+
if (!options.task) {
|
|
516
|
+
(0, swarm_setup_1.swarmSetup)();
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
(0, swarm_1.swarmLaunch)({
|
|
520
|
+
workers: options.workers || 4,
|
|
521
|
+
task: options.task,
|
|
522
|
+
bypass: options.bypass !== false,
|
|
523
|
+
noDecompose: options.decompose === false,
|
|
524
|
+
noQueen: options.queen === false,
|
|
525
|
+
queenStrategy: options.queenStrategy,
|
|
526
|
+
verbose: options.verbose,
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
swarmCmd
|
|
530
|
+
.command('setup')
|
|
531
|
+
.description('Interactive TUI wizard for configuring and launching a swarm')
|
|
532
|
+
.action(() => {
|
|
533
|
+
(0, swarm_setup_1.swarmSetup)();
|
|
534
|
+
});
|
|
535
|
+
swarmCmd.addCommand(swarm_dashboard_1.swarmDashboardCommand);
|
|
473
536
|
// Handle `-help` (single dash) โ rewrite to `--help` for Commander compatibility
|
|
474
537
|
const helpIdx = process.argv.indexOf('-help');
|
|
475
538
|
if (helpIdx !== -1) {
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
interface BindSessionPayload {
|
|
2
|
+
realSession: string;
|
|
3
|
+
projectPath: string;
|
|
4
|
+
pendingSession?: string;
|
|
5
|
+
sessionId?: string;
|
|
6
|
+
}
|
|
1
7
|
/**
|
|
2
8
|
* Bind the real session name to the proxy
|
|
3
|
-
* This replaces the
|
|
9
|
+
* This replaces the pending placeholder for this user, enabling proper eviction.
|
|
4
10
|
*/
|
|
5
|
-
export declare function bindSession(realSession
|
|
11
|
+
export declare function bindSession({ realSession, projectPath, pendingSession, sessionId, }: BindSessionPayload): Promise<boolean>;
|
|
12
|
+
export {};
|
|
@@ -3,42 +3,67 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.bindSession = bindSession;
|
|
4
4
|
const state_1 = require("./state");
|
|
5
5
|
const MEMORY_API_URL = process.env.EKKOS_PROXY_URL || 'https://proxy.ekkos.dev';
|
|
6
|
+
function extractUserIdFromToken(token) {
|
|
7
|
+
const normalized = token.trim();
|
|
8
|
+
if (!normalized)
|
|
9
|
+
return undefined;
|
|
10
|
+
if (normalized.startsWith('ekk_')) {
|
|
11
|
+
const parts = normalized.split('_');
|
|
12
|
+
if (parts.length >= 2 && parts[1]) {
|
|
13
|
+
return parts[1];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const colonParts = normalized.split(':');
|
|
17
|
+
if (colonParts.length >= 2 && colonParts[0]) {
|
|
18
|
+
return colonParts[0];
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
6
22
|
/**
|
|
7
23
|
* Bind the real session name to the proxy
|
|
8
|
-
* This replaces the
|
|
24
|
+
* This replaces the pending placeholder for this user, enabling proper eviction.
|
|
9
25
|
*/
|
|
10
|
-
async function bindSession(realSession, projectPath) {
|
|
26
|
+
async function bindSession({ realSession, projectPath, pendingSession, sessionId, }) {
|
|
11
27
|
try {
|
|
12
28
|
// Get userId same way as run.ts
|
|
13
29
|
const config = (0, state_1.getConfig)();
|
|
30
|
+
const authToken = (0, state_1.getAuthToken)();
|
|
14
31
|
let userId = config?.userId;
|
|
15
32
|
if (!userId || userId === 'anonymous') {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const parts = token.split('_');
|
|
19
|
-
if (parts.length >= 2) {
|
|
20
|
-
userId = parts[1];
|
|
21
|
-
}
|
|
33
|
+
if (authToken) {
|
|
34
|
+
userId = extractUserIdFromToken(authToken);
|
|
22
35
|
}
|
|
23
36
|
}
|
|
24
|
-
if (!userId || userId === 'anonymous')
|
|
37
|
+
if (!userId || userId === 'anonymous' || !authToken)
|
|
25
38
|
return false;
|
|
39
|
+
const bindAuthToken = authToken.startsWith('ekk_')
|
|
40
|
+
? authToken
|
|
41
|
+
: `${userId}:${authToken}`;
|
|
42
|
+
const body = {
|
|
43
|
+
userId,
|
|
44
|
+
realSession,
|
|
45
|
+
projectPath,
|
|
46
|
+
};
|
|
47
|
+
if (pendingSession) {
|
|
48
|
+
body.pendingSession = pendingSession;
|
|
49
|
+
}
|
|
50
|
+
if (sessionId) {
|
|
51
|
+
body.sessionId = sessionId;
|
|
52
|
+
}
|
|
26
53
|
// Use global fetch (Node 18+)
|
|
27
54
|
const response = await fetch(`${MEMORY_API_URL}/proxy/session/bind`, {
|
|
28
55
|
method: 'POST',
|
|
29
56
|
signal: AbortSignal.timeout(2500),
|
|
30
57
|
headers: {
|
|
31
|
-
'Content-Type': 'application/json'
|
|
58
|
+
'Content-Type': 'application/json',
|
|
59
|
+
'x-ekkos-auth-token': bindAuthToken,
|
|
60
|
+
Authorization: `Bearer ${bindAuthToken}`,
|
|
32
61
|
},
|
|
33
|
-
body: JSON.stringify(
|
|
34
|
-
userId,
|
|
35
|
-
realSession,
|
|
36
|
-
projectPath
|
|
37
|
-
})
|
|
62
|
+
body: JSON.stringify(body),
|
|
38
63
|
});
|
|
39
64
|
return response.ok;
|
|
40
65
|
}
|
|
41
|
-
catch
|
|
66
|
+
catch {
|
|
42
67
|
// Fail silently - don't crash CLI
|
|
43
68
|
return false;
|
|
44
69
|
}
|
package/dist/utils/state.d.ts
CHANGED
|
@@ -80,7 +80,7 @@ export declare function getMostRecentSession(): {
|
|
|
80
80
|
*/
|
|
81
81
|
export declare function getActiveSessions(): ActiveSession[];
|
|
82
82
|
/**
|
|
83
|
-
* Register a new active session
|
|
83
|
+
* Register a new active session (for swarm tracking)
|
|
84
84
|
*/
|
|
85
85
|
export declare function registerActiveSession(sessionId: string, sessionName: string, projectPath: string): ActiveSession;
|
|
86
86
|
/**
|
package/dist/utils/state.js
CHANGED
|
@@ -244,7 +244,7 @@ function getMostRecentSession() {
|
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
247
|
-
// MULTI-SESSION SUPPORT
|
|
247
|
+
// MULTI-SESSION/SWARM SUPPORT
|
|
248
248
|
// Track multiple concurrent Claude Code sessions without state collision
|
|
249
249
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
250
250
|
/**
|
|
@@ -271,7 +271,7 @@ function getActiveSessions() {
|
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
273
|
/**
|
|
274
|
-
* Register a new active session
|
|
274
|
+
* Register a new active session (for swarm tracking)
|
|
275
275
|
*/
|
|
276
276
|
function registerActiveSession(sessionId, sessionName, projectPath) {
|
|
277
277
|
ensureEkkosDir();
|
package/package.json
CHANGED
package/templates/CLAUDE.md
CHANGED
|
@@ -364,20 +364,21 @@ Call `ekkOS_Directive` when user says:
|
|
|
364
364
|
**EVERY response MUST end with this footer:**
|
|
365
365
|
```
|
|
366
366
|
---
|
|
367
|
-
{IDE} ({Model}) ยท ๐ง **ekkOS_โข** ยท {SessionName} ยท ๐
{Timestamp}
|
|
367
|
+
{IDE} ({Model}) ยท ๐ง **ekkOS_โข** ยท Turn {N} ยท {SessionName} ยท ๐
{Timestamp}
|
|
368
368
|
```
|
|
369
369
|
|
|
370
370
|
**How to detect values:**
|
|
371
371
|
- **IDE**: Claude Code, Cursor, Windsurf, etc. (from environment)
|
|
372
372
|
- **Model**: Sonnet 4.5, Opus 4.5, etc. (from your model name)
|
|
373
|
+
- **Turn Number**: From hook header (e.g., "Turn 47") - starts at 0 for each new session
|
|
373
374
|
- **Session Name**: From hook header (e.g., "sol-gem-dig") - human-readable session identifier
|
|
374
|
-
- **Timestamp**: From hook header (accurate local time in EST
|
|
375
|
+
- **Timestamp**: From hook header (accurate local time in EST)
|
|
375
376
|
|
|
376
377
|
**Examples:**
|
|
377
|
-
- `Claude Code (
|
|
378
|
-
- `Cursor (Claude Sonnet 4) ยท ๐ง **ekkOS_โข** ยท bright-falcon-soars ยท ๐
2026-01-09 10:15 AM EST`
|
|
378
|
+
- `Claude Code (Sonnet 4.5) ยท ๐ง **ekkOS_โข** ยท Turn 12 ยท cosmic-penguin-runs ยท ๐
2026-01-09 4:50 PM EST`
|
|
379
|
+
- `Cursor (Claude Sonnet 4) ยท ๐ง **ekkOS_โข** ยท Turn 5 ยท bright-falcon-soars ยท ๐
2026-01-09 10:15 AM EST`
|
|
379
380
|
|
|
380
|
-
**The hook header shows:** `๐ง ekkOS Memory | {SessionName} | {timestamp}`
|
|
381
|
+
**The hook header shows:** `๐ง ekkOS Memory | Turn {N} | {Context%} | {SessionName} | {timestamp}`
|
|
381
382
|
|
|
382
383
|
---
|
|
383
384
|
|
package/templates/hooks/stop.ps1
CHANGED
|
@@ -12,6 +12,14 @@
|
|
|
12
12
|
|
|
13
13
|
$ErrorActionPreference = "SilentlyContinue"
|
|
14
14
|
|
|
15
|
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
16
|
+
# PROXY MODE DETECTION
|
|
17
|
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
18
|
+
$ProxyMode = $false
|
|
19
|
+
if ($env:ANTHROPIC_BASE_URL -and $env:ANTHROPIC_BASE_URL -like "*proxy.ekkos.dev*") {
|
|
20
|
+
$ProxyMode = $true
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
16
24
|
# CONFIG PATHS
|
|
17
25
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -87,6 +95,14 @@ function Convert-UuidToWords {
|
|
|
87
95
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
88
96
|
$inputJson = [Console]::In.ReadToEnd()
|
|
89
97
|
|
|
98
|
+
if ($ProxyMode) {
|
|
99
|
+
$projectState = Join-Path (Join-Path ((Get-Location).Path) ".claude\state") "hook-state.json"
|
|
100
|
+
$globalState = Join-Path $env:USERPROFILE ".claude\state\hook-state.json"
|
|
101
|
+
if (Test-Path $projectState) { Remove-Item $projectState -Force }
|
|
102
|
+
if (Test-Path $globalState) { Remove-Item $globalState -Force }
|
|
103
|
+
exit 0
|
|
104
|
+
}
|
|
105
|
+
|
|
90
106
|
# Get session ID from state
|
|
91
107
|
$stateFile = Join-Path $env:USERPROFILE ".claude\state\hook-state.json"
|
|
92
108
|
$sessionFile = Join-Path $env:USERPROFILE ".claude\state\current-session.json"
|
|
@@ -110,39 +126,37 @@ if ($rawSessionId -eq "unknown" -and (Test-Path $sessionFile)) {
|
|
|
110
126
|
|
|
111
127
|
$sessionName = Convert-UuidToWords $rawSessionId
|
|
112
128
|
|
|
129
|
+
function Get-PendingSessionFromProxyBaseUrl {
|
|
130
|
+
param([string]$baseUrl)
|
|
131
|
+
if (-not $baseUrl) { return $null }
|
|
132
|
+
try {
|
|
133
|
+
$uri = [System.Uri]$baseUrl
|
|
134
|
+
$segments = $uri.AbsolutePath.Trim('/') -split '/'
|
|
135
|
+
if (-not $segments -or $segments.Count -lt 3) { return $null }
|
|
136
|
+
$proxyIndex = [Array]::IndexOf($segments, 'proxy')
|
|
137
|
+
if ($proxyIndex -lt 0 -or $segments.Count -le ($proxyIndex + 2)) { return $null }
|
|
138
|
+
$candidate = [System.Uri]::UnescapeDataString($segments[$proxyIndex + 2])
|
|
139
|
+
if ($candidate -eq '_pending' -or $candidate -eq 'pending' -or $candidate.StartsWith('_pending-')) {
|
|
140
|
+
return $candidate
|
|
141
|
+
}
|
|
142
|
+
} catch {}
|
|
143
|
+
return $null
|
|
144
|
+
}
|
|
145
|
+
# Session binding removed โ proxy now self-resolves _pending โ word-based
|
|
146
|
+
# names inline via uuidToSessionName(). No external bind call needed.
|
|
147
|
+
|
|
113
148
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
114
|
-
#
|
|
115
|
-
# Windows has no PTY so run.ts can't detect the session name. The stop hook
|
|
116
|
-
# is the first place we have a confirmed session name, so we bind here.
|
|
117
|
-
# Mac does this in stop.sh (lines 171-179). Logic is identical.
|
|
149
|
+
# AUTH LOADING (direct mode only)
|
|
118
150
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
151
|
+
$authToken = $null
|
|
152
|
+
$userId = $null
|
|
119
153
|
$configFile = Join-Path $EkkosConfigDir "config.json"
|
|
120
|
-
if (
|
|
154
|
+
if (Test-Path $configFile) {
|
|
121
155
|
try {
|
|
122
|
-
$
|
|
123
|
-
$
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if ($userId -and $authToken) {
|
|
127
|
-
$projectPath = (Get-Location).Path
|
|
128
|
-
$pendingSession = if ($env:EKKOS_PENDING_SESSION) { $env:EKKOS_PENDING_SESSION } else { "_pending" }
|
|
129
|
-
|
|
130
|
-
$projectPath = $projectPath -replace '\\', '/'
|
|
131
|
-
$bindBody = @{
|
|
132
|
-
userId = $userId
|
|
133
|
-
realSession = $sessionName
|
|
134
|
-
projectPath = $projectPath
|
|
135
|
-
pendingSession = $pendingSession
|
|
136
|
-
} | ConvertTo-Json -Depth 10
|
|
137
|
-
|
|
138
|
-
Start-Job -ScriptBlock {
|
|
139
|
-
param($body, $token)
|
|
140
|
-
Invoke-RestMethod -Uri "https://mcp.ekkos.dev/proxy/session/bind" `
|
|
141
|
-
-Method POST `
|
|
142
|
-
-Headers @{ "Content-Type" = "application/json" } `
|
|
143
|
-
-Body ([System.Text.Encoding]::UTF8.GetBytes($body)) -ErrorAction SilentlyContinue | Out-Null
|
|
144
|
-
} -ArgumentList $bindBody, $authToken | Out-Null
|
|
145
|
-
}
|
|
156
|
+
$cfg = Get-Content $configFile -Raw | ConvertFrom-Json
|
|
157
|
+
$authToken = $cfg.hookApiKey
|
|
158
|
+
if (-not $authToken) { $authToken = $cfg.apiKey }
|
|
159
|
+
$userId = $cfg.userId
|
|
146
160
|
} catch {}
|
|
147
161
|
}
|
|
148
162
|
|
package/templates/hooks/stop.sh
CHANGED
|
@@ -18,6 +18,14 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
18
18
|
PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
|
19
19
|
STATE_DIR="$PROJECT_ROOT/.claude/state"
|
|
20
20
|
|
|
21
|
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
22
|
+
# PROXY MODE DETECTION
|
|
23
|
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
24
|
+
PROXY_MODE=false
|
|
25
|
+
if [[ "$ANTHROPIC_BASE_URL" == *"proxy.ekkos.dev"* ]]; then
|
|
26
|
+
PROXY_MODE=true
|
|
27
|
+
fi
|
|
28
|
+
|
|
21
29
|
mkdir -p "$STATE_DIR" 2>/dev/null
|
|
22
30
|
|
|
23
31
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -53,6 +61,14 @@ TRANSCRIPT_PATH=$(parse_json_value "$INPUT" '.transcript_path')
|
|
|
53
61
|
MODEL_USED=$(parse_json_value "$INPUT" '.model')
|
|
54
62
|
[ -z "$MODEL_USED" ] && MODEL_USED="claude-sonnet-4-5"
|
|
55
63
|
|
|
64
|
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
65
|
+
# PROXY MODE: Slim hook path (local cleanup only)
|
|
66
|
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
67
|
+
if [ "$PROXY_MODE" = "true" ]; then
|
|
68
|
+
rm -f "$STATE_DIR/hook-state.json" "$HOME/.claude/state/hook-state.json" 2>/dev/null || true
|
|
69
|
+
exit 0
|
|
70
|
+
fi
|
|
71
|
+
|
|
56
72
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
57
73
|
# Session ID
|
|
58
74
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -163,20 +179,8 @@ fi
|
|
|
163
179
|
|
|
164
180
|
MEMORY_API_URL="https://mcp.ekkos.dev"
|
|
165
181
|
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
# The CLI may be in spawn pass-through mode (no PTY = blind to TUI output),
|
|
169
|
-
# so the stop hook (which IS sighted) must bind the session.
|
|
170
|
-
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
171
|
-
if [ -n "$SESSION_NAME" ] && [ "$SESSION_NAME" != "unknown-session-starts" ] && [ -n "$USER_ID" ]; then
|
|
172
|
-
PROJECT_PATH_FOR_BIND=$(pwd)
|
|
173
|
-
PENDING_SESSION_FOR_BIND="${EKKOS_PENDING_SESSION:-_pending}"
|
|
174
|
-
curl -s -X POST "$MEMORY_API_URL/proxy/session/bind" \
|
|
175
|
-
-H "Content-Type: application/json" \
|
|
176
|
-
-d "{\"userId\":\"$USER_ID\",\"realSession\":\"$SESSION_NAME\",\"projectPath\":\"$PROJECT_PATH_FOR_BIND\",\"pendingSession\":\"$PENDING_SESSION_FOR_BIND\"}" \
|
|
177
|
-
--connect-timeout 1 \
|
|
178
|
-
--max-time 2 >/dev/null 2>&1 &
|
|
179
|
-
fi
|
|
182
|
+
# Session binding removed โ proxy now self-resolves _pending โ word-based
|
|
183
|
+
# names inline via uuidToSessionName(). No external bind call needed.
|
|
180
184
|
|
|
181
185
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
182
186
|
# EVICTION: Handled by IPC (In-Place Progressive Compression) in the proxy.
|
|
@@ -12,6 +12,14 @@
|
|
|
12
12
|
|
|
13
13
|
$ErrorActionPreference = "SilentlyContinue"
|
|
14
14
|
|
|
15
|
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
16
|
+
# PROXY MODE DETECTION
|
|
17
|
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
18
|
+
$ProxyMode = $false
|
|
19
|
+
if ($env:ANTHROPIC_BASE_URL -and $env:ANTHROPIC_BASE_URL -like "*proxy.ekkos.dev*") {
|
|
20
|
+
$ProxyMode = $true
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
16
24
|
# CONFIG PATHS - No hardcoded word arrays per spec v1.2 Addendum
|
|
17
25
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -77,6 +85,100 @@ if ($rawSessionId -eq "unknown") {
|
|
|
77
85
|
}
|
|
78
86
|
}
|
|
79
87
|
|
|
88
|
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
89
|
+
# PROXY MODE: Slim hook path (cosmetic output + local state only)
|
|
90
|
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
91
|
+
if ($ProxyMode) {
|
|
92
|
+
function Get-ProxySessionName {
|
|
93
|
+
param([string]$sessionId)
|
|
94
|
+
if (-not $sessionId -or $sessionId -eq "unknown") { return "unknown-session" }
|
|
95
|
+
if ($sessionId -notmatch '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$') {
|
|
96
|
+
return $sessionId
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (-not $script:SessionWords) { Load-SessionWords }
|
|
100
|
+
if (-not $script:SessionWords) { return $sessionId }
|
|
101
|
+
|
|
102
|
+
$adjectives = $script:SessionWords.adjectives
|
|
103
|
+
$nouns = $script:SessionWords.nouns
|
|
104
|
+
$verbs = $script:SessionWords.verbs
|
|
105
|
+
if (-not $adjectives -or -not $nouns -or -not $verbs) { return $sessionId }
|
|
106
|
+
|
|
107
|
+
$clean = $sessionId -replace "-", ""
|
|
108
|
+
if ($clean.Length -lt 12) { return $sessionId }
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
$adj = [Convert]::ToInt32($clean.Substring(0, 4), 16) % $adjectives.Length
|
|
112
|
+
$noun = [Convert]::ToInt32($clean.Substring(4, 4), 16) % $nouns.Length
|
|
113
|
+
$verb = [Convert]::ToInt32($clean.Substring(8, 4), 16) % $verbs.Length
|
|
114
|
+
return "$($adjectives[$adj])-$($nouns[$noun])-$($verbs[$verb])"
|
|
115
|
+
} catch {
|
|
116
|
+
return $sessionId
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
$sessionName = Get-ProxySessionName $rawSessionId
|
|
121
|
+
$timestampUtc = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
|
|
122
|
+
$displayTime = (Get-Date).ToString("yyyy-MM-dd hh:mm:ss tt zzz")
|
|
123
|
+
$projectPath = if ($env:PWD) { $env:PWD } else { (Get-Location).Path }
|
|
124
|
+
|
|
125
|
+
New-Item -ItemType Directory -Path $EkkosConfigDir -Force | Out-Null
|
|
126
|
+
New-Item -ItemType Directory -Path (Join-Path $env:USERPROFILE ".claude\state") -Force | Out-Null
|
|
127
|
+
|
|
128
|
+
$globalState = @{
|
|
129
|
+
session_id = $rawSessionId
|
|
130
|
+
session_name = $sessionName
|
|
131
|
+
project = $projectPath
|
|
132
|
+
timestamp = $timestampUtc
|
|
133
|
+
} | ConvertTo-Json -Depth 10 -Compress
|
|
134
|
+
Set-Content -Path (Join-Path $EkkosConfigDir "current-session.json") -Value $globalState -Force
|
|
135
|
+
|
|
136
|
+
$localState = @{
|
|
137
|
+
session_id = $rawSessionId
|
|
138
|
+
session_name = $sessionName
|
|
139
|
+
timestamp = $timestampUtc
|
|
140
|
+
} | ConvertTo-Json -Depth 10 -Compress
|
|
141
|
+
Set-Content -Path (Join-Path $env:USERPROFILE ".claude\state\current-session.json") -Value $localState -Force
|
|
142
|
+
|
|
143
|
+
$activeSessionsPath = Join-Path $EkkosConfigDir "active-sessions.json"
|
|
144
|
+
$sessions = @()
|
|
145
|
+
if (Test-Path $activeSessionsPath) {
|
|
146
|
+
try { $sessions = @(Get-Content $activeSessionsPath -Raw | ConvertFrom-Json) } catch { $sessions = @() }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
$entry = @{
|
|
150
|
+
sessionId = $rawSessionId
|
|
151
|
+
sessionName = $sessionName
|
|
152
|
+
projectPath = $projectPath
|
|
153
|
+
startedAt = $timestampUtc
|
|
154
|
+
lastHeartbeat = $timestampUtc
|
|
155
|
+
pid = 0
|
|
156
|
+
}
|
|
157
|
+
$idx = -1
|
|
158
|
+
for ($i = 0; $i -lt $sessions.Count; $i++) {
|
|
159
|
+
if ($sessions[$i].sessionId -eq $rawSessionId) { $idx = $i; break }
|
|
160
|
+
}
|
|
161
|
+
if ($idx -ge 0) {
|
|
162
|
+
$entry.startedAt = if ($sessions[$idx].startedAt) { $sessions[$idx].startedAt } else { $timestampUtc }
|
|
163
|
+
$sessions[$idx] = $entry
|
|
164
|
+
} else {
|
|
165
|
+
$sessions += $entry
|
|
166
|
+
}
|
|
167
|
+
Set-Content -Path $activeSessionsPath -Value ($sessions | ConvertTo-Json -Depth 10) -Force
|
|
168
|
+
|
|
169
|
+
$hint = @{
|
|
170
|
+
sessionName = $sessionName
|
|
171
|
+
sessionId = $rawSessionId
|
|
172
|
+
projectPath = $projectPath
|
|
173
|
+
ts = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
|
|
174
|
+
} | ConvertTo-Json -Depth 10 -Compress
|
|
175
|
+
Set-Content -Path (Join-Path $EkkosConfigDir "hook-session-hint.json") -Value $hint -Force
|
|
176
|
+
|
|
177
|
+
$esc = [char]27
|
|
178
|
+
Write-Output "${esc}[0;36m${esc}[1m๐ง ekkOS Memory${esc}[0m ${esc}[2m| $sessionName | $displayTime${esc}[0m"
|
|
179
|
+
exit 0
|
|
180
|
+
}
|
|
181
|
+
|
|
80
182
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
81
183
|
# INTELLIGENT TOOL ROUTER: Multi-trigger skill detection
|
|
82
184
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -139,39 +241,24 @@ function Convert-UuidToWords {
|
|
|
139
241
|
|
|
140
242
|
$sessionName = Convert-UuidToWords $rawSessionId
|
|
141
243
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
$bindBody = @{
|
|
158
|
-
userId = $userId
|
|
159
|
-
realSession = $sessionName
|
|
160
|
-
projectPath = $projectPath
|
|
161
|
-
pendingSession = $pendingSession
|
|
162
|
-
} | ConvertTo-Json -Depth 10 -Compress
|
|
163
|
-
|
|
164
|
-
Start-Job -ScriptBlock {
|
|
165
|
-
param($body)
|
|
166
|
-
Invoke-RestMethod -Uri "https://mcp.ekkos.dev/proxy/session/bind" `
|
|
167
|
-
-Method POST `
|
|
168
|
-
-Headers @{ "Content-Type" = "application/json" } `
|
|
169
|
-
-Body ([System.Text.Encoding]::UTF8.GetBytes($body)) -ErrorAction SilentlyContinue | Out-Null
|
|
170
|
-
} -ArgumentList $bindBody | Out-Null
|
|
171
|
-
}
|
|
172
|
-
} catch {}
|
|
173
|
-
}
|
|
244
|
+
function Get-PendingSessionFromProxyBaseUrl {
|
|
245
|
+
param([string]$baseUrl)
|
|
246
|
+
if (-not $baseUrl) { return $null }
|
|
247
|
+
try {
|
|
248
|
+
$uri = [System.Uri]$baseUrl
|
|
249
|
+
$segments = $uri.AbsolutePath.Trim('/') -split '/'
|
|
250
|
+
if (-not $segments -or $segments.Count -lt 3) { return $null }
|
|
251
|
+
$proxyIndex = [Array]::IndexOf($segments, 'proxy')
|
|
252
|
+
if ($proxyIndex -lt 0 -or $segments.Count -le ($proxyIndex + 2)) { return $null }
|
|
253
|
+
$candidate = [System.Uri]::UnescapeDataString($segments[$proxyIndex + 2])
|
|
254
|
+
if ($candidate -eq '_pending' -or $candidate -eq 'pending' -or $candidate.StartsWith('_pending-')) {
|
|
255
|
+
return $candidate
|
|
256
|
+
}
|
|
257
|
+
} catch {}
|
|
258
|
+
return $null
|
|
174
259
|
}
|
|
260
|
+
# Session binding removed โ proxy now self-resolves _pending โ word-based
|
|
261
|
+
# names inline via uuidToSessionName(). No external bind call needed.
|
|
175
262
|
|
|
176
263
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
177
264
|
# SESSION CURRENT: Update Redis with current session name
|
|
@@ -325,7 +412,8 @@ if ($skillReminders.Count -gt 0) {
|
|
|
325
412
|
|
|
326
413
|
$output += @"
|
|
327
414
|
|
|
328
|
-
<footer-format>End responses with: Claude Code ({Model}) ยท ๐ง ekkOS_โข ยท $sessionName ยท
|
|
415
|
+
<footer-format>End responses with: Claude Code ({Model}) ยท ๐ง ekkOS_โข ยท $sessionName ยท $timestamp</footer-format>
|
|
416
|
+
<footer-note>Do not include a turn counter in the footer.</footer-note>
|
|
329
417
|
"@
|
|
330
418
|
|
|
331
419
|
Write-Output $output
|
|
@@ -13,6 +13,14 @@ set +e
|
|
|
13
13
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
14
|
PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
|
15
15
|
|
|
16
|
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
17
|
+
# PROXY MODE DETECTION
|
|
18
|
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
19
|
+
PROXY_MODE=false
|
|
20
|
+
if [[ "$ANTHROPIC_BASE_URL" == *"proxy.ekkos.dev"* ]]; then
|
|
21
|
+
PROXY_MODE=true
|
|
22
|
+
fi
|
|
23
|
+
|
|
16
24
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
17
25
|
# CONFIG PATHS - No jq dependency (v1.2 spec)
|
|
18
26
|
# Session words live in ~/.ekkos/ so they work in ANY project
|
|
@@ -64,6 +72,92 @@ if [ "$RAW_SESSION_ID" = "unknown" ] || [ "$RAW_SESSION_ID" = "null" ] || [ -z "
|
|
|
64
72
|
fi
|
|
65
73
|
fi
|
|
66
74
|
|
|
75
|
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
76
|
+
# PROXY MODE: Slim hook path (cosmetic output + local state only)
|
|
77
|
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
78
|
+
if [ "$PROXY_MODE" = "true" ]; then
|
|
79
|
+
SESSION_ID="$RAW_SESSION_ID"
|
|
80
|
+
UUID_REGEX='^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
|
|
81
|
+
SESSION_NAME="$SESSION_ID"
|
|
82
|
+
|
|
83
|
+
if [[ "$SESSION_ID" =~ $UUID_REGEX ]] && [ -f "$JSON_PARSE_HELPER" ]; then
|
|
84
|
+
SESSION_NAME=$(node -e "
|
|
85
|
+
const fs = require('fs');
|
|
86
|
+
const sid = process.argv[1] || '';
|
|
87
|
+
const wordsFile = process.argv[2];
|
|
88
|
+
const fallbackFile = process.argv[3];
|
|
89
|
+
const helper = process.argv[4];
|
|
90
|
+
let wordsPath = wordsFile;
|
|
91
|
+
if (!fs.existsSync(wordsPath)) wordsPath = fallbackFile;
|
|
92
|
+
if (!fs.existsSync(wordsPath) || !fs.existsSync(helper)) {
|
|
93
|
+
console.log(sid || 'unknown-session');
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
const cp = require('child_process');
|
|
97
|
+
function readList(path) {
|
|
98
|
+
const out = cp.spawnSync('node', [helper, wordsPath, path], { encoding: 'utf8' });
|
|
99
|
+
if (out.status !== 0) return [];
|
|
100
|
+
return (out.stdout || '').split('\\n').map(s => s.trim()).filter(Boolean);
|
|
101
|
+
}
|
|
102
|
+
const adjectives = readList('.adjectives');
|
|
103
|
+
const nouns = readList('.nouns');
|
|
104
|
+
const verbs = readList('.verbs');
|
|
105
|
+
if (!adjectives.length || !nouns.length || !verbs.length) {
|
|
106
|
+
console.log(sid);
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}
|
|
109
|
+
const hex = sid.replace(/-/g, '').slice(0, 12);
|
|
110
|
+
if (!/^[0-9a-fA-F]{12}$/.test(hex)) {
|
|
111
|
+
console.log(sid);
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
114
|
+
const adj = parseInt(hex.slice(0, 4), 16) % adjectives.length;
|
|
115
|
+
const noun = parseInt(hex.slice(4, 8), 16) % nouns.length;
|
|
116
|
+
const verb = parseInt(hex.slice(8, 12), 16) % verbs.length;
|
|
117
|
+
console.log(adjectives[adj] + '-' + nouns[noun] + '-' + verbs[verb]);
|
|
118
|
+
" "$SESSION_ID" "$SESSION_WORDS_JSON" "$SESSION_WORDS_DEFAULT" "$JSON_PARSE_HELPER" 2>/dev/null || echo "$SESSION_ID")
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
TIMESTAMP_UTC=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
122
|
+
DISPLAY_TIME=$(date "+%Y-%m-%d %I:%M:%S %p %Z")
|
|
123
|
+
mkdir -p "$HOME/.ekkos" "$HOME/.claude/state" 2>/dev/null || true
|
|
124
|
+
echo "{\"session_id\":\"$SESSION_ID\",\"session_name\":\"$SESSION_NAME\",\"project\":\"$PROJECT_ROOT\",\"timestamp\":\"$TIMESTAMP_UTC\"}" > "$HOME/.ekkos/current-session.json" 2>/dev/null || true
|
|
125
|
+
echo "{\"session_id\":\"$SESSION_ID\",\"session_name\":\"$SESSION_NAME\",\"timestamp\":\"$TIMESTAMP_UTC\"}" > "$HOME/.claude/state/current-session.json" 2>/dev/null || true
|
|
126
|
+
|
|
127
|
+
ACTIVE_SESSIONS_FILE="$HOME/.ekkos/active-sessions.json"
|
|
128
|
+
node -e "
|
|
129
|
+
const fs = require('fs');
|
|
130
|
+
const file = process.argv[1];
|
|
131
|
+
const sid = process.argv[2];
|
|
132
|
+
const sname = process.argv[3];
|
|
133
|
+
const ts = process.argv[4];
|
|
134
|
+
const project = process.argv[5];
|
|
135
|
+
let sessions = [];
|
|
136
|
+
try {
|
|
137
|
+
if (fs.existsSync(file)) {
|
|
138
|
+
sessions = JSON.parse(fs.readFileSync(file, 'utf8') || '[]');
|
|
139
|
+
}
|
|
140
|
+
} catch {}
|
|
141
|
+
const index = sessions.findIndex(s => s.sessionId === sid);
|
|
142
|
+
if (index >= 0) {
|
|
143
|
+
sessions[index] = { ...sessions[index], sessionName: sname, projectPath: project, lastHeartbeat: ts };
|
|
144
|
+
} else {
|
|
145
|
+
sessions.push({ sessionId: sid, sessionName: sname, projectPath: project, startedAt: ts, lastHeartbeat: ts, pid: 0 });
|
|
146
|
+
}
|
|
147
|
+
fs.writeFileSync(file, JSON.stringify(sessions, null, 2));
|
|
148
|
+
" "$ACTIVE_SESSIONS_FILE" "$SESSION_ID" "$SESSION_NAME" "$TIMESTAMP_UTC" "$PROJECT_ROOT" 2>/dev/null || true
|
|
149
|
+
|
|
150
|
+
# Dashboard/local tooling hint file
|
|
151
|
+
echo "{\"sessionName\":\"$SESSION_NAME\",\"sessionId\":\"$SESSION_ID\",\"projectPath\":\"$PROJECT_ROOT\",\"ts\":$(date +%s)000}" > "$HOME/.ekkos/hook-session-hint.json" 2>/dev/null || true
|
|
152
|
+
|
|
153
|
+
CYAN='\033[0;36m'
|
|
154
|
+
DIM='\033[2m'
|
|
155
|
+
BOLD='\033[1m'
|
|
156
|
+
RESET='\033[0m'
|
|
157
|
+
echo -e "${CYAN}${BOLD}๐ง ekkOS Memory${RESET} ${DIM}| ${SESSION_NAME} | ${DISPLAY_TIME}${RESET}"
|
|
158
|
+
exit 0
|
|
159
|
+
fi
|
|
160
|
+
|
|
67
161
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
68
162
|
# SKILL AUTO-FIRE: Detect keywords and inject skill reminders
|
|
69
163
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -138,7 +232,6 @@ echo "{\"session_id\": \"$SESSION_ID\", \"timestamp\": \"$(date -u +%Y-%m-%dT%H:
|
|
|
138
232
|
PROJECT_SESSION_DIR="$STATE_DIR/sessions"
|
|
139
233
|
mkdir -p "$PROJECT_SESSION_DIR" 2>/dev/null || true
|
|
140
234
|
TURN_COUNTER_FILE="$PROJECT_SESSION_DIR/${SESSION_ID}.turn"
|
|
141
|
-
CONTEXT_SIZE_FILE="$PROJECT_SESSION_DIR/${SESSION_ID}.context"
|
|
142
235
|
|
|
143
236
|
# Count API round-trips from transcript to match TUI turn counter
|
|
144
237
|
TURN_NUMBER=1
|
|
@@ -159,28 +252,7 @@ fi
|
|
|
159
252
|
|
|
160
253
|
echo "$TURN_NUMBER" > "$TURN_COUNTER_FILE"
|
|
161
254
|
|
|
162
|
-
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
163
|
-
# Context size tracking - Uses tokenizer script (single source)
|
|
164
|
-
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
165
|
-
PREV_CONTEXT_PERCENT=0
|
|
166
|
-
[ -f "$CONTEXT_SIZE_FILE" ] && PREV_CONTEXT_PERCENT=$(cat "$CONTEXT_SIZE_FILE" 2>/dev/null || echo "0")
|
|
167
|
-
|
|
168
|
-
TOKEN_PERCENT=0
|
|
169
|
-
MAX_TOKENS=200000
|
|
170
|
-
TOKENIZER_SCRIPT="$SCRIPT_DIR/lib/count-tokens.cjs"
|
|
171
|
-
|
|
172
|
-
if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ] && [ -f "$TOKENIZER_SCRIPT" ]; then
|
|
173
|
-
TOKEN_COUNT=$(node "$TOKENIZER_SCRIPT" "$TRANSCRIPT_PATH" 2>/dev/null || echo "0")
|
|
174
|
-
if [[ "$TOKEN_COUNT" =~ ^[0-9]+$ ]] && [ "$TOKEN_COUNT" -gt 0 ]; then
|
|
175
|
-
TOKEN_PERCENT=$((TOKEN_COUNT * 100 / MAX_TOKENS))
|
|
176
|
-
[ "$TOKEN_PERCENT" -gt 100 ] && TOKEN_PERCENT=100
|
|
177
|
-
# In proxy mode, IPC compresses ~65-70% โ show estimated post-compression %
|
|
178
|
-
IPC_PERCENT=$((TOKEN_PERCENT * 30 / 100))
|
|
179
|
-
[ "$IPC_PERCENT" -lt 1 ] && IPC_PERCENT=1
|
|
180
|
-
fi
|
|
181
|
-
fi
|
|
182
255
|
|
|
183
|
-
echo "$TOKEN_PERCENT" > "$CONTEXT_SIZE_FILE"
|
|
184
256
|
|
|
185
257
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
186
258
|
# COLORS
|
|
@@ -311,32 +383,8 @@ try {
|
|
|
311
383
|
--max-time 3 >/dev/null 2>&1 &
|
|
312
384
|
fi
|
|
313
385
|
|
|
314
|
-
#
|
|
315
|
-
|
|
316
|
-
if [ -f "$HOME/.ekkos/config.json" ] && [ -f "$JSON_PARSE_HELPER" ]; then
|
|
317
|
-
EKKOS_USER_ID=$(node "$JSON_PARSE_HELPER" "$HOME/.ekkos/config.json" '.userId' 2>/dev/null || echo "")
|
|
318
|
-
fi
|
|
319
|
-
if [ -n "$EKKOS_USER_ID" ] && [ -n "$SESSION_NAME" ]; then
|
|
320
|
-
PENDING_SESSION="${EKKOS_PENDING_SESSION:-_pending}"
|
|
321
|
-
node -e "
|
|
322
|
-
const https = require('https');
|
|
323
|
-
const body = JSON.stringify({
|
|
324
|
-
userId: process.argv[1],
|
|
325
|
-
realSession: process.argv[2],
|
|
326
|
-
projectPath: process.argv[3],
|
|
327
|
-
pendingSession: process.argv[4]
|
|
328
|
-
});
|
|
329
|
-
const req = https.request({
|
|
330
|
-
hostname: 'mcp.ekkos.dev',
|
|
331
|
-
path: '/proxy/session/bind',
|
|
332
|
-
method: 'POST',
|
|
333
|
-
headers: {'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body)}
|
|
334
|
-
}, () => {});
|
|
335
|
-
req.on('error', () => {});
|
|
336
|
-
req.write(body);
|
|
337
|
-
req.end();
|
|
338
|
-
" "$EKKOS_USER_ID" "$SESSION_NAME" "$PROJECT_ROOT" "$PENDING_SESSION" 2>/dev/null &
|
|
339
|
-
fi
|
|
386
|
+
# Session binding removed โ proxy now self-resolves _pending โ word-based
|
|
387
|
+
# names inline via uuidToSessionName(). No external bind call needed.
|
|
340
388
|
fi
|
|
341
389
|
|
|
342
390
|
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -410,49 +458,7 @@ turns.slice(0, -1).forEach(t => {
|
|
|
410
458
|
fi
|
|
411
459
|
fi
|
|
412
460
|
|
|
413
|
-
|
|
414
|
-
# COMPACTION DETECTION: If context dropped dramatically, auto-restore
|
|
415
|
-
# Was >50% last turn, now <15% = compaction happened
|
|
416
|
-
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
417
|
-
if [ "$PREV_CONTEXT_PERCENT" -gt 50 ] && [ "$TOKEN_PERCENT" -lt 15 ] && [ -n "$AUTH_TOKEN" ]; then
|
|
418
|
-
echo ""
|
|
419
|
-
echo -e "${GREEN}${BOLD}๐ CONTEXT RESTORED${RESET} ${DIM}| Compaction detected | Auto-loading recent turns...${RESET}"
|
|
420
|
-
|
|
421
|
-
RESTORE_RESPONSE=$(curl -s -X POST "$MEMORY_API_URL/api/v1/turns/recall" \
|
|
422
|
-
-H "Authorization: Bearer $AUTH_TOKEN" \
|
|
423
|
-
-H "Content-Type: application/json" \
|
|
424
|
-
-d "{\"session_id\": \"${SESSION_ID}\", \"last_n\": 10, \"format\": \"summary\"}" \
|
|
425
|
-
--connect-timeout 3 \
|
|
426
|
-
--max-time 5 2>/dev/null || echo '{"turns":[]}')
|
|
427
|
-
|
|
428
|
-
RESTORED_COUNT=$(echo "$RESTORE_RESPONSE" | node -e "
|
|
429
|
-
const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
|
|
430
|
-
console.log((d.turns || []).length);
|
|
431
|
-
" 2>/dev/null || echo "0")
|
|
432
|
-
|
|
433
|
-
if [ "$RESTORED_COUNT" -gt 0 ]; then
|
|
434
|
-
echo -e "${GREEN} โ${RESET} Restored ${RESTORED_COUNT} turns from Layer 2"
|
|
435
|
-
echo ""
|
|
436
|
-
echo -e "${MAGENTA}${BOLD}## Recent Context (auto-restored)${RESET}"
|
|
437
|
-
echo ""
|
|
438
|
-
|
|
439
|
-
echo "$RESTORE_RESPONSE" | node -e "
|
|
440
|
-
const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
|
|
441
|
-
(d.turns || []).forEach(t => {
|
|
442
|
-
const q = (t.user_query || '...').substring(0, 120);
|
|
443
|
-
const a = (t.assistant_response || '...').substring(0, 250);
|
|
444
|
-
console.log('**Turn ' + (t.turn_number || '?') + '**: ' + q + '...\n> ' + a + '...\n');
|
|
445
|
-
});
|
|
446
|
-
" 2>/dev/null || true
|
|
447
|
-
|
|
448
|
-
echo ""
|
|
449
|
-
echo -e "${DIM}Full history: \"turns 1-${TURN_NUMBER}\" or \"recall yesterday\"${RESET}"
|
|
450
|
-
fi
|
|
451
|
-
|
|
452
|
-
echo ""
|
|
453
|
-
echo -e "${CYAN}${BOLD}๐ง ekkOS Memory${RESET} ${DIM}| ${SESSION_NAME} | ${CURRENT_TIME}${RESET}"
|
|
454
|
-
|
|
455
|
-
elif [ "$POST_CLEAR_DETECTED" = true ] && [ -n "$AUTH_TOKEN" ]; then
|
|
461
|
+
if [ "$POST_CLEAR_DETECTED" = true ] && [ -n "$AUTH_TOKEN" ]; then
|
|
456
462
|
# /clear detected - show visible restoration banner
|
|
457
463
|
echo -e "${GREEN}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${RESET}" >&2
|
|
458
464
|
echo -e "${GREEN}${BOLD}๐ SESSION CONTINUED${RESET} ${DIM}| ${TURN_NUMBER} turns preserved | Context restored${RESET}" >&2
|
|
@@ -497,9 +503,6 @@ const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
|
|
|
497
503
|
echo ""
|
|
498
504
|
echo -e "${CYAN}${BOLD}๐ง ekkOS Memory${RESET} ${DIM}| ${SESSION_NAME} | ${CURRENT_TIME}${RESET}"
|
|
499
505
|
|
|
500
|
-
elif [ "$TOKEN_PERCENT" -ge 50 ]; then
|
|
501
|
-
echo -e "${CYAN}${BOLD}๐ง ekkOS Memory${RESET} ${DIM}| ~${IPC_PERCENT:-0}% IPC | ${SESSION_NAME} | ${CURRENT_TIME}${RESET}"
|
|
502
|
-
|
|
503
506
|
else
|
|
504
507
|
echo -e "${CYAN}${BOLD}๐ง ekkOS Memory${RESET} ${DIM}| ${SESSION_NAME} | ${CURRENT_TIME}${RESET}"
|
|
505
508
|
fi
|