@hmj-ai/cflow 1.3.2 → 1.3.3

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/README.md CHANGED
@@ -79,6 +79,7 @@ npm start
79
79
  - Runtime 配置每次保存都会生成不可变 Profile 版本;发布 Flow 时会 pin 精确 Profile 版本,Run Ledger 记录实际执行版本。
80
80
  - 设置页只读展示当前工作区,并提供默认 Agent 和测试最长等待时间;资源绑定按需放在折叠的高级区域。
81
81
  - Runtime 必须返回符合 CF output contract 的 JSON,否则 Flow fail closed。
82
+ - Runtime 工具请求默认按 CF 的 effects 自动授权:已声明且位于工作区内的读写和命令操作无需逐次确认;未声明能力或工作区外路径会被拒绝。Flow 中显式的人工审批节点仍需单独批准。
82
83
  - Flow/CF 草稿会保存到 SQLite;Resource Profile 可在设置中创建、编辑与删除。
83
84
 
84
85
  非内置 Agent 可以通过 JSON manifest 接入。项目级文件放在 `<workspace>/.cflow/agents.d/*.json`,用户级文件放在 `~/.config/cflow/agents.d/*.json`;项目配置优先级更高。最小示例:
@@ -26,7 +26,7 @@ export const builtinAdapterDescriptors = [
26
26
  permissionMode: {
27
27
  environment: 'INITIAL_AGENT_MODE',
28
28
  readOnly: 'read-only',
29
- workspaceWrite: 'workspace-write',
29
+ workspaceWrite: 'agent',
30
30
  },
31
31
  staticEnvironment: { NO_BROWSER: '1' },
32
32
  },
@@ -15,6 +15,48 @@ export class RuntimeExecutionException extends Error {
15
15
  this.details = details;
16
16
  }
17
17
  }
18
+ export function isAcpToolAllowed(tool, effects, root, cwd, analysis = false) {
19
+ const kind = String(tool.kind ?? tool.name ?? tool.title ?? '').toLowerCase();
20
+ const effectType = kind.includes('read') || kind.includes('search')
21
+ ? 'file-read'
22
+ : kind.includes('edit') ||
23
+ kind.includes('write') ||
24
+ kind.includes('delete') ||
25
+ kind.includes('move')
26
+ ? 'file-write'
27
+ : kind.includes('exec') || kind.includes('command') || kind.includes('terminal')
28
+ ? 'command'
29
+ : undefined;
30
+ const declared = effectType
31
+ ? analysis && effectType === 'file-read'
32
+ ? true
33
+ : effects.some((effect) => effect.type === effectType && effect.scope === 'workspace')
34
+ : false;
35
+ const locations = Array.isArray(tool.locations) ? [...tool.locations] : [];
36
+ if (tool.rawInput && typeof tool.rawInput === 'object') {
37
+ for (const key of ['path', 'file', 'filePath', 'filename']) {
38
+ const value = tool.rawInput[key];
39
+ if (typeof value === 'string')
40
+ locations.push(value);
41
+ }
42
+ }
43
+ const inWorkspace = locations.every((location) => {
44
+ let value = typeof location === 'string' ? location : (location?.path ?? location?.uri);
45
+ if (typeof value === 'string' && value.startsWith('file://')) {
46
+ try {
47
+ value = new URL(value).pathname;
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ }
53
+ if (typeof value === 'string' && !isAbsolute(value))
54
+ value = resolve(cwd, value);
55
+ return Boolean(root) && (!value || isWithinDirectory(root, value));
56
+ });
57
+ const scopedRead = !analysis || effectType !== 'file-read' || locations.length > 0;
58
+ return Boolean(declared && inWorkspace && scopedRead);
59
+ }
18
60
  const defaultTraits = (overrides = {}) => ({
19
61
  backendKind: 'acp',
20
62
  sessionMode: 'per-cf-call',
@@ -775,48 +817,8 @@ export class RuntimeManager {
775
817
  return await acpClient({ name: 'CFlow' })
776
818
  .onRequest(methods.client.session.requestPermission, async (ctx) => {
777
819
  const tool = ctx?.params?.toolCall ?? ctx?.toolCall ?? {};
778
- const kind = String(tool.kind ?? tool.name ?? tool.title ?? '').toLowerCase();
779
- const effectType = kind.includes('read') || kind.includes('search')
780
- ? 'file-read'
781
- : kind.includes('edit') ||
782
- kind.includes('write') ||
783
- kind.includes('delete') ||
784
- kind.includes('move')
785
- ? 'file-write'
786
- : kind.includes('exec') || kind.includes('command') || kind.includes('terminal')
787
- ? 'command'
788
- : undefined;
789
820
  const root = analysis?.allowedRoot ?? context?.workspaceRoot;
790
- const declared = effectType
791
- ? analysis && effectType === 'file-read'
792
- ? true
793
- : effects.some((effect) => effect.type === effectType && effect.scope === 'workspace')
794
- : false;
795
- const locations = Array.isArray(tool.locations) ? [...tool.locations] : [];
796
- const raw = tool.rawInput;
797
- if (raw && typeof raw === 'object') {
798
- for (const key of ['path', 'file', 'filePath', 'filename']) {
799
- const value = raw[key];
800
- if (typeof value === 'string')
801
- locations.push(value);
802
- }
803
- }
804
- const inWorkspace = locations.every((location) => {
805
- let value = typeof location === 'string' ? location : (location?.path ?? location?.uri);
806
- if (typeof value === 'string' && value.startsWith('file://')) {
807
- try {
808
- value = new URL(value).pathname;
809
- }
810
- catch {
811
- return false;
812
- }
813
- }
814
- if (analysis && typeof value === 'string' && !isAbsolute(value))
815
- value = resolve(cwd, value);
816
- return Boolean(root) && (!value || isWithinDirectory(root, value));
817
- });
818
- const scopedRead = !analysis || effectType !== 'file-read' || locations.length > 0;
819
- if (!declared || !inWorkspace || !scopedRead) {
821
+ if (!isAcpToolAllowed(tool, effects, root, cwd, Boolean(analysis))) {
820
822
  permissionDenied = true;
821
823
  const option = ctx?.params?.options?.find((o) => String(o.kind).startsWith('reject'));
822
824
  return option
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hmj-ai/cflow",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "以 Flow 为核心的本机多 Agent 编排工作台",
5
5
  "main": "dist/src/server.js",
6
6
  "bin": {