Dashboard › opencode › Distillation
eade4b49-ec8b-41e8-88d8-301a5a5c7a53["lore_tm_v1_C5OSLGWpG51HY74h95aRH2sFqPSslDAR-3wPbFC4Roo","lore_tm_v1_7TtkSGmU7_XQkd2D9paR_XLFF9ZakQslv4_FnVxSezg","lore_tm_v1_DsWTFbGmMfvxl7RmmfIrmZnBP3EvRpDFhx1l_G-GkUc","lore_tm_v1_My_4KQK7bv7W0CG1EtTYnWxWfWLwdNARZrMy4kbT3SI","lore_tm_v1_ZUgVduYeRhwVujGvYjhEx6ePSh2WFYuY5muXMPTGGyI","lore_tm_v1_4E79MWt-qb2RCYGS6NJEzGvPSU6O-UUbEog1urI_5CM","lore_tm_v1_KWFSIs5P2nl4Y4qC87B2o3PAA5w8S1dX2d7JYLY-ivA","lore_tm_v1_fqp_Klp_9DNRwJxf-ZhY7bCAEJtct83d5Z4WV4EzbCQ","lore_tm_v1_F9efXtVI6WrSq5l5Kp-0YLDd2j9bRuTJvRryCncVLV8"]
gateway.port or lore.pid files./home/byk/Code/opencode-lore/packages/gateway/test/start-gateway-quiet.test.ts is an 82-line Vitest regression suite for startGateway({ quiet }), prompted by Pi TUI breakage reported by Daniel Griesser: embedded OpenCode/Pi callers use { quiet: true, local: true }, and any stdout/stderr output corrupts the host’s full-screen TUI.start-gateway-quiet.test.ts pins two contracts: quiet: true produces no stderr message containing Shutting down, while quiet: false still produces that message for the lore start CLI.startOwned(quiet: boolean) test helper creates an isolated XDG_DATA_HOME using mkdtempSync(join(tmpdir(), "lore-quiet-test-")), dynamically imports startGateway from ../src/cli/start, starts it with { port: 0, local: true, quiet }, asserts handle.owned === true, and registers recursive forced cleanup. afterEach runs shutdown/cleanup callbacks and restores or deletes the original XDG_DATA_HOME./home/byk/Code/opencode-lore/packages/gateway/src/auth.ts documents two distinct auth contracts: resolveAuth permits the process-global fallback only for callers that omit request-scoped auth context, while another request-scoped resolver intentionally never consults that fallback, preventing cross-client credential inheritance./home/byk/Code/opencode-lore/packages/opencode/src/index.ts imports helper functions from ./internal rather than exporting them from the plugin entry because OpenCode’s legacy loader invokes every exported function as a plugin; leaked helper exports previously inserted undefined into the hooks array and caused undefined is not an object (evaluating 'A.event').resolveGatewayUrl() uses this order: 1. LORE_REMOTE_URL, stripping a trailing slash and probing it, then falling back locally if unreachable; 2. LORE_GATEWAY_URL, also normalized and probed; 3. the port returned by dynamically imported readPortFile() from @loreai/gateway; 4. known ports 3207 and 5673. Local probes use http://127.0.0.1:${port}.startInProcess() dynamically imports startGateway from @loreai/gateway and calls startGateway({ quiet: true, local: true }). It returns http://127.0.0.1:${handle.port}, logs reuse when handle.owned is false, and on failure stores the exact error text in lastGatewayStartError, logs failed to start gateway in-process:, and returns null.processInitDone, processLoreActive, processLoreBase, and memoized loreInitPromise: Promise<string | null> | null; memoization prevents concurrent LorePlugin calls from racing during gateway probe/start./home/byk/Code/opencode-lore/packages/opencode/src/index.ts keeps per-project state in projectState, a Map<string, { projectPath: string; gitRemote: string; lastSeenAt: number }> keyed by ctx.project.id, preventing the prior “last project wins” race in which project A requests could be attributed to project B after a sub-agent or project initialization changed global state.currentProject: { path: string; gitRemote: string } | undefined; this fixes the “git-remote magnet” failure where a non-repository path could be paired with a remote left over from a different project.projectState and sessionParent entries use SESSION_STATE_TTL_MS = 24 * 60 * 60 * 1000 (24 hours). reapStaleProjectState() and reapStaleSessionParent() delete entries whose lastSeenAt is older than Date.now() - SESSION_STATE_TTL_MS; the source estimates each project entry at about 200 bytes and 100 projects at 20 KB.sessionParent is a Map<string, { parentID: string | null; lastSeenAt: number }> keyed by session ID. resolveParentSession(client, sessionID) returns and refreshes cached entries; otherwise it calls client.session.get({ path: { id: sessionID } }), derives res.data?.parentID ?? null, caches successful results including null, reaps stale entries, and returns the parent.resolveParentSession(...) catches all lookup failures and returns null, allowing transient failures such as an unreachable server or not-yet-persisted session to be retried next turn rather than permanently classifying a real sub-agent as primary.isInertTestEnv() returns false when LORE_OPENCODE_FORCE_ACTIVE === "1"; otherwise it is true when NODE_ENV === "test" or any process.argv entry contains .test.. Active tests can retain NODE_ENV=test while exercising discovery/interception through a controlled LORE_GATEWAY_URL, mirroring Pi’s LORE_PI_FORCE_ACTIVE.LorePlugin treats Lore as disabled when LORE_DISABLED is "1" or "true". Outside inert test mode it calls log.silenceStderr() before initialization so neither plugin logs nor the in-process gateway’s bundled core writes to OpenCode’s TUI; logs continue to the file and Sentry sink and remain available through lore logs.resolveGatewayUrl() and logs gateway detected at ${existingUrl} when successful; otherwise it logs starting gateway in-process…, calls startInProcess(), and logs gateway started in-process at ${startedUrl} on success. The resulting state is persisted in processLoreActive and processLoreBase.LorePlugin builds Lore failed to start — memory features are unavailable. plus either Gateway error: ${lastGatewayStartError} or Ensure @loreai/gateway is installed.. Errors matching not found|not exported|cannot find module additionally recommend pnpm --filter @loreai/gateway run bundle or run build for a dev checkout. surfaceGatewayUnavailable(ctx.client, msg) presents a TUI-safe toast because log.error is stderr-silenced; this addresses Daniel’s Windows report.thisProjectPath = discoverWorkspaceRoot(ctx.worktree || ctx.directory). It reuses a cached Git remote only when the stored projectPath matches; otherwise it evaluates getGitRemote(thisProjectPath) ?? "", updates projectState, reaps stale state, and atomically sets currentProject = { path: thisProjectPath, gitRemote: thisGitRemote }.config hook sets cfg.compaction = { auto: false, prune: false }, because gateway compaction replaces OpenCode’s built-in compaction.config hook registers three hidden workers in cfg.agent, preserving any existing agents: 1. lore-distill — mode: "subagent", hidden: true, description Lore memory distillation worker; 2. lore-curator — mode: "subagent", hidden: true, description Lore knowledge curator worker; 3. lore-query-expand — mode: "subagent", hidden: true, description Lore query expansion worker. mode: "subagent" is required because OpenCode defaults agents to mode: "all" and honors hidden: true only for subagent-mode agents.config hook calls applyLoreProviderConfig(cfg, gatewayBase) to pin the Anthropic provider’s base URL to Lore."chat.headers" hook adds gatewayAccessHeadersForRemote(gatewayBase), stable x-lore-session-id from input.sessionID, and x-lore-agent from input.agent."chat.headers" hook awaits resolveParentSession(ctx.client, input.sessionID) and, when non-null, sets x-parent-session-id. This lets the gateway recognize OpenCode Task child sessions as sub-agents and size their LTM injection under issue #1300, matching Claude Code’s native signal."chat.headers" hook sets x-lore-project from thisProjectPath and conditionally sets x-lore-git-remote from the same project’s thisGitRemote, ensuring concurrent sub-agents and sibling projects cannot clobber one another’s attribution.(input.provider as Record<string, unknown> | undefined)?.id; when present, it sets x-lore-provider so the gateway uses provider-based protocol/upstream routing instead of model-prefix guessing.vllm, ollama, and llamacpp, the "chat.headers" hook derives LORE_UPSTREAM_${providerID.toUpperCase().replace(/-/g, "_")} and sets x-lore-upstream-url only when that header is not already present, thereby never clobbering an existing real upstream URL.LORE_UPSTREAM_EXTRA_HEADERS line-by-line using /\r?\n/, ignores blank or colonless lines, splits each valid line at its first colon, trims name/value, and forwards it only when shouldForwardUpstreamExtraHeader(name) permits it. This supports corporate proxies, LiteLLM, and Cloudflare AI Gateway while avoiding headers managed by Lore; the gateway applies the same environment variable as a fallback when the plugin is bypassed.active: ${projectPath}. When Lore is active it installs installFetchInterceptor(...) once per process, logs routing through ${gatewayBase}, and logs dashboard: ${gatewayBase}/ui.getHeaders() starts with gatewayAccessHeadersForRemote(gatewayBase), then emits x-lore-project from currentProject.path and emits x-lore-git-remote only from the same currentProject object when non-empty. This fallback is for requests such as embedding/image generation that bypass "chat.headers".e.stack || e.message for Error values or String(e) otherwise, logs init failed: ${detail} to the file and Sentry sink, and rethrows because OpenCode’s plugin loader otherwise catches and hides the root cause./home/byk/Code/opencode-lore/packages/opencode/src/index.ts must export only named LorePlugin and the same-reference default LorePlugin. OpenCode’s legacy loader invokes every function export and throws on non-function exports, dropping the entire plugin; helpers belong in ./internal, and test/index.test.ts guards the entry-module export shape./home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/plugin.ts defines the v2 plugin shape as { readonly id: string; readonly setup: (context: Context) => Promise<Cleanup | void> | Cleanup | void }, where Cleanup = () => Promise<void> | void; define(plugin: Plugin) returns the plugin unchanged.Context exposes app, location, options, agent, aisdk, catalog, command, event, experimental.terminal.read, integration, mcp, generate, permission, plugin.list, reference, rpc, session, shell, skill, storage, tool, vcs, websearch, and worktree./home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/session.ts defines SessionRequestKind in this exact order: "primary" | "compaction" | "title" | "generate".prompt with mutable prompt, optional metadata, and SessionInbox.Delivery; 2. context with agent/model, mutable system, messages, tools, generation overrides, and providerOptions; 3. "model.request" with kind, optional baseURL, and mutable headers; 4. "http.request" with a mutable Request; 5. "http.response" with original request and mutable Response; 6. retry with SessionError.Error, attempt number, and mutable retry decision.SessionRetryDecision is exactly { retry: false } | { retry: true; delay: number }.SessionDomain exposes create, get, switchAgent, switchModel, prompt, generate, command, synthetic, interrupt, rename, move, wait, and context, plus hook: ModelHooks<SessionHooks>./home/byk/Code/opencode-v2-pilot/packages/plugin/src/promise/registration.ts defines registrations with asynchronous dispose(): Promise<void>. Hooks<Spec> registers callbacks receiving mutable hook inputs and returns Promise<Registration>.ModelHooks<Spec> supports optional { readonly providerID?: string } only for hook specs containing a readonly model field; provider-scoped hooks apply to one provider, while unscoped hooks apply to every provider.Context.location: Location.Info; the accompanying core search shows broad use of Location.Service across instructions, formatter, filesystem, worktree, config, VCS, AI SDK, session, shell, RPC, environment, permissions, tools, MCP, and plugin host modules.