@askexenow/exe-os 0.9.263 → 0.9.265
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/stack-manifests/v0.9.json +1 -1
- package/dist/backfill-metadata-WM46YQZL.js +597 -0
- package/dist/bin/agentic-ontology-backfill.js +1 -1
- package/dist/bin/agentic-reflection-backfill.js +1 -1
- package/dist/bin/agentic-semantic-label.js +1 -1
- package/dist/bin/backfill-conversations.js +1 -1
- package/dist/bin/backfill-responses.js +1 -1
- package/dist/bin/backfill-vectors.js +2 -2
- package/dist/bin/bulk-sync-postgres.js +1 -1
- package/dist/bin/cleanup-stale-review-tasks.js +2 -2
- package/dist/bin/cli.js +5 -5
- package/dist/bin/exe-assign.js +1 -1
- package/dist/bin/exe-boot.js +3 -3
- package/dist/bin/exe-dispatch.js +2 -2
- package/dist/bin/exe-doctor.js +1 -1
- package/dist/bin/exe-export-behaviors.js +2 -2
- package/dist/bin/exe-forget.js +3 -3
- package/dist/bin/exe-gateway.js +5 -5
- package/dist/bin/exe-heartbeat.js +3 -3
- package/dist/bin/exe-kill.js +3 -3
- package/dist/bin/exe-launch-agent.js +3 -3
- package/dist/bin/exe-pending-messages.js +3 -3
- package/dist/bin/exe-pending-notifications.js +2 -2
- package/dist/bin/exe-pending-reviews.js +6 -4
- package/dist/bin/exe-review.js +3 -3
- package/dist/bin/exe-search.js +2 -2
- package/dist/bin/exe-session-cleanup.js +5 -5
- package/dist/bin/exe-start-codex.js +1 -1
- package/dist/bin/exe-start-opencode.js +1 -1
- package/dist/bin/exe-status.js +3 -3
- package/dist/bin/exe-support.js +1 -1
- package/dist/bin/exe-team.js +1 -1
- package/dist/bin/git-sweep.js +2 -2
- package/dist/bin/graph-backfill.js +1 -1
- package/dist/bin/graph-export.js +2 -2
- package/dist/bin/import-history.js +2 -2
- package/dist/bin/intercom-check.js +5 -4
- package/dist/bin/mcp-sessions.js +2 -2
- package/dist/bin/orchestration-metrics.js +1 -1
- package/dist/bin/scan-tasks.js +2 -2
- package/dist/bin/shard-migrate.js +1 -1
- package/dist/capacity-monitor-FZORNXTA.js +49 -0
- package/dist/catchup-brief-PRKHIRWW.js +151 -0
- package/dist/chunk-2GU3NYMB.js +813 -0
- package/dist/chunk-2VT7Z2E2.js +197 -0
- package/dist/chunk-3L6XLN4V.js +1090 -0
- package/dist/chunk-3T4PNG5O.js +447 -0
- package/dist/chunk-3W324KN7.js +13696 -0
- package/dist/{chunk-BJYXHSFN.js → chunk-3X555IGG.js} +1 -1
- package/dist/chunk-4IATAIAF.js +89 -0
- package/dist/chunk-5M4F2FVD.js +204 -0
- package/dist/chunk-5TANMPI4.js +377 -0
- package/dist/chunk-67E5WIMW.js +333 -0
- package/dist/chunk-6NRJIARA.js +346 -0
- package/dist/chunk-6O22GTSE.js +1148 -0
- package/dist/chunk-7O5CMDP6.js +1345 -0
- package/dist/chunk-7P2JKEO3.js +382 -0
- package/dist/chunk-A4UY44T7.js +486 -0
- package/dist/chunk-AZHPQGSI.js +159 -0
- package/dist/chunk-BQAC3GCO.js +127 -0
- package/dist/chunk-CIWJRYIC.js +244 -0
- package/dist/chunk-CZO2DGGF.js +214 -0
- package/dist/chunk-D55SXO3N.js +3951 -0
- package/dist/chunk-EPDSRI6O.js +284 -0
- package/dist/chunk-EU34R2A3.js +1073 -0
- package/dist/chunk-G4KEDLSM.js +488 -0
- package/dist/{chunk-235ZCOYB.js → chunk-IBGC6JFX.js} +2 -2
- package/dist/chunk-IODP4JNE.js +551 -0
- package/dist/chunk-IZ6LCET7.js +58 -0
- package/dist/chunk-LPG3U5UW.js +731 -0
- package/dist/chunk-NH6TPXZV.js +13696 -0
- package/dist/chunk-O5TZI7OZ.js +50 -0
- package/dist/chunk-Q3N4KHLM.js +330 -0
- package/dist/chunk-QHQTMWYH.js +54 -0
- package/dist/chunk-QNYOPM2L.js +1921 -0
- package/dist/chunk-SPOA7EOD.js +81 -0
- package/dist/chunk-TK23WXKB.js +128 -0
- package/dist/chunk-TOVXER6J.js +76 -0
- package/dist/chunk-UIPAZYP7.js +171 -0
- package/dist/chunk-WXW4GF6M.js +495 -0
- package/dist/{chunk-KQNVIDBP.js → chunk-XLYBSXWS.js} +4 -3
- package/dist/core-memory-QXMQ5I7S.js +110 -0
- package/dist/crm-webhook-CH5W633Y.js +10 -0
- package/dist/cto-delegation-gate-XY3NMGTE.js +206 -0
- package/dist/daemon-orchestration-LS62JMTI.js +135 -0
- package/dist/dreaming-ZBKE2GFX.js +32 -0
- package/dist/exe-export-JNSQRIWI.js +73 -0
- package/dist/exe-import-AVGWQZLU.js +76 -0
- package/dist/exe-key-WR6QEHYO.js +579 -0
- package/dist/exe-snapshot-U6K3J6BD.js +164 -0
- package/dist/fast-db-init-ZHRRYI7M.js +7 -0
- package/dist/gateway/index.js +6 -6
- package/dist/git-task-sweep-64KSWRUI.js +40 -0
- package/dist/hooks/bug-report-worker.js +4 -4
- package/dist/hooks/codex-stop-task-finalizer.js +4 -4
- package/dist/hooks/commit-complete.js +4 -4
- package/dist/hooks/error-recall.js +2 -2
- package/dist/hooks/ingest.js +2 -2
- package/dist/hooks/instructions-loaded.js +1 -1
- package/dist/hooks/manifest.json +18 -18
- package/dist/hooks/notification.js +1 -1
- package/dist/hooks/post-compact.js +2 -2
- package/dist/hooks/post-tool-combined.js +2 -2
- package/dist/hooks/pre-compact.js +3 -3
- package/dist/hooks/pre-tool-use.js +6 -6
- package/dist/hooks/prompt-submit.js +16 -11
- package/dist/hooks/session-end.js +5 -5
- package/dist/hooks/session-start.js +6 -6
- package/dist/hooks/stop.js +5 -5
- package/dist/hooks/subagent-stop.js +2 -2
- package/dist/hooks/summary-worker.js +5 -5
- package/dist/index.js +9 -9
- package/dist/lib/consolidation.js +2 -2
- package/dist/lib/exe-daemon.js +17 -17
- package/dist/lib/hybrid-search.js +2 -2
- package/dist/lib/messaging.js +2 -2
- package/dist/lib/schedules.js +2 -2
- package/dist/lib/store.js +1 -1
- package/dist/lib/tasks.js +3 -3
- package/dist/lib/tmux-routing.js +1 -1
- package/dist/mcp/register-tools.js +25 -25
- package/dist/mcp/server.js +26 -26
- package/dist/mcp/tools/create-task.js +4 -4
- package/dist/mcp/tools/list-tasks.js +4 -4
- package/dist/mcp/tools/send-message.js +3 -3
- package/dist/mcp/tools/update-task.js +4 -4
- package/dist/notifications-45QSHDFA.js +45 -0
- package/dist/orchestrator-7XBMFK7D.js +33 -0
- package/dist/pipeline-router-MQKRNCTR.js +13 -0
- package/dist/reranker-CJW3UYE2.js +19 -0
- package/dist/review-polling-RL75XLAY.js +124 -0
- package/dist/runtime/index.js +3 -3
- package/dist/session-events-ZULAN4XL.js +36 -0
- package/dist/session-scope-V2RSOTDU.js +86 -0
- package/dist/skill-refinement-BSX6Q6IN.js +157 -0
- package/dist/{support-outbox-SZVLHHZG.js → support-outbox-SO73Q5H2.js} +202 -7
- package/dist/task-enforcement-JRTAOYZT.js +333 -0
- package/dist/task-scope-GNCB2GAM.js +35 -0
- package/dist/tasks-crud-MZIOYF3R.js +77 -0
- package/dist/tasks-notify-7KNZ4ULO.js +38 -0
- package/dist/tasks-review-U5VEV4Y7.js +47 -0
- package/dist/telemetry-upload-BIB5TJA4.js +739 -0
- package/dist/tui/App.js +7 -7
- package/dist/tui-data-ZSB5DDEY.js +258 -0
- package/dist/worker-gate-TXLX33PX.js +21 -0
- package/dist/workflow-engine-3PIT3Y56.js +28 -0
- package/package.json +1 -1
- package/release-notes.json +27 -24
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getClient
|
|
3
|
+
} from "./chunk-QQZMP6QL.js";
|
|
4
|
+
import "./chunk-2I23RPSI.js";
|
|
5
|
+
import "./chunk-ACBEUQR2.js";
|
|
6
|
+
import "./chunk-FXU7JOXK.js";
|
|
7
|
+
import "./chunk-VXIMSRTO.js";
|
|
8
|
+
import "./chunk-LYH5HE24.js";
|
|
9
|
+
import "./chunk-MLKGABMK.js";
|
|
10
|
+
|
|
11
|
+
// src/lib/skill-refinement.ts
|
|
12
|
+
import crypto from "crypto";
|
|
13
|
+
async function incrementSkillUsage(behaviors) {
|
|
14
|
+
const skills = behaviors.filter((b) => b.domain === "skill");
|
|
15
|
+
if (skills.length === 0) return;
|
|
16
|
+
try {
|
|
17
|
+
const client = getClient();
|
|
18
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19
|
+
for (const skill of skills) {
|
|
20
|
+
await client.execute({
|
|
21
|
+
sql: `UPDATE behaviors SET use_count = use_count + 1, last_used_at = ?, updated_at = ? WHERE id = ?`,
|
|
22
|
+
args: [now, now, skill.id]
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
} catch (err) {
|
|
26
|
+
process.stderr.write(
|
|
27
|
+
`[skill-refinement] Failed to increment usage: ${err instanceof Error ? err.message : String(err)}
|
|
28
|
+
`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function incrementSkillSuccess(assignedTo, projectName) {
|
|
33
|
+
try {
|
|
34
|
+
const client = getClient();
|
|
35
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
36
|
+
const cutoff = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
|
|
37
|
+
const rows = await client.execute({
|
|
38
|
+
sql: `SELECT id FROM behaviors
|
|
39
|
+
WHERE agent_id = ? AND domain = 'skill' AND active = 1
|
|
40
|
+
AND use_count > 0 AND last_used_at > ?
|
|
41
|
+
${projectName ? "AND (project_name = ? OR project_name IS NULL)" : ""}`,
|
|
42
|
+
args: projectName ? [assignedTo, cutoff, projectName] : [assignedTo, cutoff]
|
|
43
|
+
});
|
|
44
|
+
for (const row of rows.rows) {
|
|
45
|
+
const id = String(row.id);
|
|
46
|
+
await client.execute({
|
|
47
|
+
sql: `UPDATE behaviors SET success_count = success_count + 1, updated_at = ? WHERE id = ?`,
|
|
48
|
+
args: [now, id]
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (rows.rows.length > 0) {
|
|
52
|
+
process.stderr.write(
|
|
53
|
+
`[skill-refinement] Incremented success_count for ${rows.rows.length} skill(s) (agent: ${assignedTo})
|
|
54
|
+
`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
process.stderr.write(
|
|
59
|
+
`[skill-refinement] Failed to increment success: ${err instanceof Error ? err.message : String(err)}
|
|
60
|
+
`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
var MIN_USES_FOR_REVIEW = 3;
|
|
65
|
+
var LOW_SUCCESS_RATE = 0.8;
|
|
66
|
+
var HIGH_SUCCESS_RATE = 0.9;
|
|
67
|
+
var MIN_USES_FOR_PROMOTION = 10;
|
|
68
|
+
async function runSkillRefinement() {
|
|
69
|
+
const client = getClient();
|
|
70
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
71
|
+
const result = {
|
|
72
|
+
reviewed: 0,
|
|
73
|
+
flaggedLowSuccess: 0,
|
|
74
|
+
boostedConfidence: 0,
|
|
75
|
+
promotedToIdentity: 0
|
|
76
|
+
};
|
|
77
|
+
const rows = await client.execute({
|
|
78
|
+
sql: `SELECT id, agent_id, content, use_count, success_count, last_refined_at, project_name
|
|
79
|
+
FROM behaviors
|
|
80
|
+
WHERE domain = 'skill' AND active = 1 AND use_count >= ?`,
|
|
81
|
+
args: [MIN_USES_FOR_REVIEW]
|
|
82
|
+
});
|
|
83
|
+
for (const rawRow of rows.rows) {
|
|
84
|
+
const row = rawRow;
|
|
85
|
+
result.reviewed++;
|
|
86
|
+
const successRate = row.use_count > 0 ? row.success_count / row.use_count : 0;
|
|
87
|
+
if (successRate < LOW_SUCCESS_RATE) {
|
|
88
|
+
result.flaggedLowSuccess++;
|
|
89
|
+
const { writeMemory } = await import("./lib/store.js");
|
|
90
|
+
await writeMemory({
|
|
91
|
+
id: crypto.randomUUID(),
|
|
92
|
+
agent_id: row.agent_id,
|
|
93
|
+
agent_role: "",
|
|
94
|
+
session_id: "",
|
|
95
|
+
timestamp: now,
|
|
96
|
+
tool_name: "skill-refinement",
|
|
97
|
+
project_name: row.project_name ?? "",
|
|
98
|
+
has_error: false,
|
|
99
|
+
raw_text: `SKILL REVIEW NEEDED: "${row.content.slice(0, 100)}..." has ${Math.round(successRate * 100)}% success rate (${row.success_count}/${row.use_count} uses). Consider revising or deactivating.`,
|
|
100
|
+
vector: null,
|
|
101
|
+
memory_type: "observation"
|
|
102
|
+
});
|
|
103
|
+
await client.execute({
|
|
104
|
+
sql: `UPDATE behaviors SET last_refined_at = ?, updated_at = ? WHERE id = ?`,
|
|
105
|
+
args: [now, now, row.id]
|
|
106
|
+
});
|
|
107
|
+
} else if (row.use_count >= MIN_USES_FOR_PROMOTION && successRate > HIGH_SUCCESS_RATE) {
|
|
108
|
+
result.promotedToIdentity++;
|
|
109
|
+
try {
|
|
110
|
+
const { getIdentity, updateIdentity } = await import("./lib/identity.js");
|
|
111
|
+
const identity = await getIdentity(row.agent_id);
|
|
112
|
+
if (identity) {
|
|
113
|
+
const promotionBlock = `
|
|
114
|
+
|
|
115
|
+
## Promoted Skill (auto-promoted ${now.split("T")[0]})
|
|
116
|
+
${row.content}`;
|
|
117
|
+
await updateIdentity(row.agent_id, identity + promotionBlock, "skill-refinement");
|
|
118
|
+
}
|
|
119
|
+
await client.execute({
|
|
120
|
+
sql: `UPDATE behaviors SET active = 0, last_refined_at = ?, updated_at = ? WHERE id = ?`,
|
|
121
|
+
args: [now, now, row.id]
|
|
122
|
+
});
|
|
123
|
+
const { writeMemory: writePromoMemory } = await import("./lib/store.js");
|
|
124
|
+
await writePromoMemory({
|
|
125
|
+
id: crypto.randomUUID(),
|
|
126
|
+
agent_id: row.agent_id,
|
|
127
|
+
agent_role: "",
|
|
128
|
+
session_id: "",
|
|
129
|
+
timestamp: now,
|
|
130
|
+
tool_name: "skill-refinement",
|
|
131
|
+
project_name: row.project_name ?? "",
|
|
132
|
+
has_error: false,
|
|
133
|
+
raw_text: `SKILL PROMOTED TO IDENTITY: "${row.content.slice(0, 100)}..." \u2014 ${row.success_count}/${row.use_count} uses (${Math.round(successRate * 100)}% success). Deactivated as behavior, appended to identity.`,
|
|
134
|
+
vector: null,
|
|
135
|
+
memory_type: "decision"
|
|
136
|
+
});
|
|
137
|
+
} catch (err) {
|
|
138
|
+
process.stderr.write(
|
|
139
|
+
`[skill-refinement] Failed to promote skill ${row.id}: ${err instanceof Error ? err.message : String(err)}
|
|
140
|
+
`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
} else if (successRate > HIGH_SUCCESS_RATE) {
|
|
144
|
+
result.boostedConfidence++;
|
|
145
|
+
await client.execute({
|
|
146
|
+
sql: `UPDATE behaviors SET priority = 'p0', last_refined_at = ?, updated_at = ? WHERE id = ? AND priority != 'p0'`,
|
|
147
|
+
args: [now, now, row.id]
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
export {
|
|
154
|
+
incrementSkillSuccess,
|
|
155
|
+
incrementSkillUsage,
|
|
156
|
+
runSkillRefinement
|
|
157
|
+
};
|
|
@@ -5,16 +5,19 @@ import {
|
|
|
5
5
|
import "./chunk-MLKGABMK.js";
|
|
6
6
|
|
|
7
7
|
// src/lib/support-outbox.ts
|
|
8
|
-
import { readdirSync, readFileSync, writeFileSync, existsSync } from "fs";
|
|
8
|
+
import { mkdirSync, readdirSync, readFileSync, writeFileSync, existsSync } from "fs";
|
|
9
9
|
import path from "path";
|
|
10
10
|
import os from "os";
|
|
11
11
|
var EXE_DIR = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
|
|
12
12
|
var BUG_DIR = path.join(EXE_DIR, "bug-reports");
|
|
13
13
|
var FEATURE_DIR = path.join(EXE_DIR, "feature-requests");
|
|
14
|
+
var STATUS_UPDATE_DIR = path.join(EXE_DIR, "support-status-updates");
|
|
14
15
|
var SKIPPED_MARKER = "upstream_skipped: ttl_expired";
|
|
15
16
|
var TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
16
17
|
var BUG_ENDPOINT = "https://api.askexe.com/v1/support/bug-reports";
|
|
17
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";
|
|
18
21
|
var DEFAULT_MAX_PER_FLUSH = 3;
|
|
19
22
|
var consecutiveFailures = 0;
|
|
20
23
|
var lastFailureTime = 0;
|
|
@@ -47,6 +50,35 @@ function loadSupportAdminToken() {
|
|
|
47
50
|
const support = config?.support;
|
|
48
51
|
return process.env.ASKEXE_SUPPORT_ADMIN_TOKEN || process.env.EXE_SUPPORT_ADMIN_TOKEN || support?.adminToken || support?.admin_token;
|
|
49
52
|
}
|
|
53
|
+
function normalizeBaseEndpoint(value, fallback) {
|
|
54
|
+
let raw = value || fallback;
|
|
55
|
+
try {
|
|
56
|
+
const parsed = new URL(raw);
|
|
57
|
+
if (parsed.hostname === "askexe.com" || parsed.hostname === "cloud.askexe.com") {
|
|
58
|
+
parsed.hostname = "api.askexe.com";
|
|
59
|
+
raw = parsed.toString();
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
return raw.replace(/\/+$/, "").replace(/\/(bug-reports|feature-requests)\/?$/, "");
|
|
64
|
+
}
|
|
65
|
+
function buildStatusUpdateEndpoint(endpointPath, adminToken) {
|
|
66
|
+
const config = loadConfigFile();
|
|
67
|
+
const support = config?.support;
|
|
68
|
+
const routerUrl = process.env.API_ROUTER_URL?.replace(/\/+$/, "");
|
|
69
|
+
if (adminToken) {
|
|
70
|
+
const adminBase = normalizeBaseEndpoint(
|
|
71
|
+
process.env.ASKEXE_SUPPORT_ADMIN_ENDPOINT || support?.adminEndpoint || support?.admin_endpoint || ADMIN_STATUS_BASE_ENDPOINT,
|
|
72
|
+
ADMIN_STATUS_BASE_ENDPOINT
|
|
73
|
+
);
|
|
74
|
+
return `${adminBase}/${endpointPath.replace(/^\/+/, "")}`;
|
|
75
|
+
}
|
|
76
|
+
const customerBase = normalizeBaseEndpoint(
|
|
77
|
+
support?.bugReportEndpoint || process.env.EXE_BUG_REPORT_ENDPOINT || (routerUrl ? `${routerUrl}/v1/support` : CUSTOMER_STATUS_BASE_ENDPOINT),
|
|
78
|
+
CUSTOMER_STATUS_BASE_ENDPOINT
|
|
79
|
+
);
|
|
80
|
+
return `${customerBase}/${endpointPath.replace(/^\/+/, "")}`;
|
|
81
|
+
}
|
|
50
82
|
function readTrimmed(filePath) {
|
|
51
83
|
try {
|
|
52
84
|
const value = readFileSync(filePath, "utf-8").trim();
|
|
@@ -55,6 +87,56 @@ function readTrimmed(filePath) {
|
|
|
55
87
|
return void 0;
|
|
56
88
|
}
|
|
57
89
|
}
|
|
90
|
+
function statusUpdateFileName(kind, id) {
|
|
91
|
+
const safeId = id.replace(/[^a-z0-9-]/gi, "").slice(0, 64) || "unknown";
|
|
92
|
+
return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${kind}-${safeId}.json`;
|
|
93
|
+
}
|
|
94
|
+
function findExistingUnsentStatusUpdate(kind, id) {
|
|
95
|
+
if (!existsSync(STATUS_UPDATE_DIR)) return void 0;
|
|
96
|
+
try {
|
|
97
|
+
for (const file of readdirSync(STATUS_UPDATE_DIR).filter((f) => f.endsWith(".json"))) {
|
|
98
|
+
const filePath = path.join(STATUS_UPDATE_DIR, file);
|
|
99
|
+
const record = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
100
|
+
if (record.kind === kind && record.id === id && !record.sent_at) return filePath;
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
return void 0;
|
|
104
|
+
}
|
|
105
|
+
return void 0;
|
|
106
|
+
}
|
|
107
|
+
function queueSupportStatusUpdateSync(input) {
|
|
108
|
+
const id = input.id.trim();
|
|
109
|
+
if (!id) return void 0;
|
|
110
|
+
try {
|
|
111
|
+
mkdirSync(STATUS_UPDATE_DIR, { recursive: true });
|
|
112
|
+
const existingPath = findExistingUnsentStatusUpdate(input.kind, id);
|
|
113
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
114
|
+
const filePath = existingPath ?? path.join(STATUS_UPDATE_DIR, statusUpdateFileName(input.kind, id));
|
|
115
|
+
let attempts = 0;
|
|
116
|
+
if (existingPath) {
|
|
117
|
+
try {
|
|
118
|
+
const existing = JSON.parse(readFileSync(existingPath, "utf-8"));
|
|
119
|
+
attempts = existing.attempts ?? 0;
|
|
120
|
+
} catch {
|
|
121
|
+
attempts = 0;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const record = {
|
|
125
|
+
id,
|
|
126
|
+
kind: input.kind,
|
|
127
|
+
endpointPath: input.endpointPath,
|
|
128
|
+
method: input.method ?? "PATCH",
|
|
129
|
+
payload: input.payload,
|
|
130
|
+
created_at: existingPath ? JSON.parse(readFileSync(existingPath, "utf-8")).created_at : createdAt,
|
|
131
|
+
attempts,
|
|
132
|
+
last_error: input.lastError
|
|
133
|
+
};
|
|
134
|
+
writeFileSync(filePath, JSON.stringify(record, null, 2), "utf-8");
|
|
135
|
+
return filePath;
|
|
136
|
+
} catch {
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
58
140
|
function extractField(content, field) {
|
|
59
141
|
const match = content.match(new RegExp(`^${field}:\\s*(.+)$`, "m"));
|
|
60
142
|
return match?.[1]?.trim();
|
|
@@ -240,26 +322,115 @@ ${SKIPPED_MARKER}
|
|
|
240
322
|
}
|
|
241
323
|
return { sent, errors, skipped };
|
|
242
324
|
}
|
|
325
|
+
async function sendStatusUpdateRecord(record, filePath, licenseKey, licenseToken, adminToken) {
|
|
326
|
+
const endpoint = buildStatusUpdateEndpoint(record.endpointPath, adminToken);
|
|
327
|
+
const resp = await fetch(endpoint, {
|
|
328
|
+
method: record.method ?? "PATCH",
|
|
329
|
+
headers: {
|
|
330
|
+
"content-type": "application/json",
|
|
331
|
+
...adminToken ? { authorization: `Bearer ${adminToken}` } : {},
|
|
332
|
+
...!adminToken && licenseKey ? { "x-exe-license-key": licenseKey } : {},
|
|
333
|
+
...!adminToken && licenseToken ? { "x-exe-license-token": licenseToken } : {}
|
|
334
|
+
},
|
|
335
|
+
body: JSON.stringify(record.payload),
|
|
336
|
+
signal: AbortSignal.timeout(5e3)
|
|
337
|
+
});
|
|
338
|
+
if (resp.ok) {
|
|
339
|
+
writeFileSync(filePath, JSON.stringify({ ...record, sent_at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), "utf-8");
|
|
340
|
+
onNetworkSuccess();
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
writeFileSync(filePath, JSON.stringify({
|
|
344
|
+
...record,
|
|
345
|
+
attempts: (record.attempts ?? 0) + 1,
|
|
346
|
+
last_error: `HTTP ${resp.status}`,
|
|
347
|
+
last_attempt_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
348
|
+
}, null, 2), "utf-8");
|
|
349
|
+
onNetworkFailure();
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
async function flushStatusUpdates(licenseKey, licenseToken, adminToken, maxPerFlush) {
|
|
353
|
+
let sent = 0;
|
|
354
|
+
let errors = 0;
|
|
355
|
+
let skipped = 0;
|
|
356
|
+
if (!existsSync(STATUS_UPDATE_DIR)) return { sent, errors, skipped };
|
|
357
|
+
let files;
|
|
358
|
+
try {
|
|
359
|
+
files = readdirSync(STATUS_UPDATE_DIR).filter((f) => f.endsWith(".json")).sort();
|
|
360
|
+
} catch {
|
|
361
|
+
return { sent, errors, skipped };
|
|
362
|
+
}
|
|
363
|
+
for (const file of files) {
|
|
364
|
+
if (sent >= maxPerFlush) break;
|
|
365
|
+
if (shouldBackoff()) break;
|
|
366
|
+
const filePath = path.join(STATUS_UPDATE_DIR, file);
|
|
367
|
+
let record;
|
|
368
|
+
try {
|
|
369
|
+
record = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
370
|
+
} catch {
|
|
371
|
+
skipped++;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (record.sent_at) {
|
|
375
|
+
skipped++;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
if (Date.now() - new Date(record.created_at).getTime() > TTL_MS) {
|
|
380
|
+
writeFileSync(filePath, JSON.stringify({
|
|
381
|
+
...record,
|
|
382
|
+
skipped_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
383
|
+
last_error: "ttl_expired"
|
|
384
|
+
}, null, 2), "utf-8");
|
|
385
|
+
skipped++;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
} catch {
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
const ok = await sendStatusUpdateRecord(record, filePath, licenseKey, licenseToken, adminToken);
|
|
392
|
+
if (ok) sent++;
|
|
393
|
+
else errors++;
|
|
394
|
+
} catch (err) {
|
|
395
|
+
try {
|
|
396
|
+
writeFileSync(filePath, JSON.stringify({
|
|
397
|
+
...record,
|
|
398
|
+
attempts: (record.attempts ?? 0) + 1,
|
|
399
|
+
last_error: err instanceof Error ? err.message : String(err),
|
|
400
|
+
last_attempt_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
401
|
+
}, null, 2), "utf-8");
|
|
402
|
+
} catch {
|
|
403
|
+
}
|
|
404
|
+
onNetworkFailure();
|
|
405
|
+
errors++;
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return { sent, errors, skipped };
|
|
410
|
+
}
|
|
243
411
|
async function flushSupportOutbox(opts) {
|
|
244
412
|
const maxPerFlush = opts?.maxPerFlush ?? DEFAULT_MAX_PER_FLUSH;
|
|
245
413
|
const licenseKey = loadLicenseKey();
|
|
246
414
|
const licenseToken = loadLicenseToken();
|
|
247
415
|
const adminToken = loadSupportAdminToken();
|
|
248
|
-
const [bugs, features] = await Promise.all([
|
|
416
|
+
const [bugs, features, statusUpdates] = await Promise.all([
|
|
249
417
|
flushDir(BUG_DIR, BUG_ENDPOINT, "bug", licenseKey, licenseToken, adminToken, maxPerFlush),
|
|
250
|
-
flushDir(FEATURE_DIR, FEATURE_ENDPOINT, "feature", licenseKey, licenseToken, adminToken, maxPerFlush)
|
|
418
|
+
flushDir(FEATURE_DIR, FEATURE_ENDPOINT, "feature", licenseKey, licenseToken, adminToken, maxPerFlush),
|
|
419
|
+
flushStatusUpdates(licenseKey, licenseToken, adminToken, maxPerFlush)
|
|
251
420
|
]);
|
|
252
421
|
return {
|
|
253
422
|
bugsSent: bugs.sent,
|
|
254
423
|
featuresSent: features.sent,
|
|
255
|
-
|
|
256
|
-
|
|
424
|
+
statusUpdatesSent: statusUpdates.sent,
|
|
425
|
+
errors: bugs.errors + features.errors + statusUpdates.errors,
|
|
426
|
+
skipped: bugs.skipped + features.skipped + statusUpdates.skipped
|
|
257
427
|
};
|
|
258
428
|
}
|
|
259
429
|
async function getOutboxStatus() {
|
|
260
430
|
return {
|
|
261
431
|
bugReports: scanDir(BUG_DIR),
|
|
262
|
-
featureRequests: scanDir(FEATURE_DIR)
|
|
432
|
+
featureRequests: scanDir(FEATURE_DIR),
|
|
433
|
+
statusUpdates: scanStatusDir(STATUS_UPDATE_DIR)
|
|
263
434
|
};
|
|
264
435
|
}
|
|
265
436
|
function scanDir(dir) {
|
|
@@ -288,8 +459,32 @@ function scanDir(dir) {
|
|
|
288
459
|
}
|
|
289
460
|
return { total: files.length, unsent, oldest };
|
|
290
461
|
}
|
|
462
|
+
function scanStatusDir(dir) {
|
|
463
|
+
if (!existsSync(dir)) return { total: 0, unsent: 0 };
|
|
464
|
+
let files;
|
|
465
|
+
try {
|
|
466
|
+
files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
467
|
+
} catch {
|
|
468
|
+
return { total: 0, unsent: 0 };
|
|
469
|
+
}
|
|
470
|
+
let unsent = 0;
|
|
471
|
+
let oldest;
|
|
472
|
+
for (const file of files) {
|
|
473
|
+
try {
|
|
474
|
+
const record = JSON.parse(readFileSync(path.join(dir, file), "utf-8"));
|
|
475
|
+
if (!record.sent_at) {
|
|
476
|
+
unsent++;
|
|
477
|
+
if (record.created_at && (!oldest || record.created_at < oldest)) oldest = record.created_at;
|
|
478
|
+
}
|
|
479
|
+
} catch {
|
|
480
|
+
unsent++;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return { total: files.length, unsent, oldest };
|
|
484
|
+
}
|
|
291
485
|
export {
|
|
292
486
|
flushSupportOutbox as flushBugReportOutbox,
|
|
293
487
|
flushSupportOutbox,
|
|
294
|
-
getOutboxStatus
|
|
488
|
+
getOutboxStatus,
|
|
489
|
+
queueSupportStatusUpdateSync
|
|
295
490
|
};
|