kumo-cli 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -5
- package/dist/auth/credentialsStore.js +61 -14
- package/dist/auth/credentialsStore.js.map +1 -1
- package/dist/auth/credentialsStore.test.js +60 -0
- package/dist/auth/credentialsStore.test.js.map +1 -0
- package/dist/claude/fileHistory.js +35 -0
- package/dist/claude/fileHistory.js.map +1 -0
- package/dist/claude/sessionScanner.js +7 -95
- package/dist/claude/sessionScanner.js.map +1 -1
- package/dist/claude/sessionScanner.test.js +24 -0
- package/dist/claude/sessionScanner.test.js.map +1 -0
- package/dist/claude/transcriptParser.js +83 -0
- package/dist/claude/transcriptParser.js.map +1 -0
- package/dist/claude/transcriptParser.test.js +78 -0
- package/dist/claude/transcriptParser.test.js.map +1 -0
- package/dist/commands/agentPrompt.js +18 -0
- package/dist/commands/agentPrompt.js.map +1 -0
- package/dist/commands/cliArgs.js +56 -0
- package/dist/commands/cliArgs.js.map +1 -0
- package/dist/commands/cliArgs.test.js +25 -0
- package/dist/commands/cliArgs.test.js.map +1 -0
- package/dist/core/logger.js +7 -2
- package/dist/core/logger.js.map +1 -1
- package/dist/core/projectFileWatcher.js +56 -0
- package/dist/core/projectFileWatcher.js.map +1 -0
- package/dist/handlers/fileHandlerPayloads.js +72 -0
- package/dist/handlers/fileHandlerPayloads.js.map +1 -0
- package/dist/handlers/fileHandlerPayloads.test.js +55 -0
- package/dist/handlers/fileHandlerPayloads.test.js.map +1 -0
- package/dist/handlers/fileHandlers.js +74 -0
- package/dist/handlers/fileHandlers.js.map +1 -0
- package/dist/handlers/messageRouter.js +8 -203
- package/dist/handlers/messageRouter.js.map +1 -1
- package/dist/handlers/router.types.js +2 -0
- package/dist/handlers/router.types.js.map +1 -0
- package/dist/handlers/runtimeHandlers.js +55 -0
- package/dist/handlers/runtimeHandlers.js.map +1 -0
- package/dist/handlers/sessionHandlers.js +68 -0
- package/dist/handlers/sessionHandlers.js.map +1 -0
- package/dist/handlers/terminalHandlers.js +40 -0
- package/dist/handlers/terminalHandlers.js.map +1 -0
- package/dist/index.js +59 -145
- package/dist/index.js.map +1 -1
- package/dist/pty/notificationDetector.js +29 -0
- package/dist/pty/notificationDetector.js.map +1 -0
- package/dist/pty/notificationDetector.test.js +24 -0
- package/dist/pty/notificationDetector.test.js.map +1 -0
- package/dist/pty/ptyMenuParser.js +11 -277
- package/dist/pty/ptyMenuParser.js.map +1 -1
- package/dist/pty/ptyMenuParser.test.js +26 -0
- package/dist/pty/ptyMenuParser.test.js.map +1 -0
- package/dist/pty/selectionMenuDetector.js +49 -0
- package/dist/pty/selectionMenuDetector.js.map +1 -0
- package/dist/pty/selectionMenuDetector.test.js +62 -0
- package/dist/pty/selectionMenuDetector.test.js.map +1 -0
- package/dist/pty/selectionMenuMeta.js +54 -0
- package/dist/pty/selectionMenuMeta.js.map +1 -0
- package/dist/pty/selectionMenuMeta.test.js +26 -0
- package/dist/pty/selectionMenuMeta.test.js.map +1 -0
- package/dist/pty/selectionMenuParser.js +83 -0
- package/dist/pty/selectionMenuParser.js.map +1 -0
- package/dist/pty/selectionMenuParser.test.js +30 -0
- package/dist/pty/selectionMenuParser.test.js.map +1 -0
- package/dist/pty/statusSessionDetector.js +28 -0
- package/dist/pty/statusSessionDetector.js.map +1 -0
- package/dist/pty/statusSessionDetector.test.js +23 -0
- package/dist/pty/statusSessionDetector.test.js.map +1 -0
- package/dist/pty/terminalManager.js +11 -6
- package/dist/pty/terminalManager.js.map +1 -1
- package/dist/pty/textInputDetector.js +60 -0
- package/dist/pty/textInputDetector.js.map +1 -0
- package/dist/pty/textInputDetector.test.js +32 -0
- package/dist/pty/textInputDetector.test.js.map +1 -0
- package/dist/services/bootstrapService.js +28 -0
- package/dist/services/bootstrapService.js.map +1 -0
- package/dist/services/claudeAdapterProxy.js +104 -0
- package/dist/services/claudeAdapterProxy.js.map +1 -0
- package/dist/services/claudeMenuBridge.js +116 -0
- package/dist/services/claudeMenuBridge.js.map +1 -0
- package/dist/services/claudeMenuBridge.test.js +90 -0
- package/dist/services/claudeMenuBridge.test.js.map +1 -0
- package/dist/services/claudeService.js +7 -79
- package/dist/services/claudeService.js.map +1 -1
- package/dist/services/claudeService.test.js +39 -0
- package/dist/services/claudeService.test.js.map +1 -0
- package/dist/services/cliRuntime.js +49 -0
- package/dist/services/cliRuntime.js.map +1 -0
- package/dist/services/cliRuntime.test.js +55 -0
- package/dist/services/cliRuntime.test.js.map +1 -0
- package/dist/services/fileService.js +22 -24
- package/dist/services/fileService.js.map +1 -1
- package/dist/services/modelAdapter.test.js +47 -0
- package/dist/services/modelAdapter.test.js.map +1 -0
- package/dist/services/modelArgs.test.js +64 -0
- package/dist/services/modelArgs.test.js.map +1 -0
- package/dist/services/modelBaseUrl.test.js +32 -0
- package/dist/services/modelBaseUrl.test.js.map +1 -0
- package/dist/services/modelService.js +54 -12
- package/dist/services/modelService.js.map +1 -1
- package/dist/services/modelService.test.js +136 -0
- package/dist/services/modelService.test.js.map +1 -0
- package/dist/services/notificationDedupe.js +23 -0
- package/dist/services/notificationDedupe.js.map +1 -0
- package/dist/services/notificationDedupe.test.js +20 -0
- package/dist/services/notificationDedupe.test.js.map +1 -0
- package/dist/services/pairingService.js +17 -8
- package/dist/services/pairingService.js.map +1 -1
- package/dist/services/sessionListService.js +19 -0
- package/dist/services/sessionListService.js.map +1 -0
- package/dist/services/sessionListService.test.js +22 -0
- package/dist/services/sessionListService.test.js.map +1 -0
- package/dist/services/sessionService.js +8 -45
- package/dist/services/sessionService.js.map +1 -1
- package/dist/services/tunnelHelpers.js +69 -0
- package/dist/services/tunnelHelpers.js.map +1 -0
- package/dist/services/tunnelHelpers.test.js +42 -0
- package/dist/services/tunnelHelpers.test.js.map +1 -0
- package/dist/services/tunnelService.js +69 -25
- package/dist/services/tunnelService.js.map +1 -1
- package/dist/services/tunnelService.test.js +91 -0
- package/dist/services/tunnelService.test.js.map +1 -0
- package/dist/transport/webrtcTransport.js +145 -0
- package/dist/transport/webrtcTransport.js.map +1 -0
- package/dist/transport/webrtcTransport.test.js +104 -0
- package/dist/transport/webrtcTransport.test.js.map +1 -0
- package/dist/transport/webrtcTypes.js +29 -0
- package/dist/transport/webrtcTypes.js.map +1 -0
- package/dist/transport/webrtcTypes.test.js +20 -0
- package/dist/transport/webrtcTypes.test.js.map +1 -0
- package/dist/transport/wsClient.js +16 -3
- package/dist/transport/wsClient.js.map +1 -1
- package/dist/utils/safePath.js +18 -6
- package/dist/utils/safePath.js.map +1 -1
- package/package.json +8 -2
- package/scripts/run-tests.mjs +45 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { ModelService } from './modelService.js';
|
|
4
|
+
test('apply replaces legacy model args with the selected model flag', async () => {
|
|
5
|
+
const service = new ModelService();
|
|
6
|
+
const args = service.apply({
|
|
7
|
+
model: 'claude-sonnet-4',
|
|
8
|
+
baseURL: 'https://provider.example.com/custom',
|
|
9
|
+
apiKey: 'sk-test',
|
|
10
|
+
}, ['--verbose', '--model', 'legacy-model']);
|
|
11
|
+
const env = await service.buildEnv();
|
|
12
|
+
assert.deepEqual(args, ['--verbose', '--model', 'claude-sonnet-4']);
|
|
13
|
+
assert.equal(env.ANTHROPIC_BASE_URL, 'https://provider.example.com/custom');
|
|
14
|
+
assert.equal(env.ANTHROPIC_API_KEY, 'sk-test');
|
|
15
|
+
await service.dispose();
|
|
16
|
+
});
|
|
17
|
+
test('apply accepts payloads without legacy model name', async () => {
|
|
18
|
+
const service = new ModelService();
|
|
19
|
+
const args = service.apply({
|
|
20
|
+
baseURL: 'https://api.example.com/root/v1/messages',
|
|
21
|
+
apiKey: 'sk-test',
|
|
22
|
+
}, ['-m', 'legacy-model', '--debug']);
|
|
23
|
+
const env = await service.buildEnv();
|
|
24
|
+
assert.deepEqual(args, ['--debug']);
|
|
25
|
+
assert.equal(env.ANTHROPIC_BASE_URL, 'https://api.example.com/root');
|
|
26
|
+
assert.equal(env.ANTHROPIC_API_KEY, 'sk-test');
|
|
27
|
+
await service.dispose();
|
|
28
|
+
});
|
|
29
|
+
test('apply keeps no-set requests free of model flags', async () => {
|
|
30
|
+
const service = new ModelService();
|
|
31
|
+
const args = service.apply(null, ['--model', 'legacy-model', '--debug']);
|
|
32
|
+
const env = await service.buildEnv();
|
|
33
|
+
assert.deepEqual(args, ['--debug']);
|
|
34
|
+
assert.equal(service.getCurrent(), null);
|
|
35
|
+
assert.equal(env.ANTHROPIC_BASE_URL, undefined);
|
|
36
|
+
assert.equal(env.ANTHROPIC_API_KEY, undefined);
|
|
37
|
+
await service.dispose();
|
|
38
|
+
});
|
|
39
|
+
test('ensureBeforeSpawn does not fallback to default after no-set request', async () => {
|
|
40
|
+
const service = new ModelService();
|
|
41
|
+
service.apply(null, ['--debug']);
|
|
42
|
+
service.fetchDefault = async () => ({
|
|
43
|
+
model: 'claude-sonnet-4',
|
|
44
|
+
baseURL: 'https://provider.example.com/custom',
|
|
45
|
+
apiKey: 'sk-test',
|
|
46
|
+
});
|
|
47
|
+
const args = await service.ensureBeforeSpawn(['--debug'], 'device-token');
|
|
48
|
+
assert.deepEqual(args, ['--debug']);
|
|
49
|
+
assert.equal(service.getCurrent(), null);
|
|
50
|
+
await service.dispose();
|
|
51
|
+
});
|
|
52
|
+
test('ensureBeforeSpawn still loads default when no selection was applied', async () => {
|
|
53
|
+
const service = new ModelService();
|
|
54
|
+
service.fetchDefault = async () => ({
|
|
55
|
+
model: 'claude-sonnet-4',
|
|
56
|
+
baseURL: 'https://provider.example.com/custom',
|
|
57
|
+
apiKey: 'sk-test',
|
|
58
|
+
});
|
|
59
|
+
const args = await service.ensureBeforeSpawn(['--debug'], 'device-token');
|
|
60
|
+
assert.deepEqual(args, ['--debug', '--model', 'claude-sonnet-4']);
|
|
61
|
+
assert.equal(service.getCurrent()?.model, 'claude-sonnet-4');
|
|
62
|
+
await service.dispose();
|
|
63
|
+
});
|
|
64
|
+
//# sourceMappingURL=modelArgs.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelArgs.test.js","sourceRoot":"","sources":["../../src/services/modelArgs.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { ModelService } from './modelService.js';
|
|
4
|
+
test('strips v1 messages suffix for Claude-compatible providers', async () => {
|
|
5
|
+
const service = new ModelService();
|
|
6
|
+
const url = await service.resolveClaudeBaseURL({
|
|
7
|
+
model: 'claude-haiku-4.5',
|
|
8
|
+
baseURL: 'https://api.example.com/root/v1/messages',
|
|
9
|
+
apiKey: 'sk-test',
|
|
10
|
+
});
|
|
11
|
+
assert.equal(url, 'https://api.example.com/root');
|
|
12
|
+
});
|
|
13
|
+
test('uses local adapter proxy for OpenAI chat completions providers', async () => {
|
|
14
|
+
const service = new ModelService();
|
|
15
|
+
const url = await service.resolveClaudeBaseURL({
|
|
16
|
+
model: 'deepseek-v4-flash-free',
|
|
17
|
+
baseURL: 'https://opencode.ai/zen/v1/chat/completions',
|
|
18
|
+
apiKey: 'public',
|
|
19
|
+
});
|
|
20
|
+
assert.match(url, /^http:\/\/127\.0\.0\.1:\d+$/);
|
|
21
|
+
await service.dispose();
|
|
22
|
+
});
|
|
23
|
+
test('keeps unknown provider url unchanged', async () => {
|
|
24
|
+
const service = new ModelService();
|
|
25
|
+
const url = await service.resolveClaudeBaseURL({
|
|
26
|
+
model: 'custom',
|
|
27
|
+
baseURL: 'https://provider.example.com/custom',
|
|
28
|
+
apiKey: 'sk-test',
|
|
29
|
+
});
|
|
30
|
+
assert.equal(url, 'https://provider.example.com/custom');
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=modelBaseUrl.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelBaseUrl.test.js","sourceRoot":"","sources":["../../src/services/modelBaseUrl.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,IAAI,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;IAC3E,MAAM,OAAO,GAAG,IAAI,YAAY,EAAE,CAAC;IACnC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC;QAC7C,KAAK,EAAE,kBAAkB;QACzB,OAAO,EAAE,0CAA0C;QACnD,MAAM,EAAE,SAAS;KAClB,CAAC,CAAC;IACH,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,8BAA8B,CAAC,CAAC;AACpD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;IAChF,MAAM,OAAO,GAAG,IAAI,YAAY,EAAE,CAAC;IACnC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC;QAC7C,KAAK,EAAE,wBAAwB;QAC/B,OAAO,EAAE,6CAA6C;QACtD,MAAM,EAAE,QAAQ;KACjB,CAAC,CAAC;IACH,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,6BAA6B,CAAC,CAAC;IACjD,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;AAC1B,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;IACtD,MAAM,OAAO,GAAG,IAAI,YAAY,EAAE,CAAC;IACnC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC;QAC7C,KAAK,EAAE,QAAQ;QACf,OAAO,EAAE,qCAAqC;QAC9C,MAAM,EAAE,SAAS;KAClB,CAAC,CAAC;IACH,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,qCAAqC,CAAC,CAAC;AAC3D,CAAC,CAAC,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { httpRequest } from '../http/httpClient.js';
|
|
2
2
|
import { SERVER_HTTP } from '../core/config.js';
|
|
3
|
+
import { ClaudeAdapterProxy } from './claudeAdapterProxy.js';
|
|
3
4
|
// strip any existing --model / --model=... / -m flag pairs
|
|
4
5
|
export function removeModelArgs(args) {
|
|
5
6
|
const result = [];
|
|
@@ -18,37 +19,72 @@ export function removeModelArgs(args) {
|
|
|
18
19
|
// orchestrates the currently-active model pushed from backend via ws
|
|
19
20
|
export class ModelService {
|
|
20
21
|
current = null;
|
|
22
|
+
suppressDefaultFallback = false;
|
|
23
|
+
adapterProxy = new ClaudeAdapterProxy();
|
|
21
24
|
getCurrent() { return this.current; }
|
|
22
|
-
//
|
|
25
|
+
// cache incoming connection settings and rebuild the runtime model args
|
|
23
26
|
apply(payload, args) {
|
|
24
27
|
const cleaned = removeModelArgs(args);
|
|
25
28
|
if (payload && typeof payload === 'object') {
|
|
26
29
|
const p = payload;
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
model,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
const current = {
|
|
31
|
+
model: typeof p['model'] === 'string' && p['model'].trim()
|
|
32
|
+
? p['model'].trim()
|
|
33
|
+
: this.current?.model ?? '',
|
|
34
|
+
baseURL: typeof p['baseURL'] === 'string' ? p['baseURL'] : undefined,
|
|
35
|
+
apiKey: typeof p['apiKey'] === 'string' ? p['apiKey'] : undefined,
|
|
36
|
+
};
|
|
37
|
+
if (current.model || current.baseURL || current.apiKey) {
|
|
38
|
+
this.current = current;
|
|
39
|
+
this.suppressDefaultFallback = false;
|
|
40
|
+
if (current.model)
|
|
41
|
+
cleaned.push('--model', current.model);
|
|
35
42
|
return cleaned;
|
|
36
43
|
}
|
|
37
44
|
}
|
|
38
45
|
this.current = null;
|
|
46
|
+
this.suppressDefaultFallback = payload === null;
|
|
39
47
|
return cleaned;
|
|
40
48
|
}
|
|
41
49
|
// build env overrides for claude pty based on current model
|
|
42
|
-
buildEnv() {
|
|
43
|
-
const env = {
|
|
50
|
+
async buildEnv() {
|
|
51
|
+
const env = {
|
|
52
|
+
ANTHROPIC_BASE_URL: undefined,
|
|
53
|
+
ANTHROPIC_API_KEY: undefined,
|
|
54
|
+
ANTHROPIC_AUTH_TOKEN: undefined,
|
|
55
|
+
};
|
|
44
56
|
if (this.current?.baseURL) {
|
|
45
|
-
env.ANTHROPIC_BASE_URL = this.current
|
|
57
|
+
env.ANTHROPIC_BASE_URL = await this.resolveClaudeBaseURL(this.current);
|
|
46
58
|
env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
47
59
|
}
|
|
60
|
+
else {
|
|
61
|
+
await this.adapterProxy.stop();
|
|
62
|
+
}
|
|
48
63
|
if (this.current?.apiKey)
|
|
49
64
|
env.ANTHROPIC_API_KEY = this.current.apiKey;
|
|
65
|
+
if (env.ANTHROPIC_BASE_URL?.startsWith('http://127.0.0.1:'))
|
|
66
|
+
env.ANTHROPIC_AUTH_TOKEN = 'default';
|
|
50
67
|
return env;
|
|
51
68
|
}
|
|
69
|
+
// normalize provider endpoints into Claude-compatible base urls
|
|
70
|
+
async resolveClaudeBaseURL(model) {
|
|
71
|
+
const baseURL = (model.baseURL ?? '').trim().replace(/\/+$/, '');
|
|
72
|
+
if (!baseURL)
|
|
73
|
+
return baseURL;
|
|
74
|
+
if (/\/v1\/messages$/i.test(baseURL)) {
|
|
75
|
+
await this.adapterProxy.stop();
|
|
76
|
+
return baseURL.replace(/\/v1\/messages$/i, '');
|
|
77
|
+
}
|
|
78
|
+
if (/\/v1\/chat\/completions$/i.test(baseURL)) {
|
|
79
|
+
return this.adapterProxy.start({
|
|
80
|
+
baseURL,
|
|
81
|
+
apiKey: model.apiKey ?? '',
|
|
82
|
+
model: model.model,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
await this.adapterProxy.stop();
|
|
86
|
+
return baseURL;
|
|
87
|
+
}
|
|
52
88
|
// fetch the user's current default model from backend using deviceToken
|
|
53
89
|
async fetchDefault(deviceToken) {
|
|
54
90
|
if (!deviceToken)
|
|
@@ -75,10 +111,16 @@ export class ModelService {
|
|
|
75
111
|
async ensureBeforeSpawn(args, deviceToken) {
|
|
76
112
|
if (this.current)
|
|
77
113
|
return args;
|
|
114
|
+
if (this.suppressDefaultFallback)
|
|
115
|
+
return args;
|
|
78
116
|
const def = await this.fetchDefault(deviceToken);
|
|
79
117
|
if (def)
|
|
80
118
|
return this.apply({ model: def.model, baseURL: def.baseURL, apiKey: def.apiKey }, args);
|
|
81
119
|
return args;
|
|
82
120
|
}
|
|
121
|
+
// release local adapter resources
|
|
122
|
+
async dispose() {
|
|
123
|
+
await this.adapterProxy.stop();
|
|
124
|
+
}
|
|
83
125
|
}
|
|
84
126
|
//# sourceMappingURL=modelService.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modelService.js","sourceRoot":"","sources":["../../src/services/modelService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"modelService.js","sourceRoot":"","sources":["../../src/services/modelService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { ModelService } from './modelService.js';
|
|
4
|
+
import { convertRequestToOpenAI } from 'claude-adapter';
|
|
5
|
+
test('strips v1 messages suffix for Claude-compatible providers', async () => {
|
|
6
|
+
const service = new ModelService();
|
|
7
|
+
const url = await service.resolveClaudeBaseURL({
|
|
8
|
+
model: 'claude-haiku-4.5',
|
|
9
|
+
baseURL: 'https://api.example.com/root/v1/messages',
|
|
10
|
+
apiKey: 'sk-test',
|
|
11
|
+
});
|
|
12
|
+
assert.equal(url, 'https://api.example.com/root');
|
|
13
|
+
});
|
|
14
|
+
test('uses local adapter proxy for OpenAI chat completions providers', async () => {
|
|
15
|
+
const service = new ModelService();
|
|
16
|
+
const url = await service.resolveClaudeBaseURL({
|
|
17
|
+
model: 'deepseek-v4-flash-free',
|
|
18
|
+
baseURL: 'https://opencode.ai/zen/v1/chat/completions',
|
|
19
|
+
apiKey: 'public',
|
|
20
|
+
});
|
|
21
|
+
assert.match(url, /^http:\/\/127\.0\.0\.1:\d+$/);
|
|
22
|
+
await service.dispose();
|
|
23
|
+
});
|
|
24
|
+
test('keeps unknown provider url unchanged', async () => {
|
|
25
|
+
const service = new ModelService();
|
|
26
|
+
const url = await service.resolveClaudeBaseURL({
|
|
27
|
+
model: 'custom',
|
|
28
|
+
baseURL: 'https://provider.example.com/custom',
|
|
29
|
+
apiKey: 'sk-test',
|
|
30
|
+
});
|
|
31
|
+
assert.equal(url, 'https://provider.example.com/custom');
|
|
32
|
+
});
|
|
33
|
+
test('apply replaces legacy model args with the selected model flag', async () => {
|
|
34
|
+
const service = new ModelService();
|
|
35
|
+
const args = service.apply({
|
|
36
|
+
model: 'claude-sonnet-4',
|
|
37
|
+
baseURL: 'https://provider.example.com/custom',
|
|
38
|
+
apiKey: 'sk-test',
|
|
39
|
+
}, ['--verbose', '--model', 'legacy-model']);
|
|
40
|
+
const env = await service.buildEnv();
|
|
41
|
+
assert.deepEqual(args, ['--verbose', '--model', 'claude-sonnet-4']);
|
|
42
|
+
assert.equal(env.ANTHROPIC_BASE_URL, 'https://provider.example.com/custom');
|
|
43
|
+
assert.equal(env.ANTHROPIC_API_KEY, 'sk-test');
|
|
44
|
+
await service.dispose();
|
|
45
|
+
});
|
|
46
|
+
test('apply accepts payloads without legacy model name', async () => {
|
|
47
|
+
const service = new ModelService();
|
|
48
|
+
const args = service.apply({
|
|
49
|
+
baseURL: 'https://api.example.com/root/v1/messages',
|
|
50
|
+
apiKey: 'sk-test',
|
|
51
|
+
}, ['-m', 'legacy-model', '--debug']);
|
|
52
|
+
const env = await service.buildEnv();
|
|
53
|
+
assert.deepEqual(args, ['--debug']);
|
|
54
|
+
assert.equal(env.ANTHROPIC_BASE_URL, 'https://api.example.com/root');
|
|
55
|
+
assert.equal(env.ANTHROPIC_API_KEY, 'sk-test');
|
|
56
|
+
await service.dispose();
|
|
57
|
+
});
|
|
58
|
+
test('apply keeps no-set requests free of model flags', async () => {
|
|
59
|
+
const service = new ModelService();
|
|
60
|
+
const args = service.apply(null, ['--model', 'legacy-model', '--debug']);
|
|
61
|
+
const env = await service.buildEnv();
|
|
62
|
+
assert.deepEqual(args, ['--debug']);
|
|
63
|
+
assert.equal(service.getCurrent(), null);
|
|
64
|
+
assert.equal(env.ANTHROPIC_BASE_URL, undefined);
|
|
65
|
+
assert.equal(env.ANTHROPIC_API_KEY, undefined);
|
|
66
|
+
await service.dispose();
|
|
67
|
+
});
|
|
68
|
+
test('ensureBeforeSpawn does not fallback to default after no-set request', async () => {
|
|
69
|
+
const service = new ModelService();
|
|
70
|
+
service.apply(null, ['--debug']);
|
|
71
|
+
service.fetchDefault = async () => ({
|
|
72
|
+
model: 'claude-sonnet-4',
|
|
73
|
+
baseURL: 'https://provider.example.com/custom',
|
|
74
|
+
apiKey: 'sk-test',
|
|
75
|
+
});
|
|
76
|
+
const args = await service.ensureBeforeSpawn(['--debug'], 'device-token');
|
|
77
|
+
assert.deepEqual(args, ['--debug']);
|
|
78
|
+
assert.equal(service.getCurrent(), null);
|
|
79
|
+
await service.dispose();
|
|
80
|
+
});
|
|
81
|
+
test('ensureBeforeSpawn still loads default when no selection was applied', async () => {
|
|
82
|
+
const service = new ModelService();
|
|
83
|
+
service.fetchDefault = async () => ({
|
|
84
|
+
model: 'claude-sonnet-4',
|
|
85
|
+
baseURL: 'https://provider.example.com/custom',
|
|
86
|
+
apiKey: 'sk-test',
|
|
87
|
+
});
|
|
88
|
+
const args = await service.ensureBeforeSpawn(['--debug'], 'device-token');
|
|
89
|
+
assert.deepEqual(args, ['--debug', '--model', 'claude-sonnet-4']);
|
|
90
|
+
assert.equal(service.getCurrent()?.model, 'claude-sonnet-4');
|
|
91
|
+
await service.dispose();
|
|
92
|
+
});
|
|
93
|
+
test('xml tool format avoids tool role messages for strict providers', () => {
|
|
94
|
+
const request = {
|
|
95
|
+
model: 'claude-sonnet-4',
|
|
96
|
+
max_tokens: 128,
|
|
97
|
+
messages: [
|
|
98
|
+
{ role: 'user', content: 'list files' },
|
|
99
|
+
{
|
|
100
|
+
role: 'assistant',
|
|
101
|
+
content: [
|
|
102
|
+
{
|
|
103
|
+
type: 'tool_use',
|
|
104
|
+
id: 'toolu_1',
|
|
105
|
+
name: 'ls',
|
|
106
|
+
input: { path: '.' },
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
role: 'user',
|
|
112
|
+
content: [
|
|
113
|
+
{
|
|
114
|
+
type: 'tool_result',
|
|
115
|
+
tool_use_id: 'toolu_1',
|
|
116
|
+
content: 'package.json',
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
tools: [
|
|
122
|
+
{
|
|
123
|
+
name: 'ls',
|
|
124
|
+
description: 'list files',
|
|
125
|
+
input_schema: {
|
|
126
|
+
type: 'object',
|
|
127
|
+
properties: { path: { type: 'string' } },
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
const converted = convertRequestToOpenAI(request, 'deepseek-v4-flash-free', 'xml');
|
|
133
|
+
assert.deepEqual([...new Set(converted.messages.map((message) => message.role))].sort(), ['assistant', 'system', 'user']);
|
|
134
|
+
assert.equal(converted.messages.some((message) => message.role === 'tool'), false);
|
|
135
|
+
});
|
|
136
|
+
//# sourceMappingURL=modelService.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelService.test.js","sourceRoot":"","sources":["../../src/services/modelService.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const NOTIFICATION_DEDUPE_WINDOW_MS = 5 * 60 * 1000;
|
|
2
|
+
// keep a short-lived cache of normalized notifications to avoid duplicate ws sends
|
|
3
|
+
export class NotificationDedupe {
|
|
4
|
+
dedupes = new Map();
|
|
5
|
+
// return true only when the notification is outside the dedupe window
|
|
6
|
+
shouldSend(level, message, now = Date.now()) {
|
|
7
|
+
const key = this.buildKey(level, message);
|
|
8
|
+
for (const [existingKey, expiresAt] of this.dedupes) {
|
|
9
|
+
if (expiresAt <= now)
|
|
10
|
+
this.dedupes.delete(existingKey);
|
|
11
|
+
}
|
|
12
|
+
const expiresAt = this.dedupes.get(key);
|
|
13
|
+
if (expiresAt && expiresAt > now)
|
|
14
|
+
return false;
|
|
15
|
+
this.dedupes.set(key, now + NOTIFICATION_DEDUPE_WINDOW_MS);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
// normalize notification text so cosmetic whitespace changes do not bypass dedupe
|
|
19
|
+
buildKey(level, message) {
|
|
20
|
+
return `${level.trim().toLowerCase()}::${message.trim().replace(/\s+/g, ' ').toLowerCase()}`;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=notificationDedupe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notificationDedupe.js","sourceRoot":"","sources":["../../src/services/notificationDedupe.ts"],"names":[],"mappings":"AAAA,MAAM,6BAA6B,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAEpD,mFAAmF;AACnF,MAAM,OAAO,kBAAkB;IACrB,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE5C,sEAAsE;IACtE,UAAU,CAAC,KAAa,EAAE,OAAe,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACzD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC1C,KAAK,MAAM,CAAC,WAAW,EAAE,SAAS,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACpD,IAAI,SAAS,IAAI,GAAG;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACzD,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,SAAS,IAAI,SAAS,GAAG,GAAG;YAAE,OAAO,KAAK,CAAC;QAC/C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,6BAA6B,CAAC,CAAC;QAC3D,OAAO,IAAI,CAAC;IACd,CAAC;IAED,kFAAkF;IAC1E,QAAQ,CAAC,KAAa,EAAE,OAAe;QAC7C,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;IAC/F,CAAC;CACF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { NotificationDedupe } from './notificationDedupe.js';
|
|
4
|
+
test('dedupes same notification within 5 minutes', () => {
|
|
5
|
+
const dedupe = new NotificationDedupe();
|
|
6
|
+
const now = new Date('2026-05-29T10:09:17.389Z').getTime();
|
|
7
|
+
const first = dedupe.shouldSend('error', 'IDE extension install failed (see /status for info)', now);
|
|
8
|
+
const second = dedupe.shouldSend('error', ' IDE extension install failed (see /status for info) ', now);
|
|
9
|
+
assert.equal(first, true);
|
|
10
|
+
assert.equal(second, false);
|
|
11
|
+
});
|
|
12
|
+
test('allows same notification again after 5 minutes', () => {
|
|
13
|
+
const dedupe = new NotificationDedupe();
|
|
14
|
+
const base = new Date('2026-05-29T10:09:17.389Z').getTime();
|
|
15
|
+
const first = dedupe.shouldSend('error', 'IDE extension install failed (see /status for info)', base);
|
|
16
|
+
const second = dedupe.shouldSend('error', 'IDE extension install failed (see /status for info)', base + 5 * 60 * 1000 + 1);
|
|
17
|
+
assert.equal(first, true);
|
|
18
|
+
assert.equal(second, true);
|
|
19
|
+
});
|
|
20
|
+
//# sourceMappingURL=notificationDedupe.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notificationDedupe.test.js","sourceRoot":"","sources":["../../src/services/notificationDedupe.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,IAAI,CAAC,4CAA4C,EAAE,GAAG,EAAE;IACtD,MAAM,MAAM,GAAG,IAAI,kBAAkB,EAAE,CAAC;IACxC,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC,OAAO,EAAE,CAAC;IAE3D,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,qDAAqD,EAAE,GAAG,CAAC,CAAC;IACrG,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,yDAAyD,EAAE,GAAG,CAAC,CAAC;IAE1G,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1B,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAC9B,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gDAAgD,EAAE,GAAG,EAAE;IAC1D,MAAM,MAAM,GAAG,IAAI,kBAAkB,EAAE,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC,OAAO,EAAE,CAAC;IAE5D,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,qDAAqD,EAAE,IAAI,CAAC,CAAC;IACtG,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,qDAAqD,EAAE,IAAI,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC;IAE3H,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1B,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC7B,CAAC,CAAC,CAAC"}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import readline from 'node:readline';
|
|
3
|
-
import { randomUUID } from 'node:crypto';
|
|
4
3
|
import { httpRequest } from '../http/httpClient.js';
|
|
5
4
|
import { SERVER_HTTP } from '../core/config.js';
|
|
6
|
-
import { saveCredentials,
|
|
5
|
+
import { ensureDeviceUuid, saveCredentials, saveDeviceIdentity } from '../auth/credentialsStore.js';
|
|
6
|
+
const BANNER_TIPS = [
|
|
7
|
+
'tip: press --help to see the command guide.',
|
|
8
|
+
'tip: run `kumo reset claude` to pair the Claude agent again.',
|
|
9
|
+
];
|
|
7
10
|
// poll pair status until approved/rejected or timeout (5 min)
|
|
8
11
|
async function pollPairStatus(requestId) {
|
|
9
12
|
const deadline = Date.now() + 5 * 60 * 1000;
|
|
@@ -49,7 +52,7 @@ export async function verifyCredentials(creds) {
|
|
|
49
52
|
return null;
|
|
50
53
|
}
|
|
51
54
|
}
|
|
52
|
-
function printBanner() {
|
|
55
|
+
export function printBanner() {
|
|
53
56
|
const cloud = [
|
|
54
57
|
" \x1b[1;36m_ __ _ _ __ __ ____ \x1b[0m",
|
|
55
58
|
" \x1b[1;36m| |/ /| | | || \\/ | / __ \\\x1b[0m",
|
|
@@ -62,16 +65,21 @@ function printBanner() {
|
|
|
62
65
|
console.log(line);
|
|
63
66
|
console.log('');
|
|
64
67
|
console.log(' \x1b[2mwhere your code drifts between devices, softly.\x1b[0m');
|
|
68
|
+
const tip = BANNER_TIPS[Math.floor(Math.random() * BANNER_TIPS.length)];
|
|
69
|
+
console.log(` \x1b[2;37m${tip}\x1b[0m`);
|
|
65
70
|
console.log('');
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
}
|
|
72
|
+
// print pairing-specific instructions only when the cli needs a fresh code
|
|
73
|
+
function printPairingInstructions() {
|
|
74
|
+
console.log(' \x1b[36m›\x1b[0m open the \x1b[1mKumo app\x1b[0m, sign in, and copy the active 9-digit pairing code.');
|
|
75
|
+
console.log(' \x1b[36m›\x1b[0m if needed, tap \x1b[1m"Regenerate"\x1b[0m for a new code, then enter it below. spaces are fine.');
|
|
68
76
|
console.log('');
|
|
69
77
|
}
|
|
70
78
|
// interactive pairing flow: ask for code, submit to backend, poll until approved
|
|
71
79
|
export async function pairFlow() {
|
|
72
80
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
73
81
|
const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a)));
|
|
74
|
-
|
|
82
|
+
printPairingInstructions();
|
|
75
83
|
try {
|
|
76
84
|
while (true) {
|
|
77
85
|
const raw = (await ask('\x1b[1;33m✦ pairing code ›\x1b[0m ')).trim();
|
|
@@ -84,7 +92,7 @@ export async function pairFlow() {
|
|
|
84
92
|
console.warn('[kumo-cli] pairing code must be exactly 9 digits, please try again');
|
|
85
93
|
continue;
|
|
86
94
|
}
|
|
87
|
-
const deviceUuid =
|
|
95
|
+
const deviceUuid = ensureDeviceUuid();
|
|
88
96
|
const deviceName = `${os.userInfo().username}@${os.hostname()}`;
|
|
89
97
|
let result;
|
|
90
98
|
try {
|
|
@@ -111,6 +119,7 @@ export async function pairFlow() {
|
|
|
111
119
|
console.warn('[kumo-cli] please try again with a fresh code');
|
|
112
120
|
continue;
|
|
113
121
|
}
|
|
122
|
+
saveDeviceIdentity(approved.device.deviceUuid);
|
|
114
123
|
const creds = {
|
|
115
124
|
deviceUuid: approved.device.deviceUuid,
|
|
116
125
|
deviceToken: approved.deviceToken,
|
|
@@ -118,7 +127,7 @@ export async function pairFlow() {
|
|
|
118
127
|
user: approved.user ?? undefined,
|
|
119
128
|
};
|
|
120
129
|
saveCredentials(creds);
|
|
121
|
-
console.log(
|
|
130
|
+
console.log('[kumo-cli] paired successfully');
|
|
122
131
|
return creds;
|
|
123
132
|
}
|
|
124
133
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pairingService.js","sourceRoot":"","sources":["../../src/services/pairingService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,QAAQ,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"pairingService.js","sourceRoot":"","sources":["../../src/services/pairingService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as sessionService from './sessionService.js';
|
|
2
|
+
// merge persisted sessions with the active session ids seen in the current runtime
|
|
3
|
+
export function mergeSessionActivity(sessions, activeIds, currentSessionId) {
|
|
4
|
+
const resolvedActiveIds = new Set(activeIds);
|
|
5
|
+
if (currentSessionId) {
|
|
6
|
+
resolvedActiveIds.add(currentSessionId);
|
|
7
|
+
}
|
|
8
|
+
return sessions.map((session) => ({
|
|
9
|
+
...session,
|
|
10
|
+
isActive: resolvedActiveIds.has(session.sessionId),
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
// load the session list payload exactly once so callers do not duplicate the same merge logic
|
|
14
|
+
export async function loadSessionList(cwdArg, currentSessionId) {
|
|
15
|
+
const sessions = await sessionService.getSessionListForCwd(cwdArg);
|
|
16
|
+
const activeIds = await sessionService.getActiveSessionIds();
|
|
17
|
+
return mergeSessionActivity(sessions, activeIds, currentSessionId);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=sessionListService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionListService.js","sourceRoot":"","sources":["../../src/services/sessionListService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAC;AAMtD,mFAAmF;AACnF,MAAM,UAAU,oBAAoB,CAClC,QAA4B,EAC5B,SAAsB,EACtB,gBAAgC;IAEhC,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;IAC7C,IAAI,gBAAgB,EAAE,CAAC;QACrB,iBAAiB,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAChC,GAAG,OAAO;QACV,QAAQ,EAAE,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC;KACnD,CAAC,CAAC,CAAC;AACN,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAe,EAAE,gBAAgC;IACrF,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;IACnE,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,mBAAmB,EAAE,CAAC;IAC7D,OAAO,oBAAoB,CAAC,QAAQ,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAC;AACrE,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mergeSessionActivity } from './sessionListService.js';
|
|
4
|
+
test('mergeSessionActivity marks persisted active sessions', () => {
|
|
5
|
+
const sessions = [
|
|
6
|
+
{ sessionId: 's1', firstMessage: 'hello', lastTimestamp: '2026-06-22T10:00:00.000Z' },
|
|
7
|
+
{ sessionId: 's2', firstMessage: 'world', lastTimestamp: '2026-06-22T09:00:00.000Z' },
|
|
8
|
+
];
|
|
9
|
+
const result = mergeSessionActivity(sessions, new Set(['s2']));
|
|
10
|
+
assert.deepEqual(result, [
|
|
11
|
+
{ sessionId: 's1', firstMessage: 'hello', lastTimestamp: '2026-06-22T10:00:00.000Z', isActive: false },
|
|
12
|
+
{ sessionId: 's2', firstMessage: 'world', lastTimestamp: '2026-06-22T09:00:00.000Z', isActive: true },
|
|
13
|
+
]);
|
|
14
|
+
});
|
|
15
|
+
test('mergeSessionActivity includes the current runtime session id', () => {
|
|
16
|
+
const sessions = [
|
|
17
|
+
{ sessionId: 's1', firstMessage: 'hello', lastTimestamp: '2026-06-22T10:00:00.000Z' },
|
|
18
|
+
];
|
|
19
|
+
const result = mergeSessionActivity(sessions, new Set(), 's1');
|
|
20
|
+
assert.equal(result[0]?.isActive, true);
|
|
21
|
+
});
|
|
22
|
+
//# sourceMappingURL=sessionListService.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionListService.test.js","sourceRoot":"","sources":["../../src/services/sessionListService.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAE/D,IAAI,CAAC,sDAAsD,EAAE,GAAG,EAAE;IAChE,MAAM,QAAQ,GAAG;QACf,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,0BAA0B,EAAE;QACrF,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,0BAA0B,EAAE;KACtF,CAAC;IAEF,MAAM,MAAM,GAAG,oBAAoB,CAAC,QAAQ,EAAE,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAE/D,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE;QACvB,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,0BAA0B,EAAE,QAAQ,EAAE,KAAK,EAAE;QACtG,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,0BAA0B,EAAE,QAAQ,EAAE,IAAI,EAAE;KACtG,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,8DAA8D,EAAE,GAAG,EAAE;IACxE,MAAM,QAAQ,GAAG;QACf,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,0BAA0B,EAAE;KACtF,CAAC;IAEF,MAAM,MAAM,GAAG,oBAAoB,CAAC,QAAQ,EAAE,IAAI,GAAG,EAAU,EAAE,IAAI,CAAC,CAAC;IAEvE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;AAC1C,CAAC,CAAC,CAAC"}
|