Files
Dirigent/plugin/core/padded-cell.ts
hzhang b5196e972c feat: rewrite plugin as v2 with globalThis-based turn management
Complete rewrite of the Dirigent plugin turn management system to work
correctly with OpenClaw's VM-context-per-session architecture:

- All turn state stored on globalThis (persists across VM context hot-reloads)
- Hooks registered unconditionally on every api instance; event-level dedup
  (runId Set for agent_end, WeakSet for before_model_resolve) prevents
  double-processing
- Gateway lifecycle events (gateway_start/stop) guarded once via globalThis flag
- Shared initializingChannels lock prevents concurrent channel init across VM
  contexts in message_received and before_model_resolve
- New ChannelStore and IdentityRegistry replace old policy/session-state modules
- Added agent_end hook with tail-match polling for Discord delivery confirmation
- Added web control page, padded-cell auto-scan, discussion tool support
- Removed obsolete v1 modules: channel-resolver, channel-modes, discussion-service,
  session-state, turn-bootstrap, policy/store, rules, decision-input

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 22:41:25 +01:00

60 lines
1.8 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import type { IdentityRegistry } from "./identity-registry.js";
type EgoData = {
columns?: string[];
agentScope?: Record<string, Record<string, string>>;
};
/**
* Scan padded-cell's ego.json and upsert agent Discord IDs into the identity registry.
* Only runs if ego.json contains the "discord-id" column — otherwise treated as absent.
*
* @returns number of entries upserted, or -1 if padded-cell is not detected.
*/
export function scanPaddedCell(
registry: IdentityRegistry,
openclawDir: string,
logger: { info: (m: string) => void; warn: (m: string) => void },
): number {
const egoPath = path.join(openclawDir, "ego.json");
if (!fs.existsSync(egoPath)) {
logger.info("dirigent: padded-cell ego.json not found — skipping auto-registration");
return -1;
}
let ego: EgoData;
try {
ego = JSON.parse(fs.readFileSync(egoPath, "utf8"));
} catch (e) {
logger.warn(`dirigent: failed to parse ego.json: ${String(e)}`);
return -1;
}
if (!Array.isArray(ego.columns) || !ego.columns.includes("discord-id")) {
logger.info('dirigent: ego.json does not have "discord-id" column — padded-cell not configured for Discord, skipping');
return -1;
}
const agentScope = ego.agentScope ?? {};
let count = 0;
for (const [agentId, fields] of Object.entries(agentScope)) {
const discordUserId = fields["discord-id"];
if (!discordUserId || typeof discordUserId !== "string") continue;
const existing = registry.findByAgentId(agentId);
registry.upsert({
agentId,
discordUserId,
agentName: existing?.agentName ?? agentId,
});
count++;
}
logger.info(`dirigent: padded-cell scan complete — upserted ${count} identity entries`);
return count;
}