@deepstrike/sdk 0.1.15 → 0.2.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.
Files changed (57) hide show
  1. package/dist/collaboration/harness.d.ts +4 -1
  2. package/dist/collaboration/harness.js +20 -6
  3. package/dist/collaboration/modes/creator-verifier.d.ts +8 -2
  4. package/dist/collaboration/modes/creator-verifier.js +9 -3
  5. package/dist/collaboration/pool.d.ts +24 -3
  6. package/dist/collaboration/pool.js +65 -5
  7. package/dist/harness/harness.d.ts +5 -1
  8. package/dist/harness/harness.js +23 -1
  9. package/dist/index.d.ts +10 -1
  10. package/dist/index.js +5 -0
  11. package/dist/kernel.d.ts +12 -43
  12. package/dist/kernel.js +1 -1
  13. package/dist/providers/anthropic.js +7 -2
  14. package/dist/providers/base.d.ts +12 -0
  15. package/dist/providers/base.js +75 -0
  16. package/dist/providers/catalog.d.ts +3 -2
  17. package/dist/providers/catalog.js +61 -13
  18. package/dist/providers/deepseek.js +1 -1
  19. package/dist/providers/gemini.d.ts +3 -0
  20. package/dist/providers/gemini.js +26 -6
  21. package/dist/providers/glm.d.ts +9 -0
  22. package/dist/providers/glm.js +21 -0
  23. package/dist/providers/kimi.js +2 -0
  24. package/dist/providers/minimax.js +5 -0
  25. package/dist/providers/openai-responses.js +9 -0
  26. package/dist/providers/openai.js +52 -6
  27. package/dist/providers/profiles.d.ts +1304 -242
  28. package/dist/providers/profiles.js +429 -26
  29. package/dist/providers/qwen.d.ts +3 -1
  30. package/dist/providers/qwen.js +43 -13
  31. package/dist/runtime/archive.d.ts +15 -0
  32. package/dist/runtime/archive.js +35 -0
  33. package/dist/runtime/execution-plane.js +14 -4
  34. package/dist/runtime/filtered-plane.d.ts +14 -0
  35. package/dist/runtime/filtered-plane.js +45 -0
  36. package/dist/runtime/kernel-step.d.ts +88 -0
  37. package/dist/runtime/kernel-step.js +262 -0
  38. package/dist/runtime/process-sandbox-plane.d.ts +4 -4
  39. package/dist/runtime/process-sandbox-plane.js +5 -5
  40. package/dist/runtime/provider-replay.js +37 -3
  41. package/dist/runtime/replay-sanitize.d.ts +5 -0
  42. package/dist/runtime/replay-sanitize.js +25 -0
  43. package/dist/runtime/runner.d.ts +60 -4
  44. package/dist/runtime/runner.js +589 -86
  45. package/dist/runtime/session-log.d.ts +93 -1
  46. package/dist/runtime/session-repair.d.ts +37 -0
  47. package/dist/runtime/session-repair.js +77 -0
  48. package/dist/runtime/sub-agent-orchestrator.d.ts +19 -0
  49. package/dist/runtime/sub-agent-orchestrator.js +114 -0
  50. package/dist/skills/watcher.d.ts +19 -0
  51. package/dist/skills/watcher.js +38 -0
  52. package/dist/tools/index.d.ts +4 -1
  53. package/dist/tools/index.js +82 -11
  54. package/dist/types/agent.d.ts +68 -0
  55. package/dist/types/agent.js +78 -0
  56. package/dist/types.d.ts +27 -0
  57. package/package.json +2 -2
@@ -1,4 +1,23 @@
1
- import type { ProviderReplay, ToolCall } from "../types.js";
1
+ import type { ProviderReplay, ToolCall, ToolErrorKind } from "../types.js";
2
+ export type RollbackReason = {
3
+ kind: "fatal_tool_error";
4
+ tool_name: string;
5
+ error: string;
6
+ } | {
7
+ kind: "governance_denied";
8
+ tool_name: string;
9
+ reason: string;
10
+ } | {
11
+ kind: "provider_failure";
12
+ error: string;
13
+ } | {
14
+ kind: "timeout";
15
+ } | {
16
+ kind: "user_interrupt";
17
+ } | {
18
+ kind: "malformed_replay";
19
+ reason: string;
20
+ };
2
21
  export type SessionEvent = {
3
22
  kind: "run_started";
4
23
  run_id: string;
@@ -24,12 +43,85 @@ export type SessionEvent = {
24
43
  call_id: string;
25
44
  output: string;
26
45
  is_error?: boolean;
46
+ is_fatal?: boolean;
47
+ error_kind?: ToolErrorKind;
27
48
  token_count?: number;
28
49
  }>;
50
+ } | {
51
+ kind: "tool_argument_repaired";
52
+ turn: number;
53
+ tool: string;
54
+ original_arguments: string;
55
+ repaired_arguments: string;
56
+ } | {
57
+ kind: "tool_denied";
58
+ turn: number;
59
+ call_id: string;
60
+ tool_name: string;
61
+ reason: string;
62
+ } | {
63
+ kind: "permission_requested";
64
+ turn: number;
65
+ tool: string;
66
+ arguments: string;
67
+ reason?: string;
68
+ } | {
69
+ kind: "permission_resolved";
70
+ turn: number;
71
+ approved: boolean;
72
+ responder: string;
29
73
  } | {
30
74
  kind: "compressed";
31
75
  turn: number;
32
76
  archived_seq_range: [number, number];
77
+ action?: "snip_compact" | "micro_compact" | "context_collapse" | "auto_compact";
78
+ summary?: string;
79
+ summary_tokens?: number;
80
+ archive_ref?: string;
81
+ preserved_refs?: string[];
82
+ } | {
83
+ kind: "rollbacked";
84
+ turn: number;
85
+ checkpoint_history_len: number;
86
+ reason?: RollbackReason;
87
+ } | {
88
+ kind: "capability_changed";
89
+ turn: number;
90
+ added: string[];
91
+ removed: string[];
92
+ change_kind?: string;
93
+ capability_id?: string;
94
+ version?: string;
95
+ mounted_by?: string;
96
+ mount_reason?: string;
97
+ } | {
98
+ kind: "milestone_advanced";
99
+ turn: number;
100
+ phase_id: string;
101
+ capabilities_unlocked: string[];
102
+ } | {
103
+ kind: "milestone_blocked";
104
+ turn: number;
105
+ phase_id: string;
106
+ reason: string;
107
+ } | {
108
+ kind: "milestone_evidence";
109
+ turn: number;
110
+ phase_id: string;
111
+ evidence: string[];
112
+ } | {
113
+ kind: "checkpoint_taken";
114
+ turn: number;
115
+ history_len: number;
116
+ } | {
117
+ kind: "agent_spawned";
118
+ turn: number;
119
+ agent_id: string;
120
+ parent_session_id: string;
121
+ role: string;
122
+ isolation: string;
123
+ context_inheritance: string;
124
+ permitted_capability_ids: string[];
33
125
  } | {
34
126
  kind: "run_terminal";
35
127
  reason: string;
@@ -0,0 +1,37 @@
1
+ import type { ProviderReplay, ToolCall } from "../types.js";
2
+ import type { SessionEvent } from "./session-log.js";
3
+ export { REPLAY_CONTENT_MAX_BYTES as RECOVERY_CONTENT_MAX_BYTES } from "./replay-sanitize.js";
4
+ /** Minimal native blocks when provider_replay was not persisted. */
5
+ export declare function synthesizeProviderReplay(content: string, toolCalls: ToolCall[]): ProviderReplay | undefined;
6
+ export declare function effectiveProviderReplay(content: string, toolCalls: ToolCall[], stored?: ProviderReplay): ProviderReplay | undefined;
7
+ export declare function normalizeLlmCompleted(event: Extract<SessionEvent, {
8
+ kind: "llm_completed";
9
+ }>, maxBytes?: number): Extract<SessionEvent, {
10
+ kind: "llm_completed";
11
+ }>;
12
+ /** Repair event log for recovery minimum set before preload/wake. */
13
+ export declare function repairEventsForRecovery(events: Array<{
14
+ seq: number;
15
+ event: SessionEvent;
16
+ }>, maxBytes?: number): Array<{
17
+ seq: number;
18
+ event: SessionEvent;
19
+ }>;
20
+ /** Build a complete llm_completed payload for SessionLog append. */
21
+ export declare function buildLlmCompletedEvent(input: {
22
+ turn: number;
23
+ content: string;
24
+ tokenCount?: number;
25
+ toolCalls: ToolCall[];
26
+ providerReplay?: ProviderReplay;
27
+ }): Extract<SessionEvent, {
28
+ kind: "llm_completed";
29
+ }>;
30
+ /** Build run_terminal with required recovery fields. */
31
+ export declare function buildRunTerminalEvent(input: {
32
+ reason: string;
33
+ turnsUsed: number;
34
+ totalTokens: number;
35
+ }): Extract<SessionEvent, {
36
+ kind: "run_terminal";
37
+ }>;
@@ -0,0 +1,77 @@
1
+ import { sanitizeReplayText } from "./replay-sanitize.js";
2
+ export { REPLAY_CONTENT_MAX_BYTES as RECOVERY_CONTENT_MAX_BYTES } from "./replay-sanitize.js";
3
+ function estimateTokenCount(text) {
4
+ return Math.max(1, Math.ceil(text.length / 4));
5
+ }
6
+ function parseToolInput(args) {
7
+ try {
8
+ return JSON.parse(args || "{}");
9
+ }
10
+ catch {
11
+ return {};
12
+ }
13
+ }
14
+ /** Minimal native blocks when provider_replay was not persisted. */
15
+ export function synthesizeProviderReplay(content, toolCalls) {
16
+ if (!toolCalls.length)
17
+ return undefined;
18
+ const blocks = [];
19
+ if (content)
20
+ blocks.push({ type: "text", text: content });
21
+ for (const tc of toolCalls) {
22
+ blocks.push({
23
+ type: "tool_use",
24
+ id: tc.id,
25
+ name: tc.name,
26
+ input: parseToolInput(tc.arguments),
27
+ });
28
+ }
29
+ return { native_blocks: blocks };
30
+ }
31
+ export function effectiveProviderReplay(content, toolCalls, stored) {
32
+ if (stored?.native_blocks?.length || stored?.reasoning_content != null) {
33
+ return stored;
34
+ }
35
+ return synthesizeProviderReplay(content, toolCalls);
36
+ }
37
+ export function normalizeLlmCompleted(event, maxBytes) {
38
+ const content = sanitizeReplayText(event.content ?? "", maxBytes);
39
+ const toolCalls = event.tool_calls ?? [];
40
+ const providerReplay = effectiveProviderReplay(content, toolCalls, event.provider_replay);
41
+ return {
42
+ kind: "llm_completed",
43
+ turn: event.turn,
44
+ content,
45
+ tool_calls: toolCalls,
46
+ token_count: event.token_count ?? estimateTokenCount(content),
47
+ ...(providerReplay ? { provider_replay: providerReplay } : {}),
48
+ };
49
+ }
50
+ /** Repair event log for recovery minimum set before preload/wake. */
51
+ export function repairEventsForRecovery(events, maxBytes) {
52
+ return events.map(entry => {
53
+ if (entry.event.kind !== "llm_completed")
54
+ return entry;
55
+ return { ...entry, event: normalizeLlmCompleted(entry.event, maxBytes) };
56
+ });
57
+ }
58
+ /** Build a complete llm_completed payload for SessionLog append. */
59
+ export function buildLlmCompletedEvent(input) {
60
+ return normalizeLlmCompleted({
61
+ kind: "llm_completed",
62
+ turn: input.turn,
63
+ content: sanitizeReplayText(input.content),
64
+ tool_calls: input.toolCalls ?? [],
65
+ token_count: input.tokenCount,
66
+ provider_replay: input.providerReplay,
67
+ });
68
+ }
69
+ /** Build run_terminal with required recovery fields. */
70
+ export function buildRunTerminalEvent(input) {
71
+ return {
72
+ kind: "run_terminal",
73
+ reason: input.reason,
74
+ turns_used: Math.max(0, input.turnsUsed),
75
+ total_tokens: Math.max(0, input.totalTokens),
76
+ };
77
+ }
@@ -0,0 +1,19 @@
1
+ import type { StreamEvent } from "../types.js";
2
+ import type { AgentRunSpec, AgentSpawnedObservation, SubAgentResult } from "../types/agent.js";
3
+ import type { RuntimeOptions } from "./runner.js";
4
+ import type { SessionLog } from "./session-log.js";
5
+ export interface SubAgentRunContext {
6
+ parentOpts: RuntimeOptions;
7
+ parentSessionId: string;
8
+ spec: AgentRunSpec;
9
+ manifest: AgentSpawnedObservation;
10
+ sessionLog: SessionLog;
11
+ }
12
+ /** Host-side driver for kernel-isolated sub-agent runs. */
13
+ export declare class SubAgentOrchestrator {
14
+ stream(ctx: SubAgentRunContext): AsyncIterable<StreamEvent>;
15
+ run(ctx: SubAgentRunContext): Promise<SubAgentResult>;
16
+ }
17
+ export declare const defaultSubAgentOrchestrator: SubAgentOrchestrator;
18
+ /** Kernel spawn without an active parent run loop (harness / coordinator use). */
19
+ export declare function spawnStandalone(parentOpts: RuntimeOptions, parentSessionId: string, spec: AgentRunSpec, orchestrator?: SubAgentOrchestrator): Promise<SubAgentResult>;
@@ -0,0 +1,114 @@
1
+ import { agentRunSpecToKernel } from "../types/agent.js";
2
+ import { FilteredExecutionPlane } from "./filtered-plane.js";
3
+ import { kernelApply } from "./kernel-step.js";
4
+ function terminationFromStatus(status) {
5
+ const normalized = status.toLowerCase();
6
+ if (normalized === "completed" ||
7
+ normalized === "max_turns" ||
8
+ normalized === "token_budget" ||
9
+ normalized === "timeout" ||
10
+ normalized === "user_abort" ||
11
+ normalized === "error" ||
12
+ normalized === "milestone_exceeded") {
13
+ return normalized;
14
+ }
15
+ return status;
16
+ }
17
+ /** Host-side driver for kernel-isolated sub-agent runs. */
18
+ export class SubAgentOrchestrator {
19
+ async *stream(ctx) {
20
+ const permitted = new Set(ctx.manifest.permitted_capability_ids ?? []);
21
+ const filteredPlane = new FilteredExecutionPlane(ctx.parentOpts.executionPlane, permitted);
22
+ let systemPrompt = ctx.parentOpts.systemPrompt;
23
+ let inheritEvents;
24
+ if (ctx.manifest.context_inheritance === "full") {
25
+ inheritEvents = await ctx.sessionLog.read(ctx.parentSessionId);
26
+ }
27
+ else if (ctx.manifest.context_inheritance === "system_only") {
28
+ const parentEvents = await ctx.sessionLog.read(ctx.parentSessionId);
29
+ const started = parentEvents.find(e => e.event.kind === "run_started");
30
+ if (started?.event.kind === "run_started" && started.event.system_prompt) {
31
+ systemPrompt = started.event.system_prompt;
32
+ }
33
+ }
34
+ const { RuntimeRunner } = await import("./runner.js");
35
+ const childRunner = new RuntimeRunner({
36
+ ...ctx.parentOpts,
37
+ executionPlane: filteredPlane,
38
+ agentId: ctx.spec.identity.agentId,
39
+ systemPrompt,
40
+ sessionLog: ctx.sessionLog,
41
+ });
42
+ yield* childRunner.run({
43
+ sessionId: ctx.spec.identity.sessionId,
44
+ goal: ctx.spec.goal,
45
+ inheritEvents,
46
+ });
47
+ }
48
+ async run(ctx) {
49
+ let done;
50
+ let finalText = "";
51
+ for await (const evt of this.stream(ctx)) {
52
+ if (evt.type === "text_delta")
53
+ finalText += evt.delta;
54
+ if (evt.type === "done")
55
+ done = evt;
56
+ }
57
+ const loopResult = {
58
+ termination: terminationFromStatus(done?.status ?? "error"),
59
+ turnsUsed: done?.iterations ?? 0,
60
+ totalTokensUsed: done?.totalTokens ?? 0,
61
+ ...(finalText ? { finalMessage: { role: "assistant", content: finalText, toolCalls: [] } } : {}),
62
+ };
63
+ return { agentId: ctx.spec.identity.agentId, result: loopResult };
64
+ }
65
+ }
66
+ export const defaultSubAgentOrchestrator = new SubAgentOrchestrator();
67
+ /** Kernel spawn without an active parent run loop (harness / coordinator use). */
68
+ export async function spawnStandalone(parentOpts, parentSessionId, spec, orchestrator = defaultSubAgentOrchestrator) {
69
+ const kernel = (await import("../kernel.js")).getKernel();
70
+ const runtime = new kernel.KernelRuntime({
71
+ maxTokens: parentOpts.maxTokens,
72
+ maxTurns: parentOpts.maxTurns ?? 25,
73
+ timeoutMs: parentOpts.timeoutMs !== undefined ? BigInt(parentOpts.timeoutMs) : undefined,
74
+ });
75
+ const pending = [];
76
+ kernelApply(runtime, pending, { kind: "start_run", task: { goal: "coordinator", criteria: [] } });
77
+ const observations = kernelApply(runtime, pending, {
78
+ kind: "spawn_sub_agent",
79
+ spec: agentRunSpecToKernel(spec),
80
+ parent_session_id: parentSessionId,
81
+ });
82
+ const spawned = observations.find(o => o.kind === "agent_spawned");
83
+ if (!spawned || typeof spawned.agent_id !== "string") {
84
+ throw new Error("spawn_sub_agent did not emit agent_spawned");
85
+ }
86
+ const turn = spawned.turn ?? 0;
87
+ await parentOpts.sessionLog.append(parentSessionId, {
88
+ kind: "agent_spawned",
89
+ turn,
90
+ agent_id: spawned.agent_id,
91
+ parent_session_id: spawned.parent_session_id ?? parentSessionId,
92
+ role: spawned.role ?? spec.role,
93
+ isolation: spawned.isolation ?? spec.isolation ?? "shared",
94
+ context_inheritance: spawned.context_inheritance ?? "none",
95
+ permitted_capability_ids: spawned.permitted_capability_ids ?? [],
96
+ });
97
+ const manifest = {
98
+ kind: "agent_spawned",
99
+ turn,
100
+ agent_id: spawned.agent_id,
101
+ parent_session_id: spawned.parent_session_id ?? parentSessionId,
102
+ role: spawned.role ?? spec.role,
103
+ isolation: spawned.isolation ?? spec.isolation ?? "shared",
104
+ context_inheritance: spawned.context_inheritance ?? "none",
105
+ permitted_capability_ids: spawned.permitted_capability_ids ?? [],
106
+ };
107
+ return orchestrator.run({
108
+ parentOpts,
109
+ parentSessionId,
110
+ spec,
111
+ manifest,
112
+ sessionLog: parentOpts.sessionLog,
113
+ });
114
+ }
@@ -0,0 +1,19 @@
1
+ import { type FSWatcher } from "fs";
2
+ export type SkillChangeHandler = (skillDir: string) => void | Promise<void>;
3
+ /**
4
+ * Watch a skill directory for changes and invoke `onChanged` whenever a skill
5
+ * file (`.md`, `.json`, or `.py`) is created, modified, or removed.
6
+ *
7
+ * Uses Node.js native `fs.watch` — no extra dependencies.
8
+ * The returned handle should be closed when the runner terminates.
9
+ *
10
+ * ```ts
11
+ * const watcher = watchSkillDir(opts.skillDir, async (dir) => {
12
+ * const metas = await scanSkillDir(dir)
13
+ * runtime.updateAvailableSkills(metas)
14
+ * })
15
+ * // later:
16
+ * watcher.close()
17
+ * ```
18
+ */
19
+ export declare function watchSkillDir(skillDir: string, onChanged: SkillChangeHandler): FSWatcher;
@@ -0,0 +1,38 @@
1
+ import { watch } from "fs";
2
+ import { resolve, extname } from "path";
3
+ const WATCHED_EXTS = new Set([".md", ".json", ".py"]);
4
+ /**
5
+ * Watch a skill directory for changes and invoke `onChanged` whenever a skill
6
+ * file (`.md`, `.json`, or `.py`) is created, modified, or removed.
7
+ *
8
+ * Uses Node.js native `fs.watch` — no extra dependencies.
9
+ * The returned handle should be closed when the runner terminates.
10
+ *
11
+ * ```ts
12
+ * const watcher = watchSkillDir(opts.skillDir, async (dir) => {
13
+ * const metas = await scanSkillDir(dir)
14
+ * runtime.updateAvailableSkills(metas)
15
+ * })
16
+ * // later:
17
+ * watcher.close()
18
+ * ```
19
+ */
20
+ export function watchSkillDir(skillDir, onChanged) {
21
+ const dir = resolve(skillDir);
22
+ let debounceTimer = null;
23
+ const watcher = watch(dir, { persistent: false }, (_eventType, filename) => {
24
+ if (!filename)
25
+ return;
26
+ if (!WATCHED_EXTS.has(extname(filename)))
27
+ return;
28
+ // Debounce: collapse rapid bursts (e.g. editor save-then-format) into one
29
+ // reload tick with a 200 ms window.
30
+ if (debounceTimer !== null)
31
+ clearTimeout(debounceTimer);
32
+ debounceTimer = setTimeout(() => {
33
+ debounceTimer = null;
34
+ Promise.resolve(onChanged(dir)).catch(() => { });
35
+ }, 200);
36
+ });
37
+ return watcher;
38
+ }
@@ -8,7 +8,10 @@ export declare function streamingTool(name: string, description: string, paramet
8
8
  export declare function isAsyncIterable<T>(value: unknown): value is AsyncIterable<T>;
9
9
  export declare function normalizeToolChunk(chunk: ToolChunk): Exclude<ToolChunk, string>;
10
10
  export declare function toolChunkText(chunk: ToolChunk): string;
11
- export declare function validateToolArguments(schemaJson: string, args: Record<string, unknown>): string | undefined;
11
+ export declare function validateToolArguments(schemaJson: string, args: Record<string, unknown>): {
12
+ error?: string;
13
+ repaired: boolean;
14
+ };
12
15
  export declare function executeTools(calls: {
13
16
  id: string;
14
17
  name: string;
@@ -26,25 +26,87 @@ export function validateToolArguments(schemaJson, args) {
26
26
  schema = JSON.parse(schemaJson);
27
27
  }
28
28
  catch {
29
- return "invalid tool schema";
29
+ return { error: "invalid tool schema", repaired: false };
30
30
  }
31
- return validateValue(schema, args, "$", true);
31
+ const state = { repaired: false };
32
+ const wrapper = { root: args };
33
+ const error = validateValue(schema, wrapper, "root", "$", state);
34
+ return { error, repaired: state.repaired };
32
35
  }
33
- function validateValue(schema, value, path, isRoot = false) {
36
+ function validateValue(schema, parent, key, path, state) {
37
+ let value = parent[key];
34
38
  const expectedType = schema.type;
39
+ // 1. 类型自动规整 (Auto-cast)
40
+ if (typeof expectedType === "string") {
41
+ if (expectedType === "boolean") {
42
+ if (value === "true") {
43
+ parent[key] = true;
44
+ value = true;
45
+ state.repaired = true;
46
+ }
47
+ else if (value === "false") {
48
+ parent[key] = false;
49
+ value = false;
50
+ state.repaired = true;
51
+ }
52
+ }
53
+ else if (expectedType === "number" || expectedType === "integer") {
54
+ if (typeof value === "string") {
55
+ const num = Number(value);
56
+ if (!Number.isNaN(num)) {
57
+ if (expectedType === "integer") {
58
+ if (Number.isInteger(num)) {
59
+ parent[key] = num;
60
+ value = num;
61
+ state.repaired = true;
62
+ }
63
+ }
64
+ else {
65
+ parent[key] = num;
66
+ value = num;
67
+ state.repaired = true;
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
73
+ // 2. 补默认值 (Default Injection)
74
+ if (expectedType === "object") {
75
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
76
+ return `${path} must be object`;
77
+ }
78
+ const properties = schema.properties ?? {};
79
+ for (const [propKey, childSchema] of Object.entries(properties)) {
80
+ if (!(propKey in value)) {
81
+ if ("default" in childSchema) {
82
+ value[propKey] = childSchema.default;
83
+ state.repaired = true;
84
+ }
85
+ }
86
+ }
87
+ }
88
+ // 3. 校验并递归
35
89
  if (typeof expectedType === "string") {
36
90
  if (expectedType === "object") {
37
91
  if (!value || typeof value !== "object" || Array.isArray(value))
38
92
  return `${path} must be object`;
39
93
  const obj = value;
94
+ // 3a. 裁剪多余字段
95
+ const properties = schema.properties ?? {};
96
+ const allowedKeys = new Set(Object.keys(properties));
97
+ for (const objKey of Object.keys(obj)) {
98
+ if (!allowedKeys.has(objKey)) {
99
+ delete obj[objKey];
100
+ state.repaired = true;
101
+ }
102
+ }
40
103
  for (const required of schema.required ?? []) {
41
104
  if (!(required in obj))
42
105
  return `${path}.${required} is required`;
43
106
  }
44
- const properties = schema.properties ?? {};
45
- for (const [key, child] of Object.entries(properties)) {
46
- if (key in obj) {
47
- const err = validateValue(child, obj[key], `${path}.${key}`);
107
+ for (const [propKey, child] of Object.entries(properties)) {
108
+ if (propKey in obj) {
109
+ const err = validateValue(child, obj, propKey, `${path}.${propKey}`, state);
48
110
  if (err)
49
111
  return err;
50
112
  }
@@ -53,6 +115,15 @@ function validateValue(schema, value, path, isRoot = false) {
53
115
  else if (expectedType === "array") {
54
116
  if (!Array.isArray(value))
55
117
  return `${path} must be array`;
118
+ const arr = value;
119
+ const itemsSchema = schema.items;
120
+ if (itemsSchema) {
121
+ for (let i = 0; i < arr.length; i++) {
122
+ const err = validateValue(itemsSchema, arr, i, `${path}[${i}]`, state);
123
+ if (err)
124
+ return err;
125
+ }
126
+ }
56
127
  }
57
128
  else if (expectedType === "string") {
58
129
  if (typeof value !== "string")
@@ -71,7 +142,7 @@ function validateValue(schema, value, path, isRoot = false) {
71
142
  return `${path} must be boolean`;
72
143
  }
73
144
  }
74
- else if (isRoot && (!value || typeof value !== "object" || Array.isArray(value))) {
145
+ else if (path === "$" && (!value || typeof value !== "object" || Array.isArray(value))) {
75
146
  return `${path} must be object`;
76
147
  }
77
148
  if (Array.isArray(schema.enum) && !schema.enum.includes(value))
@@ -85,9 +156,9 @@ export async function executeTools(calls, registry) {
85
156
  return { callId: c.id, output: `unknown tool: ${c.name}`, isError: true };
86
157
  try {
87
158
  const args = JSON.parse(c.arguments || "{}");
88
- const validationError = validateToolArguments(t.schema.parameters, args);
89
- if (validationError)
90
- return { callId: c.id, output: `invalid arguments: ${validationError}`, isError: true };
159
+ const validation = validateToolArguments(t.schema.parameters, args);
160
+ if (validation.error)
161
+ return { callId: c.id, output: `invalid arguments: ${validation.error}`, isError: true };
91
162
  const output = await t.execute(args);
92
163
  if (isAsyncIterable(output)) {
93
164
  let combined = "";
@@ -0,0 +1,68 @@
1
+ import type { Message } from "../types.js";
2
+ export type KernelAgentRole = "explore" | "plan" | "implement" | "verify" | "custom";
3
+ export type AgentIsolation = "shared" | "read_only" | "worktree" | "remote";
4
+ export type ContextInheritance = "none" | "system_only" | "full";
5
+ export type TerminationReason = "completed" | "max_turns" | "token_budget" | "timeout" | "user_abort" | "error" | "milestone_exceeded";
6
+ export type MilestonePolicy = "require_verifier" | "terminate" | "auto_pass";
7
+ export interface AgentIdentity {
8
+ agentId: string;
9
+ sessionId: string;
10
+ isSubAgent: boolean;
11
+ parentSessionId?: string;
12
+ }
13
+ export interface AgentCapabilityFilter {
14
+ allowedKinds?: string[];
15
+ allowedIds?: string[];
16
+ }
17
+ export interface AgentRunSpec {
18
+ identity: AgentIdentity;
19
+ role: KernelAgentRole;
20
+ isolation?: AgentIsolation;
21
+ goal: string;
22
+ verificationContractId?: string;
23
+ capabilityFilter?: AgentCapabilityFilter;
24
+ milestones?: MilestoneContract;
25
+ metadata?: Record<string, unknown>;
26
+ }
27
+ export interface AgentSpawnedObservation {
28
+ kind: "agent_spawned";
29
+ turn?: number;
30
+ agent_id: string;
31
+ parent_session_id: string;
32
+ role: string;
33
+ isolation: string;
34
+ context_inheritance: string;
35
+ permitted_capability_ids: string[];
36
+ }
37
+ export interface LoopResult {
38
+ termination: TerminationReason | string;
39
+ finalMessage?: Message;
40
+ turnsUsed: number;
41
+ totalTokensUsed: number;
42
+ }
43
+ export interface SubAgentResult {
44
+ agentId: string;
45
+ result: LoopResult;
46
+ }
47
+ export interface MilestoneCheckResult {
48
+ phaseId: string;
49
+ passed: boolean;
50
+ reason?: string;
51
+ }
52
+ export interface MilestonePhase {
53
+ id: string;
54
+ criteria?: string[];
55
+ unlocks?: Array<Record<string, unknown>>;
56
+ verifier?: Record<string, unknown>;
57
+ requiredEvidence?: string[];
58
+ }
59
+ export interface MilestoneContract {
60
+ phases: MilestonePhase[];
61
+ }
62
+ export declare function agentIdentitySub(agentId: string, sessionId: string, parentSessionId?: string): AgentIdentity;
63
+ export declare function agentRunSpecToKernel(spec: AgentRunSpec): Record<string, unknown>;
64
+ export declare function milestoneContractToKernel(contract: MilestoneContract): Record<string, unknown>;
65
+ export declare function milestoneCheckResultToKernel(result: MilestoneCheckResult): Record<string, unknown>;
66
+ export declare function subAgentResultToKernel(result: SubAgentResult): Record<string, unknown>;
67
+ export declare function milestoneCheckPass(phaseId: string): MilestoneCheckResult;
68
+ export declare function milestoneCheckFail(phaseId: string, reason: string): MilestoneCheckResult;