@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.
- package/README.md +14 -1
- package/dist/agents/security-agent.d.ts +31 -0
- package/dist/agents/security-agent.js +180 -0
- package/dist/agents/security-rules.d.ts +35 -0
- package/dist/agents/security-rules.js +206 -0
- package/dist/agents/specialists.d.ts +6 -0
- package/dist/agents/specialists.js +45 -0
- package/dist/architect.js +5 -0
- package/dist/auth.js +1 -1
- package/dist/channels/matrix.d.ts +4 -0
- package/dist/channels/matrix.js +28 -0
- package/dist/channels/office.d.ts +78 -0
- package/dist/channels/office.js +169 -0
- package/dist/channels/registry.d.ts +8 -0
- package/dist/channels/registry.js +38 -0
- package/dist/channels/signal.d.ts +4 -0
- package/dist/channels/signal.js +29 -0
- package/dist/channels/slack.d.ts +4 -0
- package/dist/channels/slack.js +97 -0
- package/dist/channels/teams.d.ts +4 -0
- package/dist/channels/teams.js +29 -0
- package/dist/channels/telegram.d.ts +4 -0
- package/dist/channels/telegram.js +28 -0
- package/dist/channels/types.d.ts +50 -0
- package/dist/channels/types.js +13 -0
- package/dist/channels/whatsapp.d.ts +4 -0
- package/dist/channels/whatsapp.js +28 -0
- package/dist/cli.js +81 -0
- package/dist/computer-use-coordinator.d.ts +44 -0
- package/dist/computer-use-coordinator.js +0 -0
- package/dist/file-library.d.ts +76 -0
- package/dist/file-library.js +269 -0
- package/dist/ide/mcp-server.js +4 -4
- package/dist/managed-agents-anthropic.d.ts +90 -0
- package/dist/managed-agents-anthropic.js +123 -0
- package/dist/plugins-integrity.d.ts +72 -0
- package/dist/plugins-integrity.js +153 -0
- package/dist/plugins.d.ts +13 -2
- package/dist/plugins.js +87 -10
- package/dist/setup-editor.d.ts +28 -0
- package/dist/setup-editor.js +222 -0
- package/dist/tools/anthropic-managed-agents-tools.d.ts +22 -0
- package/dist/tools/anthropic-managed-agents-tools.js +191 -0
- package/dist/tools/channel-tools.d.ts +4 -0
- package/dist/tools/channel-tools.js +80 -0
- package/dist/tools/computer-coordinator-tools.d.ts +13 -0
- package/dist/tools/computer-coordinator-tools.js +104 -0
- package/dist/tools/computer.js +463 -299
- package/dist/tools/file-library-tools.d.ts +12 -0
- package/dist/tools/file-library-tools.js +191 -0
- package/dist/tools/image-thoughtful.d.ts +31 -0
- package/dist/tools/image-thoughtful.js +233 -0
- package/dist/tools/index.js +1 -0
- package/dist/tools/matrix.js +3 -3
- package/dist/tools/redblue.js +2 -2
- package/dist/tools/security-agent-tools.d.ts +34 -0
- package/dist/tools/security-agent-tools.js +30 -0
- package/dist/tools/security-hunt.js +1 -1
- package/dist/tools/subagent.js +2 -2
- package/dist/tools/swarm-2026-04.d.ts +2 -0
- package/dist/tools/swarm-2026-04.js +91 -0
- package/dist/tools/visa-payments.js +1 -1
- package/dist/tools/workspace-agent-tools.d.ts +19 -0
- package/dist/tools/workspace-agent-tools.js +191 -0
- package/dist/workspace-agents.d.ts +132 -0
- package/dist/workspace-agents.js +379 -0
- 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.
|
|
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": {
|