@cyberdyne-systems/agent-safety 2026.3.12 → 2026.3.14

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/index.ts CHANGED
@@ -46,6 +46,14 @@ export default function register(api: OpenClawPluginApi) {
46
46
  const telegramApproval = pluginConfig.telegramApproval ?? false;
47
47
  const telegramOwnerId = pluginConfig.telegramOwnerId;
48
48
 
49
+ // Update owner's UID to match Telegram owner ID if configured
50
+ if (telegramOwnerId) {
51
+ const owner = store.getOwner();
52
+ if (owner && owner.uid !== telegramOwnerId) {
53
+ store.update(owner.id, { uid: telegramOwnerId });
54
+ }
55
+ }
56
+
49
57
  // Register the agent-facing safety tool
50
58
  api.registerTool(
51
59
  ((_ctx) => {
@@ -64,10 +72,13 @@ export default function register(api: OpenClawPluginApi) {
64
72
  if (toolName === "agent_safety") return;
65
73
 
66
74
  const actionCategory = toolNameToCategory(toolName);
67
- const requester = store.resolveRequester(
68
- ctx.requesterSenderId ?? undefined,
69
- (ctx as Record<string, unknown>).senderIsOwner as boolean | undefined,
70
- );
75
+ const senderId = ctx.requesterSenderId ?? undefined;
76
+ // If sender matches configured Telegram owner ID, treat as owner
77
+ const senderIsOwner =
78
+ ((ctx as Record<string, unknown>).senderIsOwner as boolean | undefined) ||
79
+ (telegramOwnerId && senderId === telegramOwnerId) ||
80
+ undefined;
81
+ const requester = store.resolveRequester(senderId, senderIsOwner || undefined);
71
82
  const owner = store.getOwner();
72
83
  const stakeholders = store.list();
73
84
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyberdyne-systems/agent-safety",
3
- "version": "2026.3.12",
3
+ "version": "2026.3.14",
4
4
  "description": "Agent safety system: stakeholder model, action validator, and safety dashboard — based on arXiv:2602.20021",
5
5
  "type": "module",
6
6
  "dependencies": {
package/src/approval.ts CHANGED
@@ -43,11 +43,11 @@ export class ApprovalManager {
43
43
 
44
44
  /** Check if there's a cached decision for a similar request */
45
45
  getCachedDecision(
46
- toolName: string,
46
+ _toolName: string,
47
47
  actionCategory: ActionCategory,
48
48
  requesterName: string,
49
49
  ): "approved" | "denied" | null {
50
- const key = `${toolName}:${actionCategory}:${requesterName}`;
50
+ const key = `${actionCategory}:${requesterName}`;
51
51
  return this.decisions.get(key) ?? null;
52
52
  }
53
53
 
@@ -58,8 +58,8 @@ export class ApprovalManager {
58
58
  approval.decision = decision;
59
59
  approval.decidedAt = Date.now();
60
60
 
61
- // Cache the decision for future similar requests
62
- const key = `${approval.toolName}:${approval.actionCategory}:${approval.requesterName}`;
61
+ // Cache the decision for future similar requests (by category, not tool name)
62
+ const key = `${approval.actionCategory}:${approval.requesterName}`;
63
63
  this.decisions.set(key, decision);
64
64
 
65
65
  this.pending.delete(id);
@@ -106,10 +106,10 @@ describe("Integration: full hook pipeline", () => {
106
106
 
107
107
  // Requester resolution
108
108
  it("resolves owner, known user, unknown sender", () => {
109
- // Owner running dangerous command → blocked (requires Telegram approval)
109
+ // Owner running dangerous command → warned but NOT blocked (owner is trusted)
110
110
  expect(
111
111
  simulateHook(store, auditLog, "bash", { command: "rm -rf /tmp/test" }, undefined, true).block,
112
- ).toBe(true);
112
+ ).toBe(false);
113
113
  // Owner running safe command → allowed
114
114
  expect(
115
115
  simulateHook(store, auditLog, "bash", { command: "ls -la" }, undefined, true).block,
package/src/validator.ts CHANGED
@@ -179,9 +179,11 @@ export function quickCheck(input: {
179
179
  : isPrivEsc
180
180
  ? "privilege escalation"
181
181
  : "shell injection";
182
+ // Owner gets WARN (logged but not blocked); non-owner gets BLOCK
183
+ const isOwner = requester.role === "owner" || requester.trust >= 4;
182
184
  return {
183
- verdict: "BLOCK",
184
- riskScore: 95,
185
+ verdict: isOwner ? "WARN" : "BLOCK",
186
+ riskScore: isOwner ? 60 : 95,
185
187
  risks: [
186
188
  {
187
189
  type: "reversibility",
@@ -204,9 +206,10 @@ export function quickCheck(input: {
204
206
  /while\s+true|for\s*\(\s*;\s*;\s*\)|watch\s+-n|tail\s+-f/i.test(command) &&
205
207
  !/(timeout|sleep\s+\d+.*&&\s*exit|--timeout)/i.test(command);
206
208
  if (hasInfiniteLoop) {
209
+ const isOwner = requester.role === "owner" || requester.trust >= 4;
207
210
  return {
208
- verdict: "BLOCK",
209
- riskScore: 80,
211
+ verdict: isOwner ? "WARN" : "BLOCK",
212
+ riskScore: isOwner ? 50 : 80,
210
213
  risks: [
211
214
  {
212
215
  type: "resource",