@kernel.chat/kbot 3.99.31 → 3.99.34

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 (67) hide show
  1. package/README.md +14 -1
  2. package/dist/agents/security-agent.d.ts +31 -0
  3. package/dist/agents/security-agent.js +180 -0
  4. package/dist/agents/security-rules.d.ts +35 -0
  5. package/dist/agents/security-rules.js +206 -0
  6. package/dist/agents/specialists.d.ts +6 -0
  7. package/dist/agents/specialists.js +45 -0
  8. package/dist/architect.js +5 -0
  9. package/dist/auth.js +1 -1
  10. package/dist/channels/matrix.d.ts +4 -0
  11. package/dist/channels/matrix.js +28 -0
  12. package/dist/channels/office.d.ts +78 -0
  13. package/dist/channels/office.js +169 -0
  14. package/dist/channels/registry.d.ts +8 -0
  15. package/dist/channels/registry.js +38 -0
  16. package/dist/channels/signal.d.ts +4 -0
  17. package/dist/channels/signal.js +29 -0
  18. package/dist/channels/slack.d.ts +4 -0
  19. package/dist/channels/slack.js +97 -0
  20. package/dist/channels/teams.d.ts +4 -0
  21. package/dist/channels/teams.js +29 -0
  22. package/dist/channels/telegram.d.ts +4 -0
  23. package/dist/channels/telegram.js +28 -0
  24. package/dist/channels/types.d.ts +50 -0
  25. package/dist/channels/types.js +13 -0
  26. package/dist/channels/whatsapp.d.ts +4 -0
  27. package/dist/channels/whatsapp.js +28 -0
  28. package/dist/cli.js +81 -0
  29. package/dist/computer-use-coordinator.d.ts +44 -0
  30. package/dist/computer-use-coordinator.js +0 -0
  31. package/dist/file-library.d.ts +76 -0
  32. package/dist/file-library.js +269 -0
  33. package/dist/ide/mcp-server.js +4 -4
  34. package/dist/managed-agents-anthropic.d.ts +90 -0
  35. package/dist/managed-agents-anthropic.js +123 -0
  36. package/dist/plugins-integrity.d.ts +72 -0
  37. package/dist/plugins-integrity.js +153 -0
  38. package/dist/plugins.d.ts +13 -2
  39. package/dist/plugins.js +87 -10
  40. package/dist/setup-editor.d.ts +28 -0
  41. package/dist/setup-editor.js +222 -0
  42. package/dist/tools/anthropic-managed-agents-tools.d.ts +22 -0
  43. package/dist/tools/anthropic-managed-agents-tools.js +191 -0
  44. package/dist/tools/channel-tools.d.ts +4 -0
  45. package/dist/tools/channel-tools.js +80 -0
  46. package/dist/tools/computer-coordinator-tools.d.ts +13 -0
  47. package/dist/tools/computer-coordinator-tools.js +104 -0
  48. package/dist/tools/computer.js +463 -299
  49. package/dist/tools/file-library-tools.d.ts +12 -0
  50. package/dist/tools/file-library-tools.js +191 -0
  51. package/dist/tools/image-thoughtful.d.ts +31 -0
  52. package/dist/tools/image-thoughtful.js +233 -0
  53. package/dist/tools/index.js +1 -0
  54. package/dist/tools/matrix.js +3 -3
  55. package/dist/tools/redblue.js +2 -2
  56. package/dist/tools/security-agent-tools.d.ts +34 -0
  57. package/dist/tools/security-agent-tools.js +30 -0
  58. package/dist/tools/security-hunt.js +1 -1
  59. package/dist/tools/subagent.js +2 -2
  60. package/dist/tools/swarm-2026-04.d.ts +2 -0
  61. package/dist/tools/swarm-2026-04.js +91 -0
  62. package/dist/tools/visa-payments.js +1 -1
  63. package/dist/tools/workspace-agent-tools.d.ts +19 -0
  64. package/dist/tools/workspace-agent-tools.js +191 -0
  65. package/dist/workspace-agents.d.ts +132 -0
  66. package/dist/workspace-agents.js +379 -0
  67. package/package.json +1 -1
@@ -0,0 +1,379 @@
1
+ /**
2
+ * Workspace Agents — long-running named agents bound to a workspace with
3
+ * permissions and resumable state. Parity with OpenAI's Workspace Agents
4
+ * (Apr 2026). Wraps the hierarchical planner at ./planner/hierarchical/.
5
+ *
6
+ * State JSON shape (one file per agent at <root>/<id>.json):
7
+ * { id, name, mission, allowedTools, scopes, status, createdAt, updatedAt,
8
+ * currentPlanId?, history: [{ ts, event, data }] }
9
+ *
10
+ * Storage root: process.env.KBOT_WORKSPACE_AGENTS_ROOT
11
+ * ?? <homedir>/.kbot/workspace-agents
12
+ *
13
+ * Permissions: every tool invocation must pass through `gate(toolName)` which
14
+ * checks `allowedTools`. Scopes are recorded but enforcement is per-tool via
15
+ * the allowedTools whitelist.
16
+ */
17
+ import { randomUUID } from 'node:crypto';
18
+ import { promises as fs } from 'node:fs';
19
+ import os from 'node:os';
20
+ import path from 'node:path';
21
+ export class ScopeError extends Error {
22
+ toolName;
23
+ constructor(toolName, message) {
24
+ super(message ?? `Tool "${toolName}" is not in this agent's allowedTools`);
25
+ this.name = 'ScopeError';
26
+ this.toolName = toolName;
27
+ }
28
+ }
29
+ export class WorkspaceAgentError extends Error {
30
+ constructor(message) {
31
+ super(message);
32
+ this.name = 'WorkspaceAgentError';
33
+ }
34
+ }
35
+ // ─────────────────────────────────────────────────────────────────────────────
36
+ // Storage helpers
37
+ // ─────────────────────────────────────────────────────────────────────────────
38
+ export function defaultRoot() {
39
+ const env = process.env.KBOT_WORKSPACE_AGENTS_ROOT;
40
+ if (env && env.trim().length > 0)
41
+ return env;
42
+ return path.join(os.homedir(), '.kbot', 'workspace-agents');
43
+ }
44
+ async function ensureDir(dir) {
45
+ await fs.mkdir(dir, { recursive: true });
46
+ }
47
+ function statePath(root, id) {
48
+ return path.join(root, `${id}.json`);
49
+ }
50
+ async function readState(root, id) {
51
+ try {
52
+ const raw = await fs.readFile(statePath(root, id), 'utf8');
53
+ return JSON.parse(raw);
54
+ }
55
+ catch (e) {
56
+ if (e.code === 'ENOENT')
57
+ return null;
58
+ throw e;
59
+ }
60
+ }
61
+ async function writeState(root, state) {
62
+ await ensureDir(root);
63
+ const tmp = statePath(root, state.id) + '.tmp';
64
+ await fs.writeFile(tmp, JSON.stringify(state, null, 2), 'utf8');
65
+ await fs.rename(tmp, statePath(root, state.id));
66
+ }
67
+ async function listAll(root) {
68
+ try {
69
+ const entries = await fs.readdir(root);
70
+ const out = [];
71
+ for (const e of entries) {
72
+ if (!e.endsWith('.json'))
73
+ continue;
74
+ try {
75
+ const raw = await fs.readFile(path.join(root, e), 'utf8');
76
+ out.push(JSON.parse(raw));
77
+ }
78
+ catch {
79
+ // skip corrupt entries
80
+ }
81
+ }
82
+ return out;
83
+ }
84
+ catch (e) {
85
+ if (e.code === 'ENOENT')
86
+ return [];
87
+ throw e;
88
+ }
89
+ }
90
+ /**
91
+ * Default planner adapter — implements the 3-tier strategy:
92
+ *
93
+ * Tier 1: KBOT_PLANNER=hierarchical AND non-null agentOpts → real
94
+ * HierarchicalPlanner.planAndExecute. Tool calls extracted from
95
+ * the resulting Action.steps and surfaced for gating.
96
+ * Tier 2: HierarchicalPlanner module loadable but no agentOpts → call
97
+ * createGoal only; emit a TODO note; return early.
98
+ * Tier 3: Module import fails (e.g. test env) → deterministic stub
99
+ * `{ planId: 'stub', steps: [] }`.
100
+ *
101
+ * The function never throws on planner-internal failures: each tier degrades
102
+ * to the next so the WorkspaceAgent.start() lifecycle stays predictable.
103
+ */
104
+ export const defaultPlannerStart = async (taskInput, state, agentOpts) => {
105
+ // Try to import the planner module first. If this fails we're in Tier 3.
106
+ let mod;
107
+ try {
108
+ mod = await import('./planner/hierarchical/session-planner.js');
109
+ }
110
+ catch {
111
+ // Tier 3: stub fallback (current behavior).
112
+ return { planId: 'stub', steps: [] };
113
+ }
114
+ const planner = new mod.HierarchicalPlanner();
115
+ // Tier 1: real planner — needs both the env flag and agentOpts.
116
+ if (process.env.KBOT_PLANNER === 'hierarchical' && agentOpts) {
117
+ try {
118
+ const goal = await planner.createGoal({
119
+ title: state.name,
120
+ intent: state.mission,
121
+ acceptance: [taskInput],
122
+ tags: ['workspace-agent', state.id],
123
+ });
124
+ const result = await planner.planAndExecute(taskInput, {
125
+ sessionId: state.id,
126
+ agentOpts,
127
+ autoApprove: true,
128
+ });
129
+ const steps = result.action.steps;
130
+ const toolCalls = steps
131
+ .filter(s => typeof s.tool === 'string' && s.tool.length > 0)
132
+ .map(s => ({
133
+ tool: s.tool,
134
+ args: s.args,
135
+ result: s.result,
136
+ }));
137
+ return {
138
+ planId: goal.id,
139
+ steps,
140
+ toolCalls,
141
+ };
142
+ }
143
+ catch (err) {
144
+ // If tier-1 itself throws, don't crash the whole start() — degrade to
145
+ // Tier 2 so we still record the goal and a planner_note explaining why.
146
+ const msg = err instanceof Error ? err.message : String(err);
147
+ try {
148
+ const goal = await planner.createGoal({
149
+ title: state.name,
150
+ intent: state.mission,
151
+ acceptance: [taskInput],
152
+ tags: ['workspace-agent', state.id],
153
+ });
154
+ return {
155
+ planId: goal.id,
156
+ steps: [],
157
+ notes: [
158
+ `tier-1 planner failed (${msg}); recorded goal only`,
159
+ ],
160
+ };
161
+ }
162
+ catch {
163
+ return { planId: 'stub', steps: [] };
164
+ }
165
+ }
166
+ }
167
+ // Tier 2: planner loadable but no agentOpts (or flag absent) — record a
168
+ // goal and surface a TODO so callers know to wire AgentOptions through.
169
+ try {
170
+ const goal = await planner.createGoal({
171
+ title: state.name,
172
+ intent: state.mission,
173
+ acceptance: [taskInput],
174
+ tags: ['workspace-agent', state.id],
175
+ });
176
+ return {
177
+ planId: goal.id,
178
+ steps: [],
179
+ notes: [
180
+ 'real planAndExecute requires AgentOptions; configure caller to pass them.',
181
+ ],
182
+ };
183
+ }
184
+ catch {
185
+ // Tier 3: createGoal failed (e.g. read-only home dir) → stub.
186
+ return { planId: 'stub', steps: [] };
187
+ }
188
+ };
189
+ export class WorkspaceAgent {
190
+ root;
191
+ plannerStart;
192
+ constructor(opts = {}) {
193
+ this.root = opts.root ?? defaultRoot();
194
+ this.plannerStart = opts.plannerStart ?? defaultPlannerStart;
195
+ }
196
+ // ── Public API ───────────────────────────────────────────────────────────
197
+ async create(opts) {
198
+ if (!opts.name || !opts.name.trim()) {
199
+ throw new WorkspaceAgentError('create: name is required');
200
+ }
201
+ if (!opts.mission || !opts.mission.trim()) {
202
+ throw new WorkspaceAgentError('create: mission is required');
203
+ }
204
+ const existing = await listAll(this.root);
205
+ if (existing.some(s => s.name === opts.name)) {
206
+ throw new WorkspaceAgentError(`create: an agent named "${opts.name}" already exists in this workspace`);
207
+ }
208
+ const now = new Date().toISOString();
209
+ const state = {
210
+ id: randomUUID(),
211
+ name: opts.name,
212
+ mission: opts.mission,
213
+ allowedTools: opts.allowedTools ?? [],
214
+ scopes: opts.scopes ?? [],
215
+ status: 'idle',
216
+ createdAt: now,
217
+ updatedAt: now,
218
+ history: [
219
+ { ts: now, event: 'created', data: { name: opts.name } },
220
+ ],
221
+ };
222
+ await writeState(this.root, state);
223
+ return { id: state.id, name: state.name };
224
+ }
225
+ async start(agentId, taskInput, agentOpts = null) {
226
+ const state = await this.requireState(agentId);
227
+ if (state.status === 'running') {
228
+ throw new WorkspaceAgentError(`start: agent ${agentId} is already running`);
229
+ }
230
+ if (state.status === 'completed') {
231
+ throw new WorkspaceAgentError(`start: agent ${agentId} is completed; create a new agent`);
232
+ }
233
+ state.status = 'running';
234
+ state.updatedAt = new Date().toISOString();
235
+ state.history.push({
236
+ ts: state.updatedAt,
237
+ event: 'started',
238
+ data: { taskInput },
239
+ });
240
+ await writeState(this.root, state);
241
+ let planResult;
242
+ try {
243
+ planResult = await this.plannerStart(taskInput, state, agentOpts);
244
+ }
245
+ catch (err) {
246
+ const msg = err instanceof Error ? err.message : String(err);
247
+ state.status = 'failed';
248
+ state.updatedAt = new Date().toISOString();
249
+ state.history.push({
250
+ ts: state.updatedAt,
251
+ event: 'planner_error',
252
+ data: { message: msg },
253
+ });
254
+ await writeState(this.root, state);
255
+ throw new WorkspaceAgentError(`start: planner failed: ${msg}`);
256
+ }
257
+ state.currentPlanId = planResult.planId;
258
+ state.updatedAt = new Date().toISOString();
259
+ state.history.push({
260
+ ts: state.updatedAt,
261
+ event: 'plan_created',
262
+ data: { planId: planResult.planId, stepCount: planResult.steps.length },
263
+ });
264
+ await writeState(this.root, state);
265
+ // Surface any planner-emitted notes onto the agent's timeline.
266
+ if (planResult.notes && planResult.notes.length > 0) {
267
+ for (const note of planResult.notes) {
268
+ await this.appendEvent(agentId, 'planner_note', { message: note });
269
+ }
270
+ }
271
+ // Gate + record every tool call the planner produced. ScopeError from
272
+ // gate() must NOT escape start() — convert to a `tool_blocked` event and
273
+ // continue with the rest.
274
+ if (planResult.toolCalls && planResult.toolCalls.length > 0) {
275
+ for (const call of planResult.toolCalls) {
276
+ try {
277
+ await this.gate(agentId, call.tool);
278
+ }
279
+ catch (err) {
280
+ if (err instanceof ScopeError) {
281
+ await this.appendEvent(agentId, 'tool_blocked', {
282
+ tool: call.tool,
283
+ reason: err.message,
284
+ });
285
+ continue;
286
+ }
287
+ throw err;
288
+ }
289
+ await this.recordToolCall(agentId, call.tool, call.args, call.result);
290
+ }
291
+ }
292
+ return {
293
+ id: state.id,
294
+ status: state.status,
295
+ planId: planResult.planId,
296
+ steps: planResult.steps,
297
+ };
298
+ }
299
+ async resume(agentId) {
300
+ const state = await this.requireState(agentId);
301
+ if (state.status !== 'paused' && state.status !== 'failed') {
302
+ throw new WorkspaceAgentError(`resume: agent ${agentId} has status "${state.status}"; resume only allowed from paused or failed`);
303
+ }
304
+ state.status = 'running';
305
+ state.updatedAt = new Date().toISOString();
306
+ state.history.push({ ts: state.updatedAt, event: 'resumed' });
307
+ await writeState(this.root, state);
308
+ return state;
309
+ }
310
+ async stop(agentId) {
311
+ const state = await this.requireState(agentId);
312
+ state.status = 'paused';
313
+ state.updatedAt = new Date().toISOString();
314
+ state.history.push({ ts: state.updatedAt, event: 'stopped' });
315
+ await writeState(this.root, state);
316
+ return state;
317
+ }
318
+ async status(agentId) {
319
+ return this.requireState(agentId);
320
+ }
321
+ async list() {
322
+ const all = await listAll(this.root);
323
+ return all.map(s => ({ id: s.id, name: s.name, status: s.status }));
324
+ }
325
+ /**
326
+ * Permission gate. Throws ScopeError if the tool isn't in allowedTools and
327
+ * appends a `tool_denied` event to history. On allow, appends `tool_allowed`.
328
+ */
329
+ async gate(agentId, toolName) {
330
+ const state = await this.requireState(agentId);
331
+ if (!state.allowedTools.includes(toolName)) {
332
+ state.history.push({
333
+ ts: new Date().toISOString(),
334
+ event: 'tool_denied',
335
+ data: { tool: toolName },
336
+ });
337
+ state.updatedAt = new Date().toISOString();
338
+ await writeState(this.root, state);
339
+ throw new ScopeError(toolName);
340
+ }
341
+ state.history.push({
342
+ ts: new Date().toISOString(),
343
+ event: 'tool_allowed',
344
+ data: { tool: toolName },
345
+ });
346
+ state.updatedAt = new Date().toISOString();
347
+ await writeState(this.root, state);
348
+ }
349
+ /**
350
+ * Append a tool-call record to the agent's history. Caller must call
351
+ * `gate()` first; this method does NOT enforce permissions.
352
+ */
353
+ async recordToolCall(agentId, toolName, args, result) {
354
+ const state = await this.requireState(agentId);
355
+ state.history.push({
356
+ ts: new Date().toISOString(),
357
+ event: 'tool_call',
358
+ data: { tool: toolName, args, result },
359
+ });
360
+ state.updatedAt = new Date().toISOString();
361
+ await writeState(this.root, state);
362
+ }
363
+ // ── Internals ────────────────────────────────────────────────────────────
364
+ async appendEvent(agentId, event, data) {
365
+ const state = await this.requireState(agentId);
366
+ const ts = new Date().toISOString();
367
+ state.history.push({ ts, event, data });
368
+ state.updatedAt = ts;
369
+ await writeState(this.root, state);
370
+ }
371
+ async requireState(agentId) {
372
+ const state = await readState(this.root, agentId);
373
+ if (!state) {
374
+ throw new WorkspaceAgentError(`agent ${agentId} not found`);
375
+ }
376
+ return state;
377
+ }
378
+ }
379
+ //# sourceMappingURL=workspace-agents.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kernel.chat/kbot",
3
- "version": "3.99.31",
3
+ "version": "3.99.34",
4
4
  "description": "Open-source terminal AI agent. 787+ tools, 35 agents, 20 providers. Dreams, learns, watches your system. Controls your phone. Fully local, fully sovereign. MIT.",
5
5
  "type": "module",
6
6
  "repository": {