evolcore 0.0.35 → 0.0.36
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 +31 -0
- package/README.md +6 -0
- package/bin/install-codex-managed-hooks.mjs +20 -3
- package/dist/agents/baseagent-availability.js +1 -1
- package/dist/agents/baseagent.js +88 -0
- package/dist/agents/claude-runner.js +59 -12
- package/dist/agents/codex/app-server-client.js +18 -1
- package/dist/agents/codex-runner.js +262 -21
- package/dist/agents/ecagent-runner.js +47 -11
- package/dist/agents/gemini-runner.js +36 -12
- package/dist/aun/aid/client.js +4 -3
- package/dist/aun/aid/control-aid.js +270 -8
- package/dist/aun/aid/identity.js +6 -3
- package/dist/aun/aid/ownership-operation.js +58 -0
- package/dist/aun/aid/store.js +1 -0
- package/dist/aun/group/fs-download.js +67 -2
- package/dist/aun/group/history.js +20 -5
- package/dist/aun/group/operations.js +8 -2
- package/dist/aun/menu/client.js +39 -2
- package/dist/aun/menu/transport.js +1 -1
- package/dist/aun/msg/history.js +43 -4
- package/dist/aun/msg/managed-operation.js +30 -53
- package/dist/aun/msg/p2p.js +51 -15
- package/dist/aun/outbox.js +4 -0
- package/dist/channels/aun.js +260 -73
- package/dist/channels/daemon.js +6 -4
- package/dist/channels/feishu.js +3 -0
- package/dist/cli/agent-command.js +161 -223
- package/dist/cli/agent-create.js +190 -223
- package/dist/cli/agent.js +3 -1
- package/dist/cli/aid-bind-command.js +53 -0
- package/dist/cli/aun-commands.js +384 -91
- package/dist/cli/bench.js +1 -1
- package/dist/cli/cli-argv.js +74 -0
- package/dist/cli/daemon-commands.js +105 -74
- package/dist/cli/diag.js +1 -1
- package/dist/cli/fs-command.js +18 -19
- package/dist/cli/getmeta-command.js +159 -6
- package/dist/cli/index.js +6 -12
- package/dist/cli/init-channel.js +8 -55
- package/dist/cli/init.js +125 -218
- package/dist/cli/queue-command.js +11 -8
- package/dist/cli/restart-monitor.js +134 -48
- package/dist/cli/session-command.js +1 -1
- package/dist/cli/stats.js +7 -5
- package/dist/cli/thread-command.js +103 -30
- package/dist/cli/trigger-command.js +1 -1
- package/dist/cli/watch-msg-diag.js +1 -0
- package/dist/config/access-policy.js +2 -2
- package/dist/config/boot-log.js +14 -11
- package/dist/config/config-field-policy.js +9 -1
- package/dist/config/config-manager.js +176 -90
- package/dist/config/config-operation-service.js +7 -14
- package/dist/config/contact-book-store.js +142 -26
- package/dist/config/contact-book.js +4 -2
- package/dist/config/lifecycle.js +1 -1
- package/dist/config/owner-config.js +96 -0
- package/dist/config/peer-role-resolver.js +12 -18
- package/dist/config/providers-config.js +382 -0
- package/dist/config/providers-import.js +74 -0
- package/dist/config/providers-management.js +328 -0
- package/dist/config/providers.js +22 -0
- package/dist/config/resolved-config-op.js +17 -14
- package/dist/config/snapshot.js +28 -16
- package/dist/config-store.js +18 -14
- package/dist/core/agent-ipc-registration.js +21 -63
- package/dist/core/agent-runtime.js +16 -5
- package/dist/core/auth/agent-delegation.js +199 -25
- package/dist/core/auth/auth-gateway.js +44 -2
- package/dist/core/auth/authorization-denial.js +1 -1
- package/dist/core/auth/debug-context.js +2 -1
- package/dist/core/auth/delegation-authorization.js +2 -2
- package/dist/core/auth/delegation-diagnostics.js +4 -1
- package/dist/core/auth/operation-authorizer.js +10 -1
- package/dist/core/auth/operation-catalog.js +19 -1
- package/dist/core/bootstrap-service.js +43 -20
- package/dist/core/channel-loader.js +0 -1
- package/dist/core/channel-runtime.js +30 -4
- package/dist/core/command/agent-control.js +196 -37
- package/dist/core/command/cli-intent-parser.js +60 -4
- package/dist/core/command/command-handler.js +212 -60
- package/dist/core/command/connect-menu.js +1 -0
- package/dist/core/command/diag-service.js +5 -4
- package/dist/core/command/file-search.js +368 -0
- package/dist/core/command/menu-catalog.js +2 -1
- package/dist/core/command/menu-handler.js +292 -107
- package/dist/core/command/menu-protocol.js +23 -0
- package/dist/core/command/role-menu.js +7 -19
- package/dist/core/command/skill-slash.js +130 -25
- package/dist/core/command/slash-handler.js +101 -53
- package/dist/core/command/topic-slash.js +16 -5
- package/dist/core/daemon-bootstrap.js +7 -1
- package/dist/core/daemon-control.js +19 -1
- package/dist/core/daemon-lifecycle.js +2 -1
- package/dist/core/event-catalog.js +2 -0
- package/dist/core/evolagent-registry.js +13 -2
- package/dist/core/evolagent.js +3 -6
- package/dist/core/handoff/ipc-registration.js +2 -2
- package/dist/core/handoff/runtime.js +40 -6
- package/dist/core/handoff/store.js +1 -0
- package/dist/core/inference/text-inference.js +95 -6
- package/dist/core/ipc-registration.js +43 -24
- package/dist/core/memory/consolidation-context.js +4 -1
- package/dist/core/memory/consolidation.js +25 -14
- package/dist/core/message/aun-ipc-sender.js +296 -88
- package/dist/core/message/aun-request-registry.js +226 -0
- package/dist/core/message/create-status.js +33 -4
- package/dist/core/message/message-bridge.js +62 -13
- package/dist/core/message/message-log.js +0 -1
- package/dist/core/message/message-queue.js +4 -1
- package/dist/core/message/message-utils.js +8 -1
- package/dist/core/message/peer-mode.js +17 -9
- package/dist/core/message/response-engine.js +266 -124
- package/dist/core/message/retry-scheduler.js +11 -8
- package/dist/core/model/catalog-service.js +60 -18
- package/dist/core/model/model-catalog.js +112 -9
- package/dist/core/model/model-fallback.js +94 -26
- package/dist/core/ownership/owner-resolver.js +687 -0
- package/dist/core/permission/approval-gateway.js +196 -62
- package/dist/core/permission/ec-command-parser.js +138 -25
- package/dist/core/permission/filesystem-approval-scope.js +141 -0
- package/dist/core/permission/protected-paths.js +14 -1
- package/dist/core/permission/sandbox-runtime.js +171 -18
- package/dist/core/permission/tool-policy.js +13 -2
- package/dist/core/role/runtime-policy.js +3 -4
- package/dist/core/session/session-idle-monitor.js +10 -8
- package/dist/core/session/session-manager.js +72 -45
- package/dist/core/session/session-mapper.js +18 -7
- package/dist/core/session/session-renew.js +5 -4
- package/dist/core/session/session-title.js +2 -1
- package/dist/core/session/session-turn-coordinator.js +4 -2
- package/dist/core/skill/index-generator.js +2 -16
- package/dist/core/task/task-recovery-service.js +15 -2
- package/dist/core/thread/command-parser.js +28 -13
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +11 -8
- package/dist/core/thread/thread-operation.js +79 -44
- package/dist/core/thread/thread-route.js +54 -6
- package/dist/core/thread/thread-service.js +236 -45
- package/dist/core/thread/topic-id.js +248 -0
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/eck/message-renderer.js +12 -2
- package/dist/index.js +50 -27
- package/dist/ipc.js +59 -6
- package/dist/paths.js +1 -0
- package/dist/product.js +1 -0
- package/dist/response-system/engines/v1/proactive-flow.js +10 -3
- package/dist/stats/db.js +8 -0
- package/dist/stats/writer.js +26 -4
- package/dist/trigger/authorization.js +48 -3
- package/dist/trigger/feedback.js +28 -10
- package/dist/trigger/ipc-service.js +86 -10
- package/dist/trigger/local-files.js +55 -23
- package/dist/trigger/scheduler.js +57 -11
- package/dist/trigger/system-definitions.js +1 -0
- package/dist/trigger/validation.js +44 -15
- package/dist/utils/aid-bind.js +17 -297
- package/dist/utils/avatar-upload.js +16 -4
- package/dist/utils/cross-platform.js +109 -34
- package/dist/utils/ecweb-supervisor.js +1 -1
- package/dist/utils/ecweb-utils.js +8 -1
- package/dist/utils/error-utils.js +44 -6
- package/dist/utils/instance-registry.js +12 -2
- package/dist/utils/npm-ops.js +102 -27
- package/dist/utils/package-version-info.js +72 -27
- package/dist/utils/restart-safety.js +50 -0
- package/dist/utils/upgrade-jobs.js +304 -0
- package/dist/utils/windows-autostart.js +17 -10
- package/kits/docs/INDEX.md +5 -0
- package/kits/docs/aun/ABILITY_CALLING.md +239 -0
- package/kits/docs/evolcore/INDEX.md +14 -3
- package/kits/docs/evolcore/agent-create.md +55 -141
- package/kits/docs/evolcore/agent.md +38 -16
- package/kits/docs/evolcore/aid.md +34 -0
- package/kits/docs/evolcore/config.md +20 -1
- package/kits/docs/evolcore/event.md +2 -2
- package/kits/docs/evolcore/fs.md +18 -2
- package/kits/docs/evolcore/group-collaboration.md +13 -246
- package/kits/docs/evolcore/group.md +2 -1
- package/kits/docs/evolcore/memory-consolidation-batches.md +1 -1
- package/kits/docs/evolcore/menu-protocol.md +65 -1
- package/kits/docs/evolcore/msg.md +64 -2
- package/kits/docs/evolcore/skill.md +86 -0
- package/kits/docs/evolcore/stats.md +6 -0
- package/kits/docs/evolcore/task.md +2 -0
- package/kits/docs/evolcore/thread-chain.md +145 -51
- package/kits/docs/evolcore/trigger.md +48 -3
- package/kits/eck_manifest.json +21 -0
- package/kits/eck_message_manifest.json +4 -4
- package/kits/migrations/migrate-role-config-v5.mjs +20 -7
- package/kits/rules/01-overview.md +2 -1
- package/kits/rules/03-identity.md +3 -1
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/07-memory-and-thread.md +1 -1
- package/kits/schemas/_meta.json +16 -1
- package/kits/schemas/agent-config.schema.13.json +44 -2
- package/kits/schemas/contact-book.schema.3.json +5 -0
- package/kits/schemas/daemon.schema.6.json +3 -1
- package/kits/schemas/daemon.schema.8.json +329 -0
- package/kits/schemas/defaults.schema.5.json +39 -3
- package/kits/schemas/providers.schema.1.json +76 -0
- package/kits/schemas/relation-config.schema.9.json +5 -1
- package/kits/skills/.switches.json +28 -0
- package/kits/skills/aid-ability/SKILL.md +214 -0
- package/kits/skills/ec-agent-create/SKILL.md +162 -0
- package/kits/skills/ec-group/SKILL.md +2 -1
- package/kits/skills/ec-group-collaboration/SKILL.md +228 -0
- package/kits/skills/ec-identity/SKILL.md +143 -0
- package/kits/skills/ec-skill/scripts/cli.mjs +36 -3
- package/kits/skills/ec-skill/scripts/skill.mjs +202 -15
- package/kits/skills/ec-trigger/SKILL.md +167 -0
- package/kits/skills/ec-trigger/references/examples.md +153 -0
- package/kits/skills/thread-ops/SKILL.md +108 -420
- package/kits/templates/message-fragments/thread-context.md +1 -2
- package/kits/templates/message-fragments/trigger-thread-check.md +1 -1
- package/kits/templates/roles/member.json +1 -1
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +1 -0
- package/kits/templates/system-fragments/channel.md +2 -1
- package/kits/templates/system-fragments/commands.md +5 -4
- package/kits/templates/system-fragments/identity.md +2 -0
- package/kits/templates/system-fragments/session.md +1 -1
- package/kits/templates/system-fragments/venue.md +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
import { resolvePaths } from '../../paths.js';
|
|
5
|
+
import { appendJsonlDurable } from '../session/session-fs-store.js';
|
|
6
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
|
|
7
|
+
const MAX_TIMER_MS = 2_147_483_647;
|
|
8
|
+
/** AUN message.send accepts volatile TTL values from 1 through 3600 seconds. */
|
|
9
|
+
export const MAX_AUN_VOLATILE_TTL_SECONDS = 3_600;
|
|
10
|
+
export function requestTtlSeconds(timeoutMs) {
|
|
11
|
+
const value = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
12
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
13
|
+
throw new Error('request timeout must be a positive number');
|
|
14
|
+
}
|
|
15
|
+
const ttl = Math.ceil(value / 1_000);
|
|
16
|
+
if (ttl > MAX_AUN_VOLATILE_TTL_SECONDS) {
|
|
17
|
+
throw new Error(`request timeout cannot exceed ${MAX_AUN_VOLATILE_TTL_SECONDS}s for volatile AUN messages`);
|
|
18
|
+
}
|
|
19
|
+
return Math.max(1, ttl);
|
|
20
|
+
}
|
|
21
|
+
function requestFile(selfAid) {
|
|
22
|
+
return path.join(resolvePaths().dataDir, 'aun-requests', `${encodeURIComponent(selfAid)}.jsonl`);
|
|
23
|
+
}
|
|
24
|
+
function randomRequestId() {
|
|
25
|
+
return `req_${Date.now().toString(36)}_${crypto.randomBytes(8).toString('hex')}`;
|
|
26
|
+
}
|
|
27
|
+
function stringValue(value) {
|
|
28
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
29
|
+
}
|
|
30
|
+
function normalizeTimeoutMs(value) {
|
|
31
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
|
|
32
|
+
return DEFAULT_REQUEST_TIMEOUT_MS;
|
|
33
|
+
return Math.min(MAX_TIMER_MS, Math.max(1, value));
|
|
34
|
+
}
|
|
35
|
+
export class AunRequestRegistry {
|
|
36
|
+
records = new Map();
|
|
37
|
+
waiters = new Map();
|
|
38
|
+
loaded = new Set();
|
|
39
|
+
key(selfAid, requestId) {
|
|
40
|
+
return `${selfAid}\0${requestId}`;
|
|
41
|
+
}
|
|
42
|
+
ensureLoaded(selfAid) {
|
|
43
|
+
if (this.loaded.has(selfAid))
|
|
44
|
+
return;
|
|
45
|
+
this.loaded.add(selfAid);
|
|
46
|
+
const file = requestFile(selfAid);
|
|
47
|
+
try {
|
|
48
|
+
const lines = fs.readFileSync(file, 'utf8').split(/\r?\n/);
|
|
49
|
+
for (const line of lines) {
|
|
50
|
+
if (!line.trim())
|
|
51
|
+
continue;
|
|
52
|
+
try {
|
|
53
|
+
const event = JSON.parse(line);
|
|
54
|
+
if (event?.event !== 'upsert' || !event.record?.requestId)
|
|
55
|
+
continue;
|
|
56
|
+
const recovered = event.record.state === 'created' || event.record.state === 'sending'
|
|
57
|
+
? { ...event.record, state: 'unknown', updatedAt: Date.now() }
|
|
58
|
+
: event.record;
|
|
59
|
+
this.records.set(this.key(selfAid, recovered.requestId), recovered);
|
|
60
|
+
}
|
|
61
|
+
catch { /* ignore an incomplete/corrupt historical line */ }
|
|
62
|
+
}
|
|
63
|
+
for (const record of this.recordsFor(selfAid)) {
|
|
64
|
+
for (const alias of record.correlationKeys)
|
|
65
|
+
this.records.set(this.key(selfAid, alias), record);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch { /* first use */ }
|
|
69
|
+
}
|
|
70
|
+
recordsFor(selfAid) {
|
|
71
|
+
const seen = new Set();
|
|
72
|
+
for (const record of this.records.values())
|
|
73
|
+
if (record.selfAid === selfAid)
|
|
74
|
+
seen.add(record);
|
|
75
|
+
return [...seen];
|
|
76
|
+
}
|
|
77
|
+
persist(record) {
|
|
78
|
+
const file = requestFile(record.selfAid);
|
|
79
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
80
|
+
appendJsonlDurable(file, { event: 'upsert', record });
|
|
81
|
+
this.records.set(this.key(record.selfAid, record.requestId), record);
|
|
82
|
+
for (const alias of record.correlationKeys)
|
|
83
|
+
this.records.set(this.key(record.selfAid, alias), record);
|
|
84
|
+
}
|
|
85
|
+
notify(record) {
|
|
86
|
+
if (!['completed', 'failed', 'unknown', 'pending'].includes(record.state))
|
|
87
|
+
return;
|
|
88
|
+
const waiters = this.waiters.get(this.key(record.selfAid, record.requestId));
|
|
89
|
+
if (!waiters)
|
|
90
|
+
return;
|
|
91
|
+
for (const waiter of waiters)
|
|
92
|
+
waiter(record);
|
|
93
|
+
this.waiters.delete(this.key(record.selfAid, record.requestId));
|
|
94
|
+
}
|
|
95
|
+
prepare(params) {
|
|
96
|
+
this.ensureLoaded(params.selfAid);
|
|
97
|
+
const requestId = stringValue(params.payload.request_id);
|
|
98
|
+
const callId = stringValue(params.payload.call_id);
|
|
99
|
+
const chosen = requestId || callId || randomRequestId();
|
|
100
|
+
const keys = [...new Set([chosen, requestId, callId].filter((value) => !!value))];
|
|
101
|
+
if (keys.some(key => this.records.has(this.key(params.selfAid, key)))) {
|
|
102
|
+
throw Object.assign(new Error('Request correlation ID is already registered'), { code: 'REQUEST_ID_CONFLICT' });
|
|
103
|
+
}
|
|
104
|
+
const correlationKind = requestId || !callId ? 'request_id' : 'call_id';
|
|
105
|
+
const now = Date.now();
|
|
106
|
+
const timeoutMs = normalizeTimeoutMs(params.timeoutMs);
|
|
107
|
+
const record = {
|
|
108
|
+
selfAid: params.selfAid,
|
|
109
|
+
targetAid: params.targetAid,
|
|
110
|
+
requestId: chosen,
|
|
111
|
+
correlationKind,
|
|
112
|
+
correlationKeys: keys,
|
|
113
|
+
...(params.origin ? { origin: { ...params.origin } } : {}),
|
|
114
|
+
state: 'created',
|
|
115
|
+
createdAt: now,
|
|
116
|
+
updatedAt: now,
|
|
117
|
+
deadlineAt: now + timeoutMs,
|
|
118
|
+
};
|
|
119
|
+
const payload = { ...params.payload };
|
|
120
|
+
if (!requestId && !callId)
|
|
121
|
+
payload.request_id = chosen;
|
|
122
|
+
this.persist(record);
|
|
123
|
+
return { requestId: chosen, payload, record };
|
|
124
|
+
}
|
|
125
|
+
markSending(selfAid, requestId) {
|
|
126
|
+
return this.update(selfAid, requestId, { state: 'sending' });
|
|
127
|
+
}
|
|
128
|
+
markTransportAccepted(selfAid, requestId, messageId, status) {
|
|
129
|
+
const current = this.get(selfAid, requestId);
|
|
130
|
+
if (!current)
|
|
131
|
+
return undefined;
|
|
132
|
+
const state = current.state === 'created' || current.state === 'sending' ? 'sent' : current.state;
|
|
133
|
+
return this.update(selfAid, requestId, { state, outboundMessageId: messageId, transportStatus: status });
|
|
134
|
+
}
|
|
135
|
+
markFailed(selfAid, requestId, error) {
|
|
136
|
+
const current = this.get(selfAid, requestId);
|
|
137
|
+
if (current?.state === 'completed' || current?.state === 'failed')
|
|
138
|
+
return current;
|
|
139
|
+
return this.update(selfAid, requestId, { state: 'failed', error });
|
|
140
|
+
}
|
|
141
|
+
update(selfAid, requestId, patch) {
|
|
142
|
+
this.ensureLoaded(selfAid);
|
|
143
|
+
const existing = this.records.get(this.key(selfAid, requestId));
|
|
144
|
+
if (!existing)
|
|
145
|
+
return undefined;
|
|
146
|
+
const record = { ...existing, ...patch, updatedAt: Date.now() };
|
|
147
|
+
this.persist(record);
|
|
148
|
+
this.notify(record);
|
|
149
|
+
return record;
|
|
150
|
+
}
|
|
151
|
+
updateWithoutNotify(selfAid, requestId, patch) {
|
|
152
|
+
this.ensureLoaded(selfAid);
|
|
153
|
+
const existing = this.records.get(this.key(selfAid, requestId));
|
|
154
|
+
if (!existing)
|
|
155
|
+
return undefined;
|
|
156
|
+
const record = { ...existing, ...patch, updatedAt: Date.now() };
|
|
157
|
+
this.persist(record);
|
|
158
|
+
return record;
|
|
159
|
+
}
|
|
160
|
+
isIntermediateResponse(payload) {
|
|
161
|
+
// A generic application ACK means that the request was accepted. A later
|
|
162
|
+
// response carrying the same correlation key remains eligible to finish
|
|
163
|
+
// the wait; the transport does not interpret any business message type.
|
|
164
|
+
return payload.role === 'ack';
|
|
165
|
+
}
|
|
166
|
+
resolveIncoming(params) {
|
|
167
|
+
if (!params.payload || typeof params.payload !== 'object' || Array.isArray(params.payload))
|
|
168
|
+
return undefined;
|
|
169
|
+
this.ensureLoaded(params.selfAid);
|
|
170
|
+
const payload = params.payload;
|
|
171
|
+
const keys = [stringValue(payload.request_id), stringValue(payload.call_id)].filter((value) => !!value);
|
|
172
|
+
for (const key of keys) {
|
|
173
|
+
const record = this.records.get(this.key(params.selfAid, key));
|
|
174
|
+
if (!record || record.targetAid !== params.fromAid || ['failed', 'completed'].includes(record.state))
|
|
175
|
+
continue;
|
|
176
|
+
if (this.isIntermediateResponse(payload)) {
|
|
177
|
+
return this.updateWithoutNotify(params.selfAid, record.requestId, {
|
|
178
|
+
ackMessageId: params.messageId,
|
|
179
|
+
ackPayload: payload,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
const updated = this.update(params.selfAid, record.requestId, {
|
|
183
|
+
state: 'completed',
|
|
184
|
+
responseMessageId: params.messageId,
|
|
185
|
+
responsePayload: payload,
|
|
186
|
+
});
|
|
187
|
+
return updated;
|
|
188
|
+
}
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
get(selfAid, requestId) {
|
|
192
|
+
this.ensureLoaded(selfAid);
|
|
193
|
+
const record = this.records.get(this.key(selfAid, requestId));
|
|
194
|
+
if (record && record.state === 'sent' && record.deadlineAt !== undefined && record.deadlineAt <= Date.now()) {
|
|
195
|
+
return this.update(selfAid, record.requestId, { state: 'pending' });
|
|
196
|
+
}
|
|
197
|
+
return record;
|
|
198
|
+
}
|
|
199
|
+
async wait(selfAid, requestId, timeoutMs) {
|
|
200
|
+
const current = this.get(selfAid, requestId);
|
|
201
|
+
if (!current || ['completed', 'failed', 'unknown'].includes(current.state))
|
|
202
|
+
return current;
|
|
203
|
+
const normalizedTimeoutMs = normalizeTimeoutMs(timeoutMs);
|
|
204
|
+
const waitMs = Math.max(0, Math.min(normalizedTimeoutMs, current.deadlineAt ? current.deadlineAt - Date.now() : normalizedTimeoutMs));
|
|
205
|
+
if (waitMs === 0)
|
|
206
|
+
return this.update(selfAid, requestId, { state: 'pending' });
|
|
207
|
+
return new Promise(resolve => {
|
|
208
|
+
const key = this.key(selfAid, requestId);
|
|
209
|
+
const callback = (record) => { clearTimeout(timer); resolve(record); };
|
|
210
|
+
const waiters = this.waiters.get(key) ?? new Set();
|
|
211
|
+
waiters.add(callback);
|
|
212
|
+
this.waiters.set(key, waiters);
|
|
213
|
+
const timer = setTimeout(() => {
|
|
214
|
+
if (!this.waiters.get(key)?.delete(callback))
|
|
215
|
+
return;
|
|
216
|
+
if (this.waiters.get(key)?.size === 0)
|
|
217
|
+
this.waiters.delete(key);
|
|
218
|
+
resolve(this.update(selfAid, requestId, { state: 'pending' }));
|
|
219
|
+
}, waitMs);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
let defaultRegistry;
|
|
224
|
+
export function getAunRequestRegistry() {
|
|
225
|
+
return defaultRegistry ??= new AunRequestRegistry();
|
|
226
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { creationOwnershipBinding } from '../ownership/owner-resolver.js';
|
|
3
5
|
const FILE = 'create-status.json';
|
|
4
6
|
export function readCreateStatus(agentDir) {
|
|
5
7
|
try {
|
|
@@ -22,12 +24,13 @@ export function removeCreateStatus(agentDir) {
|
|
|
22
24
|
export class CreateStatusWriter {
|
|
23
25
|
agentDir;
|
|
24
26
|
status;
|
|
25
|
-
|
|
27
|
+
creationId = randomUUID();
|
|
28
|
+
constructor(agentDir, aid, context) {
|
|
26
29
|
this.agentDir = agentDir;
|
|
27
30
|
const now = Date.now();
|
|
28
|
-
this.status = { aid, status: 'in_progress', currentPhase: null, steps: [], error: null, startedAt: now, updatedAt: now };
|
|
31
|
+
this.status = { aid, creationId: this.creationId, ...context, status: 'in_progress', currentPhase: null, steps: [], error: null, startedAt: now, updatedAt: now };
|
|
29
32
|
fs.mkdirSync(agentDir, { recursive: true });
|
|
30
|
-
this.flush();
|
|
33
|
+
this.flush(true);
|
|
31
34
|
}
|
|
32
35
|
begin(phase) {
|
|
33
36
|
this.status.currentPhase = phase;
|
|
@@ -36,6 +39,28 @@ export class CreateStatusWriter {
|
|
|
36
39
|
}
|
|
37
40
|
done(phase, detail) { this.mark(phase, 'done', detail); }
|
|
38
41
|
warn(phase, detail) { this.mark(phase, 'warn', detail); }
|
|
42
|
+
/** Immutable correlation; a later CLI bind/transfer must not replace it. */
|
|
43
|
+
setOwnershipBinding(binding) {
|
|
44
|
+
const expected = creationOwnershipBinding({
|
|
45
|
+
operation: binding.operation, operation_id: binding.operationId,
|
|
46
|
+
sub_aid: binding.subjectAid, owner_aid: binding.ownerAid,
|
|
47
|
+
base_relation_version: binding.baseRelationVersion, target_relation_version: binding.targetRelationVersion,
|
|
48
|
+
status: 'pending',
|
|
49
|
+
}, this.status.aid, this.status.ownerAid ?? '');
|
|
50
|
+
if (!this.status.requesterAid || binding.source !== 'agent_create' || !expected) {
|
|
51
|
+
throw new Error('Creation Ownership binding does not match the creation request');
|
|
52
|
+
}
|
|
53
|
+
if (this.status.ownershipBinding) {
|
|
54
|
+
if (this.status.ownershipBinding.operationId !== binding.operationId) {
|
|
55
|
+
throw new Error('Creation Ownership binding is already recorded');
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (this.status.status !== 'in_progress')
|
|
60
|
+
throw new Error('Agent creation has already finished');
|
|
61
|
+
this.status.ownershipBinding = expected;
|
|
62
|
+
this.flush();
|
|
63
|
+
}
|
|
39
64
|
finishReady() { this.status.status = 'ready'; this.status.currentPhase = null; this.flush(); }
|
|
40
65
|
finishFailed(phase, error) {
|
|
41
66
|
this.mark(phase, 'failed', error);
|
|
@@ -57,7 +82,11 @@ export class CreateStatusWriter {
|
|
|
57
82
|
}
|
|
58
83
|
this.flush();
|
|
59
84
|
}
|
|
60
|
-
flush() {
|
|
85
|
+
flush(initial = false) {
|
|
86
|
+
// A failed worker or a deleted Agent must not overwrite a newer creation.
|
|
87
|
+
if (!initial && readCreateStatus(this.agentDir)?.creationId !== this.creationId) {
|
|
88
|
+
throw new Error('Agent creation record was removed or replaced');
|
|
89
|
+
}
|
|
61
90
|
this.status.updatedAt = Date.now();
|
|
62
91
|
const file = path.join(this.agentDir, FILE);
|
|
63
92
|
const tmp = `${file}.tmp`;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loadAgent } from '../../config-store.js';
|
|
2
|
+
import { subscribeUpgradeJob } from '../../utils/upgrade-jobs.js';
|
|
2
3
|
import { initializationBlocksTasks } from '../../cli/agent-create.js';
|
|
3
4
|
import { createHash, randomBytes } from 'crypto';
|
|
4
5
|
import fs from 'fs';
|
|
@@ -17,6 +18,8 @@ import { resolveContactView, resolvePeerDisplayLabel, resolvePrincipal } from '.
|
|
|
17
18
|
import { findContactRequestNote, expirePendingContactRequests, reviewContactRequest, submitContactRequest, contactRequestSubmissionProtocolResult, queryOwnContactRequestStatus, } from '../../config/contact-request-service.js';
|
|
18
19
|
import { isValidAid } from '../../aun/aid/validation.js';
|
|
19
20
|
import { PeerIdentityCache, formatPeerKey } from '../relation/peer-identity.js';
|
|
21
|
+
import { ownerResolver } from '../ownership/owner-resolver.js';
|
|
22
|
+
import { isTopicSession, sessionLocalTopicId } from '../thread/topic-id.js';
|
|
20
23
|
import { resolveEffective } from '../../config/config-manager.js';
|
|
21
24
|
import { createDecisionInferenceProvider } from '../inference/text-inference.js';
|
|
22
25
|
import { AutoMentionGate } from './auto-mention-gate.js';
|
|
@@ -116,8 +119,8 @@ export class MessageBridge {
|
|
|
116
119
|
self: item.selfAid,
|
|
117
120
|
peerKey: formatPeerKey('aun', item.groupId),
|
|
118
121
|
}, { cache: true, expand: true });
|
|
119
|
-
const model = config.decision_model?.model?.trim();
|
|
120
|
-
const provider = createDecisionInferenceProvider(config.decision_model);
|
|
122
|
+
const model = config.Services?.decision ? config.Services.decision.model?.trim() : config.decision_model?.model?.trim();
|
|
123
|
+
const provider = createDecisionInferenceProvider(config.decision_model, config.Services);
|
|
121
124
|
return model && provider ? { model, provider } : undefined;
|
|
122
125
|
},
|
|
123
126
|
resolveResources: async (item) => {
|
|
@@ -245,11 +248,11 @@ export class MessageBridge {
|
|
|
245
248
|
this.groupInviteReviewService?.attach(binding);
|
|
246
249
|
}
|
|
247
250
|
/**
|
|
248
|
-
* AUN
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
+
* Handle AUN's two slash-bind forms: bare `/bind` requests protocol
|
|
252
|
+
* ownership from the first configured Owner; `/bind <channel>` creates an
|
|
253
|
+
* external-channel contact mapping.
|
|
251
254
|
*/
|
|
252
|
-
|
|
255
|
+
async handleAunBindCommand(input) {
|
|
253
256
|
const match = /^\s*\/bind(?:\s+(.+?))?\s*$/i.exec(input.content);
|
|
254
257
|
if (!match)
|
|
255
258
|
return { handled: false };
|
|
@@ -270,7 +273,42 @@ export class MessageBridge {
|
|
|
270
273
|
};
|
|
271
274
|
}
|
|
272
275
|
const args = String(match[1] || '').trim().split(/\s+/).filter(Boolean);
|
|
273
|
-
if (args.length
|
|
276
|
+
if (args.length === 0) {
|
|
277
|
+
const configuredOwner = ownerResolver.configuredOwnerList(selfAid)[0];
|
|
278
|
+
if (configuredOwner !== actorId) {
|
|
279
|
+
return { handled: true, status: 'not-first-owner', reply: '仅 Agent 配置中的首个 Owner 可在 AUN 私聊发送 /bind 发起 ownership 绑定。' };
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
const state = await ownerResolver.bind(selfAid, actorId);
|
|
283
|
+
const alreadyBound = state.consensusStatus === 'consensus' && state.ownerAid === actorId;
|
|
284
|
+
if (!alreadyBound && state.task && ['rejected', 'stopped', 'error'].includes(state.task.status)) {
|
|
285
|
+
return {
|
|
286
|
+
handled: true,
|
|
287
|
+
status: 'ownership-failed',
|
|
288
|
+
reply: `协议 Owner 绑定未完成:${state.task.reason ?? state.task.code ?? state.task.status}`,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
handled: true,
|
|
293
|
+
status: alreadyBound ? 'already-bound' : 'ownership-submitted',
|
|
294
|
+
reply: alreadyBound
|
|
295
|
+
? '协议 Owner 已绑定,无需重复操作。'
|
|
296
|
+
: state.task?.status === 'completed'
|
|
297
|
+
? '绑定任务已完成,等待协议共识核验。'
|
|
298
|
+
: `已向 ${actorId} 发起 ownership 绑定邀约,请在 Evol 中确认。`,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
303
|
+
logger.warn(`[MessageBridge] AUN ownership bind failed: subject=${selfAid} owner=${actorId} error=${message}`);
|
|
304
|
+
return {
|
|
305
|
+
handled: true,
|
|
306
|
+
status: 'ownership-failed',
|
|
307
|
+
reply: `协议 Owner 绑定未完成:${message}`,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (args.length > 2) {
|
|
274
312
|
return { handled: true, status: 'format', reply: this.aunSelfServiceBindUsage() };
|
|
275
313
|
}
|
|
276
314
|
const channelType = args[0].toLowerCase();
|
|
@@ -359,7 +397,7 @@ export class MessageBridge {
|
|
|
359
397
|
}
|
|
360
398
|
}
|
|
361
399
|
aunSelfServiceBindUsage() {
|
|
362
|
-
return `用法:/bind <渠道> [实例名]
|
|
400
|
+
return `用法:/bind(首个配置 Owner 发起 ownership 绑定);/bind <渠道> [实例名](联系人绑定)。支持渠道:${SELF_SERVICE_CONTACT_BIND_TYPES.join('、')}。`;
|
|
363
401
|
}
|
|
364
402
|
aunSelfServiceBindInstances(channelType, instances) {
|
|
365
403
|
const names = instances.map(instance => String(instance.name || '').trim()).filter(Boolean);
|
|
@@ -396,7 +434,7 @@ export class MessageBridge {
|
|
|
396
434
|
return true;
|
|
397
435
|
}
|
|
398
436
|
if (context.channelType === 'aun') {
|
|
399
|
-
const aunSelfServiceBind = this.
|
|
437
|
+
const aunSelfServiceBind = await this.handleAunBindCommand({
|
|
400
438
|
selfAid: context.selfAid,
|
|
401
439
|
actorId: context.actorId,
|
|
402
440
|
chatType: context.chatType,
|
|
@@ -404,7 +442,7 @@ export class MessageBridge {
|
|
|
404
442
|
owningAgent,
|
|
405
443
|
});
|
|
406
444
|
if (aunSelfServiceBind.handled) {
|
|
407
|
-
logger.info(`[MessageBridge] AUN
|
|
445
|
+
logger.info(`[MessageBridge] AUN bind command handled before admission: channel=${context.channelKey} actor=${context.actorId || '<none>'} status=${aunSelfServiceBind.status}`);
|
|
408
446
|
if (aunSelfServiceBind.reply) {
|
|
409
447
|
await this.sendAdmissionControlReply(context, msg, sendReply, aunSelfServiceBind.reply);
|
|
410
448
|
}
|
|
@@ -1414,7 +1452,7 @@ export class MessageBridge {
|
|
|
1414
1452
|
releaseAdmission = await this.acquireAdmission(admissionRoute);
|
|
1415
1453
|
const hadMainSession = msg.threadId ? true : this.sessionManager.hasMainSession(channelName, msg.channelId, msg.channelType || effectiveChannelType, msg.selfAID || selfAid);
|
|
1416
1454
|
let session = diagnosticSession ?? await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, this.extractTopicName(msg), msg.peerId, chatType, owningAgent?.baseagent, msg.selfAID, msg.channelType || effectiveChannelType, msg.peerType, identity, readTopicLabel(msg) ?? readTopicLabel(msg.replyContext?.metadata), readTopicStatus(msg) ?? readTopicStatus(msg.replyContext?.metadata));
|
|
1417
|
-
if (session
|
|
1455
|
+
if (isTopicSession(session) && typeof this.messageQueue.reserveSessionIngress === 'function') {
|
|
1418
1456
|
releaseSessionIngress = this.messageQueue.reserveSessionIngress(session.id);
|
|
1419
1457
|
}
|
|
1420
1458
|
const replyToMessageId = typeof msg.replyContext?.metadata?.refMessageId === 'string'
|
|
@@ -1549,7 +1587,8 @@ export class MessageBridge {
|
|
|
1549
1587
|
from: msg.peerId || 'unknown',
|
|
1550
1588
|
to: msg.selfAID || 'self',
|
|
1551
1589
|
sessionId: session.id,
|
|
1552
|
-
|
|
1590
|
+
// 消息日志用本端身份(含主话题规范 id),与出站日志的 localTopicId 一致。
|
|
1591
|
+
threadId: sessionLocalTopicId(session) ?? '',
|
|
1553
1592
|
chatType,
|
|
1554
1593
|
groupId: msg.groupId ?? null,
|
|
1555
1594
|
msgId: msg.messageId ?? null,
|
|
@@ -1608,7 +1647,7 @@ export class MessageBridge {
|
|
|
1608
1647
|
const member = item.value;
|
|
1609
1648
|
appendMessageLog(inboundEntry.chatDir, buildInboundEntry({
|
|
1610
1649
|
from: item.sender, to: selfAid || '', sessionId: session.id,
|
|
1611
|
-
threadId: session
|
|
1650
|
+
threadId: sessionLocalTopicId(session) ?? '', chatType: 'group', groupId: msg.groupId,
|
|
1612
1651
|
msgId: item.messageId, content: member.messageLogContent ?? item.content,
|
|
1613
1652
|
permMode: item.role, timestamp: item.receivedAt,
|
|
1614
1653
|
receivedAt: member.receivedAt, gatewaySeq: member.gatewaySeq,
|
|
@@ -2047,6 +2086,16 @@ export class MessageBridge {
|
|
|
2047
2086
|
try {
|
|
2048
2087
|
await adapter.send(envelope, { kind: 'custom', channelType: channel, payload: timedResponse });
|
|
2049
2088
|
logMenuRequestCompleted(request, timedResponse, flowContext, { delivery: 'sent' });
|
|
2089
|
+
const upgradeData = 'data' in response ? response.data : undefined;
|
|
2090
|
+
if (request.type === 'menu.action' && request.action === 'upgrade' && (request.name === 'system' || request.cmd === '/system') && upgradeData?.jobId) {
|
|
2091
|
+
const jobId = upgradeData.jobId;
|
|
2092
|
+
subscribeUpgradeJob(jobId, async (event) => {
|
|
2093
|
+
const permission = await this.cmdHandler.execMenuAction('/system', 'upgrade', { mode: 'status', jobId }, channel, msg.channelId, msg.peerId, undefined, msg.replyContext?.delivery?.chatType, undefined, false, undefined, 'menu', msg.replyContext);
|
|
2094
|
+
if ('error' in permission)
|
|
2095
|
+
throw new Error('Upgrade event authorization expired');
|
|
2096
|
+
await adapter.send(envelope, { kind: 'custom', channelType: channel, payload: event });
|
|
2097
|
+
}, [channel, msg.channelId, msg.peerId, msg.peerDeviceId || '', jobId].join('\u001f'));
|
|
2098
|
+
}
|
|
2050
2099
|
}
|
|
2051
2100
|
catch (error) {
|
|
2052
2101
|
logMenuRequestCompleted(request, timedResponse, flowContext, {
|
|
@@ -5,6 +5,7 @@ import { LogicalQueueBridge } from './logical-queue-bridge.js';
|
|
|
5
5
|
import { normalizeCausation } from '../causation/context.js';
|
|
6
6
|
import { KeyedFairMutex } from '../handoff/mutex.js';
|
|
7
7
|
import { ExecutionTimeoutError, OrphanedTargetSessionError, isExecutionFailure } from '../../utils/error-utils.js';
|
|
8
|
+
import { isTopicThreadId } from '../thread/topic-id.js';
|
|
8
9
|
const DEFAULT_AGENT_NAME = '<unknown>';
|
|
9
10
|
const RESTART_RESUME_CONTENT = 'evolcore 服务已重启,请继续之前未完成的任务。';
|
|
10
11
|
export class MessageQueue {
|
|
@@ -462,7 +463,9 @@ export class MessageQueue {
|
|
|
462
463
|
return false;
|
|
463
464
|
if (scope.groupId !== undefined && message.groupId !== scope.groupId)
|
|
464
465
|
return false;
|
|
465
|
-
|
|
466
|
+
// 主会话的三种形态('' / 'main' / 规范 id)视为同一个:只比较话题 id。
|
|
467
|
+
if (scope.threadId !== undefined
|
|
468
|
+
&& (isTopicThreadId(message.threadId) ? message.threadId : '') !== (isTopicThreadId(scope.threadId) ? scope.threadId : ''))
|
|
466
469
|
return false;
|
|
467
470
|
return true;
|
|
468
471
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 从 message-processor.ts 提取的独立工具函数,供 ResponseEngine 和其他模块使用。
|
|
5
5
|
*/
|
|
6
|
+
import { sessionLocalTopicId, sessionTopicThreadId } from '../thread/topic-id.js';
|
|
6
7
|
import { renderCommandCardAsText, renderActionAsText } from '../interaction-router.js';
|
|
7
8
|
/** A legacy adapter resolved its send call without providing a structured receipt. */
|
|
8
9
|
export const LEGACY_INTERACTION_ACCEPTED = 'legacy-accepted';
|
|
@@ -99,9 +100,15 @@ export function deliveryTargetFromSession(session) {
|
|
|
99
100
|
}
|
|
100
101
|
/** Preserve channel-specific reply fields while filling the session route. */
|
|
101
102
|
export function replyContextFromSession(session, context) {
|
|
103
|
+
const topicThreadId = sessionTopicThreadId(session);
|
|
104
|
+
const localTopicId = sessionLocalTopicId(session);
|
|
102
105
|
return {
|
|
103
106
|
...(session.metadata?.replyContext ?? {}),
|
|
104
|
-
|
|
107
|
+
// 线上只带话题 id:主话题 id 是"本端+对端"拼出来的身份,发出去会让对端
|
|
108
|
+
// 凭空建一个叫 `main:…` 的话题。主话题的线上形态就是"不带话题字段"。
|
|
109
|
+
...(topicThreadId ? { threadId: topicThreadId } : {}),
|
|
110
|
+
// 本地日志要的是完整身份(含主话题规范 id),单独用不线上的字段携带。
|
|
111
|
+
...(localTopicId ? { localTopicId } : {}),
|
|
105
112
|
...(context ?? {}),
|
|
106
113
|
// Session chatType/groupId is the persisted routing authority. The nested
|
|
107
114
|
// reply context can outlive a route correction, so only an explicit
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { loadSchema } from '../../config/schema-registry.js';
|
|
2
2
|
import { resolveEffective } from '../../config/config-manager.js';
|
|
3
3
|
import { resolveRuntimeStringField } from '../role/runtime-policy.js';
|
|
4
|
+
import { isManagementRole } from '../../config/builtin-roles.js';
|
|
4
5
|
/** Resolve the effective chat mode from the ConfigManager layering context. */
|
|
5
6
|
export function resolveChatMode(params) {
|
|
6
7
|
let configured;
|
|
@@ -20,6 +21,11 @@ export function resolveChatMode(params) {
|
|
|
20
21
|
if (kind === 'agent')
|
|
21
22
|
return 'proactive';
|
|
22
23
|
const field = chatmodeFieldForPeer(params.chatType, params.peerType);
|
|
24
|
+
// 人类私聊的角色门槛(硬约束):interactive 是「输出即回复」的高信任模式,
|
|
25
|
+
// 只有 owner/admin 才走下面的分层配置;其余角色(含未分配/未知)直接 proactive,
|
|
26
|
+
// 关系级或 agent 级把 chatmode.private 写成 interactive 都不生效。
|
|
27
|
+
if (isPrivateChatmodeRoleLocked(field, params.role))
|
|
28
|
+
return 'proactive';
|
|
23
29
|
const decision = resolveRuntimeStringField({
|
|
24
30
|
selfAid: params.self,
|
|
25
31
|
role: params.role,
|
|
@@ -32,16 +38,18 @@ export function resolveChatMode(params) {
|
|
|
32
38
|
: chatmodeFactoryDefaults()[field];
|
|
33
39
|
}
|
|
34
40
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
* 人类私聊(chatmode.private)是否被角色锁定为 proactive。
|
|
42
|
+
* owner/admin 之外的任何角色(含 null/'none'/未知)都锁定。
|
|
43
|
+
*/
|
|
44
|
+
export function isPrivateChatmodeRoleLocked(field, role) {
|
|
45
|
+
return field === 'private' && !isManagementRole(role);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 出站发送路径(`ec msg send` / daemon 私聊发送)用的判定,与入站共用
|
|
49
|
+
* `resolveChatMode` 的硬约束:
|
|
39
50
|
* - 非人类对端(isMachine)→ proactive
|
|
40
|
-
* -
|
|
41
|
-
* - 人类且 owner/admin →
|
|
42
|
-
*
|
|
43
|
-
* 角色未知时不能假设高信任:interactive 表示"输出即回复"的同步交互,
|
|
44
|
-
* 只应给已确认的管理者。
|
|
51
|
+
* - 人类且非 owner/admin(含角色未知)→ proactive
|
|
52
|
+
* - 人类且 owner/admin → 分层配置(未配置时取出厂默认)
|
|
45
53
|
*/
|
|
46
54
|
export function chatmodeForPeerIdentity(identity, role) {
|
|
47
55
|
if (identity.isMachine)
|