@soleri/core 9.5.0 → 9.6.0
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/dist/adapters/claude-code-adapter.d.ts +27 -0
- package/dist/adapters/claude-code-adapter.d.ts.map +1 -0
- package/dist/adapters/claude-code-adapter.js +111 -0
- package/dist/adapters/claude-code-adapter.js.map +1 -0
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +10 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/registry.d.ts +21 -0
- package/dist/adapters/registry.d.ts.map +1 -0
- package/dist/adapters/registry.js +44 -0
- package/dist/adapters/registry.js.map +1 -0
- package/dist/adapters/types.d.ts +93 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +10 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/brain/brain.d.ts +12 -1
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +106 -44
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/intelligence.d.ts.map +1 -1
- package/dist/brain/intelligence.js +36 -30
- package/dist/brain/intelligence.js.map +1 -1
- package/dist/chat/agent-loop.js +1 -1
- package/dist/chat/agent-loop.js.map +1 -1
- package/dist/chat/notifications.d.ts.map +1 -1
- package/dist/chat/notifications.js +4 -0
- package/dist/chat/notifications.js.map +1 -1
- package/dist/control/intent-router.d.ts +1 -0
- package/dist/control/intent-router.d.ts.map +1 -1
- package/dist/control/intent-router.js +11 -5
- package/dist/control/intent-router.js.map +1 -1
- package/dist/curator/curator.d.ts +4 -0
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +141 -27
- package/dist/curator/curator.js.map +1 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/llm-client.d.ts.map +1 -1
- package/dist/llm/llm-client.js +1 -0
- package/dist/llm/llm-client.js.map +1 -1
- package/dist/packs/index.d.ts +3 -2
- package/dist/packs/index.d.ts.map +1 -1
- package/dist/packs/index.js +3 -2
- package/dist/packs/index.js.map +1 -1
- package/dist/packs/lockfile.d.ts +23 -1
- package/dist/packs/lockfile.d.ts.map +1 -1
- package/dist/packs/lockfile.js +50 -4
- package/dist/packs/lockfile.js.map +1 -1
- package/dist/packs/pack-installer.d.ts +10 -0
- package/dist/packs/pack-installer.d.ts.map +1 -1
- package/dist/packs/pack-installer.js +69 -2
- package/dist/packs/pack-installer.js.map +1 -1
- package/dist/packs/pack-lifecycle.d.ts +50 -0
- package/dist/packs/pack-lifecycle.d.ts.map +1 -0
- package/dist/packs/pack-lifecycle.js +91 -0
- package/dist/packs/pack-lifecycle.js.map +1 -0
- package/dist/packs/types.d.ts +64 -44
- package/dist/packs/types.d.ts.map +1 -1
- package/dist/packs/types.js +9 -0
- package/dist/packs/types.js.map +1 -1
- package/dist/persistence/sqlite-provider.d.ts +5 -1
- package/dist/persistence/sqlite-provider.d.ts.map +1 -1
- package/dist/persistence/sqlite-provider.js +22 -2
- package/dist/persistence/sqlite-provider.js.map +1 -1
- package/dist/planning/github-projection.d.ts +8 -8
- package/dist/planning/github-projection.d.ts.map +1 -1
- package/dist/planning/github-projection.js +42 -42
- package/dist/planning/github-projection.js.map +1 -1
- package/dist/plugins/types.d.ts +21 -21
- package/dist/queue/pipeline-runner.d.ts.map +1 -1
- package/dist/queue/pipeline-runner.js +4 -0
- package/dist/queue/pipeline-runner.js.map +1 -1
- package/dist/runtime/curator-extra-ops.d.ts.map +1 -1
- package/dist/runtime/curator-extra-ops.js +9 -1
- package/dist/runtime/curator-extra-ops.js.map +1 -1
- package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
- package/dist/runtime/facades/memory-facade.js +169 -0
- package/dist/runtime/facades/memory-facade.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +133 -4
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +128 -90
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/session-briefing.d.ts.map +1 -1
- package/dist/runtime/session-briefing.js +44 -11
- package/dist/runtime/session-briefing.js.map +1 -1
- package/dist/runtime/shutdown-registry.d.ts +36 -0
- package/dist/runtime/shutdown-registry.d.ts.map +1 -0
- package/dist/runtime/shutdown-registry.js +74 -0
- package/dist/runtime/shutdown-registry.js.map +1 -0
- package/dist/runtime/types.d.ts +10 -1
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/subagent/concurrency-manager.d.ts +29 -0
- package/dist/subagent/concurrency-manager.d.ts.map +1 -0
- package/dist/subagent/concurrency-manager.js +73 -0
- package/dist/subagent/concurrency-manager.js.map +1 -0
- package/dist/subagent/dispatcher.d.ts +41 -0
- package/dist/subagent/dispatcher.d.ts.map +1 -0
- package/dist/subagent/dispatcher.js +259 -0
- package/dist/subagent/dispatcher.js.map +1 -0
- package/dist/subagent/index.d.ts +14 -0
- package/dist/subagent/index.d.ts.map +1 -0
- package/dist/subagent/index.js +15 -0
- package/dist/subagent/index.js.map +1 -0
- package/dist/subagent/orphan-reaper.d.ts +37 -0
- package/dist/subagent/orphan-reaper.d.ts.map +1 -0
- package/dist/subagent/orphan-reaper.js +71 -0
- package/dist/subagent/orphan-reaper.js.map +1 -0
- package/dist/subagent/result-aggregator.d.ts +7 -0
- package/dist/subagent/result-aggregator.d.ts.map +1 -0
- package/dist/subagent/result-aggregator.js +57 -0
- package/dist/subagent/result-aggregator.js.map +1 -0
- package/dist/subagent/task-checkout.d.ts +36 -0
- package/dist/subagent/task-checkout.d.ts.map +1 -0
- package/dist/subagent/task-checkout.js +52 -0
- package/dist/subagent/task-checkout.js.map +1 -0
- package/dist/subagent/types.d.ts +114 -0
- package/dist/subagent/types.d.ts.map +1 -0
- package/dist/subagent/types.js +9 -0
- package/dist/subagent/types.js.map +1 -0
- package/dist/subagent/workspace-resolver.d.ts +35 -0
- package/dist/subagent/workspace-resolver.d.ts.map +1 -0
- package/dist/subagent/workspace-resolver.js +99 -0
- package/dist/subagent/workspace-resolver.js.map +1 -0
- package/dist/transport/http-server.d.ts.map +1 -1
- package/dist/transport/http-server.js +49 -3
- package/dist/transport/http-server.js.map +1 -1
- package/dist/transport/ws-server.d.ts.map +1 -1
- package/dist/transport/ws-server.js +7 -0
- package/dist/transport/ws-server.js.map +1 -1
- package/dist/vault/linking.d.ts +3 -4
- package/dist/vault/linking.d.ts.map +1 -1
- package/dist/vault/linking.js +79 -32
- package/dist/vault/linking.js.map +1 -1
- package/dist/vault/vault-maintenance.d.ts.map +1 -1
- package/dist/vault/vault-maintenance.js +7 -14
- package/dist/vault/vault-maintenance.js.map +1 -1
- package/dist/vault/vault-memories.d.ts.map +1 -1
- package/dist/vault/vault-memories.js +19 -9
- package/dist/vault/vault-memories.js.map +1 -1
- package/dist/vault/vault-schema.d.ts +1 -0
- package/dist/vault/vault-schema.d.ts.map +1 -1
- package/dist/vault/vault-schema.js +20 -0
- package/dist/vault/vault-schema.js.map +1 -1
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +7 -3
- package/dist/vault/vault.js.map +1 -1
- package/package.json +8 -2
- package/src/__tests__/adapters/claude-code-adapter.test.ts +167 -0
- package/src/__tests__/adapters/registry.test.ts +100 -0
- package/src/__tests__/packs/pack-lifecycle.test.ts +379 -0
- package/src/__tests__/subagent/concurrency-manager.test.ts +132 -0
- package/src/__tests__/subagent/dispatcher.test.ts +195 -0
- package/src/__tests__/subagent/orphan-reaper.test.ts +141 -0
- package/src/__tests__/subagent/result-aggregator.test.ts +141 -0
- package/src/__tests__/subagent/task-checkout.test.ts +86 -0
- package/src/__tests__/subagent/workspace-resolver.test.ts +138 -0
- package/src/adapters/claude-code-adapter.ts +163 -0
- package/src/adapters/index.ts +22 -0
- package/src/adapters/registry.ts +53 -0
- package/src/adapters/types.ts +114 -0
- package/src/curator/curator.ts +1 -0
- package/src/index.ts +38 -1
- package/src/packs/index.ts +5 -1
- package/src/packs/lockfile.ts +70 -5
- package/src/packs/pack-installer.ts +78 -2
- package/src/packs/pack-lifecycle.ts +115 -0
- package/src/packs/pack-lockfile.test.ts +1 -1
- package/src/packs/pack-system.test.ts +1 -1
- package/src/packs/types.ts +40 -2
- package/src/persistence/sqlite-provider.ts +26 -2
- package/src/runtime/admin-setup-ops.test.ts +9 -4
- package/src/runtime/orchestrate-ops.ts +153 -1
- package/src/runtime/runtime.ts +15 -0
- package/src/runtime/session-briefing.test.ts +94 -2
- package/src/runtime/session-briefing.ts +48 -12
- package/src/runtime/types.ts +6 -0
- package/src/subagent/concurrency-manager.ts +89 -0
- package/src/subagent/dispatcher.ts +326 -0
- package/src/subagent/index.ts +28 -0
- package/src/subagent/orphan-reaper.ts +82 -0
- package/src/subagent/result-aggregator.ts +66 -0
- package/src/subagent/task-checkout.ts +60 -0
- package/src/subagent/types.ts +138 -0
- package/src/subagent/workspace-resolver.ts +117 -0
- package/src/vault/vault-scaling.test.ts +3 -2
- package/vitest.config.ts +2 -0
- package/src/hooks/index.ts +0 -6
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
vi.mock('node:child_process', () => ({
|
|
4
|
+
execSync: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
vi.mock('node:fs', () => ({
|
|
8
|
+
existsSync: vi.fn().mockReturnValue(false),
|
|
9
|
+
mkdirSync: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
import { WorkspaceResolver } from '../../subagent/workspace-resolver.js';
|
|
13
|
+
import { execSync } from 'node:child_process';
|
|
14
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
15
|
+
|
|
16
|
+
/** Normalize path separators to forward slashes for cross-platform assertions. */
|
|
17
|
+
const norm = (p: string): string => p.replace(/\\/g, '/');
|
|
18
|
+
|
|
19
|
+
describe('WorkspaceResolver', () => {
|
|
20
|
+
let resolver: WorkspaceResolver;
|
|
21
|
+
const baseDir = '/projects/test-repo';
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vi.clearAllMocks();
|
|
25
|
+
resolver = new WorkspaceResolver(baseDir);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('resolve() returns original workspace when isolate=false', () => {
|
|
29
|
+
const result = resolver.resolve('task-1', '/original/workspace', false);
|
|
30
|
+
expect(result).toBe('/original/workspace');
|
|
31
|
+
expect(execSync).not.toHaveBeenCalled();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('resolve() creates a worktree when isolate=true', () => {
|
|
35
|
+
(execSync as ReturnType<typeof vi.fn>).mockReturnValue('');
|
|
36
|
+
|
|
37
|
+
const result = resolver.resolve('task-1', '/original/workspace', true);
|
|
38
|
+
expect(norm(result)).toBe(`${baseDir}/.soleri/worktrees/task-1`);
|
|
39
|
+
expect(execSync).toHaveBeenCalledWith(
|
|
40
|
+
expect.stringContaining('git worktree add'),
|
|
41
|
+
expect.objectContaining({ cwd: baseDir }),
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('resolve() falls back to original workspace when git fails', () => {
|
|
46
|
+
(execSync as ReturnType<typeof vi.fn>).mockImplementation(() => {
|
|
47
|
+
throw new Error('git worktree failed');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Suppress console.warn from the fallback
|
|
51
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
52
|
+
const result = resolver.resolve('task-fail', '/original/workspace', true);
|
|
53
|
+
expect(result).toBe('/original/workspace');
|
|
54
|
+
warnSpy.mockRestore();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('cleanup() calls git worktree remove and git branch -D', () => {
|
|
58
|
+
(execSync as ReturnType<typeof vi.fn>).mockReturnValue('');
|
|
59
|
+
|
|
60
|
+
// First create a worktree
|
|
61
|
+
resolver.resolve('task-1', '/original', true);
|
|
62
|
+
vi.clearAllMocks();
|
|
63
|
+
|
|
64
|
+
// Now clean it up
|
|
65
|
+
resolver.cleanup('task-1');
|
|
66
|
+
expect(execSync).toHaveBeenCalledWith(
|
|
67
|
+
expect.stringContaining('git worktree remove'),
|
|
68
|
+
expect.objectContaining({ cwd: baseDir }),
|
|
69
|
+
);
|
|
70
|
+
expect(execSync).toHaveBeenCalledWith(
|
|
71
|
+
expect.stringContaining('git branch -D'),
|
|
72
|
+
expect.objectContaining({ cwd: baseDir }),
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('cleanup() silently handles errors', () => {
|
|
77
|
+
(execSync as ReturnType<typeof vi.fn>)
|
|
78
|
+
.mockReturnValueOnce('') // worktree add succeeds
|
|
79
|
+
.mockImplementation(() => {
|
|
80
|
+
throw new Error('worktree already removed');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
resolver.resolve('task-err', '/original', true);
|
|
84
|
+
|
|
85
|
+
// Should not throw
|
|
86
|
+
expect(() => resolver.cleanup('task-err')).not.toThrow();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('cleanup() is a no-op for unknown task IDs', () => {
|
|
90
|
+
resolver.cleanup('nonexistent');
|
|
91
|
+
expect(execSync).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('listActive() tracks created worktrees', () => {
|
|
95
|
+
(execSync as ReturnType<typeof vi.fn>).mockReturnValue('');
|
|
96
|
+
|
|
97
|
+
resolver.resolve('task-a', '/ws', true);
|
|
98
|
+
resolver.resolve('task-b', '/ws', true);
|
|
99
|
+
|
|
100
|
+
const active = resolver.listActive();
|
|
101
|
+
expect(active).toHaveLength(2);
|
|
102
|
+
expect(active.map((w) => w.taskId).sort()).toEqual(['task-a', 'task-b']);
|
|
103
|
+
expect(norm(active[0].path)).toContain('.soleri/worktrees/');
|
|
104
|
+
expect(active[0].branch).toContain('subagent/');
|
|
105
|
+
expect(active[0].createdAt).toBeGreaterThan(0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('cleanupAll() removes all worktrees', () => {
|
|
109
|
+
(execSync as ReturnType<typeof vi.fn>).mockReturnValue('');
|
|
110
|
+
|
|
111
|
+
resolver.resolve('task-a', '/ws', true);
|
|
112
|
+
resolver.resolve('task-b', '/ws', true);
|
|
113
|
+
expect(resolver.listActive()).toHaveLength(2);
|
|
114
|
+
|
|
115
|
+
resolver.cleanupAll();
|
|
116
|
+
expect(resolver.listActive()).toHaveLength(0);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('isActive() returns correct state', () => {
|
|
120
|
+
(execSync as ReturnType<typeof vi.fn>).mockReturnValue('');
|
|
121
|
+
|
|
122
|
+
expect(resolver.isActive('task-1')).toBe(false);
|
|
123
|
+
resolver.resolve('task-1', '/ws', true);
|
|
124
|
+
expect(resolver.isActive('task-1')).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('resolve() creates parent directory if it does not exist', () => {
|
|
128
|
+
(existsSync as ReturnType<typeof vi.fn>).mockReturnValue(false);
|
|
129
|
+
(execSync as ReturnType<typeof vi.fn>).mockReturnValue('');
|
|
130
|
+
|
|
131
|
+
resolver.resolve('task-mkdir', '/ws', true);
|
|
132
|
+
const calledPath = (mkdirSync as ReturnType<typeof vi.fn>).mock.calls[0][0] as string;
|
|
133
|
+
expect(norm(calledPath)).toContain('.soleri/worktrees');
|
|
134
|
+
expect((mkdirSync as ReturnType<typeof vi.fn>).mock.calls[0][1]).toEqual(
|
|
135
|
+
expect.objectContaining({ recursive: true }),
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClaudeCodeRuntimeAdapter — RuntimeAdapter implementation for Claude Code CLI.
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around a dispatch function (actual child process spawning is #411).
|
|
5
|
+
* Provides environment detection, session codec, and skill sync.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execSync } from 'node:child_process';
|
|
9
|
+
import { platform } from 'node:os';
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
RuntimeAdapter,
|
|
13
|
+
AdapterExecutionContext,
|
|
14
|
+
AdapterExecutionResult,
|
|
15
|
+
AdapterSessionState,
|
|
16
|
+
AdapterSessionCodec,
|
|
17
|
+
AdapterEnvironmentTestResult,
|
|
18
|
+
} from './types.js';
|
|
19
|
+
import type { SkillEntry } from '../skills/sync-skills.js';
|
|
20
|
+
|
|
21
|
+
// ─── Types ───────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
/** Shape of Claude Code session data */
|
|
24
|
+
interface ClaudeCodeSessionData {
|
|
25
|
+
sessionId: string;
|
|
26
|
+
cwd: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Dispatch function signature — injected via constructor */
|
|
30
|
+
export type ClaudeCodeDispatchFn = (
|
|
31
|
+
prompt: string,
|
|
32
|
+
workspace: string,
|
|
33
|
+
config?: Record<string, unknown>,
|
|
34
|
+
) => Promise<{
|
|
35
|
+
exitCode: number;
|
|
36
|
+
output?: string;
|
|
37
|
+
usage?: { inputTokens?: number; outputTokens?: number };
|
|
38
|
+
}>;
|
|
39
|
+
|
|
40
|
+
// ─── Session Codec ───────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
const claudeCodeSessionCodec: AdapterSessionCodec = {
|
|
43
|
+
serialize(state: AdapterSessionState): string {
|
|
44
|
+
return JSON.stringify(state);
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
deserialize(serialized: string): AdapterSessionState {
|
|
48
|
+
return JSON.parse(serialized) as AdapterSessionState;
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
getDisplayId(state: AdapterSessionState): string {
|
|
52
|
+
const data = state.data as unknown as ClaudeCodeSessionData;
|
|
53
|
+
return data.sessionId ?? 'unknown';
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// ─── Adapter ─────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
export class ClaudeCodeRuntimeAdapter implements RuntimeAdapter {
|
|
60
|
+
readonly type = 'claude-code' as const;
|
|
61
|
+
readonly sessionCodec = claudeCodeSessionCodec;
|
|
62
|
+
|
|
63
|
+
private readonly dispatch: ClaudeCodeDispatchFn | undefined;
|
|
64
|
+
|
|
65
|
+
constructor(dispatch?: ClaudeCodeDispatchFn) {
|
|
66
|
+
this.dispatch = dispatch;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Execute ──────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
async execute(ctx: AdapterExecutionContext): Promise<AdapterExecutionResult> {
|
|
72
|
+
if (!this.dispatch) {
|
|
73
|
+
return {
|
|
74
|
+
exitCode: 1,
|
|
75
|
+
summary: 'No dispatch function configured — cannot execute.',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
ctx.onLog?.(`[claude-code] Executing run ${ctx.runId} in ${ctx.workspace}`);
|
|
80
|
+
|
|
81
|
+
const result = await this.dispatch(ctx.prompt, ctx.workspace, ctx.config);
|
|
82
|
+
|
|
83
|
+
const executionResult: AdapterExecutionResult = {
|
|
84
|
+
exitCode: result.exitCode,
|
|
85
|
+
provider: 'anthropic',
|
|
86
|
+
summary: result.output,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
if (result.usage) {
|
|
90
|
+
executionResult.usage = {
|
|
91
|
+
inputTokens: result.usage.inputTokens,
|
|
92
|
+
outputTokens: result.usage.outputTokens,
|
|
93
|
+
totalTokens: (result.usage.inputTokens ?? 0) + (result.usage.outputTokens ?? 0),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Propagate session state if the incoming context had one
|
|
98
|
+
if (ctx.session) {
|
|
99
|
+
executionResult.sessionState = ctx.session;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return executionResult;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Environment Test ─────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
async testEnvironment(): Promise<AdapterEnvironmentTestResult> {
|
|
108
|
+
try {
|
|
109
|
+
const cmd = platform() === 'win32' ? 'where claude' : 'which claude';
|
|
110
|
+
const cliPath = execSync(cmd, { encoding: 'utf-8', timeout: 5_000 }).trim();
|
|
111
|
+
|
|
112
|
+
// Try to get version
|
|
113
|
+
let version: string | undefined;
|
|
114
|
+
try {
|
|
115
|
+
version = execSync('claude --version', {
|
|
116
|
+
encoding: 'utf-8',
|
|
117
|
+
timeout: 5_000,
|
|
118
|
+
}).trim();
|
|
119
|
+
} catch {
|
|
120
|
+
// Version detection is optional — CLI may not support --version
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
available: true,
|
|
125
|
+
version,
|
|
126
|
+
details: { path: cliPath },
|
|
127
|
+
};
|
|
128
|
+
} catch (err) {
|
|
129
|
+
return {
|
|
130
|
+
available: false,
|
|
131
|
+
error: err instanceof Error ? err.message : 'Claude CLI not found in PATH',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Skill Sync ───────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
async syncSkills(skills: SkillEntry[]): Promise<void> {
|
|
139
|
+
if (skills.length === 0) return;
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
// Dynamic import to avoid hard failure if sync-skills is unavailable
|
|
143
|
+
const { syncSkillsToClaudeCode } = await import('../skills/sync-skills.js');
|
|
144
|
+
|
|
145
|
+
// syncSkillsToClaudeCode expects directories, but we have individual
|
|
146
|
+
// SkillEntry items. Extract unique parent directories from source paths.
|
|
147
|
+
const dirs = [
|
|
148
|
+
...new Set(
|
|
149
|
+
skills.map((s) => {
|
|
150
|
+
// sourcePath is typically <dir>/<skill-name>/SKILL.md — go up two levels
|
|
151
|
+
const parts = s.sourcePath.split('/');
|
|
152
|
+
// Remove filename and skill folder to get the skills root dir
|
|
153
|
+
return parts.slice(0, -2).join('/');
|
|
154
|
+
}),
|
|
155
|
+
),
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
syncSkillsToClaudeCode(dirs);
|
|
159
|
+
} catch {
|
|
160
|
+
// Graceful degradation — skill sync is optional
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime adapter abstraction — dispatch work to any AI CLI.
|
|
3
|
+
*
|
|
4
|
+
* @module adapters
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Types
|
|
8
|
+
export type {
|
|
9
|
+
RuntimeAdapter,
|
|
10
|
+
AdapterExecutionContext,
|
|
11
|
+
AdapterExecutionResult,
|
|
12
|
+
AdapterTokenUsage,
|
|
13
|
+
AdapterSessionState,
|
|
14
|
+
AdapterSessionCodec,
|
|
15
|
+
AdapterEnvironmentTestResult,
|
|
16
|
+
} from './types.js';
|
|
17
|
+
|
|
18
|
+
// Registry
|
|
19
|
+
export { RuntimeAdapterRegistry } from './registry.js';
|
|
20
|
+
|
|
21
|
+
// Built-in adapters
|
|
22
|
+
export { ClaudeCodeRuntimeAdapter } from './claude-code-adapter.js';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RuntimeAdapterRegistry — simple Map-backed registry for runtime adapters.
|
|
3
|
+
*
|
|
4
|
+
* Adapters register at engine startup. No dynamic loading.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { RuntimeAdapter } from './types.js';
|
|
8
|
+
|
|
9
|
+
export class RuntimeAdapterRegistry {
|
|
10
|
+
private readonly adapters = new Map<string, RuntimeAdapter>();
|
|
11
|
+
private defaultType: string | undefined;
|
|
12
|
+
|
|
13
|
+
/** Register an adapter. Throws if the type is already registered. */
|
|
14
|
+
register(type: string, adapter: RuntimeAdapter): void {
|
|
15
|
+
if (this.adapters.has(type)) {
|
|
16
|
+
throw new Error(`RuntimeAdapterRegistry: adapter type "${type}" is already registered.`);
|
|
17
|
+
}
|
|
18
|
+
this.adapters.set(type, adapter);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Get an adapter by type. Throws if not found. */
|
|
22
|
+
get(type: string): RuntimeAdapter {
|
|
23
|
+
const adapter = this.adapters.get(type);
|
|
24
|
+
if (!adapter) {
|
|
25
|
+
const available = this.list().join(', ') || '(none)';
|
|
26
|
+
throw new Error(
|
|
27
|
+
`RuntimeAdapterRegistry: unknown adapter type "${type}". Registered: ${available}`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return adapter;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** List all registered adapter type strings. */
|
|
34
|
+
list(): string[] {
|
|
35
|
+
return [...this.adapters.keys()];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Set the default adapter type. Throws if the type is not registered. */
|
|
39
|
+
setDefault(type: string): void {
|
|
40
|
+
if (!this.adapters.has(type)) {
|
|
41
|
+
throw new Error(`RuntimeAdapterRegistry: cannot set default to unregistered type "${type}".`);
|
|
42
|
+
}
|
|
43
|
+
this.defaultType = type;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Get the default adapter. Throws if no default is set. */
|
|
47
|
+
getDefault(): RuntimeAdapter {
|
|
48
|
+
if (!this.defaultType) {
|
|
49
|
+
throw new Error('RuntimeAdapterRegistry: no default adapter has been set.');
|
|
50
|
+
}
|
|
51
|
+
return this.get(this.defaultType);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime adapter abstraction — dispatch work to any AI CLI.
|
|
3
|
+
*
|
|
4
|
+
* SEPARATE from enforcement HostAdapter (which translates rules).
|
|
5
|
+
* This is about executing tasks on different runtimes (Claude Code, Codex, Cursor, etc.)
|
|
6
|
+
*
|
|
7
|
+
* Inspired by Paperclip AI's ServerAdapterModule pattern.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { SkillEntry } from '../skills/sync-skills.js';
|
|
11
|
+
|
|
12
|
+
// ─── Execution Context ──────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
/** Context passed to an adapter when executing a task */
|
|
15
|
+
export interface AdapterExecutionContext {
|
|
16
|
+
/** Unique run identifier */
|
|
17
|
+
runId: string;
|
|
18
|
+
/** The prompt or task description to execute */
|
|
19
|
+
prompt: string;
|
|
20
|
+
/** Working directory for execution */
|
|
21
|
+
workspace: string;
|
|
22
|
+
/** Session state from previous run (adapter-specific format) */
|
|
23
|
+
session?: AdapterSessionState;
|
|
24
|
+
/** Skills to inject into the runtime */
|
|
25
|
+
skills?: SkillEntry[];
|
|
26
|
+
/** Adapter-specific configuration overrides */
|
|
27
|
+
config?: Record<string, unknown>;
|
|
28
|
+
/** Callback for streaming log output */
|
|
29
|
+
onLog?: (message: string) => void;
|
|
30
|
+
/** Callback for metadata events (tokens used, model, etc.) */
|
|
31
|
+
onMeta?: (meta: Record<string, unknown>) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Opaque session state — each adapter defines its own shape */
|
|
35
|
+
export interface AdapterSessionState {
|
|
36
|
+
/** Adapter type that created this state */
|
|
37
|
+
adapterType: string;
|
|
38
|
+
/** Serialized session data (adapter-specific) */
|
|
39
|
+
data: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── Execution Result ───────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
/** Result returned after adapter execution */
|
|
45
|
+
export interface AdapterExecutionResult {
|
|
46
|
+
/** Exit code (0 = success) */
|
|
47
|
+
exitCode: number;
|
|
48
|
+
/** Whether execution timed out */
|
|
49
|
+
timedOut?: boolean;
|
|
50
|
+
/** Token usage */
|
|
51
|
+
usage?: AdapterTokenUsage;
|
|
52
|
+
/** Session state to persist for next run */
|
|
53
|
+
sessionState?: AdapterSessionState;
|
|
54
|
+
/** Human-readable summary of what was done */
|
|
55
|
+
summary?: string;
|
|
56
|
+
/** Structured result data */
|
|
57
|
+
resultData?: Record<string, unknown>;
|
|
58
|
+
/** Provider and model info */
|
|
59
|
+
provider?: string;
|
|
60
|
+
model?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Token usage reported by the adapter */
|
|
64
|
+
export interface AdapterTokenUsage {
|
|
65
|
+
inputTokens?: number;
|
|
66
|
+
outputTokens?: number;
|
|
67
|
+
totalTokens?: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── Session Codec ──────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
/** Per-adapter session serialization — each runtime stores different session data */
|
|
73
|
+
export interface AdapterSessionCodec {
|
|
74
|
+
/** Serialize session state for persistence */
|
|
75
|
+
serialize(state: AdapterSessionState): string;
|
|
76
|
+
/** Deserialize persisted session state */
|
|
77
|
+
deserialize(serialized: string): AdapterSessionState;
|
|
78
|
+
/** Get a human-readable display ID for the session */
|
|
79
|
+
getDisplayId(state: AdapterSessionState): string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ─── Environment Test ───────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
/** Result of testing whether a runtime is available */
|
|
85
|
+
export interface AdapterEnvironmentTestResult {
|
|
86
|
+
/** Whether the runtime CLI is available */
|
|
87
|
+
available: boolean;
|
|
88
|
+
/** Runtime version (if detected) */
|
|
89
|
+
version?: string;
|
|
90
|
+
/** Additional details (path, capabilities, etc.) */
|
|
91
|
+
details?: Record<string, unknown>;
|
|
92
|
+
/** Error message if not available */
|
|
93
|
+
error?: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ─── Runtime Adapter Interface ──────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
/** Core adapter interface — implement this for each AI runtime */
|
|
99
|
+
export interface RuntimeAdapter {
|
|
100
|
+
/** Adapter type identifier (e.g., 'claude-code', 'codex', 'cursor') */
|
|
101
|
+
readonly type: string;
|
|
102
|
+
|
|
103
|
+
/** Execute a task in this runtime */
|
|
104
|
+
execute(ctx: AdapterExecutionContext): Promise<AdapterExecutionResult>;
|
|
105
|
+
|
|
106
|
+
/** Test whether this runtime is available in the current environment */
|
|
107
|
+
testEnvironment(): Promise<AdapterEnvironmentTestResult>;
|
|
108
|
+
|
|
109
|
+
/** Optional: session codec for persisting runtime-specific session state */
|
|
110
|
+
sessionCodec?: AdapterSessionCodec;
|
|
111
|
+
|
|
112
|
+
/** Optional: sync skills into the runtime's expected format */
|
|
113
|
+
syncSkills?(skills: SkillEntry[]): Promise<void>;
|
|
114
|
+
}
|
package/src/curator/curator.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
// ─── Adapters ────────────────────────────────────────────────────────
|
|
2
|
+
export { RuntimeAdapterRegistry } from './adapters/registry.js';
|
|
3
|
+
export { ClaudeCodeRuntimeAdapter } from './adapters/claude-code-adapter.js';
|
|
4
|
+
export type {
|
|
5
|
+
RuntimeAdapter,
|
|
6
|
+
AdapterExecutionContext,
|
|
7
|
+
AdapterExecutionResult,
|
|
8
|
+
AdapterTokenUsage,
|
|
9
|
+
AdapterSessionState,
|
|
10
|
+
AdapterSessionCodec,
|
|
11
|
+
AdapterEnvironmentTestResult,
|
|
12
|
+
} from './adapters/types.js';
|
|
13
|
+
|
|
14
|
+
// ─── Subagent ────────────────────────────────────────────────────────
|
|
15
|
+
export { SubagentDispatcher } from './subagent/dispatcher.js';
|
|
16
|
+
export { TaskCheckout } from './subagent/task-checkout.js';
|
|
17
|
+
export { WorkspaceResolver } from './subagent/workspace-resolver.js';
|
|
18
|
+
export { ConcurrencyManager } from './subagent/concurrency-manager.js';
|
|
19
|
+
export { OrphanReaper } from './subagent/orphan-reaper.js';
|
|
20
|
+
export { aggregate as aggregateResults } from './subagent/result-aggregator.js';
|
|
21
|
+
export type {
|
|
22
|
+
SubagentTask,
|
|
23
|
+
SubagentStatus,
|
|
24
|
+
SubagentResult,
|
|
25
|
+
DispatchOptions,
|
|
26
|
+
AggregatedResult,
|
|
27
|
+
ClaimInfo,
|
|
28
|
+
WorktreeInfo,
|
|
29
|
+
TrackedProcess,
|
|
30
|
+
} from './subagent/types.js';
|
|
31
|
+
|
|
1
32
|
// ─── Paths ──────────────────────────────────────────────────────────
|
|
2
33
|
export {
|
|
3
34
|
SOLERI_HOME,
|
|
@@ -617,7 +648,13 @@ export { computeContentHash } from './vault/content-hash.js';
|
|
|
617
648
|
export type { HashableEntry } from './vault/content-hash.js';
|
|
618
649
|
|
|
619
650
|
// ─── Knowledge Packs ────────────────────────────────────────────────────
|
|
620
|
-
export {
|
|
651
|
+
export {
|
|
652
|
+
PackInstaller,
|
|
653
|
+
PackLifecycleManager,
|
|
654
|
+
packManifestSchema,
|
|
655
|
+
VALID_TRANSITIONS,
|
|
656
|
+
} from './packs/index.js';
|
|
657
|
+
export type { PackState, PackTransition } from './packs/index.js';
|
|
621
658
|
export { PackLockfile, inferPackType } from './packs/index.js';
|
|
622
659
|
export { resolvePack, checkNpmVersion, checkVersionCompat } from './packs/index.js';
|
|
623
660
|
export type {
|
package/src/packs/index.ts
CHANGED
|
@@ -5,17 +5,21 @@
|
|
|
5
5
|
export {
|
|
6
6
|
packManifestSchema,
|
|
7
7
|
PACK_TIERS,
|
|
8
|
+
VALID_TRANSITIONS,
|
|
8
9
|
type PackManifest,
|
|
9
10
|
type PackTier as ManifestPackTier,
|
|
10
11
|
type PackStatus,
|
|
12
|
+
type PackState,
|
|
13
|
+
type PackTransition,
|
|
11
14
|
type InstalledPack,
|
|
12
15
|
type InstallResult,
|
|
13
16
|
type ValidateResult,
|
|
14
17
|
} from './types.js';
|
|
15
18
|
|
|
16
19
|
export { PackInstaller } from './pack-installer.js';
|
|
20
|
+
export { PackLifecycleManager } from './pack-lifecycle.js';
|
|
17
21
|
|
|
18
|
-
export { PackLockfile, inferPackType } from './lockfile.js';
|
|
22
|
+
export { PackLockfile, inferPackType, LOCKFILE_VERSION } from './lockfile.js';
|
|
19
23
|
export type { LockEntry, PackType, PackSource, PackTier, LockfileData } from './lockfile.js';
|
|
20
24
|
|
|
21
25
|
export { resolvePack, checkNpmVersion, checkVersionCompat } from './resolver.js';
|
package/src/packs/lockfile.ts
CHANGED
|
@@ -41,15 +41,25 @@ export interface LockEntry {
|
|
|
41
41
|
soleriRange?: string;
|
|
42
42
|
/** Pack tier: default (ships with engine), community (free), premium (unlocked today) */
|
|
43
43
|
tier?: PackTier;
|
|
44
|
+
/** Current pack lifecycle state */
|
|
45
|
+
state?: string;
|
|
46
|
+
/** Most recent lifecycle transition */
|
|
47
|
+
lastTransition?: { from: string; to: string; timestamp: string; reason?: string };
|
|
48
|
+
/** ISO timestamp when the pack was disabled */
|
|
49
|
+
disabledAt?: string;
|
|
50
|
+
/** Error details if the pack is in an error state */
|
|
51
|
+
errorMessage?: string;
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
export type PackType = 'hooks' | 'skills' | 'knowledge' | 'domain' | 'bundle';
|
|
47
55
|
export type PackSource = 'built-in' | 'local' | 'npm';
|
|
48
56
|
export type PackTier = 'default' | 'community' | 'premium';
|
|
49
57
|
|
|
58
|
+
export const LOCKFILE_VERSION = 2;
|
|
59
|
+
|
|
50
60
|
export interface LockfileData {
|
|
51
61
|
/** Lockfile format version */
|
|
52
|
-
version:
|
|
62
|
+
version: number;
|
|
53
63
|
/** Map of packId → lock entry */
|
|
54
64
|
packs: Record<string, LockEntry>;
|
|
55
65
|
}
|
|
@@ -141,18 +151,73 @@ export class PackLockfile {
|
|
|
141
151
|
return 'sha256-' + createHash('sha256').update(content).digest('hex');
|
|
142
152
|
}
|
|
143
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Update lifecycle fields for a pack entry and save.
|
|
156
|
+
*/
|
|
157
|
+
updateLifecycle(
|
|
158
|
+
packId: string,
|
|
159
|
+
state: string,
|
|
160
|
+
transition?: { from: string; to: string; reason?: string },
|
|
161
|
+
): void {
|
|
162
|
+
const entry = this.data.packs[packId];
|
|
163
|
+
if (!entry) return;
|
|
164
|
+
|
|
165
|
+
entry.state = state;
|
|
166
|
+
|
|
167
|
+
if (transition) {
|
|
168
|
+
entry.lastTransition = {
|
|
169
|
+
from: transition.from,
|
|
170
|
+
to: transition.to,
|
|
171
|
+
timestamp: new Date().toISOString(),
|
|
172
|
+
reason: transition.reason,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (state === 'disabled') {
|
|
177
|
+
entry.disabledAt = new Date().toISOString();
|
|
178
|
+
} else {
|
|
179
|
+
delete entry.disabledAt;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (state === 'error') {
|
|
183
|
+
// errorMessage is set externally if needed; keep existing value
|
|
184
|
+
} else {
|
|
185
|
+
delete entry.errorMessage;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.dirty = true;
|
|
189
|
+
this.save();
|
|
190
|
+
}
|
|
191
|
+
|
|
144
192
|
private load(): LockfileData {
|
|
145
193
|
if (!existsSync(this.filePath)) {
|
|
146
|
-
return { version:
|
|
194
|
+
return { version: LOCKFILE_VERSION, packs: {} };
|
|
147
195
|
}
|
|
148
196
|
try {
|
|
149
197
|
const raw = JSON.parse(readFileSync(this.filePath, 'utf-8'));
|
|
150
|
-
if (
|
|
198
|
+
if (typeof raw.packs !== 'object') {
|
|
199
|
+
return { version: LOCKFILE_VERSION, packs: {} };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Migrate v1 → v2: add lifecycle fields
|
|
203
|
+
if (raw.version === 1) {
|
|
204
|
+
for (const entry of Object.values(raw.packs) as LockEntry[]) {
|
|
205
|
+
if (!entry.state) {
|
|
206
|
+
entry.state = 'ready';
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
raw.version = LOCKFILE_VERSION;
|
|
210
|
+
// Mark dirty so the migrated data gets persisted on next save
|
|
211
|
+
this.dirty = true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (raw.version === LOCKFILE_VERSION) {
|
|
151
215
|
return raw as LockfileData;
|
|
152
216
|
}
|
|
153
|
-
|
|
217
|
+
|
|
218
|
+
return { version: LOCKFILE_VERSION, packs: {} };
|
|
154
219
|
} catch {
|
|
155
|
-
return { version:
|
|
220
|
+
return { version: LOCKFILE_VERSION, packs: {} };
|
|
156
221
|
}
|
|
157
222
|
}
|
|
158
223
|
}
|