volute 0.35.0 → 0.36.0
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/dist/{activity-events-ZW4SDL2C.js → activity-events-PWOGSMRL.js} +4 -4
- package/dist/{ai-service-LURBEDDB.js → ai-service-GSZWIETO.js} +5 -5
- package/dist/{archive-ESU2FUN4.js → archive-Y2YEOCGB.js} +3 -3
- package/dist/{auth-WX4TESEI.js → auth-YTQME4EV.js} +5 -5
- package/dist/{chat-QXAJF3FU.js → chat-ED7YOGKO.js} +4 -4
- package/dist/{chunk-AOB6GVRM.js → chunk-46DYYHN6.js} +8 -3
- package/dist/{chunk-5XJYUFZH.js → chunk-6F3YNULE.js} +68 -22
- package/dist/{chunk-BDYXIWA5.js → chunk-75AJ54GM.js} +13 -2
- package/dist/{chunk-5N7Y5WAM.js → chunk-7PTQGPJY.js} +28 -12
- package/dist/{chunk-CORXD635.js → chunk-B35VNNSS.js} +3 -3
- package/dist/{chunk-PWQ2ITYG.js → chunk-BOLJUV77.js} +4 -4
- package/dist/{chunk-ZSR72JB3.js → chunk-CU6OFXMM.js} +1 -1
- package/dist/{chunk-2TGZJFAT.js → chunk-DJT5Y4UF.js} +3 -3
- package/dist/{chunk-XRQSAMX2.js → chunk-DMV5P2LU.js} +3 -3
- package/dist/{chunk-WJPROOU5.js → chunk-DQ7VBXAP.js} +635 -3692
- package/dist/{chunk-IJHIXLVN.js → chunk-GBDVNPN2.js} +13 -11
- package/dist/{chunk-WZRZFFCL.js → chunk-IIWF2IPD.js} +146 -186
- package/dist/{chunk-F7ZNLYKZ.js → chunk-KAB6UGOL.js} +2 -2
- package/dist/{chunk-VHJRZM2S.js → chunk-L72WYMF7.js} +2 -2
- package/dist/{chunk-NJK5SDGR.js → chunk-LGNUFVMR.js} +1 -1
- package/dist/{chunk-FT5KETXZ.js → chunk-M5RYAA5I.js} +2 -2
- package/dist/{chunk-J6CJQDWI.js → chunk-N2AUHW4C.js} +2 -2
- package/dist/{chunk-BMZQYACC.js → chunk-NUX47Y2V.js} +19 -4
- package/dist/{chunk-AN2W47GW.js → chunk-PJ4IPTIN.js} +2 -2
- package/dist/{chunk-N446KRP7.js → chunk-PY557GDR.js} +2 -2
- package/dist/{chunk-MDJGMOSD.js → chunk-PZYJBOQP.js} +6 -6
- package/dist/chunk-RG5TOL4O.js +18 -0
- package/dist/{chunk-N5LMGYXX.js → chunk-SWW6AUVW.js} +2 -2
- package/dist/{chunk-BKF4WQCY.js → chunk-T2TP6ZC6.js} +20 -8
- package/dist/{chunk-VY3RB2V7.js → chunk-TWAN7ZNO.js} +3 -3
- package/dist/{chunk-QCH6K235.js → chunk-UI7RPV2B.js} +1 -1
- package/dist/{chunk-QWTR6AWZ.js → chunk-X2J7QUFH.js} +2 -2
- package/dist/{chunk-A2ZLHBHG.js → chunk-YDBAY3NA.js} +2 -2
- package/dist/{chunk-BV65KRHM.js → chunk-YTWZORJN.js} +2 -2
- package/dist/{chunk-OTC67N2Z.js → chunk-ZTVKQOU7.js} +1 -1
- package/dist/cli.js +16 -16
- package/dist/{cloud-sync-6JL4C24T.js → cloud-sync-BOCZSDIA.js} +19 -20
- package/dist/connectors/discord-bridge.js +3 -3
- package/dist/connectors/slack-bridge.js +3 -3
- package/dist/connectors/telegram-bridge.js +3 -3
- package/dist/{conversations-2PW57WO2.js → conversations-HH3CJD4E.js} +15 -9
- package/dist/{create-UVCK2CS6.js → create-QBEPSD2Z.js} +1 -1
- package/dist/{daemon-restart-HSZ3BCX5.js → daemon-restart-SIR3UR4B.js} +10 -10
- package/dist/daemon.js +446 -328
- package/dist/{db-BVBJ57TU.js → db-URORGSXQ.js} +2 -2
- package/dist/delivery-manager-WTGIPBGY.js +30 -0
- package/dist/{delivery-router-HEJSJAHQ.js → delivery-router-VSULHXNH.js} +4 -4
- package/dist/down-DGGLZ5TA.js +17 -0
- package/dist/{exec-PY7THYH4.js → exec-X3C6ZZTQ.js} +4 -4
- package/dist/{export-OAS6QVBN.js → export-HTFOHOKL.js} +3 -3
- package/dist/{extension-D74CNM7G.js → extension-AKZ46YSL.js} +22 -3
- package/dist/{extensions-XDDFY72A.js → extensions-OOSFVH7U.js} +21 -20
- package/dist/{files-CWTK6V3H.js → files-H2YLRD37.js} +3 -3
- package/dist/{import-5A3T7QV4.js → import-OL5BZX7S.js} +6 -6
- package/dist/{isolation-TK5RX2WM.js → isolation-N74RWOUX.js} +3 -3
- package/dist/{list-PDMQM7ZV.js → list-GJ4RUQQT.js} +7 -1
- package/dist/{login-7TE6CIZF.js → login-JXRVMBRB.js} +2 -2
- package/dist/{logout-T4XS6LRU.js → logout-FW243JBU.js} +2 -2
- package/dist/message-delivery-YORUXKDQ.js +40 -0
- package/dist/{mind-5IEYKV7I.js → mind-6VJJHF65.js} +6 -6
- package/dist/{mind-activity-tracker-QBLIV7ZJ.js → mind-activity-tracker-66UVYIFW.js} +5 -5
- package/dist/{mind-history-IE2QH7U5.js → mind-history-MII2SK7F.js} +81 -14
- package/dist/mind-manager-TJ2SUPRX.js +30 -0
- package/dist/mind-service-E7FM2WZF.js +36 -0
- package/dist/{package-D2FSVFAX.js → package-3W2MEXHB.js} +5 -5
- package/dist/{read-67VRP2DO.js → read-ZUDG4JWU.js} +4 -4
- package/dist/{registry-GBSNW3HG.js → registry-YPHK534W.js} +2 -2
- package/dist/{sandbox-R37VIU36.js → sandbox-LP6YRAXS.js} +5 -5
- package/dist/scheduler-FRJ5DK24.js +30 -0
- package/dist/{schema-XVZ2CLKW.js → schema-MISD3JFG.js} +3 -1
- package/dist/{seed-EQORWX77.js → seed-CEC4RC23.js} +1 -1
- package/dist/{seed-cmd-ZM2XGVU2.js → seed-cmd-WTTG7SRQ.js} +2 -2
- package/dist/{seed-create-DRWGGHEI.js → seed-create-M6RCC6RP.js} +3 -3
- package/dist/{seed-sprout-JYXGXOP3.js → seed-sprout-ZKCHFJKH.js} +10 -10
- package/dist/{send-JBJJQ7CA.js → send-LXUT2GGR.js} +3 -3
- package/dist/{service-WNPCNHOX.js → service-M6N3RUYU.js} +5 -5
- package/dist/{setup-BJ4YAY26.js → setup-PJOF5UV5.js} +7 -7
- package/dist/{setup-RHJRFURI.js → setup-PMJHCZQX.js} +5 -3
- package/dist/skills/tending/SKILL.md +52 -0
- package/dist/{skills-EKMCQ46K.js → skills-2PTRTBQP.js} +7 -7
- package/dist/sleep-manager-WAZWMFJT.js +34 -0
- package/dist/spirit-6KVDIROQ.js +24 -0
- package/dist/{sprout-HE4TITMK.js → sprout-WX2FFYLP.js} +1 -1
- package/dist/src-FQE4BHRG.js +617 -0
- package/dist/src-GW6FP6VL.js +425 -0
- package/dist/src-QEOLMAYC.js +2133 -0
- package/dist/{status-ZK34WYIM.js → status-3IVSLJDN.js} +6 -6
- package/dist/system-chat-2IFS5HCX.js +34 -0
- package/dist/{tailscale-ZIZ2HWJ5.js → tailscale-DZU4WM3E.js} +3 -3
- package/dist/{template-hash-A7FNHTB7.js → template-hash-6ITI3WC4.js} +2 -2
- package/dist/up-4SCIUIMG.js +19 -0
- package/dist/{update-ANE5ZM7F.js → update-RIQYUPVN.js} +6 -6
- package/dist/{update-check-UV55CBEP.js → update-check-4TIJKVGD.js} +3 -3
- package/dist/{version-notify-FXSEMXWW.js → version-notify-UXSHBZ35.js} +21 -22
- package/dist/{volute-config-D2XVS2YI.js → volute-config-V7UFFBG3.js} +1 -1
- package/dist/web-assets/assets/index-C-eYso8Y.js +75 -0
- package/dist/web-assets/assets/index-CCv_fSte.css +1 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0006_channels.sql +17 -0
- package/drizzle/0007_drop_conversation_name_title.sql +11 -0
- package/drizzle/0008_performance_indexes.sql +6 -0
- package/drizzle/meta/0006_snapshot.json +7 -0
- package/drizzle/meta/0007_snapshot.json +7 -0
- package/drizzle/meta/_journal.json +21 -0
- package/package.json +5 -5
- package/templates/_base/home/.config/routes.json +2 -2
- package/templates/_base/home/VOLUTE.md +1 -2
- package/templates/_base/src/lib/format-prefix.ts +1 -7
- package/templates/claude/.init/.config/routes.json +2 -2
- package/templates/codex/.init/.config/routes.json +2 -2
- package/templates/pi/.init/.config/routes.json +2 -2
- package/dist/delivery-manager-H5ZVBMCQ.js +0 -31
- package/dist/down-74VXM45A.js +0 -17
- package/dist/message-delivery-GRC4W6P7.js +0 -41
- package/dist/mind-manager-HFLB5653.js +0 -31
- package/dist/mind-service-X2CAA6W6.js +0 -37
- package/dist/scheduler-Y7O4CJXL.js +0 -31
- package/dist/sleep-manager-7KFK3USC.js +0 -35
- package/dist/spirit-ZFRDXMG7.js +0 -23
- package/dist/system-chat-IDPHYHY4.js +0 -35
- package/dist/up-77ICEDEW.js +0 -19
- package/dist/web-assets/assets/index-BhxWKvbB.css +0 -1
- package/dist/web-assets/assets/index-CHVKJ9II.js +0 -75
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createExtension
|
|
4
|
+
} from "./chunk-RG5TOL4O.js";
|
|
5
|
+
import "./chunk-7KJOFUNN.js";
|
|
6
|
+
|
|
7
|
+
// packages/extensions/plan/src/index.ts
|
|
8
|
+
import { resolve } from "path";
|
|
9
|
+
|
|
10
|
+
// packages/extensions/plan/src/plans.ts
|
|
11
|
+
async function startPlan(db, getUser, userId, title, description) {
|
|
12
|
+
db.prepare(
|
|
13
|
+
"UPDATE plans SET status = 'archived', completed_at = datetime('now') WHERE status = 'active'"
|
|
14
|
+
).run();
|
|
15
|
+
const row = db.prepare(
|
|
16
|
+
`INSERT INTO plans (title, description, set_by)
|
|
17
|
+
VALUES (?, ?, ?)
|
|
18
|
+
RETURNING *`
|
|
19
|
+
).get(title, description, userId);
|
|
20
|
+
const user = await getUser(userId);
|
|
21
|
+
return {
|
|
22
|
+
...row,
|
|
23
|
+
set_by_username: user?.username ?? "unknown",
|
|
24
|
+
set_by_display_name: user?.display_name ?? null
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async function getActivePlan(db, getUser) {
|
|
28
|
+
const row = db.prepare("SELECT * FROM plans WHERE status = 'active' LIMIT 1").get();
|
|
29
|
+
if (!row) return null;
|
|
30
|
+
const user = await getUser(row.set_by);
|
|
31
|
+
const logs = db.prepare("SELECT * FROM plan_logs WHERE plan_id = ? ORDER BY created_at DESC LIMIT 20").all(row.id);
|
|
32
|
+
const messages = db.prepare("SELECT * FROM plan_messages WHERE plan_id = ? ORDER BY id DESC LIMIT 10").all(row.id);
|
|
33
|
+
const latestMessage = messages.length > 0 ? messages[0].content : null;
|
|
34
|
+
return {
|
|
35
|
+
...row,
|
|
36
|
+
set_by_username: user?.username ?? "unknown",
|
|
37
|
+
set_by_display_name: user?.display_name ?? null,
|
|
38
|
+
logs,
|
|
39
|
+
messages,
|
|
40
|
+
latestMessage
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function logProgress(db, planId, mindName, content) {
|
|
44
|
+
return db.prepare(
|
|
45
|
+
`INSERT INTO plan_logs (plan_id, mind_name, content)
|
|
46
|
+
VALUES (?, ?, ?)
|
|
47
|
+
RETURNING *`
|
|
48
|
+
).get(planId, mindName, content);
|
|
49
|
+
}
|
|
50
|
+
function addPlanMessage(db, planId, content) {
|
|
51
|
+
return db.prepare(
|
|
52
|
+
`INSERT INTO plan_messages (plan_id, content)
|
|
53
|
+
VALUES (?, ?)
|
|
54
|
+
RETURNING *`
|
|
55
|
+
).get(planId, content);
|
|
56
|
+
}
|
|
57
|
+
function finishPlan(db, planId, message) {
|
|
58
|
+
const result = db.prepare(
|
|
59
|
+
"UPDATE plans SET status = 'completed', completed_at = datetime('now'), finish_message = ? WHERE id = ? AND status = 'active'"
|
|
60
|
+
).run(message ?? null, planId);
|
|
61
|
+
return result.changes > 0;
|
|
62
|
+
}
|
|
63
|
+
async function listPlans(db, getUser, opts) {
|
|
64
|
+
const limit = opts?.limit ?? 20;
|
|
65
|
+
const offset = opts?.offset ?? 0;
|
|
66
|
+
const rows = opts?.status ? db.prepare("SELECT * FROM plans WHERE status = ? ORDER BY created_at DESC LIMIT ? OFFSET ?").all(opts.status, limit, offset) : db.prepare("SELECT * FROM plans ORDER BY created_at DESC LIMIT ? OFFSET ?").all(limit, offset);
|
|
67
|
+
const userCache = /* @__PURE__ */ new Map();
|
|
68
|
+
const result = [];
|
|
69
|
+
for (const row of rows) {
|
|
70
|
+
if (!userCache.has(row.set_by)) {
|
|
71
|
+
const u = await getUser(row.set_by);
|
|
72
|
+
userCache.set(row.set_by, {
|
|
73
|
+
username: u?.username ?? "unknown",
|
|
74
|
+
display_name: u?.display_name ?? null
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const userInfo = userCache.get(row.set_by);
|
|
78
|
+
result.push({
|
|
79
|
+
...row,
|
|
80
|
+
set_by_username: userInfo.username,
|
|
81
|
+
set_by_display_name: userInfo.display_name
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// packages/extensions/plan/src/commands.ts
|
|
88
|
+
async function announceToSystem(text, ctx) {
|
|
89
|
+
if (!ctx) return false;
|
|
90
|
+
try {
|
|
91
|
+
await ctx.announceToSystem(text);
|
|
92
|
+
return true;
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error("[plan] Failed to announce to system channel:", err);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function createCommands() {
|
|
99
|
+
return {
|
|
100
|
+
start: {
|
|
101
|
+
description: "Start a new system plan",
|
|
102
|
+
args: [
|
|
103
|
+
{ name: "title", required: true, description: "Plan title" },
|
|
104
|
+
{ name: "description", description: "Plan description (or pipe via stdin)" }
|
|
105
|
+
],
|
|
106
|
+
handler: async ({ args }, ctx) => {
|
|
107
|
+
if (!ctx.db) return { error: "Plan extension requires a database" };
|
|
108
|
+
const mindName = ctx.mindName;
|
|
109
|
+
if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
|
|
110
|
+
const user = await ctx.getUserByUsername(mindName);
|
|
111
|
+
if (!user) return { error: `Unknown mind: ${mindName}` };
|
|
112
|
+
const title = args.title;
|
|
113
|
+
const description = args.description ?? ctx.stdin ?? "";
|
|
114
|
+
if (!title) return { error: 'Usage: volute plan start "title" "description"' };
|
|
115
|
+
const plan = await startPlan(ctx.db, ctx.getUser, user.id, title, description);
|
|
116
|
+
ctx.publishActivity({
|
|
117
|
+
type: "plan_started",
|
|
118
|
+
mind: user.username,
|
|
119
|
+
summary: `${user.username} started plan: "${title}"`,
|
|
120
|
+
metadata: { planId: plan.id, title }
|
|
121
|
+
});
|
|
122
|
+
return { output: `Plan started: ${plan.title}` };
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
message: {
|
|
126
|
+
description: "Post a message about the current plan (sent to #system)",
|
|
127
|
+
args: [{ name: "content", description: "Message content (or pipe via stdin)" }],
|
|
128
|
+
handler: async ({ args }, ctx) => {
|
|
129
|
+
if (!ctx.db) return { error: "Plan extension requires a database" };
|
|
130
|
+
const mindName = ctx.mindName;
|
|
131
|
+
if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
|
|
132
|
+
const content = args.content ?? ctx.stdin;
|
|
133
|
+
if (!content) return { error: 'Usage: volute plan message "your message"' };
|
|
134
|
+
const plan = await getActivePlan(ctx.db, ctx.getUser);
|
|
135
|
+
if (!plan) return { error: "No active plan" };
|
|
136
|
+
const msg = addPlanMessage(ctx.db, plan.id, content);
|
|
137
|
+
ctx.publishActivity({
|
|
138
|
+
type: "plan_message",
|
|
139
|
+
mind: mindName,
|
|
140
|
+
summary: `Plan message: "${content.slice(0, 100)}"`,
|
|
141
|
+
metadata: { planId: plan.id, messageId: msg.id }
|
|
142
|
+
});
|
|
143
|
+
const announced = await announceToSystem(`[Plan: ${plan.title}] ${content}`, ctx);
|
|
144
|
+
return {
|
|
145
|
+
output: announced ? "Message posted to #system." : "Message logged (system channel unavailable)."
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
log: {
|
|
150
|
+
description: "Log progress on the current plan",
|
|
151
|
+
args: [{ name: "content", description: "Progress update (or pipe via stdin)" }],
|
|
152
|
+
handler: async ({ args }, ctx) => {
|
|
153
|
+
if (!ctx.db) return { error: "Plan extension requires a database" };
|
|
154
|
+
const mindName = ctx.mindName;
|
|
155
|
+
if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
|
|
156
|
+
const content = args.content ?? ctx.stdin;
|
|
157
|
+
if (!content) return { error: 'Usage: volute plan log "progress update"' };
|
|
158
|
+
const plan = await getActivePlan(ctx.db, ctx.getUser);
|
|
159
|
+
if (!plan) return { error: "No active plan" };
|
|
160
|
+
const log = logProgress(ctx.db, plan.id, mindName, content);
|
|
161
|
+
ctx.publishActivity({
|
|
162
|
+
type: "plan_progress",
|
|
163
|
+
mind: mindName,
|
|
164
|
+
summary: `${mindName} logged progress: "${content.slice(0, 100)}"`,
|
|
165
|
+
metadata: { planId: plan.id, logId: log.id }
|
|
166
|
+
});
|
|
167
|
+
return { output: "Progress logged." };
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
current: {
|
|
171
|
+
description: "Show the current active plan",
|
|
172
|
+
handler: async (_parsed, ctx) => {
|
|
173
|
+
if (!ctx.db) return { error: "Plan extension requires a database" };
|
|
174
|
+
const plan = await getActivePlan(ctx.db, ctx.getUser);
|
|
175
|
+
if (!plan) return { output: "No active plan." };
|
|
176
|
+
const lines = [
|
|
177
|
+
`# ${plan.title}`,
|
|
178
|
+
"",
|
|
179
|
+
`Set by ${plan.set_by_username} \u2014 ${new Date(plan.created_at).toLocaleString()}`
|
|
180
|
+
];
|
|
181
|
+
if (plan.description) {
|
|
182
|
+
lines.push("", plan.description);
|
|
183
|
+
}
|
|
184
|
+
if (plan.latestMessage) {
|
|
185
|
+
lines.push("", `## Latest message`, "", plan.latestMessage);
|
|
186
|
+
}
|
|
187
|
+
if (plan.logs.length > 0) {
|
|
188
|
+
lines.push("", "## Progress");
|
|
189
|
+
for (const log of plan.logs) {
|
|
190
|
+
const date = new Date(log.created_at).toLocaleString();
|
|
191
|
+
lines.push(` ${log.mind_name} (${date}): ${log.content}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return { output: lines.join("\n") };
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
history: {
|
|
198
|
+
description: "List past plans",
|
|
199
|
+
flags: {
|
|
200
|
+
limit: { type: "number", description: "Max number of plans to show (default: 10)" }
|
|
201
|
+
},
|
|
202
|
+
handler: async ({ flags }, ctx) => {
|
|
203
|
+
if (!ctx.db) return { error: "Plan extension requires a database" };
|
|
204
|
+
const limit = flags.limit ?? 10;
|
|
205
|
+
const plans = await listPlans(ctx.db, ctx.getUser, { limit });
|
|
206
|
+
if (plans.length === 0) return { output: "No plans found." };
|
|
207
|
+
const lines = plans.map((p) => {
|
|
208
|
+
const date = new Date(p.created_at).toLocaleDateString();
|
|
209
|
+
const status = p.status === "active" ? " [active]" : "";
|
|
210
|
+
return ` ${p.title} (${date}, by ${p.set_by_username})${status}`;
|
|
211
|
+
});
|
|
212
|
+
return { output: lines.join("\n") };
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
finish: {
|
|
216
|
+
description: "Finish the current plan with a closing message",
|
|
217
|
+
args: [{ name: "message", description: "Closing message (or pipe via stdin)" }],
|
|
218
|
+
handler: async ({ args }, ctx) => {
|
|
219
|
+
if (!ctx.db) return { error: "Plan extension requires a database" };
|
|
220
|
+
const mindName = ctx.mindName;
|
|
221
|
+
if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
|
|
222
|
+
const plan = await getActivePlan(ctx.db, ctx.getUser);
|
|
223
|
+
if (!plan) return { error: "No active plan" };
|
|
224
|
+
const message = args.message ?? ctx.stdin ?? "";
|
|
225
|
+
finishPlan(ctx.db, plan.id, message);
|
|
226
|
+
ctx.publishActivity({
|
|
227
|
+
type: "plan_finished",
|
|
228
|
+
mind: mindName,
|
|
229
|
+
summary: `${mindName} finished plan: "${plan.title}"`,
|
|
230
|
+
metadata: { planId: plan.id }
|
|
231
|
+
});
|
|
232
|
+
const announcement = message ? `[Plan finished: ${plan.title}] ${message}` : `[Plan finished: ${plan.title}]`;
|
|
233
|
+
const announced = await announceToSystem(announcement, ctx);
|
|
234
|
+
const suffix = announced ? "" : " (system channel unavailable)";
|
|
235
|
+
return { output: `Finished: ${plan.title}${suffix}` };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// packages/extensions/plan/src/db.ts
|
|
242
|
+
function initDb(db) {
|
|
243
|
+
db.exec(`
|
|
244
|
+
CREATE TABLE IF NOT EXISTS plans (
|
|
245
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
246
|
+
title TEXT NOT NULL,
|
|
247
|
+
description TEXT NOT NULL DEFAULT '',
|
|
248
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
249
|
+
set_by INTEGER NOT NULL,
|
|
250
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
251
|
+
completed_at TEXT,
|
|
252
|
+
finish_message TEXT
|
|
253
|
+
);
|
|
254
|
+
CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status);
|
|
255
|
+
CREATE INDEX IF NOT EXISTS idx_plans_created_at ON plans(created_at);
|
|
256
|
+
|
|
257
|
+
CREATE TABLE IF NOT EXISTS plan_logs (
|
|
258
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
259
|
+
plan_id INTEGER NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
|
|
260
|
+
mind_name TEXT NOT NULL,
|
|
261
|
+
content TEXT NOT NULL,
|
|
262
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
263
|
+
);
|
|
264
|
+
CREATE INDEX IF NOT EXISTS idx_plan_logs_plan_id ON plan_logs(plan_id);
|
|
265
|
+
|
|
266
|
+
CREATE TABLE IF NOT EXISTS plan_messages (
|
|
267
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
268
|
+
plan_id INTEGER NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
|
|
269
|
+
content TEXT NOT NULL,
|
|
270
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
271
|
+
);
|
|
272
|
+
CREATE INDEX IF NOT EXISTS idx_plan_messages_plan_id ON plan_messages(plan_id);
|
|
273
|
+
`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// packages/extensions/plan/src/routes.ts
|
|
277
|
+
import { Hono } from "hono";
|
|
278
|
+
function resolveUserId(c) {
|
|
279
|
+
const user = c.get("user");
|
|
280
|
+
if (!user || user.id === 0) return null;
|
|
281
|
+
return user;
|
|
282
|
+
}
|
|
283
|
+
async function parseJson(c) {
|
|
284
|
+
try {
|
|
285
|
+
return await c.req.json();
|
|
286
|
+
} catch {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function createRoutes(ctx) {
|
|
291
|
+
if (!ctx.db) throw new Error("Plan extension requires a database");
|
|
292
|
+
const db = ctx.db;
|
|
293
|
+
const { getUser } = ctx;
|
|
294
|
+
const app = new Hono().get("/current", async (c) => {
|
|
295
|
+
const plan = await getActivePlan(db, getUser);
|
|
296
|
+
if (!plan) return c.json(null);
|
|
297
|
+
return c.json(plan);
|
|
298
|
+
}).get("/", async (c) => {
|
|
299
|
+
const status = c.req.query("status");
|
|
300
|
+
const rawLimit = c.req.query("limit");
|
|
301
|
+
const rawOffset = c.req.query("offset");
|
|
302
|
+
const limit = rawLimit ? parseInt(rawLimit, 10) : void 0;
|
|
303
|
+
const offset = rawOffset ? parseInt(rawOffset, 10) : void 0;
|
|
304
|
+
if (limit !== void 0 && Number.isNaN(limit) || offset !== void 0 && Number.isNaN(offset)) {
|
|
305
|
+
return c.json({ error: "Invalid limit or offset parameter" }, 400);
|
|
306
|
+
}
|
|
307
|
+
const plans = await listPlans(db, getUser, { status: status ?? void 0, limit, offset });
|
|
308
|
+
return c.json(plans);
|
|
309
|
+
}).post("/", async (c) => {
|
|
310
|
+
const actor = resolveUserId(c);
|
|
311
|
+
if (!actor) return c.json({ error: "Unauthorized" }, 401);
|
|
312
|
+
if (actor.role !== "admin" && actor.user_type !== "mind") {
|
|
313
|
+
return c.json({ error: "Only spirit or admin can start plans" }, 403);
|
|
314
|
+
}
|
|
315
|
+
const body = await parseJson(c);
|
|
316
|
+
if (!body) return c.json({ error: "Invalid JSON body" }, 400);
|
|
317
|
+
if (!body.title) return c.json({ error: "title is required" }, 400);
|
|
318
|
+
const plan = await startPlan(db, getUser, actor.id, body.title, body.description ?? "");
|
|
319
|
+
ctx.publishActivity({
|
|
320
|
+
type: "plan_started",
|
|
321
|
+
mind: actor.username,
|
|
322
|
+
summary: `${actor.username} started plan: "${body.title}"`,
|
|
323
|
+
metadata: { planId: plan.id, title: body.title }
|
|
324
|
+
});
|
|
325
|
+
return c.json(plan, 201);
|
|
326
|
+
}).post("/:id{[0-9]+}/message", async (c) => {
|
|
327
|
+
const actor = resolveUserId(c);
|
|
328
|
+
if (!actor) return c.json({ error: "Unauthorized" }, 401);
|
|
329
|
+
const planId = parseInt(c.req.param("id"), 10);
|
|
330
|
+
if (Number.isNaN(planId)) return c.json({ error: "Invalid plan ID" }, 400);
|
|
331
|
+
const plan = db.prepare("SELECT id FROM plans WHERE id = ? AND status = 'active'").get(planId);
|
|
332
|
+
if (!plan) return c.json({ error: "Active plan not found" }, 404);
|
|
333
|
+
const body = await parseJson(c);
|
|
334
|
+
if (!body) return c.json({ error: "Invalid JSON body" }, 400);
|
|
335
|
+
if (!body.content) return c.json({ error: "content is required" }, 400);
|
|
336
|
+
const msg = addPlanMessage(db, planId, body.content);
|
|
337
|
+
ctx.publishActivity({
|
|
338
|
+
type: "plan_message",
|
|
339
|
+
mind: actor.username,
|
|
340
|
+
summary: `Plan message: "${body.content.slice(0, 100)}"`,
|
|
341
|
+
metadata: { planId, messageId: msg.id }
|
|
342
|
+
});
|
|
343
|
+
return c.json(msg, 201);
|
|
344
|
+
}).post("/:id{[0-9]+}/log", async (c) => {
|
|
345
|
+
const actor = resolveUserId(c);
|
|
346
|
+
if (!actor) return c.json({ error: "Unauthorized" }, 401);
|
|
347
|
+
const planId = parseInt(c.req.param("id"), 10);
|
|
348
|
+
if (Number.isNaN(planId)) return c.json({ error: "Invalid plan ID" }, 400);
|
|
349
|
+
const plan = db.prepare("SELECT id FROM plans WHERE id = ? AND status = 'active'").get(planId);
|
|
350
|
+
if (!plan) return c.json({ error: "Active plan not found" }, 404);
|
|
351
|
+
const body = await parseJson(c);
|
|
352
|
+
if (!body) return c.json({ error: "Invalid JSON body" }, 400);
|
|
353
|
+
if (!body.content) return c.json({ error: "content is required" }, 400);
|
|
354
|
+
const log = logProgress(db, planId, actor.username, body.content);
|
|
355
|
+
ctx.publishActivity({
|
|
356
|
+
type: "plan_progress",
|
|
357
|
+
mind: actor.username,
|
|
358
|
+
summary: `${actor.username} logged progress: "${body.content.slice(0, 100)}"`,
|
|
359
|
+
metadata: { planId, logId: log.id }
|
|
360
|
+
});
|
|
361
|
+
return c.json(log, 201);
|
|
362
|
+
}).patch("/:id{[0-9]+}/finish", async (c) => {
|
|
363
|
+
const actor = resolveUserId(c);
|
|
364
|
+
if (!actor) return c.json({ error: "Unauthorized" }, 401);
|
|
365
|
+
if (actor.role !== "admin" && actor.user_type !== "mind") {
|
|
366
|
+
return c.json({ error: "Only spirit or admin can finish plans" }, 403);
|
|
367
|
+
}
|
|
368
|
+
const planId = parseInt(c.req.param("id"), 10);
|
|
369
|
+
if (Number.isNaN(planId)) return c.json({ error: "Invalid plan ID" }, 400);
|
|
370
|
+
const body = await parseJson(c);
|
|
371
|
+
const message = body?.message;
|
|
372
|
+
const ok = finishPlan(db, planId, message);
|
|
373
|
+
if (!ok) return c.json({ error: "Plan not found" }, 404);
|
|
374
|
+
ctx.publishActivity({
|
|
375
|
+
type: "plan_finished",
|
|
376
|
+
mind: actor.username,
|
|
377
|
+
summary: `${actor.username} finished a plan`,
|
|
378
|
+
metadata: { planId }
|
|
379
|
+
});
|
|
380
|
+
return c.json({ ok: true });
|
|
381
|
+
}).get("/feed", async (c) => {
|
|
382
|
+
const rawLimit = c.req.query("limit");
|
|
383
|
+
const limit = rawLimit ? parseInt(rawLimit, 10) : 5;
|
|
384
|
+
if (Number.isNaN(limit)) return c.json({ error: "Invalid limit parameter" }, 400);
|
|
385
|
+
const plans = await listPlans(db, getUser, { limit });
|
|
386
|
+
return c.json(
|
|
387
|
+
plans.map((p) => ({
|
|
388
|
+
id: `plan-${p.id}`,
|
|
389
|
+
title: p.title,
|
|
390
|
+
url: `/plan`,
|
|
391
|
+
date: p.created_at,
|
|
392
|
+
author: p.set_by_username,
|
|
393
|
+
bodyHtml: p.description || `<em>${p.status}</em>`,
|
|
394
|
+
icon: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6"/><path d="M8 4v4l2.5 2.5"/></svg>',
|
|
395
|
+
color: "blue"
|
|
396
|
+
}))
|
|
397
|
+
);
|
|
398
|
+
});
|
|
399
|
+
return app;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// packages/extensions/plan/src/index.ts
|
|
403
|
+
var assetsDir = resolve(import.meta.dirname, "../dist/ui");
|
|
404
|
+
var skillsDir = resolve(import.meta.dirname, "../skills");
|
|
405
|
+
var src_default = createExtension({
|
|
406
|
+
id: "plan",
|
|
407
|
+
name: "Plan",
|
|
408
|
+
version: "0.1.0",
|
|
409
|
+
description: "System-wide plans for coordinated mind activity",
|
|
410
|
+
icon: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6"/><path d="M8 4v4l2.5 2.5"/></svg>',
|
|
411
|
+
color: "blue",
|
|
412
|
+
routes: (ctx) => createRoutes(ctx),
|
|
413
|
+
commands: createCommands(),
|
|
414
|
+
initDb,
|
|
415
|
+
skillsDir,
|
|
416
|
+
standardSkill: true,
|
|
417
|
+
ui: {
|
|
418
|
+
assetsDir,
|
|
419
|
+
systemSection: { id: "plan", label: "Plan", urlPatterns: ["/plan"] },
|
|
420
|
+
feedSource: { endpoint: "/api/ext/plan/feed" }
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
export {
|
|
424
|
+
src_default as default
|
|
425
|
+
};
|