import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import type { AgentTool } from "@gajae-code/agent-core"; import { logger } from "@gajae-code/utils"; import { isAutoresearchAuthorizedResearchPath } from "../autoresearch/git"; import { expandApplyPatchToEntries } from "../edit/modes/apply-patch"; import { GJC_SESSION_PREFIX, modeStatePath as sessionModeStatePath } from "../gjc-runtime/session-layout"; import { resolveGjcSessionForRead } from "../gjc-runtime/session-resolution"; import { ModeStateSchema } from "../gjc-runtime/state-schema"; import { getSkillManifest } from "../gjc-runtime/workflow-manifest"; import { LocalProtocolHandler, resolveLocalUrlToPath } from "../internal-urls/local-protocol"; import { resolveToCwd } from "../tools/path-utils"; import { ToolError } from "../tools/tool-errors"; import { listActiveSkills, readVisibleSkillActiveState, type SkillActiveEntry } from "./active-state"; import { type CanonicalGjcWorkflowSkill, sanctionedWorkflowStateCommand, workflowModeStateFileName, } from "./workflow-state-contract"; export const DEEP_INTERVIEW_MUTATION_BLOCK_MESSAGE = "Deep-interview phase boundary: continue gathering context/questions/risks and emit a handoff/spec before code edits. Mutation tools and patch execution are blocked while deep-interview is active; finalize specs through `gjc deep-interview --write --stage final` or hand off to an execution phase."; export const WORKFLOW_STATE_MUTATION_BLOCK_MESSAGE = ".gjc workflow state and artifacts are runtime-owned. Agent mutation tools cannot edit `.gjc/**`; use the sanctioned `gjc` CLI instead."; export const RALPLAN_MUTATION_BLOCK_MESSAGE = "Ralplan planning phase boundary: keep refining the consensus plan and persist plan artifacts through `gjc ralplan --write` (stage scratch files under a temp dir if needed). Product-code mutation tools and patch execution are blocked while ralplan is active; mutate only after the plan is approved and execution begins."; export const ULTRAGOAL_GOAL_PLANNING_MUTATION_BLOCK_MESSAGE = "Ultragoal goal-planning phase boundary: finish goal planning and record goals through `gjc ultragoal` before editing code. Product-code mutation tools and patch execution are blocked until goal planning completes and execution begins."; export const AUTORESEARCH_MUTATION_BLOCK_MESSAGE = "Autoresearch research-only boundary: this workflow produces findings, evidence, and a verdict — never product code. Product-code mutation tools and patch execution are blocked everywhere, on every branch. Mission research artifacts (`autoresearch.sh` at the workdir root and mission state through the sanctioned `gjc autoresearch` CLI) stay writable; run experiments through the `python` tool, or finish the mission and hand off to an approved implementation workflow."; /** Resolve the phase-boundary block message for the active planning skill. */ function planningPhaseBlockMessage(skill: CanonicalGjcWorkflowSkill): string { if (skill === "ralplan") return RALPLAN_MUTATION_BLOCK_MESSAGE; if (skill === "autoresearch") return AUTORESEARCH_MUTATION_BLOCK_MESSAGE; if (skill === "ultragoal") return ULTRAGOAL_GOAL_PLANNING_MUTATION_BLOCK_MESSAGE; return DEEP_INTERVIEW_MUTATION_BLOCK_MESSAGE; } const BLOCKED_TOOL_NAMES = new Set(["edit", "write", "ast_edit", "bash"]); /** * Only `/dev/null` is exempt. `/dev/stdout`, `/dev/stderr`, and `/dev/fd/` are descriptor * aliases: `exec 1<>src/product.ts; printf x >/dev/stdout` reaches a real repository file * through a rebound descriptor, so they must stay blocked. */ const DEVICE_SINK_PATHS = new Set(["/dev/null"]); /** Bash write forms the plain `>`/`>>` scanner misses: clobber, dup-to-path, and read-write open. */ const BASH_EXTENDED_WRITE_RE = /(?:>\||<>|>&(?![\s;&|]*(?:\d+|-)(?:[\s;&|]|$)))\s*([^\s;&|]+)/g; /** Descriptor rebinding is not statically resolvable; any `exec` redirection forces a fail-closed verdict. */ const BASH_EXEC_REDIRECT_RE = /(?:^|[;&|\n])\s*(?:sudo\s+)?exec\b[^;&|\n]*[<>]/i; const ARCHIVE_OR_SQLITE_BASE_RE = /^(.+?\.(?:tar\.gz|sqlite3|sqlite|db3|zip|tgz|tar|db))(?:$|:)/i; const INTERNAL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i; const VIM_FILE_SWITCH_RE = /^\s*:(?:e|e!|edit|edit!)(?:\s+([^<\r\n]+))?(?:|\r|\n|$)/i; const BASH_MUTATION_COMMAND_RE = /(?:^|[;&|\n])\s*(?:\w+=[^\s]+\s+)*(?:sudo\s+)?(?:[^\s;&|]*\/)?(?:tee|touch|rm|mkdir|cp|mv|install|truncate)\b([^;&|\n]*)|(?:^|[^<>])(?:>>?|\d>>?)\s*([^\s;&|]+)/gi; const BASH_IN_PLACE_MUTATION_COMMAND_RE = /(?:^|[;&|\n])\s*(?:\w+=[^\s]+\s+)*(?:sudo\s+)?(?:sed|perl)\b([^;&|\n]*)/gi; const BASH_OPAQUE_INTERPRETER_WRITE_RE = /(?:^|[;&|\n])\s*(?:\w+=[^\s]+\s+)*(?:sudo\s+)?(?:python3?|node|ruby)\b[^;&|\n]*(?:-c|-e)\b[^;&|\n]*(?:open\s*\(|writeFile(?:Sync)?\s*\(|\.write\s*\()/i; const BASH_HEREDOC_OPAQUE_INTERPRETER_WRITE_RE = /(?:^|[;&|\n])\s*(?:\w+=[^\s]+\s+)*(?:sudo\s+)?(?:python3?|node|ruby)\b[^;&|\n]*(?:<<[-]?\s*['"]?\w+['"]?)[\s\S]*(?:open\s*\(|writeFile(?:Sync)?\s*\(|\.write\s*\()/i; const BASH_DD_OUTPUT_RE = /(?:^|[;&|\n])\s*(?:\w+=[^\s]+\s+)*(?:sudo\s+)?(?:[^\s;&|]*\/)?dd\b([^;&|\n]*)/gi; /** Literal `sh|bash|zsh -c '