@ory/argus 0.9.0 → 0.10.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 +3 -3
- package/assets/skills/auth-setup/SKILL.md +209 -49
- package/assets/skills/login-flow/SKILL.md +192 -17
- package/assets/skills/social-login/SKILL.md +9 -0
- package/dist/adapters.d.ts +104 -0
- package/dist/adapters.js +201 -0
- package/dist/agent-auth.js +13 -0
- package/dist/contract-suite.d.ts +87 -0
- package/dist/contract-suite.js +239 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +8 -1
- package/dist/lifecycle.js +129 -0
- package/dist/local/configs.d.ts +2 -2
- package/dist/local/configs.js +10 -1
- package/dist/local/manager.d.ts +6 -4
- package/dist/local/manager.js +91 -16
- package/dist/permissions.d.ts +15 -3
- package/dist/permissions.js +17 -2
- package/dist/testing.d.ts +214 -0
- package/dist/testing.js +372 -0
- package/dist/tool-catalog.d.ts +6 -0
- package/dist/tool-catalog.js +54 -0
- package/package.json +1 -1
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.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;
|
|
5
|
+
exports.wrapTool = exports.registerSubagent = exports.complete = exports.gate = exports.sessionStart = exports.resolveNamespace = 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");
|
|
@@ -164,3 +164,10 @@ var index_js_3 = require("./otel/index.js");
|
|
|
164
164
|
Object.defineProperty(exports, "OtlpExporter", { enumerable: true, get: function () { return index_js_3.OtlpExporter; } });
|
|
165
165
|
Object.defineProperty(exports, "otlpExporterFromEnv", { enumerable: true, get: function () { return index_js_3.otlpExporterFromEnv; } });
|
|
166
166
|
Object.defineProperty(exports, "parseKeyValueList", { enumerable: true, get: function () { return index_js_3.parseKeyValueList; } });
|
|
167
|
+
var adapters_js_1 = require("./adapters.js");
|
|
168
|
+
Object.defineProperty(exports, "resolveNamespace", { enumerable: true, get: function () { return adapters_js_1.resolveNamespace; } });
|
|
169
|
+
Object.defineProperty(exports, "sessionStart", { enumerable: true, get: function () { return adapters_js_1.sessionStart; } });
|
|
170
|
+
Object.defineProperty(exports, "gate", { enumerable: true, get: function () { return adapters_js_1.gate; } });
|
|
171
|
+
Object.defineProperty(exports, "complete", { enumerable: true, get: function () { return adapters_js_1.complete; } });
|
|
172
|
+
Object.defineProperty(exports, "registerSubagent", { enumerable: true, get: function () { return adapters_js_1.registerSubagent; } });
|
|
173
|
+
Object.defineProperty(exports, "wrapTool", { enumerable: true, get: function () { return adapters_js_1.wrapTool; } });
|
package/dist/lifecycle.js
CHANGED
|
@@ -44,6 +44,7 @@ exports.isToolExecutionPhase = isToolExecutionPhase;
|
|
|
44
44
|
* `passthrough` via {@link classifyLifecycle}.
|
|
45
45
|
*/
|
|
46
46
|
exports.HARNESS_LIFECYCLE_MAP = {
|
|
47
|
+
// ── Coding-agent harnesses ──
|
|
47
48
|
"claude-code": {
|
|
48
49
|
SessionStart: "session.start",
|
|
49
50
|
SessionEnd: "session.stop",
|
|
@@ -88,6 +89,134 @@ exports.HARNESS_LIFECYCLE_MAP = {
|
|
|
88
89
|
"tool.execute.before": "tool.before",
|
|
89
90
|
"tool.execute.after": "tool.after",
|
|
90
91
|
},
|
|
92
|
+
// Continue's `Stop` brackets a turn (not the session) and `ConfigChange`
|
|
93
|
+
// carries no lifecycle meaning — both fall through to passthrough and are
|
|
94
|
+
// traced as audit spans by the handler.
|
|
95
|
+
continue: {
|
|
96
|
+
SessionStart: "session.start",
|
|
97
|
+
SessionEnd: "session.stop",
|
|
98
|
+
PreToolUse: "tool.before",
|
|
99
|
+
PostToolUse: "tool.after",
|
|
100
|
+
PostToolUseFailure: "tool.failure",
|
|
101
|
+
PermissionRequest: "permission.ask",
|
|
102
|
+
UserPromptSubmit: "user.prompt",
|
|
103
|
+
SubagentStart: "subagent.start",
|
|
104
|
+
SubagentStop: "subagent.stop",
|
|
105
|
+
PreCompact: "compaction",
|
|
106
|
+
},
|
|
107
|
+
// Goose's file/shell notification hooks surface activity to the user and
|
|
108
|
+
// are trace-only — user-facing, never gated.
|
|
109
|
+
goose: {
|
|
110
|
+
SessionStart: "session.start",
|
|
111
|
+
SessionEnd: "session.stop",
|
|
112
|
+
Stop: "session.stop",
|
|
113
|
+
PreToolUse: "tool.before",
|
|
114
|
+
PostToolUse: "tool.after",
|
|
115
|
+
PostToolUseFailure: "tool.failure",
|
|
116
|
+
UserPromptSubmit: "user.prompt",
|
|
117
|
+
BeforeReadFile: "user.interaction",
|
|
118
|
+
AfterFileEdit: "user.interaction",
|
|
119
|
+
BeforeShellExecution: "user.interaction",
|
|
120
|
+
AfterShellExecution: "user.interaction",
|
|
121
|
+
},
|
|
122
|
+
cline: {
|
|
123
|
+
agent_start: "session.start",
|
|
124
|
+
agent_resume: "session.start",
|
|
125
|
+
agent_abort: "session.stop",
|
|
126
|
+
agent_end: "session.stop",
|
|
127
|
+
agent_error: "session.stop",
|
|
128
|
+
session_shutdown: "session.stop",
|
|
129
|
+
tool_call: "tool.before",
|
|
130
|
+
tool_result: "tool.after",
|
|
131
|
+
prompt_submit: "user.prompt",
|
|
132
|
+
pre_compact: "compaction",
|
|
133
|
+
},
|
|
134
|
+
// Amp is hybrid: session.start / tool.result are in-process plugin events;
|
|
135
|
+
// tool.call is gated by the subprocess permission delegate. agent.start /
|
|
136
|
+
// agent.end bracket a turn and fall through to passthrough.
|
|
137
|
+
amp: {
|
|
138
|
+
"session.start": "session.start",
|
|
139
|
+
"tool.call": "tool.before",
|
|
140
|
+
"tool.result": "tool.after",
|
|
141
|
+
},
|
|
142
|
+
// Pi runs session start in the extension factory body (awaited before
|
|
143
|
+
// startup); `session_start` is the harness's equivalent event name.
|
|
144
|
+
pi: {
|
|
145
|
+
session_start: "session.start",
|
|
146
|
+
tool_call: "tool.before",
|
|
147
|
+
tool_result: "tool.after",
|
|
148
|
+
},
|
|
149
|
+
// Google Antigravity (`agy` CLI / IDE). Subprocess hooks with
|
|
150
|
+
// Claude-Code-style snake_case payloads. PreInvocation/PostInvocation
|
|
151
|
+
// bracket each agent turn (not user prompts), so they fall through to
|
|
152
|
+
// passthrough and are traced as audit spans by the handler.
|
|
153
|
+
antigravity: {
|
|
154
|
+
SessionStart: "session.start",
|
|
155
|
+
SessionEnd: "session.stop",
|
|
156
|
+
Stop: "session.stop",
|
|
157
|
+
PreToolUse: "tool.before",
|
|
158
|
+
PostToolUse: "tool.after",
|
|
159
|
+
Notification: "user.interaction",
|
|
160
|
+
},
|
|
161
|
+
// ── Agent SDK integrations ── (parity with `ory_argus.lifecycle`; each
|
|
162
|
+
// entry names the canonical extension point the integration hooks)
|
|
163
|
+
langchain: {
|
|
164
|
+
"wrap_tool_call.before": "tool.before",
|
|
165
|
+
"wrap_tool_call.after": "tool.after",
|
|
166
|
+
on_tool_start: "tool.before",
|
|
167
|
+
on_tool_end: "tool.after",
|
|
168
|
+
on_tool_error: "tool.failure",
|
|
169
|
+
},
|
|
170
|
+
"openai-agents": {
|
|
171
|
+
tool_input_guardrail: "tool.before",
|
|
172
|
+
tool_output_guardrail: "tool.after",
|
|
173
|
+
on_tool_start: "tool.before",
|
|
174
|
+
on_tool_end: "tool.after",
|
|
175
|
+
on_handoff: "subagent.start",
|
|
176
|
+
},
|
|
177
|
+
crewai: {
|
|
178
|
+
ToolUsageStartedEvent: "tool.before",
|
|
179
|
+
ToolUsageFinishedEvent: "tool.after",
|
|
180
|
+
ToolUsageErrorEvent: "tool.failure",
|
|
181
|
+
},
|
|
182
|
+
llamaindex: {
|
|
183
|
+
AgentToolCallEvent: "tool.before",
|
|
184
|
+
function_call: "tool.before",
|
|
185
|
+
function_call_result: "tool.after",
|
|
186
|
+
},
|
|
187
|
+
"pydantic-ai": {
|
|
188
|
+
"call_tool.before": "tool.before",
|
|
189
|
+
"call_tool.after": "tool.after",
|
|
190
|
+
},
|
|
191
|
+
"agent-framework": {
|
|
192
|
+
"function_middleware.before": "tool.before",
|
|
193
|
+
"function_middleware.after": "tool.after",
|
|
194
|
+
},
|
|
195
|
+
"vercel-ai": {
|
|
196
|
+
"execute.before": "tool.before",
|
|
197
|
+
"execute.after": "tool.after",
|
|
198
|
+
},
|
|
199
|
+
"claude-agent-sdk": {
|
|
200
|
+
PreToolUse: "tool.before",
|
|
201
|
+
PostToolUse: "tool.after",
|
|
202
|
+
},
|
|
203
|
+
"google-adk": {
|
|
204
|
+
before_tool_callback: "tool.before",
|
|
205
|
+
after_tool_callback: "tool.after",
|
|
206
|
+
transfer_to_agent: "subagent.start",
|
|
207
|
+
},
|
|
208
|
+
mastra: {
|
|
209
|
+
beforeToolCall: "tool.before",
|
|
210
|
+
afterToolCall: "tool.after",
|
|
211
|
+
},
|
|
212
|
+
strands: {
|
|
213
|
+
BeforeToolCallEvent: "tool.before",
|
|
214
|
+
AfterToolCallEvent: "tool.after",
|
|
215
|
+
},
|
|
216
|
+
"cloudflare-agents": {
|
|
217
|
+
"execute.before": "tool.before",
|
|
218
|
+
"execute.after": "tool.after",
|
|
219
|
+
},
|
|
91
220
|
};
|
|
92
221
|
/**
|
|
93
222
|
* Resolve a harness event name to its canonical {@link LifecyclePhase}.
|
package/dist/local/configs.d.ts
CHANGED
|
@@ -73,8 +73,8 @@ export declare const GATEWAY_PORT = 4000;
|
|
|
73
73
|
export declare const GATEWAY_URL = "http://localhost:4000";
|
|
74
74
|
export declare const CONSOLE_PORT = 4100;
|
|
75
75
|
export declare const CONSOLE_URL = "http://localhost:4100";
|
|
76
|
-
export declare const LOGIN_UI_PORT =
|
|
77
|
-
export declare const LOGIN_UI_URL = "http://localhost:
|
|
76
|
+
export declare const LOGIN_UI_PORT = 4455;
|
|
77
|
+
export declare const LOGIN_UI_URL = "http://localhost:4455";
|
|
78
78
|
export declare const KRATOS_PUBLIC_PORT = 4433;
|
|
79
79
|
export declare const KRATOS_ADMIN_PORT = 4434;
|
|
80
80
|
export declare const KETO_READ_PORT = 4466;
|
package/dist/local/configs.js
CHANGED
|
@@ -189,6 +189,14 @@ function nginxGatewayService() {
|
|
|
189
189
|
* `COOKIE_SECRET` and `CSRF_COOKIE_SECRET` are required by the image —
|
|
190
190
|
* the container exits at startup if they're missing. Both must be at
|
|
191
191
|
* least 32 chars. Fixed dev-only values so launches are deterministic.
|
|
192
|
+
*
|
|
193
|
+
* `DANGEROUSLY_DISABLE_SECURE_CSRF_COOKIES=true` is required for local
|
|
194
|
+
* dev (HTTP, not HTTPS). Without it the csrf-csrf library sets `Secure`
|
|
195
|
+
* cookies, which the browser silently drops over plain HTTP. This causes
|
|
196
|
+
* every self-service flow (login, registration, settings) to fail with a
|
|
197
|
+
* 403 CSRF error, and the registration handler's redirectOnSoftError
|
|
198
|
+
* enters an infinite redirect loop — pegging CPU and making the UI
|
|
199
|
+
* unusable. Only safe for loopback-local dev.
|
|
192
200
|
*/
|
|
193
201
|
function loginUiService() {
|
|
194
202
|
return ` login-ui:
|
|
@@ -207,6 +215,7 @@ function loginUiService() {
|
|
|
207
215
|
COOKIE_SECRET: "ory-agent-plugins-local-cookie-secret-32+"
|
|
208
216
|
CSRF_COOKIE_NAME: "ory_agent_plugins_local_csrf"
|
|
209
217
|
CSRF_COOKIE_SECRET: "ory-agent-plugins-local-csrf-secret-32+!"
|
|
218
|
+
DANGEROUSLY_DISABLE_SECURE_CSRF_COOKIES: "true"
|
|
210
219
|
depends_on:
|
|
211
220
|
- kratos
|
|
212
221
|
- hydra
|
|
@@ -616,7 +625,7 @@ exports.GATEWAY_PORT = 4000;
|
|
|
616
625
|
exports.GATEWAY_URL = `http://localhost:${exports.GATEWAY_PORT}`;
|
|
617
626
|
exports.CONSOLE_PORT = 4100;
|
|
618
627
|
exports.CONSOLE_URL = `http://localhost:${exports.CONSOLE_PORT}`;
|
|
619
|
-
exports.LOGIN_UI_PORT =
|
|
628
|
+
exports.LOGIN_UI_PORT = 4455;
|
|
620
629
|
exports.LOGIN_UI_URL = `http://localhost:${exports.LOGIN_UI_PORT}`;
|
|
621
630
|
exports.KRATOS_PUBLIC_PORT = 4433;
|
|
622
631
|
exports.KRATOS_ADMIN_PORT = 4434;
|
package/dist/local/manager.d.ts
CHANGED
|
@@ -24,10 +24,12 @@ export interface EnsureLocalOryStackResult {
|
|
|
24
24
|
* Bring up the local Ory stack and wait for the gateway to be healthy.
|
|
25
25
|
*
|
|
26
26
|
* Idempotent and fail-graceful: returns a typed result instead of exiting.
|
|
27
|
-
* If the gateway is already healthy the
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
27
|
+
* If the gateway is already healthy AND the running stack's config matches
|
|
28
|
+
* what the current code generates (verified by a config fingerprint), the
|
|
29
|
+
* function short-circuits without touching Docker; otherwise it regenerates
|
|
30
|
+
* the config and runs `docker compose up -d --wait` to reconcile. When
|
|
31
|
+
* `quiet` is set, progress is logged to stderr only (suitable for the dev
|
|
32
|
+
* launcher); otherwise it prints to stdout in the format `localUp` historically used.
|
|
31
33
|
*/
|
|
32
34
|
export declare function ensureLocalOryStack(opts?: {
|
|
33
35
|
quiet?: boolean;
|
package/dist/local/manager.js
CHANGED
|
@@ -57,6 +57,7 @@ exports.localEnv = localEnv;
|
|
|
57
57
|
exports.localConfigure = localConfigure;
|
|
58
58
|
const fs = __importStar(require("node:fs"));
|
|
59
59
|
const path = __importStar(require("node:path"));
|
|
60
|
+
const crypto = __importStar(require("node:crypto"));
|
|
60
61
|
const node_child_process_1 = require("node:child_process");
|
|
61
62
|
const configs_js_1 = require("./configs.js");
|
|
62
63
|
const health_js_1 = require("./health.js");
|
|
@@ -196,28 +197,86 @@ function dumpCapturedOutput(label, result) {
|
|
|
196
197
|
process.stderr.write(`── end ${label} output ──\n\n`);
|
|
197
198
|
}
|
|
198
199
|
// ─── Write config files ────────────────────────────────────────────
|
|
200
|
+
/**
|
|
201
|
+
* Render the full set of local-stack config files as `{ relPath, content }`
|
|
202
|
+
* pairs. Pure function of the code, `projectRoot`, and `consoleCfg` — no I/O.
|
|
203
|
+
* `writeConfigs` writes these and `configFingerprint` hashes them, so the
|
|
204
|
+
* on-disk stack and its change-detection signal never drift.
|
|
205
|
+
*/
|
|
206
|
+
function renderConfigs(projectRoot, consoleCfg) {
|
|
207
|
+
return [
|
|
208
|
+
{
|
|
209
|
+
relPath: "docker-compose.yml",
|
|
210
|
+
content: (0, configs_js_1.dockerComposeYaml)(projectRoot, consoleCfg),
|
|
211
|
+
},
|
|
212
|
+
{ relPath: "kratos/kratos.yml", content: (0, configs_js_1.kratosConfigYaml)() },
|
|
213
|
+
{
|
|
214
|
+
relPath: "kratos/identity-schema.json",
|
|
215
|
+
content: (0, configs_js_1.kratosIdentitySchema)(),
|
|
216
|
+
},
|
|
217
|
+
{ relPath: "keto/keto.yml", content: (0, configs_js_1.ketoConfigYaml)() },
|
|
218
|
+
{ relPath: "hydra/hydra.yml", content: (0, configs_js_1.hydraConfigYaml)() },
|
|
219
|
+
// nginx config is always emitted — the gateway is no longer optional.
|
|
220
|
+
{ relPath: "nginx/nginx.conf", content: (0, configs_js_1.nginxConf)() },
|
|
221
|
+
];
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Stable content hash of the rendered config set. Used to decide whether an
|
|
225
|
+
* already-running stack still matches the config the current code would
|
|
226
|
+
* generate; a change (e.g. the login-UI port, a service env var) invalidates
|
|
227
|
+
* the fast-path short-circuit so the stack is reconciled instead of reused.
|
|
228
|
+
*/
|
|
229
|
+
function configFingerprint(projectRoot, consoleCfg) {
|
|
230
|
+
const hash = crypto.createHash("sha256");
|
|
231
|
+
for (const { relPath, content } of renderConfigs(projectRoot, consoleCfg)) {
|
|
232
|
+
hash.update(relPath);
|
|
233
|
+
hash.update("\0");
|
|
234
|
+
hash.update(content);
|
|
235
|
+
hash.update("\0");
|
|
236
|
+
}
|
|
237
|
+
return hash.digest("hex");
|
|
238
|
+
}
|
|
239
|
+
const CONFIG_FINGERPRINT_FILE = ".config-fingerprint";
|
|
240
|
+
/** Read the fingerprint recorded after the last successful bring-up. */
|
|
241
|
+
function readConfigFingerprint(localDir) {
|
|
242
|
+
try {
|
|
243
|
+
return fs
|
|
244
|
+
.readFileSync(path.join(localDir, CONFIG_FINGERPRINT_FILE), "utf8")
|
|
245
|
+
.trim();
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/** Record the fingerprint of the config the running stack was brought up with. */
|
|
252
|
+
function writeConfigFingerprint(localDir, fingerprint) {
|
|
253
|
+
try {
|
|
254
|
+
fs.writeFileSync(path.join(localDir, CONFIG_FINGERPRINT_FILE), fingerprint + "\n");
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
// Best-effort: a missing fingerprint only costs a reconcile next launch.
|
|
258
|
+
}
|
|
259
|
+
}
|
|
199
260
|
function writeConfigs(localDir, projectRoot, consoleCfg) {
|
|
200
261
|
ensureDir(localDir);
|
|
201
262
|
ensureDir(path.join(localDir, "kratos"));
|
|
202
263
|
ensureDir(path.join(localDir, "keto"));
|
|
203
264
|
ensureDir(path.join(localDir, "hydra"));
|
|
204
265
|
ensureDir(path.join(localDir, "nginx"));
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
fs.writeFileSync(path.join(localDir, "keto", "keto.yml"), (0, configs_js_1.ketoConfigYaml)());
|
|
209
|
-
fs.writeFileSync(path.join(localDir, "hydra", "hydra.yml"), (0, configs_js_1.hydraConfigYaml)());
|
|
210
|
-
// nginx config is always emitted — the gateway is no longer optional.
|
|
211
|
-
fs.writeFileSync(path.join(localDir, "nginx", "nginx.conf"), (0, configs_js_1.nginxConf)());
|
|
266
|
+
for (const { relPath, content } of renderConfigs(projectRoot, consoleCfg)) {
|
|
267
|
+
fs.writeFileSync(path.join(localDir, relPath), content);
|
|
268
|
+
}
|
|
212
269
|
}
|
|
213
270
|
/**
|
|
214
271
|
* Bring up the local Ory stack and wait for the gateway to be healthy.
|
|
215
272
|
*
|
|
216
273
|
* Idempotent and fail-graceful: returns a typed result instead of exiting.
|
|
217
|
-
* If the gateway is already healthy the
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
274
|
+
* If the gateway is already healthy AND the running stack's config matches
|
|
275
|
+
* what the current code generates (verified by a config fingerprint), the
|
|
276
|
+
* function short-circuits without touching Docker; otherwise it regenerates
|
|
277
|
+
* the config and runs `docker compose up -d --wait` to reconcile. When
|
|
278
|
+
* `quiet` is set, progress is logged to stderr only (suitable for the dev
|
|
279
|
+
* launcher); otherwise it prints to stdout in the format `localUp` historically used.
|
|
221
280
|
*/
|
|
222
281
|
async function ensureLocalOryStack(opts = {}) {
|
|
223
282
|
const localDir = opts.localDir ?? getLocalDir();
|
|
@@ -232,13 +291,25 @@ async function ensureLocalOryStack(opts = {}) {
|
|
|
232
291
|
}
|
|
233
292
|
const gatewayCfg = (0, configs_js_1.getGatewayConfig)();
|
|
234
293
|
const healthUrl = `${configs_js_1.GATEWAY_URL}${gatewayCfg.healthPath}`;
|
|
235
|
-
// Short-circuit if the gateway is already serving
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
294
|
+
// Short-circuit if the gateway is already serving AND the running stack was
|
|
295
|
+
// brought up with the same config the current code would generate — keeps
|
|
296
|
+
// repeated dev launches fast without re-pulling/restarting healthy
|
|
297
|
+
// containers. Gateway health alone is not enough: a config change to any
|
|
298
|
+
// service (e.g. the login-UI port, a service env var) leaves the gateway
|
|
299
|
+
// healthy while the rest of the stack runs stale config, so we also require
|
|
300
|
+
// the config fingerprint to match. On a mismatch we fall through and let
|
|
301
|
+
// `docker compose up -d --wait` diff the regenerated YAML and recreate only
|
|
302
|
+
// the changed containers.
|
|
303
|
+
//
|
|
304
|
+
// Skip the short-circuit entirely when ORY_CONSOLE_LITE_PATH is explicitly
|
|
305
|
+
// set so a rebuild is forced even when the path is unchanged (the console
|
|
306
|
+
// source tree behind the path may have changed, which the fingerprint of the
|
|
307
|
+
// generated config can't see).
|
|
240
308
|
const explicitConsoleLiteOverride = (process.env.ORY_CONSOLE_LITE_PATH ?? "").trim() !== "";
|
|
309
|
+
const fingerprint = configFingerprint(projectRoot, consoleCfg);
|
|
310
|
+
const configUnchanged = readConfigFingerprint(localDir) === fingerprint;
|
|
241
311
|
if (!explicitConsoleLiteOverride &&
|
|
312
|
+
configUnchanged &&
|
|
242
313
|
(await isGatewayHealthy(1_500, healthUrl))) {
|
|
243
314
|
return { status: "already-running", gatewayUrl: configs_js_1.GATEWAY_URL, localDir };
|
|
244
315
|
}
|
|
@@ -320,6 +391,10 @@ async function ensureLocalOryStack(opts = {}) {
|
|
|
320
391
|
detail: "Gateway did not become healthy within 2 minutes.",
|
|
321
392
|
};
|
|
322
393
|
}
|
|
394
|
+
// Record the fingerprint of the config the stack was actually brought up
|
|
395
|
+
// with (consoleCfg may have fallen back to disabled above), so the next
|
|
396
|
+
// launch can short-circuit only while the config is unchanged.
|
|
397
|
+
writeConfigFingerprint(localDir, configFingerprint(projectRoot, consoleCfg));
|
|
323
398
|
return { status: "started", gatewayUrl: configs_js_1.GATEWAY_URL, localDir };
|
|
324
399
|
}
|
|
325
400
|
async function localUp(opts = {}) {
|
package/dist/permissions.d.ts
CHANGED
|
@@ -118,12 +118,20 @@ export interface ApplyPermissionModeContext {
|
|
|
118
118
|
*/
|
|
119
119
|
export declare function applyPermissionMode(client: OryAgentClient, allowed: boolean, context?: ApplyPermissionModeContext): ModeDecision;
|
|
120
120
|
/**
|
|
121
|
-
* Outcome of {@link gateToolCall}. Either
|
|
121
|
+
* Outcome of {@link gateToolCall}. Either Ory is disabled entirely
|
|
122
|
+
* (`config.auditOnly` kill switch), the tool is a user-interaction
|
|
122
123
|
* primitive (`AskUserQuestion`, `ExitPlanMode`, `TodoWrite`, …) and we
|
|
123
124
|
* pass through with a single audit span, or it's a real tool execution
|
|
124
125
|
* and the caller gets the standard {@link PermissionDecision}.
|
|
125
126
|
*/
|
|
126
127
|
export type ToolGateOutcome = {
|
|
128
|
+
kind: "audit_only";
|
|
129
|
+
/** Attributes to attach to the caller's pass-through trace, if any. */
|
|
130
|
+
spanAttributes: {
|
|
131
|
+
auditOnly: true;
|
|
132
|
+
toolName: string;
|
|
133
|
+
};
|
|
134
|
+
} | {
|
|
127
135
|
kind: "interactive";
|
|
128
136
|
/** Attributes to attach to the caller's pass-through trace, if any. */
|
|
129
137
|
spanAttributes: {
|
|
@@ -147,8 +155,12 @@ export interface GateToolCallArgs {
|
|
|
147
155
|
modeOverride?: PermissionMode;
|
|
148
156
|
}
|
|
149
157
|
/**
|
|
150
|
-
* Single entry point for the pre-tool-use gate.
|
|
151
|
-
*
|
|
158
|
+
* Single entry point for the pre-tool-use gate. Applies the
|
|
159
|
+
* `config.auditOnly` kill switch first — when set, Ory is disabled
|
|
160
|
+
* entirely and the caller gets `{ kind: "audit_only" }` back without any
|
|
161
|
+
* permission check (record the invocation as an audit span and proceed).
|
|
162
|
+
* Otherwise splits the harness's incoming "tool" into two semantic
|
|
163
|
+
* categories:
|
|
152
164
|
*
|
|
153
165
|
* - **Interactive** — the tool surfaces UI to the user
|
|
154
166
|
* (`AskUserQuestion`, `ExitPlanMode`, `TodoWrite`, plus anything
|
package/dist/permissions.js
CHANGED
|
@@ -78,8 +78,12 @@ function applyPermissionMode(client, allowed, context = {}) {
|
|
|
78
78
|
return { kind: "deny", mode: "enforce", spanAttributes };
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
|
-
* Single entry point for the pre-tool-use gate.
|
|
82
|
-
*
|
|
81
|
+
* Single entry point for the pre-tool-use gate. Applies the
|
|
82
|
+
* `config.auditOnly` kill switch first — when set, Ory is disabled
|
|
83
|
+
* entirely and the caller gets `{ kind: "audit_only" }` back without any
|
|
84
|
+
* permission check (record the invocation as an audit span and proceed).
|
|
85
|
+
* Otherwise splits the harness's incoming "tool" into two semantic
|
|
86
|
+
* categories:
|
|
83
87
|
*
|
|
84
88
|
* - **Interactive** — the tool surfaces UI to the user
|
|
85
89
|
* (`AskUserQuestion`, `ExitPlanMode`, `TodoWrite`, plus anything
|
|
@@ -99,6 +103,17 @@ function applyPermissionMode(client, allowed, context = {}) {
|
|
|
99
103
|
* semantics; the new `interactive` kind means "do nothing else."
|
|
100
104
|
*/
|
|
101
105
|
async function gateToolCall(client, args) {
|
|
106
|
+
if ((0, config_js_1.resolveConfig)().auditOnly) {
|
|
107
|
+
client.logger.debug("tool.audit_only", {
|
|
108
|
+
harness: args.harness,
|
|
109
|
+
toolName: args.toolName,
|
|
110
|
+
note: "audit-only mode — skipping permission check",
|
|
111
|
+
});
|
|
112
|
+
return {
|
|
113
|
+
kind: "audit_only",
|
|
114
|
+
spanAttributes: { auditOnly: true, toolName: args.toolName },
|
|
115
|
+
};
|
|
116
|
+
}
|
|
102
117
|
if ((0, tool_catalog_js_1.isInteractiveTool)(args.harness, args.toolName)) {
|
|
103
118
|
client.logger.debug("tool.interactive", {
|
|
104
119
|
harness: args.harness,
|