Files
NetMesh/infrastructure/ai/harness/builtinSkillRunner.ts
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

153 lines
4.2 KiB
TypeScript

/**
* Runner for built-in diagnostic skills.
*
* Fetches the skill by id, filters its steps for the target session's shell
* family, executes each step sequentially via the bridge, and assembles a
* single structured report with labels, stdout/stderr, and exit codes.
*/
import { getBuiltinSkill, filterStepsForShell, type SkillStep } from './builtinSkills';
import type { ToolDeps, ToolExecResult } from '../shared/toolExecutors';
interface RunOptions {
chatSessionId?: string;
}
interface StepResult {
label: string;
command: string;
stdout: string;
stderr: string;
exitCode: number | null;
durationMs: number;
ok: boolean;
}
export interface SkillRunReport {
skill: string;
sessionId: string;
shellType?: string;
os?: string;
hostname?: string;
startedAt: string;
completedAt: string;
durationMs: number;
steps: StepResult[];
}
export async function executeSkillRun(
deps: ToolDeps,
args: Record<string, unknown>,
options: RunOptions = {},
): Promise<ToolExecResult<SkillRunReport>> {
const { bridge, context, permissionMode } = deps;
const resolveContext = () => (typeof context === 'function' ? context() : context);
const ctx = resolveContext();
const sessionId = typeof args.sessionId === 'string' ? args.sessionId.trim() : '';
const skillName = typeof args.skillName === 'string' ? args.skillName.trim() : '';
if (!sessionId) {
return { ok: false, error: 'Missing sessionId.' };
}
if (!skillName) {
return { ok: false, error: 'Missing skillName.' };
}
if (permissionMode === 'observer') {
return { ok: false, error: 'Observer mode: skill execution is disabled. Switch to Confirm or Auto mode.' };
}
// Validate session is in scope
const session = ctx.sessions.find(s => s.sessionId === sessionId);
if (!session) {
return { ok: false, error: `Session "${sessionId}" is not in the current AI scope.` };
}
// Resolve skill
const skill = getBuiltinSkill(skillName);
if (!skill) {
const available = Object.keys((await import('./builtinSkills')).BUILTIN_SKILLS).join(', ');
return { ok: false, error: `Unknown skill "${skillName}". Available: ${available}` };
}
// Filter steps for this shell family
const steps = filterStepsForShell(skill, session.shellType);
if (steps.length === 0) {
return {
ok: false,
error: `Skill "${skill.id}" has no steps for shell family "${session.shellType ?? 'unknown'}".`,
};
}
// Run each step sequentially, capture everything
const startedAt = new Date();
const stepResults: StepResult[] = [];
for (const step of steps) {
const stepResult = await runStep(bridge, sessionId, step, options.chatSessionId);
stepResults.push(stepResult);
}
const completedAt = new Date();
const report: SkillRunReport = {
skill: skill.id,
sessionId,
shellType: session.shellType,
os: session.os,
hostname: session.hostname,
startedAt: startedAt.toISOString(),
completedAt: completedAt.toISOString(),
durationMs: completedAt.getTime() - startedAt.getTime(),
steps: stepResults,
};
return { ok: true, data: report };
}
async function runStep(
bridge: NonNullable<ToolDeps['bridge']>,
sessionId: string,
step: SkillStep,
chatSessionId?: string,
): Promise<StepResult> {
const t0 = Date.now();
try {
const result = await bridge.aiExec(sessionId, step.command, chatSessionId);
const durationMs = Date.now() - t0;
if (!result.ok && result.error) {
return {
label: step.label,
command: step.command,
stdout: result.stdout || '',
stderr: `[exec error] ${result.error}`,
exitCode: null,
durationMs,
ok: false,
};
}
return {
label: step.label,
command: step.command,
stdout: result.stdout || '',
stderr: result.stderr || '',
exitCode: result.exitCode ?? -1,
durationMs,
ok: (result.exitCode ?? 0) === 0,
};
} catch (err) {
const durationMs = Date.now() - t0;
return {
label: step.label,
command: step.command,
stdout: '',
stderr: `[exception] ${err instanceof Error ? err.message : String(err)}`,
exitCode: null,
durationMs,
ok: false,
};
}
}