klaus-agent 0.3.1 → 0.4.1

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 (90) hide show
  1. package/README.md +36 -2
  2. package/README.zh-CN.md +35 -1
  3. package/dist/approval/approval.d.ts +8 -3
  4. package/dist/approval/approval.js +23 -23
  5. package/dist/approval/approval.js.map +1 -1
  6. package/dist/approval/types.d.ts +1 -0
  7. package/dist/background/task-manager.d.ts +2 -0
  8. package/dist/background/task-manager.js +14 -0
  9. package/dist/background/task-manager.js.map +1 -1
  10. package/dist/compaction/compaction.d.ts +0 -1
  11. package/dist/compaction/compaction.js +21 -35
  12. package/dist/compaction/compaction.js.map +1 -1
  13. package/dist/compaction/summarizer.js +2 -7
  14. package/dist/compaction/summarizer.js.map +1 -1
  15. package/dist/core/agent-loop.d.ts +2 -1
  16. package/dist/core/agent-loop.js +14 -9
  17. package/dist/core/agent-loop.js.map +1 -1
  18. package/dist/core/agent.d.ts +7 -0
  19. package/dist/core/agent.js +35 -10
  20. package/dist/core/agent.js.map +1 -1
  21. package/dist/index.d.ts +7 -0
  22. package/dist/index.js +5 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/injection/history-normalizer.js +20 -10
  25. package/dist/injection/history-normalizer.js.map +1 -1
  26. package/dist/llm/types.d.ts +2 -0
  27. package/dist/multi-agent/task-executor.d.ts +2 -1
  28. package/dist/multi-agent/types.d.ts +3 -0
  29. package/dist/planning/planning-manager.d.ts +2 -0
  30. package/dist/planning/planning-manager.js +6 -0
  31. package/dist/planning/planning-manager.js.map +1 -1
  32. package/dist/providers/anthropic.js +14 -6
  33. package/dist/providers/anthropic.js.map +1 -1
  34. package/dist/providers/google.js.map +1 -1
  35. package/dist/providers/openai-codex.js +5 -8
  36. package/dist/providers/openai-codex.js.map +1 -1
  37. package/dist/providers/openai.js +3 -2
  38. package/dist/providers/openai.js.map +1 -1
  39. package/dist/providers/shared.d.ts +2 -2
  40. package/dist/providers/shared.js +11 -6
  41. package/dist/providers/shared.js.map +1 -1
  42. package/dist/session/session-manager.d.ts +1 -0
  43. package/dist/session/session-manager.js +11 -1
  44. package/dist/session/session-manager.js.map +1 -1
  45. package/dist/task-graph/result-injection.d.ts +8 -0
  46. package/dist/task-graph/result-injection.js +26 -0
  47. package/dist/task-graph/result-injection.js.map +1 -0
  48. package/dist/task-graph/task-graph.d.ts +41 -0
  49. package/dist/task-graph/task-graph.js +266 -0
  50. package/dist/task-graph/task-graph.js.map +1 -0
  51. package/dist/task-graph/tools.d.ts +3 -0
  52. package/dist/task-graph/tools.js +106 -0
  53. package/dist/task-graph/tools.js.map +1 -0
  54. package/dist/task-graph/types.d.ts +44 -0
  55. package/dist/task-graph/types.js +9 -0
  56. package/dist/task-graph/types.js.map +1 -0
  57. package/dist/tools/executor.d.ts +3 -2
  58. package/dist/tools/mcp-adapter.js +22 -5
  59. package/dist/tools/mcp-adapter.js.map +1 -1
  60. package/dist/utils/id.js +2 -6
  61. package/dist/utils/id.js.map +1 -1
  62. package/dist/wire/wire.d.ts +2 -1
  63. package/package.json +1 -1
  64. package/src/approval/approval.ts +29 -23
  65. package/src/approval/types.ts +1 -0
  66. package/src/background/task-manager.ts +17 -0
  67. package/src/compaction/compaction.ts +23 -36
  68. package/src/compaction/summarizer.ts +2 -7
  69. package/src/core/agent-loop.ts +17 -10
  70. package/src/core/agent.ts +41 -9
  71. package/src/index.ts +15 -0
  72. package/src/injection/history-normalizer.ts +22 -12
  73. package/src/llm/types.ts +2 -0
  74. package/src/multi-agent/task-executor.ts +1 -1
  75. package/src/multi-agent/types.ts +3 -0
  76. package/src/planning/planning-manager.ts +8 -0
  77. package/src/providers/anthropic.ts +70 -57
  78. package/src/providers/google.ts +1 -1
  79. package/src/providers/openai-codex.ts +7 -2
  80. package/src/providers/openai.ts +8 -3
  81. package/src/providers/shared.ts +11 -6
  82. package/src/session/session-manager.ts +15 -4
  83. package/src/task-graph/result-injection.ts +29 -0
  84. package/src/task-graph/task-graph.ts +298 -0
  85. package/src/task-graph/tools.ts +109 -0
  86. package/src/task-graph/types.ts +52 -0
  87. package/src/tools/executor.ts +2 -2
  88. package/src/tools/mcp-adapter.ts +23 -7
  89. package/src/utils/id.ts +3 -6
  90. package/src/wire/wire.ts +1 -1
@@ -0,0 +1,106 @@
1
+ // Task graph tools — CRUD + dependency management + background execution
2
+ import { Type } from "@sinclair/typebox";
3
+ import { TASK_GRAPH_TOOL_NAMES } from "./types.js";
4
+ export function createTaskGraphTools(graph) {
5
+ return [
6
+ {
7
+ name: TASK_GRAPH_TOOL_NAMES.create,
8
+ label: "Create Task",
9
+ description: "Create a new task in the task graph. Tasks start as pending. " +
10
+ "Use task_depend to set up dependency ordering.",
11
+ parameters: Type.Object({
12
+ subject: Type.String({ description: "Short title for the task." }),
13
+ description: Type.Optional(Type.String({ description: "Detailed description." })),
14
+ }),
15
+ async execute(_id, params) {
16
+ const task = graph.create(params.subject, params.description);
17
+ return text(`Created task ${task.id}: ${task.subject}\n\n${graph.render()}`);
18
+ },
19
+ },
20
+ {
21
+ name: TASK_GRAPH_TOOL_NAMES.depend,
22
+ label: "Add Task Dependency",
23
+ description: "Add a dependency: task_id cannot start until blocked_by_id completes. " +
24
+ "Rejects if this would create a cycle.",
25
+ parameters: Type.Object({
26
+ task_id: Type.String({ description: "Task that is blocked." }),
27
+ blocked_by_id: Type.String({ description: "Task that must complete first." }),
28
+ }),
29
+ async execute(_id, params) {
30
+ graph.addDependency(params.task_id, params.blocked_by_id);
31
+ return text(`Dependency added: ${params.task_id} blocked by ${params.blocked_by_id}\n\n${graph.render()}`);
32
+ },
33
+ },
34
+ {
35
+ name: TASK_GRAPH_TOOL_NAMES.update,
36
+ label: "Update Task",
37
+ description: "Update a task's status, owner, or result. " +
38
+ "Setting status to 'completed' auto-unblocks dependent tasks. " +
39
+ "Cannot start a task that has unfinished blockers.",
40
+ parameters: Type.Object({
41
+ task_id: Type.String({ description: "Task ID." }),
42
+ status: Type.Optional(Type.Union([Type.Literal("pending"), Type.Literal("in_progress"), Type.Literal("completed"), Type.Literal("failed")], { description: "New status." })),
43
+ owner: Type.Optional(Type.String({ description: "Assign to an agent or user." })),
44
+ result: Type.Optional(Type.String({ description: "Result summary." })),
45
+ }),
46
+ async execute(_id, params) {
47
+ const task = graph.update(params.task_id, {
48
+ status: params.status,
49
+ owner: params.owner,
50
+ result: params.result,
51
+ });
52
+ return text(`Updated task ${task.id}\n\n${graph.render()}`);
53
+ },
54
+ },
55
+ {
56
+ name: TASK_GRAPH_TOOL_NAMES.list,
57
+ label: "List Tasks",
58
+ description: "List all tasks with their status, dependencies, and progress.",
59
+ parameters: Type.Object({
60
+ filter: Type.Optional(Type.Union([Type.Literal("all"), Type.Literal("ready"), Type.Literal("blocked"), Type.Literal("in_progress"), Type.Literal("completed")], { description: "Filter tasks by category. Default: all." })),
61
+ }),
62
+ async execute(_id, params) {
63
+ let tasks;
64
+ switch (params.filter) {
65
+ case "ready":
66
+ tasks = graph.listReady();
67
+ break;
68
+ case "blocked":
69
+ tasks = graph.listBlocked();
70
+ break;
71
+ case "in_progress":
72
+ tasks = graph.listAll().filter((t) => t.status === "in_progress");
73
+ break;
74
+ case "completed":
75
+ tasks = graph.listAll().filter((t) => t.status === "completed");
76
+ break;
77
+ default: tasks = graph.listAll();
78
+ }
79
+ if (tasks.length === 0)
80
+ return text(`No tasks matching filter: ${params.filter ?? "all"}`);
81
+ if (!params.filter || params.filter === "all")
82
+ return text(graph.render());
83
+ const lines = tasks.map((t) => `${t.id}: ${t.subject} [${t.status}]`);
84
+ return text(`${params.filter}: ${tasks.length} task(s)\n${lines.join("\n")}`);
85
+ },
86
+ },
87
+ {
88
+ name: TASK_GRAPH_TOOL_NAMES.get,
89
+ label: "Get Task",
90
+ description: "Get detailed information about a specific task.",
91
+ parameters: Type.Object({
92
+ task_id: Type.String({ description: "Task ID." }),
93
+ }),
94
+ async execute(_id, params) {
95
+ const task = graph.get(params.task_id);
96
+ if (!task)
97
+ return text(`Task not found: ${params.task_id}`);
98
+ return text(JSON.stringify(task, null, 2));
99
+ },
100
+ },
101
+ ];
102
+ }
103
+ function text(t) {
104
+ return { content: [{ type: "text", text: t }] };
105
+ }
106
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/task-graph/tools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ export declare const TASK_GRAPH_TOOL_NAMES: {
2
+ readonly create: "task_create";
3
+ readonly depend: "task_depend";
4
+ readonly update: "task_update";
5
+ readonly list: "task_list";
6
+ readonly get: "task_get";
7
+ };
8
+ export type TaskStatus = "pending" | "in_progress" | "completed" | "failed";
9
+ export interface TaskNode {
10
+ id: string;
11
+ subject: string;
12
+ description: string;
13
+ status: TaskStatus;
14
+ /** IDs of tasks that must complete before this one can start. */
15
+ blockedBy: string[];
16
+ /** IDs of tasks that this task blocks (reverse edges, maintained automatically). */
17
+ blocks: string[];
18
+ /** Agent or user assigned to this task. */
19
+ owner: string;
20
+ /** Result summary after completion/failure. */
21
+ result?: string;
22
+ /** Background execution handle ID, if running in background. */
23
+ backgroundId?: string;
24
+ createdAt: number;
25
+ updatedAt: number;
26
+ }
27
+ export interface TaskGraphConfig {
28
+ /** Directory for persisting task graph to disk. If omitted, in-memory only. */
29
+ persistDir?: string;
30
+ /** Maximum number of tasks. Default: 100. */
31
+ maxTasks?: number;
32
+ /**
33
+ * Auto-inject completed background task results before each LLM call.
34
+ * Default: true.
35
+ */
36
+ autoInjectResults?: boolean;
37
+ }
38
+ export interface CompletedTaskResult {
39
+ taskId: string;
40
+ subject: string;
41
+ result: string;
42
+ status: "completed" | "failed";
43
+ unblockedTasks: string[];
44
+ }
@@ -0,0 +1,9 @@
1
+ // Task graph types — dependency-aware task DAG with background execution
2
+ export const TASK_GRAPH_TOOL_NAMES = {
3
+ create: "task_create",
4
+ depend: "task_depend",
5
+ update: "task_update",
6
+ list: "task_list",
7
+ get: "task_get",
8
+ };
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/task-graph/types.ts"],"names":[],"mappings":"AAAA,yEAAyE;AAEzE,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,MAAM,EAAE,aAAa;IACrB,MAAM,EAAE,aAAa;IACrB,MAAM,EAAE,aAAa;IACrB,IAAI,EAAE,WAAW;IACjB,GAAG,EAAE,UAAU;CACP,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import type { AgentTool, AgentToolResult, BeforeToolCallContext, BeforeToolCallResult, AfterToolCallContext, AfterToolCallResult } from "./types.js";
2
2
  import type { ToolCallBlock } from "../llm/types.js";
3
3
  import type { Approval } from "../approval/types.js";
4
- export interface ToolExecutorConfig {
4
+ interface ToolExecutorConfig {
5
5
  tools: AgentTool[];
6
6
  mode: "sequential" | "parallel";
7
7
  approval: Approval;
@@ -11,7 +11,7 @@ export interface ToolExecutorConfig {
11
11
  afterToolCall?: (ctx: AfterToolCallContext) => Promise<AfterToolCallResult | void>;
12
12
  onEvent: (event: ToolExecutorEvent) => void;
13
13
  }
14
- export type ToolExecutorEvent = {
14
+ type ToolExecutorEvent = {
15
15
  type: "tool_execution_start";
16
16
  toolCallId: string;
17
17
  toolName: string;
@@ -35,3 +35,4 @@ export interface ToolCallResult {
35
35
  isError: boolean;
36
36
  }
37
37
  export declare function executeToolCalls(toolCalls: ToolCallBlock[], config: ToolExecutorConfig): Promise<ToolCallResult[]>;
38
+ export {};
@@ -88,15 +88,32 @@ export class MCPAdapter {
88
88
  async execute(toolCallId, params, context) {
89
89
  // Approval is handled by the executor via approvalAction field
90
90
  // No need to request approval here
91
- // Call with timeout
91
+ // Call with timeout and abort signal support
92
92
  const callPromise = client.callTool(def.name, params);
93
93
  let result;
94
+ let timer;
95
+ let abortHandler;
96
+ const racers = [callPromise];
94
97
  if (timeout) {
95
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`MCP tool ${def.name} timed out after ${timeout}ms`)), timeout));
96
- result = await Promise.race([callPromise, timeoutPromise]);
98
+ racers.push(new Promise((_, reject) => {
99
+ timer = setTimeout(() => reject(new Error(`MCP tool ${def.name} timed out after ${timeout}ms`)), timeout);
100
+ }));
97
101
  }
98
- else {
99
- result = await callPromise;
102
+ if (context.signal.aborted)
103
+ throw new Error("Aborted");
104
+ racers.push(new Promise((_, reject) => {
105
+ abortHandler = () => reject(new Error("Aborted"));
106
+ context.signal.addEventListener("abort", abortHandler, { once: true });
107
+ }));
108
+ try {
109
+ result = await Promise.race(racers);
110
+ }
111
+ finally {
112
+ if (timer !== undefined)
113
+ clearTimeout(timer);
114
+ if (abortHandler) {
115
+ context.signal.removeEventListener("abort", abortHandler);
116
+ }
100
117
  }
101
118
  // Convert MCP result to AgentToolResult
102
119
  const content = result.content
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-adapter.js","sourceRoot":"","sources":["../../src/tools/mcp-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp-adapter.js","sourceRoot":"","sources":["../../src/tools/mcp-adapter.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAIhE,OAAO,EAAE,IAAI,EAAgB,MAAM,mBAAmB,CAAC;AA2CvD,sBAAsB;AAEtB,MAAM,OAAO,UAAU;IAMX;IACA;IANF,QAAQ,GAAG,IAAI,GAAG,EAA2B,CAAC;IAC9C,QAAQ,GAAG,IAAI,GAAG,EAAqB,CAAC;IACxC,eAAe,GAAyB,IAAI,CAAC;IAErD,YACU,QAA2B,EAC3B,cAAsD;QADtD,aAAQ,GAAR,QAAQ,CAAmB;QAC3B,mBAAc,GAAd,cAAc,CAAwC;QAE9D,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;YAC9B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE;gBAC7B,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,MAAM,EAAE,SAAS;gBACjB,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,8CAA8C;IAC9C,YAAY;QACV,IAAI,IAAI,CAAC,eAAe;YAAE,OAAO;QACjC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;IACzC,CAAC;IAED,yCAAyC;IACzC,KAAK,CAAC,YAAY;QAChB,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,MAAM,IAAI,CAAC,eAAe,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,uCAAuC;IACvC,WAAW;QACT,MAAM,KAAK,GAAgB,EAAE,CAAC;QAC9B,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAC5C,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,sBAAsB;IACtB,WAAW;QACT,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,yBAAyB;IACzB,KAAK,CAAC,OAAO;QACX,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAC5C,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;YACvB,CAAC;YAAC,MAAM,CAAC;gBACP,sBAAsB;YACxB,CAAC;QACH,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,mBAAmB;IAEX,KAAK,CAAC,QAAQ;QACpB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;QACzE,MAAM,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;IAEO,KAAK,CAAC,WAAW,CAAC,MAAuB;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAE,CAAC;QAC/C,MAAM,CAAC,MAAM,GAAG,YAAY,CAAC;QAE7B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;YAC3C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAEvC,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;YAEvB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC;YAC1C,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;YAEjG,MAAM,CAAC,MAAM,GAAG,WAAW,CAAC;YAC5B,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QACvB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAC;YACzB,MAAM,CAAC,KAAK,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;IAEO,YAAY,CAClB,UAAkB,EAClB,GAAsB,EACtB,MAAiB,EACjB,OAAgB;QAEhB,MAAM,QAAQ,GAAG,GAAG,UAAU,KAAK,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9C,MAAM,MAAM,GAAI,GAAG,CAAC,WAAuB,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAE/D,OAAO;YACL,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,GAAG,CAAC,IAAI;YACf,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,aAAa,GAAG,CAAC,IAAI,EAAE;YACvD,UAAU,EAAE,MAAM;YAClB,cAAc,EAAE,OAAO,UAAU,IAAI,GAAG,CAAC,IAAI,EAAE;YAE/C,KAAK,CAAC,OAAO,CACX,UAAkB,EAClB,MAA+B,EAC/B,OAA6B;gBAE7B,+DAA+D;gBAC/D,mCAAmC;gBAEnC,6CAA6C;gBAC7C,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;gBACtD,IAAI,MAAqB,CAAC;gBAE1B,IAAI,KAAgD,CAAC;gBACrD,IAAI,YAAsC,CAAC;gBAC3C,MAAM,MAAM,GAA6B,CAAC,WAAW,CAAC,CAAC;gBAEvD,IAAI,OAAO,EAAE,CAAC;oBACZ,MAAM,CAAC,IAAI,CAAC,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;wBAC3C,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,YAAY,GAAG,CAAC,IAAI,oBAAoB,OAAO,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;oBAC5G,CAAC,CAAC,CAAC,CAAC;gBACN,CAAC;gBAED,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO;oBAAE,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC;gBACvD,MAAM,CAAC,IAAI,CAAC,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;oBAC3C,YAAY,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;oBAClD,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;gBACzE,CAAC,CAAC,CAAC,CAAC;gBAEJ,IAAI,CAAC;oBACH,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBACtC,CAAC;wBAAS,CAAC;oBACT,IAAI,KAAK,KAAK,SAAS;wBAAE,YAAY,CAAC,KAAK,CAAC,CAAC;oBAC7C,IAAI,YAAY,EAAE,CAAC;wBACjB,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;oBAC5D,CAAC;gBACH,CAAC;gBAED,wCAAwC;gBACxC,MAAM,OAAO,GAAkB,MAAM,CAAC,OAAO;qBAC1C,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC;qBAC1C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC,CAAC,IAAK,EAAE,CAAC,CAAC,CAAC;gBAE1D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC;gBACzD,CAAC;gBAED,OAAO,EAAE,OAAO,EAAE,CAAC;YACrB,CAAC;SACF,CAAC;IACJ,CAAC;CACF"}
package/dist/utils/id.js CHANGED
@@ -1,9 +1,5 @@
1
+ import { randomBytes } from "node:crypto";
1
2
  export function generateId() {
2
- const chars = "0123456789abcdefghijklmnopqrstuvwxyz";
3
- let id = "";
4
- for (let i = 0; i < 12; i++) {
5
- id += chars[Math.floor(Math.random() * chars.length)];
6
- }
7
- return id;
3
+ return randomBytes(9).toString("base64url").slice(0, 12);
8
4
  }
9
5
  //# sourceMappingURL=id.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/utils/id.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,UAAU;IACxB,MAAM,KAAK,GAAG,sCAAsC,CAAC;IACrD,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC"}
1
+ {"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/utils/id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,UAAU,UAAU;IACxB,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC3D,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import type { WireMessage, WireSubscriber, WireSubscription } from "./types.js";
2
- export interface WireOptions {
2
+ interface WireOptions {
3
3
  /** Max messages to buffer for replay to late subscribers. 0 = no buffering. */
4
4
  bufferSize?: number;
5
5
  }
@@ -18,3 +18,4 @@ export declare class Wire {
18
18
  getBuffer(): readonly WireMessage[];
19
19
  dispose(): void;
20
20
  }
21
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klaus-agent",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "Universal agent framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -8,21 +8,27 @@ interface PendingRequest {
8
8
  resolve: (approved: boolean) => void;
9
9
  }
10
10
 
11
+ interface SharedApprovalState {
12
+ yolo: boolean;
13
+ autoApproveActions: Set<string>;
14
+ }
15
+
11
16
  export class ApprovalImpl implements Approval {
12
- private _yolo: boolean;
13
- private _autoApproveActions: Set<string>;
17
+ private _shared: SharedApprovalState;
14
18
  private _pending = new Map<string, PendingRequest>();
15
19
  private _queue: ApprovalRequest[] = [];
16
20
  private _waiters: ((req: ApprovalRequest) => void)[] = [];
17
21
 
18
- constructor(config?: ApprovalConfig) {
19
- this._yolo = config?.yolo ?? false;
20
- this._autoApproveActions = new Set(config?.autoApproveActions ?? []);
22
+ constructor(config?: ApprovalConfig, sharedState?: SharedApprovalState) {
23
+ this._shared = sharedState ?? {
24
+ yolo: config?.yolo ?? false,
25
+ autoApproveActions: new Set(config?.autoApproveActions ?? []),
26
+ };
21
27
  }
22
28
 
23
29
  async request(sender: string, action: string, description: string, toolCallId: string): Promise<boolean> {
24
- if (this._yolo) return true;
25
- if (this._autoApproveActions.has(action)) return true;
30
+ if (this._shared.yolo) return true;
31
+ if (this._shared.autoApproveActions.has(action)) return true;
26
32
 
27
33
  const req: ApprovalRequest = {
28
34
  id: generateId(),
@@ -72,7 +78,7 @@ export class ApprovalImpl implements Approval {
72
78
  this._pending.delete(requestId);
73
79
 
74
80
  if (response === "approve_for_session") {
75
- this._autoApproveActions.add(pending.request.action);
81
+ this._shared.autoApproveActions.add(pending.request.action);
76
82
  pending.resolve(true);
77
83
  } else {
78
84
  pending.resolve(response === "approve");
@@ -80,29 +86,29 @@ export class ApprovalImpl implements Approval {
80
86
  }
81
87
 
82
88
  setYolo(yolo: boolean): void {
83
- this._yolo = yolo;
89
+ this._shared.yolo = yolo;
84
90
  }
85
91
 
86
92
  isYolo(): boolean {
87
- return this._yolo;
93
+ return this._shared.yolo;
88
94
  }
89
95
 
90
96
  get autoApproveActions(): Set<string> {
91
- return this._autoApproveActions;
97
+ return this._shared.autoApproveActions;
92
98
  }
93
99
 
94
100
  share(): Approval {
95
- // Shared state (yolo, autoApproveActions), independent queue
96
- // Use a shared state object so changes propagate bidirectionally
97
- const shared = new ApprovalImpl();
98
- shared._autoApproveActions = this._autoApproveActions; // same reference
99
- // Share yolo via getter/setter delegation
100
- const parent = this;
101
- Object.defineProperty(shared, '_yolo', {
102
- get() { return parent._yolo; },
103
- set(v: boolean) { parent._yolo = v; },
104
- configurable: true,
105
- });
106
- return shared;
101
+ // Shared state (yolo, autoApproveActions) via same reference, independent queue
102
+ return new ApprovalImpl(undefined, this._shared);
103
+ }
104
+
105
+ dispose(): void {
106
+ this.cancelPendingWaiters();
107
+ // Reject all pending approval requests so blocked tools don't hang forever
108
+ for (const [id, pending] of this._pending) {
109
+ pending.resolve(false);
110
+ }
111
+ this._pending.clear();
112
+ this._queue = [];
107
113
  }
108
114
  }
@@ -23,4 +23,5 @@ export interface Approval {
23
23
  isYolo(): boolean;
24
24
  readonly autoApproveActions: Set<string>;
25
25
  share(): Approval;
26
+ dispose(): void;
26
27
  }
@@ -82,6 +82,23 @@ export class BackgroundTaskManager {
82
82
  }
83
83
  }
84
84
 
85
+ /** Remove completed/failed tasks older than `maxAgeMs` (default: 5 minutes). */
86
+ prune(maxAgeMs = 5 * 60 * 1000): number {
87
+ const now = Date.now();
88
+ let removed = 0;
89
+ for (const [id, task] of this._tasks) {
90
+ if (
91
+ (task.status === "completed" || task.status === "failed") &&
92
+ task.completedAt &&
93
+ now - task.completedAt > maxAgeMs
94
+ ) {
95
+ this._tasks.delete(id);
96
+ removed++;
97
+ }
98
+ }
99
+ return removed;
100
+ }
101
+
85
102
  dispose(): void {
86
103
  this.abortAll();
87
104
  this._tasks.clear();
@@ -75,6 +75,14 @@ export function findCutPoint(
75
75
  }
76
76
  }
77
77
 
78
+ // If all messages fit within keepRecentTokens, nothing can be discarded.
79
+ // Return firstKeptIndex: 0 so callers know there's nothing to cut —
80
+ // this prevents infinite compaction loops when context exceeds max but
81
+ // all messages fall within the keep window.
82
+ if (cutIndex >= messages.length) {
83
+ return { firstKeptIndex: 0, isSplitTurn: false };
84
+ }
85
+
78
86
  // Ensure we don't cut at a tool_result (must follow its tool_call)
79
87
  while (cutIndex < messages.length) {
80
88
  const msg = messages[cutIndex];
@@ -85,8 +93,8 @@ export function findCutPoint(
85
93
  }
86
94
  }
87
95
 
88
- // Don't cut if nothing to discard
89
- if (cutIndex <= 1) {
96
+ // After adjusting for tool_results, check if we've pushed past all messages
97
+ if (cutIndex >= messages.length || cutIndex <= 1) {
90
98
  return { firstKeptIndex: 0, isSplitTurn: false };
91
99
  }
92
100
 
@@ -108,25 +116,24 @@ export function microCompact(messages: Message[], keepRecent: number): Message[]
108
116
 
109
117
  if (toolResultIndices.length <= keepRecent) return messages;
110
118
 
119
+ // Build toolCallId → toolName map in one pass
120
+ const toolCallNames = new Map<string, string>();
121
+ for (const msg of messages) {
122
+ if (msg.role === "assistant") {
123
+ for (const b of msg.content) {
124
+ if (b.type === "tool_call") {
125
+ toolCallNames.set(b.id, b.name);
126
+ }
127
+ }
128
+ }
129
+ }
130
+
111
131
  const toReplace = toolResultIndices.slice(0, -keepRecent);
112
132
  const result = [...messages];
113
133
 
114
134
  for (const idx of toReplace) {
115
135
  const msg = result[idx] as ToolResultMessage;
116
-
117
- let toolName = "tool";
118
- for (let j = idx - 1; j >= 0; j--) {
119
- const prev = result[j];
120
- if (prev.role === "assistant") {
121
- const call = prev.content.find(
122
- (b): b is ToolCallBlock => b.type === "tool_call" && b.id === msg.toolCallId,
123
- );
124
- if (call) {
125
- toolName = call.name;
126
- break;
127
- }
128
- }
129
- }
136
+ const toolName = toolCallNames.get(msg.toolCallId) ?? "tool";
130
137
 
131
138
  result[idx] = {
132
139
  role: "tool_result",
@@ -139,23 +146,3 @@ export function microCompact(messages: Message[], keepRecent: number): Message[]
139
146
  return result;
140
147
  }
141
148
 
142
- export function messagesToText(messages: AgentMessage[]): string {
143
- const parts: string[] = [];
144
- for (const msg of messages) {
145
- if (!msg || typeof msg !== "object" || !("role" in msg)) continue;
146
- const m = msg as Message;
147
-
148
- if (m.role === "user") {
149
- const text = typeof m.content === "string" ? m.content : m.content.filter((b) => b.type === "text").map((b) => (b as { text: string }).text).join("\n");
150
- parts.push(`User: ${text}`);
151
- } else if (m.role === "assistant") {
152
- const text = m.content.filter((b) => b.type === "text").map((b) => (b as { text: string }).text).join("\n");
153
- const toolCalls = m.content.filter((b) => b.type === "tool_call").map((b) => (b as { name: string }).name);
154
- parts.push(`Assistant: ${text}${toolCalls.length ? ` [tools: ${toolCalls.join(", ")}]` : ""}`);
155
- } else if (m.role === "tool_result") {
156
- const text = typeof m.content === "string" ? m.content : m.content.filter((b) => b.type === "text").map((b) => (b as { text: string }).text).join("\n");
157
- parts.push(`Tool result: ${text.slice(0, 500)}`);
158
- }
159
- }
160
- return parts.join("\n");
161
- }
@@ -3,7 +3,6 @@
3
3
  import type { LLMProvider, LLMRequestOptions, AssistantMessage } from "../llm/types.js";
4
4
  import type { AgentMessage } from "../types.js";
5
5
  import type { CompactionSummarizer, CompactionInput } from "./types.js";
6
- import { messagesToText } from "./compaction.js";
7
6
 
8
7
  const SUMMARIZE_PROMPT = `You are a conversation summarizer. Summarize the following conversation history concisely, preserving:
9
8
  - Key decisions and outcomes
@@ -42,13 +41,9 @@ export class LLMSummarizer implements CompactionSummarizer {
42
41
 
43
42
  let result = "";
44
43
  for await (const event of this.provider.stream(options)) {
45
- if (event.type === "text") {
46
- result += event.text;
47
- } else if (event.type === "done") {
44
+ if (event.type === "done") {
48
45
  const textBlocks = event.message.content.filter((b) => b.type === "text");
49
- if (textBlocks.length > 0) {
50
- result = textBlocks.map((b) => (b as { text: string }).text).join("");
51
- }
46
+ result = textBlocks.map((b) => (b as { text: string }).text).join("");
52
47
  }
53
48
  }
54
49
 
@@ -29,7 +29,7 @@ import { estimateTokens, shouldCompact, findCutPoint, microCompact } from "../co
29
29
  import { normalizeHistory } from "../injection/history-normalizer.js";
30
30
  import { calculateCost } from "../providers/shared.js";
31
31
 
32
- export interface AgentLoopConfig {
32
+ interface AgentLoopConfig {
33
33
  provider: LLMProvider;
34
34
  modelId: string;
35
35
  systemPrompt: string;
@@ -91,7 +91,7 @@ function toolResultsToMessages(results: ToolCallResult[]): ToolResultMessage[] {
91
91
  role: "tool_result" as const,
92
92
  toolCallId: r.toolCallId,
93
93
  content: r.result.content,
94
- isError: r.isError || undefined,
94
+ isError: r.isError,
95
95
  }));
96
96
  }
97
97
 
@@ -185,14 +185,10 @@ export async function runAgentLoop(
185
185
  }
186
186
  allMessages.push(...toKeep);
187
187
 
188
- // Persist compaction to session
189
- // Append compaction entry as boundary marker, then re-append
190
- // kept messages after it so buildSessionContext picks them up
188
+ // Persist compaction boundary marker to session.
189
+ // Kept messages are already in the session tree — only append the compaction entry.
191
190
  if (sessionManager) {
192
191
  await sessionManager.appendCompaction(summary, sessionManager.getLeafId() ?? "", tokens);
193
- for (const msg of toKeep) {
194
- await sessionManager.appendMessage(msg);
195
- }
196
192
  }
197
193
 
198
194
  onEvent({ type: "compaction_end", summary });
@@ -257,7 +253,7 @@ export async function runAgentLoop(
257
253
 
258
254
  // --- Phase-aware tool filtering ---
259
255
  let visibleTools = allTools;
260
- if (config.planningManager?.phase === "planning" && config.planningManager.allowedInPlanning.size > 2) {
256
+ if (config.planningManager?.phase === "planning" && config.planningManager.hasConfiguredReadOnlyTools) {
261
257
  visibleTools = allTools.filter((t) => config.planningManager!.allowedInPlanning.has(t.name));
262
258
  }
263
259
 
@@ -442,9 +438,20 @@ export async function runAgentLoop(
442
438
  }
443
439
  }
444
440
  } catch (err) {
445
- if (!(err instanceof Error && err.name === "AbortError")) {
441
+ const isAbort = err instanceof Error && err.name === "AbortError";
442
+ if (!isAbort) {
446
443
  onEvent({ type: "error", error: err instanceof Error ? err : new Error(String(err)) });
447
444
  }
445
+
446
+ onEvent({ type: "agent_end", messages: allMessages });
447
+ await extensionRunner?.emitSimple("agent_end", { messages: allMessages });
448
+
449
+ // Re-throw non-abort errors so the caller (Agent._runLoop) can set error state.
450
+ // AbortError is intentional (user called agent.abort()), not an error condition.
451
+ if (!isAbort) {
452
+ throw err;
453
+ }
454
+ return allMessages;
448
455
  }
449
456
 
450
457
  onEvent({ type: "agent_end", messages: allMessages });