@soleri/core 9.11.0 → 9.13.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/types.d.ts +2 -0
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/brain/brain.d.ts +5 -1
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +97 -10
- package/dist/brain/brain.js.map +1 -1
- package/dist/dream/cron-manager.d.ts +10 -0
- package/dist/dream/cron-manager.d.ts.map +1 -0
- package/dist/dream/cron-manager.js +122 -0
- package/dist/dream/cron-manager.js.map +1 -0
- package/dist/dream/dream-engine.d.ts +34 -0
- package/dist/dream/dream-engine.d.ts.map +1 -0
- package/dist/dream/dream-engine.js +88 -0
- package/dist/dream/dream-engine.js.map +1 -0
- package/dist/dream/dream-ops.d.ts +8 -0
- package/dist/dream/dream-ops.d.ts.map +1 -0
- package/dist/dream/dream-ops.js +49 -0
- package/dist/dream/dream-ops.js.map +1 -0
- package/dist/dream/index.d.ts +7 -0
- package/dist/dream/index.d.ts.map +1 -0
- package/dist/dream/index.js +5 -0
- package/dist/dream/index.js.map +1 -0
- package/dist/dream/schema.d.ts +3 -0
- package/dist/dream/schema.d.ts.map +1 -0
- package/dist/dream/schema.js +16 -0
- package/dist/dream/schema.js.map +1 -0
- package/dist/embeddings/index.d.ts +5 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +3 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/embeddings/openai-provider.d.ts +31 -0
- package/dist/embeddings/openai-provider.d.ts.map +1 -0
- package/dist/embeddings/openai-provider.js +120 -0
- package/dist/embeddings/openai-provider.js.map +1 -0
- package/dist/embeddings/pipeline.d.ts +36 -0
- package/dist/embeddings/pipeline.d.ts.map +1 -0
- package/dist/embeddings/pipeline.js +78 -0
- package/dist/embeddings/pipeline.js.map +1 -0
- package/dist/embeddings/types.d.ts +62 -0
- package/dist/embeddings/types.d.ts.map +1 -0
- package/dist/embeddings/types.js +3 -0
- package/dist/embeddings/types.js.map +1 -0
- package/dist/engine/bin/soleri-engine.js +4 -1
- package/dist/engine/bin/soleri-engine.js.map +1 -1
- package/dist/engine/module-manifest.d.ts.map +1 -1
- package/dist/engine/module-manifest.js +20 -0
- package/dist/engine/module-manifest.js.map +1 -1
- package/dist/engine/register-engine.d.ts.map +1 -1
- package/dist/engine/register-engine.js +12 -0
- package/dist/engine/register-engine.js.map +1 -1
- package/dist/flows/chain-types.d.ts +8 -8
- package/dist/flows/dispatch-registry.d.ts +15 -1
- package/dist/flows/dispatch-registry.d.ts.map +1 -1
- package/dist/flows/dispatch-registry.js +28 -1
- package/dist/flows/dispatch-registry.js.map +1 -1
- package/dist/flows/executor.d.ts +20 -2
- package/dist/flows/executor.d.ts.map +1 -1
- package/dist/flows/executor.js +79 -1
- package/dist/flows/executor.js.map +1 -1
- package/dist/flows/index.d.ts +2 -1
- package/dist/flows/index.d.ts.map +1 -1
- package/dist/flows/index.js.map +1 -1
- package/dist/flows/types.d.ts +43 -21
- package/dist/flows/types.d.ts.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/persona/defaults.d.ts +8 -0
- package/dist/persona/defaults.d.ts.map +1 -1
- package/dist/persona/defaults.js +49 -0
- package/dist/persona/defaults.js.map +1 -1
- package/dist/plugins/types.d.ts +31 -31
- package/dist/runtime/admin-ops.d.ts.map +1 -1
- package/dist/runtime/admin-ops.js +15 -0
- package/dist/runtime/admin-ops.js.map +1 -1
- package/dist/runtime/admin-setup-ops.js +2 -2
- package/dist/runtime/admin-setup-ops.js.map +1 -1
- package/dist/runtime/embedding-ops.d.ts +12 -0
- package/dist/runtime/embedding-ops.d.ts.map +1 -0
- package/dist/runtime/embedding-ops.js +96 -0
- package/dist/runtime/embedding-ops.js.map +1 -0
- package/dist/runtime/facades/embedding-facade.d.ts +7 -0
- package/dist/runtime/facades/embedding-facade.d.ts.map +1 -0
- package/dist/runtime/facades/embedding-facade.js +8 -0
- package/dist/runtime/facades/embedding-facade.js.map +1 -0
- package/dist/runtime/facades/index.d.ts.map +1 -1
- package/dist/runtime/facades/index.js +12 -0
- package/dist/runtime/facades/index.js.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.js +120 -0
- package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
- package/dist/runtime/feature-flags.d.ts.map +1 -1
- package/dist/runtime/feature-flags.js +4 -0
- package/dist/runtime/feature-flags.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +140 -9
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
- package/dist/runtime/planning-extra-ops.js +51 -0
- package/dist/runtime/planning-extra-ops.js.map +1 -1
- package/dist/runtime/preflight.d.ts +32 -0
- package/dist/runtime/preflight.d.ts.map +1 -0
- package/dist/runtime/preflight.js +29 -0
- package/dist/runtime/preflight.js.map +1 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +33 -2
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/types.d.ts +27 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/skills/step-tracker.d.ts +39 -0
- package/dist/skills/step-tracker.d.ts.map +1 -0
- package/dist/skills/step-tracker.js +105 -0
- package/dist/skills/step-tracker.js.map +1 -0
- package/dist/skills/sync-skills.d.ts +3 -2
- package/dist/skills/sync-skills.d.ts.map +1 -1
- package/dist/skills/sync-skills.js +42 -8
- package/dist/skills/sync-skills.js.map +1 -1
- package/dist/subagent/dispatcher.d.ts +4 -3
- package/dist/subagent/dispatcher.d.ts.map +1 -1
- package/dist/subagent/dispatcher.js +57 -35
- package/dist/subagent/dispatcher.js.map +1 -1
- package/dist/subagent/index.d.ts +1 -0
- package/dist/subagent/index.d.ts.map +1 -1
- package/dist/subagent/index.js.map +1 -1
- package/dist/subagent/orphan-reaper.d.ts +51 -4
- package/dist/subagent/orphan-reaper.d.ts.map +1 -1
- package/dist/subagent/orphan-reaper.js +103 -3
- package/dist/subagent/orphan-reaper.js.map +1 -1
- package/dist/subagent/types.d.ts +7 -0
- package/dist/subagent/types.d.ts.map +1 -1
- package/dist/subagent/workspace-resolver.d.ts +2 -0
- package/dist/subagent/workspace-resolver.d.ts.map +1 -1
- package/dist/subagent/workspace-resolver.js +3 -1
- package/dist/subagent/workspace-resolver.js.map +1 -1
- package/dist/vault/vault-entries.d.ts +18 -0
- package/dist/vault/vault-entries.d.ts.map +1 -1
- package/dist/vault/vault-entries.js +73 -0
- package/dist/vault/vault-entries.js.map +1 -1
- package/dist/vault/vault-manager.d.ts.map +1 -1
- package/dist/vault/vault-manager.js +1 -0
- package/dist/vault/vault-manager.js.map +1 -1
- package/dist/vault/vault-schema.d.ts.map +1 -1
- package/dist/vault/vault-schema.js +14 -0
- package/dist/vault/vault-schema.js.map +1 -1
- package/dist/vault/vault.d.ts +1 -0
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js.map +1 -1
- package/package.json +3 -5
- package/src/__tests__/cron-manager.test.ts +132 -0
- package/src/__tests__/deviation-detection.test.ts +234 -0
- package/src/__tests__/embeddings.test.ts +536 -0
- package/src/__tests__/preflight.test.ts +97 -0
- package/src/__tests__/step-persistence.test.ts +324 -0
- package/src/__tests__/step-tracker.test.ts +260 -0
- package/src/__tests__/subagent/dispatcher.test.ts +122 -4
- package/src/__tests__/subagent/orphan-reaper.test.ts +148 -12
- package/src/__tests__/subagent/process-lifecycle.test.ts +422 -0
- package/src/__tests__/subagent/workspace-resolver.test.ts +6 -1
- package/src/adapters/types.ts +2 -0
- package/src/brain/brain.ts +117 -9
- package/src/dream/cron-manager.ts +137 -0
- package/src/dream/dream-engine.ts +119 -0
- package/src/dream/dream-ops.ts +56 -0
- package/src/dream/dream.test.ts +182 -0
- package/src/dream/index.ts +6 -0
- package/src/dream/schema.ts +17 -0
- package/src/embeddings/openai-provider.ts +158 -0
- package/src/embeddings/pipeline.ts +126 -0
- package/src/embeddings/types.ts +67 -0
- package/src/engine/bin/soleri-engine.ts +4 -1
- package/src/engine/module-manifest.test.ts +4 -4
- package/src/engine/module-manifest.ts +20 -0
- package/src/engine/register-engine.ts +12 -0
- package/src/flows/dispatch-registry.ts +44 -1
- package/src/flows/executor.ts +93 -2
- package/src/flows/index.ts +2 -0
- package/src/flows/types.ts +39 -1
- package/src/index.ts +12 -0
- package/src/persona/defaults.test.ts +39 -1
- package/src/persona/defaults.ts +65 -0
- package/src/planning/goal-ancestry.test.ts +3 -5
- package/src/planning/planner.test.ts +2 -3
- package/src/runtime/admin-ops.test.ts +2 -2
- package/src/runtime/admin-ops.ts +17 -0
- package/src/runtime/admin-setup-ops.ts +2 -2
- package/src/runtime/embedding-ops.ts +116 -0
- package/src/runtime/facades/admin-facade.test.ts +31 -0
- package/src/runtime/facades/embedding-facade.ts +11 -0
- package/src/runtime/facades/index.ts +12 -0
- package/src/runtime/facades/orchestrate-facade.test.ts +16 -0
- package/src/runtime/facades/orchestrate-facade.ts +146 -0
- package/src/runtime/feature-flags.ts +4 -0
- package/src/runtime/orchestrate-ops.test.ts +131 -0
- package/src/runtime/orchestrate-ops.ts +158 -10
- package/src/runtime/planning-extra-ops.ts +77 -0
- package/src/runtime/preflight.ts +53 -0
- package/src/runtime/runtime.ts +41 -2
- package/src/runtime/types.ts +20 -0
- package/src/skills/__tests__/sync-skills.test.ts +132 -0
- package/src/skills/step-tracker.ts +162 -0
- package/src/skills/sync-skills.ts +54 -9
- package/src/subagent/dispatcher.ts +62 -39
- package/src/subagent/index.ts +1 -0
- package/src/subagent/orphan-reaper.test.ts +135 -0
- package/src/subagent/orphan-reaper.ts +130 -7
- package/src/subagent/types.ts +10 -0
- package/src/subagent/workspace-resolver.ts +3 -1
- package/src/vault/vault-entries.ts +112 -0
- package/src/vault/vault-manager.ts +1 -0
- package/src/vault/vault-scaling.test.ts +3 -2
- package/src/vault/vault-schema.ts +15 -0
- package/src/vault/vault.ts +1 -0
- package/vitest.config.ts +2 -1
- package/dist/brain/strength-scorer.d.ts +0 -31
- package/dist/brain/strength-scorer.d.ts.map +0 -1
- package/dist/brain/strength-scorer.js +0 -264
- package/dist/brain/strength-scorer.js.map +0 -1
- package/dist/engine/index.d.ts +0 -21
- package/dist/engine/index.d.ts.map +0 -1
- package/dist/engine/index.js +0 -18
- package/dist/engine/index.js.map +0 -1
- package/dist/hooks/index.d.ts +0 -2
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/hooks/index.js +0 -2
- package/dist/hooks/index.js.map +0 -1
- package/dist/persona/index.d.ts +0 -5
- package/dist/persona/index.d.ts.map +0 -1
- package/dist/persona/index.js +0 -4
- package/dist/persona/index.js.map +0 -1
- package/dist/vault/vault-interfaces.d.ts +0 -153
- package/dist/vault/vault-interfaces.d.ts.map +0 -1
- package/dist/vault/vault-interfaces.js +0 -2
- package/dist/vault/vault-interfaces.js.map +0 -1
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { OrphanReaper } from './orphan-reaper.js';
|
|
3
|
+
|
|
4
|
+
describe('OrphanReaper', () => {
|
|
5
|
+
describe('killProcessGroup', () => {
|
|
6
|
+
let reaper: OrphanReaper;
|
|
7
|
+
let killSpy: ReturnType<typeof vi.spyOn>;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
reaper = new OrphanReaper();
|
|
11
|
+
killSpy = vi.spyOn(process, 'kill');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
killSpy.mockRestore();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('kills the entire process group with -pid', () => {
|
|
19
|
+
killSpy.mockImplementation(() => true);
|
|
20
|
+
|
|
21
|
+
const result = reaper.killProcessGroup(1234);
|
|
22
|
+
|
|
23
|
+
expect(killSpy).toHaveBeenCalledWith(-1234, 'SIGTERM');
|
|
24
|
+
expect(result).toEqual({ killed: true, method: 'group' });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('accepts a custom signal', () => {
|
|
28
|
+
killSpy.mockImplementation(() => true);
|
|
29
|
+
|
|
30
|
+
const result = reaper.killProcessGroup(1234, 'SIGKILL');
|
|
31
|
+
|
|
32
|
+
expect(killSpy).toHaveBeenCalledWith(-1234, 'SIGKILL');
|
|
33
|
+
expect(result).toEqual({ killed: true, method: 'group' });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('falls back to single-process kill when group ESRCH', () => {
|
|
37
|
+
killSpy.mockImplementation((pid: number) => {
|
|
38
|
+
if (pid < 0) {
|
|
39
|
+
const err = new Error('No such process') as NodeJS.ErrnoException;
|
|
40
|
+
err.code = 'ESRCH';
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const result = reaper.killProcessGroup(1234);
|
|
47
|
+
|
|
48
|
+
expect(killSpy).toHaveBeenCalledWith(-1234, 'SIGTERM');
|
|
49
|
+
expect(killSpy).toHaveBeenCalledWith(1234, 'SIGTERM');
|
|
50
|
+
expect(result).toEqual({ killed: true, method: 'single' });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('returns killed:false when both group and single fail', () => {
|
|
54
|
+
killSpy.mockImplementation(() => {
|
|
55
|
+
const err = new Error('No such process') as NodeJS.ErrnoException;
|
|
56
|
+
err.code = 'ESRCH';
|
|
57
|
+
throw err;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const result = reaper.killProcessGroup(1234);
|
|
61
|
+
|
|
62
|
+
expect(result).toEqual({ killed: false, method: 'single' });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('returns killed:false on EPERM for group kill', () => {
|
|
66
|
+
killSpy.mockImplementation(() => {
|
|
67
|
+
const err = new Error('Operation not permitted') as NodeJS.ErrnoException;
|
|
68
|
+
err.code = 'EPERM';
|
|
69
|
+
throw err;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const result = reaper.killProcessGroup(1234);
|
|
73
|
+
|
|
74
|
+
expect(result).toEqual({ killed: false, method: 'group' });
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('killAll', () => {
|
|
79
|
+
let reaper: OrphanReaper;
|
|
80
|
+
let killSpy: ReturnType<typeof vi.spyOn>;
|
|
81
|
+
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
reaper = new OrphanReaper();
|
|
84
|
+
killSpy = vi.spyOn(process, 'kill').mockImplementation(() => true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
afterEach(() => {
|
|
88
|
+
killSpy.mockRestore();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('kills all tracked processes and clears tracking', () => {
|
|
92
|
+
reaper.register(100, 'task-a');
|
|
93
|
+
reaper.register(200, 'task-b');
|
|
94
|
+
|
|
95
|
+
const results = reaper.killAll();
|
|
96
|
+
|
|
97
|
+
expect(results.size).toBe(2);
|
|
98
|
+
expect(results.get(100)).toEqual({ killed: true, method: 'group' });
|
|
99
|
+
expect(results.get(200)).toEqual({ killed: true, method: 'group' });
|
|
100
|
+
// Tracking is cleared
|
|
101
|
+
expect(reaper.listTracked()).toEqual([]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('returns empty map when nothing is tracked', () => {
|
|
105
|
+
const results = reaper.killAll();
|
|
106
|
+
expect(results.size).toBe(0);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('uses custom signal for all kills', () => {
|
|
110
|
+
reaper.register(100, 'task-a');
|
|
111
|
+
|
|
112
|
+
reaper.killAll('SIGKILL');
|
|
113
|
+
|
|
114
|
+
expect(killSpy).toHaveBeenCalledWith(-100, 'SIGKILL');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('handles mixed success/failure', () => {
|
|
118
|
+
reaper.register(100, 'task-a');
|
|
119
|
+
reaper.register(200, 'task-b');
|
|
120
|
+
|
|
121
|
+
killSpy.mockImplementation((pid: number) => {
|
|
122
|
+
// PID 100 group kill works, PID 200 is already dead
|
|
123
|
+
if (pid === -100) return true;
|
|
124
|
+
const err = new Error('No such process') as NodeJS.ErrnoException;
|
|
125
|
+
err.code = 'ESRCH';
|
|
126
|
+
throw err;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const results = reaper.killAll();
|
|
130
|
+
|
|
131
|
+
expect(results.get(100)).toEqual({ killed: true, method: 'group' });
|
|
132
|
+
expect(results.get(200)).toEqual({ killed: false, method: 'single' });
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -5,9 +5,34 @@
|
|
|
5
5
|
* - No error → process is alive
|
|
6
6
|
* - ESRCH → process is dead (reap it)
|
|
7
7
|
* - EPERM → process is alive but we lack permission to signal it
|
|
8
|
+
*
|
|
9
|
+
* Process group management:
|
|
10
|
+
* - `killProcessGroup()` sends a signal to the entire process group (-pid)
|
|
11
|
+
* - `killAll()` kills all tracked processes via process groups
|
|
12
|
+
* - Group kill only works if the child was spawned with `detached: true`
|
|
13
|
+
* or is otherwise a process group leader. Falls back to single-process
|
|
14
|
+
* kill when the group doesn't exist (ESRCH on -pid).
|
|
8
15
|
*/
|
|
9
16
|
|
|
10
|
-
import type { TrackedProcess } from './types.js';
|
|
17
|
+
import type { TrackedProcess, KillResult } from './types.js';
|
|
18
|
+
|
|
19
|
+
const KILL_GRACE_PERIOD_MS = 5_000;
|
|
20
|
+
|
|
21
|
+
/** Result of a reap cycle. */
|
|
22
|
+
export interface ReapResult {
|
|
23
|
+
/** Task IDs of processes that were found dead and cleaned up. */
|
|
24
|
+
reaped: string[];
|
|
25
|
+
/** Task IDs of processes that are still alive. */
|
|
26
|
+
alive: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Result of a process group kill attempt */
|
|
30
|
+
export interface ProcessGroupKillResult {
|
|
31
|
+
/** Whether the kill signal was delivered */
|
|
32
|
+
killed: boolean;
|
|
33
|
+
/** Whether the group or single-process kill path was used */
|
|
34
|
+
method: 'group' | 'single';
|
|
35
|
+
}
|
|
11
36
|
|
|
12
37
|
export class OrphanReaper {
|
|
13
38
|
private readonly tracked = new Map<number, TrackedProcess>();
|
|
@@ -29,20 +54,68 @@ export class OrphanReaper {
|
|
|
29
54
|
|
|
30
55
|
/**
|
|
31
56
|
* Check each tracked PID for liveness. Dead processes are removed from
|
|
32
|
-
* tracking, the onOrphan callback is invoked, and
|
|
57
|
+
* tracking, the onOrphan callback is invoked, and the result summarises
|
|
58
|
+
* which task IDs were reaped vs still alive.
|
|
33
59
|
*/
|
|
34
|
-
reap():
|
|
35
|
-
const reaped:
|
|
60
|
+
reap(): ReapResult {
|
|
61
|
+
const reaped: string[] = [];
|
|
62
|
+
const alive: string[] = [];
|
|
36
63
|
|
|
37
64
|
for (const [pid, entry] of this.tracked) {
|
|
38
65
|
if (!this.isAlive(pid)) {
|
|
39
66
|
this.tracked.delete(pid);
|
|
40
67
|
this.onOrphan?.(entry.taskId, pid);
|
|
41
|
-
reaped.push(entry);
|
|
68
|
+
reaped.push(entry.taskId);
|
|
69
|
+
} else {
|
|
70
|
+
alive.push(entry.taskId);
|
|
42
71
|
}
|
|
43
72
|
}
|
|
44
73
|
|
|
45
|
-
return reaped;
|
|
74
|
+
return { reaped, alive };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Kill a process by PID. Sends SIGTERM first; if `escalate` is true
|
|
79
|
+
* (default), waits 5 seconds and sends SIGKILL if still alive.
|
|
80
|
+
*
|
|
81
|
+
* Handles gracefully: process already dead, EPERM, etc.
|
|
82
|
+
*/
|
|
83
|
+
async killProcess(pid: number, escalate = true): Promise<KillResult> {
|
|
84
|
+
// If already dead, report as killed via SIGTERM (no-op)
|
|
85
|
+
if (!this.isAlive(pid)) {
|
|
86
|
+
return { killed: true, signal: 'SIGTERM' };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Send SIGTERM
|
|
90
|
+
try {
|
|
91
|
+
process.kill(pid, 'SIGTERM');
|
|
92
|
+
} catch {
|
|
93
|
+
// ESRCH = already dead, EPERM = can't signal
|
|
94
|
+
return { killed: !this.isAlive(pid), signal: 'SIGTERM' };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// If not escalating, check once and return
|
|
98
|
+
if (!escalate) {
|
|
99
|
+
// Give a brief moment for the signal to take effect
|
|
100
|
+
return { killed: !this.isAlive(pid), signal: 'SIGTERM' };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Wait grace period, then check if still alive
|
|
104
|
+
await new Promise<void>((resolve) => setTimeout(resolve, KILL_GRACE_PERIOD_MS));
|
|
105
|
+
|
|
106
|
+
if (!this.isAlive(pid)) {
|
|
107
|
+
return { killed: true, signal: 'SIGTERM' };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Escalate to SIGKILL
|
|
111
|
+
try {
|
|
112
|
+
process.kill(pid, 'SIGKILL');
|
|
113
|
+
} catch {
|
|
114
|
+
// Process may have died between check and kill
|
|
115
|
+
return { killed: !this.isAlive(pid), signal: 'SIGKILL' };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { killed: true, signal: 'SIGKILL' };
|
|
46
119
|
}
|
|
47
120
|
|
|
48
121
|
/** Return all currently tracked processes. */
|
|
@@ -60,6 +133,56 @@ export class OrphanReaper {
|
|
|
60
133
|
this.tracked.clear();
|
|
61
134
|
}
|
|
62
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Kill an entire process group by negating the PID.
|
|
138
|
+
*
|
|
139
|
+
* Attempts `process.kill(-pid, signal)` first to kill the whole group.
|
|
140
|
+
* Falls back to `process.kill(pid, signal)` if the group kill fails
|
|
141
|
+
* (e.g., ESRCH when the process isn't a group leader).
|
|
142
|
+
*
|
|
143
|
+
* **Limitation:** Group kill only works if the child was spawned with
|
|
144
|
+
* `detached: true` or is otherwise a process group leader. On macOS,
|
|
145
|
+
* `process.kill(-pid)` works for process group leaders.
|
|
146
|
+
*/
|
|
147
|
+
killProcessGroup(pid: number, signal: NodeJS.Signals = 'SIGTERM'): ProcessGroupKillResult {
|
|
148
|
+
// Try group kill first
|
|
149
|
+
try {
|
|
150
|
+
process.kill(-pid, signal);
|
|
151
|
+
return { killed: true, method: 'group' };
|
|
152
|
+
} catch (groupErr: unknown) {
|
|
153
|
+
const groupCode = (groupErr as NodeJS.ErrnoException).code;
|
|
154
|
+
// ESRCH on the group means it's not a group leader — fall back to single
|
|
155
|
+
if (groupCode === 'ESRCH') {
|
|
156
|
+
try {
|
|
157
|
+
process.kill(pid, signal);
|
|
158
|
+
return { killed: true, method: 'single' };
|
|
159
|
+
} catch {
|
|
160
|
+
// Process is already dead
|
|
161
|
+
return { killed: false, method: 'single' };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// EPERM means the process group exists but we can't signal it
|
|
165
|
+
// Any other error — process is dead or inaccessible
|
|
166
|
+
return { killed: false, method: 'group' };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Kill all tracked processes via process groups and clear tracking.
|
|
172
|
+
*
|
|
173
|
+
* Returns a summary of kill results keyed by PID.
|
|
174
|
+
*/
|
|
175
|
+
killAll(signal: NodeJS.Signals = 'SIGTERM'): Map<number, ProcessGroupKillResult> {
|
|
176
|
+
const results = new Map<number, ProcessGroupKillResult>();
|
|
177
|
+
|
|
178
|
+
for (const [pid] of this.tracked) {
|
|
179
|
+
results.set(pid, this.killProcessGroup(pid, signal));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.tracked.clear();
|
|
183
|
+
return results;
|
|
184
|
+
}
|
|
185
|
+
|
|
63
186
|
// ── internals ──────────────────────────────────────────────────────
|
|
64
187
|
|
|
65
188
|
/**
|
|
@@ -68,7 +191,7 @@ export class OrphanReaper {
|
|
|
68
191
|
* - EPERM → alive (exists but we can't signal it)
|
|
69
192
|
* - ESRCH → dead
|
|
70
193
|
*/
|
|
71
|
-
|
|
194
|
+
isAlive(pid: number): boolean {
|
|
72
195
|
try {
|
|
73
196
|
process.kill(pid, 0);
|
|
74
197
|
return true;
|
package/src/subagent/types.ts
CHANGED
|
@@ -136,3 +136,13 @@ export interface TrackedProcess {
|
|
|
136
136
|
/** When the process was registered */
|
|
137
137
|
registeredAt: number;
|
|
138
138
|
}
|
|
139
|
+
|
|
140
|
+
// ─── Kill Result ───────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
/** Result of attempting to kill a process */
|
|
143
|
+
export interface KillResult {
|
|
144
|
+
/** Whether the process was successfully killed */
|
|
145
|
+
killed: boolean;
|
|
146
|
+
/** Which signal ultimately killed the process */
|
|
147
|
+
signal: 'SIGTERM' | 'SIGKILL';
|
|
148
|
+
}
|
|
@@ -72,6 +72,8 @@ export class WorkspaceResolver {
|
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* Remove the worktree for a given task.
|
|
75
|
+
* Deletes the worktree directory and local branch.
|
|
76
|
+
* Worktree branches are local-only — never pushed to remote.
|
|
75
77
|
* Silently handles errors (e.g., worktree already removed).
|
|
76
78
|
*/
|
|
77
79
|
cleanup(taskId: string): void {
|
|
@@ -86,7 +88,7 @@ export class WorkspaceResolver {
|
|
|
86
88
|
// Silently ignore — worktree may already be gone
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
// Clean up the branch
|
|
91
|
+
// Clean up the local branch (worktree branches are local-only)
|
|
90
92
|
if (info.branch) {
|
|
91
93
|
try {
|
|
92
94
|
execSync(`git branch -D "${info.branch}"`, { ...EXEC_OPTS, cwd: this.baseDir });
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { PersistenceProvider } from '../persistence/types.js';
|
|
6
6
|
import type { IntelligenceEntry } from '../intelligence/types.js';
|
|
7
|
+
import type { StoredVector } from '../embeddings/types.js';
|
|
7
8
|
import type { LinkManager } from './linking.js';
|
|
8
9
|
import { computeContentHash } from './content-hash.js';
|
|
9
10
|
import type { SearchResult, VaultStats } from './vault.js';
|
|
@@ -377,6 +378,117 @@ export function contentHashStats(provider: PersistenceProvider): {
|
|
|
377
378
|
return { total, hashed, uniqueHashes };
|
|
378
379
|
}
|
|
379
380
|
|
|
381
|
+
// ── Vector Operations ────────────────────────────────────────────────────
|
|
382
|
+
|
|
383
|
+
/** Store a vector for an entry. Upserts — replaces if exists. */
|
|
384
|
+
export function storeVector(
|
|
385
|
+
provider: PersistenceProvider,
|
|
386
|
+
entryId: string,
|
|
387
|
+
vector: number[],
|
|
388
|
+
model: string,
|
|
389
|
+
dimensions: number,
|
|
390
|
+
): void {
|
|
391
|
+
const blob = Buffer.from(new Float32Array(vector).buffer);
|
|
392
|
+
provider.run(
|
|
393
|
+
`INSERT INTO entry_vectors (entry_id, vector, model, dimensions, created_at)
|
|
394
|
+
VALUES (@entryId, @vector, @model, @dimensions, @createdAt)
|
|
395
|
+
ON CONFLICT(entry_id) DO UPDATE SET
|
|
396
|
+
vector = excluded.vector, model = excluded.model,
|
|
397
|
+
dimensions = excluded.dimensions, created_at = excluded.created_at`,
|
|
398
|
+
{
|
|
399
|
+
entryId,
|
|
400
|
+
vector: blob,
|
|
401
|
+
model,
|
|
402
|
+
dimensions,
|
|
403
|
+
createdAt: Date.now(),
|
|
404
|
+
},
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** Get the stored vector for an entry, or null. */
|
|
409
|
+
export function getVector(provider: PersistenceProvider, entryId: string): StoredVector | null {
|
|
410
|
+
const row = provider.get<{
|
|
411
|
+
entry_id: string;
|
|
412
|
+
vector: Buffer;
|
|
413
|
+
model: string;
|
|
414
|
+
dimensions: number;
|
|
415
|
+
created_at: number;
|
|
416
|
+
}>('SELECT * FROM entry_vectors WHERE entry_id = ?', [entryId]);
|
|
417
|
+
if (!row) return null;
|
|
418
|
+
return {
|
|
419
|
+
entryId: row.entry_id,
|
|
420
|
+
vector: Array.from(
|
|
421
|
+
new Float32Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength / 4),
|
|
422
|
+
),
|
|
423
|
+
model: row.model,
|
|
424
|
+
dimensions: row.dimensions,
|
|
425
|
+
createdAt: row.created_at,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** Delete the vector for an entry. */
|
|
430
|
+
export function deleteVector(provider: PersistenceProvider, entryId: string): void {
|
|
431
|
+
provider.run('DELETE FROM entry_vectors WHERE entry_id = ?', [entryId]);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** Get IDs of entries that have no vector for the given model. */
|
|
435
|
+
export function getEntriesWithoutVectors(provider: PersistenceProvider, model: string): string[] {
|
|
436
|
+
const rows = provider.all<{ id: string }>(
|
|
437
|
+
`SELECT e.id FROM entries e
|
|
438
|
+
LEFT JOIN entry_vectors ev ON e.id = ev.entry_id AND ev.model = @model
|
|
439
|
+
WHERE ev.entry_id IS NULL`,
|
|
440
|
+
{ model },
|
|
441
|
+
);
|
|
442
|
+
return rows.map((r) => r.id);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Brute-force cosine similarity search over all stored vectors.
|
|
447
|
+
* Returns top-K entries sorted by similarity descending.
|
|
448
|
+
* For <100K entries, brute-force is fast enough (~50ms).
|
|
449
|
+
*/
|
|
450
|
+
export function cosineSearch(
|
|
451
|
+
provider: PersistenceProvider,
|
|
452
|
+
queryVector: number[],
|
|
453
|
+
topK: number,
|
|
454
|
+
): Array<{ entryId: string; similarity: number }> {
|
|
455
|
+
const rows = provider.all<{
|
|
456
|
+
entry_id: string;
|
|
457
|
+
vector: Buffer;
|
|
458
|
+
}>('SELECT entry_id, vector FROM entry_vectors');
|
|
459
|
+
|
|
460
|
+
// Precompute query norm
|
|
461
|
+
let queryNorm = 0;
|
|
462
|
+
for (let i = 0; i < queryVector.length; i++) {
|
|
463
|
+
queryNorm += queryVector[i] * queryVector[i];
|
|
464
|
+
}
|
|
465
|
+
queryNorm = Math.sqrt(queryNorm);
|
|
466
|
+
if (queryNorm === 0) return [];
|
|
467
|
+
|
|
468
|
+
const results: Array<{ entryId: string; similarity: number }> = [];
|
|
469
|
+
|
|
470
|
+
for (const row of rows) {
|
|
471
|
+
const stored = new Float32Array(
|
|
472
|
+
row.vector.buffer,
|
|
473
|
+
row.vector.byteOffset,
|
|
474
|
+
row.vector.byteLength / 4,
|
|
475
|
+
);
|
|
476
|
+
let dot = 0;
|
|
477
|
+
let storedNorm = 0;
|
|
478
|
+
for (let i = 0; i < stored.length; i++) {
|
|
479
|
+
dot += queryVector[i] * stored[i];
|
|
480
|
+
storedNorm += stored[i] * stored[i];
|
|
481
|
+
}
|
|
482
|
+
storedNorm = Math.sqrt(storedNorm);
|
|
483
|
+
if (storedNorm === 0) continue;
|
|
484
|
+
const similarity = dot / (queryNorm * storedNorm);
|
|
485
|
+
results.push({ entryId: row.entry_id, similarity });
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
results.sort((a, b) => b.similarity - a.similarity);
|
|
489
|
+
return results.slice(0, topK);
|
|
490
|
+
}
|
|
491
|
+
|
|
380
492
|
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
381
493
|
|
|
382
494
|
function gc(provider: PersistenceProvider, col: string): Record<string, number> {
|
|
@@ -11,6 +11,7 @@ import { Brain } from '../brain/brain.js';
|
|
|
11
11
|
import type { IntelligenceEntry } from '../intelligence/types.js';
|
|
12
12
|
|
|
13
13
|
const isCI = !!process.env.CI;
|
|
14
|
+
const isWindows = process.platform === 'win32';
|
|
14
15
|
const DOMAINS = ['design', 'a11y', 'performance', 'security', 'architecture', 'testing', 'ux'];
|
|
15
16
|
const TYPES: IntelligenceEntry['type'][] = ['pattern', 'anti-pattern', 'rule', 'playbook'];
|
|
16
17
|
const SEVERITIES: IntelligenceEntry['severity'][] = ['critical', 'warning', 'suggestion'];
|
|
@@ -77,7 +78,7 @@ describe('Vault Scaling — 10K entries', () => {
|
|
|
77
78
|
const elapsed = performance.now() - start;
|
|
78
79
|
|
|
79
80
|
expect(results.length).toBeGreaterThan(0);
|
|
80
|
-
expect(elapsed).toBeLessThan(isCI ? 500 : 50);
|
|
81
|
+
expect(elapsed).toBeLessThan(isCI ? (isWindows ? 2000 : 500) : 50);
|
|
81
82
|
}
|
|
82
83
|
});
|
|
83
84
|
|
|
@@ -90,7 +91,7 @@ describe('Vault Scaling — 10K entries', () => {
|
|
|
90
91
|
const elapsed = performance.now() - start;
|
|
91
92
|
|
|
92
93
|
expect(results.length).toBeGreaterThan(0);
|
|
93
|
-
expect(elapsed).toBeLessThan(isCI ?
|
|
94
|
+
expect(elapsed).toBeLessThan(isCI ? (isWindows ? 2000 : 1000) : 200);
|
|
94
95
|
});
|
|
95
96
|
|
|
96
97
|
test('list with filters under 200ms at 10K', () => {
|
|
@@ -28,6 +28,7 @@ export function initializeSchema(provider: PersistenceProvider): void {
|
|
|
28
28
|
migrateContentHash(provider);
|
|
29
29
|
migrateTierColumn(provider);
|
|
30
30
|
migratePerformanceIndexes(provider);
|
|
31
|
+
migrateVectorStorage(provider);
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
function createCoreTables(provider: PersistenceProvider): void {
|
|
@@ -257,3 +258,17 @@ export function migratePerformanceIndexes(provider: PersistenceProvider): void {
|
|
|
257
258
|
/* brain_sessions table doesn't exist yet — indexes will be created on next init */
|
|
258
259
|
}
|
|
259
260
|
}
|
|
261
|
+
|
|
262
|
+
function migrateVectorStorage(provider: PersistenceProvider): void {
|
|
263
|
+
provider.execSql(`
|
|
264
|
+
CREATE TABLE IF NOT EXISTS entry_vectors (
|
|
265
|
+
entry_id TEXT PRIMARY KEY,
|
|
266
|
+
vector BLOB NOT NULL,
|
|
267
|
+
model TEXT NOT NULL,
|
|
268
|
+
dimensions INTEGER NOT NULL,
|
|
269
|
+
created_at INTEGER NOT NULL,
|
|
270
|
+
FOREIGN KEY (entry_id) REFERENCES entries(id) ON DELETE CASCADE
|
|
271
|
+
);
|
|
272
|
+
`);
|
|
273
|
+
provider.execSql('CREATE INDEX IF NOT EXISTS idx_entry_vectors_model ON entry_vectors(model)');
|
|
274
|
+
}
|
package/src/vault/vault.ts
CHANGED
package/vitest.config.ts
CHANGED
|
@@ -6,7 +6,8 @@ export default defineConfig({
|
|
|
6
6
|
pool: 'forks',
|
|
7
7
|
poolOptions: { forks: { singleFork: true } },
|
|
8
8
|
testTimeout: 30_000,
|
|
9
|
-
|
|
9
|
+
include: ['src/**/*.test.ts'],
|
|
10
|
+
exclude: ['**/node_modules/**'],
|
|
10
11
|
coverage: {
|
|
11
12
|
provider: 'v8',
|
|
12
13
|
include: ['src/**/*.ts'],
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Strength Scorer — computes pattern strength scores from brain feedback data.
|
|
3
|
-
*
|
|
4
|
-
* Extracted from intelligence.ts (Phase 1, Wave 1A).
|
|
5
|
-
* Responsible for: computeStrengths, getStrengths, recommend, buildGlobalRegistry, buildDomainProfiles.
|
|
6
|
-
*/
|
|
7
|
-
import type { Vault } from '../vault/vault.js';
|
|
8
|
-
import type { PatternStrength, StrengthsQuery, GlobalPattern, DomainProfile } from './types.js';
|
|
9
|
-
export declare class StrengthScorer {
|
|
10
|
-
private vault;
|
|
11
|
-
private provider;
|
|
12
|
-
constructor(vault: Vault);
|
|
13
|
-
computeStrengths(): PatternStrength[];
|
|
14
|
-
getStrengths(query?: StrengthsQuery): PatternStrength[];
|
|
15
|
-
recommend(context: {
|
|
16
|
-
domain?: string;
|
|
17
|
-
task?: string;
|
|
18
|
-
source?: string;
|
|
19
|
-
limit?: number;
|
|
20
|
-
}): PatternStrength[];
|
|
21
|
-
buildGlobalRegistry(strengths: PatternStrength[]): number;
|
|
22
|
-
buildDomainProfiles(strengths: PatternStrength[]): number;
|
|
23
|
-
getGlobalPatterns(limit?: number): GlobalPattern[];
|
|
24
|
-
getDomainProfile(domain: string): DomainProfile | null;
|
|
25
|
-
private scoreFromFeedback;
|
|
26
|
-
private persistStrength;
|
|
27
|
-
private expandWithFallback;
|
|
28
|
-
private boostByTaskContext;
|
|
29
|
-
private boostBySource;
|
|
30
|
-
}
|
|
31
|
-
//# sourceMappingURL=strength-scorer.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"strength-scorer.d.ts","sourceRoot":"","sources":["../../src/brain/strength-scorer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE/C,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA2EhG,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,QAAQ,CAAsB;gBAE1B,KAAK,EAAE,KAAK;IAKxB,gBAAgB,IAAI,eAAe,EAAE;IA8BrC,YAAY,CAAC,KAAK,CAAC,EAAE,cAAc,GAAG,eAAe,EAAE;IAyBvD,SAAS,CAAC,OAAO,EAAE;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GAAG,eAAe,EAAE;IAyBrB,mBAAmB,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,MAAM;IA4BzD,mBAAmB,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,MAAM;IAuDzD,iBAAiB,CAAC,KAAK,SAAK,GAAG,aAAa,EAAE;IAkB9C,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAsBtD,OAAO,CAAC,iBAAiB;IA+BzB,OAAO,CAAC,eAAe;IAsBvB,OAAO,CAAC,kBAAkB;IAiB1B,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,aAAa;CAqBtB"}
|