@openlucaskaka/kagent 0.1.7

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 (117) hide show
  1. package/README.md +353 -0
  2. package/npm/bin/kagent-serve.js +6 -0
  3. package/npm/bin/kagent.js +6 -0
  4. package/npm/lib/App.js +524 -0
  5. package/npm/lib/app-state.js +224 -0
  6. package/npm/lib/approval-choice.js +25 -0
  7. package/npm/lib/commands.js +59 -0
  8. package/npm/lib/editor.js +188 -0
  9. package/npm/lib/ink-runner.js +41 -0
  10. package/npm/lib/kagent-home.js +39 -0
  11. package/npm/lib/launcher.js +221 -0
  12. package/npm/lib/protocol.js +33 -0
  13. package/npm/lib/provider-setup.js +139 -0
  14. package/npm/lib/python-runner.js +892 -0
  15. package/npm/lib/runtime-client.js +390 -0
  16. package/npm/lib/terminal-input.js +127 -0
  17. package/npm/lib/terminal-text.js +19 -0
  18. package/npm/lib/terminal-width.js +14 -0
  19. package/npm/lib/transcript.js +227 -0
  20. package/npm/lib/ui-components.js +247 -0
  21. package/npm/lib/update-manager.js +334 -0
  22. package/package.json +39 -0
  23. package/pyproject.toml +55 -0
  24. package/src/kagent/__init__.py +90 -0
  25. package/src/kagent/cli/__init__.py +5 -0
  26. package/src/kagent/cli/__main__.py +6 -0
  27. package/src/kagent/cli/commands.py +112 -0
  28. package/src/kagent/cli/conversation.py +127 -0
  29. package/src/kagent/cli/interactive.py +841 -0
  30. package/src/kagent/cli/main.py +685 -0
  31. package/src/kagent/cli/memory.py +460 -0
  32. package/src/kagent/cli/pending_approval.py +169 -0
  33. package/src/kagent/cli/provider.py +27 -0
  34. package/src/kagent/cli/session_commands.py +401 -0
  35. package/src/kagent/cli/stdio_runtime.py +784 -0
  36. package/src/kagent/cli/trace.py +67 -0
  37. package/src/kagent/cli/ui.py +931 -0
  38. package/src/kagent/core/__init__.py +0 -0
  39. package/src/kagent/core/agent.py +296 -0
  40. package/src/kagent/core/faults.py +11 -0
  41. package/src/kagent/core/invariants.py +47 -0
  42. package/src/kagent/core/normalization.py +81 -0
  43. package/src/kagent/core/planning.py +48 -0
  44. package/src/kagent/core/state.py +73 -0
  45. package/src/kagent/core/summary.py +64 -0
  46. package/src/kagent/core/tools.py +196 -0
  47. package/src/kagent/core/trace.py +57 -0
  48. package/src/kagent/eval/__init__.py +11 -0
  49. package/src/kagent/eval/cases.py +229 -0
  50. package/src/kagent/eval/evaluator.py +216 -0
  51. package/src/kagent/integrations/__init__.py +3 -0
  52. package/src/kagent/integrations/audit.py +95 -0
  53. package/src/kagent/integrations/backends.py +131 -0
  54. package/src/kagent/integrations/memory.py +301 -0
  55. package/src/kagent/ops/__init__.py +0 -0
  56. package/src/kagent/ops/batch.py +156 -0
  57. package/src/kagent/ops/doctor.py +255 -0
  58. package/src/kagent/ops/metrics.py +214 -0
  59. package/src/kagent/ops/release_evidence.py +877 -0
  60. package/src/kagent/ops/release_manifest.py +142 -0
  61. package/src/kagent/ops/trace_replay.py +285 -0
  62. package/src/kagent/providers/__init__.py +7 -0
  63. package/src/kagent/providers/embeddings.py +187 -0
  64. package/src/kagent/providers/llm.py +770 -0
  65. package/src/kagent/py.typed +1 -0
  66. package/src/kagent/runtime/__init__.py +28 -0
  67. package/src/kagent/runtime/action_graph.py +543 -0
  68. package/src/kagent/runtime/agent.py +2089 -0
  69. package/src/kagent/runtime/approval.py +64 -0
  70. package/src/kagent/runtime/cancellation.py +64 -0
  71. package/src/kagent/runtime/checkpoint_state.py +146 -0
  72. package/src/kagent/runtime/checkpoint_storage.py +270 -0
  73. package/src/kagent/runtime/context.py +65 -0
  74. package/src/kagent/runtime/file_transaction.py +195 -0
  75. package/src/kagent/runtime/hooks.py +74 -0
  76. package/src/kagent/runtime/metadata.py +116 -0
  77. package/src/kagent/runtime/patch_checkpoints.py +385 -0
  78. package/src/kagent/runtime/policy.py +50 -0
  79. package/src/kagent/runtime/presentation.py +205 -0
  80. package/src/kagent/runtime/redaction.py +130 -0
  81. package/src/kagent/runtime/sandbox.py +331 -0
  82. package/src/kagent/runtime/skills.py +66 -0
  83. package/src/kagent/runtime/steering.py +56 -0
  84. package/src/kagent/runtime/steps.py +255 -0
  85. package/src/kagent/runtime/task_state.py +40 -0
  86. package/src/kagent/runtime/tools.py +3532 -0
  87. package/src/kagent/runtime/types.py +240 -0
  88. package/src/kagent/runtime/workspace.py +597 -0
  89. package/src/kagent/service/__init__.py +26 -0
  90. package/src/kagent/service/__main__.py +6 -0
  91. package/src/kagent/service/active_runs.py +178 -0
  92. package/src/kagent/service/cli.py +737 -0
  93. package/src/kagent/service/contract.py +2571 -0
  94. package/src/kagent/service/errors.py +71 -0
  95. package/src/kagent/service/idempotency.py +584 -0
  96. package/src/kagent/service/router.py +884 -0
  97. package/src/kagent/service/run.py +150 -0
  98. package/src/kagent/service/runtime.py +1915 -0
  99. package/src/kagent/service/runtime_approval.py +20 -0
  100. package/src/kagent/service/runtime_cancel.py +192 -0
  101. package/src/kagent/service/runtime_lifecycle.py +229 -0
  102. package/src/kagent/service/runtime_metadata.py +21 -0
  103. package/src/kagent/service/runtime_policy.py +115 -0
  104. package/src/kagent/service/runtime_recovery.py +573 -0
  105. package/src/kagent/service/runtime_resume.py +382 -0
  106. package/src/kagent/service/runtime_resume_claim.py +116 -0
  107. package/src/kagent/service/runtime_run.py +350 -0
  108. package/src/kagent/service/runtime_status.py +2007 -0
  109. package/src/kagent/service/safety.py +139 -0
  110. package/src/kagent/service/server.py +114 -0
  111. package/src/kagent/service/status.py +233 -0
  112. package/src/kagent/service/trace_store.py +322 -0
  113. package/src/kagent/service/transport.py +24 -0
  114. package/src/kagent/utils/__init__.py +0 -0
  115. package/src/kagent/utils/config_validation.py +21 -0
  116. package/src/kagent/utils/json_output.py +23 -0
  117. package/src/kagent/utils/paths.py +473 -0
@@ -0,0 +1,892 @@
1
+ "use strict";
2
+
3
+ const childProcess = require("child_process");
4
+ const crypto = require("crypto");
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+
8
+ const { kagentCachePath, resolveKagentHome } = require("./kagent-home");
9
+
10
+ const TEMP_RUNTIME_STALE_MS = 24 * 60 * 60 * 1000;
11
+ const PYTHON_ENTRYPOINTS = Object.freeze({
12
+ kagent: "import sys; sys.argv = sys.argv[1:]; from kagent.cli import main; raise SystemExit(main())",
13
+ "kagent-serve": "import sys; sys.argv = sys.argv[1:]; from kagent.service import main; raise SystemExit(main())"
14
+ });
15
+ const SECURE_FILESYSTEM_HELPER = String.raw`
16
+ import ctypes
17
+ import errno
18
+ import os
19
+ import re
20
+ import secrets
21
+ import stat
22
+ import sys
23
+
24
+
25
+ DIRECTORY_FLAGS = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW
26
+
27
+
28
+ def fail(message):
29
+ raise RuntimeError(message)
30
+
31
+
32
+ def path_parts(target):
33
+ if not os.path.isabs(target):
34
+ fail("managed path must be absolute")
35
+ normalized = os.path.normpath(target)
36
+ parts = [part for part in normalized.split(os.sep) if part]
37
+ if not parts:
38
+ fail("refusing managed filesystem root")
39
+ return parts
40
+
41
+
42
+ def open_directory(target, create_missing):
43
+ parts = path_parts(target)
44
+ current_fd = os.open(os.sep, DIRECTORY_FLAGS)
45
+ try:
46
+ for part in parts:
47
+ try:
48
+ next_fd = os.open(part, DIRECTORY_FLAGS, dir_fd=current_fd)
49
+ except FileNotFoundError:
50
+ if not create_missing:
51
+ raise
52
+ try:
53
+ os.mkdir(part, 0o700, dir_fd=current_fd)
54
+ except FileExistsError:
55
+ pass
56
+ next_fd = os.open(part, DIRECTORY_FLAGS, dir_fd=current_fd)
57
+ except OSError as error:
58
+ if error.errno in (errno.ELOOP, errno.ENOTDIR):
59
+ fail(f"refusing symbolic link or non-directory in managed path: {target}")
60
+ raise
61
+ os.close(current_fd)
62
+ current_fd = next_fd
63
+ return current_fd
64
+ except BaseException:
65
+ os.close(current_fd)
66
+ raise
67
+
68
+
69
+ def ensure_directory(target):
70
+ directory_fd = open_directory(target, True)
71
+ try:
72
+ if not stat.S_ISDIR(os.fstat(directory_fd).st_mode):
73
+ fail(f"managed path is not a directory: {target}")
74
+ os.fchmod(directory_fd, 0o700)
75
+ finally:
76
+ os.close(directory_fd)
77
+
78
+
79
+ def write_file(target):
80
+ parent, name = os.path.split(os.path.normpath(target))
81
+ if not name or name in (".", ".."):
82
+ fail(f"managed path is not a file: {target}")
83
+ parent_fd = open_directory(parent, False)
84
+ file_fd = None
85
+ try:
86
+ try:
87
+ file_fd = os.open(
88
+ name,
89
+ os.O_WRONLY | os.O_CREAT | os.O_TRUNC | os.O_NOFOLLOW,
90
+ 0o600,
91
+ dir_fd=parent_fd,
92
+ )
93
+ except OSError as error:
94
+ if error.errno in (errno.ELOOP, errno.ENOTDIR):
95
+ fail(f"refusing symbolic link in managed path: {target}")
96
+ raise
97
+ if not stat.S_ISREG(os.fstat(file_fd).st_mode):
98
+ fail(f"managed path is not a file: {target}")
99
+ os.fchmod(file_fd, 0o600)
100
+ body = sys.stdin.buffer.read()
101
+ offset = 0
102
+ while offset < len(body):
103
+ offset += os.write(file_fd, body[offset:])
104
+ os.fchmod(file_fd, 0o600)
105
+ finally:
106
+ if file_fd is not None:
107
+ os.close(file_fd)
108
+ os.close(parent_fd)
109
+
110
+
111
+ def managed_parent(target):
112
+ parent, name = os.path.split(os.path.normpath(target))
113
+ if not name or name in (".", ".."):
114
+ fail(f"managed path has no valid name: {target}")
115
+ return open_directory(parent, False), name
116
+
117
+
118
+ def directory_stat(parent_fd, name, target):
119
+ try:
120
+ value = os.stat(name, dir_fd=parent_fd, follow_symlinks=False)
121
+ except FileNotFoundError:
122
+ return None
123
+ if stat.S_ISLNK(value.st_mode):
124
+ fail(f"refusing symbolic link in managed path: {target}")
125
+ if not stat.S_ISDIR(value.st_mode):
126
+ fail(f"managed path is not a directory: {target}")
127
+ return value
128
+
129
+
130
+ def validate_directory(target):
131
+ directory_fd = open_directory(target, False)
132
+ try:
133
+ if not stat.S_ISDIR(os.fstat(directory_fd).st_mode):
134
+ fail(f"managed path is not a directory: {target}")
135
+ finally:
136
+ os.close(directory_fd)
137
+
138
+
139
+ def create_temp_directory(final_target):
140
+ parent_fd, final_name = managed_parent(final_target)
141
+ try:
142
+ for _attempt in range(100):
143
+ name = f"t{secrets.token_hex(31)}{secrets.choice('0123456789abcdef')}"
144
+ try:
145
+ os.mkdir(name, 0o700, dir_fd=parent_fd)
146
+ except FileExistsError:
147
+ continue
148
+ directory_fd = os.open(name, DIRECTORY_FLAGS, dir_fd=parent_fd)
149
+ try:
150
+ os.fchmod(directory_fd, 0o700)
151
+ finally:
152
+ os.close(directory_fd)
153
+ return os.path.join(os.path.dirname(os.path.normpath(final_target)), name)
154
+ fail(f"unable to allocate temporary runtime directory for {final_target}")
155
+ finally:
156
+ os.close(parent_fd)
157
+
158
+
159
+ def remove_directory_contents(directory_fd):
160
+ try:
161
+ names = os.listdir(directory_fd)
162
+ except FileNotFoundError:
163
+ return
164
+ for name in names:
165
+ try:
166
+ value = os.stat(name, dir_fd=directory_fd, follow_symlinks=False)
167
+ except FileNotFoundError:
168
+ continue
169
+ if stat.S_ISDIR(value.st_mode):
170
+ try:
171
+ child_fd = os.open(name, DIRECTORY_FLAGS, dir_fd=directory_fd)
172
+ except FileNotFoundError:
173
+ continue
174
+ except OSError as error:
175
+ if error.errno in (errno.ELOOP, errno.ENOTDIR):
176
+ fail(f"temporary runtime directory changed type: {name}")
177
+ raise
178
+ try:
179
+ remove_directory_contents(child_fd)
180
+ finally:
181
+ os.close(child_fd)
182
+ try:
183
+ os.rmdir(name, dir_fd=directory_fd)
184
+ except FileNotFoundError:
185
+ continue
186
+ else:
187
+ try:
188
+ os.unlink(name, dir_fd=directory_fd)
189
+ except FileNotFoundError:
190
+ continue
191
+ except IsADirectoryError:
192
+ fail(f"temporary runtime entry changed type: {name}")
193
+
194
+
195
+ def remove_tree_entry(parent_fd, name, target):
196
+ try:
197
+ directory_fd = os.open(name, DIRECTORY_FLAGS, dir_fd=parent_fd)
198
+ except FileNotFoundError:
199
+ return
200
+ except OSError as error:
201
+ if error.errno in (errno.ELOOP, errno.ENOTDIR):
202
+ fail(f"refusing symbolic link or non-directory temporary runtime: {target}")
203
+ raise
204
+ try:
205
+ remove_directory_contents(directory_fd)
206
+ finally:
207
+ os.close(directory_fd)
208
+ try:
209
+ os.rmdir(name, dir_fd=parent_fd)
210
+ except FileNotFoundError:
211
+ return
212
+
213
+
214
+ def remove_tree(target):
215
+ parent_fd, name = managed_parent(target)
216
+ try:
217
+ remove_tree_entry(parent_fd, name, target)
218
+ finally:
219
+ os.close(parent_fd)
220
+
221
+
222
+ def cleanup_temp_directories(parent_target, stale_before_ms):
223
+ parent_fd = open_directory(parent_target, False)
224
+ try:
225
+ for name in os.listdir(parent_fd):
226
+ if re.fullmatch(r"t[0-9a-f]{63}", name) is None:
227
+ continue
228
+ target = os.path.join(parent_target, name)
229
+ try:
230
+ value = os.stat(name, dir_fd=parent_fd, follow_symlinks=False)
231
+ except FileNotFoundError:
232
+ continue
233
+ if stat.S_ISLNK(value.st_mode):
234
+ fail(f"refusing symbolic link temporary runtime: {target}")
235
+ if not stat.S_ISDIR(value.st_mode):
236
+ fail(f"temporary runtime is not a directory: {target}")
237
+ if value.st_mtime * 1000 >= stale_before_ms:
238
+ continue
239
+ remove_tree_entry(parent_fd, name, target)
240
+ finally:
241
+ os.close(parent_fd)
242
+
243
+
244
+ def rename_no_replace(parent_fd, source_name, target_name, platform_name):
245
+ libc = ctypes.CDLL(None, use_errno=True)
246
+ source = os.fsencode(source_name)
247
+ target = os.fsencode(target_name)
248
+ if platform_name == "darwin":
249
+ rename = libc.renameatx_np
250
+ rename.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint]
251
+ rename.restype = ctypes.c_int
252
+ result = rename(parent_fd, source, parent_fd, target, 0x00000004)
253
+ elif platform_name.startswith("linux") and hasattr(libc, "renameat2"):
254
+ rename = libc.renameat2
255
+ rename.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint]
256
+ rename.restype = ctypes.c_int
257
+ result = rename(parent_fd, source, parent_fd, target, 1)
258
+ else:
259
+ fail(f"unsupported atomic publish platform: {platform_name}")
260
+ if result == 0:
261
+ return True
262
+ error_number = ctypes.get_errno()
263
+ if error_number in (errno.EEXIST, errno.ENOTEMPTY):
264
+ return False
265
+ raise OSError(error_number, os.strerror(error_number))
266
+
267
+
268
+ def publish_directory(temp_target, final_target, platform_name):
269
+ temp_parent = os.path.dirname(os.path.normpath(temp_target))
270
+ final_parent = os.path.dirname(os.path.normpath(final_target))
271
+ if temp_parent != final_parent:
272
+ fail("temporary and final runtime directories must share a parent")
273
+ parent_fd = open_directory(final_parent, False)
274
+ try:
275
+ temp_name = os.path.basename(temp_target)
276
+ final_name = os.path.basename(final_target)
277
+ directory_stat(parent_fd, temp_name, temp_target)
278
+ if rename_no_replace(parent_fd, temp_name, final_name, platform_name):
279
+ return "published"
280
+ directory_stat(parent_fd, final_name, final_target)
281
+ return "exists"
282
+ finally:
283
+ os.close(parent_fd)
284
+
285
+
286
+ try:
287
+ operation, target = sys.argv[1:3]
288
+ if operation == "ensure-directory":
289
+ ensure_directory(target)
290
+ elif operation == "write-file":
291
+ write_file(target)
292
+ elif operation == "validate-directory":
293
+ validate_directory(target)
294
+ elif operation == "create-temp-directory":
295
+ sys.stdout.write(create_temp_directory(target))
296
+ elif operation == "remove-tree":
297
+ remove_tree(target)
298
+ elif operation == "cleanup-temp-directories":
299
+ cleanup_temp_directories(target, int(sys.argv[3]))
300
+ elif operation == "publish-directory":
301
+ platform_name = sys.argv[4] if len(sys.argv) > 4 and sys.argv[4] else sys.platform
302
+ sys.stdout.write(publish_directory(target, sys.argv[3], platform_name))
303
+ else:
304
+ fail(f"unsupported secure filesystem operation: {operation}")
305
+ except BaseException as error:
306
+ sys.stderr.write(f"{error}\n")
307
+ raise SystemExit(1)
308
+ `;
309
+
310
+ function packageRoot() {
311
+ return path.resolve(__dirname, "..", "..");
312
+ }
313
+
314
+ function readPackageVersion(root) {
315
+ const packageJsonPath = path.join(root, "package.json");
316
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
317
+ return packageJson.version;
318
+ }
319
+
320
+ function maybePrintNodeHandledOutput(commandName, args, version) {
321
+ if (commandName !== "kagent") {
322
+ return false;
323
+ }
324
+ const versionOutput = versionOutputTarget(args);
325
+ if (versionOutput === null) {
326
+ return false;
327
+ }
328
+ const body = `${JSON.stringify({ version }, null, 2)}\n`;
329
+ if (versionOutput) {
330
+ fs.writeFileSync(versionOutput, body, { encoding: "utf8" });
331
+ } else {
332
+ process.stdout.write(body);
333
+ }
334
+ return true;
335
+ }
336
+
337
+ function versionOutputTarget(args) {
338
+ if (args.length === 1 && args[0] === "--version") {
339
+ return "";
340
+ }
341
+ if (args.length === 3 && args[0] === "--version" && args[1] === "--output") {
342
+ return args[2];
343
+ }
344
+ if (args.length === 3 && args[0] === "--output" && args[2] === "--version") {
345
+ return args[1];
346
+ }
347
+ return null;
348
+ }
349
+
350
+ function cacheRoot(env = process.env) {
351
+ if (env.KAGENT_NODE_VENV) {
352
+ return path.resolve(env.KAGENT_NODE_VENV);
353
+ }
354
+ return kagentCachePath("npm-python", env);
355
+ }
356
+
357
+ function metadataCacheRoot(env = process.env) {
358
+ return path.dirname(kagentCachePath("npm-python", env));
359
+ }
360
+
361
+ function updateStatePath(env = process.env) {
362
+ return path.join(metadataCacheRoot(env), "npm-self-update.json");
363
+ }
364
+
365
+ function rejectSymlinks(targetPath) {
366
+ const resolved = path.resolve(targetPath);
367
+ const parsed = path.parse(resolved);
368
+ const parts = resolved.slice(parsed.root.length).split(path.sep).filter(Boolean);
369
+ let current = parsed.root;
370
+ for (const part of parts) {
371
+ current = path.join(current, part);
372
+ let stat;
373
+ try {
374
+ stat = fs.lstatSync(current);
375
+ } catch (error) {
376
+ if (error.code === "ENOENT") {
377
+ return;
378
+ }
379
+ throw error;
380
+ }
381
+ if (stat.isSymbolicLink()) {
382
+ throw new Error(`refusing symbolic link in managed path: ${current}`);
383
+ }
384
+ }
385
+ }
386
+
387
+ function ensurePrivateDirectory(directory) {
388
+ const resolved = path.resolve(directory);
389
+ runSecureFilesystemOperation("ensure-directory", resolved);
390
+ return directory;
391
+ }
392
+
393
+ function ensureCacheRoot(env = process.env) {
394
+ if (env.KAGENT_NODE_VENV) {
395
+ return ensurePrivateDirectory(path.resolve(env.KAGENT_NODE_VENV));
396
+ }
397
+ const home = ensurePrivateDirectory(resolveKagentHome(env));
398
+ const cache = ensurePrivateDirectory(path.join(home, "cache"));
399
+ return ensurePrivateDirectory(path.join(cache, "npm-python"));
400
+ }
401
+
402
+ function ensureMetadataCacheRoot(env = process.env) {
403
+ const home = ensurePrivateDirectory(resolveKagentHome(env));
404
+ return ensurePrivateDirectory(path.join(home, "cache"));
405
+ }
406
+
407
+ function writePrivateFile(filePath, body) {
408
+ runSecureFilesystemOperation("write-file", path.resolve(filePath), body);
409
+ }
410
+
411
+ function assertSupportedPlatform(platform = process.platform) {
412
+ if (platform !== "darwin" && platform !== "linux") {
413
+ throw new Error(`kagent npm Python runtime supports only macOS and Linux, not ${platform}`);
414
+ }
415
+ }
416
+
417
+ function runSecureFilesystemOperation(operation, targetPath, body = "", extraArgs = []) {
418
+ assertSupportedPlatform();
419
+ const python = findPython();
420
+ const result = childProcess.spawnSync(
421
+ python,
422
+ ["-c", SECURE_FILESYSTEM_HELPER, operation, targetPath].concat(extraArgs),
423
+ {
424
+ encoding: "utf8",
425
+ env: process.env,
426
+ input: body,
427
+ stdio: ["pipe", "pipe", "pipe"]
428
+ }
429
+ );
430
+ if (result.error) {
431
+ throw result.error;
432
+ }
433
+ if (result.status !== 0) {
434
+ const detail = String(result.stderr || result.stdout || "").trim();
435
+ const suffix = detail ? `: ${detail.slice(-4000)}` : "";
436
+ throw new Error(
437
+ `secure filesystem ${operation} failed for ${targetPath}${suffix}`
438
+ );
439
+ }
440
+ return String(result.stdout || "").trim();
441
+ }
442
+
443
+ function privateFileStat(filePath) {
444
+ let stat;
445
+ try {
446
+ stat = fs.lstatSync(filePath);
447
+ } catch (error) {
448
+ if (error.code === "ENOENT") {
449
+ return null;
450
+ }
451
+ throw error;
452
+ }
453
+ if (stat.isSymbolicLink()) {
454
+ throw new Error(`refusing symbolic link in managed path: ${filePath}`);
455
+ }
456
+ if (!stat.isFile()) {
457
+ throw new Error(`managed path is not a file: ${filePath}`);
458
+ }
459
+ return stat;
460
+ }
461
+
462
+ function validatePrivateDirectory(directory) {
463
+ runSecureFilesystemOperation("validate-directory", path.resolve(directory));
464
+ }
465
+
466
+ function createTempRuntime(finalDirectory) {
467
+ return runSecureFilesystemOperation("create-temp-directory", path.resolve(finalDirectory));
468
+ }
469
+
470
+ function removeTempRuntime(directory) {
471
+ runSecureFilesystemOperation("remove-tree", path.resolve(directory));
472
+ }
473
+
474
+ function cleanupStaleTempRuntimes(parentDirectory, staleMs = TEMP_RUNTIME_STALE_MS) {
475
+ runSecureFilesystemOperation(
476
+ "cleanup-temp-directories",
477
+ path.resolve(parentDirectory),
478
+ "",
479
+ [String(Date.now() - staleMs)]
480
+ );
481
+ }
482
+
483
+ function publishRuntime(tempDirectory, finalDirectory, platformOverride = "") {
484
+ return runSecureFilesystemOperation(
485
+ "publish-directory",
486
+ path.resolve(tempDirectory),
487
+ "",
488
+ [path.resolve(finalDirectory), platformOverride]
489
+ );
490
+ }
491
+
492
+ function candidatePythons() {
493
+ const configured = process.env.KAGENT_PYTHON ? [process.env.KAGENT_PYTHON] : [];
494
+ return configured.concat(["python3", "python"]);
495
+ }
496
+
497
+ function commandWorks(command, args) {
498
+ const result = childProcess.spawnSync(command, args, {
499
+ encoding: "utf8",
500
+ stdio: "ignore"
501
+ });
502
+ return result.status === 0;
503
+ }
504
+
505
+ function findPython() {
506
+ for (const command of candidatePythons()) {
507
+ if (commandWorks(command, ["-c", "import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)"])) {
508
+ return command;
509
+ }
510
+ }
511
+ throw new Error("kagent requires Python 3.9+. Install python3 or set KAGENT_PYTHON.");
512
+ }
513
+
514
+ function runChecked(command, args, options) {
515
+ const result = childProcess.spawnSync(command, args, {
516
+ cwd: options.cwd,
517
+ env: process.env,
518
+ encoding: "utf8",
519
+ stdio: options.stdio || "inherit"
520
+ });
521
+ if (result.error) {
522
+ throw result.error;
523
+ }
524
+ if (result.status !== 0) {
525
+ const detail = [result.stdout, result.stderr]
526
+ .filter((value) => typeof value === "string" && value.trim())
527
+ .join("\n")
528
+ .trim();
529
+ const suffix = detail ? `\n${detail.slice(-4000)}` : "";
530
+ throw new Error(
531
+ `${command} ${args.join(" ")} failed with exit code ${result.status}${suffix}`
532
+ );
533
+ }
534
+ }
535
+
536
+ function readUpdateState(env = process.env) {
537
+ const statePath = updateStatePath(env);
538
+ rejectSymlinks(statePath);
539
+ if (!privateFileStat(statePath)) {
540
+ return null;
541
+ }
542
+ try {
543
+ const state = JSON.parse(fs.readFileSync(statePath, "utf8"));
544
+ return state && typeof state === "object" ? state : null;
545
+ } catch (_error) {
546
+ return null;
547
+ }
548
+ }
549
+
550
+ function writeUpdateState(state, env = process.env) {
551
+ const statePath = path.join(ensureMetadataCacheRoot(env), "npm-self-update.json");
552
+ writePrivateFile(statePath, `${JSON.stringify(state, null, 2)}\n`);
553
+ }
554
+
555
+ function venvPythonPath(venvDir) {
556
+ if (process.platform === "win32") {
557
+ return path.join(venvDir, "Scripts", "python.exe");
558
+ }
559
+ return path.join(venvDir, "bin", "python");
560
+ }
561
+
562
+ function markerPath(venvDir) {
563
+ return path.join(venvDir, ".kagent-node-install.json");
564
+ }
565
+
566
+ function installMarker(version, dependencyFingerprint, pythonIdentityHash) {
567
+ return {
568
+ schema: 1,
569
+ dependencyHash: dependencyFingerprint,
570
+ pythonIdentityHash,
571
+ createdFromVersion: version
572
+ };
573
+ }
574
+
575
+ function sha256(value) {
576
+ return crypto.createHash("sha256").update(value).digest("hex");
577
+ }
578
+
579
+ function projectTable(pyproject) {
580
+ const match = pyproject.match(/^\s*\[project\]\s*(?:#.*)?$/m);
581
+ if (!match) {
582
+ throw new Error("pyproject.toml does not declare [project]");
583
+ }
584
+ const bodyStart = match.index + match[0].length;
585
+ const remaining = pyproject.slice(bodyStart);
586
+ const nextTable = remaining.search(/^\s*\[/m);
587
+ return nextTable === -1 ? remaining : remaining.slice(0, nextTable);
588
+ }
589
+
590
+ function tomlString(value) {
591
+ if (value.startsWith('"')) {
592
+ return JSON.parse(value);
593
+ }
594
+ return value.slice(1, -1);
595
+ }
596
+
597
+ function projectString(project, key) {
598
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
599
+ const match = project.match(
600
+ new RegExp(`^\\s*${escapedKey}\\s*=\\s*("(?:\\\\.|[^"\\\\])*"|'[^']*')\\s*(?:#.*)?$`, "m")
601
+ );
602
+ if (!match) {
603
+ throw new Error(`pyproject.toml [project] does not declare ${key}`);
604
+ }
605
+ return tomlString(match[1]);
606
+ }
607
+
608
+ function projectStringArray(project, key) {
609
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
610
+ const assignment = new RegExp(`^\\s*${escapedKey}\\s*=\\s*\\[`, "m").exec(project);
611
+ if (!assignment) {
612
+ throw new Error(`pyproject.toml [project] does not declare ${key}`);
613
+ }
614
+ const values = [];
615
+ let index = assignment.index + assignment[0].length;
616
+ while (index < project.length) {
617
+ const character = project[index];
618
+ if (character === "]") {
619
+ return values;
620
+ }
621
+ if (character === "#") {
622
+ while (index < project.length && project[index] !== "\n") index += 1;
623
+ continue;
624
+ }
625
+ if (character === '"' || character === "'") {
626
+ const quote = character;
627
+ const start = index;
628
+ index += 1;
629
+ while (index < project.length) {
630
+ if (quote === '"' && project[index] === "\\") {
631
+ index += 2;
632
+ continue;
633
+ }
634
+ if (project[index] === quote) {
635
+ index += 1;
636
+ values.push(tomlString(project.slice(start, index)));
637
+ break;
638
+ }
639
+ index += 1;
640
+ }
641
+ if (project[index - 1] !== quote) {
642
+ throw new Error(`unterminated string in [project].${key}`);
643
+ }
644
+ continue;
645
+ }
646
+ index += 1;
647
+ }
648
+ throw new Error(`unterminated array in [project].${key}`);
649
+ }
650
+
651
+ function dependencyHash(root) {
652
+ const pyproject = fs.readFileSync(path.join(root, "pyproject.toml"), "utf8");
653
+ const project = projectTable(pyproject);
654
+ return sha256(JSON.stringify({
655
+ requiresPython: projectString(project, "requires-python"),
656
+ dependencies: projectStringArray(project, "dependencies")
657
+ }));
658
+ }
659
+
660
+ function readMarker(venvDir) {
661
+ const pathToMarker = markerPath(venvDir);
662
+ if (!privateFileStat(pathToMarker)) {
663
+ return null;
664
+ }
665
+ try {
666
+ return JSON.parse(fs.readFileSync(pathToMarker, "utf8"));
667
+ } catch (_error) {
668
+ return null;
669
+ }
670
+ }
671
+
672
+ function markerMatches(actual, expected) {
673
+ return Boolean(actual) &&
674
+ actual.schema === expected.schema &&
675
+ actual.dependencyHash === expected.dependencyHash &&
676
+ actual.pythonIdentityHash === expected.pythonIdentityHash;
677
+ }
678
+
679
+ function writeMarker(venvDir, marker) {
680
+ writePrivateFile(markerPath(venvDir), `${JSON.stringify(marker, null, 2)}\n`);
681
+ }
682
+
683
+ function pythonRuntimeIdentity(python) {
684
+ const result = childProcess.spawnSync(
685
+ python,
686
+ ["-c", "import json, os, platform, sys, sysconfig; print(json.dumps({'implementation': sys.implementation.name, 'major': sys.version_info[0], 'minor': sys.version_info[1], 'cacheTag': sys.implementation.cache_tag, 'soabi': sysconfig.get_config_var('SOABI'), 'machine': platform.machine(), 'executable': os.path.realpath(sys.executable), 'prefix': os.path.realpath(sys.prefix), 'basePrefix': os.path.realpath(sys.base_prefix), 'execPrefix': os.path.realpath(sys.exec_prefix), 'baseExecPrefix': os.path.realpath(sys.base_exec_prefix)}))"],
687
+ { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
688
+ );
689
+ if (result.error) {
690
+ throw result.error;
691
+ }
692
+ if (result.status !== 0) {
693
+ throw new Error(`failed to identify Python runtime: ${String(result.stderr || "").trim()}`);
694
+ }
695
+ const identity = JSON.parse(result.stdout);
696
+ if (!/^[A-Za-z0-9_-]+$/.test(identity.implementation) ||
697
+ identity.implementation.length > 24 ||
698
+ !Number.isInteger(identity.major) || !Number.isInteger(identity.minor) ||
699
+ identity.major < 0 || identity.minor < 0) {
700
+ throw new Error("Python runtime returned an invalid identity");
701
+ }
702
+ return identity;
703
+ }
704
+
705
+ function safeRuntimeComponent(value, label, maxLength) {
706
+ if (typeof value !== "string" || value.length === 0 || value.length > maxLength ||
707
+ !/^[A-Za-z0-9_.-]+$/.test(value)) {
708
+ throw new Error(`invalid ${label} runtime component`);
709
+ }
710
+ return value;
711
+ }
712
+
713
+ function runtimeCacheDirectory(cache, identity, platform, arch, dependencyFingerprint) {
714
+ const implementation = safeRuntimeComponent(identity.implementation, "implementation", 24);
715
+ const hostPlatform = safeRuntimeComponent(platform, "platform", 24);
716
+ const hostArch = safeRuntimeComponent(arch, "architecture", 32);
717
+ const identityFingerprint = sha256(JSON.stringify(identity));
718
+ const abi = `${implementation}-${identity.major}.${identity.minor}-${identityFingerprint}`;
719
+ return path.join(cache, abi, `${hostPlatform}-${hostArch}`, dependencyFingerprint);
720
+ }
721
+
722
+ function runtimeState(venvDir, expectedMarker, pythonWorks = commandWorks) {
723
+ let stat;
724
+ try {
725
+ stat = fs.lstatSync(venvDir);
726
+ } catch (error) {
727
+ if (error.code === "ENOENT") {
728
+ return "missing";
729
+ }
730
+ throw error;
731
+ }
732
+ if (stat.isSymbolicLink()) {
733
+ throw new Error(`refusing symbolic link in managed path: ${venvDir}`);
734
+ }
735
+ if (!stat.isDirectory()) {
736
+ throw new Error(`invalid cached Python runtime: ${venvDir}`);
737
+ }
738
+ validatePrivateDirectory(venvDir);
739
+ const marker = readMarker(venvDir);
740
+ const pythonPath = venvPythonPath(venvDir);
741
+ if (!markerMatches(marker, expectedMarker) ||
742
+ !fs.existsSync(pythonPath) || !pythonWorks(pythonPath, ["-c", "import kagent"])) {
743
+ throw new Error(`invalid cached Python runtime: ${venvDir}`);
744
+ }
745
+ return "valid";
746
+ }
747
+
748
+ function ensureVenv(root, version, options = {}) {
749
+ const platform = options.platform || process.platform;
750
+ assertSupportedPlatform(platform);
751
+ const python = options.python || findPython();
752
+ const identity = options.pythonIdentity || pythonRuntimeIdentity(python);
753
+ const dependencyFingerprint = dependencyHash(root);
754
+ const pythonIdentityHash = sha256(JSON.stringify(identity));
755
+ const cache = options.cacheRoot || ensureCacheRoot();
756
+ const arch = options.arch || process.arch;
757
+ const venvDir = runtimeCacheDirectory(cache, identity, platform, arch, dependencyFingerprint);
758
+ const ensureDirectory = options.ensurePrivateDirectory || ensurePrivateDirectory;
759
+ const checkedRun = options.runChecked || runChecked;
760
+ const markerWriter = options.writeMarker || writeMarker;
761
+ const pythonWorks = options.runtimePythonWorks || commandWorks;
762
+ const expectedMarker = installMarker(version, dependencyFingerprint, pythonIdentityHash);
763
+ ensureDirectory(path.dirname(venvDir));
764
+ if (runtimeState(venvDir, expectedMarker, pythonWorks) === "valid") {
765
+ return venvDir;
766
+ }
767
+
768
+ cleanupStaleTempRuntimes(path.dirname(venvDir), options.tempRuntimeStaleMs);
769
+ if (runtimeState(venvDir, expectedMarker, pythonWorks) === "valid") {
770
+ return venvDir;
771
+ }
772
+ const tempDir = createTempRuntime(venvDir);
773
+ try {
774
+ process.stderr.write(`kagent: preparing Python runtime in ${tempDir}\n`);
775
+ checkedRun(python, ["-m", "venv", tempDir], { cwd: root });
776
+ const tempPython = venvPythonPath(tempDir);
777
+ process.stderr.write("kagent: preparing Python runtime\n");
778
+ checkedRun(
779
+ tempPython,
780
+ ["-m", "pip", "install", "--disable-pip-version-check", "--quiet", root],
781
+ { cwd: root, stdio: "pipe" }
782
+ );
783
+ markerWriter(tempDir, expectedMarker);
784
+ if (runtimeState(tempDir, expectedMarker, pythonWorks) !== "valid") {
785
+ throw new Error(`failed to prepare Python runtime: ${tempDir}`);
786
+ }
787
+ const publishResult = publishRuntime(tempDir, venvDir);
788
+ if (publishResult !== "published" && publishResult !== "exists") {
789
+ throw new Error(`invalid Python runtime publish result: ${publishResult}`);
790
+ }
791
+ if (runtimeState(venvDir, expectedMarker, pythonWorks) !== "valid") {
792
+ throw new Error(`invalid cached Python runtime: ${venvDir}`);
793
+ }
794
+ return venvDir;
795
+ } finally {
796
+ removeTempRuntime(tempDir);
797
+ }
798
+ }
799
+
800
+ function pythonEnvironment(root, env = process.env) {
801
+ const source = path.join(root, "src");
802
+ return Object.assign({}, env, {
803
+ PYTHONPATH: env.PYTHONPATH ? `${source}${path.delimiter}${env.PYTHONPATH}` : source
804
+ });
805
+ }
806
+
807
+ function pythonEntrypointArgs(commandName, args) {
808
+ const code = PYTHON_ENTRYPOINTS[commandName];
809
+ if (!code) {
810
+ throw new Error(`unsupported Python entrypoint: ${commandName}`);
811
+ }
812
+ return ["-c", code, commandName].concat(args || []);
813
+ }
814
+
815
+ function spawnEntrypoint(venvDir, root, commandName, args) {
816
+ const command = venvPythonPath(venvDir);
817
+ const result = childProcess.spawnSync(command, pythonEntrypointArgs(commandName, args), {
818
+ env: pythonEnvironment(root),
819
+ stdio: "inherit"
820
+ });
821
+ if (result.error) {
822
+ throw result.error;
823
+ }
824
+ process.exit(result.status === null ? 1 : result.status);
825
+ }
826
+
827
+ function ensurePythonRuntime() {
828
+ const root = packageRoot();
829
+ const version = readPackageVersion(root);
830
+ const venvDir = ensureVenv(root, version);
831
+ return {
832
+ root,
833
+ version,
834
+ venvDir,
835
+ pythonPath: venvPythonPath(venvDir)
836
+ };
837
+ }
838
+
839
+ function spawnPythonModule(moduleName, args, options) {
840
+ const runtime = ensurePythonRuntime();
841
+ const provided = options || {};
842
+ const spawnOptions = Object.assign(
843
+ { cwd: runtime.root, stdio: ["pipe", "pipe", "pipe"] },
844
+ provided
845
+ );
846
+ spawnOptions.env = pythonEnvironment(
847
+ runtime.root,
848
+ Object.assign({}, process.env, provided.env || {})
849
+ );
850
+ return childProcess.spawn(
851
+ runtime.pythonPath,
852
+ ["-m", moduleName].concat(args || []),
853
+ spawnOptions
854
+ );
855
+ }
856
+
857
+ function runPythonEntrypoint(commandName, args) {
858
+ try {
859
+ const root = packageRoot();
860
+ const version = readPackageVersion(root);
861
+ if (maybePrintNodeHandledOutput(commandName, args, version)) {
862
+ return;
863
+ }
864
+ const venvDir = ensureVenv(root, version);
865
+ spawnEntrypoint(venvDir, root, commandName, args);
866
+ } catch (error) {
867
+ process.stderr.write(`kagent failed to start: ${error.message}\n`);
868
+ process.exit(1);
869
+ }
870
+ }
871
+
872
+ module.exports = {
873
+ ensurePythonRuntime,
874
+ runPythonEntrypoint,
875
+ spawnPythonModule,
876
+ _internals: {
877
+ cacheRoot,
878
+ dependencyHash,
879
+ ensureCacheRoot,
880
+ ensurePrivateDirectory,
881
+ ensureVenv,
882
+ metadataCacheRoot,
883
+ maybePrintNodeHandledOutput,
884
+ pythonEnvironment,
885
+ pythonEntrypointArgs,
886
+ pythonRuntimeIdentity,
887
+ publishRuntime,
888
+ readUpdateState,
889
+ spawnEntrypoint,
890
+ writeUpdateState
891
+ }
892
+ };