@synaplink/orqlaude 0.3.2 → 0.4.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.
Files changed (41) hide show
  1. package/.mcp.json.template +4 -1
  2. package/README.md +45 -8
  3. package/dist/__tests__/state.test.js +1 -1
  4. package/dist/__tests__/state_dir.test.js +13 -0
  5. package/dist/__tests__/state_dir.test.js.map +1 -1
  6. package/dist/__tests__/userio.test.d.ts +1 -0
  7. package/dist/__tests__/userio.test.js +101 -0
  8. package/dist/__tests__/userio.test.js.map +1 -0
  9. package/dist/lib/state.d.ts +57 -2
  10. package/dist/lib/state.js +16 -4
  11. package/dist/lib/state.js.map +1 -1
  12. package/dist/lib/state_dir.d.ts +3 -1
  13. package/dist/lib/state_dir.js +33 -5
  14. package/dist/lib/state_dir.js.map +1 -1
  15. package/dist/server.js +3 -1
  16. package/dist/server.js.map +1 -1
  17. package/dist/telegram/api.d.ts +20 -0
  18. package/dist/telegram/api.js +38 -2
  19. package/dist/telegram/api.js.map +1 -1
  20. package/dist/telegram/bot.js +6 -1
  21. package/dist/telegram/bot.js.map +1 -1
  22. package/dist/telegram/commands.d.ts +5 -1
  23. package/dist/telegram/commands.js +108 -7
  24. package/dist/telegram/commands.js.map +1 -1
  25. package/dist/telegram/notifier.d.ts +3 -11
  26. package/dist/telegram/notifier.js +103 -24
  27. package/dist/telegram/notifier.js.map +1 -1
  28. package/dist/tools/broker.js +26 -16
  29. package/dist/tools/broker.js.map +1 -1
  30. package/dist/tools/dispatch.js +13 -3
  31. package/dist/tools/dispatch.js.map +1 -1
  32. package/dist/tools/lifecycle.js +38 -3
  33. package/dist/tools/lifecycle.js.map +1 -1
  34. package/dist/tools/ping.js +11 -3
  35. package/dist/tools/ping.js.map +1 -1
  36. package/dist/tools/planning.js +93 -33
  37. package/dist/tools/planning.js.map +1 -1
  38. package/dist/tools/userio.d.ts +4 -0
  39. package/dist/tools/userio.js +116 -0
  40. package/dist/tools/userio.js.map +1 -0
  41. package/package.json +1 -1
@@ -0,0 +1,116 @@
1
+ import { z } from "zod";
2
+ import { randomUUID } from "node:crypto";
3
+ import { findPlan, findUserResponseRequest } from "../lib/state.js";
4
+ /**
5
+ * Broker-to-user tools (v0.4+).
6
+ *
7
+ * The other broker tools (send_message / post_note) only connect primary
8
+ * Claude to spawned children. These three close the gap to the user:
9
+ *
10
+ * notify_user one-way push (Telegram), no response expected
11
+ * request_user_response push + await; user taps a button or replies
12
+ * poll_user_response primary Claude polls for the answer
13
+ *
14
+ * Implementation: writes to plan.userNotifications / plan.userResponseRequests.
15
+ * The Telegram notifier (separate process, `orqlaude tg start`) detects new
16
+ * entries on its 5-second tick and pushes them. The bot listens for callback_query
17
+ * updates and writes the response back into state.
18
+ */
19
+ const URGENCY = z.enum(["low", "normal", "high"]);
20
+ export function registerUserIo(server, store, audit) {
21
+ // ---- notify_user ----------------------------------------------------------
22
+ server.tool("notify_user", "PRIMARY CLAUDE → USER (Telegram). One-way push of an arbitrary message to the user's whitelisted Telegram chats. The notifier picks it up on its next 5-second tick. Use for mid-fleet status updates the user might want to know about (e.g. 'reviewer agent flagged 3 BLOCKERs', 'agent 2 finished early', 'budget at 80%'). Doesn't await a response — pair with request_user_response if you need one.", {
23
+ plan_id: z.string().describe("Plan id this notification belongs to (for filtering / context)."),
24
+ text: z.string().min(1).max(2000).describe("The message text. Will be Markdown-escaped before send. Keep concise."),
25
+ urgency: URGENCY.default("normal").describe("Affects emoji prefix in the Telegram message. low → 💬, normal → 📢, high → 🚨."),
26
+ task_id: z.string().optional().describe("Optional: attribute the notification to a specific task in the plan."),
27
+ }, audit.wrap("notify_user", async ({ plan_id, text, urgency, task_id }) => {
28
+ const result = await store.update((state) => {
29
+ const plan = findPlan(state, plan_id);
30
+ const note = {
31
+ id: randomUUID(),
32
+ taskId: task_id,
33
+ text,
34
+ urgency,
35
+ createdAt: Date.now(),
36
+ delivered: false,
37
+ };
38
+ plan.userNotifications.push(note);
39
+ return {
40
+ plan_id,
41
+ notification_id: note.id,
42
+ queued: true,
43
+ delivered_via_telegram: "pending — depends on `orqlaude tg start` running with at least one whitelisted user.",
44
+ };
45
+ });
46
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
47
+ }, ({ plan_id }) => ({ planId: plan_id })));
48
+ // ---- request_user_response -----------------------------------------------
49
+ server.tool("request_user_response", "PRIMARY CLAUDE asks the user a question via Telegram and awaits the response. If `options` is provided, the message has inline-keyboard buttons; otherwise the user is told to reply with `/respond <short_id> <text>`. Returns a `request_id` — poll it via `poll_user_response`. Times out after `timeout_sec` (default 600s = 10 min); if no response, status returns `timed_out`.", {
50
+ plan_id: z.string(),
51
+ prompt: z.string().min(1).max(2000).describe("The question to ask the user."),
52
+ options: z.array(z.string().min(1).max(32)).max(8).optional().describe("Optional list of button labels (≤8, each ≤32 chars). If provided, Telegram shows an inline keyboard. If omitted, freeform reply expected."),
53
+ timeout_sec: z.number().int().positive().max(3600).default(600).describe("Max seconds to wait. After this, poll returns `timed_out`. Default 600 (10 min); cap 3600 (1h)."),
54
+ task_id: z.string().optional(),
55
+ }, audit.wrap("request_user_response", async ({ plan_id, prompt, options, timeout_sec, task_id }) => {
56
+ const result = await store.update((state) => {
57
+ const plan = findPlan(state, plan_id);
58
+ const id = randomUUID();
59
+ const shortId = id.slice(0, 8);
60
+ const req = {
61
+ id,
62
+ shortId,
63
+ taskId: task_id,
64
+ prompt,
65
+ options,
66
+ createdAt: Date.now(),
67
+ timeoutAt: Date.now() + timeout_sec * 1000,
68
+ delivered: false,
69
+ };
70
+ plan.userResponseRequests.push(req);
71
+ return {
72
+ plan_id,
73
+ request_id: id,
74
+ short_id: shortId,
75
+ timeout_at: req.timeoutAt,
76
+ has_options: Boolean(options && options.length > 0),
77
+ next_step: "Poll `poll_user_response(request_id)` periodically. It returns status=`pending` until the user answers (or timeout). If no Telegram bot is running, the user can't respond — fall back to AskUserQuestion.",
78
+ };
79
+ });
80
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
81
+ }, ({ plan_id }) => ({ planId: plan_id })));
82
+ // ---- poll_user_response ---------------------------------------------------
83
+ server.tool("poll_user_response", "Poll a previously-issued `request_user_response`. Returns `status: pending | answered | timed_out | cancelled` and the `response` text when answered. Safe to call repeatedly; no side effects.", {
84
+ request_id: z.string().describe("Either the full UUID or the 8-char short_id returned by request_user_response."),
85
+ }, audit.wrap("poll_user_response", async ({ request_id }) => {
86
+ const result = await store.read((state) => {
87
+ const found = findUserResponseRequest(state, request_id);
88
+ if (!found) {
89
+ return { request_id, status: "unknown", note: "No request with that id or short_id." };
90
+ }
91
+ const { req } = found;
92
+ if (req.cancelled)
93
+ return { request_id: req.id, status: "cancelled" };
94
+ if (req.response !== undefined) {
95
+ return {
96
+ request_id: req.id,
97
+ status: "answered",
98
+ response: req.response,
99
+ responded_at: req.respondedAt,
100
+ };
101
+ }
102
+ if (Date.now() > req.timeoutAt) {
103
+ return { request_id: req.id, status: "timed_out", timeout_at: req.timeoutAt };
104
+ }
105
+ return {
106
+ request_id: req.id,
107
+ status: "pending",
108
+ delivered: req.delivered,
109
+ timeout_at: req.timeoutAt,
110
+ remaining_sec: Math.max(0, Math.round((req.timeoutAt - Date.now()) / 1000)),
111
+ };
112
+ });
113
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
114
+ }));
115
+ }
116
+ //# sourceMappingURL=userio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"userio.js","sourceRoot":"","sources":["../../src/tools/userio.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synaplink/orqlaude",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Multi-agent orchestrator for Claude Code. One primary session decomposes a complex task, gets a single budget approval, then dispatches N parallel agents via the Desktop app's native spawn_task. Tracks cost/status via JSONL tails; brokers messages between agents; detects hallucination; manages PRs.",
5
5
  "type": "module",
6
6
  "bin": {