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.
- package/CHANGELOG.md +20 -0
- package/README.md +44 -44
- package/agents/broadcaster.agent.md +30 -0
- package/agents/heavy-lifter.agent.md +30 -0
- package/agents/manager.agent.md +29 -0
- package/agents/miner.agent.md +29 -0
- package/dist/src/agent-bus/Agent.d.ts +49 -0
- package/dist/src/agent-bus/Agent.js +97 -0
- package/dist/src/agent-bus/AgentLoader.d.ts +33 -0
- package/dist/src/agent-bus/AgentLoader.js +102 -0
- package/dist/src/agent-bus/AgentRegistry.d.ts +17 -0
- package/dist/src/agent-bus/AgentRegistry.js +20 -0
- package/dist/src/agent-bus/LLMProvider.d.ts +36 -0
- package/dist/src/agent-bus/LLMProvider.js +125 -0
- package/dist/src/agent-bus/MessageBus.d.ts +47 -0
- package/dist/src/agent-bus/MessageBus.js +81 -0
- package/dist/src/agent-bus/TaskQueue.d.ts +27 -0
- package/dist/src/agent-bus/TaskQueue.js +57 -0
- package/dist/src/agent-bus/index.d.ts +6 -0
- package/dist/src/agent-bus/index.js +6 -0
- package/dist/src/agents/BroadcasterAgent.d.ts +19 -0
- package/dist/src/agents/BroadcasterAgent.js +70 -0
- package/dist/src/agents/HeavyLifterAgent.d.ts +26 -0
- package/dist/src/agents/HeavyLifterAgent.js +73 -0
- package/dist/src/agents/ManagerAgent.d.ts +20 -0
- package/dist/src/agents/ManagerAgent.js +71 -0
- package/dist/src/agents/MinerAgent.d.ts +19 -0
- package/dist/src/agents/MinerAgent.js +70 -0
- package/dist/src/agents/index.d.ts +4 -0
- package/dist/src/agents/index.js +4 -0
- package/dist/src/auth.d.ts +49 -2
- package/dist/src/auth.js +149 -7
- package/dist/src/autosync.d.ts +1 -1
- package/dist/src/autosync.js +3 -3
- package/dist/src/bridge.d.ts +45 -0
- package/dist/src/bridge.js +189 -0
- package/dist/src/commands.d.ts +1 -1
- package/dist/src/commands.js +419 -295
- package/dist/src/context-capture.d.ts +22 -0
- package/dist/src/context-capture.js +41 -0
- package/dist/src/discover-schema.d.ts +1 -1
- package/dist/src/discover-schema.js +4 -4
- package/dist/src/embed-queue.d.ts +1 -1
- package/dist/src/embed-queue.js +1 -1
- package/dist/src/engine.d.ts +65 -0
- package/dist/src/engine.js +244 -0
- package/dist/src/events.js +1 -1
- package/dist/src/graph.d.ts +3 -3
- package/dist/src/identity-injector.d.ts +15 -0
- package/dist/src/identity-injector.js +102 -0
- package/dist/src/index.js +88 -49
- package/dist/src/intake.js +4 -4
- package/dist/src/lance.d.ts +11 -37
- package/dist/src/lance.js +183 -240
- package/dist/src/modal-client.d.ts +14 -4
- package/dist/src/modal-client.js +50 -34
- package/dist/src/modal-config.d.ts +26 -10
- package/dist/src/modal-config.js +36 -5
- package/dist/src/personalize.d.ts +47 -0
- package/dist/src/personalize.js +352 -0
- package/dist/src/server.d.ts +30 -4
- package/dist/src/server.js +146 -64
- package/dist/src/settings-ui.d.ts +3 -1
- package/dist/src/settings-ui.js +84 -77
- package/dist/src/status.d.ts +25 -0
- package/dist/src/status.js +49 -0
- package/dist/src/sync.d.ts +1 -1
- package/dist/src/sync.js +19 -19
- package/dist/src/tombstone.js +2 -2
- package/dist/src/tools.js +112 -130
- package/dist/src/types.d.ts +86 -18
- package/dist/src/types.js +7 -4
- package/dist/src/utils.d.ts +5 -1
- package/dist/src/utils.js +24 -8
- package/dist/src/vault-tools.d.ts +29 -0
- package/dist/src/vault-tools.js +314 -0
- package/dist/src/vault-writer.d.ts +4 -5
- package/dist/src/vault-writer.js +13 -13
- package/dist/src/vm-handlers.d.ts +15 -0
- package/dist/src/vm-handlers.js +469 -0
- package/dist/src/watcher.d.ts +18 -4
- package/dist/src/watcher.js +109 -98
- package/dist/test/auth.test.js +57 -7
- package/dist/test/bridge-identity.test.js +388 -0
- package/dist/test/discover-schema.test.js +2 -2
- package/dist/test/engine-identity.test.js +119 -0
- package/dist/test/index.test.js +5 -6
- package/dist/test/lance-modal.test.js +7 -7
- package/dist/test/modal-config.test.js +51 -29
- package/dist/test/personalize.test.js +212 -0
- package/dist/test/rest-queue.test.js +1 -1
- package/dist/test/rest-setup.test.js +21 -21
- package/dist/test/{rest-wiki.test.js → rest-vm.test.js} +27 -23
- package/dist/test/server-ws.test.js +4 -4
- package/dist/test/sync.test.js +2 -2
- package/dist/test/tombstone.test.js +1 -1
- package/dist/test/vault-tools.test.js +224 -0
- package/package.json +2 -1
- package/scripts/check-deps.sh +207 -0
- package/scripts/e2e-ws.mjs +4 -4
- package/scripts/modal-e2e-smoke.mjs +1 -1
- package/scripts/setup-test-config.sh +1 -5
- package/scripts/test-slash-commands.mjs +197 -0
- package/skills/.agents/broadcaster.agent.md +31 -0
- package/skills/.agents/heavy-lifter.agent.md +31 -0
- package/skills/.agents/manager.agent.md +30 -0
- package/skills/.agents/miner.agent.md +30 -0
- package/skills/vault-mind/SKILL.md +50 -50
- package/skills/vault-mind/references/tool-reference.md +25 -25
- package/skills/vault-mind-broadcaster/SKILL.md +1 -1
- package/skills/vault-mind-heavy-lifter/SKILL.md +3 -3
- package/skills/vault-mind-manager/SKILL.md +7 -7
- package/skills/vault-mind-miner/SKILL.md +11 -11
- package/skills/vault-mind-setup/SKILL.md +65 -265
- package/skills/vault-mind-setup/references/obsidian-vault-structure.md +6 -6
- package/skills/vault-mind-setup/references/pi-extension-wiring.md +24 -33
- package/skills/vault-mind-setup/references/troubleshooting-tree.md +25 -26
- package/dist/src/auto-index.d.ts +0 -47
- 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
|
+
}
|