@pikiloom/kernel 0.1.5 → 0.2.1
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 +35 -0
- package/dist/contracts/driver.d.ts +15 -0
- package/dist/contracts/ports.d.ts +2 -0
- package/dist/drivers/claude.d.ts +5 -1
- package/dist/drivers/claude.js +4 -0
- package/dist/drivers/codex.d.ts +5 -1
- package/dist/drivers/codex.js +4 -0
- package/dist/drivers/gemini.d.ts +5 -1
- package/dist/drivers/gemini.js +4 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -0
- package/dist/ports/defaults.js +7 -1
- package/dist/runtime/loom.d.ts +10 -0
- package/dist/runtime/loom.js +18 -2
- package/dist/workspace/index.d.ts +5 -0
- package/dist/workspace/index.js +7 -0
- package/dist/workspace/mcp.d.ts +44 -0
- package/dist/workspace/mcp.js +82 -0
- package/dist/workspace/native.d.ts +14 -0
- package/dist/workspace/native.js +340 -0
- package/dist/workspace/paths.d.ts +33 -0
- package/dist/workspace/paths.js +40 -0
- package/dist/workspace/sessions.d.ts +49 -0
- package/dist/workspace/sessions.js +130 -0
- package/dist/workspace/skills.d.ts +44 -0
- package/dist/workspace/skills.js +173 -0
- package/llms.txt +23 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -275,6 +275,40 @@ plugins are the composable per-capability layer on top.)
|
|
|
275
275
|
|
|
276
276
|
---
|
|
277
277
|
|
|
278
|
+
## Workspace: unified directory + session / skill / mcp management
|
|
279
|
+
|
|
280
|
+
One explicitly-configurable **top-level directory** (`createLoom({ stateDirName })`, default
|
|
281
|
+
`'pikiloom'` → `.pikiloom`) gives a consuming app the same "everything under one folder" model
|
|
282
|
+
pikiloom uses, resolved in two scopes — global (`~/.pikiloom`) and per-workspace
|
|
283
|
+
(`<workdir>/.pikiloom`). It's exposed off the `Loom`:
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
const loom = createLoom({ drivers: [new ClaudeDriver(), new CodexDriver()], stateDirName: 'pikiloom' });
|
|
287
|
+
|
|
288
|
+
// Unified, searchable session list — the kernel's MANAGED sessions (scoped per workspace by the
|
|
289
|
+
// cwd they ran in) MERGED with each agent's OWN native sessions (claude/codex read their on-disk
|
|
290
|
+
// transcripts). Global view, per-workspace view, and search — all kernel-owned:
|
|
291
|
+
await loom.sessions.list({ scope: 'workspace', workdir }); // this folder (managed + native)
|
|
292
|
+
await loom.sessions.list({ scope: 'global' }); // every workspace's managed sessions
|
|
293
|
+
await loom.sessions.search({ query: 'deploy', workdir });
|
|
294
|
+
|
|
295
|
+
// Skills registry: one canonical dir, symlinked into every agent's skills dir.
|
|
296
|
+
loom.skills.ensureLinks('workspace', workdir); // <wd>/.claude/skills + .agents/skills → <wd>/.pikiloom/skills
|
|
297
|
+
loom.skills.list({ workdir }); // installed skills (workspace + global)
|
|
298
|
+
await loom.skills.search('pdf'); // installable skills (npm)
|
|
299
|
+
|
|
300
|
+
// MCP catalog + discovery (enabling a server stays on the Plugin.tools()/ToolProvider seam):
|
|
301
|
+
loom.mcp.recommended(); // curated catalog
|
|
302
|
+
await loom.mcp.search('postgres'); // MCP registry → npm
|
|
303
|
+
|
|
304
|
+
loom.paths.skillsDir('global'); // ~/.pikiloom/skills, etc.
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
A driver opts into native discovery by implementing
|
|
308
|
+
`listNativeSessions?({ workdir, limit }): NativeSessionInfo[]` (Claude/Codex/Gemini do; the pure
|
|
309
|
+
readers are also exported as `discover{Claude,Codex,Gemini}NativeSessions`). All of this is
|
|
310
|
+
node-builtins-only and additive — every existing port/default is unchanged.
|
|
311
|
+
|
|
278
312
|
## Exports
|
|
279
313
|
|
|
280
314
|
Main entry `@pikiloom/kernel` re-exports everything. Subpaths: `@pikiloom/kernel/drivers`,
|
|
@@ -284,6 +318,7 @@ Main entry `@pikiloom/kernel` re-exports everything. Subpaths: `@pikiloom/kernel
|
|
|
284
318
|
- Drivers: `EchoDriver`, `ClaudeDriver`, `CodexDriver`, `GeminiDriver`, `HermesDriver`
|
|
285
319
|
- Surfaces: `WebSurface`, `CliSurface`
|
|
286
320
|
- Ports/defaults: `FsSessionStore`, `NullModelResolver`, `NoopToolProvider`, `PassthroughSystemPromptBuilder`, `AutoCancelInteractionHandler`, `DeferToTerminalInteractionHandler`, `NoopCatalog`, `defaultBaseDir`
|
|
321
|
+
- Workspace: `resolveLoomPaths`, `SessionsManager`, `SkillsManager`, `McpRegistry`, `ensureDirSymlink`, `discover{Claude,Codex,Gemini}NativeSessions` + types `LoomPaths`, `LoomScope`, `ManagedSessionInfo`, `NativeSessionInfo`, `SkillInfo`, `McpCatalogEntry`
|
|
287
322
|
- Protocol: `UniversalSnapshot`, `diffSnapshot`, `applySnapshotPatch`, `emptySnapshot`, `PROTOCOL_VERSION`, all wire/`Client*`/`Server*` message types
|
|
288
323
|
- Types: `AgentDriver`, `AgentTurnInput`, `DriverContext`, `DriverEvent`, `DriverResult`, `LoomIO`, `PromptInput`, `Surface`, `Plugin`, `SpawnContribution`, `SessionStore`, `ModelResolver`, `ToolProvider`, `SystemPromptBuilder`, `InteractionHandler`, `Catalog`, …
|
|
289
324
|
|
|
@@ -82,6 +82,17 @@ export interface TuiSpec {
|
|
|
82
82
|
cwd: string;
|
|
83
83
|
env?: Record<string, string>;
|
|
84
84
|
}
|
|
85
|
+
export interface NativeSessionInfo {
|
|
86
|
+
sessionId: string;
|
|
87
|
+
title: string | null;
|
|
88
|
+
preview: string | null;
|
|
89
|
+
cwd: string | null;
|
|
90
|
+
model: string | null;
|
|
91
|
+
createdAt: string | null;
|
|
92
|
+
updatedAt: string | null;
|
|
93
|
+
running: boolean;
|
|
94
|
+
messageCount?: number | null;
|
|
95
|
+
}
|
|
85
96
|
export interface AgentDriver {
|
|
86
97
|
readonly id: string;
|
|
87
98
|
readonly capabilities?: {
|
|
@@ -92,4 +103,8 @@ export interface AgentDriver {
|
|
|
92
103
|
};
|
|
93
104
|
run(input: AgentTurnInput, ctx: DriverContext): Promise<DriverResult>;
|
|
94
105
|
tui?(input: TuiInput): TuiSpec;
|
|
106
|
+
listNativeSessions?(opts: {
|
|
107
|
+
workdir: string;
|
|
108
|
+
limit?: number;
|
|
109
|
+
}): NativeSessionInfo[] | Promise<NativeSessionInfo[]>;
|
|
95
110
|
}
|
|
@@ -5,9 +5,11 @@ export interface CoreSessionRecord {
|
|
|
5
5
|
sessionId: string;
|
|
6
6
|
nativeSessionId?: string | null;
|
|
7
7
|
workspacePath: string;
|
|
8
|
+
workdir?: string | null;
|
|
8
9
|
createdAt: string;
|
|
9
10
|
updatedAt: string;
|
|
10
11
|
title?: string | null;
|
|
12
|
+
preview?: string | null;
|
|
11
13
|
model?: string | null;
|
|
12
14
|
effort?: string | null;
|
|
13
15
|
runState?: 'running' | 'completed' | 'incomplete';
|
package/dist/drivers/claude.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentDriver, AgentTurnInput, DriverContext, DriverResult, DriverEvent, TuiInput, TuiSpec } from '../contracts/driver.js';
|
|
1
|
+
import type { AgentDriver, AgentTurnInput, DriverContext, DriverResult, DriverEvent, TuiInput, TuiSpec, NativeSessionInfo } from '../contracts/driver.js';
|
|
2
2
|
import type { UniversalUsage, UniversalPlan } from '../protocol/index.js';
|
|
3
3
|
export declare class ClaudeDriver implements AgentDriver {
|
|
4
4
|
private readonly bin;
|
|
@@ -11,6 +11,10 @@ export declare class ClaudeDriver implements AgentDriver {
|
|
|
11
11
|
};
|
|
12
12
|
constructor(bin?: string);
|
|
13
13
|
tui(input: TuiInput): TuiSpec;
|
|
14
|
+
listNativeSessions(opts: {
|
|
15
|
+
workdir: string;
|
|
16
|
+
limit?: number;
|
|
17
|
+
}): NativeSessionInfo[];
|
|
14
18
|
run(input: AgentTurnInput, ctx: DriverContext): Promise<DriverResult>;
|
|
15
19
|
private usage;
|
|
16
20
|
}
|
package/dist/drivers/claude.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { discoverClaudeNativeSessions } from '../workspace/native.js';
|
|
2
3
|
// Real driver: shells the local `claude` CLI in stream-json mode and normalizes its
|
|
3
4
|
// events into kernel DriverEvents. Faithful to pikiloom's claude.ts event shapes
|
|
4
5
|
// (system / stream_event{message_start,content_block_delta,message_delta} / assistant / result),
|
|
@@ -22,6 +23,9 @@ export class ClaudeDriver {
|
|
|
22
23
|
args.push(...input.extraArgs);
|
|
23
24
|
return { command: this.bin, args, cwd: input.workdir, env: input.env };
|
|
24
25
|
}
|
|
26
|
+
listNativeSessions(opts) {
|
|
27
|
+
return discoverClaudeNativeSessions(opts.workdir, { limit: opts.limit });
|
|
28
|
+
}
|
|
25
29
|
run(input, ctx) {
|
|
26
30
|
const steerable = !!input.steerable;
|
|
27
31
|
const args = ['-p', '--verbose', '--output-format', 'stream-json', '--include-partial-messages'];
|
package/dist/drivers/codex.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentDriver, AgentTurnInput, DriverContext, DriverEvent, DriverResult, TuiInput, TuiSpec } from '../contracts/driver.js';
|
|
1
|
+
import type { AgentDriver, AgentTurnInput, DriverContext, DriverEvent, DriverResult, TuiInput, TuiSpec, NativeSessionInfo } from '../contracts/driver.js';
|
|
2
2
|
import type { UniversalUsage } from '../protocol/index.js';
|
|
3
3
|
export declare function codexToolSummary(item: any): {
|
|
4
4
|
id: string;
|
|
@@ -29,6 +29,10 @@ export declare class CodexDriver implements AgentDriver {
|
|
|
29
29
|
constructor(bin?: string);
|
|
30
30
|
run(input: AgentTurnInput, ctx: DriverContext): Promise<DriverResult>;
|
|
31
31
|
tui(input: TuiInput): TuiSpec;
|
|
32
|
+
listNativeSessions(opts: {
|
|
33
|
+
workdir: string;
|
|
34
|
+
limit?: number;
|
|
35
|
+
}): NativeSessionInfo[];
|
|
32
36
|
}
|
|
33
37
|
export interface CodexUsageState {
|
|
34
38
|
input: number | null;
|
package/dist/drivers/codex.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { discoverCodexNativeSessions } from '../workspace/native.js';
|
|
2
3
|
// Minimal newline-delimited JSON-RPC client for `codex app-server` (ported faithfully
|
|
3
4
|
// from pikiloom's CodexAppServer; trimmed to what the kernel needs).
|
|
4
5
|
class AppServer {
|
|
@@ -405,6 +406,9 @@ export class CodexDriver {
|
|
|
405
406
|
args.push(...input.extraArgs);
|
|
406
407
|
return { command: this.bin, args, cwd: input.workdir, env: input.env };
|
|
407
408
|
}
|
|
409
|
+
listNativeSessions(opts) {
|
|
410
|
+
return discoverCodexNativeSessions(opts.workdir, { limit: opts.limit });
|
|
411
|
+
}
|
|
408
412
|
}
|
|
409
413
|
const IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp']);
|
|
410
414
|
function buildTurnInput(prompt, attachments) {
|
package/dist/drivers/gemini.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentDriver, AgentTurnInput, DriverContext, DriverResult, DriverEvent, TuiInput, TuiSpec } from '../contracts/driver.js';
|
|
1
|
+
import type { AgentDriver, AgentTurnInput, DriverContext, DriverResult, DriverEvent, TuiInput, TuiSpec, NativeSessionInfo } from '../contracts/driver.js';
|
|
2
2
|
export declare class GeminiDriver implements AgentDriver {
|
|
3
3
|
private readonly bin;
|
|
4
4
|
readonly id = "gemini";
|
|
@@ -11,6 +11,10 @@ export declare class GeminiDriver implements AgentDriver {
|
|
|
11
11
|
constructor(bin?: string);
|
|
12
12
|
run(input: AgentTurnInput, ctx: DriverContext): Promise<DriverResult>;
|
|
13
13
|
tui(input: TuiInput): TuiSpec;
|
|
14
|
+
listNativeSessions(opts: {
|
|
15
|
+
workdir: string;
|
|
16
|
+
limit?: number;
|
|
17
|
+
}): NativeSessionInfo[];
|
|
14
18
|
}
|
|
15
19
|
export declare function parseGeminiEvent(ev: any, s: any, tools: Map<string, {
|
|
16
20
|
name: string;
|
package/dist/drivers/gemini.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { discoverGeminiNativeSessions } from '../workspace/native.js';
|
|
2
3
|
// Native kernel Gemini driver: `gemini --output-format stream-json ... -p <prompt>` and
|
|
3
4
|
// parse its stream-json events into kernel DriverEvents. Faithful to pikiloom's geminiParse.
|
|
4
5
|
export class GeminiDriver {
|
|
@@ -80,6 +81,9 @@ export class GeminiDriver {
|
|
|
80
81
|
args.push(...input.extraArgs);
|
|
81
82
|
return { command: this.bin, args, cwd: input.workdir, env: input.env };
|
|
82
83
|
}
|
|
84
|
+
listNativeSessions(opts) {
|
|
85
|
+
return discoverGeminiNativeSessions(opts.workdir, { limit: opts.limit });
|
|
86
|
+
}
|
|
83
87
|
}
|
|
84
88
|
export function parseGeminiEvent(ev, s, tools, emit) {
|
|
85
89
|
const t = ev.type || '';
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { SessionRunner } from './runtime/session-runner.js';
|
|
|
4
4
|
export { runTurn, type RunTurnOptions, type TurnOutcome } from './runtime/turn.js';
|
|
5
5
|
export { PtyBridge, ptyAvailable, type PtyExit, type PtyOpenOpts } from './runtime/pty.js';
|
|
6
6
|
export { attachTui, type AttachTuiOptions } from './runtime/tui.js';
|
|
7
|
-
export type { AgentDriver, AgentTurnInput, DriverContext, DriverEvent, DriverResult, SteerFn, McpServerSpec, TuiInput, TuiSpec, } from './contracts/driver.js';
|
|
7
|
+
export type { AgentDriver, AgentTurnInput, DriverContext, DriverEvent, DriverResult, SteerFn, McpServerSpec, TuiInput, TuiSpec, NativeSessionInfo, } from './contracts/driver.js';
|
|
8
8
|
export type { SessionStore, CoreSessionRecord, ModelResolver, ModelInjection, ToolProvider, SystemPromptBuilder, InteractionHandler, Catalog, } from './contracts/ports.js';
|
|
9
9
|
export type { LoomIO, PromptInput, Surface, SurfaceCapabilities, Plugin, SpawnContribution, } from './contracts/surface.js';
|
|
10
10
|
export { FsSessionStore, NullModelResolver, NoopToolProvider, PassthroughSystemPromptBuilder, AutoCancelInteractionHandler, DeferToTerminalInteractionHandler, NoopCatalog, defaultBaseDir, } from './ports/defaults.js';
|
|
@@ -15,4 +15,5 @@ export { GeminiDriver } from './drivers/gemini.js';
|
|
|
15
15
|
export { HermesDriver } from './drivers/hermes.js';
|
|
16
16
|
export { WebSurface, type WebSurfaceOptions } from './surfaces/web.js';
|
|
17
17
|
export { CliSurface } from './surfaces/cli.js';
|
|
18
|
+
export * from './workspace/index.js';
|
|
18
19
|
export * from './protocol/index.js';
|
package/dist/index.js
CHANGED
|
@@ -25,5 +25,7 @@ export { GeminiDriver } from './drivers/gemini.js';
|
|
|
25
25
|
export { HermesDriver } from './drivers/hermes.js';
|
|
26
26
|
export { WebSurface } from './surfaces/web.js';
|
|
27
27
|
export { CliSurface } from './surfaces/cli.js';
|
|
28
|
+
// Workspace: unified top-level directory + session/skill/mcp management (loom.paths/sessions/skills/mcp)
|
|
29
|
+
export * from './workspace/index.js';
|
|
28
30
|
// Protocol (the wire vocabulary; shared with transports)
|
|
29
31
|
export * from './protocol/index.js';
|
package/dist/ports/defaults.js
CHANGED
|
@@ -25,11 +25,15 @@ export class FsSessionStore {
|
|
|
25
25
|
if (!existing) {
|
|
26
26
|
const now = new Date().toISOString();
|
|
27
27
|
await this.save({
|
|
28
|
-
agent, sessionId, workspacePath,
|
|
28
|
+
agent, sessionId, workspacePath, workdir: path.resolve(opts.workdir),
|
|
29
29
|
createdAt: now, updatedAt: now,
|
|
30
30
|
title: opts.title ?? null, runState: 'running', runDetail: null,
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
|
+
else if (!existing.workdir) {
|
|
34
|
+
existing.workdir = path.resolve(opts.workdir);
|
|
35
|
+
await this.save(existing);
|
|
36
|
+
}
|
|
33
37
|
return { sessionId, workspacePath };
|
|
34
38
|
}
|
|
35
39
|
async get(agent, sessionId) {
|
|
@@ -74,6 +78,8 @@ export class FsSessionStore {
|
|
|
74
78
|
rec.nativeSessionId = result.sessionId;
|
|
75
79
|
if (result.text && !rec.title)
|
|
76
80
|
rec.title = result.text.slice(0, 80);
|
|
81
|
+
if (result.text)
|
|
82
|
+
rec.preview = result.text.replace(/\s+/g, ' ').trim().slice(0, 200) || rec.preview || null;
|
|
77
83
|
await this.save(rec);
|
|
78
84
|
}
|
|
79
85
|
async appendTurn(agent, sessionId, turn) {
|
package/dist/runtime/loom.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import type { AgentDriver, TuiSpec } from '../contracts/driver.js';
|
|
|
2
2
|
import type { SessionStore, ModelResolver, ToolProvider, SystemPromptBuilder, Catalog, InteractionHandler } from '../contracts/ports.js';
|
|
3
3
|
import type { LoomIO, Surface, Plugin } from '../contracts/surface.js';
|
|
4
4
|
import { PtyBridge, type PtyOpenOpts, type PtyExit } from './pty.js';
|
|
5
|
+
import { type LoomPaths } from '../workspace/paths.js';
|
|
6
|
+
import { SessionsManager } from '../workspace/sessions.js';
|
|
7
|
+
import { SkillsManager } from '../workspace/skills.js';
|
|
8
|
+
import { McpRegistry } from '../workspace/mcp.js';
|
|
5
9
|
export interface TuiLaunchOptions {
|
|
6
10
|
agent?: string;
|
|
7
11
|
workdir?: string;
|
|
@@ -9,9 +13,11 @@ export interface TuiLaunchOptions {
|
|
|
9
13
|
sessionId?: string | null;
|
|
10
14
|
}
|
|
11
15
|
export interface LoomConfig {
|
|
16
|
+
stateDirName?: string;
|
|
12
17
|
appNamespace?: string;
|
|
13
18
|
workdir?: string;
|
|
14
19
|
defaultAgent?: string;
|
|
20
|
+
agentSkillDirs?: string[];
|
|
15
21
|
drivers?: AgentDriver[];
|
|
16
22
|
surfaces?: Surface[];
|
|
17
23
|
plugins?: Plugin[];
|
|
@@ -27,6 +33,10 @@ export interface LoomConfig {
|
|
|
27
33
|
}
|
|
28
34
|
export interface Loom {
|
|
29
35
|
readonly io: LoomIO;
|
|
36
|
+
readonly paths: LoomPaths;
|
|
37
|
+
readonly sessions: SessionsManager;
|
|
38
|
+
readonly skills: SkillsManager;
|
|
39
|
+
readonly mcp: McpRegistry;
|
|
30
40
|
registerDriver(driver: AgentDriver): void;
|
|
31
41
|
registerPlugin(plugin: Plugin): void;
|
|
32
42
|
start(): Promise<void>;
|
package/dist/runtime/loom.js
CHANGED
|
@@ -2,8 +2,15 @@ import { FsSessionStore, NullModelResolver, NoopToolProvider, PassthroughSystemP
|
|
|
2
2
|
import { Hub } from './hub.js';
|
|
3
3
|
import { PtyBridge } from './pty.js';
|
|
4
4
|
import { attachTui } from './tui.js';
|
|
5
|
+
import { resolveLoomPaths } from '../workspace/paths.js';
|
|
6
|
+
import { SessionsManager } from '../workspace/sessions.js';
|
|
7
|
+
import { SkillsManager } from '../workspace/skills.js';
|
|
8
|
+
import { McpRegistry } from '../workspace/mcp.js';
|
|
5
9
|
export function createLoom(config = {}) {
|
|
6
|
-
|
|
10
|
+
// The top-level dir defaults to 'pikiloom'; appNamespace (the default session-store base)
|
|
11
|
+
// falls back to it so a single knob configures everything for a fresh consumer.
|
|
12
|
+
const stateDirName = config.stateDirName || config.appNamespace || 'pikiloom';
|
|
13
|
+
const appNamespace = config.appNamespace || stateDirName;
|
|
7
14
|
const workdir = config.workdir || process.cwd();
|
|
8
15
|
const log = config.log || (() => { });
|
|
9
16
|
const drivers = new Map();
|
|
@@ -12,11 +19,16 @@ export function createLoom(config = {}) {
|
|
|
12
19
|
const defaultAgent = config.defaultAgent || config.drivers?.[0]?.id || 'echo';
|
|
13
20
|
// Held as a live reference so registerPlugin() mutates the same array the Hub iterates.
|
|
14
21
|
const plugins = [...(config.plugins || [])];
|
|
22
|
+
const sessionStore = config.sessionStore || new FsSessionStore(defaultBaseDir(appNamespace));
|
|
23
|
+
const paths = resolveLoomPaths({ stateDirName });
|
|
24
|
+
const sessions = new SessionsManager({ store: sessionStore, drivers: () => drivers, defaultWorkdir: workdir, log });
|
|
25
|
+
const skills = new SkillsManager({ paths, agentSkillDirs: config.agentSkillDirs, log });
|
|
26
|
+
const mcp = new McpRegistry({ log });
|
|
15
27
|
const hub = new Hub({
|
|
16
28
|
drivers,
|
|
17
29
|
defaultAgent,
|
|
18
30
|
workdir,
|
|
19
|
-
sessionStore
|
|
31
|
+
sessionStore,
|
|
20
32
|
modelResolver: config.modelResolver || new NullModelResolver(),
|
|
21
33
|
toolProvider: config.toolProvider || new NoopToolProvider(),
|
|
22
34
|
systemPromptBuilder: config.systemPromptBuilder || new PassthroughSystemPromptBuilder(),
|
|
@@ -37,6 +49,10 @@ export function createLoom(config = {}) {
|
|
|
37
49
|
};
|
|
38
50
|
return {
|
|
39
51
|
io: hub,
|
|
52
|
+
paths,
|
|
53
|
+
sessions,
|
|
54
|
+
skills,
|
|
55
|
+
mcp,
|
|
40
56
|
registerDriver(driver) { drivers.set(driver.id, driver); },
|
|
41
57
|
registerPlugin(plugin) { plugins.push(plugin); },
|
|
42
58
|
async start() {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { resolveLoomPaths, normalizeStateDirName, type LoomPaths, type LoomScope } from './paths.js';
|
|
2
|
+
export { discoverClaudeNativeSessions, discoverCodexNativeSessions, discoverGeminiNativeSessions, encodeClaudeProjectDir, NATIVE_SESSION_RUNNING_THRESHOLD_MS, type DiscoverOptions, type NativeSessionInfo, } from './native.js';
|
|
3
|
+
export { SessionsManager, type ManagedSessionInfo, type SessionSource, type ListSessionsOptions, type SearchSessionsOptions, type SessionsManagerDeps, } from './sessions.js';
|
|
4
|
+
export { SkillsManager, ensureDirSymlink, type SkillInfo, type SkillSearchResult, type SkillsManagerOptions, } from './skills.js';
|
|
5
|
+
export { McpRegistry, type McpCatalogEntry, type McpSearchResult, type McpRegistryOptions, } from './mcp.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Workspace subsystem: the unified top-level directory + session/skill/mcp management that
|
|
2
|
+
// a consuming app gets "for free" off createLoom() (exposed as loom.paths/sessions/skills/mcp).
|
|
3
|
+
export { resolveLoomPaths, normalizeStateDirName } from './paths.js';
|
|
4
|
+
export { discoverClaudeNativeSessions, discoverCodexNativeSessions, discoverGeminiNativeSessions, encodeClaudeProjectDir, NATIVE_SESSION_RUNNING_THRESHOLD_MS, } from './native.js';
|
|
5
|
+
export { SessionsManager, } from './sessions.js';
|
|
6
|
+
export { SkillsManager, ensureDirSymlink, } from './skills.js';
|
|
7
|
+
export { McpRegistry, } from './mcp.js';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { McpServerSpec } from '../contracts/driver.js';
|
|
2
|
+
export interface McpCatalogEntry {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
category?: string;
|
|
7
|
+
brand?: string;
|
|
8
|
+
transport: {
|
|
9
|
+
type: 'stdio';
|
|
10
|
+
command: string;
|
|
11
|
+
args?: string[];
|
|
12
|
+
} | {
|
|
13
|
+
type: 'http';
|
|
14
|
+
url: string;
|
|
15
|
+
};
|
|
16
|
+
/** Required credential env var names (so a UI can prompt for them). */
|
|
17
|
+
envKeys?: string[];
|
|
18
|
+
homepage?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface McpSearchResult {
|
|
21
|
+
name: string;
|
|
22
|
+
description: string | null;
|
|
23
|
+
source: 'registry' | 'npm';
|
|
24
|
+
npmPackage?: string | null;
|
|
25
|
+
homepage?: string | null;
|
|
26
|
+
}
|
|
27
|
+
export interface McpRegistryOptions {
|
|
28
|
+
/** Replace/extend the built-in recommended catalog. */
|
|
29
|
+
recommended?: McpCatalogEntry[];
|
|
30
|
+
fetchImpl?: typeof fetch;
|
|
31
|
+
log?: (msg: string) => void;
|
|
32
|
+
}
|
|
33
|
+
export declare class McpRegistry {
|
|
34
|
+
private readonly _recommended;
|
|
35
|
+
private readonly fetchImpl;
|
|
36
|
+
private readonly log?;
|
|
37
|
+
constructor(opts?: McpRegistryOptions);
|
|
38
|
+
/** The curated catalog of well-known servers. */
|
|
39
|
+
recommended(): McpCatalogEntry[];
|
|
40
|
+
/** Turn a catalog entry into a kernel McpServerSpec ready to hand to a driver/plugin. */
|
|
41
|
+
toServerSpec(entry: McpCatalogEntry, env?: Record<string, string>): McpServerSpec;
|
|
42
|
+
/** Search the public MCP registry, falling back to npm. Best-effort; [] on failure. */
|
|
43
|
+
search(query: string, limit?: number): Promise<McpSearchResult[]>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const BUILTIN_RECOMMENDED = [
|
|
2
|
+
{ id: 'filesystem', name: 'Filesystem', description: 'Read/write files under allowed directories.', category: 'core', transport: { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem'] }, homepage: 'https://github.com/modelcontextprotocol/servers' },
|
|
3
|
+
{ id: 'github', name: 'GitHub', description: 'GitHub repos, issues, and PRs.', category: 'dev', brand: 'github', transport: { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-github'] }, envKeys: ['GITHUB_PERSONAL_ACCESS_TOKEN'], homepage: 'https://github.com/modelcontextprotocol/servers' },
|
|
4
|
+
{ id: 'fetch', name: 'Fetch', description: 'Fetch and convert web pages to markdown.', category: 'web', transport: { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-fetch'] }, homepage: 'https://github.com/modelcontextprotocol/servers' },
|
|
5
|
+
{ id: 'git', name: 'Git', description: 'Local git repository operations.', category: 'dev', transport: { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-git'] }, homepage: 'https://github.com/modelcontextprotocol/servers' },
|
|
6
|
+
{ id: 'memory', name: 'Memory', description: 'A knowledge-graph memory store.', category: 'core', transport: { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-memory'] }, homepage: 'https://github.com/modelcontextprotocol/servers' },
|
|
7
|
+
{ id: 'playwright', name: 'Playwright', description: 'Drive a real browser for automation.', category: 'web', brand: 'playwright', transport: { type: 'stdio', command: 'npx', args: ['-y', '@playwright/mcp@latest'] }, homepage: 'https://github.com/microsoft/playwright-mcp' },
|
|
8
|
+
{ id: 'context7', name: 'Context7', description: 'Up-to-date library docs & code examples.', category: 'docs', transport: { type: 'http', url: 'https://mcp.context7.com/mcp' }, homepage: 'https://context7.com' },
|
|
9
|
+
];
|
|
10
|
+
export class McpRegistry {
|
|
11
|
+
_recommended;
|
|
12
|
+
fetchImpl;
|
|
13
|
+
log;
|
|
14
|
+
constructor(opts = {}) {
|
|
15
|
+
this._recommended = opts.recommended?.length ? opts.recommended : BUILTIN_RECOMMENDED;
|
|
16
|
+
this.fetchImpl = opts.fetchImpl ?? (typeof fetch === 'function' ? fetch : undefined);
|
|
17
|
+
this.log = opts.log;
|
|
18
|
+
}
|
|
19
|
+
/** The curated catalog of well-known servers. */
|
|
20
|
+
recommended() {
|
|
21
|
+
return this._recommended.map(e => ({ ...e }));
|
|
22
|
+
}
|
|
23
|
+
/** Turn a catalog entry into a kernel McpServerSpec ready to hand to a driver/plugin. */
|
|
24
|
+
toServerSpec(entry, env) {
|
|
25
|
+
if (entry.transport.type === 'http') {
|
|
26
|
+
return { name: entry.id, type: 'http', url: entry.transport.url };
|
|
27
|
+
}
|
|
28
|
+
return { name: entry.id, type: 'stdio', command: entry.transport.command, args: entry.transport.args, env };
|
|
29
|
+
}
|
|
30
|
+
/** Search the public MCP registry, falling back to npm. Best-effort; [] on failure. */
|
|
31
|
+
async search(query, limit = 20) {
|
|
32
|
+
const q = (query || '').trim();
|
|
33
|
+
const n = Math.max(1, Math.min(50, limit));
|
|
34
|
+
const fetchImpl = this.fetchImpl;
|
|
35
|
+
if (!fetchImpl)
|
|
36
|
+
return [];
|
|
37
|
+
// 1) Official MCP registry.
|
|
38
|
+
try {
|
|
39
|
+
const url = `https://registry.modelcontextprotocol.io/v0/servers?search=${encodeURIComponent(q)}&limit=${n}`;
|
|
40
|
+
const res = await fetchImpl(url, { headers: { accept: 'application/json' } });
|
|
41
|
+
if (res.ok) {
|
|
42
|
+
const data = await res.json();
|
|
43
|
+
const servers = Array.isArray(data?.servers) ? data.servers : Array.isArray(data?.data) ? data.data : [];
|
|
44
|
+
const mapped = servers.map((s) => ({
|
|
45
|
+
name: String(s?.name ?? s?.id ?? ''),
|
|
46
|
+
description: s?.description ?? null,
|
|
47
|
+
source: 'registry',
|
|
48
|
+
npmPackage: s?.packages?.[0]?.identifier ?? s?.npm ?? null,
|
|
49
|
+
homepage: s?.repository?.url ?? s?.homepage ?? null,
|
|
50
|
+
})).filter(s => s.name);
|
|
51
|
+
if (mapped.length)
|
|
52
|
+
return mapped.slice(0, n);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
this.log?.(`[mcp] registry search failed: ${e?.message || e}`);
|
|
57
|
+
}
|
|
58
|
+
// 2) npm fallback.
|
|
59
|
+
try {
|
|
60
|
+
const url = `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(`mcp server ${q}`.trim())}&size=${n}`;
|
|
61
|
+
const res = await fetchImpl(url, { headers: { accept: 'application/json' } });
|
|
62
|
+
if (!res.ok)
|
|
63
|
+
return [];
|
|
64
|
+
const data = await res.json();
|
|
65
|
+
const objects = Array.isArray(data?.objects) ? data.objects : [];
|
|
66
|
+
return objects.map((o) => {
|
|
67
|
+
const pkg = o?.package ?? {};
|
|
68
|
+
return {
|
|
69
|
+
name: String(pkg.name ?? ''),
|
|
70
|
+
description: pkg.description ?? null,
|
|
71
|
+
source: 'npm',
|
|
72
|
+
npmPackage: pkg.name ?? null,
|
|
73
|
+
homepage: pkg.links?.homepage ?? pkg.links?.npm ?? null,
|
|
74
|
+
};
|
|
75
|
+
}).filter(s => s.name).slice(0, n);
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
this.log?.(`[mcp] npm search failed: ${e?.message || e}`);
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { NativeSessionInfo } from '../contracts/driver.js';
|
|
2
|
+
export type { NativeSessionInfo } from '../contracts/driver.js';
|
|
3
|
+
export interface DiscoverOptions {
|
|
4
|
+
home?: string;
|
|
5
|
+
limit?: number;
|
|
6
|
+
/** A session whose file changed within this window is treated as "running". */
|
|
7
|
+
runningThresholdMs?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare const NATIVE_SESSION_RUNNING_THRESHOLD_MS = 10000;
|
|
10
|
+
/** Claude encodes a workdir into a project dir name by replacing non-alphanumerics with '-'. */
|
|
11
|
+
export declare function encodeClaudeProjectDir(workdir: string): string;
|
|
12
|
+
export declare function discoverClaudeNativeSessions(workdir: string, opts?: DiscoverOptions): NativeSessionInfo[];
|
|
13
|
+
export declare function discoverCodexNativeSessions(workdir: string, opts?: DiscoverOptions): NativeSessionInfo[];
|
|
14
|
+
export declare function discoverGeminiNativeSessions(workdir: string, opts?: DiscoverOptions): NativeSessionInfo[];
|