opencode-workspace-mailbox 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +347 -2
- package/dist/config.d.ts +32 -0
- package/dist/config.js +85 -0
- package/dist/config.js.map +1 -0
- package/dist/discovery.d.ts +41 -0
- package/dist/discovery.js +101 -0
- package/dist/discovery.js.map +1 -0
- package/dist/hash.d.ts +6 -0
- package/dist/hash.js +11 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +537 -0
- package/dist/index.js.map +1 -0
- package/dist/mailbox.d.ts +35 -0
- package/dist/mailbox.js +80 -0
- package/dist/mailbox.js.map +1 -0
- package/dist/parser.d.ts +17 -0
- package/dist/parser.js +78 -0
- package/dist/parser.js.map +1 -0
- package/dist/paths.d.ts +8 -0
- package/dist/paths.js +34 -0
- package/dist/paths.js.map +1 -0
- package/dist/prompt.d.ts +3 -0
- package/dist/prompt.js +15 -0
- package/dist/prompt.js.map +1 -0
- package/dist/state.d.ts +27 -0
- package/dist/state.js +94 -0
- package/dist/state.js.map +1 -0
- package/dist/store.d.ts +34 -0
- package/dist/store.js +55 -0
- package/dist/store.js.map +1 -0
- package/package.json +46 -4
- package/src/config.ts +133 -0
- package/src/discovery.ts +133 -0
- package/src/hash.ts +12 -0
- package/src/index.ts +589 -0
- package/src/mailbox.ts +105 -0
- package/src/parser.ts +103 -0
- package/src/paths.ts +38 -0
- package/src/prompt.ts +20 -0
- package/src/state.ts +119 -0
- package/src/store.ts +91 -0
package/dist/mailbox.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { messageHash } from "./hash.js";
|
|
4
|
+
import { isOpen, parseMailbox } from "./parser.js";
|
|
5
|
+
import { isSafeRecipient, mailboxFile, PathSafetyError } from "./paths.js";
|
|
6
|
+
function pad2(n) {
|
|
7
|
+
return String(n).padStart(2, "0");
|
|
8
|
+
}
|
|
9
|
+
function buildHeader(to, from, now) {
|
|
10
|
+
const date = `${now.getFullYear()}-${pad2(now.getMonth() + 1)}-${pad2(now.getDate())}`;
|
|
11
|
+
const time = `${pad2(now.getHours())}:${pad2(now.getMinutes())}`;
|
|
12
|
+
return `${date} ${time} — from ${from} → to ${to}`;
|
|
13
|
+
}
|
|
14
|
+
export function sendMessage(req) {
|
|
15
|
+
if (!isSafeRecipient(req.to)) {
|
|
16
|
+
throw new PathSafetyError(`Invalid recipient "${req.to}"`);
|
|
17
|
+
}
|
|
18
|
+
const topic = (req.topic ?? "").replace(/\r?\n/g, " ").trim();
|
|
19
|
+
if (!topic)
|
|
20
|
+
throw new Error("topic is required");
|
|
21
|
+
const body = (req.body ?? "").trim();
|
|
22
|
+
const file = mailboxFile(req.mailboxDir, req.to);
|
|
23
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
24
|
+
const status = req.status ?? "OPEN";
|
|
25
|
+
const header = buildHeader(req.to, req.from, req.now ?? new Date());
|
|
26
|
+
const block = `### ${header}\n**Status:** ${status}\n**Topic:** ${topic}\n${body}\n`;
|
|
27
|
+
const preamble = `# ${req.to} mailbox\n\nMessages addressed to \`${req.to}\`. Append-only.\n`;
|
|
28
|
+
if (!existsSync(file)) {
|
|
29
|
+
writeFileSync(file, preamble, "utf8");
|
|
30
|
+
appendFileSync(file, `\n${block}`, "utf8");
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const existing = readFileSync(file, "utf8");
|
|
34
|
+
const sep = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
35
|
+
appendFileSync(file, `${sep}${block}`, "utf8");
|
|
36
|
+
}
|
|
37
|
+
const hash = messageHash({ header, topic, body });
|
|
38
|
+
return { file, header, hash, text: block };
|
|
39
|
+
}
|
|
40
|
+
export function readMailbox(file) {
|
|
41
|
+
if (!existsSync(file))
|
|
42
|
+
return { file, exists: false, messages: [], open: [] };
|
|
43
|
+
const text = readFileSync(file, "utf8");
|
|
44
|
+
const messages = parseMailbox(text);
|
|
45
|
+
return { file, exists: true, messages, open: messages.filter(isOpen) };
|
|
46
|
+
}
|
|
47
|
+
export function ackMessage(req) {
|
|
48
|
+
const hasHash = typeof req.hash === "string" && req.hash.length > 0;
|
|
49
|
+
const hasHeader = typeof req.header === "string" && req.header.length > 0;
|
|
50
|
+
if ((hasHash ? 1 : 0) + (hasHeader ? 1 : 0) !== 1) {
|
|
51
|
+
return { changed: false, reason: "missing-args" };
|
|
52
|
+
}
|
|
53
|
+
if (!existsSync(req.file))
|
|
54
|
+
return { changed: false, reason: "not-found" };
|
|
55
|
+
const text = readFileSync(req.file, "utf8");
|
|
56
|
+
const messages = parseMailbox(text);
|
|
57
|
+
const matches = messages.filter((m) => {
|
|
58
|
+
if (hasHash)
|
|
59
|
+
return m.hash === req.hash;
|
|
60
|
+
return m.header.trim().toLowerCase() === req.header.trim().toLowerCase();
|
|
61
|
+
});
|
|
62
|
+
if (matches.length === 0)
|
|
63
|
+
return { changed: false, reason: "not-found" };
|
|
64
|
+
if (matches.length > 1)
|
|
65
|
+
return { changed: false, reason: "ambiguous" };
|
|
66
|
+
const message = matches[0];
|
|
67
|
+
if (message.status === "DONE")
|
|
68
|
+
return { changed: false, message, reason: "already-done" };
|
|
69
|
+
if (message.statusStart < 0 || message.statusEnd < 0)
|
|
70
|
+
return { changed: false, reason: "not-found" };
|
|
71
|
+
// Replace the whole status-line content, not just the first word: real
|
|
72
|
+
// messages carry annotations like `OPEN (confirm one detail)` and flipping the
|
|
73
|
+
// token alone would leave a nonsensical `DONE (confirm one detail)`.
|
|
74
|
+
const lineEnd = message.statusLineEnd >= message.statusEnd ? message.statusLineEnd : message.statusEnd;
|
|
75
|
+
const next = text.slice(0, message.statusStart) + "DONE" + text.slice(lineEnd);
|
|
76
|
+
writeFileSync(req.file, next, "utf8");
|
|
77
|
+
const updated = parseMailbox(next).find((m) => m.hash === message.hash) ?? message;
|
|
78
|
+
return { changed: true, message: updated };
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=mailbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mailbox.js","sourceRoot":"","sources":["../src/mailbox.ts"],"names":[],"mappings":"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"}
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type MailboxMessage = {
|
|
2
|
+
header: string;
|
|
3
|
+
from: string;
|
|
4
|
+
to: string;
|
|
5
|
+
status: string;
|
|
6
|
+
topic: string;
|
|
7
|
+
body: string;
|
|
8
|
+
start: number;
|
|
9
|
+
end: number;
|
|
10
|
+
statusStart: number;
|
|
11
|
+
statusEnd: number;
|
|
12
|
+
/** Char offset of the end of the status line's content (before its line ending); -1 if absent. */
|
|
13
|
+
statusLineEnd: number;
|
|
14
|
+
hash: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function parseMailbox(text: string): MailboxMessage[];
|
|
17
|
+
export declare function isOpen(message: MailboxMessage): boolean;
|
package/dist/parser.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { messageHash } from "./hash.js";
|
|
2
|
+
function parseHeader(headerText) {
|
|
3
|
+
const s = headerText.trim();
|
|
4
|
+
const re = /(?:—|–|-)\s*from\s+(.+?)\s*(?:→|->|-->)\s*to\s+(.+)$/;
|
|
5
|
+
const m = s.match(re);
|
|
6
|
+
if (!m)
|
|
7
|
+
return { from: "", to: "" };
|
|
8
|
+
return { from: m[1].trim(), to: m[2].trim() };
|
|
9
|
+
}
|
|
10
|
+
const HEADER_DATE_RE = /^\d{4}-\d{2}-\d{2}/;
|
|
11
|
+
/**
|
|
12
|
+
* A `###` line is a message header only when it carries a date or a
|
|
13
|
+
* `from ... to ...` shape. Anything else (`### Notes` inside a body) stays part
|
|
14
|
+
* of the previous message instead of splitting it.
|
|
15
|
+
*/
|
|
16
|
+
function looksLikeMessageHeader(header) {
|
|
17
|
+
const text = header.trim();
|
|
18
|
+
if (HEADER_DATE_RE.test(text))
|
|
19
|
+
return true;
|
|
20
|
+
const { from, to } = parseHeader(text);
|
|
21
|
+
return from.length > 0 && to.length > 0;
|
|
22
|
+
}
|
|
23
|
+
export function parseMailbox(text) {
|
|
24
|
+
const messages = [];
|
|
25
|
+
const starts = Array.from(text.matchAll(/^###[ \t]+/gm))
|
|
26
|
+
.map((match) => match.index)
|
|
27
|
+
.filter((index) => {
|
|
28
|
+
const nl = text.indexOf("\n", index);
|
|
29
|
+
const line = text.slice(index, nl === -1 ? text.length : nl).replace(/\r$/, "");
|
|
30
|
+
return looksLikeMessageHeader(line.replace(/^###[ \t]+/, ""));
|
|
31
|
+
});
|
|
32
|
+
for (let i = 0; i < starts.length; i++) {
|
|
33
|
+
const start = starts[i];
|
|
34
|
+
const end = i + 1 < starts.length ? starts[i + 1] : text.length;
|
|
35
|
+
let nl = text.indexOf("\n", start);
|
|
36
|
+
if (nl === -1 || nl >= end)
|
|
37
|
+
nl = end;
|
|
38
|
+
let headerLine = text.slice(start, nl);
|
|
39
|
+
if (headerLine.endsWith("\r"))
|
|
40
|
+
headerLine = headerLine.slice(0, -1);
|
|
41
|
+
const header = headerLine.replace(/^###[ \t]+/, "");
|
|
42
|
+
const { from, to } = parseHeader(header);
|
|
43
|
+
const regionStart = nl >= text.length ? nl : nl + 1;
|
|
44
|
+
const region = text.slice(regionStart, end);
|
|
45
|
+
let status = "";
|
|
46
|
+
let statusStart = -1;
|
|
47
|
+
let statusEnd = -1;
|
|
48
|
+
let statusLineEnd = -1;
|
|
49
|
+
const statusMatch = /\*\*Status:\*\*[ \t]*([^\r\n]*)/.exec(region);
|
|
50
|
+
if (statusMatch) {
|
|
51
|
+
const raw = statusMatch[1];
|
|
52
|
+
const word = raw.match(/\S+/);
|
|
53
|
+
if (word) {
|
|
54
|
+
status = word[0].toUpperCase();
|
|
55
|
+
const prefix = statusMatch[0].length - statusMatch[1].length;
|
|
56
|
+
statusStart = regionStart + statusMatch.index + prefix + (word.index ?? 0);
|
|
57
|
+
statusEnd = statusStart + word[0].length;
|
|
58
|
+
}
|
|
59
|
+
statusLineEnd = regionStart + statusMatch.index + statusMatch[0].length;
|
|
60
|
+
}
|
|
61
|
+
let topic = "";
|
|
62
|
+
let topicLineEnd = -1;
|
|
63
|
+
const topicMatch = /\*\*Topic:\*\*[ \t]*([^\r\n]*)/.exec(region);
|
|
64
|
+
if (topicMatch) {
|
|
65
|
+
topic = topicMatch[1].trim();
|
|
66
|
+
topicLineEnd = regionStart + topicMatch.index + topicMatch[0].length;
|
|
67
|
+
}
|
|
68
|
+
const bodyStart = Math.max(regionStart, statusLineEnd, topicLineEnd);
|
|
69
|
+
const body = text.slice(bodyStart, end).trim();
|
|
70
|
+
const hash = messageHash({ header, topic, body });
|
|
71
|
+
messages.push({ header, from, to, status, topic, body, start, end, statusStart, statusEnd, statusLineEnd, hash });
|
|
72
|
+
}
|
|
73
|
+
return messages;
|
|
74
|
+
}
|
|
75
|
+
export function isOpen(message) {
|
|
76
|
+
return message.status === "OPEN";
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"}
|
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const RECIPIENT_RE: RegExp;
|
|
2
|
+
export declare class PathSafetyError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare function isSafeRecipient(name: unknown): name is string;
|
|
6
|
+
export declare function resolveInside(baseDir: string, ...segments: string[]): string;
|
|
7
|
+
export declare function mailboxFile(mailboxDir: string, recipient: string): string;
|
|
8
|
+
export declare function assertInside(baseDir: string, candidate: string, label: string): string;
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
export const RECIPIENT_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
3
|
+
export class PathSafetyError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "PathSafetyError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function isSafeRecipient(name) {
|
|
10
|
+
return typeof name === "string" && RECIPIENT_RE.test(name);
|
|
11
|
+
}
|
|
12
|
+
export function resolveInside(baseDir, ...segments) {
|
|
13
|
+
const target = path.resolve(baseDir, ...segments);
|
|
14
|
+
const rel = path.relative(baseDir, target);
|
|
15
|
+
if (rel === "" || rel === ".." || rel.startsWith(".." + path.sep) || path.isAbsolute(rel)) {
|
|
16
|
+
throw new PathSafetyError(`Path escapes base directory: ${target}`);
|
|
17
|
+
}
|
|
18
|
+
return target;
|
|
19
|
+
}
|
|
20
|
+
export function mailboxFile(mailboxDir, recipient) {
|
|
21
|
+
if (!isSafeRecipient(recipient)) {
|
|
22
|
+
throw new PathSafetyError(`Invalid recipient "${recipient}"`);
|
|
23
|
+
}
|
|
24
|
+
return resolveInside(mailboxDir, recipient + ".md");
|
|
25
|
+
}
|
|
26
|
+
export function assertInside(baseDir, candidate, label) {
|
|
27
|
+
try {
|
|
28
|
+
return resolveInside(baseDir, candidate);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw new PathSafetyError(`${label} must resolve inside the project directory: ${candidate}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,MAAM,CAAC,MAAM,YAAY,GAAG,4BAA4B,CAAA;AAExD,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACxC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAA;IAC/B,CAAC;CACF;AAED,MAAM,UAAU,eAAe,CAAC,IAAa;IAC3C,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC5D,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,GAAG,QAAkB;IAClE,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAA;IACjD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC1C,IAAI,GAAG,KAAK,EAAE,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1F,MAAM,IAAI,eAAe,CAAC,gCAAgC,MAAM,EAAE,CAAC,CAAA;IACrE,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,UAAkB,EAAE,SAAiB;IAC/D,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,eAAe,CAAC,sBAAsB,SAAS,GAAG,CAAC,CAAA;IAC/D,CAAC;IACD,OAAO,aAAa,CAAC,UAAU,EAAE,SAAS,GAAG,KAAK,CAAC,CAAA;AACrD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,SAAiB,EAAE,KAAa;IAC5E,IAAI,CAAC;QACH,OAAO,aAAa,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,eAAe,CAAC,GAAG,KAAK,+CAA+C,SAAS,EAAE,CAAC,CAAA;IAC/F,CAAC;AACH,CAAC"}
|
package/dist/prompt.d.ts
ADDED
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function truncate(text, max) {
|
|
2
|
+
return text.length > max ? text.slice(0, max) : text;
|
|
3
|
+
}
|
|
4
|
+
export function buildIdlePrompt(project, messages) {
|
|
5
|
+
const n = messages.length;
|
|
6
|
+
const topics = messages.map((m) => `- ${truncate(m.topic || "(no topic)", 120)}`).join("\n");
|
|
7
|
+
return (`You have ${n} OPEN mailbox message(s) for this project.\n\n` +
|
|
8
|
+
`Load the \`workspace-mailbox\` skill, read \`handoffs/${project}.md\`, handle every OPEN message,\n` +
|
|
9
|
+
`flip each one to DONE (use \`mailbox_ack\`), and reply only if a decision is needed.\n\n` +
|
|
10
|
+
`Topics:\n${topics}`);
|
|
11
|
+
}
|
|
12
|
+
export function buildCapNotice(project, count, maxAutoRounds = 2) {
|
|
13
|
+
return `Mailbox: ${count} OPEN message(s) in ${project} — auto-handling stopped after ${maxAutoRounds} round(s); ask me to handle them.`;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAEA,SAAS,QAAQ,CAAC,IAAY,EAAE,GAAW;IACzC,OAAO,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AACtD,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,QAA0B;IACzE,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAA;IACzB,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,YAAY,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC5F,OAAO,CACL,YAAY,CAAC,gDAAgD;QAC7D,yDAAyD,OAAO,qCAAqC;QACrG,0FAA0F;QAC1F,YAAY,MAAM,EAAE,CACrB,CAAA;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,KAAa,EAAE,aAAa,GAAG,CAAC;IAC9E,OAAO,YAAY,KAAK,uBAAuB,OAAO,kCAAkC,aAAa,mCAAmC,CAAA;AAC1I,CAAC"}
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type SessionState = {
|
|
2
|
+
autoRounds: number;
|
|
3
|
+
lastPromptAt: number;
|
|
4
|
+
lastNoticeAt?: number;
|
|
5
|
+
noticeKey?: string;
|
|
6
|
+
spawnsThisTurn?: number;
|
|
7
|
+
};
|
|
8
|
+
export type MailboxState = {
|
|
9
|
+
version: 1;
|
|
10
|
+
prompted: string[];
|
|
11
|
+
ownWrites: string[];
|
|
12
|
+
sessions: Record<string, SessionState>;
|
|
13
|
+
/** Session ids this plugin created (spawn-on-send). */
|
|
14
|
+
spawnedSessions: string[];
|
|
15
|
+
/** Message hashes that already triggered a spawn. */
|
|
16
|
+
spawnedHashes: string[];
|
|
17
|
+
lastSpawnAt?: number;
|
|
18
|
+
};
|
|
19
|
+
export declare function emptyState(): MailboxState;
|
|
20
|
+
export declare class StateStore {
|
|
21
|
+
private file;
|
|
22
|
+
private now;
|
|
23
|
+
constructor(file: string, now?: () => number);
|
|
24
|
+
read(): MailboxState;
|
|
25
|
+
write(state: MailboxState): void;
|
|
26
|
+
update(mutator: (state: MailboxState) => void): MailboxState;
|
|
27
|
+
}
|
package/dist/state.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const MAX_ENTRIES = 500;
|
|
4
|
+
export function emptyState() {
|
|
5
|
+
return { version: 1, prompted: [], ownWrites: [], sessions: {}, spawnedSessions: [], spawnedHashes: [] };
|
|
6
|
+
}
|
|
7
|
+
function finiteInt(value) {
|
|
8
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.floor(value) : 0;
|
|
9
|
+
}
|
|
10
|
+
function asStringArray(value) {
|
|
11
|
+
if (!Array.isArray(value))
|
|
12
|
+
return [];
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
const out = [];
|
|
15
|
+
for (const entry of value) {
|
|
16
|
+
if (typeof entry === "string" && !seen.has(entry)) {
|
|
17
|
+
seen.add(entry);
|
|
18
|
+
out.push(entry);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return out.slice(-MAX_ENTRIES);
|
|
22
|
+
}
|
|
23
|
+
function coerceSession(value) {
|
|
24
|
+
const obj = value && typeof value === "object" ? value : {};
|
|
25
|
+
const session = {
|
|
26
|
+
autoRounds: finiteInt(obj.autoRounds),
|
|
27
|
+
lastPromptAt: finiteInt(obj.lastPromptAt),
|
|
28
|
+
};
|
|
29
|
+
if (typeof obj.lastNoticeAt === "number" && Number.isFinite(obj.lastNoticeAt)) {
|
|
30
|
+
session.lastNoticeAt = obj.lastNoticeAt;
|
|
31
|
+
}
|
|
32
|
+
if (typeof obj.noticeKey === "string") {
|
|
33
|
+
session.noticeKey = obj.noticeKey;
|
|
34
|
+
}
|
|
35
|
+
if (typeof obj.spawnsThisTurn === "number" && Number.isFinite(obj.spawnsThisTurn)) {
|
|
36
|
+
session.spawnsThisTurn = Math.max(0, Math.floor(obj.spawnsThisTurn));
|
|
37
|
+
}
|
|
38
|
+
return session;
|
|
39
|
+
}
|
|
40
|
+
function coerce(value) {
|
|
41
|
+
if (!value || typeof value !== "object")
|
|
42
|
+
return emptyState();
|
|
43
|
+
const obj = value;
|
|
44
|
+
const sessions = {};
|
|
45
|
+
if (obj.sessions && typeof obj.sessions === "object") {
|
|
46
|
+
for (const [key, entry] of Object.entries(obj.sessions)) {
|
|
47
|
+
sessions[key] = coerceSession(entry);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
version: 1,
|
|
52
|
+
prompted: asStringArray(obj.prompted),
|
|
53
|
+
ownWrites: asStringArray(obj.ownWrites),
|
|
54
|
+
sessions,
|
|
55
|
+
spawnedSessions: asStringArray(obj.spawnedSessions),
|
|
56
|
+
spawnedHashes: asStringArray(obj.spawnedHashes),
|
|
57
|
+
...(typeof obj.lastSpawnAt === "number" && Number.isFinite(obj.lastSpawnAt)
|
|
58
|
+
? { lastSpawnAt: obj.lastSpawnAt }
|
|
59
|
+
: {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export class StateStore {
|
|
63
|
+
file;
|
|
64
|
+
now;
|
|
65
|
+
constructor(file, now) {
|
|
66
|
+
this.file = file;
|
|
67
|
+
this.now = now ?? (() => Date.now());
|
|
68
|
+
}
|
|
69
|
+
read() {
|
|
70
|
+
if (!existsSync(this.file))
|
|
71
|
+
return emptyState();
|
|
72
|
+
try {
|
|
73
|
+
const raw = readFileSync(this.file, "utf8");
|
|
74
|
+
return coerce(JSON.parse(raw));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return emptyState();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
write(state) {
|
|
81
|
+
const clean = coerce(state);
|
|
82
|
+
mkdirSync(path.dirname(this.file), { recursive: true });
|
|
83
|
+
const tmp = `${this.file}.tmp`;
|
|
84
|
+
writeFileSync(tmp, JSON.stringify(clean, null, 2), "utf8");
|
|
85
|
+
renameSync(tmp, this.file);
|
|
86
|
+
}
|
|
87
|
+
update(mutator) {
|
|
88
|
+
const state = this.read();
|
|
89
|
+
mutator(state);
|
|
90
|
+
this.write(state);
|
|
91
|
+
return state;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type AckResult, type SendResult } from "./mailbox.js";
|
|
2
|
+
import type { MailboxMessage } from "./parser.js";
|
|
3
|
+
export type MailboxFilter = {
|
|
4
|
+
status?: string;
|
|
5
|
+
topicContains?: string;
|
|
6
|
+
limit?: number;
|
|
7
|
+
};
|
|
8
|
+
export type AppendInput = {
|
|
9
|
+
from: string;
|
|
10
|
+
topic: string;
|
|
11
|
+
body: string;
|
|
12
|
+
status?: "OPEN" | "DONE";
|
|
13
|
+
now?: Date;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Storage contract. The file backend is the MVP; a SQLite backend only has to
|
|
17
|
+
* implement these five operations to replace it without touching the tools,
|
|
18
|
+
* the wake path, or spawn-on-send.
|
|
19
|
+
*/
|
|
20
|
+
export interface MailboxStore {
|
|
21
|
+
readonly kind: string;
|
|
22
|
+
readonly dir: string;
|
|
23
|
+
/** Create the project's mailbox file if it is missing; returns its path. */
|
|
24
|
+
ensure(project: string): string;
|
|
25
|
+
listOpen(project: string): MailboxMessage[];
|
|
26
|
+
read(project: string): MailboxMessage[];
|
|
27
|
+
append(project: string, input: AppendInput): SendResult;
|
|
28
|
+
ack(project: string, target: {
|
|
29
|
+
hash?: string;
|
|
30
|
+
header?: string;
|
|
31
|
+
}): AckResult;
|
|
32
|
+
query(project: string, filter?: MailboxFilter): MailboxMessage[];
|
|
33
|
+
}
|
|
34
|
+
export declare function createFileStore(dir: string): MailboxStore;
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ackMessage, readMailbox, sendMessage } from "./mailbox.js";
|
|
4
|
+
import { mailboxFile } from "./paths.js";
|
|
5
|
+
export function createFileStore(dir) {
|
|
6
|
+
const root = path.resolve(dir);
|
|
7
|
+
const fileFor = (project) => mailboxFile(root, project);
|
|
8
|
+
return {
|
|
9
|
+
kind: "file",
|
|
10
|
+
dir: root,
|
|
11
|
+
ensure(project) {
|
|
12
|
+
const target = fileFor(project);
|
|
13
|
+
if (!existsSync(target)) {
|
|
14
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
15
|
+
writeFileSync(target, `# ${project} mailbox\n\nMessages addressed to \`${project}\`. Append-only.\n`, "utf8");
|
|
16
|
+
}
|
|
17
|
+
return target;
|
|
18
|
+
},
|
|
19
|
+
listOpen(project) {
|
|
20
|
+
return readMailbox(fileFor(project)).open;
|
|
21
|
+
},
|
|
22
|
+
read(project) {
|
|
23
|
+
return readMailbox(fileFor(project)).messages;
|
|
24
|
+
},
|
|
25
|
+
append(project, input) {
|
|
26
|
+
return sendMessage({
|
|
27
|
+
mailboxDir: root,
|
|
28
|
+
to: project,
|
|
29
|
+
from: input.from,
|
|
30
|
+
topic: input.topic,
|
|
31
|
+
body: input.body,
|
|
32
|
+
status: input.status,
|
|
33
|
+
now: input.now,
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
ack(project, target) {
|
|
37
|
+
return ackMessage({ file: fileFor(project), hash: target.hash, header: target.header });
|
|
38
|
+
},
|
|
39
|
+
query(project, filter = {}) {
|
|
40
|
+
let list = readMailbox(fileFor(project)).messages;
|
|
41
|
+
if (filter.status) {
|
|
42
|
+
const wanted = filter.status.toUpperCase();
|
|
43
|
+
list = list.filter((message) => message.status === wanted);
|
|
44
|
+
}
|
|
45
|
+
if (filter.topicContains) {
|
|
46
|
+
const needle = filter.topicContains.toLowerCase();
|
|
47
|
+
list = list.filter((message) => message.topic.toLowerCase().includes(needle));
|
|
48
|
+
}
|
|
49
|
+
if (filter.limit && filter.limit > 0)
|
|
50
|
+
list = list.slice(-filter.limit);
|
|
51
|
+
return list;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-workspace-mailbox",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A per-project mailbox for OpenCode sessions — detect OPEN handoff messages on idle, prompt the agent to handle them, with loop guards",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/mr-wolf-rsh/opencode-workspace-mailbox.git"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"src",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"prepare": "npm run build",
|
|
25
|
+
"prepack": "npm run build",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"test": "vitest run"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"opencode",
|
|
31
|
+
"plugin",
|
|
32
|
+
"mailbox",
|
|
33
|
+
"handoff",
|
|
34
|
+
"agent",
|
|
35
|
+
"automation"
|
|
36
|
+
],
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@opencode-ai/plugin": ">=1.0.0",
|
|
39
|
+
"@opencode-ai/sdk": ">=1.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@opencode-ai/plugin": "^1.18.18",
|
|
43
|
+
"@opencode-ai/sdk": "^1.18.18",
|
|
44
|
+
"@types/node": "^22.10.0",
|
|
45
|
+
"typescript": "^5.6.3",
|
|
46
|
+
"vitest": "^2.1.8"
|
|
47
|
+
}
|
|
48
|
+
}
|