@questionbase/deskfree 0.3.0-alpha.9 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,727 +1,419 @@
1
- import { IncomingMessage, ServerResponse } from 'node:http';
1
+ import { PluginLogger, DeskFreeClient, DeskFreeTool, ChatMessage, RuntimeBootstrapConfig } from '@deskfree/core';
2
+ import * as _anthropic_ai_claude_agent_sdk from '@anthropic-ai/claude-agent-sdk';
3
+ import { McpSdkServerConfigWithInstance, SDKMessage, SDKUserMessage, Query, tool } from '@anthropic-ai/claude-agent-sdk';
2
4
 
5
+ interface StartAgentOptions {
6
+ log?: PluginLogger;
7
+ }
8
+ /** Dispose function returned by startAgent — call to shut down cleanly. */
9
+ type DisposeAgent = () => void;
3
10
  /**
4
- * Minimal type definitions for the OpenClaw plugin API.
5
- * These mirror the actual OpenClaw types but are defined inline
6
- * to avoid a hard dependency on the openclaw package at build time.
7
- * At runtime, the real types are provided by OpenClaw.
11
+ * Full agent startup sequence:
12
+ *
13
+ * 1. Load local config (token + apiUrl from env)
14
+ * 2. Create DeskFree client
15
+ * 3. Bootstrap: fetch config from API (wsUrl, model, region, tools)
16
+ * 4. Install enabled tool packages
17
+ * 5. Configure Agent SDK env vars (Bedrock, region, model)
18
+ * 6. Create MCP servers
19
+ * 7. Start health server + WS gateway + heartbeat
8
20
  */
21
+ declare function startAgent(opts?: StartAgentOptions): Promise<DisposeAgent>;
9
22
 
10
- interface AnyAgentTool {
11
- name: string;
12
- description: string;
13
- parameters: Record<string, unknown>;
14
- execute: (id: string, params: Record<string, unknown>) => Promise<{
15
- content: Array<{
16
- type: string;
17
- text?: string;
18
- }>;
19
- }>;
20
- optional?: boolean;
21
- }
22
- interface OpenClawPluginToolContext {
23
- config?: unknown;
24
- messageChannel?: unknown;
25
- agentAccountId?: string;
26
- sandboxed?: boolean;
27
- }
28
- type OpenClawPluginToolFactory = (ctx: OpenClawPluginToolContext) => AnyAgentTool | AnyAgentTool[] | null | undefined;
29
- interface OpenClawPluginToolOptions {
30
- optional?: boolean;
31
- }
32
- interface OpenClawConfig {
33
- channels?: {
34
- deskfree?: DeskFreeChannelConfig;
35
- [key: string]: unknown;
36
- };
37
- plugins?: {
38
- entries?: {
39
- [pluginId: string]: {
40
- enabled?: boolean;
41
- [key: string]: unknown;
42
- };
43
- };
44
- [key: string]: unknown;
45
- };
46
- [key: string]: unknown;
47
- }
48
- interface OpenClawPluginApi {
49
- runtime: PluginRuntime;
50
- config: OpenClawConfig;
51
- pluginConfig: Record<string, unknown>;
52
- logger: PluginLogger;
53
- registerChannel(registration: {
54
- plugin: ChannelPlugin;
55
- }): void;
56
- registerHttpRoute(params: {
57
- path: string;
58
- handler: (req: IncomingMessage, res: ServerResponse) => Promise<void> | void;
59
- }): void;
60
- registerTool(tool: AnyAgentTool | OpenClawPluginToolFactory, opts?: OpenClawPluginToolOptions): void;
61
- registerHook(events: string | string[], handler: (event: Record<string, unknown>) => Promise<unknown> | unknown, opts?: {
62
- priority?: number;
63
- }): void;
64
- on<K extends PluginHookName>(hookName: K, handler: PluginHookHandlerMap[K], opts?: {
65
- priority?: number;
66
- }): void;
67
- }
68
- type PluginHookName = 'before_agent_start' | 'llm_input' | 'llm_output' | 'agent_end' | 'before_compaction' | 'after_compaction' | 'before_reset' | 'message_received' | 'message_sending' | 'message_sent' | 'before_tool_call' | 'after_tool_call' | 'tool_result_persist' | 'session_start' | 'session_end' | 'gateway_start' | 'gateway_stop';
69
- interface PluginHookAgentContext {
70
- agentId?: string;
71
- sessionKey?: string;
23
+ interface OrchestratorQueryOptions {
24
+ prompt: string;
25
+ orchestratorServer: McpSdkServerConfigWithInstance;
26
+ model: string;
27
+ /** Agent SDK session ID to resume (for multi-turn conversations). */
72
28
  sessionId?: string;
73
- workspaceDir?: string;
74
- messageProvider?: string;
75
29
  }
76
- interface PluginHookBeforeAgentStartEvent {
30
+ /**
31
+ * Run the orchestrator agent. Returns an async iterable of SDKMessage events.
32
+ *
33
+ * The orchestrator:
34
+ * - Uses DeskFree orchestrator tools via MCP
35
+ * - Dispatches workers via the deskfree_dispatch_worker MCP tool
36
+ * - Persists sessions for multi-turn conversations
37
+ */
38
+ declare function runOrchestrator(opts: OrchestratorQueryOptions): AsyncGenerator<SDKMessage, void>;
39
+ interface HeartbeatOptions {
77
40
  prompt: string;
78
- messages?: unknown[];
79
- }
80
- interface PluginHookBeforeAgentStartResult {
81
- systemPrompt?: string;
82
- prependContext?: string;
83
- }
84
- type PluginHookHandlerMap = {
85
- before_agent_start: (event: PluginHookBeforeAgentStartEvent, ctx: PluginHookAgentContext) => Promise<PluginHookBeforeAgentStartResult | void> | PluginHookBeforeAgentStartResult | void;
86
- llm_input: (event: Record<string, unknown>, ctx: PluginHookAgentContext) => Promise<void> | void;
87
- llm_output: (event: Record<string, unknown>, ctx: PluginHookAgentContext) => Promise<void> | void;
88
- agent_end: (event: Record<string, unknown>, ctx: PluginHookAgentContext) => Promise<void> | void;
89
- before_compaction: (event: Record<string, unknown>, ctx: PluginHookAgentContext) => Promise<void> | void;
90
- after_compaction: (event: Record<string, unknown>, ctx: PluginHookAgentContext) => Promise<void> | void;
91
- before_reset: (event: Record<string, unknown>, ctx: PluginHookAgentContext) => Promise<void> | void;
92
- message_received: (event: Record<string, unknown>, ctx: Record<string, unknown>) => Promise<void> | void;
93
- message_sending: (event: Record<string, unknown>, ctx: Record<string, unknown>) => Promise<Record<string, unknown> | void> | Record<string, unknown> | void;
94
- message_sent: (event: Record<string, unknown>, ctx: Record<string, unknown>) => Promise<void> | void;
95
- before_tool_call: (event: Record<string, unknown>, ctx: Record<string, unknown>) => Promise<Record<string, unknown> | void> | Record<string, unknown> | void;
96
- after_tool_call: (event: Record<string, unknown>, ctx: Record<string, unknown>) => Promise<void> | void;
97
- tool_result_persist: (event: Record<string, unknown>, ctx: Record<string, unknown>) => Record<string, unknown> | void;
98
- session_start: (event: Record<string, unknown>, ctx: Record<string, unknown>) => Promise<void> | void;
99
- session_end: (event: Record<string, unknown>, ctx: Record<string, unknown>) => Promise<void> | void;
100
- gateway_start: (event: Record<string, unknown>, ctx: Record<string, unknown>) => Promise<void> | void;
101
- gateway_stop: (event: Record<string, unknown>, ctx: Record<string, unknown>) => Promise<void> | void;
102
- };
103
- interface PluginRuntime {
104
- version: string;
105
- config: {
106
- loadConfig(): OpenClawConfig;
107
- writeConfigFile(cfg: OpenClawConfig): Promise<void>;
108
- };
109
- channel: {
110
- reply: {
111
- finalizeInboundContext<T extends Record<string, unknown>>(ctx: T): T & FinalizedMsgContext;
112
- dispatchReplyFromConfig(params: {
113
- ctx: FinalizedMsgContext;
114
- cfg: OpenClawConfig;
115
- dispatcher: ReplyDispatcher;
116
- replyOptions?: Record<string, unknown>;
117
- }): Promise<{
118
- queuedFinal: boolean;
119
- }>;
120
- createReplyDispatcherWithTyping(params: {
121
- channel: string;
122
- accountId: string;
123
- deliver: (payload: {
124
- text?: string;
125
- body?: string;
126
- }) => Promise<void>;
127
- [key: string]: unknown;
128
- }): {
129
- dispatcher: ReplyDispatcher;
130
- replyOptions: Record<string, unknown>;
131
- markDispatchIdle: () => void;
132
- };
133
- };
134
- text: {
135
- chunkMarkdownText(text: string, limit: number): string[];
136
- };
137
- };
138
- logging: {
139
- createLogger(name: string): PluginLogger;
140
- };
141
- state: {
142
- resolveStateDir(): string;
143
- };
144
- }
145
- interface FinalizedMsgContext {
146
- Body: string;
147
- RawBody?: string;
148
- ChatType: string;
149
- Provider: string;
150
- Surface: string;
151
- From: string;
152
- To: string;
153
- MessageSid: string;
154
- Timestamp?: string;
155
- BodyForAgent?: string;
156
- [key: string]: unknown;
157
- }
158
- interface ReplyDispatcher {
159
- send(reply: unknown): Promise<void>;
160
- getQueuedCounts(): unknown;
161
- }
162
- interface PluginLogger {
163
- info(msg: string): void;
164
- warn(msg: string): void;
165
- error(msg: string): void;
166
- debug(msg: string): void;
167
- }
168
- interface ChannelMessagingTargetResolver {
169
- hint?: string;
170
- looksLikeId?(raw: string, normalized?: string): boolean;
171
- }
172
- interface ChannelMessagingAdapter {
173
- normalizeTarget?(raw: string): string | undefined;
174
- targetResolver?: ChannelMessagingTargetResolver;
175
- }
176
- interface ChannelPlugin {
177
- id: string;
178
- meta: ChannelMeta;
179
- capabilities: ChannelCapabilities;
180
- config: ChannelConfigAdapter;
181
- outbound?: ChannelOutboundAdapter;
182
- gateway?: ChannelGatewayAdapter;
183
- setup?: ChannelSetupAdapter;
184
- security?: ChannelSecurityAdapter;
185
- messaging?: ChannelMessagingAdapter;
186
- onboarding?: ChannelOnboardingAdapter;
187
- status?: ChannelStatusAdapter;
188
- }
189
- interface ChannelMeta {
190
- name: string;
191
- icon?: string;
192
- description?: string;
193
- }
194
- interface ChannelCapabilities {
195
- text: boolean;
196
- media: boolean;
197
- reactions: boolean;
198
- threads: boolean;
199
- editing: boolean;
200
- }
201
- interface ChannelConfigAdapter {
202
- listAccountIds(cfg: OpenClawConfig): string[];
203
- resolveAccount(cfg: OpenClawConfig, accountId?: string | null): ResolvedDeskFreeAccount;
204
- isConfigured?(account: ResolvedDeskFreeAccount, cfg?: OpenClawConfig): boolean;
205
- isEnabled?(account: ResolvedDeskFreeAccount, cfg?: OpenClawConfig): boolean;
206
- describeAccount?(account: ResolvedDeskFreeAccount, cfg?: OpenClawConfig): ChannelAccountSnapshot;
207
- }
208
- interface ChannelOutboundAdapter {
209
- deliveryMode: 'direct' | 'gateway' | 'hybrid';
210
- resolveTarget?(target: string, ctx: {
211
- cfg: OpenClawConfig;
212
- accountId?: string | null;
213
- }): {
214
- to: string;
215
- } | null;
216
- sendText?(ctx: ChannelOutboundContext): Promise<OutboundDeliveryResult>;
217
- textChunkLimit?: number;
218
- chunkerMode?: 'text' | 'markdown';
219
- }
220
- interface ChannelGatewayAdapter {
221
- startAccount?(ctx: ChannelGatewayContext): Promise<unknown>;
222
- stopAccount?(ctx: ChannelGatewayContext): Promise<void>;
223
- logoutAccount?(ctx: ChannelLogoutContext): Promise<ChannelLogoutResult>;
224
- }
225
- interface ChannelGatewayContext {
226
- cfg: OpenClawConfig;
227
- accountId: string;
228
- account: ResolvedDeskFreeAccount;
229
- runtime: PluginRuntime;
230
- abortSignal: AbortSignal;
231
- log?: PluginLogger;
232
- getStatus(): ChannelAccountSnapshot;
233
- setStatus(next: Partial<ChannelAccountSnapshot>): void;
234
- }
235
- interface ChannelSetupAdapter {
236
- applyAccountConfig(params: {
237
- cfg: OpenClawConfig;
238
- accountId: string;
239
- input: Record<string, string>;
240
- }): OpenClawConfig;
241
- validateInput?(params: {
242
- cfg: OpenClawConfig;
243
- accountId: string;
244
- input: Record<string, string>;
245
- }): string | null;
246
- }
247
- interface ChannelSecurityDmPolicy {
248
- policy: string;
249
- allowFrom?: Array<string | number> | null;
250
- policyPath?: string;
251
- allowFromPath: string;
252
- approveHint: string;
253
- normalizeEntry?: (raw: string) => string;
254
- }
255
- interface ChannelSecurityAdapter {
256
- resolveDmPolicy?(ctx: {
257
- cfg: OpenClawConfig;
258
- accountId?: string | null;
259
- account: ResolvedDeskFreeAccount;
260
- }): ChannelSecurityDmPolicy | null;
261
- collectWarnings?(ctx: {
262
- cfg: OpenClawConfig;
263
- accountId?: string | null;
264
- account: ResolvedDeskFreeAccount;
265
- }): Promise<string[]> | string[];
266
- }
267
- interface ChannelOutboundContext {
268
- cfg: OpenClawConfig;
269
- to: string;
270
- text: string;
271
- threadId?: string | number | null;
272
- replyToId?: string | null;
273
- accountId?: string | null;
274
- }
275
- interface OutboundDeliveryResult {
276
- channel: string;
277
- success: boolean;
278
- threadId?: string;
279
- }
280
- interface ChannelAccountSnapshot {
281
- accountId?: string;
282
- enabled?: boolean;
283
- configured?: boolean;
284
- running?: boolean;
285
- lastStartAt?: number;
286
- lastStopAt?: number;
287
- lastError?: string;
288
- probe?: ChannelProbeResult;
289
- }
290
- interface ResolvedDeskFreeAccount {
291
- accountId: string;
292
- botToken: string;
293
- apiUrl: string;
294
- wsUrl: string;
295
- userId: string;
296
- enabled: boolean;
297
- botName?: string;
298
- humanName?: string;
299
- }
300
- interface DeskFreeChannelConfig {
301
- enabled?: boolean;
302
- botToken: string;
303
- apiUrl: string;
304
- wsUrl: string;
305
- userId: string;
306
- botName?: string;
307
- humanName?: string;
308
- accounts?: Record<string, Omit<DeskFreeChannelConfig, 'accounts'>>;
309
- }
310
- interface ChatMessageAttachment {
311
- name: string;
312
- contentType: string;
313
- size: number;
314
- url: string;
315
- }
316
- interface ChatMessage {
317
- messageId: string;
318
- botId: string;
319
- humanId: string;
320
- authorType: 'bot' | 'user';
321
- content: string;
322
- attachments?: ChatMessageAttachment[];
323
- taskId?: string | null;
324
- createdAt: string;
325
- userName?: string | null;
326
- botName?: string | null;
327
- }
328
- interface MessagesResponse {
329
- items: ChatMessage[];
330
- cursor: string | null;
331
- hasMore: boolean;
332
- }
333
- interface WsTicketResponse {
334
- ticket: string;
335
- wsUrl: string;
336
- expiresAt: string;
337
- }
338
- interface WizardPrompter {
339
- text(params: {
340
- message: string;
341
- initialValue?: string;
342
- placeholder?: string;
343
- validate?: (value: string) => string | undefined;
344
- }): Promise<string>;
345
- confirm(params: {
346
- message: string;
347
- initialValue?: boolean;
348
- }): Promise<boolean>;
349
- note(message: string, title?: string): Promise<void>;
350
- }
351
- interface ChannelOnboardingStatus {
352
- channel: string;
353
- configured: boolean;
354
- statusLines: string[];
355
- selectionHint?: string;
356
- quickstartScore?: number;
357
- }
358
- interface ChannelOnboardingResult {
359
- cfg: OpenClawConfig;
360
- accountId?: string;
361
- }
362
- interface ChannelOnboardingAdapter {
363
- channel: string;
364
- getStatus(ctx: {
365
- cfg: OpenClawConfig;
366
- }): Promise<ChannelOnboardingStatus>;
367
- configure(ctx: {
368
- cfg: OpenClawConfig;
369
- runtime: PluginRuntime;
370
- prompter: WizardPrompter;
371
- accountOverrides: Record<string, string>;
372
- shouldPromptAccountIds: boolean;
373
- forceAllowFrom: boolean;
374
- }): Promise<ChannelOnboardingResult>;
375
- disable?(cfg: OpenClawConfig): OpenClawConfig;
376
- }
377
- interface ChannelProbeResult {
378
- ok: boolean;
379
- error?: string;
380
- }
381
- interface ChannelStatusIssue {
382
- channel: string;
383
- accountId: string;
384
- kind: 'config' | 'auth' | 'runtime';
385
- message: string;
386
- fix?: string;
387
- }
388
- interface ChannelStatusAdapter {
389
- probeAccount?(params: {
390
- account: ResolvedDeskFreeAccount;
391
- timeoutMs: number;
392
- cfg: OpenClawConfig;
393
- }): Promise<ChannelProbeResult>;
394
- buildAccountSnapshot?(params: {
395
- account: ResolvedDeskFreeAccount;
396
- cfg: OpenClawConfig;
397
- runtime?: ChannelAccountSnapshot;
398
- probe?: ChannelProbeResult;
399
- }): ChannelAccountSnapshot;
400
- collectStatusIssues?(accounts: ChannelAccountSnapshot[]): ChannelStatusIssue[];
401
- }
402
- interface ChannelLogoutContext {
403
- cfg: OpenClawConfig;
404
- accountId: string;
405
- account: ResolvedDeskFreeAccount;
406
- runtime: PluginRuntime;
407
- log?: PluginLogger;
408
- }
409
- interface ChannelLogoutResult {
410
- cleared: boolean;
411
- loggedOut?: boolean;
412
- [key: string]: unknown;
413
- }
414
- interface WsNotification {
415
- action: 'notify';
416
- hint: string;
41
+ orchestratorServer: McpSdkServerConfigWithInstance;
42
+ model: string;
417
43
  }
418
- interface Goal {
419
- goalId: string;
420
- botId: string;
421
- createdById: string;
422
- title: string;
423
- description?: string | null;
424
- status: 'active' | 'paused' | 'completed' | 'archived';
425
- createdAt: string;
426
- updatedAt: string;
427
- }
428
- interface Task {
429
- taskId: string;
430
- title: string;
431
- status: 'ready_for_bot' | 'working_on_it' | 'waiting_for_human' | 'done';
432
- instructions?: string;
433
- createdAt: string;
434
- updatedAt: string;
435
- botId?: string | null;
436
- createdById: string;
437
- reason?: string | null;
438
- deliverable?: string | null;
439
- }
440
- interface TaskMessage {
441
- messageId: string;
442
- authorType: 'bot' | 'user';
443
- content: string;
444
- attachments: ChatMessageAttachment[];
445
- createdAt: string;
446
- userName?: string | null;
447
- }
448
- interface TaskWithContext extends Task {
449
- instructions: string;
450
- deliverable: string;
451
- messages: TaskMessage[];
44
+ /**
45
+ * Run a one-shot heartbeat query (no session persistence).
46
+ */
47
+ declare function runHeartbeat(opts: HeartbeatOptions): AsyncGenerator<SDKMessage, void>;
48
+
49
+ interface WorkerQueryOptions {
50
+ prompt: string | AsyncIterable<SDKUserMessage>;
51
+ workerServer: McpSdkServerConfigWithInstance;
52
+ model: string;
53
+ /** Agent SDK session ID to resume (for multi-turn task conversations). */
54
+ sessionId?: string;
452
55
  }
453
56
  /**
454
- * Response from the tasks.create bot endpoint.
57
+ * Run the worker agent directly. Returns a Query (async iterable of
58
+ * SDKMessage events with a .close() method for teardown).
455
59
  *
456
- * The backend mutation (`trpc.bot.tasks.create`) returns the Task object
457
- * directly (not wrapped in `{ task: ... }`), unlike `goals.create` which
458
- * returns `{ goal: Goal }`.
60
+ * Used for task thread messages where the router routes to 'runner'.
61
+ * The worker:
62
+ * - Claims a task and loads its context
63
+ * - Executes work (update files, steps, send messages)
64
+ * - Completes the task
459
65
  */
460
- type CreateTaskResponse = Task;
461
- interface CompleteTaskInput {
462
- taskId: string;
463
- outcome: 'done' | 'blocked';
464
- }
465
- interface WorkspaceStateTask {
466
- taskId: string;
467
- title: string;
468
- status: 'ready_for_bot' | 'working_on_it' | 'waiting_for_human' | 'done';
469
- instructions?: string | null;
470
- deliverable?: string | null;
471
- createdAt: string;
472
- updatedAt: string;
473
- }
474
- interface WorkspaceStateGoal {
475
- goalId: string;
476
- title: string;
477
- description?: string | null;
478
- status: 'active' | 'paused' | 'completed' | 'archived';
479
- tasks: WorkspaceStateTask[];
480
- }
481
- interface WorkspaceState {
482
- goals: WorkspaceStateGoal[];
483
- unlinkedTasks: WorkspaceStateTask[];
484
- recentlyDone: WorkspaceStateTask[];
485
- }
66
+ declare function runWorker(opts: WorkerQueryOptions): Query;
486
67
 
487
- /** Enhanced error class for DeskFree API errors with user-friendly messages */
488
- declare class DeskFreeError extends Error {
489
- readonly type: 'network' | 'auth' | 'server' | 'client' | 'timeout' | 'invalid_response';
490
- readonly statusCode?: number;
491
- readonly userMessage: string;
492
- readonly procedure: string;
493
- constructor(type: DeskFreeError['type'], procedure: string, message: string, userMessage: string, statusCode?: number);
494
- static fromResponse(response: Response, procedure: string, responseText: string): DeskFreeError;
495
- static timeout(procedure: string, timeoutMs: number): DeskFreeError;
496
- static invalidResponse(procedure: string): DeskFreeError;
497
- static network(procedure: string, originalError: Error): DeskFreeError;
498
- }
499
- declare class DeskFreeClient {
500
- private botToken;
501
- private apiUrl;
502
- private requestTimeoutMs;
503
- constructor(botToken: string, apiUrl: string, options?: {
504
- requestTimeoutMs?: number;
505
- });
506
- private request;
507
- /**
508
- * Validates that a string parameter is non-empty.
509
- * Catches invalid inputs before they hit the network.
510
- */
511
- private requireNonEmpty;
68
+ /**
69
+ * Manages daily log files for memory observations.
70
+ *
71
+ * Logs are stored at `{stateDir}/memory/{botId}/daily/YYYY-MM-DD.md`.
72
+ * Each entry is a single line: `- [ISO timestamp] (task: {taskId}) {content}`
73
+ *
74
+ * Appends are atomic via O_APPEND flag (safe for writes <4KB on Linux).
75
+ */
76
+ declare class DailyLogManager {
77
+ private readonly dailyDir;
78
+ constructor(stateDir: string, botId: string);
79
+ /** Ensure the daily log directory exists. */
80
+ init(): void;
81
+ /** Append a learning entry to today's log file. */
82
+ appendLearning(content: string, taskId?: string): Promise<void>;
83
+ /** Read today's daily log, or null if it doesn't exist. */
84
+ readToday(): Promise<string | null>;
85
+ /** Read all daily logs, concatenated with date headers. */
86
+ readAllLogs(): Promise<string | null>;
512
87
  /**
513
- * Send a typing indicator to the DeskFree conversation.
514
- *
515
- * @param input - Optional parameters including taskId to scope the indicator
88
+ * Read recent daily logs up to a character budget (newest first).
89
+ * Returns as many days as fit. Quiet week → 14 days. Busy day → just today.
516
90
  */
517
- typing(input?: {
518
- taskId?: string;
519
- clear?: boolean;
520
- }): Promise<void>;
91
+ readRecentWithBudget(maxChars: number): Promise<string | null>;
92
+ /** Delete daily log files older than the given number of days. */
93
+ pruneOlderThan(days: number): void;
94
+ private todayPath;
95
+ }
96
+
97
+ interface WorkerManagerDeps {
98
+ client: DeskFreeClient;
99
+ createWorkerServer: () => McpSdkServerConfigWithInstance;
100
+ model: string;
101
+ log: PluginLogger;
102
+ /** Max concurrent workers. Defaults to 5. */
103
+ maxConcurrentWorkers?: number;
104
+ /** DailyLogManager for injecting recent observations into worker context. */
105
+ dailyLog?: DailyLogManager;
106
+ /** Character budget for daily log injection (~4 chars/token). Defaults to 16000. */
107
+ dailyLogCharBudget?: number;
108
+ /** Path to persist session history JSON (taskId → sessionId). */
109
+ sessionHistoryPath?: string;
110
+ }
111
+ declare class WorkerManager {
112
+ private workers;
113
+ /** Persists taskId → sessionId across worker restarts for session resume. */
114
+ private sessionHistory;
115
+ private pendingQueue;
116
+ private deps;
117
+ private maxConcurrent;
118
+ constructor(deps: WorkerManagerDeps);
119
+ /** Check if a worker is currently active for a task. */
120
+ has(taskId: string): boolean;
521
121
  /**
522
- * Update an existing bot message content (for streaming).
122
+ * Dispatch a new long-lived worker for a task.
123
+ * Claims the task and loads workspace state server-side, then builds
124
+ * an enriched first message so the worker can start immediately.
523
125
  *
524
- * @param input - messageId, new content, and streaming flag
126
+ * If at capacity, queues the dispatch and notifies the user.
127
+ * If a worker already exists for this taskId, returns 'already_running'.
525
128
  */
526
- updateMessage(input: {
527
- messageId: string;
528
- content: string;
529
- streaming?: boolean;
530
- }): Promise<{
531
- messageId: string;
532
- content: string;
533
- }>;
129
+ dispatch(taskId: string, userMessage?: string): Promise<'started' | 'already_running' | 'queued'>;
130
+ /** Number of active workers. */
131
+ get activeCount(): number;
132
+ /** Number of queued dispatches waiting for a slot. */
133
+ get queuedCount(): number;
134
+ /** Maximum number of concurrent workers allowed. */
135
+ get maxConcurrentWorkers(): number;
534
136
  /**
535
- * Send a text message (with optional attachments) to a DeskFree conversation.
536
- *
537
- * @param input - Message content, optional userId, taskId, and attachments
137
+ * Push a message into an active worker's channel.
138
+ * Resets the idle timer.
538
139
  */
539
- sendMessage(input: {
540
- userId?: string;
541
- content: string;
542
- taskId?: string;
543
- attachments?: Array<{
544
- s3Key: string;
545
- name: string;
546
- contentType: string;
547
- size: number;
548
- }>;
549
- }): Promise<{
550
- messageId: string;
551
- content: string;
552
- }>;
553
- /** Fetch paginated message history for a conversation. */
554
- listMessages(input: {
555
- userId?: string;
556
- cursor?: string | null;
557
- limit?: number;
558
- }): Promise<MessagesResponse>;
559
- /** Obtain a one-time WebSocket authentication ticket for real-time notifications. */
560
- getWsTicket(): Promise<WsTicketResponse>;
561
- /** Create a new goal. */
562
- createGoal(input: {
563
- title: string;
564
- description?: string;
565
- }): Promise<{
566
- goal: Goal;
567
- }>;
568
- /** Update a goal's status, title, or description. */
569
- updateGoal(input: {
570
- goalId: string;
571
- title?: string;
572
- description?: string;
573
- status?: string;
574
- }): Promise<Goal>;
575
- /** Create a new task, optionally with a recurring schedule. */
576
- createTask(input: {
577
- title: string;
578
- instructions?: string;
579
- goalId?: string;
580
- isRecurring?: boolean;
581
- recurringSchedule?: {
582
- frequency: 'daily' | 'weekly' | 'biweekly' | 'monthly';
583
- dayOfWeek?: number;
584
- dayOfMonth?: number;
585
- time: string;
586
- timezone?: string;
587
- };
588
- }): Promise<CreateTaskResponse>;
589
- /** Claim a task so the bot can begin working on it. Returns enriched context. */
590
- claimTask(input: {
591
- taskId: string;
592
- }): Promise<TaskWithContext>;
593
- /** Update the deliverable (markdown content) for a task. */
594
- updateDeliverable(input: {
595
- taskId: string;
596
- deliverable: string;
597
- }): Promise<void>;
598
- /** Send an agent status update to DeskFree. */
599
- statusUpdate(input: {
600
- status: 'idle' | 'working' | 'responding';
601
- activeSubAgents: Array<{
602
- label: string;
603
- status: 'running' | 'completed' | 'failed';
604
- startedAt: string;
605
- completedAt?: string;
606
- tokenUsage?: number;
607
- task?: string;
608
- }>;
609
- model?: string;
610
- lastActivity?: string;
611
- pluginVersion?: string;
612
- openclawVersion?: string;
613
- }): Promise<{
614
- success: boolean;
615
- }>;
616
- /** Report error log entries to the backend for CloudWatch/Slack visibility. */
617
- reportError(input: {
618
- errors: Array<{
619
- message: string;
620
- level: 'warn' | 'error' | 'fatal';
621
- timestamp: string;
622
- metadata?: Record<string, unknown>;
623
- }>;
624
- }): Promise<{
625
- accepted: number;
626
- }>;
627
- /** Get short-lived AWS credentials for S3 workspace access. */
628
- workspaceCredentials(): Promise<{
629
- accessKeyId: string;
630
- secretAccessKey: string;
631
- sessionToken: string;
632
- expiration: Date;
633
- s3Uri: string;
634
- region: string;
635
- }>;
636
- /** Notify DeskFree that workspace files have changed locally. */
637
- workspaceRead(input: {
638
- path: string;
639
- }): Promise<{
640
- path: string;
641
- content: string;
642
- lastModified: string;
643
- }>;
644
- /** Get full workspace snapshot — goals with tasks, unlinked tasks, recently done. */
645
- getState(): Promise<WorkspaceState>;
646
- /** Report token usage for a task. Atomically increments rollup columns. */
647
- reportUsage(input: {
648
- taskId: string;
649
- inputTokens: number;
650
- outputTokens: number;
651
- thinkingTokens: number;
652
- model: string;
653
- estimatedCost: number;
654
- }): Promise<{
655
- success: boolean;
656
- }>;
657
- /** Complete a task with an outcome. Moves task to waiting_for_human. */
658
- completeTask(input: CompleteTaskInput): Promise<Task & {
659
- outcome: 'done' | 'blocked';
660
- }>;
140
+ pushMessage(taskId: string, content: string): void;
141
+ /** Tear down a specific worker. */
142
+ teardown(taskId: string): void;
143
+ /** Tear down all active workers and clear the queue (shutdown hook). */
144
+ teardownAll(): void;
145
+ /** Core worker startup logic (no capacity check — callers must check first). */
146
+ private startWorker;
147
+ /** Drain the pending queue — called when a worker finishes. */
148
+ private drainQueue;
149
+ private startIdleTimer;
661
150
  /**
662
- * Lightweight health check that verifies connectivity and authentication.
663
- *
664
- * @param timeoutMs - Maximum time to wait for the probe response
665
- * @returns Probe result indicating success or a descriptive error
151
+ * Background drain loop processes query output and streams it back
152
+ * to DeskFree via DeskFreeStreamingSession.
666
153
  */
667
- probe(timeoutMs: number): Promise<ChannelProbeResult>;
154
+ private drainLoop;
155
+ /** Load session history from disk (best-effort). */
156
+ private loadSessionHistory;
157
+ /** Persist session history to disk (best-effort). */
158
+ private saveSessionHistory;
159
+ /** Build an SDKUserMessage from plain text content. */
160
+ private buildUserMessage;
668
161
  }
669
162
 
670
163
  /**
671
- * Convenience: report an error if the reporter is initialized.
672
- * No-op if called before initErrorReporter(). Never throws.
164
+ * Create an in-process MCP server with orchestrator tools.
165
+ *
166
+ * When a WorkerManager is provided, adds the deskfree_dispatch_worker
167
+ * tool that lets the orchestrator dispatch long-lived workers for tasks.
673
168
  */
674
- declare function reportError(level: 'warn' | 'error' | 'fatal', message: string, metadata?: Record<string, unknown>): void;
169
+ declare function createOrchestratorMcpServer(client: DeskFreeClient, customTools?: DeskFreeTool[], workerManager?: WorkerManager): _anthropic_ai_claude_agent_sdk.McpSdkServerConfigWithInstance;
675
170
 
171
+ interface ContentScanner {
172
+ scan(content: string): Promise<ScanResult>;
173
+ }
174
+ interface ScanResult {
175
+ safe: boolean;
176
+ reason?: string;
177
+ }
676
178
  /**
677
- * Offline queue for failed API requests.
179
+ * Default content scanner applies heuristic injection pattern detection.
180
+ * In production the backend may provide a more sophisticated scanner.
181
+ */
182
+ declare class DefaultContentScanner implements ContentScanner {
183
+ private readonly log?;
184
+ constructor(log?: PluginLogger | undefined);
185
+ scan(content: string): Promise<ScanResult>;
186
+ }
187
+ /**
188
+ * Wrap a tool's execute function with content scan verification.
189
+ * Used to gate file write operations.
678
190
  *
679
- * When the DeskFree API is unreachable (network errors, timeouts),
680
- * outbound requests are queued in memory and retried with exponential
681
- * backoff when connectivity is restored.
191
+ * @param execute - The original tool execute function
192
+ * @param extractContent - Function to extract the text content to scan from args
193
+ * @param scanner - ContentScanner implementation
194
+ */
195
+ declare function withContentScan<TArgs extends Record<string, unknown>>(execute: (args: TArgs) => Promise<unknown>, extractContent: (args: TArgs) => string | null | undefined, scanner: ContentScanner): (args: TArgs) => Promise<unknown>;
196
+ /**
197
+ * Validate a URL is safe to fetch (HTTPS only, no private IPs).
198
+ * Throws an error with a descriptive message if validation fails.
199
+ */
200
+ declare function validateDownloadUrl(url: string): void;
201
+ /**
202
+ * Check whether a content type is allowed for attachment downloads.
682
203
  */
204
+ declare function isContentTypeAllowed(contentType: string): boolean;
205
+ /**
206
+ * Sanitize a filename to prevent directory traversal and injection.
207
+ */
208
+ declare function sanitizeFileName(fileName: string): string;
683
209
 
684
- /** A queued request that can be retried. */
685
- interface QueuedRequest {
686
- id: string;
687
- /** Human-readable description for logging. */
688
- label: string;
689
- /** The function to execute. Must be idempotent for safe retries. */
690
- execute: () => Promise<void>;
691
- /** Number of retry attempts so far. */
692
- attempts: number;
693
- /** Timestamp when the request was first queued. */
694
- queuedAt: number;
695
- }
696
- declare class OfflineQueue {
697
- private queue;
698
- private processing;
699
- private retryTimer;
700
- private log;
701
- constructor(log: PluginLogger);
702
- /** Number of requests currently queued. */
703
- get size(): number;
210
+ /**
211
+ * Create an in-process MCP server with worker tools.
212
+ *
213
+ * If a ContentScanner is provided, the deskfree_update_file tool's execute
214
+ * function is wrapped to scan file content for prompt injection before
215
+ * allowing the write.
216
+ */
217
+ declare function createWorkerMcpServer(client: DeskFreeClient, customTools?: DeskFreeTool[], contentScanner?: ContentScanner, dailyLog?: DailyLogManager): _anthropic_ai_claude_agent_sdk.McpSdkServerConfigWithInstance;
218
+
219
+ /**
220
+ * MCP Tool Adapter
221
+ *
222
+ * Converts DeskFreeTool[] into Agent SDK tool() definitions.
223
+ * JSON Schema parameters are converted to Zod schemas for the SDK's
224
+ * type-safe tool() function.
225
+ */
226
+
227
+ type AgentSdkTool = ReturnType<typeof tool>;
228
+ /**
229
+ * Adapt a single DeskFreeTool into an Agent SDK tool() definition.
230
+ *
231
+ * The handler delegates to `tool.execute()` which returns a `ToolResult`
232
+ * — this is already compatible with the MCP `CallToolResult` shape:
233
+ * `{ content: Array<{ type: 'text', text: string }>, isError?: boolean }`
234
+ */
235
+ declare function adaptTool(deskfreeTool: DeskFreeTool): AgentSdkTool;
236
+ /**
237
+ * Adapt an array of DeskFree tools into Agent SDK tool() definitions.
238
+ */
239
+ declare function adaptTools(tools: DeskFreeTool[]): AgentSdkTool[];
240
+
241
+ /**
242
+ * Session Store — maps DeskFree session keys to Agent SDK session IDs.
243
+ *
244
+ * Session keys:
245
+ * dm:<peerId> — DM messages routed to orchestrator
246
+ * task:<taskId> — Task thread messages routed to worker
247
+ *
248
+ * The Agent SDK handles conversation history internally via session
249
+ * persistence. We only need to track the mapping from our session keys
250
+ * to the Agent SDK session IDs.
251
+ *
252
+ * Why in-memory is fine:
253
+ * - Container restarts are clean — agents re-claim context from backend
254
+ * - Session ID mapping is a performance optimization, not state of record
255
+ * - Backend stores all messages persistently
256
+ */
257
+ interface SessionEntry {
258
+ /** Agent SDK session ID for resuming conversations. */
259
+ agentSessionId: string;
260
+ /** Timestamp of last activity (for TTL eviction). */
261
+ lastActivityAt: number;
262
+ }
263
+ declare class SessionStore {
264
+ private readonly staleTtlMs;
265
+ private readonly maxSessions;
266
+ private readonly sessions;
267
+ private cleanupTimer;
268
+ constructor(staleTtlMs?: number, // 30 min TTL
269
+ maxSessions?: number);
704
270
  /**
705
- * Enqueue a failed request for later retry.
706
- * Returns false if the queue is full.
271
+ * Get the Agent SDK session ID for a given key.
272
+ * Returns undefined if no session exists or it has expired.
707
273
  */
708
- enqueue(request: Omit<QueuedRequest, 'attempts' | 'queuedAt'>): boolean;
274
+ getSessionId(key: string): string | undefined;
709
275
  /**
710
- * Attempt to flush all queued requests.
711
- * Called when connectivity is restored (e.g., WebSocket reconnect).
276
+ * Store an Agent SDK session ID for a given key.
712
277
  */
713
- flush(): Promise<void>;
714
- /** Clear all queued requests. */
715
- clear(): void;
716
- /** Stop the retry timer (for cleanup). */
717
- destroy(): void;
718
- private scheduleRetry;
278
+ setSessionId(key: string, agentSessionId: string): void;
279
+ /** Delete a specific session. */
280
+ delete(key: string): void;
281
+ /** Number of active sessions. */
282
+ get size(): number;
283
+ /** Remove sessions that haven't been active within the TTL. */
284
+ private cleanup;
285
+ /** Evict the session with the oldest lastActivityAt. */
286
+ private evictOldest;
287
+ /** Dispose — clear the cleanup timer. */
288
+ dispose(): void;
289
+ }
290
+
291
+ interface WorkerStatus {
292
+ activeWorkers: number;
293
+ queuedTasks: number;
294
+ maxConcurrency: number;
295
+ }
296
+ interface GatewayConfig {
297
+ client: DeskFreeClient;
298
+ wsUrl: string;
299
+ accountId: string;
300
+ stateDir: string;
301
+ log: PluginLogger;
302
+ abortSignal: AbortSignal;
303
+ onMessage: (message: ChatMessage) => Promise<void>;
304
+ getWorkerStatus?: () => WorkerStatus;
305
+ }
306
+ /**
307
+ * Start the WS gateway — reconnection loop with backoff.
308
+ *
309
+ * Loop: getWsTicket() → connect → listen for notify → poll messages → deliver
310
+ * On disconnect: backoff → reconnect
311
+ * Fallback: interval-based polling when WS is unavailable
312
+ */
313
+ declare function startGateway(config: GatewayConfig): Promise<void>;
314
+
315
+ interface ToolPackageConfig {
316
+ package: string;
317
+ version: string;
719
318
  }
319
+ /**
320
+ * Install tool packages into an isolated directory.
321
+ * Creates a minimal package.json and runs `bun install`.
322
+ *
323
+ * Tool packages are pre-bundled single files with zero deps —
324
+ * install is fast (~2-5s for a handful of tools).
325
+ *
326
+ * Security: only @deskfree/tool-* packages are allowed.
327
+ */
328
+ declare function installTools(tools: ToolPackageConfig[], toolsDir: string, log?: PluginLogger): Promise<void>;
329
+ /**
330
+ * Load installed tool packages and extract DeskFreeTool definitions.
331
+ *
332
+ * Each @deskfree/tool-* package must export one of:
333
+ * export const tools: DeskFreeTool[]
334
+ * export function createTools(): DeskFreeTool[]
335
+ */
336
+ declare function loadToolModules(tools: ToolPackageConfig[], toolsDir: string, log?: PluginLogger): Promise<DeskFreeTool[]>;
720
337
 
721
- declare const plugin: {
722
- id: string;
723
- name: string;
724
- register(api: OpenClawPluginApi): void;
725
- };
338
+ /**
339
+ * Local config: values known before connecting to the API.
340
+ * Only botToken + apiUrl are required. Everything else has sensible defaults.
341
+ */
342
+ interface LocalConfig {
343
+ /** Bot authentication token (required) */
344
+ botToken: string;
345
+ /** DeskFree backend API URL (required) */
346
+ apiUrl: string;
347
+ /** Directory for cursor + media storage */
348
+ stateDir: string;
349
+ /** Directory for dynamic tool installs */
350
+ toolsDir: string;
351
+ /** Log level */
352
+ logLevel: 'debug' | 'info' | 'warn' | 'error';
353
+ /** HTTP port for health check endpoint */
354
+ healthPort: number;
355
+ }
356
+ /**
357
+ * Full runtime config: local config + API-bootstrapped values.
358
+ * Built by merging LocalConfig with the config.get response.
359
+ * Env var overrides take precedence over API values.
360
+ */
361
+ interface RuntimeConfig extends LocalConfig {
362
+ /** DeskFree WebSocket URL */
363
+ wsUrl: string;
364
+ /** Model ID (Bedrock or Anthropic format) */
365
+ model: string;
366
+ /** AWS region for Bedrock */
367
+ awsRegion: string;
368
+ /** Heartbeat interval in ms (0 = disabled) */
369
+ heartbeatIntervalMs: number;
370
+ /** Tool packages to install */
371
+ tools: Array<{
372
+ package: string;
373
+ version: string;
374
+ }>;
375
+ /** Account ID (set after first API call) */
376
+ accountId: string;
377
+ /** LLM provider: bedrock (default), anthropic, ollama, or claude-code */
378
+ provider: 'bedrock' | 'anthropic' | 'ollama' | 'claude-code';
379
+ /** Anthropic API key (only set when provider is 'anthropic') */
380
+ anthropicApiKey?: string;
381
+ /** Base URL for Ollama-compatible API (only set when provider is 'ollama') */
382
+ baseUrl?: string;
383
+ /** Bot ID for this runtime instance */
384
+ botId: string;
385
+ /** File ID for the bot's Memory file (null if not created yet) */
386
+ memoryFileId: string | null;
387
+ /** Hour of day (0-23) in local time for the nightly sleep cycle (null = disabled) */
388
+ sleepHour: number | null;
389
+ /** Hour of day (0-23) in local time for the evening dusk planning cycle (null = disabled) */
390
+ duskHour: number | null;
391
+ /** IANA timezone string (e.g. 'America/New_York') for sleep scheduling */
392
+ timezone: string | null;
393
+ }
394
+ /**
395
+ * Load local config from environment variables.
396
+ *
397
+ * Checks DESKFREE_LAUNCH first — a base64-encoded JSON with { botToken, apiUrl }.
398
+ * Falls back to individual env vars DESKFREE_BOT_TOKEN and DESKFREE_API_URL.
399
+ */
400
+ declare function loadConfig(): LocalConfig;
401
+ /**
402
+ * Merge local config with API-bootstrapped config.
403
+ * Env var overrides take precedence over API values for debugging/testing.
404
+ */
405
+ declare function mergeWithRemoteConfig(local: LocalConfig, remote: RuntimeBootstrapConfig): RuntimeConfig;
406
+
407
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error';
408
+ /**
409
+ * Structured JSON logger that satisfies the PluginLogger interface.
410
+ * Writes newline-delimited JSON to stdout/stderr.
411
+ *
412
+ * Supports optional structured fields that are merged into the JSON entry:
413
+ * log.info('message routed', { sessionKey: 'dm:abc', target: 'orchestrator' })
414
+ */
415
+ declare function createLogger(component: string, minLevel?: LogLevel): PluginLogger;
416
+ /** Default logger for the root process. */
417
+ declare const logger: PluginLogger;
726
418
 
727
- export { type AnyAgentTool, type ChannelAccountSnapshot, type ChannelCapabilities, type ChannelConfigAdapter, type ChannelGatewayAdapter, type ChannelGatewayContext, type ChannelLogoutContext, type ChannelLogoutResult, type ChannelMessagingAdapter, type ChannelMessagingTargetResolver, type ChannelMeta, type ChannelOnboardingAdapter, type ChannelOnboardingResult, type ChannelOnboardingStatus, type ChannelOutboundAdapter, type ChannelOutboundContext, type ChannelPlugin, type ChannelProbeResult, type ChannelSecurityAdapter, type ChannelSecurityDmPolicy, type ChannelSetupAdapter, type ChannelStatusAdapter, type ChannelStatusIssue, type ChatMessage, type ChatMessageAttachment, type CompleteTaskInput, type CreateTaskResponse, type DeskFreeChannelConfig, DeskFreeClient, DeskFreeError, type FinalizedMsgContext, type Goal, type MessagesResponse, OfflineQueue, type OpenClawConfig, type OpenClawPluginApi, type OpenClawPluginToolContext, type OpenClawPluginToolFactory, type OpenClawPluginToolOptions, type OutboundDeliveryResult, type PluginHookAgentContext, type PluginHookBeforeAgentStartEvent, type PluginHookBeforeAgentStartResult, type PluginHookHandlerMap, type PluginHookName, type PluginLogger, type PluginRuntime, type ReplyDispatcher, type ResolvedDeskFreeAccount, type Task, type TaskMessage, type TaskWithContext, type WizardPrompter, type WorkspaceState, type WorkspaceStateGoal, type WorkspaceStateTask, type WsNotification, type WsTicketResponse, plugin as default, reportError };
419
+ export { type ContentScanner, DefaultContentScanner, type DisposeAgent, type GatewayConfig, type HeartbeatOptions, type LocalConfig, type LogLevel, type OrchestratorQueryOptions, type RuntimeConfig, type ScanResult, type SessionEntry, SessionStore, type StartAgentOptions, type ToolPackageConfig, type WorkerQueryOptions, adaptTool, adaptTools, createLogger, createOrchestratorMcpServer, createWorkerMcpServer, installTools, isContentTypeAllowed, loadConfig, loadToolModules, logger, mergeWithRemoteConfig, runHeartbeat, runOrchestrator, runWorker, sanitizeFileName, startAgent, startGateway, validateDownloadUrl, withContentScan };