@aria_asi/cli 0.2.24 → 0.2.25
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/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/opencode.js +125 -10
- package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
- package/dist/sdk/BUNDLED.json +1 -1
- package/hooks/aria-agent-handoff.mjs +62 -1
- package/opencode-plugins/harness-context/index.js +60 -0
- package/opencode-plugins/harness-context/inject-context.mjs +120 -0
- package/opencode-plugins/harness-context/package.json +9 -0
- package/opencode-plugins/harness-role/index.js +77 -0
- package/opencode-plugins/harness-role/package.json +9 -0
- package/package.json +3 -2
- package/src/connectors/opencode.ts +156 -19
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../../../src/connectors/opencode.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../../../src/connectors/opencode.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAiD/C,wBAAsB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAyH3E;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAqC5D"}
|
|
@@ -1,6 +1,52 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, readdirSync, statSync, chmodSync, unlinkSync, } from 'fs';
|
|
2
2
|
import { homedir } from 'os';
|
|
3
3
|
import * as path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
// ── Bundled OpenCode plugins ────────────────────────────────────────────────
|
|
6
|
+
//
|
|
7
|
+
// Two plugins ship with the connector:
|
|
8
|
+
// - harness-context: spawns the bundled inject-context.mjs on session start
|
|
9
|
+
// and prepends the resolved harness packet to OpenCode's
|
|
10
|
+
// system prompt. Routes through the canonical SDK at
|
|
11
|
+
// ~/.claude/aria-sdk/.
|
|
12
|
+
// - harness-role: injects the active role profile (default
|
|
13
|
+
// opencode_deepseek_engineer) — intro / continuity /
|
|
14
|
+
// post-action governance rules.
|
|
15
|
+
//
|
|
16
|
+
// Both live at <pkg>/opencode-plugins/<name>/. connectOpenCode copies them
|
|
17
|
+
// into ~/.opencode/plugins/<name>/ and wires the absolute paths into
|
|
18
|
+
// ~/.opencode/config.json's `plugin` (singular, OpenCode v2 schema) array.
|
|
19
|
+
//
|
|
20
|
+
// Compiled location of THIS file (claude-code path applies the same way):
|
|
21
|
+
// <pkg>/dist/aria-connector/src/connectors/opencode.js
|
|
22
|
+
// Plugins ship at <pkg>/opencode-plugins/ → 4 dirs up + 'opencode-plugins'.
|
|
23
|
+
const PLUGIN_NAMES = ['harness-context', 'harness-role'];
|
|
24
|
+
function packageOpenCodePluginsDir() {
|
|
25
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
return path.resolve(here, '..', '..', '..', '..', 'opencode-plugins');
|
|
27
|
+
}
|
|
28
|
+
function copyPluginDir(srcDir, dstDir, logs) {
|
|
29
|
+
if (!existsSync(dstDir)) {
|
|
30
|
+
mkdirSync(dstDir, { recursive: true, mode: 0o755 });
|
|
31
|
+
}
|
|
32
|
+
for (const name of readdirSync(srcDir)) {
|
|
33
|
+
const src = path.join(srcDir, name);
|
|
34
|
+
const stat = statSync(src);
|
|
35
|
+
if (!stat.isFile())
|
|
36
|
+
continue;
|
|
37
|
+
const dst = path.join(dstDir, name);
|
|
38
|
+
copyFileSync(src, dst);
|
|
39
|
+
if (name.endsWith('.mjs') || name.endsWith('.js')) {
|
|
40
|
+
try {
|
|
41
|
+
chmodSync(dst, 0o755);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// chmod failure isn't fatal on filesystems that ignore mode bits.
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
logs.push(`Installed plugin → ${dstDir}`);
|
|
49
|
+
}
|
|
4
50
|
export async function connectOpenCode(config) {
|
|
5
51
|
const logs = [];
|
|
6
52
|
const opencodeDir = path.join(homedir(), '.opencode');
|
|
@@ -8,17 +54,51 @@ export async function connectOpenCode(config) {
|
|
|
8
54
|
logs.push('No ~/.opencode directory found — OpenCode may not be installed');
|
|
9
55
|
return logs;
|
|
10
56
|
}
|
|
57
|
+
// ── Install bundled plugins into ~/.opencode/plugins/<name>/ ──────────────
|
|
58
|
+
const pluginsRoot = path.join(opencodeDir, 'plugins');
|
|
59
|
+
if (!existsSync(pluginsRoot)) {
|
|
60
|
+
mkdirSync(pluginsRoot, { recursive: true, mode: 0o755 });
|
|
61
|
+
}
|
|
62
|
+
// Remove legacy single-file plugin shadows (~/.opencode/plugins/<name>.js).
|
|
63
|
+
// OpenCode v2 auto-discovers BOTH loose .js files and dir-shaped plugins;
|
|
64
|
+
// when both exist with the same name, behavior is undefined and on at least
|
|
65
|
+
// one machine the loose .js (carrying old hardcoded paths to
|
|
66
|
+
// ~/rei-ai-brain/harness/inject-context.mjs) was winning over the dir
|
|
67
|
+
// install. Idempotent cleanup so re-running connect heals these.
|
|
68
|
+
for (const pluginName of PLUGIN_NAMES) {
|
|
69
|
+
const shadowPath = path.join(pluginsRoot, `${pluginName}.js`);
|
|
70
|
+
if (existsSync(shadowPath)) {
|
|
71
|
+
try {
|
|
72
|
+
unlinkSync(shadowPath);
|
|
73
|
+
logs.push(`Removed legacy single-file plugin shadow: ${shadowPath}`);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Permission or fs failure isn't fatal — the install proceeds.
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const bundledDir = packageOpenCodePluginsDir();
|
|
81
|
+
const installedPaths = [];
|
|
82
|
+
for (const pluginName of PLUGIN_NAMES) {
|
|
83
|
+
const srcDir = path.join(bundledDir, pluginName);
|
|
84
|
+
if (!existsSync(srcDir)) {
|
|
85
|
+
logs.push(`⚠ Bundled plugin missing: ${srcDir} — connector tarball may be incomplete`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const dstDir = path.join(pluginsRoot, pluginName);
|
|
89
|
+
copyPluginDir(srcDir, dstDir, logs);
|
|
90
|
+
installedPaths.push(dstDir);
|
|
91
|
+
}
|
|
92
|
+
// ── Wire ~/.opencode/config.json ──────────────────────────────────────────
|
|
11
93
|
const configPath = path.join(opencodeDir, 'config.json');
|
|
12
94
|
if (existsSync(configPath)) {
|
|
13
95
|
try {
|
|
14
96
|
const opencodeConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
15
97
|
let modified = false;
|
|
16
|
-
//
|
|
17
|
-
// That package name was written
|
|
18
|
-
// such npm package exists —
|
|
19
|
-
//
|
|
20
|
-
// non-existent plugin name crashes OpenCode on open. Idempotent
|
|
21
|
-
// cleanup so re-running connect heals broken installs.
|
|
98
|
+
// Heal legacy '@aria/connector' references in the older `plugins`
|
|
99
|
+
// (plural) array. That package name was written by older versions
|
|
100
|
+
// of this connector but no such npm package exists — loading a
|
|
101
|
+
// non-existent plugin crashes OpenCode on open. Idempotent.
|
|
22
102
|
if (Array.isArray(opencodeConfig.plugins)) {
|
|
23
103
|
const before = opencodeConfig.plugins.length;
|
|
24
104
|
opencodeConfig.plugins = opencodeConfig.plugins.filter((p) => p !== '@aria/connector');
|
|
@@ -27,6 +107,27 @@ export async function connectOpenCode(config) {
|
|
|
27
107
|
logs.push('Removed legacy @aria/connector reference from OpenCode plugins (was crashing on open)');
|
|
28
108
|
}
|
|
29
109
|
}
|
|
110
|
+
// OpenCode v2 schema: `plugin` (singular) array of absolute paths or
|
|
111
|
+
// npm names. We write absolute paths to the locally-installed plugin
|
|
112
|
+
// dirs above, so OpenCode resolves them as Node modules without any
|
|
113
|
+
// network or registry dependency.
|
|
114
|
+
const existingPlugin = Array.isArray(opencodeConfig.plugin)
|
|
115
|
+
? opencodeConfig.plugin.slice()
|
|
116
|
+
: [];
|
|
117
|
+
const stringSet = new Set(existingPlugin.filter((p) => typeof p === 'string'));
|
|
118
|
+
let pluginAdded = false;
|
|
119
|
+
for (const installPath of installedPaths) {
|
|
120
|
+
if (!stringSet.has(installPath)) {
|
|
121
|
+
existingPlugin.push(installPath);
|
|
122
|
+
stringSet.add(installPath);
|
|
123
|
+
pluginAdded = true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (pluginAdded) {
|
|
127
|
+
opencodeConfig.plugin = existingPlugin;
|
|
128
|
+
modified = true;
|
|
129
|
+
logs.push(`Wired ${installedPaths.length} Aria plugin(s) into OpenCode config.plugin`);
|
|
130
|
+
}
|
|
30
131
|
if (!opencodeConfig.agentsMdPath) {
|
|
31
132
|
opencodeConfig.agentsMdPath = path.join(homedir(), '.aria', 'AGENTS.md');
|
|
32
133
|
modified = true;
|
|
@@ -43,6 +144,7 @@ export async function connectOpenCode(config) {
|
|
|
43
144
|
logs.push('Failed to parse OpenCode config.json');
|
|
44
145
|
}
|
|
45
146
|
}
|
|
147
|
+
// ── Write the harness AGENTS.md ───────────────────────────────────────────
|
|
46
148
|
const ariaDir = path.join(homedir(), '.aria');
|
|
47
149
|
if (!existsSync(ariaDir)) {
|
|
48
150
|
mkdirSync(ariaDir, { recursive: true });
|
|
@@ -59,11 +161,24 @@ export async function disconnectOpenCode() {
|
|
|
59
161
|
if (existsSync(configPath)) {
|
|
60
162
|
try {
|
|
61
163
|
const opencodeConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
164
|
+
// Strip any locally-installed Aria plugin paths from `plugin` (singular).
|
|
165
|
+
const pluginsRoot = path.join(homedir(), '.opencode', 'plugins');
|
|
166
|
+
if (Array.isArray(opencodeConfig.plugin)) {
|
|
167
|
+
const before = opencodeConfig.plugin.length;
|
|
168
|
+
opencodeConfig.plugin = opencodeConfig.plugin.filter((p) => {
|
|
169
|
+
if (typeof p !== 'string')
|
|
170
|
+
return true;
|
|
171
|
+
return !PLUGIN_NAMES.some((n) => p === path.join(pluginsRoot, n));
|
|
172
|
+
});
|
|
173
|
+
if (opencodeConfig.plugin.length !== before) {
|
|
174
|
+
logs.push('Removed Aria plugin paths from OpenCode config.plugin');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Strip the legacy @aria/connector reference if present.
|
|
62
178
|
if (Array.isArray(opencodeConfig.plugins)) {
|
|
63
179
|
opencodeConfig.plugins = opencodeConfig.plugins.filter((p) => p !== '@aria/connector');
|
|
64
|
-
writeFileSync(configPath, JSON.stringify(opencodeConfig, null, 2));
|
|
65
|
-
logs.push('Removed @aria/connector from OpenCode plugins');
|
|
66
180
|
}
|
|
181
|
+
writeFileSync(configPath, JSON.stringify(opencodeConfig, null, 2));
|
|
67
182
|
}
|
|
68
183
|
catch {
|
|
69
184
|
logs.push('Failed to update OpenCode config');
|
|
@@ -78,7 +193,7 @@ function buildOpenCodeAgentsMd(config) {
|
|
|
78
193
|
.join('\n\n');
|
|
79
194
|
return `# Aria Harness — AGENTS.md
|
|
80
195
|
|
|
81
|
-
Automatically injected by @
|
|
196
|
+
Automatically injected by @aria_asi/cli. This file provides Aria's cognitive harness
|
|
82
197
|
to OpenCode sessions.
|
|
83
198
|
|
|
84
199
|
## Connected Repositories
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opencode.js","sourceRoot":"","sources":["../../../../src/connectors/opencode.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"opencode.js","sourceRoot":"","sources":["../../../../src/connectors/opencode.ts"],"names":[],"mappings":"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"}
|
package/dist/sdk/BUNDLED.json
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
22
22
|
import { dirname, join } from 'node:path';
|
|
23
23
|
import { homedir } from 'node:os';
|
|
24
|
+
import { createRequire } from 'node:module';
|
|
24
25
|
|
|
25
26
|
const HOME = homedir();
|
|
26
27
|
const LOG = `${HOME}/.claude/aria-pre-tool-gate.log`;
|
|
@@ -129,6 +130,62 @@ if (isClientTier) {
|
|
|
129
130
|
parentLedgerPath = `${HOME}/.claude/aria-discoveries-${safeSession}.jsonl`;
|
|
130
131
|
}
|
|
131
132
|
|
|
133
|
+
// ── Harness packet fetch for sub-agent substrate binding ────────────────────
|
|
134
|
+
// Layer 1: after writing identity handoff, fetch the harness packet from
|
|
135
|
+
// /api/harness/codex so the sub-agent can load COGNITION (not just identity).
|
|
136
|
+
// Fail-soft: any fetch failure logs a warning and leaves the handoff identity-only.
|
|
137
|
+
// Never blocks the spawn — packet is substrate enrichment, not a gate.
|
|
138
|
+
//
|
|
139
|
+
// Tier-aware packet paths:
|
|
140
|
+
// Owner: ~/.claude/aria-agent-harness-packet.json
|
|
141
|
+
// Client: /var/lib/aria-licensee/{jti}/aria-agent-harness-packet.json
|
|
142
|
+
let packetPath = null;
|
|
143
|
+
if (isClientTier) {
|
|
144
|
+
packetPath = `/var/lib/aria-licensee/${jti}/aria-agent-harness-packet.json`;
|
|
145
|
+
} else {
|
|
146
|
+
packetPath = `${HOME}/.claude/aria-agent-harness-packet.json`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function fetchAndWriteHarnessPacket() {
|
|
150
|
+
if (!harnessToken || !harnessUrl) {
|
|
151
|
+
audit('warn: skipping packet fetch — no harnessToken or harnessUrl');
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
const body = JSON.stringify({
|
|
156
|
+
stage: 'agent-spawn',
|
|
157
|
+
actor: 'harness-http-client',
|
|
158
|
+
system: 'claude-coding-agent',
|
|
159
|
+
surface: 'platform:harness-http-client',
|
|
160
|
+
isHamza: ownerTier.hamza,
|
|
161
|
+
sessionId: sessionId,
|
|
162
|
+
mode: 'subagent',
|
|
163
|
+
});
|
|
164
|
+
const resp = await fetch(`${harnessUrl}/api/harness/codex`, {
|
|
165
|
+
method: 'POST',
|
|
166
|
+
headers: {
|
|
167
|
+
'Content-Type': 'application/json',
|
|
168
|
+
'Authorization': `Bearer ${harnessToken}`,
|
|
169
|
+
},
|
|
170
|
+
body,
|
|
171
|
+
});
|
|
172
|
+
if (!resp.ok) {
|
|
173
|
+
audit(`warn: harness packet fetch HTTP ${resp.status} — identity-only handoff`);
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
const data = await resp.json();
|
|
177
|
+
mkdirSync(dirname(packetPath), { recursive: true });
|
|
178
|
+
writeFileSync(packetPath, JSON.stringify(data, null, 2));
|
|
179
|
+
audit(`wrote harness packet tier=${isClientTier ? 'client' : 'owner'} path=${packetPath}`);
|
|
180
|
+
return packetPath;
|
|
181
|
+
} catch (err) {
|
|
182
|
+
audit(`warn: harness packet fetch failed: ${(err?.message || err).toString().slice(0, 200)} — identity-only handoff`);
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const resolvedPacketPath = await fetchAndWriteHarnessPacket();
|
|
188
|
+
|
|
132
189
|
const handoff = {
|
|
133
190
|
writtenAt: new Date().toISOString(),
|
|
134
191
|
parentSessionId: sessionId,
|
|
@@ -137,6 +194,9 @@ const handoff = {
|
|
|
137
194
|
ownerTier,
|
|
138
195
|
parentLedgerPath,
|
|
139
196
|
ttlMs: HANDOFF_TTL_MS,
|
|
197
|
+
// harnessPacketPath is set only if packet was successfully fetched;
|
|
198
|
+
// absent means sub-agent should fall back to calling /api/harness/codex directly.
|
|
199
|
+
...(resolvedPacketPath ? { harnessPacketPath: resolvedPacketPath } : {}),
|
|
140
200
|
};
|
|
141
201
|
|
|
142
202
|
try {
|
|
@@ -144,7 +204,8 @@ try {
|
|
|
144
204
|
writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
|
|
145
205
|
audit(
|
|
146
206
|
`wrote handoff tier=${isClientTier ? 'client' : 'owner'} jti=${jti ?? 'none'} ` +
|
|
147
|
-
`session=${sessionId} hamza=${ownerTier.hamza} role=${ownerTier.roleProfile}
|
|
207
|
+
`session=${sessionId} hamza=${ownerTier.hamza} role=${ownerTier.roleProfile} ` +
|
|
208
|
+
`packetPath=${resolvedPacketPath ?? 'none'}`,
|
|
148
209
|
);
|
|
149
210
|
} catch (err) {
|
|
150
211
|
audit(`ERROR: handoff write failed: ${(err?.message || err).toString().slice(0, 200)}`);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aria Harness Context Plugin for OpenCode.
|
|
3
|
+
*
|
|
4
|
+
* Injects the live harness packet into OpenCode's system prompt on every
|
|
5
|
+
* session start. Routes through the canonical @aria/harness-http-client SDK
|
|
6
|
+
* via the inject-context.mjs script that ships alongside this plugin.
|
|
7
|
+
*
|
|
8
|
+
* Distribution: this dir is installed by `aria connect` (via connectors/
|
|
9
|
+
* opencode.ts) into `~/.opencode/plugins/harness-context/`. The plugin's
|
|
10
|
+
* absolute install path is wired into ~/.opencode/config.json's `plugin`
|
|
11
|
+
* (singular) array. OpenCode loads it on session start.
|
|
12
|
+
*
|
|
13
|
+
* inject-context.mjs is resolved RELATIVE TO THIS FILE (via import.meta.url),
|
|
14
|
+
* so the same install layout works on every machine — no $HOME or repo-path
|
|
15
|
+
* assumptions.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { execFileSync } from 'node:child_process';
|
|
19
|
+
import { existsSync } from 'node:fs';
|
|
20
|
+
import { dirname, join } from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
|
|
23
|
+
const PLUGIN_DIR = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const INJECT_SCRIPT = join(PLUGIN_DIR, 'inject-context.mjs');
|
|
25
|
+
|
|
26
|
+
function getHarnessContext() {
|
|
27
|
+
if (!existsSync(INJECT_SCRIPT)) {
|
|
28
|
+
process.stderr.write(`[harness-context] inject-context.mjs missing at ${INJECT_SCRIPT}\n`);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
// execFileSync — argv-safe (no shell injection surface even though the
|
|
33
|
+
// path is internal). 15s ceiling matches OpenCode's tolerance for
|
|
34
|
+
// session-start blocking. Stdout is the prepend-able context; stderr
|
|
35
|
+
// is the diagnostic surface.
|
|
36
|
+
const output = execFileSync(process.execPath, [INJECT_SCRIPT], {
|
|
37
|
+
encoding: 'utf-8',
|
|
38
|
+
timeout: 15000,
|
|
39
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
40
|
+
env: process.env,
|
|
41
|
+
});
|
|
42
|
+
return output;
|
|
43
|
+
} catch (err) {
|
|
44
|
+
process.stderr.write(`[harness-context] inject-context failed: ${err && err.message ? err.message : String(err)}\n`);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default async function HarnessContextPlugin(ctx) {
|
|
50
|
+
const context = getHarnessContext();
|
|
51
|
+
if (context) {
|
|
52
|
+
try {
|
|
53
|
+
ctx.system?.prepend?.(context.slice(0, 8000));
|
|
54
|
+
} catch (_) {
|
|
55
|
+
// ctx.system shape varies across OpenCode versions; if prepend isn't
|
|
56
|
+
// available we silently no-op rather than crash plugin load.
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Aria harness context injector — bundled alongside the harness-context
|
|
3
|
+
// OpenCode plugin. Resolved relative to the plugin (via import.meta.url),
|
|
4
|
+
// so the install layout is portable across machines.
|
|
5
|
+
//
|
|
6
|
+
// Contract with the plugin:
|
|
7
|
+
// - stdin: ignored
|
|
8
|
+
// - stdout: text to prepend to OpenCode's system prompt
|
|
9
|
+
// - exit 0 on success; non-zero treated as failure (plugin returns null)
|
|
10
|
+
//
|
|
11
|
+
// SDK resolution: prefers ~/.claude/aria-sdk/index.js (the SDK installed by
|
|
12
|
+
// `aria connect`'s claude-code path — clients with both connectors share it).
|
|
13
|
+
// Cache fast-path uses ~/.claude/.aria-harness-last-packet.json so OpenCode
|
|
14
|
+
// session-start doesn't block on a slow consult endpoint.
|
|
15
|
+
|
|
16
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
17
|
+
import { homedir } from 'node:os';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
|
|
20
|
+
const HOME = homedir();
|
|
21
|
+
const LICENSE_PATH = join(HOME, '.aria', 'license.json');
|
|
22
|
+
const OWNER_TOKEN_PATH = join(HOME, '.aria', 'owner-token');
|
|
23
|
+
const SDK_PATH = join(HOME, '.claude', 'aria-sdk', 'index.js');
|
|
24
|
+
const PACKET_CACHE_PATH = join(HOME, '.claude', '.aria-harness-last-packet.json');
|
|
25
|
+
const PACKET_CACHE_TTL_SEC = Number(process.env.ARIA_HARNESS_CACHE_TTL_SEC || '60');
|
|
26
|
+
|
|
27
|
+
function fail(reason) {
|
|
28
|
+
process.stderr.write(`[inject-context] ${reason}\n`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveApiKey() {
|
|
33
|
+
if (process.env.ARIA_API_KEY) return process.env.ARIA_API_KEY;
|
|
34
|
+
if (process.env.ARIA_HARNESS_TOKEN) return process.env.ARIA_HARNESS_TOKEN;
|
|
35
|
+
if (process.env.ARIA_MASTER_TOKEN) return process.env.ARIA_MASTER_TOKEN;
|
|
36
|
+
if (existsSync(OWNER_TOKEN_PATH)) {
|
|
37
|
+
try {
|
|
38
|
+
const v = readFileSync(OWNER_TOKEN_PATH, 'utf8').trim();
|
|
39
|
+
if (v) return v;
|
|
40
|
+
} catch {}
|
|
41
|
+
}
|
|
42
|
+
if (existsSync(LICENSE_PATH)) {
|
|
43
|
+
try {
|
|
44
|
+
const j = JSON.parse(readFileSync(LICENSE_PATH, 'utf8'));
|
|
45
|
+
if (j.token) return j.token;
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveBaseUrl() {
|
|
52
|
+
if (process.env.ARIA_HARNESS_BASE_URL) return process.env.ARIA_HARNESS_BASE_URL.replace(/\/+$/, '');
|
|
53
|
+
if (process.env.ARIA_SOUL_URL) return process.env.ARIA_SOUL_URL.replace(/\/+$/, '');
|
|
54
|
+
return 'http://localhost:30080';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function loadCachedPacket() {
|
|
58
|
+
try {
|
|
59
|
+
if (!existsSync(PACKET_CACHE_PATH)) return null;
|
|
60
|
+
const ageSec = (Date.now() - statSync(PACKET_CACHE_PATH).mtimeMs) / 1000;
|
|
61
|
+
if (ageSec > PACKET_CACHE_TTL_SEC) return null;
|
|
62
|
+
return JSON.parse(readFileSync(PACKET_CACHE_PATH, 'utf8'));
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function formatPacketAsContext(packet) {
|
|
69
|
+
if (typeof packet === 'string') return packet;
|
|
70
|
+
if (packet && typeof packet === 'object') {
|
|
71
|
+
if (packet.packet && typeof packet.packet === 'object') {
|
|
72
|
+
return JSON.stringify(packet.packet, null, 2);
|
|
73
|
+
}
|
|
74
|
+
if (packet.harness && typeof packet.harness === 'string') return packet.harness;
|
|
75
|
+
return JSON.stringify(packet, null, 2);
|
|
76
|
+
}
|
|
77
|
+
return '';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function fetchViaSdk(baseUrl, apiKey) {
|
|
81
|
+
if (!existsSync(SDK_PATH)) {
|
|
82
|
+
fail(`SDK not found at ${SDK_PATH} — run \`aria connect\` to install`);
|
|
83
|
+
}
|
|
84
|
+
const mod = await import(SDK_PATH);
|
|
85
|
+
const Client = mod.HTTPHarnessClient;
|
|
86
|
+
if (!Client) fail('SDK loaded but HTTPHarnessClient not exported — bundle is broken');
|
|
87
|
+
const client = new Client({ baseUrl, apiKey, workspaceRoot: process.cwd() });
|
|
88
|
+
return client.getHarnessPacket({
|
|
89
|
+
stage: 'session-start',
|
|
90
|
+
actor: 'opencode',
|
|
91
|
+
system: 'opencode',
|
|
92
|
+
platform: 'opencode',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function main() {
|
|
97
|
+
// Cache fast-path first — don't block OpenCode startup on a slow consult.
|
|
98
|
+
const cached = loadCachedPacket();
|
|
99
|
+
if (cached) {
|
|
100
|
+
process.stdout.write(formatPacketAsContext(cached));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const apiKey = resolveApiKey();
|
|
105
|
+
if (!apiKey) {
|
|
106
|
+
fail('no API key — set ARIA_API_KEY or run `aria login`');
|
|
107
|
+
}
|
|
108
|
+
const baseUrl = resolveBaseUrl();
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const packet = await fetchViaSdk(baseUrl, apiKey);
|
|
112
|
+
process.stdout.write(formatPacketAsContext(packet));
|
|
113
|
+
} catch (err) {
|
|
114
|
+
fail(`SDK fetch failed: ${err && err.message ? err.message : String(err)}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
main().catch((err) => {
|
|
119
|
+
fail(`unexpected: ${err && err.message ? err.message : String(err)}`);
|
|
120
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aria Harness Role Plugin for OpenCode.
|
|
3
|
+
*
|
|
4
|
+
* Resolves the active role profile on session start and injects lane-specific
|
|
5
|
+
* governance rules (intro, continuity, post-action). Default profile is
|
|
6
|
+
* opencode_deepseek_engineer; override with OPENCODE_ARIA_ROLE_PROFILE env.
|
|
7
|
+
*
|
|
8
|
+
* Distribution: installed by `aria connect` (via connectors/opencode.ts) into
|
|
9
|
+
* `~/.opencode/plugins/harness-role/`. The plugin's absolute install path is
|
|
10
|
+
* wired into ~/.opencode/config.json's `plugin` array.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const ROLE_PROFILES = [
|
|
14
|
+
{
|
|
15
|
+
id: 'opencode_deepseek_engineer',
|
|
16
|
+
label: 'DeepSeek Engineer',
|
|
17
|
+
authority: 'full_write',
|
|
18
|
+
destructiveAllowed: false,
|
|
19
|
+
contractRequired: true,
|
|
20
|
+
ruleIntro: 'You are an engineering execution lane. Prefer exact files, commands, state transitions, and verification over explanation.',
|
|
21
|
+
ruleContinuity: 'Continue the active plan. Update issue/task ledger after meaningful state changes. Keep exact files, commands, evidence, and blockers visible.',
|
|
22
|
+
rulePostAction: 'After work, write outcome, verification, deploy state, residual risk, and next action.',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'opencode_code_reviewer',
|
|
26
|
+
label: 'Code Reviewer',
|
|
27
|
+
authority: 'read_only',
|
|
28
|
+
destructiveAllowed: false,
|
|
29
|
+
contractRequired: true,
|
|
30
|
+
ruleIntro: 'You are a quality gate, not a generic explainer. Prioritize contradictions, missing evidence, regressions, and unverifiable completion claims.',
|
|
31
|
+
ruleContinuity: 'If output is not proven, fail it cleanly and say what evidence or fix is missing.',
|
|
32
|
+
rulePostAction: 'After review, record findings, severity, and specific fix recommendations.',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'opencode_infra_operator',
|
|
36
|
+
label: 'Infra Operator',
|
|
37
|
+
authority: 'destructive',
|
|
38
|
+
destructiveAllowed: true,
|
|
39
|
+
contractRequired: true,
|
|
40
|
+
ruleIntro: 'You are an infra/execution lane. Prefer exact commands, state transitions, and verification. Never report success from intent alone.',
|
|
41
|
+
ruleContinuity: 'Report only what was executed, observed, changed, or still blocked. Bind every action to a tracked issue.',
|
|
42
|
+
rulePostAction: 'Record deploy state, rollback plan, residual risk, and verification evidence.',
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
function resolveRole() {
|
|
47
|
+
const envRole = (process.env.OPENCODE_ARIA_ROLE_PROFILE || '').trim().toLowerCase();
|
|
48
|
+
if (envRole) {
|
|
49
|
+
const found = ROLE_PROFILES.find(p => p.id === envRole);
|
|
50
|
+
if (found) return found;
|
|
51
|
+
}
|
|
52
|
+
return ROLE_PROFILES.find(p => p.id === 'opencode_deepseek_engineer');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default async function HarnessRolePlugin(ctx) {
|
|
56
|
+
const role = resolveRole();
|
|
57
|
+
const systemBlock = [
|
|
58
|
+
`=== ARIA LANE: ${role.label} ===`,
|
|
59
|
+
`Role: ${role.id} | Authority: ${role.authority} | Contract Required: ${role.contractRequired}`,
|
|
60
|
+
'',
|
|
61
|
+
role.ruleIntro,
|
|
62
|
+
'',
|
|
63
|
+
role.ruleContinuity,
|
|
64
|
+
'',
|
|
65
|
+
role.rulePostAction,
|
|
66
|
+
].join('\n');
|
|
67
|
+
|
|
68
|
+
process.stderr.write(`[harness-role] Active role: ${role.id} (${role.label})\n`);
|
|
69
|
+
process.stderr.write(`[harness-role] Authority: ${role.authority} | Destructive: ${role.destructiveAllowed} | Contract: ${role.contractRequired}\n`);
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
ctx.system?.prepend?.(systemBlock);
|
|
73
|
+
} catch (_) {
|
|
74
|
+
// Silent — different OpenCode versions expose ctx differently.
|
|
75
|
+
}
|
|
76
|
+
return {};
|
|
77
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aria_asi/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.25",
|
|
4
4
|
"description": "Aria Smart CLI — the world's first harness-powered terminal companion",
|
|
5
5
|
"bin": {
|
|
6
6
|
"aria": "./bin/aria.js"
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"bin",
|
|
38
38
|
"dist",
|
|
39
39
|
"src",
|
|
40
|
-
"hooks"
|
|
40
|
+
"hooks",
|
|
41
|
+
"opencode-plugins"
|
|
41
42
|
],
|
|
42
43
|
"engines": {
|
|
43
44
|
"node": ">=20.0.0"
|
|
@@ -1,8 +1,66 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
writeFileSync,
|
|
6
|
+
copyFileSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
statSync,
|
|
9
|
+
chmodSync,
|
|
10
|
+
unlinkSync,
|
|
11
|
+
} from 'fs';
|
|
2
12
|
import { homedir } from 'os';
|
|
3
13
|
import * as path from 'path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
4
15
|
import type { AriaConfig } from '../config.js';
|
|
5
16
|
|
|
17
|
+
// ── Bundled OpenCode plugins ────────────────────────────────────────────────
|
|
18
|
+
//
|
|
19
|
+
// Two plugins ship with the connector:
|
|
20
|
+
// - harness-context: spawns the bundled inject-context.mjs on session start
|
|
21
|
+
// and prepends the resolved harness packet to OpenCode's
|
|
22
|
+
// system prompt. Routes through the canonical SDK at
|
|
23
|
+
// ~/.claude/aria-sdk/.
|
|
24
|
+
// - harness-role: injects the active role profile (default
|
|
25
|
+
// opencode_deepseek_engineer) — intro / continuity /
|
|
26
|
+
// post-action governance rules.
|
|
27
|
+
//
|
|
28
|
+
// Both live at <pkg>/opencode-plugins/<name>/. connectOpenCode copies them
|
|
29
|
+
// into ~/.opencode/plugins/<name>/ and wires the absolute paths into
|
|
30
|
+
// ~/.opencode/config.json's `plugin` (singular, OpenCode v2 schema) array.
|
|
31
|
+
//
|
|
32
|
+
// Compiled location of THIS file (claude-code path applies the same way):
|
|
33
|
+
// <pkg>/dist/aria-connector/src/connectors/opencode.js
|
|
34
|
+
// Plugins ship at <pkg>/opencode-plugins/ → 4 dirs up + 'opencode-plugins'.
|
|
35
|
+
|
|
36
|
+
const PLUGIN_NAMES = ['harness-context', 'harness-role'] as const;
|
|
37
|
+
|
|
38
|
+
function packageOpenCodePluginsDir(): string {
|
|
39
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
40
|
+
return path.resolve(here, '..', '..', '..', '..', 'opencode-plugins');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function copyPluginDir(srcDir: string, dstDir: string, logs: string[]): void {
|
|
44
|
+
if (!existsSync(dstDir)) {
|
|
45
|
+
mkdirSync(dstDir, { recursive: true, mode: 0o755 });
|
|
46
|
+
}
|
|
47
|
+
for (const name of readdirSync(srcDir)) {
|
|
48
|
+
const src = path.join(srcDir, name);
|
|
49
|
+
const stat = statSync(src);
|
|
50
|
+
if (!stat.isFile()) continue;
|
|
51
|
+
const dst = path.join(dstDir, name);
|
|
52
|
+
copyFileSync(src, dst);
|
|
53
|
+
if (name.endsWith('.mjs') || name.endsWith('.js')) {
|
|
54
|
+
try {
|
|
55
|
+
chmodSync(dst, 0o755);
|
|
56
|
+
} catch {
|
|
57
|
+
// chmod failure isn't fatal on filesystems that ignore mode bits.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
logs.push(`Installed plugin → ${dstDir}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
6
64
|
export async function connectOpenCode(config: AriaConfig): Promise<string[]> {
|
|
7
65
|
const logs: string[] = [];
|
|
8
66
|
const opencodeDir = path.join(homedir(), '.opencode');
|
|
@@ -12,29 +70,91 @@ export async function connectOpenCode(config: AriaConfig): Promise<string[]> {
|
|
|
12
70
|
return logs;
|
|
13
71
|
}
|
|
14
72
|
|
|
73
|
+
// ── Install bundled plugins into ~/.opencode/plugins/<name>/ ──────────────
|
|
74
|
+
const pluginsRoot = path.join(opencodeDir, 'plugins');
|
|
75
|
+
if (!existsSync(pluginsRoot)) {
|
|
76
|
+
mkdirSync(pluginsRoot, { recursive: true, mode: 0o755 });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Remove legacy single-file plugin shadows (~/.opencode/plugins/<name>.js).
|
|
80
|
+
// OpenCode v2 auto-discovers BOTH loose .js files and dir-shaped plugins;
|
|
81
|
+
// when both exist with the same name, behavior is undefined and on at least
|
|
82
|
+
// one machine the loose .js (carrying old hardcoded paths to
|
|
83
|
+
// ~/rei-ai-brain/harness/inject-context.mjs) was winning over the dir
|
|
84
|
+
// install. Idempotent cleanup so re-running connect heals these.
|
|
85
|
+
for (const pluginName of PLUGIN_NAMES) {
|
|
86
|
+
const shadowPath = path.join(pluginsRoot, `${pluginName}.js`);
|
|
87
|
+
if (existsSync(shadowPath)) {
|
|
88
|
+
try {
|
|
89
|
+
unlinkSync(shadowPath);
|
|
90
|
+
logs.push(`Removed legacy single-file plugin shadow: ${shadowPath}`);
|
|
91
|
+
} catch {
|
|
92
|
+
// Permission or fs failure isn't fatal — the install proceeds.
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const bundledDir = packageOpenCodePluginsDir();
|
|
98
|
+
const installedPaths: string[] = [];
|
|
99
|
+
for (const pluginName of PLUGIN_NAMES) {
|
|
100
|
+
const srcDir = path.join(bundledDir, pluginName);
|
|
101
|
+
if (!existsSync(srcDir)) {
|
|
102
|
+
logs.push(`⚠ Bundled plugin missing: ${srcDir} — connector tarball may be incomplete`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const dstDir = path.join(pluginsRoot, pluginName);
|
|
106
|
+
copyPluginDir(srcDir, dstDir, logs);
|
|
107
|
+
installedPaths.push(dstDir);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Wire ~/.opencode/config.json ──────────────────────────────────────────
|
|
15
111
|
const configPath = path.join(opencodeDir, 'config.json');
|
|
16
112
|
if (existsSync(configPath)) {
|
|
17
113
|
try {
|
|
18
|
-
const opencodeConfig = JSON.parse(
|
|
114
|
+
const opencodeConfig: Record<string, unknown> = JSON.parse(
|
|
115
|
+
readFileSync(configPath, 'utf-8'),
|
|
116
|
+
);
|
|
19
117
|
let modified = false;
|
|
20
118
|
|
|
21
|
-
//
|
|
22
|
-
// That package name was written
|
|
23
|
-
// such npm package exists —
|
|
24
|
-
//
|
|
25
|
-
// non-existent plugin name crashes OpenCode on open. Idempotent
|
|
26
|
-
// cleanup so re-running connect heals broken installs.
|
|
119
|
+
// Heal legacy '@aria/connector' references in the older `plugins`
|
|
120
|
+
// (plural) array. That package name was written by older versions
|
|
121
|
+
// of this connector but no such npm package exists — loading a
|
|
122
|
+
// non-existent plugin crashes OpenCode on open. Idempotent.
|
|
27
123
|
if (Array.isArray(opencodeConfig.plugins)) {
|
|
28
|
-
const before = opencodeConfig.plugins.length;
|
|
29
|
-
opencodeConfig.plugins = opencodeConfig.plugins.filter(
|
|
30
|
-
(p
|
|
124
|
+
const before = (opencodeConfig.plugins as unknown[]).length;
|
|
125
|
+
opencodeConfig.plugins = (opencodeConfig.plugins as unknown[]).filter(
|
|
126
|
+
(p) => p !== '@aria/connector',
|
|
31
127
|
);
|
|
32
|
-
if (opencodeConfig.plugins.length !== before) {
|
|
128
|
+
if ((opencodeConfig.plugins as unknown[]).length !== before) {
|
|
33
129
|
modified = true;
|
|
34
130
|
logs.push('Removed legacy @aria/connector reference from OpenCode plugins (was crashing on open)');
|
|
35
131
|
}
|
|
36
132
|
}
|
|
37
133
|
|
|
134
|
+
// OpenCode v2 schema: `plugin` (singular) array of absolute paths or
|
|
135
|
+
// npm names. We write absolute paths to the locally-installed plugin
|
|
136
|
+
// dirs above, so OpenCode resolves them as Node modules without any
|
|
137
|
+
// network or registry dependency.
|
|
138
|
+
const existingPlugin: unknown[] = Array.isArray(opencodeConfig.plugin)
|
|
139
|
+
? (opencodeConfig.plugin as unknown[]).slice()
|
|
140
|
+
: [];
|
|
141
|
+
const stringSet = new Set(
|
|
142
|
+
existingPlugin.filter((p): p is string => typeof p === 'string'),
|
|
143
|
+
);
|
|
144
|
+
let pluginAdded = false;
|
|
145
|
+
for (const installPath of installedPaths) {
|
|
146
|
+
if (!stringSet.has(installPath)) {
|
|
147
|
+
existingPlugin.push(installPath);
|
|
148
|
+
stringSet.add(installPath);
|
|
149
|
+
pluginAdded = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (pluginAdded) {
|
|
153
|
+
opencodeConfig.plugin = existingPlugin;
|
|
154
|
+
modified = true;
|
|
155
|
+
logs.push(`Wired ${installedPaths.length} Aria plugin(s) into OpenCode config.plugin`);
|
|
156
|
+
}
|
|
157
|
+
|
|
38
158
|
if (!opencodeConfig.agentsMdPath) {
|
|
39
159
|
opencodeConfig.agentsMdPath = path.join(homedir(), '.aria', 'AGENTS.md');
|
|
40
160
|
modified = true;
|
|
@@ -51,11 +171,11 @@ export async function connectOpenCode(config: AriaConfig): Promise<string[]> {
|
|
|
51
171
|
}
|
|
52
172
|
}
|
|
53
173
|
|
|
174
|
+
// ── Write the harness AGENTS.md ───────────────────────────────────────────
|
|
54
175
|
const ariaDir = path.join(homedir(), '.aria');
|
|
55
176
|
if (!existsSync(ariaDir)) {
|
|
56
177
|
mkdirSync(ariaDir, { recursive: true });
|
|
57
178
|
}
|
|
58
|
-
|
|
59
179
|
const ariaAgentsPath = path.join(ariaDir, 'AGENTS.md');
|
|
60
180
|
const agentsContent = buildOpenCodeAgentsMd(config);
|
|
61
181
|
writeFileSync(ariaAgentsPath, agentsContent);
|
|
@@ -70,14 +190,31 @@ export async function disconnectOpenCode(): Promise<string[]> {
|
|
|
70
190
|
|
|
71
191
|
if (existsSync(configPath)) {
|
|
72
192
|
try {
|
|
73
|
-
const opencodeConfig = JSON.parse(
|
|
193
|
+
const opencodeConfig: Record<string, unknown> = JSON.parse(
|
|
194
|
+
readFileSync(configPath, 'utf-8'),
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// Strip any locally-installed Aria plugin paths from `plugin` (singular).
|
|
198
|
+
const pluginsRoot = path.join(homedir(), '.opencode', 'plugins');
|
|
199
|
+
if (Array.isArray(opencodeConfig.plugin)) {
|
|
200
|
+
const before = (opencodeConfig.plugin as unknown[]).length;
|
|
201
|
+
opencodeConfig.plugin = (opencodeConfig.plugin as unknown[]).filter((p) => {
|
|
202
|
+
if (typeof p !== 'string') return true;
|
|
203
|
+
return !PLUGIN_NAMES.some((n) => p === path.join(pluginsRoot, n));
|
|
204
|
+
});
|
|
205
|
+
if ((opencodeConfig.plugin as unknown[]).length !== before) {
|
|
206
|
+
logs.push('Removed Aria plugin paths from OpenCode config.plugin');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Strip the legacy @aria/connector reference if present.
|
|
74
211
|
if (Array.isArray(opencodeConfig.plugins)) {
|
|
75
|
-
opencodeConfig.plugins = opencodeConfig.plugins.filter(
|
|
76
|
-
(p
|
|
212
|
+
opencodeConfig.plugins = (opencodeConfig.plugins as unknown[]).filter(
|
|
213
|
+
(p) => p !== '@aria/connector',
|
|
77
214
|
);
|
|
78
|
-
writeFileSync(configPath, JSON.stringify(opencodeConfig, null, 2));
|
|
79
|
-
logs.push('Removed @aria/connector from OpenCode plugins');
|
|
80
215
|
}
|
|
216
|
+
|
|
217
|
+
writeFileSync(configPath, JSON.stringify(opencodeConfig, null, 2));
|
|
81
218
|
} catch {
|
|
82
219
|
logs.push('Failed to update OpenCode config');
|
|
83
220
|
}
|
|
@@ -94,7 +231,7 @@ function buildOpenCodeAgentsMd(config: AriaConfig): string {
|
|
|
94
231
|
|
|
95
232
|
return `# Aria Harness — AGENTS.md
|
|
96
233
|
|
|
97
|
-
Automatically injected by @
|
|
234
|
+
Automatically injected by @aria_asi/cli. This file provides Aria's cognitive harness
|
|
98
235
|
to OpenCode sessions.
|
|
99
236
|
|
|
100
237
|
## Connected Repositories
|