skillscript-runtime 0.23.1 → 0.24.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 +10 -0
- package/dist/bootstrap-from-env.d.ts +46 -0
- package/dist/bootstrap-from-env.d.ts.map +1 -0
- package/dist/bootstrap-from-env.js +130 -0
- package/dist/bootstrap-from-env.js.map +1 -0
- package/dist/cli.js +19 -164
- package/dist/cli.js.map +1 -1
- package/dist/connectors/registry.d.ts +9 -0
- package/dist/connectors/registry.d.ts.map +1 -1
- package/dist/connectors/registry.js +23 -0
- package/dist/connectors/registry.js.map +1 -1
- package/dist/connectors/skill-store.d.ts +1 -0
- package/dist/connectors/skill-store.d.ts.map +1 -1
- package/dist/connectors/skill-store.js +27 -0
- package/dist/connectors/skill-store.js.map +1 -1
- package/dist/connectors/sqlite-skill-store.d.ts +1 -0
- package/dist/connectors/sqlite-skill-store.d.ts.map +1 -1
- package/dist/connectors/sqlite-skill-store.js +10 -0
- package/dist/connectors/sqlite-skill-store.js.map +1 -1
- package/dist/connectors/types.d.ts +11 -0
- package/dist/connectors/types.d.ts.map +1 -1
- package/dist/connectors/types.js.map +1 -1
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +4 -0
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/spa/app.js +33 -5
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +22 -1
- package/dist/mcp-server.js.map +1 -1
- package/docs/adopter-playbook.md +39 -4
- package/docs/configuration.md +2 -2
- package/docs/connector-contract-reference.md +4 -0
- package/docs/sqlite-skill-store.md +3 -2
- package/examples/connectors/SkillStoreTemplate/SkillStoreTemplate.ts +19 -0
- package/examples/custom-bootstrap.example.ts +17 -7
- package/examples/skillscripts/hello-world.skill.provenance.json +1 -1
- package/package.json +1 -1
package/docs/adopter-playbook.md
CHANGED
|
@@ -149,7 +149,7 @@ $ my_store.search query="customer feedback" region="eu-west" cluster="prod" -> C
|
|
|
149
149
|
|
|
150
150
|
This skill body is locked to `my_store` — its specific kwargs (`region`, `cluster`) and response shape. To move to a different substrate, every call site has to be rewritten.
|
|
151
151
|
|
|
152
|
-
**⚠ Programmatic
|
|
152
|
+
**⚠ Programmatic adopters:** use **`bootstrapFromEnv()`** and it loads `$SKILLSCRIPT_HOME/connectors.json` (plus `.env` + `SKILLSCRIPT_*`) for you, exactly like the CLI. Only **raw `bootstrap()`** skips them — it reads `connectors.json` solely when you pass `connectorsConfigPath`, and skipping it makes `$ name.tool` calls fail at runtime with `unknown-connector` and no hint that the file simply wasn't read. See §"Programmatic bootstrap path" below — `bootstrapFromEnv()` is the recommended default; the raw-`bootstrap()` explicit-wiring pattern is for hand-assembled custom substrates.
|
|
153
153
|
|
|
154
154
|
### Picking — the tradeoff
|
|
155
155
|
|
|
@@ -481,9 +481,42 @@ skillfile shell-audit
|
|
|
481
481
|
|
|
482
482
|
Paste into your `$SKILLSCRIPT_HOME/.env` (review/narrow as desired). The audit is the canonical path — running it lets you make explicit policy decisions instead of discovering refusals through runtime errors.
|
|
483
483
|
|
|
484
|
-
### Programmatic bootstrap path
|
|
484
|
+
### Programmatic bootstrap path
|
|
485
485
|
|
|
486
|
-
**
|
|
486
|
+
**Most adopters operate via the web dashboard** after a one-time `skillfile init`. To stand that dashboard up from your own code, use **`bootstrapFromEnv()`** — the blessed entry point that wires a full runtime + `DashboardServer` from `$SKILLSCRIPT_HOME` *exactly the way the CLI does*: it loads `.env`, `skillscript.config.json`, and `connectors.json`, resolves the whole `SKILLSCRIPT_*` env cascade, calls `bootstrap()`, wires declarative triggers, and assembles the server.
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
import { bootstrapFromEnv } from "skillscript-runtime";
|
|
490
|
+
|
|
491
|
+
const { wired, server } = await bootstrapFromEnv({ mode: "dashboard" /* or "serve" */ });
|
|
492
|
+
wired.scheduler.start();
|
|
493
|
+
await server.start();
|
|
494
|
+
// On shutdown, reap stdio-bridged connector children (RemoteMcpConnector):
|
|
495
|
+
process.on("SIGTERM", async () => {
|
|
496
|
+
await wired.scheduler.stop();
|
|
497
|
+
await server.stop();
|
|
498
|
+
await wired.registry.disposeAll();
|
|
499
|
+
});
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Both are returned **unstarted** — you decide when to `start()`. Options: `mode`, `home` (default `$SKILLSCRIPT_HOME`), `configPath`, `connectorsConfigPath`, `port`, `host`, and `overrides`. **Precedence**: explicit option > `overrides` (passed to `bootstrap()`, wins last) > `SKILLSCRIPT_*` env > `skillscript.config.json` > default.
|
|
503
|
+
|
|
504
|
+
To wire your **own custom substrate** (e.g. a remote `SkillStore`), two paths:
|
|
505
|
+
- **Declarative** — if it's expressible in `connectors.json` via the `{ "type": "custom", "module": "./my-store.js", "export": "MyStore" }` substrate form, just declare it; `bootstrapFromEnv()` honors `substrate.*` like the CLI.
|
|
506
|
+
- **Instance injection** — pass a pre-built instance through `overrides`, the common path for a store that needs constructor wiring you hold in code:
|
|
507
|
+
```typescript
|
|
508
|
+
const { wired, server } = await bootstrapFromEnv({
|
|
509
|
+
mode: "dashboard",
|
|
510
|
+
overrides: { skillStore: new MyRemoteSkillStore({ /* client, auth, ... */ }) },
|
|
511
|
+
});
|
|
512
|
+
```
|
|
513
|
+
Everything else (data store, models, connectors, env cascade) still auto-wires from `$SKILLSCRIPT_HOME`. `overrides` accepts any `bootstrap()` option (`skillStore` / `dataStore` / `localModel` / `agentConnector` / allowlists / …).
|
|
514
|
+
|
|
515
|
+
This closes the silent CLI-vs-programmatic asymmetry described below; reach for raw `bootstrap()` only when you're hand-assembling a registry that neither `connectors.json` nor `overrides` can express.
|
|
516
|
+
|
|
517
|
+
#### Raw `bootstrap()` — the CLI-auto-vs-programmatic-explicit asymmetry
|
|
518
|
+
|
|
519
|
+
`bootstrapFromEnv()` does the discovery steps below for you. If you call **`bootstrap()` directly** instead (hand-assembling substrates), be aware the CLI (`skillfile dashboard` / `serve`) does several discovery steps automatically — load `$SKILLSCRIPT_HOME/.env`, read `SKILLSCRIPT_*` env vars, load `$SKILLSCRIPT_HOME/connectors.json` — that raw `bootstrap()` does NOT. Each surface then needs explicit wiring:
|
|
487
520
|
|
|
488
521
|
| Surface | CLI path | Programmatic path |
|
|
489
522
|
|---|---|---|
|
|
@@ -1103,7 +1136,9 @@ The `// ADOPTER:myorg —` prefix is greppable across merges; your future-self c
|
|
|
1103
1136
|
|
|
1104
1137
|
### 4. Treat `src/bootstrap.ts` as reference, not canonical
|
|
1105
1138
|
|
|
1106
|
-
|
|
1139
|
+
For the standard "wire the whole runtime + dashboard from `$SKILLSCRIPT_HOME` like the CLI does" case — which is most adopters — call **`bootstrapFromEnv()`** (see §"Programmatic bootstrap path"). It's the supported public entry point; you don't hand-assemble anything, and you swap substrates declaratively via `connectors.json`. Remember to `await wired.registry.disposeAll()` on shutdown so stdio-bridged connector children (RemoteMcpConnector) are reaped, not orphaned — `bootstrapFromEnv()` callers own teardown (the CLI does it for you).
|
|
1140
|
+
|
|
1141
|
+
Drop to raw `bootstrap()` + `Registry` only when you're hand-assembling a substrate that `connectors.json` can't express — import the public APIs (`Registry`, the connector classes, `loadConnectorsConfig`, `loadSkillscriptConfig`, etc.) rather than modifying the bundled bootstrap, which churns on every upstream release.
|
|
1107
1142
|
|
|
1108
1143
|
See `examples/custom-bootstrap.example.ts` for a worked walkthrough.
|
|
1109
1144
|
|
package/docs/configuration.md
CHANGED
|
@@ -375,7 +375,7 @@ Underscore-prefixed top-level keys (`_comment`, `_note_security`, etc.) are igno
|
|
|
375
375
|
|
|
376
376
|
Write `class FooSkillStore implements SkillStore { ... }` (or DataStore, LocalModel). Wire it via either:
|
|
377
377
|
|
|
378
|
-
**(a) Programmatic bootstrap (recommended today)** —
|
|
378
|
+
**(a) Programmatic bootstrap (recommended today)** — for the common case (wire everything from `$SKILLSCRIPT_HOME` like the CLI does), call **`bootstrapFromEnv()`** and declare your substrate in `connectors.json`; it loads `.env` + config + `connectors.json`, resolves the env cascade, and returns a fully-wired `{ wired, server }` (both unstarted). Reach for the raw-`Registry` assembly below only when hand-constructing a substrate `connectors.json` can't express:
|
|
379
379
|
|
|
380
380
|
```typescript
|
|
381
381
|
import { Registry, McpServer, Scheduler } from "skillscript-runtime";
|
|
@@ -386,7 +386,7 @@ registry.registerSkillStore("primary", new FooSkillStore({ /* config */ }));
|
|
|
386
386
|
// ... register other substrates, then construct Scheduler + McpServer + DashboardServer
|
|
387
387
|
```
|
|
388
388
|
|
|
389
|
-
See [`docs/adopter-playbook.md`](adopter-playbook.md) for the
|
|
389
|
+
See [`docs/adopter-playbook.md`](adopter-playbook.md) §"Programmatic bootstrap path" for both — `bootstrapFromEnv()` (recommended) and the raw pattern.
|
|
390
390
|
|
|
391
391
|
**(b) `connectors.json` custom form** (deferred to follow-up):
|
|
392
392
|
|
|
@@ -270,6 +270,10 @@ Adopter substrates with their own ownership concept (e.g., AMP's `author:<id>` t
|
|
|
270
270
|
|
|
271
271
|
**`delete()` is destructive in the bundled impls — permanent, name-reclaiming.** The contract signature is `delete(name): Promise<void>`. The bundled stores erase rather than tombstone: `FilesystemSkillStore` unlinks the skill file + version sidecar, `SqliteSkillStore` hard-cascades both tables. After delete the skill is gone from `query()`/`load()`/`metadata()`/`versions()`, the name frees up immediately for a fresh `store()` (clean history, no orphan rows), and there is no trash and no restore. Delete is **operator-only** (CLI `skillfile delete` / the dashboard) — there is no agent/MCP delete surface — and both surfaces gate it behind a confirm + reverse-dependency check. Your impl *may* soft-delete instead (tombstone + filter from normal views); the runtime only requires "remove from normal views," and recovery semantics are your store's concern.
|
|
272
272
|
|
|
273
|
+
**`version()` — OPTIONAL cheap change-token (helps remote stores).** A `version(): Promise<string>` method returns a store-wide token that fingerprints the whole namespace **without loading any skill bodies**. `skill_list` returns it as `catalog_version` and honors a caller's `if_none_match`: when the token still matches, the response is `{ not_modified: true }` and the catalog rebuild is skipped. That rebuild otherwise costs one `load()` *per skill* (to parse each entry's effectful footprint) — free against a local store, but a network round-trip per entry against a remote one, so a polling dashboard hammers the substrate. **Optional**: a store without `version()` just always rebuilds (today's behavior, no change).
|
|
274
|
+
|
|
275
|
+
**The contract invariant** — `version()` MUST change whenever the catalog's *observable content* changes: an add, a remove, a status change, **and a body edit even if the status is unchanged**. A token that fails this serves a stale catalog. ⚠ **The subtle trap:** a token over only `(id, status)` is exact in **secured mode** (an edit forces the skill back to `Draft`, so status moves) but goes **stale in unsecured mode** on a body-edit-with-unchanged-status. So fold a per-skill **content revision** (a content hash, version, or `updated_at`) into the fingerprint — don't ship a status-only token unless your deployment is secured-mode-only, and if it is, document that limitation. Bundled impls satisfy the invariant in *both* modes: `FilesystemSkillStore` hashes each skill file's `(name, mtime)` (a rewrite moves the mtime); `SqliteSkillStore` hashes `(name, status, current_version)` — `current_version` is the content hash. **Implement it on any network-backed SkillStore**, fingerprinting whatever your substrate exposes cheaply (a list ETag, a max-revision/seq, or a metadata digest), as long as every observable change moves it.
|
|
276
|
+
|
|
273
277
|
### DataStore conventions
|
|
274
278
|
|
|
275
279
|
**`summary`/`detail` split is convention, not contract field.** The DataStore contract gives `write()` a single `content: string`. Bundled `SqliteDataStore` maps this to `summary = first line (≤200 chars)` and `detail = full content`. Adopter substrates with native summary/detail concepts (AMP's `summary` + `detail` columns) can pre-compose and pass via `metadata`, but the basic mapping convention is "first line is the preview." Diverge and the dashboard's memory rendering looks weird, but skills still work.
|
|
@@ -141,7 +141,7 @@ SqliteSkillStore is the storage layer. Authoring happens above:
|
|
|
141
141
|
- **MCP tool**: agents call `skill_write` directly; same path as the dashboard
|
|
142
142
|
- **Programmatic**: your code calls `store.store(name, source, metadata?)` directly
|
|
143
143
|
|
|
144
|
-
The dashboard
|
|
144
|
+
The dashboard defaults to FilesystemSkillStore. For a SqliteSkillStore-backed dashboard, set `substrate.skill_store: "sqlite"` in `connectors.json` — both `skillfile dashboard` and the programmatic `bootstrapFromEnv()` honor it; no custom bootstrap needed.
|
|
145
145
|
|
|
146
146
|
---
|
|
147
147
|
|
|
@@ -164,6 +164,7 @@ When forking into your codebase:
|
|
|
164
164
|
2. Replace the SQL with your substrate's API (HTTP, DataStore, vector DB, etc.)
|
|
165
165
|
3. Update `staticCapabilities()` to match what your substrate actually supports — drop `supports_versioning` if you can't track history, drop `supports_tag_filter` if querying tags isn't tractable
|
|
166
166
|
4. Update `manifest()` to describe your substrate (`kind: "amp"` or whatever)
|
|
167
|
-
5.
|
|
167
|
+
5. **Optional but high-value for network-backed forks: implement `version()`** — a cheap store-wide change-token computed WITHOUT loading bodies (a list ETag, max-revision, or metadata digest), where any add/remove/edit/status-change moves it. It lets `skill_list` skip its N+1 catalog rebuild on unchanged polls (each entry otherwise costs a `load()` — one network round-trip per skill against a remote store). `SqliteSkillStore` hashes `(name, status, current_version)` in one body-free query. Skip it and `skill_list` just always rebuilds.
|
|
168
|
+
6. Tests: copy `tests/SqliteSkillStore.test.ts` as a starting point + run the conformance suite (`SkillStoreConformance.buildTests()` from `skillscript-runtime/testing`)
|
|
168
169
|
|
|
169
170
|
The conformance suite catches drift from the contract surface. If your fork passes, the runtime treats it interchangeably with FilesystemSkillStore / SqliteSkillStore / etc.
|
|
@@ -222,4 +222,23 @@ export class SkillStoreTemplate implements SkillStore {
|
|
|
222
222
|
// - Return VersionInfo with previous_status populated
|
|
223
223
|
throw new Error("TODO: update_status() — atomic status transition + audit row.");
|
|
224
224
|
}
|
|
225
|
+
|
|
226
|
+
// OPTIONAL (v0.24.0) — implement this on a NETWORK-BACKED store. It returns a
|
|
227
|
+
// cheap store-wide change-token computed WITHOUT loading skill bodies. The MCP
|
|
228
|
+
// `skill_list` tool returns it as `catalog_version` and honors a caller's
|
|
229
|
+
// `if_none_match`: when the token is unchanged it replies `{ not_modified: true }`
|
|
230
|
+
// and skips the catalog rebuild — which otherwise loads EVERY skill body (one
|
|
231
|
+
// network round-trip per skill) to parse its effectful footprint. For a remote
|
|
232
|
+
// store this is the difference between a chatty poll and a single cheap probe.
|
|
233
|
+
//
|
|
234
|
+
// Fingerprint whatever your substrate exposes cheaply (a list ETag, a
|
|
235
|
+
// max-revision/seq, or a metadata digest), as long as ANY add/remove/edit/
|
|
236
|
+
// status-change moves the token. DELETE this method if you can't compute one
|
|
237
|
+
// cheaply — it's optional, and `skill_list` just always rebuilds without it.
|
|
238
|
+
async version(): Promise<string> {
|
|
239
|
+
// TODO — e.g. a single metadata-only list:
|
|
240
|
+
// const rows = await this.api.list({ fields: ["id", "status", "rev"] }); // NO bodies
|
|
241
|
+
// return sha256(rows.map(r => `${r.id}:${r.status}:${r.rev}`).join("\n")).slice(0, 16);
|
|
242
|
+
throw new Error("TODO: version() — optional cheap change-token (no body loads). Delete if unsupported.");
|
|
243
|
+
}
|
|
225
244
|
}
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
// Worked example: writing a custom bootstrap that wires adopter-specific
|
|
2
2
|
// substrates against skillscript-runtime's public APIs.
|
|
3
3
|
//
|
|
4
|
-
// **
|
|
5
|
-
//
|
|
6
|
-
// `
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
4
|
+
// **First: do you even need this?** For the common case — wire the whole
|
|
5
|
+
// runtime + dashboard from `$SKILLSCRIPT_HOME` exactly like the CLI does —
|
|
6
|
+
// call `bootstrapFromEnv()` instead (it loads `.env` + `skillscript.config.json`
|
|
7
|
+
// + `connectors.json`, resolves the SKILLSCRIPT_* env cascade, and returns
|
|
8
|
+
// `{ wired, server }` unstarted). You can swap substrates declaratively via
|
|
9
|
+
// `connectors.json` (`substrate.skill_store: "sqlite"`, a custom class, etc.)
|
|
10
|
+
// without writing any of the assembly below. See docs/adopter-playbook.md
|
|
11
|
+
// §"Programmatic bootstrap path".
|
|
12
|
+
//
|
|
13
|
+
// **When to write your own bootstrap (this file).** Reach for raw `bootstrap()`
|
|
14
|
+
// + `Registry` only when hand-assembling a substrate `connectors.json` can't
|
|
15
|
+
// express. The bundled `bootstrap()` from `skillscript-runtime` wires
|
|
16
|
+
// `FilesystemSkillStore` + `OllamaLocalModel` + `SqliteDataStore` + the typed-
|
|
17
|
+
// contract bridges. If your deployment uses different substrates (your own data
|
|
18
|
+
// store, a hosted LLM API, an MCP server for agent delivery, etc.), assemble
|
|
19
|
+
// your own here rather than modifying the bundled one. Prevents merge conflicts
|
|
20
|
+
// on every upstream release that touches `src/bootstrap.ts`.
|
|
11
21
|
//
|
|
12
22
|
// **What to copy from this file.** Take what fits your deployment and
|
|
13
23
|
// drop the rest. The shape is: construct a Registry, register your
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillscript-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Runtime, compiler, lint, CLI, and dashboard for Skillscript — a small declarative language for authoring agent workflows.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Scott Shwarts <scotts@pobox.com>",
|