@zhin.js/plugin-ledger 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +24 -0
  2. package/commands/ledger-add/index.js +6 -4
  3. package/commands/ledger-add/index.ts +6 -4
  4. package/commands/ledger-delete/index.js +6 -4
  5. package/commands/ledger-delete/index.ts +6 -4
  6. package/commands/ledger-list/index.js +8 -6
  7. package/commands/ledger-list/index.ts +8 -6
  8. package/commands/ledger-month/index.js +5 -3
  9. package/commands/ledger-month/index.ts +5 -3
  10. package/commands/ledger-report/index.js +11 -11
  11. package/commands/ledger-report/index.ts +10 -12
  12. package/commands/ledger-stats/index.js +8 -6
  13. package/commands/ledger-stats/index.ts +8 -6
  14. package/commands/ledger-today/index.js +5 -3
  15. package/commands/ledger-today/index.ts +5 -3
  16. package/commands/ledger-week/index.js +5 -3
  17. package/commands/ledger-week/index.ts +5 -3
  18. package/lib/config.d.ts +11 -5
  19. package/lib/config.d.ts.map +1 -1
  20. package/lib/config.js +3 -3
  21. package/lib/config.js.map +1 -1
  22. package/lib/console.d.ts +4 -0
  23. package/lib/console.d.ts.map +1 -0
  24. package/lib/console.js +39 -0
  25. package/lib/console.js.map +1 -0
  26. package/lib/entry-input.d.ts +3 -2
  27. package/lib/entry-input.d.ts.map +1 -1
  28. package/lib/entry-input.js +11 -9
  29. package/lib/entry-input.js.map +1 -1
  30. package/lib/i18n.d.ts +2 -0
  31. package/lib/i18n.d.ts.map +1 -0
  32. package/lib/i18n.js +200 -0
  33. package/lib/i18n.js.map +1 -0
  34. package/lib/identity.d.ts +3 -1
  35. package/lib/identity.d.ts.map +1 -1
  36. package/lib/identity.js +5 -2
  37. package/lib/identity.js.map +1 -1
  38. package/lib/index.d.ts +3 -1
  39. package/lib/index.d.ts.map +1 -1
  40. package/lib/index.js +3 -1
  41. package/lib/index.js.map +1 -1
  42. package/lib/money.d.ts +2 -1
  43. package/lib/money.d.ts.map +1 -1
  44. package/lib/money.js +6 -5
  45. package/lib/money.js.map +1 -1
  46. package/lib/period.d.ts +4 -3
  47. package/lib/period.d.ts.map +1 -1
  48. package/lib/period.js +8 -16
  49. package/lib/period.js.map +1 -1
  50. package/lib/report-format.d.ts +5 -4
  51. package/lib/report-format.d.ts.map +1 -1
  52. package/lib/report-format.js +25 -24
  53. package/lib/report-format.js.map +1 -1
  54. package/lib/service.d.ts +14 -1
  55. package/lib/service.d.ts.map +1 -1
  56. package/lib/service.js +41 -24
  57. package/lib/service.js.map +1 -1
  58. package/lib/store.d.ts +3 -1
  59. package/lib/store.d.ts.map +1 -1
  60. package/lib/store.js +8 -4
  61. package/lib/store.js.map +1 -1
  62. package/lib/tool-caller.d.ts +32 -0
  63. package/lib/tool-caller.d.ts.map +1 -0
  64. package/lib/tool-caller.js +74 -0
  65. package/lib/tool-caller.js.map +1 -0
  66. package/package.json +36 -7
  67. package/pages/index/index.js +63 -0
  68. package/pages/index/index.ts +65 -0
  69. package/plugin.js +3 -1
  70. package/schema.json +15 -0
  71. package/skills/ledger/SKILL.md +41 -0
  72. package/skills/ledger/tools/ledger_add/index.js +25 -0
  73. package/skills/ledger/tools/ledger_add/index.ts +26 -0
  74. package/skills/ledger/tools/ledger_delete/index.js +22 -0
  75. package/skills/ledger/tools/ledger_delete/index.ts +23 -0
  76. package/skills/ledger/tools/ledger_list/index.js +23 -0
  77. package/skills/ledger/tools/ledger_list/index.ts +24 -0
  78. package/skills/ledger/tools/ledger_summary/index.js +27 -0
  79. package/skills/ledger/tools/ledger_summary/index.ts +28 -0
  80. package/src/config.ts +3 -3
  81. package/src/console.ts +47 -0
  82. package/src/entry-input.ts +12 -9
  83. package/src/i18n.ts +200 -0
  84. package/src/identity.ts +6 -2
  85. package/src/index.ts +9 -1
  86. package/src/money.ts +7 -5
  87. package/src/period.ts +7 -13
  88. package/src/report-format.ts +26 -24
  89. package/src/service.ts +56 -24
  90. package/src/store.ts +9 -4
  91. package/src/tool-caller.ts +101 -0
package/src/store.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { resolveLocale, type Locale } from 'zhin.js/i18n';
1
2
  import type { DatabaseHostModel, DeliveryTarget, PluginDatabaseHost } from 'zhin.js';
2
3
 
3
4
  export const ENTRY_TABLE = 'ledger_entries';
@@ -20,6 +21,7 @@ export interface LedgerUserRecord {
20
21
  readonly ownerKey: string;
21
22
  readonly nextSeq: number;
22
23
  readonly reportEnabled: boolean;
24
+ readonly reportLocale?: Locale;
23
25
  readonly reportTarget: DeliveryTarget | undefined;
24
26
  readonly lastReportDate: string;
25
27
  }
@@ -38,9 +40,10 @@ export function defineLedgerTables(database: Pick<PluginDatabaseHost, 'define'>)
38
40
  owner_key: { type: 'text', nullable: false },
39
41
  next_seq: { type: 'integer', default: 1 },
40
42
  report_enabled: { type: 'integer', default: 0 },
43
+ report_locale: { type: 'text', length: 16, nullable: true },
41
44
  report_target: { type: 'text', default: '' },
42
45
  last_report_date: { type: 'text', default: '' },
43
- });
46
+ }, { addColumns: ['report_locale'] });
44
47
  database.define(PRIVATE_TARGET_TABLE, {
45
48
  owner_key: { type: 'text', nullable: false },
46
49
  target: { type: 'text', nullable: false },
@@ -72,6 +75,7 @@ function toUser(row: Record<string, unknown>): LedgerUserRecord {
72
75
  ownerKey: String(row.owner_key),
73
76
  nextSeq: Number(row.next_seq ?? 1),
74
77
  reportEnabled: Number(row.report_enabled ?? 0) === 1,
78
+ reportLocale: resolveLocale(row.report_locale),
75
79
  reportTarget: parseTarget(row.report_target),
76
80
  lastReportDate: String(row.last_report_date ?? ''),
77
81
  };
@@ -97,7 +101,7 @@ export class LedgerStore {
97
101
 
98
102
  async getUser(ownerKey: string): Promise<LedgerUserRecord | undefined> {
99
103
  const rows = await this.#model(USER_TABLE)
100
- .select('owner_key', 'next_seq', 'report_enabled', 'report_target', 'last_report_date')
104
+ .select('owner_key', 'next_seq', 'report_enabled', 'report_locale', 'report_target', 'last_report_date')
101
105
  .where({ owner_key: ownerKey });
102
106
  const first = rows[0];
103
107
  return first ? toUser(first) : undefined;
@@ -154,10 +158,11 @@ export class LedgerStore {
154
158
  await this.#model(ENTRY_TABLE).delete().where({ owner_key: ownerKey, seq });
155
159
  }
156
160
 
157
- async setReport(ownerKey: string, enabled: boolean, target: DeliveryTarget | undefined, lastReportDate: string): Promise<void> {
161
+ async setReport(ownerKey: string, enabled: boolean, target: DeliveryTarget | undefined, lastReportDate: string, locale?: Locale): Promise<void> {
158
162
  await this.#ensureUser(ownerKey);
159
163
  await this.#model(USER_TABLE).update({
160
164
  report_enabled: enabled ? 1 : 0,
165
+ ...(enabled ? { report_locale: resolveLocale(locale) } : {}),
161
166
  report_target: target ? JSON.stringify(target) : '',
162
167
  last_report_date: lastReportDate,
163
168
  }).where({ owner_key: ownerKey });
@@ -184,7 +189,7 @@ export class LedgerStore {
184
189
 
185
190
  async listReportSubscribers(): Promise<LedgerUserRecord[]> {
186
191
  const rows = await this.#model(USER_TABLE)
187
- .select('owner_key', 'next_seq', 'report_enabled', 'report_target', 'last_report_date')
192
+ .select('owner_key', 'next_seq', 'report_enabled', 'report_locale', 'report_target', 'last_report_date')
188
193
  .where({ report_enabled: 1 });
189
194
  return rows.map(toUser);
190
195
  }
@@ -0,0 +1,101 @@
1
+ import { resolveLocale, type Locale } from 'zhin.js/i18n';
2
+ import { createDeliveryTarget, type DeliveryTarget } from 'zhin.js';
3
+ import { adapterFeatureId, type AdapterIndex } from 'zhin.js/adapter';
4
+ import { messages as t } from './i18n.js';
5
+ import { LedgerInputError, ledgerOwnerKey, type LedgerCaller } from './identity.js';
6
+
7
+ export type LedgerToolOrigin =
8
+ | Readonly<{
9
+ kind: 'im';
10
+ platform: string;
11
+ endpoint: string;
12
+ scope: 'private' | 'group' | 'channel';
13
+ sceneId: string;
14
+ messageId?: string;
15
+ threadId?: string;
16
+ conversation?: DeliveryTarget & Readonly<{ name?: string }>;
17
+ }>
18
+ | Readonly<{ kind: Exclude<string, 'im'>; [key: string]: unknown }>;
19
+
20
+ export interface LedgerToolCallerContext {
21
+ readonly origin: LedgerToolOrigin;
22
+ readonly locale?: Locale;
23
+ readonly senderId?: string;
24
+ readonly principal?: Readonly<{ subjectId: string; roles: readonly string[] }>;
25
+ project?<T>(feature: typeof adapterFeatureId): T;
26
+ }
27
+
28
+ function isImOrigin(
29
+ origin: LedgerToolOrigin,
30
+ ): origin is Extract<LedgerToolOrigin, { readonly kind: 'im' }> {
31
+ return origin.kind === 'im';
32
+ }
33
+
34
+ function resolveToolConversation(context: LedgerToolCallerContext, locale: Locale): DeliveryTarget {
35
+ const origin = context.origin;
36
+ if (!isImOrigin(origin)) {
37
+ throw new LedgerInputError(t(locale, 'actor'));
38
+ }
39
+ let endpointId = origin.conversation?.endpoint.id ?? origin.endpoint;
40
+ if (typeof context.project === 'function') {
41
+ try {
42
+ const index = context.project<AdapterIndex>(adapterFeatureId);
43
+ if (index && typeof index.describe === 'function') {
44
+ const matches = index
45
+ .describe()
46
+ .filter(
47
+ (endpoint) =>
48
+ (String(endpoint.id) === origin.endpoint || endpoint.name === origin.endpoint) &&
49
+ index.clientAdapter(endpoint.id) === origin.platform,
50
+ );
51
+ if (matches.length === 1) {
52
+ endpointId = String(matches[0]!.id);
53
+ }
54
+ }
55
+ } catch {
56
+ // Fall back to origin endpoint when AdapterIndex projection is unavailable in lightweight contexts.
57
+ }
58
+ }
59
+ if (!endpointId) {
60
+ throw new LedgerInputError(t(locale, 'actor'));
61
+ }
62
+ if (origin.conversation) {
63
+ return createDeliveryTarget({
64
+ conversation: {
65
+ ...origin.conversation,
66
+ endpoint: { id: endpointId, adapter: origin.platform },
67
+ },
68
+ });
69
+ }
70
+ return createDeliveryTarget({
71
+ conversation: {
72
+ endpoint: { id: endpointId, adapter: origin.platform },
73
+ kind: origin.scope,
74
+ id: origin.sceneId,
75
+ ...(origin.threadId !== undefined ? { threadId: origin.threadId } : {}),
76
+ },
77
+ });
78
+ }
79
+
80
+ /** Builds a LedgerCaller from an Agent Tool execution context. */
81
+ export function ledgerCallerFromTool(context: LedgerToolCallerContext): LedgerCaller {
82
+ const locale = resolveLocale(context.locale);
83
+ const senderId = (context.principal?.subjectId ?? context.senderId)?.trim();
84
+ if (!senderId) {
85
+ throw new LedgerInputError(t(locale, 'actor'));
86
+ }
87
+ const conversation = resolveToolConversation(context, locale);
88
+ const endpoint = conversation.endpoint;
89
+ const isPrivate = conversation.kind === 'private';
90
+ const privateTarget: DeliveryTarget = isPrivate
91
+ ? conversation
92
+ : Object.freeze({ endpoint, kind: 'private', id: senderId });
93
+ return {
94
+ ownerKey: ledgerOwnerKey(endpoint.adapter, endpoint.id, senderId),
95
+ senderId,
96
+ locale,
97
+ conversation,
98
+ privateTarget,
99
+ isPrivate,
100
+ };
101
+ }