@ifsoul/core 0.7.0 → 0.8.0
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.md +1 -1
- package/dist/packages/model-providers/src/ollama.d.ts +2 -0
- package/dist/packages/model-providers/src/ollama.js +5 -0
- package/dist/packages/model-providers/src/openai.js +47 -1
- package/dist/packages/protocol/src/index.d.ts +1 -1
- package/dist/packages/protocol/src/index.js +1 -1
- package/dist/packages/runtime/src/behavior.d.ts +9 -2
- package/dist/packages/runtime/src/behavior.js +107 -17
- package/dist/packages/runtime/src/participants.d.ts +13 -0
- package/dist/packages/runtime/src/participants.js +55 -21
- package/package.json +1 -1
- package/release.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ compose these public artifacts. See [repository ownership](https://github.com/op
|
|
|
15
15
|
Use Node.js 22.18–24 and an ESM project:
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
|
-
npm install @ifsoul/core@0.
|
|
18
|
+
npm install @ifsoul/core@0.8.0
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
The package includes compiled JavaScript and TypeScript declarations. Import the
|
|
@@ -6,4 +6,6 @@ export declare function createOllamaProvider(options?: {
|
|
|
6
6
|
fetch?: FetchLike;
|
|
7
7
|
limits?: Partial<ProviderLimits>;
|
|
8
8
|
budgetPolicy?: ProviderBudgetPolicy;
|
|
9
|
+
/** Optional local sampling override within the supported product range [0, 1]. */
|
|
10
|
+
temperature?: number;
|
|
9
11
|
}): OllamaClient;
|
|
@@ -23,6 +23,10 @@ const localModel = (value) => typeof value === "string" &&
|
|
|
23
23
|
!value.endsWith(":cloud") &&
|
|
24
24
|
!/(?:^|[/:_-])(?:embed(?:ding)?s?|nomic|bge)(?:[/:_-]|$)/i.test(value);
|
|
25
25
|
export function createOllamaProvider(options = {}) {
|
|
26
|
+
const { temperature } = options;
|
|
27
|
+
if (temperature !== undefined &&
|
|
28
|
+
(!Number.isFinite(temperature) || temperature < 0 || temperature > 1))
|
|
29
|
+
throw new ProviderFailure("configuration");
|
|
26
30
|
const fetcher = options.fetch ?? fetch;
|
|
27
31
|
const discover = async (signal, limits) => {
|
|
28
32
|
const base = loopbackBase(options.baseUrl ?? DEFAULT_OLLAMA_URL);
|
|
@@ -64,6 +68,7 @@ export function createOllamaProvider(options = {}) {
|
|
|
64
68
|
...(contextBudget
|
|
65
69
|
? { num_ctx: contextBudget.windowTokens }
|
|
66
70
|
: {}),
|
|
71
|
+
...(temperature === undefined ? {} : { temperature }),
|
|
67
72
|
},
|
|
68
73
|
}, limits);
|
|
69
74
|
const raw = object(await fetchJSON(fetcher, `${base}/api/chat`, {
|
|
@@ -5,6 +5,52 @@ export const OPENAI_MODELS = [
|
|
|
5
5
|
"gpt-4.1-mini-2025-04-14",
|
|
6
6
|
];
|
|
7
7
|
export const DEFAULT_OPENAI_MODEL = OPENAI_MODELS[0];
|
|
8
|
+
/** Project generation constraints into the documented Structured Outputs subset. */
|
|
9
|
+
function responsesSchema(schema) {
|
|
10
|
+
const copy = structuredClone(schema);
|
|
11
|
+
function visit(value) {
|
|
12
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
13
|
+
return;
|
|
14
|
+
const node = value;
|
|
15
|
+
if (typeof node.const === "string" &&
|
|
16
|
+
(node.type === undefined || node.type === "string") &&
|
|
17
|
+
(node.enum === undefined ||
|
|
18
|
+
(Array.isArray(node.enum) && node.enum.includes(node.const)))) {
|
|
19
|
+
node.type = "string";
|
|
20
|
+
node.enum = [node.const];
|
|
21
|
+
delete node.const;
|
|
22
|
+
}
|
|
23
|
+
if (node.type === "array" || node.items !== undefined)
|
|
24
|
+
delete node.uniqueItems;
|
|
25
|
+
for (const map of [
|
|
26
|
+
node.properties,
|
|
27
|
+
node.patternProperties,
|
|
28
|
+
node.$defs,
|
|
29
|
+
node.definitions,
|
|
30
|
+
])
|
|
31
|
+
if (map && typeof map === "object" && !Array.isArray(map))
|
|
32
|
+
Object.values(map).forEach(visit);
|
|
33
|
+
for (const keyword of ["items", "prefixItems", "anyOf", "allOf", "oneOf"]) {
|
|
34
|
+
const child = node[keyword];
|
|
35
|
+
if (Array.isArray(child))
|
|
36
|
+
child.forEach(visit);
|
|
37
|
+
else
|
|
38
|
+
visit(child);
|
|
39
|
+
}
|
|
40
|
+
for (const keyword of [
|
|
41
|
+
"additionalProperties",
|
|
42
|
+
"contains",
|
|
43
|
+
"if",
|
|
44
|
+
"then",
|
|
45
|
+
"else",
|
|
46
|
+
"not",
|
|
47
|
+
"propertyNames",
|
|
48
|
+
])
|
|
49
|
+
visit(node[keyword]);
|
|
50
|
+
}
|
|
51
|
+
visit(copy);
|
|
52
|
+
return copy;
|
|
53
|
+
}
|
|
8
54
|
/** Credential is confined to this client's closure and Authorization header. */
|
|
9
55
|
export function createOpenAIProvider(options) {
|
|
10
56
|
return {
|
|
@@ -31,7 +77,7 @@ export function createOpenAIProvider(options) {
|
|
|
31
77
|
type: "json_schema",
|
|
32
78
|
name: "ifsoul_content",
|
|
33
79
|
strict: true,
|
|
34
|
-
schema: input.schema,
|
|
80
|
+
schema: responsesSchema(input.schema),
|
|
35
81
|
},
|
|
36
82
|
},
|
|
37
83
|
}, limits);
|
|
@@ -6,6 +6,6 @@ export declare function canonical(value: unknown): string;
|
|
|
6
6
|
export { resolveResourceLimits, BUFFERED_JSON_MAX_BYTES, MAX_TIMER_MS, } from "./resource-policy.js";
|
|
7
7
|
export type { BudgetReport } from "./resource-policy.js";
|
|
8
8
|
/** Current train identifier; wire families require exact kind/version validation. */
|
|
9
|
-
export declare const IFSOUL_VERSION: "0.
|
|
9
|
+
export declare const IFSOUL_VERSION: "0.8.0";
|
|
10
10
|
/** Structural current ModelProvider spec validation; not a provider wire transport. */
|
|
11
11
|
export declare function validateModelProviderDocument(value: unknown): boolean;
|
|
@@ -47,7 +47,7 @@ export function canonical(value) {
|
|
|
47
47
|
}
|
|
48
48
|
export { resolveResourceLimits, BUFFERED_JSON_MAX_BYTES, MAX_TIMER_MS, } from "./resource-policy.js";
|
|
49
49
|
/** Current train identifier; wire families require exact kind/version validation. */
|
|
50
|
-
export const IFSOUL_VERSION = "0.
|
|
50
|
+
export const IFSOUL_VERSION = "0.8.0";
|
|
51
51
|
/** Structural current ModelProvider spec validation; not a provider wire transport. */
|
|
52
52
|
export function validateModelProviderDocument(value) {
|
|
53
53
|
return validators.current_provider(value) === true;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ParticipantState, ParticipantStore, SessionAccess } from "./participants.js";
|
|
1
|
+
import type { AuthorizeSessionEvidence, SessionEvidenceQuery, ParticipantState, ParticipantStore, SessionAccess } from "./participants.js";
|
|
2
2
|
import type { BehaviorConfiguration, BehaviorOperatorPolicy, BehaviorPermissions, BehaviorSettings, BehaviorInspection, BehaviorProposal, BehaviorEventKind, BehaviorEditorSnapshot } from "./behavior-types.js";
|
|
3
3
|
export type * from "./behavior-types.js";
|
|
4
4
|
export declare const DEFAULT_BEHAVIOR_OPERATOR: Readonly<BehaviorOperatorPolicy>;
|
|
@@ -20,12 +20,19 @@ export declare function effectiveBehavior(s: ParticipantState, access: SessionAc
|
|
|
20
20
|
};
|
|
21
21
|
export declare function behaviorPermissions(s: ParticipantState, access: SessionAccess, npcId: string, sessionId: string, eventId?: string, operator?: BehaviorOperatorPolicy): BehaviorPermissions;
|
|
22
22
|
export declare const behaviorRevision: (s: ParticipantState) => number;
|
|
23
|
+
interface EvidenceVisibilityOptions {
|
|
24
|
+
authorizeEvidence?: AuthorizeSessionEvidence;
|
|
25
|
+
purpose?: SessionEvidenceQuery["purpose"];
|
|
26
|
+
targetParticipantId?: string;
|
|
27
|
+
audienceParticipantIds?: string[];
|
|
28
|
+
}
|
|
23
29
|
/** Previously unobserved/unremembered evidence never becomes implicit model context. */
|
|
24
|
-
export declare function behaviorEvidenceVisible(s: ParticipantState, access: SessionAccess, npcId: string, sessionId: string, eventId: string, operator?: BehaviorOperatorPolicy): boolean;
|
|
30
|
+
export declare function behaviorEvidenceVisible(s: ParticipantState, access: SessionAccess, npcId: string, sessionId: string, eventId: string, operator?: BehaviorOperatorPolicy, options?: EvidenceVisibilityOptions): boolean;
|
|
25
31
|
export declare function createBehaviorRuntime(store: ParticipantStore, options?: {
|
|
26
32
|
operator?: BehaviorOperatorPolicy;
|
|
27
33
|
limits?: Partial<BehaviorLimits>;
|
|
28
34
|
ceilings?: Partial<BehaviorLimits>;
|
|
35
|
+
authorizeEvidence?: AuthorizeSessionEvidence;
|
|
29
36
|
}): {
|
|
30
37
|
read(a: SessionAccess): BehaviorEditorSnapshot;
|
|
31
38
|
update(a: SessionAccess, expectedRevision: number, config: BehaviorConfiguration): BehaviorConfiguration;
|
|
@@ -138,21 +138,64 @@ export function behaviorPermissions(s, access, npcId, sessionId, eventId, operat
|
|
|
138
138
|
}
|
|
139
139
|
export const behaviorRevision = (s) => s.behavior?.config.revision ?? 0;
|
|
140
140
|
/** Previously unobserved/unremembered evidence never becomes implicit model context. */
|
|
141
|
-
export function behaviorEvidenceVisible(s, access, npcId, sessionId, eventId, operator = DEFAULT_BEHAVIOR_OPERATOR) {
|
|
142
|
-
sessionFor(s, access, sessionId, npcId);
|
|
143
|
-
const event = s.events.find((
|
|
144
|
-
|
|
145
|
-
|
|
141
|
+
export function behaviorEvidenceVisible(s, access, npcId, sessionId, eventId, operator = DEFAULT_BEHAVIOR_OPERATOR, options = {}) {
|
|
142
|
+
const session = sessionFor(s, access, sessionId, npcId);
|
|
143
|
+
const event = s.events.find((item) => item.eventId === eventId);
|
|
144
|
+
const sourceSession = event && s.sessions.find((item) => item.sessionId === event.sessionId);
|
|
145
|
+
if (!event || !sourceSession)
|
|
146
146
|
return false;
|
|
147
|
+
const restricted = sourceSession.hostId !== access.hostId ||
|
|
148
|
+
options.authorizeEvidence !== undefined;
|
|
147
149
|
if (!s.behavior)
|
|
148
|
-
return
|
|
149
|
-
const observation = s.behavior.observations.find((
|
|
150
|
+
return !restricted;
|
|
151
|
+
const observation = s.behavior.observations.find((item) => item.npcId === npcId && item.eventId === eventId);
|
|
150
152
|
const current = behaviorPermissions(s, access, npcId, sessionId, eventId, operator);
|
|
151
|
-
|
|
152
|
-
observation.permissions.
|
|
153
|
-
|
|
154
|
-
current.
|
|
155
|
-
|
|
153
|
+
if (!observation?.permissions.observe ||
|
|
154
|
+
!observation.permissions.remember ||
|
|
155
|
+
!current.observe ||
|
|
156
|
+
!current.remember)
|
|
157
|
+
return false;
|
|
158
|
+
if (!restricted)
|
|
159
|
+
return true;
|
|
160
|
+
const audienceParticipantIds = [
|
|
161
|
+
...new Set(options.audienceParticipantIds ?? []),
|
|
162
|
+
];
|
|
163
|
+
const requiredAudience = [
|
|
164
|
+
...new Set([
|
|
165
|
+
npcId,
|
|
166
|
+
...audienceParticipantIds,
|
|
167
|
+
...(options.targetParticipantId ? [options.targetParticipantId] : []),
|
|
168
|
+
]),
|
|
169
|
+
];
|
|
170
|
+
const recordedAudience = [
|
|
171
|
+
event.senderId,
|
|
172
|
+
...event.recipientIds,
|
|
173
|
+
...event.observerIds,
|
|
174
|
+
];
|
|
175
|
+
if (!options.authorizeEvidence ||
|
|
176
|
+
!session.recallWorldIds.includes(sourceSession.worldId) ||
|
|
177
|
+
!sourceSession.members.some((member) => member.participantId === npcId && member.role === "npc") ||
|
|
178
|
+
!requiredAudience.every((id) => recordedAudience.includes(id) &&
|
|
179
|
+
sourceSession.members.some((member) => member.participantId === id)))
|
|
180
|
+
return false;
|
|
181
|
+
const sourcePermissions = behaviorPermissions(s, { ...access, hostId: sourceSession.hostId }, npcId, sourceSession.sessionId, eventId, operator);
|
|
182
|
+
if (!sourcePermissions.observe || !sourcePermissions.remember)
|
|
183
|
+
return false;
|
|
184
|
+
try {
|
|
185
|
+
return (options.authorizeEvidence(structuredClone({
|
|
186
|
+
purpose: options.purpose ?? "recall",
|
|
187
|
+
access,
|
|
188
|
+
session,
|
|
189
|
+
npcId,
|
|
190
|
+
targetParticipantId: options.targetParticipantId,
|
|
191
|
+
audienceParticipantIds,
|
|
192
|
+
sourceSession,
|
|
193
|
+
event,
|
|
194
|
+
})) === true);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
156
199
|
}
|
|
157
200
|
function validGoals(value) {
|
|
158
201
|
return (Array.isArray(value) &&
|
|
@@ -213,6 +256,7 @@ const valueKey = (value) => JSON.stringify([
|
|
|
213
256
|
value.worldId ?? null,
|
|
214
257
|
]);
|
|
215
258
|
export function createBehaviorRuntime(store, options = {}) {
|
|
259
|
+
const authorizeEvidence = options.authorizeEvidence;
|
|
216
260
|
const operator = structuredClone(options.operator ?? DEFAULT_BEHAVIOR_OPERATOR);
|
|
217
261
|
if (!keys(operator, [
|
|
218
262
|
"canEditConfig",
|
|
@@ -250,6 +294,37 @@ export function createBehaviorRuntime(store, options = {}) {
|
|
|
250
294
|
};
|
|
251
295
|
const inspect = (s, a, sessionId, npcId) => {
|
|
252
296
|
const session = sessionFor(s, a, sessionId, npcId), b = s.behavior, config = base(s);
|
|
297
|
+
const belongs = (item) => item.npcId === npcId &&
|
|
298
|
+
(!item.worldId || item.worldId === session.worldId);
|
|
299
|
+
const values = (b?.values ?? []).filter(belongs);
|
|
300
|
+
const history = (b?.history ?? []).filter(belongs);
|
|
301
|
+
const deniedKeys = new Set();
|
|
302
|
+
const acceptedKeys = new Set();
|
|
303
|
+
if (authorizeEvidence) {
|
|
304
|
+
for (const entry of history) {
|
|
305
|
+
const key = valueKey(entry);
|
|
306
|
+
if (deniedKeys.has(key) || entry.kind !== "evidence")
|
|
307
|
+
continue;
|
|
308
|
+
const target = entry.targetParticipantId;
|
|
309
|
+
if (!entry.evidenceEventIds.length ||
|
|
310
|
+
!entry.evidenceEventIds.every((eventId) => behaviorEvidenceVisible(s, a, npcId, sessionId, eventId, operator, {
|
|
311
|
+
authorizeEvidence,
|
|
312
|
+
purpose: "relationship-evidence",
|
|
313
|
+
targetParticipantId: target,
|
|
314
|
+
audienceParticipantIds: target ? [target] : [],
|
|
315
|
+
}))) {
|
|
316
|
+
deniedKeys.add(key);
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
acceptedKeys.add(key);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const visible = (item) => {
|
|
323
|
+
if (!authorizeEvidence)
|
|
324
|
+
return true;
|
|
325
|
+
const key = valueKey(item);
|
|
326
|
+
return acceptedKeys.has(key) && !deniedKeys.has(key);
|
|
327
|
+
};
|
|
253
328
|
return {
|
|
254
329
|
npcId,
|
|
255
330
|
sessionId,
|
|
@@ -257,8 +332,8 @@ export function createBehaviorRuntime(store, options = {}) {
|
|
|
257
332
|
stateRevision: b ? revision(b, npcId) : 0,
|
|
258
333
|
effective: effectiveBehavior(s, a, npcId, sessionId, operator),
|
|
259
334
|
parameters: config.parameters,
|
|
260
|
-
values:
|
|
261
|
-
history:
|
|
335
|
+
values: values.filter(visible),
|
|
336
|
+
history: history.filter(visible),
|
|
262
337
|
};
|
|
263
338
|
};
|
|
264
339
|
return {
|
|
@@ -501,13 +576,26 @@ export function createBehaviorRuntime(store, options = {}) {
|
|
|
501
576
|
const e = s.events.find((e) => e.eventId === eventId), sourceSession = e && s.sessions.find((x) => x.sessionId === e.sessionId), observed = b.observations.find((o) => o.eventId === eventId && o.npcId === proposal.npcId);
|
|
502
577
|
if (!e ||
|
|
503
578
|
!sourceSession ||
|
|
504
|
-
sourceSession.hostId !== a.hostId ||
|
|
505
579
|
!session.recallWorldIds.includes(sourceSession.worldId) ||
|
|
506
580
|
(d.scope === "world" &&
|
|
507
581
|
sourceSession.worldId !== session.worldId) ||
|
|
508
582
|
!observed?.permissions.observe ||
|
|
509
583
|
![e.senderId, ...e.recipientIds, ...e.observerIds].includes(proposal.npcId))
|
|
510
584
|
return failure("unauthorized");
|
|
585
|
+
const external = sourceSession.hostId !== a.hostId;
|
|
586
|
+
if (external || authorizeEvidence) {
|
|
587
|
+
const target = change.targetParticipantId;
|
|
588
|
+
if ((external && d.kind !== "relationship") ||
|
|
589
|
+
!behaviorEvidenceVisible(s, a, proposal.npcId, proposal.sessionId, eventId, operator, {
|
|
590
|
+
authorizeEvidence,
|
|
591
|
+
purpose: "relationship-evidence",
|
|
592
|
+
targetParticipantId: target,
|
|
593
|
+
audienceParticipantIds: target ? [target] : [],
|
|
594
|
+
}) ||
|
|
595
|
+
(d.kind === "relationship" &&
|
|
596
|
+
!behaviorPermissions(s, { ...a, hostId: sourceSession.hostId }, proposal.npcId, sourceSession.sessionId, eventId, operator).updateRelationship))
|
|
597
|
+
return failure("unauthorized");
|
|
598
|
+
}
|
|
511
599
|
const current = behaviorPermissions(s, a, proposal.npcId, proposal.sessionId, eventId, operator);
|
|
512
600
|
if (!current.observe ||
|
|
513
601
|
(d.kind === "relationship" &&
|
|
@@ -528,9 +616,11 @@ export function createBehaviorRuntime(store, options = {}) {
|
|
|
528
616
|
: {}),
|
|
529
617
|
...(d.scope === "world" ? { worldId: session.worldId } : {}),
|
|
530
618
|
}, key = valueKey(keyData);
|
|
619
|
+
const priorEvidence = new Set(b.history
|
|
620
|
+
.filter((entry) => valueKey(entry) === key)
|
|
621
|
+
.flatMap((entry) => entry.evidenceEventIds));
|
|
531
622
|
if (changedKeys.has(key) ||
|
|
532
|
-
|
|
533
|
-
h.evidenceEventIds.some((e) => change.evidenceEventIds.includes(e))))
|
|
623
|
+
change.evidenceEventIds.every((eventId) => priorEvidence.has(eventId)))
|
|
534
624
|
return failure("conflict");
|
|
535
625
|
changedKeys.add(key);
|
|
536
626
|
const initialValues = effectiveBehavior(s, a, proposal.npcId, proposal.sessionId, operator).initialValues;
|
|
@@ -43,6 +43,18 @@ export interface AddressedEvent {
|
|
|
43
43
|
observerIds: string[];
|
|
44
44
|
text: string;
|
|
45
45
|
}
|
|
46
|
+
/** Server-supplied authority over one existing event from a different host. */
|
|
47
|
+
export interface SessionEvidenceQuery {
|
|
48
|
+
purpose: "recall" | "relationship-evidence";
|
|
49
|
+
access: SessionAccess;
|
|
50
|
+
session: ParticipantSession;
|
|
51
|
+
npcId: string;
|
|
52
|
+
targetParticipantId?: string;
|
|
53
|
+
audienceParticipantIds: string[];
|
|
54
|
+
sourceSession: ParticipantSession;
|
|
55
|
+
event: AddressedEvent;
|
|
56
|
+
}
|
|
57
|
+
export type AuthorizeSessionEvidence = (query: Readonly<SessionEvidenceQuery>) => boolean;
|
|
46
58
|
export interface SessionTurn {
|
|
47
59
|
turnId: string;
|
|
48
60
|
sessionId: string;
|
|
@@ -82,6 +94,7 @@ export declare class SessionError extends Error {
|
|
|
82
94
|
/** No account system, model, transport or host mechanics are constructed here. */
|
|
83
95
|
export declare function createParticipantRuntime(store: ParticipantStore, options?: {
|
|
84
96
|
behaviorOperator?: BehaviorOperatorPolicy;
|
|
97
|
+
authorizeEvidence?: AuthorizeSessionEvidence;
|
|
85
98
|
}): {
|
|
86
99
|
/** Only trusted verified host mappings may call this; imported IDs never register themselves. */
|
|
87
100
|
register(access: SessionAccess, participant: SessionParticipant): SessionParticipant;
|
|
@@ -30,6 +30,7 @@ const audience = (e) => [
|
|
|
30
30
|
];
|
|
31
31
|
/** No account system, model, transport or host mechanics are constructed here. */
|
|
32
32
|
export function createParticipantRuntime(store, options = {}) {
|
|
33
|
+
const authorizeEvidence = options.authorizeEvidence;
|
|
33
34
|
const behaviorOperator = options.behaviorOperator
|
|
34
35
|
? structuredClone(options.behaviorOperator)
|
|
35
36
|
: undefined;
|
|
@@ -166,13 +167,33 @@ export function createParticipantRuntime(store, options = {}) {
|
|
|
166
167
|
history(access, sessionId, participantId) {
|
|
167
168
|
return run(access, (s) => {
|
|
168
169
|
const current = session(s, access, sessionId);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
const member = current.members.find((item) => item.participantId === participantId);
|
|
171
|
+
if (!member)
|
|
172
|
+
return fail("unauthorized");
|
|
173
|
+
return s.events.filter((event) => {
|
|
174
|
+
const recordedAudience = audience(event);
|
|
175
|
+
if (event.sessionId !== sessionId ||
|
|
176
|
+
!recordedAudience.includes(participantId))
|
|
177
|
+
return false;
|
|
178
|
+
if (!authorizeEvidence)
|
|
179
|
+
return (member.role !== "npc" ||
|
|
180
|
+
behaviorEvidenceVisible(s, access, participantId, sessionId, event.eventId));
|
|
181
|
+
const npcs = current.members.filter((item) => item.role === "npc" &&
|
|
182
|
+
recordedAudience.includes(item.participantId) &&
|
|
183
|
+
(member.role === "player" ||
|
|
184
|
+
item.participantId === participantId));
|
|
185
|
+
const players = current.members.filter((item) => item.role === "player" &&
|
|
186
|
+
recordedAudience.includes(item.participantId));
|
|
187
|
+
return npcs.some((npc) => behaviorEvidenceVisible(s, access, npc.participantId, sessionId, event.eventId, behaviorOperator, {
|
|
188
|
+
authorizeEvidence,
|
|
189
|
+
audienceParticipantIds: recordedAudience.filter((id) => id !== npc.participantId),
|
|
190
|
+
targetParticipantId: member.role === "player"
|
|
191
|
+
? participantId
|
|
192
|
+
: players.length === 1
|
|
193
|
+
? players[0]?.participantId
|
|
194
|
+
: undefined,
|
|
195
|
+
}));
|
|
196
|
+
});
|
|
176
197
|
});
|
|
177
198
|
},
|
|
178
199
|
/** Source-grounded relationship evidence, keyed by verified persistent player ID. */
|
|
@@ -185,11 +206,20 @@ export function createParticipantRuntime(store, options = {}) {
|
|
|
185
206
|
return {
|
|
186
207
|
npcId,
|
|
187
208
|
playerId,
|
|
188
|
-
events: s.events.filter((
|
|
189
|
-
audience(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
209
|
+
events: s.events.filter((event) => {
|
|
210
|
+
if (![npcId, playerId].every((id) => audience(event).includes(id)))
|
|
211
|
+
return false;
|
|
212
|
+
const source = s.sessions.find((item) => item.sessionId === event.sessionId);
|
|
213
|
+
return (!!source &&
|
|
214
|
+
current.recallWorldIds.includes(source.worldId) &&
|
|
215
|
+
((source.hostId === access.hostId && !authorizeEvidence) ||
|
|
216
|
+
behaviorEvidenceVisible(s, access, npcId, sessionId, event.eventId, behaviorOperator, {
|
|
217
|
+
authorizeEvidence,
|
|
218
|
+
purpose: "relationship-evidence",
|
|
219
|
+
targetParticipantId: playerId,
|
|
220
|
+
audienceParticipantIds: [playerId],
|
|
221
|
+
})));
|
|
222
|
+
}),
|
|
193
223
|
};
|
|
194
224
|
});
|
|
195
225
|
},
|
|
@@ -243,16 +273,20 @@ export function createParticipantRuntime(store, options = {}) {
|
|
|
243
273
|
behaviorRevision: behaviorRevision(s),
|
|
244
274
|
});
|
|
245
275
|
// Recall is NPC-private, with stable player identity across worlds. Sharing is checked at completion.
|
|
276
|
+
const participants = [...ticket.recipientIds, ...ticket.observerIds];
|
|
277
|
+
const playerIds = participants.filter((id) => current.members.some((member) => member.role === "player" && member.participantId === id));
|
|
246
278
|
const memories = s.events
|
|
247
|
-
.filter((
|
|
248
|
-
source.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
ticket.npcId,
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
279
|
+
.filter((event) => {
|
|
280
|
+
const source = s.sessions.find((item) => item.sessionId === event.sessionId);
|
|
281
|
+
return (!!source &&
|
|
282
|
+
current.recallWorldIds.includes(source.worldId) &&
|
|
283
|
+
[ticket.npcId, ...participants].every((id) => audience(event).includes(id)) &&
|
|
284
|
+
behaviorEvidenceVisible(s, access, ticket.npcId, ticket.sessionId, event.eventId, source.hostId === access.hostId ? undefined : behaviorOperator, {
|
|
285
|
+
authorizeEvidence,
|
|
286
|
+
targetParticipantId: playerIds.length === 1 ? playerIds[0] : undefined,
|
|
287
|
+
audienceParticipantIds: participants,
|
|
288
|
+
}));
|
|
289
|
+
})
|
|
256
290
|
.slice(-64);
|
|
257
291
|
if (new TextEncoder().encode(JSON.stringify({ ticket, trigger, memories })).length > 65536)
|
|
258
292
|
return fail("limit");
|
package/package.json
CHANGED
package/release.json
CHANGED