pi-vault-mind 0.8.0 → 0.8.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 (119) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +44 -44
  3. package/agents/broadcaster.agent.md +30 -0
  4. package/agents/heavy-lifter.agent.md +30 -0
  5. package/agents/manager.agent.md +29 -0
  6. package/agents/miner.agent.md +29 -0
  7. package/dist/src/agent-bus/Agent.d.ts +49 -0
  8. package/dist/src/agent-bus/Agent.js +97 -0
  9. package/dist/src/agent-bus/AgentLoader.d.ts +33 -0
  10. package/dist/src/agent-bus/AgentLoader.js +102 -0
  11. package/dist/src/agent-bus/AgentRegistry.d.ts +17 -0
  12. package/dist/src/agent-bus/AgentRegistry.js +20 -0
  13. package/dist/src/agent-bus/LLMProvider.d.ts +36 -0
  14. package/dist/src/agent-bus/LLMProvider.js +125 -0
  15. package/dist/src/agent-bus/MessageBus.d.ts +47 -0
  16. package/dist/src/agent-bus/MessageBus.js +81 -0
  17. package/dist/src/agent-bus/TaskQueue.d.ts +27 -0
  18. package/dist/src/agent-bus/TaskQueue.js +57 -0
  19. package/dist/src/agent-bus/index.d.ts +6 -0
  20. package/dist/src/agent-bus/index.js +6 -0
  21. package/dist/src/agents/BroadcasterAgent.d.ts +19 -0
  22. package/dist/src/agents/BroadcasterAgent.js +70 -0
  23. package/dist/src/agents/HeavyLifterAgent.d.ts +26 -0
  24. package/dist/src/agents/HeavyLifterAgent.js +73 -0
  25. package/dist/src/agents/ManagerAgent.d.ts +20 -0
  26. package/dist/src/agents/ManagerAgent.js +71 -0
  27. package/dist/src/agents/MinerAgent.d.ts +19 -0
  28. package/dist/src/agents/MinerAgent.js +70 -0
  29. package/dist/src/agents/index.d.ts +4 -0
  30. package/dist/src/agents/index.js +4 -0
  31. package/dist/src/auth.d.ts +49 -2
  32. package/dist/src/auth.js +149 -7
  33. package/dist/src/autosync.d.ts +1 -1
  34. package/dist/src/autosync.js +3 -3
  35. package/dist/src/bridge.d.ts +45 -0
  36. package/dist/src/bridge.js +189 -0
  37. package/dist/src/commands.d.ts +1 -1
  38. package/dist/src/commands.js +419 -295
  39. package/dist/src/context-capture.d.ts +22 -0
  40. package/dist/src/context-capture.js +41 -0
  41. package/dist/src/discover-schema.d.ts +1 -1
  42. package/dist/src/discover-schema.js +4 -4
  43. package/dist/src/embed-queue.d.ts +1 -1
  44. package/dist/src/embed-queue.js +1 -1
  45. package/dist/src/engine.d.ts +65 -0
  46. package/dist/src/engine.js +244 -0
  47. package/dist/src/events.js +1 -1
  48. package/dist/src/graph.d.ts +3 -3
  49. package/dist/src/identity-injector.d.ts +15 -0
  50. package/dist/src/identity-injector.js +102 -0
  51. package/dist/src/index.js +88 -49
  52. package/dist/src/intake.js +4 -4
  53. package/dist/src/lance.d.ts +11 -37
  54. package/dist/src/lance.js +183 -240
  55. package/dist/src/modal-client.d.ts +14 -4
  56. package/dist/src/modal-client.js +50 -34
  57. package/dist/src/modal-config.d.ts +26 -10
  58. package/dist/src/modal-config.js +36 -5
  59. package/dist/src/personalize.d.ts +47 -0
  60. package/dist/src/personalize.js +352 -0
  61. package/dist/src/server.d.ts +30 -4
  62. package/dist/src/server.js +146 -64
  63. package/dist/src/settings-ui.d.ts +3 -1
  64. package/dist/src/settings-ui.js +84 -77
  65. package/dist/src/status.d.ts +25 -0
  66. package/dist/src/status.js +49 -0
  67. package/dist/src/sync.d.ts +1 -1
  68. package/dist/src/sync.js +19 -19
  69. package/dist/src/tombstone.js +2 -2
  70. package/dist/src/tools.js +112 -130
  71. package/dist/src/types.d.ts +86 -18
  72. package/dist/src/types.js +7 -4
  73. package/dist/src/utils.d.ts +5 -1
  74. package/dist/src/utils.js +24 -8
  75. package/dist/src/vault-tools.d.ts +29 -0
  76. package/dist/src/vault-tools.js +314 -0
  77. package/dist/src/vault-writer.d.ts +4 -5
  78. package/dist/src/vault-writer.js +13 -13
  79. package/dist/src/vm-handlers.d.ts +15 -0
  80. package/dist/src/vm-handlers.js +469 -0
  81. package/dist/src/watcher.d.ts +18 -4
  82. package/dist/src/watcher.js +109 -98
  83. package/dist/test/auth.test.js +57 -7
  84. package/dist/test/bridge-identity.test.js +388 -0
  85. package/dist/test/discover-schema.test.js +2 -2
  86. package/dist/test/engine-identity.test.js +119 -0
  87. package/dist/test/index.test.js +5 -6
  88. package/dist/test/lance-modal.test.js +7 -7
  89. package/dist/test/modal-config.test.js +51 -29
  90. package/dist/test/personalize.test.js +212 -0
  91. package/dist/test/rest-queue.test.js +1 -1
  92. package/dist/test/rest-setup.test.js +21 -21
  93. package/dist/test/{rest-wiki.test.js → rest-vm.test.js} +27 -23
  94. package/dist/test/server-ws.test.js +4 -4
  95. package/dist/test/sync.test.js +2 -2
  96. package/dist/test/tombstone.test.js +1 -1
  97. package/dist/test/vault-tools.test.js +224 -0
  98. package/package.json +2 -1
  99. package/scripts/check-deps.sh +207 -0
  100. package/scripts/e2e-ws.mjs +4 -4
  101. package/scripts/modal-e2e-smoke.mjs +1 -1
  102. package/scripts/setup-test-config.sh +1 -5
  103. package/scripts/test-slash-commands.mjs +197 -0
  104. package/skills/.agents/broadcaster.agent.md +31 -0
  105. package/skills/.agents/heavy-lifter.agent.md +31 -0
  106. package/skills/.agents/manager.agent.md +30 -0
  107. package/skills/.agents/miner.agent.md +30 -0
  108. package/skills/vault-mind/SKILL.md +50 -50
  109. package/skills/vault-mind/references/tool-reference.md +25 -25
  110. package/skills/vault-mind-broadcaster/SKILL.md +1 -1
  111. package/skills/vault-mind-heavy-lifter/SKILL.md +3 -3
  112. package/skills/vault-mind-manager/SKILL.md +7 -7
  113. package/skills/vault-mind-miner/SKILL.md +11 -11
  114. package/skills/vault-mind-setup/SKILL.md +65 -265
  115. package/skills/vault-mind-setup/references/obsidian-vault-structure.md +6 -6
  116. package/skills/vault-mind-setup/references/pi-extension-wiring.md +24 -33
  117. package/skills/vault-mind-setup/references/troubleshooting-tree.md +25 -26
  118. package/dist/src/auto-index.d.ts +0 -47
  119. package/dist/src/auto-index.js +0 -122
@@ -0,0 +1,36 @@
1
+ export interface LLMConfig {
2
+ model: string;
3
+ /** Primary remote endpoint (e.g. Modal deployment). */
4
+ remoteUrl?: string;
5
+ /** Local fallback endpoint (e.g. Ollama). */
6
+ localUrl?: string;
7
+ /** API key for Bearer auth. Defaults to "ollama" (Ollama ignores it). */
8
+ apiKey?: string;
9
+ temperature?: number;
10
+ }
11
+ export interface PromptOptions {
12
+ systemPrompt?: string;
13
+ jsonMode?: boolean;
14
+ }
15
+ /**
16
+ * LLMProvider
17
+ *
18
+ * Interface for talking to the intelligence layer.
19
+ * A concrete implementation would hit an Ollama endpoint or external API.
20
+ */
21
+ export declare abstract class LLMProvider {
22
+ protected config: LLMConfig;
23
+ constructor(config: LLMConfig);
24
+ abstract prompt(text: string, options?: PromptOptions): Promise<string>;
25
+ }
26
+ /**
27
+ * AgentModelProvider — real LLM access for internal agents.
28
+ * Remote-first, local-fallback: tries remoteUrl, then falls back
29
+ * to localUrl (typically a local Ollama instance) on any failure.
30
+ * Both endpoints speak OpenAI-compatible `/v1/chat/completions`.
31
+ *
32
+ * Pattern adapted from kylebrodeur/microfactory PiModelRouterLLMProvider (MIT).
33
+ */
34
+ export declare class AgentModelProvider extends LLMProvider {
35
+ prompt(text: string, options?: PromptOptions): Promise<string>;
36
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * LLMProvider
3
+ *
4
+ * Interface for talking to the intelligence layer.
5
+ * A concrete implementation would hit an Ollama endpoint or external API.
6
+ */
7
+ export class LLMProvider {
8
+ config;
9
+ constructor(config) {
10
+ this.config = config;
11
+ }
12
+ }
13
+ /**
14
+ * AgentModelProvider — real LLM access for internal agents.
15
+ * Remote-first, local-fallback: tries remoteUrl, then falls back
16
+ * to localUrl (typically a local Ollama instance) on any failure.
17
+ * Both endpoints speak OpenAI-compatible `/v1/chat/completions`.
18
+ *
19
+ * Pattern adapted from kylebrodeur/microfactory PiModelRouterLLMProvider (MIT).
20
+ */
21
+ export class AgentModelProvider extends LLMProvider {
22
+ async prompt(text, options) {
23
+ const jsonMode = options?.jsonMode === true;
24
+ const messages = [];
25
+ if (options?.systemPrompt) {
26
+ messages.push({ role: "system", content: options.systemPrompt });
27
+ }
28
+ messages.push({ role: "user", content: text });
29
+ const body = JSON.stringify({
30
+ model: this.config.model,
31
+ messages,
32
+ temperature: this.config.temperature ?? 0.1,
33
+ stream: false,
34
+ });
35
+ const endpoints = [this.config.remoteUrl ?? "http://127.0.0.1:11434/v1"];
36
+ if (this.config.localUrl) {
37
+ endpoints.push(this.config.localUrl);
38
+ }
39
+ let lastError = null;
40
+ for (const baseUrl of endpoints) {
41
+ try {
42
+ const resp = await fetch(`${baseUrl}/chat/completions`, {
43
+ method: "POST",
44
+ headers: {
45
+ "Content-Type": "application/json",
46
+ Authorization: `Bearer ${this.config.apiKey ?? "ollama"}`,
47
+ },
48
+ body,
49
+ });
50
+ if (!resp.ok) {
51
+ const errBody = await resp.text().catch(() => "");
52
+ throw new Error(`HTTP ${resp.status}: ${errBody.slice(0, 200)}`);
53
+ }
54
+ const data = (await resp.json());
55
+ let result = (data.choices?.[0]?.message?.content ?? "").trim();
56
+ if (!result)
57
+ throw new Error("Model returned empty response");
58
+ if (jsonMode)
59
+ result = extractJSON(result);
60
+ return result;
61
+ }
62
+ catch (err) {
63
+ lastError = err instanceof Error ? err : new Error(String(err));
64
+ console.warn(`[AgentModelProvider] ${baseUrl} failed: ${lastError.message} — trying next`);
65
+ }
66
+ }
67
+ throw lastError ?? new Error("AgentModelProvider: no endpoints configured");
68
+ }
69
+ }
70
+ /**
71
+ * Strip markdown fences and extract the first valid JSON object/array.
72
+ * Local models (Gemma, Llama, etc.) often wrap JSON in ```json fences
73
+ * even when explicitly told not to.
74
+ */
75
+ function extractJSON(raw) {
76
+ // Try direct parse first — most common case
77
+ try {
78
+ JSON.parse(raw);
79
+ return raw;
80
+ }
81
+ catch {
82
+ // fall through to fence stripping
83
+ }
84
+ // Strip ```json / ``` fences
85
+ const stripped = raw
86
+ .replace(/^```(?:json)?\s*\n?/gm, "")
87
+ .replace(/\n?```\s*$/gm, "")
88
+ .trim();
89
+ // Find first { or [ and try to parse from there
90
+ const start = Math.min(...[stripped.indexOf("{"), stripped.indexOf("[")].filter((i) => i >= 0));
91
+ if (start >= 0) {
92
+ const candidate = stripped.slice(start);
93
+ try {
94
+ JSON.parse(candidate);
95
+ return candidate;
96
+ }
97
+ catch {
98
+ // last resort: find balanced braces
99
+ let depth = 0;
100
+ let end = start;
101
+ const target = candidate[0] === "{" ? "}" : "]";
102
+ const open = candidate[0];
103
+ for (let i = 0; i < candidate.length; i++) {
104
+ if (candidate[i] === open)
105
+ depth++;
106
+ else if (candidate[i] === target) {
107
+ depth--;
108
+ if (depth === 0) {
109
+ end = i + 1;
110
+ break;
111
+ }
112
+ }
113
+ }
114
+ const balanced = candidate.slice(0, end);
115
+ try {
116
+ JSON.parse(balanced);
117
+ return balanced;
118
+ }
119
+ catch {
120
+ // give up, return stripped
121
+ }
122
+ }
123
+ }
124
+ return stripped;
125
+ }
@@ -0,0 +1,47 @@
1
+ export interface Message {
2
+ id: string;
3
+ topic: string;
4
+ payload: Record<string, unknown>;
5
+ senderId: string;
6
+ timestamp: number;
7
+ }
8
+ export type MessageHandler = (message: Message) => void | Promise<void>;
9
+ /**
10
+ * MessageBus
11
+ *
12
+ * An event-driven stigmergic environment for agents to drop messages (pheromones).
13
+ * Instead of explicitly addressing one another, agents broadcast onto topics,
14
+ * simulating environment-mediated coordination (Stigmergy) dropping O(n²) scaling.
15
+ */
16
+ export declare class MessageBus {
17
+ private listeners;
18
+ private history;
19
+ private maxHistorySize;
20
+ constructor(maxHistorySize?: number);
21
+ /**
22
+ * Subscribe to a topic
23
+ */
24
+ subscribe(topic: string, handler: MessageHandler): void;
25
+ /**
26
+ * Unsubscribe from a topic
27
+ */
28
+ unsubscribe(topic: string, handler: MessageHandler): void;
29
+ /**
30
+ * Broadcast a message to a topic
31
+ */
32
+ publish(topic: string, senderId: string, payload: Record<string, unknown>): Promise<void>;
33
+ /**
34
+ * Get max history size
35
+ */
36
+ getHistoryLimit(): number;
37
+ /**
38
+ * Set max history size (0 = unlimited)
39
+ */
40
+ setHistoryLimit(limit: number): void;
41
+ /** Trim history if over limit */
42
+ private _trimHistory;
43
+ /**
44
+ * Get all messages for a given topic
45
+ */
46
+ getHistory(topic?: string): Message[];
47
+ }
@@ -0,0 +1,81 @@
1
+ import { randomUUID } from "node:crypto";
2
+ /**
3
+ * MessageBus
4
+ *
5
+ * An event-driven stigmergic environment for agents to drop messages (pheromones).
6
+ * Instead of explicitly addressing one another, agents broadcast onto topics,
7
+ * simulating environment-mediated coordination (Stigmergy) dropping O(n²) scaling.
8
+ */
9
+ export class MessageBus {
10
+ listeners = new Map();
11
+ history = [];
12
+ maxHistorySize;
13
+ constructor(maxHistorySize = 10000) {
14
+ this.maxHistorySize = Math.max(0, maxHistorySize);
15
+ }
16
+ /**
17
+ * Subscribe to a topic
18
+ */
19
+ subscribe(topic, handler) {
20
+ if (!this.listeners.has(topic)) {
21
+ this.listeners.set(topic, new Set());
22
+ }
23
+ this.listeners.get(topic)?.add(handler);
24
+ }
25
+ /**
26
+ * Unsubscribe from a topic
27
+ */
28
+ unsubscribe(topic, handler) {
29
+ const topicListeners = this.listeners.get(topic);
30
+ if (topicListeners) {
31
+ topicListeners.delete(handler);
32
+ }
33
+ }
34
+ /**
35
+ * Broadcast a message to a topic
36
+ */
37
+ async publish(topic, senderId, payload) {
38
+ const message = {
39
+ id: randomUUID(),
40
+ topic,
41
+ payload,
42
+ senderId,
43
+ timestamp: Date.now(),
44
+ };
45
+ this.history.push(message);
46
+ this._trimHistory();
47
+ const topicListeners = this.listeners.get(topic);
48
+ if (topicListeners) {
49
+ const promises = Array.from(topicListeners).map((handler) => handler(message));
50
+ await Promise.allSettled(promises);
51
+ }
52
+ }
53
+ /**
54
+ * Get max history size
55
+ */
56
+ getHistoryLimit() {
57
+ return this.maxHistorySize;
58
+ }
59
+ /**
60
+ * Set max history size (0 = unlimited)
61
+ */
62
+ setHistoryLimit(limit) {
63
+ this.maxHistorySize = Math.max(0, limit);
64
+ this._trimHistory();
65
+ }
66
+ /** Trim history if over limit */
67
+ _trimHistory() {
68
+ if (this.maxHistorySize > 0 && this.history.length > this.maxHistorySize) {
69
+ this.history = this.history.slice(-this.maxHistorySize);
70
+ }
71
+ }
72
+ /**
73
+ * Get all messages for a given topic
74
+ */
75
+ getHistory(topic) {
76
+ if (topic) {
77
+ return this.history.filter((m) => m.topic === topic);
78
+ }
79
+ return [...this.history];
80
+ }
81
+ }
@@ -0,0 +1,27 @@
1
+ export interface Task<TInput = unknown, TOutput = unknown> {
2
+ id: string;
3
+ type: string;
4
+ input: TInput;
5
+ status: "pending" | "in_progress" | "completed" | "failed";
6
+ result?: TOutput;
7
+ error?: string;
8
+ assignedTo?: string;
9
+ createdAt: number;
10
+ updatedAt: number;
11
+ }
12
+ /**
13
+ * TaskQueue
14
+ *
15
+ * Simple abstraction for managing asynchronous units of work.
16
+ * Could represent a coding file to parse, or a physical 3D print job.
17
+ */
18
+ export declare class TaskQueue {
19
+ private tasks;
20
+ add<TInput>(type: string, input: TInput): string;
21
+ get(id: string): Task | undefined;
22
+ getAll(): Task[];
23
+ getPending(): Task[];
24
+ claim(id: string, agentId: string): boolean;
25
+ complete<TOutput>(id: string, agentId: string, result: TOutput): void;
26
+ fail(id: string, agentId: string, error: string): void;
27
+ }
@@ -0,0 +1,57 @@
1
+ import { randomUUID } from "node:crypto";
2
+ /**
3
+ * TaskQueue
4
+ *
5
+ * Simple abstraction for managing asynchronous units of work.
6
+ * Could represent a coding file to parse, or a physical 3D print job.
7
+ */
8
+ export class TaskQueue {
9
+ tasks = new Map();
10
+ add(type, input) {
11
+ const id = randomUUID();
12
+ this.tasks.set(id, {
13
+ id,
14
+ type,
15
+ input,
16
+ status: "pending",
17
+ createdAt: Date.now(),
18
+ updatedAt: Date.now(),
19
+ });
20
+ return id;
21
+ }
22
+ get(id) {
23
+ return this.tasks.get(id);
24
+ }
25
+ getAll() {
26
+ return Array.from(this.tasks.values());
27
+ }
28
+ getPending() {
29
+ return Array.from(this.tasks.values()).filter((t) => t.status === "pending");
30
+ }
31
+ claim(id, agentId) {
32
+ const task = this.tasks.get(id);
33
+ if (task && task.status === "pending") {
34
+ task.status = "in_progress";
35
+ task.assignedTo = agentId;
36
+ task.updatedAt = Date.now();
37
+ return true;
38
+ }
39
+ return false;
40
+ }
41
+ complete(id, agentId, result) {
42
+ const task = this.tasks.get(id);
43
+ if (task && task.assignedTo === agentId) {
44
+ task.status = "completed";
45
+ task.result = result;
46
+ task.updatedAt = Date.now();
47
+ }
48
+ }
49
+ fail(id, agentId, error) {
50
+ const task = this.tasks.get(id);
51
+ if (task && task.assignedTo === agentId) {
52
+ task.status = "failed";
53
+ task.error = error;
54
+ task.updatedAt = Date.now();
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,6 @@
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";
@@ -0,0 +1,6 @@
1
+ export { Agent } from "./Agent.js";
2
+ export { AgentLoader } from "./AgentLoader.js";
3
+ export { AgentRegistry } from "./AgentRegistry.js";
4
+ export { LLMProvider, AgentModelProvider, } from "./LLMProvider.js";
5
+ export { MessageBus } from "./MessageBus.js";
6
+ export { TaskQueue } from "./TaskQueue.js";
@@ -0,0 +1,19 @@
1
+ /**
2
+ * BroadcasterAgent — content distribution agent.
3
+ *
4
+ * Allowed tools: read, write, edit, vm_search, vm_fts_search
5
+ * Cannot write to durable store or publish.
6
+ * Writes output only under Agent/Presentations/.
7
+ */
8
+ import { Agent, type AgentConfig } from "../agent-bus/Agent.js";
9
+ import type { LLMProvider } from "../agent-bus/LLMProvider.js";
10
+ import type { MessageBus } from "../agent-bus/MessageBus.js";
11
+ import type { TaskQueue } from "../agent-bus/TaskQueue.js";
12
+ export declare class BroadcasterAgent extends Agent {
13
+ private taskQueue;
14
+ constructor(config: AgentConfig, bus: MessageBus, llm?: LLMProvider);
15
+ setTaskQueue(taskQueue: TaskQueue): void;
16
+ private onJobAvailable;
17
+ private executeJob;
18
+ tick(): Promise<void>;
19
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * BroadcasterAgent — content distribution agent.
3
+ *
4
+ * Allowed tools: read, write, edit, vm_search, vm_fts_search
5
+ * Cannot write to durable store or publish.
6
+ * Writes output only under Agent/Presentations/.
7
+ */
8
+ import { Agent } from "../agent-bus/Agent.js";
9
+ export class BroadcasterAgent extends Agent {
10
+ taskQueue = null;
11
+ constructor(config, bus, llm) {
12
+ super(config, bus, llm);
13
+ this.bus.subscribe("JOB_AVAILABLE", this.onJobAvailable.bind(this));
14
+ }
15
+ setTaskQueue(taskQueue) {
16
+ this.taskQueue = taskQueue;
17
+ }
18
+ async onJobAvailable(message) {
19
+ if (!this.taskQueue)
20
+ return;
21
+ const job = message.payload;
22
+ if (job.role !== "broadcaster")
23
+ return;
24
+ const taskId = this.taskQueue.add("agent-dispatch", job);
25
+ if (!this.taskQueue.claim(taskId, this.config.id))
26
+ return;
27
+ try {
28
+ await this.executeJob(job);
29
+ this.taskQueue.complete(taskId, this.config.id, { dispatchId: job.dispatchId });
30
+ await this.broadcast("JOB_COMPLETED", {
31
+ dispatchId: job.dispatchId,
32
+ agentId: this.config.id,
33
+ status: "done",
34
+ });
35
+ }
36
+ catch (err) {
37
+ const msg = err instanceof Error ? err.message : String(err);
38
+ this.taskQueue.fail(taskId, this.config.id, msg);
39
+ await this.broadcast("JOB_COMPLETED", {
40
+ dispatchId: job.dispatchId,
41
+ agentId: this.config.id,
42
+ status: "failed",
43
+ error: msg,
44
+ });
45
+ }
46
+ }
47
+ async executeJob(job) {
48
+ await this.invokePiTool("read", { path: job.filePath });
49
+ if (this.llm) {
50
+ const plan = await this.askLLM(`You are the Broadcaster agent. Task: ${job.instruction}\n\nSource file: ${job.filePath}\n\nCreate content under Agent/Presentations/. You cannot write to the durable store. Plan the tool calls needed. Return JSON: { "tools": [{"name": "...", "args": {...}}] }`);
51
+ if (plan?.tools) {
52
+ for (const tool of plan.tools) {
53
+ await this.invokePiTool(tool.name, tool.args);
54
+ }
55
+ }
56
+ }
57
+ await this.invokePiTool("edit", {
58
+ path: job.filePath,
59
+ edits: [
60
+ {
61
+ oldText: `<!-- pi-dispatch:${job.dispatchId} -->`,
62
+ newText: `> Broadcaster completed. See dispatch ${job.dispatchId}.`,
63
+ },
64
+ ],
65
+ });
66
+ }
67
+ async tick() {
68
+ // Event-driven — work arrives via JOB_AVAILABLE subscription
69
+ }
70
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * HeavyLifterAgent — external CLI execution agent.
3
+ *
4
+ * Allowed tools: read, write, edit, grep, find, ls, bash, vm_search, vm_fts_search
5
+ * Cannot write to durable store or publish.
6
+ * Runs bash in isolated worktree.
7
+ */
8
+ import { Agent, type AgentConfig } from "../agent-bus/Agent.js";
9
+ import type { LLMProvider } from "../agent-bus/LLMProvider.js";
10
+ import type { MessageBus } from "../agent-bus/MessageBus.js";
11
+ import type { TaskQueue } from "../agent-bus/TaskQueue.js";
12
+ export interface DispatchJob {
13
+ dispatchId: string;
14
+ role: string;
15
+ filePath: string;
16
+ instruction: string;
17
+ vaultPath: string;
18
+ }
19
+ export declare class HeavyLifterAgent extends Agent {
20
+ private taskQueue;
21
+ constructor(config: AgentConfig, bus: MessageBus, llm?: LLMProvider);
22
+ setTaskQueue(taskQueue: TaskQueue): void;
23
+ private onJobAvailable;
24
+ private executeJob;
25
+ tick(): Promise<void>;
26
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * HeavyLifterAgent — external CLI execution agent.
3
+ *
4
+ * Allowed tools: read, write, edit, grep, find, ls, bash, vm_search, vm_fts_search
5
+ * Cannot write to durable store or publish.
6
+ * Runs bash in isolated worktree.
7
+ */
8
+ import { Agent } from "../agent-bus/Agent.js";
9
+ export class HeavyLifterAgent extends Agent {
10
+ taskQueue = null;
11
+ constructor(config, bus, llm) {
12
+ super(config, bus, llm);
13
+ this.bus.subscribe("JOB_AVAILABLE", this.onJobAvailable.bind(this));
14
+ }
15
+ setTaskQueue(taskQueue) {
16
+ this.taskQueue = taskQueue;
17
+ }
18
+ async onJobAvailable(message) {
19
+ if (!this.taskQueue)
20
+ return;
21
+ const job = message.payload;
22
+ if (job.role !== "heavy-lifter")
23
+ return;
24
+ const taskId = this.taskQueue.add("agent-dispatch", job);
25
+ if (!this.taskQueue.claim(taskId, this.config.id))
26
+ return;
27
+ try {
28
+ await this.executeJob(job);
29
+ this.taskQueue.complete(taskId, this.config.id, { dispatchId: job.dispatchId });
30
+ await this.broadcast("JOB_COMPLETED", {
31
+ dispatchId: job.dispatchId,
32
+ agentId: this.config.id,
33
+ status: "done",
34
+ });
35
+ }
36
+ catch (err) {
37
+ const msg = err instanceof Error ? err.message : String(err);
38
+ this.taskQueue.fail(taskId, this.config.id, msg);
39
+ await this.broadcast("JOB_COMPLETED", {
40
+ dispatchId: job.dispatchId,
41
+ agentId: this.config.id,
42
+ status: "failed",
43
+ error: msg,
44
+ });
45
+ }
46
+ }
47
+ async executeJob(job) {
48
+ // Read the source file
49
+ await this.invokePiTool("read", { path: job.filePath });
50
+ // Execute the instruction — the agent uses its LLM to decide which tools to call
51
+ if (this.llm) {
52
+ const plan = await this.askLLM(`You are the Heavy-Lifter agent. Task: ${job.instruction}\n\nSource file: ${job.filePath}\nVault: ${job.vaultPath}\n\nPlan the tool calls needed. Return JSON: { "tools": [{"name": "...", "args": {...}}] }`);
53
+ if (plan?.tools) {
54
+ for (const tool of plan.tools) {
55
+ await this.invokePiTool(tool.name, tool.args);
56
+ }
57
+ }
58
+ }
59
+ // Write results back to the source file (replace dispatch marker)
60
+ await this.invokePiTool("edit", {
61
+ path: job.filePath,
62
+ edits: [
63
+ {
64
+ oldText: `<!-- pi-dispatch:${job.dispatchId} -->`,
65
+ newText: `> Heavy-Lifter completed. See dispatch ${job.dispatchId}.`,
66
+ },
67
+ ],
68
+ });
69
+ }
70
+ async tick() {
71
+ // HeavyLifter is event-driven — work arrives via JOB_AVAILABLE subscription
72
+ }
73
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * ManagerAgent — final gate and publishing agent.
3
+ *
4
+ * Allowed tools: all vm_* tools + read, write, edit, grep, find, ls, subagent
5
+ * Write collections: all
6
+ * Can publish (vm_sync enabled).
7
+ * Only the Manager can dispatch subagents and publish to the human-facing vault.
8
+ */
9
+ import { Agent, type AgentConfig } from "../agent-bus/Agent.js";
10
+ import type { LLMProvider } from "../agent-bus/LLMProvider.js";
11
+ import type { MessageBus } from "../agent-bus/MessageBus.js";
12
+ import type { TaskQueue } from "../agent-bus/TaskQueue.js";
13
+ export declare class ManagerAgent extends Agent {
14
+ private taskQueue;
15
+ constructor(config: AgentConfig, bus: MessageBus, llm?: LLMProvider);
16
+ setTaskQueue(taskQueue: TaskQueue): void;
17
+ private onJobAvailable;
18
+ private executeJob;
19
+ tick(): Promise<void>;
20
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * ManagerAgent — final gate and publishing agent.
3
+ *
4
+ * Allowed tools: all vm_* tools + read, write, edit, grep, find, ls, subagent
5
+ * Write collections: all
6
+ * Can publish (vm_sync enabled).
7
+ * Only the Manager can dispatch subagents and publish to the human-facing vault.
8
+ */
9
+ import { Agent } from "../agent-bus/Agent.js";
10
+ export class ManagerAgent extends Agent {
11
+ taskQueue = null;
12
+ constructor(config, bus, llm) {
13
+ super(config, bus, llm);
14
+ this.bus.subscribe("JOB_AVAILABLE", this.onJobAvailable.bind(this));
15
+ }
16
+ setTaskQueue(taskQueue) {
17
+ this.taskQueue = taskQueue;
18
+ }
19
+ async onJobAvailable(message) {
20
+ if (!this.taskQueue)
21
+ return;
22
+ const job = message.payload;
23
+ if (job.role !== "manager")
24
+ return;
25
+ const taskId = this.taskQueue.add("agent-dispatch", job);
26
+ if (!this.taskQueue.claim(taskId, this.config.id))
27
+ return;
28
+ try {
29
+ await this.executeJob(job);
30
+ this.taskQueue.complete(taskId, this.config.id, { dispatchId: job.dispatchId });
31
+ await this.broadcast("JOB_COMPLETED", {
32
+ dispatchId: job.dispatchId,
33
+ agentId: this.config.id,
34
+ status: "done",
35
+ });
36
+ }
37
+ catch (err) {
38
+ const msg = err instanceof Error ? err.message : String(err);
39
+ this.taskQueue.fail(taskId, this.config.id, msg);
40
+ await this.broadcast("JOB_COMPLETED", {
41
+ dispatchId: job.dispatchId,
42
+ agentId: this.config.id,
43
+ status: "failed",
44
+ error: msg,
45
+ });
46
+ }
47
+ }
48
+ async executeJob(job) {
49
+ await this.invokePiTool("read", { path: job.filePath });
50
+ if (this.llm) {
51
+ const plan = await this.askLLM(`You are the Manager agent. Task: ${job.instruction}\n\nSource file: ${job.filePath}\n\nYou are the final gate. You may dispatch subagents, write to any collection, and publish to the vault. Plan the tool calls needed. Return JSON: { "tools": [{"name": "...", "args": {...}}] }`);
52
+ if (plan?.tools) {
53
+ for (const tool of plan.tools) {
54
+ await this.invokePiTool(tool.name, tool.args);
55
+ }
56
+ }
57
+ }
58
+ await this.invokePiTool("edit", {
59
+ path: job.filePath,
60
+ edits: [
61
+ {
62
+ oldText: `<!-- pi-dispatch:${job.dispatchId} -->`,
63
+ newText: `> Manager completed. See dispatch ${job.dispatchId}.`,
64
+ },
65
+ ],
66
+ });
67
+ }
68
+ async tick() {
69
+ // Event-driven — work arrives via JOB_AVAILABLE subscription
70
+ }
71
+ }