pi-vault-mind 0.14.2 → 0.16.3

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 (164) hide show
  1. package/CHANGELOG.md +293 -0
  2. package/README.md +5 -5
  3. package/agents/broadcaster.agent.md +2 -2
  4. package/agents/heavy-lifter.agent.md +1 -1
  5. package/agents/main.agent.md +29 -0
  6. package/agents/manager.agent.md +1 -1
  7. package/agents/miner.agent.md +1 -1
  8. package/dist/packages/obsidian/src/client.js +331 -0
  9. package/dist/packages/obsidian/src/types.js +1 -0
  10. package/dist/src/activity.d.ts +49 -0
  11. package/dist/src/activity.js +175 -0
  12. package/dist/src/agent-bus/Agent.d.ts +9 -1
  13. package/dist/src/agent-bus/Agent.js +18 -2
  14. package/dist/src/agent-bus/index.d.ts +5 -5
  15. package/dist/src/agent-bus/index.js +1 -1
  16. package/dist/src/agent-queue.js +12 -3
  17. package/dist/src/agents/BroadcasterAgent.d.ts +1 -1
  18. package/dist/src/agents/BroadcasterAgent.js +2 -2
  19. package/dist/src/agents/HeavyLifterAgent.d.ts +1 -1
  20. package/dist/src/agents/HeavyLifterAgent.js +2 -2
  21. package/dist/src/agents/ManagerAgent.js +1 -1
  22. package/dist/src/agents/MinerAgent.d.ts +1 -1
  23. package/dist/src/agents/MinerAgent.js +2 -2
  24. package/dist/src/agents/index.d.ts +2 -2
  25. package/dist/src/agents/index.js +2 -2
  26. package/dist/src/auth.d.ts +17 -15
  27. package/dist/src/auth.js +26 -37
  28. package/dist/src/autosync.js +2 -2
  29. package/dist/src/commands.d.ts +2 -19
  30. package/dist/src/commands.js +301 -231
  31. package/dist/src/config-keys.d.ts +16 -0
  32. package/dist/src/config-keys.js +45 -0
  33. package/dist/src/discover-schema.js +4 -4
  34. package/dist/src/edits.d.ts +21 -0
  35. package/dist/src/edits.js +75 -0
  36. package/dist/src/embedding-probe.d.ts +34 -0
  37. package/dist/src/embedding-probe.js +142 -0
  38. package/dist/src/embedding-providers.d.ts +133 -0
  39. package/dist/src/embedding-providers.js +173 -0
  40. package/dist/src/embedding-secrets.d.ts +38 -0
  41. package/dist/src/embedding-secrets.js +291 -0
  42. package/dist/src/engine.d.ts +6 -0
  43. package/dist/src/engine.js +78 -6
  44. package/dist/src/events.js +7 -10
  45. package/dist/src/extension-packages.d.ts +9 -0
  46. package/dist/src/extension-packages.js +14 -0
  47. package/dist/src/git.d.ts +19 -0
  48. package/dist/src/git.js +78 -0
  49. package/dist/src/graph.d.ts +2 -1
  50. package/dist/src/graph.js +47 -13
  51. package/dist/src/identities-config.d.ts +6 -0
  52. package/dist/src/identities-config.js +39 -0
  53. package/dist/src/identity-injector.js +1 -1
  54. package/dist/src/index.js +9 -26
  55. package/dist/src/lance.d.ts +9 -0
  56. package/dist/src/lance.js +143 -203
  57. package/dist/src/modal-client.d.ts +16 -0
  58. package/dist/src/modal-client.js +16 -0
  59. package/dist/src/modal-config.d.ts +37 -14
  60. package/dist/src/modal-config.js +59 -32
  61. package/dist/src/model-router.d.ts +27 -0
  62. package/dist/src/model-router.js +67 -0
  63. package/dist/src/models.d.ts +42 -0
  64. package/dist/src/models.js +78 -0
  65. package/dist/src/personalize.js +17 -3
  66. package/dist/src/scaffold.d.ts +48 -0
  67. package/dist/src/scaffold.js +246 -0
  68. package/dist/src/server.d.ts +47 -4
  69. package/dist/src/server.js +988 -221
  70. package/dist/src/session-search.d.ts +63 -0
  71. package/dist/src/session-search.js +379 -0
  72. package/dist/src/settings-ui.d.ts +2 -2
  73. package/dist/src/settings-ui.js +76 -135
  74. package/dist/src/sync.d.ts +3 -3
  75. package/dist/src/sync.js +19 -15
  76. package/dist/src/tool-catalog.d.ts +18 -0
  77. package/dist/src/tool-catalog.js +232 -0
  78. package/dist/src/tools/marksman.js +1 -1
  79. package/dist/src/tools.js +111 -65
  80. package/dist/src/types.d.ts +127 -71
  81. package/dist/src/utils.d.ts +57 -6
  82. package/dist/src/utils.js +114 -28
  83. package/dist/src/vault-tools.d.ts +34 -0
  84. package/dist/src/vault-tools.js +99 -0
  85. package/dist/src/vault-writer.js +4 -1
  86. package/dist/src/vm-handlers.js +140 -43
  87. package/dist/src/watcher.js +9 -9
  88. package/dist/test/activity-tool.test.js +54 -0
  89. package/dist/test/agent-bus/Agent.test.js +174 -0
  90. package/dist/test/agent-bus/AgentLoader.test.js +180 -0
  91. package/dist/test/agent-bus/AgentRegistry.test.js +86 -0
  92. package/dist/test/agent-bus/LLMProvider.test.js +162 -0
  93. package/dist/test/agent-bus/TaskQueue.test.js +256 -0
  94. package/dist/test/agent-dispatch-context.test.js +73 -0
  95. package/dist/test/agent-queue.test.js +1 -1
  96. package/dist/test/agents/Agents.test.js +282 -0
  97. package/dist/test/auth.test.js +57 -56
  98. package/dist/test/bridge-identity.test.js +34 -3
  99. package/dist/test/classify-search-mode.test.js +51 -0
  100. package/dist/test/commands.test.js +410 -0
  101. package/dist/test/config-merge.test.js +98 -0
  102. package/dist/test/context-capture.test.js +56 -0
  103. package/dist/test/discover-schema.test.js +8 -5
  104. package/dist/test/dispatch.test.js +45 -91
  105. package/dist/test/edits.test.js +144 -0
  106. package/dist/test/embedding-probe.test.js +424 -0
  107. package/dist/test/embedding-providers.test.js +157 -0
  108. package/dist/test/embedding-secrets.test.js +210 -0
  109. package/dist/test/engine-identity.test.js +1 -1
  110. package/dist/test/events.test.js +675 -0
  111. package/dist/test/git-bridge.test.js +160 -0
  112. package/dist/test/graph.test.js +464 -0
  113. package/dist/test/helpers/server.js +81 -0
  114. package/dist/test/identity-injector.test.js +197 -0
  115. package/dist/test/index.test.js +5 -7
  116. package/dist/test/lance-modal.test.js +67 -2
  117. package/dist/test/lance.test.js +429 -0
  118. package/dist/test/modal-client.test.js +1 -1
  119. package/dist/test/modal-config.test.js +92 -79
  120. package/dist/test/model-readiness.test.js +122 -0
  121. package/dist/test/model-router.test.js +99 -0
  122. package/dist/test/obsidian-client.test.js +42 -0
  123. package/dist/test/personalize.test.js +1 -1
  124. package/dist/test/rest-models.test.js +153 -0
  125. package/dist/test/rest-queue.test.js +135 -87
  126. package/dist/test/rest-setup.test.js +625 -115
  127. package/dist/test/rest-vm.test.js +355 -94
  128. package/dist/test/server-cors.test.js +48 -0
  129. package/dist/test/server-ws.test.js +61 -34
  130. package/dist/test/session-crud.test.js +323 -0
  131. package/dist/test/session-search.test.js +139 -0
  132. package/dist/test/settings-ui.test.js +113 -4
  133. package/dist/test/sync.test.js +12 -12
  134. package/dist/test/tombstone.test.js +8 -10
  135. package/dist/test/tool-catalog.test.js +86 -0
  136. package/dist/test/utils.test.js +588 -0
  137. package/dist/test/vault-tools.test.js +103 -12
  138. package/dist/test/vault-writer.test.js +371 -0
  139. package/dist/test/vm-handlers.test.js +620 -0
  140. package/extension-packages.json +9 -0
  141. package/package.json +107 -90
  142. package/pi-vault-mind.config.example.json +24 -2
  143. package/scripts/check-deps.sh +35 -32
  144. package/scripts/cli-e2e.mjs +215 -0
  145. package/scripts/configuration-e2e.mjs +783 -0
  146. package/scripts/e2e-commands.mjs +391 -0
  147. package/scripts/e2e-ws.mjs +3 -2
  148. package/scripts/fetch-modal-token.sh +4 -3
  149. package/scripts/generate-config-keys-json.mjs +45 -0
  150. package/scripts/generate-extension-packages-json.mjs +55 -0
  151. package/scripts/pi-session-query +0 -0
  152. package/scripts/publish-obsidian-plugin.sh +0 -0
  153. package/scripts/reset-test-vault.sh +2 -2
  154. package/scripts/setup-test-config.sh +0 -0
  155. package/scripts/setup-vault-pi.sh +48 -9
  156. package/scripts/test-slash-commands.mjs +2 -2
  157. package/scripts/vault-pi.sh +12 -3
  158. package/skills/github-release-notes-file/SKILL.md +42 -0
  159. package/skills/obsidian-guided-test-session/SKILL.md +110 -0
  160. package/skills/obsidian-interactive-test/SKILL.md +88 -0
  161. package/skills/obsidian-plugin-publish/SKILL.md +58 -0
  162. package/skills/pi-vault-mind-release/SKILL.md +141 -0
  163. package/skills/pre-test-release/SKILL.md +116 -0
  164. package/dist/test/auto-indexer.test.js +0 -2
@@ -0,0 +1,331 @@
1
+ /**
2
+ * HTTP + WebSocket client for the pi-vault-mind extension.
3
+ *
4
+ * REST calls use `fetch`. WebSocket events use the native `WebSocket` global
5
+ * with exponential backoff on disconnect (1, 2, 4, 8, 16, 30 s).
6
+ */
7
+ export class VaultMindClient {
8
+ config;
9
+ ws = null;
10
+ reconnectTimer = null;
11
+ reconnectDelay = 1000;
12
+ intentionalClose = false;
13
+ eventHandlers = new Set();
14
+ stateHandlers = new Set();
15
+ _state = { connected: false };
16
+ constructor(config) {
17
+ this.config = config;
18
+ }
19
+ get baseUrl() {
20
+ return `http://${this.config.host}:${this.config.port}`;
21
+ }
22
+ get wsUrl() {
23
+ return `ws://${this.config.host}:${this.config.port}/agent/stream`;
24
+ }
25
+ get authHeaders() {
26
+ return {
27
+ "Content-Type": "application/json",
28
+ Authorization: `Bearer ${this.config.token}`,
29
+ };
30
+ }
31
+ setState(state) {
32
+ this._state = state;
33
+ for (const h of this.stateHandlers)
34
+ h(state);
35
+ }
36
+ get state() {
37
+ return this._state;
38
+ }
39
+ subscribeEvents(handler) {
40
+ this.eventHandlers.add(handler);
41
+ return () => this.eventHandlers.delete(handler);
42
+ }
43
+ subscribeState(handler) {
44
+ this.stateHandlers.add(handler);
45
+ return () => this.stateHandlers.delete(handler);
46
+ }
47
+ emit(event) {
48
+ for (const h of this.eventHandlers) {
49
+ try {
50
+ h(event);
51
+ }
52
+ catch {
53
+ // never let a bad listener break others
54
+ }
55
+ }
56
+ }
57
+ connect() {
58
+ if (this.ws)
59
+ return;
60
+ try {
61
+ this.ws = new WebSocket(this.wsUrl, [`Authorization: Bearer ${this.config.token}`]);
62
+ }
63
+ catch (err) {
64
+ this.setState({ connected: false, error: String(err), reconnecting: true });
65
+ this.scheduleReconnect();
66
+ return;
67
+ }
68
+ this.ws.onopen = () => {
69
+ this.reconnectDelay = 1000;
70
+ this.setState({ connected: true, reconnecting: false });
71
+ };
72
+ this.ws.onmessage = (event) => {
73
+ try {
74
+ const parsed = JSON.parse(event.data);
75
+ this.emit(parsed);
76
+ }
77
+ catch {
78
+ // ignore malformed events
79
+ }
80
+ };
81
+ this.ws.onclose = (event) => {
82
+ this.ws = null;
83
+ if (this.intentionalClose) {
84
+ this.setState({ connected: false, error: undefined, reconnecting: false });
85
+ return;
86
+ }
87
+ const wasConnected = this._state.connected;
88
+ this.setState({ connected: false, error: `closed ${event.code}`, reconnecting: true });
89
+ if (wasConnected || event.code !== 4401) {
90
+ this.scheduleReconnect();
91
+ }
92
+ };
93
+ this.ws.onerror = () => {
94
+ this.setState({ connected: this.ws?.readyState === WebSocket.OPEN, error: "socket error" });
95
+ };
96
+ }
97
+ disconnect() {
98
+ if (this.reconnectTimer) {
99
+ clearTimeout(this.reconnectTimer);
100
+ this.reconnectTimer = null;
101
+ }
102
+ this.intentionalClose = true;
103
+ if (this.ws) {
104
+ this.ws.close();
105
+ this.ws = null;
106
+ }
107
+ this.setState({ connected: false, error: undefined, reconnecting: false });
108
+ this.intentionalClose = false;
109
+ }
110
+ scheduleReconnect() {
111
+ if (this.reconnectTimer)
112
+ return;
113
+ this.reconnectTimer = setTimeout(() => {
114
+ this.reconnectTimer = null;
115
+ this.connect();
116
+ }, this.reconnectDelay);
117
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30000);
118
+ }
119
+ async httpJson(method, path, body) {
120
+ const res = await fetch(`${this.baseUrl}${path}`, {
121
+ method,
122
+ headers: this.authHeaders,
123
+ body: body ? JSON.stringify(body) : undefined,
124
+ });
125
+ const text = await res.text();
126
+ if (!res.ok) {
127
+ let serverMessage = text;
128
+ try {
129
+ const parsed = JSON.parse(text);
130
+ if (typeof parsed.message === "string")
131
+ serverMessage = parsed.message;
132
+ }
133
+ catch {
134
+ // keep raw text as the server message
135
+ }
136
+ // Never echo the request body or secret material in the thrown error.
137
+ throw new Error(`${res.status} ${serverMessage}`);
138
+ }
139
+ return text ? JSON.parse(text) : undefined;
140
+ }
141
+ async status() {
142
+ return (await this.httpJson("GET", "/vm/status"));
143
+ }
144
+ async setup(body) {
145
+ return (await this.httpJson("POST", "/vm/setup", body));
146
+ }
147
+ async probeEmbedding(body) {
148
+ return (await this.httpJson("POST", "/vm/embedding/probe", body));
149
+ }
150
+ async putEmbeddingSecrets(body) {
151
+ return (await this.httpJson("PUT", "/vm/embedding/secrets", body));
152
+ }
153
+ async startServer() {
154
+ return (await this.httpJson("POST", "/server/start"));
155
+ }
156
+ async toggleWatcher() {
157
+ return (await this.httpJson("POST", "/vm/watcher/toggle"));
158
+ }
159
+ async listQueue(status) {
160
+ const query = status ? `?status=${status}` : "";
161
+ return (await this.httpJson("GET", `/agent/queue${query}`));
162
+ }
163
+ async retryJob(id) {
164
+ return (await this.httpJson("POST", `/agent/jobs/${encodeURIComponent(id)}/retry`));
165
+ }
166
+ async cancelJob(id) {
167
+ return (await this.httpJson("POST", `/agent/jobs/${encodeURIComponent(id)}/cancel`));
168
+ }
169
+ /**
170
+ * Unified search with mode dispatch. Calls POST /vm/search with mode param.
171
+ * mode='hybrid' (default) = vector+FTS rank-merge, 'semantic' = vector-only,
172
+ * 'fts' = keyword, 'graph' = entity traversal, 'full' = all merged.
173
+ */
174
+ async searchWithMode(params) {
175
+ return (await this.httpJson("POST", "/vm/search", {
176
+ collection: params.collection ?? "main",
177
+ query: params.query,
178
+ mode: params.mode ?? "hybrid",
179
+ limit: params.limit ?? 5,
180
+ ...(params.entity ? { entity: params.entity } : {}),
181
+ ...(params.depth ? { depth: params.depth } : {}),
182
+ ...(params.sources ? { sources: params.sources } : {}),
183
+ }));
184
+ }
185
+ async gitStatus() {
186
+ return (await this.httpJson("GET", "/git/status"));
187
+ }
188
+ async gitBranches() {
189
+ return (await this.httpJson("GET", "/git/branches"));
190
+ }
191
+ async gitCheckout(branch) {
192
+ return (await this.httpJson("POST", "/git/checkout", { branch }));
193
+ }
194
+ async listSessions() {
195
+ return (await this.httpJson("GET", "/sessions"));
196
+ }
197
+ async getSession(id) {
198
+ return (await this.httpJson("GET", `/sessions/${encodeURIComponent(id)}`));
199
+ }
200
+ async renameSession(id, name) {
201
+ return (await this.httpJson("PATCH", `/sessions/${encodeURIComponent(id)}`, { name }));
202
+ }
203
+ async deleteSession(id) {
204
+ return (await this.httpJson("DELETE", `/sessions/${encodeURIComponent(id)}`));
205
+ }
206
+ async exportSession(id) {
207
+ return (await this.httpJson("POST", `/sessions/${encodeURIComponent(id)}/export`));
208
+ }
209
+ async archiveSession(id) {
210
+ return (await this.httpJson("POST", `/sessions/${encodeURIComponent(id)}/archive`));
211
+ }
212
+ /**
213
+ * List pending edits, optionally filtered by vault-relative file path.
214
+ */
215
+ async listEdits(filePath) {
216
+ const query = filePath ? `?path=${encodeURIComponent(filePath)}` : "";
217
+ const res = await this.httpJson("GET", `/vm/edits${query}`);
218
+ return res.edits;
219
+ }
220
+ /**
221
+ * Get a specific pending edit by ID.
222
+ */
223
+ async getEdit(id) {
224
+ const res = await this.httpJson("GET", `/vm/edits/${encodeURIComponent(id)}`);
225
+ return res.edit;
226
+ }
227
+ /**
228
+ * Apply a pending edit to the target file.
229
+ */
230
+ async applyEdit(id) {
231
+ return (await this.httpJson("POST", `/vm/edits/${encodeURIComponent(id)}/apply`));
232
+ }
233
+ /**
234
+ * Reject a pending edit.
235
+ */
236
+ async rejectEdit(id) {
237
+ return (await this.httpJson("POST", `/vm/edits/${encodeURIComponent(id)}/reject`));
238
+ }
239
+ async listPending() {
240
+ const res = await this.httpJson("GET", "/vm/pending");
241
+ return res.pending;
242
+ }
243
+ async approveEntry(id, collection, action) {
244
+ await this.httpJson("POST", "/vm/approve", { id, collection, action });
245
+ }
246
+ async pushContext(filePath, selection, cursor) {
247
+ return (await this.httpJson("POST", "/vault-mind/context", {
248
+ filePath,
249
+ selection,
250
+ cursor,
251
+ }));
252
+ }
253
+ async scan(file, vault) {
254
+ try {
255
+ return (await this.httpJson("POST", "/vault-mind/scan", { file, vault }));
256
+ }
257
+ catch (err) {
258
+ return { error: String(err) };
259
+ }
260
+ }
261
+ async dispatch(role, instruction, file, vault) {
262
+ try {
263
+ return (await this.httpJson("POST", "/vault-mind/dispatch", {
264
+ role,
265
+ instruction,
266
+ file,
267
+ vault,
268
+ }));
269
+ }
270
+ catch (err) {
271
+ return { error: String(err) };
272
+ }
273
+ }
274
+ async vmStats() {
275
+ return (await this.httpJson("GET", "/vm/stats"));
276
+ }
277
+ /** GET /vault-mind/config — full config + hasToken + remote block */
278
+ async getConfig() {
279
+ return (await this.httpJson("GET", "/vault-mind/config"));
280
+ }
281
+ /** GET /vault-mind/paths — vault/agent/env paths */
282
+ async paths() {
283
+ return (await this.httpJson("GET", "/vault-mind/paths"));
284
+ }
285
+ /** GET /vault-mind/models — provider/model catalog parsed from pi's models.json */
286
+ async getModels() {
287
+ return (await this.httpJson("GET", "/vault-mind/models"));
288
+ }
289
+ /** POST /vm/token — write PVM_API_TOKEN to vault-mind.env */
290
+ async writeToken(token) {
291
+ return (await this.httpJson("POST", "/vm/token", { token }));
292
+ }
293
+ /** PATCH /vm/config — partial config update (deep-merged on vaultMind) */
294
+ async updateConfig(partial) {
295
+ return (await this.httpJson("PATCH", "/vm/config", partial));
296
+ }
297
+ /** GET /vault-mind/tools — full tool catalog + per-role allowlists */
298
+ async listTools() {
299
+ return (await this.httpJson("GET", "/vault-mind/tools"));
300
+ }
301
+ /** GET /vault-mind/identities — identity registry */
302
+ async listIdentities() {
303
+ return (await this.httpJson("GET", "/vault-mind/identities"));
304
+ }
305
+ /** PUT /vault-mind/identities/:role/allowedTools — update per-role allowlist */
306
+ async setAllowedTools(role, allowedTools) {
307
+ return (await this.httpJson("PUT", `/vault-mind/identities/${encodeURIComponent(role)}/allowedTools`, { allowedTools }));
308
+ }
309
+ /** POST /vault-mind/reload-identities — reload bridge registry */
310
+ async reloadIdentities() {
311
+ return (await this.httpJson("POST", "/vault-mind/reload-identities"));
312
+ }
313
+ /** POST /vm/reindex — trigger a remote reindex of all collections */
314
+ async reindex() {
315
+ return (await this.httpJson("POST", "/vm/reindex"));
316
+ }
317
+ /** GET /agent/activity — list activity events, optionally filtered by jobId and limited. */
318
+ async listActivity(opts = {}) {
319
+ const params = new URLSearchParams();
320
+ if (opts.jobId !== undefined)
321
+ params.set("jobId", opts.jobId);
322
+ if (opts.limit !== undefined)
323
+ params.set("limit", String(opts.limit));
324
+ const qs = params.toString();
325
+ return (await this.httpJson("GET", `/agent/activity${qs ? `?${qs}` : ""}`));
326
+ }
327
+ /** GET /vault-mind/model-readiness — returns model registry status and current selected model */
328
+ async getModelReadiness() {
329
+ return (await this.httpJson("GET", "/vault-mind/model-readiness"));
330
+ }
331
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,49 @@
1
+ /**
2
+ * ActivityEvent — v1 status-level timeline derived from queue transitions.
3
+ *
4
+ * Persists append-only JSONL at the path resolved via `resolveVaultFile(cfg, "activity")`
5
+ * (default: `.vault-mind/agent-activity.jsonl`). IDs are monotonic per vault,
6
+ * initialised lazily by scanning the existing file on first write.
7
+ */
8
+ import type { QueueEvent } from "./agent-queue.js";
9
+ export type ActivityKind = "session" | "status" | "tool" | "gate" | "approval";
10
+ export type ActivityTone = "neutral" | "ok" | "warn" | "error";
11
+ export interface ActivityEvent {
12
+ /** Monotonic integer ID, unique within a vault. */
13
+ id: number;
14
+ /** Correlates to AgentJob.id / dispatchId. */
15
+ jobId: string;
16
+ /** ISO 8601 timestamp. */
17
+ ts: string;
18
+ kind: ActivityKind;
19
+ /** Short human-readable label. */
20
+ label: string;
21
+ /** Optional detail line (error text, tool args, gate reason). */
22
+ detail?: string;
23
+ tone?: ActivityTone;
24
+ }
25
+ /** Append one ActivityEvent to the vault's activity JSONL. */
26
+ export declare function appendActivity(vaultPath: string, event: ActivityEvent): void;
27
+ /**
28
+ * List activity events in chronological order (JSONL append order = creation order).
29
+ * - `jobId`: when provided, only events matching that job are returned.
30
+ * - `limit`: maximum number of events to return; must be a positive integer.
31
+ * Non-positive or missing values are treated as unbounded.
32
+ * Malformed JSONL rows are silently skipped.
33
+ */
34
+ export declare function listActivity(vaultPath: string, opts?: {
35
+ jobId?: string;
36
+ limit?: number;
37
+ }): ActivityEvent[];
38
+ /**
39
+ * Map a QueueEvent to an ActivityEvent and allocate the next monotonic ID.
40
+ * Returns `null` for event types that should not produce an activity record.
41
+ */
42
+ export declare function mapQueueEventToActivity(e: QueueEvent, vaultPath: string): ActivityEvent | null;
43
+ /**
44
+ * Map a tool-call request to an ActivityEvent with `kind: "tool"`.
45
+ * `dispatchId` correlates to AgentJob.id when available; falls back to `agentId`
46
+ * for tool calls outside a dispatch context (e.g. interactive agent use).
47
+ * `args` is summarised to a short detail line (first 200 chars of JSON).
48
+ */
49
+ export declare function mapToolCallToActivity(toolName: string, agentId: string, dispatchId: string | undefined, args: Record<string, unknown>, vaultPath: string): ActivityEvent;
@@ -0,0 +1,175 @@
1
+ /**
2
+ * ActivityEvent — v1 status-level timeline derived from queue transitions.
3
+ *
4
+ * Persists append-only JSONL at the path resolved via `resolveVaultFile(cfg, "activity")`
5
+ * (default: `.vault-mind/agent-activity.jsonl`). IDs are monotonic per vault,
6
+ * initialised lazily by scanning the existing file on first write.
7
+ */
8
+ import * as fs from "node:fs";
9
+ import * as path from "node:path";
10
+ import { loadConfig, resolveVaultFile } from "./utils.js";
11
+ // ── Path resolution ───────────────────────────────────────────────────────────
12
+ /** Fully-resolved path to the activity JSONL for a vault. */
13
+ function resolvedActivityPath(vaultPath) {
14
+ const cfg = loadConfig(vaultPath).vaultMind;
15
+ return path.join(vaultPath, resolveVaultFile(cfg, "activity"));
16
+ }
17
+ // ── Monotonic ID counter ──────────────────────────────────────────────────────
18
+ /** Per-vault in-memory counter. Initialised lazily from the JSONL file. */
19
+ const nextIdCache = new Map();
20
+ /**
21
+ * Return the next monotonic ID for the vault and advance the counter.
22
+ * On first call for a vault, scans the JSONL file to find the current maximum.
23
+ */
24
+ function nextActivityId(vaultPath) {
25
+ const cached = nextIdCache.get(vaultPath);
26
+ if (cached !== undefined) {
27
+ nextIdCache.set(vaultPath, cached + 1);
28
+ return cached;
29
+ }
30
+ // Lazy init: find the max id already in the file.
31
+ const p = resolvedActivityPath(vaultPath);
32
+ let maxId = 0;
33
+ if (fs.existsSync(p)) {
34
+ for (const line of fs.readFileSync(p, "utf-8").split(/\r?\n/)) {
35
+ const trimmed = line.trim();
36
+ if (!trimmed)
37
+ continue;
38
+ try {
39
+ const rec = JSON.parse(trimmed);
40
+ if (typeof rec.id === "number" && Number.isInteger(rec.id) && rec.id > maxId) {
41
+ maxId = rec.id;
42
+ }
43
+ }
44
+ catch {
45
+ // skip malformed row
46
+ }
47
+ }
48
+ }
49
+ const id = maxId + 1;
50
+ nextIdCache.set(vaultPath, id + 1);
51
+ return id;
52
+ }
53
+ // ── Persistence ───────────────────────────────────────────────────────────────
54
+ /** Append one ActivityEvent to the vault's activity JSONL. */
55
+ export function appendActivity(vaultPath, event) {
56
+ const p = resolvedActivityPath(vaultPath);
57
+ const dir = path.dirname(p);
58
+ if (!fs.existsSync(dir)) {
59
+ fs.mkdirSync(dir, { recursive: true });
60
+ }
61
+ fs.appendFileSync(p, `${JSON.stringify(event)}\n`, "utf-8");
62
+ }
63
+ // ── Listing ───────────────────────────────────────────────────────────────────
64
+ /**
65
+ * List activity events in chronological order (JSONL append order = creation order).
66
+ * - `jobId`: when provided, only events matching that job are returned.
67
+ * - `limit`: maximum number of events to return; must be a positive integer.
68
+ * Non-positive or missing values are treated as unbounded.
69
+ * Malformed JSONL rows are silently skipped.
70
+ */
71
+ export function listActivity(vaultPath, opts = {}) {
72
+ const p = resolvedActivityPath(vaultPath);
73
+ if (!fs.existsSync(p))
74
+ return [];
75
+ const { jobId, limit } = opts;
76
+ const cap = typeof limit === "number" && limit > 0 ? limit : Number.MAX_SAFE_INTEGER;
77
+ const out = [];
78
+ for (const line of fs.readFileSync(p, "utf-8").split(/\r?\n/)) {
79
+ const trimmed = line.trim();
80
+ if (!trimmed)
81
+ continue;
82
+ try {
83
+ const rec = JSON.parse(trimmed);
84
+ if (jobId !== undefined && rec.jobId !== jobId)
85
+ continue;
86
+ out.push(rec);
87
+ if (out.length >= cap)
88
+ break;
89
+ }
90
+ catch {
91
+ // skip malformed row
92
+ }
93
+ }
94
+ return out;
95
+ }
96
+ // ── Queue-event mapping ───────────────────────────────────────────────────────
97
+ /**
98
+ * Map a QueueEvent to an ActivityEvent and allocate the next monotonic ID.
99
+ * Returns `null` for event types that should not produce an activity record.
100
+ */
101
+ export function mapQueueEventToActivity(e, vaultPath) {
102
+ let label;
103
+ let tone;
104
+ let detail;
105
+ let jobId;
106
+ switch (e.type) {
107
+ case "job-created":
108
+ jobId = e.job.id;
109
+ label = "job created";
110
+ tone = "neutral";
111
+ break;
112
+ case "job-updated":
113
+ jobId = e.job.id;
114
+ label = `job ${e.job.status}`;
115
+ tone = "neutral";
116
+ break;
117
+ case "job-completed":
118
+ jobId = e.job.id;
119
+ if (e.job.status === "failed") {
120
+ label = "job failed";
121
+ tone = "error";
122
+ if (e.job.lastError)
123
+ detail = e.job.lastError;
124
+ }
125
+ else if (e.job.status === "done") {
126
+ label = "job done";
127
+ tone = "ok";
128
+ }
129
+ else if (e.job.status === "cancelled") {
130
+ label = "job cancelled";
131
+ tone = "warn";
132
+ }
133
+ else {
134
+ label = `job ${e.job.status}`;
135
+ }
136
+ break;
137
+ default:
138
+ return null;
139
+ }
140
+ const id = nextActivityId(vaultPath);
141
+ const event = {
142
+ id,
143
+ jobId,
144
+ ts: new Date().toISOString(),
145
+ kind: "status",
146
+ label,
147
+ };
148
+ if (tone !== undefined)
149
+ event.tone = tone;
150
+ if (detail !== undefined)
151
+ event.detail = detail;
152
+ return event;
153
+ }
154
+ // ── Tool-call mapping ─────────────────────────────────────────────────────────
155
+ /**
156
+ * Map a tool-call request to an ActivityEvent with `kind: "tool"`.
157
+ * `dispatchId` correlates to AgentJob.id when available; falls back to `agentId`
158
+ * for tool calls outside a dispatch context (e.g. interactive agent use).
159
+ * `args` is summarised to a short detail line (first 200 chars of JSON).
160
+ */
161
+ export function mapToolCallToActivity(toolName, agentId, dispatchId, args, vaultPath) {
162
+ const id = nextActivityId(vaultPath);
163
+ const argSummary = JSON.stringify(args);
164
+ const event = {
165
+ id,
166
+ jobId: dispatchId ?? agentId,
167
+ ts: new Date().toISOString(),
168
+ kind: "tool",
169
+ label: toolName,
170
+ };
171
+ if (argSummary.length > 0 && argSummary !== "{}") {
172
+ event.detail = argSummary.length > 200 ? `${argSummary.slice(0, 200)}…` : argSummary;
173
+ }
174
+ return event;
175
+ }
@@ -15,6 +15,8 @@ export declare abstract class Agent {
15
15
  config: AgentConfig;
16
16
  protected bus: MessageBus;
17
17
  protected llm?: LLMProvider;
18
+ /** Current dispatch context — set when executing a job, included in tool-call payloads. */
19
+ protected currentDispatchId?: string;
18
20
  constructor(config: AgentConfig, bus: MessageBus, llm?: LLMProvider);
19
21
  /**
20
22
  * Broadcast a message to the environment
@@ -24,10 +26,16 @@ export declare abstract class Agent {
24
26
  * Direct message another agent
25
27
  */
26
28
  sendDirect(targetAgentId: string, payload: Record<string, unknown>): Promise<void>;
29
+ /**
30
+ * Set the dispatch context for the duration of `fn`, so tool calls made
31
+ * during `fn` carry the `dispatchId` for activity correlation.
32
+ * Clears the context in `finally` so it never leaks across jobs.
33
+ */
34
+ protected withDispatchContext<T>(dispatchId: string, fn: () => Promise<T>): Promise<T>;
27
35
  /**
28
36
  * Hook for handling incoming messages. To be overridden by implementations.
29
37
  */
30
- protected handleMessage(message: Message): Promise<void>;
38
+ protected handleMessage(_message: Message): Promise<void>;
31
39
  /**
32
40
  * Perform an LLM-driven decision in JSON mode
33
41
  */
@@ -9,6 +9,8 @@ export class Agent {
9
9
  config;
10
10
  bus;
11
11
  llm;
12
+ /** Current dispatch context — set when executing a job, included in tool-call payloads. */
13
+ currentDispatchId;
12
14
  constructor(config, bus, llm) {
13
15
  this.config = config;
14
16
  this.bus = bus;
@@ -29,10 +31,24 @@ export class Agent {
29
31
  async sendDirect(targetAgentId, payload) {
30
32
  await this.bus.publish(`direct:${targetAgentId}`, this.config.id, payload);
31
33
  }
34
+ /**
35
+ * Set the dispatch context for the duration of `fn`, so tool calls made
36
+ * during `fn` carry the `dispatchId` for activity correlation.
37
+ * Clears the context in `finally` so it never leaks across jobs.
38
+ */
39
+ async withDispatchContext(dispatchId, fn) {
40
+ this.currentDispatchId = dispatchId;
41
+ try {
42
+ return await fn();
43
+ }
44
+ finally {
45
+ this.currentDispatchId = undefined;
46
+ }
47
+ }
32
48
  /**
33
49
  * Hook for handling incoming messages. To be overridden by implementations.
34
50
  */
35
- async handleMessage(message) {
51
+ async handleMessage(_message) {
36
52
  // Default: Do nothing
37
53
  }
38
54
  async askLLM(promptText, jsonMode = true) {
@@ -63,7 +79,6 @@ export class Agent {
63
79
  const requestTopic = "pi_tool_bridge_requests";
64
80
  const responseTopic = `pi_tool_bridge_responses:${this.config.id}:${requestId}`;
65
81
  return new Promise((resolve, reject) => {
66
- // biome-ignore lint/style/useConst: assigned after handler/subscribe setup
67
82
  let timeout;
68
83
  const cleanup = () => {
69
84
  clearTimeout(timeout);
@@ -91,6 +106,7 @@ export class Agent {
91
106
  toolName,
92
107
  args,
93
108
  responseTopic,
109
+ ...(this.currentDispatchId ? { dispatchId: this.currentDispatchId } : {}),
94
110
  });
95
111
  });
96
112
  }
@@ -1,6 +1,6 @@
1
1
  export { Agent, type AgentConfig } from "./Agent.js";
2
- export { AgentLoader, type AgentFileDefinition, type AgentLoaderConfig } from "./AgentLoader.js";
3
- export { AgentRegistry, type AgentConstructor } from "./AgentRegistry.js";
4
- export { LLMProvider, AgentModelProvider, type LLMConfig, type PromptOptions, } from "./LLMProvider.js";
5
- export { MessageBus, type Message, type MessageHandler } from "./MessageBus.js";
6
- export { TaskQueue, type Task } from "./TaskQueue.js";
2
+ export { type AgentFileDefinition, AgentLoader, type AgentLoaderConfig } from "./AgentLoader.js";
3
+ export { type AgentConstructor, AgentRegistry } from "./AgentRegistry.js";
4
+ export { AgentModelProvider, type LLMConfig, LLMProvider, type PromptOptions, } from "./LLMProvider.js";
5
+ export { type Message, MessageBus, type MessageHandler } from "./MessageBus.js";
6
+ export { type Task, TaskQueue } from "./TaskQueue.js";
@@ -1,6 +1,6 @@
1
1
  export { Agent } from "./Agent.js";
2
2
  export { AgentLoader } from "./AgentLoader.js";
3
3
  export { AgentRegistry } from "./AgentRegistry.js";
4
- export { LLMProvider, AgentModelProvider, } from "./LLMProvider.js";
4
+ export { AgentModelProvider, LLMProvider, } from "./LLMProvider.js";
5
5
  export { MessageBus } from "./MessageBus.js";
6
6
  export { TaskQueue } from "./TaskQueue.js";