@ory/openclaw 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/dist/plugin.js ADDED
@@ -0,0 +1,502 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createOryPlugin = createOryPlugin;
4
+ const argus_1 = require("@ory/argus");
5
+ /**
6
+ * Create the Ory plugin entry for OpenClaw.
7
+ *
8
+ * OpenClaw plugins export { id, name, register(api) } where register
9
+ * receives a PluginApi for registering hooks. The `before_tool_call`
10
+ * hook can return `{ block: true }` to deny tool execution — this
11
+ * makes permission checks enforceable, unlike harnesses that can only
12
+ * log denials.
13
+ *
14
+ * The `session_start` hook returns `Promise<void>` so the auth gate is
15
+ * advisory at session start: the gate still runs to emit the audit span,
16
+ * refresh tokens, and prompt for the project URL when interactive, but
17
+ * cannot hard-block the session.
18
+ */
19
+ function createOryPlugin(clientOrConfig, deps = {}) {
20
+ return {
21
+ id: "ory-agent-security",
22
+ name: "Ory Agent Security",
23
+ register(api) {
24
+ const client = clientOrConfig instanceof argus_1.OryAgentClient
25
+ ? clientOrConfig
26
+ : clientOrConfig
27
+ ? new argus_1.OryAgentClient({
28
+ ...clientOrConfig,
29
+ harness: "openclaw",
30
+ })
31
+ : argus_1.OryAgentClient.fromEnv("openclaw");
32
+ api.registerHook("session_start", createSessionStartHandler(client, deps), { name: "ory.session-verify", description: "Verify session via Ory Identities" });
33
+ api.registerHook("before_agent_run", createBeforeAgentRunHandler(client, deps), { name: "ory.user-auth-gate", description: "Block runs when the user auth gate denies" });
34
+ api.registerHook("before_tool_call", createBeforeToolCallHandler(client), { name: "ory.permission-check", description: "Check tool permissions via Ory Permissions" });
35
+ api.registerHook("after_tool_call", createAfterToolCallHandler(client), { name: "ory.trace-complete", description: "Record tool completion trace" });
36
+ api.registerHook("subagent_spawning", createSubagentSpawningHandler(client, deps), { name: "ory.subagent-register", description: "Register Ory identity and write delegation tuple for spawned sub-agent" });
37
+ api.registerHook("subagent_spawned", createSubagentSpawnedHandler(client), { name: "ory.subagent-spawned", description: "Record sub-agent spawn audit span" });
38
+ api.registerHook("subagent_ended", createSubagentEndedHandler(client), { name: "ory.subagent-ended", description: "Record sub-agent termination audit span" });
39
+ },
40
+ };
41
+ }
42
+ // ─── session_start ─────────────────────────────────────────────────
43
+ function createSessionStartHandler(client, deps) {
44
+ return async (ctx) => {
45
+ client.logger.info("lifecycle.session_start", {
46
+ sessionId: ctx.sessionId,
47
+ model: ctx.model,
48
+ });
49
+ // Set trace context for this session
50
+ client.tracer.setContext({
51
+ traceId: (0, argus_1.deriveTraceId)(ctx.sessionId),
52
+ sessionId: ctx.sessionId,
53
+ });
54
+ // Run the user auth gate. session_start cannot hard-block in OpenClaw
55
+ // (handler returns void), so allowBlock is false — the gate still emits
56
+ // the user.auth audit span, refreshes tokens, and prompts for the
57
+ // project URL when interactive. When ORY_AUTH_GATE is unset the gate is
58
+ // a no-op (mode === "disabled") and we fall through to the legacy
59
+ // verification path.
60
+ const userGate = deps.authGate ?? argus_1.ensureUserAuthenticated;
61
+ const decision = await userGate(client, {
62
+ binName: "ory-openclaw",
63
+ harness: "openclaw",
64
+ allowBlock: false,
65
+ });
66
+ // Resolve the agent identity (machine credentials). Never blocks;
67
+ // attaches the agent's bearer token to outgoing Ory API calls.
68
+ const agentGate = deps.agentGate ?? argus_1.ensureAgentIdentity;
69
+ await agentGate(client, { projectUrl: (0, argus_1.resolveConfig)().projectUrl, harness: "openclaw" });
70
+ if (decision.mode !== "disabled") {
71
+ return;
72
+ }
73
+ const modelAttr = ctx.model ? { model: ctx.model } : {};
74
+ const resolved = (0, argus_1.resolveConfig)();
75
+ if (resolved.auditOnly) {
76
+ client.logger.info("config.audit_only", {
77
+ message: "Audit-only mode enabled. Auth and permission checks are disabled.",
78
+ });
79
+ client.tracer.record("session.start", "ok", {
80
+ attributes: { sessionId: ctx.sessionId, mode: "audit-only", ...modelAttr },
81
+ });
82
+ return;
83
+ }
84
+ if (!resolved.projectUrl) {
85
+ client.logger.warn("config.not_configured", {
86
+ message: "Ory plugin is not configured. Auth and permission checks are disabled. " +
87
+ "Run 'npx ory-openclaw configure' to connect to an Ory project.",
88
+ });
89
+ client.tracer.record("session.start", "skipped", {
90
+ attributes: { reason: "not_configured", ...modelAttr },
91
+ });
92
+ return;
93
+ }
94
+ const sessionToken = process.env.ORY_SESSION_TOKEN;
95
+ const oauth2Token = process.env.ORY_OAUTH2_TOKEN;
96
+ if (sessionToken) {
97
+ await verifySessionToken(client, sessionToken, ctx.sessionId, ctx.model);
98
+ return;
99
+ }
100
+ if (oauth2Token) {
101
+ await verifyOAuth2Token(client, oauth2Token, ctx.sessionId, ctx.model);
102
+ return;
103
+ }
104
+ client.logger.warn("session.no_credentials", {
105
+ message: "Neither ORY_SESSION_TOKEN nor ORY_OAUTH2_TOKEN is set. " +
106
+ "Skipping authentication.",
107
+ });
108
+ client.tracer.record("session.start", "skipped", {
109
+ attributes: { reason: "no_credentials", ...modelAttr },
110
+ });
111
+ };
112
+ }
113
+ async function verifySessionToken(client, sessionToken, sessionId, model) {
114
+ const modelAttr = model ? { model } : {};
115
+ try {
116
+ const session = await client.verifySession(sessionToken);
117
+ if (!session.active) {
118
+ client.logger.warn("session.inactive", {
119
+ message: "Ory session is not active. Re-authenticate to enable auth checks.",
120
+ });
121
+ client.tracer.record("session.start", "skipped", {
122
+ attributes: { reason: "session_inactive", sessionId, ...modelAttr },
123
+ });
124
+ }
125
+ else {
126
+ client.tracer.record("session.start", "ok", {
127
+ attributes: {
128
+ sessionId,
129
+ identityId: session.identityId,
130
+ aal: session.authenticatorAssuranceLevel,
131
+ ...modelAttr,
132
+ },
133
+ });
134
+ }
135
+ }
136
+ catch (err) {
137
+ const oryErr = err;
138
+ client.logger.warn("session.verify_failed", {
139
+ code: isOryError(err) ? oryErr.code : "unknown",
140
+ message: err instanceof Error ? err.message : String(err),
141
+ });
142
+ client.tracer.record("session.start", "error", {
143
+ attributes: { error: isOryError(err) ? oryErr.code : "unknown", ...modelAttr },
144
+ });
145
+ }
146
+ }
147
+ async function verifyOAuth2Token(client, oauth2Token, sessionId, model) {
148
+ const modelAttr = model ? { model } : {};
149
+ try {
150
+ const tokenInfo = await client.introspectToken(oauth2Token);
151
+ if (!tokenInfo.active) {
152
+ client.logger.warn("oauth2.token_inactive", {
153
+ message: "Ory OAuth2 token is not active. Obtain a new token to enable auth checks.",
154
+ });
155
+ client.tracer.record("session.start", "skipped", {
156
+ attributes: { reason: "token_inactive", sessionId, ...modelAttr },
157
+ });
158
+ return;
159
+ }
160
+ client.logger.info("oauth2.session_authenticated", {
161
+ clientId: tokenInfo.clientId,
162
+ subject: tokenInfo.subject,
163
+ });
164
+ client.tracer.record("session.start", "ok", {
165
+ attributes: {
166
+ sessionId,
167
+ clientId: tokenInfo.clientId,
168
+ subject: tokenInfo.subject,
169
+ ...modelAttr,
170
+ },
171
+ });
172
+ }
173
+ catch (err) {
174
+ const oryErr = err;
175
+ client.logger.warn("oauth2.introspect_failed", {
176
+ code: isOryError(err) ? oryErr.code : "unknown",
177
+ message: err instanceof Error ? err.message : String(err),
178
+ });
179
+ client.tracer.record("session.start", "error", {
180
+ attributes: { error: isOryError(err) ? oryErr.code : "unknown", ...modelAttr },
181
+ });
182
+ }
183
+ }
184
+ // ─── before_tool_call ──────────────────────────────────────────────
185
+ function createBeforeToolCallHandler(client) {
186
+ return async (ctx) => {
187
+ client.logger.info("lifecycle.before_tool_call", {
188
+ toolId: ctx.toolId,
189
+ toolName: ctx.toolName,
190
+ sessionId: ctx.sessionId,
191
+ callId: ctx.callId,
192
+ });
193
+ // Set trace context for this tool invocation
194
+ client.tracer.setContext({
195
+ traceId: (0, argus_1.deriveTraceId)(ctx.sessionId),
196
+ sessionId: ctx.sessionId,
197
+ });
198
+ const inputSummary = (0, argus_1.summarizeToolInput)(ctx.toolId, ctx.args);
199
+ // In audit-only mode, log the invocation but skip permission checks
200
+ if ((0, argus_1.resolveConfig)().auditOnly) {
201
+ client.tracer.record("tool.invoke", "ok", {
202
+ attributes: { toolName: ctx.toolId, ...inputSummary },
203
+ });
204
+ return;
205
+ }
206
+ // If Ory is not configured, pass through
207
+ if (!(0, argus_1.resolveConfig)().projectUrl) {
208
+ client.tracer.record("tool.invoke", "skipped", {
209
+ attributes: { toolName: ctx.toolId, reason: "not_configured", ...inputSummary },
210
+ });
211
+ return;
212
+ }
213
+ const subject = (0, argus_1.resolveUserSubject)(client, ctx.sessionId ? `session:${ctx.sessionId}` : "agent:openclaw");
214
+ const subjectId = (0, argus_1.subjectLabel)(subject);
215
+ const mcpTool = (0, argus_1.parseMcpToolGeneric)(ctx.toolId);
216
+ try {
217
+ if (mcpTool) {
218
+ const mcpResult = await (0, argus_1.checkMcpPermission)(client, mcpTool, {
219
+ subject,
220
+ spanAttributes: { toolName: ctx.toolId },
221
+ });
222
+ if (!mcpResult.allowed) {
223
+ client.tracer.record("tool.block", "denied", {
224
+ attributes: { toolName: ctx.toolId, mcpServer: mcpTool.serverName, mcpTool: mcpTool.toolName, ...inputSummary, ...(0, argus_1.alertAttributes)(true) },
225
+ });
226
+ const blockReason = (0, argus_1.formatDenialMessage)({
227
+ tool: ctx.toolId,
228
+ subjectId,
229
+ mcp: mcpTool,
230
+ });
231
+ client.logger.warn("tool.denied", {
232
+ toolId: ctx.toolId,
233
+ subjectId,
234
+ message: blockReason,
235
+ });
236
+ return { block: true, blockReason };
237
+ }
238
+ client.tracer.record("tool.invoke", "ok", {
239
+ attributes: { toolName: ctx.toolId, mcpServer: mcpTool.serverName, mcpTool: mcpTool.toolName, ...inputSummary },
240
+ });
241
+ return;
242
+ }
243
+ const namespace = resolveNamespace();
244
+ const result = await client.checkPermission({
245
+ namespace,
246
+ object: ctx.toolId,
247
+ relation: "use",
248
+ ...subject,
249
+ }, { spanAttributes: { toolName: ctx.toolId } });
250
+ if (!result.allowed) {
251
+ client.tracer.record("tool.block", "denied", {
252
+ attributes: { toolName: ctx.toolId, ...inputSummary, allowed: result.allowed, ...(0, argus_1.alertAttributes)(true) },
253
+ });
254
+ const blockReason = (0, argus_1.formatDenialMessage)({
255
+ tool: ctx.toolId,
256
+ subjectId,
257
+ namespace,
258
+ });
259
+ client.logger.warn("tool.denied", {
260
+ toolId: ctx.toolId,
261
+ subjectId,
262
+ message: blockReason,
263
+ });
264
+ // OpenClaw supports blocking — return { block: true, blockReason }
265
+ return { block: true, blockReason };
266
+ }
267
+ client.tracer.record("tool.invoke", "ok", {
268
+ attributes: { toolName: ctx.toolId, ...inputSummary, allowed: result.allowed },
269
+ });
270
+ }
271
+ catch (err) {
272
+ handlePermissionError(err, ctx.toolId, client);
273
+ // Fail open — allow the tool call
274
+ }
275
+ };
276
+ }
277
+ // ─── after_tool_call ───────────────────────────────────────────────
278
+ function createAfterToolCallHandler(client) {
279
+ return async (ctx) => {
280
+ client.logger.info("lifecycle.after_tool_call", {
281
+ toolId: ctx.toolId,
282
+ toolName: ctx.toolName,
283
+ sessionId: ctx.sessionId,
284
+ callId: ctx.callId,
285
+ durationMs: ctx.durationMs,
286
+ hasError: !!ctx.error,
287
+ });
288
+ // Set trace context for this tool completion
289
+ client.tracer.setContext({
290
+ traceId: (0, argus_1.deriveTraceId)(ctx.sessionId),
291
+ sessionId: ctx.sessionId,
292
+ });
293
+ const status = ctx.error ? "error" : "ok";
294
+ client.tracer.record("tool.complete", status, {
295
+ attributes: {
296
+ toolName: ctx.toolId,
297
+ ...(0, argus_1.summarizeToolInput)(ctx.toolId, ctx.args),
298
+ ...(0, argus_1.summarizeToolOutput)(ctx.toolId, ctx.result),
299
+ durationMs: ctx.durationMs,
300
+ ...(ctx.error ? { error: ctx.error } : {}),
301
+ },
302
+ });
303
+ };
304
+ }
305
+ // ─── before_agent_run ──────────────────────────────────────────────
306
+ //
307
+ // Real per-turn enforcement gate. `session_start` cannot hard-block in
308
+ // OpenClaw (handler returns void), so the user auth gate runs there in
309
+ // advisory mode. `before_agent_run` is the first hook OpenClaw exposes
310
+ // that can return a block decision on the user prompt — we re-run the
311
+ // gate here so denied users can't drive the agent. `ensureUserAuthenticated`
312
+ // reuses persisted tokens, so per-turn calls are non-interactive after
313
+ // the first session.
314
+ function createBeforeAgentRunHandler(client, deps) {
315
+ return async (ctx) => {
316
+ client.tracer.setContext({
317
+ traceId: (0, argus_1.deriveTraceId)(ctx.sessionKey ?? ctx.sessionId ?? "openclaw"),
318
+ sessionId: ctx.sessionKey ?? ctx.sessionId,
319
+ });
320
+ const userGate = deps.authGate ?? argus_1.ensureUserAuthenticated;
321
+ const decision = await userGate(client, {
322
+ binName: "ory-openclaw",
323
+ harness: "openclaw",
324
+ allowBlock: true,
325
+ });
326
+ if (!decision.proceed) {
327
+ client.logger.warn("before_agent_run.blocked", {
328
+ reason: decision.reason,
329
+ mode: decision.mode,
330
+ });
331
+ return {
332
+ outcome: "block",
333
+ reason: decision.reason ?? "User authentication required",
334
+ message: decision.reason,
335
+ };
336
+ }
337
+ // Disabled gate or successful auth: let the run proceed.
338
+ return;
339
+ };
340
+ }
341
+ // ─── subagent_spawning ─────────────────────────────────────────────
342
+ //
343
+ // Fires before a child agent run starts. We resolve a distinct OAuth2
344
+ // identity for the sub-agent (DCR) and write the agent→sub-agent
345
+ // delegation tuple. Best-effort: failures here never abort the child run.
346
+ function createSubagentSpawningHandler(client, deps) {
347
+ return async (ctx) => {
348
+ const subAgentType = ctx.agentId ?? ctx.label;
349
+ client.tracer.setContext({
350
+ traceId: (0, argus_1.deriveTraceId)(ctx.sessionKey ?? ctx.childSessionKey ?? "openclaw"),
351
+ sessionId: ctx.sessionKey,
352
+ });
353
+ client.logger.info("lifecycle.subagent_spawning", {
354
+ agentId: ctx.agentId,
355
+ label: ctx.label,
356
+ childSessionKey: ctx.childSessionKey,
357
+ mode: ctx.mode,
358
+ requester: ctx.requester,
359
+ });
360
+ client.tracer.record("subagent.start", "ok", {
361
+ attributes: {
362
+ subAgentType,
363
+ agentId: ctx.agentId,
364
+ childSessionKey: ctx.childSessionKey,
365
+ mode: ctx.mode,
366
+ requester: ctx.requester,
367
+ },
368
+ });
369
+ if (!subAgentType) {
370
+ client.logger.debug("subagent.no_type", {
371
+ message: "subagent_spawning event without agentId/label — skipping DCR.",
372
+ });
373
+ return;
374
+ }
375
+ const subAgentGate = deps.subAgentGate ?? argus_1.ensureSubAgentIdentity;
376
+ let identity;
377
+ try {
378
+ identity = await subAgentGate(client, {
379
+ subAgentType,
380
+ projectUrl: (0, argus_1.resolveConfig)().projectUrl,
381
+ harness: "openclaw",
382
+ });
383
+ }
384
+ catch (err) {
385
+ client.logger.warn("subagent.identity.failed", {
386
+ subAgentType,
387
+ message: err instanceof Error ? err.message : String(err),
388
+ });
389
+ return;
390
+ }
391
+ if (identity.kind !== "dynamic" || !identity.subject)
392
+ return;
393
+ const agent = client.agentPrincipal.subject;
394
+ if (!agent) {
395
+ client.logger.debug("delegation.skip", {
396
+ reason: "agent principal not populated",
397
+ subAgentType,
398
+ });
399
+ return;
400
+ }
401
+ try {
402
+ await client.createRelationship({
403
+ namespace: resolveNamespace(),
404
+ object: `subagent:${identity.subject}`,
405
+ relation: "delegate",
406
+ subjectId: `agent:${agent}`,
407
+ }, { spanAttributes: { delegation: "agent-to-subagent", subAgentType } });
408
+ }
409
+ catch (err) {
410
+ const oryErr = err;
411
+ client.logger.warn("delegation.agent_to_subagent.failed", {
412
+ subAgentType,
413
+ code: oryErr.code,
414
+ message: oryErr.message,
415
+ });
416
+ }
417
+ };
418
+ }
419
+ // ─── subagent_spawned ──────────────────────────────────────────────
420
+ function createSubagentSpawnedHandler(client) {
421
+ return async (ctx) => {
422
+ client.tracer.setContext({
423
+ traceId: (0, argus_1.deriveTraceId)(ctx.sessionKey ?? ctx.childSessionKey ?? "openclaw"),
424
+ sessionId: ctx.sessionKey,
425
+ });
426
+ client.logger.info("lifecycle.subagent_spawned", {
427
+ agentId: ctx.agentId,
428
+ childRunId: ctx.childRunId,
429
+ childSessionKey: ctx.childSessionKey,
430
+ });
431
+ client.tracer.record("subagent.start", "ok", {
432
+ attributes: {
433
+ subAgentType: ctx.agentId ?? ctx.label,
434
+ agentId: ctx.agentId,
435
+ childRunId: ctx.childRunId,
436
+ stage: "spawned",
437
+ },
438
+ });
439
+ };
440
+ }
441
+ // ─── subagent_ended ────────────────────────────────────────────────
442
+ function createSubagentEndedHandler(client) {
443
+ return async (ctx) => {
444
+ client.tracer.setContext({
445
+ traceId: (0, argus_1.deriveTraceId)(ctx.sessionKey ?? ctx.childSessionKey ?? "openclaw"),
446
+ sessionId: ctx.sessionKey,
447
+ });
448
+ client.logger.info("lifecycle.subagent_ended", {
449
+ agentId: ctx.agentId,
450
+ childRunId: ctx.childRunId,
451
+ reason: ctx.reason,
452
+ });
453
+ client.tracer.record("subagent.stop", "ok", {
454
+ attributes: {
455
+ subAgentType: ctx.agentId ?? ctx.label,
456
+ agentId: ctx.agentId,
457
+ childRunId: ctx.childRunId,
458
+ reason: ctx.reason,
459
+ },
460
+ });
461
+ };
462
+ }
463
+ // ─── Helpers ───────────────────────────────────────────────────────
464
+ function resolveNamespace() {
465
+ return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
466
+ }
467
+ function isOryError(err) {
468
+ return (typeof err === "object" &&
469
+ err !== null &&
470
+ "code" in err &&
471
+ "message" in err);
472
+ }
473
+ function handlePermissionError(err, toolId, client) {
474
+ if (isOryError(err)) {
475
+ if (err.code === "network_error" || err.code === "rate_limited") {
476
+ client.logger.warn("permission.fallback", {
477
+ toolId,
478
+ code: err.code,
479
+ message: "Failing open",
480
+ });
481
+ client.tracer.record("tool.invoke", "ok", {
482
+ attributes: { toolName: toolId, failOpen: true, reason: err.code },
483
+ });
484
+ return;
485
+ }
486
+ client.logger.error("permission.check.error", {
487
+ toolId,
488
+ code: err.code,
489
+ message: err.message,
490
+ });
491
+ }
492
+ else {
493
+ client.logger.error("permission.check.error", {
494
+ toolId,
495
+ error: err instanceof Error ? err.message : String(err),
496
+ });
497
+ }
498
+ // Record fail-open trace for unknown errors too
499
+ client.tracer.record("tool.invoke", "ok", {
500
+ attributes: { toolName: toolId, failOpen: true },
501
+ });
502
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * OpenClaw plugin types.
3
+ *
4
+ * OpenClaw plugins export a plugin entry with { id, name, register(api) }.
5
+ * The `register` function receives a PluginApi that allows registering
6
+ * hooks, tools, and other extensions.
7
+ *
8
+ * Key difference from other harnesses: the `before_tool_call` hook can
9
+ * return `{ block: true }` to deny tool execution, making permission
10
+ * checks enforceable rather than advisory.
11
+ *
12
+ * See: OpenClaw plugin SDK docs for canonical types.
13
+ */
14
+ export interface OpenClawPluginEntry {
15
+ id: string;
16
+ name: string;
17
+ version?: string;
18
+ register(api: OpenClawPluginApi): void;
19
+ }
20
+ export interface OpenClawPluginApi {
21
+ registerHook<E extends HookEvent>(event: E, handler: HookHandler<E>, opts?: HookRegistrationOpts): void;
22
+ }
23
+ export interface HookRegistrationOpts {
24
+ name?: string;
25
+ description?: string;
26
+ }
27
+ export type HookEvent = "session_start" | "session_end" | "before_tool_call" | "after_tool_call" | "before_agent_start" | "before_agent_run" | "subagent_spawning" | "subagent_spawned" | "subagent_ended";
28
+ export type HookHandler<E extends HookEvent> = E extends "before_tool_call" ? (ctx: BeforeToolCallContext) => Promise<HookResult | void> : E extends "after_tool_call" ? (ctx: AfterToolCallContext) => Promise<void> : E extends "session_start" ? (ctx: SessionStartContext) => Promise<void> : E extends "before_agent_start" ? (ctx: BeforeAgentStartContext) => Promise<void> : E extends "before_agent_run" ? (ctx: BeforeAgentRunContext) => Promise<BeforeAgentRunResult | void> : E extends "session_end" ? (ctx: SessionEndContext) => Promise<void> : E extends "subagent_spawning" ? (ctx: SubagentSpawningContext) => Promise<SubagentSpawningResult | void> : E extends "subagent_spawned" ? (ctx: SubagentSpawnedContext) => Promise<void> : E extends "subagent_ended" ? (ctx: SubagentEndedContext) => Promise<void> : never;
29
+ export interface BeforeToolCallContext {
30
+ toolId: string;
31
+ toolName: string;
32
+ args: Record<string, unknown>;
33
+ sessionId: string;
34
+ callId: string;
35
+ }
36
+ export interface AfterToolCallContext {
37
+ toolId: string;
38
+ toolName: string;
39
+ args: Record<string, unknown>;
40
+ result: unknown;
41
+ sessionId: string;
42
+ callId: string;
43
+ durationMs: number;
44
+ error?: string;
45
+ }
46
+ export interface SessionStartContext {
47
+ sessionId: string;
48
+ model?: string;
49
+ gateway?: {
50
+ version: string;
51
+ };
52
+ }
53
+ export interface BeforeAgentStartContext {
54
+ sessionId: string;
55
+ model?: string;
56
+ config?: Record<string, unknown>;
57
+ }
58
+ export interface SessionEndContext {
59
+ sessionId: string;
60
+ reason?: string;
61
+ }
62
+ /**
63
+ * Context for `before_agent_run` — fires before each model submission
64
+ * with the final prompt and session messages assembled. Unlike
65
+ * `session_start`, this hook can hard-block via a `BeforeAgentRunResult`.
66
+ */
67
+ export interface BeforeAgentRunContext {
68
+ sessionId?: string;
69
+ sessionKey?: string;
70
+ runId?: string;
71
+ agentId?: string;
72
+ model?: string;
73
+ /** Final user prompt / message text, when available. */
74
+ prompt?: string;
75
+ /** Assembled message list (shape varies — treat as opaque). */
76
+ messages?: unknown[];
77
+ [key: string]: unknown;
78
+ }
79
+ /**
80
+ * Return shape for `before_agent_run`. `outcome: "block"` aborts the
81
+ * run before the model is called. Returning void (or no outcome) lets
82
+ * the run proceed.
83
+ */
84
+ export interface BeforeAgentRunResult {
85
+ outcome?: "block";
86
+ reason?: string;
87
+ message?: string;
88
+ }
89
+ export interface SubagentSpawningContext {
90
+ /** Stable identifier of the child agent definition (e.g. "Explore"). */
91
+ agentId?: string;
92
+ /** Human-readable label for the child invocation. */
93
+ label?: string;
94
+ /** Session key of the spawning (parent) session. */
95
+ sessionKey?: string;
96
+ /** Session key the child will run under. */
97
+ childSessionKey?: string;
98
+ /** Invocation mode (e.g. "thread"/"foreground"). */
99
+ mode?: string;
100
+ /** Subject that requested the spawn (e.g. user vs. tool-driven). */
101
+ requester?: string;
102
+ /** Whether a separate thread was requested for the child run. */
103
+ threadRequested?: boolean;
104
+ [key: string]: unknown;
105
+ }
106
+ export interface SubagentSpawnedContext extends SubagentSpawningContext {
107
+ /** Resolved session/run identifier for the child. */
108
+ childRunId?: string;
109
+ }
110
+ export interface SubagentEndedContext extends SubagentSpawningContext {
111
+ childRunId?: string;
112
+ reason?: string;
113
+ /** Final response/output from the child run (audit-only). */
114
+ result?: unknown;
115
+ }
116
+ export interface HookResult {
117
+ /** Set to true to block tool execution. */
118
+ block?: boolean;
119
+ /**
120
+ * Human-readable reason shown to the user when the tool call is blocked.
121
+ * Maps to OpenClaw's PluginHookBeforeToolCallResult.blockReason.
122
+ */
123
+ blockReason?: string;
124
+ }
125
+ /**
126
+ * Return shape for `subagent_spawning`. Returning `{ status: "error" }`
127
+ * aborts the child run before it starts.
128
+ */
129
+ export interface SubagentSpawningResult {
130
+ status?: "error";
131
+ error?: string;
132
+ }
package/dist/types.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ /**
3
+ * OpenClaw plugin types.
4
+ *
5
+ * OpenClaw plugins export a plugin entry with { id, name, register(api) }.
6
+ * The `register` function receives a PluginApi that allows registering
7
+ * hooks, tools, and other extensions.
8
+ *
9
+ * Key difference from other harnesses: the `before_tool_call` hook can
10
+ * return `{ block: true }` to deny tool execution, making permission
11
+ * checks enforceable rather than advisory.
12
+ *
13
+ * See: OpenClaw plugin SDK docs for canonical types.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
1
+ {
2
+ "id": "ory-agent-security",
3
+ "name": "Ory Agent Security",
4
+ "version": "0.1.0",
5
+ "description": "Authentication, authorization, and distributed tracing for OpenClaw via Ory",
6
+ "entry": "./dist/index.js"
7
+ }