@dobby.ai/dobby 0.1.0 → 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 (156) hide show
  1. package/README.md +84 -39
  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/commands/doctor.js +81 -2
  5. package/dist/src/cli/commands/extension.js +3 -1
  6. package/dist/src/cli/commands/init.js +43 -173
  7. package/dist/src/cli/commands/topology.js +38 -14
  8. package/dist/src/cli/program.js +15 -137
  9. package/dist/src/cli/shared/config-io.js +3 -31
  10. package/dist/src/cli/shared/config-mutators.js +33 -9
  11. package/dist/src/cli/shared/configure-sections.js +52 -12
  12. package/dist/src/cli/shared/init-catalog.js +89 -46
  13. package/dist/src/cli/shared/local-extension-specs.js +85 -0
  14. package/dist/src/cli/shared/schema-prompts.js +26 -2
  15. package/dist/src/core/gateway.js +3 -1
  16. package/dist/src/core/routing.js +53 -38
  17. package/dist/src/core/types.js +2 -0
  18. package/dist/src/cron/config.js +2 -2
  19. package/dist/src/cron/service.js +87 -23
  20. package/dist/src/cron/store.js +1 -1
  21. package/dist/src/main.js +0 -0
  22. package/dist/src/shared/dobby-repo.js +40 -0
  23. package/package.json +11 -4
  24. package/.env.example +0 -9
  25. package/AGENTS.md +0 -267
  26. package/ROADMAP.md +0 -34
  27. package/config/cron.example.json +0 -9
  28. package/config/gateway.example.json +0 -128
  29. package/config/models.custom.example.json +0 -27
  30. package/dist/src/agent/tests/event-forwarder.test.js +0 -113
  31. package/dist/src/cli/shared/config-path.js +0 -207
  32. package/dist/src/cli/shared/init-models-file.js +0 -65
  33. package/dist/src/cli/shared/presets.js +0 -86
  34. package/dist/src/cli/tests/config-command.test.js +0 -42
  35. package/dist/src/cli/tests/config-io.test.js +0 -64
  36. package/dist/src/cli/tests/config-mutators.test.js +0 -47
  37. package/dist/src/cli/tests/config-path.test.js +0 -21
  38. package/dist/src/cli/tests/discord-config.test.js +0 -23
  39. package/dist/src/cli/tests/doctor.test.js +0 -107
  40. package/dist/src/cli/tests/init-catalog.test.js +0 -87
  41. package/dist/src/cli/tests/presets.test.js +0 -41
  42. package/dist/src/cli/tests/program-options.test.js +0 -92
  43. package/dist/src/cli/tests/routing-config.test.js +0 -199
  44. package/dist/src/cli/tests/routing-legacy.test.js +0 -191
  45. package/dist/src/core/tests/control-command.test.js +0 -17
  46. package/dist/src/core/tests/gateway-update-strategy.test.js +0 -167
  47. package/dist/src/core/tests/runtime-registry.test.js +0 -116
  48. package/dist/src/core/tests/typing-controller.test.js +0 -103
  49. package/docs/BOXLITE_SANDBOX_FEASIBILITY.md +0 -175
  50. package/docs/CRON_SCHEDULER_DESIGN.md +0 -374
  51. package/docs/DOCKER_SANDBOX_vs_BOXLITE.md +0 -77
  52. package/docs/EXTENSION_SYSTEM_ARCHITECTURE.md +0 -119
  53. package/docs/MVP.md +0 -135
  54. package/docs/RUNBOOK.md +0 -242
  55. package/docs/TEAMWORK_HANDOFF_DESIGN.md +0 -440
  56. package/plugins/connector-discord/dobby.manifest.json +0 -18
  57. package/plugins/connector-discord/index.js +0 -1
  58. package/plugins/connector-discord/package-lock.json +0 -360
  59. package/plugins/connector-discord/package.json +0 -38
  60. package/plugins/connector-discord/src/connector.ts +0 -350
  61. package/plugins/connector-discord/src/contribution.ts +0 -21
  62. package/plugins/connector-discord/src/mapper.ts +0 -102
  63. package/plugins/connector-discord/tsconfig.json +0 -19
  64. package/plugins/connector-feishu/dobby.manifest.json +0 -18
  65. package/plugins/connector-feishu/index.js +0 -1
  66. package/plugins/connector-feishu/package-lock.json +0 -618
  67. package/plugins/connector-feishu/package.json +0 -38
  68. package/plugins/connector-feishu/src/connector.ts +0 -343
  69. package/plugins/connector-feishu/src/contribution.ts +0 -26
  70. package/plugins/connector-feishu/src/mapper.ts +0 -401
  71. package/plugins/connector-feishu/tsconfig.json +0 -19
  72. package/plugins/plugin-sdk/index.d.ts +0 -261
  73. package/plugins/plugin-sdk/index.js +0 -1
  74. package/plugins/plugin-sdk/package-lock.json +0 -12
  75. package/plugins/plugin-sdk/package.json +0 -22
  76. package/plugins/provider-claude/dobby.manifest.json +0 -17
  77. package/plugins/provider-claude/index.js +0 -1
  78. package/plugins/provider-claude/package-lock.json +0 -3398
  79. package/plugins/provider-claude/package.json +0 -39
  80. package/plugins/provider-claude/src/contribution.ts +0 -1018
  81. package/plugins/provider-claude/tsconfig.json +0 -19
  82. package/plugins/provider-claude-cli/dobby.manifest.json +0 -17
  83. package/plugins/provider-claude-cli/index.js +0 -1
  84. package/plugins/provider-claude-cli/package-lock.json +0 -2898
  85. package/plugins/provider-claude-cli/package.json +0 -38
  86. package/plugins/provider-claude-cli/src/contribution.ts +0 -1673
  87. package/plugins/provider-claude-cli/tsconfig.json +0 -19
  88. package/plugins/provider-pi/dobby.manifest.json +0 -17
  89. package/plugins/provider-pi/index.js +0 -1
  90. package/plugins/provider-pi/package-lock.json +0 -3877
  91. package/plugins/provider-pi/package.json +0 -40
  92. package/plugins/provider-pi/src/contribution.ts +0 -476
  93. package/plugins/provider-pi/tsconfig.json +0 -19
  94. package/plugins/sandbox-core/boxlite.js +0 -1
  95. package/plugins/sandbox-core/dobby.manifest.json +0 -17
  96. package/plugins/sandbox-core/docker.js +0 -1
  97. package/plugins/sandbox-core/package-lock.json +0 -136
  98. package/plugins/sandbox-core/package.json +0 -39
  99. package/plugins/sandbox-core/src/boxlite-context.ts +0 -2
  100. package/plugins/sandbox-core/src/boxlite-contribution.ts +0 -53
  101. package/plugins/sandbox-core/src/boxlite-executor.ts +0 -911
  102. package/plugins/sandbox-core/src/docker-contribution.ts +0 -43
  103. package/plugins/sandbox-core/src/docker-executor.ts +0 -217
  104. package/plugins/sandbox-core/tsconfig.json +0 -19
  105. package/scripts/local-extensions.mjs +0 -168
  106. package/src/agent/event-forwarder.ts +0 -414
  107. package/src/cli/commands/config.ts +0 -328
  108. package/src/cli/commands/configure.ts +0 -92
  109. package/src/cli/commands/cron.ts +0 -410
  110. package/src/cli/commands/doctor.ts +0 -230
  111. package/src/cli/commands/extension.ts +0 -205
  112. package/src/cli/commands/init.ts +0 -396
  113. package/src/cli/commands/start.ts +0 -223
  114. package/src/cli/commands/topology.ts +0 -383
  115. package/src/cli/index.ts +0 -9
  116. package/src/cli/program.ts +0 -465
  117. package/src/cli/shared/config-io.ts +0 -277
  118. package/src/cli/shared/config-mutators.ts +0 -440
  119. package/src/cli/shared/config-schema.ts +0 -228
  120. package/src/cli/shared/config-types.ts +0 -121
  121. package/src/cli/shared/configure-sections.ts +0 -551
  122. package/src/cli/shared/discord-config.ts +0 -14
  123. package/src/cli/shared/init-catalog.ts +0 -189
  124. package/src/cli/shared/init-models-file.ts +0 -77
  125. package/src/cli/shared/runtime.ts +0 -33
  126. package/src/cli/shared/schema-prompts.ts +0 -414
  127. package/src/cli/tests/config-command.test.ts +0 -56
  128. package/src/cli/tests/config-io.test.ts +0 -92
  129. package/src/cli/tests/config-mutators.test.ts +0 -59
  130. package/src/cli/tests/doctor.test.ts +0 -120
  131. package/src/cli/tests/init-catalog.test.ts +0 -96
  132. package/src/cli/tests/program-options.test.ts +0 -113
  133. package/src/cli/tests/routing-config.test.ts +0 -209
  134. package/src/core/control-command.ts +0 -12
  135. package/src/core/dedup-store.ts +0 -103
  136. package/src/core/gateway.ts +0 -607
  137. package/src/core/routing.ts +0 -379
  138. package/src/core/runtime-registry.ts +0 -141
  139. package/src/core/tests/control-command.test.ts +0 -20
  140. package/src/core/tests/runtime-registry.test.ts +0 -140
  141. package/src/core/tests/typing-controller.test.ts +0 -129
  142. package/src/core/types.ts +0 -318
  143. package/src/core/typing-controller.ts +0 -119
  144. package/src/cron/config.ts +0 -154
  145. package/src/cron/schedule.ts +0 -61
  146. package/src/cron/service.ts +0 -249
  147. package/src/cron/store.ts +0 -155
  148. package/src/cron/types.ts +0 -60
  149. package/src/extension/loader.ts +0 -145
  150. package/src/extension/manager.ts +0 -355
  151. package/src/extension/manifest.ts +0 -26
  152. package/src/extension/registry.ts +0 -229
  153. package/src/main.ts +0 -8
  154. package/src/sandbox/executor.ts +0 -44
  155. package/src/sandbox/host-executor.ts +0 -118
  156. package/tsconfig.json +0 -18
@@ -1,128 +0,0 @@
1
- {
2
- "extensions": {
3
- "allowList": [
4
- {
5
- "package": "@dobby.ai/provider-pi",
6
- "enabled": true
7
- },
8
- {
9
- "package": "@dobby.ai/connector-discord",
10
- "enabled": true
11
- },
12
- {
13
- "package": "@dobby.ai/connector-feishu",
14
- "enabled": false
15
- },
16
- {
17
- "package": "@dobby.ai/provider-claude-cli",
18
- "enabled": false
19
- }
20
- ]
21
- },
22
- "providers": {
23
- "default": "pi.main",
24
- "items": {
25
- "pi.main": {
26
- "type": "provider.pi",
27
- "provider": "custom-openai",
28
- "model": "example-model",
29
- "thinkingLevel": "off",
30
- "modelsFile": "./models.custom.json"
31
- }
32
- }
33
- },
34
- "connectors": {
35
- "items": {
36
- "discord.main": {
37
- "type": "connector.discord",
38
- "botName": "dobby-main",
39
- "botToken": "REPLACE_WITH_DISCORD_BOT_TOKEN",
40
- "reconnectStaleMs": 60000,
41
- "reconnectCheckIntervalMs": 10000
42
- }
43
- }
44
- },
45
- "sandboxes": {
46
- "default": "host.builtin",
47
- "items": {}
48
- },
49
- "routes": {
50
- "defaults": {
51
- "provider": "pi.main",
52
- "sandbox": "host.builtin",
53
- "tools": "full",
54
- "mentions": "required"
55
- },
56
- "items": {
57
- "projectA": {
58
- "projectRoot": "/Users/you/workspace/project-a"
59
- }
60
- }
61
- },
62
- "bindings": {
63
- "items": {
64
- "discord.main.projectA": {
65
- "connector": "discord.main",
66
- "source": {
67
- "type": "channel",
68
- "id": "YOUR_DISCORD_CHANNEL_ID_A"
69
- },
70
- "route": "projectA"
71
- }
72
- }
73
- },
74
- "_examples": {
75
- "connectors": {
76
- "items": {
77
- "feishu.main": {
78
- "type": "connector.feishu",
79
- "appId": "cli_xxx",
80
- "appSecret": "REPLACE_WITH_FEISHU_APP_SECRET",
81
- "domain": "feishu",
82
- "botName": "dobby-feishu",
83
- "messageFormat": "card_markdown",
84
- "replyMode": "direct",
85
- "cardTitle": "Dobby",
86
- "downloadAttachments": true
87
- }
88
- }
89
- },
90
- "providers": {
91
- "items": {
92
- "claude-cli.main": {
93
- "type": "provider.claude-cli",
94
- "model": "claude-sonnet-4-5",
95
- "maxTurns": 20,
96
- "command": "claude",
97
- "commandArgs": [],
98
- "authMode": "auto",
99
- "permissionMode": "bypassPermissions",
100
- "streamVerbose": true
101
- }
102
- }
103
- },
104
- "routes": {
105
- "items": {
106
- "projectA": {
107
- "provider": "claude-cli.main"
108
- }
109
- }
110
- },
111
- "bindings": {
112
- "items": {
113
- "feishu.main.projectA": {
114
- "connector": "feishu.main",
115
- "source": {
116
- "type": "chat",
117
- "id": "oc_xxx"
118
- },
119
- "route": "projectA"
120
- }
121
- }
122
- }
123
- },
124
- "data": {
125
- "rootDir": "./data",
126
- "dedupTtlMs": 604800000
127
- }
128
- }
@@ -1,27 +0,0 @@
1
- {
2
- "providers": {
3
- "custom-openai": {
4
- "baseUrl": "https://api.example.com/v1",
5
- "api": "openai-completions",
6
- "apiKey": "CUSTOM_PROVIDER_AUTH_TOKEN",
7
- "models": [
8
- {
9
- "id": "example-model",
10
- "name": "example-model",
11
- "reasoning": false,
12
- "input": [
13
- "text"
14
- ],
15
- "contextWindow": 128000,
16
- "maxTokens": 8192,
17
- "cost": {
18
- "input": 0,
19
- "output": 0,
20
- "cacheRead": 0,
21
- "cacheWrite": 0
22
- }
23
- }
24
- ]
25
- }
26
- }
27
- }
@@ -1,113 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { EventForwarder } from "../event-forwarder.js";
4
- class FakeConnector {
5
- id = "connector.test";
6
- platform = "test";
7
- name = "test";
8
- capabilities;
9
- sent = [];
10
- sentCount = 0;
11
- constructor(updateStrategy, maxTextLength) {
12
- this.capabilities = {
13
- updateStrategy,
14
- supportsThread: false,
15
- supportsTyping: false,
16
- supportsFileUpload: false,
17
- ...(maxTextLength !== undefined ? { maxTextLength } : {}),
18
- };
19
- }
20
- async start(_ctx) { }
21
- async send(message) {
22
- this.sent.push(message);
23
- this.sentCount += 1;
24
- return { messageId: `msg-${this.sentCount}` };
25
- }
26
- async stop() { }
27
- }
28
- const noopLogger = {
29
- info: () => { },
30
- warn: () => { },
31
- error: () => { },
32
- debug: () => { },
33
- };
34
- function createInbound() {
35
- return {
36
- connectorId: "connector.test",
37
- platform: "test",
38
- accountId: "bot",
39
- routeId: "route.main",
40
- routeChannelId: "route.main",
41
- chatId: "chat.main",
42
- messageId: "inbound-1",
43
- userId: "user-1",
44
- text: "hello",
45
- attachments: [],
46
- timestampMs: Date.now(),
47
- raw: {},
48
- isDirectMessage: false,
49
- mentionedBot: true,
50
- };
51
- }
52
- function sleep(ms) {
53
- return new Promise((resolve) => {
54
- setTimeout(resolve, ms);
55
- });
56
- }
57
- test("edit strategy keeps create+update flow", async () => {
58
- const connector = new FakeConnector("edit");
59
- const forwarder = new EventForwarder(connector, createInbound(), null, noopLogger, {
60
- updateIntervalMs: 1,
61
- });
62
- forwarder.handleEvent({ type: "message_complete", text: "hello" });
63
- await sleep(5);
64
- forwarder.handleEvent({ type: "message_complete", text: "hello world" });
65
- await sleep(5);
66
- await forwarder.finalize();
67
- assert.equal(connector.sent.length, 2);
68
- assert.equal(connector.sent[0]?.mode, "create");
69
- assert.equal(connector.sent[0]?.text, "hello");
70
- assert.equal(connector.sent[1]?.mode, "update");
71
- assert.equal(connector.sent[1]?.text, "hello world");
72
- });
73
- test("final_only suppresses tool/status outbound messages", async () => {
74
- const connector = new FakeConnector("final_only");
75
- const forwarder = new EventForwarder(connector, createInbound(), null, noopLogger, {
76
- toolMessageMode: "all",
77
- updateIntervalMs: 1,
78
- });
79
- forwarder.handleEvent({ type: "status", message: "starting" });
80
- forwarder.handleEvent({ type: "tool_start", toolName: "bash" });
81
- forwarder.handleEvent({ type: "message_delta", delta: "hello " });
82
- forwarder.handleEvent({ type: "tool_end", toolName: "bash", isError: false, output: "ok" });
83
- forwarder.handleEvent({ type: "message_delta", delta: "world" });
84
- await forwarder.finalize();
85
- assert.equal(connector.sent.length, 1);
86
- assert.equal(connector.sent[0]?.mode, "create");
87
- assert.equal(connector.sent[0]?.text, "hello world");
88
- });
89
- test("final_only splits long final text into multiple create messages", async () => {
90
- const connector = new FakeConnector("final_only", 5);
91
- const forwarder = new EventForwarder(connector, createInbound(), null, noopLogger);
92
- forwarder.handleEvent({ type: "message_complete", text: "12345678901" });
93
- await forwarder.finalize();
94
- assert.equal(connector.sent.length, 3);
95
- assert.deepEqual(connector.sent.map((message) => ({ mode: message.mode, text: message.text })), [
96
- { mode: "create", text: "12345" },
97
- { mode: "create", text: "67890" },
98
- { mode: "create", text: "1" },
99
- ]);
100
- });
101
- test("append sends streaming increments as create messages only", async () => {
102
- const connector = new FakeConnector("append");
103
- const forwarder = new EventForwarder(connector, createInbound(), null, noopLogger, {
104
- updateIntervalMs: 5,
105
- });
106
- forwarder.handleEvent({ type: "message_delta", delta: "hello" });
107
- await sleep(20);
108
- forwarder.handleEvent({ type: "message_delta", delta: " world" });
109
- await sleep(20);
110
- await forwarder.finalize();
111
- assert.deepEqual(connector.sent.map((message) => message.mode), ["create", "create"]);
112
- assert.deepEqual(connector.sent.map((message) => message.text), ["hello", " world"]);
113
- });
@@ -1,207 +0,0 @@
1
- const BLOCKED_OBJECT_KEYS = new Set(["__proto__", "prototype", "constructor"]);
2
- /**
3
- * Returns true when a path segment represents an array index.
4
- */
5
- function isIndexSegment(raw) {
6
- return /^[0-9]+$/.test(raw);
7
- }
8
- /**
9
- * Rejects dangerous object keys that could enable prototype pollution.
10
- */
11
- function validatePathSegments(path) {
12
- for (const segment of path) {
13
- if (!isIndexSegment(segment) && BLOCKED_OBJECT_KEYS.has(segment)) {
14
- throw new Error(`Invalid path segment: ${segment}`);
15
- }
16
- }
17
- }
18
- /**
19
- * Parses dot/bracket path syntax into normalized path segments.
20
- */
21
- export function parsePath(rawPath) {
22
- const trimmed = rawPath.trim();
23
- if (!trimmed) {
24
- return [];
25
- }
26
- const segments = [];
27
- let current = "";
28
- let index = 0;
29
- while (index < trimmed.length) {
30
- const char = trimmed[index];
31
- if (char === "\\") {
32
- const next = trimmed[index + 1];
33
- if (next) {
34
- current += next;
35
- }
36
- index += 2;
37
- continue;
38
- }
39
- if (char === ".") {
40
- if (current) {
41
- segments.push(current);
42
- }
43
- current = "";
44
- index += 1;
45
- continue;
46
- }
47
- if (char === "[") {
48
- if (current) {
49
- segments.push(current);
50
- }
51
- current = "";
52
- const closeIndex = trimmed.indexOf("]", index);
53
- if (closeIndex === -1) {
54
- throw new Error(`Invalid path (missing ']'): ${rawPath}`);
55
- }
56
- const inside = trimmed.slice(index + 1, closeIndex).trim();
57
- if (!inside) {
58
- throw new Error(`Invalid path (empty '[]'): ${rawPath}`);
59
- }
60
- segments.push(inside);
61
- index = closeIndex + 1;
62
- continue;
63
- }
64
- current += char;
65
- index += 1;
66
- }
67
- if (current) {
68
- segments.push(current);
69
- }
70
- const normalized = segments.map((segment) => segment.trim()).filter(Boolean);
71
- validatePathSegments(normalized);
72
- return normalized;
73
- }
74
- /**
75
- * Safe own-property check wrapper.
76
- */
77
- function hasOwnKey(value, key) {
78
- return Object.prototype.hasOwnProperty.call(value, key);
79
- }
80
- /**
81
- * Reads a value from object/array structures by parsed path segments.
82
- */
83
- export function getAtPath(root, path) {
84
- let current = root;
85
- for (const segment of path) {
86
- if (!current || typeof current !== "object") {
87
- return { found: false };
88
- }
89
- if (Array.isArray(current)) {
90
- if (!isIndexSegment(segment)) {
91
- return { found: false };
92
- }
93
- const index = Number.parseInt(segment, 10);
94
- if (!Number.isFinite(index) || index < 0 || index >= current.length) {
95
- return { found: false };
96
- }
97
- current = current[index];
98
- continue;
99
- }
100
- const record = current;
101
- if (!hasOwnKey(record, segment)) {
102
- return { found: false };
103
- }
104
- current = record[segment];
105
- }
106
- return { found: true, value: current };
107
- }
108
- /**
109
- * Sets a value at path, creating intermediate objects/arrays as needed.
110
- */
111
- export function setAtPath(root, path, value) {
112
- if (path.length === 0) {
113
- throw new Error("Path is empty.");
114
- }
115
- let current = root;
116
- for (let i = 0; i < path.length - 1; i += 1) {
117
- const segment = path[i];
118
- const next = path[i + 1];
119
- const nextIsIndex = Boolean(next && isIndexSegment(next));
120
- if (Array.isArray(current)) {
121
- if (!isIndexSegment(segment)) {
122
- throw new Error(`Expected numeric index for array segment '${segment}'`);
123
- }
124
- const index = Number.parseInt(segment, 10);
125
- const existing = current[index];
126
- if (!existing || typeof existing !== "object") {
127
- current[index] = nextIsIndex ? [] : {};
128
- }
129
- current = current[index];
130
- continue;
131
- }
132
- if (!current || typeof current !== "object") {
133
- throw new Error(`Cannot traverse into '${segment}' (not an object)`);
134
- }
135
- const record = current;
136
- const existing = hasOwnKey(record, segment) ? record[segment] : undefined;
137
- if (!existing || typeof existing !== "object") {
138
- record[segment] = nextIsIndex ? [] : {};
139
- }
140
- current = record[segment];
141
- }
142
- const tail = path[path.length - 1];
143
- if (Array.isArray(current)) {
144
- if (!isIndexSegment(tail)) {
145
- throw new Error(`Expected numeric index for array segment '${tail}'`);
146
- }
147
- const index = Number.parseInt(tail, 10);
148
- current[index] = value;
149
- return;
150
- }
151
- if (!current || typeof current !== "object") {
152
- throw new Error(`Cannot set '${tail}' (parent is not an object)`);
153
- }
154
- current[tail] = value;
155
- }
156
- /**
157
- * Removes a value at path and returns whether the target existed.
158
- */
159
- export function unsetAtPath(root, path) {
160
- if (path.length === 0) {
161
- return false;
162
- }
163
- let current = root;
164
- for (let i = 0; i < path.length - 1; i += 1) {
165
- const segment = path[i];
166
- if (!current || typeof current !== "object") {
167
- return false;
168
- }
169
- if (Array.isArray(current)) {
170
- if (!isIndexSegment(segment)) {
171
- return false;
172
- }
173
- const index = Number.parseInt(segment, 10);
174
- if (!Number.isFinite(index) || index < 0 || index >= current.length) {
175
- return false;
176
- }
177
- current = current[index];
178
- continue;
179
- }
180
- const record = current;
181
- if (!hasOwnKey(record, segment)) {
182
- return false;
183
- }
184
- current = record[segment];
185
- }
186
- const tail = path[path.length - 1];
187
- if (Array.isArray(current)) {
188
- if (!isIndexSegment(tail)) {
189
- return false;
190
- }
191
- const index = Number.parseInt(tail, 10);
192
- if (!Number.isFinite(index) || index < 0 || index >= current.length) {
193
- return false;
194
- }
195
- current.splice(index, 1);
196
- return true;
197
- }
198
- if (!current || typeof current !== "object") {
199
- return false;
200
- }
201
- const record = current;
202
- if (!hasOwnKey(record, tail)) {
203
- return false;
204
- }
205
- delete record[tail];
206
- return true;
207
- }
@@ -1,65 +0,0 @@
1
- import { access, mkdir, writeFile } from "node:fs/promises";
2
- import { dirname, isAbsolute, resolve } from "node:path";
3
- import { homedir } from "node:os";
4
- const DEFAULT_PROVIDER_PI_MODELS_FILE = "./models.custom.json";
5
- const PROVIDER_PI_MODELS_TEMPLATE = {
6
- providers: {
7
- "custom-openai": {
8
- baseUrl: "https://api.example.com/v1",
9
- api: "openai-completions",
10
- apiKey: "CUSTOM_PROVIDER_AUTH_TOKEN",
11
- models: [
12
- {
13
- id: "example-model",
14
- name: "example-model",
15
- reasoning: false,
16
- input: ["text"],
17
- contextWindow: 128000,
18
- maxTokens: 8192,
19
- cost: {
20
- input: 0,
21
- output: 0,
22
- cacheRead: 0,
23
- cacheWrite: 0,
24
- },
25
- },
26
- ],
27
- },
28
- },
29
- };
30
- function expandHome(value) {
31
- if (value === "~") {
32
- return homedir();
33
- }
34
- if (value.startsWith("~/") || value.startsWith("~\\")) {
35
- return resolve(homedir(), value.slice(2));
36
- }
37
- return value;
38
- }
39
- async function fileExists(path) {
40
- try {
41
- await access(path);
42
- return true;
43
- }
44
- catch {
45
- return false;
46
- }
47
- }
48
- function resolveModelsFilePath(configPath, value) {
49
- const configDir = dirname(resolve(configPath));
50
- const resolvedValue = expandHome(value && value.trim().length > 0 ? value.trim() : DEFAULT_PROVIDER_PI_MODELS_FILE);
51
- return isAbsolute(resolvedValue) ? resolve(resolvedValue) : resolve(configDir, resolvedValue);
52
- }
53
- /**
54
- * Creates provider.pi models file only when missing.
55
- */
56
- export async function ensureProviderPiModelsFile(configPath, providerConfig) {
57
- const modelsFile = typeof providerConfig.modelsFile === "string" ? providerConfig.modelsFile : undefined;
58
- const targetPath = resolveModelsFilePath(configPath, modelsFile);
59
- if (await fileExists(targetPath)) {
60
- return { created: false, path: targetPath };
61
- }
62
- await mkdir(dirname(targetPath), { recursive: true });
63
- await writeFile(targetPath, `${JSON.stringify(PROVIDER_PI_MODELS_TEMPLATE, null, 2)}\n`, "utf-8");
64
- return { created: true, path: targetPath };
65
- }
@@ -1,86 +0,0 @@
1
- import { DEFAULT_DISCORD_CONNECTOR_INSTANCE_ID, DISCORD_CONNECTOR_CONTRIBUTION_ID, } from "./discord-config.js";
2
- const PRESET_IDS = ["discord-pi", "discord-claude-cli"];
3
- /**
4
- * Returns all preset identifiers supported by `dobby init`.
5
- */
6
- export function listPresetIds() {
7
- return [...PRESET_IDS];
8
- }
9
- /**
10
- * Type guard for validating preset ids provided by users.
11
- */
12
- export function isPresetId(value) {
13
- return PRESET_IDS.includes(value);
14
- }
15
- /**
16
- * Builds preset-specific extension, instance, and route defaults for init flow.
17
- */
18
- export function createPresetConfig(presetId, context) {
19
- const baseRoute = {
20
- projectRoot: context.projectRoot,
21
- tools: "full",
22
- systemPromptFile: "",
23
- allowMentionsOnly: !context.allowAllMessages,
24
- maxConcurrentTurns: 1,
25
- sandboxId: "host.builtin",
26
- };
27
- if (presetId === "discord-claude-cli") {
28
- return {
29
- id: presetId,
30
- extensionPackages: ["@dobby/provider-claude-cli", "@dobby/connector-discord"],
31
- providerInstanceId: "claude-cli.main",
32
- providerContributionId: "provider.claude-cli",
33
- providerConfig: {
34
- model: "claude-sonnet-4-5",
35
- maxTurns: 20,
36
- command: "claude",
37
- commandArgs: [],
38
- authMode: "auto",
39
- permissionMode: "bypassPermissions",
40
- streamVerbose: true,
41
- },
42
- connectorInstanceId: DEFAULT_DISCORD_CONNECTOR_INSTANCE_ID,
43
- connectorContributionId: DISCORD_CONNECTOR_CONTRIBUTION_ID,
44
- connectorConfig: {
45
- botName: context.botName,
46
- botToken: context.botToken,
47
- botChannelMap: {
48
- [context.channelId]: context.routeId,
49
- },
50
- reconnectStaleMs: 60_000,
51
- reconnectCheckIntervalMs: 10_000,
52
- },
53
- routeProfile: {
54
- ...baseRoute,
55
- providerId: "claude-cli.main",
56
- },
57
- };
58
- }
59
- return {
60
- id: "discord-pi",
61
- extensionPackages: ["@dobby/provider-pi", "@dobby/connector-discord"],
62
- providerInstanceId: "pi.main",
63
- providerContributionId: "provider.pi",
64
- providerConfig: {
65
- provider: "custom-openai",
66
- model: "example-model",
67
- thinkingLevel: "off",
68
- modelsFile: "./models.custom.json",
69
- },
70
- connectorInstanceId: DEFAULT_DISCORD_CONNECTOR_INSTANCE_ID,
71
- connectorContributionId: DISCORD_CONNECTOR_CONTRIBUTION_ID,
72
- connectorConfig: {
73
- botName: context.botName,
74
- botToken: context.botToken,
75
- botChannelMap: {
76
- [context.channelId]: context.routeId,
77
- },
78
- reconnectStaleMs: 60_000,
79
- reconnectCheckIntervalMs: 10_000,
80
- },
81
- routeProfile: {
82
- ...baseRoute,
83
- providerId: "pi.main",
84
- },
85
- };
86
- }
@@ -1,42 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { buildConfigListEntries, CONFIG_SECTION_VALUES, isConfigSection, previewConfigValue, } from "../commands/config.js";
4
- test("isConfigSection accepts only supported top-level config sections", () => {
5
- for (const section of CONFIG_SECTION_VALUES) {
6
- assert.equal(isConfigSection(section), true);
7
- }
8
- assert.equal(isConfigSection("provider"), false);
9
- assert.equal(isConfigSection("bot"), false);
10
- });
11
- test("previewConfigValue returns stable compact previews", () => {
12
- assert.equal(previewConfigValue("abc"), "\"abc\"");
13
- assert.equal(previewConfigValue(123), "123");
14
- assert.equal(previewConfigValue(true), "true");
15
- assert.equal(previewConfigValue(null), "null");
16
- assert.equal(previewConfigValue({ a: 1, b: 2, c: 3, d: 4 }), "{a, b, c, ...}");
17
- });
18
- test("buildConfigListEntries summarizes object values with type and child counts", () => {
19
- const entries = buildConfigListEntries({
20
- providers: {
21
- default: "pi.main",
22
- items: {
23
- "pi.main": { type: "provider.pi" },
24
- },
25
- },
26
- featureFlag: true,
27
- });
28
- assert.deepEqual(entries.map((entry) => ({ key: entry.key, type: entry.type, children: entry.children })), [
29
- { key: "featureFlag", type: "boolean", children: undefined },
30
- { key: "providers", type: "object", children: 2 },
31
- ]);
32
- });
33
- test("buildConfigListEntries handles primitive roots", () => {
34
- const entries = buildConfigListEntries("plain");
35
- assert.deepEqual(entries, [
36
- {
37
- key: "(value)",
38
- type: "string",
39
- preview: "\"plain\"",
40
- },
41
- ]);
42
- });