evolcore 0.0.33 → 0.0.35-test.0
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 +59 -0
- package/bin/ec.js +3 -2
- package/bin/install-codex-managed-hooks.mjs +38 -4
- package/dist/agents/baseagent-availability.js +20 -36
- package/dist/agents/claude-runner.js +93 -14
- package/dist/agents/codex/diagnostics.js +79 -22
- package/dist/agents/codex-runner.js +245 -47
- package/dist/agents/ecagent-runner.js +90 -27
- package/dist/agents/runner-types.js +5 -6
- package/dist/aun/aid/agentmd.js +13 -2
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/msg/managed-operation.js +16 -0
- package/dist/aun/msg/p2p.js +18 -2
- package/dist/channels/aun.js +123 -7
- package/dist/channels/daemon.js +6 -3
- package/dist/cli/aun-commands.js +39 -14
- package/dist/cli/ctl-command.js +3 -1
- package/dist/cli/daemon-commands.js +130 -23
- package/dist/cli/fs-command.js +2 -1
- package/dist/cli/getmeta-command.js +211 -0
- package/dist/cli/index.js +26 -6
- package/dist/cli/integrity-command.js +196 -0
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/memory-command.js +200 -156
- package/dist/cli/thread-command.js +17 -7
- package/dist/cli/trigger-command.js +6 -1
- package/dist/config/config-field-policy.js +4 -0
- package/dist/config/config-manager.js +19 -0
- package/dist/config/contact-book-store.js +5 -0
- package/dist/config/resolved-config-op.js +25 -8
- package/dist/config/role-service.js +13 -0
- package/dist/config/runtime-env.js +16 -0
- package/dist/config-store.js +30 -0
- package/dist/core/audit/log-integrity.js +3 -1
- package/dist/core/auth/agent-delegation.js +108 -35
- package/dist/core/auth/delegation-diagnostics.js +414 -0
- package/dist/core/auth/operation-authorizer.js +22 -3
- package/dist/core/auth/operation-catalog.js +16 -0
- package/dist/core/channel-runtime.js +1 -1
- package/dist/core/command/agent-control.js +18 -26
- package/dist/core/command/command-handler.js +145 -5
- package/dist/core/command/daemon-diagnostics.js +1 -0
- package/dist/core/command/menu-catalog.js +3 -0
- package/dist/core/command/menu-handler.js +24 -7
- package/dist/core/command/menu-protocol.js +1 -0
- package/dist/core/command/slash-handler.js +2 -0
- package/dist/core/command/topic-slash.js +4 -7
- package/dist/core/daemon-bootstrap.js +4 -7
- package/dist/core/event-catalog.js +14 -0
- package/dist/core/file-integrity/read.js +61 -0
- package/dist/core/file-integrity/store.js +262 -0
- package/dist/core/ipc-registration.js +47 -2
- package/dist/core/memory/batch-meta.js +133 -0
- package/dist/core/memory/consolidation-context.js +132 -28
- package/dist/core/memory/consolidation-ops-records.js +14 -16
- package/dist/core/memory/consolidation-trigger.js +15 -7
- package/dist/core/memory/consolidation.js +146 -26
- package/dist/core/memory/index-manager.js +19 -28
- package/dist/core/memory/memory-admin.js +19 -14
- package/dist/core/memory/memory-document.js +53 -9
- package/dist/core/memory/memory-writer.js +50 -19
- package/dist/core/message/aun-ipc-sender.js +19 -1
- package/dist/core/message/im-renderer.js +20 -4
- package/dist/core/message/message-bridge.js +5 -4
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/peer-mode.js +17 -0
- package/dist/core/message/response-engine.js +598 -50
- package/dist/core/permission/ec-command-parser.js +169 -44
- package/dist/core/permission/protected-paths.js +5 -1
- package/dist/core/permission/readonly-shell-query.js +43 -5
- package/dist/core/permission/session-path-grants.js +334 -0
- package/dist/core/permission/tool-canonical.js +226 -0
- package/dist/core/permission/tool-policy.js +60 -62
- package/dist/core/relation/peer-identity.js +87 -12
- package/dist/core/role/runtime-policy.js +21 -10
- package/dist/core/session/session-fs-store.js +123 -8
- package/dist/core/session/session-manager.js +131 -6
- package/dist/core/session/session-title.js +26 -1
- package/dist/core/skill/index-generator.js +17 -3
- package/dist/core/system-session/grants.js +40 -0
- package/dist/core/system-session/memory.js +289 -0
- package/dist/core/system-session/registry.js +74 -0
- package/dist/core/system-session/types.js +14 -0
- package/dist/core/thread/command-parser.js +6 -1
- package/dist/core/thread/ipc-operation.js +33 -16
- package/dist/core/thread/thread-operation.js +62 -24
- package/dist/core/thread/thread-service.js +258 -51
- package/dist/eck/kit-renderer.js +7 -2
- package/dist/eck/manifest-engine.js +24 -4
- package/dist/index.js +11 -2
- package/dist/ipc.js +51 -40
- package/dist/paths.js +70 -0
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/feedback.js +6 -1
- package/dist/trigger/parser.js +8 -1
- package/dist/trigger/system-definitions.js +3 -3
- package/dist/utils/cross-platform.js +17 -5
- package/dist/utils/error-dict.json +18 -4
- package/dist/utils/error-utils.js +121 -0
- package/dist/utils/instance-registry.js +13 -8
- package/dist/utils/log-writer.js +12 -0
- package/dist/utils/process-introspect.js +11 -0
- package/kits/docs/context-assembly.md +0 -1
- package/kits/docs/evolcore/agent-create.md +5 -4
- package/kits/docs/evolcore/agent.md +2 -2
- package/kits/docs/evolcore/config.md +2 -2
- package/kits/docs/evolcore/ctl.md +2 -2
- package/kits/docs/evolcore/ec-capabilities.md +1 -1
- package/kits/docs/evolcore/fs-architecture.md +9 -10
- package/kits/docs/evolcore/fs.md +2 -2
- package/kits/docs/evolcore/group-collaboration.md +3 -3
- package/kits/docs/evolcore/group.md +14 -12
- package/kits/docs/evolcore/memory-architecture.md +4 -5
- package/kits/docs/evolcore/memory-consolidation.md +19 -16
- package/kits/docs/evolcore/memory-tags.md +3 -1
- package/kits/docs/evolcore/memory.md +24 -12
- package/kits/docs/evolcore/model.md +16 -0
- package/kits/docs/evolcore/msg.md +7 -7
- package/kits/docs/evolcore/stats.md +1 -1
- package/kits/docs/evolcore/thread-chain.md +30 -14
- package/kits/docs/prompt-loading-architecture.md +0 -1
- package/kits/eck_manifest.auxiliary.json +9 -0
- package/kits/eck_manifest.json +20 -20
- package/kits/eck_manifest.memory.json +20 -12
- package/kits/rules/01-overview.md +11 -0
- package/kits/rules/03-identity.md +6 -3
- package/kits/rules/06-channel.md +1 -1
- package/kits/rules/07-memory-and-thread.md +13 -16
- package/kits/schemas/_meta.json +7 -1
- package/kits/schemas/agent-config.schema.13.json +1 -1
- package/kits/schemas/daemon.schema.7.json +140 -0
- package/kits/skills/ec-group/SKILL.md +8 -5
- package/kits/skills/ec-skill/scripts/cli.mjs +1 -1
- package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
- package/kits/skills/thread-ops/SKILL.md +18 -18
- package/kits/templates/memory-consolidation/forget.md +2 -2
- package/kits/templates/memory-consolidation/generalize.md +4 -4
- package/kits/templates/memory-consolidation/notes.md +12 -12
- package/kits/templates/memory-consolidation/profile.md +3 -3
- package/kits/templates/memory-consolidation/promote.md +3 -3
- package/kits/templates/memory-consolidation/workflow.md +42 -7
- package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
- package/kits/templates/system-fragments/bash-description.md +9 -0
- package/kits/templates/system-fragments/bootstrap.md +4 -3
- package/kits/templates/system-fragments/channel.md +2 -2
- package/kits/templates/system-fragments/memory.md +16 -9
- package/kits/templates/system-fragments/reply.md +6 -6
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +4 -3
- package/dist/core/memory/memory-session-policy.js +0 -41
- package/dist/core/memory/memory-signature.js +0 -257
|
@@ -7,6 +7,7 @@ import { resolveRoot } from '../../paths.js';
|
|
|
7
7
|
import { containsHClassReference, containsAgentScopeReference, containsLClassReference, checkProtectedPathAccess, getExistingHClassMaskTargets, hClassGrantIncludesProtectedPath, isHClassPath, isSameOrDescendant, isLClassPath, lClassGrantIncludesProtectedPath, resolveProtectedCandidate, resolveProtectedCandidateWithoutFinalSymlink, } from './protected-paths.js';
|
|
8
8
|
import { classifyEvolcoreShellCommand, hasCodexCmdCarrierEcIntent, hasPowerShellCredentialAssignment, normalizeEvolcoreCommandArgv, parseCodexToolCommand, parseBoundedOutputShellCommand, parseShellApplyPatchInvocation, parseLiteralShellArgv, resolveCodexShellCarrierArgv, resolveCodexShellCarrierString, } from './ec-command-parser.js';
|
|
9
9
|
import { analyzeReadonlyShellQuery, } from './readonly-shell-query.js';
|
|
10
|
+
import { applyPatchChangePaths, collectFileChangePaths } from './tool-canonical.js';
|
|
10
11
|
/** Resolve the session-owned temporary root supplied by the execution host. */
|
|
11
12
|
export function resolveManagedTempDir(env = process.env) {
|
|
12
13
|
const raw = typeof env.TMPDIR === 'string' ? env.TMPDIR.trim() : '';
|
|
@@ -1438,32 +1439,41 @@ function globReadScope(input, options) {
|
|
|
1438
1439
|
? resolveProtectedCandidate(prefix || path.parse(pattern).root)
|
|
1439
1440
|
: resolveProtectedCandidate(prefix || '.', base);
|
|
1440
1441
|
}
|
|
1441
|
-
function protectedReadToolStaysInWorkspace(toolName, input, projectPath) {
|
|
1442
|
+
function protectedReadToolStaysInWorkspace(toolName, input, projectPath, grantedRoots = [], excludeWorkspace = false) {
|
|
1442
1443
|
const workspace = resolveProtectedCandidate(projectPath);
|
|
1444
|
+
// A granted root is an explicitly authorized read boundary for this session
|
|
1445
|
+
// (see session-path-grants.ts). Bash already honours its own managed temp
|
|
1446
|
+
// root; read tools must honour the same table or a session cannot read
|
|
1447
|
+
// files the policy layer granted it.
|
|
1448
|
+
// excludeWorkspace:系统会话的 cwd 只用于解析相对路径,本身不是可读根。
|
|
1449
|
+
const withinBoundary = (candidate) => (!excludeWorkspace && isSameOrDescendant(candidate, workspace))
|
|
1450
|
+
|| grantedRoots.some(root => isSameOrDescendant(candidate, root));
|
|
1443
1451
|
if (toolName === 'Read') {
|
|
1444
1452
|
if (typeof input.file_path !== 'string' || !input.file_path)
|
|
1445
1453
|
return false;
|
|
1446
|
-
return
|
|
1454
|
+
return withinBoundary(resolveProtectedCandidate(input.file_path, projectPath));
|
|
1447
1455
|
}
|
|
1448
1456
|
if (toolName === 'Grep') {
|
|
1449
1457
|
const scope = typeof input.path === 'string' && input.path ? input.path : '.';
|
|
1450
|
-
return
|
|
1458
|
+
return withinBoundary(resolveProtectedCandidate(scope, projectPath));
|
|
1451
1459
|
}
|
|
1452
1460
|
if (toolName === 'Glob') {
|
|
1453
1461
|
const baseValue = typeof input.path === 'string' && input.path ? input.path : '.';
|
|
1454
1462
|
const base = resolveProtectedCandidate(baseValue, projectPath);
|
|
1455
|
-
if (!
|
|
1463
|
+
if (!withinBoundary(base))
|
|
1456
1464
|
return false;
|
|
1457
1465
|
const scope = globReadScope(input, { cwd: projectPath });
|
|
1458
|
-
return
|
|
1466
|
+
return withinBoundary(scope);
|
|
1459
1467
|
}
|
|
1460
1468
|
return true;
|
|
1461
1469
|
}
|
|
1462
|
-
function readonlyShellStaysInWorkspace(analysis, projectPath,
|
|
1470
|
+
function readonlyShellStaysInWorkspace(analysis, projectPath, readRoots = [], excludeWorkspace = false) {
|
|
1463
1471
|
if (analysis.kind !== 'proven-readonly')
|
|
1464
1472
|
return false;
|
|
1465
1473
|
const workspace = resolveProtectedCandidate(projectPath);
|
|
1466
|
-
const
|
|
1474
|
+
const roots = readRoots
|
|
1475
|
+
.filter(root => typeof root === 'string' && path.isAbsolute(root))
|
|
1476
|
+
.map(root => resolveProtectedCandidate(root));
|
|
1467
1477
|
return analysis.ir.pipelines.flatMap(pipeline => pipeline.flatMap(command => command.pathOperands.map(operand => ({
|
|
1468
1478
|
operand,
|
|
1469
1479
|
accessKind: command.accessKind ?? (operand.access === 'recursive' ? 'enumerate' : 'content'),
|
|
@@ -1471,8 +1481,8 @@ function readonlyShellStaysInWorkspace(analysis, projectPath, managedTempDir) {
|
|
|
1471
1481
|
const candidate = accessKind === 'metadata' && operand.access === 'exact'
|
|
1472
1482
|
? resolveProtectedCandidateWithoutFinalSymlink(operand.value, projectPath)
|
|
1473
1483
|
: resolveProtectedCandidate(operand.value, projectPath);
|
|
1474
|
-
return isSameOrDescendant(candidate, workspace)
|
|
1475
|
-
|| (
|
|
1484
|
+
return (!excludeWorkspace && isSameOrDescendant(candidate, workspace))
|
|
1485
|
+
|| roots.some(root => isSameOrDescendant(candidate, root));
|
|
1476
1486
|
})());
|
|
1477
1487
|
}
|
|
1478
1488
|
const READONLY_SOURCE_ROOTS = new Set([
|
|
@@ -1571,10 +1581,15 @@ export function checkReadonly(toolName, input, projectPath, context) {
|
|
|
1571
1581
|
'Read', 'Glob', 'Grep', 'WebSearch', 'WebFetch',
|
|
1572
1582
|
'TaskList', 'TaskGet', 'ToolSearch',
|
|
1573
1583
|
]);
|
|
1584
|
+
// 会话私有临时根在授权表之外恒为可读:runner 自己的临时产物都落在这里。
|
|
1585
|
+
const grantedRoots = [...new Set([
|
|
1586
|
+
...(context?.grantedReadRoots ?? []),
|
|
1587
|
+
...(context?.managedTempDir ? [context.managedTempDir] : []),
|
|
1588
|
+
])];
|
|
1574
1589
|
if (readOnlyTools.has(toolName)) {
|
|
1575
1590
|
const paths = protectedReadToolPaths(toolName, input);
|
|
1576
1591
|
const pathOptions = { cwd: projectPath, root: context?.root, selfAid: context?.selfAid };
|
|
1577
|
-
if (!protectedReadToolStaysInWorkspace(toolName, input, projectPath)) {
|
|
1592
|
+
if (!protectedReadToolStaysInWorkspace(toolName, input, projectPath, grantedRoots, context?.excludeWorkspace === true)) {
|
|
1578
1593
|
return { behavior: 'deny', message: '🔒 只读模式:文件读取范围不能越出当前项目目录', policyCode: 'readonly_workspace_escape' };
|
|
1579
1594
|
}
|
|
1580
1595
|
const directHPath = paths.find(filePath => containsHClassReference(filePath, pathOptions)
|
|
@@ -1626,7 +1641,7 @@ export function checkReadonly(toolName, input, projectPath, context) {
|
|
|
1626
1641
|
if (protectedOperands.analysis.kind === 'proven-readonly'
|
|
1627
1642
|
&& !protectedOperands.hClass
|
|
1628
1643
|
&& !protectedOperands.lClass
|
|
1629
|
-
&& readonlyShellStaysInWorkspace(protectedOperands.analysis, projectPath,
|
|
1644
|
+
&& readonlyShellStaysInWorkspace(protectedOperands.analysis, projectPath, grantedRoots, context?.excludeWorkspace === true)) {
|
|
1630
1645
|
return { behavior: 'allow' };
|
|
1631
1646
|
}
|
|
1632
1647
|
if (protectedOperands.hClass) {
|
|
@@ -1816,53 +1831,6 @@ function collectFilesystemWriteGrantPaths(value, output, projectRootSubpaths) {
|
|
|
1816
1831
|
}
|
|
1817
1832
|
}
|
|
1818
1833
|
}
|
|
1819
|
-
function collectExplicitFileChangePaths(record, output) {
|
|
1820
|
-
const explicitPathKeys = ['path', 'filePath', 'file_path', 'movePath', 'move_path', 'destinationPath', 'targetPath'];
|
|
1821
|
-
const hasExplicitPath = explicitPathKeys.some(key => typeof record[key] === 'string');
|
|
1822
|
-
for (const key of explicitPathKeys) {
|
|
1823
|
-
if (typeof record[key] === 'string' && record[key])
|
|
1824
|
-
output.push(record[key]);
|
|
1825
|
-
}
|
|
1826
|
-
if (record.kind && typeof record.kind === 'object') {
|
|
1827
|
-
collectExplicitFileChangePaths(record.kind, output);
|
|
1828
|
-
}
|
|
1829
|
-
return hasExplicitPath;
|
|
1830
|
-
}
|
|
1831
|
-
function collectFileChangePaths(value, output) {
|
|
1832
|
-
if (Array.isArray(value)) {
|
|
1833
|
-
for (const entry of value) {
|
|
1834
|
-
if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
|
|
1835
|
-
collectExplicitFileChangePaths(entry, output);
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
1838
|
-
return;
|
|
1839
|
-
}
|
|
1840
|
-
if (!value || typeof value !== 'object')
|
|
1841
|
-
return;
|
|
1842
|
-
const record = value;
|
|
1843
|
-
const hasExplicitPath = collectExplicitFileChangePaths(record, output);
|
|
1844
|
-
// Claude-style inputs are maps keyed by path; Codex v2 uses an array of
|
|
1845
|
-
// records with an explicit path field. Only treat keys as paths for the map
|
|
1846
|
-
// shape so diff text and metadata never become path candidates.
|
|
1847
|
-
if (!hasExplicitPath) {
|
|
1848
|
-
for (const [filePath, change] of Object.entries(record)) {
|
|
1849
|
-
if (filePath !== 'kind' && filePath !== 'type')
|
|
1850
|
-
output.push(filePath);
|
|
1851
|
-
if (change && typeof change === 'object' && !Array.isArray(change)) {
|
|
1852
|
-
collectExplicitFileChangePaths(change, output);
|
|
1853
|
-
}
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
|
-
function applyPatchChangePaths(changes) {
|
|
1858
|
-
const paths = [];
|
|
1859
|
-
for (const change of changes) {
|
|
1860
|
-
paths.push(change.path);
|
|
1861
|
-
if (change.movePath)
|
|
1862
|
-
paths.push(change.movePath);
|
|
1863
|
-
}
|
|
1864
|
-
return paths;
|
|
1865
|
-
}
|
|
1866
1834
|
function applyPatchWorkspaceEscape(changes, context) {
|
|
1867
1835
|
const workspace = resolveProtectedCandidate(context.workspacePath ?? context.projectPath);
|
|
1868
1836
|
const managedTempDir = context.managedTempDir
|
|
@@ -2066,7 +2034,7 @@ export function checkHClassWrite(toolName, input, context) {
|
|
|
2066
2034
|
}
|
|
2067
2035
|
else if (normalized.includes('/backups/config/') || normalized.includes('/.snapshots/')) {
|
|
2068
2036
|
suggestion = '\n\n💡 快照由系统自动管理,请使用:\n' +
|
|
2069
|
-
|
|
2037
|
+
` • 创建快照:ec config snapshot --desc '<说明>'\n` +
|
|
2070
2038
|
' • 查看历史:ec config history\n' +
|
|
2071
2039
|
' • 恢复快照:ec config restore <version>';
|
|
2072
2040
|
}
|
|
@@ -2346,6 +2314,36 @@ function prepareBoundedOutputInput(input, command, kind, context) {
|
|
|
2346
2314
|
reason: kind === 'ec' ? 'ec-command' : 'host-process-command',
|
|
2347
2315
|
};
|
|
2348
2316
|
}
|
|
2317
|
+
const BASH_RISK_LEVELS = new Set(['低', '中', '高']);
|
|
2318
|
+
/**
|
|
2319
|
+
* Bash 调用的 description 必须是「<命令目标描述> [<风险等级>]」。
|
|
2320
|
+
* 只适用于 Bash 工具 schema 带 description 字段的基座(Claude Code、ecagent),
|
|
2321
|
+
* 是调用格式约定而非权限判定,不区分会话类型与 permissionMode。
|
|
2322
|
+
*/
|
|
2323
|
+
export function checkBashDescription(toolName, input) {
|
|
2324
|
+
if (toolName !== 'Bash')
|
|
2325
|
+
return undefined;
|
|
2326
|
+
const FORMAT_HINT = 'description 格式为「<命令目标描述> [<风险等级>]」,风险等级只能是 [低]、[中]、[高] 之一。示例:"查看工作区未提交的改动 [低]"、"安装项目依赖 [中]"、"推送 main 分支到远端 [高]"。这是格式问题,不是权限问题,补全后重试。';
|
|
2327
|
+
const deny = (problem, policyCode) => ({
|
|
2328
|
+
behavior: 'deny',
|
|
2329
|
+
input,
|
|
2330
|
+
message: `Bash 调用被拒:${problem}。${FORMAT_HINT}`,
|
|
2331
|
+
policyCode,
|
|
2332
|
+
});
|
|
2333
|
+
const description = typeof input.description === 'string' ? input.description.trim() : '';
|
|
2334
|
+
if (!description) {
|
|
2335
|
+
return deny('缺少 description,既没有命令目标描述,也没有风险等级标记', 'bash_description_missing');
|
|
2336
|
+
}
|
|
2337
|
+
const tag = description.match(/\[([^[\]]*)\]$/);
|
|
2338
|
+
if (!tag)
|
|
2339
|
+
return deny('description 末尾缺少风险等级标记', 'bash_risk_level_missing');
|
|
2340
|
+
if (!BASH_RISK_LEVELS.has(tag[1]))
|
|
2341
|
+
return deny(`风险等级标记 [${tag[1]}] 无效`, 'bash_risk_level_invalid');
|
|
2342
|
+
if (!description.slice(0, tag.index).trim()) {
|
|
2343
|
+
return deny('description 缺少命令目标描述,只有风险等级标记', 'bash_description_target_missing');
|
|
2344
|
+
}
|
|
2345
|
+
return undefined;
|
|
2346
|
+
}
|
|
2349
2347
|
/** Shared fail-closed preflight used before runner-specific mode and approval handling. */
|
|
2350
2348
|
export function evaluateToolPreflight(toolName, input, context) {
|
|
2351
2349
|
let applyPatchFileChanges;
|
|
@@ -2602,7 +2600,7 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
2602
2600
|
? '🔒 EC 命令包含无法安全解析的 PowerShell 展开;临时路径仅支持已登记的当前会话 $env:TMPDIR 或 ${env:TMPDIR},也可使用完整路径。纯文字请使用单引号,或用 PowerShell 反引号转义 $ 和正文内的双引号。'
|
|
2603
2601
|
: shellDialect === 'cmd'
|
|
2604
2602
|
? '🔒 EC 命令包含未转义的 cmd.exe 展开;请不要使用 %VAR%、!VAR! 或 ^ 转义。cmd.exe 传给 ec 时必须保持原文字面量'
|
|
2605
|
-
: '🔒 EC
|
|
2603
|
+
: '🔒 EC 自由文本参数(正文、--desc、--name、--keywords 等)一律用单引号包住;双引号内的反引号与 $ 会被 shell 展开。正文含单引号时改用 $\'…\' 语法(支持 \\n \\t \\\\ \\\')。';
|
|
2606
2604
|
return {
|
|
2607
2605
|
behavior: 'deny',
|
|
2608
2606
|
input,
|
|
@@ -2612,10 +2610,10 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
2612
2610
|
}
|
|
2613
2611
|
if (ecCommand.issue === 'invalid-quote') {
|
|
2614
2612
|
const message = shellDialect === 'powershell'
|
|
2615
|
-
? '🔒 EC
|
|
2613
|
+
? '🔒 EC 命令引号未闭合或存在错误嵌套。Note: PowerShell 把 U+2018/U+2019/U+201A/U+201B 也当引号定界符——正文里出现这四个字符同样会报此错。正文一律用半角单引号(ASCII 0x27)包住,正文内部的半角撇号用 `ʼ`(U+02BC)或 `'`(U+FF07)代替,或把消息正文写入当前 $TMPDIR 后使用 --text-from-file'
|
|
2616
2614
|
: shellDialect === 'cmd'
|
|
2617
2615
|
? '🔒 EC 命令引号未闭合或存在错误嵌套;cmd.exe carrier 的命令正文请保持一个完整参数,不要用未配对的双引号'
|
|
2618
|
-
: '🔒 EC
|
|
2616
|
+
: '🔒 EC 命令引号未闭合或存在错误嵌套。自由文本参数用单引号包住;正文含单引号时改用 $\'…\' 语法,内部用 \\\' 转义,如 $\'it\\\'s fine\'。';
|
|
2619
2617
|
return {
|
|
2620
2618
|
behavior: 'deny',
|
|
2621
2619
|
input,
|
|
@@ -1,3 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 解析 agent.md 的 YAML frontmatter。
|
|
3
|
+
*
|
|
4
|
+
* agent.md 形如:
|
|
5
|
+
* ---
|
|
6
|
+
* aid: xxx
|
|
7
|
+
* type: human
|
|
8
|
+
* ...
|
|
9
|
+
* ---
|
|
10
|
+
* <正文>
|
|
11
|
+
*
|
|
12
|
+
* 返回整份 frontmatter(不是只挑 type/name),调用方据此写完整画像。
|
|
13
|
+
* 文档不合法(无 frontmatter、YAML 解析失败)时返回空对象——身份缓存
|
|
14
|
+
* 不应因为一个可选的元数据块解析失败而整体失效。
|
|
15
|
+
*/
|
|
16
|
+
export function parseAgentMdFrontmatter(content) {
|
|
17
|
+
const text = content.replace(/^\uFEFF/, '');
|
|
18
|
+
const lines = text.split(/\r\n|\n|\r/);
|
|
19
|
+
if (lines[0]?.trim() !== '---')
|
|
20
|
+
return {};
|
|
21
|
+
const end = lines.slice(1).findIndex(line => line.trim() === '---');
|
|
22
|
+
if (end < 0)
|
|
23
|
+
return {};
|
|
24
|
+
try {
|
|
25
|
+
const parsed = parseYaml(lines.slice(1, end + 1).join('\n'));
|
|
26
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
27
|
+
? parsed
|
|
28
|
+
: {};
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 兼容旧缓存文件:字段从 `isAgent` 改名为 `isMachine` 之前写下的
|
|
36
|
+
* peer-identity.json 只有 isAgent。两者语义相同(type !== 'human'),
|
|
37
|
+
* 缓存有效期长达 30 天,不能指望靠下一次刷新自然迁移——
|
|
38
|
+
* 读取时直接归一,缺字段就用旧字段或 type 推出。
|
|
39
|
+
*/
|
|
40
|
+
function normalizeCachedIdentity(raw) {
|
|
41
|
+
if (!raw || typeof raw !== 'object')
|
|
42
|
+
return null;
|
|
43
|
+
const value = raw;
|
|
44
|
+
const type = typeof value.type === 'string' ? value.type : 'unknown';
|
|
45
|
+
const isMachine = typeof value.isMachine === 'boolean'
|
|
46
|
+
? value.isMachine
|
|
47
|
+
: typeof value.isAgent === 'boolean'
|
|
48
|
+
? value.isAgent
|
|
49
|
+
: type !== 'human';
|
|
50
|
+
return { ...value, type, isMachine, isAgent: isMachine };
|
|
51
|
+
}
|
|
1
52
|
/**
|
|
2
53
|
* PeerIdentityCache - 对端身份缓存管理
|
|
3
54
|
*
|
|
@@ -13,6 +64,7 @@
|
|
|
13
64
|
import * as fs from 'fs';
|
|
14
65
|
import * as path from 'path';
|
|
15
66
|
import * as crypto from 'crypto';
|
|
67
|
+
import { parse as parseYaml } from 'yaml';
|
|
16
68
|
import { logger } from '../../utils/logger.js';
|
|
17
69
|
import { agentMdPath } from '../../paths.js';
|
|
18
70
|
/**
|
|
@@ -56,7 +108,7 @@ export class PeerIdentityCache {
|
|
|
56
108
|
const filePath = this.getFilePath(channelType, peerId, agentDir);
|
|
57
109
|
try {
|
|
58
110
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
59
|
-
return JSON.parse(content);
|
|
111
|
+
return normalizeCachedIdentity(JSON.parse(content));
|
|
60
112
|
}
|
|
61
113
|
catch {
|
|
62
114
|
return null;
|
|
@@ -87,6 +139,10 @@ export class PeerIdentityCache {
|
|
|
87
139
|
const cached = this.get(channelType, peerId, agentDir);
|
|
88
140
|
if (!cached)
|
|
89
141
|
return true;
|
|
142
|
+
// 旧快照缺 metadata(字段补全之前写入)时需要重建一次,否则 30 天内
|
|
143
|
+
// 都会直接命中缓存,新字段永远补不上。
|
|
144
|
+
if (cached.source !== 'unknown' && cached.metadata === undefined)
|
|
145
|
+
return true;
|
|
90
146
|
const effectiveMaxAge = cached.source === 'unknown'
|
|
91
147
|
? Math.min(maxAgeMs, this.UNKNOWN_CACHE_MAX_AGE_MS)
|
|
92
148
|
: maxAgeMs;
|
|
@@ -98,17 +154,28 @@ export class PeerIdentityCache {
|
|
|
98
154
|
* @param verifiedAt 验签通过时间戳;未验签传 0
|
|
99
155
|
*/
|
|
100
156
|
static updateFromAgentMd(channelType, peerId, agentDir, agentMd, verifiedAt, source = 'agentmd') {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
|
|
157
|
+
// 保留整份 frontmatter:这份缓存是对端自我声明的完整画像,
|
|
158
|
+
// 只截取 type/name 会在上游新增字段(exttype/owner/tags/avatar…)时静默丢失。
|
|
159
|
+
const metadata = parseAgentMdFrontmatter(agentMd);
|
|
160
|
+
const readString = (key) => {
|
|
161
|
+
const value = metadata[key];
|
|
162
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
163
|
+
};
|
|
164
|
+
const type = readString('type') ?? 'unknown';
|
|
165
|
+
const isMachine = type !== 'human';
|
|
166
|
+
const name = readString('name');
|
|
167
|
+
const exttype = readString('exttype');
|
|
168
|
+
const owner = readString('owner');
|
|
106
169
|
const agentMdHash = 'sha256:' + crypto.createHash('sha256').update(agentMd, 'utf-8').digest('hex');
|
|
107
170
|
const now = Date.now();
|
|
108
171
|
const identity = {
|
|
109
172
|
aid: peerId,
|
|
110
173
|
type,
|
|
111
|
-
|
|
174
|
+
isMachine,
|
|
175
|
+
isAgent: isMachine,
|
|
176
|
+
...(exttype ? { exttype } : {}),
|
|
177
|
+
...(owner ? { owner } : {}),
|
|
178
|
+
metadata,
|
|
112
179
|
name,
|
|
113
180
|
agentMdHash,
|
|
114
181
|
agentMdUpdatedAt: now,
|
|
@@ -120,7 +187,7 @@ export class PeerIdentityCache {
|
|
|
120
187
|
try {
|
|
121
188
|
fs.mkdirSync(path.join(path.dirname(filePath), 'shared'), { recursive: true });
|
|
122
189
|
fs.writeFileSync(filePath, JSON.stringify(identity, null, 2), 'utf-8');
|
|
123
|
-
logger.debug(`[PeerIdentityCache] Updated: ${channelType}#${peerId} type=${type}
|
|
190
|
+
logger.debug(`[PeerIdentityCache] Updated: ${channelType}#${peerId} type=${type} isMachine=${isMachine} source=${source}`);
|
|
124
191
|
}
|
|
125
192
|
catch (err) {
|
|
126
193
|
logger.warn(`[PeerIdentityCache] Failed to write cache: ${filePath} err=${err}`);
|
|
@@ -146,6 +213,7 @@ export class PeerIdentityCache {
|
|
|
146
213
|
const identity = {
|
|
147
214
|
aid: peerId,
|
|
148
215
|
type: 'unknown',
|
|
216
|
+
isMachine: true,
|
|
149
217
|
isAgent: true,
|
|
150
218
|
agentMdHash: '',
|
|
151
219
|
agentMdUpdatedAt: 0,
|
|
@@ -188,6 +256,7 @@ export class PeerIdentityCache {
|
|
|
188
256
|
return {
|
|
189
257
|
aid: '',
|
|
190
258
|
type: 'unknown',
|
|
259
|
+
isMachine: true,
|
|
191
260
|
isAgent: true,
|
|
192
261
|
agentMdHash: '',
|
|
193
262
|
agentMdUpdatedAt: 0,
|
|
@@ -212,7 +281,10 @@ export class PeerIdentityCache {
|
|
|
212
281
|
try {
|
|
213
282
|
logger.debug(`[PeerIdentityCache] Syncing agent.md: ${channelType}#${peerId}`);
|
|
214
283
|
const { agentmdSync } = await import('../../aun/aid/agentmd.js');
|
|
215
|
-
|
|
284
|
+
// 缺 metadata 的旧快照(字段补全之前写入的)也要重写一次:
|
|
285
|
+
// 否则命中 hash 相同分支只会 touch,永远补不上 exttype/owner/metadata。
|
|
286
|
+
const invalidated = stale?.lastCheckedAt === 0 || revision > 0
|
|
287
|
+
|| (!!stale && stale.source !== 'unknown' && stale.metadata === undefined);
|
|
216
288
|
const result = await agentmdSync(peerId, {
|
|
217
289
|
store,
|
|
218
290
|
aunPath: resolvedAunPath,
|
|
@@ -222,7 +294,7 @@ export class PeerIdentityCache {
|
|
|
222
294
|
if ((this.invalidationRevisions.get(cachePath) ?? 0) !== revision) {
|
|
223
295
|
// A newer event arrived during this read. Its next lookup must refresh.
|
|
224
296
|
return this.get(channelType, peerId, agentDir) ?? {
|
|
225
|
-
aid: peerId, type: 'unknown', isAgent: true, agentMdHash: '', agentMdUpdatedAt: 0,
|
|
297
|
+
aid: peerId, type: 'unknown', isMachine: true, isAgent: true, agentMdHash: '', agentMdUpdatedAt: 0,
|
|
226
298
|
verifiedAt: 0, lastCheckedAt: 0, source: 'unknown',
|
|
227
299
|
};
|
|
228
300
|
}
|
|
@@ -241,10 +313,13 @@ export class PeerIdentityCache {
|
|
|
241
313
|
if (!verified) {
|
|
242
314
|
logger.info(`[PeerIdentityCache] agent.md unverified for ${peerId}: status=${result.verification?.status ?? 'unknown'} reason=${result.verification?.reason ?? '-'} (type 仍按声明解析)`);
|
|
243
315
|
}
|
|
244
|
-
// 3. 比较 hash,内容与可信级别都未变时仅 touch
|
|
316
|
+
// 3. 比较 hash,内容与可信级别都未变时仅 touch。
|
|
317
|
+
// 例外:旧快照若缺 metadata(字段补全之前写入),必须重写一次,
|
|
318
|
+
// 否则这里每次都走 touch,新字段永远补不上。
|
|
245
319
|
const newHash = 'sha256:' + crypto.createHash('sha256').update(content, 'utf-8').digest('hex');
|
|
246
320
|
const cached = this.get(channelType, peerId, agentDir);
|
|
247
|
-
if (cached && cached.agentMdHash === newHash && cached.source === source
|
|
321
|
+
if (cached && cached.agentMdHash === newHash && cached.source === source
|
|
322
|
+
&& cached.metadata !== undefined) {
|
|
248
323
|
return this.touchLastChecked(channelType, peerId, agentDir, cached);
|
|
249
324
|
}
|
|
250
325
|
return this.updateFromAgentMd(channelType, peerId, agentDir, content, verified ? Date.now() : 0, source);
|
|
@@ -147,13 +147,17 @@ export function validateRuntimeStringFieldOverride(input) {
|
|
|
147
147
|
* Resolve the Agent-level host execution policy for one authenticated principal.
|
|
148
148
|
* The Agent flag is independent of role permissionMode values.
|
|
149
149
|
*/
|
|
150
|
+
export function isAgentFullAccessOwner(agentAid, principalId) {
|
|
151
|
+
if (!principalId)
|
|
152
|
+
return false;
|
|
153
|
+
const config = read(ConfigTarget.Agent, { self: agentAid }, { cache: false });
|
|
154
|
+
return config?.owners?.includes(principalId) === true
|
|
155
|
+
&& loadDaemonConfig().owners?.includes(principalId) === true;
|
|
156
|
+
}
|
|
150
157
|
export function agentFullAccessStatus(agentAid, principalId) {
|
|
151
158
|
const config = read(ConfigTarget.Agent, { self: agentAid }, { cache: false });
|
|
152
|
-
const daemon = loadDaemonConfig();
|
|
153
159
|
const enabled = config?.fullaccess?.enabled === true;
|
|
154
|
-
const eligible =
|
|
155
|
-
&& !!config?.owners?.includes(principalId)
|
|
156
|
-
&& !!daemon.owners?.includes(principalId);
|
|
160
|
+
const eligible = isAgentFullAccessOwner(agentAid, principalId);
|
|
157
161
|
return { enabled, eligible, effective: enabled && eligible };
|
|
158
162
|
}
|
|
159
163
|
/** Create a trusted per-call claim for a direct private message. */
|
|
@@ -189,13 +193,20 @@ export function assertAgentPolicyAuthorization(authorization) {
|
|
|
189
193
|
throw new Error('Agent fullaccess policy disabled or current owner is no longer eligible');
|
|
190
194
|
}
|
|
191
195
|
}
|
|
192
|
-
/** Only
|
|
193
|
-
export function agentFullAccessWriteError(
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
+
/** Only authenticated ECWeb management or a direct double-Owner Menu request may mutate this policy. */
|
|
197
|
+
export function agentFullAccessWriteError(agentAid, subject) {
|
|
198
|
+
if (subject?.ecwebManagement === true) {
|
|
199
|
+
if (!subject.isDaemonOwner || subject.canApprovePersistentFullAccess === false || subject.chatType !== 'private') {
|
|
200
|
+
return 'Only authenticated ECWeb management may configure Agent fullaccess';
|
|
201
|
+
}
|
|
202
|
+
return undefined;
|
|
196
203
|
}
|
|
197
|
-
if (
|
|
198
|
-
|
|
204
|
+
if (subject?.agentFullAccessManagement !== true
|
|
205
|
+
|| subject.chatType !== 'private'
|
|
206
|
+
|| !subject.isDaemonOwner
|
|
207
|
+
|| subject.processRole !== 'daemon-owner'
|
|
208
|
+
|| !isAgentFullAccessOwner(agentAid, subject.principalId)) {
|
|
209
|
+
return 'Agent fullaccess requires a direct Menu request from both the target Agent Owner and daemon Owner';
|
|
199
210
|
}
|
|
200
211
|
return undefined;
|
|
201
212
|
}
|
|
@@ -178,6 +178,111 @@ export function appendJsonlDurable(filePath, record) {
|
|
|
178
178
|
fs.closeSync(fd);
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* meta_*.jsonl(含 `_threads/`)是快照日志:每行一份完整 Session,只有最后一行是权威状态,
|
|
183
|
+
* 之前的行只供排障回看。超过 256KB 压到最近 50 行。
|
|
184
|
+
*/
|
|
185
|
+
export const SESSION_META_COMPACT_POLICY = {
|
|
186
|
+
maxBytes: 256 * 1024,
|
|
187
|
+
keepLines: 50,
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* 从 [0, endExclusive) 区间由后往前逐行产出(不含换行符,含空行)。
|
|
191
|
+
* 按 64KB 分块倒读,内存只与单行长度相关,与文件大小无关。
|
|
192
|
+
*/
|
|
193
|
+
function* iterateLinesBackward(fd, endExclusive, filePath) {
|
|
194
|
+
const chunkSize = 64 * 1024;
|
|
195
|
+
let cursor = endExclusive;
|
|
196
|
+
let carry = Buffer.alloc(0);
|
|
197
|
+
while (cursor > 0) {
|
|
198
|
+
const start = Math.max(0, cursor - chunkSize);
|
|
199
|
+
const chunk = readExactAt(fd, cursor - start, start, filePath);
|
|
200
|
+
const buf = carry.length ? Buffer.concat([chunk, carry]) : chunk;
|
|
201
|
+
let end = buf.length;
|
|
202
|
+
let newline = buf.lastIndexOf(0x0a, end - 1);
|
|
203
|
+
while (newline >= 0) {
|
|
204
|
+
yield buf.subarray(newline + 1, end);
|
|
205
|
+
end = newline;
|
|
206
|
+
newline = end > 0 ? buf.lastIndexOf(0x0a, end - 1) : -1;
|
|
207
|
+
}
|
|
208
|
+
carry = Buffer.from(buf.subarray(0, end));
|
|
209
|
+
cursor = start;
|
|
210
|
+
}
|
|
211
|
+
if (carry.length)
|
|
212
|
+
yield carry;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* 快照日志的尾部压缩:文件超过 policy.maxBytes 时,只保留最近 keepLines 行
|
|
216
|
+
* (且不超过 maxBytes/2 字节,至少保留最后一行),写 tmp → fsync → rename 覆盖。
|
|
217
|
+
*
|
|
218
|
+
* - 保留行按字节原样拷贝,不解析,坏的末行仍交给读取方报错,不在这里被掩盖。
|
|
219
|
+
* - 末尾未以换行结束(崩溃残留或有人正在写)时不压缩,留给下次 append 的尾部修复。
|
|
220
|
+
* - rename 前复查文件大小,期间有其他进程追加则放弃本次压缩,避免吞掉新写入的行。
|
|
221
|
+
* - 直接 rename 覆盖,不先 unlink,不留目标文件缺失的窗口给并发读者。
|
|
222
|
+
*
|
|
223
|
+
* @returns 是否实际做了压缩。失败时抛错,原文件保持不变。
|
|
224
|
+
*/
|
|
225
|
+
export function compactJsonlTail(filePath, policy) {
|
|
226
|
+
let fd;
|
|
227
|
+
try {
|
|
228
|
+
fd = fs.openSync(filePath, 'r');
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
if (error.code === 'ENOENT')
|
|
232
|
+
return false;
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
let output;
|
|
236
|
+
let size;
|
|
237
|
+
try {
|
|
238
|
+
size = fs.fstatSync(fd).size;
|
|
239
|
+
if (size <= policy.maxBytes)
|
|
240
|
+
return false;
|
|
241
|
+
if (readExactAt(fd, 1, size - 1, filePath)[0] !== 0x0a)
|
|
242
|
+
return false;
|
|
243
|
+
const budget = Math.floor(policy.maxBytes / 2);
|
|
244
|
+
const kept = [];
|
|
245
|
+
let keptBytes = 0;
|
|
246
|
+
for (const line of iterateLinesBackward(fd, size - 1, filePath)) {
|
|
247
|
+
if (line.toString('utf8').trim() === '')
|
|
248
|
+
continue;
|
|
249
|
+
if (kept.length > 0 && (kept.length >= policy.keepLines || keptBytes + line.length + 1 > budget))
|
|
250
|
+
break;
|
|
251
|
+
kept.push(Buffer.from(line));
|
|
252
|
+
keptBytes += line.length + 1;
|
|
253
|
+
}
|
|
254
|
+
if (keptBytes >= size)
|
|
255
|
+
return false;
|
|
256
|
+
output = Buffer.concat(kept.reverse().flatMap(line => [line, Buffer.from('\n')]));
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
fs.closeSync(fd);
|
|
260
|
+
}
|
|
261
|
+
const tmpPath = filePath + '.compact.tmp';
|
|
262
|
+
const tmpFd = fs.openSync(tmpPath, 'w');
|
|
263
|
+
try {
|
|
264
|
+
writeAllSync(tmpFd, output);
|
|
265
|
+
fs.fsyncSync(tmpFd);
|
|
266
|
+
}
|
|
267
|
+
finally {
|
|
268
|
+
fs.closeSync(tmpFd);
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
if (fs.statSync(filePath).size !== size) {
|
|
272
|
+
fs.unlinkSync(tmpPath);
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
fs.renameSync(tmpPath, filePath);
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
try {
|
|
279
|
+
fs.unlinkSync(tmpPath);
|
|
280
|
+
}
|
|
281
|
+
catch { /* best effort */ }
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
181
286
|
function writeAllSync(fd, content) {
|
|
182
287
|
let offset = 0;
|
|
183
288
|
while (offset < content.length) {
|
|
@@ -323,12 +428,24 @@ export function readJsonFile(filePath) {
|
|
|
323
428
|
throw e;
|
|
324
429
|
}
|
|
325
430
|
}
|
|
431
|
+
/**
|
|
432
|
+
* 宽松读取最后一条可解析记录:从尾部倒读,跳过空行和坏行(含未终止的尾行)。
|
|
433
|
+
* 开销只与末尾几行有关,不随文件大小增长。
|
|
434
|
+
*/
|
|
326
435
|
export function readLastJsonlLine(filePath) {
|
|
436
|
+
let fd;
|
|
327
437
|
try {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
438
|
+
fd = fs.openSync(filePath, 'r');
|
|
439
|
+
}
|
|
440
|
+
catch (e) {
|
|
441
|
+
if (e.code === 'ENOENT')
|
|
442
|
+
return undefined;
|
|
443
|
+
throw e;
|
|
444
|
+
}
|
|
445
|
+
try {
|
|
446
|
+
const size = fs.fstatSync(fd).size;
|
|
447
|
+
for (const raw of iterateLinesBackward(fd, size, filePath)) {
|
|
448
|
+
const line = raw.toString('utf8').trim();
|
|
332
449
|
if (!line)
|
|
333
450
|
continue;
|
|
334
451
|
try {
|
|
@@ -340,10 +457,8 @@ export function readLastJsonlLine(filePath) {
|
|
|
340
457
|
}
|
|
341
458
|
return undefined;
|
|
342
459
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
return undefined;
|
|
346
|
-
throw e;
|
|
460
|
+
finally {
|
|
461
|
+
fs.closeSync(fd);
|
|
347
462
|
}
|
|
348
463
|
}
|
|
349
464
|
export function readAllJsonlLines(filePath) {
|