@siduri-x/core 2.0.1 → 2.0.3
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/dist/action-policy.d.ts +8 -1
- package/dist/action-policy.js +84 -24
- package/dist/action-policy.test.js +67 -1
- package/dist/action.d.ts +1 -1
- package/dist/capability.d.ts +6 -2
- package/dist/capability.js +5 -1
- package/dist/capability.test.js +1 -0
- package/dist/chat-contract.d.ts +3 -1
- package/dist/chat-contract.js +5 -2
- package/dist/container.d.ts +74 -0
- package/dist/container.js +81 -0
- package/dist/context.d.ts +3 -1
- package/dist/context.js +13 -0
- package/dist/index.d.ts +11 -3
- package/dist/index.js +2 -0
- package/dist/input-normalizer.js +3 -1
- package/dist/intent-classifier.d.ts +4 -2
- package/dist/intent-classifier.js +32 -1
- package/dist/intent-classifier.test.js +52 -0
- package/dist/memory-settler.d.ts +2 -1
- package/dist/memory-settler.js +9 -1
- package/dist/perception-cycle.test.js +134 -0
- package/dist/perception-pipeline.d.ts +76 -0
- package/dist/perception-pipeline.js +258 -0
- package/dist/perception-pipeline.test.d.ts +1 -0
- package/dist/perception-pipeline.test.js +65 -0
- package/dist/prompt-compiler.d.ts +2 -1
- package/dist/prompt-compiler.js +7 -1
- package/dist/proposals.d.ts +4 -1
- package/dist/response-envelope.d.ts +2 -1
- package/dist/response-envelope.js +2 -1
- package/dist/runtime-facades.test.js +27 -27
- package/dist/runtime.d.ts +36 -113
- package/dist/runtime.js +58 -403
- package/dist/siduri-db.d.ts +25 -9
- package/dist/siduri-db.js +114 -15
- package/dist/siduri-db.test.js +4 -3
- package/dist/sqlite-action-store.d.ts +1 -1
- package/dist/sqlite-action-store.js +37 -6
- package/dist/sqlite-action-store.test.js +9 -0
- package/package.json +1 -1
package/dist/action-policy.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface ActionPolicyEngineOptions {
|
|
|
18
18
|
allowedApproverRoles?: string[];
|
|
19
19
|
requiredApproverCapabilities?: string[];
|
|
20
20
|
}
|
|
21
|
-
export type ActionApprovalDecisionCode = 'APPROVED' | 'REJECTED_UNAUTHORIZED' | 'REJECTED_UNAUTHENTICATED' | 'REJECTED_ROLE_MISMATCH' | 'REJECTED_MISSING_APPROVER_ID' | 'REJECTED_MISSING_CAPABILITY';
|
|
21
|
+
export type ActionApprovalDecisionCode = 'APPROVED' | 'REJECTED_UNAUTHORIZED' | 'REJECTED_UNAUTHENTICATED' | 'REJECTED_ROLE_MISMATCH' | 'REJECTED_MISSING_APPROVER_ID' | 'REJECTED_MISSING_CAPABILITY' | 'REJECTED_UNKNOWN_EXECUTION';
|
|
22
22
|
export interface ActionApprovalResult {
|
|
23
23
|
approved: boolean;
|
|
24
24
|
decisionCode: ActionApprovalDecisionCode;
|
|
@@ -42,6 +42,13 @@ export interface ApproveActionOptions {
|
|
|
42
42
|
* Optional capabilities held by the approver.
|
|
43
43
|
*/
|
|
44
44
|
approverCapabilities?: string[];
|
|
45
|
+
/**
|
|
46
|
+
* Optional explicit action binding parameters when pre-authorizing a known action
|
|
47
|
+
*/
|
|
48
|
+
toolName?: string;
|
|
49
|
+
parametersHash?: string;
|
|
50
|
+
companionId?: string;
|
|
51
|
+
actorId?: string;
|
|
45
52
|
}
|
|
46
53
|
export declare function normalizeApproverRole(role?: string, actorId?: string): string;
|
|
47
54
|
export declare class ActionPolicyEngine {
|
package/dist/action-policy.js
CHANGED
|
@@ -10,18 +10,10 @@ function normalizeApproverRole(role, actorId) {
|
|
|
10
10
|
return 'administrator';
|
|
11
11
|
return lower;
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (lower.includes('owner'))
|
|
18
|
-
return 'owner';
|
|
19
|
-
if (lower.includes('operator'))
|
|
20
|
-
return 'operator';
|
|
21
|
-
if (lower.includes('viewer') || lower.includes('guest') || lower.includes('visitor'))
|
|
22
|
-
return 'viewer';
|
|
23
|
-
}
|
|
24
|
-
return 'owner'; // Default to single-owner role
|
|
13
|
+
// Principle of least privilege: Never derive privileged roles (administrator, owner, operator)
|
|
14
|
+
// from arbitrary substring matching on unverified actorId strings.
|
|
15
|
+
// When an explicit authorization role is not provided, default to 'viewer'.
|
|
16
|
+
return 'viewer';
|
|
25
17
|
}
|
|
26
18
|
class ActionPolicyEngine {
|
|
27
19
|
toolRegistry = new Map();
|
|
@@ -96,7 +88,9 @@ class ActionPolicyEngine {
|
|
|
96
88
|
}
|
|
97
89
|
// Role check if tool restricts roles (supports administrator/owner role parity)
|
|
98
90
|
if (toolDef.allowedRoles && toolDef.allowedRoles.length > 0) {
|
|
99
|
-
const actorRole = effectiveContext.actor.authorizationRole ||
|
|
91
|
+
const actorRole = effectiveContext.actor.authorizationRole ||
|
|
92
|
+
effectiveContext.actor.role ||
|
|
93
|
+
(effectiveContext.actor.authenticated === false ? 'viewer' : 'owner');
|
|
100
94
|
const normalizedActorRoles = new Set([actorRole.toLowerCase()]);
|
|
101
95
|
if (actorRole.toLowerCase() === 'administrator' || actorRole.toLowerCase() === 'owner') {
|
|
102
96
|
normalizedActorRoles.add('administrator');
|
|
@@ -158,6 +152,26 @@ class ActionPolicyEngine {
|
|
|
158
152
|
this.approvedExecutions.set(executionId, approvalRecord);
|
|
159
153
|
}
|
|
160
154
|
}
|
|
155
|
+
// Cryptographic / structural binding verification:
|
|
156
|
+
// Ensure the approval record matches the exact tool, parameters, companion, and actor
|
|
157
|
+
if (approvalRecord) {
|
|
158
|
+
const toolMatch = !approvalRecord.toolName || approvalRecord.toolName === action.toolName;
|
|
159
|
+
const paramsMatch = !approvalRecord.parametersHash || approvalRecord.parametersHash === paramsHash;
|
|
160
|
+
const companionMatch = !approvalRecord.companionId || approvalRecord.companionId === effectiveContext.companionId;
|
|
161
|
+
const actorMatch = !approvalRecord.actorId || approvalRecord.actorId === effectiveContext.actor.actorId;
|
|
162
|
+
if (!toolMatch || !paramsMatch || !companionMatch || !actorMatch) {
|
|
163
|
+
const decision = {
|
|
164
|
+
allowed: false,
|
|
165
|
+
reason: `Approval record for execution "${executionId}" does not match the requested tool, parameters, companion, or actor`,
|
|
166
|
+
riskLevel,
|
|
167
|
+
requiredCapabilities: requiredCaps,
|
|
168
|
+
executionId,
|
|
169
|
+
decisionCode: 'REJECTED_APPROVAL_MISMATCH',
|
|
170
|
+
};
|
|
171
|
+
await this.recordAudit(action, effectiveContext, decision, 'REJECTED');
|
|
172
|
+
return { decision };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
161
175
|
let isApproved = Boolean(approvalRecord);
|
|
162
176
|
if (!isApproved && typeof this.store.isActionApproved === 'function') {
|
|
163
177
|
isApproved = await this.store.isActionApproved(executionId);
|
|
@@ -183,6 +197,27 @@ class ActionPolicyEngine {
|
|
|
183
197
|
}
|
|
184
198
|
if (requiresExplicitApproval && !isApproved) {
|
|
185
199
|
this.pendingActions.set(executionId, { action, context: effectiveContext, toolDef });
|
|
200
|
+
if (typeof this.store.reserveExecution === 'function') {
|
|
201
|
+
const record = {
|
|
202
|
+
executionId,
|
|
203
|
+
actionId: action.actionId,
|
|
204
|
+
toolName: action.toolName,
|
|
205
|
+
providerId: toolDef.providerId || 'builtin',
|
|
206
|
+
parametersHash: paramsHash,
|
|
207
|
+
lifecycle: 'POLICY_CHECKED',
|
|
208
|
+
decision: {
|
|
209
|
+
allowed: false,
|
|
210
|
+
reason: `Action "${action.toolName}" has risk level ${riskLevel} and requires explicit approval`,
|
|
211
|
+
riskLevel,
|
|
212
|
+
requiredCapabilities: requiredCaps,
|
|
213
|
+
executionId,
|
|
214
|
+
decisionCode: 'REJECTED_HIGH_RISK_UNAPPROVED',
|
|
215
|
+
},
|
|
216
|
+
createdAt: new Date().toISOString(),
|
|
217
|
+
updatedAt: new Date().toISOString(),
|
|
218
|
+
};
|
|
219
|
+
await this.store.reserveExecution(record).catch(() => { });
|
|
220
|
+
}
|
|
186
221
|
const decision = {
|
|
187
222
|
allowed: false,
|
|
188
223
|
reason: `Action "${action.toolName}" has risk level ${riskLevel} and requires explicit approval`,
|
|
@@ -271,21 +306,42 @@ class ActionPolicyEngine {
|
|
|
271
306
|
await this.recordApprovalAudit(options.executionId, actorId, approverRole, false, result.reason);
|
|
272
307
|
return result;
|
|
273
308
|
}
|
|
274
|
-
// 3.
|
|
309
|
+
// 3. Locate pending in-memory action or persisted execution record
|
|
275
310
|
const pending = this.pendingActions.get(options.executionId);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
311
|
+
let executionRecord;
|
|
312
|
+
if (!pending && typeof this.store.getExecution === 'function') {
|
|
313
|
+
executionRecord = await this.store.getExecution(options.executionId);
|
|
314
|
+
}
|
|
315
|
+
if (!pending && !executionRecord && !options.toolName) {
|
|
316
|
+
const result = {
|
|
317
|
+
approved: false,
|
|
318
|
+
decisionCode: 'REJECTED_UNKNOWN_EXECUTION',
|
|
319
|
+
reason: `Cannot approve execution "${options.executionId}": execution is not pending or recognized`,
|
|
320
|
+
executionId: options.executionId,
|
|
321
|
+
approverActorId: actorId,
|
|
322
|
+
};
|
|
323
|
+
await this.recordApprovalAudit(options.executionId, actorId, approverRole, false, result.reason);
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
const targetToolName = pending?.action.toolName || executionRecord?.toolName || options.toolName || '';
|
|
327
|
+
const targetToolDef = pending?.toolDef || (targetToolName ? this.findToolDefinition(targetToolName) : undefined);
|
|
328
|
+
const targetParamsHash = (pending ? (0, capability_1.computeParametersHash)(pending.action.parameters) : undefined) ||
|
|
329
|
+
executionRecord?.parametersHash ||
|
|
330
|
+
options.parametersHash;
|
|
331
|
+
const targetCompanionId = pending?.context?.companionId || pending?.action.context?.companionId || options.companionId;
|
|
332
|
+
const targetActorId = pending?.context?.actor.actorId || pending?.action.context?.actor.actorId || options.actorId;
|
|
333
|
+
if (targetToolDef) {
|
|
334
|
+
if (targetToolDef.allowedRoles && targetToolDef.allowedRoles.length > 0 && !isOwnerOrAdmin) {
|
|
335
|
+
const normalizedToolRoles = new Set(targetToolDef.allowedRoles.map((r) => r.toLowerCase()));
|
|
280
336
|
if (!normalizedToolRoles.has(approverRole)) {
|
|
281
337
|
const result = {
|
|
282
338
|
approved: false,
|
|
283
339
|
decisionCode: 'REJECTED_ROLE_MISMATCH',
|
|
284
|
-
reason: `Approver role "${approverRole}" is not authorized to approve tool "${
|
|
340
|
+
reason: `Approver role "${approverRole}" is not authorized to approve tool "${targetToolDef.name}" (requires: ${targetToolDef.allowedRoles.join(', ')})`,
|
|
285
341
|
executionId: options.executionId,
|
|
286
342
|
approverActorId: actorId,
|
|
287
343
|
};
|
|
288
|
-
await this.recordApprovalAudit(options.executionId, actorId, approverRole, false, result.reason,
|
|
344
|
+
await this.recordApprovalAudit(options.executionId, actorId, approverRole, false, result.reason, targetToolDef.name);
|
|
289
345
|
return result;
|
|
290
346
|
}
|
|
291
347
|
}
|
|
@@ -299,25 +355,29 @@ class ActionPolicyEngine {
|
|
|
299
355
|
executionId: options.executionId,
|
|
300
356
|
approverActorId: actorId,
|
|
301
357
|
};
|
|
302
|
-
await this.recordApprovalAudit(options.executionId, actorId, approverRole, false, result.reason,
|
|
358
|
+
await this.recordApprovalAudit(options.executionId, actorId, approverRole, false, result.reason, targetToolDef.name);
|
|
303
359
|
return result;
|
|
304
360
|
}
|
|
305
361
|
}
|
|
306
362
|
}
|
|
307
|
-
// 4. Record verified approval
|
|
363
|
+
// 4. Record verified approval with exact tool, parameter, companion, and actor binding
|
|
308
364
|
const record = {
|
|
309
365
|
executionId: options.executionId,
|
|
310
366
|
approverActorId: actorId,
|
|
311
367
|
reason: options.reason,
|
|
312
368
|
approverRole,
|
|
313
369
|
approvedAt: new Date().toISOString(),
|
|
370
|
+
toolName: targetToolName,
|
|
371
|
+
parametersHash: targetParamsHash,
|
|
372
|
+
companionId: targetCompanionId,
|
|
373
|
+
actorId: targetActorId,
|
|
314
374
|
};
|
|
315
375
|
this.approvedExecutions.set(options.executionId, record);
|
|
316
376
|
if (typeof this.store.saveApproval === 'function') {
|
|
317
|
-
await this.store.saveApproval(options.executionId, actorId, options.reason, approverRole);
|
|
377
|
+
await this.store.saveApproval(options.executionId, actorId, options.reason, approverRole, targetToolName, targetParamsHash, targetCompanionId, targetActorId);
|
|
318
378
|
}
|
|
319
379
|
const reason = options.reason || 'Action approved by authorized policy approver';
|
|
320
|
-
await this.recordApprovalAudit(options.executionId, actorId, approverRole, true, reason,
|
|
380
|
+
await this.recordApprovalAudit(options.executionId, actorId, approverRole, true, reason, targetToolName);
|
|
321
381
|
return {
|
|
322
382
|
approved: true,
|
|
323
383
|
decisionCode: 'APPROVED',
|
|
@@ -125,9 +125,10 @@ describe('ActionPolicyEngine Boundary', () => {
|
|
|
125
125
|
expect(res1.decision.decisionCode).toBe('REJECTED_HIGH_RISK_UNAPPROVED');
|
|
126
126
|
expect(res1.capability).toBeUndefined();
|
|
127
127
|
// Grant explicit approval
|
|
128
|
-
engine.approveAction({
|
|
128
|
+
await engine.approveAction({
|
|
129
129
|
executionId: 'exec-admin-delete-1',
|
|
130
130
|
approverActorId: 'admin-super-user',
|
|
131
|
+
approverRole: 'administrator',
|
|
131
132
|
});
|
|
132
133
|
// Second attempt: approved
|
|
133
134
|
const res2 = await engine.evaluateAction(action);
|
|
@@ -293,5 +294,70 @@ describe('ActionPolicyEngine Boundary', () => {
|
|
|
293
294
|
expect(rejectionEvent?.actorId).toBe('viewer-tamper');
|
|
294
295
|
expect(rejectionEvent?.decision?.decisionCode).toBe('REJECTED_UNAUTHORIZED');
|
|
295
296
|
});
|
|
297
|
+
it('rejects approval attempt for arbitrary unknown execution ID', async () => {
|
|
298
|
+
const res = await engine.approveAction({
|
|
299
|
+
executionId: 'arbitrary-unregistered-id',
|
|
300
|
+
approverActorId: 'admin-bob',
|
|
301
|
+
approverRole: 'administrator',
|
|
302
|
+
});
|
|
303
|
+
expect(res.approved).toBe(false);
|
|
304
|
+
expect(res.decisionCode).toBe('REJECTED_UNKNOWN_EXECUTION');
|
|
305
|
+
});
|
|
306
|
+
it('rejects privilege escalation from actorId strings (attacker-admin is not an administrator without explicit role)', async () => {
|
|
307
|
+
await engine.evaluateAction(criticalAction);
|
|
308
|
+
const res = await engine.approveAction({
|
|
309
|
+
executionId: 'exec-sec-auth-1',
|
|
310
|
+
approverActorId: 'attacker-admin-user',
|
|
311
|
+
// Notice no approverRole is provided!
|
|
312
|
+
});
|
|
313
|
+
expect(res.approved).toBe(false);
|
|
314
|
+
expect(res.decisionCode).toBe('REJECTED_UNAUTHORIZED');
|
|
315
|
+
});
|
|
316
|
+
it('rejects action evaluation if parameters are tampered after approval (REJECTED_APPROVAL_MISMATCH)', async () => {
|
|
317
|
+
await engine.evaluateAction(criticalAction);
|
|
318
|
+
const approval = await engine.approveAction({
|
|
319
|
+
executionId: 'exec-sec-auth-1',
|
|
320
|
+
approverActorId: 'admin-bob',
|
|
321
|
+
approverRole: 'administrator',
|
|
322
|
+
});
|
|
323
|
+
expect(approval.approved).toBe(true);
|
|
324
|
+
// Attacker re-evaluates the same approved executionId but with injected dangerous parameters
|
|
325
|
+
const tamperedAction = {
|
|
326
|
+
...criticalAction,
|
|
327
|
+
parameters: { injected: 'malicious-payload' },
|
|
328
|
+
};
|
|
329
|
+
const evalTampered = await engine.evaluateAction(tamperedAction);
|
|
330
|
+
expect(evalTampered.decision.allowed).toBe(false);
|
|
331
|
+
expect(evalTampered.decision.decisionCode).toBe('REJECTED_APPROVAL_MISMATCH');
|
|
332
|
+
expect(evalTampered.capability).toBeUndefined();
|
|
333
|
+
});
|
|
334
|
+
it('rejects action evaluation if tool name is swapped after approval (REJECTED_APPROVAL_MISMATCH)', async () => {
|
|
335
|
+
await engine.evaluateAction(criticalAction);
|
|
336
|
+
const approval = await engine.approveAction({
|
|
337
|
+
executionId: 'exec-sec-auth-1',
|
|
338
|
+
approverActorId: 'admin-bob',
|
|
339
|
+
approverRole: 'administrator',
|
|
340
|
+
});
|
|
341
|
+
expect(approval.approved).toBe(true);
|
|
342
|
+
// Register another tool
|
|
343
|
+
engine.registerToolDefinition({
|
|
344
|
+
name: 'admin/format_drive',
|
|
345
|
+
providerId: 'admin',
|
|
346
|
+
description: 'Format drive',
|
|
347
|
+
inputSchema: {},
|
|
348
|
+
riskLevel: 'CRITICAL',
|
|
349
|
+
allowedRoles: ['administrator'],
|
|
350
|
+
requiresApproval: true,
|
|
351
|
+
});
|
|
352
|
+
// Attacker re-uses the executionId on a different tool
|
|
353
|
+
const swappedAction = {
|
|
354
|
+
...criticalAction,
|
|
355
|
+
toolName: 'admin/format_drive',
|
|
356
|
+
};
|
|
357
|
+
const evalSwapped = await engine.evaluateAction(swappedAction);
|
|
358
|
+
expect(evalSwapped.decision.allowed).toBe(false);
|
|
359
|
+
expect(evalSwapped.decision.decisionCode).toBe('REJECTED_APPROVAL_MISMATCH');
|
|
360
|
+
expect(evalSwapped.capability).toBeUndefined();
|
|
361
|
+
});
|
|
296
362
|
});
|
|
297
363
|
});
|
package/dist/action.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface ActionPolicyDecision {
|
|
|
15
15
|
riskLevel: ActionRiskLevel;
|
|
16
16
|
requiredCapabilities?: string[];
|
|
17
17
|
executionId: string;
|
|
18
|
-
decisionCode: 'ALLOWED_AUTO' | 'ALLOWED_POLICY' | 'REJECTED_UNKNOWN_TOOL' | 'REJECTED_UNAUTHORIZED' | 'REJECTED_MISSING_CAPABILITY' | 'REJECTED_HIGH_RISK_UNAPPROVED' | 'REJECTED_CHANNEL_RESTRICTED' | 'REJECTED_COMPANION_MISMATCH' | 'REJECTED_POLICY';
|
|
18
|
+
decisionCode: 'ALLOWED_AUTO' | 'ALLOWED_POLICY' | 'REJECTED_UNKNOWN_TOOL' | 'REJECTED_UNAUTHORIZED' | 'REJECTED_MISSING_CAPABILITY' | 'REJECTED_HIGH_RISK_UNAPPROVED' | 'REJECTED_CHANNEL_RESTRICTED' | 'REJECTED_COMPANION_MISMATCH' | 'REJECTED_APPROVAL_MISMATCH' | 'REJECTED_POLICY';
|
|
19
19
|
}
|
|
20
20
|
export interface ActionAuditEvent {
|
|
21
21
|
executionId: string;
|
package/dist/capability.d.ts
CHANGED
|
@@ -43,12 +43,16 @@ export interface ActionApprovalRecord {
|
|
|
43
43
|
reason?: string;
|
|
44
44
|
approvedAt: string;
|
|
45
45
|
approverRole?: string;
|
|
46
|
+
toolName?: string;
|
|
47
|
+
parametersHash?: string;
|
|
48
|
+
companionId?: string;
|
|
49
|
+
actorId?: string;
|
|
46
50
|
}
|
|
47
51
|
export interface ActionStore {
|
|
48
52
|
reserveExecution(record: PersistentExecutionRecord): Promise<boolean>;
|
|
49
53
|
updateExecution(record: PersistentExecutionRecord): Promise<void>;
|
|
50
54
|
getExecution(executionId: string): Promise<PersistentExecutionRecord | undefined>;
|
|
51
|
-
saveApproval(executionId: string, approverActorId: string, reason?: string, approverRole?: string): Promise<void>;
|
|
55
|
+
saveApproval(executionId: string, approverActorId: string, reason?: string, approverRole?: string, toolName?: string, parametersHash?: string, companionId?: string, actorId?: string): Promise<void>;
|
|
52
56
|
isActionApproved(executionId: string): Promise<boolean>;
|
|
53
57
|
getApproval?(executionId: string): Promise<ActionApprovalRecord | undefined>;
|
|
54
58
|
appendAudit(event: ActionAuditEvent): Promise<void>;
|
|
@@ -62,7 +66,7 @@ export declare class InMemoryActionStore implements ActionStore {
|
|
|
62
66
|
reserveExecution(record: PersistentExecutionRecord): Promise<boolean>;
|
|
63
67
|
updateExecution(record: PersistentExecutionRecord): Promise<void>;
|
|
64
68
|
getExecution(executionId: string): Promise<PersistentExecutionRecord | undefined>;
|
|
65
|
-
saveApproval(executionId: string, approverActorId: string, reason?: string, approverRole?: string): Promise<void>;
|
|
69
|
+
saveApproval(executionId: string, approverActorId: string, reason?: string, approverRole?: string, toolName?: string, parametersHash?: string, companionId?: string, actorId?: string): Promise<void>;
|
|
66
70
|
isActionApproved(executionId: string): Promise<boolean>;
|
|
67
71
|
getApproval(executionId: string): Promise<ActionApprovalRecord | undefined>;
|
|
68
72
|
appendAudit(event: ActionAuditEvent): Promise<void>;
|
package/dist/capability.js
CHANGED
|
@@ -53,13 +53,17 @@ class InMemoryActionStore {
|
|
|
53
53
|
const rec = this.executions.get(executionId);
|
|
54
54
|
return rec ? { ...rec } : undefined;
|
|
55
55
|
}
|
|
56
|
-
async saveApproval(executionId, approverActorId, reason, approverRole) {
|
|
56
|
+
async saveApproval(executionId, approverActorId, reason, approverRole, toolName, parametersHash, companionId, actorId) {
|
|
57
57
|
this.approvals.set(executionId, {
|
|
58
58
|
executionId,
|
|
59
59
|
approverActorId,
|
|
60
60
|
reason,
|
|
61
61
|
approverRole,
|
|
62
62
|
approvedAt: new Date().toISOString(),
|
|
63
|
+
toolName,
|
|
64
|
+
parametersHash,
|
|
65
|
+
companionId,
|
|
66
|
+
actorId,
|
|
63
67
|
});
|
|
64
68
|
}
|
|
65
69
|
async isActionApproved(executionId) {
|
package/dist/capability.test.js
CHANGED
|
@@ -281,6 +281,7 @@ describe('AuthorizationCapability Cryptographic & Tamper Review', () => {
|
|
|
281
281
|
await engine1.approveAction({
|
|
282
282
|
executionId: 'exec-restart-1',
|
|
283
283
|
approverActorId: 'operator-1',
|
|
284
|
+
approverRole: 'operator',
|
|
284
285
|
reason: 'Scheduled maintenance',
|
|
285
286
|
});
|
|
286
287
|
// 3. Process restarts: new ActionPolicyEngine instance with empty in-memory set but shared durable store
|
package/dist/chat-contract.d.ts
CHANGED
|
@@ -78,4 +78,6 @@ export interface ChatResponse {
|
|
|
78
78
|
* Canonical helper to dispatch a chat request to a SiduriRuntime instance.
|
|
79
79
|
* Guarantees a 1:1 identical response structure for localweb (apps/api) and standalone CLI.
|
|
80
80
|
*/
|
|
81
|
-
export declare function dispatchCompanionChat(runtime: SiduriRuntime
|
|
81
|
+
export declare function dispatchCompanionChat(runtime: SiduriRuntime | {
|
|
82
|
+
runtime: SiduriRuntime;
|
|
83
|
+
}, payload: ChatRequest): Promise<ChatResponse>;
|
package/dist/chat-contract.js
CHANGED
|
@@ -6,6 +6,9 @@ exports.dispatchCompanionChat = dispatchCompanionChat;
|
|
|
6
6
|
* Guarantees a 1:1 identical response structure for localweb (apps/api) and standalone CLI.
|
|
7
7
|
*/
|
|
8
8
|
async function dispatchCompanionChat(runtime, payload) {
|
|
9
|
+
const runner = 'runtime' in runtime && typeof runtime.handleUserMessage !== 'function'
|
|
10
|
+
? runtime.runtime
|
|
11
|
+
: runtime;
|
|
9
12
|
const userMessage = payload.message || payload.text || '';
|
|
10
13
|
const history = Array.isArray(payload.history) ? payload.history : [];
|
|
11
14
|
let roleOrContext;
|
|
@@ -19,8 +22,8 @@ async function dispatchCompanionChat(runtime, payload) {
|
|
|
19
22
|
roleOrContext = 'OWNER';
|
|
20
23
|
}
|
|
21
24
|
const runtimeResult = (payload.medium || payload.signal)
|
|
22
|
-
? await
|
|
23
|
-
: await
|
|
25
|
+
? await runner.handleUserMessage(userMessage, roleOrContext, history, payload.medium, payload.signal)
|
|
26
|
+
: await runner.handleUserMessage(userMessage, roleOrContext, history);
|
|
24
27
|
const delivery = runtimeResult?.delivery;
|
|
25
28
|
// Normalize response plan
|
|
26
29
|
const speech = delivery?.displayText ||
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { BrainOrgan, MemoryOrgan, VoiceOrgan, KnowledgeOrgan, VisionOrgan, BehaviorOrgan, BodyOrgan, HandsOrgan, EarOrgan, ObservationOrgan, MouthOrgan, SelfRepository, EKnowledgeOrgan, OrganConfig, ActionStore, ActionPolicyEngine, ResponseGatingEngine, ExperienceDispatcher, ExperienceAdapter } from './index';
|
|
2
|
+
import { SessionHistoryManager } from './session-history';
|
|
3
|
+
import { SiduriRuntime } from './runtime';
|
|
4
|
+
export interface SiduriRuntimeConfig {
|
|
5
|
+
name: string;
|
|
6
|
+
brain?: OrganConfig | Record<string, unknown>;
|
|
7
|
+
voice?: OrganConfig | Record<string, unknown>;
|
|
8
|
+
memory?: OrganConfig | Record<string, unknown>;
|
|
9
|
+
knowledge?: OrganConfig | Record<string, unknown>;
|
|
10
|
+
behavior?: OrganConfig | Record<string, unknown>;
|
|
11
|
+
body?: OrganConfig | Record<string, unknown>;
|
|
12
|
+
vision?: OrganConfig | Record<string, unknown>;
|
|
13
|
+
hands?: OrganConfig | Record<string, unknown>;
|
|
14
|
+
ear?: OrganConfig | Record<string, unknown>;
|
|
15
|
+
observation?: OrganConfig | Record<string, unknown>;
|
|
16
|
+
mouth?: OrganConfig | Record<string, unknown>;
|
|
17
|
+
self?: OrganConfig | Record<string, unknown>;
|
|
18
|
+
externalKnowledge?: OrganConfig | Record<string, unknown>;
|
|
19
|
+
actionPolicy?: Record<string, unknown>;
|
|
20
|
+
actionStore?: 'in-memory' | 'sqlite' | {
|
|
21
|
+
type: 'sqlite' | 'in-memory';
|
|
22
|
+
dbPath?: string;
|
|
23
|
+
};
|
|
24
|
+
actionStorePath?: string;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
export interface RuntimeOrgans {
|
|
28
|
+
brain?: BrainOrgan;
|
|
29
|
+
memory?: MemoryOrgan;
|
|
30
|
+
voice?: VoiceOrgan | ExperienceAdapter;
|
|
31
|
+
knowledge?: KnowledgeOrgan;
|
|
32
|
+
vision?: VisionOrgan;
|
|
33
|
+
behavior?: BehaviorOrgan;
|
|
34
|
+
body?: BodyOrgan | ExperienceAdapter;
|
|
35
|
+
hands?: HandsOrgan;
|
|
36
|
+
ear?: EarOrgan;
|
|
37
|
+
observation?: ObservationOrgan;
|
|
38
|
+
mouth?: MouthOrgan;
|
|
39
|
+
self?: SelfRepository;
|
|
40
|
+
externalKnowledge?: EKnowledgeOrgan;
|
|
41
|
+
actionStore?: ActionStore;
|
|
42
|
+
actionPolicy?: ActionPolicyEngine;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* CompanionContainer manages the lifecycle, discovery, wiring, and dependency injection
|
|
46
|
+
* of organs and infrastructure for a companion instance.
|
|
47
|
+
*/
|
|
48
|
+
export declare class CompanionContainer {
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly config: SiduriRuntimeConfig;
|
|
51
|
+
readonly organs: RuntimeOrgans;
|
|
52
|
+
readonly gating: ResponseGatingEngine;
|
|
53
|
+
readonly actionPolicy: ActionPolicyEngine;
|
|
54
|
+
readonly dispatcher: ExperienceDispatcher;
|
|
55
|
+
readonly sessionHistory: SessionHistoryManager;
|
|
56
|
+
private _runtime?;
|
|
57
|
+
constructor(id: string, config: SiduriRuntimeConfig, organs?: RuntimeOrgans);
|
|
58
|
+
get brain(): BrainOrgan | undefined;
|
|
59
|
+
get memory(): MemoryOrgan | undefined;
|
|
60
|
+
get voice(): ExperienceAdapter | VoiceOrgan | undefined;
|
|
61
|
+
get knowledge(): KnowledgeOrgan | undefined;
|
|
62
|
+
get vision(): VisionOrgan | undefined;
|
|
63
|
+
get behavior(): BehaviorOrgan | undefined;
|
|
64
|
+
get body(): ExperienceAdapter | BodyOrgan | undefined;
|
|
65
|
+
get hands(): HandsOrgan | undefined;
|
|
66
|
+
get ear(): EarOrgan | undefined;
|
|
67
|
+
get observation(): ObservationOrgan | undefined;
|
|
68
|
+
set observation(org: ObservationOrgan | undefined);
|
|
69
|
+
get mouth(): MouthOrgan | undefined;
|
|
70
|
+
get self(): SelfRepository | undefined;
|
|
71
|
+
get externalKnowledge(): EKnowledgeOrgan | undefined;
|
|
72
|
+
initialize(): Promise<void>;
|
|
73
|
+
get runtime(): SiduriRuntime;
|
|
74
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CompanionContainer = void 0;
|
|
4
|
+
const index_1 = require("./index");
|
|
5
|
+
const session_history_1 = require("./session-history");
|
|
6
|
+
const runtime_1 = require("./runtime");
|
|
7
|
+
/**
|
|
8
|
+
* CompanionContainer manages the lifecycle, discovery, wiring, and dependency injection
|
|
9
|
+
* of organs and infrastructure for a companion instance.
|
|
10
|
+
*/
|
|
11
|
+
class CompanionContainer {
|
|
12
|
+
id;
|
|
13
|
+
config;
|
|
14
|
+
organs;
|
|
15
|
+
gating;
|
|
16
|
+
actionPolicy;
|
|
17
|
+
dispatcher;
|
|
18
|
+
sessionHistory;
|
|
19
|
+
_runtime;
|
|
20
|
+
constructor(id, config, organs = {}) {
|
|
21
|
+
this.id = id;
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.organs = organs;
|
|
24
|
+
this.gating = new index_1.ResponseGatingEngine();
|
|
25
|
+
let actionStore = organs.actionStore;
|
|
26
|
+
if (!actionStore) {
|
|
27
|
+
const storeOpt = config.actionStore;
|
|
28
|
+
const storePath = config.actionStorePath || (typeof storeOpt === 'object' ? storeOpt.dbPath : undefined);
|
|
29
|
+
if (storeOpt === 'sqlite' || (typeof storeOpt === 'object' && storeOpt.type === 'sqlite') || storePath) {
|
|
30
|
+
actionStore = new index_1.SqliteActionStore({ dbPath: storePath });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
this.actionPolicy = organs.actionPolicy || new index_1.ActionPolicyEngine({
|
|
34
|
+
store: actionStore,
|
|
35
|
+
});
|
|
36
|
+
this.dispatcher = new index_1.ExperienceDispatcher();
|
|
37
|
+
if (this.organs.voice && typeof this.organs.voice.handleEvent === 'function') {
|
|
38
|
+
this.dispatcher.registerAdapter(this.organs.voice);
|
|
39
|
+
}
|
|
40
|
+
if (this.organs.body && typeof this.organs.body.handleEvent === 'function') {
|
|
41
|
+
this.dispatcher.registerAdapter(this.organs.body);
|
|
42
|
+
}
|
|
43
|
+
if (this.organs.mouth && typeof this.organs.mouth.handleEvent === 'function') {
|
|
44
|
+
this.dispatcher.registerAdapter(this.organs.mouth);
|
|
45
|
+
}
|
|
46
|
+
this.sessionHistory = new session_history_1.SessionHistoryManager();
|
|
47
|
+
}
|
|
48
|
+
// Direct organ accessors
|
|
49
|
+
get brain() { return this.organs.brain; }
|
|
50
|
+
get memory() { return this.organs.memory; }
|
|
51
|
+
get voice() { return this.organs.voice; }
|
|
52
|
+
get knowledge() { return this.organs.knowledge; }
|
|
53
|
+
get vision() { return this.organs.vision; }
|
|
54
|
+
get behavior() { return this.organs.behavior; }
|
|
55
|
+
get body() { return this.organs.body; }
|
|
56
|
+
get hands() { return this.organs.hands; }
|
|
57
|
+
get ear() { return this.organs.ear; }
|
|
58
|
+
get observation() { return this.organs.observation; }
|
|
59
|
+
set observation(org) { this.organs.observation = org; }
|
|
60
|
+
get mouth() { return this.organs.mouth; }
|
|
61
|
+
get self() { return this.organs.self; }
|
|
62
|
+
get externalKnowledge() { return this.organs.externalKnowledge; }
|
|
63
|
+
async initialize() {
|
|
64
|
+
if (this.organs.memory && typeof this.organs.memory.initialize === 'function') {
|
|
65
|
+
await this.organs.memory.initialize(this.id);
|
|
66
|
+
}
|
|
67
|
+
if (this.organs.hands && typeof this.organs.hands.listTools === 'function') {
|
|
68
|
+
const tools = await this.organs.hands.listTools();
|
|
69
|
+
for (const tool of tools) {
|
|
70
|
+
this.actionPolicy.registerToolDefinition(tool);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
get runtime() {
|
|
75
|
+
if (!this._runtime) {
|
|
76
|
+
this._runtime = new runtime_1.SiduriRuntime(this.id, this.config, this);
|
|
77
|
+
}
|
|
78
|
+
return this._runtime;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.CompanionContainer = CompanionContainer;
|
package/dist/context.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface ConversationContext {
|
|
|
12
12
|
[key: string]: unknown;
|
|
13
13
|
}
|
|
14
14
|
export type SubjectKind = 'actor' | 'companion' | 'configured';
|
|
15
|
+
export type InteractionMode = 'casual' | 'teach' | 'hybrid';
|
|
15
16
|
export interface SubjectRef {
|
|
16
17
|
subjectId: string;
|
|
17
18
|
kind: SubjectKind;
|
|
@@ -23,9 +24,10 @@ export interface RequestContext {
|
|
|
23
24
|
conversation: ConversationContext;
|
|
24
25
|
source?: 'local' | 'external' | string;
|
|
25
26
|
subject?: SubjectRef;
|
|
27
|
+
mode?: InteractionMode;
|
|
26
28
|
metadata?: Record<string, unknown>;
|
|
27
29
|
}
|
|
28
|
-
export type DiagnosticCode = 'legacy_role_removed' | 'anonymous_session_generated' | 'companion_default_mapped_for_bootstrap' | 'actor_scoped_subject_mapped';
|
|
30
|
+
export type DiagnosticCode = 'legacy_role_removed' | 'anonymous_session_generated' | 'companion_default_mapped_for_bootstrap' | 'actor_scoped_subject_mapped' | 'role_escalation_attempt_suppressed' | 'capability_escalation_attempt_suppressed';
|
|
29
31
|
export type ContextErrorCode = 'MISSING_CONTEXT' | 'INVALID_CONTEXT' | 'FORBIDDEN_CONTEXT' | 'AMBIGUOUS_CONTEXT' | 'UNAUTHORIZED_CAPABILITY';
|
|
30
32
|
export interface ContextError {
|
|
31
33
|
code: ContextErrorCode;
|
package/dist/context.js
CHANGED
|
@@ -55,6 +55,19 @@ function validateRequestContext(context) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
if (ctx.mode !== undefined) {
|
|
59
|
+
if (ctx.mode !== 'casual' && ctx.mode !== 'teach' && ctx.mode !== 'hybrid') {
|
|
60
|
+
return {
|
|
61
|
+
accepted: false,
|
|
62
|
+
error: {
|
|
63
|
+
code: 'INVALID_CONTEXT',
|
|
64
|
+
message: `Invalid interaction mode: '${ctx.mode}' (expected 'casual', 'teach', or 'hybrid')`,
|
|
65
|
+
field: 'mode',
|
|
66
|
+
correlationId: ctx.conversation?.correlationId,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
58
71
|
if (missingFields.length > 0) {
|
|
59
72
|
return {
|
|
60
73
|
accepted: false,
|
package/dist/index.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export * from './session-history';
|
|
|
25
25
|
export * from './schema-validator';
|
|
26
26
|
export * from './siduri-db';
|
|
27
27
|
export * from './database';
|
|
28
|
+
export * from './container';
|
|
29
|
+
export * from './perception-pipeline';
|
|
28
30
|
import { EvidenceRecord } from './evidence';
|
|
29
31
|
import { ActionIntent } from './action';
|
|
30
32
|
import { RequestContext } from './context';
|
|
@@ -253,19 +255,25 @@ export interface HealthProbeResult {
|
|
|
253
255
|
}
|
|
254
256
|
export type HealthProbeFn = (context: HealthProbeContext) => Promise<HealthProbeResult> | HealthProbeResult;
|
|
255
257
|
export * from './mouth-types';
|
|
256
|
-
import type { SelfIdentity, PersonalityTraits, SelfDirective, SelfRelationship, LifeInventoryItem, LifeScheduleItem, LifePreference, MemoryClaim, EpisodicEvent } from './siduri-db';
|
|
258
|
+
import type { SelfIdentity, PersonalityTraits, SelfDirective, SelfRelationship, SelfDialogueExample, LifeInventoryItem, LifeScheduleItem, LifePreference, MemoryClaim, EpisodicEvent } from './siduri-db';
|
|
259
|
+
export type { SelfIdentity, PersonalityTraits, SelfDirective, SelfRelationship, SelfDialogueExample, LifeInventoryItem, LifeScheduleItem, LifePreference, MemoryClaim, EpisodicEvent, };
|
|
257
260
|
export interface SelfRepository {
|
|
258
261
|
getIdentity(companionId: string): Promise<SelfIdentity | undefined>;
|
|
259
|
-
getPersonality(companionId: string): Promise<PersonalityTraits>;
|
|
262
|
+
getPersonality?(companionId: string): Promise<PersonalityTraits>;
|
|
260
263
|
getActiveDirectives(companionId: string): Promise<SelfDirective[]>;
|
|
261
264
|
getRelationship(companionId: string, entityId: string): Promise<SelfRelationship | null>;
|
|
265
|
+
getRelationships?(companionId: string): Promise<SelfRelationship[]>;
|
|
266
|
+
getExemplars?(companionId: string): Promise<SelfDialogueExample[]>;
|
|
267
|
+
setExemplars?(companionId: string, exemplars: SelfDialogueExample[]): Promise<void>;
|
|
262
268
|
commitDirectives(companionId: string, directives: SelfDirective[]): Promise<void>;
|
|
263
269
|
updateRelationship(companionId: string, rel: SelfRelationship): Promise<void>;
|
|
264
270
|
disableDirective?(id: string): Promise<void>;
|
|
265
271
|
getActiveSelf?(companionId: string): Promise<{
|
|
266
272
|
identity?: SelfIdentity;
|
|
267
|
-
personality
|
|
273
|
+
personality?: PersonalityTraits;
|
|
268
274
|
directives: SelfDirective[];
|
|
275
|
+
relationships?: SelfRelationship[];
|
|
276
|
+
exemplars?: SelfDialogueExample[];
|
|
269
277
|
}>;
|
|
270
278
|
}
|
|
271
279
|
export interface LifeDatabase {
|
package/dist/index.js
CHANGED
|
@@ -42,5 +42,7 @@ __exportStar(require("./session-history"), exports);
|
|
|
42
42
|
__exportStar(require("./schema-validator"), exports);
|
|
43
43
|
__exportStar(require("./siduri-db"), exports);
|
|
44
44
|
__exportStar(require("./database"), exports);
|
|
45
|
+
__exportStar(require("./container"), exports);
|
|
46
|
+
__exportStar(require("./perception-pipeline"), exports);
|
|
45
47
|
// Mouth (Communication & Output Delivery)
|
|
46
48
|
__exportStar(require("./mouth-types"), exports);
|
package/dist/input-normalizer.js
CHANGED
|
@@ -16,7 +16,9 @@ async function normalizeUserInput(message, roleOrContext = 'OWNER', history = []
|
|
|
16
16
|
}
|
|
17
17
|
const isContextObject = typeof roleOrContext === 'object' && roleOrContext !== null;
|
|
18
18
|
const role = isContextObject
|
|
19
|
-
? (roleOrContext.actor?.authorizationRole === 'viewer'
|
|
19
|
+
? (roleOrContext.actor?.authorizationRole === 'viewer' || roleOrContext.actor?.authenticated === false
|
|
20
|
+
? 'VIEWER'
|
|
21
|
+
: 'OWNER')
|
|
20
22
|
: roleOrContext;
|
|
21
23
|
const requestContext = isContextObject
|
|
22
24
|
? roleOrContext
|