@yaklabs-ai/plugin-sdk 0.12.3 → 0.12.5
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/README.md +5 -3
- package/THIRD-PARTY-INVENTORY.json +8 -1
- package/dist/disclosure.d.ts +139 -1
- package/dist/disclosure.js +32 -8
- package/dist/kay-agent-testing.d.ts +138 -1
- package/dist/kay-agent-testing.js +20 -1
- package/dist/kay-agent.d.ts +137 -0
- package/dist/kay-app.d.ts +263 -0
- package/package.json +3 -2
- package/skills/directory-plugin-authoring/SKILL.md +42 -8
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ it under the name your plugin code actually imports:
|
|
|
13
13
|
```json
|
|
14
14
|
{
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@kay/plugin-sdk": "npm:@yaklabs-ai/plugin-sdk@^0.12.
|
|
16
|
+
"@kay/plugin-sdk": "npm:@yaklabs-ai/plugin-sdk@^0.12.5"
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
```
|
|
@@ -131,8 +131,10 @@ Pass the loader's inputs, not a subset — the audit judges what it is given:
|
|
|
131
131
|
violations Kay itself would not report.
|
|
132
132
|
|
|
133
133
|
`auditSkillCodegenPairs({ declaredSkills, exportedSkills, packageDir })` is the
|
|
134
|
-
companion check for a bundled-skill plugin.
|
|
135
|
-
|
|
134
|
+
companion check for a bundled-skill plugin. It compares the decoded YAML
|
|
135
|
+
`name` and `description` with the export, so quoting and block scalars are
|
|
136
|
+
supported; body content must match after trimming surrounding whitespace.
|
|
137
|
+
Both return violations rather than asserting, so any runner works. Both read from disk
|
|
136
138
|
and call your own `help()`, so run them in Node, and keep `activate` free of
|
|
137
139
|
side effects: the probe calls it.
|
|
138
140
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"package": "@yaklabs-ai/plugin-sdk",
|
|
4
|
-
"version": "0.12.
|
|
4
|
+
"version": "0.12.5",
|
|
5
5
|
"target": "npm",
|
|
6
6
|
"firstPartyLicense": "MIT",
|
|
7
7
|
"bundled": [
|
|
@@ -53,6 +53,13 @@
|
|
|
53
53
|
"source": "https://github.com/colinhacks/zod",
|
|
54
54
|
"note": "Installed as a consumer dependency; it is not bundled into the staged artifacts.",
|
|
55
55
|
"versionRange": "^4.0.0"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "yaml",
|
|
59
|
+
"license": "ISC",
|
|
60
|
+
"source": "https://github.com/eemeli/yaml",
|
|
61
|
+
"note": "Installed as a consumer dependency; it is not bundled into the staged artifacts.",
|
|
62
|
+
"versionRange": "^2.9.0"
|
|
56
63
|
}
|
|
57
64
|
]
|
|
58
65
|
}
|
package/dist/disclosure.d.ts
CHANGED
|
@@ -213,7 +213,8 @@ export declare interface AuditPluginDisclosureInput {
|
|
|
213
213
|
* The codegen-pair equivalence check every bundled-skill plugin hand-rolled (see
|
|
214
214
|
* `plugins/ARCHITECTURE.md` → "Bundled skills", step 4): every exported `PluginSkillDefinition`
|
|
215
215
|
* must have a matching manifest declaration (same `localId`, same `description`), and the
|
|
216
|
-
* declared `SKILL.md` file's
|
|
216
|
+
* declared `SKILL.md` file's decoded YAML name and description must match the export.
|
|
217
|
+
* The body is compared byte-for-byte after trimming surrounding whitespace.
|
|
217
218
|
*/
|
|
218
219
|
export declare function auditSkillCodegenPairs(input: AuditSkillCodegenPairsInput): Promise<readonly DisclosureViolation[]>;
|
|
219
220
|
|
|
@@ -1265,6 +1266,14 @@ declare interface KayAgentPluginApi {
|
|
|
1265
1266
|
* the same zone-of-record port the session stamp uses; see {@link KayAppTime.snapshot}.
|
|
1266
1267
|
*/
|
|
1267
1268
|
readonly time: KayAppTime;
|
|
1269
|
+
/**
|
|
1270
|
+
* File I/O in user folders, run by the host on its own worker threads under a deadline and
|
|
1271
|
+
* answered as a typed result. Always present and ungated: unlike {@link filesystem} it takes
|
|
1272
|
+
* absolute paths and checks no grant, because it adds no authority a plugin's `node:fs` does not
|
|
1273
|
+
* already have. What it adds is that a slow or unresponsive folder never blocks the daemon. See
|
|
1274
|
+
* {@link KayAppUserFiles}.
|
|
1275
|
+
*/
|
|
1276
|
+
readonly userFiles: KayAppUserFiles;
|
|
1268
1277
|
/**
|
|
1269
1278
|
* Publish the plugin's declared events, for example so a default notification rule can tell the
|
|
1270
1279
|
* user what the agent just did. Always present, like {@link withJev}: there is nothing to
|
|
@@ -1569,6 +1578,135 @@ declare interface KayAppTimeSnapshot {
|
|
|
1569
1578
|
readonly zoneSource: "host" | "utc-fallback";
|
|
1570
1579
|
}
|
|
1571
1580
|
|
|
1581
|
+
declare interface KayAppUserFileEntry {
|
|
1582
|
+
readonly name: string;
|
|
1583
|
+
/** From the directory entry itself: a symlink is reported as `symlink`, never followed. */
|
|
1584
|
+
readonly kind: KayAppUserFileKind;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
/**
|
|
1588
|
+
* The plugin-facing user-files contract: file I/O in folders the user owns, done by the host on
|
|
1589
|
+
* its own worker threads.
|
|
1590
|
+
*
|
|
1591
|
+
* Ungated and always present, like `app.paths`. It confers no authority: a plugin runs in the
|
|
1592
|
+
* daemon's process and can call `node:fs` itself. What the door adds is where the work runs. A
|
|
1593
|
+
* `node:fs` sync call blocks the daemon's event loop for as long as the path takes to answer,
|
|
1594
|
+
* which under a macOS-protected folder or on a drive that stopped answering can be a very long
|
|
1595
|
+
* time (kay#11575); an async one holds a thread of the pool the whole daemon shares. The host runs
|
|
1596
|
+
* each request on a dedicated worker, gives it a deadline, and answers a typed result instead of
|
|
1597
|
+
* throwing.
|
|
1598
|
+
*
|
|
1599
|
+
* This is a leaf module. It lives beside both target SDKs, like `time.ts`, so the `kay.agent` SDK
|
|
1600
|
+
* exposes the same door without importing the `kay.app` surface.
|
|
1601
|
+
*/
|
|
1602
|
+
declare type KayAppUserFileKind = "file" | "directory" | "symlink" | "other";
|
|
1603
|
+
|
|
1604
|
+
declare interface KayAppUserFileListing {
|
|
1605
|
+
readonly entries: readonly KayAppUserFileEntry[];
|
|
1606
|
+
/** True when the directory had more entries than `maxEntries`; the rest were not read. */
|
|
1607
|
+
readonly entryLimitReached: boolean;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
/**
|
|
1611
|
+
* File I/O in user folders, run by the host off the daemon's main thread. Every path is absolute.
|
|
1612
|
+
* No method rejects; each answers a {@link KayAppUserFilesResult}. This is not a sandbox: it does
|
|
1613
|
+
* not limit which paths a plugin may touch. Added in SDK 0.12.5: a plugin that calls it declares
|
|
1614
|
+
* `compatibility.sdk` `^0.12.5`, so an older Bonsai refuses a directory or repository plugin at
|
|
1615
|
+
* load instead of the call failing.
|
|
1616
|
+
*/
|
|
1617
|
+
declare interface KayAppUserFiles {
|
|
1618
|
+
/** Follows symlinks. */
|
|
1619
|
+
stat(path: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<KayAppUserFileStat>>;
|
|
1620
|
+
/** UTF-8. */
|
|
1621
|
+
readText(path: string, options?: KayAppUserFilesReadTextOptions): Promise<KayAppUserFilesResult<string>>;
|
|
1622
|
+
list(path: string, options?: KayAppUserFilesListOptions): Promise<KayAppUserFilesResult<KayAppUserFileListing>>;
|
|
1623
|
+
/** Depth-first below `path`; symlinks are reported, never followed. */
|
|
1624
|
+
walk(path: string, options?: KayAppUserFilesWalkOptions): Promise<KayAppUserFilesResult<KayAppUserFileWalk>>;
|
|
1625
|
+
/** The path with every symlink resolved. */
|
|
1626
|
+
realpath(path: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<string>>;
|
|
1627
|
+
/**
|
|
1628
|
+
* Creates or replaces a UTF-8 file, not atomically. After `timeout` or `aborted` the write may
|
|
1629
|
+
* still happen.
|
|
1630
|
+
*/
|
|
1631
|
+
writeText(path: string, content: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<null>>;
|
|
1632
|
+
/** Creates the directory and any missing parents; succeeds when it already exists. */
|
|
1633
|
+
mkdir(path: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<null>>;
|
|
1634
|
+
rename(from: string, to: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<null>>;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
declare interface KayAppUserFilesCallOptions {
|
|
1638
|
+
/** Deadline in milliseconds from the call, waiting included. Default 5000, at most 60000. */
|
|
1639
|
+
readonly timeoutMs?: number;
|
|
1640
|
+
readonly signal?: AbortSignal;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
declare interface KayAppUserFilesListOptions extends KayAppUserFilesCallOptions {
|
|
1644
|
+
/** Default 5000, at most 50000. */
|
|
1645
|
+
readonly maxEntries?: number;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
declare interface KayAppUserFilesReadTextOptions extends KayAppUserFilesCallOptions {
|
|
1649
|
+
/** A larger file answers `error` with code `EFBIG`. Default 8 MiB, at most 64 MiB. */
|
|
1650
|
+
readonly maxBytes?: number;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
/**
|
|
1654
|
+
* Every answer. `missing`: nothing exists at the path (ENOENT). `timeout`: no answer from the path
|
|
1655
|
+
* in time. Its `reason` is `deadline` (this request ran out of time), `cooling-down` (a request at
|
|
1656
|
+
* or above this path timed out recently, so the host did not try) or `saturated` (the host's
|
|
1657
|
+
* workers are stuck on other paths, so it did not try). `aborted`: the caller's signal fired.
|
|
1658
|
+
* `error`: anything else, with the Node error `code` when there is one (`EACCES`, `EISDIR`,
|
|
1659
|
+
* `EFBIG` for a file over `maxBytes`, `EINVAL` for a relative path).
|
|
1660
|
+
*/
|
|
1661
|
+
declare type KayAppUserFilesResult<T> = {
|
|
1662
|
+
readonly kind: "ok";
|
|
1663
|
+
readonly value: T;
|
|
1664
|
+
} | {
|
|
1665
|
+
readonly kind: "missing";
|
|
1666
|
+
} | {
|
|
1667
|
+
readonly kind: "timeout";
|
|
1668
|
+
readonly reason: "deadline" | "cooling-down" | "saturated";
|
|
1669
|
+
readonly detail: string;
|
|
1670
|
+
} | {
|
|
1671
|
+
readonly kind: "aborted";
|
|
1672
|
+
} | {
|
|
1673
|
+
readonly kind: "error";
|
|
1674
|
+
readonly code?: string;
|
|
1675
|
+
readonly detail: string;
|
|
1676
|
+
};
|
|
1677
|
+
|
|
1678
|
+
declare interface KayAppUserFileStat {
|
|
1679
|
+
/** What the path is after following symlinks; never `symlink`. */
|
|
1680
|
+
readonly kind: "file" | "directory" | "other";
|
|
1681
|
+
/** Bytes. */
|
|
1682
|
+
readonly size: number;
|
|
1683
|
+
/** Milliseconds since the Unix epoch. */
|
|
1684
|
+
readonly modifiedAtMs: number;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
declare interface KayAppUserFilesWalkOptions extends KayAppUserFilesCallOptions {
|
|
1688
|
+
/** Default 10000, at most 100000. */
|
|
1689
|
+
readonly maxEntries?: number;
|
|
1690
|
+
/** Deepest level reported; the root's children are depth 1. Default 16, at most 64. */
|
|
1691
|
+
readonly maxDepth?: number;
|
|
1692
|
+
/** Report and descend into dot-prefixed entries. Default true. */
|
|
1693
|
+
readonly includeHidden?: boolean;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
declare interface KayAppUserFileWalk {
|
|
1697
|
+
readonly entries: readonly KayAppUserFileWalkEntry[];
|
|
1698
|
+
/** True when the walk stopped at `maxEntries`. */
|
|
1699
|
+
readonly entryLimitReached: boolean;
|
|
1700
|
+
/** True when a directory at `maxDepth` was reported but not descended into. */
|
|
1701
|
+
readonly depthLimitReached: boolean;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
declare interface KayAppUserFileWalkEntry {
|
|
1705
|
+
/** Path segments below the walk's root. */
|
|
1706
|
+
readonly segments: readonly string[];
|
|
1707
|
+
readonly kind: KayAppUserFileKind;
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1572
1710
|
declare type KnownApi = "openai-completions" | "mistral-conversations" | "openai-responses" | "azure-openai-responses" | "openai-codex-responses" | "anthropic-messages" | "bedrock-converse-stream" | "google-generative-ai" | "google-vertex" | "pi-messages";
|
|
1573
1711
|
|
|
1574
1712
|
declare type KnownProvider = "amazon-bedrock" | "ant-ling" | "anthropic" | "google" | "google-vertex" | "openai" | "azure-openai-responses" | "openai-codex" | "radius" | "typesafe" | "nvidia" | "deepseek" | "github-copilot" | "xai" | "groq" | "cerebras" | "openrouter" | "vercel-ai-gateway" | "zai" | "zai-coding-cn" | "mistral" | "minimax" | "minimax-cn" | "moonshotai" | "moonshotai-cn" | "huggingface" | "fireworks" | "together" | "baseten" | "opencode" | "opencode-go" | "kimi-coding" | "meta" | "cloudflare-workers-ai" | "cloudflare-ai-gateway" | "qwen-token-plan" | "qwen-token-plan-cn" | "qwen-token-plan-individual" | "xiaomi" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-sgp";
|
package/dist/disclosure.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// packages/plugin-sdk/dist/disclosure/disclosure-conformance.js
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
3
4
|
|
|
4
5
|
// packages/herd-uri/dist/connection.js
|
|
5
6
|
var CONNECTION_SELECTOR_RE = /^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)@([^/]+)(?:\/(.*))?$/;
|
|
@@ -700,24 +701,36 @@ async function auditSkillCodegenPairFile(skill, declaration, packageDir) {
|
|
|
700
701
|
];
|
|
701
702
|
}
|
|
702
703
|
const codegenPairMatch = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/.exec(file);
|
|
703
|
-
const
|
|
704
|
-
|
|
704
|
+
const yaml = codegenPairMatch?.[1];
|
|
705
|
+
let frontmatter = {};
|
|
706
|
+
let parseError;
|
|
707
|
+
if (yaml !== void 0) {
|
|
708
|
+
try {
|
|
709
|
+
const parsed = parseYaml(yaml);
|
|
710
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
711
|
+
frontmatter = parsed;
|
|
712
|
+
} else {
|
|
713
|
+
parseError = "frontmatter must be a YAML mapping";
|
|
714
|
+
}
|
|
715
|
+
} catch (error) {
|
|
716
|
+
parseError = errorMessage(error);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
const parseDetail = parseError === void 0 ? "" : ` Could not parse '${declaration.path}': ${parseError}`;
|
|
705
720
|
const body = codegenPairMatch?.[2];
|
|
706
721
|
const violations = [];
|
|
707
|
-
if (frontmatter
|
|
708
|
-
`)) {
|
|
722
|
+
if (frontmatter["name"] !== skill.name) {
|
|
709
723
|
violations.push({
|
|
710
724
|
moduleId: skill.name,
|
|
711
725
|
rule: "skill-twin-frontmatter-name-mismatch",
|
|
712
|
-
detail: `SKILL.md frontmatter does not
|
|
726
|
+
detail: `SKILL.md frontmatter name does not match the exported name '${skill.name}'.${parseDetail}`
|
|
713
727
|
});
|
|
714
728
|
}
|
|
715
|
-
if (frontmatter
|
|
716
|
-
`)) {
|
|
729
|
+
if (frontmatter["description"] !== skill.description) {
|
|
717
730
|
violations.push({
|
|
718
731
|
moduleId: skill.name,
|
|
719
732
|
rule: "skill-twin-frontmatter-description-mismatch",
|
|
720
|
-
detail:
|
|
733
|
+
detail: `SKILL.md frontmatter description does not match the exported description.${parseDetail}`
|
|
721
734
|
});
|
|
722
735
|
}
|
|
723
736
|
if (body === void 0 || body.trim() !== skill.content.trim()) {
|
|
@@ -811,6 +824,17 @@ function createKayAgentProbeCapabilityHandles() {
|
|
|
811
824
|
// real capability keeps for a zone the host cannot read.
|
|
812
825
|
time: {
|
|
813
826
|
snapshot: () => ({ nowMs: 0, zone: "UTC", zoneSource: "utc-fallback" })
|
|
827
|
+
},
|
|
828
|
+
// Inert like the filesystem broker: a probe must never touch the user's disk.
|
|
829
|
+
userFiles: {
|
|
830
|
+
stat: inertResult,
|
|
831
|
+
readText: inertResult,
|
|
832
|
+
list: inertResult,
|
|
833
|
+
walk: inertResult,
|
|
834
|
+
realpath: inertResult,
|
|
835
|
+
writeText: inertResult,
|
|
836
|
+
mkdir: inertResult,
|
|
837
|
+
rename: inertResult
|
|
814
838
|
}
|
|
815
839
|
};
|
|
816
840
|
}
|
|
@@ -531,7 +531,7 @@ declare interface ExtensionPaths {
|
|
|
531
531
|
export declare function fauxAuthContext(overrides?: Partial<ExtensionAuthContext>): ExtensionAuthContext;
|
|
532
532
|
|
|
533
533
|
/** The capability handles a `KayAgentPluginApi` always carries — authority is decided per call. */
|
|
534
|
-
export declare type FauxKayAgentCapabilities = Pick<KayAgentPluginApi, "filesystem" | "network" | "webSession" | "process" | "whiteboard" | "interaction" | "interceptToolCall" | "withJev" | "events" | "time">;
|
|
534
|
+
export declare type FauxKayAgentCapabilities = Pick<KayAgentPluginApi, "filesystem" | "network" | "webSession" | "process" | "whiteboard" | "interaction" | "interceptToolCall" | "withJev" | "events" | "time" | "userFiles">;
|
|
535
535
|
|
|
536
536
|
export declare function fauxKayAgentCapabilities(): FauxKayAgentCapabilities;
|
|
537
537
|
|
|
@@ -1167,6 +1167,14 @@ declare interface KayAgentPluginApi {
|
|
|
1167
1167
|
* the same zone-of-record port the session stamp uses; see {@link KayAppTime.snapshot}.
|
|
1168
1168
|
*/
|
|
1169
1169
|
readonly time: KayAppTime;
|
|
1170
|
+
/**
|
|
1171
|
+
* File I/O in user folders, run by the host on its own worker threads under a deadline and
|
|
1172
|
+
* answered as a typed result. Always present and ungated: unlike {@link filesystem} it takes
|
|
1173
|
+
* absolute paths and checks no grant, because it adds no authority a plugin's `node:fs` does not
|
|
1174
|
+
* already have. What it adds is that a slow or unresponsive folder never blocks the daemon. See
|
|
1175
|
+
* {@link KayAppUserFiles}.
|
|
1176
|
+
*/
|
|
1177
|
+
readonly userFiles: KayAppUserFiles;
|
|
1170
1178
|
/**
|
|
1171
1179
|
* Publish the plugin's declared events, for example so a default notification rule can tell the
|
|
1172
1180
|
* user what the agent just did. Always present, like {@link withJev}: there is nothing to
|
|
@@ -1471,6 +1479,135 @@ declare interface KayAppTimeSnapshot {
|
|
|
1471
1479
|
readonly zoneSource: "host" | "utc-fallback";
|
|
1472
1480
|
}
|
|
1473
1481
|
|
|
1482
|
+
declare interface KayAppUserFileEntry {
|
|
1483
|
+
readonly name: string;
|
|
1484
|
+
/** From the directory entry itself: a symlink is reported as `symlink`, never followed. */
|
|
1485
|
+
readonly kind: KayAppUserFileKind;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
/**
|
|
1489
|
+
* The plugin-facing user-files contract: file I/O in folders the user owns, done by the host on
|
|
1490
|
+
* its own worker threads.
|
|
1491
|
+
*
|
|
1492
|
+
* Ungated and always present, like `app.paths`. It confers no authority: a plugin runs in the
|
|
1493
|
+
* daemon's process and can call `node:fs` itself. What the door adds is where the work runs. A
|
|
1494
|
+
* `node:fs` sync call blocks the daemon's event loop for as long as the path takes to answer,
|
|
1495
|
+
* which under a macOS-protected folder or on a drive that stopped answering can be a very long
|
|
1496
|
+
* time (kay#11575); an async one holds a thread of the pool the whole daemon shares. The host runs
|
|
1497
|
+
* each request on a dedicated worker, gives it a deadline, and answers a typed result instead of
|
|
1498
|
+
* throwing.
|
|
1499
|
+
*
|
|
1500
|
+
* This is a leaf module. It lives beside both target SDKs, like `time.ts`, so the `kay.agent` SDK
|
|
1501
|
+
* exposes the same door without importing the `kay.app` surface.
|
|
1502
|
+
*/
|
|
1503
|
+
declare type KayAppUserFileKind = "file" | "directory" | "symlink" | "other";
|
|
1504
|
+
|
|
1505
|
+
declare interface KayAppUserFileListing {
|
|
1506
|
+
readonly entries: readonly KayAppUserFileEntry[];
|
|
1507
|
+
/** True when the directory had more entries than `maxEntries`; the rest were not read. */
|
|
1508
|
+
readonly entryLimitReached: boolean;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
/**
|
|
1512
|
+
* File I/O in user folders, run by the host off the daemon's main thread. Every path is absolute.
|
|
1513
|
+
* No method rejects; each answers a {@link KayAppUserFilesResult}. This is not a sandbox: it does
|
|
1514
|
+
* not limit which paths a plugin may touch. Added in SDK 0.12.5: a plugin that calls it declares
|
|
1515
|
+
* `compatibility.sdk` `^0.12.5`, so an older Bonsai refuses a directory or repository plugin at
|
|
1516
|
+
* load instead of the call failing.
|
|
1517
|
+
*/
|
|
1518
|
+
declare interface KayAppUserFiles {
|
|
1519
|
+
/** Follows symlinks. */
|
|
1520
|
+
stat(path: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<KayAppUserFileStat>>;
|
|
1521
|
+
/** UTF-8. */
|
|
1522
|
+
readText(path: string, options?: KayAppUserFilesReadTextOptions): Promise<KayAppUserFilesResult<string>>;
|
|
1523
|
+
list(path: string, options?: KayAppUserFilesListOptions): Promise<KayAppUserFilesResult<KayAppUserFileListing>>;
|
|
1524
|
+
/** Depth-first below `path`; symlinks are reported, never followed. */
|
|
1525
|
+
walk(path: string, options?: KayAppUserFilesWalkOptions): Promise<KayAppUserFilesResult<KayAppUserFileWalk>>;
|
|
1526
|
+
/** The path with every symlink resolved. */
|
|
1527
|
+
realpath(path: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<string>>;
|
|
1528
|
+
/**
|
|
1529
|
+
* Creates or replaces a UTF-8 file, not atomically. After `timeout` or `aborted` the write may
|
|
1530
|
+
* still happen.
|
|
1531
|
+
*/
|
|
1532
|
+
writeText(path: string, content: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<null>>;
|
|
1533
|
+
/** Creates the directory and any missing parents; succeeds when it already exists. */
|
|
1534
|
+
mkdir(path: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<null>>;
|
|
1535
|
+
rename(from: string, to: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<null>>;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
declare interface KayAppUserFilesCallOptions {
|
|
1539
|
+
/** Deadline in milliseconds from the call, waiting included. Default 5000, at most 60000. */
|
|
1540
|
+
readonly timeoutMs?: number;
|
|
1541
|
+
readonly signal?: AbortSignal;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
declare interface KayAppUserFilesListOptions extends KayAppUserFilesCallOptions {
|
|
1545
|
+
/** Default 5000, at most 50000. */
|
|
1546
|
+
readonly maxEntries?: number;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
declare interface KayAppUserFilesReadTextOptions extends KayAppUserFilesCallOptions {
|
|
1550
|
+
/** A larger file answers `error` with code `EFBIG`. Default 8 MiB, at most 64 MiB. */
|
|
1551
|
+
readonly maxBytes?: number;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
/**
|
|
1555
|
+
* Every answer. `missing`: nothing exists at the path (ENOENT). `timeout`: no answer from the path
|
|
1556
|
+
* in time. Its `reason` is `deadline` (this request ran out of time), `cooling-down` (a request at
|
|
1557
|
+
* or above this path timed out recently, so the host did not try) or `saturated` (the host's
|
|
1558
|
+
* workers are stuck on other paths, so it did not try). `aborted`: the caller's signal fired.
|
|
1559
|
+
* `error`: anything else, with the Node error `code` when there is one (`EACCES`, `EISDIR`,
|
|
1560
|
+
* `EFBIG` for a file over `maxBytes`, `EINVAL` for a relative path).
|
|
1561
|
+
*/
|
|
1562
|
+
declare type KayAppUserFilesResult<T> = {
|
|
1563
|
+
readonly kind: "ok";
|
|
1564
|
+
readonly value: T;
|
|
1565
|
+
} | {
|
|
1566
|
+
readonly kind: "missing";
|
|
1567
|
+
} | {
|
|
1568
|
+
readonly kind: "timeout";
|
|
1569
|
+
readonly reason: "deadline" | "cooling-down" | "saturated";
|
|
1570
|
+
readonly detail: string;
|
|
1571
|
+
} | {
|
|
1572
|
+
readonly kind: "aborted";
|
|
1573
|
+
} | {
|
|
1574
|
+
readonly kind: "error";
|
|
1575
|
+
readonly code?: string;
|
|
1576
|
+
readonly detail: string;
|
|
1577
|
+
};
|
|
1578
|
+
|
|
1579
|
+
declare interface KayAppUserFileStat {
|
|
1580
|
+
/** What the path is after following symlinks; never `symlink`. */
|
|
1581
|
+
readonly kind: "file" | "directory" | "other";
|
|
1582
|
+
/** Bytes. */
|
|
1583
|
+
readonly size: number;
|
|
1584
|
+
/** Milliseconds since the Unix epoch. */
|
|
1585
|
+
readonly modifiedAtMs: number;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
declare interface KayAppUserFilesWalkOptions extends KayAppUserFilesCallOptions {
|
|
1589
|
+
/** Default 10000, at most 100000. */
|
|
1590
|
+
readonly maxEntries?: number;
|
|
1591
|
+
/** Deepest level reported; the root's children are depth 1. Default 16, at most 64. */
|
|
1592
|
+
readonly maxDepth?: number;
|
|
1593
|
+
/** Report and descend into dot-prefixed entries. Default true. */
|
|
1594
|
+
readonly includeHidden?: boolean;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
declare interface KayAppUserFileWalk {
|
|
1598
|
+
readonly entries: readonly KayAppUserFileWalkEntry[];
|
|
1599
|
+
/** True when the walk stopped at `maxEntries`. */
|
|
1600
|
+
readonly entryLimitReached: boolean;
|
|
1601
|
+
/** True when a directory at `maxDepth` was reported but not descended into. */
|
|
1602
|
+
readonly depthLimitReached: boolean;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
declare interface KayAppUserFileWalkEntry {
|
|
1606
|
+
/** Path segments below the walk's root. */
|
|
1607
|
+
readonly segments: readonly string[];
|
|
1608
|
+
readonly kind: KayAppUserFileKind;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1474
1611
|
declare type KnownApi = "openai-completions" | "mistral-conversations" | "openai-responses" | "azure-openai-responses" | "openai-codex-responses" | "anthropic-messages" | "bedrock-converse-stream" | "google-generative-ai" | "google-vertex" | "pi-messages";
|
|
1475
1612
|
|
|
1476
1613
|
declare type KnownProvider = "amazon-bedrock" | "ant-ling" | "anthropic" | "google" | "google-vertex" | "openai" | "azure-openai-responses" | "openai-codex" | "radius" | "typesafe" | "nvidia" | "deepseek" | "github-copilot" | "xai" | "groq" | "cerebras" | "openrouter" | "vercel-ai-gateway" | "zai" | "zai-coding-cn" | "mistral" | "minimax" | "minimax-cn" | "moonshotai" | "moonshotai-cn" | "huggingface" | "fireworks" | "together" | "baseten" | "opencode" | "opencode-go" | "kimi-coding" | "meta" | "cloudflare-workers-ai" | "cloudflare-ai-gateway" | "qwen-token-plan" | "qwen-token-plan-cn" | "qwen-token-plan-individual" | "xiaomi" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-sgp";
|
|
@@ -135,6 +135,16 @@ var UNWIRED_SURFACE = {
|
|
|
135
135
|
screenshot: NOT_WIRED,
|
|
136
136
|
open: NOT_WIRED
|
|
137
137
|
};
|
|
138
|
+
var USER_FILE_METHODS = [
|
|
139
|
+
"stat",
|
|
140
|
+
"readText",
|
|
141
|
+
"list",
|
|
142
|
+
"walk",
|
|
143
|
+
"realpath",
|
|
144
|
+
"writeText",
|
|
145
|
+
"mkdir",
|
|
146
|
+
"rename"
|
|
147
|
+
];
|
|
138
148
|
function fauxKayAgentCapabilities() {
|
|
139
149
|
return {
|
|
140
150
|
filesystem: {
|
|
@@ -191,7 +201,16 @@ function fauxKayAgentCapabilities() {
|
|
|
191
201
|
// zone-of-record path overrides this handle after the spread.
|
|
192
202
|
time: {
|
|
193
203
|
snapshot: () => ({ nowMs: 0, zone: "UTC", zoneSource: "utc-fallback" })
|
|
194
|
-
}
|
|
204
|
+
},
|
|
205
|
+
// Answers every call `error` naming itself, keeping the real contract's promise never to
|
|
206
|
+
// reject. A test that exercises a plugin's file I/O overrides this handle after the spread.
|
|
207
|
+
userFiles: Object.fromEntries(USER_FILE_METHODS.map((name) => [
|
|
208
|
+
name,
|
|
209
|
+
async () => ({
|
|
210
|
+
kind: "error",
|
|
211
|
+
detail: `userFiles.${name} is not wired in this test double`
|
|
212
|
+
})
|
|
213
|
+
]))
|
|
195
214
|
};
|
|
196
215
|
}
|
|
197
216
|
function defaultTestToolContext() {
|
package/dist/kay-agent.d.ts
CHANGED
|
@@ -1628,6 +1628,14 @@ export declare interface KayAgentPluginApi {
|
|
|
1628
1628
|
* the same zone-of-record port the session stamp uses; see {@link KayAppTime.snapshot}.
|
|
1629
1629
|
*/
|
|
1630
1630
|
readonly time: KayAppTime;
|
|
1631
|
+
/**
|
|
1632
|
+
* File I/O in user folders, run by the host on its own worker threads under a deadline and
|
|
1633
|
+
* answered as a typed result. Always present and ungated: unlike {@link filesystem} it takes
|
|
1634
|
+
* absolute paths and checks no grant, because it adds no authority a plugin's `node:fs` does not
|
|
1635
|
+
* already have. What it adds is that a slow or unresponsive folder never blocks the daemon. See
|
|
1636
|
+
* {@link KayAppUserFiles}.
|
|
1637
|
+
*/
|
|
1638
|
+
readonly userFiles: KayAppUserFiles;
|
|
1631
1639
|
/**
|
|
1632
1640
|
* Publish the plugin's declared events, for example so a default notification rule can tell the
|
|
1633
1641
|
* user what the agent just did. Always present, like {@link withJev}: there is nothing to
|
|
@@ -1946,6 +1954,135 @@ declare interface KayAppTimeSnapshot {
|
|
|
1946
1954
|
readonly zoneSource: "host" | "utc-fallback";
|
|
1947
1955
|
}
|
|
1948
1956
|
|
|
1957
|
+
export declare interface KayAppUserFileEntry {
|
|
1958
|
+
readonly name: string;
|
|
1959
|
+
/** From the directory entry itself: a symlink is reported as `symlink`, never followed. */
|
|
1960
|
+
readonly kind: KayAppUserFileKind;
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
/**
|
|
1964
|
+
* The plugin-facing user-files contract: file I/O in folders the user owns, done by the host on
|
|
1965
|
+
* its own worker threads.
|
|
1966
|
+
*
|
|
1967
|
+
* Ungated and always present, like `app.paths`. It confers no authority: a plugin runs in the
|
|
1968
|
+
* daemon's process and can call `node:fs` itself. What the door adds is where the work runs. A
|
|
1969
|
+
* `node:fs` sync call blocks the daemon's event loop for as long as the path takes to answer,
|
|
1970
|
+
* which under a macOS-protected folder or on a drive that stopped answering can be a very long
|
|
1971
|
+
* time (kay#11575); an async one holds a thread of the pool the whole daemon shares. The host runs
|
|
1972
|
+
* each request on a dedicated worker, gives it a deadline, and answers a typed result instead of
|
|
1973
|
+
* throwing.
|
|
1974
|
+
*
|
|
1975
|
+
* This is a leaf module. It lives beside both target SDKs, like `time.ts`, so the `kay.agent` SDK
|
|
1976
|
+
* exposes the same door without importing the `kay.app` surface.
|
|
1977
|
+
*/
|
|
1978
|
+
export declare type KayAppUserFileKind = "file" | "directory" | "symlink" | "other";
|
|
1979
|
+
|
|
1980
|
+
export declare interface KayAppUserFileListing {
|
|
1981
|
+
readonly entries: readonly KayAppUserFileEntry[];
|
|
1982
|
+
/** True when the directory had more entries than `maxEntries`; the rest were not read. */
|
|
1983
|
+
readonly entryLimitReached: boolean;
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
/**
|
|
1987
|
+
* File I/O in user folders, run by the host off the daemon's main thread. Every path is absolute.
|
|
1988
|
+
* No method rejects; each answers a {@link KayAppUserFilesResult}. This is not a sandbox: it does
|
|
1989
|
+
* not limit which paths a plugin may touch. Added in SDK 0.12.5: a plugin that calls it declares
|
|
1990
|
+
* `compatibility.sdk` `^0.12.5`, so an older Bonsai refuses a directory or repository plugin at
|
|
1991
|
+
* load instead of the call failing.
|
|
1992
|
+
*/
|
|
1993
|
+
export declare interface KayAppUserFiles {
|
|
1994
|
+
/** Follows symlinks. */
|
|
1995
|
+
stat(path: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<KayAppUserFileStat>>;
|
|
1996
|
+
/** UTF-8. */
|
|
1997
|
+
readText(path: string, options?: KayAppUserFilesReadTextOptions): Promise<KayAppUserFilesResult<string>>;
|
|
1998
|
+
list(path: string, options?: KayAppUserFilesListOptions): Promise<KayAppUserFilesResult<KayAppUserFileListing>>;
|
|
1999
|
+
/** Depth-first below `path`; symlinks are reported, never followed. */
|
|
2000
|
+
walk(path: string, options?: KayAppUserFilesWalkOptions): Promise<KayAppUserFilesResult<KayAppUserFileWalk>>;
|
|
2001
|
+
/** The path with every symlink resolved. */
|
|
2002
|
+
realpath(path: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<string>>;
|
|
2003
|
+
/**
|
|
2004
|
+
* Creates or replaces a UTF-8 file, not atomically. After `timeout` or `aborted` the write may
|
|
2005
|
+
* still happen.
|
|
2006
|
+
*/
|
|
2007
|
+
writeText(path: string, content: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<null>>;
|
|
2008
|
+
/** Creates the directory and any missing parents; succeeds when it already exists. */
|
|
2009
|
+
mkdir(path: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<null>>;
|
|
2010
|
+
rename(from: string, to: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<null>>;
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
export declare interface KayAppUserFilesCallOptions {
|
|
2014
|
+
/** Deadline in milliseconds from the call, waiting included. Default 5000, at most 60000. */
|
|
2015
|
+
readonly timeoutMs?: number;
|
|
2016
|
+
readonly signal?: AbortSignal;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
export declare interface KayAppUserFilesListOptions extends KayAppUserFilesCallOptions {
|
|
2020
|
+
/** Default 5000, at most 50000. */
|
|
2021
|
+
readonly maxEntries?: number;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
export declare interface KayAppUserFilesReadTextOptions extends KayAppUserFilesCallOptions {
|
|
2025
|
+
/** A larger file answers `error` with code `EFBIG`. Default 8 MiB, at most 64 MiB. */
|
|
2026
|
+
readonly maxBytes?: number;
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
/**
|
|
2030
|
+
* Every answer. `missing`: nothing exists at the path (ENOENT). `timeout`: no answer from the path
|
|
2031
|
+
* in time. Its `reason` is `deadline` (this request ran out of time), `cooling-down` (a request at
|
|
2032
|
+
* or above this path timed out recently, so the host did not try) or `saturated` (the host's
|
|
2033
|
+
* workers are stuck on other paths, so it did not try). `aborted`: the caller's signal fired.
|
|
2034
|
+
* `error`: anything else, with the Node error `code` when there is one (`EACCES`, `EISDIR`,
|
|
2035
|
+
* `EFBIG` for a file over `maxBytes`, `EINVAL` for a relative path).
|
|
2036
|
+
*/
|
|
2037
|
+
export declare type KayAppUserFilesResult<T> = {
|
|
2038
|
+
readonly kind: "ok";
|
|
2039
|
+
readonly value: T;
|
|
2040
|
+
} | {
|
|
2041
|
+
readonly kind: "missing";
|
|
2042
|
+
} | {
|
|
2043
|
+
readonly kind: "timeout";
|
|
2044
|
+
readonly reason: "deadline" | "cooling-down" | "saturated";
|
|
2045
|
+
readonly detail: string;
|
|
2046
|
+
} | {
|
|
2047
|
+
readonly kind: "aborted";
|
|
2048
|
+
} | {
|
|
2049
|
+
readonly kind: "error";
|
|
2050
|
+
readonly code?: string;
|
|
2051
|
+
readonly detail: string;
|
|
2052
|
+
};
|
|
2053
|
+
|
|
2054
|
+
export declare interface KayAppUserFileStat {
|
|
2055
|
+
/** What the path is after following symlinks; never `symlink`. */
|
|
2056
|
+
readonly kind: "file" | "directory" | "other";
|
|
2057
|
+
/** Bytes. */
|
|
2058
|
+
readonly size: number;
|
|
2059
|
+
/** Milliseconds since the Unix epoch. */
|
|
2060
|
+
readonly modifiedAtMs: number;
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
export declare interface KayAppUserFilesWalkOptions extends KayAppUserFilesCallOptions {
|
|
2064
|
+
/** Default 10000, at most 100000. */
|
|
2065
|
+
readonly maxEntries?: number;
|
|
2066
|
+
/** Deepest level reported; the root's children are depth 1. Default 16, at most 64. */
|
|
2067
|
+
readonly maxDepth?: number;
|
|
2068
|
+
/** Report and descend into dot-prefixed entries. Default true. */
|
|
2069
|
+
readonly includeHidden?: boolean;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
export declare interface KayAppUserFileWalk {
|
|
2073
|
+
readonly entries: readonly KayAppUserFileWalkEntry[];
|
|
2074
|
+
/** True when the walk stopped at `maxEntries`. */
|
|
2075
|
+
readonly entryLimitReached: boolean;
|
|
2076
|
+
/** True when a directory at `maxDepth` was reported but not descended into. */
|
|
2077
|
+
readonly depthLimitReached: boolean;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
export declare interface KayAppUserFileWalkEntry {
|
|
2081
|
+
/** Path segments below the walk's root. */
|
|
2082
|
+
readonly segments: readonly string[];
|
|
2083
|
+
readonly kind: KayAppUserFileKind;
|
|
2084
|
+
}
|
|
2085
|
+
|
|
1949
2086
|
declare const KayPluginManifestV1: z.ZodObject<{
|
|
1950
2087
|
protocol: z.ZodLiteral<"kay.plugin.v1">;
|
|
1951
2088
|
id: z.ZodString;
|
package/dist/kay-app.d.ts
CHANGED
|
@@ -2322,6 +2322,30 @@ export declare interface KayAppEndpoints {
|
|
|
2322
2322
|
register(name: string, handler: (payload: unknown) => unknown | Promise<unknown>): void;
|
|
2323
2323
|
}
|
|
2324
2324
|
|
|
2325
|
+
/**
|
|
2326
|
+
* The plugin-facing `kay.app` repository contract: what git checkout encloses a folder, read by
|
|
2327
|
+
* the host.
|
|
2328
|
+
*
|
|
2329
|
+
* Ungated and always present, like `app.paths`: a plugin runs in the daemon's process and could
|
|
2330
|
+
* read these files itself. What `app.repos` adds is that the host reads them asynchronously, under
|
|
2331
|
+
* a time limit, and shares one cache with the daemon. A plugin that reads `.git` files with
|
|
2332
|
+
* `node:fs` sync calls blocks the daemon's event loop for as long as each read takes, which on a
|
|
2333
|
+
* protected or unresponsive folder can be a very long time (kay#11575).
|
|
2334
|
+
*/
|
|
2335
|
+
/** One `[remote "<name>"]` in the repository's common `config`. */
|
|
2336
|
+
export declare interface KayAppGitRemote {
|
|
2337
|
+
readonly name: string;
|
|
2338
|
+
/** The `url =` value as written, still usable to connect. */
|
|
2339
|
+
readonly url: string;
|
|
2340
|
+
/**
|
|
2341
|
+
* `url` reduced to a key that is the same however the repository is addressed: no scheme, user
|
|
2342
|
+
* or port, lowercase host, no trailing `.git` (`git@github.com:org/repo.git` and
|
|
2343
|
+
* `https://github.com/org/repo` are both `github.com/org/repo`). Absent when `url` reduces to
|
|
2344
|
+
* nothing. For `owner/repo`, take the last two `/` segments.
|
|
2345
|
+
*/
|
|
2346
|
+
readonly identity?: string;
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2325
2349
|
/** A real human carrier projected into the source session's transcript. */
|
|
2326
2350
|
export declare interface KayAppHumanMessageSessionEvent extends KayAppSessionEventBase {
|
|
2327
2351
|
readonly type: "human-message";
|
|
@@ -2698,6 +2722,20 @@ export declare interface KayAppPluginApi {
|
|
|
2698
2722
|
* `Intl` or `process.env.TZ`; see {@link KayAppTime.snapshot}.
|
|
2699
2723
|
*/
|
|
2700
2724
|
readonly time: KayAppTime;
|
|
2725
|
+
/**
|
|
2726
|
+
* Git facts about any folder (its checkout, primary checkout, common dir, remotes, and git's
|
|
2727
|
+
* linked-worktree registry), read by the host asynchronously and under a time limit. Always
|
|
2728
|
+
* present and ungated, like {@link paths}. Use it instead of reading `.git` files with `node:fs`,
|
|
2729
|
+
* which blocks the daemon; see {@link KayAppRepos}.
|
|
2730
|
+
*/
|
|
2731
|
+
readonly repos: KayAppRepos;
|
|
2732
|
+
/**
|
|
2733
|
+
* File I/O in user folders (stat, read, list, walk, realpath, write, mkdir, rename), run by the
|
|
2734
|
+
* host on its own worker threads under a deadline and answered as a typed result. Always present
|
|
2735
|
+
* and ungated, like {@link paths}. Use it instead of `node:fs`, whose sync calls block the
|
|
2736
|
+
* daemon; see {@link KayAppUserFiles}.
|
|
2737
|
+
*/
|
|
2738
|
+
readonly userFiles: KayAppUserFiles;
|
|
2701
2739
|
/**
|
|
2702
2740
|
* Read/arrange access to the live desktop workspace. Present whenever the host has a workspace
|
|
2703
2741
|
* (a headless daemon may not); absence is a host fact, never a grant signal. Reading the snapshot
|
|
@@ -2872,6 +2910,102 @@ export declare interface KayAppRegisterUriModuleOptions {
|
|
|
2872
2910
|
readonly scope: "app";
|
|
2873
2911
|
}
|
|
2874
2912
|
|
|
2913
|
+
/** The git checkout that encloses a folder. Paths are absolute and not resolved through symlinks. */
|
|
2914
|
+
export declare interface KayAppRepoFacts {
|
|
2915
|
+
/** The directory holding the `.git` entry: the nearest checkout at or above the folder. */
|
|
2916
|
+
readonly repoRoot: string;
|
|
2917
|
+
/**
|
|
2918
|
+
* This checkout's own git directory: `<repoRoot>/.git`, or the target of a `.git` pointer file
|
|
2919
|
+
* (a linked worktree or a submodule). `null` when the pointer could not be read.
|
|
2920
|
+
*/
|
|
2921
|
+
readonly gitDir: string | null;
|
|
2922
|
+
/**
|
|
2923
|
+
* The git directory every worktree of the repository shares, as `git rev-parse
|
|
2924
|
+
* --git-common-dir` names it; for a submodule, its own `.git/modules/<name>`. `null` when the
|
|
2925
|
+
* host cannot place it (an unreadable pointer or `commondir` file).
|
|
2926
|
+
*/
|
|
2927
|
+
readonly commonDir: string | null;
|
|
2928
|
+
/**
|
|
2929
|
+
* The primary checkout that owns a linked worktree; `repoRoot` itself for an ordinary
|
|
2930
|
+
* checkout, a submodule, or a worktree of a bare repository. Every worktree of one repository
|
|
2931
|
+
* shares this value.
|
|
2932
|
+
*/
|
|
2933
|
+
readonly primaryCheckoutRoot: string;
|
|
2934
|
+
/** A stable name for the repository: the primary checkout's directory name. */
|
|
2935
|
+
readonly displayName: string;
|
|
2936
|
+
/** Every remote in the common `config`, in file order. Empty when the config is unreadable. */
|
|
2937
|
+
readonly remotes: readonly KayAppGitRemote[];
|
|
2938
|
+
/**
|
|
2939
|
+
* The `origin` remote. `absent` means the config was read and has no origin; `unavailable`
|
|
2940
|
+
* means the config could not be read, which is not the same answer and is usually temporary.
|
|
2941
|
+
*/
|
|
2942
|
+
readonly origin: {
|
|
2943
|
+
readonly kind: "origin";
|
|
2944
|
+
readonly url: string;
|
|
2945
|
+
readonly identity: string;
|
|
2946
|
+
} | {
|
|
2947
|
+
readonly kind: "absent";
|
|
2948
|
+
} | {
|
|
2949
|
+
readonly kind: "unavailable";
|
|
2950
|
+
readonly detail: string;
|
|
2951
|
+
};
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
export declare interface KayAppRepoReadOptions {
|
|
2955
|
+
/**
|
|
2956
|
+
* How old a cached answer may be, in milliseconds. `0` asks the host to read again; `Infinity`
|
|
2957
|
+
* accepts any cached answer. Default 5000.
|
|
2958
|
+
*/
|
|
2959
|
+
readonly maxAgeMs?: number;
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
/** What the host knows about one folder. */
|
|
2963
|
+
export declare type KayAppRepoResolution = {
|
|
2964
|
+
readonly kind: "resolved";
|
|
2965
|
+
/** The folder asked about, as an absolute path. */
|
|
2966
|
+
readonly folder: string;
|
|
2967
|
+
/**
|
|
2968
|
+
* Whether the folder itself exists. A folder that is gone still reports the checkout that
|
|
2969
|
+
* encloses where it was, so a deleted subdirectory keeps its repository.
|
|
2970
|
+
*/
|
|
2971
|
+
readonly folderExists: boolean;
|
|
2972
|
+
/** `null` when no checkout encloses the folder. */
|
|
2973
|
+
readonly repo: KayAppRepoFacts | null;
|
|
2974
|
+
} | {
|
|
2975
|
+
/**
|
|
2976
|
+
* The host could not answer: `timeout` when the reads took longer than the host's time limit
|
|
2977
|
+
* (the folder may be on an unresponsive drive), `error` when they failed unexpectedly. Not
|
|
2978
|
+
* cached, so asking again later may succeed. Treat it as "do not know", never as "no repo".
|
|
2979
|
+
*/
|
|
2980
|
+
readonly kind: "unknown";
|
|
2981
|
+
readonly folder: string;
|
|
2982
|
+
readonly reason: "timeout" | "error";
|
|
2983
|
+
readonly detail: string;
|
|
2984
|
+
};
|
|
2985
|
+
|
|
2986
|
+
/**
|
|
2987
|
+
* `app.repos`: git facts about folders, read by the host. Added in SDK 0.12.4: a plugin that calls
|
|
2988
|
+
* it declares `compatibility.sdk` `^0.12.4`, so an older Bonsai refuses a directory or repository
|
|
2989
|
+
* plugin at load instead of the call failing.
|
|
2990
|
+
*
|
|
2991
|
+
* Both members never reject and do not use `this`, so they are safe to destructure. Call them
|
|
2992
|
+
* where you need the answer, not inside `activate()`.
|
|
2993
|
+
*/
|
|
2994
|
+
export declare interface KayAppRepos {
|
|
2995
|
+
/**
|
|
2996
|
+
* The checkout that encloses `folder` (an absolute path), or why the host could not tell. A
|
|
2997
|
+
* relative path answers `unknown`.
|
|
2998
|
+
*/
|
|
2999
|
+
readonly resolve: (folder: string, options?: KayAppRepoReadOptions) => Promise<KayAppRepoResolution>;
|
|
3000
|
+
/**
|
|
3001
|
+
* The working directories of the linked worktrees git has registered under `commonDir`
|
|
3002
|
+
* (`<commonDir>/worktrees/<name>/gitdir`), including ones whose directory has since been
|
|
3003
|
+
* deleted. Empty when there are none; `undefined` when the registry or one of its entries could
|
|
3004
|
+
* not be read, or not in time. `undefined` is not cached; treat it as "do not know".
|
|
3005
|
+
*/
|
|
3006
|
+
readonly linkedWorktrees: (commonDir: string, options?: KayAppRepoReadOptions) => Promise<readonly string[] | undefined>;
|
|
3007
|
+
}
|
|
3008
|
+
|
|
2875
3009
|
/** What the plugin is asking for, and what it intends to do with it. */
|
|
2876
3010
|
export declare interface KayAppResolveCredentialRequest {
|
|
2877
3011
|
/** An auth service this plugin's own manifest declares, by its LOCAL id. */
|
|
@@ -4097,6 +4231,135 @@ export declare interface KayAppUiActionSessionEvent extends KayAppSessionEventBa
|
|
|
4097
4231
|
readonly toolCallId: string;
|
|
4098
4232
|
}
|
|
4099
4233
|
|
|
4234
|
+
export declare interface KayAppUserFileEntry {
|
|
4235
|
+
readonly name: string;
|
|
4236
|
+
/** From the directory entry itself: a symlink is reported as `symlink`, never followed. */
|
|
4237
|
+
readonly kind: KayAppUserFileKind;
|
|
4238
|
+
}
|
|
4239
|
+
|
|
4240
|
+
/**
|
|
4241
|
+
* The plugin-facing user-files contract: file I/O in folders the user owns, done by the host on
|
|
4242
|
+
* its own worker threads.
|
|
4243
|
+
*
|
|
4244
|
+
* Ungated and always present, like `app.paths`. It confers no authority: a plugin runs in the
|
|
4245
|
+
* daemon's process and can call `node:fs` itself. What the door adds is where the work runs. A
|
|
4246
|
+
* `node:fs` sync call blocks the daemon's event loop for as long as the path takes to answer,
|
|
4247
|
+
* which under a macOS-protected folder or on a drive that stopped answering can be a very long
|
|
4248
|
+
* time (kay#11575); an async one holds a thread of the pool the whole daemon shares. The host runs
|
|
4249
|
+
* each request on a dedicated worker, gives it a deadline, and answers a typed result instead of
|
|
4250
|
+
* throwing.
|
|
4251
|
+
*
|
|
4252
|
+
* This is a leaf module. It lives beside both target SDKs, like `time.ts`, so the `kay.agent` SDK
|
|
4253
|
+
* exposes the same door without importing the `kay.app` surface.
|
|
4254
|
+
*/
|
|
4255
|
+
export declare type KayAppUserFileKind = "file" | "directory" | "symlink" | "other";
|
|
4256
|
+
|
|
4257
|
+
export declare interface KayAppUserFileListing {
|
|
4258
|
+
readonly entries: readonly KayAppUserFileEntry[];
|
|
4259
|
+
/** True when the directory had more entries than `maxEntries`; the rest were not read. */
|
|
4260
|
+
readonly entryLimitReached: boolean;
|
|
4261
|
+
}
|
|
4262
|
+
|
|
4263
|
+
/**
|
|
4264
|
+
* File I/O in user folders, run by the host off the daemon's main thread. Every path is absolute.
|
|
4265
|
+
* No method rejects; each answers a {@link KayAppUserFilesResult}. This is not a sandbox: it does
|
|
4266
|
+
* not limit which paths a plugin may touch. Added in SDK 0.12.5: a plugin that calls it declares
|
|
4267
|
+
* `compatibility.sdk` `^0.12.5`, so an older Bonsai refuses a directory or repository plugin at
|
|
4268
|
+
* load instead of the call failing.
|
|
4269
|
+
*/
|
|
4270
|
+
export declare interface KayAppUserFiles {
|
|
4271
|
+
/** Follows symlinks. */
|
|
4272
|
+
stat(path: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<KayAppUserFileStat>>;
|
|
4273
|
+
/** UTF-8. */
|
|
4274
|
+
readText(path: string, options?: KayAppUserFilesReadTextOptions): Promise<KayAppUserFilesResult<string>>;
|
|
4275
|
+
list(path: string, options?: KayAppUserFilesListOptions): Promise<KayAppUserFilesResult<KayAppUserFileListing>>;
|
|
4276
|
+
/** Depth-first below `path`; symlinks are reported, never followed. */
|
|
4277
|
+
walk(path: string, options?: KayAppUserFilesWalkOptions): Promise<KayAppUserFilesResult<KayAppUserFileWalk>>;
|
|
4278
|
+
/** The path with every symlink resolved. */
|
|
4279
|
+
realpath(path: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<string>>;
|
|
4280
|
+
/**
|
|
4281
|
+
* Creates or replaces a UTF-8 file, not atomically. After `timeout` or `aborted` the write may
|
|
4282
|
+
* still happen.
|
|
4283
|
+
*/
|
|
4284
|
+
writeText(path: string, content: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<null>>;
|
|
4285
|
+
/** Creates the directory and any missing parents; succeeds when it already exists. */
|
|
4286
|
+
mkdir(path: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<null>>;
|
|
4287
|
+
rename(from: string, to: string, options?: KayAppUserFilesCallOptions): Promise<KayAppUserFilesResult<null>>;
|
|
4288
|
+
}
|
|
4289
|
+
|
|
4290
|
+
export declare interface KayAppUserFilesCallOptions {
|
|
4291
|
+
/** Deadline in milliseconds from the call, waiting included. Default 5000, at most 60000. */
|
|
4292
|
+
readonly timeoutMs?: number;
|
|
4293
|
+
readonly signal?: AbortSignal;
|
|
4294
|
+
}
|
|
4295
|
+
|
|
4296
|
+
export declare interface KayAppUserFilesListOptions extends KayAppUserFilesCallOptions {
|
|
4297
|
+
/** Default 5000, at most 50000. */
|
|
4298
|
+
readonly maxEntries?: number;
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4301
|
+
export declare interface KayAppUserFilesReadTextOptions extends KayAppUserFilesCallOptions {
|
|
4302
|
+
/** A larger file answers `error` with code `EFBIG`. Default 8 MiB, at most 64 MiB. */
|
|
4303
|
+
readonly maxBytes?: number;
|
|
4304
|
+
}
|
|
4305
|
+
|
|
4306
|
+
/**
|
|
4307
|
+
* Every answer. `missing`: nothing exists at the path (ENOENT). `timeout`: no answer from the path
|
|
4308
|
+
* in time. Its `reason` is `deadline` (this request ran out of time), `cooling-down` (a request at
|
|
4309
|
+
* or above this path timed out recently, so the host did not try) or `saturated` (the host's
|
|
4310
|
+
* workers are stuck on other paths, so it did not try). `aborted`: the caller's signal fired.
|
|
4311
|
+
* `error`: anything else, with the Node error `code` when there is one (`EACCES`, `EISDIR`,
|
|
4312
|
+
* `EFBIG` for a file over `maxBytes`, `EINVAL` for a relative path).
|
|
4313
|
+
*/
|
|
4314
|
+
export declare type KayAppUserFilesResult<T> = {
|
|
4315
|
+
readonly kind: "ok";
|
|
4316
|
+
readonly value: T;
|
|
4317
|
+
} | {
|
|
4318
|
+
readonly kind: "missing";
|
|
4319
|
+
} | {
|
|
4320
|
+
readonly kind: "timeout";
|
|
4321
|
+
readonly reason: "deadline" | "cooling-down" | "saturated";
|
|
4322
|
+
readonly detail: string;
|
|
4323
|
+
} | {
|
|
4324
|
+
readonly kind: "aborted";
|
|
4325
|
+
} | {
|
|
4326
|
+
readonly kind: "error";
|
|
4327
|
+
readonly code?: string;
|
|
4328
|
+
readonly detail: string;
|
|
4329
|
+
};
|
|
4330
|
+
|
|
4331
|
+
export declare interface KayAppUserFileStat {
|
|
4332
|
+
/** What the path is after following symlinks; never `symlink`. */
|
|
4333
|
+
readonly kind: "file" | "directory" | "other";
|
|
4334
|
+
/** Bytes. */
|
|
4335
|
+
readonly size: number;
|
|
4336
|
+
/** Milliseconds since the Unix epoch. */
|
|
4337
|
+
readonly modifiedAtMs: number;
|
|
4338
|
+
}
|
|
4339
|
+
|
|
4340
|
+
export declare interface KayAppUserFilesWalkOptions extends KayAppUserFilesCallOptions {
|
|
4341
|
+
/** Default 10000, at most 100000. */
|
|
4342
|
+
readonly maxEntries?: number;
|
|
4343
|
+
/** Deepest level reported; the root's children are depth 1. Default 16, at most 64. */
|
|
4344
|
+
readonly maxDepth?: number;
|
|
4345
|
+
/** Report and descend into dot-prefixed entries. Default true. */
|
|
4346
|
+
readonly includeHidden?: boolean;
|
|
4347
|
+
}
|
|
4348
|
+
|
|
4349
|
+
export declare interface KayAppUserFileWalk {
|
|
4350
|
+
readonly entries: readonly KayAppUserFileWalkEntry[];
|
|
4351
|
+
/** True when the walk stopped at `maxEntries`. */
|
|
4352
|
+
readonly entryLimitReached: boolean;
|
|
4353
|
+
/** True when a directory at `maxDepth` was reported but not descended into. */
|
|
4354
|
+
readonly depthLimitReached: boolean;
|
|
4355
|
+
}
|
|
4356
|
+
|
|
4357
|
+
export declare interface KayAppUserFileWalkEntry {
|
|
4358
|
+
/** Path segments below the walk's root. */
|
|
4359
|
+
readonly segments: readonly string[];
|
|
4360
|
+
readonly kind: KayAppUserFileKind;
|
|
4361
|
+
}
|
|
4362
|
+
|
|
4100
4363
|
export declare interface KayAppWhiteboard {
|
|
4101
4364
|
/**
|
|
4102
4365
|
* Binds read-only Whiteboard access to the exact context issued to this plugin's session source.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yaklabs-ai/plugin-sdk",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.5",
|
|
4
4
|
"description": "The Kay plugin SDK — types and helpers for authoring a Kay directory plugin.",
|
|
5
5
|
"author": "YakLabs",
|
|
6
6
|
"type": "module",
|
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
"skills"
|
|
52
52
|
],
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"zod": "^4.0.0"
|
|
54
|
+
"zod": "^4.0.0",
|
|
55
|
+
"yaml": "^2.9.0"
|
|
55
56
|
},
|
|
56
57
|
"publishConfig": {
|
|
57
58
|
"registry": "https://registry.npmjs.org",
|
|
@@ -55,6 +55,31 @@ and symlinking `node_modules/@kay` into the copy does not rescue it either: pnpm
|
|
|
55
55
|
layout does not expose workspace packages that way. The scaffold exists precisely so this
|
|
56
56
|
cannot happen.
|
|
57
57
|
|
|
58
|
+
## Check the manifest before you wait for a load
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
pnpm plugin:check ~/.kay/plugins/widgets
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Run from the root of a Kay checkout, against the plugin directory. It runs the daemon's OWN
|
|
65
|
+
manifest schema and contract resolver (imported from `@kay/plugin-protocol`, not re-implemented),
|
|
66
|
+
so it answers "would Kay accept this manifest?" in about a second instead of after a load cycle.
|
|
67
|
+
Run it after every manifest edit, and before concluding a plugin is broken for some deeper reason.
|
|
68
|
+
|
|
69
|
+
It prints the resolved global contribution ids, exits non-zero when the plugin would be refused,
|
|
70
|
+
and separates the two outcomes that look alike from outside:
|
|
71
|
+
|
|
72
|
+
- **Refused outright** — schema-invalid manifest, a reserved `kay.*` / `herd.*` id, a declared
|
|
73
|
+
path escaping the plugin directory, or contributions that do not resolve. Every contribution
|
|
74
|
+
shares ONE flat `<namespace>.<localId>` space ACROSS kinds, so a tool and an endpoint both
|
|
75
|
+
called `inspect` collide and the whole plugin is refused (#8936).
|
|
76
|
+
- **Loads, minus something** — a retired `compatibility` / `permissions.byTarget` key the parser
|
|
77
|
+
strips silently, a `toolName` that is not the namespace-derived name (that tool is excluded and
|
|
78
|
+
nothing tells the user), a missing entrypoint or skill file.
|
|
79
|
+
|
|
80
|
+
It reads the manifest only. A clean verdict says nothing about `activate()` throwing, the
|
|
81
|
+
disclosure audit, or an id another installed plugin already claims.
|
|
82
|
+
|
|
58
83
|
## The SDK resolution contract
|
|
59
84
|
|
|
60
85
|
- Plugin source imports `@kay/plugin-sdk/<subpath>` as a **bare specifier**
|
|
@@ -123,8 +148,10 @@ edited.
|
|
|
123
148
|
IS a git repository: a version is a commit, and a directory without git backing fails to
|
|
124
149
|
load (the plugin's row says exactly that, with the fix). So after writing the files, commit
|
|
125
150
|
them: `git init && git add -A && git commit -m "adopt plugin"` inside the plugin directory.
|
|
126
|
-
The daemon watches the root,
|
|
127
|
-
|
|
151
|
+
The daemon watches the root and, under the watch, checks each plugin's Git HEAD every 5
|
|
152
|
+
seconds, so it notices the plugin by itself within about 10 seconds even when file events
|
|
153
|
+
are not arriving: no verb called and no restart. The source refresh verb forces the walk if
|
|
154
|
+
you want it now.
|
|
128
155
|
|
|
129
156
|
The root is `<home>/.kay/plugins`, and **`<home>` is the running instance's home**: a dev
|
|
130
157
|
app launched against its own state root has its own plugins root, not your real
|
|
@@ -144,9 +171,13 @@ list` names the exact local root — because writing into the wrong one produces
|
|
|
144
171
|
commit** identifies the serving snapshot. Try new tools in a fresh session;
|
|
145
172
|
an existing session may still hold its previous generation.
|
|
146
173
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
174
|
+
Kay notices a commit within about 10 seconds even with no file events, and acquiring it may
|
|
175
|
+
take up to 30 seconds more. A **Running commit** still behind `git rev-parse --short HEAD` a
|
|
176
|
+
minute later, with a diagnostic on the plugin's row, is a failed read: fix what it names
|
|
177
|
+
instead of refreshing and waiting. Without a diagnostic, refresh the `local` source. A failed committed
|
|
178
|
+
update keeps the last working revision and shows that diagnostic. Fix and commit the
|
|
179
|
+
repair. Explicit source refresh skips the wait and retries a failed read; a commit
|
|
180
|
+
does not normally need it, and it never activates uncommitted files. Keep runtime data in
|
|
150
181
|
host-owned plugin storage, not beside the snapshot's source files.
|
|
151
182
|
|
|
152
183
|
4. **Grant what the manifest asks for, or activation is refused.** Anything declared under
|
|
@@ -177,9 +208,10 @@ list` names the exact local root — because writing into the wrong one produces
|
|
|
177
208
|
The commit is the activation boundary, so run these against the working tree first. Each catches
|
|
178
209
|
a failure class the load would otherwise be the first thing to surface:
|
|
179
210
|
|
|
180
|
-
1. **
|
|
181
|
-
|
|
182
|
-
|
|
211
|
+
1. **Run `pnpm plugin:check <dir>`** from a Kay checkout. It validates `kay-plugin.json` against
|
|
212
|
+
`KayPluginManifestV1` and resolves its contributions with the daemon's own resolver (both from
|
|
213
|
+
`@kay/plugin-protocol`), not just "it is valid JSON": that schema and resolver are the gate the
|
|
214
|
+
scan actually runs. A non-zero exit means the plugin would be refused.
|
|
183
215
|
2. **Run the disclosure audit yourself**: `auditPluginDisclosure` from `@kay/plugin-sdk/disclosure`.
|
|
184
216
|
It is not redundant with typecheck. A plugin shipped a `search` verb with no `verbDocs` entry
|
|
185
217
|
that typechecked clean and would have failed the whole `kay.agent` target.
|
|
@@ -229,6 +261,8 @@ on the plugin's row (or on the plugin source's row, when the whole source found
|
|
|
229
261
|
- **The disclosure audit is fail-closed** and runs on every directory plugin; a violation fails
|
|
230
262
|
the whole `kay.agent` target. Run `auditPluginDisclosure` yourself before committing, per the
|
|
231
263
|
pre-commit checks above.
|
|
264
|
+
- **Before any of the above, run `pnpm plugin:check <dir>`.** A manifest the daemon cannot
|
|
265
|
+
resolve produces a plugin that never appears, and the check names the reason without a load.
|
|
232
266
|
|
|
233
267
|
## Declaring a scheduled workflow
|
|
234
268
|
|