@notis_ai/cli 0.2.0-beta.146.1 → 0.2.0-beta.151.1
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 +2 -2
- package/dist/agent-hooks/notis-agent-hook.mjs +542 -146
- package/dist/base-skills/notis-apps/SKILL.md +12 -9
- package/package.json +1 -1
- package/skills/notis-apps/cli.md +2 -2
- package/src/command-specs/apps.js +118 -18
- package/src/runtime/app-dev-process-identity.js +111 -0
- package/src/runtime/app-dev-server.js +236 -8
- package/src/runtime/app-platform.js +23 -1
- package/template/packages/sdk/src/config.ts +6 -0
- package/template/packages/sdk/src/hooks/useNotis.ts +3 -0
- package/template/packages/sdk/src/hooks/useNotisNavigation.ts +7 -4
- package/template/packages/sdk/src/runtime.ts +3 -0
|
@@ -4564,7 +4564,7 @@ var init_skill_sync = __esm({
|
|
|
4564
4564
|
});
|
|
4565
4565
|
|
|
4566
4566
|
// src/cli.js
|
|
4567
|
-
import { readFileSync as
|
|
4567
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
4568
4568
|
import { dirname as dirname18, join as join19 } from "node:path";
|
|
4569
4569
|
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
4570
4570
|
|
|
@@ -4587,7 +4587,7 @@ var {
|
|
|
4587
4587
|
|
|
4588
4588
|
// src/command-specs/apps.js
|
|
4589
4589
|
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
4590
|
-
import { mkdirSync as
|
|
4590
|
+
import { mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync3, readFileSync as readFileSync13, readdirSync as readdirSync6, rmSync as rmSync10 } from "node:fs";
|
|
4591
4591
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
4592
4592
|
import { basename as basename3, isAbsolute as isAbsolute2, join as join12, relative as relative3, resolve as resolve7, sep } from "node:path";
|
|
4593
4593
|
|
|
@@ -5849,6 +5849,9 @@ function validateConfiguredRoutes(routes) {
|
|
|
5849
5849
|
if (route.default) {
|
|
5850
5850
|
defaultCount += 1;
|
|
5851
5851
|
}
|
|
5852
|
+
if (route.resourceDeepLinks !== void 0 && typeof route.resourceDeepLinks !== "boolean") {
|
|
5853
|
+
throw usageError(`Route "${route.slug}" resourceDeepLinks must be a boolean.`);
|
|
5854
|
+
}
|
|
5852
5855
|
}
|
|
5853
5856
|
if (defaultCount !== 1) {
|
|
5854
5857
|
throw usageError(`Expected exactly one default route, received ${defaultCount}.`);
|
|
@@ -5940,6 +5943,7 @@ function generateManifest(appConfig, projectDir) {
|
|
|
5940
5943
|
icon: route.icon || null,
|
|
5941
5944
|
parentSlug: route.parentSlug || null,
|
|
5942
5945
|
default: route.default || false,
|
|
5946
|
+
resourceDeepLinks: route.resourceDeepLinks === true,
|
|
5943
5947
|
export_name: route.exportName || route.export_name || exportNameFromPath(route.path),
|
|
5944
5948
|
collection: route.collection || null
|
|
5945
5949
|
};
|
|
@@ -6031,6 +6035,14 @@ function generateManifest(appConfig, projectDir) {
|
|
|
6031
6035
|
onboarding: appConfig.onboarding || null
|
|
6032
6036
|
};
|
|
6033
6037
|
}
|
|
6038
|
+
function appRowFieldsFromManifest(manifest) {
|
|
6039
|
+
const app = manifest?.app && typeof manifest.app === "object" ? manifest.app : {};
|
|
6040
|
+
const displayName = typeof app.title === "string" && app.title.trim() ? app.title.trim() : typeof app.name === "string" && app.name.trim() ? app.name.trim() : null;
|
|
6041
|
+
return {
|
|
6042
|
+
...displayName ? { name: displayName } : {},
|
|
6043
|
+
accent: app.accent ?? null
|
|
6044
|
+
};
|
|
6045
|
+
}
|
|
6034
6046
|
function normalizeAppToolBindings(bindings) {
|
|
6035
6047
|
return (Array.isArray(bindings) ? bindings : []).map((binding) => {
|
|
6036
6048
|
const name = typeof binding?.name === "string" ? binding.name.trim() : "";
|
|
@@ -7642,7 +7654,7 @@ async function updateAppVersion(supabaseUrl, supabaseKey, appId, newVersion, man
|
|
|
7642
7654
|
},
|
|
7643
7655
|
body: JSON.stringify({
|
|
7644
7656
|
manifest: { ...manifest, version: newVersion },
|
|
7645
|
-
|
|
7657
|
+
...appRowFieldsFromManifest(manifest),
|
|
7646
7658
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
7647
7659
|
})
|
|
7648
7660
|
});
|
|
@@ -7709,9 +7721,20 @@ async function directDeploy(projectDir, appId) {
|
|
|
7709
7721
|
|
|
7710
7722
|
// src/runtime/app-dev-server.js
|
|
7711
7723
|
import { createServer } from "node:http";
|
|
7712
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
7713
|
-
import {
|
|
7724
|
+
import { execFileSync as execFileSync2, spawn as spawn2 } from "node:child_process";
|
|
7725
|
+
import {
|
|
7726
|
+
appendFileSync,
|
|
7727
|
+
existsSync as existsSync7,
|
|
7728
|
+
mkdirSync as mkdirSync5,
|
|
7729
|
+
readFileSync as readFileSync7,
|
|
7730
|
+
renameSync as renameSync4,
|
|
7731
|
+
rmSync as rmSync5,
|
|
7732
|
+
statSync as statSync3,
|
|
7733
|
+
watch as fsWatch
|
|
7734
|
+
} from "node:fs";
|
|
7735
|
+
import { freemem, loadavg, totalmem } from "node:os";
|
|
7714
7736
|
import { dirname as dirname6, join as join7, resolve as resolve5 } from "node:path";
|
|
7737
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
7715
7738
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7716
7739
|
|
|
7717
7740
|
// src/runtime/app-dev-sessions.js
|
|
@@ -8783,6 +8806,100 @@ function removeAppDevSession(sessionId, filePath) {
|
|
|
8783
8806
|
return result;
|
|
8784
8807
|
}
|
|
8785
8808
|
|
|
8809
|
+
// src/runtime/app-dev-process-identity.js
|
|
8810
|
+
import { execFileSync } from "node:child_process";
|
|
8811
|
+
import { readlinkSync, realpathSync as realpathSync2 } from "node:fs";
|
|
8812
|
+
var NOTIS_APP_BUILD_COMMAND_FINGERPRINT = "npm:run-build:watch:v1";
|
|
8813
|
+
var NOTIS_APPS_DEV_HOST_COMMAND_FINGERPRINT = "notis:apps-dev:v1";
|
|
8814
|
+
function normalizePath(value) {
|
|
8815
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
8816
|
+
try {
|
|
8817
|
+
return realpathSync2(value.trim());
|
|
8818
|
+
} catch {
|
|
8819
|
+
return null;
|
|
8820
|
+
}
|
|
8821
|
+
}
|
|
8822
|
+
function isExpectedNotisBuildCommand(command) {
|
|
8823
|
+
if (typeof command !== "string") return false;
|
|
8824
|
+
return /(?:^|[/\s])npm(?:\s|$)/.test(command) && /\brun\s+build\b/.test(command) && /(?:^|\s)--watch(?:\s|$)/.test(command);
|
|
8825
|
+
}
|
|
8826
|
+
function isExpectedNotisAppsDevHostCommand(command) {
|
|
8827
|
+
return typeof command === "string" && /(?:notis(?:\.js)?|@notis_ai[/\\]cli)/.test(command) && /\bapps\s+dev\b/.test(command);
|
|
8828
|
+
}
|
|
8829
|
+
function readDarwinProcessCwd(pid, execute) {
|
|
8830
|
+
const output = execute("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], {
|
|
8831
|
+
encoding: "utf8",
|
|
8832
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
8833
|
+
});
|
|
8834
|
+
const line = output.split("\n").find((entry) => entry.startsWith("n"));
|
|
8835
|
+
return line ? line.slice(1) : null;
|
|
8836
|
+
}
|
|
8837
|
+
function inspectAppDevWatcherProcess(pid, {
|
|
8838
|
+
platform = process.platform,
|
|
8839
|
+
execute = execFileSync,
|
|
8840
|
+
readLink = readlinkSync
|
|
8841
|
+
} = {}) {
|
|
8842
|
+
if (!Number.isSafeInteger(pid) || pid <= 0 || platform === "win32") return null;
|
|
8843
|
+
try {
|
|
8844
|
+
const ps = (field) => execute("ps", ["-o", `${field}=`, "-p", String(pid)], {
|
|
8845
|
+
encoding: "utf8",
|
|
8846
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
8847
|
+
}).trim();
|
|
8848
|
+
const processGroupPid = Number.parseInt(ps("pgid"), 10);
|
|
8849
|
+
const startIdentity = ps("lstart").replace(/\s+/g, " ").trim();
|
|
8850
|
+
const command = ps("command");
|
|
8851
|
+
const cwd = platform === "linux" ? readLink(`/proc/${pid}/cwd`) : readDarwinProcessCwd(pid, execute);
|
|
8852
|
+
const projectDir = normalizePath(cwd);
|
|
8853
|
+
if (!Number.isSafeInteger(processGroupPid) || processGroupPid <= 0) return null;
|
|
8854
|
+
if (!startIdentity || !command || !projectDir) return null;
|
|
8855
|
+
return { pid, processGroupPid, startIdentity, command, projectDir };
|
|
8856
|
+
} catch {
|
|
8857
|
+
return null;
|
|
8858
|
+
}
|
|
8859
|
+
}
|
|
8860
|
+
function captureDesktopWatcherOwnership({
|
|
8861
|
+
pid,
|
|
8862
|
+
projectDir,
|
|
8863
|
+
desktopOwnerId,
|
|
8864
|
+
desktopOwnerScope,
|
|
8865
|
+
inspect: inspect2 = inspectAppDevWatcherProcess
|
|
8866
|
+
} = {}) {
|
|
8867
|
+
const owner = typeof desktopOwnerId === "string" ? desktopOwnerId.trim() : "";
|
|
8868
|
+
const ownerScope = typeof desktopOwnerScope === "string" ? desktopOwnerScope.trim() : "";
|
|
8869
|
+
const expectedProjectDir = normalizePath(projectDir);
|
|
8870
|
+
if (!owner || !ownerScope || !expectedProjectDir) return null;
|
|
8871
|
+
const identity = inspect2(pid);
|
|
8872
|
+
if (!identity || identity.processGroupPid !== pid || identity.projectDir !== expectedProjectDir || !isExpectedNotisBuildCommand(identity.command)) {
|
|
8873
|
+
return null;
|
|
8874
|
+
}
|
|
8875
|
+
return {
|
|
8876
|
+
desktopOwnerId: owner,
|
|
8877
|
+
desktopOwnerScope: ownerScope,
|
|
8878
|
+
watcherProcessGroupPid: identity.processGroupPid,
|
|
8879
|
+
watcherStartIdentity: identity.startIdentity,
|
|
8880
|
+
watcherProjectDir: identity.projectDir,
|
|
8881
|
+
watcherCommandFingerprint: NOTIS_APP_BUILD_COMMAND_FINGERPRINT
|
|
8882
|
+
};
|
|
8883
|
+
}
|
|
8884
|
+
function captureDesktopHostOwnership({
|
|
8885
|
+
pid = process.pid,
|
|
8886
|
+
desktopOwnerId,
|
|
8887
|
+
desktopOwnerScope,
|
|
8888
|
+
inspect: inspect2 = inspectAppDevWatcherProcess
|
|
8889
|
+
} = {}) {
|
|
8890
|
+
const owner = typeof desktopOwnerId === "string" ? desktopOwnerId.trim() : "";
|
|
8891
|
+
const ownerScope = typeof desktopOwnerScope === "string" ? desktopOwnerScope.trim() : "";
|
|
8892
|
+
if (!owner || !ownerScope) return null;
|
|
8893
|
+
const identity = inspect2(pid);
|
|
8894
|
+
if (!identity || !isExpectedNotisAppsDevHostCommand(identity.command)) return null;
|
|
8895
|
+
return {
|
|
8896
|
+
desktopOwnerId: owner,
|
|
8897
|
+
desktopOwnerScope: ownerScope,
|
|
8898
|
+
desktopHostStartIdentity: identity.startIdentity,
|
|
8899
|
+
desktopHostCommandFingerprint: NOTIS_APPS_DEV_HOST_COMMAND_FINGERPRINT
|
|
8900
|
+
};
|
|
8901
|
+
}
|
|
8902
|
+
|
|
8786
8903
|
// src/runtime/app-dev-server.js
|
|
8787
8904
|
var CONTENT_TYPES = {
|
|
8788
8905
|
".js": "application/javascript; charset=utf-8",
|
|
@@ -8795,6 +8912,100 @@ var CLI_ROOT2 = resolve5(RUNTIME_DIR, "../..");
|
|
|
8795
8912
|
var REPO_ROOT = resolve5(RUNTIME_DIR, "../../../..");
|
|
8796
8913
|
var HARNESS_TEMPLATE_PATH = join7(CLI_ROOT2, "template", ".harness", "index.html.tmpl");
|
|
8797
8914
|
var FALLBACK_REACT_VERSION = "19.0.0";
|
|
8915
|
+
var BUILD_PROCESS_STOP_GRACE_MS = 1e3;
|
|
8916
|
+
var DEV_DIAGNOSTIC_INTERVAL_MS = 3e4;
|
|
8917
|
+
var DEV_DIAGNOSTIC_MAX_BYTES = 20 * 1024 * 1024;
|
|
8918
|
+
function processGroupIsRunning(pid, signalProcess = process.kill) {
|
|
8919
|
+
try {
|
|
8920
|
+
signalProcess(-pid, 0);
|
|
8921
|
+
return true;
|
|
8922
|
+
} catch (error) {
|
|
8923
|
+
return error?.code !== "ESRCH";
|
|
8924
|
+
}
|
|
8925
|
+
}
|
|
8926
|
+
function readProcessGroupRssBytes(groupPids) {
|
|
8927
|
+
if (process.platform === "win32" || groupPids.size === 0) return /* @__PURE__ */ new Map();
|
|
8928
|
+
try {
|
|
8929
|
+
const output = execFileSync2("ps", ["-axo", "pgid=,rss="], {
|
|
8930
|
+
encoding: "utf8",
|
|
8931
|
+
maxBuffer: 1024 * 1024,
|
|
8932
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
8933
|
+
});
|
|
8934
|
+
const rssByGroup = /* @__PURE__ */ new Map();
|
|
8935
|
+
for (const line of output.split("\n")) {
|
|
8936
|
+
const [rawGroupPid, rawRssKiB] = line.trim().split(/\s+/, 2);
|
|
8937
|
+
const groupPid = Number.parseInt(rawGroupPid, 10);
|
|
8938
|
+
if (!groupPids.has(groupPid)) continue;
|
|
8939
|
+
const rssKiB = Number.parseInt(rawRssKiB, 10);
|
|
8940
|
+
if (!Number.isFinite(rssKiB)) continue;
|
|
8941
|
+
rssByGroup.set(groupPid, (rssByGroup.get(groupPid) || 0) + rssKiB * 1024);
|
|
8942
|
+
}
|
|
8943
|
+
return rssByGroup;
|
|
8944
|
+
} catch {
|
|
8945
|
+
return /* @__PURE__ */ new Map();
|
|
8946
|
+
}
|
|
8947
|
+
}
|
|
8948
|
+
async function terminateBuildProcessTree(child, {
|
|
8949
|
+
platform = process.platform,
|
|
8950
|
+
signalProcess = process.kill,
|
|
8951
|
+
spawnProcess = spawn2,
|
|
8952
|
+
graceMs = BUILD_PROCESS_STOP_GRACE_MS
|
|
8953
|
+
} = {}) {
|
|
8954
|
+
const pid = child?.pid;
|
|
8955
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return;
|
|
8956
|
+
if (platform === "win32") {
|
|
8957
|
+
await new Promise((resolvePromise) => {
|
|
8958
|
+
let settled = false;
|
|
8959
|
+
const finish = () => {
|
|
8960
|
+
if (settled) return;
|
|
8961
|
+
settled = true;
|
|
8962
|
+
resolvePromise();
|
|
8963
|
+
};
|
|
8964
|
+
try {
|
|
8965
|
+
const killer = spawnProcess("taskkill.exe", ["/pid", String(pid), "/t", "/f"], {
|
|
8966
|
+
stdio: "ignore",
|
|
8967
|
+
windowsHide: true
|
|
8968
|
+
});
|
|
8969
|
+
killer.once("error", () => {
|
|
8970
|
+
try {
|
|
8971
|
+
child.kill("SIGTERM");
|
|
8972
|
+
} catch {
|
|
8973
|
+
}
|
|
8974
|
+
finish();
|
|
8975
|
+
});
|
|
8976
|
+
killer.once("exit", finish);
|
|
8977
|
+
setTimeout(finish, graceMs).unref?.();
|
|
8978
|
+
} catch {
|
|
8979
|
+
try {
|
|
8980
|
+
child.kill("SIGTERM");
|
|
8981
|
+
} catch {
|
|
8982
|
+
}
|
|
8983
|
+
finish();
|
|
8984
|
+
}
|
|
8985
|
+
});
|
|
8986
|
+
return;
|
|
8987
|
+
}
|
|
8988
|
+
try {
|
|
8989
|
+
signalProcess(-pid, "SIGTERM");
|
|
8990
|
+
} catch (error) {
|
|
8991
|
+
if (error?.code === "ESRCH") return;
|
|
8992
|
+
try {
|
|
8993
|
+
child.kill("SIGTERM");
|
|
8994
|
+
} catch {
|
|
8995
|
+
return;
|
|
8996
|
+
}
|
|
8997
|
+
}
|
|
8998
|
+
const deadline = Date.now() + graceMs;
|
|
8999
|
+
while (processGroupIsRunning(pid, signalProcess) && Date.now() < deadline) {
|
|
9000
|
+
await delay(25);
|
|
9001
|
+
}
|
|
9002
|
+
if (!processGroupIsRunning(pid, signalProcess)) return;
|
|
9003
|
+
try {
|
|
9004
|
+
signalProcess(-pid, "SIGKILL");
|
|
9005
|
+
} catch (error) {
|
|
9006
|
+
if (error?.code !== "ESRCH") throw error;
|
|
9007
|
+
}
|
|
9008
|
+
}
|
|
8798
9009
|
function extFor(pathname) {
|
|
8799
9010
|
const idx = pathname.lastIndexOf(".");
|
|
8800
9011
|
return idx === -1 ? "" : pathname.slice(idx);
|
|
@@ -8966,10 +9177,11 @@ function buildHarnessDescriptor({ state, manifest, appConfig, route, scenario =
|
|
|
8966
9177
|
icon: route.icon || null,
|
|
8967
9178
|
parentSlug: route.parentSlug || null,
|
|
8968
9179
|
default: Boolean(route.default),
|
|
9180
|
+
resourceDeepLinks: route.resourceDeepLinks === true,
|
|
8969
9181
|
collection: route.collection || null
|
|
8970
9182
|
},
|
|
8971
9183
|
databases,
|
|
8972
|
-
context: { collectionItem: null, screenshotScenario: scenario },
|
|
9184
|
+
context: { collectionItem: null, resourceId: null, screenshotScenario: scenario },
|
|
8973
9185
|
tools
|
|
8974
9186
|
};
|
|
8975
9187
|
}
|
|
@@ -9011,6 +9223,10 @@ async function startAppDevServer({
|
|
|
9011
9223
|
watch = true,
|
|
9012
9224
|
sessionsFilePath,
|
|
9013
9225
|
harness = {},
|
|
9226
|
+
diagnosticsFile = process.env.NOTIS_DEV_DIAGNOSTICS_FILE || null,
|
|
9227
|
+
desktopOwnerId = process.env.NOTIS_APPS_DEV_DESKTOP_OWNER_ID || null,
|
|
9228
|
+
desktopOwnerScope = process.env.NOTIS_APPS_DEV_DESKTOP_OWNER_SCOPE || null,
|
|
9229
|
+
terminateBuildProcess = terminateBuildProcessTree,
|
|
9014
9230
|
log = (msg) => process.stdout.write(`${msg}
|
|
9015
9231
|
`),
|
|
9016
9232
|
logError = (msg) => process.stderr.write(`${msg}
|
|
@@ -9020,6 +9236,9 @@ async function startAppDevServer({
|
|
|
9020
9236
|
throw new Error("startAppDevServer requires at least one app.");
|
|
9021
9237
|
}
|
|
9022
9238
|
const appState = /* @__PURE__ */ new Map();
|
|
9239
|
+
let diagnosticsTimer = null;
|
|
9240
|
+
let diagnosticWriteFailed = false;
|
|
9241
|
+
let serverClosing = false;
|
|
9023
9242
|
const hostSseClients = /* @__PURE__ */ new Set();
|
|
9024
9243
|
const createAppState = (app) => {
|
|
9025
9244
|
let resolveBundleReady;
|
|
@@ -9044,16 +9263,65 @@ async function startAppDevServer({
|
|
|
9044
9263
|
prepareTimer: null,
|
|
9045
9264
|
reloadTimer: null,
|
|
9046
9265
|
buildProcess: null,
|
|
9266
|
+
watcherOwnership: null,
|
|
9047
9267
|
lastMtimeMs: 0,
|
|
9048
9268
|
watchPollTimer: null,
|
|
9049
9269
|
bundleReady: false,
|
|
9050
9270
|
bundleReadyPromise,
|
|
9051
|
-
resolveBundleReady
|
|
9271
|
+
resolveBundleReady,
|
|
9272
|
+
buildProcessStopPromise: null
|
|
9052
9273
|
};
|
|
9053
9274
|
};
|
|
9054
9275
|
for (const app of apps) {
|
|
9055
9276
|
appState.set(app.slug, createAppState(app));
|
|
9056
9277
|
}
|
|
9278
|
+
function writeDevDiagnostic(event) {
|
|
9279
|
+
if (!diagnosticsFile) return;
|
|
9280
|
+
const memory = process.memoryUsage();
|
|
9281
|
+
const watcherPids = new Set(
|
|
9282
|
+
[...appState.values()].map((state) => state.buildProcess?.pid).filter((pid) => Number.isSafeInteger(pid) && pid > 0)
|
|
9283
|
+
);
|
|
9284
|
+
const watcherGroupRss = readProcessGroupRssBytes(watcherPids);
|
|
9285
|
+
const record = {
|
|
9286
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9287
|
+
event,
|
|
9288
|
+
host_pid: process.pid,
|
|
9289
|
+
parent_pid: process.ppid,
|
|
9290
|
+
rss_bytes: memory.rss,
|
|
9291
|
+
heap_used_bytes: memory.heapUsed,
|
|
9292
|
+
heap_total_bytes: memory.heapTotal,
|
|
9293
|
+
external_bytes: memory.external,
|
|
9294
|
+
system_free_bytes: freemem(),
|
|
9295
|
+
system_total_bytes: totalmem(),
|
|
9296
|
+
load_average_1m: loadavg()[0],
|
|
9297
|
+
watcher_groups_rss_bytes: [...watcherGroupRss.values()].reduce((total, rss) => total + rss, 0),
|
|
9298
|
+
apps: [...appState.values()].map((state) => ({
|
|
9299
|
+
slug: state.slug,
|
|
9300
|
+
project_dir: state.projectDir,
|
|
9301
|
+
watcher_pid: state.buildProcess?.pid || null,
|
|
9302
|
+
watcher_exit_code: state.buildProcess?.exitCode ?? null,
|
|
9303
|
+
watcher_signal: state.buildProcess?.signalCode ?? null,
|
|
9304
|
+
watcher_group_rss_bytes: watcherGroupRss.get(state.buildProcess?.pid) ?? null,
|
|
9305
|
+
bundle_ready: state.bundleReady
|
|
9306
|
+
}))
|
|
9307
|
+
};
|
|
9308
|
+
try {
|
|
9309
|
+
mkdirSync5(dirname6(diagnosticsFile), { recursive: true, mode: 448 });
|
|
9310
|
+
if (existsSync7(diagnosticsFile) && statSync3(diagnosticsFile).size >= DEV_DIAGNOSTIC_MAX_BYTES) {
|
|
9311
|
+
const previous = `${diagnosticsFile}.previous`;
|
|
9312
|
+
rmSync5(previous, { force: true });
|
|
9313
|
+
renameSync4(diagnosticsFile, previous);
|
|
9314
|
+
}
|
|
9315
|
+
appendFileSync(diagnosticsFile, `${JSON.stringify(record)}
|
|
9316
|
+
`, { mode: 384 });
|
|
9317
|
+
diagnosticWriteFailed = false;
|
|
9318
|
+
} catch (error) {
|
|
9319
|
+
if (!diagnosticWriteFailed) {
|
|
9320
|
+
diagnosticWriteFailed = true;
|
|
9321
|
+
logError(`[notis apps dev] persistent diagnostics failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
9322
|
+
}
|
|
9323
|
+
}
|
|
9324
|
+
}
|
|
9057
9325
|
function broadcastReload(slug) {
|
|
9058
9326
|
const state = appState.get(slug);
|
|
9059
9327
|
if (!state) return;
|
|
@@ -9487,21 +9755,49 @@ data: ${JSON.stringify({ slug, at: Date.now() })}
|
|
|
9487
9755
|
await prepareArtifactBuild(state.projectDir);
|
|
9488
9756
|
watchManifestInputs(state);
|
|
9489
9757
|
pollForBundleAndWatch(state);
|
|
9490
|
-
|
|
9758
|
+
const buildProcess = spawn2("npm", ["run", "build", "--", "--watch"], {
|
|
9491
9759
|
cwd: state.projectDir,
|
|
9760
|
+
detached: process.platform !== "win32",
|
|
9492
9761
|
stdio: "inherit",
|
|
9493
9762
|
env: { ...process.env, NOTIS_DEV: "1" }
|
|
9494
9763
|
});
|
|
9495
|
-
state.buildProcess
|
|
9764
|
+
state.buildProcess = buildProcess;
|
|
9765
|
+
for (let attempt = 0; attempt < 5 && !state.watcherOwnership; attempt += 1) {
|
|
9766
|
+
state.watcherOwnership = captureDesktopWatcherOwnership({
|
|
9767
|
+
pid: buildProcess.pid,
|
|
9768
|
+
projectDir: state.projectDir,
|
|
9769
|
+
desktopOwnerId,
|
|
9770
|
+
desktopOwnerScope
|
|
9771
|
+
});
|
|
9772
|
+
if (!state.watcherOwnership && attempt < 4) await delay(10);
|
|
9773
|
+
}
|
|
9774
|
+
buildProcess.on("exit", (code) => {
|
|
9496
9775
|
if (code !== 0 && code !== null) {
|
|
9497
9776
|
logError(`[notis apps dev] ${state.slug}: vite build --watch exited with code ${code}`);
|
|
9498
9777
|
}
|
|
9778
|
+
if (!serverClosing) {
|
|
9779
|
+
if (state.buildProcess === buildProcess) state.buildProcess = null;
|
|
9780
|
+
const stopPromise = terminateBuildProcess(buildProcess).catch((error) => {
|
|
9781
|
+
logError(`[notis apps dev] ${state.slug}: watcher cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
9782
|
+
});
|
|
9783
|
+
state.buildProcessStopPromise = stopPromise;
|
|
9784
|
+
void stopPromise.finally(() => {
|
|
9785
|
+
if (state.buildProcessStopPromise === stopPromise) {
|
|
9786
|
+
state.buildProcessStopPromise = null;
|
|
9787
|
+
}
|
|
9788
|
+
});
|
|
9789
|
+
}
|
|
9499
9790
|
});
|
|
9500
9791
|
} else {
|
|
9501
9792
|
updateBundleDir(state, resolveBundleDir(state));
|
|
9502
9793
|
}
|
|
9503
9794
|
log(`[notis apps dev] ${state.slug}: serving bundle at http://127.0.0.1:${port}/a/${state.slug}/bundle/app.js`);
|
|
9504
9795
|
}
|
|
9796
|
+
writeDevDiagnostic("started");
|
|
9797
|
+
if (diagnosticsFile) {
|
|
9798
|
+
diagnosticsTimer = setInterval(() => writeDevDiagnostic("sample"), DEV_DIAGNOSTIC_INTERVAL_MS);
|
|
9799
|
+
diagnosticsTimer.unref?.();
|
|
9800
|
+
}
|
|
9505
9801
|
return {
|
|
9506
9802
|
port,
|
|
9507
9803
|
updateApp(slug, updates = {}) {
|
|
@@ -9523,7 +9819,19 @@ data: ${JSON.stringify({ slug, at: Date.now() })}
|
|
|
9523
9819
|
if (!state) return Promise.reject(new Error(`unknown app: ${slug}`));
|
|
9524
9820
|
return state.bundleReadyPromise;
|
|
9525
9821
|
},
|
|
9822
|
+
getWatcherOwnership(slug) {
|
|
9823
|
+
const state = appState.get(slug);
|
|
9824
|
+
if (!state) throw new Error(`unknown app: ${slug}`);
|
|
9825
|
+
return state.watcherOwnership ? { ...state.watcherOwnership } : null;
|
|
9826
|
+
},
|
|
9526
9827
|
async close() {
|
|
9828
|
+
serverClosing = true;
|
|
9829
|
+
if (diagnosticsTimer) {
|
|
9830
|
+
clearInterval(diagnosticsTimer);
|
|
9831
|
+
diagnosticsTimer = null;
|
|
9832
|
+
}
|
|
9833
|
+
writeDevDiagnostic("stopping");
|
|
9834
|
+
const buildProcessStops = [];
|
|
9527
9835
|
for (const state of appState.values()) {
|
|
9528
9836
|
if (state.prepareTimer) clearTimeout(state.prepareTimer);
|
|
9529
9837
|
if (state.reloadTimer) clearTimeout(state.reloadTimer);
|
|
@@ -9548,10 +9856,16 @@ data: ${JSON.stringify({ slug, at: Date.now() })}
|
|
|
9548
9856
|
}
|
|
9549
9857
|
}
|
|
9550
9858
|
state.sseClients.clear();
|
|
9551
|
-
if (state.
|
|
9552
|
-
state.
|
|
9859
|
+
if (state.buildProcessStopPromise) {
|
|
9860
|
+
buildProcessStops.push(state.buildProcessStopPromise);
|
|
9861
|
+
}
|
|
9862
|
+
if (state.buildProcess) {
|
|
9863
|
+
const buildProcess = state.buildProcess;
|
|
9864
|
+
state.buildProcess = null;
|
|
9865
|
+
buildProcessStops.push(terminateBuildProcess(buildProcess));
|
|
9553
9866
|
}
|
|
9554
9867
|
}
|
|
9868
|
+
await Promise.allSettled(buildProcessStops);
|
|
9555
9869
|
for (const res of hostSseClients) {
|
|
9556
9870
|
try {
|
|
9557
9871
|
res.end();
|
|
@@ -9560,6 +9874,7 @@ data: ${JSON.stringify({ slug, at: Date.now() })}
|
|
|
9560
9874
|
}
|
|
9561
9875
|
hostSseClients.clear();
|
|
9562
9876
|
await new Promise((resolvePromise) => server.close(() => resolvePromise()));
|
|
9877
|
+
writeDevDiagnostic("stopped");
|
|
9563
9878
|
}
|
|
9564
9879
|
};
|
|
9565
9880
|
}
|
|
@@ -9569,12 +9884,12 @@ import { randomUUID as randomUUID4 } from "node:crypto";
|
|
|
9569
9884
|
import {
|
|
9570
9885
|
existsSync as existsSync8,
|
|
9571
9886
|
lstatSync as lstatSync3,
|
|
9572
|
-
mkdirSync as
|
|
9887
|
+
mkdirSync as mkdirSync6,
|
|
9573
9888
|
readFileSync as readFileSync8,
|
|
9574
9889
|
readdirSync as readdirSync4,
|
|
9575
|
-
realpathSync as
|
|
9576
|
-
renameSync as
|
|
9577
|
-
rmSync as
|
|
9890
|
+
realpathSync as realpathSync3,
|
|
9891
|
+
renameSync as renameSync5,
|
|
9892
|
+
rmSync as rmSync6,
|
|
9578
9893
|
statSync as statSync4,
|
|
9579
9894
|
writeFileSync as writeFileSync5
|
|
9580
9895
|
} from "node:fs";
|
|
@@ -9604,13 +9919,13 @@ function readLockOwner2(lockPath) {
|
|
|
9604
9919
|
}
|
|
9605
9920
|
}
|
|
9606
9921
|
function withLock(filePath, callback) {
|
|
9607
|
-
|
|
9922
|
+
mkdirSync6(dirname7(filePath), { recursive: true, mode: 448 });
|
|
9608
9923
|
const lockPath = `${filePath}.lock`;
|
|
9609
9924
|
const owner = `${process.pid}.${randomUUID4()}`;
|
|
9610
9925
|
const startedAt = Date.now();
|
|
9611
9926
|
while (true) {
|
|
9612
9927
|
try {
|
|
9613
|
-
|
|
9928
|
+
mkdirSync6(lockPath, { mode: 448 });
|
|
9614
9929
|
writeFileSync5(join8(lockPath, "owner"), owner, { mode: 384 });
|
|
9615
9930
|
break;
|
|
9616
9931
|
} catch (error) {
|
|
@@ -9627,7 +9942,7 @@ function withLock(filePath, callback) {
|
|
|
9627
9942
|
process.kill(pid, 0);
|
|
9628
9943
|
} catch (ownerError) {
|
|
9629
9944
|
if (ownerError?.code === "ESRCH") {
|
|
9630
|
-
|
|
9945
|
+
rmSync6(lockPath, { recursive: true, force: true });
|
|
9631
9946
|
continue;
|
|
9632
9947
|
}
|
|
9633
9948
|
}
|
|
@@ -9646,7 +9961,7 @@ function withLock(filePath, callback) {
|
|
|
9646
9961
|
return callback();
|
|
9647
9962
|
} finally {
|
|
9648
9963
|
if (readLockOwner2(lockPath) === owner) {
|
|
9649
|
-
|
|
9964
|
+
rmSync6(lockPath, { recursive: true, force: true });
|
|
9650
9965
|
}
|
|
9651
9966
|
}
|
|
9652
9967
|
}
|
|
@@ -9661,7 +9976,7 @@ function canonicalExistingDirectory(inputPath) {
|
|
|
9661
9976
|
if (!stat2.isDirectory()) {
|
|
9662
9977
|
throw usageError(`App development root is not a directory: ${absolute}`);
|
|
9663
9978
|
}
|
|
9664
|
-
return
|
|
9979
|
+
return realpathSync3(absolute);
|
|
9665
9980
|
}
|
|
9666
9981
|
function normalizeRegistry(raw) {
|
|
9667
9982
|
const roots = Array.isArray(raw?.roots) ? raw.roots : [];
|
|
@@ -9687,14 +10002,14 @@ function readRaw(filePath) {
|
|
|
9687
10002
|
}
|
|
9688
10003
|
}
|
|
9689
10004
|
function writeRaw(registry, filePath) {
|
|
9690
|
-
|
|
10005
|
+
mkdirSync6(dirname7(filePath), { recursive: true, mode: 448 });
|
|
9691
10006
|
const normalized = {
|
|
9692
10007
|
version: APP_DEV_ROOTS_VERSION,
|
|
9693
10008
|
roots: normalizeRegistry(registry)
|
|
9694
10009
|
};
|
|
9695
10010
|
const temporary = `${filePath}.${process.pid}.${randomUUID4()}.tmp`;
|
|
9696
10011
|
writeFileSync5(temporary, JSON.stringify(normalized, null, 2), { mode: 384 });
|
|
9697
|
-
|
|
10012
|
+
renameSync5(temporary, filePath);
|
|
9698
10013
|
return normalized;
|
|
9699
10014
|
}
|
|
9700
10015
|
function migrateLegacyProjectsUnlocked(registry, options) {
|
|
@@ -9722,12 +10037,12 @@ function migrateLegacyProjectsUnlocked(registry, options) {
|
|
|
9722
10037
|
} catch {
|
|
9723
10038
|
}
|
|
9724
10039
|
}
|
|
9725
|
-
|
|
10040
|
+
rmSync6(path3, { force: true });
|
|
9726
10041
|
return { version: APP_DEV_ROOTS_VERSION, roots: [...byPath.values()] };
|
|
9727
10042
|
}
|
|
9728
10043
|
function realpathIfExists(path3) {
|
|
9729
10044
|
try {
|
|
9730
|
-
return
|
|
10045
|
+
return realpathSync3(path3);
|
|
9731
10046
|
} catch {
|
|
9732
10047
|
return resolve6(path3);
|
|
9733
10048
|
}
|
|
@@ -9808,7 +10123,7 @@ function discoverRegisteredAppProjects(options = {}) {
|
|
|
9808
10123
|
|
|
9809
10124
|
// src/runtime/agent-browser.js
|
|
9810
10125
|
import { spawn as spawn3, spawnSync } from "node:child_process";
|
|
9811
|
-
import { mkdirSync as
|
|
10126
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
|
|
9812
10127
|
import { dirname as dirname8 } from "node:path";
|
|
9813
10128
|
var PREPARE_CAPTURE_SCRIPT = "(() => { const s = document.getElementById('harness-status'); if (s) s.style.display = 'none'; const r = document.getElementById('root'); if (r) { r.style.paddingTop = '0'; r.style.minHeight = '0'; } document.body.style.minHeight = '0'; document.documentElement.style.minHeight = '0'; return true; })()";
|
|
9814
10129
|
var FONTS_STATUS_SCRIPT = "(() => (document.fonts ? document.fonts.status : 'loaded'))()";
|
|
@@ -9876,7 +10191,7 @@ async function waitForFonts(sessionName, timeoutMs = 3e3) {
|
|
|
9876
10191
|
} catch {
|
|
9877
10192
|
return;
|
|
9878
10193
|
}
|
|
9879
|
-
await
|
|
10194
|
+
await delay2(150);
|
|
9880
10195
|
}
|
|
9881
10196
|
}
|
|
9882
10197
|
async function setViewport(sessionName, width, height, scale = null) {
|
|
@@ -9887,7 +10202,7 @@ async function setViewport(sessionName, width, height, scale = null) {
|
|
|
9887
10202
|
const result = await runAgentBrowser(args, { timeoutMs: 5e3 });
|
|
9888
10203
|
return result.exitCode === 0;
|
|
9889
10204
|
}
|
|
9890
|
-
function
|
|
10205
|
+
function delay2(ms) {
|
|
9891
10206
|
return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
|
|
9892
10207
|
}
|
|
9893
10208
|
function commandError(phase, result) {
|
|
@@ -10010,15 +10325,15 @@ async function runHarnessRoute({
|
|
|
10010
10325
|
while (Date.now() < deadline) {
|
|
10011
10326
|
const lastRead = await readHarness(sessionName, 5e3);
|
|
10012
10327
|
if (!lastRead.ok) {
|
|
10013
|
-
await
|
|
10328
|
+
await delay2(250);
|
|
10014
10329
|
continue;
|
|
10015
10330
|
}
|
|
10016
10331
|
const harness2 = lastRead.harness;
|
|
10017
10332
|
if (harness2?.mounted || Array.isArray(harness2?.errors) && harness2.errors.length > 0) {
|
|
10018
|
-
await
|
|
10333
|
+
await delay2(250);
|
|
10019
10334
|
break;
|
|
10020
10335
|
}
|
|
10021
|
-
await
|
|
10336
|
+
await delay2(250);
|
|
10022
10337
|
}
|
|
10023
10338
|
const finalRead = await readHarness(sessionName, 5e3);
|
|
10024
10339
|
if (!finalRead.ok) {
|
|
@@ -10038,7 +10353,7 @@ async function runHarnessRoute({
|
|
|
10038
10353
|
timeoutMs: 1e4
|
|
10039
10354
|
});
|
|
10040
10355
|
if (snapshot.exitCode === 0) {
|
|
10041
|
-
|
|
10356
|
+
mkdirSync7(dirname8(snapshotPath), { recursive: true });
|
|
10042
10357
|
writeFileSync6(snapshotPath, snapshot.stdout);
|
|
10043
10358
|
savedSnapshotPath = snapshotPath;
|
|
10044
10359
|
}
|
|
@@ -10096,7 +10411,7 @@ async function captureHarnessScreenshot({
|
|
|
10096
10411
|
} else {
|
|
10097
10412
|
lastReadError = read.toolError;
|
|
10098
10413
|
}
|
|
10099
|
-
await
|
|
10414
|
+
await delay2(250);
|
|
10100
10415
|
}
|
|
10101
10416
|
if (lastErrors.length > 0) {
|
|
10102
10417
|
return { ok: false, mounted, screenshotPath: null, errors: lastErrors, tool_error: null };
|
|
@@ -10134,7 +10449,7 @@ async function captureHarnessScreenshot({
|
|
|
10134
10449
|
break;
|
|
10135
10450
|
}
|
|
10136
10451
|
framing = { ...fitted, content_height: contentHeight };
|
|
10137
|
-
await
|
|
10452
|
+
await delay2(150);
|
|
10138
10453
|
const remeasured = await evalNumber(sessionName, CONTENT_HEIGHT_SCRIPT);
|
|
10139
10454
|
if (remeasured == null || Math.abs(remeasured - contentHeight) <= 32) {
|
|
10140
10455
|
framing.content_height = remeasured ?? contentHeight;
|
|
@@ -10143,8 +10458,8 @@ async function captureHarnessScreenshot({
|
|
|
10143
10458
|
contentHeight = remeasured;
|
|
10144
10459
|
}
|
|
10145
10460
|
}
|
|
10146
|
-
await
|
|
10147
|
-
|
|
10461
|
+
await delay2(500);
|
|
10462
|
+
mkdirSync7(dirname8(screenshotPath), { recursive: true });
|
|
10148
10463
|
const screenshotArgs = ["--session", sessionName, "screenshot"];
|
|
10149
10464
|
if (focusSelector) {
|
|
10150
10465
|
screenshotArgs.push(focusSelector);
|
|
@@ -10157,7 +10472,7 @@ async function captureHarnessScreenshot({
|
|
|
10157
10472
|
const dimensions = pngDimensions2(screenshotPath);
|
|
10158
10473
|
if (!focusSelector && framing && (!dimensions || dimensions.width !== width || dimensions.height !== height)) {
|
|
10159
10474
|
await setViewport(sessionName, width, height);
|
|
10160
|
-
await
|
|
10475
|
+
await delay2(300);
|
|
10161
10476
|
const retry = await runAgentBrowser(
|
|
10162
10477
|
["--session", sessionName, "screenshot", screenshotPath, "--screenshot-format", "png"],
|
|
10163
10478
|
{ timeoutMs: 15e3 }
|
|
@@ -10193,7 +10508,7 @@ async function closeAgentBrowserSession(sessionName) {
|
|
|
10193
10508
|
|
|
10194
10509
|
// src/runtime/app-dev-host-lock.js
|
|
10195
10510
|
import { createHash, randomUUID as randomUUID5 } from "node:crypto";
|
|
10196
|
-
import { lstatSync as lstatSync4, mkdirSync as
|
|
10511
|
+
import { lstatSync as lstatSync4, mkdirSync as mkdirSync8, readFileSync as readFileSync10, rmSync as rmSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
10197
10512
|
import { homedir as homedir4 } from "node:os";
|
|
10198
10513
|
import { join as join9 } from "node:path";
|
|
10199
10514
|
var DEFAULT_LOCK_ROOT = join9(homedir4(), ".notis", "app-dev-host-locks");
|
|
@@ -10228,25 +10543,25 @@ function reclaimable(path3, now, staleAfterMs) {
|
|
|
10228
10543
|
}
|
|
10229
10544
|
function tryAcquireAppDevHostLock(options) {
|
|
10230
10545
|
const lockRoot = options.lockRoot || DEFAULT_LOCK_ROOT;
|
|
10231
|
-
|
|
10546
|
+
mkdirSync8(lockRoot, { recursive: true, mode: 448 });
|
|
10232
10547
|
const path3 = join9(lockRoot, lockName(options.identity, options.apiBase, options.projectDir));
|
|
10233
10548
|
const ownerId = `${process.pid}.${randomUUID5()}`;
|
|
10234
10549
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
10235
10550
|
try {
|
|
10236
|
-
|
|
10551
|
+
mkdirSync8(path3, { mode: 448 });
|
|
10237
10552
|
try {
|
|
10238
10553
|
writeFileSync7(join9(path3, OWNER_FILE), JSON.stringify({ pid: process.pid, ownerId }), {
|
|
10239
10554
|
mode: 384
|
|
10240
10555
|
});
|
|
10241
10556
|
} catch (error) {
|
|
10242
|
-
|
|
10557
|
+
rmSync7(path3, { recursive: true, force: true });
|
|
10243
10558
|
throw error;
|
|
10244
10559
|
}
|
|
10245
10560
|
return { path: path3, ownerId };
|
|
10246
10561
|
} catch (error) {
|
|
10247
10562
|
if (error?.code !== "EEXIST") throw error;
|
|
10248
10563
|
if (reclaimable(path3, options.now ?? Date.now(), options.staleAfterMs ?? 6e4)) {
|
|
10249
|
-
|
|
10564
|
+
rmSync7(path3, { recursive: true, force: true });
|
|
10250
10565
|
continue;
|
|
10251
10566
|
}
|
|
10252
10567
|
return null;
|
|
@@ -10257,7 +10572,7 @@ function tryAcquireAppDevHostLock(options) {
|
|
|
10257
10572
|
function releaseAppDevHostLock(lock) {
|
|
10258
10573
|
try {
|
|
10259
10574
|
if (readOwner(lock.path)?.ownerId === lock.ownerId) {
|
|
10260
|
-
|
|
10575
|
+
rmSync7(lock.path, { recursive: true, force: true });
|
|
10261
10576
|
}
|
|
10262
10577
|
} catch {
|
|
10263
10578
|
}
|
|
@@ -10268,10 +10583,10 @@ import { randomUUID as randomUUID6 } from "node:crypto";
|
|
|
10268
10583
|
import {
|
|
10269
10584
|
existsSync as existsSync9,
|
|
10270
10585
|
lstatSync as lstatSync5,
|
|
10271
|
-
mkdirSync as
|
|
10586
|
+
mkdirSync as mkdirSync9,
|
|
10272
10587
|
readFileSync as readFileSync11,
|
|
10273
|
-
renameSync as
|
|
10274
|
-
rmSync as
|
|
10588
|
+
renameSync as renameSync6,
|
|
10589
|
+
rmSync as rmSync8,
|
|
10275
10590
|
writeFileSync as writeFileSync8
|
|
10276
10591
|
} from "node:fs";
|
|
10277
10592
|
import { homedir as homedir5 } from "node:os";
|
|
@@ -10304,20 +10619,20 @@ function reclaimConsumerLock(lockPath) {
|
|
|
10304
10619
|
return Date.now() - stat2.mtimeMs >= CONSUMER_LOCK_STALE_AFTER_MS;
|
|
10305
10620
|
}
|
|
10306
10621
|
function withLock2(filePath, callback) {
|
|
10307
|
-
|
|
10622
|
+
mkdirSync9(dirname9(filePath), { recursive: true, mode: 448 });
|
|
10308
10623
|
const lockPath = `${filePath}.lock`;
|
|
10309
10624
|
const owner = `${process.pid}.${randomUUID6()}`;
|
|
10310
10625
|
const startedAt = Date.now();
|
|
10311
10626
|
while (true) {
|
|
10312
10627
|
try {
|
|
10313
|
-
|
|
10628
|
+
mkdirSync9(lockPath, { mode: 448 });
|
|
10314
10629
|
writeFileSync8(join10(lockPath, "owner"), owner, { mode: 384 });
|
|
10315
10630
|
break;
|
|
10316
10631
|
} catch (error) {
|
|
10317
10632
|
if (error?.code !== "EEXIST") throw error;
|
|
10318
10633
|
try {
|
|
10319
10634
|
if (reclaimConsumerLock(lockPath)) {
|
|
10320
|
-
|
|
10635
|
+
rmSync8(lockPath, { recursive: true, force: true });
|
|
10321
10636
|
continue;
|
|
10322
10637
|
}
|
|
10323
10638
|
} catch (lockError) {
|
|
@@ -10335,7 +10650,7 @@ function withLock2(filePath, callback) {
|
|
|
10335
10650
|
} finally {
|
|
10336
10651
|
try {
|
|
10337
10652
|
if (readFileSync11(join10(lockPath, "owner"), "utf8").trim() === owner) {
|
|
10338
|
-
|
|
10653
|
+
rmSync8(lockPath, { recursive: true, force: true });
|
|
10339
10654
|
}
|
|
10340
10655
|
} catch {
|
|
10341
10656
|
}
|
|
@@ -10357,10 +10672,10 @@ function readAppDevConsumers(filePath = DEFAULT_APP_DEV_CONSUMERS_FILE, now = Da
|
|
|
10357
10672
|
}
|
|
10358
10673
|
}
|
|
10359
10674
|
function writeAppDevConsumers(filePath, consumers) {
|
|
10360
|
-
|
|
10675
|
+
mkdirSync9(dirname9(filePath), { recursive: true, mode: 448 });
|
|
10361
10676
|
const temporary = `${filePath}.${process.pid}.${randomUUID6()}.tmp`;
|
|
10362
10677
|
writeFileSync8(temporary, JSON.stringify({ version: 1, consumers }, null, 2), { mode: 384 });
|
|
10363
|
-
|
|
10678
|
+
renameSync6(temporary, filePath);
|
|
10364
10679
|
}
|
|
10365
10680
|
function heartbeatAppDevConsumer(lease, filePath = DEFAULT_APP_DEV_CONSUMERS_FILE) {
|
|
10366
10681
|
return withLock2(filePath, () => {
|
|
@@ -10597,12 +10912,12 @@ import {
|
|
|
10597
10912
|
timingSafeEqual
|
|
10598
10913
|
} from "node:crypto";
|
|
10599
10914
|
import {
|
|
10600
|
-
mkdirSync as
|
|
10915
|
+
mkdirSync as mkdirSync10,
|
|
10601
10916
|
readdirSync as readdirSync5,
|
|
10602
10917
|
readFileSync as readFileSync12,
|
|
10603
|
-
renameSync as
|
|
10918
|
+
renameSync as renameSync7,
|
|
10604
10919
|
rmdirSync as rmdirSync2,
|
|
10605
|
-
rmSync as
|
|
10920
|
+
rmSync as rmSync9,
|
|
10606
10921
|
statSync as statSync5,
|
|
10607
10922
|
writeFileSync as writeFileSync9
|
|
10608
10923
|
} from "node:fs";
|
|
@@ -10610,7 +10925,7 @@ import { createServer as createServer3 } from "node:http";
|
|
|
10610
10925
|
import { homedir as homedir6 } from "node:os";
|
|
10611
10926
|
import { basename as basename2, dirname as dirname10, join as join11 } from "node:path";
|
|
10612
10927
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
10613
|
-
import { execFileSync, spawn as spawn4 } from "node:child_process";
|
|
10928
|
+
import { execFileSync as execFileSync3, spawn as spawn4 } from "node:child_process";
|
|
10614
10929
|
import { createInterface } from "node:readline/promises";
|
|
10615
10930
|
var DEFAULT_CLI_OAUTH_SCOPES = [
|
|
10616
10931
|
"notis:read",
|
|
@@ -11263,7 +11578,7 @@ function legacyPendingAuthorizationFile(runtime) {
|
|
|
11263
11578
|
}
|
|
11264
11579
|
function savePendingAuthorization(runtime, pending) {
|
|
11265
11580
|
const file = pendingAuthorizationFile(runtime);
|
|
11266
|
-
|
|
11581
|
+
mkdirSync10(dirname10(file), { recursive: true });
|
|
11267
11582
|
writeFileSync9(file, JSON.stringify(pending, null, 2), { mode: 384 });
|
|
11268
11583
|
}
|
|
11269
11584
|
function readPendingAuthorization(runtime, { includeExpired = false } = {}) {
|
|
@@ -11286,7 +11601,7 @@ function readPendingAuthorization(runtime, { includeExpired = false } = {}) {
|
|
|
11286
11601
|
}
|
|
11287
11602
|
function clearPendingAuthorization(runtime, file = pendingAuthorizationFile(runtime)) {
|
|
11288
11603
|
try {
|
|
11289
|
-
|
|
11604
|
+
rmSync9(file);
|
|
11290
11605
|
} catch {
|
|
11291
11606
|
}
|
|
11292
11607
|
}
|
|
@@ -11295,7 +11610,7 @@ function clearPendingAuthorizations(runtime) {
|
|
|
11295
11610
|
const legacyFile = legacyPendingAuthorizationFile(runtime);
|
|
11296
11611
|
try {
|
|
11297
11612
|
const pending = JSON.parse(readFileSync12(legacyFile, "utf-8"));
|
|
11298
|
-
if (pending?.profile === runtime.profileName)
|
|
11613
|
+
if (pending?.profile === runtime.profileName) rmSync9(legacyFile);
|
|
11299
11614
|
} catch {
|
|
11300
11615
|
}
|
|
11301
11616
|
}
|
|
@@ -11399,20 +11714,20 @@ function listenerGlobalLockIsStale(lockDir, staleMs) {
|
|
|
11399
11714
|
function retireStaleListenerGlobalLock(lockDir) {
|
|
11400
11715
|
const retiredDir = `${lockDir}.stale-${process.pid}-${base64url(randomBytes(8))}`;
|
|
11401
11716
|
try {
|
|
11402
|
-
|
|
11717
|
+
renameSync7(lockDir, retiredDir);
|
|
11403
11718
|
} catch {
|
|
11404
11719
|
return false;
|
|
11405
11720
|
}
|
|
11406
|
-
|
|
11721
|
+
rmSync9(retiredDir, { recursive: true, force: true });
|
|
11407
11722
|
return true;
|
|
11408
11723
|
}
|
|
11409
11724
|
async function acquireListenerStartLock(runtime) {
|
|
11410
11725
|
const lockDir = listenerStartLockDir(runtime);
|
|
11411
|
-
|
|
11726
|
+
mkdirSync10(dirname10(lockDir), { recursive: true });
|
|
11412
11727
|
const deadline = Date.now() + LISTENER_START_LOCK_STALE_MS * 2;
|
|
11413
11728
|
for (; ; ) {
|
|
11414
11729
|
try {
|
|
11415
|
-
|
|
11730
|
+
mkdirSync10(lockDir);
|
|
11416
11731
|
return lockDir;
|
|
11417
11732
|
} catch (error) {
|
|
11418
11733
|
if (error?.code !== "EEXIST") throw error;
|
|
@@ -11446,11 +11761,11 @@ async function acquireListenerGlobalLock({
|
|
|
11446
11761
|
heartbeatMs = LISTENER_GLOBAL_LOCK_HEARTBEAT_MS
|
|
11447
11762
|
} = {}) {
|
|
11448
11763
|
const lockDir = listenerGlobalLockDir();
|
|
11449
|
-
|
|
11764
|
+
mkdirSync10(dirname10(lockDir), { recursive: true });
|
|
11450
11765
|
const deadline = Date.now() + waitMs;
|
|
11451
11766
|
for (; ; ) {
|
|
11452
11767
|
try {
|
|
11453
|
-
|
|
11768
|
+
mkdirSync10(lockDir);
|
|
11454
11769
|
const lock = {
|
|
11455
11770
|
lockDir,
|
|
11456
11771
|
ownerToken: base64url(randomBytes(24)),
|
|
@@ -11487,23 +11802,23 @@ function releaseListenerGlobalLock(lock) {
|
|
|
11487
11802
|
if (owner?.owner_token !== lock.ownerToken) return;
|
|
11488
11803
|
const releasedDir = `${lock.lockDir}.released-${process.pid}-${lock.ownerToken}`;
|
|
11489
11804
|
try {
|
|
11490
|
-
|
|
11805
|
+
renameSync7(lock.lockDir, releasedDir);
|
|
11491
11806
|
} catch {
|
|
11492
11807
|
return;
|
|
11493
11808
|
}
|
|
11494
11809
|
const movedOwner = readListenerGlobalLockOwner(releasedDir);
|
|
11495
11810
|
if (movedOwner?.owner_token !== lock.ownerToken) {
|
|
11496
11811
|
try {
|
|
11497
|
-
|
|
11812
|
+
renameSync7(releasedDir, lock.lockDir);
|
|
11498
11813
|
} catch {
|
|
11499
11814
|
}
|
|
11500
11815
|
return;
|
|
11501
11816
|
}
|
|
11502
|
-
|
|
11817
|
+
rmSync9(releasedDir, { recursive: true, force: true });
|
|
11503
11818
|
}
|
|
11504
11819
|
function saveListenerState(runtime, state) {
|
|
11505
11820
|
const file = listenerStateFile(runtime);
|
|
11506
|
-
|
|
11821
|
+
mkdirSync10(dirname10(file), { recursive: true });
|
|
11507
11822
|
writeFileSync9(file, JSON.stringify(state, null, 2), { mode: 384 });
|
|
11508
11823
|
}
|
|
11509
11824
|
function readListenerState(runtime) {
|
|
@@ -11524,7 +11839,7 @@ function clearListenerState(runtime, { ownerPid = null } = {}) {
|
|
|
11524
11839
|
}
|
|
11525
11840
|
}
|
|
11526
11841
|
try {
|
|
11527
|
-
|
|
11842
|
+
rmSync9(listenerStateFile(runtime));
|
|
11528
11843
|
} catch {
|
|
11529
11844
|
}
|
|
11530
11845
|
}
|
|
@@ -11542,7 +11857,7 @@ function readLiveListener(runtime, { sameApiBase = true } = {}) {
|
|
|
11542
11857
|
}
|
|
11543
11858
|
function listenerProcessIsAlive(pid, {
|
|
11544
11859
|
platform = process.platform,
|
|
11545
|
-
run =
|
|
11860
|
+
run = execFileSync3,
|
|
11546
11861
|
signal = process.kill.bind(process),
|
|
11547
11862
|
expectedIdentity = null,
|
|
11548
11863
|
expectedScriptPath = null
|
|
@@ -11607,7 +11922,7 @@ async function startDetachedLoopbackListener(runtime, {
|
|
|
11607
11922
|
const identityToken = randomBytes(24).toString("base64url");
|
|
11608
11923
|
const payloadFile = `${listenerStateFile(runtime)}.payload.${process.pid}.${randomBytes(8).toString("hex")}`;
|
|
11609
11924
|
try {
|
|
11610
|
-
|
|
11925
|
+
mkdirSync10(dirname10(payloadFile), { recursive: true });
|
|
11611
11926
|
writeFileSync9(payloadFile, JSON.stringify({
|
|
11612
11927
|
profile: runtime.profileName,
|
|
11613
11928
|
api_base: runtime.apiBase,
|
|
@@ -11634,7 +11949,7 @@ async function startDetachedLoopbackListener(runtime, {
|
|
|
11634
11949
|
});
|
|
11635
11950
|
} catch {
|
|
11636
11951
|
try {
|
|
11637
|
-
|
|
11952
|
+
rmSync9(payloadFile);
|
|
11638
11953
|
} catch {
|
|
11639
11954
|
}
|
|
11640
11955
|
return null;
|
|
@@ -11663,7 +11978,7 @@ async function startDetachedLoopbackListener(runtime, {
|
|
|
11663
11978
|
} catch {
|
|
11664
11979
|
}
|
|
11665
11980
|
try {
|
|
11666
|
-
|
|
11981
|
+
rmSync9(payloadFile);
|
|
11667
11982
|
} catch {
|
|
11668
11983
|
}
|
|
11669
11984
|
return null;
|
|
@@ -12355,11 +12670,11 @@ function lockIsStale() {
|
|
|
12355
12670
|
}
|
|
12356
12671
|
}
|
|
12357
12672
|
async function acquireRefreshLock(runtime, waitMs = 6e4) {
|
|
12358
|
-
|
|
12673
|
+
mkdirSync10(join11(homedir6(), ".notis"), { recursive: true });
|
|
12359
12674
|
const deadline = Date.now() + waitMs;
|
|
12360
12675
|
for (; ; ) {
|
|
12361
12676
|
try {
|
|
12362
|
-
|
|
12677
|
+
mkdirSync10(OAUTH_LOCK_DIR);
|
|
12363
12678
|
return true;
|
|
12364
12679
|
} catch (error) {
|
|
12365
12680
|
if (error?.code !== "EEXIST") throw error;
|
|
@@ -13085,6 +13400,67 @@ function projectIsWithinRoot(projectDir, rootDir) {
|
|
|
13085
13400
|
const nested = relative3(rootDir, projectDir);
|
|
13086
13401
|
return nested === "" || nested !== ".." && !nested.startsWith(`..${sep}`) && !isAbsolute2(nested);
|
|
13087
13402
|
}
|
|
13403
|
+
function parseNotisAppVersion(value) {
|
|
13404
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(
|
|
13405
|
+
String(value || "").trim()
|
|
13406
|
+
);
|
|
13407
|
+
if (!match) return null;
|
|
13408
|
+
const prerelease = match[4] ? match[4].split(".") : [];
|
|
13409
|
+
if (prerelease.some((identifier) => /^\d+$/.test(identifier) && identifier.length > 1 && identifier.startsWith("0"))) {
|
|
13410
|
+
return null;
|
|
13411
|
+
}
|
|
13412
|
+
return {
|
|
13413
|
+
major: match[1],
|
|
13414
|
+
minor: match[2],
|
|
13415
|
+
patch: match[3],
|
|
13416
|
+
prerelease
|
|
13417
|
+
};
|
|
13418
|
+
}
|
|
13419
|
+
function compareNumericSemverIdentifiers(left, right) {
|
|
13420
|
+
if (left.length !== right.length) return left.length > right.length ? 1 : -1;
|
|
13421
|
+
return left === right ? 0 : left > right ? 1 : -1;
|
|
13422
|
+
}
|
|
13423
|
+
function compareNotisPrerelease(left, right) {
|
|
13424
|
+
if (left.length === 0 || right.length === 0) {
|
|
13425
|
+
return left.length === right.length ? 0 : left.length === 0 ? 1 : -1;
|
|
13426
|
+
}
|
|
13427
|
+
const length = Math.max(left.length, right.length);
|
|
13428
|
+
for (let index = 0; index < length; index += 1) {
|
|
13429
|
+
const leftIdentifier = left[index];
|
|
13430
|
+
const rightIdentifier = right[index];
|
|
13431
|
+
if (leftIdentifier === void 0 || rightIdentifier === void 0) {
|
|
13432
|
+
return leftIdentifier === rightIdentifier ? 0 : leftIdentifier === void 0 ? -1 : 1;
|
|
13433
|
+
}
|
|
13434
|
+
if (leftIdentifier === rightIdentifier) continue;
|
|
13435
|
+
const leftNumeric = /^\d+$/.test(leftIdentifier);
|
|
13436
|
+
const rightNumeric = /^\d+$/.test(rightIdentifier);
|
|
13437
|
+
if (leftNumeric && rightNumeric) {
|
|
13438
|
+
return compareNumericSemverIdentifiers(leftIdentifier, rightIdentifier);
|
|
13439
|
+
}
|
|
13440
|
+
if (leftNumeric !== rightNumeric) return leftNumeric ? -1 : 1;
|
|
13441
|
+
return leftIdentifier > rightIdentifier ? 1 : -1;
|
|
13442
|
+
}
|
|
13443
|
+
return 0;
|
|
13444
|
+
}
|
|
13445
|
+
function compareNotisAppVersions(leftValue, rightValue) {
|
|
13446
|
+
const left = parseNotisAppVersion(leftValue);
|
|
13447
|
+
const right = parseNotisAppVersion(rightValue);
|
|
13448
|
+
if (!left || !right) return null;
|
|
13449
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
13450
|
+
const comparison = compareNumericSemverIdentifiers(left[key], right[key]);
|
|
13451
|
+
if (comparison !== 0) return comparison;
|
|
13452
|
+
}
|
|
13453
|
+
return compareNotisPrerelease(left.prerelease, right.prerelease);
|
|
13454
|
+
}
|
|
13455
|
+
function readLocalNotisAppVersion(projectDir) {
|
|
13456
|
+
try {
|
|
13457
|
+
const packageJson = JSON.parse(readFileSync13(join12(projectDir, "package.json"), "utf8"));
|
|
13458
|
+
const version = String(packageJson.notisAppVersion || "").trim();
|
|
13459
|
+
return parseNotisAppVersion(version) ? version : null;
|
|
13460
|
+
} catch {
|
|
13461
|
+
return null;
|
|
13462
|
+
}
|
|
13463
|
+
}
|
|
13088
13464
|
function discoverAppDevLaunchProjects(rootDir, {
|
|
13089
13465
|
skipRootRegistration = false,
|
|
13090
13466
|
registerRoot = registerAppDevRoot,
|
|
@@ -13320,7 +13696,7 @@ function pruneStaleScreenshotFiles(outputDir, keepCount) {
|
|
|
13320
13696
|
if (!entry.isFile()) continue;
|
|
13321
13697
|
const match = /^screenshot-(\d+)\.png$/i.exec(entry.name);
|
|
13322
13698
|
if (match && Number.parseInt(match[1], 10) > keepCount) {
|
|
13323
|
-
|
|
13699
|
+
rmSync10(join12(outputDir, entry.name), { force: true });
|
|
13324
13700
|
}
|
|
13325
13701
|
}
|
|
13326
13702
|
}
|
|
@@ -13331,12 +13707,6 @@ function screenshotIndexByRouteSlug(manifest) {
|
|
|
13331
13707
|
const routes = Array.isArray(manifest?.routes) ? manifest.routes : [];
|
|
13332
13708
|
return new Map(routes.map((route, index) => [route.slug, index + 1]));
|
|
13333
13709
|
}
|
|
13334
|
-
function appRowFieldsFromManifest(manifest) {
|
|
13335
|
-
const app = manifest?.app && typeof manifest.app === "object" ? manifest.app : {};
|
|
13336
|
-
return {
|
|
13337
|
-
accent: app.accent ?? null
|
|
13338
|
-
};
|
|
13339
|
-
}
|
|
13340
13710
|
function screenshotExitCode(failedCount) {
|
|
13341
13711
|
return failedCount === 0 ? EXIT_CODES.ok : EXIT_CODES.unexpected;
|
|
13342
13712
|
}
|
|
@@ -13472,7 +13842,7 @@ function renderVerifyReport({ summary, results, noBrowser }) {
|
|
|
13472
13842
|
}
|
|
13473
13843
|
return lines.join("\n");
|
|
13474
13844
|
}
|
|
13475
|
-
function buildManifestForDev(appConfig) {
|
|
13845
|
+
function buildManifestForDev(appConfig, projectDir) {
|
|
13476
13846
|
const routes = Array.isArray(appConfig.routes) ? appConfig.routes : [];
|
|
13477
13847
|
return {
|
|
13478
13848
|
version: 1,
|
|
@@ -13480,7 +13850,8 @@ function buildManifestForDev(appConfig) {
|
|
|
13480
13850
|
app: {
|
|
13481
13851
|
name: appConfig.name,
|
|
13482
13852
|
description: appConfig.description || null,
|
|
13483
|
-
icon: appConfig.icon || null
|
|
13853
|
+
icon: appConfig.icon || null,
|
|
13854
|
+
release_version: readLocalNotisAppVersion(projectDir)
|
|
13484
13855
|
},
|
|
13485
13856
|
routes: routes.map((route) => ({
|
|
13486
13857
|
path: route.path,
|
|
@@ -13489,6 +13860,7 @@ function buildManifestForDev(appConfig) {
|
|
|
13489
13860
|
icon: route.icon || null,
|
|
13490
13861
|
parentSlug: route.parentSlug || null,
|
|
13491
13862
|
default: route.default || false,
|
|
13863
|
+
resourceDeepLinks: route.resourceDeepLinks === true,
|
|
13492
13864
|
export_name: route.exportName || route.export_name,
|
|
13493
13865
|
collection: route.collection || null
|
|
13494
13866
|
})),
|
|
@@ -13606,7 +13978,7 @@ async function ensureDevInstall({
|
|
|
13606
13978
|
if (!devSlug) {
|
|
13607
13979
|
throw usageError(`notis.config.ts devSlug or name in ${projectDir} must slugify to a non-empty value.`);
|
|
13608
13980
|
}
|
|
13609
|
-
const manifest = buildManifestForDev(appConfig);
|
|
13981
|
+
const manifest = buildManifestForDev(appConfig, projectDir);
|
|
13610
13982
|
const skills = resolveConfiguredAppSkills(appConfig, projectDir);
|
|
13611
13983
|
const profileKey = linkedStateProfileKey(ctx.runtime);
|
|
13612
13984
|
let linkedState = readLinkedState(projectDir, profileKey);
|
|
@@ -13675,6 +14047,9 @@ async function ensureDevInstall({
|
|
|
13675
14047
|
"version"
|
|
13676
14048
|
].includes(key))
|
|
13677
14049
|
) : linkedState;
|
|
14050
|
+
const localReleaseVersion = manifest.app.release_version || null;
|
|
14051
|
+
const installedReleaseVersion = linkedApp?.manifest?.app?.release_version || "0.0.0";
|
|
14052
|
+
const mountEligible = !linkedApp || compareNotisAppVersions(localReleaseVersion, installedReleaseVersion) === 1;
|
|
13678
14053
|
const ensureArguments = buildEnsureDevInstallArguments({
|
|
13679
14054
|
appConfig,
|
|
13680
14055
|
manifest,
|
|
@@ -13735,6 +14110,9 @@ async function ensureDevInstall({
|
|
|
13735
14110
|
linkedAppId: runtimeLinkedState?.app_id || null,
|
|
13736
14111
|
targetAppId: runtimeLinkedState?.app_id || null,
|
|
13737
14112
|
targetAppSlug: linkedApp?.slug || null,
|
|
14113
|
+
localReleaseVersion,
|
|
14114
|
+
installedReleaseVersion: linkedApp ? installedReleaseVersion : null,
|
|
14115
|
+
mountEligible,
|
|
13738
14116
|
databaseMaterialization: ensureResult.payload.database_materialization || { created: [], unresolved: [] },
|
|
13739
14117
|
liveData: ensureResult.payload.live_data || null
|
|
13740
14118
|
};
|
|
@@ -13755,6 +14133,13 @@ function databaseMaterializationWarnings(apps) {
|
|
|
13755
14133
|
function liveDataWarnings(apps) {
|
|
13756
14134
|
return apps.filter((app) => app.liveData?.warning).map((app) => `${app.name}: ${app.liveData.warning}`);
|
|
13757
14135
|
}
|
|
14136
|
+
function versionPrecedenceWarnings(apps) {
|
|
14137
|
+
return apps.filter((app) => app.targetAppId && app.mountEligible === false).map((app) => {
|
|
14138
|
+
const localVersion = app.localReleaseVersion || "missing or invalid";
|
|
14139
|
+
const pullCommand = `npx --package @notis_ai/cli@latest -- notis apps pull ${app.targetAppId} ${JSON.stringify(app.projectDir)} --force`;
|
|
14140
|
+
return `${app.name}: local version ${localVersion} is not strictly newer than installed version ${app.installedReleaseVersion}; Workspace keeps serving the online bundle. Preserve any local edits, pull latest with \`${pullCommand}\`, then bump package.json notisAppVersion before development.`;
|
|
14141
|
+
});
|
|
14142
|
+
}
|
|
13758
14143
|
async function getAccessibleApp(runtime, appId, runTool = runToolCommand) {
|
|
13759
14144
|
const result = await runTool({
|
|
13760
14145
|
runtime,
|
|
@@ -14078,7 +14463,8 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
14078
14463
|
updateApp: () => {
|
|
14079
14464
|
},
|
|
14080
14465
|
waitForBundle: async () => {
|
|
14081
|
-
}
|
|
14466
|
+
},
|
|
14467
|
+
getWatcherOwnership: () => null
|
|
14082
14468
|
} : await startAppDevServer({
|
|
14083
14469
|
apps: canonicalCandidates.map((app) => ({
|
|
14084
14470
|
slug: app.devSlug,
|
|
@@ -14093,6 +14479,10 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
14093
14479
|
port,
|
|
14094
14480
|
sessionsFilePath
|
|
14095
14481
|
});
|
|
14482
|
+
const desktopHostOwnership = captureDesktopHostOwnership({
|
|
14483
|
+
desktopOwnerId: process.env.NOTIS_APPS_DEV_DESKTOP_OWNER_ID,
|
|
14484
|
+
desktopOwnerScope: process.env.NOTIS_APPS_DEV_DESKTOP_OWNER_SCOPE
|
|
14485
|
+
});
|
|
14096
14486
|
let heartbeatTimer = setInterval(() => {
|
|
14097
14487
|
try {
|
|
14098
14488
|
heartbeatAppDevSession(sessionId, (/* @__PURE__ */ new Date()).toISOString(), sessionsFilePath);
|
|
@@ -14146,6 +14536,8 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
14146
14536
|
sessionId,
|
|
14147
14537
|
hostPid: process.pid,
|
|
14148
14538
|
sourceHost: !sharedBundleBaseUrls,
|
|
14539
|
+
...desktopHostOwnership || {},
|
|
14540
|
+
...devServer.getWatcherOwnership(app.devSlug) || {},
|
|
14149
14541
|
bundleReady: devServer.isBundleReady(app.devSlug),
|
|
14150
14542
|
...!sharedBundleBaseUrls ? {
|
|
14151
14543
|
discoveredProjects: discoveredAppDirs,
|
|
@@ -14221,7 +14613,8 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
14221
14613
|
...canonicalSelection.warnings,
|
|
14222
14614
|
...registrationWarnings,
|
|
14223
14615
|
...databaseMaterializationWarnings(apps),
|
|
14224
|
-
...liveDataWarnings(apps)
|
|
14616
|
+
...liveDataWarnings(apps),
|
|
14617
|
+
...versionPrecedenceWarnings(apps)
|
|
14225
14618
|
];
|
|
14226
14619
|
let consumerTimer = null;
|
|
14227
14620
|
ctx.output.emitSuccess({
|
|
@@ -14244,7 +14637,10 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
14244
14637
|
created: app.created,
|
|
14245
14638
|
linked_app_id: app.linkedAppId,
|
|
14246
14639
|
database_materialization: app.databaseMaterialization,
|
|
14247
|
-
live_data: app.liveData
|
|
14640
|
+
live_data: app.liveData,
|
|
14641
|
+
local_release_version: app.localReleaseVersion,
|
|
14642
|
+
installed_release_version: app.installedReleaseVersion,
|
|
14643
|
+
mount_eligible: app.mountEligible
|
|
14248
14644
|
}))
|
|
14249
14645
|
},
|
|
14250
14646
|
warnings,
|
|
@@ -14254,9 +14650,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
14254
14650
|
`Watching ${readAppDevRoots().roots.length} persistent development root(s).`,
|
|
14255
14651
|
...useInstalledDatabases ? [`Databases: ${apps.filter((app) => app.liveData?.enabled).length}/${apps.length} app(s) reading the installed app's live rows`] : [],
|
|
14256
14652
|
"",
|
|
14257
|
-
...apps.map((app) => ` ${app.name.padEnd(24)} ${app.bundleBaseUrl} -> ${app.appHref}`),
|
|
14653
|
+
...apps.map((app) => app.mountEligible ? ` ${app.name.padEnd(24)} ${app.bundleBaseUrl} -> ${app.appHref}` : ` ${app.name.padEnd(24)} online v${app.installedReleaseVersion} (local ${app.localReleaseVersion || "version missing"})`),
|
|
14258
14654
|
"",
|
|
14259
|
-
sharedBundleBaseUrls ? `
|
|
14655
|
+
sharedBundleBaseUrls ? `Attached to the shared source host: ${apps.filter((app) => app.mountEligible).length}/${apps.length} app${apps.length === 1 ? "" : "s"} eligible to substitute.` : `Serving one shared loopback host for ${apps.length} app${apps.length === 1 ? "" : "s"}; ${apps.filter((app) => app.mountEligible).length} eligible to substitute.`,
|
|
14260
14656
|
"",
|
|
14261
14657
|
"Press Ctrl-C to stop."
|
|
14262
14658
|
].join("\n")
|
|
@@ -14287,11 +14683,11 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
14287
14683
|
}
|
|
14288
14684
|
}
|
|
14289
14685
|
try {
|
|
14290
|
-
|
|
14686
|
+
await devServer.close();
|
|
14291
14687
|
} catch {
|
|
14292
14688
|
}
|
|
14293
14689
|
try {
|
|
14294
|
-
|
|
14690
|
+
removeAppDevSession(sessionId, sessionsFilePath);
|
|
14295
14691
|
} catch {
|
|
14296
14692
|
}
|
|
14297
14693
|
if (sourceHostLock) {
|
|
@@ -14666,7 +15062,7 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
14666
15062
|
`)
|
|
14667
15063
|
});
|
|
14668
15064
|
browserTouched = true;
|
|
14669
|
-
|
|
15065
|
+
mkdirSync11(outputDir, { recursive: true });
|
|
14670
15066
|
const results = [];
|
|
14671
15067
|
for (const capture of captures) {
|
|
14672
15068
|
const { route, scenario, focus, theme, fileName } = capture;
|
|
@@ -14764,7 +15160,7 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
14764
15160
|
}
|
|
14765
15161
|
}
|
|
14766
15162
|
if (rawOutputDir) {
|
|
14767
|
-
|
|
15163
|
+
rmSync10(rawOutputDir, { recursive: true, force: true });
|
|
14768
15164
|
}
|
|
14769
15165
|
}
|
|
14770
15166
|
}
|
|
@@ -14830,7 +15226,7 @@ async function appsPullHandler(ctx) {
|
|
|
14830
15226
|
project_dir: pulled.projectDir,
|
|
14831
15227
|
version: pulled.version
|
|
14832
15228
|
},
|
|
14833
|
-
humanSummary: `Pulled ${versionLabel} to ${pulled.projectDir}.
|
|
15229
|
+
humanSummary: `Pulled ${versionLabel} to ${pulled.projectDir}. Increment package.json notisAppVersion above the pulled release, then run \`cd ${pulled.projectDir} && npm install && notis apps dev\` to substitute the online bundle.`
|
|
14834
15230
|
});
|
|
14835
15231
|
}
|
|
14836
15232
|
function updateLinkedDeployState(projectDir, linkedState, appId, version, profileKey = null) {
|
|
@@ -15212,7 +15608,7 @@ var appsCommandSpecs = [
|
|
|
15212
15608
|
{
|
|
15213
15609
|
command_path: ["apps", "dev"],
|
|
15214
15610
|
summary: "Register a development root and connect its apps to the shared local development host.",
|
|
15215
|
-
when_to_use: "Run this once for any folder that should be watched permanently. The folder itself, direct child apps, and apps/* are discovered automatically by every signed-in Notis Desktop instance.",
|
|
15611
|
+
when_to_use: "Run this once for any folder that should be watched permanently. The folder itself, direct child apps, and apps/* are discovered automatically by every signed-in Notis Desktop instance. A linked app substitutes its online bundle only when local notisAppVersion is strictly greater than the installed release.",
|
|
15216
15612
|
args_schema: {
|
|
15217
15613
|
arguments: [
|
|
15218
15614
|
{ token: "[dir]", key: "dir", description: "Project directory or monorepo root (default: current dir)." }
|
|
@@ -15374,7 +15770,7 @@ var appsCommandSpecs = [
|
|
|
15374
15770
|
{
|
|
15375
15771
|
command_path: ["apps", "pull"],
|
|
15376
15772
|
summary: "Download a Notis app source snapshot into a local project folder.",
|
|
15377
|
-
when_to_use: "Edit an installed app locally.
|
|
15773
|
+
when_to_use: "Edit an installed app locally. Preserve any local edits, pull and link the latest persisted source, then increment package.json notisAppVersion above that release before notis apps dev; continue with build and deploy.",
|
|
15378
15774
|
args_schema: {
|
|
15379
15775
|
arguments: [
|
|
15380
15776
|
{ token: "<app-id>", description: "Remote app ID to pull." },
|
|
@@ -15482,7 +15878,7 @@ var appsCommandSpecs = [
|
|
|
15482
15878
|
];
|
|
15483
15879
|
|
|
15484
15880
|
// src/command-specs/tools.js
|
|
15485
|
-
import { accessSync, constants as fsConstants2, createReadStream as createReadStream2, existsSync as existsSync11, readFileSync as
|
|
15881
|
+
import { accessSync, constants as fsConstants2, createReadStream as createReadStream2, existsSync as existsSync11, readFileSync as readFileSync14, statSync as statSync6 } from "node:fs";
|
|
15486
15882
|
import { createHash as createHash3 } from "node:crypto";
|
|
15487
15883
|
import { basename as basename4 } from "node:path";
|
|
15488
15884
|
async function resolveJsonInput(value, label) {
|
|
@@ -15499,7 +15895,7 @@ async function resolveJsonInput(value, label) {
|
|
|
15499
15895
|
if (!existsSync11(filePath)) {
|
|
15500
15896
|
throw usageError(`File not found: ${filePath}`);
|
|
15501
15897
|
}
|
|
15502
|
-
return parseJson(
|
|
15898
|
+
return parseJson(readFileSync14(filePath, "utf-8"), label);
|
|
15503
15899
|
}
|
|
15504
15900
|
return parseJson(value, label);
|
|
15505
15901
|
}
|
|
@@ -16373,7 +16769,7 @@ var metaCommandSpecs = [
|
|
|
16373
16769
|
];
|
|
16374
16770
|
|
|
16375
16771
|
// src/command-specs/onboarding.js
|
|
16376
|
-
import { readFileSync as
|
|
16772
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
16377
16773
|
import { dirname as dirname15, join as join15 } from "node:path";
|
|
16378
16774
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
16379
16775
|
|
|
@@ -16385,10 +16781,10 @@ import { basename as basename5 } from "node:path";
|
|
|
16385
16781
|
import {
|
|
16386
16782
|
chmodSync,
|
|
16387
16783
|
existsSync as existsSync12,
|
|
16388
|
-
mkdirSync as
|
|
16389
|
-
readFileSync as
|
|
16784
|
+
mkdirSync as mkdirSync12,
|
|
16785
|
+
readFileSync as readFileSync15,
|
|
16390
16786
|
readdirSync as readdirSync7,
|
|
16391
|
-
renameSync as
|
|
16787
|
+
renameSync as renameSync8,
|
|
16392
16788
|
writeFileSync as writeFileSync10
|
|
16393
16789
|
} from "node:fs";
|
|
16394
16790
|
import { createHash as createHash4 } from "node:crypto";
|
|
@@ -16405,13 +16801,13 @@ var MANAGED_HOOK_MARKER = "--notis-managed-agent-hook";
|
|
|
16405
16801
|
var LEGACY_MANAGED_HOOK_MARKER = "NOTIS_MANAGED_AGENT_HOOK=1";
|
|
16406
16802
|
var AGENT_IDS = Object.freeze(["codex", "claude-code"]);
|
|
16407
16803
|
function atomicWrite(filePath, contents, mode = 384) {
|
|
16408
|
-
|
|
16804
|
+
mkdirSync12(dirname13(filePath), { recursive: true, mode: 448 });
|
|
16409
16805
|
const temporaryPath = `${filePath}.notis-${process.pid}-${Date.now()}.tmp`;
|
|
16410
16806
|
writeFileSync10(temporaryPath, contents, { mode });
|
|
16411
|
-
|
|
16807
|
+
renameSync8(temporaryPath, filePath);
|
|
16412
16808
|
}
|
|
16413
16809
|
function readText(filePath) {
|
|
16414
|
-
return existsSync12(filePath) ?
|
|
16810
|
+
return existsSync12(filePath) ? readFileSync15(filePath, "utf-8") : "";
|
|
16415
16811
|
}
|
|
16416
16812
|
function activeCodexInstructionsPath(home) {
|
|
16417
16813
|
const overridePath = join13(home, ".codex", "AGENTS.override.md");
|
|
@@ -16454,7 +16850,7 @@ function upsertInstructionBlock(filePath, block) {
|
|
|
16454
16850
|
}
|
|
16455
16851
|
function readJsonObject(filePath) {
|
|
16456
16852
|
if (!existsSync12(filePath)) return {};
|
|
16457
|
-
const raw =
|
|
16853
|
+
const raw = readFileSync15(filePath, "utf-8");
|
|
16458
16854
|
try {
|
|
16459
16855
|
const parsed = JSON.parse(raw);
|
|
16460
16856
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
@@ -16506,7 +16902,7 @@ function installManagedHookRuntime({
|
|
|
16506
16902
|
nodePath = process.execPath,
|
|
16507
16903
|
platform = process.platform
|
|
16508
16904
|
} = {}) {
|
|
16509
|
-
const bundle =
|
|
16905
|
+
const bundle = readFileSync15(bundlePath);
|
|
16510
16906
|
const digest = createHash4("sha256").update(bundle).digest("hex");
|
|
16511
16907
|
const runtimePath = join13(
|
|
16512
16908
|
home,
|
|
@@ -16516,7 +16912,7 @@ function installManagedHookRuntime({
|
|
|
16516
16912
|
digest,
|
|
16517
16913
|
"notis-agent-hook.mjs"
|
|
16518
16914
|
);
|
|
16519
|
-
const existingDigest = existsSync12(runtimePath) ? createHash4("sha256").update(
|
|
16915
|
+
const existingDigest = existsSync12(runtimePath) ? createHash4("sha256").update(readFileSync15(runtimePath)).digest("hex") : null;
|
|
16520
16916
|
if (existingDigest !== digest) atomicWrite(runtimePath, bundle, 320);
|
|
16521
16917
|
chmodSync(runtimePath, 320);
|
|
16522
16918
|
const launcherPath = join13(
|
|
@@ -16635,7 +17031,7 @@ function installAgentSetup({
|
|
|
16635
17031
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(String(profileName || ""))) {
|
|
16636
17032
|
throw new Error("A valid authenticated Notis profile is required for agent setup");
|
|
16637
17033
|
}
|
|
16638
|
-
const instructions =
|
|
17034
|
+
const instructions = readFileSync15(INSTRUCTIONS_PATH, "utf-8").trim();
|
|
16639
17035
|
const results = [];
|
|
16640
17036
|
const shouldRepairManagedHooks = memoryHooks === true || memoryHooks === null && agents.some((agentId) => fileHasManagedNotisHook(agentPaths(agentId, home).hooks));
|
|
16641
17037
|
const hookRuntime = shouldRepairManagedHooks ? installManagedHookRuntime({ home, platform }) : null;
|
|
@@ -16680,9 +17076,9 @@ function installAgentSetup({
|
|
|
16680
17076
|
import { createHash as createHash5 } from "node:crypto";
|
|
16681
17077
|
import {
|
|
16682
17078
|
existsSync as existsSync13,
|
|
16683
|
-
mkdirSync as
|
|
16684
|
-
readFileSync as
|
|
16685
|
-
renameSync as
|
|
17079
|
+
mkdirSync as mkdirSync13,
|
|
17080
|
+
readFileSync as readFileSync16,
|
|
17081
|
+
renameSync as renameSync9,
|
|
16686
17082
|
writeFileSync as writeFileSync11
|
|
16687
17083
|
} from "node:fs";
|
|
16688
17084
|
import { homedir as homedir8 } from "node:os";
|
|
@@ -16700,7 +17096,7 @@ function readState(sessionId, home) {
|
|
|
16700
17096
|
const filePath = statePath(sessionId, home);
|
|
16701
17097
|
if (!filePath || !existsSync13(filePath)) return { seen: [], pending: null };
|
|
16702
17098
|
try {
|
|
16703
|
-
const parsed = JSON.parse(
|
|
17099
|
+
const parsed = JSON.parse(readFileSync16(filePath, "utf-8"));
|
|
16704
17100
|
return parsed && typeof parsed === "object" ? {
|
|
16705
17101
|
seen: Array.isArray(parsed.seen) ? parsed.seen.filter((item) => typeof item === "string") : [],
|
|
16706
17102
|
pending: parsed.pending && typeof parsed.pending === "object" ? parsed.pending : null,
|
|
@@ -16730,11 +17126,11 @@ function stateForInput(sessionId, input, home) {
|
|
|
16730
17126
|
function writeState(sessionId, state, home) {
|
|
16731
17127
|
const filePath = statePath(sessionId, home);
|
|
16732
17128
|
if (!filePath) return;
|
|
16733
|
-
|
|
17129
|
+
mkdirSync13(dirname14(filePath), { recursive: true, mode: 448 });
|
|
16734
17130
|
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
16735
17131
|
writeFileSync11(temporaryPath, `${JSON.stringify(state)}
|
|
16736
17132
|
`, { mode: 384 });
|
|
16737
|
-
|
|
17133
|
+
renameSync9(temporaryPath, filePath);
|
|
16738
17134
|
}
|
|
16739
17135
|
function rememberPendingTurn(input, home = homedir8()) {
|
|
16740
17136
|
const sessionId = input?.session_id;
|
|
@@ -17276,7 +17672,7 @@ async function fetchBrief(apiBase, timeoutMs) {
|
|
|
17276
17672
|
} catch {
|
|
17277
17673
|
}
|
|
17278
17674
|
try {
|
|
17279
|
-
return { markdown:
|
|
17675
|
+
return { markdown: readFileSync17(BUNDLED_BRIEF_PATH, "utf-8"), source: "bundled" };
|
|
17280
17676
|
} catch {
|
|
17281
17677
|
return { markdown: null, source: null };
|
|
17282
17678
|
}
|
|
@@ -17444,7 +17840,7 @@ var onboardingCommandSpecs = [
|
|
|
17444
17840
|
|
|
17445
17841
|
// src/command-specs/diagnostics.js
|
|
17446
17842
|
import { createHash as createHash7, randomUUID as randomUUID10 } from "node:crypto";
|
|
17447
|
-
import { existsSync as existsSync14, readFileSync as
|
|
17843
|
+
import { existsSync as existsSync14, readFileSync as readFileSync18 } from "node:fs";
|
|
17448
17844
|
var ENTITLEMENT_FIELDS = [
|
|
17449
17845
|
"created_at",
|
|
17450
17846
|
"included_credit_seat_count",
|
|
@@ -17850,7 +18246,7 @@ ORDER BY i.created_at ASC;`;
|
|
|
17850
18246
|
function traceFileDiagnostics(path3) {
|
|
17851
18247
|
if (!path3) return null;
|
|
17852
18248
|
if (!existsSync14(path3)) throw usageError(`Trace file not found: ${path3}`);
|
|
17853
|
-
const trace = JSON.parse(
|
|
18249
|
+
const trace = JSON.parse(readFileSync18(path3, "utf-8"));
|
|
17854
18250
|
const text = JSON.stringify(trace).toLowerCase();
|
|
17855
18251
|
const modelCounts = {};
|
|
17856
18252
|
const generationIds = /* @__PURE__ */ new Set();
|
|
@@ -18044,13 +18440,13 @@ var diagnosticCommandSpecs = [
|
|
|
18044
18440
|
import { createHash as createHash8, randomUUID as randomUUID11 } from "node:crypto";
|
|
18045
18441
|
import {
|
|
18046
18442
|
mkdtempSync as mkdtempSync4,
|
|
18047
|
-
readFileSync as
|
|
18048
|
-
rmSync as
|
|
18443
|
+
readFileSync as readFileSync19,
|
|
18444
|
+
rmSync as rmSync11,
|
|
18049
18445
|
writeFileSync as writeFileSync12
|
|
18050
18446
|
} from "node:fs";
|
|
18051
18447
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
18052
18448
|
import { basename as basename6, join as join16 } from "node:path";
|
|
18053
|
-
import { setTimeout as
|
|
18449
|
+
import { setTimeout as delay3 } from "node:timers/promises";
|
|
18054
18450
|
function discoveryNames(payload) {
|
|
18055
18451
|
return [...new Set(
|
|
18056
18452
|
(payload.results || []).flatMap((result) => [
|
|
@@ -18089,7 +18485,7 @@ async function fetchDiscoveryWithRetry(ctx, useCase, knownFields) {
|
|
|
18089
18485
|
message: `Tool discovery transiently failed; retrying (${attempt}/${maxAttempts - 1})`,
|
|
18090
18486
|
requestId: error?.details?.request_id || null
|
|
18091
18487
|
});
|
|
18092
|
-
await
|
|
18488
|
+
await delay3(250 * 2 ** (attempt - 1));
|
|
18093
18489
|
}
|
|
18094
18490
|
}
|
|
18095
18491
|
throw usageError("Dropbox tool discovery exhausted its retries.");
|
|
@@ -18145,7 +18541,7 @@ unicode=\xE9t\xE9 Z\xFCrich \u6771\u4EAC
|
|
|
18145
18541
|
);
|
|
18146
18542
|
}
|
|
18147
18543
|
const localHash = await hashFileSha256(fixturePath);
|
|
18148
|
-
const localBytes =
|
|
18544
|
+
const localBytes = readFileSync19(fixturePath);
|
|
18149
18545
|
const destination = remotePath(
|
|
18150
18546
|
ctx.options.remoteFolder,
|
|
18151
18547
|
`notis-file-upload-${marker}-${basename6(fixturePath)}`
|
|
@@ -18358,7 +18754,7 @@ unicode=\xE9t\xE9 Z\xFCrich \u6771\u4EAC
|
|
|
18358
18754
|
result.cleanup.deleted = false;
|
|
18359
18755
|
}
|
|
18360
18756
|
}
|
|
18361
|
-
|
|
18757
|
+
rmSync11(tempDir, { recursive: true, force: true });
|
|
18362
18758
|
}
|
|
18363
18759
|
}
|
|
18364
18760
|
var smokeCommandSpecs = [
|
|
@@ -18666,7 +19062,7 @@ var profileCommandSpecs = [
|
|
|
18666
19062
|
|
|
18667
19063
|
// src/runtime/git.js
|
|
18668
19064
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
18669
|
-
import { lstatSync as lstatSync6, readFileSync as
|
|
19065
|
+
import { lstatSync as lstatSync6, readFileSync as readFileSync20 } from "node:fs";
|
|
18670
19066
|
import { basename as basename7, relative as relative4, resolve as resolve8, sep as sep2 } from "node:path";
|
|
18671
19067
|
var GIT_TIMEOUT_MS = 12e4;
|
|
18672
19068
|
var MAX_SECRET_SCAN_BYTES = 1e6;
|
|
@@ -18735,7 +19131,7 @@ function sensitiveAutoCommitFiles(repository) {
|
|
|
18735
19131
|
try {
|
|
18736
19132
|
const stat2 = lstatSync6(absolute);
|
|
18737
19133
|
if (!stat2.isFile() || stat2.size > MAX_SECRET_SCAN_BYTES) continue;
|
|
18738
|
-
const content =
|
|
19134
|
+
const content = readFileSync20(absolute, "utf8");
|
|
18739
19135
|
if (SENSITIVE_CONTENT.some((pattern) => pattern.test(content))) sensitive.push(path3);
|
|
18740
19136
|
} catch {
|
|
18741
19137
|
}
|
|
@@ -19121,10 +19517,10 @@ import {
|
|
|
19121
19517
|
cpSync as cpSync2,
|
|
19122
19518
|
existsSync as existsSync15,
|
|
19123
19519
|
lstatSync as lstatSync7,
|
|
19124
|
-
mkdirSync as
|
|
19125
|
-
readlinkSync,
|
|
19126
|
-
renameSync as
|
|
19127
|
-
rmSync as
|
|
19520
|
+
mkdirSync as mkdirSync14,
|
|
19521
|
+
readlinkSync as readlinkSync2,
|
|
19522
|
+
renameSync as renameSync10,
|
|
19523
|
+
rmSync as rmSync12,
|
|
19128
19524
|
symlinkSync
|
|
19129
19525
|
} from "node:fs";
|
|
19130
19526
|
import { homedir as homedir9 } from "node:os";
|
|
@@ -19168,35 +19564,35 @@ function getBaseSkillPaths({ home = homedir9(), userId = null } = {}) {
|
|
|
19168
19564
|
function sameLinkTarget(linkPath, expectedTarget) {
|
|
19169
19565
|
try {
|
|
19170
19566
|
if (!lstatSync7(linkPath).isSymbolicLink()) return false;
|
|
19171
|
-
return resolve9(dirname16(linkPath),
|
|
19567
|
+
return resolve9(dirname16(linkPath), readlinkSync2(linkPath)) === resolve9(expectedTarget);
|
|
19172
19568
|
} catch {
|
|
19173
19569
|
return false;
|
|
19174
19570
|
}
|
|
19175
19571
|
}
|
|
19176
19572
|
function backupConflict(linkPath, backupRoot, targetLabel, skillName, now) {
|
|
19177
19573
|
const backupPath = join17(backupRoot, String(now), targetLabel, skillName);
|
|
19178
|
-
|
|
19179
|
-
|
|
19574
|
+
mkdirSync14(dirname16(backupPath), { recursive: true, mode: 448 });
|
|
19575
|
+
renameSync10(linkPath, backupPath);
|
|
19180
19576
|
return backupPath;
|
|
19181
19577
|
}
|
|
19182
19578
|
function replaceDirectory(source, destination) {
|
|
19183
|
-
|
|
19579
|
+
mkdirSync14(dirname16(destination), { recursive: true, mode: 448 });
|
|
19184
19580
|
const temporary = `${destination}.tmp-${process.pid}-${Date.now()}`;
|
|
19185
19581
|
const previous = `${destination}.previous-${process.pid}-${Date.now()}`;
|
|
19186
|
-
|
|
19582
|
+
rmSync12(temporary, { recursive: true, force: true });
|
|
19187
19583
|
cpSync2(source, temporary, { recursive: true });
|
|
19188
19584
|
let movedPrevious = false;
|
|
19189
19585
|
try {
|
|
19190
19586
|
if (existsSync15(destination)) {
|
|
19191
|
-
|
|
19587
|
+
renameSync10(destination, previous);
|
|
19192
19588
|
movedPrevious = true;
|
|
19193
19589
|
}
|
|
19194
|
-
|
|
19195
|
-
if (movedPrevious)
|
|
19590
|
+
renameSync10(temporary, destination);
|
|
19591
|
+
if (movedPrevious) rmSync12(previous, { recursive: true, force: true });
|
|
19196
19592
|
} catch (error) {
|
|
19197
|
-
|
|
19593
|
+
rmSync12(temporary, { recursive: true, force: true });
|
|
19198
19594
|
if (movedPrevious && !existsSync15(destination) && existsSync15(previous)) {
|
|
19199
|
-
|
|
19595
|
+
renameSync10(previous, destination);
|
|
19200
19596
|
}
|
|
19201
19597
|
throw error;
|
|
19202
19598
|
}
|
|
@@ -19224,7 +19620,7 @@ function reconcileBaseSkills({
|
|
|
19224
19620
|
result.installed += 1;
|
|
19225
19621
|
}
|
|
19226
19622
|
for (const targetRoot of paths.targetRoots) {
|
|
19227
|
-
|
|
19623
|
+
mkdirSync14(targetRoot, { recursive: true, mode: 448 });
|
|
19228
19624
|
const targetLabel = relative5(home, targetRoot).replaceAll("/", "_") || "home";
|
|
19229
19625
|
for (const name of BASE_SKILL_NAMES) {
|
|
19230
19626
|
const skillTarget = join17(paths.baseRoot, name);
|
|
@@ -19294,7 +19690,7 @@ function processIsAlive2(pid) {
|
|
|
19294
19690
|
return error?.code === "EPERM";
|
|
19295
19691
|
}
|
|
19296
19692
|
}
|
|
19297
|
-
function
|
|
19693
|
+
function delay4(milliseconds) {
|
|
19298
19694
|
return new Promise((resolve10) => setTimeout(resolve10, milliseconds));
|
|
19299
19695
|
}
|
|
19300
19696
|
async function quarantineStaleLock(lockDirectory, snapshot) {
|
|
@@ -19345,7 +19741,7 @@ async function withSkillSyncLock(callback, {
|
|
|
19345
19741
|
}
|
|
19346
19742
|
const observed = await lockSnapshot(lockDirectory);
|
|
19347
19743
|
if (observed && now() - observed.at > staleMs && !processIsAlive2(observed.pid)) {
|
|
19348
|
-
await
|
|
19744
|
+
await delay4(Math.max(pollMs, 10));
|
|
19349
19745
|
const current = await lockSnapshot(lockDirectory);
|
|
19350
19746
|
if (sameLockSnapshot(observed, current) && !processIsAlive2(current?.pid)) {
|
|
19351
19747
|
if (await quarantineStaleLock(lockDirectory, current)) continue;
|
|
@@ -19354,7 +19750,7 @@ async function withSkillSyncLock(callback, {
|
|
|
19354
19750
|
if (now() >= deadline) {
|
|
19355
19751
|
throw new Error("Timed out waiting for another Notis skill sync to finish.");
|
|
19356
19752
|
}
|
|
19357
|
-
await
|
|
19753
|
+
await delay4(pollMs);
|
|
19358
19754
|
}
|
|
19359
19755
|
const heartbeatMs = Math.max(10, Math.min(3e4, Math.floor(staleMs / 3)));
|
|
19360
19756
|
let heartbeatStopped = false;
|
|
@@ -19573,7 +19969,7 @@ async function reportCliCommand({
|
|
|
19573
19969
|
function readCliVersion() {
|
|
19574
19970
|
try {
|
|
19575
19971
|
const manifestPath = join19(dirname18(fileURLToPath10(import.meta.url)), "..", "package.json");
|
|
19576
|
-
const version = JSON.parse(
|
|
19972
|
+
const version = JSON.parse(readFileSync21(manifestPath, "utf-8")).version;
|
|
19577
19973
|
if (typeof version === "string" && version.trim()) {
|
|
19578
19974
|
return version.trim();
|
|
19579
19975
|
}
|