@cjhyy/code-shell-core 0.8.0 → 0.8.1

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/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Public API exports.
5
5
  */
6
- export declare const VERSION = "0.8.0";
6
+ export declare const VERSION = "0.8.1";
7
7
  export type { Message, ContentBlock, ToolDefinition, ToolCall, ToolResult, RegisteredTool, TranscriptEvent, TranscriptEventType, SessionState, SessionKind, SessionWorkspace, SessionForkLineage, ContextUsageAnchor, SessionStatus, TokenUsage, CompiledInput, PermissionDecision, PermissionMode, PermissionRule, TurnPhase, TurnResult, TerminalReason, TurnCompletionKind, StreamEvent, StreamCallback, LLMConfig, ClientDefaults, LLMResponse, Settings, MCPServerConfig, } from "./types.js";
8
8
  export type { GoalConfig, GoalLifecycleConfig, GoalLifecyclePhase, GoalLifecycleTerminalReason, GoalLifecycleV1, } from "./goal/lifecycle.js";
9
9
  export { FrameworkError, LLMError, LLMRateLimitError, ContextLimitError, ToolError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError, PermissionDeniedError, SessionError, TranscriptError, ConfigError, SandboxUnavailableError, } from "./exceptions.js";
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Public API exports.
5
5
  */
6
- export const VERSION = "0.8.0";
6
+ export const VERSION = "0.8.1";
7
7
  // ─── Exceptions ──────────────────────────────────────────────────
8
8
  export { FrameworkError, LLMError, LLMRateLimitError, ContextLimitError, ToolError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError, PermissionDeniedError, SessionError, TranscriptError, ConfigError, SandboxUnavailableError, } from "./exceptions.js";
9
9
  // ─── Engine (primary API) ────────────────────────────────────────
@@ -29,11 +29,12 @@
29
29
  * approval round-trip; it is not an authority to write anywhere on disk.
30
30
  * Callers must consult classifyPath before honoring acceptEdits.
31
31
  */
32
- import { constants, realpathSync, existsSync, readFileSync, writeFileSync, mkdirSync, renameSync, } from "node:fs";
32
+ import { constants, realpathSync, existsSync, readFileSync, writeFileSync, mkdirSync, renameSync, statSync, } from "node:fs";
33
33
  import { open } from "node:fs/promises";
34
34
  import { homedir } from "node:os";
35
35
  import { randomUUID } from "node:crypto";
36
- import { dirname, isAbsolute, join, resolve as resolvePath, sep } from "node:path";
36
+ import { dirname, isAbsolute, join, relative, resolve as resolvePath, sep } from "node:path";
37
+ import { readInstalledPlugins } from "../plugins/installedPlugins.js";
37
38
  const FINAL_WRITE_PATH_SNAPSHOT = Symbol("codeshell.finalWritePathSnapshot");
38
39
  /**
39
40
  * Default sensitive path patterns. These are evaluated AFTER home-expansion
@@ -392,6 +393,88 @@ function isInsideDir(child, parent) {
392
393
  const p = par.endsWith(sep) ? par : par + sep;
393
394
  return c === par || c.startsWith(p);
394
395
  }
396
+ function configuredUserHome() {
397
+ return process.env.HOME ?? homedir();
398
+ }
399
+ /**
400
+ * Whether `resolved` belongs to one concrete Skill tree rooted below
401
+ * `<skillsRoot>/<skill>/`.
402
+ *
403
+ * Both the root and manifest are realpathed, so a reference symlink cannot
404
+ * turn this read-only exception into access outside the managed Skill tree.
405
+ */
406
+ function isSkillTreeResource(resolved, skillsRoot) {
407
+ try {
408
+ const realSkillsRoot = realpathSync(skillsRoot);
409
+ if (!isInsideDir(resolved, realSkillsRoot))
410
+ return false;
411
+ const rel = relative(realSkillsRoot, resolved);
412
+ const skillName = rel.split(sep).filter(Boolean)[0];
413
+ if (!skillName || skillName === "..")
414
+ return false;
415
+ const skillRoot = realpathSync(join(realSkillsRoot, skillName));
416
+ if (!isInsideDir(skillRoot, realSkillsRoot))
417
+ return false;
418
+ const manifest = realpathSync(join(skillRoot, "SKILL.md"));
419
+ if (!isInsideDir(manifest, skillRoot) || !statSync(manifest).isFile())
420
+ return false;
421
+ return isInsideDir(resolved, skillRoot);
422
+ }
423
+ catch {
424
+ return false;
425
+ }
426
+ }
427
+ /**
428
+ * Skill instructions routinely link to sibling references/scripts/assets.
429
+ * The Skill builtin can already read the registered SKILL.md, so asking again
430
+ * for every referenced file is both inconsistent and capable of wedging a
431
+ * headless run.
432
+ *
433
+ * Keep the exception narrow:
434
+ * - read-only (the caller checks the operation);
435
+ * - user Skills, or an installed plugin recorded in the V2 registry;
436
+ * - plugin installs must realpath beneath the managed plugin cache;
437
+ * - the target must remain inside a directory containing a real SKILL.md.
438
+ */
439
+ function isRegisteredSkillResourceRead(resolved) {
440
+ let codeShellRoot;
441
+ try {
442
+ codeShellRoot = realpathSync(join(configuredUserHome(), ".code-shell"));
443
+ }
444
+ catch {
445
+ return false;
446
+ }
447
+ if (!isInsideDir(resolved, codeShellRoot))
448
+ return false;
449
+ if (isSkillTreeResource(resolved, join(codeShellRoot, "skills")))
450
+ return true;
451
+ let cacheRoot;
452
+ try {
453
+ cacheRoot = realpathSync(join(codeShellRoot, "plugins", "cache"));
454
+ }
455
+ catch {
456
+ return false;
457
+ }
458
+ const installed = readInstalledPlugins();
459
+ for (const entries of Object.values(installed.plugins)) {
460
+ for (const entry of entries) {
461
+ try {
462
+ const installRoot = realpathSync(entry.installPath);
463
+ if (installRoot === cacheRoot || !isInsideDir(installRoot, cacheRoot))
464
+ continue;
465
+ const skillsRoot = realpathSync(join(installRoot, "skills"));
466
+ if (!isInsideDir(skillsRoot, installRoot))
467
+ continue;
468
+ if (isSkillTreeResource(resolved, skillsRoot))
469
+ return true;
470
+ }
471
+ catch {
472
+ // A stale/tampered registry entry must never broaden read authority.
473
+ }
474
+ }
475
+ }
476
+ return false;
477
+ }
395
478
  /**
396
479
  * Returns the matching sensitive-dir entry (with the user's home prefix) if
397
480
  * `resolved` lives underneath any sensitive directory, else undefined.
@@ -476,6 +559,20 @@ export function classifyPath(rawPath, opts) {
476
559
  const sensitiveFile = matchSensitiveFile(resolved);
477
560
  const sensitiveLabel = sensitiveDir ?? sensitiveFile;
478
561
  const insideWorkspace = isInsideDir(resolved, workspace);
562
+ // Registered Skill resources are managed runtime inputs. Their SKILL.md is
563
+ // already readable through the Skill builtin; allow its contained reference
564
+ // files through the ordinary Read tool as well. A credential-shaped basename
565
+ // (.env, token.txt, key files, ...) deliberately keeps the sensitive-file
566
+ // gate even inside a Skill tree.
567
+ if (opts.operation === "read" &&
568
+ !sensitiveFile &&
569
+ isRegisteredSkillResourceRead(resolved)) {
570
+ return {
571
+ decision: "allow",
572
+ reason: "registered Skill resource read",
573
+ resolvedPath: resolved,
574
+ };
575
+ }
479
576
  // Sensitive: write is always denied, read always asks. Workspace placement
480
577
  // doesn't soften the rule — an `.env` in the project still asks on read.
481
578
  if (sensitiveLabel) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cjhyy/code-shell-core",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Core engine for code-shell — agent orchestration, tool execution, hooks, protocol. UI-agnostic.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",