flavor-code 1.2.4 → 1.2.5

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.
@@ -2953,10 +2953,12 @@ function getToolCategory(name) {
2953
2953
  return isNetworkTool(name) ? "network" : CATEGORY_MAP[name] ?? "unknown";
2954
2954
  }
2955
2955
  var PermissionEngine = class {
2956
+ #lexicalWorkspace;
2956
2957
  #workspace;
2957
2958
  #mode;
2958
2959
  constructor(options) {
2959
2960
  const root = resolve2(options.workspace);
2961
+ this.#lexicalWorkspace = root;
2960
2962
  this.#workspace = existsSync(root) ? realpathSync.native(root) : root;
2961
2963
  this.#mode = canonicalMode(options.mode ?? "default");
2962
2964
  }
@@ -2977,13 +2979,13 @@ var PermissionEngine = class {
2977
2979
  return { decision: "deny", reason: `${request.tool} requires source and destination paths` };
2978
2980
  }
2979
2981
  for (const path of paths) {
2980
- const classification = classifyPath(this.#workspace, path);
2982
+ const classification = classifyPath(this.#lexicalWorkspace, this.#workspace, path);
2981
2983
  if (classification.escape) return { decision: "deny", reason: classification.reason ?? "Path escapes the workspace" };
2982
2984
  if (request.agent === "subagent" && !classification.inside) {
2983
2985
  return { decision: "deny", reason: "Subagents are restricted to the workspace" };
2984
2986
  }
2985
2987
  }
2986
- const inside = paths.every((path) => classifyPath(this.#workspace, path).inside);
2988
+ const inside = paths.every((path) => classifyPath(this.#lexicalWorkspace, this.#workspace, path).inside);
2987
2989
  if (this.#mode === "plan") {
2988
2990
  return READ_TOOLS.has(request.tool) ? { decision: "allow" } : { decision: "deny", reason: "Plan mode is read-only" };
2989
2991
  }
@@ -3010,11 +3012,11 @@ var PermissionEngine = class {
3010
3012
  const analysis = request.args === void 0 ? analyzeCommand(request.command ?? "") : analyzeArgumentCommand(request.command ?? "", request.args);
3011
3013
  if (request.agent === "subagent") {
3012
3014
  if (request.cwd === void 0) return { decision: "ask", reason: "Subagent shell commands require an explicit workspace cwd" };
3013
- const cwd = classifyPath(this.#workspace, request.cwd);
3015
+ const cwd = classifyPath(this.#lexicalWorkspace, this.#workspace, request.cwd);
3014
3016
  if (cwd.escape || !cwd.inside) return { decision: "deny", reason: "Subagent shell cwd must remain in the workspace" };
3015
3017
  if (analysis.destructive) return { decision: "deny", reason: "Destructive commands are forbidden for subagents" };
3016
3018
  if (analysis.wrapped || analysis.opaque || !isRoutineCommand(analysis.command)) return { decision: "ask", reason: "Subagent shell command requires main-Agent approval" };
3017
- const argumentDecision = assessRoutineArguments(analysis.command, request.cwd, this.#workspace);
3019
+ const argumentDecision = assessRoutineArguments(analysis.command, request.cwd, this.#lexicalWorkspace, this.#workspace);
3018
3020
  if (argumentDecision !== "allow") {
3019
3021
  return argumentDecision === "deny" ? { decision: "deny", reason: "Subagent command arguments escape the workspace" } : { decision: "ask", reason: "Ambiguous subagent command arguments require main-Agent approval" };
3020
3022
  }
@@ -3026,12 +3028,12 @@ var PermissionEngine = class {
3026
3028
  if (analysis.wrapped || analysis.opaque) return ask(this.#mode, "Shell wrapper requires approval");
3027
3029
  if (this.#mode === "bypassPermissions") return { decision: "allow" };
3028
3030
  if ((this.#mode === "acceptEdits" || this.#mode === "auto") && isRoutineCommand(analysis.command)) {
3029
- const cwd = request.cwd ?? this.#workspace;
3030
- const cwdClassification = classifyPath(this.#workspace, cwd);
3031
+ const cwd = request.cwd ?? this.#lexicalWorkspace;
3032
+ const cwdClassification = classifyPath(this.#lexicalWorkspace, this.#workspace, cwd);
3031
3033
  if (cwdClassification.escape || !cwdClassification.inside) {
3032
3034
  return { decision: "deny", reason: "Routine command cwd must remain in the workspace" };
3033
3035
  }
3034
- const argumentDecision = assessRoutineArguments(analysis.command, cwd, this.#workspace);
3036
+ const argumentDecision = assessRoutineArguments(analysis.command, cwd, this.#lexicalWorkspace, this.#workspace);
3035
3037
  if (argumentDecision === "deny") return { decision: "deny", reason: "Routine command arguments escape the workspace" };
3036
3038
  if (argumentDecision === "ask") return ask(this.#mode, "Ambiguous routine command arguments require approval");
3037
3039
  return { decision: "allow" };
@@ -3068,16 +3070,16 @@ function analyzeArgumentCommand(executable, args) {
3068
3070
  }
3069
3071
  return { command, destructive: isForbiddenCommand(command), opaque: false, wrapped: false };
3070
3072
  }
3071
- function classifyPath(workspace, input) {
3072
- const candidate = resolve2(workspace, input);
3073
- const lexicalInside = isWithin(workspace, candidate);
3073
+ function classifyPath(lexicalWorkspace, physicalWorkspace, input) {
3074
+ const candidate = resolve2(lexicalWorkspace, input);
3075
+ const lexicalInside = isWithin(lexicalWorkspace, candidate);
3074
3076
  const traversal = input.split(/[\\/]+/).includes("..");
3075
- const startsInWorkspace = pathStartsWith(workspace, input);
3077
+ const startsInWorkspace = pathStartsWith(lexicalWorkspace, input);
3076
3078
  if ((!isAbsolute(input) || startsInWorkspace) && traversal && !lexicalInside) {
3077
3079
  return { inside: false, escape: true, reason: "Path traversal escapes the workspace" };
3078
3080
  }
3079
3081
  const physical = resolvePhysical(candidate);
3080
- const physicalInside = isWithin(workspace, physical);
3082
+ const physicalInside = isWithin(physicalWorkspace, physical);
3081
3083
  if (lexicalInside && !physicalInside) return { inside: false, escape: true, reason: "Symlink escapes the workspace" };
3082
3084
  return { inside: physicalInside, escape: false };
3083
3085
  }
@@ -3167,7 +3169,7 @@ function isRoutineCommand(command) {
3167
3169
  if (command.executable === "go") return first === "test";
3168
3170
  return ["pytest", "vitest", "jest", "eslint", "tsc"].includes(command.executable);
3169
3171
  }
3170
- function assessRoutineArguments(command, cwd, workspace) {
3172
+ function assessRoutineArguments(command, cwd, lexicalWorkspace, physicalWorkspace) {
3171
3173
  const args = routineArguments(command);
3172
3174
  if (args === void 0) return "ask";
3173
3175
  const pathOptions = /* @__PURE__ */ new Set([
@@ -3192,7 +3194,7 @@ function assessRoutineArguments(command, cwd, workspace) {
3192
3194
  const option2 = equals[1]?.toLowerCase() ?? "";
3193
3195
  const value = equals[2] ?? "";
3194
3196
  if (!pathOptions.has(option2)) return "ask";
3195
- const decision = assessArgumentPath(value, cwd, workspace);
3197
+ const decision = assessArgumentPath(value, cwd, lexicalWorkspace, physicalWorkspace);
3196
3198
  if (decision !== "allow") return decision;
3197
3199
  continue;
3198
3200
  }
@@ -3200,14 +3202,14 @@ function assessRoutineArguments(command, cwd, workspace) {
3200
3202
  if (option !== void 0 && pathOptions.has(option)) {
3201
3203
  const value = args[index + 1];
3202
3204
  if (value === void 0 || value.startsWith("-")) return "ask";
3203
- const decision = assessArgumentPath(value, cwd, workspace);
3205
+ const decision = assessArgumentPath(value, cwd, lexicalWorkspace, physicalWorkspace);
3204
3206
  if (decision !== "allow") return decision;
3205
3207
  index += 1;
3206
3208
  continue;
3207
3209
  }
3208
3210
  if (arg.startsWith("-")) continue;
3209
3211
  if (looksLikePath(arg)) {
3210
- const decision = assessArgumentPath(arg, cwd, workspace);
3212
+ const decision = assessArgumentPath(arg, cwd, lexicalWorkspace, physicalWorkspace);
3211
3213
  if (decision !== "allow") return decision;
3212
3214
  continue;
3213
3215
  }
@@ -3225,10 +3227,10 @@ function routineArguments(command) {
3225
3227
  if (["pytest", "vitest", "jest", "eslint", "tsc"].includes(command.executable)) return command.args;
3226
3228
  return void 0;
3227
3229
  }
3228
- function assessArgumentPath(value, cwd, workspace) {
3230
+ function assessArgumentPath(value, cwd, lexicalWorkspace, physicalWorkspace) {
3229
3231
  if (value.length === 0) return "ask";
3230
3232
  const resolved = resolve2(cwd, value);
3231
- const classification = classifyPath(workspace, resolved);
3233
+ const classification = classifyPath(lexicalWorkspace, physicalWorkspace, resolved);
3232
3234
  return classification.escape || !classification.inside ? "deny" : "allow";
3233
3235
  }
3234
3236
  function looksLikePath(value) {
@@ -3477,7 +3479,8 @@ var ToolRuntime = class {
3477
3479
  } catch {
3478
3480
  }
3479
3481
  }
3480
- if (pre.decision === "ask" || permission.decision === "ask") {
3482
+ const sessionAllowed = this.#alwaysAllowed.has(getToolCategory(tool.name));
3483
+ if (!sessionAllowed && (pre.decision === "ask" || permission.decision === "ask")) {
3481
3484
  const reason = [pre.reason, permission.reason].filter((value) => value !== void 0).join("\n") || "Approval required";
3482
3485
  const requestDecision = await this.#hooks.emit({
3483
3486
  version: 1,
@@ -3502,8 +3505,7 @@ var ToolRuntime = class {
3502
3505
  throw signal.reason;
3503
3506
  } else {
3504
3507
  const category = getToolCategory(tool.name);
3505
- if (this.#alwaysAllowed.has(category)) {
3506
- } else if (this.#approve === void 0) {
3508
+ if (this.#approve === void 0) {
3507
3509
  return this.#fail(tool.name, input, context.agent, "permission_denied", reason);
3508
3510
  } else {
3509
3511
  const decision = await this.#approve({ ...request, reason }, signal);
@@ -8251,9 +8253,11 @@ var SkillRegistry = class {
8251
8253
  for (const configuredRoot of roots) {
8252
8254
  const root = resolve11(configuredRoot);
8253
8255
  let entries;
8256
+ let physicalRoot = root;
8254
8257
  try {
8255
8258
  const rootInfo = await lstat5(root);
8256
8259
  if (rootInfo.isSymbolicLink() || !rootInfo.isDirectory()) throw new Error("Skill root must be a real directory");
8260
+ physicalRoot = await realpath4(root);
8257
8261
  entries = await readdir4(root, { withFileTypes: true });
8258
8262
  } catch (error) {
8259
8263
  if (isMissing(error)) continue;
@@ -8266,12 +8270,12 @@ var SkillRegistry = class {
8266
8270
  try {
8267
8271
  if (entry2.isSymbolicLink()) throw new Error("Symlinked skill directories are not allowed");
8268
8272
  if (!entry2.isDirectory()) continue;
8269
- const physicalRoot = await realpath4(skillRoot);
8270
- if (!isWithin3(root, physicalRoot)) throw new Error("Skill directory escapes its registry root");
8273
+ const physicalSkillRoot = await realpath4(skillRoot);
8274
+ if (!isWithin3(physicalRoot, physicalSkillRoot)) throw new Error("Skill directory escapes its registry root");
8271
8275
  const skillFile = resolve11(skillRoot, "SKILL.md");
8272
8276
  const parsed = await readFrontmatterFile(
8273
8277
  skillFile,
8274
- physicalRoot,
8278
+ physicalSkillRoot,
8275
8279
  this.#options.maxMetadataBytes,
8276
8280
  this.#options.openFile
8277
8281
  );
@@ -8282,7 +8286,7 @@ var SkillRegistry = class {
8282
8286
  name: parsed.name,
8283
8287
  description: parsed.description,
8284
8288
  source,
8285
- root: physicalRoot,
8289
+ root: physicalSkillRoot,
8286
8290
  disableModelInvocation: parsed.disableModelInvocation
8287
8291
  }),
8288
8292
  skillFile
@@ -10513,6 +10517,7 @@ function createEditTool(workspace, options = {}) {
10513
10517
  paths: (input) => [guard.lexical(input.path)],
10514
10518
  execute: async (input, signal) => {
10515
10519
  abortIfNeeded(signal);
10520
+ const displayPath = guard.lexical(input.path);
10516
10521
  const path = await guard.existing(input.path);
10517
10522
  const contents = await readText(path);
10518
10523
  const hasCRLF = contents.includes("\r\n");
@@ -10528,11 +10533,11 @@ function createEditTool(workspace, options = {}) {
10528
10533
  }
10529
10534
  const updatedLF = contentsLF.slice(0, first) + newTextLF + contentsLF.slice(first + oldTextLF.length);
10530
10535
  const updated = hasCRLF ? updatedLF.replace(/\n/g, "\r\n") : updatedLF;
10531
- await options.beforeCommit?.({ path, before: contents, after: updated, kind: "update" }, signal);
10536
+ await options.beforeCommit?.({ path: displayPath, before: contents, after: updated, kind: "update" }, signal);
10532
10537
  await atomicWrite(path, updated, signal);
10533
10538
  return withToolPresentation(
10534
- { path, replacements: 1 },
10535
- buildFileChangePresentation(path, contents, updated, "update")
10539
+ { path: displayPath, replacements: 1 },
10540
+ buildFileChangePresentation(displayPath, contents, updated, "update")
10536
10541
  );
10537
10542
  }
10538
10543
  };
package/dist/sdk/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  FlavorRpcServer,
5
5
  TraceRecordSchema,
6
6
  TraceRecorder
7
- } from "../chunk-L5A2NDMT.js";
7
+ } from "../chunk-Z3BW52MK.js";
8
8
  import {
9
9
  AgentMessageQueue,
10
10
  DockerExecutionEnvironment,
@@ -13,7 +13,7 @@ import {
13
13
  WorkspaceCheckpointStore,
14
14
  buildDockerInvocation,
15
15
  createProductionRuntime
16
- } from "../chunk-MRISY6KK.js";
16
+ } from "../chunk-3TYRDK34.js";
17
17
  import "../chunk-HFR2WS6T.js";
18
18
  import "../chunk-N2S7USST.js";
19
19
  import "../chunk-S32JRDZK.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flavor-code",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "author": "YachuanWzh",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -21,6 +21,7 @@
21
21
  "dist/",
22
22
  "!dist/**/*.map",
23
23
  "README.md",
24
+ "README.zh-CN.md",
24
25
  "技术方案报告.md",
25
26
  "LICENSE"
26
27
  ],