@wrongstack/core 0.299.0 → 0.301.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/dist/chronicle/index.d.ts +2 -0
- package/dist/chronicle/index.js +297 -10
- package/dist/chronicle/project-server.js +253 -15
- package/dist/chronicle/sqlite-compaction.d.ts +20 -0
- package/dist/chronicle/sqlite-journal.d.ts +57 -0
- package/dist/coordination/agents/capability-manifest.d.ts +7 -0
- package/dist/coordination/agents/index.d.ts +3 -2
- package/dist/coordination/agents/index.js +818 -570
- package/dist/coordination/agents/project-agent-identity.d.ts +6 -6
- package/dist/coordination/agents/role-skills.d.ts +1 -1
- package/dist/coordination/agents/types.d.ts +6 -0
- package/dist/coordination/director.d.ts +10 -2
- package/dist/coordination/fleet-manager.d.ts +48 -3
- package/dist/coordination/ifleet-manager.d.ts +2 -0
- package/dist/coordination/index.js +6880 -6440
- package/dist/coordination/mail-tools.d.ts +3 -3
- package/dist/coordination/mailbox-project-server.js +3 -4
- package/dist/coordination/mailbox-types.d.ts +6 -0
- package/dist/coordination/multi-agent-coordinator.d.ts +1 -0
- package/dist/core/agent-response.d.ts +5 -1
- package/dist/core/agent-tools.d.ts +3 -0
- package/dist/core/context.d.ts +20 -0
- package/dist/core/fallback-model.d.ts +48 -0
- package/dist/core/index.d.ts +11 -9
- package/dist/core/index.js +1119 -310
- package/dist/core/instruction-template.d.ts +83 -0
- package/dist/core/next-steps-slot.d.ts +88 -0
- package/dist/core/system-prompt-blocks.d.ts +10 -1
- package/dist/core/system-prompt-builder.d.ts +40 -4
- package/dist/core/system-prompt-memory-skills.d.ts +1 -0
- package/dist/core/system-prompt-skill-bodies.d.ts +3 -3
- package/dist/defaults/index.js +9805 -8767
- package/dist/design/index.js +11 -3
- package/dist/execution/auto-compaction-middleware.d.ts +17 -0
- package/dist/execution/autonomy-brain.d.ts +15 -2
- package/dist/execution/brain-runtime.d.ts +3 -1
- package/dist/execution/compaction-core.d.ts +41 -2
- package/dist/execution/council-brain.d.ts +47 -1
- package/dist/execution/council-orchestrator.d.ts +25 -6
- package/dist/execution/council-prompts.d.ts +12 -1
- package/dist/execution/index.d.ts +11 -10
- package/dist/execution/index.js +10507 -9189
- package/dist/execution/tool-executor.d.ts +4 -1
- package/dist/execution/topic-shift-advisor.d.ts +53 -0
- package/dist/extension/index.js +8 -2
- package/dist/extension/registry.d.ts +8 -1
- package/dist/fleet-notifier.d.ts +9 -2
- package/dist/goal/index.js +11 -3
- package/dist/hooks/index.js +50 -13
- package/dist/hooks/runner.d.ts +12 -1
- package/dist/hq/index.js +59 -21
- package/dist/hq/protocol/fleet.d.ts +20 -0
- package/dist/hq/protocol.js +10 -0
- package/dist/hq/publisher.d.ts +21 -3
- package/dist/index.d.ts +22 -20
- package/dist/index.js +12211 -8572
- package/dist/infrastructure/index.js +108 -21
- package/dist/kernel/events/brain-events.d.ts +9 -0
- package/dist/kernel/events/provider-events.d.ts +42 -1
- package/dist/kernel/events/session-events.d.ts +13 -0
- package/dist/kernel/events/tool-events.d.ts +3 -3
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +18 -1
- package/dist/models/provider-credentials.d.ts +54 -0
- package/dist/plugin/api.d.ts +6 -0
- package/dist/plugin/config.d.ts +55 -0
- package/dist/plugin/index.d.ts +2 -1
- package/dist/plugin/index.js +2179 -544
- package/dist/plugins/auto-review-plugin.d.ts +3 -0
- package/dist/plugins/cloud-config-sync-plugin.d.ts +22 -0
- package/dist/plugins/review-claim-registry.d.ts +23 -8
- package/dist/plugins/review-types.d.ts +2 -0
- package/dist/registry/index.js +109 -74
- package/dist/registry/tool-registry.d.ts +15 -0
- package/dist/security/capabilities.d.ts +2 -0
- package/dist/security/index.d.ts +2 -2
- package/dist/security/index.js +166 -44
- package/dist/security/permission-helpers.d.ts +23 -6
- package/dist/security/permission-policy.d.ts +16 -0
- package/dist/security/readonly-permission-policy.d.ts +20 -0
- package/dist/security/totp.d.ts +14 -0
- package/dist/session-registry-atomic-file.d.ts +32 -5
- package/dist/skills/frontmatter.d.ts +6 -0
- package/dist/skills/index.js +22 -5
- package/dist/storage/cloud-config-sync/sanitize.d.ts +54 -0
- package/dist/storage/cloud-config-sync.d.ts +87 -0
- package/dist/storage/director-state.d.ts +7 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +887 -74
- package/dist/tools/fallback-system-config-view-tool.d.ts +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +898 -335
- package/dist/tools/one-shot-llm-tool.d.ts +1 -0
- package/dist/tools/plugin-manager.d.ts +27 -0
- package/dist/types/config/mcp-features.d.ts +18 -11
- package/dist/types/config/root.d.ts +8 -1
- package/dist/types/config/runtime.d.ts +20 -6
- package/dist/types/config/skills-fleet-brain.d.ts +12 -4
- package/dist/types/config/tools.d.ts +16 -0
- package/dist/types/context-window.d.ts +1 -0
- package/dist/types/council.d.ts +11 -0
- package/dist/types/hooks.d.ts +14 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.js +1 -0
- package/dist/types/multi-agent.d.ts +12 -0
- package/dist/types/one-shot-llm.d.ts +25 -0
- package/dist/types/plugin.d.ts +28 -0
- package/dist/types/provider.d.ts +16 -0
- package/dist/types/runtime-capability-manifest.d.ts +168 -0
- package/dist/types/skill.d.ts +5 -0
- package/dist/types/system-prompt.d.ts +3 -1
- package/dist/types/tool-executor.d.ts +8 -1
- package/dist/utils/context-breakdown.d.ts +8 -2
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +141 -31
- package/dist/utils/regex-guard.d.ts +16 -2
- package/dist/utils/sage-output-block.d.ts +7 -10
- package/dist/utils/wstack-paths.d.ts +11 -3
- package/dist/worktree/index.js +4 -4
- package/instructions/agents/browser.md +1 -4
- package/instructions/agents/e2e.md +17 -15
- package/instructions/agents/ios.md +3 -3
- package/instructions/agents/search.md +1 -1
- package/instructions/autonomy/goal-preamble.md +4 -4
- package/instructions/coordination/director-preamble.md +2 -3
- package/instructions/coordination/subagent-baseline.md +3 -1
- package/instructions/leader-after-task.md +12 -0
- package/instructions/llm/chimera-review.md +1 -1
- package/instructions/modes/audit-lite.md +1 -1
- package/instructions/sections/tool/common-patterns.md +18 -2
- package/instructions/sections/tool/mailbox-compact.md +1 -1
- package/instructions/sections/tool/mailbox-full.md +1 -1
- package/instructions/system-lite.md +91 -3
- package/instructions/system-pro.md +296 -102
- package/instructions/system.md +248 -92
- package/package.json +3 -3
- package/skills/api-design/SKILL.md +3 -0
- package/skills/audit-log/SKILL.md +2 -0
- package/skills/auto-review/SKILL.md +6 -1
- package/skills/bug-hunter/SKILL.md +4 -1
- package/skills/chimera/SKILL.md +3 -0
- package/skills/data-governance/SKILL.md +3 -0
- package/skills/design-system/SKILL.md +5 -2
- package/skills/docker-deploy/SKILL.md +2 -0
- package/skills/git-flow/SKILL.md +2 -0
- package/skills/mailbox-bridge/SKILL.md +3 -0
- package/skills/mnemosyne/SKILL.md +2 -0
- package/skills/multi-agent/SKILL.md +4 -1
- package/skills/node-modern/SKILL.md +4 -1
- package/skills/observability/SKILL.md +3 -0
- package/skills/output-standards/SKILL.md +2 -0
- package/skills/plugin-author/SKILL.md +4 -1
- package/skills/prompt-engineering/SKILL.md +3 -1
- package/skills/react-modern/SKILL.md +6 -3
- package/skills/refactor-planner/SKILL.md +2 -0
- package/skills/research-web/SKILL.md +3 -0
- package/skills/sdd/SKILL.md +3 -1
- package/skills/security-scanner/SKILL.md +3 -0
- package/skills/skill-creator/SKILL.md +2 -0
- package/skills/tech-stack/SKILL.md +3 -0
- package/skills/testing/SKILL.md +4 -1
- package/skills/typescript-strict/SKILL.md +4 -1
- package/skills/wrongstack-kanban/SKILL.md +6 -3
- package/skills/wrongstack-mailbox/SKILL.md +4 -1
- package/skills/wrongstack-mailbox-mcp/SKILL.md +10 -8
package/dist/security/index.js
CHANGED
|
@@ -47,6 +47,8 @@ var ToolCapabilities = {
|
|
|
47
47
|
NET_OUTBOUND: "net.outbound",
|
|
48
48
|
/** Can mutate in-memory session todos only. */
|
|
49
49
|
SESSION_TODO: "session.todo",
|
|
50
|
+
/** Can park after-task `<nextsteps>` suggestions for the current turn. */
|
|
51
|
+
SESSION_NEXTSTEPS: "session.nextsteps",
|
|
50
52
|
/** Can mutate in-memory session mode only. */
|
|
51
53
|
SESSION_MODE: "session.mode",
|
|
52
54
|
/** Can inspect registered tool metadata. */
|
|
@@ -712,22 +714,22 @@ var UNSAFE_PROPERTY_NAMES = /* @__PURE__ */ new Set(["__proto__", "prototype", "
|
|
|
712
714
|
function isRecord(value) {
|
|
713
715
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
714
716
|
}
|
|
715
|
-
function error(diagnostics, code,
|
|
716
|
-
diagnostics.push({ severity: "error", code, path:
|
|
717
|
+
function error(diagnostics, code, path9, message) {
|
|
718
|
+
diagnostics.push({ severity: "error", code, path: path9, message });
|
|
717
719
|
}
|
|
718
|
-
function validateStringList(value,
|
|
720
|
+
function validateStringList(value, path9, diagnostics, limits) {
|
|
719
721
|
if (!Array.isArray(value)) {
|
|
720
|
-
error(diagnostics, limits.listErrorCode,
|
|
722
|
+
error(diagnostics, limits.listErrorCode, path9, `must be an array of strings`);
|
|
721
723
|
return void 0;
|
|
722
724
|
}
|
|
723
725
|
if (value.length > limits.maxItems) {
|
|
724
|
-
error(diagnostics, limits.tooManyCode,
|
|
726
|
+
error(diagnostics, limits.tooManyCode, path9, `must contain at most ${limits.maxItems} ${limits.itemLabel}s`);
|
|
725
727
|
return void 0;
|
|
726
728
|
}
|
|
727
729
|
const seen = /* @__PURE__ */ new Set();
|
|
728
730
|
const out = [];
|
|
729
731
|
for (const [index, entry] of value.entries()) {
|
|
730
|
-
const itemPath = `${
|
|
732
|
+
const itemPath = `${path9}[${index}]`;
|
|
731
733
|
if (typeof entry !== "string" || entry.length === 0 || entry.length > limits.maxChars) {
|
|
732
734
|
error(
|
|
733
735
|
diagnostics,
|
|
@@ -1467,6 +1469,7 @@ function numericValue(value) {
|
|
|
1467
1469
|
|
|
1468
1470
|
// src/security/permission-policy.ts
|
|
1469
1471
|
import * as fs from "node:fs/promises";
|
|
1472
|
+
import * as path6 from "node:path";
|
|
1470
1473
|
|
|
1471
1474
|
// src/utils/atomic-write.ts
|
|
1472
1475
|
import {
|
|
@@ -1767,19 +1770,19 @@ var UNSAFE_PROPERTY_NAMES2 = /* @__PURE__ */ new Set(["__proto__", "prototype",
|
|
|
1767
1770
|
function isRecord2(value) {
|
|
1768
1771
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1769
1772
|
}
|
|
1770
|
-
function error2(diagnostics, code,
|
|
1771
|
-
diagnostics.push({ severity: "error", code, path:
|
|
1773
|
+
function error2(diagnostics, code, path9, message) {
|
|
1774
|
+
diagnostics.push({ severity: "error", code, path: path9, message });
|
|
1772
1775
|
}
|
|
1773
|
-
function validatePatterns(value,
|
|
1776
|
+
function validatePatterns(value, path9, diagnostics) {
|
|
1774
1777
|
if (!Array.isArray(value)) {
|
|
1775
|
-
error2(diagnostics, "invalid_pattern_list",
|
|
1778
|
+
error2(diagnostics, "invalid_pattern_list", path9, "must be an array of strings");
|
|
1776
1779
|
return void 0;
|
|
1777
1780
|
}
|
|
1778
1781
|
if (value.length > TRUST_POLICY_LIMITS.maxPatternsPerRule) {
|
|
1779
1782
|
error2(
|
|
1780
1783
|
diagnostics,
|
|
1781
1784
|
"too_many_patterns",
|
|
1782
|
-
|
|
1785
|
+
path9,
|
|
1783
1786
|
`must contain at most ${TRUST_POLICY_LIMITS.maxPatternsPerRule} patterns`
|
|
1784
1787
|
);
|
|
1785
1788
|
return void 0;
|
|
@@ -1787,7 +1790,7 @@ function validatePatterns(value, path6, diagnostics) {
|
|
|
1787
1790
|
const patterns = [];
|
|
1788
1791
|
const seen = /* @__PURE__ */ new Set();
|
|
1789
1792
|
for (const [index, pattern] of value.entries()) {
|
|
1790
|
-
const itemPath = `${
|
|
1793
|
+
const itemPath = `${path9}[${index}]`;
|
|
1791
1794
|
if (typeof pattern !== "string" || pattern.length === 0 || pattern.length > TRUST_POLICY_LIMITS.maxPatternChars) {
|
|
1792
1795
|
error2(
|
|
1793
1796
|
diagnostics,
|
|
@@ -2201,6 +2204,19 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
2201
2204
|
}
|
|
2202
2205
|
};
|
|
2203
2206
|
|
|
2207
|
+
// src/security/permission-helpers.ts
|
|
2208
|
+
import { realpathSync } from "node:fs";
|
|
2209
|
+
import * as path5 from "node:path";
|
|
2210
|
+
|
|
2211
|
+
// src/utils/wstack-paths.ts
|
|
2212
|
+
import * as os from "node:os";
|
|
2213
|
+
import * as path4 from "node:path";
|
|
2214
|
+
function wstackGlobalRoot() {
|
|
2215
|
+
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
2216
|
+
if (fromEnv && fromEnv.trim().length > 0) return path4.resolve(fromEnv);
|
|
2217
|
+
return path4.join(os.homedir(), ".wrongstack");
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2204
2220
|
// src/security/permission-helpers.ts
|
|
2205
2221
|
function matchesTrust(patterns, subject) {
|
|
2206
2222
|
return patterns.includes(subject) || matchAny(patterns, subject);
|
|
@@ -2272,9 +2288,51 @@ var SHELL_READ_VERBS = /* @__PURE__ */ new Set([
|
|
|
2272
2288
|
function stripShellQuotes(value) {
|
|
2273
2289
|
return value.replace(/^['"]|['"]$/g, "");
|
|
2274
2290
|
}
|
|
2291
|
+
var AGENT_STATE_SENSITIVE_BASENAMES = /^(?:config\.json|config\.local\.json|trust\.json|auth\.json|\.key)$/i;
|
|
2292
|
+
function unescapeGlobSubject(value) {
|
|
2293
|
+
return value.replace(/\\([*?[\]])/g, "$1");
|
|
2294
|
+
}
|
|
2295
|
+
function normalizeForCompare(value) {
|
|
2296
|
+
const forward = unescapeGlobSubject(value).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
2297
|
+
return process.platform === "win32" ? forward.toLowerCase() : forward;
|
|
2298
|
+
}
|
|
2299
|
+
function realpathOfNearestExisting(p) {
|
|
2300
|
+
let probe = p;
|
|
2301
|
+
const tail = [];
|
|
2302
|
+
for (; ; ) {
|
|
2303
|
+
try {
|
|
2304
|
+
return tail.length === 0 ? realpathSync(probe) : path5.join(realpathSync(probe), ...tail);
|
|
2305
|
+
} catch {
|
|
2306
|
+
const parent = path5.dirname(probe);
|
|
2307
|
+
if (parent === probe) return p;
|
|
2308
|
+
tail.unshift(path5.basename(probe));
|
|
2309
|
+
probe = parent;
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
var agentStateRootRealCache = /* @__PURE__ */ new Map();
|
|
2314
|
+
function isInsideAgentStateRoot(absPath) {
|
|
2315
|
+
const lexicalRoot = normalizeForCompare(path5.resolve(wstackGlobalRoot()));
|
|
2316
|
+
if (!lexicalRoot) return false;
|
|
2317
|
+
const target = normalizeForCompare(absPath);
|
|
2318
|
+
if (target === lexicalRoot || target.startsWith(`${lexicalRoot}/`)) return true;
|
|
2319
|
+
let realRoot = agentStateRootRealCache.get(lexicalRoot);
|
|
2320
|
+
if (realRoot === void 0) {
|
|
2321
|
+
realRoot = normalizeForCompare(realpathOfNearestExisting(path5.resolve(wstackGlobalRoot())));
|
|
2322
|
+
agentStateRootRealCache.set(lexicalRoot, realRoot);
|
|
2323
|
+
}
|
|
2324
|
+
if (!realRoot || realRoot === lexicalRoot) return false;
|
|
2325
|
+
const realTarget = normalizeForCompare(realpathOfNearestExisting(absPath));
|
|
2326
|
+
return realTarget === realRoot || realTarget.startsWith(`${realRoot}/`);
|
|
2327
|
+
}
|
|
2328
|
+
function isProtectedAgentStatePath(absPath) {
|
|
2329
|
+
if (!isInsideAgentStateRoot(absPath)) return false;
|
|
2330
|
+
return AGENT_STATE_SENSITIVE_BASENAMES.test(path5.basename(normalizeForCompare(absPath)));
|
|
2331
|
+
}
|
|
2275
2332
|
function pathLooksSensitive(rawPath) {
|
|
2276
2333
|
const normalized = stripShellQuotes(rawPath).replace(/\\/g, "/");
|
|
2277
|
-
|
|
2334
|
+
if (SENSITIVE_READ_PATHS.some((pattern) => pattern.test(normalized))) return true;
|
|
2335
|
+
return isProtectedAgentStatePath(normalized);
|
|
2278
2336
|
}
|
|
2279
2337
|
function inputPathLooksSensitive(input) {
|
|
2280
2338
|
if (!input || typeof input !== "object") return false;
|
|
@@ -2298,6 +2356,34 @@ function shellCommandReadsSensitivePath(command) {
|
|
|
2298
2356
|
}
|
|
2299
2357
|
|
|
2300
2358
|
// src/security/permission-policy.ts
|
|
2359
|
+
function fsWriteTargetPaths(input) {
|
|
2360
|
+
const out = [];
|
|
2361
|
+
if (!input || typeof input !== "object") return out;
|
|
2362
|
+
const obj = input;
|
|
2363
|
+
for (const key of [
|
|
2364
|
+
"path",
|
|
2365
|
+
"file_path",
|
|
2366
|
+
"file",
|
|
2367
|
+
"filePath",
|
|
2368
|
+
"files",
|
|
2369
|
+
"target",
|
|
2370
|
+
"targetPath",
|
|
2371
|
+
"out",
|
|
2372
|
+
"directory",
|
|
2373
|
+
"cwd",
|
|
2374
|
+
"template"
|
|
2375
|
+
]) {
|
|
2376
|
+
const value = obj[key];
|
|
2377
|
+
if (typeof value === "string") {
|
|
2378
|
+
if (value.length > 0) out.push(value);
|
|
2379
|
+
} else if (Array.isArray(value)) {
|
|
2380
|
+
for (const item of value) {
|
|
2381
|
+
if (typeof item === "string" && item.length > 0) out.push(item);
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
return out;
|
|
2386
|
+
}
|
|
2301
2387
|
var DefaultPermissionPolicy = class {
|
|
2302
2388
|
policy = {};
|
|
2303
2389
|
loaded = false;
|
|
@@ -2383,6 +2469,30 @@ var DefaultPermissionPolicy = class {
|
|
|
2383
2469
|
getYoloDestructive() {
|
|
2384
2470
|
return this.yoloDestructive;
|
|
2385
2471
|
}
|
|
2472
|
+
/**
|
|
2473
|
+
* True when this call is an FS_WRITE whose target lands inside WrongStack's
|
|
2474
|
+
* own state root.
|
|
2475
|
+
*
|
|
2476
|
+
* FS_WRITE tools disagree on where the target path lives: write/edit use
|
|
2477
|
+
* `path`, replace/format/git `files` (string OR array), patch `directory`,
|
|
2478
|
+
* design `out`, scaffold writes under `cwd`/`template`. Checking only
|
|
2479
|
+
* `path`/`file_path` let a state-root write merely switch tools, so every
|
|
2480
|
+
* path-bearing key is inspected.
|
|
2481
|
+
*
|
|
2482
|
+
* Shared by the YOLO carve-out and the eval-cache guard so the two cannot
|
|
2483
|
+
* drift: the carve-out scans all path keys while the cache key fingerprints
|
|
2484
|
+
* only the subject key, so the cache must consult the same predicate or a
|
|
2485
|
+
* secondary state-root key can be replayed a cached `auto`.
|
|
2486
|
+
*/
|
|
2487
|
+
hasAgentStateWriteTarget(tool, input, ctx) {
|
|
2488
|
+
if (!hasCapability(tool, ToolCapabilities.FS_WRITE)) return false;
|
|
2489
|
+
for (const targetPath of fsWriteTargetPaths(input)) {
|
|
2490
|
+
const base = ctx.workingDir ?? ctx.cwd;
|
|
2491
|
+
const resolved = base ? path6.resolve(base, targetPath) : path6.resolve(targetPath);
|
|
2492
|
+
if (isInsideAgentStateRoot(resolved)) return true;
|
|
2493
|
+
}
|
|
2494
|
+
return false;
|
|
2495
|
+
}
|
|
2386
2496
|
/**
|
|
2387
2497
|
* True when YOLO is on but this specific call is a destructive shell command
|
|
2388
2498
|
* the user has not opted to auto-approve. Shared by `evaluate()` and the
|
|
@@ -2390,6 +2500,7 @@ var DefaultPermissionPolicy = class {
|
|
|
2390
2500
|
*/
|
|
2391
2501
|
yoloBlockedAsDestructive(tool, input, ctx) {
|
|
2392
2502
|
if (!this.yolo || this.yoloDestructive) return false;
|
|
2503
|
+
if (this.hasAgentStateWriteTarget(tool, input, ctx)) return true;
|
|
2393
2504
|
const isShellSurface = tool.name === "bash" || tool.name === "exec" || (tool.capabilities ?? []).includes("shell.arbitrary");
|
|
2394
2505
|
if (!isShellSurface) return false;
|
|
2395
2506
|
const command = getInputString(input, "command") ?? shellCommandLineFromInput(input);
|
|
@@ -2496,7 +2607,7 @@ var DefaultPermissionPolicy = class {
|
|
|
2496
2607
|
const subject = subjectForToolInput(tool.name, input, tool.subjectKey);
|
|
2497
2608
|
const cacheKey = `${tool.name}::${subject ?? tool.name}`;
|
|
2498
2609
|
const evalKey = `${cacheKey}::${permissionFingerprint(tool)}`;
|
|
2499
|
-
if (tool.name !== "write") {
|
|
2610
|
+
if (tool.name !== "write" && !this.hasAgentStateWriteTarget(tool, input, ctx)) {
|
|
2500
2611
|
const cached = this._evalCache.get(evalKey);
|
|
2501
2612
|
if (cached !== void 0) return cached;
|
|
2502
2613
|
}
|
|
@@ -2604,7 +2715,7 @@ var DefaultPermissionPolicy = class {
|
|
|
2604
2715
|
return decision;
|
|
2605
2716
|
}
|
|
2606
2717
|
if (tool.name === "write" && subject) {
|
|
2607
|
-
if (ctx.hasRead(subject)) {
|
|
2718
|
+
if (ctx.hasRead(subject) && !isInsideAgentStateRoot(subject)) {
|
|
2608
2719
|
return {
|
|
2609
2720
|
permission: "auto",
|
|
2610
2721
|
source: "context",
|
|
@@ -2954,13 +3065,14 @@ var DefaultPermissionPolicy = class {
|
|
|
2954
3065
|
}
|
|
2955
3066
|
add("yolo", false, "auto", "yolo", "YOLO mode is not active");
|
|
2956
3067
|
if (tool.name === "write" && subject) {
|
|
2957
|
-
const
|
|
3068
|
+
const isAgentState = isInsideAgentStateRoot(subject);
|
|
3069
|
+
const hasRead = ctx.hasRead(subject) && !isAgentState;
|
|
2958
3070
|
add(
|
|
2959
3071
|
"write smart bypass",
|
|
2960
3072
|
hasRead,
|
|
2961
3073
|
"auto",
|
|
2962
3074
|
"context",
|
|
2963
|
-
hasRead ? `file "${subject}" was already read in this session \u2014 auto-approving write` : `file "${subject}" was not read in this session \u2014 bypass does not apply`
|
|
3075
|
+
isAgentState ? `file "${subject}" is WrongStack's own state \u2014 bypass never applies, write always confirms` : hasRead ? `file "${subject}" was already read in this session \u2014 auto-approving write` : `file "${subject}" was not read in this session \u2014 bypass does not apply`
|
|
2964
3076
|
);
|
|
2965
3077
|
if (hasRead) {
|
|
2966
3078
|
winnerIndex = steps.length - 1;
|
|
@@ -3042,7 +3154,7 @@ var DefaultPermissionPolicy = class {
|
|
|
3042
3154
|
};
|
|
3043
3155
|
|
|
3044
3156
|
// src/security/readonly-permission-policy.ts
|
|
3045
|
-
import * as
|
|
3157
|
+
import * as path7 from "node:path";
|
|
3046
3158
|
var MUTATION_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
3047
3159
|
ToolCapabilities.FS_WRITE,
|
|
3048
3160
|
ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
|
|
@@ -3056,9 +3168,11 @@ var MUTATION_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
|
3056
3168
|
ToolCapabilities.PACKAGE_INSTALL,
|
|
3057
3169
|
ToolCapabilities.SUBAGENT_SPAWN
|
|
3058
3170
|
]);
|
|
3059
|
-
function
|
|
3171
|
+
function toolMutates(tool) {
|
|
3172
|
+
if (tool.mutating === true) return true;
|
|
3060
3173
|
return (tool.capabilities ?? []).some((c) => MUTATION_CAPABILITIES.has(c));
|
|
3061
3174
|
}
|
|
3175
|
+
var hasMutationCapability = toolMutates;
|
|
3062
3176
|
function isAllowedReportFile(input, projectRoot) {
|
|
3063
3177
|
if (!input || typeof input !== "object") return false;
|
|
3064
3178
|
const record = input;
|
|
@@ -3066,9 +3180,9 @@ function isAllowedReportFile(input, projectRoot) {
|
|
|
3066
3180
|
if (typeof inputPath !== "string") return false;
|
|
3067
3181
|
if (!inputPath.endsWith(".md")) return false;
|
|
3068
3182
|
try {
|
|
3069
|
-
const resolved =
|
|
3070
|
-
const tempDir =
|
|
3071
|
-
if (!resolved.startsWith(tempDir +
|
|
3183
|
+
const resolved = path7.resolve(projectRoot, inputPath);
|
|
3184
|
+
const tempDir = path7.resolve(projectRoot, ".temp_files");
|
|
3185
|
+
if (!resolved.startsWith(tempDir + path7.sep) && resolved !== tempDir) return false;
|
|
3072
3186
|
return true;
|
|
3073
3187
|
} catch {
|
|
3074
3188
|
return false;
|
|
@@ -3091,10 +3205,12 @@ var ReadOnlyPermissionPolicy = class {
|
|
|
3091
3205
|
if (isAllowedReportFile(input, this.projectRoot)) {
|
|
3092
3206
|
return this.inner.evaluate(tool, input, ctx);
|
|
3093
3207
|
}
|
|
3208
|
+
const matched = (tool.capabilities ?? []).filter((c) => MUTATION_CAPABILITIES.has(c));
|
|
3209
|
+
const why = matched.length > 0 ? `requires mutation capabilities (${matched.join(", ")})` : "is declared mutating";
|
|
3094
3210
|
return {
|
|
3095
3211
|
permission: "deny",
|
|
3096
3212
|
source: "readonly_mode",
|
|
3097
|
-
reason: `Session is in read-only mode: tool "${tool.name}"
|
|
3213
|
+
reason: `Session is in read-only mode: tool "${tool.name}" ${why}, which is not allowed. Only .md report files under .temp_files/ may be written.`
|
|
3098
3214
|
};
|
|
3099
3215
|
}
|
|
3100
3216
|
async trust(rule) {
|
|
@@ -3217,16 +3333,20 @@ function generateTotp(secretBase32, atMs = Date.now(), stepSeconds = DEFAULT_STE
|
|
|
3217
3333
|
return hotp(secret, counter, digits);
|
|
3218
3334
|
}
|
|
3219
3335
|
function verifyTotp(code, secretBase32, atMs = Date.now(), window = DEFAULT_WINDOW, stepSeconds = DEFAULT_STEP_SECONDS, digits = DEFAULT_DIGITS) {
|
|
3220
|
-
|
|
3336
|
+
return verifyTotpCounter(code, secretBase32, atMs, window, stepSeconds, digits) !== void 0;
|
|
3337
|
+
}
|
|
3338
|
+
function verifyTotpCounter(code, secretBase32, atMs = Date.now(), window = DEFAULT_WINDOW, stepSeconds = DEFAULT_STEP_SECONDS, digits = DEFAULT_DIGITS) {
|
|
3339
|
+
if (typeof code !== "string" || code.length !== digits) return void 0;
|
|
3221
3340
|
const secret = base32Decode(secretBase32);
|
|
3222
3341
|
const currentCounter = totpCounter(Math.floor(atMs / 1e3), stepSeconds);
|
|
3223
3342
|
for (let offset = -window; offset <= window; offset++) {
|
|
3224
|
-
const
|
|
3343
|
+
const counter = currentCounter + offset;
|
|
3344
|
+
const expected = hotp(secret, counter, digits);
|
|
3225
3345
|
if (timingSafeEqual(Buffer.from(expected), Buffer.from(code))) {
|
|
3226
|
-
return
|
|
3346
|
+
return counter;
|
|
3227
3347
|
}
|
|
3228
3348
|
}
|
|
3229
|
-
return
|
|
3349
|
+
return void 0;
|
|
3230
3350
|
}
|
|
3231
3351
|
function buildOtpAuthUri(secretBase32, account, issuer = "WrongStack HQ", stepSeconds = DEFAULT_STEP_SECONDS, digits = DEFAULT_DIGITS) {
|
|
3232
3352
|
const label = `${encodeURIComponent(issuer)}:${encodeURIComponent(account)}`;
|
|
@@ -3275,7 +3395,7 @@ function verifyRecoveryCode(code, storedHashes) {
|
|
|
3275
3395
|
import { createCipheriv, createDecipheriv, randomBytes as randomBytes2, scryptSync } from "node:crypto";
|
|
3276
3396
|
import * as fs2 from "node:fs";
|
|
3277
3397
|
import * as fsp from "node:fs/promises";
|
|
3278
|
-
import * as
|
|
3398
|
+
import * as path8 from "node:path";
|
|
3279
3399
|
|
|
3280
3400
|
// src/utils/deep-merge.ts
|
|
3281
3401
|
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -3578,7 +3698,7 @@ var DefaultSecretVault = class {
|
|
|
3578
3698
|
const oldVersion = this._keyVersion;
|
|
3579
3699
|
const newKey = randomBytes2(KEY_BYTES);
|
|
3580
3700
|
const newVersion = oldVersion + 1;
|
|
3581
|
-
fs2.mkdirSync(
|
|
3701
|
+
fs2.mkdirSync(path8.dirname(this.keyFile), { recursive: true });
|
|
3582
3702
|
const passphrase = getVaultPassphrase();
|
|
3583
3703
|
if (passphrase) {
|
|
3584
3704
|
writeKeyFileAtomicSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase));
|
|
@@ -3665,7 +3785,7 @@ var DefaultSecretVault = class {
|
|
|
3665
3785
|
} catch (err) {
|
|
3666
3786
|
if (err.code !== "ENOENT") throw err;
|
|
3667
3787
|
}
|
|
3668
|
-
fs2.mkdirSync(
|
|
3788
|
+
fs2.mkdirSync(path8.dirname(this.keyFile), { recursive: true });
|
|
3669
3789
|
const key = randomBytes2(KEY_BYTES);
|
|
3670
3790
|
const passphrase = getVaultPassphrase();
|
|
3671
3791
|
const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
|
|
@@ -3725,7 +3845,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
|
3725
3845
|
}
|
|
3726
3846
|
const merged = deepMerge(current, patch ?? {});
|
|
3727
3847
|
const encrypted = encryptConfigSecrets(merged, vault);
|
|
3728
|
-
await fsp.mkdir(
|
|
3848
|
+
await fsp.mkdir(path8.dirname(configPath), { recursive: true });
|
|
3729
3849
|
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
3730
3850
|
await restrictFilePermissions2(configPath);
|
|
3731
3851
|
await vault.flushHardening?.();
|
|
@@ -3947,33 +4067,33 @@ var RESERVED_BYPASS_FIELDS = /* @__PURE__ */ new Set([
|
|
|
3947
4067
|
function isRecord3(value) {
|
|
3948
4068
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3949
4069
|
}
|
|
3950
|
-
function addString(issues, value,
|
|
4070
|
+
function addString(issues, value, path9, required = true) {
|
|
3951
4071
|
if (typeof value === "string" && value.trim().length > 0) return true;
|
|
3952
4072
|
if (!required && value === void 0) return false;
|
|
3953
|
-
issues.push({ path:
|
|
4073
|
+
issues.push({ path: path9, message: "must be a non-empty string" });
|
|
3954
4074
|
return false;
|
|
3955
4075
|
}
|
|
3956
|
-
function addEnum(issues, value,
|
|
4076
|
+
function addEnum(issues, value, path9, values) {
|
|
3957
4077
|
if (typeof value === "string" && values.has(value)) return true;
|
|
3958
|
-
issues.push({ path:
|
|
4078
|
+
issues.push({ path: path9, message: `must be one of: ${[...values].join(", ")}` });
|
|
3959
4079
|
return false;
|
|
3960
4080
|
}
|
|
3961
|
-
function validateAttributes(issues, value,
|
|
4081
|
+
function validateAttributes(issues, value, path9) {
|
|
3962
4082
|
if (!isRecord3(value)) {
|
|
3963
|
-
issues.push({ path:
|
|
4083
|
+
issues.push({ path: path9, message: "must be an object" });
|
|
3964
4084
|
return false;
|
|
3965
4085
|
}
|
|
3966
4086
|
let valid = true;
|
|
3967
4087
|
for (const [key, item] of Object.entries(value)) {
|
|
3968
4088
|
if (RESERVED_BYPASS_FIELDS.has(key)) {
|
|
3969
4089
|
issues.push({
|
|
3970
|
-
path: `${
|
|
4090
|
+
path: `${path9}.${key}`,
|
|
3971
4091
|
message: "generic authorization bypass fields are forbidden"
|
|
3972
4092
|
});
|
|
3973
4093
|
valid = false;
|
|
3974
4094
|
} else if (item !== null && !["string", "number", "boolean"].includes(typeof item)) {
|
|
3975
4095
|
issues.push({
|
|
3976
|
-
path: `${
|
|
4096
|
+
path: `${path9}.${key}`,
|
|
3977
4097
|
message: "must be a string, number, boolean, or null"
|
|
3978
4098
|
});
|
|
3979
4099
|
valid = false;
|
|
@@ -3981,16 +4101,16 @@ function validateAttributes(issues, value, path6) {
|
|
|
3981
4101
|
}
|
|
3982
4102
|
return valid;
|
|
3983
4103
|
}
|
|
3984
|
-
function validateScope(issues, value,
|
|
4104
|
+
function validateScope(issues, value, path9) {
|
|
3985
4105
|
if (!isRecord3(value)) {
|
|
3986
|
-
issues.push({ path:
|
|
4106
|
+
issues.push({ path: path9, message: "must be an object" });
|
|
3987
4107
|
return false;
|
|
3988
4108
|
}
|
|
3989
4109
|
for (const field of ["projectId", "sessionId", "cwd"]) {
|
|
3990
|
-
if (value[field] !== void 0) addString(issues, value[field], `${
|
|
4110
|
+
if (value[field] !== void 0) addString(issues, value[field], `${path9}.${field}`);
|
|
3991
4111
|
}
|
|
3992
4112
|
if (value.constraints !== void 0)
|
|
3993
|
-
validateAttributes(issues, value.constraints, `${
|
|
4113
|
+
validateAttributes(issues, value.constraints, `${path9}.constraints`);
|
|
3994
4114
|
return true;
|
|
3995
4115
|
}
|
|
3996
4116
|
function decodeTrustBoundaryRequest(input) {
|
|
@@ -4113,9 +4233,11 @@ export {
|
|
|
4113
4233
|
sanitizeApiError,
|
|
4114
4234
|
scrubErrorDetail,
|
|
4115
4235
|
scrubErrorText,
|
|
4236
|
+
toolMutates,
|
|
4116
4237
|
validateDirectoryPolicy,
|
|
4117
4238
|
validateTrustPolicy,
|
|
4118
4239
|
verifyRecoveryCode,
|
|
4119
|
-
verifyTotp
|
|
4240
|
+
verifyTotp,
|
|
4241
|
+
verifyTotpCounter
|
|
4120
4242
|
};
|
|
4121
4243
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure helpers for permission policy evaluation — trust-pattern matching,
|
|
3
|
-
* tool fingerprinting, and sensitive-read detection.
|
|
4
|
-
*
|
|
5
|
-
* Extracted from permission-policy.ts. No class state, no I/O.
|
|
6
|
-
*/
|
|
7
1
|
import type { Tool } from '../types/tool.js';
|
|
8
2
|
/**
|
|
9
3
|
* Match a computed subject against stored trust patterns.
|
|
@@ -88,6 +82,29 @@ export declare function alwaysAllowUnavailableReason(tool: Tool, input: unknown)
|
|
|
88
82
|
*/
|
|
89
83
|
export declare function permissionFingerprint(tool: Tool): string;
|
|
90
84
|
export declare function shellCommandLineFromInput(input: unknown): string | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* True when `absPath` is the wstack global root (`~/.wrongstack`, or
|
|
87
|
+
* `WRONGSTACK_HOME`) or lives inside it.
|
|
88
|
+
*
|
|
89
|
+
* Callers use this to refuse *silent* access to WrongStack's own state. It is
|
|
90
|
+
* not a containment check — the file tools deliberately allow this root so the
|
|
91
|
+
* agent can manage its memory and sessions. What it gates is whether that
|
|
92
|
+
* access may happen without the user seeing it.
|
|
93
|
+
*
|
|
94
|
+
* The comparison is realpath-aware on both sides: `~/.wrongstack` may itself
|
|
95
|
+
* be a symlink, and the file tools canonicalize subjects to realpaths
|
|
96
|
+
* (`safeResolveReal`, WS-048), so a purely lexical check misses every state
|
|
97
|
+
* file under a symlinked root. The lexical pass stays first — it is free and
|
|
98
|
+
* correct for the default layout — and the realpath pass only runs when it
|
|
99
|
+
* fails, comparing the root (cached) against the subject's nearest existing
|
|
100
|
+
* ancestor.
|
|
101
|
+
*/
|
|
102
|
+
export declare function isInsideAgentStateRoot(absPath: string): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* True when writing `absPath` must not be silently auto-approved, because the
|
|
105
|
+
* file is part of WrongStack's own trusted state rather than project content.
|
|
106
|
+
*/
|
|
107
|
+
export declare function isProtectedAgentStatePath(absPath: string): boolean;
|
|
91
108
|
export declare function inputPathLooksSensitive(input: unknown): boolean;
|
|
92
109
|
export declare function shellCommandReadsSensitivePath(command: string): boolean;
|
|
93
110
|
//# sourceMappingURL=permission-helpers.d.ts.map
|
|
@@ -92,6 +92,22 @@ export declare class DefaultPermissionPolicy implements PermissionPolicy {
|
|
|
92
92
|
setYoloDestructive(enabled: boolean): void;
|
|
93
93
|
/** Whether YOLO currently extends to destructive shell commands. */
|
|
94
94
|
getYoloDestructive(): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* True when this call is an FS_WRITE whose target lands inside WrongStack's
|
|
97
|
+
* own state root.
|
|
98
|
+
*
|
|
99
|
+
* FS_WRITE tools disagree on where the target path lives: write/edit use
|
|
100
|
+
* `path`, replace/format/git `files` (string OR array), patch `directory`,
|
|
101
|
+
* design `out`, scaffold writes under `cwd`/`template`. Checking only
|
|
102
|
+
* `path`/`file_path` let a state-root write merely switch tools, so every
|
|
103
|
+
* path-bearing key is inspected.
|
|
104
|
+
*
|
|
105
|
+
* Shared by the YOLO carve-out and the eval-cache guard so the two cannot
|
|
106
|
+
* drift: the carve-out scans all path keys while the cache key fingerprints
|
|
107
|
+
* only the subject key, so the cache must consult the same predicate or a
|
|
108
|
+
* secondary state-root key can be replayed a cached `auto`.
|
|
109
|
+
*/
|
|
110
|
+
private hasAgentStateWriteTarget;
|
|
95
111
|
/**
|
|
96
112
|
* True when YOLO is on but this specific call is a destructive shell command
|
|
97
113
|
* the user has not opted to auto-approve. Shared by `evaluate()` and the
|
|
@@ -31,6 +31,26 @@
|
|
|
31
31
|
import type { Context } from '../core/context.js';
|
|
32
32
|
import type { PermissionDecision, PermissionPolicy, PermissionTrace } from '../types/permission.js';
|
|
33
33
|
import type { Tool } from '../types/tool.js';
|
|
34
|
+
/**
|
|
35
|
+
* Does this tool change something?
|
|
36
|
+
*
|
|
37
|
+
* `capabilities` is the precise answer and stays authoritative — but it is
|
|
38
|
+
* OPT-IN metadata, and a repo-wide count found 154 tools declaring
|
|
39
|
+
* `mutating: true` of which 121 carried no mutation capability at all (28 of
|
|
40
|
+
* those at `permission: 'auto'`, including tools that write API keys and one
|
|
41
|
+
* that sends messages outbound). Read-only mode consulted only `capabilities`,
|
|
42
|
+
* so every one of them ran unblocked and unprompted, while
|
|
43
|
+
* `permission-policy.ts` was already answering the same question with
|
|
44
|
+
* `tool.mutating || …`.
|
|
45
|
+
*
|
|
46
|
+
* Trusting `mutating` here closes all 121 at once and makes the two policies
|
|
47
|
+
* agree. It fails in the safe direction: a tool that declares itself mutating
|
|
48
|
+
* is blocked in read-only mode even if its capability list is incomplete.
|
|
49
|
+
* Fixing the capability labels is still worth doing — it is what the finer
|
|
50
|
+
* grained gates key on — but it is no longer what stands between read-only
|
|
51
|
+
* mode and a write.
|
|
52
|
+
*/
|
|
53
|
+
export declare function toolMutates(tool: Tool): boolean;
|
|
34
54
|
/**
|
|
35
55
|
* Read-only permission policy — session-scoped toggle via ctx.meta['readOnly'].
|
|
36
56
|
*
|
package/dist/security/totp.d.ts
CHANGED
|
@@ -55,6 +55,20 @@ export declare function generateTotp(secretBase32: string, atMs?: number, stepSe
|
|
|
55
55
|
* @returns `true` if the code matches any step within the window.
|
|
56
56
|
*/
|
|
57
57
|
export declare function verifyTotp(code: string, secretBase32: string, atMs?: number, window?: number, stepSeconds?: number, digits?: number): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Like {@link verifyTotp}, but returns the time-step counter the code matched
|
|
60
|
+
* so the caller can enforce single use.
|
|
61
|
+
*
|
|
62
|
+
* RFC 6238 §5.2 requires that a code be accepted only once: the validation
|
|
63
|
+
* window is ±1 step by default, so a code observed in transit stays
|
|
64
|
+
* arithmetically valid for up to ~90 seconds. Recording the matched counter and
|
|
65
|
+
* refusing anything at or below it turns that window into one-shot. HQ's
|
|
66
|
+
* `/api/login/verify` does this against `mutableAuth.totpLastUsedCounter`.
|
|
67
|
+
*
|
|
68
|
+
* @returns the matched counter, or `undefined` when no step in the window
|
|
69
|
+
* matches.
|
|
70
|
+
*/
|
|
71
|
+
export declare function verifyTotpCounter(code: string, secretBase32: string, atMs?: number, window?: number, stepSeconds?: number, digits?: number): number | undefined;
|
|
58
72
|
/**
|
|
59
73
|
* Build an `otpauth://` URI for QR-code provisioning (RFC 6238 / Google Auth).
|
|
60
74
|
*
|
|
@@ -1,12 +1,39 @@
|
|
|
1
1
|
import type { SessionRegistryEntry } from './session-registry-types.js';
|
|
2
2
|
/** Also the scan interval for {@link pruneStaleTempFiles} — see its caller. */
|
|
3
3
|
export declare const STALE_TMP_MS = 60000;
|
|
4
|
+
/** `host:pid` owner stamp written into the advisory lock on acquisition. */
|
|
5
|
+
export declare function lockOwnerStamp(): string;
|
|
4
6
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Unlink `lockPath` only if it still carries OUR exact host:pid stamp. A
|
|
8
|
+
* stale-lock breaker may have stolen the lock and re-stamped it with another
|
|
9
|
+
* host:pid; the resumed holder must not remove the new owner's lock. An EMPTY
|
|
10
|
+
* read is never ours to remove either — it may be the new owner's lock in its
|
|
11
|
+
* open-to-stamp gap (that lock is mtime-bounded instead).
|
|
12
|
+
*
|
|
13
|
+
* Residual race: between the owner read and the unlink, a foreign-host lease
|
|
14
|
+
* break could replace the lock; unlinking would then remove the new owner's
|
|
15
|
+
* lock. Exploiting it requires a >STALE_LOCK_MS hold AND a rename+stamp
|
|
16
|
+
* landing inside that microsecond window, so it is accepted and bounded by
|
|
17
|
+
* the same foreign-lease logic that enabled it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function maybeUnlinkOwnedLock(lockPath: string): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Break a contended lock if it is stale: a same-host owner pid that is no
|
|
22
|
+
* longer alive, or a foreign-host/ownerless lock older than
|
|
23
|
+
* {@link STALE_LOCK_MS}. A live same-host owner's lock is only broken past
|
|
24
|
+
* {@link SAME_HOST_STALE_MS} — a live-but-wedged holder must not degrade
|
|
25
|
+
* cross-session writes forever, but mtime alone must not false-positive while
|
|
26
|
+
* the holder is mid-critical-section. Returns true when the lock was removed
|
|
27
|
+
* (caller should retry acquisition).
|
|
28
|
+
*
|
|
29
|
+
* Removal is an atomic RENAME to a unique tombstone, never unlink: with
|
|
30
|
+
* unlink, two waiters can both read a stale lock and one's unlink can remove
|
|
31
|
+
* the OTHER's fresh lock, letting both into the critical section (POSIX).
|
|
32
|
+
* Rename wins for exactly one waiter; losers get ENOENT and simply retry.
|
|
33
|
+
* (A rename landing after the winner re-created the lock could still move the
|
|
34
|
+
* winner's fresh lock — a pre-existing microsecond TOCTOU; the caller's
|
|
35
|
+
* stamp-guarded cleanup prevents the resumed holder from unlinking the new
|
|
36
|
+
* owner's lock.)
|
|
10
37
|
*/
|
|
11
38
|
export declare function breakStaleLock(lockPath: string): Promise<boolean>;
|
|
12
39
|
export declare function writeAtomicFile(filePath: string, registry: Record<string, SessionRegistryEntry>): Promise<void>;
|
|
@@ -28,6 +28,12 @@ export interface ParsedSkillFrontmatter {
|
|
|
28
28
|
* consult this field, so it neither grants nor restricts any tool.
|
|
29
29
|
*/
|
|
30
30
|
allowedTools?: string[] | undefined;
|
|
31
|
+
/** WrongStack stable runtime capabilities required before this skill may load. */
|
|
32
|
+
requiredCapabilities?: string[] | undefined;
|
|
33
|
+
/** Exact registered tool names required before this skill may render or load. */
|
|
34
|
+
requiredTools?: string[] | undefined;
|
|
35
|
+
/** Capabilities that improve the workflow but have documented fallbacks. */
|
|
36
|
+
optionalCapabilities?: string[] | undefined;
|
|
31
37
|
}
|
|
32
38
|
/**
|
|
33
39
|
* Parse the YAML frontmatter block from a raw SKILL.md file. Returns `{}` when
|