@sokel-dev/plugin-sdk 0.3.0 → 0.5.3
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 +62 -47
- package/README.zh-CN.md +95 -0
- package/dist/src/auth.d.ts +12 -8
- package/dist/src/auth.js +2 -9
- package/dist/src/auth.js.map +1 -1
- package/dist/src/contract.d.ts +27 -9
- package/dist/src/contract.js +10 -9
- package/dist/src/contract.js.map +1 -1
- package/dist/src/env.d.ts +1 -6
- package/dist/src/env.js +6 -3
- package/dist/src/env.js.map +1 -1
- package/dist/src/events.d.ts +32 -23
- package/dist/src/events.js +32 -31
- package/dist/src/events.js.map +1 -1
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.js +6 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/nats.d.ts +22 -16
- package/dist/src/nats.js +83 -64
- package/dist/src/nats.js.map +1 -1
- package/dist/src/plugin.d.ts +24 -19
- package/dist/src/plugin.js +42 -29
- package/dist/src/plugin.js.map +1 -1
- package/dist/src/runtime.d.ts +30 -23
- package/dist/src/runtime.js +28 -25
- package/dist/src/runtime.js.map +1 -1
- package/dist/src/webhook.d.ts +15 -10
- package/dist/src/webhook.js +8 -12
- package/dist/src/webhook.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# Sokel Plugin SDK — Node.js / TypeScript
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
[简体中文](README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
Write [Sokel](https://github.com/sokel-dev) plugins in TypeScript. The contract lives in a
|
|
6
|
+
language-neutral `manifest.yml`; `sokel-gen` turns it into TypeScript interfaces and typed
|
|
7
|
+
registration functions, and the SDK handles registration, transport, credentials, files, heartbeats
|
|
8
|
+
and reconnects.
|
|
5
9
|
|
|
6
10
|
```ts
|
|
7
11
|
onIssuesList(p, async (ctx, in_) => {
|
|
@@ -10,72 +14,83 @@ onIssuesList(p, async (ctx, in_) => {
|
|
|
10
14
|
});
|
|
11
15
|
```
|
|
12
16
|
|
|
13
|
-
`in_.project`
|
|
17
|
+
A typo in `in_.project` is a compile error, not a failed call in production — there is no `any`
|
|
18
|
+
anywhere in your code.
|
|
14
19
|
|
|
15
|
-
##
|
|
20
|
+
## Install
|
|
16
21
|
|
|
17
22
|
```bash
|
|
18
23
|
npm install @sokel-dev/plugin-sdk
|
|
19
|
-
go install github.com/sokel-dev/sokel-plugin-sdk/cmd/sokel-gen@latest #
|
|
24
|
+
go install github.com/sokel-dev/sokel-plugin-sdk/cmd/sokel-gen@latest # the generator
|
|
20
25
|
```
|
|
21
26
|
|
|
22
|
-
`sokel-gen`
|
|
27
|
+
`sokel-gen` is a single binary (written in Go) used **only at generation time**; running a plugin
|
|
28
|
+
does not need it.
|
|
23
29
|
|
|
24
|
-
##
|
|
30
|
+
## Four steps
|
|
25
31
|
|
|
26
32
|
```bash
|
|
27
33
|
sokel-gen init -lang ts ./my-plugin
|
|
28
|
-
cd my-plugin
|
|
29
|
-
|
|
30
|
-
sokel-gen generate . # sokel.yaml → src/sokel.gen.ts
|
|
34
|
+
cd my-plugin && npm install
|
|
35
|
+
sokel-gen generate . # manifest.yml → src/sokel.gen.ts
|
|
31
36
|
npm run build && npm start
|
|
32
37
|
```
|
|
33
38
|
|
|
34
|
-
1.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
1. **Declare** — `manifest.yml`: operations, events, credentials, authentication. Format:
|
|
40
|
+
[docs/manifest.md](../docs/manifest.md), or run `sokel-gen docs`.
|
|
41
|
+
2. **Generate** — `sokel-gen generate .` writes `src/sokel.gen.ts`: an `XxxIn` / `XxxOut` interface
|
|
42
|
+
pair and an `onXxx(p, fn)` per operation; a payload interface and a
|
|
43
|
+
`triggerXxx(ctx, eventId, payload)` per event.
|
|
44
|
+
3. **Implement** — handler signatures are fully concrete; return a value or a promise.
|
|
45
|
+
4. **Connect** — `await p.run()`. A plugin **dials out**: no inbound port, no public IP, no firewall
|
|
46
|
+
hole.
|
|
39
47
|
|
|
40
|
-
##
|
|
48
|
+
## Why not zod
|
|
41
49
|
|
|
42
|
-
zod schema
|
|
43
|
-
|
|
50
|
+
A zod schema is a runtime object, so declaring the contract in zod means the contract only exists
|
|
51
|
+
once the process is running — and every language would have to interpret that DSL for itself. The
|
|
52
|
+
declaration stays in `manifest.yml`; TypeScript takes only the types, which is what TypeScript is good
|
|
53
|
+
at: compile-time checks, zero runtime cost.
|
|
44
54
|
|
|
45
|
-
##
|
|
55
|
+
## What you can do
|
|
46
56
|
|
|
47
|
-
|
|
|
57
|
+
| Task | How |
|
|
48
58
|
|---|---|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
| Read credentials | `ctx.credentialAs<Credential>()` |
|
|
60
|
+
| Read an input file's bytes | `await ctx.fetch(in_.file)` |
|
|
61
|
+
| Produce a file | `await ctx.upload(name, mime, bytes)`, or `await ctx.uploadFile(path)` for large files |
|
|
62
|
+
| Stream output | `out.text(...)` frame by frame for humans, `out.vars({...})` for downstream nodes |
|
|
63
|
+
| Push an event | `await triggerMessage(ctx, eventId, {...})` |
|
|
64
|
+
| Long-running event source | `p.registerSource(id, label, fn)`; loop while `!ctx.stopped` |
|
|
65
|
+
| Handle a platform-relayed webhook | `p.registerWebhook(fn)`, return `ok()` / `text(401, "...")` |
|
|
66
|
+
| Collaborative authentication | `p.registerAuth({ start, poll, submit })` |
|
|
67
|
+
| Refresh a session credential | `await ctx.updateCredential({ session: "…" })` |
|
|
68
|
+
| Report runtime state | `ctx.reportStatus("auth_required", "…")` |
|
|
69
|
+
|
|
70
|
+
`uploadFile(path)` streams from disk: memory stays at one chunk (1 MiB) regardless of file size.
|
|
71
|
+
Anything above a few hundred megabytes should use it — `upload(bytes)` reads the whole file into
|
|
72
|
+
memory first, and the symptom of that is a container mysteriously killed by the OOM reaper.
|
|
73
|
+
|
|
74
|
+
## Configuration
|
|
75
|
+
|
|
76
|
+
The SDK reads `SOKEL_`-prefixed environment variables:
|
|
77
|
+
|
|
78
|
+
| Variable | Required | Meaning |
|
|
65
79
|
|---|---|---|
|
|
66
|
-
| `SOKEL_ENDPOINT` |
|
|
67
|
-
| `SOKEL_TOKEN` |
|
|
68
|
-
| `SOKEL_NATS_TOKEN` |
|
|
69
|
-
| `SOKEL_NATS_CA` |
|
|
70
|
-
| `SOKEL_INSTANCE_ID` |
|
|
71
|
-
| `SOKEL_REGION` |
|
|
80
|
+
| `SOKEL_ENDPOINT` | yes | `nats://broker:4222`, or an `https://` platform URL to discover the broker from |
|
|
81
|
+
| `SOKEL_TOKEN` | yes | Access-group token (`skp_…`) identifying plugin + workspace |
|
|
82
|
+
| `SOKEL_NATS_TOKEN` | no | Broker-level auth |
|
|
83
|
+
| `SOKEL_NATS_CA` | no | Custom CA bundle for `tls://` brokers |
|
|
84
|
+
| `SOKEL_INSTANCE_ID` | no | Pin a replica identity (otherwise derived from the token and cached on disk) |
|
|
85
|
+
| `SOKEL_REGION` | no | Region label for the replica |
|
|
72
86
|
|
|
73
|
-
|
|
87
|
+
Credentials are never stored by the plugin: the platform injects the resolved fields with each call.
|
|
74
88
|
|
|
75
|
-
##
|
|
89
|
+
## Example
|
|
76
90
|
|
|
77
|
-
[`examples/kitchen-sink`](../examples/kitchen-sink)
|
|
78
|
-
|
|
91
|
+
[`examples/kitchen-sink`](../examples/kitchen-sink) covers every shape — each field type, files,
|
|
92
|
+
streaming, events, webhooks, collaborative auth — and the Node and Python implementations share one
|
|
93
|
+
declaration.
|
|
79
94
|
|
|
80
95
|
```bash
|
|
81
96
|
cd examples/kitchen-sink/node
|
|
@@ -83,7 +98,7 @@ npm install && npm run build
|
|
|
83
98
|
SOKEL_ENDPOINT=nats://localhost:4222 SOKEL_TOKEN=skp_xxx npm start
|
|
84
99
|
```
|
|
85
100
|
|
|
86
|
-
##
|
|
101
|
+
## Developing this SDK
|
|
87
102
|
|
|
88
103
|
```bash
|
|
89
104
|
pnpm install
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Sokel Plugin SDK — Node.js / TypeScript
|
|
2
|
+
|
|
3
|
+
用 TypeScript 写 [Sokel](https://github.com/sokel-dev) 插件。契约写在 `manifest.yml` 里(语言中立),
|
|
4
|
+
`sokel-gen` 把它生成成 TS 接口与类型化的注册口;SDK 负责注册、传输、凭证、文件、心跳与重连。
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
onIssuesList(p, async (ctx, in_) => {
|
|
8
|
+
const issues = await client.listIssues(in_.project, in_.state);
|
|
9
|
+
return { issues, count: issues.length };
|
|
10
|
+
});
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`in_.project` 拼错是编译错误,不是线上的一次失败调用——代码里没有任何 `any`。
|
|
14
|
+
|
|
15
|
+
## 装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @sokel-dev/plugin-sdk
|
|
19
|
+
go install github.com/sokel-dev/sokel-plugin-sdk/cmd/sokel-gen@latest # 生成器
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`sokel-gen` 是个单文件二进制(Go 写的),只在**生成时**用到;跑插件时不需要它。
|
|
23
|
+
|
|
24
|
+
## 四步
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
sokel-gen init -lang ts ./my-plugin
|
|
28
|
+
cd my-plugin
|
|
29
|
+
npm install
|
|
30
|
+
sokel-gen generate . # manifest.yml → src/sokel.gen.ts
|
|
31
|
+
npm run build && npm start
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
1. **声明** —— `manifest.yml`:操作、事件、凭证、认证方式。格式见 [docs/manifest.md](../docs/manifest.md)。
|
|
35
|
+
2. **生成** —— `sokel-gen generate .` 产出 `src/sokel.gen.ts`:每个操作一对 `XxxIn` / `XxxOut` 接口
|
|
36
|
+
和一个 `onXxx(p, fn)`;每个事件一个 payload 接口和一个 `triggerXxx(ctx, eventId, payload)`。
|
|
37
|
+
3. **实现** —— handler 签名完全具体,返回值可以是对象也可以是 Promise。
|
|
38
|
+
4. **连接** —— `await p.run()`。插件**出站**连平台:无入站端口、无公网 IP、无防火墙洞。
|
|
39
|
+
|
|
40
|
+
## 为什么不是 zod
|
|
41
|
+
|
|
42
|
+
zod schema 是运行时对象:用它声明契约意味着「契约只有跑起来才知道」,而且每种语言都得
|
|
43
|
+
自己解释一遍那套 DSL。声明留在 `manifest.yml`,TS 这边只要类型——类型在编译期,运行时零开销。
|
|
44
|
+
|
|
45
|
+
## 能力一览
|
|
46
|
+
|
|
47
|
+
| 要做的事 | 怎么写 |
|
|
48
|
+
|---|---|
|
|
49
|
+
| 读凭证 | `ctx.credentialAs<Credential>()` |
|
|
50
|
+
| 取入参文件的字节 | `await ctx.fetch(in_.file)` |
|
|
51
|
+
| 产出文件 | `await ctx.upload(name, mime, bytes)` → 放进出参 |
|
|
52
|
+
| 流式产出 | `out.text(...)` 逐帧给人看,`out.vars({...})` 给下游 |
|
|
53
|
+
| 推事件 | `await triggerMessage(ctx, eventId, {...})` |
|
|
54
|
+
| 常驻事件源 | `p.registerSource(id, label, fn)`,循环里判 `ctx.stopped` |
|
|
55
|
+
| 平台代收 webhook | `p.registerWebhook(fn)`,返回 `ok()` / `text(401, "...")` |
|
|
56
|
+
| 协作式认证 | `p.registerAuth({ start, poll, submit })` |
|
|
57
|
+
| 会话型凭证刷新 | `await ctx.updateCredential({ session: "…" })` |
|
|
58
|
+
| 自报运行态 | `ctx.reportStatus("auth_required", "…")` |
|
|
59
|
+
|
|
60
|
+
## 配置
|
|
61
|
+
|
|
62
|
+
SDK 读 `SOKEL_` 前缀的环境变量:
|
|
63
|
+
|
|
64
|
+
| 变量 | 必填 | 含义 |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `SOKEL_ENDPOINT` | 是 | `nats://broker:4222`,或 `https://` 平台地址(经 `/connect-info` 发现 broker) |
|
|
67
|
+
| `SOKEL_TOKEN` | 是 | 接入组 token(`skp_…`),平台据此认「插件 + 工作空间」 |
|
|
68
|
+
| `SOKEL_NATS_TOKEN` | 否 | broker 的传输层鉴权 |
|
|
69
|
+
| `SOKEL_NATS_CA` | 否 | `tls://` broker 的自定义 CA |
|
|
70
|
+
| `SOKEL_INSTANCE_ID` | 否 | 固定副本身份(默认按 token 指纹落盘复用) |
|
|
71
|
+
| `SOKEL_REGION` | 否 | 副本的地域标注 |
|
|
72
|
+
|
|
73
|
+
凭证从不由插件存储:平台随每次调用把解析后的字段下发下来。
|
|
74
|
+
|
|
75
|
+
## 例子
|
|
76
|
+
|
|
77
|
+
[`examples/kitchen-sink`](../examples/kitchen-sink) 覆盖了全部形态——每种字段、文件、流式、
|
|
78
|
+
事件、webhook、协作式认证各一份,Node 与 Python 实现的是同一份声明。
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cd examples/kitchen-sink/node
|
|
82
|
+
npm install && npm run build
|
|
83
|
+
SOKEL_ENDPOINT=nats://localhost:4222 SOKEL_TOKEN=skp_xxx npm start
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 开发本 SDK
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pnpm install
|
|
90
|
+
pnpm test # tsc + node --test
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
Apache-2.0.
|
package/dist/src/auth.d.ts
CHANGED
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Collaborative credential authentication: some credentials cannot be typed in by hand — a QR scan,
|
|
3
|
+
* a verification code, an OAuth consent page.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
+
* the panel's "log in" button -> start returns a challenge -> (scan / type it back)
|
|
6
|
+
* -> poll every 2s -> confirmed
|
|
5
7
|
*
|
|
6
|
-
*
|
|
7
|
-
* auth.start / auth.poll / auth.submit
|
|
8
|
-
*
|
|
8
|
+
* The shape is declared in manifest.yml under credential.auth; the handlers hang off the reserved
|
|
9
|
+
* operation ids auth.start / auth.poll / auth.submit. **Do not** register a business operation named
|
|
10
|
+
* auth_start: those three names were never reserved, so any plugin with an operation of that name
|
|
11
|
+
* made the panel's button appear out of nowhere.
|
|
9
12
|
*/
|
|
10
13
|
import type { Ctx } from "./runtime.js";
|
|
11
14
|
export declare const PENDING = "pending";
|
|
12
15
|
export declare const SCANNED = "scanned";
|
|
13
16
|
export declare const CONFIRMED = "confirmed";
|
|
14
17
|
export declare const EXPIRED = "expired";
|
|
15
|
-
/** start
|
|
18
|
+
/** What start hands back. The panel renders by kind: qr draws a code, input shows the prompt. */
|
|
16
19
|
export interface AuthChallenge {
|
|
17
20
|
authId?: string;
|
|
18
21
|
kind?: "qr" | "input";
|
|
19
|
-
/** data-uri
|
|
22
|
+
/** The QR image as a data-uri. */
|
|
20
23
|
qrImage?: string;
|
|
21
24
|
prompt?: string;
|
|
22
25
|
expiresIn?: number;
|
|
23
26
|
}
|
|
24
|
-
/** poll
|
|
27
|
+
/** The result of poll. Carry `session` only once confirmed — handing it over earlier makes the
|
|
28
|
+
* platform rewrite the credential row again and again. */
|
|
25
29
|
export interface AuthState {
|
|
26
30
|
status: string;
|
|
27
31
|
session?: Record<string, unknown>;
|
package/dist/src/auth.js
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* 面板点「登录」→ start 拿挑战 →(扫码 / 回填)→ 2s 轮询 poll → confirmed。
|
|
5
|
-
*
|
|
6
|
-
* 形态写在 sokel.yaml 的 credential.auth 里(声明式),处理器挂在保留操作 id
|
|
7
|
-
* auth.start / auth.poll / auth.submit 上——**不要**自己注册叫 auth_start 的业务操作:
|
|
8
|
-
* 那三个名字从来不是保留字,任何插件的同名业务操作都会让面板的按钮凭空出现。
|
|
9
|
-
*/
|
|
1
|
+
// Copyright 2026 The Sokel Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
10
3
|
export const PENDING = "pending";
|
|
11
4
|
export const SCANNED = "scanned";
|
|
12
5
|
export const CONFIRMED = "confirmed";
|
package/dist/src/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/auth.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/auth.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAiBtC,MAAM,CAAC,MAAM,OAAO,GAAG,SAAS,CAAC;AACjC,MAAM,CAAC,MAAM,OAAO,GAAG,SAAS,CAAC;AACjC,MAAM,CAAC,MAAM,SAAS,GAAG,WAAW,CAAC;AACrC,MAAM,CAAC,MAAM,OAAO,GAAG,SAAS,CAAC"}
|
package/dist/src/contract.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The runtime view of a contract (wire protocol §5).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* A contract is **data**: `sokel-gen` renders a CONTRACT constant from manifest.yml, and the runtime
|
|
5
|
+
* only looks things up in it and reports it. So there is no second Field validator here — that
|
|
6
|
+
* would be a second definition of the contract, and two definitions drift.
|
|
6
7
|
*/
|
|
7
|
-
/**
|
|
8
|
+
/** One input/output field (protocol §5's Field). Shape only; validation lives in sokel-gen. */
|
|
8
9
|
export interface Field {
|
|
9
10
|
name: string;
|
|
10
11
|
label?: string;
|
|
@@ -36,6 +37,11 @@ export interface OperationSpec {
|
|
|
36
37
|
stream?: boolean;
|
|
37
38
|
internal?: boolean;
|
|
38
39
|
timeoutSec?: number;
|
|
40
|
+
/** Which platform capability slot this operation fills ("rowstore.query" comes from
|
|
41
|
+
* `implements:` in manifest.yml). Absent on ordinary operations. It is reported as-is; the
|
|
42
|
+
* platform routes on it. Missing it here made every generated shell that declares `implements:`
|
|
43
|
+
* fail to compile. */
|
|
44
|
+
capability?: string;
|
|
39
45
|
inputs: Field[];
|
|
40
46
|
outputs: Field[];
|
|
41
47
|
}
|
|
@@ -49,9 +55,12 @@ export interface AuthFlowSpec {
|
|
|
49
55
|
kind: "qr" | "input" | "oauth";
|
|
50
56
|
steps?: string[];
|
|
51
57
|
}
|
|
52
|
-
/**
|
|
58
|
+
/** The shape of the generated CONTRACT. Keys match the registration payload (protocol §3)
|
|
59
|
+
* verbatim, so they are reported as-is with no translation step. */
|
|
53
60
|
export interface ContractData {
|
|
54
61
|
name?: string;
|
|
62
|
+
/** Publisher identity: `<org>/<name>` is how the plugin is addressed in the marketplace. */
|
|
63
|
+
org?: string;
|
|
55
64
|
label?: string;
|
|
56
65
|
desc?: string;
|
|
57
66
|
version?: string;
|
|
@@ -65,16 +74,24 @@ export interface ContractData {
|
|
|
65
74
|
scopes?: string[];
|
|
66
75
|
};
|
|
67
76
|
capabilities?: Record<string, boolean>;
|
|
77
|
+
/** Translations of the human-facing strings, keyed by locale then by the source string
|
|
78
|
+
* (`{"zh-CN": {"Row query": "按行查询"}}`). Generated from `locales/<lang>.json` next to the
|
|
79
|
+
* manifest; the platform renders whichever locale the viewer is in. */
|
|
80
|
+
locales?: Record<string, Record<string, string>>;
|
|
68
81
|
doc?: string;
|
|
69
82
|
doc_url?: string;
|
|
70
83
|
}
|
|
71
|
-
/**
|
|
84
|
+
/** Reserved operation ids (the auth flow).
|
|
85
|
+
*
|
|
86
|
+
* A **plain** business id cannot contain a dot (it must match ^[a-z][a-z0-9_]*$), but a capability
|
|
87
|
+
* slot's id does — `implements:` produces "rowstore.query" — so "has a dot" no longer means
|
|
88
|
+
* "reserved". Compare against these constants rather than looking for a dot. */
|
|
72
89
|
export declare const OP_AUTH_START = "auth.start";
|
|
73
90
|
export declare const OP_AUTH_POLL = "auth.poll";
|
|
74
91
|
export declare const OP_AUTH_SUBMIT = "auth.submit";
|
|
75
|
-
/**
|
|
92
|
+
/** The special operation name for platform-relayed webhooks (it reuses the call frame, §7b). */
|
|
76
93
|
export declare const OP_WEBHOOK = "__webhook__";
|
|
77
|
-
/**
|
|
94
|
+
/** Capability bit: registering a webhook handler *is* the declaration; the author does not repeat it. */
|
|
78
95
|
export declare const CAP_WEBHOOK = "webhook";
|
|
79
96
|
export declare class Contract {
|
|
80
97
|
readonly data: ContractData;
|
|
@@ -83,6 +100,7 @@ export declare class Contract {
|
|
|
83
100
|
operation(id: string): OperationSpec | undefined;
|
|
84
101
|
isStream(id: string): boolean;
|
|
85
102
|
eventIds(): string[];
|
|
86
|
-
/**
|
|
103
|
+
/** The contract half of the registration payload. Empty values are omitted (the protocol makes
|
|
104
|
+
* every new field optional). */
|
|
87
105
|
payload(): Record<string, unknown>;
|
|
88
106
|
}
|
package/dist/src/contract.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Copyright 2026 The Sokel Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/** Reserved operation ids (the auth flow).
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*/
|
|
7
|
-
/** 保留操作 id(认证流)。带点号,业务 id 产生不出来(业务 id 限定 ^[a-z][a-z0-9_]*$)。 */
|
|
5
|
+
* A **plain** business id cannot contain a dot (it must match ^[a-z][a-z0-9_]*$), but a capability
|
|
6
|
+
* slot's id does — `implements:` produces "rowstore.query" — so "has a dot" no longer means
|
|
7
|
+
* "reserved". Compare against these constants rather than looking for a dot. */
|
|
8
8
|
export const OP_AUTH_START = "auth.start";
|
|
9
9
|
export const OP_AUTH_POLL = "auth.poll";
|
|
10
10
|
export const OP_AUTH_SUBMIT = "auth.submit";
|
|
11
|
-
/**
|
|
11
|
+
/** The special operation name for platform-relayed webhooks (it reuses the call frame, §7b). */
|
|
12
12
|
export const OP_WEBHOOK = "__webhook__";
|
|
13
|
-
/**
|
|
13
|
+
/** Capability bit: registering a webhook handler *is* the declaration; the author does not repeat it. */
|
|
14
14
|
export const CAP_WEBHOOK = "webhook";
|
|
15
15
|
export class Contract {
|
|
16
16
|
data;
|
|
@@ -29,7 +29,8 @@ export class Contract {
|
|
|
29
29
|
eventIds() {
|
|
30
30
|
return (this.data.events ?? []).map((e) => e.id);
|
|
31
31
|
}
|
|
32
|
-
/**
|
|
32
|
+
/** The contract half of the registration payload. Empty values are omitted (the protocol makes
|
|
33
|
+
* every new field optional). */
|
|
33
34
|
payload() {
|
|
34
35
|
const out = { operations: this.operations() };
|
|
35
36
|
const d = this.data;
|
package/dist/src/contract.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/contract.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/contract.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAgFtC;;;;gFAIgF;AAChF,MAAM,CAAC,MAAM,aAAa,GAAG,YAAY,CAAC;AAC1C,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAC;AACxC,MAAM,CAAC,MAAM,cAAc,GAAG,aAAa,CAAC;AAE5C,gGAAgG;AAChG,MAAM,CAAC,MAAM,UAAU,GAAG,aAAa,CAAC;AAExC,yGAAyG;AACzG,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAC;AAErC,MAAM,OAAO,QAAQ;IACE;IAArB,YAAqB,IAAkB;QAAlB,SAAI,GAAJ,IAAI,CAAc;IAAG,CAAC;IAE3C,UAAU;QACR,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC;IACpC,CAAC;IAED,SAAS,CAAC,EAAU;QAClB,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,QAAQ,CAAC,EAAU;QACjB,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,CAAC;IAED,QAAQ;QACN,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACnD,CAAC;IAED;oCACgC;IAChC,OAAO;QACL,MAAM,GAAG,GAA4B,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QACvE,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;QACpB,IAAI,CAAC,CAAC,iBAAiB,EAAE,MAAM;YAAE,GAAG,CAAC,iBAAiB,GAAG,CAAC,CAAC,iBAAiB,CAAC;QAC7E,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM;YAAE,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;QAC5C,IAAI,CAAC,CAAC,aAAa,EAAE,MAAM;YAAE,GAAG,CAAC,aAAa,GAAG,CAAC,CAAC,aAAa,CAAC;QACjE,IAAI,CAAC,CAAC,SAAS;YAAE,GAAG,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC;QAC7C,IAAI,CAAC,CAAC,KAAK;YAAE,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;QACjC,IAAI,CAAC,CAAC,YAAY,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,MAAM;YAAE,GAAG,CAAC,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC;QAC5F,IAAI,CAAC,CAAC,GAAG;YAAE,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;QAC3B,IAAI,CAAC,CAAC,OAAO;YAAE,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC;QACvC,OAAO,GAAG,CAAC;IACb,CAAC;CACF"}
|
package/dist/src/env.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 插件侧环境变量的统一读法:把 SOKEL_ 前缀收在一处(对齐 Go 侧 pluginenv)。
|
|
3
|
-
*
|
|
4
|
-
* 没有第二个前缀的兼容层——认第二个前缀省下的是一次重新部署,换来的是一个没人敢摘的包袱。
|
|
5
|
-
*/
|
|
6
|
-
/** 读 SOKEL_<name>。name 不带前缀,如 env("TOKEN")。 */
|
|
1
|
+
/** Read SOKEL_<name>. `name` carries no prefix, e.g. env("TOKEN"). */
|
|
7
2
|
export declare function env(name: string): string;
|
|
8
3
|
export declare function envOr(name: string, fallback: string): string;
|
package/dist/src/env.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
// Copyright 2026 The Sokel Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
4
|
+
* One place to read the plugin's environment variables (mirrors pluginenv on the Go side).
|
|
3
5
|
*
|
|
4
|
-
*
|
|
6
|
+
* There is no compatibility layer for a second prefix: accepting one saves a single redeploy and
|
|
7
|
+
* buys a piece of history nobody dares remove.
|
|
5
8
|
*/
|
|
6
9
|
const PREFIX = "SOKEL_";
|
|
7
|
-
/**
|
|
10
|
+
/** Read SOKEL_<name>. `name` carries no prefix, e.g. env("TOKEN"). */
|
|
8
11
|
export function env(name) {
|
|
9
12
|
return (process.env[PREFIX + name] ?? "").trim();
|
|
10
13
|
}
|
package/dist/src/env.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/env.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/env.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAEtC;;;;;GAKG;AAEH,MAAM,MAAM,GAAG,QAAQ,CAAC;AAExB,sEAAsE;AACtE,MAAM,UAAU,GAAG,CAAC,IAAY;IAC9B,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,IAAY,EAAE,QAAgB;IAClD,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC;AAC/B,CAAC"}
|
package/dist/src/events.d.ts
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Event sources: the plugin pushes external events to the platform to start workflows (protocol §7).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* How this differs from operations: an operation is request/reply (the platform calls the plugin);
|
|
5
|
+
* an event is fire-and-forget (the plugin pushes to the platform).
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
* supervisor
|
|
7
|
+
* Many bots, one replica (protocol v1.3): every registration and heartbeat returns "the subset of
|
|
8
|
+
* credentials assigned to this replica", and the supervisor reconciles against it — one source
|
|
9
|
+
* instance per credential, cancelled when the credential goes away, restarted when its fields change.
|
|
8
10
|
*/
|
|
9
11
|
import type { FileRuntime, SokelFile } from "./runtime.js";
|
|
10
12
|
export declare const TRIGGER_SUBJECT = "sokel.trigger";
|
|
11
13
|
export declare const CREDENTIAL_UPDATE_SUBJECT = "sokel.credential.update";
|
|
12
|
-
/**
|
|
14
|
+
/** One entry of the registration reply's credentials list — a bot identity assigned here. */
|
|
13
15
|
export declare class CredEntry {
|
|
14
16
|
readonly id: string;
|
|
15
17
|
readonly fields: Record<string, string>;
|
|
16
18
|
constructor(id?: string, fields?: Record<string, string>);
|
|
17
|
-
/**
|
|
19
|
+
/** A stable signature of the fields; reconcile uses it to decide "fields changed -> restart". */
|
|
18
20
|
sig(): string;
|
|
19
21
|
}
|
|
20
22
|
export interface SourceState {
|
|
@@ -24,29 +26,32 @@ export interface SourceState {
|
|
|
24
26
|
error?: string;
|
|
25
27
|
since: string;
|
|
26
28
|
}
|
|
27
|
-
/**
|
|
29
|
+
/** Runtime state per source × credential. Reported with each registration/heartbeat so the panel
|
|
30
|
+
* can show every bot. */
|
|
28
31
|
export declare class StateBoard {
|
|
29
32
|
private readonly now;
|
|
30
33
|
private readonly m;
|
|
31
34
|
constructor(now?: () => string);
|
|
32
35
|
set(sourceId: string, credId: string, status: string, error?: string): void;
|
|
33
36
|
/**
|
|
34
|
-
*
|
|
37
|
+
* Overwrite only while the instance is still `running`.
|
|
35
38
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
39
|
+
* A source that reported its own status (auth_required, say) and then returned normally should not
|
|
40
|
+
* have that sentence overwritten on the way out: all the panel would show is "exited", and *why*
|
|
41
|
+
* it exited is the half that matters.
|
|
38
42
|
*/
|
|
39
43
|
setIfRunning(sourceId: string, credId: string, status: string, error?: string): void;
|
|
40
44
|
removeCred(credId: string): void;
|
|
41
45
|
snapshot(): SourceState[];
|
|
42
46
|
}
|
|
43
47
|
export type Publish = (subject: string, data: Uint8Array) => void | Promise<void>;
|
|
44
|
-
/**
|
|
48
|
+
/** The context for a long-running source or a webhook: push events, read and write back
|
|
49
|
+
* credentials, upload attachments, report state. */
|
|
45
50
|
export declare class SourceCtx {
|
|
46
51
|
readonly credential: Record<string, string>;
|
|
47
52
|
readonly credentialId: string;
|
|
48
53
|
readonly sourceId: string;
|
|
49
|
-
/**
|
|
54
|
+
/** Set to true when reconcile stops this instance. Long-poll loops run `while (!ctx.stopped)`. */
|
|
50
55
|
stopped: boolean;
|
|
51
56
|
private readonly token;
|
|
52
57
|
private readonly publish;
|
|
@@ -63,33 +68,36 @@ export declare class SourceCtx {
|
|
|
63
68
|
board?: StateBoard;
|
|
64
69
|
files?: FileRuntime;
|
|
65
70
|
});
|
|
66
|
-
/**
|
|
71
|
+
/** Read the credential into a typed shape (same as Ctx.credentialAs on the operation side). */
|
|
67
72
|
credentialAs<T extends object>(): Partial<T>;
|
|
68
73
|
/**
|
|
69
|
-
*
|
|
74
|
+
* Push one event (fire-and-forget).
|
|
70
75
|
*
|
|
71
|
-
* event
|
|
72
|
-
*
|
|
73
|
-
*
|
|
76
|
+
* `event` must be a declared event id: a typo fails here rather than turning into a message nobody
|
|
77
|
+
* on the platform claims. That failure mode has no symptoms — the plugin log looks fine and the
|
|
78
|
+
* workflow simply never starts. `eventId` is the idempotency key; the platform deduplicates on
|
|
79
|
+
* (plugin, event, eventId).
|
|
74
80
|
*/
|
|
75
81
|
trigger(event: string, eventId: string, payload: unknown): Promise<void>;
|
|
76
82
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
83
|
+
* Write a patch back to the credential bound to this instance (how a session-style credential
|
|
84
|
+
* refreshes itself while running).
|
|
85
|
+
* The platform is the only store for credentials; a plugin never persists them locally.
|
|
79
86
|
*/
|
|
80
87
|
updateCredential(patch: Record<string, string>): Promise<void>;
|
|
81
|
-
/**
|
|
88
|
+
/** Report state (an expired session becomes auth_required); it rides the heartbeat and lights up
|
|
89
|
+
* "needs login" in the panel. */
|
|
82
90
|
reportStatus(status: string, msg?: string): void;
|
|
83
91
|
upload(name: string, mime: string, data: Uint8Array): Promise<SokelFile>;
|
|
84
92
|
fetch(f: SokelFile): Promise<Uint8Array>;
|
|
85
93
|
}
|
|
86
|
-
/**
|
|
94
|
+
/** A long-running event source. The SDK runs fn in its own task; inside, ctx.trigger pushes. */
|
|
87
95
|
export interface Source {
|
|
88
96
|
id: string;
|
|
89
97
|
label: string;
|
|
90
98
|
fn: (ctx: SourceCtx) => Promise<void>;
|
|
91
99
|
}
|
|
92
|
-
/**
|
|
100
|
+
/** Per-credential supervisor: starts, stops and restarts instances to match the assigned set. */
|
|
93
101
|
export declare class SourceSupervisor {
|
|
94
102
|
private readonly start;
|
|
95
103
|
private readonly running;
|
|
@@ -97,5 +105,6 @@ export declare class SourceSupervisor {
|
|
|
97
105
|
reconcile(desired: CredEntry[]): void;
|
|
98
106
|
stopAll(): void;
|
|
99
107
|
}
|
|
100
|
-
/**
|
|
108
|
+
/** Empty (a plugin with no credentials) becomes one bare instance, so both cases take the same
|
|
109
|
+
* code path. */
|
|
101
110
|
export declare function desiredSourceCreds(creds: CredEntry[]): CredEntry[];
|