opencode-swarm 6.70.0 → 6.71.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/cli/index.js +781 -734
- package/dist/config/schema.d.ts +4 -0
- package/dist/hooks/guardrails.d.ts +7 -0
- package/dist/index.js +17706 -17371
- package/dist/scope/scope-persistence.d.ts +109 -0
- package/dist/scope/scope-persistence.test.d.ts +13 -0
- package/package.json +1 -1
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope persistence for #519 (v6.71.1 hotfix).
|
|
3
|
+
*
|
|
4
|
+
* Persists declared coder scope to `.swarm/scopes/scope-{taskId}.json` so that
|
|
5
|
+
* scope survives cross-process delegation — in-memory `session.declaredCoderScope`
|
|
6
|
+
* is lost when a coder session starts in a separate process (#496 root cause B).
|
|
7
|
+
*
|
|
8
|
+
* Also exposes a fallback reader that reads `plan.json:phases[].tasks[].files_touched`
|
|
9
|
+
* for the active task, so architect-authored plans become a durable scope source
|
|
10
|
+
* even when `declare_scope` was never called (#496 root cause C mitigation).
|
|
11
|
+
*
|
|
12
|
+
* Read/write contract:
|
|
13
|
+
* - Atomic write via temp + rename (POSIX atomic on same filesystem).
|
|
14
|
+
* - File lock via proper-lockfile while writing.
|
|
15
|
+
* - Schema versioning: readers fail closed on unknown version.
|
|
16
|
+
* - TTL: default 24h from declaredAt; expired scopes return null.
|
|
17
|
+
* - Symlink guards (defence in depth):
|
|
18
|
+
* * realpath containment check on `.swarm/scopes/` (closes parent-dir attack)
|
|
19
|
+
* * O_NOFOLLOW on both write-create and read-fd (closes leaf-file TOCTOU)
|
|
20
|
+
* * taskId-in-file must match the filename (closes cross-pollination)
|
|
21
|
+
* * declaredAt must be <= now (closes future-timestamp attack)
|
|
22
|
+
* * files array capped at MAX_FILES_PER_SCOPE (DoS cap)
|
|
23
|
+
* * plan.json size capped at MAX_PLAN_BYTES (DoS cap)
|
|
24
|
+
* * Windows reserved device names rejected (CON, NUL, LPT1, …)
|
|
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:
|
|
32
|
+
* - realpath resolves POSIX symlinks and Windows junctions, but the
|
|
33
|
+
* Windows behaviour is not covered by CI (Linux-only test matrix).
|
|
34
|
+
* - O_NOFOLLOW is a no-op on Windows (falls back to 0). The realpath
|
|
35
|
+
* containment check on `.swarm/scopes/` remains the primary guard
|
|
36
|
+
* 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
|
|
38
|
+
* LOCK_STALE_MS (30s). During that window, concurrent `declare_scope`
|
|
39
|
+
* calls fail silently and the architect relies on in-memory state.
|
|
40
|
+
* Acceptable because in-memory state remains authoritative inside the
|
|
41
|
+
* live process; disk is a fallback.
|
|
42
|
+
* 4. Temp-file leak: a crash between `Bun.write(tmp)` and `renameSync`
|
|
43
|
+
* leaves `scope-{id}.json.tmp.{ts}.{rand}` files. No sweeper runs today;
|
|
44
|
+
* accumulation is bounded by `/swarm close` (which rm -rf's .swarm/scopes/).
|
|
45
|
+
*
|
|
46
|
+
* NOT a security boundary. Bash remains unguarded at the write-authority layer.
|
|
47
|
+
* The durable fix lives at the syscall layer (#520). This module closes the
|
|
48
|
+
* cross-process gap and the plan-as-scope gap, both of which are mitigations.
|
|
49
|
+
*/
|
|
50
|
+
declare const SCOPE_SCHEMA_VERSION: 1;
|
|
51
|
+
export interface PersistedScope {
|
|
52
|
+
version: typeof SCOPE_SCHEMA_VERSION;
|
|
53
|
+
taskId: string;
|
|
54
|
+
declaredAt: number;
|
|
55
|
+
expiresAt: number;
|
|
56
|
+
files: string[];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Write declared scope to `.swarm/scopes/scope-{taskId}.json` atomically.
|
|
60
|
+
* Safe to call concurrently — proper-lockfile serialises writers per-file.
|
|
61
|
+
*
|
|
62
|
+
* Silent on I/O failure: scope persistence is a defense-in-depth layer, not a
|
|
63
|
+
* hard requirement. In-memory state remains authoritative for the live process.
|
|
64
|
+
*/
|
|
65
|
+
export declare function writeScopeToDisk(directory: string, taskId: string, files: string[], ttlMs?: number): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Read persisted scope for a task. Returns null on:
|
|
68
|
+
* - file missing
|
|
69
|
+
* - file is a symlink (lstat guard — prevents hostile repo pre-seeding)
|
|
70
|
+
* - unknown schema version (fail-closed)
|
|
71
|
+
* - expired TTL
|
|
72
|
+
* - malformed JSON
|
|
73
|
+
* - invalid taskId
|
|
74
|
+
*/
|
|
75
|
+
export declare function readScopeFromDisk(directory: string, taskId: string): string[] | null;
|
|
76
|
+
/**
|
|
77
|
+
* Read declared scope for a task from `.swarm/plan.json:phases[].tasks[].files_touched`.
|
|
78
|
+
* Mirrors the logic in src/hooks/diff-scope.ts:15-47 but kept independent so a
|
|
79
|
+
* future diff-scope refactor doesn't ripple into authority-layer reads.
|
|
80
|
+
*
|
|
81
|
+
* Returns null on missing plan, task not found, no files_touched, or parse error.
|
|
82
|
+
*/
|
|
83
|
+
export declare function readPlanScope(directory: string, taskId: string): string[] | null;
|
|
84
|
+
/**
|
|
85
|
+
* Remove scope file for a single task. Called when a task transitions to
|
|
86
|
+
* completed/closed so stale scope doesn't leak into later tasks with the same id.
|
|
87
|
+
*/
|
|
88
|
+
export declare function clearScopeForTask(directory: string, taskId: string): void;
|
|
89
|
+
/**
|
|
90
|
+
* Remove the entire `.swarm/scopes/` directory. Called by /swarm close so the
|
|
91
|
+
* next session starts without inherited scope.
|
|
92
|
+
*/
|
|
93
|
+
export declare function clearAllScopes(directory: string): void;
|
|
94
|
+
/**
|
|
95
|
+
* Resolve scope for a task with the full fallback chain:
|
|
96
|
+
* 1. in-memory session.declaredCoderScope (fast path; live process)
|
|
97
|
+
* 2. `.swarm/scopes/scope-{taskId}.json` (cross-process durable)
|
|
98
|
+
* 3. `.swarm/plan.json:phases[].tasks[].files_touched` (architect-authored)
|
|
99
|
+
* 4. caller-supplied pending-map fallback (delegation-gate module map)
|
|
100
|
+
*
|
|
101
|
+
* Any null/empty result falls through to the next layer. First non-empty wins.
|
|
102
|
+
*/
|
|
103
|
+
export declare function resolveScopeWithFallbacks(input: {
|
|
104
|
+
directory: string;
|
|
105
|
+
taskId: string | null | undefined;
|
|
106
|
+
inMemoryScope: string[] | null | undefined;
|
|
107
|
+
pendingMapScope: string[] | null | undefined;
|
|
108
|
+
}): string[] | null;
|
|
109
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for scope-persistence (#519 v6.71.1).
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Atomic write + read round-trip
|
|
6
|
+
* - Schema-version fail-closed on unknown version
|
|
7
|
+
* - TTL expiry returns null
|
|
8
|
+
* - lstat symlink guard
|
|
9
|
+
* - Plan.json fallback (files_touched) for active task
|
|
10
|
+
* - Resolve chain order: memory → disk → plan.json → pending-map
|
|
11
|
+
* - Invalid taskId rejection
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.71.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",
|