botmux 2.64.0 → 2.65.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/README.en.md +1 -1
- package/README.md +3 -3
- package/dist/adapters/backend/herdr-backend.d.ts +8 -1
- package/dist/adapters/backend/herdr-backend.d.ts.map +1 -1
- package/dist/adapters/backend/herdr-backend.js +15 -2
- package/dist/adapters/backend/herdr-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-backend.d.ts +17 -1
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +25 -4
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/backend/types.d.ts +14 -0
- package/dist/adapters/backend/types.d.ts.map +1 -1
- package/dist/adapters/backend/types.js.map +1 -1
- package/dist/adapters/backend/zellij-backend.d.ts +12 -1
- package/dist/adapters/backend/zellij-backend.d.ts.map +1 -1
- package/dist/adapters/backend/zellij-backend.js +25 -8
- package/dist/adapters/backend/zellij-backend.js.map +1 -1
- package/dist/bot-registry.d.ts +39 -0
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +30 -0
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli/send-dispatch.d.ts +23 -0
- package/dist/cli/send-dispatch.d.ts.map +1 -0
- package/dist/cli/send-dispatch.js +23 -0
- package/dist/cli/send-dispatch.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +141 -58
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +8 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -6
- package/dist/config.js.map +1 -1
- package/dist/core/ask-broker.d.ts +33 -0
- package/dist/core/ask-broker.d.ts.map +1 -1
- package/dist/core/ask-broker.js +58 -0
- package/dist/core/ask-broker.js.map +1 -1
- package/dist/core/ask-hook/claude-code.d.ts.map +1 -1
- package/dist/core/ask-hook/claude-code.js +15 -9
- package/dist/core/ask-hook/claude-code.js.map +1 -1
- package/dist/core/ask-hook/codex.d.ts.map +1 -1
- package/dist/core/ask-hook/codex.js +2 -1
- package/dist/core/ask-hook/codex.js.map +1 -1
- package/dist/core/ask-hook/opencode.d.ts.map +1 -1
- package/dist/core/ask-hook/opencode.js +9 -6
- package/dist/core/ask-hook/opencode.js.map +1 -1
- package/dist/core/ask-hook/types.d.ts +3 -1
- package/dist/core/ask-hook/types.d.ts.map +1 -1
- package/dist/core/ask-types.d.ts +13 -6
- package/dist/core/ask-types.d.ts.map +1 -1
- package/dist/core/ask-types.js.map +1 -1
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +255 -4
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/daemon-heartbeat.d.ts +15 -0
- package/dist/core/daemon-heartbeat.d.ts.map +1 -0
- package/dist/core/daemon-heartbeat.js +83 -0
- package/dist/core/daemon-heartbeat.js.map +1 -0
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +80 -33
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/dispatch.d.ts +1 -23
- package/dist/core/dispatch.d.ts.map +1 -1
- package/dist/core/dispatch.js +1 -17
- package/dist/core/dispatch.js.map +1 -1
- package/dist/core/idle-worker-sweeper.d.ts +13 -0
- package/dist/core/idle-worker-sweeper.d.ts.map +1 -0
- package/dist/core/idle-worker-sweeper.js +42 -0
- package/dist/core/idle-worker-sweeper.js.map +1 -0
- package/dist/core/maintenance-schedule.d.ts +34 -0
- package/dist/core/maintenance-schedule.d.ts.map +1 -0
- package/dist/core/maintenance-schedule.js +72 -0
- package/dist/core/maintenance-schedule.js.map +1 -0
- package/dist/core/maintenance.d.ts +43 -0
- package/dist/core/maintenance.d.ts.map +1 -0
- package/dist/core/maintenance.js +160 -0
- package/dist/core/maintenance.js.map +1 -0
- package/dist/core/reply-target.d.ts +23 -0
- package/dist/core/reply-target.d.ts.map +1 -0
- package/dist/core/reply-target.js +47 -0
- package/dist/core/reply-target.js.map +1 -0
- package/dist/core/restart-report.d.ts +49 -0
- package/dist/core/restart-report.d.ts.map +1 -0
- package/dist/core/restart-report.js +98 -0
- package/dist/core/restart-report.js.map +1 -0
- package/dist/core/scheduler.d.ts.map +1 -1
- package/dist/core/scheduler.js +20 -0
- package/dist/core/scheduler.js.map +1 -1
- package/dist/core/session-manager.d.ts +26 -10
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +104 -26
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-marker.d.ts +13 -0
- package/dist/core/session-marker.d.ts.map +1 -0
- package/dist/core/session-marker.js +55 -0
- package/dist/core/session-marker.js.map +1 -0
- package/dist/core/types.d.ts +20 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/worker-budget.d.ts +19 -0
- package/dist/core/worker-budget.d.ts.map +1 -0
- package/dist/core/worker-budget.js +50 -0
- package/dist/core/worker-budget.js.map +1 -0
- package/dist/core/worker-pool.d.ts +5 -2
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +105 -12
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +243 -38
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults.js +118 -0
- package/dist/dashboard/web/bot-defaults.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +44 -0
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/settings.d.ts.map +1 -1
- package/dist/dashboard/web/settings.js +84 -13
- package/dist/dashboard/web/settings.js.map +1 -1
- package/dist/dashboard-web/app.js +568 -503
- package/dist/dashboard.js +87 -7
- package/dist/dashboard.js.map +1 -1
- package/dist/global-config.d.ts +46 -0
- package/dist/global-config.d.ts.map +1 -1
- package/dist/global-config.js +115 -0
- package/dist/global-config.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +72 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +72 -1
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/ask-card.d.ts.map +1 -1
- package/dist/im/lark/ask-card.js +15 -1
- package/dist/im/lark/ask-card.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts +17 -0
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +146 -0
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +119 -4
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/client.d.ts +3 -2
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +28 -2
- package/dist/im/lark/client.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts +7 -17
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +170 -50
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/message-parser.d.ts +1 -0
- package/dist/im/lark/message-parser.d.ts.map +1 -1
- package/dist/im/lark/message-parser.js +1 -0
- package/dist/im/lark/message-parser.js.map +1 -1
- package/dist/im/lark/reply-mode-command.d.ts +2 -0
- package/dist/im/lark/reply-mode-command.d.ts.map +1 -0
- package/dist/im/lark/reply-mode-command.js +102 -0
- package/dist/im/lark/reply-mode-command.js.map +1 -0
- package/dist/services/bot-config-store.d.ts +144 -0
- package/dist/services/bot-config-store.d.ts.map +1 -0
- package/dist/services/bot-config-store.js +241 -0
- package/dist/services/bot-config-store.js.map +1 -0
- package/dist/services/card-prefs-store.d.ts +5 -0
- package/dist/services/card-prefs-store.d.ts.map +1 -1
- package/dist/services/card-prefs-store.js +47 -0
- package/dist/services/card-prefs-store.js.map +1 -1
- package/dist/services/chat-reply-mode-store.d.ts +28 -0
- package/dist/services/chat-reply-mode-store.d.ts.map +1 -0
- package/dist/services/chat-reply-mode-store.js +115 -0
- package/dist/services/chat-reply-mode-store.js.map +1 -0
- package/dist/services/hook-runner.d.ts +43 -0
- package/dist/services/hook-runner.d.ts.map +1 -0
- package/dist/services/hook-runner.js +394 -0
- package/dist/services/hook-runner.js.map +1 -0
- package/dist/services/restart-intent-store.d.ts +26 -0
- package/dist/services/restart-intent-store.d.ts.map +1 -0
- package/dist/services/restart-intent-store.js +84 -0
- package/dist/services/restart-intent-store.js.map +1 -0
- package/dist/services/session-lifecycle-hooks.d.ts +10 -0
- package/dist/services/session-lifecycle-hooks.d.ts.map +1 -0
- package/dist/services/session-lifecycle-hooks.js +66 -0
- package/dist/services/session-lifecycle-hooks.js.map +1 -0
- package/dist/services/session-store.d.ts +6 -0
- package/dist/services/session-store.d.ts.map +1 -1
- package/dist/services/session-store.js +25 -0
- package/dist/services/session-store.js.map +1 -1
- package/dist/skills/definitions.d.ts.map +1 -1
- package/dist/skills/definitions.js +28 -3
- package/dist/skills/definitions.js.map +1 -1
- package/dist/types.d.ts +33 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/install-info.d.ts +13 -0
- package/dist/utils/install-info.d.ts.map +1 -0
- package/dist/utils/install-info.js +56 -0
- package/dist/utils/install-info.js.map +1 -0
- package/dist/utils/listen-with-probe.d.ts +26 -0
- package/dist/utils/listen-with-probe.d.ts.map +1 -0
- package/dist/utils/listen-with-probe.js +64 -0
- package/dist/utils/listen-with-probe.js.map +1 -0
- package/dist/utils/web-terminal-listen.d.ts +30 -0
- package/dist/utils/web-terminal-listen.d.ts.map +1 -0
- package/dist/utils/web-terminal-listen.js +81 -0
- package/dist/utils/web-terminal-listen.js.map +1 -0
- package/dist/worker.js +71 -44
- package/dist/worker.js.map +1 -1
- package/dist/workflows/definition.d.ts +30 -30
- package/dist/workflows/events/payloads.d.ts +4 -4
- package/dist/workflows/events/schema.d.ts +156 -156
- package/package.json +1 -1
package/dist/core/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAiBA;;6DAE6D;AAC7D,MAAM,UAAU,iBAAiB,CAAC,EAAc;IAC9C,IAAI,EAAE,CAAC,WAAW,KAAK,YAAY,IAAI,EAAE,CAAC,WAAW,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC,WAAW,CAAC;IAC1F,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC/C,CAAC;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAiBA;;6DAE6D;AAC7D,MAAM,UAAU,iBAAiB,CAAC,EAAc;IAC9C,IAAI,EAAE,CAAC,WAAW,KAAK,YAAY,IAAI,EAAE,CAAC,WAAW,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC,WAAW,CAAC;IAC1F,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC/C,CAAC;AA6GD;;;;;uDAKuD;AACvD,MAAM,UAAU,UAAU,CAAC,QAAgB,EAAE,SAAiB;IAC5D,OAAO,GAAG,QAAQ,KAAK,SAAS,EAAE,CAAC;AACrC,CAAC;AAED;;+BAE+B;AAC/B,MAAM,UAAU,eAAe,CAAC,EAAiB;IAC/C,OAAO,EAAE,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC;AACpE,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { WorkerConfig } from '../global-config.js';
|
|
2
|
+
export declare const MIN_AUTO_MAX_LIVE_WORKERS = 4;
|
|
3
|
+
export declare const MAX_AUTO_MAX_LIVE_WORKERS = 32;
|
|
4
|
+
export declare const DEFAULT_IDLE_SUSPEND_MS: number;
|
|
5
|
+
export interface WorkerResources {
|
|
6
|
+
cpuCount: number;
|
|
7
|
+
memoryBytes: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ResolvedWorkerBudget {
|
|
10
|
+
maxLiveWorkers: number;
|
|
11
|
+
idleSuspendMs: number;
|
|
12
|
+
autoMaxLiveWorkers: number;
|
|
13
|
+
maxLiveWorkersSource: 'auto' | 'config';
|
|
14
|
+
idleSuspendMsSource: 'default' | 'config';
|
|
15
|
+
}
|
|
16
|
+
export declare function detectWorkerResources(): WorkerResources;
|
|
17
|
+
export declare function autoMaxLiveWorkers(resources?: WorkerResources): number;
|
|
18
|
+
export declare function resolveWorkerBudget(workerConfig?: WorkerConfig, resources?: WorkerResources): ResolvedWorkerBudget;
|
|
19
|
+
//# sourceMappingURL=worker-budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-budget.d.ts","sourceRoot":"","sources":["../../src/core/worker-budget.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAC3C,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAC5C,eAAO,MAAM,uBAAuB,QAAc,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,GAAG,QAAQ,CAAC;IACxC,mBAAmB,EAAE,SAAS,GAAG,QAAQ,CAAC;CAC3C;AAwBD,wBAAgB,qBAAqB,IAAI,eAAe,CAMvD;AAED,wBAAgB,kBAAkB,CAAC,SAAS,GAAE,eAAyC,GAAG,MAAM,CAI/F;AAED,wBAAgB,mBAAmB,CACjC,YAAY,CAAC,EAAE,YAAY,EAC3B,SAAS,GAAE,eAAyC,GACnD,oBAAoB,CAStB"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { availableParallelism, totalmem } from 'node:os';
|
|
3
|
+
export const MIN_AUTO_MAX_LIVE_WORKERS = 4;
|
|
4
|
+
export const MAX_AUTO_MAX_LIVE_WORKERS = 32;
|
|
5
|
+
export const DEFAULT_IDLE_SUSPEND_MS = 30 * 60_000;
|
|
6
|
+
function clamp(n, min, max) {
|
|
7
|
+
return Math.max(min, Math.min(max, n));
|
|
8
|
+
}
|
|
9
|
+
function readMemoryLimitFile(path) {
|
|
10
|
+
if (!existsSync(path))
|
|
11
|
+
return undefined;
|
|
12
|
+
const raw = readFileSync(path, 'utf-8').trim();
|
|
13
|
+
if (!raw || raw === 'max')
|
|
14
|
+
return undefined;
|
|
15
|
+
const n = Number(raw);
|
|
16
|
+
return Number.isFinite(n) && n > 0 ? n : undefined;
|
|
17
|
+
}
|
|
18
|
+
function cgroupMemoryLimitBytes(hostMemoryBytes) {
|
|
19
|
+
const limits = [
|
|
20
|
+
readMemoryLimitFile('/sys/fs/cgroup/memory.max'),
|
|
21
|
+
readMemoryLimitFile('/sys/fs/cgroup/memory/memory.limit_in_bytes'),
|
|
22
|
+
].filter((n) => n !== undefined);
|
|
23
|
+
if (limits.length === 0)
|
|
24
|
+
return undefined;
|
|
25
|
+
const limit = Math.min(...limits);
|
|
26
|
+
return limit < hostMemoryBytes ? limit : undefined;
|
|
27
|
+
}
|
|
28
|
+
export function detectWorkerResources() {
|
|
29
|
+
const hostMemoryBytes = totalmem();
|
|
30
|
+
return {
|
|
31
|
+
cpuCount: Math.max(1, availableParallelism?.() ?? 1),
|
|
32
|
+
memoryBytes: cgroupMemoryLimitBytes(hostMemoryBytes) ?? hostMemoryBytes,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function autoMaxLiveWorkers(resources = detectWorkerResources()) {
|
|
36
|
+
const cpuBudget = Math.max(1, resources.cpuCount) * 2;
|
|
37
|
+
const memoryBudget = Math.max(1, Math.round(resources.memoryBytes / 1024 ** 3));
|
|
38
|
+
return clamp(Math.min(cpuBudget, memoryBudget), MIN_AUTO_MAX_LIVE_WORKERS, MAX_AUTO_MAX_LIVE_WORKERS);
|
|
39
|
+
}
|
|
40
|
+
export function resolveWorkerBudget(workerConfig, resources = detectWorkerResources()) {
|
|
41
|
+
const auto = autoMaxLiveWorkers(resources);
|
|
42
|
+
return {
|
|
43
|
+
maxLiveWorkers: workerConfig?.maxLiveWorkers ?? auto,
|
|
44
|
+
idleSuspendMs: workerConfig?.idleSuspendMs ?? DEFAULT_IDLE_SUSPEND_MS,
|
|
45
|
+
autoMaxLiveWorkers: auto,
|
|
46
|
+
maxLiveWorkersSource: workerConfig?.maxLiveWorkers === undefined ? 'auto' : 'config',
|
|
47
|
+
idleSuspendMsSource: workerConfig?.idleSuspendMs === undefined ? 'default' : 'config',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=worker-budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-budget.js","sourceRoot":"","sources":["../../src/core/worker-budget.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { type ChildProcess } from 'node:child_process';
|
|
6
6
|
import type { CliId } from '../adapters/cli/types.js';
|
|
7
|
+
import type { BackendType } from '../adapters/backend/types.js';
|
|
7
8
|
import type { WorkerToDaemon, Session } from '../types.js';
|
|
8
9
|
import { type DaemonSession } from './types.js';
|
|
9
10
|
import { type CliUsageLimitState } from '../utils/cli-usage-limit.js';
|
|
10
11
|
export interface WorkerPoolCallbacks {
|
|
11
|
-
sessionReply: (rootId: string, content: string, msgType?: string, larkAppId?: string) => Promise<string>;
|
|
12
|
+
sessionReply: (rootId: string, content: string, msgType?: string, larkAppId?: string, turnId?: string) => Promise<string>;
|
|
12
13
|
getSessionWorkingDir: (ds?: DaemonSession) => string;
|
|
13
14
|
getActiveCount: () => number;
|
|
14
15
|
/** Close a stale session (message withdrawn, etc.) */
|
|
@@ -107,7 +108,7 @@ export declare function recallFrozenCards(ds: DaemonSession): void;
|
|
|
107
108
|
* Note: this does NOT itself flip `ds.streamingCardForced` — the caller sets
|
|
108
109
|
* that so the card keeps live-patching afterwards even when the bot opted out.
|
|
109
110
|
*/
|
|
110
|
-
export declare function postFreshStreamingCard(ds: DaemonSession, sessionReply: (rootId: string, content: string, msgType?: string, larkAppId?: string) => Promise<string>): Promise<boolean>;
|
|
111
|
+
export declare function postFreshStreamingCard(ds: DaemonSession, sessionReply: (rootId: string, content: string, msgType?: string, larkAppId?: string, turnId?: string) => Promise<string>): Promise<boolean>;
|
|
111
112
|
/**
|
|
112
113
|
* Audience for a private `/card`: the bot's `allowedUsers` (the canOperate set —
|
|
113
114
|
* owner & co-owners), deduped, `ou_` only. Talk-only grants (`globalGrants` /
|
|
@@ -189,6 +190,8 @@ export declare function ensureCliEnv(cliId: CliId, cliPathOverride?: string): vo
|
|
|
189
190
|
* clobbers other keys; any failure is swallowed so it can't block spawn. */
|
|
190
191
|
export declare function ensureClaudeFolderTrust(workingDir: string, stateJsonPath?: string): void;
|
|
191
192
|
export declare function killWorker(ds: DaemonSession): void;
|
|
193
|
+
export declare function isSuspendableBackendType(backendType: BackendType | undefined): boolean;
|
|
194
|
+
export declare function suspendWorker(ds: DaemonSession, reason?: string): boolean;
|
|
192
195
|
/**
|
|
193
196
|
* Idempotent close: kill worker if alive, mark Session status='closed' + closedAt,
|
|
194
197
|
* publish session.exited (if a live worker was killed) and session.update
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-pool.d.ts","sourceRoot":"","sources":["../../src/core/worker-pool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"worker-pool.d.ts","sourceRoot":"","sources":["../../src/core/worker-pool.ts"],"names":[],"mappings":"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"}
|
package/dist/core/worker-pool.js
CHANGED
|
@@ -32,6 +32,7 @@ import { dashboardEventBus } from './dashboard-events.js';
|
|
|
32
32
|
import { composeRowFromActive } from './dashboard-rows.js';
|
|
33
33
|
import { publishAttentionPatch } from './session-activity.js';
|
|
34
34
|
import { knownBotOpenIdsFromCrossRef } from '../utils/bot-routing.js';
|
|
35
|
+
import { emitSessionLifecycleHook, emitSessionStateTransitionHook } from '../services/session-lifecycle-hooks.js';
|
|
35
36
|
import { sessionKey, sessionAnchorId } from './types.js';
|
|
36
37
|
import { claimPendingResponseCard, COMPLETED_REACTION_EMOJI_TYPE, markPendingResponseCardPatchedIfCurrent, syncPendingResponseState } from './pending-response.js';
|
|
37
38
|
import { buildTerminalUrl } from './terminal-url.js';
|
|
@@ -375,7 +376,7 @@ export async function postFreshStreamingCard(ds, sessionReply) {
|
|
|
375
376
|
const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, title, ds.lastScreenContent ?? '', status, effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, !!ds.adoptedFrom, false, localeForBot(ds.larkAppId), cardUsageLimit(ds), writableTerminalLinkFor(ds));
|
|
376
377
|
ds.streamCardId = CARD_POSTING_SENTINEL;
|
|
377
378
|
try {
|
|
378
|
-
ds.streamCardId = await sessionReply(sessionAnchorId(ds), cardJson, 'interactive', ds.larkAppId);
|
|
379
|
+
ds.streamCardId = await sessionReply(sessionAnchorId(ds), cardJson, 'interactive', ds.larkAppId, ds.currentReplyTarget?.turnId);
|
|
379
380
|
// This card is now the live one for the current turn. Clear the new-turn
|
|
380
381
|
// pending flag so the next screen_update PATCHes it instead of POSTing a
|
|
381
382
|
// duplicate (the gate above only suppresses cards when disabled+unforced;
|
|
@@ -804,6 +805,41 @@ export function killWorker(ds) {
|
|
|
804
805
|
ds.workerPort = null;
|
|
805
806
|
ds.workerToken = null;
|
|
806
807
|
}
|
|
808
|
+
export function isSuspendableBackendType(backendType) {
|
|
809
|
+
return backendType === 'tmux' || backendType === 'herdr' || backendType === 'zellij';
|
|
810
|
+
}
|
|
811
|
+
export function suspendWorker(ds, reason = 'suspended_idle') {
|
|
812
|
+
if (!ds.worker || ds.worker.killed)
|
|
813
|
+
return false;
|
|
814
|
+
if (!isSuspendableBackendType(ds.initConfig?.backendType))
|
|
815
|
+
return false;
|
|
816
|
+
const w = ds.worker;
|
|
817
|
+
try {
|
|
818
|
+
w.send({ type: 'suspend' });
|
|
819
|
+
}
|
|
820
|
+
catch {
|
|
821
|
+
try {
|
|
822
|
+
w.kill('SIGTERM');
|
|
823
|
+
}
|
|
824
|
+
catch { /* already gone */ }
|
|
825
|
+
}
|
|
826
|
+
armWorkerKillBackstop(w, tag(ds));
|
|
827
|
+
ds.worker = null;
|
|
828
|
+
ds.workerPort = null;
|
|
829
|
+
ds.workerToken = null;
|
|
830
|
+
ds.session.webPort = undefined;
|
|
831
|
+
sessionStore.updateSessionPid(ds.session.sessionId, null);
|
|
832
|
+
sessionStore.updateSession(ds.session);
|
|
833
|
+
if (!ds.exitEventEmitted) {
|
|
834
|
+
ds.exitEventEmitted = true;
|
|
835
|
+
dashboardEventBus.publish({
|
|
836
|
+
type: 'session.exited',
|
|
837
|
+
body: { sessionId: ds.session.sessionId, reason },
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
logger.info(`[${tag(ds)}] Worker suspended (${reason}); session remains active`);
|
|
841
|
+
return true;
|
|
842
|
+
}
|
|
807
843
|
function armWorkerKillBackstop(w, label) {
|
|
808
844
|
const sigterm = setTimeout(() => {
|
|
809
845
|
if (w.exitCode === null && w.signalCode === null) {
|
|
@@ -851,6 +887,7 @@ export async function closeSession(sessionId) {
|
|
|
851
887
|
type: 'session.exited',
|
|
852
888
|
body: { sessionId, reason: 'dashboard_close' },
|
|
853
889
|
});
|
|
890
|
+
emitSessionLifecycleHook(ds, 'session.exit', { reason: 'dashboard_close' });
|
|
854
891
|
}
|
|
855
892
|
}
|
|
856
893
|
// Persistence path — load → mark closed → save (delegated to sessionStore).
|
|
@@ -1204,6 +1241,7 @@ export function forkWorker(ds, prompt, resume = false) {
|
|
|
1204
1241
|
botName: bot.botName,
|
|
1205
1242
|
botOpenId: bot.botOpenId,
|
|
1206
1243
|
locale: botLocale(botCfg),
|
|
1244
|
+
turnId: ds.currentReplyTarget?.turnId,
|
|
1207
1245
|
};
|
|
1208
1246
|
worker.send(initMsg);
|
|
1209
1247
|
ds.initConfig = initMsg;
|
|
@@ -1230,11 +1268,16 @@ export function forkWorker(ds, prompt, resume = false) {
|
|
|
1230
1268
|
type: 'session.spawned',
|
|
1231
1269
|
body: { session: composeRowFromActive(ds) },
|
|
1232
1270
|
});
|
|
1271
|
+
emitSessionLifecycleHook(ds, 'session.start', {
|
|
1272
|
+
reason: resume ? 'resume' : 'worker_spawn',
|
|
1273
|
+
pid: worker.pid ?? null,
|
|
1274
|
+
});
|
|
1233
1275
|
}
|
|
1234
1276
|
// ─── Shared worker IPC handler ──────────────────────────────────────────────
|
|
1235
1277
|
function setupWorkerHandlers(ds, worker) {
|
|
1236
1278
|
const cb = requireCallbacks();
|
|
1237
1279
|
const t = tag(ds);
|
|
1280
|
+
const scopedReply = (content, msgType, turnId) => cb.sessionReply(sessionAnchorId(ds), content, msgType, ds.larkAppId, turnId);
|
|
1238
1281
|
const bot = getBot(ds.larkAppId);
|
|
1239
1282
|
const botCfg = bot.config;
|
|
1240
1283
|
const loc = botLocale(botCfg);
|
|
@@ -1274,6 +1317,15 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
1274
1317
|
logger.info(`[${t}] Streaming card disabled for this bot — skipping card post`);
|
|
1275
1318
|
break;
|
|
1276
1319
|
}
|
|
1320
|
+
// Restart recovery: stay silent in the group. The session was restored
|
|
1321
|
+
// after a daemon restart; don't auto-post/patch a streaming card here.
|
|
1322
|
+
// The owner gets a private DM summary instead, and the surviving card
|
|
1323
|
+
// (if any) is left untouched. The next real user turn clears this flag
|
|
1324
|
+
// (rememberLastCliInput) and the normal card flow resumes.
|
|
1325
|
+
if (ds.suppressRecoveryCard) {
|
|
1326
|
+
logger.info(`[${t}] Restored session — suppressing recovery streaming card (silent restart)`);
|
|
1327
|
+
break;
|
|
1328
|
+
}
|
|
1277
1329
|
// If a previous streaming card survived (e.g. daemon restart), try to
|
|
1278
1330
|
// PATCH it with the new "starting" state instead of POSTing a fresh card.
|
|
1279
1331
|
// ds.streamCardPending forces a new card (e.g. mid-session repo switch
|
|
@@ -1332,7 +1384,7 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
1332
1384
|
// before the first screen_update lands. Fresh worker spawn
|
|
1333
1385
|
// has lastScreenContent undefined → '' (unchanged).
|
|
1334
1386
|
ds.lastScreenContent ?? '', initStatus, effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, isAdopt, showTakeover, loc, initStatus === 'limited' ? ds.usageLimit : undefined, writableTerminalLinkFor(ds));
|
|
1335
|
-
ds.streamCardId = await
|
|
1387
|
+
ds.streamCardId = await scopedReply(streamCardJson, 'interactive', msg.turnId);
|
|
1336
1388
|
// This card IS the current turn's live card — clear the new-turn flag
|
|
1337
1389
|
// so subsequent screen_updates PATCH it (starting → working) instead of
|
|
1338
1390
|
// POSTing a second card. Without this, a re-fork that happens while
|
|
@@ -1366,7 +1418,7 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
1366
1418
|
// Fallback: send static session card
|
|
1367
1419
|
try {
|
|
1368
1420
|
const cardJson = buildSessionCard(ds.session.sessionId, sessionAnchorId(ds), readOnlyUrl, ds.session.title || getCliDisplayName(effectiveCliId), effectiveCliId, undefined, !!ds.adoptedFrom, loc);
|
|
1369
|
-
await
|
|
1421
|
+
await scopedReply(cardJson, 'interactive', msg.turnId);
|
|
1370
1422
|
}
|
|
1371
1423
|
catch (fallbackErr) {
|
|
1372
1424
|
if (fallbackErr instanceof MessageWithdrawnError) {
|
|
@@ -1412,11 +1464,20 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
1412
1464
|
},
|
|
1413
1465
|
},
|
|
1414
1466
|
});
|
|
1467
|
+
emitSessionStateTransitionHook(ds, prevStatus, ds.lastScreenStatus, {
|
|
1468
|
+
source: 'screen_update',
|
|
1469
|
+
content: msg.content,
|
|
1470
|
+
});
|
|
1415
1471
|
}
|
|
1416
1472
|
// Bot opted out of the streaming card — dashboard SSE above already got
|
|
1417
1473
|
// the status patch; just don't touch any Lark card.
|
|
1418
1474
|
if (streamingCardDisabled(ds))
|
|
1419
1475
|
break;
|
|
1476
|
+
// Restart recovery: a restored worker may emit screen updates as the CLI
|
|
1477
|
+
// redraws on resume. Stay silent (no post/patch) until the first real
|
|
1478
|
+
// user turn clears the flag. Dashboard SSE above still reflects status.
|
|
1479
|
+
if (ds.suppressRecoveryCard)
|
|
1480
|
+
break;
|
|
1420
1481
|
const readUrl = buildTerminalUrl(ds);
|
|
1421
1482
|
const turnTitle = ds.currentTurnTitle || ds.session.title || getCliDisplayName(effectiveCliId);
|
|
1422
1483
|
const mode = ds.displayMode ?? 'hidden';
|
|
@@ -1437,7 +1498,7 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
1437
1498
|
// not POSTed as duplicate cards.
|
|
1438
1499
|
ds.streamCardPending = false;
|
|
1439
1500
|
ds.streamCardId = CARD_POSTING_SENTINEL;
|
|
1440
|
-
|
|
1501
|
+
scopedReply(cardJson, 'interactive', msg.turnId)
|
|
1441
1502
|
.then(msgId => {
|
|
1442
1503
|
ds.streamCardId = msgId;
|
|
1443
1504
|
persistStreamCardState(ds);
|
|
@@ -1477,8 +1538,14 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
1477
1538
|
if (ds.streamCardPending)
|
|
1478
1539
|
break;
|
|
1479
1540
|
ds.currentImageKey = msg.imageKey;
|
|
1541
|
+
const prevStatus = ds.lastScreenStatus;
|
|
1480
1542
|
updateUsageLimitState(ds, msg.usageLimit);
|
|
1481
1543
|
ds.lastScreenStatus = (msg.usageLimit ?? ds.usageLimit) ? 'limited' : msg.status;
|
|
1544
|
+
emitSessionStateTransitionHook(ds, prevStatus, ds.lastScreenStatus, {
|
|
1545
|
+
source: 'screenshot_uploaded',
|
|
1546
|
+
imageKey: msg.imageKey,
|
|
1547
|
+
content: ds.lastScreenContent ?? '',
|
|
1548
|
+
});
|
|
1482
1549
|
persistStreamCardState(ds);
|
|
1483
1550
|
if ((ds.displayMode ?? 'hidden') !== 'screenshot')
|
|
1484
1551
|
break;
|
|
@@ -1501,6 +1568,18 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
1501
1568
|
ds.tuiPromptOptions = msg.options;
|
|
1502
1569
|
ds.tuiPromptMultiSelect = msg.multiSelect;
|
|
1503
1570
|
ds.tuiToggledIndices = [];
|
|
1571
|
+
emitSessionLifecycleHook(ds, 'session.requires_attention', {
|
|
1572
|
+
reason: 'tui_prompt',
|
|
1573
|
+
description: msg.description,
|
|
1574
|
+
optionsCount: msg.options.length,
|
|
1575
|
+
optionsPreview: msg.options.slice(0, 5).map(option => ({
|
|
1576
|
+
text: option.text,
|
|
1577
|
+
label: option.label,
|
|
1578
|
+
type: option.type,
|
|
1579
|
+
selected: option.selected,
|
|
1580
|
+
})),
|
|
1581
|
+
multiSelect: msg.multiSelect,
|
|
1582
|
+
});
|
|
1504
1583
|
const prevTuiTurnTitle = ds.currentTurnTitle;
|
|
1505
1584
|
ds.currentTurnTitle = msg.description; // store for card PATCH on toggle
|
|
1506
1585
|
if (prevTuiTurnTitle !== ds.currentTurnTitle) {
|
|
@@ -1514,7 +1593,7 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
1514
1593
|
}
|
|
1515
1594
|
try {
|
|
1516
1595
|
const cardJson = buildTuiPromptCard(sessionAnchorId(ds), ds.session.sessionId, msg.description, msg.options, msg.multiSelect, undefined, loc);
|
|
1517
|
-
const cardMsgId = await
|
|
1596
|
+
const cardMsgId = await scopedReply(cardJson, 'interactive', msg.turnId);
|
|
1518
1597
|
ds.tuiPromptCardId = cardMsgId;
|
|
1519
1598
|
publishAttentionPatch(ds);
|
|
1520
1599
|
}
|
|
@@ -1556,7 +1635,7 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
1556
1635
|
// leave status='active' and still get the notice.
|
|
1557
1636
|
if (ds.session.status !== 'closed') {
|
|
1558
1637
|
try {
|
|
1559
|
-
await
|
|
1638
|
+
await scopedReply(tr('worker.adopted_session_exited', undefined, loc), 'text', undefined);
|
|
1560
1639
|
}
|
|
1561
1640
|
catch { /* best effort */ }
|
|
1562
1641
|
}
|
|
@@ -1584,7 +1663,7 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
1584
1663
|
killWorker(ds);
|
|
1585
1664
|
const cliName = getCliDisplayName(effectiveCliId);
|
|
1586
1665
|
try {
|
|
1587
|
-
await
|
|
1666
|
+
await scopedReply(tr('worker.crash_loop_stopped', { cliName, count: rc.count }, loc), 'text', undefined);
|
|
1588
1667
|
}
|
|
1589
1668
|
catch (replyErr) {
|
|
1590
1669
|
if (replyErr instanceof MessageWithdrawnError) {
|
|
@@ -1607,8 +1686,12 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
1607
1686
|
}
|
|
1608
1687
|
case 'user_notify': {
|
|
1609
1688
|
logger.warn(`[${t}] Worker user_notify: ${msg.message}`);
|
|
1689
|
+
emitSessionLifecycleHook(ds, 'session.requires_attention', {
|
|
1690
|
+
reason: 'user_notify',
|
|
1691
|
+
message: msg.message,
|
|
1692
|
+
});
|
|
1610
1693
|
try {
|
|
1611
|
-
await
|
|
1694
|
+
await scopedReply(msg.message, 'text', msg.turnId);
|
|
1612
1695
|
}
|
|
1613
1696
|
catch (err) {
|
|
1614
1697
|
logger.error(`[${t}] Failed to deliver user_notify to Lark: ${err.message}`);
|
|
@@ -1654,7 +1737,7 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
1654
1737
|
recipientOpenId,
|
|
1655
1738
|
brand: resolveBrandLabel(ds.larkAppId),
|
|
1656
1739
|
});
|
|
1657
|
-
|
|
1740
|
+
scopedReply(cardJson, 'interactive', msg.turnId).catch((err) => {
|
|
1658
1741
|
logger.warn(`[${t}] Failed to deliver adopt_preamble to Lark: ${err.message}`);
|
|
1659
1742
|
});
|
|
1660
1743
|
break;
|
|
@@ -1681,6 +1764,10 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
1681
1764
|
reason: code === 0 ? 'graceful' : `exit_code_${code}`,
|
|
1682
1765
|
},
|
|
1683
1766
|
});
|
|
1767
|
+
emitSessionLifecycleHook(ds, 'session.exit', {
|
|
1768
|
+
reason: code === 0 ? 'graceful' : `exit_code_${code}`,
|
|
1769
|
+
code,
|
|
1770
|
+
});
|
|
1684
1771
|
}
|
|
1685
1772
|
});
|
|
1686
1773
|
}
|
|
@@ -1694,6 +1781,7 @@ const FINAL_OUTPUT_RETRY_BACKOFF_MS = [0, 5000, 15000]; // immediate, +5s, +15s
|
|
|
1694
1781
|
function deliverFinalOutput(ds, msg, t, attempt, lockedPendingCardId, lockedQuoteTargetId) {
|
|
1695
1782
|
const cb = requireCallbacks();
|
|
1696
1783
|
const effectiveCliId = ds.session.cliId ?? getBot(ds.larkAppId).config.cliId;
|
|
1784
|
+
const scopedReply = (content, msgType, turnId) => cb.sessionReply(sessionAnchorId(ds), content, msgType, ds.larkAppId, turnId);
|
|
1697
1785
|
setTimeout(async () => {
|
|
1698
1786
|
let pendingCardId;
|
|
1699
1787
|
let pendingQuoteTargetId;
|
|
@@ -1734,7 +1822,7 @@ function deliverFinalOutput(ds, msg, t, attempt, lockedPendingCardId, lockedQuot
|
|
|
1734
1822
|
if (pendingCardId) {
|
|
1735
1823
|
try {
|
|
1736
1824
|
if (ds.session.pendingResponseCardId !== pendingCardId) {
|
|
1737
|
-
await
|
|
1825
|
+
await scopedReply(cardJson, 'interactive', msg.turnId);
|
|
1738
1826
|
}
|
|
1739
1827
|
else {
|
|
1740
1828
|
writePendingResponsePatchMarker(ds.session.sessionId, pendingCardId);
|
|
@@ -1755,14 +1843,14 @@ function deliverFinalOutput(ds, msg, t, attempt, lockedPendingCardId, lockedQuot
|
|
|
1755
1843
|
if (!(err instanceof MessageWithdrawnError))
|
|
1756
1844
|
throw err;
|
|
1757
1845
|
logger.warn(`[${t}] Pending response card withdrawn while forwarding final_output; sending a new reply`);
|
|
1758
|
-
await
|
|
1846
|
+
await scopedReply(cardJson, 'interactive', msg.turnId);
|
|
1759
1847
|
markPendingResponseCardPatchedIfCurrent(ds.session, pendingCardId);
|
|
1760
1848
|
syncPendingResponseState(ds, ds.session);
|
|
1761
1849
|
sessionStore.updateSession(ds.session);
|
|
1762
1850
|
}
|
|
1763
1851
|
}
|
|
1764
1852
|
else {
|
|
1765
|
-
await
|
|
1853
|
+
await scopedReply(cardJson, 'interactive', msg.turnId);
|
|
1766
1854
|
}
|
|
1767
1855
|
ds.lastBridgeEmittedUuid = msg.lastUuid;
|
|
1768
1856
|
logger.info(`[${t}] Bridge final_output forwarded (turn ${msg.turnId.substring(0, 8)}, ${msg.content.length} chars, kind=${msg.kind ?? 'bridge'}, attempt ${attempt + 1})`);
|
|
@@ -1977,6 +2065,11 @@ export function forkAdoptWorker(ds, opts) {
|
|
|
1977
2065
|
type: 'session.spawned',
|
|
1978
2066
|
body: { session: composeRowFromActive(ds) },
|
|
1979
2067
|
});
|
|
2068
|
+
emitSessionLifecycleHook(ds, 'session.start', {
|
|
2069
|
+
reason: opts?.restoredFromMetadata ? 'adopt_restore' : 'adopt',
|
|
2070
|
+
pid: worker.pid ?? null,
|
|
2071
|
+
adoptedFrom: adopted.tmuxTarget,
|
|
2072
|
+
});
|
|
1980
2073
|
}
|
|
1981
2074
|
// ─── Kill stale PIDs ────────────────────────────────────────────────────────
|
|
1982
2075
|
export function killStalePids(activeSessions_) {
|