oh-my-opencode-slim 2.0.5 → 2.1.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.ja-JP.md +47 -21
- package/README.ko-KR.md +45 -19
- package/README.md +56 -32
- package/README.zh-CN.md +48 -24
- package/dist/agents/council.d.ts +1 -1
- package/dist/agents/index.d.ts +7 -2
- package/dist/agents/orchestrator.d.ts +1 -1
- package/dist/cli/custom-skills-registry.d.ts +18 -0
- package/dist/cli/custom-skills.d.ts +3 -19
- package/dist/cli/index.js +1089 -185
- package/dist/cli/providers.d.ts +5 -9
- package/dist/cli/skills.d.ts +2 -2
- package/dist/companion/manager.d.ts +7 -0
- package/dist/config/loader.d.ts +5 -2
- package/dist/config/schema.d.ts +4 -0
- package/dist/council/council-manager.d.ts +1 -1
- package/dist/hooks/auto-update-checker/skill-sync.d.ts +59 -1
- package/dist/hooks/filter-available-skills/index.d.ts +1 -2
- package/dist/hooks/foreground-fallback/index.d.ts +5 -1
- package/dist/hooks/image-hook.d.ts +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/loop-command/index.d.ts +13 -0
- package/dist/hooks/phase-reminder/index.d.ts +1 -2
- package/dist/hooks/task-session-manager/index.d.ts +1 -2
- package/dist/hooks/task-session-manager/pending-call-tracker.d.ts +13 -0
- package/dist/hooks/task-session-manager/task-context-tracker.d.ts +14 -0
- package/dist/hooks/types.d.ts +3 -1
- package/dist/index.js +2116 -670
- package/dist/interview/dashboard-manager.d.ts +21 -0
- package/dist/interview/manager.d.ts +0 -14
- package/dist/interview/service.d.ts +7 -0
- package/dist/interview/session-server.d.ts +21 -0
- package/dist/interview/types.d.ts +3 -1
- package/dist/loop/loop-session.d.ts +64 -0
- package/dist/multiplexer/factory.d.ts +5 -5
- package/dist/multiplexer/herdr/index.d.ts +31 -0
- package/dist/multiplexer/index.d.ts +1 -0
- package/dist/multiplexer/session-manager.d.ts +1 -0
- package/dist/multiplexer/types.d.ts +4 -4
- package/dist/tui.js +82 -39
- package/dist/utils/background-job-board.d.ts +17 -1
- package/dist/utils/session.d.ts +1 -1
- package/oh-my-opencode-slim.schema.json +6 -1
- package/package.json +1 -1
- package/src/skills/clonedeps/SKILL.md +2 -2
- package/src/skills/clonedeps/codemap.md +23 -32
- package/src/skills/codemap/SKILL.md +2 -2
- package/src/skills/codemap.md +63 -36
- package/src/skills/loop-engineering/SKILL.md +30 -0
- package/src/skills/reflect/SKILL.md +133 -0
- package/src/skills/release-smoke-test/SKILL.md +159 -0
- package/src/skills/simplify/SKILL.md +6 -6
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PluginInput } from '@opencode-ai/plugin';
|
|
2
|
+
import type { PluginConfig } from '../config';
|
|
3
|
+
export declare function createDashboardManager(ctx: PluginInput, config: PluginConfig, dashboardPort: number, outputFolder: string): {
|
|
4
|
+
registerCommand: (config: Record<string, unknown>) => void;
|
|
5
|
+
handleCommandExecuteBefore: (input: {
|
|
6
|
+
command: string;
|
|
7
|
+
sessionID: string;
|
|
8
|
+
arguments: string;
|
|
9
|
+
}, output: {
|
|
10
|
+
parts: Array<{
|
|
11
|
+
type: string;
|
|
12
|
+
text?: string;
|
|
13
|
+
}>;
|
|
14
|
+
}) => Promise<void>;
|
|
15
|
+
handleEvent: (input: {
|
|
16
|
+
event: {
|
|
17
|
+
type: string;
|
|
18
|
+
properties?: Record<string, unknown>;
|
|
19
|
+
};
|
|
20
|
+
}) => Promise<void>;
|
|
21
|
+
};
|
|
@@ -1,19 +1,5 @@
|
|
|
1
1
|
import type { PluginInput } from '@opencode-ai/plugin';
|
|
2
2
|
import type { PluginConfig } from '../config';
|
|
3
|
-
/**
|
|
4
|
-
* Interview Manager — Composition root.
|
|
5
|
-
*
|
|
6
|
-
* Two modes:
|
|
7
|
-
*
|
|
8
|
-
* 1. **Dashboard mode** (dashboard:true or port>0):
|
|
9
|
-
* First process to bind the port becomes the dashboard (dumb aggregator).
|
|
10
|
-
* Other processes register as sessions and push state to it.
|
|
11
|
-
* Sessions drive LLM interaction locally, dashboard just serves the web UI.
|
|
12
|
-
*
|
|
13
|
-
* 2. **Per-session mode** (default, port=0, dashboard:false):
|
|
14
|
-
* Upstream behavior. Each process runs its own interview server on a random
|
|
15
|
-
* port. Lazy startup on first /interview command.
|
|
16
|
-
*/
|
|
17
3
|
export declare function createInterviewManager(ctx: PluginInput, config: PluginConfig): {
|
|
18
4
|
registerCommand: (config: Record<string, unknown>) => void;
|
|
19
5
|
handleCommandExecuteBefore: (input: {
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { PluginInput } from '@opencode-ai/plugin';
|
|
2
2
|
import type { InterviewConfig } from '../config';
|
|
3
3
|
import type { InterviewAnswer, InterviewFileItem, InterviewListItem, InterviewRecord, InterviewState } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Cap on retained abandoned interview records. Abandoned interviews are kept
|
|
6
|
+
* briefly so a still-open browser tab can render their final state, but
|
|
7
|
+
* without a bound the `interviewsById` and `browserOpened` collections grow
|
|
8
|
+
* for the life of a long-running session/dashboard process.
|
|
9
|
+
*/
|
|
10
|
+
export declare const MAX_RETAINED_ABANDONED = 50;
|
|
4
11
|
export declare function createInterviewService(ctx: PluginInput, config?: InterviewConfig, deps?: {
|
|
5
12
|
openBrowser?: (url: string) => void;
|
|
6
13
|
env?: NodeJS.ProcessEnv;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PluginInput } from '@opencode-ai/plugin';
|
|
2
|
+
import type { InterviewConfig } from '../config';
|
|
3
|
+
export declare function createPerSessionInterviewServer(ctx: PluginInput, interviewConfig: InterviewConfig | undefined, outputFolder: string): {
|
|
4
|
+
registerCommand: (config: Record<string, unknown>) => void;
|
|
5
|
+
handleCommandExecuteBefore: (input: {
|
|
6
|
+
command: string;
|
|
7
|
+
sessionID: string;
|
|
8
|
+
arguments: string;
|
|
9
|
+
}, output: {
|
|
10
|
+
parts: Array<{
|
|
11
|
+
type: string;
|
|
12
|
+
text?: string;
|
|
13
|
+
}>;
|
|
14
|
+
}) => Promise<void>;
|
|
15
|
+
handleEvent: (input: {
|
|
16
|
+
event: {
|
|
17
|
+
type: string;
|
|
18
|
+
properties?: Record<string, unknown>;
|
|
19
|
+
};
|
|
20
|
+
}) => Promise<void>;
|
|
21
|
+
};
|
|
@@ -14,7 +14,7 @@ export interface InterviewAssistantState {
|
|
|
14
14
|
title?: string;
|
|
15
15
|
questions: InterviewQuestion[];
|
|
16
16
|
}
|
|
17
|
-
/** Raw question object from LLM output
|
|
17
|
+
/** Raw question object from LLM output - loose, everything optional. */
|
|
18
18
|
export declare const RawQuestionSchema: z.ZodObject<{
|
|
19
19
|
id: z.ZodOptional<z.ZodString>;
|
|
20
20
|
question: z.ZodOptional<z.ZodString>;
|
|
@@ -33,6 +33,8 @@ export interface InterviewRecord {
|
|
|
33
33
|
idea: string;
|
|
34
34
|
markdownPath: string;
|
|
35
35
|
createdAt: string;
|
|
36
|
+
abandonedAt?: string;
|
|
37
|
+
abandonedOrder?: number;
|
|
36
38
|
status: 'active' | 'abandoned';
|
|
37
39
|
baseMessageCount: number;
|
|
38
40
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export declare function loopDirname(loopID: string, goal: string): string;
|
|
2
|
+
export type LoopPhase = 'executing' | 'verifying' | 'done' | 'escalated' | 'cancelled';
|
|
3
|
+
export type ExecuteAgent = 'fixer' | 'designer' | 'explorer' | 'librarian';
|
|
4
|
+
export type VerifyAgent = 'oracle' | 'observer' | 'test';
|
|
5
|
+
export type SuccessCriterion = {
|
|
6
|
+
type: 'test';
|
|
7
|
+
command: string;
|
|
8
|
+
} | {
|
|
9
|
+
type: 'build';
|
|
10
|
+
command: string;
|
|
11
|
+
} | {
|
|
12
|
+
type: 'lint';
|
|
13
|
+
command: string;
|
|
14
|
+
} | {
|
|
15
|
+
type: 'fileExists';
|
|
16
|
+
path: string;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'command';
|
|
19
|
+
command: string;
|
|
20
|
+
expectExitCode?: number;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'oracle';
|
|
23
|
+
} | {
|
|
24
|
+
type: 'observer';
|
|
25
|
+
} | {
|
|
26
|
+
type: 'manual';
|
|
27
|
+
};
|
|
28
|
+
export interface LoopDefinition {
|
|
29
|
+
goal: string;
|
|
30
|
+
successCriteria: string;
|
|
31
|
+
success: SuccessCriterion;
|
|
32
|
+
maxAttempts: number;
|
|
33
|
+
executeAgent: ExecuteAgent;
|
|
34
|
+
verifyAgent: VerifyAgent;
|
|
35
|
+
contextFiles?: string[];
|
|
36
|
+
parentSessionID?: string;
|
|
37
|
+
}
|
|
38
|
+
export type VerificationResult = {
|
|
39
|
+
passed: true;
|
|
40
|
+
reason: string;
|
|
41
|
+
} | {
|
|
42
|
+
passed: false;
|
|
43
|
+
reason: string;
|
|
44
|
+
suggestedFix?: string;
|
|
45
|
+
};
|
|
46
|
+
export interface AttemptRecord {
|
|
47
|
+
attemptNumber: number;
|
|
48
|
+
executionResult: string;
|
|
49
|
+
verificationResult: VerificationResult;
|
|
50
|
+
artifactPaths?: string[];
|
|
51
|
+
}
|
|
52
|
+
export interface LoopSession {
|
|
53
|
+
loopID: string;
|
|
54
|
+
definition: LoopDefinition;
|
|
55
|
+
currentPhase: LoopPhase;
|
|
56
|
+
attempts: number;
|
|
57
|
+
activeJobID?: string;
|
|
58
|
+
history: AttemptRecord[];
|
|
59
|
+
historyDir: string;
|
|
60
|
+
manualReviewPending: boolean;
|
|
61
|
+
}
|
|
62
|
+
export declare function createLoopSession(definition: LoopDefinition, loopID: string): LoopSession;
|
|
63
|
+
export declare function compactAttempt(attempt: AttemptRecord): string;
|
|
64
|
+
export declare function writeHistoryFile(session: LoopSession): void;
|
|
@@ -6,9 +6,9 @@ import type { Multiplexer } from './types';
|
|
|
6
6
|
/**
|
|
7
7
|
* Create a multiplexer instance based on config.
|
|
8
8
|
*
|
|
9
|
-
* Do not cache instances: tmux/zellij integrations may depend on
|
|
10
|
-
* per-process environment like TMUX_PANE/ZELLIJ, which should
|
|
11
|
-
* fresh for each plugin context.
|
|
9
|
+
* Do not cache instances: tmux/zellij/herdr integrations may depend on
|
|
10
|
+
* per-process environment like TMUX_PANE/ZELLIJ/HERDR_PANE_ID, which should
|
|
11
|
+
* be captured fresh for each plugin context.
|
|
12
12
|
*/
|
|
13
13
|
export declare function getMultiplexer(config: MultiplexerConfig): Multiplexer | null;
|
|
14
14
|
/**
|
|
@@ -17,9 +17,9 @@ export declare function getMultiplexer(config: MultiplexerConfig): Multiplexer |
|
|
|
17
17
|
export declare function clearMultiplexerCache(): void;
|
|
18
18
|
/**
|
|
19
19
|
* Get the effective multiplexer type for auto mode
|
|
20
|
-
* Returns the actual type that would be used (tmux/zellij/none)
|
|
20
|
+
* Returns the actual type that would be used (tmux/zellij/herdr/none)
|
|
21
21
|
*/
|
|
22
|
-
export declare function getAutoMultiplexerType(): 'tmux' | 'zellij' | 'none';
|
|
22
|
+
export declare function getAutoMultiplexerType(): 'tmux' | 'zellij' | 'herdr' | 'none';
|
|
23
23
|
/**
|
|
24
24
|
* Start background availability check for a multiplexer
|
|
25
25
|
*/
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Herdr multiplexer implementation
|
|
3
|
+
*
|
|
4
|
+
* Splits panes for sub-agent sessions in Herdr.
|
|
5
|
+
*
|
|
6
|
+
* Herdr is an agent-aware terminal multiplexer (workspaces → tabs → panes).
|
|
7
|
+
* Pane IDs use the format `w<workspace>:p<pane>`. The CLI outputs
|
|
8
|
+
* newline-delimited JSON; `pane split` returns a `pane_info` result whose
|
|
9
|
+
* `pane.pane_id` field is the new pane's ID.
|
|
10
|
+
*
|
|
11
|
+
* Environment detection: Herdr injects `HERDR_ENV=1` and `HERDR_PANE_ID`
|
|
12
|
+
* into every pane it manages.
|
|
13
|
+
*/
|
|
14
|
+
import type { MultiplexerLayout } from '../../config/schema';
|
|
15
|
+
import type { Multiplexer, PaneResult } from '../types';
|
|
16
|
+
export declare class HerdrMultiplexer implements Multiplexer {
|
|
17
|
+
readonly type: "herdr";
|
|
18
|
+
private binaryPath;
|
|
19
|
+
private hasChecked;
|
|
20
|
+
private readonly parentPaneId;
|
|
21
|
+
private readonly paneDirection;
|
|
22
|
+
constructor(layout?: MultiplexerLayout, mainPaneSize?: number);
|
|
23
|
+
isAvailable(): Promise<boolean>;
|
|
24
|
+
isInsideSession(): boolean;
|
|
25
|
+
spawnPane(sessionId: string, description: string, serverUrl: string, directory: string): Promise<PaneResult>;
|
|
26
|
+
closePane(paneId: string): Promise<boolean>;
|
|
27
|
+
applyLayout(_layout: MultiplexerLayout, _mainPaneSize: number): Promise<void>;
|
|
28
|
+
private targetPaneArg;
|
|
29
|
+
private getBinary;
|
|
30
|
+
private findBinary;
|
|
31
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Multiplexer module exports
|
|
3
3
|
*/
|
|
4
4
|
export { clearMultiplexerCache, getMultiplexer, startAvailabilityCheck, } from './factory';
|
|
5
|
+
export { HerdrMultiplexer } from './herdr';
|
|
5
6
|
export { MultiplexerSessionManager, TmuxSessionManager, } from './session-manager';
|
|
6
7
|
export { TmuxMultiplexer } from './tmux';
|
|
7
8
|
export type { Multiplexer, PaneResult } from './types';
|
|
@@ -49,6 +49,7 @@ export declare class MultiplexerSessionManager {
|
|
|
49
49
|
private isTrackedOrSpawning;
|
|
50
50
|
private updatePolling;
|
|
51
51
|
private getSessionId;
|
|
52
|
+
private backgroundJobState;
|
|
52
53
|
private isRunningBackgroundJob;
|
|
53
54
|
retryDeferredIdleClose(sessionId: string): Promise<void>;
|
|
54
55
|
cleanup(): Promise<void>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Multiplexer abstraction layer
|
|
3
3
|
*
|
|
4
|
-
* Provides a unified interface for terminal multiplexers (tmux, zellij,
|
|
5
|
-
* to spawn and manage panes for child agent sessions.
|
|
4
|
+
* Provides a unified interface for terminal multiplexers (tmux, zellij,
|
|
5
|
+
* herdr, etc.) to spawn and manage panes for child agent sessions.
|
|
6
6
|
*/
|
|
7
7
|
import type { MultiplexerConfig, MultiplexerLayout } from '../config/schema';
|
|
8
8
|
export interface PaneResult {
|
|
@@ -11,10 +11,10 @@ export interface PaneResult {
|
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* Core multiplexer interface
|
|
14
|
-
* Implementations: TmuxMultiplexer, ZellijMultiplexer
|
|
14
|
+
* Implementations: TmuxMultiplexer, ZellijMultiplexer, HerdrMultiplexer
|
|
15
15
|
*/
|
|
16
16
|
export interface Multiplexer {
|
|
17
|
-
readonly type: 'tmux' | 'zellij';
|
|
17
|
+
readonly type: 'tmux' | 'zellij' | 'herdr';
|
|
18
18
|
/**
|
|
19
19
|
* Check if the multiplexer binary is available on the system
|
|
20
20
|
*/
|
package/dist/tui.js
CHANGED
|
@@ -232,7 +232,7 @@ var CouncilConfigSchema = z.object({
|
|
|
232
232
|
default_preset: z.string().default("default"),
|
|
233
233
|
councillor_execution_mode: CouncillorExecutionModeSchema.describe('Execution mode for councillors. "serial" runs them one at a time (required for single-model systems). "parallel" runs them concurrently (default, faster for multi-model systems).'),
|
|
234
234
|
councillor_retries: z.number().int().min(0).max(5).default(3).describe("Number of retry attempts for councillors that return empty responses " + "(e.g. due to provider rate limiting). Default: 3 retries."),
|
|
235
|
-
master: z.unknown().optional().describe("DEPRECATED
|
|
235
|
+
master: z.unknown().optional().describe("DEPRECATED - ignored. Council agent synthesizes directly.")
|
|
236
236
|
}).transform((data) => {
|
|
237
237
|
const deprecated = [];
|
|
238
238
|
if (data.master !== undefined)
|
|
@@ -298,7 +298,13 @@ var AgentOverrideConfigSchema = z2.object({
|
|
|
298
298
|
options: z2.record(z2.string(), z2.unknown()).optional(),
|
|
299
299
|
displayName: z2.string().min(1).optional()
|
|
300
300
|
}).strict();
|
|
301
|
-
var MultiplexerTypeSchema = z2.enum([
|
|
301
|
+
var MultiplexerTypeSchema = z2.enum([
|
|
302
|
+
"auto",
|
|
303
|
+
"tmux",
|
|
304
|
+
"zellij",
|
|
305
|
+
"herdr",
|
|
306
|
+
"none"
|
|
307
|
+
]);
|
|
302
308
|
var MultiplexerLayoutSchema = z2.enum([
|
|
303
309
|
"main-horizontal",
|
|
304
310
|
"main-vertical",
|
|
@@ -367,24 +373,13 @@ var AcpAgentConfigSchema = z2.object({
|
|
|
367
373
|
permissionMode: AcpAgentPermissionModeSchema.default("ask")
|
|
368
374
|
}).strict();
|
|
369
375
|
var AcpAgentsConfigSchema = z2.record(z2.string(), AcpAgentConfigSchema);
|
|
370
|
-
function
|
|
376
|
+
function rejectOrchestratorPromptOnOrchestrator(overrides, ctx, pathPrefix) {
|
|
371
377
|
for (const [name, override] of Object.entries(overrides)) {
|
|
372
|
-
|
|
373
|
-
if (!isBuiltInOrAlias) {
|
|
374
|
-
continue;
|
|
375
|
-
}
|
|
376
|
-
if (override.prompt !== undefined) {
|
|
377
|
-
ctx.addIssue({
|
|
378
|
-
code: z2.ZodIssueCode.custom,
|
|
379
|
-
path: [...pathPrefix, name, "prompt"],
|
|
380
|
-
message: "prompt is only supported for custom agents"
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
if (override.orchestratorPrompt !== undefined) {
|
|
378
|
+
if (name === "orchestrator" && override.orchestratorPrompt !== undefined) {
|
|
384
379
|
ctx.addIssue({
|
|
385
380
|
code: z2.ZodIssueCode.custom,
|
|
386
381
|
path: [...pathPrefix, name, "orchestratorPrompt"],
|
|
387
|
-
message: "orchestratorPrompt is
|
|
382
|
+
message: "orchestratorPrompt is not supported for the orchestrator agent"
|
|
388
383
|
});
|
|
389
384
|
}
|
|
390
385
|
}
|
|
@@ -392,6 +387,7 @@ function validateCustomOnlyPromptFields(overrides, ctx, pathPrefix) {
|
|
|
392
387
|
var PluginConfigSchema = z2.object({
|
|
393
388
|
preset: z2.string().optional(),
|
|
394
389
|
setDefaultAgent: z2.boolean().optional(),
|
|
390
|
+
compactSidebar: z2.boolean().optional().describe("Use the compact TUI sidebar layout when enabled."),
|
|
395
391
|
autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
|
|
396
392
|
presets: z2.record(z2.string(), PresetSchema).optional(),
|
|
397
393
|
agents: z2.record(z2.string(), AgentOverrideConfigSchema).optional(),
|
|
@@ -410,11 +406,14 @@ var PluginConfigSchema = z2.object({
|
|
|
410
406
|
acpAgents: AcpAgentsConfigSchema.optional()
|
|
411
407
|
}).superRefine((value, ctx) => {
|
|
412
408
|
if (value.agents) {
|
|
413
|
-
|
|
409
|
+
rejectOrchestratorPromptOnOrchestrator(value.agents, ctx, ["agents"]);
|
|
414
410
|
}
|
|
415
411
|
if (value.presets) {
|
|
416
412
|
for (const [presetName, preset] of Object.entries(value.presets)) {
|
|
417
|
-
|
|
413
|
+
rejectOrchestratorPromptOnOrchestrator(preset, ctx, [
|
|
414
|
+
"presets",
|
|
415
|
+
presetName
|
|
416
|
+
]);
|
|
418
417
|
}
|
|
419
418
|
}
|
|
420
419
|
});
|
|
@@ -470,7 +469,7 @@ function getAgentMcpList(agentName, config) {
|
|
|
470
469
|
return defaultMcps ?? [];
|
|
471
470
|
}
|
|
472
471
|
|
|
473
|
-
// src/cli/custom-skills.ts
|
|
472
|
+
// src/cli/custom-skills-registry.ts
|
|
474
473
|
var CUSTOM_SKILLS = [
|
|
475
474
|
{
|
|
476
475
|
name: "simplify",
|
|
@@ -508,6 +507,12 @@ var CUSTOM_SKILLS = [
|
|
|
508
507
|
allowedAgents: ["orchestrator"],
|
|
509
508
|
sourcePath: "src/skills/oh-my-opencode-slim"
|
|
510
509
|
},
|
|
510
|
+
{
|
|
511
|
+
name: "release-smoke-test",
|
|
512
|
+
description: "Validate packed release candidates and bugfixes before public publish",
|
|
513
|
+
allowedAgents: ["orchestrator"],
|
|
514
|
+
sourcePath: "src/skills/release-smoke-test"
|
|
515
|
+
},
|
|
511
516
|
{
|
|
512
517
|
name: "worktrees",
|
|
513
518
|
description: "Manage Git worktrees as OMO safe isolated coding lanes for complex/risky/parallel work",
|
|
@@ -605,6 +610,7 @@ function mergePluginConfigs(base, override) {
|
|
|
605
610
|
...base,
|
|
606
611
|
...override,
|
|
607
612
|
agents: deepMerge(base.agents, override.agents),
|
|
613
|
+
presets: deepMerge(base.presets, override.presets),
|
|
608
614
|
tmux: deepMerge(base.tmux, override.tmux),
|
|
609
615
|
multiplexer: deepMerge(base.multiplexer, override.multiplexer),
|
|
610
616
|
interview: deepMerge(base.interview, override.interview),
|
|
@@ -676,15 +682,33 @@ function loadPluginConfig(directory, options) {
|
|
|
676
682
|
}
|
|
677
683
|
return config;
|
|
678
684
|
}
|
|
679
|
-
function loadAgentPrompt(agentName,
|
|
685
|
+
function loadAgentPrompt(agentName, optionsOrPreset) {
|
|
686
|
+
let preset;
|
|
687
|
+
let projectDirectory;
|
|
688
|
+
if (typeof optionsOrPreset === "string") {
|
|
689
|
+
preset = optionsOrPreset;
|
|
690
|
+
} else if (optionsOrPreset && typeof optionsOrPreset === "object") {
|
|
691
|
+
preset = optionsOrPreset.preset;
|
|
692
|
+
projectDirectory = optionsOrPreset.projectDirectory;
|
|
693
|
+
}
|
|
680
694
|
const presetDirName = preset && /^[a-zA-Z0-9_-]+$/.test(preset) ? preset : undefined;
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
}
|
|
685
|
-
|
|
695
|
+
const searchDirs = [];
|
|
696
|
+
if (projectDirectory && presetDirName) {
|
|
697
|
+
searchDirs.push(path.join(projectDirectory, ".opencode", PROMPTS_DIR_NAME, presetDirName));
|
|
698
|
+
}
|
|
699
|
+
if (projectDirectory) {
|
|
700
|
+
searchDirs.push(path.join(projectDirectory, ".opencode", PROMPTS_DIR_NAME));
|
|
701
|
+
}
|
|
702
|
+
if (presetDirName) {
|
|
703
|
+
for (const userDir of getConfigSearchDirs()) {
|
|
704
|
+
searchDirs.push(path.join(userDir, PROMPTS_DIR_NAME, presetDirName));
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
for (const userDir of getConfigSearchDirs()) {
|
|
708
|
+
searchDirs.push(path.join(userDir, PROMPTS_DIR_NAME));
|
|
709
|
+
}
|
|
686
710
|
const readFirstPrompt = (fileName, errorPrefix) => {
|
|
687
|
-
for (const dir of
|
|
711
|
+
for (const dir of searchDirs) {
|
|
688
712
|
const promptPath = path.join(dir, fileName);
|
|
689
713
|
if (!fs.existsSync(promptPath)) {
|
|
690
714
|
continue;
|
|
@@ -697,6 +721,7 @@ function loadAgentPrompt(agentName, preset) {
|
|
|
697
721
|
}
|
|
698
722
|
return;
|
|
699
723
|
};
|
|
724
|
+
const result = {};
|
|
700
725
|
result.prompt = readFirstPrompt(`${agentName}.md`, "Error reading prompt file");
|
|
701
726
|
result.appendPrompt = readFirstPrompt(`${agentName}_append.md`, "Error reading append prompt file");
|
|
702
727
|
return result;
|
|
@@ -859,25 +884,36 @@ function getSidebarAgentNames(snapshot) {
|
|
|
859
884
|
function agentRow(label, model, variant, theme) {
|
|
860
885
|
const modelParts = splitSidebarModelId(model);
|
|
861
886
|
const detailRows = [];
|
|
887
|
+
function detailRow(fieldLabel, value) {
|
|
888
|
+
return box({ width: "100%", flexDirection: "row", paddingLeft: 2 }, [
|
|
889
|
+
text({ fg: theme.textMuted, width: 9 }, [fieldLabel]),
|
|
890
|
+
text({ fg: theme.textMuted }, [value])
|
|
891
|
+
]);
|
|
892
|
+
}
|
|
862
893
|
if (modelParts.provider) {
|
|
863
|
-
detailRows.push(
|
|
894
|
+
detailRows.push(detailRow("provider", modelParts.provider));
|
|
864
895
|
}
|
|
865
|
-
detailRows.push(
|
|
896
|
+
detailRows.push(detailRow("model", modelParts.model));
|
|
866
897
|
if (variant) {
|
|
867
|
-
detailRows.push(
|
|
898
|
+
detailRows.push(detailRow("variant", variant));
|
|
868
899
|
}
|
|
869
900
|
return box({ width: "100%", flexDirection: "column", marginBottom: 1 }, [
|
|
870
901
|
text({ fg: theme.textMuted }, [label]),
|
|
871
902
|
...detailRows
|
|
872
903
|
]);
|
|
873
904
|
}
|
|
874
|
-
function
|
|
875
|
-
|
|
876
|
-
|
|
905
|
+
function compactAgentRow(label, model, variant, theme) {
|
|
906
|
+
const value = variant ? `${model} (${variant})` : model;
|
|
907
|
+
return box({
|
|
908
|
+
width: "100%",
|
|
909
|
+
flexDirection: "row",
|
|
910
|
+
justifyContent: "space-between"
|
|
911
|
+
}, [
|
|
912
|
+
text({ fg: theme.textMuted, width: 14 }, [label]),
|
|
877
913
|
text({ fg: theme.textMuted }, [value])
|
|
878
914
|
]);
|
|
879
915
|
}
|
|
880
|
-
function renderSidebar(snapshot, version, theme, configInvalid) {
|
|
916
|
+
function renderSidebar(snapshot, version, theme, configInvalid, compactSidebar) {
|
|
881
917
|
const configStatusRow = buildConfigStatusRow(configInvalid, theme);
|
|
882
918
|
return box({
|
|
883
919
|
width: "100%",
|
|
@@ -905,6 +941,9 @@ function renderSidebar(snapshot, version, theme, configInvalid) {
|
|
|
905
941
|
...getSidebarAgentNames(snapshot).map((agentName) => {
|
|
906
942
|
const model = snapshot.agentModels[agentName] ?? "pending";
|
|
907
943
|
const variant = snapshot.agentVariants[agentName];
|
|
944
|
+
if (compactSidebar) {
|
|
945
|
+
return compactAgentRow(agentName, model, variant, theme);
|
|
946
|
+
}
|
|
908
947
|
return agentRow(agentName, model, variant, theme);
|
|
909
948
|
})
|
|
910
949
|
]);
|
|
@@ -922,15 +961,19 @@ function buildConfigStatusRow(configInvalid, theme) {
|
|
|
922
961
|
text({ fg: theme.textMuted }, ["Run doctor for details"])
|
|
923
962
|
]);
|
|
924
963
|
}
|
|
925
|
-
function
|
|
964
|
+
function readConfigState(directory) {
|
|
926
965
|
let configInvalid = false;
|
|
927
|
-
loadPluginConfig(directory, {
|
|
966
|
+
const config = loadPluginConfig(directory, {
|
|
928
967
|
silent: true,
|
|
929
968
|
onWarning: () => {
|
|
930
969
|
configInvalid = true;
|
|
931
970
|
}
|
|
932
971
|
});
|
|
933
|
-
|
|
972
|
+
const compactSidebar = config.compactSidebar ?? false;
|
|
973
|
+
return { configInvalid, compactSidebar };
|
|
974
|
+
}
|
|
975
|
+
function readConfigInvalid(directory) {
|
|
976
|
+
return readConfigState(directory).configInvalid;
|
|
934
977
|
}
|
|
935
978
|
var plugin = {
|
|
936
979
|
id: `${PLUGIN_NAME}:tui`,
|
|
@@ -939,7 +982,7 @@ var plugin = {
|
|
|
939
982
|
return;
|
|
940
983
|
const version = meta.version ?? await readPackageVersion() ?? "dev";
|
|
941
984
|
let configDirectory = getTuiDirectory(api);
|
|
942
|
-
let configInvalid =
|
|
985
|
+
let { configInvalid, compactSidebar } = readConfigState(configDirectory);
|
|
943
986
|
let snapshot = readTuiSnapshot();
|
|
944
987
|
const renderTimer = setInterval(async () => {
|
|
945
988
|
try {
|
|
@@ -947,7 +990,7 @@ var plugin = {
|
|
|
947
990
|
const currentDirectory = getTuiDirectory(api);
|
|
948
991
|
if (currentDirectory !== configDirectory) {
|
|
949
992
|
configDirectory = currentDirectory;
|
|
950
|
-
configInvalid =
|
|
993
|
+
({ configInvalid, compactSidebar } = readConfigState(configDirectory));
|
|
951
994
|
}
|
|
952
995
|
api.renderer.requestRender();
|
|
953
996
|
} catch {}
|
|
@@ -959,7 +1002,7 @@ var plugin = {
|
|
|
959
1002
|
order: 900,
|
|
960
1003
|
slots: {
|
|
961
1004
|
sidebar_content() {
|
|
962
|
-
return renderSidebar(snapshot, version, api.theme.current, configInvalid);
|
|
1005
|
+
return renderSidebar(snapshot, version, api.theme.current, configInvalid, compactSidebar);
|
|
963
1006
|
}
|
|
964
1007
|
}
|
|
965
1008
|
});
|
|
@@ -14,6 +14,7 @@ export interface BackgroundJobRecord {
|
|
|
14
14
|
objective?: string;
|
|
15
15
|
state: BackgroundJobState;
|
|
16
16
|
timedOut: boolean;
|
|
17
|
+
recoverableAfterLiveBusy: boolean;
|
|
17
18
|
statusUncertain: boolean;
|
|
18
19
|
cancellationRequested: boolean;
|
|
19
20
|
terminalUnreconciled: boolean;
|
|
@@ -28,6 +29,9 @@ export interface BackgroundJobRecord {
|
|
|
28
29
|
lastUsedAt: number;
|
|
29
30
|
terminalState?: TaskOutputState;
|
|
30
31
|
contextFiles: ContextFile[];
|
|
32
|
+
totalErrors?: number;
|
|
33
|
+
timeoutCount?: number;
|
|
34
|
+
lastErrorAt?: number;
|
|
31
35
|
}
|
|
32
36
|
export interface BackgroundJobBoardOptions {
|
|
33
37
|
maxReusablePerAgent?: number;
|
|
@@ -55,12 +59,15 @@ type TerminalStateListener = (taskID: string) => void;
|
|
|
55
59
|
export declare class BackgroundJobBoard {
|
|
56
60
|
private readonly jobs;
|
|
57
61
|
private readonly counters;
|
|
58
|
-
private
|
|
62
|
+
private terminalStateListeners;
|
|
59
63
|
private readonly maxReusablePerAgent;
|
|
60
64
|
private readonly readContextMinLines;
|
|
61
65
|
private readonly readContextMaxFiles;
|
|
62
66
|
constructor(options?: BackgroundJobBoardOptions);
|
|
67
|
+
addTerminalStateListener(listener: TerminalStateListener): void;
|
|
68
|
+
removeTerminalStateListener(listener: TerminalStateListener): void;
|
|
63
69
|
setTerminalStateListener(listener?: TerminalStateListener): void;
|
|
70
|
+
private notifyTerminalStateListeners;
|
|
64
71
|
registerLaunch(input: BackgroundJobLaunchInput): BackgroundJobRecord;
|
|
65
72
|
updateStatus(input: BackgroundJobStatusInput): BackgroundJobRecord | undefined;
|
|
66
73
|
updateFromStatusOutput(output: string): BackgroundJobRecord | undefined;
|
|
@@ -70,14 +77,23 @@ export declare class BackgroundJobBoard {
|
|
|
70
77
|
force?: boolean;
|
|
71
78
|
}): BackgroundJobRecord | undefined;
|
|
72
79
|
get(taskID: string): BackgroundJobRecord | undefined;
|
|
80
|
+
field<K extends keyof BackgroundJobRecord>(taskID: string, key: K): BackgroundJobRecord[K] | undefined;
|
|
81
|
+
isRunning(taskID: string): boolean;
|
|
82
|
+
isTerminalUnreconciled(taskID: string): boolean;
|
|
83
|
+
getResultSummary(taskID: string): string | undefined;
|
|
84
|
+
getLastLiveBusyAt(taskID: string): number | undefined;
|
|
85
|
+
getParentSessionID(taskID: string): string | undefined;
|
|
86
|
+
getState(taskID: string): BackgroundJobState | undefined;
|
|
73
87
|
resolve(parentSessionID: string, taskIDOrAlias: string): BackgroundJobRecord | undefined;
|
|
74
88
|
resolveReusable(parentSessionID: string, taskIDOrAlias: string, agent?: string): BackgroundJobRecord | undefined;
|
|
89
|
+
resolveRecoverable(parentSessionID: string, taskIDOrAlias: string, agent?: string): BackgroundJobRecord | undefined;
|
|
75
90
|
markUsed(parentSessionID: string, key: string, now?: number): void;
|
|
76
91
|
taskIDs(): Set<string>;
|
|
77
92
|
addContext(taskID: string, files: ContextFile[]): void;
|
|
78
93
|
list(parentSessionID?: string): BackgroundJobRecord[];
|
|
79
94
|
hasRunning(parentSessionID: string): boolean;
|
|
80
95
|
hasTerminalUnreconciled(parentSessionID: string): boolean;
|
|
96
|
+
hasConvergenceSignals(taskID: string, threshold?: number): boolean;
|
|
81
97
|
formatForPrompt(parentSessionID: string, now?: number): string | undefined;
|
|
82
98
|
clearParent(parentSessionID: string): void;
|
|
83
99
|
drop(taskID: string): void;
|
package/dist/utils/session.d.ts
CHANGED
|
@@ -52,7 +52,7 @@ export declare function parseModelReference(model: string): {
|
|
|
52
52
|
export declare function promptWithTimeout(client: OpencodeClient, args: Parameters<OpencodeClient['session']['prompt']>[0], timeoutMs: number, signal?: AbortSignal): Promise<void>;
|
|
53
53
|
/**
|
|
54
54
|
* Result of extracting session content.
|
|
55
|
-
* `empty` is true when the assistant produced zero text content
|
|
55
|
+
* `empty` is true when the assistant produced zero text content -
|
|
56
56
|
* the provider returned an empty response (e.g. rate-limited silently).
|
|
57
57
|
*/
|
|
58
58
|
export interface SessionExtractionResult {
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
"setDefaultAgent": {
|
|
9
9
|
"type": "boolean"
|
|
10
10
|
},
|
|
11
|
+
"compactSidebar": {
|
|
12
|
+
"description": "Use the compact TUI sidebar layout when enabled.",
|
|
13
|
+
"type": "boolean"
|
|
14
|
+
},
|
|
11
15
|
"autoUpdate": {
|
|
12
16
|
"description": "Disable automatic installation of plugin updates when false. Defaults to true.",
|
|
13
17
|
"type": "boolean"
|
|
@@ -221,6 +225,7 @@
|
|
|
221
225
|
"auto",
|
|
222
226
|
"tmux",
|
|
223
227
|
"zellij",
|
|
228
|
+
"herdr",
|
|
224
229
|
"none"
|
|
225
230
|
]
|
|
226
231
|
},
|
|
@@ -417,7 +422,7 @@
|
|
|
417
422
|
"maximum": 5
|
|
418
423
|
},
|
|
419
424
|
"master": {
|
|
420
|
-
"description": "DEPRECATED
|
|
425
|
+
"description": "DEPRECATED - ignored. Council agent synthesizes directly."
|
|
421
426
|
}
|
|
422
427
|
},
|
|
423
428
|
"required": [
|
package/package.json
CHANGED
|
@@ -217,9 +217,9 @@ sentence so future agents do not need an extra read just to know what is there:
|
|
|
217
217
|
Read-only dependency source repositories are available under
|
|
218
218
|
`.slim/clonedeps/repos/` for inspection. Do not edit these clones.
|
|
219
219
|
|
|
220
|
-
- `.slim/clonedeps/repos/<safe-name>/`
|
|
220
|
+
- `.slim/clonedeps/repos/<safe-name>/` - `<repo>` at `<ref>`; <one sentence on
|
|
221
221
|
why this source is useful>.
|
|
222
|
-
- `.slim/clonedeps/repos/<safe-name-2>/`
|
|
222
|
+
- `.slim/clonedeps/repos/<safe-name-2>/` - `<repo>` at `<ref>`; <one sentence on
|
|
223
223
|
why this source is useful>.
|
|
224
224
|
```
|
|
225
225
|
|