@divaltor/academy 0.4.6 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,6 +29,9 @@ Add Academy to `opencode.jsonc`. All options are optional. Omit the version to t
29
29
  "color": "#8994B8",
30
30
  "model": "opencode/claude-opus-5#max",
31
31
  },
32
+ "cafe": {
33
+ "disabled": true,
34
+ },
32
35
  },
33
36
  "experimental": {
34
37
  "communication": {
@@ -43,7 +46,7 @@ Add Academy to `opencode.jsonc`. All options are optional. Omit the version to t
43
46
  }
44
47
  ```
45
48
 
46
- Each agent under `agents` accepts `name`, `color`, and `model`. Colors may be six-digit hex values or OpenCode theme colors. Model references use `provider/model` or `provider/model#variant`.
49
+ Each agent under `agents` accepts `name`, `color`, `model`, and `disabled`. Colors may be six-digit hex values or OpenCode theme colors. Model references use `provider/model` or `provider/model#variant`. Set `disabled` to `true` to hide an agent and prevent new subagent or Academy thread creation while retaining its definition so existing sessions can continue. Disabling Rudolf restores OpenCode's Build agent as the default.
47
50
 
48
51
  Without a version suffix OpenCode resolves `latest` on install; run `opencode plugin update` to pull newer releases.
49
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@divaltor/academy",
3
- "version": "0.4.6",
3
+ "version": "0.6.0",
4
4
  "description": "An OpenCode plugin that trains a Tracen Academy roster of specialist coding agents to research, build, and review.",
5
5
  "homepage": "https://github.com/divaltor/academy#readme",
6
6
  "bugs": {
@@ -29,7 +29,7 @@
29
29
  "fix": "ultracite fix"
30
30
  },
31
31
  "dependencies": {
32
- "@ff-labs/fff-bun": "0.10.6",
32
+ "@ff-labs/fff-bun": "0.11.0",
33
33
  "@opencode/plugin": "2.0.11",
34
34
  "@opencode/schema": "2.0.11",
35
35
  "effect": "4.0.0-rc.112"
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ import { Config, Effect, Option, Redacted, Schema } from "effect";
4
4
  import { agents, rudolfCommunicationPrompt } from "./agents";
5
5
  import { FffTools } from "./fff-tools";
6
6
  import { SessionCommunication } from "./session-communication";
7
+ import { SessionGuidance } from "./session-guidance";
7
8
 
8
9
  const ModelReference = Schema.NonEmptyString.check(
9
10
  Schema.isPattern(/^[^/#]+\/[^#]+(?:#[^#]+)?$/u)
@@ -14,6 +15,7 @@ const parseModel = (reference: string | undefined) =>
14
15
 
15
16
  const AgentOptions = Schema.Struct({
16
17
  color: Schema.optionalKey(Agent.Color),
18
+ disabled: Schema.optionalKey(Schema.Boolean),
17
19
  model: Schema.optionalKey(ModelReference),
18
20
  name: Schema.optionalKey(Schema.NonEmptyString),
19
21
  });
@@ -53,6 +55,12 @@ const setup = Effect.fn("Academy.setup")(function* setup(ctx: Plugin.Context) {
53
55
  const agentID = (id: (typeof agents)[number]["id"]) =>
54
56
  Agent.ID.make(agentOptions[id]?.name ?? id);
55
57
  const configuredAgentIDs = agents.map((agent) => agentID(agent.id));
58
+ const disabledAgentIDs = new Set(
59
+ agents
60
+ .filter((agent) => agentOptions[agent.id]?.disabled === true)
61
+ .map((agent) => agentID(agent.id))
62
+ );
63
+ const rudolfDisabled = disabledAgentIDs.has(agentID("rudolf"));
56
64
  const communication = options.experimental?.communication;
57
65
  const communicationEnabled = communication?.enabled === true;
58
66
 
@@ -82,10 +90,12 @@ const setup = Effect.fn("Academy.setup")(function* setup(ctx: Plugin.Context) {
82
90
  yield* FffTools.register(ctx);
83
91
  }
84
92
 
93
+ yield* SessionGuidance.register(ctx);
94
+
85
95
  yield* ctx.agent.transform((editor) => {
86
96
  for (const id of ["build", "plan", "explore", "general"]) {
87
97
  editor.update(id, (agent) => {
88
- agent.hidden = true;
98
+ agent.hidden = !(id === "build" && rudolfDisabled);
89
99
  });
90
100
  }
91
101
 
@@ -102,6 +112,7 @@ const setup = Effect.fn("Academy.setup")(function* setup(ctx: Plugin.Context) {
102
112
  );
103
113
  agent.description = definition.description;
104
114
  agent.mode = definition.mode;
115
+ agent.hidden = disabledAgentIDs.has(agentID(definition.id));
105
116
  agent.color =
106
117
  agentOptions[definition.id]?.color ?? agent.color ?? definition.color;
107
118
  const base =
@@ -133,11 +144,36 @@ const setup = Effect.fn("Academy.setup")(function* setup(ctx: Plugin.Context) {
133
144
  });
134
145
  }
135
146
 
147
+ for (const agent of editor.list()) {
148
+ editor.update(agent.id, (editable) => {
149
+ editable.permissions.push(
150
+ ...disabledAgentIDs.values().map((id) => ({
151
+ action: "subagent",
152
+ effect: "deny" as const,
153
+ resource: id,
154
+ }))
155
+ );
156
+ });
157
+ }
158
+
159
+ if (rudolfDisabled) {
160
+ editor.default(
161
+ editor.list().find((agent) => agent.mode === "primary" && !agent.hidden)
162
+ ?.id
163
+ );
164
+ return;
165
+ }
166
+
136
167
  editor.default(agentID("rudolf"));
137
168
  });
138
169
 
139
170
  if (communication !== undefined && communicationEnabled) {
140
- yield* SessionCommunication.register(ctx, communication, agentID("rudolf"));
171
+ yield* SessionCommunication.register(
172
+ ctx,
173
+ communication,
174
+ agentID("rudolf"),
175
+ disabledAgentIDs
176
+ );
141
177
  }
142
178
  });
143
179
 
@@ -206,7 +206,8 @@ export namespace SessionCommunication {
206
206
  function* register(
207
207
  ctx: Plugin.Context,
208
208
  options: Options,
209
- primaryAgent: Agent.ID
209
+ primaryAgent: Agent.ID,
210
+ disabledAgentIDs: ReadonlySet<Agent.ID>
210
211
  ) {
211
212
  const scanRecords = Effect.fn("SessionCommunication.scanThreadRecords")(
212
213
  function* scanThreadRecords(
@@ -340,6 +341,12 @@ export namespace SessionCommunication {
340
341
  "only the primary Academy session can create threads"
341
342
  );
342
343
  }
344
+ if (
345
+ input.agent !== undefined &&
346
+ disabledAgentIDs.has(input.agent)
347
+ ) {
348
+ return failure(`agent ${input.agent} is disabled`);
349
+ }
343
350
 
344
351
  const detached = input.detached ?? false;
345
352
  const depth = yield* threadDepthFor(
@@ -0,0 +1,24 @@
1
+ import type { Plugin } from "@opencode/plugin/effect";
2
+ import { Effect } from "effect";
3
+
4
+ export namespace SessionGuidance {
5
+ const anthropicRule = [
6
+ "Search and edit rules: never run shell `grep` or `sed`, including piped segments (`| grep`, `| sed`).",
7
+ '- Search: use the `grep` tool first; in shell use only `rg` (`rg -n "pattern" path`, `-F` literal, `-i` case-insensitive, `-v` invert, `-l`/`-c` files/count, `-A`/`-B`/`-C` context, `--replace` for substitutions).',
8
+ "- Edit/view: use Edit (or Write for new files) and Read with offset/limit instead of `sed -i`, `sed -n 'A,Bp'`, or `/pat/d`.",
9
+ "- `rg` is not `grep`-compatible: it recurses, respects .gitignore, and skips hidden files; add `--hidden --no-ignore` only when hidden or ignored files are explicitly required.",
10
+ ].join("\n");
11
+
12
+ export const register = Effect.fn("SessionGuidance.register")(
13
+ function* register(ctx: Plugin.Context) {
14
+ yield* ctx.session.hook(
15
+ "context",
16
+ (input) =>
17
+ Effect.sync(() => {
18
+ input.system.push({ text: anthropicRule, type: "text" as const });
19
+ }),
20
+ { providerID: "anthropic" }
21
+ );
22
+ }
23
+ );
24
+ }