klyro 1.0.5 → 1.0.7
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/README.md +13 -0
- package/dist/agent/custom-agents.d.ts +3 -0
- package/dist/agent/custom-agents.js +96 -0
- package/dist/agent/orchestrator.d.ts +26 -0
- package/dist/agent/orchestrator.js +41 -4
- package/dist/agent/runtime.d.ts +15 -0
- package/dist/agent/runtime.js +232 -61
- package/dist/chat.d.ts +10 -0
- package/dist/chat.js +39 -7
- package/dist/checkpoints/store.d.ts +11 -0
- package/dist/checkpoints/store.js +32 -0
- package/dist/cli/auth.d.ts +10 -3
- package/dist/cli/auth.js +43 -5
- package/dist/cli/completion.js +2 -2
- package/dist/cli/config.d.ts +4 -4
- package/dist/cli/doctor.js +0 -1
- package/dist/cli/eval.d.ts +15 -1
- package/dist/cli/eval.js +43 -5
- package/dist/cli/hooks.d.ts +74 -5
- package/dist/cli/hooks.js +118 -7
- package/dist/cli/init.d.ts +6 -0
- package/dist/cli/init.js +60 -0
- package/dist/cli/keychain.d.ts +10 -0
- package/dist/cli/keychain.js +86 -0
- package/dist/cli/repl.js +188 -30
- package/dist/cli/run.d.ts +7 -1
- package/dist/cli/run.js +92 -50
- package/dist/cli/setup.js +3 -2
- package/dist/cli/slash/custom.d.ts +25 -0
- package/dist/cli/slash/custom.js +166 -0
- package/dist/cli/slash/parser.d.ts +9 -1
- package/dist/cli/slash/parser.js +34 -9
- package/dist/cli/update.d.ts +3 -1
- package/dist/cli/update.js +16 -1
- package/dist/context/accounting.d.ts +6 -0
- package/dist/context/accounting.js +8 -2
- package/dist/context/compaction.d.ts +2 -1
- package/dist/context/compaction.js +39 -12
- package/dist/context/memory.d.ts +11 -0
- package/dist/context/memory.js +59 -4
- package/dist/eval/harness.d.ts +40 -5
- package/dist/eval/harness.js +103 -10
- package/dist/eval/judge.d.ts +32 -0
- package/dist/eval/judge.js +63 -0
- package/dist/eval/tasks.js +134 -0
- package/dist/index.js +239 -130
- package/dist/mcp/auth.d.ts +85 -0
- package/dist/mcp/auth.js +249 -0
- package/dist/mcp/client.d.ts +15 -0
- package/dist/mcp/client.js +42 -2
- package/dist/mcp/config.d.ts +31 -1
- package/dist/mcp/config.js +84 -1
- package/dist/mcp/registry.d.ts +19 -0
- package/dist/mcp/registry.js +118 -2
- package/dist/mcp/remote.d.ts +36 -0
- package/dist/mcp/remote.js +207 -0
- package/dist/mcp/sse.d.ts +42 -0
- package/dist/mcp/sse.js +310 -0
- package/dist/persistence/audit.d.ts +15 -3
- package/dist/persistence/audit.js +84 -13
- package/dist/persistence/store.d.ts +9 -0
- package/dist/persistence/store.js +17 -0
- package/dist/policy/approval.d.ts +15 -1
- package/dist/policy/approval.js +8 -0
- package/dist/policy/engine.js +9 -0
- package/dist/providers/endpoints.d.ts +43 -0
- package/dist/providers/endpoints.js +104 -0
- package/dist/providers.js +17 -14
- package/dist/tools/shell/shell-exec.d.ts +13 -0
- package/dist/tools/shell/shell-exec.js +64 -2
- package/dist/tui/app.js +172 -15
- package/dist/tui/app.test.js +27 -2
- package/dist/tui/approval.js +55 -1
- package/dist/tui/scroll-model.d.ts +2 -2
- package/dist/tui/scroll-model.js +9 -3
- package/dist/tui/tokens.d.ts +8 -11
- package/dist/tui/tokens.js +18 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,6 +55,19 @@ node dist/index.js chat
|
|
|
55
55
|
| `KLYRO_WORKER=0` | Disable subprocess isolation for subagents |
|
|
56
56
|
| `KLYRO_SESSIONS_DIR`, `KLYRO_UPDATE_CACHE`, `KLYRO_CREDENTIALS_FILE` | Relocatable state (tests + power users) |
|
|
57
57
|
|
|
58
|
+
## New in recent releases
|
|
59
|
+
|
|
60
|
+
- `klyro run --bare` — deterministic runs: skips MCP, hooks, memory/KLYRO.md/context, persistence
|
|
61
|
+
- `klyro mcp trust <name>` / `mcp prompts [server]` / `mcp add <name> <https-url>` — remote MCP + prompt trust
|
|
62
|
+
- `klyro agents lint` — validate `.klyro/agents/*.md` (ids, tool names)
|
|
63
|
+
- `klyro init` — scan-seeded `KLYRO.md` + `.mcp.json` (never overwrites)
|
|
64
|
+
- `klyro update --apply` — opt-in self-apply of the verified update
|
|
65
|
+
- `klyro eval --judge-model <id>` — model-graded rubric scoring
|
|
66
|
+
- Hooks: `matcher` scoping, stdin JSON, `sessionStart`/`sessionEnd`/`stop` events, JSON verdicts
|
|
67
|
+
- Custom agents (`.klyro/agents/*.md`), custom commands (`.klyro/commands/*.md`), vim mode (`/vim`), `@`-file completion
|
|
68
|
+
- Credentials prefer the OS keychain (macOS Keychain, Linux libsecret), 0600 file fallback
|
|
69
|
+
- Headless JSON ends with exactly one stable `kind:result` envelope (parse the LAST line)
|
|
70
|
+
|
|
58
71
|
## Documentation
|
|
59
72
|
|
|
60
73
|
| Doc | Purpose |
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom subagents from markdown files:
|
|
3
|
+
* `<cwd>/.klyro/agents/*.md` (project) + `~/.klyro/agents/*.md` (global).
|
|
4
|
+
* Project wins on id clash (including overriding a builtin).
|
|
5
|
+
*
|
|
6
|
+
* Frontmatter fields: name (default: filename), description, tools
|
|
7
|
+
* (comma/list — omitted inherits parent tools), model, readonly,
|
|
8
|
+
* canSpawn, maxSteps, maxTokens, maxCost, maxTimeMs, allowedPaths.
|
|
9
|
+
* The markdown body becomes specialist instructions (`prompt`) prepended
|
|
10
|
+
* to the delegated task. Unknown tool names are NOT rejected here —
|
|
11
|
+
* `resolveCapabilities` drops them with reasons at spawn time.
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from 'node:fs';
|
|
14
|
+
import * as os from 'node:os';
|
|
15
|
+
import * as path from 'node:path';
|
|
16
|
+
import { parseFrontmatter, parseList, parseBool, parseInt_ } from '../cli/slash/custom.js';
|
|
17
|
+
const AGENT_ID_RE = /^[A-Za-z0-9_-]{1,32}$/;
|
|
18
|
+
function readAgentFile(file, source) {
|
|
19
|
+
let raw;
|
|
20
|
+
try {
|
|
21
|
+
raw = fs.readFileSync(file, 'utf-8');
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const { data, body } = parseFrontmatter(raw);
|
|
27
|
+
const fallback = path.basename(file, path.extname(file));
|
|
28
|
+
const id = (data['name'] || fallback).toLowerCase();
|
|
29
|
+
if (!AGENT_ID_RE.test(id))
|
|
30
|
+
return null;
|
|
31
|
+
const description = data['description'] || `Custom agent ${id}`;
|
|
32
|
+
const def = { id, description };
|
|
33
|
+
const tools = parseList(data['tools']);
|
|
34
|
+
if (tools.length > 0)
|
|
35
|
+
def.allowedTools = tools;
|
|
36
|
+
if (data['model'])
|
|
37
|
+
def.model = data['model'];
|
|
38
|
+
if (data['readonly'] !== undefined && data['readonly'] !== '')
|
|
39
|
+
def.readonly = parseBool(data['readonly'], false);
|
|
40
|
+
if (data['canSpawn'] !== undefined && data['canSpawn'] !== '')
|
|
41
|
+
def.canSpawn = parseBool(data['canSpawn'], false);
|
|
42
|
+
const maxSteps = parseInt_(data['maxsteps']);
|
|
43
|
+
if (maxSteps !== undefined)
|
|
44
|
+
def.maxSteps = maxSteps;
|
|
45
|
+
const maxTokens = parseInt_(data['maxtokens']);
|
|
46
|
+
if (maxTokens !== undefined)
|
|
47
|
+
def.maxTokens = maxTokens;
|
|
48
|
+
const maxCost = data['maxcost'] !== undefined && data['maxcost'] !== '' ? Number(data['maxcost']) : undefined;
|
|
49
|
+
if (maxCost !== undefined && Number.isFinite(maxCost) && maxCost > 0)
|
|
50
|
+
def.maxCost = maxCost;
|
|
51
|
+
const maxTimeMs = parseInt_(data['maxtimems']);
|
|
52
|
+
if (maxTimeMs !== undefined)
|
|
53
|
+
def.maxTimeMs = maxTimeMs;
|
|
54
|
+
const paths = parseList(data['allowedpaths']);
|
|
55
|
+
if (paths.length > 0)
|
|
56
|
+
def.allowedPaths = paths;
|
|
57
|
+
const prompt = body.trim();
|
|
58
|
+
if (prompt)
|
|
59
|
+
def.prompt = prompt;
|
|
60
|
+
def.source = source;
|
|
61
|
+
return def;
|
|
62
|
+
}
|
|
63
|
+
function listAgentFiles(dir) {
|
|
64
|
+
let entries;
|
|
65
|
+
try {
|
|
66
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
return entries
|
|
72
|
+
.filter((e) => e.isFile() && e.name.toLowerCase().endsWith('.md'))
|
|
73
|
+
.map((e) => path.join(dir, e.name))
|
|
74
|
+
.sort();
|
|
75
|
+
}
|
|
76
|
+
/** Load custom agents: global first, project wins on id clash. Never throws. */
|
|
77
|
+
export function loadCustomAgents(cwd) {
|
|
78
|
+
const byId = new Map();
|
|
79
|
+
try {
|
|
80
|
+
const home = os.homedir() || process.cwd();
|
|
81
|
+
for (const f of listAgentFiles(path.join(home, '.klyro', 'agents'))) {
|
|
82
|
+
const d = readAgentFile(f, 'global');
|
|
83
|
+
if (d)
|
|
84
|
+
byId.set(d.id, d);
|
|
85
|
+
}
|
|
86
|
+
for (const f of listAgentFiles(path.join(cwd, '.klyro', 'agents'))) {
|
|
87
|
+
const d = readAgentFile(f, 'project');
|
|
88
|
+
if (d)
|
|
89
|
+
byId.set(d.id, d);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return [...byId.values()];
|
|
94
|
+
}
|
|
95
|
+
return [...byId.values()];
|
|
96
|
+
}
|
|
@@ -43,9 +43,21 @@ export interface AgentDefinition {
|
|
|
43
43
|
* spawn time (`undefined` = no additional constraint).
|
|
44
44
|
*/
|
|
45
45
|
allowedPaths?: string[];
|
|
46
|
+
/**
|
|
47
|
+
* Specialist instructions (from `.klyro/agents/*.md` body or programmatic
|
|
48
|
+
* defs). Prepended to the delegated task at spawn time.
|
|
49
|
+
*/
|
|
50
|
+
prompt?: string;
|
|
51
|
+
/** Where the definition came from (builtins omit this = 'builtin'). */
|
|
52
|
+
source?: 'builtin' | 'project' | 'global';
|
|
46
53
|
}
|
|
47
54
|
/** Default agents a model can delegate to. */
|
|
48
55
|
export declare const BUILTIN_AGENTS: readonly AgentDefinition[];
|
|
56
|
+
/**
|
|
57
|
+
* Layer specialist instructions into a child's system prompt (not the user
|
|
58
|
+
* task). Pure — unit-tested directly. No-op when the def has no prompt.
|
|
59
|
+
*/
|
|
60
|
+
export declare function layerSpecialistPrompt(base: RuntimeDeps['systemPrompt'], def: Pick<AgentDefinition, 'id' | 'prompt'>): RuntimeDeps['systemPrompt'];
|
|
49
61
|
/** Compact summary returned to the parent — the child's transcript stays separate. */
|
|
50
62
|
export interface ChildSummary {
|
|
51
63
|
taskId: string;
|
|
@@ -158,7 +170,20 @@ export interface OrchestratorOpts {
|
|
|
158
170
|
* always isolate. Defaults to false.
|
|
159
171
|
*/
|
|
160
172
|
isTui?: boolean;
|
|
173
|
+
/**
|
|
174
|
+
* Working directory used to discover custom agents
|
|
175
|
+
* (`.klyro/agents/*.md`). Defaults to `process.cwd()`.
|
|
176
|
+
*/
|
|
177
|
+
cwd?: string;
|
|
161
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* All agents: builtins plus custom `.klyro/agents/*.md` definitions.
|
|
181
|
+
* Custom ids win on clash (including overriding a builtin) — the override
|
|
182
|
+
* is surfaced via `source`. No instance needed; used by CLI + spawn paths.
|
|
183
|
+
*/
|
|
184
|
+
export declare function listAllAgents(cwd?: string): AgentDefinition[];
|
|
185
|
+
/** Find one agent by id across builtins + custom files. */
|
|
186
|
+
export declare function findAgent(id: string, cwd?: string): AgentDefinition | undefined;
|
|
162
187
|
/**
|
|
163
188
|
* Build a `subtask.progress` note for one finished tool call.
|
|
164
189
|
* Pure — unit-tested directly (see agent-tools.test.ts).
|
|
@@ -182,6 +207,7 @@ export declare class AgentOrchestrator {
|
|
|
182
207
|
readonly taskManager: TaskManager;
|
|
183
208
|
readonly workerSpawner: WorkerSpawner;
|
|
184
209
|
readonly isTui: boolean;
|
|
210
|
+
private readonly customCwd;
|
|
185
211
|
/** Per-task spawn metadata: capability drops + worktree placement. */
|
|
186
212
|
private readonly taskMeta;
|
|
187
213
|
/** Finished summaries not yet drained via `drainCompletions`. */
|
|
@@ -19,6 +19,7 @@ import { TaskManager } from './task-manager.js';
|
|
|
19
19
|
import { WorkerSpawner } from './worker-spawner.js';
|
|
20
20
|
import { resolveCapabilities, DEFAULT_WRITE_TOOLS, DEFAULT_SPAWN_TOOLS, DEFAULT_DENIED_TOOLS, } from './capabilities.js';
|
|
21
21
|
import { forkChild, workerEntryPath } from './child-worker.js';
|
|
22
|
+
import { loadCustomAgents } from './custom-agents.js';
|
|
22
23
|
import { resolveAndFollowSymlinks } from '../policy/path-guard.js';
|
|
23
24
|
import { ensureGitRepo, createWorktree, mergeWorktree, removeWorktree, deleteBranch, } from './worktree-manager.js';
|
|
24
25
|
/** Concurrency budgets enforced in `spawnAgent` (CONCURRENCY_LIMIT on exceed). */
|
|
@@ -70,6 +71,39 @@ export const BUILTIN_AGENTS = [
|
|
|
70
71
|
allowedTools: ['read_file', 'list_directory', 'glob', 'grep', 'search_files', 'repo_map', 'recent_files'],
|
|
71
72
|
},
|
|
72
73
|
];
|
|
74
|
+
/**
|
|
75
|
+
* Layer specialist instructions into a child's system prompt (not the user
|
|
76
|
+
* task). Pure — unit-tested directly. No-op when the def has no prompt.
|
|
77
|
+
*/
|
|
78
|
+
export function layerSpecialistPrompt(base, def) {
|
|
79
|
+
if (!def.prompt)
|
|
80
|
+
return base;
|
|
81
|
+
return (ctx) => {
|
|
82
|
+
const r = base(ctx);
|
|
83
|
+
const block = `\n\n<specialist id="${def.id}">\n${def.prompt}\n</specialist>`;
|
|
84
|
+
return typeof r === 'string' ? r + block : { ...r, system: r.system + block };
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* All agents: builtins plus custom `.klyro/agents/*.md` definitions.
|
|
89
|
+
* Custom ids win on clash (including overriding a builtin) — the override
|
|
90
|
+
* is surfaced via `source`. No instance needed; used by CLI + spawn paths.
|
|
91
|
+
*/
|
|
92
|
+
export function listAllAgents(cwd) {
|
|
93
|
+
const byId = new Map();
|
|
94
|
+
for (const d of BUILTIN_AGENTS)
|
|
95
|
+
byId.set(d.id, { ...d, source: 'builtin' });
|
|
96
|
+
try {
|
|
97
|
+
for (const d of loadCustomAgents(cwd ?? process.cwd()))
|
|
98
|
+
byId.set(d.id, d);
|
|
99
|
+
}
|
|
100
|
+
catch { /* custom agents are best-effort */ }
|
|
101
|
+
return [...byId.values()];
|
|
102
|
+
}
|
|
103
|
+
/** Find one agent by id across builtins + custom files. */
|
|
104
|
+
export function findAgent(id, cwd) {
|
|
105
|
+
return listAllAgents(cwd).find((a) => a.id === id);
|
|
106
|
+
}
|
|
73
107
|
/** Map a runtime `RunResult.status` to a task status. */
|
|
74
108
|
function mapResultStatus(status) {
|
|
75
109
|
switch (status) {
|
|
@@ -132,6 +166,7 @@ export class AgentOrchestrator {
|
|
|
132
166
|
taskManager;
|
|
133
167
|
workerSpawner;
|
|
134
168
|
isTui;
|
|
169
|
+
customCwd;
|
|
135
170
|
/** Per-task spawn metadata: capability drops + worktree placement. */
|
|
136
171
|
taskMeta = new Map();
|
|
137
172
|
/** Finished summaries not yet drained via `drainCompletions`. */
|
|
@@ -142,12 +177,13 @@ export class AgentOrchestrator {
|
|
|
142
177
|
this.taskManager = opts.taskManager ?? new TaskManager({ sessionId: opts.sessionId });
|
|
143
178
|
this.workerSpawner = opts.workerSpawner ?? new WorkerSpawner();
|
|
144
179
|
this.isTui = opts.isTui ?? false;
|
|
180
|
+
this.customCwd = opts.cwd;
|
|
145
181
|
}
|
|
146
182
|
listAgents() {
|
|
147
|
-
return
|
|
183
|
+
return listAllAgents(this.customCwd);
|
|
148
184
|
}
|
|
149
185
|
getAgent(id) {
|
|
150
|
-
return
|
|
186
|
+
return listAllAgents(this.customCwd).find((a) => a.id === id);
|
|
151
187
|
}
|
|
152
188
|
/** Build the bridge the parent's runtime hands to tools. */
|
|
153
189
|
bridgeFor(parent) {
|
|
@@ -345,7 +381,8 @@ export class AgentOrchestrator {
|
|
|
345
381
|
}
|
|
346
382
|
this.taskMeta.set(record.id, worktree ? { def, dropped: resolved.dropped, worktree, repoCwd } : { def, dropped: resolved.dropped });
|
|
347
383
|
const childRegistry = new ScopedRegistry(this.deps.registry, resolved.allowed);
|
|
348
|
-
const
|
|
384
|
+
const childPromptFn = layerSpecialistPrompt(this.deps.systemPrompt, def);
|
|
385
|
+
const childDeps = { ...this.deps, registry: childRegistry, systemPrompt: childPromptFn };
|
|
349
386
|
const childRef = {
|
|
350
387
|
taskId: record.id,
|
|
351
388
|
...(parent.taskId !== undefined ? { parentTaskId: parent.taskId } : {}),
|
|
@@ -405,7 +442,7 @@ export class AgentOrchestrator {
|
|
|
405
442
|
let childOutcome;
|
|
406
443
|
try {
|
|
407
444
|
if (useProcessIsolation) {
|
|
408
|
-
const sysPrompt = resolveSystemPrompt(
|
|
445
|
+
const sysPrompt = resolveSystemPrompt(childPromptFn, { cwd: childCwd });
|
|
409
446
|
// Splice the volatile telemetry suffix into the stable prefix so the
|
|
410
447
|
// child's provider sees one system string. Telemetry is best-effort
|
|
411
448
|
// inside the child (it re-emits); the goal here is parity, not
|
package/dist/agent/runtime.d.ts
CHANGED
|
@@ -85,6 +85,11 @@ export interface RunOptions {
|
|
|
85
85
|
temperature?: number;
|
|
86
86
|
signal?: AbortSignal;
|
|
87
87
|
nonInteractive: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Bare mode: skip all hooks (load + sessionStart/stop). The caller is
|
|
90
|
+
* responsible for skipping MCP/persistence/context (see runOnce `bare`).
|
|
91
|
+
*/
|
|
92
|
+
bare?: boolean;
|
|
88
93
|
/**
|
|
89
94
|
* Optional pre-existing transcript to seed the conversation. When set,
|
|
90
95
|
* the runtime skips the initial `[{role:'user', content:[text(task)]}]`
|
|
@@ -126,6 +131,16 @@ export interface RunOptions {
|
|
|
126
131
|
store?: import('../persistence/store.js').SessionStore;
|
|
127
132
|
sessionId?: string;
|
|
128
133
|
};
|
|
134
|
+
/**
|
|
135
|
+
* Level 10 — tamper-evident audit. When an AuditLog is provided, the
|
|
136
|
+
* runtime writes policy decisions and tool completions into the chained
|
|
137
|
+
* audit stream (complements, does not replace, persistence). Defaults off
|
|
138
|
+
* so callers opt in; `klyro` CLI enables it when a sessions dir exists.
|
|
139
|
+
*/
|
|
140
|
+
audit?: {
|
|
141
|
+
log?: import('../persistence/audit.js').AuditLog;
|
|
142
|
+
sessionId?: string;
|
|
143
|
+
};
|
|
129
144
|
/**
|
|
130
145
|
* Orchestration context (P0). Present for any agent that is itself managed
|
|
131
146
|
* by an AgentOrchestrator — so a child knows who its parent is, how deep the
|