@synaplink/orqlaude 0.4.0 → 0.5.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/README.md +55 -8
- package/dist/__tests__/v05.test.d.ts +1 -0
- package/dist/__tests__/v05.test.js +80 -0
- package/dist/__tests__/v05.test.js.map +1 -0
- package/dist/__tests__/v052.test.d.ts +1 -0
- package/dist/__tests__/v052.test.js +53 -0
- package/dist/__tests__/v052.test.js.map +1 -0
- package/dist/cli.js +65 -42
- package/dist/cli.js.map +1 -1
- package/dist/lib/agnet.d.ts +26 -0
- package/dist/lib/agnet.js +94 -0
- package/dist/lib/agnet.js.map +1 -0
- package/dist/lib/state.d.ts +50 -0
- package/dist/lib/state.js +10 -0
- package/dist/lib/state.js.map +1 -1
- package/dist/lib/style.d.ts +42 -0
- package/dist/lib/style.js +96 -0
- package/dist/lib/style.js.map +1 -0
- package/dist/server.js +1 -1
- package/dist/telegram/api.d.ts +20 -0
- package/dist/telegram/api.js +32 -0
- package/dist/telegram/api.js.map +1 -1
- package/dist/telegram/notifier.js +206 -6
- package/dist/telegram/notifier.js.map +1 -1
- package/dist/tools/dispatch.js +109 -23
- package/dist/tools/dispatch.js.map +1 -1
- package/dist/tools/ping.js +1 -1
- package/dist/tools/planning.js +8 -0
- package/dist/tools/planning.js.map +1 -1
- package/dist/tools/userio.js +79 -1
- package/dist/tools/userio.js.map +1 -1
- package/package.json +3 -2
package/dist/lib/state.d.ts
CHANGED
|
@@ -27,6 +27,9 @@ export interface Task {
|
|
|
27
27
|
branchHint?: string;
|
|
28
28
|
status: TaskStatus;
|
|
29
29
|
spawnedSessionId?: string;
|
|
30
|
+
/** v0.5+: Human-friendly Agnet designation (e.g. "Zenith"). Used in CLI
|
|
31
|
+
* output and Telegram notifications. Stable per task_id. */
|
|
32
|
+
agnetName?: string;
|
|
30
33
|
/** Optional per-task token budget hint. If set, status() surfaces a soft
|
|
31
34
|
* warning when usage exceeds 70% of this value, separate from the
|
|
32
35
|
* plan-wide hard cap. */
|
|
@@ -91,6 +94,47 @@ export interface UserNotification {
|
|
|
91
94
|
delivered: boolean;
|
|
92
95
|
deliveredAt?: number;
|
|
93
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* v0.5+: Streamed message from primary Claude to user. A stream is a long-
|
|
99
|
+
* running message that gets *edited in place* as new chunks arrive — the
|
|
100
|
+
* Telegram-shaped equivalent of a streamed assistant reply.
|
|
101
|
+
*
|
|
102
|
+
* Lifecycle:
|
|
103
|
+
* 1. stream_to_user_start writes the initial record + first content.
|
|
104
|
+
* Notifier sends the message and records telegramMessageId.
|
|
105
|
+
* 2. stream_to_user_append adds a chunk to `content`. Notifier edits the
|
|
106
|
+
* Telegram message on its next tick (throttled to ~1 edit/1.5s by
|
|
107
|
+
* Telegram rate limits).
|
|
108
|
+
* 3. stream_to_user_end finalizes. Notifier does a final edit with an
|
|
109
|
+
* "✓ complete" marker.
|
|
110
|
+
*/
|
|
111
|
+
export interface UserStream {
|
|
112
|
+
id: string;
|
|
113
|
+
shortId: string;
|
|
114
|
+
taskId?: string;
|
|
115
|
+
/** Title shown in bold at the top of the message (e.g. "Agnet Verdant"). */
|
|
116
|
+
title: string;
|
|
117
|
+
/** Full accumulated content. */
|
|
118
|
+
content: string;
|
|
119
|
+
status: "active" | "ended";
|
|
120
|
+
createdAt: number;
|
|
121
|
+
endedAt?: number;
|
|
122
|
+
/** v0.5.1+: deterministic non-zero integer for Telegram's sendMessageDraft.
|
|
123
|
+
* Derived from the stream UUID so it's stable across restarts. */
|
|
124
|
+
draftId?: number;
|
|
125
|
+
/** v0.5.1+: which transport path is active.
|
|
126
|
+
* "draft" → sendMessageDraft for each update, sendMessage to persist on end.
|
|
127
|
+
* "edit" → fallback: sendMessage once + editMessageText for updates (v0.5 default). */
|
|
128
|
+
transport?: "draft" | "edit";
|
|
129
|
+
/** Per-chat persistence so the notifier knows which message to edit (edit path) or persist (draft path). */
|
|
130
|
+
telegramChatId?: number;
|
|
131
|
+
telegramMessageId?: number;
|
|
132
|
+
/** v0.5.1+: whether the stream's final persisted message has been sent. */
|
|
133
|
+
finalSent?: boolean;
|
|
134
|
+
/** Last delivered content snapshot (so notifier knows what to send / edit). */
|
|
135
|
+
lastDeliveredContent?: string;
|
|
136
|
+
lastEditedAt?: number;
|
|
137
|
+
}
|
|
94
138
|
/**
|
|
95
139
|
* Outbound question from primary Claude to the user, with an awaited
|
|
96
140
|
* response. The notifier pushes to Telegram (with an inline keyboard if
|
|
@@ -137,6 +181,8 @@ export interface Plan {
|
|
|
137
181
|
/** v0.4+: outbound notifications / questions from primary Claude to user. */
|
|
138
182
|
userNotifications: UserNotification[];
|
|
139
183
|
userResponseRequests: UserResponseRequest[];
|
|
184
|
+
/** v0.5+: streaming-message records (edit-in-place Telegram messages). */
|
|
185
|
+
userStreams: UserStream[];
|
|
140
186
|
reviewPlanId?: string;
|
|
141
187
|
}
|
|
142
188
|
export interface State {
|
|
@@ -168,6 +214,10 @@ export declare class StateStore {
|
|
|
168
214
|
private persist;
|
|
169
215
|
}
|
|
170
216
|
export declare function newPlan(rootTask: string, budgetCapTokens: number, tasksInput: Array<Omit<Task, "id" | "status">>): Plan;
|
|
217
|
+
export declare function findUserStream(state: State, streamId: string): {
|
|
218
|
+
plan: Plan;
|
|
219
|
+
stream: UserStream;
|
|
220
|
+
} | undefined;
|
|
171
221
|
export declare function findUserResponseRequest(state: State, requestId: string): {
|
|
172
222
|
plan: Plan;
|
|
173
223
|
req: UserResponseRequest;
|
package/dist/lib/state.js
CHANGED
|
@@ -161,6 +161,7 @@ function migrate(input) {
|
|
|
161
161
|
claims: p.claims ?? [],
|
|
162
162
|
userNotifications: p.userNotifications ?? [],
|
|
163
163
|
userResponseRequests: p.userResponseRequests ?? [],
|
|
164
|
+
userStreams: p.userStreams ?? [],
|
|
164
165
|
};
|
|
165
166
|
}
|
|
166
167
|
return out;
|
|
@@ -185,8 +186,17 @@ export function newPlan(rootTask, budgetCapTokens, tasksInput) {
|
|
|
185
186
|
claims: [],
|
|
186
187
|
userNotifications: [],
|
|
187
188
|
userResponseRequests: [],
|
|
189
|
+
userStreams: [],
|
|
188
190
|
};
|
|
189
191
|
}
|
|
192
|
+
export function findUserStream(state, streamId) {
|
|
193
|
+
for (const plan of Object.values(state.plans)) {
|
|
194
|
+
const stream = plan.userStreams.find((s) => s.id === streamId || s.shortId === streamId);
|
|
195
|
+
if (stream)
|
|
196
|
+
return { plan, stream };
|
|
197
|
+
}
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
190
200
|
export function findUserResponseRequest(state, requestId) {
|
|
191
201
|
for (const plan of Object.values(state.plans)) {
|
|
192
202
|
const req = plan.userResponseRequests.find((r) => r.id === requestId || r.shortId === requestId);
|
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,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic-palette terminal styling. Uses ANSI 24-bit truecolor escapes so
|
|
3
|
+
* the rendered colors match the brand exactly when the terminal supports it.
|
|
4
|
+
*
|
|
5
|
+
* Brand colors (from Anthropic's public design):
|
|
6
|
+
* • Claude Coral #DA7756 — the signature warm orange. Primary brand.
|
|
7
|
+
* • Cream #F5F4EE — soft off-white. Secondary text / surfaces.
|
|
8
|
+
* • Crimson #BB5944 — deeper terracotta. Accent / hover.
|
|
9
|
+
* • Charcoal #2A2926 — near-black with brown warmth. Body text.
|
|
10
|
+
* • Dim Sand #B9B6AB — muted tan. Captions / disabled.
|
|
11
|
+
*
|
|
12
|
+
* Color is automatically disabled when:
|
|
13
|
+
* • stdout is not a TTY (piped output)
|
|
14
|
+
* • NO_COLOR env var is set (https://no-color.org/)
|
|
15
|
+
* • TERM is "dumb"
|
|
16
|
+
*
|
|
17
|
+
* Use `style.claude("text")` rather than concatenating escape codes directly,
|
|
18
|
+
* so a consumer reading orqlaude's output to a file gets clean text.
|
|
19
|
+
*/
|
|
20
|
+
export declare const style: {
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
/** Claude coral — primary brand color. Use for headings + accents. */
|
|
23
|
+
coral: (s: string) => string;
|
|
24
|
+
/** Cream — soft off-white. Use for secondary emphasis. */
|
|
25
|
+
cream: (s: string) => string;
|
|
26
|
+
/** Crimson — deeper terracotta. Use for warnings, accents. */
|
|
27
|
+
crimson: (s: string) => string;
|
|
28
|
+
/** Charcoal — near-black warm. Use for body text. */
|
|
29
|
+
charcoal: (s: string) => string;
|
|
30
|
+
/** Sand — muted tan. Use for captions, disabled. */
|
|
31
|
+
sand: (s: string) => string;
|
|
32
|
+
/** Coral background — for the orqlaude header banner. */
|
|
33
|
+
coralBg: (s: string) => string;
|
|
34
|
+
bold: (s: string) => string;
|
|
35
|
+
dim: (s: string) => string;
|
|
36
|
+
italic: (s: string) => string;
|
|
37
|
+
underline: (s: string) => string;
|
|
38
|
+
};
|
|
39
|
+
/** Status-keyed color: maps task/plan status strings to the brand palette. */
|
|
40
|
+
export declare function styleStatus(status: string): (s: string) => string;
|
|
41
|
+
/** orqlaude header banner. Shown at the top of `orqlaude` CLI invocations. */
|
|
42
|
+
export declare function banner(): string;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic-palette terminal styling. Uses ANSI 24-bit truecolor escapes so
|
|
3
|
+
* the rendered colors match the brand exactly when the terminal supports it.
|
|
4
|
+
*
|
|
5
|
+
* Brand colors (from Anthropic's public design):
|
|
6
|
+
* • Claude Coral #DA7756 — the signature warm orange. Primary brand.
|
|
7
|
+
* • Cream #F5F4EE — soft off-white. Secondary text / surfaces.
|
|
8
|
+
* • Crimson #BB5944 — deeper terracotta. Accent / hover.
|
|
9
|
+
* • Charcoal #2A2926 — near-black with brown warmth. Body text.
|
|
10
|
+
* • Dim Sand #B9B6AB — muted tan. Captions / disabled.
|
|
11
|
+
*
|
|
12
|
+
* Color is automatically disabled when:
|
|
13
|
+
* • stdout is not a TTY (piped output)
|
|
14
|
+
* • NO_COLOR env var is set (https://no-color.org/)
|
|
15
|
+
* • TERM is "dumb"
|
|
16
|
+
*
|
|
17
|
+
* Use `style.claude("text")` rather than concatenating escape codes directly,
|
|
18
|
+
* so a consumer reading orqlaude's output to a file gets clean text.
|
|
19
|
+
*/
|
|
20
|
+
const NO_COLOR = !!process.env.NO_COLOR;
|
|
21
|
+
const TERM_DUMB = process.env.TERM === "dumb";
|
|
22
|
+
const FORCE_COLOR = process.env.FORCE_COLOR && process.env.FORCE_COLOR !== "0";
|
|
23
|
+
const ENABLED = FORCE_COLOR || (!NO_COLOR && !TERM_DUMB && process.stdout.isTTY);
|
|
24
|
+
function fg(r, g, b) {
|
|
25
|
+
return `\x1b[38;2;${r};${g};${b}m`;
|
|
26
|
+
}
|
|
27
|
+
function bg(r, g, b) {
|
|
28
|
+
return `\x1b[48;2;${r};${g};${b}m`;
|
|
29
|
+
}
|
|
30
|
+
const RESET = "\x1b[0m";
|
|
31
|
+
const BOLD = "\x1b[1m";
|
|
32
|
+
const DIM = "\x1b[2m";
|
|
33
|
+
const ITALIC = "\x1b[3m";
|
|
34
|
+
const UNDERLINE = "\x1b[4m";
|
|
35
|
+
const FG_CORAL = fg(0xda, 0x77, 0x56);
|
|
36
|
+
const FG_CREAM = fg(0xf5, 0xf4, 0xee);
|
|
37
|
+
const FG_CRIMSON = fg(0xbb, 0x59, 0x44);
|
|
38
|
+
const FG_CHARCOAL = fg(0x2a, 0x29, 0x26);
|
|
39
|
+
const FG_SAND = fg(0xb9, 0xb6, 0xab);
|
|
40
|
+
const BG_CORAL = bg(0xda, 0x77, 0x56);
|
|
41
|
+
function wrap(prefix, text) {
|
|
42
|
+
if (!ENABLED)
|
|
43
|
+
return text;
|
|
44
|
+
return `${prefix}${text}${RESET}`;
|
|
45
|
+
}
|
|
46
|
+
export const style = {
|
|
47
|
+
enabled: ENABLED,
|
|
48
|
+
/** Claude coral — primary brand color. Use for headings + accents. */
|
|
49
|
+
coral: (s) => wrap(FG_CORAL, s),
|
|
50
|
+
/** Cream — soft off-white. Use for secondary emphasis. */
|
|
51
|
+
cream: (s) => wrap(FG_CREAM, s),
|
|
52
|
+
/** Crimson — deeper terracotta. Use for warnings, accents. */
|
|
53
|
+
crimson: (s) => wrap(FG_CRIMSON, s),
|
|
54
|
+
/** Charcoal — near-black warm. Use for body text. */
|
|
55
|
+
charcoal: (s) => wrap(FG_CHARCOAL, s),
|
|
56
|
+
/** Sand — muted tan. Use for captions, disabled. */
|
|
57
|
+
sand: (s) => wrap(FG_SAND, s),
|
|
58
|
+
/** Coral background — for the orqlaude header banner. */
|
|
59
|
+
coralBg: (s) => wrap(`${BG_CORAL}${fg(0x2a, 0x29, 0x26)}`, s),
|
|
60
|
+
bold: (s) => wrap(BOLD, s),
|
|
61
|
+
dim: (s) => wrap(DIM, s),
|
|
62
|
+
italic: (s) => wrap(ITALIC, s),
|
|
63
|
+
underline: (s) => wrap(UNDERLINE, s),
|
|
64
|
+
};
|
|
65
|
+
/** Status-keyed color: maps task/plan status strings to the brand palette. */
|
|
66
|
+
export function styleStatus(status) {
|
|
67
|
+
switch (status) {
|
|
68
|
+
case "draft":
|
|
69
|
+
case "estimating":
|
|
70
|
+
case "pending":
|
|
71
|
+
return style.sand;
|
|
72
|
+
case "awaiting_approval":
|
|
73
|
+
case "approved":
|
|
74
|
+
case "dispatching":
|
|
75
|
+
return style.cream;
|
|
76
|
+
case "running":
|
|
77
|
+
case "dispatched":
|
|
78
|
+
return style.coral;
|
|
79
|
+
case "done":
|
|
80
|
+
case "collected":
|
|
81
|
+
return (s) => style.bold(style.coral(s));
|
|
82
|
+
case "failed":
|
|
83
|
+
case "cancelled":
|
|
84
|
+
case "cancelled_overbudget":
|
|
85
|
+
return style.crimson;
|
|
86
|
+
default:
|
|
87
|
+
return (s) => s;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** orqlaude header banner. Shown at the top of `orqlaude` CLI invocations. */
|
|
91
|
+
export function banner() {
|
|
92
|
+
if (!ENABLED)
|
|
93
|
+
return "orqlaude";
|
|
94
|
+
return style.coral("◆ ") + style.bold(style.coral("orqlaude")) + style.sand(" — multi-agent orchestrator for Claude Code");
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=style.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"style.js","sourceRoot":"","sources":["../../src/lib/style.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
package/dist/telegram/api.d.ts
CHANGED
|
@@ -53,6 +53,26 @@ export declare class TelegramApi {
|
|
|
53
53
|
}>;
|
|
54
54
|
/** Acknowledge a callback_query so the Telegram client stops the spinner. */
|
|
55
55
|
answerCallbackQuery(callbackQueryId: string, text?: string): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Stream a partial draft message. Telegram animates updates that share the
|
|
58
|
+
* same `draft_id`, and the draft is ephemeral (~30s) — so when the stream
|
|
59
|
+
* is complete, follow up with a real `sendMessage` to persist the final
|
|
60
|
+
* content in the chat.
|
|
61
|
+
*
|
|
62
|
+
* Returns `{ ok: true }` on success, or `{ ok: false, status, body }` so
|
|
63
|
+
* callers can fall back to `editMessageText` on older Bot API servers that
|
|
64
|
+
* don't yet support drafts.
|
|
65
|
+
*/
|
|
66
|
+
sendMessageDraft(chatId: number, draftId: number, text: string, opts?: {
|
|
67
|
+
parseMode?: "Markdown" | "HTML";
|
|
68
|
+
messageThreadId?: number;
|
|
69
|
+
}): Promise<{
|
|
70
|
+
ok: true;
|
|
71
|
+
} | {
|
|
72
|
+
ok: false;
|
|
73
|
+
status: number;
|
|
74
|
+
body: string;
|
|
75
|
+
}>;
|
|
56
76
|
/** Edit a previously-sent message — used to mark questions as answered. */
|
|
57
77
|
editMessageText(chatId: number, messageId: number, text: string, opts?: {
|
|
58
78
|
parseMode?: "Markdown" | "HTML";
|
package/dist/telegram/api.js
CHANGED
|
@@ -64,6 +64,38 @@ export class TelegramApi {
|
|
|
64
64
|
process.stderr.write(`[orqlaude tg] answerCallbackQuery ${res.status}: ${await res.text()}\n`);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Stream a partial draft message. Telegram animates updates that share the
|
|
69
|
+
* same `draft_id`, and the draft is ephemeral (~30s) — so when the stream
|
|
70
|
+
* is complete, follow up with a real `sendMessage` to persist the final
|
|
71
|
+
* content in the chat.
|
|
72
|
+
*
|
|
73
|
+
* Returns `{ ok: true }` on success, or `{ ok: false, status, body }` so
|
|
74
|
+
* callers can fall back to `editMessageText` on older Bot API servers that
|
|
75
|
+
* don't yet support drafts.
|
|
76
|
+
*/
|
|
77
|
+
async sendMessageDraft(chatId, draftId, text, opts = {}) {
|
|
78
|
+
if (!Number.isInteger(draftId) || draftId === 0) {
|
|
79
|
+
throw new Error("draftId must be a non-zero integer");
|
|
80
|
+
}
|
|
81
|
+
const body = {
|
|
82
|
+
chat_id: chatId,
|
|
83
|
+
draft_id: draftId,
|
|
84
|
+
text,
|
|
85
|
+
parse_mode: opts.parseMode,
|
|
86
|
+
};
|
|
87
|
+
if (opts.messageThreadId !== undefined)
|
|
88
|
+
body.message_thread_id = opts.messageThreadId;
|
|
89
|
+
const res = await fetch(this.endpoint("sendMessageDraft"), {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers: { "content-type": "application/json" },
|
|
92
|
+
body: JSON.stringify(body),
|
|
93
|
+
});
|
|
94
|
+
if (!res.ok) {
|
|
95
|
+
return { ok: false, status: res.status, body: await res.text() };
|
|
96
|
+
}
|
|
97
|
+
return { ok: true };
|
|
98
|
+
}
|
|
67
99
|
/** Edit a previously-sent message — used to mark questions as answered. */
|
|
68
100
|
async editMessageText(chatId, messageId, text, opts = {}) {
|
|
69
101
|
const res = await fetch(this.endpoint("editMessageText"), {
|
package/dist/telegram/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/telegram/api.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/telegram/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { promises as fs } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { StateStore } from "../lib/state.js";
|
|
4
|
+
import { agnetLabel } from "../lib/agnet.js";
|
|
5
|
+
/** Minimum interval between edits when using the editMessageText fallback
|
|
6
|
+
* (the Bot API limits edits to ~1/sec per chat; we use 1.5s for headroom).
|
|
7
|
+
* sendMessageDraft is designed for streaming so we use a shorter throttle. */
|
|
8
|
+
const STREAM_EDIT_THROTTLE_MS = 1500;
|
|
9
|
+
const STREAM_DRAFT_THROTTLE_MS = 400;
|
|
10
|
+
/**
|
|
11
|
+
* Derive a stable, non-zero 48-bit integer draft_id from a stream UUID. Same
|
|
12
|
+
* stream UUID always yields the same draft_id, so a restart of the notifier
|
|
13
|
+
* can pick up an in-flight stream and continue animating it.
|
|
14
|
+
*/
|
|
15
|
+
function streamDraftId(streamId) {
|
|
16
|
+
const hex = streamId.replace(/-/g, "").slice(0, 12); // 48 bits, safely in MAX_SAFE_INTEGER
|
|
17
|
+
const n = parseInt(hex, 16);
|
|
18
|
+
return n === 0 ? 1 : n;
|
|
19
|
+
}
|
|
4
20
|
const EMPTY_CURSOR = {
|
|
5
21
|
initialized: false,
|
|
6
22
|
lastNoteId: null,
|
|
@@ -9,6 +25,7 @@ const EMPTY_CURSOR = {
|
|
|
9
25
|
alertedHallucinations: [],
|
|
10
26
|
notifiedUserNotificationIds: [],
|
|
11
27
|
notifiedUserRequestIds: [],
|
|
28
|
+
notifiedUserStreamIds: [],
|
|
12
29
|
};
|
|
13
30
|
const URGENCY_EMOJI = { low: "💬", normal: "📢", high: "🚨" };
|
|
14
31
|
export class Notifier {
|
|
@@ -65,6 +82,8 @@ export class Notifier {
|
|
|
65
82
|
cursor.notifiedUserNotificationIds.push(n.id);
|
|
66
83
|
for (const r of plan.userResponseRequests)
|
|
67
84
|
cursor.notifiedUserRequestIds.push(r.id);
|
|
85
|
+
for (const s of plan.userStreams)
|
|
86
|
+
cursor.notifiedUserStreamIds.push(s.id);
|
|
68
87
|
}
|
|
69
88
|
cursor.initialized = true;
|
|
70
89
|
await this.saveCursor();
|
|
@@ -99,20 +118,27 @@ export class Notifier {
|
|
|
99
118
|
for (const task of plan.tasks) {
|
|
100
119
|
const prev = cursor.taskStatus[task.id];
|
|
101
120
|
if (prev !== task.status) {
|
|
121
|
+
// v0.5: prefix every task-status message with the Agnet designation
|
|
122
|
+
// so the user sees who's reporting in.
|
|
123
|
+
const agnet = escapeMd(agnetLabel(task.agnetName));
|
|
124
|
+
const title = escapeMd(truncate(task.title, 50));
|
|
102
125
|
if (task.status === "done") {
|
|
103
126
|
const prSuffix = task.prUrl ? `\n${task.prUrl}` : "";
|
|
104
|
-
plainMessages.push(`✓ *${
|
|
127
|
+
plainMessages.push(`✓ *${agnet}* finished — _${title}_${prSuffix}`);
|
|
105
128
|
}
|
|
106
129
|
else if (task.status === "failed") {
|
|
107
|
-
plainMessages.push(`❌ *${
|
|
130
|
+
plainMessages.push(`❌ *${agnet}* failed — _${title}_${task.exitReason ? `\n${escapeMd(task.exitReason)}` : ""}`);
|
|
108
131
|
}
|
|
109
132
|
else if (task.status === "cancelled") {
|
|
110
|
-
plainMessages.push(`🛑 *${
|
|
133
|
+
plainMessages.push(`🛑 *${agnet}* cancelled — _${title}_`);
|
|
134
|
+
}
|
|
135
|
+
else if (task.status === "running" && prev === "dispatched") {
|
|
136
|
+
plainMessages.push(`▶ *${agnet}* started — _${title}_`);
|
|
111
137
|
}
|
|
112
138
|
cursor.taskStatus[task.id] = task.status;
|
|
113
139
|
}
|
|
114
140
|
}
|
|
115
|
-
// ---- agent notes ----
|
|
141
|
+
// ---- agent (Agnet) notes ----
|
|
116
142
|
let foundLast = cursor.lastNoteId === null;
|
|
117
143
|
for (const note of plan.notes) {
|
|
118
144
|
if (!foundLast) {
|
|
@@ -120,9 +146,10 @@ export class Notifier {
|
|
|
120
146
|
foundLast = true;
|
|
121
147
|
continue;
|
|
122
148
|
}
|
|
123
|
-
const
|
|
149
|
+
const task = plan.tasks.find((t) => t.id === note.taskId);
|
|
150
|
+
const agnet = escapeMd(agnetLabel(task?.agnetName));
|
|
124
151
|
const blocking = note.blocking ? " 🟡 blocking" : "";
|
|
125
|
-
plainMessages.push(`📝 *${
|
|
152
|
+
plainMessages.push(`📝 *${agnet}*${blocking}\n${escapeMd(truncate(note.text, 300))}${note.prUrl ? `\n${note.prUrl}` : ""}`);
|
|
126
153
|
cursor.lastNoteId = note.id;
|
|
127
154
|
}
|
|
128
155
|
// ---- v0.4: user notifications ----
|
|
@@ -168,6 +195,107 @@ export class Notifier {
|
|
|
168
195
|
}
|
|
169
196
|
}
|
|
170
197
|
}
|
|
198
|
+
// ---- v0.5+ stream handling (draft-first, edit fallback) ----
|
|
199
|
+
//
|
|
200
|
+
// For each stream we maintain ONE of two transports per Telegram chat:
|
|
201
|
+
// 1. "draft" (preferred, v0.5.1+): sendMessageDraft with a stable
|
|
202
|
+
// draft_id. Telegram animates updates and shows them as an
|
|
203
|
+
// ephemeral preview. When the stream ends we call sendMessage to
|
|
204
|
+
// persist the final content.
|
|
205
|
+
// 2. "edit" (fallback): sendMessage once + editMessageText for each
|
|
206
|
+
// update. Used when sendMessageDraft is unavailable on the bot's
|
|
207
|
+
// Telegram server.
|
|
208
|
+
//
|
|
209
|
+
// We probe draft on first attempt; if it fails (404 / method not found),
|
|
210
|
+
// we switch the stream's transport to "edit" and stick with it for the
|
|
211
|
+
// rest of its lifecycle.
|
|
212
|
+
for (const plan of plans) {
|
|
213
|
+
for (const stream of plan.userStreams) {
|
|
214
|
+
const isNew = !cursor.notifiedUserStreamIds.includes(stream.id);
|
|
215
|
+
const hasNewContent = stream.lastDeliveredContent !== stream.content;
|
|
216
|
+
const justEnded = stream.status === "ended" && !stream.finalSent;
|
|
217
|
+
if (!isNew && !hasNewContent && !justEnded)
|
|
218
|
+
continue;
|
|
219
|
+
// Throttle: drafts are designed for streaming, so we tick faster.
|
|
220
|
+
const throttle = stream.transport === "edit" ? STREAM_EDIT_THROTTLE_MS : STREAM_DRAFT_THROTTLE_MS;
|
|
221
|
+
const now = Date.now();
|
|
222
|
+
if (!isNew && !justEnded && stream.lastEditedAt && now - stream.lastEditedAt < throttle) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const draftId = stream.draftId ?? streamDraftId(stream.id);
|
|
226
|
+
if (!stream.draftId) {
|
|
227
|
+
// First touch — record draft id.
|
|
228
|
+
await store.update((state) => {
|
|
229
|
+
for (const p of Object.values(state.plans)) {
|
|
230
|
+
const s = p.userStreams.find((x) => x.id === stream.id);
|
|
231
|
+
if (s)
|
|
232
|
+
s.draftId = draftId;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
for (const entry of this.cfg.whitelist) {
|
|
237
|
+
// Effective transport: explicit, else default to "draft" and let
|
|
238
|
+
// the first call probe.
|
|
239
|
+
const transport = stream.transport ?? "draft";
|
|
240
|
+
if (transport === "draft") {
|
|
241
|
+
// Draft path. text="" on initial empty content gives the
|
|
242
|
+
// Telegram "Thinking…" placeholder.
|
|
243
|
+
const text = formatStreamMessage(stream);
|
|
244
|
+
const result = await this.api.sendMessageDraft(entry.chatId, draftId, text, { parseMode: "Markdown" });
|
|
245
|
+
if (!result.ok) {
|
|
246
|
+
// Server doesn't support drafts (or other failure). Switch to
|
|
247
|
+
// edit fallback for the rest of this stream's life.
|
|
248
|
+
process.stderr.write(`[orqlaude tg stream] sendMessageDraft failed (${result.status}); falling back to edit. body=${result.body.slice(0, 200)}\n`);
|
|
249
|
+
await store.update((state) => {
|
|
250
|
+
for (const p of Object.values(state.plans)) {
|
|
251
|
+
const s = p.userStreams.find((x) => x.id === stream.id);
|
|
252
|
+
if (s)
|
|
253
|
+
s.transport = "edit";
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
// Now do the edit-path open in this same tick.
|
|
257
|
+
await openOrEditEditMode(this.api, store, stream, entry.chatId, justEnded);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
// Draft sent. Record delivery state.
|
|
261
|
+
await store.update((state) => {
|
|
262
|
+
for (const p of Object.values(state.plans)) {
|
|
263
|
+
const s = p.userStreams.find((x) => x.id === stream.id);
|
|
264
|
+
if (s) {
|
|
265
|
+
s.transport = "draft";
|
|
266
|
+
s.telegramChatId = s.telegramChatId ?? entry.chatId;
|
|
267
|
+
s.lastDeliveredContent = text;
|
|
268
|
+
s.lastEditedAt = Date.now();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
// On end: persist the final message via sendMessage and mark.
|
|
273
|
+
if (justEnded) {
|
|
274
|
+
try {
|
|
275
|
+
await this.api.sendMessage(entry.chatId, formatStreamEnded(stream), { parseMode: "Markdown" });
|
|
276
|
+
await store.update((state) => {
|
|
277
|
+
for (const p of Object.values(state.plans)) {
|
|
278
|
+
const s = p.userStreams.find((x) => x.id === stream.id);
|
|
279
|
+
if (s)
|
|
280
|
+
s.finalSent = true;
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
process.stderr.write(`[orqlaude tg stream] final persist failed: ${err.message}\n`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
// Edit path.
|
|
292
|
+
await openOrEditEditMode(this.api, store, stream, entry.chatId, justEnded);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (isNew)
|
|
296
|
+
cursor.notifiedUserStreamIds.push(stream.id);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
171
299
|
// Send question pushes. Persist message_id back into state so the bot
|
|
172
300
|
// can edit on response.
|
|
173
301
|
for (const qp of questionPushes) {
|
|
@@ -200,6 +328,78 @@ export class Notifier {
|
|
|
200
328
|
}
|
|
201
329
|
}
|
|
202
330
|
}
|
|
331
|
+
function formatStreamMessage(stream) {
|
|
332
|
+
// Title bold; body plain (caller-escaped if they care).
|
|
333
|
+
const body = stream.content ? `\n${stream.content}` : "";
|
|
334
|
+
return `*${escapeMd(stream.title)}*${body}`;
|
|
335
|
+
}
|
|
336
|
+
function formatStreamEnded(stream) {
|
|
337
|
+
const body = stream.content ? `\n${stream.content}` : "";
|
|
338
|
+
return `*${escapeMd(stream.title)}* ✓${body}`;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Edit-mode fallback: maintain a single persistent Telegram message via
|
|
342
|
+
* sendMessage + editMessageText. Used when sendMessageDraft is unavailable.
|
|
343
|
+
*/
|
|
344
|
+
async function openOrEditEditMode(api, store, stream, chatId, justEnded) {
|
|
345
|
+
if (!stream.telegramMessageId) {
|
|
346
|
+
// First time on this chat — send the message.
|
|
347
|
+
try {
|
|
348
|
+
const { message_id } = await api.sendMessage(chatId, formatStreamMessage(stream), { parseMode: "Markdown" });
|
|
349
|
+
await store.update((state) => {
|
|
350
|
+
for (const p of Object.values(state.plans)) {
|
|
351
|
+
const s = p.userStreams.find((x) => x.id === stream.id);
|
|
352
|
+
if (s) {
|
|
353
|
+
s.transport = "edit";
|
|
354
|
+
s.telegramMessageId = message_id;
|
|
355
|
+
s.telegramChatId = chatId;
|
|
356
|
+
s.lastDeliveredContent = stream.content;
|
|
357
|
+
s.lastEditedAt = Date.now();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
// If the stream is already ended, do one more edit to mark complete.
|
|
362
|
+
if (justEnded) {
|
|
363
|
+
try {
|
|
364
|
+
await api.editMessageText(chatId, message_id, formatStreamEnded(stream), { parseMode: "Markdown" });
|
|
365
|
+
await store.update((state) => {
|
|
366
|
+
for (const p of Object.values(state.plans)) {
|
|
367
|
+
const s = p.userStreams.find((x) => x.id === stream.id);
|
|
368
|
+
if (s)
|
|
369
|
+
s.finalSent = true;
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
catch (err) {
|
|
374
|
+
process.stderr.write(`[orqlaude tg stream] edit-mode final edit failed: ${err.message}\n`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
catch (err) {
|
|
379
|
+
process.stderr.write(`[orqlaude tg stream] edit-mode open failed: ${err.message}\n`);
|
|
380
|
+
}
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
// Subsequent updates — edit in place.
|
|
384
|
+
try {
|
|
385
|
+
const text = justEnded ? formatStreamEnded(stream) : formatStreamMessage(stream);
|
|
386
|
+
await api.editMessageText(stream.telegramChatId, stream.telegramMessageId, text, { parseMode: "Markdown" });
|
|
387
|
+
await store.update((state) => {
|
|
388
|
+
for (const p of Object.values(state.plans)) {
|
|
389
|
+
const s = p.userStreams.find((x) => x.id === stream.id);
|
|
390
|
+
if (s) {
|
|
391
|
+
s.lastDeliveredContent = text;
|
|
392
|
+
s.lastEditedAt = Date.now();
|
|
393
|
+
if (justEnded)
|
|
394
|
+
s.finalSent = true;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
catch (err) {
|
|
400
|
+
process.stderr.write(`[orqlaude tg stream] edit-mode update failed: ${err.message}\n`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
203
403
|
function buildInlineKeyboard(shortId, options) {
|
|
204
404
|
// Two columns max; one row per pair. callback_data format:
|
|
205
405
|
// orq:resp:<shortId>:<optionIdx>
|