arkaos 4.36.0 → 4.37.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.
Files changed (43) hide show
  1. package/VERSION +1 -1
  2. package/core/runtime/opencode.py +16 -5
  3. package/core/runtime/opencode_hooks.py +313 -0
  4. package/harness/codex/AGENTS.md +1 -1
  5. package/harness/copilot/copilot-instructions.md +1 -1
  6. package/harness/cursor/rules/arkaos.mdc +2 -2
  7. package/harness/gemini/GEMINI.md +1 -1
  8. package/harness/opencode/AGENTS.md +1 -1
  9. package/harness/opencode/agents/arka-architect-gabriel.md +1 -1
  10. package/harness/opencode/agents/arka-brand-director-valentina.md +1 -1
  11. package/harness/opencode/agents/arka-cfo-helena.md +1 -1
  12. package/harness/opencode/agents/arka-chief-of-staff-afonso.md +1 -1
  13. package/harness/opencode/agents/arka-community-strategist-beatriz.md +1 -1
  14. package/harness/opencode/agents/arka-content-strategist-rafael.md +1 -1
  15. package/harness/opencode/agents/arka-conversion-strategist-ines.md +1 -1
  16. package/harness/opencode/agents/arka-coo-sofia.md +1 -1
  17. package/harness/opencode/agents/arka-copy-director-eduardo.md +1 -1
  18. package/harness/opencode/agents/arka-cqo-marta.md +1 -1
  19. package/harness/opencode/agents/arka-cto-marco.md +1 -1
  20. package/harness/opencode/agents/arka-design-ops-lead-iris.md +1 -1
  21. package/harness/opencode/agents/arka-ecom-director-ricardo.md +1 -1
  22. package/harness/opencode/agents/arka-knowledge-director-clara.md +1 -1
  23. package/harness/opencode/agents/arka-leadership-director-rodrigo.md +1 -1
  24. package/harness/opencode/agents/arka-marketing-director-luna.md +1 -1
  25. package/harness/opencode/agents/arka-ops-lead-daniel.md +1 -1
  26. package/harness/opencode/agents/arka-pm-director-carolina.md +1 -1
  27. package/harness/opencode/agents/arka-revops-lead-vicente.md +1 -1
  28. package/harness/opencode/agents/arka-saas-strategist-tiago.md +1 -1
  29. package/harness/opencode/agents/arka-sales-director-miguel.md +1 -1
  30. package/harness/opencode/agents/arka-strategy-director-tomas.md +1 -1
  31. package/harness/opencode/agents/arka-tech-director-francisca.md +1 -1
  32. package/harness/opencode/agents/arka-tech-lead-paulo.md +1 -1
  33. package/harness/opencode/agents/arka-video-producer-simao.md +1 -1
  34. package/harness/opencode/agents-meta.json +27 -0
  35. package/harness/opencode/opencode.json +23 -0
  36. package/harness/opencode/plugins/arka.ts +113 -0
  37. package/harness/zed/.rules +1 -1
  38. package/installer/adapters/opencode.js +142 -16
  39. package/installer/assets/opencode/arka.ts +113 -0
  40. package/knowledge/skills-manifest.json +1 -1
  41. package/package.json +1 -1
  42. package/pyproject.toml +1 -1
  43. package/scripts/harness_gen.py +43 -3
@@ -3,7 +3,7 @@ description: "Tech Lead — ArkaOS /dev department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Paulo, Tech Lead of the ArkaOS /dev department (v4.36.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Paulo, Tech Lead of the ArkaOS /dev department (v4.37.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: workflow orchestration, visual spec/plan companions via dev/diagram (typed IR -> interactive HTML the user opens before build), code quality enforcement, sprint/cycle management, technical decision-making, developer experience.
9
9
 
@@ -3,7 +3,7 @@ description: "Video Producer & Production Lead — ArkaOS /content department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Simão, Video Producer & Production Lead of the ArkaOS /content department (v4.36.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Simão, Video Producer & Production Lead of the ArkaOS /content department (v4.37.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: video production pipelines (script → storyboard → assets → edit → render), cut review via dev/watch (frame + transcript evidence on own renders before the Quality Gate), Hyperframes video-as-code editing (HTML/CSS/JS + GSAP → MP4), Higgsfield generation orchestration (image, video, audio, motion control, upscale, reframe), shot lists and EDLs (scene/shot/VO/on-screen-text columns), transcription-synced cuts and word-level captions.
9
9
 
@@ -0,0 +1,27 @@
1
+ {
2
+ "arka-cto-marco.md": "opus",
3
+ "arka-cfo-helena.md": "opus",
4
+ "arka-coo-sofia.md": "opus",
5
+ "arka-copy-director-eduardo.md": "sonnet",
6
+ "arka-tech-director-francisca.md": "sonnet",
7
+ "arka-cqo-marta.md": "opus",
8
+ "arka-design-ops-lead-iris.md": "sonnet",
9
+ "arka-brand-director-valentina.md": "sonnet",
10
+ "arka-community-strategist-beatriz.md": "sonnet",
11
+ "arka-content-strategist-rafael.md": "sonnet",
12
+ "arka-video-producer-simao.md": "opus",
13
+ "arka-architect-gabriel.md": "sonnet",
14
+ "arka-tech-lead-paulo.md": "sonnet",
15
+ "arka-ecom-director-ricardo.md": "sonnet",
16
+ "arka-knowledge-director-clara.md": "sonnet",
17
+ "arka-conversion-strategist-ines.md": "sonnet",
18
+ "arka-leadership-director-rodrigo.md": "sonnet",
19
+ "arka-marketing-director-luna.md": "sonnet",
20
+ "arka-ops-lead-daniel.md": "sonnet",
21
+ "arka-chief-of-staff-afonso.md": "sonnet",
22
+ "arka-pm-director-carolina.md": "sonnet",
23
+ "arka-saas-strategist-tiago.md": "sonnet",
24
+ "arka-revops-lead-vicente.md": "sonnet",
25
+ "arka-sales-director-miguel.md": "sonnet",
26
+ "arka-strategy-director-tomas.md": "sonnet"
27
+ }
@@ -11,6 +11,29 @@
11
11
  "start"
12
12
  ],
13
13
  "enabled": true
14
+ },
15
+ "arka-prompts": {
16
+ "type": "local",
17
+ "command": [
18
+ "uv",
19
+ "--directory",
20
+ "~/.claude/skills/arka/mcp-server",
21
+ "run",
22
+ "server.py"
23
+ ],
24
+ "enabled": true
25
+ },
26
+ "memory-bank": {
27
+ "type": "local",
28
+ "command": [
29
+ "npx",
30
+ "-y",
31
+ "@allpepper/memory-bank-mcp"
32
+ ],
33
+ "environment": {
34
+ "MEMORY_BANK_ROOT": "~/memory-bank"
35
+ },
36
+ "enabled": true
14
37
  }
15
38
  }
16
39
  }
@@ -0,0 +1,113 @@
1
+ import type { Plugin } from "@opencode-ai/plugin"
2
+ import { homedir } from "node:os"
3
+ import { join } from "node:path"
4
+
5
+ const ARKA_PY = join(homedir(), ".arkaos", "bin", "arka-py")
6
+
7
+ type HookResult = Record<string, any> | null
8
+
9
+ function runHook(action: string, payload: Record<string, any>): HookResult {
10
+ try {
11
+ const body = JSON.stringify({ action, ...payload })
12
+ const proc = Bun.spawnSync([ARKA_PY, "-m", "core.runtime.opencode_hooks"], {
13
+ stdin: Buffer.from(body),
14
+ stdout: "pipe",
15
+ stderr: "pipe",
16
+ })
17
+ if (proc.exitCode !== 0) return null
18
+ return JSON.parse(proc.stdout.toString() || "null")
19
+ } catch {
20
+ return null
21
+ }
22
+ }
23
+
24
+ export const ArkaPlugin: Plugin = async ({ client, directory }) => {
25
+ const log = async (level: "info" | "warn" | "error", message: string) => {
26
+ try {
27
+ await client.app.log({ body: { service: "arka", level, message } })
28
+ } catch {}
29
+ }
30
+
31
+ await log("info", "ArkaOS opencode bridge loaded")
32
+
33
+ return {
34
+ "tui.prompt.append": async (input: any, output: any) => {
35
+ const result = runHook("prompt", {
36
+ prompt: String(input?.text ?? ""),
37
+ session_id: String(input?.sessionID ?? ""),
38
+ })
39
+ const suggestions: string[] = result?.suggestions ?? []
40
+ if (suggestions.length > 0) {
41
+ output.text += "\n\n" + suggestions.join("\n")
42
+ }
43
+ },
44
+
45
+ "tool.execute.before": async (input: any, output: any) => {
46
+ const result = runHook("pre_tool", {
47
+ tool: String(input?.tool ?? ""),
48
+ args: output?.args ?? {},
49
+ session_id: String(input?.sessionID ?? ""),
50
+ cwd: directory,
51
+ })
52
+ if (!result) return
53
+ if (result.allow === false) {
54
+ throw new Error(result.message || "Blocked by ArkaOS gate")
55
+ }
56
+ if (result.message) {
57
+ await log("warn", result.message)
58
+ }
59
+ },
60
+
61
+ "tool.execute.after": async (input: any) => {
62
+ runHook("post_tool", {
63
+ tool: String(input?.tool ?? ""),
64
+ session_id: String(input?.sessionID ?? ""),
65
+ ok: true,
66
+ })
67
+ },
68
+
69
+ "experimental.session.compacting": async (_input: any, output: any) => {
70
+ const result = runHook("compact", {})
71
+ const context: string[] = result?.context ?? []
72
+ for (const line of context) {
73
+ output.context.push(line)
74
+ }
75
+ },
76
+
77
+ event: async ({ event }: any) => {
78
+ if (event?.type === "session.created") {
79
+ await log("info", "ArkaOS gates active (kb-first, frontend, compliance)")
80
+ return
81
+ }
82
+ if (event?.type !== "session.idle") return
83
+ try {
84
+ const sessionID = String(event?.properties?.sessionID ?? "")
85
+ if (!sessionID) return
86
+ const response: any = await client.session.messages({
87
+ path: { id: sessionID },
88
+ })
89
+ const messages: any[] = Array.isArray(response?.data)
90
+ ? response.data
91
+ : Array.isArray(response)
92
+ ? response
93
+ : []
94
+ const lastAssistant = [...messages]
95
+ .reverse()
96
+ .find((m: any) => m?.info?.role === "assistant" || m?.role === "assistant")
97
+ const parts: any[] = lastAssistant?.parts ?? []
98
+ const text = parts
99
+ .filter((p: any) => p?.type === "text")
100
+ .map((p: any) => String(p?.text ?? ""))
101
+ .join("\n")
102
+ if (!text) return
103
+ const result = runHook("idle", {
104
+ response_text: text.slice(-20000),
105
+ session_id: sessionID,
106
+ })
107
+ for (const nudge of result?.nudges ?? []) {
108
+ await log("warn", nudge)
109
+ }
110
+ } catch {}
111
+ },
112
+ }
113
+ }
@@ -1,6 +1,6 @@
1
1
  # ArkaOS — The Operating System for AI Agent Teams
2
2
 
3
- > v4.36.0 — 89 agents, 17 departments, 331 skills. Generated by `scripts/harness_gen.py`; do not edit.
3
+ > v4.37.0 — 89 agents, 17 departments, 331 skills. Generated by `scripts/harness_gen.py`; do not edit.
4
4
 
5
5
  You are operating within ArkaOS. Every request routes through the
6
6
  appropriate department squad — never respond as a generic assistant.
@@ -1,4 +1,5 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
2
3
  import { join } from "node:path";
3
4
  import { listBundleFiles, readBundleFile } from "../harness-bundle.js";
4
5
 
@@ -7,9 +8,13 @@ import { listBundleFiles, readBundleFile } from "../harness-bundle.js";
7
8
  * bundle onto OpenCode's native surfaces (opencode.ai/docs):
8
9
  *
9
10
  * AGENTS.md instructions contract (same body as codex)
10
- * agents/*.md curated agent cut (C-suite + squad leads)
11
+ * agents/*.md curated agent cut (C-suite + squad leads), with the
12
+ * agent-YAML model tier resolved onto the user's own
13
+ * opencode model/small_model (agents-meta.json sidecar)
11
14
  * commands/*.md one /arka-<dept> router command per department
12
- * opencode.json arka-tools MCP merged NON-destructively
15
+ * plugins/arka.ts governance plugin (kb-first/frontend/compliance gates)
16
+ * opencode.json arka-tools MCP + seeded user MCPs — merged
17
+ * NON-destructively
13
18
  *
14
19
  * Merge contract: user keys ALWAYS win. An existing `$schema`, any
15
20
  * user-defined `mcp` server, or a user-modified `arka-tools` entry is
@@ -62,6 +67,88 @@ export function mergeOpencodeConfig(existingText, fragmentText) {
62
67
  return { config: merged, changed };
63
68
  }
64
69
 
70
+ /** Expand leading `~` in command arrays / env values (pure, testable). */
71
+ export function expandHome(value) {
72
+ if (typeof value === "string") {
73
+ return value.startsWith("~/") ? join(homedir(), value.slice(2)) : value;
74
+ }
75
+ if (Array.isArray(value)) return value.map(expandHome);
76
+ if (value && typeof value === "object") {
77
+ return Object.fromEntries(
78
+ Object.entries(value).map(([k, v]) => [k, expandHome(v)])
79
+ );
80
+ }
81
+ return value;
82
+ }
83
+
84
+ /**
85
+ * User-local MCPs seeded from ArkaOS state (never in the static bundle —
86
+ * paths/endpoints differ per machine). Returns only entries the user
87
+ * config does NOT already define.
88
+ */
89
+ export function userLocalMcpSeeds(existingMcp) {
90
+ const home = homedir();
91
+ const seeds = {};
92
+ try {
93
+ const profile = JSON.parse(
94
+ readFileSync(join(home, ".arkaos", "profile.json"), "utf-8")
95
+ );
96
+ if (profile.vaultPath && existsSync(profile.vaultPath)) {
97
+ seeds.obsidian = {
98
+ type: "local",
99
+ command: ["npx", "-y", "@bitbonsai/mcpvault@latest", profile.vaultPath],
100
+ enabled: true,
101
+ };
102
+ }
103
+ } catch {}
104
+ try {
105
+ const arkaConfig = JSON.parse(
106
+ readFileSync(join(home, ".arkaos", "config.json"), "utf-8")
107
+ );
108
+ const graphify = arkaConfig?.knowledge?.graphify;
109
+ if (graphify?.enabled && graphify?.url) {
110
+ let headers = graphify.headers;
111
+ if (!headers) {
112
+ try {
113
+ const claude = JSON.parse(
114
+ readFileSync(join(home, ".claude.json"), "utf-8")
115
+ );
116
+ const raw = claude?.mcpServers?.graphify?.headers;
117
+ if (raw) {
118
+ headers = Object.fromEntries(
119
+ Object.entries(raw).map(([k, v]) => [
120
+ k,
121
+ String(v).replace(/^Bearer\s+Bearer\s+/i, "Bearer "),
122
+ ])
123
+ );
124
+ }
125
+ } catch {}
126
+ }
127
+ seeds.graphify = {
128
+ type: "remote",
129
+ url: graphify.url,
130
+ ...(headers ? { headers } : {}),
131
+ enabled: true,
132
+ };
133
+ }
134
+ } catch {}
135
+ return Object.fromEntries(
136
+ Object.entries(seeds).filter(([name]) => !(name in (existingMcp || {})))
137
+ );
138
+ }
139
+
140
+ /**
141
+ * Inject `model:` into a deployed agent file's frontmatter, mapping the
142
+ * ArkaOS tier onto the user's own opencode models. Skips when the file
143
+ * already pins a model or no model is configured.
144
+ */
145
+ export function injectAgentModel(content, tier, mainModel, smallModel) {
146
+ if (!mainModel || /^model:/m.test(content)) return content;
147
+ const resolved = tier === "haiku" ? smallModel || mainModel : mainModel;
148
+ if (!resolved) return content;
149
+ return content.replace(/^---\n/, `---\nmodel: ${resolved}\n`);
150
+ }
151
+
65
152
  export default {
66
153
  configureHooks(config) {
67
154
  // OpenCode has no shell-hook chain; "configure" means deploying the
@@ -77,42 +164,81 @@ export default {
77
164
  mkdirSync(config.configDir, { recursive: true });
78
165
  writeFileSync(join(config.configDir, "AGENTS.md"), agentsMd);
79
166
 
167
+ const existingText = existsSync(config.settingsFile)
168
+ ? readFileSync(config.settingsFile, "utf-8")
169
+ : "";
170
+ let existingConfig = {};
171
+ try {
172
+ existingConfig = existingText.trim() ? JSON.parse(existingText) : {};
173
+ } catch {
174
+ existingConfig = {};
175
+ }
176
+
177
+ let agentsMeta = {};
178
+ try {
179
+ agentsMeta = JSON.parse(
180
+ readBundleFile("opencode", "agents-meta.json") || "{}"
181
+ );
182
+ } catch {
183
+ agentsMeta = {};
184
+ }
185
+
80
186
  let deployed = 0;
81
187
  for (const sub of ["agents", "commands"]) {
82
188
  const names = listBundleFiles("opencode", sub);
83
189
  if (names.length === 0) continue;
84
190
  mkdirSync(join(config.configDir, sub), { recursive: true });
85
191
  for (const name of names) {
86
- const content = readBundleFile("opencode", `${sub}/${name}`);
192
+ let content = readBundleFile("opencode", `${sub}/${name}`);
87
193
  if (content === null) continue;
194
+ if (sub === "agents" && agentsMeta[name]) {
195
+ content = injectAgentModel(
196
+ content,
197
+ agentsMeta[name],
198
+ existingConfig.model,
199
+ existingConfig.small_model
200
+ );
201
+ }
88
202
  writeFileSync(join(config.configDir, sub, name), content);
89
203
  deployed += 1;
90
204
  }
91
205
  }
92
206
 
207
+ const plugin = readBundleFile("opencode", "plugins/arka.ts");
208
+ if (plugin !== null) {
209
+ mkdirSync(join(config.configDir, "plugins"), { recursive: true });
210
+ writeFileSync(join(config.configDir, "plugins", "arka.ts"), plugin);
211
+ deployed += 1;
212
+ }
213
+
93
214
  const fragment = readBundleFile("opencode", "opencode.json");
94
215
  if (fragment) {
95
- const existingText = existsSync(config.settingsFile)
96
- ? readFileSync(config.settingsFile, "utf-8")
97
- : "";
98
- const result = mergeOpencodeConfig(existingText, fragment);
216
+ const expanded = JSON.stringify(expandHome(JSON.parse(fragment)));
217
+ const result = mergeOpencodeConfig(existingText, expanded);
99
218
  if (result === null) {
100
219
  console.warn(
101
220
  " opencode.json unreadable — left untouched " +
102
221
  "(the merge is never destructive)."
103
222
  );
104
- } else if (result.changed) {
105
- writeFileSync(
106
- config.settingsFile,
107
- JSON.stringify(result.config, null, 2) + "\n"
108
- );
109
- console.log(
110
- " opencode.json updated (arka-tools MCP registered, user keys preserved)."
111
- );
223
+ } else {
224
+ const seeds = userLocalMcpSeeds(result.config.mcp || {});
225
+ if (Object.keys(seeds).length > 0) {
226
+ result.config.mcp = { ...(result.config.mcp || {}), ...seeds };
227
+ result.changed = true;
228
+ }
229
+ if (result.changed) {
230
+ writeFileSync(
231
+ config.settingsFile,
232
+ JSON.stringify(result.config, null, 2) + "\n"
233
+ );
234
+ console.log(
235
+ " opencode.json updated (arka MCPs registered, user keys preserved)."
236
+ );
237
+ }
112
238
  }
113
239
  }
114
240
  console.log(
115
- ` OpenCode configured (AGENTS.md + ${deployed} agent/command files).`
241
+ ` OpenCode configured (AGENTS.md + ${deployed} agent/command/plugin files).`
116
242
  );
117
243
  },
118
244
  };
@@ -0,0 +1,113 @@
1
+ import type { Plugin } from "@opencode-ai/plugin"
2
+ import { homedir } from "node:os"
3
+ import { join } from "node:path"
4
+
5
+ const ARKA_PY = join(homedir(), ".arkaos", "bin", "arka-py")
6
+
7
+ type HookResult = Record<string, any> | null
8
+
9
+ function runHook(action: string, payload: Record<string, any>): HookResult {
10
+ try {
11
+ const body = JSON.stringify({ action, ...payload })
12
+ const proc = Bun.spawnSync([ARKA_PY, "-m", "core.runtime.opencode_hooks"], {
13
+ stdin: Buffer.from(body),
14
+ stdout: "pipe",
15
+ stderr: "pipe",
16
+ })
17
+ if (proc.exitCode !== 0) return null
18
+ return JSON.parse(proc.stdout.toString() || "null")
19
+ } catch {
20
+ return null
21
+ }
22
+ }
23
+
24
+ export const ArkaPlugin: Plugin = async ({ client, directory }) => {
25
+ const log = async (level: "info" | "warn" | "error", message: string) => {
26
+ try {
27
+ await client.app.log({ body: { service: "arka", level, message } })
28
+ } catch {}
29
+ }
30
+
31
+ await log("info", "ArkaOS opencode bridge loaded")
32
+
33
+ return {
34
+ "tui.prompt.append": async (input: any, output: any) => {
35
+ const result = runHook("prompt", {
36
+ prompt: String(input?.text ?? ""),
37
+ session_id: String(input?.sessionID ?? ""),
38
+ })
39
+ const suggestions: string[] = result?.suggestions ?? []
40
+ if (suggestions.length > 0) {
41
+ output.text += "\n\n" + suggestions.join("\n")
42
+ }
43
+ },
44
+
45
+ "tool.execute.before": async (input: any, output: any) => {
46
+ const result = runHook("pre_tool", {
47
+ tool: String(input?.tool ?? ""),
48
+ args: output?.args ?? {},
49
+ session_id: String(input?.sessionID ?? ""),
50
+ cwd: directory,
51
+ })
52
+ if (!result) return
53
+ if (result.allow === false) {
54
+ throw new Error(result.message || "Blocked by ArkaOS gate")
55
+ }
56
+ if (result.message) {
57
+ await log("warn", result.message)
58
+ }
59
+ },
60
+
61
+ "tool.execute.after": async (input: any) => {
62
+ runHook("post_tool", {
63
+ tool: String(input?.tool ?? ""),
64
+ session_id: String(input?.sessionID ?? ""),
65
+ ok: true,
66
+ })
67
+ },
68
+
69
+ "experimental.session.compacting": async (_input: any, output: any) => {
70
+ const result = runHook("compact", {})
71
+ const context: string[] = result?.context ?? []
72
+ for (const line of context) {
73
+ output.context.push(line)
74
+ }
75
+ },
76
+
77
+ event: async ({ event }: any) => {
78
+ if (event?.type === "session.created") {
79
+ await log("info", "ArkaOS gates active (kb-first, frontend, compliance)")
80
+ return
81
+ }
82
+ if (event?.type !== "session.idle") return
83
+ try {
84
+ const sessionID = String(event?.properties?.sessionID ?? "")
85
+ if (!sessionID) return
86
+ const response: any = await client.session.messages({
87
+ path: { id: sessionID },
88
+ })
89
+ const messages: any[] = Array.isArray(response?.data)
90
+ ? response.data
91
+ : Array.isArray(response)
92
+ ? response
93
+ : []
94
+ const lastAssistant = [...messages]
95
+ .reverse()
96
+ .find((m: any) => m?.info?.role === "assistant" || m?.role === "assistant")
97
+ const parts: any[] = lastAssistant?.parts ?? []
98
+ const text = parts
99
+ .filter((p: any) => p?.type === "text")
100
+ .map((p: any) => String(p?.text ?? ""))
101
+ .join("\n")
102
+ if (!text) return
103
+ const result = runHook("idle", {
104
+ response_text: text.slice(-20000),
105
+ session_id: sessionID,
106
+ })
107
+ for (const nudge of result?.nudges ?? []) {
108
+ await log("warn", nudge)
109
+ }
110
+ } catch {}
111
+ },
112
+ }
113
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_meta": {
3
3
  "generator": "scripts/marketplace_gen.py",
4
- "version": "4.36.0",
4
+ "version": "4.37.0",
5
5
  "marketplace": "arkaos"
6
6
  },
7
7
  "structural": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "4.36.0",
3
+ "version": "4.37.0",
4
4
  "description": "The Operating System for AI Agent Teams",
5
5
  "type": "module",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "arkaos-core"
3
- version = "4.36.0"
3
+ version = "4.37.0"
4
4
  description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -283,10 +283,22 @@ def _agent_markdown(agent: dict) -> str:
283
283
 
284
284
 
285
285
  def _opencode_agent_files() -> dict[str, str]:
286
- return {
286
+ curated = _curated_agents(_load_agents())
287
+ files = {
287
288
  f"opencode/agents/arka-{agent['id']}.md": _agent_markdown(agent)
288
- for agent in _curated_agents(_load_agents())
289
+ for agent in curated
289
290
  }
291
+ # Model-tier sidecar: the installer adapter maps each tier onto the
292
+ # user's own opencode model/small_model at deploy time (the bundle
293
+ # cannot hardcode a provider — opencode runs on any provider).
294
+ files["opencode/agents-meta.json"] = json.dumps(
295
+ {
296
+ f"arka-{agent['id']}.md": agent.get("model", "sonnet")
297
+ for agent in curated
298
+ },
299
+ indent=2,
300
+ ) + "\n"
301
+ return files
290
302
 
291
303
 
292
304
  def _opencode_command_files() -> dict[str, str]:
@@ -310,7 +322,10 @@ def _opencode_command_files() -> dict[str, str]:
310
322
  def _opencode_config() -> str:
311
323
  """Reference opencode.json fragment. The installer adapter merges it
312
324
  NON-destructively — user keys always win; this file is never copied
313
- over an existing config verbatim."""
325
+ over an existing config verbatim. ``~`` placeholders are expanded by
326
+ the adapter against the installing user's home directory. User-local
327
+ MCPs (obsidian vault, graphify endpoint) are seeded adapter-side from
328
+ ~/.arkaos/profile.json and ~/.arkaos/config.json, not here."""
314
329
  config = {
315
330
  "$schema": "https://opencode.ai/config.json",
316
331
  "mcp": {
@@ -319,11 +334,35 @@ def _opencode_config() -> str:
319
334
  "command": ["npx", "-y", "arkaos", "mcp", "start"],
320
335
  "enabled": True,
321
336
  },
337
+ "arka-prompts": {
338
+ "type": "local",
339
+ "command": [
340
+ "uv",
341
+ "--directory",
342
+ "~/.claude/skills/arka/mcp-server",
343
+ "run",
344
+ "server.py",
345
+ ],
346
+ "enabled": True,
347
+ },
348
+ "memory-bank": {
349
+ "type": "local",
350
+ "command": ["npx", "-y", "@allpepper/memory-bank-mcp"],
351
+ "environment": {"MEMORY_BANK_ROOT": "~/memory-bank"},
352
+ "enabled": True,
353
+ },
322
354
  },
323
355
  }
324
356
  return json.dumps(config, indent=2) + "\n"
325
357
 
326
358
 
359
+ def _opencode_plugin() -> str:
360
+ """Source of the opencode governance plugin (hooks parity). Lives in
361
+ installer/assets so the plugin is reviewable TS, not generated text."""
362
+ path = ROOT / "installer" / "assets" / "opencode" / "arka.ts"
363
+ return path.read_text(encoding="utf-8")
364
+
365
+
327
366
  def generate() -> dict[str, str]:
328
367
  """Return {relative path under harness/: content}."""
329
368
  body = _contract_body()
@@ -337,6 +376,7 @@ def generate() -> dict[str, str]:
337
376
  for rel, content in _cursor_files().items():
338
377
  files[f"cursor/{rel}"] = content
339
378
  files["opencode/opencode.json"] = _opencode_config()
379
+ files["opencode/plugins/arka.ts"] = _opencode_plugin()
340
380
  files.update(_opencode_agent_files())
341
381
  files.update(_opencode_command_files())
342
382
  return files