@foxden-app/foxclaw 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +36 -0
- package/LICENSE +22 -0
- package/README.md +244 -0
- package/README_EN.md +244 -0
- package/dist/channels/bridge_messaging_router.d.ts +27 -0
- package/dist/channels/bridge_messaging_router.js +85 -0
- package/dist/channels/telegram/telegram_channel_adapter.d.ts +12 -0
- package/dist/channels/telegram/telegram_channel_adapter.js +21 -0
- package/dist/channels/telegram/telegram_messaging_port.d.ts +25 -0
- package/dist/channels/telegram/telegram_messaging_port.js +51 -0
- package/dist/channels/weixin/account_store.d.ts +15 -0
- package/dist/channels/weixin/account_store.js +54 -0
- package/dist/channels/weixin/ilink/aes_ecb.d.ts +3 -0
- package/dist/channels/weixin/ilink/aes_ecb.js +12 -0
- package/dist/channels/weixin/ilink/api.d.ts +44 -0
- package/dist/channels/weixin/ilink/api.js +187 -0
- package/dist/channels/weixin/ilink/cdn_upload.d.ts +11 -0
- package/dist/channels/weixin/ilink/cdn_upload.js +60 -0
- package/dist/channels/weixin/ilink/cdn_url.d.ts +7 -0
- package/dist/channels/weixin/ilink/cdn_url.js +7 -0
- package/dist/channels/weixin/ilink/constants.d.ts +7 -0
- package/dist/channels/weixin/ilink/constants.js +27 -0
- package/dist/channels/weixin/ilink/context.d.ts +13 -0
- package/dist/channels/weixin/ilink/context.js +13 -0
- package/dist/channels/weixin/ilink/login_qr.d.ts +34 -0
- package/dist/channels/weixin/ilink/login_qr.js +233 -0
- package/dist/channels/weixin/ilink/media_image.d.ts +11 -0
- package/dist/channels/weixin/ilink/media_image.js +44 -0
- package/dist/channels/weixin/ilink/mime.d.ts +3 -0
- package/dist/channels/weixin/ilink/mime.js +36 -0
- package/dist/channels/weixin/ilink/pic_decrypt.d.ts +2 -0
- package/dist/channels/weixin/ilink/pic_decrypt.js +56 -0
- package/dist/channels/weixin/ilink/random.d.ts +2 -0
- package/dist/channels/weixin/ilink/random.js +7 -0
- package/dist/channels/weixin/ilink/redact.d.ts +4 -0
- package/dist/channels/weixin/ilink/redact.js +34 -0
- package/dist/channels/weixin/ilink/runtime_attach.d.ts +3 -0
- package/dist/channels/weixin/ilink/runtime_attach.js +13 -0
- package/dist/channels/weixin/ilink/send.d.ts +21 -0
- package/dist/channels/weixin/ilink/send.js +108 -0
- package/dist/channels/weixin/ilink/session_guard.d.ts +6 -0
- package/dist/channels/weixin/ilink/session_guard.js +39 -0
- package/dist/channels/weixin/ilink/types.d.ts +155 -0
- package/dist/channels/weixin/ilink/types.js +10 -0
- package/dist/channels/weixin/ilink/upload.d.ts +15 -0
- package/dist/channels/weixin/ilink/upload.js +75 -0
- package/dist/channels/weixin/sync_buf_store.d.ts +3 -0
- package/dist/channels/weixin/sync_buf_store.js +19 -0
- package/dist/channels/weixin/weixin_channel_adapter.d.ts +18 -0
- package/dist/channels/weixin/weixin_channel_adapter.js +273 -0
- package/dist/channels/weixin/weixin_messaging_port.d.ts +29 -0
- package/dist/channels/weixin/weixin_messaging_port.js +113 -0
- package/dist/codex_app/client.d.ts +176 -0
- package/dist/codex_app/client.js +1230 -0
- package/dist/codex_app/deeplink.d.ts +7 -0
- package/dist/codex_app/deeplink.js +29 -0
- package/dist/codex_app/local_usage.d.ts +16 -0
- package/dist/codex_app/local_usage.js +123 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.js +131 -0
- package/dist/controller/access.d.ts +11 -0
- package/dist/controller/access.js +33 -0
- package/dist/controller/activity.d.ts +62 -0
- package/dist/controller/activity.js +330 -0
- package/dist/controller/commands.d.ts +6 -0
- package/dist/controller/commands.js +17 -0
- package/dist/controller/controller.d.ts +326 -0
- package/dist/controller/controller.js +7503 -0
- package/dist/controller/observer.d.ts +16 -0
- package/dist/controller/observer.js +98 -0
- package/dist/controller/presentation.d.ts +80 -0
- package/dist/controller/presentation.js +568 -0
- package/dist/controller/service_tier.d.ts +9 -0
- package/dist/controller/service_tier.js +32 -0
- package/dist/controller/session_observer.d.ts +22 -0
- package/dist/controller/session_observer.js +259 -0
- package/dist/controller/status.d.ts +10 -0
- package/dist/controller/status.js +28 -0
- package/dist/core/bridge_scope.d.ts +18 -0
- package/dist/core/bridge_scope.js +46 -0
- package/dist/core/channel_port.d.ts +15 -0
- package/dist/core/channel_port.js +1 -0
- package/dist/i18n.d.ts +1108 -0
- package/dist/i18n.js +1154 -0
- package/dist/lock.d.ts +7 -0
- package/dist/lock.js +80 -0
- package/dist/logger.d.ts +12 -0
- package/dist/logger.js +57 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +236 -0
- package/dist/runtime.d.ts +3 -0
- package/dist/runtime.js +14 -0
- package/dist/store/database.d.ts +79 -0
- package/dist/store/database.js +489 -0
- package/dist/store/migrate_bridge_scope.d.ts +6 -0
- package/dist/store/migrate_bridge_scope.js +59 -0
- package/dist/telegram/addressing.d.ts +33 -0
- package/dist/telegram/addressing.js +57 -0
- package/dist/telegram/api.d.ts +14 -0
- package/dist/telegram/api.js +89 -0
- package/dist/telegram/gateway.d.ts +76 -0
- package/dist/telegram/gateway.js +383 -0
- package/dist/telegram/media.d.ts +34 -0
- package/dist/telegram/media.js +180 -0
- package/dist/telegram/rendering.d.ts +10 -0
- package/dist/telegram/rendering.js +21 -0
- package/dist/telegram/scope.d.ts +6 -0
- package/dist/telegram/scope.js +24 -0
- package/dist/telegram/text.d.ts +7 -0
- package/dist/telegram/text.js +47 -0
- package/dist/types.d.ts +343 -0
- package/dist/types.js +1 -0
- package/docs/agent-assisted-install.md +84 -0
- package/docs/install-for-beginners.md +287 -0
- package/docs/troubleshooting.md +239 -0
- package/package.json +62 -0
- package/scripts/doctor.sh +3 -0
- package/scripts/launchd/install.sh +54 -0
- package/scripts/status.sh +3 -0
- package/scripts/systemd/install.sh +83 -0
- package/scripts/systemd/uninstall.sh +15 -0
- package/skills/foxclaw/SKILL.md +167 -0
- package/skills/foxclaw/agents/openai.yaml +4 -0
- package/skills/foxclaw/references/telegram-setup.md +93 -0
- package/skills/foxclaw/scripts/bootstrap_host.py +350 -0
- package/skills/foxclaw/scripts/bootstrap_remote.py +67 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface OpenUrlCommand {
|
|
2
|
+
command: string;
|
|
3
|
+
args: string[];
|
|
4
|
+
}
|
|
5
|
+
export declare function buildThreadDeepLink(threadId: string): string;
|
|
6
|
+
export declare function getOpenUrlCommand(url: string, platform?: NodeJS.Platform): OpenUrlCommand;
|
|
7
|
+
export declare function openUrl(url: string, platform?: NodeJS.Platform): Promise<void>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
export function buildThreadDeepLink(threadId) {
|
|
3
|
+
return `codex://threads/${encodeURIComponent(threadId)}`;
|
|
4
|
+
}
|
|
5
|
+
export function getOpenUrlCommand(url, platform = process.platform) {
|
|
6
|
+
switch (platform) {
|
|
7
|
+
case 'darwin':
|
|
8
|
+
return { command: 'open', args: [url] };
|
|
9
|
+
case 'win32':
|
|
10
|
+
return { command: 'cmd', args: ['/c', 'start', '', url] };
|
|
11
|
+
default:
|
|
12
|
+
return { command: 'xdg-open', args: [url] };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export async function openUrl(url, platform = process.platform) {
|
|
16
|
+
const { command, args } = getOpenUrlCommand(url, platform);
|
|
17
|
+
await new Promise((resolve, reject) => {
|
|
18
|
+
const child = spawn(command, args, {
|
|
19
|
+
stdio: 'ignore',
|
|
20
|
+
detached: true,
|
|
21
|
+
windowsHide: true,
|
|
22
|
+
});
|
|
23
|
+
child.once('error', reject);
|
|
24
|
+
child.once('spawn', () => {
|
|
25
|
+
child.unref();
|
|
26
|
+
resolve();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface CodexLocalUsageTotals {
|
|
2
|
+
inputTokens: number;
|
|
3
|
+
cachedInputTokens: number;
|
|
4
|
+
outputTokens: number;
|
|
5
|
+
reasoningOutputTokens: number;
|
|
6
|
+
totalTokens: number;
|
|
7
|
+
}
|
|
8
|
+
export interface CodexLocalUsageStats {
|
|
9
|
+
sessionFiles: number;
|
|
10
|
+
sessionsWithUsage: number;
|
|
11
|
+
turns: number;
|
|
12
|
+
usageEvents: number;
|
|
13
|
+
totals: CodexLocalUsageTotals;
|
|
14
|
+
latestSessionMtimeMs: number | null;
|
|
15
|
+
}
|
|
16
|
+
export declare function readCodexLocalUsageStats(codexHome?: string): Promise<CodexLocalUsageStats>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createReadStream } from 'node:fs';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import readline from 'node:readline';
|
|
6
|
+
export async function readCodexLocalUsageStats(codexHome = resolveCodexHome()) {
|
|
7
|
+
const sessionFiles = await listJsonlFiles([
|
|
8
|
+
path.join(codexHome, 'sessions'),
|
|
9
|
+
path.join(codexHome, 'archived_sessions'),
|
|
10
|
+
]);
|
|
11
|
+
const totals = emptyTotals();
|
|
12
|
+
const turnIds = new Set();
|
|
13
|
+
let sessionsWithUsage = 0;
|
|
14
|
+
let usageEvents = 0;
|
|
15
|
+
let latestSessionMtimeMs = null;
|
|
16
|
+
for (const filePath of sessionFiles) {
|
|
17
|
+
const stat = await fs.stat(filePath).catch(() => null);
|
|
18
|
+
if (stat) {
|
|
19
|
+
latestSessionMtimeMs = Math.max(latestSessionMtimeMs ?? 0, stat.mtimeMs);
|
|
20
|
+
}
|
|
21
|
+
const fileUsage = await readSessionUsage(filePath, turnIds);
|
|
22
|
+
usageEvents += fileUsage.usageEvents;
|
|
23
|
+
if (fileUsage.totalUsage) {
|
|
24
|
+
sessionsWithUsage += 1;
|
|
25
|
+
addUsage(totals, fileUsage.totalUsage);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
sessionFiles: sessionFiles.length,
|
|
30
|
+
sessionsWithUsage,
|
|
31
|
+
turns: turnIds.size,
|
|
32
|
+
usageEvents,
|
|
33
|
+
totals,
|
|
34
|
+
latestSessionMtimeMs,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function resolveCodexHome() {
|
|
38
|
+
return process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
39
|
+
}
|
|
40
|
+
async function listJsonlFiles(roots) {
|
|
41
|
+
const results = [];
|
|
42
|
+
for (const root of roots) {
|
|
43
|
+
await collectJsonlFiles(root, results);
|
|
44
|
+
}
|
|
45
|
+
return results;
|
|
46
|
+
}
|
|
47
|
+
async function collectJsonlFiles(targetPath, results) {
|
|
48
|
+
const stat = await fs.stat(targetPath).catch(() => null);
|
|
49
|
+
if (!stat)
|
|
50
|
+
return;
|
|
51
|
+
if (stat.isFile()) {
|
|
52
|
+
if (targetPath.endsWith('.jsonl')) {
|
|
53
|
+
results.push(targetPath);
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!stat.isDirectory())
|
|
58
|
+
return;
|
|
59
|
+
const entries = await fs.readdir(targetPath, { withFileTypes: true });
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
await collectJsonlFiles(path.join(targetPath, entry.name), results);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function readSessionUsage(filePath, turnIds) {
|
|
65
|
+
const stream = createReadStream(filePath, { encoding: 'utf8' });
|
|
66
|
+
const reader = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
67
|
+
let totalUsage = null;
|
|
68
|
+
let usageEvents = 0;
|
|
69
|
+
try {
|
|
70
|
+
for await (const line of reader) {
|
|
71
|
+
if (!line.trim())
|
|
72
|
+
continue;
|
|
73
|
+
let event;
|
|
74
|
+
try {
|
|
75
|
+
event = JSON.parse(line);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const turnId = typeof event?.payload?.turn_id === 'string' ? event.payload.turn_id : null;
|
|
81
|
+
if (turnId) {
|
|
82
|
+
turnIds.add(turnId);
|
|
83
|
+
}
|
|
84
|
+
const info = event?.payload?.info;
|
|
85
|
+
if (info?.last_token_usage || info?.lastTokenUsage) {
|
|
86
|
+
usageEvents += 1;
|
|
87
|
+
}
|
|
88
|
+
const totalTokenUsage = info?.total_token_usage ?? info?.totalTokenUsage;
|
|
89
|
+
if (totalTokenUsage) {
|
|
90
|
+
totalUsage = totalTokenUsage;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
reader.close();
|
|
96
|
+
}
|
|
97
|
+
return { usageEvents, totalUsage };
|
|
98
|
+
}
|
|
99
|
+
function emptyTotals() {
|
|
100
|
+
return {
|
|
101
|
+
inputTokens: 0,
|
|
102
|
+
cachedInputTokens: 0,
|
|
103
|
+
outputTokens: 0,
|
|
104
|
+
reasoningOutputTokens: 0,
|
|
105
|
+
totalTokens: 0,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function addUsage(totals, usage) {
|
|
109
|
+
const inputTokens = numberField(usage, 'input_tokens', 'inputTokens');
|
|
110
|
+
const outputTokens = numberField(usage, 'output_tokens', 'outputTokens');
|
|
111
|
+
const totalTokens = numberField(usage, 'total_tokens', 'totalTokens');
|
|
112
|
+
totals.inputTokens += inputTokens;
|
|
113
|
+
totals.cachedInputTokens += numberField(usage, 'cached_input_tokens', 'cachedInputTokens');
|
|
114
|
+
totals.outputTokens += outputTokens;
|
|
115
|
+
totals.reasoningOutputTokens += numberField(usage, 'reasoning_output_tokens', 'reasoningOutputTokens');
|
|
116
|
+
totals.totalTokens += totalTokens || inputTokens + outputTokens;
|
|
117
|
+
}
|
|
118
|
+
function numberField(source, snakeKey, camelKey) {
|
|
119
|
+
const snakeValue = source[snakeKey];
|
|
120
|
+
const value = snakeValue === undefined || snakeValue === null ? source[camelKey] : snakeValue;
|
|
121
|
+
const numeric = Number(value);
|
|
122
|
+
return Number.isFinite(numeric) ? numeric : 0;
|
|
123
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { LogLevel } from './logger.js';
|
|
2
|
+
import type { ApprovalPolicyValue, SandboxModeValue } from './types.js';
|
|
3
|
+
export declare const APP_HOME: string;
|
|
4
|
+
export declare const DEFAULT_STORE_PATH: string;
|
|
5
|
+
export declare const DEFAULT_STATUS_PATH: string;
|
|
6
|
+
export declare const DEFAULT_LOG_PATH: string;
|
|
7
|
+
export declare const DEFAULT_LOCK_PATH: string;
|
|
8
|
+
export declare const DEFAULT_CODEX_APP_SERVER_STATE_PATH: string;
|
|
9
|
+
export declare const DEFAULT_CODEX_APP_SERVER_LOG_PATH: string;
|
|
10
|
+
export interface AppConfig {
|
|
11
|
+
tgBotToken: string;
|
|
12
|
+
tgAllowedUserId: string;
|
|
13
|
+
tgAllowedChatId: string | null;
|
|
14
|
+
tgAllowedTopicId: number | null;
|
|
15
|
+
codexCliBin: string;
|
|
16
|
+
codexAppAutolaunch: boolean;
|
|
17
|
+
codexAppLaunchCmd: string;
|
|
18
|
+
codexAppServerStatePath: string;
|
|
19
|
+
codexAppServerLogPath: string;
|
|
20
|
+
codexAppSyncOnOpen: boolean;
|
|
21
|
+
codexAppSyncOnTurnComplete: boolean;
|
|
22
|
+
storePath: string;
|
|
23
|
+
logLevel: LogLevel;
|
|
24
|
+
defaultCwd: string;
|
|
25
|
+
defaultApprovalPolicy: ApprovalPolicyValue;
|
|
26
|
+
defaultSandboxMode: SandboxModeValue;
|
|
27
|
+
telegramPollIntervalMs: number;
|
|
28
|
+
telegramPreviewThrottleMs: number;
|
|
29
|
+
threadListLimit: number;
|
|
30
|
+
statusPath: string;
|
|
31
|
+
logPath: string;
|
|
32
|
+
lockPath: string;
|
|
33
|
+
/** When true, start Weixin (iLink) long-poll alongside Telegram. */
|
|
34
|
+
wxEnabled: boolean;
|
|
35
|
+
/** Allowed `from_user_id` values for inbound Weixin messages (empty = allow any). */
|
|
36
|
+
wxAllowedIlinkUserIds: string[];
|
|
37
|
+
weixinAccountsDir: string;
|
|
38
|
+
weixinSyncBufDir: string;
|
|
39
|
+
weixinMediaDir: string;
|
|
40
|
+
/** Optional `SKRouteTag` header for some IDC deployments. */
|
|
41
|
+
wxIlinkRouteTag: string | null;
|
|
42
|
+
}
|
|
43
|
+
export declare function loadConfig(): AppConfig;
|
|
44
|
+
export declare function ensureAppDirs(config: AppConfig): void;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import dotenv from 'dotenv';
|
|
6
|
+
export const APP_HOME = path.join(os.homedir(), '.foxclaw');
|
|
7
|
+
export const DEFAULT_STORE_PATH = path.join(APP_HOME, 'data', 'bridge.sqlite');
|
|
8
|
+
export const DEFAULT_STATUS_PATH = path.join(APP_HOME, 'runtime', 'status.json');
|
|
9
|
+
export const DEFAULT_LOG_PATH = path.join(APP_HOME, 'logs', 'service.log');
|
|
10
|
+
export const DEFAULT_LOCK_PATH = path.join(APP_HOME, 'runtime', 'bridge.lock');
|
|
11
|
+
export const DEFAULT_CODEX_APP_SERVER_STATE_PATH = path.join(APP_HOME, 'runtime', 'codex-app-server.json');
|
|
12
|
+
export const DEFAULT_CODEX_APP_SERVER_LOG_PATH = path.join(APP_HOME, 'logs', 'codex-app-server.log');
|
|
13
|
+
export function loadConfig() {
|
|
14
|
+
dotenv.config();
|
|
15
|
+
const config = {
|
|
16
|
+
tgBotToken: required('TG_BOT_TOKEN'),
|
|
17
|
+
tgAllowedUserId: required('TG_ALLOWED_USER_ID'),
|
|
18
|
+
tgAllowedChatId: optional('TG_ALLOWED_CHAT_ID'),
|
|
19
|
+
tgAllowedTopicId: nullableIntEnv('TG_ALLOWED_TOPIC_ID'),
|
|
20
|
+
codexCliBin: process.env.CODEX_CLI_BIN || resolveCommand('codex') || 'codex',
|
|
21
|
+
codexAppAutolaunch: boolEnv('CODEX_APP_AUTOLAUNCH', true),
|
|
22
|
+
codexAppLaunchCmd: process.env.CODEX_APP_LAUNCH_CMD || 'codex app',
|
|
23
|
+
codexAppServerStatePath: process.env.CODEX_APP_SERVER_STATE_PATH || DEFAULT_CODEX_APP_SERVER_STATE_PATH,
|
|
24
|
+
codexAppServerLogPath: process.env.CODEX_APP_SERVER_LOG_PATH || DEFAULT_CODEX_APP_SERVER_LOG_PATH,
|
|
25
|
+
codexAppSyncOnOpen: boolEnv('CODEX_APP_SYNC_ON_OPEN', true),
|
|
26
|
+
codexAppSyncOnTurnComplete: boolEnv('CODEX_APP_SYNC_ON_TURN_COMPLETE', false),
|
|
27
|
+
storePath: process.env.STORE_PATH || DEFAULT_STORE_PATH,
|
|
28
|
+
logLevel: parseLogLevel(process.env.LOG_LEVEL || 'info'),
|
|
29
|
+
defaultCwd: process.env.DEFAULT_CWD || process.cwd(),
|
|
30
|
+
defaultApprovalPolicy: parseApprovalPolicy(process.env.DEFAULT_APPROVAL_POLICY || 'on-request'),
|
|
31
|
+
defaultSandboxMode: parseSandboxMode(process.env.DEFAULT_SANDBOX_MODE || 'workspace-write'),
|
|
32
|
+
telegramPollIntervalMs: intEnv('TELEGRAM_POLL_INTERVAL_MS', 1200),
|
|
33
|
+
telegramPreviewThrottleMs: intEnv('TELEGRAM_PREVIEW_THROTTLE_MS', 800),
|
|
34
|
+
threadListLimit: intEnv('THREAD_LIST_LIMIT', 10),
|
|
35
|
+
statusPath: DEFAULT_STATUS_PATH,
|
|
36
|
+
logPath: DEFAULT_LOG_PATH,
|
|
37
|
+
lockPath: process.env.LOCK_PATH || DEFAULT_LOCK_PATH,
|
|
38
|
+
wxEnabled: boolEnv('WX_ENABLED', false),
|
|
39
|
+
wxAllowedIlinkUserIds: parseCommaSeparatedIds(process.env.WX_ALLOWED_ILINK_USER_IDS),
|
|
40
|
+
weixinAccountsDir: process.env.WEIXIN_ACCOUNTS_DIR || path.join(APP_HOME, 'weixin', 'accounts'),
|
|
41
|
+
weixinSyncBufDir: process.env.WEIXIN_SYNC_BUF_DIR || path.join(APP_HOME, 'weixin', 'sync-buf'),
|
|
42
|
+
weixinMediaDir: process.env.WEIXIN_MEDIA_DIR || path.join(APP_HOME, 'weixin', 'media'),
|
|
43
|
+
wxIlinkRouteTag: optional('WX_ILINK_ROUTE_TAG'),
|
|
44
|
+
};
|
|
45
|
+
ensureAppDirs(config);
|
|
46
|
+
return config;
|
|
47
|
+
}
|
|
48
|
+
export function ensureAppDirs(config) {
|
|
49
|
+
const dirs = [
|
|
50
|
+
path.dirname(config.storePath),
|
|
51
|
+
path.dirname(config.statusPath),
|
|
52
|
+
path.dirname(config.logPath),
|
|
53
|
+
path.dirname(config.lockPath),
|
|
54
|
+
path.dirname(config.codexAppServerStatePath),
|
|
55
|
+
path.dirname(config.codexAppServerLogPath),
|
|
56
|
+
];
|
|
57
|
+
if (config.wxEnabled) {
|
|
58
|
+
dirs.push(config.weixinAccountsDir, config.weixinSyncBufDir, config.weixinMediaDir);
|
|
59
|
+
}
|
|
60
|
+
for (const dir of dirs) {
|
|
61
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function parseCommaSeparatedIds(raw) {
|
|
65
|
+
if (!raw?.trim())
|
|
66
|
+
return [];
|
|
67
|
+
return raw
|
|
68
|
+
.split(',')
|
|
69
|
+
.map((s) => s.trim())
|
|
70
|
+
.filter(Boolean);
|
|
71
|
+
}
|
|
72
|
+
function required(key) {
|
|
73
|
+
const value = process.env[key];
|
|
74
|
+
if (!value || !value.trim()) {
|
|
75
|
+
throw new Error(`${key} is required`);
|
|
76
|
+
}
|
|
77
|
+
return value.trim();
|
|
78
|
+
}
|
|
79
|
+
function optional(key) {
|
|
80
|
+
const value = process.env[key];
|
|
81
|
+
if (!value || !value.trim())
|
|
82
|
+
return null;
|
|
83
|
+
return value.trim();
|
|
84
|
+
}
|
|
85
|
+
function intEnv(key, fallback) {
|
|
86
|
+
const value = process.env[key];
|
|
87
|
+
if (!value)
|
|
88
|
+
return fallback;
|
|
89
|
+
const parsed = Number.parseInt(value, 10);
|
|
90
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
91
|
+
}
|
|
92
|
+
function nullableIntEnv(key) {
|
|
93
|
+
const value = process.env[key];
|
|
94
|
+
if (!value || !value.trim())
|
|
95
|
+
return null;
|
|
96
|
+
const parsed = Number.parseInt(value, 10);
|
|
97
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
98
|
+
}
|
|
99
|
+
function boolEnv(key, fallback) {
|
|
100
|
+
const value = process.env[key];
|
|
101
|
+
if (!value)
|
|
102
|
+
return fallback;
|
|
103
|
+
return value !== 'false' && value !== '0';
|
|
104
|
+
}
|
|
105
|
+
function parseLogLevel(value) {
|
|
106
|
+
if (value === 'debug' || value === 'info' || value === 'warn' || value === 'error')
|
|
107
|
+
return value;
|
|
108
|
+
return 'info';
|
|
109
|
+
}
|
|
110
|
+
function parseApprovalPolicy(value) {
|
|
111
|
+
if (value === 'on-failure' || value === 'never' || value === 'untrusted' || value === 'on-request')
|
|
112
|
+
return value;
|
|
113
|
+
return 'on-request';
|
|
114
|
+
}
|
|
115
|
+
function parseSandboxMode(value) {
|
|
116
|
+
if (value === 'read-only' || value === 'workspace-write' || value === 'danger-full-access')
|
|
117
|
+
return value;
|
|
118
|
+
return 'workspace-write';
|
|
119
|
+
}
|
|
120
|
+
function resolveCommand(commandName) {
|
|
121
|
+
try {
|
|
122
|
+
const which = process.platform === 'win32' ? 'where' : 'which';
|
|
123
|
+
const result = spawnSync(which, [commandName], { encoding: 'utf8' });
|
|
124
|
+
if (result.status !== 0)
|
|
125
|
+
return null;
|
|
126
|
+
return String(result.stdout).trim().split(/\r?\n/, 1)[0] || null;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AppConfig } from '../config.js';
|
|
2
|
+
import type { AccessPresetValue, ApprovalPolicyValue, ChatSessionSettings, SandboxModeValue } from '../types.js';
|
|
3
|
+
export interface ResolvedAccessMode {
|
|
4
|
+
preset: AccessPresetValue;
|
|
5
|
+
approvalPolicy: ApprovalPolicyValue;
|
|
6
|
+
sandboxMode: SandboxModeValue;
|
|
7
|
+
}
|
|
8
|
+
export declare const ACCESS_PRESETS: AccessPresetValue[];
|
|
9
|
+
export declare function normalizeAccessPreset(value: string | null | undefined): AccessPresetValue | null;
|
|
10
|
+
export declare function resolveConfiguredAccessPreset(settings: Pick<ChatSessionSettings, 'accessPreset'> | null | undefined): AccessPresetValue;
|
|
11
|
+
export declare function resolveAccessMode(config: Pick<AppConfig, 'defaultApprovalPolicy' | 'defaultSandboxMode'>, settings: Pick<ChatSessionSettings, 'accessPreset'> | null | undefined): ResolvedAccessMode;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const ACCESS_PRESETS = ['read-only', 'default', 'full-access'];
|
|
2
|
+
export function normalizeAccessPreset(value) {
|
|
3
|
+
if (value === 'read-only' || value === 'default' || value === 'full-access') {
|
|
4
|
+
return value;
|
|
5
|
+
}
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
export function resolveConfiguredAccessPreset(settings) {
|
|
9
|
+
return settings?.accessPreset ?? 'default';
|
|
10
|
+
}
|
|
11
|
+
export function resolveAccessMode(config, settings) {
|
|
12
|
+
const preset = resolveConfiguredAccessPreset(settings);
|
|
13
|
+
switch (preset) {
|
|
14
|
+
case 'read-only':
|
|
15
|
+
return {
|
|
16
|
+
preset,
|
|
17
|
+
approvalPolicy: 'on-request',
|
|
18
|
+
sandboxMode: 'read-only',
|
|
19
|
+
};
|
|
20
|
+
case 'full-access':
|
|
21
|
+
return {
|
|
22
|
+
preset,
|
|
23
|
+
approvalPolicy: 'never',
|
|
24
|
+
sandboxMode: 'danger-full-access',
|
|
25
|
+
};
|
|
26
|
+
default:
|
|
27
|
+
return {
|
|
28
|
+
preset: 'default',
|
|
29
|
+
approvalPolicy: config.defaultApprovalPolicy,
|
|
30
|
+
sandboxMode: config.defaultSandboxMode,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { JsonRpcNotification } from '../codex_app/client.js';
|
|
2
|
+
export interface RawExecCommandEvent {
|
|
3
|
+
callId: string;
|
|
4
|
+
turnId: string;
|
|
5
|
+
command: string[];
|
|
6
|
+
cwd: string | null;
|
|
7
|
+
parsedCmd: any[];
|
|
8
|
+
}
|
|
9
|
+
export type TurnActivityState = 'thinking' | 'reading' | 'searching' | 'editing' | 'running_command' | 'approval_waiting' | 'completed' | 'interrupted';
|
|
10
|
+
export type TurnOutputKind = 'commentary' | 'final_answer' | 'tool_summary' | 'error';
|
|
11
|
+
export type TurnActivityEvent = {
|
|
12
|
+
kind: 'user_message';
|
|
13
|
+
turnId: string;
|
|
14
|
+
text: string;
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'agent_message_started';
|
|
17
|
+
turnId: string;
|
|
18
|
+
itemId: string;
|
|
19
|
+
phase: string | null;
|
|
20
|
+
outputKind: TurnOutputKind;
|
|
21
|
+
isPlan?: boolean;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'agent_message_delta';
|
|
24
|
+
turnId: string;
|
|
25
|
+
itemId: string;
|
|
26
|
+
delta: string;
|
|
27
|
+
outputKind: TurnOutputKind;
|
|
28
|
+
isPlan?: boolean;
|
|
29
|
+
} | {
|
|
30
|
+
kind: 'agent_message_completed';
|
|
31
|
+
turnId: string;
|
|
32
|
+
itemId: string;
|
|
33
|
+
phase: string | null;
|
|
34
|
+
text: string | null;
|
|
35
|
+
outputKind: TurnOutputKind;
|
|
36
|
+
isPlan?: boolean;
|
|
37
|
+
} | {
|
|
38
|
+
kind: 'reasoning_started';
|
|
39
|
+
turnId: string;
|
|
40
|
+
state: 'thinking';
|
|
41
|
+
} | {
|
|
42
|
+
kind: 'reasoning_completed';
|
|
43
|
+
turnId: string;
|
|
44
|
+
state: 'thinking';
|
|
45
|
+
} | {
|
|
46
|
+
kind: 'tool_started';
|
|
47
|
+
turnId: string;
|
|
48
|
+
exec: RawExecCommandEvent;
|
|
49
|
+
state: TurnActivityState;
|
|
50
|
+
} | {
|
|
51
|
+
kind: 'tool_completed';
|
|
52
|
+
turnId: string;
|
|
53
|
+
exec: RawExecCommandEvent;
|
|
54
|
+
state: TurnActivityState;
|
|
55
|
+
} | {
|
|
56
|
+
kind: 'turn_completed';
|
|
57
|
+
turnId: string;
|
|
58
|
+
state: 'completed' | 'interrupted';
|
|
59
|
+
};
|
|
60
|
+
export declare function normalizeTurnActivityEvent(notification: JsonRpcNotification): TurnActivityEvent | null;
|
|
61
|
+
export declare function classifyAgentOutput(phase: string | null, completed: boolean): TurnOutputKind;
|
|
62
|
+
export declare function inferToolActivityState(event: RawExecCommandEvent): TurnActivityState;
|