@ory/opencode 0.14.0 → 1.0.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 +77 -66
- package/dist/cli/config.d.ts +90 -0
- package/dist/cli/config.js +267 -0
- package/dist/cli/main.js +94 -63
- package/dist/cli/setup.d.ts +3 -0
- package/dist/cli/setup.js +39 -56
- package/dist/plugin.d.ts +1 -16
- package/dist/plugin.js +90 -345
- package/package.json +9 -8
package/dist/plugin.js
CHANGED
|
@@ -2,18 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createOryPlugin = createOryPlugin;
|
|
4
4
|
const argus_1 = require("@ory/argus");
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
8
|
-
* OpenCode plugins export { server: Plugin } where Plugin receives
|
|
9
|
-
* context and returns a Hooks object. Hook functions use a mutating
|
|
10
|
-
* (input, output) => Promise<void> pattern.
|
|
11
|
-
*
|
|
12
|
-
* The user login runs on every session at startup — it emits the audit
|
|
13
|
-
* span, refreshes tokens, and may launch a browser flow when interactive.
|
|
14
|
-
* It never blocks the session; enforcement is governed solely by
|
|
15
|
-
* permissionMode at tool-call time.
|
|
16
|
-
*/
|
|
5
|
+
const HARNESS = "opencode";
|
|
6
|
+
const SESSION_CACHE_LIMIT = 256;
|
|
17
7
|
function createOryPlugin(clientOrConfig, deps = {}) {
|
|
18
8
|
return {
|
|
19
9
|
id: "ory",
|
|
@@ -21,45 +11,66 @@ function createOryPlugin(clientOrConfig, deps = {}) {
|
|
|
21
11
|
const client = clientOrConfig instanceof argus_1.OryAgentClient
|
|
22
12
|
? clientOrConfig
|
|
23
13
|
: clientOrConfig
|
|
24
|
-
? new argus_1.OryAgentClient({
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
})
|
|
28
|
-
: argus_1.OryAgentClient.fromEnv("opencode");
|
|
29
|
-
// Log session status on startup (never blocks)
|
|
30
|
-
await verifyOnStartup(client, deps);
|
|
14
|
+
? new argus_1.OryAgentClient({ ...clientOrConfig, harness: HARNESS })
|
|
15
|
+
: argus_1.OryAgentClient.fromEnv(HARNESS);
|
|
16
|
+
const ensureSessionStarted = createSessionStarter(client, deps);
|
|
31
17
|
return {
|
|
32
18
|
config: createConfigHandler(client),
|
|
33
|
-
"chat.message": createChatMessageHandler(client),
|
|
34
|
-
"permission.ask": createPermissionHandler(client, options),
|
|
35
|
-
"tool.execute.before": createToolBeforeHandler(client),
|
|
19
|
+
"chat.message": createChatMessageHandler(client, ensureSessionStarted),
|
|
20
|
+
"permission.ask": createPermissionHandler(client, ensureSessionStarted, options),
|
|
21
|
+
"tool.execute.before": createToolBeforeHandler(client, ensureSessionStarted),
|
|
36
22
|
"tool.execute.after": createToolAfterHandler(client),
|
|
37
23
|
};
|
|
38
24
|
},
|
|
39
25
|
};
|
|
40
26
|
}
|
|
41
|
-
// ─── config ────────────────────────────────────────────────────────
|
|
42
27
|
function createConfigHandler(client) {
|
|
43
28
|
return async (_input) => {
|
|
29
|
+
const resolved = (0, argus_1.resolveConfig)();
|
|
44
30
|
client.logger.info("lifecycle.config", {
|
|
45
|
-
projectUrl:
|
|
46
|
-
|
|
31
|
+
projectUrl: resolved.projectUrl,
|
|
32
|
+
securityConnected: resolved.security.connected,
|
|
47
33
|
});
|
|
48
|
-
client.
|
|
34
|
+
client.logger.activity("config.resolve", "ok", {
|
|
49
35
|
attributes: {
|
|
50
|
-
projectUrl:
|
|
51
|
-
|
|
36
|
+
projectUrl: resolved.projectUrl,
|
|
37
|
+
securityConnected: resolved.security.connected,
|
|
52
38
|
},
|
|
53
39
|
});
|
|
54
40
|
};
|
|
55
41
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
42
|
+
function createSessionStarter(client, deps) {
|
|
43
|
+
const sessions = new Map();
|
|
44
|
+
const completed = new Set();
|
|
45
|
+
return async (sessionId) => {
|
|
46
|
+
let start = sessions.get(sessionId);
|
|
47
|
+
if (!start) {
|
|
48
|
+
start = (0, argus_1.withHookContext)(client, { sessionId }, async () => {
|
|
49
|
+
await (0, argus_1.sessionStart)(client, {
|
|
50
|
+
harness: HARNESS,
|
|
51
|
+
binName: "ory-opencode",
|
|
52
|
+
userLogin: deps.userLogin,
|
|
53
|
+
agentGate: deps.agentGate,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
sessions.set(sessionId, start);
|
|
57
|
+
start.then(() => {
|
|
58
|
+
completed.add(sessionId);
|
|
59
|
+
while (sessions.size > SESSION_CACHE_LIMIT) {
|
|
60
|
+
const oldest = completed.values().next().value;
|
|
61
|
+
if (oldest === undefined)
|
|
62
|
+
break;
|
|
63
|
+
completed.delete(oldest);
|
|
64
|
+
sessions.delete(oldest);
|
|
65
|
+
}
|
|
66
|
+
}, () => sessions.delete(sessionId));
|
|
67
|
+
}
|
|
68
|
+
await start;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function createChatMessageHandler(client, ensureSessionStarted) {
|
|
72
|
+
return (input, _output) => (0, argus_1.withHookContext)(client, { sessionId: input.sessionID }, async () => {
|
|
73
|
+
await ensureSessionStarted(input.sessionID);
|
|
63
74
|
client.logger.info("lifecycle.chat_message", {
|
|
64
75
|
sessionID: input.sessionID,
|
|
65
76
|
messageID: input.messageID,
|
|
@@ -67,7 +78,7 @@ function createChatMessageHandler(client) {
|
|
|
67
78
|
providerID: input.model?.providerID,
|
|
68
79
|
modelID: input.model?.modelID,
|
|
69
80
|
});
|
|
70
|
-
client.
|
|
81
|
+
client.logger.activity("user.prompt", "ok", {
|
|
71
82
|
attributes: {
|
|
72
83
|
messageID: input.messageID,
|
|
73
84
|
agent: input.agent,
|
|
@@ -75,332 +86,66 @@ function createChatMessageHandler(client) {
|
|
|
75
86
|
modelID: input.model?.modelID,
|
|
76
87
|
},
|
|
77
88
|
});
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
// ─── Session verification on startup ────────────────────────────────
|
|
81
|
-
async function verifyOnStartup(client, deps) {
|
|
82
|
-
// Run the user login on every session. The flow emits the user.auth
|
|
83
|
-
// audit span, refreshes tokens, and may prompt the user, but never
|
|
84
|
-
// prevents the session from starting — enforcement is governed solely
|
|
85
|
-
// by permissionMode at tool-call time.
|
|
86
|
-
const userGate = deps.userLogin ?? argus_1.ensureUserAuthenticated;
|
|
87
|
-
await userGate(client, {
|
|
88
|
-
binName: "ory-opencode",
|
|
89
|
-
harness: "opencode",
|
|
90
89
|
});
|
|
91
|
-
// Resolve the agent identity (machine credentials). Never blocks;
|
|
92
|
-
// attaches the agent's bearer token to outgoing Ory API calls.
|
|
93
|
-
const agentGate = deps.agentGate ?? argus_1.ensureAgentIdentity;
|
|
94
|
-
await agentGate(client, { projectUrl: (0, argus_1.resolveConfig)().projectUrl, harness: "opencode" });
|
|
95
|
-
// Record the user→agent delegation so the audit trail captures that
|
|
96
|
-
// this user authorized this agent for this session. Written at most once
|
|
97
|
-
// per install. Best-effort: requires both principals to be populated, and
|
|
98
|
-
// any failure is logged and swallowed (fail-open — delegation tracking is
|
|
99
|
-
// for audit, not enforcement).
|
|
100
|
-
await (0, argus_1.writeUserDelegatesAgent)(client);
|
|
101
90
|
}
|
|
102
|
-
|
|
103
|
-
function createPermissionHandler(client, _options) {
|
|
91
|
+
function createPermissionHandler(client, ensureSessionStarted, _options) {
|
|
104
92
|
return async (input, output) => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
toolID:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// the final call. Pre-answering on Ory's behalf hides the prompt the
|
|
118
|
-
// user is meant to see.
|
|
119
|
-
if ((0, argus_1.isInteractiveTool)("opencode", toolId)) {
|
|
120
|
-
client.tracer.record("user.interaction", "ok", {
|
|
121
|
-
attributes: { harness: "opencode", toolName: toolId, kind: "permission-ask" },
|
|
93
|
+
const run = async () => {
|
|
94
|
+
if (input.sessionID)
|
|
95
|
+
await ensureSessionStarted(input.sessionID);
|
|
96
|
+
const toolName = input.toolID ?? input.type ?? "unknown";
|
|
97
|
+
client.logger.info("lifecycle.permission_ask", { toolID: toolName, title: input.title });
|
|
98
|
+
const result = await (0, argus_1.decideTool)(client, {
|
|
99
|
+
harness: HARNESS,
|
|
100
|
+
toolName,
|
|
101
|
+
subjectFallback: input.sessionID ? `session:${input.sessionID}` : undefined,
|
|
102
|
+
canBlock: true,
|
|
103
|
+
mcpTool: (0, argus_1.parseMcpToolGeneric)(toolName) ?? undefined,
|
|
104
|
+
extraActivityAttributes: { kind: "permission-ask" },
|
|
122
105
|
});
|
|
123
|
-
|
|
106
|
+
applyPermissionAskDecision(output, result.kind);
|
|
107
|
+
};
|
|
108
|
+
if (input.sessionID) {
|
|
109
|
+
await (0, argus_1.withHookContext)(client, { sessionId: input.sessionID }, run);
|
|
124
110
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// would silently match the agent's tuples instead of the user's.
|
|
128
|
-
const subject = (0, argus_1.resolveUserSubject)(client, input.sessionID ? `session:${input.sessionID}` : undefined);
|
|
129
|
-
const mcpTool = (0, argus_1.parseMcpToolGeneric)(toolId);
|
|
130
|
-
try {
|
|
131
|
-
if (mcpTool) {
|
|
132
|
-
const mcpResult = await (0, argus_1.checkMcpPermission)(client, mcpTool, {
|
|
133
|
-
subject,
|
|
134
|
-
spanAttributes: { toolName: toolId },
|
|
135
|
-
});
|
|
136
|
-
const decision = (0, argus_1.applyPermissionMode)(client, mcpResult.allowed, {
|
|
137
|
-
object: mcpTool.serverName,
|
|
138
|
-
relation: "use",
|
|
139
|
-
subjectId: (0, argus_1.subjectLabel)(subject),
|
|
140
|
-
...("subjectSet" in subject ? { subjectSet: subject.subjectSet } : {}),
|
|
141
|
-
spanAttributes: { toolName: toolId, mcpServer: mcpTool.serverName, mcpTool: mcpTool.toolName },
|
|
142
|
-
});
|
|
143
|
-
applyPermissionAskDecision(output, decision.kind);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
const outcome = await (0, argus_1.gateToolCall)(client, {
|
|
147
|
-
harness: "opencode",
|
|
148
|
-
toolName: toolId,
|
|
149
|
-
check: { namespace: resolveNamespace(), object: toolId, relation: "use", ...subject },
|
|
150
|
-
spanAttributes: { toolName: toolId },
|
|
151
|
-
});
|
|
152
|
-
if (outcome.kind === "interactive" || outcome.kind === "audit_only") {
|
|
153
|
-
// Ory is disabled (audit-only kill switch) or this is a
|
|
154
|
-
// user-interaction primitive: leave output.status as "ask" (default)
|
|
155
|
-
// so the harness's native prompt reaches the user.
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
const decision = outcome;
|
|
159
|
-
if (decision.kind === "fail_open") {
|
|
160
|
-
const code = decision.error.code;
|
|
161
|
-
if (code === "network_error" || code === "rate_limited") {
|
|
162
|
-
client.logger.warn("permission.fallback", {
|
|
163
|
-
toolID: toolId,
|
|
164
|
-
code,
|
|
165
|
-
});
|
|
166
|
-
// Leave output.status as "ask" (default)
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
output.status = "ask";
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
applyPermissionAskDecision(output, decision.kind);
|
|
173
|
-
}
|
|
174
|
-
catch (err) {
|
|
175
|
-
if (isOryError(err) && (err.code === "network_error" || err.code === "rate_limited")) {
|
|
176
|
-
client.logger.warn("permission.fallback", {
|
|
177
|
-
toolID: toolId,
|
|
178
|
-
code: err.code,
|
|
179
|
-
});
|
|
180
|
-
// Leave output.status as "ask" (default)
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
// For other errors, also fall back to ask
|
|
184
|
-
output.status = "ask";
|
|
111
|
+
else {
|
|
112
|
+
await run();
|
|
185
113
|
}
|
|
186
114
|
};
|
|
187
115
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
116
|
+
function createToolBeforeHandler(client, ensureSessionStarted) {
|
|
117
|
+
return (input, output) => (0, argus_1.withHookContext)(client, { sessionId: input.sessionID }, async () => {
|
|
118
|
+
client.logger.info("lifecycle.tool_execute_before", input);
|
|
119
|
+
await ensureSessionStarted(input.sessionID);
|
|
120
|
+
const result = await (0, argus_1.gate)(client, {
|
|
121
|
+
harness: HARNESS,
|
|
122
|
+
toolName: input.tool,
|
|
123
|
+
toolArgs: output.args,
|
|
124
|
+
subjectFallback: `session:${input.sessionID}`,
|
|
125
|
+
canBlock: false,
|
|
126
|
+
mcpTool: (0, argus_1.parseMcpToolGeneric)(input.tool) ?? undefined,
|
|
195
127
|
});
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
traceId: (0, argus_1.deriveTraceId)(input.sessionID),
|
|
199
|
-
sessionId: input.sessionID,
|
|
200
|
-
});
|
|
201
|
-
const inputSummary = (0, argus_1.summarizeToolInput)(input.tool, output.args);
|
|
202
|
-
// In audit-only mode, log the invocation but skip permission checks
|
|
203
|
-
if ((0, argus_1.resolveConfig)().auditOnly) {
|
|
204
|
-
client.tracer.record("tool.invoke", "ok", {
|
|
205
|
-
attributes: { toolName: input.tool, ...inputSummary },
|
|
206
|
-
});
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
const subject = (0, argus_1.resolveUserSubject)(client, input.sessionID ? `session:${input.sessionID}` : undefined);
|
|
210
|
-
const subjectId = (0, argus_1.subjectLabel)(subject);
|
|
211
|
-
const mcpTool = (0, argus_1.parseMcpToolGeneric)(input.tool);
|
|
212
|
-
try {
|
|
213
|
-
if (mcpTool) {
|
|
214
|
-
const mcpResult = await (0, argus_1.checkMcpPermission)(client, mcpTool, {
|
|
215
|
-
subject,
|
|
216
|
-
spanAttributes: { toolName: input.tool },
|
|
217
|
-
});
|
|
218
|
-
const mcpAttrs = {
|
|
219
|
-
toolName: input.tool,
|
|
220
|
-
mcpServer: mcpTool.serverName,
|
|
221
|
-
mcpTool: mcpTool.toolName,
|
|
222
|
-
...inputSummary,
|
|
223
|
-
};
|
|
224
|
-
const decision = (0, argus_1.applyPermissionMode)(client, mcpResult.allowed, {
|
|
225
|
-
object: mcpTool.serverName,
|
|
226
|
-
relation: "use",
|
|
227
|
-
subjectId,
|
|
228
|
-
...("subjectSet" in subject ? { subjectSet: subject.subjectSet } : {}),
|
|
229
|
-
spanAttributes: mcpAttrs,
|
|
230
|
-
});
|
|
231
|
-
const decisionAttrs = decision.spanAttributes;
|
|
232
|
-
if (decision.kind === "allow") {
|
|
233
|
-
client.tracer.record("tool.invoke", "ok", {
|
|
234
|
-
attributes: { ...mcpAttrs, ...decisionAttrs },
|
|
235
|
-
});
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
if (decision.kind === "observe") {
|
|
239
|
-
client.tracer.record("tool.block", "denied", {
|
|
240
|
-
attributes: { ...mcpAttrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
|
|
241
|
-
});
|
|
242
|
-
client.tracer.record("tool.invoke", "ok", {
|
|
243
|
-
attributes: { ...mcpAttrs, ...decisionAttrs, allowed: false, observed: true },
|
|
244
|
-
});
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
client.tracer.record("tool.block", "denied", {
|
|
248
|
-
attributes: { ...mcpAttrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
|
|
249
|
-
});
|
|
250
|
-
const message = (0, argus_1.formatDenialMessage)({
|
|
251
|
-
tool: input.tool,
|
|
252
|
-
subjectId,
|
|
253
|
-
mcp: mcpTool,
|
|
254
|
-
});
|
|
255
|
-
client.logger.warn("tool.denied", {
|
|
256
|
-
tool: input.tool,
|
|
257
|
-
subjectId,
|
|
258
|
-
message,
|
|
259
|
-
});
|
|
260
|
-
// OpenCode has no native block signal in tool.execute.before;
|
|
261
|
-
// throwing aborts execution (documented .env-protection pattern)
|
|
262
|
-
// and surfaces error.message back to the agent.
|
|
263
|
-
throw new argus_1.OryDenialError({
|
|
264
|
-
tool: input.tool,
|
|
265
|
-
subjectId,
|
|
266
|
-
mcp: mcpTool,
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
const namespace = resolveNamespace();
|
|
270
|
-
const outcome = await (0, argus_1.gateToolCall)(client, {
|
|
271
|
-
harness: "opencode",
|
|
272
|
-
toolName: input.tool,
|
|
273
|
-
check: { namespace, object: input.tool, relation: "use", ...subject },
|
|
274
|
-
spanAttributes: { toolName: input.tool },
|
|
275
|
-
});
|
|
276
|
-
// Interactive tools (operator-extensible via ORY_INTERACTIVE_TOOLS):
|
|
277
|
-
// user.interaction span is already recorded; pass through with no
|
|
278
|
-
// throw, no tool.invoke, no tool.block.
|
|
279
|
-
if (outcome.kind === "interactive") {
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
const decision = outcome;
|
|
283
|
-
if (decision.kind === "fail_open") {
|
|
284
|
-
handlePermissionError(decision.error, input.tool, client);
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
const attrs = { toolName: input.tool, ...inputSummary };
|
|
288
|
-
const decisionAttrs = decision.spanAttributes;
|
|
289
|
-
if (decision.kind === "allow") {
|
|
290
|
-
client.tracer.record("tool.invoke", "ok", {
|
|
291
|
-
attributes: { ...attrs, ...decisionAttrs, allowed: true },
|
|
292
|
-
});
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
if (decision.kind === "observe") {
|
|
296
|
-
client.tracer.record("tool.block", "denied", {
|
|
297
|
-
attributes: { ...attrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
|
|
298
|
-
});
|
|
299
|
-
client.tracer.record("tool.invoke", "ok", {
|
|
300
|
-
attributes: { ...attrs, ...decisionAttrs, allowed: false, observed: true },
|
|
301
|
-
});
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
client.tracer.record("tool.block", "denied", {
|
|
305
|
-
attributes: { ...attrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
|
|
306
|
-
});
|
|
307
|
-
const message = (0, argus_1.formatDenialMessage)({
|
|
308
|
-
tool: input.tool,
|
|
309
|
-
subjectId,
|
|
310
|
-
namespace,
|
|
311
|
-
});
|
|
312
|
-
client.logger.warn("tool.denied", {
|
|
313
|
-
tool: input.tool,
|
|
314
|
-
subjectId,
|
|
315
|
-
message,
|
|
316
|
-
});
|
|
317
|
-
throw new argus_1.OryDenialError({
|
|
128
|
+
if (result.kind === "deny") {
|
|
129
|
+
throw new argus_1.OryDenialError(result.denialContext ?? {
|
|
318
130
|
tool: input.tool,
|
|
319
|
-
subjectId,
|
|
320
|
-
namespace,
|
|
131
|
+
subjectId: result.subject,
|
|
132
|
+
namespace: result.namespace,
|
|
321
133
|
});
|
|
322
134
|
}
|
|
323
|
-
|
|
324
|
-
if (err instanceof argus_1.OryDenialError)
|
|
325
|
-
throw err;
|
|
326
|
-
handlePermissionError(err, input.tool, client);
|
|
327
|
-
}
|
|
328
|
-
};
|
|
135
|
+
});
|
|
329
136
|
}
|
|
330
|
-
// ─── tool.execute.after ─────────────────────────────────────────────
|
|
331
137
|
function createToolAfterHandler(client) {
|
|
332
|
-
return
|
|
333
|
-
client.logger.info("lifecycle.tool_execute_after",
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
});
|
|
338
|
-
client.tracer.setContext({
|
|
339
|
-
traceId: (0, argus_1.deriveTraceId)(input.sessionID),
|
|
340
|
-
sessionId: input.sessionID,
|
|
138
|
+
return (input, output) => (0, argus_1.withHookContext)(client, { sessionId: input.sessionID }, async () => {
|
|
139
|
+
client.logger.info("lifecycle.tool_execute_after", input);
|
|
140
|
+
(0, argus_1.complete)(client, {
|
|
141
|
+
toolName: input.tool,
|
|
142
|
+
output: { title: output.title, output: output.output },
|
|
341
143
|
});
|
|
342
|
-
|
|
343
|
-
// so input summary is unavailable here; output summary only.
|
|
344
|
-
client.tracer.record("tool.complete", "ok", {
|
|
345
|
-
attributes: {
|
|
346
|
-
toolName: input.tool,
|
|
347
|
-
...(0, argus_1.summarizeToolOutput)(input.tool, {
|
|
348
|
-
title: output.title,
|
|
349
|
-
output: output.output,
|
|
350
|
-
}),
|
|
351
|
-
},
|
|
352
|
-
});
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
// ─── Helpers ────────────────────────────────────────────────────────
|
|
356
|
-
function resolveNamespace() {
|
|
357
|
-
return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
|
|
144
|
+
});
|
|
358
145
|
}
|
|
359
|
-
/**
|
|
360
|
-
* Map an Ory permission decision onto OpenCode's `permission.ask` output.
|
|
361
|
-
*
|
|
362
|
-
* Only explicit outcomes answer the prompt: `deny` → `"deny"`,
|
|
363
|
-
* `allow` → `"allow"`. An `observe`-mode deny leaves `output.status`
|
|
364
|
-
* untouched (OpenCode's default is `"ask"`), so the harness's native
|
|
365
|
-
* user prompt still appears — observe mode must never escalate a
|
|
366
|
-
* policy-denied tool into an auto-approval. The observe deny is already
|
|
367
|
-
* recorded as a `permission.observe_deny` audit span by the gate.
|
|
368
|
-
*/
|
|
369
146
|
function applyPermissionAskDecision(output, kind) {
|
|
370
|
-
if (kind === "deny")
|
|
147
|
+
if (kind === "deny")
|
|
371
148
|
output.status = "deny";
|
|
372
|
-
|
|
373
|
-
else if (kind === "allow") {
|
|
149
|
+
else if (kind === "allow")
|
|
374
150
|
output.status = "allow";
|
|
375
|
-
}
|
|
376
|
-
// kind === "observe": leave the harness's native prompt in place.
|
|
377
|
-
}
|
|
378
|
-
function isOryError(err) {
|
|
379
|
-
return (typeof err === "object" &&
|
|
380
|
-
err !== null &&
|
|
381
|
-
"code" in err &&
|
|
382
|
-
"message" in err);
|
|
383
|
-
}
|
|
384
|
-
function handlePermissionError(err, tool, client) {
|
|
385
|
-
if (isOryError(err)) {
|
|
386
|
-
if (err.code === "network_error" || err.code === "rate_limited") {
|
|
387
|
-
client.logger.warn("permission.fallback", {
|
|
388
|
-
tool,
|
|
389
|
-
code: err.code,
|
|
390
|
-
message: "Failing open",
|
|
391
|
-
});
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
client.logger.error("permission.check.error", {
|
|
395
|
-
tool,
|
|
396
|
-
code: err.code,
|
|
397
|
-
message: err.message,
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
401
|
-
client.logger.error("permission.check.error", {
|
|
402
|
-
tool,
|
|
403
|
-
error: err instanceof Error ? err.message : String(err),
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
151
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ory/opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Ory plugin for OpenCode: scaffolding skills, a local Ory instance, and authentication, authorization, and audit for every tool call",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://ory.com",
|
|
@@ -33,8 +33,7 @@
|
|
|
33
33
|
"agent-security",
|
|
34
34
|
"guardrails",
|
|
35
35
|
"llm",
|
|
36
|
-
"
|
|
37
|
-
"distributed-tracing",
|
|
36
|
+
"activity-auditing",
|
|
38
37
|
"observability",
|
|
39
38
|
"kratos",
|
|
40
39
|
"keto",
|
|
@@ -45,6 +44,10 @@
|
|
|
45
44
|
"registry": "https://registry.npmjs.org/",
|
|
46
45
|
"provenance": true
|
|
47
46
|
},
|
|
47
|
+
"reova": {
|
|
48
|
+
"enabled": true,
|
|
49
|
+
"endpoint": "https://telemetry.reo.dev/data"
|
|
50
|
+
},
|
|
48
51
|
"main": "dist/index.js",
|
|
49
52
|
"types": "dist/index.d.ts",
|
|
50
53
|
"exports": {
|
|
@@ -59,12 +62,11 @@
|
|
|
59
62
|
},
|
|
60
63
|
"files": [
|
|
61
64
|
"dist",
|
|
62
|
-
"!dist/dev",
|
|
63
65
|
"!dist/**/*.tsbuildinfo"
|
|
64
66
|
],
|
|
65
67
|
"dependencies": {
|
|
66
|
-
"
|
|
67
|
-
"@ory/argus": "0.
|
|
68
|
+
"reova": "^0.7.0",
|
|
69
|
+
"@ory/argus": "1.0.0"
|
|
68
70
|
},
|
|
69
71
|
"engines": {
|
|
70
72
|
"node": ">=22"
|
|
@@ -74,7 +76,6 @@
|
|
|
74
76
|
"clean": "rm -rf dist *.tsbuildinfo",
|
|
75
77
|
"test": "vitest run",
|
|
76
78
|
"test:watch": "vitest",
|
|
77
|
-
"typecheck": "tsc --noEmit"
|
|
78
|
-
"dev": "node dist/dev/launcher.js"
|
|
79
|
+
"typecheck": "tsc --noEmit"
|
|
79
80
|
}
|
|
80
81
|
}
|