@dobby.ai/dobby 0.1.1 → 0.1.2

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.
Files changed (136) hide show
  1. package/README.md +20 -7
  2. package/dist/src/agent/event-forwarder.js +185 -16
  3. package/dist/src/cli/commands/cron.js +39 -35
  4. package/dist/src/cli/program.js +0 -6
  5. package/dist/src/core/types.js +2 -0
  6. package/dist/src/cron/config.js +2 -2
  7. package/dist/src/cron/service.js +87 -23
  8. package/dist/src/cron/store.js +1 -1
  9. package/package.json +9 -3
  10. package/.env.example +0 -8
  11. package/AGENTS.md +0 -267
  12. package/ROADMAP.md +0 -34
  13. package/config/cron.example.json +0 -9
  14. package/config/gateway.example.json +0 -132
  15. package/dist/plugins/connector-discord/src/mapper.js +0 -75
  16. package/dist/src/cli/tests/config-command.test.js +0 -42
  17. package/dist/src/cli/tests/config-io.test.js +0 -64
  18. package/dist/src/cli/tests/config-mutators.test.js +0 -47
  19. package/dist/src/cli/tests/discord-mapper.test.js +0 -90
  20. package/dist/src/cli/tests/doctor.test.js +0 -252
  21. package/dist/src/cli/tests/init-catalog.test.js +0 -134
  22. package/dist/src/cli/tests/program-options.test.js +0 -78
  23. package/dist/src/cli/tests/routing-config.test.js +0 -254
  24. package/dist/src/core/tests/control-command.test.js +0 -17
  25. package/dist/src/core/tests/runtime-registry.test.js +0 -116
  26. package/dist/src/core/tests/typing-controller.test.js +0 -103
  27. package/docs/BOXLITE_SANDBOX_FEASIBILITY.md +0 -175
  28. package/docs/CRON_SCHEDULER_DESIGN.md +0 -374
  29. package/docs/DOCKER_SANDBOX_vs_BOXLITE.md +0 -77
  30. package/docs/EXTENSION_SYSTEM_ARCHITECTURE.md +0 -119
  31. package/docs/MVP.md +0 -135
  32. package/docs/RUNBOOK.md +0 -243
  33. package/docs/TEAMWORK_HANDOFF_DESIGN.md +0 -440
  34. package/plugins/connector-discord/dobby.manifest.json +0 -18
  35. package/plugins/connector-discord/index.js +0 -1
  36. package/plugins/connector-discord/package-lock.json +0 -360
  37. package/plugins/connector-discord/package.json +0 -38
  38. package/plugins/connector-discord/src/connector.ts +0 -345
  39. package/plugins/connector-discord/src/contribution.ts +0 -21
  40. package/plugins/connector-discord/src/mapper.ts +0 -101
  41. package/plugins/connector-discord/tsconfig.json +0 -19
  42. package/plugins/connector-feishu/dobby.manifest.json +0 -18
  43. package/plugins/connector-feishu/index.js +0 -1
  44. package/plugins/connector-feishu/package-lock.json +0 -618
  45. package/plugins/connector-feishu/package.json +0 -38
  46. package/plugins/connector-feishu/src/connector.ts +0 -343
  47. package/plugins/connector-feishu/src/contribution.ts +0 -26
  48. package/plugins/connector-feishu/src/mapper.ts +0 -401
  49. package/plugins/connector-feishu/tsconfig.json +0 -19
  50. package/plugins/plugin-sdk/index.d.ts +0 -261
  51. package/plugins/plugin-sdk/index.js +0 -1
  52. package/plugins/plugin-sdk/package-lock.json +0 -12
  53. package/plugins/plugin-sdk/package.json +0 -22
  54. package/plugins/provider-claude/dobby.manifest.json +0 -17
  55. package/plugins/provider-claude/index.js +0 -1
  56. package/plugins/provider-claude/package-lock.json +0 -3398
  57. package/plugins/provider-claude/package.json +0 -39
  58. package/plugins/provider-claude/src/contribution.ts +0 -1018
  59. package/plugins/provider-claude/tsconfig.json +0 -19
  60. package/plugins/provider-claude-cli/dobby.manifest.json +0 -17
  61. package/plugins/provider-claude-cli/index.js +0 -1
  62. package/plugins/provider-claude-cli/package-lock.json +0 -2898
  63. package/plugins/provider-claude-cli/package.json +0 -38
  64. package/plugins/provider-claude-cli/src/contribution.ts +0 -1673
  65. package/plugins/provider-claude-cli/tsconfig.json +0 -19
  66. package/plugins/provider-pi/dobby.manifest.json +0 -17
  67. package/plugins/provider-pi/index.js +0 -1
  68. package/plugins/provider-pi/package-lock.json +0 -3877
  69. package/plugins/provider-pi/package.json +0 -40
  70. package/plugins/provider-pi/src/contribution.ts +0 -606
  71. package/plugins/provider-pi/tsconfig.json +0 -19
  72. package/plugins/sandbox-core/boxlite.js +0 -1
  73. package/plugins/sandbox-core/dobby.manifest.json +0 -17
  74. package/plugins/sandbox-core/docker.js +0 -1
  75. package/plugins/sandbox-core/package-lock.json +0 -136
  76. package/plugins/sandbox-core/package.json +0 -39
  77. package/plugins/sandbox-core/src/boxlite-context.ts +0 -2
  78. package/plugins/sandbox-core/src/boxlite-contribution.ts +0 -53
  79. package/plugins/sandbox-core/src/boxlite-executor.ts +0 -911
  80. package/plugins/sandbox-core/src/docker-contribution.ts +0 -43
  81. package/plugins/sandbox-core/src/docker-executor.ts +0 -217
  82. package/plugins/sandbox-core/tsconfig.json +0 -19
  83. package/scripts/local-extensions.mjs +0 -168
  84. package/src/agent/event-forwarder.ts +0 -414
  85. package/src/cli/commands/config.ts +0 -328
  86. package/src/cli/commands/configure.ts +0 -92
  87. package/src/cli/commands/cron.ts +0 -410
  88. package/src/cli/commands/doctor.ts +0 -331
  89. package/src/cli/commands/extension.ts +0 -207
  90. package/src/cli/commands/init.ts +0 -211
  91. package/src/cli/commands/start.ts +0 -223
  92. package/src/cli/commands/topology.ts +0 -415
  93. package/src/cli/index.ts +0 -9
  94. package/src/cli/program.ts +0 -314
  95. package/src/cli/shared/config-io.ts +0 -245
  96. package/src/cli/shared/config-mutators.ts +0 -470
  97. package/src/cli/shared/config-schema.ts +0 -228
  98. package/src/cli/shared/config-types.ts +0 -129
  99. package/src/cli/shared/configure-sections.ts +0 -595
  100. package/src/cli/shared/discord-config.ts +0 -14
  101. package/src/cli/shared/init-catalog.ts +0 -249
  102. package/src/cli/shared/local-extension-specs.ts +0 -108
  103. package/src/cli/shared/runtime.ts +0 -33
  104. package/src/cli/shared/schema-prompts.ts +0 -443
  105. package/src/cli/tests/config-command.test.ts +0 -56
  106. package/src/cli/tests/config-io.test.ts +0 -92
  107. package/src/cli/tests/config-mutators.test.ts +0 -59
  108. package/src/cli/tests/discord-mapper.test.ts +0 -128
  109. package/src/cli/tests/doctor.test.ts +0 -269
  110. package/src/cli/tests/init-catalog.test.ts +0 -144
  111. package/src/cli/tests/program-options.test.ts +0 -95
  112. package/src/cli/tests/routing-config.test.ts +0 -281
  113. package/src/core/control-command.ts +0 -12
  114. package/src/core/dedup-store.ts +0 -103
  115. package/src/core/gateway.ts +0 -609
  116. package/src/core/routing.ts +0 -404
  117. package/src/core/runtime-registry.ts +0 -141
  118. package/src/core/tests/control-command.test.ts +0 -20
  119. package/src/core/tests/runtime-registry.test.ts +0 -140
  120. package/src/core/tests/typing-controller.test.ts +0 -129
  121. package/src/core/types.ts +0 -324
  122. package/src/core/typing-controller.ts +0 -119
  123. package/src/cron/config.ts +0 -154
  124. package/src/cron/schedule.ts +0 -61
  125. package/src/cron/service.ts +0 -249
  126. package/src/cron/store.ts +0 -155
  127. package/src/cron/types.ts +0 -60
  128. package/src/extension/loader.ts +0 -145
  129. package/src/extension/manager.ts +0 -355
  130. package/src/extension/manifest.ts +0 -26
  131. package/src/extension/registry.ts +0 -229
  132. package/src/main.ts +0 -8
  133. package/src/sandbox/executor.ts +0 -44
  134. package/src/sandbox/host-executor.ts +0 -118
  135. package/src/shared/dobby-repo.ts +0 -48
  136. package/tsconfig.json +0 -18
@@ -1,40 +0,0 @@
1
- {
2
- "name": "@dobby.ai/provider-pi",
3
- "version": "0.1.3",
4
- "private": false,
5
- "type": "module",
6
- "description": "Built-in pi provider extension package",
7
- "main": "./index.js",
8
- "scripts": {
9
- "build": "tsc -p tsconfig.json",
10
- "check": "tsc -p tsconfig.json --noEmit",
11
- "prepack": "npm run build"
12
- },
13
- "dependencies": {
14
- "@mariozechner/pi-agent-core": "^0.53.0",
15
- "@mariozechner/pi-ai": "^0.53.0",
16
- "@mariozechner/pi-coding-agent": "^0.53.0",
17
- "zod": "^4.3.6"
18
- },
19
- "peerDependencies": {
20
- "@dobby.ai/plugin-sdk": "^0.1.0"
21
- },
22
- "peerDependenciesMeta": {
23
- "@dobby.ai/plugin-sdk": {
24
- "optional": true
25
- }
26
- },
27
- "devDependencies": {
28
- "@dobby.ai/plugin-sdk": "file:../plugin-sdk",
29
- "@types/node": "^22.10.5",
30
- "typescript": "^5.9.2"
31
- },
32
- "files": [
33
- "index.js",
34
- "dist/",
35
- "dobby.manifest.json"
36
- ],
37
- "publishConfig": {
38
- "access": "public"
39
- }
40
- }
@@ -1,606 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
- import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
4
- import type { AgentMessage, ThinkingLevel } from "@mariozechner/pi-agent-core";
5
- import {
6
- AuthStorage,
7
- type CreateAgentSessionOptions,
8
- type ToolDefinition,
9
- createAgentSession,
10
- createBashTool,
11
- createEditTool,
12
- createFindTool,
13
- createGrepTool,
14
- createLsTool,
15
- createReadTool,
16
- createWriteTool,
17
- ModelRegistry,
18
- SessionManager,
19
- type AgentSession,
20
- type BashOperations,
21
- type EditOperations,
22
- type ReadOperations,
23
- type WriteOperations,
24
- } from "@mariozechner/pi-coding-agent";
25
- import type { Api, ImageContent, Model } from "@mariozechner/pi-ai";
26
- import { z } from "zod";
27
- import type {
28
- GatewayAgentEvent,
29
- GatewayAgentRuntime,
30
- ProviderContributionModule,
31
- ProviderInstance,
32
- ProviderInstanceCreateOptions,
33
- ProviderSessionArchiveOptions,
34
- ProviderRuntimeCreateOptions,
35
- } from "@dobby.ai/plugin-sdk";
36
-
37
- const BOXLITE_CONTEXT_CONVERSATION_KEY_ENV = "__IM_AGENT_BOXLITE_CONVERSATION_KEY";
38
- const BOXLITE_CONTEXT_PROJECT_ROOT_ENV = "__IM_AGENT_BOXLITE_PROJECT_ROOT";
39
-
40
- type RuntimeTool = NonNullable<CreateAgentSessionOptions["tools"]>[number];
41
-
42
- interface PiProviderConfig {
43
- provider: string;
44
- model: string;
45
- baseUrl: string;
46
- apiKey: string;
47
- api: Api;
48
- headers?: Record<string, string>;
49
- authHeader: boolean;
50
- models: PiProviderModelConfig[];
51
- thinkingLevel: ThinkingLevel;
52
- agentDir?: string;
53
- authFile?: string;
54
- }
55
-
56
- interface PiProviderModelConfig {
57
- id: string;
58
- name: string;
59
- api?: Api;
60
- reasoning: boolean;
61
- input: Array<"text" | "image">;
62
- cost: {
63
- input: number;
64
- output: number;
65
- cacheRead: number;
66
- cacheWrite: number;
67
- };
68
- contextWindow: number;
69
- maxTokens: number;
70
- headers?: Record<string, string>;
71
- }
72
-
73
- interface BuiltTools {
74
- activeTools: RuntimeTool[];
75
- customTools: ToolDefinition[];
76
- }
77
-
78
- const DEFAULT_PI_PROVIDER_NAME = "custom-openai";
79
- const DEFAULT_PI_PROVIDER_API = "openai-completions";
80
- const DEFAULT_PI_MODEL_COST = {
81
- input: 0,
82
- output: 0,
83
- cacheRead: 0,
84
- cacheWrite: 0,
85
- } as const;
86
-
87
- const PI_PROVIDER_APIS = [
88
- "anthropic-messages",
89
- "openai-completions",
90
- "mistral-conversations",
91
- "openai-responses",
92
- "azure-openai-responses",
93
- "openai-codex-responses",
94
- "google-generative-ai",
95
- "google-gemini-cli",
96
- "google-vertex",
97
- "bedrock-converse-stream",
98
- ] as const;
99
-
100
- const piProviderApiSchema = z.enum(PI_PROVIDER_APIS);
101
-
102
- const piProviderModelSchema = z.object({
103
- id: z.string().min(1),
104
- name: z.string().min(1).optional(),
105
- api: piProviderApiSchema.optional(),
106
- reasoning: z.boolean().default(false),
107
- input: z.array(z.enum(["text", "image"])).min(1).default(["text"]),
108
- cost: z.object({
109
- input: z.number().default(0),
110
- output: z.number().default(0),
111
- cacheRead: z.number().default(0),
112
- cacheWrite: z.number().default(0),
113
- }).default(DEFAULT_PI_MODEL_COST),
114
- contextWindow: z.number().int().positive().default(128000),
115
- maxTokens: z.number().int().positive().default(16384),
116
- headers: z.record(z.string(), z.string()).optional(),
117
- });
118
-
119
- const piProviderConfigSchema = z.object({
120
- provider: z.string().min(1).optional(),
121
- model: z.string().min(1),
122
- baseUrl: z.string().min(1),
123
- apiKey: z.string().min(1),
124
- api: piProviderApiSchema.optional(),
125
- headers: z.record(z.string(), z.string()).optional(),
126
- authHeader: z.boolean().optional(),
127
- models: z.array(piProviderModelSchema).min(1).optional(),
128
- thinkingLevel: z.enum(["off", "minimal", "low", "medium", "high", "xhigh"]).optional(),
129
- agentDir: z.string().optional(),
130
- authFile: z.string().optional(),
131
- });
132
-
133
- function buildImplicitModelConfig(modelId: string): PiProviderModelConfig {
134
- return {
135
- id: modelId,
136
- name: modelId,
137
- reasoning: false,
138
- input: ["text"],
139
- cost: { ...DEFAULT_PI_MODEL_COST },
140
- contextWindow: 128000,
141
- maxTokens: 16384,
142
- };
143
- }
144
-
145
- function normalizePiProviderModels(
146
- activeModelId: string,
147
- models: Array<z.infer<typeof piProviderModelSchema>> | undefined,
148
- ): PiProviderModelConfig[] {
149
- if (!models || models.length === 0) {
150
- return [buildImplicitModelConfig(activeModelId)];
151
- }
152
-
153
- const normalizedModels = models.map((model) => ({
154
- id: model.id,
155
- name: model.name ?? model.id,
156
- ...(model.api ? { api: model.api } : {}),
157
- reasoning: model.reasoning,
158
- input: model.input,
159
- cost: model.cost,
160
- contextWindow: model.contextWindow,
161
- maxTokens: model.maxTokens,
162
- ...(model.headers ? { headers: model.headers } : {}),
163
- }));
164
-
165
- if (!normalizedModels.some((model) => model.id === activeModelId)) {
166
- throw new Error(`Configured model '${activeModelId}' is not present in provider.pi models`);
167
- }
168
-
169
- return normalizedModels;
170
- }
171
-
172
- const IMAGE_MIME_TYPES: Record<string, string> = {
173
- ".jpg": "image/jpeg",
174
- ".jpeg": "image/jpeg",
175
- ".png": "image/png",
176
- ".gif": "image/gif",
177
- ".webp": "image/webp",
178
- };
179
-
180
- function assertWithinRoot(absolutePath: string, rootDir: string): void {
181
- const normalizedRoot = resolve(rootDir);
182
- const normalizedPath = resolve(absolutePath);
183
- const rootPrefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;
184
-
185
- if (normalizedPath !== normalizedRoot && !normalizedPath.startsWith(rootPrefix)) {
186
- throw new Error(`Path '${normalizedPath}' is outside allowed project root '${normalizedRoot}'`);
187
- }
188
- }
189
-
190
- function safeSegment(value: string): string {
191
- return value.replaceAll(/[^a-zA-Z0-9._-]/g, "_");
192
- }
193
-
194
- function extractAssistantText(message: AgentMessage): string {
195
- if (message.role !== "assistant") return "";
196
- const blocks = message.content.filter((block): block is { type: "text"; text: string } => block.type === "text");
197
- return blocks.map((block) => block.text).join("\n");
198
- }
199
-
200
- function extractToolResultText(result: unknown): string {
201
- if (!result || typeof result !== "object") return "(no output)";
202
- const content = (result as { content?: unknown }).content;
203
- if (!Array.isArray(content)) return "(no output)";
204
-
205
- const textBlocks = content.filter(
206
- (block): block is { type: "text"; text: string } =>
207
- typeof block === "object" &&
208
- block !== null &&
209
- "type" in block &&
210
- (block as { type: string }).type === "text" &&
211
- "text" in block,
212
- );
213
-
214
- const text = textBlocks.map((block) => block.text).join("\n").trim();
215
- return text.length > 0 ? text : "(no output)";
216
- }
217
-
218
- function normalizeMaybePath(configBaseDir: string, value: string | undefined): string | undefined {
219
- if (!value) return undefined;
220
- const trimmed = value.trim();
221
- if (trimmed.length === 0) return undefined;
222
- if (trimmed === "~") return resolve(process.env.HOME ?? "", ".");
223
- if (trimmed.startsWith("~/") || trimmed.startsWith("~\\")) {
224
- return resolve(process.env.HOME ?? "", trimmed.slice(2));
225
- }
226
- return resolve(configBaseDir, trimmed);
227
- }
228
-
229
- function formatArchiveStamp(timestampMs: number): string {
230
- return new Date(timestampMs).toISOString().replaceAll(":", "-").replaceAll(".", "-");
231
- }
232
-
233
- async function archiveSessionPath(
234
- sessionsDir: string,
235
- sourcePath: string,
236
- archivedAtMs: number,
237
- ): Promise<string | undefined> {
238
- const relativePath = relative(sessionsDir, sourcePath);
239
- if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
240
- throw new Error(`Session path '${sourcePath}' is outside sessions dir '${sessionsDir}'`);
241
- }
242
-
243
- const archiveRoot = join(sessionsDir, "_archived", `${formatArchiveStamp(archivedAtMs)}-${randomUUID().slice(0, 8)}`);
244
- const archivePath = join(archiveRoot, relativePath);
245
- await mkdir(dirname(archivePath), { recursive: true });
246
-
247
- try {
248
- await rename(sourcePath, archivePath);
249
- return archivePath;
250
- } catch (error) {
251
- const asErr = error as NodeJS.ErrnoException;
252
- if (asErr.code === "ENOENT") {
253
- return undefined;
254
- }
255
- throw error;
256
- }
257
- }
258
-
259
- class PiGatewayRuntime implements GatewayAgentRuntime {
260
- constructor(private readonly session: AgentSession) {}
261
-
262
- async prompt(text: string, options?: { images?: ImageContent[] }): Promise<void> {
263
- if (options?.images && options.images.length > 0) {
264
- await this.session.prompt(text, { images: options.images });
265
- return;
266
- }
267
- await this.session.prompt(text);
268
- }
269
-
270
- subscribe(listener: (event: GatewayAgentEvent) => void): () => void {
271
- return this.session.subscribe((event) => {
272
- if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
273
- listener({ type: "message_delta", delta: event.assistantMessageEvent.delta });
274
- return;
275
- }
276
-
277
- if (event.type === "message_end" && event.message.role === "assistant") {
278
- const finalText = extractAssistantText(event.message);
279
- if (finalText.trim().length > 0) {
280
- listener({ type: "message_complete", text: finalText });
281
- }
282
- return;
283
- }
284
-
285
- if (event.type === "tool_execution_start") {
286
- listener({ type: "tool_start", toolName: event.toolName });
287
- return;
288
- }
289
-
290
- if (event.type === "tool_execution_end") {
291
- listener({
292
- type: "tool_end",
293
- toolName: event.toolName,
294
- isError: event.isError,
295
- output: extractToolResultText(event.result),
296
- });
297
- return;
298
- }
299
-
300
- if (event.type === "auto_compaction_start") {
301
- listener({ type: "status", message: `Compacting context (${event.reason})...` });
302
- return;
303
- }
304
-
305
- if (event.type === "auto_retry_start") {
306
- listener({ type: "status", message: `Retrying (${event.attempt}/${event.maxAttempts})...` });
307
- }
308
- });
309
- }
310
-
311
- async abort(): Promise<void> {
312
- await this.session.abort();
313
- }
314
-
315
- dispose(): void {
316
- this.session.dispose();
317
- }
318
- }
319
-
320
- class PiProviderInstanceImpl implements ProviderInstance {
321
- private readonly authStorage: AuthStorage;
322
- private readonly modelRegistry: ModelRegistry;
323
- private readonly model: Model<Api>;
324
-
325
- constructor(
326
- readonly id: string,
327
- private readonly providerConfig: PiProviderConfig,
328
- private readonly dataConfig: ProviderInstanceCreateOptions["data"],
329
- private readonly logger: ProviderInstanceCreateOptions["host"]["logger"],
330
- ) {
331
- this.authStorage = AuthStorage.create(providerConfig.authFile);
332
- // Point the registry at a never-created file so provider.pi relies solely on inline config.
333
- this.modelRegistry = new ModelRegistry(this.authStorage, join(dataConfig.stateDir, "__dobby_provider_pi_inline_models__", `${safeSegment(id)}.json`));
334
- this.modelRegistry.registerProvider(providerConfig.provider, {
335
- baseUrl: providerConfig.baseUrl,
336
- apiKey: providerConfig.apiKey,
337
- api: providerConfig.api,
338
- ...(providerConfig.headers ? { headers: providerConfig.headers } : {}),
339
- ...(providerConfig.authHeader ? { authHeader: true } : {}),
340
- models: providerConfig.models.map((model) => ({
341
- id: model.id,
342
- name: model.name,
343
- ...(model.api ? { api: model.api } : {}),
344
- reasoning: model.reasoning,
345
- input: model.input,
346
- cost: model.cost,
347
- contextWindow: model.contextWindow,
348
- maxTokens: model.maxTokens,
349
- ...(model.headers ? { headers: model.headers } : {}),
350
- })),
351
- });
352
-
353
- const model = this.modelRegistry.find(providerConfig.provider, providerConfig.model);
354
- if (!model) {
355
- throw new Error(`Configured model '${providerConfig.provider}/${providerConfig.model}' not found`);
356
- }
357
- this.model = model;
358
- }
359
-
360
- async createRuntime(options: ProviderRuntimeCreateOptions): Promise<GatewayAgentRuntime> {
361
- const sessionFile = options.sessionPolicy === "ephemeral"
362
- ? this.getEphemeralSessionFilePath(options.conversationKey)
363
- : this.getSessionFilePath(options.inbound);
364
- const sessionDir = dirname(sessionFile);
365
-
366
- await mkdir(this.dataConfig.sessionsDir, { recursive: true });
367
- await mkdir(this.dataConfig.logsDir, { recursive: true });
368
- await mkdir(this.dataConfig.stateDir, { recursive: true });
369
- await mkdir(this.dataConfig.attachmentsDir, { recursive: true });
370
- await mkdir(sessionDir, { recursive: true });
371
-
372
- const builtTools = this.buildTools(
373
- options.conversationKey,
374
- options.route.profile.projectRoot,
375
- options.route.profile.tools,
376
- options,
377
- );
378
-
379
- const sessionOptions = {
380
- cwd: options.route.profile.projectRoot,
381
- authStorage: this.authStorage,
382
- modelRegistry: this.modelRegistry,
383
- sessionManager: SessionManager.open(sessionFile, sessionDir),
384
- model: this.model,
385
- thinkingLevel: this.providerConfig.thinkingLevel,
386
- tools: builtTools.activeTools,
387
- customTools: builtTools.customTools,
388
- ...(this.providerConfig.agentDir ? { agentDir: this.providerConfig.agentDir } : {}),
389
- };
390
-
391
- const { session } = await createAgentSession(sessionOptions);
392
-
393
- this.logger.info(
394
- {
395
- providerInstance: this.id,
396
- activeTools: builtTools.activeTools.map((tool) => tool.name),
397
- },
398
- "Provider runtime initialized",
399
- );
400
-
401
- if (options.route.profile.systemPromptFile) {
402
- try {
403
- const prompt = await readFile(options.route.profile.systemPromptFile, "utf-8");
404
- session.agent.setSystemPrompt(prompt);
405
- } catch (error) {
406
- this.logger.warn(
407
- { err: error, routeId: options.route.routeId, file: options.route.profile.systemPromptFile },
408
- "Failed to load route system prompt; continuing with default",
409
- );
410
- }
411
- }
412
-
413
- return new PiGatewayRuntime(session);
414
- }
415
-
416
- async archiveSession(options: ProviderSessionArchiveOptions): Promise<{ archived: boolean; archivePath?: string }> {
417
- const sessionFile = options.sessionPolicy === "ephemeral"
418
- ? this.getEphemeralSessionFilePath(options.conversationKey)
419
- : this.getSessionFilePath(options.inbound);
420
- const archivePath = await archiveSessionPath(
421
- this.dataConfig.sessionsDir,
422
- sessionFile,
423
- options.archivedAtMs ?? Date.now(),
424
- );
425
- return archivePath ? { archived: true, archivePath } : { archived: false };
426
- }
427
-
428
- private buildTools(
429
- conversationKey: string,
430
- projectRoot: string,
431
- profile: "full" | "readonly",
432
- options: ProviderRuntimeCreateOptions,
433
- ): BuiltTools {
434
- const readOps: ReadOperations = {
435
- access: async (absolutePath) => {
436
- assertWithinRoot(absolutePath, projectRoot);
437
- },
438
- readFile: async (absolutePath) => {
439
- assertWithinRoot(absolutePath, projectRoot);
440
- return readFile(absolutePath);
441
- },
442
- detectImageMimeType: async (absolutePath) => {
443
- const ext = extname(absolutePath).toLowerCase();
444
- return IMAGE_MIME_TYPES[ext] ?? null;
445
- },
446
- };
447
-
448
- const writeOps: WriteOperations = {
449
- mkdir: async (dir) => {
450
- assertWithinRoot(dir, projectRoot);
451
- await mkdir(dir, { recursive: true });
452
- },
453
- writeFile: async (absolutePath, content) => {
454
- assertWithinRoot(absolutePath, projectRoot);
455
- await writeFile(absolutePath, content, "utf-8");
456
- },
457
- };
458
-
459
- const editOps: EditOperations = {
460
- access: async (absolutePath) => {
461
- assertWithinRoot(absolutePath, projectRoot);
462
- },
463
- readFile: async (absolutePath) => {
464
- assertWithinRoot(absolutePath, projectRoot);
465
- return readFile(absolutePath);
466
- },
467
- writeFile: async (absolutePath, content) => {
468
- assertWithinRoot(absolutePath, projectRoot);
469
- await writeFile(absolutePath, content, "utf-8");
470
- },
471
- };
472
-
473
- const bashOps: BashOperations = {
474
- exec: async (command, cwd, execOptionsFromTool) => {
475
- const env: NodeJS.ProcessEnv = {
476
- ...(execOptionsFromTool.env ?? {}),
477
- [BOXLITE_CONTEXT_CONVERSATION_KEY_ENV]: conversationKey,
478
- [BOXLITE_CONTEXT_PROJECT_ROOT_ENV]: projectRoot,
479
- };
480
-
481
- const execOptions = {
482
- ...(execOptionsFromTool.signal ? { signal: execOptionsFromTool.signal } : {}),
483
- ...(execOptionsFromTool.timeout !== undefined ? { timeoutSeconds: execOptionsFromTool.timeout } : {}),
484
- env,
485
- };
486
-
487
- this.logger.info(
488
- {
489
- providerInstance: this.id,
490
- sandboxExecutorType: options.executor.constructor?.name ?? "unknown",
491
- cwd,
492
- timeoutSeconds: execOptions.timeoutSeconds,
493
- },
494
- "Dispatching bash command to sandbox executor",
495
- );
496
-
497
- const result = await options.executor.exec(command, cwd, execOptions);
498
- const combined = `${result.stdout}${result.stderr}`;
499
- if (combined.length > 0) {
500
- execOptionsFromTool.onData(Buffer.from(combined, "utf-8"));
501
- }
502
-
503
- this.logger.info(
504
- {
505
- providerInstance: this.id,
506
- sandboxExecutorType: options.executor.constructor?.name ?? "unknown",
507
- code: result.code,
508
- killed: result.killed,
509
- stdoutBytes: Buffer.byteLength(result.stdout, "utf-8"),
510
- stderrBytes: Buffer.byteLength(result.stderr, "utf-8"),
511
- },
512
- "Sandbox executor completed bash command",
513
- );
514
-
515
- return { exitCode: result.killed ? null : result.code };
516
- },
517
- };
518
-
519
- const readTool = createReadTool(projectRoot, { operations: readOps }) as RuntimeTool;
520
- const bashTool = createBashTool(projectRoot, { operations: bashOps }) as RuntimeTool;
521
- const editTool = createEditTool(projectRoot, { operations: editOps }) as RuntimeTool;
522
- const writeTool = createWriteTool(projectRoot, { operations: writeOps }) as RuntimeTool;
523
- const grepTool = createGrepTool(projectRoot) as RuntimeTool;
524
- const findTool = createFindTool(projectRoot) as RuntimeTool;
525
- const lsTool = createLsTool(projectRoot) as RuntimeTool;
526
-
527
- if (profile === "readonly") {
528
- const activeTools = [readTool, grepTool, findTool, lsTool];
529
- return {
530
- activeTools,
531
- customTools: activeTools.map((tool) => this.toCustomToolDefinition(tool)),
532
- };
533
- }
534
-
535
- const activeTools = [readTool, bashTool, editTool, writeTool];
536
- return {
537
- activeTools,
538
- customTools: activeTools.map((tool) => this.toCustomToolDefinition(tool)),
539
- };
540
- }
541
-
542
- private toCustomToolDefinition(tool: RuntimeTool): ToolDefinition {
543
- return {
544
- name: tool.name,
545
- label: tool.label,
546
- description: tool.description,
547
- parameters: tool.parameters,
548
- execute: (toolCallId, params, signal, onUpdate) => tool.execute(toolCallId, params, signal, onUpdate),
549
- };
550
- }
551
-
552
- private getSessionFilePath(inbound: ProviderRuntimeCreateOptions["inbound"]): string {
553
- const guildSegment = safeSegment(inbound.guildId ?? "dm");
554
- const connectorSegment = safeSegment(inbound.connectorId);
555
- const sourceSegment = safeSegment(inbound.source.id);
556
- const threadSegment = safeSegment(inbound.threadId ?? "root");
557
- const chatSegment = safeSegment(inbound.chatId);
558
-
559
- return join(
560
- this.dataConfig.sessionsDir,
561
- connectorSegment,
562
- inbound.platform,
563
- safeSegment(inbound.accountId),
564
- guildSegment,
565
- sourceSegment,
566
- threadSegment,
567
- `${chatSegment}.jsonl`,
568
- );
569
- }
570
-
571
- private getEphemeralSessionFilePath(conversationKey: string): string {
572
- return join(
573
- this.dataConfig.sessionsDir,
574
- "_cron-ephemeral",
575
- `${safeSegment(conversationKey)}.jsonl`,
576
- );
577
- }
578
- }
579
-
580
- export const providerPiContribution: ProviderContributionModule = {
581
- kind: "provider",
582
- configSchema: z.toJSONSchema(piProviderConfigSchema),
583
- createInstance(options) {
584
- const parsed = piProviderConfigSchema.parse(options.config);
585
- const agentDir = normalizeMaybePath(options.host.configBaseDir, parsed.agentDir);
586
- const authFile = normalizeMaybePath(options.host.configBaseDir, parsed.authFile);
587
-
588
- const normalizedConfig: PiProviderConfig = {
589
- provider: parsed.provider ?? DEFAULT_PI_PROVIDER_NAME,
590
- model: parsed.model,
591
- baseUrl: parsed.baseUrl,
592
- apiKey: parsed.apiKey,
593
- api: parsed.api ?? DEFAULT_PI_PROVIDER_API,
594
- ...(parsed.headers ? { headers: parsed.headers } : {}),
595
- authHeader: parsed.authHeader ?? false,
596
- models: normalizePiProviderModels(parsed.model, parsed.models),
597
- thinkingLevel: parsed.thinkingLevel ?? "off",
598
- ...(agentDir ? { agentDir } : {}),
599
- ...(authFile ? { authFile } : {}),
600
- };
601
-
602
- return new PiProviderInstanceImpl(options.instanceId, normalizedConfig, options.data, options.host.logger);
603
- },
604
- };
605
-
606
- export default providerPiContribution;
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "types": ["node"],
9
- "strict": true,
10
- "esModuleInterop": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "skipLibCheck": true,
13
- "noUncheckedIndexedAccess": true,
14
- "exactOptionalPropertyTypes": true,
15
- "resolveJsonModule": true
16
- },
17
- "include": ["src/**/*.ts"],
18
- "exclude": ["dist", "node_modules"]
19
- }
@@ -1 +0,0 @@
1
- export { sandboxBoxliteContribution as contribution, sandboxBoxliteContribution as default } from "./dist/boxlite-contribution.js";
@@ -1,17 +0,0 @@
1
- {
2
- "apiVersion": "1.0",
3
- "name": "@dobby.ai/sandbox-core",
4
- "version": "0.1.0",
5
- "contributions": [
6
- {
7
- "id": "sandbox.docker",
8
- "kind": "sandbox",
9
- "entry": "./dist/docker-contribution.js"
10
- },
11
- {
12
- "id": "sandbox.boxlite",
13
- "kind": "sandbox",
14
- "entry": "./dist/boxlite-contribution.js"
15
- }
16
- ]
17
- }
@@ -1 +0,0 @@
1
- export { sandboxDockerContribution as contribution, sandboxDockerContribution as default } from "./dist/docker-contribution.js";