evolcore 0.0.11 → 0.0.12

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 (102) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/bin/codex-managed-hook.mjs +80 -0
  3. package/dist/agents/claude-runner.js +35 -2
  4. package/dist/agents/codex-app-server-client.js +120 -14
  5. package/dist/agents/codex-runner.js +273 -35
  6. package/dist/agents/ecagent-runner.js +36 -4
  7. package/dist/agents/gemini-runner.js +1 -0
  8. package/dist/aun/aid/client.js +11 -21
  9. package/dist/aun/aid/managed-operation.js +107 -0
  10. package/dist/aun/msg/group.js +81 -12
  11. package/dist/aun/msg/managed-operation.js +804 -0
  12. package/dist/aun/msg/mention-schema.js +20 -0
  13. package/dist/aun/msg/p2p.js +7 -0
  14. package/dist/channels/aun.js +288 -108
  15. package/dist/channels/daemon.js +13 -0
  16. package/dist/cli/agent-command.js +56 -8
  17. package/dist/cli/agent.js +170 -6
  18. package/dist/cli/aun-commands.js +270 -69
  19. package/dist/cli/bench.js +12 -3
  20. package/dist/cli/daemon-commands.js +31 -31
  21. package/dist/cli/fs-command.js +60 -0
  22. package/dist/cli/handoff-command.js +3 -0
  23. package/dist/cli/index.js +30 -2
  24. package/dist/cli/managed-command-guard.js +41 -0
  25. package/dist/cli/model.js +11 -4
  26. package/dist/cli/queue-command.js +11 -1
  27. package/dist/cli/response.js +71 -0
  28. package/dist/cli/restart-monitor.js +4 -18
  29. package/dist/cli/trigger-command.js +4 -0
  30. package/dist/config/builtin-roles.js +4 -0
  31. package/dist/config/contact-book-store.js +11 -3
  32. package/dist/config/contact-request-service.js +161 -15
  33. package/dist/config/mention-mode.js +8 -24
  34. package/dist/core/auth/agent-delegation.js +0 -1
  35. package/dist/core/auth/operation-authorizer.js +30 -7
  36. package/dist/core/auth/operation-catalog.js +187 -25
  37. package/dist/core/bootstrap-messages.js +18 -0
  38. package/dist/core/bootstrap-service.js +63 -4
  39. package/dist/core/capability/providers/claude-capability-provider.js +9 -31
  40. package/dist/core/capability/providers/codex-capability-provider.js +4 -15
  41. package/dist/core/capability/skill-discovery.js +55 -0
  42. package/dist/core/channel-loader.js +1 -1
  43. package/dist/core/command/cli-intent-parser.js +9 -0
  44. package/dist/core/command/command-handler.js +467 -6
  45. package/dist/core/command/connect-menu.js +15 -0
  46. package/dist/core/command/group-menu.js +4 -4
  47. package/dist/core/command/menu-catalog.js +366 -0
  48. package/dist/core/command/menu-handler.js +147 -35
  49. package/dist/core/command/menu-protocol.js +71 -0
  50. package/dist/core/command/slash-gate.js +5 -2
  51. package/dist/core/command/slash-handler.js +41 -21
  52. package/dist/core/daemon-file-cache.js +2 -1
  53. package/dist/core/data-migration.js +9 -2
  54. package/dist/core/evolagent.js +4 -2
  55. package/dist/core/handoff/dispatcher.js +5 -2
  56. package/dist/core/handoff/runtime.js +145 -21
  57. package/dist/core/handoff/store.js +24 -0
  58. package/dist/core/handoff/types.js +1 -0
  59. package/dist/core/message/logical-queue-bridge.js +1 -0
  60. package/dist/core/message/mention-schema.js +126 -0
  61. package/dist/core/message/message-bridge.js +236 -78
  62. package/dist/core/message/message-log.js +1 -0
  63. package/dist/core/message/message-queue.js +1 -0
  64. package/dist/core/message/response-engine.js +54 -36
  65. package/dist/core/permission/ec-command-parser.js +156 -10
  66. package/dist/core/permission/sandbox-runtime.js +22 -1
  67. package/dist/core/permission/tool-policy.js +73 -30
  68. package/dist/core/protected-paths.js +7 -2
  69. package/dist/core/session/session-manager.js +21 -2
  70. package/dist/eck/manifest-engine.js +39 -13
  71. package/dist/index.js +384 -98
  72. package/dist/ipc.js +218 -9
  73. package/dist/response-system/coordinator.js +6 -4
  74. package/dist/response-system/engines/v1/proactive-flow.js +26 -8
  75. package/dist/response-system/modes/single-session/index.js +14 -1
  76. package/dist/response-system/selector.js +6 -5
  77. package/dist/trigger/anomaly-store.js +4 -0
  78. package/dist/trigger/script-executor.js +1 -0
  79. package/dist/utils/codex-app-server-registry.js +90 -0
  80. package/dist/utils/codex-cli.js +5 -1
  81. package/dist/utils/cross-platform.js +15 -0
  82. package/dist/utils/npm-ops.js +5 -12
  83. package/dist/utils/tool-summary.js +11 -0
  84. package/kits/docs/channels/aun.md +1 -1
  85. package/kits/docs/context-assembly.md +2 -2
  86. package/kits/docs/evolcore/agent.md +11 -6
  87. package/kits/docs/evolcore/aid.md +14 -0
  88. package/kits/docs/prompt-loading-architecture.md +1 -2
  89. package/kits/docs/venues/group.md +1 -1
  90. package/kits/eck_manifest.json +0 -12
  91. package/kits/rules/04-relation.md +1 -1
  92. package/kits/rules/05-venue.md +2 -2
  93. package/kits/schemas/single-session.schema.1.json +3 -3
  94. package/kits/schemas/single-session.schema.2.json +3 -3
  95. package/kits/templates/roles/admin.json +22 -0
  96. package/kits/templates/roles/member.json +27 -0
  97. package/kits/templates/roles/visitor.json +33 -3
  98. package/kits/templates/system-fragments/bootstrap.md +1 -1
  99. package/kits/templates/system-fragments/channel.md +1 -1
  100. package/kits/templates/system-fragments/session.md +1 -1
  101. package/package.json +2 -2
  102. package/dist/core/command/evol-menu-version-gate.js +0 -39
@@ -0,0 +1,804 @@
1
+ import { TextDecoder } from 'util';
2
+ import { getAidStore, loadClient, SLOT } from '../aid/index.js';
3
+ import { msgHistory } from './history.js';
4
+ import { msgOnline } from './p2p.js';
5
+ import { resolveRoot } from '../../paths.js';
6
+ import { isExplicitGroupId } from '../group-identity.js';
7
+ import { isValidAid } from '../aid/validation.js';
8
+ import { groupBan, groupBanlist, groupCreate, groupDissolve, groupInfo, groupInvite, groupJoin, groupKick, groupLeave, groupList, groupMembers, groupOnline, groupPull, groupAck, groupResume, groupRules, groupRulesFileGet, groupRulesFilePublish, groupRulesFileSet, groupSetRole, groupSuspend, groupTransferOwner, groupUnban, groupUpdate, groupUpdateRules, } from './group.js';
9
+ /** Rules uploaded by a managed task are carried as bounded UTF-8 content. */
10
+ export const MAX_MANAGED_GROUP_RULES_BYTES = 4 * 1024;
11
+ const RELATION_GROUP_READ_KINDS = new Set([
12
+ 'info',
13
+ 'pull',
14
+ 'members',
15
+ 'online',
16
+ 'rules.get',
17
+ 'rules.get-legacy',
18
+ ]);
19
+ /** Read operations that a user role may perform against its current group relation. */
20
+ export function isManagedGroupRelationRead(command) {
21
+ return RELATION_GROUP_READ_KINDS.has(command.kind);
22
+ }
23
+ export class ManagedGroupOperationError extends Error {
24
+ code;
25
+ constructor(code, message) {
26
+ super(message);
27
+ this.code = code;
28
+ this.name = 'ManagedGroupOperationError';
29
+ }
30
+ }
31
+ const GROUP_VALUE_FLAGS = new Set([
32
+ '--format', '--after-seq', '--limit', '--visibility', '--description', '--join-mode', '--group-id',
33
+ '--name', '--page', '--size', '--mode', '--question', '--max-pending', '--message', '--answer', '--duration',
34
+ ]);
35
+ function failGroup(code, reason) {
36
+ return { ok: false, code, reason };
37
+ }
38
+ function parseFlags(args) {
39
+ const positionals = [];
40
+ const values = new Map();
41
+ for (let i = 0; i < args.length; i++) {
42
+ const token = args[i];
43
+ if (!token.startsWith('--')) {
44
+ positionals.push(token);
45
+ continue;
46
+ }
47
+ if (token === '--aun-path' || token === '--app') {
48
+ return failGroup('NOT_ALLOWED', `${token} is not allowed for managed group operations`);
49
+ }
50
+ if (!GROUP_VALUE_FLAGS.has(token)) {
51
+ return failGroup('NOT_ALLOWED', `unsupported managed group option: ${token}`);
52
+ }
53
+ const value = args[++i];
54
+ if (value === undefined || value.startsWith('--')) {
55
+ return failGroup('INVALID_ARGUMENT', `${token} requires a value`);
56
+ }
57
+ if (values.has(token))
58
+ return failGroup('INVALID_ARGUMENT', `${token} may only be specified once`);
59
+ values.set(token, value);
60
+ }
61
+ const format = values.get('--format');
62
+ if (format !== undefined && format !== 'json')
63
+ return failGroup('INVALID_ARGUMENT', '--format only supports json');
64
+ return { positionals, values };
65
+ }
66
+ function requiredAid(value, label) {
67
+ const aid = String(value || '').trim().replace(/^@/, '');
68
+ if (!aid)
69
+ return failGroup('INVALID_ARGUMENT', `missing ${label}`);
70
+ if (!isValidAid(aid))
71
+ return failGroup('INVALID_AID', `invalid ${label}: ${aid}`);
72
+ return aid;
73
+ }
74
+ function requiredGroup(value) {
75
+ const groupId = String(value || '').trim();
76
+ if (!groupId)
77
+ return failGroup('INVALID_ARGUMENT', 'missing group AID');
78
+ if (!isExplicitGroupId(groupId))
79
+ return failGroup('INVALID_GROUP', `invalid group AID: ${groupId}`);
80
+ return groupId;
81
+ }
82
+ function numberFlag(flags, name) {
83
+ const raw = flags.get(name);
84
+ if (raw === undefined)
85
+ return undefined;
86
+ const value = Number(raw);
87
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
88
+ return failGroup('INVALID_ARGUMENT', `${name} must be a non-negative integer`);
89
+ }
90
+ return value;
91
+ }
92
+ function onlyFlags(flags, allowed) {
93
+ const accepted = new Set(['--format', ...allowed]);
94
+ for (const flag of flags.keys()) {
95
+ if (!accepted.has(flag))
96
+ return failGroup('NOT_ALLOWED', `${flag} is not allowed for this managed group command`);
97
+ }
98
+ return undefined;
99
+ }
100
+ function command(kind, operationId, canonicalArgv, from, extra = {}) {
101
+ return { ok: true, command: { kind, operationId, canonicalArgv, from, ...extra } };
102
+ }
103
+ function withFromAndGroup(sub, parsed, operationId, groupRequired = true) {
104
+ const from = requiredAid(parsed.positionals[0], 'from AID');
105
+ if (typeof from !== 'string')
106
+ return from;
107
+ let groupId;
108
+ if (groupRequired) {
109
+ const parsedGroup = requiredGroup(parsed.positionals[1]);
110
+ if (typeof parsedGroup !== 'string')
111
+ return parsedGroup;
112
+ groupId = parsedGroup;
113
+ }
114
+ return command(sub, operationId, ['group', sub, from, ...(groupId ? [groupId] : [])], from, groupId ? { groupId } : {});
115
+ }
116
+ /** Parse the restricted, self-scoped group command grammar used by the daemon. */
117
+ export function resolveManagedGroupOperation(argv) {
118
+ const raw = argv[0] === 'group' ? argv.slice(1) : argv;
119
+ const sub = raw[0];
120
+ if (!sub || sub === 'send')
121
+ return failGroup('NOT_ALLOWED', 'managed group operations do not allow send; use the group send daemon route');
122
+ const parsed = parseFlags(raw.slice(1));
123
+ if (!('positionals' in parsed))
124
+ return parsed;
125
+ const { positionals, values } = parsed;
126
+ if (sub === 'list') {
127
+ const flagsError = onlyFlags(values, ['--size']);
128
+ if (flagsError)
129
+ return flagsError;
130
+ if (positionals.length !== 1)
131
+ return failGroup('INVALID_ARGUMENT', 'list accepts exactly <from>');
132
+ const base = withFromAndGroup(sub, parsed, 'ec.group.list', false);
133
+ if (!base.ok)
134
+ return base;
135
+ const size = numberFlag(values, '--size');
136
+ if (typeof size !== 'number' && size !== undefined)
137
+ return size;
138
+ return command(sub, 'ec.group.list', ['group', sub, base.command.from], base.command.from, { size });
139
+ }
140
+ if (sub === 'create') {
141
+ const flagsError = onlyFlags(values, ['--visibility', '--description', '--join-mode', '--group-id']);
142
+ if (flagsError)
143
+ return flagsError;
144
+ const from = requiredAid(positionals[0], 'from AID');
145
+ if (typeof from !== 'string')
146
+ return from;
147
+ const name = positionals[1];
148
+ if (!name || positionals.length !== 2)
149
+ return failGroup('INVALID_ARGUMENT', 'create accepts exactly <from> <name>');
150
+ const visibility = values.get('--visibility');
151
+ const joinMode = values.get('--join-mode');
152
+ if (visibility !== undefined && !['public', 'private'].includes(visibility))
153
+ return failGroup('INVALID_ARGUMENT', 'invalid --visibility');
154
+ if (joinMode !== undefined && !['open', 'approval', 'invite_only', 'closed'].includes(joinMode))
155
+ return failGroup('INVALID_ARGUMENT', 'invalid --join-mode');
156
+ const groupId = values.get('--group-id');
157
+ if (groupId !== undefined && !isExplicitGroupId(groupId))
158
+ return failGroup('INVALID_GROUP', `invalid --group-id: ${groupId}`);
159
+ return command(sub, 'ec.group.create', ['group', sub, from, name], from, { name, visibility, description: values.get('--description'), joinMode, groupId });
160
+ }
161
+ const simple = new Map([
162
+ ['pull', 'ec.group.pull'], ['ack', 'ec.group.ack'], ['info', 'ec.group.info'], ['update', 'ec.group.update'],
163
+ ['dissolve', 'ec.group.dissolve'], ['suspend', 'ec.group.suspend'], ['resume', 'ec.group.resume'],
164
+ ['join', 'ec.group.join'], ['leave', 'ec.group.leave'], ['invite', 'ec.group.invite'], ['kick', 'ec.group.kick'],
165
+ ['members', 'ec.group.members'], ['online', 'ec.group.online'], ['role', 'ec.group.role'], ['owner', 'ec.group.owner'],
166
+ ['ban', 'ec.group.ban'], ['unban', 'ec.group.unban'],
167
+ ]);
168
+ if (simple.has(sub)) {
169
+ const allowedFlags = {
170
+ pull: ['--after-seq', '--limit'], ack: [], info: [], update: ['--name', '--description'], dissolve: [],
171
+ suspend: [], resume: [], join: ['--message', '--answer'], leave: [], invite: [], kick: [],
172
+ members: ['--page', '--size'], online: [], role: [], owner: [], ban: ['--duration'], unban: [],
173
+ };
174
+ const flagsError = onlyFlags(values, allowedFlags[sub] ?? []);
175
+ if (flagsError)
176
+ return flagsError;
177
+ const base = withFromAndGroup(sub, parsed, simple.get(sub));
178
+ if (!base.ok)
179
+ return base;
180
+ const extra = {};
181
+ if (sub === 'pull') {
182
+ if (positionals.length !== 2)
183
+ return failGroup('INVALID_ARGUMENT', 'pull accepts exactly <from> <group-aid>');
184
+ const afterSeq = numberFlag(values, '--after-seq');
185
+ const limit = numberFlag(values, '--limit');
186
+ if (typeof afterSeq !== 'number' && afterSeq !== undefined)
187
+ return afterSeq;
188
+ if (typeof limit !== 'number' && limit !== undefined)
189
+ return limit;
190
+ Object.assign(extra, { afterSeq, limit });
191
+ }
192
+ else if (sub === 'ack') {
193
+ const seq = Number(positionals[2]);
194
+ if (positionals.length !== 3 || !Number.isInteger(seq) || seq < 0)
195
+ return failGroup('INVALID_ARGUMENT', 'ack requires a non-negative integer seq');
196
+ extra.seq = seq;
197
+ }
198
+ else if (sub === 'update') {
199
+ if (positionals.length !== 2 || (values.get('--name') === undefined && values.get('--description') === undefined))
200
+ return failGroup('INVALID_ARGUMENT', 'update requires --name or --description');
201
+ Object.assign(extra, { name: values.get('--name'), description: values.get('--description') });
202
+ }
203
+ else if (sub === 'join') {
204
+ if (positionals.length !== 2)
205
+ return failGroup('INVALID_ARGUMENT', 'join accepts exactly <from> <group-aid>');
206
+ Object.assign(extra, { message: values.get('--message'), answer: values.get('--answer') });
207
+ }
208
+ else if (sub === 'invite') {
209
+ if (positionals.length < 3)
210
+ return failGroup('INVALID_ARGUMENT', 'invite requires at least one member AID');
211
+ const members = positionals.slice(2);
212
+ for (const aid of members) {
213
+ const checked = requiredAid(aid, 'member AID');
214
+ if (typeof checked !== 'string')
215
+ return checked;
216
+ }
217
+ extra.members = members;
218
+ }
219
+ else if (sub === 'kick' || sub === 'unban') {
220
+ if (positionals.length !== 3)
221
+ return failGroup('INVALID_ARGUMENT', `${sub} requires a member AID`);
222
+ const memberAid = requiredAid(positionals[2], 'member AID');
223
+ if (typeof memberAid !== 'string')
224
+ return memberAid;
225
+ extra.memberAid = memberAid;
226
+ }
227
+ else if (sub === 'members') {
228
+ if (positionals.length !== 2)
229
+ return failGroup('INVALID_ARGUMENT', 'members accepts exactly <from> <group-aid>');
230
+ const page = numberFlag(values, '--page');
231
+ const size = numberFlag(values, '--size');
232
+ if (typeof page !== 'number' && page !== undefined)
233
+ return page;
234
+ if (typeof size !== 'number' && size !== undefined)
235
+ return size;
236
+ Object.assign(extra, { page, size });
237
+ }
238
+ else if (sub === 'role') {
239
+ if (positionals.length !== 4)
240
+ return failGroup('INVALID_ARGUMENT', 'role requires member AID and admin|member');
241
+ const memberAid = requiredAid(positionals[2], 'member AID');
242
+ if (typeof memberAid !== 'string')
243
+ return memberAid;
244
+ if (!['admin', 'member'].includes(positionals[3]))
245
+ return failGroup('INVALID_ARGUMENT', 'role must be admin or member');
246
+ Object.assign(extra, { memberAid, role: positionals[3] });
247
+ }
248
+ else if (sub === 'owner') {
249
+ if (positionals.length !== 3)
250
+ return failGroup('INVALID_ARGUMENT', 'owner requires a new owner AID');
251
+ const newOwner = requiredAid(positionals[2], 'new owner AID');
252
+ if (typeof newOwner !== 'string')
253
+ return newOwner;
254
+ extra.newOwner = newOwner;
255
+ }
256
+ else if (sub === 'ban') {
257
+ if (positionals.length > 3)
258
+ return failGroup('INVALID_ARGUMENT', 'ban accepts at most one member AID');
259
+ if (positionals.length === 3) {
260
+ const memberAid = requiredAid(positionals[2], 'member AID');
261
+ if (typeof memberAid !== 'string')
262
+ return memberAid;
263
+ extra.memberAid = memberAid;
264
+ }
265
+ const durationSeconds = numberFlag(values, '--duration');
266
+ if (typeof durationSeconds !== 'number' && durationSeconds !== undefined)
267
+ return durationSeconds;
268
+ extra.durationSeconds = durationSeconds;
269
+ extra.list = positionals.length === 2;
270
+ }
271
+ else if (sub === 'info' || sub === 'dissolve' || sub === 'suspend' || sub === 'resume' || sub === 'leave' || sub === 'online') {
272
+ if (positionals.length !== 2)
273
+ return failGroup('INVALID_ARGUMENT', `${sub} accepts exactly <from> <group-aid>`);
274
+ }
275
+ return command(sub, base.command.operationId, ['group', sub, base.command.from, ...(base.command.groupId ? [base.command.groupId] : [])], base.command.from, { groupId: base.command.groupId, ...extra });
276
+ }
277
+ if (sub === 'rules') {
278
+ const flagsError = onlyFlags(values, ['--mode', '--question', '--max-pending']);
279
+ if (flagsError)
280
+ return flagsError;
281
+ const from = requiredAid(positionals[0], 'from AID');
282
+ if (typeof from !== 'string')
283
+ return from;
284
+ const groupId = requiredGroup(positionals[1]);
285
+ if (typeof groupId !== 'string')
286
+ return groupId;
287
+ const rest = positionals.slice(2);
288
+ const action = rest[0];
289
+ if (action === 'get' || action === 'publish') {
290
+ const actionFlagsError = onlyFlags(values, []);
291
+ if (actionFlagsError)
292
+ return actionFlagsError;
293
+ if (rest.length !== 1)
294
+ return failGroup('INVALID_ARGUMENT', `rules ${action} accepts no positional arguments`);
295
+ return command(`rules.${action}`, `ec.group.rules.${action}`, ['group', 'rules', from, groupId, action], from, { groupId });
296
+ }
297
+ if (action === 'set') {
298
+ const actionFlagsError = onlyFlags(values, []);
299
+ if (actionFlagsError)
300
+ return actionFlagsError;
301
+ if (rest.length !== 2)
302
+ return failGroup('INVALID_ARGUMENT', 'rules set requires a file argument');
303
+ return command('rules.set', 'ec.group.rules.set', ['group', 'rules', from, groupId, 'set', '<rules-content>'], from, { groupId, filePath: rest[1] });
304
+ }
305
+ if (rest.length !== 0)
306
+ return failGroup('INVALID_ARGUMENT', 'unknown rules action');
307
+ const wantsUpdate = values.has('--mode') || values.has('--question') || values.has('--max-pending');
308
+ const operationId = wantsUpdate ? 'ec.group.rules.update' : 'ec.group.rules.read';
309
+ const mode = values.get('--mode');
310
+ if (mode !== undefined && !['open', 'approval', 'invite_only', 'closed'].includes(mode))
311
+ return failGroup('INVALID_ARGUMENT', 'invalid --mode');
312
+ const maxPending = numberFlag(values, '--max-pending');
313
+ if (typeof maxPending !== 'number' && maxPending !== undefined)
314
+ return maxPending;
315
+ return command(wantsUpdate ? 'rules.update' : 'rules.get-legacy', operationId, ['group', 'rules', from, groupId, ...(wantsUpdate ? ['update'] : ['read'])], from, { groupId, mode, question: values.get('--question'), maxPending });
316
+ }
317
+ return failGroup('NOT_ALLOWED', `unsupported managed group command: ${sub}`);
318
+ }
319
+ /** Execute a parsed command using daemon-owned AUN credentials and root. */
320
+ export async function executeManagedGroupOperation(command, rulesContent) {
321
+ const opts = { aunPath: resolveRoot() };
322
+ let result;
323
+ switch (command.kind) {
324
+ case 'list':
325
+ result = await groupList({ from: command.from, size: command.size, ...opts });
326
+ break;
327
+ case 'create':
328
+ result = await groupCreate({ from: command.from, name: command.name, visibility: command.visibility, description: command.description, joinMode: command.joinMode, groupId: command.groupId, ...opts });
329
+ break;
330
+ case 'pull':
331
+ result = await groupPull({ from: command.from, groupId: command.groupId, afterSeq: command.afterSeq, limit: command.limit, ...opts });
332
+ break;
333
+ case 'ack':
334
+ result = await groupAck({ from: command.from, groupId: command.groupId, seq: command.seq, ...opts });
335
+ break;
336
+ case 'info':
337
+ result = await groupInfo({ from: command.from, groupId: command.groupId, ...opts });
338
+ break;
339
+ case 'update':
340
+ result = await groupUpdate({ from: command.from, groupId: command.groupId, name: command.name, description: command.description, ...opts });
341
+ break;
342
+ case 'dissolve':
343
+ result = await groupDissolve({ from: command.from, groupId: command.groupId, ...opts });
344
+ break;
345
+ case 'suspend':
346
+ result = await groupSuspend({ from: command.from, groupId: command.groupId, ...opts });
347
+ break;
348
+ case 'resume':
349
+ result = await groupResume({ from: command.from, groupId: command.groupId, ...opts });
350
+ break;
351
+ case 'join':
352
+ result = await groupJoin({ from: command.from, groupId: command.groupId, message: command.message, answer: command.answer, ...opts });
353
+ break;
354
+ case 'leave':
355
+ result = await groupLeave({ from: command.from, groupId: command.groupId, ...opts });
356
+ break;
357
+ case 'invite':
358
+ result = await groupInvite({ from: command.from, groupId: command.groupId, members: command.members, ...opts });
359
+ break;
360
+ case 'kick':
361
+ result = await groupKick({ from: command.from, groupId: command.groupId, memberAid: command.memberAid, ...opts });
362
+ break;
363
+ case 'members':
364
+ result = await groupMembers({ from: command.from, groupId: command.groupId, page: command.page, size: command.size, ...opts });
365
+ break;
366
+ case 'online':
367
+ result = await groupOnline({ from: command.from, groupId: command.groupId, ...opts });
368
+ break;
369
+ case 'role':
370
+ result = await groupSetRole({ from: command.from, groupId: command.groupId, memberAid: command.memberAid, role: command.role, ...opts });
371
+ break;
372
+ case 'owner':
373
+ result = await groupTransferOwner({ from: command.from, groupId: command.groupId, newOwner: command.newOwner, ...opts });
374
+ break;
375
+ case 'ban':
376
+ result = command.list ? await groupBanlist({ from: command.from, groupId: command.groupId, ...opts }) : await groupBan({ from: command.from, groupId: command.groupId, memberAid: command.memberAid, durationSeconds: command.durationSeconds, ...opts });
377
+ break;
378
+ case 'unban':
379
+ result = await groupUnban({ from: command.from, groupId: command.groupId, memberAid: command.memberAid, ...opts });
380
+ break;
381
+ case 'rules.get':
382
+ result = await groupRulesFileGet({ from: command.from, groupId: command.groupId, ...opts });
383
+ break;
384
+ case 'rules.set':
385
+ if (typeof rulesContent !== 'string')
386
+ throw new ManagedGroupOperationError('RULES_CONTENT_REQUIRED', 'rules set requires validated rules content');
387
+ if (!rulesContent || Buffer.byteLength(rulesContent, 'utf8') > MAX_MANAGED_GROUP_RULES_BYTES || Buffer.from(rulesContent, 'utf8').toString('utf8') !== rulesContent) {
388
+ throw new ManagedGroupOperationError('INVALID_RULES_CONTENT', `rules content must be valid non-empty UTF-8 up to ${MAX_MANAGED_GROUP_RULES_BYTES} bytes`);
389
+ }
390
+ result = await groupRulesFileSet({ from: command.from, groupId: command.groupId, content: rulesContent, ...opts });
391
+ break;
392
+ case 'rules.publish':
393
+ result = await groupRulesFilePublish({ from: command.from, groupId: command.groupId, ...opts });
394
+ break;
395
+ case 'rules.update':
396
+ result = await groupUpdateRules({ from: command.from, groupId: command.groupId, mode: command.mode, question: command.question, maxPending: command.maxPending, ...opts });
397
+ break;
398
+ case 'rules.get-legacy':
399
+ result = await groupRules({ from: command.from, groupId: command.groupId, ...opts });
400
+ break;
401
+ default: throw new ManagedGroupOperationError('NOT_ALLOWED', `unsupported managed group operation: ${command.kind}`);
402
+ }
403
+ if (!result || result.ok !== true) {
404
+ throw new ManagedGroupOperationError('GROUP_OPERATION_FAILED', String(result?.error ?? 'group operation failed'));
405
+ }
406
+ return result;
407
+ }
408
+ export class ManagedMsgOperationError extends Error {
409
+ code;
410
+ constructor(code, message) {
411
+ super(message);
412
+ this.code = code;
413
+ this.name = 'ManagedMsgOperationError';
414
+ }
415
+ }
416
+ const HISTORY_VALUE_FLAGS = new Set([
417
+ '--session', '--limit', '--before', '--after', '--direction', '--format',
418
+ ]);
419
+ function failMsg(code, reason) {
420
+ return { ok: false, code, reason };
421
+ }
422
+ function parseTime(value) {
423
+ const trimmed = value.trim();
424
+ if (!trimmed)
425
+ return undefined;
426
+ if (/^-?\d+$/.test(trimmed)) {
427
+ const timestamp = Number(trimmed);
428
+ return Number.isSafeInteger(timestamp) ? timestamp : undefined;
429
+ }
430
+ const timestamp = Date.parse(trimmed);
431
+ return Number.isFinite(timestamp) ? timestamp : undefined;
432
+ }
433
+ /** Parse the daemon-owned, private-relation msg read grammar. */
434
+ export function resolveManagedMsgOperation(argv) {
435
+ const raw = argv[0] === 'msg' ? argv.slice(1) : argv;
436
+ const kind = raw[0];
437
+ if (kind !== 'history' && kind !== 'online') {
438
+ return failMsg('NOT_ALLOWED', `unsupported managed msg command: ${kind || '<missing>'}`);
439
+ }
440
+ const from = String(raw[1] || '').replace(/^@/, '');
441
+ const target = String(raw[2] || '').replace(/^@/, '');
442
+ if (!isValidAid(from))
443
+ return failMsg('INVALID_AID', `invalid from AID: ${from || '<missing>'}`);
444
+ if (!isValidAid(target))
445
+ return failMsg('INVALID_AID', `invalid target AID: ${target || '<missing>'}`);
446
+ if (kind === 'online') {
447
+ const allowed = new Set(['--format']);
448
+ let format;
449
+ for (let index = 3; index < raw.length; index++) {
450
+ const flag = raw[index];
451
+ if (!allowed.has(flag))
452
+ return failMsg('NOT_ALLOWED', `unsupported managed online option: ${flag}`);
453
+ if (format !== undefined)
454
+ return failMsg('INVALID_ARGUMENT', '--format may only be specified once');
455
+ format = raw[++index];
456
+ if (!format || format.startsWith('--'))
457
+ return failMsg('INVALID_ARGUMENT', '--format requires a value');
458
+ }
459
+ if (format !== undefined && format !== 'json')
460
+ return failMsg('INVALID_ARGUMENT', '--format only supports json');
461
+ return {
462
+ ok: true,
463
+ command: {
464
+ kind,
465
+ operationId: 'ec.msg.online',
466
+ canonicalArgv: ['msg', kind, from, target],
467
+ from,
468
+ target,
469
+ },
470
+ };
471
+ }
472
+ const values = new Map();
473
+ for (let index = 3; index < raw.length; index++) {
474
+ const flag = raw[index];
475
+ if (flag === '--aun-path' || flag === '--app' || flag === '--channel' || flag === '--channel-name') {
476
+ return failMsg('NOT_ALLOWED', `${flag} is not allowed for managed msg history`);
477
+ }
478
+ if (!HISTORY_VALUE_FLAGS.has(flag))
479
+ return failMsg('NOT_ALLOWED', `unsupported managed history option: ${flag}`);
480
+ if (values.has(flag))
481
+ return failMsg('INVALID_ARGUMENT', `${flag} may only be specified once`);
482
+ const value = raw[++index];
483
+ if (!value || value.startsWith('--'))
484
+ return failMsg('INVALID_ARGUMENT', `${flag} requires a value`);
485
+ values.set(flag, value);
486
+ }
487
+ const format = values.get('--format');
488
+ if (format !== undefined && format !== 'text' && format !== 'json') {
489
+ return failMsg('INVALID_ARGUMENT', '--format only supports text|json');
490
+ }
491
+ const limitRaw = values.get('--limit');
492
+ const limit = limitRaw === undefined ? undefined : Number(limitRaw);
493
+ if (limit !== undefined && (!Number.isInteger(limit) || limit < 1 || limit > 500)) {
494
+ return failMsg('INVALID_ARGUMENT', '--limit must be an integer from 1 to 500');
495
+ }
496
+ const direction = values.get('--direction');
497
+ if (direction !== undefined && !['in', 'out', 'all'].includes(direction)) {
498
+ return failMsg('INVALID_ARGUMENT', '--direction only supports in|out|all');
499
+ }
500
+ const beforeRaw = values.get('--before');
501
+ const afterRaw = values.get('--after');
502
+ const before = beforeRaw === undefined ? undefined : parseTime(beforeRaw);
503
+ const after = afterRaw === undefined ? undefined : parseTime(afterRaw);
504
+ if (beforeRaw !== undefined && before === undefined)
505
+ return failMsg('INVALID_ARGUMENT', 'invalid --before time');
506
+ if (afterRaw !== undefined && after === undefined)
507
+ return failMsg('INVALID_ARGUMENT', 'invalid --after time');
508
+ if (before !== undefined && after !== undefined && after >= before) {
509
+ return failMsg('INVALID_ARGUMENT', '--after must be earlier than --before');
510
+ }
511
+ return {
512
+ ok: true,
513
+ command: {
514
+ kind,
515
+ operationId: 'ec.msg.history',
516
+ canonicalArgv: ['msg', kind, from, target],
517
+ from,
518
+ target,
519
+ session: values.get('--session'),
520
+ limit,
521
+ before,
522
+ after,
523
+ direction,
524
+ },
525
+ };
526
+ }
527
+ /** Execute using daemon-owned AUN state and credentials. */
528
+ export async function executeManagedMsgOperation(command) {
529
+ const result = command.kind === 'history'
530
+ ? msgHistory({
531
+ from: command.from,
532
+ target: command.target,
533
+ channel: 'aun',
534
+ session: command.session,
535
+ limit: command.limit,
536
+ before: command.before,
537
+ after: command.after,
538
+ direction: command.direction,
539
+ })
540
+ : await msgOnline({
541
+ from: command.from,
542
+ targets: [command.target],
543
+ aunPath: resolveRoot(),
544
+ });
545
+ if (!result || result.ok !== true) {
546
+ throw new ManagedMsgOperationError(typeof result?.code === 'string' ? result.code : 'MSG_OPERATION_FAILED', String(result?.error ?? 'msg operation failed'));
547
+ }
548
+ return result;
549
+ }
550
+ export const MAX_MANAGED_FS_CAT_BYTES = 256 * 1024;
551
+ const MAX_MANAGED_FS_HEAD_BYTES = 4096;
552
+ const MAX_REMOTE_PATH_LENGTH = 2048;
553
+ export class ManagedFsOperationError extends Error {
554
+ code;
555
+ constructor(code, message) {
556
+ super(message);
557
+ this.code = code;
558
+ this.name = 'ManagedFsOperationError';
559
+ }
560
+ }
561
+ const FS_VALUE_FLAGS = new Set([
562
+ '--as', '--format', '--max-bytes', '--head-bytes', '--name', '--pattern', '--type', '--node-type',
563
+ '--size', '--mtime', '--page', '--page-size',
564
+ ]);
565
+ function failFs(code, reason) {
566
+ return { ok: false, code, reason };
567
+ }
568
+ function positiveInteger(value, flag, max) {
569
+ if (value === undefined)
570
+ return undefined;
571
+ const number = Number(value);
572
+ if (!Number.isInteger(number) || number < 1 || number > max) {
573
+ return failFs('INVALID_ARGUMENT', `${flag} must be an integer from 1 to ${max}`);
574
+ }
575
+ return number;
576
+ }
577
+ function parseRemote(value, allowEmpty) {
578
+ if (!value)
579
+ return failFs('INVALID_ARGUMENT', 'missing remote group path');
580
+ const separator = value.indexOf(':');
581
+ if (separator <= 0)
582
+ return failFs('INVALID_PATH', 'remote path must use <group-aid>:<absolute-path>');
583
+ const groupId = value.slice(0, separator).trim();
584
+ const remotePath = value.slice(separator + 1);
585
+ if (!isValidAid(groupId) || !isExplicitGroupId(groupId)) {
586
+ return failFs('INVALID_GROUP', `invalid group AID: ${groupId}`);
587
+ }
588
+ if (remotePath === '' && allowEmpty)
589
+ return { groupId, remotePath: '' };
590
+ if (!remotePath.startsWith('/') || remotePath.length > MAX_REMOTE_PATH_LENGTH || /[\u0000-\u001f\u007f]/.test(remotePath)) {
591
+ return failFs('INVALID_PATH', 'remote group path must be a bounded absolute path');
592
+ }
593
+ return { groupId, remotePath };
594
+ }
595
+ /** Parse the daemon-owned, current-group read-only filesystem grammar. */
596
+ export function resolveManagedFsOperation(argv) {
597
+ const raw = argv[0] === 'fs' ? argv.slice(1) : argv;
598
+ const kind = raw[0];
599
+ if (!kind || !['ls', 'stat', 'lstat', 'cat', 'find', 'df'].includes(kind)) {
600
+ return failFs('NOT_ALLOWED', `unsupported managed fs command: ${kind || '<missing>'}`);
601
+ }
602
+ const values = new Map();
603
+ const positionals = [];
604
+ let follow = false;
605
+ for (let index = 1; index < raw.length; index++) {
606
+ const token = raw[index];
607
+ if (token === '-L' || token === '--follow') {
608
+ if (kind !== 'stat')
609
+ return failFs('NOT_ALLOWED', `${token} is only allowed for stat`);
610
+ follow = true;
611
+ continue;
612
+ }
613
+ if (!token.startsWith('-')) {
614
+ positionals.push(token);
615
+ continue;
616
+ }
617
+ if (token === '--aun-path' || token === '--app' || token === '--token') {
618
+ return failFs('NOT_ALLOWED', `${token} is not allowed for managed fs operations`);
619
+ }
620
+ if (!FS_VALUE_FLAGS.has(token))
621
+ return failFs('NOT_ALLOWED', `unsupported managed fs option: ${token}`);
622
+ if (values.has(token))
623
+ return failFs('INVALID_ARGUMENT', `${token} may only be specified once`);
624
+ const value = raw[++index];
625
+ if (!value || value.startsWith('--'))
626
+ return failFs('INVALID_ARGUMENT', `${token} requires a value`);
627
+ values.set(token, value);
628
+ }
629
+ if (positionals.length !== 1)
630
+ return failFs('INVALID_ARGUMENT', `${kind} accepts exactly one remote group path`);
631
+ const actorAidValue = values.get('--as');
632
+ const actorAid = actorAidValue === undefined ? undefined : actorAidValue.replace(/^@/, '');
633
+ if (actorAid !== undefined && !isValidAid(actorAid))
634
+ return failFs('INVALID_AID', `invalid --as AID: ${actorAid || '<missing>'}`);
635
+ const format = values.get('--format');
636
+ if (format !== undefined && format !== 'json')
637
+ return failFs('INVALID_ARGUMENT', '--format only supports json');
638
+ const allowedByKind = {
639
+ ls: new Set(['--as', '--format']),
640
+ stat: new Set(['--as', '--format']),
641
+ lstat: new Set(['--as', '--format']),
642
+ cat: new Set(['--as', '--format', '--max-bytes', '--head-bytes']),
643
+ find: new Set(['--as', '--format', '--name', '--pattern', '--type', '--node-type', '--size', '--mtime', '--page', '--page-size']),
644
+ df: new Set(['--as', '--format']),
645
+ };
646
+ for (const flag of values.keys()) {
647
+ if (!allowedByKind[kind].has(flag))
648
+ return failFs('NOT_ALLOWED', `${flag} is not allowed for managed fs ${kind}`);
649
+ }
650
+ const remote = parseRemote(positionals[0], kind === 'df');
651
+ if (!('groupId' in remote))
652
+ return remote;
653
+ const effectiveKind = kind === 'stat' && !follow ? 'lstat' : kind;
654
+ const maxBytes = positiveInteger(values.get('--max-bytes'), '--max-bytes', MAX_MANAGED_FS_CAT_BYTES);
655
+ if (typeof maxBytes !== 'number' && maxBytes !== undefined)
656
+ return maxBytes;
657
+ const headBytes = positiveInteger(values.get('--head-bytes'), '--head-bytes', MAX_MANAGED_FS_HEAD_BYTES);
658
+ if (typeof headBytes !== 'number' && headBytes !== undefined)
659
+ return headBytes;
660
+ const page = positiveInteger(values.get('--page'), '--page', 10_000);
661
+ if (typeof page !== 'number' && page !== undefined)
662
+ return page;
663
+ const pageSize = positiveInteger(values.get('--page-size'), '--page-size', 200);
664
+ if (typeof pageSize !== 'number' && pageSize !== undefined)
665
+ return pageSize;
666
+ const nodeType = values.get('--type') ?? values.get('--node-type');
667
+ if (nodeType !== undefined && !['f', 'd', 'l'].includes(nodeType)) {
668
+ return failFs('INVALID_ARGUMENT', '--type only supports f|d|l');
669
+ }
670
+ return {
671
+ ok: true,
672
+ command: {
673
+ kind: effectiveKind,
674
+ operationId: `ec.fs.${effectiveKind}`,
675
+ canonicalArgv: ['fs', effectiveKind, `${remote.groupId}:${remote.remotePath}`, ...(actorAid ? ['--as', actorAid] : [])],
676
+ actorAid,
677
+ groupId: remote.groupId,
678
+ remotePath: remote.remotePath,
679
+ maxBytes,
680
+ headBytes,
681
+ name: values.get('--name') ?? values.get('--pattern'),
682
+ nodeType,
683
+ size: values.get('--size'),
684
+ mtime: values.get('--mtime'),
685
+ page,
686
+ pageSize,
687
+ },
688
+ };
689
+ }
690
+ function numberFrom(value, keys) {
691
+ for (const key of keys) {
692
+ const number = Number(value?.[key]);
693
+ if (Number.isFinite(number))
694
+ return number;
695
+ }
696
+ return undefined;
697
+ }
698
+ function bytesFromDownload(value) {
699
+ const candidate = value?.bytes ?? value?.data ?? value?.content ?? value?.blob ?? value;
700
+ if (candidate instanceof Uint8Array)
701
+ return candidate;
702
+ if (Buffer.isBuffer(candidate))
703
+ return new Uint8Array(candidate);
704
+ if (typeof candidate === 'string')
705
+ return new Uint8Array(Buffer.from(candidate, 'base64'));
706
+ if (Array.isArray(candidate))
707
+ return new Uint8Array(candidate);
708
+ throw new ManagedFsOperationError('FS_DOWNLOAD_INVALID', 'group filesystem returned an unsupported blob');
709
+ }
710
+ async function withClient(actorAid, fn) {
711
+ const store = await getAidStore({ slotId: SLOT.cli, aunPath: resolveRoot() });
712
+ let client;
713
+ try {
714
+ client = await loadClient(store, actorAid);
715
+ await client.connect({ connection_kind: 'short', short_ttl_ms: 30000, auto_reconnect: false });
716
+ return await fn(client);
717
+ }
718
+ finally {
719
+ if (client) {
720
+ try {
721
+ await client.close();
722
+ }
723
+ catch { /* best effort */ }
724
+ }
725
+ try {
726
+ store.close();
727
+ }
728
+ catch { /* best effort */ }
729
+ }
730
+ }
731
+ /** Execute a parsed read against the current group using daemon-owned credentials. */
732
+ export async function executeManagedFsOperation(command) {
733
+ if (!command.actorAid)
734
+ throw new ManagedFsOperationError('INVALID_ACTOR', 'managed fs actor is required');
735
+ const ref = `${command.groupId}:${command.remotePath}`;
736
+ try {
737
+ return await withClient(command.actorAid, async (client) => {
738
+ switch (command.kind) {
739
+ case 'ls': {
740
+ const raw = await client.group.fs.ls(ref);
741
+ const value = raw;
742
+ const items = Array.isArray(value) ? value : value?.items ?? value?.nodes ?? value?.entries ?? [];
743
+ return { ok: true, command: 'ls', path: ref, items };
744
+ }
745
+ case 'stat':
746
+ return { ok: true, command: 'stat', path: ref, node: await client.group.fs.stat(ref) };
747
+ case 'lstat':
748
+ return { ok: true, command: 'lstat', path: ref, node: await client.group.fs.lstat(ref) };
749
+ case 'find': {
750
+ const raw = await client.group.fs.find(ref, {
751
+ name: command.name,
752
+ pattern: command.name,
753
+ type: command.nodeType,
754
+ size: command.size,
755
+ mtime: command.mtime,
756
+ page: command.page,
757
+ page_size: command.pageSize,
758
+ });
759
+ const value = raw;
760
+ const items = Array.isArray(value) ? value : value?.items ?? value?.nodes ?? value?.entries ?? [];
761
+ return { ok: true, command: 'find', path: ref, items };
762
+ }
763
+ case 'df':
764
+ return { ok: true, command: 'df', path: `${command.groupId}:`, usage: await client.group.fs.df(`${command.groupId}:`) };
765
+ case 'cat': {
766
+ const node = await client.group.fs.stat(ref);
767
+ const size = numberFrom(node, ['size', 'size_bytes', 'bytes']);
768
+ const maxBytes = Math.min(command.maxBytes ?? MAX_MANAGED_FS_CAT_BYTES, MAX_MANAGED_FS_CAT_BYTES);
769
+ if (size === undefined)
770
+ throw new ManagedFsOperationError('FS_SIZE_REQUIRED', 'group file size is required before managed cat');
771
+ if (size > maxBytes)
772
+ throw new ManagedFsOperationError('FS_CAT_TOO_LARGE', `managed cat is limited to ${maxBytes} bytes`);
773
+ const bytes = bytesFromDownload(await client.group.fs.cp(ref, { kind: 'blob' }, { verifyHash: true }));
774
+ if (bytes.byteLength > maxBytes)
775
+ throw new ManagedFsOperationError('FS_CAT_TOO_LARGE', `managed cat is limited to ${maxBytes} bytes`);
776
+ const headBytes = Math.min(command.headBytes ?? 256, MAX_MANAGED_FS_HEAD_BYTES, bytes.byteLength);
777
+ if (bytes.subarray(0, Math.min(bytes.byteLength, 8192)).some(byte => byte === 0)) {
778
+ return {
779
+ ok: true,
780
+ command: 'cat',
781
+ path: ref,
782
+ size: bytes.byteLength,
783
+ binary: true,
784
+ head: { encoding: 'base64', bytes: headBytes, data: Buffer.from(bytes.subarray(0, headBytes)).toString('base64') },
785
+ };
786
+ }
787
+ let text;
788
+ try {
789
+ text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
790
+ }
791
+ catch {
792
+ throw new ManagedFsOperationError('FS_NOT_UTF8', 'managed cat only returns UTF-8 text or a bounded binary head');
793
+ }
794
+ return { ok: true, command: 'cat', path: ref, size: bytes.byteLength, binary: false, text };
795
+ }
796
+ }
797
+ });
798
+ }
799
+ catch (error) {
800
+ if (error instanceof ManagedFsOperationError)
801
+ throw error;
802
+ throw new ManagedFsOperationError('FS_OPERATION_FAILED', error instanceof Error ? error.message : String(error));
803
+ }
804
+ }