@threadbase-sh/streamer 1.37.0 → 1.38.0
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/dist/cli.cjs +1304 -368
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +1005 -128
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +131 -5
- package/dist/index.d.ts +131 -5
- package/dist/index.js +1003 -126
- package/dist/index.js.map +1 -1
- package/dist/launchd-entry.cjs +113 -30
- package/dist/launchd-entry.cjs.map +1 -1
- package/dist/migrations/013_add_push_token_kind.sql +63 -0
- package/dist/pg-migrations/007_create_push_tokens.sql +93 -0
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -4632,11 +4632,11 @@ var require_tools = __commonJS({
|
|
|
4632
4632
|
}
|
|
4633
4633
|
}
|
|
4634
4634
|
}
|
|
4635
|
-
function buildFormatters(level, bindings,
|
|
4635
|
+
function buildFormatters(level, bindings, log12) {
|
|
4636
4636
|
return {
|
|
4637
4637
|
level,
|
|
4638
4638
|
bindings,
|
|
4639
|
-
log:
|
|
4639
|
+
log: log12
|
|
4640
4640
|
};
|
|
4641
4641
|
}
|
|
4642
4642
|
function normalizeDestFileDescriptor(destination) {
|
|
@@ -5018,11 +5018,11 @@ var require_proto = __commonJS({
|
|
|
5018
5018
|
}
|
|
5019
5019
|
} else instance[serializersSym] = serializers;
|
|
5020
5020
|
if (options.hasOwnProperty("formatters")) {
|
|
5021
|
-
const { level, bindings: chindings, log:
|
|
5021
|
+
const { level, bindings: chindings, log: log12 } = options.formatters;
|
|
5022
5022
|
instance[formattersSym] = buildFormatters(
|
|
5023
5023
|
level || formatters.level,
|
|
5024
5024
|
chindings || resetChildingsFormatter,
|
|
5025
|
-
|
|
5025
|
+
log12 || formatters.log
|
|
5026
5026
|
);
|
|
5027
5027
|
} else {
|
|
5028
5028
|
instance[formattersSym] = buildFormatters(
|
|
@@ -5932,7 +5932,7 @@ var require_pino = __commonJS({
|
|
|
5932
5932
|
} = symbols;
|
|
5933
5933
|
var { epochTime, nullTime } = time3;
|
|
5934
5934
|
var { pid } = process;
|
|
5935
|
-
var
|
|
5935
|
+
var hostname5 = os2.hostname();
|
|
5936
5936
|
var defaultErrorSerializer = stdSerializers.err;
|
|
5937
5937
|
var defaultOptions2 = {
|
|
5938
5938
|
level: "info",
|
|
@@ -5942,7 +5942,7 @@ var require_pino = __commonJS({
|
|
|
5942
5942
|
errorKey: "err",
|
|
5943
5943
|
nestedKey: null,
|
|
5944
5944
|
enabled: true,
|
|
5945
|
-
base: { pid, hostname:
|
|
5945
|
+
base: { pid, hostname: hostname5 },
|
|
5946
5946
|
serializers: Object.assign(/* @__PURE__ */ Object.create(null), {
|
|
5947
5947
|
err: defaultErrorSerializer
|
|
5948
5948
|
}),
|
|
@@ -6133,6 +6133,86 @@ var init_logger = __esm({
|
|
|
6133
6133
|
}
|
|
6134
6134
|
});
|
|
6135
6135
|
|
|
6136
|
+
// src/feature-flags.ts
|
|
6137
|
+
function findFeatureFlag(id) {
|
|
6138
|
+
return FEATURE_FLAGS.find((f2) => f2.id === id);
|
|
6139
|
+
}
|
|
6140
|
+
function parseBooleanEnv(raw2) {
|
|
6141
|
+
if (raw2 === void 0) return void 0;
|
|
6142
|
+
const v2 = raw2.trim().toLowerCase();
|
|
6143
|
+
if (v2 === "") return false;
|
|
6144
|
+
return !(v2 === "0" || v2 === "false" || v2 === "no" || v2 === "off");
|
|
6145
|
+
}
|
|
6146
|
+
function validateFeatureFlagValues(raw2) {
|
|
6147
|
+
if (!raw2 || typeof raw2 !== "object" || Array.isArray(raw2)) return {};
|
|
6148
|
+
const out = {};
|
|
6149
|
+
const dropped = [];
|
|
6150
|
+
for (const [id, value] of Object.entries(raw2)) {
|
|
6151
|
+
if (!findFeatureFlag(id) || typeof value !== "boolean") {
|
|
6152
|
+
dropped.push(id);
|
|
6153
|
+
continue;
|
|
6154
|
+
}
|
|
6155
|
+
out[id] = value;
|
|
6156
|
+
}
|
|
6157
|
+
if (dropped.length > 0) {
|
|
6158
|
+
getLogger("feature-flags").warn(
|
|
6159
|
+
`Ignoring unknown or non-boolean feature flags: ${dropped.join(", ")}`,
|
|
6160
|
+
{
|
|
6161
|
+
event: "config.feature_flags_dropped",
|
|
6162
|
+
dropped
|
|
6163
|
+
}
|
|
6164
|
+
);
|
|
6165
|
+
}
|
|
6166
|
+
return out;
|
|
6167
|
+
}
|
|
6168
|
+
function parseFeatureFlagArgs(entries) {
|
|
6169
|
+
const values = {};
|
|
6170
|
+
const errors = [];
|
|
6171
|
+
for (const entry of entries) {
|
|
6172
|
+
const eq = entry.indexOf("=");
|
|
6173
|
+
const id = (eq === -1 ? entry : entry.slice(0, eq)).trim();
|
|
6174
|
+
const rawValue = eq === -1 ? "true" : entry.slice(eq + 1).trim().toLowerCase();
|
|
6175
|
+
if (!findFeatureFlag(id)) {
|
|
6176
|
+
errors.push(
|
|
6177
|
+
`Unknown feature flag "${id}". Known flags: ${FEATURE_FLAGS.map((f2) => f2.id).join(", ")}`
|
|
6178
|
+
);
|
|
6179
|
+
continue;
|
|
6180
|
+
}
|
|
6181
|
+
if (rawValue !== "true" && rawValue !== "false") {
|
|
6182
|
+
errors.push(`Invalid value "${rawValue}" for feature flag "${id}" \u2014 expected true/false`);
|
|
6183
|
+
continue;
|
|
6184
|
+
}
|
|
6185
|
+
values[id] = rawValue === "true";
|
|
6186
|
+
}
|
|
6187
|
+
return { values, errors };
|
|
6188
|
+
}
|
|
6189
|
+
function resolveFeatureFlags(opts) {
|
|
6190
|
+
const env = opts?.env ?? process.env;
|
|
6191
|
+
const out = {};
|
|
6192
|
+
for (const def of FEATURE_FLAGS) {
|
|
6193
|
+
out[def.id] = parseBooleanEnv(env[def.env]) ?? opts?.cli?.[def.id] ?? opts?.yaml?.[def.id] ?? def.default;
|
|
6194
|
+
}
|
|
6195
|
+
return out;
|
|
6196
|
+
}
|
|
6197
|
+
function nonDefaultFeatureFlags(values) {
|
|
6198
|
+
return FEATURE_FLAGS.filter((f2) => values[f2.id] !== f2.default).map((f2) => f2.id);
|
|
6199
|
+
}
|
|
6200
|
+
var FEATURE_FLAGS;
|
|
6201
|
+
var init_feature_flags = __esm({
|
|
6202
|
+
"src/feature-flags.ts"() {
|
|
6203
|
+
"use strict";
|
|
6204
|
+
init_logger();
|
|
6205
|
+
FEATURE_FLAGS = [
|
|
6206
|
+
{
|
|
6207
|
+
id: "codexSystemPrompt",
|
|
6208
|
+
description: "Send the built system prompt to fresh Codex sessions. Off by default: Codex has no --system-prompt flag, so the prompt goes in the positional [PROMPT] argument, which Codex treats as the user's opening turn rather than a system-level instruction.",
|
|
6209
|
+
default: false,
|
|
6210
|
+
env: "THREADBASE_FEATURE_CODEX_SYSTEM_PROMPT"
|
|
6211
|
+
}
|
|
6212
|
+
];
|
|
6213
|
+
}
|
|
6214
|
+
});
|
|
6215
|
+
|
|
6136
6216
|
// src/auth.ts
|
|
6137
6217
|
function configDir() {
|
|
6138
6218
|
return process.env.THREADBASE_CONFIG_DIR ?? (0, import_path.join)((0, import_os.homedir)(), ".threadbase");
|
|
@@ -6297,6 +6377,21 @@ function setClaudeExtraArgs(text) {
|
|
|
6297
6377
|
}
|
|
6298
6378
|
setConfigValue("claude_extra_args", trimmed && trimmed.length > 0 ? trimmed : void 0);
|
|
6299
6379
|
}
|
|
6380
|
+
function loadFeatureFlags() {
|
|
6381
|
+
try {
|
|
6382
|
+
const content = (0, import_fs.readFileSync)(configFile(), "utf-8");
|
|
6383
|
+
const match2 = content.match(/^feature_flags:\s*(.+)$/m);
|
|
6384
|
+
if (!match2?.[1]) return {};
|
|
6385
|
+
return validateFeatureFlagValues(JSON.parse(match2[1].trim()));
|
|
6386
|
+
} catch (err) {
|
|
6387
|
+
if (err.code !== "ENOENT") {
|
|
6388
|
+
getLogger("auth").warn(`Ignoring unreadable feature_flags in server.yaml: ${String(err)}`, {
|
|
6389
|
+
event: "config.feature_flags_parse_failed"
|
|
6390
|
+
});
|
|
6391
|
+
}
|
|
6392
|
+
return {};
|
|
6393
|
+
}
|
|
6394
|
+
}
|
|
6300
6395
|
function validatePublicUrl(raw2) {
|
|
6301
6396
|
let parsed;
|
|
6302
6397
|
try {
|
|
@@ -6354,6 +6449,7 @@ var init_auth = __esm({
|
|
|
6354
6449
|
import_os = require("os");
|
|
6355
6450
|
import_path = require("path");
|
|
6356
6451
|
init_claude_flags();
|
|
6452
|
+
init_feature_flags();
|
|
6357
6453
|
init_logger();
|
|
6358
6454
|
}
|
|
6359
6455
|
});
|
|
@@ -8078,7 +8174,7 @@ var require_merge = __commonJS({
|
|
|
8078
8174
|
var require_addPairToJSMap = __commonJS({
|
|
8079
8175
|
"node_modules/yaml/dist/nodes/addPairToJSMap.js"(exports2) {
|
|
8080
8176
|
"use strict";
|
|
8081
|
-
var
|
|
8177
|
+
var log12 = require_log();
|
|
8082
8178
|
var merge2 = require_merge();
|
|
8083
8179
|
var stringify = require_stringify();
|
|
8084
8180
|
var identity = require_identity();
|
|
@@ -8127,7 +8223,7 @@ var require_addPairToJSMap = __commonJS({
|
|
|
8127
8223
|
let jsonStr = JSON.stringify(strKey);
|
|
8128
8224
|
if (jsonStr.length > 40)
|
|
8129
8225
|
jsonStr = jsonStr.substring(0, 36) + '..."';
|
|
8130
|
-
|
|
8226
|
+
log12.warn(ctx.doc.options.logLevel, `Keys with collection values will be stringified due to JS Object restrictions: ${jsonStr}. Set mapAsMap: true to use object keys.`);
|
|
8131
8227
|
ctx.mapKeyWarned = true;
|
|
8132
8228
|
}
|
|
8133
8229
|
return strKey;
|
|
@@ -13543,7 +13639,7 @@ var require_public_api = __commonJS({
|
|
|
13543
13639
|
var composer = require_composer();
|
|
13544
13640
|
var Document = require_Document();
|
|
13545
13641
|
var errors = require_errors();
|
|
13546
|
-
var
|
|
13642
|
+
var log12 = require_log();
|
|
13547
13643
|
var identity = require_identity();
|
|
13548
13644
|
var lineCounter = require_line_counter();
|
|
13549
13645
|
var parser = require_parser();
|
|
@@ -13595,7 +13691,7 @@ var require_public_api = __commonJS({
|
|
|
13595
13691
|
const doc = parseDocument(src, options);
|
|
13596
13692
|
if (!doc)
|
|
13597
13693
|
return null;
|
|
13598
|
-
doc.warnings.forEach((warning) =>
|
|
13694
|
+
doc.warnings.forEach((warning) => log12.warn(doc.options.logLevel, warning));
|
|
13599
13695
|
if (doc.errors.length > 0) {
|
|
13600
13696
|
if (doc.options.logLevel !== "silent")
|
|
13601
13697
|
throw doc.errors[0];
|
|
@@ -14354,8 +14450,8 @@ function uint8ArrayToBase64(bytes) {
|
|
|
14354
14450
|
}
|
|
14355
14451
|
return btoa(binaryString);
|
|
14356
14452
|
}
|
|
14357
|
-
function base64urlToUint8Array(
|
|
14358
|
-
const base643 =
|
|
14453
|
+
function base64urlToUint8Array(base64url4) {
|
|
14454
|
+
const base643 = base64url4.replace(/-/g, "+").replace(/_/g, "/");
|
|
14359
14455
|
const padding = "=".repeat((4 - base643.length % 4) % 4);
|
|
14360
14456
|
return base64ToUint8Array(base643 + padding);
|
|
14361
14457
|
}
|
|
@@ -37111,7 +37207,7 @@ var require_logging = __commonJS({
|
|
|
37111
37207
|
_logVerbosity = verbosity;
|
|
37112
37208
|
};
|
|
37113
37209
|
exports2.setLoggerVerbosity = setLoggerVerbosity;
|
|
37114
|
-
var
|
|
37210
|
+
var log12 = (severity, ...args) => {
|
|
37115
37211
|
let logFunction;
|
|
37116
37212
|
if (severity >= _logVerbosity) {
|
|
37117
37213
|
switch (severity) {
|
|
@@ -37133,7 +37229,7 @@ var require_logging = __commonJS({
|
|
|
37133
37229
|
}
|
|
37134
37230
|
}
|
|
37135
37231
|
};
|
|
37136
|
-
exports2.log =
|
|
37232
|
+
exports2.log = log12;
|
|
37137
37233
|
var tracersString = (_d = (_c = process.env.GRPC_NODE_TRACE) !== null && _c !== void 0 ? _c : process.env.GRPC_TRACE) !== null && _d !== void 0 ? _d : "";
|
|
37138
37234
|
var enabledTracers = /* @__PURE__ */ new Set();
|
|
37139
37235
|
var disabledTracers = /* @__PURE__ */ new Set();
|
|
@@ -38519,8 +38615,8 @@ var require_service_config = __commonJS({
|
|
|
38519
38615
|
}
|
|
38520
38616
|
if (Array.isArray(validatedConfig.clientHostname)) {
|
|
38521
38617
|
let hostnameMatched = false;
|
|
38522
|
-
for (const
|
|
38523
|
-
if (
|
|
38618
|
+
for (const hostname5 of validatedConfig.clientHostname) {
|
|
38619
|
+
if (hostname5 === os2.hostname()) {
|
|
38524
38620
|
hostnameMatched = true;
|
|
38525
38621
|
}
|
|
38526
38622
|
}
|
|
@@ -51089,8 +51185,8 @@ var require_single_subchannel_channel = __commonJS({
|
|
|
51089
51185
|
if (splitPath2.length >= 2) {
|
|
51090
51186
|
serviceName = splitPath2[1];
|
|
51091
51187
|
}
|
|
51092
|
-
const
|
|
51093
|
-
this.serviceUrl = `https://${
|
|
51188
|
+
const hostname5 = (_b = (_a3 = (0, uri_parser_1.splitHostPort)(this.options.host)) === null || _a3 === void 0 ? void 0 : _a3.host) !== null && _b !== void 0 ? _b : "localhost";
|
|
51189
|
+
this.serviceUrl = `https://${hostname5}/${serviceName}`;
|
|
51094
51190
|
const timeout = (0, deadline_1.getRelativeTimeout)(options.deadline);
|
|
51095
51191
|
if (timeout !== Infinity) {
|
|
51096
51192
|
if (timeout <= 0) {
|
|
@@ -51737,8 +51833,8 @@ var require_resolver_dns = __commonJS({
|
|
|
51737
51833
|
}
|
|
51738
51834
|
trace("Looking up DNS hostname " + this.dnsHostname);
|
|
51739
51835
|
this.latestLookupResult = null;
|
|
51740
|
-
const
|
|
51741
|
-
this.pendingLookupPromise = this.lookup(
|
|
51836
|
+
const hostname5 = this.dnsHostname;
|
|
51837
|
+
this.pendingLookupPromise = this.lookup(hostname5);
|
|
51742
51838
|
this.pendingLookupPromise.then((addressList) => {
|
|
51743
51839
|
if (this.pendingLookupPromise === null) {
|
|
51744
51840
|
return;
|
|
@@ -51761,7 +51857,7 @@ var require_resolver_dns = __commonJS({
|
|
|
51761
51857
|
this.listener((0, call_interface_1.statusOrFromError)(this.defaultResolutionError), {}, this.latestServiceConfigResult, "");
|
|
51762
51858
|
});
|
|
51763
51859
|
if (this.isServiceConfigEnabled && this.pendingTxtPromise === null) {
|
|
51764
|
-
this.pendingTxtPromise = this.resolveTxt(
|
|
51860
|
+
this.pendingTxtPromise = this.resolveTxt(hostname5);
|
|
51765
51861
|
this.pendingTxtPromise.then((txtRecord) => {
|
|
51766
51862
|
if (this.pendingTxtPromise === null) {
|
|
51767
51863
|
return;
|
|
@@ -51803,12 +51899,12 @@ var require_resolver_dns = __commonJS({
|
|
|
51803
51899
|
this.continueResolving = true;
|
|
51804
51900
|
}
|
|
51805
51901
|
}
|
|
51806
|
-
async lookup(
|
|
51902
|
+
async lookup(hostname5) {
|
|
51807
51903
|
if (environment_1.GRPC_NODE_USE_ALTERNATIVE_RESOLVER) {
|
|
51808
51904
|
trace("Using alternative DNS resolver.");
|
|
51809
51905
|
const records = await Promise.allSettled([
|
|
51810
|
-
this.alternativeResolver.resolve4(
|
|
51811
|
-
this.alternativeResolver.resolve6(
|
|
51906
|
+
this.alternativeResolver.resolve4(hostname5),
|
|
51907
|
+
this.alternativeResolver.resolve6(hostname5)
|
|
51812
51908
|
]);
|
|
51813
51909
|
if (records.every((result) => result.status === "rejected")) {
|
|
51814
51910
|
throw new Error(records[0].reason);
|
|
@@ -51820,15 +51916,15 @@ var require_resolver_dns = __commonJS({
|
|
|
51820
51916
|
port: +this.port
|
|
51821
51917
|
}));
|
|
51822
51918
|
}
|
|
51823
|
-
const addressList = await dns_1.promises.lookup(
|
|
51919
|
+
const addressList = await dns_1.promises.lookup(hostname5, { all: true });
|
|
51824
51920
|
return addressList.map((addr) => ({ host: addr.address, port: +this.port }));
|
|
51825
51921
|
}
|
|
51826
|
-
async resolveTxt(
|
|
51922
|
+
async resolveTxt(hostname5) {
|
|
51827
51923
|
if (environment_1.GRPC_NODE_USE_ALTERNATIVE_RESOLVER) {
|
|
51828
51924
|
trace("Using alternative DNS resolver.");
|
|
51829
|
-
return this.alternativeResolver.resolveTxt(
|
|
51925
|
+
return this.alternativeResolver.resolveTxt(hostname5);
|
|
51830
51926
|
}
|
|
51831
|
-
return dns_1.promises.resolveTxt(
|
|
51927
|
+
return dns_1.promises.resolveTxt(hostname5);
|
|
51832
51928
|
}
|
|
51833
51929
|
startNextResolutionTimer() {
|
|
51834
51930
|
var _a3, _b;
|
|
@@ -51956,13 +52052,13 @@ var require_http_proxy = __commonJS({
|
|
|
51956
52052
|
userCred = proxyUrl.username;
|
|
51957
52053
|
}
|
|
51958
52054
|
}
|
|
51959
|
-
const
|
|
52055
|
+
const hostname5 = proxyUrl.hostname;
|
|
51960
52056
|
let port = proxyUrl.port;
|
|
51961
52057
|
if (port === "") {
|
|
51962
52058
|
port = "80";
|
|
51963
52059
|
}
|
|
51964
52060
|
const result = {
|
|
51965
|
-
address: `${
|
|
52061
|
+
address: `${hostname5}:${port}`
|
|
51966
52062
|
};
|
|
51967
52063
|
if (userCred) {
|
|
51968
52064
|
result.creds = userCred;
|
|
@@ -53275,8 +53371,8 @@ var require_load_balancing_call = __commonJS({
|
|
|
53275
53371
|
if (splitPath2.length >= 2) {
|
|
53276
53372
|
serviceName = splitPath2[1];
|
|
53277
53373
|
}
|
|
53278
|
-
const
|
|
53279
|
-
this.serviceUrl = `https://${
|
|
53374
|
+
const hostname5 = (_b = (_a3 = (0, uri_parser_1.splitHostPort)(this.host)) === null || _a3 === void 0 ? void 0 : _a3.host) !== null && _b !== void 0 ? _b : "localhost";
|
|
53375
|
+
this.serviceUrl = `https://${hostname5}/${serviceName}`;
|
|
53280
53376
|
this.startTime = /* @__PURE__ */ new Date();
|
|
53281
53377
|
}
|
|
53282
53378
|
getDeadlineInfo() {
|
|
@@ -91085,9 +91181,9 @@ var require_parse_host_uri = __commonJS({
|
|
|
91085
91181
|
var ipv4Hostname = "(?:\\d{1,3}(?:\\.\\d{1,3}){3})";
|
|
91086
91182
|
var ipv6Hostname = "(?:\\[(?<ipv6>[0-9a-fA-F.:]+)\\])";
|
|
91087
91183
|
var dnsHostname = "(?:[^:/]+)";
|
|
91088
|
-
var
|
|
91184
|
+
var hostname5 = `(?:${ipv4Hostname}|${ipv6Hostname}|${dnsHostname})`;
|
|
91089
91185
|
var port = "(?::(?<port>\\d+))";
|
|
91090
|
-
var protoHostPortRegex = new RegExp(`^${scheme}??(?<hostname>${
|
|
91186
|
+
var protoHostPortRegex = new RegExp(`^${scheme}??(?<hostname>${hostname5})${port}?$`);
|
|
91091
91187
|
function splitProtoHostPort(uri) {
|
|
91092
91188
|
const match2 = protoHostPortRegex.exec(uri);
|
|
91093
91189
|
if (!match2?.groups)
|
|
@@ -91099,9 +91195,9 @@ var require_parse_host_uri = __commonJS({
|
|
|
91099
91195
|
};
|
|
91100
91196
|
}
|
|
91101
91197
|
function joinProtoHostPort(components) {
|
|
91102
|
-
const { scheme: scheme2, hostname:
|
|
91198
|
+
const { scheme: scheme2, hostname: hostname6, port: port2 } = components;
|
|
91103
91199
|
const schemeText = scheme2 ? `${scheme2}:` : "";
|
|
91104
|
-
const hostnameText =
|
|
91200
|
+
const hostnameText = hostname6.includes(":") ? `[${hostname6}]` : hostname6;
|
|
91105
91201
|
const portText = port2 !== void 0 ? `:${port2}` : "";
|
|
91106
91202
|
return `${schemeText}${hostnameText}${portText}`;
|
|
91107
91203
|
}
|
|
@@ -98687,7 +98783,7 @@ var require_scan = __commonJS({
|
|
|
98687
98783
|
var require_parse4 = __commonJS({
|
|
98688
98784
|
"node_modules/picomatch/lib/parse.js"(exports2, module2) {
|
|
98689
98785
|
"use strict";
|
|
98690
|
-
var
|
|
98786
|
+
var constants2 = require_constants5();
|
|
98691
98787
|
var utils = require_utils2();
|
|
98692
98788
|
var {
|
|
98693
98789
|
MAX_LENGTH,
|
|
@@ -98695,7 +98791,7 @@ var require_parse4 = __commonJS({
|
|
|
98695
98791
|
REGEX_NON_SPECIAL_CHARS,
|
|
98696
98792
|
REGEX_SPECIAL_CHARS_BACKREF,
|
|
98697
98793
|
REPLACEMENTS
|
|
98698
|
-
} =
|
|
98794
|
+
} = constants2;
|
|
98699
98795
|
var expandRange = (args, options) => {
|
|
98700
98796
|
if (typeof options.expandRange === "function") {
|
|
98701
98797
|
return options.expandRange(...args, options);
|
|
@@ -98901,7 +98997,7 @@ var require_parse4 = __commonJS({
|
|
|
98901
98997
|
if (options.maxExtglobRecursion === false) {
|
|
98902
98998
|
return { risky: false };
|
|
98903
98999
|
}
|
|
98904
|
-
const max = typeof options.maxExtglobRecursion === "number" ? options.maxExtglobRecursion :
|
|
99000
|
+
const max = typeof options.maxExtglobRecursion === "number" ? options.maxExtglobRecursion : constants2.DEFAULT_MAX_EXTGLOB_RECURSION;
|
|
98905
99001
|
const branches = splitTopLevel(body).map((branch) => branch.trim());
|
|
98906
99002
|
if (branches.length > 1) {
|
|
98907
99003
|
if (branches.some((branch) => branch === "") || branches.some((branch) => /^[*?]+$/.test(branch)) || hasRepeatedCharPrefixOverlap(branches)) {
|
|
@@ -98934,8 +99030,8 @@ var require_parse4 = __commonJS({
|
|
|
98934
99030
|
const tokens = [bos];
|
|
98935
99031
|
const capture = opts.capture ? "" : "?:";
|
|
98936
99032
|
const win32 = utils.isWindows(options);
|
|
98937
|
-
const PLATFORM_CHARS =
|
|
98938
|
-
const EXTGLOB_CHARS =
|
|
99033
|
+
const PLATFORM_CHARS = constants2.globChars(win32);
|
|
99034
|
+
const EXTGLOB_CHARS = constants2.extglobChars(PLATFORM_CHARS);
|
|
98939
99035
|
const {
|
|
98940
99036
|
DOT_LITERAL,
|
|
98941
99037
|
PLUS_LITERAL,
|
|
@@ -99634,7 +99730,7 @@ var require_parse4 = __commonJS({
|
|
|
99634
99730
|
NO_DOTS_SLASH,
|
|
99635
99731
|
STAR,
|
|
99636
99732
|
START_ANCHOR
|
|
99637
|
-
} =
|
|
99733
|
+
} = constants2.globChars(win32);
|
|
99638
99734
|
const nodot = opts.dot ? NO_DOTS : NO_DOT;
|
|
99639
99735
|
const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
|
|
99640
99736
|
const capture = opts.capture ? "" : "?:";
|
|
@@ -99693,7 +99789,7 @@ var require_picomatch = __commonJS({
|
|
|
99693
99789
|
var scan = require_scan();
|
|
99694
99790
|
var parse3 = require_parse4();
|
|
99695
99791
|
var utils = require_utils2();
|
|
99696
|
-
var
|
|
99792
|
+
var constants2 = require_constants5();
|
|
99697
99793
|
var isObject2 = (val) => val && typeof val === "object" && !Array.isArray(val);
|
|
99698
99794
|
var picomatch = (glob, options, returnState = false) => {
|
|
99699
99795
|
if (Array.isArray(glob)) {
|
|
@@ -99821,7 +99917,7 @@ var require_picomatch = __commonJS({
|
|
|
99821
99917
|
return /$^/;
|
|
99822
99918
|
}
|
|
99823
99919
|
};
|
|
99824
|
-
picomatch.constants =
|
|
99920
|
+
picomatch.constants = constants2;
|
|
99825
99921
|
module2.exports = picomatch;
|
|
99826
99922
|
}
|
|
99827
99923
|
});
|
|
@@ -104039,11 +104135,11 @@ var require_pg_connection_string = __commonJS({
|
|
|
104039
104135
|
config2.client_encoding = result.searchParams.get("encoding");
|
|
104040
104136
|
return config2;
|
|
104041
104137
|
}
|
|
104042
|
-
const
|
|
104138
|
+
const hostname5 = dummyHost ? "" : result.hostname;
|
|
104043
104139
|
if (!config2.host) {
|
|
104044
|
-
config2.host = decodeURIComponent(
|
|
104045
|
-
} else if (
|
|
104046
|
-
result.pathname =
|
|
104140
|
+
config2.host = decodeURIComponent(hostname5);
|
|
104141
|
+
} else if (hostname5 && /^%2f/i.test(hostname5)) {
|
|
104142
|
+
result.pathname = hostname5 + result.pathname;
|
|
104047
104143
|
}
|
|
104048
104144
|
if (!config2.port) {
|
|
104049
104145
|
config2.port = result.port;
|
|
@@ -119963,7 +120059,7 @@ var require_crc = __commonJS({
|
|
|
119963
120059
|
var require_parser3 = __commonJS({
|
|
119964
120060
|
"node_modules/pngjs/lib/parser.js"(exports2, module2) {
|
|
119965
120061
|
"use strict";
|
|
119966
|
-
var
|
|
120062
|
+
var constants2 = require_constants7();
|
|
119967
120063
|
var CrcCalculator = require_crc();
|
|
119968
120064
|
var Parser = module2.exports = function(options, dependencies) {
|
|
119969
120065
|
this._options = options;
|
|
@@ -119974,12 +120070,12 @@ var require_parser3 = __commonJS({
|
|
|
119974
120070
|
this._palette = [];
|
|
119975
120071
|
this._colorType = 0;
|
|
119976
120072
|
this._chunks = {};
|
|
119977
|
-
this._chunks[
|
|
119978
|
-
this._chunks[
|
|
119979
|
-
this._chunks[
|
|
119980
|
-
this._chunks[
|
|
119981
|
-
this._chunks[
|
|
119982
|
-
this._chunks[
|
|
120073
|
+
this._chunks[constants2.TYPE_IHDR] = this._handleIHDR.bind(this);
|
|
120074
|
+
this._chunks[constants2.TYPE_IEND] = this._handleIEND.bind(this);
|
|
120075
|
+
this._chunks[constants2.TYPE_IDAT] = this._handleIDAT.bind(this);
|
|
120076
|
+
this._chunks[constants2.TYPE_PLTE] = this._handlePLTE.bind(this);
|
|
120077
|
+
this._chunks[constants2.TYPE_tRNS] = this._handleTRNS.bind(this);
|
|
120078
|
+
this._chunks[constants2.TYPE_gAMA] = this._handleGAMA.bind(this);
|
|
119983
120079
|
this.read = dependencies.read;
|
|
119984
120080
|
this.error = dependencies.error;
|
|
119985
120081
|
this.metadata = dependencies.metadata;
|
|
@@ -119994,10 +120090,10 @@ var require_parser3 = __commonJS({
|
|
|
119994
120090
|
};
|
|
119995
120091
|
};
|
|
119996
120092
|
Parser.prototype.start = function() {
|
|
119997
|
-
this.read(
|
|
120093
|
+
this.read(constants2.PNG_SIGNATURE.length, this._parseSignature.bind(this));
|
|
119998
120094
|
};
|
|
119999
120095
|
Parser.prototype._parseSignature = function(data) {
|
|
120000
|
-
let signature =
|
|
120096
|
+
let signature = constants2.PNG_SIGNATURE;
|
|
120001
120097
|
for (let i = 0; i < signature.length; i++) {
|
|
120002
120098
|
if (data[i] !== signature[i]) {
|
|
120003
120099
|
this.error(new Error("Invalid file signature"));
|
|
@@ -120014,7 +120110,7 @@ var require_parser3 = __commonJS({
|
|
|
120014
120110
|
name += String.fromCharCode(data[i]);
|
|
120015
120111
|
}
|
|
120016
120112
|
let ancillary = Boolean(data[4] & 32);
|
|
120017
|
-
if (!this._hasIHDR && type !==
|
|
120113
|
+
if (!this._hasIHDR && type !== constants2.TYPE_IHDR) {
|
|
120018
120114
|
this.error(new Error("Expected IHDR on beggining"));
|
|
120019
120115
|
return;
|
|
120020
120116
|
}
|
|
@@ -120062,7 +120158,7 @@ var require_parser3 = __commonJS({
|
|
|
120062
120158
|
this.error(new Error("Unsupported bit depth " + depth));
|
|
120063
120159
|
return;
|
|
120064
120160
|
}
|
|
120065
|
-
if (!(colorType in
|
|
120161
|
+
if (!(colorType in constants2.COLORTYPE_TO_BPP_MAP)) {
|
|
120066
120162
|
this.error(new Error("Unsupported color type"));
|
|
120067
120163
|
return;
|
|
120068
120164
|
}
|
|
@@ -120079,16 +120175,16 @@ var require_parser3 = __commonJS({
|
|
|
120079
120175
|
return;
|
|
120080
120176
|
}
|
|
120081
120177
|
this._colorType = colorType;
|
|
120082
|
-
let bpp =
|
|
120178
|
+
let bpp = constants2.COLORTYPE_TO_BPP_MAP[this._colorType];
|
|
120083
120179
|
this._hasIHDR = true;
|
|
120084
120180
|
this.metadata({
|
|
120085
120181
|
width,
|
|
120086
120182
|
height,
|
|
120087
120183
|
depth,
|
|
120088
120184
|
interlace: Boolean(interlace),
|
|
120089
|
-
palette: Boolean(colorType &
|
|
120090
|
-
color: Boolean(colorType &
|
|
120091
|
-
alpha: Boolean(colorType &
|
|
120185
|
+
palette: Boolean(colorType & constants2.COLORTYPE_PALETTE),
|
|
120186
|
+
color: Boolean(colorType & constants2.COLORTYPE_COLOR),
|
|
120187
|
+
alpha: Boolean(colorType & constants2.COLORTYPE_ALPHA),
|
|
120092
120188
|
bpp,
|
|
120093
120189
|
colorType
|
|
120094
120190
|
});
|
|
@@ -120112,7 +120208,7 @@ var require_parser3 = __commonJS({
|
|
|
120112
120208
|
};
|
|
120113
120209
|
Parser.prototype._parseTRNS = function(data) {
|
|
120114
120210
|
this._crc.write(data);
|
|
120115
|
-
if (this._colorType ===
|
|
120211
|
+
if (this._colorType === constants2.COLORTYPE_PALETTE_COLOR) {
|
|
120116
120212
|
if (this._palette.length === 0) {
|
|
120117
120213
|
this.error(new Error("Transparency chunk must be after palette"));
|
|
120118
120214
|
return;
|
|
@@ -120126,10 +120222,10 @@ var require_parser3 = __commonJS({
|
|
|
120126
120222
|
}
|
|
120127
120223
|
this.palette(this._palette);
|
|
120128
120224
|
}
|
|
120129
|
-
if (this._colorType ===
|
|
120225
|
+
if (this._colorType === constants2.COLORTYPE_GRAYSCALE) {
|
|
120130
120226
|
this.transColor([data.readUInt16BE(0)]);
|
|
120131
120227
|
}
|
|
120132
|
-
if (this._colorType ===
|
|
120228
|
+
if (this._colorType === constants2.COLORTYPE_COLOR) {
|
|
120133
120229
|
this.transColor([
|
|
120134
120230
|
data.readUInt16BE(0),
|
|
120135
120231
|
data.readUInt16BE(2),
|
|
@@ -120143,7 +120239,7 @@ var require_parser3 = __commonJS({
|
|
|
120143
120239
|
};
|
|
120144
120240
|
Parser.prototype._parseGAMA = function(data) {
|
|
120145
120241
|
this._crc.write(data);
|
|
120146
|
-
this.gamma(data.readUInt32BE(0) /
|
|
120242
|
+
this.gamma(data.readUInt32BE(0) / constants2.GAMMA_DIVISION);
|
|
120147
120243
|
this._handleChunkEnd();
|
|
120148
120244
|
};
|
|
120149
120245
|
Parser.prototype._handleIDAT = function(length) {
|
|
@@ -120155,7 +120251,7 @@ var require_parser3 = __commonJS({
|
|
|
120155
120251
|
};
|
|
120156
120252
|
Parser.prototype._parseIDAT = function(length, data) {
|
|
120157
120253
|
this._crc.write(data);
|
|
120158
|
-
if (this._colorType ===
|
|
120254
|
+
if (this._colorType === constants2.COLORTYPE_PALETTE_COLOR && this._palette.length === 0) {
|
|
120159
120255
|
throw new Error("Expected palette not found");
|
|
120160
120256
|
}
|
|
120161
120257
|
this.inflateData(data);
|
|
@@ -120643,9 +120739,9 @@ var require_parser_async = __commonJS({
|
|
|
120643
120739
|
var require_bitpacker = __commonJS({
|
|
120644
120740
|
"node_modules/pngjs/lib/bitpacker.js"(exports2, module2) {
|
|
120645
120741
|
"use strict";
|
|
120646
|
-
var
|
|
120742
|
+
var constants2 = require_constants7();
|
|
120647
120743
|
module2.exports = function(dataIn, width, height, options) {
|
|
120648
|
-
let outHasAlpha = [
|
|
120744
|
+
let outHasAlpha = [constants2.COLORTYPE_COLOR_ALPHA, constants2.COLORTYPE_ALPHA].indexOf(
|
|
120649
120745
|
options.colorType
|
|
120650
120746
|
) !== -1;
|
|
120651
120747
|
if (options.colorType === options.inputColorType) {
|
|
@@ -120665,11 +120761,11 @@ var require_bitpacker = __commonJS({
|
|
|
120665
120761
|
}
|
|
120666
120762
|
let data = options.bitDepth !== 16 ? dataIn : new Uint16Array(dataIn.buffer);
|
|
120667
120763
|
let maxValue = 255;
|
|
120668
|
-
let inBpp =
|
|
120764
|
+
let inBpp = constants2.COLORTYPE_TO_BPP_MAP[options.inputColorType];
|
|
120669
120765
|
if (inBpp === 4 && !options.inputHasAlpha) {
|
|
120670
120766
|
inBpp = 3;
|
|
120671
120767
|
}
|
|
120672
|
-
let outBpp =
|
|
120768
|
+
let outBpp = constants2.COLORTYPE_TO_BPP_MAP[options.colorType];
|
|
120673
120769
|
if (options.bitDepth === 16) {
|
|
120674
120770
|
maxValue = 65535;
|
|
120675
120771
|
outBpp *= 2;
|
|
@@ -120693,24 +120789,24 @@ var require_bitpacker = __commonJS({
|
|
|
120693
120789
|
let blue;
|
|
120694
120790
|
let alpha = maxValue;
|
|
120695
120791
|
switch (options.inputColorType) {
|
|
120696
|
-
case
|
|
120792
|
+
case constants2.COLORTYPE_COLOR_ALPHA:
|
|
120697
120793
|
alpha = data[inIndex + 3];
|
|
120698
120794
|
red = data[inIndex];
|
|
120699
120795
|
green = data[inIndex + 1];
|
|
120700
120796
|
blue = data[inIndex + 2];
|
|
120701
120797
|
break;
|
|
120702
|
-
case
|
|
120798
|
+
case constants2.COLORTYPE_COLOR:
|
|
120703
120799
|
red = data[inIndex];
|
|
120704
120800
|
green = data[inIndex + 1];
|
|
120705
120801
|
blue = data[inIndex + 2];
|
|
120706
120802
|
break;
|
|
120707
|
-
case
|
|
120803
|
+
case constants2.COLORTYPE_ALPHA:
|
|
120708
120804
|
alpha = data[inIndex + 1];
|
|
120709
120805
|
red = data[inIndex];
|
|
120710
120806
|
green = red;
|
|
120711
120807
|
blue = red;
|
|
120712
120808
|
break;
|
|
120713
|
-
case
|
|
120809
|
+
case constants2.COLORTYPE_GRAYSCALE:
|
|
120714
120810
|
red = data[inIndex];
|
|
120715
120811
|
green = red;
|
|
120716
120812
|
blue = red;
|
|
@@ -120743,8 +120839,8 @@ var require_bitpacker = __commonJS({
|
|
|
120743
120839
|
for (let x = 0; x < width; x++) {
|
|
120744
120840
|
let rgba = getRGBA(data, inIndex);
|
|
120745
120841
|
switch (options.colorType) {
|
|
120746
|
-
case
|
|
120747
|
-
case
|
|
120842
|
+
case constants2.COLORTYPE_COLOR_ALPHA:
|
|
120843
|
+
case constants2.COLORTYPE_COLOR:
|
|
120748
120844
|
if (options.bitDepth === 8) {
|
|
120749
120845
|
outData[outIndex] = rgba.red;
|
|
120750
120846
|
outData[outIndex + 1] = rgba.green;
|
|
@@ -120761,8 +120857,8 @@ var require_bitpacker = __commonJS({
|
|
|
120761
120857
|
}
|
|
120762
120858
|
}
|
|
120763
120859
|
break;
|
|
120764
|
-
case
|
|
120765
|
-
case
|
|
120860
|
+
case constants2.COLORTYPE_ALPHA:
|
|
120861
|
+
case constants2.COLORTYPE_GRAYSCALE: {
|
|
120766
120862
|
let grayscale = (rgba.red + rgba.green + rgba.blue) / 3;
|
|
120767
120863
|
if (options.bitDepth === 8) {
|
|
120768
120864
|
outData[outIndex] = grayscale;
|
|
@@ -120935,7 +121031,7 @@ var require_filter_pack = __commonJS({
|
|
|
120935
121031
|
var require_packer = __commonJS({
|
|
120936
121032
|
"node_modules/pngjs/lib/packer.js"(exports2, module2) {
|
|
120937
121033
|
"use strict";
|
|
120938
|
-
var
|
|
121034
|
+
var constants2 = require_constants7();
|
|
120939
121035
|
var CrcStream = require_crc();
|
|
120940
121036
|
var bitPacker = require_bitpacker();
|
|
120941
121037
|
var filter = require_filter_pack();
|
|
@@ -120948,23 +121044,23 @@ var require_packer = __commonJS({
|
|
|
120948
121044
|
options.inputHasAlpha = options.inputHasAlpha != null ? options.inputHasAlpha : true;
|
|
120949
121045
|
options.deflateFactory = options.deflateFactory || zlib.createDeflate;
|
|
120950
121046
|
options.bitDepth = options.bitDepth || 8;
|
|
120951
|
-
options.colorType = typeof options.colorType === "number" ? options.colorType :
|
|
120952
|
-
options.inputColorType = typeof options.inputColorType === "number" ? options.inputColorType :
|
|
121047
|
+
options.colorType = typeof options.colorType === "number" ? options.colorType : constants2.COLORTYPE_COLOR_ALPHA;
|
|
121048
|
+
options.inputColorType = typeof options.inputColorType === "number" ? options.inputColorType : constants2.COLORTYPE_COLOR_ALPHA;
|
|
120953
121049
|
if ([
|
|
120954
|
-
|
|
120955
|
-
|
|
120956
|
-
|
|
120957
|
-
|
|
121050
|
+
constants2.COLORTYPE_GRAYSCALE,
|
|
121051
|
+
constants2.COLORTYPE_COLOR,
|
|
121052
|
+
constants2.COLORTYPE_COLOR_ALPHA,
|
|
121053
|
+
constants2.COLORTYPE_ALPHA
|
|
120958
121054
|
].indexOf(options.colorType) === -1) {
|
|
120959
121055
|
throw new Error(
|
|
120960
121056
|
"option color type:" + options.colorType + " is not supported at present"
|
|
120961
121057
|
);
|
|
120962
121058
|
}
|
|
120963
121059
|
if ([
|
|
120964
|
-
|
|
120965
|
-
|
|
120966
|
-
|
|
120967
|
-
|
|
121060
|
+
constants2.COLORTYPE_GRAYSCALE,
|
|
121061
|
+
constants2.COLORTYPE_COLOR,
|
|
121062
|
+
constants2.COLORTYPE_COLOR_ALPHA,
|
|
121063
|
+
constants2.COLORTYPE_ALPHA
|
|
120968
121064
|
].indexOf(options.inputColorType) === -1) {
|
|
120969
121065
|
throw new Error(
|
|
120970
121066
|
"option input color type:" + options.inputColorType + " is not supported at present"
|
|
@@ -120988,7 +121084,7 @@ var require_packer = __commonJS({
|
|
|
120988
121084
|
};
|
|
120989
121085
|
Packer.prototype.filterData = function(data, width, height) {
|
|
120990
121086
|
let packedData = bitPacker(data, width, height, this._options);
|
|
120991
|
-
let bpp =
|
|
121087
|
+
let bpp = constants2.COLORTYPE_TO_BPP_MAP[this._options.colorType];
|
|
120992
121088
|
let filteredData = filter(packedData, width, height, this._options, bpp);
|
|
120993
121089
|
return filteredData;
|
|
120994
121090
|
};
|
|
@@ -121008,8 +121104,8 @@ var require_packer = __commonJS({
|
|
|
121008
121104
|
};
|
|
121009
121105
|
Packer.prototype.packGAMA = function(gamma) {
|
|
121010
121106
|
let buf = Buffer.alloc(4);
|
|
121011
|
-
buf.writeUInt32BE(Math.floor(gamma *
|
|
121012
|
-
return this._packChunk(
|
|
121107
|
+
buf.writeUInt32BE(Math.floor(gamma * constants2.GAMMA_DIVISION), 0);
|
|
121108
|
+
return this._packChunk(constants2.TYPE_gAMA, buf);
|
|
121013
121109
|
};
|
|
121014
121110
|
Packer.prototype.packIHDR = function(width, height) {
|
|
121015
121111
|
let buf = Buffer.alloc(13);
|
|
@@ -121020,13 +121116,13 @@ var require_packer = __commonJS({
|
|
|
121020
121116
|
buf[10] = 0;
|
|
121021
121117
|
buf[11] = 0;
|
|
121022
121118
|
buf[12] = 0;
|
|
121023
|
-
return this._packChunk(
|
|
121119
|
+
return this._packChunk(constants2.TYPE_IHDR, buf);
|
|
121024
121120
|
};
|
|
121025
121121
|
Packer.prototype.packIDAT = function(data) {
|
|
121026
|
-
return this._packChunk(
|
|
121122
|
+
return this._packChunk(constants2.TYPE_IDAT, data);
|
|
121027
121123
|
};
|
|
121028
121124
|
Packer.prototype.packIEND = function() {
|
|
121029
|
-
return this._packChunk(
|
|
121125
|
+
return this._packChunk(constants2.TYPE_IEND, null);
|
|
121030
121126
|
};
|
|
121031
121127
|
}
|
|
121032
121128
|
});
|
|
@@ -121037,7 +121133,7 @@ var require_packer_async = __commonJS({
|
|
|
121037
121133
|
"use strict";
|
|
121038
121134
|
var util = require("util");
|
|
121039
121135
|
var Stream = require("stream");
|
|
121040
|
-
var
|
|
121136
|
+
var constants2 = require_constants7();
|
|
121041
121137
|
var Packer = require_packer();
|
|
121042
121138
|
var PackerAsync = module2.exports = function(opt) {
|
|
121043
121139
|
Stream.call(this);
|
|
@@ -121048,7 +121144,7 @@ var require_packer_async = __commonJS({
|
|
|
121048
121144
|
};
|
|
121049
121145
|
util.inherits(PackerAsync, Stream);
|
|
121050
121146
|
PackerAsync.prototype.pack = function(data, width, height, gamma) {
|
|
121051
|
-
this.emit("data", Buffer.from(
|
|
121147
|
+
this.emit("data", Buffer.from(constants2.PNG_SIGNATURE));
|
|
121052
121148
|
this.emit("data", this._packer.packIHDR(width, height));
|
|
121053
121149
|
if (gamma) {
|
|
121054
121150
|
this.emit("data", this._packer.packGAMA(gamma));
|
|
@@ -121376,7 +121472,7 @@ var require_packer_sync = __commonJS({
|
|
|
121376
121472
|
if (!zlib.deflateSync) {
|
|
121377
121473
|
hasSyncZlib = false;
|
|
121378
121474
|
}
|
|
121379
|
-
var
|
|
121475
|
+
var constants2 = require_constants7();
|
|
121380
121476
|
var Packer = require_packer();
|
|
121381
121477
|
module2.exports = function(metaData, opt) {
|
|
121382
121478
|
if (!hasSyncZlib) {
|
|
@@ -121387,7 +121483,7 @@ var require_packer_sync = __commonJS({
|
|
|
121387
121483
|
let options = opt || {};
|
|
121388
121484
|
let packer = new Packer(options);
|
|
121389
121485
|
let chunks = [];
|
|
121390
|
-
chunks.push(Buffer.from(
|
|
121486
|
+
chunks.push(Buffer.from(constants2.PNG_SIGNATURE));
|
|
121391
121487
|
chunks.push(packer.packIHDR(metaData.width, metaData.height));
|
|
121392
121488
|
if (metaData.gamma) {
|
|
121393
121489
|
chunks.push(packer.packGAMA(metaData.gamma));
|
|
@@ -122490,10 +122586,10 @@ var require_truncate = __commonJS({
|
|
|
122490
122586
|
"node_modules/semver/functions/truncate.js"(exports2, module2) {
|
|
122491
122587
|
"use strict";
|
|
122492
122588
|
var parse3 = require_parse5();
|
|
122493
|
-
var
|
|
122589
|
+
var constants2 = require_constants8();
|
|
122494
122590
|
var SemVer = require_semver();
|
|
122495
122591
|
var truncate = (version2, truncation, options) => {
|
|
122496
|
-
if (!
|
|
122592
|
+
if (!constants2.RELEASE_TYPES.includes(truncation)) {
|
|
122497
122593
|
return null;
|
|
122498
122594
|
}
|
|
122499
122595
|
const clonedVersion = cloneInputVersion(version2, options);
|
|
@@ -123542,7 +123638,7 @@ var require_semver2 = __commonJS({
|
|
|
123542
123638
|
"node_modules/semver/index.js"(exports2, module2) {
|
|
123543
123639
|
"use strict";
|
|
123544
123640
|
var internalRe = require_re();
|
|
123545
|
-
var
|
|
123641
|
+
var constants2 = require_constants8();
|
|
123546
123642
|
var SemVer = require_semver();
|
|
123547
123643
|
var identifiers = require_identifiers();
|
|
123548
123644
|
var parse3 = require_parse5();
|
|
@@ -123626,8 +123722,8 @@ var require_semver2 = __commonJS({
|
|
|
123626
123722
|
re: internalRe.re,
|
|
123627
123723
|
src: internalRe.src,
|
|
123628
123724
|
tokens: internalRe.t,
|
|
123629
|
-
SEMVER_SPEC_VERSION:
|
|
123630
|
-
RELEASE_TYPES:
|
|
123725
|
+
SEMVER_SPEC_VERSION: constants2.SEMVER_SPEC_VERSION,
|
|
123726
|
+
RELEASE_TYPES: constants2.RELEASE_TYPES,
|
|
123631
123727
|
compareIdentifiers: identifiers.compareIdentifiers,
|
|
123632
123728
|
rcompareIdentifiers: identifiers.rcompareIdentifiers
|
|
123633
123729
|
};
|
|
@@ -123850,7 +123946,7 @@ function readMarker() {
|
|
|
123850
123946
|
const parsed = JSON.parse(raw2);
|
|
123851
123947
|
return MarkerSchema.parse(parsed);
|
|
123852
123948
|
} catch (err) {
|
|
123853
|
-
|
|
123949
|
+
log7.warn(`marker at ${markerPath()} is malformed; treating as absent`, {
|
|
123854
123950
|
error: err instanceof Error ? err.message : String(err)
|
|
123855
123951
|
});
|
|
123856
123952
|
return null;
|
|
@@ -123867,7 +123963,7 @@ function writeMarker(marker) {
|
|
|
123867
123963
|
function clearMarker() {
|
|
123868
123964
|
if ((0, import_node_fs21.existsSync)(markerPath())) (0, import_node_fs21.rmSync)(markerPath());
|
|
123869
123965
|
}
|
|
123870
|
-
var import_node_fs21, import_node_path25,
|
|
123966
|
+
var import_node_fs21, import_node_path25, log7;
|
|
123871
123967
|
var init_marker = __esm({
|
|
123872
123968
|
"src/lifecycle/marker.ts"() {
|
|
123873
123969
|
"use strict";
|
|
@@ -123876,7 +123972,7 @@ var init_marker = __esm({
|
|
|
123876
123972
|
init_logger();
|
|
123877
123973
|
init_constants();
|
|
123878
123974
|
init_marker_schema();
|
|
123879
|
-
|
|
123975
|
+
log7 = getLogger("lifecycle.marker");
|
|
123880
123976
|
}
|
|
123881
123977
|
});
|
|
123882
123978
|
|
|
@@ -124077,7 +124173,7 @@ function readPrefs() {
|
|
|
124077
124173
|
try {
|
|
124078
124174
|
return PrefsSchema.parse(JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8")));
|
|
124079
124175
|
} catch (err) {
|
|
124080
|
-
|
|
124176
|
+
log9.warn(`prefs at ${path2} are malformed; treating as empty`, {
|
|
124081
124177
|
error: err instanceof Error ? err.message : String(err)
|
|
124082
124178
|
});
|
|
124083
124179
|
return { repos: {} };
|
|
@@ -124112,7 +124208,7 @@ function forgetAll() {
|
|
|
124112
124208
|
const path2 = prefsPath();
|
|
124113
124209
|
if ((0, import_node_fs23.existsSync)(path2)) (0, import_node_fs23.rmSync)(path2);
|
|
124114
124210
|
}
|
|
124115
|
-
var import_node_fs23, import_node_path26,
|
|
124211
|
+
var import_node_fs23, import_node_path26, log9, PrefsSchema;
|
|
124116
124212
|
var init_prefs = __esm({
|
|
124117
124213
|
"src/lifecycle/prefs.ts"() {
|
|
124118
124214
|
"use strict";
|
|
@@ -124121,7 +124217,7 @@ var init_prefs = __esm({
|
|
|
124121
124217
|
init_zod();
|
|
124122
124218
|
init_logger();
|
|
124123
124219
|
init_constants();
|
|
124124
|
-
|
|
124220
|
+
log9 = getLogger("lifecycle.prefs");
|
|
124125
124221
|
PrefsSchema = external_exports.object({
|
|
124126
124222
|
repos: external_exports.record(
|
|
124127
124223
|
external_exports.string(),
|
|
@@ -124224,7 +124320,7 @@ function takeoverProd(opts) {
|
|
|
124224
124320
|
`prod is already suspended by dev pid ${existing.devPid} (since ${existing.suspendedAt}). Stop that dev session first, or run 'tb-streamer prod doctor'.`
|
|
124225
124321
|
);
|
|
124226
124322
|
}
|
|
124227
|
-
|
|
124323
|
+
log10.info(`stale marker found (pid ${existing.devPid} is gone) \u2014 clearing and proceeding`);
|
|
124228
124324
|
}
|
|
124229
124325
|
getSupervisor().bootoutAgent();
|
|
124230
124326
|
writeMarker({
|
|
@@ -124239,7 +124335,7 @@ function takeoverProd(opts) {
|
|
|
124239
124335
|
const m2 = readMarker();
|
|
124240
124336
|
if (m2 && m2.devPid === process.pid) {
|
|
124241
124337
|
writeMarker({ ...m2, userHeld: true });
|
|
124242
|
-
|
|
124338
|
+
log10.info(
|
|
124243
124339
|
`prod is suspended (userHeld). Run 'tb-streamer prod start' to restore the supervised instance.`
|
|
124244
124340
|
);
|
|
124245
124341
|
}
|
|
@@ -124258,14 +124354,14 @@ function takeoverProd(opts) {
|
|
|
124258
124354
|
});
|
|
124259
124355
|
process.on("uncaughtException", (err) => {
|
|
124260
124356
|
flipUserHeld();
|
|
124261
|
-
|
|
124357
|
+
log10.error(`uncaught: ${err.message}`);
|
|
124262
124358
|
process.exit(1);
|
|
124263
124359
|
});
|
|
124264
124360
|
process.on("exit", () => {
|
|
124265
124361
|
flipUserHeld();
|
|
124266
124362
|
});
|
|
124267
124363
|
}
|
|
124268
|
-
var import_node_net,
|
|
124364
|
+
var import_node_net, log10;
|
|
124269
124365
|
var init_dev_takeover = __esm({
|
|
124270
124366
|
"src/lifecycle/dev-takeover.ts"() {
|
|
124271
124367
|
"use strict";
|
|
@@ -124274,7 +124370,7 @@ var init_dev_takeover = __esm({
|
|
|
124274
124370
|
init_marker();
|
|
124275
124371
|
init_platform();
|
|
124276
124372
|
init_prefs();
|
|
124277
|
-
|
|
124373
|
+
log10 = getLogger("lifecycle.dev-takeover");
|
|
124278
124374
|
}
|
|
124279
124375
|
});
|
|
124280
124376
|
|
|
@@ -127787,6 +127883,9 @@ function appendDevSessionMarker() {
|
|
|
127787
127883
|
}
|
|
127788
127884
|
}
|
|
127789
127885
|
|
|
127886
|
+
// cli/index.ts
|
|
127887
|
+
init_feature_flags();
|
|
127888
|
+
|
|
127790
127889
|
// src/lan-url.ts
|
|
127791
127890
|
var import_os2 = require("os");
|
|
127792
127891
|
function resolveServerUrl({ publicUrl, port }) {
|
|
@@ -132113,7 +132212,7 @@ var REF_PREFIX = "ref: refs/heads/";
|
|
|
132113
132212
|
var MAX_DEPTH = 6;
|
|
132114
132213
|
function readGitBranch(projectPath) {
|
|
132115
132214
|
if (!projectPath) return null;
|
|
132116
|
-
const
|
|
132215
|
+
const log12 = getLogger2();
|
|
132117
132216
|
let dir = projectPath;
|
|
132118
132217
|
let depth = 0;
|
|
132119
132218
|
while (depth < MAX_DEPTH) {
|
|
@@ -132122,11 +132221,11 @@ function readGitBranch(projectPath) {
|
|
|
132122
132221
|
const content = (0, import_fs2.readFileSync)(headPath, "utf-8").trim();
|
|
132123
132222
|
if (content.startsWith(REF_PREFIX)) {
|
|
132124
132223
|
const branch = content.slice(REF_PREFIX.length);
|
|
132125
|
-
|
|
132224
|
+
log12.trace({ projectPath, dir, branch }, "git: branch resolved");
|
|
132126
132225
|
return branch;
|
|
132127
132226
|
}
|
|
132128
132227
|
if (content.length >= 7) {
|
|
132129
|
-
|
|
132228
|
+
log12.trace({ projectPath, dir }, "git: detached HEAD");
|
|
132130
132229
|
return "(detached)";
|
|
132131
132230
|
}
|
|
132132
132231
|
return null;
|
|
@@ -132137,7 +132236,7 @@ function readGitBranch(projectPath) {
|
|
|
132137
132236
|
dir = parent;
|
|
132138
132237
|
depth++;
|
|
132139
132238
|
}
|
|
132140
|
-
|
|
132239
|
+
log12.trace({ projectPath }, "git: no .git found within depth");
|
|
132141
132240
|
return null;
|
|
132142
132241
|
}
|
|
132143
132242
|
function generateMatches(meta3, query) {
|
|
@@ -132430,8 +132529,8 @@ function cleanSystemTags(text) {
|
|
|
132430
132529
|
return text.replace(SYSTEM_TAG_RE, "").replace(/[^\S\n]+/g, " ").replace(/\n{3,}/g, "\n\n").trim();
|
|
132431
132530
|
}
|
|
132432
132531
|
async function parseMeta(filePath, account, tier) {
|
|
132433
|
-
const
|
|
132434
|
-
|
|
132532
|
+
const log12 = getLogger2();
|
|
132533
|
+
log12.trace({ filePath, account, tier: tier.name }, "parseMeta: start");
|
|
132435
132534
|
const state = initialReducerState();
|
|
132436
132535
|
const fileStream = (0, import_fs3.createReadStream)(filePath);
|
|
132437
132536
|
const rl = (0, import_readline.createInterface)({ input: fileStream, crlfDelay: Infinity });
|
|
@@ -132448,22 +132547,22 @@ async function parseMeta(filePath, account, tier) {
|
|
|
132448
132547
|
reduceLine(state, entry, tier);
|
|
132449
132548
|
}
|
|
132450
132549
|
} catch (err) {
|
|
132451
|
-
|
|
132550
|
+
log12.warn({ filePath, err }, "parseMeta: read failed");
|
|
132452
132551
|
return null;
|
|
132453
132552
|
}
|
|
132454
132553
|
if (state.badJsonLines > 0) {
|
|
132455
|
-
|
|
132554
|
+
log12.warn(
|
|
132456
132555
|
{ filePath, badJsonLines: state.badJsonLines },
|
|
132457
132556
|
"parseMeta: skipped malformed JSON lines"
|
|
132458
132557
|
);
|
|
132459
132558
|
}
|
|
132460
132559
|
const meta3 = finalizeMeta(state, filePath, account, tier);
|
|
132461
|
-
if (!meta3)
|
|
132560
|
+
if (!meta3) log12.trace({ filePath }, "parseMeta: no messages");
|
|
132462
132561
|
return meta3;
|
|
132463
132562
|
}
|
|
132464
132563
|
async function parseConversation(filePath, account) {
|
|
132465
|
-
const
|
|
132466
|
-
|
|
132564
|
+
const log12 = getLogger2();
|
|
132565
|
+
log12.trace({ filePath, account }, "parseConversation: start");
|
|
132467
132566
|
const messages = [];
|
|
132468
132567
|
let badJsonLines = 0;
|
|
132469
132568
|
const textParts = [];
|
|
@@ -132496,17 +132595,17 @@ async function parseConversation(filePath, account) {
|
|
|
132496
132595
|
}
|
|
132497
132596
|
}
|
|
132498
132597
|
} catch (err) {
|
|
132499
|
-
|
|
132598
|
+
log12.warn({ filePath, err }, "parseConversation: read failed");
|
|
132500
132599
|
return null;
|
|
132501
132600
|
}
|
|
132502
132601
|
if (badJsonLines > 0) {
|
|
132503
|
-
|
|
132602
|
+
log12.warn({ filePath, badJsonLines }, "parseConversation: skipped malformed JSON lines");
|
|
132504
132603
|
}
|
|
132505
132604
|
if (messages.length === 0) {
|
|
132506
|
-
|
|
132605
|
+
log12.trace({ filePath }, "parseConversation: no messages");
|
|
132507
132606
|
return null;
|
|
132508
132607
|
}
|
|
132509
|
-
|
|
132608
|
+
log12.debug({ filePath, messageCount: messages.length }, "parseConversation: complete");
|
|
132510
132609
|
applyTeamInfo(messages, state);
|
|
132511
132610
|
return {
|
|
132512
132611
|
id: filePath,
|
|
@@ -132771,15 +132870,15 @@ async function detectDefaultProfile() {
|
|
|
132771
132870
|
};
|
|
132772
132871
|
}
|
|
132773
132872
|
async function loadProfiles(configPath) {
|
|
132774
|
-
const
|
|
132873
|
+
const log12 = getLogger2();
|
|
132775
132874
|
try {
|
|
132776
132875
|
const resolved = resolveConfigDir(configPath);
|
|
132777
132876
|
const data = await (0, import_promises4.readFile)((0, import_path5.join)(resolved, PROFILES_FILE), "utf-8");
|
|
132778
132877
|
const profiles = JSON.parse(data);
|
|
132779
|
-
|
|
132878
|
+
log12.debug({ configPath, count: profiles.length }, "profiles: loaded");
|
|
132780
132879
|
return profiles;
|
|
132781
132880
|
} catch (err) {
|
|
132782
|
-
|
|
132881
|
+
log12.debug({ configPath, err }, "profiles: load failed, using default");
|
|
132783
132882
|
const defaultProfile = await detectDefaultProfile();
|
|
132784
132883
|
return [defaultProfile];
|
|
132785
132884
|
}
|
|
@@ -132790,7 +132889,7 @@ function canonicalPath(p2) {
|
|
|
132790
132889
|
var CodexCliProvider = class {
|
|
132791
132890
|
name = CODEX_CLI_PROVIDER;
|
|
132792
132891
|
async discover(roots) {
|
|
132793
|
-
const
|
|
132892
|
+
const log12 = getLogger2();
|
|
132794
132893
|
const results = [];
|
|
132795
132894
|
for (const root of roots) {
|
|
132796
132895
|
let paths;
|
|
@@ -132802,7 +132901,7 @@ var CodexCliProvider = class {
|
|
|
132802
132901
|
unique: true
|
|
132803
132902
|
});
|
|
132804
132903
|
} catch (err) {
|
|
132805
|
-
|
|
132904
|
+
log12.warn({ root, err }, "codex discovery: glob failed");
|
|
132806
132905
|
continue;
|
|
132807
132906
|
}
|
|
132808
132907
|
for (const filePath of paths) {
|
|
@@ -132810,7 +132909,7 @@ var CodexCliProvider = class {
|
|
|
132810
132909
|
const s3 = await (0, import_promises5.stat)(filePath);
|
|
132811
132910
|
if (s3.size > 0) results.push({ filePath: canonicalPath(filePath), account: "codex" });
|
|
132812
132911
|
} catch (err) {
|
|
132813
|
-
|
|
132912
|
+
log12.warn({ filePath, err }, "codex discovery: stat failed");
|
|
132814
132913
|
}
|
|
132815
132914
|
}
|
|
132816
132915
|
}
|
|
@@ -132954,7 +133053,7 @@ function getShortProjectName3(fullPath) {
|
|
|
132954
133053
|
return fullPath.split("/").filter(Boolean).slice(-3).join("/");
|
|
132955
133054
|
}
|
|
132956
133055
|
async function parseCodexConversation(filePath, account) {
|
|
132957
|
-
const
|
|
133056
|
+
const log12 = getLogger2();
|
|
132958
133057
|
const messages = [];
|
|
132959
133058
|
const textParts = [];
|
|
132960
133059
|
let sessionId = "";
|
|
@@ -132990,7 +133089,7 @@ async function parseCodexConversation(filePath, account) {
|
|
|
132990
133089
|
if (role === "user") lastUserText = text;
|
|
132991
133090
|
}
|
|
132992
133091
|
} catch (err) {
|
|
132993
|
-
|
|
133092
|
+
log12.warn({ filePath, err }, "parseCodexConversation: read failed");
|
|
132994
133093
|
return null;
|
|
132995
133094
|
}
|
|
132996
133095
|
if (messages.length === 0) return null;
|
|
@@ -133012,7 +133111,7 @@ async function parseCodexConversation(filePath, account) {
|
|
|
133012
133111
|
var EXCLUDED_SEGMENTS = ["/memory/", "/tool-results/"];
|
|
133013
133112
|
var STAT_CONCURRENCY = 32;
|
|
133014
133113
|
async function discoverJsonlFiles(dirs, onProgress) {
|
|
133015
|
-
const
|
|
133114
|
+
const log12 = getLogger2();
|
|
133016
133115
|
const results = [];
|
|
133017
133116
|
for (const { projectsDir, account } of dirs) {
|
|
133018
133117
|
let filePaths;
|
|
@@ -133023,7 +133122,7 @@ async function discoverJsonlFiles(dirs, onProgress) {
|
|
|
133023
133122
|
dot: false
|
|
133024
133123
|
});
|
|
133025
133124
|
} catch (err) {
|
|
133026
|
-
|
|
133125
|
+
log12.warn({ projectsDir, account, err }, "discovery: glob failed");
|
|
133027
133126
|
continue;
|
|
133028
133127
|
}
|
|
133029
133128
|
const filtered = filePaths.filter((fp) => !EXCLUDED_SEGMENTS.some((seg) => fp.includes(seg)));
|
|
@@ -133038,7 +133137,7 @@ async function discoverJsonlFiles(dirs, onProgress) {
|
|
|
133038
133137
|
const s3 = await (0, import_promises6.stat)(filePath);
|
|
133039
133138
|
return { filePath, size: s3.size };
|
|
133040
133139
|
} catch (err) {
|
|
133041
|
-
|
|
133140
|
+
log12.warn({ filePath, err }, "discovery: stat failed");
|
|
133042
133141
|
return { filePath, size: -1 };
|
|
133043
133142
|
}
|
|
133044
133143
|
})
|
|
@@ -133054,7 +133153,7 @@ async function discoverJsonlFiles(dirs, onProgress) {
|
|
|
133054
133153
|
}
|
|
133055
133154
|
}
|
|
133056
133155
|
}
|
|
133057
|
-
|
|
133156
|
+
log12.debug(
|
|
133058
133157
|
{
|
|
133059
133158
|
projectsDir,
|
|
133060
133159
|
account,
|
|
@@ -133068,7 +133167,7 @@ async function discoverJsonlFiles(dirs, onProgress) {
|
|
|
133068
133167
|
);
|
|
133069
133168
|
onProgress?.(results.length);
|
|
133070
133169
|
}
|
|
133071
|
-
|
|
133170
|
+
log12.debug({ totalFiles: results.length, dirs: dirs.length }, "discovery: complete");
|
|
133072
133171
|
return results;
|
|
133073
133172
|
}
|
|
133074
133173
|
var ThreadbaseProvider = class {
|
|
@@ -133378,7 +133477,7 @@ function classify(filePath, existing) {
|
|
|
133378
133477
|
return { change: "reindex", stat: stat42 };
|
|
133379
133478
|
}
|
|
133380
133479
|
async function parseMetaWithProvider(provider, filePath, account, tier) {
|
|
133381
|
-
const
|
|
133480
|
+
const log12 = getLogger2();
|
|
133382
133481
|
const acc = provider.createEmptyAccumulator();
|
|
133383
133482
|
const rl = (0, import_readline3.createInterface)({
|
|
133384
133483
|
input: (0, import_fs11.createReadStream)(filePath),
|
|
@@ -133396,11 +133495,11 @@ async function parseMetaWithProvider(provider, filePath, account, tier) {
|
|
|
133396
133495
|
try {
|
|
133397
133496
|
provider.reduceEntry(acc, entry, tier);
|
|
133398
133497
|
} catch (err) {
|
|
133399
|
-
|
|
133498
|
+
log12.warn({ filePath, provider: provider.name, err }, "provider reduce threw; line skipped");
|
|
133400
133499
|
}
|
|
133401
133500
|
}
|
|
133402
133501
|
} catch (err) {
|
|
133403
|
-
|
|
133502
|
+
log12.warn({ filePath, provider: provider.name, err }, "provider parse: read failed");
|
|
133404
133503
|
return null;
|
|
133405
133504
|
}
|
|
133406
133505
|
return provider.finalize(acc, filePath, account, tier);
|
|
@@ -133574,8 +133673,8 @@ CREATE INDEX IF NOT EXISTS idx_scanned_dirs_parent_root ON scanned_dirs(parent_r
|
|
|
133574
133673
|
function runMigrations(db) {
|
|
133575
133674
|
const current = db.pragma("user_version", { simple: true });
|
|
133576
133675
|
if (current >= SCHEMA_VERSION) return;
|
|
133577
|
-
const
|
|
133578
|
-
|
|
133676
|
+
const log12 = getLogger2();
|
|
133677
|
+
log12.info({ from: current, to: SCHEMA_VERSION }, "migrations: applying");
|
|
133579
133678
|
if (current >= 1 && current < 2 && tableExists(db, "conversations")) {
|
|
133580
133679
|
for (const [col, ddl] of [
|
|
133581
133680
|
[
|
|
@@ -133617,7 +133716,7 @@ function openDatabase(dbPath) {
|
|
|
133617
133716
|
}
|
|
133618
133717
|
var FULL_RECONCILE_EVERY_N_SCANS = 20;
|
|
133619
133718
|
async function discoverJsonlFilesGated(dirs, files, scannedDirs, options = {}) {
|
|
133620
|
-
const
|
|
133719
|
+
const log12 = getLogger2();
|
|
133621
133720
|
if (options.fullRescan) {
|
|
133622
133721
|
return discoverJsonlFiles(dirs);
|
|
133623
133722
|
}
|
|
@@ -133659,7 +133758,7 @@ async function discoverJsonlFilesGated(dirs, files, scannedDirs, options = {}) {
|
|
|
133659
133758
|
}
|
|
133660
133759
|
}
|
|
133661
133760
|
}
|
|
133662
|
-
|
|
133761
|
+
log12.debug(
|
|
133663
133762
|
{ totalFiles: results.length, dirs: dirs.length },
|
|
133664
133763
|
"dir-watermark: gated discovery complete"
|
|
133665
133764
|
);
|
|
@@ -134165,7 +134264,7 @@ var PersistentEngine = class {
|
|
|
134165
134264
|
// changed since the last index, and upsert their metadata. Returns the number
|
|
134166
134265
|
// of files seen on disk this pass (the scan "scanned" count).
|
|
134167
134266
|
async indexAll(activeProfiles, options) {
|
|
134168
|
-
const
|
|
134267
|
+
const log12 = getLogger2();
|
|
134169
134268
|
const tier = resolveTier(options.tier ?? "standard", options.tiers);
|
|
134170
134269
|
const enabled = options.providers ?? [CLAUDE_CODE_PROVIDER];
|
|
134171
134270
|
const discovered = [];
|
|
@@ -134229,7 +134328,7 @@ var PersistentEngine = class {
|
|
|
134229
134328
|
for (const path2 of this.files.activePathsByAccounts([...coveredAccounts])) {
|
|
134230
134329
|
if (!seen.has(path2)) this.markDeleted(path2);
|
|
134231
134330
|
}
|
|
134232
|
-
|
|
134331
|
+
log12.info({ scanned, indexed: this.conversations.count() }, "persistent: indexAll complete");
|
|
134233
134332
|
return { scanned };
|
|
134234
134333
|
}
|
|
134235
134334
|
// (Re)index a single file. Classifies the change vs. the persisted cursor:
|
|
@@ -134240,7 +134339,7 @@ var PersistentEngine = class {
|
|
|
134240
134339
|
// alongside the meta so callers (refreshFile) can keep, extend, or evict
|
|
134241
134340
|
// their own per-file caches without re-stat'ing the file (racy) themselves.
|
|
134242
134341
|
async indexFile(rawFilePath, account, tierName, customTiers, resolveGitBranch, force = false, provider) {
|
|
134243
|
-
const
|
|
134342
|
+
const log12 = getLogger2();
|
|
134244
134343
|
const tier = resolveTier(tierName, customTiers);
|
|
134245
134344
|
const filePath = canonicalPath(rawFilePath);
|
|
134246
134345
|
const existing = this.files.getByPath(filePath);
|
|
@@ -134271,7 +134370,7 @@ var PersistentEngine = class {
|
|
|
134271
134370
|
try {
|
|
134272
134371
|
result = await tailReduce(filePath, startOffset, startLine, state, tier);
|
|
134273
134372
|
} catch (err) {
|
|
134274
|
-
|
|
134373
|
+
log12.warn({ filePath, err }, "persistent: tail read failed");
|
|
134275
134374
|
return { meta: null, change };
|
|
134276
134375
|
}
|
|
134277
134376
|
const meta3 = finalizeMeta(state, filePath, account, tier);
|
|
@@ -134314,7 +134413,7 @@ var PersistentEngine = class {
|
|
|
134314
134413
|
)
|
|
134315
134414
|
);
|
|
134316
134415
|
}
|
|
134317
|
-
|
|
134416
|
+
log12.debug(
|
|
134318
134417
|
{ filePath, change, bytesRead: result.newOffset - startOffset, msgs: meta3.messageCount },
|
|
134319
134418
|
"persistent: indexed file"
|
|
134320
134419
|
);
|
|
@@ -134326,7 +134425,7 @@ var PersistentEngine = class {
|
|
|
134326
134425
|
// so the next pass classifies an unchanged file as "unchanged" and skips it;
|
|
134327
134426
|
// any change reparses from 0 again. No reducer_state is persisted.
|
|
134328
134427
|
async indexFileWithProvider(provider, filePath, account, tier, stat42, resolveGitBranch) {
|
|
134329
|
-
const
|
|
134428
|
+
const log12 = getLogger2();
|
|
134330
134429
|
const meta3 = await parseMetaWithProvider(provider, filePath, account, tier);
|
|
134331
134430
|
if (!meta3) {
|
|
134332
134431
|
this.markDeleted(filePath);
|
|
@@ -134354,7 +134453,7 @@ var PersistentEngine = class {
|
|
|
134354
134453
|
});
|
|
134355
134454
|
});
|
|
134356
134455
|
upsert();
|
|
134357
|
-
|
|
134456
|
+
log12.debug(
|
|
134358
134457
|
{ filePath, provider: provider.name, msgs: meta3.messageCount },
|
|
134359
134458
|
"persistent: indexed provider file"
|
|
134360
134459
|
);
|
|
@@ -134468,7 +134567,7 @@ var FileWatcher = class {
|
|
|
134468
134567
|
// Resolves once every underlying chokidar watcher has finished its initial
|
|
134469
134568
|
// scan, so the caller knows subsequent FS changes will be observed.
|
|
134470
134569
|
async start() {
|
|
134471
|
-
const
|
|
134570
|
+
const log12 = getLogger2();
|
|
134472
134571
|
const ready = [];
|
|
134473
134572
|
for (const profile of this.profiles) {
|
|
134474
134573
|
const dir = getProjectsDir(profile);
|
|
@@ -134480,7 +134579,7 @@ var FileWatcher = class {
|
|
|
134480
134579
|
watcher.on("add", (p2) => this.dispatch(p2, profile.id, "add")).on("change", (p2) => this.dispatch(p2, profile.id, "change")).on("unlink", (p2) => this.dispatch(p2, profile.id, "unlink"));
|
|
134481
134580
|
ready.push(new Promise((resolve4) => watcher.once("ready", () => resolve4())));
|
|
134482
134581
|
this.watchers.push(watcher);
|
|
134483
|
-
|
|
134582
|
+
log12.debug({ dir, account: profile.id }, "watcher: watching");
|
|
134484
134583
|
}
|
|
134485
134584
|
await Promise.all(ready);
|
|
134486
134585
|
}
|
|
@@ -134527,14 +134626,14 @@ var IndexQueue = class {
|
|
|
134527
134626
|
async drain() {
|
|
134528
134627
|
if (this.running) return;
|
|
134529
134628
|
this.running = true;
|
|
134530
|
-
const
|
|
134629
|
+
const log12 = getLogger2();
|
|
134531
134630
|
while (this.pending.size > 0) {
|
|
134532
134631
|
const [path2, job] = this.pending.entries().next().value;
|
|
134533
134632
|
this.pending.delete(path2);
|
|
134534
134633
|
try {
|
|
134535
134634
|
await this.process(job);
|
|
134536
134635
|
} catch (err) {
|
|
134537
|
-
|
|
134636
|
+
log12.warn({ filePath: job.filePath, err }, "index-queue: job failed");
|
|
134538
134637
|
}
|
|
134539
134638
|
}
|
|
134540
134639
|
this.running = false;
|
|
@@ -134643,13 +134742,13 @@ var ConversationScanner = class {
|
|
|
134643
134742
|
// active metas and run the identical filter/sort/view/paginate pipeline as
|
|
134644
134743
|
// the in-memory path — guaranteeing an identical ScanResult shape.
|
|
134645
134744
|
async scanPersistent(activeProfiles, options) {
|
|
134646
|
-
const
|
|
134745
|
+
const log12 = getLogger2();
|
|
134647
134746
|
const startedAt = Date.now();
|
|
134648
134747
|
const engine = this.engine();
|
|
134649
134748
|
const { scanned } = await engine.indexAll(activeProfiles, options);
|
|
134650
134749
|
const allMetas = engine.allActive();
|
|
134651
134750
|
const { conversations, total } = this.finalize(allMetas, options);
|
|
134652
|
-
|
|
134751
|
+
log12.info(
|
|
134653
134752
|
{ scanned, kept: allMetas.length, filteredTotal: total, elapsedMs: Date.now() - startedAt },
|
|
134654
134753
|
"scan: complete (persistent)"
|
|
134655
134754
|
);
|
|
@@ -134676,10 +134775,10 @@ var ConversationScanner = class {
|
|
|
134676
134775
|
return { conversations, total };
|
|
134677
134776
|
}
|
|
134678
134777
|
async scanInMemory(activeProfiles, options) {
|
|
134679
|
-
const
|
|
134778
|
+
const log12 = getLogger2();
|
|
134680
134779
|
const startedAt = Date.now();
|
|
134681
134780
|
const tier = this.lastTier;
|
|
134682
|
-
|
|
134781
|
+
log12.info(
|
|
134683
134782
|
{
|
|
134684
134783
|
activeProfiles: activeProfiles.length,
|
|
134685
134784
|
tier: tier.name,
|
|
@@ -134733,7 +134832,7 @@ var ConversationScanner = class {
|
|
|
134733
134832
|
return meta3;
|
|
134734
134833
|
} catch (err) {
|
|
134735
134834
|
parseFailures++;
|
|
134736
|
-
|
|
134835
|
+
log12.warn({ filePath, account, provider: provider.name, err }, "scan: parse threw");
|
|
134737
134836
|
return null;
|
|
134738
134837
|
}
|
|
134739
134838
|
})
|
|
@@ -134753,12 +134852,12 @@ var ConversationScanner = class {
|
|
|
134753
134852
|
options.onBatch?.(batchMetas);
|
|
134754
134853
|
}
|
|
134755
134854
|
scanned += batch.length;
|
|
134756
|
-
|
|
134855
|
+
log12.debug({ scanned, totalFiles, batchKept: batchMetas.length }, "scan: batch complete");
|
|
134757
134856
|
options.onProgress?.(scanned, totalFiles);
|
|
134758
134857
|
}
|
|
134759
134858
|
const { conversations, total } = this.finalize(allMetas, options);
|
|
134760
134859
|
const elapsedMs = Date.now() - startedAt;
|
|
134761
|
-
|
|
134860
|
+
log12.info(
|
|
134762
134861
|
{
|
|
134763
134862
|
totalFiles,
|
|
134764
134863
|
scanned,
|
|
@@ -134772,13 +134871,13 @@ var ConversationScanner = class {
|
|
|
134772
134871
|
return { conversations, total, scanned };
|
|
134773
134872
|
}
|
|
134774
134873
|
async search(query, options = {}) {
|
|
134775
|
-
const
|
|
134776
|
-
|
|
134874
|
+
const log12 = getLogger2();
|
|
134875
|
+
log12.debug({ query, indexSize: this.indexer.getDocumentCount() }, "search: start");
|
|
134777
134876
|
let results;
|
|
134778
134877
|
if (this.persistent) {
|
|
134779
134878
|
const engine = this.engine();
|
|
134780
134879
|
if (engine.conversations.count() === 0) {
|
|
134781
|
-
|
|
134880
|
+
log12.debug("search: persistent index empty, triggering scan");
|
|
134782
134881
|
const profiles = await this.resolveProfiles(options.profiles);
|
|
134783
134882
|
const activeProfiles = profiles.filter((p2) => p2.enabled && p2.scanHistory !== false);
|
|
134784
134883
|
await engine.indexAll(activeProfiles, { ...options, limit: void 0, offset: void 0 });
|
|
@@ -134791,7 +134890,7 @@ var ConversationScanner = class {
|
|
|
134791
134890
|
}));
|
|
134792
134891
|
} else {
|
|
134793
134892
|
if (this.indexer.getDocumentCount() === 0) {
|
|
134794
|
-
|
|
134893
|
+
log12.debug("search: index empty, triggering scan");
|
|
134795
134894
|
await this.scan({ ...options, limit: void 0, offset: void 0 });
|
|
134796
134895
|
}
|
|
134797
134896
|
results = this.indexer.search(query, {
|
|
@@ -134834,23 +134933,23 @@ var ConversationScanner = class {
|
|
|
134834
134933
|
const limit = options.limit ?? 50;
|
|
134835
134934
|
const offset = options.offset ?? 0;
|
|
134836
134935
|
const sliced = results.slice(offset, offset + limit);
|
|
134837
|
-
|
|
134936
|
+
log12.debug({ query, matched: results.length, returned: sliced.length }, "search: complete");
|
|
134838
134937
|
return sliced;
|
|
134839
134938
|
}
|
|
134840
134939
|
async getConversation(id, _options) {
|
|
134841
|
-
const
|
|
134940
|
+
const log12 = getLogger2();
|
|
134842
134941
|
const cid = canonicalPath(id);
|
|
134843
134942
|
const cached3 = this.conversationLRU.get(cid);
|
|
134844
134943
|
if (cached3) {
|
|
134845
|
-
|
|
134944
|
+
log12.debug({ id }, "getConversation: cache hit");
|
|
134846
134945
|
return cached3.conversation;
|
|
134847
134946
|
}
|
|
134848
134947
|
const meta3 = this.persistent ? this.engine().getByIdOrSession(cid) : this.metadataCache.get(cid) ?? this.resolveSessionId(cid);
|
|
134849
134948
|
if (!meta3) {
|
|
134850
|
-
|
|
134949
|
+
log12.debug({ id }, "getConversation: not found in metadata");
|
|
134851
134950
|
return null;
|
|
134852
134951
|
}
|
|
134853
|
-
|
|
134952
|
+
log12.debug({ id, filePath: meta3.filePath }, "getConversation: cache miss, parsing");
|
|
134854
134953
|
try {
|
|
134855
134954
|
if (this.persistent && meta3.provider !== CODEX_CLI_PROVIDER) {
|
|
134856
134955
|
const parsed = await parseConversationResumable(meta3.filePath, meta3.account);
|
|
@@ -134863,7 +134962,7 @@ var ConversationScanner = class {
|
|
|
134863
134962
|
}
|
|
134864
134963
|
return conversation;
|
|
134865
134964
|
} catch (err) {
|
|
134866
|
-
|
|
134965
|
+
log12.warn({ id, filePath: meta3.filePath, err }, "getConversation: parse failed");
|
|
134867
134966
|
return null;
|
|
134868
134967
|
}
|
|
134869
134968
|
}
|
|
@@ -134949,7 +135048,7 @@ var ConversationScanner = class {
|
|
|
134949
135048
|
return refresh;
|
|
134950
135049
|
}
|
|
134951
135050
|
async doRefreshFile(filePath, account) {
|
|
134952
|
-
const
|
|
135051
|
+
const log12 = getLogger2();
|
|
134953
135052
|
if (this.persistent) {
|
|
134954
135053
|
const engine = this.engine();
|
|
134955
135054
|
const previous2 = engine.getByIdOrSession(filePath);
|
|
@@ -134976,7 +135075,7 @@ var ConversationScanner = class {
|
|
|
134976
135075
|
} else if (change === "appended") {
|
|
134977
135076
|
await this.extendCachedConversations(cacheKeys, filePath, meta22.account);
|
|
134978
135077
|
}
|
|
134979
|
-
|
|
135078
|
+
log12.debug({ filePath, change, kept: !!meta22 }, "refreshFile: updated persistent index");
|
|
134980
135079
|
return meta22;
|
|
134981
135080
|
}
|
|
134982
135081
|
const previous = this.metadataCache.get(filePath);
|
|
@@ -134985,7 +135084,7 @@ var ConversationScanner = class {
|
|
|
134985
135084
|
try {
|
|
134986
135085
|
meta3 = await parseMeta(filePath, resolvedAccount, this.lastTier);
|
|
134987
135086
|
} catch (err) {
|
|
134988
|
-
|
|
135087
|
+
log12.warn({ filePath, err }, "refreshFile: parseMeta threw");
|
|
134989
135088
|
meta3 = null;
|
|
134990
135089
|
}
|
|
134991
135090
|
const evict = (m2) => {
|
|
@@ -135001,7 +135100,7 @@ var ConversationScanner = class {
|
|
|
135001
135100
|
this.removeFromSessionIndex(previous);
|
|
135002
135101
|
this.indexer.removeDocument(previous.id);
|
|
135003
135102
|
}
|
|
135004
|
-
|
|
135103
|
+
log12.debug({ filePath }, "refreshFile: dropped (no parseable messages)");
|
|
135005
135104
|
return null;
|
|
135006
135105
|
}
|
|
135007
135106
|
meta3.gitBranch = readGitBranch(meta3.projectPath);
|
|
@@ -135014,7 +135113,7 @@ var ConversationScanner = class {
|
|
|
135014
135113
|
} else {
|
|
135015
135114
|
this.indexer.addDocument(meta3);
|
|
135016
135115
|
}
|
|
135017
|
-
|
|
135116
|
+
log12.debug(
|
|
135018
135117
|
{ filePath, messageCount: meta3.messageCount },
|
|
135019
135118
|
"refreshFile: updated in-memory indexes"
|
|
135020
135119
|
);
|
|
@@ -138222,6 +138321,7 @@ function readRawBody2(req) {
|
|
|
138222
138321
|
var createConfigRoutes = (deps) => {
|
|
138223
138322
|
const app = new Hono2();
|
|
138224
138323
|
app.get("/claude-flags", (c) => c.json(deps.claudeFlagsConfig()));
|
|
138324
|
+
app.get("/feature-flags", (c) => c.json(deps.featureFlagsConfig()));
|
|
138225
138325
|
app.put("/claude-flags", async (c) => {
|
|
138226
138326
|
if (deps.localNoAuth) {
|
|
138227
138327
|
return c.json({ error: "claude flag changes are disabled while localNoAuth is active" }, 403);
|
|
@@ -138475,7 +138575,266 @@ function createLogsRoutes() {
|
|
|
138475
138575
|
var import_node_child_process2 = require("child_process");
|
|
138476
138576
|
var import_node_crypto = require("crypto");
|
|
138477
138577
|
var import_os5 = require("os");
|
|
138578
|
+
|
|
138579
|
+
// src/db/repositories/push.repository.ts
|
|
138580
|
+
var FAILURE_STREAK_LIMIT = 5;
|
|
138581
|
+
var PUSH_TOKEN_KINDS = ["expo", "liveactivity_start", "liveactivity_update"];
|
|
138582
|
+
var DEFAULT_PUSH_TOKEN_KIND = "expo";
|
|
138583
|
+
function isPushTokenKind(value) {
|
|
138584
|
+
return typeof value === "string" && PUSH_TOKEN_KINDS.includes(value);
|
|
138585
|
+
}
|
|
138586
|
+
function tokenState(row, now = Date.now()) {
|
|
138587
|
+
if (row.revoked_at != null) return "revoked";
|
|
138588
|
+
if (row.expires_at != null && row.expires_at <= now) return "expired";
|
|
138589
|
+
if (row.failure_streak >= FAILURE_STREAK_LIMIT) return "dead";
|
|
138590
|
+
if (row.failure_streak > 0) return "failing";
|
|
138591
|
+
if (row.last_success_at == null) return "never-delivered";
|
|
138592
|
+
return "healthy";
|
|
138593
|
+
}
|
|
138594
|
+
function toHealth(row, now = Date.now()) {
|
|
138595
|
+
return {
|
|
138596
|
+
platform: row.platform,
|
|
138597
|
+
deviceId: row.device_id,
|
|
138598
|
+
registeredAt: row.registered_at,
|
|
138599
|
+
lastSuccessAt: row.last_success_at,
|
|
138600
|
+
lastFailureAt: row.last_failure_at,
|
|
138601
|
+
lastFailureCode: row.last_failure_code,
|
|
138602
|
+
failureStreak: row.failure_streak,
|
|
138603
|
+
revokedAt: row.revoked_at,
|
|
138604
|
+
state: tokenState(row, now),
|
|
138605
|
+
kind: row.kind,
|
|
138606
|
+
activityId: row.activity_id,
|
|
138607
|
+
sessionId: row.session_id,
|
|
138608
|
+
expiresAt: row.expires_at
|
|
138609
|
+
};
|
|
138610
|
+
}
|
|
138611
|
+
var PushRepository = class {
|
|
138612
|
+
upsertStmt;
|
|
138613
|
+
getStmt;
|
|
138614
|
+
listActiveStmt;
|
|
138615
|
+
listAllStmt;
|
|
138616
|
+
successStmt;
|
|
138617
|
+
failureStmt;
|
|
138618
|
+
revokeStmt;
|
|
138619
|
+
claimEventStmt;
|
|
138620
|
+
markDeliveredStmt;
|
|
138621
|
+
listByKindSessionStmt;
|
|
138622
|
+
listByKindStmt;
|
|
138623
|
+
listRenewableStmt;
|
|
138624
|
+
claimRenewalStmt;
|
|
138625
|
+
expireStmt;
|
|
138626
|
+
expireSessionActivitiesStmt;
|
|
138627
|
+
constructor(db) {
|
|
138628
|
+
this.upsertStmt = db.prepare(`
|
|
138629
|
+
INSERT INTO push_tokens (
|
|
138630
|
+
token, platform, device_id, registered_at,
|
|
138631
|
+
kind, activity_id, session_id, expires_at, stale_date, started_at
|
|
138632
|
+
)
|
|
138633
|
+
VALUES (
|
|
138634
|
+
@token, @platform, @device_id, @registered_at,
|
|
138635
|
+
@kind, @activity_id, @session_id, @expires_at, @stale_date, @started_at
|
|
138636
|
+
)
|
|
138637
|
+
ON CONFLICT(token) DO UPDATE SET
|
|
138638
|
+
platform = excluded.platform,
|
|
138639
|
+
device_id = COALESCE(excluded.device_id, push_tokens.device_id),
|
|
138640
|
+
registered_at = excluded.registered_at,
|
|
138641
|
+
kind = excluded.kind,
|
|
138642
|
+
activity_id = COALESCE(excluded.activity_id, push_tokens.activity_id),
|
|
138643
|
+
session_id = COALESCE(excluded.session_id, push_tokens.session_id),
|
|
138644
|
+
expires_at = excluded.expires_at,
|
|
138645
|
+
stale_date = excluded.stale_date,
|
|
138646
|
+
-- Preserve the ORIGINAL start across a re-registration. iOS renders its
|
|
138647
|
+
-- own ticking timer from started_at, so overwriting it with a fresh
|
|
138648
|
+
-- value visibly resets the user's elapsed time to zero.
|
|
138649
|
+
started_at = COALESCE(push_tokens.started_at, excluded.started_at),
|
|
138650
|
+
-- A fresh registration clears prior failure state and any revocation:
|
|
138651
|
+
-- the client is telling us this token is live again. renewed_at clears
|
|
138652
|
+
-- too \u2014 this is a new activity generation, so it is renewable again.
|
|
138653
|
+
failure_streak = 0,
|
|
138654
|
+
last_failure_at = NULL,
|
|
138655
|
+
last_failure_code = NULL,
|
|
138656
|
+
revoked_at = NULL,
|
|
138657
|
+
renewed_at = NULL
|
|
138658
|
+
`);
|
|
138659
|
+
this.getStmt = db.prepare("SELECT * FROM push_tokens WHERE token = ?");
|
|
138660
|
+
this.listActiveStmt = db.prepare(`
|
|
138661
|
+
SELECT * FROM push_tokens
|
|
138662
|
+
WHERE revoked_at IS NULL AND failure_streak < ${FAILURE_STREAK_LIMIT}
|
|
138663
|
+
AND kind = 'expo'
|
|
138664
|
+
ORDER BY registered_at ASC
|
|
138665
|
+
`);
|
|
138666
|
+
this.listAllStmt = db.prepare("SELECT * FROM push_tokens ORDER BY registered_at ASC");
|
|
138667
|
+
this.successStmt = db.prepare(`
|
|
138668
|
+
UPDATE push_tokens
|
|
138669
|
+
SET last_success_at = @at, failure_streak = 0,
|
|
138670
|
+
last_failure_code = NULL
|
|
138671
|
+
WHERE token = @token
|
|
138672
|
+
`);
|
|
138673
|
+
this.failureStmt = db.prepare(`
|
|
138674
|
+
UPDATE push_tokens
|
|
138675
|
+
SET last_failure_at = @at, last_failure_code = @code,
|
|
138676
|
+
failure_streak = failure_streak + 1
|
|
138677
|
+
WHERE token = @token
|
|
138678
|
+
`);
|
|
138679
|
+
this.revokeStmt = db.prepare("UPDATE push_tokens SET revoked_at = ? WHERE token = ?");
|
|
138680
|
+
this.listByKindSessionStmt = db.prepare(`
|
|
138681
|
+
SELECT * FROM push_tokens
|
|
138682
|
+
WHERE kind = @kind AND session_id = @session_id
|
|
138683
|
+
AND revoked_at IS NULL AND failure_streak < ${FAILURE_STREAK_LIMIT}
|
|
138684
|
+
AND (expires_at IS NULL OR expires_at > @now)
|
|
138685
|
+
ORDER BY registered_at ASC
|
|
138686
|
+
`);
|
|
138687
|
+
this.listByKindStmt = db.prepare(`
|
|
138688
|
+
SELECT * FROM push_tokens
|
|
138689
|
+
WHERE kind = @kind
|
|
138690
|
+
AND revoked_at IS NULL AND failure_streak < ${FAILURE_STREAK_LIMIT}
|
|
138691
|
+
AND (expires_at IS NULL OR expires_at > @now)
|
|
138692
|
+
ORDER BY registered_at ASC
|
|
138693
|
+
`);
|
|
138694
|
+
this.listRenewableStmt = db.prepare(`
|
|
138695
|
+
SELECT * FROM push_tokens
|
|
138696
|
+
WHERE kind = 'liveactivity_update'
|
|
138697
|
+
AND stale_date IS NOT NULL AND renewed_at IS NULL
|
|
138698
|
+
AND revoked_at IS NULL AND failure_streak < ${FAILURE_STREAK_LIMIT}
|
|
138699
|
+
ORDER BY stale_date ASC
|
|
138700
|
+
`);
|
|
138701
|
+
this.claimRenewalStmt = db.prepare(`
|
|
138702
|
+
UPDATE push_tokens SET renewed_at = @at
|
|
138703
|
+
WHERE token = @token AND renewed_at IS NULL
|
|
138704
|
+
`);
|
|
138705
|
+
this.expireStmt = db.prepare("UPDATE push_tokens SET expires_at = ? WHERE token = ?");
|
|
138706
|
+
this.expireSessionActivitiesStmt = db.prepare(`
|
|
138707
|
+
UPDATE push_tokens SET expires_at = @at
|
|
138708
|
+
WHERE session_id = @session_id AND kind = 'liveactivity_update'
|
|
138709
|
+
AND (expires_at IS NULL OR expires_at > @at)
|
|
138710
|
+
`);
|
|
138711
|
+
this.claimEventStmt = db.prepare(`
|
|
138712
|
+
INSERT OR IGNORE INTO push_events (event_id, session_id, created_at)
|
|
138713
|
+
VALUES (@event_id, @session_id, @created_at)
|
|
138714
|
+
`);
|
|
138715
|
+
this.markDeliveredStmt = db.prepare(
|
|
138716
|
+
"UPDATE push_events SET delivered_at = ? WHERE event_id = ?"
|
|
138717
|
+
);
|
|
138718
|
+
}
|
|
138719
|
+
/**
|
|
138720
|
+
* Register or refresh a token.
|
|
138721
|
+
*
|
|
138722
|
+
* `kind` defaults to Expo so a released client posting `{ token, platform }`
|
|
138723
|
+
* keeps working — tb-mobile cannot be force-updated, and every client
|
|
138724
|
+
* predating Live Activities is registering an Expo relay token.
|
|
138725
|
+
*
|
|
138726
|
+
* Several rows per device is normal and intended: a device runs one activity
|
|
138727
|
+
* per live session, each with its own update token. The token itself is the
|
|
138728
|
+
* primary key, so distinct activities never collide.
|
|
138729
|
+
*/
|
|
138730
|
+
register(args) {
|
|
138731
|
+
this.upsertStmt.run({
|
|
138732
|
+
token: args.token,
|
|
138733
|
+
platform: args.platform,
|
|
138734
|
+
device_id: args.deviceId ?? null,
|
|
138735
|
+
registered_at: args.now ?? Date.now(),
|
|
138736
|
+
kind: args.kind ?? DEFAULT_PUSH_TOKEN_KIND,
|
|
138737
|
+
activity_id: args.activityId ?? null,
|
|
138738
|
+
session_id: args.sessionId ?? null,
|
|
138739
|
+
expires_at: args.expiresAt ?? null,
|
|
138740
|
+
stale_date: args.staleDate ?? null,
|
|
138741
|
+
started_at: args.startedAt ?? null
|
|
138742
|
+
});
|
|
138743
|
+
}
|
|
138744
|
+
get(token) {
|
|
138745
|
+
return this.getStmt.get(token) ?? null;
|
|
138746
|
+
}
|
|
138747
|
+
/**
|
|
138748
|
+
* Expo tokens eligible for delivery — not revoked, not past the failure limit.
|
|
138749
|
+
*
|
|
138750
|
+
* Deliberately Expo-only. ActivityKit tokens go over direct APNs with a
|
|
138751
|
+
* different topic and are rejected by Expo's relay, so the ordinary
|
|
138752
|
+
* notification fan-out must not see them.
|
|
138753
|
+
*/
|
|
138754
|
+
listDeliverable() {
|
|
138755
|
+
return this.listActiveStmt.all();
|
|
138756
|
+
}
|
|
138757
|
+
/** Live-activity tokens for one session, eligible for delivery. */
|
|
138758
|
+
listForSession(kind, sessionId, now = Date.now()) {
|
|
138759
|
+
return this.listByKindSessionStmt.all({
|
|
138760
|
+
kind,
|
|
138761
|
+
session_id: sessionId,
|
|
138762
|
+
now
|
|
138763
|
+
});
|
|
138764
|
+
}
|
|
138765
|
+
/**
|
|
138766
|
+
* Every deliverable token of one kind.
|
|
138767
|
+
*
|
|
138768
|
+
* Used for push-to-start, which is app-wide rather than session-scoped: the
|
|
138769
|
+
* activity does not exist yet, so there is no per-activity token to look up.
|
|
138770
|
+
*/
|
|
138771
|
+
listByKind(kind, now = Date.now()) {
|
|
138772
|
+
return this.listByKindStmt.all({ kind, now });
|
|
138773
|
+
}
|
|
138774
|
+
/** Unrenewed activities with a renewal deadline, soonest first. */
|
|
138775
|
+
listRenewable() {
|
|
138776
|
+
return this.listRenewableStmt.all();
|
|
138777
|
+
}
|
|
138778
|
+
/**
|
|
138779
|
+
* Claim a row for renewal.
|
|
138780
|
+
*
|
|
138781
|
+
* Returns true exactly once per row. A restart re-arms timers from the
|
|
138782
|
+
* persisted deadline, so the same renewal can be attempted twice; the loser
|
|
138783
|
+
* gets false and must not send. Doing this as a conditional UPDATE rather
|
|
138784
|
+
* than read-then-write avoids the race where both attempts observe
|
|
138785
|
+
* "not yet renewed".
|
|
138786
|
+
*/
|
|
138787
|
+
claimRenewal(token, now = Date.now()) {
|
|
138788
|
+
return this.claimRenewalStmt.run({ token, at: now }).changes > 0;
|
|
138789
|
+
}
|
|
138790
|
+
/** Mark one token expired, so it stops being a delivery target. */
|
|
138791
|
+
expire(token, now = Date.now()) {
|
|
138792
|
+
this.expireStmt.run(now, token);
|
|
138793
|
+
}
|
|
138794
|
+
/**
|
|
138795
|
+
* Expire every live activity for a session.
|
|
138796
|
+
*
|
|
138797
|
+
* Called when the session ends. Without this, a per-activity token outlives
|
|
138798
|
+
* its session and a later renewal sweep would resurrect an activity for a
|
|
138799
|
+
* session that is already gone.
|
|
138800
|
+
*/
|
|
138801
|
+
expireSessionActivities(sessionId, now = Date.now()) {
|
|
138802
|
+
this.expireSessionActivitiesStmt.run({ session_id: sessionId, at: now });
|
|
138803
|
+
}
|
|
138804
|
+
/** Every token, including dead and revoked ones, for the health report. */
|
|
138805
|
+
listHealth(now = Date.now()) {
|
|
138806
|
+
return this.listAllStmt.all().map((r) => toHealth(r, now));
|
|
138807
|
+
}
|
|
138808
|
+
recordSuccess(token, now = Date.now()) {
|
|
138809
|
+
this.successStmt.run({ token, at: now });
|
|
138810
|
+
}
|
|
138811
|
+
recordFailure(token, code, now = Date.now()) {
|
|
138812
|
+
this.failureStmt.run({ token, at: now, code });
|
|
138813
|
+
}
|
|
138814
|
+
revoke(token, now = Date.now()) {
|
|
138815
|
+
return this.revokeStmt.run(now, token).changes > 0;
|
|
138816
|
+
}
|
|
138817
|
+
/**
|
|
138818
|
+
* Claim an event id for delivery.
|
|
138819
|
+
*
|
|
138820
|
+
* Returns true exactly once per event id. A retry, a reconnect
|
|
138821
|
+
* reconciliation, or two triggers firing for the same underlying event all
|
|
138822
|
+
* get false and must not notify — the user should never be told twice about
|
|
138823
|
+
* one thing.
|
|
138824
|
+
*/
|
|
138825
|
+
claimEvent(eventId, sessionId, now = Date.now()) {
|
|
138826
|
+
return this.claimEventStmt.run({ event_id: eventId, session_id: sessionId, created_at: now }).changes > 0;
|
|
138827
|
+
}
|
|
138828
|
+
markDelivered(eventId, now = Date.now()) {
|
|
138829
|
+
this.markDeliveredStmt.run(now, eventId);
|
|
138830
|
+
}
|
|
138831
|
+
};
|
|
138832
|
+
|
|
138833
|
+
// src/api/routes/misc.routes.ts
|
|
138478
138834
|
init_logger();
|
|
138835
|
+
function numberOrNull(value) {
|
|
138836
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
138837
|
+
}
|
|
138479
138838
|
function readJsonBody(req) {
|
|
138480
138839
|
return new Promise((resolve4, reject) => {
|
|
138481
138840
|
const chunks = [];
|
|
@@ -138522,7 +138881,11 @@ var createMiscRoutes = (deps) => {
|
|
|
138522
138881
|
// Capability flag: this server serves /api/config/claude-flags. Additive —
|
|
138523
138882
|
// older clients ignore it, and clients talking to an older server see it
|
|
138524
138883
|
// absent and hide the UI rather than 404ing.
|
|
138525
|
-
claudeFlags: true
|
|
138884
|
+
claudeFlags: true,
|
|
138885
|
+
// Same contract: this server serves GET /api/config/feature-flags. Lives
|
|
138886
|
+
// here rather than behind /api/config (admin-only) so a read-only client
|
|
138887
|
+
// still learns the server supports flags even if it can't read values.
|
|
138888
|
+
featureFlags: true
|
|
138526
138889
|
});
|
|
138527
138890
|
});
|
|
138528
138891
|
app.get("/api/profiles", (c) => c.json([]));
|
|
@@ -138549,6 +138912,22 @@ var createMiscRoutes = (deps) => {
|
|
|
138549
138912
|
if (platform3 !== "ios" && platform3 !== "android") {
|
|
138550
138913
|
return c.json({ error: "platform must be 'ios' or 'android'" }, 400);
|
|
138551
138914
|
}
|
|
138915
|
+
const kind = body?.kind === void 0 ? DEFAULT_PUSH_TOKEN_KIND : body.kind;
|
|
138916
|
+
if (!isPushTokenKind(kind)) {
|
|
138917
|
+
return c.json(
|
|
138918
|
+
{ error: `kind must be one of ${PUSH_TOKEN_KINDS.join(", ")}`, code: "INVALID_KIND" },
|
|
138919
|
+
400
|
|
138920
|
+
);
|
|
138921
|
+
}
|
|
138922
|
+
if (kind === "liveactivity_update" && typeof body?.activityId !== "string") {
|
|
138923
|
+
return c.json(
|
|
138924
|
+
{
|
|
138925
|
+
error: "activityId is required for kind 'liveactivity_update'",
|
|
138926
|
+
code: "MISSING_ACTIVITY"
|
|
138927
|
+
},
|
|
138928
|
+
400
|
|
138929
|
+
);
|
|
138930
|
+
}
|
|
138552
138931
|
const repo = deps.pushRepo();
|
|
138553
138932
|
if (!repo) {
|
|
138554
138933
|
return c.json({ error: "Push registration is unavailable", code: "STORE_UNAVAILABLE" }, 503);
|
|
@@ -138556,7 +138935,13 @@ var createMiscRoutes = (deps) => {
|
|
|
138556
138935
|
repo.register({
|
|
138557
138936
|
token,
|
|
138558
138937
|
platform: platform3,
|
|
138559
|
-
deviceId: typeof body?.deviceId === "string" ? body.deviceId : null
|
|
138938
|
+
deviceId: typeof body?.deviceId === "string" ? body.deviceId : null,
|
|
138939
|
+
kind,
|
|
138940
|
+
activityId: typeof body?.activityId === "string" ? body.activityId : null,
|
|
138941
|
+
sessionId: typeof body?.sessionId === "string" ? body.sessionId : null,
|
|
138942
|
+
expiresAt: numberOrNull(body?.expiresAt),
|
|
138943
|
+
staleDate: numberOrNull(body?.staleDate),
|
|
138944
|
+
startedAt: numberOrNull(body?.startedAt)
|
|
138560
138945
|
});
|
|
138561
138946
|
return c.json({ ok: true });
|
|
138562
138947
|
});
|
|
@@ -141004,125 +141389,6 @@ function deriveNameFromPath(path2) {
|
|
|
141004
141389
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
141005
141390
|
}
|
|
141006
141391
|
|
|
141007
|
-
// src/db/repositories/push.repository.ts
|
|
141008
|
-
var FAILURE_STREAK_LIMIT = 5;
|
|
141009
|
-
function tokenState(row) {
|
|
141010
|
-
if (row.revoked_at != null) return "revoked";
|
|
141011
|
-
if (row.failure_streak >= FAILURE_STREAK_LIMIT) return "dead";
|
|
141012
|
-
if (row.failure_streak > 0) return "failing";
|
|
141013
|
-
if (row.last_success_at == null) return "never-delivered";
|
|
141014
|
-
return "healthy";
|
|
141015
|
-
}
|
|
141016
|
-
function toHealth(row) {
|
|
141017
|
-
return {
|
|
141018
|
-
platform: row.platform,
|
|
141019
|
-
deviceId: row.device_id,
|
|
141020
|
-
registeredAt: row.registered_at,
|
|
141021
|
-
lastSuccessAt: row.last_success_at,
|
|
141022
|
-
lastFailureAt: row.last_failure_at,
|
|
141023
|
-
lastFailureCode: row.last_failure_code,
|
|
141024
|
-
failureStreak: row.failure_streak,
|
|
141025
|
-
revokedAt: row.revoked_at,
|
|
141026
|
-
state: tokenState(row)
|
|
141027
|
-
};
|
|
141028
|
-
}
|
|
141029
|
-
var PushRepository = class {
|
|
141030
|
-
upsertStmt;
|
|
141031
|
-
getStmt;
|
|
141032
|
-
listActiveStmt;
|
|
141033
|
-
listAllStmt;
|
|
141034
|
-
successStmt;
|
|
141035
|
-
failureStmt;
|
|
141036
|
-
revokeStmt;
|
|
141037
|
-
claimEventStmt;
|
|
141038
|
-
markDeliveredStmt;
|
|
141039
|
-
constructor(db) {
|
|
141040
|
-
this.upsertStmt = db.prepare(`
|
|
141041
|
-
INSERT INTO push_tokens (token, platform, device_id, registered_at)
|
|
141042
|
-
VALUES (@token, @platform, @device_id, @registered_at)
|
|
141043
|
-
ON CONFLICT(token) DO UPDATE SET
|
|
141044
|
-
platform = excluded.platform,
|
|
141045
|
-
device_id = COALESCE(excluded.device_id, push_tokens.device_id),
|
|
141046
|
-
registered_at = excluded.registered_at,
|
|
141047
|
-
-- A fresh registration clears prior failure state and any revocation:
|
|
141048
|
-
-- the client is telling us this token is live again.
|
|
141049
|
-
failure_streak = 0,
|
|
141050
|
-
last_failure_at = NULL,
|
|
141051
|
-
last_failure_code = NULL,
|
|
141052
|
-
revoked_at = NULL
|
|
141053
|
-
`);
|
|
141054
|
-
this.getStmt = db.prepare("SELECT * FROM push_tokens WHERE token = ?");
|
|
141055
|
-
this.listActiveStmt = db.prepare(`
|
|
141056
|
-
SELECT * FROM push_tokens
|
|
141057
|
-
WHERE revoked_at IS NULL AND failure_streak < ${FAILURE_STREAK_LIMIT}
|
|
141058
|
-
ORDER BY registered_at ASC
|
|
141059
|
-
`);
|
|
141060
|
-
this.listAllStmt = db.prepare("SELECT * FROM push_tokens ORDER BY registered_at ASC");
|
|
141061
|
-
this.successStmt = db.prepare(`
|
|
141062
|
-
UPDATE push_tokens
|
|
141063
|
-
SET last_success_at = @at, failure_streak = 0,
|
|
141064
|
-
last_failure_code = NULL
|
|
141065
|
-
WHERE token = @token
|
|
141066
|
-
`);
|
|
141067
|
-
this.failureStmt = db.prepare(`
|
|
141068
|
-
UPDATE push_tokens
|
|
141069
|
-
SET last_failure_at = @at, last_failure_code = @code,
|
|
141070
|
-
failure_streak = failure_streak + 1
|
|
141071
|
-
WHERE token = @token
|
|
141072
|
-
`);
|
|
141073
|
-
this.revokeStmt = db.prepare("UPDATE push_tokens SET revoked_at = ? WHERE token = ?");
|
|
141074
|
-
this.claimEventStmt = db.prepare(`
|
|
141075
|
-
INSERT OR IGNORE INTO push_events (event_id, session_id, created_at)
|
|
141076
|
-
VALUES (@event_id, @session_id, @created_at)
|
|
141077
|
-
`);
|
|
141078
|
-
this.markDeliveredStmt = db.prepare(
|
|
141079
|
-
"UPDATE push_events SET delivered_at = ? WHERE event_id = ?"
|
|
141080
|
-
);
|
|
141081
|
-
}
|
|
141082
|
-
register(args) {
|
|
141083
|
-
this.upsertStmt.run({
|
|
141084
|
-
token: args.token,
|
|
141085
|
-
platform: args.platform,
|
|
141086
|
-
device_id: args.deviceId ?? null,
|
|
141087
|
-
registered_at: args.now ?? Date.now()
|
|
141088
|
-
});
|
|
141089
|
-
}
|
|
141090
|
-
get(token) {
|
|
141091
|
-
return this.getStmt.get(token) ?? null;
|
|
141092
|
-
}
|
|
141093
|
-
/** Tokens eligible for delivery — not revoked, not past the failure limit. */
|
|
141094
|
-
listDeliverable() {
|
|
141095
|
-
return this.listActiveStmt.all();
|
|
141096
|
-
}
|
|
141097
|
-
/** Every token, including dead and revoked ones, for the health report. */
|
|
141098
|
-
listHealth() {
|
|
141099
|
-
return this.listAllStmt.all().map(toHealth);
|
|
141100
|
-
}
|
|
141101
|
-
recordSuccess(token, now = Date.now()) {
|
|
141102
|
-
this.successStmt.run({ token, at: now });
|
|
141103
|
-
}
|
|
141104
|
-
recordFailure(token, code, now = Date.now()) {
|
|
141105
|
-
this.failureStmt.run({ token, at: now, code });
|
|
141106
|
-
}
|
|
141107
|
-
revoke(token, now = Date.now()) {
|
|
141108
|
-
return this.revokeStmt.run(now, token).changes > 0;
|
|
141109
|
-
}
|
|
141110
|
-
/**
|
|
141111
|
-
* Claim an event id for delivery.
|
|
141112
|
-
*
|
|
141113
|
-
* Returns true exactly once per event id. A retry, a reconnect
|
|
141114
|
-
* reconciliation, or two triggers firing for the same underlying event all
|
|
141115
|
-
* get false and must not notify — the user should never be told twice about
|
|
141116
|
-
* one thing.
|
|
141117
|
-
*/
|
|
141118
|
-
claimEvent(eventId, sessionId, now = Date.now()) {
|
|
141119
|
-
return this.claimEventStmt.run({ event_id: eventId, session_id: sessionId, created_at: now }).changes > 0;
|
|
141120
|
-
}
|
|
141121
|
-
markDelivered(eventId, now = Date.now()) {
|
|
141122
|
-
this.markDeliveredStmt.run(now, eventId);
|
|
141123
|
-
}
|
|
141124
|
-
};
|
|
141125
|
-
|
|
141126
141392
|
// src/db/repositories/sessions.repository.ts
|
|
141127
141393
|
var SessionsRepository = class {
|
|
141128
141394
|
constructor(store) {
|
|
@@ -141156,6 +141422,9 @@ async function recordUpload(pool2, instanceId, row) {
|
|
|
141156
141422
|
);
|
|
141157
141423
|
}
|
|
141158
141424
|
|
|
141425
|
+
// src/server.ts
|
|
141426
|
+
init_feature_flags();
|
|
141427
|
+
|
|
141159
141428
|
// src/handlers/handleListProjects.ts
|
|
141160
141429
|
var import_fs18 = require("fs");
|
|
141161
141430
|
var import_os7 = require("os");
|
|
@@ -143429,10 +143698,10 @@ function fingerprintOf(ids) {
|
|
|
143429
143698
|
return `sha256:${(0, import_crypto11.createHash)("sha256").update(sorted.join("\n")).digest("hex")}`;
|
|
143430
143699
|
}
|
|
143431
143700
|
var CacheIntegrityMonitor = class {
|
|
143432
|
-
constructor(cache, wsHub,
|
|
143701
|
+
constructor(cache, wsHub, log12, cacheDir, rescan, runDuringReset) {
|
|
143433
143702
|
this.cache = cache;
|
|
143434
143703
|
this.wsHub = wsHub;
|
|
143435
|
-
this.log =
|
|
143704
|
+
this.log = log12;
|
|
143436
143705
|
this.cacheDir = cacheDir;
|
|
143437
143706
|
this.rescan = rescan;
|
|
143438
143707
|
this.runDuringReset = runDuringReset;
|
|
@@ -144264,6 +144533,585 @@ function deriveProjectChatTitle(input) {
|
|
|
144264
144533
|
return `Untitled \xB7 ${input.id.slice(0, 8)}`;
|
|
144265
144534
|
}
|
|
144266
144535
|
|
|
144536
|
+
// src/services/push/apnsClient.ts
|
|
144537
|
+
var import_node_crypto3 = require("crypto");
|
|
144538
|
+
var import_node_http2 = require("http2");
|
|
144539
|
+
init_logger();
|
|
144540
|
+
var log3 = getLogger("apns");
|
|
144541
|
+
var APNS_HOST_SANDBOX = "api.sandbox.push.apple.com";
|
|
144542
|
+
var APNS_MAX_PAYLOAD_BYTES = 4096;
|
|
144543
|
+
var JWT_TTL_SECONDS = 3e3;
|
|
144544
|
+
var DEAD_TOKEN_REASONS = /* @__PURE__ */ new Set([
|
|
144545
|
+
"BadDeviceToken",
|
|
144546
|
+
"DeviceTokenNotForTopic",
|
|
144547
|
+
"Unregistered",
|
|
144548
|
+
"ExpiredToken"
|
|
144549
|
+
]);
|
|
144550
|
+
function base64url3(input) {
|
|
144551
|
+
return Buffer.from(input).toString("base64url");
|
|
144552
|
+
}
|
|
144553
|
+
function readApnsCredentialsFromEnv(env = process.env) {
|
|
144554
|
+
const key = env.APNS_KEY;
|
|
144555
|
+
if (!key || key.trim().length === 0) return null;
|
|
144556
|
+
const keyId = env.APNS_KEY_ID?.trim();
|
|
144557
|
+
const teamId = env.APNS_TEAM_ID?.trim();
|
|
144558
|
+
const bundleId = env.APNS_BUNDLE_ID?.trim();
|
|
144559
|
+
if (!keyId || !teamId || !bundleId) return null;
|
|
144560
|
+
const host = env.APNS_HOST ?? APNS_HOST_SANDBOX;
|
|
144561
|
+
return { key, keyId, teamId, bundleId, host };
|
|
144562
|
+
}
|
|
144563
|
+
function describeMissingApnsCredentials(env = process.env) {
|
|
144564
|
+
if (!env.APNS_KEY || env.APNS_KEY.trim().length === 0) {
|
|
144565
|
+
return "APNS_KEY is not set, so Live Activity push is disabled. Set it to the p8 key contents (not a path) to enable it.";
|
|
144566
|
+
}
|
|
144567
|
+
const missing = [
|
|
144568
|
+
["APNS_KEY_ID", env.APNS_KEY_ID],
|
|
144569
|
+
["APNS_TEAM_ID", env.APNS_TEAM_ID],
|
|
144570
|
+
["APNS_BUNDLE_ID", env.APNS_BUNDLE_ID]
|
|
144571
|
+
].filter(([, value]) => !value || value.trim().length === 0).map(([name]) => name);
|
|
144572
|
+
if (missing.length === 0) return null;
|
|
144573
|
+
return `APNS_KEY is set but ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} not, so Live Activity push is disabled. Under launchd, APNS_KEY_ID is derived from the AuthKey_<keyId>.p8 filename; the team and bundle ids must be set explicitly.`;
|
|
144574
|
+
}
|
|
144575
|
+
var ApnsClient = class {
|
|
144576
|
+
constructor(creds) {
|
|
144577
|
+
this.creds = creds;
|
|
144578
|
+
}
|
|
144579
|
+
creds;
|
|
144580
|
+
session = null;
|
|
144581
|
+
cachedJwt = null;
|
|
144582
|
+
/**
|
|
144583
|
+
* The `apns-topic` for Live Activity pushes.
|
|
144584
|
+
*
|
|
144585
|
+
* The `.push-type.liveactivity` suffix is mandatory and is why the signing key
|
|
144586
|
+
* must be Team Scoped (All Topics) — a key scoped to the bundle id alone
|
|
144587
|
+
* cannot sign this topic.
|
|
144588
|
+
*/
|
|
144589
|
+
get topic() {
|
|
144590
|
+
return `${this.creds.bundleId}.push-type.liveactivity`;
|
|
144591
|
+
}
|
|
144592
|
+
/**
|
|
144593
|
+
* Mint or reuse the provider JWT.
|
|
144594
|
+
*
|
|
144595
|
+
* ES256 over the p8 key. Cached until shortly before expiry: Apple rejects a
|
|
144596
|
+
* token older than an hour, but minting one per request is wasteful and can
|
|
144597
|
+
* trip APNs' provider-token-update throttle.
|
|
144598
|
+
*/
|
|
144599
|
+
getJwt(now = Date.now()) {
|
|
144600
|
+
const nowSeconds = Math.floor(now / 1e3);
|
|
144601
|
+
if (this.cachedJwt && this.cachedJwt.expiresAt > nowSeconds + 60) {
|
|
144602
|
+
return this.cachedJwt.token;
|
|
144603
|
+
}
|
|
144604
|
+
const header = base64url3(JSON.stringify({ alg: "ES256", kid: this.creds.keyId, typ: "JWT" }));
|
|
144605
|
+
const payload = base64url3(JSON.stringify({ iss: this.creds.teamId, iat: nowSeconds }));
|
|
144606
|
+
const signingInput = `${header}.${payload}`;
|
|
144607
|
+
const signature = (0, import_node_crypto3.createSign)("SHA256").update(signingInput).sign({ key: this.creds.key, dsaEncoding: "ieee-p1363" });
|
|
144608
|
+
const token = `${signingInput}.${base64url3(signature)}`;
|
|
144609
|
+
this.cachedJwt = { token, expiresAt: nowSeconds + JWT_TTL_SECONDS };
|
|
144610
|
+
return token;
|
|
144611
|
+
}
|
|
144612
|
+
/**
|
|
144613
|
+
* Reuse one HTTP/2 session across sends.
|
|
144614
|
+
*
|
|
144615
|
+
* APNs expects a long-lived connection; a fresh TLS handshake per push is slow
|
|
144616
|
+
* and Apple treats connection churn as abuse.
|
|
144617
|
+
*/
|
|
144618
|
+
getSession() {
|
|
144619
|
+
if (this.session && !this.session.closed && !this.session.destroyed) {
|
|
144620
|
+
return this.session;
|
|
144621
|
+
}
|
|
144622
|
+
const session = (0, import_node_http2.connect)(`https://${this.creds.host}`);
|
|
144623
|
+
session.on("error", (err) => {
|
|
144624
|
+
log3.warn("apns.session_error", { event: "apns.session_error", err: String(err) });
|
|
144625
|
+
});
|
|
144626
|
+
this.session = session;
|
|
144627
|
+
return session;
|
|
144628
|
+
}
|
|
144629
|
+
/**
|
|
144630
|
+
* Send one push.
|
|
144631
|
+
*
|
|
144632
|
+
* Resolves with a result rather than rejecting on an APNs rejection: a
|
|
144633
|
+
* rejected push is an expected outcome the caller must act on (retire the
|
|
144634
|
+
* token), not an exception. Only a genuinely unexpected local failure throws,
|
|
144635
|
+
* and the caller logs it.
|
|
144636
|
+
*/
|
|
144637
|
+
async send(args) {
|
|
144638
|
+
const body = Buffer.from(JSON.stringify(args.payload), "utf-8");
|
|
144639
|
+
if (body.byteLength > APNS_MAX_PAYLOAD_BYTES) {
|
|
144640
|
+
throw new Error(
|
|
144641
|
+
`APNs payload is ${body.byteLength} bytes, over the ${APNS_MAX_PAYLOAD_BYTES} byte limit`
|
|
144642
|
+
);
|
|
144643
|
+
}
|
|
144644
|
+
const session = this.getSession();
|
|
144645
|
+
const headers = {
|
|
144646
|
+
[import_node_http2.constants.HTTP2_HEADER_METHOD]: "POST",
|
|
144647
|
+
[import_node_http2.constants.HTTP2_HEADER_PATH]: `/3/device/${args.deviceToken}`,
|
|
144648
|
+
[import_node_http2.constants.HTTP2_HEADER_AUTHORIZATION]: `bearer ${this.getJwt()}`,
|
|
144649
|
+
"apns-push-type": "liveactivity",
|
|
144650
|
+
"apns-topic": this.topic,
|
|
144651
|
+
"apns-priority": String(args.priority ?? 10),
|
|
144652
|
+
...args.expirationSeconds != null && {
|
|
144653
|
+
"apns-expiration": String(args.expirationSeconds)
|
|
144654
|
+
},
|
|
144655
|
+
[import_node_http2.constants.HTTP2_HEADER_CONTENT_TYPE]: "application/json",
|
|
144656
|
+
[import_node_http2.constants.HTTP2_HEADER_CONTENT_LENGTH]: String(body.byteLength)
|
|
144657
|
+
};
|
|
144658
|
+
return new Promise((resolve4, reject) => {
|
|
144659
|
+
const req = session.request(headers);
|
|
144660
|
+
req.setTimeout(args.timeoutMs ?? 1e4, () => {
|
|
144661
|
+
req.close(import_node_http2.constants.NGHTTP2_CANCEL);
|
|
144662
|
+
resolve4({ ok: false, status: 0, reason: "Timeout", tokenDead: false });
|
|
144663
|
+
});
|
|
144664
|
+
let status = 0;
|
|
144665
|
+
req.on("response", (resHeaders) => {
|
|
144666
|
+
status = Number(resHeaders[import_node_http2.constants.HTTP2_HEADER_STATUS] ?? 0);
|
|
144667
|
+
});
|
|
144668
|
+
const chunks = [];
|
|
144669
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
144670
|
+
req.on("error", reject);
|
|
144671
|
+
req.on("end", () => {
|
|
144672
|
+
const raw2 = Buffer.concat(chunks).toString("utf-8");
|
|
144673
|
+
let reason;
|
|
144674
|
+
if (raw2.length > 0) {
|
|
144675
|
+
try {
|
|
144676
|
+
reason = JSON.parse(raw2).reason;
|
|
144677
|
+
} catch {
|
|
144678
|
+
reason = raw2.slice(0, 200);
|
|
144679
|
+
}
|
|
144680
|
+
}
|
|
144681
|
+
resolve4({
|
|
144682
|
+
ok: status === 200,
|
|
144683
|
+
status,
|
|
144684
|
+
reason,
|
|
144685
|
+
tokenDead: reason != null && DEAD_TOKEN_REASONS.has(reason)
|
|
144686
|
+
});
|
|
144687
|
+
});
|
|
144688
|
+
req.end(body);
|
|
144689
|
+
});
|
|
144690
|
+
}
|
|
144691
|
+
/** Close the shared connection. Called on server shutdown. */
|
|
144692
|
+
close() {
|
|
144693
|
+
this.session?.close();
|
|
144694
|
+
this.session = null;
|
|
144695
|
+
}
|
|
144696
|
+
};
|
|
144697
|
+
|
|
144698
|
+
// src/services/push/liveActivityNotifier.ts
|
|
144699
|
+
init_logger();
|
|
144700
|
+
|
|
144701
|
+
// src/services/push/liveActivityContentState.ts
|
|
144702
|
+
var LAST_OUTPUT_MAX_LENGTH = 90;
|
|
144703
|
+
function toLiveActivityStatus(status) {
|
|
144704
|
+
return status === "running" || status === "waiting_input" ? status : null;
|
|
144705
|
+
}
|
|
144706
|
+
function truncateLastOutput(raw2) {
|
|
144707
|
+
const oneLine = raw2.replace(/\s+/g, " ").trim();
|
|
144708
|
+
return oneLine.length <= LAST_OUTPUT_MAX_LENGTH ? oneLine : oneLine.slice(0, LAST_OUTPUT_MAX_LENGTH);
|
|
144709
|
+
}
|
|
144710
|
+
|
|
144711
|
+
// src/services/push/liveActivityNotifier.ts
|
|
144712
|
+
var log4 = getLogger("live-activity");
|
|
144713
|
+
function contentStateForSession(args) {
|
|
144714
|
+
const status = toLiveActivityStatus(args.session.status);
|
|
144715
|
+
if (!status) return null;
|
|
144716
|
+
return {
|
|
144717
|
+
sessionId: args.session.id,
|
|
144718
|
+
serverId: args.serverId,
|
|
144719
|
+
projectName: args.session.projectName,
|
|
144720
|
+
status,
|
|
144721
|
+
startedAt: args.startedAtOverride ?? args.session.startedAt.getTime(),
|
|
144722
|
+
lastOutput: truncateLastOutput(args.session.lastOutput ?? ""),
|
|
144723
|
+
...args.serverLabel != null && { serverLabel: args.serverLabel }
|
|
144724
|
+
};
|
|
144725
|
+
}
|
|
144726
|
+
var LiveActivityNotifier = class {
|
|
144727
|
+
constructor(sender, serverId, serverLabel) {
|
|
144728
|
+
this.sender = sender;
|
|
144729
|
+
this.serverId = serverId;
|
|
144730
|
+
this.serverLabel = serverLabel;
|
|
144731
|
+
}
|
|
144732
|
+
sender;
|
|
144733
|
+
serverId;
|
|
144734
|
+
serverLabel;
|
|
144735
|
+
/**
|
|
144736
|
+
* Last status pushed per session.
|
|
144737
|
+
*
|
|
144738
|
+
* Live Activity pushes are rate-limited by iOS and the surface only renders
|
|
144739
|
+
* `running` vs `waiting_input`, so re-pushing an unchanged status is pure
|
|
144740
|
+
* budget spend for no visible change. This is what makes the notifier
|
|
144741
|
+
* edge-triggered rather than level-triggered.
|
|
144742
|
+
*/
|
|
144743
|
+
lastPushed = /* @__PURE__ */ new Map();
|
|
144744
|
+
/**
|
|
144745
|
+
* React to a session status change.
|
|
144746
|
+
*
|
|
144747
|
+
* Fire-and-forget by design: a push must never delay or fail a session
|
|
144748
|
+
* transition, so this returns a promise the caller may ignore and every error
|
|
144749
|
+
* is logged rather than propagated.
|
|
144750
|
+
*/
|
|
144751
|
+
async onStatusChange(session) {
|
|
144752
|
+
const status = toLiveActivityStatus(session.status);
|
|
144753
|
+
try {
|
|
144754
|
+
if (!status) {
|
|
144755
|
+
await this.endFor(session);
|
|
144756
|
+
return;
|
|
144757
|
+
}
|
|
144758
|
+
if (this.lastPushed.get(session.id) === status) return;
|
|
144759
|
+
const contentState = contentStateForSession({
|
|
144760
|
+
session,
|
|
144761
|
+
serverId: this.serverId,
|
|
144762
|
+
serverLabel: this.serverLabel
|
|
144763
|
+
});
|
|
144764
|
+
if (!contentState) return;
|
|
144765
|
+
const outcome = await this.sender.send({
|
|
144766
|
+
sessionId: session.id,
|
|
144767
|
+
event: "update",
|
|
144768
|
+
contentState
|
|
144769
|
+
});
|
|
144770
|
+
this.lastPushed.set(session.id, status);
|
|
144771
|
+
if (outcome.attempted > 0) {
|
|
144772
|
+
log4.info("live_activity.updated", {
|
|
144773
|
+
event: "live_activity.updated",
|
|
144774
|
+
sessionId: session.id,
|
|
144775
|
+
status,
|
|
144776
|
+
...outcome
|
|
144777
|
+
});
|
|
144778
|
+
}
|
|
144779
|
+
} catch (err) {
|
|
144780
|
+
log4.error("live_activity.notify_failed", {
|
|
144781
|
+
event: "live_activity.notify_failed",
|
|
144782
|
+
sessionId: session.id,
|
|
144783
|
+
status: session.status,
|
|
144784
|
+
err: String(err)
|
|
144785
|
+
});
|
|
144786
|
+
}
|
|
144787
|
+
}
|
|
144788
|
+
async endFor(session) {
|
|
144789
|
+
const lastStatus = this.lastPushed.get(session.id);
|
|
144790
|
+
this.lastPushed.delete(session.id);
|
|
144791
|
+
const contentState = contentStateForSession({
|
|
144792
|
+
session: {
|
|
144793
|
+
...session,
|
|
144794
|
+
status: lastStatus === "waiting_input" ? "waiting_input" : "running"
|
|
144795
|
+
},
|
|
144796
|
+
serverId: this.serverId,
|
|
144797
|
+
serverLabel: this.serverLabel
|
|
144798
|
+
});
|
|
144799
|
+
if (!contentState) return;
|
|
144800
|
+
const outcome = await this.sender.end({ sessionId: session.id, contentState });
|
|
144801
|
+
if (outcome.attempted > 0) {
|
|
144802
|
+
log4.info("live_activity.ended", {
|
|
144803
|
+
event: "live_activity.ended",
|
|
144804
|
+
sessionId: session.id,
|
|
144805
|
+
...outcome
|
|
144806
|
+
});
|
|
144807
|
+
}
|
|
144808
|
+
}
|
|
144809
|
+
/** Drop cached state for a session, so a resume re-pushes its first status. */
|
|
144810
|
+
forget(sessionId) {
|
|
144811
|
+
this.lastPushed.delete(sessionId);
|
|
144812
|
+
}
|
|
144813
|
+
};
|
|
144814
|
+
|
|
144815
|
+
// src/services/push/liveActivityRenewal.ts
|
|
144816
|
+
init_logger();
|
|
144817
|
+
|
|
144818
|
+
// src/services/push/liveActivitySender.ts
|
|
144819
|
+
init_logger();
|
|
144820
|
+
var log5 = getLogger("live-activity");
|
|
144821
|
+
var ACTIVITY_MAX_LIFETIME_MS = 8 * 60 * 60 * 1e3;
|
|
144822
|
+
function buildActivityKitPayload(args) {
|
|
144823
|
+
return {
|
|
144824
|
+
aps: {
|
|
144825
|
+
timestamp: Math.floor(args.now / 1e3),
|
|
144826
|
+
event: args.event,
|
|
144827
|
+
"content-state": args.contentState,
|
|
144828
|
+
...args.staleDate != null && { "stale-date": Math.floor(args.staleDate / 1e3) },
|
|
144829
|
+
...args.dismissalDate != null && {
|
|
144830
|
+
"dismissal-date": Math.floor(args.dismissalDate / 1e3)
|
|
144831
|
+
}
|
|
144832
|
+
}
|
|
144833
|
+
};
|
|
144834
|
+
}
|
|
144835
|
+
var LiveActivitySender = class {
|
|
144836
|
+
constructor(apns, repo) {
|
|
144837
|
+
this.apns = apns;
|
|
144838
|
+
this.repo = repo;
|
|
144839
|
+
}
|
|
144840
|
+
apns;
|
|
144841
|
+
repo;
|
|
144842
|
+
/**
|
|
144843
|
+
* Push to every live activity of a session.
|
|
144844
|
+
*
|
|
144845
|
+
* Sends are independent: one rejected token must not stop the others, because
|
|
144846
|
+
* a single dead device would otherwise silence every other device watching the
|
|
144847
|
+
* same session.
|
|
144848
|
+
*/
|
|
144849
|
+
async send(args) {
|
|
144850
|
+
const now = args.now ?? Date.now();
|
|
144851
|
+
return this.sendToTokens({
|
|
144852
|
+
tokens: this.repo.listForSession("liveactivity_update", args.sessionId, now),
|
|
144853
|
+
sessionId: args.sessionId,
|
|
144854
|
+
event: args.event,
|
|
144855
|
+
contentState: args.contentState,
|
|
144856
|
+
now,
|
|
144857
|
+
priority: args.priority
|
|
144858
|
+
});
|
|
144859
|
+
}
|
|
144860
|
+
/**
|
|
144861
|
+
* Push to an explicit token list.
|
|
144862
|
+
*
|
|
144863
|
+
* Renewal needs this: a replacement activity does not exist yet, so it is
|
|
144864
|
+
* started via the app-wide push-to-start token rather than any per-session
|
|
144865
|
+
* lookup. Shares one fan-out body with `send()` so failure handling cannot
|
|
144866
|
+
* drift between the two paths.
|
|
144867
|
+
*/
|
|
144868
|
+
async sendToTokens(args) {
|
|
144869
|
+
const now = args.now ?? Date.now();
|
|
144870
|
+
const tokens = args.tokens;
|
|
144871
|
+
const outcome = {
|
|
144872
|
+
attempted: tokens.length,
|
|
144873
|
+
succeeded: 0,
|
|
144874
|
+
retired: 0
|
|
144875
|
+
};
|
|
144876
|
+
if (tokens.length === 0) return outcome;
|
|
144877
|
+
const results = await Promise.all(
|
|
144878
|
+
tokens.map(
|
|
144879
|
+
(row) => this.sendToToken(row, args.event, args.contentState, now, args.priority, args.staleDate)
|
|
144880
|
+
)
|
|
144881
|
+
);
|
|
144882
|
+
for (const { row, result, error: error51 } of results) {
|
|
144883
|
+
if (error51) {
|
|
144884
|
+
log5.error("live_activity.send_failed", {
|
|
144885
|
+
event: "live_activity.send_failed",
|
|
144886
|
+
sessionId: args.sessionId,
|
|
144887
|
+
activityId: row.activity_id,
|
|
144888
|
+
apnsEvent: args.event,
|
|
144889
|
+
err: String(error51)
|
|
144890
|
+
});
|
|
144891
|
+
this.repo.recordFailure(row.token, "SendError", now);
|
|
144892
|
+
continue;
|
|
144893
|
+
}
|
|
144894
|
+
if (!result) continue;
|
|
144895
|
+
if (result.ok) {
|
|
144896
|
+
this.repo.recordSuccess(row.token, now);
|
|
144897
|
+
outcome.succeeded += 1;
|
|
144898
|
+
continue;
|
|
144899
|
+
}
|
|
144900
|
+
this.repo.recordFailure(row.token, result.reason ?? `HTTP_${result.status}`, now);
|
|
144901
|
+
if (result.tokenDead) {
|
|
144902
|
+
this.repo.expire(row.token, now);
|
|
144903
|
+
outcome.retired += 1;
|
|
144904
|
+
}
|
|
144905
|
+
log5.warn("live_activity.send_rejected", {
|
|
144906
|
+
event: "live_activity.send_rejected",
|
|
144907
|
+
sessionId: args.sessionId,
|
|
144908
|
+
activityId: row.activity_id,
|
|
144909
|
+
apnsEvent: args.event,
|
|
144910
|
+
status: result.status,
|
|
144911
|
+
reason: result.reason,
|
|
144912
|
+
tokenDead: result.tokenDead
|
|
144913
|
+
});
|
|
144914
|
+
}
|
|
144915
|
+
return outcome;
|
|
144916
|
+
}
|
|
144917
|
+
/**
|
|
144918
|
+
* End every live activity for a session and stop tracking them.
|
|
144919
|
+
*
|
|
144920
|
+
* Expiring locally is what stops the renewal sweep from later resurrecting an
|
|
144921
|
+
* activity for a session that has already finished.
|
|
144922
|
+
*/
|
|
144923
|
+
async end(args) {
|
|
144924
|
+
const now = args.now ?? Date.now();
|
|
144925
|
+
const outcome = await this.send({
|
|
144926
|
+
sessionId: args.sessionId,
|
|
144927
|
+
event: "end",
|
|
144928
|
+
contentState: args.contentState,
|
|
144929
|
+
now
|
|
144930
|
+
});
|
|
144931
|
+
this.repo.expireSessionActivities(args.sessionId, now);
|
|
144932
|
+
return outcome;
|
|
144933
|
+
}
|
|
144934
|
+
async sendToToken(row, event, contentState, now, priority, staleDateOverride) {
|
|
144935
|
+
const staleDate = event === "update" ? staleDateOverride ?? row.stale_date ?? contentState.startedAt + ACTIVITY_MAX_LIFETIME_MS : null;
|
|
144936
|
+
try {
|
|
144937
|
+
const result = await this.apns.send({
|
|
144938
|
+
deviceToken: row.token,
|
|
144939
|
+
payload: buildActivityKitPayload({ event, contentState, now, staleDate }),
|
|
144940
|
+
priority
|
|
144941
|
+
});
|
|
144942
|
+
return { row, result };
|
|
144943
|
+
} catch (error51) {
|
|
144944
|
+
return { row, error: error51 };
|
|
144945
|
+
}
|
|
144946
|
+
}
|
|
144947
|
+
};
|
|
144948
|
+
|
|
144949
|
+
// src/services/push/liveActivityRenewal.ts
|
|
144950
|
+
var log6 = getLogger("live-activity");
|
|
144951
|
+
var RENEWAL_LEAD_MS = 30 * 60 * 1e3;
|
|
144952
|
+
var MAX_TIMER_MS = 60 * 60 * 1e3;
|
|
144953
|
+
function renewalDueAt(row) {
|
|
144954
|
+
return row.stale_date == null ? null : row.stale_date - RENEWAL_LEAD_MS;
|
|
144955
|
+
}
|
|
144956
|
+
var LiveActivityRenewalScheduler = class {
|
|
144957
|
+
constructor(deps) {
|
|
144958
|
+
this.deps = deps;
|
|
144959
|
+
this.now = deps.now ?? (() => Date.now());
|
|
144960
|
+
}
|
|
144961
|
+
deps;
|
|
144962
|
+
timer = null;
|
|
144963
|
+
stopped = false;
|
|
144964
|
+
now;
|
|
144965
|
+
/**
|
|
144966
|
+
* Arm the scheduler from persisted state.
|
|
144967
|
+
*
|
|
144968
|
+
* Called on boot, which is what makes a renewal survive a restart: the
|
|
144969
|
+
* deadlines were never in memory to begin with.
|
|
144970
|
+
*/
|
|
144971
|
+
start() {
|
|
144972
|
+
this.stopped = false;
|
|
144973
|
+
void this.tick();
|
|
144974
|
+
}
|
|
144975
|
+
stop() {
|
|
144976
|
+
this.stopped = true;
|
|
144977
|
+
if (this.timer) {
|
|
144978
|
+
clearTimeout(this.timer);
|
|
144979
|
+
this.timer = null;
|
|
144980
|
+
}
|
|
144981
|
+
}
|
|
144982
|
+
/**
|
|
144983
|
+
* Renew everything due, then sleep until the next deadline.
|
|
144984
|
+
*
|
|
144985
|
+
* Re-reads from the DB every tick rather than caching a schedule in memory, so
|
|
144986
|
+
* an activity registered after boot is picked up without re-arming anything.
|
|
144987
|
+
*/
|
|
144988
|
+
async tick() {
|
|
144989
|
+
if (this.stopped) return;
|
|
144990
|
+
const now = this.now();
|
|
144991
|
+
try {
|
|
144992
|
+
for (const row of this.deps.repo.listRenewable()) {
|
|
144993
|
+
const dueAt = renewalDueAt(row);
|
|
144994
|
+
if (dueAt == null || dueAt > now) continue;
|
|
144995
|
+
await this.renew(row, now);
|
|
144996
|
+
}
|
|
144997
|
+
} catch (err) {
|
|
144998
|
+
log6.error("live_activity.renewal_sweep_failed", {
|
|
144999
|
+
event: "live_activity.renewal_sweep_failed",
|
|
145000
|
+
err: String(err)
|
|
145001
|
+
});
|
|
145002
|
+
}
|
|
145003
|
+
this.scheduleNext();
|
|
145004
|
+
}
|
|
145005
|
+
scheduleNext() {
|
|
145006
|
+
if (this.stopped) return;
|
|
145007
|
+
const now = this.now();
|
|
145008
|
+
const pending = this.deps.repo.listRenewable().map(renewalDueAt).filter((d) => d != null);
|
|
145009
|
+
const nextDue = pending.length > 0 ? Math.min(...pending) : now + MAX_TIMER_MS;
|
|
145010
|
+
const delay = Math.min(Math.max(nextDue - now, 0), MAX_TIMER_MS);
|
|
145011
|
+
this.timer = setTimeout(() => void this.tick(), delay);
|
|
145012
|
+
this.timer.unref?.();
|
|
145013
|
+
}
|
|
145014
|
+
/**
|
|
145015
|
+
* Renew one activity.
|
|
145016
|
+
*
|
|
145017
|
+
* Claims first: `claimRenewal()` succeeds exactly once per row, so a timer
|
|
145018
|
+
* re-armed after a restart mid-window cannot send a second time.
|
|
145019
|
+
*/
|
|
145020
|
+
async renew(row, now) {
|
|
145021
|
+
if (!row.session_id) return;
|
|
145022
|
+
const session = this.deps.sessionStore.getManaged(row.session_id);
|
|
145023
|
+
const status = session ? toLiveActivityStatus(session.status) : null;
|
|
145024
|
+
if (!session || !status) {
|
|
145025
|
+
this.deps.repo.claimRenewal(row.token, now);
|
|
145026
|
+
this.deps.repo.expire(row.token, now);
|
|
145027
|
+
log6.info("live_activity.renewal_skipped", {
|
|
145028
|
+
event: "live_activity.renewal_skipped",
|
|
145029
|
+
sessionId: row.session_id,
|
|
145030
|
+
activityId: row.activity_id,
|
|
145031
|
+
reason: session ? `status_${session.status}` : "session_gone"
|
|
145032
|
+
});
|
|
145033
|
+
return;
|
|
145034
|
+
}
|
|
145035
|
+
if (!this.deps.repo.claimRenewal(row.token, now)) {
|
|
145036
|
+
return;
|
|
145037
|
+
}
|
|
145038
|
+
const startedAt = row.started_at ?? session.startedAt.getTime();
|
|
145039
|
+
const contentState = {
|
|
145040
|
+
sessionId: session.id,
|
|
145041
|
+
serverId: this.deps.serverId,
|
|
145042
|
+
projectName: session.projectName,
|
|
145043
|
+
status,
|
|
145044
|
+
startedAt,
|
|
145045
|
+
lastOutput: session.lastOutput ?? "",
|
|
145046
|
+
...this.deps.serverLabel != null && { serverLabel: this.deps.serverLabel }
|
|
145047
|
+
};
|
|
145048
|
+
try {
|
|
145049
|
+
await this.deps.sender.send({
|
|
145050
|
+
sessionId: session.id,
|
|
145051
|
+
event: "end",
|
|
145052
|
+
contentState: { ...contentState, lastOutput: truncateLastOutput(contentState.lastOutput) },
|
|
145053
|
+
now
|
|
145054
|
+
});
|
|
145055
|
+
this.deps.repo.expire(row.token, now);
|
|
145056
|
+
const started = await this.startReplacement({
|
|
145057
|
+
sessionId: session.id,
|
|
145058
|
+
startedAt,
|
|
145059
|
+
now
|
|
145060
|
+
});
|
|
145061
|
+
log6.info("live_activity.renewed", {
|
|
145062
|
+
event: "live_activity.renewed",
|
|
145063
|
+
sessionId: session.id,
|
|
145064
|
+
activityId: row.activity_id,
|
|
145065
|
+
// Logged because a regression here is invisible on the server and only
|
|
145066
|
+
// shows up as a reset timer on someone's Lock Screen.
|
|
145067
|
+
startedAt,
|
|
145068
|
+
replacementRequested: started
|
|
145069
|
+
});
|
|
145070
|
+
} catch (err) {
|
|
145071
|
+
log6.error("live_activity.renewal_failed", {
|
|
145072
|
+
event: "live_activity.renewal_failed",
|
|
145073
|
+
sessionId: session.id,
|
|
145074
|
+
activityId: row.activity_id,
|
|
145075
|
+
err: String(err)
|
|
145076
|
+
});
|
|
145077
|
+
}
|
|
145078
|
+
}
|
|
145079
|
+
/**
|
|
145080
|
+
* Ask the device to start a replacement activity.
|
|
145081
|
+
*
|
|
145082
|
+
* Uses the app-wide push-to-start token, because the replacement does not
|
|
145083
|
+
* exist yet and therefore has no per-activity token. Returns false when the
|
|
145084
|
+
* device never registered one, which is not an error: the app simply cannot be
|
|
145085
|
+
* asked to start an activity remotely, and the next foreground WS update
|
|
145086
|
+
* recreates it.
|
|
145087
|
+
*/
|
|
145088
|
+
async startReplacement(args) {
|
|
145089
|
+
const starters = this.deps.repo.listByKind("liveactivity_start", args.now);
|
|
145090
|
+
if (starters.length === 0) return false;
|
|
145091
|
+
const session = this.deps.sessionStore.getManaged(args.sessionId);
|
|
145092
|
+
const status = session ? toLiveActivityStatus(session.status) : null;
|
|
145093
|
+
if (!session || !status) return false;
|
|
145094
|
+
await this.deps.sender.sendToTokens({
|
|
145095
|
+
tokens: starters,
|
|
145096
|
+
event: "update",
|
|
145097
|
+
sessionId: args.sessionId,
|
|
145098
|
+
contentState: {
|
|
145099
|
+
sessionId: session.id,
|
|
145100
|
+
serverId: this.deps.serverId,
|
|
145101
|
+
projectName: session.projectName,
|
|
145102
|
+
status,
|
|
145103
|
+
// Carried through unchanged — the whole point of the renewal.
|
|
145104
|
+
startedAt: args.startedAt,
|
|
145105
|
+
lastOutput: truncateLastOutput(session.lastOutput ?? ""),
|
|
145106
|
+
...this.deps.serverLabel != null && { serverLabel: this.deps.serverLabel }
|
|
145107
|
+
},
|
|
145108
|
+
now: args.now,
|
|
145109
|
+
staleDate: args.startedAt + ACTIVITY_MAX_LIFETIME_MS
|
|
145110
|
+
});
|
|
145111
|
+
return true;
|
|
145112
|
+
}
|
|
145113
|
+
};
|
|
145114
|
+
|
|
144267
145115
|
// src/services/questions/parseStatusLine.ts
|
|
144268
145116
|
var MODEL_RE = /(Opus|Sonnet|Haiku|Fable)\s+[\d.]+(?:\s*\([^)]*\))?/;
|
|
144269
145117
|
var EFFORT_RE = /●\s*([A-Za-z]+)\s*·\s*\/effort/;
|
|
@@ -144972,13 +145820,13 @@ function hashPrefix(text) {
|
|
|
144972
145820
|
}
|
|
144973
145821
|
|
|
144974
145822
|
// src/utils/conversationEtag.ts
|
|
144975
|
-
var
|
|
145823
|
+
var import_node_crypto4 = require("crypto");
|
|
144976
145824
|
function computeConversationEtag({
|
|
144977
145825
|
filePath,
|
|
144978
145826
|
messageCount,
|
|
144979
145827
|
timestamp: timestamp2
|
|
144980
145828
|
}) {
|
|
144981
|
-
const digest = (0,
|
|
145829
|
+
const digest = (0, import_node_crypto4.createHash)("sha1").update(`${filePath}:${messageCount}:${timestamp2}`).digest("hex").slice(0, 16);
|
|
144982
145830
|
return `"${digest}"`;
|
|
144983
145831
|
}
|
|
144984
145832
|
|
|
@@ -145241,6 +146089,12 @@ var StreamerServer = class {
|
|
|
145241
146089
|
sessionInputAttempts = /* @__PURE__ */ new Map();
|
|
145242
146090
|
ptyGracePeriodMs;
|
|
145243
146091
|
defaultSystemPrompt;
|
|
146092
|
+
// Resolved once at boot; see src/feature-flags.ts. Total map — every registry
|
|
146093
|
+
// id is present, so indexing it never yields undefined.
|
|
146094
|
+
featureFlags;
|
|
146095
|
+
// Derived from featureFlags.codexSystemPrompt. Kept as its own field so the
|
|
146096
|
+
// read site in startFresh() is unchanged.
|
|
146097
|
+
codexSystemPromptEnabled;
|
|
145244
146098
|
defaultPermissionMode;
|
|
145245
146099
|
defaultModel;
|
|
145246
146100
|
defaultEffort;
|
|
@@ -145296,6 +146150,12 @@ var StreamerServer = class {
|
|
|
145296
146150
|
// Paired-device registry (C5). Null when the cache DB failed to open — auth
|
|
145297
146151
|
// then falls back to the shared API key alone, which is the pre-C5 behaviour.
|
|
145298
146152
|
devicesRepo = null;
|
|
146153
|
+
// Live Activity push (Feature 12). Null when APNS_KEY is unset — the ordinary
|
|
146154
|
+
// case on a dev machine and in CI, where the feature is simply off. Missing an
|
|
146155
|
+
// optional push credential must never stop the server from booting.
|
|
146156
|
+
apnsClient = null;
|
|
146157
|
+
liveActivityNotifier = null;
|
|
146158
|
+
liveActivityRenewal = null;
|
|
145299
146159
|
discoveryCache = null;
|
|
145300
146160
|
cacheDir;
|
|
145301
146161
|
tailSize;
|
|
@@ -145331,6 +146191,11 @@ var StreamerServer = class {
|
|
|
145331
146191
|
this.codexRoots = config2.codexRoots ?? [(0, import_path29.join)((0, import_os12.homedir)(), ".codex", "sessions")];
|
|
145332
146192
|
this.ptyGracePeriodMs = config2.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
|
|
145333
146193
|
this.defaultSystemPrompt = config2.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
146194
|
+
this.featureFlags = resolveFeatureFlags({ cli: config2.featureFlags, yaml: loadFeatureFlags() });
|
|
146195
|
+
if (config2.codexSystemPromptEnabled !== void 0) {
|
|
146196
|
+
this.featureFlags.codexSystemPrompt = config2.codexSystemPromptEnabled;
|
|
146197
|
+
}
|
|
146198
|
+
this.codexSystemPromptEnabled = this.featureFlags.codexSystemPrompt;
|
|
145334
146199
|
this.defaultPermissionMode = config2.defaultPermissionMode ?? loadDefaultPermissionMode() ?? "acceptEdits";
|
|
145335
146200
|
this.defaultModel = config2.defaultModel ?? "sonnet";
|
|
145336
146201
|
this.defaultEffort = config2.defaultEffort ?? "low";
|
|
@@ -145346,6 +146211,13 @@ var StreamerServer = class {
|
|
|
145346
146211
|
}, this.directoryDebounceMs);
|
|
145347
146212
|
this.includeAgents = parseIncludeAgentsEnv(process.env.THREADBASE_INCLUDE_AGENTS);
|
|
145348
146213
|
this.agentEntrypoints = parseAgentEntrypointsEnv(process.env.THREADBASE_AGENT_ENTRYPOINTS);
|
|
146214
|
+
const enabledFlags = nonDefaultFeatureFlags(this.featureFlags);
|
|
146215
|
+
if (enabledFlags.length > 0) {
|
|
146216
|
+
this.log.info(`Feature flags active: ${enabledFlags.join(", ")}`, {
|
|
146217
|
+
event: "config.feature_flags_active",
|
|
146218
|
+
flags: enabledFlags
|
|
146219
|
+
});
|
|
146220
|
+
}
|
|
145349
146221
|
const rawRoot = process.env.THREADBASE_BROWSE_ROOT ?? loadBrowseRoot() ?? config2.browseRoot;
|
|
145350
146222
|
if (rawRoot) {
|
|
145351
146223
|
(0, import_promises16.realpath)(rawRoot).then((resolved) => {
|
|
@@ -145539,6 +146411,7 @@ var StreamerServer = class {
|
|
|
145539
146411
|
if (resp) {
|
|
145540
146412
|
this.wsHub.broadcast({ type: "session_update", session: resp });
|
|
145541
146413
|
}
|
|
146414
|
+
void this.liveActivityNotifier?.onStatusChange(session);
|
|
145542
146415
|
this.sessionStatusBus.emit(`status:${session.id}`, session.status);
|
|
145543
146416
|
}
|
|
145544
146417
|
});
|
|
@@ -145573,6 +146446,7 @@ var StreamerServer = class {
|
|
|
145573
146446
|
logMenubarRequests: this.logMenubarRequests,
|
|
145574
146447
|
rotateApiKey: () => this.rotateApiKey(),
|
|
145575
146448
|
claudeFlagsConfig: () => this.getClaudeFlagsConfig(),
|
|
146449
|
+
featureFlagsConfig: () => this.getFeatureFlagsConfig(),
|
|
145576
146450
|
setClaudeFlagsConfig: (values, extraArgs) => this.setClaudeFlagsConfig(values, extraArgs),
|
|
145577
146451
|
publicUrl: this.publicUrl,
|
|
145578
146452
|
browseRoot: this.browseRoot,
|
|
@@ -145804,6 +146678,41 @@ var StreamerServer = class {
|
|
|
145804
146678
|
}
|
|
145805
146679
|
this.ptyGraceDeferCounts.delete(sessionId);
|
|
145806
146680
|
}
|
|
146681
|
+
/**
|
|
146682
|
+
* Bring up Live Activity push, if credentials are present (Feature 12).
|
|
146683
|
+
*
|
|
146684
|
+
* APNS_KEY absent is the ordinary case on a dev machine and in CI, so this
|
|
146685
|
+
* logs once at info and leaves the feature off rather than failing: the server
|
|
146686
|
+
* must not refuse to boot over a missing optional push credential.
|
|
146687
|
+
*
|
|
146688
|
+
* The key is read from the environment as PEM contents and never from a path
|
|
146689
|
+
* on disk; neither it nor any device token is ever logged.
|
|
146690
|
+
*/
|
|
146691
|
+
initLiveActivityPush(pushRepo) {
|
|
146692
|
+
const creds = readApnsCredentialsFromEnv();
|
|
146693
|
+
if (!creds) {
|
|
146694
|
+
const why = describeMissingApnsCredentials();
|
|
146695
|
+
if (why) this.log.info(why, { event: "live_activity.disabled" });
|
|
146696
|
+
return;
|
|
146697
|
+
}
|
|
146698
|
+
this.apnsClient = new ApnsClient(creds);
|
|
146699
|
+
const sender = new LiveActivitySender(this.apnsClient, pushRepo);
|
|
146700
|
+
const serverId = process.env.THREADBASE_INSTANCE_ID ?? (0, import_os12.hostname)();
|
|
146701
|
+
this.liveActivityNotifier = new LiveActivityNotifier(sender, serverId, (0, import_os12.hostname)());
|
|
146702
|
+
this.liveActivityRenewal = new LiveActivityRenewalScheduler({
|
|
146703
|
+
repo: pushRepo,
|
|
146704
|
+
sender,
|
|
146705
|
+
sessionStore: this.sessionStore,
|
|
146706
|
+
serverId,
|
|
146707
|
+
serverLabel: (0, import_os12.hostname)()
|
|
146708
|
+
});
|
|
146709
|
+
this.liveActivityRenewal.start();
|
|
146710
|
+
this.log.info("Live Activity push enabled", {
|
|
146711
|
+
event: "live_activity.enabled",
|
|
146712
|
+
host: creds.host,
|
|
146713
|
+
topic: `${creds.bundleId}.push-type.liveactivity`
|
|
146714
|
+
});
|
|
146715
|
+
}
|
|
145807
146716
|
/**
|
|
145808
146717
|
* Classify sessions left behind by previous streamer runs (C1 Phase 3a).
|
|
145809
146718
|
*
|
|
@@ -146113,6 +147022,7 @@ var StreamerServer = class {
|
|
|
146113
147022
|
this.cacheMetadataRepo = new CacheMetadataRepository(db);
|
|
146114
147023
|
this.pushRepo = new PushRepository(db);
|
|
146115
147024
|
this.devicesRepo = new DevicesRepository(db);
|
|
147025
|
+
this.initLiveActivityPush(this.pushRepo);
|
|
146116
147026
|
this.cacheMonitor = new CacheIntegrityMonitor(
|
|
146117
147027
|
this.cache,
|
|
146118
147028
|
this.wsHub,
|
|
@@ -146347,6 +147257,8 @@ var StreamerServer = class {
|
|
|
146347
147257
|
this.externalTails.clear();
|
|
146348
147258
|
this.wsHub.dispose();
|
|
146349
147259
|
this.pairTokens.dispose();
|
|
147260
|
+
this.liveActivityRenewal?.stop();
|
|
147261
|
+
this.apnsClient?.close();
|
|
146350
147262
|
if (this.dbPool) {
|
|
146351
147263
|
await this.dbPool.end();
|
|
146352
147264
|
}
|
|
@@ -146418,7 +147330,6 @@ var StreamerServer = class {
|
|
|
146418
147330
|
json2(res, 400, { error: message });
|
|
146419
147331
|
return;
|
|
146420
147332
|
}
|
|
146421
|
-
const { hostname: hostname4 } = require("os");
|
|
146422
147333
|
const ts2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
146423
147334
|
this.log.info(`[pair] token exchanged from ${ip} at ${ts2}`, {
|
|
146424
147335
|
event: "pair.token_exchanged",
|
|
@@ -146441,7 +147352,7 @@ var StreamerServer = class {
|
|
|
146441
147352
|
nonce: sealed.nonce,
|
|
146442
147353
|
ephemeralPublicKey: sealed.ephemeralPublicKey,
|
|
146443
147354
|
publicUrl: this.publicUrl,
|
|
146444
|
-
machineName:
|
|
147355
|
+
machineName: (0, import_os12.hostname)(),
|
|
146445
147356
|
...device && {
|
|
146446
147357
|
deviceId: device.deviceId,
|
|
146447
147358
|
deviceToken: device.deviceToken,
|
|
@@ -146463,6 +147374,16 @@ var StreamerServer = class {
|
|
|
146463
147374
|
});
|
|
146464
147375
|
return { newKey, persisted };
|
|
146465
147376
|
}
|
|
147377
|
+
/**
|
|
147378
|
+
* The registry ships with the values so a client renders the list from one
|
|
147379
|
+
* round-trip, same as getClaudeFlagsConfig().
|
|
147380
|
+
*
|
|
147381
|
+
* Deliberately no `persisted` field: unlike claude-flags there is no PUT, and
|
|
147382
|
+
* the absence of that field is the signal that this endpoint is read-only.
|
|
147383
|
+
*/
|
|
147384
|
+
getFeatureFlagsConfig() {
|
|
147385
|
+
return { registry: FEATURE_FLAGS, values: this.featureFlags };
|
|
147386
|
+
}
|
|
146466
147387
|
getClaudeFlagsConfig() {
|
|
146467
147388
|
return {
|
|
146468
147389
|
registry: CLAUDE_FLAGS,
|
|
@@ -148175,12 +149096,13 @@ var StreamerServer = class {
|
|
|
148175
149096
|
BROWSE_SYSTEM_PROMPT(this.browseRoot),
|
|
148176
149097
|
typeof clientPrompt === "string" ? clientPrompt : null
|
|
148177
149098
|
].filter(Boolean);
|
|
149099
|
+
const includeSystemPrompt = provider !== CODEX_CLI_PROVIDER2 || this.codexSystemPromptEnabled;
|
|
148178
149100
|
try {
|
|
148179
149101
|
const session = await this.ptyManager.startFresh({
|
|
148180
149102
|
provider,
|
|
148181
149103
|
projectPath: resolvedPath,
|
|
148182
149104
|
projectName: body.projectName,
|
|
148183
|
-
systemPrompt: systemPromptParts.join("\n"),
|
|
149105
|
+
...includeSystemPrompt && { systemPrompt: systemPromptParts.join("\n") },
|
|
148184
149106
|
permissionMode: this.defaultPermissionMode,
|
|
148185
149107
|
claudeFlags: this.claudeFlags,
|
|
148186
149108
|
claudeExtraArgs: this.claudeExtraArgs,
|
|
@@ -148886,7 +149808,7 @@ function isBrewInstall(scriptPath = process.argv[1] ?? "") {
|
|
|
148886
149808
|
}
|
|
148887
149809
|
|
|
148888
149810
|
// src/updater/download.ts
|
|
148889
|
-
var
|
|
149811
|
+
var import_node_crypto5 = require("crypto");
|
|
148890
149812
|
var import_node_fs9 = require("fs");
|
|
148891
149813
|
var import_node_path10 = require("path");
|
|
148892
149814
|
var import_promises17 = require("stream/promises");
|
|
@@ -148953,7 +149875,7 @@ async function downloadAndVerify(opts) {
|
|
|
148953
149875
|
if (!res.ok || !res.body) {
|
|
148954
149876
|
throw new Error(`Failed to download ${artifact.filename}: ${res.status} ${res.statusText}`);
|
|
148955
149877
|
}
|
|
148956
|
-
const hash2 = (0,
|
|
149878
|
+
const hash2 = (0, import_node_crypto5.createHash)("sha256");
|
|
148957
149879
|
const out = (0, import_node_fs9.createWriteStream)(targetPath);
|
|
148958
149880
|
let bytes = 0;
|
|
148959
149881
|
const measured = new TransformStream({
|
|
@@ -149258,7 +150180,7 @@ var import_node_path17 = require("path");
|
|
|
149258
150180
|
var import_path32 = __toESM(require("path"), 1);
|
|
149259
150181
|
var import_node_fs13 = __toESM(require("fs"), 1);
|
|
149260
150182
|
var import_node_assert = __toESM(require("assert"), 1);
|
|
149261
|
-
var
|
|
150183
|
+
var import_node_crypto6 = require("crypto");
|
|
149262
150184
|
var import_node_fs14 = __toESM(require("fs"), 1);
|
|
149263
150185
|
var import_node_path18 = __toESM(require("path"), 1);
|
|
149264
150186
|
var import_fs33 = __toESM(require("fs"), 1);
|
|
@@ -151666,7 +152588,7 @@ var Te = fo === "win32";
|
|
|
151666
152588
|
var mo = 1024;
|
|
151667
152589
|
var uo = (s3, t) => {
|
|
151668
152590
|
if (!Te) return import_node_fs14.default.unlink(s3, t);
|
|
151669
|
-
let e = s3 + ".DELETE." + (0,
|
|
152591
|
+
let e = s3 + ".DELETE." + (0, import_node_crypto6.randomBytes)(16).toString("hex");
|
|
151670
152592
|
import_node_fs14.default.rename(s3, e, (i) => {
|
|
151671
152593
|
if (i) return t(i);
|
|
151672
152594
|
import_node_fs14.default.unlink(e, t);
|
|
@@ -151674,7 +152596,7 @@ var uo = (s3, t) => {
|
|
|
151674
152596
|
};
|
|
151675
152597
|
var po = (s3) => {
|
|
151676
152598
|
if (!Te) return import_node_fs14.default.unlinkSync(s3);
|
|
151677
|
-
let t = s3 + ".DELETE." + (0,
|
|
152599
|
+
let t = s3 + ".DELETE." + (0, import_node_crypto6.randomBytes)(16).toString("hex");
|
|
151678
152600
|
import_node_fs14.default.renameSync(s3, t), import_node_fs14.default.unlinkSync(t);
|
|
151679
152601
|
};
|
|
151680
152602
|
var vr = (s3, t, e) => s3 !== void 0 && s3 === s3 >>> 0 ? s3 : t !== void 0 && t === t >>> 0 ? t : e;
|
|
@@ -152331,7 +153253,7 @@ init_launchd();
|
|
|
152331
153253
|
init_marker();
|
|
152332
153254
|
init_platform();
|
|
152333
153255
|
init_logger();
|
|
152334
|
-
var
|
|
153256
|
+
var log8 = getLogger("prod");
|
|
152335
153257
|
function clearSupervisorLogs() {
|
|
152336
153258
|
let paths;
|
|
152337
153259
|
try {
|
|
@@ -152488,12 +153410,12 @@ function registerProdCommands(program3) {
|
|
|
152488
153410
|
prod.command("start").description("Restore prod after a user-held suspension").option("--clear-logs", "Clear existing stdout + stderr logs", false).action(async (opts) => {
|
|
152489
153411
|
if (opts.clearLogs === true) clearSupervisorLogs();
|
|
152490
153412
|
const r = await runProdStart();
|
|
152491
|
-
|
|
153413
|
+
log8.info(r.message, void 0, "console");
|
|
152492
153414
|
if (!r.ok) process.exitCode = 1;
|
|
152493
153415
|
});
|
|
152494
153416
|
prod.command("stop").description("Unload the launchd agent (prod will not auto-restart)").action(async () => {
|
|
152495
153417
|
const r = await runProdStop();
|
|
152496
|
-
|
|
153418
|
+
log8.info(r.message, void 0, "console");
|
|
152497
153419
|
});
|
|
152498
153420
|
prod.command("status").description("Report whether prod is supervised, suspended, or down").action(async () => {
|
|
152499
153421
|
const s3 = await runProdStatus();
|
|
@@ -152502,7 +153424,7 @@ function registerProdCommands(program3) {
|
|
|
152502
153424
|
`pid: ${s3.agentPid ?? "(none)"}`,
|
|
152503
153425
|
s3.marker ? `marker: ${s3.marker.userHeld ? "userHeld (intentional stop)" : "dev-suspended"}, devPid=${s3.marker.devPid}, port=${s3.marker.port}, repo=${s3.marker.repoToplevel}` : "marker: none"
|
|
152504
153426
|
];
|
|
152505
|
-
|
|
153427
|
+
log8.info(parts.join("\n "), void 0, "console");
|
|
152506
153428
|
});
|
|
152507
153429
|
prod.command("restart").description("Stop + restart the supervised streamer (re-reads service definition)").option("--clear-logs", "Clear existing stdout + stderr logs", false).action(async (opts) => {
|
|
152508
153430
|
const sup = getSupervisor();
|
|
@@ -152511,17 +153433,17 @@ function registerProdCommands(program3) {
|
|
|
152511
153433
|
sup.bootoutAgent();
|
|
152512
153434
|
sup.bootstrapAgent(specPath, { afterBootout: true });
|
|
152513
153435
|
const what = process.platform === "darwin" ? `agent restarted from ${specPath}` : `task '${TASK_NAME}' restarted`;
|
|
152514
|
-
|
|
153436
|
+
log8.info(what, void 0, "console");
|
|
152515
153437
|
});
|
|
152516
153438
|
prod.command("doctor").description("Detect & repair stale markers, missing agent, plist drift").option("--fix", "Apply repairs (default is dry-run)", false).action(async (opts) => {
|
|
152517
153439
|
const r = await runProdDoctor({ fix: opts.fix === true });
|
|
152518
|
-
|
|
152519
|
-
for (const f2 of r.findings)
|
|
153440
|
+
log8.info(`findings: ${r.findings.length === 0 ? "(none)" : ""}`, void 0, "console");
|
|
153441
|
+
for (const f2 of r.findings) log8.info(` - ${f2}`, void 0, "console");
|
|
152520
153442
|
if (r.repairs.length) {
|
|
152521
|
-
|
|
152522
|
-
for (const fix of r.repairs)
|
|
153443
|
+
log8.info(`repairs:`, void 0, "console");
|
|
153444
|
+
for (const fix of r.repairs) log8.info(` - ${fix}`, void 0, "console");
|
|
152523
153445
|
} else if (!opts.fix && r.findings.length > 0) {
|
|
152524
|
-
|
|
153446
|
+
log8.info(`(re-run with --fix to apply repairs)`, void 0, "console");
|
|
152525
153447
|
}
|
|
152526
153448
|
});
|
|
152527
153449
|
prod.command("logs").description("Tail the supervised streamer's stdout + stderr log files").option("-n, --lines <count>", "Seed with the last N lines (default 50)", "50").option("--no-follow", "Print last N lines and exit (do not follow)").option("--errors-only", "Tail only stderr", false).option("--clear", "Truncate stdout + stderr logs in place, then exit", false).action(async (opts) => {
|
|
@@ -152532,14 +153454,14 @@ function registerProdCommands(program3) {
|
|
|
152532
153454
|
errorsOnly: opts.errorsOnly === true,
|
|
152533
153455
|
clear: opts.clear === true
|
|
152534
153456
|
});
|
|
152535
|
-
if (r.message)
|
|
153457
|
+
if (r.message) log8.info(r.message, void 0, "console");
|
|
152536
153458
|
if (!r.ok) process.exitCode = 1;
|
|
152537
153459
|
});
|
|
152538
153460
|
program3.addCommand(prod);
|
|
152539
153461
|
}
|
|
152540
153462
|
|
|
152541
153463
|
// cli/index.ts
|
|
152542
|
-
var
|
|
153464
|
+
var log11 = getLogger("cli");
|
|
152543
153465
|
var program2 = new Command();
|
|
152544
153466
|
program2.name("threadbase-streamer").description("PTY session management, WebSocket streaming, and REST API server for Claude Code").version(getVersion());
|
|
152545
153467
|
program2.command("serve").description("Start the streamer server").option("-p, --port <number>", "Port to listen on", "8766").option("--api-key <key>", "API key for authentication").option("--local-no-auth", "Skip auth for localhost requests", false).option("-v, --verbose", "Verbose output", false).option("--log-menubar-requests", "Log /healthz requests from the menubar app", false).option("--browse-root <path>", "Root directory for file browsing").option(
|
|
@@ -152561,6 +153483,10 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152561
153483
|
"--claude-flag <id=value>",
|
|
152562
153484
|
"Allowlisted Claude CLI flag applied to every spawned session, e.g. --claude-flag permissionMode=bypassPermissions. Repeatable; repeat the same id to build a list (--claude-flag addDir=/a --claude-flag addDir=/b). Overrides claude_flags: in ~/.threadbase/server.yaml and makes the value non-persistable.",
|
|
152563
153485
|
(value, previous = []) => [...previous, value]
|
|
153486
|
+
).option(
|
|
153487
|
+
"--feature <id=bool>",
|
|
153488
|
+
"Enable or disable a server feature flag, e.g. --feature codexSystemPrompt=true. Repeatable. Overridden by the flag's THREADBASE_FEATURE_* env var; overrides feature_flags: in ~/.threadbase/server.yaml.",
|
|
153489
|
+
(value, previous = []) => [...previous, value]
|
|
152564
153490
|
).option(
|
|
152565
153491
|
"--claude-extra-args <args>",
|
|
152566
153492
|
"Free-text argv appended verbatim to every spawned Claude session, after the allowlisted flags. Unvalidated escape hatch."
|
|
@@ -152577,14 +153503,14 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152577
153503
|
const { checkSqliteAbi: checkSqliteAbi2 } = await Promise.resolve().then(() => (init_check_sqlite_abi(), check_sqlite_abi_exports));
|
|
152578
153504
|
checkSqliteAbi2();
|
|
152579
153505
|
} catch (err) {
|
|
152580
|
-
|
|
153506
|
+
log11.error(err instanceof Error ? err.message : String(err), void 0, "console");
|
|
152581
153507
|
process.exit(1);
|
|
152582
153508
|
}
|
|
152583
153509
|
if (opts.multiAgentFlow) {
|
|
152584
153510
|
process.env.MULTI_AGENT_FLOW = "true";
|
|
152585
153511
|
}
|
|
152586
153512
|
if (opts.defaultPermissionMode !== void 0 && !isPermissionMode(opts.defaultPermissionMode)) {
|
|
152587
|
-
|
|
153513
|
+
log11.error(
|
|
152588
153514
|
`Invalid --default-permission-mode: ${opts.defaultPermissionMode} (expected one of ${PERMISSION_MODES.join(", ")})`,
|
|
152589
153515
|
void 0,
|
|
152590
153516
|
"console"
|
|
@@ -152600,7 +153526,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152600
153526
|
const value = eq === -1 ? true : entry.slice(eq + 1);
|
|
152601
153527
|
const def = findFlag(id);
|
|
152602
153528
|
if (!def) {
|
|
152603
|
-
|
|
153529
|
+
log11.error(
|
|
152604
153530
|
`Invalid --claude-flag id: ${id} (expected one of ${CLAUDE_FLAGS.map((f2) => f2.id).join(", ")})`,
|
|
152605
153531
|
void 0,
|
|
152606
153532
|
"console"
|
|
@@ -152617,14 +153543,23 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152617
153543
|
claudeFlags = validateFlagValues(raw2);
|
|
152618
153544
|
for (const id of Object.keys(raw2)) {
|
|
152619
153545
|
if (!(id in claudeFlags)) {
|
|
152620
|
-
|
|
153546
|
+
log11.error(`Invalid value for --claude-flag ${id}`, void 0, "console");
|
|
152621
153547
|
process.exit(1);
|
|
152622
153548
|
}
|
|
152623
153549
|
}
|
|
152624
153550
|
}
|
|
153551
|
+
let featureFlags;
|
|
153552
|
+
if (Array.isArray(opts.feature) && opts.feature.length > 0) {
|
|
153553
|
+
const parsed = parseFeatureFlagArgs(opts.feature);
|
|
153554
|
+
if (parsed.errors.length > 0) {
|
|
153555
|
+
for (const error51 of parsed.errors) log11.error(`--feature: ${error51}`, void 0, "console");
|
|
153556
|
+
process.exit(1);
|
|
153557
|
+
}
|
|
153558
|
+
featureFlags = parsed.values;
|
|
153559
|
+
}
|
|
152625
153560
|
const validEfforts = ["low", "medium", "high", "xhigh", "max"];
|
|
152626
153561
|
if (opts.defaultEffort !== void 0 && !validEfforts.includes(opts.defaultEffort)) {
|
|
152627
|
-
|
|
153562
|
+
log11.error(
|
|
152628
153563
|
`Invalid --default-effort: ${opts.defaultEffort} (expected one of ${validEfforts.join(", ")})`,
|
|
152629
153564
|
void 0,
|
|
152630
153565
|
"console"
|
|
@@ -152635,7 +153570,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152635
153570
|
if (opts.ptyGracePeriodMs !== void 0) {
|
|
152636
153571
|
const parsed = Number(opts.ptyGracePeriodMs);
|
|
152637
153572
|
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
152638
|
-
|
|
153573
|
+
log11.error(
|
|
152639
153574
|
`Invalid --pty-grace-period-ms: ${opts.ptyGracePeriodMs} (expected a non-negative integer; 0 disables auto-hold)`,
|
|
152640
153575
|
void 0,
|
|
152641
153576
|
"console"
|
|
@@ -152655,7 +153590,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152655
153590
|
const { detectConflictingAgents: detectConflictingAgents2, formatConflictMessage: formatConflictMessage2 } = await Promise.resolve().then(() => (init_conflict_check(), conflict_check_exports));
|
|
152656
153591
|
const conflicts = detectConflictingAgents2();
|
|
152657
153592
|
if (conflicts.length > 0) {
|
|
152658
|
-
|
|
153593
|
+
log11.warn(formatConflictMessage2(conflicts), void 0, "console");
|
|
152659
153594
|
}
|
|
152660
153595
|
}
|
|
152661
153596
|
let resolvedDefaultPermissionMode = opts.defaultPermissionMode;
|
|
@@ -152701,20 +153636,21 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152701
153636
|
defaultEffort: opts.defaultEffort,
|
|
152702
153637
|
ptyGracePeriodMs,
|
|
152703
153638
|
claudeFlags,
|
|
153639
|
+
featureFlags,
|
|
152704
153640
|
claudeExtraArgs: opts.claudeExtraArgs
|
|
152705
153641
|
});
|
|
152706
153642
|
await server.listen(resolvedPort);
|
|
152707
153643
|
{
|
|
152708
153644
|
const v2 = getVersion();
|
|
152709
|
-
|
|
153645
|
+
log11.info(`Threadbase Streamer v${v2}`, { version: v2, port: resolvedPort });
|
|
152710
153646
|
}
|
|
152711
|
-
|
|
153647
|
+
log11.info(`Listening on http://localhost:${resolvedPort}`, {
|
|
152712
153648
|
url: `http://localhost:${resolvedPort}`
|
|
152713
153649
|
});
|
|
152714
|
-
|
|
153650
|
+
log11.info(`WebSocket at ws://localhost:${resolvedPort}/ws`, {
|
|
152715
153651
|
wsUrl: `ws://localhost:${resolvedPort}/ws`
|
|
152716
153652
|
});
|
|
152717
|
-
|
|
153653
|
+
log11.info(`API key: ${apiKey}`, { apiKeyMasked: `${apiKey.slice(0, 6)}\u2026` });
|
|
152718
153654
|
try {
|
|
152719
153655
|
await printServerBanner({
|
|
152720
153656
|
port: resolvedPort,
|
|
@@ -152724,15 +153660,15 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152724
153660
|
});
|
|
152725
153661
|
} catch (err) {
|
|
152726
153662
|
const message = err instanceof Error ? err.message : String(err);
|
|
152727
|
-
|
|
152728
|
-
|
|
153663
|
+
log11.warn(`(skipped pairing QR: ${message})`, { reason: message });
|
|
153664
|
+
log11.info(
|
|
152729
153665
|
printUrlBanner({ url: resolveServerUrl({ publicUrl, port: resolvedPort }) }),
|
|
152730
153666
|
void 0,
|
|
152731
153667
|
"console"
|
|
152732
153668
|
);
|
|
152733
153669
|
}
|
|
152734
153670
|
const shutdown = async () => {
|
|
152735
|
-
|
|
153671
|
+
log11.info("Shutting down...");
|
|
152736
153672
|
await server.close();
|
|
152737
153673
|
process.exit(0);
|
|
152738
153674
|
};
|
|
@@ -152740,7 +153676,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152740
153676
|
process.on("SIGINT", shutdown);
|
|
152741
153677
|
process.on("SIGTERM", shutdown);
|
|
152742
153678
|
process.on("uncaughtException", (err) => {
|
|
152743
|
-
|
|
153679
|
+
log11.error(`uncaught: ${err.message}`, {
|
|
152744
153680
|
error: err.message,
|
|
152745
153681
|
stack: err.stack,
|
|
152746
153682
|
event: "process.uncaught"
|
|
@@ -152749,7 +153685,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152749
153685
|
});
|
|
152750
153686
|
process.on("unhandledRejection", (reason) => {
|
|
152751
153687
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
152752
|
-
|
|
153688
|
+
log11.error(`unhandled rejection: ${msg}`, {
|
|
152753
153689
|
error: msg,
|
|
152754
153690
|
event: "process.unhandled_rejection"
|
|
152755
153691
|
});
|
|
@@ -152774,10 +153710,10 @@ program2.command("cache").description("Manage the local SQLite conversation cach
|
|
|
152774
153710
|
const f2 = dbPath + suffix;
|
|
152775
153711
|
if (existsSync15(f2)) {
|
|
152776
153712
|
rmSync6(f2);
|
|
152777
|
-
|
|
153713
|
+
log11.info(`Deleted ${f2}`, { path: f2 }, "console");
|
|
152778
153714
|
}
|
|
152779
153715
|
}
|
|
152780
|
-
|
|
153716
|
+
log11.info("Cache cleared. Restart the server to rebuild.", void 0, "console");
|
|
152781
153717
|
})
|
|
152782
153718
|
);
|
|
152783
153719
|
program2.command("pair").description("Print a pairing QR code (server must already be running)").option("-p, --port <number>", "Port the server is listening on", "8766").action(async (opts) => {
|
|
@@ -152802,7 +153738,7 @@ program2.command("set-key [key]").description("Set the streamer API key in ~/.th
|
|
|
152802
153738
|
program2.command("update").description("Check for streamer updates from GitHub Releases and install them").option("--check", "Check only; do not install", false).option("--version <version>", "Pin to a specific release tag").option("--allow-major", "Allow a major-version bump", false).option("--force", "Skip the active-session defer check", false).option("--dry-run", "Print what would be installed without writing to disk", false).option("-p, --port <number>", "Port of the running streamer for active-session check", "8766").action(async (opts) => {
|
|
152803
153739
|
const cfg = loadUpdateConfig();
|
|
152804
153740
|
if (!cfg) {
|
|
152805
|
-
|
|
153741
|
+
log11.warn(
|
|
152806
153742
|
`No update config found at ${DEFAULT_CONFIG_PATH}. Create one with at least 'github_repo: owner/name' to enable updates.`,
|
|
152807
153743
|
void 0,
|
|
152808
153744
|
"console"
|
|
@@ -152821,11 +153757,11 @@ program2.command("update").description("Check for streamer updates from GitHub R
|
|
|
152821
153757
|
appendUpdateLog(
|
|
152822
153758
|
`[check] current=${result2.current} latest=${result2.latest ?? "none"} status=${result2.reason}`
|
|
152823
153759
|
);
|
|
152824
|
-
|
|
152825
|
-
|
|
152826
|
-
|
|
152827
|
-
|
|
152828
|
-
|
|
153760
|
+
log11.info(`Current : ${result2.current}`, void 0, "console");
|
|
153761
|
+
log11.info(`Latest : ${result2.latest ?? "(none)"}`, void 0, "console");
|
|
153762
|
+
log11.info(`Channel : ${cfg.channel}`, void 0, "console");
|
|
153763
|
+
log11.info(`Diff : ${result2.diff ?? "(none)"}`, void 0, "console");
|
|
153764
|
+
log11.info(`Status : ${result2.reason}`, void 0, "console");
|
|
152829
153765
|
return;
|
|
152830
153766
|
}
|
|
152831
153767
|
const port = Number.parseInt(opts.port, 10);
|
|
@@ -152841,20 +153777,20 @@ program2.command("update").description("Check for streamer updates from GitHub R
|
|
|
152841
153777
|
});
|
|
152842
153778
|
switch (result.kind) {
|
|
152843
153779
|
case "no-op":
|
|
152844
|
-
|
|
152845
|
-
|
|
152846
|
-
|
|
153780
|
+
log11.info(`Current : ${result.current}`, void 0, "console");
|
|
153781
|
+
log11.info(`Latest : ${result.latest ?? "(none)"}`, void 0, "console");
|
|
153782
|
+
log11.info(`Status : ${result.reason}`, void 0, "console");
|
|
152847
153783
|
break;
|
|
152848
153784
|
case "unsupported-install":
|
|
152849
|
-
|
|
153785
|
+
log11.warn(result.reason, void 0, "console");
|
|
152850
153786
|
process.exitCode = 2;
|
|
152851
153787
|
break;
|
|
152852
153788
|
case "deferred":
|
|
152853
|
-
|
|
153789
|
+
log11.warn(`Deferred: ${result.reason}`, void 0, "console");
|
|
152854
153790
|
process.exitCode = 2;
|
|
152855
153791
|
break;
|
|
152856
153792
|
case "dry-run":
|
|
152857
|
-
|
|
153793
|
+
log11.info(
|
|
152858
153794
|
`Would install ${result.latest} from ${result.tarballUrl}`,
|
|
152859
153795
|
void 0,
|
|
152860
153796
|
"console"
|
|
@@ -152862,28 +153798,28 @@ program2.command("update").description("Check for streamer updates from GitHub R
|
|
|
152862
153798
|
break;
|
|
152863
153799
|
case "installed":
|
|
152864
153800
|
if (result.restart.method.startsWith("failed:")) {
|
|
152865
|
-
|
|
153801
|
+
log11.error(
|
|
152866
153802
|
`Installed ${result.installed} on disk, but the running service was not updated. Restart: ${result.restart.method}.`,
|
|
152867
153803
|
void 0,
|
|
152868
153804
|
"console"
|
|
152869
153805
|
);
|
|
152870
153806
|
process.exitCode = 1;
|
|
152871
153807
|
} else {
|
|
152872
|
-
|
|
153808
|
+
log11.info(
|
|
152873
153809
|
`Installed ${result.installed} (was ${result.previous}). Restart: ${result.restart.method}.`,
|
|
152874
153810
|
void 0,
|
|
152875
153811
|
"console"
|
|
152876
153812
|
);
|
|
152877
153813
|
}
|
|
152878
153814
|
if (result.pruned.length > 0) {
|
|
152879
|
-
|
|
153815
|
+
log11.info(`Pruned old releases: ${result.pruned.join(", ")}`, void 0, "console");
|
|
152880
153816
|
}
|
|
152881
153817
|
break;
|
|
152882
153818
|
}
|
|
152883
153819
|
} catch (err) {
|
|
152884
153820
|
const message = err instanceof Error ? err.message : String(err);
|
|
152885
153821
|
appendUpdateLog(`[error] ${message}`);
|
|
152886
|
-
|
|
153822
|
+
log11.error(`Update failed: ${message}`, { error: message }, "console");
|
|
152887
153823
|
process.exitCode = 1;
|
|
152888
153824
|
}
|
|
152889
153825
|
});
|
|
@@ -152929,7 +153865,7 @@ async function printServerBanner({
|
|
|
152929
153865
|
}) {
|
|
152930
153866
|
const url2 = resolveServerUrl({ publicUrl, port });
|
|
152931
153867
|
if (!includeQr) {
|
|
152932
|
-
|
|
153868
|
+
log11.info(printUrlBanner({ url: url2 }), void 0, "console");
|
|
152933
153869
|
return;
|
|
152934
153870
|
}
|
|
152935
153871
|
const res = await fetch(`http://localhost:${port}/api/pair/start`, {
|
|
@@ -152944,9 +153880,9 @@ async function printServerBanner({
|
|
|
152944
153880
|
const expSeconds = Math.floor(expiresAt / 1e3);
|
|
152945
153881
|
const payload = `threadbase://pair?url=${encodeURIComponent(url2)}&token=${token}&exp=${expSeconds}`;
|
|
152946
153882
|
const qr2 = await generateQr(payload);
|
|
152947
|
-
|
|
152948
|
-
|
|
152949
|
-
|
|
153883
|
+
log11.info(printUrlBanner({ url: url2, qr: qr2, expiresAt }), void 0, "console");
|
|
153884
|
+
log11.info(`Pair URL: ${payload}`, void 0, "console");
|
|
153885
|
+
log11.info(`Expires in ${expiresInSeconds}s`, void 0, "console");
|
|
152950
153886
|
}
|
|
152951
153887
|
/*! Bundled license information:
|
|
152952
153888
|
|