astrocode-workflow 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/state/db.d.ts +1 -1
- package/dist/state/db.js +62 -4
- package/dist/tools/workflow.d.ts +1 -1
- package/dist/tools/workflow.js +114 -94
- package/dist/ui/inject.d.ts +3 -17
- package/dist/ui/inject.js +68 -98
- package/package.json +1 -1
- package/src/state/db.ts +63 -4
- package/src/tools/workflow.ts +155 -136
- package/src/tools/workflow.ts.backup +681 -0
- package/src/ui/inject.ts +78 -107
package/src/ui/inject.ts
CHANGED
|
@@ -1,136 +1,107 @@
|
|
|
1
1
|
// src/ui/inject.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// - Retries with backoff
|
|
7
|
-
// - flush() lets callers wait until injections are actually sent
|
|
8
|
-
//
|
|
9
|
-
// IMPORTANT: Callers who need reliability must `await injectChatPrompt(...)`
|
|
10
|
-
// or `await flushChatPrompts()` after enqueueing.
|
|
11
|
-
|
|
12
|
-
type QueueItem = {
|
|
2
|
+
|
|
3
|
+
let isInjecting = false;
|
|
4
|
+
|
|
5
|
+
const injectionQueue: Array<{
|
|
13
6
|
ctx: any;
|
|
14
|
-
sessionId
|
|
7
|
+
sessionId?: string;
|
|
15
8
|
text: string;
|
|
16
9
|
agent?: string;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let workerRunning = false;
|
|
22
|
-
|
|
23
|
-
// Used to let callers await "queue drained"
|
|
24
|
-
let drainWaiters: Array<() => void> = [];
|
|
10
|
+
toast?: { title: string; message: string; variant?: "info" | "success" | "warning" | "error"; durationMs?: number };
|
|
11
|
+
retry?: { maxAttempts?: number; baseDelayMs?: number };
|
|
12
|
+
}> = [];
|
|
25
13
|
|
|
26
14
|
function sleep(ms: number) {
|
|
27
|
-
return new Promise
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function resolveDrainWaitersIfIdle() {
|
|
31
|
-
if (workerRunning) return;
|
|
32
|
-
if (injectionQueue.length !== 0) return;
|
|
33
|
-
|
|
34
|
-
const waiters = drainWaiters;
|
|
35
|
-
drainWaiters = [];
|
|
36
|
-
for (const w of waiters) w();
|
|
15
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
37
16
|
}
|
|
38
17
|
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
|
|
18
|
+
function resolveSessionId(ctx: any, sessionId?: string): string | null {
|
|
19
|
+
if (sessionId) return sessionId;
|
|
20
|
+
const direct = (ctx as any)?.sessionID ?? (ctx as any)?.sessionId ?? (ctx as any)?.session?.id;
|
|
21
|
+
if (typeof direct === "string" && direct.length > 0) return direct;
|
|
22
|
+
return null;
|
|
42
23
|
}
|
|
43
24
|
|
|
44
|
-
async function
|
|
45
|
-
const { ctx, sessionId, text } = opts;
|
|
46
|
-
const agent = opts.agent ?? "Astro";
|
|
47
|
-
const prefixedText = `[${agent}]\n\n${text}`;
|
|
48
|
-
|
|
49
|
-
if (!sessionId) {
|
|
50
|
-
console.warn("[Astrocode] Injection skipped: missing sessionId");
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
25
|
+
async function tryInjectOnce(opts: { ctx: any; sessionId: string; text: string; agent: string }): Promise<void> {
|
|
26
|
+
const { ctx, sessionId, text, agent } = opts;
|
|
53
27
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
28
|
+
// Prefer explicit chat prompt API
|
|
29
|
+
const promptApi = (ctx as any)?.client?.session?.prompt;
|
|
30
|
+
if (!promptApi) {
|
|
31
|
+
throw new Error("API not available (ctx.client.session.prompt)");
|
|
58
32
|
}
|
|
59
33
|
|
|
60
|
-
const
|
|
61
|
-
let attempt = 0;
|
|
62
|
-
|
|
63
|
-
while (attempt < maxAttempts) {
|
|
64
|
-
attempt += 1;
|
|
65
|
-
try {
|
|
66
|
-
await prompt({
|
|
67
|
-
path: { id: sessionId },
|
|
68
|
-
body: {
|
|
69
|
-
parts: [{ type: "text", text: prefixedText }],
|
|
70
|
-
agent,
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
|
-
return;
|
|
74
|
-
} catch (err) {
|
|
75
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
76
|
-
const isLast = attempt >= maxAttempts;
|
|
77
|
-
|
|
78
|
-
if (isLast) {
|
|
79
|
-
console.warn(`[Astrocode] Injection failed (final): ${msg}`);
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
34
|
+
const prefixedText = `[${agent}]\n\n${text}`;
|
|
82
35
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
36
|
+
// Some hosts reject unknown fields; keep body minimal and stable.
|
|
37
|
+
await promptApi({
|
|
38
|
+
path: { id: sessionId },
|
|
39
|
+
body: {
|
|
40
|
+
parts: [{ type: "text", text: prefixedText }],
|
|
41
|
+
},
|
|
42
|
+
});
|
|
89
43
|
}
|
|
90
44
|
|
|
91
|
-
async function
|
|
92
|
-
if (
|
|
93
|
-
|
|
45
|
+
async function processQueue() {
|
|
46
|
+
if (isInjecting) return;
|
|
47
|
+
if (injectionQueue.length === 0) return;
|
|
48
|
+
|
|
49
|
+
isInjecting = true;
|
|
94
50
|
|
|
95
51
|
try {
|
|
96
|
-
// Drain sequentially to preserve ordering
|
|
97
52
|
while (injectionQueue.length > 0) {
|
|
98
53
|
const item = injectionQueue.shift();
|
|
99
54
|
if (!item) continue;
|
|
100
|
-
|
|
55
|
+
|
|
56
|
+
const { ctx, text, agent = "Astro" } = item;
|
|
57
|
+
const sessionId = resolveSessionId(ctx, item.sessionId);
|
|
58
|
+
|
|
59
|
+
if (!sessionId) {
|
|
60
|
+
// Drop on floor: we cannot recover without a session id.
|
|
61
|
+
// Keep draining the queue so we don't stall.
|
|
62
|
+
// eslint-disable-next-line no-console
|
|
63
|
+
console.warn("[Astrocode] Injection skipped: no sessionId");
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const maxAttempts = item.retry?.maxAttempts ?? 4;
|
|
68
|
+
const baseDelayMs = item.retry?.baseDelayMs ?? 250;
|
|
69
|
+
|
|
70
|
+
let lastErr: any = null;
|
|
71
|
+
|
|
72
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
73
|
+
try {
|
|
74
|
+
await tryInjectOnce({ ctx, sessionId, text, agent });
|
|
75
|
+
lastErr = null;
|
|
76
|
+
break;
|
|
77
|
+
} catch (e) {
|
|
78
|
+
lastErr = e;
|
|
79
|
+
const delay = baseDelayMs * Math.pow(2, attempt - 1); // 250, 500, 1000, 2000
|
|
80
|
+
// eslint-disable-next-line no-console
|
|
81
|
+
console.warn(`[Astrocode] Injection attempt ${attempt}/${maxAttempts} failed: ${String(e)}; retrying in ${delay}ms`);
|
|
82
|
+
await sleep(delay);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (lastErr) {
|
|
87
|
+
// eslint-disable-next-line no-console
|
|
88
|
+
console.warn(`[Astrocode] Injection failed permanently after ${maxAttempts} attempts: ${String(lastErr)}`);
|
|
89
|
+
}
|
|
101
90
|
}
|
|
102
91
|
} finally {
|
|
103
|
-
|
|
104
|
-
resolveDrainWaitersIfIdle();
|
|
92
|
+
isInjecting = false;
|
|
105
93
|
}
|
|
106
94
|
}
|
|
107
95
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
void runWorkerLoop();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Wait until all queued injections have been processed (sent or exhausted retries).
|
|
120
|
-
*/
|
|
121
|
-
export function flushChatPrompts(): Promise<void> {
|
|
122
|
-
if (!workerRunning && injectionQueue.length === 0) return Promise.resolve();
|
|
123
|
-
return new Promise<void>((resolve) => {
|
|
124
|
-
drainWaiters.push(resolve);
|
|
125
|
-
// Ensure worker is running (in case someone enqueued without kick)
|
|
126
|
-
void runWorkerLoop();
|
|
96
|
+
export async function injectChatPrompt(opts: { ctx: any; sessionId?: string; text: string; agent?: string }) {
|
|
97
|
+
injectionQueue.push({
|
|
98
|
+
ctx: opts.ctx,
|
|
99
|
+
sessionId: opts.sessionId,
|
|
100
|
+
text: opts.text,
|
|
101
|
+
agent: opts.agent ?? "Astro",
|
|
102
|
+
retry: { maxAttempts: 4, baseDelayMs: 250 },
|
|
127
103
|
});
|
|
128
|
-
}
|
|
129
104
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
*/
|
|
133
|
-
export async function injectChatPrompt(opts: QueueItem): Promise<void> {
|
|
134
|
-
enqueueChatPrompt(opts);
|
|
135
|
-
await flushChatPrompts();
|
|
105
|
+
// Fire-and-forget; queue drain is serialized by isInjecting.
|
|
106
|
+
void processQueue();
|
|
136
107
|
}
|