@kohala/devkit 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/CONTRIBUTING.md +9 -0
- package/README.md +5 -0
- package/dist/cli/index.js +276 -53
- package/dist/cli/index.js.map +1 -1
- package/docs/DEPLOY.md +18 -0
- package/docs/MANIFEST.md +45 -1
- package/docs/QUICKSTART.md +8 -2
- package/package.json +2 -1
- package/templates/python/README.md +15 -0
- package/templates/python/kohala.json +17 -0
- package/templates/python/skills/_tools.py +71 -0
- package/templates/python/skills/main.py +25 -0
- package/templates/typescript/README.md +17 -0
- package/templates/typescript/kohala.json +18 -0
- package/templates/typescript/package.json +12 -0
- package/templates/typescript/skills/_tools.ts +121 -0
- package/templates/typescript/skills/main.ts +18 -0
- package/templates/typescript/tsconfig.json +11 -0
- package/templates/README.md +0 -40
- package/templates/skills/_tools.py +0 -125
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @kohala/devkit
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
- **TypeScript/JavaScript agents can now be shipped with the CLI.** `kohala
|
|
6
|
+
validate` and `kohala deploy` discover `.ts` / `.js` skill scripts
|
|
7
|
+
alongside `.py` (same extension table the platform uses) and report the
|
|
8
|
+
detected runtime per skill. A Node-lane skill's upload now carries
|
|
9
|
+
`runtimeLanguage` and the manifest's new `dependencies` (npm packages) as
|
|
10
|
+
`scriptDependencies`, so the platform runs its acceptance checks — compile,
|
|
11
|
+
security scan, npm allowlist — when the script is attached instead of
|
|
12
|
+
letting it fail on its first hosted run. A package outside the allowlist is
|
|
13
|
+
reported locally, with the platform's own message, before anything is sent
|
|
14
|
+
(`--allow-unknown-packages` defers to the server). A skill file with an
|
|
15
|
+
extension no runtime can execute is now a validation error for wrap-mode
|
|
16
|
+
agents. Python projects deploy byte-for-byte as before: their payload gains
|
|
17
|
+
no new fields.
|
|
18
|
+
- **Start a TypeScript agent with one command.** `kohala init my-agent
|
|
19
|
+
--language ts` creates a typed `main.ts`, a local `_tools.ts` adapter with
|
|
20
|
+
the same API as the hosted Node SDK, strict TypeScript configuration, and
|
|
21
|
+
declared npm packages. The generated project validates, runs locally through
|
|
22
|
+
the Node lane, and produces a complete dry-run deploy plan without edits.
|
|
23
|
+
|
|
3
24
|
## 0.1.7
|
|
4
25
|
|
|
5
26
|
- **BUG-006 fixed: `kohala deploy --run` no longer misreports every 409 as
|
package/CONTRIBUTING.md
CHANGED
|
@@ -24,6 +24,15 @@ npm test # vitest (unit + CLI e2e)
|
|
|
24
24
|
npm run build # tsup -> dist/
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
## Updating dependencies
|
|
28
|
+
|
|
29
|
+
`devkit/package-lock.json` is committed so CI and local development install the
|
|
30
|
+
same dependency tree. When changing dependencies, run `npm install` from the
|
|
31
|
+
`devkit` directory and commit both `package.json` and `package-lock.json`.
|
|
32
|
+
Use `npm ci` when you only need to install the committed dependency tree.
|
|
33
|
+
CI pins npm 11.6.0 to avoid an npm 10 peer-resolution crash; use the same npm
|
|
34
|
+
version when intentionally regenerating the lockfile.
|
|
35
|
+
|
|
27
36
|
## Ground rules
|
|
28
37
|
|
|
29
38
|
- **Errors fail loudly.** No silent fallbacks, no mock LLM responses, no
|
package/README.md
CHANGED
|
@@ -54,6 +54,11 @@ skill scripts).
|
|
|
54
54
|
}
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
- Skill scripts can be Python (`main.py`) or TypeScript/JavaScript
|
|
58
|
+
(`main.ts`) — the extension picks the runtime, and a TypeScript agent
|
|
59
|
+
declares its npm packages with `"dependencies": ["zod"]` (allowlisted
|
|
60
|
+
packages only). `kohala run --local` executes Python skills; TypeScript
|
|
61
|
+
skills run hosted. See [MANIFEST.md](docs/MANIFEST.md#script-languages).
|
|
57
62
|
- `runtimeMode: "wrap"` executes your script directly and validates its
|
|
58
63
|
output (stdout). Use `llm.complete` from the script SDK to call an LLM
|
|
59
64
|
mid-script (needs `ANTHROPIC_API_KEY` or `GEMINI_API_KEY`).
|
package/dist/cli/index.js
CHANGED
|
@@ -13,6 +13,45 @@ import pc from "picocolors";
|
|
|
13
13
|
|
|
14
14
|
// src/manifest/schema.ts
|
|
15
15
|
import { z } from "zod";
|
|
16
|
+
|
|
17
|
+
// src/manifest/language.ts
|
|
18
|
+
var RUNTIME_LANGUAGES = ["python", "node"];
|
|
19
|
+
var ENTRYPOINT_EXTENSIONS = {
|
|
20
|
+
python: [".py"],
|
|
21
|
+
node: [".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"]
|
|
22
|
+
};
|
|
23
|
+
var SCRIPT_FILE_EXTENSIONS = Object.values(ENTRYPOINT_EXTENSIONS).flat();
|
|
24
|
+
var LANGUAGE_LABEL = {
|
|
25
|
+
python: "Python",
|
|
26
|
+
node: "TypeScript/JavaScript"
|
|
27
|
+
};
|
|
28
|
+
function languageForEntrypointFile(filename) {
|
|
29
|
+
const lower = (filename || "").toLowerCase();
|
|
30
|
+
for (const language of RUNTIME_LANGUAGES) {
|
|
31
|
+
if (ENTRYPOINT_EXTENSIONS[language].some((ext) => lower.endsWith(ext))) {
|
|
32
|
+
return language;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function skillLanguages(skills) {
|
|
38
|
+
return Object.entries(skills).map(([name, scriptFilename]) => ({
|
|
39
|
+
name,
|
|
40
|
+
scriptFilename,
|
|
41
|
+
language: languageForEntrypointFile(scriptFilename) ?? "python"
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
function summarizeLanguages(skills) {
|
|
45
|
+
const languages = new Set(skillLanguages(skills).map((skill) => skill.language));
|
|
46
|
+
if (languages.size === 0) return "none";
|
|
47
|
+
if (languages.size > 1) return "mixed";
|
|
48
|
+
return [...languages][0];
|
|
49
|
+
}
|
|
50
|
+
function hasNodeSkill(skills) {
|
|
51
|
+
return skillLanguages(skills).some((skill) => skill.language === "node");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/manifest/schema.ts
|
|
16
55
|
var BILLING_PERIODS = ["day", "week", "month"];
|
|
17
56
|
var shapeValidatorSchema = z.object({
|
|
18
57
|
type: z.literal("shape"),
|
|
@@ -53,6 +92,8 @@ var nameSchema = z.string().min(1).max(64).regex(
|
|
|
53
92
|
/^[a-z0-9][a-z0-9_-]*$/i,
|
|
54
93
|
"must start with a letter or digit and contain only letters, digits, '-' and '_'"
|
|
55
94
|
);
|
|
95
|
+
var skillScriptSchema = z.string().min(1);
|
|
96
|
+
var dependenciesSchema = z.array(z.string().min(1).max(214)).max(50).default([]);
|
|
56
97
|
var manifestSchema = z.object({
|
|
57
98
|
name: nameSchema,
|
|
58
99
|
/** The agent's mission text (platform: agentCharter). */
|
|
@@ -67,13 +108,43 @@ var manifestSchema = z.object({
|
|
|
67
108
|
* "llm" — run a real tool-use loop against the developer's own LLM key.
|
|
68
109
|
*/
|
|
69
110
|
runtimeMode: z.enum(["wrap", "llm"]),
|
|
70
|
-
/**
|
|
71
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Map of skill name -> script filename in `skills/`, e.g.
|
|
113
|
+
* {"collect": "main.py"} or {"collect": "main.ts"}. The file extension
|
|
114
|
+
* decides which runtime the platform executes the script in.
|
|
115
|
+
*/
|
|
116
|
+
skills: z.record(z.string().min(1), skillScriptSchema).default({}),
|
|
117
|
+
/**
|
|
118
|
+
* npm packages the TypeScript/JavaScript skills import beyond Node's
|
|
119
|
+
* standard library. Only packages on the platform's allowlist are
|
|
120
|
+
* installed — `kohala validate` and `kohala deploy` refuse anything else
|
|
121
|
+
* offline, before the deploy request. Python skills never use this: pip
|
|
122
|
+
* packages are not installable from the CLI deploy path.
|
|
123
|
+
*/
|
|
124
|
+
dependencies: dependenciesSchema,
|
|
72
125
|
/** Cron expression. Used only on deploy; local runs are always manual. */
|
|
73
126
|
schedule: z.string().min(1).optional(),
|
|
74
127
|
caps: capsSchema,
|
|
75
128
|
validators: z.array(validatorSchema).default([])
|
|
76
129
|
}).strict();
|
|
130
|
+
function crossFieldProblems(manifest) {
|
|
131
|
+
const problems = [];
|
|
132
|
+
if (manifest.runtimeMode === "wrap") {
|
|
133
|
+
for (const [name, filename] of Object.entries(manifest.skills)) {
|
|
134
|
+
if (languageForEntrypointFile(filename) === null) {
|
|
135
|
+
problems.push(
|
|
136
|
+
`skills.${name}: "${filename}" must be a script the platform can run in wrap mode \u2014 one of: ${SCRIPT_FILE_EXTENSIONS.join(", ")}`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (manifest.dependencies.length > 0 && !hasNodeSkill(manifest.skills)) {
|
|
142
|
+
problems.push(
|
|
143
|
+
`dependencies: declares npm package(s) (${manifest.dependencies.join(", ")}) but no skill is a TypeScript/JavaScript entrypoint \u2014 only those install npm packages (hint: use a ${ENTRYPOINT_EXTENSIONS.node.join(" / ")} skill script, or remove "dependencies")`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return problems;
|
|
147
|
+
}
|
|
77
148
|
|
|
78
149
|
// src/cli/init.ts
|
|
79
150
|
function templatesDir() {
|
|
@@ -83,9 +154,11 @@ function templatesDir() {
|
|
|
83
154
|
path.resolve(here, "..", "..", "..", "templates")
|
|
84
155
|
];
|
|
85
156
|
for (const candidate of candidates) {
|
|
86
|
-
if (fs.existsSync(path.join(candidate, "kohala.json"))) return candidate;
|
|
157
|
+
if (fs.existsSync(path.join(candidate, "python", "kohala.json"))) return candidate;
|
|
87
158
|
}
|
|
88
|
-
throw new Error(
|
|
159
|
+
throw new Error(
|
|
160
|
+
`Could not locate the devkit templates directory (looked in: ${candidates.join(", ")})`
|
|
161
|
+
);
|
|
89
162
|
}
|
|
90
163
|
function copyTemplates(sourceDir, targetDir, agentName) {
|
|
91
164
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
@@ -101,7 +174,7 @@ function copyTemplates(sourceDir, targetDir, agentName) {
|
|
|
101
174
|
}
|
|
102
175
|
}
|
|
103
176
|
function registerInitCommand(program2) {
|
|
104
|
-
program2.command("init").argument("<name>", "agent name (also the directory name)").description("Scaffold a new agent: kohala.json, a working skill, and the local SDK").action((name) => {
|
|
177
|
+
program2.command("init").argument("<name>", "agent name (also the directory name)").option("--language <language>", "scaffold language: python or ts", "python").description("Scaffold a new agent: kohala.json, a working skill, and the local SDK").action((name, options) => {
|
|
105
178
|
const nameCheck = manifestSchema.shape.name.safeParse(name);
|
|
106
179
|
if (!nameCheck.success) {
|
|
107
180
|
throw new Error(
|
|
@@ -112,7 +185,12 @@ function registerInitCommand(program2) {
|
|
|
112
185
|
if (fs.existsSync(targetDir)) {
|
|
113
186
|
throw new Error(`${targetDir} already exists \u2014 pick a new name or remove the directory.`);
|
|
114
187
|
}
|
|
115
|
-
|
|
188
|
+
const language = options.language.toLowerCase();
|
|
189
|
+
const template = language === "ts" || language === "typescript" ? "typescript" : language === "py" || language === "python" ? "python" : null;
|
|
190
|
+
if (!template) {
|
|
191
|
+
throw new Error(`Unsupported language "${options.language}". Choose "python" or "ts".`);
|
|
192
|
+
}
|
|
193
|
+
copyTemplates(path.join(templatesDir(), template), targetDir, name);
|
|
116
194
|
console.log(pc.green(`Created agent "${name}" in ${targetDir}`));
|
|
117
195
|
console.log("");
|
|
118
196
|
console.log("Next steps:");
|
|
@@ -143,7 +221,8 @@ var FIELD_HINTS = {
|
|
|
143
221
|
charter: "write the agent's mission as a non-empty string",
|
|
144
222
|
toolAllowlist: 'list allowed tool names, e.g. ["s3.put", "http.post_json"]',
|
|
145
223
|
runtimeMode: 'must be "wrap" (script wrapper) or "llm" (tool-use loop)',
|
|
146
|
-
skills: 'map skill name to script filename, e.g. {"collect": "main.py"}',
|
|
224
|
+
skills: 'map skill name to script filename, e.g. {"collect": "main.py"} or {"collect": "main.ts"}',
|
|
225
|
+
dependencies: 'list npm packages the TypeScript/JavaScript skills import, e.g. ["zod"]',
|
|
147
226
|
schedule: 'use a cron expression like "0 9 * * *" (only used on deploy)',
|
|
148
227
|
caps: "set caps.perRunTokens and caps.perDayTokens as positive integers",
|
|
149
228
|
validators: 'each validator needs a "type" of "shape", "freshness" or "invariant"'
|
|
@@ -187,6 +266,10 @@ function loadManifest(agentDir) {
|
|
|
187
266
|
if (!result.success) {
|
|
188
267
|
throw new ManifestError(`${filePath} failed validation`, formatManifestIssues(result.error));
|
|
189
268
|
}
|
|
269
|
+
const crossField = crossFieldProblems(result.data);
|
|
270
|
+
if (crossField.length > 0) {
|
|
271
|
+
throw new ManifestError(`${filePath} failed validation`, crossField);
|
|
272
|
+
}
|
|
190
273
|
return result.data;
|
|
191
274
|
}
|
|
192
275
|
|
|
@@ -399,49 +482,112 @@ function unknownToolIds(tools) {
|
|
|
399
482
|
return tools.filter((t) => !KNOWN_TOOL_IDS.has(t));
|
|
400
483
|
}
|
|
401
484
|
|
|
485
|
+
// src/manifest/npm-packages.ts
|
|
486
|
+
var CLI_FRAMEWORK = "custom";
|
|
487
|
+
var ALLOWED_NPM_PACKAGES = [
|
|
488
|
+
"zod",
|
|
489
|
+
"openai",
|
|
490
|
+
"@anthropic-ai/sdk",
|
|
491
|
+
"ai",
|
|
492
|
+
"@ai-sdk/openai",
|
|
493
|
+
"@ai-sdk/anthropic",
|
|
494
|
+
"date-fns",
|
|
495
|
+
"cheerio",
|
|
496
|
+
"js-yaml"
|
|
497
|
+
];
|
|
498
|
+
function normalizeNpmName(name) {
|
|
499
|
+
return name.trim().toLowerCase();
|
|
500
|
+
}
|
|
501
|
+
function rejectedNpmPackages(dependencies) {
|
|
502
|
+
const allowed = new Set(ALLOWED_NPM_PACKAGES.map(normalizeNpmName));
|
|
503
|
+
const seen = /* @__PURE__ */ new Set();
|
|
504
|
+
const rejected = [];
|
|
505
|
+
for (const dependency of dependencies) {
|
|
506
|
+
const normalized = normalizeNpmName(dependency);
|
|
507
|
+
if (!normalized || allowed.has(normalized) || seen.has(normalized)) continue;
|
|
508
|
+
seen.add(normalized);
|
|
509
|
+
rejected.push(dependency.trim());
|
|
510
|
+
}
|
|
511
|
+
return rejected;
|
|
512
|
+
}
|
|
513
|
+
function npmAllowlistMessage(rejected) {
|
|
514
|
+
return `unsupported npm dependencies for framework "${CLI_FRAMEWORK}": ${rejected.join(", ")}. Supported packages: ${ALLOWED_NPM_PACKAGES.map(normalizeNpmName).sort().join(", ")}`;
|
|
515
|
+
}
|
|
516
|
+
|
|
402
517
|
// src/cli/validate.ts
|
|
403
518
|
function registerValidateCommand(program2) {
|
|
404
519
|
program2.command("validate").argument("<agent>", "agent directory (containing kohala.json)").option(
|
|
405
520
|
"--allow-unknown-tools",
|
|
406
521
|
"do not fail on tool ids missing from the bundled catalog snapshot"
|
|
407
|
-
).
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
522
|
+
).option(
|
|
523
|
+
"--allow-unknown-packages",
|
|
524
|
+
"do not fail on npm packages missing from the bundled allowlist snapshot"
|
|
525
|
+
).description("Validate an agent's kohala.json and print precise errors").action(
|
|
526
|
+
(agent, options) => {
|
|
527
|
+
const agentDir = path3.resolve(process.cwd(), agent);
|
|
528
|
+
try {
|
|
529
|
+
const manifest = loadManifest(agentDir);
|
|
530
|
+
const unknown = unknownToolIds(manifest.toolAllowlist);
|
|
531
|
+
if (unknown.length > 0) {
|
|
532
|
+
const msg = `toolAllowlist contains tool id(s) not in the Kohala tool catalog: ` + unknown.join(", ");
|
|
533
|
+
if (options.allowUnknownTools) {
|
|
534
|
+
console.warn(pc2.yellow(`warning: ${msg}`));
|
|
535
|
+
} else {
|
|
536
|
+
console.error(pc2.red(msg));
|
|
537
|
+
console.error(
|
|
538
|
+
pc2.dim(
|
|
539
|
+
" If these are new platform tools the bundled snapshot doesn't know yet, re-run with --allow-unknown-tools (the deploy endpoint will still verify against the live catalog)."
|
|
540
|
+
)
|
|
541
|
+
);
|
|
542
|
+
process.exitCode = 1;
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
const rejected = rejectedNpmPackages(manifest.dependencies);
|
|
547
|
+
if (rejected.length > 0) {
|
|
548
|
+
const msg = npmAllowlistMessage(rejected);
|
|
549
|
+
if (options.allowUnknownPackages) {
|
|
550
|
+
console.warn(pc2.yellow(`warning: ${msg}`));
|
|
551
|
+
} else {
|
|
552
|
+
console.error(pc2.red(msg));
|
|
553
|
+
console.error(
|
|
554
|
+
pc2.dim(
|
|
555
|
+
" The platform refuses these when the script is attached, so deploy would fail. Remove them, or re-run with --allow-unknown-packages if the bundled snapshot is stale (the deploy endpoint still enforces the live allowlist)."
|
|
556
|
+
)
|
|
557
|
+
);
|
|
558
|
+
process.exitCode = 1;
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
console.log(pc2.green(`kohala.json for "${manifest.name}" is valid.`));
|
|
563
|
+
console.log(
|
|
564
|
+
pc2.dim(
|
|
565
|
+
` runtimeMode=${manifest.runtimeMode} language=${summarizeLanguages(manifest.skills)} skills=${Object.keys(manifest.skills).length} validators=${manifest.validators.length} allowlist=[${manifest.toolAllowlist.join(", ")}]`
|
|
566
|
+
)
|
|
567
|
+
);
|
|
568
|
+
for (const skill of skillLanguages(manifest.skills)) {
|
|
569
|
+
console.log(
|
|
419
570
|
pc2.dim(
|
|
420
|
-
|
|
571
|
+
` skill "${skill.name}" \u2192 skills/${skill.scriptFilename} (${LANGUAGE_LABEL[skill.language]})`
|
|
421
572
|
)
|
|
422
573
|
);
|
|
574
|
+
}
|
|
575
|
+
if (manifest.dependencies.length > 0) {
|
|
576
|
+
console.log(pc2.dim(` npm packages: ${manifest.dependencies.join(", ")}`));
|
|
577
|
+
}
|
|
578
|
+
} catch (error) {
|
|
579
|
+
if (error instanceof ManifestError) {
|
|
580
|
+
console.error(pc2.red(error.message));
|
|
581
|
+
for (const problem of error.problems) {
|
|
582
|
+
console.error(pc2.red(` \u2022 ${problem}`));
|
|
583
|
+
}
|
|
423
584
|
process.exitCode = 1;
|
|
424
585
|
return;
|
|
425
586
|
}
|
|
587
|
+
throw error;
|
|
426
588
|
}
|
|
427
|
-
console.log(pc2.green(`kohala.json for "${manifest.name}" is valid.`));
|
|
428
|
-
console.log(
|
|
429
|
-
pc2.dim(
|
|
430
|
-
` runtimeMode=${manifest.runtimeMode} skills=${Object.keys(manifest.skills).length} validators=${manifest.validators.length} allowlist=[${manifest.toolAllowlist.join(", ")}]`
|
|
431
|
-
)
|
|
432
|
-
);
|
|
433
|
-
} catch (error) {
|
|
434
|
-
if (error instanceof ManifestError) {
|
|
435
|
-
console.error(pc2.red(error.message));
|
|
436
|
-
for (const problem of error.problems) {
|
|
437
|
-
console.error(pc2.red(` \u2022 ${problem}`));
|
|
438
|
-
}
|
|
439
|
-
process.exitCode = 1;
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
throw error;
|
|
443
589
|
}
|
|
444
|
-
|
|
590
|
+
);
|
|
445
591
|
}
|
|
446
592
|
|
|
447
593
|
// src/cli/run.ts
|
|
@@ -1556,6 +1702,7 @@ async function findPython() {
|
|
|
1556
1702
|
}
|
|
1557
1703
|
|
|
1558
1704
|
// src/emulator/runner.ts
|
|
1705
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1559
1706
|
var MAX_REPAIR_ATTEMPTS = 2;
|
|
1560
1707
|
function resolveSkill(manifest, requested) {
|
|
1561
1708
|
const entries = Object.entries(manifest.skills);
|
|
@@ -1689,7 +1836,12 @@ async function runShift(options) {
|
|
|
1689
1836
|
});
|
|
1690
1837
|
if (!execution.ok) {
|
|
1691
1838
|
if (meter.abortedWith?.code === "PER_RUN_TOKEN_CAP") {
|
|
1692
|
-
return finish(
|
|
1839
|
+
return finish(
|
|
1840
|
+
"aborted_per_run_token_cap",
|
|
1841
|
+
execution.stdout,
|
|
1842
|
+
[],
|
|
1843
|
+
meter.abortedWith.message
|
|
1844
|
+
);
|
|
1693
1845
|
}
|
|
1694
1846
|
return finish("error", execution.stdout, [], execution.errorDetail);
|
|
1695
1847
|
}
|
|
@@ -1705,7 +1857,12 @@ async function runShift(options) {
|
|
|
1705
1857
|
}
|
|
1706
1858
|
repairReason = failures.map((failure) => `${failure.validator}: ${failure.detail}`).join("; ");
|
|
1707
1859
|
if (attempt === MAX_REPAIR_ATTEMPTS) {
|
|
1708
|
-
return finish(
|
|
1860
|
+
return finish(
|
|
1861
|
+
"failed",
|
|
1862
|
+
execution.stdout,
|
|
1863
|
+
validatorResults,
|
|
1864
|
+
`validators failed after ${MAX_REPAIR_ATTEMPTS} repair attempts: ${repairReason}`
|
|
1865
|
+
);
|
|
1709
1866
|
}
|
|
1710
1867
|
}
|
|
1711
1868
|
throw new Error("repair loop exited without a result");
|
|
@@ -1727,10 +1884,29 @@ function recordValidatorResults(trace, runId, agent, results) {
|
|
|
1727
1884
|
}
|
|
1728
1885
|
}
|
|
1729
1886
|
async function executeScript(options) {
|
|
1730
|
-
const python = await findPython();
|
|
1731
1887
|
const scriptPath = path8.join(options.agentDir, "skills", options.scriptFilename);
|
|
1888
|
+
const extension = path8.extname(options.scriptFilename).toLowerCase();
|
|
1889
|
+
let command;
|
|
1890
|
+
let args;
|
|
1891
|
+
if (extension === ".py") {
|
|
1892
|
+
command = await findPython();
|
|
1893
|
+
args = [scriptPath];
|
|
1894
|
+
} else if (extension === ".ts" || extension === ".mts" || extension === ".cts") {
|
|
1895
|
+
command = process.execPath;
|
|
1896
|
+
const tsxCli = fileURLToPath2(import.meta.resolve("tsx/cli"));
|
|
1897
|
+
args = [tsxCli, scriptPath];
|
|
1898
|
+
} else if (extension === ".js" || extension === ".mjs" || extension === ".cjs") {
|
|
1899
|
+
command = process.execPath;
|
|
1900
|
+
args = [scriptPath];
|
|
1901
|
+
} else {
|
|
1902
|
+
return {
|
|
1903
|
+
ok: false,
|
|
1904
|
+
stdout: "",
|
|
1905
|
+
errorDetail: `Unsupported skill script extension "${extension || "(none)"}". Use .py, .ts, .mts, .cts, .js, .mjs, or .cjs.`
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1732
1908
|
try {
|
|
1733
|
-
const result = await execa2(
|
|
1909
|
+
const result = await execa2(command, args, {
|
|
1734
1910
|
cwd: options.agentDir,
|
|
1735
1911
|
env: {
|
|
1736
1912
|
KOHALA_RPC_URL: options.rpcUrl,
|
|
@@ -1760,11 +1936,7 @@ async function executeScript(options) {
|
|
|
1760
1936
|
|
|
1761
1937
|
// src/cli/run.ts
|
|
1762
1938
|
function registerRunCommand(program2) {
|
|
1763
|
-
program2.command("run").argument("<agent>", "agent directory (containing kohala.json)").option("--local", "run against the local emulator (required for now)").option("--skill <name>", "which skill to run (defaults to the only skill)").option(
|
|
1764
|
-
"--backend <backend>",
|
|
1765
|
-
"memory backend for this run: file | postgres",
|
|
1766
|
-
"file"
|
|
1767
|
-
).option("--url <url>", "postgres connection string (or set DATABASE_URL)").description("Run a shift against the local emulator").action(
|
|
1939
|
+
program2.command("run").argument("<agent>", "agent directory (containing kohala.json)").option("--local", "run against the local emulator (required for now)").option("--skill <name>", "which skill to run (defaults to the only skill)").option("--backend <backend>", "memory backend for this run: file | postgres", "file").option("--url <url>", "postgres connection string (or set DATABASE_URL)").description("Run a shift against the local emulator").action(
|
|
1768
1940
|
async (agent, options) => {
|
|
1769
1941
|
if (!options.local) {
|
|
1770
1942
|
throw new Error(
|
|
@@ -1787,7 +1959,13 @@ function registerRunCommand(program2) {
|
|
|
1787
1959
|
console.log(
|
|
1788
1960
|
pc3.cyan(`Running shift for "${manifest.name}" (${manifest.runtimeMode} mode)...`)
|
|
1789
1961
|
);
|
|
1790
|
-
const result = await runShift({
|
|
1962
|
+
const result = await runShift({
|
|
1963
|
+
rootDir,
|
|
1964
|
+
agentDir,
|
|
1965
|
+
manifest,
|
|
1966
|
+
store,
|
|
1967
|
+
skill: options.skill
|
|
1968
|
+
});
|
|
1791
1969
|
console.log("");
|
|
1792
1970
|
if (result.status === "succeeded") {
|
|
1793
1971
|
console.log(pc3.green(`\u2714 Shift ${result.runId} succeeded`));
|
|
@@ -1798,7 +1976,9 @@ function registerRunCommand(program2) {
|
|
|
1798
1976
|
console.log(pc3.dim(` tokens used: ${result.totalTokens} (counted, never billed)`));
|
|
1799
1977
|
for (const validator of result.validatorResults) {
|
|
1800
1978
|
const mark = validator.passed ? pc3.green("passed") : pc3.red("failed");
|
|
1801
|
-
console.log(
|
|
1979
|
+
console.log(
|
|
1980
|
+
pc3.dim(` validator ${validator.validator}: `) + mark + pc3.dim(` \u2014 ${validator.detail}`)
|
|
1981
|
+
);
|
|
1802
1982
|
}
|
|
1803
1983
|
if (result.output.trim() !== "") {
|
|
1804
1984
|
console.log("");
|
|
@@ -2243,6 +2423,9 @@ import pc8 from "picocolors";
|
|
|
2243
2423
|
import fs12 from "fs";
|
|
2244
2424
|
import path13 from "path";
|
|
2245
2425
|
var DEFAULT_BASE_URL = "https://kohala.ai";
|
|
2426
|
+
function skillPayloadLanguage(payload) {
|
|
2427
|
+
return payload.runtimeLanguage ?? "python";
|
|
2428
|
+
}
|
|
2246
2429
|
function buildDeployPlan(manifest, agentDir) {
|
|
2247
2430
|
const skills = Object.entries(manifest.skills).map(([name, scriptFilename]) => {
|
|
2248
2431
|
const scriptPath = path13.join(agentDir, "skills", scriptFilename);
|
|
@@ -2251,11 +2434,16 @@ function buildDeployPlan(manifest, agentDir) {
|
|
|
2251
2434
|
`Skill "${name}" points at ${scriptFilename}, but ${scriptPath} does not exist.`
|
|
2252
2435
|
);
|
|
2253
2436
|
}
|
|
2437
|
+
const language = languageForEntrypointFile(scriptFilename) ?? "python";
|
|
2254
2438
|
return {
|
|
2255
2439
|
name,
|
|
2256
2440
|
scriptFilename,
|
|
2257
2441
|
description: `Skill "${name}" of agent "${manifest.name}"`,
|
|
2258
|
-
code: fs12.readFileSync(scriptPath, "utf8")
|
|
2442
|
+
code: fs12.readFileSync(scriptPath, "utf8"),
|
|
2443
|
+
...language === "node" ? {
|
|
2444
|
+
runtimeLanguage: "node",
|
|
2445
|
+
...manifest.dependencies.length > 0 ? { scriptDependencies: manifest.dependencies } : {}
|
|
2446
|
+
} : {}
|
|
2259
2447
|
};
|
|
2260
2448
|
});
|
|
2261
2449
|
if (manifest.schedule && skills.length === 0) {
|
|
@@ -2280,6 +2468,17 @@ function buildDeployPlan(manifest, agentDir) {
|
|
|
2280
2468
|
}
|
|
2281
2469
|
};
|
|
2282
2470
|
}
|
|
2471
|
+
function readableApiError(body) {
|
|
2472
|
+
const raw = (body ?? "").trim();
|
|
2473
|
+
try {
|
|
2474
|
+
const parsed = JSON.parse(raw);
|
|
2475
|
+
const parts = [parsed.error, parsed.message].filter((part) => typeof part === "string" && part.trim() !== "").map((part) => part.trim());
|
|
2476
|
+
const unique = parts.filter((part, index) => parts.indexOf(part) === index);
|
|
2477
|
+
if (unique.length > 0) return unique.join(" \u2014 ").slice(0, 1e3);
|
|
2478
|
+
} catch {
|
|
2479
|
+
}
|
|
2480
|
+
return raw.slice(0, 500);
|
|
2481
|
+
}
|
|
2283
2482
|
var DeployError = class extends Error {
|
|
2284
2483
|
constructor(status, message) {
|
|
2285
2484
|
super(message);
|
|
@@ -2326,7 +2525,7 @@ var KohalaClient = class {
|
|
|
2326
2525
|
const text2 = await response.text();
|
|
2327
2526
|
throw new DeployError(
|
|
2328
2527
|
response.status,
|
|
2329
|
-
`Kohala API error ${response.status} on ${method} ${apiPath}: ${text2
|
|
2528
|
+
`Kohala API error ${response.status} on ${method} ${apiPath}: ${readableApiError(text2)}`
|
|
2330
2529
|
);
|
|
2331
2530
|
}
|
|
2332
2531
|
const text = await response.text();
|
|
@@ -2393,10 +2592,25 @@ var KohalaClient = class {
|
|
|
2393
2592
|
|
|
2394
2593
|
// src/cli/deploy.ts
|
|
2395
2594
|
function registerDeployCommand(program2) {
|
|
2396
|
-
program2.command("deploy").argument("<agent>", "agent directory (containing kohala.json)").option("--dry-run", "print the payloads without sending anything").option("--base-url <url>", "API base URL", DEFAULT_BASE_URL).option("--run", "trigger a manual run after deploying").
|
|
2595
|
+
program2.command("deploy").argument("<agent>", "agent directory (containing kohala.json)").option("--dry-run", "print the payloads without sending anything").option("--base-url <url>", "API base URL", DEFAULT_BASE_URL).option("--run", "trigger a manual run after deploying").option(
|
|
2596
|
+
"--allow-unknown-packages",
|
|
2597
|
+
"do not fail on npm packages missing from the bundled allowlist snapshot"
|
|
2598
|
+
).description("Deploy an agent to kohala.ai (idempotent on agent name)").action(
|
|
2397
2599
|
async (agent, options) => {
|
|
2398
2600
|
const agentDir = path14.resolve(process.cwd(), agent);
|
|
2399
2601
|
const manifest = loadManifest(agentDir);
|
|
2602
|
+
const rejected = rejectedNpmPackages(manifest.dependencies);
|
|
2603
|
+
if (rejected.length > 0) {
|
|
2604
|
+
const message = npmAllowlistMessage(rejected);
|
|
2605
|
+
if (options.allowUnknownPackages) {
|
|
2606
|
+
console.warn(pc8.yellow(`warning: ${message}`));
|
|
2607
|
+
} else {
|
|
2608
|
+
throw new Error(
|
|
2609
|
+
`${message}
|
|
2610
|
+
The platform refuses these when the script is attached, so this deploy would fail. Remove them, or re-run with --allow-unknown-packages if the bundled snapshot is stale (the deploy endpoint still enforces the live allowlist).`
|
|
2611
|
+
);
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2400
2614
|
const plan = buildDeployPlan(manifest, agentDir);
|
|
2401
2615
|
if (options.dryRun) {
|
|
2402
2616
|
console.log(pc8.cyan("Dry run \u2014 nothing will be sent. Deploy plan:"));
|
|
@@ -2405,7 +2619,11 @@ function registerDeployCommand(program2) {
|
|
|
2405
2619
|
console.log(JSON.stringify(plan.agent, null, 2));
|
|
2406
2620
|
for (const skill of plan.skills) {
|
|
2407
2621
|
console.log("");
|
|
2408
|
-
console.log(
|
|
2622
|
+
console.log(
|
|
2623
|
+
pc8.bold(
|
|
2624
|
+
`2. POST /api/v1/agents/:id/skills \u2014 "${skill.name}" (${LANGUAGE_LABEL[skillPayloadLanguage(skill)]})`
|
|
2625
|
+
)
|
|
2626
|
+
);
|
|
2409
2627
|
console.log(
|
|
2410
2628
|
JSON.stringify(
|
|
2411
2629
|
{ ...skill, code: `<${Buffer.byteLength(skill.code)} bytes of ${skill.scriptFilename}>` },
|
|
@@ -2459,7 +2677,12 @@ function registerDeployCommand(program2) {
|
|
|
2459
2677
|
);
|
|
2460
2678
|
for (const skill of plan.skills) {
|
|
2461
2679
|
await client.upsertSkill(upserted.id, skill);
|
|
2462
|
-
|
|
2680
|
+
const packages = skill.scriptDependencies?.length ? `, npm: ${skill.scriptDependencies.join(", ")}` : "";
|
|
2681
|
+
console.log(
|
|
2682
|
+
pc8.green(
|
|
2683
|
+
` \u2714 skill "${skill.name}" uploaded (${skill.scriptFilename}, ${LANGUAGE_LABEL[skillPayloadLanguage(skill)]}${packages})`
|
|
2684
|
+
)
|
|
2685
|
+
);
|
|
2463
2686
|
}
|
|
2464
2687
|
if (plan.agent.agentScheduleCron) {
|
|
2465
2688
|
const filenames = plan.skills.map((skill) => skill.scriptFilename);
|