evolcore 0.0.12 → 0.0.14
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 +37 -0
- package/bin/codex-managed-hook.mjs +4 -1
- package/bin/ec-safe-output.js +26 -7
- package/bin/install-codex-managed-hooks.mjs +201 -0
- package/dist/agents/claude-runner.js +105 -11
- package/dist/agents/codex-app-server-client.js +176 -7
- package/dist/agents/codex-runner.js +248 -84
- package/dist/agents/ecagent-runner.js +21 -2
- package/dist/agents/gemini-runner.js +9 -4
- package/dist/aun/aid/control-aid.js +36 -13
- package/dist/aun/aid/store.js +13 -7
- package/dist/aun/group-fs-download.js +56 -0
- package/dist/aun/group-identity.js +1 -2
- package/dist/aun/msg/group.js +11 -33
- package/dist/aun/msg/index.js +1 -2
- package/dist/aun/msg/managed-operation.js +176 -55
- package/dist/aun/msg/upload.js +16 -1
- package/dist/aun/outbox.js +31 -2
- package/dist/aun/rpc/client.js +66 -0
- package/dist/aun/rpc/index.js +1 -2
- package/dist/aun/storage/{upload.js → client.js} +11 -2
- package/dist/aun/storage/index.js +1 -2
- package/dist/channels/aun.js +514 -150
- package/dist/channels/daemon.js +2 -0
- package/dist/cli/agent.js +9 -96
- package/dist/cli/aun-commands.js +1 -1
- package/dist/cli/cli-argv.js +10 -0
- package/dist/cli/config.js +9 -17
- package/dist/cli/ctl-command.js +1 -1
- package/dist/cli/daemon-commands.js +39 -24
- package/dist/cli/fs-command.js +65 -19
- package/dist/cli/init.js +77 -8
- package/dist/cli/queue-command.js +42 -0
- package/dist/cli/restart-monitor.js +3 -14
- package/dist/cli/task-context.js +172 -0
- package/dist/cli/trigger-command.js +7 -0
- package/dist/config/access-policy.js +1 -2
- package/dist/config/builtin-roles.js +7 -0
- package/dist/config/config-manager.js +27 -3
- package/dist/config/contact-book-store.js +7 -2
- package/dist/config/peer-role-resolver.js +6 -2
- package/dist/config/role-migration-startup.js +53 -0
- package/dist/config/role-store.js +1 -1
- package/dist/core/auth/agent-delegation.js +1 -1
- package/dist/core/auth/auth-gateway.js +30 -1
- package/dist/core/auth/authorization-audit.js +47 -0
- package/dist/core/auth/operation-authorizer.js +1 -0
- package/dist/core/auth/operation-catalog.js +10 -1
- package/dist/core/bootstrap-messages.js +9 -2
- package/dist/core/bootstrap-service.js +8 -1
- package/dist/core/command/command-handler.js +201 -51
- package/dist/core/command/menu-catalog.js +4 -0
- package/dist/core/command/menu-handler.js +85 -18
- package/dist/core/command/menu-protocol.js +23 -2
- package/dist/core/command/slash-handler.js +58 -10
- package/dist/core/event-catalog.js +8 -0
- package/dist/core/handoff/runtime.js +12 -1
- package/dist/core/handoff/store.js +5 -1
- package/dist/core/interaction-router.js +10 -0
- package/dist/core/message/im-renderer.js +34 -1
- package/dist/core/message/message-bridge.js +68 -13
- package/dist/core/message/message-log.js +2 -0
- package/dist/core/message/message-queue.js +267 -39
- package/dist/core/message/message-utils.js +81 -4
- package/dist/core/message/response-engine.js +242 -48
- package/dist/core/message/stream-debouncer.js +10 -2
- package/dist/core/permission/approval-gateway.js +1 -0
- package/dist/core/permission/ec-command-parser.js +649 -18
- package/dist/core/permission/index.js +1 -1
- package/dist/core/permission/mode.js +15 -0
- package/dist/core/permission/readonly-shell-query.js +148 -8
- package/dist/core/permission/sandbox-runtime.js +21 -0
- package/dist/core/permission/tool-policy.js +266 -56
- package/dist/core/protected-paths.js +107 -2
- package/dist/core/runtime-lock.js +101 -0
- package/dist/core/session/session-manager.js +21 -2
- package/dist/core/session/session-mapper.js +20 -1
- package/dist/eck/detect.js +18 -1
- package/dist/eck/group-rules-sync.js +15 -44
- package/dist/index.js +191 -89
- package/dist/ipc.js +16 -3
- package/dist/paths.js +3 -0
- package/dist/response-system/engines/v1/proactive-flow.js +92 -8
- package/dist/response-system/modes/single-session/index.js +3 -0
- package/dist/trigger/feedback.js +64 -11
- package/dist/trigger/history.js +42 -7
- package/dist/trigger/manager.js +21 -1
- package/dist/trigger/parser.js +14 -2
- package/dist/trigger/scheduler.js +1 -0
- package/dist/trigger/validation.js +67 -6
- package/dist/utils/ecweb-supervisor.js +332 -0
- package/dist/utils/error-utils.js +17 -0
- package/dist/utils/logger.js +37 -4
- package/kits/docs/evolcore/trigger.md +17 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/templates/roles/admin.json +7 -0
- package/kits/templates/roles/member.json +8 -1
- package/kits/templates/roles/visitor.json +2 -0
- package/package.json +1 -1
- package/dist/aun/msg/payload-type.js +0 -27
- package/dist/aun/rpc/caller.js +0 -42
- package/dist/aun/rpc/connection.js +0 -25
- package/dist/aun/storage/manage.js +0 -10
- package/dist/config/access-policy-domain.js +0 -18
- package/dist/config/config-batch-get.js +0 -11
- package/dist/config/owner-policy.js +0 -4
- package/dist/config/role-config-v4-startup.js +0 -32
- package/dist/config/role-config-v5-startup.js +0 -27
- package/dist/core/auth/trigger-authorization.js +0 -15
- package/dist/core/interaction-registration.js +0 -10
- package/dist/core/permission/execution-sandbox.js +0 -16
- package/dist/core/relation/peer-key.js +0 -1
- package/dist/core/session/session-key.js +0 -24
- package/dist/eck/baseagent-caps.js +0 -18
- package/dist/eck/rules-loader.js +0 -28
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export const RUNTIME_LOCK_DIR_ENV = 'EVOLCORE_RUNTIME_LOCK_DIR';
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a task-provided lock location without accepting relative or escaped
|
|
7
|
+
* paths. When no managed task context is present, callers retain their legacy
|
|
8
|
+
* on-disk lock path for daemon-side compatibility and migration tooling.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveManagedLockPath(namespace, identity, legacyPath) {
|
|
11
|
+
const configured = process.env[RUNTIME_LOCK_DIR_ENV]?.trim();
|
|
12
|
+
if (!configured || !path.isAbsolute(configured) || !namespace || !identity || !isTrustedRuntimeLockDir(configured)) {
|
|
13
|
+
return legacyPath;
|
|
14
|
+
}
|
|
15
|
+
const digest = crypto.createHash('sha256').update(identity).digest('hex').slice(0, 24);
|
|
16
|
+
return path.join(path.resolve(configured), namespace, `${digest}.lock`);
|
|
17
|
+
}
|
|
18
|
+
function isTrustedRuntimeLockDir(candidate) {
|
|
19
|
+
const resolved = path.resolve(candidate);
|
|
20
|
+
if (path.basename(resolved) !== 'evolcore-locks')
|
|
21
|
+
return false;
|
|
22
|
+
const tmpRoot = process.env.TMPDIR?.trim();
|
|
23
|
+
if (!tmpRoot || !path.isAbsolute(tmpRoot))
|
|
24
|
+
return false;
|
|
25
|
+
const resolvedTmpRoot = path.resolve(tmpRoot);
|
|
26
|
+
const relative = path.relative(resolvedTmpRoot, resolved);
|
|
27
|
+
const isWithinTmp = relative !== '..'
|
|
28
|
+
&& !relative.startsWith(`..${path.sep}`)
|
|
29
|
+
&& !path.isAbsolute(relative);
|
|
30
|
+
// The daemon supplies each task either a generic evolcore-runtime-* child
|
|
31
|
+
// or Codex's private digest directory. Both share a sibling evolcore-locks
|
|
32
|
+
// directory so mutations from separate sessions still serialize. Do not
|
|
33
|
+
// accept arbitrary TMPDIR siblings supplied by a child process.
|
|
34
|
+
const tmpBase = path.basename(resolvedTmpRoot);
|
|
35
|
+
const tmpParent = path.dirname(resolvedTmpRoot);
|
|
36
|
+
const isGenericSessionRuntime = /^evolcore-runtime-[a-f0-9]{24}$/.test(tmpBase);
|
|
37
|
+
const isCodexSessionRuntime = /^[a-f0-9]{24}$/.test(tmpBase)
|
|
38
|
+
&& /^evolcore-codex-(?:\d+|user)$/.test(path.basename(tmpParent));
|
|
39
|
+
const isManagedSessionSibling = (isGenericSessionRuntime || isCodexSessionRuntime)
|
|
40
|
+
&& path.dirname(resolved) === tmpParent;
|
|
41
|
+
if (!isWithinTmp && !isManagedSessionSibling)
|
|
42
|
+
return false;
|
|
43
|
+
try {
|
|
44
|
+
const tmpStat = fs.lstatSync(resolvedTmpRoot);
|
|
45
|
+
if (!tmpStat.isDirectory() || tmpStat.isSymbolicLink())
|
|
46
|
+
return false;
|
|
47
|
+
if (typeof process.getuid === 'function' && tmpStat.uid !== process.getuid())
|
|
48
|
+
return false;
|
|
49
|
+
if ((tmpStat.mode & 0o077) !== 0)
|
|
50
|
+
return false;
|
|
51
|
+
if (isManagedSessionSibling) {
|
|
52
|
+
const parentStat = fs.lstatSync(tmpParent);
|
|
53
|
+
if (!parentStat.isDirectory() || parentStat.isSymbolicLink())
|
|
54
|
+
return false;
|
|
55
|
+
if (typeof process.getuid === 'function' && parentStat.uid !== process.getuid())
|
|
56
|
+
return false;
|
|
57
|
+
if ((parentStat.mode & 0o077) !== 0)
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const stat = fs.lstatSync(resolved);
|
|
61
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
62
|
+
return false;
|
|
63
|
+
if (typeof process.getuid === 'function' && stat.uid !== process.getuid())
|
|
64
|
+
return false;
|
|
65
|
+
return (stat.mode & 0o077) === 0;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function ensureManagedLockParent(lockPath) {
|
|
72
|
+
const configured = process.env[RUNTIME_LOCK_DIR_ENV]?.trim();
|
|
73
|
+
if (!configured || !isTrustedRuntimeLockDir(configured)) {
|
|
74
|
+
throw new Error('managed runtime lock directory is not trusted');
|
|
75
|
+
}
|
|
76
|
+
const root = path.resolve(configured);
|
|
77
|
+
const parent = path.resolve(path.dirname(lockPath));
|
|
78
|
+
const relative = path.relative(root, parent);
|
|
79
|
+
if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
80
|
+
throw new Error('managed runtime lock parent escapes its runtime directory');
|
|
81
|
+
}
|
|
82
|
+
// Create each component individually. mkdir({recursive:true}) would follow a
|
|
83
|
+
// pre-existing symlink in a namespace component before it can be checked.
|
|
84
|
+
let current = root;
|
|
85
|
+
for (const segment of relative.split(path.sep).filter(Boolean)) {
|
|
86
|
+
current = path.join(current, segment);
|
|
87
|
+
try {
|
|
88
|
+
fs.mkdirSync(current, { mode: 0o700 });
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (error?.code !== 'EEXIST')
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
const stat = fs.lstatSync(current);
|
|
95
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()
|
|
96
|
+
|| (typeof process.getuid === 'function' && stat.uid !== process.getuid())
|
|
97
|
+
|| (stat.mode & 0o077) !== 0) {
|
|
98
|
+
throw new Error(`managed runtime lock path is not a private real directory: ${current}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -2,8 +2,7 @@ import { ensureDir } from '../../utils/atomic-write.js';
|
|
|
2
2
|
import { logger } from '../../utils/logger.js';
|
|
3
3
|
import { encodePath } from '../../utils/cross-platform.js';
|
|
4
4
|
import { chatDirPath, generateSessionId, formatTimestamp, atomicWriteJson, atomicWriteText, appendJsonl, readJsonFile, readLastJsonlLine, readAllJsonlLines, scanChatDirs, scanMetaFiles, ensureChatDir, readThreadIndex, writeThreadIndex, } from './session-fs-store.js';
|
|
5
|
-
import { sessionToFile, fileToSession } from './session-mapper.js';
|
|
6
|
-
import { formatSessionKey, DEFAULT_THREAD_ID } from './session-key.js';
|
|
5
|
+
import { sessionToFile, fileToSession, formatSessionKey, DEFAULT_THREAD_ID } from './session-mapper.js';
|
|
7
6
|
import { tryParseChannelKey } from '../channel-loader.js';
|
|
8
7
|
import { isSystemControlChannel } from '../system-channels.js';
|
|
9
8
|
import path from 'path';
|
|
@@ -597,6 +596,14 @@ export class SessionManager {
|
|
|
597
596
|
mutated = true;
|
|
598
597
|
}
|
|
599
598
|
}
|
|
599
|
+
if (chatType === 'group' && metadata?.groupId) {
|
|
600
|
+
if (!session.metadata)
|
|
601
|
+
session.metadata = {};
|
|
602
|
+
if (session.metadata.groupId !== metadata.groupId) {
|
|
603
|
+
session.metadata.groupId = metadata.groupId;
|
|
604
|
+
mutated = true;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
600
607
|
if (metadata?.peerType && session.metadata?.peerType !== metadata.peerType) {
|
|
601
608
|
if (!session.metadata)
|
|
602
609
|
session.metadata = {};
|
|
@@ -636,6 +643,9 @@ export class SessionManager {
|
|
|
636
643
|
if (metadata?.peerType) {
|
|
637
644
|
session.metadata.peerType = metadata.peerType;
|
|
638
645
|
}
|
|
646
|
+
if (chatType === 'group' && metadata?.groupId) {
|
|
647
|
+
session.metadata.groupId = metadata.groupId;
|
|
648
|
+
}
|
|
639
649
|
this.persistSession(session, 'sync');
|
|
640
650
|
return session;
|
|
641
651
|
}
|
|
@@ -702,11 +712,20 @@ export class SessionManager {
|
|
|
702
712
|
const existing = this.readMetaLatest(metaPath);
|
|
703
713
|
if (existing) {
|
|
704
714
|
const validSessionId = this.validateSessionFile(existing);
|
|
715
|
+
let mutated = false;
|
|
705
716
|
if (metadata) {
|
|
706
717
|
const creatorPeerId = existing.metadata?.peerId;
|
|
707
718
|
existing.metadata = { ...(existing.metadata || {}), ...metadata };
|
|
708
719
|
if (creatorPeerId && existing.metadata)
|
|
709
720
|
existing.metadata.peerId = creatorPeerId;
|
|
721
|
+
mutated = true;
|
|
722
|
+
}
|
|
723
|
+
if (chatType && existing.chatType !== chatType) {
|
|
724
|
+
logger.info(`[SessionManager] Updating thread chatType for session ${existing.id}: ${existing.chatType} -> ${chatType}`);
|
|
725
|
+
existing.chatType = chatType;
|
|
726
|
+
mutated = true;
|
|
727
|
+
}
|
|
728
|
+
if (mutated) {
|
|
710
729
|
this.persistSession(existing, 'none');
|
|
711
730
|
}
|
|
712
731
|
return { ...existing, agentSessionId: validSessionId };
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import { formatTimestamp } from './session-fs-store.js';
|
|
2
|
-
|
|
2
|
+
/** Default topic component used by the stable session routing key. */
|
|
3
|
+
export const DEFAULT_THREAD_ID = 'main';
|
|
4
|
+
export function formatSessionKey(channelType, channelId, threadId) {
|
|
5
|
+
const tid = threadId || DEFAULT_THREAD_ID;
|
|
6
|
+
return `${channelType}#${encodeURIComponent(channelId)}#${encodeURIComponent(tid)}`;
|
|
7
|
+
}
|
|
8
|
+
export function parseSessionKey(key) {
|
|
9
|
+
const first = key.indexOf('#');
|
|
10
|
+
if (first <= 0)
|
|
11
|
+
throw new Error(`Invalid session key: ${key}`);
|
|
12
|
+
const rest = key.slice(first + 1);
|
|
13
|
+
const second = rest.indexOf('#');
|
|
14
|
+
if (second <= 0)
|
|
15
|
+
throw new Error(`Invalid session key (missing threadId): ${key}`);
|
|
16
|
+
return {
|
|
17
|
+
channelType: key.slice(0, first),
|
|
18
|
+
channelId: decodeURIComponent(rest.slice(0, second)),
|
|
19
|
+
threadId: decodeURIComponent(rest.slice(second + 1)),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
3
22
|
export function sessionToFile(session) {
|
|
4
23
|
const metadata = {};
|
|
5
24
|
if (session.metadata) {
|
package/dist/eck/detect.js
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
|
|
3
|
+
export const BASEAGENT_CAPS = {
|
|
4
|
+
'claude-code': {
|
|
5
|
+
autoLoadsRules: true,
|
|
6
|
+
supportsSystemPrompt: true,
|
|
7
|
+
},
|
|
8
|
+
'claude': {
|
|
9
|
+
autoLoadsRules: true,
|
|
10
|
+
supportsSystemPrompt: true,
|
|
11
|
+
},
|
|
12
|
+
'codex': {
|
|
13
|
+
autoLoadsRules: false,
|
|
14
|
+
supportsSystemPrompt: true,
|
|
15
|
+
},
|
|
16
|
+
'gemini': {
|
|
17
|
+
autoLoadsRules: false,
|
|
18
|
+
supportsSystemPrompt: true,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
4
21
|
const MAX_DEPTH = 5;
|
|
5
22
|
export function resolveEckInjection(agentConfig, projectPath, kitsRulesPath) {
|
|
6
23
|
const caps = BASEAGENT_CAPS[agentConfig.baseAgent]
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { createHash } from 'crypto';
|
|
4
3
|
import { TextDecoder } from 'util';
|
|
5
4
|
import { getAidStore, isValidAid, loadClient, SLOT } from '../aun/aid/index.js';
|
|
5
|
+
import { downloadGroupFsBytes, GroupFsDownloadError } from '../aun/group-fs-download.js';
|
|
6
6
|
import { checkGroupIndex as checkGroupIndexRpc, getGroupIndex as getGroupIndexRpc } from '../aun/msg/group-index.js';
|
|
7
7
|
import { agentRelationsDir } from '../paths.js';
|
|
8
8
|
import { formatPeerKey } from '../core/relation/peer-identity.js';
|
|
@@ -220,7 +220,9 @@ function lazyGroupClient(selfAid) {
|
|
|
220
220
|
client: async () => {
|
|
221
221
|
if (client)
|
|
222
222
|
return client;
|
|
223
|
-
|
|
223
|
+
// This short-lived RPC client must never share the daemon's gateway
|
|
224
|
+
// cursor. A short connection can perform a background pull on connect.
|
|
225
|
+
store = await getAidStore({ slotId: SLOT.groupRules });
|
|
224
226
|
client = await loadClient(store, selfAid);
|
|
225
227
|
await client.connect({ connection_kind: 'short', short_ttl_ms: 30000, auto_reconnect: false });
|
|
226
228
|
return client;
|
|
@@ -242,39 +244,15 @@ function lazyGroupClient(selfAid) {
|
|
|
242
244
|
};
|
|
243
245
|
}
|
|
244
246
|
async function downloadGroupFileBytes(client, remoteRef) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
throw statusError('unreadable', 'group.fs.create_download_ticket returned invalid response');
|
|
248
|
-
}
|
|
249
|
-
const downloadUrl = stringField(ticket, ['download_url', 'url']);
|
|
250
|
-
if (!downloadUrl) {
|
|
251
|
-
throw statusError('unreadable', 'group.fs.create_download_ticket did not return download_url');
|
|
252
|
-
}
|
|
253
|
-
const bytes = await client.group.fs.lowlevel.httpGet(downloadUrl, bearerHeaders(client));
|
|
254
|
-
const expectedSha = stringField(ticket, ['sha256']);
|
|
255
|
-
if (expectedSha && sha256Hex(bytes).toLowerCase() !== expectedSha.toLowerCase()) {
|
|
256
|
-
throw statusError('unreadable', 'download hash verification failed');
|
|
247
|
+
try {
|
|
248
|
+
return (await downloadGroupFsBytes(client, remoteRef)).bytes;
|
|
257
249
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
function bearerHeaders(client) {
|
|
264
|
-
const token = accessTokenFromClient(client);
|
|
265
|
-
return token ? { Authorization: `Bearer ${token}` } : undefined;
|
|
266
|
-
}
|
|
267
|
-
function accessTokenFromClient(client) {
|
|
268
|
-
const anyClient = client;
|
|
269
|
-
if (typeof anyClient.getAccessToken === 'function') {
|
|
270
|
-
const token = stringValue(anyClient.getAccessToken());
|
|
271
|
-
if (token)
|
|
272
|
-
return token;
|
|
250
|
+
catch (error) {
|
|
251
|
+
if (error instanceof GroupFsDownloadError) {
|
|
252
|
+
throw statusError('unreadable', error.message);
|
|
253
|
+
}
|
|
254
|
+
throw error;
|
|
273
255
|
}
|
|
274
|
-
return stringValue(anyClient.accessToken)
|
|
275
|
-
|| stringValue(anyClient.access_token)
|
|
276
|
-
|| stringValue(anyClient._identity?.access_token)
|
|
277
|
-
|| stringValue(anyClient._sessionParams?.access_token);
|
|
278
256
|
}
|
|
279
257
|
function assertUtf8(bytes) {
|
|
280
258
|
new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
@@ -302,6 +280,10 @@ function classifyError(e) {
|
|
|
302
280
|
if (e instanceof StatusError)
|
|
303
281
|
return e.status;
|
|
304
282
|
const msg = errorMessage(e).toLowerCase();
|
|
283
|
+
// A peer cache miss is a signer/identity lookup failure, not a missing
|
|
284
|
+
// rules file.
|
|
285
|
+
if (msg.includes('peer not found in cache'))
|
|
286
|
+
return 'unreadable';
|
|
305
287
|
if (msg.includes('forbidden') || msg.includes('unauthorized') || msg.includes('permission'))
|
|
306
288
|
return 'forbidden';
|
|
307
289
|
if (msg.includes('not found') || msg.includes('does not exist'))
|
|
@@ -314,17 +296,6 @@ function errorMessage(e) {
|
|
|
314
296
|
function isRecord(x) {
|
|
315
297
|
return typeof x === 'object' && x !== null && !Array.isArray(x);
|
|
316
298
|
}
|
|
317
|
-
function stringValue(x) {
|
|
318
|
-
return typeof x === 'string' ? x : '';
|
|
319
|
-
}
|
|
320
|
-
function stringField(obj, keys) {
|
|
321
|
-
for (const k of keys) {
|
|
322
|
-
const v = obj[k];
|
|
323
|
-
if (typeof v === 'string')
|
|
324
|
-
return v;
|
|
325
|
-
}
|
|
326
|
-
return undefined;
|
|
327
|
-
}
|
|
328
299
|
function numberField(obj, keys) {
|
|
329
300
|
for (const k of keys) {
|
|
330
301
|
const v = obj[k];
|