@m1a0rz/agent-identity 0.1.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.
Files changed (125) hide show
  1. package/README-cn.md +223 -0
  2. package/README.md +223 -0
  3. package/dist/index.d.ts +14 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +306 -0
  6. package/dist/src/actions/identity-actions.d.ts +142 -0
  7. package/dist/src/actions/identity-actions.d.ts.map +1 -0
  8. package/dist/src/actions/identity-actions.js +429 -0
  9. package/dist/src/commands/identity-commands.d.ts +33 -0
  10. package/dist/src/commands/identity-commands.d.ts.map +1 -0
  11. package/dist/src/commands/identity-commands.js +572 -0
  12. package/dist/src/hooks/after-tool-call.d.ts +22 -0
  13. package/dist/src/hooks/after-tool-call.d.ts.map +1 -0
  14. package/dist/src/hooks/after-tool-call.js +35 -0
  15. package/dist/src/hooks/before-agent-start.d.ts +30 -0
  16. package/dist/src/hooks/before-agent-start.d.ts.map +1 -0
  17. package/dist/src/hooks/before-agent-start.js +93 -0
  18. package/dist/src/hooks/before-tool-call.d.ts +38 -0
  19. package/dist/src/hooks/before-tool-call.d.ts.map +1 -0
  20. package/dist/src/hooks/before-tool-call.js +138 -0
  21. package/dist/src/risk/classify-risk.d.ts +24 -0
  22. package/dist/src/risk/classify-risk.d.ts.map +1 -0
  23. package/dist/src/risk/classify-risk.js +61 -0
  24. package/dist/src/risk/diagnose-risk.d.ts +21 -0
  25. package/dist/src/risk/diagnose-risk.d.ts.map +1 -0
  26. package/dist/src/risk/diagnose-risk.js +37 -0
  27. package/dist/src/risk/llm-risk-check.d.ts +27 -0
  28. package/dist/src/risk/llm-risk-check.d.ts.map +1 -0
  29. package/dist/src/risk/llm-risk-check.js +274 -0
  30. package/dist/src/risk/low-risk-tools.d.ts +5 -0
  31. package/dist/src/risk/low-risk-tools.d.ts.map +1 -0
  32. package/dist/src/risk/low-risk-tools.js +29 -0
  33. package/dist/src/routes/oidc-login.d.ts +51 -0
  34. package/dist/src/routes/oidc-login.d.ts.map +1 -0
  35. package/dist/src/routes/oidc-login.js +153 -0
  36. package/dist/src/services/identity-client.d.ts +366 -0
  37. package/dist/src/services/identity-client.d.ts.map +1 -0
  38. package/dist/src/services/identity-client.js +578 -0
  39. package/dist/src/services/identity-credentials.d.ts +28 -0
  40. package/dist/src/services/identity-credentials.d.ts.map +1 -0
  41. package/dist/src/services/identity-credentials.js +170 -0
  42. package/dist/src/services/identity-service.d.ts +33 -0
  43. package/dist/src/services/identity-service.d.ts.map +1 -0
  44. package/dist/src/services/identity-service.js +53 -0
  45. package/dist/src/services/oidc-client.d.ts +57 -0
  46. package/dist/src/services/oidc-client.d.ts.map +1 -0
  47. package/dist/src/services/oidc-client.js +127 -0
  48. package/dist/src/services/send-notification-feishu.d.ts +27 -0
  49. package/dist/src/services/send-notification-feishu.d.ts.map +1 -0
  50. package/dist/src/services/send-notification-feishu.js +148 -0
  51. package/dist/src/services/session-refresh.d.ts +16 -0
  52. package/dist/src/services/session-refresh.d.ts.map +1 -0
  53. package/dist/src/services/session-refresh.js +38 -0
  54. package/dist/src/store/credential-env-bindings.d.ts +16 -0
  55. package/dist/src/store/credential-env-bindings.d.ts.map +1 -0
  56. package/dist/src/store/credential-env-bindings.js +61 -0
  57. package/dist/src/store/credential-store.d.ts +31 -0
  58. package/dist/src/store/credential-store.d.ts.map +1 -0
  59. package/dist/src/store/credential-store.js +57 -0
  60. package/dist/src/store/oidc-state-store.d.ts +15 -0
  61. package/dist/src/store/oidc-state-store.d.ts.map +1 -0
  62. package/dist/src/store/oidc-state-store.js +32 -0
  63. package/dist/src/store/session-store.d.ts +21 -0
  64. package/dist/src/store/session-store.d.ts.map +1 -0
  65. package/dist/src/store/session-store.js +69 -0
  66. package/dist/src/store/tip-store.d.ts +21 -0
  67. package/dist/src/store/tip-store.d.ts.map +1 -0
  68. package/dist/src/store/tip-store.js +60 -0
  69. package/dist/src/store/tool-approval-store.d.ts +44 -0
  70. package/dist/src/store/tool-approval-store.d.ts.map +1 -0
  71. package/dist/src/store/tool-approval-store.js +147 -0
  72. package/dist/src/tools/identity-approve-tool.d.ts +24 -0
  73. package/dist/src/tools/identity-approve-tool.d.ts.map +1 -0
  74. package/dist/src/tools/identity-approve-tool.js +36 -0
  75. package/dist/src/tools/identity-config.d.ts +13 -0
  76. package/dist/src/tools/identity-config.d.ts.map +1 -0
  77. package/dist/src/tools/identity-config.js +18 -0
  78. package/dist/src/tools/identity-fetch.d.ts +21 -0
  79. package/dist/src/tools/identity-fetch.d.ts.map +1 -0
  80. package/dist/src/tools/identity-fetch.js +63 -0
  81. package/dist/src/tools/identity-list-credentials.d.ts +15 -0
  82. package/dist/src/tools/identity-list-credentials.d.ts.map +1 -0
  83. package/dist/src/tools/identity-list-credentials.js +30 -0
  84. package/dist/src/tools/identity-list-risk-patterns.d.ts +13 -0
  85. package/dist/src/tools/identity-list-risk-patterns.d.ts.map +1 -0
  86. package/dist/src/tools/identity-list-risk-patterns.js +23 -0
  87. package/dist/src/tools/identity-list-tips.d.ts +13 -0
  88. package/dist/src/tools/identity-list-tips.d.ts.map +1 -0
  89. package/dist/src/tools/identity-list-tips.js +21 -0
  90. package/dist/src/tools/identity-login.d.ts +14 -0
  91. package/dist/src/tools/identity-login.d.ts.map +1 -0
  92. package/dist/src/tools/identity-login.js +40 -0
  93. package/dist/src/tools/identity-logout.d.ts +13 -0
  94. package/dist/src/tools/identity-logout.d.ts.map +1 -0
  95. package/dist/src/tools/identity-logout.js +24 -0
  96. package/dist/src/tools/identity-risk-check.d.ts +29 -0
  97. package/dist/src/tools/identity-risk-check.d.ts.map +1 -0
  98. package/dist/src/tools/identity-risk-check.js +54 -0
  99. package/dist/src/tools/identity-set-binding.d.ts +16 -0
  100. package/dist/src/tools/identity-set-binding.d.ts.map +1 -0
  101. package/dist/src/tools/identity-set-binding.js +31 -0
  102. package/dist/src/tools/identity-status.d.ts +13 -0
  103. package/dist/src/tools/identity-status.d.ts.map +1 -0
  104. package/dist/src/tools/identity-status.js +41 -0
  105. package/dist/src/tools/identity-unset-binding.d.ts +15 -0
  106. package/dist/src/tools/identity-unset-binding.d.ts.map +1 -0
  107. package/dist/src/tools/identity-unset-binding.js +25 -0
  108. package/dist/src/tools/identity-whoami.d.ts +13 -0
  109. package/dist/src/tools/identity-whoami.d.ts.map +1 -0
  110. package/dist/src/tools/identity-whoami.js +38 -0
  111. package/dist/src/types.d.ts +93 -0
  112. package/dist/src/types.d.ts.map +1 -0
  113. package/dist/src/types.js +5 -0
  114. package/dist/src/utils/approval-channel.d.ts +11 -0
  115. package/dist/src/utils/approval-channel.d.ts.map +1 -0
  116. package/dist/src/utils/approval-channel.js +13 -0
  117. package/dist/src/utils/auth.d.ts +24 -0
  118. package/dist/src/utils/auth.d.ts.map +1 -0
  119. package/dist/src/utils/auth.js +44 -0
  120. package/dist/src/utils/derive-session-key.d.ts +78 -0
  121. package/dist/src/utils/derive-session-key.d.ts.map +1 -0
  122. package/dist/src/utils/derive-session-key.js +198 -0
  123. package/openclaw.plugin.json +162 -0
  124. package/package.json +33 -0
  125. package/skills/SKILL.md +230 -0
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Session token refresh: when userToken expires, use refresh_token to get new tokens.
3
+ * Used by before_agent_start when GetWorkloadAccessTokenForJWT fails with "token has expired".
4
+ */
5
+ import { getSession, setSession } from "../store/session-store.js";
6
+ import { fetchOIDCDiscovery, refreshAccessToken } from "./oidc-client.js";
7
+ /**
8
+ * Refresh session userToken using refresh_token grant.
9
+ * Updates session with new userToken (and rotated refresh_token if returned).
10
+ * Returns the new userToken or null if refresh failed or no refresh token.
11
+ */
12
+ export async function refreshSessionUserToken(storeDir, sessionKey, getOidcConfig) {
13
+ const session = await getSession(storeDir, sessionKey);
14
+ if (!session?.refreshToken)
15
+ return null;
16
+ try {
17
+ const config = await getOidcConfig();
18
+ const discovery = await fetchOIDCDiscovery(config.discoveryUrl);
19
+ const tokens = await refreshAccessToken({
20
+ tokenEndpoint: discovery.token_endpoint,
21
+ clientId: config.clientId,
22
+ clientSecret: config.clientSecret,
23
+ refreshToken: session.refreshToken,
24
+ });
25
+ const userToken = tokens.id_token ?? tokens.access_token;
26
+ const newRefreshToken = tokens.refresh_token ?? session.refreshToken;
27
+ await setSession(storeDir, sessionKey, {
28
+ ...session,
29
+ userToken,
30
+ refreshToken: newRefreshToken,
31
+ expiresAt: Date.now() + (tokens.expires_in ?? 3600) * 1000,
32
+ });
33
+ return userToken;
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Credential-to-env-var bindings, per sessionKey.
3
+ * Used by `identity set <provider> <envVar>` to configure injection of stored credentials.
4
+ * Format: { [sessionKey]: { [provider]: envVar } }.
5
+ */
6
+ /**
7
+ * Get bindings for a session.
8
+ */
9
+ export declare function loadCredentialEnvBindings(storeDir: string, sessionKey?: string | null): Promise<Record<string, string>>;
10
+ /**
11
+ * Load all bindings (for list-tips etc). Returns { sessionKey: { provider: envVar } }.
12
+ */
13
+ export declare function loadAllCredentialEnvBindings(storeDir: string): Promise<Record<string, Record<string, string>>>;
14
+ export declare function setCredentialEnvBinding(storeDir: string, sessionKey: string, provider: string, envVar: string): Promise<void>;
15
+ export declare function deleteCredentialEnvBinding(storeDir: string, sessionKey: string, provider: string): Promise<void>;
16
+ //# sourceMappingURL=credential-env-bindings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-env-bindings.d.ts","sourceRoot":"","sources":["../../../src/store/credential-env-bindings.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA6BH;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,GACzB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAIjC;AAED;;GAEG;AACH,wBAAsB,4BAA4B,CAChD,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAEjD;AAED,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,0BAA0B,CAC9C,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAWf"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Credential-to-env-var bindings, per sessionKey.
3
+ * Used by `identity set <provider> <envVar>` to configure injection of stored credentials.
4
+ * Format: { [sessionKey]: { [provider]: envVar } }.
5
+ */
6
+ import fs from "node:fs/promises";
7
+ import path from "node:path";
8
+ const BINDINGS_FILENAME = "credential-env-bindings.json";
9
+ async function loadBindingsFile(storeDir) {
10
+ const p = path.join(storeDir, BINDINGS_FILENAME);
11
+ try {
12
+ const raw = await fs.readFile(p, "utf-8");
13
+ const parsed = JSON.parse(raw);
14
+ const entries = Object.entries(parsed ?? {}).filter(([, v]) => typeof v === "object" && v !== null && !Array.isArray(v));
15
+ return Object.fromEntries(entries);
16
+ }
17
+ catch {
18
+ return {};
19
+ }
20
+ }
21
+ async function saveBindingsFile(storeDir, data) {
22
+ await fs.mkdir(storeDir, { recursive: true });
23
+ const p = path.join(storeDir, BINDINGS_FILENAME);
24
+ await fs.writeFile(p, JSON.stringify(data, null, 2), "utf-8");
25
+ }
26
+ /**
27
+ * Get bindings for a session.
28
+ */
29
+ export async function loadCredentialEnvBindings(storeDir, sessionKey) {
30
+ if (!sessionKey)
31
+ return {};
32
+ const file = await loadBindingsFile(storeDir);
33
+ return file[sessionKey] ?? {};
34
+ }
35
+ /**
36
+ * Load all bindings (for list-tips etc). Returns { sessionKey: { provider: envVar } }.
37
+ */
38
+ export async function loadAllCredentialEnvBindings(storeDir) {
39
+ return loadBindingsFile(storeDir);
40
+ }
41
+ export async function setCredentialEnvBinding(storeDir, sessionKey, provider, envVar) {
42
+ const file = await loadBindingsFile(storeDir);
43
+ const session = file[sessionKey] ?? {};
44
+ session[provider] = envVar;
45
+ file[sessionKey] = session;
46
+ await saveBindingsFile(storeDir, file);
47
+ }
48
+ export async function deleteCredentialEnvBinding(storeDir, sessionKey, provider) {
49
+ const file = await loadBindingsFile(storeDir);
50
+ const session = file[sessionKey];
51
+ if (!session || !(provider in session))
52
+ return;
53
+ delete session[provider];
54
+ if (Object.keys(session).length === 0) {
55
+ delete file[sessionKey];
56
+ }
57
+ else {
58
+ file[sessionKey] = session;
59
+ }
60
+ await saveBindingsFile(storeDir, file);
61
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Credential store: (sessionKey, provider) -> credential (in-memory only).
3
+ * Credentials are per-session; not persisted; lost on gateway restart.
4
+ */
5
+ export type ApiKeyCredential = {
6
+ type: "api_key";
7
+ key: string;
8
+ value?: string;
9
+ /** When set, resolve value from process.env at read time (do not persist). */
10
+ valueFromEnv?: string;
11
+ };
12
+ export type OAuth2Credential = {
13
+ type: "oauth2";
14
+ status: "pending_auth" | "authenticated" | "expired";
15
+ accessToken?: string;
16
+ refreshToken?: string;
17
+ expiresAt?: number;
18
+ scopes?: string[];
19
+ };
20
+ export type CredentialEntry = ApiKeyCredential | OAuth2Credential;
21
+ export declare function loadCredentials(_storeDir: string, sessionKey?: string): Promise<Record<string, CredentialEntry>>;
22
+ export declare function getCredential(_storeDir: string, sessionKey: string, provider: string): Promise<CredentialEntry | null>;
23
+ /**
24
+ * Resolve the actual value for a credential (for injection).
25
+ * For api_key with valueFromEnv, reads from process.env at runtime.
26
+ */
27
+ export declare function resolveCredentialValue(entry: CredentialEntry): string | undefined;
28
+ export declare function setCredential(_storeDir: string, sessionKey: string, provider: string, entry: CredentialEntry): Promise<void>;
29
+ /** Remove all credentials for a session (e.g. on logout). */
30
+ export declare function deleteCredentialsForSession(_storeDir: string, sessionKey: string): void;
31
+ //# sourceMappingURL=credential-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-store.d.ts","sourceRoot":"","sources":["../../../src/store/credential-store.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,SAAS,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,cAAc,GAAG,eAAe,GAAG,SAAS,CAAC;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;AAUlE,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAW1C;AAED,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAQjC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,GAAG,SAAS,CAOjF;AAED,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,6DAA6D;AAC7D,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAKvF"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Credential store: (sessionKey, provider) -> credential (in-memory only).
3
+ * Credentials are per-session; not persisted; lost on gateway restart.
4
+ */
5
+ const SESSION_PROVIDER_SEP = "\0";
6
+ function credKey(sessionKey, provider) {
7
+ return `${sessionKey}${SESSION_PROVIDER_SEP}${provider}`;
8
+ }
9
+ const credentials = new Map();
10
+ export async function loadCredentials(_storeDir, sessionKey) {
11
+ const all = Object.fromEntries(credentials);
12
+ if (!sessionKey)
13
+ return all;
14
+ const out = {};
15
+ for (const [k, v] of Object.entries(all)) {
16
+ const [, prov] = k.split(SESSION_PROVIDER_SEP);
17
+ if (prov && k.startsWith(sessionKey + SESSION_PROVIDER_SEP)) {
18
+ out[prov] = v;
19
+ }
20
+ }
21
+ return out;
22
+ }
23
+ export async function getCredential(_storeDir, sessionKey, provider) {
24
+ const entry = credentials.get(credKey(sessionKey, provider));
25
+ if (!entry)
26
+ return null;
27
+ if (entry.type === "oauth2" && entry.expiresAt && entry.expiresAt < Date.now()) {
28
+ entry.status = "expired";
29
+ return entry;
30
+ }
31
+ return entry;
32
+ }
33
+ /**
34
+ * Resolve the actual value for a credential (for injection).
35
+ * For api_key with valueFromEnv, reads from process.env at runtime.
36
+ */
37
+ export function resolveCredentialValue(entry) {
38
+ if (entry.type === "oauth2" && entry.accessToken)
39
+ return entry.accessToken;
40
+ if (entry.type === "api_key") {
41
+ if (entry.valueFromEnv)
42
+ return process.env[entry.valueFromEnv];
43
+ return entry.value;
44
+ }
45
+ return undefined;
46
+ }
47
+ export async function setCredential(_storeDir, sessionKey, provider, entry) {
48
+ credentials.set(credKey(sessionKey, provider), entry);
49
+ }
50
+ /** Remove all credentials for a session (e.g. on logout). */
51
+ export function deleteCredentialsForSession(_storeDir, sessionKey) {
52
+ const prefix = sessionKey + SESSION_PROVIDER_SEP;
53
+ for (const k of [...credentials.keys()]) {
54
+ if (k.startsWith(prefix))
55
+ credentials.delete(k);
56
+ }
57
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * OAuth2 state store for OIDC login flow (in-memory only).
3
+ * Stores state -> { sessionKey, redirectUri, deliveryTarget? } with TTL. Lost on gateway restart.
4
+ */
5
+ import type { SessionKeyDeliveryTarget } from "../utils/derive-session-key.js";
6
+ export type OIDCStateEntry = {
7
+ sessionKey: string;
8
+ redirectUri: string;
9
+ createdAt: number;
10
+ /** Delivery target for sending follow-up message; avoids parsing sessionKey. */
11
+ deliveryTarget?: SessionKeyDeliveryTarget | null;
12
+ };
13
+ export declare function createState(_storeDir: string, sessionKey: string, redirectUri: string, state: string, deliveryTarget?: SessionKeyDeliveryTarget | null): Promise<void>;
14
+ export declare function consumeState(_storeDir: string, state: string): Promise<OIDCStateEntry | null>;
15
+ //# sourceMappingURL=oidc-state-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oidc-state-store.d.ts","sourceRoot":"","sources":["../../../src/store/oidc-state-store.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAE/E,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,cAAc,CAAC,EAAE,wBAAwB,GAAG,IAAI,CAAC;CAClD,CAAC;AAYF,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,cAAc,CAAC,EAAE,wBAAwB,GAAG,IAAI,GAC/C,OAAO,CAAC,IAAI,CAAC,CAQf;AAED,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAOhC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * OAuth2 state store for OIDC login flow (in-memory only).
3
+ * Stores state -> { sessionKey, redirectUri, deliveryTarget? } with TTL. Lost on gateway restart.
4
+ */
5
+ const STATE_TTL_MS = 5 * 60 * 1000; // 5 min
6
+ const states = new Map();
7
+ function pruneExpired() {
8
+ const now = Date.now();
9
+ for (const [k, v] of states.entries()) {
10
+ if (now - v.createdAt >= STATE_TTL_MS)
11
+ states.delete(k);
12
+ }
13
+ }
14
+ export async function createState(_storeDir, sessionKey, redirectUri, state, deliveryTarget) {
15
+ pruneExpired();
16
+ states.set(state, {
17
+ sessionKey,
18
+ redirectUri,
19
+ createdAt: Date.now(),
20
+ deliveryTarget: deliveryTarget ?? undefined,
21
+ });
22
+ }
23
+ export async function consumeState(_storeDir, state) {
24
+ pruneExpired();
25
+ const entry = states.get(state);
26
+ states.delete(state);
27
+ if (!entry)
28
+ return null;
29
+ if (Date.now() - entry.createdAt > STATE_TTL_MS)
30
+ return null;
31
+ return entry;
32
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Session store: sessionKey → userToken mapping.
3
+ * Persists to ~/.openclaw/plugins/identity/sessions.json
4
+ * Prunes expired entries on load/save to prevent growth.
5
+ */
6
+ export type SessionEntry = {
7
+ userToken: string;
8
+ sub: string;
9
+ /** OIDC refresh token for silent token renewal. */
10
+ refreshToken?: string;
11
+ claims?: Record<string, unknown>;
12
+ loginAt: number;
13
+ expiresAt?: number;
14
+ };
15
+ export declare function ensureStoreDir(storeDir: string): Promise<void>;
16
+ export declare function loadSessions(storeDir: string): Promise<Record<string, SessionEntry>>;
17
+ export declare function saveSessions(storeDir: string, sessions: Record<string, SessionEntry>): Promise<void>;
18
+ export declare function getSession(storeDir: string, sessionKey: string): Promise<SessionEntry | null>;
19
+ export declare function setSession(storeDir: string, sessionKey: string, entry: SessionEntry): Promise<void>;
20
+ export declare function deleteSession(storeDir: string, sessionKey: string): Promise<void>;
21
+ //# sourceMappingURL=session-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../src/store/session-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAMF,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpE;AAeD,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAc1F;AAED,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GACrC,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAU9B;AAED,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIvF"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Session store: sessionKey → userToken mapping.
3
+ * Persists to ~/.openclaw/plugins/identity/sessions.json
4
+ * Prunes expired entries on load/save to prevent growth.
5
+ */
6
+ import fs from "node:fs/promises";
7
+ import path from "node:path";
8
+ const SESSIONS_FILENAME = "sessions.json";
9
+ /** Max session age when no expiresAt (e.g. legacy entries). Default 30 days. */
10
+ const MAX_SESSION_AGE_MS = 30 * 24 * 60 * 60 * 1000;
11
+ export async function ensureStoreDir(storeDir) {
12
+ await fs.mkdir(storeDir, { recursive: true });
13
+ }
14
+ function pruneExpiredSessions(sessions) {
15
+ const now = Date.now();
16
+ const pruned = {};
17
+ for (const [k, v] of Object.entries(sessions)) {
18
+ if (v.expiresAt != null && v.expiresAt < now)
19
+ continue;
20
+ if (v.expiresAt == null && now - v.loginAt > MAX_SESSION_AGE_MS)
21
+ continue;
22
+ pruned[k] = v;
23
+ }
24
+ return pruned;
25
+ }
26
+ export async function loadSessions(storeDir) {
27
+ const sessionsPath = path.join(storeDir, SESSIONS_FILENAME);
28
+ let sessions;
29
+ try {
30
+ const raw = await fs.readFile(sessionsPath, "utf-8");
31
+ sessions = JSON.parse(raw);
32
+ }
33
+ catch {
34
+ return {};
35
+ }
36
+ const pruned = pruneExpiredSessions(sessions);
37
+ if (Object.keys(pruned).length < Object.keys(sessions).length) {
38
+ await saveSessions(storeDir, pruned);
39
+ }
40
+ return pruned;
41
+ }
42
+ export async function saveSessions(storeDir, sessions) {
43
+ const pruned = pruneExpiredSessions(sessions);
44
+ await ensureStoreDir(storeDir);
45
+ const sessionsPath = path.join(storeDir, SESSIONS_FILENAME);
46
+ await fs.writeFile(sessionsPath, JSON.stringify(pruned, null, 2), "utf-8");
47
+ }
48
+ export async function getSession(storeDir, sessionKey) {
49
+ const sessions = await loadSessions(storeDir);
50
+ const entry = sessions[sessionKey];
51
+ if (!entry)
52
+ return null;
53
+ if (entry.expiresAt && entry.expiresAt < Date.now()) {
54
+ delete sessions[sessionKey];
55
+ await saveSessions(storeDir, sessions);
56
+ return null;
57
+ }
58
+ return entry;
59
+ }
60
+ export async function setSession(storeDir, sessionKey, entry) {
61
+ const sessions = await loadSessions(storeDir);
62
+ sessions[sessionKey] = entry;
63
+ await saveSessions(storeDir, sessions);
64
+ }
65
+ export async function deleteSession(storeDir, sessionKey) {
66
+ const sessions = await loadSessions(storeDir);
67
+ delete sessions[sessionKey];
68
+ await saveSessions(storeDir, sessions);
69
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * TIP (Trusted Identity Provider) token store.
3
+ * Caches sessionKey → TIP token mapping.
4
+ * Persists to ~/.openclaw/plugins/identity/tip-tokens.json
5
+ * Prunes expired entries on load/save to prevent growth.
6
+ */
7
+ export type TIPTokenEntry = {
8
+ token: string;
9
+ sub: string;
10
+ agentId?: string;
11
+ chain?: string[];
12
+ issuedAt: number;
13
+ expiresAt: number;
14
+ parentSessionKey?: string;
15
+ };
16
+ export declare function ensureStoreDir(storeDir: string): Promise<void>;
17
+ export declare function loadTIPTokens(storeDir: string): Promise<Record<string, TIPTokenEntry>>;
18
+ export declare function saveTIPTokens(storeDir: string, tokens: Record<string, TIPTokenEntry>): Promise<void>;
19
+ export declare function getTIPToken(storeDir: string, sessionKey: string): Promise<TIPTokenEntry | null>;
20
+ export declare function setTIPToken(storeDir: string, sessionKey: string, entry: TIPTokenEntry): Promise<void>;
21
+ //# sourceMappingURL=tip-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tip-store.d.ts","sourceRoot":"","sources":["../../../src/store/tip-store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAIF,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpE;AAaD,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAc5F;AAED,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GACpC,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAU/B;AAED,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC,IAAI,CAAC,CAIf"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * TIP (Trusted Identity Provider) token store.
3
+ * Caches sessionKey → TIP token mapping.
4
+ * Persists to ~/.openclaw/plugins/identity/tip-tokens.json
5
+ * Prunes expired entries on load/save to prevent growth.
6
+ */
7
+ import fs from "node:fs/promises";
8
+ import path from "node:path";
9
+ const TIP_TOKENS_FILENAME = "tip-tokens.json";
10
+ export async function ensureStoreDir(storeDir) {
11
+ await fs.mkdir(storeDir, { recursive: true });
12
+ }
13
+ function pruneExpiredTIPTokens(tokens) {
14
+ const now = Date.now();
15
+ const pruned = {};
16
+ for (const [k, v] of Object.entries(tokens)) {
17
+ if (v.expiresAt >= now)
18
+ pruned[k] = v;
19
+ }
20
+ return pruned;
21
+ }
22
+ export async function loadTIPTokens(storeDir) {
23
+ const tipPath = path.join(storeDir, TIP_TOKENS_FILENAME);
24
+ let tokens;
25
+ try {
26
+ const raw = await fs.readFile(tipPath, "utf-8");
27
+ tokens = JSON.parse(raw);
28
+ }
29
+ catch {
30
+ return {};
31
+ }
32
+ const pruned = pruneExpiredTIPTokens(tokens);
33
+ if (Object.keys(pruned).length < Object.keys(tokens).length) {
34
+ await saveTIPTokens(storeDir, pruned);
35
+ }
36
+ return pruned;
37
+ }
38
+ export async function saveTIPTokens(storeDir, tokens) {
39
+ const pruned = pruneExpiredTIPTokens(tokens);
40
+ await ensureStoreDir(storeDir);
41
+ const tipPath = path.join(storeDir, TIP_TOKENS_FILENAME);
42
+ await fs.writeFile(tipPath, JSON.stringify(pruned, null, 2), "utf-8");
43
+ }
44
+ export async function getTIPToken(storeDir, sessionKey) {
45
+ const tokens = await loadTIPTokens(storeDir);
46
+ const entry = tokens[sessionKey];
47
+ if (!entry)
48
+ return null;
49
+ if (entry.expiresAt < Date.now()) {
50
+ delete tokens[sessionKey];
51
+ await saveTIPTokens(storeDir, tokens);
52
+ return null;
53
+ }
54
+ return entry;
55
+ }
56
+ export async function setTIPToken(storeDir, sessionKey, entry) {
57
+ const tokens = await loadTIPTokens(storeDir);
58
+ tokens[sessionKey] = entry;
59
+ await saveTIPTokens(storeDir, tokens);
60
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * In-memory store for tool approval flow.
3
+ * Pending: awaiting user approval. Approval: recorded for retry-path allow.
4
+ */
5
+ export type PendingEntry = {
6
+ approvalId: string;
7
+ sessionKey: string;
8
+ toolName: string;
9
+ params: Record<string, unknown>;
10
+ expiresAtMs: number;
11
+ };
12
+ /**
13
+ * Hash for tool+params, used as approval key and short approval id.
14
+ */
15
+ export declare function hashToolParams(toolName: string, params: Record<string, unknown>): string;
16
+ /**
17
+ * Short id for approval messages (first 8 chars of hash).
18
+ */
19
+ export declare function shortApprovalId(fullHash: string): string;
20
+ export declare function createPending(params: {
21
+ approvalId: string;
22
+ sessionKey: string;
23
+ toolName: string;
24
+ params: Record<string, unknown>;
25
+ ttlMs: number;
26
+ }): void;
27
+ export declare function getPending(approvalId: string): PendingEntry | undefined;
28
+ /**
29
+ * Approve a pending request. Records approval for retry-path and poll-path; removes from pending.
30
+ * When approverSessionKey is provided, verifies it matches the pending entry's sessionKey.
31
+ */
32
+ export declare function approve(approvalId: string, ttlMs: number, approverSessionKey?: string | null): boolean;
33
+ /**
34
+ * Reject a pending request. When rejecterSessionKey is provided, verifies it matches.
35
+ */
36
+ export declare function reject(approvalId: string, rejecterSessionKey?: string | null): boolean;
37
+ export declare function hasRecentApproval(sessionKey: string, toolName: string, params: Record<string, unknown>): boolean;
38
+ export declare function consumeApproval(sessionKey: string, toolName: string, params: Record<string, unknown>): boolean;
39
+ export declare function getPendingForSession(sessionKey: string): PendingEntry[];
40
+ /**
41
+ * Poll until approved or timeout.
42
+ */
43
+ export declare function pollForApproval(approvalId: string, timeoutMs: number, onCheck?: () => void): Promise<boolean>;
44
+ //# sourceMappingURL=tool-approval-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-approval-store.d.ts","sourceRoot":"","sources":["../../../src/store/tool-approval-store.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAoBF;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAGxF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAExD;AAaD,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,IAAI,CAUP;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAOvE;AAED;;;GAGG;AACH,wBAAgB,OAAO,CACrB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,GACjC,OAAO,CAkBT;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAatF;AAED,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAQT;AAED,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAST;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,EAAE,CASvE;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,IAAI,GACnB,OAAO,CAAC,OAAO,CAAC,CAwBlB"}
@@ -0,0 +1,147 @@
1
+ /**
2
+ * In-memory store for tool approval flow.
3
+ * Pending: awaiting user approval. Approval: recorded for retry-path allow.
4
+ */
5
+ import { createHash } from "node:crypto";
6
+ const POLL_INTERVAL_MS = 500;
7
+ const pendingByApprovalId = new Map();
8
+ const approvalKeys = new Map(); // key -> expiresAtMs (for retry-path)
9
+ const approvedById = new Map(); // approvalId -> expiresAtMs (for poll-path)
10
+ /**
11
+ * Canonical JSON for stable hashing (sorted keys, no functions).
12
+ */
13
+ function canonicalJson(obj) {
14
+ if (obj === null || obj === undefined)
15
+ return "null";
16
+ if (typeof obj !== "object")
17
+ return JSON.stringify(obj);
18
+ if (Array.isArray(obj))
19
+ return "[" + obj.map(canonicalJson).join(",") + "]";
20
+ const keys = Object.keys(obj).sort();
21
+ const pairs = keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(obj[k])}`);
22
+ return "{" + pairs.join(",") + "}";
23
+ }
24
+ /**
25
+ * Hash for tool+params, used as approval key and short approval id.
26
+ */
27
+ export function hashToolParams(toolName, params) {
28
+ const payload = `${toolName}:${canonicalJson(params)}`;
29
+ return createHash("sha256").update(payload, "utf-8").digest("hex");
30
+ }
31
+ /**
32
+ * Short id for approval messages (first 8 chars of hash).
33
+ */
34
+ export function shortApprovalId(fullHash) {
35
+ return fullHash.slice(0, 8);
36
+ }
37
+ /**
38
+ * Approval key for hasRecentApproval lookup.
39
+ */
40
+ function approvalKey(sessionKey, toolName, params) {
41
+ return `${sessionKey}:${hashToolParams(toolName, params)}`;
42
+ }
43
+ export function createPending(params) {
44
+ const { approvalId, sessionKey, toolName, params: p, ttlMs } = params;
45
+ const expiresAtMs = Date.now() + ttlMs;
46
+ pendingByApprovalId.set(approvalId, {
47
+ approvalId,
48
+ sessionKey,
49
+ toolName,
50
+ params: p,
51
+ expiresAtMs,
52
+ });
53
+ }
54
+ export function getPending(approvalId) {
55
+ const entry = pendingByApprovalId.get(approvalId);
56
+ if (!entry || Date.now() > entry.expiresAtMs) {
57
+ pendingByApprovalId.delete(approvalId);
58
+ return undefined;
59
+ }
60
+ return entry;
61
+ }
62
+ /**
63
+ * Approve a pending request. Records approval for retry-path and poll-path; removes from pending.
64
+ * When approverSessionKey is provided, verifies it matches the pending entry's sessionKey.
65
+ */
66
+ export function approve(approvalId, ttlMs, approverSessionKey) {
67
+ const entry = getPending(approvalId);
68
+ if (!entry)
69
+ return false;
70
+ if (approverSessionKey != null &&
71
+ approverSessionKey.trim() !== "" &&
72
+ entry.sessionKey !== approverSessionKey) {
73
+ return false;
74
+ }
75
+ const expiresAtMs = Date.now() + ttlMs;
76
+ pendingByApprovalId.delete(approvalId);
77
+ const key = approvalKey(entry.sessionKey, entry.toolName, entry.params);
78
+ approvalKeys.set(key, expiresAtMs);
79
+ approvedById.set(approvalId, expiresAtMs);
80
+ return true;
81
+ }
82
+ /**
83
+ * Reject a pending request. When rejecterSessionKey is provided, verifies it matches.
84
+ */
85
+ export function reject(approvalId, rejecterSessionKey) {
86
+ const entry = getPending(approvalId);
87
+ if (!entry)
88
+ return false;
89
+ if (rejecterSessionKey != null &&
90
+ rejecterSessionKey.trim() !== "" &&
91
+ entry.sessionKey !== rejecterSessionKey) {
92
+ return false;
93
+ }
94
+ return pendingByApprovalId.delete(approvalId);
95
+ }
96
+ export function hasRecentApproval(sessionKey, toolName, params) {
97
+ const key = approvalKey(sessionKey, toolName, params);
98
+ const expiresAt = approvalKeys.get(key);
99
+ if (!expiresAt || Date.now() > expiresAt) {
100
+ approvalKeys.delete(key);
101
+ return false;
102
+ }
103
+ return true;
104
+ }
105
+ export function consumeApproval(sessionKey, toolName, params) {
106
+ const key = approvalKey(sessionKey, toolName, params);
107
+ const expiresAt = approvalKeys.get(key);
108
+ if (!expiresAt || Date.now() > expiresAt) {
109
+ approvalKeys.delete(key);
110
+ return false;
111
+ }
112
+ approvalKeys.delete(key);
113
+ return true;
114
+ }
115
+ export function getPendingForSession(sessionKey) {
116
+ const now = Date.now();
117
+ const result = [];
118
+ for (const entry of pendingByApprovalId.values()) {
119
+ if (entry.sessionKey === sessionKey && entry.expiresAtMs > now) {
120
+ result.push(entry);
121
+ }
122
+ }
123
+ return result;
124
+ }
125
+ /**
126
+ * Poll until approved or timeout.
127
+ */
128
+ export async function pollForApproval(approvalId, timeoutMs, onCheck) {
129
+ const deadline = Date.now() + timeoutMs;
130
+ while (Date.now() < deadline) {
131
+ onCheck?.();
132
+ if (approvedById.has(approvalId)) {
133
+ const expiresAt = approvedById.get(approvalId);
134
+ if (Date.now() <= expiresAt) {
135
+ approvedById.delete(approvalId);
136
+ return true;
137
+ }
138
+ approvedById.delete(approvalId);
139
+ }
140
+ const entry = getPending(approvalId);
141
+ if (!entry) {
142
+ return false;
143
+ }
144
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
145
+ }
146
+ return false;
147
+ }