evolcore 0.0.21 → 0.0.23
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/AGENTS.md +62 -0
- package/CHANGELOG.md +53 -0
- package/README.md +2 -0
- package/bin/codex-managed-acl.ps1 +155 -0
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +376 -96
- package/dist/agents/baseagent-availability.js +48 -0
- package/dist/agents/baseagent.js +1 -1
- package/dist/agents/claude-runner.js +44 -226
- package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +103 -17
- package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
- package/dist/agents/codex/catalog.js +70 -0
- package/dist/agents/codex/diagnostics.js +154 -0
- package/dist/agents/codex-runner.js +1292 -219
- package/dist/agents/ecagent-runner.js +10 -31
- package/dist/agents/gemini-runner.js +2 -6
- package/dist/aun/aid/store.js +2 -0
- package/dist/aun/menu-client.js +272 -0
- package/dist/aun/msg/group.js +65 -4
- package/dist/aun/msg/index.js +1 -1
- package/dist/aun/msg/managed-operation.js +51 -4
- package/dist/aun/outbox.js +7 -0
- package/dist/aun/rpc/client.js +8 -1
- package/dist/channels/aun.js +240 -36
- package/dist/channels/wechat.js +81 -10
- package/dist/cli/aun-commands.js +48 -4
- package/dist/cli/command-log.js +21 -2
- package/dist/cli/daemon-commands.js +169 -51
- package/dist/cli/index.js +20 -0
- package/dist/cli/init.js +2 -10
- package/dist/cli/menu-client.js +95 -0
- package/dist/cli/model.js +23 -8
- package/dist/cli/task-context.js +16 -2
- package/dist/cli/trigger-command.js +22 -5
- package/dist/config/config-field-policy.js +3 -0
- package/dist/config/config-manager.js +32 -5
- package/dist/config/contact-book-store.js +25 -3
- package/dist/config/schema-migration.js +14 -2
- package/dist/core/auth/agent-delegation.js +15 -0
- package/dist/core/auth/auth-gateway.js +8 -0
- package/dist/core/auth/authorization-audit.js +70 -6
- package/dist/core/auth/operation-catalog.js +4 -0
- package/dist/core/auth/remote-access-store.js +135 -0
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +93 -25
- package/dist/core/command/command-handler.js +62 -5
- package/dist/core/command/daemon-diagnostics.js +448 -0
- package/dist/core/command/menu-catalog.js +3 -0
- package/dist/core/command/menu-handler.js +150 -33
- package/dist/core/command/menu-protocol.js +120 -1
- package/dist/core/command/slash-gate.js +3 -3
- package/dist/core/command/slash-handler.js +83 -40
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +32 -0
- package/dist/core/handoff/runtime.js +23 -3
- package/dist/core/message/im-renderer.js +7 -3
- package/dist/core/message/message-bridge.js +60 -2
- package/dist/core/message/message-log.js +33 -0
- package/dist/core/message/message-queue.js +41 -15
- package/dist/core/message/response-engine.js +246 -114
- package/dist/core/model/catalog-service.js +308 -0
- package/dist/core/model/model-catalog.js +124 -334
- package/dist/core/model/model-diagnostics.js +4 -5
- package/dist/core/model/model-fallback.js +9 -16
- package/dist/core/model/model-permission.js +1 -1
- package/dist/core/permission/approval-gateway.js +16 -1
- package/dist/core/permission/ec-command-parser.js +272 -70
- package/dist/core/permission/protected-paths.js +74 -14
- package/dist/core/permission/sandbox-runtime.js +4 -2
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +290 -30
- package/dist/core/runtime-lock.js +13 -8
- package/dist/core/session/session-manager.js +30 -0
- package/dist/core/session/session-renew.js +18 -1
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/index.js +104 -34
- package/dist/ipc.js +218 -96
- package/dist/paths.js +18 -0
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/feedback.js +14 -2
- package/dist/trigger/manager.js +3 -1
- package/dist/trigger/parser.js +86 -13
- package/dist/trigger/patch.js +104 -1
- package/dist/trigger/scheduler.js +20 -3
- package/dist/utils/ecweb-supervisor.js +11 -2
- package/dist/utils/ecweb-utils.js +8 -26
- package/dist/utils/error-utils.js +7 -0
- package/dist/utils/logger.js +9 -4
- package/dist/utils/npm-ops.js +47 -15
- package/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/evolcore/INDEX.md +3 -2
- package/kits/docs/evolcore/agent-create.md +159 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/group-collaboration.md +256 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/menu-client.md +34 -0
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
- package/kits/migrations/migrate-role-config-v4.mjs +11 -5
- package/kits/migrations/migrate-role-config-v5.mjs +8 -2
- package/kits/schemas/_meta.json +3 -2
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +10 -2
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +11 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getRunnerCatalog, catalogEfforts, assertCatalogSelectable } from '../core/model/model-catalog.js';
|
|
1
2
|
import fs from 'fs';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import { AgentHarness, JsonlSessionRepo, NodeExecutionEnv, } from '../../ecagent/dist/node.js';
|
|
@@ -20,8 +21,6 @@ const PROVIDER_ID = 'evolcore-gateway';
|
|
|
20
21
|
const DEFAULT_SYSTEM_PROMPT = `You are ecagent, the coding agent built into EvolCore.
|
|
21
22
|
Work directly in the current project. Inspect relevant files before changing them, keep edits scoped, and verify the result.
|
|
22
23
|
Use the provided tools for filesystem and shell work. Never claim a tool action succeeded unless its result confirms success.`;
|
|
23
|
-
const MODEL_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
24
|
-
const MODEL_LIST_TIMEOUT_MS = 8_000;
|
|
25
24
|
const MAX_READ_BYTES = 1024 * 1024;
|
|
26
25
|
const MAX_TOOL_OUTPUT = 200_000;
|
|
27
26
|
const MAX_SCAN_FILES = 5_000;
|
|
@@ -705,7 +704,6 @@ export class EcagentRunner {
|
|
|
705
704
|
permissionGateway;
|
|
706
705
|
sendPromptFn;
|
|
707
706
|
onCompactStart;
|
|
708
|
-
modelCache;
|
|
709
707
|
constructor(config, callbacks, runtime) {
|
|
710
708
|
this.config = {
|
|
711
709
|
...config,
|
|
@@ -745,35 +743,16 @@ export class EcagentRunner {
|
|
|
745
743
|
{ key: 'bypass', nameZh: '放行', description: '普通审批清单自动执行,强危险操作仍需审批', available: true },
|
|
746
744
|
];
|
|
747
745
|
}
|
|
746
|
+
getModelCatalog() {
|
|
747
|
+
return getRunnerCatalog('ecagent', this.agentAid, this.config);
|
|
748
|
+
}
|
|
748
749
|
async listModels() {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
headers: {
|
|
756
|
-
authorization: `Bearer ${this.config.apiKey}`,
|
|
757
|
-
...this.config.headers,
|
|
758
|
-
},
|
|
759
|
-
signal,
|
|
760
|
-
});
|
|
761
|
-
if (!response.ok)
|
|
762
|
-
throw new Error(`Failed to list ecagent models: HTTP ${response.status}`);
|
|
763
|
-
const payload = await response.json();
|
|
764
|
-
const ids = [...new Set((payload.data ?? [])
|
|
765
|
-
.map(item => typeof item.id === 'string' ? item.id : '')
|
|
766
|
-
.filter(id => id.startsWith('claude-') || (id.startsWith('gpt-') && !id.startsWith('gpt-image-'))))];
|
|
767
|
-
if (ids.length === 0)
|
|
768
|
-
throw new Error('Gateway returned no supported Claude or GPT models');
|
|
769
|
-
this.modelCache = { ids, fetchedAt: Date.now() };
|
|
770
|
-
return [...ids];
|
|
771
|
-
}
|
|
772
|
-
catch (error) {
|
|
773
|
-
if (stale && stale.length > 0)
|
|
774
|
-
return [...stale];
|
|
775
|
-
throw error;
|
|
776
|
-
}
|
|
750
|
+
const catalog = await this.getModelCatalog();
|
|
751
|
+
assertCatalogSelectable(catalog);
|
|
752
|
+
return catalog.models.map(m => m.id);
|
|
753
|
+
}
|
|
754
|
+
async getAvailableEfforts(model) {
|
|
755
|
+
return catalogEfforts(await this.getModelCatalog(), model, 'ecagent');
|
|
777
756
|
}
|
|
778
757
|
registerStream(key, stream) { this.activeStreams.set(key, stream); }
|
|
779
758
|
cleanupStream(key) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getCatalog, assertCatalogSelectable } from '../core/model/model-catalog.js';
|
|
1
2
|
/**
|
|
2
3
|
* Gemini Agent Runner
|
|
3
4
|
*
|
|
@@ -39,11 +40,6 @@ const MIME_EXT = {
|
|
|
39
40
|
'image/webp': '.webp',
|
|
40
41
|
};
|
|
41
42
|
// ── Gemini 模型列表 ──
|
|
42
|
-
const GEMINI_MODELS = [
|
|
43
|
-
'gemini-2.5-pro',
|
|
44
|
-
'gemini-2.5-flash',
|
|
45
|
-
'gemini-2.5-flash-lite',
|
|
46
|
-
];
|
|
47
43
|
const GEMINI_READONLY_TOOLS = [
|
|
48
44
|
'read_file', 'list_directory', 'glob', 'grep_search', 'google_web_search',
|
|
49
45
|
'codebase_investigator', 'cli_help', 'get_internal_docs', 'update_topic', 'complete_task',
|
|
@@ -296,7 +292,7 @@ export class GeminiRunner {
|
|
|
296
292
|
// ── ModelSwitcher ──
|
|
297
293
|
setModel(model) { this.model = model; }
|
|
298
294
|
getModel() { return this.model; }
|
|
299
|
-
listModels() { return
|
|
295
|
+
async listModels() { const catalog = await getCatalog(undefined, 'gemini'); assertCatalogSelectable(catalog); return catalog.models.map(m => m.id); }
|
|
300
296
|
// ── Effort (not applicable) ──
|
|
301
297
|
setEffort(_effort) { }
|
|
302
298
|
getEffort() { return undefined; }
|
package/dist/aun/aid/store.js
CHANGED
|
@@ -24,6 +24,7 @@ const GROUP_PULL_PAGINATION_PATCH = Symbol('evolcore.groupPullPaginationPatch');
|
|
|
24
24
|
* - CLI / ecweb 查询使用新的独立 slot,不能共享 daemon 的 token 或 seq 游标。
|
|
25
25
|
* - netcheck 使用独立消费者,不得因诊断连接影响 daemon 的入站顺序。
|
|
26
26
|
* - 群规则同步是短连接 RPC,也必须使用独立 slot,避免触碰 daemon 消费游标。
|
|
27
|
+
* - menu-client 是远程 Control AID 的短连接请求器,使用独立 slot,避免触碰 daemon 消费游标。
|
|
27
28
|
* - bench 各并发单元用 evolcore-bench-<N>,各自独立隔离键,互不干扰
|
|
28
29
|
*/
|
|
29
30
|
export const SLOT = {
|
|
@@ -32,6 +33,7 @@ export const SLOT = {
|
|
|
32
33
|
daemon: 'evolcore daemon',
|
|
33
34
|
cli: 'evolcore-cli',
|
|
34
35
|
groupRules: 'evolcore-group-rules',
|
|
36
|
+
menuClient: 'evolcore-menu-client',
|
|
35
37
|
bench: 'evolcore-bench',
|
|
36
38
|
netcheck: 'evolcore-netcheck',
|
|
37
39
|
};
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import { createShortConnection } from './rpc/client.js';
|
|
5
|
+
import { SLOT } from './aid/store.js';
|
|
6
|
+
import { isValidAid } from './aid/validation.js';
|
|
7
|
+
import { resolveRoot } from '../paths.js';
|
|
8
|
+
import { readInstalledEvolcoreVersion } from '../utils/evolcore-version.js';
|
|
9
|
+
import { buildMenuClientProtectedHeaders, parseMenuResponse, validateMenuRequest, } from '../core/command/menu-protocol.js';
|
|
10
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
11
|
+
const MAX_TIMEOUT_MS = 120_000;
|
|
12
|
+
const POLL_INTERVAL_MS = 200;
|
|
13
|
+
const PULL_LIMIT = 50;
|
|
14
|
+
const LOCK_STALE_MS = 5 * 60_000;
|
|
15
|
+
export class MenuClientError extends Error {
|
|
16
|
+
code;
|
|
17
|
+
data;
|
|
18
|
+
constructor(code, message, data) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.data = data;
|
|
22
|
+
this.name = 'MenuClientError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function callClient(conn, method, params) {
|
|
26
|
+
try {
|
|
27
|
+
return await conn.call(method, params);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error instanceof MenuClientError)
|
|
31
|
+
throw error;
|
|
32
|
+
const code = String(error?.code ?? '').toLowerCase();
|
|
33
|
+
const message = error?.message ?? String(error);
|
|
34
|
+
const timedOut = code.includes('timeout') || code.includes('timed_out') || /timed?\s*out|timeout/i.test(String(message));
|
|
35
|
+
throw new MenuClientError(timedOut ? 'TIMEOUT' : 'RPC_FAILED', String(message));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function normalizeTimeout(value) {
|
|
39
|
+
const timeout = value ?? DEFAULT_TIMEOUT_MS;
|
|
40
|
+
if (!Number.isFinite(timeout) || timeout < 1_000 || timeout > MAX_TIMEOUT_MS) {
|
|
41
|
+
throw new MenuClientError('INVALID_ARGUMENT', `timeout must be between 1s and ${MAX_TIMEOUT_MS / 1000}s`);
|
|
42
|
+
}
|
|
43
|
+
return Math.round(timeout);
|
|
44
|
+
}
|
|
45
|
+
function lockPath(aunPath, fromAid) {
|
|
46
|
+
const identity = crypto.createHash('sha256').update(fromAid).digest('hex').slice(0, 16);
|
|
47
|
+
return path.join(aunPath ?? resolveRoot(), 'data', 'instance', `menu-client-${identity}.lock`);
|
|
48
|
+
}
|
|
49
|
+
function acquireClientLock(aunPath, fromAid) {
|
|
50
|
+
const file = lockPath(aunPath, fromAid);
|
|
51
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
52
|
+
const token = `${process.pid}:${crypto.randomUUID()}`;
|
|
53
|
+
try {
|
|
54
|
+
const stat = fs.statSync(file);
|
|
55
|
+
if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
|
|
56
|
+
try {
|
|
57
|
+
fs.unlinkSync(file);
|
|
58
|
+
}
|
|
59
|
+
catch { }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch { }
|
|
63
|
+
let fd;
|
|
64
|
+
try {
|
|
65
|
+
fd = fs.openSync(file, 'wx', 0o600);
|
|
66
|
+
fs.writeFileSync(fd, token, 'utf8');
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
try {
|
|
70
|
+
if (fd !== undefined)
|
|
71
|
+
fs.closeSync(fd);
|
|
72
|
+
}
|
|
73
|
+
catch { }
|
|
74
|
+
try {
|
|
75
|
+
if (fs.existsSync(file))
|
|
76
|
+
fs.unlinkSync(file);
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
if (error?.code === 'EEXIST')
|
|
80
|
+
throw new MenuClientError('CLIENT_BUSY', 'Another ec menu-client request is using the configured slot');
|
|
81
|
+
throw new MenuClientError('CLIENT_LOCK_FAILED', `Unable to acquire menu-client lock: ${error?.message ?? String(error)}`);
|
|
82
|
+
}
|
|
83
|
+
return () => {
|
|
84
|
+
try {
|
|
85
|
+
const current = fs.readFileSync(file, 'utf8');
|
|
86
|
+
if (current === token)
|
|
87
|
+
fs.unlinkSync(file);
|
|
88
|
+
}
|
|
89
|
+
catch { }
|
|
90
|
+
if (fd !== undefined) {
|
|
91
|
+
try {
|
|
92
|
+
fs.closeSync(fd);
|
|
93
|
+
}
|
|
94
|
+
catch { }
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function normalizeRequest(value) {
|
|
99
|
+
const request = { ...value };
|
|
100
|
+
if (typeof request.id !== 'string' || !request.id.trim())
|
|
101
|
+
request.id = crypto.randomUUID();
|
|
102
|
+
const error = validateMenuRequest(request);
|
|
103
|
+
if (error)
|
|
104
|
+
throw new MenuClientError(error.code, error.message, error.data);
|
|
105
|
+
return request;
|
|
106
|
+
}
|
|
107
|
+
function responseFromMessage(message) {
|
|
108
|
+
const from = typeof message?.from === 'string'
|
|
109
|
+
? message.from.trim()
|
|
110
|
+
: typeof message?.envelope?.from === 'string' ? message.envelope.from.trim() : '';
|
|
111
|
+
let payload = message?.payload;
|
|
112
|
+
if (typeof payload === 'string') {
|
|
113
|
+
try {
|
|
114
|
+
payload = JSON.parse(payload);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload))
|
|
121
|
+
return undefined;
|
|
122
|
+
if (from && typeof payload.type === 'string' && payload.type === 'menu.response') {
|
|
123
|
+
try {
|
|
124
|
+
return parseMenuResponse(payload);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
async function pollResponse(conn, targetAid, requestId, timeoutMs, initialAfterSeq) {
|
|
133
|
+
const deadline = Date.now() + timeoutMs;
|
|
134
|
+
let afterSeq = initialAfterSeq;
|
|
135
|
+
while (Date.now() < deadline) {
|
|
136
|
+
const result = await callClient(conn, 'message.pull', { after_seq: afterSeq, limit: PULL_LIMIT });
|
|
137
|
+
const messages = Array.isArray(result?.messages) ? result.messages : [];
|
|
138
|
+
let maxSeq = afterSeq;
|
|
139
|
+
for (const message of messages) {
|
|
140
|
+
const seq = Number(message?.seq);
|
|
141
|
+
if (Number.isSafeInteger(seq) && seq > maxSeq)
|
|
142
|
+
maxSeq = seq;
|
|
143
|
+
const response = responseFromMessage(message);
|
|
144
|
+
const messageFrom = typeof message?.from === 'string'
|
|
145
|
+
? message.from.trim()
|
|
146
|
+
: typeof message?.envelope?.from === 'string' ? message.envelope.from.trim() : '';
|
|
147
|
+
if (response && messageFrom === targetAid && response.id === requestId) {
|
|
148
|
+
if (Number.isSafeInteger(maxSeq) && maxSeq > afterSeq) {
|
|
149
|
+
try {
|
|
150
|
+
await callClient(conn, 'message.ack', { seq: maxSeq });
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
throw new MenuClientError('ACK_FAILED', error?.message ?? String(error));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return response;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (Number.isSafeInteger(maxSeq) && maxSeq > afterSeq) {
|
|
160
|
+
afterSeq = maxSeq;
|
|
161
|
+
try {
|
|
162
|
+
await callClient(conn, 'message.ack', { seq: afterSeq });
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
throw new MenuClientError('ACK_FAILED', error?.message ?? String(error));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const waitMs = Math.min(POLL_INTERVAL_MS, Math.max(1, deadline - Date.now()));
|
|
169
|
+
await new Promise(resolve => setTimeout(resolve, waitMs));
|
|
170
|
+
}
|
|
171
|
+
throw new MenuClientError('TIMEOUT', `Timed out waiting for Menu response ${requestId}`);
|
|
172
|
+
}
|
|
173
|
+
async function drainBaseline(conn) {
|
|
174
|
+
let afterSeq = 0;
|
|
175
|
+
for (let page = 0; page < 200; page += 1) {
|
|
176
|
+
const result = await callClient(conn, 'message.pull', { after_seq: afterSeq, limit: PULL_LIMIT });
|
|
177
|
+
const messages = Array.isArray(result?.messages) ? result.messages : [];
|
|
178
|
+
let maxSeq = Math.max(afterSeq, Number(result?.server_ack_seq) || 0);
|
|
179
|
+
for (const message of messages) {
|
|
180
|
+
const seq = Number(message?.seq);
|
|
181
|
+
if (Number.isSafeInteger(seq) && seq > maxSeq)
|
|
182
|
+
maxSeq = seq;
|
|
183
|
+
}
|
|
184
|
+
if (maxSeq > afterSeq) {
|
|
185
|
+
try {
|
|
186
|
+
await callClient(conn, 'message.ack', { seq: maxSeq });
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
throw new MenuClientError('ACK_FAILED', error?.message ?? String(error));
|
|
190
|
+
}
|
|
191
|
+
afterSeq = maxSeq;
|
|
192
|
+
}
|
|
193
|
+
if (messages.length < PULL_LIMIT)
|
|
194
|
+
return afterSeq;
|
|
195
|
+
}
|
|
196
|
+
throw new MenuClientError('BASELINE_TOO_LARGE', 'menu-client backlog exceeds the bounded baseline drain');
|
|
197
|
+
}
|
|
198
|
+
async function sendOnce(options, request, timeoutMs) {
|
|
199
|
+
const slotId = options.slotId ?? SLOT.menuClient;
|
|
200
|
+
const encrypt = options.encrypt ?? true;
|
|
201
|
+
if (!encrypt) {
|
|
202
|
+
throw new MenuClientError('INVALID_CLIENT', 'ec-menu-client requires E2EE protected headers; remove --no-encrypt');
|
|
203
|
+
}
|
|
204
|
+
const headers = buildMenuClientProtectedHeaders({
|
|
205
|
+
clientName: 'ec-menu-client',
|
|
206
|
+
clientVersion: readInstalledEvolcoreVersion(),
|
|
207
|
+
remoteAccessToken: options.remoteAccessToken ?? options.ecwebToken,
|
|
208
|
+
});
|
|
209
|
+
const conn = await createShortConnection(options.fromAid, {
|
|
210
|
+
aunPath: options.aunPath,
|
|
211
|
+
slotId,
|
|
212
|
+
shortTtlMs: timeoutMs * (request.type === 'menu.query' || request.type === 'menu.options' ? 2 : 1) + 10_000,
|
|
213
|
+
});
|
|
214
|
+
try {
|
|
215
|
+
let baselineSeq = await drainBaseline(conn);
|
|
216
|
+
const attempts = request.type === 'menu.query' || request.type === 'menu.options' ? 2 : 1;
|
|
217
|
+
let lastError;
|
|
218
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
219
|
+
const sent = await callClient(conn, 'message.send', {
|
|
220
|
+
to: options.targetAid,
|
|
221
|
+
payload: request,
|
|
222
|
+
encrypt,
|
|
223
|
+
...(encrypt ? { protected_headers: headers } : {}),
|
|
224
|
+
});
|
|
225
|
+
const messageId = sent?.message_id ?? sent?.message?.message_id;
|
|
226
|
+
if (typeof messageId !== 'string' || !messageId.trim()) {
|
|
227
|
+
throw new MenuClientError('SEND_FAILED', 'message.send returned no message_id');
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
return await pollResponse(conn, options.targetAid, request.id, timeoutMs, baselineSeq);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
lastError = error;
|
|
234
|
+
if (attempt + 1 < attempts && error instanceof MenuClientError && error.code === 'TIMEOUT') {
|
|
235
|
+
baselineSeq = await drainBaseline(conn);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
throw lastError ?? new MenuClientError('INTERNAL_ERROR', 'Menu request failed');
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
await conn.close();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
export async function executeMenuClientRequest(options) {
|
|
248
|
+
if (!isValidAid(options.fromAid) || !isValidAid(options.targetAid)) {
|
|
249
|
+
throw new MenuClientError('INVALID_ARGUMENT', 'fromAid and targetAid must be valid AIDs');
|
|
250
|
+
}
|
|
251
|
+
if (options.slotId !== undefined && (typeof options.slotId !== 'string' || !options.slotId.trim())) {
|
|
252
|
+
throw new MenuClientError('INVALID_ARGUMENT', 'slotId must be a non-empty string');
|
|
253
|
+
}
|
|
254
|
+
const slotId = options.slotId ?? SLOT.menuClient;
|
|
255
|
+
if (slotId.length > 128 || !/^[A-Za-z0-9._-][A-Za-z0-9._/ :\-]*$/.test(slotId)) {
|
|
256
|
+
throw new MenuClientError('INVALID_ARGUMENT', 'slotId contains unsupported characters');
|
|
257
|
+
}
|
|
258
|
+
if (options.aunPath !== undefined && (typeof options.aunPath !== 'string' || !options.aunPath.trim())) {
|
|
259
|
+
throw new MenuClientError('INVALID_ARGUMENT', 'aunPath must be a non-empty path');
|
|
260
|
+
}
|
|
261
|
+
const request = normalizeRequest(options.request);
|
|
262
|
+
const timeoutMs = normalizeTimeout(options.timeoutMs);
|
|
263
|
+
const release = options.slotId && options.slotId !== SLOT.menuClient
|
|
264
|
+
? () => { }
|
|
265
|
+
: acquireClientLock(options.aunPath, options.fromAid);
|
|
266
|
+
try {
|
|
267
|
+
return await sendOnce(options, request, timeoutMs);
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
release();
|
|
271
|
+
}
|
|
272
|
+
}
|
package/dist/aun/msg/group.js
CHANGED
|
@@ -297,8 +297,16 @@ export async function groupAck(args) {
|
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
299
|
export async function groupCreate(args) {
|
|
300
|
-
|
|
301
|
-
|
|
300
|
+
return groupCreateWithDependencies(args, { getAidStore, loadClient });
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Injectable boundary for the lifecycle call. Keeping the store/client
|
|
304
|
+
* construction outside the RPC makes the aidStore contract regression-testable
|
|
305
|
+
* without opening a real gateway connection.
|
|
306
|
+
*/
|
|
307
|
+
export async function groupCreateWithDependencies(args, dependencies) {
|
|
308
|
+
const store = await dependencies.getAidStore({ slotId: args.slotId ?? SLOT.cli, aunPath: args.aunPath });
|
|
309
|
+
const conn = await dependencies.loadClient(store, args.from);
|
|
302
310
|
try {
|
|
303
311
|
await conn.connect({ connection_kind: 'short', short_ttl_ms: 30000, auto_reconnect: false });
|
|
304
312
|
const params = { name: args.name };
|
|
@@ -313,7 +321,7 @@ export async function groupCreate(args) {
|
|
|
313
321
|
params.description = args.description;
|
|
314
322
|
if (args.joinMode)
|
|
315
323
|
params.join_mode = args.joinMode;
|
|
316
|
-
const result = await conn.createGroup(params);
|
|
324
|
+
const result = await conn.createGroup(params, { aidStore: store });
|
|
317
325
|
return { ok: true, group: result?.group ?? result };
|
|
318
326
|
}
|
|
319
327
|
catch (e) {
|
|
@@ -510,7 +518,16 @@ export async function groupInvite(args) {
|
|
|
510
518
|
try {
|
|
511
519
|
for (const memberAid of args.members) {
|
|
512
520
|
try {
|
|
513
|
-
|
|
521
|
+
// fastaun 0.5.12 introduced targeted invitations. The current gateway
|
|
522
|
+
// supports this lifecycle directly; do not bypass invitee consent with
|
|
523
|
+
// the immediate-membership group.add_member operation.
|
|
524
|
+
const inviteParams = {
|
|
525
|
+
group_id: args.groupId,
|
|
526
|
+
aid: memberAid,
|
|
527
|
+
};
|
|
528
|
+
if (args.memberType !== undefined)
|
|
529
|
+
inviteParams.member_type = args.memberType;
|
|
530
|
+
await conn.call('group.invite_member', inviteParams);
|
|
514
531
|
added.push(memberAid);
|
|
515
532
|
}
|
|
516
533
|
catch (e) {
|
|
@@ -523,6 +540,50 @@ export async function groupInvite(args) {
|
|
|
523
540
|
await conn.close();
|
|
524
541
|
}
|
|
525
542
|
}
|
|
543
|
+
/** List pending targeted invitations for the authenticated AID. */
|
|
544
|
+
export async function groupInvites(args) {
|
|
545
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
546
|
+
try {
|
|
547
|
+
const params = {};
|
|
548
|
+
if (args.page !== undefined)
|
|
549
|
+
params.page = args.page;
|
|
550
|
+
if (args.size !== undefined)
|
|
551
|
+
params.size = args.size;
|
|
552
|
+
const result = await conn.call('group.list_my_invites', params);
|
|
553
|
+
const invites = Array.isArray(result) ? result : Array.isArray(result?.invites)
|
|
554
|
+
? result.invites
|
|
555
|
+
: Array.isArray(result?.items) ? result.items : [];
|
|
556
|
+
return {
|
|
557
|
+
ok: true,
|
|
558
|
+
invites,
|
|
559
|
+
page: typeof result?.page === 'number' ? result.page : args.page,
|
|
560
|
+
size: typeof result?.size === 'number' ? result.size : args.size,
|
|
561
|
+
nextCursor: typeof result?.next_cursor === 'string' ? result.next_cursor : null,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
catch (e) {
|
|
565
|
+
return formatRpcError(e);
|
|
566
|
+
}
|
|
567
|
+
finally {
|
|
568
|
+
await conn.close();
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
async function groupInviteAction(args, method) {
|
|
572
|
+
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
573
|
+
try {
|
|
574
|
+
const result = await conn.call(method, { invite_id: args.inviteId });
|
|
575
|
+
return { ok: true, group_id: result?.group_id ?? '', data: result };
|
|
576
|
+
}
|
|
577
|
+
catch (e) {
|
|
578
|
+
return formatRpcError(e);
|
|
579
|
+
}
|
|
580
|
+
finally {
|
|
581
|
+
await conn.close();
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
export const groupAcceptInvite = (args) => groupInviteAction(args, 'group.accept_invite');
|
|
585
|
+
export const groupRejectInvite = (args) => groupInviteAction(args, 'group.reject_invite');
|
|
586
|
+
export const groupRevokeInvite = (args) => groupInviteAction(args, 'group.revoke_invite');
|
|
526
587
|
export async function groupKick(args) {
|
|
527
588
|
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
528
589
|
try {
|
package/dist/aun/msg/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { msgSend, msgPull, msgAck, msgRecall, msgOnline, } from './p2p.js';
|
|
2
2
|
export { msgHistory, MAX_MSG_HISTORY_LIMIT } from './history.js';
|
|
3
|
-
export { groupSend, groupPull, groupAck, groupCreate, groupInfo, groupList, groupUpdate, groupDissolve, groupSuspend, groupResume, groupJoin, groupLeave, groupInvite, groupKick, groupMembers, groupOnline, groupSetRole, groupTransferOwner, groupBan, groupUnban, groupBanlist, groupRules, groupUpdateRules, groupRulesFileGet, groupRulesFileSet, groupRulesFilePublish, } from './group.js';
|
|
3
|
+
export { groupSend, groupPull, groupAck, groupCreate, groupInfo, groupList, groupUpdate, groupDissolve, groupSuspend, groupResume, groupJoin, groupLeave, groupInvite, groupInvites, groupAcceptInvite, groupRejectInvite, groupRevokeInvite, groupKick, groupMembers, groupOnline, groupSetRole, groupTransferOwner, groupBan, groupUnban, groupBanlist, groupRules, groupUpdateRules, groupRulesFileGet, groupRulesFileSet, groupRulesFilePublish, } from './group.js';
|
|
4
4
|
export { uploadFileAndBuildPayload, inferPayloadType, isValidPayloadType } from './upload.js';
|
|
@@ -7,7 +7,7 @@ import { isExplicitGroupId } from '../group-identity.js';
|
|
|
7
7
|
import { isValidAid } from '../aid/validation.js';
|
|
8
8
|
import { tryParseChannelKey } from '../../core/channel-loader.js';
|
|
9
9
|
import { downloadGroupFsBytes } from '../group-fs-download.js';
|
|
10
|
-
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';
|
|
10
|
+
import { groupBan, groupBanlist, groupCreate, groupDissolve, groupInfo, groupInvite, groupJoin, groupKick, groupLeave, groupList, groupMembers, groupOnline, groupPull, groupAck, groupAcceptInvite, groupInvites, groupRejectInvite, groupRevokeInvite, groupResume, groupRules, groupRulesFileGet, groupRulesFilePublish, groupRulesFileSet, groupSetRole, groupSuspend, groupTransferOwner, groupUnban, groupUpdate, groupUpdateRules, } from './group.js';
|
|
11
11
|
/** Rules uploaded by a managed task are carried as bounded UTF-8 content. */
|
|
12
12
|
export const MAX_MANAGED_GROUP_RULES_BYTES = 4 * 1024;
|
|
13
13
|
const RELATION_GROUP_READ_KINDS = new Set([
|
|
@@ -32,7 +32,7 @@ export class ManagedGroupOperationError extends Error {
|
|
|
32
32
|
}
|
|
33
33
|
const GROUP_VALUE_FLAGS = new Set([
|
|
34
34
|
'--format', '--after-seq', '--limit', '--visibility', '--description', '--join-mode', '--group-id',
|
|
35
|
-
'--name', '--page', '--size', '--mode', '--question', '--max-pending', '--message', '--answer', '--duration',
|
|
35
|
+
'--name', '--page', '--size', '--mode', '--question', '--max-pending', '--message', '--answer', '--duration', '--member-type',
|
|
36
36
|
]);
|
|
37
37
|
function failGroup(code, reason) {
|
|
38
38
|
return { ok: false, code, reason };
|
|
@@ -125,6 +125,37 @@ export function resolveManagedGroupOperation(argv) {
|
|
|
125
125
|
if (!('positionals' in parsed))
|
|
126
126
|
return parsed;
|
|
127
127
|
const { positionals, values } = parsed;
|
|
128
|
+
if (sub === 'invites') {
|
|
129
|
+
const flagsError = onlyFlags(values, ['--page', '--size']);
|
|
130
|
+
if (flagsError)
|
|
131
|
+
return flagsError;
|
|
132
|
+
if (positionals.length !== 1)
|
|
133
|
+
return failGroup('INVALID_ARGUMENT', 'invites accepts exactly <from>');
|
|
134
|
+
const from = requiredAid(positionals[0], 'from AID');
|
|
135
|
+
if (typeof from !== 'string')
|
|
136
|
+
return from;
|
|
137
|
+
const page = numberFlag(values, '--page');
|
|
138
|
+
const size = numberFlag(values, '--size');
|
|
139
|
+
if (typeof page !== 'number' && page !== undefined)
|
|
140
|
+
return page;
|
|
141
|
+
if (typeof size !== 'number' && size !== undefined)
|
|
142
|
+
return size;
|
|
143
|
+
return command(sub, 'ec.group.invites', ['group', sub, from], from, { page, size });
|
|
144
|
+
}
|
|
145
|
+
if (sub === 'accept-invite' || sub === 'reject-invite' || sub === 'revoke-invite') {
|
|
146
|
+
const flagsError = onlyFlags(values, []);
|
|
147
|
+
if (flagsError)
|
|
148
|
+
return flagsError;
|
|
149
|
+
if (positionals.length !== 2)
|
|
150
|
+
return failGroup('INVALID_ARGUMENT', `${sub} accepts exactly <from> <invite-id>`);
|
|
151
|
+
const from = requiredAid(positionals[0], 'from AID');
|
|
152
|
+
if (typeof from !== 'string')
|
|
153
|
+
return from;
|
|
154
|
+
const inviteId = String(positionals[1]).trim();
|
|
155
|
+
if (!inviteId)
|
|
156
|
+
return failGroup('INVALID_ARGUMENT', 'missing invite-id');
|
|
157
|
+
return command(sub, `ec.group.${sub}`, ['group', sub, from, inviteId], from, { inviteId });
|
|
158
|
+
}
|
|
128
159
|
if (sub === 'list') {
|
|
129
160
|
const flagsError = onlyFlags(values, ['--size']);
|
|
130
161
|
if (flagsError)
|
|
@@ -170,7 +201,7 @@ export function resolveManagedGroupOperation(argv) {
|
|
|
170
201
|
if (simple.has(sub)) {
|
|
171
202
|
const allowedFlags = {
|
|
172
203
|
pull: ['--after-seq', '--limit'], ack: [], info: [], update: ['--name', '--description'], dissolve: [],
|
|
173
|
-
suspend: [], resume: [], join: ['--message', '--answer'], leave: [], invite: [], kick: [],
|
|
204
|
+
suspend: [], resume: [], join: ['--message', '--answer'], leave: [], invite: ['--member-type'], kick: [],
|
|
174
205
|
members: ['--page', '--size'], online: [], role: [], owner: [], ban: ['--duration'], unban: [],
|
|
175
206
|
};
|
|
176
207
|
const flagsError = onlyFlags(values, allowedFlags[sub] ?? []);
|
|
@@ -216,7 +247,11 @@ export function resolveManagedGroupOperation(argv) {
|
|
|
216
247
|
if (typeof checked !== 'string')
|
|
217
248
|
return checked;
|
|
218
249
|
}
|
|
250
|
+
const memberType = values.get('--member-type');
|
|
251
|
+
if (memberType !== undefined && memberType !== 'human' && memberType !== 'ai')
|
|
252
|
+
return failGroup('INVALID_ARGUMENT', 'invalid --member-type');
|
|
219
253
|
extra.members = members;
|
|
254
|
+
extra.memberType = memberType;
|
|
220
255
|
}
|
|
221
256
|
else if (sub === 'kick' || sub === 'unban') {
|
|
222
257
|
if (positionals.length !== 3)
|
|
@@ -357,7 +392,19 @@ export async function executeManagedGroupOperation(command, rulesContent) {
|
|
|
357
392
|
result = await groupLeave({ from: command.from, groupId: command.groupId, ...opts });
|
|
358
393
|
break;
|
|
359
394
|
case 'invite':
|
|
360
|
-
result = await groupInvite({ from: command.from, groupId: command.groupId, members: command.members, ...opts });
|
|
395
|
+
result = await groupInvite({ from: command.from, groupId: command.groupId, members: command.members, memberType: command.memberType, ...opts });
|
|
396
|
+
break;
|
|
397
|
+
case 'invites':
|
|
398
|
+
result = await groupInvites({ from: command.from, page: command.page, size: command.size, ...opts });
|
|
399
|
+
break;
|
|
400
|
+
case 'accept-invite':
|
|
401
|
+
result = await groupAcceptInvite({ from: command.from, inviteId: command.inviteId, ...opts });
|
|
402
|
+
break;
|
|
403
|
+
case 'reject-invite':
|
|
404
|
+
result = await groupRejectInvite({ from: command.from, inviteId: command.inviteId, ...opts });
|
|
405
|
+
break;
|
|
406
|
+
case 'revoke-invite':
|
|
407
|
+
result = await groupRevokeInvite({ from: command.from, inviteId: command.inviteId, ...opts });
|
|
361
408
|
break;
|
|
362
409
|
case 'kick':
|
|
363
410
|
result = await groupKick({ from: command.from, groupId: command.groupId, memberAid: command.memberAid, ...opts });
|
package/dist/aun/outbox.js
CHANGED
|
@@ -215,6 +215,13 @@ export function findByDedupeKey(aid, dedupeKey, opts = {}, queue = 'default') {
|
|
|
215
215
|
: load(aid, queue);
|
|
216
216
|
return entries.find(entry => entry.dedupeKey === dedupeKey);
|
|
217
217
|
}
|
|
218
|
+
/** Find all durable entries belonging to an operation family. */
|
|
219
|
+
export function findByDedupePrefix(aid, dedupePrefix, opts = {}, queue = 'default') {
|
|
220
|
+
const entries = opts.includeTerminal
|
|
221
|
+
? readEntries(aid, queue).filter(entry => !isExpired(entry))
|
|
222
|
+
: load(aid, queue);
|
|
223
|
+
return entries.filter(entry => typeof entry.dedupeKey === 'string' && entry.dedupeKey.startsWith(dedupePrefix));
|
|
224
|
+
}
|
|
218
225
|
export function cleanup(aid, queue = 'default') {
|
|
219
226
|
const all = readEntries(aid, queue);
|
|
220
227
|
let expired = 0;
|
package/dist/aun/rpc/client.js
CHANGED
|
@@ -3,11 +3,18 @@ export async function createShortConnection(aid, opts) {
|
|
|
3
3
|
const store = await getAidStore({ slotId: opts?.slotId ?? SLOT.cli, aunPath: opts?.aunPath });
|
|
4
4
|
try {
|
|
5
5
|
const client = await loadClient(store, aid);
|
|
6
|
-
await client.connect({ connection_kind: 'short', short_ttl_ms: 30000, auto_reconnect: false });
|
|
6
|
+
await client.connect({ connection_kind: 'short', short_ttl_ms: opts?.shortTtlMs ?? 30000, auto_reconnect: false });
|
|
7
7
|
return {
|
|
8
8
|
async call(method, params) {
|
|
9
9
|
return client.call(method, params);
|
|
10
10
|
},
|
|
11
|
+
group: {
|
|
12
|
+
inviteMember: (params) => client.group.inviteMember(params),
|
|
13
|
+
listMyInvites: (params) => client.group.listMyInvites(params),
|
|
14
|
+
acceptInvite: (params) => client.group.acceptInvite(params),
|
|
15
|
+
rejectInvite: (params) => client.group.rejectInvite(params),
|
|
16
|
+
revokeInvite: (params) => client.group.revokeInvite(params),
|
|
17
|
+
},
|
|
11
18
|
async close() {
|
|
12
19
|
try {
|
|
13
20
|
await client.close();
|