merge: integrate origin/main (406 commits) into live branch

Merge upstream's paired-room stabilization, dedicated message-runtime-loop,
outbound delivery refactor (ipc-outbound-delivery / deliverFormattedCanonicalMessage),
discord module split, dashboard rework, and migrations 015-018 into our branch.

Conflict policy: prefer upstream for the heavily-refactored paired-room /
message-runtime / db cluster (it supersedes our earlier loop band-aids), keep
only orthogonal unique fixes:
- codex bundled-JS launcher fix (codex-warmup.ts)
- slot-aligned Claude+Codex usage primer (usage-primer.ts) re-wired into index.ts
- MemoryHigh=3G cgroup bound, discord perms diagnostic, prompt docs
- progress null-race + silent-failure publish guards (message-turn-controller.ts)

Dropped (superseded by upstream or unwired): reviewer STEP_DONE/TASK_DONE loop
band-aids, router markdown-escape override, register tribunal-default+git-init,
restart-context rewindToSeq (unwired).

Verified: typecheck clean, build clean, vitest shows zero new regressions vs the
origin/main baseline (only pre-existing env-config failures remain).
This commit is contained in:
Codex
2026-06-08 21:15:39 +09:00
409 changed files with 53523 additions and 10332 deletions

View File

@@ -155,7 +155,7 @@ function getStoredRoomRoleOverrideRows(
agent_config_json: string | null;
created_at: string;
updated_at: string;
}> = [];
}>;
try {
rows = database
.prepare(

View File

@@ -191,7 +191,7 @@ export function ensureLinuxReadonlySandboxAppArmorSupport(options?: {
const sysctlContents =
'# Managed by EJClaw setup to allow bubblewrap readonly sandboxing.\n' +
'kernel.apparmor_restrict_unprivileged_userns=0\n';
let existingContents: string | null = null;
let existingContents: string | null;
try {
existingContents = readFileSyncFn(sysctlPath, 'utf-8');

View File

@@ -3,7 +3,6 @@
*
* EJClaw is Discord-only, so registrations must target Discord channel IDs.
*/
import { execFileSync } from 'child_process';
import fs from 'fs';
import path from 'path';
@@ -13,45 +12,6 @@ import { isValidGroupFolder } from '../src/group-folder.js';
import { logger } from '../src/logger.js';
import { emitStatus } from './status.js';
/**
* Resolve a usable git-repo work_dir for a new tribunal room.
* Order: <folder> if .git exists, else <folder>/repo if .git exists,
* else `git init` <folder> (with an empty initial commit) and use it.
* Returns the absolute work_dir path.
*/
function ensureWorkDirForFolder(folderName: string): string {
const folderPath = path.join(GROUPS_DIR, folderName);
fs.mkdirSync(folderPath, { recursive: true });
if (fs.existsSync(path.join(folderPath, '.git'))) {
return folderPath;
}
const repoSub = path.join(folderPath, 'repo');
if (fs.existsSync(path.join(repoSub, '.git'))) {
return repoSub;
}
// No git repo found anywhere — initialize at the folder root.
const gitEnv = {
...process.env,
GIT_AUTHOR_NAME: 'ejclaw',
GIT_AUTHOR_EMAIL: 'ejclaw@local',
GIT_COMMITTER_NAME: 'ejclaw',
GIT_COMMITTER_EMAIL: 'ejclaw@local',
};
execFileSync('git', ['init', '-q'], { cwd: folderPath, env: gitEnv });
execFileSync(
'git',
['commit', '-q', '--allow-empty', '-m', `init ${folderName} workspace`],
{ cwd: folderPath, env: gitEnv },
);
logger.info(
{ folder: folderName, workDir: folderPath },
'Initialized git repository for tribunal workspace',
);
return folderPath;
}
interface RegisterArgs {
jid: string;
name: string;
@@ -142,20 +102,10 @@ export async function run(args: string[]): Promise<void> {
logger.info(parsed, 'Registering channel');
initDatabase();
// Defaults for newly registered rooms:
// roomMode = tribunal, owner = claude-code, reviewer = codex
// Tribunal rooms also require a git-backed work_dir; auto-init if missing.
const workDir = ensureWorkDirForFolder(parsed.folder);
assignRoom(parsed.jid, {
name: parsed.name,
folder: parsed.folder,
isMain: parsed.isMain,
roomMode: 'tribunal',
ownerAgentType: 'claude-code',
reviewerAgentType: 'codex',
workDir,
});
logger.info('Assigned room through canonical room service');

View File

@@ -99,7 +99,9 @@ export function restartStackServices(
throw error;
}
if (managedServiceCaller) {
throw new Error(MANAGED_SERVICE_CALLER_FALLBACK_MESSAGE);
throw new Error(MANAGED_SERVICE_CALLER_FALLBACK_MESSAGE, {
cause: error,
});
}
return restartStackServicesDirect(projectRoot, deps);

View File

@@ -15,7 +15,6 @@ import {
ensureLinuxReadonlySandboxAppArmorSupport,
getPlatform,
getNodePath,
getServiceManager,
} from './platform.js';
import { getServiceDefs } from './service-defs.js';
import { setupLaunchd, setupLinux } from './service-installers.js';
@@ -30,7 +29,6 @@ export async function run(_args: string[]): Promise<void> {
const platform = getPlatform();
const nodePath = getNodePath();
const homeDir = os.homedir();
const serviceManager = getServiceManager();
logger.info({ platform, nodePath, projectRoot }, 'Setting up service');