oh-my-opencode-slim 2.0.4 → 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 +61 -21
- package/README.ko-KR.md +59 -19
- package/README.md +73 -20
- package/README.zh-CN.md +62 -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/agents/permissions.d.ts +10 -0
- package/dist/cli/custom-skills-registry.d.ts +18 -0
- package/dist/cli/custom-skills.d.ts +3 -19
- package/dist/cli/index.js +1093 -186
- package/dist/cli/providers.d.ts +5 -9
- package/dist/cli/skills.d.ts +3 -3
- package/dist/companion/manager.d.ts +7 -0
- package/dist/config/constants.d.ts +3 -2
- package/dist/config/loader.d.ts +5 -2
- package/dist/config/schema.d.ts +7 -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 +3491 -876
- package/dist/interview/dashboard-manager.d.ts +21 -0
- package/dist/interview/dashboard.d.ts +5 -0
- package/dist/interview/document.d.ts +4 -1
- package/dist/interview/manager.d.ts +0 -14
- package/dist/interview/server.d.ts +2 -0
- package/dist/interview/service.d.ts +9 -0
- package/dist/interview/session-server.d.ts +21 -0
- package/dist/interview/types.d.ts +16 -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 +3 -0
- package/dist/multiplexer/types.d.ts +4 -4
- package/dist/tools/acp-run.d.ts +1 -1
- package/dist/tui-state.d.ts +3 -0
- package/dist/tui.d.ts +4 -1
- package/dist/tui.js +142 -55
- package/dist/utils/background-job-board.d.ts +20 -0
- package/dist/utils/env.d.ts +3 -0
- package/dist/utils/session.d.ts +1 -1
- package/oh-my-opencode-slim.schema.json +24 -4
- 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
package/dist/tui.js
CHANGED
|
@@ -118,14 +118,14 @@ var ALL_AGENT_NAMES = ["orchestrator", ...SUBAGENT_NAMES];
|
|
|
118
118
|
var PROTECTED_AGENTS = new Set(["orchestrator", "councillor"]);
|
|
119
119
|
var DEFAULT_MODELS = {
|
|
120
120
|
orchestrator: undefined,
|
|
121
|
-
oracle:
|
|
122
|
-
librarian:
|
|
123
|
-
explorer:
|
|
124
|
-
designer:
|
|
125
|
-
fixer:
|
|
126
|
-
observer:
|
|
127
|
-
council:
|
|
128
|
-
councillor:
|
|
121
|
+
oracle: undefined,
|
|
122
|
+
librarian: undefined,
|
|
123
|
+
explorer: undefined,
|
|
124
|
+
designer: undefined,
|
|
125
|
+
fixer: undefined,
|
|
126
|
+
observer: undefined,
|
|
127
|
+
council: undefined,
|
|
128
|
+
councillor: undefined
|
|
129
129
|
};
|
|
130
130
|
var POLL_INTERVAL_BACKGROUND_MS = 2000;
|
|
131
131
|
var MAX_POLL_TIME_MS = 5 * 60 * 1000;
|
|
@@ -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",
|
|
@@ -353,6 +359,7 @@ var CompanionConfigSchema = z2.object({
|
|
|
353
359
|
debug: z2.boolean().optional().describe("Enable verbose native companion debug logs.")
|
|
354
360
|
});
|
|
355
361
|
var AcpAgentPermissionModeSchema = z2.enum(["ask", "allow", "reject"]);
|
|
362
|
+
var MAX_ACP_TIMEOUT_MS = 2147483647;
|
|
356
363
|
var AcpAgentConfigSchema = z2.object({
|
|
357
364
|
command: z2.string().min(1),
|
|
358
365
|
args: z2.array(z2.string()).default([]),
|
|
@@ -362,28 +369,17 @@ var AcpAgentConfigSchema = z2.object({
|
|
|
362
369
|
prompt: z2.string().min(1).optional(),
|
|
363
370
|
orchestratorPrompt: z2.string().min(1).optional(),
|
|
364
371
|
wrapperModel: ProviderModelIdSchema.optional(),
|
|
365
|
-
timeoutMs: z2.number().int().min(
|
|
372
|
+
timeoutMs: z2.number().int().min(0).max(MAX_ACP_TIMEOUT_MS).default(0).describe("Timeout for a single ACP run in milliseconds. Set to 0 to disable the timeout."),
|
|
366
373
|
permissionMode: AcpAgentPermissionModeSchema.default("ask")
|
|
367
374
|
}).strict();
|
|
368
375
|
var AcpAgentsConfigSchema = z2.record(z2.string(), AcpAgentConfigSchema);
|
|
369
|
-
function
|
|
376
|
+
function rejectOrchestratorPromptOnOrchestrator(overrides, ctx, pathPrefix) {
|
|
370
377
|
for (const [name, override] of Object.entries(overrides)) {
|
|
371
|
-
|
|
372
|
-
if (!isBuiltInOrAlias) {
|
|
373
|
-
continue;
|
|
374
|
-
}
|
|
375
|
-
if (override.prompt !== undefined) {
|
|
376
|
-
ctx.addIssue({
|
|
377
|
-
code: z2.ZodIssueCode.custom,
|
|
378
|
-
path: [...pathPrefix, name, "prompt"],
|
|
379
|
-
message: "prompt is only supported for custom agents"
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
if (override.orchestratorPrompt !== undefined) {
|
|
378
|
+
if (name === "orchestrator" && override.orchestratorPrompt !== undefined) {
|
|
383
379
|
ctx.addIssue({
|
|
384
380
|
code: z2.ZodIssueCode.custom,
|
|
385
381
|
path: [...pathPrefix, name, "orchestratorPrompt"],
|
|
386
|
-
message: "orchestratorPrompt is
|
|
382
|
+
message: "orchestratorPrompt is not supported for the orchestrator agent"
|
|
387
383
|
});
|
|
388
384
|
}
|
|
389
385
|
}
|
|
@@ -391,11 +387,14 @@ function validateCustomOnlyPromptFields(overrides, ctx, pathPrefix) {
|
|
|
391
387
|
var PluginConfigSchema = z2.object({
|
|
392
388
|
preset: z2.string().optional(),
|
|
393
389
|
setDefaultAgent: z2.boolean().optional(),
|
|
390
|
+
compactSidebar: z2.boolean().optional().describe("Use the compact TUI sidebar layout when enabled."),
|
|
394
391
|
autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
|
|
395
392
|
presets: z2.record(z2.string(), PresetSchema).optional(),
|
|
396
393
|
agents: z2.record(z2.string(), AgentOverrideConfigSchema).optional(),
|
|
397
394
|
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."),
|
|
398
395
|
disabled_mcps: z2.array(z2.string()).optional(),
|
|
396
|
+
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
|
+
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."),
|
|
399
398
|
multiplexer: MultiplexerConfigSchema.optional(),
|
|
400
399
|
tmux: TmuxConfigSchema.optional(),
|
|
401
400
|
websearch: WebsearchConfigSchema.optional(),
|
|
@@ -407,11 +406,14 @@ var PluginConfigSchema = z2.object({
|
|
|
407
406
|
acpAgents: AcpAgentsConfigSchema.optional()
|
|
408
407
|
}).superRefine((value, ctx) => {
|
|
409
408
|
if (value.agents) {
|
|
410
|
-
|
|
409
|
+
rejectOrchestratorPromptOnOrchestrator(value.agents, ctx, ["agents"]);
|
|
411
410
|
}
|
|
412
411
|
if (value.presets) {
|
|
413
412
|
for (const [presetName, preset] of Object.entries(value.presets)) {
|
|
414
|
-
|
|
413
|
+
rejectOrchestratorPromptOnOrchestrator(preset, ctx, [
|
|
414
|
+
"presets",
|
|
415
|
+
presetName
|
|
416
|
+
]);
|
|
415
417
|
}
|
|
416
418
|
}
|
|
417
419
|
});
|
|
@@ -467,7 +469,7 @@ function getAgentMcpList(agentName, config) {
|
|
|
467
469
|
return defaultMcps ?? [];
|
|
468
470
|
}
|
|
469
471
|
|
|
470
|
-
// src/cli/custom-skills.ts
|
|
472
|
+
// src/cli/custom-skills-registry.ts
|
|
471
473
|
var CUSTOM_SKILLS = [
|
|
472
474
|
{
|
|
473
475
|
name: "simplify",
|
|
@@ -505,6 +507,12 @@ var CUSTOM_SKILLS = [
|
|
|
505
507
|
allowedAgents: ["orchestrator"],
|
|
506
508
|
sourcePath: "src/skills/oh-my-opencode-slim"
|
|
507
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
|
+
},
|
|
508
516
|
{
|
|
509
517
|
name: "worktrees",
|
|
510
518
|
description: "Manage Git worktrees as OMO safe isolated coding lanes for complex/risky/parallel work",
|
|
@@ -602,6 +610,7 @@ function mergePluginConfigs(base, override) {
|
|
|
602
610
|
...base,
|
|
603
611
|
...override,
|
|
604
612
|
agents: deepMerge(base.agents, override.agents),
|
|
613
|
+
presets: deepMerge(base.presets, override.presets),
|
|
605
614
|
tmux: deepMerge(base.tmux, override.tmux),
|
|
606
615
|
multiplexer: deepMerge(base.multiplexer, override.multiplexer),
|
|
607
616
|
interview: deepMerge(base.interview, override.interview),
|
|
@@ -673,15 +682,33 @@ function loadPluginConfig(directory, options) {
|
|
|
673
682
|
}
|
|
674
683
|
return config;
|
|
675
684
|
}
|
|
676
|
-
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
|
+
}
|
|
677
694
|
const presetDirName = preset && /^[a-zA-Z0-9_-]+$/.test(preset) ? preset : undefined;
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
}
|
|
682
|
-
|
|
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
|
+
}
|
|
683
710
|
const readFirstPrompt = (fileName, errorPrefix) => {
|
|
684
|
-
for (const dir of
|
|
711
|
+
for (const dir of searchDirs) {
|
|
685
712
|
const promptPath = path.join(dir, fileName);
|
|
686
713
|
if (!fs.existsSync(promptPath)) {
|
|
687
714
|
continue;
|
|
@@ -694,6 +721,7 @@ function loadAgentPrompt(agentName, preset) {
|
|
|
694
721
|
}
|
|
695
722
|
return;
|
|
696
723
|
};
|
|
724
|
+
const result = {};
|
|
697
725
|
result.prompt = readFirstPrompt(`${agentName}.md`, "Error reading prompt file");
|
|
698
726
|
result.appendPrompt = readFirstPrompt(`${agentName}_append.md`, "Error reading append prompt file");
|
|
699
727
|
return result;
|
|
@@ -732,7 +760,8 @@ function emptySnapshot() {
|
|
|
732
760
|
return {
|
|
733
761
|
version: 1,
|
|
734
762
|
updatedAt: Date.now(),
|
|
735
|
-
agentModels: {}
|
|
763
|
+
agentModels: {},
|
|
764
|
+
agentVariants: {}
|
|
736
765
|
};
|
|
737
766
|
}
|
|
738
767
|
function parseSnapshot(value) {
|
|
@@ -742,7 +771,8 @@ function parseSnapshot(value) {
|
|
|
742
771
|
return {
|
|
743
772
|
version: 1,
|
|
744
773
|
updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : Date.now(),
|
|
745
|
-
agentModels: parsed.agentModels ?? {}
|
|
774
|
+
agentModels: parsed.agentModels ?? {},
|
|
775
|
+
agentVariants: parsed.agentVariants ?? {}
|
|
746
776
|
};
|
|
747
777
|
}
|
|
748
778
|
function readTuiSnapshot() {
|
|
@@ -776,14 +806,32 @@ function updateSnapshot(mutator) {
|
|
|
776
806
|
function recordTuiAgentModels(input) {
|
|
777
807
|
updateSnapshot((snapshot) => {
|
|
778
808
|
snapshot.agentModels = { ...input.agentModels };
|
|
809
|
+
snapshot.agentVariants = { ...input.agentVariants ?? {} };
|
|
779
810
|
});
|
|
780
811
|
}
|
|
781
812
|
function recordTuiAgentModel(input) {
|
|
782
813
|
updateSnapshot((snapshot) => {
|
|
783
814
|
snapshot.agentModels[input.agentName] = input.model;
|
|
815
|
+
if (input.variant !== undefined) {
|
|
816
|
+
if (input.variant === null) {
|
|
817
|
+
delete snapshot.agentVariants[input.agentName];
|
|
818
|
+
} else {
|
|
819
|
+
snapshot.agentVariants[input.agentName] = input.variant;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
784
822
|
});
|
|
785
823
|
}
|
|
786
824
|
|
|
825
|
+
// src/utils/env.ts
|
|
826
|
+
var TRUTHY_ENV_VALUES = new Set(["1", "true", "yes", "on"]);
|
|
827
|
+
var PLUGIN_DISABLE_ENV = "OH_MY_OPENCODE_SLIM_DISABLE";
|
|
828
|
+
function isTruthyEnvValue(value) {
|
|
829
|
+
return TRUTHY_ENV_VALUES.has(value?.trim().toLowerCase() ?? "");
|
|
830
|
+
}
|
|
831
|
+
function isPluginDisabledByEnv(env = process.env) {
|
|
832
|
+
return isTruthyEnvValue(env[PLUGIN_DISABLE_ENV]);
|
|
833
|
+
}
|
|
834
|
+
|
|
787
835
|
// src/tui.ts
|
|
788
836
|
var PLUGIN_NAME = "oh-my-opencode-slim";
|
|
789
837
|
var CONFIG_WARNING_COLOR = "orange";
|
|
@@ -816,27 +864,56 @@ function text(props, children) {
|
|
|
816
864
|
function box(props, children = []) {
|
|
817
865
|
return element("box", props, children);
|
|
818
866
|
}
|
|
819
|
-
function truncate(value, max = 24) {
|
|
820
|
-
return value.length > max ? `${value.slice(0, max - 1)}…` : value;
|
|
821
|
-
}
|
|
822
867
|
function getTuiDirectory(api) {
|
|
823
868
|
return api.state?.path?.directory ?? process.cwd();
|
|
824
869
|
}
|
|
825
|
-
function
|
|
826
|
-
const
|
|
827
|
-
|
|
870
|
+
function splitSidebarModelId(model) {
|
|
871
|
+
const slashIndex = model.indexOf("/");
|
|
872
|
+
if (slashIndex === -1) {
|
|
873
|
+
return { model };
|
|
874
|
+
}
|
|
875
|
+
return {
|
|
876
|
+
provider: model.slice(0, slashIndex),
|
|
877
|
+
model: model.slice(slashIndex + 1)
|
|
878
|
+
};
|
|
828
879
|
}
|
|
829
880
|
function getSidebarAgentNames(snapshot) {
|
|
830
881
|
const configuredAgents = Object.keys(snapshot.agentModels);
|
|
831
882
|
return configuredAgents.length > 0 ? configuredAgents : FALLBACK_SIDEBAR_AGENTS;
|
|
832
883
|
}
|
|
833
|
-
function
|
|
834
|
-
|
|
884
|
+
function agentRow(label, model, variant, theme) {
|
|
885
|
+
const modelParts = splitSidebarModelId(model);
|
|
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
|
+
}
|
|
893
|
+
if (modelParts.provider) {
|
|
894
|
+
detailRows.push(detailRow("provider", modelParts.provider));
|
|
895
|
+
}
|
|
896
|
+
detailRows.push(detailRow("model", modelParts.model));
|
|
897
|
+
if (variant) {
|
|
898
|
+
detailRows.push(detailRow("variant", variant));
|
|
899
|
+
}
|
|
900
|
+
return box({ width: "100%", flexDirection: "column", marginBottom: 1 }, [
|
|
835
901
|
text({ fg: theme.textMuted }, [label]),
|
|
836
|
-
|
|
902
|
+
...detailRows
|
|
837
903
|
]);
|
|
838
904
|
}
|
|
839
|
-
function
|
|
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]),
|
|
913
|
+
text({ fg: theme.textMuted }, [value])
|
|
914
|
+
]);
|
|
915
|
+
}
|
|
916
|
+
function renderSidebar(snapshot, version, theme, configInvalid, compactSidebar) {
|
|
840
917
|
const configStatusRow = buildConfigStatusRow(configInvalid, theme);
|
|
841
918
|
return box({
|
|
842
919
|
width: "100%",
|
|
@@ -863,7 +940,11 @@ function renderSidebar(snapshot, version, theme, configInvalid) {
|
|
|
863
940
|
]),
|
|
864
941
|
...getSidebarAgentNames(snapshot).map((agentName) => {
|
|
865
942
|
const model = snapshot.agentModels[agentName] ?? "pending";
|
|
866
|
-
|
|
943
|
+
const variant = snapshot.agentVariants[agentName];
|
|
944
|
+
if (compactSidebar) {
|
|
945
|
+
return compactAgentRow(agentName, model, variant, theme);
|
|
946
|
+
}
|
|
947
|
+
return agentRow(agentName, model, variant, theme);
|
|
867
948
|
})
|
|
868
949
|
]);
|
|
869
950
|
}
|
|
@@ -880,22 +961,28 @@ function buildConfigStatusRow(configInvalid, theme) {
|
|
|
880
961
|
text({ fg: theme.textMuted }, ["Run doctor for details"])
|
|
881
962
|
]);
|
|
882
963
|
}
|
|
883
|
-
function
|
|
964
|
+
function readConfigState(directory) {
|
|
884
965
|
let configInvalid = false;
|
|
885
|
-
loadPluginConfig(directory, {
|
|
966
|
+
const config = loadPluginConfig(directory, {
|
|
886
967
|
silent: true,
|
|
887
968
|
onWarning: () => {
|
|
888
969
|
configInvalid = true;
|
|
889
970
|
}
|
|
890
971
|
});
|
|
891
|
-
|
|
972
|
+
const compactSidebar = config.compactSidebar ?? false;
|
|
973
|
+
return { configInvalid, compactSidebar };
|
|
974
|
+
}
|
|
975
|
+
function readConfigInvalid(directory) {
|
|
976
|
+
return readConfigState(directory).configInvalid;
|
|
892
977
|
}
|
|
893
978
|
var plugin = {
|
|
894
979
|
id: `${PLUGIN_NAME}:tui`,
|
|
895
980
|
tui: async (api, _options, meta) => {
|
|
981
|
+
if (isPluginDisabledByEnv())
|
|
982
|
+
return;
|
|
896
983
|
const version = meta.version ?? await readPackageVersion() ?? "dev";
|
|
897
984
|
let configDirectory = getTuiDirectory(api);
|
|
898
|
-
let configInvalid =
|
|
985
|
+
let { configInvalid, compactSidebar } = readConfigState(configDirectory);
|
|
899
986
|
let snapshot = readTuiSnapshot();
|
|
900
987
|
const renderTimer = setInterval(async () => {
|
|
901
988
|
try {
|
|
@@ -903,7 +990,7 @@ var plugin = {
|
|
|
903
990
|
const currentDirectory = getTuiDirectory(api);
|
|
904
991
|
if (currentDirectory !== configDirectory) {
|
|
905
992
|
configDirectory = currentDirectory;
|
|
906
|
-
configInvalid =
|
|
993
|
+
({ configInvalid, compactSidebar } = readConfigState(configDirectory));
|
|
907
994
|
}
|
|
908
995
|
api.renderer.requestRender();
|
|
909
996
|
} catch {}
|
|
@@ -915,7 +1002,7 @@ var plugin = {
|
|
|
915
1002
|
order: 900,
|
|
916
1003
|
slots: {
|
|
917
1004
|
sidebar_content() {
|
|
918
|
-
return renderSidebar(snapshot, version, api.theme.current, configInvalid);
|
|
1005
|
+
return renderSidebar(snapshot, version, api.theme.current, configInvalid, compactSidebar);
|
|
919
1006
|
}
|
|
920
1007
|
}
|
|
921
1008
|
});
|
|
@@ -923,8 +1010,8 @@ var plugin = {
|
|
|
923
1010
|
};
|
|
924
1011
|
var tui_default = plugin;
|
|
925
1012
|
export {
|
|
1013
|
+
splitSidebarModelId,
|
|
926
1014
|
readConfigInvalid,
|
|
927
1015
|
getSidebarAgentNames,
|
|
928
|
-
formatSidebarModelName,
|
|
929
1016
|
tui_default as default
|
|
930
1017
|
};
|
|
@@ -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;
|
|
@@ -51,13 +55,19 @@ export interface BackgroundJobStatusInput {
|
|
|
51
55
|
lastStatusError?: string;
|
|
52
56
|
now?: number;
|
|
53
57
|
}
|
|
58
|
+
type TerminalStateListener = (taskID: string) => void;
|
|
54
59
|
export declare class BackgroundJobBoard {
|
|
55
60
|
private readonly jobs;
|
|
56
61
|
private readonly counters;
|
|
62
|
+
private terminalStateListeners;
|
|
57
63
|
private readonly maxReusablePerAgent;
|
|
58
64
|
private readonly readContextMinLines;
|
|
59
65
|
private readonly readContextMaxFiles;
|
|
60
66
|
constructor(options?: BackgroundJobBoardOptions);
|
|
67
|
+
addTerminalStateListener(listener: TerminalStateListener): void;
|
|
68
|
+
removeTerminalStateListener(listener: TerminalStateListener): void;
|
|
69
|
+
setTerminalStateListener(listener?: TerminalStateListener): void;
|
|
70
|
+
private notifyTerminalStateListeners;
|
|
61
71
|
registerLaunch(input: BackgroundJobLaunchInput): BackgroundJobRecord;
|
|
62
72
|
updateStatus(input: BackgroundJobStatusInput): BackgroundJobRecord | undefined;
|
|
63
73
|
updateFromStatusOutput(output: string): BackgroundJobRecord | undefined;
|
|
@@ -67,14 +77,23 @@ export declare class BackgroundJobBoard {
|
|
|
67
77
|
force?: boolean;
|
|
68
78
|
}): BackgroundJobRecord | undefined;
|
|
69
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;
|
|
70
87
|
resolve(parentSessionID: string, taskIDOrAlias: string): BackgroundJobRecord | undefined;
|
|
71
88
|
resolveReusable(parentSessionID: string, taskIDOrAlias: string, agent?: string): BackgroundJobRecord | undefined;
|
|
89
|
+
resolveRecoverable(parentSessionID: string, taskIDOrAlias: string, agent?: string): BackgroundJobRecord | undefined;
|
|
72
90
|
markUsed(parentSessionID: string, key: string, now?: number): void;
|
|
73
91
|
taskIDs(): Set<string>;
|
|
74
92
|
addContext(taskID: string, files: ContextFile[]): void;
|
|
75
93
|
list(parentSessionID?: string): BackgroundJobRecord[];
|
|
76
94
|
hasRunning(parentSessionID: string): boolean;
|
|
77
95
|
hasTerminalUnreconciled(parentSessionID: string): boolean;
|
|
96
|
+
hasConvergenceSignals(taskID: string, threshold?: number): boolean;
|
|
78
97
|
formatForPrompt(parentSessionID: string, now?: number): string | undefined;
|
|
79
98
|
clearParent(parentSessionID: string): void;
|
|
80
99
|
drop(taskID: string): void;
|
|
@@ -87,3 +106,4 @@ export declare function deriveTaskSessionLabel(input: {
|
|
|
87
106
|
prompt?: string;
|
|
88
107
|
agentType: string;
|
|
89
108
|
}): string;
|
|
109
|
+
export {};
|
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"
|
|
@@ -197,6 +201,20 @@
|
|
|
197
201
|
"type": "string"
|
|
198
202
|
}
|
|
199
203
|
},
|
|
204
|
+
"disabled_tools": {
|
|
205
|
+
"description": "Tool names to disable completely. Disabled tools are not registered with OpenCode and cannot be used by agents.",
|
|
206
|
+
"type": "array",
|
|
207
|
+
"items": {
|
|
208
|
+
"type": "string"
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
"disabled_skills": {
|
|
212
|
+
"description": "Skill names to disable completely. Disabled skills are not granted to agents, even when referenced by presets or agent overrides.",
|
|
213
|
+
"type": "array",
|
|
214
|
+
"items": {
|
|
215
|
+
"type": "string"
|
|
216
|
+
}
|
|
217
|
+
},
|
|
200
218
|
"multiplexer": {
|
|
201
219
|
"type": "object",
|
|
202
220
|
"properties": {
|
|
@@ -207,6 +225,7 @@
|
|
|
207
225
|
"auto",
|
|
208
226
|
"tmux",
|
|
209
227
|
"zellij",
|
|
228
|
+
"herdr",
|
|
210
229
|
"none"
|
|
211
230
|
]
|
|
212
231
|
},
|
|
@@ -403,7 +422,7 @@
|
|
|
403
422
|
"maximum": 5
|
|
404
423
|
},
|
|
405
424
|
"master": {
|
|
406
|
-
"description": "DEPRECATED
|
|
425
|
+
"description": "DEPRECATED - ignored. Council agent synthesizes directly."
|
|
407
426
|
}
|
|
408
427
|
},
|
|
409
428
|
"required": [
|
|
@@ -515,10 +534,11 @@
|
|
|
515
534
|
"pattern": "^[^/\\s]+\\/[^\\s]+$"
|
|
516
535
|
},
|
|
517
536
|
"timeoutMs": {
|
|
518
|
-
"default":
|
|
537
|
+
"default": 0,
|
|
538
|
+
"description": "Timeout for a single ACP run in milliseconds. Set to 0 to disable the timeout.",
|
|
519
539
|
"type": "integer",
|
|
520
|
-
"minimum":
|
|
521
|
-
"maximum":
|
|
540
|
+
"minimum": 0,
|
|
541
|
+
"maximum": 2147483647
|
|
522
542
|
},
|
|
523
543
|
"permissionMode": {
|
|
524
544
|
"default": "ask",
|
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
|
|
|
@@ -1,41 +1,32 @@
|
|
|
1
1
|
# src/skills/clonedeps/
|
|
2
2
|
|
|
3
3
|
## Responsibility
|
|
4
|
-
|
|
5
|
-
Workflow-only bundled OpenCode skill for local dependency source mirroring. It
|
|
6
|
-
instructs the orchestrator to use `@librarian` for dependency discovery and
|
|
7
|
-
source URL/ref resolution, then perform approved git/filesystem operations
|
|
8
|
-
directly.
|
|
4
|
+
Manages the cloning and management of read-only dependency source repositories into `.slim/clonedeps/repos/` for offline inspection. This skill provides a workflow (not a command wrapper) that guides the orchestrator and `@librarian` through cloning dependency sources so agents can inspect library internals without requiring network access.
|
|
9
5
|
|
|
10
6
|
## Design
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
- No
|
|
15
|
-
|
|
16
|
-
- State is trackable project metadata stored in `.slim/clonedeps.json`; clone
|
|
17
|
-
contents live under `.slim/clonedeps/repos/<safe-dependency-name>/` and are
|
|
18
|
-
ignored by git.
|
|
19
|
-
- The workflow updates `.gitignore`, `.ignore`, and root `AGENTS.md` with
|
|
20
|
-
concise marker sections so cloned source stays out of git but visible to
|
|
21
|
-
OpenCode and discoverable by future agents.
|
|
7
|
+
- **Workflow skill, not a command wrapper**: No helper scripts or TypeScript utility functions. The orchestrator owns the decision-making; `@librarian` recommends sources; the orchestrator performs filesystem/git operations directly.
|
|
8
|
+
- **Read-only clones**: Dependencies are cloned into `.slim/clonedeps/repos/` and should not be modified.
|
|
9
|
+
- **Cache strategy**: Existing clones are reused when they satisfy the task. Only fetch when the manifest entry is missing or stale.
|
|
10
|
+
- **Agent-driven**: No runtime TS helpers (no `getClonedDepPath` utility). Agents resolve paths from `.slim/clonedeps.json` if needed.
|
|
11
|
+
- **Configuration**: Uses `.slim/clonedeps.json` in the project root to define which repositories are cloned and their checked-out refs.
|
|
22
12
|
|
|
23
13
|
## Flow
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
`.slim/clonedeps/repos/<safe-repo-name>/`.
|
|
32
|
-
5. Orchestrator writes `.slim/clonedeps.json` with paths, refs, and reasons.
|
|
33
|
-
6. Orchestrator updates `.gitignore`, `.ignore`, and root `AGENTS.md`; the
|
|
34
|
-
AGENTS section lists each read-only clone path directly with a one-sentence
|
|
35
|
-
purpose.
|
|
14
|
+
1. **Check existing state**: Read `.slim/clonedeps.json` (if it exists). Check whether each listed `path` exists under `.slim/clonedeps/repos/`.
|
|
15
|
+
2. **Ask librarian for plan**: Delegate dependency discovery and source resolution to `@librarian`, who returns a small plan (dependency name, repo URL, ref, package subdirectory, reason).
|
|
16
|
+
3. **Verify and confirm**: Orchestrator verifies refs with `git ls-remote`, avoids unsafe URLs, presents plan to user, and gets approval before cloning.
|
|
17
|
+
4. **Clone sources**: Orchestrator runs git commands directly. Creates one folder per source under `.slim/clonedeps/repos/<safe-repo-name>/`. Prefers shallow clones, pinned tags, HTTPS URLs.
|
|
18
|
+
5. **Write local state**: Writes `.slim/clonedeps.json` with structured manifest (version, updatedAt, dependencies array).
|
|
19
|
+
6. **Update ignore files**: Adds idempotent marker blocks to `.gitignore` and `.ignore`.
|
|
20
|
+
7. **Register in AGENTS.md**: Appends a `## Cloned Dependency Source` section so future agents know what exists.
|
|
36
21
|
|
|
37
22
|
## Integration
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
|
|
23
|
+
- **Consumed by**: Agents that need to inspect dependency internals (e.g., `@librarian`, `@explorer`) via the registered paths in AGENTS.md.
|
|
24
|
+
- **Depends on**: Git CLI (for `git clone`, `git ls-remote`), `.slim/clonedeps.json` manifest.
|
|
25
|
+
- **Outputs**: Local filesystem paths under `.slim/clonedeps/repos/` for agent inspection.
|
|
26
|
+
|
|
27
|
+
## Notes
|
|
28
|
+
- Cloned repositories are read-only and should not be edited.
|
|
29
|
+
- The cache directory (`.slim/clonedeps/repos/`) is in the **project directory**, not the user's home directory.
|
|
30
|
+
- This skill is for development and debugging; it does not affect runtime behavior.
|
|
31
|
+
- `.slim/clonedeps.json` is small structured metadata that can be committed.
|
|
32
|
+
- Only `.slim/clonedeps/repos/` is gitignored.
|
|
@@ -87,7 +87,7 @@ Once all specific directories are mapped, the Orchestrator must create or update
|
|
|
87
87
|
|
|
88
88
|
**OpenCode auto-loads `AGENTS.md` into agent context on every session.** To ensure agents automatically discover and use the codemap, update (or create) `AGENTS.md` at the repo root:
|
|
89
89
|
|
|
90
|
-
1. If `AGENTS.md` already exists and already contains a `## Repository Map` section, **skip this step**
|
|
90
|
+
1. If `AGENTS.md` already exists and already contains a `## Repository Map` section, **skip this step** - the reference is already set up.
|
|
91
91
|
2. If `AGENTS.md` exists but has no `## Repository Map` section, **append** the section below.
|
|
92
92
|
3. If `AGENTS.md` doesn't exist, **create** it with the section below.
|
|
93
93
|
|
|
@@ -104,7 +104,7 @@ Before working on any task, read `codemap.md` to understand:
|
|
|
104
104
|
For deep work on a specific folder, also read that folder's `codemap.md`.
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
This is idempotent
|
|
107
|
+
This is idempotent - repeated codemap runs will detect the existing section and skip. No duplication.
|
|
108
108
|
|
|
109
109
|
## Codemap Content
|
|
110
110
|
|