@synaplink/orqlaude 0.3.0 → 0.3.2
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/.mcp.json.template +1 -1
- package/LICENSE +21 -0
- package/README.md +13 -4
- package/dist/__tests__/state_dir.test.d.ts +1 -0
- package/dist/__tests__/state_dir.test.js +122 -0
- package/dist/__tests__/state_dir.test.js.map +1 -0
- package/dist/__tests__/v031.test.d.ts +1 -0
- package/dist/__tests__/v031.test.js +126 -0
- package/dist/__tests__/v031.test.js.map +1 -0
- package/dist/cli.js +15 -2
- package/dist/cli.js.map +1 -1
- package/dist/lib/audit.js +42 -11
- package/dist/lib/audit.js.map +1 -1
- package/dist/lib/state.d.ts +30 -17
- package/dist/lib/state.js +110 -27
- package/dist/lib/state.js.map +1 -1
- package/dist/lib/state_dir.d.ts +12 -0
- package/dist/lib/state_dir.js +116 -0
- package/dist/lib/state_dir.js.map +1 -0
- package/dist/server.js +6 -5
- package/dist/server.js.map +1 -1
- package/dist/telegram/config.d.ts +1 -0
- package/dist/telegram/config.js +21 -4
- package/dist/telegram/config.js.map +1 -1
- package/dist/telegram/notifier.d.ts +14 -0
- package/dist/telegram/notifier.js +38 -20
- package/dist/telegram/notifier.js.map +1 -1
- package/dist/tools/ping.js +6 -2
- package/dist/tools/ping.js.map +1 -1
- package/package.json +5 -3
package/dist/lib/state.js
CHANGED
|
@@ -2,72 +2,162 @@ import { promises as fs } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
const EMPTY_STATE = { schemaVersion: 2, plans: {} };
|
|
5
|
+
const LOCK_TIMEOUT_MS = 5_000;
|
|
6
|
+
const LOCK_RETRY_BASE_MS = 30;
|
|
5
7
|
export class StateStore {
|
|
6
8
|
filePath;
|
|
9
|
+
lockPath;
|
|
7
10
|
cache = null;
|
|
8
11
|
writeLock = Promise.resolve();
|
|
9
12
|
constructor(stateDir) {
|
|
10
13
|
this.filePath = path.join(stateDir, "orqlaude-state.json");
|
|
14
|
+
this.lockPath = path.join(stateDir, "lock");
|
|
11
15
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Always reload from disk under the lock to defeat cross-process staleness.
|
|
18
|
+
*/
|
|
19
|
+
async loadFresh() {
|
|
15
20
|
try {
|
|
16
21
|
const raw = await fs.readFile(this.filePath, "utf8");
|
|
17
22
|
const parsed = JSON.parse(raw);
|
|
18
|
-
|
|
23
|
+
const state = migrate(parsed);
|
|
24
|
+
this.cache = state;
|
|
25
|
+
return state;
|
|
19
26
|
}
|
|
20
27
|
catch (err) {
|
|
21
28
|
if (err.code === "ENOENT") {
|
|
22
29
|
this.cache = structuredClone(EMPTY_STATE);
|
|
30
|
+
return this.cache;
|
|
23
31
|
}
|
|
24
|
-
|
|
25
|
-
throw err;
|
|
26
|
-
}
|
|
32
|
+
throw err;
|
|
27
33
|
}
|
|
28
|
-
return this.cache;
|
|
29
34
|
}
|
|
30
|
-
|
|
35
|
+
/** Read path: still funneled through the writeLock so we never see torn
|
|
36
|
+
* state from a partially-applied mutator in this process. */
|
|
37
|
+
async read(reader) {
|
|
31
38
|
let release = () => { };
|
|
32
39
|
const next = new Promise((res) => (release = res));
|
|
33
40
|
const prev = this.writeLock;
|
|
34
41
|
this.writeLock = prev.then(() => next);
|
|
35
42
|
await prev;
|
|
36
43
|
try {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
// Cheap path: if we have a cache, use it. Cache is always written
|
|
45
|
+
// through after a successful persist, so it reflects the last
|
|
46
|
+
// committed state.
|
|
47
|
+
const state = this.cache ?? (await this.loadFresh());
|
|
48
|
+
return reader(state);
|
|
41
49
|
}
|
|
42
50
|
finally {
|
|
43
51
|
release();
|
|
44
52
|
}
|
|
45
53
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Mutate state under both an in-process lock and a cross-process file lock.
|
|
56
|
+
* Re-reads from disk before applying the mutator to pick up writes from
|
|
57
|
+
* other processes (Telegram bot, CLI, fresh MCP invocation). On throw,
|
|
58
|
+
* restores the in-memory cache from the pre-mutation snapshot.
|
|
59
|
+
*/
|
|
60
|
+
async update(mutator) {
|
|
61
|
+
let releaseInProcess = () => { };
|
|
62
|
+
const next = new Promise((res) => (releaseInProcess = res));
|
|
63
|
+
const prev = this.writeLock;
|
|
64
|
+
this.writeLock = prev.then(() => next);
|
|
65
|
+
await prev;
|
|
66
|
+
try {
|
|
67
|
+
await this.acquireFileLock();
|
|
68
|
+
// Always reload from disk under the lock — another process may have
|
|
69
|
+
// written since we last cached.
|
|
70
|
+
const fresh = await this.loadFresh();
|
|
71
|
+
const snapshot = structuredClone(fresh);
|
|
72
|
+
try {
|
|
73
|
+
const result = await mutator(fresh);
|
|
74
|
+
await this.persist(fresh);
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
// Roll back the in-memory cache to the pre-mutation snapshot so
|
|
79
|
+
// subsequent readers see the correct state.
|
|
80
|
+
this.cache = snapshot;
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
await this.releaseFileLock();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
releaseInProcess();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async acquireFileLock() {
|
|
92
|
+
await fs.mkdir(path.dirname(this.lockPath), { recursive: true });
|
|
93
|
+
const start = Date.now();
|
|
94
|
+
while (Date.now() - start < LOCK_TIMEOUT_MS) {
|
|
95
|
+
try {
|
|
96
|
+
const fh = await fs.open(this.lockPath, "wx", 0o600);
|
|
97
|
+
await fh.write(`${process.pid}\n${Date.now()}\n`);
|
|
98
|
+
await fh.close();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
if (err.code !== "EEXIST")
|
|
103
|
+
throw err;
|
|
104
|
+
// Lock exists. Check if it's stale (PID no longer alive).
|
|
105
|
+
try {
|
|
106
|
+
const held = (await fs.readFile(this.lockPath, "utf8")).split("\n")[0]?.trim();
|
|
107
|
+
const heldPid = parseInt(held ?? "", 10);
|
|
108
|
+
if (Number.isFinite(heldPid) && !isProcessAlive(heldPid)) {
|
|
109
|
+
await fs.unlink(this.lockPath).catch(() => { });
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
/* race: someone deleted it before we read. retry. */
|
|
115
|
+
}
|
|
116
|
+
await sleep(LOCK_RETRY_BASE_MS + Math.random() * LOCK_RETRY_BASE_MS);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
throw new Error(`orqlaude: could not acquire state lock (${this.lockPath}) within ${LOCK_TIMEOUT_MS}ms`);
|
|
120
|
+
}
|
|
121
|
+
async releaseFileLock() {
|
|
122
|
+
await fs.unlink(this.lockPath).catch(() => {
|
|
123
|
+
/* already gone; not fatal */
|
|
124
|
+
});
|
|
49
125
|
}
|
|
50
126
|
async persist(state) {
|
|
51
127
|
await fs.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
52
|
-
const tmp = `${this.filePath}.${process.pid}.tmp`;
|
|
53
|
-
await fs.writeFile(tmp, JSON.stringify(state, null, 2));
|
|
128
|
+
const tmp = `${this.filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
129
|
+
await fs.writeFile(tmp, JSON.stringify(state, null, 2), { mode: 0o600 });
|
|
54
130
|
await fs.rename(tmp, this.filePath);
|
|
55
131
|
this.cache = state;
|
|
56
132
|
}
|
|
57
133
|
}
|
|
134
|
+
function isProcessAlive(pid) {
|
|
135
|
+
try {
|
|
136
|
+
process.kill(pid, 0);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function sleep(ms) {
|
|
144
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
145
|
+
}
|
|
58
146
|
/** Forward-compatible migration from earlier schemas. */
|
|
59
147
|
function migrate(input) {
|
|
60
148
|
const v = input.schemaVersion ?? 1;
|
|
61
149
|
if (v === 2 && input.plans)
|
|
62
150
|
return input;
|
|
63
|
-
// v1 → v2: synthesize token fields from USD if missing.
|
|
64
151
|
const out = { schemaVersion: 2, plans: {} };
|
|
65
152
|
for (const [id, plan] of Object.entries(input.plans ?? {})) {
|
|
66
153
|
const p = plan;
|
|
67
154
|
out.plans[id] = {
|
|
68
155
|
...p,
|
|
69
|
-
budgetCapTokens: p.budgetCapTokens ?? Math.round((p.budgetCapUsd ?? 5) * 25_000),
|
|
156
|
+
budgetCapTokens: p.budgetCapTokens ?? Math.round((p.budgetCapUsd ?? 5) * 25_000),
|
|
70
157
|
perAgentCapTokens: p.perAgentCapTokens ?? Math.round((p.perAgentCapUsd ?? 1) * 25_000),
|
|
158
|
+
tasks: p.tasks ?? [],
|
|
159
|
+
notes: p.notes ?? [],
|
|
160
|
+
messages: p.messages ?? [],
|
|
71
161
|
claims: p.claims ?? [],
|
|
72
162
|
};
|
|
73
163
|
}
|
|
@@ -108,7 +198,6 @@ export function findTask(plan, taskId) {
|
|
|
108
198
|
export function findTaskBySession(plan, sessionId) {
|
|
109
199
|
return plan.tasks.find((t) => t.spawnedSessionId === sessionId);
|
|
110
200
|
}
|
|
111
|
-
/** Locate the plan+task a given child session belongs to. */
|
|
112
201
|
export function planForSession(state, sessionId) {
|
|
113
202
|
for (const plan of Object.values(state.plans)) {
|
|
114
203
|
const task = findTaskBySession(plan, sessionId);
|
|
@@ -117,11 +206,6 @@ export function planForSession(state, sessionId) {
|
|
|
117
206
|
}
|
|
118
207
|
return undefined;
|
|
119
208
|
}
|
|
120
|
-
/**
|
|
121
|
-
* Find a dispatched-but-unclaimed task by task_id. Used for self-registration:
|
|
122
|
-
* when a freshly-spawned child agent calls `checkin` with its task_id (which we
|
|
123
|
-
* embed in the spawn prompt), we can adopt it.
|
|
124
|
-
*/
|
|
125
209
|
export function unclaimedTaskById(state, taskId) {
|
|
126
210
|
for (const plan of Object.values(state.plans)) {
|
|
127
211
|
const task = plan.tasks.find((t) => t.id === taskId && !t.spawnedSessionId);
|
|
@@ -130,7 +214,6 @@ export function unclaimedTaskById(state, taskId) {
|
|
|
130
214
|
}
|
|
131
215
|
return undefined;
|
|
132
216
|
}
|
|
133
|
-
/** Normalize a path for claim comparison (handle . , .. , trailing slash, case). */
|
|
134
217
|
export function normalizeClaimPath(p, cwd) {
|
|
135
218
|
const abs = path.isAbsolute(p) ? p : path.resolve(cwd, p);
|
|
136
219
|
return path.normalize(abs);
|
package/dist/lib/state.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/lib/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/lib/state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface StateDirResolution {
|
|
2
|
+
path: string;
|
|
3
|
+
source: "env" | "worktree" | "project-root" | "home-fallback";
|
|
4
|
+
cwd: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function resolveStateDir(): StateDirResolution;
|
|
7
|
+
/**
|
|
8
|
+
* Convenience used by server.ts and cli.ts: resolve, ensure dir exists, log
|
|
9
|
+
* a one-line note to stderr if we fell back to the home dir. Safe to call
|
|
10
|
+
* multiple times.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveAndEnsureStateDir(): Promise<StateDirResolution>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { promises as fs, statSync, readFileSync, accessSync, constants } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import crypto from "node:crypto";
|
|
5
|
+
/**
|
|
6
|
+
* Resolve where orqlaude's state directory lives.
|
|
7
|
+
*
|
|
8
|
+
* MCP servers don't get to pick their cwd — the host (Claude Desktop, an
|
|
9
|
+
* IDE plugin, a shell) chooses it. Some hosts launch with cwd=/ or another
|
|
10
|
+
* unwritable dir for sandbox reasons. We can't crash there.
|
|
11
|
+
*
|
|
12
|
+
* Resolution order (first match wins):
|
|
13
|
+
*
|
|
14
|
+
* 1. `ORQLAUDE_STATE_DIR` env var — explicit override always wins.
|
|
15
|
+
* 2. Git worktree resolution: if `<cwd>/.git` is a regular FILE pointing at
|
|
16
|
+
* `<main>/.git/worktrees/<n>`, resolve to `<main>/.orqlaude`. This is
|
|
17
|
+
* what lets spawn_task'd children share state with the parent fleet.
|
|
18
|
+
* 3. If cwd looks like a project root (writable + has `.git/`,
|
|
19
|
+
* `package.json`, `pyproject.toml`, or `Cargo.toml`) → `<cwd>/.orqlaude`.
|
|
20
|
+
* 4. Otherwise → `~/.orqlaude/projects/<basename>-<hash>/` where hash is
|
|
21
|
+
* derived from the cwd path. Stable across restarts; per-cwd isolated.
|
|
22
|
+
*
|
|
23
|
+
* Step 4 is the safety net for cwd=/ and similar. We also write a one-line
|
|
24
|
+
* note to stderr so the developer sees where state landed.
|
|
25
|
+
*/
|
|
26
|
+
const SYSTEM_DIRS = new Set([
|
|
27
|
+
"/",
|
|
28
|
+
"/private",
|
|
29
|
+
"/tmp",
|
|
30
|
+
"/var",
|
|
31
|
+
"/usr",
|
|
32
|
+
"/etc",
|
|
33
|
+
"/Applications",
|
|
34
|
+
"/Library",
|
|
35
|
+
"/System",
|
|
36
|
+
"/Volumes",
|
|
37
|
+
"/opt",
|
|
38
|
+
"/bin",
|
|
39
|
+
"/sbin",
|
|
40
|
+
]);
|
|
41
|
+
const PROJECT_MARKERS = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod"];
|
|
42
|
+
export function resolveStateDir() {
|
|
43
|
+
const cwd = process.cwd();
|
|
44
|
+
if (process.env.ORQLAUDE_STATE_DIR) {
|
|
45
|
+
return { path: process.env.ORQLAUDE_STATE_DIR, source: "env", cwd };
|
|
46
|
+
}
|
|
47
|
+
const worktree = tryWorktreeResolve(cwd);
|
|
48
|
+
if (worktree)
|
|
49
|
+
return { path: worktree, source: "worktree", cwd };
|
|
50
|
+
if (looksLikeProjectRoot(cwd) && isWritable(cwd)) {
|
|
51
|
+
return { path: path.join(cwd, ".orqlaude"), source: "project-root", cwd };
|
|
52
|
+
}
|
|
53
|
+
return { path: homeFallback(cwd), source: "home-fallback", cwd };
|
|
54
|
+
}
|
|
55
|
+
function tryWorktreeResolve(cwd) {
|
|
56
|
+
try {
|
|
57
|
+
const dotGit = path.join(cwd, ".git");
|
|
58
|
+
const stat = statSync(dotGit);
|
|
59
|
+
if (!stat.isFile())
|
|
60
|
+
return null;
|
|
61
|
+
const content = readFileSync(dotGit, "utf8");
|
|
62
|
+
// Format: "gitdir: /path/to/main/.git/worktrees/<name>"
|
|
63
|
+
const m = content.match(/^gitdir:\s*(.+?)\/worktrees\/[^\/\s]+\s*$/m);
|
|
64
|
+
if (!m)
|
|
65
|
+
return null;
|
|
66
|
+
const mainGitDir = m[1]; // /path/to/main/.git
|
|
67
|
+
const mainCheckout = path.dirname(mainGitDir);
|
|
68
|
+
return path.join(mainCheckout, ".orqlaude");
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function looksLikeProjectRoot(dir) {
|
|
75
|
+
if (SYSTEM_DIRS.has(path.normalize(dir)))
|
|
76
|
+
return false;
|
|
77
|
+
for (const marker of PROJECT_MARKERS) {
|
|
78
|
+
try {
|
|
79
|
+
statSync(path.join(dir, marker));
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
/* missing; try next */
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
function isWritable(dir) {
|
|
89
|
+
try {
|
|
90
|
+
accessSync(dir, constants.W_OK);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function homeFallback(cwd) {
|
|
98
|
+
const hash = crypto.createHash("sha256").update(cwd).digest("hex").slice(0, 12);
|
|
99
|
+
const safeBasename = (path.basename(cwd) || "root").replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
100
|
+
return path.join(os.homedir(), ".orqlaude", "projects", `${safeBasename}-${hash}`);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Convenience used by server.ts and cli.ts: resolve, ensure dir exists, log
|
|
104
|
+
* a one-line note to stderr if we fell back to the home dir. Safe to call
|
|
105
|
+
* multiple times.
|
|
106
|
+
*/
|
|
107
|
+
export async function resolveAndEnsureStateDir() {
|
|
108
|
+
const r = resolveStateDir();
|
|
109
|
+
await fs.mkdir(r.path, { recursive: true, mode: 0o700 });
|
|
110
|
+
if (r.source === "home-fallback") {
|
|
111
|
+
process.stderr.write(`[orqlaude] cwd=${r.cwd} is not a project root; state stored at ${r.path}. ` +
|
|
112
|
+
`Set ORQLAUDE_STATE_DIR to override.\n`);
|
|
113
|
+
}
|
|
114
|
+
return r;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=state_dir.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state_dir.js","sourceRoot":"","sources":["../../src/lib/state_dir.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import path from "node:path";
|
|
3
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
4
|
import { StateStore } from "./lib/state.js";
|
|
6
5
|
import { AuditLog } from "./lib/audit.js";
|
|
6
|
+
import { resolveAndEnsureStateDir } from "./lib/state_dir.js";
|
|
7
7
|
import { registerPing } from "./tools/ping.js";
|
|
8
8
|
import { registerPlanning } from "./tools/planning.js";
|
|
9
9
|
import { registerDispatch } from "./tools/dispatch.js";
|
|
@@ -13,15 +13,16 @@ import { registerReview } from "./tools/review.js";
|
|
|
13
13
|
/**
|
|
14
14
|
* orqlaude — multi-agent orchestrator for Claude Code.
|
|
15
15
|
*
|
|
16
|
-
* State
|
|
17
|
-
*
|
|
16
|
+
* State dir is resolved robustly: env var > git-worktree > project-root cwd >
|
|
17
|
+
* `~/.orqlaude/projects/<hash>` as a last-resort home fallback (covers
|
|
18
|
+
* MCP hosts that launch with cwd=/). See lib/state_dir.ts for full logic.
|
|
18
19
|
*/
|
|
19
|
-
const stateDir =
|
|
20
|
+
const { path: stateDir } = await resolveAndEnsureStateDir();
|
|
20
21
|
const store = new StateStore(stateDir);
|
|
21
22
|
const audit = new AuditLog(stateDir);
|
|
22
23
|
const server = new McpServer({
|
|
23
24
|
name: "orqlaude",
|
|
24
|
-
version: "0.3.
|
|
25
|
+
version: "0.3.2",
|
|
25
26
|
});
|
|
26
27
|
registerPing(server);
|
|
27
28
|
registerPlanning(server, store, audit);
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD;;;;;;GAMG;AACH,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,MAAM,wBAAwB,EAAE,CAAC;AAC5D,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC;AACvC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAErC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,YAAY,CAAC,MAAM,CAAC,CAAC;AACrB,gBAAgB,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AACvC,gBAAgB,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AACvC,cAAc,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AACrC,iBAAiB,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AACxC,cAAc,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAErC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
|
|
@@ -26,5 +26,6 @@ export interface TelegramConfig {
|
|
|
26
26
|
declare const CONFIG_PATH: string;
|
|
27
27
|
export declare function loadConfig(): Promise<TelegramConfig>;
|
|
28
28
|
export declare function saveConfig(cfg: TelegramConfig): Promise<void>;
|
|
29
|
+
export declare function loadConfigSafe(): Promise<TelegramConfig>;
|
|
29
30
|
export declare function isAuthorized(cfg: TelegramConfig, userId: number): boolean;
|
|
30
31
|
export { CONFIG_PATH };
|
package/dist/telegram/config.js
CHANGED
|
@@ -22,11 +22,28 @@ export async function loadConfig() {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
export async function saveConfig(cfg) {
|
|
25
|
-
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
26
|
+
// v0.3.1: create the tmp file with mode 0o600 AND O_EXCL ("wx" flag) from
|
|
27
|
+
// the start. This closes the race window where the token sat at default
|
|
28
|
+
// umask (0644) between writeFile and the post-rename chmod, and defeats a
|
|
29
|
+
// symlink-swap attack on the predictable tmp path.
|
|
30
|
+
// Use a high-entropy suffix instead of just pid to further reduce
|
|
31
|
+
// predictability.
|
|
32
|
+
const suffix = `${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 10)}`;
|
|
33
|
+
const tmp = `${CONFIG_PATH}.${suffix}.tmp`;
|
|
34
|
+
await fs.writeFile(tmp, JSON.stringify(cfg, null, 2), { mode: 0o600, flag: "wx" });
|
|
28
35
|
await fs.rename(tmp, CONFIG_PATH);
|
|
29
|
-
|
|
36
|
+
}
|
|
37
|
+
export async function loadConfigSafe() {
|
|
38
|
+
// Wrapper that swallows JSON parse errors with a warning instead of
|
|
39
|
+
// crashing the bot. Useful when a partially-written config exists.
|
|
40
|
+
try {
|
|
41
|
+
return await loadConfig();
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
process.stderr.write(`[orqlaude tg] config parse failed, falling back to empty: ${err.message}\n`);
|
|
45
|
+
return { ...EMPTY };
|
|
46
|
+
}
|
|
30
47
|
}
|
|
31
48
|
export function isAuthorized(cfg, userId) {
|
|
32
49
|
if (userId === cfg.ownerId)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/telegram/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AA8BzB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;AACxD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;AAE3D,MAAM,KAAK,GAAmB;IAC5B,QAAQ,EAAE,EAAE;IACZ,OAAO,EAAE,IAAI;IACb,SAAS,EAAE,EAAE;IACb,eAAe,EAAE,EAAE;CACpB,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;QAC1D,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,MAAM,EAAE,CAAC;IACjC,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;QAC/C,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAmB;IAClD,MAAM,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/telegram/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AA8BzB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;AACxD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;AAE3D,MAAM,KAAK,GAAmB;IAC5B,QAAQ,EAAE,EAAE;IACZ,OAAO,EAAE,IAAI;IACb,SAAS,EAAE,EAAE;IACb,eAAe,EAAE,EAAE;CACpB,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;QAC1D,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,MAAM,EAAE,CAAC;IACjC,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;QAC/C,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAmB;IAClD,MAAM,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7D,0EAA0E;IAC1E,wEAAwE;IACxE,0EAA0E;IAC1E,mDAAmD;IACnD,kEAAkE;IAClE,kBAAkB;IAClB,MAAM,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;IACzF,MAAM,GAAG,GAAG,GAAG,WAAW,IAAI,MAAM,MAAM,CAAC;IAC3C,MAAM,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACnF,MAAM,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,oEAAoE;IACpE,mEAAmE;IACnE,IAAI,CAAC;QACH,OAAO,MAAM,UAAU,EAAE,CAAC;IAC5B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,6DAA8D,GAAa,CAAC,OAAO,IAAI,CAAC,CAAC;QAC9G,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;IACtB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAmB,EAAE,MAAc;IAC9D,IAAI,MAAM,KAAK,GAAG,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IACxC,OAAO,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;AACxD,CAAC;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -12,3 +12,17 @@ export declare class Notifier {
|
|
|
12
12
|
/** One pass: detect deltas, push notifications, advance cursor. */
|
|
13
13
|
tick(): Promise<void>;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Escape characters that Telegram MarkdownV1 treats as syntax.
|
|
17
|
+
*
|
|
18
|
+
* Per https://core.telegram.org/bots/api#markdown-style, V1's reserved set is
|
|
19
|
+
* `_ * ` ` [`. Without escaping these in user-supplied strings, an innocuous
|
|
20
|
+
* branch name like `feature/foo_bar` or a note containing a single `*` makes
|
|
21
|
+
* sendMessage 400 → notifier swallows the error → cursor already advanced →
|
|
22
|
+
* notification permanently lost.
|
|
23
|
+
*
|
|
24
|
+
* URL text (e.g. PR links) is appended raw and not run through this escaper,
|
|
25
|
+
* because backslashing chars in URLs breaks them. Keep URL fragments out of
|
|
26
|
+
* this function's input.
|
|
27
|
+
*/
|
|
28
|
+
export declare function escapeMd(s: string): string;
|
|
@@ -2,6 +2,7 @@ import { promises as fs } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { StateStore } from "../lib/state.js";
|
|
4
4
|
const EMPTY_CURSOR = {
|
|
5
|
+
initialized: false,
|
|
5
6
|
lastNoteId: null,
|
|
6
7
|
taskStatus: {},
|
|
7
8
|
planStatus: {},
|
|
@@ -38,7 +39,7 @@ export class Notifier {
|
|
|
38
39
|
if (!this.cursor)
|
|
39
40
|
return;
|
|
40
41
|
await fs.mkdir(path.dirname(this.cursorPath), { recursive: true });
|
|
41
|
-
const tmp = `${this.cursorPath}.${process.pid}.tmp`;
|
|
42
|
+
const tmp = `${this.cursorPath}.${process.pid}.${Date.now()}.tmp`;
|
|
42
43
|
await fs.writeFile(tmp, JSON.stringify(this.cursor, null, 2));
|
|
43
44
|
await fs.rename(tmp, this.cursorPath);
|
|
44
45
|
}
|
|
@@ -49,13 +50,25 @@ export class Notifier {
|
|
|
49
50
|
const cursor = await this.loadCursor();
|
|
50
51
|
const store = new StateStore(path.join(this.projectDir, ".orqlaude"));
|
|
51
52
|
const plans = await store.read((s) => Object.values(s.plans));
|
|
53
|
+
// First-tick seed: silently snapshot current state, no messages.
|
|
54
|
+
if (!cursor.initialized) {
|
|
55
|
+
for (const plan of plans) {
|
|
56
|
+
cursor.planStatus[plan.id] = plan.status;
|
|
57
|
+
for (const task of plan.tasks)
|
|
58
|
+
cursor.taskStatus[task.id] = task.status;
|
|
59
|
+
if (plan.notes.length > 0)
|
|
60
|
+
cursor.lastNoteId = plan.notes[plan.notes.length - 1].id;
|
|
61
|
+
}
|
|
62
|
+
cursor.initialized = true;
|
|
63
|
+
await this.saveCursor();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
52
66
|
const messages = [];
|
|
53
67
|
for (const plan of plans) {
|
|
54
68
|
const prevStatus = cursor.planStatus[plan.id];
|
|
55
|
-
// Plan status transitions we care about
|
|
56
69
|
if (prevStatus !== plan.status) {
|
|
57
70
|
if (!prevStatus && plan.status === "draft") {
|
|
58
|
-
messages.push(`📋 *New plan* \`${plan.id.slice(0, 8)}\` — ${plan.tasks.length} task(s)\n${truncate(plan.rootTask, 100)}`);
|
|
71
|
+
messages.push(`📋 *New plan* \`${plan.id.slice(0, 8)}\` — ${plan.tasks.length} task(s)\n${escapeMd(truncate(plan.rootTask, 100))}`);
|
|
59
72
|
}
|
|
60
73
|
else if (plan.status === "approved" && prevStatus !== "approved") {
|
|
61
74
|
messages.push(`✅ *Approved* \`${plan.id.slice(0, 8)}\` — spawning ${plan.tasks.length} agents`);
|
|
@@ -73,24 +86,23 @@ export class Notifier {
|
|
|
73
86
|
}
|
|
74
87
|
cursor.planStatus[plan.id] = plan.status;
|
|
75
88
|
}
|
|
76
|
-
// Per-task status transitions
|
|
77
89
|
for (const task of plan.tasks) {
|
|
78
90
|
const prev = cursor.taskStatus[task.id];
|
|
79
91
|
if (prev !== task.status) {
|
|
80
92
|
if (task.status === "done") {
|
|
81
93
|
const prSuffix = task.prUrl ? `\n${task.prUrl}` : "";
|
|
82
|
-
messages.push(`✓ *${truncate(task.title, 60)}* — done${prSuffix}`);
|
|
94
|
+
messages.push(`✓ *${escapeMd(truncate(task.title, 60))}* — done${prSuffix}`);
|
|
83
95
|
}
|
|
84
96
|
else if (task.status === "failed") {
|
|
85
|
-
messages.push(`❌ *${truncate(task.title, 60)}* — failed${task.exitReason ? `\n${task.exitReason}` : ""}`);
|
|
97
|
+
messages.push(`❌ *${escapeMd(truncate(task.title, 60))}* — failed${task.exitReason ? `\n${escapeMd(task.exitReason)}` : ""}`);
|
|
86
98
|
}
|
|
87
99
|
else if (task.status === "cancelled") {
|
|
88
|
-
messages.push(`🛑 *${truncate(task.title, 60)}* — cancelled`);
|
|
100
|
+
messages.push(`🛑 *${escapeMd(truncate(task.title, 60))}* — cancelled`);
|
|
89
101
|
}
|
|
90
102
|
cursor.taskStatus[task.id] = task.status;
|
|
91
103
|
}
|
|
92
104
|
}
|
|
93
|
-
// New notes
|
|
105
|
+
// New notes after the last-seen id.
|
|
94
106
|
let foundLast = cursor.lastNoteId === null;
|
|
95
107
|
for (const note of plan.notes) {
|
|
96
108
|
if (!foundLast) {
|
|
@@ -100,22 +112,12 @@ export class Notifier {
|
|
|
100
112
|
}
|
|
101
113
|
const taskTitle = plan.tasks.find((t) => t.id === note.taskId)?.title ?? note.taskId.slice(0, 8);
|
|
102
114
|
const blocking = note.blocking ? " 🟡 blocking" : "";
|
|
103
|
-
messages.push(`📝 *${truncate(taskTitle, 50)}*${blocking}\n${truncate(note.text, 300)}${note.prUrl ? `\n${note.prUrl}` : ""}`);
|
|
115
|
+
messages.push(`📝 *${escapeMd(truncate(taskTitle, 50))}*${blocking}\n${escapeMd(truncate(note.text, 300))}${note.prUrl ? `\n${note.prUrl}` : ""}`);
|
|
104
116
|
cursor.lastNoteId = note.id;
|
|
105
117
|
}
|
|
106
|
-
// If we never had a cursor, seed it to the last note so we don't blast history.
|
|
107
|
-
if (cursor.lastNoteId === null && plan.notes.length > 0) {
|
|
108
|
-
cursor.lastNoteId = plan.notes[plan.notes.length - 1].id;
|
|
109
|
-
}
|
|
110
118
|
}
|
|
111
|
-
|
|
112
|
-
// still save cursor in case statuses changed but no message produced
|
|
113
|
-
await this.saveCursor();
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
// Save cursor BEFORE sending, to avoid resends on partial failures.
|
|
119
|
+
// Save cursor BEFORE sending so a failed send won't re-spam on retry.
|
|
117
120
|
await this.saveCursor();
|
|
118
|
-
// Push.
|
|
119
121
|
for (const entry of this.cfg.whitelist) {
|
|
120
122
|
for (const msg of messages) {
|
|
121
123
|
try {
|
|
@@ -128,6 +130,22 @@ export class Notifier {
|
|
|
128
130
|
}
|
|
129
131
|
}
|
|
130
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Escape characters that Telegram MarkdownV1 treats as syntax.
|
|
135
|
+
*
|
|
136
|
+
* Per https://core.telegram.org/bots/api#markdown-style, V1's reserved set is
|
|
137
|
+
* `_ * ` ` [`. Without escaping these in user-supplied strings, an innocuous
|
|
138
|
+
* branch name like `feature/foo_bar` or a note containing a single `*` makes
|
|
139
|
+
* sendMessage 400 → notifier swallows the error → cursor already advanced →
|
|
140
|
+
* notification permanently lost.
|
|
141
|
+
*
|
|
142
|
+
* URL text (e.g. PR links) is appended raw and not run through this escaper,
|
|
143
|
+
* because backslashing chars in URLs breaks them. Keep URL fragments out of
|
|
144
|
+
* this function's input.
|
|
145
|
+
*/
|
|
146
|
+
export function escapeMd(s) {
|
|
147
|
+
return s.replace(/([_*`\[])/g, "\\$1");
|
|
148
|
+
}
|
|
131
149
|
function truncate(s, n) {
|
|
132
150
|
if (s.length <= n)
|
|
133
151
|
return s;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifier.js","sourceRoot":"","sources":["../../src/telegram/notifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAa,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"notifier.js","sourceRoot":"","sources":["../../src/telegram/notifier.ts"],"names":[],"mappings":"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"}
|