klaus-agent 0.1.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 (163) hide show
  1. package/README.md +685 -0
  2. package/dist/approval/approval.d.ts +18 -0
  3. package/dist/approval/approval.js +93 -0
  4. package/dist/approval/approval.js.map +1 -0
  5. package/dist/approval/types.d.ts +21 -0
  6. package/dist/approval/types.js +3 -0
  7. package/dist/approval/types.js.map +1 -0
  8. package/dist/background/task-manager.d.ts +14 -0
  9. package/dist/background/task-manager.js +89 -0
  10. package/dist/background/task-manager.js.map +1 -0
  11. package/dist/background/tools.d.ts +4 -0
  12. package/dist/background/tools.js +74 -0
  13. package/dist/background/tools.js.map +1 -0
  14. package/dist/background/types.d.ts +31 -0
  15. package/dist/background/types.js +3 -0
  16. package/dist/background/types.js.map +1 -0
  17. package/dist/checkpoint/checkpoint-manager.d.ts +19 -0
  18. package/dist/checkpoint/checkpoint-manager.js +49 -0
  19. package/dist/checkpoint/checkpoint-manager.js.map +1 -0
  20. package/dist/checkpoint/dmail.d.ts +10 -0
  21. package/dist/checkpoint/dmail.js +26 -0
  22. package/dist/checkpoint/dmail.js.map +1 -0
  23. package/dist/checkpoint/types.d.ts +12 -0
  24. package/dist/checkpoint/types.js +3 -0
  25. package/dist/checkpoint/types.js.map +1 -0
  26. package/dist/compaction/compaction.d.ts +6 -0
  27. package/dist/compaction/compaction.js +104 -0
  28. package/dist/compaction/compaction.js.map +1 -0
  29. package/dist/compaction/summarizer.d.ts +10 -0
  30. package/dist/compaction/summarizer.js +75 -0
  31. package/dist/compaction/summarizer.js.map +1 -0
  32. package/dist/compaction/types.d.ts +23 -0
  33. package/dist/compaction/types.js +3 -0
  34. package/dist/compaction/types.js.map +1 -0
  35. package/dist/core/agent-loop.d.ts +37 -0
  36. package/dist/core/agent-loop.js +337 -0
  37. package/dist/core/agent-loop.js.map +1 -0
  38. package/dist/core/agent.d.ts +97 -0
  39. package/dist/core/agent.js +335 -0
  40. package/dist/core/agent.js.map +1 -0
  41. package/dist/extensions/runner.d.ts +19 -0
  42. package/dist/extensions/runner.js +88 -0
  43. package/dist/extensions/runner.js.map +1 -0
  44. package/dist/extensions/types.d.ts +179 -0
  45. package/dist/extensions/types.js +3 -0
  46. package/dist/extensions/types.js.map +1 -0
  47. package/dist/index.d.ts +78 -0
  48. package/dist/index.js +53 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/injection/history-normalizer.d.ts +2 -0
  51. package/dist/injection/history-normalizer.js +34 -0
  52. package/dist/injection/history-normalizer.js.map +1 -0
  53. package/dist/injection/injection-manager.d.ts +8 -0
  54. package/dist/injection/injection-manager.js +28 -0
  55. package/dist/injection/injection-manager.js.map +1 -0
  56. package/dist/injection/types.d.ts +8 -0
  57. package/dist/injection/types.js +3 -0
  58. package/dist/injection/types.js.map +1 -0
  59. package/dist/llm/provider.d.ts +16 -0
  60. package/dist/llm/provider.js +233 -0
  61. package/dist/llm/provider.js.map +1 -0
  62. package/dist/llm/types.d.ts +110 -0
  63. package/dist/llm/types.js +3 -0
  64. package/dist/llm/types.js.map +1 -0
  65. package/dist/multi-agent/labor-market.d.ts +16 -0
  66. package/dist/multi-agent/labor-market.js +40 -0
  67. package/dist/multi-agent/labor-market.js.map +1 -0
  68. package/dist/multi-agent/task-executor.d.ts +12 -0
  69. package/dist/multi-agent/task-executor.js +38 -0
  70. package/dist/multi-agent/task-executor.js.map +1 -0
  71. package/dist/multi-agent/task-tool.d.ts +4 -0
  72. package/dist/multi-agent/task-tool.js +46 -0
  73. package/dist/multi-agent/task-tool.js.map +1 -0
  74. package/dist/multi-agent/types.d.ts +7 -0
  75. package/dist/multi-agent/types.js +3 -0
  76. package/dist/multi-agent/types.js.map +1 -0
  77. package/dist/session/session-context-builder.d.ts +2 -0
  78. package/dist/session/session-context-builder.js +52 -0
  79. package/dist/session/session-context-builder.js.map +1 -0
  80. package/dist/session/session-manager.d.ts +30 -0
  81. package/dist/session/session-manager.js +209 -0
  82. package/dist/session/session-manager.js.map +1 -0
  83. package/dist/session/types.d.ts +61 -0
  84. package/dist/session/types.js +3 -0
  85. package/dist/session/types.js.map +1 -0
  86. package/dist/skills/discovery.d.ts +2 -0
  87. package/dist/skills/discovery.js +29 -0
  88. package/dist/skills/discovery.js.map +1 -0
  89. package/dist/skills/loader.d.ts +3 -0
  90. package/dist/skills/loader.js +46 -0
  91. package/dist/skills/loader.js.map +1 -0
  92. package/dist/skills/skill-tool.d.ts +3 -0
  93. package/dist/skills/skill-tool.js +38 -0
  94. package/dist/skills/skill-tool.js.map +1 -0
  95. package/dist/skills/types.d.ts +14 -0
  96. package/dist/skills/types.js +3 -0
  97. package/dist/skills/types.js.map +1 -0
  98. package/dist/tools/executor.d.ts +37 -0
  99. package/dist/tools/executor.js +131 -0
  100. package/dist/tools/executor.js.map +1 -0
  101. package/dist/tools/mcp-adapter.d.ts +57 -0
  102. package/dist/tools/mcp-adapter.js +113 -0
  103. package/dist/tools/mcp-adapter.js.map +1 -0
  104. package/dist/tools/types.d.ts +43 -0
  105. package/dist/tools/types.js +2 -0
  106. package/dist/tools/types.js.map +1 -0
  107. package/dist/types.d.ts +96 -0
  108. package/dist/types.js +3 -0
  109. package/dist/types.js.map +1 -0
  110. package/dist/utils/id.d.ts +1 -0
  111. package/dist/utils/id.js +9 -0
  112. package/dist/utils/id.js.map +1 -0
  113. package/dist/utils/jsonl.d.ts +3 -0
  114. package/dist/utils/jsonl.js +17 -0
  115. package/dist/utils/jsonl.js.map +1 -0
  116. package/dist/wire/types.d.ts +10 -0
  117. package/dist/wire/types.js +3 -0
  118. package/dist/wire/types.js.map +1 -0
  119. package/dist/wire/wire.d.ts +20 -0
  120. package/dist/wire/wire.js +71 -0
  121. package/dist/wire/wire.js.map +1 -0
  122. package/package.json +26 -0
  123. package/src/approval/approval.ts +108 -0
  124. package/src/approval/types.ts +26 -0
  125. package/src/background/task-manager.ts +112 -0
  126. package/src/background/tools.ts +84 -0
  127. package/src/background/types.ts +29 -0
  128. package/src/checkpoint/checkpoint-manager.ts +60 -0
  129. package/src/checkpoint/dmail.ts +35 -0
  130. package/src/checkpoint/types.ts +16 -0
  131. package/src/compaction/compaction.ts +119 -0
  132. package/src/compaction/summarizer.ts +83 -0
  133. package/src/compaction/types.ts +29 -0
  134. package/src/core/agent-loop.ts +427 -0
  135. package/src/core/agent.ts +430 -0
  136. package/src/extensions/runner.ts +138 -0
  137. package/src/extensions/types.ts +177 -0
  138. package/src/index.ts +221 -0
  139. package/src/injection/history-normalizer.ts +44 -0
  140. package/src/injection/injection-manager.ts +34 -0
  141. package/src/injection/types.ts +12 -0
  142. package/src/llm/provider.ts +254 -0
  143. package/src/llm/types.ts +146 -0
  144. package/src/multi-agent/labor-market.ts +54 -0
  145. package/src/multi-agent/task-executor.ts +49 -0
  146. package/src/multi-agent/task-tool.ts +58 -0
  147. package/src/multi-agent/types.ts +10 -0
  148. package/src/session/session-context-builder.ts +65 -0
  149. package/src/session/session-manager.ts +258 -0
  150. package/src/session/types.ts +93 -0
  151. package/src/skills/discovery.ts +32 -0
  152. package/src/skills/loader.ts +54 -0
  153. package/src/skills/skill-tool.ts +50 -0
  154. package/src/skills/types.ts +18 -0
  155. package/src/tools/executor.ts +196 -0
  156. package/src/tools/mcp-adapter.ts +185 -0
  157. package/src/tools/types.ts +64 -0
  158. package/src/types.ts +96 -0
  159. package/src/utils/id.ts +8 -0
  160. package/src/utils/jsonl.ts +19 -0
  161. package/src/wire/types.ts +14 -0
  162. package/src/wire/wire.ts +79 -0
  163. package/tsconfig.json +19 -0
@@ -0,0 +1,3 @@
1
+ // Wire — typed async event channel types
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/wire/types.ts"],"names":[],"mappings":"AAAA,yCAAyC"}
@@ -0,0 +1,20 @@
1
+ import type { WireMessage, WireSubscriber, WireSubscription } from "./types.js";
2
+ export interface WireOptions {
3
+ /** Max messages to buffer for replay to late subscribers. 0 = no buffering. */
4
+ bufferSize?: number;
5
+ }
6
+ export declare class Wire {
7
+ private _subscribers;
8
+ private _buffer;
9
+ private _bufferSize;
10
+ constructor(options?: WireOptions);
11
+ publish<T>(type: string, payload: T): WireMessage<T>;
12
+ subscribe(fn: WireSubscriber, options?: {
13
+ replay?: boolean;
14
+ }): WireSubscription;
15
+ on<T>(type: string, fn: (message: WireMessage<T>) => void, options?: {
16
+ replay?: boolean;
17
+ }): WireSubscription;
18
+ getBuffer(): readonly WireMessage[];
19
+ dispose(): void;
20
+ }
@@ -0,0 +1,71 @@
1
+ // Wire — typed async event channel with optional replay buffer
2
+ import { generateId } from "../utils/id.js";
3
+ export class Wire {
4
+ _subscribers = new Set();
5
+ _buffer = [];
6
+ _bufferSize;
7
+ constructor(options) {
8
+ this._bufferSize = options?.bufferSize ?? 0;
9
+ }
10
+ publish(type, payload) {
11
+ const message = {
12
+ id: generateId(),
13
+ timestamp: Date.now(),
14
+ type,
15
+ payload,
16
+ };
17
+ if (this._bufferSize > 0) {
18
+ this._buffer.push(message);
19
+ if (this._buffer.length > this._bufferSize) {
20
+ this._buffer.shift();
21
+ }
22
+ }
23
+ for (const fn of this._subscribers) {
24
+ try {
25
+ fn(message);
26
+ }
27
+ catch {
28
+ // Subscriber errors should not break the publisher
29
+ }
30
+ }
31
+ return message;
32
+ }
33
+ subscribe(fn, options) {
34
+ if (options?.replay) {
35
+ for (const msg of this._buffer) {
36
+ try {
37
+ fn(msg);
38
+ }
39
+ catch { }
40
+ }
41
+ }
42
+ this._subscribers.add(fn);
43
+ return { unsubscribe: () => this._subscribers.delete(fn) };
44
+ }
45
+ on(type, fn, options) {
46
+ const filtered = (msg) => {
47
+ if (msg.type === type)
48
+ fn(msg);
49
+ };
50
+ if (options?.replay) {
51
+ for (const msg of this._buffer) {
52
+ if (msg.type === type) {
53
+ try {
54
+ fn(msg);
55
+ }
56
+ catch { }
57
+ }
58
+ }
59
+ }
60
+ this._subscribers.add(filtered);
61
+ return { unsubscribe: () => this._subscribers.delete(filtered) };
62
+ }
63
+ getBuffer() {
64
+ return this._buffer;
65
+ }
66
+ dispose() {
67
+ this._subscribers.clear();
68
+ this._buffer = [];
69
+ }
70
+ }
71
+ //# sourceMappingURL=wire.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire.js","sourceRoot":"","sources":["../../src/wire/wire.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAE/D,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAQ5C,MAAM,OAAO,IAAI;IACP,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,OAAO,GAAkB,EAAE,CAAC;IAC5B,WAAW,CAAS;IAE5B,YAAY,OAAqB;QAC/B,IAAI,CAAC,WAAW,GAAG,OAAO,EAAE,UAAU,IAAI,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,CAAI,IAAY,EAAE,OAAU;QACjC,MAAM,OAAO,GAAmB;YAC9B,EAAE,EAAE,UAAU,EAAE;YAChB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,IAAI;YACJ,OAAO;SACR,CAAC;QAEF,IAAI,IAAI,CAAC,WAAW,GAAG,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC3B,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;gBAC3C,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACvB,CAAC;QACH,CAAC;QAED,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACnC,IAAI,CAAC;gBACH,EAAE,CAAC,OAAO,CAAC,CAAC;YACd,CAAC;YAAC,MAAM,CAAC;gBACP,mDAAmD;YACrD,CAAC;QACH,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,SAAS,CAAC,EAAkB,EAAE,OAA8B;QAC1D,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;YACpB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBAC/B,IAAI,CAAC;oBAAC,EAAE,CAAC,GAAG,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAA,CAAC;YAC3B,CAAC;QACH,CAAC;QACD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC1B,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;IAC7D,CAAC;IAED,EAAE,CAAI,IAAY,EAAE,EAAqC,EAAE,OAA8B;QACvF,MAAM,QAAQ,GAAmB,CAAC,GAAG,EAAE,EAAE;YACvC,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI;gBAAE,EAAE,CAAC,GAAqB,CAAC,CAAC;QACnD,CAAC,CAAC;QACF,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;YACpB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBAC/B,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;oBACtB,IAAI,CAAC;wBAAC,EAAE,CAAC,GAAqB,CAAC,CAAC;oBAAC,CAAC;oBAAC,MAAM,CAAC,CAAA,CAAC;gBAC7C,CAAC;YACH,CAAC;QACH,CAAC;QACD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAChC,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;IACnE,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,OAAO;QACL,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;IACpB,CAAC;CACF"}
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "klaus-agent",
3
+ "version": "0.1.0",
4
+ "description": "Universal agent framework",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "dev": "tsc --watch"
17
+ },
18
+ "dependencies": {
19
+ "@anthropic-ai/sdk": "^0.39.0",
20
+ "@sinclair/typebox": "^0.34.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^25.5.0",
24
+ "typescript": "^5.7.0"
25
+ }
26
+ }
@@ -0,0 +1,108 @@
1
+ // Queue-based approval system
2
+
3
+ import { generateId } from "../utils/id.js";
4
+ import type { Approval, ApprovalConfig, ApprovalRequest, ApprovalResponse } from "./types.js";
5
+
6
+ interface PendingRequest {
7
+ request: ApprovalRequest;
8
+ resolve: (approved: boolean) => void;
9
+ }
10
+
11
+ export class ApprovalImpl implements Approval {
12
+ private _yolo: boolean;
13
+ private _autoApproveActions: Set<string>;
14
+ private _pending = new Map<string, PendingRequest>();
15
+ private _queue: ApprovalRequest[] = [];
16
+ private _waiters: ((req: ApprovalRequest) => void)[] = [];
17
+
18
+ constructor(config?: ApprovalConfig) {
19
+ this._yolo = config?.yolo ?? false;
20
+ this._autoApproveActions = new Set(config?.autoApproveActions ?? []);
21
+ }
22
+
23
+ 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;
26
+
27
+ const req: ApprovalRequest = {
28
+ id: generateId(),
29
+ toolCallId,
30
+ sender,
31
+ action,
32
+ description,
33
+ };
34
+
35
+ return new Promise<boolean>((resolve) => {
36
+ this._pending.set(req.id, { request: req, resolve });
37
+
38
+ // Deliver to waiter or queue
39
+ const waiter = this._waiters.shift();
40
+ if (waiter) {
41
+ waiter(req);
42
+ } else {
43
+ this._queue.push(req);
44
+ }
45
+ });
46
+ }
47
+
48
+ async fetchRequest(): Promise<ApprovalRequest> {
49
+ const queued = this._queue.shift();
50
+ if (queued) return queued;
51
+
52
+ return new Promise<ApprovalRequest>((resolve, reject) => {
53
+ const waiter = (req: ApprovalRequest) => resolve(req);
54
+ (waiter as any)._reject = reject;
55
+ this._waiters.push(waiter);
56
+ });
57
+ }
58
+
59
+ /** Cancel all pending waiters (e.g., on agent dispose) */
60
+ cancelPendingWaiters(): void {
61
+ for (const waiter of this._waiters) {
62
+ const reject = (waiter as any)._reject;
63
+ if (reject) reject(new Error("Approval cancelled"));
64
+ }
65
+ this._waiters = [];
66
+ }
67
+
68
+ resolve(requestId: string, response: ApprovalResponse): void {
69
+ const pending = this._pending.get(requestId);
70
+ if (!pending) return;
71
+
72
+ this._pending.delete(requestId);
73
+
74
+ if (response === "approve_for_session") {
75
+ this._autoApproveActions.add(pending.request.action);
76
+ pending.resolve(true);
77
+ } else {
78
+ pending.resolve(response === "approve");
79
+ }
80
+ }
81
+
82
+ setYolo(yolo: boolean): void {
83
+ this._yolo = yolo;
84
+ }
85
+
86
+ isYolo(): boolean {
87
+ return this._yolo;
88
+ }
89
+
90
+ get autoApproveActions(): Set<string> {
91
+ return this._autoApproveActions;
92
+ }
93
+
94
+ 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;
107
+ }
108
+ }
@@ -0,0 +1,26 @@
1
+ // Approval system types
2
+
3
+ export type ApprovalResponse = "approve" | "approve_for_session" | "reject";
4
+
5
+ export interface ApprovalRequest {
6
+ id: string;
7
+ toolCallId: string;
8
+ sender: string;
9
+ action: string;
10
+ description: string;
11
+ }
12
+
13
+ export interface ApprovalConfig {
14
+ yolo?: boolean;
15
+ autoApproveActions?: string[];
16
+ }
17
+
18
+ export interface Approval {
19
+ request(sender: string, action: string, description: string, toolCallId: string): Promise<boolean>;
20
+ fetchRequest(): Promise<ApprovalRequest>;
21
+ resolve(requestId: string, response: ApprovalResponse): void;
22
+ setYolo(yolo: boolean): void;
23
+ isYolo(): boolean;
24
+ readonly autoApproveActions: Set<string>;
25
+ share(): Approval;
26
+ }
@@ -0,0 +1,112 @@
1
+ // Background task manager — in-process async task execution
2
+
3
+ import { generateId } from "../utils/id.js";
4
+ import type { BackgroundTaskStatus, BackgroundTaskInfo, BackgroundTaskHandle, BackgroundTaskEvent } from "./types.js";
5
+
6
+ interface InternalTask {
7
+ id: string;
8
+ name: string;
9
+ status: BackgroundTaskStatus;
10
+ createdAt: number;
11
+ startedAt?: number;
12
+ completedAt?: number;
13
+ result?: unknown;
14
+ error?: Error;
15
+ abortController: AbortController;
16
+ }
17
+
18
+ export class BackgroundTaskManager {
19
+ private _tasks = new Map<string, InternalTask>();
20
+ private _onEvent?: (event: BackgroundTaskEvent) => void;
21
+
22
+ constructor(onEvent?: (event: BackgroundTaskEvent) => void) {
23
+ this._onEvent = onEvent;
24
+ }
25
+
26
+ spawn<T>(name: string, fn: (signal: AbortSignal) => Promise<T>): BackgroundTaskHandle<T> {
27
+ const id = generateId();
28
+ const abortController = new AbortController();
29
+ const now = Date.now();
30
+
31
+ const task: InternalTask = {
32
+ id,
33
+ name,
34
+ status: "running",
35
+ createdAt: now,
36
+ startedAt: now,
37
+ abortController,
38
+ };
39
+
40
+ this._tasks.set(id, task);
41
+ this._onEvent?.({ type: "task_started", task: this._toInfo(task) });
42
+
43
+ fn(abortController.signal).then(
44
+ (result) => {
45
+ task.status = "completed";
46
+ task.completedAt = Date.now();
47
+ task.result = result;
48
+ try { this._onEvent?.({ type: "task_completed", task: this._toInfo(task), result }); } catch {}
49
+ },
50
+ (err) => {
51
+ task.status = "failed";
52
+ task.completedAt = Date.now();
53
+ task.error = err instanceof Error ? err : new Error(String(err));
54
+ try { this._onEvent?.({ type: "task_failed", task: this._toInfo(task), error: task.error.message }); } catch {}
55
+ },
56
+ );
57
+
58
+ return this._toHandle<T>(task);
59
+ }
60
+
61
+ get(id: string): BackgroundTaskHandle | undefined {
62
+ const task = this._tasks.get(id);
63
+ return task ? this._toHandle(task) : undefined;
64
+ }
65
+
66
+ list(): BackgroundTaskInfo[] {
67
+ return [...this._tasks.values()].map((t) => this._toInfo(t));
68
+ }
69
+
70
+ abort(id: string): boolean {
71
+ const task = this._tasks.get(id);
72
+ if (!task || task.status !== "running") return false;
73
+ task.abortController.abort();
74
+ return true;
75
+ }
76
+
77
+ abortAll(): void {
78
+ for (const task of this._tasks.values()) {
79
+ if (task.status === "running") {
80
+ task.abortController.abort();
81
+ }
82
+ }
83
+ }
84
+
85
+ dispose(): void {
86
+ this.abortAll();
87
+ this._tasks.clear();
88
+ }
89
+
90
+ private _toInfo(task: InternalTask): BackgroundTaskInfo {
91
+ return {
92
+ id: task.id,
93
+ name: task.name,
94
+ status: task.status,
95
+ createdAt: task.createdAt,
96
+ startedAt: task.startedAt,
97
+ completedAt: task.completedAt,
98
+ error: task.error?.message,
99
+ };
100
+ }
101
+
102
+ private _toHandle<T>(task: InternalTask): BackgroundTaskHandle<T> {
103
+ return {
104
+ get id() { return task.id; },
105
+ get name() { return task.name; },
106
+ get status() { return task.status; },
107
+ get result() { return task.result as T | undefined; },
108
+ get error() { return task.error; },
109
+ abort: () => task.abortController.abort(),
110
+ };
111
+ }
112
+ }
@@ -0,0 +1,84 @@
1
+ // Built-in background task tools — allows LLM to manage background tasks
2
+
3
+ import { Type } from "@sinclair/typebox";
4
+ import type { AgentTool, AgentToolResult, ToolExecutionContext } from "../tools/types.js";
5
+ import type { BackgroundTaskManager } from "./task-manager.js";
6
+ import type { TaskFactory } from "./types.js";
7
+
8
+ export function createBackgroundTaskTools(
9
+ manager: BackgroundTaskManager,
10
+ factories?: Record<string, TaskFactory>,
11
+ ): AgentTool[] {
12
+ const tools: AgentTool[] = [
13
+ {
14
+ name: "check_task_status",
15
+ label: "Check Task Status",
16
+ description: "Check the status of background tasks. If no task_id is provided, returns all tasks.",
17
+ parameters: Type.Object({
18
+ task_id: Type.Optional(Type.String({ description: "Task ID to check. Omit to list all tasks." })),
19
+ }),
20
+ async execute(_toolCallId: string, params: { task_id?: string }): Promise<AgentToolResult> {
21
+ if (params.task_id) {
22
+ const handle = manager.get(params.task_id);
23
+ if (!handle) {
24
+ return { content: [{ type: "text", text: `No task found with ID: ${params.task_id}` }] };
25
+ }
26
+ return { content: [{ type: "text", text: JSON.stringify({ id: handle.id, name: handle.name, status: handle.status }, null, 2) }] };
27
+ }
28
+ const tasks = manager.list();
29
+ if (tasks.length === 0) {
30
+ return { content: [{ type: "text", text: "No background tasks." }] };
31
+ }
32
+ return { content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }] };
33
+ },
34
+ },
35
+ {
36
+ name: "get_task_result",
37
+ label: "Get Task Result",
38
+ description: "Get the result of a completed background task.",
39
+ parameters: Type.Object({
40
+ task_id: Type.String({ description: "Task ID to get the result for." }),
41
+ }),
42
+ async execute(_toolCallId: string, params: { task_id: string }): Promise<AgentToolResult> {
43
+ const handle = manager.get(params.task_id);
44
+ if (!handle) {
45
+ return { content: [{ type: "text", text: `No task found with ID: ${params.task_id}` }] };
46
+ }
47
+ if (handle.status === "running") {
48
+ return { content: [{ type: "text", text: `Task "${handle.name}" is still running.` }] };
49
+ }
50
+ if (handle.status === "failed") {
51
+ return { content: [{ type: "text", text: `Task "${handle.name}" failed: ${handle.error?.message ?? "unknown error"}` }] };
52
+ }
53
+ const resultText = handle.result !== undefined ? JSON.stringify(handle.result, null, 2) : "(no result)";
54
+ return { content: [{ type: "text", text: resultText }] };
55
+ },
56
+ },
57
+ ];
58
+
59
+ if (factories && Object.keys(factories).length > 0) {
60
+ const names = Object.keys(factories);
61
+ const description = `Start a background task. Available tasks:\n${names.map((n) => `- ${n}`).join("\n")}`;
62
+
63
+ tools.push({
64
+ name: "start_background_task",
65
+ label: "Start Background Task",
66
+ description,
67
+ parameters: Type.Object({
68
+ task_name: Type.String({ description: "Name of the task to start." }),
69
+ args: Type.Optional(Type.Unknown({ description: "Arguments to pass to the task." })),
70
+ }),
71
+ async execute(_toolCallId: string, params: { task_name: string; args?: unknown }): Promise<AgentToolResult> {
72
+ const factory = factories[params.task_name];
73
+ if (!factory) {
74
+ const available = Object.keys(factories).join(", ");
75
+ return { content: [{ type: "text", text: `Unknown task: "${params.task_name}". Available: ${available}` }] };
76
+ }
77
+ const handle = manager.spawn(params.task_name, (signal) => factory(params.args, signal));
78
+ return { content: [{ type: "text", text: `Task started: ${handle.name} (ID: ${handle.id})` }] };
79
+ },
80
+ });
81
+ }
82
+
83
+ return tools;
84
+ }
@@ -0,0 +1,29 @@
1
+ // Background task types
2
+
3
+ export type BackgroundTaskStatus = "pending" | "running" | "completed" | "failed";
4
+
5
+ export interface BackgroundTaskInfo {
6
+ id: string;
7
+ name: string;
8
+ status: BackgroundTaskStatus;
9
+ createdAt: number;
10
+ startedAt?: number;
11
+ completedAt?: number;
12
+ error?: string;
13
+ }
14
+
15
+ export interface BackgroundTaskHandle<T = unknown> {
16
+ readonly id: string;
17
+ readonly name: string;
18
+ readonly status: BackgroundTaskStatus;
19
+ readonly result?: T;
20
+ readonly error?: Error;
21
+ abort(): void;
22
+ }
23
+
24
+ export type BackgroundTaskEvent =
25
+ | { type: "task_started"; task: BackgroundTaskInfo }
26
+ | { type: "task_completed"; task: BackgroundTaskInfo; result: unknown }
27
+ | { type: "task_failed"; task: BackgroundTaskInfo; error: string };
28
+
29
+ export type TaskFactory = (args: unknown, signal: AbortSignal) => Promise<unknown>;
@@ -0,0 +1,60 @@
1
+ // Checkpoint manager — creates checkpoints in session tree, handles D-Mail revert
2
+
3
+ import type { SessionManager } from "../session/session-manager.js";
4
+ import type { CheckpointInfo } from "./types.js";
5
+ import { DenwaRenji } from "./dmail.js";
6
+
7
+ export class CheckpointManager {
8
+ private _checkpoints: CheckpointInfo[] = [];
9
+ private _nextId = 0; // Global counter, never resets after D-Mail
10
+ private _denwaRenji = new DenwaRenji();
11
+
12
+ constructor(private _session: SessionManager) {}
13
+
14
+ get denwaRenji(): DenwaRenji {
15
+ return this._denwaRenji;
16
+ }
17
+
18
+ async checkpoint(): Promise<CheckpointInfo> {
19
+ const checkpointId = this._nextId++;
20
+ const entryId = await this._session.appendCheckpoint(checkpointId);
21
+
22
+ const info: CheckpointInfo = { checkpointId, entryId };
23
+ this._checkpoints.push(info);
24
+ this._denwaRenji.setCheckpointCount(this._checkpoints.length);
25
+
26
+ return info;
27
+ }
28
+
29
+ getCheckpoint(checkpointId: number): CheckpointInfo | undefined {
30
+ return this._checkpoints.find((cp) => cp.checkpointId === checkpointId);
31
+ }
32
+
33
+ getAllCheckpoints(): CheckpointInfo[] {
34
+ return [...this._checkpoints];
35
+ }
36
+
37
+ /**
38
+ * Handle D-Mail: branch from the target checkpoint and inject the message.
39
+ * Returns the D-Mail content if one was pending, null otherwise.
40
+ */
41
+ async handleDMail(): Promise<string | null> {
42
+ const dmail = this._denwaRenji.fetchPendingDMail();
43
+ if (!dmail) return null;
44
+
45
+ const checkpoint = this._checkpoints.find((cp) => cp.checkpointId === dmail.checkpointId);
46
+ if (!checkpoint) {
47
+ throw new Error(`Checkpoint ${dmail.checkpointId} not found`);
48
+ }
49
+
50
+ // Branch session tree back to checkpoint entry
51
+ this._session.branch(checkpoint.entryId);
52
+
53
+ // Remove checkpoints after the target (keep target itself)
54
+ const targetIdx = this._checkpoints.indexOf(checkpoint);
55
+ this._checkpoints.length = targetIdx + 1;
56
+ this._denwaRenji.setCheckpointCount(this._checkpoints.length);
57
+
58
+ return dmail.message;
59
+ }
60
+ }
@@ -0,0 +1,35 @@
1
+ // DenwaRenji — D-Mail state machine
2
+
3
+ import type { DMail } from "./types.js";
4
+
5
+ export class DenwaRenji {
6
+ private _pendingDMail: DMail | null = null;
7
+ private _checkpointCount = 0;
8
+
9
+ sendDMail(message: string, checkpointId: number): void {
10
+ if (checkpointId < 0 || checkpointId >= this._checkpointCount) {
11
+ throw new Error(
12
+ `Invalid checkpoint ID ${checkpointId}. Valid range: 0-${this._checkpointCount - 1}`,
13
+ );
14
+ }
15
+ this._pendingDMail = { message, checkpointId };
16
+ }
17
+
18
+ setCheckpointCount(n: number): void {
19
+ this._checkpointCount = n;
20
+ }
21
+
22
+ getCheckpointCount(): number {
23
+ return this._checkpointCount;
24
+ }
25
+
26
+ fetchPendingDMail(): DMail | null {
27
+ const dmail = this._pendingDMail;
28
+ this._pendingDMail = null;
29
+ return dmail;
30
+ }
31
+
32
+ hasPendingDMail(): boolean {
33
+ return this._pendingDMail !== null;
34
+ }
35
+ }
@@ -0,0 +1,16 @@
1
+ // Checkpoint and D-Mail types
2
+
3
+ export interface CheckpointConfig {
4
+ enabled?: boolean;
5
+ enableDMail?: boolean;
6
+ }
7
+
8
+ export interface DMail {
9
+ message: string;
10
+ checkpointId: number;
11
+ }
12
+
13
+ export interface CheckpointInfo {
14
+ checkpointId: number;
15
+ entryId: string;
16
+ }