@zhin.js/plugin-roulette 0.1.7 → 0.1.9

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/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @zhin.js/plugin-roulette
2
2
 
3
+ ## 0.1.9
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [b0c02a2]
8
+ - Updated dependencies [af8f803]
9
+ - Updated dependencies [26fda97]
10
+ - Updated dependencies [976886c]
11
+ - Updated dependencies [032dc96]
12
+ - @zhin.js/core@1.1.55
13
+ - @zhin.js/game-kit@1.1.20
14
+
15
+ ## 0.1.8
16
+
17
+ ### Patch Changes
18
+
19
+ - a9f4e48: PATCH COMPATIBILITY CUTOVER: this batch intentionally changes plugin APIs. The maintainer explicitly chose patch releases with declared version boundaries. Upgrade the complete tested dependency set; patch numbering does not promise compatibility with the removed interfaces.
20
+
21
+ The first planned floors are zhin.js 1.1.19, Core 1.1.54, Adapter 1.1.26, FeatureKit 1.1.10, Plugin Runtime 1.1.18, Runtime/Isolate 1.1.16 and Agent 1.1.45. Keep old-interface plugins with the earlier locked stack until they are migrated. Adapter's independent Feature API changes from 1.0.0 to 2.0.0; official consumers declare ^2.0.0. Existing Feature API admission rejects explicitly incompatible ranges; omitted API metadata keeps its existing behavior and does not certify compatibility. Runtime engine and patch-only release gates remain unchanged.
22
+
23
+ Use the native-Client Adapter target contract, canonical FeatureKit AdapterRegistry augmentation and named Endpoint control/management requests. Route message controls from canonical message/conversation refs; retain exact source authority and propagate caller/account/explicit-Root-stop cancellation. Replace removed Agent activity feedback exports with source-bound feedback. HMR pause/resume belongs to concrete plugin instances; plugins choose accepted-work policy without changing Controller commit/rollback authority. Cooperative worker/process control cancellation does not kill an isolate or reopen retired business admission.
24
+
25
+ Provide authenticated historical-data preview, exact selected binding, transactional application, authoritative inspection and explicit resumption, including active games. Preserve before-images and journals; never replay an unconfirmed effect, infer an account from an alias, or reuse historical permission grants. Owner V2 approvals remain inactive legacy data, while new writes use V3. Rollback requires matching software, lockfile and verified data; installing old code alone cannot read a new V3 approval file. Conflicting after-images are never overwritten.
26
+
27
+ See docs/plans/adapter-operation-version-boundaries.md and the Chinese/English upgrade guide for the complete version inventory, custom-plugin changes, acceptance and recovery sequence. These code and metadata changes do not authorize merging, publication, deployment, login, production migration or removal of installed workspace patches.
28
+
29
+ - Updated dependencies [2c3610a]
30
+ - Updated dependencies [a9f4e48]
31
+ - Updated dependencies [f8505b0]
32
+ - @zhin.js/core@1.1.54
33
+ - @zhin.js/game-kit@1.1.19
34
+
3
35
  ## 0.1.7
4
36
 
5
37
  ### Patch Changes
@@ -1,14 +1,21 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { gameCommandFeedback } from '@zhin.js/game-kit';
2
+ import { gameCommandFeedback, gamePlayerFromInput, replyGameOperation } from '@zhin.js/game-kit';
3
3
  import { defineCommand } from 'zhin.js/command';
4
- import { getRouletteHelp } from "../../../lib/roulette-actions.js";
4
+ import { resolveRouletteFlow } from "../../../lib/runtime.js";
5
+ import { getRouletteHelp, normalizeRouletteAction } from "../../../lib/roulette-actions.js";
5
6
  import { runRouletteCommand } from "../../../lib/roulette-command.js";
6
7
  export default defineCommand({
7
8
  description: '俄罗斯轮盘:多人轮流开枪,中弹者输', descriptionI18n: { en: "Russian roulette: take turns; the player hit loses" },
8
9
  params: { action: { type: 'string', default: '' } },
9
10
  async execute(context) {
10
11
  const action = String(context.params.action ?? '');
11
- const reply = await runRouletteCommand(context, context.input, action);
12
- return reply ?? gameCommandFeedback(context, 'unknown', action) + '\n\n' + getRouletteHelp(context);
12
+ const normalized = normalizeRouletteAction(action);
13
+ if (normalized === 'help')
14
+ return getRouletteHelp(context);
15
+ if (!normalized)
16
+ return gameCommandFeedback(context, 'unknown', action) + '\n\n' + getRouletteHelp(context);
17
+ const flow = resolveRouletteFlow(context), player = gamePlayerFromInput(context.input, context);
18
+ await replyGameOperation({ session: flow.services.sessions, player, reply: context.session?.reply,
19
+ run: () => runRouletteCommand(context, context.input, action) });
13
20
  },
14
21
  });
@@ -1,5 +1,5 @@
1
1
  import { outboundHostToken } from 'zhin.js';
2
- import { resolveLocale } from 'zhin.js/i18n';
2
+ import { sendGameSessionMessage } from '@zhin.js/game-kit';
3
3
  import { expireOverdueSessions } from './game-flow.js';
4
4
  import { resolveRouletteFlow } from './runtime.js';
5
5
  /** Turn and lobby deadlines are checked every five seconds. */
@@ -28,16 +28,7 @@ function readOutbound(context) {
28
28
  async function announce(outbound, notice) {
29
29
  const session = notice.session;
30
30
  try {
31
- await outbound.send({
32
- locale: resolveLocale(session.locale),
33
- adapter: session.adapter,
34
- endpointKey: session.endpoint,
35
- conversation: {
36
- kind: session.channel_type,
37
- id: session.channel_id,
38
- },
39
- content: notice.text,
40
- });
31
+ await sendGameSessionMessage({ outbound, session, content: notice.text });
41
32
  }
42
33
  catch {
43
34
  // The result is already stored; a failed notice must not stop the other sessions.
@@ -1,9 +1,9 @@
1
- import { type GameMessageLike } from '@zhin.js/game-kit';
1
+ import { type GamePlayer } from '@zhin.js/game-kit';
2
2
  import type { RouletteConfig } from './config.js';
3
3
  import type { RouletteSessionRow } from './models.js';
4
4
  import type { MutePenaltyPort, PenaltyConversation } from './mute-penalty.js';
5
5
  import { type RouletteCommandAction } from './roulette-actions.js';
6
- import { type EndpointIdentity, type RouletteServices } from './session-service.js';
6
+ import { type RouletteServices } from './session-service.js';
7
7
  /** Everything one action needs, resolved from the current runtime generation. */
8
8
  export interface RouletteFlow {
9
9
  readonly services: RouletteServices;
@@ -12,8 +12,7 @@ export interface RouletteFlow {
12
12
  }
13
13
  /** Who sent the action, from the runtime message. */
14
14
  export interface RouletteActor {
15
- readonly message: GameMessageLike;
16
- readonly endpoint: EndpointIdentity;
15
+ readonly message: GamePlayer;
17
16
  readonly roles: readonly string[];
18
17
  }
19
18
  export interface ExpiredSessionNotice {
package/lib/game-flow.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createMessages, resolveLocale } from 'zhin.js/i18n';
2
- import { gameCommandHint, GameSessionConflictError, SessionRevisionConflictError, channelKey } from '@zhin.js/game-kit';
2
+ import { gameCommandHint, GameSessionConflictError, SessionRevisionConflictError, channelKey, gameTargetFromKey } from '@zhin.js/game-kit';
3
3
  import { RouletteRuleError, loserOf } from './engine.js';
4
4
  import { getRouletteHelp } from './roulette-actions.js';
5
5
  import { playerFromMessage, stateOf, } from './session-service.js';
@@ -8,25 +8,27 @@ const DEADLINE_ACTOR = 'deadline';
8
8
  const MANAGER_ROLES = ['master', 'trusted', 'owner', 'admin'];
9
9
  function noGame(message) { return messages(message.locale, 'noGame') + gameCommandHint(message, 'roulette', 'open'); }
10
10
  export async function runRouletteAction(flow, actor, action) {
11
- const message = actor.message;
12
- const locale = resolveLocale(message.locale);
13
- if (action === 'help')
14
- return getRouletteHelp(message);
15
- if (message.$channel.type === 'private')
16
- return messages(locale, 'text1');
17
- if (action === 'penalty-on' || action === 'penalty-off' || action === 'penalty-status') {
18
- return handlePenaltySetting(flow, actor, action);
19
- }
20
- const notices = [];
21
- let session = await flow.services.sessions.getActiveByChannel(channelKey(message));
22
- if (session && isOverdue(session, Date.now())) {
23
- const expired = await expireSession(flow, session);
24
- if (expired)
25
- notices.push(expired);
26
- session = null;
27
- }
28
- const reply = await handleGameAction(flow, actor, action, session);
29
- return [...notices, reply].join('\n\n');
11
+ return flow.services.sessions.runInScope({ scope: channelKey(actor.message), run: async () => {
12
+ const message = actor.message;
13
+ const locale = resolveLocale(message.locale);
14
+ if (action === 'help')
15
+ return getRouletteHelp(message);
16
+ if (message.target.kind === 'private')
17
+ return messages(locale, 'text1');
18
+ if (action === 'penalty-on' || action === 'penalty-off' || action === 'penalty-status') {
19
+ return handlePenaltySetting(flow, actor, action);
20
+ }
21
+ const notices = [];
22
+ let session = await flow.services.sessions.getActiveByChannel(channelKey(message));
23
+ if (session && isOverdue(session, Date.now())) {
24
+ const expired = await expireSession(flow, session);
25
+ if (expired)
26
+ notices.push(expired);
27
+ session = null;
28
+ }
29
+ const reply = await handleGameAction(flow, actor, action, session);
30
+ return [...notices, reply].join('\n\n');
31
+ } });
30
32
  }
31
33
  /** Called by the deadline schedule; each returned notice still has to be delivered. */
32
34
  export async function expireOverdueSessions(flow, now, onNotice) {
@@ -34,14 +36,16 @@ export async function expireOverdueSessions(flow, now, onNotice) {
34
36
  const notices = [];
35
37
  for (const session of overdue) {
36
38
  try {
37
- const text = await expireSession(flow, session);
38
- if (!text)
39
- continue;
40
- const notice = { session, text };
41
- notices.push(notice);
42
- // Announce as soon as the result is stored, so a later failure cannot drop it.
43
- if (onNotice)
44
- await onNotice(notice);
39
+ await flow.services.sessions.runInScope({ scope: session.channel_key, run: async () => {
40
+ const text = await expireSession(flow, session);
41
+ if (!text)
42
+ return;
43
+ const notice = { session, text };
44
+ notices.push(notice);
45
+ // Announce as soon as the result is stored, so a later failure cannot drop it.
46
+ if (onNotice)
47
+ await onNotice(notice);
48
+ } });
45
49
  }
46
50
  catch (error) {
47
51
  // The session stays active when anything before the commit failed; the next sweep retries it.
@@ -67,7 +71,7 @@ async function handleGameAction(flow, actor, action, session) {
67
71
  if (!gameAction)
68
72
  return noGame(message);
69
73
  try {
70
- const result = await flow.services.sessions.applyAction(session, message.$sender.id, gameAction, flow.config);
74
+ const result = await flow.services.sessions.applyAction(session, message.sender.id, gameAction, flow.config);
71
75
  return describeResult(flow, gameAction, result, message);
72
76
  }
73
77
  catch (error) {
@@ -86,7 +90,7 @@ async function openLobby(flow, actor, session) {
86
90
  return messages(locale, 'text4', { p0: describeStatus(stateOf(session), flow.config, { ...actor.message, locale }) });
87
91
  }
88
92
  try {
89
- const created = await flow.services.sessions.createLobby(actor.message, actor.endpoint, flow.config);
93
+ const created = await flow.services.sessions.createLobby(actor.message, flow.config);
90
94
  return describeLobby(stateOf(created), flow.config, { ...actor.message, locale });
91
95
  }
92
96
  catch (error) {
@@ -165,7 +169,7 @@ async function applyLoserPenalty(flow, session, state, penaltyEnabled) {
165
169
  async function handlePenaltySetting(flow, actor, action) {
166
170
  const message = actor.message;
167
171
  const locale = resolveLocale(message.locale);
168
- if (message.$channel.type !== 'group')
172
+ if (message.target.kind !== 'group')
169
173
  return messages(locale, 'text9');
170
174
  const key = channelKey(message);
171
175
  const settings = flow.services.settings;
@@ -177,13 +181,13 @@ async function handlePenaltySetting(flow, actor, action) {
177
181
  if (!isManager(actor.roles))
178
182
  return messages(locale, 'text10');
179
183
  if (action === 'penalty-off') {
180
- await settings.setMutePenalty(key, false, message.$sender.id);
184
+ await settings.setMutePenalty(key, false, message.sender.id);
181
185
  return messages(locale, 'text11');
182
186
  }
183
187
  if (support !== 'supported') {
184
188
  return messages(locale, 'text12');
185
189
  }
186
- await settings.setMutePenalty(key, true, message.$sender.id);
190
+ await settings.setMutePenalty(key, true, message.sender.id);
187
191
  return messages(locale, 'text13', { p0: flow.config.muteSeconds });
188
192
  }
189
193
  function describePenaltyStatus(enabled, supported, config, locale) {
@@ -206,7 +210,7 @@ function toGameAction(action, message) {
206
210
  return null;
207
211
  }
208
212
  function mayCancel(session, message, senderRoles) {
209
- if (stateOf(session).hostId === message.$sender.id)
213
+ if (stateOf(session).hostId === message.sender.id)
210
214
  return true;
211
215
  return isManager(senderRoles);
212
216
  }
@@ -217,19 +221,17 @@ function isOverdue(session, now) {
217
221
  return session.deadline_at > 0 && session.deadline_at <= now;
218
222
  }
219
223
  export function conversationOfSession(session) {
220
- return {
221
- endpointAdapter: session.endpoint_adapter,
222
- endpointId: session.endpoint_id,
223
- kind: session.channel_type,
224
- id: session.channel_id,
225
- };
224
+ const target = gameTargetFromKey(session.channel_key);
225
+ if (!target)
226
+ throw new Error('Historical roulette session awaits explicit target migration');
227
+ return { endpointAdapter: target.endpoint.adapter, endpointId: target.endpoint.id, kind: target.kind, id: target.id };
226
228
  }
227
229
  function conversationOfActor(actor) {
228
230
  return {
229
- endpointAdapter: actor.endpoint.adapter,
230
- endpointId: actor.endpoint.id,
231
- kind: actor.message.$channel.type,
232
- id: actor.message.$channel.id,
231
+ endpointAdapter: actor.message.target.endpoint.adapter,
232
+ endpointId: actor.message.target.endpoint.id,
233
+ kind: actor.message.target.kind,
234
+ id: actor.message.target.id,
233
235
  };
234
236
  }
235
237
  const messages = createMessages({
package/lib/index.d.ts CHANGED
@@ -4,5 +4,5 @@ export { expireOverdueSessions, runRouletteAction, type RouletteActor, type Roul
4
4
  export { DEADLINE_CRON, runDeadlineSweep } from './deadline-job.js';
5
5
  export { resolveMutePenalty, type AdapterProjectionContext, type MutePenaltyPort, type MuteSupport, type PenaltyConversation, } from './mute-penalty.js';
6
6
  export { ROULETTE_HELP, normalizeRouletteAction, type RouletteCommandAction } from './roulette-actions.js';
7
- export { endpointOf, rouletteServicesToken, resolveRouletteFlow } from './runtime.js';
8
- export { RouletteGroupSettings, RouletteSessionService, createServices, type EndpointIdentity, type RouletteServices, } from './session-service.js';
7
+ export { rouletteServicesToken, resolveRouletteFlow } from './runtime.js';
8
+ export { RouletteGroupSettings, RouletteSessionService, createServices, type RouletteServices, } from './session-service.js';
package/lib/index.js CHANGED
@@ -4,5 +4,5 @@ export { expireOverdueSessions, runRouletteAction } from './game-flow.js';
4
4
  export { DEADLINE_CRON, runDeadlineSweep } from './deadline-job.js';
5
5
  export { resolveMutePenalty, } from './mute-penalty.js';
6
6
  export { ROULETTE_HELP, normalizeRouletteAction } from './roulette-actions.js';
7
- export { endpointOf, rouletteServicesToken, resolveRouletteFlow } from './runtime.js';
7
+ export { rouletteServicesToken, resolveRouletteFlow } from './runtime.js';
8
8
  export { RouletteGroupSettings, RouletteSessionService, createServices, } from './session-service.js';
@@ -1,4 +1,4 @@
1
- import { adapterFeatureId, resolveEndpointManagement } from 'zhin.js/adapter';
1
+ import { adapterFeatureId } from 'zhin.js/adapter';
2
2
  export function resolveMutePenalty(context) {
3
3
  return {
4
4
  support(conversation) {
@@ -11,25 +11,25 @@ export function resolveMutePenalty(context) {
11
11
  if (!muteGroupMember) {
12
12
  throw new Error('This Endpoint cannot mute group members');
13
13
  }
14
- await muteGroupMember(conversation.id, userId, durationSeconds);
14
+ await muteGroupMember({ groupId: conversation.id, userId, durationSeconds });
15
15
  },
16
16
  };
17
17
  }
18
- /** project(adapterFeatureId) → connection(adapter, endpointId) → resolveEndpointManagement → muteGroupMember. */
18
+ /** Resolve canonical identity, then capture its guarded management port from the same Index. */
19
19
  function findMuteGroupMember(context, conversation) {
20
20
  const adapters = readAdapterProjection(context);
21
21
  if (!adapters)
22
22
  return undefined;
23
23
  if (!conversation.endpointAdapter || !conversation.endpointId)
24
24
  return undefined;
25
- let endpoint;
25
+ let management;
26
26
  try {
27
- endpoint = adapters.connection(conversation.endpointAdapter, conversation.endpointId);
27
+ const id = adapters.resolve(conversation.endpointAdapter, conversation.endpointId);
28
+ management = id ? adapters.management(id) : undefined;
28
29
  }
29
30
  catch {
30
31
  return undefined;
31
32
  }
32
- const management = resolveEndpointManagement(endpoint);
33
33
  const muteGroupMember = management?.muteGroupMember;
34
34
  if (typeof muteGroupMember !== 'function')
35
35
  return undefined;
@@ -47,7 +47,7 @@ function readAdapterProjection(context) {
47
47
  if (!projection || typeof projection !== 'object')
48
48
  return undefined;
49
49
  const lookup = projection;
50
- if (typeof lookup.connection !== 'function')
50
+ if (typeof lookup.resolve !== 'function' || typeof lookup.management !== 'function')
51
51
  return undefined;
52
52
  return lookup;
53
53
  }
@@ -1,6 +1,6 @@
1
- import type { GameMessageLike } from '@zhin.js/game-kit';
1
+ import type { GamePlayer } from '@zhin.js/game-kit';
2
2
  export type RouletteCommandAction = 'help' | 'open' | 'join' | 'start' | 'fire' | 'leave' | 'cancel' | 'status' | 'penalty-on' | 'penalty-off' | 'penalty-status';
3
3
  /** Empty input means help; unknown words return null so normal chat is never hijacked. */
4
4
  export declare function normalizeRouletteAction(raw: string): RouletteCommandAction | null;
5
- export declare function getRouletteHelp(message?: Pick<GameMessageLike, 'locale' | 'formatCommand'>): string;
5
+ export declare function getRouletteHelp(message?: Pick<GamePlayer, 'locale' | 'formatCommand'>): string;
6
6
  export declare const ROULETTE_HELP: string;
@@ -1,7 +1,7 @@
1
- import { messageFromCommandInput } from '@zhin.js/game-kit';
1
+ import { gamePlayerFromInput } from '@zhin.js/game-kit';
2
2
  import { runRouletteAction } from './game-flow.js';
3
3
  import { normalizeRouletteAction, getRouletteHelp } from './roulette-actions.js';
4
- import { endpointOf, resolveRouletteFlow, senderRolesOf } from './runtime.js';
4
+ import { resolveRouletteFlow, senderRolesOf } from './runtime.js';
5
5
  /**
6
6
  * Shared entry for the `roulette` command and the `轮盘` alias middleware.
7
7
  * Returns null for unknown words so the alias middleware lets the chat through.
@@ -11,11 +11,10 @@ export async function runRouletteCommand(context, input, rawAction) {
11
11
  if (!action)
12
12
  return null;
13
13
  if (action === 'help')
14
- return getRouletteHelp(messageFromCommandInput(input, context));
14
+ return getRouletteHelp(gamePlayerFromInput(input, context));
15
15
  const flow = resolveRouletteFlow(context);
16
16
  const actor = {
17
- message: messageFromCommandInput(input, context),
18
- endpoint: endpointOf(input),
17
+ message: gamePlayerFromInput(input, context),
19
18
  roles: senderRolesOf(input),
20
19
  };
21
20
  return runRouletteAction(flow, actor, action);
package/lib/runtime.d.ts CHANGED
@@ -2,7 +2,7 @@ import type { Locale } from 'zhin.js/i18n';
2
2
  import { type Token } from 'zhin.js';
3
3
  import type { RouletteFlow } from './game-flow.js';
4
4
  import { type AdapterProjectionContext } from './mute-penalty.js';
5
- import type { EndpointIdentity, RouletteServices } from './session-service.js';
5
+ import type { RouletteServices } from './session-service.js';
6
6
  /** Owner-scoped services consumed by discovered commands, middleware and the deadline job. */
7
7
  export declare const rouletteServicesToken: Token<RouletteServices>;
8
8
  /** A command, middleware or schedule capability context. */
@@ -15,5 +15,3 @@ export interface RouletteCapabilityContext extends AdapterProjectionContext {
15
15
  export declare function resolveRouletteFlow(context: RouletteCapabilityContext): RouletteFlow;
16
16
  /** Framework roles (master / trusted / user) of whoever sent the message. */
17
17
  export declare function senderRolesOf(input: unknown): readonly string[];
18
- /** `message.conversation.endpoint`, or an empty identity (no mute possible) when absent. */
19
- export declare function endpointOf(input: unknown): EndpointIdentity;
package/lib/runtime.js CHANGED
@@ -22,19 +22,3 @@ export function senderRolesOf(input) {
22
22
  return [];
23
23
  return roles.filter((role) => typeof role === 'string');
24
24
  }
25
- /** `message.conversation.endpoint`, or an empty identity (no mute possible) when absent. */
26
- export function endpointOf(input) {
27
- const empty = { adapter: '', id: '' };
28
- if (!input || typeof input !== 'object')
29
- return empty;
30
- const conversation = input.conversation;
31
- if (!conversation || typeof conversation !== 'object')
32
- return empty;
33
- const endpoint = conversation.endpoint;
34
- if (!endpoint || typeof endpoint !== 'object')
35
- return empty;
36
- const identity = endpoint;
37
- if (typeof identity.adapter !== 'string' || typeof identity.id !== 'string')
38
- return empty;
39
- return { adapter: identity.adapter, id: identity.id };
40
- }
@@ -1,14 +1,9 @@
1
1
  import type { Database, Models } from '@zhin.js/core';
2
- import { VersionedSessionService, type GameMessageLike } from '@zhin.js/game-kit';
2
+ import { VersionedSessionService, type GamePlayer } from '@zhin.js/game-kit';
3
3
  import type { RouletteConfig } from './config.js';
4
4
  import { type RoulettePlayer, type RouletteState } from './engine.js';
5
5
  import type { RouletteSessionRow } from './models.js';
6
6
  export type RouletteDatabase = Database<unknown, Models, string>;
7
- /** `message.conversation.endpoint`: the adapter plugin owner and the Endpoint capability id. */
8
- export interface EndpointIdentity {
9
- readonly adapter: string;
10
- readonly id: string;
11
- }
12
7
  export type RouletteAction = {
13
8
  readonly type: 'join';
14
9
  readonly player: RoulettePlayer;
@@ -36,7 +31,7 @@ export interface RouletteServiceOptions {
36
31
  export declare class RouletteSessionService extends VersionedSessionService<RouletteSessionRow> {
37
32
  #private;
38
33
  constructor(database: RouletteDatabase, options?: RouletteServiceOptions);
39
- createLobby(message: GameMessageLike, endpoint: EndpointIdentity, config: RouletteConfig): Promise<RouletteSessionRow>;
34
+ createLobby(message: GamePlayer, config: RouletteConfig): Promise<RouletteSessionRow>;
40
35
  /** Every seated player counts as busy, not just the host. */
41
36
  getActiveForUser(channelKeyValue: string, userId: string): Promise<RouletteSessionRow | null>;
42
37
  applyAction(session: RouletteSessionRow, actorId: string, action: RouletteAction, config: RouletteConfig): Promise<RouletteActionResult>;
@@ -56,4 +51,4 @@ export interface RouletteServices {
56
51
  }
57
52
  export declare function createServices(database: RouletteDatabase, options?: RouletteServiceOptions): RouletteServices;
58
53
  export declare function stateOf(session: RouletteSessionRow): RouletteState;
59
- export declare function playerFromMessage(message: GameMessageLike): RoulettePlayer;
54
+ export declare function playerFromMessage(message: GamePlayer): RoulettePlayer;
@@ -1,4 +1,5 @@
1
- import { VersionedSessionService, channelKey, generateCompactId, secureRandomInt, } from '@zhin.js/game-kit';
1
+ import { encodeStorageIdentity } from 'zhin.js/plugin-runtime';
2
+ import { VersionedSessionService, generateCompactId, secureRandomInt, } from '@zhin.js/game-kit';
2
3
  import { cancelGame, createLobby, currentPlayer, decodeRouletteState, expireLobby, expireTurn, isOpen, joinLobby, leaveGame, pullTrigger, startRound, } from './engine.js';
3
4
  export class RouletteSessionService extends VersionedSessionService {
4
5
  #chooseBulletChamber;
@@ -11,30 +12,28 @@ export class RouletteSessionService extends VersionedSessionService {
11
12
  });
12
13
  this.#chooseBulletChamber = options.chooseBulletChamber ?? secureRandomInt;
13
14
  }
14
- async createLobby(message, endpoint, config) {
15
+ async createLobby(message, config) {
15
16
  const now = Date.now();
16
17
  const host = playerFromMessage(message);
17
18
  const state = createLobby(host, tableRules(config));
18
19
  return this.createRow({
19
- id: generateCompactId('rlt_'),
20
- locale: message.locale ?? 'zh-CN',
21
- adapter: String(message.$adapter),
22
- endpoint: message.$endpoint,
23
- channel_type: message.$channel.type,
24
- channel_id: message.$channel.id,
25
- channel_key: channelKey(message),
26
- endpoint_adapter: endpoint.adapter,
27
- endpoint_id: endpoint.id,
28
- host_id: host.id,
29
- state_json: JSON.stringify(state),
30
- phase: state.phase,
31
- turn: '',
32
- revision: 0,
33
- processed_actions: '[]',
34
- deadline_at: now + config.lobbyTimeoutSeconds * 1000,
35
- status: 'active',
36
- updated_at: now,
37
- created_at: now,
20
+ target: message.target,
21
+ fields: {
22
+ id: generateCompactId('rlt_'),
23
+ locale: message.locale ?? 'zh-CN',
24
+ endpoint_adapter: encodeStorageIdentity(message.target.endpoint.adapter),
25
+ endpoint_id: encodeStorageIdentity(message.target.endpoint.id),
26
+ host_id: host.id,
27
+ state_json: JSON.stringify(state),
28
+ phase: state.phase,
29
+ turn: '',
30
+ revision: 0,
31
+ processed_actions: '[]',
32
+ deadline_at: now + config.lobbyTimeoutSeconds * 1000,
33
+ status: 'active',
34
+ updated_at: now,
35
+ created_at: now,
36
+ },
38
37
  });
39
38
  }
40
39
  /** Every seated player counts as busy, not just the host. */
@@ -121,8 +120,8 @@ export function stateOf(session) {
121
120
  return decodeRouletteState(session.state_json);
122
121
  }
123
122
  export function playerFromMessage(message) {
124
- const name = message.$sender.name?.trim() || message.$sender.id;
125
- return { id: message.$sender.id, name };
123
+ const name = message.sender.name?.trim() || message.sender.id;
124
+ return { id: message.sender.id, name };
126
125
  }
127
126
  function tableRules(config) {
128
127
  return { chamberCount: config.chambers, maxPlayers: config.maxPlayers };
package/lib/view.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type { GameMessageLike } from '@zhin.js/game-kit';
1
+ import type { GamePlayer } from '@zhin.js/game-kit';
2
2
  import type { RouletteConfig } from './config.js';
3
3
  import { type RoulettePlayer, type RouletteState } from './engine.js';
4
- type Presentation = Pick<GameMessageLike, 'locale' | 'formatCommand'>;
4
+ type Presentation = Pick<GamePlayer, 'locale' | 'formatCommand'>;
5
5
  export declare const ROULETTE_TITLE: string;
6
6
  export declare function describeLobby(state: RouletteState, config: RouletteConfig, presentation?: Presentation): string;
7
7
  export declare function describeRoundStart(state: RouletteState, config: RouletteConfig, presentation?: Presentation): string;
@@ -1,10 +1,23 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { defineGameCommandAliasMiddleware } from '@zhin.js/game-kit';
2
+ import { defineGameCommandAliasMiddleware, gamePlayerFromInput, replyGameOperation } from '@zhin.js/game-kit';
3
+ import { normalizeRouletteAction, getRouletteHelp } from "../../lib/roulette-actions.js";
4
+ import { resolveRouletteFlow } from "../../lib/runtime.js";
3
5
  import { runRouletteCommand } from "../../lib/roulette-command.js";
4
6
  /** `轮盘 加入` / `/轮盘赌 开枪` / `roulette start` route to the same logic as the command. */
5
7
  export default defineGameCommandAliasMiddleware({
6
8
  aliases: ['轮盘', '轮盘赌', 'roulette'],
7
9
  async run(action, input, context) {
8
- return runRouletteCommand(context, input, action);
10
+ const normalized = normalizeRouletteAction(action);
11
+ if (!normalized)
12
+ return false;
13
+ if (normalized === 'help') {
14
+ await context.input.$reply(getRouletteHelp({ locale: context.input.locale,
15
+ formatCommand: path => context.input.formatCommand?.(path, context.owner.id) }));
16
+ return true;
17
+ }
18
+ const flow = resolveRouletteFlow(context), player = gamePlayerFromInput(input);
19
+ await replyGameOperation({ session: flow.services.sessions, player, reply: content => context.input.$reply(content),
20
+ run: () => runRouletteCommand(context, input, action) });
21
+ return true;
9
22
  },
10
23
  });
@@ -1,5 +1,5 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { channelKey, messageFromCommandInput } from '@zhin.js/game-kit';
2
+ import { channelKey, gamePlayerFromInput, replyGameOperation } from '@zhin.js/game-kit';
3
3
  import { defineMiddleware } from 'zhin.js/middleware';
4
4
  import { runRouletteCommand } from "../../lib/roulette-command.js";
5
5
  import { rouletteServicesToken } from "../../lib/runtime.js";
@@ -11,20 +11,23 @@ const FIRE_WORDS = ['开枪', '扣动扳机'];
11
11
  export default defineMiddleware({
12
12
  target: 'inbound',
13
13
  async handle(context, next) {
14
+ if (!context.input.sender?.id) {
15
+ await next();
16
+ return;
17
+ }
14
18
  const text = context.input.content?.trim() ?? '';
15
19
  if (!FIRE_WORDS.includes(text)) {
16
20
  await next();
17
21
  return;
18
22
  }
19
- const message = messageFromCommandInput(context.input);
23
+ const message = gamePlayerFromInput(context.input);
20
24
  const services = context.use(rouletteServicesToken);
21
25
  const session = await services.sessions.getActiveByChannel(channelKey(message));
22
- if (!session || session.turn !== message.$sender.id) {
26
+ if (!session || session.turn !== message.sender.id) {
23
27
  await next();
24
28
  return;
25
29
  }
26
- const reply = await runRouletteCommand(context, context.input, 'fire');
27
- if (reply)
28
- await context.input.$reply(reply);
30
+ await replyGameOperation({ session: services.sessions, player: message, reply: content => context.input.$reply(content),
31
+ run: () => runRouletteCommand(context, context.input, 'fire') });
29
32
  },
30
33
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-roulette",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Zhin.js 俄罗斯轮盘 — 多人轮流开枪,可按群开启禁言惩罚 (Plugin Runtime)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -30,15 +30,15 @@
30
30
  ],
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
- "@zhin.js/core": "1.1.53",
34
- "@zhin.js/game-kit": "1.1.18"
33
+ "@zhin.js/core": "1.1.55",
34
+ "@zhin.js/game-kit": "1.1.20"
35
35
  },
36
36
  "devDependencies": {
37
37
  "typescript": "^6.0.3",
38
38
  "vitest": "^4.1.10",
39
- "@zhin.js/schedule-feature": "1.1.9",
40
- "@zhin.js/testing": "1.0.6",
41
- "zhin.js": "1.1.18"
39
+ "@zhin.js/schedule-feature": "1.1.11",
40
+ "@zhin.js/testing": "1.0.7",
41
+ "zhin.js": "1.1.20"
42
42
  },
43
43
  "repository": {
44
44
  "type": "git",
@@ -79,10 +79,10 @@
79
79
  "plugins": []
80
80
  },
81
81
  "peerDependencies": {
82
- "@zhin.js/command": "^1.1.11",
83
- "@zhin.js/middleware": "^1.1.10",
84
- "zhin.js": "^1.1.18",
85
- "@zhin.js/schedule-feature": "^1.1.9"
82
+ "@zhin.js/command": "^1.1.13",
83
+ "@zhin.js/middleware": "^1.1.12",
84
+ "zhin.js": "^1.1.20",
85
+ "@zhin.js/schedule-feature": "^1.1.11"
86
86
  },
87
87
  "peerDependenciesMeta": {
88
88
  "@zhin.js/command": {