opencode-orchestrator 1.5.4 → 1.6.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/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![MIT License](https://img.shields.io/badge/license-MIT-red.svg)](LICENSE)
7
7
  [![npm](https://img.shields.io/npm/v/opencode-orchestrator.svg)](https://www.npmjs.com/package/opencode-orchestrator)
8
8
  <!-- VERSION:START -->
9
- **Version:** `1.5.4`
9
+ **Version:** `1.6.1`
10
10
  <!-- VERSION:END -->
11
11
  </div>
12
12
 
@@ -84,6 +84,8 @@ Model selection follows normal OpenCode inheritance. The plugin does not force a
84
84
 
85
85
  Legacy top-level concurrency keys (`agentConcurrency`, `providerConcurrency`, `modelConcurrency`, `defaultConcurrency`) are still accepted for backward compatibility, but the plugin tuple is the preferred location.
86
86
 
87
+ Plugin options are schema-described in `opencode-orchestrator.schema.json` (generated from the Zod source, shipped in the package) for editor autocomplete and validation. Invalid or missing option fields fall back to defaults rather than failing.
88
+
87
89
  ## 3. Run
88
90
 
89
91
  Inside OpenCode:
@@ -132,17 +134,32 @@ TUI commands:
132
134
 
133
135
  ## 4. How It Works
134
136
 
135
- ```mermaid
136
- flowchart LR
137
- U["/task input"] --> C["Commander"]
138
- C --> P["Planner"]
139
- C --> W["Worker pool"]
140
- W --> R["Reviewer"]
141
- P --> S["Mission state"]
142
- W --> S
143
- R --> V{"Verified?"}
144
- V -- "no" --> C
145
- V -- "yes" --> D["Done"]
137
+ ```text
138
+ /task input
139
+ |
140
+ v
141
+ +-------------+
142
+ +----->| Commander |
143
+ | +------+------+
144
+ | | delegates
145
+ | +-----+------+
146
+ | v v
147
+ | +---------+ +-------------+
148
+ | | Planner | | Worker pool |
149
+ | +----+----+ +--+-------+--+
150
+ | | writes | impl |
151
+ | v v v
152
+ | +------------------+ +----------+
153
+ | | Mission state | | Reviewer |
154
+ | | (.opencode/) | +----+-----+
155
+ | +------------------+ |
156
+ | v
157
+ | no (keep working) +-----------+
158
+ +-----------------------+ Verified? |
159
+ +-----+-----+
160
+ | yes
161
+ v
162
+ Done
146
163
  ```
147
164
 
148
165
  | Agent | Purpose |
@@ -152,6 +169,14 @@ flowchart LR
152
169
  | Worker | Implements scoped file changes with isolated context. |
153
170
  | Reviewer | Checks completion evidence, tests, and integration risk. |
154
171
 
172
+ The mission loop adjudicates continuation at the idle boundary rather than trusting a model's "done":
173
+
174
+ 1. Tool calls are observed to record changed files and verification runs; if files changed with no verification, the continuation prompt emphatically asks the model to run tests/build/lint and cite results (a nudge, never a hard block).
175
+ 2. Before declaring done the model is asked for a short self-account (scope fit, verification, residual risk).
176
+ 3. After sustained stagnation the loop stops blind retries and escalates: DECOMPOSE → RE-PLAN → ASK the user.
177
+
178
+ Memory retrieval is role-aware (planners favor structure, workers favor exact matches, reviewers favor breadth) and memory notes carry a relevance `horizon`.
179
+
155
180
  Runtime evidence is written only when enabled:
156
181
 
157
182
  | Artifact | Purpose |
@@ -163,13 +188,22 @@ Runtime evidence is written only when enabled:
163
188
 
164
189
  ## 5. Developer Notes
165
190
 
191
+ Local checks that mirror CI (`.github/workflows/ci.yml`), which gates every push and PR:
192
+
166
193
  ```bash
194
+ # TypeScript
167
195
  npm run build
168
196
  npx tsc --noEmit
169
197
  npm test
170
- cargo test --workspace --all-targets
198
+
199
+ # Rust (same gates CI enforces)
200
+ cargo fmt --all --check
201
+ cargo clippy --workspace --all-targets -- -D warnings
202
+ cargo test --workspace
171
203
  ```
172
204
 
205
+ Regenerate the plugin-options JSON Schema after changing option types: `npm run gen:schema`.
206
+
173
207
  Useful references:
174
208
 
175
209
  1. OpenCode plugins: https://opencode.ai/docs/plugins/
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Prompt Registry
3
+ *
4
+ * Central composition seam for agent system prompts (Builder-inspired:
5
+ * `builder_app/src/system_prompt.rs` + `template_engine.rs`).
6
+ *
7
+ * Goals:
8
+ * 1. One place that joins prompt sections into a final system prompt.
9
+ * 2. Lightweight `{{var}}` interpolation for runtime context injection.
10
+ * 3. Model-tier "profiles" — a `compact` profile drops sections tagged
11
+ * `verbose: true`, so weaker/smaller models can be given a leaner prompt
12
+ * without duplicating prompt text. The default `standard` profile keeps
13
+ * every section, so existing composed output is unchanged.
14
+ */
15
+ export type PromptProfile = "standard" | "compact";
16
+ /** A prompt fragment. Plain strings are always kept; tagged sections can be
17
+ * dropped by profile. */
18
+ export interface PromptSection {
19
+ body: string;
20
+ /** Dropped under the `compact` profile. */
21
+ verbose?: boolean;
22
+ }
23
+ export type PromptFragment = string | PromptSection;
24
+ /**
25
+ * Replace `{{key}}` placeholders. Unknown keys are left intact, so calling with
26
+ * no vars is an identity transform on text that has no placeholders.
27
+ */
28
+ export declare function interpolate(template: string, vars?: Record<string, string | number>): string;
29
+ export interface ComposeOptions {
30
+ profile?: PromptProfile;
31
+ vars?: Record<string, string | number>;
32
+ separator?: string;
33
+ }
34
+ /**
35
+ * Compose a final prompt from ordered fragments. With the default `standard`
36
+ * profile, no vars, and the default separator this is exactly
37
+ * `fragments.join("\n\n")` — preserving existing prompt output byte-for-byte.
38
+ */
39
+ export declare function composePrompt(fragments: PromptFragment[], options?: ComposeOptions): string;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Schema-driven plugin options (Builder learning, Phase G).
3
+ *
4
+ * Builder derives one JSON Schema from its config types so the TOML, the schema
5
+ * file, and the editor UI never drift (`builder.schema.json` via schemars).
6
+ * Orchestrator models its plugin-tuple options as Zod here, which is both the
7
+ * runtime validator and the source for the exported JSON Schema.
8
+ *
9
+ * Parsing stays tolerant — invalid or missing fields fall back to defaults —
10
+ * to preserve the previous hand-rolled behavior exactly.
11
+ */
12
+ import { z } from "zod";
13
+ export declare const MissionLoopOptionsSchema: z.ZodCatch<z.ZodObject<{
14
+ ledger: z.ZodDefault<z.ZodCatch<z.ZodBoolean>>;
15
+ markdownMemory: z.ZodDefault<z.ZodCatch<z.ZodBoolean>>;
16
+ maxEvidenceEvents: z.ZodDefault<z.ZodCatch<z.ZodNumber>>;
17
+ }, z.core.$strip>>;
18
+ /** Full plugin-tuple options object — used to generate the public JSON Schema. */
19
+ export declare const OrchestratorOptionsSchema: z.ZodObject<{
20
+ agentConcurrency: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
21
+ providerConcurrency: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
22
+ modelConcurrency: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
23
+ defaultConcurrency: z.ZodOptional<z.ZodNumber>;
24
+ missionLoop: z.ZodOptional<z.ZodCatch<z.ZodObject<{
25
+ ledger: z.ZodDefault<z.ZodCatch<z.ZodBoolean>>;
26
+ markdownMemory: z.ZodDefault<z.ZodCatch<z.ZodBoolean>>;
27
+ maxEvidenceEvents: z.ZodDefault<z.ZodCatch<z.ZodNumber>>;
28
+ }, z.core.$strip>>>;
29
+ }, z.core.$loose>;
30
+ /** Tolerant parse of the `missionLoop` option block. */
31
+ export declare function parseMissionLoopOptions(value: unknown): {
32
+ ledger: boolean;
33
+ markdownMemory: boolean;
34
+ maxEvidenceEvents: number;
35
+ };
36
+ /** JSON Schema for editor autocomplete / external validation. */
37
+ export declare function orchestratorOptionsJsonSchema(): unknown;
@@ -1,5 +1,5 @@
1
1
  export declare class KnowledgeContextProvider {
2
- buildPrompt(directory: string, query: string): string | null;
2
+ buildPrompt(directory: string, query: string, role?: string): string | null;
3
3
  private collectMarkdownFiles;
4
4
  private walkDirectory;
5
5
  private indexKnowledge;
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import type { TagIndexer } from "./tag-indexer.js";
6
6
  import type { GraphParser } from "./graph-parser.js";
7
+ import { type EngineWeights } from "./retrieval-weights.js";
7
8
  /** Unified search result with provenance tracking. */
8
9
  export interface SearchResult {
9
10
  noteName: string;
@@ -24,7 +25,7 @@ export declare class HybridSearch {
24
25
  /**
25
26
  * Fuse lexical, tag, and graph rankings via RRF to produce a single list.
26
27
  */
27
- search(query: string, maxResults?: number): SearchResult[];
28
+ search(query: string, maxResults?: number, weights?: EngineWeights): SearchResult[];
28
29
  /**
29
30
  * BM25-inspired term-frequency scoring across all indexed documents.
30
31
  * Approximates IDF via corpus size and document frequency.
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Role-aware retrieval weights and memory horizons.
3
+ *
4
+ * Builder learning (adoption assessment 2026-06-19, item E):
5
+ * - Context horizons classify a memory by how long it stays relevant
6
+ * (`builder` MEMORY-SYSTEM deep dive: strategic / execution / closure).
7
+ * - The contextual reranker biases retrieval engines differently per role
8
+ * (`unified_retrieval.rs`): planners favor structure, workers favor exact
9
+ * lexical hits, reviewers favor breadth.
10
+ *
11
+ * Defaults are neutral (all weights = 1), so omitting a role leaves retrieval
12
+ * behavior unchanged.
13
+ */
14
+ /** Relative multipliers applied to each hybrid-search engine's RRF contribution. */
15
+ export interface EngineWeights {
16
+ lexical: number;
17
+ tag: number;
18
+ graph: number;
19
+ }
20
+ export declare const NEUTRAL_WEIGHTS: EngineWeights;
21
+ /**
22
+ * Per-role engine bias. Roles are matched case-insensitively; unknown roles
23
+ * fall back to neutral weights.
24
+ */
25
+ export declare const ROLE_WEIGHTS: Record<string, EngineWeights>;
26
+ export declare function weightsForRole(role?: string | null): EngineWeights;
27
+ /** Memory relevance horizon, derived from the memory level. */
28
+ export type MemoryHorizon = "strategic" | "execution" | "closure";
29
+ export declare function horizonForLevel(level: string): MemoryHorizon;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Mission evidence store (Builder learning, adoption assessment item C).
3
+ *
4
+ * Builder treats a model's "done" as a candidate until the runtime has recorded
5
+ * enough evidence to close the task (`builder_app/src/plumbing_trace.rs`:
6
+ * changed files must be traced/verified). Orchestrator is a plugin and cannot
7
+ * own the per-turn loop, but it DOES own the mission-loop continuation decision.
8
+ *
9
+ * This module records, by observing `tool.execute.after`, which files a session
10
+ * changed and whether a verification command (test/build/lint) ran afterward. A
11
+ * "wiring gap" exists when files were changed with no later verification. The
12
+ * gap is surfaced as an emphatic continuation nudge — never a hard block — so it
13
+ * can never cause a false "not done" loop.
14
+ */
15
+ export declare function recordChangedFile(sessionID: string, filePath: string, now?: number): void;
16
+ export declare function recordVerification(sessionID: string, now?: number): void;
17
+ /** Record evidence from a single observed tool call. */
18
+ export declare function recordToolEvidence(sessionID: string, tool: string, args: Record<string, unknown> | undefined, now?: number): void;
19
+ /** Number of changed files with no verification recorded after the last change. */
20
+ export declare function getUnverifiedChangeCount(sessionID: string): number;
21
+ export declare function getChangedFiles(sessionID: string): string[];
22
+ export declare function clearEvidence(sessionID: string): void;
@@ -9,6 +9,8 @@ import type { MissionLoopState, MissionLoopOptions } from "../../shared/loop/typ
9
9
  type MissionContinuationContext = {
10
10
  verificationSummary?: string;
11
11
  continuationReason?: string;
12
+ /** Files changed this mission with no verification recorded afterward. */
13
+ unverifiedChanges?: number;
12
14
  };
13
15
  type MissionContinuationInput = string | MissionContinuationContext;
14
16
  /**