@ory/argus 0.8.0 → 0.8.2
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/client.js +2 -4
- package/dist/dev.js +48 -12
- package/dist/index.d.ts +3 -2
- package/dist/index.js +12 -1
- package/dist/lifecycle.d.ts +59 -0
- package/dist/lifecycle.js +123 -0
- package/dist/otel/otlp.d.ts +22 -10
- package/dist/otel/otlp.js +59 -22
- package/dist/permissions.d.ts +51 -0
- package/dist/permissions.js +48 -0
- package/dist/tool-catalog.d.ts +31 -0
- package/dist/tool-catalog.js +58 -1
- package/dist/tracer.d.ts +1 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -658,10 +658,8 @@ function attachOtlpExporterFromEnv(client, harness) {
|
|
|
658
658
|
return;
|
|
659
659
|
client.tracer.setExporter(exporter);
|
|
660
660
|
client.logger.info("otel.export.enabled", {
|
|
661
|
-
endpoint:
|
|
662
|
-
|
|
663
|
-
process.env.ORY_OTLP_ENDPOINT,
|
|
664
|
-
serviceName: process.env.OTEL_SERVICE_NAME ?? `ory-agent-plugin-${harness}`,
|
|
661
|
+
endpoint: exporter.endpoint,
|
|
662
|
+
serviceName: exporter.resource.attributes["service.name"],
|
|
665
663
|
});
|
|
666
664
|
}
|
|
667
665
|
/**
|
package/dist/dev.js
CHANGED
|
@@ -322,16 +322,7 @@ async function runDevLauncher(config) {
|
|
|
322
322
|
? buildLocalOryEnv(localOry.gatewayUrl, localOry.seed)
|
|
323
323
|
: { ORY_AGENT_SUBJECT_ID: sessionName }),
|
|
324
324
|
...(otelEndpoint
|
|
325
|
-
?
|
|
326
|
-
OTEL_EXPORTER_OTLP_ENDPOINT: otelEndpoint,
|
|
327
|
-
// ORY-prefixed alias for harnesses (e.g. Claude Code) that
|
|
328
|
-
// filter the env passed to hook subprocesses and drop
|
|
329
|
-
// OTEL_*-prefixed vars. The OTLP exporter reads this as a
|
|
330
|
-
// fallback so traces still reach Jaeger when OTEL_* is stripped.
|
|
331
|
-
ORY_OTLP_ENDPOINT: otelEndpoint,
|
|
332
|
-
OTEL_SERVICE_NAME: process.env.OTEL_SERVICE_NAME ??
|
|
333
|
-
`ory-agent-plugin-${config.harnessName}`,
|
|
334
|
-
}
|
|
325
|
+
? buildOtelEnv(otelEndpoint, config.harnessName)
|
|
335
326
|
: {}),
|
|
336
327
|
};
|
|
337
328
|
const result = (0, node_child_process_1.spawnSync)(config.command, forwardedArgs, {
|
|
@@ -488,10 +479,53 @@ async function bootstrapLocalOry(localDir) {
|
|
|
488
479
|
`${seed.permissions.tuples} permissions in '${namespace}' for ${seed.permissions.subject}.`);
|
|
489
480
|
return { active: true, gatewayUrl, seed };
|
|
490
481
|
}
|
|
482
|
+
/**
|
|
483
|
+
* OTel env vars the dev launcher propagates into the harness child. Each
|
|
484
|
+
* entry pairs the standard `OTEL_*` name with its `ORY_OTLP_*` alias; the
|
|
485
|
+
* alias is set in lockstep so harnesses (notably Claude Code) that sanitize
|
|
486
|
+
* `OTEL_*` out of the hook-subprocess env still receive the configuration.
|
|
487
|
+
*
|
|
488
|
+
* Keep this list in sync with `ORY_ENV_ALIAS` in
|
|
489
|
+
* `packages/core/src/otel/otlp.ts` — the exporter consults the same aliases
|
|
490
|
+
* on the read side.
|
|
491
|
+
*/
|
|
492
|
+
const OTEL_FORWARDED_VARS = [
|
|
493
|
+
["OTEL_EXPORTER_OTLP_ENDPOINT", "ORY_OTLP_ENDPOINT"],
|
|
494
|
+
["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "ORY_OTLP_TRACES_ENDPOINT"],
|
|
495
|
+
["OTEL_EXPORTER_OTLP_HEADERS", "ORY_OTLP_HEADERS"],
|
|
496
|
+
["OTEL_EXPORTER_OTLP_TRACES_HEADERS", "ORY_OTLP_TRACES_HEADERS"],
|
|
497
|
+
["OTEL_EXPORTER_OTLP_PROTOCOL", "ORY_OTLP_PROTOCOL"],
|
|
498
|
+
["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", "ORY_OTLP_TRACES_PROTOCOL"],
|
|
499
|
+
["OTEL_SERVICE_NAME", "ORY_OTLP_SERVICE_NAME"],
|
|
500
|
+
["OTEL_RESOURCE_ATTRIBUTES", "ORY_OTLP_RESOURCE_ATTRIBUTES"],
|
|
501
|
+
];
|
|
502
|
+
/**
|
|
503
|
+
* Compose the OTLP env block for the harness child process. The resolved
|
|
504
|
+
* endpoint is always written under both `OTEL_EXPORTER_OTLP_ENDPOINT` and
|
|
505
|
+
* `ORY_OTLP_ENDPOINT`; every other OTel knob inherited from the parent
|
|
506
|
+
* shell (and `OTEL_SERVICE_NAME`, which we default when unset) is mirrored
|
|
507
|
+
* to its `ORY_OTLP_*` alias on the way through.
|
|
508
|
+
*/
|
|
509
|
+
function buildOtelEnv(endpoint, harnessName) {
|
|
510
|
+
const out = {};
|
|
511
|
+
const defaults = {
|
|
512
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: endpoint,
|
|
513
|
+
OTEL_SERVICE_NAME: process.env.OTEL_SERVICE_NAME ?? `ory-agent-plugin-${harnessName}`,
|
|
514
|
+
};
|
|
515
|
+
for (const [standardName, aliasName] of OTEL_FORWARDED_VARS) {
|
|
516
|
+
const value = defaults[standardName] ?? process.env[standardName];
|
|
517
|
+
if (!value)
|
|
518
|
+
continue;
|
|
519
|
+
out[standardName] = value;
|
|
520
|
+
out[aliasName] = value;
|
|
521
|
+
}
|
|
522
|
+
return out;
|
|
523
|
+
}
|
|
491
524
|
/**
|
|
492
525
|
* Determine the OTLP endpoint for the dev launch:
|
|
493
526
|
* 1. Honor OTEL_EXPORTER_OTLP_ENDPOINT / OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
|
|
494
|
-
* from the parent shell (Honeycomb,
|
|
527
|
+
* (or their ORY_OTLP_* aliases) from the parent shell (Honeycomb,
|
|
528
|
+
* custom collector, etc.).
|
|
495
529
|
* 2. Otherwise auto-launch a local Jaeger container and use it. If Jaeger
|
|
496
530
|
* is already reachable (from `local up` or a prior dev launch), reuse
|
|
497
531
|
* it without spawning another container.
|
|
@@ -500,7 +534,9 @@ async function bootstrapLocalOry(localDir) {
|
|
|
500
534
|
*/
|
|
501
535
|
async function resolveOtelEndpoint() {
|
|
502
536
|
const fromEnv = process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ??
|
|
503
|
-
process.env.OTEL_EXPORTER_OTLP_ENDPOINT
|
|
537
|
+
process.env.OTEL_EXPORTER_OTLP_ENDPOINT ??
|
|
538
|
+
process.env.ORY_OTLP_TRACES_ENDPOINT ??
|
|
539
|
+
process.env.ORY_OTLP_ENDPOINT;
|
|
504
540
|
if (fromEnv)
|
|
505
541
|
return fromEnv;
|
|
506
542
|
console.log("[ory-dev] Ensuring Jaeger is running for trace viewing...");
|
package/dist/index.d.ts
CHANGED
|
@@ -15,8 +15,9 @@ export { runDevLauncher, type DevLauncherConfig, type InstallContext, } from "./
|
|
|
15
15
|
export { renderOrySkills, renderOryCommands, commandToSkill, commandToToml, commandToFrontmatterMarkdown, commandToPlainMarkdown, toSkillMarkdown, writeSkillTree, removeSkillDirs, ORY_SKILL_NAMES, ORY_COMMAND_SKILL_NAMES, ORY_COMMAND_SLUGS, type RenderedSkill, type RenderedCommand, type RenderProfileOptions, } from "./skills.js";
|
|
16
16
|
export { runLocalCommand, ensureDevJaeger, stopDevJaeger, DEV_JAEGER_CONTAINER, type EnsureDevJaegerResult, type StopDevJaegerResult, } from "./local/index.js";
|
|
17
17
|
export { runRegistryCommand } from "./registry/index.js";
|
|
18
|
-
export { checkAndDecide, applyPermissionMode, type PermissionDecision, type ModeDecision, type DecisionSpanAttributes, type CheckAndDecideOptions, type ApplyPermissionModeContext, } from "./permissions.js";
|
|
19
|
-
export { HARNESS_TOOL_CATALOG, KNOWN_HARNESSES, ALL_TOOLS, getToolCatalog, type KnownHarness, } from "./tool-catalog.js";
|
|
18
|
+
export { checkAndDecide, applyPermissionMode, gateToolCall, type PermissionDecision, type ModeDecision, type DecisionSpanAttributes, type CheckAndDecideOptions, type ApplyPermissionModeContext, type GateToolCallArgs, type ToolGateOutcome, } from "./permissions.js";
|
|
19
|
+
export { HARNESS_TOOL_CATALOG, KNOWN_HARNESSES, ALL_TOOLS, getToolCatalog, INTERACTIVE_TOOL_CATALOG, getInteractiveToolCatalog, isInteractiveTool, type KnownHarness, } from "./tool-catalog.js";
|
|
20
|
+
export { classifyLifecycle, isUserFacingPhase, isToolExecutionPhase, HARNESS_LIFECYCLE_MAP, USER_FACING_PHASES, TOOL_EXECUTION_PHASES, type LifecyclePhase, } from "./lifecycle.js";
|
|
20
21
|
export { parseClaudeCodeMcpTool, parseGeminiMcpTool, parseMcpToolGeneric, checkMcpPermission, type McpToolIdentifier, type McpPermissionCheckOptions, type McpPermissionResult, } from "./mcp.js";
|
|
21
22
|
export { resolveUserSubject, subjectLabel, type UserSubjectRef, } from "./subject.js";
|
|
22
23
|
export { formatDenialMessage, formatDenialSummary, formatAlertMessage, formatAlertSummary, alertAttributes, OryDenialError, type DenialContext, type AlertAttributes, } from "./denial.js";
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.printOryConfig = exports.runAgentCommand = exports.runConfigureCommand = exports.AGENT_TOKEN_EXPIRY_SKEW_SEC = exports.clearSubAgentDynamicCredentials = exports.saveSubAgentDynamicCredentials = exports.loadSubAgentDynamicCredentials = exports.clearAgentDynamicCredentials = exports.saveAgentDynamicCredentials = exports.loadAgentDynamicCredentials = exports.registerAgentClient = exports.fetchClientCredentialsToken = exports.ensureSubAgentIdentity = exports.ensureAgentIdentity = exports.resolveAgentCredentials = exports.ensureAuthenticated = exports.ensureUserAuthenticated = exports.TOKEN_EXPIRY_SKEW_SEC = exports.waitForPeerTokensSync = exports.waitForPeerTokens = exports.clearPkceFlightLock = exports.tryAcquirePkceFlightLock = exports.refreshAndSave = exports.isExpired = exports.clearTokens = exports.saveTokens = exports.loadTokens = exports.DEFAULT_LOGIN_TIMEOUT_MS = exports.LOOPBACK_PORTS = exports.buildAuthorizeUrl = exports.sha256Base64Url = exports.generateCodeVerifier = exports.detectHeadless = exports.refreshAccessToken = exports.pkceLogin = exports.getHarnessDataDir = exports.getDataDir = exports.getConfigPath = exports.mutateConfig = exports.resolveConfig = exports.saveConfig = exports.loadConfig = exports.watchTraceFile = exports.formatSpan = exports.deriveTraceId = exports.ActiveSpan = exports.Tracer = exports.redactLogData = exports.DebugLogger = exports.OryAgentClient = void 0;
|
|
4
4
|
exports.runLocalCommand = exports.ORY_COMMAND_SLUGS = exports.ORY_COMMAND_SKILL_NAMES = exports.ORY_SKILL_NAMES = exports.removeSkillDirs = exports.writeSkillTree = exports.toSkillMarkdown = exports.commandToPlainMarkdown = exports.commandToFrontmatterMarkdown = exports.commandToToml = exports.commandToSkill = exports.renderOryCommands = exports.renderOrySkills = exports.runDevLauncher = exports.unregisterPlugin = exports.registerPlugin = exports.removeMcpServer = exports.mergeMcpServer = exports.mcpServerEntry = exports.resolveMcpServerCommand = exports.printNextSteps = exports.printSetupHelp = exports.removeFlatHooks = exports.mergeFlatHooks = exports.flatHookEntry = exports.removeMatcherHooks = exports.mergeMatcherHooks = exports.matcherHookEntry = exports.resolveHookCommand = exports.isOryHookCommand = exports.writeJsonFile = exports.readJsonFile = exports.parseSetupArgs = exports.printPermissionsSection = exports.printAgentIdentitySection = exports.printUserIdentitySection = exports.runStatusCommand = exports.printPermissionsOnboardingHelp = exports.maybeAutoBootstrap = exports.isUserIdentityCached = exports.runPermissionsCommand = exports.interactiveConfigPrompt = exports.promptForProjectUrl = exports.promptOnTty = exports.isTtyAvailable = exports.runWatchCommand = exports.printTraceTail = exports.printEnvHelp = exports.printLogTail = exports.printEnvironment = void 0;
|
|
5
|
-
exports.parseKeyValueList = exports.otlpExporterFromEnv = exports.OtlpExporter = exports.summarizeToolOutput = exports.summarizeToolInput = exports.OryDenialError = exports.alertAttributes = exports.formatAlertSummary = exports.formatAlertMessage = exports.formatDenialSummary = exports.formatDenialMessage = exports.subjectLabel = exports.resolveUserSubject = exports.checkMcpPermission = exports.parseMcpToolGeneric = exports.parseGeminiMcpTool = exports.parseClaudeCodeMcpTool = exports.getToolCatalog = exports.ALL_TOOLS = exports.KNOWN_HARNESSES = exports.HARNESS_TOOL_CATALOG = exports.applyPermissionMode = exports.checkAndDecide = exports.runRegistryCommand = exports.DEV_JAEGER_CONTAINER = exports.stopDevJaeger = exports.ensureDevJaeger = void 0;
|
|
5
|
+
exports.parseKeyValueList = exports.otlpExporterFromEnv = exports.OtlpExporter = exports.summarizeToolOutput = exports.summarizeToolInput = exports.OryDenialError = exports.alertAttributes = exports.formatAlertSummary = exports.formatAlertMessage = exports.formatDenialSummary = exports.formatDenialMessage = exports.subjectLabel = exports.resolveUserSubject = exports.checkMcpPermission = exports.parseMcpToolGeneric = exports.parseGeminiMcpTool = exports.parseClaudeCodeMcpTool = exports.TOOL_EXECUTION_PHASES = exports.USER_FACING_PHASES = exports.HARNESS_LIFECYCLE_MAP = exports.isToolExecutionPhase = exports.isUserFacingPhase = exports.classifyLifecycle = exports.isInteractiveTool = exports.getInteractiveToolCatalog = exports.INTERACTIVE_TOOL_CATALOG = exports.getToolCatalog = exports.ALL_TOOLS = exports.KNOWN_HARNESSES = exports.HARNESS_TOOL_CATALOG = exports.gateToolCall = exports.applyPermissionMode = exports.checkAndDecide = exports.runRegistryCommand = exports.DEV_JAEGER_CONTAINER = exports.stopDevJaeger = exports.ensureDevJaeger = void 0;
|
|
6
6
|
var client_js_1 = require("./client.js");
|
|
7
7
|
Object.defineProperty(exports, "OryAgentClient", { enumerable: true, get: function () { return client_js_1.OryAgentClient; } });
|
|
8
8
|
var logger_js_1 = require("./logger.js");
|
|
@@ -126,11 +126,22 @@ Object.defineProperty(exports, "runRegistryCommand", { enumerable: true, get: fu
|
|
|
126
126
|
var permissions_js_1 = require("./permissions.js");
|
|
127
127
|
Object.defineProperty(exports, "checkAndDecide", { enumerable: true, get: function () { return permissions_js_1.checkAndDecide; } });
|
|
128
128
|
Object.defineProperty(exports, "applyPermissionMode", { enumerable: true, get: function () { return permissions_js_1.applyPermissionMode; } });
|
|
129
|
+
Object.defineProperty(exports, "gateToolCall", { enumerable: true, get: function () { return permissions_js_1.gateToolCall; } });
|
|
129
130
|
var tool_catalog_js_1 = require("./tool-catalog.js");
|
|
130
131
|
Object.defineProperty(exports, "HARNESS_TOOL_CATALOG", { enumerable: true, get: function () { return tool_catalog_js_1.HARNESS_TOOL_CATALOG; } });
|
|
131
132
|
Object.defineProperty(exports, "KNOWN_HARNESSES", { enumerable: true, get: function () { return tool_catalog_js_1.KNOWN_HARNESSES; } });
|
|
132
133
|
Object.defineProperty(exports, "ALL_TOOLS", { enumerable: true, get: function () { return tool_catalog_js_1.ALL_TOOLS; } });
|
|
133
134
|
Object.defineProperty(exports, "getToolCatalog", { enumerable: true, get: function () { return tool_catalog_js_1.getToolCatalog; } });
|
|
135
|
+
Object.defineProperty(exports, "INTERACTIVE_TOOL_CATALOG", { enumerable: true, get: function () { return tool_catalog_js_1.INTERACTIVE_TOOL_CATALOG; } });
|
|
136
|
+
Object.defineProperty(exports, "getInteractiveToolCatalog", { enumerable: true, get: function () { return tool_catalog_js_1.getInteractiveToolCatalog; } });
|
|
137
|
+
Object.defineProperty(exports, "isInteractiveTool", { enumerable: true, get: function () { return tool_catalog_js_1.isInteractiveTool; } });
|
|
138
|
+
var lifecycle_js_1 = require("./lifecycle.js");
|
|
139
|
+
Object.defineProperty(exports, "classifyLifecycle", { enumerable: true, get: function () { return lifecycle_js_1.classifyLifecycle; } });
|
|
140
|
+
Object.defineProperty(exports, "isUserFacingPhase", { enumerable: true, get: function () { return lifecycle_js_1.isUserFacingPhase; } });
|
|
141
|
+
Object.defineProperty(exports, "isToolExecutionPhase", { enumerable: true, get: function () { return lifecycle_js_1.isToolExecutionPhase; } });
|
|
142
|
+
Object.defineProperty(exports, "HARNESS_LIFECYCLE_MAP", { enumerable: true, get: function () { return lifecycle_js_1.HARNESS_LIFECYCLE_MAP; } });
|
|
143
|
+
Object.defineProperty(exports, "USER_FACING_PHASES", { enumerable: true, get: function () { return lifecycle_js_1.USER_FACING_PHASES; } });
|
|
144
|
+
Object.defineProperty(exports, "TOOL_EXECUTION_PHASES", { enumerable: true, get: function () { return lifecycle_js_1.TOOL_EXECUTION_PHASES; } });
|
|
134
145
|
var mcp_js_1 = require("./mcp.js");
|
|
135
146
|
Object.defineProperty(exports, "parseClaudeCodeMcpTool", { enumerable: true, get: function () { return mcp_js_1.parseClaudeCodeMcpTool; } });
|
|
136
147
|
Object.defineProperty(exports, "parseGeminiMcpTool", { enumerable: true, get: function () { return mcp_js_1.parseGeminiMcpTool; } });
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical lifecycle vocabulary for harness plugins.
|
|
3
|
+
*
|
|
4
|
+
* Each harness exposes a different set of hook event names (Claude Code's
|
|
5
|
+
* `PreToolUse`, Gemini's `BeforeTool`, OpenCode's `tool.execute.before`,
|
|
6
|
+
* …). Mapping them onto a shared phase set lets the plugin code reason
|
|
7
|
+
* about *what is happening* without first remembering *which harness
|
|
8
|
+
* we're in*. Spans, logs, docs, and tests all reference the same set of
|
|
9
|
+
* phases.
|
|
10
|
+
*
|
|
11
|
+
* Phases split into three buckets:
|
|
12
|
+
*
|
|
13
|
+
* - **Session / agent lifecycle.** `session.start`, `session.stop`,
|
|
14
|
+
* `compaction`. The session begins or ends, or the harness compacts
|
|
15
|
+
* context. Plugins authenticate here (start) and may flush state
|
|
16
|
+
* (stop).
|
|
17
|
+
*
|
|
18
|
+
* - **User-facing interactions.** `user.prompt`, `user.interaction`,
|
|
19
|
+
* `permission.ask`. The harness is communicating with the human:
|
|
20
|
+
* they typed a prompt, the harness is surfacing a confirmation,
|
|
21
|
+
* the harness is asking whether to allow a tool. These are *traced*
|
|
22
|
+
* but never gated by a tool permission check — the user is the
|
|
23
|
+
* final decision-maker. (`permission.ask` does consult Ory to
|
|
24
|
+
* inform the harness's UI, but the call is advisory.)
|
|
25
|
+
*
|
|
26
|
+
* - **Tool execution.** `tool.before`, `tool.after`, `tool.failure`,
|
|
27
|
+
* `subagent.start`, `subagent.stop`. The agent is reaching into an
|
|
28
|
+
* external system (or spawning a child agent that will). These are
|
|
29
|
+
* the only phases that run a true `use` permission gate.
|
|
30
|
+
*
|
|
31
|
+
* `passthrough` is the explicit "nothing to do here" bucket — the
|
|
32
|
+
* harness fired an event we don't model, and we record an audit span
|
|
33
|
+
* and return.
|
|
34
|
+
*/
|
|
35
|
+
export type LifecyclePhase = "session.start" | "session.stop" | "user.prompt" | "user.interaction" | "permission.ask" | "tool.before" | "tool.after" | "tool.failure" | "subagent.start" | "subagent.stop" | "compaction" | "passthrough";
|
|
36
|
+
/**
|
|
37
|
+
* Per-harness map from the harness's native event name to a canonical
|
|
38
|
+
* {@link LifecyclePhase}. Event names that don't appear here resolve to
|
|
39
|
+
* `passthrough` via {@link classifyLifecycle}.
|
|
40
|
+
*/
|
|
41
|
+
export declare const HARNESS_LIFECYCLE_MAP: Record<string, Record<string, LifecyclePhase>>;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a harness event name to its canonical {@link LifecyclePhase}.
|
|
44
|
+
* Unknown harnesses or unknown event names both yield `passthrough`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function classifyLifecycle(harness: string, event: string): LifecyclePhase;
|
|
47
|
+
/**
|
|
48
|
+
* Phases that surface to the human (prompts, confirmations, notifications,
|
|
49
|
+
* advisory permission asks). Tool-call permission gates must never fire
|
|
50
|
+
* on these — the user is the decision-maker.
|
|
51
|
+
*/
|
|
52
|
+
export declare const USER_FACING_PHASES: ReadonlySet<LifecyclePhase>;
|
|
53
|
+
/**
|
|
54
|
+
* Phases that represent actual tool execution against external systems.
|
|
55
|
+
* Only these run a real `use` permission check.
|
|
56
|
+
*/
|
|
57
|
+
export declare const TOOL_EXECUTION_PHASES: ReadonlySet<LifecyclePhase>;
|
|
58
|
+
export declare function isUserFacingPhase(phase: LifecyclePhase): boolean;
|
|
59
|
+
export declare function isToolExecutionPhase(phase: LifecyclePhase): boolean;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Canonical lifecycle vocabulary for harness plugins.
|
|
4
|
+
*
|
|
5
|
+
* Each harness exposes a different set of hook event names (Claude Code's
|
|
6
|
+
* `PreToolUse`, Gemini's `BeforeTool`, OpenCode's `tool.execute.before`,
|
|
7
|
+
* …). Mapping them onto a shared phase set lets the plugin code reason
|
|
8
|
+
* about *what is happening* without first remembering *which harness
|
|
9
|
+
* we're in*. Spans, logs, docs, and tests all reference the same set of
|
|
10
|
+
* phases.
|
|
11
|
+
*
|
|
12
|
+
* Phases split into three buckets:
|
|
13
|
+
*
|
|
14
|
+
* - **Session / agent lifecycle.** `session.start`, `session.stop`,
|
|
15
|
+
* `compaction`. The session begins or ends, or the harness compacts
|
|
16
|
+
* context. Plugins authenticate here (start) and may flush state
|
|
17
|
+
* (stop).
|
|
18
|
+
*
|
|
19
|
+
* - **User-facing interactions.** `user.prompt`, `user.interaction`,
|
|
20
|
+
* `permission.ask`. The harness is communicating with the human:
|
|
21
|
+
* they typed a prompt, the harness is surfacing a confirmation,
|
|
22
|
+
* the harness is asking whether to allow a tool. These are *traced*
|
|
23
|
+
* but never gated by a tool permission check — the user is the
|
|
24
|
+
* final decision-maker. (`permission.ask` does consult Ory to
|
|
25
|
+
* inform the harness's UI, but the call is advisory.)
|
|
26
|
+
*
|
|
27
|
+
* - **Tool execution.** `tool.before`, `tool.after`, `tool.failure`,
|
|
28
|
+
* `subagent.start`, `subagent.stop`. The agent is reaching into an
|
|
29
|
+
* external system (or spawning a child agent that will). These are
|
|
30
|
+
* the only phases that run a true `use` permission gate.
|
|
31
|
+
*
|
|
32
|
+
* `passthrough` is the explicit "nothing to do here" bucket — the
|
|
33
|
+
* harness fired an event we don't model, and we record an audit span
|
|
34
|
+
* and return.
|
|
35
|
+
*/
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.TOOL_EXECUTION_PHASES = exports.USER_FACING_PHASES = exports.HARNESS_LIFECYCLE_MAP = void 0;
|
|
38
|
+
exports.classifyLifecycle = classifyLifecycle;
|
|
39
|
+
exports.isUserFacingPhase = isUserFacingPhase;
|
|
40
|
+
exports.isToolExecutionPhase = isToolExecutionPhase;
|
|
41
|
+
/**
|
|
42
|
+
* Per-harness map from the harness's native event name to a canonical
|
|
43
|
+
* {@link LifecyclePhase}. Event names that don't appear here resolve to
|
|
44
|
+
* `passthrough` via {@link classifyLifecycle}.
|
|
45
|
+
*/
|
|
46
|
+
exports.HARNESS_LIFECYCLE_MAP = {
|
|
47
|
+
"claude-code": {
|
|
48
|
+
SessionStart: "session.start",
|
|
49
|
+
SessionEnd: "session.stop",
|
|
50
|
+
PreToolUse: "tool.before",
|
|
51
|
+
PostToolUse: "tool.after",
|
|
52
|
+
PostToolUseFailure: "tool.failure",
|
|
53
|
+
PermissionRequest: "permission.ask",
|
|
54
|
+
UserPromptSubmit: "user.prompt",
|
|
55
|
+
SubagentStart: "subagent.start",
|
|
56
|
+
SubagentStop: "subagent.stop",
|
|
57
|
+
},
|
|
58
|
+
codex: {
|
|
59
|
+
SessionStart: "session.start",
|
|
60
|
+
Stop: "session.stop",
|
|
61
|
+
PreToolUse: "tool.before",
|
|
62
|
+
PostToolUse: "tool.after",
|
|
63
|
+
PermissionRequest: "permission.ask",
|
|
64
|
+
UserPromptSubmit: "user.prompt",
|
|
65
|
+
},
|
|
66
|
+
"gemini-cli": {
|
|
67
|
+
SessionStart: "session.start",
|
|
68
|
+
SessionEnd: "session.stop",
|
|
69
|
+
BeforeTool: "tool.before",
|
|
70
|
+
AfterTool: "tool.after",
|
|
71
|
+
BeforeToolSelection: "tool.before",
|
|
72
|
+
Notification: "user.interaction",
|
|
73
|
+
PreCompress: "compaction",
|
|
74
|
+
},
|
|
75
|
+
openclaw: {
|
|
76
|
+
session_start: "session.start",
|
|
77
|
+
before_agent_run: "user.prompt",
|
|
78
|
+
before_tool_call: "tool.before",
|
|
79
|
+
after_tool_call: "tool.after",
|
|
80
|
+
subagent_spawning: "subagent.start",
|
|
81
|
+
subagent_spawned: "subagent.start",
|
|
82
|
+
subagent_ended: "subagent.stop",
|
|
83
|
+
},
|
|
84
|
+
opencode: {
|
|
85
|
+
config: "session.start",
|
|
86
|
+
"chat.message": "user.prompt",
|
|
87
|
+
"permission.ask": "permission.ask",
|
|
88
|
+
"tool.execute.before": "tool.before",
|
|
89
|
+
"tool.execute.after": "tool.after",
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Resolve a harness event name to its canonical {@link LifecyclePhase}.
|
|
94
|
+
* Unknown harnesses or unknown event names both yield `passthrough`.
|
|
95
|
+
*/
|
|
96
|
+
function classifyLifecycle(harness, event) {
|
|
97
|
+
return exports.HARNESS_LIFECYCLE_MAP[harness]?.[event] ?? "passthrough";
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Phases that surface to the human (prompts, confirmations, notifications,
|
|
101
|
+
* advisory permission asks). Tool-call permission gates must never fire
|
|
102
|
+
* on these — the user is the decision-maker.
|
|
103
|
+
*/
|
|
104
|
+
exports.USER_FACING_PHASES = new Set([
|
|
105
|
+
"user.prompt",
|
|
106
|
+
"user.interaction",
|
|
107
|
+
"permission.ask",
|
|
108
|
+
]);
|
|
109
|
+
/**
|
|
110
|
+
* Phases that represent actual tool execution against external systems.
|
|
111
|
+
* Only these run a real `use` permission check.
|
|
112
|
+
*/
|
|
113
|
+
exports.TOOL_EXECUTION_PHASES = new Set([
|
|
114
|
+
"tool.before",
|
|
115
|
+
"tool.after",
|
|
116
|
+
"tool.failure",
|
|
117
|
+
]);
|
|
118
|
+
function isUserFacingPhase(phase) {
|
|
119
|
+
return exports.USER_FACING_PHASES.has(phase);
|
|
120
|
+
}
|
|
121
|
+
function isToolExecutionPhase(phase) {
|
|
122
|
+
return exports.TOOL_EXECUTION_PHASES.has(phase);
|
|
123
|
+
}
|
package/dist/otel/otlp.d.ts
CHANGED
|
@@ -7,16 +7,22 @@
|
|
|
7
7
|
* is converted to a `ReadableSpan` shape and handed to the SDK exporter,
|
|
8
8
|
* which serializes (JSON or protobuf) and POSTs to an OTLP HTTP endpoint.
|
|
9
9
|
*
|
|
10
|
-
* Standard OTel env vars consumed by `otlpExporterFromEnv()
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
10
|
+
* Standard OTel env vars consumed by `otlpExporterFromEnv()`. Each one has
|
|
11
|
+
* an `ORY_OTLP_*`-prefixed alias that is consulted as a fallback when the
|
|
12
|
+
* standard name is unset. Some harnesses (notably Claude Code) sanitize the
|
|
13
|
+
* env passed to hook subprocesses and drop every `OTEL_*` var, so the dev
|
|
14
|
+
* launcher mirrors each setting under the Ory prefix to survive the strip.
|
|
15
|
+
*
|
|
16
|
+
* Standard name Ory-prefixed fallback
|
|
17
|
+
* ───────────────────────────────────── ─────────────────────────────────
|
|
18
|
+
* OTEL_EXPORTER_OTLP_ENDPOINT ORY_OTLP_ENDPOINT
|
|
19
|
+
* OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ORY_OTLP_TRACES_ENDPOINT
|
|
20
|
+
* OTEL_EXPORTER_OTLP_HEADERS ORY_OTLP_HEADERS
|
|
21
|
+
* OTEL_EXPORTER_OTLP_TRACES_HEADERS ORY_OTLP_TRACES_HEADERS
|
|
22
|
+
* OTEL_EXPORTER_OTLP_PROTOCOL ORY_OTLP_PROTOCOL
|
|
23
|
+
* OTEL_EXPORTER_OTLP_TRACES_PROTOCOL ORY_OTLP_TRACES_PROTOCOL
|
|
24
|
+
* OTEL_SERVICE_NAME ORY_OTLP_SERVICE_NAME
|
|
25
|
+
* OTEL_RESOURCE_ATTRIBUTES ORY_OTLP_RESOURCE_ATTRIBUTES
|
|
20
26
|
*
|
|
21
27
|
* Failures are swallowed (callers receive a resolved Promise) so trace
|
|
22
28
|
* export can never break the agent session. Transport errors surface
|
|
@@ -80,6 +86,12 @@ export declare function otlpExporterFromEnv(opts: {
|
|
|
80
86
|
onError?: (err: unknown) => void;
|
|
81
87
|
env?: NodeJS.ProcessEnv;
|
|
82
88
|
}): OtlpExporter | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* Read an OTel env var, falling back to its `ORY_OTLP_*`-prefixed alias when
|
|
91
|
+
* the standard name is unset. Trims whitespace and treats empty strings as
|
|
92
|
+
* absent so a propagated-but-blank var doesn't shadow the alias.
|
|
93
|
+
*/
|
|
94
|
+
export declare function readEnvWithAlias(env: NodeJS.ProcessEnv, standardName: string): string | undefined;
|
|
83
95
|
/**
|
|
84
96
|
* Parse the OTel `k=v,k2=v2` env-var format. Values may be URL-encoded.
|
|
85
97
|
*/
|
package/dist/otel/otlp.js
CHANGED
|
@@ -8,16 +8,22 @@
|
|
|
8
8
|
* is converted to a `ReadableSpan` shape and handed to the SDK exporter,
|
|
9
9
|
* which serializes (JSON or protobuf) and POSTs to an OTLP HTTP endpoint.
|
|
10
10
|
*
|
|
11
|
-
* Standard OTel env vars consumed by `otlpExporterFromEnv()
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
11
|
+
* Standard OTel env vars consumed by `otlpExporterFromEnv()`. Each one has
|
|
12
|
+
* an `ORY_OTLP_*`-prefixed alias that is consulted as a fallback when the
|
|
13
|
+
* standard name is unset. Some harnesses (notably Claude Code) sanitize the
|
|
14
|
+
* env passed to hook subprocesses and drop every `OTEL_*` var, so the dev
|
|
15
|
+
* launcher mirrors each setting under the Ory prefix to survive the strip.
|
|
16
|
+
*
|
|
17
|
+
* Standard name Ory-prefixed fallback
|
|
18
|
+
* ───────────────────────────────────── ─────────────────────────────────
|
|
19
|
+
* OTEL_EXPORTER_OTLP_ENDPOINT ORY_OTLP_ENDPOINT
|
|
20
|
+
* OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ORY_OTLP_TRACES_ENDPOINT
|
|
21
|
+
* OTEL_EXPORTER_OTLP_HEADERS ORY_OTLP_HEADERS
|
|
22
|
+
* OTEL_EXPORTER_OTLP_TRACES_HEADERS ORY_OTLP_TRACES_HEADERS
|
|
23
|
+
* OTEL_EXPORTER_OTLP_PROTOCOL ORY_OTLP_PROTOCOL
|
|
24
|
+
* OTEL_EXPORTER_OTLP_TRACES_PROTOCOL ORY_OTLP_TRACES_PROTOCOL
|
|
25
|
+
* OTEL_SERVICE_NAME ORY_OTLP_SERVICE_NAME
|
|
26
|
+
* OTEL_RESOURCE_ATTRIBUTES ORY_OTLP_RESOURCE_ATTRIBUTES
|
|
21
27
|
*
|
|
22
28
|
* Failures are swallowed (callers receive a resolved Promise) so trace
|
|
23
29
|
* export can never break the agent session. Transport errors surface
|
|
@@ -26,6 +32,7 @@
|
|
|
26
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
33
|
exports.OtlpExporter = void 0;
|
|
28
34
|
exports.otlpExporterFromEnv = otlpExporterFromEnv;
|
|
35
|
+
exports.readEnvWithAlias = readEnvWithAlias;
|
|
29
36
|
exports.parseKeyValueList = parseKeyValueList;
|
|
30
37
|
exports.toReadableSpan = toReadableSpan;
|
|
31
38
|
const api_1 = require("@opentelemetry/api");
|
|
@@ -124,9 +131,10 @@ function otlpExporterFromEnv(opts) {
|
|
|
124
131
|
const protocol = resolveProtocol(env, opts.onError);
|
|
125
132
|
if (!protocol)
|
|
126
133
|
return undefined;
|
|
127
|
-
const headers = mergeHeaders(parseKeyValueList(env
|
|
134
|
+
const headers = mergeHeaders(parseKeyValueList(readEnvWithAlias(env, "OTEL_EXPORTER_OTLP_HEADERS")), parseKeyValueList(readEnvWithAlias(env, "OTEL_EXPORTER_OTLP_TRACES_HEADERS")));
|
|
128
135
|
const resource = {
|
|
129
|
-
"service.name": env
|
|
136
|
+
"service.name": readEnvWithAlias(env, "OTEL_SERVICE_NAME") ??
|
|
137
|
+
`ory-agent-plugin-${opts.harness}`,
|
|
130
138
|
"service.namespace": "ory.agent_plugins",
|
|
131
139
|
"ory.harness": opts.harness,
|
|
132
140
|
"process.pid": process.pid,
|
|
@@ -145,7 +153,7 @@ function otlpExporterFromEnv(opts) {
|
|
|
145
153
|
resource["ory.git.branch"] = opts.gitBranch;
|
|
146
154
|
if (opts.gitCommit)
|
|
147
155
|
resource["ory.git.commit"] = opts.gitCommit;
|
|
148
|
-
for (const [k, v] of Object.entries(parseKeyValueList(env
|
|
156
|
+
for (const [k, v] of Object.entries(parseKeyValueList(readEnvWithAlias(env, "OTEL_RESOURCE_ATTRIBUTES")))) {
|
|
149
157
|
resource[k] = v;
|
|
150
158
|
}
|
|
151
159
|
return new OtlpExporter({
|
|
@@ -157,22 +165,20 @@ function otlpExporterFromEnv(opts) {
|
|
|
157
165
|
});
|
|
158
166
|
}
|
|
159
167
|
function resolveEndpoint(env) {
|
|
160
|
-
|
|
168
|
+
// ORY_OTLP_*-prefixed aliases survive harnesses (e.g. Claude Code) that
|
|
169
|
+
// sanitize the env passed to hook subprocesses and drop OTEL_*-prefixed
|
|
170
|
+
// vars. The standard OTel name still wins when present.
|
|
171
|
+
const tracesUrl = readEnvWithAlias(env, "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT");
|
|
161
172
|
if (tracesUrl)
|
|
162
173
|
return tracesUrl;
|
|
163
|
-
|
|
164
|
-
// Some harnesses (e.g. Claude Code) filter the env they pass to hook
|
|
165
|
-
// subprocesses and drop OTEL_*-prefixed vars, but allow ORY_*-prefixed
|
|
166
|
-
// ones through. The alias lets the launcher hand the OTLP endpoint to
|
|
167
|
-
// the hook process without depending on OTEL_* propagation.
|
|
168
|
-
const base = env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim() || env.ORY_OTLP_ENDPOINT?.trim();
|
|
174
|
+
const base = readEnvWithAlias(env, "OTEL_EXPORTER_OTLP_ENDPOINT");
|
|
169
175
|
if (!base)
|
|
170
176
|
return undefined;
|
|
171
177
|
return base.replace(/\/+$/, "") + TRACES_PATH;
|
|
172
178
|
}
|
|
173
179
|
function resolveProtocol(env, onError) {
|
|
174
|
-
const raw = (env
|
|
175
|
-
env
|
|
180
|
+
const raw = (readEnvWithAlias(env, "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL") ??
|
|
181
|
+
readEnvWithAlias(env, "OTEL_EXPORTER_OTLP_PROTOCOL") ??
|
|
176
182
|
"http/protobuf")
|
|
177
183
|
.trim()
|
|
178
184
|
.toLowerCase();
|
|
@@ -181,6 +187,37 @@ function resolveProtocol(env, onError) {
|
|
|
181
187
|
onError?.(new Error(`OTEL_EXPORTER_OTLP_PROTOCOL=${raw} not supported (only http/protobuf and http/json). Skipping OTel export.`));
|
|
182
188
|
return undefined;
|
|
183
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Standard OTel env var name → Ory-prefixed fallback. Standard wins when set;
|
|
192
|
+
* the alias is consulted only when the standard name is empty/missing. The
|
|
193
|
+
* historical `ORY_OTLP_ENDPOINT` alias for `OTEL_EXPORTER_OTLP_ENDPOINT` is
|
|
194
|
+
* preserved verbatim for compatibility with existing dev-launcher envs.
|
|
195
|
+
*/
|
|
196
|
+
const ORY_ENV_ALIAS = {
|
|
197
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: "ORY_OTLP_ENDPOINT",
|
|
198
|
+
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "ORY_OTLP_TRACES_ENDPOINT",
|
|
199
|
+
OTEL_EXPORTER_OTLP_HEADERS: "ORY_OTLP_HEADERS",
|
|
200
|
+
OTEL_EXPORTER_OTLP_TRACES_HEADERS: "ORY_OTLP_TRACES_HEADERS",
|
|
201
|
+
OTEL_EXPORTER_OTLP_PROTOCOL: "ORY_OTLP_PROTOCOL",
|
|
202
|
+
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: "ORY_OTLP_TRACES_PROTOCOL",
|
|
203
|
+
OTEL_SERVICE_NAME: "ORY_OTLP_SERVICE_NAME",
|
|
204
|
+
OTEL_RESOURCE_ATTRIBUTES: "ORY_OTLP_RESOURCE_ATTRIBUTES",
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Read an OTel env var, falling back to its `ORY_OTLP_*`-prefixed alias when
|
|
208
|
+
* the standard name is unset. Trims whitespace and treats empty strings as
|
|
209
|
+
* absent so a propagated-but-blank var doesn't shadow the alias.
|
|
210
|
+
*/
|
|
211
|
+
function readEnvWithAlias(env, standardName) {
|
|
212
|
+
const primary = env[standardName]?.trim();
|
|
213
|
+
if (primary)
|
|
214
|
+
return primary;
|
|
215
|
+
const aliasName = ORY_ENV_ALIAS[standardName];
|
|
216
|
+
if (!aliasName)
|
|
217
|
+
return undefined;
|
|
218
|
+
const alias = env[aliasName]?.trim();
|
|
219
|
+
return alias || undefined;
|
|
220
|
+
}
|
|
184
221
|
// ─── Env parsing helpers ───────────────────────────────────────────
|
|
185
222
|
/**
|
|
186
223
|
* Parse the OTel `k=v,k2=v2` env-var format. Values may be URL-encoded.
|
package/dist/permissions.d.ts
CHANGED
|
@@ -117,6 +117,57 @@ export interface ApplyPermissionModeContext {
|
|
|
117
117
|
* proceed despite the deny.
|
|
118
118
|
*/
|
|
119
119
|
export declare function applyPermissionMode(client: OryAgentClient, allowed: boolean, context?: ApplyPermissionModeContext): ModeDecision;
|
|
120
|
+
/**
|
|
121
|
+
* Outcome of {@link gateToolCall}. Either the tool is a user-interaction
|
|
122
|
+
* primitive (`AskUserQuestion`, `ExitPlanMode`, `TodoWrite`, …) and we
|
|
123
|
+
* pass through with a single audit span, or it's a real tool execution
|
|
124
|
+
* and the caller gets the standard {@link PermissionDecision}.
|
|
125
|
+
*/
|
|
126
|
+
export type ToolGateOutcome = {
|
|
127
|
+
kind: "interactive";
|
|
128
|
+
/** Attributes to attach to the caller's pass-through trace, if any. */
|
|
129
|
+
spanAttributes: {
|
|
130
|
+
interactive: true;
|
|
131
|
+
toolName: string;
|
|
132
|
+
};
|
|
133
|
+
} | PermissionDecision;
|
|
134
|
+
export interface GateToolCallArgs {
|
|
135
|
+
/** Harness name (`claude-code`, `codex`, …). Used to look up the interactive-tool catalog. */
|
|
136
|
+
harness: string;
|
|
137
|
+
/** The tool the agent is invoking. */
|
|
138
|
+
toolName: string;
|
|
139
|
+
/** Permission check to run when the tool is a real execution. */
|
|
140
|
+
check: PermissionCheck;
|
|
141
|
+
/**
|
|
142
|
+
* Attributes merged into the permission span (real path) and the
|
|
143
|
+
* `user.interaction` span (interactive path).
|
|
144
|
+
*/
|
|
145
|
+
spanAttributes?: Record<string, unknown>;
|
|
146
|
+
/** Override the resolved {@link PermissionMode}. Tests use this. */
|
|
147
|
+
modeOverride?: PermissionMode;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Single entry point for the pre-tool-use gate. Splits the harness's
|
|
151
|
+
* incoming "tool" into two semantic categories:
|
|
152
|
+
*
|
|
153
|
+
* - **Interactive** — the tool surfaces UI to the user
|
|
154
|
+
* (`AskUserQuestion`, `ExitPlanMode`, `TodoWrite`, plus anything
|
|
155
|
+
* listed in `ORY_INTERACTIVE_TOOLS`). The plugin must not gate these
|
|
156
|
+
* through Ory: the user is the decision-maker, and blocking them in
|
|
157
|
+
* enforce mode (or logging a misleading observe-deny) hides the very
|
|
158
|
+
* prompt the user needs to see. We record one `user.interaction`
|
|
159
|
+
* audit span and return — no permission check, no `tool.invoke`,
|
|
160
|
+
* no `tool.block`.
|
|
161
|
+
*
|
|
162
|
+
* - **Execution** — every other tool. Delegates to
|
|
163
|
+
* {@link checkAndDecide} so observe/enforce/fail-open behavior is
|
|
164
|
+
* identical to the legacy code path.
|
|
165
|
+
*
|
|
166
|
+
* The caller branches on `outcome.kind`. The four execution kinds
|
|
167
|
+
* (`allow`, `deny`, `observe`, `fail_open`) keep their existing
|
|
168
|
+
* semantics; the new `interactive` kind means "do nothing else."
|
|
169
|
+
*/
|
|
170
|
+
export declare function gateToolCall(client: OryAgentClient, args: GateToolCallArgs): Promise<ToolGateOutcome>;
|
|
120
171
|
/**
|
|
121
172
|
* Run a permission check and resolve the configured mode against the
|
|
122
173
|
* result. Never throws — fail-open scenarios are surfaced as a typed
|
package/dist/permissions.js
CHANGED
|
@@ -23,8 +23,10 @@
|
|
|
23
23
|
*/
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
25
|
exports.applyPermissionMode = applyPermissionMode;
|
|
26
|
+
exports.gateToolCall = gateToolCall;
|
|
26
27
|
exports.checkAndDecide = checkAndDecide;
|
|
27
28
|
const config_js_1 = require("./config.js");
|
|
29
|
+
const tool_catalog_js_1 = require("./tool-catalog.js");
|
|
28
30
|
function formatSubjectSet(set) {
|
|
29
31
|
if (!set)
|
|
30
32
|
return undefined;
|
|
@@ -75,6 +77,52 @@ function applyPermissionMode(client, allowed, context = {}) {
|
|
|
75
77
|
}
|
|
76
78
|
return { kind: "deny", mode: "enforce", spanAttributes };
|
|
77
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Single entry point for the pre-tool-use gate. Splits the harness's
|
|
82
|
+
* incoming "tool" into two semantic categories:
|
|
83
|
+
*
|
|
84
|
+
* - **Interactive** — the tool surfaces UI to the user
|
|
85
|
+
* (`AskUserQuestion`, `ExitPlanMode`, `TodoWrite`, plus anything
|
|
86
|
+
* listed in `ORY_INTERACTIVE_TOOLS`). The plugin must not gate these
|
|
87
|
+
* through Ory: the user is the decision-maker, and blocking them in
|
|
88
|
+
* enforce mode (or logging a misleading observe-deny) hides the very
|
|
89
|
+
* prompt the user needs to see. We record one `user.interaction`
|
|
90
|
+
* audit span and return — no permission check, no `tool.invoke`,
|
|
91
|
+
* no `tool.block`.
|
|
92
|
+
*
|
|
93
|
+
* - **Execution** — every other tool. Delegates to
|
|
94
|
+
* {@link checkAndDecide} so observe/enforce/fail-open behavior is
|
|
95
|
+
* identical to the legacy code path.
|
|
96
|
+
*
|
|
97
|
+
* The caller branches on `outcome.kind`. The four execution kinds
|
|
98
|
+
* (`allow`, `deny`, `observe`, `fail_open`) keep their existing
|
|
99
|
+
* semantics; the new `interactive` kind means "do nothing else."
|
|
100
|
+
*/
|
|
101
|
+
async function gateToolCall(client, args) {
|
|
102
|
+
if ((0, tool_catalog_js_1.isInteractiveTool)(args.harness, args.toolName)) {
|
|
103
|
+
client.logger.debug("tool.interactive", {
|
|
104
|
+
harness: args.harness,
|
|
105
|
+
toolName: args.toolName,
|
|
106
|
+
note: "user-interaction primitive — skipping permission check",
|
|
107
|
+
});
|
|
108
|
+
client.tracer.record("user.interaction", "ok", {
|
|
109
|
+
attributes: {
|
|
110
|
+
harness: args.harness,
|
|
111
|
+
toolName: args.toolName,
|
|
112
|
+
kind: "tool",
|
|
113
|
+
...args.spanAttributes,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
kind: "interactive",
|
|
118
|
+
spanAttributes: { interactive: true, toolName: args.toolName },
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return checkAndDecide(client, args.check, {
|
|
122
|
+
spanAttributes: args.spanAttributes,
|
|
123
|
+
modeOverride: args.modeOverride,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
78
126
|
/**
|
|
79
127
|
* Run a permission check and resolve the configured mode against the
|
|
80
128
|
* result. Never throws — fail-open scenarios are surfaced as a typed
|
package/dist/tool-catalog.d.ts
CHANGED
|
@@ -36,3 +36,34 @@ export declare function getToolCatalog(harness: string): readonly string[];
|
|
|
36
36
|
* broad access so the same launcher can run every harness end-to-end.
|
|
37
37
|
*/
|
|
38
38
|
export declare const ALL_TOOLS: readonly string[];
|
|
39
|
+
/**
|
|
40
|
+
* Per-harness tool names whose semantics are "ask / inform the user"
|
|
41
|
+
* rather than "act on an external system". These reach the pre-tool-use
|
|
42
|
+
* hook the same way real tools do (e.g. Claude Code's `AskUserQuestion`
|
|
43
|
+
* arrives via `PreToolUse`), but they aren't tool *executions* — gating
|
|
44
|
+
* them through Ory would either block the user from being asked or, in
|
|
45
|
+
* observe mode, log a misleading `permission.observe_deny` for an event
|
|
46
|
+
* the user is about to handle directly.
|
|
47
|
+
*
|
|
48
|
+
* Plugins consult this list in their pre-tool gate and short-circuit to
|
|
49
|
+
* a single `user.interaction` audit span when a match is found. See
|
|
50
|
+
* `gateToolCall` in `permissions.ts`.
|
|
51
|
+
*
|
|
52
|
+
* The list is best-effort and additive: operators can extend it at
|
|
53
|
+
* runtime via the `ORY_INTERACTIVE_TOOLS` env var (comma-separated names
|
|
54
|
+
* applied to every harness).
|
|
55
|
+
*/
|
|
56
|
+
export declare const INTERACTIVE_TOOL_CATALOG: Record<string, readonly string[]>;
|
|
57
|
+
/**
|
|
58
|
+
* Names from {@link INTERACTIVE_TOOL_CATALOG} for the given harness,
|
|
59
|
+
* merged with any operator-supplied names from `ORY_INTERACTIVE_TOOLS`.
|
|
60
|
+
* Unknown harnesses still respect the env-var extension.
|
|
61
|
+
*/
|
|
62
|
+
export declare function getInteractiveToolCatalog(harness: string): readonly string[];
|
|
63
|
+
/**
|
|
64
|
+
* Does the named tool, for the given harness, represent a user-facing
|
|
65
|
+
* interaction (ask / inform / confirm) rather than an external-system
|
|
66
|
+
* tool execution? Treats unknown harnesses the same as known ones — the
|
|
67
|
+
* env-var extension still applies.
|
|
68
|
+
*/
|
|
69
|
+
export declare function isInteractiveTool(harness: string, toolName: string): boolean;
|
package/dist/tool-catalog.js
CHANGED
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
* catalog update is safe.
|
|
14
14
|
*/
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.ALL_TOOLS = exports.KNOWN_HARNESSES = exports.HARNESS_TOOL_CATALOG = void 0;
|
|
16
|
+
exports.INTERACTIVE_TOOL_CATALOG = exports.ALL_TOOLS = exports.KNOWN_HARNESSES = exports.HARNESS_TOOL_CATALOG = void 0;
|
|
17
17
|
exports.getToolCatalog = getToolCatalog;
|
|
18
|
+
exports.getInteractiveToolCatalog = getInteractiveToolCatalog;
|
|
19
|
+
exports.isInteractiveTool = isInteractiveTool;
|
|
18
20
|
/**
|
|
19
21
|
* Built-in tool names known to ship with each supported harness.
|
|
20
22
|
*/
|
|
@@ -67,3 +69,58 @@ function getToolCatalog(harness) {
|
|
|
67
69
|
* broad access so the same launcher can run every harness end-to-end.
|
|
68
70
|
*/
|
|
69
71
|
exports.ALL_TOOLS = Array.from(new Set(Object.values(exports.HARNESS_TOOL_CATALOG).flat()));
|
|
72
|
+
/**
|
|
73
|
+
* Per-harness tool names whose semantics are "ask / inform the user"
|
|
74
|
+
* rather than "act on an external system". These reach the pre-tool-use
|
|
75
|
+
* hook the same way real tools do (e.g. Claude Code's `AskUserQuestion`
|
|
76
|
+
* arrives via `PreToolUse`), but they aren't tool *executions* — gating
|
|
77
|
+
* them through Ory would either block the user from being asked or, in
|
|
78
|
+
* observe mode, log a misleading `permission.observe_deny` for an event
|
|
79
|
+
* the user is about to handle directly.
|
|
80
|
+
*
|
|
81
|
+
* Plugins consult this list in their pre-tool gate and short-circuit to
|
|
82
|
+
* a single `user.interaction` audit span when a match is found. See
|
|
83
|
+
* `gateToolCall` in `permissions.ts`.
|
|
84
|
+
*
|
|
85
|
+
* The list is best-effort and additive: operators can extend it at
|
|
86
|
+
* runtime via the `ORY_INTERACTIVE_TOOLS` env var (comma-separated names
|
|
87
|
+
* applied to every harness).
|
|
88
|
+
*/
|
|
89
|
+
exports.INTERACTIVE_TOOL_CATALOG = {
|
|
90
|
+
"claude-code": ["AskUserQuestion", "ExitPlanMode", "TodoWrite"],
|
|
91
|
+
codex: [],
|
|
92
|
+
"gemini-cli": [],
|
|
93
|
+
openclaw: [],
|
|
94
|
+
opencode: [],
|
|
95
|
+
};
|
|
96
|
+
function parseEnvInteractiveTools() {
|
|
97
|
+
const raw = process.env.ORY_INTERACTIVE_TOOLS;
|
|
98
|
+
if (!raw)
|
|
99
|
+
return [];
|
|
100
|
+
return raw
|
|
101
|
+
.split(",")
|
|
102
|
+
.map((name) => name.trim())
|
|
103
|
+
.filter((name) => name.length > 0);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Names from {@link INTERACTIVE_TOOL_CATALOG} for the given harness,
|
|
107
|
+
* merged with any operator-supplied names from `ORY_INTERACTIVE_TOOLS`.
|
|
108
|
+
* Unknown harnesses still respect the env-var extension.
|
|
109
|
+
*/
|
|
110
|
+
function getInteractiveToolCatalog(harness) {
|
|
111
|
+
const builtIn = exports.INTERACTIVE_TOOL_CATALOG[harness] ?? [];
|
|
112
|
+
const envExtra = parseEnvInteractiveTools();
|
|
113
|
+
if (envExtra.length === 0)
|
|
114
|
+
return builtIn;
|
|
115
|
+
// Stable order: built-ins first, then env additions, deduplicated.
|
|
116
|
+
return Array.from(new Set([...builtIn, ...envExtra]));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Does the named tool, for the given harness, represent a user-facing
|
|
120
|
+
* interaction (ask / inform / confirm) rather than an external-system
|
|
121
|
+
* tool execution? Treats unknown harnesses the same as known ones — the
|
|
122
|
+
* env-var extension still applies.
|
|
123
|
+
*/
|
|
124
|
+
function isInteractiveTool(harness, toolName) {
|
|
125
|
+
return getInteractiveToolCatalog(harness).includes(toolName);
|
|
126
|
+
}
|
package/dist/tracer.d.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { EventEmitter } from "node:events";
|
|
20
20
|
import type { SpanExporter } from "./otel/exporter.js";
|
|
21
|
-
export type TraceEvent = "session.start" | "session.end" | "session.verify" | "user.auth" | "user.prompt" | "agent.auth" | "oauth2.introspect" | "oauth2.login" | "oauth2.refresh" | "permission.check" | "permission.batch_check" | "permission.observe_deny" | "tool.invoke" | "tool.complete" | "tool.block" | "tool.fail" | "turn.stop" | "subagent.start" | "subagent.stop" | "notification" | "compaction" | "relationship.create" | "relationship.delete" | "relationship.patch" | "hook.receive" | "hook.passthrough" | "config.resolve";
|
|
21
|
+
export type TraceEvent = "session.start" | "session.end" | "session.verify" | "user.auth" | "user.prompt" | "user.interaction" | "agent.auth" | "oauth2.introspect" | "oauth2.login" | "oauth2.refresh" | "permission.check" | "permission.batch_check" | "permission.observe_deny" | "tool.invoke" | "tool.complete" | "tool.block" | "tool.fail" | "turn.stop" | "subagent.start" | "subagent.stop" | "notification" | "compaction" | "relationship.create" | "relationship.delete" | "relationship.patch" | "hook.receive" | "hook.passthrough" | "config.resolve";
|
|
22
22
|
export type SpanStatus = "ok" | "error" | "denied" | "skipped";
|
|
23
23
|
export interface TraceSpan {
|
|
24
24
|
traceId: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ory/argus",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Ory Argus: the core API for building authentication, authorization, and audit into AI agent harness plugins, extensions, and custom integrations",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://ory.com",
|