@m1a0rz/agent-identity 0.3.1 → 0.3.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/README-cn.md +19 -9
- package/README.md +22 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -17
- package/dist/src/actions/identity-actions.d.ts +7 -1
- package/dist/src/actions/identity-actions.d.ts.map +1 -1
- package/dist/src/actions/identity-actions.js +82 -34
- package/dist/src/commands/identity-commands.d.ts.map +1 -1
- package/dist/src/commands/identity-commands.js +9 -4
- package/dist/src/hooks/after-tool-call.d.ts +12 -0
- package/dist/src/hooks/after-tool-call.d.ts.map +1 -0
- package/dist/src/hooks/after-tool-call.js +31 -0
- package/dist/src/hooks/before-agent-start.d.ts +3 -3
- package/dist/src/hooks/before-agent-start.d.ts.map +1 -1
- package/dist/src/hooks/before-agent-start.js +6 -20
- package/dist/src/hooks/before-tool-call.d.ts +19 -14
- package/dist/src/hooks/before-tool-call.d.ts.map +1 -1
- package/dist/src/hooks/before-tool-call.js +192 -130
- package/dist/src/hooks/subagent-ended-cleanup.js +1 -1
- package/dist/src/risk/low-risk-tools.d.ts.map +1 -1
- package/dist/src/risk/low-risk-tools.js +0 -2
- package/dist/src/services/tip-acquisition.d.ts +0 -1
- package/dist/src/services/tip-acquisition.d.ts.map +1 -1
- package/dist/src/services/tip-acquisition.js +2 -2
- package/dist/src/services/tip-propagation.d.ts.map +1 -1
- package/dist/src/services/tip-propagation.js +1 -2
- package/dist/src/services/tip-with-refresh.d.ts +1 -1
- package/dist/src/services/tip-with-refresh.d.ts.map +1 -1
- package/dist/src/services/tip-with-refresh.js +3 -5
- package/dist/src/store/credential-env-snapshot.d.ts +38 -0
- package/dist/src/store/credential-env-snapshot.d.ts.map +1 -0
- package/dist/src/store/credential-env-snapshot.js +101 -0
- package/dist/src/store/encryption.d.ts +30 -0
- package/dist/src/store/encryption.d.ts.map +1 -0
- package/dist/src/store/encryption.js +147 -0
- package/dist/src/store/session-store.d.ts.map +1 -1
- package/dist/src/store/session-store.js +91 -8
- package/dist/src/store/tip-store.d.ts +11 -6
- package/dist/src/store/tip-store.d.ts.map +1 -1
- package/dist/src/store/tip-store.js +23 -54
- package/dist/src/tools/identity-fetch.d.ts.map +1 -1
- package/dist/src/tools/identity-fetch.js +1 -0
- package/dist/src/tools/identity-list-credentials.d.ts +2 -0
- package/dist/src/tools/identity-list-credentials.d.ts.map +1 -1
- package/dist/src/tools/identity-list-credentials.js +6 -3
- package/package.json +1 -1
- package/skills/SKILL.md +75 -150
package/README-cn.md
CHANGED
|
@@ -11,7 +11,9 @@ UserPool OIDC 登录、TIP (Trusted Identity Provider) 令牌(通过 Identity
|
|
|
11
11
|
- **OIDC 登录**:`/identity login` 返回 IdP 授权 URL。用户打开 URL 后,IdP 重定向到 `/identity/oauth/callback`。
|
|
12
12
|
- **TIP 令牌**:当会话中有已登录用户时,`before_agent_start` 钩子会获取 TIP 令牌。
|
|
13
13
|
- **凭据 3LO**:`/identity fetch <provider>` 返回授权 URL。IdP 重定向到 Identity 提供的回调地址(控制台配置)。
|
|
14
|
-
- **凭据绑定**:`/identity set <provider> <envVar>`
|
|
14
|
+
- **凭据绑定**:`/identity set <provider> <envVar>` 将存储的凭据绑定到环境变量。凭据按工具调用粒度安全注入,并发多用户会话之间互相隔离。
|
|
15
|
+
- **加密会话存储**:`sessions.json` 使用 AES-256-GCM 加密存储在磁盘上。旧版明文文件首次加载时自动迁移。
|
|
16
|
+
- **内存 TIP 缓存**:TIP 令牌仅存储在内存中(不持久化到磁盘)。TIP 是短效令牌,可随时从用户 session token 重新获取。
|
|
15
17
|
- **动态 UserPool**:通过 `userPoolName` + `clientName` 解析 OIDC 配置(无需手动配置 clientId)。
|
|
16
18
|
- **凭证加载**:从环境变量、文件或 STS AssumeRole 加载 AK/SK(veadk 风格)。
|
|
17
19
|
|
|
@@ -251,16 +253,24 @@ TIP token 通过 `GetWorkloadAccessTokenForJWT` 获取。工作负载行为:
|
|
|
251
253
|
|
|
252
254
|
## 钩子
|
|
253
255
|
|
|
254
|
-
- **before_agent_start** - 获取 TIP token
|
|
255
|
-
- **subagent_spawned** - 在子 agent 创建时将 TIP
|
|
256
|
-
- **before_tool_call** -
|
|
256
|
+
- **before_agent_start** - 仅为主 agent 获取 TIP token。
|
|
257
|
+
- **subagent_spawned** - 在子 agent 创建时将 TIP 传播到子会话。
|
|
258
|
+
- **before_tool_call** - 群组上下文注入、可选 AuthZ(TIP 检查、CheckPermission、风险审批)、工具调用级凭据注入。
|
|
259
|
+
- **after_tool_call** - 清理工具调用级凭据注入状态。
|
|
257
260
|
|
|
258
261
|
## 数据存储
|
|
259
262
|
|
|
260
263
|
插件数据位于 `~/.openclaw/plugins/identity/`:
|
|
261
264
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
| 文件 | 描述 |
|
|
266
|
+
|------|------|
|
|
267
|
+
| `sessions.json` | 加密存储的 sessionKey → userToken 映射。加载/保存时清理过期条目。 |
|
|
268
|
+
| `credential-env-bindings.json` | 按 session:`{ [sessionKey]: { [provider]: envVar } }` |
|
|
269
|
+
|
|
270
|
+
**仅内存存储(不持久化到磁盘):**
|
|
271
|
+
|
|
272
|
+
| 数据 | 描述 |
|
|
273
|
+
|------|------|
|
|
274
|
+
| TIP 令牌 | sessionKey → TIP token 缓存。短效,按需从 session token 重新获取。 |
|
|
275
|
+
| 凭据 | 按 session(api_key、oauth2)。gateway 重启后丢失;logout 时清除。 |
|
|
276
|
+
| OIDC state | 临时数据,5 分钟 TTL。 |
|
package/README.md
CHANGED
|
@@ -9,11 +9,13 @@ Integrates with [Volcengine Agent Identity and Permission Management](https://ww
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
11
|
- **OIDC Login**: `/identity login` returns IdP auth URL (no HTTP start endpoint). User opens URL, IdP redirects to `/identity/oauth/callback`.
|
|
12
|
-
- **TIP Token**: `before_agent_start` hook fetches TIP token when session has a logged-in user
|
|
12
|
+
- **TIP Token**: `before_agent_start` hook fetches TIP token when session has a logged-in user.
|
|
13
13
|
- **Credential 3LO**: `/identity fetch <provider>` returns auth URL. IdP redirects to Identity-provided callback (control-plane config).
|
|
14
|
-
- **Credential Binding**: `/identity set <provider> <envVar>` binds stored credential to env var. Credentials are injected
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
14
|
+
- **Credential Binding**: `/identity set <provider> <envVar>` binds stored credential to env var. Credentials are securely injected per-tool-call, isolated between concurrent multi-user sessions.
|
|
15
|
+
- **Encrypted Session Storage**: `sessions.json` is encrypted at rest (AES-256-GCM). Plaintext sessions from older versions are auto-migrated on first load.
|
|
16
|
+
- **In-memory TIP Cache**: TIP tokens are stored only in memory (no disk persistence). They are short-lived and re-obtained from the user's session token on demand.
|
|
17
|
+
- **Dynamic UserPool**: Resolve OIDC config by `userPoolName` + `clientName` (no manual clientId).
|
|
18
|
+
- **Credentials**: Load AK/SK from env, file, or STS AssumeRole (veadk-style).
|
|
17
19
|
|
|
18
20
|
## HTTP Endpoints
|
|
19
21
|
|
|
@@ -251,16 +253,24 @@ Follow-up messages (login success, credential fetch done) are not delivered when
|
|
|
251
253
|
|
|
252
254
|
## Hooks
|
|
253
255
|
|
|
254
|
-
- **before_agent_start** - Fetch TIP token
|
|
255
|
-
- **subagent_spawned** - Propagate TIP to child session on subagent spawn
|
|
256
|
-
- **before_tool_call** -
|
|
256
|
+
- **before_agent_start** - Fetch TIP token for main agent only.
|
|
257
|
+
- **subagent_spawned** - Propagate TIP to child session on subagent spawn.
|
|
258
|
+
- **before_tool_call** - Group context injection, optional AuthZ (TIP check, CheckPermission, risk approval), and per-tool-call credential injection.
|
|
259
|
+
- **after_tool_call** - Clean up per-tool-call credential injection state.
|
|
257
260
|
|
|
258
261
|
## Data Storage
|
|
259
262
|
|
|
260
263
|
Plugin data at `~/.openclaw/plugins/identity/`:
|
|
261
264
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
| File | Description |
|
|
266
|
+
|------|-------------|
|
|
267
|
+
| `sessions.json` | Encrypted sessionKey → userToken mapping. Expired entries pruned on load/save. |
|
|
268
|
+
| `credential-env-bindings.json` | Per-session: `{ [sessionKey]: { [provider]: envVar } }` |
|
|
269
|
+
|
|
270
|
+
**In-memory only (not persisted to disk):**
|
|
271
|
+
|
|
272
|
+
| Data | Description |
|
|
273
|
+
|------|-------------|
|
|
274
|
+
| TIP tokens | sessionKey → TIP token cache. Short-lived, re-obtained from session token on demand. |
|
|
275
|
+
| Credentials | Per-session (api_key, oauth2). Lost on gateway restart; cleared on logout. |
|
|
276
|
+
| OIDC state | Ephemeral, 5 min TTL. |
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAqE7D,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,GAAG,EAAE,iBAAiB,QA0YtD"}
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import { createSubagentEndedCleanupHandler } from "./src/hooks/subagent-ended-cl
|
|
|
22
22
|
import { setSender, clearSender } from "./src/store/group-sender-store.js";
|
|
23
23
|
import { deriveSessionKey, isGroupOrChannelSessionKey, } from "./src/utils/derive-session-key.js";
|
|
24
24
|
import { createBeforeToolCallHandler } from "./src/hooks/before-tool-call.js";
|
|
25
|
+
import { createAfterToolCallHandler } from "./src/hooks/after-tool-call.js";
|
|
25
26
|
import * as skillPathStore from "./src/store/skill-path-store.js";
|
|
26
27
|
import { createOIDCCallbackHandler, createOIDCCallbackHandlerLazy, } from "./src/routes/oidc-login.js";
|
|
27
28
|
import { IdentityClient, resolveOIDCConfig, } from "./src/services/identity-client.js";
|
|
@@ -43,6 +44,7 @@ import { createIdentityUnsetBindingTool } from "./src/tools/identity-unset-bindi
|
|
|
43
44
|
import { createIdentityWhoamiTool } from "./src/tools/identity-whoami.js";
|
|
44
45
|
import { parseSessionKeyToDeliveryTarget, } from "./src/utils/derive-session-key.js";
|
|
45
46
|
import { logInfo, logWarn } from "./src/utils/logger.js";
|
|
47
|
+
import { initEncryptionKey } from "./src/store/encryption.js";
|
|
46
48
|
const PLUGIN_STORE_DIR = "~/.openclaw/plugins/identity";
|
|
47
49
|
/**
|
|
48
50
|
* Whether Identity should be enabled.
|
|
@@ -64,6 +66,7 @@ function hasAnyIdentityConfig(identity) {
|
|
|
64
66
|
export default function register(api) {
|
|
65
67
|
const pluginConfig = (api.pluginConfig ?? {});
|
|
66
68
|
const storeDir = api.resolvePath(PLUGIN_STORE_DIR);
|
|
69
|
+
initEncryptionKey(storeDir);
|
|
67
70
|
const identityCfg = pluginConfig.identity;
|
|
68
71
|
const hasIdentity = hasAnyIdentityConfig(identityCfg);
|
|
69
72
|
const userpool = pluginConfig.userpool;
|
|
@@ -366,10 +369,7 @@ export default function register(api) {
|
|
|
366
369
|
logger: api.logger,
|
|
367
370
|
}));
|
|
368
371
|
}
|
|
369
|
-
const toolCheck = authz?.toolCheck ?? false;
|
|
370
372
|
const skillReadCheck = authz?.skillReadCheck ?? false;
|
|
371
|
-
const requireRiskApproval = authz?.requireRiskApproval ?? false;
|
|
372
|
-
const hasAuthz = toolCheck || skillReadCheck || requireRiskApproval;
|
|
373
373
|
api.on("llm_input", createLlmInputHandler({
|
|
374
374
|
enabled: skillReadCheck,
|
|
375
375
|
logger: api.logger,
|
|
@@ -380,18 +380,19 @@ export default function register(api) {
|
|
|
380
380
|
skillPathStore.clearSessionById(ctx.sessionId);
|
|
381
381
|
});
|
|
382
382
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
383
|
+
// before_tool_call: authz, credential injection, group sender context
|
|
384
|
+
api.on("before_tool_call", createBeforeToolCallHandler({
|
|
385
|
+
storeDir,
|
|
386
|
+
identityClient: hasIdentity ? identityClient : undefined,
|
|
387
|
+
namespaceName: authz?.namespaceName ?? "default",
|
|
388
|
+
logger: api.logger,
|
|
389
|
+
sendToSession,
|
|
390
|
+
authz,
|
|
391
|
+
approvalTtlMs,
|
|
392
|
+
identityService: hasIdentity ? identityService : undefined,
|
|
393
|
+
getOidcConfigForRefresh: getOidcConfigForRefresh ?? undefined,
|
|
394
|
+
configWorkloadName: identityCfg?.workloadName,
|
|
395
|
+
}));
|
|
396
|
+
// Companion after_tool_call: restore env snapshot set by credential injection
|
|
397
|
+
api.on("after_tool_call", createAfterToolCallHandler({ logger: api.logger }));
|
|
397
398
|
}
|
|
@@ -86,7 +86,11 @@ export type ListCredentialsResult = {
|
|
|
86
86
|
hasMore: boolean;
|
|
87
87
|
totalCount?: number;
|
|
88
88
|
};
|
|
89
|
-
export
|
|
89
|
+
export type ListCredentialsFilter = {
|
|
90
|
+
name?: string;
|
|
91
|
+
flow?: string;
|
|
92
|
+
};
|
|
93
|
+
export declare function runListCredentials(deps: IdentityActionsDeps, sessionKey: string, page?: number, filter?: ListCredentialsFilter): Promise<ListCredentialsResult>;
|
|
90
94
|
export type ListTipsResult = {
|
|
91
95
|
tips: Array<{
|
|
92
96
|
sessionKey: string;
|
|
@@ -124,6 +128,8 @@ export declare function runFetch(deps: IdentityActionsDeps, sessionKey: string,
|
|
|
124
128
|
scopes?: string[];
|
|
125
129
|
deliveryTarget?: SessionKeyDeliveryTarget | null;
|
|
126
130
|
config?: OpenClawConfig;
|
|
131
|
+
/** "tool" = agent tool call, "command" = slash command. Defaults to "command". */
|
|
132
|
+
source?: "tool" | "command";
|
|
127
133
|
}): Promise<FetchResult>;
|
|
128
134
|
export type SetBindingResult = {
|
|
129
135
|
ok: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity-actions.d.ts","sourceRoot":"","sources":["../../../src/actions/identity-actions.ts"],"names":[],"mappings":"AAgBA;;;GAGG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAc/E,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,8BAA8B,CAAC;AAUtC,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,eAAe,CAAC;IACjC,aAAa,EAAE,MAAM,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACnD,uBAAuB,CAAC,EAAE,MAAM,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC9D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,uBAAuB,CAAC;IACzC,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,qBAAqB,CAAC,EAAE,CACtB,kBAAkB,EAAE,wBAAwB,GAAG,MAAM,EACrD,IAAI,EAAE,MAAM,KACT,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,YAAY,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"identity-actions.d.ts","sourceRoot":"","sources":["../../../src/actions/identity-actions.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,7 +20,7 @@ import { loadCredentialEnvBindings, loadAllCredentialEnvBindings, setCredentialE
|
|
|
20
20
|
import { loadCredentials, setCredential, getCredential, deleteCredentialsForSession, } from "../store/credential-store.js";
|
|
21
21
|
import { getSession, deleteSession } from "../store/session-store.js";
|
|
22
22
|
import { createState } from "../store/oidc-state-store.js";
|
|
23
|
-
import {
|
|
23
|
+
import { getAllTIPTokens, deleteTIPToken } from "../store/tip-store.js";
|
|
24
24
|
import { extractDelegationChainFromJwt } from "../utils/auth.js";
|
|
25
25
|
import { resolveAgentId, } from "../utils/derive-session-key.js";
|
|
26
26
|
function inferFlowFromProvider(info) {
|
|
@@ -32,6 +32,7 @@ function inferFlowFromProvider(info) {
|
|
|
32
32
|
}
|
|
33
33
|
const OAUTH_POLL_INTERVAL_MS = 2500;
|
|
34
34
|
const OAUTH_POLL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
35
|
+
const OAUTH_QUICK_RETRY_MS = 1000;
|
|
35
36
|
function sleep(ms) {
|
|
36
37
|
return new Promise((r) => setTimeout(r, ms));
|
|
37
38
|
}
|
|
@@ -156,30 +157,40 @@ export async function runLogout(deps, sessionKey) {
|
|
|
156
157
|
const { storeDir, logger } = deps;
|
|
157
158
|
logDebug(logger, `logout sessionKey=${sessionKey.slice(0, 24)}...`);
|
|
158
159
|
await deleteSession(storeDir, sessionKey);
|
|
159
|
-
|
|
160
|
-
delete tokens[sessionKey];
|
|
161
|
-
await saveTIPTokens(storeDir, tokens);
|
|
160
|
+
deleteTIPToken(sessionKey);
|
|
162
161
|
deleteCredentialsForSession(storeDir, sessionKey);
|
|
163
162
|
return { ok: true };
|
|
164
163
|
}
|
|
165
164
|
const LIST_PAGE_SIZE = 10;
|
|
166
|
-
export async function runListCredentials(deps, sessionKey, page = 1) {
|
|
167
|
-
const { storeDir, identityClient, logger } = deps;
|
|
165
|
+
export async function runListCredentials(deps, sessionKey, page = 1, filter) {
|
|
166
|
+
const { storeDir, identityClient, identityService, logger } = deps;
|
|
168
167
|
const creds = await loadCredentials(storeDir, sessionKey);
|
|
169
168
|
const bindings = await loadCredentialEnvBindings(storeDir, sessionKey);
|
|
170
169
|
let providers = [];
|
|
171
170
|
let totalCount = 0;
|
|
172
171
|
if (identityClient) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
PageSize: LIST_PAGE_SIZE,
|
|
177
|
-
});
|
|
178
|
-
providers = result.CredentialProviders ?? result.Data ?? [];
|
|
179
|
-
totalCount = result.TotalCount ?? 0;
|
|
172
|
+
const session = await getSession(storeDir, sessionKey);
|
|
173
|
+
if (!session || !identityService.parseUserToken(session.userToken).valid) {
|
|
174
|
+
logWarn(logger, `list-credentials: no valid session for key=${sessionKey.slice(0, 24)}...`);
|
|
180
175
|
}
|
|
181
|
-
|
|
182
|
-
|
|
176
|
+
else {
|
|
177
|
+
try {
|
|
178
|
+
const apiFilter = {};
|
|
179
|
+
if (filter?.name)
|
|
180
|
+
apiFilter.Name = filter.name;
|
|
181
|
+
if (filter?.flow)
|
|
182
|
+
apiFilter.Flow = filter.flow;
|
|
183
|
+
const result = await identityClient.listCredentialProviders({
|
|
184
|
+
PageNumber: page,
|
|
185
|
+
PageSize: LIST_PAGE_SIZE,
|
|
186
|
+
...(Object.keys(apiFilter).length > 0 ? { Filter: apiFilter } : {}),
|
|
187
|
+
});
|
|
188
|
+
providers = result.CredentialProviders ?? result.Data ?? [];
|
|
189
|
+
totalCount = result.TotalCount ?? 0;
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
logWarn(logger, `list-credentials API error: ${String(e)}`);
|
|
193
|
+
}
|
|
183
194
|
}
|
|
184
195
|
}
|
|
185
196
|
const providerNames = new Set(providers.map((p) => p.Name));
|
|
@@ -232,7 +243,7 @@ export async function runListCredentials(deps, sessionKey, page = 1) {
|
|
|
232
243
|
}
|
|
233
244
|
export async function runListTips(deps) {
|
|
234
245
|
const { storeDir } = deps;
|
|
235
|
-
const tokens =
|
|
246
|
+
const tokens = getAllTIPTokens();
|
|
236
247
|
const bindingsBySession = await loadAllCredentialEnvBindings(storeDir);
|
|
237
248
|
const now = Date.now();
|
|
238
249
|
const tips = [];
|
|
@@ -292,27 +303,10 @@ export async function runConfig(deps) {
|
|
|
292
303
|
}
|
|
293
304
|
export async function runFetch(deps, sessionKey, params) {
|
|
294
305
|
const { identityClient, storeDir, sendCredentialMessage, logger } = deps;
|
|
295
|
-
const { provider, flow, flowExplicit, redirectUrl, scopes, deliveryTarget, config } = params;
|
|
306
|
+
const { provider, flow, flowExplicit, redirectUrl, scopes, deliveryTarget, config, source = "command" } = params;
|
|
296
307
|
if (!identityClient) {
|
|
297
308
|
return { kind: "error", message: "Identity service not configured. Cannot add credentials." };
|
|
298
309
|
}
|
|
299
|
-
let effectiveFlow = flow;
|
|
300
|
-
if (!flowExplicit) {
|
|
301
|
-
try {
|
|
302
|
-
const listResult = await identityClient.listCredentialProviders({
|
|
303
|
-
PageNumber: 1,
|
|
304
|
-
PageSize: 20,
|
|
305
|
-
Filter: { Name: provider },
|
|
306
|
-
});
|
|
307
|
-
const list = listResult.CredentialProviders ?? listResult.Data ?? [];
|
|
308
|
-
const info = list.find((p) => p.Name === provider);
|
|
309
|
-
if (info)
|
|
310
|
-
effectiveFlow = inferFlowFromProvider(info);
|
|
311
|
-
}
|
|
312
|
-
catch {
|
|
313
|
-
// keep default
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
310
|
const ctxAgentId = resolveAgentId({ sessionKey, config: config });
|
|
317
311
|
const tipRefreshOptions = deps.getOidcConfigForRefresh
|
|
318
312
|
? {
|
|
@@ -330,6 +324,23 @@ export async function runFetch(deps, sessionKey, params) {
|
|
|
330
324
|
message: "Login first with `/identity login` to establish identity before adding credentials.",
|
|
331
325
|
};
|
|
332
326
|
}
|
|
327
|
+
let effectiveFlow = flow;
|
|
328
|
+
if (!flowExplicit) {
|
|
329
|
+
try {
|
|
330
|
+
const listResult = await identityClient.listCredentialProviders({
|
|
331
|
+
PageNumber: 1,
|
|
332
|
+
PageSize: 20,
|
|
333
|
+
Filter: { Name: provider },
|
|
334
|
+
});
|
|
335
|
+
const list = listResult.CredentialProviders ?? listResult.Data ?? [];
|
|
336
|
+
const info = list.find((p) => p.Name === provider);
|
|
337
|
+
if (info)
|
|
338
|
+
effectiveFlow = inferFlowFromProvider(info);
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
// keep default
|
|
342
|
+
}
|
|
343
|
+
}
|
|
333
344
|
try {
|
|
334
345
|
if (effectiveFlow === "apikey") {
|
|
335
346
|
const result = await identityClient.getResourceApiKey({
|
|
@@ -361,6 +372,43 @@ export async function runFetch(deps, sessionKey, params) {
|
|
|
361
372
|
return { kind: "success", message: `✓ Credential for \`${provider}\` added (direct token).` };
|
|
362
373
|
}
|
|
363
374
|
if (oauthResult.authorizationUrl) {
|
|
375
|
+
// Quick retry: the server may have a cached token that wasn't ready on
|
|
376
|
+
// the first call (e.g. user previously authorized). A short pause then
|
|
377
|
+
// re-check avoids unnecessary polling / returning authUrl to the user.
|
|
378
|
+
await sleep(OAUTH_QUICK_RETRY_MS);
|
|
379
|
+
try {
|
|
380
|
+
const retry = await identityClient.getResourceOauth2Token({
|
|
381
|
+
providerName: provider,
|
|
382
|
+
identityToken: tip.token,
|
|
383
|
+
flow: oauth2Flow,
|
|
384
|
+
redirectUrl,
|
|
385
|
+
scopes: scopes?.length ? scopes : undefined,
|
|
386
|
+
});
|
|
387
|
+
if (retry.accessToken) {
|
|
388
|
+
await setCredential(storeDir, sessionKey, provider, {
|
|
389
|
+
type: "oauth2",
|
|
390
|
+
status: "authenticated",
|
|
391
|
+
accessToken: retry.accessToken,
|
|
392
|
+
expiresAt: Date.now() + 3600 * 1000,
|
|
393
|
+
});
|
|
394
|
+
return { kind: "success", message: `✓ Credential for \`${provider}\` added (cached token).` };
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
// quick retry failed — proceed with authUrl flow
|
|
399
|
+
}
|
|
400
|
+
if (source === "tool") {
|
|
401
|
+
// Agent tool context: return authUrl for the agent to display.
|
|
402
|
+
// No background polling — the agent should re-call identity_fetch
|
|
403
|
+
// after the user completes authorization in the browser.
|
|
404
|
+
logInfo(logger, `fetch returning auth URL for provider=${provider} (tool, no poll)`);
|
|
405
|
+
return {
|
|
406
|
+
kind: "auth_url",
|
|
407
|
+
authUrl: oauthResult.authorizationUrl,
|
|
408
|
+
message: `Open this URL to authorize \`${provider}\`. After you complete authorization in the browser, tell me and I will fetch the credential.`,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
// Slash command context: start background polling and notify via channel message.
|
|
364
412
|
logInfo(logger, `fetch returning auth URL for provider=${provider}, starting poll`);
|
|
365
413
|
const target = deliveryTarget ?? sessionKey;
|
|
366
414
|
pollOAuthAndNotify({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity-commands.d.ts","sourceRoot":"","sources":["../../../src/commands/identity-commands.ts"],"names":[],"mappings":"AAgBA;;;GAGG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAUL,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,SAAS,EACf,MAAM,gCAAgC,CAAC;AAYxC,YAAY,EAAE,oBAAoB,EAAE,SAAS,EAAE,CAAC;AAEhD,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"identity-commands.d.ts","sourceRoot":"","sources":["../../../src/commands/identity-commands.ts"],"names":[],"mappings":"AAgBA;;;GAGG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAUL,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,SAAS,EACf,MAAM,gCAAgC,CAAC;AAYxC,YAAY,EAAE,oBAAoB,EAAE,SAAS,EAAE,CAAC;AAEhD,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,CAAC;AAyoBvD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,oBAAoB;;;;;mBAlf3C,oBAAoB,KAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;EA2fpE;AAED,0CAA0C;AAC1C,wBAAgB,eAAe,CAAC,IAAI,EAAE,oBAAoB;;;;;mBA9frC,oBAAoB,KAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;EAugBpE"}
|
|
@@ -88,7 +88,7 @@ function parseSubcommand(args) {
|
|
|
88
88
|
rest: raw.slice(space + 1).trim(),
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
-
/** Parse list args: optional page. E.g. "list", "list 2", "list --
|
|
91
|
+
/** Parse list args: optional page and filters. E.g. "list", "list 2", "list --name=github --flow=M2M". */
|
|
92
92
|
function parseListArgs(rest) {
|
|
93
93
|
const parts = rest.split(/\s+/).filter(Boolean);
|
|
94
94
|
const flags = {};
|
|
@@ -104,7 +104,11 @@ function parseListArgs(rest) {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
const page = flags.page ? parseInt(flags.page, 10) : positional[0];
|
|
107
|
-
return {
|
|
107
|
+
return {
|
|
108
|
+
page: Number.isFinite(page) && page >= 1 ? page : 1,
|
|
109
|
+
name: flags.name || undefined,
|
|
110
|
+
flow: flags.flow || undefined,
|
|
111
|
+
};
|
|
108
112
|
}
|
|
109
113
|
/** Parse fetch args: provider and flags (--flow, --redirectUrl, --scopes). flow can be omitted to auto-infer from provider. */
|
|
110
114
|
function parseFetchArgs(rest) {
|
|
@@ -331,8 +335,9 @@ async function handleLogout(deps, sessionKey) {
|
|
|
331
335
|
return { text: "✓ Logged out." };
|
|
332
336
|
}
|
|
333
337
|
async function handleListCredentials(deps, sessionKey, rest) {
|
|
334
|
-
const { page } = parseListArgs(rest);
|
|
335
|
-
const
|
|
338
|
+
const { page, name, flow } = parseListArgs(rest);
|
|
339
|
+
const filter = name || flow ? { name, flow } : undefined;
|
|
340
|
+
const result = await runListCredentials(deps, sessionKey, page, filter);
|
|
336
341
|
const lines = [];
|
|
337
342
|
if (result.providers.length > 0) {
|
|
338
343
|
lines.push(`**Available (page ${result.page}):**`);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type PluginLogger } from "../utils/logger.js";
|
|
2
|
+
export type AfterToolCallDeps = {
|
|
3
|
+
logger?: PluginLogger;
|
|
4
|
+
};
|
|
5
|
+
export declare function createAfterToolCallHandler(deps: AfterToolCallDeps): (event: {
|
|
6
|
+
toolName: string;
|
|
7
|
+
runId?: string;
|
|
8
|
+
toolCallId?: string;
|
|
9
|
+
}, _ctx: {
|
|
10
|
+
sessionKey?: string;
|
|
11
|
+
}) => void;
|
|
12
|
+
//# sourceMappingURL=after-tool-call.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"after-tool-call.d.ts","sourceRoot":"","sources":["../../../src/hooks/after-tool-call.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAY,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEjE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB,CAAC;AAEF,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,iBAAiB,IAI9D,OAAO;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,EAChE,MAAM;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,KAC5B,IAAI,CASR"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* after_tool_call hook: restore process.env snapshot and release per-key locks
|
|
18
|
+
* set by before_tool_call credential injection.
|
|
19
|
+
*/
|
|
20
|
+
import { hasSnapshot, restoreEnvSnapshot } from "../store/credential-env-snapshot.js";
|
|
21
|
+
import { logDebug } from "../utils/logger.js";
|
|
22
|
+
export function createAfterToolCallHandler(deps) {
|
|
23
|
+
const { logger } = deps;
|
|
24
|
+
return (event, _ctx) => {
|
|
25
|
+
if (!hasSnapshot(event.runId, event.toolCallId))
|
|
26
|
+
return;
|
|
27
|
+
logDebug(logger, `restoring env snapshot for tool=${event.toolName} ` +
|
|
28
|
+
`run=${event.runId ?? "?"} toolCallId=${event.toolCallId ?? "?"}`);
|
|
29
|
+
restoreEnvSnapshot(event.runId, event.toolCallId);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* before_agent_start hook: fetch TIP token for main agent only.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* 3. Main: getOrRefreshTIPToken (fetches TIP, refreshes userToken if expired)
|
|
3
|
+
* Credential env injection is handled per-tool-call in before_tool_call
|
|
4
|
+
* to avoid process.env race conditions between concurrent runs.
|
|
6
5
|
*/
|
|
7
6
|
import type { IdentityService } from "../services/identity-service.js";
|
|
8
7
|
import type { OIDCConfigForRefresh } from "../services/session-refresh.js";
|
|
@@ -13,6 +12,7 @@ export type BeforeAgentStartDeps = {
|
|
|
13
12
|
getOidcConfigForRefresh?: () => Promise<OIDCConfigForRefresh>;
|
|
14
13
|
logger: {
|
|
15
14
|
info?: (msg: string) => void;
|
|
15
|
+
debug?: (msg: string) => void;
|
|
16
16
|
warn?: (msg: string) => void;
|
|
17
17
|
};
|
|
18
18
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"before-agent-start.d.ts","sourceRoot":"","sources":["../../../src/hooks/before-agent-start.ts"],"names":[],"mappings":"AAgBA
|
|
1
|
+
{"version":3,"file":"before-agent-start.d.ts","sourceRoot":"","sources":["../../../src/hooks/before-agent-start.ts"],"names":[],"mappings":"AAgBA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAM3E,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,eAAe,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,uBAAuB,CAAC,EAAE,MAAM,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC9D,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CACvG,CAAC;AAEF,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,oBAAoB,IAWpE,QAAQ;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,EAChD,KAAK;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,KAC7C,OAAO,CAAC;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAuB/C"}
|
|
@@ -14,15 +14,11 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import { getOrRefreshTIPToken } from "../services/tip-with-refresh.js";
|
|
17
|
-
import { logWarn } from "../utils/logger.js";
|
|
18
|
-
import { loadCredentialEnvBindings } from "../store/credential-env-bindings.js";
|
|
19
|
-
import { getCredential, resolveCredentialValue } from "../store/credential-store.js";
|
|
17
|
+
import { logDebug, logWarn } from "../utils/logger.js";
|
|
20
18
|
import { isSubagentSessionKey } from "../utils/derive-session-key.js";
|
|
21
19
|
import { resolveEffectiveSessionKey } from "../store/group-sender-store.js";
|
|
22
20
|
export function createBeforeAgentStartHandler(deps) {
|
|
23
21
|
const { storeDir, identityService, configWorkloadName, getOidcConfigForRefresh, logger } = deps;
|
|
24
|
-
// Always pass identityService so we can try fetch with session.userToken when TIP is missing/expired.
|
|
25
|
-
// getOidcConfigForRefresh is only needed for refresh when userToken itself has expired.
|
|
26
22
|
const tipRefreshOptions = {
|
|
27
23
|
identityService,
|
|
28
24
|
getOidcConfigForRefresh,
|
|
@@ -36,27 +32,17 @@ export function createBeforeAgentStartHandler(deps) {
|
|
|
36
32
|
if (isSubagentSessionKey(sessionKey))
|
|
37
33
|
return;
|
|
38
34
|
const effectiveKey = resolveEffectiveSessionKey(sessionKey);
|
|
39
|
-
|
|
40
|
-
const bindings = await loadCredentialEnvBindings(storeDir, effectiveKey);
|
|
41
|
-
for (const [provider, envVar] of Object.entries(bindings)) {
|
|
42
|
-
const cred = await getCredential(storeDir, effectiveKey, provider);
|
|
43
|
-
const value = cred ? resolveCredentialValue(cred) : undefined;
|
|
44
|
-
if (value)
|
|
45
|
-
process.env[envVar] = value;
|
|
46
|
-
else
|
|
47
|
-
delete process.env[envVar];
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
/* best-effort */
|
|
52
|
-
}
|
|
35
|
+
logDebug(logger, `before_agent_start: fetching TIP for key=${effectiveKey}`);
|
|
53
36
|
try {
|
|
54
37
|
const tip = await getOrRefreshTIPToken(storeDir, effectiveKey, {
|
|
55
38
|
...tipRefreshOptions,
|
|
56
39
|
ctxAgentId: ctx.agentId,
|
|
57
40
|
});
|
|
58
|
-
if (!tip)
|
|
41
|
+
if (!tip) {
|
|
42
|
+
logDebug(logger, `before_agent_start: no TIP available for key=${effectiveKey}`);
|
|
59
43
|
return;
|
|
44
|
+
}
|
|
45
|
+
logDebug(logger, `before_agent_start: TIP ready for key=${effectiveKey} sub=${tip.sub}`);
|
|
60
46
|
}
|
|
61
47
|
catch (err) {
|
|
62
48
|
logWarn(logger, `failed to get TIP for ${effectiveKey}: ${String(err)}`);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* before_tool_call hook
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* before_tool_call hook — three sequential phases:
|
|
3
|
+
*
|
|
4
|
+
* 1. Group sender context injection (_enhancedContext).
|
|
5
|
+
* 2. Session + AuthZ gate (mandatory session check, optional CheckPermission / risk).
|
|
6
|
+
* 3. Per-tool-call credential injection (params + process.env snapshot).
|
|
6
7
|
*
|
|
7
8
|
* @see https://github.com/volcengine/veadk-python/blob/main/veadk/tools/builtin_tools/agent_authorization.py
|
|
8
9
|
*/
|
|
@@ -12,34 +13,38 @@ import type { OIDCConfigForRefresh } from "../services/session-refresh.js";
|
|
|
12
13
|
import type { PluginConfig } from "../types.js";
|
|
13
14
|
export type BeforeToolCallDeps = {
|
|
14
15
|
storeDir: string;
|
|
15
|
-
/** When set, call CheckPermission with principal/originalCallers from TIP token, resource=toolName. */
|
|
16
16
|
identityClient?: IdentityClientInterface;
|
|
17
|
-
/** Namespace for CheckPermission. From authz.namespaceName, default "default". */
|
|
18
17
|
namespaceName?: string;
|
|
19
18
|
logger: {
|
|
20
19
|
debug?: (msg: string) => void;
|
|
21
20
|
warn?: (msg: string) => void;
|
|
22
21
|
};
|
|
23
|
-
/** Send message to session (Channel only). For sync approval flow. */
|
|
24
22
|
sendToSession?: (targetOrSessionKey: string, text: string) => Promise<void>;
|
|
25
|
-
/** Authz config. */
|
|
26
23
|
authz?: PluginConfig["authz"];
|
|
27
|
-
approvalTtlMs
|
|
28
|
-
/** When set, attempt TIP refresh when expired (uses session refresh_token). */
|
|
24
|
+
approvalTtlMs?: number;
|
|
29
25
|
identityService?: IdentityService;
|
|
30
26
|
getOidcConfigForRefresh?: () => Promise<OIDCConfigForRefresh>;
|
|
31
27
|
configWorkloadName?: string;
|
|
32
28
|
};
|
|
29
|
+
type HookResult = {
|
|
30
|
+
params?: Record<string, unknown>;
|
|
31
|
+
block?: boolean;
|
|
32
|
+
blockReason?: string;
|
|
33
|
+
};
|
|
34
|
+
declare function resolveCredentials(storeDir: string, effectiveKey: string): Promise<Record<string, string>>;
|
|
33
35
|
export declare function createBeforeToolCallHandler(deps: BeforeToolCallDeps): (event: {
|
|
34
36
|
toolName: string;
|
|
35
37
|
runId?: string;
|
|
38
|
+
toolCallId?: string;
|
|
36
39
|
params: Record<string, unknown>;
|
|
37
40
|
}, ctx: {
|
|
38
41
|
agentId?: string;
|
|
39
42
|
sessionKey?: string;
|
|
40
43
|
toolName: string;
|
|
41
|
-
}) => Promise<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
}) => Promise<HookResult | void>;
|
|
45
|
+
/** Exposed for testing. */
|
|
46
|
+
export declare const __testing: {
|
|
47
|
+
resolveCredentials: typeof resolveCredentials;
|
|
48
|
+
};
|
|
49
|
+
export {};
|
|
45
50
|
//# sourceMappingURL=before-tool-call.d.ts.map
|