@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 +4 -1
- package/package.json +2 -2
- package/src/index.ts +38 -2
- package/src/session-communication.ts +8 -1
- package/src/session-guidance.ts +24 -0
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 `
|
|
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.
|
|
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.
|
|
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 =
|
|
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(
|
|
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
|
+
}
|