clankie 0.2.2 → 0.2.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 +17 -16
- package/dist/cli.js +301862 -0
- package/dist/koffi-216xhpes.node +0 -0
- package/dist/koffi-2erktc37.node +0 -0
- package/dist/koffi-2rrez93a.node +0 -0
- package/dist/koffi-2wv0r22g.node +0 -0
- package/dist/koffi-3kae4xj3.node +0 -0
- package/dist/koffi-3rkr2zqv.node +0 -0
- package/dist/koffi-abxfktv9.node +0 -0
- package/dist/koffi-c67c0c5b.node +0 -0
- package/dist/koffi-cnf0q0dx.node +0 -0
- package/dist/koffi-df38sqz5.node +0 -0
- package/dist/koffi-gfbqb3a0.node +0 -0
- package/dist/koffi-kjemmmem.node +0 -0
- package/dist/koffi-kkrfq9yv.node +0 -0
- package/dist/koffi-mzaqwwqy.node +0 -0
- package/dist/koffi-q49fgkeq.node +0 -0
- package/dist/koffi-q54bk8bf.node +0 -0
- package/dist/koffi-x1790w0j.node +0 -0
- package/dist/koffi-yxvjwcj6.node +0 -0
- package/package.json +8 -7
- package/web-ui-dist/_shell.html +2 -2
- package/web-ui-dist/assets/{card-BUP-xovx.js → card-Ce8RCN8-.js} +1 -1
- package/web-ui-dist/assets/{extensions-DC620Nmx.js → extensions-D-3Wl_TA.js} +1 -1
- package/web-ui-dist/assets/{index-DurjG9O_.js → index-ClDMn-6f.js} +1 -1
- package/web-ui-dist/assets/{loader-circle-DbOtKfCA.js → loader-circle-CpT1_nns.js} +1 -1
- package/web-ui-dist/assets/{main-B2sRcuyZ.js → main-J9rrgTOF.js} +4 -4
- package/web-ui-dist/assets/{sessions._sessionId-BJazw9EJ.js → sessions._sessionId-D6gfJDaW.js} +2 -2
- package/web-ui-dist/assets/{settings-Bv8oeIho.js → settings-ZDTymG3K.js} +1 -1
- package/web-ui-dist/manifest.json +23 -23
- package/src/agent.ts +0 -118
- package/src/channels/channel.ts +0 -57
- package/src/channels/slack.ts +0 -376
- package/src/channels/web.ts +0 -1375
- package/src/cli.ts +0 -505
- package/src/config.ts +0 -261
- package/src/daemon.ts +0 -380
- package/src/extensions/workspace-jail.ts +0 -171
- package/src/service.ts +0 -374
- package/src/sessions.ts +0 -262
package/src/sessions.ts
DELETED
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared session management for channels.
|
|
3
|
-
*
|
|
4
|
-
* Both daemon (for Slack) and WebChannel (for web-ui) use this
|
|
5
|
-
* to create and cache agent sessions per chat.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
9
|
-
import { join } from "node:path";
|
|
10
|
-
import type { ImageContent } from "@mariozechner/pi-ai";
|
|
11
|
-
import {
|
|
12
|
-
type AgentSession,
|
|
13
|
-
AuthStorage,
|
|
14
|
-
type CreateAgentSessionResult,
|
|
15
|
-
createAgentSession,
|
|
16
|
-
DefaultResourceLoader,
|
|
17
|
-
type ExtensionFactory,
|
|
18
|
-
ModelRegistry,
|
|
19
|
-
SessionManager,
|
|
20
|
-
} from "@mariozechner/pi-coding-agent";
|
|
21
|
-
import type { Attachment } from "./channels/channel.ts";
|
|
22
|
-
import { type AppConfig, getAgentDir, getAppDir, getAuthPath, getWorkspace } from "./config.ts";
|
|
23
|
-
import { createWorkspaceJailExtension } from "./extensions/workspace-jail.ts";
|
|
24
|
-
|
|
25
|
-
// ─── Session cache (one session per chat) ──────────────────────────────────────
|
|
26
|
-
|
|
27
|
-
const sessionCache = new Map<string, AgentSession>();
|
|
28
|
-
|
|
29
|
-
// Track active session name per chat (for /switch command)
|
|
30
|
-
const activeSessionNames = new Map<string, string>();
|
|
31
|
-
|
|
32
|
-
/** Lock to serialize message processing per chat */
|
|
33
|
-
const chatLocks = new Map<string, Promise<void>>();
|
|
34
|
-
|
|
35
|
-
// ─── Session factory ───────────────────────────────────────────────────────────
|
|
36
|
-
|
|
37
|
-
export async function getOrCreateSession(chatKey: string, config: AppConfig): Promise<AgentSession> {
|
|
38
|
-
console.log(`[session] getOrCreateSession called - chatKey: ${chatKey}, cache has: ${sessionCache.size} entries`);
|
|
39
|
-
const cached = sessionCache.get(chatKey);
|
|
40
|
-
if (cached) {
|
|
41
|
-
console.log(`[session] Returning cached session - chatKey: ${chatKey}, session.sessionId: ${cached.sessionId}`);
|
|
42
|
-
return cached;
|
|
43
|
-
}
|
|
44
|
-
console.log(`[session] No cached session found for chatKey: ${chatKey}, creating new...`);
|
|
45
|
-
|
|
46
|
-
const agentDir = getAgentDir(config);
|
|
47
|
-
const cwd = getWorkspace(config);
|
|
48
|
-
|
|
49
|
-
const authStorage = AuthStorage.create(getAuthPath());
|
|
50
|
-
const modelRegistry = new ModelRegistry(authStorage);
|
|
51
|
-
|
|
52
|
-
// Build extension factories (workspace jail if enabled)
|
|
53
|
-
const extensionFactories: ExtensionFactory[] = [];
|
|
54
|
-
const restrictToWorkspace = config.agent?.restrictToWorkspace ?? true; // default: enabled
|
|
55
|
-
if (restrictToWorkspace) {
|
|
56
|
-
const allowedPaths = config.agent?.allowedPaths ?? [];
|
|
57
|
-
extensionFactories.push(createWorkspaceJailExtension(cwd, allowedPaths));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const loader = new DefaultResourceLoader({
|
|
61
|
-
cwd,
|
|
62
|
-
agentDir,
|
|
63
|
-
extensionFactories,
|
|
64
|
-
});
|
|
65
|
-
await loader.reload();
|
|
66
|
-
|
|
67
|
-
// Use a stable session directory per chat so conversations persist across restarts
|
|
68
|
-
const sessionDir = join(getAppDir(), "sessions", chatKey);
|
|
69
|
-
|
|
70
|
-
// Ensure session directory exists
|
|
71
|
-
if (!existsSync(sessionDir)) {
|
|
72
|
-
mkdirSync(sessionDir, { recursive: true });
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// SessionManager.continueRecent continues the most recent session in the given directory
|
|
76
|
-
// It SHOULD keep using that directory for all future saves
|
|
77
|
-
const sessionManager = SessionManager.continueRecent(cwd, sessionDir);
|
|
78
|
-
console.log(`[session] SessionManager created for chatKey: ${chatKey}, sessionDir: ${sessionDir}`);
|
|
79
|
-
|
|
80
|
-
// Resolve model from config → pi auto-detection
|
|
81
|
-
const modelSpec = config.agent?.model?.primary;
|
|
82
|
-
let model: ReturnType<typeof modelRegistry.find> | undefined;
|
|
83
|
-
if (modelSpec) {
|
|
84
|
-
const slash = modelSpec.indexOf("/");
|
|
85
|
-
if (slash !== -1) {
|
|
86
|
-
const provider = modelSpec.substring(0, slash);
|
|
87
|
-
const modelId = modelSpec.substring(slash + 1);
|
|
88
|
-
model = modelRegistry.find(provider, modelId);
|
|
89
|
-
if (!model) {
|
|
90
|
-
console.warn(`[session] Warning: model "${modelSpec}" from config not found, falling back to auto-detection`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const result: CreateAgentSessionResult = await createAgentSession({
|
|
96
|
-
cwd,
|
|
97
|
-
agentDir,
|
|
98
|
-
authStorage,
|
|
99
|
-
modelRegistry,
|
|
100
|
-
resourceLoader: loader,
|
|
101
|
-
sessionManager,
|
|
102
|
-
model,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const { session } = result;
|
|
106
|
-
console.log(
|
|
107
|
-
`[session] Created AgentSession - chatKey: ${chatKey}, session.sessionId: ${session.sessionId}, session.sessionFile: ${session.sessionFile}`,
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
// Bind extensions (headless — no UI)
|
|
111
|
-
await session.bindExtensions({
|
|
112
|
-
commandContextActions: {
|
|
113
|
-
waitForIdle: () => session.agent.waitForIdle(),
|
|
114
|
-
newSession: async (opts) => {
|
|
115
|
-
const success = await session.newSession({ parentSession: opts?.parentSession });
|
|
116
|
-
if (success && opts?.setup) {
|
|
117
|
-
await opts.setup(session.sessionManager);
|
|
118
|
-
}
|
|
119
|
-
return { cancelled: !success };
|
|
120
|
-
},
|
|
121
|
-
fork: async (entryId) => {
|
|
122
|
-
const r = await session.fork(entryId);
|
|
123
|
-
return { cancelled: r.cancelled };
|
|
124
|
-
},
|
|
125
|
-
navigateTree: async (targetId, opts) => {
|
|
126
|
-
const r = await session.navigateTree(targetId, {
|
|
127
|
-
summarize: opts?.summarize,
|
|
128
|
-
customInstructions: opts?.customInstructions,
|
|
129
|
-
replaceInstructions: opts?.replaceInstructions,
|
|
130
|
-
label: opts?.label,
|
|
131
|
-
});
|
|
132
|
-
return { cancelled: r.cancelled };
|
|
133
|
-
},
|
|
134
|
-
switchSession: async (sessionPath) => {
|
|
135
|
-
const success = await session.switchSession(sessionPath);
|
|
136
|
-
return { cancelled: !success };
|
|
137
|
-
},
|
|
138
|
-
reload: async () => {
|
|
139
|
-
await session.reload();
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
onError: (err) => {
|
|
143
|
-
console.error(`[session] Extension error (${err.extensionPath}): ${err.error}`);
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
// Subscribe to enable session persistence
|
|
148
|
-
session.subscribe(() => {});
|
|
149
|
-
|
|
150
|
-
console.log(`[session] Caching session - chatKey: ${chatKey}, session.sessionId: ${session.sessionId}`);
|
|
151
|
-
sessionCache.set(chatKey, session);
|
|
152
|
-
|
|
153
|
-
// Log the cache state
|
|
154
|
-
console.log(`[session] Session cache now has ${sessionCache.size} entries`);
|
|
155
|
-
|
|
156
|
-
return session;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// ─── Session helpers ───────────────────────────────────────────────────────────
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* List all session names for a given chat identifier.
|
|
163
|
-
* Scans ~/.clankie/sessions/ for directories matching the chatIdentifier prefix.
|
|
164
|
-
*/
|
|
165
|
-
export function listSessionNames(chatIdentifier: string): string[] {
|
|
166
|
-
const sessionsDir = join(getAppDir(), "sessions");
|
|
167
|
-
if (!existsSync(sessionsDir)) {
|
|
168
|
-
return [];
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
try {
|
|
172
|
-
const { readdirSync, statSync } = require("node:fs");
|
|
173
|
-
const entries = readdirSync(sessionsDir);
|
|
174
|
-
const sessionNames = new Set<string>();
|
|
175
|
-
|
|
176
|
-
for (const entry of entries) {
|
|
177
|
-
// Session directories are named: {channel}_{chatId}_{sessionName}
|
|
178
|
-
// We want to extract unique sessionNames for this chatIdentifier
|
|
179
|
-
if (entry.startsWith(`${chatIdentifier}_`)) {
|
|
180
|
-
const entryPath = join(sessionsDir, entry);
|
|
181
|
-
if (statSync(entryPath).isDirectory()) {
|
|
182
|
-
// Extract session name from: chatIdentifier_sessionName
|
|
183
|
-
const sessionName = entry.substring(chatIdentifier.length + 1);
|
|
184
|
-
if (sessionName) {
|
|
185
|
-
sessionNames.add(sessionName);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return Array.from(sessionNames).sort();
|
|
192
|
-
} catch (err) {
|
|
193
|
-
console.error(`[session] Error listing session names: ${err instanceof Error ? err.message : String(err)}`);
|
|
194
|
-
return [];
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Get or set active session name for a chat.
|
|
200
|
-
*/
|
|
201
|
-
export function getActiveSessionName(chatIdentifier: string): string {
|
|
202
|
-
return activeSessionNames.get(chatIdentifier) ?? "default";
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export function setActiveSessionName(chatIdentifier: string, sessionName: string): void {
|
|
206
|
-
activeSessionNames.set(chatIdentifier, sessionName);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// ─── Attachment helpers ────────────────────────────────────────────────────────
|
|
210
|
-
|
|
211
|
-
const IMAGE_MIME_PREFIXES = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
|
212
|
-
|
|
213
|
-
/** Convert image attachments to pi's ImageContent format for vision models. */
|
|
214
|
-
export function toImageContents(attachments?: Attachment[]): ImageContent[] {
|
|
215
|
-
if (!attachments) return [];
|
|
216
|
-
return attachments
|
|
217
|
-
.filter((a) => IMAGE_MIME_PREFIXES.some((prefix) => a.mimeType.startsWith(prefix)))
|
|
218
|
-
.map((a) => ({ type: "image" as const, data: a.data, mimeType: a.mimeType }));
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/** Save non-image attachments to disk and return their paths. */
|
|
222
|
-
export async function saveNonImageAttachments(
|
|
223
|
-
attachments: Attachment[] | undefined,
|
|
224
|
-
chatKey: string,
|
|
225
|
-
): Promise<{ fileName: string; path: string }[]> {
|
|
226
|
-
if (!attachments) return [];
|
|
227
|
-
|
|
228
|
-
const nonImages = attachments.filter((a) => !IMAGE_MIME_PREFIXES.some((prefix) => a.mimeType.startsWith(prefix)));
|
|
229
|
-
if (nonImages.length === 0) return [];
|
|
230
|
-
|
|
231
|
-
const { mkdirSync, writeFileSync } = await import("node:fs");
|
|
232
|
-
const { join } = await import("node:path");
|
|
233
|
-
|
|
234
|
-
const dir = join(getAppDir(), "attachments", chatKey);
|
|
235
|
-
mkdirSync(dir, { recursive: true });
|
|
236
|
-
|
|
237
|
-
const results: { fileName: string; path: string }[] = [];
|
|
238
|
-
for (const att of nonImages) {
|
|
239
|
-
const name = att.fileName || `file_${Date.now()}`;
|
|
240
|
-
const filePath = join(dir, name);
|
|
241
|
-
writeFileSync(filePath, Buffer.from(att.data, "base64"));
|
|
242
|
-
results.push({ fileName: name, path: filePath });
|
|
243
|
-
console.log(`[session] Saved attachment: ${filePath} (${att.mimeType})`);
|
|
244
|
-
}
|
|
245
|
-
return results;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// ─── Chat lock helpers ─────────────────────────────────────────────────────────
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Acquire a lock for a chat to serialize message processing.
|
|
252
|
-
* Returns a promise that completes when the action finishes.
|
|
253
|
-
*/
|
|
254
|
-
export async function withChatLock<T>(chatKey: string, action: () => Promise<T>): Promise<T> {
|
|
255
|
-
const previous = chatLocks.get(chatKey) ?? Promise.resolve();
|
|
256
|
-
const current = previous.then(action, action); // Run action even if previous failed
|
|
257
|
-
chatLocks.set(
|
|
258
|
-
chatKey,
|
|
259
|
-
current.catch(() => {}),
|
|
260
|
-
); // Swallow errors in the chain
|
|
261
|
-
return current;
|
|
262
|
-
}
|