@kernel.chat/kbot 4.1.1 → 4.3.0

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.
@@ -0,0 +1,28 @@
1
+ import type { ToolDefinition } from '../../tools/index.js';
2
+ import type { AgentSdkExecutableTool, AgentSdkTool } from './types.js';
3
+ export interface FromAgentSdkOptions {
4
+ /** kbot tier to assign to the imported tool. Default 'free'. */
5
+ tier?: ToolDefinition['tier'];
6
+ /** Custom timeout (ms). */
7
+ timeout?: number;
8
+ /** Max result size (bytes). */
9
+ maxResultSize?: number;
10
+ /**
11
+ * Fallback executor when the source tool ships no handler. Useful when the
12
+ * caller routes execution elsewhere (e.g., back through the Anthropic SDK).
13
+ */
14
+ fallbackExecutor?: (toolName: string, args: Record<string, unknown>) => Promise<string> | string;
15
+ }
16
+ /**
17
+ * Convert a non-executable Agent SDK tool definition into a kbot ToolDefinition.
18
+ * Because the source has no handler, an executor must be supplied via opts.fallbackExecutor
19
+ * — otherwise the resulting tool will return a structured "no handler" error string when invoked.
20
+ */
21
+ export declare function fromAgentSdkTool(tool: AgentSdkTool, opts?: FromAgentSdkOptions): ToolDefinition;
22
+ /**
23
+ * Convert an executable Agent SDK tool (schema + handler) into a kbot ToolDefinition.
24
+ * Preferred over fromAgentSdkTool() when the caller has the implementation in process.
25
+ */
26
+ export declare function fromAgentSdkExecutableTool(tool: AgentSdkExecutableTool, opts?: Omit<FromAgentSdkOptions, 'fallbackExecutor'>): ToolDefinition;
27
+ export declare function fromAgentSdkTools(tools: AgentSdkTool[], opts?: FromAgentSdkOptions): ToolDefinition[];
28
+ //# sourceMappingURL=from-agent-sdk.d.ts.map
@@ -0,0 +1,107 @@
1
+ // Anthropic Agent SDK tool → kbot ToolDefinition
2
+ //
3
+ // Lets a kbot host import third-party Agent SDK tools (or hand-written
4
+ // schema/handler pairs) and register them in the kbot tool registry. Schema
5
+ // is downconverted from JSON Schema to kbot's flatter parameter shape.
6
+ //
7
+ // Inverse of to-agent-sdk.ts. Round-trip is lossy in general (JSON Schema is
8
+ // strictly richer than kbot's shape), but is stable for the param shapes
9
+ // that kbot itself uses.
10
+ const JSON_TO_KBOT = {
11
+ string: 'string',
12
+ number: 'number',
13
+ integer: 'integer',
14
+ boolean: 'boolean',
15
+ object: 'object',
16
+ array: 'array',
17
+ null: 'null',
18
+ };
19
+ function pickType(t) {
20
+ if (typeof t === 'string')
21
+ return JSON_TO_KBOT[t] ?? 'string';
22
+ if (Array.isArray(t)) {
23
+ // Pick the first non-null type to keep parity with kbot's single-type shape.
24
+ const first = t.find((x) => x !== 'null');
25
+ return first ? JSON_TO_KBOT[first] ?? 'string' : 'string';
26
+ }
27
+ return 'string';
28
+ }
29
+ function mapProperty(p, required) {
30
+ const out = {
31
+ type: pickType(p.type),
32
+ description: typeof p.description === 'string' ? p.description : '',
33
+ required,
34
+ };
35
+ if (p.default !== undefined)
36
+ out.default = p.default;
37
+ if (p.items !== undefined)
38
+ out.items = p.items;
39
+ if (p.properties)
40
+ out.properties = p.properties;
41
+ return out;
42
+ }
43
+ function buildParameters(schema) {
44
+ if (!schema || typeof schema !== 'object')
45
+ return {};
46
+ const required = new Set(schema.required ?? []);
47
+ const out = {};
48
+ for (const [name, prop] of Object.entries(schema.properties ?? {})) {
49
+ out[name] = mapProperty(prop, required.has(name));
50
+ }
51
+ return out;
52
+ }
53
+ /**
54
+ * Convert a non-executable Agent SDK tool definition into a kbot ToolDefinition.
55
+ * Because the source has no handler, an executor must be supplied via opts.fallbackExecutor
56
+ * — otherwise the resulting tool will return a structured "no handler" error string when invoked.
57
+ */
58
+ export function fromAgentSdkTool(tool, opts = {}) {
59
+ const fallback = opts.fallbackExecutor;
60
+ return {
61
+ name: tool.name,
62
+ description: tool.description,
63
+ parameters: buildParameters(tool.input_schema),
64
+ tier: opts.tier ?? 'free',
65
+ timeout: opts.timeout,
66
+ maxResultSize: opts.maxResultSize,
67
+ async execute(args) {
68
+ if (fallback) {
69
+ try {
70
+ const result = await fallback(tool.name, args);
71
+ return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
72
+ }
73
+ catch (e) {
74
+ return `Error: ${e.message}`;
75
+ }
76
+ }
77
+ return `Error: tool "${tool.name}" was imported without a handler. Provide opts.fallbackExecutor when calling fromAgentSdkTool().`;
78
+ },
79
+ };
80
+ }
81
+ /**
82
+ * Convert an executable Agent SDK tool (schema + handler) into a kbot ToolDefinition.
83
+ * Preferred over fromAgentSdkTool() when the caller has the implementation in process.
84
+ */
85
+ export function fromAgentSdkExecutableTool(tool, opts = {}) {
86
+ return {
87
+ name: tool.name,
88
+ description: tool.description,
89
+ parameters: buildParameters(tool.input_schema),
90
+ tier: opts.tier ?? 'free',
91
+ timeout: opts.timeout,
92
+ maxResultSize: opts.maxResultSize,
93
+ async execute(args) {
94
+ try {
95
+ const result = await tool.handler(args);
96
+ return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
97
+ }
98
+ catch (e) {
99
+ return `Error: ${e.message}`;
100
+ }
101
+ },
102
+ };
103
+ }
104
+ export function fromAgentSdkTools(tools, opts = {}) {
105
+ return tools.map((t) => fromAgentSdkTool(t, opts));
106
+ }
107
+ //# sourceMappingURL=from-agent-sdk.js.map
@@ -0,0 +1,4 @@
1
+ export type { AgentSdkTool, AgentSdkExecutableTool, AgentSdkInputSchema, JsonSchemaProperty, JsonSchemaType, } from './types.js';
2
+ export { toAgentSdkTool, toAgentSdkTools, type ToAgentSdkOptions, } from './to-agent-sdk.js';
3
+ export { fromAgentSdkTool, fromAgentSdkExecutableTool, fromAgentSdkTools, type FromAgentSdkOptions, } from './from-agent-sdk.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,14 @@
1
+ // Anthropic Agent SDK adapter — bidirectional schema translation between
2
+ // kbot's ToolDefinition surface and the Agent SDK / Messages API tool surface.
3
+ //
4
+ // kbot stays provider-agnostic — this adapter takes no runtime dependency
5
+ // on @anthropic-ai/sdk. It only translates schemas and (for the from-side)
6
+ // wraps optional executors. Round-trip kbot → Agent SDK → kbot is stable
7
+ // for the parameter shapes kbot itself uses.
8
+ //
9
+ // Background: with the Anthropic Agent SDK opening to external developers
10
+ // in May 2026, kbot tools can now be advertised to that ecosystem and
11
+ // vice-versa — without coupling the registry to one provider's runtime.
12
+ export { toAgentSdkTool, toAgentSdkTools, } from './to-agent-sdk.js';
13
+ export { fromAgentSdkTool, fromAgentSdkExecutableTool, fromAgentSdkTools, } from './from-agent-sdk.js';
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,13 @@
1
+ import type { ToolDefinition } from '../../tools/index.js';
2
+ import type { AgentSdkTool } from './types.js';
3
+ export interface ToAgentSdkOptions {
4
+ /** If true (default), kbot tool names are passed through unchanged. */
5
+ preserveName?: boolean;
6
+ /** Optional rename hook. Wins over preserveName. */
7
+ renameTool?: (name: string) => string;
8
+ /** If true, pass `additionalProperties: false` on the input schema. Default true. */
9
+ strict?: boolean;
10
+ }
11
+ export declare function toAgentSdkTool(tool: ToolDefinition, opts?: ToAgentSdkOptions): AgentSdkTool;
12
+ export declare function toAgentSdkTools(tools: ToolDefinition[], opts?: ToAgentSdkOptions): AgentSdkTool[];
13
+ //# sourceMappingURL=to-agent-sdk.d.ts.map
@@ -0,0 +1,73 @@
1
+ // kbot ToolDefinition → Anthropic Agent SDK tool
2
+ //
3
+ // One-way schema translation. The Agent SDK delivers tool_use blocks and
4
+ // expects the host to route them to a handler — that routing belongs in the
5
+ // caller's agent loop, not in the adapter. This file only produces the
6
+ // schema half so kbot tools can be advertised to the Agent SDK / Messages
7
+ // API without taking a runtime dependency on @anthropic-ai/sdk.
8
+ const KBOT_TYPE_TO_JSON = {
9
+ string: 'string',
10
+ number: 'number',
11
+ integer: 'integer',
12
+ boolean: 'boolean',
13
+ object: 'object',
14
+ array: 'array',
15
+ null: 'null',
16
+ };
17
+ function mapType(t) {
18
+ const lc = t.toLowerCase();
19
+ return KBOT_TYPE_TO_JSON[lc] ?? 'string';
20
+ }
21
+ function mapParameter(p) {
22
+ const out = {
23
+ type: mapType(p.type),
24
+ description: p.description,
25
+ };
26
+ if (p.default !== undefined)
27
+ out.default = p.default;
28
+ if (p.items)
29
+ out.items = p.items;
30
+ if (p.properties) {
31
+ const nested = {};
32
+ for (const [k, v] of Object.entries(p.properties)) {
33
+ // Best-effort: nested properties in kbot params are loosely typed.
34
+ const vv = v;
35
+ nested[k] = {
36
+ type: vv.type ? mapType(vv.type) : 'string',
37
+ description: vv.description ?? '',
38
+ };
39
+ }
40
+ out.properties = nested;
41
+ }
42
+ return out;
43
+ }
44
+ export function toAgentSdkTool(tool, opts = {}) {
45
+ const properties = {};
46
+ const required = [];
47
+ for (const [name, param] of Object.entries(tool.parameters)) {
48
+ properties[name] = mapParameter(param);
49
+ if (param.required)
50
+ required.push(name);
51
+ }
52
+ const input_schema = {
53
+ type: 'object',
54
+ properties,
55
+ additionalProperties: opts.strict === false ? true : false,
56
+ };
57
+ if (required.length > 0)
58
+ input_schema.required = required;
59
+ const name = opts.renameTool
60
+ ? opts.renameTool(tool.name)
61
+ : opts.preserveName === false
62
+ ? tool.name.replace(/[^A-Za-z0-9_-]/g, '_')
63
+ : tool.name;
64
+ return {
65
+ name,
66
+ description: tool.description,
67
+ input_schema,
68
+ };
69
+ }
70
+ export function toAgentSdkTools(tools, opts = {}) {
71
+ return tools.map((t) => toAgentSdkTool(t, opts));
72
+ }
73
+ //# sourceMappingURL=to-agent-sdk.js.map
@@ -0,0 +1,41 @@
1
+ export type JsonSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null';
2
+ export interface JsonSchemaProperty {
3
+ type?: JsonSchemaType | JsonSchemaType[];
4
+ description?: string;
5
+ enum?: unknown[];
6
+ items?: JsonSchemaProperty | Record<string, unknown>;
7
+ properties?: Record<string, JsonSchemaProperty>;
8
+ required?: string[];
9
+ default?: unknown;
10
+ [k: string]: unknown;
11
+ }
12
+ export interface AgentSdkInputSchema {
13
+ type: 'object';
14
+ properties: Record<string, JsonSchemaProperty>;
15
+ required?: string[];
16
+ additionalProperties?: boolean;
17
+ }
18
+ /**
19
+ * The on-the-wire tool definition the Agent SDK and the Messages API both
20
+ * accept. Names are snake_case by convention; the SDK does not enforce.
21
+ */
22
+ export interface AgentSdkTool {
23
+ name: string;
24
+ description: string;
25
+ input_schema: AgentSdkInputSchema;
26
+ }
27
+ /**
28
+ * Optional executable companion. The SDK delivers `tool_use` blocks; the
29
+ * caller is responsible for routing them to a handler. `AgentSdkExecutableTool`
30
+ * couples the schema with a handler so the from-adapter can hand back something
31
+ * the kbot registry can run.
32
+ */
33
+ export interface AgentSdkExecutableTool extends AgentSdkTool {
34
+ /**
35
+ * Handler may return a string (passed through) or arbitrary JSON-serializable
36
+ * value (stringified by the adapter). Mirrors what real Agent SDK handlers
37
+ * return in practice.
38
+ */
39
+ handler: (input: Record<string, unknown>) => Promise<unknown> | unknown;
40
+ }
41
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,9 @@
1
+ // Anthropic Agent SDK tool surface — minimal type model.
2
+ //
3
+ // Mirrors the Tool shape used by @anthropic-ai/sdk and the Agent SDK
4
+ // without taking a runtime dependency. kbot stays provider-agnostic;
5
+ // this adapter only ever speaks JSON Schema and JSON.
6
+ //
7
+ // Reference: https://docs.anthropic.com/en/api/messages#tools
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
package/dist/cli.js CHANGED
@@ -56,6 +56,7 @@ async function main() {
56
56
  .option('--lite', 'Lightweight mode — skip heavy tools (auto-enabled on Replit)')
57
57
  .option('--safe', 'Confirm destructive operations')
58
58
  .option('--strict', 'Confirm ALL operations')
59
+ .option('--persona <id>', 'Scope tool access to a persona (researcher, coder, computer-use)')
59
60
  .option('--ollama-launch', 'Auto-configure for ollama launch (sets Ollama as provider)')
60
61
  .argument('[prompt...]', 'One-shot prompt')
61
62
  .helpOption('-h, --help', 'display help for command')
@@ -4369,7 +4370,7 @@ async function main() {
4369
4370
  }
4370
4371
  // Permission mode: autonomous by default, users opt-in to confirmations
4371
4372
  {
4372
- const { setPermissionMode } = await import('./permissions.js');
4373
+ const { setPermissionMode, setActivePersona } = await import('./permissions.js');
4373
4374
  if (opts.yes) {
4374
4375
  // --yes / -y: skip all confirmations (for scripts & CI)
4375
4376
  setPermissionMode('permissive');
@@ -4384,6 +4385,19 @@ async function main() {
4384
4385
  // DEFAULT: permissive — kbot acts autonomously, no confirmation prompts
4385
4386
  setPermissionMode('permissive');
4386
4387
  }
4388
+ // Persona scoping (v4.2.0). Optional. Falls back to env var.
4389
+ const personaId = opts.persona || process.env.KBOT_PERSONA || null;
4390
+ if (personaId) {
4391
+ try {
4392
+ setActivePersona(personaId);
4393
+ if (!opts.quiet && !opts.pipe)
4394
+ printInfo(`Persona: ${personaId}`);
4395
+ }
4396
+ catch (err) {
4397
+ printError(err.message);
4398
+ process.exit(1);
4399
+ }
4400
+ }
4387
4401
  }
4388
4402
  // Register built-in agents (hacker, operator, dreamer) so --agent flag works
4389
4403
  registerBuiltinAgents();
@@ -1,4 +1,21 @@
1
+ import { type Persona } from './futures/persona/index.js';
1
2
  export type PermissionMode = 'permissive' | 'normal' | 'strict';
3
+ /**
4
+ * Set (or clear) the active persona by id. Pass null to disable persona checking.
5
+ * Throws if id is not found in PERSONA_REGISTRY.
6
+ *
7
+ * v4.2.0 wires the futures/persona substrate into the live permissions chain.
8
+ * When a persona is set, every checkPermission() call runs canInvoke() FIRST;
9
+ * if the persona denies, the tool is blocked before the destructive-op prompt.
10
+ */
11
+ export declare function setActivePersona(id: string | null): void;
12
+ /** Get the currently active persona, or null if none. */
13
+ export declare function getActivePersona(): Persona | null;
14
+ /**
15
+ * Check the active persona (if any) against a tool invocation.
16
+ * Returns the denial reason string if denied, or null if allowed (or no persona set).
17
+ */
18
+ export declare function checkPersonaScope(toolName: string, args: Record<string, unknown>): string | null;
2
19
  /** Set the permission mode */
3
20
  export declare function setPermissionMode(mode: PermissionMode): void;
4
21
  /** Get the current permission mode */
@@ -13,7 +13,46 @@
13
13
  // 'strict' — confirm all file writes and tool calls
14
14
  import { createInterface } from 'node:readline';
15
15
  import chalk from 'chalk';
16
+ import { canInvoke, PERSONA_REGISTRY, } from './futures/persona/index.js';
16
17
  let currentMode = 'normal';
18
+ /** Active persona for this CLI run. null = no persona-scoping (default). */
19
+ let activePersona = null;
20
+ /**
21
+ * Set (or clear) the active persona by id. Pass null to disable persona checking.
22
+ * Throws if id is not found in PERSONA_REGISTRY.
23
+ *
24
+ * v4.2.0 wires the futures/persona substrate into the live permissions chain.
25
+ * When a persona is set, every checkPermission() call runs canInvoke() FIRST;
26
+ * if the persona denies, the tool is blocked before the destructive-op prompt.
27
+ */
28
+ export function setActivePersona(id) {
29
+ if (id === null) {
30
+ activePersona = null;
31
+ return;
32
+ }
33
+ const persona = PERSONA_REGISTRY[id];
34
+ if (!persona) {
35
+ const known = Object.keys(PERSONA_REGISTRY).join(', ');
36
+ throw new Error(`unknown persona "${id}". Known personas: ${known}`);
37
+ }
38
+ activePersona = persona;
39
+ }
40
+ /** Get the currently active persona, or null if none. */
41
+ export function getActivePersona() {
42
+ return activePersona;
43
+ }
44
+ /**
45
+ * Check the active persona (if any) against a tool invocation.
46
+ * Returns the denial reason string if denied, or null if allowed (or no persona set).
47
+ */
48
+ export function checkPersonaScope(toolName, args) {
49
+ if (!activePersona)
50
+ return null;
51
+ const verdict = canInvoke(activePersona, toolName, args);
52
+ if (verdict.allowed)
53
+ return null;
54
+ return `Persona '${activePersona.id}' denies '${toolName}': ${verdict.reason ?? 'permission denied'}`;
55
+ }
17
56
  /** Patterns that always require confirmation in normal mode */
18
57
  const DESTRUCTIVE_PATTERNS = [
19
58
  { pattern: /^git\s+push/i, reason: 'Pushes code to remote — visible to others' },
@@ -119,6 +158,15 @@ export async function confirmToolCall(toolName, args, reason) {
119
158
  * Used as middleware in the tool execution pipeline.
120
159
  */
121
160
  export async function checkPermission(toolName, args) {
161
+ // Persona check fires BEFORE the destructive-op prompt. If a persona is
162
+ // set and denies the tool, fail fast — no confirmation prompt, no retry.
163
+ const personaDenial = checkPersonaScope(toolName, args);
164
+ if (personaDenial) {
165
+ console.log();
166
+ console.log(` ${chalk.red('✗')} ${personaDenial}`);
167
+ console.log();
168
+ return false;
169
+ }
122
170
  const reason = needsConfirmation(toolName, args);
123
171
  if (!reason)
124
172
  return true;
@@ -11,6 +11,7 @@
11
11
  // pipeline.use(metricsMiddleware(recordMetrics))
12
12
  // pipeline.use(executionMiddleware(executeTool))
13
13
  // await pipeline.execute(ctx)
14
+ import { checkPersonaScope } from './permissions.js';
14
15
  export class ToolPipeline {
15
16
  middleware = [];
16
17
  /** Append middleware to the end of the pipeline */
@@ -56,6 +57,16 @@ export class ToolPipeline {
56
57
  */
57
58
  export function permissionMiddleware(checkPermission) {
58
59
  return async (ctx, next) => {
60
+ // v4.2.0: persona scope check fires BEFORE the destructive-op
61
+ // confirmation. A persona denial blocks the tool with a clear reason
62
+ // and is non-interactive — no prompt, no retry.
63
+ const personaDenial = checkPersonaScope(ctx.toolName, ctx.toolArgs);
64
+ if (personaDenial) {
65
+ ctx.aborted = true;
66
+ ctx.abortReason = personaDenial;
67
+ ctx.error = personaDenial;
68
+ return;
69
+ }
59
70
  const allowed = await checkPermission(ctx.toolName, ctx.toolArgs);
60
71
  if (!allowed) {
61
72
  ctx.aborted = true;
@@ -0,0 +1,47 @@
1
+ import type { ToolDefinition } from './index.js';
2
+ type Severity = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW' | 'INFO';
3
+ export interface SurfaceSignal {
4
+ id: string;
5
+ category: string;
6
+ severity: Severity;
7
+ file: string;
8
+ line: number;
9
+ excerpt: string;
10
+ pattern: string;
11
+ ts: number;
12
+ }
13
+ export interface SurfaceMap {
14
+ sessionId: string;
15
+ target: string;
16
+ startedAt: number;
17
+ filesWalked: number;
18
+ bytesRead: number;
19
+ signals: SurfaceSignal[];
20
+ skipped: {
21
+ path: string;
22
+ reason: string;
23
+ }[];
24
+ }
25
+ export interface BuildSurfaceMapOptions {
26
+ target: string;
27
+ excludes?: Iterable<string>;
28
+ severityFloor?: Severity;
29
+ sessionId?: string;
30
+ }
31
+ export declare function buildSurfaceMap(opts: BuildSurfaceMapOptions): SurfaceMap;
32
+ export declare function persistSurfaceMap(map: SurfaceMap, baseDir?: string): string;
33
+ export declare function renderSurfaceMap(map: SurfaceMap, persistedTo: string): string;
34
+ export interface RunOptions {
35
+ target: string;
36
+ severityFloor?: Severity;
37
+ excludes?: string[];
38
+ baseDir?: string;
39
+ }
40
+ export declare function runSecurityAuditLocal(opts: RunOptions): {
41
+ map: SurfaceMap;
42
+ persistedTo: string;
43
+ markdown: string;
44
+ };
45
+ export declare const securityAuditLocalTool: ToolDefinition;
46
+ export {};
47
+ //# sourceMappingURL=security-audit-local.d.ts.map