evolcore 0.0.35 → 0.0.37
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 +42 -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 +177 -85
- 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 +115 -36
- 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,58 @@
|
|
|
1
|
+
import { isValidAid } from './validation.js';
|
|
2
|
+
import { ownerResolver, OwnershipError } from '../../core/ownership/owner-resolver.js';
|
|
3
|
+
import { logger } from '../../utils/logger.js';
|
|
4
|
+
export function ownershipFailure(error) {
|
|
5
|
+
const details = error && typeof error === 'object' ? error : {};
|
|
6
|
+
return {
|
|
7
|
+
ok: false,
|
|
8
|
+
code: typeof details.stringCode === 'string' ? details.stringCode
|
|
9
|
+
: typeof details.code === 'string' ? details.code : 'OWNERSHIP_OPERATION_FAILED',
|
|
10
|
+
error: error instanceof Error ? error.message : typeof details.error === 'string' ? details.error : String(error),
|
|
11
|
+
...(typeof details.retryable === 'boolean' ? { retryable: details.retryable } : {}),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function parseOwnershipCommand(argv) {
|
|
15
|
+
const args = argv[0] === 'aid' ? argv.slice(1) : [...argv];
|
|
16
|
+
if (args[0] !== 'bind' || !args[1]) {
|
|
17
|
+
throw new OwnershipError('INVALID_ARGUMENT', 'Use ec aid bind <ownerAid> or ec aid bind status');
|
|
18
|
+
}
|
|
19
|
+
const action = args[1] === 'status' ? 'status' : 'bind';
|
|
20
|
+
if (action === 'bind' && !isValidAid(args[1]))
|
|
21
|
+
throw new OwnershipError('INVALID_AID', 'A valid owner AID is required');
|
|
22
|
+
let subjectAid;
|
|
23
|
+
let formatSeen = false;
|
|
24
|
+
for (let index = 2; index < args.length; index += 2) {
|
|
25
|
+
if (args[index] === '--subject' && !subjectAid && isValidAid(args[index + 1] ?? ''))
|
|
26
|
+
subjectAid = args[index + 1];
|
|
27
|
+
else if (args[index] === '--format' && args[index + 1] === 'json' && !formatSeen)
|
|
28
|
+
formatSeen = true;
|
|
29
|
+
else
|
|
30
|
+
throw new OwnershipError('INVALID_ARGUMENT', 'Only one --subject <subjectAid> and --format json are supported');
|
|
31
|
+
}
|
|
32
|
+
return action === 'status' ? { action, subjectAid } : { action, subjectAid, ownerAid: args[1] };
|
|
33
|
+
}
|
|
34
|
+
export async function executeOwnershipCommand(command, defaultSubjectAid) {
|
|
35
|
+
const subjectAid = command.subjectAid ?? defaultSubjectAid;
|
|
36
|
+
if (!subjectAid)
|
|
37
|
+
throw new OwnershipError('SUBJECT_REQUIRED', 'No local subject AID is available');
|
|
38
|
+
if (command.action === 'status') {
|
|
39
|
+
ownerResolver.assertLocalSubject(subjectAid);
|
|
40
|
+
return ownerResolver.refresh(subjectAid);
|
|
41
|
+
}
|
|
42
|
+
logger.info(`[ownership] bind requested subject=${subjectAid} owner=${command.ownerAid}`);
|
|
43
|
+
try {
|
|
44
|
+
ownerResolver.assertLocalSubject(subjectAid);
|
|
45
|
+
const result = await ownerResolver.bind(subjectAid, command.ownerAid);
|
|
46
|
+
const task = result.task;
|
|
47
|
+
const log = task && ['rejected', 'stopped', 'error'].includes(task.status) ? logger.warn : logger.info;
|
|
48
|
+
log(`[ownership] bind result ${JSON.stringify({ subjectAid, ownerAid: command.ownerAid, operationId: task?.operation_id,
|
|
49
|
+
taskStatus: task?.status, step: task?.step, consensusStatus: result.consensusStatus, requestStatus: result.requestStatus,
|
|
50
|
+
code: task?.code, reason: task?.reason })}`);
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
const details = ownershipFailure(error);
|
|
55
|
+
logger.warn(`[ownership] bind failed subject=${subjectAid} owner=${command.ownerAid} code=${details.code} error=${JSON.stringify(details.error)}`);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
package/dist/aun/aid/store.js
CHANGED
|
@@ -1,4 +1,61 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { StorageLowLevel, StorageVFS } from '@agentunion/fastaun';
|
|
3
|
+
export const DEFAULT_FS_CAT_BYTES = 128 * 1024;
|
|
4
|
+
export const MAX_FS_CAT_BYTES = 128 * 1024;
|
|
5
|
+
export class FsReadBudgetError extends Error {
|
|
6
|
+
maxBytes;
|
|
7
|
+
code = 'FS_CAT_TOO_LARGE';
|
|
8
|
+
constructor(maxBytes) {
|
|
9
|
+
super(`cat exceeds read budget ${maxBytes} bytes`);
|
|
10
|
+
this.maxBytes = maxBytes;
|
|
11
|
+
this.name = 'FsReadBudgetError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function validateFsReadBudget(maxBytes) {
|
|
15
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > MAX_FS_CAT_BYTES) {
|
|
16
|
+
throw new RangeError(`cat read budget must be an integer from 1 to ${MAX_FS_CAT_BYTES}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function downloadWithinBudget(url, maxBytes, headers) {
|
|
20
|
+
validateFsReadBudget(maxBytes);
|
|
21
|
+
const controller = new AbortController();
|
|
22
|
+
const timeout = setTimeout(() => controller.abort(), 30_000);
|
|
23
|
+
try {
|
|
24
|
+
const response = await fetch(url, { headers, signal: controller.signal });
|
|
25
|
+
if (!response.ok)
|
|
26
|
+
throw new Error(`HTTP GET failed: status=${response.status}`);
|
|
27
|
+
const contentLength = response.headers.get('content-length');
|
|
28
|
+
if (contentLength !== null && Number(contentLength) > maxBytes)
|
|
29
|
+
throw new FsReadBudgetError(maxBytes);
|
|
30
|
+
if (!response.body)
|
|
31
|
+
throw new Error('HTTP GET returned no response body');
|
|
32
|
+
const chunks = [];
|
|
33
|
+
let totalBytes = 0;
|
|
34
|
+
for await (const chunk of response.body) {
|
|
35
|
+
totalBytes += chunk.byteLength;
|
|
36
|
+
if (totalBytes > maxBytes)
|
|
37
|
+
throw new FsReadBudgetError(maxBytes);
|
|
38
|
+
chunks.push(chunk);
|
|
39
|
+
}
|
|
40
|
+
return Buffer.concat(chunks, totalBytes);
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
clearTimeout(timeout);
|
|
44
|
+
controller.abort();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export async function readPersonalFsBytesWithinBudget(client, path, maxBytes, options) {
|
|
48
|
+
validateFsReadBudget(maxBytes);
|
|
49
|
+
const lowlevel = new StorageLowLevel(client);
|
|
50
|
+
const getObject = lowlevel.getObject.bind(lowlevel);
|
|
51
|
+
lowlevel.getObject = params => getObject({ ...params, offset: 0, limit: maxBytes + 1 });
|
|
52
|
+
lowlevel.httpGet = (url, headers) => downloadWithinBudget(url, maxBytes, headers);
|
|
53
|
+
const storage = new StorageVFS(client, { lowlevel, useFsRpc: client.storage.useFsRpc });
|
|
54
|
+
const bytes = await storage.readBytes(path, options);
|
|
55
|
+
if (bytes.byteLength > maxBytes)
|
|
56
|
+
throw new FsReadBudgetError(maxBytes);
|
|
57
|
+
return bytes;
|
|
58
|
+
}
|
|
2
59
|
export class GroupFsDownloadError extends Error {
|
|
3
60
|
code;
|
|
4
61
|
constructor(code, message) {
|
|
@@ -8,7 +65,9 @@ export class GroupFsDownloadError extends Error {
|
|
|
8
65
|
}
|
|
9
66
|
}
|
|
10
67
|
/** Download a group file into memory through the ticket/data plane. */
|
|
11
|
-
export async function downloadGroupFsBytes(client, path) {
|
|
68
|
+
export async function downloadGroupFsBytes(client, path, options = {}) {
|
|
69
|
+
if (options.maxBytes !== undefined)
|
|
70
|
+
validateFsReadBudget(options.maxBytes);
|
|
12
71
|
const ticketValue = await client.call('group.fs.create_download_ticket', { path });
|
|
13
72
|
if (!isRecord(ticketValue)) {
|
|
14
73
|
throw new GroupFsDownloadError('INVALID_TICKET', 'group.fs.create_download_ticket returned invalid response');
|
|
@@ -17,7 +76,13 @@ export async function downloadGroupFsBytes(client, path) {
|
|
|
17
76
|
if (!downloadUrl) {
|
|
18
77
|
throw new GroupFsDownloadError('MISSING_DOWNLOAD_URL', 'group.fs.create_download_ticket did not return download_url');
|
|
19
78
|
}
|
|
20
|
-
const
|
|
79
|
+
const size = ticketValue.size ?? ticketValue.size_bytes;
|
|
80
|
+
if (options.maxBytes !== undefined && typeof size === 'number' && size > options.maxBytes) {
|
|
81
|
+
throw new FsReadBudgetError(options.maxBytes);
|
|
82
|
+
}
|
|
83
|
+
const bytes = options.maxBytes === undefined
|
|
84
|
+
? await client.group.fs.lowlevel.httpGet(downloadUrl, bearerHeaders(client))
|
|
85
|
+
: await downloadWithinBudget(downloadUrl, options.maxBytes, bearerHeaders(client));
|
|
21
86
|
const expectedSha = stringField(ticketValue, ['sha256']);
|
|
22
87
|
if (expectedSha && sha256Hex(bytes).toLowerCase() !== expectedSha.toLowerCase()) {
|
|
23
88
|
throw new GroupFsDownloadError('HASH_MISMATCH', 'download hash verification failed');
|
|
@@ -5,6 +5,8 @@ import { isValidAid } from '../aid/validation.js';
|
|
|
5
5
|
import { normalizeGroupAid } from './identity.js';
|
|
6
6
|
import { resolvePaths } from '../../paths.js';
|
|
7
7
|
import { chatDirPath } from '../../core/session/session-fs-store.js';
|
|
8
|
+
import { isMainSessionThreadId, normalizeThreadOptionValue } from '../../core/thread/topic-id.js';
|
|
9
|
+
import { parseTimeArg, TIME_ARG_FORMATS } from '../../cli/cli-argv.js';
|
|
8
10
|
import { isTransientProtocolMessage, messageLogPath, } from '../../core/message/message-log.js';
|
|
9
11
|
export class GroupHistoryError extends Error {
|
|
10
12
|
code;
|
|
@@ -59,11 +61,19 @@ export function parseGroupHistoryArgs(argv) {
|
|
|
59
61
|
const raw = values.get(flag);
|
|
60
62
|
if (raw === undefined)
|
|
61
63
|
return undefined;
|
|
62
|
-
const result =
|
|
63
|
-
if (
|
|
64
|
-
fail('INVALID_ARGUMENT', flag
|
|
64
|
+
const result = parseTimeArg(raw);
|
|
65
|
+
if (result === undefined)
|
|
66
|
+
fail('INVALID_ARGUMENT', `${flag} 需要有效时间(支持 ${TIME_ARG_FORMATS})`);
|
|
65
67
|
return result;
|
|
66
68
|
};
|
|
69
|
+
// `--thread` 接受 `ec thread list` 打印的 `<群AID>/<id>`;前缀必须就是本次查询的群
|
|
70
|
+
let thread = values.get('--thread');
|
|
71
|
+
if (thread !== undefined) {
|
|
72
|
+
const normalized = normalizeThreadOptionValue(thread, peer => normalizeGroupAid(peer, from.slice(from.indexOf('.') + 1)) === groupId);
|
|
73
|
+
if (!normalized.ok)
|
|
74
|
+
return fail('INVALID_ARGUMENT', normalized.error);
|
|
75
|
+
thread = normalized.threadId;
|
|
76
|
+
}
|
|
67
77
|
const before = time('--before');
|
|
68
78
|
const after = time('--after');
|
|
69
79
|
if (before !== undefined && after !== undefined && after >= before)
|
|
@@ -81,7 +91,7 @@ export function parseGroupHistoryArgs(argv) {
|
|
|
81
91
|
}
|
|
82
92
|
}
|
|
83
93
|
return {
|
|
84
|
-
from, groupId, thread
|
|
94
|
+
from, groupId, thread,
|
|
85
95
|
limit, direction, before, beforeId: values.get('--before-id'),
|
|
86
96
|
after, afterId: values.get('--after-id'), message: values.get('--message'), offset,
|
|
87
97
|
};
|
|
@@ -170,7 +180,12 @@ function* records(file, args) {
|
|
|
170
180
|
}
|
|
171
181
|
}
|
|
172
182
|
function matchThread(entry, thread) {
|
|
173
|
-
|
|
183
|
+
if (thread === 'all')
|
|
184
|
+
return true;
|
|
185
|
+
// 消息日志里主话题有 '' / null / 规范 id 三种历史形态,与 ec msg history 同样归一。
|
|
186
|
+
if (isMainSessionThreadId(thread))
|
|
187
|
+
return isMainSessionThreadId(entry.threadId);
|
|
188
|
+
return entry.threadId === thread;
|
|
174
189
|
}
|
|
175
190
|
function utf8Prefix(content, bytes) {
|
|
176
191
|
const buffer = Buffer.from(content);
|
|
@@ -15,6 +15,7 @@ import { resolvePaths } from '../../paths.js';
|
|
|
15
15
|
import { readDelegationCredential } from '../../core/auth/agent-delegation.js';
|
|
16
16
|
import { normalizeAunMentionEntries, withAunAttention } from '../msg/mention-schema.js';
|
|
17
17
|
import { downloadGroupFsBytes } from './fs-download.js';
|
|
18
|
+
import { isTopicThreadId } from '../../core/thread/topic-id.js';
|
|
18
19
|
import { normalizeGroupAid, sameGroupAid } from './identity.js';
|
|
19
20
|
/** Map a group directory probe failure to a stable Menu protocol code. */
|
|
20
21
|
export function groupDirectoryErrorCode(rawCode, error) {
|
|
@@ -53,10 +54,15 @@ function groupThreadId(args, payload, runtimeContext) {
|
|
|
53
54
|
: undefined;
|
|
54
55
|
return payloadThread || args.thread || (isCurrentGroupRuntime(args, runtimeContext) ? runtimeContext?.threadId : undefined);
|
|
55
56
|
}
|
|
56
|
-
|
|
57
|
+
/** 把群发送的话题路由写进线上 payload。导出供单测固定"主话题 id 不上线"的契约。 */
|
|
58
|
+
export function applyGroupRoutingPayloadFields(payload, args, runtimeContext) {
|
|
57
59
|
const threadId = groupThreadId(args, payload, runtimeContext);
|
|
58
|
-
|
|
60
|
+
// 主话题的规范 id(`main:<a>:<b>`)是本端身份,不进线上报文:群里的主话题
|
|
61
|
+
// 线上形态就是"不带 thread_id"。`args.thread` 已是规范 id 时同样拦掉。
|
|
62
|
+
if (isTopicThreadId(threadId))
|
|
59
63
|
payload.thread_id = threadId;
|
|
64
|
+
else if (payload.thread_id !== undefined)
|
|
65
|
+
delete payload.thread_id;
|
|
60
66
|
}
|
|
61
67
|
function groupSendLogMetadata(args, payload, runtimeContext) {
|
|
62
68
|
const classified = payload ? classifyAunPayloadForLog(payload) : undefined;
|
package/dist/aun/menu/client.js
CHANGED
|
@@ -7,7 +7,7 @@ import { SLOT } from '../aid/store.js';
|
|
|
7
7
|
import { isValidAid } from '../aid/validation.js';
|
|
8
8
|
import { resolveRoot } from '../../paths.js';
|
|
9
9
|
import { readInstalledEvolcoreVersion } from '../../utils/evolcore-version.js';
|
|
10
|
-
import { buildMenuClientProtectedHeaders, parseMenuResponse, validateMenuRequest, } from '../../core/command/menu-protocol.js';
|
|
10
|
+
import { buildMenuClientProtectedHeaders, parseMenuEvent, parseMenuResponse, validateMenuRequest, } from '../../core/command/menu-protocol.js';
|
|
11
11
|
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
12
12
|
const MAX_TIMEOUT_MS = 120_000;
|
|
13
13
|
const POLL_INTERVAL_MS = 200;
|
|
@@ -27,11 +27,30 @@ export class MenuClientError extends Error {
|
|
|
27
27
|
export class ManagedMenuClient {
|
|
28
28
|
send;
|
|
29
29
|
pending = new Map();
|
|
30
|
+
eventHandlers = new Map();
|
|
30
31
|
constructor(send) {
|
|
31
32
|
this.send = send;
|
|
32
33
|
}
|
|
33
34
|
accept(fromAid, payload, encrypted) {
|
|
34
|
-
if (!payload || typeof payload !== 'object'
|
|
35
|
+
if (!payload || typeof payload !== 'object')
|
|
36
|
+
return false;
|
|
37
|
+
if (payload.type === 'menu.event') {
|
|
38
|
+
if (!encrypted)
|
|
39
|
+
return true;
|
|
40
|
+
try {
|
|
41
|
+
const event = parseMenuEvent(payload);
|
|
42
|
+
const key = this.key(fromAid, event.requestId);
|
|
43
|
+
const entry = this.eventHandlers.get(key);
|
|
44
|
+
entry?.handler(event);
|
|
45
|
+
if (event.event === 'upgrade.result' && entry) {
|
|
46
|
+
clearTimeout(entry.timer);
|
|
47
|
+
this.eventHandlers.delete(key);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch { }
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (payload.type !== 'menu.response')
|
|
35
54
|
return false;
|
|
36
55
|
if (!encrypted)
|
|
37
56
|
return true;
|
|
@@ -46,6 +65,9 @@ export class ManagedMenuClient {
|
|
|
46
65
|
for (const pending of this.pending.values())
|
|
47
66
|
pending.reject(new MenuClientError('AUN_DISCONNECTED', 'AUN channel disconnected'));
|
|
48
67
|
this.pending.clear();
|
|
68
|
+
for (const entry of this.eventHandlers.values())
|
|
69
|
+
clearTimeout(entry.timer);
|
|
70
|
+
this.eventHandlers.clear();
|
|
49
71
|
}
|
|
50
72
|
async request(options) {
|
|
51
73
|
if (!isValidAid(options.fromAid) || !isValidAid(options.targetAid))
|
|
@@ -63,6 +85,14 @@ export class ManagedMenuClient {
|
|
|
63
85
|
if (this.pending.has(key))
|
|
64
86
|
throw new MenuClientError('CLIENT_BUSY', 'Request ID is already in flight for this daemon');
|
|
65
87
|
const headers = buildMenuClientProtectedHeaders({ clientName: 'ec-menu-client', clientVersion: readInstalledEvolcoreVersion(), remoteAccessToken: options.remoteAccessToken ?? options.ecwebToken });
|
|
88
|
+
if (options.onEvent && request.type === 'menu.action' && request.action === 'upgrade' && request.args?.mode === 'update') {
|
|
89
|
+
const existing = this.eventHandlers.get(key);
|
|
90
|
+
if (existing)
|
|
91
|
+
clearTimeout(existing.timer);
|
|
92
|
+
const timer = setTimeout(() => this.eventHandlers.delete(key), 6 * 60_000);
|
|
93
|
+
timer.unref?.();
|
|
94
|
+
this.eventHandlers.set(key, { handler: options.onEvent, timer });
|
|
95
|
+
}
|
|
66
96
|
const attempts = request.type === 'menu.query' || request.type === 'menu.options' ? 2 : 1;
|
|
67
97
|
let timer;
|
|
68
98
|
try {
|
|
@@ -82,6 +112,13 @@ export class ManagedMenuClient {
|
|
|
82
112
|
});
|
|
83
113
|
try {
|
|
84
114
|
const [, value] = await Promise.all([sent, response]);
|
|
115
|
+
if (value.error || !(value.data && typeof value.data === 'object' && typeof value.data.jobId === 'string')) {
|
|
116
|
+
const handler = this.eventHandlers.get(key);
|
|
117
|
+
if (handler) {
|
|
118
|
+
clearTimeout(handler.timer);
|
|
119
|
+
this.eventHandlers.delete(key);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
85
122
|
return value;
|
|
86
123
|
}
|
|
87
124
|
catch (error) {
|
|
@@ -30,7 +30,7 @@ export function menuTransportParams(method, params) {
|
|
|
30
30
|
if (method !== 'message.send' || typeof params.payload?.type !== 'string'
|
|
31
31
|
|| !params.payload.type.startsWith('menu.'))
|
|
32
32
|
return params;
|
|
33
|
-
if (params.payload.type !== 'menu.response')
|
|
33
|
+
if (params.payload.type !== 'menu.response' && params.payload.type !== 'menu.event')
|
|
34
34
|
return { ...params, ttl: MENU_MESSAGE_TTL_SECONDS };
|
|
35
35
|
const { to_device_id: target, menu_client_sdk_version: sdkVersion, menu_client_volatile_v1: capability, ...rest } = params;
|
|
36
36
|
delete rest.device_id;
|
package/dist/aun/msg/history.js
CHANGED
|
@@ -4,6 +4,7 @@ import { isTransientProtocolMessage, messageLogPath } from '../../core/message/m
|
|
|
4
4
|
import { chatDirPath, readAllJsonlLines, readJsonFile, readLastJsonlLine, scanChatDirs, scanMetaFiles } from '../../core/session/session-fs-store.js';
|
|
5
5
|
import { tryParseChannelKey } from '../../core/channel-loader.js';
|
|
6
6
|
import { resolvePaths } from '../../paths.js';
|
|
7
|
+
import { isMainSessionThreadId } from '../../core/thread/topic-id.js';
|
|
7
8
|
const DEFAULT_LIMIT = 50;
|
|
8
9
|
export const MAX_MSG_HISTORY_LIMIT = 500;
|
|
9
10
|
const SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/;
|
|
@@ -52,7 +53,16 @@ function msgHistoryByLegacyRoute(args, options) {
|
|
|
52
53
|
return error('INVALID_CHANNEL_NAME', '无效 channel 实例名');
|
|
53
54
|
}
|
|
54
55
|
const chatDir = chatDirPath(options.sessionsDir, channel, target, args.from, channel === 'aun' && !args.channelName ? undefined : `${channel}#${args.from}#${args.channelName ?? 'main'}`);
|
|
55
|
-
|
|
56
|
+
// --thread <话题id> 且未显式指定 --session 时,从 _threads/ 反查对应 sessionId,
|
|
57
|
+
// 避免默认 'latest' 锁定主会话导致话题消息被 sessionId 白名单全部过滤掉。
|
|
58
|
+
let sessionArg = args.session ?? 'latest';
|
|
59
|
+
if (args.threadId !== undefined && !isMainSessionThreadId(args.threadId) && args.session === undefined) {
|
|
60
|
+
const found = findSessionIdByThreadId(chatDir, args.threadId);
|
|
61
|
+
if (!found)
|
|
62
|
+
return error('SESSION_NOT_FOUND', `找不到话题 ${args.threadId} 对应的会话(话题必须存在于本端索引)`);
|
|
63
|
+
sessionArg = found;
|
|
64
|
+
}
|
|
65
|
+
const resolvedSession = resolveLegacySessionFilter(chatDir, args.from, target, channel, sessionArg);
|
|
56
66
|
if (!resolvedSession.ok)
|
|
57
67
|
return resolvedSession;
|
|
58
68
|
return readHistoryScopes({
|
|
@@ -165,13 +175,28 @@ function readHistoryScopes(input) {
|
|
|
165
175
|
catch (cause) {
|
|
166
176
|
return error('HISTORY_READ_FAILED', cause instanceof Error ? cause.message : String(cause));
|
|
167
177
|
}
|
|
178
|
+
// 主话题请求不受会话筛选影响(跨会话总线语义)
|
|
179
|
+
const filteringMainTopic = input.args.threadId !== undefined && isMainSessionThreadId(input.args.threadId);
|
|
168
180
|
const messages = entries
|
|
169
181
|
.filter(({ entry }) => isMessageLogEntry(entry))
|
|
170
182
|
.filter(({ entry }) => !isTransientProtocolMessage(entry))
|
|
171
|
-
|
|
172
|
-
|
|
183
|
+
// 会话筛选:主话题自动放行,其他按 sessionIds 白名单过滤
|
|
184
|
+
.filter(({ entry, sessionIds }) => {
|
|
185
|
+
if (filteringMainTopic && isMainSessionThreadId(entry.threadId))
|
|
186
|
+
return true;
|
|
187
|
+
return sessionIds === undefined || (typeof entry.sessionId === 'string' && sessionIds.has(entry.sessionId));
|
|
188
|
+
})
|
|
173
189
|
.filter(({ entry }) => input.direction === 'all' || entry.dir === input.direction)
|
|
174
|
-
|
|
190
|
+
// 话题过滤:主话题历史行可能是空值/`main` 或现行规范 id,三者都算同一条主线。
|
|
191
|
+
.filter(({ entry }) => {
|
|
192
|
+
const want = input.args.threadId;
|
|
193
|
+
if (want === undefined)
|
|
194
|
+
return true;
|
|
195
|
+
const actual = entry.threadId;
|
|
196
|
+
if (isMainSessionThreadId(want))
|
|
197
|
+
return isMainSessionThreadId(actual);
|
|
198
|
+
return actual === want;
|
|
199
|
+
})
|
|
175
200
|
.filter(({ entry }) => input.args.after === undefined || entry.ts > input.args.after)
|
|
176
201
|
.filter(({ entry }) => input.args.before === undefined || entry.ts < input.args.before)
|
|
177
202
|
.sort((left, right) => left.entry.ts - right.entry.ts || left.scope - right.scope)
|
|
@@ -251,6 +276,20 @@ function nonAunSessionBelongsToPeer(session, selfAid, channelKey, channelType, p
|
|
|
251
276
|
return false;
|
|
252
277
|
return session.metadata?.peerId === peerId;
|
|
253
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* 从 `_threads/` 扫描,找出 threadId 字段等于指定话题 id 的 session,返回其 sessionId。
|
|
281
|
+
* 用于 `--thread <话题id>` 未指定 `--session` 时自动定位对应会话,避免回落到主会话。
|
|
282
|
+
*/
|
|
283
|
+
function findSessionIdByThreadId(chatDir, threadId) {
|
|
284
|
+
for (const entry of scanMetaFiles(path.join(chatDir, '_threads'))) {
|
|
285
|
+
if (entry.includes('.fork-history'))
|
|
286
|
+
continue;
|
|
287
|
+
const session = readLastJsonlLine(path.join(chatDir, '_threads', entry));
|
|
288
|
+
if (session?.id && session.threadId === threadId)
|
|
289
|
+
return session.id;
|
|
290
|
+
}
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
254
293
|
/**
|
|
255
294
|
* `active.json` is only a cache. Session JSONL files are the authoritative
|
|
256
295
|
* history index, including archived and topic sessions, so selector reads
|
|
@@ -7,7 +7,9 @@ import { resolveRoot } from '../../paths.js';
|
|
|
7
7
|
import { normalizeGroupAid } from '../group/identity.js';
|
|
8
8
|
import { isValidAid } from '../aid/validation.js';
|
|
9
9
|
import { tryParseChannelKey } from '../../core/channel-loader.js';
|
|
10
|
-
import {
|
|
10
|
+
import { normalizeThreadOptionValue } from '../../core/thread/topic-id.js';
|
|
11
|
+
import { parseTimeArg, TIME_ARG_FORMATS } from '../../cli/cli-argv.js';
|
|
12
|
+
import { DEFAULT_FS_CAT_BYTES, MAX_FS_CAT_BYTES, FsReadBudgetError, downloadGroupFsBytes, readPersonalFsBytesWithinBudget, } from '../group/fs-download.js';
|
|
11
13
|
import { groupBan, groupBanlist, groupBlock, groupUnblock, groupBlocklist, groupReportStatus, groupMemberStatuses, 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/operations.js';
|
|
12
14
|
import { handleGroupMenu, GroupMenuError } from '../../core/command/group-menu.js';
|
|
13
15
|
import { readRolesConfig } from '../../config/roles.js';
|
|
@@ -604,17 +606,6 @@ const HISTORY_VALUE_FLAGS = new Set([
|
|
|
604
606
|
function failMsg(code, reason) {
|
|
605
607
|
return { ok: false, code, reason };
|
|
606
608
|
}
|
|
607
|
-
function parseTime(value) {
|
|
608
|
-
const trimmed = value.trim();
|
|
609
|
-
if (!trimmed)
|
|
610
|
-
return undefined;
|
|
611
|
-
if (/^-?\d+$/.test(trimmed)) {
|
|
612
|
-
const timestamp = Number(trimmed);
|
|
613
|
-
return Number.isSafeInteger(timestamp) ? timestamp : undefined;
|
|
614
|
-
}
|
|
615
|
-
const timestamp = Date.parse(trimmed);
|
|
616
|
-
return Number.isFinite(timestamp) ? timestamp : undefined;
|
|
617
|
-
}
|
|
618
609
|
/** Parse the daemon-owned msg read grammar. AUN keeps positional peer AIDs;
|
|
619
610
|
* non-AUN history is selected by its canonical channel key and native peer ID. */
|
|
620
611
|
export function resolveManagedMsgOperation(argv) {
|
|
@@ -713,22 +704,28 @@ export function resolveManagedMsgOperation(argv) {
|
|
|
713
704
|
}
|
|
714
705
|
const beforeRaw = values.get('--before');
|
|
715
706
|
const afterRaw = values.get('--after');
|
|
716
|
-
const before = beforeRaw === undefined ? undefined :
|
|
717
|
-
const after = afterRaw === undefined ? undefined :
|
|
707
|
+
const before = beforeRaw === undefined ? undefined : parseTimeArg(beforeRaw);
|
|
708
|
+
const after = afterRaw === undefined ? undefined : parseTimeArg(afterRaw);
|
|
718
709
|
if (beforeRaw !== undefined && before === undefined)
|
|
719
|
-
return failMsg('INVALID_ARGUMENT',
|
|
710
|
+
return failMsg('INVALID_ARGUMENT', `invalid --before time(支持 ${TIME_ARG_FORMATS})`);
|
|
720
711
|
if (afterRaw !== undefined && after === undefined)
|
|
721
|
-
return failMsg('INVALID_ARGUMENT',
|
|
712
|
+
return failMsg('INVALID_ARGUMENT', `invalid --after time(支持 ${TIME_ARG_FORMATS})`);
|
|
722
713
|
if (before !== undefined && after !== undefined && after >= before) {
|
|
723
714
|
return failMsg('INVALID_ARGUMENT', '--after must be earlier than --before');
|
|
724
715
|
}
|
|
725
716
|
const target = channelKey === undefined ? String(positionals[0] || '').replace(/^@/, '') : undefined;
|
|
726
|
-
const
|
|
727
|
-
|
|
717
|
+
const threadRaw = values.get('--thread');
|
|
718
|
+
let threadId;
|
|
719
|
+
if (threadRaw !== undefined) {
|
|
728
720
|
if (kind !== 'history')
|
|
729
721
|
return failMsg('INVALID_ARGUMENT', '--thread is only supported for history');
|
|
730
722
|
if (channelKey !== undefined)
|
|
731
723
|
return failMsg('INVALID_ARGUMENT', '--thread cannot be combined with --channel-key/--peer-id');
|
|
724
|
+
// 接受 `ec thread list` 打印的 `<peer>/<id>`;前缀必须就是本次查询的对端
|
|
725
|
+
const normalized = normalizeThreadOptionValue(threadRaw, peer => peer === target);
|
|
726
|
+
if (!normalized.ok)
|
|
727
|
+
return failMsg('INVALID_ARGUMENT', normalized.error);
|
|
728
|
+
threadId = normalized.threadId;
|
|
732
729
|
if (!/^thread-[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(threadId))
|
|
733
730
|
return failMsg('INVALID_ARGUMENT', 'invalid --thread id');
|
|
734
731
|
}
|
|
@@ -747,8 +744,9 @@ export function resolveManagedMsgOperation(argv) {
|
|
|
747
744
|
canonicalArgv.push('--after', values.get('--after'));
|
|
748
745
|
if (values.has('--direction'))
|
|
749
746
|
canonicalArgv.push('--direction', values.get('--direction'));
|
|
750
|
-
|
|
751
|
-
|
|
747
|
+
// canonicalArgv 进审计 hash,保留调用方原文;归一后的 id 只用于执行
|
|
748
|
+
if (threadRaw !== undefined)
|
|
749
|
+
canonicalArgv.push('--thread', threadRaw);
|
|
752
750
|
return {
|
|
753
751
|
ok: true,
|
|
754
752
|
command: {
|
|
@@ -793,7 +791,6 @@ export async function executeManagedMsgOperation(command) {
|
|
|
793
791
|
}
|
|
794
792
|
return result;
|
|
795
793
|
}
|
|
796
|
-
export const MAX_MANAGED_FS_CAT_BYTES = 256 * 1024;
|
|
797
794
|
const MAX_MANAGED_FS_HEAD_BYTES = 4096;
|
|
798
795
|
const MAX_REMOTE_PATH_LENGTH = 2048;
|
|
799
796
|
export class ManagedFsOperationError extends Error {
|
|
@@ -816,8 +813,10 @@ function positiveInteger(value, flag, max) {
|
|
|
816
813
|
if (value === undefined)
|
|
817
814
|
return undefined;
|
|
818
815
|
const number = Number(value);
|
|
819
|
-
if (!Number.
|
|
820
|
-
return failFs('INVALID_ARGUMENT',
|
|
816
|
+
if (!Number.isSafeInteger(number) || number < 1 || (max !== undefined && number > max)) {
|
|
817
|
+
return failFs('INVALID_ARGUMENT', max === undefined
|
|
818
|
+
? `${flag} must be a positive safe integer`
|
|
819
|
+
: `${flag} must be an integer from 1 to ${max}`);
|
|
821
820
|
}
|
|
822
821
|
return number;
|
|
823
822
|
}
|
|
@@ -846,7 +845,7 @@ function parseRemote(value, allowEmpty) {
|
|
|
846
845
|
}
|
|
847
846
|
return { targetAid, remotePath };
|
|
848
847
|
}
|
|
849
|
-
/** Parse the daemon-owned managed filesystem grammar
|
|
848
|
+
/** Parse the daemon-owned managed filesystem grammar; the daemon resolves the permitted target backend. */
|
|
850
849
|
export function resolveManagedFsOperation(argv) {
|
|
851
850
|
const raw = argv[0] === 'fs' ? argv.slice(1) : argv;
|
|
852
851
|
const rawKind = raw[0];
|
|
@@ -1000,7 +999,7 @@ export function resolveManagedFsOperation(argv) {
|
|
|
1000
999
|
actorAid, targetAid: remote.targetAid, remotePath: remote.remotePath, recursive, overwrite,
|
|
1001
1000
|
} };
|
|
1002
1001
|
const effectiveKind = rawKind === 'stat' && !follow ? 'lstat' : rawKind;
|
|
1003
|
-
const maxBytes = positiveInteger(values.get('--max-bytes'), '--max-bytes',
|
|
1002
|
+
const maxBytes = positiveInteger(values.get('--max-bytes'), '--max-bytes', MAX_FS_CAT_BYTES);
|
|
1004
1003
|
if (typeof maxBytes !== 'number' && maxBytes !== undefined)
|
|
1005
1004
|
return maxBytes;
|
|
1006
1005
|
const headBytes = positiveInteger(values.get('--head-bytes'), '--head-bytes', MAX_MANAGED_FS_HEAD_BYTES);
|
|
@@ -1041,14 +1040,6 @@ function parseManagedGroupAclSpec(value, requirePerms) {
|
|
|
1041
1040
|
}
|
|
1042
1041
|
return { grantee, perms };
|
|
1043
1042
|
}
|
|
1044
|
-
function numberFrom(value, keys) {
|
|
1045
|
-
for (const key of keys) {
|
|
1046
|
-
const number = Number(value?.[key]);
|
|
1047
|
-
if (Number.isFinite(number))
|
|
1048
|
-
return number;
|
|
1049
|
-
}
|
|
1050
|
-
return undefined;
|
|
1051
|
-
}
|
|
1052
1043
|
async function withClient(actorAid, fn) {
|
|
1053
1044
|
const store = await getAidStore({ slotId: SLOT.cli, aunPath: resolveRoot() });
|
|
1054
1045
|
let client;
|
|
@@ -1140,16 +1131,7 @@ export async function executeManagedFsOperationWithClient(command, client) {
|
|
|
1140
1131
|
case 'setfacl':
|
|
1141
1132
|
throw new ManagedFsOperationError('NOT_ALLOWED', 'Managed personal storage ACL changes are not supported');
|
|
1142
1133
|
case 'cat': {
|
|
1143
|
-
const
|
|
1144
|
-
const size = numberFrom(node, ['size', 'sizeBytes', 'size_bytes']);
|
|
1145
|
-
const maxBytes = Math.min(command.maxBytes ?? MAX_MANAGED_FS_CAT_BYTES, MAX_MANAGED_FS_CAT_BYTES);
|
|
1146
|
-
if (size === undefined)
|
|
1147
|
-
throw new ManagedFsOperationError('FS_SIZE_REQUIRED', 'personal file size is required before managed cat');
|
|
1148
|
-
if (size > maxBytes)
|
|
1149
|
-
throw new ManagedFsOperationError('FS_CAT_TOO_LARGE', `managed cat is limited to ${maxBytes} bytes`);
|
|
1150
|
-
const bytes = await client.storage.readBytes(command.remotePath, { owner });
|
|
1151
|
-
if (bytes.byteLength > maxBytes)
|
|
1152
|
-
throw new ManagedFsOperationError('FS_CAT_TOO_LARGE', `managed cat is limited to ${maxBytes} bytes`);
|
|
1134
|
+
const bytes = await readPersonalFsBytesWithinBudget(client, command.remotePath, command.maxBytes ?? DEFAULT_FS_CAT_BYTES, { owner });
|
|
1153
1135
|
const headBytes = Math.min(command.headBytes ?? 256, MAX_MANAGED_FS_HEAD_BYTES, bytes.byteLength);
|
|
1154
1136
|
if (bytes.subarray(0, Math.min(bytes.byteLength, 8192)).some(byte => byte === 0)) {
|
|
1155
1137
|
return {
|
|
@@ -1235,16 +1217,9 @@ export async function executeManagedFsOperationWithClient(command, client) {
|
|
|
1235
1217
|
return { ok: true, command: 'setfacl', path: ref, grantee: command.aclGrantee, removed: !!command.aclRemove, result };
|
|
1236
1218
|
}
|
|
1237
1219
|
case 'cat': {
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
if (size === undefined)
|
|
1242
|
-
throw new ManagedFsOperationError('FS_SIZE_REQUIRED', 'group file size is required before managed cat');
|
|
1243
|
-
if (size > maxBytes)
|
|
1244
|
-
throw new ManagedFsOperationError('FS_CAT_TOO_LARGE', `managed cat is limited to ${maxBytes} bytes`);
|
|
1245
|
-
const { bytes } = await downloadGroupFsBytes(client, ref);
|
|
1246
|
-
if (bytes.byteLength > maxBytes)
|
|
1247
|
-
throw new ManagedFsOperationError('FS_CAT_TOO_LARGE', `managed cat is limited to ${maxBytes} bytes`);
|
|
1220
|
+
const { bytes } = await downloadGroupFsBytes(client, ref, {
|
|
1221
|
+
maxBytes: command.maxBytes ?? DEFAULT_FS_CAT_BYTES,
|
|
1222
|
+
});
|
|
1248
1223
|
const headBytes = Math.min(command.headBytes ?? 256, MAX_MANAGED_FS_HEAD_BYTES, bytes.byteLength);
|
|
1249
1224
|
if (bytes.subarray(0, Math.min(bytes.byteLength, 8192)).some(byte => byte === 0)) {
|
|
1250
1225
|
return {
|
|
@@ -1271,6 +1246,8 @@ export async function executeManagedFsOperationWithClient(command, client) {
|
|
|
1271
1246
|
catch (error) {
|
|
1272
1247
|
if (error instanceof ManagedFsOperationError)
|
|
1273
1248
|
throw error;
|
|
1249
|
+
if (error instanceof FsReadBudgetError)
|
|
1250
|
+
throw new ManagedFsOperationError(error.code, error.message);
|
|
1274
1251
|
throw new ManagedFsOperationError('FS_OPERATION_FAILED', error instanceof Error ? error.message : String(error));
|
|
1275
1252
|
}
|
|
1276
1253
|
}
|