refactor(x): single live homes for getErrorDetails and isPathInside

getErrorDetails was the last live import out of runtime/legacy/ that
isn't part of the code-mode / mini-apps carve-out — four knowledge
pipelines pulled it from legacy/utils.ts, blocking 'legacy deletes as
a unit'. Their agents run via the headless runner now, so the
RunFailedError unwrap branch was dead for them; the live version in
application/lib/errors.ts is the plain Error->message idiom, and the
legacy copy (with the RunFailedError coupling) is deleted as
consumerless.

isPathInside backs path-grant permission decisions, and byte-identical
copies lived in filesystem/files.ts, assembly/permission-metadata.ts,
and legacy/repo.ts — a place for a future divergence to become a
permission bypass in exactly one caller. files.ts now exports the one
live copy; permission-metadata imports it (it already imports files.ts,
so no new edge). legacy/repo.ts deliberately keeps its frozen private
copy: the quarantine must not import live modules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ramnique Singh 2026-07-10 17:54:43 +05:30
parent 7fdfa32c7e
commit 1f0cdbd366
8 changed files with 17 additions and 21 deletions

View file

@ -0,0 +1,7 @@
// One home for the error -> human-readable message idiom. The legacy runs
// engine's getErrorDetails also unwrapped RunFailedError's multi-error list;
// live callers run agents through the headless runner, whose HeadlessRunError
// carries the failure detail in .message — so this is the whole contract.
export function getErrorDetails(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}

View file

@ -68,7 +68,11 @@ const MAX_BYTES_LABEL = `${MAX_BYTES / 1024} KB`;
let knowledgeCommitTimer: ReturnType<typeof setTimeout> | null = null;
let canonicalWorkspaceRoot: string | null = null;
function isPathInside(parent: string, child: string): boolean {
// Exported as the one live containment check: permission decisions
// (assembly/permission-metadata) key on it, so a divergent copy is a
// permission-bypass risk, not a style issue. (legacy/repo.ts keeps its own
// frozen copy — the quarantine must not import live modules.)
export function isPathInside(parent: string, child: string): boolean {
const relative = path.relative(parent, child);
return relative === '' || (!!relative && !relative.startsWith('..') && !path.isAbsolute(relative));
}

View file

@ -4,7 +4,7 @@ import { google } from 'googleapis';
import { WorkDir } from '../config/config.js';
import { runWhenPossible } from '../runtime/assembly/headless-app.js';
import { getKgModel } from '../models/defaults.js';
import { getErrorDetails } from '../runtime/legacy/utils.js';
import { getErrorDetails } from '../application/lib/errors.js';
import { serviceLogger } from '../services/service_logger.js';
import { loadUserConfig, updateUserEmail } from '../config/user_config.js';
import { GoogleClientFactory } from './google-client-factory.js';

View file

@ -3,7 +3,7 @@ import path from 'path';
import { WorkDir } from '../config/config.js';
import { getKgModel } from '../models/defaults.js';
import { runWhenPossible, toolInputPaths } from '../runtime/assembly/headless-app.js';
import { getErrorDetails } from '../runtime/legacy/utils.js';
import { getErrorDetails } from '../application/lib/errors.js';
import { serviceLogger, type ServiceRunContext } from '../services/service_logger.js';
import {
loadState,

View file

@ -3,7 +3,7 @@ import path from 'path';
import { WorkDir } from '../config/config.js';
import { runWhenPossible, toolInputPaths } from '../runtime/assembly/headless-app.js';
import { getKgModel } from '../models/defaults.js';
import { getErrorDetails } from '../runtime/legacy/utils.js';
import { getErrorDetails } from '../application/lib/errors.js';
import { serviceLogger } from '../services/service_logger.js';
import { limitEventItems } from './limit_event_items.js';
import {

View file

@ -3,7 +3,7 @@ import path from 'path';
import { WorkDir } from '../config/config.js';
import { runWhenPossible, toolInputPaths } from '../runtime/assembly/headless-app.js';
import { getKgModel } from '../models/defaults.js';
import { getErrorDetails } from '../runtime/legacy/utils.js';
import { getErrorDetails } from '../application/lib/errors.js';
import { serviceLogger } from '../services/service_logger.js';
import { limitEventItems } from './limit_event_items.js';
import {

View file

@ -11,15 +11,10 @@ import { z } from "zod";
import { ToolPermissionMetadata } from "@x/shared/dist/runs.js";
import { isBlocked, extractCommandNames } from "../../application/lib/command-executor.js";
import { getFileAccessAllowList, type FileAccessGrant, type FileAccessOperation } from "../../config/security.js";
import { resolveFilePathForPermission } from "../../filesystem/files.js";
import { isPathInside, resolveFilePathForPermission } from "../../filesystem/files.js";
type ToolPermissionMetadataValue = z.infer<typeof ToolPermissionMetadata>;
function isPathInside(parent: string, child: string): boolean {
const relative = path.relative(parent, child);
return relative === '' || (!!relative && !relative.startsWith('..') && !path.isAbsolute(relative));
}
function fileGrantCoversPath(grant: FileAccessGrant, operation: FileAccessOperation, resolvedPath: string): boolean {
return grant.operation === operation && isPathInside(path.resolve(grant.pathPrefix), path.resolve(resolvedPath));
}

View file

@ -20,16 +20,6 @@ export class RunFailedError extends Error {
}
}
export function getErrorDetails(error: unknown): string {
if (error instanceof RunFailedError) {
return error.errors.join("\n\n");
}
if (error instanceof Error) {
return error.message;
}
return String(error);
}
/**
* Extract the assistant's final text response from a run's log.
* @param runId