copilot-hub 0.1.13 → 0.1.16
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/apps/agent-engine/dist/agent-worker.js +28 -20
- package/apps/agent-engine/dist/config.js +1 -2
- package/apps/agent-engine/dist/index.js +146 -41
- package/apps/control-plane/dist/agent-worker.js +28 -20
- package/apps/control-plane/dist/channels/channel-factory.js +1 -2
- package/apps/control-plane/dist/channels/hub-model-utils.js +280 -0
- package/apps/control-plane/dist/channels/hub-ops-commands.js +859 -143
- package/apps/control-plane/dist/channels/telegram-channel.js +197 -53
- package/apps/control-plane/dist/channels/whatsapp-channel.js +6 -1
- package/apps/control-plane/dist/config.js +1 -2
- package/apps/control-plane/dist/copilot-hub.js +2 -3
- package/apps/control-plane/dist/index.js +41 -23
- package/apps/control-plane/dist/kernel/admin-contract.js +1 -2
- package/apps/control-plane/dist/test/hub-model-utils.test.js +170 -0
- package/package.json +4 -2
- package/packages/core/dist/agent-supervisor.d.ts +109 -28
- package/packages/core/dist/agent-supervisor.js +99 -45
- package/packages/core/dist/agent-supervisor.js.map +1 -1
- package/packages/core/dist/bot-manager.d.ts +86 -45
- package/packages/core/dist/bot-manager.js +17 -3
- package/packages/core/dist/bot-manager.js.map +1 -1
- package/packages/core/dist/bot-runtime.d.ts +225 -125
- package/packages/core/dist/bot-runtime.js +240 -52
- package/packages/core/dist/bot-runtime.js.map +1 -1
- package/packages/core/dist/bridge-service.d.ts +158 -31
- package/packages/core/dist/bridge-service.js +33 -22
- package/packages/core/dist/bridge-service.js.map +1 -1
- package/packages/core/dist/capability-manager.d.ts +60 -12
- package/packages/core/dist/capability-manager.js +74 -37
- package/packages/core/dist/capability-manager.js.map +1 -1
- package/packages/core/dist/channel-factory.d.ts +9 -4
- package/packages/core/dist/channel-factory.js +1 -2
- package/packages/core/dist/channel-factory.js.map +1 -1
- package/packages/core/dist/codex-app-client.d.ts +110 -43
- package/packages/core/dist/codex-app-client.js +182 -333
- package/packages/core/dist/codex-app-client.js.map +1 -1
- package/packages/core/dist/codex-app-events.d.ts +30 -0
- package/packages/core/dist/codex-app-events.js +266 -0
- package/packages/core/dist/codex-app-events.js.map +1 -0
- package/packages/core/dist/codex-app-utils.d.ts +28 -0
- package/packages/core/dist/codex-app-utils.js +164 -0
- package/packages/core/dist/codex-app-utils.js.map +1 -0
- package/packages/core/dist/codex-provider.d.ts +36 -27
- package/packages/core/dist/codex-provider.js +12 -11
- package/packages/core/dist/codex-provider.js.map +1 -1
- package/packages/core/dist/codex-quota-display.d.ts +12 -0
- package/packages/core/dist/codex-quota-display.js +56 -0
- package/packages/core/dist/codex-quota-display.js.map +1 -0
- package/packages/core/dist/extension-contract.d.ts +1 -1
- package/packages/core/dist/extension-contract.js +0 -1
- package/packages/core/dist/extension-contract.js.map +1 -1
- package/packages/core/dist/kernel-control-plane.d.ts +52 -12
- package/packages/core/dist/kernel-control-plane.js +95 -32
- package/packages/core/dist/kernel-control-plane.js.map +1 -1
- package/packages/core/dist/provider-factory.d.ts +20 -6
- package/packages/core/dist/provider-factory.js +20 -8
- package/packages/core/dist/provider-factory.js.map +1 -1
- package/packages/core/dist/telegram-channel.d.ts +103 -16
- package/packages/core/dist/telegram-channel.js +195 -59
- package/packages/core/dist/telegram-channel.js.map +1 -1
- package/packages/core/dist/whatsapp-channel.d.ts +21 -20
- package/packages/core/dist/whatsapp-channel.js +6 -1
- package/packages/core/dist/whatsapp-channel.js.map +1 -1
- package/packages/core/package.json +4 -0
- package/scripts/dist/daemon.mjs +41 -2
- package/scripts/src/daemon.mts +45 -2
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import { CodexProvider } from "./codex-provider.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
type ProviderConfig = {
|
|
3
|
+
kind?: unknown;
|
|
4
|
+
options?: unknown;
|
|
5
|
+
} | null;
|
|
6
|
+
type ProviderDefaults = {
|
|
7
|
+
defaultKind?: unknown;
|
|
8
|
+
codexBin?: unknown;
|
|
9
|
+
codexHomeDir?: unknown;
|
|
10
|
+
codexSandbox?: unknown;
|
|
11
|
+
codexApprovalPolicy?: unknown;
|
|
12
|
+
codexModel?: unknown;
|
|
13
|
+
} | null;
|
|
14
|
+
type CreateAssistantProviderParams = {
|
|
15
|
+
providerConfig?: ProviderConfig;
|
|
16
|
+
providerDefaults?: ProviderDefaults;
|
|
17
|
+
workspaceRoot: string;
|
|
18
|
+
turnActivityTimeoutMs?: number;
|
|
19
|
+
};
|
|
20
|
+
export declare function createAssistantProvider({ providerConfig, providerDefaults, workspaceRoot, turnActivityTimeoutMs, }: CreateAssistantProviderParams): CodexProvider;
|
|
21
|
+
export {};
|
|
@@ -1,21 +1,33 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import { CodexProvider } from "./codex-provider.js";
|
|
3
2
|
export function createAssistantProvider({ providerConfig, providerDefaults, workspaceRoot, turnActivityTimeoutMs, }) {
|
|
4
|
-
const defaults = providerDefaults
|
|
5
|
-
const
|
|
3
|
+
const defaults = asRecord(providerDefaults);
|
|
4
|
+
const provider = asRecord(providerConfig);
|
|
5
|
+
const kind = String(provider.kind ?? defaults.defaultKind ?? "codex")
|
|
6
6
|
.trim()
|
|
7
7
|
.toLowerCase();
|
|
8
|
-
const options =
|
|
8
|
+
const options = asRecord(provider.options);
|
|
9
9
|
if (kind === "codex") {
|
|
10
|
-
|
|
10
|
+
const codexProviderConfig = {
|
|
11
11
|
codexBin: String(options.codexBin ?? defaults.codexBin ?? "codex"),
|
|
12
|
-
codexHomeDir: options.codexHomeDir ?? defaults.codexHomeDir
|
|
12
|
+
codexHomeDir: normalizeOptionalString(options.codexHomeDir ?? defaults.codexHomeDir),
|
|
13
13
|
sandboxMode: String(options.sandboxMode ?? defaults.codexSandbox ?? "danger-full-access"),
|
|
14
14
|
approvalPolicy: String(options.approvalPolicy ?? defaults.codexApprovalPolicy ?? "never"),
|
|
15
|
+
model: normalizeOptionalString(options.model ?? defaults.codexModel),
|
|
15
16
|
workspaceRoot,
|
|
16
|
-
turnActivityTimeoutMs,
|
|
17
|
-
}
|
|
17
|
+
...(turnActivityTimeoutMs === undefined ? {} : { turnActivityTimeoutMs }),
|
|
18
|
+
};
|
|
19
|
+
return new CodexProvider(codexProviderConfig);
|
|
18
20
|
}
|
|
19
21
|
throw new Error(`Unknown assistant provider kind '${kind}'.`);
|
|
20
22
|
}
|
|
23
|
+
function asRecord(value) {
|
|
24
|
+
return value && typeof value === "object" ? value : {};
|
|
25
|
+
}
|
|
26
|
+
function normalizeOptionalString(value) {
|
|
27
|
+
if (value === null || value === undefined) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const normalized = String(value).trim();
|
|
31
|
+
return normalized || null;
|
|
32
|
+
}
|
|
21
33
|
//# sourceMappingURL=provider-factory.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-factory.js","sourceRoot":"","sources":["../src/provider-factory.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"provider-factory.js","sourceRoot":"","sources":["../src/provider-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAyBpD,MAAM,UAAU,uBAAuB,CAAC,EACtC,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,qBAAqB,GACS;IAC9B,MAAM,QAAQ,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,WAAW,IAAI,OAAO,CAAC;SAClE,IAAI,EAAE;SACN,WAAW,EAAE,CAAC;IACjB,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAE3C,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,MAAM,mBAAmB,GAAG;YAC1B,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,IAAI,OAAO,CAAC;YAClE,YAAY,EAAE,uBAAuB,CAAC,OAAO,CAAC,YAAY,IAAI,QAAQ,CAAC,YAAY,CAAC;YACpF,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW,IAAI,QAAQ,CAAC,YAAY,IAAI,oBAAoB,CAAC;YACzF,cAAc,EAAE,MAAM,CAAC,OAAO,CAAC,cAAc,IAAI,QAAQ,CAAC,mBAAmB,IAAI,OAAO,CAAC;YACzF,KAAK,EAAE,uBAAuB,CAAC,OAAO,CAAC,KAAK,IAAI,QAAQ,CAAC,UAAU,CAAC;YACpE,aAAa;YACb,GAAG,CAAC,qBAAqB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,qBAAqB,EAAE,CAAC;SAC1E,CAAC;QACF,OAAO,IAAI,aAAa,CAAC,mBAAmB,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,oCAAoC,IAAI,IAAI,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAE,KAAuB,CAAC,CAAC,CAAC,EAAE,CAAC;AAC5E,CAAC;AAED,SAAS,uBAAuB,CAAC,KAAc;IAC7C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IACxC,OAAO,UAAU,IAAI,IAAI,CAAC;AAC5B,CAAC"}
|
|
@@ -1,27 +1,114 @@
|
|
|
1
|
+
import { Bot } from "grammy";
|
|
2
|
+
type TelegramChannelConfig = {
|
|
3
|
+
kind?: unknown;
|
|
4
|
+
id?: string;
|
|
5
|
+
token?: string;
|
|
6
|
+
allowedChatIds?: string | Array<string | number>;
|
|
7
|
+
};
|
|
8
|
+
type ThreadState = {
|
|
9
|
+
turnCount?: number;
|
|
10
|
+
sessionId?: string;
|
|
11
|
+
updatedAt?: string;
|
|
12
|
+
};
|
|
13
|
+
type PendingApproval = {
|
|
14
|
+
id: string;
|
|
15
|
+
kind: string;
|
|
16
|
+
command?: string;
|
|
17
|
+
cwd?: string;
|
|
18
|
+
reason?: string;
|
|
19
|
+
threadId?: string;
|
|
20
|
+
metadata?: {
|
|
21
|
+
chatId?: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
type ApprovalDecision = "accept" | "acceptForSession" | "decline";
|
|
25
|
+
type InterruptResult = {
|
|
26
|
+
interrupted?: boolean;
|
|
27
|
+
method?: string;
|
|
28
|
+
reason?: string;
|
|
29
|
+
error?: string;
|
|
30
|
+
};
|
|
31
|
+
type TurnInputItem = {
|
|
32
|
+
type?: string;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
};
|
|
35
|
+
type ActiveTurn = {
|
|
36
|
+
threadId: string;
|
|
37
|
+
token: string;
|
|
38
|
+
};
|
|
39
|
+
type TurnControlState = {
|
|
40
|
+
token: string;
|
|
41
|
+
threadId: string;
|
|
42
|
+
messageId: number;
|
|
43
|
+
};
|
|
44
|
+
type RuntimeLike = {
|
|
45
|
+
runtimeId?: string;
|
|
46
|
+
runtimeName?: string;
|
|
47
|
+
resolveThreadIdForChannel: (args: {
|
|
48
|
+
channelKind: string;
|
|
49
|
+
channelId: string;
|
|
50
|
+
externalUserId: string;
|
|
51
|
+
}) => Promise<string>;
|
|
52
|
+
resetThread: (threadId: string) => Promise<unknown>;
|
|
53
|
+
getThread: (threadId: string) => Promise<{
|
|
54
|
+
thread: ThreadState;
|
|
55
|
+
}>;
|
|
56
|
+
buildWebBotUrl: () => string;
|
|
57
|
+
listPendingApprovals: (threadId: string) => Promise<PendingApproval[]>;
|
|
58
|
+
resolvePendingApproval: (args: {
|
|
59
|
+
threadId: string;
|
|
60
|
+
approvalId: string;
|
|
61
|
+
decision: ApprovalDecision;
|
|
62
|
+
}) => Promise<unknown>;
|
|
63
|
+
interruptThread: (threadId: string) => Promise<InterruptResult | null>;
|
|
64
|
+
sendTurn: (args: {
|
|
65
|
+
threadId: string;
|
|
66
|
+
prompt: string;
|
|
67
|
+
inputItems?: TurnInputItem[];
|
|
68
|
+
source?: string;
|
|
69
|
+
metadata?: Record<string, unknown>;
|
|
70
|
+
}) => Promise<{
|
|
71
|
+
assistantText?: string;
|
|
72
|
+
}>;
|
|
73
|
+
getWorkspaceRoot?: () => string;
|
|
74
|
+
getProviderUsage?: () => Promise<unknown>;
|
|
75
|
+
};
|
|
1
76
|
export declare class TelegramChannel {
|
|
2
77
|
#private;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
78
|
+
kind: "telegram";
|
|
79
|
+
id: string;
|
|
80
|
+
config: TelegramChannelConfig;
|
|
81
|
+
runtime: RuntimeLike;
|
|
82
|
+
allowedChatIds: Set<string>;
|
|
83
|
+
bot: Bot | null;
|
|
84
|
+
running: boolean;
|
|
85
|
+
error: string | null;
|
|
86
|
+
activeTurnsByChat: Map<string, ActiveTurn>;
|
|
87
|
+
turnControlByChat: Map<string, TurnControlState>;
|
|
88
|
+
nextTurnToken: number;
|
|
89
|
+
constructor({ channelConfig, runtime, }: {
|
|
90
|
+
channelConfig: TelegramChannelConfig;
|
|
91
|
+
runtime: unknown;
|
|
6
92
|
});
|
|
7
93
|
start(): Promise<{
|
|
8
|
-
kind:
|
|
9
|
-
id:
|
|
10
|
-
running:
|
|
11
|
-
error:
|
|
94
|
+
kind: string;
|
|
95
|
+
id: string;
|
|
96
|
+
running: boolean;
|
|
97
|
+
error: string | null;
|
|
12
98
|
}>;
|
|
13
99
|
stop(): Promise<{
|
|
14
|
-
kind:
|
|
15
|
-
id:
|
|
16
|
-
running:
|
|
17
|
-
error:
|
|
100
|
+
kind: string;
|
|
101
|
+
id: string;
|
|
102
|
+
running: boolean;
|
|
103
|
+
error: string | null;
|
|
18
104
|
}>;
|
|
19
105
|
shutdown(): Promise<void>;
|
|
20
106
|
getStatus(): {
|
|
21
|
-
kind:
|
|
22
|
-
id:
|
|
23
|
-
running:
|
|
24
|
-
error:
|
|
107
|
+
kind: string;
|
|
108
|
+
id: string;
|
|
109
|
+
running: boolean;
|
|
110
|
+
error: string | null;
|
|
25
111
|
};
|
|
26
|
-
notifyApproval(approval:
|
|
112
|
+
notifyApproval(approval: PendingApproval): Promise<void>;
|
|
27
113
|
}
|
|
114
|
+
export {};
|
|
@@ -1,13 +1,27 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Bot } from "grammy";
|
|
5
|
+
import { formatCodexQuotaLine, hasCodexQuotaWindows } from "./codex-quota-display.js";
|
|
6
|
+
const CODEX_USAGE_CACHE_TTL_MS = 60_000;
|
|
7
|
+
let cachedCodexUsage = null;
|
|
5
8
|
export class TelegramChannel {
|
|
6
|
-
|
|
9
|
+
kind;
|
|
10
|
+
id;
|
|
11
|
+
config;
|
|
12
|
+
runtime;
|
|
13
|
+
allowedChatIds;
|
|
14
|
+
bot;
|
|
15
|
+
running;
|
|
16
|
+
error;
|
|
17
|
+
activeTurnsByChat;
|
|
18
|
+
turnControlByChat;
|
|
19
|
+
nextTurnToken;
|
|
20
|
+
constructor({ channelConfig, runtime, }) {
|
|
7
21
|
this.kind = "telegram";
|
|
8
22
|
this.id = String(channelConfig.id ?? "telegram");
|
|
9
23
|
this.config = channelConfig;
|
|
10
|
-
this.runtime = runtime;
|
|
24
|
+
this.runtime = toRuntimeLike(runtime);
|
|
11
25
|
this.allowedChatIds = normalizeAllowedChatIds(channelConfig.allowedChatIds);
|
|
12
26
|
this.bot = null;
|
|
13
27
|
this.running = false;
|
|
@@ -320,7 +334,7 @@ export class TelegramChannel {
|
|
|
320
334
|
console.error(`[${this.runtime.runtimeId}:${this.id}] Telegram error in update ${error.ctx.update.update_id}: ${details}`);
|
|
321
335
|
});
|
|
322
336
|
}
|
|
323
|
-
async #routeIncomingPrompt({ chatId, prompt, mode = "auto_message", inputItems = null }) {
|
|
337
|
+
async #routeIncomingPrompt({ chatId, prompt, mode = "auto_message", inputItems = null, }) {
|
|
324
338
|
const normalizedPrompt = String(prompt ?? "").trim();
|
|
325
339
|
if (!normalizedPrompt) {
|
|
326
340
|
if (this.bot) {
|
|
@@ -383,7 +397,7 @@ export class TelegramChannel {
|
|
|
383
397
|
url: dataUrl,
|
|
384
398
|
};
|
|
385
399
|
}
|
|
386
|
-
async #buildVoicePrompt({ chatId, message }) {
|
|
400
|
+
async #buildVoicePrompt({ chatId, message, }) {
|
|
387
401
|
const voice = message?.voice;
|
|
388
402
|
if (!voice || !voice.file_id) {
|
|
389
403
|
throw new Error("Telegram update does not contain voice payload.");
|
|
@@ -434,7 +448,7 @@ export class TelegramChannel {
|
|
|
434
448
|
}
|
|
435
449
|
return Buffer.from(await response.arrayBuffer());
|
|
436
450
|
}
|
|
437
|
-
async #saveTelegramMediaFile({ chatId, fileId, mediaKind, preferredExtension }) {
|
|
451
|
+
async #saveTelegramMediaFile({ chatId, fileId, mediaKind, preferredExtension, }) {
|
|
438
452
|
if (!this.bot) {
|
|
439
453
|
throw new Error("Telegram bot is not running.");
|
|
440
454
|
}
|
|
@@ -468,7 +482,7 @@ export class TelegramChannel {
|
|
|
468
482
|
}
|
|
469
483
|
return path.resolve(process.cwd());
|
|
470
484
|
}
|
|
471
|
-
async #processTurn({ chatId, threadId, prompt, inputItems = null }) {
|
|
485
|
+
async #processTurn({ chatId, threadId, prompt, inputItems = null, }) {
|
|
472
486
|
if (!this.bot) {
|
|
473
487
|
return;
|
|
474
488
|
}
|
|
@@ -477,16 +491,19 @@ export class TelegramChannel {
|
|
|
477
491
|
let controlStatus = "Generation completed.";
|
|
478
492
|
try {
|
|
479
493
|
await this.bot.api.sendChatAction(chatId, "typing");
|
|
480
|
-
const
|
|
494
|
+
const payload = {
|
|
481
495
|
threadId,
|
|
482
496
|
prompt,
|
|
483
|
-
inputItems: Array.isArray(inputItems) ? inputItems : undefined,
|
|
484
497
|
source: "telegram",
|
|
485
498
|
metadata: {
|
|
486
499
|
chatId,
|
|
487
500
|
channelId: this.id,
|
|
488
501
|
},
|
|
489
|
-
}
|
|
502
|
+
};
|
|
503
|
+
if (Array.isArray(inputItems)) {
|
|
504
|
+
payload.inputItems = inputItems;
|
|
505
|
+
}
|
|
506
|
+
const result = await this.runtime.sendTurn(payload);
|
|
490
507
|
await sendChunkedMessage(this.bot.api, chatId, result.assistantText || "Assistant returned no text output.");
|
|
491
508
|
}
|
|
492
509
|
catch (error) {
|
|
@@ -509,7 +526,11 @@ export class TelegramChannel {
|
|
|
509
526
|
}
|
|
510
527
|
}
|
|
511
528
|
async #handleStopTurn(context) {
|
|
512
|
-
const chatId =
|
|
529
|
+
const chatId = getContextChatId(context);
|
|
530
|
+
if (!chatId) {
|
|
531
|
+
await context.reply("Unable to resolve chat.");
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
513
534
|
if (!this.#isAllowedChat(chatId)) {
|
|
514
535
|
await context.reply("Chat not allowed for this bot.");
|
|
515
536
|
return;
|
|
@@ -523,12 +544,16 @@ export class TelegramChannel {
|
|
|
523
544
|
await context.reply(formatInterruptResult(result));
|
|
524
545
|
}
|
|
525
546
|
async #handleSteer(context) {
|
|
526
|
-
const chatId =
|
|
547
|
+
const chatId = getContextChatId(context);
|
|
548
|
+
if (!chatId) {
|
|
549
|
+
await context.reply("Unable to resolve chat.");
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
527
552
|
if (!this.#isAllowedChat(chatId)) {
|
|
528
553
|
await context.reply("Chat not allowed for this bot.");
|
|
529
554
|
return;
|
|
530
555
|
}
|
|
531
|
-
const instruction = extractCommandTail(context
|
|
556
|
+
const instruction = extractCommandTail(getContextMessageText(context));
|
|
532
557
|
const threadId = await this.runtime.resolveThreadIdForChannel({
|
|
533
558
|
channelKind: this.kind,
|
|
534
559
|
channelId: this.id,
|
|
@@ -621,9 +646,9 @@ export class TelegramChannel {
|
|
|
621
646
|
const key = String(chatId);
|
|
622
647
|
await this.#closeTurnControls(chatId, null, null);
|
|
623
648
|
try {
|
|
624
|
-
const
|
|
625
|
-
const inProgressText =
|
|
626
|
-
? ["Generation in progress.",
|
|
649
|
+
const quota = await resolveCodexQuotaLine(this.runtime);
|
|
650
|
+
const inProgressText = quota.line
|
|
651
|
+
? ["Generation in progress.", quota.line].join("\n")
|
|
627
652
|
: "Generation in progress.";
|
|
628
653
|
const sent = await this.bot.api.sendMessage(chatId, inProgressText, {
|
|
629
654
|
reply_markup: buildTurnControlKeyboard(token),
|
|
@@ -641,12 +666,12 @@ export class TelegramChannel {
|
|
|
641
666
|
}
|
|
642
667
|
#scheduleTurnControlQuotaRefresh(chatId, token, attempt) {
|
|
643
668
|
const safeAttempt = Number.isFinite(attempt) ? Number(attempt) : 1;
|
|
644
|
-
if (safeAttempt >
|
|
669
|
+
if (safeAttempt > 12) {
|
|
645
670
|
return;
|
|
646
671
|
}
|
|
647
672
|
setTimeout(() => {
|
|
648
673
|
void this.#refreshTurnControlQuota(chatId, token, safeAttempt);
|
|
649
|
-
},
|
|
674
|
+
}, 1500);
|
|
650
675
|
}
|
|
651
676
|
async #refreshTurnControlQuota(chatId, token, attempt) {
|
|
652
677
|
if (!this.bot) {
|
|
@@ -657,12 +682,17 @@ export class TelegramChannel {
|
|
|
657
682
|
if (!current || String(current.token) !== String(token)) {
|
|
658
683
|
return;
|
|
659
684
|
}
|
|
660
|
-
const
|
|
661
|
-
if (!
|
|
685
|
+
const quota = await resolveCodexQuotaLine(this.runtime);
|
|
686
|
+
if (!quota.line) {
|
|
687
|
+
this.#scheduleTurnControlQuotaRefresh(chatId, token, Number(attempt) + 1);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
// Keep polling until the real quota percentages arrive (model-only line is not enough).
|
|
691
|
+
if (!quota.hasQuotaWindows) {
|
|
662
692
|
this.#scheduleTurnControlQuotaRefresh(chatId, token, Number(attempt) + 1);
|
|
663
693
|
return;
|
|
664
694
|
}
|
|
665
|
-
const inProgressText = ["Generation in progress.",
|
|
695
|
+
const inProgressText = ["Generation in progress.", quota.line].join("\n");
|
|
666
696
|
try {
|
|
667
697
|
await this.bot.api.editMessageText(chatId, current.messageId, inProgressText, {
|
|
668
698
|
reply_markup: buildTurnControlKeyboard(token),
|
|
@@ -706,7 +736,11 @@ export class TelegramChannel {
|
|
|
706
736
|
}
|
|
707
737
|
}
|
|
708
738
|
async #handleSingleApproval(context, decision) {
|
|
709
|
-
const chatId =
|
|
739
|
+
const chatId = getContextChatId(context);
|
|
740
|
+
if (!chatId) {
|
|
741
|
+
await context.reply("Unable to resolve chat.");
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
710
744
|
if (!this.#isAllowedChat(chatId)) {
|
|
711
745
|
await context.reply("Chat not allowed for this bot.");
|
|
712
746
|
return;
|
|
@@ -716,7 +750,7 @@ export class TelegramChannel {
|
|
|
716
750
|
channelId: this.id,
|
|
717
751
|
externalUserId: chatId,
|
|
718
752
|
});
|
|
719
|
-
const requestedId = extractFirstCommandArgument(context
|
|
753
|
+
const requestedId = extractFirstCommandArgument(getContextMessageText(context));
|
|
720
754
|
if (requestedId.toLowerCase() === "all") {
|
|
721
755
|
const summary = await this.#resolveAllApprovals({ threadId, decision });
|
|
722
756
|
await context.reply(summary);
|
|
@@ -744,7 +778,11 @@ export class TelegramChannel {
|
|
|
744
778
|
await context.reply(`Approved '${target.id}'.`);
|
|
745
779
|
}
|
|
746
780
|
async #handleBulkApproval(context, decision) {
|
|
747
|
-
const chatId =
|
|
781
|
+
const chatId = getContextChatId(context);
|
|
782
|
+
if (!chatId) {
|
|
783
|
+
await context.reply("Unable to resolve chat.");
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
748
786
|
if (!this.#isAllowedChat(chatId)) {
|
|
749
787
|
await context.reply("Chat not allowed for this bot.");
|
|
750
788
|
return;
|
|
@@ -760,7 +798,7 @@ export class TelegramChannel {
|
|
|
760
798
|
});
|
|
761
799
|
await context.reply(summary);
|
|
762
800
|
}
|
|
763
|
-
async #resolveAllApprovals({ threadId, decision }) {
|
|
801
|
+
async #resolveAllApprovals({ threadId, decision, }) {
|
|
764
802
|
const approvals = await this.runtime.listPendingApprovals(threadId);
|
|
765
803
|
if (approvals.length === 0) {
|
|
766
804
|
return "No pending approvals.";
|
|
@@ -798,6 +836,15 @@ export class TelegramChannel {
|
|
|
798
836
|
return set.has(String(chatId));
|
|
799
837
|
}
|
|
800
838
|
}
|
|
839
|
+
function toRuntimeLike(runtime) {
|
|
840
|
+
return runtime;
|
|
841
|
+
}
|
|
842
|
+
function getContextChatId(context) {
|
|
843
|
+
return String(context.chat?.id ?? "").trim();
|
|
844
|
+
}
|
|
845
|
+
function getContextMessageText(context) {
|
|
846
|
+
return String(context.message?.text ?? "").trim();
|
|
847
|
+
}
|
|
801
848
|
async function sendChunkedMessage(botApi, chatId, text) {
|
|
802
849
|
const max = 3900;
|
|
803
850
|
for (let start = 0; start < text.length; start += max) {
|
|
@@ -832,7 +879,7 @@ function selectApproval(approvals, requestedId) {
|
|
|
832
879
|
return approvals.find((approval) => approval.id === wantedId) ?? null;
|
|
833
880
|
}
|
|
834
881
|
if (approvals.length === 1) {
|
|
835
|
-
return approvals[0];
|
|
882
|
+
return approvals[0] ?? null;
|
|
836
883
|
}
|
|
837
884
|
return null;
|
|
838
885
|
}
|
|
@@ -889,12 +936,12 @@ function isTurnInterruptedError(message) {
|
|
|
889
936
|
normalized.includes("turn interrupted by user") ||
|
|
890
937
|
normalized.includes("process stopped while waiting for turn completion"));
|
|
891
938
|
}
|
|
892
|
-
async function buildTurnFailureMessage({ runtime, safeError }) {
|
|
939
|
+
async function buildTurnFailureMessage({ runtime, safeError, }) {
|
|
893
940
|
if (isQuotaLimitError(safeError)) {
|
|
894
|
-
const
|
|
941
|
+
const quota = await resolveCodexQuotaLine(runtime);
|
|
895
942
|
const base = "Execution paused: Codex quota limit reached for this account.";
|
|
896
|
-
if (
|
|
897
|
-
return `${base}\n${
|
|
943
|
+
if (quota.line) {
|
|
944
|
+
return `${base}\n${quota.line}`;
|
|
898
945
|
}
|
|
899
946
|
return `${base}\nPlease retry after the quota reset window.`;
|
|
900
947
|
}
|
|
@@ -1012,52 +1059,141 @@ function parseTurnControlCallbackData(raw) {
|
|
|
1012
1059
|
if (!match) {
|
|
1013
1060
|
return null;
|
|
1014
1061
|
}
|
|
1062
|
+
const action = match[1];
|
|
1063
|
+
const token = match[2];
|
|
1064
|
+
if (action !== "stop" || !token) {
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1015
1067
|
return {
|
|
1016
|
-
action
|
|
1017
|
-
token
|
|
1068
|
+
action,
|
|
1069
|
+
token,
|
|
1018
1070
|
};
|
|
1019
1071
|
}
|
|
1020
1072
|
async function resolveCodexQuotaLine(runtime) {
|
|
1021
1073
|
if (!runtime || typeof runtime.getProviderUsage !== "function") {
|
|
1022
|
-
return
|
|
1074
|
+
return resolveCodexQuotaLineFromSnapshot(null);
|
|
1023
1075
|
}
|
|
1024
1076
|
try {
|
|
1025
1077
|
const snapshot = await runtime.getProviderUsage();
|
|
1026
|
-
|
|
1078
|
+
const first = resolveCodexQuotaLineFromSnapshot(snapshot);
|
|
1079
|
+
if (first.hasQuotaWindows) {
|
|
1080
|
+
return first;
|
|
1081
|
+
}
|
|
1082
|
+
const fallbackSnapshot = await fetchCodexQuotaSnapshotFromAuth();
|
|
1083
|
+
if (!fallbackSnapshot) {
|
|
1084
|
+
return first;
|
|
1085
|
+
}
|
|
1086
|
+
const merged = snapshot && typeof snapshot === "object"
|
|
1087
|
+
? {
|
|
1088
|
+
...snapshot,
|
|
1089
|
+
...fallbackSnapshot,
|
|
1090
|
+
}
|
|
1091
|
+
: fallbackSnapshot;
|
|
1092
|
+
return resolveCodexQuotaLineFromSnapshot(merged);
|
|
1027
1093
|
}
|
|
1028
1094
|
catch {
|
|
1029
|
-
return
|
|
1095
|
+
return resolveCodexQuotaLineFromSnapshot(null);
|
|
1030
1096
|
}
|
|
1031
1097
|
}
|
|
1032
|
-
function
|
|
1033
|
-
const
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
return "";
|
|
1039
|
-
}
|
|
1040
|
-
return `Codex quota: ${windows.join(" | ")}`;
|
|
1098
|
+
function resolveCodexQuotaLineFromSnapshot(snapshot) {
|
|
1099
|
+
const typed = snapshot;
|
|
1100
|
+
return {
|
|
1101
|
+
line: formatCodexQuotaLine(typed),
|
|
1102
|
+
hasQuotaWindows: hasCodexQuotaWindows(typed),
|
|
1103
|
+
};
|
|
1041
1104
|
}
|
|
1042
|
-
function
|
|
1043
|
-
const
|
|
1044
|
-
if (
|
|
1045
|
-
return
|
|
1105
|
+
async function fetchCodexQuotaSnapshotFromAuth() {
|
|
1106
|
+
const now = Date.now();
|
|
1107
|
+
if (cachedCodexUsage && now < cachedCodexUsage.expiresAt) {
|
|
1108
|
+
return cachedCodexUsage.snapshot;
|
|
1109
|
+
}
|
|
1110
|
+
const auth = await readCodexAuthTokens();
|
|
1111
|
+
if (!auth?.accessToken) {
|
|
1112
|
+
return null;
|
|
1113
|
+
}
|
|
1114
|
+
const controller = new AbortController();
|
|
1115
|
+
const timeout = setTimeout(() => {
|
|
1116
|
+
controller.abort();
|
|
1117
|
+
}, 3500);
|
|
1118
|
+
try {
|
|
1119
|
+
const headers = {
|
|
1120
|
+
Authorization: `Bearer ${auth.accessToken}`,
|
|
1121
|
+
Accept: "application/json",
|
|
1122
|
+
"User-Agent": "CopilotHub",
|
|
1123
|
+
};
|
|
1124
|
+
if (auth.accountId) {
|
|
1125
|
+
headers["ChatGPT-Account-Id"] = auth.accountId;
|
|
1126
|
+
}
|
|
1127
|
+
const response = await fetch("https://chatgpt.com/backend-api/wham/usage", {
|
|
1128
|
+
method: "GET",
|
|
1129
|
+
headers,
|
|
1130
|
+
signal: controller.signal,
|
|
1131
|
+
});
|
|
1132
|
+
if (!response.ok) {
|
|
1133
|
+
return null;
|
|
1134
|
+
}
|
|
1135
|
+
const payload = (await response.json());
|
|
1136
|
+
const primary = normalizeWhamWindow(payload?.rate_limit?.primary_window);
|
|
1137
|
+
const secondary = normalizeWhamWindow(payload?.rate_limit?.secondary_window);
|
|
1138
|
+
const hasData = Number.isFinite(Number(primary?.remainingPercent)) ||
|
|
1139
|
+
Number.isFinite(Number(secondary?.remainingPercent));
|
|
1140
|
+
if (!hasData) {
|
|
1141
|
+
return null;
|
|
1142
|
+
}
|
|
1143
|
+
const snapshot = { primary, secondary };
|
|
1144
|
+
cachedCodexUsage = {
|
|
1145
|
+
expiresAt: now + CODEX_USAGE_CACHE_TTL_MS,
|
|
1146
|
+
snapshot,
|
|
1147
|
+
};
|
|
1148
|
+
return snapshot;
|
|
1149
|
+
}
|
|
1150
|
+
catch {
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1153
|
+
finally {
|
|
1154
|
+
clearTimeout(timeout);
|
|
1046
1155
|
}
|
|
1047
|
-
const resetAt = Number(windowSnapshot?.resetsAt);
|
|
1048
|
-
const resetLabel = Number.isFinite(resetAt) ? `, reset ${formatEpochSeconds(resetAt)}` : "";
|
|
1049
|
-
return `${label} ${Math.round(clampPercent(remaining))}%${resetLabel}`;
|
|
1050
1156
|
}
|
|
1051
|
-
function
|
|
1052
|
-
const
|
|
1053
|
-
|
|
1054
|
-
|
|
1157
|
+
function normalizeWhamWindow(window) {
|
|
1158
|
+
const used = Number(window?.used_percent);
|
|
1159
|
+
const remainingDirect = Number(window?.remaining_percent);
|
|
1160
|
+
let remaining = null;
|
|
1161
|
+
if (Number.isFinite(remainingDirect)) {
|
|
1162
|
+
remaining = clampPercent(remainingDirect);
|
|
1163
|
+
}
|
|
1164
|
+
else if (Number.isFinite(used)) {
|
|
1165
|
+
remaining = clampPercent(100 - used);
|
|
1166
|
+
}
|
|
1167
|
+
const resetSeconds = Number(window?.reset_at ?? window?.resets_at);
|
|
1168
|
+
const resetsAt = Number.isFinite(resetSeconds) ? resetSeconds : null;
|
|
1169
|
+
return {
|
|
1170
|
+
remainingPercent: remaining,
|
|
1171
|
+
resetsAt,
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
async function readCodexAuthTokens() {
|
|
1175
|
+
const codexHome = resolveCodexHomeDir();
|
|
1176
|
+
const authPath = path.join(codexHome, "auth.json");
|
|
1177
|
+
try {
|
|
1178
|
+
const raw = await fs.readFile(authPath, "utf8");
|
|
1179
|
+
const parsed = JSON.parse(raw);
|
|
1180
|
+
const accessToken = String(parsed?.tokens?.access_token ?? "").trim();
|
|
1181
|
+
if (!accessToken) {
|
|
1182
|
+
return null;
|
|
1183
|
+
}
|
|
1184
|
+
const accountId = String(parsed?.tokens?.account_id ?? "").trim() || null;
|
|
1185
|
+
return { accessToken, accountId };
|
|
1055
1186
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1187
|
+
catch {
|
|
1188
|
+
return null;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
function resolveCodexHomeDir() {
|
|
1192
|
+
const fromEnv = String(process.env.CODEX_HOME_DIR ?? process.env.CODEX_HOME ?? "").trim();
|
|
1193
|
+
if (fromEnv) {
|
|
1194
|
+
return path.resolve(fromEnv);
|
|
1059
1195
|
}
|
|
1060
|
-
return
|
|
1196
|
+
return path.join(os.homedir(), ".codex");
|
|
1061
1197
|
}
|
|
1062
1198
|
function clampPercent(value) {
|
|
1063
1199
|
const n = Number(value);
|