@ifsoul/core 0.3.0 → 0.5.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 +30 -33
- package/dist/packages/memory/src/sql.js +54 -5
- package/dist/packages/protocol/src/generated/validators.cjs +1 -1
- package/dist/packages/protocol/src/index.d.ts +1 -6
- package/dist/packages/protocol/src/index.js +3 -16
- package/dist/packages/protocol/src/types.d.ts +1 -1
- package/dist/packages/runtime/src/behavior-types.d.ts +150 -0
- package/dist/packages/runtime/src/behavior-types.js +1 -0
- package/dist/packages/runtime/src/behavior.d.ts +40 -0
- package/dist/packages/runtime/src/behavior.js +621 -0
- package/dist/packages/runtime/src/group-scheduler.d.ts +165 -0
- package/dist/packages/runtime/src/group-scheduler.js +647 -0
- package/dist/packages/runtime/src/index.d.ts +4 -0
- package/dist/packages/runtime/src/index.js +4 -1
- package/dist/packages/runtime/src/participants.d.ts +107 -0
- package/dist/packages/runtime/src/participants.js +309 -0
- package/dist/packages/runtime/src/types.d.ts +1 -0
- package/package.json +2 -3
- package/packages/protocol/schemas/current/archive.schema.json +16 -16
- package/packages/protocol/schemas/current/character.schema.json +21 -21
- package/packages/protocol/schemas/current/common.schema.json +15 -15
- package/packages/protocol/schemas/current/context.schema.json +24 -24
- package/packages/protocol/schemas/current/conversation.schema.json +39 -39
- package/packages/protocol/schemas/current/environment.schema.json +18 -18
- package/packages/protocol/schemas/current/memory.schema.json +66 -66
- package/packages/protocol/schemas/current/model-provider.schema.json +40 -40
- package/packages/protocol/schemas/current/planning.schema.json +61 -61
- package/packages/protocol/schemas/current/provider-common.schema.json +6 -6
- package/release.json +7 -12
- package/specs/README.md +3 -86
- package/specs/current/README.md +3 -5
- package/specs/current/examples/archive-fork.json +12 -12
- package/specs/current/examples/archive-resume.json +12 -12
- package/specs/current/examples/character-instance.json +1 -1
- package/specs/current/examples/character-template.json +1 -1
- package/specs/current/examples/context-after-inspection.json +1 -1
- package/specs/current/examples/context-chat.json +1 -1
- package/specs/current/examples/context-daily.json +1 -1
- package/specs/current/examples/context-no-speech.json +1 -1
- package/specs/current/examples/conversation-delivery.json +1 -1
- package/specs/current/examples/conversation-live-request.json +1 -1
- package/specs/current/examples/conversation-live-response.json +1 -1
- package/specs/current/examples/conversation-no-speech-request.json +1 -1
- package/specs/current/examples/conversation-no-speech-response.json +1 -1
- package/specs/current/examples/conversation-requested-request.json +1 -1
- package/specs/current/examples/conversation-requested-response.json +1 -1
- package/specs/current/examples/environment-chat.json +1 -1
- package/specs/current/examples/environment-game.json +1 -1
- package/specs/current/examples/environment-no-speech.json +1 -1
- package/specs/current/examples/intent-checkpoint.json +1 -1
- package/specs/current/examples/memory-episode.json +1 -1
- package/specs/current/examples/memory-event-conversation.json +1 -1
- package/specs/current/examples/memory-event-observed.json +2 -2
- package/specs/current/examples/memory-event-plan-proposal.json +2 -2
- package/specs/current/examples/memory-event-proposed.json +2 -2
- package/specs/current/examples/memory-fact.json +1 -1
- package/specs/current/examples/memory-relationship.json +1 -1
- package/specs/current/examples/memory-working-context.json +1 -1
- package/specs/current/examples/plan-cancel-request.json +1 -1
- package/specs/current/examples/plan-cancel-result.json +1 -1
- package/specs/current/examples/plan-daily.json +1 -1
- package/specs/current/examples/plan-partial-decision.json +1 -1
- package/specs/current/examples/portable-memory.jsonl +9 -9
- package/specs/current/examples/task-outcome.json +1 -1
- package/specs/current/examples/task-progress.json +1 -1
- package/specs/current/model-provider/examples/capabilities.json +1 -1
- package/specs/current/model-provider/examples/error.json +1 -1
- package/specs/current/model-provider/examples/request.json +1 -1
- package/specs/current/model-provider/examples/response.json +1 -1
- package/docs/npm-publishing.md +0 -44
- package/packages/protocol/schemas/action-result.schema.json +0 -187
- package/packages/protocol/schemas/action.schema.json +0 -68
- package/packages/protocol/schemas/archive-manifest.schema.json +0 -126
- package/packages/protocol/schemas/capability.schema.json +0 -131
- package/packages/protocol/schemas/character-instance.schema.json +0 -73
- package/packages/protocol/schemas/character-template.schema.json +0 -67
- package/packages/protocol/schemas/common.schema.json +0 -154
- package/packages/protocol/schemas/derived-memory.schema.json +0 -145
- package/packages/protocol/schemas/memory-event.schema.json +0 -123
- package/packages/protocol/schemas/model-provider.schema.json +0 -326
- package/packages/protocol/schemas/perception.schema.json +0 -89
- package/packages/protocol/schemas/permission-policy.schema.json +0 -153
- package/packages/protocol/schemas/v0.2/archive.schema.json +0 -205
- package/packages/protocol/schemas/v0.2/character.schema.json +0 -344
- package/packages/protocol/schemas/v0.2/common.schema.json +0 -239
- package/packages/protocol/schemas/v0.2/context.schema.json +0 -230
- package/packages/protocol/schemas/v0.2/conversation.schema.json +0 -340
- package/packages/protocol/schemas/v0.2/environment.schema.json +0 -244
- package/packages/protocol/schemas/v0.2/memory.schema.json +0 -495
- package/packages/protocol/schemas/v0.2/planning.schema.json +0 -443
- package/packages/protocol/schemas/world-event.schema.json +0 -74
- package/specs/character/README.md +0 -84
- package/specs/character/examples/archive-manifest.json +0 -32
- package/specs/character/examples/character-instance.json +0 -20
- package/specs/character/examples/character-template.json +0 -19
- package/specs/memory/README.md +0 -80
- package/specs/memory/examples/derived-memory.json +0 -21
- package/specs/memory/examples/memory-event.json +0 -35
- package/specs/model-provider/README.md +0 -85
- package/specs/model-provider/examples/capabilities.json +0 -10
- package/specs/model-provider/examples/error.json +0 -18
- package/specs/model-provider/examples/request.json +0 -17
- package/specs/model-provider/examples/response.json +0 -19
- package/specs/v0.2/README.md +0 -421
- package/specs/v0.2/examples/archive-fork.json +0 -64
- package/specs/v0.2/examples/archive-resume.json +0 -63
- package/specs/v0.2/examples/character-instance.json +0 -57
- package/specs/v0.2/examples/character-template.json +0 -45
- package/specs/v0.2/examples/context-after-inspection.json +0 -44
- package/specs/v0.2/examples/context-chat.json +0 -27
- package/specs/v0.2/examples/context-daily.json +0 -69
- package/specs/v0.2/examples/context-no-speech.json +0 -27
- package/specs/v0.2/examples/conversation-delivery.json +0 -17
- package/specs/v0.2/examples/conversation-live-request.json +0 -31
- package/specs/v0.2/examples/conversation-live-response.json +0 -23
- package/specs/v0.2/examples/conversation-no-speech-request.json +0 -23
- package/specs/v0.2/examples/conversation-no-speech-response.json +0 -22
- package/specs/v0.2/examples/conversation-requested-request.json +0 -31
- package/specs/v0.2/examples/conversation-requested-response.json +0 -23
- package/specs/v0.2/examples/environment-chat.json +0 -43
- package/specs/v0.2/examples/environment-game.json +0 -80
- package/specs/v0.2/examples/environment-no-speech.json +0 -47
- package/specs/v0.2/examples/intent-checkpoint.json +0 -29
- package/specs/v0.2/examples/memory-episode.json +0 -28
- package/specs/v0.2/examples/memory-event-conversation.json +0 -32
- package/specs/v0.2/examples/memory-event-observed.json +0 -50
- package/specs/v0.2/examples/memory-event-plan-proposal.json +0 -82
- package/specs/v0.2/examples/memory-event-proposed.json +0 -53
- package/specs/v0.2/examples/memory-fact.json +0 -23
- package/specs/v0.2/examples/memory-relationship.json +0 -25
- package/specs/v0.2/examples/memory-working-context.json +0 -24
- package/specs/v0.2/examples/plan-cancel-request.json +0 -16
- package/specs/v0.2/examples/plan-cancel-result.json +0 -21
- package/specs/v0.2/examples/plan-daily.json +0 -57
- package/specs/v0.2/examples/plan-partial-decision.json +0 -28
- package/specs/v0.2/examples/portable-memory.jsonl +0 -9
- package/specs/v0.2/examples/task-outcome.json +0 -21
- package/specs/v0.2/examples/task-progress.json +0 -19
- package/specs/world-adapter/README.md +0 -114
- package/specs/world-adapter/examples/action-result-unknown.json +0 -23
- package/specs/world-adapter/examples/action-result.json +0 -45
- package/specs/world-adapter/examples/action.json +0 -17
- package/specs/world-adapter/examples/capability.json +0 -44
- package/specs/world-adapter/examples/perception.json +0 -30
- package/specs/world-adapter/examples/permission-decision.json +0 -14
- package/specs/world-adapter/examples/permission-policy.json +0 -17
- package/specs/world-adapter/examples/world-event.json +0 -25
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { BehaviorOperatorPolicy } from "./behavior-types.js";
|
|
2
|
+
import type { BehaviorState } from "./behavior-types.js";
|
|
3
|
+
import type { PlanningState } from "./group-scheduler.js";
|
|
4
|
+
/** Trusted session domain. Roots authenticate callers before constructing this access. */
|
|
5
|
+
export interface SessionAccess {
|
|
6
|
+
principalId: string;
|
|
7
|
+
hostId: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SessionParticipant {
|
|
10
|
+
participantId: string;
|
|
11
|
+
kind: "npc" | "player";
|
|
12
|
+
/** Trusted host subject, never a display name or imported character identifier. */
|
|
13
|
+
subjectId: string;
|
|
14
|
+
}
|
|
15
|
+
export interface SessionMember {
|
|
16
|
+
participantId: string;
|
|
17
|
+
role: "npc" | "player";
|
|
18
|
+
embodimentId?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ParticipantSession {
|
|
21
|
+
sessionId: string;
|
|
22
|
+
revision: number;
|
|
23
|
+
hostId: string;
|
|
24
|
+
worldId: string;
|
|
25
|
+
recallWorldIds: string[];
|
|
26
|
+
members: SessionMember[];
|
|
27
|
+
groups: {
|
|
28
|
+
groupId: string;
|
|
29
|
+
participantIds: string[];
|
|
30
|
+
}[];
|
|
31
|
+
channels: {
|
|
32
|
+
channelId: string;
|
|
33
|
+
participantIds: string[];
|
|
34
|
+
}[];
|
|
35
|
+
}
|
|
36
|
+
export interface AddressedEvent {
|
|
37
|
+
eventId: string;
|
|
38
|
+
sessionId: string;
|
|
39
|
+
revision: number;
|
|
40
|
+
channelId: string;
|
|
41
|
+
senderId: string;
|
|
42
|
+
recipientIds: string[];
|
|
43
|
+
observerIds: string[];
|
|
44
|
+
text: string;
|
|
45
|
+
}
|
|
46
|
+
export interface SessionTurn {
|
|
47
|
+
turnId: string;
|
|
48
|
+
sessionId: string;
|
|
49
|
+
revision: number;
|
|
50
|
+
npcId: string;
|
|
51
|
+
eventId: string;
|
|
52
|
+
recipientIds: string[];
|
|
53
|
+
observerIds: string[];
|
|
54
|
+
}
|
|
55
|
+
export interface ParticipantState {
|
|
56
|
+
format: "ifsoul-participants/0.4.0";
|
|
57
|
+
planning?: PlanningState;
|
|
58
|
+
behavior?: BehaviorState;
|
|
59
|
+
participants: SessionParticipant[];
|
|
60
|
+
sessions: ParticipantSession[];
|
|
61
|
+
events: AddressedEvent[];
|
|
62
|
+
transitions: {
|
|
63
|
+
transitionId: string;
|
|
64
|
+
session: ParticipantSession;
|
|
65
|
+
}[];
|
|
66
|
+
turns: {
|
|
67
|
+
ticket: SessionTurn;
|
|
68
|
+
output?: AddressedEvent;
|
|
69
|
+
cancelled?: boolean;
|
|
70
|
+
behaviorRevision?: number;
|
|
71
|
+
}[];
|
|
72
|
+
}
|
|
73
|
+
/** Must atomically commit the draft only when work succeeds; namespace by verified principal. */
|
|
74
|
+
export interface ParticipantStore {
|
|
75
|
+
transact<T>(principalId: string, work: (state: ParticipantState) => T): T;
|
|
76
|
+
}
|
|
77
|
+
export declare const emptyParticipantState: () => ParticipantState;
|
|
78
|
+
export declare class SessionError extends Error {
|
|
79
|
+
readonly code: "unauthorized" | "invalid" | "stale" | "conflict" | "busy" | "limit";
|
|
80
|
+
constructor(code: "unauthorized" | "invalid" | "stale" | "conflict" | "busy" | "limit");
|
|
81
|
+
}
|
|
82
|
+
/** No account system, model, transport or host mechanics are constructed here. */
|
|
83
|
+
export declare function createParticipantRuntime(store: ParticipantStore, options?: {
|
|
84
|
+
behaviorOperator?: BehaviorOperatorPolicy;
|
|
85
|
+
}): {
|
|
86
|
+
/** Only trusted verified host mappings may call this; imported IDs never register themselves. */
|
|
87
|
+
register(access: SessionAccess, participant: SessionParticipant): SessionParticipant;
|
|
88
|
+
/** Full explicit replacement is a trusted transition, including leave/join/group/channel/host handoff. */
|
|
89
|
+
transition(access: SessionAccess, transitionId: string, expectedRevision: number, next: ParticipantSession): ParticipantSession;
|
|
90
|
+
append(access: SessionAccess, event: AddressedEvent): AddressedEvent;
|
|
91
|
+
/** Owner-private history; never use a group log as a model context. */
|
|
92
|
+
history(access: SessionAccess, sessionId: string, participantId: string): AddressedEvent[];
|
|
93
|
+
/** Source-grounded relationship evidence, keyed by verified persistent player ID. */
|
|
94
|
+
relationshipEvidence(access: SessionAccess, sessionId: string, npcId: string, playerId: string): {
|
|
95
|
+
npcId: string;
|
|
96
|
+
playerId: string;
|
|
97
|
+
events: AddressedEvent[];
|
|
98
|
+
};
|
|
99
|
+
begin(access: SessionAccess, ticket: SessionTurn): {
|
|
100
|
+
ticket: SessionTurn;
|
|
101
|
+
trigger: AddressedEvent;
|
|
102
|
+
output: AddressedEvent | undefined;
|
|
103
|
+
memories: AddressedEvent[];
|
|
104
|
+
};
|
|
105
|
+
cancel(access: SessionAccess, ticket: SessionTurn): undefined;
|
|
106
|
+
complete(access: SessionAccess, ticket: SessionTurn, output: AddressedEvent): AddressedEvent;
|
|
107
|
+
};
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { behaviorPermissions, behaviorRevision, behaviorEvidenceVisible, } from "./behavior.js";
|
|
2
|
+
export const emptyParticipantState = () => ({
|
|
3
|
+
format: "ifsoul-participants/0.4.0",
|
|
4
|
+
participants: [],
|
|
5
|
+
sessions: [],
|
|
6
|
+
events: [],
|
|
7
|
+
transitions: [],
|
|
8
|
+
turns: [],
|
|
9
|
+
});
|
|
10
|
+
export class SessionError extends Error {
|
|
11
|
+
code;
|
|
12
|
+
constructor(code) {
|
|
13
|
+
super(`Session operation rejected: ${code}`);
|
|
14
|
+
this.code = code;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const fail = (code) => {
|
|
18
|
+
throw new SessionError(code);
|
|
19
|
+
};
|
|
20
|
+
const id = (x) => typeof x === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(x);
|
|
21
|
+
const ids = (x) => Array.isArray(x) &&
|
|
22
|
+
x.length <= 64 &&
|
|
23
|
+
x.every(id) &&
|
|
24
|
+
new Set(x).size === x.length;
|
|
25
|
+
const equal = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
26
|
+
const audience = (e) => [
|
|
27
|
+
e.senderId,
|
|
28
|
+
...e.recipientIds,
|
|
29
|
+
...e.observerIds,
|
|
30
|
+
];
|
|
31
|
+
/** No account system, model, transport or host mechanics are constructed here. */
|
|
32
|
+
export function createParticipantRuntime(store, options = {}) {
|
|
33
|
+
const behaviorOperator = options.behaviorOperator
|
|
34
|
+
? structuredClone(options.behaviorOperator)
|
|
35
|
+
: undefined;
|
|
36
|
+
const run = (access, work) => {
|
|
37
|
+
if (!access || !id(access.principalId) || !id(access.hostId))
|
|
38
|
+
fail("unauthorized");
|
|
39
|
+
return store.transact(access.principalId, (state) => {
|
|
40
|
+
if (state.format !== "ifsoul-participants/0.4.0")
|
|
41
|
+
fail("invalid");
|
|
42
|
+
return structuredClone(work(state));
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
const session = (s, a, sessionId, revision) => {
|
|
46
|
+
const found = s.sessions.find((x) => x.sessionId === sessionId);
|
|
47
|
+
if (!found || found.hostId !== a.hostId)
|
|
48
|
+
return fail("unauthorized");
|
|
49
|
+
if (revision !== undefined && found.revision !== revision)
|
|
50
|
+
fail("stale");
|
|
51
|
+
return found;
|
|
52
|
+
};
|
|
53
|
+
const eventValid = (s, a, e) => {
|
|
54
|
+
if (!e ||
|
|
55
|
+
!id(e.eventId) ||
|
|
56
|
+
!id(e.senderId) ||
|
|
57
|
+
!id(e.channelId) ||
|
|
58
|
+
!ids(e.recipientIds) ||
|
|
59
|
+
!ids(e.observerIds) ||
|
|
60
|
+
typeof e.text !== "string" ||
|
|
61
|
+
!e.text.length ||
|
|
62
|
+
e.text.length > 4096)
|
|
63
|
+
fail("invalid");
|
|
64
|
+
const current = session(s, a, e.sessionId, e.revision);
|
|
65
|
+
const channel = current.channels.find((x) => x.channelId === e.channelId);
|
|
66
|
+
if (!channel ||
|
|
67
|
+
audience(e).some((p) => !channel.participantIds.includes(p) ||
|
|
68
|
+
!current.members.some((m) => m.participantId === p)))
|
|
69
|
+
fail("unauthorized");
|
|
70
|
+
return current;
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
/** Only trusted verified host mappings may call this; imported IDs never register themselves. */
|
|
74
|
+
register(access, participant) {
|
|
75
|
+
return run(access, (s) => {
|
|
76
|
+
if (!participant ||
|
|
77
|
+
!id(participant.participantId) ||
|
|
78
|
+
!id(participant.subjectId) ||
|
|
79
|
+
!["npc", "player"].includes(participant.kind))
|
|
80
|
+
fail("invalid");
|
|
81
|
+
const previous = s.participants.find((p) => p.participantId === participant.participantId ||
|
|
82
|
+
(p.subjectId === participant.subjectId &&
|
|
83
|
+
p.kind === participant.kind));
|
|
84
|
+
if (previous && !equal(previous, participant))
|
|
85
|
+
fail("conflict");
|
|
86
|
+
if (!previous) {
|
|
87
|
+
if (s.participants.length >= 256)
|
|
88
|
+
fail("limit");
|
|
89
|
+
s.participants.push(structuredClone(participant));
|
|
90
|
+
}
|
|
91
|
+
return participant;
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
/** Full explicit replacement is a trusted transition, including leave/join/group/channel/host handoff. */
|
|
95
|
+
transition(access, transitionId, expectedRevision, next) {
|
|
96
|
+
return run(access, (s) => {
|
|
97
|
+
if (!id(transitionId) ||
|
|
98
|
+
!next ||
|
|
99
|
+
!id(next.sessionId) ||
|
|
100
|
+
!id(next.hostId) ||
|
|
101
|
+
!id(next.worldId) ||
|
|
102
|
+
!ids(next.recallWorldIds) ||
|
|
103
|
+
!next.recallWorldIds.includes(next.worldId) ||
|
|
104
|
+
!Number.isSafeInteger(expectedRevision) ||
|
|
105
|
+
expectedRevision < 0 ||
|
|
106
|
+
next.revision !== expectedRevision + 1)
|
|
107
|
+
fail("invalid");
|
|
108
|
+
const old = s.sessions.find((x) => x.sessionId === next.sessionId);
|
|
109
|
+
if ((old && old.hostId !== access.hostId) ||
|
|
110
|
+
(!old && next.hostId !== access.hostId))
|
|
111
|
+
fail("unauthorized");
|
|
112
|
+
const receipt = s.transitions.find((x) => x.transitionId === transitionId);
|
|
113
|
+
if (receipt) {
|
|
114
|
+
if (!equal(receipt.session, next))
|
|
115
|
+
fail("conflict");
|
|
116
|
+
return receipt.session;
|
|
117
|
+
}
|
|
118
|
+
if ((old?.revision ?? 0) !== expectedRevision)
|
|
119
|
+
fail("stale");
|
|
120
|
+
if (old && old.worldId !== next.worldId)
|
|
121
|
+
fail("conflict");
|
|
122
|
+
if (!Array.isArray(next.members) ||
|
|
123
|
+
next.members.length > 64 ||
|
|
124
|
+
!ids(next.members.map((m) => m.participantId)) ||
|
|
125
|
+
next.members.some((m) => !s.participants.some((p) => p.participantId === m.participantId && p.kind === m.role) ||
|
|
126
|
+
(m.embodimentId !== undefined && !id(m.embodimentId))))
|
|
127
|
+
fail("invalid");
|
|
128
|
+
for (const [list, key] of [
|
|
129
|
+
[next.groups, "groupId"],
|
|
130
|
+
[next.channels, "channelId"],
|
|
131
|
+
]) {
|
|
132
|
+
if (!Array.isArray(list) ||
|
|
133
|
+
list.length > 64 ||
|
|
134
|
+
!ids(list.map((x) => x[key])))
|
|
135
|
+
fail("invalid");
|
|
136
|
+
if (list.some((x) => !ids(x.participantIds) ||
|
|
137
|
+
x.participantIds.some((p) => !next.members.some((m) => m.participantId === p))))
|
|
138
|
+
fail("unauthorized");
|
|
139
|
+
}
|
|
140
|
+
if (s.transitions.length >= 2048 || (!old && s.sessions.length >= 128))
|
|
141
|
+
fail("limit");
|
|
142
|
+
if (old)
|
|
143
|
+
s.sessions[s.sessions.indexOf(old)] = structuredClone(next);
|
|
144
|
+
else
|
|
145
|
+
s.sessions.push(structuredClone(next));
|
|
146
|
+
s.transitions.push({ transitionId, session: structuredClone(next) });
|
|
147
|
+
return next;
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
append(access, event) {
|
|
151
|
+
return run(access, (s) => {
|
|
152
|
+
eventValid(s, access, event);
|
|
153
|
+
const prior = s.events.find((e) => e.eventId === event.eventId);
|
|
154
|
+
if (prior) {
|
|
155
|
+
if (!equal(prior, event))
|
|
156
|
+
fail("conflict");
|
|
157
|
+
return prior;
|
|
158
|
+
}
|
|
159
|
+
if (s.events.length >= 2048)
|
|
160
|
+
fail("limit");
|
|
161
|
+
s.events.push(structuredClone(event));
|
|
162
|
+
return event;
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
/** Owner-private history; never use a group log as a model context. */
|
|
166
|
+
history(access, sessionId, participantId) {
|
|
167
|
+
return run(access, (s) => {
|
|
168
|
+
const current = session(s, access, sessionId);
|
|
169
|
+
if (!current.members.some((m) => m.participantId === participantId))
|
|
170
|
+
fail("unauthorized");
|
|
171
|
+
return s.events.filter((e) => e.sessionId === sessionId &&
|
|
172
|
+
audience(e).includes(participantId) &&
|
|
173
|
+
(current.members.find((m) => m.participantId === participantId)
|
|
174
|
+
?.role !== "npc" ||
|
|
175
|
+
behaviorEvidenceVisible(s, access, participantId, sessionId, e.eventId)));
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
/** Source-grounded relationship evidence, keyed by verified persistent player ID. */
|
|
179
|
+
relationshipEvidence(access, sessionId, npcId, playerId) {
|
|
180
|
+
return run(access, (s) => {
|
|
181
|
+
const current = session(s, access, sessionId);
|
|
182
|
+
if (!current.members.some((m) => m.participantId === npcId && m.role === "npc") ||
|
|
183
|
+
!s.participants.some((p) => p.participantId === playerId && p.kind === "player"))
|
|
184
|
+
return fail("unauthorized");
|
|
185
|
+
return {
|
|
186
|
+
npcId,
|
|
187
|
+
playerId,
|
|
188
|
+
events: s.events.filter((e) => audience(e).includes(npcId) &&
|
|
189
|
+
audience(e).includes(playerId) &&
|
|
190
|
+
s.sessions.some((source) => source.sessionId === e.sessionId &&
|
|
191
|
+
source.hostId === access.hostId &&
|
|
192
|
+
current.recallWorldIds.includes(source.worldId))),
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
},
|
|
196
|
+
begin(access, ticket) {
|
|
197
|
+
return run(access, (s) => {
|
|
198
|
+
if (!ticket ||
|
|
199
|
+
!id(ticket.turnId) ||
|
|
200
|
+
!id(ticket.npcId) ||
|
|
201
|
+
!ids(ticket.recipientIds) ||
|
|
202
|
+
!ids(ticket.observerIds))
|
|
203
|
+
fail("invalid");
|
|
204
|
+
const current = session(s, access, ticket.sessionId, ticket.revision);
|
|
205
|
+
if (!current.members.some((m) => m.participantId === ticket.npcId && m.role === "npc"))
|
|
206
|
+
fail("unauthorized");
|
|
207
|
+
const trigger = s.events.find((e) => e.eventId === ticket.eventId &&
|
|
208
|
+
e.sessionId === ticket.sessionId &&
|
|
209
|
+
e.revision === ticket.revision);
|
|
210
|
+
if (!trigger ||
|
|
211
|
+
!audience(trigger).includes(ticket.npcId) ||
|
|
212
|
+
[...ticket.recipientIds, ...ticket.observerIds].some((p) => !audience(trigger).includes(p)))
|
|
213
|
+
return fail("unauthorized");
|
|
214
|
+
const policy = behaviorPermissions(s, access, ticket.npcId, ticket.sessionId, ticket.eventId, behaviorOperator);
|
|
215
|
+
if (!policy.observe ||
|
|
216
|
+
!policy.speak ||
|
|
217
|
+
(s.behavior &&
|
|
218
|
+
!s.behavior.observations.some((o) => o.npcId === ticket.npcId &&
|
|
219
|
+
o.eventId === ticket.eventId &&
|
|
220
|
+
o.permissions.observe)))
|
|
221
|
+
return fail("unauthorized");
|
|
222
|
+
const replay = s.turns.find((t) => t.ticket.turnId === ticket.turnId);
|
|
223
|
+
if (replay) {
|
|
224
|
+
if (!equal(replay.ticket, ticket) || !replay.output)
|
|
225
|
+
return fail("conflict");
|
|
226
|
+
return {
|
|
227
|
+
ticket,
|
|
228
|
+
trigger,
|
|
229
|
+
memories: [],
|
|
230
|
+
output: replay.output,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
if (s.turns.some((t) => t.ticket.npcId === ticket.npcId &&
|
|
234
|
+
!t.output &&
|
|
235
|
+
!t.cancelled &&
|
|
236
|
+
s.sessions.some((x) => x.sessionId === t.ticket.sessionId &&
|
|
237
|
+
x.revision === t.ticket.revision)))
|
|
238
|
+
fail("busy");
|
|
239
|
+
if (s.turns.length >= 2048)
|
|
240
|
+
fail("limit");
|
|
241
|
+
s.turns.push({
|
|
242
|
+
ticket: structuredClone(ticket),
|
|
243
|
+
behaviorRevision: behaviorRevision(s),
|
|
244
|
+
});
|
|
245
|
+
// Recall is NPC-private, with stable player identity across worlds. Sharing is checked at completion.
|
|
246
|
+
const memories = s.events
|
|
247
|
+
.filter((e) => s.sessions.some((source) => source.sessionId === e.sessionId &&
|
|
248
|
+
source.hostId === access.hostId &&
|
|
249
|
+
current.recallWorldIds.includes(source.worldId)) &&
|
|
250
|
+
behaviorEvidenceVisible(s, access, ticket.npcId, ticket.sessionId, e.eventId) &&
|
|
251
|
+
[
|
|
252
|
+
ticket.npcId,
|
|
253
|
+
...ticket.recipientIds,
|
|
254
|
+
...ticket.observerIds,
|
|
255
|
+
].every((p) => audience(e).includes(p)))
|
|
256
|
+
.slice(-64);
|
|
257
|
+
if (new TextEncoder().encode(JSON.stringify({ ticket, trigger, memories })).length > 65536)
|
|
258
|
+
return fail("limit");
|
|
259
|
+
return {
|
|
260
|
+
ticket,
|
|
261
|
+
trigger,
|
|
262
|
+
output: undefined,
|
|
263
|
+
memories,
|
|
264
|
+
};
|
|
265
|
+
});
|
|
266
|
+
},
|
|
267
|
+
cancel(access, ticket) {
|
|
268
|
+
return run(access, (s) => {
|
|
269
|
+
session(s, access, ticket.sessionId, ticket.revision);
|
|
270
|
+
const pending = s.turns.find((t) => t.ticket.turnId === ticket.turnId);
|
|
271
|
+
if (!pending || !equal(pending.ticket, ticket) || pending.output)
|
|
272
|
+
return fail("conflict");
|
|
273
|
+
pending.cancelled = true;
|
|
274
|
+
});
|
|
275
|
+
},
|
|
276
|
+
complete(access, ticket, output) {
|
|
277
|
+
return run(access, (s) => {
|
|
278
|
+
const pending = s.turns.find((t) => t.ticket.turnId === ticket.turnId);
|
|
279
|
+
if (!pending || pending.cancelled || !equal(pending.ticket, ticket))
|
|
280
|
+
return fail("unauthorized");
|
|
281
|
+
session(s, access, ticket.sessionId, ticket.revision);
|
|
282
|
+
if ((pending.behaviorRevision ?? 0) !== behaviorRevision(s) ||
|
|
283
|
+
!behaviorPermissions(s, access, ticket.npcId, ticket.sessionId, ticket.eventId, behaviorOperator).speak)
|
|
284
|
+
return fail("stale");
|
|
285
|
+
if (pending.output) {
|
|
286
|
+
if (!equal(pending.output, output))
|
|
287
|
+
fail("conflict");
|
|
288
|
+
return pending.output;
|
|
289
|
+
}
|
|
290
|
+
eventValid(s, access, output);
|
|
291
|
+
if (output.sessionId !== ticket.sessionId ||
|
|
292
|
+
output.revision !== ticket.revision ||
|
|
293
|
+
output.senderId !== ticket.npcId ||
|
|
294
|
+
output.channelId !==
|
|
295
|
+
s.events.find((e) => e.eventId === ticket.eventId)?.channelId ||
|
|
296
|
+
!equal(output.recipientIds, ticket.recipientIds) ||
|
|
297
|
+
!equal(output.observerIds, ticket.observerIds))
|
|
298
|
+
fail("unauthorized");
|
|
299
|
+
if (s.events.some((e) => e.eventId === output.eventId))
|
|
300
|
+
fail("conflict");
|
|
301
|
+
if (s.events.length >= 2048)
|
|
302
|
+
fail("limit");
|
|
303
|
+
s.events.push(structuredClone(output));
|
|
304
|
+
pending.output = structuredClone(output);
|
|
305
|
+
return output;
|
|
306
|
+
});
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
}
|
|
@@ -18,6 +18,7 @@ export interface TrustedSession {
|
|
|
18
18
|
access: TrustedAccess;
|
|
19
19
|
}
|
|
20
20
|
export interface ControllerInput {
|
|
21
|
+
behavior?: Pick<import("./behavior-types.js").BehaviorInspection, "configRevision" | "effective" | "parameters" | "values">;
|
|
21
22
|
portrayal?: {
|
|
22
23
|
characterId: string;
|
|
23
24
|
name: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ifsoul/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Experimental public IfSoul protocol, runtime, memory and provider primitives",
|
|
@@ -22,8 +22,7 @@
|
|
|
22
22
|
"LICENSE",
|
|
23
23
|
"LICENSES/CC-BY-4.0.txt",
|
|
24
24
|
"LICENSES/CC0-1.0.txt",
|
|
25
|
-
"release.json"
|
|
26
|
-
"docs/npm-publishing.md"
|
|
25
|
+
"release.json"
|
|
27
26
|
],
|
|
28
27
|
"sideEffects": false,
|
|
29
28
|
"exports": {
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "urn:ifsoul:protocol:0.
|
|
3
|
+
"$id": "urn:ifsoul:protocol:0.4.0:archive",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
6
6
|
"schemaVersion": {
|
|
7
|
-
"const": "0.
|
|
7
|
+
"const": "0.4.0"
|
|
8
8
|
},
|
|
9
9
|
"kind": {
|
|
10
10
|
"const": "archive-manifest"
|
|
11
11
|
},
|
|
12
12
|
"archiveVersion": {
|
|
13
|
-
"const": "0.
|
|
13
|
+
"const": "0.4.0"
|
|
14
14
|
},
|
|
15
15
|
"archiveId": {
|
|
16
|
-
"$ref": "urn:ifsoul:protocol:0.
|
|
16
|
+
"$ref": "urn:ifsoul:protocol:0.4.0:common#/$defs/id"
|
|
17
17
|
},
|
|
18
18
|
"characterId": {
|
|
19
|
-
"$ref": "urn:ifsoul:protocol:0.
|
|
19
|
+
"$ref": "urn:ifsoul:protocol:0.4.0:common#/$defs/id"
|
|
20
20
|
},
|
|
21
21
|
"createdAt": {
|
|
22
|
-
"$ref": "urn:ifsoul:protocol:0.
|
|
22
|
+
"$ref": "urn:ifsoul:protocol:0.4.0:common#/$defs/utc"
|
|
23
23
|
},
|
|
24
24
|
"transferIntent": {
|
|
25
25
|
"oneOf": [
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"const": "resume"
|
|
31
31
|
},
|
|
32
32
|
"expectedCharacterId": {
|
|
33
|
-
"$ref": "urn:ifsoul:protocol:0.
|
|
33
|
+
"$ref": "urn:ifsoul:protocol:0.4.0:common#/$defs/id"
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"required": ["mode", "expectedCharacterId"],
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
"const": "fork"
|
|
44
44
|
},
|
|
45
45
|
"newCharacterId": {
|
|
46
|
-
"$ref": "urn:ifsoul:protocol:0.
|
|
46
|
+
"$ref": "urn:ifsoul:protocol:0.4.0:common#/$defs/id"
|
|
47
47
|
},
|
|
48
48
|
"newParticipantId": {
|
|
49
|
-
"$ref": "urn:ifsoul:protocol:0.
|
|
49
|
+
"$ref": "urn:ifsoul:protocol:0.4.0:common#/$defs/id"
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
"required": ["mode", "newCharacterId", "newParticipantId"],
|
|
@@ -61,10 +61,10 @@
|
|
|
61
61
|
"properties": {
|
|
62
62
|
"schemaId": {
|
|
63
63
|
"type": "string",
|
|
64
|
-
"pattern": "^urn:ifsoul:protocol:0[.]
|
|
64
|
+
"pattern": "^urn:ifsoul:protocol:0[.]4[.]0:(character|memory)$"
|
|
65
65
|
},
|
|
66
66
|
"schemaVersion": {
|
|
67
|
-
"const": "0.
|
|
67
|
+
"const": "0.4.0"
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
70
|
"required": ["schemaId", "schemaVersion"],
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
},
|
|
89
89
|
"schemaId": {
|
|
90
90
|
"type": "string",
|
|
91
|
-
"pattern": "^urn:ifsoul:protocol:0[.]
|
|
91
|
+
"pattern": "^urn:ifsoul:protocol:0[.]4[.]0:(character|memory)$"
|
|
92
92
|
},
|
|
93
93
|
"recordCount": {
|
|
94
94
|
"type": "integer",
|
|
@@ -164,23 +164,23 @@
|
|
|
164
164
|
"additionalProperties": false
|
|
165
165
|
},
|
|
166
166
|
"provenance": {
|
|
167
|
-
"$ref": "urn:ifsoul:protocol:0.
|
|
167
|
+
"$ref": "urn:ifsoul:protocol:0.4.0:common#/$defs/provenance"
|
|
168
168
|
},
|
|
169
169
|
"portableCheckpointIds": {
|
|
170
170
|
"type": "array",
|
|
171
171
|
"items": {
|
|
172
|
-
"$ref": "urn:ifsoul:protocol:0.
|
|
172
|
+
"$ref": "urn:ifsoul:protocol:0.4.0:common#/$defs/id"
|
|
173
173
|
},
|
|
174
174
|
"minItems": 0,
|
|
175
175
|
"uniqueItems": true
|
|
176
176
|
},
|
|
177
177
|
"extensions": {
|
|
178
|
-
"$ref": "urn:ifsoul:protocol:0.
|
|
178
|
+
"$ref": "urn:ifsoul:protocol:0.4.0:common#/$defs/extensions"
|
|
179
179
|
},
|
|
180
180
|
"requiredExtensions": {
|
|
181
181
|
"type": "array",
|
|
182
182
|
"items": {
|
|
183
|
-
"$ref": "urn:ifsoul:protocol:0.
|
|
183
|
+
"$ref": "urn:ifsoul:protocol:0.4.0:common#/$defs/extensionName"
|
|
184
184
|
},
|
|
185
185
|
"minItems": 0,
|
|
186
186
|
"maxItems": 16,
|