oh-my-opencode-slim 2.1.0 → 2.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.ja-JP.md +31 -30
- package/README.ko-KR.md +31 -30
- package/README.md +57 -33
- package/README.zh-CN.md +29 -28
- package/dist/agents/index.d.ts +0 -4
- package/dist/cli/index.js +90 -16
- package/dist/cli/providers.d.ts +7 -7
- package/dist/companion/manager.d.ts +3 -0
- package/dist/config/constants.d.ts +12 -1
- package/dist/config/council-schema.d.ts +37 -12
- package/dist/config/schema.d.ts +691 -6
- package/dist/config/strip-orchestrator-model.d.ts +9 -0
- package/dist/council/council-manager.d.ts +7 -3
- package/dist/hooks/command-hook-utils.d.ts +5 -0
- package/dist/hooks/foreground-fallback/index.d.ts +53 -7
- package/dist/hooks/image-hook.d.ts +1 -0
- package/dist/hooks/index.d.ts +2 -1
- package/dist/hooks/phase-reminder/index.d.ts +3 -1
- package/dist/hooks/post-file-tool-nudge/index.d.ts +15 -9
- package/dist/hooks/session-lifecycle.d.ts +11 -0
- package/dist/hooks/task-session-manager/index.d.ts +14 -2
- package/dist/index.js +2804 -801
- package/dist/interview/document.d.ts +2 -1
- package/dist/interview/service.d.ts +2 -0
- package/dist/interview/ui.d.ts +0 -1
- package/dist/multiplexer/cmux/close-policy.d.ts +20 -0
- package/dist/multiplexer/cmux/index.d.ts +102 -0
- package/dist/multiplexer/cmux/session-lifecycle.d.ts +92 -0
- package/dist/multiplexer/cmux/session-state.d.ts +45 -0
- package/dist/multiplexer/factory.d.ts +0 -9
- package/dist/multiplexer/herdr/index.d.ts +7 -3
- package/dist/multiplexer/index.d.ts +3 -1
- package/dist/multiplexer/session-manager.d.ts +14 -6
- package/dist/multiplexer/shared.d.ts +31 -0
- package/dist/multiplexer/tmux/index.d.ts +0 -1
- package/dist/multiplexer/types.d.ts +5 -2
- package/dist/multiplexer/zellij/index.d.ts +0 -1
- package/dist/tools/cancel-task.d.ts +2 -2
- package/dist/tools/smartfetch/utils.d.ts +3 -1
- package/dist/tui-state.d.ts +5 -5
- package/dist/tui.d.ts +1 -0
- package/dist/tui.js +130 -29
- package/dist/utils/background-job-board.d.ts +5 -1
- package/dist/utils/background-job-coordinator.d.ts +72 -0
- package/dist/utils/background-job-store.d.ts +46 -0
- package/dist/utils/councillor-models.d.ts +20 -0
- package/dist/utils/escape-html.d.ts +1 -0
- package/dist/utils/frontmatter.d.ts +6 -0
- package/dist/utils/index.d.ts +3 -1
- package/dist/utils/internal-initiator.d.ts +6 -1
- package/dist/utils/logger.d.ts +0 -2
- package/dist/utils/session.d.ts +1 -1
- package/oh-my-opencode-slim.schema.json +728 -1
- package/package.json +1 -1
- package/src/skills/clonedeps/SKILL.md +32 -29
- package/src/skills/codemap.md +7 -3
- package/src/skills/deepwork/SKILL.md +17 -6
- package/src/skills/oh-my-opencode-slim/SKILL.md +3 -3
- package/src/skills/release-smoke-test/SKILL.md +2 -2
- package/src/skills/verification-planning/SKILL.md +103 -0
- package/src/skills/worktrees/SKILL.md +14 -7
package/dist/tui.js
CHANGED
|
@@ -131,7 +131,12 @@ var POLL_INTERVAL_BACKGROUND_MS = 2000;
|
|
|
131
131
|
var MAX_POLL_TIME_MS = 5 * 60 * 1000;
|
|
132
132
|
var DEFAULT_MAX_SUBAGENT_DEPTH = 3;
|
|
133
133
|
var PHASE_REMINDER_TEXT = `!IMPORTANT! Scheduler workflow: plan lanes/dependencies → dispatch background specialists → track task IDs → wait for hook-driven completion → reconcile terminal results → verify. Do not poll running jobs, consume running-job output, or advance dependent work. !END!`;
|
|
134
|
-
|
|
134
|
+
function formatSystemReminder(text) {
|
|
135
|
+
return `<system-reminder>
|
|
136
|
+
${text}
|
|
137
|
+
</system-reminder>`;
|
|
138
|
+
}
|
|
139
|
+
var PHASE_REMINDER = formatSystemReminder(PHASE_REMINDER_TEXT);
|
|
135
140
|
var WRITABLE_FILE_OPERATIONS_RULES = `**File Operations Rules**:
|
|
136
141
|
- Prefer dedicated file tools for normal code work: glob/grep/ast_grep_search for discovery, read for file contents, and edit/write/apply_patch for targeted source changes.
|
|
137
142
|
- Use bash for execution and automation: git, package managers, tests, builds, scripts, diagnostics, and shell-native filesystem operations.
|
|
@@ -150,6 +155,13 @@ var NO_SHELL_READONLY_FILE_OPERATIONS_RULES = `**File Operations Rules**:
|
|
|
150
155
|
var TMUX_SPAWN_DELAY_MS = 500;
|
|
151
156
|
var COUNCILLOR_STAGGER_MS = 250;
|
|
152
157
|
var DEFAULT_DISABLED_AGENTS = ["observer"];
|
|
158
|
+
var DEFAULT_MAX_SESSIONS_PER_AGENT = 2;
|
|
159
|
+
var DEFAULT_READ_CONTEXT_MIN_LINES = 10;
|
|
160
|
+
var DEFAULT_READ_CONTEXT_MAX_FILES = 8;
|
|
161
|
+
var DEFAULT_IMAGE_ROUTING = "auto";
|
|
162
|
+
function resolveImageRouting(imageRouting) {
|
|
163
|
+
return imageRouting ?? DEFAULT_IMAGE_ROUTING;
|
|
164
|
+
}
|
|
153
165
|
|
|
154
166
|
// src/config/loader.ts
|
|
155
167
|
import * as fs from "node:fs";
|
|
@@ -188,11 +200,35 @@ function getOpenCodeConfigPaths() {
|
|
|
188
200
|
}
|
|
189
201
|
// src/config/council-schema.ts
|
|
190
202
|
import { z } from "zod";
|
|
191
|
-
|
|
203
|
+
|
|
204
|
+
// src/utils/councillor-models.ts
|
|
205
|
+
function normalizeCouncillorModels(model, fallbackVariant) {
|
|
206
|
+
const raw = Array.isArray(model) ? model : [model];
|
|
207
|
+
return raw.map((entry) => typeof entry === "string" ? { id: entry, variant: fallbackVariant } : { id: entry.id, variant: entry.variant ?? fallbackVariant });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/config/council-schema.ts
|
|
211
|
+
var ModelIdSchema = z.string().regex(/^[^/\s]+\/[^\s]+$/, 'Expected provider/model format (e.g. "openai/gpt-5.6-luna")');
|
|
212
|
+
var CouncillorModelEntrySchema = z.object({
|
|
213
|
+
id: ModelIdSchema,
|
|
214
|
+
variant: z.string().optional()
|
|
215
|
+
});
|
|
216
|
+
var CouncillorModelSchema = z.union([
|
|
217
|
+
ModelIdSchema,
|
|
218
|
+
z.array(z.union([ModelIdSchema, CouncillorModelEntrySchema])).min(1)
|
|
219
|
+
]).describe('Model ID in provider/model format (e.g. "openai/gpt-5.6-luna"), or an ' + "ordered fallback chain (array of model IDs or { id, variant } entries) " + "tried in order until one responds.");
|
|
192
220
|
var CouncillorConfigSchema = z.object({
|
|
193
|
-
model:
|
|
221
|
+
model: CouncillorModelSchema,
|
|
194
222
|
variant: z.string().optional(),
|
|
195
223
|
prompt: z.string().optional().describe("Optional role/guidance injected into the councillor user prompt")
|
|
224
|
+
}).transform((c) => {
|
|
225
|
+
const models = normalizeCouncillorModels(c.model, c.variant);
|
|
226
|
+
return {
|
|
227
|
+
model: models[0].id,
|
|
228
|
+
variant: c.variant,
|
|
229
|
+
prompt: c.prompt,
|
|
230
|
+
models
|
|
231
|
+
};
|
|
196
232
|
});
|
|
197
233
|
var CouncilPresetSchema = z.record(z.string(), z.record(z.string(), z.unknown())).transform((entries, ctx) => {
|
|
198
234
|
const councillors = {};
|
|
@@ -278,6 +314,33 @@ var ManualPlanSchema = z2.object({
|
|
|
278
314
|
librarian: ManualAgentPlanSchema,
|
|
279
315
|
fixer: ManualAgentPlanSchema
|
|
280
316
|
}).strict();
|
|
317
|
+
var PermissionActionSchema = z2.enum(["ask", "allow", "deny"]);
|
|
318
|
+
var PermissionRuleSchema = z2.union([
|
|
319
|
+
PermissionActionSchema,
|
|
320
|
+
z2.record(z2.string(), PermissionActionSchema)
|
|
321
|
+
]);
|
|
322
|
+
var PermissionObjectSchema = z2.object({
|
|
323
|
+
read: PermissionRuleSchema.optional(),
|
|
324
|
+
edit: PermissionRuleSchema.optional(),
|
|
325
|
+
glob: PermissionRuleSchema.optional(),
|
|
326
|
+
grep: PermissionRuleSchema.optional(),
|
|
327
|
+
list: PermissionRuleSchema.optional(),
|
|
328
|
+
bash: PermissionRuleSchema.optional(),
|
|
329
|
+
task: PermissionRuleSchema.optional(),
|
|
330
|
+
external_directory: PermissionRuleSchema.optional(),
|
|
331
|
+
lsp: PermissionRuleSchema.optional(),
|
|
332
|
+
skill: PermissionRuleSchema.optional(),
|
|
333
|
+
todowrite: PermissionActionSchema.optional(),
|
|
334
|
+
question: PermissionActionSchema.optional(),
|
|
335
|
+
webfetch: PermissionActionSchema.optional(),
|
|
336
|
+
websearch: PermissionActionSchema.optional(),
|
|
337
|
+
codesearch: PermissionActionSchema.optional(),
|
|
338
|
+
doom_loop: PermissionActionSchema.optional()
|
|
339
|
+
}).catchall(PermissionRuleSchema);
|
|
340
|
+
var PermissionConfigSchema = z2.union([
|
|
341
|
+
PermissionActionSchema,
|
|
342
|
+
PermissionObjectSchema
|
|
343
|
+
]);
|
|
281
344
|
var AgentOverrideConfigSchema = z2.object({
|
|
282
345
|
model: z2.union([
|
|
283
346
|
z2.string(),
|
|
@@ -296,13 +359,15 @@ var AgentOverrideConfigSchema = z2.object({
|
|
|
296
359
|
prompt: z2.string().min(1).optional(),
|
|
297
360
|
orchestratorPrompt: z2.string().min(1).optional(),
|
|
298
361
|
options: z2.record(z2.string(), z2.unknown()).optional(),
|
|
299
|
-
displayName: z2.string().min(1).optional()
|
|
362
|
+
displayName: z2.string().min(1).optional(),
|
|
363
|
+
permission: PermissionConfigSchema.optional()
|
|
300
364
|
}).strict();
|
|
301
365
|
var MultiplexerTypeSchema = z2.enum([
|
|
302
366
|
"auto",
|
|
303
367
|
"tmux",
|
|
304
368
|
"zellij",
|
|
305
369
|
"herdr",
|
|
370
|
+
"cmux",
|
|
306
371
|
"none"
|
|
307
372
|
]);
|
|
308
373
|
var MultiplexerLayoutSchema = z2.enum([
|
|
@@ -346,7 +411,9 @@ var FailoverConfigSchema = z2.object({
|
|
|
346
411
|
enabled: z2.boolean().default(true),
|
|
347
412
|
timeoutMs: z2.number().min(0).default(15000),
|
|
348
413
|
retryDelayMs: z2.number().min(0).default(500),
|
|
349
|
-
|
|
414
|
+
maxRetries: z2.number().int().min(0).default(3).describe("Number of consecutive 429/rate-limit responses tolerated on the " + "same model before aborting (or swapping to the next fallback " + "model when a chain is configured)."),
|
|
415
|
+
retry_on_empty: z2.boolean().default(true).describe("When true (default), empty provider responses are treated as failures, " + "triggering fallback/retry. Set to false to treat them as successes."),
|
|
416
|
+
runtimeOverride: z2.boolean().optional().describe("DEPRECATED: no longer used. Previously controlled whether out-of-chain " + "runtime model picks triggered fallback. Fallback is now always " + "disabled when a user explicitly selects a model via /model.")
|
|
350
417
|
}).strict();
|
|
351
418
|
var CompanionConfigSchema = z2.object({
|
|
352
419
|
enabled: z2.boolean().optional(),
|
|
@@ -387,12 +454,14 @@ function rejectOrchestratorPromptOnOrchestrator(overrides, ctx, pathPrefix) {
|
|
|
387
454
|
var PluginConfigSchema = z2.object({
|
|
388
455
|
preset: z2.string().optional(),
|
|
389
456
|
setDefaultAgent: z2.boolean().optional(),
|
|
390
|
-
compactSidebar: z2.boolean().optional().describe("Use the compact TUI sidebar layout
|
|
457
|
+
compactSidebar: z2.boolean().optional().describe("Use the compact TUI sidebar layout. Defaults to true; set false to use the expanded layout."),
|
|
458
|
+
stripOrchestratorModel: z2.boolean().optional().describe("When true, omit orchestrator.model and orchestrator.variant from the SDK config so OpenCode uses the session model selected with /model after subagent dispatch. An explicitly selected preset that sets orchestrator.model is preserved. Defaults to false."),
|
|
391
459
|
autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
|
|
392
460
|
presets: z2.record(z2.string(), PresetSchema).optional(),
|
|
393
461
|
agents: z2.record(z2.string(), AgentOverrideConfigSchema).optional(),
|
|
394
462
|
disabled_agents: z2.array(z2.string()).optional().describe("Agent names to disable completely. " + "Disabled agents are not instantiated and cannot be delegated to. " + "Orchestrator and council internal agents (councillor) cannot be disabled. " + "By default, 'observer' is disabled. Remove it from this list and configure a vision-capable model to enable."),
|
|
395
|
-
|
|
463
|
+
image_routing: z2.enum(["auto", "direct"]).optional().describe("How image attachments are handled. " + "When omitted, preserves legacy conditional behavior: intercept " + 'attachments only when observer is enabled. "auto": requires ' + "observer to be enabled and saves attachments to disk before " + 'nudging delegation to @observer. "direct": always passes ' + "attachments to the orchestrator untouched."),
|
|
464
|
+
disabled_mcps: z2.array(z2.string()).optional().describe("MCP server names to disable completely. Disabled servers are not " + "started and cannot be used by agents."),
|
|
396
465
|
disabled_tools: z2.array(z2.string()).optional().describe("Tool names to disable completely. Disabled tools are not registered with OpenCode and cannot be used by agents."),
|
|
397
466
|
disabled_skills: z2.array(z2.string()).optional().describe("Skill names to disable completely. Disabled skills are not granted to agents, even when referenced by presets or agent overrides."),
|
|
398
467
|
multiplexer: MultiplexerConfigSchema.optional(),
|
|
@@ -495,6 +564,12 @@ var CUSTOM_SKILLS = [
|
|
|
495
564
|
allowedAgents: ["orchestrator"],
|
|
496
565
|
sourcePath: "src/skills/deepwork"
|
|
497
566
|
},
|
|
567
|
+
{
|
|
568
|
+
name: "verification-planning",
|
|
569
|
+
description: "Plan credible, proportionate evidence before non-trivial implementation",
|
|
570
|
+
allowedAgents: ["orchestrator"],
|
|
571
|
+
sourcePath: "src/skills/verification-planning"
|
|
572
|
+
},
|
|
498
573
|
{
|
|
499
574
|
name: "reflect",
|
|
500
575
|
description: "Review repeated work and suggest reusable workflow improvements",
|
|
@@ -599,6 +674,23 @@ function findConfigPathInDirs(configDirs, baseName) {
|
|
|
599
674
|
}
|
|
600
675
|
return null;
|
|
601
676
|
}
|
|
677
|
+
function validateFinalImageRouting(config, configPath, options) {
|
|
678
|
+
if (config.image_routing !== "auto")
|
|
679
|
+
return true;
|
|
680
|
+
const disabledAgents = config.disabled_agents ?? DEFAULT_DISABLED_AGENTS;
|
|
681
|
+
if (!disabledAgents.includes("observer"))
|
|
682
|
+
return true;
|
|
683
|
+
const message = 'image_routing "auto" requires observer to be enabled. ' + 'Remove "observer" from disabled_agents.';
|
|
684
|
+
options?.onWarning?.({
|
|
685
|
+
path: configPath,
|
|
686
|
+
kind: "invalid-schema",
|
|
687
|
+
message
|
|
688
|
+
});
|
|
689
|
+
if (!options?.silent) {
|
|
690
|
+
console.warn(`[oh-my-opencode-slim] Invalid config: ${message}`);
|
|
691
|
+
}
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
602
694
|
function findPluginConfigPaths(directory) {
|
|
603
695
|
const userConfigPath = findConfigPathInDirs(getConfigSearchDirs(), "oh-my-opencode-slim");
|
|
604
696
|
const projectConfigBasePath = path.join(directory, ".opencode", "oh-my-opencode-slim");
|
|
@@ -680,6 +772,7 @@ function loadPluginConfig(directory, options) {
|
|
|
680
772
|
debug: config.companion.debug ?? false
|
|
681
773
|
};
|
|
682
774
|
}
|
|
775
|
+
validateFinalImageRouting(config, projectConfigPath ?? userConfigPath ?? "", options);
|
|
683
776
|
return config;
|
|
684
777
|
}
|
|
685
778
|
function loadAgentPrompt(agentName, optionsOrPreset) {
|
|
@@ -745,6 +838,7 @@ function migrateTmuxToMultiplexer(config) {
|
|
|
745
838
|
}
|
|
746
839
|
|
|
747
840
|
// src/tui-state.ts
|
|
841
|
+
import { createHash } from "node:crypto";
|
|
748
842
|
import * as fs2 from "node:fs";
|
|
749
843
|
import * as os from "node:os";
|
|
750
844
|
import * as path2 from "node:path";
|
|
@@ -753,8 +847,11 @@ var STATE_FILE = "tui-state.json";
|
|
|
753
847
|
function dataDir() {
|
|
754
848
|
return process.env.XDG_DATA_HOME ?? path2.join(os.homedir(), ".local", "share");
|
|
755
849
|
}
|
|
756
|
-
function
|
|
757
|
-
return path2.
|
|
850
|
+
function projectScope(projectDir) {
|
|
851
|
+
return createHash("sha256").update(path2.resolve(projectDir)).digest("hex").slice(0, 12);
|
|
852
|
+
}
|
|
853
|
+
function getTuiStatePath(projectDir) {
|
|
854
|
+
return path2.join(dataDir(), "opencode", "storage", STATE_DIR, projectScope(projectDir), STATE_FILE);
|
|
758
855
|
}
|
|
759
856
|
function emptySnapshot() {
|
|
760
857
|
return {
|
|
@@ -775,42 +872,42 @@ function parseSnapshot(value) {
|
|
|
775
872
|
agentVariants: parsed.agentVariants ?? {}
|
|
776
873
|
};
|
|
777
874
|
}
|
|
778
|
-
function readTuiSnapshot() {
|
|
875
|
+
function readTuiSnapshot(projectDir) {
|
|
779
876
|
try {
|
|
780
|
-
return parseSnapshot(fs2.readFileSync(getTuiStatePath(), "utf8"));
|
|
877
|
+
return parseSnapshot(fs2.readFileSync(getTuiStatePath(projectDir), "utf8"));
|
|
781
878
|
} catch {
|
|
782
879
|
return emptySnapshot();
|
|
783
880
|
}
|
|
784
881
|
}
|
|
785
|
-
async function readTuiSnapshotAsync() {
|
|
882
|
+
async function readTuiSnapshotAsync(projectDir) {
|
|
786
883
|
try {
|
|
787
|
-
return parseSnapshot(await fs2.promises.readFile(getTuiStatePath(), "utf8"));
|
|
884
|
+
return parseSnapshot(await fs2.promises.readFile(getTuiStatePath(projectDir), "utf8"));
|
|
788
885
|
} catch {
|
|
789
886
|
return emptySnapshot();
|
|
790
887
|
}
|
|
791
888
|
}
|
|
792
|
-
function writeTuiSnapshot(snapshot) {
|
|
889
|
+
function writeTuiSnapshot(snapshot, projectDir) {
|
|
793
890
|
try {
|
|
794
|
-
const filePath = getTuiStatePath();
|
|
891
|
+
const filePath = getTuiStatePath(projectDir);
|
|
795
892
|
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
796
893
|
fs2.writeFileSync(filePath, `${JSON.stringify(snapshot)}
|
|
797
894
|
`);
|
|
798
895
|
} catch {}
|
|
799
896
|
}
|
|
800
|
-
function updateSnapshot(mutator) {
|
|
801
|
-
const snapshot = readTuiSnapshot();
|
|
897
|
+
function updateSnapshot(projectDir, mutator) {
|
|
898
|
+
const snapshot = readTuiSnapshot(projectDir);
|
|
802
899
|
mutator(snapshot);
|
|
803
900
|
snapshot.updatedAt = Date.now();
|
|
804
|
-
writeTuiSnapshot(snapshot);
|
|
901
|
+
writeTuiSnapshot(snapshot, projectDir);
|
|
805
902
|
}
|
|
806
|
-
function recordTuiAgentModels(input) {
|
|
807
|
-
updateSnapshot((snapshot) => {
|
|
903
|
+
function recordTuiAgentModels(input, projectDir) {
|
|
904
|
+
updateSnapshot(projectDir, (snapshot) => {
|
|
808
905
|
snapshot.agentModels = { ...input.agentModels };
|
|
809
906
|
snapshot.agentVariants = { ...input.agentVariants ?? {} };
|
|
810
907
|
});
|
|
811
908
|
}
|
|
812
|
-
function recordTuiAgentModel(input) {
|
|
813
|
-
updateSnapshot((snapshot) => {
|
|
909
|
+
function recordTuiAgentModel(input, projectDir) {
|
|
910
|
+
updateSnapshot(projectDir, (snapshot) => {
|
|
814
911
|
snapshot.agentModels[input.agentName] = input.model;
|
|
815
912
|
if (input.variant !== undefined) {
|
|
816
913
|
if (input.variant === null) {
|
|
@@ -902,15 +999,15 @@ function agentRow(label, model, variant, theme) {
|
|
|
902
999
|
...detailRows
|
|
903
1000
|
]);
|
|
904
1001
|
}
|
|
905
|
-
function compactAgentRow(label, model,
|
|
906
|
-
const
|
|
1002
|
+
function compactAgentRow(label, model, _variant, theme) {
|
|
1003
|
+
const modelName = splitSidebarModelId(model).model;
|
|
907
1004
|
return box({
|
|
908
1005
|
width: "100%",
|
|
909
1006
|
flexDirection: "row",
|
|
910
1007
|
justifyContent: "space-between"
|
|
911
1008
|
}, [
|
|
912
1009
|
text({ fg: theme.textMuted, width: 14 }, [label]),
|
|
913
|
-
text({ fg: theme.textMuted }, [
|
|
1010
|
+
text({ fg: theme.textMuted }, [modelName])
|
|
914
1011
|
]);
|
|
915
1012
|
}
|
|
916
1013
|
function renderSidebar(snapshot, version, theme, configInvalid, compactSidebar) {
|
|
@@ -931,7 +1028,7 @@ function renderSidebar(snapshot, version, theme, configInvalid, compactSidebar)
|
|
|
931
1028
|
justifyContent: "space-between",
|
|
932
1029
|
alignItems: "center"
|
|
933
1030
|
}, [
|
|
934
|
-
box({ paddingLeft: 1, paddingRight: 1, backgroundColor: theme.accent }, [text({ fg: theme.
|
|
1031
|
+
box({ paddingLeft: 1, paddingRight: 1, backgroundColor: theme.accent }, [text({ fg: theme.text }, ["OMO-Slim"])]),
|
|
935
1032
|
text({ fg: theme.textMuted }, [`v${version}`])
|
|
936
1033
|
]),
|
|
937
1034
|
configStatusRow,
|
|
@@ -969,12 +1066,15 @@ function readConfigState(directory) {
|
|
|
969
1066
|
configInvalid = true;
|
|
970
1067
|
}
|
|
971
1068
|
});
|
|
972
|
-
const compactSidebar = config.compactSidebar ??
|
|
1069
|
+
const compactSidebar = config.compactSidebar ?? true;
|
|
973
1070
|
return { configInvalid, compactSidebar };
|
|
974
1071
|
}
|
|
975
1072
|
function readConfigInvalid(directory) {
|
|
976
1073
|
return readConfigState(directory).configInvalid;
|
|
977
1074
|
}
|
|
1075
|
+
function readCompactSidebar(directory) {
|
|
1076
|
+
return readConfigState(directory).compactSidebar;
|
|
1077
|
+
}
|
|
978
1078
|
var plugin = {
|
|
979
1079
|
id: `${PLUGIN_NAME}:tui`,
|
|
980
1080
|
tui: async (api, _options, meta) => {
|
|
@@ -983,11 +1083,11 @@ var plugin = {
|
|
|
983
1083
|
const version = meta.version ?? await readPackageVersion() ?? "dev";
|
|
984
1084
|
let configDirectory = getTuiDirectory(api);
|
|
985
1085
|
let { configInvalid, compactSidebar } = readConfigState(configDirectory);
|
|
986
|
-
let snapshot = readTuiSnapshot();
|
|
1086
|
+
let snapshot = readTuiSnapshot(configDirectory);
|
|
987
1087
|
const renderTimer = setInterval(async () => {
|
|
988
1088
|
try {
|
|
989
|
-
snapshot = await readTuiSnapshotAsync();
|
|
990
1089
|
const currentDirectory = getTuiDirectory(api);
|
|
1090
|
+
snapshot = await readTuiSnapshotAsync(currentDirectory);
|
|
991
1091
|
if (currentDirectory !== configDirectory) {
|
|
992
1092
|
configDirectory = currentDirectory;
|
|
993
1093
|
({ configInvalid, compactSidebar } = readConfigState(configDirectory));
|
|
@@ -1012,6 +1112,7 @@ var tui_default = plugin;
|
|
|
1012
1112
|
export {
|
|
1013
1113
|
splitSidebarModelId,
|
|
1014
1114
|
readConfigInvalid,
|
|
1115
|
+
readCompactSidebar,
|
|
1015
1116
|
getSidebarAgentNames,
|
|
1016
1117
|
tui_default as default
|
|
1017
1118
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BackgroundJobStore } from './background-job-store';
|
|
1
2
|
import { type TaskOutputState } from './task';
|
|
2
3
|
export interface ContextFile {
|
|
3
4
|
path: string;
|
|
@@ -56,7 +57,7 @@ export interface BackgroundJobStatusInput {
|
|
|
56
57
|
now?: number;
|
|
57
58
|
}
|
|
58
59
|
type TerminalStateListener = (taskID: string) => void;
|
|
59
|
-
export declare class BackgroundJobBoard {
|
|
60
|
+
export declare class BackgroundJobBoard implements BackgroundJobStore {
|
|
60
61
|
private readonly jobs;
|
|
61
62
|
private readonly counters;
|
|
62
63
|
private terminalStateListeners;
|
|
@@ -97,6 +98,9 @@ export declare class BackgroundJobBoard {
|
|
|
97
98
|
formatForPrompt(parentSessionID: string, now?: number): string | undefined;
|
|
98
99
|
clearParent(parentSessionID: string): void;
|
|
99
100
|
drop(taskID: string): void;
|
|
101
|
+
deferIfRunning(_sessionId: string): boolean;
|
|
102
|
+
retryDeferredClose(_sessionId: string): boolean;
|
|
103
|
+
clearDeferredClose(_sessionId: string): void;
|
|
100
104
|
private trimReusable;
|
|
101
105
|
private formatReusableJob;
|
|
102
106
|
private nextAlias;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { BackgroundJobBoard, BackgroundJobLaunchInput, BackgroundJobRecord, BackgroundJobStatusInput, ContextFile } from './background-job-board';
|
|
2
|
+
import type { BackgroundJobStore } from './background-job-store';
|
|
3
|
+
import type { TaskOutputState } from './task';
|
|
4
|
+
type TerminalStateListener = (taskID: string) => void;
|
|
5
|
+
/**
|
|
6
|
+
* BackgroundJobCoordinator owns the lifecycle policy for background jobs.
|
|
7
|
+
* It sits between the board and its consumers, providing:
|
|
8
|
+
* - Subscription interface for terminal state notifications (replaces fire-and-forget)
|
|
9
|
+
* - Lifecycle policy: determines when jobs are terminal, when closes should be deferred
|
|
10
|
+
* - Single-writer contract: coordinator is the sole writer to the board
|
|
11
|
+
*
|
|
12
|
+
* The board's guards prevent silent overwrites. The coordinator adds:
|
|
13
|
+
* - Centralized notification with guaranteed delivery
|
|
14
|
+
* - Re-checks board state before notifying (handles races)
|
|
15
|
+
*/
|
|
16
|
+
export declare class BackgroundJobCoordinator implements BackgroundJobStore {
|
|
17
|
+
private readonly board;
|
|
18
|
+
private terminalStateListeners;
|
|
19
|
+
private readonly deferredIdleCloses;
|
|
20
|
+
constructor(board: BackgroundJobBoard);
|
|
21
|
+
addTerminalStateListener(listener: TerminalStateListener): void;
|
|
22
|
+
removeTerminalStateListener(listener: TerminalStateListener): void;
|
|
23
|
+
/**
|
|
24
|
+
* Handle terminal state from board. Re-checks board state to handle races.
|
|
25
|
+
* This is the centralized lifecycle policy.
|
|
26
|
+
*/
|
|
27
|
+
private handleTerminalState;
|
|
28
|
+
/**
|
|
29
|
+
* Evaluate close policy. Returns true if session should close now.
|
|
30
|
+
* Mutates deferred state: adds to deferred set if running, removes if not.
|
|
31
|
+
*/
|
|
32
|
+
deferIfRunning(sessionId: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Retry closing a deferred session. Called when a background job completes.
|
|
35
|
+
* Returns true if the session should now close.
|
|
36
|
+
*/
|
|
37
|
+
retryDeferredClose(sessionId: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Clear deferred close state for a session being deleted.
|
|
40
|
+
*/
|
|
41
|
+
clearDeferredClose(sessionId: string): void;
|
|
42
|
+
registerLaunch(input: BackgroundJobLaunchInput): BackgroundJobRecord;
|
|
43
|
+
updateStatus(input: BackgroundJobStatusInput): BackgroundJobRecord | undefined;
|
|
44
|
+
updateFromStatusOutput(output: string): BackgroundJobRecord | undefined;
|
|
45
|
+
markRunningFromLiveSession(taskID: string, now?: number): BackgroundJobRecord | undefined;
|
|
46
|
+
markReconciled(taskID: string, now?: number): BackgroundJobRecord | undefined;
|
|
47
|
+
markCancelled(taskID: string, reason?: string, now?: number, options?: {
|
|
48
|
+
force?: boolean;
|
|
49
|
+
}): BackgroundJobRecord | undefined;
|
|
50
|
+
get(taskID: string): BackgroundJobRecord | undefined;
|
|
51
|
+
field<K extends keyof BackgroundJobRecord>(taskID: string, key: K): BackgroundJobRecord[K] | undefined;
|
|
52
|
+
isRunning(taskID: string): boolean;
|
|
53
|
+
isTerminalUnreconciled(taskID: string): boolean;
|
|
54
|
+
getResultSummary(taskID: string): string | undefined;
|
|
55
|
+
getLastLiveBusyAt(taskID: string): number | undefined;
|
|
56
|
+
getParentSessionID(taskID: string): string | undefined;
|
|
57
|
+
getState(taskID: string): TaskOutputState | 'reconciled' | undefined;
|
|
58
|
+
resolve(parentSessionID: string, taskIDOrAlias: string): BackgroundJobRecord | undefined;
|
|
59
|
+
resolveReusable(parentSessionID: string, taskIDOrAlias: string, agent?: string): BackgroundJobRecord | undefined;
|
|
60
|
+
resolveRecoverable(parentSessionID: string, taskIDOrAlias: string, agent?: string): BackgroundJobRecord | undefined;
|
|
61
|
+
markUsed(parentSessionID: string, key: string, now?: number): void;
|
|
62
|
+
taskIDs(): Set<string>;
|
|
63
|
+
addContext(taskID: string, files: ContextFile[]): void;
|
|
64
|
+
list(parentSessionID?: string): BackgroundJobRecord[];
|
|
65
|
+
hasRunning(parentSessionID: string): boolean;
|
|
66
|
+
hasTerminalUnreconciled(parentSessionID: string): boolean;
|
|
67
|
+
hasConvergenceSignals(taskID: string, threshold?: number): boolean;
|
|
68
|
+
formatForPrompt(parentSessionID: string, now?: number): string | undefined;
|
|
69
|
+
clearParent(parentSessionID: string): void;
|
|
70
|
+
drop(taskID: string): void;
|
|
71
|
+
}
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { BackgroundJobLaunchInput, BackgroundJobRecord, BackgroundJobStatusInput, ContextFile } from './background-job-board';
|
|
2
|
+
import type { TaskOutputState } from './task';
|
|
3
|
+
/**
|
|
4
|
+
* Unified interface for background job operations.
|
|
5
|
+
* Both BackgroundJobBoard and BackgroundJobCoordinator satisfy this.
|
|
6
|
+
*
|
|
7
|
+
* ponytail: single interface, both board and coordinator implement it.
|
|
8
|
+
*/
|
|
9
|
+
export interface BackgroundJobStore {
|
|
10
|
+
registerLaunch(input: BackgroundJobLaunchInput): BackgroundJobRecord;
|
|
11
|
+
updateStatus(input: BackgroundJobStatusInput): BackgroundJobRecord | undefined;
|
|
12
|
+
updateFromStatusOutput(output: string): BackgroundJobRecord | undefined;
|
|
13
|
+
markRunningFromLiveSession(taskID: string, now?: number): BackgroundJobRecord | undefined;
|
|
14
|
+
markReconciled(taskID: string, now?: number): BackgroundJobRecord | undefined;
|
|
15
|
+
markCancelled(taskID: string, reason?: string, now?: number, options?: {
|
|
16
|
+
force?: boolean;
|
|
17
|
+
}): BackgroundJobRecord | undefined;
|
|
18
|
+
clearParent(parentSessionID: string): void;
|
|
19
|
+
drop(taskID: string): void;
|
|
20
|
+
addContext(taskID: string, files: ContextFile[]): void;
|
|
21
|
+
markUsed(parentSessionID: string, key: string, now?: number): void;
|
|
22
|
+
get(taskID: string): BackgroundJobRecord | undefined;
|
|
23
|
+
field<K extends keyof BackgroundJobRecord>(taskID: string, key: K): BackgroundJobRecord[K] | undefined;
|
|
24
|
+
isRunning(taskID: string): boolean;
|
|
25
|
+
isTerminalUnreconciled(taskID: string): boolean;
|
|
26
|
+
getResultSummary(taskID: string): string | undefined;
|
|
27
|
+
getLastLiveBusyAt(taskID: string): number | undefined;
|
|
28
|
+
getParentSessionID(taskID: string): string | undefined;
|
|
29
|
+
getState(taskID: string): TaskOutputState | 'reconciled' | undefined;
|
|
30
|
+
resolve(parentSessionID: string, taskIDOrAlias: string): BackgroundJobRecord | undefined;
|
|
31
|
+
resolveReusable(parentSessionID: string, taskIDOrAlias: string, agent?: string): BackgroundJobRecord | undefined;
|
|
32
|
+
resolveRecoverable(parentSessionID: string, taskIDOrAlias: string, agent?: string): BackgroundJobRecord | undefined;
|
|
33
|
+
taskIDs(): Set<string>;
|
|
34
|
+
list(parentSessionID?: string): BackgroundJobRecord[];
|
|
35
|
+
hasRunning(parentSessionID: string): boolean;
|
|
36
|
+
hasTerminalUnreconciled(parentSessionID: string): boolean;
|
|
37
|
+
hasConvergenceSignals(taskID: string, threshold?: number): boolean;
|
|
38
|
+
formatForPrompt(parentSessionID: string, now?: number): string | undefined;
|
|
39
|
+
/** Evaluate close policy. Returns true if session should close now.
|
|
40
|
+
* Mutates deferred state: adds to deferred set if running, removes if not. */
|
|
41
|
+
deferIfRunning(sessionId: string): boolean;
|
|
42
|
+
/** Retry closing a deferred session. Returns true if session should now close. */
|
|
43
|
+
retryDeferredClose(sessionId: string): boolean;
|
|
44
|
+
/** Clear deferred close state for a session being deleted. */
|
|
45
|
+
clearDeferredClose(sessionId: string): void;
|
|
46
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for councillor model fallback chains.
|
|
3
|
+
*
|
|
4
|
+
* Kept free of any schema/validation library import so that runtime consumers
|
|
5
|
+
* (e.g. `CouncilManager`) can resolve a councillor's ordered model chain
|
|
6
|
+
* without pulling in zod or the config schema module.
|
|
7
|
+
*/
|
|
8
|
+
/** A single model in a councillor fallback chain, with optional variant. */
|
|
9
|
+
export type CouncillorModelEntry = {
|
|
10
|
+
id: string;
|
|
11
|
+
variant?: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Flatten a councillor model config into an ordered list of model entries.
|
|
15
|
+
*
|
|
16
|
+
* Accepts either a single "provider/model" string or an ordered fallback
|
|
17
|
+
* chain (array of strings and/or `{ id, variant }` entries). Entries that
|
|
18
|
+
* don't carry their own variant fall back to the shared `fallbackVariant`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function normalizeCouncillorModels(model: string | Array<string | CouncillorModelEntry>, fallbackVariant?: string): CouncillorModelEntry[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function escapeHtml(value: string): string;
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export * from './agent-variant';
|
|
2
2
|
export * from './background-job-board';
|
|
3
|
+
export * from './background-job-coordinator';
|
|
4
|
+
export * from './background-job-store';
|
|
3
5
|
export * from './internal-initiator';
|
|
4
|
-
export {
|
|
6
|
+
export { initLogger, log } from './logger';
|
|
5
7
|
export * from './polling';
|
|
6
8
|
export * from './session';
|
|
7
9
|
export * from './task';
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export declare const SLIM_INTERNAL_INITIATOR_MARKER = "<!-- SLIM_INTERNAL_INITIATOR -->";
|
|
2
|
+
export declare const INTERNAL_INITIATOR_METADATA_KEY = "oh-my-opencode-slim.internalInitiator";
|
|
2
3
|
export declare function createInternalAgentTextPart(text: string): {
|
|
3
4
|
type: 'text';
|
|
4
5
|
text: string;
|
|
6
|
+
synthetic: true;
|
|
7
|
+
metadata: {
|
|
8
|
+
'oh-my-opencode-slim.internalInitiator': true;
|
|
9
|
+
};
|
|
5
10
|
};
|
|
6
|
-
export declare function
|
|
11
|
+
export declare function isInternalInitiatorPart(part: unknown): boolean;
|
package/dist/utils/logger.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
declare function getLogDir(): string;
|
|
2
1
|
export declare function initLogger(sessionId: string): void;
|
|
3
|
-
export { getLogDir };
|
|
4
2
|
/** @internal Reset logger state for testing */
|
|
5
3
|
export declare function resetLogger(): void;
|
|
6
4
|
/** @internal Wait for queued log writes in tests. */
|
package/dist/utils/session.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare function withTimeout<T>(operation: Promise<T>, timeoutMs: number,
|
|
|
11
11
|
export declare function abortSessionWithTimeout(client: OpencodeClient, sessionId: string, timeoutMs?: number): Promise<void>;
|
|
12
12
|
/**
|
|
13
13
|
* Extract the short model label from a "provider/model" string.
|
|
14
|
-
* E.g. "openai/gpt-5.
|
|
14
|
+
* E.g. "openai/gpt-5.6-luna" → "gpt-5.6-luna"
|
|
15
15
|
*/
|
|
16
16
|
export declare function shortModelLabel(model: string): string;
|
|
17
17
|
export type PromptBody = {
|