@velanir/openclaw-participation-gate 0.1.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/README.md +151 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +2718 -0
- package/openclaw.plugin.json +156 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Velanir OpenClaw Participation Gate
|
|
2
|
+
|
|
3
|
+
OpenClaw plugin that decides whether a digital coworker should participate in a shared channel, group, room, or thread before the main agent runs.
|
|
4
|
+
|
|
5
|
+
The plugin is designed for the `requireMention: false` use case: the coworker can observe channel traffic, but should only answer when the current turn is actually for that coworker.
|
|
6
|
+
|
|
7
|
+
## Package
|
|
8
|
+
|
|
9
|
+
- Package: `@velanir/openclaw-participation-gate`
|
|
10
|
+
- OpenClaw plugin id: `velanir-participation-gate`
|
|
11
|
+
- Runtime hooks: `before_dispatch`, `message_sent`
|
|
12
|
+
- Default mode: `shadow`
|
|
13
|
+
- Target OpenClaw plugin API: `>=2026.5.4`
|
|
14
|
+
|
|
15
|
+
The platform monorepo owns the source package in `packages/openclaw-plugins/participation-gate`. Customer machines install the built package into their OpenClaw runtime.
|
|
16
|
+
|
|
17
|
+
## Behavior
|
|
18
|
+
|
|
19
|
+
Direct messages always pass through. The gate only evaluates group and channel messages.
|
|
20
|
+
|
|
21
|
+
For group and channel messages, the plugin:
|
|
22
|
+
|
|
23
|
+
1. Records recent shared-room user and coworker messages for fallback context.
|
|
24
|
+
2. Loads participation context for the current coworker.
|
|
25
|
+
3. Applies deterministic rules:
|
|
26
|
+
- respond when the message clearly addresses this coworker
|
|
27
|
+
- skip when the message clearly addresses another known coworker
|
|
28
|
+
- treat sentence-start requests such as `Albus I need you to...` as direct address without hardcoding coworker names
|
|
29
|
+
4. Calls a lightweight classifier when deterministic rules do not decide the turn.
|
|
30
|
+
5. Returns `{ handled: true }` only when the decision is skip and `mode` is `enforce`.
|
|
31
|
+
|
|
32
|
+
Explicit mentions still bypass classifier enforcement, but the inbound turn is
|
|
33
|
+
recorded before bypass so later unmentioned follow-ups have context. Successful
|
|
34
|
+
`message_sent` events are also recorded as coworker replies, which lets the
|
|
35
|
+
classifier recognize follow-ups after this coworker already answered.
|
|
36
|
+
|
|
37
|
+
Any missing runtime dependency, missing context, invalid classifier output, classifier timeout, or classifier error fails open and lets the coworker respond. This is deliberate because false silence is worse than an occasional extra reply while the plugin is being rolled out.
|
|
38
|
+
|
|
39
|
+
## Classifier Contract
|
|
40
|
+
|
|
41
|
+
The classifier returns a scored participation decision:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{ "participationScore": 0.83 }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The plugin participates when `participationScore >= classifier.threshold`. The default threshold is `0.7`.
|
|
48
|
+
|
|
49
|
+
Legacy `{ "shouldRespond": true|false }` output is still accepted for
|
|
50
|
+
compatibility, but the prompt asks for `participationScore` only.
|
|
51
|
+
If the classifier output is malformed, the plugin retries once with a strict
|
|
52
|
+
JSON repair prompt. If the score can still be recovered from truncated output,
|
|
53
|
+
the recovered score is used for the skip/respond decision. If the retry is still
|
|
54
|
+
malformed with no recoverable score, the plugin fails open with score `1.0` and
|
|
55
|
+
logs `classifier_malformed` instead of treating it as a clean `classifier_true`.
|
|
56
|
+
|
|
57
|
+
`classifier.maxOutputTokens` limits the classifier response only. It does not cap input context.
|
|
58
|
+
|
|
59
|
+
Normal decision logs include the participation score, threshold, classifier
|
|
60
|
+
prompt version, prompt hash, input hash, parse status, attempt count, output
|
|
61
|
+
hash, and output length. Exact rendered prompt/input/raw classifier output and
|
|
62
|
+
parse errors are logged only when `logging.classifierDebug` and
|
|
63
|
+
`logging.includeContent` are both enabled.
|
|
64
|
+
|
|
65
|
+
## Context Contract
|
|
66
|
+
|
|
67
|
+
Production context should come from the platform:
|
|
68
|
+
|
|
69
|
+
```http
|
|
70
|
+
GET /v1/runtime/coworkers/{coworkerId}/participation-context
|
|
71
|
+
Authorization: DPoP <scoped runtime token>
|
|
72
|
+
DPoP: <proof for this GET request>
|
|
73
|
+
Accept: application/json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Expected response:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"data": {
|
|
81
|
+
"self": {
|
|
82
|
+
"id": "coworker_albus",
|
|
83
|
+
"names": ["Albus", "Albus Dumbledore"],
|
|
84
|
+
"roleSummary": "Executive assistant for Diagon leadership"
|
|
85
|
+
},
|
|
86
|
+
"coworkers": [
|
|
87
|
+
{
|
|
88
|
+
"id": "coworker_tanya",
|
|
89
|
+
"names": ["Tanya", "Tanya Dean", "Tanya Morales"],
|
|
90
|
+
"roleSummary": "Executive assistant"
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The Platform endpoint must use the runtime identity protocol. The provider defaults to `platform.authMode: "runtime"`, requests `scope=participation-context:read` from `/v1/runtime/token`, and signs a DPoP proof for each context read.
|
|
98
|
+
|
|
99
|
+
The endpoint must be derived from platform-owned coworker and organization data. Adding or removing a digital coworker should not require editing plugin configuration on customer machines.
|
|
100
|
+
|
|
101
|
+
`names` should include display names, short names, and channel-specific aliases or mention tokens when the platform knows them. The deterministic direct-address rules depend on these aliases, and the classifier receives the same identity set.
|
|
102
|
+
|
|
103
|
+
Static context exists only for tests and local development.
|
|
104
|
+
|
|
105
|
+
## Security
|
|
106
|
+
|
|
107
|
+
The platform provider intentionally does not fall back to a broad `OCT8_API_SECRET`.
|
|
108
|
+
|
|
109
|
+
The production credential is a short-lived, DPoP-bound runtime token that can only read the current coworker's participation context. Static `platform.token` / `OCT8_PARTICIPATION_CONTEXT_TOKEN` is only read when `platform.authMode` is explicitly set to `static-token`; that mode remains prototype-only and must not be used to bypass runtime identity in live customer deployments.
|
|
110
|
+
|
|
111
|
+
Runtime mode expects the same runtime identity environment used by `oct8-secrets-runtime-v2`: `OCT8_API_URL`, `OCT8_RUNTIME_IDENTITY_ID`, `OCT8_RUNTIME_STATE_DIR`, and optionally `OCT8_RUNTIME_TOKEN_ISSUER` / `OCT8_RUNTIME_KEY_ID`. It does not read `OCT8_API_SECRET`.
|
|
112
|
+
|
|
113
|
+
## Example Config
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"mode": "shadow",
|
|
118
|
+
"classifier": {
|
|
119
|
+
"provider": "openai-codex",
|
|
120
|
+
"model": "gpt-5.5",
|
|
121
|
+
"timeoutMs": 5000,
|
|
122
|
+
"maxOutputTokens": 32,
|
|
123
|
+
"threshold": 0.7
|
|
124
|
+
},
|
|
125
|
+
"context": {
|
|
126
|
+
"source": "platform",
|
|
127
|
+
"maxMessages": 5,
|
|
128
|
+
"refreshMs": 300000
|
|
129
|
+
},
|
|
130
|
+
"platform": {
|
|
131
|
+
"authMode": "runtime",
|
|
132
|
+
"baseUrl": "https://api.velanir.ai",
|
|
133
|
+
"coworkerId": "coworker_albus"
|
|
134
|
+
},
|
|
135
|
+
"logging": {
|
|
136
|
+
"decisions": true,
|
|
137
|
+
"includeContent": false,
|
|
138
|
+
"classifierDebug": false
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Use `shadow` first to validate decisions in logs. Move to `enforce` only after the classifier and context source are behaving as expected. Platform-rendered configs should choose the coworker's configured OpenClaw model/provider unless an explicit classifier override is present.
|
|
144
|
+
|
|
145
|
+
## Development
|
|
146
|
+
|
|
147
|
+
```sh
|
|
148
|
+
pnpm --filter=@velanir/openclaw-participation-gate check-types
|
|
149
|
+
pnpm --filter=@velanir/openclaw-participation-gate test
|
|
150
|
+
pnpm --filter=@velanir/openclaw-participation-gate build
|
|
151
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as openclaw_plugin_sdk_plugin_entry from 'openclaw/plugin-sdk/plugin-entry';
|
|
2
|
+
|
|
3
|
+
declare const PLUGIN_ID = "velanir-participation-gate";
|
|
4
|
+
declare const _default: {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
configSchema: openclaw_plugin_sdk_plugin_entry.OpenClawPluginConfigSchema;
|
|
9
|
+
register: NonNullable<openclaw_plugin_sdk_plugin_entry.OpenClawPluginDefinition["register"]>;
|
|
10
|
+
} & Pick<openclaw_plugin_sdk_plugin_entry.OpenClawPluginDefinition, "kind" | "reload" | "nodeHostCommands" | "securityAuditCollectors">;
|
|
11
|
+
|
|
12
|
+
export { PLUGIN_ID, _default as default };
|