icopilot 2.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/CHANGELOG.md +250 -0
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/bin/icopilot.js +6 -0
- package/dist/acp/router.js +123 -0
- package/dist/acp/schema.js +53 -0
- package/dist/agents/aggregator.js +187 -0
- package/dist/agents/custom-agents.js +97 -0
- package/dist/agents/goal-driven.js +411 -0
- package/dist/agents/multi-repo.js +350 -0
- package/dist/agents/parallel-runner.js +181 -0
- package/dist/agents/router.js +144 -0
- package/dist/agents/self-heal.js +481 -0
- package/dist/agents/tdd-agent.js +278 -0
- package/dist/api/github-models.js +158 -0
- package/dist/bridge/ide-bridge.js +479 -0
- package/dist/cloud/routine-executor.js +34 -0
- package/dist/cloud/routine-scheduler.js +67 -0
- package/dist/cloud/routine-storage.js +297 -0
- package/dist/commands/acp-cmd.js +143 -0
- package/dist/commands/actions-cmd.js +624 -0
- package/dist/commands/agent-cmd.js +144 -0
- package/dist/commands/alias-cmd.js +132 -0
- package/dist/commands/bookmark-cmd.js +77 -0
- package/dist/commands/changelog-cmd.js +99 -0
- package/dist/commands/changes-cmd.js +120 -0
- package/dist/commands/clipboard-cmd.js +217 -0
- package/dist/commands/cloud-routine-cmd.js +265 -0
- package/dist/commands/codegen-cmd.js +544 -0
- package/dist/commands/compare-cmd.js +116 -0
- package/dist/commands/context-cmd.js +247 -0
- package/dist/commands/context-viz-cmd.js +43 -0
- package/dist/commands/conventions-cmd.js +116 -0
- package/dist/commands/cost-cmd.js +51 -0
- package/dist/commands/deps-cmd.js +294 -0
- package/dist/commands/diagram-cmd.js +658 -0
- package/dist/commands/diff-review-cmd.js +92 -0
- package/dist/commands/doc-cmd.js +412 -0
- package/dist/commands/doctor-cmd.js +152 -0
- package/dist/commands/editor-cmd.js +49 -0
- package/dist/commands/env-cmd.js +86 -0
- package/dist/commands/explain-cmd.js +78 -0
- package/dist/commands/explain-shell-cmd.js +22 -0
- package/dist/commands/explore-cmd.js +231 -0
- package/dist/commands/feedback-cmd.js +98 -0
- package/dist/commands/fix-cmd.js +17 -0
- package/dist/commands/generate-cmd.js +38 -0
- package/dist/commands/git-extra.js +197 -0
- package/dist/commands/git-log-cmd.js +98 -0
- package/dist/commands/git-undo-cmd.js +137 -0
- package/dist/commands/git.js +155 -0
- package/dist/commands/history-cmd.js +122 -0
- package/dist/commands/index-cmd.js +65 -0
- package/dist/commands/init-cmd.js +73 -0
- package/dist/commands/lint-cmd.js +133 -0
- package/dist/commands/memory-cmd.js +98 -0
- package/dist/commands/metrics-cmd.js +97 -0
- package/dist/commands/mode-prefix.js +30 -0
- package/dist/commands/multi-cmd.js +44 -0
- package/dist/commands/notify-cmd.js +204 -0
- package/dist/commands/profile-cmd.js +101 -0
- package/dist/commands/prompts.js +17 -0
- package/dist/commands/rag-cmd.js +60 -0
- package/dist/commands/readme-cmd.js +564 -0
- package/dist/commands/reasoning-cmd.js +34 -0
- package/dist/commands/refactor-cmd.js +96 -0
- package/dist/commands/release-cmd.js +450 -0
- package/dist/commands/repo-cmd.js +195 -0
- package/dist/commands/route-cmd.js +21 -0
- package/dist/commands/schedule-cmd.js +109 -0
- package/dist/commands/search-cmd.js +47 -0
- package/dist/commands/security-cmd.js +156 -0
- package/dist/commands/settings-cmd.js +238 -0
- package/dist/commands/skill-cmd.js +338 -0
- package/dist/commands/slash.js +2721 -0
- package/dist/commands/snippets-cmd.js +83 -0
- package/dist/commands/space-cmd.js +92 -0
- package/dist/commands/stash-cmd.js +156 -0
- package/dist/commands/stats-cmd.js +36 -0
- package/dist/commands/style-cmd.js +85 -0
- package/dist/commands/suggest-cmd.js +40 -0
- package/dist/commands/summary-cmd.js +138 -0
- package/dist/commands/task-cmd.js +58 -0
- package/dist/commands/team-memory-cmd.js +97 -0
- package/dist/commands/template-cmd.js +475 -0
- package/dist/commands/test-cmd.js +146 -0
- package/dist/commands/todo-cmd.js +172 -0
- package/dist/commands/tokens-cmd.js +277 -0
- package/dist/commands/trigger-cmd.js +147 -0
- package/dist/commands/undo-cmd.js +18 -0
- package/dist/commands/voice-cmd.js +89 -0
- package/dist/commands/watch-cmd.js +110 -0
- package/dist/commands/web-cmd.js +183 -0
- package/dist/commands/worktree-cmd.js +119 -0
- package/dist/config-profile.js +66 -0
- package/dist/config.js +288 -0
- package/dist/context/compactor.js +53 -0
- package/dist/context/dep-context.js +329 -0
- package/dist/context/file-refs.js +54 -0
- package/dist/context/git-context.js +229 -0
- package/dist/context/image-input.js +66 -0
- package/dist/context/memory.js +55 -0
- package/dist/context/persistent-memory.js +104 -0
- package/dist/context/pinned.js +96 -0
- package/dist/context/priority.js +150 -0
- package/dist/context/read-only.js +48 -0
- package/dist/context/smart-files.js +286 -0
- package/dist/context/team-memory.js +156 -0
- package/dist/extensions/loader.js +149 -0
- package/dist/extensions/marketplace.js +49 -0
- package/dist/extensions/slack-provider.js +181 -0
- package/dist/extensions/team.js +56 -0
- package/dist/extensions/teams-provider.js +222 -0
- package/dist/extensions/voice.js +18 -0
- package/dist/hooks/lifecycle.js +215 -0
- package/dist/hooks/precommit.js +463 -0
- package/dist/index/embeddings.js +23 -0
- package/dist/index/indexer.js +86 -0
- package/dist/index/retrieve.js +20 -0
- package/dist/index/store.js +95 -0
- package/dist/index.js +286 -0
- package/dist/intelligence/dead-code.js +457 -0
- package/dist/intelligence/error-watch.js +263 -0
- package/dist/intelligence/navigation.js +141 -0
- package/dist/intelligence/stack-trace.js +210 -0
- package/dist/intelligence/symbol-index.js +410 -0
- package/dist/knowledge/auto-memory.js +412 -0
- package/dist/knowledge/conventions.js +475 -0
- package/dist/knowledge/corrections.js +213 -0
- package/dist/knowledge/rag.js +450 -0
- package/dist/knowledge/style-learner.js +324 -0
- package/dist/logger.js +35 -0
- package/dist/mcp/client.js +144 -0
- package/dist/mcp/config.js +24 -0
- package/dist/mcp/index.js +89 -0
- package/dist/modes/auto-compact.js +20 -0
- package/dist/modes/autopilot.js +157 -0
- package/dist/modes/background.js +82 -0
- package/dist/modes/interactive.js +187 -0
- package/dist/modes/oneshot.js +36 -0
- package/dist/modes/tui.js +265 -0
- package/dist/modes/turn.js +342 -0
- package/dist/notifications/manager.js +107 -0
- package/dist/plugins/marketplace.js +244 -0
- package/dist/providers/custom-provider.js +298 -0
- package/dist/providers/local-model.js +121 -0
- package/dist/routing/profiles.js +44 -0
- package/dist/routing/router.js +18 -0
- package/dist/sandbox/container.js +151 -0
- package/dist/security/audit.js +237 -0
- package/dist/security/content-filter.js +449 -0
- package/dist/security/proxy.js +301 -0
- package/dist/security/retention.js +281 -0
- package/dist/security/roles.js +252 -0
- package/dist/server/api-server.js +679 -0
- package/dist/session/bookmarks.js +72 -0
- package/dist/session/cloud-session.js +291 -0
- package/dist/session/handoff.js +405 -0
- package/dist/session/manager.js +35 -0
- package/dist/session/session.js +296 -0
- package/dist/session/share.js +313 -0
- package/dist/session/undo-journal.js +91 -0
- package/dist/snippets/store.js +60 -0
- package/dist/spaces/space-config.js +156 -0
- package/dist/spaces/space.js +220 -0
- package/dist/stats/store.js +101 -0
- package/dist/tools/apply-patch.js +134 -0
- package/dist/tools/auto-check.js +218 -0
- package/dist/tools/diff-edit.js +150 -0
- package/dist/tools/diff-prompt.js +36 -0
- package/dist/tools/edit-file.js +66 -0
- package/dist/tools/file-ops.js +205 -0
- package/dist/tools/glob.js +17 -0
- package/dist/tools/grep.js +56 -0
- package/dist/tools/image.js +194 -0
- package/dist/tools/list-directory.js +228 -0
- package/dist/tools/memory.js +17 -0
- package/dist/tools/multi-edit.js +299 -0
- package/dist/tools/policy.js +95 -0
- package/dist/tools/registry.js +484 -0
- package/dist/tools/retry.js +74 -0
- package/dist/tools/run-in-terminal.js +162 -0
- package/dist/tools/safety.js +64 -0
- package/dist/tools/sandbox.js +15 -0
- package/dist/tools/search-symbols.js +212 -0
- package/dist/tools/shell.js +118 -0
- package/dist/tools/web.js +167 -0
- package/dist/ui/prompt.js +37 -0
- package/dist/ui/render.js +96 -0
- package/dist/ui/screen.js +13 -0
- package/dist/ui/theme.js +56 -0
- package/dist/util/browser.js +34 -0
- package/dist/util/completion.js +350 -0
- package/dist/util/cost.js +28 -0
- package/dist/util/keybindings.js +113 -0
- package/dist/util/lazy.js +26 -0
- package/dist/util/perf.js +25 -0
- package/dist/util/token-worker.js +11 -0
- package/dist/util/tokens.js +50 -0
- package/dist/workflows/builtins.js +128 -0
- package/dist/workflows/engine.js +496 -0
- package/dist/workflows/file-trigger.js +197 -0
- package/package.json +79 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { config } from '../config.js';
|
|
4
|
+
const NAME_PATTERN = /^[a-z0-9][a-z0-9_-]{0,32}$/i;
|
|
5
|
+
export function bookmarksPath() {
|
|
6
|
+
fs.mkdirSync(config.sessionDir, { recursive: true });
|
|
7
|
+
return path.join(config.sessionDir, 'bookmarks.json');
|
|
8
|
+
}
|
|
9
|
+
export function listBookmarks(sessionId) {
|
|
10
|
+
const bookmarks = readBookmarks();
|
|
11
|
+
return sessionId ? bookmarks.filter((bookmark) => bookmark.sessionId === sessionId) : bookmarks;
|
|
12
|
+
}
|
|
13
|
+
export function addBookmark(sessionId, name, index, preview) {
|
|
14
|
+
if (!NAME_PATTERN.test(name)) {
|
|
15
|
+
throw new Error('Bookmark name must match /^[a-z0-9][a-z0-9_-]{0,32}$/i.');
|
|
16
|
+
}
|
|
17
|
+
const bookmarks = readBookmarks();
|
|
18
|
+
const bookmark = {
|
|
19
|
+
sessionId,
|
|
20
|
+
name,
|
|
21
|
+
index,
|
|
22
|
+
createdAt: new Date().toISOString(),
|
|
23
|
+
preview,
|
|
24
|
+
};
|
|
25
|
+
const existing = bookmarks.findIndex((item) => item.sessionId === sessionId && item.name === name);
|
|
26
|
+
if (existing === -1) {
|
|
27
|
+
bookmarks.push(bookmark);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
bookmarks[existing] = bookmark;
|
|
31
|
+
}
|
|
32
|
+
writeBookmarks(bookmarks);
|
|
33
|
+
return bookmark;
|
|
34
|
+
}
|
|
35
|
+
export function getBookmark(sessionId, name) {
|
|
36
|
+
return (readBookmarks().find((bookmark) => bookmark.sessionId === sessionId && bookmark.name === name) ?? null);
|
|
37
|
+
}
|
|
38
|
+
export function deleteBookmark(sessionId, name) {
|
|
39
|
+
const bookmarks = readBookmarks();
|
|
40
|
+
const remaining = bookmarks.filter((bookmark) => bookmark.sessionId !== sessionId || bookmark.name !== name);
|
|
41
|
+
if (remaining.length === bookmarks.length)
|
|
42
|
+
return false;
|
|
43
|
+
writeBookmarks(remaining);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
function readBookmarks() {
|
|
47
|
+
const file = bookmarksPath();
|
|
48
|
+
try {
|
|
49
|
+
if (!fs.existsSync(file))
|
|
50
|
+
return [];
|
|
51
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
52
|
+
if (!Array.isArray(parsed))
|
|
53
|
+
return [];
|
|
54
|
+
return parsed.filter(isBookmark);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function writeBookmarks(bookmarks) {
|
|
61
|
+
fs.writeFileSync(bookmarksPath(), JSON.stringify(bookmarks, null, 2), 'utf8');
|
|
62
|
+
}
|
|
63
|
+
function isBookmark(value) {
|
|
64
|
+
if (!value || typeof value !== 'object')
|
|
65
|
+
return false;
|
|
66
|
+
const bookmark = value;
|
|
67
|
+
return (typeof bookmark.sessionId === 'string' &&
|
|
68
|
+
typeof bookmark.name === 'string' &&
|
|
69
|
+
typeof bookmark.index === 'number' &&
|
|
70
|
+
typeof bookmark.createdAt === 'string' &&
|
|
71
|
+
typeof bookmark.preview === 'string');
|
|
72
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
const CLOUD_STORE_VERSION = 1;
|
|
6
|
+
const CLOUD_SESSIONS_PATH_ENV = 'ICOPILOT_CLOUD_SESSIONS_PATH';
|
|
7
|
+
class SimulatedCloudHttpClient {
|
|
8
|
+
filePath;
|
|
9
|
+
constructor(filePath) {
|
|
10
|
+
this.filePath = filePath;
|
|
11
|
+
}
|
|
12
|
+
async create(config, opts) {
|
|
13
|
+
return this.mutate((store) => {
|
|
14
|
+
const now = new Date().toISOString();
|
|
15
|
+
const record = {
|
|
16
|
+
id: randomUUID(),
|
|
17
|
+
name: opts?.name?.trim() || `cloud-${new Date(now).toISOString().slice(0, 19)}`,
|
|
18
|
+
endpoint: config.endpoint,
|
|
19
|
+
status: 'connected',
|
|
20
|
+
createdAt: now,
|
|
21
|
+
updatedAt: now,
|
|
22
|
+
messageCount: 0,
|
|
23
|
+
messages: [],
|
|
24
|
+
};
|
|
25
|
+
store.currentSessionId = record.id;
|
|
26
|
+
for (const session of store.sessions)
|
|
27
|
+
session.status = 'idle';
|
|
28
|
+
store.sessions.unshift(record);
|
|
29
|
+
return cloneRecord(record);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async connect(sessionId) {
|
|
33
|
+
return this.mutate((store) => {
|
|
34
|
+
const session = store.sessions.find((entry) => entry.id === sessionId);
|
|
35
|
+
if (!session)
|
|
36
|
+
throw new Error(`Cloud session not found: ${sessionId}`);
|
|
37
|
+
const now = new Date().toISOString();
|
|
38
|
+
store.currentSessionId = sessionId;
|
|
39
|
+
for (const entry of store.sessions)
|
|
40
|
+
entry.status = entry.id === sessionId ? 'connected' : 'idle';
|
|
41
|
+
session.updatedAt = now;
|
|
42
|
+
return cloneRecord(session);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async disconnect() {
|
|
46
|
+
await this.mutate((store) => {
|
|
47
|
+
store.currentSessionId = undefined;
|
|
48
|
+
for (const session of store.sessions)
|
|
49
|
+
session.status = 'idle';
|
|
50
|
+
return undefined;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async send(sessionId, message) {
|
|
54
|
+
return this.mutate((store) => {
|
|
55
|
+
const session = store.sessions.find((entry) => entry.id === sessionId);
|
|
56
|
+
if (!session)
|
|
57
|
+
throw new Error(`Cloud session not found: ${sessionId}`);
|
|
58
|
+
const now = new Date().toISOString();
|
|
59
|
+
const reply = `[simulated-cloud] received: ${message}`;
|
|
60
|
+
const outgoing = [
|
|
61
|
+
{ id: randomUUID(), role: 'user', content: message, createdAt: now },
|
|
62
|
+
{ id: randomUUID(), role: 'assistant', content: reply, createdAt: now },
|
|
63
|
+
];
|
|
64
|
+
session.messages.push(...outgoing);
|
|
65
|
+
if (session.snapshot) {
|
|
66
|
+
session.snapshot.messages.push({ role: 'user', content: message }, { role: 'assistant', content: reply });
|
|
67
|
+
}
|
|
68
|
+
session.messageCount = session.messages.length;
|
|
69
|
+
session.lastMessage = reply;
|
|
70
|
+
session.updatedAt = now;
|
|
71
|
+
return {
|
|
72
|
+
sessionId: session.id,
|
|
73
|
+
response: reply,
|
|
74
|
+
messageCount: session.messageCount,
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async list() {
|
|
79
|
+
const store = this.read();
|
|
80
|
+
return store.sessions
|
|
81
|
+
.map((session) => cloneRecord(session))
|
|
82
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
83
|
+
}
|
|
84
|
+
async destroy(sessionId) {
|
|
85
|
+
return this.mutate((store) => {
|
|
86
|
+
const before = store.sessions.length;
|
|
87
|
+
store.sessions = store.sessions.filter((entry) => entry.id !== sessionId);
|
|
88
|
+
if (store.currentSessionId === sessionId)
|
|
89
|
+
store.currentSessionId = undefined;
|
|
90
|
+
return store.sessions.length !== before;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async getStatus(sessionId) {
|
|
94
|
+
const store = this.read();
|
|
95
|
+
const session = store.sessions.find((entry) => entry.id === sessionId);
|
|
96
|
+
if (!session) {
|
|
97
|
+
return { id: sessionId, exists: false, status: 'missing', messageCount: 0 };
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
id: session.id,
|
|
101
|
+
exists: true,
|
|
102
|
+
status: session.status,
|
|
103
|
+
updatedAt: session.updatedAt,
|
|
104
|
+
lastSyncedAt: session.lastSyncedAt,
|
|
105
|
+
messageCount: session.messageCount,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async sync(sessionId, localSession) {
|
|
109
|
+
return this.mutate((store) => {
|
|
110
|
+
const session = store.sessions.find((entry) => entry.id === sessionId);
|
|
111
|
+
if (!session)
|
|
112
|
+
throw new Error(`Cloud session not found: ${sessionId}`);
|
|
113
|
+
const now = new Date().toISOString();
|
|
114
|
+
session.snapshot = cloneState(localSession.state);
|
|
115
|
+
session.messages = localSession.state.messages.map(toCloudMessage);
|
|
116
|
+
session.messageCount = session.messages.length;
|
|
117
|
+
session.lastMessage = session.messages.at(-1)?.content;
|
|
118
|
+
session.lastSyncedAt = now;
|
|
119
|
+
session.updatedAt = now;
|
|
120
|
+
session.status = store.currentSessionId === sessionId ? 'connected' : session.status;
|
|
121
|
+
return cloneRecord(session);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
currentSessionId() {
|
|
125
|
+
return this.read().currentSessionId;
|
|
126
|
+
}
|
|
127
|
+
mutate(operation) {
|
|
128
|
+
const store = this.read();
|
|
129
|
+
const result = operation(store);
|
|
130
|
+
this.write(store);
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
read() {
|
|
134
|
+
ensureParentDirectory(this.filePath);
|
|
135
|
+
if (!fs.existsSync(this.filePath)) {
|
|
136
|
+
return { version: CLOUD_STORE_VERSION, sessions: [] };
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
const parsed = JSON.parse(fs.readFileSync(this.filePath, 'utf8'));
|
|
140
|
+
if (!parsed || typeof parsed !== 'object')
|
|
141
|
+
return { version: CLOUD_STORE_VERSION, sessions: [] };
|
|
142
|
+
return {
|
|
143
|
+
version: CLOUD_STORE_VERSION,
|
|
144
|
+
currentSessionId: typeof parsed.currentSessionId === 'string' ? parsed.currentSessionId : undefined,
|
|
145
|
+
sessions: Array.isArray(parsed.sessions)
|
|
146
|
+
? parsed.sessions.map(normalizeRecord).filter(isPresent)
|
|
147
|
+
: [],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return { version: CLOUD_STORE_VERSION, sessions: [] };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
write(store) {
|
|
155
|
+
ensureParentDirectory(this.filePath);
|
|
156
|
+
fs.writeFileSync(this.filePath, JSON.stringify(store, null, 2), 'utf8');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
export class CloudSession {
|
|
160
|
+
config;
|
|
161
|
+
client;
|
|
162
|
+
currentId;
|
|
163
|
+
constructor(config) {
|
|
164
|
+
this.config = config;
|
|
165
|
+
this.currentId = config.sessionId;
|
|
166
|
+
this.client = new SimulatedCloudHttpClient(resolveCloudSessionsPath());
|
|
167
|
+
}
|
|
168
|
+
async create(opts) {
|
|
169
|
+
const session = await this.client.create(this.config, opts);
|
|
170
|
+
this.currentId = session.id;
|
|
171
|
+
return session;
|
|
172
|
+
}
|
|
173
|
+
async connect(sessionId) {
|
|
174
|
+
const session = await this.client.connect(sessionId);
|
|
175
|
+
this.currentId = session.id;
|
|
176
|
+
return session;
|
|
177
|
+
}
|
|
178
|
+
async disconnect() {
|
|
179
|
+
await this.client.disconnect();
|
|
180
|
+
this.currentId = undefined;
|
|
181
|
+
}
|
|
182
|
+
async send(message) {
|
|
183
|
+
const sessionId = this.getConnectedSessionId();
|
|
184
|
+
if (!sessionId)
|
|
185
|
+
throw new Error('No cloud session connected.');
|
|
186
|
+
return this.client.send(sessionId, message);
|
|
187
|
+
}
|
|
188
|
+
async list() {
|
|
189
|
+
return this.client.list();
|
|
190
|
+
}
|
|
191
|
+
async destroy(sessionId) {
|
|
192
|
+
const destroyed = await this.client.destroy(sessionId);
|
|
193
|
+
if (destroyed && this.currentId === sessionId)
|
|
194
|
+
this.currentId = undefined;
|
|
195
|
+
return destroyed;
|
|
196
|
+
}
|
|
197
|
+
async getStatus(sessionId) {
|
|
198
|
+
return this.client.getStatus(sessionId);
|
|
199
|
+
}
|
|
200
|
+
async sync(sessionId, localSession) {
|
|
201
|
+
const synced = await this.client.sync(sessionId, localSession);
|
|
202
|
+
if (!this.currentId)
|
|
203
|
+
this.currentId = sessionId;
|
|
204
|
+
return synced;
|
|
205
|
+
}
|
|
206
|
+
getConnectedSessionId() {
|
|
207
|
+
return this.currentId || this.client.currentSessionId();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function resolveCloudSessionsPath() {
|
|
211
|
+
return (process.env[CLOUD_SESSIONS_PATH_ENV] ||
|
|
212
|
+
path.join(os.homedir(), '.icopilot', 'cloud-sessions.json'));
|
|
213
|
+
}
|
|
214
|
+
function ensureParentDirectory(filePath) {
|
|
215
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
216
|
+
}
|
|
217
|
+
function normalizeRecord(record) {
|
|
218
|
+
if (!record || typeof record !== 'object')
|
|
219
|
+
return null;
|
|
220
|
+
const candidate = record;
|
|
221
|
+
if (typeof candidate.id !== 'string')
|
|
222
|
+
return null;
|
|
223
|
+
return {
|
|
224
|
+
id: candidate.id,
|
|
225
|
+
name: typeof candidate.name === 'string' && candidate.name.trim() ? candidate.name : candidate.id,
|
|
226
|
+
endpoint: typeof candidate.endpoint === 'string' ? candidate.endpoint : '',
|
|
227
|
+
status: candidate.status === 'connected' ? 'connected' : 'idle',
|
|
228
|
+
createdAt: typeof candidate.createdAt === 'string' ? candidate.createdAt : new Date().toISOString(),
|
|
229
|
+
updatedAt: typeof candidate.updatedAt === 'string' ? candidate.updatedAt : new Date().toISOString(),
|
|
230
|
+
lastSyncedAt: typeof candidate.lastSyncedAt === 'string' ? candidate.lastSyncedAt : undefined,
|
|
231
|
+
messageCount: typeof candidate.messageCount === 'number' ? candidate.messageCount : 0,
|
|
232
|
+
lastMessage: typeof candidate.lastMessage === 'string' ? candidate.lastMessage : undefined,
|
|
233
|
+
messages: Array.isArray(candidate.messages)
|
|
234
|
+
? candidate.messages.map(normalizeMessage).filter(isPresent)
|
|
235
|
+
: [],
|
|
236
|
+
snapshot: candidate.snapshot ? cloneState(candidate.snapshot) : undefined,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function normalizeMessage(message) {
|
|
240
|
+
if (!message || typeof message !== 'object')
|
|
241
|
+
return null;
|
|
242
|
+
const candidate = message;
|
|
243
|
+
if (typeof candidate.id !== 'string' || typeof candidate.content !== 'string')
|
|
244
|
+
return null;
|
|
245
|
+
return {
|
|
246
|
+
id: candidate.id,
|
|
247
|
+
role: typeof candidate.role === 'string' ? candidate.role : 'message',
|
|
248
|
+
content: candidate.content,
|
|
249
|
+
createdAt: typeof candidate.createdAt === 'string' ? candidate.createdAt : new Date().toISOString(),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function toCloudMessage(message) {
|
|
253
|
+
return {
|
|
254
|
+
id: randomUUID(),
|
|
255
|
+
role: typeof message.role === 'string' ? message.role : 'message',
|
|
256
|
+
content: contentToText(message.content),
|
|
257
|
+
createdAt: new Date().toISOString(),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function contentToText(content) {
|
|
261
|
+
if (typeof content === 'string')
|
|
262
|
+
return content;
|
|
263
|
+
if (Array.isArray(content)) {
|
|
264
|
+
return content
|
|
265
|
+
.map((part) => {
|
|
266
|
+
if (typeof part?.text === 'string')
|
|
267
|
+
return part.text;
|
|
268
|
+
if (typeof part?.type === 'string')
|
|
269
|
+
return JSON.stringify(part);
|
|
270
|
+
return '';
|
|
271
|
+
})
|
|
272
|
+
.filter(Boolean)
|
|
273
|
+
.join('\n');
|
|
274
|
+
}
|
|
275
|
+
if (content == null)
|
|
276
|
+
return '';
|
|
277
|
+
return JSON.stringify(content, null, 2);
|
|
278
|
+
}
|
|
279
|
+
function cloneRecord(record) {
|
|
280
|
+
return {
|
|
281
|
+
...record,
|
|
282
|
+
messages: record.messages.map((message) => ({ ...message })),
|
|
283
|
+
snapshot: record.snapshot ? cloneState(record.snapshot) : undefined,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function cloneState(state) {
|
|
287
|
+
return JSON.parse(JSON.stringify(state));
|
|
288
|
+
}
|
|
289
|
+
function isPresent(value) {
|
|
290
|
+
return value !== null;
|
|
291
|
+
}
|