@controlflow-ai/daemon 0.1.2 → 0.1.4
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.md +54 -6
- package/bin/daemon.js +6 -1
- package/package.json +3 -1
- package/src/agent-avatar.ts +30 -0
- package/src/agent-key.ts +28 -0
- package/src/agent-permissions.ts +359 -0
- package/src/agent-runtime.ts +795 -28
- package/src/agent-workspace.ts +183 -0
- package/src/app.ts +1970 -79
- package/src/args.ts +54 -7
- package/src/cli.ts +873 -14
- package/src/client.ts +472 -10
- package/src/coco.ts +9 -40
- package/src/codex.ts +33 -5
- package/src/config.ts +28 -4
- package/src/console.ts +230 -20
- package/src/daemon-client.ts +116 -3
- package/src/daemon.ts +937 -99
- package/src/db.ts +3128 -122
- package/src/delivery-ws.ts +269 -0
- package/src/format.ts +4 -1
- package/src/lark/cli.ts +3 -3
- package/src/lark/event-router.ts +60 -4
- package/src/lark/inbound-events.ts +156 -3
- package/src/lark/server-integration.ts +659 -111
- package/src/lark/ws-daemon.ts +136 -10
- package/src/local-api.ts +545 -15
- package/src/local-auth.ts +33 -1
- package/src/message-attachments.ts +71 -0
- package/src/messaging-cli.ts +741 -0
- package/src/messaging-status.ts +669 -0
- package/src/migrations/024_agents_model.ts +10 -0
- package/src/migrations/025_room_archive.ts +44 -0
- package/src/migrations/026_project_archive.ts +44 -0
- package/src/migrations/027_agent_permission_profiles.ts +16 -0
- package/src/migrations/028_lark_websocket_restart_state.ts +16 -0
- package/src/migrations/029_held_message_drafts.ts +32 -0
- package/src/migrations/030_agent_room_read_state.ts +25 -0
- package/src/migrations/031_room_tasks.ts +29 -0
- package/src/migrations/032_room_reminders.ts +29 -0
- package/src/migrations/033_room_saved_messages.ts +25 -0
- package/src/migrations/034_agent_activity_events.ts +27 -0
- package/src/migrations/035_agent_avatars.ts +17 -0
- package/src/migrations/036_project_agent_defaults.ts +21 -0
- package/src/migrations/037_message_attachments.ts +36 -0
- package/src/migrations/038_agent_activity_room_scope.ts +64 -0
- package/src/migrations/039_message_attachments_path.ts +34 -0
- package/src/migrations/040_message_attachments_file_schema.ts +80 -0
- package/src/migrations/041_room_system_events.ts +30 -0
- package/src/migrations/042_message_attachment_file_kind.ts +52 -0
- package/src/migrations/043_room_mode_skill_registry.ts +92 -0
- package/src/migrations/044_workflow_runtime.ts +69 -0
- package/src/migrations/045_skill_repository_ownership.ts +64 -0
- package/src/migrations.ts +69 -1
- package/src/neeko.ts +40 -4
- package/src/runtime-env.ts +179 -0
- package/src/runtime-registry.ts +83 -13
- package/src/server.ts +244 -4
- package/src/token-file.ts +13 -6
- package/src/types.ts +362 -0
- package/src/workflow-runtime.ts +275 -0
- package/src/web.ts +0 -904
package/src/codex.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { buildPalPrompt, runtimeCwd, type AgentRuntime, type AgentRuntimeRunInput } from './agent-runtime.js';
|
|
1
|
+
import { buildPalPrompt, runtimeLaunchRoot, runtimeCwd, type AgentRuntime, type AgentRuntimeRunInput } from './agent-runtime.js';
|
|
2
|
+
import { buildCodexPermissionArgs, buildRuntimeLaunchContext, effectivePermissionProfile, normalizeWritableRoot, writableRootsForProfile, type AgentPermissionProfile, type RuntimeLaunchContext } from './agent-permissions.js';
|
|
2
3
|
export { runAgentRuntime } from './agent-runtime.js';
|
|
3
4
|
|
|
4
5
|
function extractThreadId(stdout: string): string | null {
|
|
@@ -17,19 +18,46 @@ function extractThreadId(stdout: string): string | null {
|
|
|
17
18
|
return null;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
function tomlString(value: string): string {
|
|
22
|
+
return JSON.stringify(value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function codexResumeConfigArgs(profile: AgentPermissionProfile, context: RuntimeLaunchContext): string[] {
|
|
26
|
+
if (profile.filesystemMode === 'full-access') {
|
|
27
|
+
return ['-c', 'sandbox_mode="danger-full-access"'];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const writableRoots = writableRootsForProfile(profile, context)
|
|
31
|
+
.filter((root) => normalizeWritableRoot(root) !== normalizeWritableRoot(context.runtimeRoot));
|
|
32
|
+
const args = [
|
|
33
|
+
'-c',
|
|
34
|
+
'sandbox_mode="workspace-write"',
|
|
35
|
+
'-c',
|
|
36
|
+
'sandbox_workspace_write.network_access=true',
|
|
37
|
+
];
|
|
38
|
+
if (writableRoots.length > 0) {
|
|
39
|
+
args.push('-c', `sandbox_workspace_write.writable_roots=[${writableRoots.map(tomlString).join(',')}]`);
|
|
40
|
+
}
|
|
41
|
+
return args;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function makeCodexRuntime(_agentUuid: string, model?: string | null): AgentRuntime {
|
|
45
|
+
const modelArgs = model?.trim() ? ['--model', model.trim()] : [];
|
|
21
46
|
return {
|
|
22
47
|
name: 'codex',
|
|
23
|
-
capabilities: { protocol: 'json-stream', resume: 'runtime-session-id', busyDeliveryMode: 'queue', supportsMcp: false },
|
|
48
|
+
capabilities: { protocol: 'json-stream', resume: 'runtime-session-id', busyDeliveryMode: 'queue', supportsMcp: false, supportsSteer: false },
|
|
24
49
|
command: 'codex',
|
|
25
50
|
buildPrompt: buildPalPrompt,
|
|
26
51
|
buildCwd: runtimeCwd,
|
|
27
52
|
buildArgs(input: AgentRuntimeRunInput): string[] {
|
|
28
53
|
const prompt = buildPalPrompt(input);
|
|
54
|
+
const context = input.launchContext ?? buildRuntimeLaunchContext(input);
|
|
55
|
+
const permissionProfile = effectivePermissionProfile(input);
|
|
56
|
+
const permissionArgs = buildCodexPermissionArgs(permissionProfile, context);
|
|
29
57
|
if (input.runtimeSessionId) {
|
|
30
|
-
return ['exec', 'resume', '--json', '--
|
|
58
|
+
return ['exec', 'resume', '--json', '--skip-git-repo-check', ...codexResumeConfigArgs(permissionProfile, context), ...modelArgs, ...input.extraArgs, input.runtimeSessionId, prompt];
|
|
31
59
|
}
|
|
32
|
-
return ['exec', '--json', '--
|
|
60
|
+
return ['exec', '--json', ...permissionArgs, '--skip-git-repo-check', '--cd', runtimeLaunchRoot(input), ...modelArgs, ...input.extraArgs, prompt];
|
|
33
61
|
},
|
|
34
62
|
parseOutput({ stdout, stderr, input }) {
|
|
35
63
|
return {
|
package/src/config.ts
CHANGED
|
@@ -96,11 +96,35 @@ export function privatePalCliBinDir(): string {
|
|
|
96
96
|
return join(homeDir(), 'bin');
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
export
|
|
99
|
+
export interface EnsurePrivatePalCliBinOptions {
|
|
100
|
+
platform?: NodeJS.Platform;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function cmdQuoted(path: string): string {
|
|
104
|
+
return `"${path.replace(/"/g, '""')}"`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function psQuoted(path: string): string {
|
|
108
|
+
return `'${path.replace(/'/g, "''")}'`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function privatePalCliExecutableName(platform: NodeJS.Platform = process.platform): string {
|
|
112
|
+
return platform === 'win32' ? 'pal.cmd' : 'pal';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function ensurePrivatePalCliBin(options: EnsurePrivatePalCliBinOptions = {}): string {
|
|
116
|
+
const platform = options.platform ?? process.platform;
|
|
100
117
|
const binDir = privatePalCliBinDir();
|
|
101
118
|
ensureDir(binDir);
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
119
|
+
const cliPath = repoCliPath();
|
|
120
|
+
for (const name of ['pal', 'pal.cmd', 'pal.ps1']) {
|
|
121
|
+
rmSync(join(binDir, name), { force: true });
|
|
122
|
+
}
|
|
123
|
+
if (platform === 'win32') {
|
|
124
|
+
writeFileSync(join(binDir, 'pal.cmd'), `@echo off\r\nbun ${cmdQuoted(cliPath)} %*\r\n`);
|
|
125
|
+
writeFileSync(join(binDir, 'pal.ps1'), `& bun ${psQuoted(cliPath)} @args\r\n`);
|
|
126
|
+
} else {
|
|
127
|
+
symlinkSync(cliPath, join(binDir, 'pal'));
|
|
128
|
+
}
|
|
105
129
|
return binDir;
|
|
106
130
|
}
|
package/src/console.ts
CHANGED
|
@@ -6,8 +6,10 @@ import { defaultServerUrl } from './config.js';
|
|
|
6
6
|
import { formatMessages } from './format.js';
|
|
7
7
|
import { defaultLarkConfigPath, findCredentialByAgent, loadLarkCredentials, saveLarkCredentials, unbindCredentialAgent, upsertCredential } from './lark/credentials.js';
|
|
8
8
|
import { registerLarkAppFromDeviceFlow, resolveLarkBotInfo, type LarkBotInfoResult } from './lark/setup.js';
|
|
9
|
+
import { runtimeModelOptions, validateRuntimeModel } from './runtime-registry.js';
|
|
9
10
|
import type { LarkRegistrationComplete } from './lark/app-registration.js';
|
|
10
11
|
import type { Computer, ProvisionedComputer } from './types.js';
|
|
12
|
+
import { runMessagingCommand } from './messaging-cli.js';
|
|
11
13
|
|
|
12
14
|
interface Prompt {
|
|
13
15
|
askLine(label: string): Promise<string>;
|
|
@@ -26,14 +28,27 @@ Usage:
|
|
|
26
28
|
bun run src/console.ts run-action <run-id> kill|restart
|
|
27
29
|
bun run src/console.ts computers list [--json]
|
|
28
30
|
bun run src/console.ts computer onboard [--interactive] [--name <display-name>] [--server-url <url>] [--package-name <npm-package>]
|
|
31
|
+
bun run src/console.ts computers reconnect-command [--interactive] [--computer-id <machine>] [--server-url <url>] [--package-name <npm-package>] [--json]
|
|
32
|
+
bun run src/console.ts computers delete [--interactive] [--computer-id <machine>] [--json]
|
|
29
33
|
bun run src/console.ts agents list [--json]
|
|
30
|
-
bun run src/console.ts agents onboard [--interactive] [--key <agent-key>] [--name <display-name>] [--runtime codex] [--computer-id <machine>]
|
|
31
|
-
bun run src/console.ts agents create --key <agent-key> --name <display-name> [--runtime neeko|coco|
|
|
32
|
-
bun run src/console.ts agents update [--interactive] --key <agent-key> [--runtime neeko|coco|
|
|
34
|
+
bun run src/console.ts agents onboard [--interactive] [--key <agent-key>] [--name <display-name>] [--runtime codex] [--model <model>] [--computer-id <machine>]
|
|
35
|
+
bun run src/console.ts agents create --key <agent-key> --name <display-name> [--runtime neeko|coco|codex] [--model <model>] [--desc <description>]
|
|
36
|
+
bun run src/console.ts agents update [--interactive] --key <agent-key> [--runtime neeko|coco|codex] [--model <model>]
|
|
33
37
|
[--lark-app-id <id> --lark-app-secret <secret>] [--lark-label <name>] [--lark-config <path>] [--rebind-lark] [--unbind-lark] [--no-reload]
|
|
38
|
+
bun run src/console.ts agents delete --key <agent-key> [--yes] [--lark-config <path>] [--no-reload] [--json]
|
|
34
39
|
bun run src/console.ts lark-users list [--json]
|
|
35
40
|
bun run src/console.ts lark-users add [--interactive] --user-id <union-id> [--name <display-name>]
|
|
36
41
|
bun run src/console.ts lark-users delete --user-id <union-id>
|
|
42
|
+
bun run src/console.ts messaging health [--json]
|
|
43
|
+
bun run src/console.ts messaging status [--verbose] [--json]
|
|
44
|
+
bun run src/console.ts messaging restart-lark [--app-id <id>] [--json]
|
|
45
|
+
bun run src/console.ts messaging repair-lark [--dry-run] [--json]
|
|
46
|
+
bun run src/console.ts messaging doctor-lark [--app-id <id>] [--config <path>] [--budget-ms 10000] [--json]
|
|
47
|
+
bun run src/console.ts messaging watch-lark [--app-id <id>] [--timeout-ms 60000] [--interval-ms 1000] [--json]
|
|
48
|
+
bun run src/console.ts messaging verify-lark-ingress [--app-id <id>] [--timeout-ms 60000] [--interval-ms 1000] [--json]
|
|
49
|
+
bun run src/console.ts messaging recent-lark [--limit 20] [--json]
|
|
50
|
+
bun run src/console.ts messaging probe-lark --app-id <id> --sender-user-id <union-id> --chat-id <chat-id> [--mention-open-id <open-id>] [--text <message>] [--json]
|
|
51
|
+
bun run src/console.ts messaging probe-delivery [--agent lock] [--room <room>] [--timeout-ms 120000] [--json]
|
|
37
52
|
bun run src/console.ts lark <list|daemon|events|send> [flags]
|
|
38
53
|
|
|
39
54
|
Environment:
|
|
@@ -62,11 +77,11 @@ async function reloadLarkIntegration(serverUrl: string): Promise<void> {
|
|
|
62
77
|
console.log('Lark integration reloaded.');
|
|
63
78
|
}
|
|
64
79
|
|
|
65
|
-
async function resolveBotInfoForConsole(appId: string, appSecret: string): Promise<LarkBotInfoResult> {
|
|
80
|
+
async function resolveBotInfoForConsole(appId: string, appSecret: string, options: { budgetMs?: number } = {}): Promise<LarkBotInfoResult> {
|
|
66
81
|
if (process.env.NODE_ENV === 'test' && process.env.PAL_TEST_LARK_BOT_OPEN_ID) {
|
|
67
82
|
return { ok: true, openId: process.env.PAL_TEST_LARK_BOT_OPEN_ID };
|
|
68
83
|
}
|
|
69
|
-
return resolveLarkBotInfo(appId, appSecret);
|
|
84
|
+
return resolveLarkBotInfo(appId, appSecret, options);
|
|
70
85
|
}
|
|
71
86
|
|
|
72
87
|
async function registerLarkAppForConsole(): Promise<LarkRegistrationComplete | null> {
|
|
@@ -167,7 +182,7 @@ async function askRequired(prompt: Prompt, label: string, defaultValue = ''): Pr
|
|
|
167
182
|
}
|
|
168
183
|
|
|
169
184
|
async function askRuntime(prompt: Prompt, defaultValue: string): Promise<string> {
|
|
170
|
-
const runtimes = ['codex', 'neeko', 'coco'
|
|
185
|
+
const runtimes = ['codex', 'neeko', 'coco'];
|
|
171
186
|
const fallback = runtimes.includes(defaultValue) ? defaultValue : 'codex';
|
|
172
187
|
console.log('Runtime:');
|
|
173
188
|
runtimes.forEach((runtime, index) => {
|
|
@@ -183,6 +198,29 @@ async function askRuntime(prompt: Prompt, defaultValue: string): Promise<string>
|
|
|
183
198
|
}
|
|
184
199
|
}
|
|
185
200
|
|
|
201
|
+
async function askModel(prompt: Prompt, runtime: string, defaultValue = ''): Promise<string> {
|
|
202
|
+
const models = await runtimeModelOptions(runtime);
|
|
203
|
+
if (models.length === 0) {
|
|
204
|
+
console.log(`Model: ${runtime} has no built-in model list; using runtime default.`);
|
|
205
|
+
return '';
|
|
206
|
+
}
|
|
207
|
+
const fallback = models.includes(defaultValue) ? defaultValue : models[0]!;
|
|
208
|
+
console.log('Model:');
|
|
209
|
+
console.log(' 0. Runtime default');
|
|
210
|
+
models.forEach((model, index) => {
|
|
211
|
+
const marker = model === fallback ? ' (default)' : '';
|
|
212
|
+
console.log(` ${index + 1}. ${model}${marker}`);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
while (true) {
|
|
216
|
+
const answer = await ask(prompt, `Choose ${runtime} model`, fallback);
|
|
217
|
+
if (answer === '0') return '';
|
|
218
|
+
const selected = models[Number(answer) - 1] ?? answer;
|
|
219
|
+
if (models.includes(selected)) return selected;
|
|
220
|
+
console.log(`Model must be one of: ${models.join(', ')}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
186
224
|
async function askYesNo(prompt: Prompt, label: string, defaultValue = false): Promise<boolean> {
|
|
187
225
|
const fallback = defaultValue ? 'Y/n' : 'y/N';
|
|
188
226
|
while (true) {
|
|
@@ -241,7 +279,7 @@ async function collectComputerOnboardInput(serverClient: LockClient, flags: Reco
|
|
|
241
279
|
const interactive = boolFlag(flags, 'interactive') || (!hasAnyProvisionFlag && process.stdin.isTTY === true);
|
|
242
280
|
const defaultName = flag(flags, 'name') ?? 'Local computer';
|
|
243
281
|
const defaultServerUrl = flag(flags, 'server-url') ?? serverClient.baseUrl;
|
|
244
|
-
const defaultPackageName = flag(flags, 'package-name') ?? '
|
|
282
|
+
const defaultPackageName = flag(flags, 'package-name') ?? 'bun run daemon';
|
|
245
283
|
|
|
246
284
|
if (!interactive) {
|
|
247
285
|
return {
|
|
@@ -257,12 +295,12 @@ async function collectComputerOnboardInput(serverClient: LockClient, flags: Reco
|
|
|
257
295
|
console.log('Provision a computer credential and daemon start command.');
|
|
258
296
|
const name = await askRequired(prompt, 'Computer display name', defaultName);
|
|
259
297
|
const serverUrl = await askRequired(prompt, 'Server URL', defaultServerUrl);
|
|
260
|
-
const packageName = await askRequired(prompt, 'Daemon package', defaultPackageName);
|
|
298
|
+
const packageName = await askRequired(prompt, 'Daemon command/package', defaultPackageName);
|
|
261
299
|
console.log('');
|
|
262
300
|
console.log('Summary:');
|
|
263
301
|
console.log(` name: ${name}`);
|
|
264
302
|
console.log(` server_url: ${serverUrl}`);
|
|
265
|
-
console.log(`
|
|
303
|
+
console.log(` command_prefix: ${packageName}`);
|
|
266
304
|
if (!await askYesNo(prompt, 'Provision this computer?', true)) throw new Error('computer onboarding cancelled');
|
|
267
305
|
return { name, serverUrl, packageName };
|
|
268
306
|
} finally {
|
|
@@ -329,7 +367,7 @@ async function collectLarkBindFlags(prompt: Prompt, flags: Record<string, string
|
|
|
329
367
|
};
|
|
330
368
|
}
|
|
331
369
|
|
|
332
|
-
async function collectOnboardInput(serverClient: LockClient, flags: Record<string, string | boolean>): Promise<{ agentKey: string; displayName: string; runtime: string; desc: string | null; computerId: string | undefined; larkFlags?: Record<string, string | boolean> }> {
|
|
370
|
+
async function collectOnboardInput(serverClient: LockClient, flags: Record<string, string | boolean>): Promise<{ agentKey: string; displayName: string; runtime: string; model: string | null; desc: string | null; computerId: string | undefined; larkFlags?: Record<string, string | boolean> }> {
|
|
333
371
|
const hasRequiredFlags = Boolean(flag(flags, 'key') && flag(flags, 'name'));
|
|
334
372
|
const interactive = boolFlag(flags, 'interactive') || (!hasRequiredFlags && process.stdin.isTTY === true);
|
|
335
373
|
if (!interactive) {
|
|
@@ -338,6 +376,7 @@ async function collectOnboardInput(serverClient: LockClient, flags: Record<strin
|
|
|
338
376
|
agentKey: flag(flags, 'key')!,
|
|
339
377
|
displayName: flag(flags, 'name')!,
|
|
340
378
|
runtime: flag(flags, 'runtime') ?? 'codex',
|
|
379
|
+
model: await validateRuntimeModel(flag(flags, 'runtime') ?? 'codex', flag(flags, 'model')),
|
|
341
380
|
desc: flag(flags, 'desc') ?? null,
|
|
342
381
|
computerId: flag(flags, 'computer-id'),
|
|
343
382
|
larkFlags: flag(flags, 'lark-app-id') || flag(flags, 'app-id') ? flags : undefined,
|
|
@@ -351,6 +390,7 @@ async function collectOnboardInput(serverClient: LockClient, flags: Record<strin
|
|
|
351
390
|
const agentKey = await askRequired(prompt, 'Agent key', flag(flags, 'key') ?? 'codex');
|
|
352
391
|
const displayName = await askRequired(prompt, 'Display name', flag(flags, 'name') ?? agentKey);
|
|
353
392
|
const runtime = await askRuntime(prompt, flag(flags, 'runtime') ?? 'codex');
|
|
393
|
+
const model = await askModel(prompt, runtime, flag(flags, 'model') ?? '');
|
|
354
394
|
const desc = await ask(prompt, 'Description (optional)', flag(flags, 'desc') ?? '');
|
|
355
395
|
const shouldAssign = Boolean(flag(flags, 'computer-id')) || await askYesNo(prompt, 'Assign this agent to a computer?', false);
|
|
356
396
|
const computerId = shouldAssign
|
|
@@ -361,6 +401,7 @@ async function collectOnboardInput(serverClient: LockClient, flags: Record<strin
|
|
|
361
401
|
console.log(` agent_key: ${agentKey}`);
|
|
362
402
|
console.log(` display_name: ${displayName}`);
|
|
363
403
|
console.log(` runtime: ${runtime}`);
|
|
404
|
+
console.log(` model: ${model || '-'}`);
|
|
364
405
|
console.log(` description: ${desc || '-'}`);
|
|
365
406
|
console.log(` computer_id: ${computerId || '-'}`);
|
|
366
407
|
if (!await askYesNo(prompt, 'Create/update this agent?', true)) throw new Error('onboarding cancelled');
|
|
@@ -370,6 +411,7 @@ async function collectOnboardInput(serverClient: LockClient, flags: Record<strin
|
|
|
370
411
|
agentKey,
|
|
371
412
|
displayName,
|
|
372
413
|
runtime,
|
|
414
|
+
model: model || null,
|
|
373
415
|
desc: desc || null,
|
|
374
416
|
computerId: computerId || undefined,
|
|
375
417
|
larkFlags,
|
|
@@ -380,13 +422,47 @@ async function collectOnboardInput(serverClient: LockClient, flags: Record<strin
|
|
|
380
422
|
}
|
|
381
423
|
|
|
382
424
|
async function collectAgentUpdateInput(serverClient: LockClient, flags: Record<string, string | boolean>): Promise<{ agentKey: string; flags: Record<string, string | boolean> }> {
|
|
383
|
-
if (flag(flags, 'runtime')) throw new Error('agents update --interactive currently supports only Lark bot binding or unbinding');
|
|
384
|
-
|
|
385
425
|
const prompt = await createPrompt();
|
|
386
426
|
try {
|
|
387
|
-
console.log('Agent
|
|
427
|
+
console.log('Agent settings');
|
|
388
428
|
const agents = await listAgentRecords(serverClient);
|
|
389
429
|
const agentKey = await askAgentKey(prompt, agents, flag(flags, 'key') ?? (agents.length === 1 ? '1' : ''));
|
|
430
|
+
const agent = agents.find((candidate) => candidate.agent_key === agentKey);
|
|
431
|
+
if (!agent) throw new Error(`agent ${agentKey} not found`);
|
|
432
|
+
|
|
433
|
+
if (flag(flags, 'model')) return { agentKey, flags };
|
|
434
|
+
|
|
435
|
+
const requestedLarkUpdate = Boolean(
|
|
436
|
+
boolFlag(flags, 'unbind-lark') ||
|
|
437
|
+
flag(flags, 'lark-app-id') ||
|
|
438
|
+
flag(flags, 'app-id') ||
|
|
439
|
+
flag(flags, 'lark-app-secret') ||
|
|
440
|
+
flag(flags, 'app-secret'),
|
|
441
|
+
);
|
|
442
|
+
let updateAction = requestedLarkUpdate ? (boolFlag(flags, 'unbind-lark') ? '3' : '2') : '';
|
|
443
|
+
if (!updateAction) {
|
|
444
|
+
console.log('Update:');
|
|
445
|
+
console.log(' 1. Model');
|
|
446
|
+
console.log(' 2. Bind/rebind Lark bot');
|
|
447
|
+
console.log(' 3. Unbind Lark bot');
|
|
448
|
+
updateAction = await ask(prompt, 'Choose update', '1');
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (updateAction === '1' || updateAction.toLowerCase() === 'model') {
|
|
452
|
+
const runtime = typeof agent.runtime === 'string' ? agent.runtime : '';
|
|
453
|
+
if (!runtime) throw new Error(`agent ${agentKey} has no runtime configured`);
|
|
454
|
+
const model = await askModel(prompt, runtime, typeof agent.model === 'string' ? agent.model : '');
|
|
455
|
+
console.log('');
|
|
456
|
+
console.log('Summary:');
|
|
457
|
+
console.log(` agent_key: ${agentKey}`);
|
|
458
|
+
console.log(` runtime: ${runtime}`);
|
|
459
|
+
console.log(` model: ${model || '-'}`);
|
|
460
|
+
if (!await askYesNo(prompt, 'Update this agent model?', true)) throw new Error('agent update cancelled');
|
|
461
|
+
return { agentKey, flags: { ...flags, model } };
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (updateAction !== '2' && updateAction !== '3') throw new Error('Choose 1, 2, or 3.');
|
|
465
|
+
|
|
390
466
|
const configPath = larkConfigPath(flags);
|
|
391
467
|
const store = loadLarkCredentials(configPath);
|
|
392
468
|
const existing = findCredentialByAgent(store, agentKey);
|
|
@@ -396,7 +472,20 @@ async function collectAgentUpdateInput(serverClient: LockClient, flags: Record<s
|
|
|
396
472
|
console.log('Current Lark bot: -');
|
|
397
473
|
}
|
|
398
474
|
|
|
399
|
-
|
|
475
|
+
if (updateAction === '3') {
|
|
476
|
+
if (!existing) throw new Error(`agent ${agentKey} has no Lark bot binding`);
|
|
477
|
+
if (!await askYesNo(prompt, `Unbind ${existing.appId} from ${agentKey}?`, false)) throw new Error('Lark bot unbind cancelled');
|
|
478
|
+
return {
|
|
479
|
+
agentKey,
|
|
480
|
+
flags: {
|
|
481
|
+
...flags,
|
|
482
|
+
'lark-config': configPath,
|
|
483
|
+
'unbind-lark': true,
|
|
484
|
+
},
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const defaultAction = '1';
|
|
400
489
|
console.log('Lark action:');
|
|
401
490
|
console.log(` 1. ${existing ? 'Bind/rebind bot' : 'Bind bot'}`);
|
|
402
491
|
if (existing) console.log(' 2. Unbind bot');
|
|
@@ -432,6 +521,32 @@ async function collectAgentUpdateInput(serverClient: LockClient, flags: Record<s
|
|
|
432
521
|
}
|
|
433
522
|
}
|
|
434
523
|
|
|
524
|
+
async function collectAgentDeleteInput(serverClient: LockClient, flags: Record<string, string | boolean>): Promise<string> {
|
|
525
|
+
const agents = await listAgentRecords(serverClient);
|
|
526
|
+
const explicitKey = flag(flags, 'key') ?? flag(flags, 'agent');
|
|
527
|
+
const interactive = boolFlag(flags, 'interactive') || (!explicitKey && process.stdin.isTTY === true);
|
|
528
|
+
if (!interactive) {
|
|
529
|
+
if (!explicitKey) throw new Error('agents delete requires --key when not running interactively');
|
|
530
|
+
if (!boolFlag(flags, 'yes') && !boolFlag(flags, 'force')) throw new Error('agents delete requires --yes to confirm non-interactively');
|
|
531
|
+
return explicitKey;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const prompt = await createPrompt();
|
|
535
|
+
try {
|
|
536
|
+
console.log('Delete agent');
|
|
537
|
+
const agentKey = await askAgentKey(prompt, agents, explicitKey ?? (agents.length === 1 ? '1' : ''));
|
|
538
|
+
const agent = agents.find((candidate) => candidate.agent_key === agentKey);
|
|
539
|
+
console.log('');
|
|
540
|
+
console.log('This will remove the agent record, computer assignment, room memberships, room delivery subscriptions, provider bindings, and active deliveries.');
|
|
541
|
+
console.log(` agent_key: ${agentKey}`);
|
|
542
|
+
console.log(` display_name: ${agent?.display_name ?? '-'}`);
|
|
543
|
+
if (!await askYesNo(prompt, `Delete ${agentKey}?`, false)) throw new Error('agent delete cancelled');
|
|
544
|
+
return agentKey;
|
|
545
|
+
} finally {
|
|
546
|
+
prompt.close();
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
435
550
|
async function collectLarkUserInput(flags: Record<string, string | boolean>): Promise<{ userId: string; displayName: string | null }> {
|
|
436
551
|
const userIdFlag = flag(flags, 'user-id') ?? flag(flags, 'id');
|
|
437
552
|
const interactive = boolFlag(flags, 'interactive') || (!userIdFlag && process.stdin.isTTY === true);
|
|
@@ -569,6 +684,52 @@ async function main(): Promise<void> {
|
|
|
569
684
|
return;
|
|
570
685
|
}
|
|
571
686
|
|
|
687
|
+
if (sub === 'reconnect-command') {
|
|
688
|
+
let computerId = flag(args.flags, 'computer-id');
|
|
689
|
+
const interactive = boolFlag(args.flags, 'interactive') || (!computerId && process.stdin.isTTY === true);
|
|
690
|
+
if (interactive) {
|
|
691
|
+
const prompt = await createPrompt();
|
|
692
|
+
try {
|
|
693
|
+
computerId = await askComputerId(prompt, await serverClient.listComputers());
|
|
694
|
+
} finally {
|
|
695
|
+
prompt.close();
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (!computerId) throw new Error('--computer-id is required');
|
|
699
|
+
const provisioned = await serverClient.regenerateComputerCommand(computerId, {
|
|
700
|
+
server_url: flag(args.flags, 'server-url') ?? serverClient.baseUrl,
|
|
701
|
+
package_name: flag(args.flags, 'package-name') ?? 'bun run daemon',
|
|
702
|
+
});
|
|
703
|
+
if (args.flags.json) {
|
|
704
|
+
printJson(provisioned);
|
|
705
|
+
} else {
|
|
706
|
+
console.log('Computer reconnect command generated');
|
|
707
|
+
printProvisionedComputer(provisioned);
|
|
708
|
+
}
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (sub === 'delete') {
|
|
713
|
+
let computerId = flag(args.flags, 'computer-id');
|
|
714
|
+
const interactive = boolFlag(args.flags, 'interactive') || (!computerId && process.stdin.isTTY === true);
|
|
715
|
+
if (interactive) {
|
|
716
|
+
const prompt = await createPrompt();
|
|
717
|
+
try {
|
|
718
|
+
computerId = await askComputerId(prompt, await serverClient.listComputers());
|
|
719
|
+
} finally {
|
|
720
|
+
prompt.close();
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (!computerId) throw new Error('--computer-id is required');
|
|
724
|
+
const result = await serverClient.deleteComputer(computerId);
|
|
725
|
+
if (args.flags.json) {
|
|
726
|
+
printJson(result);
|
|
727
|
+
} else {
|
|
728
|
+
console.log(`Computer deleted: ${result.computer.id}`);
|
|
729
|
+
}
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
|
|
572
733
|
throw new Error(`unknown ${args.command} subcommand: ${sub ?? '(none)'}\n\n${usage()}`);
|
|
573
734
|
}
|
|
574
735
|
|
|
@@ -581,7 +742,7 @@ async function main(): Promise<void> {
|
|
|
581
742
|
printJson(agents);
|
|
582
743
|
} else {
|
|
583
744
|
for (const agent of agents) {
|
|
584
|
-
console.log(`${agent.agent_key} name="${agent.display_name}" runtime=${agent.runtime ?? '-'} desc=${agent.description ?? '-'}`);
|
|
745
|
+
console.log(`${agent.agent_key} name="${agent.display_name}" runtime=${agent.runtime ?? '-'} model=${agent.model ?? '-'} desc=${agent.description ?? '-'}`);
|
|
585
746
|
}
|
|
586
747
|
}
|
|
587
748
|
return;
|
|
@@ -591,6 +752,7 @@ async function main(): Promise<void> {
|
|
|
591
752
|
const agentKey = flag(args.flags, 'key');
|
|
592
753
|
const displayName = flag(args.flags, 'name');
|
|
593
754
|
const runtime = flag(args.flags, 'runtime');
|
|
755
|
+
const model = flag(args.flags, 'model');
|
|
594
756
|
const desc = flag(args.flags, 'desc');
|
|
595
757
|
if (!agentKey) throw new Error('--key is required');
|
|
596
758
|
if (!displayName) throw new Error('--name is required');
|
|
@@ -602,6 +764,7 @@ async function main(): Promise<void> {
|
|
|
602
764
|
agent_key: agentKey,
|
|
603
765
|
display_name: displayName,
|
|
604
766
|
runtime: runtime ?? null,
|
|
767
|
+
model: await validateRuntimeModel(runtime ?? '', model),
|
|
605
768
|
description: desc ?? null,
|
|
606
769
|
}),
|
|
607
770
|
});
|
|
@@ -612,7 +775,7 @@ async function main(): Promise<void> {
|
|
|
612
775
|
}
|
|
613
776
|
|
|
614
777
|
if (sub === 'onboard') {
|
|
615
|
-
const { agentKey, displayName, runtime, desc, computerId, larkFlags } = await collectOnboardInput(serverClient, args.flags);
|
|
778
|
+
const { agentKey, displayName, runtime, model, desc, computerId, larkFlags } = await collectOnboardInput(serverClient, args.flags);
|
|
616
779
|
if (!agentKey) throw new Error('agent key is required');
|
|
617
780
|
if (!displayName) throw new Error('display name is required');
|
|
618
781
|
|
|
@@ -623,6 +786,7 @@ async function main(): Promise<void> {
|
|
|
623
786
|
agent_key: agentKey,
|
|
624
787
|
display_name: displayName,
|
|
625
788
|
runtime,
|
|
789
|
+
model,
|
|
626
790
|
description: desc,
|
|
627
791
|
computer_id: computerId,
|
|
628
792
|
}),
|
|
@@ -640,18 +804,29 @@ async function main(): Promise<void> {
|
|
|
640
804
|
: { agentKey: flag(args.flags, 'key') ?? '', flags: args.flags };
|
|
641
805
|
const agentKey = updateInput.agentKey;
|
|
642
806
|
const updateFlags = updateInput.flags;
|
|
643
|
-
const runtime = flag(
|
|
807
|
+
const runtime = flag(updateFlags, 'runtime');
|
|
808
|
+
const hasModel = Object.prototype.hasOwnProperty.call(updateFlags, 'model');
|
|
809
|
+
const model = flag(updateFlags, 'model');
|
|
644
810
|
if (!agentKey) throw new Error('--key is required');
|
|
645
811
|
const hasLarkBinding = Boolean(flag(updateFlags, 'lark-app-id') || flag(updateFlags, 'app-id') || flag(updateFlags, 'lark-app-secret') || flag(updateFlags, 'app-secret'));
|
|
646
812
|
const hasLarkUnbind = boolFlag(updateFlags, 'unbind-lark');
|
|
647
|
-
if (!runtime && !hasLarkBinding && !hasLarkUnbind) throw new Error('--runtime, --lark-app-id/--lark-app-secret, or --unbind-lark is required');
|
|
813
|
+
if (!runtime && !hasModel && !hasLarkBinding && !hasLarkUnbind) throw new Error('--runtime, --model, --lark-app-id/--lark-app-secret, or --unbind-lark is required');
|
|
648
814
|
|
|
649
815
|
let agent: Record<string, unknown> | undefined;
|
|
650
|
-
if (runtime) {
|
|
816
|
+
if (runtime || hasModel) {
|
|
817
|
+
if (hasModel && typeof model !== 'string') throw new Error('--model requires a value');
|
|
818
|
+
const existingAgent = hasModel && !runtime
|
|
819
|
+
? (await listAgentRecords(serverClient)).find((candidate) => candidate.agent_key === agentKey)
|
|
820
|
+
: undefined;
|
|
821
|
+
if (hasModel && !runtime && !existingAgent) throw new Error(`agent ${agentKey} not found`);
|
|
822
|
+
const runtimeForModel = runtime ?? (typeof existingAgent?.runtime === 'string' ? existingAgent.runtime : '');
|
|
651
823
|
const response = await fetch(`${serverClient.baseUrl}/api/agents/${encodeURIComponent(agentKey)}`, {
|
|
652
824
|
method: 'PATCH',
|
|
653
825
|
headers: { 'content-type': 'application/json' },
|
|
654
|
-
body: JSON.stringify({
|
|
826
|
+
body: JSON.stringify({
|
|
827
|
+
...(runtime ? { runtime } : {}),
|
|
828
|
+
...(hasModel ? { model: await validateRuntimeModel(runtimeForModel, model) } : {}),
|
|
829
|
+
}),
|
|
655
830
|
});
|
|
656
831
|
const payload = await response.json() as { data?: { agent: Record<string, unknown> }; message?: string };
|
|
657
832
|
if (!response.ok) throw new Error(payload.message ?? `update failed: ${response.status}`);
|
|
@@ -667,6 +842,36 @@ async function main(): Promise<void> {
|
|
|
667
842
|
return;
|
|
668
843
|
}
|
|
669
844
|
|
|
845
|
+
if (sub === 'delete' || sub === 'remove') {
|
|
846
|
+
const agentKey = await collectAgentDeleteInput(serverClient, args.flags);
|
|
847
|
+
const params = new URLSearchParams();
|
|
848
|
+
const configPath = larkConfigPath(args.flags);
|
|
849
|
+
if (configPath) params.set('lark_config', configPath);
|
|
850
|
+
const suffix = params.toString() ? `?${params}` : '';
|
|
851
|
+
const data = await requestJson<{ deletion: Record<string, unknown>; lark: { unbound: boolean; app_id: string | null; error?: string | null } }>(
|
|
852
|
+
`${serverClient.baseUrl}/api/agents/${encodeURIComponent(agentKey)}${suffix}`,
|
|
853
|
+
{
|
|
854
|
+
method: 'DELETE',
|
|
855
|
+
headers: { 'content-type': 'application/json' },
|
|
856
|
+
body: JSON.stringify({ confirm_delete: true, leave_rooms: true, unbind_lark: true }),
|
|
857
|
+
},
|
|
858
|
+
);
|
|
859
|
+
if (data.lark.unbound && !boolFlag(args.flags, 'no-reload')) await reloadLarkIntegration(serverClient.baseUrl);
|
|
860
|
+
if (args.flags.json) {
|
|
861
|
+
printJson(data);
|
|
862
|
+
} else {
|
|
863
|
+
const deletion = data.deletion;
|
|
864
|
+
console.log(`Deleted agent ${agentKey}.`);
|
|
865
|
+
console.log(` rooms_left: ${deletion.rooms_left}`);
|
|
866
|
+
console.log(` subscriptions_removed: ${deletion.subscriptions_removed}`);
|
|
867
|
+
console.log(` active_deliveries_canceled: ${deletion.active_deliveries_canceled}`);
|
|
868
|
+
console.log(` running_runs_marked_kill: ${deletion.running_runs_marked_kill}`);
|
|
869
|
+
if (data.lark.unbound) console.log(` lark_unbound: ${data.lark.app_id}`);
|
|
870
|
+
if (data.lark.error) console.log(` lark_unbind_error: ${data.lark.error}`);
|
|
871
|
+
}
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
|
|
670
875
|
throw new Error(`unknown agents subcommand: ${sub ?? '(none)'}\n\n${usage()}`);
|
|
671
876
|
}
|
|
672
877
|
|
|
@@ -710,6 +915,11 @@ async function main(): Promise<void> {
|
|
|
710
915
|
throw new Error(`unknown lark-users subcommand: ${sub ?? '(none)'}\n\n${usage()}`);
|
|
711
916
|
}
|
|
712
917
|
|
|
918
|
+
if (args.command === 'messaging') {
|
|
919
|
+
await runMessagingCommand(serverClient, args, usage);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
|
|
713
923
|
if (args.command === 'lark') {
|
|
714
924
|
const { runLarkCli } = await import('./lark/cli.js');
|
|
715
925
|
const code = await runLarkCli({
|