@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.
Files changed (147) hide show
  1. package/README.md +30 -33
  2. package/dist/packages/memory/src/sql.js +54 -5
  3. package/dist/packages/protocol/src/generated/validators.cjs +1 -1
  4. package/dist/packages/protocol/src/index.d.ts +1 -6
  5. package/dist/packages/protocol/src/index.js +3 -16
  6. package/dist/packages/protocol/src/types.d.ts +1 -1
  7. package/dist/packages/runtime/src/behavior-types.d.ts +150 -0
  8. package/dist/packages/runtime/src/behavior-types.js +1 -0
  9. package/dist/packages/runtime/src/behavior.d.ts +40 -0
  10. package/dist/packages/runtime/src/behavior.js +621 -0
  11. package/dist/packages/runtime/src/group-scheduler.d.ts +165 -0
  12. package/dist/packages/runtime/src/group-scheduler.js +647 -0
  13. package/dist/packages/runtime/src/index.d.ts +4 -0
  14. package/dist/packages/runtime/src/index.js +4 -1
  15. package/dist/packages/runtime/src/participants.d.ts +107 -0
  16. package/dist/packages/runtime/src/participants.js +309 -0
  17. package/dist/packages/runtime/src/types.d.ts +1 -0
  18. package/package.json +2 -3
  19. package/packages/protocol/schemas/current/archive.schema.json +16 -16
  20. package/packages/protocol/schemas/current/character.schema.json +21 -21
  21. package/packages/protocol/schemas/current/common.schema.json +15 -15
  22. package/packages/protocol/schemas/current/context.schema.json +24 -24
  23. package/packages/protocol/schemas/current/conversation.schema.json +39 -39
  24. package/packages/protocol/schemas/current/environment.schema.json +18 -18
  25. package/packages/protocol/schemas/current/memory.schema.json +66 -66
  26. package/packages/protocol/schemas/current/model-provider.schema.json +40 -40
  27. package/packages/protocol/schemas/current/planning.schema.json +61 -61
  28. package/packages/protocol/schemas/current/provider-common.schema.json +6 -6
  29. package/release.json +7 -12
  30. package/specs/README.md +3 -86
  31. package/specs/current/README.md +3 -5
  32. package/specs/current/examples/archive-fork.json +12 -12
  33. package/specs/current/examples/archive-resume.json +12 -12
  34. package/specs/current/examples/character-instance.json +1 -1
  35. package/specs/current/examples/character-template.json +1 -1
  36. package/specs/current/examples/context-after-inspection.json +1 -1
  37. package/specs/current/examples/context-chat.json +1 -1
  38. package/specs/current/examples/context-daily.json +1 -1
  39. package/specs/current/examples/context-no-speech.json +1 -1
  40. package/specs/current/examples/conversation-delivery.json +1 -1
  41. package/specs/current/examples/conversation-live-request.json +1 -1
  42. package/specs/current/examples/conversation-live-response.json +1 -1
  43. package/specs/current/examples/conversation-no-speech-request.json +1 -1
  44. package/specs/current/examples/conversation-no-speech-response.json +1 -1
  45. package/specs/current/examples/conversation-requested-request.json +1 -1
  46. package/specs/current/examples/conversation-requested-response.json +1 -1
  47. package/specs/current/examples/environment-chat.json +1 -1
  48. package/specs/current/examples/environment-game.json +1 -1
  49. package/specs/current/examples/environment-no-speech.json +1 -1
  50. package/specs/current/examples/intent-checkpoint.json +1 -1
  51. package/specs/current/examples/memory-episode.json +1 -1
  52. package/specs/current/examples/memory-event-conversation.json +1 -1
  53. package/specs/current/examples/memory-event-observed.json +2 -2
  54. package/specs/current/examples/memory-event-plan-proposal.json +2 -2
  55. package/specs/current/examples/memory-event-proposed.json +2 -2
  56. package/specs/current/examples/memory-fact.json +1 -1
  57. package/specs/current/examples/memory-relationship.json +1 -1
  58. package/specs/current/examples/memory-working-context.json +1 -1
  59. package/specs/current/examples/plan-cancel-request.json +1 -1
  60. package/specs/current/examples/plan-cancel-result.json +1 -1
  61. package/specs/current/examples/plan-daily.json +1 -1
  62. package/specs/current/examples/plan-partial-decision.json +1 -1
  63. package/specs/current/examples/portable-memory.jsonl +9 -9
  64. package/specs/current/examples/task-outcome.json +1 -1
  65. package/specs/current/examples/task-progress.json +1 -1
  66. package/specs/current/model-provider/examples/capabilities.json +1 -1
  67. package/specs/current/model-provider/examples/error.json +1 -1
  68. package/specs/current/model-provider/examples/request.json +1 -1
  69. package/specs/current/model-provider/examples/response.json +1 -1
  70. package/docs/npm-publishing.md +0 -44
  71. package/packages/protocol/schemas/action-result.schema.json +0 -187
  72. package/packages/protocol/schemas/action.schema.json +0 -68
  73. package/packages/protocol/schemas/archive-manifest.schema.json +0 -126
  74. package/packages/protocol/schemas/capability.schema.json +0 -131
  75. package/packages/protocol/schemas/character-instance.schema.json +0 -73
  76. package/packages/protocol/schemas/character-template.schema.json +0 -67
  77. package/packages/protocol/schemas/common.schema.json +0 -154
  78. package/packages/protocol/schemas/derived-memory.schema.json +0 -145
  79. package/packages/protocol/schemas/memory-event.schema.json +0 -123
  80. package/packages/protocol/schemas/model-provider.schema.json +0 -326
  81. package/packages/protocol/schemas/perception.schema.json +0 -89
  82. package/packages/protocol/schemas/permission-policy.schema.json +0 -153
  83. package/packages/protocol/schemas/v0.2/archive.schema.json +0 -205
  84. package/packages/protocol/schemas/v0.2/character.schema.json +0 -344
  85. package/packages/protocol/schemas/v0.2/common.schema.json +0 -239
  86. package/packages/protocol/schemas/v0.2/context.schema.json +0 -230
  87. package/packages/protocol/schemas/v0.2/conversation.schema.json +0 -340
  88. package/packages/protocol/schemas/v0.2/environment.schema.json +0 -244
  89. package/packages/protocol/schemas/v0.2/memory.schema.json +0 -495
  90. package/packages/protocol/schemas/v0.2/planning.schema.json +0 -443
  91. package/packages/protocol/schemas/world-event.schema.json +0 -74
  92. package/specs/character/README.md +0 -84
  93. package/specs/character/examples/archive-manifest.json +0 -32
  94. package/specs/character/examples/character-instance.json +0 -20
  95. package/specs/character/examples/character-template.json +0 -19
  96. package/specs/memory/README.md +0 -80
  97. package/specs/memory/examples/derived-memory.json +0 -21
  98. package/specs/memory/examples/memory-event.json +0 -35
  99. package/specs/model-provider/README.md +0 -85
  100. package/specs/model-provider/examples/capabilities.json +0 -10
  101. package/specs/model-provider/examples/error.json +0 -18
  102. package/specs/model-provider/examples/request.json +0 -17
  103. package/specs/model-provider/examples/response.json +0 -19
  104. package/specs/v0.2/README.md +0 -421
  105. package/specs/v0.2/examples/archive-fork.json +0 -64
  106. package/specs/v0.2/examples/archive-resume.json +0 -63
  107. package/specs/v0.2/examples/character-instance.json +0 -57
  108. package/specs/v0.2/examples/character-template.json +0 -45
  109. package/specs/v0.2/examples/context-after-inspection.json +0 -44
  110. package/specs/v0.2/examples/context-chat.json +0 -27
  111. package/specs/v0.2/examples/context-daily.json +0 -69
  112. package/specs/v0.2/examples/context-no-speech.json +0 -27
  113. package/specs/v0.2/examples/conversation-delivery.json +0 -17
  114. package/specs/v0.2/examples/conversation-live-request.json +0 -31
  115. package/specs/v0.2/examples/conversation-live-response.json +0 -23
  116. package/specs/v0.2/examples/conversation-no-speech-request.json +0 -23
  117. package/specs/v0.2/examples/conversation-no-speech-response.json +0 -22
  118. package/specs/v0.2/examples/conversation-requested-request.json +0 -31
  119. package/specs/v0.2/examples/conversation-requested-response.json +0 -23
  120. package/specs/v0.2/examples/environment-chat.json +0 -43
  121. package/specs/v0.2/examples/environment-game.json +0 -80
  122. package/specs/v0.2/examples/environment-no-speech.json +0 -47
  123. package/specs/v0.2/examples/intent-checkpoint.json +0 -29
  124. package/specs/v0.2/examples/memory-episode.json +0 -28
  125. package/specs/v0.2/examples/memory-event-conversation.json +0 -32
  126. package/specs/v0.2/examples/memory-event-observed.json +0 -50
  127. package/specs/v0.2/examples/memory-event-plan-proposal.json +0 -82
  128. package/specs/v0.2/examples/memory-event-proposed.json +0 -53
  129. package/specs/v0.2/examples/memory-fact.json +0 -23
  130. package/specs/v0.2/examples/memory-relationship.json +0 -25
  131. package/specs/v0.2/examples/memory-working-context.json +0 -24
  132. package/specs/v0.2/examples/plan-cancel-request.json +0 -16
  133. package/specs/v0.2/examples/plan-cancel-result.json +0 -21
  134. package/specs/v0.2/examples/plan-daily.json +0 -57
  135. package/specs/v0.2/examples/plan-partial-decision.json +0 -28
  136. package/specs/v0.2/examples/portable-memory.jsonl +0 -9
  137. package/specs/v0.2/examples/task-outcome.json +0 -21
  138. package/specs/v0.2/examples/task-progress.json +0 -19
  139. package/specs/world-adapter/README.md +0 -114
  140. package/specs/world-adapter/examples/action-result-unknown.json +0 -23
  141. package/specs/world-adapter/examples/action-result.json +0 -45
  142. package/specs/world-adapter/examples/action.json +0 -17
  143. package/specs/world-adapter/examples/capability.json +0 -44
  144. package/specs/world-adapter/examples/perception.json +0 -30
  145. package/specs/world-adapter/examples/permission-decision.json +0 -14
  146. package/specs/world-adapter/examples/permission-policy.json +0 -17
  147. 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.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.0:archive",
3
+ "$id": "urn:ifsoul:protocol:0.4.0:archive",
4
4
  "type": "object",
5
5
  "properties": {
6
6
  "schemaVersion": {
7
- "const": "0.3.0"
7
+ "const": "0.4.0"
8
8
  },
9
9
  "kind": {
10
10
  "const": "archive-manifest"
11
11
  },
12
12
  "archiveVersion": {
13
- "const": "0.3.0"
13
+ "const": "0.4.0"
14
14
  },
15
15
  "archiveId": {
16
- "$ref": "urn:ifsoul:protocol:0.3.0:common#/$defs/id"
16
+ "$ref": "urn:ifsoul:protocol:0.4.0:common#/$defs/id"
17
17
  },
18
18
  "characterId": {
19
- "$ref": "urn:ifsoul:protocol:0.3.0:common#/$defs/id"
19
+ "$ref": "urn:ifsoul:protocol:0.4.0:common#/$defs/id"
20
20
  },
21
21
  "createdAt": {
22
- "$ref": "urn:ifsoul:protocol:0.3.0:common#/$defs/utc"
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.3.0:common#/$defs/id"
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.3.0:common#/$defs/id"
46
+ "$ref": "urn:ifsoul:protocol:0.4.0:common#/$defs/id"
47
47
  },
48
48
  "newParticipantId": {
49
- "$ref": "urn:ifsoul:protocol:0.3.0:common#/$defs/id"
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[.]3[.]0:(character|memory)$"
64
+ "pattern": "^urn:ifsoul:protocol:0[.]4[.]0:(character|memory)$"
65
65
  },
66
66
  "schemaVersion": {
67
- "const": "0.3.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[.]3[.]0:(character|memory)$"
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.3.0:common#/$defs/provenance"
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.3.0:common#/$defs/id"
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.3.0:common#/$defs/extensions"
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.3.0:common#/$defs/extensionName"
183
+ "$ref": "urn:ifsoul:protocol:0.4.0:common#/$defs/extensionName"
184
184
  },
185
185
  "minItems": 0,
186
186
  "maxItems": 16,