jinn-cli 0.5.3 → 0.7.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/bin/jimmy.js +0 -0
- package/dist/src/cli/status.d.ts.map +1 -1
- package/dist/src/cli/status.js +10 -1
- package/dist/src/cli/status.js.map +1 -1
- package/dist/src/connectors/discord/format.d.ts +3 -0
- package/dist/src/connectors/discord/format.d.ts.map +1 -0
- package/dist/src/connectors/discord/format.js +33 -0
- package/dist/src/connectors/discord/format.js.map +1 -0
- package/dist/src/connectors/discord/index.d.ts +41 -0
- package/dist/src/connectors/discord/index.d.ts.map +1 -0
- package/dist/src/connectors/discord/index.js +302 -0
- package/dist/src/connectors/discord/index.js.map +1 -0
- package/dist/src/connectors/discord/remote.d.ts +33 -0
- package/dist/src/connectors/discord/remote.d.ts.map +1 -0
- package/dist/src/connectors/discord/remote.js +89 -0
- package/dist/src/connectors/discord/remote.js.map +1 -0
- package/dist/src/connectors/discord/threads.d.ts +5 -0
- package/dist/src/connectors/discord/threads.d.ts.map +1 -0
- package/dist/src/connectors/discord/threads.js +22 -0
- package/dist/src/connectors/discord/threads.js.map +1 -0
- package/dist/src/connectors/whatsapp/format.d.ts +2 -0
- package/dist/src/connectors/whatsapp/format.d.ts.map +1 -0
- package/dist/src/connectors/whatsapp/format.js +22 -0
- package/dist/src/connectors/whatsapp/format.js.map +1 -0
- package/dist/src/connectors/whatsapp/index.d.ts +42 -0
- package/dist/src/connectors/whatsapp/index.d.ts.map +1 -0
- package/dist/src/connectors/whatsapp/index.js +279 -0
- package/dist/src/connectors/whatsapp/index.js.map +1 -0
- package/dist/src/engines/claude.d.ts +5 -0
- package/dist/src/engines/claude.d.ts.map +1 -1
- package/dist/src/engines/claude.js +192 -22
- package/dist/src/engines/claude.js.map +1 -1
- package/dist/src/engines/mock.d.ts +16 -0
- package/dist/src/engines/mock.d.ts.map +1 -0
- package/dist/src/engines/mock.js +47 -0
- package/dist/src/engines/mock.js.map +1 -0
- package/dist/src/gateway/__tests__/costs.test.d.ts +2 -0
- package/dist/src/gateway/__tests__/costs.test.d.ts.map +1 -0
- package/dist/src/gateway/__tests__/costs.test.js +32 -0
- package/dist/src/gateway/__tests__/costs.test.js.map +1 -0
- package/dist/src/gateway/api.d.ts +1 -0
- package/dist/src/gateway/api.d.ts.map +1 -1
- package/dist/src/gateway/api.js +816 -18
- package/dist/src/gateway/api.js.map +1 -1
- package/dist/src/gateway/budgets.d.ts +16 -0
- package/dist/src/gateway/budgets.d.ts.map +1 -0
- package/dist/src/gateway/budgets.js +39 -0
- package/dist/src/gateway/budgets.js.map +1 -0
- package/dist/src/gateway/costs.d.ts +19 -0
- package/dist/src/gateway/costs.d.ts.map +1 -0
- package/dist/src/gateway/costs.js +39 -0
- package/dist/src/gateway/costs.js.map +1 -0
- package/dist/src/gateway/goals.d.ts +11 -0
- package/dist/src/gateway/goals.d.ts.map +1 -0
- package/dist/src/gateway/goals.js +72 -0
- package/dist/src/gateway/goals.js.map +1 -0
- package/dist/src/gateway/lifecycle.d.ts.map +1 -1
- package/dist/src/gateway/lifecycle.js +32 -15
- package/dist/src/gateway/lifecycle.js.map +1 -1
- package/dist/src/gateway/project-tagger.d.ts +20 -0
- package/dist/src/gateway/project-tagger.d.ts.map +1 -0
- package/dist/src/gateway/project-tagger.js +55 -0
- package/dist/src/gateway/project-tagger.js.map +1 -0
- package/dist/src/gateway/project-tagger.test.d.ts +2 -0
- package/dist/src/gateway/project-tagger.test.d.ts.map +1 -0
- package/dist/src/gateway/project-tagger.test.js +110 -0
- package/dist/src/gateway/project-tagger.test.js.map +1 -0
- package/dist/src/gateway/projects.d.ts +15 -0
- package/dist/src/gateway/projects.d.ts.map +1 -0
- package/dist/src/gateway/projects.js +48 -0
- package/dist/src/gateway/projects.js.map +1 -0
- package/dist/src/gateway/projects.test.d.ts +2 -0
- package/dist/src/gateway/projects.test.d.ts.map +1 -0
- package/dist/src/gateway/projects.test.js +85 -0
- package/dist/src/gateway/projects.test.js.map +1 -0
- package/dist/src/gateway/server.d.ts.map +1 -1
- package/dist/src/gateway/server.js +90 -2
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/tasks.d.ts +14 -0
- package/dist/src/gateway/tasks.d.ts.map +1 -0
- package/dist/src/gateway/tasks.js +51 -0
- package/dist/src/gateway/tasks.js.map +1 -0
- package/dist/src/gateway/tasks.test.d.ts +2 -0
- package/dist/src/gateway/tasks.test.d.ts.map +1 -0
- package/dist/src/gateway/tasks.test.js +131 -0
- package/dist/src/gateway/tasks.test.js.map +1 -0
- package/dist/src/sessions/callbacks.d.ts +29 -0
- package/dist/src/sessions/callbacks.d.ts.map +1 -0
- package/dist/src/sessions/callbacks.js +110 -0
- package/dist/src/sessions/callbacks.js.map +1 -0
- package/dist/src/sessions/context.js +4 -3
- package/dist/src/sessions/context.js.map +1 -1
- package/dist/src/sessions/engine-override.d.ts +3 -0
- package/dist/src/sessions/engine-override.d.ts.map +1 -0
- package/dist/src/sessions/engine-override.js +42 -0
- package/dist/src/sessions/engine-override.js.map +1 -0
- package/dist/src/sessions/manager.d.ts.map +1 -1
- package/dist/src/sessions/manager.js +410 -40
- package/dist/src/sessions/manager.js.map +1 -1
- package/dist/src/sessions/queue.d.ts +19 -2
- package/dist/src/sessions/queue.d.ts.map +1 -1
- package/dist/src/sessions/queue.js +44 -13
- package/dist/src/sessions/queue.js.map +1 -1
- package/dist/src/sessions/registry.d.ts +21 -1
- package/dist/src/sessions/registry.d.ts.map +1 -1
- package/dist/src/sessions/registry.js +88 -0
- package/dist/src/sessions/registry.js.map +1 -1
- package/dist/src/shared/rateLimit.d.ts +13 -0
- package/dist/src/shared/rateLimit.d.ts.map +1 -0
- package/dist/src/shared/rateLimit.js +30 -0
- package/dist/src/shared/rateLimit.js.map +1 -0
- package/dist/src/shared/types.d.ts +59 -3
- package/dist/src/shared/types.d.ts.map +1 -1
- package/dist/src/shared/usageAwareness.d.ts +10 -0
- package/dist/src/shared/usageAwareness.d.ts.map +1 -0
- package/dist/src/shared/usageAwareness.js +62 -0
- package/dist/src/shared/usageAwareness.js.map +1 -0
- package/dist/web/404.html +1 -1
- package/dist/web/_next/static/1HnqYp-z0yEkKBQwXlWh1/_buildManifest.js +1 -0
- package/dist/web/_next/static/1HnqYp-z0yEkKBQwXlWh1/_ssgManifest.js +1 -0
- package/dist/web/_next/static/chunks/144-548cab85cf18301a.js +1 -0
- package/dist/web/_next/static/chunks/155-592ad81a5c00a38a.js +1 -0
- package/dist/web/_next/static/chunks/192-bd69b67fd03b9f3d.js +1 -0
- package/dist/web/_next/static/chunks/458-85ba1833ffcc2e6c.js +1 -0
- package/dist/web/_next/static/chunks/51-ea7256657692ba90.js +1 -0
- package/dist/web/_next/static/chunks/625-93dc90080662b8f4.js +1 -0
- package/dist/web/_next/static/chunks/6d25620b-d9f90746a7f2178c.js +1 -0
- package/dist/web/_next/static/chunks/7273c211.7ff69b7844551452.js +1 -0
- package/dist/web/_next/static/chunks/743.588b42b673795913.js +1 -0
- package/dist/web/_next/static/chunks/865-b4eb9a132b937321.js +1 -0
- package/dist/web/_next/static/chunks/943.1c6d37432bcad8e8.js +1 -0
- package/dist/web/_next/static/chunks/app/_not-found/page-22b69e9fb96ef3fc.js +1 -0
- package/dist/web/_next/static/chunks/app/chat/page-28df51d87ba7e82b.js +1 -0
- package/dist/web/_next/static/chunks/app/cron/page-4ede68bcdb3e76ca.js +1 -0
- package/dist/web/_next/static/chunks/app/kanban/page-0e1b2a66378b8a6b.js +1 -0
- package/dist/web/_next/static/chunks/app/layout-ab9ba2b24a88513d.js +1 -0
- package/dist/web/_next/static/chunks/app/logs/page-b2ea0e6b92c54706.js +1 -0
- package/dist/web/_next/static/chunks/app/org/page-05c8d33b3bf07d3d.js +1 -0
- package/dist/web/_next/static/chunks/app/page-57dcf41f8ef011a7.js +1 -0
- package/dist/web/_next/static/chunks/app/sessions/page-17f8dfdb90a924a7.js +1 -0
- package/dist/web/_next/static/chunks/app/settings/page-2903a5e9b8b0fc4a.js +1 -0
- package/dist/web/_next/static/chunks/app/skills/page-971feac69b3a1383.js +1 -0
- package/dist/web/_next/static/chunks/framework-33aa4529cc42a877.js +1 -0
- package/dist/web/_next/static/chunks/main-app-72a2d12a170701ef.js +1 -0
- package/dist/web/_next/static/chunks/main-c1296ca5f4362ccd.js +1 -0
- package/dist/web/_next/static/chunks/pages/_app-033e463982aa0ffc.js +1 -0
- package/dist/web/_next/static/chunks/pages/_error-de3b15b767c34e44.js +1 -0
- package/dist/web/_next/static/chunks/webpack-440561fa60ef8a8f.js +1 -0
- package/dist/web/_next/static/css/e79d77e53f783583.css +1 -0
- package/dist/web/chat.html +1 -1
- package/dist/web/chat.txt +12 -12
- package/dist/web/cron.html +1 -1
- package/dist/web/cron.txt +12 -12
- package/dist/web/index.html +1 -1
- package/dist/web/index.txt +12 -12
- package/dist/web/kanban.html +1 -1
- package/dist/web/kanban.txt +12 -12
- package/dist/web/logs.html +2 -2
- package/dist/web/logs.txt +12 -12
- package/dist/web/org.html +1 -1
- package/dist/web/org.txt +12 -12
- package/dist/web/sessions.html +1 -1
- package/dist/web/sessions.txt +16 -19
- package/dist/web/settings.html +1 -1
- package/dist/web/settings.txt +12 -12
- package/dist/web/skills.html +1 -1
- package/dist/web/skills.txt +12 -12
- package/dist/web.bak/404.html +1 -0
- package/dist/web.bak/_next/static/chunks/184-5a617386af9a1dd3.js +1 -0
- package/dist/web.bak/_next/static/chunks/700-ad04a2a5b3c8880f.js +1 -0
- package/dist/web.bak/_next/static/chunks/app/chat/page-36edb6fc1d1e880b.js +1 -0
- package/dist/{web/_next/static/chunks/app/costs/page-7940c2fe7e3dace1.js → web.bak/_next/static/chunks/app/costs/page-6c5cd46a6b3cde21.js} +1 -1
- package/dist/{web/_next/static/chunks/app/cron/page-f81a986689712af7.js → web.bak/_next/static/chunks/app/cron/page-210d9d333a7eed94.js} +1 -1
- package/dist/web.bak/_next/static/chunks/app/kanban/page-c32370bcd6a7c841.js +1 -0
- package/dist/web.bak/_next/static/chunks/app/layout-3ad6b73a0904c24b.js +1 -0
- package/dist/web.bak/_next/static/chunks/app/logs/page-7322a6789e16dca4.js +1 -0
- package/dist/{web/_next/static/chunks/app/org/page-3d44d51e94edb85e.js → web.bak/_next/static/chunks/app/org/page-02263c5702e0fd3e.js} +1 -1
- package/dist/{web/_next/static/chunks/app/page-7ac43789d477a51f.js → web.bak/_next/static/chunks/app/page-b68dcf7b8802c704.js} +1 -1
- package/dist/web.bak/_next/static/chunks/app/sessions/page-bf7ad2fac281c7d6.js +1 -0
- package/dist/web.bak/_next/static/chunks/app/settings/page-d3b89563c42be2e5.js +1 -0
- package/dist/{web/_next/static/chunks/app/skills/page-26b727333df9db45.js → web.bak/_next/static/chunks/app/skills/page-194f4e97f29ab581.js} +1 -1
- package/dist/web.bak/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dist/web.bak/_next/static/css/b809b6af2d241fc8.css +1 -0
- package/dist/web.bak/chat.html +1 -0
- package/dist/web.bak/chat.txt +20 -0
- package/dist/web.bak/costs.html +16 -0
- package/dist/{web → web.bak}/costs.txt +4 -4
- package/dist/web.bak/cron.html +1 -0
- package/dist/web.bak/cron.txt +20 -0
- package/dist/web.bak/index.html +1 -0
- package/dist/web.bak/index.txt +20 -0
- package/dist/web.bak/kanban.html +1 -0
- package/dist/web.bak/kanban.txt +20 -0
- package/dist/web.bak/logs.html +7 -0
- package/dist/web.bak/logs.txt +20 -0
- package/dist/web.bak/org.html +1 -0
- package/dist/web.bak/org.txt +20 -0
- package/dist/web.bak/sessions.html +1 -0
- package/dist/web.bak/sessions.txt +17 -0
- package/dist/web.bak/settings.html +1 -0
- package/dist/web.bak/settings.txt +20 -0
- package/dist/web.bak/skills.html +1 -0
- package/dist/web.bak/skills.txt +20 -0
- package/package.json +10 -3
- package/dist/web/_next/static/chunks/282-4e9c26e9a600c58e.js +0 -1
- package/dist/web/_next/static/chunks/700-a7cbf54fe1fbf4bc.js +0 -1
- package/dist/web/_next/static/chunks/app/chat/page-757fcd211d059cb7.js +0 -1
- package/dist/web/_next/static/chunks/app/kanban/page-6ab8586b063ca3ac.js +0 -1
- package/dist/web/_next/static/chunks/app/layout-c24e2d25774ff71a.js +0 -1
- package/dist/web/_next/static/chunks/app/logs/page-388b787cb847ca97.js +0 -1
- package/dist/web/_next/static/chunks/app/sessions/page-18757fcd067b7e9d.js +0 -1
- package/dist/web/_next/static/chunks/app/settings/page-f62176848534f90a.js +0 -1
- package/dist/web/_next/static/css/bd612b1ca9b40306.css +0 -1
- package/dist/web/app-build-manifest.json +0 -3
- package/dist/web/build-manifest.json +0 -17
- package/dist/web/costs.html +0 -16
- package/dist/web/react-loadable-manifest.json +0 -1
- package/dist/web/server/app-paths-manifest.json +0 -1
- package/dist/web/server/interception-route-rewrite-manifest.js +0 -1
- package/dist/web/server/middleware-build-manifest.js +0 -19
- package/dist/web/server/middleware-manifest.json +0 -6
- package/dist/web/server/middleware-react-loadable-manifest.js +0 -1
- package/dist/web/server/next-font-manifest.js +0 -1
- package/dist/web/server/next-font-manifest.json +0 -1
- package/dist/web/server/pages-manifest.json +0 -1
- package/dist/web/server/server-reference-manifest.js +0 -1
- package/dist/web/server/server-reference-manifest.json +0 -5
- package/dist/web/static/development/_buildManifest.js +0 -1
- package/dist/web/static/development/_ssgManifest.js +0 -1
- package/dist/web/types/cache-life.d.ts +0 -141
- package/dist/web/types/package.json +0 -1
- package/dist/web/types/routes.d.ts +0 -66
- package/dist/web/types/validator.ts +0 -142
- /package/dist/{web/_next/static/J4YFiPdzNcFHieP2FIPPe → web.bak/_next/static/QrKxazgwMrykF2yLwDvUM}/_buildManifest.js +0 -0
- /package/dist/{web/_next/static/J4YFiPdzNcFHieP2FIPPe → web.bak/_next/static/QrKxazgwMrykF2yLwDvUM}/_ssgManifest.js +0 -0
- /package/dist/{web → web.bak}/_next/static/chunks/198-fd91406a158c5c25.js +0 -0
- /package/dist/{web → web.bak}/_next/static/chunks/517.62389e8d3c929c43.js +0 -0
- /package/dist/{web → web.bak}/_next/static/chunks/534-17c49c944e0d0fe1.js +0 -0
- /package/dist/{web → web.bak}/_next/static/chunks/573-070537ec2452d03e.js +0 -0
- /package/dist/{web → web.bak}/_next/static/chunks/590-2c34156c7417317e.js +0 -0
- /package/dist/{web → web.bak}/_next/static/chunks/7273c211.06e3b6021d90b73f.js +0 -0
- /package/dist/{web → web.bak}/_next/static/chunks/743-5bb03adbb0e4ddec.js +0 -0
- /package/dist/{web → web.bak}/_next/static/chunks/874.97d5a27895061057.js +0 -0
- /package/dist/{web → web.bak}/_next/static/chunks/8e6518bb-c26e82767f1faf66.js +0 -0
- /package/dist/{web → web.bak}/_next/static/chunks/app/_not-found/page-7d43a486b7014af3.js +0 -0
- /package/dist/{web → web.bak}/_next/static/chunks/framework-077b27ad7787463c.js +0 -0
- /package/dist/{web → web.bak}/_next/static/chunks/main-app-0c65af8a0fd99888.js +0 -0
- /package/dist/{web → web.bak}/_next/static/chunks/main-f1c74cefd4965abf.js +0 -0
- /package/dist/{web → web.bak}/_next/static/chunks/pages/_app-77a85fe7d6bca671.js +0 -0
- /package/dist/{web → web.bak}/_next/static/chunks/pages/_error-68febf4b34900064.js +0 -0
- /package/dist/{web → web.bak}/_next/static/chunks/webpack-0f39b7e91dce9791.js +0 -0
|
@@ -1,13 +1,72 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import { accumulateSessionCost, createSession, deleteSession, getSessionBySessionKey, insertMessage, updateSession, } from "./registry.js";
|
|
2
|
+
import { accumulateSessionCost, createSession, deleteSession, getSessionBySessionKey, getMessages, insertMessage, updateSession, } from "./registry.js";
|
|
3
|
+
import { notifyParentSession, notifyRateLimited, notifyRateLimitResumed, notifyDiscordChannel } from "./callbacks.js";
|
|
3
4
|
import { buildContext } from "./context.js";
|
|
4
5
|
import { SessionQueue } from "./queue.js";
|
|
5
6
|
import { JINN_HOME } from "../shared/paths.js";
|
|
6
7
|
import { logger } from "../shared/logger.js";
|
|
7
8
|
import { resolveEffort } from "../shared/effort.js";
|
|
9
|
+
import { computeNextRetryDelayMs, computeRateLimitDeadlineMs, detectRateLimit } from "../shared/rateLimit.js";
|
|
10
|
+
import { getClaudeExpectedResetAt, isLikelyNearClaudeUsageLimit, recordClaudeRateLimit } from "../shared/usageAwareness.js";
|
|
8
11
|
import { loadJobs } from "../cron/jobs.js";
|
|
9
12
|
import { setCronJobEnabled, triggerCronJob } from "../cron/scheduler.js";
|
|
13
|
+
import { checkBudget } from "../gateway/budgets.js";
|
|
10
14
|
import { resolveMcpServers, writeMcpConfigFile, cleanupMcpConfigFile } from "../mcp/resolver.js";
|
|
15
|
+
function maybeRevertEngineOverride(session) {
|
|
16
|
+
const meta = (session.transportMeta || {});
|
|
17
|
+
const override = meta["engineOverride"];
|
|
18
|
+
if (!override)
|
|
19
|
+
return session;
|
|
20
|
+
const originalEngine = typeof override.originalEngine === "string" ? override.originalEngine : null;
|
|
21
|
+
const originalEngineSessionId = typeof override.originalEngineSessionId === "string"
|
|
22
|
+
? override.originalEngineSessionId
|
|
23
|
+
: null;
|
|
24
|
+
const syncSince = typeof override.syncSince === "string" ? override.syncSince : null;
|
|
25
|
+
const untilIso = typeof override.until === "string" ? override.until : null;
|
|
26
|
+
if (!originalEngine || !untilIso)
|
|
27
|
+
return session;
|
|
28
|
+
const until = new Date(untilIso);
|
|
29
|
+
if (Number.isNaN(until.getTime()))
|
|
30
|
+
return session;
|
|
31
|
+
if (until.getTime() > Date.now())
|
|
32
|
+
return session;
|
|
33
|
+
const engineSessionsRaw = meta["engineSessions"];
|
|
34
|
+
const engineSessions = (engineSessionsRaw && typeof engineSessionsRaw === "object" && !Array.isArray(engineSessionsRaw))
|
|
35
|
+
? { ...engineSessionsRaw }
|
|
36
|
+
: {};
|
|
37
|
+
// Preserve the current engine session ID under its engine key
|
|
38
|
+
if (session.engine && session.engineSessionId) {
|
|
39
|
+
engineSessions[String(session.engine)] = session.engineSessionId;
|
|
40
|
+
}
|
|
41
|
+
const restoredSessionId = originalEngineSessionId
|
|
42
|
+
?? (typeof engineSessions[originalEngine] === "string" ? engineSessions[originalEngine] : null);
|
|
43
|
+
const nextMeta = { ...meta, engineSessions };
|
|
44
|
+
if (originalEngine === "claude" && syncSince && session.engine !== "claude") {
|
|
45
|
+
nextMeta["claudeSyncSince"] = syncSince;
|
|
46
|
+
}
|
|
47
|
+
delete nextMeta["engineOverride"];
|
|
48
|
+
return updateSession(session.id, {
|
|
49
|
+
engine: originalEngine,
|
|
50
|
+
engineSessionId: restoredSessionId,
|
|
51
|
+
transportMeta: nextMeta,
|
|
52
|
+
lastError: null,
|
|
53
|
+
}) ?? session;
|
|
54
|
+
}
|
|
55
|
+
function mergeTransportMeta(existing, incoming) {
|
|
56
|
+
const baseExisting = (existing && typeof existing === "object" && !Array.isArray(existing))
|
|
57
|
+
? existing
|
|
58
|
+
: {};
|
|
59
|
+
const baseIncoming = (incoming && typeof incoming === "object" && !Array.isArray(incoming))
|
|
60
|
+
? incoming
|
|
61
|
+
: {};
|
|
62
|
+
const merged = { ...baseExisting, ...baseIncoming };
|
|
63
|
+
// Preserve Jinn internal keys from being overwritten by transport adapters.
|
|
64
|
+
for (const key of ["engineOverride", "engineSessions", "claudeSyncSince"]) {
|
|
65
|
+
if (baseExisting[key] !== undefined)
|
|
66
|
+
merged[key] = baseExisting[key];
|
|
67
|
+
}
|
|
68
|
+
return merged;
|
|
69
|
+
}
|
|
11
70
|
export class SessionManager {
|
|
12
71
|
config;
|
|
13
72
|
engines;
|
|
@@ -52,18 +111,27 @@ export class SessionManager {
|
|
|
52
111
|
(opts.employee ? ` (employee: ${opts.employee.name})` : ""));
|
|
53
112
|
}
|
|
54
113
|
else {
|
|
114
|
+
const mergedMeta = mergeTransportMeta(session.transportMeta, msg.transportMeta);
|
|
55
115
|
session = updateSession(session.id, {
|
|
56
116
|
replyContext: msg.replyContext,
|
|
57
117
|
messageId: msg.messageId ?? null,
|
|
58
|
-
transportMeta:
|
|
118
|
+
transportMeta: mergedMeta,
|
|
59
119
|
...(opts.model ? { model: opts.model } : {}),
|
|
60
120
|
}) ?? session;
|
|
61
121
|
}
|
|
122
|
+
session = maybeRevertEngineOverride(session);
|
|
62
123
|
const target = connector.reconstructTarget(msg.replyContext);
|
|
63
124
|
target.messageTs ??= msg.messageId;
|
|
64
125
|
const attachmentPaths = msg.attachments
|
|
65
126
|
.map((attachment) => attachment.localPath)
|
|
66
127
|
.filter((filePath) => !!filePath);
|
|
128
|
+
if (session.status === "waiting") {
|
|
129
|
+
const expectedResetAt = getClaudeExpectedResetAt();
|
|
130
|
+
const resumeText = expectedResetAt
|
|
131
|
+
? expectedResetAt.toLocaleString("en-GB", { weekday: "short", day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit" })
|
|
132
|
+
: null;
|
|
133
|
+
await connector.replyMessage(target, `⏳ Still paused due to Claude usage limit${resumeText ? ` (resets ${resumeText})` : ""}. I queued this message and will respond automatically.`).catch(() => { });
|
|
134
|
+
}
|
|
67
135
|
if (session.status === "running" && this.queue.isRunning(msg.sessionKey) && connector.getCapabilities().reactions) {
|
|
68
136
|
await connector.addReaction(target, "clock1").catch(() => { });
|
|
69
137
|
}
|
|
@@ -93,7 +161,7 @@ export class SessionManager {
|
|
|
93
161
|
status: "running",
|
|
94
162
|
replyContext: msg.replyContext,
|
|
95
163
|
messageId: msg.messageId ?? null,
|
|
96
|
-
transportMeta: msg.transportMeta
|
|
164
|
+
transportMeta: mergeTransportMeta(session.transportMeta, msg.transportMeta),
|
|
97
165
|
lastActivity: new Date().toISOString(),
|
|
98
166
|
});
|
|
99
167
|
// Resolve MCP config before try block so it's accessible in catch for cleanup
|
|
@@ -120,75 +188,363 @@ export class SessionManager {
|
|
|
120
188
|
}
|
|
121
189
|
}
|
|
122
190
|
const effortLevel = resolveEffort(engineConfig, session, employee);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
191
|
+
// If we previously switched to GPT while Claude was rate-limited, inject a sync transcript
|
|
192
|
+
// so Claude can resume with full context when it comes back online.
|
|
193
|
+
const syncSinceIso = session.transportMeta?.claudeSyncSince;
|
|
194
|
+
let promptToRun = msg.text;
|
|
195
|
+
const syncSinceMs = typeof syncSinceIso === "string" ? new Date(syncSinceIso).getTime() : NaN;
|
|
196
|
+
const syncRequested = session.engine === "claude" && typeof syncSinceIso === "string" && Number.isFinite(syncSinceMs);
|
|
197
|
+
if (syncRequested) {
|
|
198
|
+
const sinceMessages = getMessages(session.id)
|
|
199
|
+
.filter((m) => (m.role === "user" || m.role === "assistant") && m.timestamp >= syncSinceMs)
|
|
200
|
+
.map((m) => `${m.role.toUpperCase()}: ${m.content}`);
|
|
201
|
+
const transcript = sinceMessages.slice(-20).join("\n\n");
|
|
202
|
+
promptToRun =
|
|
203
|
+
`We temporarily switched to GPT due to a Claude usage limit. Sync your context with this transcript (most recent last), then respond to the last USER message.\n\n${transcript}`;
|
|
204
|
+
}
|
|
205
|
+
// Budget enforcement — check BEFORE engine.run()
|
|
206
|
+
if (session.employee) {
|
|
207
|
+
const budgetConfig = this.config.budgets?.employees;
|
|
208
|
+
if (budgetConfig && session.employee in budgetConfig) {
|
|
209
|
+
const budgetStatus = checkBudget(session.employee, budgetConfig);
|
|
210
|
+
if (budgetStatus === 'paused') {
|
|
211
|
+
logger.warn(`Session ${session.id} blocked: employee "${session.employee}" has exceeded their budget`);
|
|
212
|
+
const pausedMsg = `Budget limit exceeded for employee "${session.employee}". Session blocked.`;
|
|
213
|
+
updateSession(session.id, {
|
|
214
|
+
status: 'error',
|
|
215
|
+
lastActivity: new Date().toISOString(),
|
|
216
|
+
lastError: pausedMsg,
|
|
217
|
+
});
|
|
218
|
+
if (decorateMessages && connector.setTypingStatus) {
|
|
219
|
+
await connector.setTypingStatus(target.channel, threadTs, '').catch(() => { });
|
|
220
|
+
}
|
|
221
|
+
await connector.replyMessage(target, `⛔ ${pausedMsg}`).catch(() => { });
|
|
222
|
+
if (decorateMessages && capabilities.reactions) {
|
|
223
|
+
await connector.removeReaction(target, 'eyes').catch(() => { });
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
138
228
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
229
|
+
// Heuristic preflight warning: Claude usage limits don't expose a precise "remaining" budget.
|
|
230
|
+
// If we've hit the limit recently and this looks like a heavy turn, warn before we spend time.
|
|
231
|
+
if (decorateMessages && session.engine === "claude" && isLikelyNearClaudeUsageLimit()) {
|
|
232
|
+
const modelName = (session.model ?? engineConfig.model ?? "").toLowerCase();
|
|
233
|
+
const heavyEffort = ["high", "xhigh", "max"].includes((effortLevel || "").toLowerCase());
|
|
234
|
+
const heavyModel = modelName.includes("opus");
|
|
235
|
+
const looksBig = attachments.length > 0 || msg.text.length > 6000;
|
|
236
|
+
if ((heavyEffort || heavyModel) && looksBig) {
|
|
237
|
+
const expectedResetAt = getClaudeExpectedResetAt();
|
|
238
|
+
const resumeText = expectedResetAt
|
|
239
|
+
? expectedResetAt.toLocaleString("en-GB", { weekday: "short", day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit" })
|
|
240
|
+
: null;
|
|
241
|
+
await connector.replyMessage(target, `⚠️ Heads up: Claude usage limits were hit recently, and this looks like a bigger task. If you're near the limit, it may pause${resumeText ? ` until ~${resumeText}` : ""}.`).catch(() => { });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const result = await engine.run({
|
|
245
|
+
prompt: promptToRun,
|
|
246
|
+
resumeSessionId: session.engineSessionId ?? undefined,
|
|
247
|
+
systemPrompt,
|
|
248
|
+
cwd: JINN_HOME,
|
|
249
|
+
bin: engineConfig.bin,
|
|
250
|
+
model: session.model ?? engineConfig.model,
|
|
251
|
+
effortLevel,
|
|
252
|
+
cliFlags: employee?.cliFlags,
|
|
253
|
+
mcpConfigPath,
|
|
254
|
+
attachments: attachments.length > 0 ? attachments : undefined,
|
|
255
|
+
sessionId: session.id,
|
|
256
|
+
});
|
|
257
|
+
const wasInterrupted = result.error?.startsWith("Interrupted");
|
|
258
|
+
// Detect rate limit / usage limit errors and auto-retry
|
|
259
|
+
const rateLimit = !wasInterrupted ? detectRateLimit(result) : { limited: false };
|
|
260
|
+
if (rateLimit.limited) {
|
|
261
|
+
recordClaudeRateLimit(rateLimit.resetsAt);
|
|
262
|
+
const strategy = this.config.sessions?.rateLimitStrategy ?? "fallback";
|
|
263
|
+
// Optional fallback: switch to GPT (Codex) while Claude resets
|
|
264
|
+
if (session.engine === "claude" && strategy === "fallback") {
|
|
265
|
+
const fallbackName = this.config.sessions?.fallbackEngine ?? "codex";
|
|
266
|
+
const fallbackEngine = this.engines.get(fallbackName);
|
|
267
|
+
if (fallbackEngine) {
|
|
268
|
+
const { resumeAt } = computeNextRetryDelayMs(rateLimit.resetsAt);
|
|
269
|
+
const until = resumeAt ?? new Date(Date.now() + 6 * 60 * 60_000);
|
|
270
|
+
const syncSince = new Date().toISOString();
|
|
271
|
+
const resumeText = resumeAt
|
|
272
|
+
? resumeAt.toLocaleString("en-GB", { weekday: "short", day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit" })
|
|
273
|
+
: null;
|
|
274
|
+
notifyDiscordChannel(`⚠️ Claude usage limit reached. Session ${session.id}${session.employee ? ` (${session.employee})` : ""} switching to GPT.`);
|
|
275
|
+
await connector.replyMessage(target, `⚠️ Claude usage limit reached${resumeText ? `. Resets ${resumeText}` : ""}. Switching to GPT for now.`).catch(() => { });
|
|
276
|
+
const nextMeta = { ...(session.transportMeta || {}) };
|
|
277
|
+
const engineSessionsRaw = nextMeta.engineSessions;
|
|
278
|
+
const engineSessions = (engineSessionsRaw && typeof engineSessionsRaw === "object" && !Array.isArray(engineSessionsRaw))
|
|
279
|
+
? { ...engineSessionsRaw }
|
|
280
|
+
: {};
|
|
281
|
+
if (session.engineSessionId) {
|
|
282
|
+
engineSessions.claude = session.engineSessionId;
|
|
283
|
+
}
|
|
284
|
+
nextMeta.engineSessions = engineSessions;
|
|
285
|
+
nextMeta.engineOverride = { originalEngine: "claude", originalEngineSessionId: session.engineSessionId, until: until.toISOString(), syncSince };
|
|
286
|
+
updateSession(session.id, {
|
|
287
|
+
engine: fallbackName,
|
|
288
|
+
// Keep Claude engine_session_id intact for later restore; Codex will return its own thread id.
|
|
289
|
+
transportMeta: nextMeta,
|
|
290
|
+
status: "running",
|
|
291
|
+
lastActivity: new Date().toISOString(),
|
|
292
|
+
lastError: resumeAt
|
|
293
|
+
? `Claude usage limit — using GPT until ${resumeAt.toISOString()}`
|
|
294
|
+
: "Claude usage limit — using GPT temporarily",
|
|
295
|
+
});
|
|
296
|
+
const fallbackConfig = this.config.engines.codex;
|
|
297
|
+
const fallbackEffort = resolveEffort(fallbackConfig, session, employee);
|
|
298
|
+
const codexResume = typeof engineSessions.codex === "string" ? engineSessions.codex : undefined;
|
|
299
|
+
const history = getMessages(session.id)
|
|
300
|
+
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
301
|
+
.map((m) => `${m.role.toUpperCase()}: ${m.content}`);
|
|
302
|
+
const historyText = history.slice(-12).join("\n\n");
|
|
303
|
+
const fallbackPrompt = codexResume
|
|
304
|
+
? msg.text
|
|
305
|
+
: `Continue this conversation and respond to the last USER message.\n\nConversation so far:\n\n${historyText}`;
|
|
306
|
+
const fallbackResult = await fallbackEngine.run({
|
|
307
|
+
prompt: fallbackPrompt,
|
|
308
|
+
resumeSessionId: codexResume,
|
|
309
|
+
systemPrompt,
|
|
310
|
+
cwd: JINN_HOME,
|
|
311
|
+
bin: fallbackConfig.bin,
|
|
312
|
+
model: session.model ?? fallbackConfig.model,
|
|
313
|
+
effortLevel: fallbackEffort,
|
|
314
|
+
cliFlags: employee?.cliFlags,
|
|
315
|
+
attachments: attachments.length > 0 ? attachments : undefined,
|
|
316
|
+
sessionId: session.id,
|
|
317
|
+
});
|
|
318
|
+
const fallbackText = fallbackResult.result?.trim()
|
|
319
|
+
? fallbackResult.result
|
|
320
|
+
: fallbackResult.error || "(No response from engine)";
|
|
321
|
+
insertMessage(session.id, "assistant", fallbackText);
|
|
322
|
+
if (fallbackResult.cost || fallbackResult.numTurns) {
|
|
323
|
+
accumulateSessionCost(session.id, fallbackResult.cost ?? 0, fallbackResult.numTurns ?? 1);
|
|
324
|
+
}
|
|
325
|
+
// Persist Codex thread id so future fallbacks can resume it
|
|
326
|
+
const nextEngineSessions = { ...engineSessions };
|
|
327
|
+
if (fallbackResult.sessionId) {
|
|
328
|
+
nextEngineSessions.codex = fallbackResult.sessionId;
|
|
329
|
+
}
|
|
330
|
+
const metaAfter = { ...(getSessionBySessionKey(msg.sessionKey)?.transportMeta || nextMeta) };
|
|
331
|
+
metaAfter.engineSessions = nextEngineSessions;
|
|
332
|
+
updateSession(session.id, { transportMeta: metaAfter });
|
|
333
|
+
if (decorateMessages && connector.setTypingStatus) {
|
|
334
|
+
await connector.setTypingStatus(target.channel, threadTs, "").catch(() => { });
|
|
335
|
+
}
|
|
336
|
+
await connector.replyMessage(target, fallbackText).catch(() => { });
|
|
337
|
+
if (decorateMessages && capabilities.reactions) {
|
|
338
|
+
await connector.removeReaction(target, "eyes").catch(() => { });
|
|
339
|
+
}
|
|
340
|
+
const updated = updateSession(session.id, {
|
|
341
|
+
engineSessionId: fallbackResult.sessionId,
|
|
342
|
+
status: fallbackResult.error ? "error" : "idle",
|
|
343
|
+
replyContext: msg.replyContext,
|
|
344
|
+
messageId: msg.messageId ?? null,
|
|
345
|
+
transportMeta: mergeTransportMeta(getSessionBySessionKey(msg.sessionKey)?.transportMeta ?? session.transportMeta, msg.transportMeta),
|
|
346
|
+
lastActivity: new Date().toISOString(),
|
|
347
|
+
lastError: fallbackResult.error ?? null,
|
|
348
|
+
});
|
|
349
|
+
if (updated) {
|
|
350
|
+
notifyParentSession(updated, { result: fallbackResult.result, error: fallbackResult.error ?? null, cost: fallbackResult.cost, durationMs: fallbackResult.durationMs });
|
|
351
|
+
}
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const waitEmoji = "hourglass_flowing_sand";
|
|
356
|
+
const { delayMs, resumeAt } = computeNextRetryDelayMs(rateLimit.resetsAt);
|
|
357
|
+
const deadlineMs = computeRateLimitDeadlineMs(rateLimit.resetsAt, rateLimit.resetsAt ? 30 * 60_000 : 6 * 60 * 60_000);
|
|
358
|
+
const resumeText = resumeAt
|
|
359
|
+
? resumeAt.toLocaleString("en-GB", { weekday: "short", day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit" })
|
|
360
|
+
: null;
|
|
361
|
+
logger.info(`Session ${session.id} hit Claude usage limit — will auto-retry ${resumeAt ? `at ${resumeAt.toISOString()}` : `in ${Math.round(delayMs / 1000)}s`}`);
|
|
362
|
+
// Send hardcoded Discord notification — does not depend on LLM
|
|
363
|
+
notifyDiscordChannel(`⚠️ Claude usage limit reached. Session ${session.id}${session.employee ? ` (${session.employee})` : ""} paused${resumeText ? ` until ${resumeText}` : ""}.`);
|
|
364
|
+
// Clear "thinking" UI and show waiting state
|
|
365
|
+
if (decorateMessages && connector.setTypingStatus) {
|
|
366
|
+
await connector.setTypingStatus(target.channel, threadTs, "").catch(() => { });
|
|
367
|
+
}
|
|
368
|
+
if (decorateMessages && capabilities.reactions) {
|
|
369
|
+
await connector.removeReaction(target, "eyes").catch(() => { });
|
|
370
|
+
await connector.addReaction(target, waitEmoji).catch(() => { });
|
|
371
|
+
}
|
|
372
|
+
const waitingSession = updateSession(session.id, {
|
|
373
|
+
...(result.sessionId?.trim() ? { engineSessionId: result.sessionId } : {}),
|
|
374
|
+
status: "waiting",
|
|
375
|
+
lastActivity: new Date().toISOString(),
|
|
376
|
+
lastError: resumeAt
|
|
377
|
+
? `Claude usage limit — resumes ${resumeAt.toISOString()}`
|
|
378
|
+
: "Claude usage limit — waiting for reset",
|
|
379
|
+
}) ?? session;
|
|
380
|
+
notifyRateLimited(waitingSession, resumeAt
|
|
381
|
+
? resumeAt.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" })
|
|
382
|
+
: undefined);
|
|
383
|
+
await connector.replyMessage(target, `⏳ Claude usage limit reached${resumeText ? `. Resets ${resumeText}` : ""} — I'll continue automatically.`).catch(() => { });
|
|
384
|
+
// Keep lastActivity fresh while waiting (UI / status endpoints)
|
|
385
|
+
const heartbeat = setInterval(() => {
|
|
386
|
+
updateSession(session.id, { status: "waiting", lastActivity: new Date().toISOString() });
|
|
387
|
+
}, 60_000);
|
|
388
|
+
try {
|
|
389
|
+
let attempt = 0;
|
|
390
|
+
let nextDelayMs = delayMs;
|
|
391
|
+
while (Date.now() < deadlineMs) {
|
|
392
|
+
await new Promise(r => setTimeout(r, nextDelayMs));
|
|
393
|
+
attempt++;
|
|
394
|
+
// Check if session was stopped while waiting
|
|
395
|
+
const currentSession = getSessionBySessionKey(msg.sessionKey);
|
|
396
|
+
if (!currentSession || currentSession.status === "error") {
|
|
397
|
+
logger.info(`Session ${session.id} stopped while waiting for usage reset`);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
// Show active processing again
|
|
401
|
+
if (decorateMessages && connector.setTypingStatus) {
|
|
402
|
+
await connector.setTypingStatus(target.channel, threadTs, "is thinking...").catch(() => { });
|
|
403
|
+
}
|
|
404
|
+
if (decorateMessages && capabilities.reactions) {
|
|
405
|
+
await connector.removeReaction(target, waitEmoji).catch(() => { });
|
|
406
|
+
await connector.addReaction(target, "eyes").catch(() => { });
|
|
407
|
+
}
|
|
408
|
+
logger.info(`Session ${session.id} retrying after usage limit (attempt ${attempt})`);
|
|
409
|
+
const retryResult = await engine.run({
|
|
410
|
+
prompt: msg.text,
|
|
411
|
+
resumeSessionId: currentSession.engineSessionId ?? undefined,
|
|
412
|
+
systemPrompt,
|
|
413
|
+
cwd: JINN_HOME,
|
|
414
|
+
bin: engineConfig.bin,
|
|
415
|
+
model: currentSession.model ?? engineConfig.model,
|
|
416
|
+
effortLevel,
|
|
417
|
+
cliFlags: employee?.cliFlags,
|
|
418
|
+
mcpConfigPath,
|
|
419
|
+
attachments: attachments.length > 0 ? attachments : undefined,
|
|
420
|
+
sessionId: session.id,
|
|
421
|
+
});
|
|
422
|
+
const retryInterrupted = retryResult.error?.startsWith("Interrupted");
|
|
423
|
+
const retryRateLimit = !retryInterrupted ? detectRateLimit(retryResult) : { limited: false };
|
|
424
|
+
if (retryRateLimit.limited) {
|
|
425
|
+
recordClaudeRateLimit(retryRateLimit.resetsAt);
|
|
426
|
+
logger.info(`Session ${session.id} still rate limited (attempt ${attempt})`);
|
|
427
|
+
const next = computeNextRetryDelayMs(retryRateLimit.resetsAt);
|
|
428
|
+
nextDelayMs = next.delayMs;
|
|
429
|
+
// Return to waiting UI state
|
|
430
|
+
if (decorateMessages && connector.setTypingStatus) {
|
|
431
|
+
await connector.setTypingStatus(target.channel, threadTs, "").catch(() => { });
|
|
432
|
+
}
|
|
433
|
+
if (decorateMessages && capabilities.reactions) {
|
|
434
|
+
await connector.removeReaction(target, "eyes").catch(() => { });
|
|
435
|
+
await connector.addReaction(target, waitEmoji).catch(() => { });
|
|
436
|
+
}
|
|
437
|
+
updateSession(session.id, {
|
|
438
|
+
...(retryResult.sessionId?.trim() ? { engineSessionId: retryResult.sessionId } : {}),
|
|
439
|
+
status: "waiting",
|
|
440
|
+
lastActivity: new Date().toISOString(),
|
|
441
|
+
lastError: next.resumeAt
|
|
442
|
+
? `Claude usage limit — resumes ${next.resumeAt.toISOString()}`
|
|
443
|
+
: "Claude usage limit — waiting for reset",
|
|
444
|
+
});
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
// Success or different error — handle normally
|
|
448
|
+
const retryText = retryResult.result?.trim()
|
|
449
|
+
? retryResult.result
|
|
450
|
+
: retryResult.error || "(No response from engine)";
|
|
451
|
+
insertMessage(session.id, "assistant", retryText);
|
|
452
|
+
if (retryResult.cost || retryResult.numTurns) {
|
|
453
|
+
accumulateSessionCost(session.id, retryResult.cost ?? 0, retryResult.numTurns ?? 1);
|
|
454
|
+
}
|
|
455
|
+
// Clear typing indicator & reactions
|
|
456
|
+
if (decorateMessages && connector.setTypingStatus) {
|
|
457
|
+
await connector.setTypingStatus(target.channel, threadTs, "").catch(() => { });
|
|
458
|
+
}
|
|
459
|
+
if (decorateMessages && capabilities.reactions) {
|
|
460
|
+
await connector.removeReaction(target, "eyes").catch(() => { });
|
|
461
|
+
await connector.removeReaction(target, waitEmoji).catch(() => { });
|
|
462
|
+
}
|
|
463
|
+
await connector.replyMessage(target, retryText).catch(() => { });
|
|
464
|
+
const retryUpdated = updateSession(session.id, {
|
|
465
|
+
...(retryResult.sessionId?.trim() ? { engineSessionId: retryResult.sessionId } : {}),
|
|
466
|
+
status: retryResult.error ? "error" : "idle",
|
|
467
|
+
replyContext: msg.replyContext,
|
|
468
|
+
messageId: msg.messageId ?? null,
|
|
469
|
+
transportMeta: msg.transportMeta ?? null,
|
|
470
|
+
lastActivity: new Date().toISOString(),
|
|
471
|
+
lastError: retryResult.error ?? null,
|
|
472
|
+
});
|
|
473
|
+
if (retryUpdated) {
|
|
474
|
+
notifyRateLimitResumed(retryUpdated);
|
|
475
|
+
notifyDiscordChannel(`✅ Claude usage limit cleared. Session ${session.id}${session.employee ? ` (${session.employee})` : ""} resumed.`);
|
|
476
|
+
notifyParentSession(retryUpdated, { result: retryResult.result, error: retryResult.error ?? null, cost: retryResult.cost, durationMs: retryResult.durationMs });
|
|
477
|
+
}
|
|
478
|
+
logger.info(`Session ${session.id} resumed after usage reset`);
|
|
479
|
+
return;
|
|
144
480
|
}
|
|
145
|
-
|
|
146
|
-
|
|
481
|
+
// Exhausted waiting window
|
|
482
|
+
notifyDiscordChannel(`❌ Claude usage limit did not clear in time. Session ${session.id}${session.employee ? ` (${session.employee})` : ""} has been stopped.`);
|
|
483
|
+
await connector.replyMessage(target, "Usage limit didn't reset in time. Please try again later.").catch(() => { });
|
|
484
|
+
updateSession(session.id, {
|
|
485
|
+
status: "error",
|
|
486
|
+
lastActivity: new Date().toISOString(),
|
|
487
|
+
lastError: "Claude usage limit did not clear in time",
|
|
488
|
+
});
|
|
489
|
+
// Clear reactions on failure
|
|
490
|
+
if (decorateMessages && capabilities.reactions) {
|
|
491
|
+
await connector.removeReaction(target, "eyes").catch(() => { });
|
|
492
|
+
await connector.removeReaction(target, waitEmoji).catch(() => { });
|
|
147
493
|
}
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
finally {
|
|
497
|
+
clearInterval(heartbeat);
|
|
148
498
|
}
|
|
149
499
|
}
|
|
150
500
|
const responseText = result.result?.trim()
|
|
151
501
|
? result.result
|
|
152
502
|
: result.error || "(No response from engine)";
|
|
153
503
|
insertMessage(session.id, "assistant", responseText);
|
|
154
|
-
// Clean up temp MCP config
|
|
155
|
-
if (mcpConfigPath)
|
|
156
|
-
cleanupMcpConfigFile(session.id);
|
|
157
|
-
// Track cost
|
|
158
504
|
if (result.cost || result.numTurns) {
|
|
159
505
|
accumulateSessionCost(session.id, result.cost ?? 0, result.numTurns ?? 1);
|
|
160
506
|
}
|
|
161
|
-
// Clear typing indicator before sending response
|
|
162
507
|
if (decorateMessages && connector.setTypingStatus) {
|
|
163
508
|
await connector.setTypingStatus(target.channel, threadTs, "").catch(() => { });
|
|
164
509
|
}
|
|
165
|
-
|
|
510
|
+
if (!wasInterrupted) {
|
|
511
|
+
await connector.replyMessage(target, responseText);
|
|
512
|
+
}
|
|
166
513
|
if (decorateMessages && capabilities.reactions) {
|
|
167
514
|
await connector.removeReaction(target, "eyes").catch(() => { });
|
|
168
515
|
}
|
|
169
|
-
updateSession(session.id, {
|
|
170
|
-
engineSessionId: result.sessionId,
|
|
171
|
-
status: result.error ? "error" : "idle",
|
|
516
|
+
const updatedSession = updateSession(session.id, {
|
|
517
|
+
...(result.sessionId?.trim() ? { engineSessionId: result.sessionId } : {}),
|
|
518
|
+
status: wasInterrupted ? "idle" : (result.error ? "error" : "idle"),
|
|
172
519
|
replyContext: msg.replyContext,
|
|
173
520
|
messageId: msg.messageId ?? null,
|
|
174
|
-
transportMeta:
|
|
521
|
+
transportMeta: (() => {
|
|
522
|
+
const merged = mergeTransportMeta(getSessionBySessionKey(msg.sessionKey)?.transportMeta ?? session.transportMeta, msg.transportMeta);
|
|
523
|
+
if (syncRequested && !rateLimit.limited && !wasInterrupted) {
|
|
524
|
+
delete merged["claudeSyncSince"];
|
|
525
|
+
}
|
|
526
|
+
return merged;
|
|
527
|
+
})(),
|
|
175
528
|
lastActivity: new Date().toISOString(),
|
|
176
|
-
lastError: result.error ?? null,
|
|
529
|
+
lastError: wasInterrupted ? null : (result.error ?? null),
|
|
177
530
|
});
|
|
531
|
+
if (updatedSession) {
|
|
532
|
+
notifyParentSession(updatedSession, { result: result.result, error: wasInterrupted ? null : (result.error ?? null), cost: result.cost, durationMs: result.durationMs });
|
|
533
|
+
}
|
|
178
534
|
logger.info(`Session ${session.id} completed in ${result.durationMs ?? 0}ms` +
|
|
179
535
|
(result.cost ? ` ($${result.cost.toFixed(4)})` : ""));
|
|
180
536
|
}
|
|
181
537
|
catch (err) {
|
|
182
538
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
183
539
|
logger.error(`Session ${session.id} error: ${errMsg}`);
|
|
184
|
-
|
|
185
|
-
if (mcpConfigPath)
|
|
186
|
-
cleanupMcpConfigFile(session.id);
|
|
187
|
-
updateSession(session.id, {
|
|
540
|
+
const erroredSession = updateSession(session.id, {
|
|
188
541
|
status: "error",
|
|
189
542
|
lastActivity: new Date().toISOString(),
|
|
190
543
|
lastError: errMsg,
|
|
191
544
|
});
|
|
545
|
+
if (erroredSession) {
|
|
546
|
+
notifyParentSession(erroredSession, { error: errMsg });
|
|
547
|
+
}
|
|
192
548
|
// Clear typing indicator on error
|
|
193
549
|
if (decorateMessages && connector.setTypingStatus) {
|
|
194
550
|
await connector.setTypingStatus(target.channel, threadTs, "").catch(() => { });
|
|
@@ -196,7 +552,21 @@ export class SessionManager {
|
|
|
196
552
|
await connector.replyMessage(target, `Error: ${errMsg}`).catch(() => { });
|
|
197
553
|
if (decorateMessages && capabilities.reactions) {
|
|
198
554
|
await connector.removeReaction(target, "eyes").catch(() => { });
|
|
555
|
+
await connector.removeReaction(target, "hourglass_flowing_sand").catch(() => { });
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
finally {
|
|
559
|
+
// Clean up temp attachment files downloaded from Slack
|
|
560
|
+
for (const filePath of attachments) {
|
|
561
|
+
try {
|
|
562
|
+
fs.rmSync(filePath, { force: true });
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
// Ignore cleanup errors — best effort
|
|
566
|
+
}
|
|
199
567
|
}
|
|
568
|
+
if (mcpConfigPath)
|
|
569
|
+
cleanupMcpConfigFile(session.id);
|
|
200
570
|
}
|
|
201
571
|
}
|
|
202
572
|
async handleCommand(msg, connector) {
|