opencode-orchestrator 1.5.4 → 1.6.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 +21 -2
- package/dist/agents/prompts/registry.d.ts +39 -0
- package/dist/core/config/options-schema.d.ts +37 -0
- package/dist/core/knowledge/context-provider.d.ts +1 -1
- package/dist/core/knowledge/hybrid-search.d.ts +2 -1
- package/dist/core/knowledge/retrieval-weights.d.ts +29 -0
- package/dist/core/loop/evidence.d.ts +22 -0
- package/dist/core/loop/mission-loop.d.ts +2 -0
- package/dist/index.js +421 -48
- package/dist/index.js.map +4 -4
- package/dist/utils/parsing/safe-json.d.ts +17 -0
- package/opencode-orchestrator.schema.json +75 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://www.npmjs.com/package/opencode-orchestrator)
|
|
8
8
|
<!-- VERSION:START -->
|
|
9
|
-
**Version:** `1.
|
|
9
|
+
**Version:** `1.6.0`
|
|
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:
|
|
@@ -152,6 +154,14 @@ flowchart LR
|
|
|
152
154
|
| Worker | Implements scoped file changes with isolated context. |
|
|
153
155
|
| Reviewer | Checks completion evidence, tests, and integration risk. |
|
|
154
156
|
|
|
157
|
+
The mission loop adjudicates continuation at the idle boundary rather than trusting a model's "done":
|
|
158
|
+
|
|
159
|
+
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).
|
|
160
|
+
2. Before declaring done the model is asked for a short self-account (scope fit, verification, residual risk).
|
|
161
|
+
3. After sustained stagnation the loop stops blind retries and escalates: DECOMPOSE → RE-PLAN → ASK the user.
|
|
162
|
+
|
|
163
|
+
Memory retrieval is role-aware (planners favor structure, workers favor exact matches, reviewers favor breadth) and memory notes carry a relevance `horizon`.
|
|
164
|
+
|
|
155
165
|
Runtime evidence is written only when enabled:
|
|
156
166
|
|
|
157
167
|
| Artifact | Purpose |
|
|
@@ -163,13 +173,22 @@ Runtime evidence is written only when enabled:
|
|
|
163
173
|
|
|
164
174
|
## 5. Developer Notes
|
|
165
175
|
|
|
176
|
+
Local checks that mirror CI (`.github/workflows/ci.yml`), which gates every push and PR:
|
|
177
|
+
|
|
166
178
|
```bash
|
|
179
|
+
# TypeScript
|
|
167
180
|
npm run build
|
|
168
181
|
npx tsc --noEmit
|
|
169
182
|
npm test
|
|
170
|
-
|
|
183
|
+
|
|
184
|
+
# Rust (same gates CI enforces)
|
|
185
|
+
cargo fmt --all --check
|
|
186
|
+
cargo clippy --workspace --all-targets -- -D warnings
|
|
187
|
+
cargo test --workspace
|
|
171
188
|
```
|
|
172
189
|
|
|
190
|
+
Regenerate the plugin-options JSON Schema after changing option types: `npm run gen:schema`.
|
|
191
|
+
|
|
173
192
|
Useful references:
|
|
174
193
|
|
|
175
194
|
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
|
/**
|