opencode-writer-swarm 1.0.0 → 1.2.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/README.md +31 -0
- package/dist/commands/diagnose.d.ts +5 -0
- package/dist/commands/export.d.ts +5 -0
- package/dist/commands/index.d.ts +19 -0
- package/dist/commands/reset.d.ts +1 -0
- package/dist/config/schema.d.ts +64 -0
- package/dist/evidence/index.d.ts +1 -0
- package/dist/evidence/store.d.ts +42 -0
- package/dist/hooks/context-budget.d.ts +10 -0
- package/dist/hooks/extractors.d.ts +24 -3
- package/dist/hooks/guardrails.d.ts +22 -0
- package/dist/hooks/index.d.ts +2 -1
- package/dist/hooks/utils.d.ts +5 -29
- package/dist/index.d.ts +5 -4
- package/dist/index.js +11193 -290
- package/dist/plan/index.d.ts +2 -0
- package/dist/plan/manager.d.ts +63 -0
- package/dist/plan/schema.d.ts +160 -0
- package/dist/state.d.ts +44 -3
- package/dist/state.test.d.ts +1 -0
- package/dist/tools/file-manager.d.ts +7 -6
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -42,3 +42,34 @@ OpenCode surfaces `editor_in_chief` as the primary role inside the UI, so you ca
|
|
|
42
42
|
| `FILE_RETRY_ENABLED` / `WRITER_MAX_RETRIES` | Toggles exponential backoff retries for writer file writes and limits the number of retries. |
|
|
43
43
|
| `LOG_REDACTION_ENABLED` | When `true` (default), startup logs redact keys ending in `_KEY`, `_SECRET`, or `_TOKEN`. Setting to `false` temporarily disables redaction for debugging. |
|
|
44
44
|
| `VERBOSE_INIT` / `LOG_LEVEL=debug` | Emit detailed initialization metadata (agent count, sanitized config keys) during plugin startup. |
|
|
45
|
+
|
|
46
|
+
## Slash commands
|
|
47
|
+
|
|
48
|
+
The plugin exposes a `/swarm` command namespace for inspecting and managing the swarm state:
|
|
49
|
+
|
|
50
|
+
- `/swarm diagnose` – Runs health checks on `.swarm/plan.md`, `.swarm/context.md`, and the plugin config.
|
|
51
|
+
- `/swarm export` – Emits the current plan/context bundle as a JSON snapshot for backups or migration.
|
|
52
|
+
- `/swarm reset --confirm` – Securely deletes `.swarm/plan.md` and `.swarm/context.md` after a confirmation warning, leaving the workspace in a clean state.
|
|
53
|
+
|
|
54
|
+
Each slash command validates all arguments and file paths before performing I/O, matching the guardrail philosophy outlined below.
|
|
55
|
+
|
|
56
|
+
## Guardrails & context budget
|
|
57
|
+
|
|
58
|
+
Guardrails ensure no single session exceeds configured limits. The defaults (exposed under `guardrails` in your config) are:
|
|
59
|
+
|
|
60
|
+
- `max_tool_calls: 200`
|
|
61
|
+
- `max_duration_minutes: 30`
|
|
62
|
+
- `max_repetitions: 10`
|
|
63
|
+
- `max_consecutive_errors: 5`
|
|
64
|
+
- `warning_threshold: 0.5`
|
|
65
|
+
|
|
66
|
+
Warnings fire when you cross 50% of a limit (logged as `Guardrail warning: Approaching tool call limit`). When limits are exceeded the guardrail hook throws to halt further tool execution and protect the agent loop.
|
|
67
|
+
|
|
68
|
+
Context-budget warnings continue to execute through `experimental.chat.system.transform`, ensuring the architect agent receives system-level alerts when plan/context token budgets reach `0.7` (warning) or `0.9` (critical) of the configured window.
|
|
69
|
+
|
|
70
|
+
## Release 1.2.0
|
|
71
|
+
|
|
72
|
+
- Slash commands for `/swarm diagnose`, `/swarm export`, and `/swarm reset --confirm`.
|
|
73
|
+
- Guardrail engine enforcing tool-call/duration/repetition/error limits with configurable warning thresholds.
|
|
74
|
+
- Evidence store keeping `.swarm/evidence` shrink-wrapped with retention and guardrail-aware reads.
|
|
75
|
+
- Documentation, tests, and build/typecheck scripts locked in for a full verification phase.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { handleDiagnoseCommand } from './diagnose';
|
|
2
|
+
import { handleExportCommand } from './export';
|
|
3
|
+
import { handleResetCommand } from './reset';
|
|
4
|
+
export interface CommandInput {
|
|
5
|
+
command: string;
|
|
6
|
+
args?: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface TextPart {
|
|
9
|
+
type: 'text';
|
|
10
|
+
text: string;
|
|
11
|
+
}
|
|
12
|
+
export interface CommandOutput {
|
|
13
|
+
parts: TextPart[];
|
|
14
|
+
}
|
|
15
|
+
export interface SwarmCommandHandler {
|
|
16
|
+
(input: CommandInput, output: CommandOutput): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export declare function createSwarmCommandHandler(directory: string): SwarmCommandHandler;
|
|
19
|
+
export { handleDiagnoseCommand, handleExportCommand, handleResetCommand };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function handleResetCommand(directory: string, args: string[]): Promise<string>;
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -5,12 +5,72 @@ export declare const AgentOverrideConfigSchema: z.ZodObject<{
|
|
|
5
5
|
disabled: z.ZodOptional<z.ZodBoolean>;
|
|
6
6
|
}, z.core.$strip>;
|
|
7
7
|
export type AgentOverrideConfig = z.infer<typeof AgentOverrideConfigSchema>;
|
|
8
|
+
export declare const ContextBudgetConfigSchema: z.ZodObject<{
|
|
9
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
10
|
+
warn: z.ZodDefault<z.ZodNumber>;
|
|
11
|
+
critical: z.ZodDefault<z.ZodNumber>;
|
|
12
|
+
warn_threshold: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
critical_threshold: z.ZodOptional<z.ZodNumber>;
|
|
14
|
+
max_injection_tokens: z.ZodDefault<z.ZodNumber>;
|
|
15
|
+
model_limits: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;
|
|
16
|
+
target_agents: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
17
|
+
}, z.core.$strip>;
|
|
18
|
+
export type ContextBudgetConfig = z.infer<typeof ContextBudgetConfigSchema>;
|
|
19
|
+
export declare const EvidenceConfigSchema: z.ZodObject<{
|
|
20
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
21
|
+
max_age_days: z.ZodDefault<z.ZodNumber>;
|
|
22
|
+
max_bundles: z.ZodDefault<z.ZodNumber>;
|
|
23
|
+
auto_archive: z.ZodDefault<z.ZodBoolean>;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
export type EvidenceConfig = z.infer<typeof EvidenceConfigSchema>;
|
|
26
|
+
export declare const GuardrailsConfigSchema: z.ZodObject<{
|
|
27
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
28
|
+
max_tool_calls: z.ZodDefault<z.ZodNumber>;
|
|
29
|
+
max_duration_minutes: z.ZodDefault<z.ZodNumber>;
|
|
30
|
+
max_repetitions: z.ZodDefault<z.ZodNumber>;
|
|
31
|
+
max_consecutive_errors: z.ZodDefault<z.ZodNumber>;
|
|
32
|
+
warning_threshold: z.ZodDefault<z.ZodNumber>;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
export type GuardrailsConfig = z.infer<typeof GuardrailsConfigSchema>;
|
|
35
|
+
export declare const HooksConfigSchema: z.ZodObject<{
|
|
36
|
+
pre_agent: z.ZodOptional<z.ZodString>;
|
|
37
|
+
post_agent: z.ZodOptional<z.ZodString>;
|
|
38
|
+
}, z.core.$strip>;
|
|
39
|
+
export type HooksConfig = z.infer<typeof HooksConfigSchema>;
|
|
8
40
|
export declare const PluginConfigSchema: z.ZodObject<{
|
|
9
41
|
agents: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
10
42
|
model: z.ZodOptional<z.ZodString>;
|
|
11
43
|
temperature: z.ZodOptional<z.ZodNumber>;
|
|
12
44
|
disabled: z.ZodOptional<z.ZodBoolean>;
|
|
13
45
|
}, z.core.$strip>>>;
|
|
46
|
+
context_budget: z.ZodDefault<z.ZodObject<{
|
|
47
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
48
|
+
warn: z.ZodDefault<z.ZodNumber>;
|
|
49
|
+
critical: z.ZodDefault<z.ZodNumber>;
|
|
50
|
+
warn_threshold: z.ZodOptional<z.ZodNumber>;
|
|
51
|
+
critical_threshold: z.ZodOptional<z.ZodNumber>;
|
|
52
|
+
max_injection_tokens: z.ZodDefault<z.ZodNumber>;
|
|
53
|
+
model_limits: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;
|
|
54
|
+
target_agents: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
55
|
+
}, z.core.$strip>>;
|
|
56
|
+
evidence: z.ZodDefault<z.ZodObject<{
|
|
57
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
58
|
+
max_age_days: z.ZodDefault<z.ZodNumber>;
|
|
59
|
+
max_bundles: z.ZodDefault<z.ZodNumber>;
|
|
60
|
+
auto_archive: z.ZodDefault<z.ZodBoolean>;
|
|
61
|
+
}, z.core.$strip>>;
|
|
62
|
+
guardrails: z.ZodDefault<z.ZodObject<{
|
|
63
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
64
|
+
max_tool_calls: z.ZodDefault<z.ZodNumber>;
|
|
65
|
+
max_duration_minutes: z.ZodDefault<z.ZodNumber>;
|
|
66
|
+
max_repetitions: z.ZodDefault<z.ZodNumber>;
|
|
67
|
+
max_consecutive_errors: z.ZodDefault<z.ZodNumber>;
|
|
68
|
+
warning_threshold: z.ZodDefault<z.ZodNumber>;
|
|
69
|
+
}, z.core.$strip>>;
|
|
70
|
+
hooks: z.ZodOptional<z.ZodObject<{
|
|
71
|
+
pre_agent: z.ZodOptional<z.ZodString>;
|
|
72
|
+
post_agent: z.ZodOptional<z.ZodString>;
|
|
73
|
+
}, z.core.$strip>>;
|
|
14
74
|
qa_retry_limit: z.ZodDefault<z.ZodNumber>;
|
|
15
75
|
file_retry_enabled: z.ZodDefault<z.ZodBoolean>;
|
|
16
76
|
max_file_operation_retries: z.ZodDefault<z.ZodNumber>;
|
|
@@ -20,3 +80,7 @@ export type PluginConfig = z.infer<typeof PluginConfigSchema>;
|
|
|
20
80
|
export declare function getFileRetryEnabled(config?: PluginConfig): boolean;
|
|
21
81
|
export declare function getMaxFileRetries(config?: PluginConfig): number;
|
|
22
82
|
export declare function getConfigValidationEnabled(config?: PluginConfig): boolean;
|
|
83
|
+
export declare function getContextBudgetDefaults(): ContextBudgetConfig;
|
|
84
|
+
export declare function getEvidenceDefaults(): EvidenceConfig;
|
|
85
|
+
export declare function getGuardrailsDefaults(): GuardrailsConfig;
|
|
86
|
+
export declare function getHooksDefaults(): HooksConfig;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { EvidenceStore, type EvidenceBundle, type EvidenceRecord, type EvidenceType } from './store';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { EvidenceConfig } from '../config/schema';
|
|
2
|
+
export type EvidenceType = 'review' | 'test' | 'diff' | 'approval' | 'note';
|
|
3
|
+
export interface EvidenceBundle {
|
|
4
|
+
id?: string;
|
|
5
|
+
type: EvidenceType;
|
|
6
|
+
payload: unknown;
|
|
7
|
+
created_at?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface EvidenceRecord extends EvidenceBundle {
|
|
10
|
+
id: string;
|
|
11
|
+
created_at: string;
|
|
12
|
+
filename: string;
|
|
13
|
+
}
|
|
14
|
+
export declare class EvidenceStore {
|
|
15
|
+
private readonly directory;
|
|
16
|
+
private readonly config;
|
|
17
|
+
private readonly initPromise;
|
|
18
|
+
private readonly lockBackoffs;
|
|
19
|
+
private evidenceRoot;
|
|
20
|
+
private tmpRoot;
|
|
21
|
+
private pruneInFlight;
|
|
22
|
+
private prunePending;
|
|
23
|
+
private lastPruneEndTime;
|
|
24
|
+
constructor(directory: string, config: EvidenceConfig);
|
|
25
|
+
saveEvidence(bundle: EvidenceBundle): Promise<void>;
|
|
26
|
+
listEvidence(): Promise<EvidenceRecord[]>;
|
|
27
|
+
pruneStaleBundles(): Promise<void>;
|
|
28
|
+
private initialize;
|
|
29
|
+
private normalizeId;
|
|
30
|
+
private withLock;
|
|
31
|
+
private acquireLock;
|
|
32
|
+
private runPruneCycle;
|
|
33
|
+
private executePruneCycle;
|
|
34
|
+
private pruneOnce;
|
|
35
|
+
private scanEvidenceFiles;
|
|
36
|
+
private readRecord;
|
|
37
|
+
private cleanupTmpDirectory;
|
|
38
|
+
private writeWithRetries;
|
|
39
|
+
private safeUnlink;
|
|
40
|
+
private resolveRelativePath;
|
|
41
|
+
private sleep;
|
|
42
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PluginConfig } from '../config';
|
|
2
|
+
type SystemTransformInput = {
|
|
3
|
+
agent?: unknown;
|
|
4
|
+
model?: unknown;
|
|
5
|
+
[other: string]: unknown;
|
|
6
|
+
};
|
|
7
|
+
export declare function createContextBudgetHook(config: PluginConfig, directory: string): Partial<Record<string, (input: SystemTransformInput, output: {
|
|
8
|
+
system: string[];
|
|
9
|
+
}) => Promise<void>>>;
|
|
10
|
+
export {};
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/** Markdown extractors for plan.md and context.md files. Uses AST parsing with LRU caching. */
|
|
2
|
+
import type { Root } from 'mdast';
|
|
3
|
+
/**
|
|
4
|
+
* Parse markdown content with LRU caching.
|
|
5
|
+
* Cache entries expire after 5 minutes and are evicted based on entry count and size limits.
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseMarkdownWithCache(content: string): Root;
|
|
8
|
+
/**
|
|
9
|
+
* Reset the markdown cache and cache stats.
|
|
10
|
+
* Useful for testing.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resetMarkdownCache(): void;
|
|
13
|
+
/**
|
|
14
|
+
* Extracts the current phase information from plan content.
|
|
15
|
+
*/
|
|
16
|
+
export declare function extractCurrentPhase(planContent: string): string | null;
|
|
17
|
+
/**
|
|
18
|
+
* Extracts incomplete tasks (phases) from plan content.
|
|
19
|
+
*/
|
|
20
|
+
export declare function extractIncompleteTasks(planContent: string, maxChars?: number): string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Extracts decisions section from context content.
|
|
23
|
+
*/
|
|
24
|
+
export declare function extractDecisions(contextContent: string, maxChars?: number): string | null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { GuardrailsConfig } from '../config/schema';
|
|
2
|
+
/** Tool call input structure */
|
|
3
|
+
interface ToolInput {
|
|
4
|
+
sessionID: string;
|
|
5
|
+
tool?: string;
|
|
6
|
+
agent?: string;
|
|
7
|
+
}
|
|
8
|
+
/** Tool call output structure */
|
|
9
|
+
interface ToolOutput {
|
|
10
|
+
result?: unknown;
|
|
11
|
+
error?: unknown;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create guardrail hooks for tracking and limiting tool usage.
|
|
15
|
+
* @param config - Guardrails configuration
|
|
16
|
+
* @returns Object with toolBefore and toolAfter hooks
|
|
17
|
+
*/
|
|
18
|
+
export declare function createGuardrailsHook(config: GuardrailsConfig): {
|
|
19
|
+
toolBefore: (input: ToolInput) => Promise<void>;
|
|
20
|
+
toolAfter: (input: ToolInput, output: ToolOutput) => Promise<void>;
|
|
21
|
+
};
|
|
22
|
+
export {};
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { safeHook, composeHandlers } from './utils';
|
|
2
2
|
import { createSystemEnhancerHook } from './system-enhancer';
|
|
3
3
|
import { createDelegationTrackerHook } from './delegation-tracker';
|
|
4
|
-
|
|
4
|
+
import { createContextBudgetHook } from './context-budget';
|
|
5
|
+
export { safeHook, composeHandlers, createSystemEnhancerHook, createDelegationTrackerHook, createContextBudgetHook, };
|
package/dist/hooks/utils.d.ts
CHANGED
|
@@ -3,35 +3,11 @@ export declare function composeHandlers<I, O>(...fns: Array<(input: I, output: O
|
|
|
3
3
|
declare function isFileValidationEnabled(): boolean;
|
|
4
4
|
declare function getMaxFileBytes(): number;
|
|
5
5
|
declare function getMaxScanDepth(): number;
|
|
6
|
-
export declare function validateWriterPath(directory: string, filename: string): string
|
|
7
|
-
export declare function
|
|
6
|
+
export declare function validateWriterPath(directory: string, filename: string): Promise<string>;
|
|
7
|
+
export declare function validateSwarmPath(directory: string, filename: string): Promise<string>;
|
|
8
|
+
export declare function checkFileSizeLimit(filePath: string): Promise<void>;
|
|
8
9
|
export declare function checkDirectoryDepth(currentDepth: number): void;
|
|
9
|
-
export declare function isSymlink(filePath: string): boolean
|
|
10
|
+
export declare function isSymlink(filePath: string): Promise<boolean>;
|
|
10
11
|
export { getMaxFileBytes, getMaxScanDepth, isFileValidationEnabled };
|
|
11
12
|
export declare function readWriterFileAsync(directory: string, filename: string): Promise<string | null>;
|
|
12
|
-
|
|
13
|
-
* Estimates the number of tokens in a text string.
|
|
14
|
-
*
|
|
15
|
-
* **Formula:** `tokenCount ≈ Math.ceil(characterCount × 0.33)`
|
|
16
|
-
*
|
|
17
|
-
* This is based on the general observation that one token corresponds to roughly
|
|
18
|
-
* 3 characters of English text on average. The multiplier of 0.33 (1/3) provides
|
|
19
|
-
* a conservative upper-bound estimate.
|
|
20
|
-
*
|
|
21
|
-
* **Accuracy:**
|
|
22
|
-
* - Expected variance: approximately ±40%
|
|
23
|
-
* - Actual token counts vary significantly based on:
|
|
24
|
-
* - Language (non-English text often requires more tokens per character)
|
|
25
|
-
* - Content type (code, technical terms, vs. natural language)
|
|
26
|
-
* - Tokenizer model (GPT-3/4, Claude, etc. use different tokenization schemes)
|
|
27
|
-
* - Presence of special characters, whitespace, and punctuation
|
|
28
|
-
*
|
|
29
|
-
* **Recommendation:**
|
|
30
|
-
* Use this function only for rough budget planning and preliminary size estimates.
|
|
31
|
-
* For precise token counting required for API limits or billing, use the actual
|
|
32
|
-
* tokenizer of the target model (e.g., tiktoken for OpenAI models).
|
|
33
|
-
*
|
|
34
|
-
* @param text - The input string to estimate token count for
|
|
35
|
-
* @returns The estimated number of tokens (rounded up), or 0 for empty/null input
|
|
36
|
-
*/
|
|
37
|
-
export declare function estimateTokens(text: string): number;
|
|
13
|
+
export declare function readSwarmFileAsync(directory: string, filename: string): Promise<string | null>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { Plugin } from '@opencode-ai/plugin';
|
|
2
|
-
import type { AgentConfig as SDKAgentConfig
|
|
2
|
+
import type { AgentConfig as SDKAgentConfig } from '@opencode-ai/sdk';
|
|
3
3
|
import { type PluginConfig } from './config';
|
|
4
|
+
export * from './plan';
|
|
4
5
|
export declare function getSafeConfigKeys(config: PluginConfig): string[];
|
|
5
6
|
export declare function formatStartupLog(agentCount: number, configKeys: string[], directory: string): string;
|
|
6
|
-
|
|
7
|
+
export interface PluginInitConfig {
|
|
7
8
|
agent?: Record<string, SDKAgentConfig>;
|
|
8
|
-
}
|
|
9
|
-
export declare function ensureAgentMap(opencodeConfig:
|
|
9
|
+
}
|
|
10
|
+
export declare function ensureAgentMap(opencodeConfig: PluginInitConfig, agents: Record<string, SDKAgentConfig>, logger?: (message: string, data?: unknown) => void): PluginInitConfig;
|
|
10
11
|
export declare const WriterSwarmPlugin: Plugin;
|
|
11
12
|
export default WriterSwarmPlugin;
|