@velanir/openclaw-pending-context 0.4.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 +54 -0
- package/dist/index.d.ts +246 -0
- package/dist/index.js +1540 -0
- package/openclaw.plugin.json +139 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# @velanir/openclaw-pending-context
|
|
2
|
+
|
|
3
|
+
`@velanir/openclaw-pending-context` makes an interactive scheduling reply safe
|
|
4
|
+
to interpret without treating an old transcript mirror as workflow state.
|
|
5
|
+
|
|
6
|
+
## Canonical 0.4.0 behavior
|
|
7
|
+
|
|
8
|
+
1. It reads pending state only from the responsibility runner and injects it
|
|
9
|
+
only when the exact agent, channel, session/conversation, account, and
|
|
10
|
+
authenticated user match. Strict user and session scope both default on.
|
|
11
|
+
2. It registers `responsibility_pending` first, then
|
|
12
|
+
`responsibility_select_option`, `responsibility_reject`,
|
|
13
|
+
`responsibility_change`, and `responsibility_status`. A model never passes a
|
|
14
|
+
`userMessageId`; a host-observed inbound message is bound internally through
|
|
15
|
+
the manifest-declared trusted tool policy.
|
|
16
|
+
3. It quiets only a fresh inbound run that has matching pending context. With
|
|
17
|
+
`suppressNonFinalReplies=true` (the default), tool/progress reply payloads
|
|
18
|
+
for that run are cancelled. Ordinary chat and stale/unscoped sessions are
|
|
19
|
+
not put into global final-only mode.
|
|
20
|
+
4. The first admitted action ends the run's tool budget. Later tool calls are
|
|
21
|
+
blocked. The final reply and the persisted assistant message are rewritten
|
|
22
|
+
from the authoritative runner receipt; a model claim that a meeting was
|
|
23
|
+
scheduled without an action is replaced with a truthful failure.
|
|
24
|
+
5. A scheduling-success final requires a non-dry-run `select_option` receipt
|
|
25
|
+
with an Outlook event id, Zoom join URL, and counterpart-reply receipt.
|
|
26
|
+
Partial results, errors, and dry runs never become success claims.
|
|
27
|
+
|
|
28
|
+
The package deliberately chose the stronger Riley/Mae common behavior as the
|
|
29
|
+
source of truth. It does not copy live compiled bytes or make ordinary Teams
|
|
30
|
+
chat quiet merely because a responsibility happens to have pending work.
|
|
31
|
+
|
|
32
|
+
## Rollout and rollback
|
|
33
|
+
|
|
34
|
+
`dryRun` defaults to `true`. Set it to `false` only for a reviewed canary.
|
|
35
|
+
That alone still cannot execute an effect: the source must declare an explicit
|
|
36
|
+
`actionCommand`; there is no default effect argv. Roll back immediately by
|
|
37
|
+
setting `dryRun` to `true`, or set `suppressNonFinalReplies` to `false` for
|
|
38
|
+
diagnosis while preserving the receipt/final guard.
|
|
39
|
+
|
|
40
|
+
The plugin requires an OpenClaw runtime compatible with `2026.7.1-beta.5` or
|
|
41
|
+
newer and declares its trusted policy in `openclaw.plugin.json`. Platform
|
|
42
|
+
rendering/install work owns canary selection and the exact reviewed action
|
|
43
|
+
bridge; this package does not deploy itself or modify runner business logic.
|
|
44
|
+
|
|
45
|
+
## Develop
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm --filter=@velanir/openclaw-pending-context test
|
|
49
|
+
pnpm --filter=@velanir/openclaw-pending-context check-types
|
|
50
|
+
pnpm --filter=@velanir/openclaw-pending-context build
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`DESIGN.md` documents the original phase-one pending-state contract. This README
|
|
54
|
+
and the source/tests are the canonical 0.4.0 finalization contract.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
type PluginLogger = {
|
|
2
|
+
debug?: (message: string) => void;
|
|
3
|
+
info?: (message: string) => void;
|
|
4
|
+
warn?: (message: string) => void;
|
|
5
|
+
error?: (message: string) => void;
|
|
6
|
+
};
|
|
7
|
+
type AgentToolLike = {
|
|
8
|
+
name: string;
|
|
9
|
+
label: string;
|
|
10
|
+
description: string;
|
|
11
|
+
parameters: unknown;
|
|
12
|
+
execute: (toolCallId: string, params: unknown, signal?: unknown, onUpdate?: unknown) => Promise<AgentToolResultLike>;
|
|
13
|
+
};
|
|
14
|
+
type AgentToolResultLike = {
|
|
15
|
+
content: Array<{
|
|
16
|
+
type: "text";
|
|
17
|
+
text: string;
|
|
18
|
+
}>;
|
|
19
|
+
details: unknown;
|
|
20
|
+
};
|
|
21
|
+
type ToolFactoryContext = {
|
|
22
|
+
agentId?: string;
|
|
23
|
+
sessionKey?: string;
|
|
24
|
+
messageChannel?: string;
|
|
25
|
+
agentAccountId?: string;
|
|
26
|
+
requesterSenderId?: string;
|
|
27
|
+
requesterMessageId?: string;
|
|
28
|
+
deliveryContext?: {
|
|
29
|
+
channel?: string;
|
|
30
|
+
accountId?: string;
|
|
31
|
+
to?: string;
|
|
32
|
+
threadId?: string | number;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
type AgentToolFactory = (ctx: ToolFactoryContext) => AgentToolLike | AgentToolLike[] | null | undefined;
|
|
36
|
+
type AgentTurnPrepareContext = {
|
|
37
|
+
runId?: string;
|
|
38
|
+
agentId?: string;
|
|
39
|
+
sessionKey?: string;
|
|
40
|
+
channel?: string;
|
|
41
|
+
channelId?: string;
|
|
42
|
+
chatId?: string;
|
|
43
|
+
senderId?: string;
|
|
44
|
+
messageProvider?: string;
|
|
45
|
+
workspaceDir?: string;
|
|
46
|
+
};
|
|
47
|
+
type PluginJsonValue = null | boolean | number | string | PluginJsonValue[] | {
|
|
48
|
+
[key: string]: PluginJsonValue;
|
|
49
|
+
};
|
|
50
|
+
type SessionExtensionRegistration = {
|
|
51
|
+
namespace: string;
|
|
52
|
+
description: string;
|
|
53
|
+
project?: (ctx: {
|
|
54
|
+
sessionKey: string;
|
|
55
|
+
sessionId?: string;
|
|
56
|
+
state: PluginJsonValue | undefined;
|
|
57
|
+
}) => PluginJsonValue | undefined;
|
|
58
|
+
};
|
|
59
|
+
type TrustedToolPolicyEvent = {
|
|
60
|
+
toolName: string;
|
|
61
|
+
params: Record<string, unknown>;
|
|
62
|
+
runId?: string;
|
|
63
|
+
toolCallId?: string;
|
|
64
|
+
};
|
|
65
|
+
type TrustedToolPolicyContext = {
|
|
66
|
+
agentId?: string;
|
|
67
|
+
sessionKey?: string;
|
|
68
|
+
sessionId?: string;
|
|
69
|
+
runId?: string;
|
|
70
|
+
toolName: string;
|
|
71
|
+
toolCallId?: string;
|
|
72
|
+
channelId?: string;
|
|
73
|
+
getSessionExtension?: (namespace: string) => PluginJsonValue | undefined;
|
|
74
|
+
};
|
|
75
|
+
type TrustedToolPolicyDecision = {
|
|
76
|
+
params?: Record<string, unknown>;
|
|
77
|
+
block?: boolean;
|
|
78
|
+
blockReason?: string;
|
|
79
|
+
};
|
|
80
|
+
type TrustedToolPolicyRegistration = {
|
|
81
|
+
id: string;
|
|
82
|
+
description: string;
|
|
83
|
+
evaluate: (event: TrustedToolPolicyEvent, ctx: TrustedToolPolicyContext) => TrustedToolPolicyDecision | void | Promise<TrustedToolPolicyDecision | void>;
|
|
84
|
+
};
|
|
85
|
+
type RunContextPatch = {
|
|
86
|
+
runId: string;
|
|
87
|
+
namespace: string;
|
|
88
|
+
value?: PluginJsonValue;
|
|
89
|
+
unset?: boolean;
|
|
90
|
+
};
|
|
91
|
+
type RunContextQuery = {
|
|
92
|
+
runId: string;
|
|
93
|
+
namespace: string;
|
|
94
|
+
};
|
|
95
|
+
type OpenClawPluginApi = {
|
|
96
|
+
pluginConfig?: Record<string, unknown>;
|
|
97
|
+
logger?: PluginLogger;
|
|
98
|
+
setRunContext?: (patch: RunContextPatch) => boolean;
|
|
99
|
+
getRunContext?: (params: RunContextQuery) => PluginJsonValue | undefined;
|
|
100
|
+
runContext?: {
|
|
101
|
+
setRunContext: (patch: RunContextPatch) => boolean;
|
|
102
|
+
getRunContext: (params: RunContextQuery) => PluginJsonValue | undefined;
|
|
103
|
+
};
|
|
104
|
+
registerTool: (tool: AgentToolLike | AgentToolFactory, opts?: {
|
|
105
|
+
name?: string;
|
|
106
|
+
names?: string[];
|
|
107
|
+
optional?: boolean;
|
|
108
|
+
}) => void;
|
|
109
|
+
registerSessionExtension: (extension: SessionExtensionRegistration) => void;
|
|
110
|
+
registerTrustedToolPolicy: (policy: TrustedToolPolicyRegistration) => void;
|
|
111
|
+
on: (hookName: string, handler: (event: unknown, ctx: unknown) => unknown | Promise<unknown>, opts?: {
|
|
112
|
+
priority?: number;
|
|
113
|
+
timeoutMs?: number;
|
|
114
|
+
}) => void;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
type TurnScope = {
|
|
118
|
+
agentId?: string;
|
|
119
|
+
channel?: string;
|
|
120
|
+
userId?: string;
|
|
121
|
+
sessionKey?: string;
|
|
122
|
+
accountId?: string;
|
|
123
|
+
conversationId?: string;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
type TrustedInboundAuthorization = {
|
|
127
|
+
messageId: string;
|
|
128
|
+
senderId: string;
|
|
129
|
+
sessionKey: string;
|
|
130
|
+
runId?: string;
|
|
131
|
+
channel?: string;
|
|
132
|
+
accountId?: string;
|
|
133
|
+
conversationId?: string;
|
|
134
|
+
receivedAt: number;
|
|
135
|
+
};
|
|
136
|
+
type InboundHookEvent = {
|
|
137
|
+
messageId?: string;
|
|
138
|
+
senderId?: string;
|
|
139
|
+
from?: string;
|
|
140
|
+
sessionKey?: string;
|
|
141
|
+
runId?: string;
|
|
142
|
+
channel?: string;
|
|
143
|
+
};
|
|
144
|
+
type InboundHookContext = {
|
|
145
|
+
messageId?: string;
|
|
146
|
+
senderId?: string;
|
|
147
|
+
sessionKey?: string;
|
|
148
|
+
runId?: string;
|
|
149
|
+
channelId?: string;
|
|
150
|
+
accountId?: string;
|
|
151
|
+
conversationId?: string;
|
|
152
|
+
};
|
|
153
|
+
type ToolBindEvent = {
|
|
154
|
+
toolName?: string;
|
|
155
|
+
toolCallId?: string;
|
|
156
|
+
runId?: string;
|
|
157
|
+
};
|
|
158
|
+
type ToolBindContext = {
|
|
159
|
+
toolName?: string;
|
|
160
|
+
toolCallId?: string;
|
|
161
|
+
runId?: string;
|
|
162
|
+
sessionKey?: string;
|
|
163
|
+
};
|
|
164
|
+
type AuthorizationConsumeError = "missing_binding" | "message_replayed" | "session_mismatch" | "user_mismatch";
|
|
165
|
+
type AuthorizationConsumeResult = {
|
|
166
|
+
authorization: TrustedInboundAuthorization | null;
|
|
167
|
+
error?: AuthorizationConsumeError;
|
|
168
|
+
};
|
|
169
|
+
type InboundAuthorizationStore = {
|
|
170
|
+
recordTrusted(record: TrustedInboundAuthorization): boolean;
|
|
171
|
+
recordInbound(event: InboundHookEvent, ctx: InboundHookContext): boolean;
|
|
172
|
+
bindTool(event: ToolBindEvent, ctx: ToolBindContext): boolean;
|
|
173
|
+
hasFreshBinding(scope: TurnScope, runId?: string): boolean;
|
|
174
|
+
consumeDetailed(toolCallId: string, scope: TurnScope): AuthorizationConsumeResult;
|
|
175
|
+
consume(toolCallId: string, scope: TurnScope): TrustedInboundAuthorization | null;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
type DeliveryHookEvent = {
|
|
179
|
+
runId?: string;
|
|
180
|
+
toolName?: string;
|
|
181
|
+
toolCallId?: string;
|
|
182
|
+
result?: unknown;
|
|
183
|
+
error?: unknown;
|
|
184
|
+
kind?: string;
|
|
185
|
+
};
|
|
186
|
+
type DeliveryHookContext = {
|
|
187
|
+
runId?: string;
|
|
188
|
+
toolName?: string;
|
|
189
|
+
toolCallId?: string;
|
|
190
|
+
sessionKey?: string;
|
|
191
|
+
};
|
|
192
|
+
type TransactionDeliveryStore = {
|
|
193
|
+
markPendingTurn(runIdValue: unknown, sessionKeyValue: unknown): boolean;
|
|
194
|
+
beginAction(event: DeliveryHookEvent, ctx: DeliveryHookContext): {
|
|
195
|
+
tracked: boolean;
|
|
196
|
+
duplicate: boolean;
|
|
197
|
+
};
|
|
198
|
+
failAction(event: DeliveryHookEvent, ctx: DeliveryHookContext, errorCode: string | undefined): boolean;
|
|
199
|
+
finishAction(event: DeliveryHookEvent, ctx: DeliveryHookContext): boolean;
|
|
200
|
+
blockFurtherTool(event: DeliveryHookEvent, ctx: DeliveryHookContext): boolean;
|
|
201
|
+
suppressNonFinal(event: DeliveryHookEvent): boolean;
|
|
202
|
+
expectedFinalText(runIdValue: unknown): string | undefined;
|
|
203
|
+
expectedFinalTextForToolCall(toolCallIdValue: unknown): string | undefined;
|
|
204
|
+
pendingWithoutAction(runIdValue: unknown): boolean;
|
|
205
|
+
claimNoActionFailureForSession(sessionKeyValue: unknown): string | undefined;
|
|
206
|
+
authoritativeFinalForSession(sessionKeyValue: unknown, modelClaimsSuccess: boolean): string | undefined;
|
|
207
|
+
markFinalDelivered(sessionKeyValue: unknown): boolean;
|
|
208
|
+
finalAlreadyDelivered(sessionKeyValue: unknown): boolean;
|
|
209
|
+
claimFinalTextForSession(sessionKeyValue: unknown): string | undefined;
|
|
210
|
+
finalizeReply(event: DeliveryHookEvent): {
|
|
211
|
+
text?: string;
|
|
212
|
+
} | undefined;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
type RunnerExecInput = {
|
|
216
|
+
command: string[];
|
|
217
|
+
cwd: string;
|
|
218
|
+
timeoutMs: number;
|
|
219
|
+
maxOutputBytes: number;
|
|
220
|
+
stdin?: string;
|
|
221
|
+
};
|
|
222
|
+
type RunnerExecResult = {
|
|
223
|
+
ok: boolean;
|
|
224
|
+
stdout?: string;
|
|
225
|
+
error?: string;
|
|
226
|
+
truncated?: boolean;
|
|
227
|
+
};
|
|
228
|
+
type RunnerExec = (input: RunnerExecInput) => RunnerExecResult;
|
|
229
|
+
|
|
230
|
+
declare const PLUGIN_ID = "velanir-pending-context";
|
|
231
|
+
declare const TRUSTED_ACTION_POLICY_ID = "authoritative-pending-action";
|
|
232
|
+
type PendingContextRuntime = {
|
|
233
|
+
authorization?: InboundAuthorizationStore;
|
|
234
|
+
delivery?: TransactionDeliveryStore;
|
|
235
|
+
};
|
|
236
|
+
declare function turnScopeFromContext(ctx: AgentTurnPrepareContext): TurnScope;
|
|
237
|
+
declare function toolScopeFromContext(ctx: ToolFactoryContext): TurnScope;
|
|
238
|
+
declare function registerPendingContextPlugin(api: OpenClawPluginApi, exec: RunnerExec, runtime?: PendingContextRuntime): void;
|
|
239
|
+
declare const _default: {
|
|
240
|
+
id: string;
|
|
241
|
+
name: string;
|
|
242
|
+
description: string;
|
|
243
|
+
register(api: OpenClawPluginApi): void;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
export { PLUGIN_ID, type PendingContextRuntime, TRUSTED_ACTION_POLICY_ID, _default as default, registerPendingContextPlugin, toolScopeFromContext, turnScopeFromContext };
|