@kernel.chat/kbot 3.99.31 → 3.99.33

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 (58) hide show
  1. package/README.md +1 -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/computer-use-coordinator.d.ts +44 -0
  29. package/dist/computer-use-coordinator.js +0 -0
  30. package/dist/file-library.d.ts +76 -0
  31. package/dist/file-library.js +269 -0
  32. package/dist/managed-agents-anthropic.d.ts +90 -0
  33. package/dist/managed-agents-anthropic.js +123 -0
  34. package/dist/plugins-integrity.d.ts +72 -0
  35. package/dist/plugins-integrity.js +153 -0
  36. package/dist/plugins.d.ts +13 -2
  37. package/dist/plugins.js +87 -10
  38. package/dist/tools/anthropic-managed-agents-tools.d.ts +22 -0
  39. package/dist/tools/anthropic-managed-agents-tools.js +191 -0
  40. package/dist/tools/channel-tools.d.ts +4 -0
  41. package/dist/tools/channel-tools.js +80 -0
  42. package/dist/tools/computer-coordinator-tools.d.ts +13 -0
  43. package/dist/tools/computer-coordinator-tools.js +104 -0
  44. package/dist/tools/computer.js +463 -299
  45. package/dist/tools/file-library-tools.d.ts +12 -0
  46. package/dist/tools/file-library-tools.js +191 -0
  47. package/dist/tools/image-thoughtful.d.ts +31 -0
  48. package/dist/tools/image-thoughtful.js +233 -0
  49. package/dist/tools/index.js +1 -0
  50. package/dist/tools/security-agent-tools.d.ts +34 -0
  51. package/dist/tools/security-agent-tools.js +30 -0
  52. package/dist/tools/swarm-2026-04.d.ts +2 -0
  53. package/dist/tools/swarm-2026-04.js +91 -0
  54. package/dist/tools/workspace-agent-tools.d.ts +19 -0
  55. package/dist/tools/workspace-agent-tools.js +191 -0
  56. package/dist/workspace-agents.d.ts +132 -0
  57. package/dist/workspace-agents.js +379 -0
  58. package/package.json +1 -1
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Workspace Agent kbot tool definitions.
3
+ *
4
+ * NOT registered in tools/index.ts. Wire up via `registerWorkspaceAgentTools()`
5
+ * from a higher-level bootstrap once the feature is shipped.
6
+ *
7
+ * Each tool returns a string (the kbot tool contract). State persists via
8
+ * the WorkspaceAgent class — file at <root>/<id>.json.
9
+ */
10
+ import { WorkspaceAgent, } from '../workspace-agents.js';
11
+ function getAgent(opts) {
12
+ return new WorkspaceAgent(opts ?? {});
13
+ }
14
+ function fmt(value) {
15
+ if (typeof value === 'string')
16
+ return value;
17
+ return '```json\n' + JSON.stringify(value, null, 2) + '\n```';
18
+ }
19
+ function parseJsonArray(raw, field) {
20
+ if (raw === undefined || raw === null || raw === '')
21
+ return [];
22
+ if (Array.isArray(raw))
23
+ return raw.map(String);
24
+ if (typeof raw === 'string') {
25
+ const trimmed = raw.trim();
26
+ if (!trimmed)
27
+ return [];
28
+ try {
29
+ const parsed = JSON.parse(trimmed);
30
+ if (!Array.isArray(parsed)) {
31
+ throw new Error(`${field} must be a JSON array`);
32
+ }
33
+ return parsed.map(String);
34
+ }
35
+ catch (e) {
36
+ // accept comma-separated as a convenience
37
+ if (!trimmed.startsWith('[')) {
38
+ return trimmed
39
+ .split(',')
40
+ .map(s => s.trim())
41
+ .filter(Boolean);
42
+ }
43
+ throw e;
44
+ }
45
+ }
46
+ throw new Error(`${field} must be a string or array`);
47
+ }
48
+ export const workspaceAgentCreate = {
49
+ name: 'workspace_agent_create',
50
+ description: 'Create a long-running named workspace agent. Persists at ~/.kbot/workspace-agents/<id>.json. ' +
51
+ 'Names must be unique per workspace. Permissions enforced via allowedTools whitelist + scopes.',
52
+ parameters: {
53
+ name: {
54
+ type: 'string',
55
+ description: 'Unique name for this agent in the workspace.',
56
+ required: true,
57
+ },
58
+ mission: {
59
+ type: 'string',
60
+ description: '1–3 sentence mission the agent will pursue across sessions.',
61
+ required: true,
62
+ },
63
+ allowedTools: {
64
+ type: 'string',
65
+ description: 'JSON array (or comma-separated list) of tool names this agent may invoke. Empty = no tool calls.',
66
+ },
67
+ scopes: {
68
+ type: 'string',
69
+ description: 'JSON array (or comma-separated list) of capability scopes, e.g. ["read:files","write:tools","invoke:slack"].',
70
+ },
71
+ },
72
+ tier: 'free',
73
+ async execute(args) {
74
+ try {
75
+ const allowedTools = parseJsonArray(args.allowedTools, 'allowedTools');
76
+ const scopes = parseJsonArray(args.scopes, 'scopes');
77
+ const result = await getAgent().create({
78
+ name: String(args.name),
79
+ mission: String(args.mission),
80
+ allowedTools,
81
+ scopes,
82
+ });
83
+ return fmt({ created: result });
84
+ }
85
+ catch (e) {
86
+ return `Error: ${e.message}`;
87
+ }
88
+ },
89
+ };
90
+ export const workspaceAgentStart = {
91
+ name: 'workspace_agent_start',
92
+ description: 'Start a workspace agent on a task. Transitions status to "running", invokes the planner, persists tool calls into history.',
93
+ parameters: {
94
+ agentId: { type: 'string', description: 'Agent id from create.', required: true },
95
+ taskInput: {
96
+ type: 'string',
97
+ description: 'Task prompt to plan and execute.',
98
+ required: true,
99
+ },
100
+ },
101
+ tier: 'free',
102
+ async execute(args) {
103
+ try {
104
+ const result = await getAgent().start(String(args.agentId), String(args.taskInput));
105
+ return fmt({ started: result });
106
+ }
107
+ catch (e) {
108
+ return `Error: ${e.message}`;
109
+ }
110
+ },
111
+ };
112
+ export const workspaceAgentResume = {
113
+ name: 'workspace_agent_resume',
114
+ description: 'Resume a paused or failed workspace agent. Loads state from disk and transitions to running.',
115
+ parameters: {
116
+ agentId: { type: 'string', description: 'Agent id.', required: true },
117
+ },
118
+ tier: 'free',
119
+ async execute(args) {
120
+ try {
121
+ const state = await getAgent().resume(String(args.agentId));
122
+ return fmt({
123
+ resumed: { id: state.id, name: state.name, status: state.status },
124
+ });
125
+ }
126
+ catch (e) {
127
+ return `Error: ${e.message}`;
128
+ }
129
+ },
130
+ };
131
+ export const workspaceAgentStatus = {
132
+ name: 'workspace_agent_status',
133
+ description: 'Get full state for a workspace agent (status, history, plan).',
134
+ parameters: {
135
+ agentId: { type: 'string', description: 'Agent id.', required: true },
136
+ },
137
+ tier: 'free',
138
+ async execute(args) {
139
+ try {
140
+ const state = await getAgent().status(String(args.agentId));
141
+ return fmt(state);
142
+ }
143
+ catch (e) {
144
+ return `Error: ${e.message}`;
145
+ }
146
+ },
147
+ };
148
+ export const workspaceAgentStop = {
149
+ name: 'workspace_agent_stop',
150
+ description: 'Stop a running workspace agent. Transitions status to "paused".',
151
+ parameters: {
152
+ agentId: { type: 'string', description: 'Agent id.', required: true },
153
+ },
154
+ tier: 'free',
155
+ async execute(args) {
156
+ try {
157
+ const state = await getAgent().stop(String(args.agentId));
158
+ return fmt({
159
+ stopped: { id: state.id, name: state.name, status: state.status },
160
+ });
161
+ }
162
+ catch (e) {
163
+ return `Error: ${e.message}`;
164
+ }
165
+ },
166
+ };
167
+ export const workspaceAgentList = {
168
+ name: 'workspace_agent_list',
169
+ description: 'List all workspace agents in this workspace.',
170
+ parameters: {},
171
+ tier: 'free',
172
+ async execute() {
173
+ try {
174
+ const list = await getAgent().list();
175
+ return fmt({ agents: list, count: list.length });
176
+ }
177
+ catch (e) {
178
+ return `Error: ${e.message}`;
179
+ }
180
+ },
181
+ };
182
+ /** Convenience array — caller can iterate to register all six. */
183
+ export const workspaceAgentTools = [
184
+ workspaceAgentCreate,
185
+ workspaceAgentStart,
186
+ workspaceAgentResume,
187
+ workspaceAgentStatus,
188
+ workspaceAgentStop,
189
+ workspaceAgentList,
190
+ ];
191
+ //# sourceMappingURL=workspace-agent-tools.js.map
@@ -0,0 +1,132 @@
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 type { AgentOptions } from './agent.js';
18
+ export type WorkspaceAgentStatus = 'idle' | 'running' | 'paused' | 'completed' | 'failed';
19
+ export type Scope = string;
20
+ export interface HistoryEvent {
21
+ ts: string;
22
+ event: string;
23
+ data?: unknown;
24
+ }
25
+ export interface WorkspaceAgentState {
26
+ id: string;
27
+ name: string;
28
+ mission: string;
29
+ allowedTools: string[];
30
+ scopes: Scope[];
31
+ status: WorkspaceAgentStatus;
32
+ createdAt: string;
33
+ updatedAt: string;
34
+ currentPlanId?: string;
35
+ history: HistoryEvent[];
36
+ }
37
+ export interface CreateOptions {
38
+ name: string;
39
+ mission: string;
40
+ allowedTools?: string[];
41
+ scopes?: Scope[];
42
+ }
43
+ export interface CreateResult {
44
+ id: string;
45
+ name: string;
46
+ }
47
+ export interface StartResult {
48
+ id: string;
49
+ status: WorkspaceAgentStatus;
50
+ planId?: string;
51
+ steps: unknown[];
52
+ }
53
+ export interface ListEntry {
54
+ id: string;
55
+ name: string;
56
+ status: WorkspaceAgentStatus;
57
+ }
58
+ export declare class ScopeError extends Error {
59
+ readonly toolName: string;
60
+ constructor(toolName: string, message?: string);
61
+ }
62
+ export declare class WorkspaceAgentError extends Error {
63
+ constructor(message: string);
64
+ }
65
+ export declare function defaultRoot(): string;
66
+ /** A single tool call surfaced by the planner, ready to be gated + recorded. */
67
+ export interface PlannerToolCall {
68
+ tool: string;
69
+ args?: unknown;
70
+ result?: unknown;
71
+ }
72
+ export interface PlannerStartResult {
73
+ planId: string;
74
+ steps: unknown[];
75
+ /**
76
+ * Optional list of tool calls the planner produced. When present, the
77
+ * WorkspaceAgent will run each through `gate()` and `recordToolCall()`,
78
+ * capturing `tool_blocked` events for any that ScopeError out.
79
+ */
80
+ toolCalls?: PlannerToolCall[];
81
+ /**
82
+ * Free-form notes the planner wants surfaced as history events. Each entry
83
+ * becomes a `planner_note` event on the agent's timeline.
84
+ */
85
+ notes?: string[];
86
+ }
87
+ export type PlannerStartFn = (taskInput: string, state: WorkspaceAgentState, agentOpts?: AgentOptions | null) => Promise<PlannerStartResult>;
88
+ /**
89
+ * Default planner adapter — implements the 3-tier strategy:
90
+ *
91
+ * Tier 1: KBOT_PLANNER=hierarchical AND non-null agentOpts → real
92
+ * HierarchicalPlanner.planAndExecute. Tool calls extracted from
93
+ * the resulting Action.steps and surfaced for gating.
94
+ * Tier 2: HierarchicalPlanner module loadable but no agentOpts → call
95
+ * createGoal only; emit a TODO note; return early.
96
+ * Tier 3: Module import fails (e.g. test env) → deterministic stub
97
+ * `{ planId: 'stub', steps: [] }`.
98
+ *
99
+ * The function never throws on planner-internal failures: each tier degrades
100
+ * to the next so the WorkspaceAgent.start() lifecycle stays predictable.
101
+ */
102
+ export declare const defaultPlannerStart: PlannerStartFn;
103
+ export interface WorkspaceAgentOptions {
104
+ /** Override storage root. Defaults to env or ~/.kbot/workspace-agents. */
105
+ root?: string;
106
+ /** Override planner adapter (used by tests). */
107
+ plannerStart?: PlannerStartFn;
108
+ }
109
+ export declare class WorkspaceAgent {
110
+ private readonly root;
111
+ private readonly plannerStart;
112
+ constructor(opts?: WorkspaceAgentOptions);
113
+ create(opts: CreateOptions): Promise<CreateResult>;
114
+ start(agentId: string, taskInput: string, agentOpts?: AgentOptions | null): Promise<StartResult>;
115
+ resume(agentId: string): Promise<WorkspaceAgentState>;
116
+ stop(agentId: string): Promise<WorkspaceAgentState>;
117
+ status(agentId: string): Promise<WorkspaceAgentState>;
118
+ list(): Promise<ListEntry[]>;
119
+ /**
120
+ * Permission gate. Throws ScopeError if the tool isn't in allowedTools and
121
+ * appends a `tool_denied` event to history. On allow, appends `tool_allowed`.
122
+ */
123
+ gate(agentId: string, toolName: string): Promise<void>;
124
+ /**
125
+ * Append a tool-call record to the agent's history. Caller must call
126
+ * `gate()` first; this method does NOT enforce permissions.
127
+ */
128
+ recordToolCall(agentId: string, toolName: string, args: unknown, result?: unknown): Promise<void>;
129
+ private appendEvent;
130
+ private requireState;
131
+ }
132
+ //# sourceMappingURL=workspace-agents.d.ts.map
@@ -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