@shmulikdav/solix 1.0.3 → 1.1.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/index.js +150 -24
- package/dist/web/assets/{index-Wwg3QOZU.js → index-6_jn0Tib.js} +205 -205
- package/dist/web/assets/index-DoON6Diy.css +1 -0
- package/dist/web/icons/solix-192.png +0 -0
- package/dist/web/icons/solix-512.png +0 -0
- package/dist/web/index.html +10 -4
- package/dist/web/manifest.webmanifest +1 -0
- package/dist/web/registerSW.js +1 -0
- package/dist/web/sw.js +1 -0
- package/dist/web/workbox-9c191d2f.js +1 -0
- package/package.json +4 -4
- package/dist/web/assets/index-Bqgw2ZPc.css +0 -1
package/dist/index.js
CHANGED
|
@@ -218,6 +218,19 @@ async function demoCmd(opts = {}) {
|
|
|
218
218
|
console.log(` \u2022 1 planet at 87% context (orange flare)`);
|
|
219
219
|
console.log(` \u2022 Compass pinned (always-on)`);
|
|
220
220
|
console.log(`[solix demo] open ${BASE2} to see it.`);
|
|
221
|
+
console.log(
|
|
222
|
+
`[solix demo] in ~3s: Compass will be invoked on demo-a (watch for the toast + Audit tab row).`
|
|
223
|
+
);
|
|
224
|
+
await sleep(3e3);
|
|
225
|
+
await fetch(`${BASE2}/api/advisors/compass/invoke`, {
|
|
226
|
+
method: "POST",
|
|
227
|
+
headers: { "Content-Type": "application/json" },
|
|
228
|
+
body: JSON.stringify({
|
|
229
|
+
targetSessionId: "demo-a",
|
|
230
|
+
prompt: "Review the orbital math refactor before merging"
|
|
231
|
+
})
|
|
232
|
+
});
|
|
233
|
+
console.log(`[solix demo] Compass invoked. Demo complete.`);
|
|
221
234
|
}
|
|
222
235
|
|
|
223
236
|
// src/doctor.ts
|
|
@@ -913,7 +926,9 @@ function getDb() {
|
|
|
913
926
|
db.exec(SCHEMA);
|
|
914
927
|
ensureColumn(db, "sessions", "kind", "kind TEXT NOT NULL DEFAULT 'user'");
|
|
915
928
|
ensureColumn(db, "sessions", "advisor_role", "advisor_role TEXT");
|
|
929
|
+
ensureColumn(db, "sessions", "worktree_path", "worktree_path TEXT");
|
|
916
930
|
ensureColumn(db, "advisors", "texture_pack", "texture_pack TEXT");
|
|
931
|
+
ensureColumn(db, "missions", "error_summary", "error_summary TEXT");
|
|
917
932
|
_db = db;
|
|
918
933
|
return db;
|
|
919
934
|
}
|
|
@@ -997,7 +1012,8 @@ function rowToSession(row) {
|
|
|
997
1012
|
currentMissionId: row.current_mission_id ?? void 0,
|
|
998
1013
|
lastCompletedMissionId: row.last_completed_mission_id ?? void 0,
|
|
999
1014
|
orbitSlot: row.orbit_slot,
|
|
1000
|
-
name: row.name ?? void 0
|
|
1015
|
+
name: row.name ?? void 0,
|
|
1016
|
+
worktreePath: row.worktree_path ?? void 0
|
|
1001
1017
|
};
|
|
1002
1018
|
}
|
|
1003
1019
|
function nextOrbitSlot(db, projectId) {
|
|
@@ -1032,9 +1048,9 @@ function upsertSession(db, input) {
|
|
|
1032
1048
|
`INSERT INTO sessions (
|
|
1033
1049
|
id, pid, project_id, parent_session_id, origin, model, status,
|
|
1034
1050
|
context_usage_pct, orbit_slot, cwd, name, kind, advisor_role,
|
|
1035
|
-
created_at, updated_at
|
|
1051
|
+
worktree_path, created_at, updated_at
|
|
1036
1052
|
)
|
|
1037
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, NULL, ?, ?, ?, ?)`
|
|
1053
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, NULL, ?, ?, ?, ?, ?)`
|
|
1038
1054
|
).run(
|
|
1039
1055
|
input.id,
|
|
1040
1056
|
input.pid,
|
|
@@ -1047,6 +1063,7 @@ function upsertSession(db, input) {
|
|
|
1047
1063
|
input.cwd,
|
|
1048
1064
|
kind,
|
|
1049
1065
|
input.advisorRole ?? null,
|
|
1066
|
+
input.worktreePath ?? null,
|
|
1050
1067
|
ts2,
|
|
1051
1068
|
ts2
|
|
1052
1069
|
);
|
|
@@ -1064,7 +1081,8 @@ function upsertSession(db, input) {
|
|
|
1064
1081
|
advisorRole: input.advisorRole,
|
|
1065
1082
|
parentSessionId: input.parentSessionId,
|
|
1066
1083
|
contextUsagePct: 0,
|
|
1067
|
-
orbitSlot
|
|
1084
|
+
orbitSlot,
|
|
1085
|
+
worktreePath: input.worktreePath
|
|
1068
1086
|
};
|
|
1069
1087
|
}
|
|
1070
1088
|
function setSessionStatus(db, sessionId, status) {
|
|
@@ -1142,9 +1160,16 @@ function rowToMission(row) {
|
|
|
1142
1160
|
subagentCount: row.subagent_count,
|
|
1143
1161
|
toolCallCount: row.tool_call_count
|
|
1144
1162
|
},
|
|
1145
|
-
filesTouched
|
|
1163
|
+
filesTouched,
|
|
1164
|
+
errorSummary: row.error_summary ?? void 0
|
|
1146
1165
|
};
|
|
1147
1166
|
}
|
|
1167
|
+
function setMissionError(db, missionId, errorSummary) {
|
|
1168
|
+
db.prepare(
|
|
1169
|
+
`UPDATE missions SET error_summary = ? WHERE id = ?`
|
|
1170
|
+
).run(errorSummary, missionId);
|
|
1171
|
+
return getMission(db, missionId);
|
|
1172
|
+
}
|
|
1148
1173
|
function shortNameFromPrompt(prompt) {
|
|
1149
1174
|
const words = prompt.trim().split(/\s+/).slice(0, 3);
|
|
1150
1175
|
if (!words.length) return "New Mission";
|
|
@@ -2388,9 +2413,51 @@ function mimeFor(filePath) {
|
|
|
2388
2413
|
}
|
|
2389
2414
|
|
|
2390
2415
|
// ../server/src/launcher.ts
|
|
2391
|
-
import { spawn } from "child_process";
|
|
2392
|
-
import { existsSync as existsSync7 } from "fs";
|
|
2416
|
+
import { spawn, spawnSync as spawnSync2 } from "child_process";
|
|
2417
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
|
|
2418
|
+
import { homedir as homedir5 } from "os";
|
|
2419
|
+
import { basename as basename2, join as join9 } from "path";
|
|
2393
2420
|
import { nanoid as nanoid4 } from "nanoid";
|
|
2421
|
+
function ensureWorktree(opts) {
|
|
2422
|
+
const repoRoot = (() => {
|
|
2423
|
+
const r = spawnSync2("git", ["rev-parse", "--show-toplevel"], {
|
|
2424
|
+
cwd: opts.repoCwd,
|
|
2425
|
+
encoding: "utf8"
|
|
2426
|
+
});
|
|
2427
|
+
if (r.status !== 0) {
|
|
2428
|
+
throw new Error(`not a git repository: ${opts.repoCwd}`);
|
|
2429
|
+
}
|
|
2430
|
+
return (r.stdout ?? "").trim();
|
|
2431
|
+
})();
|
|
2432
|
+
const repoName = basename2(repoRoot);
|
|
2433
|
+
const safeBranch = opts.branch.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
2434
|
+
const worktreesDir = join9(homedir5(), ".solix", "worktrees");
|
|
2435
|
+
const path = join9(worktreesDir, `${repoName}-${safeBranch}`);
|
|
2436
|
+
const list = spawnSync2("git", ["worktree", "list", "--porcelain"], {
|
|
2437
|
+
cwd: repoRoot,
|
|
2438
|
+
encoding: "utf8"
|
|
2439
|
+
});
|
|
2440
|
+
if (list.status === 0 && (list.stdout ?? "").includes(`worktree ${path}`)) {
|
|
2441
|
+
return { path, created: false };
|
|
2442
|
+
}
|
|
2443
|
+
mkdirSync3(worktreesDir, { recursive: true });
|
|
2444
|
+
const branchProbe = spawnSync2(
|
|
2445
|
+
"git",
|
|
2446
|
+
["rev-parse", "--verify", "--quiet", `refs/heads/${opts.branch}`],
|
|
2447
|
+
{ cwd: repoRoot, encoding: "utf8" }
|
|
2448
|
+
);
|
|
2449
|
+
const args = branchProbe.status === 0 ? ["worktree", "add", path, opts.branch] : ["worktree", "add", path, "-b", opts.branch, opts.baseRef ?? "HEAD"];
|
|
2450
|
+
const add = spawnSync2("git", args, {
|
|
2451
|
+
cwd: repoRoot,
|
|
2452
|
+
encoding: "utf8"
|
|
2453
|
+
});
|
|
2454
|
+
if (add.status !== 0) {
|
|
2455
|
+
throw new Error(
|
|
2456
|
+
`git worktree add failed: ${(add.stderr ?? "").slice(0, 280)}`
|
|
2457
|
+
);
|
|
2458
|
+
}
|
|
2459
|
+
return { path, created: true };
|
|
2460
|
+
}
|
|
2394
2461
|
var FAKE_CLAUDE = process.env.SOLIX_FAKE_CLAUDE === "1";
|
|
2395
2462
|
var Launcher = class {
|
|
2396
2463
|
constructor(db, broadcaster) {
|
|
@@ -2544,14 +2611,44 @@ var Launcher = class {
|
|
|
2544
2611
|
*/
|
|
2545
2612
|
launch(opts) {
|
|
2546
2613
|
if (!opts.initialPrompt.trim()) return { ok: false };
|
|
2614
|
+
let spawnCwd = opts.cwd;
|
|
2615
|
+
let worktreePath;
|
|
2616
|
+
if (opts.worktreeBranch?.trim()) {
|
|
2617
|
+
try {
|
|
2618
|
+
const wt = ensureWorktree({
|
|
2619
|
+
repoCwd: opts.cwd,
|
|
2620
|
+
branch: opts.worktreeBranch.trim(),
|
|
2621
|
+
baseRef: opts.worktreeBaseRef?.trim() || void 0
|
|
2622
|
+
});
|
|
2623
|
+
spawnCwd = wt.path;
|
|
2624
|
+
worktreePath = wt.path;
|
|
2625
|
+
this.broadcaster.broadcast({
|
|
2626
|
+
type: "toast",
|
|
2627
|
+
level: "info",
|
|
2628
|
+
message: wt.created ? `Worktree created at ${wt.path}` : `Reusing worktree ${wt.path}`
|
|
2629
|
+
});
|
|
2630
|
+
} catch (err) {
|
|
2631
|
+
this.broadcaster.broadcast({
|
|
2632
|
+
type: "toast",
|
|
2633
|
+
level: "error",
|
|
2634
|
+
message: `Worktree setup failed: ${err.message}`
|
|
2635
|
+
});
|
|
2636
|
+
return { ok: false };
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2547
2639
|
if (FAKE_CLAUDE) {
|
|
2548
|
-
return this.launchSynthetic(
|
|
2640
|
+
return this.launchSynthetic({
|
|
2641
|
+
cwd: spawnCwd,
|
|
2642
|
+
model: opts.model,
|
|
2643
|
+
initialPrompt: opts.initialPrompt,
|
|
2644
|
+
worktreePath
|
|
2645
|
+
});
|
|
2549
2646
|
}
|
|
2550
|
-
if (!existsSync7(
|
|
2647
|
+
if (!existsSync7(spawnCwd)) {
|
|
2551
2648
|
this.broadcaster.broadcast({
|
|
2552
2649
|
type: "toast",
|
|
2553
2650
|
level: "error",
|
|
2554
|
-
message: `Launch failed: cwd does not exist (${
|
|
2651
|
+
message: `Launch failed: cwd does not exist (${spawnCwd})`
|
|
2555
2652
|
});
|
|
2556
2653
|
return { ok: false };
|
|
2557
2654
|
}
|
|
@@ -2561,9 +2658,10 @@ var Launcher = class {
|
|
|
2561
2658
|
const sessionId = `task-${nanoid4(8)}`;
|
|
2562
2659
|
return this.spawnPrint({
|
|
2563
2660
|
sessionId,
|
|
2564
|
-
cwd:
|
|
2661
|
+
cwd: spawnCwd,
|
|
2565
2662
|
args,
|
|
2566
|
-
isFollowUp: false
|
|
2663
|
+
isFollowUp: false,
|
|
2664
|
+
worktreePath
|
|
2567
2665
|
});
|
|
2568
2666
|
}
|
|
2569
2667
|
sendPromptToInternal(sessionId, text) {
|
|
@@ -2609,6 +2707,15 @@ var Launcher = class {
|
|
|
2609
2707
|
isFollowUp: true
|
|
2610
2708
|
}).ok;
|
|
2611
2709
|
}
|
|
2710
|
+
/** Returns the worktree path the launcher resolved for an internal task,
|
|
2711
|
+
* if any. Used by router.onSessionStart to persist worktree_path on the
|
|
2712
|
+
* session row when claude reports its session_start hook. */
|
|
2713
|
+
worktreePathForInternalCwd(cwd) {
|
|
2714
|
+
for (const rec of this.internalTasks.values()) {
|
|
2715
|
+
if (rec.cwd === cwd && rec.worktreePath) return rec.worktreePath;
|
|
2716
|
+
}
|
|
2717
|
+
return void 0;
|
|
2718
|
+
}
|
|
2612
2719
|
spawnPrint(opts) {
|
|
2613
2720
|
let child;
|
|
2614
2721
|
try {
|
|
@@ -2628,7 +2735,10 @@ var Launcher = class {
|
|
|
2628
2735
|
}
|
|
2629
2736
|
const pid = child.pid ?? 0;
|
|
2630
2737
|
if (!opts.isFollowUp) {
|
|
2631
|
-
this.internalTasks.set(opts.sessionId, {
|
|
2738
|
+
this.internalTasks.set(opts.sessionId, {
|
|
2739
|
+
cwd: opts.cwd,
|
|
2740
|
+
worktreePath: opts.worktreePath
|
|
2741
|
+
});
|
|
2632
2742
|
}
|
|
2633
2743
|
let stdout = "";
|
|
2634
2744
|
let stderr = "";
|
|
@@ -2681,7 +2791,8 @@ var Launcher = class {
|
|
|
2681
2791
|
projectId: project.id,
|
|
2682
2792
|
cwd: opts.cwd,
|
|
2683
2793
|
origin: "internal",
|
|
2684
|
-
model: opts.model ?? "sonnet"
|
|
2794
|
+
model: opts.model ?? "sonnet",
|
|
2795
|
+
worktreePath: opts.worktreePath
|
|
2685
2796
|
});
|
|
2686
2797
|
const active = setSessionStatus(this.db, sessionId, "active");
|
|
2687
2798
|
if (active)
|
|
@@ -2824,6 +2935,7 @@ var EventRouter = class {
|
|
|
2824
2935
|
const project = ensureProject(this.db, event.cwd);
|
|
2825
2936
|
const sessionId = this.extractSessionId(event);
|
|
2826
2937
|
const advisorRole = this.launcher?.advisorRoleForPid(event.pid);
|
|
2938
|
+
const worktreePath = this.launcher?.worktreePathForInternalCwd(event.cwd);
|
|
2827
2939
|
const session = upsertSession(this.db, {
|
|
2828
2940
|
id: sessionId,
|
|
2829
2941
|
pid: event.pid,
|
|
@@ -2833,7 +2945,8 @@ var EventRouter = class {
|
|
|
2833
2945
|
model: this.extractModel(event),
|
|
2834
2946
|
parentSessionId: this.extractParentSessionId(event),
|
|
2835
2947
|
kind: advisorRole ? "advisor" : "user",
|
|
2836
|
-
advisorRole
|
|
2948
|
+
advisorRole,
|
|
2949
|
+
worktreePath
|
|
2837
2950
|
});
|
|
2838
2951
|
this.broadcaster.broadcast({ type: "session_upsert", session });
|
|
2839
2952
|
if (!session.parentSessionId) {
|
|
@@ -2959,9 +3072,18 @@ var EventRouter = class {
|
|
|
2959
3072
|
const session = getSession(this.db, sessionId);
|
|
2960
3073
|
if (!session?.currentMissionId) return;
|
|
2961
3074
|
bumpToolCallCount(this.db, session.currentMissionId);
|
|
2962
|
-
const
|
|
2963
|
-
|
|
2964
|
-
|
|
3075
|
+
const payload = event.payload;
|
|
3076
|
+
const toolResponse = payload.tool_response;
|
|
3077
|
+
let updated = getMission(this.db, session.currentMissionId);
|
|
3078
|
+
if (toolResponse?.is_error) {
|
|
3079
|
+
const raw = typeof toolResponse.content === "string" ? toolResponse.content : JSON.stringify(toolResponse.content ?? "");
|
|
3080
|
+
const summary = raw.slice(0, 280).replace(/\s+/g, " ").trim();
|
|
3081
|
+
if (summary) {
|
|
3082
|
+
updated = setMissionError(this.db, session.currentMissionId, summary);
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
if (updated)
|
|
3086
|
+
this.broadcaster.broadcast({ type: "mission_upsert", mission: updated });
|
|
2965
3087
|
}
|
|
2966
3088
|
onNotification(event) {
|
|
2967
3089
|
const sessionId = this.extractSessionId(event);
|
|
@@ -3102,7 +3224,9 @@ var EventRouter = class {
|
|
|
3102
3224
|
return this.launcher.launch({
|
|
3103
3225
|
cwd: opts.cwd,
|
|
3104
3226
|
model: opts.model,
|
|
3105
|
-
initialPrompt: opts.initialPrompt
|
|
3227
|
+
initialPrompt: opts.initialPrompt,
|
|
3228
|
+
worktreeBranch: opts.worktreeBranch,
|
|
3229
|
+
worktreeBaseRef: opts.worktreeBaseRef
|
|
3106
3230
|
});
|
|
3107
3231
|
}
|
|
3108
3232
|
sendPromptToSession(sessionId, text) {
|
|
@@ -3214,7 +3338,9 @@ function handleClientMessage(ctx, _ws, msg) {
|
|
|
3214
3338
|
ctx.router.launchInternalSession({
|
|
3215
3339
|
cwd: msg.cwd,
|
|
3216
3340
|
model: msg.model,
|
|
3217
|
-
initialPrompt: msg.initialPrompt
|
|
3341
|
+
initialPrompt: msg.initialPrompt,
|
|
3342
|
+
worktreeBranch: msg.worktreeBranch,
|
|
3343
|
+
worktreeBaseRef: msg.worktreeBaseRef
|
|
3218
3344
|
});
|
|
3219
3345
|
break;
|
|
3220
3346
|
case "invoke_advisor":
|
|
@@ -3244,9 +3370,9 @@ import {
|
|
|
3244
3370
|
statSync as statSync5,
|
|
3245
3371
|
watch
|
|
3246
3372
|
} from "fs";
|
|
3247
|
-
import { homedir as
|
|
3248
|
-
import { join as
|
|
3249
|
-
var TRANSCRIPT_BASE =
|
|
3373
|
+
import { homedir as homedir6 } from "os";
|
|
3374
|
+
import { join as join10 } from "path";
|
|
3375
|
+
var TRANSCRIPT_BASE = join10(homedir6(), ".claude", "projects");
|
|
3250
3376
|
var CONTEXT_BUDGETS_BY_MODEL = {
|
|
3251
3377
|
"claude-opus-4-7": 2e5,
|
|
3252
3378
|
"claude-opus-4-6": 2e5,
|
|
@@ -3259,7 +3385,7 @@ function encodeProjectPath(cwd) {
|
|
|
3259
3385
|
return cwd.replace(/[/\\]/g, "-");
|
|
3260
3386
|
}
|
|
3261
3387
|
function transcriptPathFor(cwd, sessionId) {
|
|
3262
|
-
return
|
|
3388
|
+
return join10(TRANSCRIPT_BASE, encodeProjectPath(cwd), `${sessionId}.jsonl`);
|
|
3263
3389
|
}
|
|
3264
3390
|
var TranscriptWatcherManager = class {
|
|
3265
3391
|
constructor(db, broadcaster) {
|