@phenx-inc/ctlsurf 0.17.2 → 0.17.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -2
- package/bin/ctlsurf-worker.js +243 -44
- package/out/headless/index.mjs +1033 -128
- package/out/headless/index.mjs.map +4 -4
- package/out/main/index.js +933 -17
- package/out/preload/index.js +15 -0
- package/out/renderer/assets/{cssMode-BtQxI2Eb.js → cssMode-BJfoToLX.js} +3 -3
- package/out/renderer/assets/{freemarker2-DRW5dmY5.js → freemarker2-T0eMtP1Z.js} +1 -1
- package/out/renderer/assets/{handlebars-CFLF3cP7.js → handlebars-Dbbib8fi.js} +1 -1
- package/out/renderer/assets/{html-DtnBOkwp.js → html-BRHT_oxc.js} +1 -1
- package/out/renderer/assets/{htmlMode-DMVysDCg.js → htmlMode-BRlZTxME.js} +3 -3
- package/out/renderer/assets/{index-CEjwKjtd.css → index-CBWAeUYW.css} +91 -0
- package/out/renderer/assets/{index-Dm70hk5s.js → index-RCAeuJTr.js} +476 -411
- package/out/renderer/assets/{javascript-ChlyA1sf.js → javascript-BbOZOxq9.js} +2 -2
- package/out/renderer/assets/{jsonMode-D1_oHzeN.js → jsonMode-CUvAeYcT.js} +3 -3
- package/out/renderer/assets/{liquid-D4ftdy1G.js → liquid-DRIACo_s.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-Ckqu5ubG.js → lspLanguageFeatures-Ds-3nH9p.js} +1 -1
- package/out/renderer/assets/{mdx-B9wsVYSC.js → mdx-D76jlpLO.js} +1 -1
- package/out/renderer/assets/{python-e23tD6IS.js → python-CMuRPKJi.js} +1 -1
- package/out/renderer/assets/{razor-CrDyXj3d.js → razor-DRoH5mal.js} +1 -1
- package/out/renderer/assets/{tsMode-BwGC5o4O.js → tsMode-dPJzgFD7.js} +1 -1
- package/out/renderer/assets/{typescript-Do-98d_O.js → typescript-D5USKnLj.js} +1 -1
- package/out/renderer/assets/{xml-Dpdu5R3m.js → xml-qYg1wt2P.js} +1 -1
- package/out/renderer/assets/{yaml-B0Zt7LCJ.js → yaml-Uc-CvnvL.js} +1 -1
- package/out/renderer/index.html +2 -2
- package/package.json +4 -2
- package/scripts/check-work-map-gpu.cjs +62 -0
- package/scripts/check-work-map-gpu.d.cts +13 -0
- package/scripts/postinstall.cjs +44 -0
- package/scripts/prepare-work-map.py +49 -0
- package/scripts/setup-ui.cjs +161 -0
- package/scripts/test-work-map-capability.cjs +45 -0
- package/scripts/test-work-map-ui.cjs +130 -0
- package/scripts/test-work-map.cjs +352 -0
- package/scripts/work-map-clef.py +81 -0
- package/scripts/work-map-setup.cjs +127 -0
- package/src/main/index.ts +43 -1
- package/src/main/observer/observer.ts +2 -0
- package/src/main/orchestrator.ts +39 -4
- package/src/main/transcripts.ts +35 -16
- package/src/main/workMap/clef.ts +156 -0
- package/src/main/workMap/normalize.ts +172 -0
- package/src/main/workMap/source.ts +28 -0
- package/src/main/workMap/tree.ts +199 -0
- package/src/main/workMap/workMap.ts +244 -0
- package/src/preload/index.ts +17 -0
- package/src/renderer/App.tsx +15 -16
- package/src/renderer/components/ObserverPanel.tsx +3 -0
- package/src/renderer/components/SettingsDialog.tsx +0 -104
- package/src/renderer/components/WorkMapPanel.tsx +235 -0
- package/src/renderer/components/WorkMapToggle.tsx +50 -0
- package/src/renderer/lib/workMapHighlights.ts +41 -0
- package/src/renderer/styles.css +91 -0
- package/src/shared/workMap.d.ts +70 -0
package/out/headless/index.mjs
CHANGED
|
@@ -6386,15 +6386,15 @@ function redactSensitive(body) {
|
|
|
6386
6386
|
}
|
|
6387
6387
|
return null;
|
|
6388
6388
|
}
|
|
6389
|
-
async function checkCredentialsFileSafety(
|
|
6389
|
+
async function checkCredentialsFileSafety(path17, onWarn = (m) => console.warn(`anthropic-sdk: ${m}`)) {
|
|
6390
6390
|
if (typeof process === "undefined" || process.platform === "win32")
|
|
6391
6391
|
return;
|
|
6392
|
-
const
|
|
6393
|
-
let resolved =
|
|
6392
|
+
const fs12 = await import("node:fs");
|
|
6393
|
+
let resolved = path17;
|
|
6394
6394
|
let st;
|
|
6395
6395
|
try {
|
|
6396
|
-
resolved = await
|
|
6397
|
-
st = await
|
|
6396
|
+
resolved = await fs12.promises.realpath(path17);
|
|
6397
|
+
st = await fs12.promises.stat(resolved);
|
|
6398
6398
|
} catch {
|
|
6399
6399
|
return;
|
|
6400
6400
|
}
|
|
@@ -6410,27 +6410,27 @@ async function checkCredentialsFileSafety(path13, onWarn = (m) => console.warn(`
|
|
|
6410
6410
|
}
|
|
6411
6411
|
}
|
|
6412
6412
|
async function writeCredentialsFileAtomic(targetPath, data) {
|
|
6413
|
-
const
|
|
6414
|
-
const
|
|
6415
|
-
const dir =
|
|
6416
|
-
await
|
|
6413
|
+
const fs12 = await import("node:fs");
|
|
6414
|
+
const path17 = await import("node:path");
|
|
6415
|
+
const dir = path17.dirname(targetPath);
|
|
6416
|
+
await fs12.promises.mkdir(dir, { recursive: true, mode: 448 });
|
|
6417
6417
|
const tmpPath = `${targetPath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
6418
6418
|
try {
|
|
6419
|
-
const fh = await
|
|
6419
|
+
const fh = await fs12.promises.open(tmpPath, "w", 384);
|
|
6420
6420
|
try {
|
|
6421
6421
|
await fh.writeFile(JSON.stringify(data, null, 2));
|
|
6422
6422
|
await fh.sync();
|
|
6423
6423
|
} finally {
|
|
6424
6424
|
await fh.close();
|
|
6425
6425
|
}
|
|
6426
|
-
await
|
|
6426
|
+
await fs12.promises.rename(tmpPath, targetPath);
|
|
6427
6427
|
} catch (err) {
|
|
6428
|
-
await
|
|
6428
|
+
await fs12.promises.unlink(tmpPath).catch(() => {
|
|
6429
6429
|
});
|
|
6430
6430
|
throw err;
|
|
6431
6431
|
}
|
|
6432
6432
|
try {
|
|
6433
|
-
const dirFh = await
|
|
6433
|
+
const dirFh = await fs12.promises.open(dir, "r");
|
|
6434
6434
|
try {
|
|
6435
6435
|
await dirFh.sync();
|
|
6436
6436
|
} finally {
|
|
@@ -6794,12 +6794,12 @@ var init_credentials = __esm({
|
|
|
6794
6794
|
return null;
|
|
6795
6795
|
}
|
|
6796
6796
|
validateProfileName(profileName);
|
|
6797
|
-
const
|
|
6798
|
-
const
|
|
6799
|
-
const configPath =
|
|
6797
|
+
const fs12 = await import("node:fs");
|
|
6798
|
+
const path17 = await import("node:path");
|
|
6799
|
+
const configPath = path17.join(rootConfigPath, "configs", `${profileName}.json`);
|
|
6800
6800
|
let configRaw;
|
|
6801
6801
|
try {
|
|
6802
|
-
configRaw = await
|
|
6802
|
+
configRaw = await fs12.promises.readFile(configPath, "utf-8");
|
|
6803
6803
|
} catch (err) {
|
|
6804
6804
|
if (err?.code !== "ENOENT") {
|
|
6805
6805
|
throw new Error(`failed to read config file ${configPath}: ${err}`);
|
|
@@ -6880,37 +6880,37 @@ var init_credentials = __esm({
|
|
|
6880
6880
|
return null;
|
|
6881
6881
|
}
|
|
6882
6882
|
validateProfileName(profileName);
|
|
6883
|
-
const
|
|
6884
|
-
return
|
|
6883
|
+
const path17 = await import("node:path");
|
|
6884
|
+
return path17.join(rootConfigPath, "credentials", `${profileName}.json`);
|
|
6885
6885
|
};
|
|
6886
6886
|
getRootConfigPath = async () => {
|
|
6887
6887
|
if (!supportsLocalConfigFiles()) {
|
|
6888
6888
|
return null;
|
|
6889
6889
|
}
|
|
6890
|
-
const
|
|
6890
|
+
const path17 = await import("node:path");
|
|
6891
6891
|
const configDir = readEnv("ANTHROPIC_CONFIG_DIR");
|
|
6892
6892
|
if (configDir) {
|
|
6893
6893
|
return configDir;
|
|
6894
6894
|
}
|
|
6895
|
-
const
|
|
6896
|
-
if (
|
|
6895
|
+
const os8 = getPlatformHeaders()["X-Stainless-OS"];
|
|
6896
|
+
if (os8 === "Windows") {
|
|
6897
6897
|
const appData = readEnv("APPDATA");
|
|
6898
6898
|
if (appData) {
|
|
6899
|
-
return
|
|
6899
|
+
return path17.join(appData, "Anthropic");
|
|
6900
6900
|
}
|
|
6901
6901
|
const userProfile = readEnv("USERPROFILE");
|
|
6902
6902
|
if (userProfile) {
|
|
6903
|
-
return
|
|
6903
|
+
return path17.join(userProfile, "AppData", "Roaming", "Anthropic");
|
|
6904
6904
|
}
|
|
6905
6905
|
return null;
|
|
6906
6906
|
}
|
|
6907
6907
|
const xdgConfigHome = readEnv("XDG_CONFIG_HOME");
|
|
6908
6908
|
if (xdgConfigHome) {
|
|
6909
|
-
return
|
|
6909
|
+
return path17.join(xdgConfigHome, "anthropic");
|
|
6910
6910
|
}
|
|
6911
6911
|
const home = readEnv("HOME");
|
|
6912
6912
|
if (home) {
|
|
6913
|
-
return
|
|
6913
|
+
return path17.join(home, ".config", "anthropic");
|
|
6914
6914
|
}
|
|
6915
6915
|
return null;
|
|
6916
6916
|
};
|
|
@@ -6927,11 +6927,11 @@ var init_credentials = __esm({
|
|
|
6927
6927
|
if (profileName) {
|
|
6928
6928
|
return profileName;
|
|
6929
6929
|
}
|
|
6930
|
-
const
|
|
6931
|
-
const
|
|
6932
|
-
const filePath =
|
|
6930
|
+
const fs12 = await import("node:fs");
|
|
6931
|
+
const path17 = await import("node:path");
|
|
6932
|
+
const filePath = path17.join(rootConfigPath, "active_config");
|
|
6933
6933
|
try {
|
|
6934
|
-
return (await
|
|
6934
|
+
return (await fs12.promises.readFile(filePath, "utf-8")).trim() || "default";
|
|
6935
6935
|
} catch (err) {
|
|
6936
6936
|
if (err?.code !== "ENOENT") {
|
|
6937
6937
|
throw new Error(`failed to read ${filePath}: ${err}`);
|
|
@@ -6943,21 +6943,21 @@ var init_credentials = __esm({
|
|
|
6943
6943
|
});
|
|
6944
6944
|
|
|
6945
6945
|
// node_modules/@anthropic-ai/sdk/lib/credentials/identity-token.mjs
|
|
6946
|
-
function identityTokenFromFile(
|
|
6947
|
-
if (!
|
|
6946
|
+
function identityTokenFromFile(path17) {
|
|
6947
|
+
if (!path17) {
|
|
6948
6948
|
throw new AnthropicError("Identity token file path is empty");
|
|
6949
6949
|
}
|
|
6950
6950
|
return async () => {
|
|
6951
|
-
const
|
|
6951
|
+
const fs12 = await import("node:fs");
|
|
6952
6952
|
let content;
|
|
6953
6953
|
try {
|
|
6954
|
-
content = await
|
|
6954
|
+
content = await fs12.promises.readFile(path17, "utf-8");
|
|
6955
6955
|
} catch (err) {
|
|
6956
|
-
throw new AnthropicError(`Failed to read identity token file at ${
|
|
6956
|
+
throw new AnthropicError(`Failed to read identity token file at ${path17}: ${err}`);
|
|
6957
6957
|
}
|
|
6958
6958
|
const token = content.trim();
|
|
6959
6959
|
if (!token) {
|
|
6960
|
-
throw new AnthropicError(`Identity token file at ${
|
|
6960
|
+
throw new AnthropicError(`Identity token file at ${path17} is empty`);
|
|
6961
6961
|
}
|
|
6962
6962
|
return token;
|
|
6963
6963
|
};
|
|
@@ -7042,11 +7042,11 @@ var init_oidc_federation = __esm({
|
|
|
7042
7042
|
// node_modules/@anthropic-ai/sdk/lib/credentials/user-oauth.mjs
|
|
7043
7043
|
function userOAuthProvider(config) {
|
|
7044
7044
|
return async (opts) => {
|
|
7045
|
-
const
|
|
7045
|
+
const fs12 = await import("node:fs");
|
|
7046
7046
|
await checkCredentialsFileSafety(config.credentialsPath, config.onSafetyWarning);
|
|
7047
7047
|
let raw;
|
|
7048
7048
|
try {
|
|
7049
|
-
raw = await
|
|
7049
|
+
raw = await fs12.promises.readFile(config.credentialsPath, "utf-8");
|
|
7050
7050
|
} catch (err) {
|
|
7051
7051
|
throw new WorkloadIdentityError(`Credentials file not found at ${config.credentialsPath}: ${err}`);
|
|
7052
7052
|
}
|
|
@@ -7218,11 +7218,11 @@ function resolveIdentityTokenProvider(auth) {
|
|
|
7218
7218
|
}
|
|
7219
7219
|
function cachedExchangeProvider(exchange, credentialsPath, onCacheWriteError, onSafetyWarning) {
|
|
7220
7220
|
return async (opts) => {
|
|
7221
|
-
const
|
|
7221
|
+
const fs12 = await import("node:fs");
|
|
7222
7222
|
await checkCredentialsFileSafety(credentialsPath, onSafetyWarning);
|
|
7223
7223
|
let existing;
|
|
7224
7224
|
try {
|
|
7225
|
-
const raw = await
|
|
7225
|
+
const raw = await fs12.promises.readFile(credentialsPath, "utf-8");
|
|
7226
7226
|
existing = JSON.parse(raw);
|
|
7227
7227
|
const token = existing?.["access_token"];
|
|
7228
7228
|
if (token && !opts?.forceRefresh) {
|
|
@@ -8282,12 +8282,12 @@ var init_path = __esm({
|
|
|
8282
8282
|
"node_modules/@anthropic-ai/sdk/internal/utils/path.mjs"() {
|
|
8283
8283
|
init_error();
|
|
8284
8284
|
EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
8285
|
-
createPathTagFunction = (pathEncoder = encodeURIPath) => function
|
|
8285
|
+
createPathTagFunction = (pathEncoder = encodeURIPath) => function path17(statics, ...params) {
|
|
8286
8286
|
if (statics.length === 1)
|
|
8287
8287
|
return statics[0];
|
|
8288
8288
|
let postPath = false;
|
|
8289
8289
|
const invalidSegments = [];
|
|
8290
|
-
const
|
|
8290
|
+
const path18 = statics.reduce((previousValue, currentValue, index) => {
|
|
8291
8291
|
if (/[?#]/.test(currentValue)) {
|
|
8292
8292
|
postPath = true;
|
|
8293
8293
|
}
|
|
@@ -8304,7 +8304,7 @@ var init_path = __esm({
|
|
|
8304
8304
|
}
|
|
8305
8305
|
return previousValue + currentValue + (index === params.length ? "" : encoded);
|
|
8306
8306
|
}, "");
|
|
8307
|
-
const pathOnly =
|
|
8307
|
+
const pathOnly = path18.split(/[?#]/, 1)[0];
|
|
8308
8308
|
const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
|
|
8309
8309
|
let match;
|
|
8310
8310
|
while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
|
|
@@ -8325,10 +8325,10 @@ var init_path = __esm({
|
|
|
8325
8325
|
}, "");
|
|
8326
8326
|
throw new AnthropicError(`Path parameters result in path with invalid segments:
|
|
8327
8327
|
${invalidSegments.map((e) => e.error).join("\n")}
|
|
8328
|
-
${
|
|
8328
|
+
${path18}
|
|
8329
8329
|
${underline}`);
|
|
8330
8330
|
}
|
|
8331
|
-
return
|
|
8331
|
+
return path18;
|
|
8332
8332
|
};
|
|
8333
8333
|
path5 = /* @__PURE__ */ createPathTagFunction(encodeURIPath);
|
|
8334
8334
|
}
|
|
@@ -16818,9 +16818,9 @@ var init_client = __esm({
|
|
|
16818
16818
|
makeStatusError(status, error, message, headers) {
|
|
16819
16819
|
return APIError.generate(status, error, message, headers);
|
|
16820
16820
|
}
|
|
16821
|
-
buildURL(
|
|
16821
|
+
buildURL(path17, query, defaultBaseURL) {
|
|
16822
16822
|
const baseURL = !__classPrivateFieldGet(this, _BaseAnthropic_instances, "m", _BaseAnthropic_baseURLOverridden).call(this) && defaultBaseURL || this.baseURL;
|
|
16823
|
-
const url = isAbsoluteURL(
|
|
16823
|
+
const url = isAbsoluteURL(path17) ? new URL(path17) : new URL(baseURL + (baseURL.endsWith("/") && path17.startsWith("/") ? path17.slice(1) : path17));
|
|
16824
16824
|
const defaultQuery = this.defaultQuery();
|
|
16825
16825
|
const pathQuery = Object.fromEntries(url.searchParams);
|
|
16826
16826
|
if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) {
|
|
@@ -16892,24 +16892,24 @@ var init_client = __esm({
|
|
|
16892
16892
|
backendMiddleware() {
|
|
16893
16893
|
return [];
|
|
16894
16894
|
}
|
|
16895
|
-
get(
|
|
16896
|
-
return this.methodRequest("get",
|
|
16895
|
+
get(path17, opts) {
|
|
16896
|
+
return this.methodRequest("get", path17, opts);
|
|
16897
16897
|
}
|
|
16898
|
-
post(
|
|
16899
|
-
return this.methodRequest("post",
|
|
16898
|
+
post(path17, opts) {
|
|
16899
|
+
return this.methodRequest("post", path17, opts);
|
|
16900
16900
|
}
|
|
16901
|
-
patch(
|
|
16902
|
-
return this.methodRequest("patch",
|
|
16901
|
+
patch(path17, opts) {
|
|
16902
|
+
return this.methodRequest("patch", path17, opts);
|
|
16903
16903
|
}
|
|
16904
|
-
put(
|
|
16905
|
-
return this.methodRequest("put",
|
|
16904
|
+
put(path17, opts) {
|
|
16905
|
+
return this.methodRequest("put", path17, opts);
|
|
16906
16906
|
}
|
|
16907
|
-
delete(
|
|
16908
|
-
return this.methodRequest("delete",
|
|
16907
|
+
delete(path17, opts) {
|
|
16908
|
+
return this.methodRequest("delete", path17, opts);
|
|
16909
16909
|
}
|
|
16910
|
-
methodRequest(method,
|
|
16910
|
+
methodRequest(method, path17, opts) {
|
|
16911
16911
|
return this.request(Promise.resolve(opts).then((opts2) => {
|
|
16912
|
-
return { method, path:
|
|
16912
|
+
return { method, path: path17, ...opts2 };
|
|
16913
16913
|
}));
|
|
16914
16914
|
}
|
|
16915
16915
|
request(options, remainingRetries = null) {
|
|
@@ -17023,8 +17023,8 @@ var init_client = __esm({
|
|
|
17023
17023
|
}));
|
|
17024
17024
|
return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
|
|
17025
17025
|
}
|
|
17026
|
-
getAPIList(
|
|
17027
|
-
return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path:
|
|
17026
|
+
getAPIList(path17, Page2, opts) {
|
|
17027
|
+
return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path: path17, ...opts2 })) : { method: "get", path: path17, ...opts });
|
|
17028
17028
|
}
|
|
17029
17029
|
requestAPIList(Page2, options) {
|
|
17030
17030
|
const request = this.makeRequest(options, null, void 0);
|
|
@@ -17140,14 +17140,14 @@ var init_client = __esm({
|
|
|
17140
17140
|
}
|
|
17141
17141
|
async buildRequest(inputOptions, { retryCount = 0 } = {}) {
|
|
17142
17142
|
const options = { ...inputOptions };
|
|
17143
|
-
const { method, path:
|
|
17143
|
+
const { method, path: path17, query, defaultBaseURL } = options;
|
|
17144
17144
|
if (this._authState.resolution) {
|
|
17145
17145
|
await this._authState.resolution;
|
|
17146
17146
|
}
|
|
17147
17147
|
if (!this._baseURLIsExplicit && this._authState.baseURL && this.baseURL !== this._authState.baseURL) {
|
|
17148
17148
|
this.baseURL = this._authState.baseURL;
|
|
17149
17149
|
}
|
|
17150
|
-
const url = this.buildURL(
|
|
17150
|
+
const url = this.buildURL(path17, query, defaultBaseURL);
|
|
17151
17151
|
if ("timeout" in options)
|
|
17152
17152
|
validatePositiveInteger("timeout", options.timeout);
|
|
17153
17153
|
options.timeout = options.timeout ?? this.timeout;
|
|
@@ -17285,22 +17285,89 @@ var init_sdk = __esm({
|
|
|
17285
17285
|
}
|
|
17286
17286
|
});
|
|
17287
17287
|
|
|
17288
|
+
// scripts/check-work-map-gpu.cjs
|
|
17289
|
+
var require_check_work_map_gpu = __commonJS({
|
|
17290
|
+
"scripts/check-work-map-gpu.cjs"(exports, module) {
|
|
17291
|
+
function initialCapability3({ desktop = false, platform = process.platform, arch = process.arch } = {}) {
|
|
17292
|
+
if (!desktop) return { state: "unavailable", reason: "Work map is available only in the desktop app." };
|
|
17293
|
+
if (platform !== "darwin" || arch !== "arm64") return { state: "unavailable", reason: "Work map currently requires an Apple Silicon Mac with Metal GPU support." };
|
|
17294
|
+
return { state: "checking", reason: "Checking Apple GPU availability\u2026" };
|
|
17295
|
+
}
|
|
17296
|
+
function appleGpuFromReport(report) {
|
|
17297
|
+
if (!Array.isArray(report?.SPDisplaysDataType)) return null;
|
|
17298
|
+
return report.SPDisplaysDataType.find((gpu) => {
|
|
17299
|
+
const apple = gpu.spdisplays_vendor === "sppci_vendor_Apple" || /^Apple M\d/i.test(gpu.sppci_model || gpu._name || "");
|
|
17300
|
+
const metal = String(gpu.spdisplays_metal || gpu.spdisplays_metalsupport || gpu.spdisplays_mtlgpufamilysupport || "");
|
|
17301
|
+
return apple && /supported|metal/i.test(metal) && !/unsupported|not.supported|unavailable/i.test(metal);
|
|
17302
|
+
}) || null;
|
|
17303
|
+
}
|
|
17304
|
+
function usableElectronGpu(info) {
|
|
17305
|
+
if (info?.auxAttributes?.softwareRendering === true) return false;
|
|
17306
|
+
return Array.isArray(info?.gpuDevice) && info.gpuDevice.some((gpu) => gpu.active === true && (gpu.vendorId === 4203 || /Apple/i.test(gpu.deviceString || gpu.vendorString || "")));
|
|
17307
|
+
}
|
|
17308
|
+
function readGpuReport() {
|
|
17309
|
+
return new Promise((resolve4, reject) => {
|
|
17310
|
+
const { execFile: execFile3 } = __require("node:child_process");
|
|
17311
|
+
execFile3("/usr/sbin/system_profiler", ["SPDisplaysDataType", "-json"], { timeout: 5e3, maxBuffer: 1024 * 1024 }, (error, output) => {
|
|
17312
|
+
if (error) {
|
|
17313
|
+
reject(error);
|
|
17314
|
+
return;
|
|
17315
|
+
}
|
|
17316
|
+
try {
|
|
17317
|
+
resolve4(JSON.parse(output));
|
|
17318
|
+
} catch (error2) {
|
|
17319
|
+
reject(error2);
|
|
17320
|
+
}
|
|
17321
|
+
});
|
|
17322
|
+
});
|
|
17323
|
+
}
|
|
17324
|
+
async function detectCapability(options = {}) {
|
|
17325
|
+
const initial = initialCapability3(options);
|
|
17326
|
+
if (initial.state !== "checking") return initial;
|
|
17327
|
+
try {
|
|
17328
|
+
const gpu = appleGpuFromReport(await (options.readGpuReport || readGpuReport)());
|
|
17329
|
+
if (!gpu) return { state: "unavailable", reason: "No Apple GPU with Metal support was detected." };
|
|
17330
|
+
if (options.verifyGpu) {
|
|
17331
|
+
let timer;
|
|
17332
|
+
const usable = await Promise.race([
|
|
17333
|
+
options.verifyGpu(),
|
|
17334
|
+
new Promise((resolve4) => {
|
|
17335
|
+
timer = setTimeout(() => resolve4(false), 5e3);
|
|
17336
|
+
})
|
|
17337
|
+
]).finally(() => clearTimeout(timer));
|
|
17338
|
+
if (!usable) return { state: "unavailable", reason: "The Apple GPU is unavailable to the desktop app." };
|
|
17339
|
+
}
|
|
17340
|
+
return { state: "available", reason: null, gpuName: String(gpu.sppci_model || gpu._name || "Apple GPU") };
|
|
17341
|
+
} catch {
|
|
17342
|
+
return { state: "unavailable", reason: "Apple GPU availability could not be verified. Work map is disabled for this launch." };
|
|
17343
|
+
}
|
|
17344
|
+
}
|
|
17345
|
+
module.exports = { initialCapability: initialCapability3, appleGpuFromReport, usableElectronGpu, detectCapability };
|
|
17346
|
+
if (__require.main === module) {
|
|
17347
|
+
detectCapability({ desktop: true }).then((capability) => {
|
|
17348
|
+
console.log(capability.state === "available" ? `[ctlsurf] Work map hardware detected: ${capability.gpuName}. The desktop app checks GPU access again at startup.` : `[ctlsurf] ${capability.reason} Other app features remain available.`);
|
|
17349
|
+
}).catch(() => {
|
|
17350
|
+
});
|
|
17351
|
+
}
|
|
17352
|
+
}
|
|
17353
|
+
});
|
|
17354
|
+
|
|
17288
17355
|
// node_modules/electron/index.js
|
|
17289
17356
|
var require_electron = __commonJS({
|
|
17290
17357
|
"node_modules/electron/index.js"(exports, module) {
|
|
17291
|
-
var
|
|
17292
|
-
var
|
|
17293
|
-
var pathFile =
|
|
17358
|
+
var fs12 = __require("fs");
|
|
17359
|
+
var path17 = __require("path");
|
|
17360
|
+
var pathFile = path17.join(__dirname, "path.txt");
|
|
17294
17361
|
function getElectronPath() {
|
|
17295
17362
|
let executablePath;
|
|
17296
|
-
if (
|
|
17297
|
-
executablePath =
|
|
17363
|
+
if (fs12.existsSync(pathFile)) {
|
|
17364
|
+
executablePath = fs12.readFileSync(pathFile, "utf-8");
|
|
17298
17365
|
}
|
|
17299
17366
|
if (process.env.ELECTRON_OVERRIDE_DIST_PATH) {
|
|
17300
|
-
return
|
|
17367
|
+
return path17.join(process.env.ELECTRON_OVERRIDE_DIST_PATH, executablePath || "electron");
|
|
17301
17368
|
}
|
|
17302
17369
|
if (executablePath) {
|
|
17303
|
-
return
|
|
17370
|
+
return path17.join(__dirname, "dist", executablePath);
|
|
17304
17371
|
} else {
|
|
17305
17372
|
throw new Error("Electron failed to install correctly, please delete node_modules/electron and try installing again");
|
|
17306
17373
|
}
|
|
@@ -17314,7 +17381,7 @@ var require_package = __commonJS({
|
|
|
17314
17381
|
"package.json"(exports, module) {
|
|
17315
17382
|
module.exports = {
|
|
17316
17383
|
name: "@phenx-inc/ctlsurf",
|
|
17317
|
-
version: "0.17.
|
|
17384
|
+
version: "0.17.4",
|
|
17318
17385
|
description: "Agent-agnostic terminal and desktop app for ctlsurf \u2014 run Claude Code, Codex, or any coding agent with live session logging and remote control",
|
|
17319
17386
|
main: "out/main/index.js",
|
|
17320
17387
|
bin: {
|
|
@@ -17333,6 +17400,8 @@ var require_package = __commonJS({
|
|
|
17333
17400
|
"THIRD-PARTY-NOTICES.md"
|
|
17334
17401
|
],
|
|
17335
17402
|
scripts: {
|
|
17403
|
+
"test:workmap": "node scripts/test-work-map-capability.cjs && node scripts/test-work-map.cjs",
|
|
17404
|
+
"test:workmap:ui": "electron scripts/test-work-map-ui.cjs",
|
|
17336
17405
|
dev: "electron-vite dev",
|
|
17337
17406
|
"dev:terminal": "esbuild src/main/headless.ts --bundle --platform=node --target=node18 --format=esm --outfile=out/headless/index.mjs --external:node-pty --external:ws --sourcemap && node out/headless/index.mjs",
|
|
17338
17407
|
build: "electron-vite build && npm run build:headless",
|
|
@@ -17340,7 +17409,7 @@ var require_package = __commonJS({
|
|
|
17340
17409
|
prepublishOnly: "npm run build",
|
|
17341
17410
|
preview: "electron-vite preview",
|
|
17342
17411
|
package: "electron-builder",
|
|
17343
|
-
postinstall: "node
|
|
17412
|
+
postinstall: "node scripts/postinstall.cjs"
|
|
17344
17413
|
},
|
|
17345
17414
|
keywords: [
|
|
17346
17415
|
"ctlsurf",
|
|
@@ -17417,9 +17486,9 @@ function log(...args) {
|
|
|
17417
17486
|
}
|
|
17418
17487
|
|
|
17419
17488
|
// src/main/orchestrator.ts
|
|
17420
|
-
import
|
|
17421
|
-
import
|
|
17422
|
-
import
|
|
17489
|
+
import path14 from "path";
|
|
17490
|
+
import fs10 from "fs";
|
|
17491
|
+
import os5 from "os";
|
|
17423
17492
|
|
|
17424
17493
|
// src/main/pty.ts
|
|
17425
17494
|
import { createRequire } from "module";
|
|
@@ -17568,8 +17637,8 @@ var CtlsurfApi = class {
|
|
|
17568
17637
|
}
|
|
17569
17638
|
return h;
|
|
17570
17639
|
}
|
|
17571
|
-
async request(method,
|
|
17572
|
-
const url = `${this.baseUrl}${
|
|
17640
|
+
async request(method, path17, body) {
|
|
17641
|
+
const url = `${this.baseUrl}${path17}`;
|
|
17573
17642
|
const opts = {
|
|
17574
17643
|
method,
|
|
17575
17644
|
headers: this.headers()
|
|
@@ -17580,7 +17649,7 @@ var CtlsurfApi = class {
|
|
|
17580
17649
|
const res = await fetch(url, opts);
|
|
17581
17650
|
if (!res.ok) {
|
|
17582
17651
|
const text = await res.text();
|
|
17583
|
-
throw new Error(`ctlsurf API ${method} ${
|
|
17652
|
+
throw new Error(`ctlsurf API ${method} ${path17}: ${res.status} ${text}`);
|
|
17584
17653
|
}
|
|
17585
17654
|
return res.json();
|
|
17586
17655
|
}
|
|
@@ -17968,6 +18037,7 @@ ${str.slice(str.length - MAX_LOG_CHARS)}`;
|
|
|
17968
18037
|
import fs from "fs";
|
|
17969
18038
|
import path from "path";
|
|
17970
18039
|
import os from "os";
|
|
18040
|
+
import { StringDecoder } from "string_decoder";
|
|
17971
18041
|
function supportsTranscriptLogging(agentId) {
|
|
17972
18042
|
return agentId === "claude" || agentId === "codex";
|
|
17973
18043
|
}
|
|
@@ -17975,6 +18045,8 @@ var POLL_INTERVAL_MS = 1e3;
|
|
|
17975
18045
|
var DISCOVERY_SLACK_MS = 1e4;
|
|
17976
18046
|
var READ_CHUNK_BYTES = 64 * 1024;
|
|
17977
18047
|
var MAX_ENTRY_CHARS = 2e4;
|
|
18048
|
+
var MAX_READ_PER_POLL = 512 * 1024;
|
|
18049
|
+
var MAX_LINE_CHARS = 1024 * 1024;
|
|
17978
18050
|
var TranscriptTailer = class {
|
|
17979
18051
|
agentId;
|
|
17980
18052
|
cwd;
|
|
@@ -18042,7 +18114,7 @@ var TranscriptTailer = class {
|
|
|
18042
18114
|
try {
|
|
18043
18115
|
const stat2 = fs.statSync(filePath);
|
|
18044
18116
|
if (stat2.mtimeMs >= this.sinceMs - DISCOVERY_SLACK_MS) {
|
|
18045
|
-
this.files.set(filePath, { offset: 0, remainder: "", excluded: false });
|
|
18117
|
+
this.files.set(filePath, { offset: 0, remainder: "", excluded: false, decoder: new StringDecoder("utf8"), discardingLine: false, scoped: this.agentId === "claude" });
|
|
18046
18118
|
}
|
|
18047
18119
|
} catch {
|
|
18048
18120
|
}
|
|
@@ -18072,7 +18144,14 @@ var TranscriptTailer = class {
|
|
|
18072
18144
|
} catch {
|
|
18073
18145
|
return;
|
|
18074
18146
|
}
|
|
18075
|
-
if (size
|
|
18147
|
+
if (size < tail.offset) {
|
|
18148
|
+
tail.offset = 0;
|
|
18149
|
+
tail.remainder = "";
|
|
18150
|
+
tail.decoder = new StringDecoder("utf8");
|
|
18151
|
+
tail.discardingLine = false;
|
|
18152
|
+
tail.scoped = this.agentId === "claude";
|
|
18153
|
+
}
|
|
18154
|
+
if (size === tail.offset) return;
|
|
18076
18155
|
let fd;
|
|
18077
18156
|
try {
|
|
18078
18157
|
fd = fs.openSync(filePath, "r");
|
|
@@ -18081,17 +18160,26 @@ var TranscriptTailer = class {
|
|
|
18081
18160
|
}
|
|
18082
18161
|
try {
|
|
18083
18162
|
const buf = Buffer.alloc(READ_CHUNK_BYTES);
|
|
18084
|
-
|
|
18085
|
-
|
|
18163
|
+
const end = Math.min(size, tail.offset + MAX_READ_PER_POLL);
|
|
18164
|
+
while (tail.offset < end && !tail.excluded) {
|
|
18165
|
+
const bytesRead = fs.readSync(fd, buf, 0, Math.min(READ_CHUNK_BYTES, end - tail.offset), tail.offset);
|
|
18086
18166
|
if (bytesRead <= 0) break;
|
|
18087
18167
|
tail.offset += bytesRead;
|
|
18088
|
-
tail.remainder += buf.
|
|
18168
|
+
tail.remainder += tail.decoder.write(buf.subarray(0, bytesRead));
|
|
18089
18169
|
const lines = tail.remainder.split("\n");
|
|
18090
18170
|
tail.remainder = lines.pop() || "";
|
|
18091
18171
|
for (const line of lines) {
|
|
18092
|
-
|
|
18172
|
+
if (tail.discardingLine) {
|
|
18173
|
+
tail.discardingLine = false;
|
|
18174
|
+
continue;
|
|
18175
|
+
}
|
|
18176
|
+
if (line.length <= MAX_LINE_CHARS) this.handleLine(line, tail, filePath);
|
|
18093
18177
|
if (tail.excluded) break;
|
|
18094
18178
|
}
|
|
18179
|
+
if (tail.remainder.length > MAX_LINE_CHARS) {
|
|
18180
|
+
tail.remainder = "";
|
|
18181
|
+
tail.discardingLine = true;
|
|
18182
|
+
}
|
|
18095
18183
|
}
|
|
18096
18184
|
} catch (err) {
|
|
18097
18185
|
console.error(`[transcripts] Read error for ${filePath}:`, err);
|
|
@@ -18102,7 +18190,7 @@ var TranscriptTailer = class {
|
|
|
18102
18190
|
}
|
|
18103
18191
|
}
|
|
18104
18192
|
}
|
|
18105
|
-
handleLine(line, tail) {
|
|
18193
|
+
handleLine(line, tail, filePath) {
|
|
18106
18194
|
const trimmed = line.trim();
|
|
18107
18195
|
if (!trimmed) return;
|
|
18108
18196
|
let obj;
|
|
@@ -18111,16 +18199,18 @@ var TranscriptTailer = class {
|
|
|
18111
18199
|
} catch {
|
|
18112
18200
|
return;
|
|
18113
18201
|
}
|
|
18202
|
+
const entry = this.agentId === "claude" ? this.parseClaudeLine(obj) : this.parseCodexLine(obj, tail);
|
|
18203
|
+
if (tail.excluded || !tail.scoped || obj?.isSidechain || obj?.isMeta) return;
|
|
18204
|
+
if (this.agentId === "claude" && typeof obj?.cwd === "string" && stripTrailingSep(obj.cwd) !== this.cwd) return;
|
|
18114
18205
|
if (this.rawSink) {
|
|
18115
18206
|
const rawMs = typeof obj?.timestamp === "string" ? Date.parse(obj.timestamp) : NaN;
|
|
18116
|
-
if (!Number.isFinite(rawMs) || rawMs >= this.sinceMs - DISCOVERY_SLACK_MS) {
|
|
18207
|
+
if (obj?.type === "session_meta" || !Number.isFinite(rawMs) || rawMs >= this.sinceMs - DISCOVERY_SLACK_MS) {
|
|
18117
18208
|
try {
|
|
18118
|
-
this.rawSink(obj);
|
|
18209
|
+
this.rawSink(obj, { filePath, sinceMs: this.sinceMs });
|
|
18119
18210
|
} catch {
|
|
18120
18211
|
}
|
|
18121
18212
|
}
|
|
18122
18213
|
}
|
|
18123
|
-
const entry = this.agentId === "claude" ? this.parseClaudeLine(obj) : this.parseCodexLine(obj, tail);
|
|
18124
18214
|
if (!entry) return;
|
|
18125
18215
|
const ms = Date.parse(entry.ts);
|
|
18126
18216
|
if (Number.isFinite(ms) && ms < this.sinceMs - DISCOVERY_SLACK_MS) return;
|
|
@@ -18149,9 +18239,10 @@ var TranscriptTailer = class {
|
|
|
18149
18239
|
const payload = obj.payload;
|
|
18150
18240
|
if (obj.type === "session_meta") {
|
|
18151
18241
|
const metaCwd = payload?.cwd;
|
|
18152
|
-
if (typeof metaCwd
|
|
18242
|
+
if (typeof metaCwd !== "string" || stripTrailingSep(metaCwd) !== this.cwd) {
|
|
18153
18243
|
tail.excluded = true;
|
|
18154
18244
|
}
|
|
18245
|
+
tail.scoped = !tail.excluded;
|
|
18155
18246
|
return null;
|
|
18156
18247
|
}
|
|
18157
18248
|
if (obj.type !== "event_msg" || !payload || typeof payload !== "object") return null;
|
|
@@ -19848,7 +19939,7 @@ Your previous output was invalid (${lastError}). Return valid JSON matching the
|
|
|
19848
19939
|
}
|
|
19849
19940
|
}
|
|
19850
19941
|
buildUser(window2) {
|
|
19851
|
-
const
|
|
19942
|
+
const compact2 = window2.map((e) => {
|
|
19852
19943
|
const out = { id: e.id, ts: e.ts, type: e.type };
|
|
19853
19944
|
if (e.text) out.text = truncateForTier1(e.text);
|
|
19854
19945
|
if (e.tool) out.tool = e.tool;
|
|
@@ -19868,7 +19959,7 @@ Your previous output was invalid (${lastError}). Return valid JSON matching the
|
|
|
19868
19959
|
${JSON.stringify(state)}
|
|
19869
19960
|
|
|
19870
19961
|
WINDOW (${window2.length} events):
|
|
19871
|
-
${
|
|
19962
|
+
${compact2.join("\n")}`;
|
|
19872
19963
|
}
|
|
19873
19964
|
parse(text, windowIds) {
|
|
19874
19965
|
const parsed = JSON.parse(text);
|
|
@@ -20682,8 +20773,799 @@ function extractToolResultText(content) {
|
|
|
20682
20773
|
return content.filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
|
|
20683
20774
|
}
|
|
20684
20775
|
|
|
20776
|
+
// src/main/workMap/workMap.ts
|
|
20777
|
+
var import_check_work_map_gpu = __toESM(require_check_work_map_gpu());
|
|
20778
|
+
import path13 from "path";
|
|
20779
|
+
import fs9 from "fs";
|
|
20780
|
+
import os4 from "os";
|
|
20781
|
+
|
|
20782
|
+
// src/main/workMap/clef.ts
|
|
20783
|
+
import { spawn as spawn2 } from "child_process";
|
|
20784
|
+
import fs7 from "fs";
|
|
20785
|
+
import path10 from "path";
|
|
20786
|
+
function validateDecisions(value, req) {
|
|
20787
|
+
const answers = value?.answers;
|
|
20788
|
+
if (!answers || Object.keys(answers).sort().join() !== Object.keys(req.questions).sort().join()) throw new Error("Clef returned an invalid decision schema");
|
|
20789
|
+
for (const [key, q] of Object.entries(req.questions)) {
|
|
20790
|
+
const a = answers[key];
|
|
20791
|
+
const allowed = Object.keys(q.criteria).sort();
|
|
20792
|
+
if (!a || !allowed.includes(a.choice) || !a.probabilities || Object.keys(a.probabilities).sort().join() !== allowed.join()) throw new Error("Clef returned an unknown branch or activity");
|
|
20793
|
+
const values = Object.values(a.probabilities);
|
|
20794
|
+
if (values.some((p) => typeof p !== "number" || !Number.isFinite(p) || p < 0 || p > 1) || Math.abs(values.reduce((s, p) => s + p, 0) - 1) > 2e-3 || a.probabilities[a.choice] < Math.max(...values) - 1e-6) throw new Error("Clef returned invalid probabilities");
|
|
20795
|
+
}
|
|
20796
|
+
return answers;
|
|
20797
|
+
}
|
|
20798
|
+
var ClefWorker = class {
|
|
20799
|
+
constructor(scriptPath, onChange) {
|
|
20800
|
+
this.scriptPath = scriptPath;
|
|
20801
|
+
this.onChange = onChange;
|
|
20802
|
+
}
|
|
20803
|
+
state = "idle";
|
|
20804
|
+
error = null;
|
|
20805
|
+
child = null;
|
|
20806
|
+
ready = null;
|
|
20807
|
+
rejectReady = null;
|
|
20808
|
+
pending = null;
|
|
20809
|
+
seq = 0;
|
|
20810
|
+
generation = 0;
|
|
20811
|
+
idle = null;
|
|
20812
|
+
loadTimeout = null;
|
|
20813
|
+
requestTimeout = null;
|
|
20814
|
+
stopping = Promise.resolve();
|
|
20815
|
+
change(state, error = null) {
|
|
20816
|
+
this.state = state;
|
|
20817
|
+
this.error = error;
|
|
20818
|
+
this.onChange();
|
|
20819
|
+
}
|
|
20820
|
+
async predict(settings, request) {
|
|
20821
|
+
const generation = this.generation;
|
|
20822
|
+
try {
|
|
20823
|
+
await this.start(settings, generation);
|
|
20824
|
+
if (!this.child || generation !== this.generation) throw new Error("Clef process stopped");
|
|
20825
|
+
if (this.pending) throw new Error("Clef is already processing a batch");
|
|
20826
|
+
if (this.idle) clearTimeout(this.idle);
|
|
20827
|
+
this.change("thinking");
|
|
20828
|
+
const response = await new Promise((resolve4, reject) => {
|
|
20829
|
+
this.pending = { id: ++this.seq, resolve: resolve4, reject };
|
|
20830
|
+
this.requestTimeout = setTimeout(() => this.fail("Clef timed out; turn the map off and on to retry."), 2e4);
|
|
20831
|
+
this.child.stdin.write(JSON.stringify({ id: this.seq, request }) + "\n", (err) => {
|
|
20832
|
+
if (err) this.fail(`Clef input failed: ${err.message}`);
|
|
20833
|
+
});
|
|
20834
|
+
});
|
|
20835
|
+
const answers = validateDecisions(response, request);
|
|
20836
|
+
this.change("ready");
|
|
20837
|
+
this.armIdle();
|
|
20838
|
+
return answers;
|
|
20839
|
+
} catch (err) {
|
|
20840
|
+
if (generation === this.generation) this.fail(err instanceof Error ? err.message : String(err));
|
|
20841
|
+
throw err;
|
|
20842
|
+
}
|
|
20843
|
+
}
|
|
20844
|
+
async start(settings, generation) {
|
|
20845
|
+
await this.stopping;
|
|
20846
|
+
if (generation !== this.generation) throw new Error("Clef process stopped");
|
|
20847
|
+
if (this.ready) return this.ready;
|
|
20848
|
+
if (process.platform !== "darwin" || process.arch !== "arm64") throw new Error("Local Clef-Flash requires an Apple Silicon Mac. Choose Rules on this machine.");
|
|
20849
|
+
if (!fs7.existsSync(settings.pythonPath) || !fs7.existsSync(path10.join(settings.modelPath, "clef_mlx.py"))) {
|
|
20850
|
+
throw new Error("Clef runtime not found. Open Local model setup and select the Python executable and downloaded Clef-Flash folder.");
|
|
20851
|
+
}
|
|
20852
|
+
this.change("loading");
|
|
20853
|
+
const child = spawn2(settings.pythonPath, ["-u", this.scriptPath, settings.modelPath], {
|
|
20854
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
20855
|
+
env: { ...process.env, HF_HUB_OFFLINE: "1", TRANSFORMERS_OFFLINE: "1", PYTHONDONTWRITEBYTECODE: "1" }
|
|
20856
|
+
});
|
|
20857
|
+
this.child = child;
|
|
20858
|
+
let buffer = "";
|
|
20859
|
+
let stderr = "";
|
|
20860
|
+
this.ready = new Promise((resolve4, reject) => {
|
|
20861
|
+
this.rejectReady = reject;
|
|
20862
|
+
this.loadTimeout = setTimeout(() => this.fail("Clef did not load within 60 seconds."), 6e4);
|
|
20863
|
+
child.stdout.on("data", (chunk) => {
|
|
20864
|
+
if (this.child !== child) return;
|
|
20865
|
+
buffer += String(chunk);
|
|
20866
|
+
if (buffer.length > 128e3) {
|
|
20867
|
+
this.fail("Clef response exceeded its output budget");
|
|
20868
|
+
return;
|
|
20869
|
+
}
|
|
20870
|
+
let newline;
|
|
20871
|
+
while ((newline = buffer.indexOf("\n")) >= 0) {
|
|
20872
|
+
const line = buffer.slice(0, newline);
|
|
20873
|
+
buffer = buffer.slice(newline + 1);
|
|
20874
|
+
let msg;
|
|
20875
|
+
try {
|
|
20876
|
+
msg = JSON.parse(line);
|
|
20877
|
+
} catch {
|
|
20878
|
+
continue;
|
|
20879
|
+
}
|
|
20880
|
+
if (msg.ready) {
|
|
20881
|
+
if (this.loadTimeout) clearTimeout(this.loadTimeout);
|
|
20882
|
+
this.rejectReady = null;
|
|
20883
|
+
this.change("ready");
|
|
20884
|
+
resolve4();
|
|
20885
|
+
} else if (msg.error) {
|
|
20886
|
+
this.fail(String(msg.error));
|
|
20887
|
+
} else if (msg.id === this.pending?.id) {
|
|
20888
|
+
if (this.requestTimeout) clearTimeout(this.requestTimeout);
|
|
20889
|
+
const pending = this.pending;
|
|
20890
|
+
this.pending = null;
|
|
20891
|
+
pending?.resolve(msg);
|
|
20892
|
+
}
|
|
20893
|
+
}
|
|
20894
|
+
});
|
|
20895
|
+
child.stderr.on("data", (chunk) => {
|
|
20896
|
+
stderr = (stderr + String(chunk)).slice(-1e3);
|
|
20897
|
+
});
|
|
20898
|
+
child.on("error", (err) => {
|
|
20899
|
+
if (this.child === child) this.fail(`Cannot start Clef: ${err.message}`);
|
|
20900
|
+
});
|
|
20901
|
+
child.on("exit", () => {
|
|
20902
|
+
if (this.child === child) this.fail(`Clef stopped unexpectedly. ${stderr.slice(-300)}`);
|
|
20903
|
+
});
|
|
20904
|
+
});
|
|
20905
|
+
return this.ready;
|
|
20906
|
+
}
|
|
20907
|
+
armIdle() {
|
|
20908
|
+
if (this.idle) clearTimeout(this.idle);
|
|
20909
|
+
this.idle = setTimeout(() => this.stop(), 6e4);
|
|
20910
|
+
this.idle.unref();
|
|
20911
|
+
}
|
|
20912
|
+
fail(message) {
|
|
20913
|
+
this.stop(message);
|
|
20914
|
+
this.change("error", message.slice(0, 600));
|
|
20915
|
+
}
|
|
20916
|
+
stop(reason = "Clef stopped") {
|
|
20917
|
+
++this.generation;
|
|
20918
|
+
for (const timer of [this.idle, this.loadTimeout, this.requestTimeout]) if (timer) clearTimeout(timer);
|
|
20919
|
+
this.idle = this.loadTimeout = this.requestTimeout = null;
|
|
20920
|
+
this.rejectReady?.(new Error(reason));
|
|
20921
|
+
this.rejectReady = null;
|
|
20922
|
+
this.pending?.reject(new Error(reason));
|
|
20923
|
+
this.pending = null;
|
|
20924
|
+
this.ready = null;
|
|
20925
|
+
const child = this.child;
|
|
20926
|
+
this.child = null;
|
|
20927
|
+
if (child) {
|
|
20928
|
+
this.stopping = new Promise((resolve4) => child.once("close", () => resolve4()));
|
|
20929
|
+
child.kill("SIGKILL");
|
|
20930
|
+
}
|
|
20931
|
+
this.change("idle");
|
|
20932
|
+
}
|
|
20933
|
+
};
|
|
20934
|
+
|
|
20935
|
+
// src/main/workMap/normalize.ts
|
|
20936
|
+
import { createHash } from "crypto";
|
|
20937
|
+
import path11 from "path";
|
|
20938
|
+
var textOf = (value) => {
|
|
20939
|
+
if (typeof value === "string") return value;
|
|
20940
|
+
if (Array.isArray(value)) return value.map((b) => b?.text || "").filter(Boolean).join("\n");
|
|
20941
|
+
return "";
|
|
20942
|
+
};
|
|
20943
|
+
var compact = (value) => {
|
|
20944
|
+
if (typeof value === "string") return value.slice(0, 1800);
|
|
20945
|
+
try {
|
|
20946
|
+
return JSON.stringify(value ?? {}).slice(0, 1800);
|
|
20947
|
+
} catch {
|
|
20948
|
+
return "";
|
|
20949
|
+
}
|
|
20950
|
+
};
|
|
20951
|
+
var noise = (text) => /^\s*(?:<environment_context>|<user_instructions>|<permissions instructions>|<system-reminder>|<local-command|<command-|<task-notification>|<turn_aborted>|# AGENTS\.md)/i.test(text);
|
|
20952
|
+
var summaryText = (value) => {
|
|
20953
|
+
if (typeof value === "string") return value;
|
|
20954
|
+
if (!Array.isArray(value)) return "";
|
|
20955
|
+
return value.map((part) => typeof part === "string" ? part : part?.type === "summary_text" ? part.text || "" : "").filter(Boolean).join("\n\n");
|
|
20956
|
+
};
|
|
20957
|
+
function codexThinking(raw) {
|
|
20958
|
+
const p = raw?.payload;
|
|
20959
|
+
if (raw?.type === "response_item" && p?.type === "reasoning") {
|
|
20960
|
+
const text = summaryText(p.summary);
|
|
20961
|
+
return { text, key: p.id || text };
|
|
20962
|
+
}
|
|
20963
|
+
if (raw?.type === "event_msg" && p?.type === "agent_reasoning") {
|
|
20964
|
+
const text = typeof p.text === "string" ? p.text : "";
|
|
20965
|
+
return { text, key: p.id || text };
|
|
20966
|
+
}
|
|
20967
|
+
const item = raw?.type === "event_msg" && p?.type === "item_completed" ? p.item : raw?.method === "item/completed" ? raw.params?.item : null;
|
|
20968
|
+
if (item?.type === "Reasoning" || item?.type === "reasoning") {
|
|
20969
|
+
const text = summaryText(item.summary_text ?? item.summaryText ?? item.summary);
|
|
20970
|
+
return { text, key: item.id || text };
|
|
20971
|
+
}
|
|
20972
|
+
return null;
|
|
20973
|
+
}
|
|
20974
|
+
function mapThinkingStatus(raw, agent) {
|
|
20975
|
+
if (agent === "claude") {
|
|
20976
|
+
if (raw?.type !== "assistant" || !Array.isArray(raw.message?.content)) return null;
|
|
20977
|
+
const blocks = raw.message.content.filter((b) => b?.type === "thinking" || b?.type === "redacted_thinking");
|
|
20978
|
+
if (!blocks.length) return null;
|
|
20979
|
+
return blocks.some((b) => b.type === "thinking" && typeof b.thinking === "string" && b.thinking.trim()) ? "available" : "unavailable";
|
|
20980
|
+
}
|
|
20981
|
+
const thinking = codexThinking(raw);
|
|
20982
|
+
return thinking ? thinking.text.trim() ? "available" : "unavailable" : null;
|
|
20983
|
+
}
|
|
20984
|
+
function normalizeMapEvents(raw, file, agent, includeFullSource = false) {
|
|
20985
|
+
if (!raw || typeof raw !== "object" || raw.isSidechain || raw.isMeta) return [];
|
|
20986
|
+
const ts = typeof raw.timestamp === "string" ? raw.timestamp : (/* @__PURE__ */ new Date()).toISOString();
|
|
20987
|
+
const source = path11.basename(file);
|
|
20988
|
+
const events = [];
|
|
20989
|
+
const add = (kind, text, key, extra = {}) => {
|
|
20990
|
+
if (!text.trim() || (kind === "prompt" || kind === "message") && noise(text)) return;
|
|
20991
|
+
const hash = createHash("sha256").update(`${file}:${key}:${kind}`).digest("hex").slice(0, 20);
|
|
20992
|
+
const bounded = kind === "thinking" && text.length > 1800 ? `${text.slice(0, 300)}
|
|
20993
|
+
\u2026
|
|
20994
|
+
${text.slice(-1490)}` : text.trim().slice(0, 1800);
|
|
20995
|
+
const segments = kind === "thinking" || kind === "message" ? decisionCandidates(text) : void 0;
|
|
20996
|
+
events.push({
|
|
20997
|
+
id: hash,
|
|
20998
|
+
ts,
|
|
20999
|
+
kind,
|
|
21000
|
+
text: bounded,
|
|
21001
|
+
source,
|
|
21002
|
+
...extra,
|
|
21003
|
+
...segments?.length ? { segments } : {},
|
|
21004
|
+
...includeFullSource && (kind === "thinking" || kind === "message") ? { fullText: text.trim() } : {}
|
|
21005
|
+
});
|
|
21006
|
+
};
|
|
21007
|
+
if (agent === "claude") {
|
|
21008
|
+
if (raw.type !== "user" && raw.type !== "assistant") return [];
|
|
21009
|
+
const blocks = raw.message?.content;
|
|
21010
|
+
const key = raw.uuid || raw.message?.id || `${ts}:${compact(blocks)}`;
|
|
21011
|
+
if (typeof blocks === "string") add(raw.type === "user" ? "prompt" : "message", blocks, key);
|
|
21012
|
+
else if (Array.isArray(blocks)) blocks.forEach((b, i) => {
|
|
21013
|
+
if (b.type === "text") add(raw.type === "user" ? "prompt" : "message", b.text || "", `${key}:${i}`);
|
|
21014
|
+
if (raw.type === "assistant" && b.type === "thinking" && typeof b.thinking === "string") add("thinking", b.thinking, `${key}:${i}`);
|
|
21015
|
+
if (b.type === "tool_use") add("tool", compact(b.input?.command ?? b.input?.file_path ?? b.input), b.id, { tool: b.name, toolId: b.id, status: "running" });
|
|
21016
|
+
if (b.type === "tool_result") add("result", textOf(b.content) || "Tool completed", b.tool_use_id, {
|
|
21017
|
+
toolId: b.tool_use_id,
|
|
21018
|
+
status: b.is_error === true ? "failed" : "succeeded"
|
|
21019
|
+
});
|
|
21020
|
+
});
|
|
21021
|
+
return events;
|
|
21022
|
+
}
|
|
21023
|
+
const thinking = codexThinking(raw);
|
|
21024
|
+
if (thinking) {
|
|
21025
|
+
add("thinking", thinking.text, `reasoning:${thinking.key}`);
|
|
21026
|
+
return events;
|
|
21027
|
+
}
|
|
21028
|
+
const p = raw.payload;
|
|
21029
|
+
if (!p) return [];
|
|
21030
|
+
if (raw.type === "event_msg") {
|
|
21031
|
+
if (p.type === "user_message" || p.type === "agent_message") {
|
|
21032
|
+
add(p.type === "user_message" ? "prompt" : "message", textOf(p.message), `${ts}:${p.type}:${textOf(p.message)}`);
|
|
21033
|
+
}
|
|
21034
|
+
const item = p.type === "item_completed" ? p.item : null;
|
|
21035
|
+
if (item?.type === "CommandExecution") {
|
|
21036
|
+
const status = item.status === "failed" || typeof item.exit_code === "number" && item.exit_code !== 0 ? "failed" : item.status === "completed" && item.exit_code === 0 ? "succeeded" : "unknown";
|
|
21037
|
+
const command = Array.isArray(item.command) ? item.command.length >= 3 && /^-(?:l?c|cl)$/.test(item.command[1]) ? item.command.slice(2).join(" ") : item.command.join(" ") : compact(item.command);
|
|
21038
|
+
add("tool", command, item.id, { tool: "shell", toolId: item.id, status: "running" });
|
|
21039
|
+
add("result", item.aggregated_output || `Command ended (${item.exit_code ?? item.status})`, item.id, { toolId: item.id, status });
|
|
21040
|
+
}
|
|
21041
|
+
if (item?.type === "FileChange") {
|
|
21042
|
+
add("tool", Object.keys(item.changes || {}).join("\n"), item.id, { tool: "apply_patch", toolId: item.id, status: "unknown" });
|
|
21043
|
+
}
|
|
21044
|
+
}
|
|
21045
|
+
if (raw.type !== "response_item") return events;
|
|
21046
|
+
if (p.type === "message" && (p.role === "user" || p.role === "assistant")) {
|
|
21047
|
+
const text = Array.isArray(p.content) ? p.content.filter((b) => ["input_text", "output_text"].includes(b.type)).map((b) => b.text).join("\n") : "";
|
|
21048
|
+
add(p.role === "user" ? "prompt" : "message", text, p.id || `${ts}:${p.role}:${text}`);
|
|
21049
|
+
}
|
|
21050
|
+
if (p.type === "function_call" || p.type === "custom_tool_call") {
|
|
21051
|
+
if (/^(?:functions\.)?exec$/.test(p.name)) return events;
|
|
21052
|
+
let input = p.arguments ?? p.input;
|
|
21053
|
+
try {
|
|
21054
|
+
const obj = JSON.parse(input);
|
|
21055
|
+
input = obj.cmd ?? obj.command ?? obj.file_path ?? obj;
|
|
21056
|
+
} catch {
|
|
21057
|
+
}
|
|
21058
|
+
add("tool", compact(input), p.call_id, { tool: p.name, toolId: p.call_id, status: "running" });
|
|
21059
|
+
}
|
|
21060
|
+
if (p.type === "function_call_output" || p.type === "custom_tool_call_output") {
|
|
21061
|
+
const output = textOf(p.output) || compact(p.output);
|
|
21062
|
+
const exit = output.match(/(?:Process exited with code|exit_code["']?\s*:)\s*(-?\d+)/i);
|
|
21063
|
+
const status = p.is_error === true ? "failed" : exit ? Number(exit[1]) === 0 ? "succeeded" : "failed" : "unknown";
|
|
21064
|
+
add("result", output, p.call_id, { toolId: p.call_id, status });
|
|
21065
|
+
}
|
|
21066
|
+
return events;
|
|
21067
|
+
}
|
|
21068
|
+
function activityFor(e) {
|
|
21069
|
+
if (e.kind === "thinking") return "thinking";
|
|
21070
|
+
if (e.kind === "prompt") return "planning";
|
|
21071
|
+
if (e.kind === "message") return /\b(will|next|plan|investigat|checking)\b/i.test(e.text) ? "planning" : "reporting";
|
|
21072
|
+
if (/edit|write|apply_patch|filechange/i.test(e.tool || "")) return "editing";
|
|
21073
|
+
if (/\b(?:pytest|vitest|jest|tsc|unittest|npm (?:run )?(?:test|build)|cargo test|go test|node\s+\S*test[-_.])\b/i.test(e.text)) return "testing";
|
|
21074
|
+
return "investigating";
|
|
21075
|
+
}
|
|
21076
|
+
function shortLabel(text, limit2 = 72) {
|
|
21077
|
+
const line = text.replace(/```[\s\S]*?```/g, "").replace(/^[\s#>*-]+/gm, "").replace(/\s+/g, " ").trim();
|
|
21078
|
+
return line.length > limit2 ? `${line.slice(0, limit2 - 1).trimEnd()}\u2026` : line || "Current work";
|
|
21079
|
+
}
|
|
21080
|
+
function topicFor(e) {
|
|
21081
|
+
if (e.kind === "thinking") return "Reasoning & decisions";
|
|
21082
|
+
const paths = e.text.match(/(?:[\w.@-]+\/)+[\w.@-]+\.(?:tsx?|jsx?|py|json|css|md|rs|go|sh|cjs|mjs)\b/g);
|
|
21083
|
+
if (paths?.length) return shortLabel(paths[0].split("/").slice(-2).join("/"), 48);
|
|
21084
|
+
if (activityFor(e) === "testing") return "Build & tests";
|
|
21085
|
+
if (e.kind === "message") return "Approach & findings";
|
|
21086
|
+
return shortLabel(e.tool || "Investigation", 48);
|
|
21087
|
+
}
|
|
21088
|
+
function decisionCandidates(text) {
|
|
21089
|
+
const parts = text.split(/\n+|(?<=[.!?])\s+|,\s+(?=(?:so|because|since|rather|instead)\b)/i).map((s) => s.replace(/^[\s#>*-]+/, "").replace(/\*\*/g, "").trim()).filter((s) => s.length >= 15);
|
|
21090
|
+
const score = (s) => (intentFor(s) !== "context" ? 10 : 0) + (/\b(?:choose|decid|instead|suspect|likely|next|prefer)\w*\b/i.test(s) ? 5 : 0) + (/\b(?:because|since|so|avoid|tradeoff|due to|constraint|means)\b/i.test(s) ? 8 : 0);
|
|
21091
|
+
return [...new Set(parts)].map((text2, index) => ({ text: text2.slice(0, 320), index, score: score(text2) })).sort((a, b) => b.score - a.score || a.index - b.index).slice(0, 6).map((p) => p.text);
|
|
21092
|
+
}
|
|
21093
|
+
function intentFor(text) {
|
|
21094
|
+
if (/\b(?:suspect|hypothesis|likely|probably|perhaps|maybe|might|could be|seems)\b/i.test(text)) return "hypothesis";
|
|
21095
|
+
if (/\b(?:alternative|option|versus|weigh|either|could (?:use|try))\b/i.test(text)) return "alternative";
|
|
21096
|
+
if (/\b(?:choose|decided?|prefer|instead|use .+ rather than|let['’]s use)\b/i.test(text)) return "decision";
|
|
21097
|
+
if (/\b(?:building|implementing|keeping) (?:it |this )?(?:as|with|in)\b/i.test(text)) return "decision";
|
|
21098
|
+
if (/\b(?:I['’]ll|I will|we should|need to|next|going to|plan to|first (?:check|test|inspect|read))\b/i.test(text)) return "next_step";
|
|
21099
|
+
return "context";
|
|
21100
|
+
}
|
|
21101
|
+
|
|
21102
|
+
// src/main/workMap/tree.ts
|
|
21103
|
+
var ACTIVITIES = {
|
|
21104
|
+
investigating: "Reading files, searching, inspecting or diagnosing",
|
|
21105
|
+
editing: "Changing files, code, dependencies or configuration",
|
|
21106
|
+
testing: "Running tests, builds or validation",
|
|
21107
|
+
planning: "Discussing the approach or deciding what to do next",
|
|
21108
|
+
reporting: "Explaining findings or summarizing completed work"
|
|
21109
|
+
};
|
|
21110
|
+
var INTENTS = {
|
|
21111
|
+
decision: "A stated choice of approach or rejection of another approach",
|
|
21112
|
+
hypothesis: "An unverified explanation or suspicion to investigate",
|
|
21113
|
+
alternative: "Weighing different possible approaches without choosing yet",
|
|
21114
|
+
next_step: "A stated next action or check to perform",
|
|
21115
|
+
context: "General narration with no clear choice, hypothesis or next step"
|
|
21116
|
+
};
|
|
21117
|
+
var MAP_NODE_LIMIT = 240;
|
|
21118
|
+
var WorkMapTree = class {
|
|
21119
|
+
nodes = [];
|
|
21120
|
+
dropped = 0;
|
|
21121
|
+
goal = null;
|
|
21122
|
+
seq = 0;
|
|
21123
|
+
seen = /* @__PURE__ */ new Set();
|
|
21124
|
+
tools = /* @__PURE__ */ new Map();
|
|
21125
|
+
lastText = null;
|
|
21126
|
+
contextId;
|
|
21127
|
+
ingest(e) {
|
|
21128
|
+
if (this.seen.has(e.id)) return null;
|
|
21129
|
+
this.seen.add(e.id);
|
|
21130
|
+
if (this.seen.size > 1200) this.seen.delete(this.seen.values().next().value);
|
|
21131
|
+
if (e.kind === "result") {
|
|
21132
|
+
const id = this.tools.get(`${e.source}:${e.toolId}`);
|
|
21133
|
+
const node2 = this.nodes.find((n) => n.id === id);
|
|
21134
|
+
if (node2) {
|
|
21135
|
+
node2.status = e.status || "unknown";
|
|
21136
|
+
node2.evidence = [node2.evidence[0], e];
|
|
21137
|
+
}
|
|
21138
|
+
return null;
|
|
21139
|
+
}
|
|
21140
|
+
if (e.kind === "prompt" || e.kind === "message" || e.kind === "thinking") {
|
|
21141
|
+
const key = `${e.source}:${e.kind}:${e.text}`;
|
|
21142
|
+
const time = Date.parse(e.ts);
|
|
21143
|
+
if (this.lastText?.key === key && Math.abs(time - this.lastText.time) < 3e3) return null;
|
|
21144
|
+
this.lastText = { key, time };
|
|
21145
|
+
}
|
|
21146
|
+
if (e.kind === "prompt") {
|
|
21147
|
+
this.contextId = void 0;
|
|
21148
|
+
this.goal = this.add({ parentId: null, kind: "goal", title: shortLabel(e.text, 110), activity: "planning", status: "unknown", ts: e.ts, evidence: [e] }).id;
|
|
21149
|
+
this.trim();
|
|
21150
|
+
return null;
|
|
21151
|
+
}
|
|
21152
|
+
if (!this.goal) this.goal = this.add({ parentId: null, kind: "goal", title: "Session work", activity: "planning", status: "unknown", ts: e.ts, evidence: [] }).id;
|
|
21153
|
+
const topic = topicFor(e);
|
|
21154
|
+
const branch = this.branch(this.goal, topic, e.ts);
|
|
21155
|
+
const activity = activityFor(e);
|
|
21156
|
+
const statement = e.kind === "thinking" || e.kind === "message" ? e.segments?.[0] || decisionCandidates(e.text)[0] || e.text : e.text;
|
|
21157
|
+
const title = e.kind === "tool" ? `${e.tool || "Tool"} \xB7 ${shortLabel(e.text, 65)}` : shortLabel(statement, 110);
|
|
21158
|
+
const intent = e.kind === "thinking" || e.kind === "message" ? intentFor(statement) : void 0;
|
|
21159
|
+
const node = this.add({
|
|
21160
|
+
parentId: branch.id,
|
|
21161
|
+
kind: "step",
|
|
21162
|
+
title,
|
|
21163
|
+
activity,
|
|
21164
|
+
status: e.status || "unknown",
|
|
21165
|
+
ts: e.ts,
|
|
21166
|
+
evidence: [e],
|
|
21167
|
+
placement: "rules",
|
|
21168
|
+
intent,
|
|
21169
|
+
...intent && intent !== "context" ? { focus: { text: statement } } : {},
|
|
21170
|
+
...e.kind === "tool" && this.contextId ? { contextId: this.contextId } : {}
|
|
21171
|
+
});
|
|
21172
|
+
if (e.kind === "thinking" || e.kind === "message") this.contextId = node.id;
|
|
21173
|
+
if (e.toolId) this.tools.set(`${e.source}:${e.toolId}`, node.id);
|
|
21174
|
+
this.trim();
|
|
21175
|
+
return node.id;
|
|
21176
|
+
}
|
|
21177
|
+
end() {
|
|
21178
|
+
for (const n of this.nodes) if (n.status === "running") n.status = "interrupted";
|
|
21179
|
+
}
|
|
21180
|
+
request(ids) {
|
|
21181
|
+
const nodes = ids.map((id) => this.nodes.find((n) => n.id === id && n.kind === "step")).filter((n) => !!n);
|
|
21182
|
+
if (!nodes.length) return null;
|
|
21183
|
+
const parent = this.nodes.find((n) => n.id === nodes[0].parentId);
|
|
21184
|
+
if (!parent?.parentId) return null;
|
|
21185
|
+
const goalId = parent.parentId;
|
|
21186
|
+
const sameGoal = nodes.filter((n) => this.nodes.find((b) => b.id === n.parentId)?.parentId === goalId);
|
|
21187
|
+
const isIntent = (n) => n?.evidence[0]?.kind === "thinking" || n?.evidence[0]?.kind === "message";
|
|
21188
|
+
const selected = sameGoal.slice(0, isIntent(sameGoal[0]) || isIntent(sameGoal[1]) ? 1 : 2);
|
|
21189
|
+
const goal = this.nodes.find((n) => n.id === goalId);
|
|
21190
|
+
const branches = this.nodes.filter((n) => n.kind === "branch" && n.parentId === goalId).slice(-4);
|
|
21191
|
+
const criteria = Object.fromEntries(branches.map((n) => [n.id, n.title.slice(0, 48)]));
|
|
21192
|
+
criteria.new_topic = "A different work topic; create a new branch";
|
|
21193
|
+
const questions = {};
|
|
21194
|
+
const statements = {};
|
|
21195
|
+
const reasons = {};
|
|
21196
|
+
selected.forEach((node, i) => {
|
|
21197
|
+
questions[`branch_${i}`] = { type: "choice", instructions: `Which work topic does event_${i} belong to? Use the goal and stated reasoning, not incidental file paths.`, criteria };
|
|
21198
|
+
if (isIntent(node)) {
|
|
21199
|
+
const candidates = node.evidence[0].segments || decisionCandidates(node.evidence[0].text);
|
|
21200
|
+
statements[node.id] = candidates.length ? candidates : [node.evidence[0].text.slice(0, 220)];
|
|
21201
|
+
questions[`statement_${i}`] = {
|
|
21202
|
+
type: "choice",
|
|
21203
|
+
instructions: `Which excerpt best captures the agent's choice, hypothesis or next check in event_${i}?`,
|
|
21204
|
+
criteria: Object.fromEntries(statements[node.id].map((s, j) => [`s${j}`, s]))
|
|
21205
|
+
};
|
|
21206
|
+
questions[`intent_${i}`] = { type: "choice", instructions: `What does the selected excerpt express? A tentative explanation is not an established decision.`, criteria: INTENTS };
|
|
21207
|
+
reasons[node.id] = candidates.filter((s) => /\b(?:because|since|so|avoid|tradeoff|due to|constraint|rather|instead)\b/i.test(s)).slice(0, 3);
|
|
21208
|
+
if (reasons[node.id].length) questions[`reason_${i}`] = {
|
|
21209
|
+
type: "choice",
|
|
21210
|
+
instructions: "Which excerpt states why the agent chose its approach? Choose none if no supporting reason is stated.",
|
|
21211
|
+
criteria: { ...Object.fromEntries(reasons[node.id].map((s, j) => [`r${j}`, s])), none: "No supporting reason stated" }
|
|
21212
|
+
};
|
|
21213
|
+
} else {
|
|
21214
|
+
questions[`activity_${i}`] = { type: "choice", instructions: `What activity does event_${i} show?`, criteria: ACTIVITIES };
|
|
21215
|
+
}
|
|
21216
|
+
});
|
|
21217
|
+
return {
|
|
21218
|
+
ids: selected.map((n) => n.id),
|
|
21219
|
+
goalId,
|
|
21220
|
+
statements,
|
|
21221
|
+
reasons,
|
|
21222
|
+
request: {
|
|
21223
|
+
model: "clef-flash",
|
|
21224
|
+
state: JSON.stringify({ goal: goal.title, events: selected.map((n, i) => ({
|
|
21225
|
+
id: `event_${i}`,
|
|
21226
|
+
kind: n.evidence[0]?.kind,
|
|
21227
|
+
tool: n.evidence[0]?.tool,
|
|
21228
|
+
text: n.evidence[0]?.text.slice(0, 450),
|
|
21229
|
+
preceding_intent: this.nodes.find((c) => c.id === n.contextId)?.evidence[0]?.text.slice(-400),
|
|
21230
|
+
current_topic: this.nodes.find((b) => b.id === n.parentId)?.title
|
|
21231
|
+
})) }),
|
|
21232
|
+
questions
|
|
21233
|
+
}
|
|
21234
|
+
};
|
|
21235
|
+
}
|
|
21236
|
+
apply(job, answers) {
|
|
21237
|
+
job.ids.forEach((id, i) => {
|
|
21238
|
+
const node = this.nodes.find((n) => n.id === id);
|
|
21239
|
+
if (!node || !this.nodes.some((n) => n.id === job.goalId)) return;
|
|
21240
|
+
const decision = answers[`branch_${i}`];
|
|
21241
|
+
const textSource = node.evidence[0]?.kind === "thinking" || node.evidence[0]?.kind === "message";
|
|
21242
|
+
if (textSource) {
|
|
21243
|
+
const choice = answers[`statement_${i}`]?.choice;
|
|
21244
|
+
const statement = job.statements[node.id]?.[Number(choice?.slice(1))];
|
|
21245
|
+
if (statement) node.title = shortLabel(statement, 110);
|
|
21246
|
+
const statedIntent = intentFor(statement || "");
|
|
21247
|
+
node.intent = statedIntent !== "context" ? statedIntent : answers[`intent_${i}`]?.choice;
|
|
21248
|
+
const reasonChoice = answers[`reason_${i}`]?.choice;
|
|
21249
|
+
const reason = reasonChoice && reasonChoice !== "none" ? job.reasons[node.id]?.[Number(reasonChoice.slice(1))] : void 0;
|
|
21250
|
+
node.focus = statement ? { text: statement, ...reason && reason !== statement ? { reason } : {} } : void 0;
|
|
21251
|
+
}
|
|
21252
|
+
const topic = topicFor(node.evidence[0]);
|
|
21253
|
+
const newTitle = shortLabel(node.evidence[0].kind === "thinking" ? node.title : node.evidence[0].text, 48);
|
|
21254
|
+
const target = decision.choice === "new_topic" ? this.branch(job.goalId, newTitle === topic ? `${node.activity} \xB7 ${topic}` : newTitle, node.ts) : this.nodes.find((n) => n.id === decision.choice && n.kind === "branch" && n.parentId === job.goalId);
|
|
21255
|
+
if (!target) return;
|
|
21256
|
+
node.parentId = target.id;
|
|
21257
|
+
node.activity = node.evidence[0]?.kind === "thinking" ? "thinking" : textSource ? node.intent === "context" ? "reporting" : "planning" : answers[`activity_${i}`].choice;
|
|
21258
|
+
node.placement = "clef-flash";
|
|
21259
|
+
node.confidence = decision.probabilities[decision.choice];
|
|
21260
|
+
});
|
|
21261
|
+
this.removeEmptyBranches();
|
|
21262
|
+
this.trim();
|
|
21263
|
+
}
|
|
21264
|
+
add(node) {
|
|
21265
|
+
const result = { ...node, id: `map_${++this.seq}` };
|
|
21266
|
+
this.nodes.push(result);
|
|
21267
|
+
return result;
|
|
21268
|
+
}
|
|
21269
|
+
branch(goal, title, ts) {
|
|
21270
|
+
return this.nodes.find((n) => n.kind === "branch" && n.parentId === goal && n.title === title) || this.add({ parentId: goal, kind: "branch", title, activity: "planning", status: "unknown", ts, evidence: [] });
|
|
21271
|
+
}
|
|
21272
|
+
removeEmptyBranches() {
|
|
21273
|
+
const parents = new Set(this.nodes.map((n) => n.parentId));
|
|
21274
|
+
this.nodes = this.nodes.filter((n) => n.kind !== "branch" || parents.has(n.id));
|
|
21275
|
+
}
|
|
21276
|
+
trim() {
|
|
21277
|
+
while (this.nodes.length > MAP_NODE_LIMIT) {
|
|
21278
|
+
const oldGoal = this.nodes.find((n) => n.kind === "goal" && n.id !== this.goal);
|
|
21279
|
+
if (oldGoal) {
|
|
21280
|
+
const removed = /* @__PURE__ */ new Set([oldGoal.id, ...this.nodes.filter((n) => n.parentId === oldGoal.id).map((n) => n.id)]);
|
|
21281
|
+
const before = this.nodes.length;
|
|
21282
|
+
this.nodes = this.nodes.filter((n) => !removed.has(n.id) && !removed.has(n.parentId || ""));
|
|
21283
|
+
this.dropped += before - this.nodes.length;
|
|
21284
|
+
} else {
|
|
21285
|
+
const oldest = this.nodes.findIndex((n) => n.kind === "step");
|
|
21286
|
+
if (oldest < 0) break;
|
|
21287
|
+
this.nodes.splice(oldest, 1);
|
|
21288
|
+
this.dropped++;
|
|
21289
|
+
this.removeEmptyBranches();
|
|
21290
|
+
}
|
|
21291
|
+
}
|
|
21292
|
+
const retained = new Set(this.nodes.map((n) => n.id));
|
|
21293
|
+
if (this.contextId && !retained.has(this.contextId)) this.contextId = void 0;
|
|
21294
|
+
for (const [key, id] of this.tools) if (!retained.has(id)) this.tools.delete(key);
|
|
21295
|
+
}
|
|
21296
|
+
};
|
|
21297
|
+
|
|
21298
|
+
// src/main/workMap/source.ts
|
|
21299
|
+
import fs8 from "fs";
|
|
21300
|
+
import path12 from "path";
|
|
21301
|
+
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
21302
|
+
async function readMapSource(file, agent, eventId) {
|
|
21303
|
+
const decoder = new StringDecoder2("utf8");
|
|
21304
|
+
let remainder = "";
|
|
21305
|
+
let discard = false;
|
|
21306
|
+
const stream = fs8.createReadStream(file, { highWaterMark: 64 * 1024 });
|
|
21307
|
+
for await (const chunk of stream) {
|
|
21308
|
+
remainder += decoder.write(chunk);
|
|
21309
|
+
const lines = remainder.split("\n");
|
|
21310
|
+
remainder = lines.pop() || "";
|
|
21311
|
+
for (const line of lines) {
|
|
21312
|
+
if (discard) {
|
|
21313
|
+
discard = false;
|
|
21314
|
+
continue;
|
|
21315
|
+
}
|
|
21316
|
+
if (line.length > 1024 * 1024) continue;
|
|
21317
|
+
let raw;
|
|
21318
|
+
try {
|
|
21319
|
+
raw = JSON.parse(line);
|
|
21320
|
+
} catch {
|
|
21321
|
+
continue;
|
|
21322
|
+
}
|
|
21323
|
+
const event = normalizeMapEvents(raw, file, agent, true).find((e) => e.id === eventId);
|
|
21324
|
+
if (event?.fullText !== void 0) return { text: event.fullText, source: path12.basename(file) };
|
|
21325
|
+
}
|
|
21326
|
+
if (remainder.length > 1024 * 1024) {
|
|
21327
|
+
remainder = "";
|
|
21328
|
+
discard = true;
|
|
21329
|
+
}
|
|
21330
|
+
}
|
|
21331
|
+
return null;
|
|
21332
|
+
}
|
|
21333
|
+
|
|
21334
|
+
// src/main/workMap/workMap.ts
|
|
21335
|
+
var WorkMap = class {
|
|
21336
|
+
constructor(appRoot, onUpdate, roots = {}, capability = (0, import_check_work_map_gpu.initialCapability)()) {
|
|
21337
|
+
this.onUpdate = onUpdate;
|
|
21338
|
+
this.roots = roots;
|
|
21339
|
+
this.capability = capability;
|
|
21340
|
+
const cache = path13.join(appRoot, "experiments/live-map/.cache");
|
|
21341
|
+
const prepared = path13.join(os4.homedir(), ".ctlsurf/clef-flash");
|
|
21342
|
+
const pilotPython = path13.join(cache, "envs/clef-flash/bin/python");
|
|
21343
|
+
this.settings = {
|
|
21344
|
+
enabled: false,
|
|
21345
|
+
engine: "clef-flash",
|
|
21346
|
+
pythonPath: process.env.CTLSURF_CLEF_PYTHON || (fs9.existsSync(pilotPython) ? pilotPython : path13.join(prepared, "env/bin/python")),
|
|
21347
|
+
modelPath: process.env.CTLSURF_CLEF_MODEL || (fs9.existsSync(pilotPython) ? path13.join(cache, "models/clef-flash") : path13.join(prepared, "model"))
|
|
21348
|
+
};
|
|
21349
|
+
this.clef = new ClefWorker(path13.join(appRoot, "scripts/work-map-clef.py"), () => this.publish());
|
|
21350
|
+
}
|
|
21351
|
+
tabs = /* @__PURE__ */ new Map();
|
|
21352
|
+
owners = /* @__PURE__ */ new Map();
|
|
21353
|
+
settings;
|
|
21354
|
+
clef;
|
|
21355
|
+
revision = 0;
|
|
21356
|
+
epoch = 0;
|
|
21357
|
+
timer = null;
|
|
21358
|
+
publishing = null;
|
|
21359
|
+
working = false;
|
|
21360
|
+
round = 0;
|
|
21361
|
+
configure(value) {
|
|
21362
|
+
++this.epoch;
|
|
21363
|
+
const previous = this.settings;
|
|
21364
|
+
this.settings = {
|
|
21365
|
+
enabled: this.capability.state === "available" && (typeof value.enabled === "boolean" ? value.enabled : previous.enabled),
|
|
21366
|
+
engine: value.engine === "rules" || value.engine === "clef-flash" ? value.engine : previous.engine,
|
|
21367
|
+
pythonPath: typeof value.pythonPath === "string" && value.pythonPath.trim() ? value.pythonPath.trim() : previous.pythonPath,
|
|
21368
|
+
modelPath: typeof value.modelPath === "string" && value.modelPath.trim() ? value.modelPath.trim() : previous.modelPath
|
|
21369
|
+
};
|
|
21370
|
+
this.clef.stop();
|
|
21371
|
+
if (this.timer) clearTimeout(this.timer);
|
|
21372
|
+
this.timer = null;
|
|
21373
|
+
for (const tab of this.tabs.values()) {
|
|
21374
|
+
tab.pending.clear();
|
|
21375
|
+
if (this.settings.enabled && !tab.ended) this.start(tab);
|
|
21376
|
+
else {
|
|
21377
|
+
tab.tailer?.stop();
|
|
21378
|
+
tab.tailer = null;
|
|
21379
|
+
tab.tree.end();
|
|
21380
|
+
}
|
|
21381
|
+
if (this.settings.enabled && this.settings.engine === "clef-flash") {
|
|
21382
|
+
tab.tree.nodes.filter((n) => n.kind === "step" && n.placement !== "clef-flash").slice(-24).forEach((n) => tab.pending.add(n.id));
|
|
21383
|
+
}
|
|
21384
|
+
}
|
|
21385
|
+
this.publish();
|
|
21386
|
+
this.schedule();
|
|
21387
|
+
return { ...this.settings };
|
|
21388
|
+
}
|
|
21389
|
+
attach(tabId, agentId, agentName, cwd) {
|
|
21390
|
+
this.remove(tabId);
|
|
21391
|
+
const tab = { agentId, agentName, cwd, tree: new WorkMapTree(), tailer: null, ended: false, state: "idle", source: null, ignored: /* @__PURE__ */ new Set(), pending: /* @__PURE__ */ new Set(), thinkingStatus: "waiting", sources: /* @__PURE__ */ new Map() };
|
|
21392
|
+
this.tabs.set(tabId, tab);
|
|
21393
|
+
if (this.settings.enabled) this.start(tab);
|
|
21394
|
+
this.publish();
|
|
21395
|
+
}
|
|
21396
|
+
getCapability() {
|
|
21397
|
+
return { ...this.capability };
|
|
21398
|
+
}
|
|
21399
|
+
setCapability(capability) {
|
|
21400
|
+
this.capability = { ...capability };
|
|
21401
|
+
if (capability.state !== "available") this.configure({ enabled: false });
|
|
21402
|
+
this.publish();
|
|
21403
|
+
}
|
|
21404
|
+
end(tabId) {
|
|
21405
|
+
const tab = this.tabs.get(tabId);
|
|
21406
|
+
if (!tab) return;
|
|
21407
|
+
tab.ended = true;
|
|
21408
|
+
tab.tailer?.stop();
|
|
21409
|
+
tab.tailer = null;
|
|
21410
|
+
tab.pending.clear();
|
|
21411
|
+
tab.tree.end();
|
|
21412
|
+
for (const [source, owner] of this.owners) if (owner === tabId) this.owners.delete(source);
|
|
21413
|
+
if (![...this.tabs.values()].some((t) => !t.ended)) this.clef.stop();
|
|
21414
|
+
this.publish();
|
|
21415
|
+
}
|
|
21416
|
+
remove(tabId) {
|
|
21417
|
+
this.end(tabId);
|
|
21418
|
+
this.tabs.delete(tabId);
|
|
21419
|
+
for (const [source, owner] of this.owners) if (owner === tabId) this.owners.delete(source);
|
|
21420
|
+
}
|
|
21421
|
+
clear(tabId) {
|
|
21422
|
+
const tab = this.tabs.get(tabId);
|
|
21423
|
+
if (tab) {
|
|
21424
|
+
tab.tree = new WorkMapTree();
|
|
21425
|
+
tab.pending.clear();
|
|
21426
|
+
}
|
|
21427
|
+
this.publish();
|
|
21428
|
+
}
|
|
21429
|
+
noteState(tabId, state) {
|
|
21430
|
+
const tab = this.tabs.get(tabId);
|
|
21431
|
+
if (tab && tab.state !== state) {
|
|
21432
|
+
tab.state = state;
|
|
21433
|
+
this.publish();
|
|
21434
|
+
}
|
|
21435
|
+
}
|
|
21436
|
+
snapshot(tabId) {
|
|
21437
|
+
const tab = this.tabs.get(tabId);
|
|
21438
|
+
return {
|
|
21439
|
+
revision: ++this.revision,
|
|
21440
|
+
tabId,
|
|
21441
|
+
agentName: tab?.agentName || null,
|
|
21442
|
+
supported: !!tab && supportsTranscriptLogging(tab.agentId),
|
|
21443
|
+
ended: !!tab?.ended,
|
|
21444
|
+
state: tab?.state || "idle",
|
|
21445
|
+
settings: { ...this.settings },
|
|
21446
|
+
runtime: !this.settings.enabled ? "off" : this.settings.engine === "rules" ? "ready" : this.clef.state,
|
|
21447
|
+
error: this.settings.engine === "clef-flash" ? this.clef.error : null,
|
|
21448
|
+
nodes: tab?.tree.nodes || [],
|
|
21449
|
+
dropped: tab?.tree.dropped || 0,
|
|
21450
|
+
thinkingStatus: tab?.thinkingStatus || "waiting",
|
|
21451
|
+
capability: this.getCapability()
|
|
21452
|
+
};
|
|
21453
|
+
}
|
|
21454
|
+
async getSource(tabId, eventId) {
|
|
21455
|
+
const tab = this.tabs.get(tabId);
|
|
21456
|
+
const event = tab?.tree.nodes.flatMap((n) => n.evidence).find((e) => e.id === eventId && (e.kind === "thinking" || e.kind === "message"));
|
|
21457
|
+
const file = event && tab?.sources.get(event.source);
|
|
21458
|
+
if (!event || !file || !tab) return null;
|
|
21459
|
+
return readMapSource(file, tab.agentId, event.id);
|
|
21460
|
+
}
|
|
21461
|
+
start(tab) {
|
|
21462
|
+
if (tab.tailer || !supportsTranscriptLogging(tab.agentId)) return;
|
|
21463
|
+
tab.tailer = new TranscriptTailer({
|
|
21464
|
+
...this.roots,
|
|
21465
|
+
agentId: tab.agentId,
|
|
21466
|
+
cwd: tab.cwd,
|
|
21467
|
+
sink: () => {
|
|
21468
|
+
},
|
|
21469
|
+
rawSink: (raw, source) => {
|
|
21470
|
+
if (!source || !this.settings.enabled || tab.ended) return;
|
|
21471
|
+
const file = source.filePath;
|
|
21472
|
+
if (tab.ignored.has(file)) return;
|
|
21473
|
+
if (raw.type === "session_meta") {
|
|
21474
|
+
const meta = raw.payload || {};
|
|
21475
|
+
if (meta.thread_source && meta.thread_source !== "user" || meta.source && typeof meta.source === "object" || meta.agent_path && meta.agent_path !== "/root") {
|
|
21476
|
+
tab.ignored.add(file);
|
|
21477
|
+
return;
|
|
21478
|
+
}
|
|
21479
|
+
}
|
|
21480
|
+
const tabId = [...this.tabs].find(([, candidate]) => candidate === tab)?.[0];
|
|
21481
|
+
if (!tabId) return;
|
|
21482
|
+
if (this.owners.has(file) && this.owners.get(file) !== tabId) return;
|
|
21483
|
+
if (tab.source && tab.source !== file) {
|
|
21484
|
+
if (raw.type !== "session_meta" || Date.parse(raw.payload?.timestamp || raw.timestamp) < source.sinceMs) return;
|
|
21485
|
+
this.owners.delete(tab.source);
|
|
21486
|
+
}
|
|
21487
|
+
tab.source = file;
|
|
21488
|
+
this.owners.set(file, tabId);
|
|
21489
|
+
const thinking = mapThinkingStatus(raw, tab.agentId);
|
|
21490
|
+
const statusChanged = thinking !== null && thinking !== tab.thinkingStatus && (thinking === "available" || tab.thinkingStatus === "waiting");
|
|
21491
|
+
if (statusChanged) tab.thinkingStatus = thinking;
|
|
21492
|
+
const events = normalizeMapEvents(raw, file, tab.agentId);
|
|
21493
|
+
for (const e of events) {
|
|
21494
|
+
tab.sources.set(e.source, file);
|
|
21495
|
+
const id = tab.tree.ingest(e);
|
|
21496
|
+
if (id && this.settings.engine === "clef-flash") {
|
|
21497
|
+
tab.pending.add(id);
|
|
21498
|
+
if (tab.pending.size > 24) tab.pending.delete(tab.pending.values().next().value);
|
|
21499
|
+
}
|
|
21500
|
+
}
|
|
21501
|
+
const retainedSources = new Set(tab.tree.nodes.flatMap((n) => n.evidence.map((e) => e.source)));
|
|
21502
|
+
for (const name of tab.sources.keys()) if (!retainedSources.has(name)) tab.sources.delete(name);
|
|
21503
|
+
if (events.length || statusChanged) {
|
|
21504
|
+
this.publish();
|
|
21505
|
+
this.schedule();
|
|
21506
|
+
}
|
|
21507
|
+
}
|
|
21508
|
+
});
|
|
21509
|
+
tab.tailer.start();
|
|
21510
|
+
}
|
|
21511
|
+
schedule() {
|
|
21512
|
+
if (this.timer || this.working || !this.settings.enabled || this.settings.engine !== "clef-flash" || this.clef.state === "error") return;
|
|
21513
|
+
if (![...this.tabs.values()].some((t) => t.pending.size)) return;
|
|
21514
|
+
this.timer = setTimeout(() => {
|
|
21515
|
+
this.timer = null;
|
|
21516
|
+
void this.infer();
|
|
21517
|
+
}, 1e3);
|
|
21518
|
+
}
|
|
21519
|
+
async infer() {
|
|
21520
|
+
const candidates = [...this.tabs].filter(([, t]) => t.pending.size && !t.ended);
|
|
21521
|
+
const candidate = candidates[this.round++ % candidates.length];
|
|
21522
|
+
if (!candidate || !this.settings.enabled || this.settings.engine !== "clef-flash") return;
|
|
21523
|
+
const [tabId, tab] = candidate;
|
|
21524
|
+
const tree = tab.tree;
|
|
21525
|
+
const ids = [...tab.pending].slice(0, 2);
|
|
21526
|
+
const job = tree.request(ids);
|
|
21527
|
+
const consumed = job?.ids || ids;
|
|
21528
|
+
consumed.forEach((id) => tab.pending.delete(id));
|
|
21529
|
+
if (!job) {
|
|
21530
|
+
this.schedule();
|
|
21531
|
+
return;
|
|
21532
|
+
}
|
|
21533
|
+
const epoch = this.epoch;
|
|
21534
|
+
this.working = true;
|
|
21535
|
+
try {
|
|
21536
|
+
const answers = await this.clef.predict(this.settings, job.request);
|
|
21537
|
+
if (epoch === this.epoch && this.tabs.get(tabId) === tab && !tab.ended && tab.tree === tree) tree.apply(job, answers);
|
|
21538
|
+
} catch {
|
|
21539
|
+
} finally {
|
|
21540
|
+
this.working = false;
|
|
21541
|
+
this.publish();
|
|
21542
|
+
this.schedule();
|
|
21543
|
+
}
|
|
21544
|
+
}
|
|
21545
|
+
publish() {
|
|
21546
|
+
if (this.publishing) return;
|
|
21547
|
+
this.publishing = setTimeout(() => {
|
|
21548
|
+
this.publishing = null;
|
|
21549
|
+
for (const tabId of this.tabs.keys()) this.onUpdate(this.snapshot(tabId));
|
|
21550
|
+
}, 120);
|
|
21551
|
+
}
|
|
21552
|
+
shutdown() {
|
|
21553
|
+
this.settings.enabled = false;
|
|
21554
|
+
++this.epoch;
|
|
21555
|
+
for (const tab of this.tabs.values()) tab.tailer?.stop();
|
|
21556
|
+
this.clef.stop();
|
|
21557
|
+
if (this.timer) clearTimeout(this.timer);
|
|
21558
|
+
if (this.publishing) clearTimeout(this.publishing);
|
|
21559
|
+
this.timer = this.publishing = null;
|
|
21560
|
+
this.tabs.clear();
|
|
21561
|
+
this.owners.clear();
|
|
21562
|
+
}
|
|
21563
|
+
};
|
|
21564
|
+
|
|
20685
21565
|
// src/main/orchestrator.ts
|
|
21566
|
+
var import_check_work_map_gpu2 = __toESM(require_check_work_map_gpu());
|
|
20686
21567
|
var DEFAULT_IDLE_TIMEOUT_MIN = 15;
|
|
21568
|
+
var OBSERVER_ON_HOLD = true;
|
|
20687
21569
|
var DEFAULT_PROFILES = {
|
|
20688
21570
|
production: {
|
|
20689
21571
|
name: "Production",
|
|
@@ -20706,6 +21588,7 @@ var Orchestrator = class {
|
|
|
20706
21588
|
timeTracker = new TimeTracker(this.ctlsurfApi);
|
|
20707
21589
|
ticketStore = new TicketStore(this.ctlsurfApi);
|
|
20708
21590
|
observer;
|
|
21591
|
+
workMap;
|
|
20709
21592
|
// State
|
|
20710
21593
|
tabs = /* @__PURE__ */ new Map();
|
|
20711
21594
|
activeTabId = null;
|
|
@@ -20725,19 +21608,24 @@ var Orchestrator = class {
|
|
|
20725
21608
|
// until the next remote spawn for that target consumes them — never persisted.
|
|
20726
21609
|
pendingSshPasswords = /* @__PURE__ */ new Map();
|
|
20727
21610
|
pendingSshPasswordTimers = /* @__PURE__ */ new Map();
|
|
20728
|
-
constructor(settingsDir, events) {
|
|
21611
|
+
constructor(settingsDir, events, options) {
|
|
20729
21612
|
this.settingsDir = settingsDir;
|
|
20730
21613
|
this.events = events;
|
|
21614
|
+
this.workMap = new WorkMap(options?.appRoot || process.cwd(), (snapshot) => this.events.onWorkMapUpdate?.(snapshot), {}, (0, import_check_work_map_gpu2.initialCapability)({ desktop: options?.desktop === true }));
|
|
20731
21615
|
this.observer = new Observer({
|
|
20732
21616
|
settingsDir,
|
|
20733
21617
|
api: this.ctlsurfApi,
|
|
20734
|
-
getSettings: () =>
|
|
21618
|
+
getSettings: () => ({
|
|
21619
|
+
...this.settings.observer,
|
|
21620
|
+
enabled: !OBSERVER_ON_HOLD && !!this.settings.observer?.enabled
|
|
21621
|
+
}),
|
|
20735
21622
|
onUpdate: (item) => this.events.onObserverUpdate?.(item),
|
|
20736
21623
|
onActivity: (reviewing) => this.events.onObserverActivity?.(reviewing)
|
|
20737
21624
|
});
|
|
20738
21625
|
this.stateMonitor = new AgentStateMonitor(({ tabId, state, anyAwaiting }) => {
|
|
20739
21626
|
this.events.onAgentStateChange?.(tabId, state, anyAwaiting);
|
|
20740
21627
|
this.observer.noteAgentState(tabId, state);
|
|
21628
|
+
this.workMap.noteState(tabId, state);
|
|
20741
21629
|
});
|
|
20742
21630
|
this.stateMonitor.start();
|
|
20743
21631
|
this.workerWs = new WorkerWsClient({
|
|
@@ -20809,13 +21697,24 @@ var Orchestrator = class {
|
|
|
20809
21697
|
this.saveSettings();
|
|
20810
21698
|
this.restartActiveChatLogging();
|
|
20811
21699
|
}
|
|
21700
|
+
configureWorkMap(data) {
|
|
21701
|
+
const capability = this.workMap.getCapability();
|
|
21702
|
+
if (data.enabled && capability.state !== "available") throw new Error(capability.reason || "Work map is unavailable on this machine.");
|
|
21703
|
+
this.settings.workMap = this.workMap.configure(data);
|
|
21704
|
+
this.saveSettings();
|
|
21705
|
+
}
|
|
21706
|
+
setWorkMapCapability(capability) {
|
|
21707
|
+
this.workMap.setCapability(capability);
|
|
21708
|
+
this.workMap.configure({ ...this.settings.workMap, enabled: capability.state === "available" && !!this.settings.workMap?.enabled });
|
|
21709
|
+
this.events.onWorkMapCapability?.(capability);
|
|
21710
|
+
}
|
|
20812
21711
|
// ─── Observer settings ──────────────────────────
|
|
20813
21712
|
/** Summary for the settings UI — never echoes the API key back. */
|
|
20814
21713
|
getObserverSettingsSummary() {
|
|
20815
21714
|
const o = this.settings.observer || {};
|
|
20816
21715
|
const keySource = o.anthropicApiKey ? "settings" : process.env.ANTHROPIC_API_KEY ? "env" : null;
|
|
20817
21716
|
return {
|
|
20818
|
-
enabled: !!o.enabled,
|
|
21717
|
+
enabled: !OBSERVER_ON_HOLD && !!o.enabled,
|
|
20819
21718
|
hasApiKey: keySource !== null,
|
|
20820
21719
|
keySource,
|
|
20821
21720
|
dailyCapUsd: o.dailyCapUsd ?? OBSERVER_DEFAULTS.dailyCapUsd
|
|
@@ -20934,13 +21833,13 @@ var Orchestrator = class {
|
|
|
20934
21833
|
}
|
|
20935
21834
|
loadSettings() {
|
|
20936
21835
|
try {
|
|
20937
|
-
|
|
21836
|
+
fs10.mkdirSync(this.settingsDir, { recursive: true });
|
|
20938
21837
|
} catch {
|
|
20939
21838
|
}
|
|
20940
|
-
const settingsPath =
|
|
21839
|
+
const settingsPath = path14.join(this.settingsDir, "settings.json");
|
|
20941
21840
|
try {
|
|
20942
|
-
if (
|
|
20943
|
-
const raw = JSON.parse(
|
|
21841
|
+
if (fs10.existsSync(settingsPath)) {
|
|
21842
|
+
const raw = JSON.parse(fs10.readFileSync(settingsPath, "utf-8"));
|
|
20944
21843
|
if (!raw.profiles) {
|
|
20945
21844
|
this.settings = {
|
|
20946
21845
|
activeProfile: "production",
|
|
@@ -20975,12 +21874,13 @@ var Orchestrator = class {
|
|
|
20975
21874
|
}
|
|
20976
21875
|
this.applyProfile(this.getActiveProfile());
|
|
20977
21876
|
this.bridge.setLoggingEnabled(!!this.settings.logChat);
|
|
21877
|
+
this.workMap.configure({ ...this.settings.workMap, enabled: !!this.events.onWorkMapUpdate && !!this.settings.workMap?.enabled });
|
|
20978
21878
|
}
|
|
20979
21879
|
saveSettings() {
|
|
20980
|
-
const settingsPath =
|
|
21880
|
+
const settingsPath = path14.join(this.settingsDir, "settings.json");
|
|
20981
21881
|
try {
|
|
20982
|
-
|
|
20983
|
-
|
|
21882
|
+
fs10.mkdirSync(this.settingsDir, { recursive: true });
|
|
21883
|
+
fs10.writeFileSync(settingsPath, JSON.stringify(this.settings, null, 2));
|
|
20984
21884
|
} catch (err) {
|
|
20985
21885
|
log("[settings] Failed to save:", err.message);
|
|
20986
21886
|
}
|
|
@@ -21121,6 +22021,7 @@ var Orchestrator = class {
|
|
|
21121
22021
|
this.tabs.delete(tabId);
|
|
21122
22022
|
this.stateMonitor.removeTab(tabId);
|
|
21123
22023
|
void this.observer.endTab(tabId, existing.projectRootPath);
|
|
22024
|
+
this.workMap.remove(tabId);
|
|
21124
22025
|
existing.ptyManager.kill();
|
|
21125
22026
|
await this.timeTracker.endSession(tabId);
|
|
21126
22027
|
}
|
|
@@ -21135,6 +22036,7 @@ var Orchestrator = class {
|
|
|
21135
22036
|
const tab = { ptyManager, agent, cwd, termStreamBuffer: "", termStreamTimer: null };
|
|
21136
22037
|
this.tabs.set(tabId, tab);
|
|
21137
22038
|
this.stateMonitor.trackTab(tabId, agent.id, cwd);
|
|
22039
|
+
this.workMap.attach(tabId, agent.id, agent.name, cwd);
|
|
21138
22040
|
if (isRemoteAgent(agent)) {
|
|
21139
22041
|
const target = agent.args[1];
|
|
21140
22042
|
const pw = target ? this.consumePendingSshPassword(target) : void 0;
|
|
@@ -21156,6 +22058,7 @@ var Orchestrator = class {
|
|
|
21156
22058
|
if (this.tabs.get(tabId) !== tab) return;
|
|
21157
22059
|
this.events.onPtyExit(tabId, exitCode);
|
|
21158
22060
|
const observerEnd = this.observer.endTab(tabId, tab.projectRootPath);
|
|
22061
|
+
this.workMap.end(tabId);
|
|
21159
22062
|
await this.timeTracker.endSession(tabId);
|
|
21160
22063
|
void observerEnd;
|
|
21161
22064
|
if (this.tabs.get(tabId) !== tab) return;
|
|
@@ -21207,6 +22110,7 @@ var Orchestrator = class {
|
|
|
21207
22110
|
}
|
|
21208
22111
|
async killTab(tabId) {
|
|
21209
22112
|
const tab = this.tabs.get(tabId);
|
|
22113
|
+
this.workMap.remove(tabId);
|
|
21210
22114
|
if (!tab) return;
|
|
21211
22115
|
if (tab.termStreamTimer) clearTimeout(tab.termStreamTimer);
|
|
21212
22116
|
await this.timeTracker.endSession(tabId);
|
|
@@ -21338,7 +22242,7 @@ var Orchestrator = class {
|
|
|
21338
22242
|
}
|
|
21339
22243
|
this.stopNoProjectPolling();
|
|
21340
22244
|
this.workerWs.connect({
|
|
21341
|
-
machine:
|
|
22245
|
+
machine: os5.hostname(),
|
|
21342
22246
|
cwd,
|
|
21343
22247
|
agent: agent.name
|
|
21344
22248
|
});
|
|
@@ -21407,6 +22311,7 @@ var Orchestrator = class {
|
|
|
21407
22311
|
}
|
|
21408
22312
|
// ─── Shutdown ───────────────────────────────────
|
|
21409
22313
|
async shutdown() {
|
|
22314
|
+
this.workMap.shutdown();
|
|
21410
22315
|
this.stopNoProjectPolling();
|
|
21411
22316
|
this.stopChatLogging();
|
|
21412
22317
|
this.stateMonitor.stop();
|
|
@@ -21422,18 +22327,18 @@ var Orchestrator = class {
|
|
|
21422
22327
|
};
|
|
21423
22328
|
|
|
21424
22329
|
// src/main/settingsDir.ts
|
|
21425
|
-
import
|
|
21426
|
-
import
|
|
22330
|
+
import path15 from "path";
|
|
22331
|
+
import os6 from "os";
|
|
21427
22332
|
function getSettingsDir(useElectron) {
|
|
21428
22333
|
if (useElectron) {
|
|
21429
22334
|
const { app } = require_electron();
|
|
21430
22335
|
return app.getPath("userData");
|
|
21431
22336
|
}
|
|
21432
22337
|
if (process.platform === "darwin") {
|
|
21433
|
-
return
|
|
22338
|
+
return path15.join(os6.homedir(), "Library", "Application Support", "ctlsurf-worker");
|
|
21434
22339
|
}
|
|
21435
|
-
return
|
|
21436
|
-
process.env.XDG_CONFIG_HOME ||
|
|
22340
|
+
return path15.join(
|
|
22341
|
+
process.env.XDG_CONFIG_HOME || path15.join(os6.homedir(), ".config"),
|
|
21437
22342
|
"ctlsurf-worker"
|
|
21438
22343
|
);
|
|
21439
22344
|
}
|
|
@@ -21737,9 +22642,9 @@ var Tui = class {
|
|
|
21737
22642
|
|
|
21738
22643
|
// src/main/monitor.ts
|
|
21739
22644
|
import { execFile as execFile2 } from "child_process";
|
|
21740
|
-
import
|
|
21741
|
-
import
|
|
21742
|
-
import
|
|
22645
|
+
import fs11 from "fs";
|
|
22646
|
+
import os7 from "os";
|
|
22647
|
+
import path16 from "path";
|
|
21743
22648
|
var FAST_MS = 3e3;
|
|
21744
22649
|
var SLOW_MS = 1e4;
|
|
21745
22650
|
var IDLE_STOP_MS = 12e4;
|
|
@@ -21764,7 +22669,7 @@ function run(cmd, args, opts = {}) {
|
|
|
21764
22669
|
});
|
|
21765
22670
|
}
|
|
21766
22671
|
function serviceFor(agent) {
|
|
21767
|
-
const cmd = isRemoteAgent(agent) ? agent.args.slice(2).join(" ") :
|
|
22672
|
+
const cmd = isRemoteAgent(agent) ? agent.args.slice(2).join(" ") : path16.basename(agent.command);
|
|
21768
22673
|
if (/\bcodex\b/.test(cmd)) return "codex";
|
|
21769
22674
|
if (/\bclaude\b/.test(cmd)) return "claude";
|
|
21770
22675
|
return null;
|
|
@@ -21772,7 +22677,7 @@ function serviceFor(agent) {
|
|
|
21772
22677
|
function cpuTimes() {
|
|
21773
22678
|
let idle = 0;
|
|
21774
22679
|
let total = 0;
|
|
21775
|
-
for (const c of
|
|
22680
|
+
for (const c of os7.cpus()) {
|
|
21776
22681
|
const t = c.times;
|
|
21777
22682
|
idle += t.idle;
|
|
21778
22683
|
total += t.user + t.nice + t.sys + t.idle + t.irq;
|
|
@@ -21780,8 +22685,8 @@ function cpuTimes() {
|
|
|
21780
22685
|
return { idle, total };
|
|
21781
22686
|
}
|
|
21782
22687
|
async function memory() {
|
|
21783
|
-
const total =
|
|
21784
|
-
let avail =
|
|
22688
|
+
const total = os7.totalmem();
|
|
22689
|
+
let avail = os7.freemem();
|
|
21785
22690
|
try {
|
|
21786
22691
|
if (process.platform === "darwin") {
|
|
21787
22692
|
const out = await run("vm_stat", []);
|
|
@@ -21789,7 +22694,7 @@ async function memory() {
|
|
|
21789
22694
|
const pages = (name) => Number(new RegExp(`Pages ${name}:\\s+(\\d+)`).exec(out)?.[1] ?? 0);
|
|
21790
22695
|
avail = (pages("free") + pages("inactive") + pages("speculative")) * page;
|
|
21791
22696
|
} else if (process.platform === "linux") {
|
|
21792
|
-
const m = /MemAvailable:\s+(\d+) kB/.exec(await
|
|
22697
|
+
const m = /MemAvailable:\s+(\d+) kB/.exec(await fs11.promises.readFile("/proc/meminfo", "utf-8"));
|
|
21793
22698
|
if (m) avail = Number(m[1]) * 1024;
|
|
21794
22699
|
}
|
|
21795
22700
|
} catch {
|
|
@@ -21798,9 +22703,9 @@ async function memory() {
|
|
|
21798
22703
|
return { total, used: total - avail, pct: round(100 * (total - avail) / total) };
|
|
21799
22704
|
}
|
|
21800
22705
|
async function diskUsage() {
|
|
21801
|
-
if (typeof
|
|
22706
|
+
if (typeof fs11.promises.statfs !== "function") return null;
|
|
21802
22707
|
try {
|
|
21803
|
-
const s = await
|
|
22708
|
+
const s = await fs11.promises.statfs(os7.homedir());
|
|
21804
22709
|
return { total: s.blocks * s.bsize, free: s.bavail * s.bsize };
|
|
21805
22710
|
} catch {
|
|
21806
22711
|
return null;
|
|
@@ -21888,7 +22793,7 @@ async function claudeUsage() {
|
|
|
21888
22793
|
const out = await run(
|
|
21889
22794
|
"claude",
|
|
21890
22795
|
["-p", "/usage", "--output-format", "json", "--no-session-persistence"],
|
|
21891
|
-
{ cwd:
|
|
22796
|
+
{ cwd: os7.tmpdir(), timeout: 4e4 }
|
|
21892
22797
|
);
|
|
21893
22798
|
text = String(JSON.parse(out || "{}").result ?? "");
|
|
21894
22799
|
} catch (err) {
|
|
@@ -21924,7 +22829,7 @@ function codexWindow(minutes) {
|
|
|
21924
22829
|
return { label: s, short: s };
|
|
21925
22830
|
}
|
|
21926
22831
|
async function readTail(file, bytes) {
|
|
21927
|
-
const fh = await
|
|
22832
|
+
const fh = await fs11.promises.open(file, "r");
|
|
21928
22833
|
try {
|
|
21929
22834
|
const { size } = await fh.stat();
|
|
21930
22835
|
const start = Math.max(0, size - bytes);
|
|
@@ -21936,7 +22841,7 @@ async function readTail(file, bytes) {
|
|
|
21936
22841
|
}
|
|
21937
22842
|
}
|
|
21938
22843
|
async function codexSessionFiles(root, limit2) {
|
|
21939
|
-
const sortedDirs = async (dir) => (await
|
|
22844
|
+
const sortedDirs = async (dir) => (await fs11.promises.readdir(dir, { withFileTypes: true }).catch(() => [])).filter((d) => d.isDirectory()).map((d) => path16.join(dir, d.name)).sort().reverse();
|
|
21940
22845
|
const days = [];
|
|
21941
22846
|
for (const y of await sortedDirs(root)) {
|
|
21942
22847
|
for (const m of await sortedDirs(y)) {
|
|
@@ -21947,20 +22852,20 @@ async function codexSessionFiles(root, limit2) {
|
|
|
21947
22852
|
}
|
|
21948
22853
|
const files = [];
|
|
21949
22854
|
for (const day of days.slice(0, 14)) {
|
|
21950
|
-
for (const name of await
|
|
22855
|
+
for (const name of await fs11.promises.readdir(day).catch(() => [])) {
|
|
21951
22856
|
if (!name.startsWith("rollout-") || !name.endsWith(".jsonl")) continue;
|
|
21952
|
-
const file =
|
|
22857
|
+
const file = path16.join(day, name);
|
|
21953
22858
|
try {
|
|
21954
|
-
files.push({ file, mtime: (await
|
|
22859
|
+
files.push({ file, mtime: (await fs11.promises.stat(file)).mtimeMs });
|
|
21955
22860
|
} catch {
|
|
21956
22861
|
}
|
|
21957
22862
|
}
|
|
21958
22863
|
}
|
|
21959
22864
|
return files.sort((a, b) => b.mtime - a.mtime).slice(0, limit2).map((f) => f.file);
|
|
21960
22865
|
}
|
|
21961
|
-
async function codexUsage(home =
|
|
22866
|
+
async function codexUsage(home = os7.homedir()) {
|
|
21962
22867
|
const source = "your most recent Codex session";
|
|
21963
|
-
for (const file of await codexSessionFiles(
|
|
22868
|
+
for (const file of await codexSessionFiles(path16.join(home, ".codex", "sessions"), 5)) {
|
|
21964
22869
|
let lines;
|
|
21965
22870
|
try {
|
|
21966
22871
|
lines = (await readTail(file, CODEX_TAIL_BYTES)).split("\n");
|
|
@@ -21985,7 +22890,7 @@ async function codexUsage(home = os6.homedir()) {
|
|
|
21985
22890
|
windows.push({ label, short, pct: Number(w.used_percent), resets });
|
|
21986
22891
|
}
|
|
21987
22892
|
if (windows.length) {
|
|
21988
|
-
const ts = new Date((await
|
|
22893
|
+
const ts = new Date((await fs11.promises.stat(file)).mtimeMs).toISOString();
|
|
21989
22894
|
return { ok: true, windows, plan: rl.plan_type ?? null, source, ts };
|
|
21990
22895
|
}
|
|
21991
22896
|
}
|
|
@@ -22055,7 +22960,7 @@ var ResourceMonitor = class {
|
|
|
22055
22960
|
this.machine = {
|
|
22056
22961
|
ok: true,
|
|
22057
22962
|
ts: now(),
|
|
22058
|
-
cpu: { pct: cpuPct, cores:
|
|
22963
|
+
cpu: { pct: cpuPct, cores: os7.cpus().length, load: os7.loadavg().map((x) => round(x, 2)) },
|
|
22059
22964
|
mem,
|
|
22060
22965
|
disk,
|
|
22061
22966
|
gpu
|
|
@@ -22093,7 +22998,7 @@ var ResourceMonitor = class {
|
|
|
22093
22998
|
return {
|
|
22094
22999
|
machine: {
|
|
22095
23000
|
...this.machine,
|
|
22096
|
-
label: process.platform === "darwin" ? "This Mac" :
|
|
23001
|
+
label: process.platform === "darwin" ? "This Mac" : os7.hostname(),
|
|
22097
23002
|
history: [...this.history]
|
|
22098
23003
|
},
|
|
22099
23004
|
agents: this.agents,
|