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.
Files changed (40) hide show
  1. package/README.md +17 -16
  2. package/dist/cli.js +301862 -0
  3. package/dist/koffi-216xhpes.node +0 -0
  4. package/dist/koffi-2erktc37.node +0 -0
  5. package/dist/koffi-2rrez93a.node +0 -0
  6. package/dist/koffi-2wv0r22g.node +0 -0
  7. package/dist/koffi-3kae4xj3.node +0 -0
  8. package/dist/koffi-3rkr2zqv.node +0 -0
  9. package/dist/koffi-abxfktv9.node +0 -0
  10. package/dist/koffi-c67c0c5b.node +0 -0
  11. package/dist/koffi-cnf0q0dx.node +0 -0
  12. package/dist/koffi-df38sqz5.node +0 -0
  13. package/dist/koffi-gfbqb3a0.node +0 -0
  14. package/dist/koffi-kjemmmem.node +0 -0
  15. package/dist/koffi-kkrfq9yv.node +0 -0
  16. package/dist/koffi-mzaqwwqy.node +0 -0
  17. package/dist/koffi-q49fgkeq.node +0 -0
  18. package/dist/koffi-q54bk8bf.node +0 -0
  19. package/dist/koffi-x1790w0j.node +0 -0
  20. package/dist/koffi-yxvjwcj6.node +0 -0
  21. package/package.json +8 -7
  22. package/web-ui-dist/_shell.html +2 -2
  23. package/web-ui-dist/assets/{card-BUP-xovx.js → card-Ce8RCN8-.js} +1 -1
  24. package/web-ui-dist/assets/{extensions-DC620Nmx.js → extensions-D-3Wl_TA.js} +1 -1
  25. package/web-ui-dist/assets/{index-DurjG9O_.js → index-ClDMn-6f.js} +1 -1
  26. package/web-ui-dist/assets/{loader-circle-DbOtKfCA.js → loader-circle-CpT1_nns.js} +1 -1
  27. package/web-ui-dist/assets/{main-B2sRcuyZ.js → main-J9rrgTOF.js} +4 -4
  28. package/web-ui-dist/assets/{sessions._sessionId-BJazw9EJ.js → sessions._sessionId-D6gfJDaW.js} +2 -2
  29. package/web-ui-dist/assets/{settings-Bv8oeIho.js → settings-ZDTymG3K.js} +1 -1
  30. package/web-ui-dist/manifest.json +23 -23
  31. package/src/agent.ts +0 -118
  32. package/src/channels/channel.ts +0 -57
  33. package/src/channels/slack.ts +0 -376
  34. package/src/channels/web.ts +0 -1375
  35. package/src/cli.ts +0 -505
  36. package/src/config.ts +0 -261
  37. package/src/daemon.ts +0 -380
  38. package/src/extensions/workspace-jail.ts +0 -171
  39. package/src/service.ts +0 -374
  40. 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
- }