opencode-swarm 7.122.0 → 7.122.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.
Files changed (63) hide show
  1. package/dist/cli/{config-doctor-404c8mf8.js → config-doctor-3ksh1534.js} +2 -2
  2. package/dist/cli/{core-xnwasfpc.js → core-cwgb7ev8.js} +1 -1
  3. package/dist/cli/{curation-policy-cj1tpzr5.js → curation-policy-4j4qce5a.js} +5 -5
  4. package/dist/cli/{curator-llm-factory-8rg0f77t.js → curator-llm-factory-x769hn66.js} +22 -20
  5. package/dist/cli/{curator-3rze3pd4.js → curator-qfh07497.js} +22 -20
  6. package/dist/cli/{evidence-summary-service-k14ykm03.js → evidence-summary-service-96ndczbk.js} +7 -5
  7. package/dist/cli/{gate-evidence-tbpezk28.js → gate-evidence-xqks3kk2.js} +3 -3
  8. package/dist/cli/{guardrail-explain-hdsxa2c5.js → guardrail-explain-kj0e9r25.js} +23 -21
  9. package/dist/cli/{guardrail-log-dhpp0kmn.js → guardrail-log-z291m4gw.js} +3 -3
  10. package/dist/cli/{hive-promoter-dh38qd9m.js → hive-promoter-00mktxkk.js} +22 -20
  11. package/dist/cli/{index-zvae4m8g.js → index-17eybc3z.js} +1 -1
  12. package/dist/cli/{index-kyvg2cmp.js → index-19zn67q6.js} +1 -1
  13. package/dist/cli/{index-rbfq77sz.js → index-2j25br0p.js} +12 -10
  14. package/dist/cli/{index-9xpgytj9.js → index-3rr0ese8.js} +1 -1
  15. package/dist/cli/{index-gr7ssnj9.js → index-5he0kwnx.js} +3 -3
  16. package/dist/cli/{index-68aggz0v.js → index-6srk9a5q.js} +2 -2
  17. package/dist/cli/{index-qg11yf8q.js → index-6xnxz6z0.js} +1 -1
  18. package/dist/cli/{index-emkb0bbe.js → index-7cspygmb.js} +277 -673
  19. package/dist/cli/{index-3r1w7xky.js → index-cqsak7bf.js} +2 -2
  20. package/dist/cli/index-ctpgsj84.js +547 -0
  21. package/dist/cli/index-d6h5hq91.js +564 -0
  22. package/dist/cli/{index-vqcgmy4y.js → index-det5jwfk.js} +1 -1
  23. package/dist/cli/{index-3sx27bxt.js → index-dywekeen.js} +3 -3
  24. package/dist/cli/{index-wtn8wf7h.js → index-gw3m5fw0.js} +1 -1
  25. package/dist/cli/{index-rkd6qwjh.js → index-hns7xjm7.js} +2 -2
  26. package/dist/cli/{index-xdsj4qtd.js → index-j9kxwshw.js} +5 -5
  27. package/dist/cli/{index-jzpv74mq.js → index-jm81nhks.js} +24 -22
  28. package/dist/cli/{index-ahxrne50.js → index-kanrv3sv.js} +160 -58
  29. package/dist/cli/{index-j33zb3cx.js → index-m0bmh3w0.js} +4 -4
  30. package/dist/cli/index-m1abzg93.js +68 -0
  31. package/dist/cli/{index-ny13nr0x.js → index-n2712byd.js} +6 -6
  32. package/dist/cli/{index-3jvqw5hy.js → index-q27bajqb.js} +1 -1
  33. package/dist/cli/{index-qt2yaghg.js → index-vcgfqd8f.js} +1 -63
  34. package/dist/cli/{index-tftv9xh9.js → index-wr8qgpz5.js} +1 -1
  35. package/dist/cli/index.js +22 -20
  36. package/dist/cli/{knowledge-escalator-sya06p12.js → knowledge-escalator-dyxsm78k.js} +6 -6
  37. package/dist/cli/{knowledge-events-xzps61a9.js → knowledge-events-36q6nvae.js} +4 -4
  38. package/dist/cli/{knowledge-link-sy85fw3w.js → knowledge-link-4yd2z8ev.js} +3 -3
  39. package/dist/cli/{knowledge-store-qg66a93n.js → knowledge-store-e07b4s54.js} +4 -4
  40. package/dist/cli/{knowledge-validator-gar6wv4f.js → knowledge-validator-sfyf7wn2.js} +7 -7
  41. package/dist/cli/pr-subscriptions-x653151h.js +1 -1
  42. package/dist/cli/{scan-cursor-nx5y5k7r.js → scan-cursor-hm0aqqae.js} +5 -5
  43. package/dist/cli/{schema-vwxpsk6j.js → schema-13bnng2n.js} +1 -1
  44. package/dist/cli/scope-persistence-d9erwnvb.js +36 -0
  45. package/dist/cli/{skill-generator-nt5qxafq.js → skill-generator-g6r52x94.js} +8 -8
  46. package/dist/config/constants.d.ts +3 -2
  47. package/dist/hooks/delegation-gate.d.ts +19 -1
  48. package/dist/hooks/guardrails/nontransient-circuit.d.ts +47 -0
  49. package/dist/hooks/scope-guard.d.ts +9 -2
  50. package/dist/hooks/write-target-resolver.d.ts +30 -0
  51. package/dist/index.js +405 -369
  52. package/dist/sandbox/win32/restricted-environment-executor.d.ts +3 -2
  53. package/dist/scope/scope-binding.d.ts +89 -0
  54. package/dist/scope/scope-persistence.d.ts +53 -12
  55. package/dist/state.d.ts +25 -1
  56. package/dist/tools/declare-scope.d.ts +5 -1
  57. package/dist/tools/file-extractor-planner.d.ts +31 -0
  58. package/dist/tools/file-extractor.d.ts +2 -7
  59. package/package.json +1 -1
  60. package/dist/cli/index-51gg9947.js +0 -254
  61. package/dist/cli/scope-persistence-mfr36yc9.js +0 -20
  62. package/dist/cli/{index-h07wn84h.js → index-7mkpsw6g.js} +3 -3
  63. package/dist/cli/{pending-delegations-0z3znsec.js → pending-delegations-68cbaqv2.js} +1 -1
@@ -71,7 +71,7 @@ export declare class WindowsSandboxExecutor implements SandboxExecutor {
71
71
  *
72
72
  * The wrapper:
73
73
  * - Sets scoped temp directory (%TEMP%, %TMP%)
74
- * - Restricts PATH to safe system paths only
74
+ * - Preserves syntactically safe local PATH entries after system paths
75
75
  * - Removes dangerous environment variables that could be used to bypass restrictions
76
76
  * - Applies per-call envOverrides (set or unset)
77
77
  * - Executes PowerShell-native cmdlets (filesystem cmdlets only) via Invoke-Expression,
@@ -99,7 +99,8 @@ export declare class WindowsSandboxExecutor implements SandboxExecutor {
99
99
  * Return environment variable overrides required for the Windows sandbox.
100
100
  *
101
101
  * Security measures:
102
- * - PATH is restricted to essential Windows system directories only
102
+ * - PATH starts with essential Windows system directories, then retains
103
+ * syntactically safe local inherited tool directories
103
104
  * - TEMP/TMP are set to null (will be set to scoped temp at runtime via wrapCommand)
104
105
  * - Dangerous variables that don't apply to Windows are cleared for completeness
105
106
  */
@@ -0,0 +1,89 @@
1
+ import type { Plan } from '../config/plan-schema';
2
+ export declare const MAX_PENDING_SCOPE_BINDINGS = 256;
3
+ export type ScopeBindingSource = 'declare_scope' | 'plan' | 'file_directive' | 'worktree_derived';
4
+ export interface ScopeBinding {
5
+ version: 2;
6
+ workspaceIdentity: string;
7
+ planId: string;
8
+ planStructureHash: string;
9
+ taskId: string;
10
+ ownerSessionId: string;
11
+ ownerMessageId: string;
12
+ /** Exact Task tool call that activated this binding. Declarations omit it. */
13
+ dispatchCallId?: string;
14
+ activation: 'declaration' | 'pending_child' | 'active';
15
+ parentOwnerSessionId?: string;
16
+ parentCallId?: string;
17
+ source: ScopeBindingSource;
18
+ files: string[];
19
+ declaredAt: number;
20
+ expiresAt: number;
21
+ }
22
+ export declare function canonicalWorkspaceIdentity(directory: string): string | null;
23
+ export declare function normalizeScopeFiles(files: readonly string[]): string[] | null;
24
+ export declare function createScopeBinding(input: {
25
+ directory: string;
26
+ plan: Plan;
27
+ taskId: string;
28
+ files: readonly string[];
29
+ ownerSessionId: string;
30
+ ownerMessageId: string;
31
+ source: Exclude<ScopeBindingSource, 'worktree_derived'>;
32
+ dispatchCallId?: string;
33
+ activation?: ScopeBinding['activation'];
34
+ ttlMs?: number;
35
+ }): ScopeBinding | null;
36
+ export declare function registerScopeBinding(binding: ScopeBinding): void;
37
+ export declare function getScopeBinding(input: {
38
+ directory: string;
39
+ plan: Plan;
40
+ taskId: string;
41
+ ownerSessionId: string;
42
+ ownerMessageId?: string;
43
+ }): ScopeBinding | null;
44
+ /** Resolve the one Task-correlated authorization for an executing coder. */
45
+ export declare function getAuthorizedScopeBinding(input: {
46
+ directory: string;
47
+ plan: Plan;
48
+ taskId: string;
49
+ activeSessionId: string;
50
+ }): ScopeBinding | null;
51
+ export declare function getAuthorizedScopeBindingByPlanIdentity(input: {
52
+ directory: string;
53
+ planId: string;
54
+ planStructureHash: string;
55
+ taskId: string;
56
+ activeSessionId: string;
57
+ }): ScopeBinding | null;
58
+ export declare function clearScopeBindings(predicate?: (binding: ScopeBinding) => boolean): ScopeBinding[];
59
+ /**
60
+ * Atomically claim the pending scope for the exact Task call whose upstream
61
+ * metadata identified the actual OpenCode child session. The call id is part of
62
+ * the authorization identity; parent-session FIFO guessing is never used.
63
+ */
64
+ export declare function claimScopeBindingForChild(input: {
65
+ directory: string;
66
+ parentSessionId: string;
67
+ childSessionId: string;
68
+ dispatchCallId: string;
69
+ }): {
70
+ previous: ScopeBinding;
71
+ claimed: ScopeBinding;
72
+ } | null;
73
+ export declare function deriveChildScopeBinding(parent: ScopeBinding, input: {
74
+ childDirectory: string;
75
+ childSessionId: string;
76
+ parentCallId: string;
77
+ }): ScopeBinding;
78
+ export declare function resolveCoderScopeSources(input: {
79
+ explicitFiles: readonly string[] | null | undefined;
80
+ planFiles: readonly string[] | null | undefined;
81
+ fileDirectiveFiles: readonly string[] | null | undefined;
82
+ }): {
83
+ ok: true;
84
+ files: string[];
85
+ source: 'declare_scope' | 'plan' | 'file_directive';
86
+ } | {
87
+ ok: false;
88
+ code: 'SCOPE_NOT_DECLARED' | 'SCOPE_CONFLICT';
89
+ };
@@ -23,31 +23,35 @@
23
23
  * * plan.json size capped at MAX_PLAN_BYTES (DoS cap)
24
24
  * * Windows reserved device names rejected (CON, NUL, LPT1, …)
25
25
  *
26
- * RESIDUAL RISKS — explicitly accepted (#520 tracks full syscall-layer remediation):
27
- * 1. Bash / interpreter writes bypass the tool-layer authority check. This
28
- * module does not protect against a coder process running `sed -i`,
29
- * `echo >`, `python -c`, etc. Mitigation is prompt-only (see coder.ts
30
- * WRITE BLOCKED PROTOCOL) until #520 lands.
31
- * 2. Platform-portability of symlink guards:
26
+ * AUTHORITY BOUNDARY:
27
+ * - Direct-write tools and statically detected shell/interpreter write targets
28
+ * resolve through the same active Task-correlated binding.
29
+ * - A coder write with no binding or an unverifiable target fails closed with
30
+ * SCOPE_NOT_DECLARED/SCOPE_VIOLATION. Shell syntax is not an authority bypass.
31
+ * - This is command-level containment, not an operating-system syscall sandbox.
32
+ *
33
+ * RESIDUAL RISKS:
34
+ * 1. Platform-portability of symlink guards:
32
35
  * - realpath resolves POSIX symlinks and Windows junctions, but the
33
36
  * Windows behaviour is not covered by CI (Linux-only test matrix).
34
37
  * - O_NOFOLLOW is a no-op on Windows (falls back to 0). The realpath
35
38
  * containment check on `.swarm/scopes/` remains the primary guard
36
39
  * on that platform; leaf-file TOCTOU on Windows is not closed.
37
- * 3. Stale lockfile DoS: a crashed writer leaves a lock for up to
40
+ * 2. Stale lockfile DoS: a crashed writer leaves a lock for up to
38
41
  * LOCK_STALE_MS (30s). During that window, concurrent `declare_scope`
39
42
  * calls fail silently and the architect relies on in-memory state.
40
43
  * Acceptable because in-memory state remains authoritative inside the
41
44
  * live process; disk is a fallback.
42
- * 4. Temp-file leak: a crash between `Bun.write(tmp)` and `renameSync`
45
+ * 3. Temp-file leak: a crash between `Bun.write(tmp)` and `renameSync`
43
46
  * leaves `scope-{id}.json.tmp.{ts}.{rand}` files. No sweeper runs today;
44
47
  * accumulation is bounded by `/swarm close` (which rm -rf's .swarm/scopes/).
45
48
  *
46
- * NOT a standalone security boundary. Tool-layer write interception is now
47
- * implemented in shell-write-detect.ts (Phases 1-2). This module closes the
48
- * cross-process gap and the plan-as-scope gap. The syscall-layer fix (#520)
49
- * remains the durable long-term boundary.
49
+ * NOT a standalone syscall security boundary. Shell-write detection and the
50
+ * central write-target resolver provide command-level interception; this module
51
+ * supplies the durable, identity-bound authority they consume.
50
52
  */
53
+ import { type Plan } from '../config/plan-schema';
54
+ import { type ScopeBinding } from './scope-binding';
51
55
  declare const SCOPE_SCHEMA_VERSION: 1;
52
56
  export interface PersistedScope {
53
57
  version: typeof SCOPE_SCHEMA_VERSION;
@@ -56,6 +60,7 @@ export interface PersistedScope {
56
60
  expiresAt: number;
57
61
  files: string[];
58
62
  }
63
+ export type PersistedScopeBinding = ScopeBinding;
59
64
  /**
60
65
  * Write declared scope to `.swarm/scopes/scope-{taskId}.json` atomically.
61
66
  * Safe to call concurrently — proper-lockfile serialises writers per-file.
@@ -64,6 +69,42 @@ export interface PersistedScope {
64
69
  * hard requirement. In-memory state remains authoritative for the live process.
65
70
  */
66
71
  export declare function writeScopeToDisk(directory: string, taskId: string, files: string[], ttlMs?: number): Promise<void>;
72
+ /**
73
+ * Persist an identity-bound v2 authorization record. The v1 writer remains for
74
+ * compatibility with older non-authorization consumers, but strict Task
75
+ * preflight and declare_scope use only this schema.
76
+ */
77
+ export declare function writeScopeBindingToDisk(directory: string, binding: ScopeBinding): Promise<void>;
78
+ /** Read and fully validate a v2 binding; v1 records never authorize here. */
79
+ export declare function readScopeBindingFromDisk(input: {
80
+ directory: string;
81
+ taskId: string;
82
+ plan: Plan;
83
+ ownerSessionId: string;
84
+ parentCallId?: string;
85
+ requireDispatchCorrelation?: boolean;
86
+ requireDeclaration?: boolean;
87
+ }): ScopeBinding | null;
88
+ /** Remove one identity-bound authorization without disturbing sibling sessions. */
89
+ export declare function clearScopeBindingFromDisk(input: {
90
+ directory: string;
91
+ taskId: string;
92
+ ownerSessionId: string;
93
+ }): void;
94
+ export declare function resolveAuthorizedScopeBinding(input: {
95
+ directory: string;
96
+ taskId: string;
97
+ activeSessionId: string;
98
+ }): ScopeBinding | null;
99
+ /**
100
+ * Recover one unambiguous child binding after an in-memory session restart.
101
+ * Every candidate is still checked against current plan identity, exact child
102
+ * session ownership, Task-call correlation, TTL, and active state.
103
+ */
104
+ export declare function resolveAuthorizedScopeBindingForSession(input: {
105
+ directory: string;
106
+ activeSessionId: string;
107
+ }): ScopeBinding | null;
67
108
  /**
68
109
  * Read persisted scope for a task. Returns null on:
69
110
  * - file missing
package/dist/state.d.ts CHANGED
@@ -86,6 +86,13 @@ export interface AgentSessionState {
86
86
  lastInvocationIdByAgent: Record<string, number>;
87
87
  /** Active invocation windows keyed by "${agentName}:${invId}" */
88
88
  windows: Record<string, InvocationWindow>;
89
+ /**
90
+ * In-memory only circuit for known non-transient tool failures. Deliberately
91
+ * omitted from snapshots so a restarted host never inherits a stale stop.
92
+ */
93
+ nonTransientCircuit?: NonTransientCircuitState;
94
+ /** Bounded in-memory correlation for original commands replaced by wrappers. */
95
+ pendingToolExecutions?: Map<string, PendingToolExecution>;
89
96
  /** Last tool-call threshold at which a compaction hint was issued */
90
97
  lastCompactionHint: number;
91
98
  /** Count of architect direct writes to non-.swarm/ files */
@@ -265,6 +272,22 @@ export interface AgentSessionState {
265
272
  */
266
273
  cachedCohortId?: string;
267
274
  }
275
+ export type NonTransientErrorCategory = 'shell_parse_error' | 'command_not_found' | 'sandbox_wrapper_failure' | 'general_permanent';
276
+ export interface NonTransientCircuitState {
277
+ ownerAgent: string;
278
+ ownerInvocationId: number;
279
+ category: NonTransientErrorCategory | null;
280
+ sameCategoryCount: number;
281
+ hardStop: boolean;
282
+ lastSignal: string | null;
283
+ }
284
+ export interface PendingToolExecution {
285
+ tool: string;
286
+ originalCommand: string;
287
+ sandboxWrapped: boolean;
288
+ ownerAgent: string;
289
+ ownerInvocationId: number;
290
+ }
268
291
  /**
269
292
  * Represents a single agent invocation window with isolated guardrail budgets.
270
293
  * Each time the architect delegates to an agent, a new window is created.
@@ -475,7 +498,8 @@ export declare function updateAgentEventTime(sessionId: string): void;
475
498
  /**
476
499
  * Begin a new invocation window for the given agent.
477
500
  * Increments invocation ID, creates fresh budget counters.
478
- * Returns null for architect (unlimited, no window).
501
+ * Returns null for architect (unlimited, no budget window), while still
502
+ * advancing the invocation identity and clearing invocation-local safety state.
479
503
  *
480
504
  * @param sessionId - Session identifier
481
505
  * @param agentName - Agent name (with or without swarm prefix)
@@ -43,7 +43,11 @@ export declare function validateFiles(files: string[]): string[];
43
43
  * @param fallbackDir - Fallback directory for plan lookup
44
44
  * @returns DeclareScopeResult with success status and details
45
45
  */
46
- export declare function executeDeclareScope(args: DeclareScopeArgs, fallbackDir?: string): Promise<DeclareScopeResult>;
46
+ export declare function executeDeclareScope(args: DeclareScopeArgs, fallbackDir?: string, owner?: {
47
+ sessionID: string;
48
+ messageID: string;
49
+ agentName?: string;
50
+ }): Promise<DeclareScopeResult>;
47
51
  /**
48
52
  * Tool definition for declare_scope
49
53
  */
@@ -0,0 +1,31 @@
1
+ export interface ExtractCodeBlocksArgs {
2
+ content?: string;
3
+ output_dir?: string;
4
+ prefix?: string;
5
+ }
6
+ export interface PlannedExtractFile {
7
+ filename: string;
8
+ absolutePath: string;
9
+ relativePath: string;
10
+ code: string;
11
+ }
12
+ export type ExtractCodeBlocksPlan = {
13
+ status: 'ready';
14
+ targetDir: string;
15
+ files: PlannedExtractFile[];
16
+ } | {
17
+ status: 'noop';
18
+ reason: 'content-required' | 'no-code-blocks';
19
+ } | {
20
+ status: 'invalid';
21
+ reason: string;
22
+ };
23
+ /** Extract a deterministic filename from code content or context. */
24
+ export declare function extractFilename(code: string, language: string, index: number): string;
25
+ export declare function planExtractCodeBlocks(args: ExtractCodeBlocksArgs, directory: string): ExtractCodeBlocksPlan;
26
+ /**
27
+ * Plan once per tool-argument object so every pre-execution guard and the tool
28
+ * execution itself use the exact same collision-resolved targets.
29
+ */
30
+ export declare function getOrPlanExtractCodeBlocks(args: ExtractCodeBlocksArgs, directory: string): ExtractCodeBlocksPlan;
31
+ export declare function consumeExtractCodeBlocksPlan(args: ExtractCodeBlocksArgs, directory: string): ExtractCodeBlocksPlan;
@@ -1,9 +1,4 @@
1
1
  import type { ToolDefinition } from '@opencode-ai/plugin/tool';
2
- /**
3
- * Extract filename from code content or context
4
- */
5
- export declare function extractFilename(code: string, language: string, index: number): string;
6
- /**
7
- * Extract code blocks from content and save to files
8
- */
2
+ export { extractFilename } from './file-extractor-planner';
3
+ /** Extract code blocks from content and save them as one atomic write set. */
9
4
  export declare const extract_code_blocks: ToolDefinition;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.122.0",
3
+ "version": "7.122.1",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,254 +0,0 @@
1
- // @bun
2
- import {
3
- require_proper_lockfile
4
- } from "./index-7g4c7s5r.js";
5
- import {
6
- bunWrite
7
- } from "./index-qe3v54nb.js";
8
- import {
9
- __toESM
10
- } from "./index-a76rekgs.js";
11
-
12
- // src/scope/scope-persistence.ts
13
- var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
14
- import * as fs from "fs";
15
- import * as path from "path";
16
- var SCOPE_SCHEMA_VERSION = 1;
17
- var DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
18
- var LOCK_STALE_MS = 30 * 1000;
19
- var SCOPES_DIR = ".swarm/scopes";
20
- var MAX_FILES_PER_SCOPE = 1e4;
21
- var MAX_PLAN_BYTES = 10 * 1024 * 1024;
22
- var MAX_SCOPE_BYTES = 2 * 1024 * 1024;
23
- var WINDOWS_RESERVED = new Set([
24
- "CON",
25
- "PRN",
26
- "AUX",
27
- "NUL",
28
- "COM1",
29
- "COM2",
30
- "COM3",
31
- "COM4",
32
- "COM5",
33
- "COM6",
34
- "COM7",
35
- "COM8",
36
- "COM9",
37
- "LPT1",
38
- "LPT2",
39
- "LPT3",
40
- "LPT4",
41
- "LPT5",
42
- "LPT6",
43
- "LPT7",
44
- "LPT8",
45
- "LPT9"
46
- ]);
47
- function getScopesDir(directory) {
48
- return path.join(directory, SCOPES_DIR);
49
- }
50
- function isSafeTaskId(taskId) {
51
- if (typeof taskId !== "string")
52
- return false;
53
- if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(taskId))
54
- return false;
55
- const head = taskId.replace(/\.+$/, "").split(".")[0].toUpperCase();
56
- if (WINDOWS_RESERVED.has(head))
57
- return false;
58
- return true;
59
- }
60
- function isScopesDirSafe(directory, scopesDir) {
61
- try {
62
- const resolvedWorkspace = fs.realpathSync(directory);
63
- const resolvedScopes = fs.realpathSync(scopesDir);
64
- const rel = path.relative(resolvedWorkspace, resolvedScopes);
65
- return rel.length > 0 && !rel.startsWith("..") && !path.isAbsolute(rel);
66
- } catch {
67
- return false;
68
- }
69
- }
70
- function getScopeFilePath(directory, taskId) {
71
- if (!isSafeTaskId(taskId)) {
72
- throw new Error(`Invalid taskId for scope persistence: ${taskId}`);
73
- }
74
- return path.join(getScopesDir(directory), `scope-${taskId}.json`);
75
- }
76
- async function writeScopeToDisk(directory, taskId, files, ttlMs = DEFAULT_TTL_MS) {
77
- if (!isSafeTaskId(taskId))
78
- return;
79
- if (!Array.isArray(files) || files.length === 0)
80
- return;
81
- if (files.length > MAX_FILES_PER_SCOPE)
82
- return;
83
- const scopesDir = getScopesDir(directory);
84
- const scopePath = getScopeFilePath(directory, taskId);
85
- try {
86
- fs.mkdirSync(scopesDir, { recursive: true });
87
- } catch {
88
- return;
89
- }
90
- if (!isScopesDirSafe(directory, scopesDir))
91
- return;
92
- const now = Date.now();
93
- const payload = {
94
- version: SCOPE_SCHEMA_VERSION,
95
- taskId,
96
- declaredAt: now,
97
- expiresAt: now + ttlMs,
98
- files: [...files]
99
- };
100
- const content = JSON.stringify(payload, null, 2);
101
- try {
102
- const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT;
103
- const nofollow = fs.constants.O_NOFOLLOW ?? 0;
104
- const fd = fs.openSync(scopePath, flags | nofollow);
105
- fs.closeSync(fd);
106
- } catch {
107
- return;
108
- }
109
- let release;
110
- try {
111
- release = await import_proper_lockfile.default.lock(scopePath, {
112
- stale: LOCK_STALE_MS,
113
- retries: { retries: 3, minTimeout: 50, maxTimeout: 200 },
114
- realpath: false
115
- });
116
- await atomicWrite(scopePath, content);
117
- } catch {} finally {
118
- if (release) {
119
- try {
120
- await release();
121
- } catch {}
122
- }
123
- }
124
- }
125
- async function atomicWrite(targetPath, content) {
126
- const tempPath = `${targetPath}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
127
- try {
128
- await bunWrite(tempPath, content);
129
- fs.renameSync(tempPath, targetPath);
130
- } finally {
131
- try {
132
- fs.unlinkSync(tempPath);
133
- } catch {}
134
- }
135
- }
136
- function readScopeFromDisk(directory, taskId) {
137
- if (!isSafeTaskId(taskId))
138
- return null;
139
- const scopesDir = getScopesDir(directory);
140
- if (!isScopesDirSafe(directory, scopesDir))
141
- return null;
142
- const scopePath = getScopeFilePath(directory, taskId);
143
- const nofollow = fs.constants.O_NOFOLLOW ?? 0;
144
- let fd;
145
- try {
146
- fd = fs.openSync(scopePath, fs.constants.O_RDONLY | nofollow);
147
- } catch {
148
- return null;
149
- }
150
- let raw;
151
- try {
152
- const stat = fs.fstatSync(fd);
153
- if (!stat.isFile())
154
- return null;
155
- if (stat.size > MAX_SCOPE_BYTES)
156
- return null;
157
- const buf = Buffer.alloc(stat.size);
158
- fs.readSync(fd, buf, 0, stat.size, 0);
159
- raw = buf.toString("utf-8");
160
- } catch {
161
- return null;
162
- } finally {
163
- try {
164
- fs.closeSync(fd);
165
- } catch {}
166
- }
167
- if (!raw.trim())
168
- return null;
169
- let parsed;
170
- try {
171
- parsed = JSON.parse(raw);
172
- } catch {
173
- return null;
174
- }
175
- if (parsed.version !== SCOPE_SCHEMA_VERSION)
176
- return null;
177
- if (parsed.taskId !== taskId)
178
- return null;
179
- const now = Date.now();
180
- if (typeof parsed.declaredAt !== "number" || parsed.declaredAt > now) {
181
- return null;
182
- }
183
- if (typeof parsed.expiresAt !== "number" || now >= parsed.expiresAt) {
184
- return null;
185
- }
186
- if (!Array.isArray(parsed.files))
187
- return null;
188
- if (parsed.files.length > MAX_FILES_PER_SCOPE)
189
- return null;
190
- const files = parsed.files.filter((f) => typeof f === "string");
191
- return files.length > 0 ? files : null;
192
- }
193
- function readPlanScope(directory, taskId) {
194
- if (!isSafeTaskId(taskId))
195
- return null;
196
- try {
197
- const planPath = path.join(directory, ".swarm", "plan.json");
198
- const stat = fs.statSync(planPath);
199
- if (!stat.isFile())
200
- return null;
201
- if (stat.size > MAX_PLAN_BYTES)
202
- return null;
203
- const raw = fs.readFileSync(planPath, "utf-8");
204
- const plan = JSON.parse(raw);
205
- for (const phase of plan.phases ?? []) {
206
- for (const task of phase.tasks ?? []) {
207
- if (task.id !== taskId)
208
- continue;
209
- const ft = task.files_touched;
210
- if (Array.isArray(ft) && ft.length > 0) {
211
- if (ft.length > MAX_FILES_PER_SCOPE)
212
- return null;
213
- return ft.filter((f) => typeof f === "string");
214
- }
215
- if (typeof ft === "string" && ft.length > 0)
216
- return [ft];
217
- return null;
218
- }
219
- }
220
- return null;
221
- } catch {
222
- return null;
223
- }
224
- }
225
- function clearScopeForTask(directory, taskId) {
226
- if (!isSafeTaskId(taskId))
227
- return;
228
- try {
229
- fs.unlinkSync(getScopeFilePath(directory, taskId));
230
- } catch {}
231
- }
232
- function clearAllScopes(directory) {
233
- try {
234
- fs.rmSync(getScopesDir(directory), { recursive: true, force: true });
235
- } catch {}
236
- }
237
- function resolveScopeWithFallbacks(input) {
238
- const { directory, taskId, inMemoryScope, pendingMapScope } = input;
239
- if (inMemoryScope && inMemoryScope.length > 0)
240
- return inMemoryScope;
241
- if (taskId) {
242
- const disk = readScopeFromDisk(directory, taskId);
243
- if (disk && disk.length > 0)
244
- return disk;
245
- const plan = readPlanScope(directory, taskId);
246
- if (plan && plan.length > 0)
247
- return plan;
248
- }
249
- if (pendingMapScope && pendingMapScope.length > 0)
250
- return pendingMapScope;
251
- return null;
252
- }
253
-
254
- export { writeScopeToDisk, readScopeFromDisk, readPlanScope, clearScopeForTask, clearAllScopes, resolveScopeWithFallbacks };
@@ -1,20 +0,0 @@
1
- // @bun
2
- import {
3
- clearAllScopes,
4
- clearScopeForTask,
5
- readPlanScope,
6
- readScopeFromDisk,
7
- resolveScopeWithFallbacks,
8
- writeScopeToDisk
9
- } from "./index-51gg9947.js";
10
- import"./index-7g4c7s5r.js";
11
- import"./index-qe3v54nb.js";
12
- import"./index-a76rekgs.js";
13
- export {
14
- writeScopeToDisk,
15
- resolveScopeWithFallbacks,
16
- readScopeFromDisk,
17
- readPlanScope,
18
- clearScopeForTask,
19
- clearAllScopes
20
- };
@@ -1,10 +1,10 @@
1
1
  // @bun
2
- import {
3
- withEvidenceLock
4
- } from "./index-76kvm9z5.js";
5
2
  import {
6
3
  invalidateCachedArtifact
7
4
  } from "./index-fsrp8wp3.js";
5
+ import {
6
+ withEvidenceLock
7
+ } from "./index-76kvm9z5.js";
8
8
  import {
9
9
  bunWrite
10
10
  } from "./index-qe3v54nb.js";
@@ -8,10 +8,10 @@ import {
8
8
  init_logger,
9
9
  warn
10
10
  } from "./index-zgwm4ryv.js";
11
+ import"./index-fsrp8wp3.js";
11
12
  import {
12
13
  withEvidenceLock
13
14
  } from "./index-76kvm9z5.js";
14
- import"./index-fsrp8wp3.js";
15
15
  import"./index-q2rrv0cy.js";
16
16
  import {
17
17
  exports_external