@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 +1 -1
- package/dist/index.js +1 -1
- package/dist/tool-system/path-policy.js +99 -2
- package/package.json +1 -1
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.
|
|
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.
|
|
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