@inetafrica/open-claudia 2.15.0 → 3.0.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/.env.example +30 -4
- package/CHANGELOG.md +12 -0
- package/README.md +87 -66
- package/bin/agent.js +44 -18
- package/bin/cli.js +30 -28
- package/bin/dream.js +5 -11
- package/bin/loopback-client.js +29 -5
- package/bin/schedule.js +19 -5
- package/bin/tool.js +23 -5
- package/bot-agent.js +5 -2350
- package/bot.js +66 -3
- package/channels/telegram/adapter.js +6 -1
- package/channels/voice/adapter.js +1 -0
- package/channels/voice/manage.js +43 -5
- package/config-dir.js +3 -11
- package/core/actions.js +149 -40
- package/core/approvals.js +136 -29
- package/core/auth-flow.js +103 -19
- package/core/config-dir.js +19 -0
- package/core/config.js +115 -69
- package/core/doctor.js +111 -57
- package/core/dream.js +219 -62
- package/core/enforcer.js +0 -0
- package/core/entities.js +2 -1
- package/core/fsutil.js +21 -4
- package/core/handlers.js +503 -217
- package/core/ideas.js +2 -1
- package/core/jobs.js +589 -72
- package/core/lessons.js +3 -2
- package/core/loopback.js +42 -6
- package/core/memory-mutation-queue.js +241 -0
- package/core/migration-backup.js +1060 -0
- package/core/pack-review.js +295 -88
- package/core/packs.js +4 -3
- package/core/persona.js +2 -1
- package/core/process-tree.js +19 -2
- package/core/provider-migration.js +39 -0
- package/core/provider-status.js +80 -0
- package/core/providers/claude-events.js +117 -0
- package/core/providers/claude-hook.js +114 -0
- package/core/providers/claude.js +296 -0
- package/core/providers/codex-events.js +121 -0
- package/core/providers/codex-hook.js +280 -0
- package/core/providers/codex.js +286 -0
- package/core/providers/contract.js +153 -0
- package/core/providers/env.js +148 -0
- package/core/providers/events.js +170 -0
- package/core/providers/hook-command.js +22 -0
- package/core/providers/index.js +240 -0
- package/core/providers/utility-policy.js +269 -0
- package/core/recall/classic.js +2 -2
- package/core/recall/discoverer.js +71 -22
- package/core/recall/metrics.js +27 -1
- package/core/recall/tuning.js +4 -1
- package/core/recall/warm-walker.js +151 -108
- package/core/recall-filter.js +86 -61
- package/core/router.js +79 -7
- package/core/run-context.js +185 -0
- package/core/runner.js +1414 -1290
- package/core/scheduler.js +515 -210
- package/core/side-chat.js +346 -0
- package/core/state.js +1084 -97
- package/core/subagent.js +72 -98
- package/core/system-prompt.js +462 -279
- package/core/tool-guard.js +73 -39
- package/core/tools.js +22 -5
- package/core/transcripts.js +30 -1
- package/core/usage-log.js +19 -4
- package/core/utility-agent.js +654 -0
- package/core/web-auth.js +29 -13
- package/docs/CHANNEL_DESIGN.md +181 -0
- package/docs/MULTI_CHANNEL_PLAN.md +254 -0
- package/docs/PROVIDER_MIGRATION.md +67 -0
- package/health.js +50 -39
- package/package.json +48 -4
- package/setup.js +198 -38
- package/soul.md +39 -0
- package/test-ability-extraction.js +5 -0
- package/test-approval-async.js +63 -4
- package/test-cursor-removal.js +334 -0
- package/test-enforcer.js +70 -27
- package/test-fixtures/current-provider-parity.json +132 -0
- package/test-fixtures/fake-agent-cli.js +477 -0
- package/test-fixtures/migrations/claude-only/jobs.json +3 -0
- package/test-fixtures/migrations/claude-only/sessions.json +5 -0
- package/test-fixtures/migrations/claude-only/state.json +5 -0
- package/test-fixtures/migrations/cursor-selected/crons.json +3 -0
- package/test-fixtures/migrations/cursor-selected/jobs.json +3 -0
- package/test-fixtures/migrations/cursor-selected/sessions.json +5 -0
- package/test-fixtures/migrations/cursor-selected/state.json +6 -0
- package/test-fixtures/migrations/dual-provider/jobs.json +3 -0
- package/test-fixtures/migrations/dual-provider/sessions.json +7 -0
- package/test-fixtures/migrations/dual-provider/state.json +10 -0
- package/test-fixtures/migrations/malformed-partial/jobs.json +1 -0
- package/test-fixtures/migrations/malformed-partial/sessions.json +1 -0
- package/test-fixtures/migrations/malformed-partial/sessions.json.bak +3 -0
- package/test-fixtures/migrations/malformed-partial/state.json +1 -0
- package/test-fixtures/migrations/malformed-partial/state.json.bak +5 -0
- package/test-fixtures/migrations/missing-project/jobs.json +3 -0
- package/test-fixtures/migrations/missing-project/sessions.json +3 -0
- package/test-fixtures/migrations/missing-project/state.json +4 -0
- package/test-fixtures/migrations/multi-user/jobs.json +4 -0
- package/test-fixtures/migrations/multi-user/sessions.json +4 -0
- package/test-fixtures/migrations/multi-user/state.json +6 -0
- package/test-fixtures/provider-event-samples.json +17 -0
- package/test-learning-e2e.js +5 -0
- package/test-memory-mutation-queue.js +191 -0
- package/test-provider-boot-matrix.js +399 -0
- package/test-provider-bootstrap.js +158 -0
- package/test-provider-capabilities.js +232 -0
- package/test-provider-claude.js +206 -0
- package/test-provider-codex.js +228 -0
- package/test-provider-compaction.js +371 -0
- package/test-provider-core-prompt.js +264 -0
- package/test-provider-coupling-audit.js +90 -0
- package/test-provider-dream.js +312 -0
- package/test-provider-enforcer.js +252 -0
- package/test-provider-env-isolation.js +150 -0
- package/test-provider-events.js +141 -0
- package/test-provider-fixture.js +332 -0
- package/test-provider-gateway.js +508 -0
- package/test-provider-language.js +89 -0
- package/test-provider-migration-backup.js +424 -0
- package/test-provider-pack-review.js +436 -0
- package/test-provider-parity-e2e.js +537 -0
- package/test-provider-prompt-parity.js +89 -0
- package/test-provider-recall.js +271 -0
- package/test-provider-registry.js +251 -0
- package/test-provider-resume-parity.js +41 -0
- package/test-provider-run-lifecycle.js +349 -0
- package/test-provider-runner.js +271 -0
- package/test-provider-scheduler.js +689 -0
- package/test-provider-session-commands.js +185 -0
- package/test-provider-session-history.js +205 -0
- package/test-provider-side-chat.js +337 -0
- package/test-provider-state-migration.js +316 -0
- package/test-provider-stream-decoder.js +69 -0
- package/test-provider-subagent.js +228 -0
- package/test-provider-tool-hooks.js +360 -0
- package/test-recall-discoverer.js +1 -0
- package/test-recall-engine.js +3 -3
- package/test-recall-evolution.js +18 -0
- package/test-runner-watchdog-static.js +16 -8
- package/test-single-runtime.js +35 -0
- package/test-telegram-poll-recovery.js +56 -0
- package/test-tool-guard.js +56 -0
- package/test-tools.js +19 -0
- package/test-unified-identity.js +3 -1
- package/test-usage-accounting.js +92 -20
- package/test-utility-provider-policy.js +486 -0
- package/web.js +130 -35
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const crypto = require("crypto");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const { deepFreeze } = require("./run-context");
|
|
7
|
+
const { redactSensitive } = require("./redact");
|
|
8
|
+
|
|
9
|
+
const ACTIVE_TASK_DESCRIPTION_MAX = 320;
|
|
10
|
+
const SIDE_QUESTION_MAX = 8000;
|
|
11
|
+
const SIDE_RESPONSE_MAX = 3500;
|
|
12
|
+
const SIDE_TIMEOUT_MS = 90 * 1000;
|
|
13
|
+
const DEFAULT_PENDING_TTL_MS = 15 * 60 * 1000;
|
|
14
|
+
const DEFAULT_PENDING_LIMIT = 20;
|
|
15
|
+
|
|
16
|
+
function boundedText(value, maxLength) {
|
|
17
|
+
const clean = redactSensitive(String(value || "")).replace(/\s+/g, " ").trim();
|
|
18
|
+
if (clean.length <= maxLength) return clean;
|
|
19
|
+
return `${clean.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readRuntimeMode(configDir) {
|
|
23
|
+
try {
|
|
24
|
+
const mode = fs.readFileSync(path.join(configDir, ".bot-mode"), "utf8").trim().toLowerCase();
|
|
25
|
+
if (mode === "agent") return "agent";
|
|
26
|
+
} catch (_) {}
|
|
27
|
+
return "direct";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function randomToken() {
|
|
31
|
+
return crypto.randomBytes(9).toString("base64url");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function sideRunId(originalRunId, token) {
|
|
35
|
+
const base = String(originalRunId || "run").slice(0, 40);
|
|
36
|
+
return `side-${base}-${token}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createSideChatRequest({
|
|
40
|
+
runContext,
|
|
41
|
+
activeRunContext = null,
|
|
42
|
+
token = randomToken(),
|
|
43
|
+
now = Date.now(),
|
|
44
|
+
} = {}) {
|
|
45
|
+
if (!runContext || typeof runContext !== "object") throw new TypeError("captured runContext is required");
|
|
46
|
+
if (!runContext.canonicalUserId) throw new TypeError("side chat requires a canonical user");
|
|
47
|
+
const activeTaskDescription = boundedText(
|
|
48
|
+
activeRunContext?.userPrompt || activeRunContext?.label || "Open Claudia is working on the active task.",
|
|
49
|
+
ACTIVE_TASK_DESCRIPTION_MAX,
|
|
50
|
+
);
|
|
51
|
+
const question = boundedText(runContext.userPrompt, SIDE_QUESTION_MAX);
|
|
52
|
+
const providerSettings = {
|
|
53
|
+
...(runContext.providerSettings || {}),
|
|
54
|
+
budget: null,
|
|
55
|
+
permissionMode: "plan",
|
|
56
|
+
worktree: false,
|
|
57
|
+
};
|
|
58
|
+
const sideContext = deepFreeze({
|
|
59
|
+
...runContext,
|
|
60
|
+
runId: sideRunId(runContext.runId, token),
|
|
61
|
+
admittedAt: now,
|
|
62
|
+
timeoutMs: SIDE_TIMEOUT_MS,
|
|
63
|
+
purpose: "side-chat",
|
|
64
|
+
providerSettings,
|
|
65
|
+
sessionId: null,
|
|
66
|
+
admittedSessionId: null,
|
|
67
|
+
previousSessionId: null,
|
|
68
|
+
resumeSessionId: null,
|
|
69
|
+
continueSession: false,
|
|
70
|
+
fresh: true,
|
|
71
|
+
skipAutoCompact: true,
|
|
72
|
+
requiredDelivery: false,
|
|
73
|
+
userPrompt: question,
|
|
74
|
+
activeTaskDescription,
|
|
75
|
+
maxTurns: 1,
|
|
76
|
+
toolHookSettings: null,
|
|
77
|
+
imagePaths: [],
|
|
78
|
+
attachments: [],
|
|
79
|
+
lastInputWasVoice: false,
|
|
80
|
+
voiceStream: false,
|
|
81
|
+
});
|
|
82
|
+
return deepFreeze({
|
|
83
|
+
token,
|
|
84
|
+
canonicalUserId: runContext.canonicalUserId,
|
|
85
|
+
replyToMsgId: runContext.replyToMsgId,
|
|
86
|
+
provider: runContext.provider,
|
|
87
|
+
project: runContext.project,
|
|
88
|
+
origin: runContext.origin,
|
|
89
|
+
originalMessage: runContext.userPrompt,
|
|
90
|
+
activeTaskDescription,
|
|
91
|
+
activeRunId: activeRunContext?.runId || null,
|
|
92
|
+
originalRunContext: runContext,
|
|
93
|
+
runContext: sideContext,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function createSideChatPromptBuilder(buildCoreInstructions) {
|
|
98
|
+
const coreBuilder = buildCoreInstructions || ((opts) => require("./system-prompt").buildCoreInstructions(opts));
|
|
99
|
+
return async function buildSideChatPrompt(userPrompt, { runContext, provider } = {}) {
|
|
100
|
+
const activeTask = boundedText(runContext?.activeTaskDescription, ACTIVE_TASK_DESCRIPTION_MAX);
|
|
101
|
+
const projectName = boundedText(runContext?.project?.name || "unknown project", 120);
|
|
102
|
+
const transport = boundedText(runContext?.origin?.transport || "chat", 40);
|
|
103
|
+
const dynamicContext = [
|
|
104
|
+
"## Isolated side chat",
|
|
105
|
+
"A main Open Claudia task is still running. Answer this one side question briefly and conversationally.",
|
|
106
|
+
`Active task: ${activeTask || "Open Claudia is working on the active task."}`,
|
|
107
|
+
`Captured context: project=${projectName}; channel=${transport}; provider=${provider?.id || runContext?.provider || "unknown"}.`,
|
|
108
|
+
"This is a fresh, read-only, ephemeral response. Do not resume the main provider session, use tools, change files or Open Claudia state, schedule work, update memory, or claim the user's message was added to the main task.",
|
|
109
|
+
"The user can explicitly choose “Add to main task” after this response if they want the original message handled by the main conversation.",
|
|
110
|
+
].join("\n");
|
|
111
|
+
return {
|
|
112
|
+
coreInstructions: coreBuilder({ runContext }),
|
|
113
|
+
dynamicContext,
|
|
114
|
+
userPrompt: boundedText(userPrompt, SIDE_QUESTION_MAX),
|
|
115
|
+
transcriptPaths: null,
|
|
116
|
+
recallMetadata: { status: "omitted", omitted: true, reason: "isolated_side_chat" },
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function defaultDeliver(payload, store) {
|
|
122
|
+
const execute = async () => {
|
|
123
|
+
let text = payload.text;
|
|
124
|
+
try {
|
|
125
|
+
if (require("./relationship").isCurrentSpeakerGuarded()) {
|
|
126
|
+
const guard = await require("./enforcer").guardOutboundReply(text);
|
|
127
|
+
if (!guard.allow) text = "I need to check with the owner before I can answer that side question.";
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
text = "I couldn't safely classify that side response, so I held it.";
|
|
131
|
+
}
|
|
132
|
+
return require("./io").send(text, {
|
|
133
|
+
replyTo: payload.replyToMsgId,
|
|
134
|
+
keyboard: {
|
|
135
|
+
inline_keyboard: [[{
|
|
136
|
+
text: payload.action.label,
|
|
137
|
+
callback_data: payload.action.callbackData,
|
|
138
|
+
}]],
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
if (store) return require("./context").chatContext.run(store, execute);
|
|
143
|
+
return execute();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
class SideChatCoordinator {
|
|
147
|
+
constructor(options = {}) {
|
|
148
|
+
this.runSideChat = options.runSideChat || ((request) => require("./runner").runSideChatRequest(request));
|
|
149
|
+
this.enqueueMain = options.enqueueMain || ((request, store, lineage) => require("./runner").enqueueSideChatRequest(request, store, lineage));
|
|
150
|
+
this.deliver = options.deliver || defaultDeliver;
|
|
151
|
+
this.modeReader = options.modeReader || (() => readRuntimeMode(require("./config").CONFIG_DIR));
|
|
152
|
+
this.tokenFactory = options.tokenFactory || randomToken;
|
|
153
|
+
this.now = options.now || Date.now;
|
|
154
|
+
this.pendingTtlMs = options.pendingTtlMs || DEFAULT_PENDING_TTL_MS;
|
|
155
|
+
this.pendingLimit = options.pendingLimit || DEFAULT_PENDING_LIMIT;
|
|
156
|
+
this.activeByUser = new Map();
|
|
157
|
+
this.processByUser = new Map();
|
|
158
|
+
this.cancelledRequests = new Set();
|
|
159
|
+
this.pending = new Map();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
enabled() {
|
|
163
|
+
return this.modeReader() === "agent";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
isActive(canonicalUserId) {
|
|
167
|
+
return this.activeByUser.has(String(canonicalUserId));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
attachProcess(canonicalUserId, proc) {
|
|
171
|
+
if (!proc) return;
|
|
172
|
+
let resolveDone;
|
|
173
|
+
const done = new Promise((resolve) => { resolveDone = resolve; });
|
|
174
|
+
const settle = () => resolveDone();
|
|
175
|
+
proc.once("close", settle);
|
|
176
|
+
proc.once("error", settle);
|
|
177
|
+
this.processByUser.set(String(canonicalUserId), { proc, done });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
detachProcess(canonicalUserId, proc) {
|
|
181
|
+
const key = String(canonicalUserId);
|
|
182
|
+
const entry = this.processByUser.get(key);
|
|
183
|
+
if (!proc || entry?.proc === proc) this.processByUser.delete(key);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async cancel(canonicalUserId) {
|
|
187
|
+
const key = String(canonicalUserId);
|
|
188
|
+
const active = this.activeByUser.get(key);
|
|
189
|
+
if (active) this.cancelledRequests.add(active.token);
|
|
190
|
+
const entry = this.processByUser.get(key);
|
|
191
|
+
if (!entry) return !!active;
|
|
192
|
+
const { proc, done } = entry;
|
|
193
|
+
const { killProcessTree } = require("./process-tree");
|
|
194
|
+
try { killProcessTree(proc.pid, "SIGTERM"); } catch (_) {}
|
|
195
|
+
const termClosed = await Promise.race([
|
|
196
|
+
done.then(() => true),
|
|
197
|
+
new Promise((resolve) => setTimeout(() => resolve(false), 3000)),
|
|
198
|
+
]);
|
|
199
|
+
if (!termClosed) {
|
|
200
|
+
try { killProcessTree(proc.pid, "SIGKILL"); } catch (_) {}
|
|
201
|
+
await Promise.race([
|
|
202
|
+
done,
|
|
203
|
+
new Promise((resolve) => setTimeout(resolve, 2000)),
|
|
204
|
+
]);
|
|
205
|
+
}
|
|
206
|
+
if (this.processByUser.get(key) === entry) this.processByUser.delete(key);
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async cancelAll() {
|
|
211
|
+
const users = new Set([...this.activeByUser.keys(), ...this.processByUser.keys()]);
|
|
212
|
+
const outcomes = await Promise.all([...users].map((canonicalUserId) => this.cancel(canonicalUserId)));
|
|
213
|
+
return outcomes.filter(Boolean).length;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
noteMainResult(runContext, result) {
|
|
217
|
+
if (!runContext?.runId || !result?.ok || !result.sessionId) return;
|
|
218
|
+
for (const entry of this.pending.values()) {
|
|
219
|
+
if (entry.request.activeRunId === runContext.runId) entry.mainSessionId = result.sessionId;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
prune() {
|
|
224
|
+
const now = this.now();
|
|
225
|
+
for (const [token, entry] of this.pending) {
|
|
226
|
+
if (entry.expiresAt <= now) this.pending.delete(token);
|
|
227
|
+
}
|
|
228
|
+
while (this.pending.size >= this.pendingLimit) {
|
|
229
|
+
this.pending.delete(this.pending.keys().next().value);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
makePending(input) {
|
|
234
|
+
this.prune();
|
|
235
|
+
let token = this.tokenFactory();
|
|
236
|
+
while (!token || this.pending.has(token)) token = this.tokenFactory();
|
|
237
|
+
const request = createSideChatRequest({
|
|
238
|
+
runContext: input.runContext,
|
|
239
|
+
activeRunContext: input.activeRunContext,
|
|
240
|
+
token,
|
|
241
|
+
now: this.now(),
|
|
242
|
+
});
|
|
243
|
+
this.pending.set(token, {
|
|
244
|
+
request,
|
|
245
|
+
store: input.store || null,
|
|
246
|
+
mainSessionId: null,
|
|
247
|
+
expiresAt: this.now() + this.pendingTtlMs,
|
|
248
|
+
});
|
|
249
|
+
return request;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
actionFor(request) {
|
|
253
|
+
return {
|
|
254
|
+
label: "Add to main task",
|
|
255
|
+
callbackData: `sc:q:${request.token}`,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async respond(input = {}) {
|
|
260
|
+
const request = this.makePending(input);
|
|
261
|
+
const userKey = String(request.canonicalUserId);
|
|
262
|
+
if (this.activeByUser.has(userKey)) {
|
|
263
|
+
try {
|
|
264
|
+
const delivery = await this.deliver({
|
|
265
|
+
text: "A side reply is already running. Add this message to the main task if it should be handled next.",
|
|
266
|
+
replyToMsgId: request.replyToMsgId,
|
|
267
|
+
action: this.actionFor(request),
|
|
268
|
+
}, input.store || null);
|
|
269
|
+
if (delivery?.ok === false) throw new Error(delivery.error?.message || "side response delivery failed");
|
|
270
|
+
return { ok: true, status: "busy", provider: request.provider, purpose: "side-chat", enqueueToken: request.token };
|
|
271
|
+
} catch (error) {
|
|
272
|
+
return { ok: false, status: "failed", provider: request.provider, purpose: "side-chat", delivery: { ok: false, error }, enqueueToken: request.token };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
this.activeByUser.set(userKey, request);
|
|
277
|
+
let result;
|
|
278
|
+
try {
|
|
279
|
+
result = await this.runSideChat(request);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
result = { ok: false, status: "failed", provider: request.provider, purpose: "side-chat", diagnostic: `Side response failed: ${error.message}`, error };
|
|
282
|
+
}
|
|
283
|
+
try {
|
|
284
|
+
if (this.cancelledRequests.has(request.token)) {
|
|
285
|
+
return { ...result, ok: false, status: "cancelled", enqueueToken: request.token };
|
|
286
|
+
}
|
|
287
|
+
const text = result?.ok
|
|
288
|
+
? boundedText(result.text || "(no response)", SIDE_RESPONSE_MAX)
|
|
289
|
+
: boundedText(result?.diagnostic || result?.error?.message || "The side response failed.", SIDE_RESPONSE_MAX);
|
|
290
|
+
const delivery = await this.deliver({
|
|
291
|
+
text,
|
|
292
|
+
replyToMsgId: request.replyToMsgId,
|
|
293
|
+
action: this.actionFor(request),
|
|
294
|
+
}, input.store || null);
|
|
295
|
+
if (delivery?.ok === false) throw new Error(delivery.error?.message || "side response delivery failed");
|
|
296
|
+
return { ...result, status: result?.status || (result?.ok ? "succeeded" : "failed"), enqueueToken: request.token };
|
|
297
|
+
} catch (deliveryError) {
|
|
298
|
+
return {
|
|
299
|
+
...result,
|
|
300
|
+
ok: false,
|
|
301
|
+
status: "failed",
|
|
302
|
+
delivery: { ok: false, error: deliveryError },
|
|
303
|
+
enqueueToken: request.token,
|
|
304
|
+
};
|
|
305
|
+
} finally {
|
|
306
|
+
this.cancelledRequests.delete(request.token);
|
|
307
|
+
if (this.activeByUser.get(userKey) === request) this.activeByUser.delete(userKey);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async enqueue(token, canonicalUserId) {
|
|
312
|
+
this.prune();
|
|
313
|
+
const entry = this.pending.get(String(token));
|
|
314
|
+
if (!entry) return { ok: false, status: "expired" };
|
|
315
|
+
if (String(entry.request.canonicalUserId) !== String(canonicalUserId)) {
|
|
316
|
+
return { ok: false, status: "forbidden" };
|
|
317
|
+
}
|
|
318
|
+
this.pending.delete(String(token));
|
|
319
|
+
try {
|
|
320
|
+
const queued = await this.enqueueMain(entry.request, entry.store, {
|
|
321
|
+
mainRunId: entry.request.activeRunId,
|
|
322
|
+
mainSessionId: entry.mainSessionId,
|
|
323
|
+
});
|
|
324
|
+
return { ok: true, status: "queued", ...queued };
|
|
325
|
+
} catch (error) {
|
|
326
|
+
return { ok: false, status: "failed", error };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const sideChatCoordinator = new SideChatCoordinator();
|
|
332
|
+
|
|
333
|
+
module.exports = {
|
|
334
|
+
ACTIVE_TASK_DESCRIPTION_MAX,
|
|
335
|
+
DEFAULT_PENDING_LIMIT,
|
|
336
|
+
DEFAULT_PENDING_TTL_MS,
|
|
337
|
+
SIDE_QUESTION_MAX,
|
|
338
|
+
SIDE_RESPONSE_MAX,
|
|
339
|
+
SIDE_TIMEOUT_MS,
|
|
340
|
+
SideChatCoordinator,
|
|
341
|
+
boundedText,
|
|
342
|
+
createSideChatPromptBuilder,
|
|
343
|
+
createSideChatRequest,
|
|
344
|
+
readRuntimeMode,
|
|
345
|
+
sideChatCoordinator,
|
|
346
|
+
};
|