@notis_ai/cli 0.2.15 → 0.2.16
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 +616 -198
- package/dist/base-skills/notis-apps/SKILL.md +101 -37
- package/dist/base-skills/notis-cli/SKILL.md +80 -22
- package/package.json +1 -1
- package/skills/notis-apps/cli.md +2 -2
- package/src/command-specs/apps.js +183 -70
- 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/src/runtime/profiles.js +19 -11
- package/src/runtime/sync-skills.js +20 -4
- package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +7 -1
- package/template/packages/sdk/src/config.ts +6 -0
- package/template/packages/sdk/src/hooks/useCollectionInteractions.ts +23 -6
- 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/interactions/shortcuts.tsx +7 -1
- 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,7 +14479,60 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
14093
14479
|
port,
|
|
14094
14480
|
sessionsFilePath
|
|
14095
14481
|
});
|
|
14096
|
-
|
|
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
|
+
});
|
|
14486
|
+
let heartbeatTimer = null;
|
|
14487
|
+
let consumerTimer = null;
|
|
14488
|
+
let shuttingDown = false;
|
|
14489
|
+
const shutdown = async (signal) => {
|
|
14490
|
+
if (shuttingDown) return;
|
|
14491
|
+
shuttingDown = true;
|
|
14492
|
+
process.stdout.write(`
|
|
14493
|
+
[notis apps dev] stopping (${signal})...
|
|
14494
|
+
`);
|
|
14495
|
+
if (heartbeatTimer) {
|
|
14496
|
+
clearInterval(heartbeatTimer);
|
|
14497
|
+
heartbeatTimer = null;
|
|
14498
|
+
}
|
|
14499
|
+
if (consumerTimer) {
|
|
14500
|
+
clearInterval(consumerTimer);
|
|
14501
|
+
consumerTimer = null;
|
|
14502
|
+
}
|
|
14503
|
+
if (manualConsumerTimer) {
|
|
14504
|
+
clearInterval(manualConsumerTimer);
|
|
14505
|
+
manualConsumerTimer = null;
|
|
14506
|
+
}
|
|
14507
|
+
if (manualConsumerInstanceId) {
|
|
14508
|
+
try {
|
|
14509
|
+
removeAppDevConsumer(manualConsumerInstanceId);
|
|
14510
|
+
} catch {
|
|
14511
|
+
}
|
|
14512
|
+
}
|
|
14513
|
+
try {
|
|
14514
|
+
await devServer.close();
|
|
14515
|
+
} catch {
|
|
14516
|
+
}
|
|
14517
|
+
try {
|
|
14518
|
+
removeAppDevSession(sessionId, sessionsFilePath);
|
|
14519
|
+
} catch {
|
|
14520
|
+
}
|
|
14521
|
+
if (sourceHostLock) {
|
|
14522
|
+
releaseAppDevHostLock(sourceHostLock);
|
|
14523
|
+
sourceHostLock = null;
|
|
14524
|
+
}
|
|
14525
|
+
process.exit(EXIT_CODES.ok);
|
|
14526
|
+
};
|
|
14527
|
+
const handleSigint = () => {
|
|
14528
|
+
void shutdown("SIGINT");
|
|
14529
|
+
};
|
|
14530
|
+
const handleSigterm = () => {
|
|
14531
|
+
void shutdown("SIGTERM");
|
|
14532
|
+
};
|
|
14533
|
+
process.on("SIGINT", handleSigint);
|
|
14534
|
+
process.on("SIGTERM", handleSigterm);
|
|
14535
|
+
heartbeatTimer = setInterval(() => {
|
|
14097
14536
|
try {
|
|
14098
14537
|
heartbeatAppDevSession(sessionId, (/* @__PURE__ */ new Date()).toISOString(), sessionsFilePath);
|
|
14099
14538
|
} catch (error) {
|
|
@@ -14146,6 +14585,8 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
14146
14585
|
sessionId,
|
|
14147
14586
|
hostPid: process.pid,
|
|
14148
14587
|
sourceHost: !sharedBundleBaseUrls,
|
|
14588
|
+
...desktopHostOwnership || {},
|
|
14589
|
+
...devServer.getWatcherOwnership(app.devSlug) || {},
|
|
14149
14590
|
bundleReady: devServer.isBundleReady(app.devSlug),
|
|
14150
14591
|
...!sharedBundleBaseUrls ? {
|
|
14151
14592
|
discoveredProjects: discoveredAppDirs,
|
|
@@ -14194,6 +14635,8 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
14194
14635
|
}
|
|
14195
14636
|
}
|
|
14196
14637
|
if (apps.length === 0) {
|
|
14638
|
+
process.off("SIGINT", handleSigint);
|
|
14639
|
+
process.off("SIGTERM", handleSigterm);
|
|
14197
14640
|
clearInterval(heartbeatTimer);
|
|
14198
14641
|
heartbeatTimer = null;
|
|
14199
14642
|
try {
|
|
@@ -14221,9 +14664,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
14221
14664
|
...canonicalSelection.warnings,
|
|
14222
14665
|
...registrationWarnings,
|
|
14223
14666
|
...databaseMaterializationWarnings(apps),
|
|
14224
|
-
...liveDataWarnings(apps)
|
|
14667
|
+
...liveDataWarnings(apps),
|
|
14668
|
+
...versionPrecedenceWarnings(apps)
|
|
14225
14669
|
];
|
|
14226
|
-
let consumerTimer = null;
|
|
14227
14670
|
ctx.output.emitSuccess({
|
|
14228
14671
|
command: ctx.spec.command_path.join(" "),
|
|
14229
14672
|
data: {
|
|
@@ -14244,7 +14687,10 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
14244
14687
|
created: app.created,
|
|
14245
14688
|
linked_app_id: app.linkedAppId,
|
|
14246
14689
|
database_materialization: app.databaseMaterialization,
|
|
14247
|
-
live_data: app.liveData
|
|
14690
|
+
live_data: app.liveData,
|
|
14691
|
+
local_release_version: app.localReleaseVersion,
|
|
14692
|
+
installed_release_version: app.installedReleaseVersion,
|
|
14693
|
+
mount_eligible: app.mountEligible
|
|
14248
14694
|
}))
|
|
14249
14695
|
},
|
|
14250
14696
|
warnings,
|
|
@@ -14254,58 +14700,13 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
14254
14700
|
`Watching ${readAppDevRoots().roots.length} persistent development root(s).`,
|
|
14255
14701
|
...useInstalledDatabases ? [`Databases: ${apps.filter((app) => app.liveData?.enabled).length}/${apps.length} app(s) reading the installed app's live rows`] : [],
|
|
14256
14702
|
"",
|
|
14257
|
-
...apps.map((app) => ` ${app.name.padEnd(24)} ${app.bundleBaseUrl} -> ${app.appHref}`),
|
|
14703
|
+
...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
14704
|
"",
|
|
14259
|
-
sharedBundleBaseUrls ? `
|
|
14705
|
+
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
14706
|
"",
|
|
14261
14707
|
"Press Ctrl-C to stop."
|
|
14262
14708
|
].join("\n")
|
|
14263
14709
|
});
|
|
14264
|
-
let shuttingDown = false;
|
|
14265
|
-
const shutdown = async (signal) => {
|
|
14266
|
-
if (shuttingDown) return;
|
|
14267
|
-
shuttingDown = true;
|
|
14268
|
-
process.stdout.write(`
|
|
14269
|
-
[notis apps dev] stopping (${signal})...
|
|
14270
|
-
`);
|
|
14271
|
-
if (heartbeatTimer) {
|
|
14272
|
-
clearInterval(heartbeatTimer);
|
|
14273
|
-
heartbeatTimer = null;
|
|
14274
|
-
}
|
|
14275
|
-
if (consumerTimer) {
|
|
14276
|
-
clearInterval(consumerTimer);
|
|
14277
|
-
consumerTimer = null;
|
|
14278
|
-
}
|
|
14279
|
-
if (manualConsumerTimer) {
|
|
14280
|
-
clearInterval(manualConsumerTimer);
|
|
14281
|
-
manualConsumerTimer = null;
|
|
14282
|
-
}
|
|
14283
|
-
if (manualConsumerInstanceId) {
|
|
14284
|
-
try {
|
|
14285
|
-
removeAppDevConsumer(manualConsumerInstanceId);
|
|
14286
|
-
} catch {
|
|
14287
|
-
}
|
|
14288
|
-
}
|
|
14289
|
-
try {
|
|
14290
|
-
removeAppDevSession(sessionId, sessionsFilePath);
|
|
14291
|
-
} catch {
|
|
14292
|
-
}
|
|
14293
|
-
try {
|
|
14294
|
-
await devServer.close();
|
|
14295
|
-
} catch {
|
|
14296
|
-
}
|
|
14297
|
-
if (sourceHostLock) {
|
|
14298
|
-
releaseAppDevHostLock(sourceHostLock);
|
|
14299
|
-
sourceHostLock = null;
|
|
14300
|
-
}
|
|
14301
|
-
process.exit(EXIT_CODES.ok);
|
|
14302
|
-
};
|
|
14303
|
-
process.on("SIGINT", () => {
|
|
14304
|
-
void shutdown("SIGINT");
|
|
14305
|
-
});
|
|
14306
|
-
process.on("SIGTERM", () => {
|
|
14307
|
-
void shutdown("SIGTERM");
|
|
14308
|
-
});
|
|
14309
14710
|
if (consumerMode === "machine" || consumerMode === "environment") {
|
|
14310
14711
|
consumerTimer = setInterval(() => {
|
|
14311
14712
|
if (!hasAppDevConsumer(readAppDevConsumers(), {
|
|
@@ -14666,7 +15067,7 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
14666
15067
|
`)
|
|
14667
15068
|
});
|
|
14668
15069
|
browserTouched = true;
|
|
14669
|
-
|
|
15070
|
+
mkdirSync11(outputDir, { recursive: true });
|
|
14670
15071
|
const results = [];
|
|
14671
15072
|
for (const capture of captures) {
|
|
14672
15073
|
const { route, scenario, focus, theme, fileName } = capture;
|
|
@@ -14764,7 +15165,7 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
14764
15165
|
}
|
|
14765
15166
|
}
|
|
14766
15167
|
if (rawOutputDir) {
|
|
14767
|
-
|
|
15168
|
+
rmSync10(rawOutputDir, { recursive: true, force: true });
|
|
14768
15169
|
}
|
|
14769
15170
|
}
|
|
14770
15171
|
}
|
|
@@ -14803,13 +15204,19 @@ async function appsPullHandler(ctx) {
|
|
|
14803
15204
|
const appId = ctx.args.appId;
|
|
14804
15205
|
const result = await runToolCommand({
|
|
14805
15206
|
runtime: ctx.runtime,
|
|
14806
|
-
|
|
14807
|
-
|
|
15207
|
+
// Pull is source retrieval plus local link state. LIST_APPS is deliberately
|
|
15208
|
+
// non-materializing; GET_APP hydrates missing declared databases and would
|
|
15209
|
+
// turn a read-only pull into a remote mutation before build/verification.
|
|
15210
|
+
toolName: LIST_APPS_TOOL
|
|
14808
15211
|
});
|
|
14809
15212
|
if (ctx.runtime.credentialKind === "oauth" && !await ensureFreshOAuthCredential(ctx.runtime)) {
|
|
14810
15213
|
throw usageError("Pulling app source requires a current OAuth grant. Run `notis login` and retry.");
|
|
14811
15214
|
}
|
|
14812
|
-
const
|
|
15215
|
+
const apps = Array.isArray(result.payload?.apps) ? result.payload.apps : [];
|
|
15216
|
+
const app = apps.find((candidate) => (candidate?.app_id || candidate?.id) === appId);
|
|
15217
|
+
if (!app) {
|
|
15218
|
+
throw usageError(`App ${appId} is not accessible to the active profile.`);
|
|
15219
|
+
}
|
|
14813
15220
|
const defaultDir = slugify(app.slug) || slugify(app.name) || slugify(appId);
|
|
14814
15221
|
const targetDir = ctx.args.dir ? resolveProjectDir(ctx.args.dir) : defaultAppProjectDir(defaultDir);
|
|
14815
15222
|
const version = ctx.options.sourceVersion || "latest";
|
|
@@ -14830,7 +15237,7 @@ async function appsPullHandler(ctx) {
|
|
|
14830
15237
|
project_dir: pulled.projectDir,
|
|
14831
15238
|
version: pulled.version
|
|
14832
15239
|
},
|
|
14833
|
-
humanSummary: `Pulled ${versionLabel} to ${pulled.projectDir}.
|
|
15240
|
+
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
15241
|
});
|
|
14835
15242
|
}
|
|
14836
15243
|
function updateLinkedDeployState(projectDir, linkedState, appId, version, profileKey = null) {
|
|
@@ -15212,7 +15619,7 @@ var appsCommandSpecs = [
|
|
|
15212
15619
|
{
|
|
15213
15620
|
command_path: ["apps", "dev"],
|
|
15214
15621
|
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.",
|
|
15622
|
+
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
15623
|
args_schema: {
|
|
15217
15624
|
arguments: [
|
|
15218
15625
|
{ token: "[dir]", key: "dir", description: "Project directory or monorepo root (default: current dir)." }
|
|
@@ -15374,7 +15781,7 @@ var appsCommandSpecs = [
|
|
|
15374
15781
|
{
|
|
15375
15782
|
command_path: ["apps", "pull"],
|
|
15376
15783
|
summary: "Download a Notis app source snapshot into a local project folder.",
|
|
15377
|
-
when_to_use: "Edit an installed app locally.
|
|
15784
|
+
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
15785
|
args_schema: {
|
|
15379
15786
|
arguments: [
|
|
15380
15787
|
{ token: "<app-id>", description: "Remote app ID to pull." },
|
|
@@ -15482,7 +15889,7 @@ var appsCommandSpecs = [
|
|
|
15482
15889
|
];
|
|
15483
15890
|
|
|
15484
15891
|
// src/command-specs/tools.js
|
|
15485
|
-
import { accessSync, constants as fsConstants2, createReadStream as createReadStream2, existsSync as existsSync11, readFileSync as
|
|
15892
|
+
import { accessSync, constants as fsConstants2, createReadStream as createReadStream2, existsSync as existsSync11, readFileSync as readFileSync14, statSync as statSync6 } from "node:fs";
|
|
15486
15893
|
import { createHash as createHash3 } from "node:crypto";
|
|
15487
15894
|
import { basename as basename4 } from "node:path";
|
|
15488
15895
|
async function resolveJsonInput(value, label) {
|
|
@@ -15499,7 +15906,7 @@ async function resolveJsonInput(value, label) {
|
|
|
15499
15906
|
if (!existsSync11(filePath)) {
|
|
15500
15907
|
throw usageError(`File not found: ${filePath}`);
|
|
15501
15908
|
}
|
|
15502
|
-
return parseJson(
|
|
15909
|
+
return parseJson(readFileSync14(filePath, "utf-8"), label);
|
|
15503
15910
|
}
|
|
15504
15911
|
return parseJson(value, label);
|
|
15505
15912
|
}
|
|
@@ -16373,7 +16780,7 @@ var metaCommandSpecs = [
|
|
|
16373
16780
|
];
|
|
16374
16781
|
|
|
16375
16782
|
// src/command-specs/onboarding.js
|
|
16376
|
-
import { readFileSync as
|
|
16783
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
16377
16784
|
import { dirname as dirname15, join as join15 } from "node:path";
|
|
16378
16785
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
16379
16786
|
|
|
@@ -16385,10 +16792,10 @@ import { basename as basename5 } from "node:path";
|
|
|
16385
16792
|
import {
|
|
16386
16793
|
chmodSync,
|
|
16387
16794
|
existsSync as existsSync12,
|
|
16388
|
-
mkdirSync as
|
|
16389
|
-
readFileSync as
|
|
16795
|
+
mkdirSync as mkdirSync12,
|
|
16796
|
+
readFileSync as readFileSync15,
|
|
16390
16797
|
readdirSync as readdirSync7,
|
|
16391
|
-
renameSync as
|
|
16798
|
+
renameSync as renameSync8,
|
|
16392
16799
|
writeFileSync as writeFileSync10
|
|
16393
16800
|
} from "node:fs";
|
|
16394
16801
|
import { createHash as createHash4 } from "node:crypto";
|
|
@@ -16405,13 +16812,13 @@ var MANAGED_HOOK_MARKER = "--notis-managed-agent-hook";
|
|
|
16405
16812
|
var LEGACY_MANAGED_HOOK_MARKER = "NOTIS_MANAGED_AGENT_HOOK=1";
|
|
16406
16813
|
var AGENT_IDS = Object.freeze(["codex", "claude-code"]);
|
|
16407
16814
|
function atomicWrite(filePath, contents, mode = 384) {
|
|
16408
|
-
|
|
16815
|
+
mkdirSync12(dirname13(filePath), { recursive: true, mode: 448 });
|
|
16409
16816
|
const temporaryPath = `${filePath}.notis-${process.pid}-${Date.now()}.tmp`;
|
|
16410
16817
|
writeFileSync10(temporaryPath, contents, { mode });
|
|
16411
|
-
|
|
16818
|
+
renameSync8(temporaryPath, filePath);
|
|
16412
16819
|
}
|
|
16413
16820
|
function readText(filePath) {
|
|
16414
|
-
return existsSync12(filePath) ?
|
|
16821
|
+
return existsSync12(filePath) ? readFileSync15(filePath, "utf-8") : "";
|
|
16415
16822
|
}
|
|
16416
16823
|
function activeCodexInstructionsPath(home) {
|
|
16417
16824
|
const overridePath = join13(home, ".codex", "AGENTS.override.md");
|
|
@@ -16454,7 +16861,7 @@ function upsertInstructionBlock(filePath, block) {
|
|
|
16454
16861
|
}
|
|
16455
16862
|
function readJsonObject(filePath) {
|
|
16456
16863
|
if (!existsSync12(filePath)) return {};
|
|
16457
|
-
const raw =
|
|
16864
|
+
const raw = readFileSync15(filePath, "utf-8");
|
|
16458
16865
|
try {
|
|
16459
16866
|
const parsed = JSON.parse(raw);
|
|
16460
16867
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
@@ -16506,7 +16913,7 @@ function installManagedHookRuntime({
|
|
|
16506
16913
|
nodePath = process.execPath,
|
|
16507
16914
|
platform = process.platform
|
|
16508
16915
|
} = {}) {
|
|
16509
|
-
const bundle =
|
|
16916
|
+
const bundle = readFileSync15(bundlePath);
|
|
16510
16917
|
const digest = createHash4("sha256").update(bundle).digest("hex");
|
|
16511
16918
|
const runtimePath = join13(
|
|
16512
16919
|
home,
|
|
@@ -16516,7 +16923,7 @@ function installManagedHookRuntime({
|
|
|
16516
16923
|
digest,
|
|
16517
16924
|
"notis-agent-hook.mjs"
|
|
16518
16925
|
);
|
|
16519
|
-
const existingDigest = existsSync12(runtimePath) ? createHash4("sha256").update(
|
|
16926
|
+
const existingDigest = existsSync12(runtimePath) ? createHash4("sha256").update(readFileSync15(runtimePath)).digest("hex") : null;
|
|
16520
16927
|
if (existingDigest !== digest) atomicWrite(runtimePath, bundle, 320);
|
|
16521
16928
|
chmodSync(runtimePath, 320);
|
|
16522
16929
|
const launcherPath = join13(
|
|
@@ -16635,7 +17042,7 @@ function installAgentSetup({
|
|
|
16635
17042
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(String(profileName || ""))) {
|
|
16636
17043
|
throw new Error("A valid authenticated Notis profile is required for agent setup");
|
|
16637
17044
|
}
|
|
16638
|
-
const instructions =
|
|
17045
|
+
const instructions = readFileSync15(INSTRUCTIONS_PATH, "utf-8").trim();
|
|
16639
17046
|
const results = [];
|
|
16640
17047
|
const shouldRepairManagedHooks = memoryHooks === true || memoryHooks === null && agents.some((agentId) => fileHasManagedNotisHook(agentPaths(agentId, home).hooks));
|
|
16641
17048
|
const hookRuntime = shouldRepairManagedHooks ? installManagedHookRuntime({ home, platform }) : null;
|
|
@@ -16680,9 +17087,9 @@ function installAgentSetup({
|
|
|
16680
17087
|
import { createHash as createHash5 } from "node:crypto";
|
|
16681
17088
|
import {
|
|
16682
17089
|
existsSync as existsSync13,
|
|
16683
|
-
mkdirSync as
|
|
16684
|
-
readFileSync as
|
|
16685
|
-
renameSync as
|
|
17090
|
+
mkdirSync as mkdirSync13,
|
|
17091
|
+
readFileSync as readFileSync16,
|
|
17092
|
+
renameSync as renameSync9,
|
|
16686
17093
|
writeFileSync as writeFileSync11
|
|
16687
17094
|
} from "node:fs";
|
|
16688
17095
|
import { homedir as homedir8 } from "node:os";
|
|
@@ -16700,7 +17107,7 @@ function readState(sessionId, home) {
|
|
|
16700
17107
|
const filePath = statePath(sessionId, home);
|
|
16701
17108
|
if (!filePath || !existsSync13(filePath)) return { seen: [], pending: null };
|
|
16702
17109
|
try {
|
|
16703
|
-
const parsed = JSON.parse(
|
|
17110
|
+
const parsed = JSON.parse(readFileSync16(filePath, "utf-8"));
|
|
16704
17111
|
return parsed && typeof parsed === "object" ? {
|
|
16705
17112
|
seen: Array.isArray(parsed.seen) ? parsed.seen.filter((item) => typeof item === "string") : [],
|
|
16706
17113
|
pending: parsed.pending && typeof parsed.pending === "object" ? parsed.pending : null,
|
|
@@ -16730,11 +17137,11 @@ function stateForInput(sessionId, input, home) {
|
|
|
16730
17137
|
function writeState(sessionId, state, home) {
|
|
16731
17138
|
const filePath = statePath(sessionId, home);
|
|
16732
17139
|
if (!filePath) return;
|
|
16733
|
-
|
|
17140
|
+
mkdirSync13(dirname14(filePath), { recursive: true, mode: 448 });
|
|
16734
17141
|
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
16735
17142
|
writeFileSync11(temporaryPath, `${JSON.stringify(state)}
|
|
16736
17143
|
`, { mode: 384 });
|
|
16737
|
-
|
|
17144
|
+
renameSync9(temporaryPath, filePath);
|
|
16738
17145
|
}
|
|
16739
17146
|
function rememberPendingTurn(input, home = homedir8()) {
|
|
16740
17147
|
const sessionId = input?.session_id;
|
|
@@ -17276,7 +17683,7 @@ async function fetchBrief(apiBase, timeoutMs) {
|
|
|
17276
17683
|
} catch {
|
|
17277
17684
|
}
|
|
17278
17685
|
try {
|
|
17279
|
-
return { markdown:
|
|
17686
|
+
return { markdown: readFileSync17(BUNDLED_BRIEF_PATH, "utf-8"), source: "bundled" };
|
|
17280
17687
|
} catch {
|
|
17281
17688
|
return { markdown: null, source: null };
|
|
17282
17689
|
}
|
|
@@ -17444,7 +17851,7 @@ var onboardingCommandSpecs = [
|
|
|
17444
17851
|
|
|
17445
17852
|
// src/command-specs/diagnostics.js
|
|
17446
17853
|
import { createHash as createHash7, randomUUID as randomUUID10 } from "node:crypto";
|
|
17447
|
-
import { existsSync as existsSync14, readFileSync as
|
|
17854
|
+
import { existsSync as existsSync14, readFileSync as readFileSync18 } from "node:fs";
|
|
17448
17855
|
var ENTITLEMENT_FIELDS = [
|
|
17449
17856
|
"created_at",
|
|
17450
17857
|
"included_credit_seat_count",
|
|
@@ -17850,7 +18257,7 @@ ORDER BY i.created_at ASC;`;
|
|
|
17850
18257
|
function traceFileDiagnostics(path3) {
|
|
17851
18258
|
if (!path3) return null;
|
|
17852
18259
|
if (!existsSync14(path3)) throw usageError(`Trace file not found: ${path3}`);
|
|
17853
|
-
const trace = JSON.parse(
|
|
18260
|
+
const trace = JSON.parse(readFileSync18(path3, "utf-8"));
|
|
17854
18261
|
const text = JSON.stringify(trace).toLowerCase();
|
|
17855
18262
|
const modelCounts = {};
|
|
17856
18263
|
const generationIds = /* @__PURE__ */ new Set();
|
|
@@ -18044,13 +18451,13 @@ var diagnosticCommandSpecs = [
|
|
|
18044
18451
|
import { createHash as createHash8, randomUUID as randomUUID11 } from "node:crypto";
|
|
18045
18452
|
import {
|
|
18046
18453
|
mkdtempSync as mkdtempSync4,
|
|
18047
|
-
readFileSync as
|
|
18048
|
-
rmSync as
|
|
18454
|
+
readFileSync as readFileSync19,
|
|
18455
|
+
rmSync as rmSync11,
|
|
18049
18456
|
writeFileSync as writeFileSync12
|
|
18050
18457
|
} from "node:fs";
|
|
18051
18458
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
18052
18459
|
import { basename as basename6, join as join16 } from "node:path";
|
|
18053
|
-
import { setTimeout as
|
|
18460
|
+
import { setTimeout as delay3 } from "node:timers/promises";
|
|
18054
18461
|
function discoveryNames(payload) {
|
|
18055
18462
|
return [...new Set(
|
|
18056
18463
|
(payload.results || []).flatMap((result) => [
|
|
@@ -18089,7 +18496,7 @@ async function fetchDiscoveryWithRetry(ctx, useCase, knownFields) {
|
|
|
18089
18496
|
message: `Tool discovery transiently failed; retrying (${attempt}/${maxAttempts - 1})`,
|
|
18090
18497
|
requestId: error?.details?.request_id || null
|
|
18091
18498
|
});
|
|
18092
|
-
await
|
|
18499
|
+
await delay3(250 * 2 ** (attempt - 1));
|
|
18093
18500
|
}
|
|
18094
18501
|
}
|
|
18095
18502
|
throw usageError("Dropbox tool discovery exhausted its retries.");
|
|
@@ -18145,7 +18552,7 @@ unicode=\xE9t\xE9 Z\xFCrich \u6771\u4EAC
|
|
|
18145
18552
|
);
|
|
18146
18553
|
}
|
|
18147
18554
|
const localHash = await hashFileSha256(fixturePath);
|
|
18148
|
-
const localBytes =
|
|
18555
|
+
const localBytes = readFileSync19(fixturePath);
|
|
18149
18556
|
const destination = remotePath(
|
|
18150
18557
|
ctx.options.remoteFolder,
|
|
18151
18558
|
`notis-file-upload-${marker}-${basename6(fixturePath)}`
|
|
@@ -18358,7 +18765,7 @@ unicode=\xE9t\xE9 Z\xFCrich \u6771\u4EAC
|
|
|
18358
18765
|
result.cleanup.deleted = false;
|
|
18359
18766
|
}
|
|
18360
18767
|
}
|
|
18361
|
-
|
|
18768
|
+
rmSync11(tempDir, { recursive: true, force: true });
|
|
18362
18769
|
}
|
|
18363
18770
|
}
|
|
18364
18771
|
var smokeCommandSpecs = [
|
|
@@ -18666,7 +19073,7 @@ var profileCommandSpecs = [
|
|
|
18666
19073
|
|
|
18667
19074
|
// src/runtime/git.js
|
|
18668
19075
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
18669
|
-
import { lstatSync as lstatSync6, readFileSync as
|
|
19076
|
+
import { lstatSync as lstatSync6, readFileSync as readFileSync20 } from "node:fs";
|
|
18670
19077
|
import { basename as basename7, relative as relative4, resolve as resolve8, sep as sep2 } from "node:path";
|
|
18671
19078
|
var GIT_TIMEOUT_MS = 12e4;
|
|
18672
19079
|
var MAX_SECRET_SCAN_BYTES = 1e6;
|
|
@@ -18735,7 +19142,7 @@ function sensitiveAutoCommitFiles(repository) {
|
|
|
18735
19142
|
try {
|
|
18736
19143
|
const stat2 = lstatSync6(absolute);
|
|
18737
19144
|
if (!stat2.isFile() || stat2.size > MAX_SECRET_SCAN_BYTES) continue;
|
|
18738
|
-
const content =
|
|
19145
|
+
const content = readFileSync20(absolute, "utf8");
|
|
18739
19146
|
if (SENSITIVE_CONTENT.some((pattern) => pattern.test(content))) sensitive.push(path3);
|
|
18740
19147
|
} catch {
|
|
18741
19148
|
}
|
|
@@ -19121,10 +19528,10 @@ import {
|
|
|
19121
19528
|
cpSync as cpSync2,
|
|
19122
19529
|
existsSync as existsSync15,
|
|
19123
19530
|
lstatSync as lstatSync7,
|
|
19124
|
-
mkdirSync as
|
|
19125
|
-
readlinkSync,
|
|
19126
|
-
renameSync as
|
|
19127
|
-
rmSync as
|
|
19531
|
+
mkdirSync as mkdirSync14,
|
|
19532
|
+
readlinkSync as readlinkSync2,
|
|
19533
|
+
renameSync as renameSync10,
|
|
19534
|
+
rmSync as rmSync12,
|
|
19128
19535
|
symlinkSync
|
|
19129
19536
|
} from "node:fs";
|
|
19130
19537
|
import { homedir as homedir9 } from "node:os";
|
|
@@ -19168,35 +19575,35 @@ function getBaseSkillPaths({ home = homedir9(), userId = null } = {}) {
|
|
|
19168
19575
|
function sameLinkTarget(linkPath, expectedTarget) {
|
|
19169
19576
|
try {
|
|
19170
19577
|
if (!lstatSync7(linkPath).isSymbolicLink()) return false;
|
|
19171
|
-
return resolve9(dirname16(linkPath),
|
|
19578
|
+
return resolve9(dirname16(linkPath), readlinkSync2(linkPath)) === resolve9(expectedTarget);
|
|
19172
19579
|
} catch {
|
|
19173
19580
|
return false;
|
|
19174
19581
|
}
|
|
19175
19582
|
}
|
|
19176
19583
|
function backupConflict(linkPath, backupRoot, targetLabel, skillName, now) {
|
|
19177
19584
|
const backupPath = join17(backupRoot, String(now), targetLabel, skillName);
|
|
19178
|
-
|
|
19179
|
-
|
|
19585
|
+
mkdirSync14(dirname16(backupPath), { recursive: true, mode: 448 });
|
|
19586
|
+
renameSync10(linkPath, backupPath);
|
|
19180
19587
|
return backupPath;
|
|
19181
19588
|
}
|
|
19182
19589
|
function replaceDirectory(source, destination) {
|
|
19183
|
-
|
|
19590
|
+
mkdirSync14(dirname16(destination), { recursive: true, mode: 448 });
|
|
19184
19591
|
const temporary = `${destination}.tmp-${process.pid}-${Date.now()}`;
|
|
19185
19592
|
const previous = `${destination}.previous-${process.pid}-${Date.now()}`;
|
|
19186
|
-
|
|
19593
|
+
rmSync12(temporary, { recursive: true, force: true });
|
|
19187
19594
|
cpSync2(source, temporary, { recursive: true });
|
|
19188
19595
|
let movedPrevious = false;
|
|
19189
19596
|
try {
|
|
19190
19597
|
if (existsSync15(destination)) {
|
|
19191
|
-
|
|
19598
|
+
renameSync10(destination, previous);
|
|
19192
19599
|
movedPrevious = true;
|
|
19193
19600
|
}
|
|
19194
|
-
|
|
19195
|
-
if (movedPrevious)
|
|
19601
|
+
renameSync10(temporary, destination);
|
|
19602
|
+
if (movedPrevious) rmSync12(previous, { recursive: true, force: true });
|
|
19196
19603
|
} catch (error) {
|
|
19197
|
-
|
|
19604
|
+
rmSync12(temporary, { recursive: true, force: true });
|
|
19198
19605
|
if (movedPrevious && !existsSync15(destination) && existsSync15(previous)) {
|
|
19199
|
-
|
|
19606
|
+
renameSync10(previous, destination);
|
|
19200
19607
|
}
|
|
19201
19608
|
throw error;
|
|
19202
19609
|
}
|
|
@@ -19224,7 +19631,7 @@ function reconcileBaseSkills({
|
|
|
19224
19631
|
result.installed += 1;
|
|
19225
19632
|
}
|
|
19226
19633
|
for (const targetRoot of paths.targetRoots) {
|
|
19227
|
-
|
|
19634
|
+
mkdirSync14(targetRoot, { recursive: true, mode: 448 });
|
|
19228
19635
|
const targetLabel = relative5(home, targetRoot).replaceAll("/", "_") || "home";
|
|
19229
19636
|
for (const name of BASE_SKILL_NAMES) {
|
|
19230
19637
|
const skillTarget = join17(paths.baseRoot, name);
|
|
@@ -19294,7 +19701,7 @@ function processIsAlive2(pid) {
|
|
|
19294
19701
|
return error?.code === "EPERM";
|
|
19295
19702
|
}
|
|
19296
19703
|
}
|
|
19297
|
-
function
|
|
19704
|
+
function delay4(milliseconds) {
|
|
19298
19705
|
return new Promise((resolve10) => setTimeout(resolve10, milliseconds));
|
|
19299
19706
|
}
|
|
19300
19707
|
async function quarantineStaleLock(lockDirectory, snapshot) {
|
|
@@ -19315,6 +19722,20 @@ async function writeLockOwnerAtomically(lockDirectory, owner) {
|
|
|
19315
19722
|
await writeFile(temporaryOwnerPath, JSON.stringify(owner), { mode: 384 });
|
|
19316
19723
|
await rename(temporaryOwnerPath, ownerPath);
|
|
19317
19724
|
}
|
|
19725
|
+
async function releaseOwnedLock(lockDirectory, ownerId) {
|
|
19726
|
+
const owner = await lockSnapshot(lockDirectory);
|
|
19727
|
+
if (owner?.id !== ownerId) return;
|
|
19728
|
+
const quarantineRoot = join18(dirname17(lockDirectory), ".stale-operation-locks");
|
|
19729
|
+
const releasedDirectory = join18(quarantineRoot, `released.${ownerId}`);
|
|
19730
|
+
await mkdir(quarantineRoot, { recursive: true, mode: 448 });
|
|
19731
|
+
try {
|
|
19732
|
+
await rename(lockDirectory, releasedDirectory);
|
|
19733
|
+
} catch (error) {
|
|
19734
|
+
if (error?.code === "ENOENT") return;
|
|
19735
|
+
throw error;
|
|
19736
|
+
}
|
|
19737
|
+
await rm(releasedDirectory, { recursive: true, force: true });
|
|
19738
|
+
}
|
|
19318
19739
|
async function withSkillSyncLock(callback, {
|
|
19319
19740
|
home = homedir10(),
|
|
19320
19741
|
timeoutMs = DEFAULT_LOCK_TIMEOUT_MS,
|
|
@@ -19345,7 +19766,7 @@ async function withSkillSyncLock(callback, {
|
|
|
19345
19766
|
}
|
|
19346
19767
|
const observed = await lockSnapshot(lockDirectory);
|
|
19347
19768
|
if (observed && now() - observed.at > staleMs && !processIsAlive2(observed.pid)) {
|
|
19348
|
-
await
|
|
19769
|
+
await delay4(Math.max(pollMs, 10));
|
|
19349
19770
|
const current = await lockSnapshot(lockDirectory);
|
|
19350
19771
|
if (sameLockSnapshot(observed, current) && !processIsAlive2(current?.pid)) {
|
|
19351
19772
|
if (await quarantineStaleLock(lockDirectory, current)) continue;
|
|
@@ -19354,7 +19775,7 @@ async function withSkillSyncLock(callback, {
|
|
|
19354
19775
|
if (now() >= deadline) {
|
|
19355
19776
|
throw new Error("Timed out waiting for another Notis skill sync to finish.");
|
|
19356
19777
|
}
|
|
19357
|
-
await
|
|
19778
|
+
await delay4(pollMs);
|
|
19358
19779
|
}
|
|
19359
19780
|
const heartbeatMs = Math.max(10, Math.min(3e4, Math.floor(staleMs / 3)));
|
|
19360
19781
|
let heartbeatStopped = false;
|
|
@@ -19384,10 +19805,7 @@ async function withSkillSyncLock(callback, {
|
|
|
19384
19805
|
heartbeatStopped = true;
|
|
19385
19806
|
clearInterval(heartbeat);
|
|
19386
19807
|
await heartbeatInFlight;
|
|
19387
|
-
|
|
19388
|
-
if (owner?.id === ownerId) {
|
|
19389
|
-
await rm(lockDirectory, { recursive: true, force: true });
|
|
19390
|
-
}
|
|
19808
|
+
await releaseOwnedLock(lockDirectory, ownerId);
|
|
19391
19809
|
}
|
|
19392
19810
|
}
|
|
19393
19811
|
async function reconcileAllSkills({
|
|
@@ -19573,7 +19991,7 @@ async function reportCliCommand({
|
|
|
19573
19991
|
function readCliVersion() {
|
|
19574
19992
|
try {
|
|
19575
19993
|
const manifestPath = join19(dirname18(fileURLToPath10(import.meta.url)), "..", "package.json");
|
|
19576
|
-
const version = JSON.parse(
|
|
19994
|
+
const version = JSON.parse(readFileSync21(manifestPath, "utf-8")).version;
|
|
19577
19995
|
if (typeof version === "string" && version.trim()) {
|
|
19578
19996
|
return version.trim();
|
|
19579
19997
|
}
|