Something went wrong. Try again.
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019/** * Default wiring — the one async canonical bootstrap (saga d4ks8c.7; ARCHITECTURE §4/§5/§15). * * This is the single default startup path. It: * - independently discovers typed resources (SkillRegistry, ContextFileSnapshot, * AgentDefinitionSnapshot, PermissionRuleSnapshot) and dynamic extension sources, * aggregating typed diagnostics WITHOUT flattening them into `ResourceRecord`; * - constructs an `ExtensionHost` and installs bundled tool/mode/compactor extensions * through `host.install`, registers `recall` and `load_skill` as `ExtensionFunction`s * (the recall factory seam captures a `SessionStore`), and loads trusted dynamic * extensions through `ExtensionLoader`. `ExtensionApi` is the EXCLUSIVE live capability * registration surface — built-ins are never registered directly into * `DefaultToolRegistry`/`ModeController` anymore; * - builds the runtime `ToolRegistry`/`ModeController`/compactor/provider selection from * the host snapshot only; * - applies specialized permission rules in first-match order together with explicitly * supplied `opts.permissions.rules`, constructing `RealPermissionGate` when either is * present (else `AllowAllGate`); * - composes the BASE system prompt in pinned order (base identity → layered context → * available-skills catalog) that `AgentSession` then extends * (active mode → durable activated-skill bodies → latest plan → compaction handoff). * * Provider/model + compaction options are preserved (backward compatible). Resource * discovery is controlled by `opts.resources`: when absent the bootstrap stays hermetic * (pure built-in wiring, empty typed snapshots) so unit/seam tests stay deterministic; the * real CLI/SDK startup paths inject `resources` to make every completed capability live. * * No hardcoded secrets: API keys come from the `apiKey` param or `OPENAI_API_KEY` env * (the provider throws if neither is set). */import type { AgentSession } from '@/core/session/agent-session';import type { Rule } from '@/contracts/decision';import type { PermissionGate } from '@/contracts/permission-gate';import type { Compactor } from '@/contracts/compactor';import type { CodeIntelligenceProvider } from '@/contracts/code-intelligence';import type { Provider } from '@/contracts/provider';import type { ModelDescriptor } from '@/contracts/model';import type { ToolRegistry } from '@/contracts/tool-registry';import type { ExtensionFunction, ExtensionIdentity, ExtensionLoadDiagnostic, ExtensionTrustGate,} from '@/contracts/extension-api';import type { ManagedContext, CapabilityPolicy,} from '@/contracts/managed-context';import type { BootstrapContributionFactory } from '@/contracts/bootstrap-contribution';import { validateToolAllowlist, validateSkillAllowlist,} from '@/contracts/managed-context';import type { SkillRegistry, SkillRegistrySnapshot, SkillDiagnostic,} from '@/contracts/skill';import type { ContextFileSnapshot, ContextFileDiagnostic, ContextTrustGate,} from '@/contracts/context-file';import type { AgentDefinitionSnapshot, AgentDefinitionDiagnostic, AgentDefinitionTrustGate,} from '@/contracts/agent-definition';import type { PermissionRuleSnapshot, PermissionRuleDiagnostic, PermissionRuleTrustGate,} from '@/contracts/permission-rule';import { homedir } from 'node:os';import { join } from 'node:path';import { AgentSession as AgentSessionClass } from '@/core/session/agent-session';import { OpenAIProvider } from '@/core/provider/openai';import { OpenAIResponsesProvider } from '@/core/provider/openai-responses';import { DefaultToolRegistry } from '@/core/tools/registry';import { AllowAllGate } from '@/core/permission/gate';import { RealPermissionGate } from '@/core/permission/real-gate';import { InProcessEventBus } from '@/core/event-bus';import { SessionStore } from '@/core/session/store';import { ModeController } from '@/core/modes/controller';import { generateId } from '@/core/id';import { ThresholdCompactionTrigger } from '@/core/compactor/trigger';import type { CompactionConfig } from '@/contracts/compaction-trigger';import { DEFAULT_CONTEXT_WINDOW } from '@/contracts/compaction-trigger';import { ExtensionHost } from '@/core/extensions/host';import { builtinToolsExtension, builtinModesExtension, builtinCompactorExtension,} from '@/core/extensions/builtin';import { ExtensionLoader, type ExtensionImporter, type ExtensionLoaderFs,} from '@/core/extensions/loader';import { createLoadSkillExtension } from '@/core/tools/load-skill';import { createRecallExtension } from '@/core/tools/recall';import { FilteredToolRegistry } from '@/core/tools/filtered-registry';import { FilteredSkillRegistry } from '@/core/agent-skills/filtered-registry';import { loadSkillRegistry } from '@/core/agent-skills/loader';import { DefaultSkillRegistry } from '@/core/agent-skills/registry';import { formatSkillCatalog } from '@/core/agent-skills/catalog';import { ContextFileLoader, AgentDefinitionLoader, PermissionRuleLoader, defaultAgentDefinitionUserRoots,} from '@/core/resources';import { nodeResourceFilesystem, type ResourceFilesystem } from '@/core/resources/filesystem';import { defaultProjectBoundaryResolver, type ProjectBoundaryResolver,} from '@/core/project-boundary';
/** Reasonable default system prompt for a coding agent. */export const DEFAULT_SYSTEM_PROMPT = 'You are Shard, a coding agent. Use tools to help the user.';
/** Mode names accepted by the default wiring. */export type DefaultMode = 'plan' | 'build';
/** Resolved triple the provider consumes. Mirrors the L4 `ResolvedModel` shape (L2 * can't import L4 — `CORE_FORBIDDEN` eslint rule — so the type is local). The L4 resolver * (`resolveModel` in `@/cli/config/resolve`) returns `ResolvedModel`; the CLI adapts it * to this triple before injecting via `resolveProvider`. */export interface ResolvedProviderTriple { /** Model id sent in the request body's `model` field. */ model: string; /** Authentication mode; omitted preserves the legacy bearer default. */ auth?: 'bearer' | 'none'; /** Bearer token, or undefined for none auth. */ apiKey?: string; /** Resolved base URL (trailing slash already stripped by the resolver). */ baseURL?: string; /** * Wire adapter (saga hihsjg). `'responses'` → OpenAI Responses API provider (readable * reasoning summaries); `'chat-completions'` → the OpenAI-compatible provider. Defaults to * `'chat-completions'` when omitted (the historical behavior, safe for compat servers). */ api?: 'chat-completions' | 'responses'; /** Whether the model is a reasoning model (saga hihsjg); opts the Responses provider * into reasoning summaries + the developer role. Ignored for chat-completions. */ reasoning?: boolean; /** * The resolved effective reasoning effort (saga p7m8jp.1). Surfaced so the provider * factory can pass it to the Responses / Chat Completions provider. When absent the * provider applies its own adapter default (Responses currently uses `medium`). */ reasoningEffort?: string; /** * The supported reasoning effort IDs (saga p7m8jp.1). Surfaced so the runtime * authority can serve `list_reasoning_levels` and advertise capabilities. When absent * the model has no selectable effort surface. */ reasoningEfforts?: string[];}
/** * Injected L4 resolver — given {providerKey, modelName}, returns the resolved triple. * The CLI injects `resolveModel` from `@/cli/config` (L4); L2 cannot import it directly * (ARCHITECTURE §16 layer rule), so injection keeps the boundary intact. */export type ProviderResolver = (input: { providerKey?: string; modelName?: string;}) => ResolvedProviderTriple;
/** * Resource discovery options for the canonical bootstrap (d4ks8c.7). When `resources` is * absent on {@link CreateAgentSessionOptions}, discovery is SKIPPED (hermetic, pure * built-in wiring) — the historic `createDefaultCore` behavior unit/seam tests rely on. * The real CLI/SDK startup paths pass `resources` to make every completed capability live. * * Roots are injected (never hard-coded). Precedence is fixed per loader: skills * bundled → user (`~/.shard/skills` + `~/.agents/skills`) → project * (`.shard/skills` + `.agents/skills` up to the git root); context/agent/rule/extension * bundled → user → project (least-specific ancestor → cwd). Project instruction/code * resources require affirmative trust: `projectTrusted` defaults to `false`, so project * SHARD.md/AGENTS.md, agent definitions, `allow` permission rules, skills, and executable * extensions are filtered/skipped (with diagnostics, no code executed) unless the caller * opts in. Bundled/user sources are trusted by being explicitly configured. */export interface BootstrapResourcesOptions { /** Project cwd. Defaults to `process.cwd()`. Ancestors up to the git root are scanned. */ cwd?: string; /** User home; enables user-layer discovery (`~/.shard/...`, `~/.agents/skills`). */ userHome?: string; /** Bundled roots — ship defaults, lowest precedence. Applied to every specialized loader. */ bundledRoots?: string[]; /** User roots for the context/rule loaders (and the agent loader, for backward * compatibility, when {@link userAgentRoots} is unset). Default `[<userHome>/.shard]`. */ userRoots?: string[]; /** * User roots for the AGENT-DEFINITION loader only. Definitions are user-authored config, * so their authoritative home is the XDG config dir — not the `~/.shard` state home, which * also holds per-agent session data. Default: {@link defaultAgentDefinitionUserRoots} * (legacy `~/.shard/agents` fallback first, `~/.config/shard/agents` second — config-dir * definitions win same-name collisions). Explicit {@link userRoots} still feeds the agent * loader when this is unset (backward compatibility for callers pinning shared roots). */ userAgentRoots?: string[]; /** User extension roots. Default `[<userHome>/.shard]` when `userHome` set. */ userExtensionRoots?: string[]; /** Enable `AGENTS.md` compatibility in addition to `SHARD.md`. Default `true`. */ agentsMd?: boolean; /** Affirmative project trust. Default `false` (project resources filtered, no code exec). */ projectTrusted?: boolean; /** Trust gate consulted for project extension sources only. Defaults to `projectTrusted`. */ extensionTrustGate?: ExtensionTrustGate; /** Filesystem seam for resource discovery; defaults to `nodeResourceFilesystem()`. */ fs?: ResourceFilesystem; /** Filesystem seam for dynamic extension discovery; defaults to `nodeExtensionLoaderFs()`. */ extensionFs?: ExtensionLoaderFs; /** * Injectable project-boundary resolver shared across ALL loaders (skills, context, agent * definitions, permission rules, dynamic extensions). When present its canonical top-level * is authoritative for every ancestor walk (a linked worktree's worktree root wins). The * canonical bootstrap defaults to the Git-backed {@link defaultProjectBoundaryResolver} on * the PRODUCTION real-filesystem path (see {@link effectiveBoundaryResolver}); a hermetic * test that injects a fake `fs` stays on the cheap filesystem `.git`-file-or-dir fallback so * no Git is spawned. Explicit injection always wins. */ boundaryResolver?: ProjectBoundaryResolver; /** Injectable module importer for hermetic extension-loader tests. */ importer?: ExtensionImporter; /** Injectable skill registry (a fresh one is created when omitted). */ skillRegistry?: SkillRegistry;}
/** Options for `createAgentSession`. */export interface CreateAgentSessionOptions { /** Filesystem path to the session JSONL file (parent dir is mkdir -p'd). */ sessionPath: string; /** Working directory for tools. Defaults to `process.cwd()`. */ cwd?: string; /** Agent id. Generated via `generateId()` if not supplied. */ agentId?: string; /** Durable session id when it differs from the runtime agent id. */ sessionId?: string; /** Active mode at construction. Defaults to `build`. */ mode?: DefaultMode; /** Base identity system prompt. Defaults to `DEFAULT_SYSTEM_PROMPT`. Layered context + the * available-skills catalog are appended by the bootstrap (saga d4ks8c.7). */ systemPrompt?: string; /** * Opt-in permission policy. When set OR discovered rules exist, `createDefaultCore` * constructs a `RealPermissionGate` sharing the core's `eventBus` + `modeController`, * with grants persisted under `~/.shard/agents/<agentId>/`. Absent and no discovered * rules → `AllowAllGate` (no regression). For a managed root, pinned managed rules are * authoritative and evaluated first; otherwise explicit `opts.permissions.rules` are first, * followed by discovered disk rules in their first-match order. */ permissions?: { rules?: Rule[] };
/** * Compaction wiring (gy5n0k.7, saga dfcvo6). When set, `createDefaultCore` wires a * compactor (from the host snapshot) + `ThresholdCompactionTrigger` with this config * (defaults applied via `applyCompactionDefaults`). Absent → compaction disabled. */ compaction?: Partial<CompactionConfig>;
// ── Provider/model resolution (one of the two paths below) ─────────────────────
/** Explicit triple — test escape hatch. @deprecated for new callers. */ model?: string; /** API key override (falls back to `OPENAI_API_KEY` env inside the provider). */ apiKey?: string; /** Authentication mode; omitted preserves the legacy bearer default. */ auth?: 'bearer' | 'none'; /** Base URL override (falls back to `OPENAI_BASE_URL` env inside the provider). */ baseURL?: string; /** * Wire adapter (saga hihsjg). `'responses'` selects the OpenAI Responses API provider * (readable reasoning summaries for gpt-5 / o-series); the default `'chat-completions'` * keeps the OpenAI-compatible provider. Optional; the CLI surfaces the resolver's * choice, SDK callers can set it explicitly. */ api?: 'chat-completions' | 'responses'; /** Whether the model is a reasoning model (saga hihsjg); opts the Responses provider * into reasoning summaries + the developer role. Optional. */ reasoning?: boolean; /** * The resolved effective reasoning effort (saga p7m8jp.1). Passed to the provider * factory. When absent the provider applies its own adapter default. */ reasoningEffort?: string; /** * The supported reasoning effort IDs (saga p7m8jp.1). Carried so the runtime authority * can serve `list_reasoning_levels` and advertise capabilities. Optional. */ reasoningEfforts?: string[]; /** Provider key + model alias — resolved via `resolveProvider`. */ providerKey?: string; modelName?: string; /** Injected L4 resolver. Required when `model` is not set. */ resolveProvider?: ProviderResolver;
/** * Resource discovery control (d4ks8c.7). Absent → hermetic (no discovery, pure built-in * wiring). The CLI/SDK pass this to make discovered skills/context/agents/rules/extensions * live through the canonical bootstrap. */ resources?: BootstrapResourcesOptions;
/** * Managed bootstrap context (saga u4uo9m.3). When set, the bootstrap carries the daemon * context for later L3 extension injection and applies the capability policy. Only an actual * child (`capability.is_child`) receives the fresh read-only child permission gate; roots * retain normal root permission behavior. */ managed?: ManagedBootstrapOptions;
/** * Generic injected bootstrap-contribution seam (saga u4uo9m.3 plan item 5 / transport * layering). Each entry is installed through the `ExtensionHost` BEFORE the capability * snapshot is taken, so registered tools/modes/compactors are live and subject to the * capability policy. L4 (the CLI) builds these with the managed context captured in * their closures — L2 never imports L3. Absent → no extra extensions (the historic * built-in + dynamic-discovery wiring). */ extensions?: ReadonlyArray<BootstrapExtension>;
/** * Generic injected bootstrap-contribution FACTORY (saga u4uo9m.5 plan item 1). Unlike * {@link extensions} (which capture their own data in closures the L4 builds BEFORE this * bootstrap runs), a contribution factory receives the trusted snapshots DISCOVERED BY * this bootstrap plus the pinned {@link ManagedContext}. The canonical bootstrap invokes * it after resource discovery and installs the returned extensions through the * `ExtensionHost` BEFORE the capability snapshot. The concrete L3 implementation lives in * `src/subagent/**` and is imported + injected by the L4 CLI; the core never imports L3. * Consulted when {@link managed} is present, including daemon-backed roots. Absent → no * contribution extensions. */ contributionFactory?: BootstrapContributionFactory;}
/** * A bootstrap-time extension the caller injects through the `ExtensionHost` before the * capability snapshot is taken (saga u4uo9m.3). Distinct from dynamic project extensions * (which are code-execution-discovered + trust-gated): these are explicitly supplied by the * L4 wiring (e.g. the bundled L3 subagent tools in saga u4uo9m.5) and trusted by being * explicitly injected. */export interface BootstrapExtension { identity: ExtensionIdentity; install: ExtensionFunction;}
/** * Managed bootstrap options (saga u4uo9m.3). The CLI populates this from a validated * `AgentManifestV1`; L2 consumes only the plain values (no L4 import). The capability's * `is_child` flag determines whether child isolation applies; daemon-backed roots use the * same context without the child gate. */export interface ManagedBootstrapOptions { /** Managed context carried on {@link DefaultCore.managed} for L3 injection. */ context: ManagedContext; /** Capability policy (tool/skill filtering + whether this is an isolated child). */ capability: CapabilityPolicy; /** * Per-child grant directory for the fresh child `RealPermissionGate`. Defaults to * `~/.shard/agents/<agentId>/` (the historic per-agent dir); the daemon may pin a * daemon-home-relative path so grants are isolated from direct-run agents. */ childGrantBaseDir?: string; /** Optional permission rules pinned from the manifest/definition (evaluated first). */ rules?: Rule[];}
/** Aggregated typed resource diagnostics observable on {@link DefaultCore} (d4ks8c.7). */export interface BootstrapDiagnostics { readonly skills: ReadonlyArray<SkillDiagnostic>; readonly context: ReadonlyArray<ContextFileDiagnostic>; readonly agents: ReadonlyArray<AgentDefinitionDiagnostic>; readonly rules: ReadonlyArray<PermissionRuleDiagnostic>; readonly extensions: ReadonlyArray<ExtensionLoadDiagnostic>; /** Host registration diagnostics (tools/modes/compactors/providers/…). */ readonly host: ReadonlyArray<import('@/contracts/extension-api').RegistrationDiagnostic>;}
/** The individually-constructed L2 pieces, for callers that want direct access. */export interface DefaultCore { /** Active provider resolved from the authoritative ExtensionHost snapshot. */ provider: Provider; /** * Tool registry exposed to the session. A `DefaultToolRegistry` on the direct/test path; * a {@link FilteredToolRegistry} wrapping it when a managed capability policy is in effect * (saga u4uo9m.3). Typed as the `ToolRegistry` seam so callers don't depend on the concrete * class. */ registry: ToolRegistry; gate: PermissionGate; modeController: ModeController; sessionStore: SessionStore; eventBus: InProcessEventBus; agentId: string; sessionId?: string; cwd: string; systemPrompt: string; /** Compactor (from the host snapshot); undefined when compaction is disabled. */ compactor?: Compactor; /** Compaction trigger; undefined when compaction is disabled. */ compactionTrigger?: ThresholdCompactionTrigger; /** Compaction config (decision Q3); undefined when compaction is disabled. */ compactionConfig?: CompactionConfig; /** Active optional structural code intelligence provider. */ codeIndex?: CodeIntelligenceProvider; /** The authoritative `ExtensionHost` (single live capability surface). */ host: ExtensionHost; /** Typed skill registry snapshot (discovered Agent Skills). Empty when no resources. */ skills: SkillRegistrySnapshot; /** Layered context-file snapshot (SHARD.md / AGENTS.md). Empty when no resources. */ context: ContextFileSnapshot; /** Typed agent-definition snapshot (for later subagent use). Empty when no resources. */ agentDefinitions: AgentDefinitionSnapshot; /** Typed permission-rule snapshot (ordered first-match). Empty when no resources. */ permissionRules: PermissionRuleSnapshot; /** * Managed bootstrap context (saga u4uo9m.3). `undefined` for the direct/test path. * Carried as opaque data for later L3 extension injection (the bundled subagent tools * connect back to the daemon over `context.daemon_socket` and enforce * `context.launch_policy`/`context.depth`) without L2 importing L3. */ managed?: ManagedContext; /** Aggregated typed diagnostics across every specialized loader + the host. */ diagnostics: BootstrapDiagnostics;}
/** * Fill the documented compaction defaults (saga dfcvo6): `enabled` → true, * `reserveTokens` → ~20% of `contextWindow`, `keepRecentTokens` → 8192. `contextWindow` * is required on the partial. Explicit values pass through unchanged. */export function applyCompactionDefaults(partial: Partial<CompactionConfig>): CompactionConfig { if (typeof partial.contextWindow !== 'number' || partial.contextWindow <= 0) { throw new Error( `shard: compaction.contextWindow must be a positive number (got ${JSON.stringify(partial.contextWindow)})`, ); } const contextWindow = partial.contextWindow; return { contextWindow, enabled: partial.enabled ?? true, reserveTokens: partial.reserveTokens ?? Math.round(contextWindow * 0.2), keepRecentTokens: partial.keepRecentTokens ?? 8192, };}
/** The fallback context window when neither a per-model nor global override is set. */export { DEFAULT_CONTEXT_WINDOW };
// ── Trust-gate helpers ─────────────────────────────────────────────────────────
/** A constant trust gate that always returns the injected `trusted` decision. */function constantContextTrustGate(trusted: boolean): ContextTrustGate { return () => ({ trusted });}function constantAgentTrustGate(trusted: boolean): AgentDefinitionTrustGate { return () => ({ trusted });}function constantRuleTrustGate(trusted: boolean): PermissionRuleTrustGate { return () => ({ trusted });}function constantExtensionTrustGate(trusted: boolean): ExtensionTrustGate { return () => ({ trusted });}
/** Identity used when the bootstrap registers the resolved provider through the host. */const PROVIDER_IDENTITY: ExtensionIdentity = { id: 'shard.builtin.provider', name: 'OpenAI provider', version: '0.1.0',};
const SKILLS_IDENTITY: ExtensionIdentity = { id: 'shard.skills', name: 'Agent Skills',};
// ── Typed resource discovery ──────────────────────────────────────────────────
/** Empty skill registry snapshot (used when discovery is skipped). */const EMPTY_SKILL_SNAPSHOT: SkillRegistrySnapshot = { skills: [], diagnostics: [], catalog: [] };
/** * Resolve the EFFECTIVE project-boundary resolver for a resource-discovery config: * - an explicitly injected {@link BootstrapResourcesOptions.boundaryResolver} always wins; * - otherwise, on the PRODUCTION real-filesystem path (no fake `fs` seam injected), default * to the Git-backed {@link defaultProjectBoundaryResolver} so CLI/SDK and every canonical * caller share ONE canonical resolver across generic/specialized/skills/extensions walks; * - otherwise (a hermetic fake-fs test) return `undefined` so discovery stays on the cheap * filesystem `.git`-file-or-dir fallback and NEVER spawns Git. * * `stopAtGitRoot:false` still must not invoke/obey the resolver — that opt-out is enforced in * {@link resolveProjectAncestors}, which only consults the resolver when stopping is enabled. */export function effectiveBoundaryResolver( resources: BootstrapResourcesOptions,): ProjectBoundaryResolver | undefined { if (resources.boundaryResolver !== undefined) return resources.boundaryResolver; // `fs === undefined` is the "real disk" signal: a caller that injects a fake filesystem is // hermetic and must not spawn Git, so it stays on the filesystem marker fallback. return resources.fs === undefined ? defaultProjectBoundaryResolver() : undefined;}
interface DiscoveredResources { skillRegistry: SkillRegistry; skills: SkillRegistrySnapshot; context: ContextFileSnapshot; agents: AgentDefinitionSnapshot; rules: PermissionRuleSnapshot; extensionDiagnostics: ReadonlyArray<ExtensionLoadDiagnostic>;}
/** * Independently discover typed resources through the specialized loaders. Each loader owns * its format, trust, precedence, and diagnostics; results are NEVER flattened into a generic * `ResourceRecord`. The default filesystem is `nodeResourceFilesystem()`. Project files are * filtered unless `projectTrusted` is affirmed (safe default). Never throws — failures become * diagnostics. When `resources` is absent, returns empty snapshots (hermetic). */async function discoverResources( resources: BootstrapResourcesOptions | undefined, defaultCwd: string, boundaryResolver: ProjectBoundaryResolver | undefined,): Promise<DiscoveredResources> { if (resources === undefined) { return { skillRegistry: new DefaultSkillRegistry(), skills: EMPTY_SKILL_SNAPSHOT, context: { files: [], merged: '', diagnostics: [] }, agents: { agents: [], diagnostics: [] }, rules: { rules: [], records: [], diagnostics: [] }, extensionDiagnostics: [], }; } const fs = resources.fs ?? nodeResourceFilesystem(); const cwd = resources.cwd ?? defaultCwd; const userHome = resources.userHome; const bundledRoots = resources.bundledRoots ?? []; const userRoots = resources.userRoots ?? (userHome !== undefined ? [join(userHome, '.shard')] : []); const userExtensionRoots = resources.userExtensionRoots ?? (userHome !== undefined ? [join(userHome, '.shard')] : []); // Agent definitions are user-authored config (not runtime state): the dedicated agent // user layer prefers the XDG config dir. An explicit shared `userRoots` still wins for // backward compatibility; `userAgentRoots` overrides both. const agentUserRoots = resources.userAgentRoots ?? resources.userRoots ?? defaultAgentDefinitionUserRoots(userHome); const projectTrusted = resources.projectTrusted ?? false; const kinds = (resources.agentsMd ?? true) ? (['shard', 'agents'] as const) : (['shard'] as const);
// Skill registry: inject one so load_skill can consult the live, populated instance. const skillRegistry = resources.skillRegistry ?? new DefaultSkillRegistry(); const skills = await loadSkillRegistry( { bundledRoots, ...(userHome !== undefined ? { userHome } : undefined), cwd, projectTrusted, registry: skillRegistry, ...(boundaryResolver !== undefined ? { boundaryResolver } : undefined), }, fs, );
const context = await new ContextFileLoader( { bundledRoots, userRoots, cwd, trustGate: constantContextTrustGate(projectTrusted), kinds, ...(boundaryResolver !== undefined ? { boundaryResolver } : undefined), }, fs, ).load();
const agents = await new AgentDefinitionLoader( { bundledRoots, userRoots: agentUserRoots, cwd, agentDirs: ['agents', '.shard/agents'], trustGate: constantAgentTrustGate(projectTrusted), includeBuiltins: true, ...(boundaryResolver !== undefined ? { boundaryResolver } : undefined), }, fs, ).load();
const rules = await new PermissionRuleLoader( { bundledRoots, userRoots, cwd, ruleDirs: ['rules', '.shard/rules'], trustGate: constantRuleTrustGate(projectTrusted), ...(boundaryResolver !== undefined ? { boundaryResolver } : undefined), }, fs, ).load();
// Dynamic extension discovery is code execution — the ExtensionLoader runs against the // host in {@link createDefaultCore}; here we only surface its (separately-collected) // diagnostics placeholder so the aggregated stream is complete. void userExtensionRoots; return { skillRegistry, skills, context, agents, rules, extensionDiagnostics: [], };}
/** * Compose the BASE system prompt in the saga-pinned order * (base identity → layered context → available-skills catalog). Empty sections are omitted. * `AgentSession` then appends active mode → durable activated-skill bodies → latest plan → * compaction handoff (see `agent-session.ts`). */export function composeBaseSystemPrompt( baseIdentity: string, context: ContextFileSnapshot, skills: SkillRegistrySnapshot,): string { const sections: string[] = [baseIdentity]; if (context.merged.length > 0) { sections.push(`<!-- layered context -->\n${context.merged}`); } if (skills.catalog.length > 0) { const catalog = formatSkillCatalog(skills.catalog); if (catalog.length > 0) sections.push(catalog); } return sections.join('\n\n');}
/** * Construct the individual L2 pieces with the saga's defaults via the one async canonical * bootstrap (d4ks8c.7). Returns the wired pieces without bundling them into an * `AgentSession` — useful for tests or custom orchestration that wants to swap one piece. * * ## Permission rule composition precedence (req #4) * * For direct agents and managed roots, the gate consumes an ordered first-match `rules` list: * * 1. pinned `managed.rules` when present (highest authority), * 2. explicit `opts.permissions.rules`, * 3. discovered disk rules in the `PermissionRuleLoader`'s own first-match order * (project cwd → ancestors → user → bundled; untrusted project `allow` rules already * dropped by the loader). * * Managed children are isolated from host policy: their fresh child gate consumes only * pinned `managed.rules`. Within each group, source ordering is preserved. A * `RealPermissionGate` is constructed when any applicable rules or `opts.permissions` exist; * otherwise `AllowAllGate` is preserved for direct agents and managed roots. */export async function createDefaultCore(opts: CreateAgentSessionOptions): Promise<DefaultCore> { const cwd = opts.cwd ?? process.cwd(); const agentId = opts.agentId ?? generateId();
// ── Provider triple (preserved verbatim) ─────────────────────────────────────── let model = opts.model; let apiKey = opts.apiKey; let auth = opts.auth; let baseURL = opts.baseURL; let api: 'chat-completions' | 'responses' = opts.api ?? 'chat-completions'; let reasoning = opts.reasoning === true; let reasoningEffort: string | undefined = opts.reasoningEffort; // saga p7m8jp.1: carry the supported effort IDs (gates chat-completions opt-in). let reasoningEfforts: string[] | undefined = opts.reasoningEfforts; if (model === undefined) { if (opts.resolveProvider === undefined) { throw new Error( 'shard: createDefaultCore needs either { model } (explicit) or { providerKey, modelName, resolveProvider } (config-driven); neither was supplied', ); } const resolved = opts.resolveProvider({ ...(opts.providerKey !== undefined ? { providerKey: opts.providerKey } : undefined), ...(opts.modelName !== undefined ? { modelName: opts.modelName } : undefined), }); model = resolved.model; apiKey = resolved.apiKey; auth = resolved.auth; baseURL = resolved.baseURL; if (resolved.api !== undefined) api = resolved.api; if (resolved.reasoning === true) reasoning = true; // saga p7m8jp.1: carry the resolved reasoning effort through the bootstrap path. if (resolved.reasoningEffort !== undefined) reasoningEffort = resolved.reasoningEffort; if (resolved.reasoningEfforts !== undefined) reasoningEfforts = resolved.reasoningEfforts; } const providerConfig: { model: string; auth?: 'bearer' | 'none'; apiKey?: string; baseURL?: string; modelDescriptor?: ModelDescriptor; } = { model }; if (auth !== undefined) providerConfig.auth = auth; if (apiKey !== undefined) providerConfig.apiKey = apiKey; if (baseURL !== undefined) providerConfig.baseURL = baseURL; if (opts.providerKey !== undefined) { providerConfig.modelDescriptor = { id: `${opts.providerKey}:model:${model}`, provider: opts.providerKey, model, }; } // saga hihsjg: pick the wire adapter. `responses` → the OpenAI Responses API provider // (readable reasoning summaries for gpt-5 / o-series); otherwise the OpenAI-compatible // chat-completions provider (llama.cpp / DeepSeek / vLLM / Ollama). // saga p7m8jp.1/p7m8jp.2: carry the resolved reasoning effort to the provider config. // For Responses, the effort always applies (defaulting to `medium` inside the provider). // For Chat Completions, the effort is sent ONLY when the model declared `reasoningEfforts` // (explicit opt-in); generic endpoints receive no new field by default. const reasoningEffortOpt = reasoningEffort !== undefined ? { reasoningEffort } : undefined; const chatReasoningEffortOpt = reasoningEfforts !== undefined && reasoningEffort !== undefined ? { reasoningEffort } : undefined; const provider: Provider = api === 'responses' ? new OpenAIResponsesProvider({ ...providerConfig, reasoning, ...reasoningEffortOpt, }) : new OpenAIProvider({ ...providerConfig, ...chatReasoningEffortOpt, });
// ── Typed resource discovery (independent; no ResourceRecord flattening) ─────── // Compute the effective boundary resolver ONCE so every loader (skills/context/agents/rules // + the dynamic extension loader below) shares one canonical resolver. Production real-fs // (no `fs` injected) defaults to the Git-backed resolver; hermetic fake-fs stays Git-free. const effectiveBoundary = opts.resources !== undefined ? effectiveBoundaryResolver(opts.resources) : undefined; const discovered = await discoverResources(opts.resources, cwd, effectiveBoundary);
// saga u4uo9m.3: managed-child capability policy. When set, wrap the live SkillRegistry // with a filtered view BEFORE `load_skill` is installed (so the tool respects the // allowlist) and before the catalog is composed (so the system prompt hides filtered // skills). The base tool registry is filtered after the host snapshot (tools registered // by `opts.extensions` below are also subject to the policy). const managed = opts.managed; const capability = managed?.capability; const skillRegistry = capability !== undefined ? new FilteredSkillRegistry(discovered.skillRegistry, capability) : discovered.skillRegistry;
// ── ExtensionHost: the exclusive live capability registration surface ─────────── const eventBus = new InProcessEventBus(); const host = new ExtensionHost({ eventBus, agentId }); // Bundled tool/mode/compactor extensions installed through host.install (req #3). const toolsExt = builtinToolsExtension(); await host.install(toolsExt.identity, toolsExt.install); const modesExt = builtinModesExtension(); await host.install(modesExt.identity, modesExt.install); const compactorExt = builtinCompactorExtension(); await host.install(compactorExt.identity, compactorExt.install);
// SessionStore created before recall so the factory seam can capture it. const sessionStore = SessionStore.create(opts.sessionPath); const recallExt = createRecallExtension(sessionStore); await host.install(recallExt.identity, recallExt.install);
// load_skill via ExtensionApi — only when skills exist (absence behavior; req #3/#9). // Uses the (possibly filtered) live skill registry so a filtered skill is invisible. if (skillRegistry.size > 0) { await host.install(SKILLS_IDENTITY, createLoadSkillExtension(skillRegistry, opts.resources?.fs ?? nodeResourceFilesystem())); }
// Provider registered through ExtensionApi (keeps the host the single source of truth // while preserving backward-compatible `core.provider` typing). const providerInstall: ExtensionFunction = (api) => { api.registerProvider('default', provider as unknown as Provider); }; await host.install(PROVIDER_IDENTITY, providerInstall);
// Trusted dynamic extensions via ExtensionLoader (req #3). Project sources require the // trust gate; default `projectTrusted` is `false` so no project code is executed. let extensionDiagnostics = discovered.extensionDiagnostics; if (opts.resources !== undefined) { const res = opts.resources; const userHome = res.userHome; const userExtRoots = res.userExtensionRoots ?? (userHome !== undefined ? [join(userHome, '.shard')] : []); const extLoader = new ExtensionLoader({ bundledRoots: res.bundledRoots ?? [], userRoots: userExtRoots, cwd: res.cwd ?? cwd, extensionDirs: ['extensions', '.shard/extensions'], ...(res.extensionTrustGate !== undefined ? { trustGate: res.extensionTrustGate } : { trustGate: constantExtensionTrustGate(res.projectTrusted ?? false) }), ...(res.extensionFs !== undefined ? { fs: res.extensionFs } : undefined), ...(res.importer !== undefined ? { importer: res.importer } : undefined), ...(effectiveBoundary !== undefined ? { boundaryResolver: effectiveBoundary } : undefined), }); await extLoader.load(host); extensionDiagnostics = [...discovered.extensionDiagnostics, ...extLoader.listDiagnostics()]; }
// saga u4uo9m.3: generic injected bootstrap-contribution seam. Each caller-supplied // extension is installed through the ExtensionHost BEFORE the capability snapshot, so // its tools/modes/compactors are live and subject to the capability policy. L4 (the CLI) // builds these with the managed context captured in their closures — L2 never imports L3. if (opts.extensions !== undefined) { for (const ext of opts.extensions) { await host.install(ext.identity, ext.install); } }
// saga u4uo9m.5 plan item 1: generic contribution FACTORY. Unlike `opts.extensions` (which // capture their own data the L4 built before this bootstrap ran), the factory receives the // trusted snapshots DISCOVERED by this bootstrap plus the pinned managed context — so the // bundled subagent tools get the trusted agent-definition snapshot for launch // prevalidation without the core importing `src/subagent/**`. The concrete L3 factory is // injected by the L4 CLI; the core only calls the L1 function type. Consulted whenever // `managed` is present, including a daemon-backed root context. if (opts.contributionFactory !== undefined && managed !== undefined) { const contributions = opts.contributionFactory({ snapshots: { agents: discovered.agents }, managed: managed.context, }); for (const ext of contributions) { await host.install(ext.identity, ext.install); } }
// ── Build runtime registries from the host snapshot ONLY (req #3) ─────────────── const snapshot = host.snapshot(); const activeProvider = host.resolveProvider('default'); const codeIndex = host.activeCodeIntelligence(); if (activeProvider === undefined) { throw new Error('shard: no default provider registered through ExtensionApi'); }
const modes = snapshot.modes; if (modes.length === 0) { throw new Error('shard: builtin modes extension registered no modes'); } const initialMode = modes.find((m) => m.name === 'build') ?? modes[0]!; const modeController = new ModeController(initialMode); for (const m of modes) { if (m.name !== initialMode.name) modeController.register(m); } const targetMode: DefaultMode = opts.mode ?? 'build'; if (modeController.select(targetMode) !== undefined) { modeController.setActive(targetMode); }
const baseRegistry = new DefaultToolRegistry(modeController); for (const t of snapshot.tools) baseRegistry.register(t);
// saga u4uo9m.3: strict capability filtering before provider/catalog exposure. An // explicit tool allowlist is validated against the registered tool names + the six // management names (deferred L3 registration is NOT a startup failure); unknown names // fail startup. The filtered registry applies the policy at exposure so tools registered // after bootstrap are still subject to it. Omitted allowlist → defaults, except a managed // child never exposes `subagent_launch`. let registry: DefaultToolRegistry | FilteredToolRegistry = baseRegistry; if (capability !== undefined && capability.tool_allowlist !== undefined) { const registered = new Set(snapshot.tools.map((t) => t.name)); const v = validateToolAllowlist(capability.tool_allowlist, registered); if (!v.ok) throw new Error(`shard: managed tool allowlist invalid — ${v.reason}`); } if (capability !== undefined && capability.skill_allowlist !== undefined) { const v = validateSkillAllowlist(capability.skill_allowlist, new Set(discovered.skills.skills.map((s) => s.name))); if (!v.ok) throw new Error(`shard: managed skill allowlist invalid — ${v.reason}`); } if (capability !== undefined) { registry = new FilteredToolRegistry(baseRegistry, capability); }
// ── Permission gate: composed rules (req #4) ─────────────────────────────────── // saga u4uo9m.3: an actual managed child (`capability.is_child`) ALWAYS gets a fresh // RealPermissionGate with its own grant directory and a `read_only_allow` fallback // (rules/grants/mode decide first; otherwise effective read-only tools allow and uncovered // mutating tools ask). Managed roots retain normal root permission behavior. const discoveredRules = [...discovered.rules.rules]; const explicitRules = opts.permissions?.rules ?? []; const managedRules = managed?.rules ?? []; const grantsBaseDir = managed?.childGrantBaseDir ?? join(homedir(), '.shard', 'agents', agentId); let gate: PermissionGate; if (managed?.capability.is_child === true) { // Managed child: fresh gate, child grant dir, read-only-allow fallback. The isReadOnly // seam reads through the FILTERED registry so a hidden tool is treated as mutating // (safe: ask). Only manifest-pinned managed rules apply; host explicit/discovered rules // are intentionally excluded to preserve child isolation. Grant storage is lazy: // appendGrant creates the directory only when an allow-always decision is persisted. gate = new RealPermissionGate({ eventBus, modeController, agentId, baseDir: grantsBaseDir, defaultPolicy: 'read_only_allow', isReadOnly: (name: string) => registry.resolve(name)?.isReadOnly ?? false, ...(managedRules.length > 0 ? { rules: managedRules } : undefined), }); } else if (opts.permissions !== undefined || discoveredRules.length > 0 || managedRules.length > 0) { // Managed rules are pinned/authoritative, so they must precede direct and discovered rules. const combinedRules: Rule[] = [...managedRules, ...explicitRules, ...discoveredRules]; gate = new RealPermissionGate({ eventBus, modeController, agentId, baseDir: grantsBaseDir, ...(combinedRules.length > 0 ? { rules: combinedRules } : undefined), }); // Do not eagerly create ~/.shard/agents/<agentId>. The grant store creates it on the // first persisted allow-always decision, avoiding one empty directory per runtime. } else { gate = new AllowAllGate(); }
// ── Base system prompt: identity → layered context → available-skills catalog ── // saga u4uo9m.3: when managed, the caller (CLI) passes the pinned definition body as // `opts.systemPrompt` (the manifest's `system_prompt`), so the base identity is the // pinned body regardless of source-file edits (restart/relaunch determinism). The skill // catalog is the FILTERED snapshot when a capability policy is in effect. const skillsForPrompt = capability !== undefined ? skillRegistry.snapshot() : discovered.skills; const systemPrompt = composeBaseSystemPrompt( opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT, discovered.context, skillsForPrompt, );
// ── Compaction wiring (preserved; compactor now sourced from the host snapshot) ─ let compactor: Compactor | undefined; let compactionTrigger: ThresholdCompactionTrigger | undefined; let compactionConfig: CompactionConfig | undefined; if (opts.compaction) { compactor = snapshot.compactors[0]; if (compactor === undefined) { throw new Error('shard: compaction requested but no compactor registered via ExtensionApi'); } compactionTrigger = new ThresholdCompactionTrigger(); compactionConfig = applyCompactionDefaults(opts.compaction); }
const diagnostics: BootstrapDiagnostics = { skills: discovered.skills.diagnostics, context: discovered.context.diagnostics, agents: discovered.agents.diagnostics, rules: discovered.rules.diagnostics, extensions: extensionDiagnostics, host: snapshot.diagnostics, };
return { provider: activeProvider, registry, gate, modeController, sessionStore, eventBus, agentId, ...(opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {}), cwd, systemPrompt, host, skills: skillsForPrompt, context: discovered.context, agentDefinitions: discovered.agents, permissionRules: discovered.rules, ...(managed !== undefined ? { managed: managed.context } : undefined), ...(codeIndex !== undefined ? { codeIndex } : undefined), diagnostics, ...(compactor !== undefined ? { compactor } : undefined), ...(compactionTrigger !== undefined ? { compactionTrigger } : undefined), ...(compactionConfig !== undefined ? { compactionConfig } : undefined), };}
/** * Construct an AgentSession from an already-bootstrapped core. This is the sole default * core→session adapter used by CLI, SDK, and createAgentSession, preventing call sites from * accidentally dropping compaction or future session dependencies. */export function createAgentSessionFromCore(core: DefaultCore): AgentSession { return new AgentSessionClass({ provider: core.provider, registry: core.registry, gate: core.gate, modeController: core.modeController, sessionStore: core.sessionStore, eventBus: core.eventBus, agentId: core.agentId, ...(core.sessionId !== undefined ? { sessionId: core.sessionId } : {}), cwd: core.cwd, systemPrompt: core.systemPrompt, ...(core.codeIndex !== undefined ? { codeIndex: core.codeIndex } : undefined), ...(core.codeIndex !== undefined ? { onMutated: (paths: string[]) => { for (const path of paths) { try { core.codeIndex?.invalidate(path); } catch { // Best-effort: one failed path must not prevent later invalidations. } } }, } : undefined), ...(core.compactor !== undefined ? { compactor: core.compactor } : undefined), ...(core.compactionTrigger !== undefined ? { compactionTrigger: core.compactionTrigger } : undefined), ...(core.compactionConfig !== undefined ? { compactionConfig: core.compactionConfig } : undefined), });}
/** Construct a ready-to-run AgentSession through the canonical async bootstrap. */export async function createAgentSession( opts: CreateAgentSessionOptions,): Promise<AgentSession> { return createAgentSessionFromCore(await createDefaultCore(opts));}