jeopi-cli 16.2.13 → 16.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -3
- package/README.md +1 -1
- package/dist/cli.js +3087 -3060
- package/dist/types/advisor/watchdog.d.ts +2 -2
- package/dist/types/autolearn/managed-skills.d.ts +2 -2
- package/dist/types/capability/types.d.ts +2 -2
- package/dist/types/cli/commands/migrate-legacy.d.ts +1 -0
- package/dist/types/cli/config-cli.d.ts +1 -1
- package/dist/types/cli/flag-tables.d.ts +1 -1
- package/dist/types/cli/grievances-cli.d.ts +1 -1
- package/dist/types/cli/models-cli.d.ts +1 -1
- package/dist/types/cli/update-cli.d.ts +2 -2
- package/dist/types/cli/worktree-cli.d.ts +1 -1
- package/dist/types/commit/index.d.ts +1 -1
- package/dist/types/commit/pipeline.d.ts +1 -1
- package/dist/types/commit/types.d.ts +1 -1
- package/dist/types/config/prompt-templates.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +16 -6
- package/dist/types/config.d.ts +7 -7
- package/dist/types/discovery/helpers.d.ts +8 -8
- package/dist/types/discovery/omp-extension-roots.d.ts +2 -2
- package/dist/types/eval/py/runtime.d.ts +1 -1
- package/dist/types/extensibility/custom-tools/loader.d.ts +2 -2
- package/dist/types/extensibility/extensions/loader.d.ts +1 -1
- package/dist/types/extensibility/hooks/loader.d.ts +1 -1
- package/dist/types/extensibility/plugins/loader.d.ts +2 -2
- package/dist/types/extensibility/plugins/types.d.ts +13 -3
- package/dist/types/lsp/config.d.ts +2 -2
- package/dist/types/mcp/client.d.ts +1 -1
- package/dist/types/mcp/config-writer.d.ts +1 -1
- package/dist/types/mcp/oauth-flow.d.ts +1 -1
- package/dist/types/mcp/transports/http.d.ts +1 -1
- package/dist/types/memories/okf.d.ts +36 -0
- package/dist/types/modes/components/__tests__/assistant-agent-label.test.d.ts +1 -0
- package/dist/types/modes/components/assistant-message.d.ts +4 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/sdk.d.ts +5 -5
- package/dist/types/session/agent-session.d.ts +3 -0
- package/dist/types/session/auth-broker-config.d.ts +4 -4
- package/dist/types/session/snapcompact-savings-journal.d.ts +1 -1
- package/dist/types/task/commands.d.ts +1 -1
- package/dist/types/task/critic-gate.d.ts +76 -0
- package/dist/types/task/critic-gate.test.d.ts +1 -0
- package/dist/types/task/discovery.d.ts +1 -1
- package/dist/types/task/executor.d.ts +1 -1
- package/dist/types/tools/acp-bridge.d.ts +2 -2
- package/dist/types/tools/browser/tab-protocol.d.ts +1 -1
- package/dist/types/tools/index.d.ts +6 -1
- package/dist/types/tools/plan-mode-guard.d.ts +8 -0
- package/dist/types/tools/report-tool-issue.d.ts +1 -1
- package/dist/types/utils/changelog.d.ts +2 -2
- package/dist/types/utils/image-loading.d.ts +4 -4
- package/dist/types/utils/image-resize.d.ts +1 -1
- package/dist/types/web/search/index.d.ts +1 -1
- package/examples/custom-tools/README.md +1 -1
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/pirate.ts +1 -1
- package/examples/extensions/plan-mode.ts +1 -1
- package/examples/extensions/tools.ts +1 -1
- package/examples/hooks/README.md +1 -1
- package/examples/sdk/01-minimal.ts +1 -1
- package/examples/sdk/04-skills.ts +1 -1
- package/examples/sdk/06-extensions.ts +3 -3
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/examples/sdk/12-redis-sessions.ts +1 -1
- package/examples/sdk/13-sql-sessions.ts +1 -1
- package/examples/sdk/README.md +1 -1
- package/package.json +12 -12
- package/scripts/jeopi +1 -1
- package/src/advisor/__tests__/config.test.ts +4 -4
- package/src/advisor/watchdog.ts +8 -8
- package/src/autolearn/managed-skills.ts +4 -4
- package/src/autoresearch/storage.ts +2 -2
- package/src/capability/types.ts +2 -2
- package/src/cli/agents-cli.ts +2 -2
- package/src/cli/args.ts +3 -3
- package/src/cli/auth-broker-cli.ts +5 -5
- package/src/cli/auth-gateway-cli.ts +4 -4
- package/src/cli/claude-trace-cli.ts +1 -1
- package/src/cli/commands/migrate-legacy.ts +24 -0
- package/src/cli/config-cli.ts +20 -5
- package/src/cli/flag-tables.ts +1 -1
- package/src/cli/gallery-fixtures/misc.ts +7 -7
- package/src/cli/gallery-screenshot.ts +2 -2
- package/src/cli/grievances-cli.ts +1 -1
- package/src/cli/models-cli.ts +1 -1
- package/src/cli/plugin-cli.ts +2 -2
- package/src/cli/profile-alias.ts +8 -8
- package/src/cli/read-cli.ts +1 -1
- package/src/cli/ssh-cli.ts +2 -2
- package/src/cli/update-cli.ts +27 -27
- package/src/cli/worktree-cli.ts +3 -3
- package/src/cli.ts +5 -5
- package/src/commands/agents.ts +2 -2
- package/src/commands/auth-broker.ts +1 -1
- package/src/commands/config.ts +1 -1
- package/src/commands/dry-balance.ts +6 -6
- package/src/commands/launch.ts +2 -2
- package/src/commands/models.ts +1 -1
- package/src/commands/say.ts +1 -1
- package/src/commands/token.ts +5 -5
- package/src/commands/ttsr.ts +2 -2
- package/src/commands/worktree.ts +3 -3
- package/src/commit/agentic/prompts/system.md +1 -1
- package/src/commit/cli.ts +1 -1
- package/src/commit/index.ts +1 -1
- package/src/commit/pipeline.ts +1 -1
- package/src/commit/types.ts +1 -1
- package/src/config/mcp-schema.json +5 -5
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/prompt-templates.ts +2 -2
- package/src/config/settings-schema.ts +20 -8
- package/src/config/settings.ts +1 -1
- package/src/config.ts +10 -10
- package/src/dap/session.ts +2 -2
- package/src/debug/protocol-probe.ts +1 -1
- package/src/debug/raw-sse-buffer.ts +3 -3
- package/src/debug/raw-sse.ts +2 -2
- package/src/discovery/builtin.ts +9 -9
- package/src/discovery/helpers.ts +13 -13
- package/src/discovery/omp-extension-roots.ts +4 -4
- package/src/discovery/omp-plugins.ts +2 -2
- package/src/discovery/substitute-plugin-root.ts +3 -3
- package/src/eval/py/runtime.ts +1 -1
- package/src/eval/rb/runner.rb +6 -6
- package/src/export/custom-share.ts +1 -1
- package/src/extensibility/custom-tools/loader.ts +3 -3
- package/src/extensibility/extensions/loader.ts +4 -4
- package/src/extensibility/hooks/loader.ts +1 -1
- package/src/extensibility/plugins/loader.ts +16 -10
- package/src/extensibility/plugins/manager.ts +10 -9
- package/src/extensibility/plugins/types.ts +15 -4
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/lsp/config.ts +4 -4
- package/src/lsp/index.ts +1 -1
- package/src/main.ts +1 -1
- package/src/mcp/client.ts +1 -1
- package/src/mcp/config-writer.ts +2 -2
- package/src/mcp/manager.ts +1 -1
- package/src/mcp/oauth-flow.ts +1 -1
- package/src/mcp/timeout.ts +2 -2
- package/src/mcp/transports/http.ts +1 -1
- package/src/memories/index.ts +82 -11
- package/src/memories/okf.ts +129 -0
- package/src/modes/components/__tests__/assistant-agent-label.test.ts +87 -0
- package/src/modes/components/assistant-message.ts +27 -0
- package/src/modes/components/extensions/extension-dashboard.ts +1 -1
- package/src/modes/controllers/mcp-command-controller.ts +2 -2
- package/src/modes/controllers/omfg-controller.ts +2 -2
- package/src/modes/controllers/ssh-command-controller.ts +2 -2
- package/src/modes/interactive-mode.ts +3 -1
- package/src/modes/setup-wizard/index.ts +1 -1
- package/src/prompts/agents/jeo.md +20 -10
- package/src/prompts/system/autolearn-guidance.md +2 -2
- package/src/prompts/system/system-prompt.md +10 -1
- package/src/prompts/tools/learn.md +1 -1
- package/src/prompts/tools/manage-skill.md +1 -1
- package/src/sdk.ts +8 -6
- package/src/secrets/index.ts +2 -2
- package/src/session/agent-session.ts +86 -6
- package/src/session/auth-broker-config.ts +6 -6
- package/src/session/snapcompact-savings-journal.ts +1 -1
- package/src/slash-commands/helpers/mcp.ts +1 -1
- package/src/system-prompt.test.ts +14 -14
- package/src/task/commands.ts +1 -1
- package/src/task/critic-gate.test.ts +181 -0
- package/src/task/critic-gate.ts +148 -0
- package/src/task/discovery.ts +5 -5
- package/src/task/executor.ts +1 -1
- package/src/task/index.ts +35 -2
- package/src/tools/acp-bridge.ts +2 -2
- package/src/tools/browser/launch.ts +1 -1
- package/src/tools/browser/tab-protocol.ts +1 -1
- package/src/tools/grep.ts +2 -2
- package/src/tools/index.ts +6 -1
- package/src/tools/plan-mode-guard.ts +16 -0
- package/src/tools/read.ts +1 -1
- package/src/tools/report-tool-issue.ts +2 -2
- package/src/utils/changelog.ts +2 -2
- package/src/utils/image-loading.ts +4 -4
- package/src/utils/image-resize.ts +5 -5
- package/src/web/search/index.ts +1 -1
package/src/lsp/config.ts
CHANGED
|
@@ -336,7 +336,7 @@ function getConfigSources(cwd: string): ConfigSource[] {
|
|
|
336
336
|
sources.push(fileConfigSource(path.join(cwd, filename)));
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
-
// Project config directories (.
|
|
339
|
+
// Project config directories (.jeopi/, .pi/, .claude/)
|
|
340
340
|
const projectDirs = getConfigDirPaths("", { user: false, project: true, cwd });
|
|
341
341
|
for (const dir of projectDirs) {
|
|
342
342
|
for (const filename of filenames) {
|
|
@@ -344,7 +344,7 @@ function getConfigSources(cwd: string): ConfigSource[] {
|
|
|
344
344
|
}
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
-
// User config directories (~/.
|
|
347
|
+
// User config directories (~/.jeopi/agent/, ~/.pi/agent/, ~/.claude/)
|
|
348
348
|
const userDirs = getConfigDirPaths("", { user: true, project: false });
|
|
349
349
|
for (const dir of userDirs) {
|
|
350
350
|
for (const filename of filenames) {
|
|
@@ -374,8 +374,8 @@ function getConfigSources(cwd: string): ConfigSource[] {
|
|
|
374
374
|
*
|
|
375
375
|
* Priority (highest to lowest):
|
|
376
376
|
* 1. Project root: lsp.json/.lsp.json/lsp.yml/.lsp.yml/lsp.yaml/.lsp.yaml
|
|
377
|
-
* 2. Project config dirs: .
|
|
378
|
-
* 3. User config dirs: ~/.
|
|
377
|
+
* 2. Project config dirs: .jeopi/lsp.*, .pi/lsp.*, .claude/lsp.* (+ hidden variants)
|
|
378
|
+
* 3. User config dirs: ~/.jeopi/agent/lsp.*, ~/.pi/agent/lsp.*, ~/.claude/lsp.* (+ hidden variants)
|
|
379
379
|
* 4. User home root: ~/lsp.*, ~/.lsp.*
|
|
380
380
|
* 5. Auto-detect from project markers + available binaries
|
|
381
381
|
*
|
package/src/lsp/index.ts
CHANGED
|
@@ -2060,7 +2060,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
2060
2060
|
|
|
2061
2061
|
if (action === "reload" && (isWorkspace || !resolvedFile)) {
|
|
2062
2062
|
// `reload *` is the user's explicit request to re-read config from
|
|
2063
|
-
// disk. Drop the per-cwd cache entry so `.
|
|
2063
|
+
// disk. Drop the per-cwd cache entry so `.jeopi/lsp.json`, root markers,
|
|
2064
2064
|
// and plugin configs added after the first LSP call become visible —
|
|
2065
2065
|
// otherwise `getConfig` returns the first observation for the rest of
|
|
2066
2066
|
// the process lifetime (#3546).
|
package/src/main.ts
CHANGED
|
@@ -747,7 +747,7 @@ export async function createSessionManager(
|
|
|
747
747
|
|
|
748
748
|
/** Discover SYSTEM.md file if no CLI system prompt was provided */
|
|
749
749
|
function discoverSystemPromptFile(): string | undefined {
|
|
750
|
-
// Check project-local first (.
|
|
750
|
+
// Check project-local first (.jeopi/SYSTEM.md, .pi/SYSTEM.md legacy)
|
|
751
751
|
const projectPath = findConfigFile("SYSTEM.md", { user: false });
|
|
752
752
|
if (projectPath) {
|
|
753
753
|
return projectPath;
|
package/src/mcp/client.ts
CHANGED
|
@@ -129,7 +129,7 @@ async function initializeConnection(
|
|
|
129
129
|
/**
|
|
130
130
|
* Connect to an MCP server.
|
|
131
131
|
* Has a 30 second timeout by default to prevent blocking startup.
|
|
132
|
-
* Set
|
|
132
|
+
* Set JEOPI_MCP_TIMEOUT_MS=0 to disable MCP client-side timeouts.
|
|
133
133
|
*/
|
|
134
134
|
export async function connectToServer(
|
|
135
135
|
name: string,
|
package/src/mcp/config-writer.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Configuration File Writer
|
|
3
3
|
*
|
|
4
|
-
* Utilities for reading/writing .
|
|
4
|
+
* Utilities for reading/writing .jeopi/mcp.json files at user or project level.
|
|
5
5
|
*/
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as path from "node:path";
|
|
@@ -270,7 +270,7 @@ export interface SetMcpServerEnabledOptions {
|
|
|
270
270
|
projectPath: string;
|
|
271
271
|
/**
|
|
272
272
|
* Absolute path to the loaded row's source mcp.json. Provide ONLY for
|
|
273
|
-
* formats this codebase owns (native `.
|
|
273
|
+
* formats this codebase owns (native `.jeopi/mcp.json` and `mcp-json`
|
|
274
274
|
* `mcp.json`/`.mcp.json`). Tool-owned configs (opencode.json, claude.json,
|
|
275
275
|
* settings.json …) MUST be omitted; we never mutate another tool's file.
|
|
276
276
|
*/
|
package/src/mcp/manager.ts
CHANGED
|
@@ -524,7 +524,7 @@ export class MCPManager {
|
|
|
524
524
|
// Pending tasks without cached tools used to be awaited synchronously here,
|
|
525
525
|
// which gated the entire UI on the slowest server's per-request timeout
|
|
526
526
|
// (issue #2100: a single unresponsive MCP server blocked startup for the
|
|
527
|
-
// full 30 s `
|
|
527
|
+
// full 30 s `JEOPI_MCP_TIMEOUT_MS`). Leave them in flight — the background
|
|
528
528
|
// `void toolsPromise.then(...)` chain above registers their tools and
|
|
529
529
|
// fires `#onToolsChanged` once the connect finishes, or logs the failure
|
|
530
530
|
// after `allowBackgroundLogging` flips below.
|
package/src/mcp/oauth-flow.ts
CHANGED
|
@@ -19,7 +19,7 @@ const MCP_OAUTH_URL_CREDENTIAL_PREFIX = "mcp_oauth:";
|
|
|
19
19
|
const MCP_OAUTH_PROFILE_CREDENTIAL_PREFIX = `${MCP_OAUTH_URL_CREDENTIAL_PREFIX}profile:`;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Deterministic credential id for an MCP server URL scoped to
|
|
22
|
+
* Deterministic credential id for an MCP server URL scoped to a jeopi profile.
|
|
23
23
|
*
|
|
24
24
|
* Local profile stores are already separate, but auth-broker storage shares one
|
|
25
25
|
* provider namespace across profiles. Including the profile in the provider key
|
package/src/mcp/timeout.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { logger } from "jeopi-utils";
|
|
2
2
|
|
|
3
3
|
const DEFAULT_MCP_TIMEOUT_MS = 30_000;
|
|
4
|
-
const MCP_TIMEOUT_ENV = "
|
|
4
|
+
const MCP_TIMEOUT_ENV = "JEOPI_MCP_TIMEOUT_MS";
|
|
5
5
|
|
|
6
6
|
let neverAbortController: AbortController | undefined;
|
|
7
7
|
|
|
@@ -10,7 +10,7 @@ export function resolveMCPTimeoutMs(configTimeout?: number): number {
|
|
|
10
10
|
if (raw) {
|
|
11
11
|
const value = Number(raw);
|
|
12
12
|
if (Number.isFinite(value) && value >= 0) return value;
|
|
13
|
-
logger.warn("Ignoring invalid
|
|
13
|
+
logger.warn("Ignoring invalid JEOPI_MCP_TIMEOUT_MS env value; expected a non-negative number", {
|
|
14
14
|
value: raw,
|
|
15
15
|
});
|
|
16
16
|
}
|
|
@@ -24,7 +24,7 @@ const HTTP_SSE_CONNECT_TIMEOUT_MS = 1_000;
|
|
|
24
24
|
* Best-effort startup deadline for the optional Streamable HTTP GET SSE listener.
|
|
25
25
|
*
|
|
26
26
|
* Returns `0` (disabled) when the operator has explicitly disabled MCP client-side
|
|
27
|
-
* timeouts via `timeout: 0` or `
|
|
27
|
+
* timeouts via `timeout: 0` or `JEOPI_MCP_TIMEOUT_MS=0`, mirroring the rest of the
|
|
28
28
|
* MCP timeout surface. Otherwise caps the wait at one second and scales below
|
|
29
29
|
* short request timeouts so connect-time never exceeds the request budget.
|
|
30
30
|
*/
|
package/src/memories/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import readPathTemplate from "../prompts/memories/read-path.md" with { type: "te
|
|
|
17
17
|
import stageOneInputTemplate from "../prompts/memories/stage_one_input.md" with { type: "text" };
|
|
18
18
|
import stageOneSystemTemplate from "../prompts/memories/stage_one_system.md" with { type: "text" };
|
|
19
19
|
import type { AgentSession } from "../session/agent-session";
|
|
20
|
+
import { okfTimestamp, parseOkfDocument, renderOkfDocument, stripOkfFrontmatter } from "./okf";
|
|
20
21
|
import {
|
|
21
22
|
claimStage1Jobs,
|
|
22
23
|
clearMemoryData as clearMemoryDataInDb,
|
|
@@ -186,7 +187,7 @@ async function readMemoryToolDeveloperInstructionsSnapshot(
|
|
|
186
187
|
|
|
187
188
|
let summary = "";
|
|
188
189
|
try {
|
|
189
|
-
summary = (await Bun.file(path.join(memoryRoot, "memory_summary.md")).text()).trim();
|
|
190
|
+
summary = stripOkfFrontmatter(await Bun.file(path.join(memoryRoot, "memory_summary.md")).text()).trim();
|
|
190
191
|
} catch {
|
|
191
192
|
// Missing or unreadable summary — injection is best-effort; fall through
|
|
192
193
|
// so any captured lessons still surface on their own.
|
|
@@ -790,10 +791,18 @@ async function syncPhase2Artifacts(memoryRoot: string, outputs: Stage1OutputRow[
|
|
|
790
791
|
const stem = formatRolloutFilename(row.threadId, row.rolloutSlug);
|
|
791
792
|
const filename = `${stem}.md`;
|
|
792
793
|
keepFiles.add(filename);
|
|
793
|
-
const
|
|
794
|
-
|
|
794
|
+
const document = renderOkfDocument(
|
|
795
|
+
{
|
|
796
|
+
type: "Session Summary",
|
|
797
|
+
title: `Rollout ${stem}`,
|
|
798
|
+
description: "Summary of one jeopi session rollout, feeding memory consolidation.",
|
|
799
|
+
tags: ["jeopi", "memory", "rollout"],
|
|
800
|
+
timestamp: okfTimestamp(row.sourceUpdatedAt),
|
|
801
|
+
extra: { thread_id: row.threadId, updated_at: row.sourceUpdatedAt },
|
|
802
|
+
},
|
|
803
|
+
row.rolloutSummary,
|
|
795
804
|
);
|
|
796
|
-
await Bun.write(path.join(summariesDir, filename),
|
|
805
|
+
await Bun.write(path.join(summariesDir, filename), document);
|
|
797
806
|
}
|
|
798
807
|
|
|
799
808
|
const currentFiles = await fs.readdir(summariesDir).catch(() => [] as string[]);
|
|
@@ -804,7 +813,17 @@ async function syncPhase2Artifacts(memoryRoot: string, outputs: Stage1OutputRow[
|
|
|
804
813
|
}
|
|
805
814
|
|
|
806
815
|
const rawBody = buildRawMemoriesMarkdown(outputs);
|
|
807
|
-
|
|
816
|
+
const rawDocument = renderOkfDocument(
|
|
817
|
+
{
|
|
818
|
+
type: "Raw Memories",
|
|
819
|
+
title: "jeopi Raw Memories",
|
|
820
|
+
description: "Per-thread raw memory extracts feeding the consolidation pass.",
|
|
821
|
+
tags: ["jeopi", "memory", "raw"],
|
|
822
|
+
timestamp: okfTimestamp(),
|
|
823
|
+
},
|
|
824
|
+
rawBody,
|
|
825
|
+
);
|
|
826
|
+
await Bun.write(path.join(memoryRoot, "raw_memories.md"), rawDocument);
|
|
808
827
|
}
|
|
809
828
|
|
|
810
829
|
async function cleanupConsolidatedArtifacts(memoryRoot: string): Promise<void> {
|
|
@@ -837,7 +856,18 @@ async function readRolloutSummaries(memoryRoot: string): Promise<string> {
|
|
|
837
856
|
.text()
|
|
838
857
|
.catch(() => "");
|
|
839
858
|
if (!text.trim()) continue;
|
|
840
|
-
|
|
859
|
+
// OKF atoms carry thread identity in frontmatter; rebuild the plain
|
|
860
|
+
// header the consolidation prompt has always seen. Legacy files (no
|
|
861
|
+
// frontmatter) already carry that header inline and pass verbatim.
|
|
862
|
+
const { frontmatter, body } = parseOkfDocument(text);
|
|
863
|
+
const threadId = typeof frontmatter?.thread_id === "string" ? frontmatter.thread_id : undefined;
|
|
864
|
+
const updatedAt = typeof frontmatter?.updated_at === "number" ? frontmatter.updated_at : undefined;
|
|
865
|
+
const content =
|
|
866
|
+
threadId !== undefined
|
|
867
|
+
? [`thread_id: ${threadId}`, `updated_at: ${updatedAt ?? ""}`, "", body.trim()].join("\n")
|
|
868
|
+
: body.trim();
|
|
869
|
+
if (!content.trim()) continue;
|
|
870
|
+
blocks.push(`--- ${name} ---\n${content.trim()}`);
|
|
841
871
|
}
|
|
842
872
|
if (blocks.length === 0) return "No rollout summaries yet.";
|
|
843
873
|
return blocks.join("\n\n");
|
|
@@ -860,7 +890,7 @@ async function runConsolidationModel(options: {
|
|
|
860
890
|
}>;
|
|
861
891
|
}> {
|
|
862
892
|
const { memoryRoot, model, apiKey } = options;
|
|
863
|
-
const rawMemories = await Bun.file(path.join(memoryRoot, "raw_memories.md")).text();
|
|
893
|
+
const rawMemories = stripOkfFrontmatter(await Bun.file(path.join(memoryRoot, "raw_memories.md")).text());
|
|
864
894
|
const rolloutSummaries = await readRolloutSummaries(memoryRoot);
|
|
865
895
|
const input = prompt.render(consolidationTemplate, {
|
|
866
896
|
raw_memories: truncateByApproxTokens(rawMemories, 20_000),
|
|
@@ -938,8 +968,35 @@ async function applyConsolidation(
|
|
|
938
968
|
}>;
|
|
939
969
|
},
|
|
940
970
|
): Promise<void> {
|
|
941
|
-
|
|
942
|
-
await Bun.write(
|
|
971
|
+
const timestamp = okfTimestamp();
|
|
972
|
+
await Bun.write(
|
|
973
|
+
path.join(memoryRoot, "MEMORY.md"),
|
|
974
|
+
renderOkfDocument(
|
|
975
|
+
{
|
|
976
|
+
type: "Memory",
|
|
977
|
+
title: "jeopi Consolidated Memory",
|
|
978
|
+
description: "Long-term consolidated memory jeopi maintains for this project.",
|
|
979
|
+
tags: ["jeopi", "memory", "consolidated"],
|
|
980
|
+
timestamp,
|
|
981
|
+
},
|
|
982
|
+
// The model occasionally emits its own frontmatter; strip it so the
|
|
983
|
+
// atom never carries a nested fence.
|
|
984
|
+
stripOkfFrontmatter(consolidated.memoryMd.trim()),
|
|
985
|
+
),
|
|
986
|
+
);
|
|
987
|
+
await Bun.write(
|
|
988
|
+
path.join(memoryRoot, "memory_summary.md"),
|
|
989
|
+
renderOkfDocument(
|
|
990
|
+
{
|
|
991
|
+
type: "Memory Summary",
|
|
992
|
+
title: "jeopi Memory Summary",
|
|
993
|
+
description: "Prompt-time memory guidance injected at jeopi session start.",
|
|
994
|
+
tags: ["jeopi", "memory", "summary"],
|
|
995
|
+
timestamp,
|
|
996
|
+
},
|
|
997
|
+
stripOkfFrontmatter(consolidated.memorySummary.trim()),
|
|
998
|
+
),
|
|
999
|
+
);
|
|
943
1000
|
const skillsDir = path.join(memoryRoot, "skills");
|
|
944
1001
|
await fs.mkdir(skillsDir, { recursive: true });
|
|
945
1002
|
const keep = new Set<string>();
|
|
@@ -1341,23 +1398,37 @@ async function appendLearnedLine(filePath: string, line: string): Promise<void>
|
|
|
1341
1398
|
} catch (err) {
|
|
1342
1399
|
if (!isEnoent(err)) throw err;
|
|
1343
1400
|
}
|
|
1401
|
+
// The `- ` filter keeps only lesson lines, so a legacy file (no
|
|
1402
|
+
// frontmatter) and an OKF atom (frontmatter re-rendered below) both fold
|
|
1403
|
+
// into the same newest-first list.
|
|
1344
1404
|
const prior = existing
|
|
1345
1405
|
.split("\n")
|
|
1346
1406
|
.map(l => l.trim())
|
|
1347
1407
|
.filter(l => l.startsWith("- ") && l !== line);
|
|
1348
1408
|
const lessons = [line, ...prior].slice(0, MAX_LEARNED_LESSONS);
|
|
1349
|
-
|
|
1409
|
+
const document = renderOkfDocument(
|
|
1410
|
+
{
|
|
1411
|
+
type: "Lessons",
|
|
1412
|
+
title: "jeopi Learned Lessons",
|
|
1413
|
+
description: "Lessons captured via the learn tool, newest first.",
|
|
1414
|
+
tags: ["jeopi", "memory", "lessons"],
|
|
1415
|
+
timestamp: okfTimestamp(),
|
|
1416
|
+
},
|
|
1417
|
+
lessons.join("\n"),
|
|
1418
|
+
);
|
|
1419
|
+
await Bun.write(filePath, document);
|
|
1350
1420
|
}
|
|
1351
1421
|
|
|
1352
1422
|
/**
|
|
1353
1423
|
* Read `learned.md`, neutralizing each line on read too — a hand-edited or
|
|
1354
1424
|
* pre-existing file bypasses write-time normalization and the block renders
|
|
1355
1425
|
* unescaped into the system prompt. Returns "" when absent/unreadable.
|
|
1426
|
+
* OKF frontmatter is stripped so only lesson lines reach the prompt.
|
|
1356
1427
|
*/
|
|
1357
1428
|
async function readLearnedLessons(memoryRoot: string): Promise<string> {
|
|
1358
1429
|
let raw = "";
|
|
1359
1430
|
try {
|
|
1360
|
-
raw = (await Bun.file(path.join(memoryRoot, LEARNED_LESSONS_FILE)).text()).trim();
|
|
1431
|
+
raw = stripOkfFrontmatter(await Bun.file(path.join(memoryRoot, LEARNED_LESSONS_FILE)).text()).trim();
|
|
1361
1432
|
} catch {
|
|
1362
1433
|
return "";
|
|
1363
1434
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Knowledge Format (OKF v0.1) support for jeopi memory documents.
|
|
3
|
+
*
|
|
4
|
+
* OKF (https://github.com/google/open-knowledge-format — the format jeo-skills'
|
|
5
|
+
* `okf` skill validates and consumes) stores knowledge atoms as
|
|
6
|
+
* YAML-frontmatter Markdown: required `type` / `title` / `description`,
|
|
7
|
+
* optional `resource` / `tags` / `timestamp`. jeopi's local memory backend
|
|
8
|
+
* writes its artifacts (`MEMORY.md`, `memory_summary.md`, `raw_memories.md`,
|
|
9
|
+
* `rollout_summaries/*.md`, `learned.md`) as OKF atoms so any OKF consumer can
|
|
10
|
+
* read a jeopi memory root as a knowledge bundle.
|
|
11
|
+
*
|
|
12
|
+
* Prompt-injection read paths strip the frontmatter via
|
|
13
|
+
* {@link stripOkfFrontmatter} so the model-facing text stays unchanged;
|
|
14
|
+
* legacy files without frontmatter pass through verbatim.
|
|
15
|
+
*/
|
|
16
|
+
import { YAML } from "bun";
|
|
17
|
+
|
|
18
|
+
/** OKF v0.1 frontmatter fields. Only `type`/`title`/`description` are required. */
|
|
19
|
+
export interface OkfFields {
|
|
20
|
+
type: string;
|
|
21
|
+
title: string;
|
|
22
|
+
description: string;
|
|
23
|
+
resource?: string;
|
|
24
|
+
tags?: readonly string[];
|
|
25
|
+
/** ISO 8601 datetime, e.g. `2026-07-02T10:00:00Z`. */
|
|
26
|
+
timestamp?: string;
|
|
27
|
+
/** Producer-specific extension fields (e.g. `thread_id`). */
|
|
28
|
+
extra?: Record<string, string | number>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const FENCE = "---";
|
|
32
|
+
|
|
33
|
+
function yamlScalar(value: string | number): string {
|
|
34
|
+
if (typeof value === "number") return String(value);
|
|
35
|
+
return JSON.stringify(value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Render an OKF atom: YAML frontmatter followed by the markdown body. */
|
|
39
|
+
export function renderOkfDocument(fields: OkfFields, body: string): string {
|
|
40
|
+
const lines: string[] = [FENCE];
|
|
41
|
+
lines.push(`type: ${yamlScalar(fields.type)}`);
|
|
42
|
+
lines.push(`title: ${yamlScalar(fields.title)}`);
|
|
43
|
+
lines.push(`description: ${yamlScalar(fields.description)}`);
|
|
44
|
+
if (fields.resource) lines.push(`resource: ${yamlScalar(fields.resource)}`);
|
|
45
|
+
if (fields.tags?.length) lines.push(`tags: [${fields.tags.join(", ")}]`);
|
|
46
|
+
if (fields.timestamp) lines.push(`timestamp: ${fields.timestamp}`);
|
|
47
|
+
for (const [key, value] of Object.entries(fields.extra ?? {})) {
|
|
48
|
+
lines.push(`${key}: ${yamlScalar(value)}`);
|
|
49
|
+
}
|
|
50
|
+
lines.push(FENCE, "");
|
|
51
|
+
const trimmedBody = body.trim();
|
|
52
|
+
return trimmedBody ? `${lines.join("\n")}\n${trimmedBody}\n` : `${lines.join("\n")}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Locate the closing fence line of a leading frontmatter block, or -1. */
|
|
56
|
+
function findFrontmatterEnd(lines: string[]): number {
|
|
57
|
+
if (lines[0]?.trim() !== FENCE) return -1;
|
|
58
|
+
for (let index = 1; index < lines.length; index++) {
|
|
59
|
+
if (lines[index].trim() === FENCE) return index;
|
|
60
|
+
}
|
|
61
|
+
return -1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Parsed frontmatter (undefined when absent/unparseable) plus the body. */
|
|
65
|
+
export interface OkfDocumentParts {
|
|
66
|
+
frontmatter: Record<string, unknown> | undefined;
|
|
67
|
+
body: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Split an OKF document into frontmatter and body. Tolerant by design: text
|
|
72
|
+
* without a leading fence (legacy jeopi memory files, hand-edited files)
|
|
73
|
+
* returns the whole text as body with `frontmatter: undefined`, and a
|
|
74
|
+
* malformed YAML block still strips so prompt paths never leak fences.
|
|
75
|
+
*/
|
|
76
|
+
export function parseOkfDocument(text: string): OkfDocumentParts {
|
|
77
|
+
const lines = text.split("\n");
|
|
78
|
+
const end = findFrontmatterEnd(lines);
|
|
79
|
+
if (end === -1) return { frontmatter: undefined, body: text };
|
|
80
|
+
const body = lines.slice(end + 1).join("\n");
|
|
81
|
+
let frontmatter: Record<string, unknown> | undefined;
|
|
82
|
+
try {
|
|
83
|
+
const parsed = YAML.parse(lines.slice(1, end).join("\n"));
|
|
84
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
85
|
+
frontmatter = parsed as Record<string, unknown>;
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
// Malformed YAML — treat as fenceless body strip.
|
|
89
|
+
}
|
|
90
|
+
return { frontmatter, body };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Body of an OKF document with any leading frontmatter removed. */
|
|
94
|
+
export function stripOkfFrontmatter(text: string): string {
|
|
95
|
+
return parseOkfDocument(text).body;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Validate a document against the OKF v0.1 rules the jeo-skills `okf` linter
|
|
100
|
+
* enforces: a leading `---` fence, a closing fence, and non-empty
|
|
101
|
+
* `type` / `title` / `description` fields. Returns an empty array when valid.
|
|
102
|
+
*/
|
|
103
|
+
export function validateOkfDocument(text: string): string[] {
|
|
104
|
+
const errors: string[] = [];
|
|
105
|
+
const lines = text.split("\n");
|
|
106
|
+
if (lines[0]?.trim() !== FENCE) {
|
|
107
|
+
return ["missing leading frontmatter fence (---)"];
|
|
108
|
+
}
|
|
109
|
+
const end = findFrontmatterEnd(lines);
|
|
110
|
+
if (end === -1) {
|
|
111
|
+
return ["unclosed frontmatter"];
|
|
112
|
+
}
|
|
113
|
+
const { frontmatter } = parseOkfDocument(text);
|
|
114
|
+
if (!frontmatter) {
|
|
115
|
+
return ["frontmatter is not a YAML mapping"];
|
|
116
|
+
}
|
|
117
|
+
for (const field of ["type", "title", "description"] as const) {
|
|
118
|
+
const value = frontmatter[field];
|
|
119
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
120
|
+
errors.push(`missing required field '${field}'`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return errors;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Format a unix-seconds timestamp as the ISO 8601 string OKF expects. */
|
|
127
|
+
export function okfTimestamp(unixSeconds: number = Math.floor(Date.now() / 1000)): string {
|
|
128
|
+
return new Date(unixSeconds * 1000).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
129
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jeo TUI identity contract: a name label on its own line leads assistant
|
|
3
|
+
* segments with visible prose (jeo-code's `agentLabel()` ported to jeopi).
|
|
4
|
+
* Tool-call-only blocks stay unlabeled, and an empty `ui.agentLabel` disables
|
|
5
|
+
* the label entirely.
|
|
6
|
+
*/
|
|
7
|
+
import { afterEach, beforeAll, describe, expect, it } from "bun:test";
|
|
8
|
+
import type { AssistantMessage } from "jeopi-ai";
|
|
9
|
+
import { Settings } from "../../../config/settings";
|
|
10
|
+
import { getThemeByName, setThemeInstance, type Theme } from "../../theme/theme";
|
|
11
|
+
import { AssistantMessageComponent, setAssistantAgentLabel } from "../assistant-message";
|
|
12
|
+
|
|
13
|
+
const strip = (lines: readonly string[]): string =>
|
|
14
|
+
lines
|
|
15
|
+
.join("\n")
|
|
16
|
+
.replace(/\x1b\]8;[^\x1b\x07]*(?:\x07|\x1b\\)/g, "")
|
|
17
|
+
.replace(/\x1b\[[0-9;]*m/g, "");
|
|
18
|
+
|
|
19
|
+
function assistantMessage(content: AssistantMessage["content"]): AssistantMessage {
|
|
20
|
+
return {
|
|
21
|
+
role: "assistant",
|
|
22
|
+
content,
|
|
23
|
+
stopReason: "stop",
|
|
24
|
+
usage: {
|
|
25
|
+
input: 0,
|
|
26
|
+
output: 0,
|
|
27
|
+
cacheRead: 0,
|
|
28
|
+
cacheWrite: 0,
|
|
29
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
30
|
+
},
|
|
31
|
+
timestamp: Date.now(),
|
|
32
|
+
} as AssistantMessage;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe("assistant agent label (jeo identity)", () => {
|
|
36
|
+
let uiTheme: Theme;
|
|
37
|
+
|
|
38
|
+
beforeAll(async () => {
|
|
39
|
+
await Settings.init({ inMemory: true });
|
|
40
|
+
const loaded = await getThemeByName("dark");
|
|
41
|
+
if (!loaded) throw new Error("theme unavailable");
|
|
42
|
+
uiTheme = loaded;
|
|
43
|
+
setThemeInstance(uiTheme);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
setAssistantAgentLabel("jeo");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("leads a reply with the label on its own line", () => {
|
|
51
|
+
const component = new AssistantMessageComponent(
|
|
52
|
+
assistantMessage([{ type: "text", text: "The fix is in place." }]),
|
|
53
|
+
);
|
|
54
|
+
const lines = strip(component.render(80)).split("\n");
|
|
55
|
+
const labelLine = lines.findIndex(line => line.trim() === "jeo");
|
|
56
|
+
const proseLine = lines.findIndex(line => line.includes("The fix is in place."));
|
|
57
|
+
expect(labelLine).toBeGreaterThanOrEqual(0);
|
|
58
|
+
expect(proseLine).toBeGreaterThan(labelLine);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("leads a visible thinking block with the label too", () => {
|
|
62
|
+
const component = new AssistantMessageComponent(
|
|
63
|
+
assistantMessage([{ type: "thinking", thinking: "Considering the edge cases first." }]),
|
|
64
|
+
false,
|
|
65
|
+
);
|
|
66
|
+
const text = strip(component.render(80));
|
|
67
|
+
expect(text).toContain("jeo");
|
|
68
|
+
expect(text).toContain("Considering the edge cases first.");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("leaves tool-call-only blocks unlabeled", () => {
|
|
72
|
+
const component = new AssistantMessageComponent(
|
|
73
|
+
assistantMessage([
|
|
74
|
+
{ type: "toolCall", id: "call-1", name: "read", arguments: { path: "a.ts" } },
|
|
75
|
+
] as AssistantMessage["content"]),
|
|
76
|
+
);
|
|
77
|
+
expect(strip(component.render(80))).not.toContain("jeo");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("an empty label disables the identity line", () => {
|
|
81
|
+
setAssistantAgentLabel("");
|
|
82
|
+
const component = new AssistantMessageComponent(assistantMessage([{ type: "text", text: "Done." }]));
|
|
83
|
+
const lines = strip(component.render(80)).split("\n");
|
|
84
|
+
expect(lines.some(line => line.trim() === "jeo")).toBe(false);
|
|
85
|
+
expect(lines.some(line => line.includes("Done."))).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -163,6 +163,24 @@ export function resetThinkingSpeedTracker(): void {
|
|
|
163
163
|
sharedSpeedTracker.reset();
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Agent identity label led on its own line before each assistant segment —
|
|
168
|
+
* jeo-code's TUI identity ported to jeopi: a bold accent `jeo` name leads
|
|
169
|
+
* thought blocks and replies so the transcript reads as jeo speaking.
|
|
170
|
+
* Configured from `ui.agentLabel` at session start; "" disables the label.
|
|
171
|
+
*/
|
|
172
|
+
let assistantAgentLabel = "jeo";
|
|
173
|
+
|
|
174
|
+
/** Set the assistant identity label ("" disables). Called from interactive-mode startup. */
|
|
175
|
+
export function setAssistantAgentLabel(label: string): void {
|
|
176
|
+
assistantAgentLabel = label.trim();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Current assistant identity label ("" when disabled). */
|
|
180
|
+
export function getAssistantAgentLabel(): string {
|
|
181
|
+
return assistantAgentLabel;
|
|
182
|
+
}
|
|
183
|
+
|
|
166
184
|
/**
|
|
167
185
|
* Linear-interpolate two `#rrggbb` colors in sRGB space. `t` clamps to [0,1]:
|
|
168
186
|
* `t = 0` → `from`, `t = 1` → `to`. Drives the streaming speed badge, fading
|
|
@@ -728,6 +746,15 @@ export class AssistantMessageComponent extends Container {
|
|
|
728
746
|
resolveThinkingDisplay(c, this.proseOnlyThinking).visible),
|
|
729
747
|
);
|
|
730
748
|
|
|
749
|
+
// jeo identity: a bold accent name label on its own line leads every
|
|
750
|
+
// assistant segment with visible prose or a live thinking pulse —
|
|
751
|
+
// mirrors jeo-code's TUI (`agentLabel()` leading thought blocks and the
|
|
752
|
+
// final reply). Tool-call-only blocks stay unlabeled; their cards carry
|
|
753
|
+
// their own identity.
|
|
754
|
+
if (assistantAgentLabel && (hasVisibleContent || this.#shouldAnimateThinking(message))) {
|
|
755
|
+
this.#contentContainer.addChild(new Text(theme.fg("accent", theme.bold(assistantAgentLabel)), 1, 0));
|
|
756
|
+
}
|
|
757
|
+
|
|
731
758
|
// Render content in order
|
|
732
759
|
let thinkingIndex = 0;
|
|
733
760
|
for (let i = 0; i < message.content.length; i++) {
|
|
@@ -266,7 +266,7 @@ export class ExtensionDashboard implements Component {
|
|
|
266
266
|
if (!sm) return;
|
|
267
267
|
|
|
268
268
|
// MCP toggles route through the canonical denylist in
|
|
269
|
-
// `~/.
|
|
269
|
+
// `~/.jeopi/agent/mcp.json` so `/mcp list`, the MCP runtime, and this
|
|
270
270
|
// dashboard agree on every server's enabled state (issue #3827).
|
|
271
271
|
if (extensionId.startsWith("mcp:")) {
|
|
272
272
|
void this.#toggleMcpExtension(extensionId, enabled, sm);
|
|
@@ -986,8 +986,8 @@ export class MCPCommandController {
|
|
|
986
986
|
const usesMcpRemote = [config.command, ...(config.args ?? [])].some(part => part?.includes("mcp-remote"));
|
|
987
987
|
throw new Error(
|
|
988
988
|
usesMcpRemote
|
|
989
|
-
? `this server proxies OAuth through mcp-remote, which caches tokens machine-wide in ~/.mcp-auth (shared across every
|
|
990
|
-
: `stdio servers manage their own credentials, so
|
|
989
|
+
? `this server proxies OAuth through mcp-remote, which caches tokens machine-wide in ~/.mcp-auth (shared across every jeopi profile). Clear ~/.mcp-auth to force a fresh login, or replace the proxy with ${httpHint} so jeopi manages OAuth per profile.`
|
|
990
|
+
: `stdio servers manage their own credentials, so jeopi has no OAuth to reauthorize. If the service supports OAuth over HTTP, configure it as ${httpHint} instead.`,
|
|
991
991
|
);
|
|
992
992
|
}
|
|
993
993
|
// First test if server actually needs auth by connecting without OAuth
|
|
@@ -32,8 +32,8 @@ interface GenerateCandidateOptions {
|
|
|
32
32
|
type SaveCandidateResult = { kind: "saved" | "aborted" | "rejected" } | { kind: "amend"; feedback: string };
|
|
33
33
|
|
|
34
34
|
const MAX_ATTEMPTS = 3;
|
|
35
|
-
const PROJECT_OPTION = "This project (.
|
|
36
|
-
const GLOBAL_OPTION = "Global — all projects (~/.
|
|
35
|
+
const PROJECT_OPTION = "This project (.jeopi/rules)";
|
|
36
|
+
const GLOBAL_OPTION = "Global — all projects (~/.jeopi/agent/rules)";
|
|
37
37
|
const AMEND_OPTION = "Amend with feedback…";
|
|
38
38
|
|
|
39
39
|
export class OmfgController {
|
|
@@ -281,7 +281,7 @@ export class SSHCommandController {
|
|
|
281
281
|
|
|
282
282
|
// Show user-level hosts
|
|
283
283
|
if (userHosts.length > 0) {
|
|
284
|
-
lines.push(theme.fg("accent", "User level") + theme.fg("muted", ` (~/.
|
|
284
|
+
lines.push(theme.fg("accent", "User level") + theme.fg("muted", ` (~/.jeopi/agent/ssh.json):`));
|
|
285
285
|
for (const name of userHosts) {
|
|
286
286
|
const config = userConfig.hosts![name];
|
|
287
287
|
const details = this.#formatHostDetails(config);
|
|
@@ -292,7 +292,7 @@ export class SSHCommandController {
|
|
|
292
292
|
|
|
293
293
|
// Show project-level hosts
|
|
294
294
|
if (projectHosts.length > 0) {
|
|
295
|
-
lines.push(theme.fg("accent", "Project level") + theme.fg("muted", ` (.
|
|
295
|
+
lines.push(theme.fg("accent", "Project level") + theme.fg("muted", ` (.jeopi/ssh.json):`));
|
|
296
296
|
for (const name of projectHosts) {
|
|
297
297
|
const config = projectConfig.hosts![name];
|
|
298
298
|
const details = this.#formatHostDetails(config);
|
|
@@ -122,7 +122,7 @@ import { getEditorCommand, openInEditor } from "../utils/external-editor";
|
|
|
122
122
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../utils/session-color";
|
|
123
123
|
import { messageHasDisplayableThinking } from "../utils/thinking-display";
|
|
124
124
|
import { popTerminalTitle, pushTerminalTitle, setSessionTerminalTitle } from "../utils/title-generator";
|
|
125
|
-
import type
|
|
125
|
+
import { type AssistantMessageComponent, setAssistantAgentLabel } from "./components/assistant-message";
|
|
126
126
|
import type { BashExecutionComponent } from "./components/bash-execution";
|
|
127
127
|
import { ChatBlock, type ChatBlockHost } from "./components/chat-block";
|
|
128
128
|
import { CustomEditor } from "./components/custom-editor";
|
|
@@ -677,6 +677,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
677
677
|
|
|
678
678
|
this.hideThinkingBlock = settings.get("hideThinkingBlock");
|
|
679
679
|
this.proseOnlyThinking = settings.get("proseOnlyThinking");
|
|
680
|
+
// jeo TUI identity: the accent name label leading assistant segments.
|
|
681
|
+
setAssistantAgentLabel(settings.get("ui.agentLabel") ?? "jeo");
|
|
680
682
|
|
|
681
683
|
const hookCommands: SlashCommand[] = (
|
|
682
684
|
this.session.extensionRunner?.getRegisteredCommands(BUILTIN_SLASH_COMMAND_RESERVED_NAMES) ?? []
|
|
@@ -42,7 +42,7 @@ export async function selectSetupScenes(
|
|
|
42
42
|
if (!isTTY) return [];
|
|
43
43
|
if (!options.force) {
|
|
44
44
|
if (options.resuming) return [];
|
|
45
|
-
if (setupSkipEnvEnabled(options.skipEnv ?? Bun.env.
|
|
45
|
+
if (setupSkipEnvEnabled(options.skipEnv ?? Bun.env.JEOPI_SKIP_SETUP)) return [];
|
|
46
46
|
if (options.setupWizardEnabled === false) return [];
|
|
47
47
|
}
|
|
48
48
|
|