@pi-unipi/unipi 2.4.2 → 2.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/CHANGELOG.md +49 -0
- package/README.md +2 -0
- package/docs/prefix-cache-architecture.md +89 -0
- package/package.json +24 -22
- package/packages/ask-user/package.json +2 -2
- package/packages/autocomplete/package.json +1 -1
- package/packages/autocomplete/src/constants.ts +2 -0
- package/packages/btw/package.json +2 -2
- package/packages/cocoindex/README.md +2 -1
- package/packages/cocoindex/index.ts +6 -13
- package/packages/cocoindex/package.json +4 -3
- package/packages/cocoindex/tools.ts +45 -11
- package/packages/compactor/README.md +4 -2
- package/packages/compactor/package.json +3 -3
- package/packages/compactor/src/session/snapshot.ts +3 -2
- package/packages/compactor/src/tools/register.ts +6 -2
- package/packages/compactor/src/tools/vcc-recall.ts +18 -3
- package/packages/core/bounded-output.ts +106 -0
- package/packages/core/constants.ts +2 -0
- package/packages/core/index.ts +1 -0
- package/packages/core/package.json +1 -1
- package/packages/core/sandbox.ts +7 -6
- package/packages/footer/package.json +2 -2
- package/packages/image/package.json +2 -2
- package/packages/info-screen/package.json +2 -2
- package/packages/input-shortcuts/package.json +2 -2
- package/packages/kanboard/package.json +2 -2
- package/packages/mcp/README.md +13 -0
- package/packages/mcp/package.json +5 -2
- package/packages/mcp/src/bridge/registry.ts +244 -137
- package/packages/mcp/src/bridge/translator.ts +96 -21
- package/packages/mcp/src/index.ts +43 -67
- package/packages/mcp/src/tui/settings-overlay.ts +3 -9
- package/packages/memory/README.md +15 -11
- package/packages/memory/bridge/mempalace_bridge.py +636 -0
- package/packages/memory/index.ts +56 -34
- package/packages/memory/mempalace.ts +169 -18
- package/packages/memory/package.json +3 -3
- package/packages/memory/storage.ts +27 -10
- package/packages/milestone/README.md +11 -3
- package/packages/milestone/hooks.ts +103 -29
- package/packages/milestone/index.ts +1 -1
- package/packages/milestone/package.json +5 -2
- package/packages/notify/package.json +2 -2
- package/packages/ralph/index.ts +12 -18
- package/packages/ralph/package.json +6 -3
- package/packages/ralph/reminder.ts +40 -0
- package/packages/ralph/tools.ts +5 -1
- package/packages/subagents/README.md +2 -0
- package/packages/subagents/package.json +1 -1
- package/packages/subagents/src/agent-manager.ts +5 -1
- package/packages/subagents/src/agent-runner.ts +2 -2
- package/packages/subagents/src/core-compat.ts +73 -0
- package/packages/subagents/src/custom-agents.ts +10 -2
- package/packages/subagents/src/index.ts +14 -3
- package/packages/subagents/src/types.ts +2 -0
- package/packages/unipi/bundled.js +1445 -686
- package/packages/updater/package.json +2 -2
- package/packages/utility/README.md +9 -0
- package/packages/utility/package.json +2 -2
- package/packages/utility/src/index.ts +48 -0
- package/packages/utility/src/lifecycle/cleanup.ts +29 -0
- package/packages/utility/src/prefix-cache.ts +263 -0
- package/packages/utility/src/types.ts +1 -1
- package/packages/web-api/package.json +2 -2
- package/packages/workflow/README.md +8 -0
- package/packages/workflow/commands.ts +16 -26
- package/packages/workflow/index.ts +165 -85
- package/packages/workflow/package.json +5 -2
|
@@ -162,7 +162,8 @@ var init_constants = __esm({
|
|
|
162
162
|
BADGE_GEN: "badge-gen",
|
|
163
163
|
BADGE_TOGGLE: "badge-toggle",
|
|
164
164
|
BADGE_SETTINGS: "badge-settings",
|
|
165
|
-
UTIL_SETTINGS: "util-settings"
|
|
165
|
+
UTIL_SETTINGS: "util-settings",
|
|
166
|
+
PREFIX_CACHE: "prefix-cache"
|
|
166
167
|
};
|
|
167
168
|
UTILITY_TOOLS = {
|
|
168
169
|
CONTINUE: "continue_task",
|
|
@@ -206,7 +207,8 @@ var init_constants = __esm({
|
|
|
206
207
|
SYNC_INTERVAL_MS: 864e5,
|
|
207
208
|
STARTUP_TIMEOUT_MS: 1e4,
|
|
208
209
|
MAX_SERVERS: 20,
|
|
209
|
-
TOOL_NAME_SEPARATOR: "__"
|
|
210
|
+
TOOL_NAME_SEPARATOR: "__",
|
|
211
|
+
MAX_MODEL_OUTPUT_BYTES: 64 * 1024
|
|
210
212
|
};
|
|
211
213
|
COMPACTOR_INSTRUCTION = "__compactor__";
|
|
212
214
|
COMPACTOR_TOOLS = {
|
|
@@ -758,6 +760,92 @@ var init_tui_width = __esm({
|
|
|
758
760
|
}
|
|
759
761
|
});
|
|
760
762
|
|
|
763
|
+
// packages/core/bounded-output.ts
|
|
764
|
+
import { chmodSync, lstatSync, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
765
|
+
import { homedir } from "node:os";
|
|
766
|
+
import { join as join3 } from "node:path";
|
|
767
|
+
import { randomUUID } from "node:crypto";
|
|
768
|
+
function byteSlice(text, start, end) {
|
|
769
|
+
return Buffer.from(text, "utf8").subarray(start, end).toString("utf8").replace(/\uFFFD+$/u, "");
|
|
770
|
+
}
|
|
771
|
+
function secureArtifactDir(customDir) {
|
|
772
|
+
const dir = customDir ?? join3(homedir(), ".unipi", "tool-results");
|
|
773
|
+
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
774
|
+
let stat = lstatSync(dir);
|
|
775
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
776
|
+
throw new Error(`Refusing unsafe tool-result directory: ${dir}`);
|
|
777
|
+
}
|
|
778
|
+
if ((stat.mode & 63) !== 0) {
|
|
779
|
+
chmodSync(dir, 448);
|
|
780
|
+
stat = lstatSync(dir);
|
|
781
|
+
if ((stat.mode & 63) !== 0) {
|
|
782
|
+
throw new Error(`Refusing non-private tool-result directory: ${dir}`);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return dir;
|
|
786
|
+
}
|
|
787
|
+
function safePrefix(prefix) {
|
|
788
|
+
const safe = prefix.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").slice(0, 48);
|
|
789
|
+
return safe || "tool-result";
|
|
790
|
+
}
|
|
791
|
+
function boundModelOutput(text, options = {}) {
|
|
792
|
+
const maxBytes = Math.max(1024, Math.floor(options.maxBytes ?? DEFAULT_MODEL_OUTPUT_BYTES));
|
|
793
|
+
const originalBytes = Buffer.byteLength(text, "utf8");
|
|
794
|
+
if (originalBytes <= maxBytes) {
|
|
795
|
+
return { text, truncated: false, originalBytes, visibleBytes: originalBytes };
|
|
796
|
+
}
|
|
797
|
+
let artifactPath;
|
|
798
|
+
let artifactWarning;
|
|
799
|
+
if (originalBytes <= MAX_RAW_ARTIFACT_BYTES) {
|
|
800
|
+
try {
|
|
801
|
+
const dir = secureArtifactDir(options.artifactDir);
|
|
802
|
+
artifactPath = join3(dir, `${safePrefix(options.artifactPrefix ?? "tool-result")}-${randomUUID()}.txt`);
|
|
803
|
+
writeFileSync3(artifactPath, text, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
804
|
+
} catch (error) {
|
|
805
|
+
artifactWarning = `Full-output artifact unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
806
|
+
artifactPath = void 0;
|
|
807
|
+
}
|
|
808
|
+
} else {
|
|
809
|
+
artifactWarning = `Full output exceeded the ${MAX_RAW_ARTIFACT_BYTES}-byte local artifact safety cap and was not retained.`;
|
|
810
|
+
}
|
|
811
|
+
const marker = [
|
|
812
|
+
"",
|
|
813
|
+
"--- output bounded by UniPi ---",
|
|
814
|
+
artifactPath ? `Full output: ${artifactPath}` : artifactWarning,
|
|
815
|
+
`Original size: ${originalBytes} bytes; model-visible ceiling: ${maxBytes} bytes.`,
|
|
816
|
+
...artifactPath ? ["Use the read tool with offset/limit to inspect only the needed region."] : []
|
|
817
|
+
].join("\n");
|
|
818
|
+
const omissionReserve = 80;
|
|
819
|
+
const markerBytes = Buffer.byteLength(marker, "utf8");
|
|
820
|
+
const contentBudget = Math.max(1, maxBytes - markerBytes - omissionReserve);
|
|
821
|
+
const headBytes = Math.ceil(contentBudget * 0.75);
|
|
822
|
+
const tailBytes = Math.max(0, contentBudget - headBytes);
|
|
823
|
+
const head = byteSlice(text, 0, headBytes);
|
|
824
|
+
const tail2 = tailBytes > 0 ? byteSlice(text, originalBytes - tailBytes) : "";
|
|
825
|
+
const omission = `
|
|
826
|
+
\u2026 ${Math.max(0, originalBytes - contentBudget)} bytes omitted \u2026
|
|
827
|
+
`;
|
|
828
|
+
let bounded = `${head}${omission}${tail2}${marker}`;
|
|
829
|
+
if (Buffer.byteLength(bounded, "utf8") > maxBytes) {
|
|
830
|
+
bounded = byteSlice(bounded, 0, maxBytes);
|
|
831
|
+
}
|
|
832
|
+
return {
|
|
833
|
+
text: bounded,
|
|
834
|
+
truncated: true,
|
|
835
|
+
originalBytes,
|
|
836
|
+
visibleBytes: Buffer.byteLength(bounded, "utf8"),
|
|
837
|
+
artifactPath
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
var DEFAULT_MODEL_OUTPUT_BYTES, MAX_RAW_ARTIFACT_BYTES;
|
|
841
|
+
var init_bounded_output = __esm({
|
|
842
|
+
"packages/core/bounded-output.ts"() {
|
|
843
|
+
"use strict";
|
|
844
|
+
DEFAULT_MODEL_OUTPUT_BYTES = 64 * 1024;
|
|
845
|
+
MAX_RAW_ARTIFACT_BYTES = 16 * 1024 * 1024;
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
|
|
761
849
|
// packages/core/index.ts
|
|
762
850
|
var core_exports = {};
|
|
763
851
|
__export(core_exports, {
|
|
@@ -773,6 +861,7 @@ __export(core_exports, {
|
|
|
773
861
|
COMPACTOR_DIRS: () => COMPACTOR_DIRS,
|
|
774
862
|
COMPACTOR_INSTRUCTION: () => COMPACTOR_INSTRUCTION,
|
|
775
863
|
COMPACTOR_TOOLS: () => COMPACTOR_TOOLS,
|
|
864
|
+
DEFAULT_MODEL_OUTPUT_BYTES: () => DEFAULT_MODEL_OUTPUT_BYTES,
|
|
776
865
|
FOOTER_COMMANDS: () => FOOTER_COMMANDS,
|
|
777
866
|
IMAGE_COMMANDS: () => IMAGE_COMMANDS,
|
|
778
867
|
IMAGE_DIRS: () => IMAGE_DIRS,
|
|
@@ -781,6 +870,7 @@ __export(core_exports, {
|
|
|
781
870
|
KANBOARD_COMMANDS: () => KANBOARD_COMMANDS,
|
|
782
871
|
KANBOARD_DEFAULTS: () => KANBOARD_DEFAULTS,
|
|
783
872
|
KANBOARD_DIRS: () => KANBOARD_DIRS,
|
|
873
|
+
MAX_RAW_ARTIFACT_BYTES: () => MAX_RAW_ARTIFACT_BYTES,
|
|
784
874
|
MCP_COMMANDS: () => MCP_COMMANDS,
|
|
785
875
|
MCP_DEFAULTS: () => MCP_DEFAULTS,
|
|
786
876
|
MCP_DIRS: () => MCP_DIRS,
|
|
@@ -815,6 +905,7 @@ __export(core_exports, {
|
|
|
815
905
|
WORKFLOW_COMMANDS: () => WORKFLOW_COMMANDS,
|
|
816
906
|
WidthKeyedCache: () => WidthKeyedCache,
|
|
817
907
|
adaptiveInnerWidth: () => adaptiveInnerWidth,
|
|
908
|
+
boundModelOutput: () => boundModelOutput,
|
|
818
909
|
boxInnerWidth: () => boxInnerWidth,
|
|
819
910
|
contentWidth: () => contentWidth,
|
|
820
911
|
emitEvent: () => emitEvent,
|
|
@@ -863,17 +954,46 @@ var init_core = __esm({
|
|
|
863
954
|
init_utils();
|
|
864
955
|
init_model_cache();
|
|
865
956
|
init_tui_width();
|
|
957
|
+
init_bounded_output();
|
|
866
958
|
}
|
|
867
959
|
});
|
|
868
960
|
|
|
869
961
|
// packages/memory/mempalace.ts
|
|
870
962
|
import { spawnSync, spawn } from "node:child_process";
|
|
963
|
+
import { createHash } from "node:crypto";
|
|
964
|
+
import { createRequire } from "node:module";
|
|
871
965
|
import * as fs4 from "node:fs";
|
|
872
966
|
import * as path4 from "node:path";
|
|
873
967
|
import * as os from "node:os";
|
|
874
968
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
875
|
-
function
|
|
876
|
-
|
|
969
|
+
function isReadableFile(candidate) {
|
|
970
|
+
try {
|
|
971
|
+
return fs4.statSync(candidate).isFile() && fs4.accessSync(candidate, fs4.constants.R_OK) === void 0;
|
|
972
|
+
} catch {
|
|
973
|
+
return false;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
function resolveMempalaceBridgePath(moduleUrl = import.meta.url) {
|
|
977
|
+
const moduleDir = path4.dirname(fileURLToPath3(moduleUrl));
|
|
978
|
+
const candidates = [];
|
|
979
|
+
if (process.env.UNIPI_MEMPALACE_BRIDGE) {
|
|
980
|
+
candidates.push(path4.resolve(process.env.UNIPI_MEMPALACE_BRIDGE));
|
|
981
|
+
}
|
|
982
|
+
candidates.push(
|
|
983
|
+
path4.join(moduleDir, "bridge", "mempalace_bridge.py"),
|
|
984
|
+
path4.join(moduleDir, "..", "memory", "bridge", "mempalace_bridge.py")
|
|
985
|
+
);
|
|
986
|
+
try {
|
|
987
|
+
const require2 = createRequire(moduleUrl);
|
|
988
|
+
const memoryPackage = require2.resolve("@pi-unipi/memory/package.json");
|
|
989
|
+
candidates.push(path4.join(path4.dirname(memoryPackage), "bridge", "mempalace_bridge.py"));
|
|
990
|
+
} catch {
|
|
991
|
+
}
|
|
992
|
+
return candidates.find(isReadableFile) ?? null;
|
|
993
|
+
}
|
|
994
|
+
function getBridgePath() {
|
|
995
|
+
if (cachedBridgePath === void 0) cachedBridgePath = resolveMempalaceBridgePath();
|
|
996
|
+
return cachedBridgePath;
|
|
877
997
|
}
|
|
878
998
|
function which(bin) {
|
|
879
999
|
try {
|
|
@@ -973,17 +1093,71 @@ function invalidatePingVerified() {
|
|
|
973
1093
|
} catch {
|
|
974
1094
|
}
|
|
975
1095
|
}
|
|
976
|
-
function
|
|
977
|
-
|
|
1096
|
+
function getMemorySourceFingerprint(sourceDir = path4.join(os.homedir(), ".unipi", "memory")) {
|
|
1097
|
+
const hash = createHash("sha256");
|
|
1098
|
+
if (!fs4.existsSync(sourceDir)) return hash.update("missing").digest("hex");
|
|
1099
|
+
const visit = (dir) => {
|
|
1100
|
+
let entries;
|
|
1101
|
+
try {
|
|
1102
|
+
entries = fs4.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
1103
|
+
} catch {
|
|
1104
|
+
hash.update(`unreadable:${dir}`);
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
for (const entry of entries) {
|
|
1108
|
+
if (entry.name.startsWith(".")) continue;
|
|
1109
|
+
const full = path4.join(dir, entry.name);
|
|
1110
|
+
if (entry.isDirectory()) {
|
|
1111
|
+
visit(full);
|
|
1112
|
+
continue;
|
|
1113
|
+
}
|
|
1114
|
+
if (!entry.isFile() || entry.name !== "memory.db" && !entry.name.endsWith(".md")) continue;
|
|
1115
|
+
try {
|
|
1116
|
+
const stat = fs4.statSync(full);
|
|
1117
|
+
hash.update(`${path4.relative(sourceDir, full)}\0${stat.size}\0${stat.mtimeMs}
|
|
1118
|
+
`);
|
|
1119
|
+
} catch {
|
|
1120
|
+
hash.update(`unreadable:${path4.relative(sourceDir, full)}
|
|
1121
|
+
`);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
visit(sourceDir);
|
|
1126
|
+
return hash.digest("hex");
|
|
978
1127
|
}
|
|
979
|
-
function
|
|
1128
|
+
function readMigrationState(flagPath = MIGRATED_FLAG) {
|
|
980
1129
|
try {
|
|
981
|
-
|
|
982
|
-
|
|
1130
|
+
const parsed = JSON.parse(fs4.readFileSync(flagPath, "utf-8"));
|
|
1131
|
+
if (parsed?.version !== MIGRATION_STATE_VERSION || typeof parsed.completedAt !== "string" || typeof parsed.sourceFingerprint !== "string" || !parsed.result || parsed.result.failed !== 0 || parsed.result.verified !== parsed.result.discovered) return null;
|
|
1132
|
+
return parsed;
|
|
983
1133
|
} catch {
|
|
1134
|
+
return null;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
function isMigrated(sourceFingerprint = getMemorySourceFingerprint(), flagPath = MIGRATED_FLAG) {
|
|
1138
|
+
return readMigrationState(flagPath)?.sourceFingerprint === sourceFingerprint;
|
|
1139
|
+
}
|
|
1140
|
+
function markMigrated(sourceFingerprint, result, flagPath = MIGRATED_FLAG) {
|
|
1141
|
+
if (result.failed !== 0 || result.verified !== result.discovered) return false;
|
|
1142
|
+
try {
|
|
1143
|
+
fs4.mkdirSync(path4.dirname(flagPath), { recursive: true });
|
|
1144
|
+
const state = {
|
|
1145
|
+
version: MIGRATION_STATE_VERSION,
|
|
1146
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1147
|
+
sourceFingerprint,
|
|
1148
|
+
result
|
|
1149
|
+
};
|
|
1150
|
+
const temp = `${flagPath}.${process.pid}.tmp`;
|
|
1151
|
+
fs4.writeFileSync(temp, JSON.stringify(state, null, 2), "utf-8");
|
|
1152
|
+
fs4.renameSync(temp, flagPath);
|
|
1153
|
+
return true;
|
|
1154
|
+
} catch {
|
|
1155
|
+
return false;
|
|
984
1156
|
}
|
|
985
1157
|
}
|
|
986
|
-
function runBridge(install, palace, cmd, args = {}) {
|
|
1158
|
+
function runBridge(install, palace, cmd, args = {}, timeoutMs = 6e4) {
|
|
1159
|
+
const bridgePath = getBridgePath();
|
|
1160
|
+
if (!bridgePath) return null;
|
|
987
1161
|
let argsJson;
|
|
988
1162
|
try {
|
|
989
1163
|
argsJson = JSON.stringify(args);
|
|
@@ -992,9 +1166,9 @@ function runBridge(install, palace, cmd, args = {}) {
|
|
|
992
1166
|
}
|
|
993
1167
|
let res;
|
|
994
1168
|
try {
|
|
995
|
-
res = spawnSync(install.python, [
|
|
1169
|
+
res = spawnSync(install.python, [bridgePath, palace, cmd, argsJson], {
|
|
996
1170
|
encoding: "utf-8",
|
|
997
|
-
timeout:
|
|
1171
|
+
timeout: timeoutMs,
|
|
998
1172
|
maxBuffer: 64 * 1024 * 1024
|
|
999
1173
|
});
|
|
1000
1174
|
} catch {
|
|
@@ -1013,22 +1187,27 @@ function runBridge(install, palace, cmd, args = {}) {
|
|
|
1013
1187
|
return null;
|
|
1014
1188
|
}
|
|
1015
1189
|
}
|
|
1016
|
-
function runBridgeAsync(install, palace, cmd, args = {}) {
|
|
1017
|
-
return new Promise((
|
|
1190
|
+
function runBridgeAsync(install, palace, cmd, args = {}, timeoutMs = 6e4) {
|
|
1191
|
+
return new Promise((resolve14) => {
|
|
1192
|
+
const bridgePath = getBridgePath();
|
|
1193
|
+
if (!bridgePath) {
|
|
1194
|
+
resolve14(null);
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1018
1197
|
let argsJson;
|
|
1019
1198
|
try {
|
|
1020
1199
|
argsJson = JSON.stringify(args);
|
|
1021
1200
|
} catch {
|
|
1022
|
-
|
|
1201
|
+
resolve14(null);
|
|
1023
1202
|
return;
|
|
1024
1203
|
}
|
|
1025
1204
|
let child;
|
|
1026
1205
|
try {
|
|
1027
|
-
child = spawn(install.python, [
|
|
1206
|
+
child = spawn(install.python, [bridgePath, palace, cmd, argsJson], {
|
|
1028
1207
|
stdio: ["ignore", "pipe", "ignore"]
|
|
1029
1208
|
});
|
|
1030
1209
|
} catch {
|
|
1031
|
-
|
|
1210
|
+
resolve14(null);
|
|
1032
1211
|
return;
|
|
1033
1212
|
}
|
|
1034
1213
|
let out = "";
|
|
@@ -1037,7 +1216,7 @@ function runBridgeAsync(install, palace, cmd, args = {}) {
|
|
|
1037
1216
|
if (settled) return;
|
|
1038
1217
|
settled = true;
|
|
1039
1218
|
clearTimeout(timer);
|
|
1040
|
-
|
|
1219
|
+
resolve14(value);
|
|
1041
1220
|
};
|
|
1042
1221
|
const timer = setTimeout(() => {
|
|
1043
1222
|
try {
|
|
@@ -1045,7 +1224,7 @@ function runBridgeAsync(install, palace, cmd, args = {}) {
|
|
|
1045
1224
|
} catch {
|
|
1046
1225
|
}
|
|
1047
1226
|
finish(null);
|
|
1048
|
-
},
|
|
1227
|
+
}, timeoutMs);
|
|
1049
1228
|
timer.unref?.();
|
|
1050
1229
|
child.stdout?.setEncoding("utf-8");
|
|
1051
1230
|
child.stdout?.on("data", (chunk) => {
|
|
@@ -1065,7 +1244,7 @@ function runBridgeAsync(install, palace, cmd, args = {}) {
|
|
|
1065
1244
|
});
|
|
1066
1245
|
});
|
|
1067
1246
|
}
|
|
1068
|
-
var DEFAULT_PALACE, INSTALL_FLAG, MIGRATED_FLAG, PING_VERIFIED_FLAG, PING_VERIFIED_TTL_MS,
|
|
1247
|
+
var DEFAULT_PALACE, INSTALL_FLAG, MIGRATED_FLAG, PING_VERIFIED_FLAG, PING_VERIFIED_TTL_MS, MIGRATION_STATE_VERSION, cachedBridgePath;
|
|
1069
1248
|
var init_mempalace = __esm({
|
|
1070
1249
|
"packages/memory/mempalace.ts"() {
|
|
1071
1250
|
"use strict";
|
|
@@ -1074,7 +1253,7 @@ var init_mempalace = __esm({
|
|
|
1074
1253
|
MIGRATED_FLAG = path4.join(os.homedir(), ".unipi", "memory", ".mempalace-migrated");
|
|
1075
1254
|
PING_VERIFIED_FLAG = path4.join(os.homedir(), ".unipi", "memory", ".mempalace-ping-verified");
|
|
1076
1255
|
PING_VERIFIED_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1077
|
-
|
|
1256
|
+
MIGRATION_STATE_VERSION = 2;
|
|
1078
1257
|
}
|
|
1079
1258
|
});
|
|
1080
1259
|
|
|
@@ -1174,7 +1353,7 @@ function parseMemoryContent(content) {
|
|
|
1174
1353
|
const [, frontmatterStr, body] = match;
|
|
1175
1354
|
const frontmatter = yaml.load(frontmatterStr);
|
|
1176
1355
|
return {
|
|
1177
|
-
id: "",
|
|
1356
|
+
id: frontmatter.id || "",
|
|
1178
1357
|
title: frontmatter.title,
|
|
1179
1358
|
content: body.trim(),
|
|
1180
1359
|
tags: frontmatter.tags || [],
|
|
@@ -1186,6 +1365,7 @@ function parseMemoryContent(content) {
|
|
|
1186
1365
|
}
|
|
1187
1366
|
function writeMemoryFile(filePath, record) {
|
|
1188
1367
|
const frontmatter = {
|
|
1368
|
+
id: record.id,
|
|
1189
1369
|
title: record.title,
|
|
1190
1370
|
tags: record.tags,
|
|
1191
1371
|
project: record.project,
|
|
@@ -1405,12 +1585,15 @@ var init_storage = __esm({
|
|
|
1405
1585
|
}
|
|
1406
1586
|
this.mempalaceInstall = install;
|
|
1407
1587
|
this.backend = "mempalace";
|
|
1408
|
-
|
|
1588
|
+
const sourceFingerprint = getMemorySourceFingerprint(getMemoryBaseDir());
|
|
1589
|
+
if (!isMigrated(sourceFingerprint)) {
|
|
1409
1590
|
try {
|
|
1410
|
-
runBridge(install, this.palacePath, "migrate", {
|
|
1591
|
+
const result = runBridge(install, this.palacePath, "migrate", {
|
|
1411
1592
|
source_dir: getMemoryBaseDir()
|
|
1412
|
-
});
|
|
1413
|
-
|
|
1593
|
+
}, 15 * 6e4);
|
|
1594
|
+
if (result && result.failed === 0 && result.verified === result.discovered) {
|
|
1595
|
+
markMigrated(sourceFingerprint, result);
|
|
1596
|
+
}
|
|
1414
1597
|
} catch {
|
|
1415
1598
|
}
|
|
1416
1599
|
}
|
|
@@ -1597,7 +1780,7 @@ ${record.content}
|
|
|
1597
1780
|
const filePath = path5.join(this.scopeDir, file);
|
|
1598
1781
|
const record = parseMemoryFile(filePath);
|
|
1599
1782
|
if (!record) continue;
|
|
1600
|
-
const id = record.title.toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
1783
|
+
const id = record.id || record.title.toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
1601
1784
|
if (existingIds.has(id)) continue;
|
|
1602
1785
|
try {
|
|
1603
1786
|
record.id = id;
|
|
@@ -2239,12 +2422,12 @@ var init_embedding = __esm({
|
|
|
2239
2422
|
});
|
|
2240
2423
|
|
|
2241
2424
|
// packages/info-screen/usage-parser.ts
|
|
2242
|
-
import { readdirSync as
|
|
2243
|
-
import { join as
|
|
2244
|
-
import { homedir as
|
|
2425
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync5, existsSync as existsSync9, mkdirSync as mkdirSync8, writeFileSync as writeFileSync9, renameSync as renameSync3, openSync, readSync, closeSync } from "node:fs";
|
|
2426
|
+
import { join as join10, dirname as dirname9 } from "node:path";
|
|
2427
|
+
import { homedir as homedir6 } from "node:os";
|
|
2245
2428
|
function getCachePath() {
|
|
2246
|
-
const base = process.env.UNIPI_DIR ||
|
|
2247
|
-
return
|
|
2429
|
+
const base = process.env.UNIPI_DIR || join10(homedir6(), ".unipi");
|
|
2430
|
+
return join10(base, "cache", "usage-stats.json");
|
|
2248
2431
|
}
|
|
2249
2432
|
function readCache() {
|
|
2250
2433
|
const empty = { version: CACHE_VERSION, models: [], files: {} };
|
|
@@ -2262,16 +2445,16 @@ function readCache() {
|
|
|
2262
2445
|
function writeCache(cache) {
|
|
2263
2446
|
try {
|
|
2264
2447
|
const path28 = getCachePath();
|
|
2265
|
-
|
|
2448
|
+
mkdirSync8(dirname9(path28), { recursive: true });
|
|
2266
2449
|
const tmp = `${path28}.${process.pid}.tmp`;
|
|
2267
|
-
|
|
2268
|
-
|
|
2450
|
+
writeFileSync9(tmp, JSON.stringify(cache), "utf-8");
|
|
2451
|
+
renameSync3(tmp, path28);
|
|
2269
2452
|
} catch {
|
|
2270
2453
|
}
|
|
2271
2454
|
}
|
|
2272
2455
|
function getSessionsDir() {
|
|
2273
|
-
const agentDir = process.env.PI_CODING_AGENT_DIR ||
|
|
2274
|
-
return
|
|
2456
|
+
const agentDir = process.env.PI_CODING_AGENT_DIR || join10(homedir6(), ".pi", "agent");
|
|
2457
|
+
return join10(agentDir, "sessions");
|
|
2275
2458
|
}
|
|
2276
2459
|
function getPeriodBounds() {
|
|
2277
2460
|
const now2 = /* @__PURE__ */ new Date();
|
|
@@ -2361,9 +2544,9 @@ function extractRecords(filePath, modelIndex, models) {
|
|
|
2361
2544
|
}
|
|
2362
2545
|
function collectSessionFiles(dir, files) {
|
|
2363
2546
|
try {
|
|
2364
|
-
const entries =
|
|
2547
|
+
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
2365
2548
|
for (const entry of entries) {
|
|
2366
|
-
const entryPath =
|
|
2549
|
+
const entryPath = join10(dir, entry.name);
|
|
2367
2550
|
if (entry.isDirectory()) {
|
|
2368
2551
|
collectSessionFiles(entryPath, files);
|
|
2369
2552
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -2375,7 +2558,7 @@ function collectSessionFiles(dir, files) {
|
|
|
2375
2558
|
}
|
|
2376
2559
|
async function parseUsageStatsAsync() {
|
|
2377
2560
|
const yielder = async () => {
|
|
2378
|
-
await new Promise((
|
|
2561
|
+
await new Promise((resolve14) => setImmediate(resolve14));
|
|
2379
2562
|
};
|
|
2380
2563
|
const { stats, pending } = collectStats(yielder);
|
|
2381
2564
|
if (pending) await pending;
|
|
@@ -2411,7 +2594,7 @@ function collectStats(yielder) {
|
|
|
2411
2594
|
let mtimeMs = 0;
|
|
2412
2595
|
let size = 0;
|
|
2413
2596
|
try {
|
|
2414
|
-
const st =
|
|
2597
|
+
const st = statSync5(filePath);
|
|
2415
2598
|
mtimeMs = st.mtimeMs;
|
|
2416
2599
|
size = st.size;
|
|
2417
2600
|
} catch {
|
|
@@ -2592,21 +2775,21 @@ __export(env_exports, {
|
|
|
2592
2775
|
formatEnvironmentInfo: () => formatEnvironmentInfo,
|
|
2593
2776
|
getEnvironmentInfo: () => getEnvironmentInfo
|
|
2594
2777
|
});
|
|
2595
|
-
import { existsSync as existsSync18, readdirSync as
|
|
2596
|
-
import { join as
|
|
2597
|
-
import { homedir as
|
|
2778
|
+
import { existsSync as existsSync18, readdirSync as readdirSync11 } from "node:fs";
|
|
2779
|
+
import { join as join20, resolve as resolve6 } from "node:path";
|
|
2780
|
+
import { homedir as homedir16 } from "node:os";
|
|
2598
2781
|
function getEnvironmentInfo() {
|
|
2599
2782
|
const unipiModules = [];
|
|
2600
2783
|
const configPaths = [];
|
|
2601
2784
|
const extensionPaths = [];
|
|
2602
2785
|
try {
|
|
2603
|
-
const nodeModules =
|
|
2786
|
+
const nodeModules = resolve6(process.cwd(), "node_modules");
|
|
2604
2787
|
if (existsSync18(nodeModules)) {
|
|
2605
|
-
const scopePath =
|
|
2788
|
+
const scopePath = join20(nodeModules, "@pi-unipi");
|
|
2606
2789
|
if (existsSync18(scopePath)) {
|
|
2607
|
-
for (const entry of
|
|
2790
|
+
for (const entry of readdirSync11(scopePath)) {
|
|
2608
2791
|
if (entry.startsWith(".")) continue;
|
|
2609
|
-
const pkgPath =
|
|
2792
|
+
const pkgPath = join20(scopePath, entry, "package.json");
|
|
2610
2793
|
if (existsSync18(pkgPath)) {
|
|
2611
2794
|
unipiModules.push(`@pi-unipi/${entry}`);
|
|
2612
2795
|
}
|
|
@@ -2615,8 +2798,8 @@ function getEnvironmentInfo() {
|
|
|
2615
2798
|
}
|
|
2616
2799
|
} catch {
|
|
2617
2800
|
}
|
|
2618
|
-
const globalConfig =
|
|
2619
|
-
const projectConfig =
|
|
2801
|
+
const globalConfig = join20(homedir16(), ".unipi", "config");
|
|
2802
|
+
const projectConfig = resolve6(process.cwd(), ".unipi", "config");
|
|
2620
2803
|
if (existsSync18(globalConfig)) {
|
|
2621
2804
|
configPaths.push(globalConfig);
|
|
2622
2805
|
}
|
|
@@ -2624,7 +2807,7 @@ function getEnvironmentInfo() {
|
|
|
2624
2807
|
configPaths.push(projectConfig);
|
|
2625
2808
|
}
|
|
2626
2809
|
try {
|
|
2627
|
-
const piDir =
|
|
2810
|
+
const piDir = join20(homedir16(), ".pi");
|
|
2628
2811
|
if (existsSync18(piDir)) {
|
|
2629
2812
|
extensionPaths.push(piDir);
|
|
2630
2813
|
}
|
|
@@ -3861,11 +4044,11 @@ var init_schema = __esm({
|
|
|
3861
4044
|
});
|
|
3862
4045
|
|
|
3863
4046
|
// packages/compactor/src/config/manager.ts
|
|
3864
|
-
import { existsSync as existsSync31, mkdirSync as
|
|
3865
|
-
import { homedir as
|
|
3866
|
-
import { dirname as
|
|
4047
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync21, readFileSync as readFileSync29, writeFileSync as writeFileSync25 } from "node:fs";
|
|
4048
|
+
import { homedir as homedir22 } from "node:os";
|
|
4049
|
+
import { dirname as dirname24, join as join34 } from "node:path";
|
|
3867
4050
|
function projectConfigPath(cwd) {
|
|
3868
|
-
return
|
|
4051
|
+
return join34(cwd, ".unipi", "config", "compactor.json");
|
|
3869
4052
|
}
|
|
3870
4053
|
function deepMerge(base, override) {
|
|
3871
4054
|
const result = { ...base };
|
|
@@ -3900,9 +4083,9 @@ function loadConfig4(cwd) {
|
|
|
3900
4083
|
function saveConfig3(config, opts) {
|
|
3901
4084
|
try {
|
|
3902
4085
|
const targetPath = opts?.perProject && opts?.cwd ? projectConfigPath(opts.cwd) : COMPACTOR_CONFIG_PATH;
|
|
3903
|
-
const dir =
|
|
3904
|
-
if (!existsSync31(dir))
|
|
3905
|
-
|
|
4086
|
+
const dir = dirname24(targetPath);
|
|
4087
|
+
if (!existsSync31(dir)) mkdirSync21(dir, { recursive: true });
|
|
4088
|
+
writeFileSync25(targetPath, `${JSON.stringify(config, null, 2)}
|
|
3906
4089
|
`);
|
|
3907
4090
|
return { success: true };
|
|
3908
4091
|
} catch (err) {
|
|
@@ -3935,10 +4118,10 @@ function migrateConfig(partial) {
|
|
|
3935
4118
|
}
|
|
3936
4119
|
function scaffoldConfig() {
|
|
3937
4120
|
try {
|
|
3938
|
-
const dir =
|
|
3939
|
-
if (!existsSync31(dir))
|
|
4121
|
+
const dir = dirname24(COMPACTOR_CONFIG_PATH);
|
|
4122
|
+
if (!existsSync31(dir)) mkdirSync21(dir, { recursive: true });
|
|
3940
4123
|
if (!existsSync31(COMPACTOR_CONFIG_PATH)) {
|
|
3941
|
-
|
|
4124
|
+
writeFileSync25(COMPACTOR_CONFIG_PATH, `${JSON.stringify(DEFAULT_COMPACTOR_CONFIG, null, 2)}
|
|
3942
4125
|
`);
|
|
3943
4126
|
}
|
|
3944
4127
|
} catch {
|
|
@@ -3949,7 +4132,7 @@ var init_manager = __esm({
|
|
|
3949
4132
|
"packages/compactor/src/config/manager.ts"() {
|
|
3950
4133
|
"use strict";
|
|
3951
4134
|
init_schema();
|
|
3952
|
-
COMPACTOR_CONFIG_PATH =
|
|
4135
|
+
COMPACTOR_CONFIG_PATH = join34(homedir22(), ".unipi", "config", "compactor", "config.json");
|
|
3953
4136
|
readJson3 = (path28) => {
|
|
3954
4137
|
try {
|
|
3955
4138
|
return JSON.parse(readFileSync29(path28, "utf-8"));
|
|
@@ -5354,7 +5537,6 @@ function buildIntentSection(intentEvents) {
|
|
|
5354
5537
|
function buildResumeSnapshot(events, opts) {
|
|
5355
5538
|
const compactCount = opts?.compactCount ?? 1;
|
|
5356
5539
|
const searchTool = opts?.searchTool ?? "ctx_search";
|
|
5357
|
-
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
5358
5540
|
const fileEvents = [];
|
|
5359
5541
|
const taskEvents = [];
|
|
5360
5542
|
const ruleEvents = [];
|
|
@@ -5430,7 +5612,7 @@ function buildResumeSnapshot(events, opts) {
|
|
|
5430
5612
|
if (skills) sections.push(skills);
|
|
5431
5613
|
const intent = buildIntentSection(intentEvents);
|
|
5432
5614
|
if (intent) sections.push(intent);
|
|
5433
|
-
const header = `<session_resume events="${events.length}" compact_count="${compactCount}"
|
|
5615
|
+
const header = `<session_resume events="${events.length}" compact_count="${compactCount}">`;
|
|
5434
5616
|
const footer = `</session_resume>`;
|
|
5435
5617
|
const body = sections.join("\n\n");
|
|
5436
5618
|
if (body) {
|
|
@@ -5452,9 +5634,9 @@ var init_snapshot = __esm({
|
|
|
5452
5634
|
});
|
|
5453
5635
|
|
|
5454
5636
|
// packages/compactor/src/config/presets.ts
|
|
5455
|
-
import { createHash as
|
|
5637
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
5456
5638
|
function presetHash(config) {
|
|
5457
|
-
return
|
|
5639
|
+
return createHash4("sha256").update(JSON.stringify(config)).digest("hex");
|
|
5458
5640
|
}
|
|
5459
5641
|
function configsEqual(a, b) {
|
|
5460
5642
|
const aHash = presetHash(a);
|
|
@@ -6225,7 +6407,7 @@ __export(policy_exports, {
|
|
|
6225
6407
|
readsOrCreatesPolicy: () => readsOrCreatesPolicy
|
|
6226
6408
|
});
|
|
6227
6409
|
import { existsSync as existsSync36, readFileSync as readFileSync31 } from "node:fs";
|
|
6228
|
-
import { join as
|
|
6410
|
+
import { join as join37 } from "node:path";
|
|
6229
6411
|
function parseBashPattern(pattern) {
|
|
6230
6412
|
const match = pattern.match(/^Bash\((.+)\)$/);
|
|
6231
6413
|
return match ? match[1] : null;
|
|
@@ -6280,7 +6462,7 @@ function fileGlobToRegex(glob, caseInsensitive = false) {
|
|
|
6280
6462
|
return new RegExp(`^${regexStr}$`, caseInsensitive ? "i" : "");
|
|
6281
6463
|
}
|
|
6282
6464
|
function readsOrCreatesPolicy(cwd) {
|
|
6283
|
-
const settingsPath =
|
|
6465
|
+
const settingsPath = join37(cwd, ".pi", "settings.json");
|
|
6284
6466
|
if (!existsSync36(settingsPath)) return { allow: [], deny: [], ask: [] };
|
|
6285
6467
|
try {
|
|
6286
6468
|
const settings = JSON.parse(readFileSync31(settingsPath, "utf-8"));
|
|
@@ -6309,10 +6491,10 @@ __export(evaluator_exports, {
|
|
|
6309
6491
|
splitChainedCommands: () => splitChainedCommands
|
|
6310
6492
|
});
|
|
6311
6493
|
import { realpathSync as realpathSync2, existsSync as existsSync37, readFileSync as readFileSync32 } from "node:fs";
|
|
6312
|
-
import { resolve as
|
|
6313
|
-
import { homedir as
|
|
6494
|
+
import { resolve as resolve12, join as join38 } from "node:path";
|
|
6495
|
+
import { homedir as homedir24 } from "node:os";
|
|
6314
6496
|
function loadProjectPermissions(cwd, basePolicy) {
|
|
6315
|
-
const settingsPath =
|
|
6497
|
+
const settingsPath = join38(cwd, ".pi", "settings.json");
|
|
6316
6498
|
if (!existsSync37(settingsPath)) return basePolicy;
|
|
6317
6499
|
try {
|
|
6318
6500
|
const raw = readFileSync32(settingsPath, "utf-8");
|
|
@@ -6381,10 +6563,10 @@ function splitChainedCommands(command) {
|
|
|
6381
6563
|
return commands;
|
|
6382
6564
|
}
|
|
6383
6565
|
function evaluateFilePath(filePath, policy, cwd = process.cwd()) {
|
|
6384
|
-
const resolved =
|
|
6566
|
+
const resolved = resolve12(cwd, filePath);
|
|
6385
6567
|
try {
|
|
6386
6568
|
const real = realpathSync2(resolved);
|
|
6387
|
-
const home =
|
|
6569
|
+
const home = homedir24();
|
|
6388
6570
|
if (!real.startsWith(cwd) && !real.startsWith(home)) {
|
|
6389
6571
|
return "deny";
|
|
6390
6572
|
}
|
|
@@ -6551,17 +6733,20 @@ var init_thinking_label = __esm({
|
|
|
6551
6733
|
init_core();
|
|
6552
6734
|
import { dirname as dirname2 } from "node:path";
|
|
6553
6735
|
import { fileURLToPath } from "node:url";
|
|
6736
|
+
import {
|
|
6737
|
+
buildSessionContext
|
|
6738
|
+
} from "@earendil-works/pi-coding-agent";
|
|
6554
6739
|
|
|
6555
6740
|
// packages/workflow/commands.ts
|
|
6556
6741
|
init_core();
|
|
6557
6742
|
import { readFileSync as readFileSync3, readdirSync, existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
6558
|
-
import { join as
|
|
6743
|
+
import { join as join4, basename } from "path";
|
|
6559
6744
|
function suggestSpecFiles(prefix) {
|
|
6560
|
-
const specsDir =
|
|
6745
|
+
const specsDir = join4(process.cwd(), ".unipi", "docs", "specs");
|
|
6561
6746
|
if (!existsSync3(specsDir)) return [];
|
|
6562
6747
|
try {
|
|
6563
6748
|
const search2 = prefix?.trim().split(/\s+/).pop() ?? "";
|
|
6564
|
-
const files = readdirSync(specsDir).filter((f) => f.endsWith(".md")).map((f) => ({ name: f, time: statSync2(
|
|
6749
|
+
const files = readdirSync(specsDir).filter((f) => f.endsWith(".md")).map((f) => ({ name: f, time: statSync2(join4(specsDir, f)).mtimeMs })).sort((a, b) => b.time - a.time);
|
|
6565
6750
|
return files.filter((f) => !search2 || f.name.includes(search2)).map((f) => ({
|
|
6566
6751
|
value: `specs:${f.name}`,
|
|
6567
6752
|
label: basename(f.name, ".md"),
|
|
@@ -6572,11 +6757,11 @@ function suggestSpecFiles(prefix) {
|
|
|
6572
6757
|
}
|
|
6573
6758
|
}
|
|
6574
6759
|
function suggestPlanFiles(prefix) {
|
|
6575
|
-
const plansDir =
|
|
6760
|
+
const plansDir = join4(process.cwd(), ".unipi", "docs", "plans");
|
|
6576
6761
|
if (!existsSync3(plansDir)) return [];
|
|
6577
6762
|
try {
|
|
6578
6763
|
const search2 = prefix?.trim().split(/\s+/).pop() ?? "";
|
|
6579
|
-
const files = readdirSync(plansDir).filter((f) => f.endsWith(".md")).map((f) => ({ name: f, time: statSync2(
|
|
6764
|
+
const files = readdirSync(plansDir).filter((f) => f.endsWith(".md")).map((f) => ({ name: f, time: statSync2(join4(plansDir, f)).mtimeMs })).sort((a, b) => b.time - a.time);
|
|
6580
6765
|
return files.filter((f) => !search2 || f.name.includes(search2)).map((f) => ({
|
|
6581
6766
|
value: `plan:${f.name}`,
|
|
6582
6767
|
label: basename(f.name, ".md"),
|
|
@@ -6587,11 +6772,11 @@ function suggestPlanFiles(prefix) {
|
|
|
6587
6772
|
}
|
|
6588
6773
|
}
|
|
6589
6774
|
function suggestDebugFiles(prefix) {
|
|
6590
|
-
const debugDir =
|
|
6775
|
+
const debugDir = join4(process.cwd(), ".unipi", "docs", "debug");
|
|
6591
6776
|
if (!existsSync3(debugDir)) return [];
|
|
6592
6777
|
try {
|
|
6593
6778
|
const search2 = prefix?.trim().split(/\s+/).pop() ?? "";
|
|
6594
|
-
const files = readdirSync(debugDir).filter((f) => f.endsWith(".md")).map((f) => ({ name: f, time: statSync2(
|
|
6779
|
+
const files = readdirSync(debugDir).filter((f) => f.endsWith(".md")).map((f) => ({ name: f, time: statSync2(join4(debugDir, f)).mtimeMs })).sort((a, b) => b.time - a.time);
|
|
6595
6780
|
return files.filter((f) => !search2 || f.name.includes(search2)).map((f) => ({
|
|
6596
6781
|
value: `debug:${f.name}`,
|
|
6597
6782
|
label: basename(f.name, ".md"),
|
|
@@ -6602,11 +6787,11 @@ function suggestDebugFiles(prefix) {
|
|
|
6602
6787
|
}
|
|
6603
6788
|
}
|
|
6604
6789
|
function suggestChoreFiles(prefix) {
|
|
6605
|
-
const choreDir =
|
|
6790
|
+
const choreDir = join4(process.cwd(), ".unipi", "docs", "chore");
|
|
6606
6791
|
if (!existsSync3(choreDir)) return [];
|
|
6607
6792
|
try {
|
|
6608
6793
|
const search2 = prefix?.trim().split(/\s+/).pop() ?? "";
|
|
6609
|
-
const files = readdirSync(choreDir).filter((f) => f.endsWith(".md")).map((f) => ({ name: f, time: statSync2(
|
|
6794
|
+
const files = readdirSync(choreDir).filter((f) => f.endsWith(".md")).map((f) => ({ name: f, time: statSync2(join4(choreDir, f)).mtimeMs })).sort((a, b) => b.time - a.time);
|
|
6610
6795
|
return files.filter((f) => !search2 || f.name.includes(search2)).map((f) => ({
|
|
6611
6796
|
value: `chore:${f.name}`,
|
|
6612
6797
|
label: basename(f.name, ".md"),
|
|
@@ -6622,7 +6807,7 @@ function suggestWorktrees() {
|
|
|
6622
6807
|
if (worktreeSuggestionsCache?.cwd === cwd) {
|
|
6623
6808
|
return worktreeSuggestionsCache.items;
|
|
6624
6809
|
}
|
|
6625
|
-
const worktreesDir =
|
|
6810
|
+
const worktreesDir = join4(cwd, ".unipi", "worktrees");
|
|
6626
6811
|
if (!existsSync3(worktreesDir)) {
|
|
6627
6812
|
worktreeSuggestionsCache = { cwd, items: [] };
|
|
6628
6813
|
return worktreeSuggestionsCache.items;
|
|
@@ -6631,7 +6816,7 @@ function suggestWorktrees() {
|
|
|
6631
6816
|
let findWorktrees2 = function(dir, relativePath) {
|
|
6632
6817
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
6633
6818
|
for (const entry of entries) {
|
|
6634
|
-
const fullPath =
|
|
6819
|
+
const fullPath = join4(dir, entry.name);
|
|
6635
6820
|
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
6636
6821
|
if (entry.name === ".git" && entry.isFile()) {
|
|
6637
6822
|
try {
|
|
@@ -6824,18 +7009,13 @@ function registerWorkflowCommands(pi, options) {
|
|
|
6824
7009
|
fullCommand: `/${fullCommand}`,
|
|
6825
7010
|
args: args?.trim() ?? ""
|
|
6826
7011
|
};
|
|
6827
|
-
if (!options.
|
|
7012
|
+
if (!options.activateSandbox(workflowEvent)) {
|
|
6828
7013
|
if (ctx.hasUI) ctx.ui.notify("Another UniPi workflow is still active", "warning");
|
|
6829
7014
|
return;
|
|
6830
7015
|
}
|
|
6831
|
-
const currentTools = options.getActiveTools();
|
|
6832
|
-
options.saveTools(currentTools);
|
|
6833
|
-
const sandboxTools = getToolsForCommand(cmd.name, currentTools);
|
|
6834
|
-
const sandboxLevel = getSandboxLevel(cmd.name);
|
|
6835
|
-
options.setActiveTools([...sandboxTools], sandboxLevel);
|
|
6836
7016
|
let skillContent = "";
|
|
6837
7017
|
try {
|
|
6838
|
-
const skillPath =
|
|
7018
|
+
const skillPath = join4(
|
|
6839
7019
|
new URL("./skills", import.meta.url).pathname,
|
|
6840
7020
|
cmd.skillName,
|
|
6841
7021
|
"SKILL.md"
|
|
@@ -6861,7 +7041,12 @@ Arguments: ${args.trim()}`;
|
|
|
6861
7041
|
${skillContent}
|
|
6862
7042
|
</skill_content>`;
|
|
6863
7043
|
}
|
|
6864
|
-
|
|
7044
|
+
try {
|
|
7045
|
+
pi.sendUserMessage(message, { deliverAs: "followUp" });
|
|
7046
|
+
} catch (error) {
|
|
7047
|
+
options.abortWorkflow();
|
|
7048
|
+
throw error;
|
|
7049
|
+
}
|
|
6865
7050
|
if (ctx.hasUI) {
|
|
6866
7051
|
ctx.ui.notify(`Running /${fullCommand}`, "info");
|
|
6867
7052
|
const ralphStatus = options.isRalphDetected() ? "\u2713 rl" : "\u25CB rl";
|
|
@@ -6929,90 +7114,147 @@ var WorkflowLifecycle = class {
|
|
|
6929
7114
|
|
|
6930
7115
|
// packages/workflow/index.ts
|
|
6931
7116
|
var VERSION = getPackageVersion(dirname2(fileURLToPath(import.meta.url)));
|
|
6932
|
-
var
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
7117
|
+
var WORKFLOW_SANDBOX_SNAPSHOT_TYPE = "unipi-workflow-sandbox-snapshot";
|
|
7118
|
+
function sandboxRestrictions(level) {
|
|
7119
|
+
const common = [
|
|
7120
|
+
"Do not attempt to call tools blocked by name.",
|
|
7121
|
+
"If the user requests an action that requires a blocked tool, explain that the workflow sandbox does not allow it."
|
|
7122
|
+
];
|
|
7123
|
+
if (level === "brainstorm") {
|
|
7124
|
+
return [
|
|
7125
|
+
"The write tool is restricted to .unipi/docs/specs/ only.",
|
|
7126
|
+
"Use bash only for specific setup operations such as git init or mkdir; use grep, find, and ls for discovery instead of bash.",
|
|
7127
|
+
...common
|
|
7128
|
+
];
|
|
7129
|
+
}
|
|
7130
|
+
if (level === "write_unipi") {
|
|
7131
|
+
return [
|
|
7132
|
+
"The write tool is restricted to .unipi/docs/ only (specs and plans).",
|
|
7133
|
+
"Use grep, find, and ls for file discovery instead of guessing filenames.",
|
|
7134
|
+
"Bash is blocked; use read, write, edit, grep, find, and ls only.",
|
|
7135
|
+
...common
|
|
7136
|
+
];
|
|
7137
|
+
}
|
|
7138
|
+
return common;
|
|
7139
|
+
}
|
|
7140
|
+
function formatActiveSandboxSnapshot(command, level) {
|
|
7141
|
+
const blocked = getBlockedToolsForLevel(level);
|
|
7142
|
+
const blockedLine = blocked.length > 0 ? blocked.join(", ") : "none";
|
|
7143
|
+
return [
|
|
7144
|
+
"# UniPi Workflow Sandbox Snapshot",
|
|
7145
|
+
"This snapshot supersedes all prior UniPi workflow sandbox snapshots; use only this snapshot for workflow sandbox status and restrictions.",
|
|
7146
|
+
"Status: active",
|
|
7147
|
+
`Workflow: /unipi:${command}`,
|
|
7148
|
+
`Sandbox level: ${level}`,
|
|
7149
|
+
`Blocked tool names: ${blockedLine}`,
|
|
7150
|
+
"Pi's provider tool schemas and tool order remain unchanged. Calls to blocked tool names are rejected by the workflow sandbox.",
|
|
7151
|
+
["Restrictions:", ...sandboxRestrictions(level).map((restriction) => `- ${restriction}`)].join("\n")
|
|
7152
|
+
].join("\n\n");
|
|
7153
|
+
}
|
|
7154
|
+
function formatInactiveSandboxSnapshot() {
|
|
7155
|
+
return [
|
|
7156
|
+
"# UniPi Workflow Sandbox Snapshot",
|
|
7157
|
+
"This snapshot supersedes all prior UniPi workflow sandbox snapshots; use only this snapshot for workflow sandbox status and restrictions.",
|
|
7158
|
+
"Status: inactive",
|
|
7159
|
+
"No UniPi workflow sandbox is active. Prior workflow sandbox restrictions no longer apply."
|
|
7160
|
+
].join("\n\n");
|
|
7161
|
+
}
|
|
7162
|
+
function latestEffectiveSandboxSnapshot(branch) {
|
|
7163
|
+
const messages = buildSessionContext(branch).messages;
|
|
7164
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
7165
|
+
const message = messages[index];
|
|
7166
|
+
if (message.role === "custom" && message.customType === WORKFLOW_SANDBOX_SNAPSHOT_TYPE) {
|
|
7167
|
+
return {
|
|
7168
|
+
content: message.content,
|
|
7169
|
+
details: message.details
|
|
7170
|
+
};
|
|
7171
|
+
}
|
|
7172
|
+
}
|
|
7173
|
+
return void 0;
|
|
7174
|
+
}
|
|
7175
|
+
function latestHistoricalSandboxSnapshot(branch) {
|
|
7176
|
+
for (let index = branch.length - 1; index >= 0; index--) {
|
|
7177
|
+
const entry = branch[index];
|
|
7178
|
+
if (entry.type === "custom_message" && entry.customType === WORKFLOW_SANDBOX_SNAPSHOT_TYPE) {
|
|
7179
|
+
return {
|
|
7180
|
+
content: entry.content,
|
|
7181
|
+
details: entry.details
|
|
7182
|
+
};
|
|
7183
|
+
}
|
|
7184
|
+
}
|
|
7185
|
+
return void 0;
|
|
7186
|
+
}
|
|
7187
|
+
function isActiveSnapshot(snapshot) {
|
|
7188
|
+
if (typeof snapshot.details?.active === "boolean") return snapshot.details.active;
|
|
7189
|
+
return typeof snapshot.content === "string" && snapshot.content.includes("Status: active");
|
|
7190
|
+
}
|
|
6937
7191
|
function workflow_default(pi) {
|
|
6938
7192
|
const workflowLifecycle = new WorkflowLifecycle();
|
|
7193
|
+
let ralphDetected = false;
|
|
7194
|
+
let sandboxCommand = null;
|
|
6939
7195
|
registerWorkflowCommands(pi, {
|
|
6940
7196
|
isRalphDetected: () => ralphDetected,
|
|
6941
|
-
|
|
6942
|
-
setActiveTools: (tools, level) => {
|
|
6943
|
-
pi.setActiveTools(tools);
|
|
6944
|
-
sandboxActive = true;
|
|
6945
|
-
currentSandboxLevel = level;
|
|
6946
|
-
currentSandboxTools = tools;
|
|
6947
|
-
},
|
|
6948
|
-
saveTools: (tools) => {
|
|
6949
|
-
savedTools = tools;
|
|
6950
|
-
},
|
|
6951
|
-
startWorkflow: (event) => {
|
|
7197
|
+
activateSandbox: (event) => {
|
|
6952
7198
|
if (!workflowLifecycle.start(event)) return false;
|
|
7199
|
+
sandboxCommand = event.command;
|
|
6953
7200
|
emitEvent(pi, UNIPI_EVENTS.WORKFLOW_START, event);
|
|
6954
7201
|
return true;
|
|
7202
|
+
},
|
|
7203
|
+
abortWorkflow: () => {
|
|
7204
|
+
sandboxCommand = null;
|
|
7205
|
+
workflowLifecycle.reset();
|
|
6955
7206
|
}
|
|
6956
7207
|
});
|
|
6957
7208
|
pi.on("tool_call", async (event, _ctx) => {
|
|
6958
|
-
if (!
|
|
6959
|
-
const
|
|
6960
|
-
if (!
|
|
7209
|
+
if (!sandboxCommand) return;
|
|
7210
|
+
const level = getSandboxLevel(sandboxCommand);
|
|
7211
|
+
if (!isToolAllowed(level, event.toolName)) {
|
|
7212
|
+
const blocked = getBlockedToolsForLevel(level);
|
|
6961
7213
|
return {
|
|
6962
7214
|
block: true,
|
|
6963
|
-
reason: `Tool "${event.toolName}" is not allowed in ${
|
|
7215
|
+
reason: `Tool "${event.toolName}" is not allowed in ${level} sandbox. Blocked: ${blocked.join(", ")}`
|
|
6964
7216
|
};
|
|
6965
7217
|
}
|
|
6966
7218
|
});
|
|
6967
|
-
pi.on("before_agent_start", async (
|
|
6968
|
-
|
|
6969
|
-
const
|
|
6970
|
-
const
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
<sandbox>
|
|
6976
|
-
Sandbox mode: ${currentSandboxLevel}.
|
|
6977
|
-
Available tools: ${allowed.join(", ")}.${blockedLine}`;
|
|
6978
|
-
if (currentSandboxLevel === "brainstorm") {
|
|
6979
|
-
return {
|
|
6980
|
-
systemPrompt: event.systemPrompt + base + `
|
|
6981
|
-
The write tool is available but restricted to .unipi/docs/specs/ only.
|
|
6982
|
-
bash is available ONLY for specific setup use case (e.g., git init, mkdir). Do NOT use bash for reading files or listing directories \u2014 use grep, find, ls instead.
|
|
6983
|
-
Do NOT attempt to call blocked tools. Do NOT output tool call XML for them.
|
|
6984
|
-
If the user requests an action that requires a blocked tool, respond that you do not have access.
|
|
6985
|
-
</sandbox>`
|
|
6986
|
-
};
|
|
6987
|
-
}
|
|
6988
|
-
if (currentSandboxLevel === "write_unipi") {
|
|
7219
|
+
pi.on("before_agent_start", async (_event, ctx) => {
|
|
7220
|
+
const branch = ctx.sessionManager.getBranch();
|
|
7221
|
+
const latest = latestEffectiveSandboxSnapshot(branch);
|
|
7222
|
+
const historical = latestHistoricalSandboxSnapshot(branch);
|
|
7223
|
+
if (sandboxCommand) {
|
|
7224
|
+
const level = getSandboxLevel(sandboxCommand);
|
|
7225
|
+
const content = formatActiveSandboxSnapshot(sandboxCommand, level);
|
|
7226
|
+
if (latest?.content === content) return void 0;
|
|
6989
7227
|
return {
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
7228
|
+
message: {
|
|
7229
|
+
customType: WORKFLOW_SANDBOX_SNAPSHOT_TYPE,
|
|
7230
|
+
content,
|
|
7231
|
+
display: false,
|
|
7232
|
+
details: {
|
|
7233
|
+
active: true,
|
|
7234
|
+
command: sandboxCommand,
|
|
7235
|
+
level
|
|
7236
|
+
}
|
|
7237
|
+
}
|
|
6997
7238
|
};
|
|
6998
7239
|
}
|
|
7240
|
+
const prior = latest ?? historical;
|
|
7241
|
+
if (!prior || !isActiveSnapshot(prior)) return void 0;
|
|
6999
7242
|
return {
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7243
|
+
message: {
|
|
7244
|
+
customType: WORKFLOW_SANDBOX_SNAPSHOT_TYPE,
|
|
7245
|
+
content: formatInactiveSandboxSnapshot(),
|
|
7246
|
+
display: false,
|
|
7247
|
+
details: {
|
|
7248
|
+
active: false
|
|
7249
|
+
}
|
|
7250
|
+
}
|
|
7004
7251
|
};
|
|
7005
7252
|
});
|
|
7006
7253
|
pi.on("agent_end", async (event, _ctx) => {
|
|
7007
|
-
if (sandboxActive && savedTools) {
|
|
7008
|
-
pi.setActiveTools(savedTools);
|
|
7009
|
-
savedTools = null;
|
|
7010
|
-
sandboxActive = false;
|
|
7011
|
-
currentSandboxLevel = null;
|
|
7012
|
-
currentSandboxTools = null;
|
|
7013
|
-
}
|
|
7014
7254
|
const completedWorkflow = workflowLifecycle.complete(event.messages);
|
|
7015
|
-
if (completedWorkflow)
|
|
7255
|
+
if (!completedWorkflow) return;
|
|
7256
|
+
sandboxCommand = null;
|
|
7257
|
+
emitEvent(pi, UNIPI_EVENTS.WORKFLOW_END, completedWorkflow);
|
|
7016
7258
|
});
|
|
7017
7259
|
pi.on("session_start", async (_event, ctx) => {
|
|
7018
7260
|
initUnipiDirs();
|
|
@@ -7025,7 +7267,7 @@ If the user requires an action that requires a blocked tool, respond that you do
|
|
|
7025
7267
|
if (!ralphDetected) {
|
|
7026
7268
|
try {
|
|
7027
7269
|
const allTools = pi.getAllTools();
|
|
7028
|
-
ralphDetected = allTools.some((
|
|
7270
|
+
ralphDetected = allTools.some((tool) => tool.name === "ralph_start");
|
|
7029
7271
|
} catch {
|
|
7030
7272
|
}
|
|
7031
7273
|
}
|
|
@@ -7036,14 +7278,11 @@ If the user requires an action that requires a blocked tool, respond that you do
|
|
|
7036
7278
|
});
|
|
7037
7279
|
pi.events.on(UNIPI_EVENTS.MODULE_READY, (data) => {
|
|
7038
7280
|
const event = data;
|
|
7039
|
-
if (event?.name === MODULES.RALPH)
|
|
7040
|
-
ralphDetected = true;
|
|
7041
|
-
}
|
|
7281
|
+
if (event?.name === MODULES.RALPH) ralphDetected = true;
|
|
7042
7282
|
});
|
|
7043
7283
|
pi.on("session_shutdown", async () => {
|
|
7044
7284
|
ralphDetected = false;
|
|
7045
|
-
|
|
7046
|
-
sandboxActive = false;
|
|
7285
|
+
sandboxCommand = null;
|
|
7047
7286
|
workflowLifecycle.reset();
|
|
7048
7287
|
});
|
|
7049
7288
|
}
|
|
@@ -7365,7 +7604,7 @@ Use /unipi:ralph-resume <name> to continue`, "info");
|
|
|
7365
7604
|
// packages/ralph/tools.ts
|
|
7366
7605
|
init_core();
|
|
7367
7606
|
import { Type } from "typebox";
|
|
7368
|
-
function registerRalphTools(pi,
|
|
7607
|
+
function registerRalphTools(pi, getManager2) {
|
|
7369
7608
|
pi.registerTool({
|
|
7370
7609
|
name: RALPH_TOOLS.START,
|
|
7371
7610
|
label: "Start Ralph Loop",
|
|
@@ -7394,6 +7633,7 @@ function registerRalphTools(pi, manager2) {
|
|
|
7394
7633
|
)
|
|
7395
7634
|
}),
|
|
7396
7635
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
7636
|
+
const manager2 = getManager2(ctx);
|
|
7397
7637
|
const taskFile = `.unipi/ralph/${params.name.replace(/[^a-zA-Z0-9_-]/g, "_")}.md`;
|
|
7398
7638
|
if (manager2.loadState(params.name)?.status === "active") {
|
|
7399
7639
|
return {
|
|
@@ -7434,6 +7674,7 @@ function registerRalphTools(pi, manager2) {
|
|
|
7434
7674
|
],
|
|
7435
7675
|
parameters: Type.Object({}),
|
|
7436
7676
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
7677
|
+
const manager2 = getManager2(ctx);
|
|
7437
7678
|
if (!manager2.getCurrentLoop()) {
|
|
7438
7679
|
return {
|
|
7439
7680
|
content: [{ type: "text", text: "No active Ralph loop." }],
|
|
@@ -7491,6 +7732,38 @@ function registerRalphTools(pi, manager2) {
|
|
|
7491
7732
|
});
|
|
7492
7733
|
}
|
|
7493
7734
|
|
|
7735
|
+
// packages/ralph/reminder.ts
|
|
7736
|
+
init_core();
|
|
7737
|
+
var RALPH_REMINDER_TYPE = "unipi-ralph-loop-reminder";
|
|
7738
|
+
function buildRalphLoopReminder(state) {
|
|
7739
|
+
const iterStr = `${state.iteration}${state.maxIterations > 0 ? `/${state.maxIterations}` : ""}`;
|
|
7740
|
+
let instructions = "This snapshot supersedes all earlier Ralph loop reminders.\n";
|
|
7741
|
+
instructions += `You are in a Ralph loop working on: ${state.taskFile}
|
|
7742
|
+
`;
|
|
7743
|
+
if (state.itemsPerIteration > 0) {
|
|
7744
|
+
instructions += `- Work on ~${state.itemsPerIteration} items this iteration
|
|
7745
|
+
`;
|
|
7746
|
+
}
|
|
7747
|
+
instructions += "- Update the task file as you progress\n";
|
|
7748
|
+
instructions += `- When FULLY COMPLETE: ${RALPH_COMPLETE_MARKER}
|
|
7749
|
+
`;
|
|
7750
|
+
instructions += "- Otherwise, call ralph_done tool to proceed to next iteration";
|
|
7751
|
+
return `[RALPH LOOP - ${state.name} - Iteration ${iterStr}]
|
|
7752
|
+
|
|
7753
|
+
${instructions}`;
|
|
7754
|
+
}
|
|
7755
|
+
function latestRalphReminder(ctx) {
|
|
7756
|
+
const branch = ctx.sessionManager.getBranch();
|
|
7757
|
+
for (let index = branch.length - 1; index >= 0; index--) {
|
|
7758
|
+
const entry = branch[index];
|
|
7759
|
+
if (entry.type === "custom_message" && entry.customType === RALPH_REMINDER_TYPE) {
|
|
7760
|
+
return typeof entry.content === "string" ? entry.content : null;
|
|
7761
|
+
}
|
|
7762
|
+
if (entry.type === "compaction") break;
|
|
7763
|
+
}
|
|
7764
|
+
return null;
|
|
7765
|
+
}
|
|
7766
|
+
|
|
7494
7767
|
// packages/ralph/index.ts
|
|
7495
7768
|
function getInfoRegistry() {
|
|
7496
7769
|
return globalThis.__unipi_info_registry;
|
|
@@ -7507,6 +7780,7 @@ function getManager(ctx, pi) {
|
|
|
7507
7780
|
return manager;
|
|
7508
7781
|
}
|
|
7509
7782
|
function ralph_default(pi) {
|
|
7783
|
+
registerRalphTools(pi, (ctx) => getManager(ctx, pi));
|
|
7510
7784
|
registerCommands(pi);
|
|
7511
7785
|
pi.on("session_start", async (_event, ctx) => {
|
|
7512
7786
|
manager = null;
|
|
@@ -7587,24 +7861,12 @@ function ralph_default(pi) {
|
|
|
7587
7861
|
if (!currentLoop) return;
|
|
7588
7862
|
const state = mgr.loadState(currentLoop);
|
|
7589
7863
|
if (!state || state.status !== "active") return;
|
|
7590
|
-
const
|
|
7591
|
-
|
|
7592
|
-
`;
|
|
7593
|
-
if (state.itemsPerIteration > 0) {
|
|
7594
|
-
instructions += `- Work on ~${state.itemsPerIteration} items this iteration
|
|
7595
|
-
`;
|
|
7596
|
-
}
|
|
7597
|
-
instructions += `- Update the task file as you progress
|
|
7598
|
-
`;
|
|
7599
|
-
instructions += `- When FULLY COMPLETE: ${RALPH_COMPLETE_MARKER}
|
|
7600
|
-
`;
|
|
7601
|
-
instructions += `- Otherwise, call ralph_done tool to proceed to next iteration`;
|
|
7864
|
+
const content = buildRalphLoopReminder(state);
|
|
7865
|
+
if (latestRalphReminder(ctx) === content) return;
|
|
7602
7866
|
return {
|
|
7603
7867
|
message: {
|
|
7604
|
-
customType:
|
|
7605
|
-
content
|
|
7606
|
-
|
|
7607
|
-
${instructions}`,
|
|
7868
|
+
customType: RALPH_REMINDER_TYPE,
|
|
7869
|
+
content,
|
|
7608
7870
|
display: false
|
|
7609
7871
|
}
|
|
7610
7872
|
};
|
|
@@ -7618,10 +7880,6 @@ ${instructions}`,
|
|
|
7618
7880
|
}
|
|
7619
7881
|
manager = null;
|
|
7620
7882
|
});
|
|
7621
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
7622
|
-
const mgr = getManager(ctx, pi);
|
|
7623
|
-
registerRalphTools(pi, mgr);
|
|
7624
|
-
});
|
|
7625
7883
|
}
|
|
7626
7884
|
function registerCommands(pi) {
|
|
7627
7885
|
const HELP = `Ralph Loop \u2014 Long-running development loops
|
|
@@ -7922,7 +8180,7 @@ function handleNuke(rest, ctx, pi) {
|
|
|
7922
8180
|
// packages/memory/index.ts
|
|
7923
8181
|
init_core();
|
|
7924
8182
|
init_storage();
|
|
7925
|
-
import { dirname as
|
|
8183
|
+
import { dirname as dirname7 } from "node:path";
|
|
7926
8184
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
7927
8185
|
|
|
7928
8186
|
// packages/memory/tools.ts
|
|
@@ -8630,7 +8888,37 @@ init_settings();
|
|
|
8630
8888
|
function getInfoRegistry2() {
|
|
8631
8889
|
return globalThis.__unipi_info_registry;
|
|
8632
8890
|
}
|
|
8633
|
-
var VERSION3 = getPackageVersion(
|
|
8891
|
+
var VERSION3 = getPackageVersion(dirname7(fileURLToPath4(import.meta.url)));
|
|
8892
|
+
function buildMemoryRecallReminder(input) {
|
|
8893
|
+
const lines = [
|
|
8894
|
+
"## \u{1F9E0} Memory System Active",
|
|
8895
|
+
"",
|
|
8896
|
+
`You have ${input.memories.length} memories stored for project "${input.projectName}".`
|
|
8897
|
+
];
|
|
8898
|
+
if (input.canSearch && input.memories.length > 0) {
|
|
8899
|
+
const titleList = input.memories.slice(0, 20).map((memory) => `- ${memory.title}`).join("\n");
|
|
8900
|
+
const extra = input.memories.length > 20 ? `
|
|
8901
|
+
... and ${input.memories.length - 20} more` : "";
|
|
8902
|
+
lines.push(
|
|
8903
|
+
"**BEFORE starting work**, call `memory_search` with relevant keywords to check for existing context.",
|
|
8904
|
+
"",
|
|
8905
|
+
"Available memories:",
|
|
8906
|
+
titleList + extra
|
|
8907
|
+
);
|
|
8908
|
+
}
|
|
8909
|
+
if (input.canStore) {
|
|
8910
|
+
lines.push(
|
|
8911
|
+
"",
|
|
8912
|
+
"**AFTER completing the task**, if you learned something non-obvious,",
|
|
8913
|
+
"call `memory_store` to save it for future sessions."
|
|
8914
|
+
);
|
|
8915
|
+
}
|
|
8916
|
+
lines.push(
|
|
8917
|
+
"",
|
|
8918
|
+
"Guardrails: read max 10 memory results per search. Update existing memories instead of creating duplicates."
|
|
8919
|
+
);
|
|
8920
|
+
return lines.join("\n");
|
|
8921
|
+
}
|
|
8634
8922
|
var projectStorage = null;
|
|
8635
8923
|
var orphanSyncPending = false;
|
|
8636
8924
|
function ensureOrphanSync(storage) {
|
|
@@ -8784,41 +9072,17 @@ function memory_default(pi) {
|
|
|
8784
9072
|
recallDone = true;
|
|
8785
9073
|
return;
|
|
8786
9074
|
}
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
"",
|
|
8790
|
-
`You have ${projectMemories.length} memories stored for project "${projectName}".`
|
|
8791
|
-
];
|
|
8792
|
-
if (canSearch && projectMemories.length > 0) {
|
|
8793
|
-
const titleList = projectMemories.slice(0, 20).map((m) => `- ${m.title}`).join("\n");
|
|
8794
|
-
const extra = projectMemories.length > 20 ? `
|
|
8795
|
-
... and ${projectMemories.length - 20} more` : "";
|
|
8796
|
-
lines.push(
|
|
8797
|
-
"**BEFORE starting work**, call `memory_search` with relevant keywords to check for existing context.",
|
|
8798
|
-
"",
|
|
8799
|
-
"Available memories:",
|
|
8800
|
-
titleList + extra
|
|
8801
|
-
);
|
|
8802
|
-
} else {
|
|
8803
|
-
recallDone = true;
|
|
8804
|
-
}
|
|
8805
|
-
if (canStore) {
|
|
8806
|
-
lines.push(
|
|
8807
|
-
"",
|
|
8808
|
-
"**AFTER completing the task**, if you learned something non-obvious,",
|
|
8809
|
-
"call `memory_store` to save it for future sessions."
|
|
8810
|
-
);
|
|
8811
|
-
} else {
|
|
8812
|
-
storeDone = true;
|
|
8813
|
-
}
|
|
8814
|
-
lines.push(
|
|
8815
|
-
"",
|
|
8816
|
-
"Guardrails: read max 10 memory results per search. Update existing memories instead of creating duplicates."
|
|
8817
|
-
);
|
|
9075
|
+
if (!canSearch || projectMemories.length === 0) recallDone = true;
|
|
9076
|
+
if (!canStore) storeDone = true;
|
|
8818
9077
|
return {
|
|
8819
9078
|
message: {
|
|
8820
9079
|
customType: "unipi-memory-recall-reminder",
|
|
8821
|
-
content:
|
|
9080
|
+
content: buildMemoryRecallReminder({
|
|
9081
|
+
projectName,
|
|
9082
|
+
memories: projectMemories,
|
|
9083
|
+
canSearch,
|
|
9084
|
+
canStore
|
|
9085
|
+
}),
|
|
8822
9086
|
display: false
|
|
8823
9087
|
}
|
|
8824
9088
|
};
|
|
@@ -8852,13 +9116,13 @@ function memory_default(pi) {
|
|
|
8852
9116
|
|
|
8853
9117
|
// packages/info-screen/index.ts
|
|
8854
9118
|
init_core();
|
|
8855
|
-
import { dirname as
|
|
9119
|
+
import { dirname as dirname10 } from "node:path";
|
|
8856
9120
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
8857
9121
|
|
|
8858
9122
|
// packages/info-screen/config.ts
|
|
8859
|
-
import { readFileSync as readFileSync7, writeFileSync as
|
|
8860
|
-
import { join as
|
|
8861
|
-
import { homedir as
|
|
9123
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync8, existsSync as existsSync8, mkdirSync as mkdirSync7 } from "node:fs";
|
|
9124
|
+
import { join as join9, dirname as dirname8 } from "node:path";
|
|
9125
|
+
import { homedir as homedir5 } from "node:os";
|
|
8862
9126
|
|
|
8863
9127
|
// packages/info-screen/types.ts
|
|
8864
9128
|
var BOOT_MODES = ["on", "auto-close", "off"];
|
|
@@ -8870,7 +9134,7 @@ var DEFAULT_SETTINGS = {
|
|
|
8870
9134
|
};
|
|
8871
9135
|
|
|
8872
9136
|
// packages/info-screen/config.ts
|
|
8873
|
-
var SETTINGS_PATH =
|
|
9137
|
+
var SETTINGS_PATH = join9(homedir5(), ".pi", "agent", "settings.json");
|
|
8874
9138
|
var SETTINGS_KEY = "unipi";
|
|
8875
9139
|
var cachedSettings = null;
|
|
8876
9140
|
function isRecord(value) {
|
|
@@ -8886,11 +9150,11 @@ function readSettingsFile() {
|
|
|
8886
9150
|
}
|
|
8887
9151
|
}
|
|
8888
9152
|
function writeSettingsFile(data) {
|
|
8889
|
-
const dir =
|
|
9153
|
+
const dir = dirname8(SETTINGS_PATH);
|
|
8890
9154
|
if (!existsSync8(dir)) {
|
|
8891
|
-
|
|
9155
|
+
mkdirSync7(dir, { recursive: true });
|
|
8892
9156
|
}
|
|
8893
|
-
|
|
9157
|
+
writeFileSync8(SETTINGS_PATH, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
8894
9158
|
}
|
|
8895
9159
|
function getInfoSettings() {
|
|
8896
9160
|
if (cachedSettings) return cachedSettings;
|
|
@@ -9187,17 +9451,17 @@ if (!globalThis.__unipi_info_registry) {
|
|
|
9187
9451
|
|
|
9188
9452
|
// packages/info-screen/core-groups.ts
|
|
9189
9453
|
init_core();
|
|
9190
|
-
import { readFileSync as readFileSync9, readdirSync as
|
|
9191
|
-
import { join as
|
|
9192
|
-
import { homedir as
|
|
9454
|
+
import { readFileSync as readFileSync9, readdirSync as readdirSync6, existsSync as existsSync10 } from "node:fs";
|
|
9455
|
+
import { join as join11 } from "node:path";
|
|
9456
|
+
import { homedir as homedir7 } from "node:os";
|
|
9193
9457
|
init_usage_parser();
|
|
9194
9458
|
function discoverExtensions() {
|
|
9195
9459
|
const extensions = [];
|
|
9196
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE ||
|
|
9460
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || homedir7();
|
|
9197
9461
|
const cwd = process.cwd();
|
|
9198
9462
|
const settingsPaths = [
|
|
9199
|
-
|
|
9200
|
-
|
|
9463
|
+
join11(homeDir, ".pi", "agent", "settings.json"),
|
|
9464
|
+
join11(cwd, ".pi", "settings.json")
|
|
9201
9465
|
];
|
|
9202
9466
|
const counted = /* @__PURE__ */ new Set();
|
|
9203
9467
|
for (const settingsPath of settingsPaths) {
|
|
@@ -9242,13 +9506,13 @@ function discoverExtensions() {
|
|
|
9242
9506
|
}
|
|
9243
9507
|
}
|
|
9244
9508
|
const extensionDirs = [
|
|
9245
|
-
|
|
9246
|
-
|
|
9509
|
+
join11(homeDir, ".pi", "agent", "extensions"),
|
|
9510
|
+
join11(cwd, ".pi", "extensions")
|
|
9247
9511
|
];
|
|
9248
9512
|
for (const dir of extensionDirs) {
|
|
9249
9513
|
if (!existsSync10(dir)) continue;
|
|
9250
9514
|
try {
|
|
9251
|
-
const entries =
|
|
9515
|
+
const entries = readdirSync6(dir, { withFileTypes: true });
|
|
9252
9516
|
for (const entry of entries) {
|
|
9253
9517
|
const name = entry.name;
|
|
9254
9518
|
if (counted.has(name)) continue;
|
|
@@ -9310,11 +9574,11 @@ function getTotalLoadTime() {
|
|
|
9310
9574
|
var extraSkillDirs = [];
|
|
9311
9575
|
function discoverSkills() {
|
|
9312
9576
|
const skills = [];
|
|
9313
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE ||
|
|
9577
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || homedir7();
|
|
9314
9578
|
const cwd = process.cwd();
|
|
9315
9579
|
const skillDirs = [
|
|
9316
|
-
|
|
9317
|
-
|
|
9580
|
+
join11(homeDir, ".pi", "agent", "skills"),
|
|
9581
|
+
join11(cwd, ".pi", "skills"),
|
|
9318
9582
|
// Add extra dirs from extensions
|
|
9319
9583
|
...extraSkillDirs
|
|
9320
9584
|
];
|
|
@@ -9322,18 +9586,18 @@ function discoverSkills() {
|
|
|
9322
9586
|
for (const dir of skillDirs) {
|
|
9323
9587
|
if (!existsSync10(dir)) continue;
|
|
9324
9588
|
try {
|
|
9325
|
-
const entries =
|
|
9589
|
+
const entries = readdirSync6(dir, { withFileTypes: true });
|
|
9326
9590
|
for (const entry of entries) {
|
|
9327
9591
|
if (!entry.isDirectory()) continue;
|
|
9328
9592
|
const name = entry.name;
|
|
9329
9593
|
if (counted.has(name)) continue;
|
|
9330
|
-
const skillPath =
|
|
9594
|
+
const skillPath = join11(dir, name, "SKILL.md");
|
|
9331
9595
|
if (existsSync10(skillPath)) {
|
|
9332
9596
|
counted.add(name);
|
|
9333
9597
|
let source = "extension";
|
|
9334
|
-
if (dir.includes(
|
|
9598
|
+
if (dir.includes(join11(homeDir, ".pi"))) {
|
|
9335
9599
|
source = "global";
|
|
9336
|
-
} else if (dir ===
|
|
9600
|
+
} else if (dir === join11(cwd, ".pi", "skills")) {
|
|
9337
9601
|
source = "project";
|
|
9338
9602
|
}
|
|
9339
9603
|
skills.push({ name, source });
|
|
@@ -9384,7 +9648,7 @@ function registerCoreGroups() {
|
|
|
9384
9648
|
},
|
|
9385
9649
|
dataProvider: async () => {
|
|
9386
9650
|
const cwd = process.cwd();
|
|
9387
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE ||
|
|
9651
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || homedir7();
|
|
9388
9652
|
const shortCwd = cwd.startsWith(homeDir) ? `~${cwd.slice(homeDir.length)}` : cwd;
|
|
9389
9653
|
const announced = getAnnouncedModules();
|
|
9390
9654
|
const registeredGroups = infoRegistry.getAllGroups();
|
|
@@ -10433,7 +10697,7 @@ var SettingsOverlay = class _SettingsOverlay {
|
|
|
10433
10697
|
};
|
|
10434
10698
|
|
|
10435
10699
|
// packages/info-screen/index.ts
|
|
10436
|
-
var VERSION4 = getPackageVersion(
|
|
10700
|
+
var VERSION4 = getPackageVersion(dirname10(fileURLToPath5(import.meta.url)));
|
|
10437
10701
|
function info_screen_default(pi) {
|
|
10438
10702
|
setPiApi(pi);
|
|
10439
10703
|
registerCoreGroups();
|
|
@@ -10576,11 +10840,15 @@ function info_screen_default(pi) {
|
|
|
10576
10840
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
10577
10841
|
import { Text } from "@earendil-works/pi-tui";
|
|
10578
10842
|
import { Type as Type3 } from "typebox";
|
|
10579
|
-
import { existsSync as existsSync13, readdirSync as
|
|
10580
|
-
import { join as
|
|
10581
|
-
import { homedir as
|
|
10843
|
+
import { existsSync as existsSync13, readdirSync as readdirSync8 } from "node:fs";
|
|
10844
|
+
import { join as join15 } from "node:path";
|
|
10845
|
+
import { homedir as homedir11 } from "node:os";
|
|
10582
10846
|
|
|
10583
10847
|
// packages/subagents/src/core-compat.ts
|
|
10848
|
+
import { chmodSync as chmodSync2, lstatSync as lstatSync2, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10 } from "node:fs";
|
|
10849
|
+
import { homedir as homedir8 } from "node:os";
|
|
10850
|
+
import { join as join12 } from "node:path";
|
|
10851
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
10584
10852
|
var MODULES2 = { SUBAGENTS: "subagents" };
|
|
10585
10853
|
var UNIPI_EVENTS2 = {
|
|
10586
10854
|
MODULE_READY: "unipi:module:ready",
|
|
@@ -10594,6 +10862,61 @@ function emitEvent2(pi, eventName, payload) {
|
|
|
10594
10862
|
return false;
|
|
10595
10863
|
}
|
|
10596
10864
|
}
|
|
10865
|
+
var MAX_RAW_HELPER_ARTIFACT_BYTES = 16 * 1024 * 1024;
|
|
10866
|
+
function boundHelperOutput(text, maxBytes = 64 * 1024, existingArtifactPath) {
|
|
10867
|
+
const originalBytes = Buffer.byteLength(text, "utf8");
|
|
10868
|
+
if (originalBytes <= maxBytes) return { text, truncated: false, originalBytes };
|
|
10869
|
+
let artifactPath = existingArtifactPath;
|
|
10870
|
+
let artifactWarning;
|
|
10871
|
+
if (!artifactPath && originalBytes <= MAX_RAW_HELPER_ARTIFACT_BYTES) {
|
|
10872
|
+
try {
|
|
10873
|
+
const dir = join12(homedir8(), ".unipi", "tool-results");
|
|
10874
|
+
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
10875
|
+
let stat = lstatSync2(dir);
|
|
10876
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
10877
|
+
throw new Error(`Refusing unsafe tool-result directory: ${dir}`);
|
|
10878
|
+
}
|
|
10879
|
+
if ((stat.mode & 63) !== 0) {
|
|
10880
|
+
chmodSync2(dir, 448);
|
|
10881
|
+
stat = lstatSync2(dir);
|
|
10882
|
+
if ((stat.mode & 63) !== 0) throw new Error(`Refusing non-private tool-result directory: ${dir}`);
|
|
10883
|
+
}
|
|
10884
|
+
artifactPath = join12(dir, `helper-${randomUUID2()}.txt`);
|
|
10885
|
+
writeFileSync10(artifactPath, text, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
10886
|
+
} catch (error) {
|
|
10887
|
+
artifactWarning = `Full-output artifact unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
10888
|
+
artifactPath = void 0;
|
|
10889
|
+
}
|
|
10890
|
+
} else if (!artifactPath) {
|
|
10891
|
+
artifactWarning = `Full output exceeded the ${MAX_RAW_HELPER_ARTIFACT_BYTES}-byte local artifact safety cap and was not retained.`;
|
|
10892
|
+
}
|
|
10893
|
+
const marker = [
|
|
10894
|
+
"",
|
|
10895
|
+
"--- output bounded by UniPi ---",
|
|
10896
|
+
artifactPath ? `Full output: ${artifactPath}` : artifactWarning,
|
|
10897
|
+
`Original size: ${originalBytes} bytes; model-visible ceiling: ${maxBytes} bytes.`,
|
|
10898
|
+
...artifactPath ? ["Use the read tool with offset/limit to inspect only the needed region."] : []
|
|
10899
|
+
].join("\n");
|
|
10900
|
+
const markerBytes = Buffer.byteLength(marker, "utf8");
|
|
10901
|
+
const contentBudget = Math.max(1, maxBytes - markerBytes - 80);
|
|
10902
|
+
const bytes = Buffer.from(text, "utf8");
|
|
10903
|
+
const headBytes = Math.ceil(contentBudget * 0.75);
|
|
10904
|
+
const tailBytes = Math.max(0, contentBudget - headBytes);
|
|
10905
|
+
const head = bytes.subarray(0, headBytes).toString("utf8").replace(/\uFFFD+$/u, "");
|
|
10906
|
+
const tail2 = bytes.subarray(originalBytes - tailBytes).toString("utf8").replace(/^\uFFFD+/u, "");
|
|
10907
|
+
let bounded = `${head}
|
|
10908
|
+
\u2026 ${Math.max(0, originalBytes - contentBudget)} bytes omitted \u2026
|
|
10909
|
+
${tail2}${marker}`;
|
|
10910
|
+
if (Buffer.byteLength(bounded, "utf8") > maxBytes) {
|
|
10911
|
+
bounded = Buffer.from(bounded, "utf8").subarray(0, maxBytes).toString("utf8").replace(/\uFFFD+$/u, "");
|
|
10912
|
+
}
|
|
10913
|
+
return {
|
|
10914
|
+
text: bounded,
|
|
10915
|
+
truncated: true,
|
|
10916
|
+
originalBytes,
|
|
10917
|
+
artifactPath
|
|
10918
|
+
};
|
|
10919
|
+
}
|
|
10597
10920
|
async function withHerdrBlocked2(pi, label, fn) {
|
|
10598
10921
|
emitEvent2(pi, "herdr:blocked", { active: true, label });
|
|
10599
10922
|
try {
|
|
@@ -10604,7 +10927,7 @@ async function withHerdrBlocked2(pi, label, fn) {
|
|
|
10604
10927
|
}
|
|
10605
10928
|
|
|
10606
10929
|
// packages/subagents/src/agent-manager.ts
|
|
10607
|
-
import { randomUUID } from "node:crypto";
|
|
10930
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
10608
10931
|
|
|
10609
10932
|
// packages/subagents/src/agent-runner.ts
|
|
10610
10933
|
import {
|
|
@@ -10657,7 +10980,7 @@ var BUILTIN_CONFIGS = {
|
|
|
10657
10980
|
};
|
|
10658
10981
|
|
|
10659
10982
|
// packages/subagents/src/agent-runner.ts
|
|
10660
|
-
var EXCLUDED_TOOL_NAMES = ["Agent", "get_result"];
|
|
10983
|
+
var EXCLUDED_TOOL_NAMES = ["spawn_helper", "get_helper_result", "Agent", "get_result"];
|
|
10661
10984
|
var BUILTIN_TOOL_NAMES = ["read", "bash", "edit", "write", "grep", "find", "ls"];
|
|
10662
10985
|
var defaultMaxTurns;
|
|
10663
10986
|
var graceTurns = 5;
|
|
@@ -10815,22 +11138,25 @@ ${agentPrompt}`;
|
|
|
10815
11138
|
init_model_resolver();
|
|
10816
11139
|
|
|
10817
11140
|
// packages/subagents/src/custom-agents.ts
|
|
10818
|
-
import { existsSync as existsSync11, readdirSync as
|
|
10819
|
-
import { join as
|
|
10820
|
-
import { homedir as
|
|
11141
|
+
import { existsSync as existsSync11, readdirSync as readdirSync7, readFileSync as readFileSync10, renameSync as renameSync4 } from "node:fs";
|
|
11142
|
+
import { join as join13 } from "node:path";
|
|
11143
|
+
import { homedir as homedir9 } from "node:os";
|
|
10821
11144
|
import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
11145
|
+
function compareCodeUnits(a, b) {
|
|
11146
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
11147
|
+
}
|
|
10822
11148
|
function backupCorrupted(filePath) {
|
|
10823
11149
|
const backupPath = filePath + ".bak";
|
|
10824
11150
|
try {
|
|
10825
|
-
|
|
11151
|
+
renameSync4(filePath, backupPath);
|
|
10826
11152
|
} catch {
|
|
10827
11153
|
}
|
|
10828
11154
|
}
|
|
10829
11155
|
function getProjectAgentsDir(cwd) {
|
|
10830
|
-
return
|
|
11156
|
+
return join13(cwd, ".unipi", "config", "agents");
|
|
10831
11157
|
}
|
|
10832
11158
|
function getGlobalAgentsDir() {
|
|
10833
|
-
return
|
|
11159
|
+
return join13(homedir9(), ".unipi", "config", "agents");
|
|
10834
11160
|
}
|
|
10835
11161
|
var BUILTIN_TOOL_NAMES2 = ["read", "bash", "edit", "write", "grep", "find", "ls"];
|
|
10836
11162
|
function loadAgentFromFile(filePath, source) {
|
|
@@ -10871,9 +11197,9 @@ function loadCustomAgents(cwd) {
|
|
|
10871
11197
|
const agents = /* @__PURE__ */ new Map();
|
|
10872
11198
|
const globalDir = getGlobalAgentsDir();
|
|
10873
11199
|
if (existsSync11(globalDir)) {
|
|
10874
|
-
const files =
|
|
11200
|
+
const files = readdirSync7(globalDir).filter((f) => f.endsWith(".md")).sort(compareCodeUnits);
|
|
10875
11201
|
for (const file of files) {
|
|
10876
|
-
const agent = loadAgentFromFile(
|
|
11202
|
+
const agent = loadAgentFromFile(join13(globalDir, file), "global");
|
|
10877
11203
|
if (agent) {
|
|
10878
11204
|
agents.set(agent.name, agent);
|
|
10879
11205
|
}
|
|
@@ -10881,9 +11207,9 @@ function loadCustomAgents(cwd) {
|
|
|
10881
11207
|
}
|
|
10882
11208
|
const projectDir = getProjectAgentsDir(cwd);
|
|
10883
11209
|
if (existsSync11(projectDir)) {
|
|
10884
|
-
const files =
|
|
11210
|
+
const files = readdirSync7(projectDir).filter((f) => f.endsWith(".md")).sort(compareCodeUnits);
|
|
10885
11211
|
for (const file of files) {
|
|
10886
|
-
const agent = loadAgentFromFile(
|
|
11212
|
+
const agent = loadAgentFromFile(join13(projectDir, file), "project");
|
|
10887
11213
|
if (agent) {
|
|
10888
11214
|
agents.set(agent.name, agent);
|
|
10889
11215
|
}
|
|
@@ -10908,17 +11234,17 @@ var FileLock = class {
|
|
|
10908
11234
|
*/
|
|
10909
11235
|
async acquire(filePath, agentId) {
|
|
10910
11236
|
while (this.locks.has(filePath)) {
|
|
10911
|
-
await new Promise((
|
|
11237
|
+
await new Promise((resolve14) => {
|
|
10912
11238
|
const queue = this.queues.get(filePath) ?? [];
|
|
10913
|
-
queue.push(
|
|
11239
|
+
queue.push(resolve14);
|
|
10914
11240
|
this.queues.set(filePath, queue);
|
|
10915
11241
|
});
|
|
10916
11242
|
}
|
|
10917
11243
|
let releaseFn;
|
|
10918
|
-
const promise = new Promise((
|
|
11244
|
+
const promise = new Promise((resolve14) => {
|
|
10919
11245
|
releaseFn = () => {
|
|
10920
11246
|
this.locks.delete(filePath);
|
|
10921
|
-
|
|
11247
|
+
resolve14();
|
|
10922
11248
|
const queue = this.queues.get(filePath);
|
|
10923
11249
|
if (queue && queue.length > 0) {
|
|
10924
11250
|
const next = queue.shift();
|
|
@@ -10976,6 +11302,9 @@ var FileLock = class {
|
|
|
10976
11302
|
};
|
|
10977
11303
|
|
|
10978
11304
|
// packages/subagents/src/agent-manager.ts
|
|
11305
|
+
function compareCodeUnits2(a, b) {
|
|
11306
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
11307
|
+
}
|
|
10979
11308
|
var DEFAULT_MAX_CONCURRENT = 4;
|
|
10980
11309
|
var AgentManager = class {
|
|
10981
11310
|
agents = /* @__PURE__ */ new Map();
|
|
@@ -11009,7 +11338,7 @@ var AgentManager = class {
|
|
|
11009
11338
|
...Object.keys(BUILTIN_CONFIGS).filter((type) => type !== "name-gen"),
|
|
11010
11339
|
...this.customAgents.keys(),
|
|
11011
11340
|
...Object.keys(this.typeSettings)
|
|
11012
|
-
])];
|
|
11341
|
+
])].sort(compareCodeUnits2);
|
|
11013
11342
|
}
|
|
11014
11343
|
/** A type is enabled only when both JSON config and agent frontmatter allow it. */
|
|
11015
11344
|
isTypeEnabled(type) {
|
|
@@ -11033,7 +11362,7 @@ var AgentManager = class {
|
|
|
11033
11362
|
*/
|
|
11034
11363
|
spawn(pi, ctx, type, prompt, options) {
|
|
11035
11364
|
this.assertTypeEnabled(type);
|
|
11036
|
-
const id =
|
|
11365
|
+
const id = randomUUID3().slice(0, 17);
|
|
11037
11366
|
const abortController = new AbortController();
|
|
11038
11367
|
const record = {
|
|
11039
11368
|
id,
|
|
@@ -11236,9 +11565,9 @@ var AgentManager = class {
|
|
|
11236
11565
|
};
|
|
11237
11566
|
|
|
11238
11567
|
// packages/subagents/src/config.ts
|
|
11239
|
-
import { existsSync as existsSync12, mkdirSync as
|
|
11240
|
-
import { join as
|
|
11241
|
-
import { homedir as
|
|
11568
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync10, readFileSync as readFileSync11, renameSync as renameSync5, writeFileSync as writeFileSync11 } from "node:fs";
|
|
11569
|
+
import { join as join14 } from "node:path";
|
|
11570
|
+
import { homedir as homedir10 } from "node:os";
|
|
11242
11571
|
var DEFAULT_CONFIG2 = {
|
|
11243
11572
|
maxConcurrent: 4,
|
|
11244
11573
|
enabled: true,
|
|
@@ -11248,20 +11577,20 @@ var DEFAULT_CONFIG2 = {
|
|
|
11248
11577
|
}
|
|
11249
11578
|
};
|
|
11250
11579
|
function getGlobalConfigPath() {
|
|
11251
|
-
return
|
|
11580
|
+
return join14(homedir10(), ".unipi", "config", "subagents.json");
|
|
11252
11581
|
}
|
|
11253
11582
|
function getWorkspaceConfigPath(cwd) {
|
|
11254
|
-
return
|
|
11583
|
+
return join14(cwd, ".unipi", "config", "subagents.json");
|
|
11255
11584
|
}
|
|
11256
11585
|
function ensureDirExists(dirPath) {
|
|
11257
11586
|
if (!existsSync12(dirPath)) {
|
|
11258
|
-
|
|
11587
|
+
mkdirSync10(dirPath, { recursive: true });
|
|
11259
11588
|
}
|
|
11260
11589
|
}
|
|
11261
11590
|
function writeConfigAtomic(filePath, config) {
|
|
11262
11591
|
const tmpPath = filePath + ".tmp";
|
|
11263
|
-
|
|
11264
|
-
|
|
11592
|
+
writeFileSync11(tmpPath, JSON.stringify(config, null, 2), "utf-8");
|
|
11593
|
+
renameSync5(tmpPath, filePath);
|
|
11265
11594
|
}
|
|
11266
11595
|
function loadConfigFromPath(filePath) {
|
|
11267
11596
|
if (!existsSync12(filePath)) return null;
|
|
@@ -11277,7 +11606,7 @@ function loadConfigFromPath(filePath) {
|
|
|
11277
11606
|
function repairCorrupted(filePath) {
|
|
11278
11607
|
const backupPath = filePath + ".bak";
|
|
11279
11608
|
try {
|
|
11280
|
-
|
|
11609
|
+
renameSync5(filePath, backupPath);
|
|
11281
11610
|
} catch {
|
|
11282
11611
|
}
|
|
11283
11612
|
writeConfigAtomic(filePath, DEFAULT_CONFIG2);
|
|
@@ -11285,17 +11614,17 @@ function repairCorrupted(filePath) {
|
|
|
11285
11614
|
}
|
|
11286
11615
|
function initConfig(cwd) {
|
|
11287
11616
|
const globalPath = getGlobalConfigPath();
|
|
11288
|
-
const globalDir =
|
|
11289
|
-
const globalAgentsDir =
|
|
11617
|
+
const globalDir = join14(homedir10(), ".unipi", "config");
|
|
11618
|
+
const globalAgentsDir = join14(homedir10(), ".unipi", "config", "agents");
|
|
11290
11619
|
ensureDirExists(globalDir);
|
|
11291
11620
|
ensureDirExists(globalAgentsDir);
|
|
11292
11621
|
let globalConfig = loadConfigFromPath(globalPath);
|
|
11293
11622
|
if (globalConfig === null) {
|
|
11294
11623
|
globalConfig = repairCorrupted(globalPath);
|
|
11295
11624
|
}
|
|
11296
|
-
const workspaceDir =
|
|
11297
|
-
const workspaceAgentsDir =
|
|
11298
|
-
if (cwd && !cwd.startsWith(
|
|
11625
|
+
const workspaceDir = join14(cwd, ".unipi", "config");
|
|
11626
|
+
const workspaceAgentsDir = join14(cwd, ".unipi", "config", "agents");
|
|
11627
|
+
if (cwd && !cwd.startsWith(homedir10())) {
|
|
11299
11628
|
ensureDirExists(workspaceDir);
|
|
11300
11629
|
ensureDirExists(workspaceAgentsDir);
|
|
11301
11630
|
}
|
|
@@ -11959,10 +12288,10 @@ function getStatusLabel(status2, error) {
|
|
|
11959
12288
|
function src_default(pi) {
|
|
11960
12289
|
const config = initConfig(process.cwd());
|
|
11961
12290
|
if (!config.enabled) return;
|
|
11962
|
-
const homeDir =
|
|
12291
|
+
const homeDir = homedir11();
|
|
11963
12292
|
const cwd = process.cwd();
|
|
11964
|
-
const globalAgentsDir =
|
|
11965
|
-
const workspaceAgentsDir =
|
|
12293
|
+
const globalAgentsDir = join15(homeDir, ".unipi", "config", "agents");
|
|
12294
|
+
const workspaceAgentsDir = join15(cwd, ".unipi", "config", "agents");
|
|
11966
12295
|
const agentActivity = /* @__PURE__ */ new Map();
|
|
11967
12296
|
let sessionEnded = false;
|
|
11968
12297
|
const manager2 = new AgentManager(
|
|
@@ -12096,7 +12425,7 @@ function src_default(pi) {
|
|
|
12096
12425
|
for (const dir of [globalAgentsDir, workspaceAgentsDir]) {
|
|
12097
12426
|
try {
|
|
12098
12427
|
if (existsSync13(dir)) {
|
|
12099
|
-
for (const file of
|
|
12428
|
+
for (const file of readdirSync8(dir)) {
|
|
12100
12429
|
if (file.endsWith(".md") && !customTypes.includes(file.replace(".md", ""))) {
|
|
12101
12430
|
customTypes.push(file.replace(".md", ""));
|
|
12102
12431
|
}
|
|
@@ -12442,17 +12771,22 @@ Use get_result to retrieve full results.`,
|
|
|
12442
12771
|
error: record.error
|
|
12443
12772
|
});
|
|
12444
12773
|
}
|
|
12774
|
+
const output = boundHelperOutput(record.result?.trim() || "No output.");
|
|
12775
|
+
record.resultArtifactPath = output.artifactPath;
|
|
12445
12776
|
return textResult(
|
|
12446
12777
|
`Agent completed in ${(durationMs / 1e3).toFixed(1)}s (${record.toolUses} tool uses${tokenText ? `, ${tokenText} tokens` : ""}).
|
|
12447
12778
|
|
|
12448
|
-
` +
|
|
12779
|
+
` + output.text,
|
|
12449
12780
|
{
|
|
12450
12781
|
status: "completed",
|
|
12451
12782
|
toolUses: record.toolUses,
|
|
12452
12783
|
tokens: tokenText,
|
|
12453
12784
|
durationMs,
|
|
12454
12785
|
turnCount: fgState.turnCount,
|
|
12455
|
-
maxTurns: fgState.maxTurns
|
|
12786
|
+
maxTurns: fgState.maxTurns,
|
|
12787
|
+
truncated: output.truncated,
|
|
12788
|
+
originalBytes: output.originalBytes,
|
|
12789
|
+
artifactPath: output.artifactPath
|
|
12456
12790
|
}
|
|
12457
12791
|
);
|
|
12458
12792
|
}
|
|
@@ -12528,7 +12862,13 @@ Description: ${record.description}
|
|
|
12528
12862
|
} else if (record.status === "error") {
|
|
12529
12863
|
output += `Error: ${record.error}`;
|
|
12530
12864
|
} else {
|
|
12531
|
-
|
|
12865
|
+
const bounded = boundHelperOutput(
|
|
12866
|
+
record.result?.trim() || "No output.",
|
|
12867
|
+
64 * 1024,
|
|
12868
|
+
record.resultArtifactPath
|
|
12869
|
+
);
|
|
12870
|
+
record.resultArtifactPath = bounded.artifactPath;
|
|
12871
|
+
output += bounded.text;
|
|
12532
12872
|
}
|
|
12533
12873
|
if (record.status !== "running" && record.status !== "queued") {
|
|
12534
12874
|
record.resultConsumed = true;
|
|
@@ -12542,7 +12882,7 @@ Description: ${record.description}
|
|
|
12542
12882
|
// packages/btw/extensions/btw.ts
|
|
12543
12883
|
init_core();
|
|
12544
12884
|
import {
|
|
12545
|
-
buildSessionContext,
|
|
12885
|
+
buildSessionContext as buildSessionContext2,
|
|
12546
12886
|
createAgentSession as createAgentSession2,
|
|
12547
12887
|
createExtensionRuntime,
|
|
12548
12888
|
SessionManager as SessionManager2
|
|
@@ -12628,7 +12968,7 @@ function buildBtwSeedState(ctx, thread, mode) {
|
|
|
12628
12968
|
if (mode === "contextual") {
|
|
12629
12969
|
try {
|
|
12630
12970
|
messages.push(
|
|
12631
|
-
...
|
|
12971
|
+
...buildSessionContext2(ctx.sessionManager.getEntries(), ctx.sessionManager.getLeafId()).messages.filter(
|
|
12632
12972
|
(message) => !isVisibleBtwMessage(message)
|
|
12633
12973
|
)
|
|
12634
12974
|
);
|
|
@@ -14031,7 +14371,7 @@ ${summary}`;
|
|
|
14031
14371
|
|
|
14032
14372
|
// packages/web-api/src/index.ts
|
|
14033
14373
|
init_core();
|
|
14034
|
-
import { dirname as
|
|
14374
|
+
import { dirname as dirname11 } from "node:path";
|
|
14035
14375
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
14036
14376
|
|
|
14037
14377
|
// packages/web-api/src/tools.ts
|
|
@@ -16701,7 +17041,7 @@ var llmSummarizeProvider = {
|
|
|
16701
17041
|
registry.register(llmSummarizeProvider);
|
|
16702
17042
|
|
|
16703
17043
|
// packages/web-api/src/index.ts
|
|
16704
|
-
var VERSION5 = getPackageVersion(
|
|
17044
|
+
var VERSION5 = getPackageVersion(dirname11(fileURLToPath6(import.meta.url)));
|
|
16705
17045
|
function getInfoRegistry4() {
|
|
16706
17046
|
return globalThis.__unipi_info_registry;
|
|
16707
17047
|
}
|
|
@@ -16773,16 +17113,16 @@ function src_default2(pi) {
|
|
|
16773
17113
|
|
|
16774
17114
|
// packages/utility/src/index.ts
|
|
16775
17115
|
init_core();
|
|
16776
|
-
import { dirname as
|
|
17116
|
+
import { dirname as dirname14 } from "node:path";
|
|
16777
17117
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
16778
17118
|
|
|
16779
17119
|
// packages/utility/src/commands.ts
|
|
16780
17120
|
init_core();
|
|
16781
17121
|
|
|
16782
17122
|
// packages/utility/src/lifecycle/cleanup.ts
|
|
16783
|
-
import { existsSync as existsSync16, statSync as
|
|
16784
|
-
import { join as
|
|
16785
|
-
import { homedir as
|
|
17123
|
+
import { existsSync as existsSync16, statSync as statSync8, readdirSync as readdirSync10, unlinkSync as unlinkSync5, rmdirSync } from "node:fs";
|
|
17124
|
+
import { join as join18 } from "node:path";
|
|
17125
|
+
import { homedir as homedir14, tmpdir } from "node:os";
|
|
16786
17126
|
var DEFAULTS = {
|
|
16787
17127
|
dbMaxAgeDays: 14,
|
|
16788
17128
|
tempMaxAgeDays: 7,
|
|
@@ -16791,13 +17131,13 @@ var DEFAULTS = {
|
|
|
16791
17131
|
};
|
|
16792
17132
|
function expandHome(path28) {
|
|
16793
17133
|
if (path28.startsWith("~/")) {
|
|
16794
|
-
return
|
|
17134
|
+
return join18(homedir14(), path28.slice(2));
|
|
16795
17135
|
}
|
|
16796
17136
|
return path28;
|
|
16797
17137
|
}
|
|
16798
17138
|
function isStale(path28, maxAgeDays) {
|
|
16799
17139
|
try {
|
|
16800
|
-
const stats =
|
|
17140
|
+
const stats = statSync8(path28);
|
|
16801
17141
|
const ageMs = Date.now() - stats.mtime.getTime();
|
|
16802
17142
|
return ageMs > maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
16803
17143
|
} catch {
|
|
@@ -16809,7 +17149,7 @@ function hasWalLock(dbPath) {
|
|
|
16809
17149
|
const shmPath = dbPath + "-shm";
|
|
16810
17150
|
if (existsSync16(walPath)) {
|
|
16811
17151
|
try {
|
|
16812
|
-
const walStats =
|
|
17152
|
+
const walStats = statSync8(walPath);
|
|
16813
17153
|
const ageMs = Date.now() - walStats.mtime.getTime();
|
|
16814
17154
|
return ageMs > 5 * 60 * 1e3;
|
|
16815
17155
|
} catch {
|
|
@@ -16830,14 +17170,14 @@ function cleanDbs(options) {
|
|
|
16830
17170
|
const scanDir = (dir) => {
|
|
16831
17171
|
let entries;
|
|
16832
17172
|
try {
|
|
16833
|
-
entries =
|
|
17173
|
+
entries = readdirSync10(dir);
|
|
16834
17174
|
} catch {
|
|
16835
17175
|
return;
|
|
16836
17176
|
}
|
|
16837
17177
|
for (const entry of entries) {
|
|
16838
|
-
const fullPath =
|
|
17178
|
+
const fullPath = join18(dir, entry);
|
|
16839
17179
|
try {
|
|
16840
|
-
const stats =
|
|
17180
|
+
const stats = statSync8(fullPath);
|
|
16841
17181
|
if (stats.isDirectory()) {
|
|
16842
17182
|
scanDir(fullPath);
|
|
16843
17183
|
continue;
|
|
@@ -16853,7 +17193,7 @@ function cleanDbs(options) {
|
|
|
16853
17193
|
const companion = fullPath + suffix;
|
|
16854
17194
|
if (existsSync16(companion)) {
|
|
16855
17195
|
unlinkSync5(companion);
|
|
16856
|
-
result.bytesFreed +=
|
|
17196
|
+
result.bytesFreed += statSync8(companion).size;
|
|
16857
17197
|
}
|
|
16858
17198
|
}
|
|
16859
17199
|
result.removed++;
|
|
@@ -16871,6 +17211,36 @@ function cleanDbs(options) {
|
|
|
16871
17211
|
scanDir(unipiDir);
|
|
16872
17212
|
return result;
|
|
16873
17213
|
}
|
|
17214
|
+
function cleanToolResults(options) {
|
|
17215
|
+
const result = {
|
|
17216
|
+
category: "tool-results",
|
|
17217
|
+
removed: 0,
|
|
17218
|
+
bytesFreed: 0,
|
|
17219
|
+
paths: []
|
|
17220
|
+
};
|
|
17221
|
+
const dir = expandHome("~/.unipi/tool-results");
|
|
17222
|
+
if (!existsSync16(dir)) return result;
|
|
17223
|
+
let entries;
|
|
17224
|
+
try {
|
|
17225
|
+
entries = readdirSync10(dir);
|
|
17226
|
+
} catch {
|
|
17227
|
+
return result;
|
|
17228
|
+
}
|
|
17229
|
+
for (const entry of entries) {
|
|
17230
|
+
if (!/^(?:tool-result|mcp-[a-zA-Z0-9_-]+|helper)-[a-f0-9-]+\.txt$/.test(entry)) continue;
|
|
17231
|
+
const path28 = join18(dir, entry);
|
|
17232
|
+
try {
|
|
17233
|
+
const stats = statSync8(path28);
|
|
17234
|
+
if (!stats.isFile() || !isStale(path28, options.tempMaxAgeDays)) continue;
|
|
17235
|
+
result.paths.push(path28);
|
|
17236
|
+
result.bytesFreed += stats.size;
|
|
17237
|
+
if (!options.dryRun) unlinkSync5(path28);
|
|
17238
|
+
result.removed++;
|
|
17239
|
+
} catch {
|
|
17240
|
+
}
|
|
17241
|
+
}
|
|
17242
|
+
return result;
|
|
17243
|
+
}
|
|
16874
17244
|
function cleanTemps(options) {
|
|
16875
17245
|
const result = {
|
|
16876
17246
|
category: "temp",
|
|
@@ -16882,15 +17252,15 @@ function cleanTemps(options) {
|
|
|
16882
17252
|
const tmpDir = tmpdir();
|
|
16883
17253
|
let entries;
|
|
16884
17254
|
try {
|
|
16885
|
-
entries =
|
|
17255
|
+
entries = readdirSync10(tmpDir);
|
|
16886
17256
|
} catch {
|
|
16887
17257
|
return result;
|
|
16888
17258
|
}
|
|
16889
17259
|
for (const entry of entries) {
|
|
16890
17260
|
if (!patterns.some((p) => p.test(entry))) continue;
|
|
16891
|
-
const fullPath =
|
|
17261
|
+
const fullPath = join18(tmpDir, entry);
|
|
16892
17262
|
try {
|
|
16893
|
-
const stats =
|
|
17263
|
+
const stats = statSync8(fullPath);
|
|
16894
17264
|
if (!stats.isFile()) continue;
|
|
16895
17265
|
if (isStale(fullPath, options.tempMaxAgeDays)) {
|
|
16896
17266
|
result.bytesFreed += stats.size;
|
|
@@ -16918,18 +17288,18 @@ function cleanSessions(options) {
|
|
|
16918
17288
|
paths: []
|
|
16919
17289
|
};
|
|
16920
17290
|
const unipiDir = expandHome("~/.unipi");
|
|
16921
|
-
const sessionsDir =
|
|
17291
|
+
const sessionsDir = join18(unipiDir, "sessions");
|
|
16922
17292
|
if (!existsSync16(sessionsDir)) return result;
|
|
16923
17293
|
let entries;
|
|
16924
17294
|
try {
|
|
16925
|
-
entries =
|
|
17295
|
+
entries = readdirSync10(sessionsDir);
|
|
16926
17296
|
} catch {
|
|
16927
17297
|
return result;
|
|
16928
17298
|
}
|
|
16929
17299
|
for (const entry of entries) {
|
|
16930
|
-
const fullPath =
|
|
17300
|
+
const fullPath = join18(sessionsDir, entry);
|
|
16931
17301
|
try {
|
|
16932
|
-
const stats =
|
|
17302
|
+
const stats = statSync8(fullPath);
|
|
16933
17303
|
if (!stats.isDirectory()) continue;
|
|
16934
17304
|
if (isStale(fullPath, options.sessionMaxAgeDays)) {
|
|
16935
17305
|
result.bytesFreed += stats.size;
|
|
@@ -16937,10 +17307,10 @@ function cleanSessions(options) {
|
|
|
16937
17307
|
if (!options.dryRun) {
|
|
16938
17308
|
try {
|
|
16939
17309
|
const removeRecursive = (dir) => {
|
|
16940
|
-
const items =
|
|
17310
|
+
const items = readdirSync10(dir);
|
|
16941
17311
|
for (const item of items) {
|
|
16942
|
-
const itemPath =
|
|
16943
|
-
const itemStats =
|
|
17312
|
+
const itemPath = join18(dir, item);
|
|
17313
|
+
const itemStats = statSync8(itemPath);
|
|
16944
17314
|
if (itemStats.isDirectory()) {
|
|
16945
17315
|
removeRecursive(itemPath);
|
|
16946
17316
|
} else {
|
|
@@ -16973,14 +17343,14 @@ function cleanCache(options) {
|
|
|
16973
17343
|
if (!existsSync16(cacheDir)) return result;
|
|
16974
17344
|
let entries;
|
|
16975
17345
|
try {
|
|
16976
|
-
entries =
|
|
17346
|
+
entries = readdirSync10(cacheDir);
|
|
16977
17347
|
} catch {
|
|
16978
17348
|
return result;
|
|
16979
17349
|
}
|
|
16980
17350
|
for (const entry of entries) {
|
|
16981
|
-
const fullPath =
|
|
17351
|
+
const fullPath = join18(cacheDir, entry);
|
|
16982
17352
|
try {
|
|
16983
|
-
const stats =
|
|
17353
|
+
const stats = statSync8(fullPath);
|
|
16984
17354
|
if (!stats.isFile()) continue;
|
|
16985
17355
|
if (isStale(fullPath, options.tempMaxAgeDays)) {
|
|
16986
17356
|
result.bytesFreed += stats.size;
|
|
@@ -17005,6 +17375,7 @@ function cleanupStale(options = {}) {
|
|
|
17005
17375
|
const results = [
|
|
17006
17376
|
cleanDbs(opts),
|
|
17007
17377
|
cleanTemps(opts),
|
|
17378
|
+
cleanToolResults(opts),
|
|
17008
17379
|
cleanSessions(opts),
|
|
17009
17380
|
cleanCache(opts)
|
|
17010
17381
|
];
|
|
@@ -17044,18 +17415,18 @@ function formatCleanupReport(report) {
|
|
|
17044
17415
|
}
|
|
17045
17416
|
|
|
17046
17417
|
// packages/utility/src/diagnostics/engine.ts
|
|
17047
|
-
import { existsSync as existsSync17, accessSync, constants } from "node:fs";
|
|
17048
|
-
import { join as
|
|
17049
|
-
import { homedir as
|
|
17418
|
+
import { existsSync as existsSync17, accessSync as accessSync2, constants as constants2 } from "node:fs";
|
|
17419
|
+
import { join as join19 } from "node:path";
|
|
17420
|
+
import { homedir as homedir15 } from "node:os";
|
|
17050
17421
|
function expandHome2(path28) {
|
|
17051
17422
|
if (path28.startsWith("~/")) {
|
|
17052
|
-
return
|
|
17423
|
+
return join19(homedir15(), path28.slice(2));
|
|
17053
17424
|
}
|
|
17054
17425
|
return path28;
|
|
17055
17426
|
}
|
|
17056
17427
|
function isReadable(path28) {
|
|
17057
17428
|
try {
|
|
17058
|
-
|
|
17429
|
+
accessSync2(path28, constants2.R_OK);
|
|
17059
17430
|
return true;
|
|
17060
17431
|
} catch {
|
|
17061
17432
|
return false;
|
|
@@ -17063,7 +17434,7 @@ function isReadable(path28) {
|
|
|
17063
17434
|
}
|
|
17064
17435
|
function isWritable(path28) {
|
|
17065
17436
|
try {
|
|
17066
|
-
|
|
17437
|
+
accessSync2(path28, constants2.W_OK);
|
|
17067
17438
|
return true;
|
|
17068
17439
|
} catch {
|
|
17069
17440
|
return false;
|
|
@@ -19115,7 +19486,7 @@ function getLifecycle(options) {
|
|
|
19115
19486
|
}
|
|
19116
19487
|
|
|
19117
19488
|
// packages/utility/src/analytics/collector.ts
|
|
19118
|
-
import { randomUUID as
|
|
19489
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
19119
19490
|
var DEFAULTS3 = {
|
|
19120
19491
|
dbPath: "~/.unipi/analytics/events.db",
|
|
19121
19492
|
maxEvents: 1e4,
|
|
@@ -19124,7 +19495,7 @@ var DEFAULTS3 = {
|
|
|
19124
19495
|
var eventBuffer = [];
|
|
19125
19496
|
var bufferFlushTimer = null;
|
|
19126
19497
|
function generateId() {
|
|
19127
|
-
return
|
|
19498
|
+
return randomUUID4();
|
|
19128
19499
|
}
|
|
19129
19500
|
function today() {
|
|
19130
19501
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -19332,8 +19703,189 @@ function registerInfoScreen(pi) {
|
|
|
19332
19703
|
});
|
|
19333
19704
|
}
|
|
19334
19705
|
|
|
19706
|
+
// packages/utility/src/prefix-cache.ts
|
|
19707
|
+
import { createHmac, randomBytes } from "node:crypto";
|
|
19708
|
+
var EMPTY_SNAPSHOT = {
|
|
19709
|
+
epoch: 0,
|
|
19710
|
+
requests: 0,
|
|
19711
|
+
prefixExtensions: 0,
|
|
19712
|
+
identicalRetries: 0,
|
|
19713
|
+
boundaries: 0,
|
|
19714
|
+
lastTransition: "none",
|
|
19715
|
+
requestFingerprint: "none",
|
|
19716
|
+
envelopeFingerprint: "none",
|
|
19717
|
+
sequenceField: "none",
|
|
19718
|
+
sequenceItems: 0,
|
|
19719
|
+
route: "unknown",
|
|
19720
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, responses: 0 }
|
|
19721
|
+
};
|
|
19722
|
+
function canonicalize(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
19723
|
+
if (value === void 0) return { $type: "undefined" };
|
|
19724
|
+
if (typeof value === "bigint") return { $type: "bigint", value: value.toString() };
|
|
19725
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
19726
|
+
return { $type: "number", value: String(value) };
|
|
19727
|
+
}
|
|
19728
|
+
if (typeof value !== "object" || value === null) return value;
|
|
19729
|
+
if (seen.has(value)) return { $type: "cycle" };
|
|
19730
|
+
seen.add(value);
|
|
19731
|
+
if (Array.isArray(value)) {
|
|
19732
|
+
const result2 = value.map((item) => canonicalize(item, seen));
|
|
19733
|
+
seen.delete(value);
|
|
19734
|
+
return result2;
|
|
19735
|
+
}
|
|
19736
|
+
if (value instanceof Uint8Array) {
|
|
19737
|
+
seen.delete(value);
|
|
19738
|
+
return { $type: "bytes", length: value.byteLength };
|
|
19739
|
+
}
|
|
19740
|
+
const record = value;
|
|
19741
|
+
const result = {};
|
|
19742
|
+
for (const key of Object.keys(record).sort((a, b) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
19743
|
+
result[key] = canonicalize(record[key], seen);
|
|
19744
|
+
}
|
|
19745
|
+
seen.delete(value);
|
|
19746
|
+
return result;
|
|
19747
|
+
}
|
|
19748
|
+
function canonicalJson(value) {
|
|
19749
|
+
return JSON.stringify(canonicalize(value));
|
|
19750
|
+
}
|
|
19751
|
+
function routeLabel(model) {
|
|
19752
|
+
if (!model || typeof model !== "object") return "unknown";
|
|
19753
|
+
const value = model;
|
|
19754
|
+
return [value.provider, value.id, value.api].filter((part) => typeof part === "string" && part.length > 0).join("/") || "unknown";
|
|
19755
|
+
}
|
|
19756
|
+
function findSequence(payload) {
|
|
19757
|
+
for (const field of ["messages", "input", "contents"]) {
|
|
19758
|
+
if (Array.isArray(payload[field])) return { field, items: payload[field] };
|
|
19759
|
+
}
|
|
19760
|
+
return { field: "none", items: [] };
|
|
19761
|
+
}
|
|
19762
|
+
function isPrefix(previous, current) {
|
|
19763
|
+
return previous.length <= current.length && previous.every((item, index) => current[index] === item);
|
|
19764
|
+
}
|
|
19765
|
+
function usageNumber(value) {
|
|
19766
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
19767
|
+
}
|
|
19768
|
+
var PrefixCacheTracker = class {
|
|
19769
|
+
key;
|
|
19770
|
+
previous = null;
|
|
19771
|
+
seenResponses = /* @__PURE__ */ new WeakSet();
|
|
19772
|
+
snapshot = structuredClone(EMPTY_SNAPSHOT);
|
|
19773
|
+
constructor(key = randomBytes(32)) {
|
|
19774
|
+
this.key = key;
|
|
19775
|
+
}
|
|
19776
|
+
reset() {
|
|
19777
|
+
this.previous = null;
|
|
19778
|
+
this.seenResponses = /* @__PURE__ */ new WeakSet();
|
|
19779
|
+
this.snapshot = structuredClone(EMPTY_SNAPSHOT);
|
|
19780
|
+
}
|
|
19781
|
+
fingerprint(value) {
|
|
19782
|
+
return createHmac("sha256", this.key).update(canonicalJson(value)).digest("hex").slice(0, 16);
|
|
19783
|
+
}
|
|
19784
|
+
observeRequest(payload, model) {
|
|
19785
|
+
const object = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { payload };
|
|
19786
|
+
const { field, items } = findSequence(object);
|
|
19787
|
+
const route = routeLabel(model);
|
|
19788
|
+
const envelope = { route };
|
|
19789
|
+
for (const [key, value] of Object.entries(object)) {
|
|
19790
|
+
if (key !== field) envelope[key] = value;
|
|
19791
|
+
}
|
|
19792
|
+
const current = {
|
|
19793
|
+
envelopeFingerprint: this.fingerprint(envelope),
|
|
19794
|
+
requestFingerprint: this.fingerprint(object),
|
|
19795
|
+
sequenceField: field,
|
|
19796
|
+
sequenceItemFingerprints: items.map((item) => this.fingerprint(item))
|
|
19797
|
+
};
|
|
19798
|
+
let transition;
|
|
19799
|
+
let epoch = this.snapshot.epoch;
|
|
19800
|
+
if (!this.previous) {
|
|
19801
|
+
transition = "first_request";
|
|
19802
|
+
epoch = Math.max(1, epoch);
|
|
19803
|
+
} else if (current.envelopeFingerprint !== this.previous.envelopeFingerprint) {
|
|
19804
|
+
transition = "envelope_changed";
|
|
19805
|
+
epoch++;
|
|
19806
|
+
} else if (current.sequenceField !== this.previous.sequenceField) {
|
|
19807
|
+
transition = "payload_shape_changed";
|
|
19808
|
+
epoch++;
|
|
19809
|
+
} else if (current.requestFingerprint === this.previous.requestFingerprint) {
|
|
19810
|
+
transition = "identical_retry";
|
|
19811
|
+
} else if (isPrefix(this.previous.sequenceItemFingerprints, current.sequenceItemFingerprints)) {
|
|
19812
|
+
transition = "prefix_extended";
|
|
19813
|
+
} else {
|
|
19814
|
+
transition = "history_rewritten";
|
|
19815
|
+
epoch++;
|
|
19816
|
+
}
|
|
19817
|
+
this.snapshot = {
|
|
19818
|
+
...this.snapshot,
|
|
19819
|
+
epoch,
|
|
19820
|
+
requests: this.snapshot.requests + 1,
|
|
19821
|
+
prefixExtensions: this.snapshot.prefixExtensions + (transition === "prefix_extended" ? 1 : 0),
|
|
19822
|
+
identicalRetries: this.snapshot.identicalRetries + (transition === "identical_retry" ? 1 : 0),
|
|
19823
|
+
boundaries: this.snapshot.boundaries + (transition === "envelope_changed" || transition === "history_rewritten" || transition === "payload_shape_changed" ? 1 : 0),
|
|
19824
|
+
lastTransition: transition,
|
|
19825
|
+
requestFingerprint: current.requestFingerprint,
|
|
19826
|
+
envelopeFingerprint: current.envelopeFingerprint,
|
|
19827
|
+
sequenceField: current.sequenceField,
|
|
19828
|
+
sequenceItems: current.sequenceItemFingerprints.length,
|
|
19829
|
+
route
|
|
19830
|
+
};
|
|
19831
|
+
this.previous = current;
|
|
19832
|
+
return this.getSnapshot();
|
|
19833
|
+
}
|
|
19834
|
+
observeMessages(messages) {
|
|
19835
|
+
for (const message of messages) {
|
|
19836
|
+
if (!message || typeof message !== "object") continue;
|
|
19837
|
+
const value = message;
|
|
19838
|
+
if (value.role !== "assistant" || !value.usage || typeof value.usage !== "object") continue;
|
|
19839
|
+
if (value.stopReason === "error" || value.stopReason === "aborted") continue;
|
|
19840
|
+
if (this.seenResponses.has(message)) continue;
|
|
19841
|
+
this.seenResponses.add(message);
|
|
19842
|
+
const usage = value.usage;
|
|
19843
|
+
this.snapshot.usage.input += usageNumber(usage.input);
|
|
19844
|
+
this.snapshot.usage.output += usageNumber(usage.output);
|
|
19845
|
+
this.snapshot.usage.cacheRead += usageNumber(usage.cacheRead);
|
|
19846
|
+
this.snapshot.usage.cacheWrite += usageNumber(usage.cacheWrite);
|
|
19847
|
+
this.snapshot.usage.responses++;
|
|
19848
|
+
}
|
|
19849
|
+
return this.getSnapshot();
|
|
19850
|
+
}
|
|
19851
|
+
markBoundary(transition) {
|
|
19852
|
+
if (this.previous) {
|
|
19853
|
+
this.snapshot.epoch++;
|
|
19854
|
+
this.snapshot.boundaries++;
|
|
19855
|
+
}
|
|
19856
|
+
this.snapshot.lastTransition = transition;
|
|
19857
|
+
this.previous = null;
|
|
19858
|
+
}
|
|
19859
|
+
getSnapshot() {
|
|
19860
|
+
return structuredClone(this.snapshot);
|
|
19861
|
+
}
|
|
19862
|
+
};
|
|
19863
|
+
function formatPrefixCacheStats(snapshot) {
|
|
19864
|
+
const usage = snapshot.usage;
|
|
19865
|
+
const totalPrompt = usage.input + usage.cacheRead + usage.cacheWrite;
|
|
19866
|
+
const readRate = totalPrompt > 0 ? `${(usage.cacheRead / totalPrompt * 100).toFixed(1)}%` : "n/a";
|
|
19867
|
+
return [
|
|
19868
|
+
"## Provider Prefix Cache",
|
|
19869
|
+
"",
|
|
19870
|
+
`- Epoch: ${snapshot.epoch || "not observed"}`,
|
|
19871
|
+
`- Requests observed: ${snapshot.requests}`,
|
|
19872
|
+
`- Prefix extensions: ${snapshot.prefixExtensions}`,
|
|
19873
|
+
`- Explicit boundaries: ${snapshot.boundaries}`,
|
|
19874
|
+
`- Last transition: ${snapshot.lastTransition}`,
|
|
19875
|
+
`- Route: ${snapshot.route}`,
|
|
19876
|
+
`- Request fingerprint: ${snapshot.requestFingerprint}`,
|
|
19877
|
+
`- Envelope fingerprint: ${snapshot.envelopeFingerprint}`,
|
|
19878
|
+
`- Provider cache read: ${usage.cacheRead} tokens`,
|
|
19879
|
+
`- Provider cache write: ${usage.cacheWrite} tokens`,
|
|
19880
|
+
`- Observed cache-read share: ${readRate}`,
|
|
19881
|
+
"",
|
|
19882
|
+
"Fingerprints are session-local keyed HMACs. Raw prompts, messages, tool arguments, and payloads are not retained or logged.",
|
|
19883
|
+
"A prefix extension indicates structural eligibility only; provider cache retention and routing still determine actual hits."
|
|
19884
|
+
].join("\n");
|
|
19885
|
+
}
|
|
19886
|
+
|
|
19335
19887
|
// packages/utility/src/index.ts
|
|
19336
|
-
var VERSION6 = getPackageVersion(
|
|
19888
|
+
var VERSION6 = getPackageVersion(dirname14(fileURLToPath7(import.meta.url)));
|
|
19337
19889
|
var firstMessageSeen = false;
|
|
19338
19890
|
var firstUserText = "";
|
|
19339
19891
|
var firstInputCtx = null;
|
|
@@ -19348,12 +19900,44 @@ var ALL_COMMANDS = [
|
|
|
19348
19900
|
UTILITY_COMMANDS.BADGE_GEN,
|
|
19349
19901
|
UTILITY_COMMANDS.BADGE_TOGGLE,
|
|
19350
19902
|
UTILITY_COMMANDS.BADGE_SETTINGS,
|
|
19351
|
-
UTILITY_COMMANDS.UTIL_SETTINGS
|
|
19903
|
+
UTILITY_COMMANDS.UTIL_SETTINGS,
|
|
19904
|
+
UTILITY_COMMANDS.PREFIX_CACHE
|
|
19352
19905
|
].map((cmd) => `unipi:${cmd}`);
|
|
19353
19906
|
var ALL_TOOLS2 = [UTILITY_TOOLS.BATCH, UTILITY_TOOLS.ENV, UTILITY_TOOLS.SET_SESSION_NAME];
|
|
19354
19907
|
function src_default3(pi) {
|
|
19355
19908
|
const lifecycle = getLifecycle();
|
|
19356
19909
|
const analytics = getAnalyticsCollector();
|
|
19910
|
+
const prefixCache = new PrefixCacheTracker();
|
|
19911
|
+
pi.registerCommand(`unipi:${UTILITY_COMMANDS.PREFIX_CACHE}`, {
|
|
19912
|
+
description: "Show privacy-safe provider prefix-cache diagnostics",
|
|
19913
|
+
handler: async (_args, ctx) => {
|
|
19914
|
+
const report = formatPrefixCacheStats(prefixCache.getSnapshot());
|
|
19915
|
+
if (ctx.hasUI) {
|
|
19916
|
+
ctx.ui.notify(report, "info");
|
|
19917
|
+
} else {
|
|
19918
|
+
pi.sendMessage({ customType: "unipi-response", content: report, display: true }, { deliverAs: "followUp" });
|
|
19919
|
+
}
|
|
19920
|
+
}
|
|
19921
|
+
});
|
|
19922
|
+
pi.on("session_start", () => {
|
|
19923
|
+
prefixCache.reset();
|
|
19924
|
+
});
|
|
19925
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
19926
|
+
prefixCache.observeRequest(event.payload, ctx.model);
|
|
19927
|
+
});
|
|
19928
|
+
pi.on("agent_end", (event) => {
|
|
19929
|
+
prefixCache.observeMessages(event.messages);
|
|
19930
|
+
});
|
|
19931
|
+
pi.on("model_select", (event) => {
|
|
19932
|
+
if (event.previousModel && (event.previousModel.provider !== event.model.provider || event.previousModel.id !== event.model.id || event.previousModel.api !== event.model.api)) {
|
|
19933
|
+
prefixCache.markBoundary("envelope_changed");
|
|
19934
|
+
}
|
|
19935
|
+
});
|
|
19936
|
+
pi.on("thinking_level_select", (event) => {
|
|
19937
|
+
if (event.previousLevel !== event.level) prefixCache.markBoundary("envelope_changed");
|
|
19938
|
+
});
|
|
19939
|
+
pi.on("session_compact", () => prefixCache.markBoundary("history_rewritten"));
|
|
19940
|
+
pi.on("session_tree", () => prefixCache.markBoundary("history_rewritten"));
|
|
19357
19941
|
lifecycle.registerCleanup(async () => {
|
|
19358
19942
|
analytics.disable();
|
|
19359
19943
|
});
|
|
@@ -19545,7 +20129,7 @@ function registerUtilityTools(pi, nameBadgeState) {
|
|
|
19545
20129
|
|
|
19546
20130
|
// packages/ask-user/index.ts
|
|
19547
20131
|
init_core();
|
|
19548
|
-
import { dirname as
|
|
20132
|
+
import { dirname as dirname15 } from "node:path";
|
|
19549
20133
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
19550
20134
|
|
|
19551
20135
|
// packages/ask-user/tools.ts
|
|
@@ -20201,9 +20785,9 @@ function renderLauncherUI(params) {
|
|
|
20201
20785
|
}
|
|
20202
20786
|
|
|
20203
20787
|
// packages/ask-user/config.ts
|
|
20204
|
-
import { readFileSync as readFileSync16, writeFileSync as
|
|
20205
|
-
import { join as
|
|
20206
|
-
import { homedir as
|
|
20788
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync16, existsSync as existsSync21 } from "node:fs";
|
|
20789
|
+
import { join as join21 } from "node:path";
|
|
20790
|
+
import { homedir as homedir17 } from "node:os";
|
|
20207
20791
|
var DEFAULT_SETTINGS3 = {
|
|
20208
20792
|
enabled: true,
|
|
20209
20793
|
allowedFormats: {
|
|
@@ -20213,7 +20797,7 @@ var DEFAULT_SETTINGS3 = {
|
|
|
20213
20797
|
},
|
|
20214
20798
|
notifyOnAsk: true
|
|
20215
20799
|
};
|
|
20216
|
-
var SETTINGS_PATH2 =
|
|
20800
|
+
var SETTINGS_PATH2 = join21(homedir17(), ".pi", "agent", "settings.json");
|
|
20217
20801
|
var SETTINGS_KEY2 = "unipi";
|
|
20218
20802
|
var cachedSettings2 = null;
|
|
20219
20803
|
function isRecord3(value) {
|
|
@@ -20233,7 +20817,7 @@ function writeSettingsFile2(data) {
|
|
|
20233
20817
|
if (!existsSync21(dir)) {
|
|
20234
20818
|
__require("node:fs").mkdirSync(dir, { recursive: true });
|
|
20235
20819
|
}
|
|
20236
|
-
|
|
20820
|
+
writeFileSync16(SETTINGS_PATH2, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
20237
20821
|
}
|
|
20238
20822
|
function getAskUserSettings() {
|
|
20239
20823
|
if (cachedSettings2) return cachedSettings2;
|
|
@@ -20930,7 +21514,7 @@ function registerAskUserCommands(pi) {
|
|
|
20930
21514
|
}
|
|
20931
21515
|
|
|
20932
21516
|
// packages/ask-user/index.ts
|
|
20933
|
-
var VERSION7 = getPackageVersion(
|
|
21517
|
+
var VERSION7 = getPackageVersion(dirname15(fileURLToPath8(import.meta.url)));
|
|
20934
21518
|
function ask_user_default(pi) {
|
|
20935
21519
|
registerAskUserTools(pi);
|
|
20936
21520
|
registerAskUserCommands(pi);
|
|
@@ -20946,7 +21530,7 @@ function ask_user_default(pi) {
|
|
|
20946
21530
|
|
|
20947
21531
|
// packages/mcp/src/index.ts
|
|
20948
21532
|
init_core();
|
|
20949
|
-
import { dirname as
|
|
21533
|
+
import { dirname as dirname18 } from "node:path";
|
|
20950
21534
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
20951
21535
|
|
|
20952
21536
|
// packages/mcp/src/config/manager.ts
|
|
@@ -21444,7 +22028,7 @@ var McpClient = class {
|
|
|
21444
22028
|
if (this.connected) {
|
|
21445
22029
|
throw new Error("McpClient is already connected");
|
|
21446
22030
|
}
|
|
21447
|
-
return new Promise((
|
|
22031
|
+
return new Promise((resolve14, reject) => {
|
|
21448
22032
|
try {
|
|
21449
22033
|
const mergedEnv = { ...process.env, ...env };
|
|
21450
22034
|
this.process = spawn2(command, args, {
|
|
@@ -21493,7 +22077,7 @@ Stderr: ${this.stderrBuffer}`
|
|
|
21493
22077
|
}).then(() => {
|
|
21494
22078
|
this.sendNotification("notifications/initialized", {});
|
|
21495
22079
|
this.connected = true;
|
|
21496
|
-
|
|
22080
|
+
resolve14();
|
|
21497
22081
|
}).catch((err) => {
|
|
21498
22082
|
this.cleanup();
|
|
21499
22083
|
reject(
|
|
@@ -21537,16 +22121,16 @@ Stderr: ${this.stderrBuffer}`
|
|
|
21537
22121
|
this.sendNotification("shutdown", {});
|
|
21538
22122
|
} catch {
|
|
21539
22123
|
}
|
|
21540
|
-
await new Promise((
|
|
21541
|
-
const timer = setTimeout(() =>
|
|
22124
|
+
await new Promise((resolve14) => {
|
|
22125
|
+
const timer = setTimeout(() => resolve14(), 500);
|
|
21542
22126
|
this.process.on("exit", () => {
|
|
21543
22127
|
clearTimeout(timer);
|
|
21544
|
-
|
|
22128
|
+
resolve14();
|
|
21545
22129
|
});
|
|
21546
22130
|
try {
|
|
21547
22131
|
this.process.kill("SIGTERM");
|
|
21548
22132
|
} catch {
|
|
21549
|
-
|
|
22133
|
+
resolve14();
|
|
21550
22134
|
}
|
|
21551
22135
|
});
|
|
21552
22136
|
if (this.process && !this.process.killed) {
|
|
@@ -21576,7 +22160,7 @@ Stderr: ${this.stderrBuffer}`
|
|
|
21576
22160
|
}
|
|
21577
22161
|
}
|
|
21578
22162
|
sendRequest(method, params) {
|
|
21579
|
-
return new Promise((
|
|
22163
|
+
return new Promise((resolve14, reject) => {
|
|
21580
22164
|
if (!this.process?.stdin) {
|
|
21581
22165
|
reject(new Error("MCP server process not available"));
|
|
21582
22166
|
return;
|
|
@@ -21596,7 +22180,7 @@ Stderr: ${this.stderrBuffer}`
|
|
|
21596
22180
|
)
|
|
21597
22181
|
);
|
|
21598
22182
|
}, this.timeoutMs);
|
|
21599
|
-
this.pending.set(id, { resolve:
|
|
22183
|
+
this.pending.set(id, { resolve: resolve14, reject, timer });
|
|
21600
22184
|
const message = JSON.stringify(request) + "\n";
|
|
21601
22185
|
this.process.stdin.write(message);
|
|
21602
22186
|
});
|
|
@@ -21659,15 +22243,45 @@ Stderr: ${this.stderrBuffer}`
|
|
|
21659
22243
|
|
|
21660
22244
|
// packages/mcp/src/bridge/translator.ts
|
|
21661
22245
|
init_core();
|
|
22246
|
+
function compareCodeUnits3(left, right) {
|
|
22247
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
22248
|
+
}
|
|
22249
|
+
function isObject(value) {
|
|
22250
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22251
|
+
}
|
|
22252
|
+
var LITERAL_VALUE_KEYWORDS = /* @__PURE__ */ new Set(["const", "default", "enum", "examples"]);
|
|
22253
|
+
function canonicalizeValue(value, key, normalizeSchemaKeywords = true) {
|
|
22254
|
+
if (Array.isArray(value)) {
|
|
22255
|
+
if (normalizeSchemaKeywords && key === "required" && value.every((item) => typeof item === "string")) {
|
|
22256
|
+
return [...new Set(value)].sort(compareCodeUnits3);
|
|
22257
|
+
}
|
|
22258
|
+
return value.map((item) => canonicalizeValue(item, void 0, normalizeSchemaKeywords));
|
|
22259
|
+
}
|
|
22260
|
+
if (!isObject(value)) return value;
|
|
22261
|
+
const canonical = {};
|
|
22262
|
+
for (const objectKey of Object.keys(value).sort(compareCodeUnits3)) {
|
|
22263
|
+
const childNormalizesSchemaKeywords = normalizeSchemaKeywords && !LITERAL_VALUE_KEYWORDS.has(objectKey);
|
|
22264
|
+
canonical[objectKey] = canonicalizeValue(
|
|
22265
|
+
value[objectKey],
|
|
22266
|
+
objectKey,
|
|
22267
|
+
childNormalizesSchemaKeywords
|
|
22268
|
+
);
|
|
22269
|
+
}
|
|
22270
|
+
return canonical;
|
|
22271
|
+
}
|
|
22272
|
+
function canonicalizeJsonSchema(schema) {
|
|
22273
|
+
return canonicalizeValue(schema);
|
|
22274
|
+
}
|
|
21662
22275
|
function translateMcpTool(mcpTool, serverName, client) {
|
|
21663
22276
|
const separator = MCP_DEFAULTS.TOOL_NAME_SEPARATOR;
|
|
21664
22277
|
const toolName = `${serverName}${separator}${mcpTool.name}`;
|
|
21665
|
-
const inputSchema = mcpTool.inputSchema
|
|
21666
|
-
const
|
|
22278
|
+
const inputSchema = isObject(mcpTool.inputSchema) ? mcpTool.inputSchema : {};
|
|
22279
|
+
const normalizedSchema = {
|
|
21667
22280
|
type: "object",
|
|
21668
|
-
properties: inputSchema.properties
|
|
21669
|
-
required: inputSchema.required
|
|
22281
|
+
properties: isObject(inputSchema.properties) ? inputSchema.properties : {},
|
|
22282
|
+
required: Array.isArray(inputSchema.required) ? inputSchema.required : []
|
|
21670
22283
|
};
|
|
22284
|
+
const parameters = canonicalizeJsonSchema(normalizedSchema);
|
|
21671
22285
|
const description = [
|
|
21672
22286
|
mcpTool.description || `MCP tool: ${mcpTool.name}`,
|
|
21673
22287
|
`[Server: ${serverName}]`
|
|
@@ -21695,19 +22309,26 @@ function translateMcpTool(mcpTool, serverName, client) {
|
|
|
21695
22309
|
});
|
|
21696
22310
|
}
|
|
21697
22311
|
}
|
|
21698
|
-
if (result.isError) {
|
|
21699
|
-
const errorText = blocks.map((b) => b.text).join("\n") || "Unknown error";
|
|
21700
|
-
return {
|
|
21701
|
-
content: [{ type: "text", text: `MCP tool error from ${serverName}: ${errorText}` }],
|
|
21702
|
-
details: { error: true, server: serverName, tool: mcpTool.name }
|
|
21703
|
-
};
|
|
21704
|
-
}
|
|
21705
22312
|
if (blocks.length === 0) {
|
|
21706
|
-
blocks.push({ type: "text", text: "(no output)" });
|
|
22313
|
+
blocks.push({ type: "text", text: result.isError ? "Unknown error" : "(no output)" });
|
|
21707
22314
|
}
|
|
22315
|
+
const rawText = blocks.map((block2) => block2.text).join("\n");
|
|
22316
|
+
const wrapper = result.isError ? `MCP tool error from ${serverName}: ` : "";
|
|
22317
|
+
const output = boundModelOutput(rawText, {
|
|
22318
|
+
maxBytes: Math.max(1024, MCP_DEFAULTS.MAX_MODEL_OUTPUT_BYTES - Buffer.byteLength(wrapper, "utf8")),
|
|
22319
|
+
artifactPrefix: `mcp-${serverName}-${mcpTool.name}`
|
|
22320
|
+
});
|
|
22321
|
+
const visibleText = `${wrapper}${output.text}`;
|
|
21708
22322
|
return {
|
|
21709
|
-
content:
|
|
21710
|
-
details: {
|
|
22323
|
+
content: [{ type: "text", text: visibleText }],
|
|
22324
|
+
details: {
|
|
22325
|
+
error: result.isError || void 0,
|
|
22326
|
+
server: serverName,
|
|
22327
|
+
tool: mcpTool.name,
|
|
22328
|
+
truncated: output.truncated,
|
|
22329
|
+
originalBytes: output.originalBytes,
|
|
22330
|
+
artifactPath: output.artifactPath
|
|
22331
|
+
}
|
|
21711
22332
|
};
|
|
21712
22333
|
} catch (err) {
|
|
21713
22334
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -21725,6 +22346,7 @@ Check server status via /unipi:mcp-settings`
|
|
|
21725
22346
|
};
|
|
21726
22347
|
return {
|
|
21727
22348
|
name: toolName,
|
|
22349
|
+
label: toolName,
|
|
21728
22350
|
description,
|
|
21729
22351
|
parameters,
|
|
21730
22352
|
execute: execute2
|
|
@@ -21737,26 +22359,125 @@ var ServerRegistry = class {
|
|
|
21737
22359
|
emitEvent;
|
|
21738
22360
|
registerTool;
|
|
21739
22361
|
unregisterTool;
|
|
22362
|
+
canUnregisterTools;
|
|
21740
22363
|
timeoutMs;
|
|
22364
|
+
createClient;
|
|
21741
22365
|
constructor(options) {
|
|
21742
22366
|
this.emitEvent = options.emitEvent;
|
|
21743
22367
|
this.registerTool = options.registerTool;
|
|
21744
22368
|
this.unregisterTool = options.unregisterTool;
|
|
22369
|
+
this.canUnregisterTools = options.canUnregisterTools ?? true;
|
|
21745
22370
|
this.timeoutMs = options.timeoutMs ?? MCP_DEFAULTS.STARTUP_TIMEOUT_MS;
|
|
22371
|
+
this.createClient = options.createClient ?? (() => new McpClient({ timeoutMs: this.timeoutMs }));
|
|
21746
22372
|
}
|
|
21747
22373
|
/**
|
|
21748
|
-
* Start
|
|
22374
|
+
* Start one MCP server. Discovery completes before its tools are registered,
|
|
22375
|
+
* and registration always follows final Pi tool-name order.
|
|
21749
22376
|
*/
|
|
21750
22377
|
async startServer(resolved) {
|
|
21751
|
-
|
|
21752
|
-
|
|
22378
|
+
await this.startServers([resolved]);
|
|
22379
|
+
const state = this.getServerState(resolved.name);
|
|
22380
|
+
if (state?.status === "error") {
|
|
22381
|
+
throw new Error(state.error ?? `MCP server "${resolved.name}" failed to start`);
|
|
22382
|
+
}
|
|
22383
|
+
}
|
|
22384
|
+
/**
|
|
22385
|
+
* Start MCP servers behind a discovery barrier.
|
|
22386
|
+
*
|
|
22387
|
+
* Connections and tool discovery run in parallel. Once every server has
|
|
22388
|
+
* either prepared or failed, tools from all successful servers are checked
|
|
22389
|
+
* for duplicate final names, sorted, and only then registered with Pi.
|
|
22390
|
+
* Individual connection failures remain represented by error registry
|
|
22391
|
+
* entries and do not prevent tools from other servers being registered.
|
|
22392
|
+
*/
|
|
22393
|
+
async startServers(resolvedServers) {
|
|
22394
|
+
if (resolvedServers.length === 0) return;
|
|
22395
|
+
const names = /* @__PURE__ */ new Set();
|
|
22396
|
+
for (const { name } of resolvedServers) {
|
|
22397
|
+
if (names.has(name)) {
|
|
22398
|
+
throw new Error(`Duplicate MCP server name in startup batch: "${name}"`);
|
|
22399
|
+
}
|
|
22400
|
+
names.add(name);
|
|
22401
|
+
}
|
|
22402
|
+
const replacementCount = [...names].filter((name) => this.entries.has(name)).length;
|
|
22403
|
+
if (this.entries.size - replacementCount + resolvedServers.length > MCP_DEFAULTS.MAX_SERVERS) {
|
|
21753
22404
|
throw new Error(
|
|
21754
22405
|
`Maximum number of MCP servers (${MCP_DEFAULTS.MAX_SERVERS}) reached. Stop a server before starting a new one.`
|
|
21755
22406
|
);
|
|
21756
22407
|
}
|
|
21757
|
-
|
|
21758
|
-
|
|
22408
|
+
await Promise.all(
|
|
22409
|
+
[...names].filter((name) => this.entries.has(name)).map((name) => this.stopServer(name))
|
|
22410
|
+
);
|
|
22411
|
+
const settled = await Promise.allSettled(
|
|
22412
|
+
resolvedServers.map((resolved) => this.prepareServer(resolved))
|
|
22413
|
+
);
|
|
22414
|
+
const prepared = settled.filter((result) => result.status === "fulfilled").map((result) => result.value);
|
|
22415
|
+
if (prepared.length === 0) return;
|
|
22416
|
+
const registrations = prepared.flatMap(({ entry, tools }) => tools.map((tool) => ({ entry, tool }))).sort((left, right) => compareCodeUnits3(left.tool.name, right.tool.name));
|
|
22417
|
+
try {
|
|
22418
|
+
this.assertUniqueFinalToolNames(registrations.map(({ tool }) => tool.name));
|
|
22419
|
+
} catch (error) {
|
|
22420
|
+
await this.failPreparedServers(prepared, error);
|
|
22421
|
+
throw error;
|
|
22422
|
+
}
|
|
22423
|
+
const registeredNames = [];
|
|
22424
|
+
try {
|
|
22425
|
+
for (const { tool } of registrations) {
|
|
22426
|
+
this.registerTool(tool);
|
|
22427
|
+
registeredNames.push(tool.name);
|
|
22428
|
+
}
|
|
22429
|
+
} catch (error) {
|
|
22430
|
+
if (this.canUnregisterTools) {
|
|
22431
|
+
for (const toolName of registeredNames.reverse()) {
|
|
22432
|
+
try {
|
|
22433
|
+
this.unregisterTool(toolName);
|
|
22434
|
+
} catch {
|
|
22435
|
+
}
|
|
22436
|
+
}
|
|
22437
|
+
await this.failPreparedServers(prepared, error);
|
|
22438
|
+
} else {
|
|
22439
|
+
const message = `${error instanceof Error ? error.message : String(error)}; some MCP tools remain registered until Pi restarts`;
|
|
22440
|
+
for (const { entry, client, tools } of prepared) {
|
|
22441
|
+
const toolNames = tools.map((tool) => tool.name).filter((name) => registeredNames.includes(name)).sort(compareCodeUnits3);
|
|
22442
|
+
entry.toolNames = toolNames;
|
|
22443
|
+
entry.state = {
|
|
22444
|
+
...entry.state,
|
|
22445
|
+
status: "error",
|
|
22446
|
+
pid: client.pid,
|
|
22447
|
+
toolCount: toolNames.length,
|
|
22448
|
+
error: message
|
|
22449
|
+
};
|
|
22450
|
+
this.emitEvent(UNIPI_EVENTS.MCP_SERVER_ERROR, {
|
|
22451
|
+
name: entry.name,
|
|
22452
|
+
error: message
|
|
22453
|
+
});
|
|
22454
|
+
}
|
|
22455
|
+
}
|
|
22456
|
+
throw error;
|
|
22457
|
+
}
|
|
22458
|
+
for (const { entry, client, tools } of [...prepared].sort((left, right) => compareCodeUnits3(left.entry.name, right.entry.name))) {
|
|
22459
|
+
const toolNames = tools.map((tool) => tool.name).sort(compareCodeUnits3);
|
|
22460
|
+
entry.toolNames = toolNames;
|
|
22461
|
+
entry.state = {
|
|
22462
|
+
...entry.state,
|
|
22463
|
+
status: "running",
|
|
22464
|
+
pid: client.pid,
|
|
22465
|
+
toolCount: toolNames.length
|
|
22466
|
+
};
|
|
22467
|
+
this.emitEvent(UNIPI_EVENTS.MCP_SERVER_STARTED, {
|
|
22468
|
+
name: entry.name,
|
|
22469
|
+
toolCount: toolNames.length
|
|
22470
|
+
});
|
|
22471
|
+
if (toolNames.length > 0) {
|
|
22472
|
+
this.emitEvent(UNIPI_EVENTS.MCP_TOOLS_REGISTERED, {
|
|
22473
|
+
serverName: entry.name,
|
|
22474
|
+
toolNames
|
|
22475
|
+
});
|
|
22476
|
+
}
|
|
21759
22477
|
}
|
|
22478
|
+
}
|
|
22479
|
+
async prepareServer(resolved) {
|
|
22480
|
+
const { name, def } = resolved;
|
|
21760
22481
|
const state = {
|
|
21761
22482
|
name,
|
|
21762
22483
|
status: "starting",
|
|
@@ -21771,73 +22492,74 @@ var ServerRegistry = class {
|
|
|
21771
22492
|
toolNames: []
|
|
21772
22493
|
};
|
|
21773
22494
|
this.entries.set(name, entry);
|
|
22495
|
+
let client = null;
|
|
21774
22496
|
try {
|
|
21775
22497
|
const safeCommand = typeof def.command === "string" ? def.command : String(def.command);
|
|
21776
22498
|
const safeArgs = Array.isArray(def.args) ? def.args : [];
|
|
21777
22499
|
let safeEnv;
|
|
21778
|
-
if (def.env !== void 0 && def.env !== null) {
|
|
21779
|
-
|
|
21780
|
-
|
|
21781
|
-
|
|
21782
|
-
|
|
21783
|
-
|
|
21784
|
-
|
|
21785
|
-
|
|
21786
|
-
|
|
21787
|
-
|
|
21788
|
-
await
|
|
21789
|
-
|
|
21790
|
-
|
|
21791
|
-
|
|
21792
|
-
|
|
21793
|
-
|
|
21794
|
-
|
|
21795
|
-
toolNames.push(piTool.name);
|
|
21796
|
-
}
|
|
21797
|
-
entry.state = {
|
|
21798
|
-
...state,
|
|
21799
|
-
status: "running",
|
|
21800
|
-
pid: client.pid,
|
|
21801
|
-
toolCount: toolNames.length
|
|
21802
|
-
};
|
|
21803
|
-
entry.toolNames = toolNames;
|
|
21804
|
-
this.emitEvent(UNIPI_EVENTS.MCP_SERVER_STARTED, {
|
|
21805
|
-
name,
|
|
21806
|
-
toolCount: toolNames.length
|
|
21807
|
-
});
|
|
21808
|
-
if (toolNames.length > 0) {
|
|
21809
|
-
this.emitEvent(UNIPI_EVENTS.MCP_TOOLS_REGISTERED, {
|
|
21810
|
-
serverName: name,
|
|
21811
|
-
toolNames
|
|
21812
|
-
});
|
|
21813
|
-
}
|
|
21814
|
-
} catch (err) {
|
|
21815
|
-
const error = err instanceof Error ? err.message : String(err);
|
|
21816
|
-
entry.state = {
|
|
21817
|
-
...state,
|
|
21818
|
-
status: "error",
|
|
21819
|
-
error
|
|
21820
|
-
};
|
|
21821
|
-
if (entry.client) {
|
|
22500
|
+
if (def.env !== void 0 && def.env !== null && typeof def.env === "object" && !Array.isArray(def.env)) {
|
|
22501
|
+
safeEnv = {};
|
|
22502
|
+
for (const [key, value] of Object.entries(def.env)) {
|
|
22503
|
+
safeEnv[key] = typeof value === "string" ? value : String(value);
|
|
22504
|
+
}
|
|
22505
|
+
}
|
|
22506
|
+
client = this.createClient();
|
|
22507
|
+
const connectedClient = client;
|
|
22508
|
+
await connectedClient.connect(safeCommand, safeArgs, safeEnv);
|
|
22509
|
+
entry.client = connectedClient;
|
|
22510
|
+
const mcpTools = await connectedClient.listTools();
|
|
22511
|
+
const tools = mcpTools.map((tool) => translateMcpTool(tool, name, connectedClient)).sort((left, right) => compareCodeUnits3(left.name, right.name));
|
|
22512
|
+
return { entry, client: connectedClient, tools };
|
|
22513
|
+
} catch (error) {
|
|
22514
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
22515
|
+
entry.state = { ...state, status: "error", error: message };
|
|
22516
|
+
if (client) {
|
|
21822
22517
|
try {
|
|
21823
|
-
await
|
|
22518
|
+
await client.disconnect();
|
|
21824
22519
|
} catch {
|
|
21825
22520
|
}
|
|
21826
|
-
entry.client = null;
|
|
21827
22521
|
}
|
|
21828
|
-
|
|
21829
|
-
|
|
21830
|
-
|
|
21831
|
-
});
|
|
21832
|
-
throw err;
|
|
22522
|
+
entry.client = null;
|
|
22523
|
+
this.emitEvent(UNIPI_EVENTS.MCP_SERVER_ERROR, { name, error: message });
|
|
22524
|
+
throw error;
|
|
21833
22525
|
}
|
|
21834
22526
|
}
|
|
21835
|
-
|
|
21836
|
-
|
|
21837
|
-
|
|
22527
|
+
assertUniqueFinalToolNames(newNames) {
|
|
22528
|
+
const existingNames = this.getActive().flatMap((state) => this.entries.get(state.name)?.toolNames ?? []);
|
|
22529
|
+
const seen = /* @__PURE__ */ new Set();
|
|
22530
|
+
const duplicates = /* @__PURE__ */ new Set();
|
|
22531
|
+
for (const name of [...existingNames, ...newNames]) {
|
|
22532
|
+
if (seen.has(name)) duplicates.add(name);
|
|
22533
|
+
seen.add(name);
|
|
22534
|
+
}
|
|
22535
|
+
if (duplicates.size > 0) {
|
|
22536
|
+
throw new Error(
|
|
22537
|
+
`Duplicate final MCP tool name(s): ${[...duplicates].sort(compareCodeUnits3).join(", ")}`
|
|
22538
|
+
);
|
|
22539
|
+
}
|
|
22540
|
+
}
|
|
22541
|
+
async failPreparedServers(prepared, error) {
|
|
22542
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
22543
|
+
await Promise.all(prepared.map(async ({ entry, client }) => {
|
|
22544
|
+
entry.state = { ...entry.state, status: "error", toolCount: 0, error: message };
|
|
22545
|
+
entry.toolNames = [];
|
|
22546
|
+
try {
|
|
22547
|
+
await client.disconnect();
|
|
22548
|
+
} catch {
|
|
22549
|
+
}
|
|
22550
|
+
entry.client = null;
|
|
22551
|
+
this.emitEvent(UNIPI_EVENTS.MCP_SERVER_ERROR, { name: entry.name, error: message });
|
|
22552
|
+
}));
|
|
22553
|
+
}
|
|
22554
|
+
/** Stop an MCP server: unregister tools, disconnect client. */
|
|
21838
22555
|
async stopServer(name) {
|
|
21839
22556
|
const entry = this.entries.get(name);
|
|
21840
22557
|
if (!entry) return;
|
|
22558
|
+
if (entry.toolNames.length > 0 && !this.canUnregisterTools) {
|
|
22559
|
+
throw new Error(
|
|
22560
|
+
"This Pi version cannot remove MCP tools at runtime; restart Pi to change the MCP tool set."
|
|
22561
|
+
);
|
|
22562
|
+
}
|
|
21841
22563
|
for (const toolName of entry.toolNames) {
|
|
21842
22564
|
this.unregisterTool(toolName);
|
|
21843
22565
|
}
|
|
@@ -21854,78 +22576,63 @@ var ServerRegistry = class {
|
|
|
21854
22576
|
}
|
|
21855
22577
|
entry.client = null;
|
|
21856
22578
|
}
|
|
21857
|
-
entry.state = {
|
|
21858
|
-
...entry.state,
|
|
21859
|
-
status: "stopped",
|
|
21860
|
-
toolCount: 0
|
|
21861
|
-
};
|
|
22579
|
+
entry.state = { ...entry.state, status: "stopped", toolCount: 0 };
|
|
21862
22580
|
entry.toolNames = [];
|
|
21863
22581
|
this.emitEvent(UNIPI_EVENTS.MCP_SERVER_STOPPED, { name });
|
|
21864
22582
|
}
|
|
21865
|
-
/**
|
|
21866
|
-
* Restart an MCP server: stop then start.
|
|
21867
|
-
*/
|
|
22583
|
+
/** Restart an MCP server: stop then start. */
|
|
21868
22584
|
async restartServer(name) {
|
|
21869
22585
|
const entry = this.entries.get(name);
|
|
21870
|
-
if (!entry) {
|
|
21871
|
-
throw new Error(`Server '${name}' not found in registry`);
|
|
21872
|
-
}
|
|
22586
|
+
if (!entry) throw new Error(`Server '${name}' not found in registry`);
|
|
21873
22587
|
const resolved = entry.resolved;
|
|
21874
22588
|
await this.stopServer(name);
|
|
21875
22589
|
await this.startServer(resolved);
|
|
21876
22590
|
}
|
|
21877
|
-
/**
|
|
21878
|
-
* Stop all running servers.
|
|
21879
|
-
*/
|
|
22591
|
+
/** Stop all running servers when runtime unregistration is supported. */
|
|
21880
22592
|
async stopAll() {
|
|
21881
22593
|
const names = Array.from(this.entries.keys());
|
|
21882
22594
|
await Promise.allSettled(names.map((name) => this.stopServer(name)));
|
|
21883
22595
|
}
|
|
21884
|
-
/**
|
|
21885
|
-
|
|
21886
|
-
|
|
22596
|
+
/** Disconnect clients during extension shutdown without claiming tools were removed. */
|
|
22597
|
+
async disconnectAll() {
|
|
22598
|
+
await Promise.allSettled([...this.entries.values()].map(async (entry) => {
|
|
22599
|
+
if (!entry.client) return;
|
|
22600
|
+
try {
|
|
22601
|
+
await entry.client.disconnect();
|
|
22602
|
+
} finally {
|
|
22603
|
+
entry.client = null;
|
|
22604
|
+
}
|
|
22605
|
+
}));
|
|
22606
|
+
}
|
|
22607
|
+
/** Get all registered server states. */
|
|
21887
22608
|
getAll() {
|
|
21888
|
-
return Array.from(this.entries.values()).map((
|
|
22609
|
+
return Array.from(this.entries.values()).map((entry) => entry.state);
|
|
21889
22610
|
}
|
|
21890
|
-
/**
|
|
21891
|
-
* Get states of running servers.
|
|
21892
|
-
*/
|
|
22611
|
+
/** Get states of running servers. */
|
|
21893
22612
|
getActive() {
|
|
21894
|
-
return this.getAll().filter((
|
|
22613
|
+
return this.getAll().filter((state) => state.status === "running");
|
|
21895
22614
|
}
|
|
21896
|
-
/**
|
|
21897
|
-
* Get states of servers in error state.
|
|
21898
|
-
*/
|
|
22615
|
+
/** Get states of servers in error state. */
|
|
21899
22616
|
getFailed() {
|
|
21900
|
-
return this.getAll().filter((
|
|
22617
|
+
return this.getAll().filter((state) => state.status === "error");
|
|
21901
22618
|
}
|
|
21902
|
-
/**
|
|
21903
|
-
* Get total number of tools across all active servers.
|
|
21904
|
-
*/
|
|
22619
|
+
/** Get total number of tools across all active servers. */
|
|
21905
22620
|
getTotalToolCount() {
|
|
21906
|
-
return this.getActive().reduce((sum,
|
|
22621
|
+
return this.getActive().reduce((sum, state) => sum + state.toolCount, 0);
|
|
21907
22622
|
}
|
|
21908
|
-
/**
|
|
21909
|
-
* Get the state of a specific server.
|
|
21910
|
-
*/
|
|
22623
|
+
/** Get the state of a specific server. */
|
|
21911
22624
|
getServerState(name) {
|
|
21912
22625
|
return this.entries.get(name)?.state ?? null;
|
|
21913
22626
|
}
|
|
21914
|
-
/**
|
|
21915
|
-
* Get the full registry entry for a server.
|
|
21916
|
-
*/
|
|
22627
|
+
/** Get the full registry entry for a server. */
|
|
21917
22628
|
getEntry(name) {
|
|
21918
22629
|
return this.entries.get(name) ?? null;
|
|
21919
22630
|
}
|
|
21920
|
-
/**
|
|
21921
|
-
* Check if a server exists in the registry.
|
|
21922
|
-
*/
|
|
22631
|
+
/** Check if a server exists in the registry. */
|
|
21923
22632
|
hasServer(name) {
|
|
21924
22633
|
return this.entries.has(name);
|
|
21925
22634
|
}
|
|
21926
|
-
/**
|
|
21927
|
-
* Get the number of registered servers.
|
|
21928
|
-
*/
|
|
22635
|
+
/** Get the number of registered servers. */
|
|
21929
22636
|
get size() {
|
|
21930
22637
|
return this.entries.size;
|
|
21931
22638
|
}
|
|
@@ -22538,12 +23245,6 @@ function renderMcpSettingsOverlay(params) {
|
|
|
22538
23245
|
addedAt: meta.servers[server.name]?.addedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
22539
23246
|
};
|
|
22540
23247
|
saveMetadata(configDir, meta);
|
|
22541
|
-
if (!newEnabled && registry3) {
|
|
22542
|
-
try {
|
|
22543
|
-
await registry3.stopServer(server.name);
|
|
22544
|
-
} catch {
|
|
22545
|
-
}
|
|
22546
|
-
}
|
|
22547
23248
|
refreshServers();
|
|
22548
23249
|
refresh();
|
|
22549
23250
|
} catch (err) {
|
|
@@ -22690,7 +23391,7 @@ function renderMcpSettingsOverlay(params) {
|
|
|
22690
23391
|
}
|
|
22691
23392
|
|
|
22692
23393
|
// packages/mcp/src/index.ts
|
|
22693
|
-
var VERSION8 = getPackageVersion(
|
|
23394
|
+
var VERSION8 = getPackageVersion(dirname18(fileURLToPath9(import.meta.url)));
|
|
22694
23395
|
var registry2 = null;
|
|
22695
23396
|
function getInfoRegistry5() {
|
|
22696
23397
|
return globalThis.__unipi_info_registry;
|
|
@@ -22700,20 +23401,18 @@ function getRegistry() {
|
|
|
22700
23401
|
}
|
|
22701
23402
|
function src_default4(pi) {
|
|
22702
23403
|
pi.on("session_start", async (_event, ctx) => {
|
|
23404
|
+
const toolApi = pi;
|
|
23405
|
+
const registerTool = typeof toolApi.registerTool === "function" ? (tool) => toolApi.registerTool(tool) : typeof toolApi.registerExternalTool === "function" ? (tool) => toolApi.registerExternalTool(tool) : () => {
|
|
23406
|
+
throw new Error("Pi does not expose a supported MCP tool registration API");
|
|
23407
|
+
};
|
|
23408
|
+
const canUnregisterTools = typeof toolApi.unregisterTool === "function" || typeof toolApi.unregisterExternalTool === "function";
|
|
23409
|
+
const unregisterTool = typeof toolApi.unregisterTool === "function" ? (toolName) => toolApi.unregisterTool(toolName) : typeof toolApi.unregisterExternalTool === "function" ? (toolName) => toolApi.unregisterExternalTool(toolName) : () => {
|
|
23410
|
+
};
|
|
22703
23411
|
registry2 = new ServerRegistry({
|
|
22704
23412
|
emitEvent: (event, payload) => emitEvent(pi, event, payload),
|
|
22705
|
-
registerTool
|
|
22706
|
-
|
|
22707
|
-
|
|
22708
|
-
} catch {
|
|
22709
|
-
}
|
|
22710
|
-
},
|
|
22711
|
-
unregisterTool: (toolName) => {
|
|
22712
|
-
try {
|
|
22713
|
-
pi.unregisterTool?.(toolName) ?? pi.unregisterExternalTool?.(toolName);
|
|
22714
|
-
} catch {
|
|
22715
|
-
}
|
|
22716
|
-
}
|
|
23413
|
+
registerTool,
|
|
23414
|
+
unregisterTool,
|
|
23415
|
+
canUnregisterTools
|
|
22717
23416
|
});
|
|
22718
23417
|
const cwd = ctx.cwd ?? process.cwd();
|
|
22719
23418
|
let servers = [];
|
|
@@ -22722,13 +23421,10 @@ function src_default4(pi) {
|
|
|
22722
23421
|
servers = result.servers;
|
|
22723
23422
|
} catch (_err) {
|
|
22724
23423
|
}
|
|
22725
|
-
|
|
22726
|
-
|
|
22727
|
-
|
|
22728
|
-
|
|
22729
|
-
}
|
|
22730
|
-
});
|
|
22731
|
-
await Promise.allSettled(startPromises);
|
|
23424
|
+
try {
|
|
23425
|
+
await registry2.startServers(servers.filter((server) => server.enabled));
|
|
23426
|
+
} catch (_err) {
|
|
23427
|
+
}
|
|
22732
23428
|
const infoRegistry2 = getInfoRegistry5();
|
|
22733
23429
|
if (infoRegistry2 && registry2) {
|
|
22734
23430
|
const reg = registry2;
|
|
@@ -22785,14 +23481,12 @@ function src_default4(pi) {
|
|
|
22785
23481
|
`unipi:${MCP_COMMANDS.STATUS}`,
|
|
22786
23482
|
`unipi:${MCP_COMMANDS.RELOAD}`
|
|
22787
23483
|
],
|
|
22788
|
-
tools: activeServers.flatMap(
|
|
22789
|
-
(s) => registry2?.getEntry(s.name)?.toolNames ?? []
|
|
22790
|
-
)
|
|
23484
|
+
tools: activeServers.flatMap((server) => registry2?.getEntry(server.name)?.toolNames ?? []).sort(compareCodeUnits3)
|
|
22791
23485
|
});
|
|
22792
23486
|
});
|
|
22793
23487
|
pi.on("session_shutdown", async (_event, _ctx) => {
|
|
22794
23488
|
if (registry2) {
|
|
22795
|
-
await registry2.
|
|
23489
|
+
await registry2.disconnectAll();
|
|
22796
23490
|
registry2 = null;
|
|
22797
23491
|
}
|
|
22798
23492
|
});
|
|
@@ -22906,38 +23600,16 @@ Updated: ${catalog.lastUpdated}`,
|
|
|
22906
23600
|
}
|
|
22907
23601
|
});
|
|
22908
23602
|
pi.registerCommand(`unipi:${MCP_COMMANDS.RELOAD}`, {
|
|
22909
|
-
description: "
|
|
23603
|
+
description: "Explain how to reload MCP servers safely",
|
|
22910
23604
|
handler: async (_args, ctx) => {
|
|
22911
|
-
|
|
22912
|
-
if (!reg) {
|
|
22913
|
-
ctx.ui.notify("MCP extension not initialized", "warning");
|
|
22914
|
-
return;
|
|
22915
|
-
}
|
|
22916
|
-
const all = reg.getAll();
|
|
22917
|
-
if (all.length === 0) {
|
|
22918
|
-
ctx.ui.notify("No MCP servers configured. Use /unipi:mcp-add to add one.", "info");
|
|
22919
|
-
return;
|
|
22920
|
-
}
|
|
22921
|
-
ctx.ui.notify(`Reloading ${all.length} MCP server(s)...`, "info");
|
|
22922
|
-
let restarted = 0;
|
|
22923
|
-
let failed = 0;
|
|
22924
|
-
for (const state of all) {
|
|
22925
|
-
try {
|
|
22926
|
-
await reg.restartServer(state.name);
|
|
22927
|
-
restarted++;
|
|
22928
|
-
} catch (_err) {
|
|
22929
|
-
failed++;
|
|
22930
|
-
}
|
|
22931
|
-
}
|
|
22932
|
-
const msg = failed > 0 ? `Reloaded: ${restarted} ok, ${failed} failed` : `Reloaded ${restarted} MCP server(s) successfully`;
|
|
22933
|
-
ctx.ui.notify(msg, failed > 0 ? "warning" : "info");
|
|
23605
|
+
ctx.ui.notify("Restart Pi to reload MCP servers and tool schemas safely.", "info");
|
|
22934
23606
|
}
|
|
22935
23607
|
});
|
|
22936
23608
|
}
|
|
22937
23609
|
|
|
22938
23610
|
// packages/notify/index.ts
|
|
22939
23611
|
init_core();
|
|
22940
|
-
import { dirname as
|
|
23612
|
+
import { dirname as dirname21 } from "node:path";
|
|
22941
23613
|
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
22942
23614
|
|
|
22943
23615
|
// packages/notify/tools.ts
|
|
@@ -22946,12 +23618,12 @@ import { Type as Type6 } from "typebox";
|
|
|
22946
23618
|
|
|
22947
23619
|
// packages/notify/settings.ts
|
|
22948
23620
|
init_core();
|
|
22949
|
-
import { readFileSync as readFileSync19, writeFileSync as
|
|
22950
|
-
import { dirname as
|
|
22951
|
-
import { homedir as
|
|
23621
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync19, mkdirSync as mkdirSync17, existsSync as existsSync24 } from "fs";
|
|
23622
|
+
import { dirname as dirname19, join as join24 } from "path";
|
|
23623
|
+
import { homedir as homedir19 } from "os";
|
|
22952
23624
|
function resolveConfigPath() {
|
|
22953
|
-
const base = NOTIFY_DIRS.CONFIG.replace("~",
|
|
22954
|
-
return
|
|
23625
|
+
const base = NOTIFY_DIRS.CONFIG.replace("~", homedir19());
|
|
23626
|
+
return join24(base, "config.json");
|
|
22955
23627
|
}
|
|
22956
23628
|
var DEFAULT_CONFIG4 = {
|
|
22957
23629
|
defaultPlatforms: ["native"],
|
|
@@ -23001,9 +23673,9 @@ function loadConfig2() {
|
|
|
23001
23673
|
}
|
|
23002
23674
|
function saveConfig2(config) {
|
|
23003
23675
|
const configPath = resolveConfigPath();
|
|
23004
|
-
const dir =
|
|
23005
|
-
|
|
23006
|
-
|
|
23676
|
+
const dir = dirname19(configPath);
|
|
23677
|
+
mkdirSync17(dir, { recursive: true });
|
|
23678
|
+
writeFileSync19(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
23007
23679
|
}
|
|
23008
23680
|
function updateConfig(partial) {
|
|
23009
23681
|
const current = loadConfig2();
|
|
@@ -23061,19 +23733,19 @@ function mergeWithDefaults(loaded) {
|
|
|
23061
23733
|
init_core();
|
|
23062
23734
|
|
|
23063
23735
|
// packages/notify/ntfy-config.ts
|
|
23064
|
-
import { readFileSync as readFileSync20, writeFileSync as
|
|
23065
|
-
import { dirname as
|
|
23066
|
-
import { homedir as
|
|
23736
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync20, mkdirSync as mkdirSync18, existsSync as existsSync25 } from "fs";
|
|
23737
|
+
import { dirname as dirname20, join as join25 } from "path";
|
|
23738
|
+
import { homedir as homedir20 } from "os";
|
|
23067
23739
|
var DEFAULT_NTFY_CONFIG = {
|
|
23068
23740
|
enabled: false,
|
|
23069
23741
|
serverUrl: "https://ntfy.sh",
|
|
23070
23742
|
priority: 3
|
|
23071
23743
|
};
|
|
23072
23744
|
function getGlobalNtfyPath() {
|
|
23073
|
-
return
|
|
23745
|
+
return join25(homedir20(), ".unipi", "config", "notify", "ntfy.json");
|
|
23074
23746
|
}
|
|
23075
23747
|
function getProjectNtfyPath(cwd) {
|
|
23076
|
-
return
|
|
23748
|
+
return join25(cwd, ".unipi", "config", "notify", "ntfy.json");
|
|
23077
23749
|
}
|
|
23078
23750
|
function readNtfyJson(filePath) {
|
|
23079
23751
|
try {
|
|
@@ -23102,9 +23774,9 @@ function loadNtfyConfig(cwd) {
|
|
|
23102
23774
|
}
|
|
23103
23775
|
function saveNtfyConfig(scope, cwd, config) {
|
|
23104
23776
|
const filePath = scope === "project" ? getProjectNtfyPath(cwd) : getGlobalNtfyPath();
|
|
23105
|
-
const dir =
|
|
23106
|
-
|
|
23107
|
-
|
|
23777
|
+
const dir = dirname20(filePath);
|
|
23778
|
+
mkdirSync18(dir, { recursive: true });
|
|
23779
|
+
writeFileSync20(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
23108
23780
|
}
|
|
23109
23781
|
function getNtfyConfigScope(cwd) {
|
|
23110
23782
|
if (existsSync25(getProjectNtfyPath(cwd))) {
|
|
@@ -23120,8 +23792,8 @@ function migrateFromLegacyConfig() {
|
|
|
23120
23792
|
if (existsSync25(globalNtfyPath)) {
|
|
23121
23793
|
return;
|
|
23122
23794
|
}
|
|
23123
|
-
const legacyConfigPath =
|
|
23124
|
-
|
|
23795
|
+
const legacyConfigPath = join25(
|
|
23796
|
+
homedir20(),
|
|
23125
23797
|
".unipi",
|
|
23126
23798
|
"config",
|
|
23127
23799
|
"notify",
|
|
@@ -23142,9 +23814,9 @@ function migrateFromLegacyConfig() {
|
|
|
23142
23814
|
token: ntfySection.token,
|
|
23143
23815
|
priority: ntfySection.priority ?? 3
|
|
23144
23816
|
};
|
|
23145
|
-
const dir =
|
|
23146
|
-
|
|
23147
|
-
|
|
23817
|
+
const dir = dirname20(globalNtfyPath);
|
|
23818
|
+
mkdirSync18(dir, { recursive: true });
|
|
23819
|
+
writeFileSync20(
|
|
23148
23820
|
globalNtfyPath,
|
|
23149
23821
|
JSON.stringify(migratedConfig, null, 2) + "\n",
|
|
23150
23822
|
"utf-8"
|
|
@@ -23158,8 +23830,8 @@ import notifier from "node-notifier";
|
|
|
23158
23830
|
|
|
23159
23831
|
// packages/notify/platforms/focus-win.ts
|
|
23160
23832
|
import { execFile } from "child_process";
|
|
23161
|
-
import { writeFileSync as
|
|
23162
|
-
import { join as
|
|
23833
|
+
import { writeFileSync as writeFileSync21, rmSync as rmSync2, mkdtempSync } from "fs";
|
|
23834
|
+
import { join as join26 } from "path";
|
|
23163
23835
|
import { tmpdir as tmpdir2 } from "os";
|
|
23164
23836
|
var POWERCHECK_SCRIPT = `
|
|
23165
23837
|
param($targetPid)
|
|
@@ -23195,10 +23867,10 @@ async function isWindowFocusedOnWindows() {
|
|
|
23195
23867
|
}
|
|
23196
23868
|
let tmpDir = null;
|
|
23197
23869
|
try {
|
|
23198
|
-
tmpDir = mkdtempSync(
|
|
23199
|
-
const scriptPath =
|
|
23200
|
-
|
|
23201
|
-
const stdout = await new Promise((
|
|
23870
|
+
tmpDir = mkdtempSync(join26(tmpdir2(), "pi-focus-"));
|
|
23871
|
+
const scriptPath = join26(tmpDir, "check.ps1");
|
|
23872
|
+
writeFileSync21(scriptPath, POWERCHECK_SCRIPT, "utf-8");
|
|
23873
|
+
const stdout = await new Promise((resolve14, reject) => {
|
|
23202
23874
|
execFile(
|
|
23203
23875
|
"powershell.exe",
|
|
23204
23876
|
[
|
|
@@ -23213,7 +23885,7 @@ async function isWindowFocusedOnWindows() {
|
|
|
23213
23885
|
{ timeout: 5e3, encoding: "utf-8" },
|
|
23214
23886
|
(err, out) => {
|
|
23215
23887
|
if (err) reject(err);
|
|
23216
|
-
else
|
|
23888
|
+
else resolve14(out);
|
|
23217
23889
|
}
|
|
23218
23890
|
);
|
|
23219
23891
|
});
|
|
@@ -23255,7 +23927,7 @@ async function sendNativeNotification(title, message, options) {
|
|
|
23255
23927
|
if (options?.suppressWhenFocused && await isWindowFocused()) {
|
|
23256
23928
|
throw new SuppressedError2();
|
|
23257
23929
|
}
|
|
23258
|
-
return new Promise((
|
|
23930
|
+
return new Promise((resolve14, reject) => {
|
|
23259
23931
|
notifier.notify(
|
|
23260
23932
|
{
|
|
23261
23933
|
title,
|
|
@@ -23270,7 +23942,7 @@ async function sendNativeNotification(title, message, options) {
|
|
|
23270
23942
|
)
|
|
23271
23943
|
);
|
|
23272
23944
|
} else {
|
|
23273
|
-
|
|
23945
|
+
resolve14();
|
|
23274
23946
|
}
|
|
23275
23947
|
}
|
|
23276
23948
|
);
|
|
@@ -26006,7 +26678,7 @@ ${results.join("\n")}`, "info");
|
|
|
26006
26678
|
}
|
|
26007
26679
|
|
|
26008
26680
|
// packages/notify/index.ts
|
|
26009
|
-
var VERSION9 = getPackageVersion(
|
|
26681
|
+
var VERSION9 = getPackageVersion(dirname21(fileURLToPath10(import.meta.url)));
|
|
26010
26682
|
function notify_default(pi) {
|
|
26011
26683
|
registerNotifyTools(pi);
|
|
26012
26684
|
registerNotifyCommands(pi);
|
|
@@ -26035,6 +26707,9 @@ init_core();
|
|
|
26035
26707
|
init_core();
|
|
26036
26708
|
import * as fs14 from "node:fs";
|
|
26037
26709
|
import * as path13 from "node:path";
|
|
26710
|
+
import {
|
|
26711
|
+
buildSessionContext as buildSessionContext3
|
|
26712
|
+
} from "@earendil-works/pi-coding-agent";
|
|
26038
26713
|
|
|
26039
26714
|
// packages/milestone/milestone.ts
|
|
26040
26715
|
init_core();
|
|
@@ -26180,7 +26855,7 @@ function getProgressSummary(filePath) {
|
|
|
26180
26855
|
}
|
|
26181
26856
|
|
|
26182
26857
|
// packages/milestone/hooks.ts
|
|
26183
|
-
var
|
|
26858
|
+
var MILESTONE_SNAPSHOT_TYPE = "unipi-milestone-snapshot";
|
|
26184
26859
|
function formatMilestoneContext(filePath) {
|
|
26185
26860
|
const summary = getProgressSummary(filePath);
|
|
26186
26861
|
if (summary.totalItems === 0) return null;
|
|
@@ -26193,16 +26868,67 @@ function formatMilestoneContext(filePath) {
|
|
|
26193
26868
|
focus
|
|
26194
26869
|
].filter(Boolean).join("\n");
|
|
26195
26870
|
}
|
|
26871
|
+
function formatMilestoneSnapshot(workspace, context) {
|
|
26872
|
+
return [
|
|
26873
|
+
"# UniPi Milestone Snapshot",
|
|
26874
|
+
"This snapshot supersedes all prior UniPi milestone snapshots; use only this snapshot for milestone status.",
|
|
26875
|
+
`Workspace: ${workspace}`,
|
|
26876
|
+
`Status: ${context ? "active" : "inactive"}`,
|
|
26877
|
+
context ?? "No milestones are active for this workspace."
|
|
26878
|
+
].join("\n\n");
|
|
26879
|
+
}
|
|
26880
|
+
function latestEffectiveSnapshot(branch) {
|
|
26881
|
+
const messages = buildSessionContext3(branch).messages;
|
|
26882
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
26883
|
+
const message = messages[index];
|
|
26884
|
+
if (message.role === "custom" && message.customType === MILESTONE_SNAPSHOT_TYPE) {
|
|
26885
|
+
return {
|
|
26886
|
+
content: message.content,
|
|
26887
|
+
details: message.details
|
|
26888
|
+
};
|
|
26889
|
+
}
|
|
26890
|
+
}
|
|
26891
|
+
return void 0;
|
|
26892
|
+
}
|
|
26893
|
+
function latestHistoricalSnapshot(branch) {
|
|
26894
|
+
for (let index = branch.length - 1; index >= 0; index--) {
|
|
26895
|
+
const entry = branch[index];
|
|
26896
|
+
if (entry.type === "custom_message" && entry.customType === MILESTONE_SNAPSHOT_TYPE) {
|
|
26897
|
+
return {
|
|
26898
|
+
content: entry.content,
|
|
26899
|
+
details: entry.details
|
|
26900
|
+
};
|
|
26901
|
+
}
|
|
26902
|
+
}
|
|
26903
|
+
return void 0;
|
|
26904
|
+
}
|
|
26905
|
+
function isActiveSnapshot2(snapshot) {
|
|
26906
|
+
if (typeof snapshot.details?.active === "boolean") return snapshot.details.active;
|
|
26907
|
+
return typeof snapshot.content === "string" && snapshot.content.includes("Status: active");
|
|
26908
|
+
}
|
|
26196
26909
|
function registerSessionStartHook(pi) {
|
|
26197
|
-
pi.on("before_agent_start", (
|
|
26198
|
-
|
|
26199
|
-
const
|
|
26200
|
-
const milestonesPath = path13.join(cwd, MILESTONE_DIRS.MILESTONES);
|
|
26910
|
+
pi.on("before_agent_start", (_event, ctx) => {
|
|
26911
|
+
const workspace = ctx.cwd;
|
|
26912
|
+
const milestonesPath = path13.join(workspace, MILESTONE_DIRS.MILESTONES);
|
|
26201
26913
|
const context = formatMilestoneContext(milestonesPath);
|
|
26202
|
-
|
|
26203
|
-
const
|
|
26914
|
+
const branch = ctx.sessionManager.getBranch();
|
|
26915
|
+
const latest = latestEffectiveSnapshot(branch);
|
|
26916
|
+
const historical = latestHistoricalSnapshot(branch);
|
|
26917
|
+
if (!context && !latest && !historical) return void 0;
|
|
26918
|
+
const prior = latest ?? historical;
|
|
26919
|
+
if (!context && prior && !isActiveSnapshot2(prior)) return void 0;
|
|
26920
|
+
const content = formatMilestoneSnapshot(workspace, context);
|
|
26921
|
+
if (latest?.content === content) return void 0;
|
|
26204
26922
|
return {
|
|
26205
|
-
|
|
26923
|
+
message: {
|
|
26924
|
+
customType: MILESTONE_SNAPSHOT_TYPE,
|
|
26925
|
+
content,
|
|
26926
|
+
display: false,
|
|
26927
|
+
details: {
|
|
26928
|
+
active: context !== null,
|
|
26929
|
+
workspace
|
|
26930
|
+
}
|
|
26931
|
+
}
|
|
26206
26932
|
};
|
|
26207
26933
|
});
|
|
26208
26934
|
}
|
|
@@ -26258,14 +26984,16 @@ function scanModifiedDocs(dirs, since) {
|
|
|
26258
26984
|
}
|
|
26259
26985
|
function registerSessionEndHook(pi) {
|
|
26260
26986
|
const baselineSnapshots = /* @__PURE__ */ new Map();
|
|
26261
|
-
|
|
26987
|
+
let sessionStartMs = 0;
|
|
26988
|
+
let sessionWorkspace = null;
|
|
26989
|
+
pi.on("session_start", (_event, ctx) => {
|
|
26262
26990
|
sessionStartMs = Date.now();
|
|
26991
|
+
sessionWorkspace = ctx.cwd;
|
|
26263
26992
|
baselineSnapshots.clear();
|
|
26264
|
-
const cwd = process.cwd();
|
|
26265
26993
|
const scanDirs = [
|
|
26266
|
-
path13.join(
|
|
26267
|
-
path13.join(
|
|
26268
|
-
path13.join(
|
|
26994
|
+
path13.join(sessionWorkspace, ".unipi/docs/specs"),
|
|
26995
|
+
path13.join(sessionWorkspace, ".unipi/docs/plans"),
|
|
26996
|
+
path13.join(sessionWorkspace, ".unipi/docs/quick-work")
|
|
26269
26997
|
];
|
|
26270
26998
|
for (const dir of scanDirs) {
|
|
26271
26999
|
if (!fs14.existsSync(dir)) continue;
|
|
@@ -26279,13 +27007,13 @@ function registerSessionEndHook(pi) {
|
|
|
26279
27007
|
}
|
|
26280
27008
|
});
|
|
26281
27009
|
const syncModifiedDocs = () => {
|
|
26282
|
-
|
|
26283
|
-
const milestonesPath = path13.join(
|
|
27010
|
+
if (!sessionWorkspace) return;
|
|
27011
|
+
const milestonesPath = path13.join(sessionWorkspace, MILESTONE_DIRS.MILESTONES);
|
|
26284
27012
|
if (!fs14.existsSync(milestonesPath)) return;
|
|
26285
27013
|
const scanDirs = [
|
|
26286
|
-
path13.join(
|
|
26287
|
-
path13.join(
|
|
26288
|
-
path13.join(
|
|
27014
|
+
path13.join(sessionWorkspace, ".unipi/docs/specs"),
|
|
27015
|
+
path13.join(sessionWorkspace, ".unipi/docs/plans"),
|
|
27016
|
+
path13.join(sessionWorkspace, ".unipi/docs/quick-work")
|
|
26289
27017
|
];
|
|
26290
27018
|
const modifiedFiles = scanModifiedDocs(scanDirs, sessionStartMs);
|
|
26291
27019
|
for (const filePath of modifiedFiles) {
|
|
@@ -26303,13 +27031,13 @@ function registerSessionEndHook(pi) {
|
|
|
26303
27031
|
|
|
26304
27032
|
// packages/milestone/commands.ts
|
|
26305
27033
|
init_core();
|
|
26306
|
-
import { dirname as
|
|
27034
|
+
import { dirname as dirname22, join as join28 } from "node:path";
|
|
26307
27035
|
import { fileURLToPath as fileURLToPath11 } from "node:url";
|
|
26308
27036
|
import { readFileSync as readFileSync21 } from "node:fs";
|
|
26309
|
-
var SKILLS_DIR =
|
|
27037
|
+
var SKILLS_DIR = join28(dirname22(fileURLToPath11(import.meta.url)), "skills");
|
|
26310
27038
|
function loadSkill(skillName) {
|
|
26311
27039
|
try {
|
|
26312
|
-
return readFileSync21(
|
|
27040
|
+
return readFileSync21(join28(SKILLS_DIR, skillName, "SKILL.md"), "utf-8");
|
|
26313
27041
|
} catch {
|
|
26314
27042
|
return "";
|
|
26315
27043
|
}
|
|
@@ -26362,7 +27090,7 @@ ${skillContent}
|
|
|
26362
27090
|
},
|
|
26363
27091
|
getArgumentCompletions: () => {
|
|
26364
27092
|
const cwd = process.cwd();
|
|
26365
|
-
const milestonesPath =
|
|
27093
|
+
const milestonesPath = join28(cwd, MILESTONE_DIRS.MILESTONES);
|
|
26366
27094
|
const doc = parseMilestones(milestonesPath);
|
|
26367
27095
|
const suggestions = doc.phases.map((phase) => ({
|
|
26368
27096
|
value: phase.name,
|
|
@@ -26533,9 +27261,9 @@ var KanboardServer = class {
|
|
|
26533
27261
|
}
|
|
26534
27262
|
/** Bind server to a port */
|
|
26535
27263
|
listen(port) {
|
|
26536
|
-
return new Promise((
|
|
27264
|
+
return new Promise((resolve14, reject) => {
|
|
26537
27265
|
this.server.once("error", reject);
|
|
26538
|
-
this.server.listen(port, () =>
|
|
27266
|
+
this.server.listen(port, () => resolve14());
|
|
26539
27267
|
});
|
|
26540
27268
|
}
|
|
26541
27269
|
/** Handle incoming HTTP request */
|
|
@@ -26818,6 +27546,7 @@ var COMMAND_REGISTRY = {
|
|
|
26818
27546
|
"unipi:worktree-list": "workflow",
|
|
26819
27547
|
"unipi:worktree-merge": "workflow",
|
|
26820
27548
|
"unipi:consultant": "workflow",
|
|
27549
|
+
"unipi:prefix-cache": "utility",
|
|
26821
27550
|
"unipi:quick-work": "workflow",
|
|
26822
27551
|
"unipi:gather-context": "workflow",
|
|
26823
27552
|
"unipi:document": "workflow",
|
|
@@ -26925,6 +27654,7 @@ var COMMAND_DESCRIPTIONS = {
|
|
|
26925
27654
|
"unipi:worktree-list": "List all unipi worktrees",
|
|
26926
27655
|
"unipi:worktree-merge": "Merge worktree branches back to main",
|
|
26927
27656
|
"unipi:consultant": "Expert consultation \u2014 advisory analysis",
|
|
27657
|
+
"unipi:prefix-cache": "Show privacy-safe provider prefix-cache diagnostics",
|
|
26928
27658
|
"unipi:quick-work": "Fast single-task execution \u2014 one shot",
|
|
26929
27659
|
"unipi:gather-context": "Research codebase \u2014 surface patterns",
|
|
26930
27660
|
"unipi:document": "Generate documentation \u2014 README, API docs",
|
|
@@ -27504,11 +28234,11 @@ function markAutoCompactionError(state, nowMs) {
|
|
|
27504
28234
|
}
|
|
27505
28235
|
|
|
27506
28236
|
// packages/compactor/src/session/db.ts
|
|
27507
|
-
import { createHash as
|
|
28237
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
27508
28238
|
import { execFileSync } from "node:child_process";
|
|
27509
|
-
import { existsSync as existsSync32, mkdirSync as
|
|
27510
|
-
import { homedir as
|
|
27511
|
-
import { dirname as
|
|
28239
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync22 } from "node:fs";
|
|
28240
|
+
import { homedir as homedir23 } from "node:os";
|
|
28241
|
+
import { dirname as dirname25, join as join35 } from "node:path";
|
|
27512
28242
|
function getWorktreeSuffix() {
|
|
27513
28243
|
const envSuffix = process.env.COMPACTOR_SESSION_SUFFIX;
|
|
27514
28244
|
if (envSuffix !== void 0) {
|
|
@@ -27522,7 +28252,7 @@ function getWorktreeSuffix() {
|
|
|
27522
28252
|
{ encoding: "utf-8", timeout: 2e3, stdio: ["ignore", "pipe", "ignore"] }
|
|
27523
28253
|
).split(/\r?\n/).find((l) => l.startsWith("worktree "))?.replace("worktree ", "")?.trim();
|
|
27524
28254
|
if (mainWorktree && cwd !== mainWorktree) {
|
|
27525
|
-
const suffix =
|
|
28255
|
+
const suffix = createHash3("sha256").update(cwd).digest("hex").slice(0, 8);
|
|
27526
28256
|
return `__${suffix}`;
|
|
27527
28257
|
}
|
|
27528
28258
|
} catch {
|
|
@@ -27530,7 +28260,7 @@ function getWorktreeSuffix() {
|
|
|
27530
28260
|
return "";
|
|
27531
28261
|
}
|
|
27532
28262
|
function defaultDBPath(name) {
|
|
27533
|
-
return
|
|
28263
|
+
return join35(homedir23(), ".unipi", "db", "compactor", `${name}.db`);
|
|
27534
28264
|
}
|
|
27535
28265
|
var sqliteLib = null;
|
|
27536
28266
|
async function getSQLite() {
|
|
@@ -27553,8 +28283,8 @@ var SessionDB = class {
|
|
|
27553
28283
|
this.dbPath = opts?.dbPath ?? defaultDBPath("session");
|
|
27554
28284
|
}
|
|
27555
28285
|
async init() {
|
|
27556
|
-
const dir =
|
|
27557
|
-
if (!existsSync32(dir))
|
|
28286
|
+
const dir = dirname25(this.dbPath);
|
|
28287
|
+
if (!existsSync32(dir)) mkdirSync22(dir, { recursive: true });
|
|
27558
28288
|
const sqlite = await getSQLite();
|
|
27559
28289
|
const Database2 = sqlite.Database ?? sqlite.default?.Database ?? sqlite.default ?? sqlite;
|
|
27560
28290
|
this.db = new Database2(this.dbPath);
|
|
@@ -27664,7 +28394,7 @@ var SessionDB = class {
|
|
|
27664
28394
|
}
|
|
27665
28395
|
insertEvent(sessionId, event, sourceHook = "PostToolUse") {
|
|
27666
28396
|
if (!this.stmts) return;
|
|
27667
|
-
const dataHash =
|
|
28397
|
+
const dataHash = createHash3("sha256").update(event.data).digest("hex").slice(0, 16).toUpperCase();
|
|
27668
28398
|
const projectDir = String(event.project_dir ?? "").trim();
|
|
27669
28399
|
const attributionSource = String(event.attribution_source ?? "unknown");
|
|
27670
28400
|
const rawConfidence = Number(event.attribution_confidence ?? 0);
|
|
@@ -27947,8 +28677,8 @@ async function buildResumeContextMessage(db, sessionId) {
|
|
|
27947
28677
|
|
|
27948
28678
|
// packages/compactor/src/executor/executor.ts
|
|
27949
28679
|
import { spawn as spawn3, execSync, execFileSync as execFileSync2 } from "node:child_process";
|
|
27950
|
-
import { mkdtempSync as mkdtempSync2, writeFileSync as
|
|
27951
|
-
import { join as
|
|
28680
|
+
import { mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync26, rmSync as rmSync3 } from "node:fs";
|
|
28681
|
+
import { join as join36, resolve as resolve11 } from "node:path";
|
|
27952
28682
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
27953
28683
|
|
|
27954
28684
|
// packages/compactor/src/executor/runtime.ts
|
|
@@ -28040,7 +28770,7 @@ var OS_TMPDIR = (() => {
|
|
|
28040
28770
|
process.platform === "darwin" ? ["DARWIN_USER_TEMP_DIR"] : ["-u", "-d"],
|
|
28041
28771
|
{ env: { ...process.env, TMPDIR: void 0 }, encoding: "utf-8" }
|
|
28042
28772
|
).trim();
|
|
28043
|
-
const dir = process.platform === "darwin" ? result :
|
|
28773
|
+
const dir = process.platform === "darwin" ? result : resolve11(result, "..");
|
|
28044
28774
|
if (dir && dir !== process.cwd()) return dir;
|
|
28045
28775
|
} catch {
|
|
28046
28776
|
}
|
|
@@ -28129,7 +28859,7 @@ var PolyglotExecutor = class {
|
|
|
28129
28859
|
}
|
|
28130
28860
|
async execute(opts) {
|
|
28131
28861
|
const { language, code, timeout = 3e4, background = false } = opts;
|
|
28132
|
-
const tmpDir = mkdtempSync2(
|
|
28862
|
+
const tmpDir = mkdtempSync2(join36(OS_TMPDIR, ".compactor-"));
|
|
28133
28863
|
try {
|
|
28134
28864
|
const filePath = this.#writeScript(tmpDir, code, language);
|
|
28135
28865
|
const cmd = buildCommand(this.#runtimes, language, filePath);
|
|
@@ -28161,7 +28891,7 @@ var PolyglotExecutor = class {
|
|
|
28161
28891
|
async executeFile(opts) {
|
|
28162
28892
|
const { language, path: path28, timeout = 3e4, background = false } = opts;
|
|
28163
28893
|
const cmd = buildCommand(this.#runtimes, language, path28);
|
|
28164
|
-
const cwd = language === "shell" ? this.#projectRoot :
|
|
28894
|
+
const cwd = language === "shell" ? this.#projectRoot : resolve11(path28, "..");
|
|
28165
28895
|
return this.#spawn(cmd, cwd, "", timeout, background);
|
|
28166
28896
|
}
|
|
28167
28897
|
#writeScript(tmpDir, code, language) {
|
|
@@ -28179,12 +28909,12 @@ var PolyglotExecutor = class {
|
|
|
28179
28909
|
elixir: "ex"
|
|
28180
28910
|
};
|
|
28181
28911
|
const ext = extensions[language];
|
|
28182
|
-
const filePath =
|
|
28183
|
-
|
|
28912
|
+
const filePath = join36(tmpDir, `script.${ext}`);
|
|
28913
|
+
writeFileSync26(filePath, code, "utf-8");
|
|
28184
28914
|
return filePath;
|
|
28185
28915
|
}
|
|
28186
28916
|
async #spawn(cmd, cwd, tmpDir, timeout, background) {
|
|
28187
|
-
return new Promise((
|
|
28917
|
+
return new Promise((resolve14) => {
|
|
28188
28918
|
const proc = spawn3(cmd[0], cmd.slice(1), {
|
|
28189
28919
|
cwd,
|
|
28190
28920
|
env: sanitizeEnv(),
|
|
@@ -28220,14 +28950,14 @@ var PolyglotExecutor = class {
|
|
|
28220
28950
|
}, timeout);
|
|
28221
28951
|
proc.on("error", (err) => {
|
|
28222
28952
|
if (timer) clearTimeout(timer);
|
|
28223
|
-
|
|
28953
|
+
resolve14({ stdout, stderr: err.message, exitCode: 1, timedOut: false });
|
|
28224
28954
|
});
|
|
28225
28955
|
proc.on("close", (code) => {
|
|
28226
28956
|
if (timer) clearTimeout(timer);
|
|
28227
28957
|
if (background && proc.pid) {
|
|
28228
28958
|
this.#backgroundedPids.add(proc.pid);
|
|
28229
28959
|
}
|
|
28230
|
-
|
|
28960
|
+
resolve14({
|
|
28231
28961
|
stdout: stdout.slice(0, this.#hardCapBytes),
|
|
28232
28962
|
stderr: stderr.slice(0, this.#hardCapBytes),
|
|
28233
28963
|
exitCode: code ?? 0,
|
|
@@ -28238,7 +28968,7 @@ var PolyglotExecutor = class {
|
|
|
28238
28968
|
});
|
|
28239
28969
|
}
|
|
28240
28970
|
async #compileAndRun(filePath, tmpDir, timeout) {
|
|
28241
|
-
const binPath =
|
|
28971
|
+
const binPath = join36(tmpDir, "script");
|
|
28242
28972
|
try {
|
|
28243
28973
|
execSync(`rustc "${filePath}" -o "${binPath}"`, {
|
|
28244
28974
|
cwd: tmpDir,
|
|
@@ -28265,7 +28995,7 @@ init_core();
|
|
|
28265
28995
|
init_hooks();
|
|
28266
28996
|
|
|
28267
28997
|
// packages/compactor/src/compaction/search-entries.ts
|
|
28268
|
-
import { createHash as
|
|
28998
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
28269
28999
|
function tokenize(text) {
|
|
28270
29000
|
return text.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/).filter((w) => w.length > 1);
|
|
28271
29001
|
}
|
|
@@ -28309,7 +29039,7 @@ function searchEntries(blocks, query, opts) {
|
|
|
28309
29039
|
kind: b.kind
|
|
28310
29040
|
}));
|
|
28311
29041
|
const hashSource = docs.length > 0 ? `${docs.length}:${docs[0].text.slice(0, 80)}:${docs[docs.length - 1].text.slice(-80)}` : "empty";
|
|
28312
|
-
const currentHash =
|
|
29042
|
+
const currentHash = createHash5("sha256").update(hashSource).digest("hex");
|
|
28313
29043
|
let index;
|
|
28314
29044
|
let docCount;
|
|
28315
29045
|
let avgDocLen;
|
|
@@ -28348,15 +29078,29 @@ function searchEntries(blocks, query, opts) {
|
|
|
28348
29078
|
}
|
|
28349
29079
|
|
|
28350
29080
|
// packages/compactor/src/tools/vcc-recall.ts
|
|
29081
|
+
var MAX_RECALL_RESULTS = 50;
|
|
29082
|
+
var MAX_EXPANDED_HIT_BYTES = 16 * 1024;
|
|
29083
|
+
function truncateExpandedHit(text) {
|
|
29084
|
+
const bytes = Buffer.from(text, "utf8");
|
|
29085
|
+
if (bytes.byteLength <= MAX_EXPANDED_HIT_BYTES) return text;
|
|
29086
|
+
const omitted = bytes.byteLength - MAX_EXPANDED_HIT_BYTES;
|
|
29087
|
+
const visible = bytes.subarray(0, MAX_EXPANDED_HIT_BYTES).toString("utf8").replace(/\uFFFD+$/u, "");
|
|
29088
|
+
return `${visible}
|
|
29089
|
+
\u2026 ${omitted} bytes omitted from this hit; narrow the query to inspect more specific context \u2026`;
|
|
29090
|
+
}
|
|
28351
29091
|
function vccRecall(blocks, input) {
|
|
28352
|
-
const { query, mode = "bm25",
|
|
29092
|
+
const { query, mode = "bm25", expand = false } = input;
|
|
29093
|
+
const requestedLimit = Number.isFinite(input.limit) ? Math.floor(input.limit) : 10;
|
|
29094
|
+
const requestedOffset = Number.isFinite(input.offset) ? Math.floor(input.offset) : 0;
|
|
29095
|
+
const limit = Math.min(MAX_RECALL_RESULTS, Math.max(1, requestedLimit));
|
|
29096
|
+
const offset = Math.max(0, requestedOffset);
|
|
28353
29097
|
let hits = [];
|
|
28354
29098
|
if (mode === "bm25") {
|
|
28355
29099
|
const results = searchEntries(blocks, query, { limit: limit + offset, offset: 0 });
|
|
28356
29100
|
hits = results.map((r, i) => ({
|
|
28357
29101
|
index: r.docId,
|
|
28358
29102
|
score: r.score,
|
|
28359
|
-
text: expand ? r.text : r.text.slice(0, 200),
|
|
29103
|
+
text: expand ? truncateExpandedHit(r.text) : r.text.slice(0, 200),
|
|
28360
29104
|
kind: r.kind
|
|
28361
29105
|
}));
|
|
28362
29106
|
} else {
|
|
@@ -28368,7 +29112,7 @@ function vccRecall(blocks, input) {
|
|
|
28368
29112
|
hits.push({
|
|
28369
29113
|
index: i,
|
|
28370
29114
|
score: 1,
|
|
28371
|
-
text: expand ? text : text.slice(0, 200),
|
|
29115
|
+
text: expand ? truncateExpandedHit(text) : text.slice(0, 200),
|
|
28372
29116
|
kind: b.kind
|
|
28373
29117
|
});
|
|
28374
29118
|
}
|
|
@@ -28850,7 +29594,11 @@ var RecallParams = Type7.Object({
|
|
|
28850
29594
|
mode: Type7.Optional(Type7.Union([Type7.Literal("bm25"), Type7.Literal("regex")], {
|
|
28851
29595
|
description: "Search mode: bm25 (default) or regex fallback"
|
|
28852
29596
|
})),
|
|
28853
|
-
limit: Type7.Optional(Type7.Number({
|
|
29597
|
+
limit: Type7.Optional(Type7.Number({
|
|
29598
|
+
description: `Max results to return (default 10, hard cap ${MAX_RECALL_RESULTS})`,
|
|
29599
|
+
minimum: 1,
|
|
29600
|
+
maximum: MAX_RECALL_RESULTS
|
|
29601
|
+
})),
|
|
28854
29602
|
offset: Type7.Optional(Type7.Number({ description: "Pagination offset", minimum: 0 })),
|
|
28855
29603
|
expand: Type7.Optional(Type7.Boolean({ description: "Return full message content for hits" }))
|
|
28856
29604
|
});
|
|
@@ -29275,13 +30023,13 @@ function compactorExtension(pi) {
|
|
|
29275
30023
|
debug("before_agent_start", { sessionId: currentSessionId, configDebug: config.debug });
|
|
29276
30024
|
try {
|
|
29277
30025
|
const { existsSync: existsSync48 } = await import("node:fs");
|
|
29278
|
-
const { join:
|
|
30026
|
+
const { join: join50 } = await import("node:path");
|
|
29279
30027
|
const strategies = [
|
|
29280
30028
|
{ key: "commits", config: config.commits }
|
|
29281
30029
|
];
|
|
29282
30030
|
for (const { key, config: strat } of strategies) {
|
|
29283
30031
|
if (strat.autoDetect === "git") {
|
|
29284
|
-
const gitDir =
|
|
30032
|
+
const gitDir = join50(cwd, ".git");
|
|
29285
30033
|
if (!existsSync48(gitDir)) {
|
|
29286
30034
|
debug("autoDetect_disable", { strategy: key, reason: "no .git dir" });
|
|
29287
30035
|
strat.enabled = false;
|
|
@@ -32795,10 +33543,10 @@ import { Key as Key6, matchesKey as matchesKey14, truncateToWidth as truncateToW
|
|
|
32795
33543
|
// packages/updater/src/readme.ts
|
|
32796
33544
|
init_core();
|
|
32797
33545
|
init_core();
|
|
32798
|
-
import { dirname as
|
|
33546
|
+
import { dirname as dirname27 } from "node:path";
|
|
32799
33547
|
import { fileURLToPath as fileURLToPath12 } from "node:url";
|
|
32800
33548
|
import { existsSync as existsSync39, readFileSync as readFileSync34 } from "fs";
|
|
32801
|
-
import { join as
|
|
33549
|
+
import { join as join40 } from "path";
|
|
32802
33550
|
var PACKAGE_MAP = {
|
|
32803
33551
|
core: MODULES.CORE,
|
|
32804
33552
|
workflow: MODULES.WORKFLOW,
|
|
@@ -32822,13 +33570,13 @@ var PACKAGE_MAP = {
|
|
|
32822
33570
|
updater: MODULES.UPDATER
|
|
32823
33571
|
};
|
|
32824
33572
|
function resolveUnipiRoot() {
|
|
32825
|
-
const dir =
|
|
33573
|
+
const dir = dirname27(fileURLToPath12(import.meta.url));
|
|
32826
33574
|
const root = findPackageRoot(dir, "@pi-unipi/unipi");
|
|
32827
|
-
return root ??
|
|
33575
|
+
return root ?? join40(dir, "..");
|
|
32828
33576
|
}
|
|
32829
33577
|
function getPackageVersion2(dir) {
|
|
32830
33578
|
try {
|
|
32831
|
-
const pkgPath =
|
|
33579
|
+
const pkgPath = join40(dir, "package.json");
|
|
32832
33580
|
if (existsSync39(pkgPath)) {
|
|
32833
33581
|
const raw = readFileSync34(pkgPath, "utf-8");
|
|
32834
33582
|
const pkg = JSON.parse(raw);
|
|
@@ -32841,7 +33589,7 @@ function getPackageVersion2(dir) {
|
|
|
32841
33589
|
function discoverReadmes() {
|
|
32842
33590
|
const root = resolveUnipiRoot();
|
|
32843
33591
|
const entries = [];
|
|
32844
|
-
const rootReadme =
|
|
33592
|
+
const rootReadme = join40(root, "README.md");
|
|
32845
33593
|
if (existsSync39(rootReadme)) {
|
|
32846
33594
|
entries.push({
|
|
32847
33595
|
name: "unipi",
|
|
@@ -32851,16 +33599,16 @@ function discoverReadmes() {
|
|
|
32851
33599
|
});
|
|
32852
33600
|
}
|
|
32853
33601
|
for (const [shortName, fullName] of Object.entries(PACKAGE_MAP)) {
|
|
32854
|
-
const workspacePath =
|
|
32855
|
-
const nodeModulesPath =
|
|
33602
|
+
const workspacePath = join40(root, "packages", shortName, "README.md");
|
|
33603
|
+
const nodeModulesPath = join40(root, "node_modules", fullName, "README.md");
|
|
32856
33604
|
let readmePath = null;
|
|
32857
33605
|
let versionDir = null;
|
|
32858
33606
|
if (existsSync39(workspacePath)) {
|
|
32859
33607
|
readmePath = workspacePath;
|
|
32860
|
-
versionDir =
|
|
33608
|
+
versionDir = join40(root, "packages", shortName);
|
|
32861
33609
|
} else if (existsSync39(nodeModulesPath)) {
|
|
32862
33610
|
readmePath = nodeModulesPath;
|
|
32863
|
-
versionDir =
|
|
33611
|
+
versionDir = join40(root, "node_modules", fullName);
|
|
32864
33612
|
}
|
|
32865
33613
|
if (readmePath && versionDir) {
|
|
32866
33614
|
entries.push({
|
|
@@ -32876,14 +33624,14 @@ function discoverReadmes() {
|
|
|
32876
33624
|
function resolveReadmePath(packageName) {
|
|
32877
33625
|
const root = resolveUnipiRoot();
|
|
32878
33626
|
if (!packageName || packageName === "unipi") {
|
|
32879
|
-
const rootReadme =
|
|
33627
|
+
const rootReadme = join40(root, "README.md");
|
|
32880
33628
|
return existsSync39(rootReadme) ? rootReadme : null;
|
|
32881
33629
|
}
|
|
32882
33630
|
const fullName = PACKAGE_MAP[packageName];
|
|
32883
33631
|
if (!fullName) return null;
|
|
32884
|
-
const workspacePath =
|
|
33632
|
+
const workspacePath = join40(root, "packages", packageName, "README.md");
|
|
32885
33633
|
if (existsSync39(workspacePath)) return workspacePath;
|
|
32886
|
-
const nodeModulesPath =
|
|
33634
|
+
const nodeModulesPath = join40(root, "node_modules", fullName, "README.md");
|
|
32887
33635
|
if (existsSync39(nodeModulesPath)) return nodeModulesPath;
|
|
32888
33636
|
return null;
|
|
32889
33637
|
}
|
|
@@ -33159,7 +33907,7 @@ import { Key as Key7, matchesKey as matchesKey15, truncateToWidth as truncateToW
|
|
|
33159
33907
|
// packages/updater/src/changelog.ts
|
|
33160
33908
|
init_core();
|
|
33161
33909
|
import { existsSync as existsSync40, readFileSync as readFileSync36 } from "fs";
|
|
33162
|
-
import { dirname as
|
|
33910
|
+
import { dirname as dirname28, join as join41 } from "node:path";
|
|
33163
33911
|
import { fileURLToPath as fileURLToPath13 } from "node:url";
|
|
33164
33912
|
|
|
33165
33913
|
// packages/updater/src/version.ts
|
|
@@ -33184,15 +33932,15 @@ function isNewerVersion(latest, current) {
|
|
|
33184
33932
|
// packages/updater/src/changelog.ts
|
|
33185
33933
|
function resolveChangelogPath() {
|
|
33186
33934
|
try {
|
|
33187
|
-
const dir =
|
|
33935
|
+
const dir = dirname28(fileURLToPath13(import.meta.url));
|
|
33188
33936
|
const root = findPackageRoot(dir, "@pi-unipi/unipi");
|
|
33189
33937
|
if (root) {
|
|
33190
|
-
const packaged =
|
|
33938
|
+
const packaged = join41(root, "CHANGELOG.md");
|
|
33191
33939
|
if (existsSync40(packaged)) return packaged;
|
|
33192
33940
|
}
|
|
33193
33941
|
} catch {
|
|
33194
33942
|
}
|
|
33195
|
-
return
|
|
33943
|
+
return join41(process.cwd(), "CHANGELOG.md");
|
|
33196
33944
|
}
|
|
33197
33945
|
var VERSION_HEADER_RE = /^## \[(.+?)\](?:\s*[-—–]\s*(.+))?$/;
|
|
33198
33946
|
var SECTION_HEADER_RE = /^### (.+)$/;
|
|
@@ -33444,9 +34192,9 @@ function renderChangelogOverlay() {
|
|
|
33444
34192
|
|
|
33445
34193
|
// packages/updater/src/settings.ts
|
|
33446
34194
|
init_core();
|
|
33447
|
-
import { readFileSync as readFileSync37, writeFileSync as
|
|
33448
|
-
import { dirname as
|
|
33449
|
-
import { homedir as
|
|
34195
|
+
import { readFileSync as readFileSync37, writeFileSync as writeFileSync28, mkdirSync as mkdirSync24, existsSync as existsSync42 } from "fs";
|
|
34196
|
+
import { dirname as dirname29, join as join42 } from "path";
|
|
34197
|
+
import { homedir as homedir26 } from "os";
|
|
33450
34198
|
var DEFAULT_CONFIG6 = {
|
|
33451
34199
|
checkIntervalMs: 36e5,
|
|
33452
34200
|
// 1 hour
|
|
@@ -33460,8 +34208,8 @@ var VALID_INTERVALS = {
|
|
|
33460
34208
|
};
|
|
33461
34209
|
var VALID_MODES = ["disabled", "notify", "auto"];
|
|
33462
34210
|
function resolveConfigPath2() {
|
|
33463
|
-
const base = UPDATER_DIRS.CONFIG.replace("~",
|
|
33464
|
-
return
|
|
34211
|
+
const base = UPDATER_DIRS.CONFIG.replace("~", homedir26());
|
|
34212
|
+
return join42(base, "config.json");
|
|
33465
34213
|
}
|
|
33466
34214
|
function loadConfig5() {
|
|
33467
34215
|
const configPath = resolveConfigPath2();
|
|
@@ -33477,9 +34225,9 @@ function loadConfig5() {
|
|
|
33477
34225
|
}
|
|
33478
34226
|
function saveConfig4(config) {
|
|
33479
34227
|
const configPath = resolveConfigPath2();
|
|
33480
|
-
const dir =
|
|
33481
|
-
|
|
33482
|
-
|
|
34228
|
+
const dir = dirname29(configPath);
|
|
34229
|
+
mkdirSync24(dir, { recursive: true });
|
|
34230
|
+
writeFileSync28(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
33483
34231
|
}
|
|
33484
34232
|
function getIntervalLabel(ms) {
|
|
33485
34233
|
for (const [label, value] of Object.entries(VALID_INTERVALS)) {
|
|
@@ -33712,12 +34460,12 @@ init_core();
|
|
|
33712
34460
|
|
|
33713
34461
|
// packages/updater/src/cache.ts
|
|
33714
34462
|
init_core();
|
|
33715
|
-
import { readFileSync as readFileSync38, writeFileSync as
|
|
33716
|
-
import { dirname as
|
|
33717
|
-
import { homedir as
|
|
34463
|
+
import { readFileSync as readFileSync38, writeFileSync as writeFileSync29, mkdirSync as mkdirSync25, existsSync as existsSync43 } from "fs";
|
|
34464
|
+
import { dirname as dirname30, join as join43 } from "path";
|
|
34465
|
+
import { homedir as homedir27 } from "os";
|
|
33718
34466
|
function resolveCachePath() {
|
|
33719
|
-
const base = UPDATER_DIRS.CACHE.replace("~",
|
|
33720
|
-
return
|
|
34467
|
+
const base = UPDATER_DIRS.CACHE.replace("~", homedir27());
|
|
34468
|
+
return join43(base, "last-check.json");
|
|
33721
34469
|
}
|
|
33722
34470
|
function readLastCheck() {
|
|
33723
34471
|
const cachePath = resolveCachePath();
|
|
@@ -33735,9 +34483,9 @@ function readLastCheck() {
|
|
|
33735
34483
|
}
|
|
33736
34484
|
function writeLastCheck(cache) {
|
|
33737
34485
|
const cachePath = resolveCachePath();
|
|
33738
|
-
const dir =
|
|
33739
|
-
|
|
33740
|
-
|
|
34486
|
+
const dir = dirname30(cachePath);
|
|
34487
|
+
mkdirSync25(dir, { recursive: true });
|
|
34488
|
+
writeFileSync29(cachePath, JSON.stringify(cache, null, 2) + "\n", "utf-8");
|
|
33741
34489
|
}
|
|
33742
34490
|
function isCheckDue(intervalMs) {
|
|
33743
34491
|
const cache = readLastCheck();
|
|
@@ -34145,8 +34893,8 @@ init_core();
|
|
|
34145
34893
|
import { Key as Key10 } from "@earendil-works/pi-tui";
|
|
34146
34894
|
|
|
34147
34895
|
// packages/input-shortcuts/src/registers.ts
|
|
34148
|
-
import { existsSync as existsSync44, mkdirSync as
|
|
34149
|
-
import { dirname as
|
|
34896
|
+
import { existsSync as existsSync44, mkdirSync as mkdirSync26, readFileSync as readFileSync39, renameSync as renameSync8, writeFileSync as writeFileSync30 } from "node:fs";
|
|
34897
|
+
import { dirname as dirname31, join as join44 } from "node:path";
|
|
34150
34898
|
|
|
34151
34899
|
// packages/input-shortcuts/src/types.ts
|
|
34152
34900
|
var DEFAULT_CONFIG7 = {
|
|
@@ -34168,7 +34916,7 @@ var RegisterStore = class {
|
|
|
34168
34916
|
filePath;
|
|
34169
34917
|
loaded = false;
|
|
34170
34918
|
constructor(baseDir) {
|
|
34171
|
-
this.filePath = baseDir ?
|
|
34919
|
+
this.filePath = baseDir ? join44(baseDir, REGISTERS_FILE) : REGISTERS_FILE;
|
|
34172
34920
|
}
|
|
34173
34921
|
/** Get the stash register contents. */
|
|
34174
34922
|
getStash() {
|
|
@@ -34216,13 +34964,13 @@ var RegisterStore = class {
|
|
|
34216
34964
|
/** Atomic write: write to .tmp then rename. */
|
|
34217
34965
|
save() {
|
|
34218
34966
|
try {
|
|
34219
|
-
const dir =
|
|
34967
|
+
const dir = dirname31(this.filePath);
|
|
34220
34968
|
if (!existsSync44(dir)) {
|
|
34221
|
-
|
|
34969
|
+
mkdirSync26(dir, { recursive: true });
|
|
34222
34970
|
}
|
|
34223
34971
|
const tmpPath = this.filePath + ".tmp";
|
|
34224
|
-
|
|
34225
|
-
|
|
34972
|
+
writeFileSync30(tmpPath, JSON.stringify(this.data, null, 2), "utf-8");
|
|
34973
|
+
renameSync8(tmpPath, this.filePath);
|
|
34226
34974
|
} catch {
|
|
34227
34975
|
}
|
|
34228
34976
|
}
|
|
@@ -34443,8 +35191,8 @@ var ChordOverlay = class extends Container2 {
|
|
|
34443
35191
|
|
|
34444
35192
|
// packages/input-shortcuts/src/settings-overlay.ts
|
|
34445
35193
|
import { SettingsList as SettingsList3 } from "@earendil-works/pi-tui";
|
|
34446
|
-
import { existsSync as existsSync45, mkdirSync as
|
|
34447
|
-
import { dirname as
|
|
35194
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync27, readFileSync as readFileSync40, renameSync as renameSync9, writeFileSync as writeFileSync31 } from "node:fs";
|
|
35195
|
+
import { dirname as dirname32, join as join45 } from "node:path";
|
|
34448
35196
|
var ALT_KEY_OPTIONS = [
|
|
34449
35197
|
"alt+a",
|
|
34450
35198
|
"alt+b",
|
|
@@ -34476,7 +35224,7 @@ var ALT_KEY_OPTIONS = [
|
|
|
34476
35224
|
var CONFLICTS = /* @__PURE__ */ new Set(["alt+e"]);
|
|
34477
35225
|
var FREE_ALT_KEYS = ALT_KEY_OPTIONS.filter((k) => !CONFLICTS.has(k));
|
|
34478
35226
|
function loadConfig6(baseDir) {
|
|
34479
|
-
const filePath = baseDir ?
|
|
35227
|
+
const filePath = baseDir ? join45(baseDir, CONFIG_FILE) : CONFIG_FILE;
|
|
34480
35228
|
try {
|
|
34481
35229
|
if (existsSync45(filePath)) {
|
|
34482
35230
|
const raw = readFileSync40(filePath, "utf-8");
|
|
@@ -34491,15 +35239,15 @@ function loadConfig6(baseDir) {
|
|
|
34491
35239
|
return { ...DEFAULT_CONFIG7 };
|
|
34492
35240
|
}
|
|
34493
35241
|
function saveConfig5(config, baseDir) {
|
|
34494
|
-
const filePath = baseDir ?
|
|
35242
|
+
const filePath = baseDir ? join45(baseDir, CONFIG_FILE) : CONFIG_FILE;
|
|
34495
35243
|
try {
|
|
34496
|
-
const dir =
|
|
35244
|
+
const dir = dirname32(filePath);
|
|
34497
35245
|
if (!existsSync45(dir)) {
|
|
34498
|
-
|
|
35246
|
+
mkdirSync27(dir, { recursive: true });
|
|
34499
35247
|
}
|
|
34500
35248
|
const tmpPath = filePath + ".tmp";
|
|
34501
|
-
|
|
34502
|
-
|
|
35249
|
+
writeFileSync31(tmpPath, JSON.stringify(config, null, 2), "utf-8");
|
|
35250
|
+
renameSync9(tmpPath, filePath);
|
|
34503
35251
|
} catch {
|
|
34504
35252
|
}
|
|
34505
35253
|
}
|
|
@@ -34927,9 +35675,9 @@ import { Type as Type8 } from "typebox";
|
|
|
34927
35675
|
// packages/cocoindex/bridge.ts
|
|
34928
35676
|
init_core();
|
|
34929
35677
|
import { execFileSync as execFileSync3, spawn as spawn4 } from "node:child_process";
|
|
34930
|
-
import { existsSync as existsSync46, mkdirSync as
|
|
34931
|
-
import { homedir as
|
|
34932
|
-
import { join as
|
|
35678
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync28, readFileSync as readFileSync41, writeFileSync as writeFileSync32, readdirSync as readdirSync14, statSync as statSync9 } from "node:fs";
|
|
35679
|
+
import { homedir as homedir28 } from "node:os";
|
|
35680
|
+
import { join as join46 } from "node:path";
|
|
34933
35681
|
var DEFAULT_PIPELINE_DIR = ".unipi/cocoindex";
|
|
34934
35682
|
var DEFAULT_UPDATE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
34935
35683
|
var DEFAULT_LEXICAL_SCAN_LIMIT = 5e4;
|
|
@@ -34999,13 +35747,13 @@ function resolveCocoindexBin() {
|
|
|
34999
35747
|
if (resolved) return resolved;
|
|
35000
35748
|
} catch {
|
|
35001
35749
|
}
|
|
35002
|
-
const uvToolBin =
|
|
35750
|
+
const uvToolBin = join46(homedir28(), ".local", "bin", "cocoindex");
|
|
35003
35751
|
if (existsSync46(uvToolBin)) return uvToolBin;
|
|
35004
|
-
const miseRoot =
|
|
35752
|
+
const miseRoot = join46(homedir28(), ".local", "share", "mise", "installs", "python");
|
|
35005
35753
|
try {
|
|
35006
|
-
const versions =
|
|
35754
|
+
const versions = readdirSync14(miseRoot).sort().reverse();
|
|
35007
35755
|
for (const ver of versions) {
|
|
35008
|
-
const binPath =
|
|
35756
|
+
const binPath = join46(miseRoot, ver, "bin", "cocoindex");
|
|
35009
35757
|
if (existsSync46(binPath)) return binPath;
|
|
35010
35758
|
}
|
|
35011
35759
|
} catch {
|
|
@@ -35013,13 +35761,13 @@ function resolveCocoindexBin() {
|
|
|
35013
35761
|
return "cocoindex";
|
|
35014
35762
|
}
|
|
35015
35763
|
function getPipelineDir(projectDir) {
|
|
35016
|
-
return
|
|
35764
|
+
return join46(projectDir, DEFAULT_PIPELINE_DIR);
|
|
35017
35765
|
}
|
|
35018
35766
|
async function isPipelineInitialized(pipelineDir) {
|
|
35019
|
-
return existsSync46(
|
|
35767
|
+
return existsSync46(join46(pipelineDir, "main.py"));
|
|
35020
35768
|
}
|
|
35021
35769
|
function detectTargetStore(pipelineDir) {
|
|
35022
|
-
const mainPyPath =
|
|
35770
|
+
const mainPyPath = join46(pipelineDir, "main.py");
|
|
35023
35771
|
if (!existsSync46(mainPyPath)) return "unknown";
|
|
35024
35772
|
try {
|
|
35025
35773
|
const content = readFileSync41(mainPyPath, "utf-8");
|
|
@@ -35035,14 +35783,14 @@ function detectTargetStore(pipelineDir) {
|
|
|
35035
35783
|
async function initPipeline(projectDir) {
|
|
35036
35784
|
const pipelineDir = getPipelineDir(projectDir);
|
|
35037
35785
|
if (!existsSync46(pipelineDir)) {
|
|
35038
|
-
|
|
35786
|
+
mkdirSync28(pipelineDir, { recursive: true });
|
|
35039
35787
|
}
|
|
35040
|
-
if (existsSync46(
|
|
35788
|
+
if (existsSync46(join46(pipelineDir, "main.py"))) {
|
|
35041
35789
|
return { success: true };
|
|
35042
35790
|
}
|
|
35043
35791
|
const embeddingConfig = loadEmbeddingConfig2();
|
|
35044
35792
|
const template = generatePipelineTemplate(projectDir, embeddingConfig);
|
|
35045
|
-
|
|
35793
|
+
writeFileSync32(join46(pipelineDir, "main.py"), template, "utf-8");
|
|
35046
35794
|
return { success: true };
|
|
35047
35795
|
}
|
|
35048
35796
|
async function indexProject(projectDir) {
|
|
@@ -35056,7 +35804,7 @@ async function indexProject(projectDir) {
|
|
|
35056
35804
|
};
|
|
35057
35805
|
}
|
|
35058
35806
|
const pipelineDir = getPipelineDir(projectDir);
|
|
35059
|
-
if (!existsSync46(
|
|
35807
|
+
if (!existsSync46(join46(pipelineDir, "main.py"))) {
|
|
35060
35808
|
return {
|
|
35061
35809
|
success: false,
|
|
35062
35810
|
chunksProcessed: 0,
|
|
@@ -35066,7 +35814,7 @@ async function indexProject(projectDir) {
|
|
|
35066
35814
|
}
|
|
35067
35815
|
const start = Date.now();
|
|
35068
35816
|
const cocoindexBin = resolveCocoindexBin();
|
|
35069
|
-
return new Promise((
|
|
35817
|
+
return new Promise((resolve14) => {
|
|
35070
35818
|
const proc = spawn4(cocoindexBin, ["update", "main.py"], {
|
|
35071
35819
|
cwd: pipelineDir,
|
|
35072
35820
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -35096,9 +35844,9 @@ async function indexProject(projectDir) {
|
|
|
35096
35844
|
const durationMs = Date.now() - start;
|
|
35097
35845
|
const chunksProcessed = parseChunksProcessed(stdout);
|
|
35098
35846
|
if (code === 0) {
|
|
35099
|
-
|
|
35847
|
+
resolve14({ success: true, chunksProcessed, durationMs });
|
|
35100
35848
|
} else {
|
|
35101
|
-
|
|
35849
|
+
resolve14({
|
|
35102
35850
|
success: false,
|
|
35103
35851
|
chunksProcessed,
|
|
35104
35852
|
durationMs,
|
|
@@ -35109,7 +35857,7 @@ async function indexProject(projectDir) {
|
|
|
35109
35857
|
proc.on("error", (err) => {
|
|
35110
35858
|
settled = true;
|
|
35111
35859
|
clearTimeout(timer);
|
|
35112
|
-
|
|
35860
|
+
resolve14({
|
|
35113
35861
|
success: false,
|
|
35114
35862
|
chunksProcessed: 0,
|
|
35115
35863
|
durationMs: Date.now() - start,
|
|
@@ -35167,16 +35915,16 @@ function parseChunksProcessed(output) {
|
|
|
35167
35915
|
async function status(projectDir) {
|
|
35168
35916
|
const pipelineDir = getPipelineDir(projectDir);
|
|
35169
35917
|
const cliAvailable = await isAvailable();
|
|
35170
|
-
const pipelineConfigured = existsSync46(
|
|
35918
|
+
const pipelineConfigured = existsSync46(join46(pipelineDir, "main.py"));
|
|
35171
35919
|
const targetStore = detectTargetStore(pipelineDir);
|
|
35172
35920
|
let docCount = 0;
|
|
35173
35921
|
let lastRun = null;
|
|
35174
|
-
const lancedbPath =
|
|
35922
|
+
const lancedbPath = join46(pipelineDir, ".lancedb");
|
|
35175
35923
|
if (existsSync46(lancedbPath)) {
|
|
35176
35924
|
try {
|
|
35177
|
-
const stat =
|
|
35925
|
+
const stat = statSync9(lancedbPath);
|
|
35178
35926
|
lastRun = stat.mtime.toISOString();
|
|
35179
|
-
const files =
|
|
35927
|
+
const files = readdirSync14(lancedbPath, { recursive: true });
|
|
35180
35928
|
docCount = files.filter((f) => f.endsWith(".lance")).length;
|
|
35181
35929
|
} catch {
|
|
35182
35930
|
}
|
|
@@ -35194,7 +35942,7 @@ async function search(projectDir, query, options) {
|
|
|
35194
35942
|
const limit = options?.limit ?? 10;
|
|
35195
35943
|
const offset = options?.offset ?? 0;
|
|
35196
35944
|
try {
|
|
35197
|
-
const lancedbPath =
|
|
35945
|
+
const lancedbPath = join46(getPipelineDir(projectDir), ".lancedb");
|
|
35198
35946
|
if (!existsSync46(lancedbPath)) {
|
|
35199
35947
|
return [];
|
|
35200
35948
|
}
|
|
@@ -35309,7 +36057,7 @@ function countOccurrences(value, needle) {
|
|
|
35309
36057
|
}
|
|
35310
36058
|
function loadEmbeddingConfig2() {
|
|
35311
36059
|
const envKey = process.env.OPENROUTER_API_KEY ?? null;
|
|
35312
|
-
const configPath =
|
|
36060
|
+
const configPath = join46(homedir28(), ".unipi", "memory", "config.json");
|
|
35313
36061
|
try {
|
|
35314
36062
|
if (existsSync46(configPath)) {
|
|
35315
36063
|
const raw = readFileSync41(configPath, "utf-8");
|
|
@@ -35516,9 +36264,22 @@ app = coco.App(
|
|
|
35516
36264
|
}
|
|
35517
36265
|
|
|
35518
36266
|
// packages/cocoindex/tools.ts
|
|
36267
|
+
var MAX_SEARCH_RESULTS = 50;
|
|
36268
|
+
function normalizeSearchPage(limit, offset) {
|
|
36269
|
+
const requestedLimit = Number.isFinite(limit) ? Math.floor(limit) : 10;
|
|
36270
|
+
const requestedOffset = Number.isFinite(offset) ? Math.floor(offset) : 0;
|
|
36271
|
+
return {
|
|
36272
|
+
limit: Math.min(MAX_SEARCH_RESULTS, Math.max(1, requestedLimit)),
|
|
36273
|
+
offset: Math.max(0, requestedOffset)
|
|
36274
|
+
};
|
|
36275
|
+
}
|
|
35519
36276
|
var SearchParams = Type8.Object({
|
|
35520
36277
|
query: Type8.String({ description: "Search query against indexed content" }),
|
|
35521
|
-
limit: Type8.Optional(Type8.Number({
|
|
36278
|
+
limit: Type8.Optional(Type8.Number({
|
|
36279
|
+
description: `Max results (default 10, hard cap ${MAX_SEARCH_RESULTS})`,
|
|
36280
|
+
minimum: 1,
|
|
36281
|
+
maximum: MAX_SEARCH_RESULTS
|
|
36282
|
+
})),
|
|
35522
36283
|
offset: Type8.Optional(Type8.Number({ description: "Pagination offset", minimum: 0 }))
|
|
35523
36284
|
});
|
|
35524
36285
|
var StatusParams = Type8.Object({});
|
|
@@ -35534,7 +36295,7 @@ function registerCocoindexTools(pi, deps) {
|
|
|
35534
36295
|
label: "CocoIndex Search",
|
|
35535
36296
|
description: "Search indexed content using semantic vector search when available, with full-text/lexical fallbacks. Diagnostic/search only: this tool never installs CocoIndex. Use /unipi:cocoindex-init to set up/install, then /unipi:cocoindex-update to index.",
|
|
35536
36297
|
parameters: SearchParams,
|
|
35537
|
-
async execute(_toolCallId, params) {
|
|
36298
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
35538
36299
|
try {
|
|
35539
36300
|
const available = await isAvailable();
|
|
35540
36301
|
if (!available) {
|
|
@@ -35543,9 +36304,10 @@ function registerCocoindexTools(pi, deps) {
|
|
|
35543
36304
|
{ cliAvailable: false }
|
|
35544
36305
|
);
|
|
35545
36306
|
}
|
|
35546
|
-
const
|
|
35547
|
-
|
|
35548
|
-
|
|
36307
|
+
const { limit, offset } = normalizeSearchPage(params.limit, params.offset);
|
|
36308
|
+
const results = await search(deps.getProjectDir(ctx), params.query, {
|
|
36309
|
+
limit,
|
|
36310
|
+
offset
|
|
35549
36311
|
});
|
|
35550
36312
|
if (results.length === 0) {
|
|
35551
36313
|
return textResult3(
|
|
@@ -35560,7 +36322,8 @@ ${r.content.slice(0, 300)}`
|
|
|
35560
36322
|
`Found ${results.length} results for "${params.query}":
|
|
35561
36323
|
|
|
35562
36324
|
${lines.join("\n\n")}`,
|
|
35563
|
-
|
|
36325
|
+
// Avoid duplicating full result content in model-visible details.
|
|
36326
|
+
{ count: results.length, query: params.query, limit, offset }
|
|
35564
36327
|
);
|
|
35565
36328
|
} catch (err) {
|
|
35566
36329
|
return textResult3(`CocoIndex search error: ${err}`, { error: true });
|
|
@@ -35572,9 +36335,9 @@ ${lines.join("\n\n")}`,
|
|
|
35572
36335
|
label: "CocoIndex Status",
|
|
35573
36336
|
description: "Check CocoIndex indexing status. Diagnostic only; use commands for interactive install/update.",
|
|
35574
36337
|
parameters: StatusParams,
|
|
35575
|
-
async execute() {
|
|
36338
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
35576
36339
|
try {
|
|
35577
|
-
const info = await status(deps.
|
|
36340
|
+
const info = await status(deps.getProjectDir(ctx));
|
|
35578
36341
|
const lines = [
|
|
35579
36342
|
`\u{1F4E6} CocoIndex Status`,
|
|
35580
36343
|
`CLI available: ${info.cliAvailable ? "\u2705" : "\u274C"}`,
|
|
@@ -35607,8 +36370,8 @@ init_core();
|
|
|
35607
36370
|
// packages/cocoindex/installer.ts
|
|
35608
36371
|
init_core();
|
|
35609
36372
|
import { execFileSync as execFileSync4, spawn as spawn5 } from "node:child_process";
|
|
35610
|
-
import { homedir as
|
|
35611
|
-
import { delimiter, join as
|
|
36373
|
+
import { homedir as homedir29 } from "node:os";
|
|
36374
|
+
import { delimiter, join as join47 } from "node:path";
|
|
35612
36375
|
var DEFAULT_STEP_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
35613
36376
|
function detectShell() {
|
|
35614
36377
|
const shell = process.env.SHELL ?? "";
|
|
@@ -35836,7 +36599,7 @@ function buildUvInstallerFallback(shell) {
|
|
|
35836
36599
|
];
|
|
35837
36600
|
}
|
|
35838
36601
|
function runStep(step) {
|
|
35839
|
-
return new Promise((
|
|
36602
|
+
return new Promise((resolve14) => {
|
|
35840
36603
|
const proc = spawn5(step.command, step.args ?? [], {
|
|
35841
36604
|
stdio: ["ignore", "pipe", "pipe"],
|
|
35842
36605
|
env: installerEnv()
|
|
@@ -35859,20 +36622,20 @@ Timed out after ${step.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS}ms.`;
|
|
|
35859
36622
|
proc.on("close", (code, signal) => {
|
|
35860
36623
|
settled = true;
|
|
35861
36624
|
clearTimeout(timeout);
|
|
35862
|
-
|
|
36625
|
+
resolve14({ ok: code === 0, stdout, stderr, exitCode: code, signal });
|
|
35863
36626
|
});
|
|
35864
36627
|
proc.on("error", (err) => {
|
|
35865
36628
|
settled = true;
|
|
35866
36629
|
clearTimeout(timeout);
|
|
35867
|
-
|
|
36630
|
+
resolve14({ ok: false, stdout, stderr: stderr || err.message });
|
|
35868
36631
|
});
|
|
35869
36632
|
});
|
|
35870
36633
|
}
|
|
35871
36634
|
function installerEnv() {
|
|
35872
|
-
const home =
|
|
36635
|
+
const home = homedir29();
|
|
35873
36636
|
const extraPaths = [
|
|
35874
|
-
|
|
35875
|
-
|
|
36637
|
+
join47(home, ".local", "bin"),
|
|
36638
|
+
join47(home, ".local", "share", "mise", "shims")
|
|
35876
36639
|
];
|
|
35877
36640
|
const currentPath = process.env.PATH ?? "";
|
|
35878
36641
|
return {
|
|
@@ -36037,15 +36800,11 @@ ${lines.join("\n\n")}`,
|
|
|
36037
36800
|
// packages/cocoindex/index.ts
|
|
36038
36801
|
function cocoindexExtension(pi) {
|
|
36039
36802
|
registerCocoindexCommands(pi);
|
|
36803
|
+
registerCocoindexTools(pi, {
|
|
36804
|
+
getProjectDir: (ctx) => ctx.cwd ?? process.cwd()
|
|
36805
|
+
});
|
|
36040
36806
|
pi.on("session_start", async (_event, ctx) => {
|
|
36041
36807
|
const projectDir = ctx.cwd ?? process.cwd();
|
|
36042
|
-
const pipelineDir = getPipelineDir(projectDir);
|
|
36043
|
-
const initialized = await isPipelineInitialized(pipelineDir);
|
|
36044
|
-
registerCocoindexTools(pi, {
|
|
36045
|
-
projectDir,
|
|
36046
|
-
pipelineDir,
|
|
36047
|
-
initialized
|
|
36048
|
-
});
|
|
36049
36808
|
emitEvent(pi, UNIPI_EVENTS.MODULE_READY, {
|
|
36050
36809
|
name: MODULES.COCOINDEX,
|
|
36051
36810
|
version: "0.1.0",
|
|
@@ -36066,7 +36825,7 @@ function cocoindexExtension(pi) {
|
|
|
36066
36825
|
|
|
36067
36826
|
// packages/image/src/index.ts
|
|
36068
36827
|
init_core();
|
|
36069
|
-
import { dirname as
|
|
36828
|
+
import { dirname as dirname34 } from "node:path";
|
|
36070
36829
|
import { fileURLToPath as fileURLToPath14 } from "node:url";
|
|
36071
36830
|
|
|
36072
36831
|
// packages/image/src/commands.ts
|
|
@@ -37820,7 +38579,7 @@ function currentSessionModel(ctx) {
|
|
|
37820
38579
|
}
|
|
37821
38580
|
|
|
37822
38581
|
// packages/image/src/index.ts
|
|
37823
|
-
var VERSION11 = getPackageVersion(
|
|
38582
|
+
var VERSION11 = getPackageVersion(dirname34(fileURLToPath14(import.meta.url)));
|
|
37824
38583
|
function getInfoRegistry6() {
|
|
37825
38584
|
return globalThis.__unipi_info_registry;
|
|
37826
38585
|
}
|