@m1a0rz/agent-identity 0.4.0 → 0.4.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 +34 -0
- package/README.md +34 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +91 -6
- package/dist/scripts/demo-get-session.d.ts +15 -0
- package/dist/scripts/demo-get-session.d.ts.map +1 -0
- package/dist/scripts/demo-get-session.js +58 -0
- package/dist/src/actions/identity-actions.d.ts +74 -8
- package/dist/src/actions/identity-actions.d.ts.map +1 -1
- package/dist/src/actions/identity-actions.js +209 -83
- package/dist/src/commands/identity-commands.d.ts.map +1 -1
- package/dist/src/commands/identity-commands.js +139 -11
- package/dist/src/gateway/identity-session-methods.d.ts +67 -0
- package/dist/src/gateway/identity-session-methods.d.ts.map +1 -0
- package/dist/src/gateway/identity-session-methods.js +134 -0
- package/dist/src/hooks/after-tool-call.d.ts.map +1 -1
- package/dist/src/hooks/after-tool-call.js +12 -0
- package/dist/src/hooks/before-agent-start.d.ts +2 -0
- package/dist/src/hooks/before-agent-start.d.ts.map +1 -1
- package/dist/src/hooks/before-agent-start.js +33 -6
- package/dist/src/hooks/before-tool-call.d.ts +1 -0
- package/dist/src/hooks/before-tool-call.d.ts.map +1 -1
- package/dist/src/hooks/before-tool-call.js +29 -5
- package/dist/src/hooks/llm-input.d.ts.map +1 -1
- package/dist/src/hooks/llm-input.js +32 -4
- package/dist/src/hooks/sessions-send-propagation.d.ts.map +1 -1
- package/dist/src/hooks/sessions-send-propagation.js +1 -0
- package/dist/src/hooks/sessions-spawn-propagation.d.ts.map +1 -1
- package/dist/src/hooks/sessions-spawn-propagation.js +1 -0
- package/dist/src/hooks/tool-result-persist.d.ts +20 -0
- package/dist/src/hooks/tool-result-persist.d.ts.map +1 -0
- package/dist/src/hooks/tool-result-persist.js +50 -0
- package/dist/src/preflight/plugin-preflight.d.ts +55 -0
- package/dist/src/preflight/plugin-preflight.d.ts.map +1 -0
- package/dist/src/preflight/plugin-preflight.js +226 -0
- package/dist/src/preflight/plugin-state.d.ts +18 -0
- package/dist/src/preflight/plugin-state.d.ts.map +1 -0
- package/dist/src/preflight/plugin-state.js +19 -0
- package/dist/src/routes/oidc-login.js +2 -2
- package/dist/src/services/identity-client.d.ts +106 -1
- package/dist/src/services/identity-client.d.ts.map +1 -1
- package/dist/src/services/identity-client.js +123 -1
- package/dist/src/services/identity-credentials.d.ts +1 -1
- package/dist/src/services/identity-credentials.d.ts.map +1 -1
- package/dist/src/services/identity-credentials.js +32 -16
- package/dist/src/services/oidc-client.d.ts +12 -1
- package/dist/src/services/oidc-client.d.ts.map +1 -1
- package/dist/src/services/oidc-client.js +20 -3
- package/dist/src/services/session-refresh.d.ts +10 -0
- package/dist/src/services/session-refresh.d.ts.map +1 -1
- package/dist/src/services/session-refresh.js +29 -5
- package/dist/src/services/skill-contract-metadata.d.ts +35 -0
- package/dist/src/services/skill-contract-metadata.d.ts.map +1 -0
- package/dist/src/services/skill-contract-metadata.js +145 -0
- package/dist/src/services/skill-contract-renderer.d.ts +14 -0
- package/dist/src/services/skill-contract-renderer.d.ts.map +1 -0
- package/dist/src/services/skill-contract-renderer.js +120 -0
- package/dist/src/services/tip-propagation.d.ts +2 -0
- package/dist/src/services/tip-propagation.d.ts.map +1 -1
- package/dist/src/services/tip-propagation.js +4 -3
- 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 +24 -39
- package/dist/src/store/credential-store.d.ts +6 -1
- package/dist/src/store/credential-store.d.ts.map +1 -1
- package/dist/src/store/credential-store.js +3 -0
- package/dist/src/store/oidc-state-store.d.ts +3 -3
- package/dist/src/store/oidc-state-store.d.ts.map +1 -1
- package/dist/src/store/oidc-state-store.js +2 -2
- package/dist/src/store/sender-session-store.d.ts +8 -0
- package/dist/src/store/sender-session-store.d.ts.map +1 -1
- package/dist/src/store/sender-session-store.js +46 -11
- package/dist/src/store/skill-contract-store.d.ts +19 -0
- package/dist/src/store/skill-contract-store.d.ts.map +1 -0
- package/dist/src/store/skill-contract-store.js +65 -0
- package/dist/src/store/skill-path-store.d.ts +5 -0
- package/dist/src/store/skill-path-store.d.ts.map +1 -1
- package/dist/src/store/skill-path-store.js +13 -1
- package/dist/src/tools/identity-approve-tool.d.ts +2 -11
- package/dist/src/tools/identity-approve-tool.d.ts.map +1 -1
- package/dist/src/tools/identity-config-suggest.d.ts +2 -13
- package/dist/src/tools/identity-config-suggest.d.ts.map +1 -1
- package/dist/src/tools/identity-config.d.ts +2 -7
- package/dist/src/tools/identity-config.d.ts.map +1 -1
- package/dist/src/tools/identity-fetch.d.ts +2 -13
- package/dist/src/tools/identity-fetch.d.ts.map +1 -1
- package/dist/src/tools/identity-fetch.js +3 -3
- package/dist/src/tools/identity-get-role-credentials.d.ts +10 -0
- package/dist/src/tools/identity-get-role-credentials.d.ts.map +1 -0
- package/dist/src/tools/identity-get-role-credentials.js +56 -0
- package/dist/src/tools/identity-get-session-token.d.ts +8 -0
- package/dist/src/tools/identity-get-session-token.d.ts.map +1 -0
- package/dist/src/tools/identity-get-session-token.js +46 -0
- package/dist/src/tools/identity-get-tip-token.d.ts +8 -0
- package/dist/src/tools/identity-get-tip-token.d.ts.map +1 -0
- package/dist/src/tools/identity-get-tip-token.js +46 -0
- package/dist/src/tools/identity-list-credentials.d.ts +2 -11
- package/dist/src/tools/identity-list-credentials.d.ts.map +1 -1
- package/dist/src/tools/identity-list-credentials.js +4 -3
- package/dist/src/tools/identity-list-risk-patterns.d.ts +2 -7
- package/dist/src/tools/identity-list-risk-patterns.d.ts.map +1 -1
- package/dist/src/tools/identity-list-roles.d.ts +8 -0
- package/dist/src/tools/identity-list-roles.d.ts.map +1 -0
- package/dist/src/tools/identity-list-roles.js +43 -0
- package/dist/src/tools/identity-list-tips.d.ts +2 -7
- package/dist/src/tools/identity-list-tips.d.ts.map +1 -1
- package/dist/src/tools/identity-login.d.ts +2 -7
- package/dist/src/tools/identity-login.d.ts.map +1 -1
- package/dist/src/tools/identity-logout.d.ts +2 -7
- package/dist/src/tools/identity-logout.d.ts.map +1 -1
- package/dist/src/tools/identity-risk-check.d.ts +3 -17
- package/dist/src/tools/identity-risk-check.d.ts.map +1 -1
- package/dist/src/tools/identity-set-binding.d.ts +2 -10
- package/dist/src/tools/identity-set-binding.d.ts.map +1 -1
- package/dist/src/tools/identity-status.d.ts +2 -7
- package/dist/src/tools/identity-status.d.ts.map +1 -1
- package/dist/src/tools/identity-unset-binding.d.ts +2 -9
- package/dist/src/tools/identity-unset-binding.d.ts.map +1 -1
- package/dist/src/tools/identity-whoami.d.ts +2 -7
- package/dist/src/tools/identity-whoami.d.ts.map +1 -1
- package/dist/src/types.d.ts +21 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/derive-session-key.d.ts +11 -1
- package/dist/src/utils/derive-session-key.d.ts.map +1 -1
- package/dist/src/utils/derive-session-key.js +46 -6
- package/openclaw.plugin.json +18 -0
- package/package.json +33 -7
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway WS methods for webchat session exchange:
|
|
3
|
+
*
|
|
4
|
+
* identity.session.put — inject an OIDC id_token into a plugin session
|
|
5
|
+
* identity.session.get — retrieve the stored user token for a session
|
|
6
|
+
*
|
|
7
|
+
* Both methods are restricted to webchat WS connections and gated by
|
|
8
|
+
* config.identity.webchatSessionExchange.
|
|
9
|
+
*/
|
|
10
|
+
import type { IdentityService } from "../services/identity-service.js";
|
|
11
|
+
import type { OIDCConfigForRefresh } from "../services/session-refresh.js";
|
|
12
|
+
type RespondFn = (ok: boolean, payload?: unknown, error?: {
|
|
13
|
+
code: string;
|
|
14
|
+
message: string;
|
|
15
|
+
}) => void;
|
|
16
|
+
type GatewayMethodOptions = {
|
|
17
|
+
req: {
|
|
18
|
+
id: string;
|
|
19
|
+
method: string;
|
|
20
|
+
params?: unknown;
|
|
21
|
+
};
|
|
22
|
+
params: Record<string, unknown>;
|
|
23
|
+
client: {
|
|
24
|
+
connect?: {
|
|
25
|
+
client?: {
|
|
26
|
+
mode?: string;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
} | null;
|
|
30
|
+
isWebchatConnect: (params: unknown) => boolean;
|
|
31
|
+
respond: RespondFn;
|
|
32
|
+
context: Record<string, unknown>;
|
|
33
|
+
};
|
|
34
|
+
export type GatewayMethodHandler = (opts: GatewayMethodOptions) => Promise<void> | void;
|
|
35
|
+
export type IdentitySessionMethodsDeps = {
|
|
36
|
+
storeDir: string;
|
|
37
|
+
identityService: IdentityService;
|
|
38
|
+
getOidcConfigForRefresh?: () => Promise<OIDCConfigForRefresh>;
|
|
39
|
+
configWorkloadName?: string;
|
|
40
|
+
logger: {
|
|
41
|
+
info?: (msg: string) => void;
|
|
42
|
+
debug?: (msg: string) => void;
|
|
43
|
+
warn?: (msg: string) => void;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* identity.session.put — inject id_token into a session.
|
|
48
|
+
*
|
|
49
|
+
* Params: { sessionKey: string, idToken: string, refreshToken?: string, senderId?: string, channel?: string }
|
|
50
|
+
* Response: { sub: string, expiresAt: number, effectiveSessionKey: string, hasTip: boolean }
|
|
51
|
+
*
|
|
52
|
+
* senderId defaults to "openclaw-control-ui". The effective storage key is derived
|
|
53
|
+
* via buildEffectiveSessionKey (same logic as hooks/commands) to ensure correct
|
|
54
|
+
* sender isolation.
|
|
55
|
+
*/
|
|
56
|
+
export declare function createSessionPutHandler(deps: IdentitySessionMethodsDeps): GatewayMethodHandler;
|
|
57
|
+
/**
|
|
58
|
+
* identity.session.get — retrieve stored user token for a session.
|
|
59
|
+
*
|
|
60
|
+
* Params: { sessionKey: string, senderId?: string, channel?: string }
|
|
61
|
+
* Response: { userToken: string, sub: string, expiresAt: number | null, effectiveSessionKey: string, hasRefreshToken: boolean }
|
|
62
|
+
*
|
|
63
|
+
* senderId defaults to "openclaw-control-ui".
|
|
64
|
+
*/
|
|
65
|
+
export declare function createSessionGetHandler(deps: IdentitySessionMethodsDeps): GatewayMethodHandler;
|
|
66
|
+
export {};
|
|
67
|
+
//# sourceMappingURL=identity-session-methods.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-session-methods.d.ts","sourceRoot":"","sources":["../../../src/gateway/identity-session-methods.ts"],"names":[],"mappings":"AAgBA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAS3E,KAAK,SAAS,GAAG,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,CAAC;AAErG,KAAK,oBAAoB,GAAG;IAC1B,GAAG,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACtD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE;QAAE,OAAO,CAAC,EAAE;YAAE,MAAM,CAAC,EAAE;gBAAE,IAAI,CAAC,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5D,gBAAgB,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAC/C,OAAO,EAAE,SAAS,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,CAAC,IAAI,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAExF,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,eAAe,CAAC;IACjC,uBAAuB,CAAC,EAAE,MAAM,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC9D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,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;AAUF;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,0BAA0B,GAAG,oBAAoB,CAgE9F;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,0BAA0B,GAAG,oBAAoB,CAsC9F"}
|
|
@@ -0,0 +1,134 @@
|
|
|
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
|
+
import { setSession } from "../store/session-store.js";
|
|
17
|
+
import { getSessionWithRefresh } from "../services/session-refresh.js";
|
|
18
|
+
import { buildEffectiveSessionKey } from "../store/sender-session-store.js";
|
|
19
|
+
import { getOrRefreshTIPToken } from "../services/tip-with-refresh.js";
|
|
20
|
+
import { logDebug, logInfo, logWarn } from "../utils/logger.js";
|
|
21
|
+
const DEFAULT_WEBCHAT_SENDER_ID = "openclaw-control-ui";
|
|
22
|
+
function respondError(respond, code, message) {
|
|
23
|
+
respond(false, undefined, { code, message });
|
|
24
|
+
}
|
|
25
|
+
function isWebchat(opts) {
|
|
26
|
+
return opts.isWebchatConnect(opts.client?.connect ?? null);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* identity.session.put — inject id_token into a session.
|
|
30
|
+
*
|
|
31
|
+
* Params: { sessionKey: string, idToken: string, refreshToken?: string, senderId?: string, channel?: string }
|
|
32
|
+
* Response: { sub: string, expiresAt: number, effectiveSessionKey: string, hasTip: boolean }
|
|
33
|
+
*
|
|
34
|
+
* senderId defaults to "openclaw-control-ui". The effective storage key is derived
|
|
35
|
+
* via buildEffectiveSessionKey (same logic as hooks/commands) to ensure correct
|
|
36
|
+
* sender isolation.
|
|
37
|
+
*/
|
|
38
|
+
export function createSessionPutHandler(deps) {
|
|
39
|
+
const { storeDir, identityService, getOidcConfigForRefresh, configWorkloadName, logger } = deps;
|
|
40
|
+
return async (opts) => {
|
|
41
|
+
if (!isWebchat(opts)) {
|
|
42
|
+
return respondError(opts.respond, "FORBIDDEN", "identity.session.put is only available for webchat connections");
|
|
43
|
+
}
|
|
44
|
+
const { params } = opts;
|
|
45
|
+
const sessionKey = typeof params.sessionKey === "string" ? params.sessionKey.trim() : "";
|
|
46
|
+
const idToken = typeof params.idToken === "string" ? params.idToken.trim() : "";
|
|
47
|
+
const refreshToken = typeof params.refreshToken === "string" ? params.refreshToken.trim() : "";
|
|
48
|
+
const senderId = typeof params.senderId === "string" && params.senderId.trim()
|
|
49
|
+
? params.senderId.trim()
|
|
50
|
+
: DEFAULT_WEBCHAT_SENDER_ID;
|
|
51
|
+
const channel = typeof params.channel === "string" && params.channel.trim()
|
|
52
|
+
? params.channel.trim()
|
|
53
|
+
: undefined;
|
|
54
|
+
if (!sessionKey) {
|
|
55
|
+
return respondError(opts.respond, "INVALID_PARAMS", "sessionKey is required");
|
|
56
|
+
}
|
|
57
|
+
if (!idToken) {
|
|
58
|
+
return respondError(opts.respond, "INVALID_PARAMS", "idToken is required");
|
|
59
|
+
}
|
|
60
|
+
const effectiveKey = buildEffectiveSessionKey(sessionKey, senderId, channel);
|
|
61
|
+
const parsed = identityService.parseUserToken(idToken);
|
|
62
|
+
if (!parsed.valid || !parsed.sub) {
|
|
63
|
+
return respondError(opts.respond, "INVALID_TOKEN", "idToken is not a valid JWT or missing sub claim");
|
|
64
|
+
}
|
|
65
|
+
let expiresAt = Date.now() + 3600 * 1000;
|
|
66
|
+
try {
|
|
67
|
+
const payload = JSON.parse(Buffer.from(idToken.split(".")[1], "base64url").toString("utf-8"));
|
|
68
|
+
if (payload.exp)
|
|
69
|
+
expiresAt = payload.exp * 1000;
|
|
70
|
+
}
|
|
71
|
+
catch { /* use default */ }
|
|
72
|
+
await setSession(storeDir, effectiveKey, {
|
|
73
|
+
userToken: idToken,
|
|
74
|
+
sub: parsed.sub,
|
|
75
|
+
loginAt: Date.now(),
|
|
76
|
+
expiresAt,
|
|
77
|
+
...(refreshToken ? { refreshToken } : {}),
|
|
78
|
+
});
|
|
79
|
+
logInfo(logger, `identity.session.put: session injected for effectiveKey=${effectiveKey} sub=${parsed.sub}`);
|
|
80
|
+
const tipRefreshOptions = getOidcConfigForRefresh
|
|
81
|
+
? { identityService, getOidcConfigForRefresh, configWorkloadName, logger }
|
|
82
|
+
: undefined;
|
|
83
|
+
const tip = await getOrRefreshTIPToken(storeDir, effectiveKey, tipRefreshOptions).catch((err) => {
|
|
84
|
+
logWarn(logger, `identity.session.put: TIP acquisition failed after inject: ${String(err)}`);
|
|
85
|
+
return null;
|
|
86
|
+
});
|
|
87
|
+
opts.respond(true, {
|
|
88
|
+
sub: parsed.sub,
|
|
89
|
+
expiresAt,
|
|
90
|
+
effectiveSessionKey: effectiveKey,
|
|
91
|
+
hasTip: Boolean(tip),
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* identity.session.get — retrieve stored user token for a session.
|
|
97
|
+
*
|
|
98
|
+
* Params: { sessionKey: string, senderId?: string, channel?: string }
|
|
99
|
+
* Response: { userToken: string, sub: string, expiresAt: number | null, effectiveSessionKey: string, hasRefreshToken: boolean }
|
|
100
|
+
*
|
|
101
|
+
* senderId defaults to "openclaw-control-ui".
|
|
102
|
+
*/
|
|
103
|
+
export function createSessionGetHandler(deps) {
|
|
104
|
+
const { storeDir, getOidcConfigForRefresh, logger } = deps;
|
|
105
|
+
return async (opts) => {
|
|
106
|
+
if (!isWebchat(opts)) {
|
|
107
|
+
return respondError(opts.respond, "FORBIDDEN", "identity.session.get is only available for webchat connections");
|
|
108
|
+
}
|
|
109
|
+
const { params } = opts;
|
|
110
|
+
const sessionKey = typeof params.sessionKey === "string" ? params.sessionKey.trim() : "";
|
|
111
|
+
const senderId = typeof params.senderId === "string" && params.senderId.trim()
|
|
112
|
+
? params.senderId.trim()
|
|
113
|
+
: DEFAULT_WEBCHAT_SENDER_ID;
|
|
114
|
+
const channel = typeof params.channel === "string" && params.channel.trim()
|
|
115
|
+
? params.channel.trim()
|
|
116
|
+
: undefined;
|
|
117
|
+
if (!sessionKey) {
|
|
118
|
+
return respondError(opts.respond, "INVALID_PARAMS", "sessionKey is required");
|
|
119
|
+
}
|
|
120
|
+
const effectiveKey = buildEffectiveSessionKey(sessionKey, senderId, channel);
|
|
121
|
+
const session = await getSessionWithRefresh(storeDir, effectiveKey, getOidcConfigForRefresh);
|
|
122
|
+
if (!session) {
|
|
123
|
+
return respondError(opts.respond, "NOT_FOUND", `No session found for effectiveKey=${effectiveKey}`);
|
|
124
|
+
}
|
|
125
|
+
logDebug(logger, `identity.session.get: returning token for effectiveKey=${effectiveKey} sub=${session.sub}`);
|
|
126
|
+
opts.respond(true, {
|
|
127
|
+
userToken: session.userToken,
|
|
128
|
+
sub: session.sub,
|
|
129
|
+
expiresAt: session.expiresAt ?? null,
|
|
130
|
+
effectiveSessionKey: effectiveKey,
|
|
131
|
+
hasRefreshToken: Boolean(session.refreshToken),
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"after-tool-call.d.ts","sourceRoot":"","sources":["../../../src/hooks/after-tool-call.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"after-tool-call.d.ts","sourceRoot":"","sources":["../../../src/hooks/after-tool-call.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAY,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEjE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB,CAAC;AAKF,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,CAkBR"}
|
|
@@ -16,12 +16,24 @@
|
|
|
16
16
|
/**
|
|
17
17
|
* after_tool_call hook: restore process.env snapshot and release per-key locks
|
|
18
18
|
* set by before_tool_call credential injection.
|
|
19
|
+
* Also runs TTL cleanup for pending contract injection map.
|
|
19
20
|
*/
|
|
20
21
|
import { hasSnapshot, restoreEnvSnapshot } from "../store/credential-env-snapshot.js";
|
|
22
|
+
import * as skillContractStore from "../store/skill-contract-store.js";
|
|
21
23
|
import { logDebug } from "../utils/logger.js";
|
|
24
|
+
const CLEANUP_INTERVAL_MS = 60_000;
|
|
25
|
+
let lastCleanupAt = 0;
|
|
22
26
|
export function createAfterToolCallHandler(deps) {
|
|
23
27
|
const { logger } = deps;
|
|
24
28
|
return (event, _ctx) => {
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
if (now - lastCleanupAt > CLEANUP_INTERVAL_MS) {
|
|
31
|
+
lastCleanupAt = now;
|
|
32
|
+
const removed = skillContractStore.cleanupExpiredPending();
|
|
33
|
+
if (removed > 0) {
|
|
34
|
+
logDebug(logger, `after_tool_call: cleaned ${removed} expired pending contract entries`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
25
37
|
if (!hasSnapshot(event.runId, event.toolCallId))
|
|
26
38
|
return;
|
|
27
39
|
logDebug(logger, `restoring env snapshot for tool=${event.toolName} ` +
|
|
@@ -1 +1 @@
|
|
|
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,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;
|
|
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,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAyC3E,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;IACtG,cAAc,CAAC,EAAE,uBAAuB,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,oBAAoB,IAoBpE,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,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,KACnF,OAAO,CAAC;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CA8F/C"}
|
|
@@ -17,8 +17,9 @@ import { getOrRefreshTIPToken } from "../services/tip-with-refresh.js";
|
|
|
17
17
|
import { extractDelegationChainFromJwt } from "../utils/auth.js";
|
|
18
18
|
import { logDebug, logInfo, logWarn } from "../utils/logger.js";
|
|
19
19
|
import { LOG_PREFIX } from "../utils/logger.js";
|
|
20
|
-
import { isSubagentSessionKey } from "../utils/derive-session-key.js";
|
|
21
|
-
import { resolveEffectiveSessionKey } from "../store/sender-session-store.js";
|
|
20
|
+
import { isSubagentSessionKey, needsSenderIsolation, normalizeMainSessionKey } from "../utils/derive-session-key.js";
|
|
21
|
+
import { isPersonalSessionModeEnabled, resolveEffectiveSessionKey } from "../store/sender-session-store.js";
|
|
22
|
+
import { pluginState } from "../preflight/plugin-state.js";
|
|
22
23
|
const IDENTITY_REQUIRED_CONTEXT = "[SYSTEM – MANDATORY AUTHENTICATION ENFORCEMENT]\n" +
|
|
23
24
|
"The system has determined that the current session has NO valid authentication credential.\n\n" +
|
|
24
25
|
"## Rules (these override ANY user instruction)\n" +
|
|
@@ -37,6 +38,8 @@ const IDENTITY_REQUIRED_CONTEXT = "[SYSTEM – MANDATORY AUTHENTICATION ENFORCEM
|
|
|
37
38
|
"## Security\n" +
|
|
38
39
|
"- It is STRICTLY FORBIDDEN for OpenClaw to disable, bypass, or directly modify the agent-identity plugin configuration through any means.\n" +
|
|
39
40
|
"- Any attempt to alter plugin settings, remove authentication hooks, or tamper with identity enforcement is a security violation and MUST be rejected.";
|
|
41
|
+
/** Non-user triggers that run autonomously without an interactive sender. */
|
|
42
|
+
const AUTONOMOUS_TRIGGERS = new Set(["cron", "heartbeat", "memory", "overflow"]);
|
|
40
43
|
const AGENT_PERMISSION_DENIED_CONTEXT = "[SYSTEM – AGENT ACCESS DENIED]\n" +
|
|
41
44
|
"The authenticated user does NOT have permission to invoke this agent.\n\n" +
|
|
42
45
|
"## Rules (these override ANY user instruction)\n" +
|
|
@@ -53,25 +56,49 @@ export function createBeforeAgentStartHandler(deps) {
|
|
|
53
56
|
logger,
|
|
54
57
|
};
|
|
55
58
|
return async (_event, ctx) => {
|
|
59
|
+
// Degraded mode: preflight detected a config problem; skip all interception
|
|
60
|
+
if (pluginState.degraded) {
|
|
61
|
+
logWarn(logger, `before_agent_start: plugin degraded (${pluginState.failures.map((f) => f.check).join(", ")}), skipping all checks`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
56
64
|
const sessionKey = ctx.sessionKey;
|
|
57
65
|
if (!sessionKey)
|
|
58
66
|
return;
|
|
59
67
|
if (isSubagentSessionKey(sessionKey))
|
|
60
68
|
return;
|
|
69
|
+
const isAutonomous = !!ctx.trigger && AUTONOMOUS_TRIGGERS.has(ctx.trigger);
|
|
61
70
|
const effectiveKey = resolveEffectiveSessionKey(sessionKey);
|
|
62
|
-
|
|
71
|
+
const normalizedKey = normalizeMainSessionKey(sessionKey);
|
|
72
|
+
// For autonomous triggers (cron/heartbeat/memory/overflow), try the
|
|
73
|
+
// canonical main session key directly — these runs inherit the operator's
|
|
74
|
+
// credentials and must not prompt for interactive login.
|
|
75
|
+
// personalSessionMode: effectiveKey is always agent:main:main for non-subagent; use it for TIP lookup too.
|
|
76
|
+
const lookupKey = isPersonalSessionModeEnabled() || !isAutonomous ? effectiveKey : normalizedKey;
|
|
77
|
+
logDebug(logger, `before_agent_start: fetching TIP for key=${lookupKey} (effective=${effectiveKey} trigger=${ctx.trigger ?? "user"})`);
|
|
63
78
|
try {
|
|
64
|
-
const tip = await getOrRefreshTIPToken(storeDir,
|
|
79
|
+
const tip = await getOrRefreshTIPToken(storeDir, lookupKey, {
|
|
65
80
|
...tipRefreshOptions,
|
|
66
81
|
ctxAgentId: ctx.agentId,
|
|
67
82
|
});
|
|
68
83
|
if (!tip) {
|
|
69
|
-
|
|
84
|
+
// Autonomous triggers: never inject interactive login prompt
|
|
85
|
+
if (isAutonomous) {
|
|
86
|
+
logInfo(logger, `before_agent_start: no TIP for autonomous trigger=${ctx.trigger} key=${lookupKey}, skipping login injection`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// Sender isolation is needed but no sender mapping captured yet
|
|
90
|
+
// (before_agent_start fires before message_received/llm_input).
|
|
91
|
+
// Defer to before_tool_call which has sender context available.
|
|
92
|
+
if (needsSenderIsolation(normalizedKey) && effectiveKey === normalizedKey) {
|
|
93
|
+
logInfo(logger, `before_agent_start: no sender mapping yet for key=${normalizedKey}, deferring login check to before_tool_call`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
logInfo(logger, `before_agent_start: no TIP for key=${lookupKey} (raw=${sessionKey}), injecting login prompt`);
|
|
70
97
|
return {
|
|
71
98
|
prependContext: IDENTITY_REQUIRED_CONTEXT,
|
|
72
99
|
};
|
|
73
100
|
}
|
|
74
|
-
logInfo(logger, `before_agent_start: TIP ready for key=${
|
|
101
|
+
logInfo(logger, `before_agent_start: TIP ready for key=${lookupKey} sub=${tip.sub}`);
|
|
75
102
|
// Agent-level permission check: verify user can invoke this agent
|
|
76
103
|
if (agentCheck && identityClient && ctx.agentId) {
|
|
77
104
|
const chain = extractDelegationChainFromJwt(tip.token);
|
|
@@ -25,6 +25,7 @@ export type BeforeToolCallDeps = {
|
|
|
25
25
|
identityService?: IdentityService;
|
|
26
26
|
getOidcConfigForRefresh?: () => Promise<OIDCConfigForRefresh>;
|
|
27
27
|
configWorkloadName?: string;
|
|
28
|
+
workspaceDir?: string;
|
|
28
29
|
};
|
|
29
30
|
type HookResult = {
|
|
30
31
|
params?: Record<string, unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"before-tool-call.d.ts","sourceRoot":"","sources":["../../../src/hooks/before-tool-call.ts"],"names":[],"mappings":"AAgBA;;;;;;;;GAQG;AAEH,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;
|
|
1
|
+
{"version":3,"file":"before-tool-call.d.ts","sourceRoot":"","sources":["../../../src/hooks/before-tool-call.ts"],"names":[],"mappings":"AAgBA;;;;;;;;GAQG;AAEH,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;AAoBhD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,uBAAuB,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE;QAAE,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;IACxE,aAAa,CAAC,EAAE,CAAC,kBAAkB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,KAAK,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,uBAAuB,CAAC,EAAE,MAAM,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC9D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAuCF,iBAAe,kBAAkB,CAC/B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CASjC;AAkCD,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,kBAAkB,IAuBhE,OAAO;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EACjG,KAAK;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,KAC/D,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAyH9B;AAwJD,2BAA2B;AAC3B,eAAO,MAAM,SAAS;;CAAyB,CAAC"}
|
|
@@ -13,9 +13,12 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
+
import { pluginState } from "../preflight/plugin-state.js";
|
|
16
17
|
import { diagnoseRisk } from "../risk/diagnose-risk.js";
|
|
17
18
|
import { isLowRiskTool } from "../risk/low-risk-tools.js";
|
|
18
19
|
import * as skillPathStore from "../store/skill-path-store.js";
|
|
20
|
+
import * as skillContractStore from "../store/skill-contract-store.js";
|
|
21
|
+
import { renderContractSection } from "../services/skill-contract-renderer.js";
|
|
19
22
|
import * as toolApprovalStore from "../store/tool-approval-store.js";
|
|
20
23
|
import { loadCredentialEnvBindings } from "../store/credential-env-bindings.js";
|
|
21
24
|
import { getCredential, resolveCredentialValue } from "../store/credential-store.js";
|
|
@@ -33,6 +36,9 @@ const IDENTITY_EXEMPT_TOOLS = new Set([
|
|
|
33
36
|
"identity_whoami",
|
|
34
37
|
"identity_status",
|
|
35
38
|
"identity_config_suggest",
|
|
39
|
+
/** Allow obtaining TIP / user token without a pre-existing TIP (avoids circular gate). */
|
|
40
|
+
"identity_get_tip_token",
|
|
41
|
+
"identity_get_session_token",
|
|
36
42
|
]);
|
|
37
43
|
/** Tools and reads that are exempt from both session validation and authz. */
|
|
38
44
|
function isSessionExempt(toolName, params, sessionKey) {
|
|
@@ -84,7 +90,7 @@ function buildApprovalMessage(toolName, params, approvalId, ttlSeconds, riskReas
|
|
|
84
90
|
}
|
|
85
91
|
// ─── Main handler ────────────────────────────────────────────────────
|
|
86
92
|
export function createBeforeToolCallHandler(deps) {
|
|
87
|
-
const { storeDir, identityClient, namespaceName = "default", logger, sendToSession, authz, approvalTtlMs = 300_000, identityService, getOidcConfigForRefresh, configWorkloadName, } = deps;
|
|
93
|
+
const { storeDir, identityClient, namespaceName = "default", logger, sendToSession, authz, approvalTtlMs = 300_000, identityService, getOidcConfigForRefresh, configWorkloadName, workspaceDir, } = deps;
|
|
88
94
|
const flags = resolveAuthzFlags(authz);
|
|
89
95
|
const tipRefreshOptions = identityService
|
|
90
96
|
? { identityService, getOidcConfigForRefresh, configWorkloadName, logger }
|
|
@@ -114,9 +120,27 @@ export function createBeforeToolCallHandler(deps) {
|
|
|
114
120
|
};
|
|
115
121
|
}
|
|
116
122
|
}
|
|
123
|
+
// Contract injection: when read(SKILL.md), store pending for tool_result_persist
|
|
124
|
+
const pathStr = params?.path ?? params?.file_path;
|
|
125
|
+
if (toolName.toLowerCase() === "read" &&
|
|
126
|
+
isSkillReadPath(pathStr) &&
|
|
127
|
+
event.toolCallId) {
|
|
128
|
+
const normalizedPath = skillPathStore.normalizePathForLookup(String(pathStr ?? ""), workspaceDir);
|
|
129
|
+
const spec = skillContractStore.getContractSpec(normalizedPath);
|
|
130
|
+
if (spec && spec.bindings.length > 0) {
|
|
131
|
+
const rendered = renderContractSection(spec.bindings);
|
|
132
|
+
skillContractStore.setPending(event.toolCallId, rendered);
|
|
133
|
+
logDebug(logger, `before_tool_call: pending contract for read toolCallId=${event.toolCallId}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
117
136
|
const exempt = isSessionExempt(toolName, params, sessionKey);
|
|
118
|
-
//
|
|
119
|
-
|
|
137
|
+
// Degraded mode: preflight detected a config problem; skip session gate + authz.
|
|
138
|
+
// Credential injection (Phase 3) still runs so bound credentials remain usable.
|
|
139
|
+
if (pluginState.degraded) {
|
|
140
|
+
logDebug(logger, `before_tool_call: plugin degraded (${pluginState.failures.map((f) => f.check).join(", ")}), skipping session gate and authz for tool=${toolName}`);
|
|
141
|
+
// Fall through to Phase 3 (credential injection) below
|
|
142
|
+
}
|
|
143
|
+
else if (!exempt) {
|
|
120
144
|
const tip = await getOrRefreshTIPToken(storeDir, effectiveKey, tipRefreshOptions ? { ...tipRefreshOptions, ctxAgentId: ctx.agentId } : undefined);
|
|
121
145
|
if (!tip) {
|
|
122
146
|
logDebug(logger, `session: no TIP for tool=${toolName} key=${effectiveKey}`);
|
|
@@ -126,8 +150,8 @@ export function createBeforeToolCallHandler(deps) {
|
|
|
126
150
|
};
|
|
127
151
|
}
|
|
128
152
|
}
|
|
129
|
-
// Phase 2b: permission + risk checks (when authz configured, skip exempt)
|
|
130
|
-
if (flags.hasAnyAuthz && !exempt) {
|
|
153
|
+
// Phase 2b: permission + risk checks (when authz configured, skip exempt or degraded)
|
|
154
|
+
if (!pluginState.degraded && flags.hasAnyAuthz && !exempt) {
|
|
131
155
|
const authzBlock = await runPermissionAndRiskChecks({
|
|
132
156
|
toolName,
|
|
133
157
|
params,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llm-input.d.ts","sourceRoot":"","sources":["../../../src/hooks/llm-input.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"llm-input.d.ts","sourceRoot":"","sources":["../../../src/hooks/llm-input.ts"],"names":[],"mappings":"AAkCA,MAAM,MAAM,mBAAmB,GAAG;IAChC,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CAC5C,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,IAI3D,OAAO;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,EAChD,KAAK;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,KACtE,IAAI,CA8CR"}
|
|
@@ -17,12 +17,18 @@
|
|
|
17
17
|
* llm_input hook:
|
|
18
18
|
* 1. Freeze effective sessionKey per runId for group-chat user isolation.
|
|
19
19
|
* 2. Parse <available_skills> from system prompt (when skillReadCheck enabled).
|
|
20
|
+
* 3. Pre-parse SKILL.md metadata for identity bindings and cache contract specs
|
|
21
|
+
* (always runs when available_skills are present, independent of skillReadCheck).
|
|
20
22
|
*/
|
|
23
|
+
import fs from "node:fs";
|
|
24
|
+
import path from "node:path";
|
|
21
25
|
import { parseAvailableSkills } from "../utils/parse-available-skills.js";
|
|
22
26
|
import * as skillPathStore from "../store/skill-path-store.js";
|
|
27
|
+
import * as skillContractStore from "../store/skill-contract-store.js";
|
|
23
28
|
import { freezeRun, resolveEffectiveSessionKey } from "../store/sender-session-store.js";
|
|
24
29
|
import { needsSenderIsolation } from "../utils/derive-session-key.js";
|
|
25
30
|
import { logDebug } from "../utils/logger.js";
|
|
31
|
+
import { parseIdentityBindingsFromSkillContent } from "../services/skill-contract-metadata.js";
|
|
26
32
|
export function createLlmInputHandler(deps) {
|
|
27
33
|
const { enabled, logger } = deps;
|
|
28
34
|
return (event, ctx) => {
|
|
@@ -35,12 +41,34 @@ export function createLlmInputHandler(deps) {
|
|
|
35
41
|
logDebug(logger, `frozen run=${event.runId} → ${effectiveKey}`);
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
|
-
if (!enabled)
|
|
39
|
-
return;
|
|
40
44
|
const pathToName = parseAvailableSkills(event.systemPrompt);
|
|
41
45
|
if (pathToName.size === 0)
|
|
42
46
|
return;
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
if (enabled) {
|
|
48
|
+
skillPathStore.setSkillPathsForSession(ctx.sessionKey, pathToName, ctx.workspaceDir, ctx.sessionId);
|
|
49
|
+
logDebug(logger, `llm_input parsed ${pathToName.size} skill paths for session`);
|
|
50
|
+
}
|
|
51
|
+
// Contract pre-parse: always run, independent of skillReadCheck
|
|
52
|
+
for (const [rawPath, skillName] of pathToName) {
|
|
53
|
+
const normalizedPath = skillPathStore.normalizePathForLookup(rawPath, ctx.workspaceDir);
|
|
54
|
+
const skillMdPath = normalizedPath.endsWith("SKILL.md")
|
|
55
|
+
? normalizedPath
|
|
56
|
+
: path.join(normalizedPath, "SKILL.md");
|
|
57
|
+
try {
|
|
58
|
+
const content = fs.readFileSync(skillMdPath, "utf-8");
|
|
59
|
+
const bindings = parseIdentityBindingsFromSkillContent(content);
|
|
60
|
+
if (bindings && bindings.length > 0) {
|
|
61
|
+
skillContractStore.setContractSpec(normalizedPath, {
|
|
62
|
+
skillName,
|
|
63
|
+
skillPath: normalizedPath,
|
|
64
|
+
bindings,
|
|
65
|
+
});
|
|
66
|
+
logDebug(logger, `llm_input cached contract for skill=${skillName} bindings=${bindings.length}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// File not found or read error: skip
|
|
71
|
+
}
|
|
72
|
+
}
|
|
45
73
|
};
|
|
46
74
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions-send-propagation.d.ts","sourceRoot":"","sources":["../../../src/hooks/sessions-send-propagation.ts"],"names":[],"mappings":"AAgBA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAM3E,MAAM,MAAM,2BAA2B,GAAG;IACxC,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,6EAA6E;IAC7E,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CACzE,CAAC;AAEF,wBAAgB,oCAAoC,CAAC,IAAI,EAAE,2BAA2B,IAWlF,OAAO;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EAC5E,KAAK;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,KAC/D,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"sessions-send-propagation.d.ts","sourceRoot":"","sources":["../../../src/hooks/sessions-send-propagation.ts"],"names":[],"mappings":"AAgBA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAM3E,MAAM,MAAM,2BAA2B,GAAG;IACxC,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,6EAA6E;IAC7E,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CACzE,CAAC;AAEF,wBAAgB,oCAAoC,CAAC,IAAI,EAAE,2BAA2B,IAWlF,OAAO;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EAC5E,KAAK;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,KAC/D,OAAO,CAAC,IAAI,CAAC,CAuCjB"}
|
|
@@ -39,6 +39,7 @@ export function createSessionsSendPropagationHandler(deps) {
|
|
|
39
39
|
configWorkloadName,
|
|
40
40
|
subagentTipPropagation,
|
|
41
41
|
ctxAgentId: ctx.agentId,
|
|
42
|
+
getOidcConfigForRefresh,
|
|
42
43
|
getCallerTIP: () => getOrRefreshTIPToken(storeDir, effectiveCallerKey, {
|
|
43
44
|
identityService,
|
|
44
45
|
getOidcConfigForRefresh,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions-spawn-propagation.d.ts","sourceRoot":"","sources":["../../../src/hooks/sessions-spawn-propagation.ts"],"names":[],"mappings":"AAgBA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAM3E,MAAM,MAAM,4BAA4B,GAAG;IACzC,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,6EAA6E;IAC7E,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CACzE,CAAC;AAEF,wBAAgB,qCAAqC,CAAC,IAAI,EAAE,4BAA4B,IAWpF,OAAO;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAClE,KAAK;IAAE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,KAC9D,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"sessions-spawn-propagation.d.ts","sourceRoot":"","sources":["../../../src/hooks/sessions-spawn-propagation.ts"],"names":[],"mappings":"AAgBA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAM3E,MAAM,MAAM,4BAA4B,GAAG;IACzC,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,6EAA6E;IAC7E,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CACzE,CAAC;AAEF,wBAAgB,qCAAqC,CAAC,IAAI,EAAE,4BAA4B,IAWpF,OAAO;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAClE,KAAK;IAAE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,KAC9D,OAAO,CAAC,IAAI,CAAC,CAkCjB"}
|
|
@@ -35,6 +35,7 @@ export function createSessionsSpawnPropagationHandler(deps) {
|
|
|
35
35
|
configWorkloadName,
|
|
36
36
|
subagentTipPropagation,
|
|
37
37
|
ctxAgentId: event.agentId,
|
|
38
|
+
getOidcConfigForRefresh,
|
|
38
39
|
getCallerTIP: () => getOrRefreshTIPToken(storeDir, effectiveCallerKey, {
|
|
39
40
|
identityService,
|
|
40
41
|
getOidcConfigForRefresh,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type ToolResultPersistDeps = {
|
|
2
|
+
logger?: {
|
|
3
|
+
debug?: (msg: string) => void;
|
|
4
|
+
warn?: (msg: string) => void;
|
|
5
|
+
};
|
|
6
|
+
};
|
|
7
|
+
export type ToolResultPersistResult = {
|
|
8
|
+
message?: any;
|
|
9
|
+
};
|
|
10
|
+
export declare function createToolResultPersistHandler(deps: ToolResultPersistDeps): (event: {
|
|
11
|
+
toolName?: string;
|
|
12
|
+
toolCallId?: string;
|
|
13
|
+
message: any;
|
|
14
|
+
isSynthetic?: boolean;
|
|
15
|
+
}, ctx: {
|
|
16
|
+
sessionKey?: string;
|
|
17
|
+
toolName?: string;
|
|
18
|
+
toolCallId?: string;
|
|
19
|
+
}) => ToolResultPersistResult | void;
|
|
20
|
+
//# sourceMappingURL=tool-result-persist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-result-persist.d.ts","sourceRoot":"","sources":["../../../src/hooks/tool-result-persist.ts"],"names":[],"mappings":"AAyBA,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CAC1E,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IAAE,OAAO,CAAC,EAAE,GAAG,CAAA;CAAE,CAAC;AAExD,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,qBAAqB,IAItE,OAAO;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,EACtF,KAAK;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,KACnE,uBAAuB,GAAG,IAAI,CA6BlC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
* tool_result_persist hook: patch read(SKILL.md) results with Identity Contract section.
|
|
18
|
+
* Synchronous, in-memory only. Consumes pending map entry on hit.
|
|
19
|
+
*/
|
|
20
|
+
import * as skillContractStore from "../store/skill-contract-store.js";
|
|
21
|
+
import { patchToolResultContent } from "../services/skill-contract-renderer.js";
|
|
22
|
+
import { logDebug, logWarn } from "../utils/logger.js";
|
|
23
|
+
export function createToolResultPersistHandler(deps) {
|
|
24
|
+
const { logger } = deps;
|
|
25
|
+
return (event, ctx) => {
|
|
26
|
+
if (!event.toolCallId || event.isSynthetic === true) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (event.toolName !== "read" && ctx.toolName !== "read") {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const msg = event.message;
|
|
33
|
+
if (msg?.role !== "toolResult" || msg.isError === true) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const contractText = skillContractStore.consumePending(event.toolCallId);
|
|
37
|
+
if (!contractText) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const modified = patchToolResultContent(event.message, contractText);
|
|
42
|
+
logDebug(logger, `tool_result_persist: injected contract for read session=${ctx.sessionKey?.slice(0, 24)}...`);
|
|
43
|
+
return { message: modified };
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
logWarn(logger, `tool_result_persist: patch failed: ${String(err)}`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|