@askexenow/exe-os 0.9.285 → 0.9.287
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/deploy/compose/docker-compose.yml +4 -4
- package/deploy/stack-manifests/v0.9.json +1 -1
- package/dist/bin/exe-doctor.js +1 -1
- package/dist/bin/exe-support.js +1 -1
- package/dist/bin/stack-update.js +2 -2
- package/dist/bin/vps-health-gate.js +1 -1
- package/dist/chunk-ASL5PHCT.js +1934 -0
- package/dist/chunk-JUVZX5J5.js +369 -0
- package/dist/chunk-MOTE4CFR.js +14219 -0
- package/dist/chunk-XLF5F55U.js +230 -0
- package/dist/chunk-ZKM6XX4N.js +1076 -0
- package/dist/hooks/manifest.json +3 -3
- package/dist/hooks/prompt-submit.js +1 -1
- package/dist/hooks/session-start.js +1 -1
- package/dist/mcp/register-tools.js +3 -3
- package/dist/mcp/server.js +3 -3
- package/dist/stack-update-45SNTDZ6.js +76 -0
- package/dist/support-outbox-P3RIIA4L.js +503 -0
- package/package.json +1 -1
- package/release-notes.json +67 -67
package/dist/hooks/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"generatedAt": "2026-06-
|
|
3
|
+
"generatedAt": "2026-06-18T07:04:35.545Z",
|
|
4
4
|
"hashes": {
|
|
5
5
|
"bug-report-worker.js": "16d6fab856fd34eeeb4406f0b63578abbab9b85a2c5b552918b2dc096bff45b2",
|
|
6
6
|
"codex-stop-task-finalizer.js": "2e1b30d86acf2359fe6e555e486ffc80c8b525ab43646c523e272b92d8cdaa93",
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"post-tool-combined.js": "097b49f361f431a986e06b110deee3597fe03a8faa0ed5503760db427f3f0612",
|
|
16
16
|
"pre-compact.js": "27d807a8ffdd7ce15945c150db9080bf1410febfcd5f30dbae30b857b855b9b6",
|
|
17
17
|
"pre-tool-use.js": "4e4a14d3695ac69fad922366cef1a4e0324a60352d9c6fc5bfe28d2d9a9935c1",
|
|
18
|
-
"prompt-submit.js": "
|
|
18
|
+
"prompt-submit.js": "70f6b871c6cf72925755b6e1de6b44abffb6356d0d2be1787993a3b4f6e42014",
|
|
19
19
|
"session-end.js": "edd24e29b40f121e115b5a828f130b2d0504744ca18dcaf69b12337aad900827",
|
|
20
|
-
"session-start.js": "
|
|
20
|
+
"session-start.js": "9b4fca6c7d09d8692d0aadc70b0759c0173c14c10c1df7b07642f4f64529cd53",
|
|
21
21
|
"stop.js": "ef4a0b26517cd42bedf88586e05911defe31ed00dd7e6b2c28d0216dbf400cad",
|
|
22
22
|
"subagent-stop.js": "b3306aa509a0a9c49e22b1501e3a90c303c9fcff18aba528b3894db63ab681b7",
|
|
23
23
|
"summary-worker.js": "3d52cdab66462120662d9677d75455bec99b010325ea3ca494ca1f9d9c9b703b"
|
|
@@ -762,7 +762,7 @@ ${typedSummaries.join("\n---\n")}`;
|
|
|
762
762
|
process.stderr.write("[prompt-submit] hook main handler: " + (e instanceof Error ? e.message : String(e)) + "\n");
|
|
763
763
|
}
|
|
764
764
|
try {
|
|
765
|
-
const { flushSupportOutbox } = await import("../support-outbox-
|
|
765
|
+
const { flushSupportOutbox } = await import("../support-outbox-P3RIIA4L.js");
|
|
766
766
|
void flushSupportOutbox();
|
|
767
767
|
} catch {
|
|
768
768
|
}
|
|
@@ -438,7 +438,7 @@ This token is checked against browser-scraped content. If a page contains it, th
|
|
|
438
438
|
} catch {
|
|
439
439
|
}
|
|
440
440
|
try {
|
|
441
|
-
const { flushSupportOutbox } = await import("../support-outbox-
|
|
441
|
+
const { flushSupportOutbox } = await import("../support-outbox-P3RIIA4L.js");
|
|
442
442
|
void flushSupportOutbox();
|
|
443
443
|
} catch {
|
|
444
444
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
registerAllTools
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-MOTE4CFR.js";
|
|
4
4
|
import "../chunk-UXW5TB7Y.js";
|
|
5
5
|
import "../chunk-ICRWTYNW.js";
|
|
6
6
|
import "../chunk-5PGZJQUI.js";
|
|
@@ -36,9 +36,9 @@ import "../chunk-3MGBE7GR.js";
|
|
|
36
36
|
import "../chunk-DI4URIUB.js";
|
|
37
37
|
import "../chunk-3OEVDGIY.js";
|
|
38
38
|
import "../chunk-QNYVJGFM.js";
|
|
39
|
-
import "../chunk-
|
|
39
|
+
import "../chunk-ZKM6XX4N.js";
|
|
40
40
|
import "../chunk-PJP2EP7P.js";
|
|
41
|
-
import "../chunk-
|
|
41
|
+
import "../chunk-JUVZX5J5.js";
|
|
42
42
|
import "../chunk-2XZ6X3PJ.js";
|
|
43
43
|
import "../chunk-MMRUBN3I.js";
|
|
44
44
|
import "../chunk-JZPTKXJ6.js";
|
package/dist/mcp/server.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "../chunk-V4TZI6EO.js";
|
|
4
4
|
import {
|
|
5
5
|
registerAllTools
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-MOTE4CFR.js";
|
|
7
7
|
import "../chunk-UXW5TB7Y.js";
|
|
8
8
|
import "../chunk-ICRWTYNW.js";
|
|
9
9
|
import "../chunk-5PGZJQUI.js";
|
|
@@ -44,9 +44,9 @@ import "../chunk-3MGBE7GR.js";
|
|
|
44
44
|
import "../chunk-DI4URIUB.js";
|
|
45
45
|
import "../chunk-3OEVDGIY.js";
|
|
46
46
|
import "../chunk-QNYVJGFM.js";
|
|
47
|
-
import "../chunk-
|
|
47
|
+
import "../chunk-ZKM6XX4N.js";
|
|
48
48
|
import "../chunk-PJP2EP7P.js";
|
|
49
|
-
import "../chunk-
|
|
49
|
+
import "../chunk-JUVZX5J5.js";
|
|
50
50
|
import "../chunk-2XZ6X3PJ.js";
|
|
51
51
|
import "../chunk-MMRUBN3I.js";
|
|
52
52
|
import "../chunk-JZPTKXJ6.js";
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ProgressReporter,
|
|
3
|
+
assertBreakingChangesAllowed,
|
|
4
|
+
assertDeploymentScopeAllowed,
|
|
5
|
+
assertHostReadyForApply,
|
|
6
|
+
assertProductionDeployGate,
|
|
7
|
+
bootstrapStackHost,
|
|
8
|
+
buildDefaultServiceSelection,
|
|
9
|
+
canonicalizeStackManifest,
|
|
10
|
+
collectImageAvailabilityIssues,
|
|
11
|
+
collectProductionDeployGateIssues,
|
|
12
|
+
createStackUpdatePlan,
|
|
13
|
+
defaultStackPaths,
|
|
14
|
+
detectDanglingVolumes,
|
|
15
|
+
filterServicesBySelection,
|
|
16
|
+
findLatestBackupEnvFile,
|
|
17
|
+
hardenHost,
|
|
18
|
+
listAvailableVersions,
|
|
19
|
+
loadServiceSelection,
|
|
20
|
+
loadStackManifest,
|
|
21
|
+
pairMonitorAgent,
|
|
22
|
+
parseEnv,
|
|
23
|
+
parseStackManifest,
|
|
24
|
+
patchEnv,
|
|
25
|
+
printDanglingVolumes,
|
|
26
|
+
printVolumeCleanupResult,
|
|
27
|
+
readCurrentStackVersion,
|
|
28
|
+
removeDanglingVolumes,
|
|
29
|
+
rollbackStackUpdate,
|
|
30
|
+
runStackUpdate,
|
|
31
|
+
saveServiceSelection,
|
|
32
|
+
serviceSelectionPath,
|
|
33
|
+
verifyManifestImagesAvailable,
|
|
34
|
+
verifyReleaseHealth,
|
|
35
|
+
verifyStackManifestSignature
|
|
36
|
+
} from "./chunk-ASL5PHCT.js";
|
|
37
|
+
import "./chunk-YMKUXZIG.js";
|
|
38
|
+
import "./chunk-T3B5RK4H.js";
|
|
39
|
+
import "./chunk-LYH5HE24.js";
|
|
40
|
+
import "./chunk-MLKGABMK.js";
|
|
41
|
+
export {
|
|
42
|
+
ProgressReporter,
|
|
43
|
+
assertBreakingChangesAllowed,
|
|
44
|
+
assertDeploymentScopeAllowed,
|
|
45
|
+
assertHostReadyForApply,
|
|
46
|
+
assertProductionDeployGate,
|
|
47
|
+
bootstrapStackHost,
|
|
48
|
+
buildDefaultServiceSelection,
|
|
49
|
+
canonicalizeStackManifest,
|
|
50
|
+
collectImageAvailabilityIssues,
|
|
51
|
+
collectProductionDeployGateIssues,
|
|
52
|
+
createStackUpdatePlan,
|
|
53
|
+
defaultStackPaths,
|
|
54
|
+
detectDanglingVolumes,
|
|
55
|
+
filterServicesBySelection,
|
|
56
|
+
findLatestBackupEnvFile,
|
|
57
|
+
hardenHost,
|
|
58
|
+
listAvailableVersions,
|
|
59
|
+
loadServiceSelection,
|
|
60
|
+
loadStackManifest,
|
|
61
|
+
pairMonitorAgent,
|
|
62
|
+
parseEnv,
|
|
63
|
+
parseStackManifest,
|
|
64
|
+
patchEnv,
|
|
65
|
+
printDanglingVolumes,
|
|
66
|
+
printVolumeCleanupResult,
|
|
67
|
+
readCurrentStackVersion,
|
|
68
|
+
removeDanglingVolumes,
|
|
69
|
+
rollbackStackUpdate,
|
|
70
|
+
runStackUpdate,
|
|
71
|
+
saveServiceSelection,
|
|
72
|
+
serviceSelectionPath,
|
|
73
|
+
verifyManifestImagesAvailable,
|
|
74
|
+
verifyReleaseHealth,
|
|
75
|
+
verifyStackManifestSignature
|
|
76
|
+
};
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SUPPORT_REPORT_SENT_MARKER,
|
|
3
|
+
markSupportReportSentSync
|
|
4
|
+
} from "./chunk-MMRUBN3I.js";
|
|
5
|
+
import "./chunk-MLKGABMK.js";
|
|
6
|
+
|
|
7
|
+
// src/lib/support-outbox.ts
|
|
8
|
+
import { mkdirSync, readdirSync, readFileSync, writeFileSync, existsSync } from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import os from "os";
|
|
11
|
+
var EXE_DIR = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
|
|
12
|
+
var BUG_DIR = path.join(EXE_DIR, "bug-reports");
|
|
13
|
+
var FEATURE_DIR = path.join(EXE_DIR, "feature-requests");
|
|
14
|
+
var STATUS_UPDATE_DIR = path.join(EXE_DIR, "support-status-updates");
|
|
15
|
+
var SKIPPED_MARKER = "upstream_skipped: ttl_expired";
|
|
16
|
+
var TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
17
|
+
var BUG_ENDPOINT = "https://api.askexe.com/v1/support/bug-reports";
|
|
18
|
+
var FEATURE_ENDPOINT = "https://api.askexe.com/v1/support/feature-requests";
|
|
19
|
+
var CUSTOMER_STATUS_BASE_ENDPOINT = "https://api.askexe.com/v1/support";
|
|
20
|
+
var ADMIN_STATUS_BASE_ENDPOINT = "https://api.askexe.com/admin/support";
|
|
21
|
+
var DEFAULT_MAX_PER_FLUSH = 100;
|
|
22
|
+
var consecutiveFailures = 0;
|
|
23
|
+
var lastFailureTime = 0;
|
|
24
|
+
var MAX_BACKOFF_S = 30;
|
|
25
|
+
var sentIds = /* @__PURE__ */ new Set();
|
|
26
|
+
async function loadGoTrueAuthHeader() {
|
|
27
|
+
try {
|
|
28
|
+
const { getAuthHeader } = await import("./gotrue-session-2IT7MDGW.js");
|
|
29
|
+
return await getAuthHeader();
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function loadConfigFile() {
|
|
35
|
+
try {
|
|
36
|
+
const configPath = path.join(EXE_DIR, "config.json");
|
|
37
|
+
return JSON.parse(readFileSync(configPath, "utf-8"));
|
|
38
|
+
} catch {
|
|
39
|
+
return void 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function loadLicenseKey() {
|
|
43
|
+
const config = loadConfigFile();
|
|
44
|
+
const cloud = config?.cloud;
|
|
45
|
+
const license = config?.license;
|
|
46
|
+
return cloud?.apiKey || license?.key || readTrimmed(path.join(EXE_DIR, "license.key"));
|
|
47
|
+
}
|
|
48
|
+
function loadLicenseToken() {
|
|
49
|
+
try {
|
|
50
|
+
const raw = JSON.parse(readFileSync(path.join(EXE_DIR, "license-cache.json"), "utf-8"));
|
|
51
|
+
return typeof raw.token === "string" && raw.token.trim() ? raw.token.trim() : void 0;
|
|
52
|
+
} catch {
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function loadSupportAdminToken() {
|
|
57
|
+
const config = loadConfigFile();
|
|
58
|
+
const support = config?.support;
|
|
59
|
+
return process.env.ASKEXE_SUPPORT_ADMIN_TOKEN || process.env.EXE_SUPPORT_ADMIN_TOKEN || support?.adminToken || support?.admin_token;
|
|
60
|
+
}
|
|
61
|
+
function normalizeBaseEndpoint(value, fallback) {
|
|
62
|
+
let raw = value || fallback;
|
|
63
|
+
try {
|
|
64
|
+
const parsed = new URL(raw);
|
|
65
|
+
if (parsed.hostname === "askexe.com" || parsed.hostname === "cloud.askexe.com") {
|
|
66
|
+
parsed.hostname = "api.askexe.com";
|
|
67
|
+
raw = parsed.toString();
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
return raw.replace(/\/+$/, "").replace(/\/(bug-reports|feature-requests)\/?$/, "");
|
|
72
|
+
}
|
|
73
|
+
function buildStatusUpdateEndpoint(endpointPath, adminToken) {
|
|
74
|
+
const config = loadConfigFile();
|
|
75
|
+
const support = config?.support;
|
|
76
|
+
const routerUrl = process.env.API_ROUTER_URL?.replace(/\/+$/, "");
|
|
77
|
+
if (adminToken) {
|
|
78
|
+
const adminBase = normalizeBaseEndpoint(
|
|
79
|
+
process.env.ASKEXE_SUPPORT_ADMIN_ENDPOINT || support?.adminEndpoint || support?.admin_endpoint || ADMIN_STATUS_BASE_ENDPOINT,
|
|
80
|
+
ADMIN_STATUS_BASE_ENDPOINT
|
|
81
|
+
);
|
|
82
|
+
return `${adminBase}/${endpointPath.replace(/^\/+/, "")}`;
|
|
83
|
+
}
|
|
84
|
+
const customerBase = normalizeBaseEndpoint(
|
|
85
|
+
support?.bugReportEndpoint || process.env.EXE_BUG_REPORT_ENDPOINT || (routerUrl ? `${routerUrl}/v1/support` : CUSTOMER_STATUS_BASE_ENDPOINT),
|
|
86
|
+
CUSTOMER_STATUS_BASE_ENDPOINT
|
|
87
|
+
);
|
|
88
|
+
return `${customerBase}/${endpointPath.replace(/^\/+/, "")}`;
|
|
89
|
+
}
|
|
90
|
+
function readTrimmed(filePath) {
|
|
91
|
+
try {
|
|
92
|
+
const value = readFileSync(filePath, "utf-8").trim();
|
|
93
|
+
return value || void 0;
|
|
94
|
+
} catch {
|
|
95
|
+
return void 0;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function statusUpdateFileName(kind, id) {
|
|
99
|
+
const safeId = id.replace(/[^a-z0-9-]/gi, "").slice(0, 64) || "unknown";
|
|
100
|
+
return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${kind}-${safeId}.json`;
|
|
101
|
+
}
|
|
102
|
+
function findExistingUnsentStatusUpdate(kind, id) {
|
|
103
|
+
if (!existsSync(STATUS_UPDATE_DIR)) return void 0;
|
|
104
|
+
try {
|
|
105
|
+
for (const file of readdirSync(STATUS_UPDATE_DIR).filter((f) => f.endsWith(".json"))) {
|
|
106
|
+
const filePath = path.join(STATUS_UPDATE_DIR, file);
|
|
107
|
+
const record = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
108
|
+
if (record.kind === kind && record.id === id && !record.sent_at) return filePath;
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
return void 0;
|
|
112
|
+
}
|
|
113
|
+
return void 0;
|
|
114
|
+
}
|
|
115
|
+
function queueSupportStatusUpdateSync(input) {
|
|
116
|
+
const id = input.id.trim();
|
|
117
|
+
if (!id) return void 0;
|
|
118
|
+
try {
|
|
119
|
+
mkdirSync(STATUS_UPDATE_DIR, { recursive: true });
|
|
120
|
+
const existingPath = findExistingUnsentStatusUpdate(input.kind, id);
|
|
121
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
122
|
+
const filePath = existingPath ?? path.join(STATUS_UPDATE_DIR, statusUpdateFileName(input.kind, id));
|
|
123
|
+
let attempts = 0;
|
|
124
|
+
if (existingPath) {
|
|
125
|
+
try {
|
|
126
|
+
const existing = JSON.parse(readFileSync(existingPath, "utf-8"));
|
|
127
|
+
attempts = existing.attempts ?? 0;
|
|
128
|
+
} catch {
|
|
129
|
+
attempts = 0;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const record = {
|
|
133
|
+
id,
|
|
134
|
+
kind: input.kind,
|
|
135
|
+
endpointPath: input.endpointPath,
|
|
136
|
+
method: input.method ?? "PATCH",
|
|
137
|
+
payload: input.payload,
|
|
138
|
+
created_at: existingPath ? JSON.parse(readFileSync(existingPath, "utf-8")).created_at : createdAt,
|
|
139
|
+
attempts,
|
|
140
|
+
last_error: input.lastError
|
|
141
|
+
};
|
|
142
|
+
writeFileSync(filePath, JSON.stringify(record, null, 2), "utf-8");
|
|
143
|
+
return filePath;
|
|
144
|
+
} catch {
|
|
145
|
+
return void 0;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function extractField(content, field) {
|
|
149
|
+
const match = content.match(new RegExp(`^${field}:\\s*(.+)$`, "m"));
|
|
150
|
+
return match?.[1]?.trim();
|
|
151
|
+
}
|
|
152
|
+
function extractSection(content, heading) {
|
|
153
|
+
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
154
|
+
const match = content.match(new RegExp(`(?:^|\\n)##\\s+${escaped}\\s*\\n([\\s\\S]*?)(?=\\n##\\s+|\\s*$)`));
|
|
155
|
+
return match?.[1]?.trim() || void 0;
|
|
156
|
+
}
|
|
157
|
+
function firstMeaningfulParagraph(content) {
|
|
158
|
+
const stripped = content.replace(/^#\s+.*$/gm, "").replace(/^[a-z_]+:\s+.*$/gim, "").trim();
|
|
159
|
+
return stripped.split(/\n{2,}/).map((part) => part.trim()).find(Boolean) ?? content.slice(0, 2e3);
|
|
160
|
+
}
|
|
161
|
+
function normalizeSeverity(value, fallback = "p2") {
|
|
162
|
+
const raw = value?.trim().toLowerCase();
|
|
163
|
+
if (!raw) return fallback;
|
|
164
|
+
if (["p0", "p1", "p2", "p3"].includes(raw)) return raw;
|
|
165
|
+
if (["critical", "blocker", "urgent"].includes(raw)) return "p0";
|
|
166
|
+
if (["high", "major"].includes(raw)) return "p1";
|
|
167
|
+
if (["medium", "normal"].includes(raw)) return "p2";
|
|
168
|
+
if (["low", "minor", "trivial"].includes(raw)) return "p3";
|
|
169
|
+
return fallback;
|
|
170
|
+
}
|
|
171
|
+
function normalizeClassification(value) {
|
|
172
|
+
const raw = value?.trim().toLowerCase();
|
|
173
|
+
if (["upstream_bug", "customer_customization", "emergency_hotfix", "unclear"].includes(raw ?? "")) return raw;
|
|
174
|
+
if (raw?.includes("custom")) return "customer_customization";
|
|
175
|
+
if (raw?.includes("hotfix")) return "emergency_hotfix";
|
|
176
|
+
if (raw?.includes("bug") || raw?.includes("defect")) return "upstream_bug";
|
|
177
|
+
return "unclear";
|
|
178
|
+
}
|
|
179
|
+
function normalizeFeatureCategory(value) {
|
|
180
|
+
const raw = value?.trim().toLowerCase();
|
|
181
|
+
if (["upstream_feature", "local_customization", "integration", "unclear"].includes(raw ?? "")) return raw;
|
|
182
|
+
if (raw?.includes("custom")) return "local_customization";
|
|
183
|
+
if (raw?.includes("integrat")) return "integration";
|
|
184
|
+
if (raw?.includes("feature") || raw?.includes("enhancement") || raw?.includes("platform")) return "upstream_feature";
|
|
185
|
+
return "unclear";
|
|
186
|
+
}
|
|
187
|
+
function linesFromSection(value) {
|
|
188
|
+
if (!value) return void 0;
|
|
189
|
+
const lines = value.split(/\r?\n/).map((line) => line.replace(/^[-*]\s+/, "").replace(/^\d+\.\s+/, "").trim()).filter(Boolean);
|
|
190
|
+
return lines.length ? lines : void 0;
|
|
191
|
+
}
|
|
192
|
+
function shouldBackoff() {
|
|
193
|
+
if (consecutiveFailures === 0) return false;
|
|
194
|
+
const backoffMs = Math.min(Math.pow(2, consecutiveFailures), MAX_BACKOFF_S) * 1e3;
|
|
195
|
+
return Date.now() - lastFailureTime < backoffMs;
|
|
196
|
+
}
|
|
197
|
+
function onNetworkSuccess() {
|
|
198
|
+
consecutiveFailures = 0;
|
|
199
|
+
}
|
|
200
|
+
function onNetworkFailure() {
|
|
201
|
+
consecutiveFailures++;
|
|
202
|
+
lastFailureTime = Date.now();
|
|
203
|
+
}
|
|
204
|
+
function isTtlExpired(content) {
|
|
205
|
+
const createdAt = extractField(content, "created_at");
|
|
206
|
+
if (!createdAt) return false;
|
|
207
|
+
try {
|
|
208
|
+
const created = new Date(createdAt).getTime();
|
|
209
|
+
if (isNaN(created)) return false;
|
|
210
|
+
return Date.now() - created > TTL_MS;
|
|
211
|
+
} catch {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async function flushDir(dir, endpoint, kind, licenseKey, licenseToken, adminToken, maxPerFlush, gotrueAuth) {
|
|
216
|
+
let sent = 0;
|
|
217
|
+
let errors = 0;
|
|
218
|
+
let skipped = 0;
|
|
219
|
+
if (!existsSync(dir)) return { sent, errors, skipped };
|
|
220
|
+
let files;
|
|
221
|
+
try {
|
|
222
|
+
files = readdirSync(dir).filter((f) => f.endsWith(".md")).sort().reverse();
|
|
223
|
+
} catch {
|
|
224
|
+
return { sent, errors, skipped };
|
|
225
|
+
}
|
|
226
|
+
if (files.length === 0) return { sent, errors, skipped };
|
|
227
|
+
for (const file of files) {
|
|
228
|
+
if (sent >= maxPerFlush) break;
|
|
229
|
+
if (shouldBackoff()) break;
|
|
230
|
+
const filePath = path.join(dir, file);
|
|
231
|
+
let content;
|
|
232
|
+
try {
|
|
233
|
+
content = readFileSync(filePath, "utf-8");
|
|
234
|
+
} catch {
|
|
235
|
+
skipped++;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (content.includes(SUPPORT_REPORT_SENT_MARKER)) {
|
|
239
|
+
skipped++;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (content.includes(SKIPPED_MARKER)) {
|
|
243
|
+
skipped++;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const id = extractField(content, "id") ?? file.replace(".md", "");
|
|
247
|
+
if (sentIds.has(id)) {
|
|
248
|
+
skipped++;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (isTtlExpired(content)) {
|
|
252
|
+
try {
|
|
253
|
+
writeFileSync(filePath, content + `
|
|
254
|
+
${SKIPPED_MARKER}
|
|
255
|
+
`);
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
skipped++;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
let payload;
|
|
262
|
+
if (kind === "bug") {
|
|
263
|
+
const titleMatch = content.match(/^#\s*(.+)$/m);
|
|
264
|
+
const severityMatch = content.match(/^severity:\s*(.+)$/m);
|
|
265
|
+
payload = {
|
|
266
|
+
id,
|
|
267
|
+
title: titleMatch?.[1]?.trim() ?? file,
|
|
268
|
+
classification: normalizeClassification(extractField(content, "classification")),
|
|
269
|
+
severity: normalizeSeverity(severityMatch?.[1]?.trim()),
|
|
270
|
+
summary: extractSection(content, "Summary") ?? extractSection(content, "Description") ?? firstMeaningfulParagraph(content),
|
|
271
|
+
customer_impact: extractSection(content, "Customer impact"),
|
|
272
|
+
reproduction_steps: linesFromSection(extractSection(content, "Reproduction steps")),
|
|
273
|
+
expected: extractSection(content, "Expected behavior") ?? extractField(content, "expected"),
|
|
274
|
+
actual: extractSection(content, "Actual behavior") ?? extractField(content, "actual"),
|
|
275
|
+
workaround: extractSection(content, "Workaround"),
|
|
276
|
+
markdown: content,
|
|
277
|
+
source: "outbox-flusher",
|
|
278
|
+
package_version: extractField(content, "package_version") ?? process.env.npm_package_version ?? "unknown",
|
|
279
|
+
project_name: extractField(content, "project_name") ?? extractField(content, "project"),
|
|
280
|
+
agent_id: extractField(content, "filed_by"),
|
|
281
|
+
agent_role: "outbox-flusher",
|
|
282
|
+
report_path: filePath
|
|
283
|
+
};
|
|
284
|
+
} else {
|
|
285
|
+
const titleMatch = content.match(/^#\s*Feature Request\s*[—–-]\s*(.+)$/m);
|
|
286
|
+
payload = {
|
|
287
|
+
id,
|
|
288
|
+
title: titleMatch?.[1]?.trim() ?? extractField(content, "title") ?? file,
|
|
289
|
+
category: normalizeFeatureCategory(extractField(content, "category")),
|
|
290
|
+
priority: normalizeSeverity(extractField(content, "priority")),
|
|
291
|
+
product: extractField(content, "product") ?? "exe-os",
|
|
292
|
+
description: extractSection(content, "Description") ?? firstMeaningfulParagraph(content),
|
|
293
|
+
use_case: extractSection(content, "Use Case"),
|
|
294
|
+
current_workaround: extractSection(content, "Current Workaround"),
|
|
295
|
+
proposed_solution: extractSection(content, "Proposed Solution"),
|
|
296
|
+
business_impact: extractSection(content, "Business Impact"),
|
|
297
|
+
source: "outbox-flusher",
|
|
298
|
+
package_version: extractField(content, "package_version") ?? process.env.npm_package_version ?? "unknown",
|
|
299
|
+
project_name: extractField(content, "project_name") ?? extractField(content, "project"),
|
|
300
|
+
agent_id: extractField(content, "filed_by"),
|
|
301
|
+
agent_role: "outbox-flusher"
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
const resp = await fetch(endpoint, {
|
|
306
|
+
method: "POST",
|
|
307
|
+
headers: {
|
|
308
|
+
"content-type": "application/json",
|
|
309
|
+
// GoTrue JWT is preferred auth; license key/token are legacy fallback
|
|
310
|
+
...gotrueAuth && !adminToken ? gotrueAuth : {},
|
|
311
|
+
...!gotrueAuth && licenseKey ? { "x-exe-license-key": licenseKey } : {},
|
|
312
|
+
...!gotrueAuth && licenseToken ? { "x-exe-license-token": licenseToken } : {},
|
|
313
|
+
...adminToken ? { "x-askexe-admin-token": adminToken } : {}
|
|
314
|
+
},
|
|
315
|
+
body: JSON.stringify(payload),
|
|
316
|
+
signal: AbortSignal.timeout(5e3)
|
|
317
|
+
});
|
|
318
|
+
if (resp.ok || resp.status === 409) {
|
|
319
|
+
markSupportReportSentSync(filePath);
|
|
320
|
+
sentIds.add(id);
|
|
321
|
+
onNetworkSuccess();
|
|
322
|
+
sent++;
|
|
323
|
+
} else {
|
|
324
|
+
onNetworkFailure();
|
|
325
|
+
errors++;
|
|
326
|
+
}
|
|
327
|
+
} catch {
|
|
328
|
+
onNetworkFailure();
|
|
329
|
+
errors++;
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return { sent, errors, skipped };
|
|
334
|
+
}
|
|
335
|
+
async function sendStatusUpdateRecord(record, filePath, licenseKey, licenseToken, adminToken, gotrueAuth) {
|
|
336
|
+
const endpoint = buildStatusUpdateEndpoint(record.endpointPath, adminToken);
|
|
337
|
+
const resp = await fetch(endpoint, {
|
|
338
|
+
method: record.method ?? "PATCH",
|
|
339
|
+
headers: {
|
|
340
|
+
"content-type": "application/json",
|
|
341
|
+
...adminToken ? { authorization: `Bearer ${adminToken}` } : {},
|
|
342
|
+
// GoTrue JWT preferred over license key for non-admin requests
|
|
343
|
+
...!adminToken && gotrueAuth ? gotrueAuth : {},
|
|
344
|
+
...!adminToken && !gotrueAuth && licenseKey ? { "x-exe-license-key": licenseKey } : {},
|
|
345
|
+
...!adminToken && !gotrueAuth && licenseToken ? { "x-exe-license-token": licenseToken } : {}
|
|
346
|
+
},
|
|
347
|
+
body: JSON.stringify(record.payload),
|
|
348
|
+
signal: AbortSignal.timeout(5e3)
|
|
349
|
+
});
|
|
350
|
+
if (resp.ok) {
|
|
351
|
+
writeFileSync(filePath, JSON.stringify({ ...record, sent_at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), "utf-8");
|
|
352
|
+
onNetworkSuccess();
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
writeFileSync(filePath, JSON.stringify({
|
|
356
|
+
...record,
|
|
357
|
+
attempts: (record.attempts ?? 0) + 1,
|
|
358
|
+
last_error: `HTTP ${resp.status}`,
|
|
359
|
+
last_attempt_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
360
|
+
}, null, 2), "utf-8");
|
|
361
|
+
onNetworkFailure();
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
async function flushStatusUpdates(licenseKey, licenseToken, adminToken, maxPerFlush, gotrueAuth) {
|
|
365
|
+
let sent = 0;
|
|
366
|
+
let errors = 0;
|
|
367
|
+
let skipped = 0;
|
|
368
|
+
if (!existsSync(STATUS_UPDATE_DIR)) return { sent, errors, skipped };
|
|
369
|
+
let files;
|
|
370
|
+
try {
|
|
371
|
+
files = readdirSync(STATUS_UPDATE_DIR).filter((f) => f.endsWith(".json")).sort();
|
|
372
|
+
} catch {
|
|
373
|
+
return { sent, errors, skipped };
|
|
374
|
+
}
|
|
375
|
+
for (const file of files) {
|
|
376
|
+
if (sent >= maxPerFlush) break;
|
|
377
|
+
if (shouldBackoff()) break;
|
|
378
|
+
const filePath = path.join(STATUS_UPDATE_DIR, file);
|
|
379
|
+
let record;
|
|
380
|
+
try {
|
|
381
|
+
record = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
382
|
+
} catch {
|
|
383
|
+
skipped++;
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (record.sent_at) {
|
|
387
|
+
skipped++;
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
if (Date.now() - new Date(record.created_at).getTime() > TTL_MS) {
|
|
392
|
+
writeFileSync(filePath, JSON.stringify({
|
|
393
|
+
...record,
|
|
394
|
+
skipped_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
395
|
+
last_error: "ttl_expired"
|
|
396
|
+
}, null, 2), "utf-8");
|
|
397
|
+
skipped++;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
} catch {
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
const ok = await sendStatusUpdateRecord(record, filePath, licenseKey, licenseToken, adminToken, gotrueAuth);
|
|
404
|
+
if (ok) sent++;
|
|
405
|
+
else errors++;
|
|
406
|
+
} catch (err) {
|
|
407
|
+
try {
|
|
408
|
+
writeFileSync(filePath, JSON.stringify({
|
|
409
|
+
...record,
|
|
410
|
+
attempts: (record.attempts ?? 0) + 1,
|
|
411
|
+
last_error: err instanceof Error ? err.message : String(err),
|
|
412
|
+
last_attempt_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
413
|
+
}, null, 2), "utf-8");
|
|
414
|
+
} catch {
|
|
415
|
+
}
|
|
416
|
+
onNetworkFailure();
|
|
417
|
+
errors++;
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return { sent, errors, skipped };
|
|
422
|
+
}
|
|
423
|
+
async function flushSupportOutbox(opts) {
|
|
424
|
+
const maxPerFlush = opts?.maxPerFlush ?? DEFAULT_MAX_PER_FLUSH;
|
|
425
|
+
const gotrueAuth = await loadGoTrueAuthHeader();
|
|
426
|
+
const licenseKey = loadLicenseKey();
|
|
427
|
+
const licenseToken = loadLicenseToken();
|
|
428
|
+
const adminToken = loadSupportAdminToken();
|
|
429
|
+
const [bugs, features, statusUpdates] = await Promise.all([
|
|
430
|
+
flushDir(BUG_DIR, BUG_ENDPOINT, "bug", licenseKey, licenseToken, adminToken, maxPerFlush, gotrueAuth),
|
|
431
|
+
flushDir(FEATURE_DIR, FEATURE_ENDPOINT, "feature", licenseKey, licenseToken, adminToken, maxPerFlush, gotrueAuth),
|
|
432
|
+
flushStatusUpdates(licenseKey, licenseToken, adminToken, maxPerFlush, gotrueAuth)
|
|
433
|
+
]);
|
|
434
|
+
return {
|
|
435
|
+
bugsSent: bugs.sent,
|
|
436
|
+
featuresSent: features.sent,
|
|
437
|
+
statusUpdatesSent: statusUpdates.sent,
|
|
438
|
+
errors: bugs.errors + features.errors + statusUpdates.errors,
|
|
439
|
+
skipped: bugs.skipped + features.skipped + statusUpdates.skipped
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
async function getOutboxStatus() {
|
|
443
|
+
return {
|
|
444
|
+
bugReports: scanDir(BUG_DIR),
|
|
445
|
+
featureRequests: scanDir(FEATURE_DIR),
|
|
446
|
+
statusUpdates: scanStatusDir(STATUS_UPDATE_DIR)
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function scanDir(dir) {
|
|
450
|
+
if (!existsSync(dir)) return { total: 0, unsent: 0 };
|
|
451
|
+
let files;
|
|
452
|
+
try {
|
|
453
|
+
files = readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
454
|
+
} catch {
|
|
455
|
+
return { total: 0, unsent: 0 };
|
|
456
|
+
}
|
|
457
|
+
let unsent = 0;
|
|
458
|
+
let oldest;
|
|
459
|
+
for (const file of files) {
|
|
460
|
+
try {
|
|
461
|
+
const content = readFileSync(path.join(dir, file), "utf-8");
|
|
462
|
+
if (!content.includes(SUPPORT_REPORT_SENT_MARKER) && !content.includes(SKIPPED_MARKER)) {
|
|
463
|
+
unsent++;
|
|
464
|
+
const createdAt = extractField(content, "created_at");
|
|
465
|
+
if (createdAt && (!oldest || createdAt < oldest)) {
|
|
466
|
+
oldest = createdAt;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
} catch {
|
|
470
|
+
unsent++;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return { total: files.length, unsent, oldest };
|
|
474
|
+
}
|
|
475
|
+
function scanStatusDir(dir) {
|
|
476
|
+
if (!existsSync(dir)) return { total: 0, unsent: 0 };
|
|
477
|
+
let files;
|
|
478
|
+
try {
|
|
479
|
+
files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
480
|
+
} catch {
|
|
481
|
+
return { total: 0, unsent: 0 };
|
|
482
|
+
}
|
|
483
|
+
let unsent = 0;
|
|
484
|
+
let oldest;
|
|
485
|
+
for (const file of files) {
|
|
486
|
+
try {
|
|
487
|
+
const record = JSON.parse(readFileSync(path.join(dir, file), "utf-8"));
|
|
488
|
+
if (!record.sent_at) {
|
|
489
|
+
unsent++;
|
|
490
|
+
if (record.created_at && (!oldest || record.created_at < oldest)) oldest = record.created_at;
|
|
491
|
+
}
|
|
492
|
+
} catch {
|
|
493
|
+
unsent++;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return { total: files.length, unsent, oldest };
|
|
497
|
+
}
|
|
498
|
+
export {
|
|
499
|
+
flushSupportOutbox as flushBugReportOutbox,
|
|
500
|
+
flushSupportOutbox,
|
|
501
|
+
getOutboxStatus,
|
|
502
|
+
queueSupportStatusUpdateSync
|
|
503
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askexenow/exe-os",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.287",
|
|
4
4
|
"description": "AI employee operating system — persistent memory, task management, and multi-agent coordination for Claude Code.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|