Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions bridges/kimaki.sh
Original file line number Diff line number Diff line change
Expand Up @@ -363,30 +363,23 @@ _kimaki_sync_bin_helpers() {

_kimaki_remove_legacy_session_helper "$HELPER_DIR"
_kimaki_remove_legacy_command_shims "$HELPER_DIR"
_kimaki_install_homeboy_notification_context_helper "$HELPER_DIR"
_kimaki_remove_obsolete_homeboy_notification_helper "$HELPER_DIR"
_kimaki_install_dispatch_helpers
}

_kimaki_install_homeboy_notification_context_helper() {
_kimaki_remove_obsolete_homeboy_notification_helper() {
local helper_dir="$1"
local source="$SCRIPT_DIR/bridges/kimaki/homeboy-notification-context.sh"
local target="$helper_dir/wp-coding-agents-homeboy-notification"

[ -f "$source" ] || return 0
[ -e "$target" ] || return 0
if [ "${DRY_RUN:-false}" = true ]; then
if ! cmp -s "$source" "$target" 2>/dev/null; then
echo -e "${BLUE}[dry-run]${NC} Would update $target"
fi
echo -e "${BLUE}[dry-run]${NC} Would remove obsolete $target"
return 0
fi

mkdir -p "$helper_dir"
if ! cmp -s "$source" "$target" 2>/dev/null; then
cp "$source" "$target"
chmod 0755 "$target"
log " Updated $target"
UPDATED_ITEMS+=("Kimaki Homeboy notification wrapper")
fi
rm -f "$target"
log " Removed obsolete $target"
UPDATED_ITEMS+=("removed obsolete wp-coding-agents-homeboy-notification helper")
}

_kimaki_install_dispatch_helpers() {
Expand Down Expand Up @@ -819,6 +812,17 @@ bridge_sync_config() {
fi

# Copy plugins to the durable target that opencode.json loads.
local obsolete_notification_plugin="$KIMAKI_PLUGINS_DIR/homeboy-notification-context.ts"
if [ -e "$obsolete_notification_plugin" ]; then
if [ "$DRY_RUN" = true ]; then
echo -e "${BLUE}[dry-run]${NC} Would remove obsolete $obsolete_notification_plugin"
else
rm -f "$obsolete_notification_plugin"
log " Removed obsolete $obsolete_notification_plugin"
UPDATED_ITEMS+=("removed obsolete kimaki-config/plugins/homeboy-notification-context.ts")
fi
fi

if [ -d "$SCRIPT_DIR/bridges/kimaki/plugins" ]; then
if [ "$DRY_RUN" = false ]; then
mkdir -p "$KIMAKI_CONFIG_DIR/plugins" 2>/dev/null || true
Expand Down Expand Up @@ -1403,6 +1407,6 @@ bridge_vps_start_preamble() {
# Verify-block addendum printed by upgrade.sh after the standard status line.
bridge_verify_extra() {
local PLUGINS_DIR="${RESOLVED_KIMAKI_PLUGINS_DIR:-/opt/kimaki-config/plugins}"
echo "test -f $PLUGINS_DIR/dm-context-filter.ts && test -f $PLUGINS_DIR/dm-agent-sync.ts && test -f $PLUGINS_DIR/homeboy-notification-context.ts # managed OpenCode plugins installed"
echo "test -f $PLUGINS_DIR/dm-context-filter.ts && test -f $PLUGINS_DIR/dm-agent-sync.ts # managed OpenCode plugins installed"
echo "command -v kimaki >/dev/null # native Kimaki binary available"
}
25 changes: 0 additions & 25 deletions bridges/kimaki/homeboy-notification-context.sh

This file was deleted.

25 changes: 0 additions & 25 deletions bridges/kimaki/plugins/homeboy-notification-context.ts

This file was deleted.

12 changes: 11 additions & 1 deletion bridges/kimaki/post-upgrade.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ else
SKILLS_DIR="/usr/lib/node_modules/kimaki/skills"
fi

REQUIRED_PLUGINS=(dm-context-filter.ts dm-agent-sync.ts homeboy-notification-context.ts)
REQUIRED_PLUGINS=(dm-context-filter.ts dm-agent-sync.ts)
WP_CODING_AGENTS_SKILLS=(upgrade-wp-coding-agents)

if [[ -n "${KIMAKI_DIST_DIR:-}" ]]; then
Expand Down Expand Up @@ -252,6 +252,16 @@ else
PLUGINS_DIR="$PLUGIN_SOURCE_DIR"
fi

# Removed in wp-coding-agents#300. Scrub managed copies from both the durable
# source and compatibility target during upgrades, including local and service installs.
for obsolete_dir in "$PLUGIN_SOURCE_DIR" "$PLUGINS_DIR"; do
obsolete_plugin="$obsolete_dir/homeboy-notification-context.ts"
if [[ -e "$obsolete_plugin" ]]; then
rm -f "$obsolete_plugin"
echo "kimaki-config: removed obsolete plugin $obsolete_plugin"
fi
done

if [[ -d "$PLUGIN_SOURCE_DIR" ]]; then
if [[ "$PLUGINS_DIR" == "$PLUGIN_SOURCE_DIR" ]]; then
echo "kimaki-config: plugin restore not needed; opencode loads persistent plugins at $PLUGIN_SOURCE_DIR"
Expand Down
14 changes: 9 additions & 5 deletions lib/repair-opencode-json.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@

--additive is the default mode called from setup.sh and upgrade.sh: it
installs managed plugin entries the user is missing (dm-context-filter
and dm-agent-sync on Kimaki bridges) and migrates legacy agent prompts
to the top-level `instructions` array (fixes Anthropic Claude Max OAuth,
see wp-coding-agents#60). It never removes user-added plugin entries.
and dm-agent-sync on Kimaki bridges), removes retired managed plugin
entries, and migrates legacy agent prompts to the top-level `instructions`
array (fixes Anthropic Claude Max OAuth, see wp-coding-agents#60). It never
removes user-added plugin entries.

--apply is the opt-in full reconciliation, used by
`upgrade.sh --repair-opencode-json`. It removes unexpected plugin
Expand All @@ -72,7 +73,8 @@
from typing import List, Tuple


MANAGED_KIMAKI_PLUGIN_NAMES = {"dm-context-filter.ts", "dm-agent-sync.ts", "homeboy-notification-context.ts"}
MANAGED_KIMAKI_PLUGIN_NAMES = {"dm-context-filter.ts", "dm-agent-sync.ts"}
OBSOLETE_KIMAKI_PLUGIN_NAMES = {"homeboy-notification-context.ts"}
DM_MEMORY_MARKER = "/datamachine-files/"
MANAGED_EDIT_RULES = (
"wp-content/plugins/**",
Expand Down Expand Up @@ -109,7 +111,6 @@ def expected_plugins(
if chat_bridge == "kimaki":
plugins.append(f"{kimaki_plugins_dir}/dm-context-filter.ts")
plugins.append(f"{kimaki_plugins_dir}/dm-agent-sync.ts")
plugins.append(f"{kimaki_plugins_dir}/homeboy-notification-context.ts")

if claude_code_auth_plugin:
plugins.append(claude_code_auth_plugin)
Expand Down Expand Up @@ -152,6 +153,9 @@ def normalize_managed_kimaki_plugin_paths(

for plugin in current:
basename = os.path.basename(plugin)
if basename in OBSOLETE_KIMAKI_PLUGIN_NAMES:
rewrites.append({"from": plugin, "to": None})
continue
replacement = plugin
if basename in MANAGED_KIMAKI_PLUGIN_NAMES:
expected = f"{plugins_dir}/{basename}"
Expand Down
4 changes: 2 additions & 2 deletions operator-entrypoints/wp-coding-agents-setup/verify.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,13 @@ Local Kimaki plugin paths:

```bash
KIMAKI_PLUGINS_DIR="$(npm root -g)/kimaki/plugins"
test -f "$KIMAKI_PLUGINS_DIR/dm-context-filter.ts" && test -f "$KIMAKI_PLUGINS_DIR/dm-agent-sync.ts" && test -f "$KIMAKI_PLUGINS_DIR/homeboy-notification-context.ts"
test -f "$KIMAKI_PLUGINS_DIR/dm-context-filter.ts" && test -f "$KIMAKI_PLUGINS_DIR/dm-agent-sync.ts"
```

VPS Kimaki plugin paths:

```bash
test -f /opt/kimaki-config/plugins/dm-context-filter.ts && test -f /opt/kimaki-config/plugins/dm-agent-sync.ts && test -f /opt/kimaki-config/plugins/homeboy-notification-context.ts
test -f /opt/kimaki-config/plugins/dm-context-filter.ts && test -f /opt/kimaki-config/plugins/dm-agent-sync.ts
```

If either plugin file is missing, rerun setup or upgrade before trusting a new OpenCode session. OpenCode silently skips missing plugin files.
Expand Down
2 changes: 0 additions & 2 deletions runtimes/opencode.sh
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,6 @@ runtime_generate_config() {
mkdir -p "$KIMAKI_PLUGINS_DIR"
cp "$SCRIPT_DIR/bridges/kimaki/plugins/dm-context-filter.ts" "$KIMAKI_PLUGINS_DIR/" 2>/dev/null || true
cp "$SCRIPT_DIR/bridges/kimaki/plugins/dm-agent-sync.ts" "$KIMAKI_PLUGINS_DIR/" 2>/dev/null || true
cp "$SCRIPT_DIR/bridges/kimaki/plugins/homeboy-notification-context.ts" "$KIMAKI_PLUGINS_DIR/" 2>/dev/null || true
fi
else
KIMAKI_PLUGINS_DIR="/opt/kimaki-config/plugins"
Expand Down Expand Up @@ -239,7 +238,6 @@ runtime_generate_config() {
if [ "$CHAT_BRIDGE" = "kimaki" ]; then
OPENCODE_PLUGINS="${OPENCODE_PLUGINS}\n \"${KIMAKI_PLUGINS_DIR}/dm-context-filter.ts\","
OPENCODE_PLUGINS="${OPENCODE_PLUGINS}\n \"${KIMAKI_PLUGINS_DIR}/dm-agent-sync.ts\","
OPENCODE_PLUGINS="${OPENCODE_PLUGINS}\n \"${KIMAKI_PLUGINS_DIR}/homeboy-notification-context.ts\","
fi
if opencode_claude_code_auth_enabled; then
OPENCODE_PLUGINS="${OPENCODE_PLUGINS}\n \"$(opencode_claude_code_auth_plugin_path)\","
Expand Down
63 changes: 1 addition & 62 deletions scripts/kimaki-managed-plugin-rig.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// cycles, and proves the OpenCode plugin hooks execute against Kimaki's live
// installed prompt renderer.

import { execFileSync, execSync, spawn } from 'node:child_process'
import { execFileSync, execSync } from 'node:child_process'
import crypto from 'node:crypto'
import fs from 'node:fs'
import os from 'node:os'
Expand All @@ -34,12 +34,10 @@ const stagedSkillsDir = path.join(kimakiConfigDir, 'skills')
const npmSkillsDir = path.join(tempRoot, 'npm-kimaki-skills')
const siteDir = path.join(tempRoot, 'site')
const homeDir = path.join(tempRoot, 'home')
const binDir = path.join(tempRoot, 'bin')

const repoKimakiDir = path.join(repoRoot, 'bridges', 'kimaki')
const repoPluginsDir = path.join(repoKimakiDir, 'plugins')
const postUpgradePath = path.join(kimakiConfigDir, 'post-upgrade.sh')
const stagedNotificationWrapper = path.join(binDir, 'wp-coding-agents-homeboy-notification')

const artifacts = {
schema: 'wp-coding-agents/kimaki-managed-plugin-rig/v1',
Expand Down Expand Up @@ -86,13 +84,9 @@ function stageManagedConfig() {
mkdirp(npmSkillsDir)
mkdirp(siteDir)
mkdirp(homeDir)
mkdirp(binDir)

copyFile(path.join(repoPluginsDir, 'dm-context-filter.ts'), path.join(stagedPluginsDir, 'dm-context-filter.ts'))
copyFile(path.join(repoPluginsDir, 'dm-agent-sync.ts'), path.join(stagedPluginsDir, 'dm-agent-sync.ts'))
copyFile(path.join(repoPluginsDir, 'homeboy-notification-context.ts'), path.join(stagedPluginsDir, 'homeboy-notification-context.ts'))
copyFile(path.join(repoKimakiDir, 'homeboy-notification-context.sh'), stagedNotificationWrapper)
fs.chmodSync(stagedNotificationWrapper, 0o755)
copyFile(path.join(repoKimakiDir, 'post-upgrade.sh'), postUpgradePath)
fs.chmodSync(postUpgradePath, 0o755)

Expand All @@ -116,7 +110,6 @@ function writeOpencodeConfig() {
plugin: [
path.join(stagedPluginsDir, 'dm-context-filter.ts'),
path.join(stagedPluginsDir, 'dm-agent-sync.ts'),
path.join(stagedPluginsDir, 'homeboy-notification-context.ts'),
],
instructions: [],
}
Expand All @@ -128,8 +121,6 @@ function recordStaticEvidence() {
artifacts.files['site/opencode.json'] = fileRecord(path.join(siteDir, 'opencode.json'))
artifacts.files['kimaki-config/plugins/dm-context-filter.ts'] = fileRecord(path.join(stagedPluginsDir, 'dm-context-filter.ts'))
artifacts.files['kimaki-config/plugins/dm-agent-sync.ts'] = fileRecord(path.join(stagedPluginsDir, 'dm-agent-sync.ts'))
artifacts.files['kimaki-config/plugins/homeboy-notification-context.ts'] = fileRecord(path.join(stagedPluginsDir, 'homeboy-notification-context.ts'))
artifacts.files['bin/wp-coding-agents-homeboy-notification'] = fileRecord(stagedNotificationWrapper)
artifacts.files['kimaki-config/post-upgrade.sh'] = fileRecord(postUpgradePath)
for (const candidate of ['skills-enable-list.txt', 'skills-disable-list.txt']) {
const file = path.join(kimakiConfigDir, candidate)
Expand Down Expand Up @@ -414,8 +405,6 @@ async function runCycle({ name, simulatePackageWipe }) {
assert(fs.existsSync(path.join(stagedSkillsDir, 'upgrade-wp-coding-agents', 'SKILL.md')), `${name}: persistent upgrade skill source remains present`, cycle)
assert(fs.existsSync(path.join(stagedPluginsDir, 'dm-context-filter.ts')), `${name}: context filter present after restart`, cycle)
assert(fs.existsSync(path.join(stagedPluginsDir, 'dm-agent-sync.ts')), `${name}: agent sync present after restart`, cycle)
assert(fs.existsSync(path.join(stagedPluginsDir, 'homeboy-notification-context.ts')), `${name}: notification adapter present after restart`, cycle)
assert(fs.existsSync(stagedNotificationWrapper), `${name}: notification wrapper survives package wipe`, cycle)

const permission = expectedSkillPermission()
assert(permission?.['*'] === 'deny', `${name}: generated skill permission denies unlisted skills`, cycle)
Expand All @@ -435,56 +424,6 @@ async function runCycle({ name, simulatePackageWipe }) {
assert(promptEvidence.joinedSystemStaleOrchestrationLeaks.length === 0, `${name}: final system transform strips Kimaki promptAsync system field`, cycle)
assert(promptEvidence.systemAndMessageTransformsAgree, `${name}: system and message transforms agree`, cycle)
assert(promptEvidence.agentSyncLoaded, `${name}: dm-agent-sync module loads`, cycle)
await assertNotificationAdapter(name, cycle)
}

async function assertNotificationAdapter(name, cycle) {
const module = await import(pathToFileURL(path.join(stagedPluginsDir, 'homeboy-notification-context.ts')).href)
const plugin = await module.default({})
const before = plugin['tool.execute.before']
if (typeof before !== 'function') {
throw new Error('homeboy-notification-context did not expose tool.execute.before')
}

const output = { args: { command: 'homeboy status' } }
await before({}, output)
assert(output.args.command === 'wp-coding-agents-homeboy-notification status', `${name}: Homeboy command is routed through invocation wrapper`, cycle)

const absent = await runNotificationWrapper({ HOMEBOY_NOTIFICATION_ROUTE: 'discord:v1:thread:11111111111111111' })
assert(absent.transport === '' && absent.route === '', `${name}: absent Kimaki attribution omits notification context`, cycle)

const [first, second] = await Promise.all([
runNotificationWrapper({ KIMAKI_THREAD_ID: '11111111111111111' }),
runNotificationWrapper({ KIMAKI_THREAD_ID: '22222222222222222' }),
])
assert(first.route === 'discord:v1:thread:11111111111111111' && second.route === 'discord:v1:thread:22222222222222222', `${name}: concurrent thread invocations retain distinct notification routes`, cycle)
}

function runNotificationWrapper(extraEnv) {
const fakeHomeboy = path.join(binDir, 'homeboy')
if (!fs.existsSync(fakeHomeboy)) {
fs.writeFileSync(fakeHomeboy, '#!/bin/sh\nprintf "%s\\n%s\\n" "${HOMEBOY_NOTIFICATION_TRANSPORT:-}" "${HOMEBOY_NOTIFICATION_ROUTE:-}"\n', 'utf8')
fs.chmodSync(fakeHomeboy, 0o755)
}
return new Promise((resolve, reject) => {
const child = spawn(stagedNotificationWrapper, ['status'], {
env: { ...process.env, ...extraEnv, PATH: `${binDir}:${process.env.PATH || ''}` },
stdio: ['ignore', 'pipe', 'pipe'],
})
let stdout = ''
let stderr = ''
child.stdout.on('data', (chunk) => { stdout += chunk })
child.stderr.on('data', (chunk) => { stderr += chunk })
child.on('error', reject)
child.on('close', (code) => {
if (code !== 0) {
reject(new Error(`notification wrapper failed (${code}): ${stderr}`))
return
}
const [transport = '', route = ''] = stdout.trimEnd().split('\n')
resolve({ transport, route })
})
})
}

function runPostUpgrade(name) {
Expand Down
5 changes: 0 additions & 5 deletions tests/kimaki-managed-plugin-rig.sh
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,6 @@ for (const cycle of manifest.cycles) {
`${cycle.name}: dm-context-filter hook executed`,
`${cycle.name}: system and message transforms agree`,
`${cycle.name}: dm-agent-sync module loads`,
`${cycle.name}: notification adapter present after restart`,
`${cycle.name}: notification wrapper survives package wipe`,
`${cycle.name}: Homeboy command is routed through invocation wrapper`,
`${cycle.name}: absent Kimaki attribution omits notification context`,
`${cycle.name}: concurrent thread invocations retain distinct notification routes`,
]) {
if (!labels.includes(expected)) {
throw new Error(`missing check: ${expected}`);
Expand Down
6 changes: 4 additions & 2 deletions tests/opencode-local-plugin-path.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ with open(opencode_json, encoding="utf-8") as handle:
expected = [
f"{kimaki_data_dir}/kimaki-config/plugins/dm-context-filter.ts",
f"{kimaki_data_dir}/kimaki-config/plugins/dm-agent-sync.ts",
f"{kimaki_data_dir}/kimaki-config/plugins/homeboy-notification-context.ts",
f"{opencode_json.rsplit('/', 1)[0]}/.opencode/plugins/claude-code-auth.ts",
]
actual = data.get("plugin")
Expand All @@ -65,6 +64,10 @@ if [ ! -f "$SITE_PATH/.opencode/plugins/claude-code-auth.ts" ]; then
echo "FAIL: default Claude Code auth plugin was not installed"
exit 1
fi
if [ -e "$KIMAKI_DATA_DIR/kimaki-config/plugins/homeboy-notification-context.ts" ]; then
echo "FAIL: fresh config generation installed obsolete notification plugin"
exit 1
fi

WITH_CLAUDE_CODE_AUTH=false
SITE_PATH="$TMP/site-without-auth"
Expand All @@ -84,7 +87,6 @@ with open(opencode_json, encoding="utf-8") as handle:
expected = [
f"{kimaki_data_dir}/kimaki-config/plugins/dm-context-filter.ts",
f"{kimaki_data_dir}/kimaki-config/plugins/dm-agent-sync.ts",
f"{kimaki_data_dir}/kimaki-config/plugins/homeboy-notification-context.ts",
]
actual = data.get("plugin")
if actual != expected:
Expand Down
Loading
Loading