@threadbase-sh/streamer 1.37.0 → 1.39.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 +1487 -387
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +1223 -181
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +175 -8
- package/dist/index.d.ts +175 -8
- package/dist/index.js +1221 -179
- 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/migrations/014_add_conversation_meta_file_path_index.sql +5 -0
- package/dist/pg-migrations/007_create_push_tokens.sql +93 -0
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -1490,6 +1490,9 @@ function isPermissionMode(value) {
|
|
|
1490
1490
|
function isDangerousPermissionMode(mode) {
|
|
1491
1491
|
return DANGEROUS_PERMISSION_MODES.includes(mode);
|
|
1492
1492
|
}
|
|
1493
|
+
function isEffortLevel(value) {
|
|
1494
|
+
return typeof value === "string" && EFFORT_LEVELS.includes(value);
|
|
1495
|
+
}
|
|
1493
1496
|
function findFlag(id) {
|
|
1494
1497
|
return CLAUDE_FLAGS.find((f2) => f2.id === id);
|
|
1495
1498
|
}
|
|
@@ -1554,7 +1557,7 @@ function buildFlagArgs(values, extraArgs) {
|
|
|
1554
1557
|
const args = [];
|
|
1555
1558
|
const safe = validateFlagValues(values ?? {});
|
|
1556
1559
|
for (const def of CLAUDE_FLAGS) {
|
|
1557
|
-
if (def.id
|
|
1560
|
+
if (SPAWN_POSITIONAL_FLAG_IDS.has(def.id)) continue;
|
|
1558
1561
|
const value = safe[def.id];
|
|
1559
1562
|
if (value === void 0) continue;
|
|
1560
1563
|
if (def.valueType === "boolean") {
|
|
@@ -1577,7 +1580,7 @@ function buildSettingsJson(permissionMode) {
|
|
|
1577
1580
|
}
|
|
1578
1581
|
return JSON.stringify(settings);
|
|
1579
1582
|
}
|
|
1580
|
-
var PERMISSION_MODES, DANGEROUS_PERMISSION_MODES, CLAUDE_FLAGS;
|
|
1583
|
+
var PERMISSION_MODES, DANGEROUS_PERMISSION_MODES, EFFORT_LEVELS, CLAUDE_FLAGS, SPAWN_POSITIONAL_FLAG_IDS;
|
|
1581
1584
|
var init_claude_flags = __esm({
|
|
1582
1585
|
"src/claude-flags.ts"() {
|
|
1583
1586
|
"use strict";
|
|
@@ -1593,6 +1596,7 @@ var init_claude_flags = __esm({
|
|
|
1593
1596
|
"bypassPermissions",
|
|
1594
1597
|
"dontAsk"
|
|
1595
1598
|
];
|
|
1599
|
+
EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
1596
1600
|
CLAUDE_FLAGS = [
|
|
1597
1601
|
{
|
|
1598
1602
|
id: "permissionMode",
|
|
@@ -1604,9 +1608,10 @@ var init_claude_flags = __esm({
|
|
|
1604
1608
|
{ id: "addDir", flag: "--add-dir", valueType: "list", risk: "elevated" },
|
|
1605
1609
|
{ id: "allowedTools", flag: "--allowedTools", valueType: "list", risk: "elevated" },
|
|
1606
1610
|
{ id: "disallowedTools", flag: "--disallowedTools", valueType: "list", risk: "low" },
|
|
1607
|
-
{ id: "
|
|
1608
|
-
{ id: "
|
|
1611
|
+
{ id: "model", flag: "--model", valueType: "string", risk: "low" },
|
|
1612
|
+
{ id: "effort", flag: "--effort", valueType: "enum", enumValues: EFFORT_LEVELS, risk: "low" }
|
|
1609
1613
|
];
|
|
1614
|
+
SPAWN_POSITIONAL_FLAG_IDS = /* @__PURE__ */ new Set(["permissionMode", "model", "effort"]);
|
|
1610
1615
|
}
|
|
1611
1616
|
});
|
|
1612
1617
|
|
|
@@ -4632,11 +4637,11 @@ var require_tools = __commonJS({
|
|
|
4632
4637
|
}
|
|
4633
4638
|
}
|
|
4634
4639
|
}
|
|
4635
|
-
function buildFormatters(level, bindings,
|
|
4640
|
+
function buildFormatters(level, bindings, log12) {
|
|
4636
4641
|
return {
|
|
4637
4642
|
level,
|
|
4638
4643
|
bindings,
|
|
4639
|
-
log:
|
|
4644
|
+
log: log12
|
|
4640
4645
|
};
|
|
4641
4646
|
}
|
|
4642
4647
|
function normalizeDestFileDescriptor(destination) {
|
|
@@ -5018,11 +5023,11 @@ var require_proto = __commonJS({
|
|
|
5018
5023
|
}
|
|
5019
5024
|
} else instance[serializersSym] = serializers;
|
|
5020
5025
|
if (options.hasOwnProperty("formatters")) {
|
|
5021
|
-
const { level, bindings: chindings, log:
|
|
5026
|
+
const { level, bindings: chindings, log: log12 } = options.formatters;
|
|
5022
5027
|
instance[formattersSym] = buildFormatters(
|
|
5023
5028
|
level || formatters.level,
|
|
5024
5029
|
chindings || resetChildingsFormatter,
|
|
5025
|
-
|
|
5030
|
+
log12 || formatters.log
|
|
5026
5031
|
);
|
|
5027
5032
|
} else {
|
|
5028
5033
|
instance[formattersSym] = buildFormatters(
|
|
@@ -5932,7 +5937,7 @@ var require_pino = __commonJS({
|
|
|
5932
5937
|
} = symbols;
|
|
5933
5938
|
var { epochTime, nullTime } = time3;
|
|
5934
5939
|
var { pid } = process;
|
|
5935
|
-
var
|
|
5940
|
+
var hostname5 = os2.hostname();
|
|
5936
5941
|
var defaultErrorSerializer = stdSerializers.err;
|
|
5937
5942
|
var defaultOptions2 = {
|
|
5938
5943
|
level: "info",
|
|
@@ -5942,7 +5947,7 @@ var require_pino = __commonJS({
|
|
|
5942
5947
|
errorKey: "err",
|
|
5943
5948
|
nestedKey: null,
|
|
5944
5949
|
enabled: true,
|
|
5945
|
-
base: { pid, hostname:
|
|
5950
|
+
base: { pid, hostname: hostname5 },
|
|
5946
5951
|
serializers: Object.assign(/* @__PURE__ */ Object.create(null), {
|
|
5947
5952
|
err: defaultErrorSerializer
|
|
5948
5953
|
}),
|
|
@@ -6133,6 +6138,86 @@ var init_logger = __esm({
|
|
|
6133
6138
|
}
|
|
6134
6139
|
});
|
|
6135
6140
|
|
|
6141
|
+
// src/feature-flags.ts
|
|
6142
|
+
function findFeatureFlag(id) {
|
|
6143
|
+
return FEATURE_FLAGS.find((f2) => f2.id === id);
|
|
6144
|
+
}
|
|
6145
|
+
function parseBooleanEnv(raw2) {
|
|
6146
|
+
if (raw2 === void 0) return void 0;
|
|
6147
|
+
const v2 = raw2.trim().toLowerCase();
|
|
6148
|
+
if (v2 === "") return false;
|
|
6149
|
+
return !(v2 === "0" || v2 === "false" || v2 === "no" || v2 === "off");
|
|
6150
|
+
}
|
|
6151
|
+
function validateFeatureFlagValues(raw2) {
|
|
6152
|
+
if (!raw2 || typeof raw2 !== "object" || Array.isArray(raw2)) return {};
|
|
6153
|
+
const out = {};
|
|
6154
|
+
const dropped = [];
|
|
6155
|
+
for (const [id, value] of Object.entries(raw2)) {
|
|
6156
|
+
if (!findFeatureFlag(id) || typeof value !== "boolean") {
|
|
6157
|
+
dropped.push(id);
|
|
6158
|
+
continue;
|
|
6159
|
+
}
|
|
6160
|
+
out[id] = value;
|
|
6161
|
+
}
|
|
6162
|
+
if (dropped.length > 0) {
|
|
6163
|
+
getLogger("feature-flags").warn(
|
|
6164
|
+
`Ignoring unknown or non-boolean feature flags: ${dropped.join(", ")}`,
|
|
6165
|
+
{
|
|
6166
|
+
event: "config.feature_flags_dropped",
|
|
6167
|
+
dropped
|
|
6168
|
+
}
|
|
6169
|
+
);
|
|
6170
|
+
}
|
|
6171
|
+
return out;
|
|
6172
|
+
}
|
|
6173
|
+
function parseFeatureFlagArgs(entries) {
|
|
6174
|
+
const values = {};
|
|
6175
|
+
const errors = [];
|
|
6176
|
+
for (const entry of entries) {
|
|
6177
|
+
const eq = entry.indexOf("=");
|
|
6178
|
+
const id = (eq === -1 ? entry : entry.slice(0, eq)).trim();
|
|
6179
|
+
const rawValue = eq === -1 ? "true" : entry.slice(eq + 1).trim().toLowerCase();
|
|
6180
|
+
if (!findFeatureFlag(id)) {
|
|
6181
|
+
errors.push(
|
|
6182
|
+
`Unknown feature flag "${id}". Known flags: ${FEATURE_FLAGS.map((f2) => f2.id).join(", ")}`
|
|
6183
|
+
);
|
|
6184
|
+
continue;
|
|
6185
|
+
}
|
|
6186
|
+
if (rawValue !== "true" && rawValue !== "false") {
|
|
6187
|
+
errors.push(`Invalid value "${rawValue}" for feature flag "${id}" \u2014 expected true/false`);
|
|
6188
|
+
continue;
|
|
6189
|
+
}
|
|
6190
|
+
values[id] = rawValue === "true";
|
|
6191
|
+
}
|
|
6192
|
+
return { values, errors };
|
|
6193
|
+
}
|
|
6194
|
+
function resolveFeatureFlags(opts) {
|
|
6195
|
+
const env = opts?.env ?? process.env;
|
|
6196
|
+
const out = {};
|
|
6197
|
+
for (const def of FEATURE_FLAGS) {
|
|
6198
|
+
out[def.id] = parseBooleanEnv(env[def.env]) ?? opts?.cli?.[def.id] ?? opts?.yaml?.[def.id] ?? def.default;
|
|
6199
|
+
}
|
|
6200
|
+
return out;
|
|
6201
|
+
}
|
|
6202
|
+
function nonDefaultFeatureFlags(values) {
|
|
6203
|
+
return FEATURE_FLAGS.filter((f2) => values[f2.id] !== f2.default).map((f2) => f2.id);
|
|
6204
|
+
}
|
|
6205
|
+
var FEATURE_FLAGS;
|
|
6206
|
+
var init_feature_flags = __esm({
|
|
6207
|
+
"src/feature-flags.ts"() {
|
|
6208
|
+
"use strict";
|
|
6209
|
+
init_logger();
|
|
6210
|
+
FEATURE_FLAGS = [
|
|
6211
|
+
{
|
|
6212
|
+
id: "codexSystemPrompt",
|
|
6213
|
+
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.",
|
|
6214
|
+
default: false,
|
|
6215
|
+
env: "THREADBASE_FEATURE_CODEX_SYSTEM_PROMPT"
|
|
6216
|
+
}
|
|
6217
|
+
];
|
|
6218
|
+
}
|
|
6219
|
+
});
|
|
6220
|
+
|
|
6136
6221
|
// src/auth.ts
|
|
6137
6222
|
function configDir() {
|
|
6138
6223
|
return process.env.THREADBASE_CONFIG_DIR ?? (0, import_path.join)((0, import_os.homedir)(), ".threadbase");
|
|
@@ -6297,6 +6382,21 @@ function setClaudeExtraArgs(text) {
|
|
|
6297
6382
|
}
|
|
6298
6383
|
setConfigValue("claude_extra_args", trimmed && trimmed.length > 0 ? trimmed : void 0);
|
|
6299
6384
|
}
|
|
6385
|
+
function loadFeatureFlags() {
|
|
6386
|
+
try {
|
|
6387
|
+
const content = (0, import_fs.readFileSync)(configFile(), "utf-8");
|
|
6388
|
+
const match2 = content.match(/^feature_flags:\s*(.+)$/m);
|
|
6389
|
+
if (!match2?.[1]) return {};
|
|
6390
|
+
return validateFeatureFlagValues(JSON.parse(match2[1].trim()));
|
|
6391
|
+
} catch (err) {
|
|
6392
|
+
if (err.code !== "ENOENT") {
|
|
6393
|
+
getLogger("auth").warn(`Ignoring unreadable feature_flags in server.yaml: ${String(err)}`, {
|
|
6394
|
+
event: "config.feature_flags_parse_failed"
|
|
6395
|
+
});
|
|
6396
|
+
}
|
|
6397
|
+
return {};
|
|
6398
|
+
}
|
|
6399
|
+
}
|
|
6300
6400
|
function validatePublicUrl(raw2) {
|
|
6301
6401
|
let parsed;
|
|
6302
6402
|
try {
|
|
@@ -6354,6 +6454,7 @@ var init_auth = __esm({
|
|
|
6354
6454
|
import_os = require("os");
|
|
6355
6455
|
import_path = require("path");
|
|
6356
6456
|
init_claude_flags();
|
|
6457
|
+
init_feature_flags();
|
|
6357
6458
|
init_logger();
|
|
6358
6459
|
}
|
|
6359
6460
|
});
|
|
@@ -8078,7 +8179,7 @@ var require_merge = __commonJS({
|
|
|
8078
8179
|
var require_addPairToJSMap = __commonJS({
|
|
8079
8180
|
"node_modules/yaml/dist/nodes/addPairToJSMap.js"(exports2) {
|
|
8080
8181
|
"use strict";
|
|
8081
|
-
var
|
|
8182
|
+
var log12 = require_log();
|
|
8082
8183
|
var merge2 = require_merge();
|
|
8083
8184
|
var stringify = require_stringify();
|
|
8084
8185
|
var identity = require_identity();
|
|
@@ -8127,7 +8228,7 @@ var require_addPairToJSMap = __commonJS({
|
|
|
8127
8228
|
let jsonStr = JSON.stringify(strKey);
|
|
8128
8229
|
if (jsonStr.length > 40)
|
|
8129
8230
|
jsonStr = jsonStr.substring(0, 36) + '..."';
|
|
8130
|
-
|
|
8231
|
+
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
8232
|
ctx.mapKeyWarned = true;
|
|
8132
8233
|
}
|
|
8133
8234
|
return strKey;
|
|
@@ -13543,7 +13644,7 @@ var require_public_api = __commonJS({
|
|
|
13543
13644
|
var composer = require_composer();
|
|
13544
13645
|
var Document = require_Document();
|
|
13545
13646
|
var errors = require_errors();
|
|
13546
|
-
var
|
|
13647
|
+
var log12 = require_log();
|
|
13547
13648
|
var identity = require_identity();
|
|
13548
13649
|
var lineCounter = require_line_counter();
|
|
13549
13650
|
var parser = require_parser();
|
|
@@ -13595,7 +13696,7 @@ var require_public_api = __commonJS({
|
|
|
13595
13696
|
const doc = parseDocument(src, options);
|
|
13596
13697
|
if (!doc)
|
|
13597
13698
|
return null;
|
|
13598
|
-
doc.warnings.forEach((warning) =>
|
|
13699
|
+
doc.warnings.forEach((warning) => log12.warn(doc.options.logLevel, warning));
|
|
13599
13700
|
if (doc.errors.length > 0) {
|
|
13600
13701
|
if (doc.options.logLevel !== "silent")
|
|
13601
13702
|
throw doc.errors[0];
|
|
@@ -14354,8 +14455,8 @@ function uint8ArrayToBase64(bytes) {
|
|
|
14354
14455
|
}
|
|
14355
14456
|
return btoa(binaryString);
|
|
14356
14457
|
}
|
|
14357
|
-
function base64urlToUint8Array(
|
|
14358
|
-
const base643 =
|
|
14458
|
+
function base64urlToUint8Array(base64url4) {
|
|
14459
|
+
const base643 = base64url4.replace(/-/g, "+").replace(/_/g, "/");
|
|
14359
14460
|
const padding = "=".repeat((4 - base643.length % 4) % 4);
|
|
14360
14461
|
return base64ToUint8Array(base643 + padding);
|
|
14361
14462
|
}
|
|
@@ -37111,7 +37212,7 @@ var require_logging = __commonJS({
|
|
|
37111
37212
|
_logVerbosity = verbosity;
|
|
37112
37213
|
};
|
|
37113
37214
|
exports2.setLoggerVerbosity = setLoggerVerbosity;
|
|
37114
|
-
var
|
|
37215
|
+
var log12 = (severity, ...args) => {
|
|
37115
37216
|
let logFunction;
|
|
37116
37217
|
if (severity >= _logVerbosity) {
|
|
37117
37218
|
switch (severity) {
|
|
@@ -37133,7 +37234,7 @@ var require_logging = __commonJS({
|
|
|
37133
37234
|
}
|
|
37134
37235
|
}
|
|
37135
37236
|
};
|
|
37136
|
-
exports2.log =
|
|
37237
|
+
exports2.log = log12;
|
|
37137
37238
|
var tracersString = (_d = (_c = process.env.GRPC_NODE_TRACE) !== null && _c !== void 0 ? _c : process.env.GRPC_TRACE) !== null && _d !== void 0 ? _d : "";
|
|
37138
37239
|
var enabledTracers = /* @__PURE__ */ new Set();
|
|
37139
37240
|
var disabledTracers = /* @__PURE__ */ new Set();
|
|
@@ -38519,8 +38620,8 @@ var require_service_config = __commonJS({
|
|
|
38519
38620
|
}
|
|
38520
38621
|
if (Array.isArray(validatedConfig.clientHostname)) {
|
|
38521
38622
|
let hostnameMatched = false;
|
|
38522
|
-
for (const
|
|
38523
|
-
if (
|
|
38623
|
+
for (const hostname5 of validatedConfig.clientHostname) {
|
|
38624
|
+
if (hostname5 === os2.hostname()) {
|
|
38524
38625
|
hostnameMatched = true;
|
|
38525
38626
|
}
|
|
38526
38627
|
}
|
|
@@ -51089,8 +51190,8 @@ var require_single_subchannel_channel = __commonJS({
|
|
|
51089
51190
|
if (splitPath2.length >= 2) {
|
|
51090
51191
|
serviceName = splitPath2[1];
|
|
51091
51192
|
}
|
|
51092
|
-
const
|
|
51093
|
-
this.serviceUrl = `https://${
|
|
51193
|
+
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";
|
|
51194
|
+
this.serviceUrl = `https://${hostname5}/${serviceName}`;
|
|
51094
51195
|
const timeout = (0, deadline_1.getRelativeTimeout)(options.deadline);
|
|
51095
51196
|
if (timeout !== Infinity) {
|
|
51096
51197
|
if (timeout <= 0) {
|
|
@@ -51737,8 +51838,8 @@ var require_resolver_dns = __commonJS({
|
|
|
51737
51838
|
}
|
|
51738
51839
|
trace("Looking up DNS hostname " + this.dnsHostname);
|
|
51739
51840
|
this.latestLookupResult = null;
|
|
51740
|
-
const
|
|
51741
|
-
this.pendingLookupPromise = this.lookup(
|
|
51841
|
+
const hostname5 = this.dnsHostname;
|
|
51842
|
+
this.pendingLookupPromise = this.lookup(hostname5);
|
|
51742
51843
|
this.pendingLookupPromise.then((addressList) => {
|
|
51743
51844
|
if (this.pendingLookupPromise === null) {
|
|
51744
51845
|
return;
|
|
@@ -51761,7 +51862,7 @@ var require_resolver_dns = __commonJS({
|
|
|
51761
51862
|
this.listener((0, call_interface_1.statusOrFromError)(this.defaultResolutionError), {}, this.latestServiceConfigResult, "");
|
|
51762
51863
|
});
|
|
51763
51864
|
if (this.isServiceConfigEnabled && this.pendingTxtPromise === null) {
|
|
51764
|
-
this.pendingTxtPromise = this.resolveTxt(
|
|
51865
|
+
this.pendingTxtPromise = this.resolveTxt(hostname5);
|
|
51765
51866
|
this.pendingTxtPromise.then((txtRecord) => {
|
|
51766
51867
|
if (this.pendingTxtPromise === null) {
|
|
51767
51868
|
return;
|
|
@@ -51803,12 +51904,12 @@ var require_resolver_dns = __commonJS({
|
|
|
51803
51904
|
this.continueResolving = true;
|
|
51804
51905
|
}
|
|
51805
51906
|
}
|
|
51806
|
-
async lookup(
|
|
51907
|
+
async lookup(hostname5) {
|
|
51807
51908
|
if (environment_1.GRPC_NODE_USE_ALTERNATIVE_RESOLVER) {
|
|
51808
51909
|
trace("Using alternative DNS resolver.");
|
|
51809
51910
|
const records = await Promise.allSettled([
|
|
51810
|
-
this.alternativeResolver.resolve4(
|
|
51811
|
-
this.alternativeResolver.resolve6(
|
|
51911
|
+
this.alternativeResolver.resolve4(hostname5),
|
|
51912
|
+
this.alternativeResolver.resolve6(hostname5)
|
|
51812
51913
|
]);
|
|
51813
51914
|
if (records.every((result) => result.status === "rejected")) {
|
|
51814
51915
|
throw new Error(records[0].reason);
|
|
@@ -51820,15 +51921,15 @@ var require_resolver_dns = __commonJS({
|
|
|
51820
51921
|
port: +this.port
|
|
51821
51922
|
}));
|
|
51822
51923
|
}
|
|
51823
|
-
const addressList = await dns_1.promises.lookup(
|
|
51924
|
+
const addressList = await dns_1.promises.lookup(hostname5, { all: true });
|
|
51824
51925
|
return addressList.map((addr) => ({ host: addr.address, port: +this.port }));
|
|
51825
51926
|
}
|
|
51826
|
-
async resolveTxt(
|
|
51927
|
+
async resolveTxt(hostname5) {
|
|
51827
51928
|
if (environment_1.GRPC_NODE_USE_ALTERNATIVE_RESOLVER) {
|
|
51828
51929
|
trace("Using alternative DNS resolver.");
|
|
51829
|
-
return this.alternativeResolver.resolveTxt(
|
|
51930
|
+
return this.alternativeResolver.resolveTxt(hostname5);
|
|
51830
51931
|
}
|
|
51831
|
-
return dns_1.promises.resolveTxt(
|
|
51932
|
+
return dns_1.promises.resolveTxt(hostname5);
|
|
51832
51933
|
}
|
|
51833
51934
|
startNextResolutionTimer() {
|
|
51834
51935
|
var _a3, _b;
|
|
@@ -51956,13 +52057,13 @@ var require_http_proxy = __commonJS({
|
|
|
51956
52057
|
userCred = proxyUrl.username;
|
|
51957
52058
|
}
|
|
51958
52059
|
}
|
|
51959
|
-
const
|
|
52060
|
+
const hostname5 = proxyUrl.hostname;
|
|
51960
52061
|
let port = proxyUrl.port;
|
|
51961
52062
|
if (port === "") {
|
|
51962
52063
|
port = "80";
|
|
51963
52064
|
}
|
|
51964
52065
|
const result = {
|
|
51965
|
-
address: `${
|
|
52066
|
+
address: `${hostname5}:${port}`
|
|
51966
52067
|
};
|
|
51967
52068
|
if (userCred) {
|
|
51968
52069
|
result.creds = userCred;
|
|
@@ -53275,8 +53376,8 @@ var require_load_balancing_call = __commonJS({
|
|
|
53275
53376
|
if (splitPath2.length >= 2) {
|
|
53276
53377
|
serviceName = splitPath2[1];
|
|
53277
53378
|
}
|
|
53278
|
-
const
|
|
53279
|
-
this.serviceUrl = `https://${
|
|
53379
|
+
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";
|
|
53380
|
+
this.serviceUrl = `https://${hostname5}/${serviceName}`;
|
|
53280
53381
|
this.startTime = /* @__PURE__ */ new Date();
|
|
53281
53382
|
}
|
|
53282
53383
|
getDeadlineInfo() {
|
|
@@ -91085,9 +91186,9 @@ var require_parse_host_uri = __commonJS({
|
|
|
91085
91186
|
var ipv4Hostname = "(?:\\d{1,3}(?:\\.\\d{1,3}){3})";
|
|
91086
91187
|
var ipv6Hostname = "(?:\\[(?<ipv6>[0-9a-fA-F.:]+)\\])";
|
|
91087
91188
|
var dnsHostname = "(?:[^:/]+)";
|
|
91088
|
-
var
|
|
91189
|
+
var hostname5 = `(?:${ipv4Hostname}|${ipv6Hostname}|${dnsHostname})`;
|
|
91089
91190
|
var port = "(?::(?<port>\\d+))";
|
|
91090
|
-
var protoHostPortRegex = new RegExp(`^${scheme}??(?<hostname>${
|
|
91191
|
+
var protoHostPortRegex = new RegExp(`^${scheme}??(?<hostname>${hostname5})${port}?$`);
|
|
91091
91192
|
function splitProtoHostPort(uri) {
|
|
91092
91193
|
const match2 = protoHostPortRegex.exec(uri);
|
|
91093
91194
|
if (!match2?.groups)
|
|
@@ -91099,9 +91200,9 @@ var require_parse_host_uri = __commonJS({
|
|
|
91099
91200
|
};
|
|
91100
91201
|
}
|
|
91101
91202
|
function joinProtoHostPort(components) {
|
|
91102
|
-
const { scheme: scheme2, hostname:
|
|
91203
|
+
const { scheme: scheme2, hostname: hostname6, port: port2 } = components;
|
|
91103
91204
|
const schemeText = scheme2 ? `${scheme2}:` : "";
|
|
91104
|
-
const hostnameText =
|
|
91205
|
+
const hostnameText = hostname6.includes(":") ? `[${hostname6}]` : hostname6;
|
|
91105
91206
|
const portText = port2 !== void 0 ? `:${port2}` : "";
|
|
91106
91207
|
return `${schemeText}${hostnameText}${portText}`;
|
|
91107
91208
|
}
|
|
@@ -98687,7 +98788,7 @@ var require_scan = __commonJS({
|
|
|
98687
98788
|
var require_parse4 = __commonJS({
|
|
98688
98789
|
"node_modules/picomatch/lib/parse.js"(exports2, module2) {
|
|
98689
98790
|
"use strict";
|
|
98690
|
-
var
|
|
98791
|
+
var constants2 = require_constants5();
|
|
98691
98792
|
var utils = require_utils2();
|
|
98692
98793
|
var {
|
|
98693
98794
|
MAX_LENGTH,
|
|
@@ -98695,7 +98796,7 @@ var require_parse4 = __commonJS({
|
|
|
98695
98796
|
REGEX_NON_SPECIAL_CHARS,
|
|
98696
98797
|
REGEX_SPECIAL_CHARS_BACKREF,
|
|
98697
98798
|
REPLACEMENTS
|
|
98698
|
-
} =
|
|
98799
|
+
} = constants2;
|
|
98699
98800
|
var expandRange = (args, options) => {
|
|
98700
98801
|
if (typeof options.expandRange === "function") {
|
|
98701
98802
|
return options.expandRange(...args, options);
|
|
@@ -98901,7 +99002,7 @@ var require_parse4 = __commonJS({
|
|
|
98901
99002
|
if (options.maxExtglobRecursion === false) {
|
|
98902
99003
|
return { risky: false };
|
|
98903
99004
|
}
|
|
98904
|
-
const max = typeof options.maxExtglobRecursion === "number" ? options.maxExtglobRecursion :
|
|
99005
|
+
const max = typeof options.maxExtglobRecursion === "number" ? options.maxExtglobRecursion : constants2.DEFAULT_MAX_EXTGLOB_RECURSION;
|
|
98905
99006
|
const branches = splitTopLevel(body).map((branch) => branch.trim());
|
|
98906
99007
|
if (branches.length > 1) {
|
|
98907
99008
|
if (branches.some((branch) => branch === "") || branches.some((branch) => /^[*?]+$/.test(branch)) || hasRepeatedCharPrefixOverlap(branches)) {
|
|
@@ -98934,8 +99035,8 @@ var require_parse4 = __commonJS({
|
|
|
98934
99035
|
const tokens = [bos];
|
|
98935
99036
|
const capture = opts.capture ? "" : "?:";
|
|
98936
99037
|
const win32 = utils.isWindows(options);
|
|
98937
|
-
const PLATFORM_CHARS =
|
|
98938
|
-
const EXTGLOB_CHARS =
|
|
99038
|
+
const PLATFORM_CHARS = constants2.globChars(win32);
|
|
99039
|
+
const EXTGLOB_CHARS = constants2.extglobChars(PLATFORM_CHARS);
|
|
98939
99040
|
const {
|
|
98940
99041
|
DOT_LITERAL,
|
|
98941
99042
|
PLUS_LITERAL,
|
|
@@ -99634,7 +99735,7 @@ var require_parse4 = __commonJS({
|
|
|
99634
99735
|
NO_DOTS_SLASH,
|
|
99635
99736
|
STAR,
|
|
99636
99737
|
START_ANCHOR
|
|
99637
|
-
} =
|
|
99738
|
+
} = constants2.globChars(win32);
|
|
99638
99739
|
const nodot = opts.dot ? NO_DOTS : NO_DOT;
|
|
99639
99740
|
const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
|
|
99640
99741
|
const capture = opts.capture ? "" : "?:";
|
|
@@ -99693,7 +99794,7 @@ var require_picomatch = __commonJS({
|
|
|
99693
99794
|
var scan = require_scan();
|
|
99694
99795
|
var parse3 = require_parse4();
|
|
99695
99796
|
var utils = require_utils2();
|
|
99696
|
-
var
|
|
99797
|
+
var constants2 = require_constants5();
|
|
99697
99798
|
var isObject2 = (val) => val && typeof val === "object" && !Array.isArray(val);
|
|
99698
99799
|
var picomatch = (glob, options, returnState = false) => {
|
|
99699
99800
|
if (Array.isArray(glob)) {
|
|
@@ -99821,7 +99922,7 @@ var require_picomatch = __commonJS({
|
|
|
99821
99922
|
return /$^/;
|
|
99822
99923
|
}
|
|
99823
99924
|
};
|
|
99824
|
-
picomatch.constants =
|
|
99925
|
+
picomatch.constants = constants2;
|
|
99825
99926
|
module2.exports = picomatch;
|
|
99826
99927
|
}
|
|
99827
99928
|
});
|
|
@@ -104039,11 +104140,11 @@ var require_pg_connection_string = __commonJS({
|
|
|
104039
104140
|
config2.client_encoding = result.searchParams.get("encoding");
|
|
104040
104141
|
return config2;
|
|
104041
104142
|
}
|
|
104042
|
-
const
|
|
104143
|
+
const hostname5 = dummyHost ? "" : result.hostname;
|
|
104043
104144
|
if (!config2.host) {
|
|
104044
|
-
config2.host = decodeURIComponent(
|
|
104045
|
-
} else if (
|
|
104046
|
-
result.pathname =
|
|
104145
|
+
config2.host = decodeURIComponent(hostname5);
|
|
104146
|
+
} else if (hostname5 && /^%2f/i.test(hostname5)) {
|
|
104147
|
+
result.pathname = hostname5 + result.pathname;
|
|
104047
104148
|
}
|
|
104048
104149
|
if (!config2.port) {
|
|
104049
104150
|
config2.port = result.port;
|
|
@@ -119963,7 +120064,7 @@ var require_crc = __commonJS({
|
|
|
119963
120064
|
var require_parser3 = __commonJS({
|
|
119964
120065
|
"node_modules/pngjs/lib/parser.js"(exports2, module2) {
|
|
119965
120066
|
"use strict";
|
|
119966
|
-
var
|
|
120067
|
+
var constants2 = require_constants7();
|
|
119967
120068
|
var CrcCalculator = require_crc();
|
|
119968
120069
|
var Parser = module2.exports = function(options, dependencies) {
|
|
119969
120070
|
this._options = options;
|
|
@@ -119974,12 +120075,12 @@ var require_parser3 = __commonJS({
|
|
|
119974
120075
|
this._palette = [];
|
|
119975
120076
|
this._colorType = 0;
|
|
119976
120077
|
this._chunks = {};
|
|
119977
|
-
this._chunks[
|
|
119978
|
-
this._chunks[
|
|
119979
|
-
this._chunks[
|
|
119980
|
-
this._chunks[
|
|
119981
|
-
this._chunks[
|
|
119982
|
-
this._chunks[
|
|
120078
|
+
this._chunks[constants2.TYPE_IHDR] = this._handleIHDR.bind(this);
|
|
120079
|
+
this._chunks[constants2.TYPE_IEND] = this._handleIEND.bind(this);
|
|
120080
|
+
this._chunks[constants2.TYPE_IDAT] = this._handleIDAT.bind(this);
|
|
120081
|
+
this._chunks[constants2.TYPE_PLTE] = this._handlePLTE.bind(this);
|
|
120082
|
+
this._chunks[constants2.TYPE_tRNS] = this._handleTRNS.bind(this);
|
|
120083
|
+
this._chunks[constants2.TYPE_gAMA] = this._handleGAMA.bind(this);
|
|
119983
120084
|
this.read = dependencies.read;
|
|
119984
120085
|
this.error = dependencies.error;
|
|
119985
120086
|
this.metadata = dependencies.metadata;
|
|
@@ -119994,10 +120095,10 @@ var require_parser3 = __commonJS({
|
|
|
119994
120095
|
};
|
|
119995
120096
|
};
|
|
119996
120097
|
Parser.prototype.start = function() {
|
|
119997
|
-
this.read(
|
|
120098
|
+
this.read(constants2.PNG_SIGNATURE.length, this._parseSignature.bind(this));
|
|
119998
120099
|
};
|
|
119999
120100
|
Parser.prototype._parseSignature = function(data) {
|
|
120000
|
-
let signature =
|
|
120101
|
+
let signature = constants2.PNG_SIGNATURE;
|
|
120001
120102
|
for (let i = 0; i < signature.length; i++) {
|
|
120002
120103
|
if (data[i] !== signature[i]) {
|
|
120003
120104
|
this.error(new Error("Invalid file signature"));
|
|
@@ -120014,7 +120115,7 @@ var require_parser3 = __commonJS({
|
|
|
120014
120115
|
name += String.fromCharCode(data[i]);
|
|
120015
120116
|
}
|
|
120016
120117
|
let ancillary = Boolean(data[4] & 32);
|
|
120017
|
-
if (!this._hasIHDR && type !==
|
|
120118
|
+
if (!this._hasIHDR && type !== constants2.TYPE_IHDR) {
|
|
120018
120119
|
this.error(new Error("Expected IHDR on beggining"));
|
|
120019
120120
|
return;
|
|
120020
120121
|
}
|
|
@@ -120062,7 +120163,7 @@ var require_parser3 = __commonJS({
|
|
|
120062
120163
|
this.error(new Error("Unsupported bit depth " + depth));
|
|
120063
120164
|
return;
|
|
120064
120165
|
}
|
|
120065
|
-
if (!(colorType in
|
|
120166
|
+
if (!(colorType in constants2.COLORTYPE_TO_BPP_MAP)) {
|
|
120066
120167
|
this.error(new Error("Unsupported color type"));
|
|
120067
120168
|
return;
|
|
120068
120169
|
}
|
|
@@ -120079,16 +120180,16 @@ var require_parser3 = __commonJS({
|
|
|
120079
120180
|
return;
|
|
120080
120181
|
}
|
|
120081
120182
|
this._colorType = colorType;
|
|
120082
|
-
let bpp =
|
|
120183
|
+
let bpp = constants2.COLORTYPE_TO_BPP_MAP[this._colorType];
|
|
120083
120184
|
this._hasIHDR = true;
|
|
120084
120185
|
this.metadata({
|
|
120085
120186
|
width,
|
|
120086
120187
|
height,
|
|
120087
120188
|
depth,
|
|
120088
120189
|
interlace: Boolean(interlace),
|
|
120089
|
-
palette: Boolean(colorType &
|
|
120090
|
-
color: Boolean(colorType &
|
|
120091
|
-
alpha: Boolean(colorType &
|
|
120190
|
+
palette: Boolean(colorType & constants2.COLORTYPE_PALETTE),
|
|
120191
|
+
color: Boolean(colorType & constants2.COLORTYPE_COLOR),
|
|
120192
|
+
alpha: Boolean(colorType & constants2.COLORTYPE_ALPHA),
|
|
120092
120193
|
bpp,
|
|
120093
120194
|
colorType
|
|
120094
120195
|
});
|
|
@@ -120112,7 +120213,7 @@ var require_parser3 = __commonJS({
|
|
|
120112
120213
|
};
|
|
120113
120214
|
Parser.prototype._parseTRNS = function(data) {
|
|
120114
120215
|
this._crc.write(data);
|
|
120115
|
-
if (this._colorType ===
|
|
120216
|
+
if (this._colorType === constants2.COLORTYPE_PALETTE_COLOR) {
|
|
120116
120217
|
if (this._palette.length === 0) {
|
|
120117
120218
|
this.error(new Error("Transparency chunk must be after palette"));
|
|
120118
120219
|
return;
|
|
@@ -120126,10 +120227,10 @@ var require_parser3 = __commonJS({
|
|
|
120126
120227
|
}
|
|
120127
120228
|
this.palette(this._palette);
|
|
120128
120229
|
}
|
|
120129
|
-
if (this._colorType ===
|
|
120230
|
+
if (this._colorType === constants2.COLORTYPE_GRAYSCALE) {
|
|
120130
120231
|
this.transColor([data.readUInt16BE(0)]);
|
|
120131
120232
|
}
|
|
120132
|
-
if (this._colorType ===
|
|
120233
|
+
if (this._colorType === constants2.COLORTYPE_COLOR) {
|
|
120133
120234
|
this.transColor([
|
|
120134
120235
|
data.readUInt16BE(0),
|
|
120135
120236
|
data.readUInt16BE(2),
|
|
@@ -120143,7 +120244,7 @@ var require_parser3 = __commonJS({
|
|
|
120143
120244
|
};
|
|
120144
120245
|
Parser.prototype._parseGAMA = function(data) {
|
|
120145
120246
|
this._crc.write(data);
|
|
120146
|
-
this.gamma(data.readUInt32BE(0) /
|
|
120247
|
+
this.gamma(data.readUInt32BE(0) / constants2.GAMMA_DIVISION);
|
|
120147
120248
|
this._handleChunkEnd();
|
|
120148
120249
|
};
|
|
120149
120250
|
Parser.prototype._handleIDAT = function(length) {
|
|
@@ -120155,7 +120256,7 @@ var require_parser3 = __commonJS({
|
|
|
120155
120256
|
};
|
|
120156
120257
|
Parser.prototype._parseIDAT = function(length, data) {
|
|
120157
120258
|
this._crc.write(data);
|
|
120158
|
-
if (this._colorType ===
|
|
120259
|
+
if (this._colorType === constants2.COLORTYPE_PALETTE_COLOR && this._palette.length === 0) {
|
|
120159
120260
|
throw new Error("Expected palette not found");
|
|
120160
120261
|
}
|
|
120161
120262
|
this.inflateData(data);
|
|
@@ -120643,9 +120744,9 @@ var require_parser_async = __commonJS({
|
|
|
120643
120744
|
var require_bitpacker = __commonJS({
|
|
120644
120745
|
"node_modules/pngjs/lib/bitpacker.js"(exports2, module2) {
|
|
120645
120746
|
"use strict";
|
|
120646
|
-
var
|
|
120747
|
+
var constants2 = require_constants7();
|
|
120647
120748
|
module2.exports = function(dataIn, width, height, options) {
|
|
120648
|
-
let outHasAlpha = [
|
|
120749
|
+
let outHasAlpha = [constants2.COLORTYPE_COLOR_ALPHA, constants2.COLORTYPE_ALPHA].indexOf(
|
|
120649
120750
|
options.colorType
|
|
120650
120751
|
) !== -1;
|
|
120651
120752
|
if (options.colorType === options.inputColorType) {
|
|
@@ -120665,11 +120766,11 @@ var require_bitpacker = __commonJS({
|
|
|
120665
120766
|
}
|
|
120666
120767
|
let data = options.bitDepth !== 16 ? dataIn : new Uint16Array(dataIn.buffer);
|
|
120667
120768
|
let maxValue = 255;
|
|
120668
|
-
let inBpp =
|
|
120769
|
+
let inBpp = constants2.COLORTYPE_TO_BPP_MAP[options.inputColorType];
|
|
120669
120770
|
if (inBpp === 4 && !options.inputHasAlpha) {
|
|
120670
120771
|
inBpp = 3;
|
|
120671
120772
|
}
|
|
120672
|
-
let outBpp =
|
|
120773
|
+
let outBpp = constants2.COLORTYPE_TO_BPP_MAP[options.colorType];
|
|
120673
120774
|
if (options.bitDepth === 16) {
|
|
120674
120775
|
maxValue = 65535;
|
|
120675
120776
|
outBpp *= 2;
|
|
@@ -120693,24 +120794,24 @@ var require_bitpacker = __commonJS({
|
|
|
120693
120794
|
let blue;
|
|
120694
120795
|
let alpha = maxValue;
|
|
120695
120796
|
switch (options.inputColorType) {
|
|
120696
|
-
case
|
|
120797
|
+
case constants2.COLORTYPE_COLOR_ALPHA:
|
|
120697
120798
|
alpha = data[inIndex + 3];
|
|
120698
120799
|
red = data[inIndex];
|
|
120699
120800
|
green = data[inIndex + 1];
|
|
120700
120801
|
blue = data[inIndex + 2];
|
|
120701
120802
|
break;
|
|
120702
|
-
case
|
|
120803
|
+
case constants2.COLORTYPE_COLOR:
|
|
120703
120804
|
red = data[inIndex];
|
|
120704
120805
|
green = data[inIndex + 1];
|
|
120705
120806
|
blue = data[inIndex + 2];
|
|
120706
120807
|
break;
|
|
120707
|
-
case
|
|
120808
|
+
case constants2.COLORTYPE_ALPHA:
|
|
120708
120809
|
alpha = data[inIndex + 1];
|
|
120709
120810
|
red = data[inIndex];
|
|
120710
120811
|
green = red;
|
|
120711
120812
|
blue = red;
|
|
120712
120813
|
break;
|
|
120713
|
-
case
|
|
120814
|
+
case constants2.COLORTYPE_GRAYSCALE:
|
|
120714
120815
|
red = data[inIndex];
|
|
120715
120816
|
green = red;
|
|
120716
120817
|
blue = red;
|
|
@@ -120743,8 +120844,8 @@ var require_bitpacker = __commonJS({
|
|
|
120743
120844
|
for (let x = 0; x < width; x++) {
|
|
120744
120845
|
let rgba = getRGBA(data, inIndex);
|
|
120745
120846
|
switch (options.colorType) {
|
|
120746
|
-
case
|
|
120747
|
-
case
|
|
120847
|
+
case constants2.COLORTYPE_COLOR_ALPHA:
|
|
120848
|
+
case constants2.COLORTYPE_COLOR:
|
|
120748
120849
|
if (options.bitDepth === 8) {
|
|
120749
120850
|
outData[outIndex] = rgba.red;
|
|
120750
120851
|
outData[outIndex + 1] = rgba.green;
|
|
@@ -120761,8 +120862,8 @@ var require_bitpacker = __commonJS({
|
|
|
120761
120862
|
}
|
|
120762
120863
|
}
|
|
120763
120864
|
break;
|
|
120764
|
-
case
|
|
120765
|
-
case
|
|
120865
|
+
case constants2.COLORTYPE_ALPHA:
|
|
120866
|
+
case constants2.COLORTYPE_GRAYSCALE: {
|
|
120766
120867
|
let grayscale = (rgba.red + rgba.green + rgba.blue) / 3;
|
|
120767
120868
|
if (options.bitDepth === 8) {
|
|
120768
120869
|
outData[outIndex] = grayscale;
|
|
@@ -120935,7 +121036,7 @@ var require_filter_pack = __commonJS({
|
|
|
120935
121036
|
var require_packer = __commonJS({
|
|
120936
121037
|
"node_modules/pngjs/lib/packer.js"(exports2, module2) {
|
|
120937
121038
|
"use strict";
|
|
120938
|
-
var
|
|
121039
|
+
var constants2 = require_constants7();
|
|
120939
121040
|
var CrcStream = require_crc();
|
|
120940
121041
|
var bitPacker = require_bitpacker();
|
|
120941
121042
|
var filter = require_filter_pack();
|
|
@@ -120948,23 +121049,23 @@ var require_packer = __commonJS({
|
|
|
120948
121049
|
options.inputHasAlpha = options.inputHasAlpha != null ? options.inputHasAlpha : true;
|
|
120949
121050
|
options.deflateFactory = options.deflateFactory || zlib.createDeflate;
|
|
120950
121051
|
options.bitDepth = options.bitDepth || 8;
|
|
120951
|
-
options.colorType = typeof options.colorType === "number" ? options.colorType :
|
|
120952
|
-
options.inputColorType = typeof options.inputColorType === "number" ? options.inputColorType :
|
|
121052
|
+
options.colorType = typeof options.colorType === "number" ? options.colorType : constants2.COLORTYPE_COLOR_ALPHA;
|
|
121053
|
+
options.inputColorType = typeof options.inputColorType === "number" ? options.inputColorType : constants2.COLORTYPE_COLOR_ALPHA;
|
|
120953
121054
|
if ([
|
|
120954
|
-
|
|
120955
|
-
|
|
120956
|
-
|
|
120957
|
-
|
|
121055
|
+
constants2.COLORTYPE_GRAYSCALE,
|
|
121056
|
+
constants2.COLORTYPE_COLOR,
|
|
121057
|
+
constants2.COLORTYPE_COLOR_ALPHA,
|
|
121058
|
+
constants2.COLORTYPE_ALPHA
|
|
120958
121059
|
].indexOf(options.colorType) === -1) {
|
|
120959
121060
|
throw new Error(
|
|
120960
121061
|
"option color type:" + options.colorType + " is not supported at present"
|
|
120961
121062
|
);
|
|
120962
121063
|
}
|
|
120963
121064
|
if ([
|
|
120964
|
-
|
|
120965
|
-
|
|
120966
|
-
|
|
120967
|
-
|
|
121065
|
+
constants2.COLORTYPE_GRAYSCALE,
|
|
121066
|
+
constants2.COLORTYPE_COLOR,
|
|
121067
|
+
constants2.COLORTYPE_COLOR_ALPHA,
|
|
121068
|
+
constants2.COLORTYPE_ALPHA
|
|
120968
121069
|
].indexOf(options.inputColorType) === -1) {
|
|
120969
121070
|
throw new Error(
|
|
120970
121071
|
"option input color type:" + options.inputColorType + " is not supported at present"
|
|
@@ -120988,7 +121089,7 @@ var require_packer = __commonJS({
|
|
|
120988
121089
|
};
|
|
120989
121090
|
Packer.prototype.filterData = function(data, width, height) {
|
|
120990
121091
|
let packedData = bitPacker(data, width, height, this._options);
|
|
120991
|
-
let bpp =
|
|
121092
|
+
let bpp = constants2.COLORTYPE_TO_BPP_MAP[this._options.colorType];
|
|
120992
121093
|
let filteredData = filter(packedData, width, height, this._options, bpp);
|
|
120993
121094
|
return filteredData;
|
|
120994
121095
|
};
|
|
@@ -121008,8 +121109,8 @@ var require_packer = __commonJS({
|
|
|
121008
121109
|
};
|
|
121009
121110
|
Packer.prototype.packGAMA = function(gamma) {
|
|
121010
121111
|
let buf = Buffer.alloc(4);
|
|
121011
|
-
buf.writeUInt32BE(Math.floor(gamma *
|
|
121012
|
-
return this._packChunk(
|
|
121112
|
+
buf.writeUInt32BE(Math.floor(gamma * constants2.GAMMA_DIVISION), 0);
|
|
121113
|
+
return this._packChunk(constants2.TYPE_gAMA, buf);
|
|
121013
121114
|
};
|
|
121014
121115
|
Packer.prototype.packIHDR = function(width, height) {
|
|
121015
121116
|
let buf = Buffer.alloc(13);
|
|
@@ -121020,13 +121121,13 @@ var require_packer = __commonJS({
|
|
|
121020
121121
|
buf[10] = 0;
|
|
121021
121122
|
buf[11] = 0;
|
|
121022
121123
|
buf[12] = 0;
|
|
121023
|
-
return this._packChunk(
|
|
121124
|
+
return this._packChunk(constants2.TYPE_IHDR, buf);
|
|
121024
121125
|
};
|
|
121025
121126
|
Packer.prototype.packIDAT = function(data) {
|
|
121026
|
-
return this._packChunk(
|
|
121127
|
+
return this._packChunk(constants2.TYPE_IDAT, data);
|
|
121027
121128
|
};
|
|
121028
121129
|
Packer.prototype.packIEND = function() {
|
|
121029
|
-
return this._packChunk(
|
|
121130
|
+
return this._packChunk(constants2.TYPE_IEND, null);
|
|
121030
121131
|
};
|
|
121031
121132
|
}
|
|
121032
121133
|
});
|
|
@@ -121037,7 +121138,7 @@ var require_packer_async = __commonJS({
|
|
|
121037
121138
|
"use strict";
|
|
121038
121139
|
var util = require("util");
|
|
121039
121140
|
var Stream = require("stream");
|
|
121040
|
-
var
|
|
121141
|
+
var constants2 = require_constants7();
|
|
121041
121142
|
var Packer = require_packer();
|
|
121042
121143
|
var PackerAsync = module2.exports = function(opt) {
|
|
121043
121144
|
Stream.call(this);
|
|
@@ -121048,7 +121149,7 @@ var require_packer_async = __commonJS({
|
|
|
121048
121149
|
};
|
|
121049
121150
|
util.inherits(PackerAsync, Stream);
|
|
121050
121151
|
PackerAsync.prototype.pack = function(data, width, height, gamma) {
|
|
121051
|
-
this.emit("data", Buffer.from(
|
|
121152
|
+
this.emit("data", Buffer.from(constants2.PNG_SIGNATURE));
|
|
121052
121153
|
this.emit("data", this._packer.packIHDR(width, height));
|
|
121053
121154
|
if (gamma) {
|
|
121054
121155
|
this.emit("data", this._packer.packGAMA(gamma));
|
|
@@ -121376,7 +121477,7 @@ var require_packer_sync = __commonJS({
|
|
|
121376
121477
|
if (!zlib.deflateSync) {
|
|
121377
121478
|
hasSyncZlib = false;
|
|
121378
121479
|
}
|
|
121379
|
-
var
|
|
121480
|
+
var constants2 = require_constants7();
|
|
121380
121481
|
var Packer = require_packer();
|
|
121381
121482
|
module2.exports = function(metaData, opt) {
|
|
121382
121483
|
if (!hasSyncZlib) {
|
|
@@ -121387,7 +121488,7 @@ var require_packer_sync = __commonJS({
|
|
|
121387
121488
|
let options = opt || {};
|
|
121388
121489
|
let packer = new Packer(options);
|
|
121389
121490
|
let chunks = [];
|
|
121390
|
-
chunks.push(Buffer.from(
|
|
121491
|
+
chunks.push(Buffer.from(constants2.PNG_SIGNATURE));
|
|
121391
121492
|
chunks.push(packer.packIHDR(metaData.width, metaData.height));
|
|
121392
121493
|
if (metaData.gamma) {
|
|
121393
121494
|
chunks.push(packer.packGAMA(metaData.gamma));
|
|
@@ -122490,10 +122591,10 @@ var require_truncate = __commonJS({
|
|
|
122490
122591
|
"node_modules/semver/functions/truncate.js"(exports2, module2) {
|
|
122491
122592
|
"use strict";
|
|
122492
122593
|
var parse3 = require_parse5();
|
|
122493
|
-
var
|
|
122594
|
+
var constants2 = require_constants8();
|
|
122494
122595
|
var SemVer = require_semver();
|
|
122495
122596
|
var truncate = (version2, truncation, options) => {
|
|
122496
|
-
if (!
|
|
122597
|
+
if (!constants2.RELEASE_TYPES.includes(truncation)) {
|
|
122497
122598
|
return null;
|
|
122498
122599
|
}
|
|
122499
122600
|
const clonedVersion = cloneInputVersion(version2, options);
|
|
@@ -123542,7 +123643,7 @@ var require_semver2 = __commonJS({
|
|
|
123542
123643
|
"node_modules/semver/index.js"(exports2, module2) {
|
|
123543
123644
|
"use strict";
|
|
123544
123645
|
var internalRe = require_re();
|
|
123545
|
-
var
|
|
123646
|
+
var constants2 = require_constants8();
|
|
123546
123647
|
var SemVer = require_semver();
|
|
123547
123648
|
var identifiers = require_identifiers();
|
|
123548
123649
|
var parse3 = require_parse5();
|
|
@@ -123626,8 +123727,8 @@ var require_semver2 = __commonJS({
|
|
|
123626
123727
|
re: internalRe.re,
|
|
123627
123728
|
src: internalRe.src,
|
|
123628
123729
|
tokens: internalRe.t,
|
|
123629
|
-
SEMVER_SPEC_VERSION:
|
|
123630
|
-
RELEASE_TYPES:
|
|
123730
|
+
SEMVER_SPEC_VERSION: constants2.SEMVER_SPEC_VERSION,
|
|
123731
|
+
RELEASE_TYPES: constants2.RELEASE_TYPES,
|
|
123631
123732
|
compareIdentifiers: identifiers.compareIdentifiers,
|
|
123632
123733
|
rcompareIdentifiers: identifiers.rcompareIdentifiers
|
|
123633
123734
|
};
|
|
@@ -123850,7 +123951,7 @@ function readMarker() {
|
|
|
123850
123951
|
const parsed = JSON.parse(raw2);
|
|
123851
123952
|
return MarkerSchema.parse(parsed);
|
|
123852
123953
|
} catch (err) {
|
|
123853
|
-
|
|
123954
|
+
log7.warn(`marker at ${markerPath()} is malformed; treating as absent`, {
|
|
123854
123955
|
error: err instanceof Error ? err.message : String(err)
|
|
123855
123956
|
});
|
|
123856
123957
|
return null;
|
|
@@ -123867,7 +123968,7 @@ function writeMarker(marker) {
|
|
|
123867
123968
|
function clearMarker() {
|
|
123868
123969
|
if ((0, import_node_fs21.existsSync)(markerPath())) (0, import_node_fs21.rmSync)(markerPath());
|
|
123869
123970
|
}
|
|
123870
|
-
var import_node_fs21, import_node_path25,
|
|
123971
|
+
var import_node_fs21, import_node_path25, log7;
|
|
123871
123972
|
var init_marker = __esm({
|
|
123872
123973
|
"src/lifecycle/marker.ts"() {
|
|
123873
123974
|
"use strict";
|
|
@@ -123876,7 +123977,7 @@ var init_marker = __esm({
|
|
|
123876
123977
|
init_logger();
|
|
123877
123978
|
init_constants();
|
|
123878
123979
|
init_marker_schema();
|
|
123879
|
-
|
|
123980
|
+
log7 = getLogger("lifecycle.marker");
|
|
123880
123981
|
}
|
|
123881
123982
|
});
|
|
123882
123983
|
|
|
@@ -124077,7 +124178,7 @@ function readPrefs() {
|
|
|
124077
124178
|
try {
|
|
124078
124179
|
return PrefsSchema.parse(JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8")));
|
|
124079
124180
|
} catch (err) {
|
|
124080
|
-
|
|
124181
|
+
log9.warn(`prefs at ${path2} are malformed; treating as empty`, {
|
|
124081
124182
|
error: err instanceof Error ? err.message : String(err)
|
|
124082
124183
|
});
|
|
124083
124184
|
return { repos: {} };
|
|
@@ -124112,7 +124213,7 @@ function forgetAll() {
|
|
|
124112
124213
|
const path2 = prefsPath();
|
|
124113
124214
|
if ((0, import_node_fs23.existsSync)(path2)) (0, import_node_fs23.rmSync)(path2);
|
|
124114
124215
|
}
|
|
124115
|
-
var import_node_fs23, import_node_path26,
|
|
124216
|
+
var import_node_fs23, import_node_path26, log9, PrefsSchema;
|
|
124116
124217
|
var init_prefs = __esm({
|
|
124117
124218
|
"src/lifecycle/prefs.ts"() {
|
|
124118
124219
|
"use strict";
|
|
@@ -124121,7 +124222,7 @@ var init_prefs = __esm({
|
|
|
124121
124222
|
init_zod();
|
|
124122
124223
|
init_logger();
|
|
124123
124224
|
init_constants();
|
|
124124
|
-
|
|
124225
|
+
log9 = getLogger("lifecycle.prefs");
|
|
124125
124226
|
PrefsSchema = external_exports.object({
|
|
124126
124227
|
repos: external_exports.record(
|
|
124127
124228
|
external_exports.string(),
|
|
@@ -124224,7 +124325,7 @@ function takeoverProd(opts) {
|
|
|
124224
124325
|
`prod is already suspended by dev pid ${existing.devPid} (since ${existing.suspendedAt}). Stop that dev session first, or run 'tb-streamer prod doctor'.`
|
|
124225
124326
|
);
|
|
124226
124327
|
}
|
|
124227
|
-
|
|
124328
|
+
log10.info(`stale marker found (pid ${existing.devPid} is gone) \u2014 clearing and proceeding`);
|
|
124228
124329
|
}
|
|
124229
124330
|
getSupervisor().bootoutAgent();
|
|
124230
124331
|
writeMarker({
|
|
@@ -124239,7 +124340,7 @@ function takeoverProd(opts) {
|
|
|
124239
124340
|
const m2 = readMarker();
|
|
124240
124341
|
if (m2 && m2.devPid === process.pid) {
|
|
124241
124342
|
writeMarker({ ...m2, userHeld: true });
|
|
124242
|
-
|
|
124343
|
+
log10.info(
|
|
124243
124344
|
`prod is suspended (userHeld). Run 'tb-streamer prod start' to restore the supervised instance.`
|
|
124244
124345
|
);
|
|
124245
124346
|
}
|
|
@@ -124258,14 +124359,14 @@ function takeoverProd(opts) {
|
|
|
124258
124359
|
});
|
|
124259
124360
|
process.on("uncaughtException", (err) => {
|
|
124260
124361
|
flipUserHeld();
|
|
124261
|
-
|
|
124362
|
+
log10.error(`uncaught: ${err.message}`);
|
|
124262
124363
|
process.exit(1);
|
|
124263
124364
|
});
|
|
124264
124365
|
process.on("exit", () => {
|
|
124265
124366
|
flipUserHeld();
|
|
124266
124367
|
});
|
|
124267
124368
|
}
|
|
124268
|
-
var import_node_net,
|
|
124369
|
+
var import_node_net, log10;
|
|
124269
124370
|
var init_dev_takeover = __esm({
|
|
124270
124371
|
"src/lifecycle/dev-takeover.ts"() {
|
|
124271
124372
|
"use strict";
|
|
@@ -124274,7 +124375,7 @@ var init_dev_takeover = __esm({
|
|
|
124274
124375
|
init_marker();
|
|
124275
124376
|
init_platform();
|
|
124276
124377
|
init_prefs();
|
|
124277
|
-
|
|
124378
|
+
log10 = getLogger("lifecycle.dev-takeover");
|
|
124278
124379
|
}
|
|
124279
124380
|
});
|
|
124280
124381
|
|
|
@@ -127787,6 +127888,9 @@ function appendDevSessionMarker() {
|
|
|
127787
127888
|
}
|
|
127788
127889
|
}
|
|
127789
127890
|
|
|
127891
|
+
// cli/index.ts
|
|
127892
|
+
init_feature_flags();
|
|
127893
|
+
|
|
127790
127894
|
// src/lan-url.ts
|
|
127791
127895
|
var import_os2 = require("os");
|
|
127792
127896
|
function resolveServerUrl({ publicUrl, port }) {
|
|
@@ -132113,7 +132217,7 @@ var REF_PREFIX = "ref: refs/heads/";
|
|
|
132113
132217
|
var MAX_DEPTH = 6;
|
|
132114
132218
|
function readGitBranch(projectPath) {
|
|
132115
132219
|
if (!projectPath) return null;
|
|
132116
|
-
const
|
|
132220
|
+
const log12 = getLogger2();
|
|
132117
132221
|
let dir = projectPath;
|
|
132118
132222
|
let depth = 0;
|
|
132119
132223
|
while (depth < MAX_DEPTH) {
|
|
@@ -132122,11 +132226,11 @@ function readGitBranch(projectPath) {
|
|
|
132122
132226
|
const content = (0, import_fs2.readFileSync)(headPath, "utf-8").trim();
|
|
132123
132227
|
if (content.startsWith(REF_PREFIX)) {
|
|
132124
132228
|
const branch = content.slice(REF_PREFIX.length);
|
|
132125
|
-
|
|
132229
|
+
log12.trace({ projectPath, dir, branch }, "git: branch resolved");
|
|
132126
132230
|
return branch;
|
|
132127
132231
|
}
|
|
132128
132232
|
if (content.length >= 7) {
|
|
132129
|
-
|
|
132233
|
+
log12.trace({ projectPath, dir }, "git: detached HEAD");
|
|
132130
132234
|
return "(detached)";
|
|
132131
132235
|
}
|
|
132132
132236
|
return null;
|
|
@@ -132137,7 +132241,7 @@ function readGitBranch(projectPath) {
|
|
|
132137
132241
|
dir = parent;
|
|
132138
132242
|
depth++;
|
|
132139
132243
|
}
|
|
132140
|
-
|
|
132244
|
+
log12.trace({ projectPath }, "git: no .git found within depth");
|
|
132141
132245
|
return null;
|
|
132142
132246
|
}
|
|
132143
132247
|
function generateMatches(meta3, query) {
|
|
@@ -132430,8 +132534,8 @@ function cleanSystemTags(text) {
|
|
|
132430
132534
|
return text.replace(SYSTEM_TAG_RE, "").replace(/[^\S\n]+/g, " ").replace(/\n{3,}/g, "\n\n").trim();
|
|
132431
132535
|
}
|
|
132432
132536
|
async function parseMeta(filePath, account, tier) {
|
|
132433
|
-
const
|
|
132434
|
-
|
|
132537
|
+
const log12 = getLogger2();
|
|
132538
|
+
log12.trace({ filePath, account, tier: tier.name }, "parseMeta: start");
|
|
132435
132539
|
const state = initialReducerState();
|
|
132436
132540
|
const fileStream = (0, import_fs3.createReadStream)(filePath);
|
|
132437
132541
|
const rl = (0, import_readline.createInterface)({ input: fileStream, crlfDelay: Infinity });
|
|
@@ -132448,22 +132552,22 @@ async function parseMeta(filePath, account, tier) {
|
|
|
132448
132552
|
reduceLine(state, entry, tier);
|
|
132449
132553
|
}
|
|
132450
132554
|
} catch (err) {
|
|
132451
|
-
|
|
132555
|
+
log12.warn({ filePath, err }, "parseMeta: read failed");
|
|
132452
132556
|
return null;
|
|
132453
132557
|
}
|
|
132454
132558
|
if (state.badJsonLines > 0) {
|
|
132455
|
-
|
|
132559
|
+
log12.warn(
|
|
132456
132560
|
{ filePath, badJsonLines: state.badJsonLines },
|
|
132457
132561
|
"parseMeta: skipped malformed JSON lines"
|
|
132458
132562
|
);
|
|
132459
132563
|
}
|
|
132460
132564
|
const meta3 = finalizeMeta(state, filePath, account, tier);
|
|
132461
|
-
if (!meta3)
|
|
132565
|
+
if (!meta3) log12.trace({ filePath }, "parseMeta: no messages");
|
|
132462
132566
|
return meta3;
|
|
132463
132567
|
}
|
|
132464
132568
|
async function parseConversation(filePath, account) {
|
|
132465
|
-
const
|
|
132466
|
-
|
|
132569
|
+
const log12 = getLogger2();
|
|
132570
|
+
log12.trace({ filePath, account }, "parseConversation: start");
|
|
132467
132571
|
const messages = [];
|
|
132468
132572
|
let badJsonLines = 0;
|
|
132469
132573
|
const textParts = [];
|
|
@@ -132496,17 +132600,17 @@ async function parseConversation(filePath, account) {
|
|
|
132496
132600
|
}
|
|
132497
132601
|
}
|
|
132498
132602
|
} catch (err) {
|
|
132499
|
-
|
|
132603
|
+
log12.warn({ filePath, err }, "parseConversation: read failed");
|
|
132500
132604
|
return null;
|
|
132501
132605
|
}
|
|
132502
132606
|
if (badJsonLines > 0) {
|
|
132503
|
-
|
|
132607
|
+
log12.warn({ filePath, badJsonLines }, "parseConversation: skipped malformed JSON lines");
|
|
132504
132608
|
}
|
|
132505
132609
|
if (messages.length === 0) {
|
|
132506
|
-
|
|
132610
|
+
log12.trace({ filePath }, "parseConversation: no messages");
|
|
132507
132611
|
return null;
|
|
132508
132612
|
}
|
|
132509
|
-
|
|
132613
|
+
log12.debug({ filePath, messageCount: messages.length }, "parseConversation: complete");
|
|
132510
132614
|
applyTeamInfo(messages, state);
|
|
132511
132615
|
return {
|
|
132512
132616
|
id: filePath,
|
|
@@ -132771,15 +132875,15 @@ async function detectDefaultProfile() {
|
|
|
132771
132875
|
};
|
|
132772
132876
|
}
|
|
132773
132877
|
async function loadProfiles(configPath) {
|
|
132774
|
-
const
|
|
132878
|
+
const log12 = getLogger2();
|
|
132775
132879
|
try {
|
|
132776
132880
|
const resolved = resolveConfigDir(configPath);
|
|
132777
132881
|
const data = await (0, import_promises4.readFile)((0, import_path5.join)(resolved, PROFILES_FILE), "utf-8");
|
|
132778
132882
|
const profiles = JSON.parse(data);
|
|
132779
|
-
|
|
132883
|
+
log12.debug({ configPath, count: profiles.length }, "profiles: loaded");
|
|
132780
132884
|
return profiles;
|
|
132781
132885
|
} catch (err) {
|
|
132782
|
-
|
|
132886
|
+
log12.debug({ configPath, err }, "profiles: load failed, using default");
|
|
132783
132887
|
const defaultProfile = await detectDefaultProfile();
|
|
132784
132888
|
return [defaultProfile];
|
|
132785
132889
|
}
|
|
@@ -132790,7 +132894,7 @@ function canonicalPath(p2) {
|
|
|
132790
132894
|
var CodexCliProvider = class {
|
|
132791
132895
|
name = CODEX_CLI_PROVIDER;
|
|
132792
132896
|
async discover(roots) {
|
|
132793
|
-
const
|
|
132897
|
+
const log12 = getLogger2();
|
|
132794
132898
|
const results = [];
|
|
132795
132899
|
for (const root of roots) {
|
|
132796
132900
|
let paths;
|
|
@@ -132802,7 +132906,7 @@ var CodexCliProvider = class {
|
|
|
132802
132906
|
unique: true
|
|
132803
132907
|
});
|
|
132804
132908
|
} catch (err) {
|
|
132805
|
-
|
|
132909
|
+
log12.warn({ root, err }, "codex discovery: glob failed");
|
|
132806
132910
|
continue;
|
|
132807
132911
|
}
|
|
132808
132912
|
for (const filePath of paths) {
|
|
@@ -132810,7 +132914,7 @@ var CodexCliProvider = class {
|
|
|
132810
132914
|
const s3 = await (0, import_promises5.stat)(filePath);
|
|
132811
132915
|
if (s3.size > 0) results.push({ filePath: canonicalPath(filePath), account: "codex" });
|
|
132812
132916
|
} catch (err) {
|
|
132813
|
-
|
|
132917
|
+
log12.warn({ filePath, err }, "codex discovery: stat failed");
|
|
132814
132918
|
}
|
|
132815
132919
|
}
|
|
132816
132920
|
}
|
|
@@ -132954,7 +133058,7 @@ function getShortProjectName3(fullPath) {
|
|
|
132954
133058
|
return fullPath.split("/").filter(Boolean).slice(-3).join("/");
|
|
132955
133059
|
}
|
|
132956
133060
|
async function parseCodexConversation(filePath, account) {
|
|
132957
|
-
const
|
|
133061
|
+
const log12 = getLogger2();
|
|
132958
133062
|
const messages = [];
|
|
132959
133063
|
const textParts = [];
|
|
132960
133064
|
let sessionId = "";
|
|
@@ -132990,7 +133094,7 @@ async function parseCodexConversation(filePath, account) {
|
|
|
132990
133094
|
if (role === "user") lastUserText = text;
|
|
132991
133095
|
}
|
|
132992
133096
|
} catch (err) {
|
|
132993
|
-
|
|
133097
|
+
log12.warn({ filePath, err }, "parseCodexConversation: read failed");
|
|
132994
133098
|
return null;
|
|
132995
133099
|
}
|
|
132996
133100
|
if (messages.length === 0) return null;
|
|
@@ -133012,7 +133116,7 @@ async function parseCodexConversation(filePath, account) {
|
|
|
133012
133116
|
var EXCLUDED_SEGMENTS = ["/memory/", "/tool-results/"];
|
|
133013
133117
|
var STAT_CONCURRENCY = 32;
|
|
133014
133118
|
async function discoverJsonlFiles(dirs, onProgress) {
|
|
133015
|
-
const
|
|
133119
|
+
const log12 = getLogger2();
|
|
133016
133120
|
const results = [];
|
|
133017
133121
|
for (const { projectsDir, account } of dirs) {
|
|
133018
133122
|
let filePaths;
|
|
@@ -133023,7 +133127,7 @@ async function discoverJsonlFiles(dirs, onProgress) {
|
|
|
133023
133127
|
dot: false
|
|
133024
133128
|
});
|
|
133025
133129
|
} catch (err) {
|
|
133026
|
-
|
|
133130
|
+
log12.warn({ projectsDir, account, err }, "discovery: glob failed");
|
|
133027
133131
|
continue;
|
|
133028
133132
|
}
|
|
133029
133133
|
const filtered = filePaths.filter((fp) => !EXCLUDED_SEGMENTS.some((seg) => fp.includes(seg)));
|
|
@@ -133038,7 +133142,7 @@ async function discoverJsonlFiles(dirs, onProgress) {
|
|
|
133038
133142
|
const s3 = await (0, import_promises6.stat)(filePath);
|
|
133039
133143
|
return { filePath, size: s3.size };
|
|
133040
133144
|
} catch (err) {
|
|
133041
|
-
|
|
133145
|
+
log12.warn({ filePath, err }, "discovery: stat failed");
|
|
133042
133146
|
return { filePath, size: -1 };
|
|
133043
133147
|
}
|
|
133044
133148
|
})
|
|
@@ -133054,7 +133158,7 @@ async function discoverJsonlFiles(dirs, onProgress) {
|
|
|
133054
133158
|
}
|
|
133055
133159
|
}
|
|
133056
133160
|
}
|
|
133057
|
-
|
|
133161
|
+
log12.debug(
|
|
133058
133162
|
{
|
|
133059
133163
|
projectsDir,
|
|
133060
133164
|
account,
|
|
@@ -133068,7 +133172,7 @@ async function discoverJsonlFiles(dirs, onProgress) {
|
|
|
133068
133172
|
);
|
|
133069
133173
|
onProgress?.(results.length);
|
|
133070
133174
|
}
|
|
133071
|
-
|
|
133175
|
+
log12.debug({ totalFiles: results.length, dirs: dirs.length }, "discovery: complete");
|
|
133072
133176
|
return results;
|
|
133073
133177
|
}
|
|
133074
133178
|
var ThreadbaseProvider = class {
|
|
@@ -133378,7 +133482,7 @@ function classify(filePath, existing) {
|
|
|
133378
133482
|
return { change: "reindex", stat: stat42 };
|
|
133379
133483
|
}
|
|
133380
133484
|
async function parseMetaWithProvider(provider, filePath, account, tier) {
|
|
133381
|
-
const
|
|
133485
|
+
const log12 = getLogger2();
|
|
133382
133486
|
const acc = provider.createEmptyAccumulator();
|
|
133383
133487
|
const rl = (0, import_readline3.createInterface)({
|
|
133384
133488
|
input: (0, import_fs11.createReadStream)(filePath),
|
|
@@ -133396,11 +133500,11 @@ async function parseMetaWithProvider(provider, filePath, account, tier) {
|
|
|
133396
133500
|
try {
|
|
133397
133501
|
provider.reduceEntry(acc, entry, tier);
|
|
133398
133502
|
} catch (err) {
|
|
133399
|
-
|
|
133503
|
+
log12.warn({ filePath, provider: provider.name, err }, "provider reduce threw; line skipped");
|
|
133400
133504
|
}
|
|
133401
133505
|
}
|
|
133402
133506
|
} catch (err) {
|
|
133403
|
-
|
|
133507
|
+
log12.warn({ filePath, provider: provider.name, err }, "provider parse: read failed");
|
|
133404
133508
|
return null;
|
|
133405
133509
|
}
|
|
133406
133510
|
return provider.finalize(acc, filePath, account, tier);
|
|
@@ -133574,8 +133678,8 @@ CREATE INDEX IF NOT EXISTS idx_scanned_dirs_parent_root ON scanned_dirs(parent_r
|
|
|
133574
133678
|
function runMigrations(db) {
|
|
133575
133679
|
const current = db.pragma("user_version", { simple: true });
|
|
133576
133680
|
if (current >= SCHEMA_VERSION) return;
|
|
133577
|
-
const
|
|
133578
|
-
|
|
133681
|
+
const log12 = getLogger2();
|
|
133682
|
+
log12.info({ from: current, to: SCHEMA_VERSION }, "migrations: applying");
|
|
133579
133683
|
if (current >= 1 && current < 2 && tableExists(db, "conversations")) {
|
|
133580
133684
|
for (const [col, ddl] of [
|
|
133581
133685
|
[
|
|
@@ -133617,7 +133721,7 @@ function openDatabase(dbPath) {
|
|
|
133617
133721
|
}
|
|
133618
133722
|
var FULL_RECONCILE_EVERY_N_SCANS = 20;
|
|
133619
133723
|
async function discoverJsonlFilesGated(dirs, files, scannedDirs, options = {}) {
|
|
133620
|
-
const
|
|
133724
|
+
const log12 = getLogger2();
|
|
133621
133725
|
if (options.fullRescan) {
|
|
133622
133726
|
return discoverJsonlFiles(dirs);
|
|
133623
133727
|
}
|
|
@@ -133659,7 +133763,7 @@ async function discoverJsonlFilesGated(dirs, files, scannedDirs, options = {}) {
|
|
|
133659
133763
|
}
|
|
133660
133764
|
}
|
|
133661
133765
|
}
|
|
133662
|
-
|
|
133766
|
+
log12.debug(
|
|
133663
133767
|
{ totalFiles: results.length, dirs: dirs.length },
|
|
133664
133768
|
"dir-watermark: gated discovery complete"
|
|
133665
133769
|
);
|
|
@@ -134165,7 +134269,7 @@ var PersistentEngine = class {
|
|
|
134165
134269
|
// changed since the last index, and upsert their metadata. Returns the number
|
|
134166
134270
|
// of files seen on disk this pass (the scan "scanned" count).
|
|
134167
134271
|
async indexAll(activeProfiles, options) {
|
|
134168
|
-
const
|
|
134272
|
+
const log12 = getLogger2();
|
|
134169
134273
|
const tier = resolveTier(options.tier ?? "standard", options.tiers);
|
|
134170
134274
|
const enabled = options.providers ?? [CLAUDE_CODE_PROVIDER];
|
|
134171
134275
|
const discovered = [];
|
|
@@ -134229,7 +134333,7 @@ var PersistentEngine = class {
|
|
|
134229
134333
|
for (const path2 of this.files.activePathsByAccounts([...coveredAccounts])) {
|
|
134230
134334
|
if (!seen.has(path2)) this.markDeleted(path2);
|
|
134231
134335
|
}
|
|
134232
|
-
|
|
134336
|
+
log12.info({ scanned, indexed: this.conversations.count() }, "persistent: indexAll complete");
|
|
134233
134337
|
return { scanned };
|
|
134234
134338
|
}
|
|
134235
134339
|
// (Re)index a single file. Classifies the change vs. the persisted cursor:
|
|
@@ -134240,7 +134344,7 @@ var PersistentEngine = class {
|
|
|
134240
134344
|
// alongside the meta so callers (refreshFile) can keep, extend, or evict
|
|
134241
134345
|
// their own per-file caches without re-stat'ing the file (racy) themselves.
|
|
134242
134346
|
async indexFile(rawFilePath, account, tierName, customTiers, resolveGitBranch, force = false, provider) {
|
|
134243
|
-
const
|
|
134347
|
+
const log12 = getLogger2();
|
|
134244
134348
|
const tier = resolveTier(tierName, customTiers);
|
|
134245
134349
|
const filePath = canonicalPath(rawFilePath);
|
|
134246
134350
|
const existing = this.files.getByPath(filePath);
|
|
@@ -134271,7 +134375,7 @@ var PersistentEngine = class {
|
|
|
134271
134375
|
try {
|
|
134272
134376
|
result = await tailReduce(filePath, startOffset, startLine, state, tier);
|
|
134273
134377
|
} catch (err) {
|
|
134274
|
-
|
|
134378
|
+
log12.warn({ filePath, err }, "persistent: tail read failed");
|
|
134275
134379
|
return { meta: null, change };
|
|
134276
134380
|
}
|
|
134277
134381
|
const meta3 = finalizeMeta(state, filePath, account, tier);
|
|
@@ -134314,7 +134418,7 @@ var PersistentEngine = class {
|
|
|
134314
134418
|
)
|
|
134315
134419
|
);
|
|
134316
134420
|
}
|
|
134317
|
-
|
|
134421
|
+
log12.debug(
|
|
134318
134422
|
{ filePath, change, bytesRead: result.newOffset - startOffset, msgs: meta3.messageCount },
|
|
134319
134423
|
"persistent: indexed file"
|
|
134320
134424
|
);
|
|
@@ -134326,7 +134430,7 @@ var PersistentEngine = class {
|
|
|
134326
134430
|
// so the next pass classifies an unchanged file as "unchanged" and skips it;
|
|
134327
134431
|
// any change reparses from 0 again. No reducer_state is persisted.
|
|
134328
134432
|
async indexFileWithProvider(provider, filePath, account, tier, stat42, resolveGitBranch) {
|
|
134329
|
-
const
|
|
134433
|
+
const log12 = getLogger2();
|
|
134330
134434
|
const meta3 = await parseMetaWithProvider(provider, filePath, account, tier);
|
|
134331
134435
|
if (!meta3) {
|
|
134332
134436
|
this.markDeleted(filePath);
|
|
@@ -134354,7 +134458,7 @@ var PersistentEngine = class {
|
|
|
134354
134458
|
});
|
|
134355
134459
|
});
|
|
134356
134460
|
upsert();
|
|
134357
|
-
|
|
134461
|
+
log12.debug(
|
|
134358
134462
|
{ filePath, provider: provider.name, msgs: meta3.messageCount },
|
|
134359
134463
|
"persistent: indexed provider file"
|
|
134360
134464
|
);
|
|
@@ -134468,7 +134572,7 @@ var FileWatcher = class {
|
|
|
134468
134572
|
// Resolves once every underlying chokidar watcher has finished its initial
|
|
134469
134573
|
// scan, so the caller knows subsequent FS changes will be observed.
|
|
134470
134574
|
async start() {
|
|
134471
|
-
const
|
|
134575
|
+
const log12 = getLogger2();
|
|
134472
134576
|
const ready = [];
|
|
134473
134577
|
for (const profile of this.profiles) {
|
|
134474
134578
|
const dir = getProjectsDir(profile);
|
|
@@ -134480,7 +134584,7 @@ var FileWatcher = class {
|
|
|
134480
134584
|
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
134585
|
ready.push(new Promise((resolve4) => watcher.once("ready", () => resolve4())));
|
|
134482
134586
|
this.watchers.push(watcher);
|
|
134483
|
-
|
|
134587
|
+
log12.debug({ dir, account: profile.id }, "watcher: watching");
|
|
134484
134588
|
}
|
|
134485
134589
|
await Promise.all(ready);
|
|
134486
134590
|
}
|
|
@@ -134527,14 +134631,14 @@ var IndexQueue = class {
|
|
|
134527
134631
|
async drain() {
|
|
134528
134632
|
if (this.running) return;
|
|
134529
134633
|
this.running = true;
|
|
134530
|
-
const
|
|
134634
|
+
const log12 = getLogger2();
|
|
134531
134635
|
while (this.pending.size > 0) {
|
|
134532
134636
|
const [path2, job] = this.pending.entries().next().value;
|
|
134533
134637
|
this.pending.delete(path2);
|
|
134534
134638
|
try {
|
|
134535
134639
|
await this.process(job);
|
|
134536
134640
|
} catch (err) {
|
|
134537
|
-
|
|
134641
|
+
log12.warn({ filePath: job.filePath, err }, "index-queue: job failed");
|
|
134538
134642
|
}
|
|
134539
134643
|
}
|
|
134540
134644
|
this.running = false;
|
|
@@ -134643,13 +134747,13 @@ var ConversationScanner = class {
|
|
|
134643
134747
|
// active metas and run the identical filter/sort/view/paginate pipeline as
|
|
134644
134748
|
// the in-memory path — guaranteeing an identical ScanResult shape.
|
|
134645
134749
|
async scanPersistent(activeProfiles, options) {
|
|
134646
|
-
const
|
|
134750
|
+
const log12 = getLogger2();
|
|
134647
134751
|
const startedAt = Date.now();
|
|
134648
134752
|
const engine = this.engine();
|
|
134649
134753
|
const { scanned } = await engine.indexAll(activeProfiles, options);
|
|
134650
134754
|
const allMetas = engine.allActive();
|
|
134651
134755
|
const { conversations, total } = this.finalize(allMetas, options);
|
|
134652
|
-
|
|
134756
|
+
log12.info(
|
|
134653
134757
|
{ scanned, kept: allMetas.length, filteredTotal: total, elapsedMs: Date.now() - startedAt },
|
|
134654
134758
|
"scan: complete (persistent)"
|
|
134655
134759
|
);
|
|
@@ -134676,10 +134780,10 @@ var ConversationScanner = class {
|
|
|
134676
134780
|
return { conversations, total };
|
|
134677
134781
|
}
|
|
134678
134782
|
async scanInMemory(activeProfiles, options) {
|
|
134679
|
-
const
|
|
134783
|
+
const log12 = getLogger2();
|
|
134680
134784
|
const startedAt = Date.now();
|
|
134681
134785
|
const tier = this.lastTier;
|
|
134682
|
-
|
|
134786
|
+
log12.info(
|
|
134683
134787
|
{
|
|
134684
134788
|
activeProfiles: activeProfiles.length,
|
|
134685
134789
|
tier: tier.name,
|
|
@@ -134733,7 +134837,7 @@ var ConversationScanner = class {
|
|
|
134733
134837
|
return meta3;
|
|
134734
134838
|
} catch (err) {
|
|
134735
134839
|
parseFailures++;
|
|
134736
|
-
|
|
134840
|
+
log12.warn({ filePath, account, provider: provider.name, err }, "scan: parse threw");
|
|
134737
134841
|
return null;
|
|
134738
134842
|
}
|
|
134739
134843
|
})
|
|
@@ -134753,12 +134857,12 @@ var ConversationScanner = class {
|
|
|
134753
134857
|
options.onBatch?.(batchMetas);
|
|
134754
134858
|
}
|
|
134755
134859
|
scanned += batch.length;
|
|
134756
|
-
|
|
134860
|
+
log12.debug({ scanned, totalFiles, batchKept: batchMetas.length }, "scan: batch complete");
|
|
134757
134861
|
options.onProgress?.(scanned, totalFiles);
|
|
134758
134862
|
}
|
|
134759
134863
|
const { conversations, total } = this.finalize(allMetas, options);
|
|
134760
134864
|
const elapsedMs = Date.now() - startedAt;
|
|
134761
|
-
|
|
134865
|
+
log12.info(
|
|
134762
134866
|
{
|
|
134763
134867
|
totalFiles,
|
|
134764
134868
|
scanned,
|
|
@@ -134772,13 +134876,13 @@ var ConversationScanner = class {
|
|
|
134772
134876
|
return { conversations, total, scanned };
|
|
134773
134877
|
}
|
|
134774
134878
|
async search(query, options = {}) {
|
|
134775
|
-
const
|
|
134776
|
-
|
|
134879
|
+
const log12 = getLogger2();
|
|
134880
|
+
log12.debug({ query, indexSize: this.indexer.getDocumentCount() }, "search: start");
|
|
134777
134881
|
let results;
|
|
134778
134882
|
if (this.persistent) {
|
|
134779
134883
|
const engine = this.engine();
|
|
134780
134884
|
if (engine.conversations.count() === 0) {
|
|
134781
|
-
|
|
134885
|
+
log12.debug("search: persistent index empty, triggering scan");
|
|
134782
134886
|
const profiles = await this.resolveProfiles(options.profiles);
|
|
134783
134887
|
const activeProfiles = profiles.filter((p2) => p2.enabled && p2.scanHistory !== false);
|
|
134784
134888
|
await engine.indexAll(activeProfiles, { ...options, limit: void 0, offset: void 0 });
|
|
@@ -134791,7 +134895,7 @@ var ConversationScanner = class {
|
|
|
134791
134895
|
}));
|
|
134792
134896
|
} else {
|
|
134793
134897
|
if (this.indexer.getDocumentCount() === 0) {
|
|
134794
|
-
|
|
134898
|
+
log12.debug("search: index empty, triggering scan");
|
|
134795
134899
|
await this.scan({ ...options, limit: void 0, offset: void 0 });
|
|
134796
134900
|
}
|
|
134797
134901
|
results = this.indexer.search(query, {
|
|
@@ -134834,23 +134938,23 @@ var ConversationScanner = class {
|
|
|
134834
134938
|
const limit = options.limit ?? 50;
|
|
134835
134939
|
const offset = options.offset ?? 0;
|
|
134836
134940
|
const sliced = results.slice(offset, offset + limit);
|
|
134837
|
-
|
|
134941
|
+
log12.debug({ query, matched: results.length, returned: sliced.length }, "search: complete");
|
|
134838
134942
|
return sliced;
|
|
134839
134943
|
}
|
|
134840
134944
|
async getConversation(id, _options) {
|
|
134841
|
-
const
|
|
134945
|
+
const log12 = getLogger2();
|
|
134842
134946
|
const cid = canonicalPath(id);
|
|
134843
134947
|
const cached3 = this.conversationLRU.get(cid);
|
|
134844
134948
|
if (cached3) {
|
|
134845
|
-
|
|
134949
|
+
log12.debug({ id }, "getConversation: cache hit");
|
|
134846
134950
|
return cached3.conversation;
|
|
134847
134951
|
}
|
|
134848
134952
|
const meta3 = this.persistent ? this.engine().getByIdOrSession(cid) : this.metadataCache.get(cid) ?? this.resolveSessionId(cid);
|
|
134849
134953
|
if (!meta3) {
|
|
134850
|
-
|
|
134954
|
+
log12.debug({ id }, "getConversation: not found in metadata");
|
|
134851
134955
|
return null;
|
|
134852
134956
|
}
|
|
134853
|
-
|
|
134957
|
+
log12.debug({ id, filePath: meta3.filePath }, "getConversation: cache miss, parsing");
|
|
134854
134958
|
try {
|
|
134855
134959
|
if (this.persistent && meta3.provider !== CODEX_CLI_PROVIDER) {
|
|
134856
134960
|
const parsed = await parseConversationResumable(meta3.filePath, meta3.account);
|
|
@@ -134863,7 +134967,7 @@ var ConversationScanner = class {
|
|
|
134863
134967
|
}
|
|
134864
134968
|
return conversation;
|
|
134865
134969
|
} catch (err) {
|
|
134866
|
-
|
|
134970
|
+
log12.warn({ id, filePath: meta3.filePath, err }, "getConversation: parse failed");
|
|
134867
134971
|
return null;
|
|
134868
134972
|
}
|
|
134869
134973
|
}
|
|
@@ -134949,7 +135053,7 @@ var ConversationScanner = class {
|
|
|
134949
135053
|
return refresh;
|
|
134950
135054
|
}
|
|
134951
135055
|
async doRefreshFile(filePath, account) {
|
|
134952
|
-
const
|
|
135056
|
+
const log12 = getLogger2();
|
|
134953
135057
|
if (this.persistent) {
|
|
134954
135058
|
const engine = this.engine();
|
|
134955
135059
|
const previous2 = engine.getByIdOrSession(filePath);
|
|
@@ -134976,7 +135080,7 @@ var ConversationScanner = class {
|
|
|
134976
135080
|
} else if (change === "appended") {
|
|
134977
135081
|
await this.extendCachedConversations(cacheKeys, filePath, meta22.account);
|
|
134978
135082
|
}
|
|
134979
|
-
|
|
135083
|
+
log12.debug({ filePath, change, kept: !!meta22 }, "refreshFile: updated persistent index");
|
|
134980
135084
|
return meta22;
|
|
134981
135085
|
}
|
|
134982
135086
|
const previous = this.metadataCache.get(filePath);
|
|
@@ -134985,7 +135089,7 @@ var ConversationScanner = class {
|
|
|
134985
135089
|
try {
|
|
134986
135090
|
meta3 = await parseMeta(filePath, resolvedAccount, this.lastTier);
|
|
134987
135091
|
} catch (err) {
|
|
134988
|
-
|
|
135092
|
+
log12.warn({ filePath, err }, "refreshFile: parseMeta threw");
|
|
134989
135093
|
meta3 = null;
|
|
134990
135094
|
}
|
|
134991
135095
|
const evict = (m2) => {
|
|
@@ -135001,7 +135105,7 @@ var ConversationScanner = class {
|
|
|
135001
135105
|
this.removeFromSessionIndex(previous);
|
|
135002
135106
|
this.indexer.removeDocument(previous.id);
|
|
135003
135107
|
}
|
|
135004
|
-
|
|
135108
|
+
log12.debug({ filePath }, "refreshFile: dropped (no parseable messages)");
|
|
135005
135109
|
return null;
|
|
135006
135110
|
}
|
|
135007
135111
|
meta3.gitBranch = readGitBranch(meta3.projectPath);
|
|
@@ -135014,7 +135118,7 @@ var ConversationScanner = class {
|
|
|
135014
135118
|
} else {
|
|
135015
135119
|
this.indexer.addDocument(meta3);
|
|
135016
135120
|
}
|
|
135017
|
-
|
|
135121
|
+
log12.debug(
|
|
135018
135122
|
{ filePath, messageCount: meta3.messageCount },
|
|
135019
135123
|
"refreshFile: updated in-memory indexes"
|
|
135020
135124
|
);
|
|
@@ -138222,6 +138326,7 @@ function readRawBody2(req) {
|
|
|
138222
138326
|
var createConfigRoutes = (deps) => {
|
|
138223
138327
|
const app = new Hono2();
|
|
138224
138328
|
app.get("/claude-flags", (c) => c.json(deps.claudeFlagsConfig()));
|
|
138329
|
+
app.get("/feature-flags", (c) => c.json(deps.featureFlagsConfig()));
|
|
138225
138330
|
app.put("/claude-flags", async (c) => {
|
|
138226
138331
|
if (deps.localNoAuth) {
|
|
138227
138332
|
return c.json({ error: "claude flag changes are disabled while localNoAuth is active" }, 403);
|
|
@@ -138475,7 +138580,266 @@ function createLogsRoutes() {
|
|
|
138475
138580
|
var import_node_child_process2 = require("child_process");
|
|
138476
138581
|
var import_node_crypto = require("crypto");
|
|
138477
138582
|
var import_os5 = require("os");
|
|
138583
|
+
|
|
138584
|
+
// src/db/repositories/push.repository.ts
|
|
138585
|
+
var FAILURE_STREAK_LIMIT = 5;
|
|
138586
|
+
var PUSH_TOKEN_KINDS = ["expo", "liveactivity_start", "liveactivity_update"];
|
|
138587
|
+
var DEFAULT_PUSH_TOKEN_KIND = "expo";
|
|
138588
|
+
function isPushTokenKind(value) {
|
|
138589
|
+
return typeof value === "string" && PUSH_TOKEN_KINDS.includes(value);
|
|
138590
|
+
}
|
|
138591
|
+
function tokenState(row, now = Date.now()) {
|
|
138592
|
+
if (row.revoked_at != null) return "revoked";
|
|
138593
|
+
if (row.expires_at != null && row.expires_at <= now) return "expired";
|
|
138594
|
+
if (row.failure_streak >= FAILURE_STREAK_LIMIT) return "dead";
|
|
138595
|
+
if (row.failure_streak > 0) return "failing";
|
|
138596
|
+
if (row.last_success_at == null) return "never-delivered";
|
|
138597
|
+
return "healthy";
|
|
138598
|
+
}
|
|
138599
|
+
function toHealth(row, now = Date.now()) {
|
|
138600
|
+
return {
|
|
138601
|
+
platform: row.platform,
|
|
138602
|
+
deviceId: row.device_id,
|
|
138603
|
+
registeredAt: row.registered_at,
|
|
138604
|
+
lastSuccessAt: row.last_success_at,
|
|
138605
|
+
lastFailureAt: row.last_failure_at,
|
|
138606
|
+
lastFailureCode: row.last_failure_code,
|
|
138607
|
+
failureStreak: row.failure_streak,
|
|
138608
|
+
revokedAt: row.revoked_at,
|
|
138609
|
+
state: tokenState(row, now),
|
|
138610
|
+
kind: row.kind,
|
|
138611
|
+
activityId: row.activity_id,
|
|
138612
|
+
sessionId: row.session_id,
|
|
138613
|
+
expiresAt: row.expires_at
|
|
138614
|
+
};
|
|
138615
|
+
}
|
|
138616
|
+
var PushRepository = class {
|
|
138617
|
+
upsertStmt;
|
|
138618
|
+
getStmt;
|
|
138619
|
+
listActiveStmt;
|
|
138620
|
+
listAllStmt;
|
|
138621
|
+
successStmt;
|
|
138622
|
+
failureStmt;
|
|
138623
|
+
revokeStmt;
|
|
138624
|
+
claimEventStmt;
|
|
138625
|
+
markDeliveredStmt;
|
|
138626
|
+
listByKindSessionStmt;
|
|
138627
|
+
listByKindStmt;
|
|
138628
|
+
listRenewableStmt;
|
|
138629
|
+
claimRenewalStmt;
|
|
138630
|
+
expireStmt;
|
|
138631
|
+
expireSessionActivitiesStmt;
|
|
138632
|
+
constructor(db) {
|
|
138633
|
+
this.upsertStmt = db.prepare(`
|
|
138634
|
+
INSERT INTO push_tokens (
|
|
138635
|
+
token, platform, device_id, registered_at,
|
|
138636
|
+
kind, activity_id, session_id, expires_at, stale_date, started_at
|
|
138637
|
+
)
|
|
138638
|
+
VALUES (
|
|
138639
|
+
@token, @platform, @device_id, @registered_at,
|
|
138640
|
+
@kind, @activity_id, @session_id, @expires_at, @stale_date, @started_at
|
|
138641
|
+
)
|
|
138642
|
+
ON CONFLICT(token) DO UPDATE SET
|
|
138643
|
+
platform = excluded.platform,
|
|
138644
|
+
device_id = COALESCE(excluded.device_id, push_tokens.device_id),
|
|
138645
|
+
registered_at = excluded.registered_at,
|
|
138646
|
+
kind = excluded.kind,
|
|
138647
|
+
activity_id = COALESCE(excluded.activity_id, push_tokens.activity_id),
|
|
138648
|
+
session_id = COALESCE(excluded.session_id, push_tokens.session_id),
|
|
138649
|
+
expires_at = excluded.expires_at,
|
|
138650
|
+
stale_date = excluded.stale_date,
|
|
138651
|
+
-- Preserve the ORIGINAL start across a re-registration. iOS renders its
|
|
138652
|
+
-- own ticking timer from started_at, so overwriting it with a fresh
|
|
138653
|
+
-- value visibly resets the user's elapsed time to zero.
|
|
138654
|
+
started_at = COALESCE(push_tokens.started_at, excluded.started_at),
|
|
138655
|
+
-- A fresh registration clears prior failure state and any revocation:
|
|
138656
|
+
-- the client is telling us this token is live again. renewed_at clears
|
|
138657
|
+
-- too \u2014 this is a new activity generation, so it is renewable again.
|
|
138658
|
+
failure_streak = 0,
|
|
138659
|
+
last_failure_at = NULL,
|
|
138660
|
+
last_failure_code = NULL,
|
|
138661
|
+
revoked_at = NULL,
|
|
138662
|
+
renewed_at = NULL
|
|
138663
|
+
`);
|
|
138664
|
+
this.getStmt = db.prepare("SELECT * FROM push_tokens WHERE token = ?");
|
|
138665
|
+
this.listActiveStmt = db.prepare(`
|
|
138666
|
+
SELECT * FROM push_tokens
|
|
138667
|
+
WHERE revoked_at IS NULL AND failure_streak < ${FAILURE_STREAK_LIMIT}
|
|
138668
|
+
AND kind = 'expo'
|
|
138669
|
+
ORDER BY registered_at ASC
|
|
138670
|
+
`);
|
|
138671
|
+
this.listAllStmt = db.prepare("SELECT * FROM push_tokens ORDER BY registered_at ASC");
|
|
138672
|
+
this.successStmt = db.prepare(`
|
|
138673
|
+
UPDATE push_tokens
|
|
138674
|
+
SET last_success_at = @at, failure_streak = 0,
|
|
138675
|
+
last_failure_code = NULL
|
|
138676
|
+
WHERE token = @token
|
|
138677
|
+
`);
|
|
138678
|
+
this.failureStmt = db.prepare(`
|
|
138679
|
+
UPDATE push_tokens
|
|
138680
|
+
SET last_failure_at = @at, last_failure_code = @code,
|
|
138681
|
+
failure_streak = failure_streak + 1
|
|
138682
|
+
WHERE token = @token
|
|
138683
|
+
`);
|
|
138684
|
+
this.revokeStmt = db.prepare("UPDATE push_tokens SET revoked_at = ? WHERE token = ?");
|
|
138685
|
+
this.listByKindSessionStmt = db.prepare(`
|
|
138686
|
+
SELECT * FROM push_tokens
|
|
138687
|
+
WHERE kind = @kind AND session_id = @session_id
|
|
138688
|
+
AND revoked_at IS NULL AND failure_streak < ${FAILURE_STREAK_LIMIT}
|
|
138689
|
+
AND (expires_at IS NULL OR expires_at > @now)
|
|
138690
|
+
ORDER BY registered_at ASC
|
|
138691
|
+
`);
|
|
138692
|
+
this.listByKindStmt = db.prepare(`
|
|
138693
|
+
SELECT * FROM push_tokens
|
|
138694
|
+
WHERE kind = @kind
|
|
138695
|
+
AND revoked_at IS NULL AND failure_streak < ${FAILURE_STREAK_LIMIT}
|
|
138696
|
+
AND (expires_at IS NULL OR expires_at > @now)
|
|
138697
|
+
ORDER BY registered_at ASC
|
|
138698
|
+
`);
|
|
138699
|
+
this.listRenewableStmt = db.prepare(`
|
|
138700
|
+
SELECT * FROM push_tokens
|
|
138701
|
+
WHERE kind = 'liveactivity_update'
|
|
138702
|
+
AND stale_date IS NOT NULL AND renewed_at IS NULL
|
|
138703
|
+
AND revoked_at IS NULL AND failure_streak < ${FAILURE_STREAK_LIMIT}
|
|
138704
|
+
ORDER BY stale_date ASC
|
|
138705
|
+
`);
|
|
138706
|
+
this.claimRenewalStmt = db.prepare(`
|
|
138707
|
+
UPDATE push_tokens SET renewed_at = @at
|
|
138708
|
+
WHERE token = @token AND renewed_at IS NULL
|
|
138709
|
+
`);
|
|
138710
|
+
this.expireStmt = db.prepare("UPDATE push_tokens SET expires_at = ? WHERE token = ?");
|
|
138711
|
+
this.expireSessionActivitiesStmt = db.prepare(`
|
|
138712
|
+
UPDATE push_tokens SET expires_at = @at
|
|
138713
|
+
WHERE session_id = @session_id AND kind = 'liveactivity_update'
|
|
138714
|
+
AND (expires_at IS NULL OR expires_at > @at)
|
|
138715
|
+
`);
|
|
138716
|
+
this.claimEventStmt = db.prepare(`
|
|
138717
|
+
INSERT OR IGNORE INTO push_events (event_id, session_id, created_at)
|
|
138718
|
+
VALUES (@event_id, @session_id, @created_at)
|
|
138719
|
+
`);
|
|
138720
|
+
this.markDeliveredStmt = db.prepare(
|
|
138721
|
+
"UPDATE push_events SET delivered_at = ? WHERE event_id = ?"
|
|
138722
|
+
);
|
|
138723
|
+
}
|
|
138724
|
+
/**
|
|
138725
|
+
* Register or refresh a token.
|
|
138726
|
+
*
|
|
138727
|
+
* `kind` defaults to Expo so a released client posting `{ token, platform }`
|
|
138728
|
+
* keeps working — tb-mobile cannot be force-updated, and every client
|
|
138729
|
+
* predating Live Activities is registering an Expo relay token.
|
|
138730
|
+
*
|
|
138731
|
+
* Several rows per device is normal and intended: a device runs one activity
|
|
138732
|
+
* per live session, each with its own update token. The token itself is the
|
|
138733
|
+
* primary key, so distinct activities never collide.
|
|
138734
|
+
*/
|
|
138735
|
+
register(args) {
|
|
138736
|
+
this.upsertStmt.run({
|
|
138737
|
+
token: args.token,
|
|
138738
|
+
platform: args.platform,
|
|
138739
|
+
device_id: args.deviceId ?? null,
|
|
138740
|
+
registered_at: args.now ?? Date.now(),
|
|
138741
|
+
kind: args.kind ?? DEFAULT_PUSH_TOKEN_KIND,
|
|
138742
|
+
activity_id: args.activityId ?? null,
|
|
138743
|
+
session_id: args.sessionId ?? null,
|
|
138744
|
+
expires_at: args.expiresAt ?? null,
|
|
138745
|
+
stale_date: args.staleDate ?? null,
|
|
138746
|
+
started_at: args.startedAt ?? null
|
|
138747
|
+
});
|
|
138748
|
+
}
|
|
138749
|
+
get(token) {
|
|
138750
|
+
return this.getStmt.get(token) ?? null;
|
|
138751
|
+
}
|
|
138752
|
+
/**
|
|
138753
|
+
* Expo tokens eligible for delivery — not revoked, not past the failure limit.
|
|
138754
|
+
*
|
|
138755
|
+
* Deliberately Expo-only. ActivityKit tokens go over direct APNs with a
|
|
138756
|
+
* different topic and are rejected by Expo's relay, so the ordinary
|
|
138757
|
+
* notification fan-out must not see them.
|
|
138758
|
+
*/
|
|
138759
|
+
listDeliverable() {
|
|
138760
|
+
return this.listActiveStmt.all();
|
|
138761
|
+
}
|
|
138762
|
+
/** Live-activity tokens for one session, eligible for delivery. */
|
|
138763
|
+
listForSession(kind, sessionId, now = Date.now()) {
|
|
138764
|
+
return this.listByKindSessionStmt.all({
|
|
138765
|
+
kind,
|
|
138766
|
+
session_id: sessionId,
|
|
138767
|
+
now
|
|
138768
|
+
});
|
|
138769
|
+
}
|
|
138770
|
+
/**
|
|
138771
|
+
* Every deliverable token of one kind.
|
|
138772
|
+
*
|
|
138773
|
+
* Used for push-to-start, which is app-wide rather than session-scoped: the
|
|
138774
|
+
* activity does not exist yet, so there is no per-activity token to look up.
|
|
138775
|
+
*/
|
|
138776
|
+
listByKind(kind, now = Date.now()) {
|
|
138777
|
+
return this.listByKindStmt.all({ kind, now });
|
|
138778
|
+
}
|
|
138779
|
+
/** Unrenewed activities with a renewal deadline, soonest first. */
|
|
138780
|
+
listRenewable() {
|
|
138781
|
+
return this.listRenewableStmt.all();
|
|
138782
|
+
}
|
|
138783
|
+
/**
|
|
138784
|
+
* Claim a row for renewal.
|
|
138785
|
+
*
|
|
138786
|
+
* Returns true exactly once per row. A restart re-arms timers from the
|
|
138787
|
+
* persisted deadline, so the same renewal can be attempted twice; the loser
|
|
138788
|
+
* gets false and must not send. Doing this as a conditional UPDATE rather
|
|
138789
|
+
* than read-then-write avoids the race where both attempts observe
|
|
138790
|
+
* "not yet renewed".
|
|
138791
|
+
*/
|
|
138792
|
+
claimRenewal(token, now = Date.now()) {
|
|
138793
|
+
return this.claimRenewalStmt.run({ token, at: now }).changes > 0;
|
|
138794
|
+
}
|
|
138795
|
+
/** Mark one token expired, so it stops being a delivery target. */
|
|
138796
|
+
expire(token, now = Date.now()) {
|
|
138797
|
+
this.expireStmt.run(now, token);
|
|
138798
|
+
}
|
|
138799
|
+
/**
|
|
138800
|
+
* Expire every live activity for a session.
|
|
138801
|
+
*
|
|
138802
|
+
* Called when the session ends. Without this, a per-activity token outlives
|
|
138803
|
+
* its session and a later renewal sweep would resurrect an activity for a
|
|
138804
|
+
* session that is already gone.
|
|
138805
|
+
*/
|
|
138806
|
+
expireSessionActivities(sessionId, now = Date.now()) {
|
|
138807
|
+
this.expireSessionActivitiesStmt.run({ session_id: sessionId, at: now });
|
|
138808
|
+
}
|
|
138809
|
+
/** Every token, including dead and revoked ones, for the health report. */
|
|
138810
|
+
listHealth(now = Date.now()) {
|
|
138811
|
+
return this.listAllStmt.all().map((r) => toHealth(r, now));
|
|
138812
|
+
}
|
|
138813
|
+
recordSuccess(token, now = Date.now()) {
|
|
138814
|
+
this.successStmt.run({ token, at: now });
|
|
138815
|
+
}
|
|
138816
|
+
recordFailure(token, code, now = Date.now()) {
|
|
138817
|
+
this.failureStmt.run({ token, at: now, code });
|
|
138818
|
+
}
|
|
138819
|
+
revoke(token, now = Date.now()) {
|
|
138820
|
+
return this.revokeStmt.run(now, token).changes > 0;
|
|
138821
|
+
}
|
|
138822
|
+
/**
|
|
138823
|
+
* Claim an event id for delivery.
|
|
138824
|
+
*
|
|
138825
|
+
* Returns true exactly once per event id. A retry, a reconnect
|
|
138826
|
+
* reconciliation, or two triggers firing for the same underlying event all
|
|
138827
|
+
* get false and must not notify — the user should never be told twice about
|
|
138828
|
+
* one thing.
|
|
138829
|
+
*/
|
|
138830
|
+
claimEvent(eventId, sessionId, now = Date.now()) {
|
|
138831
|
+
return this.claimEventStmt.run({ event_id: eventId, session_id: sessionId, created_at: now }).changes > 0;
|
|
138832
|
+
}
|
|
138833
|
+
markDelivered(eventId, now = Date.now()) {
|
|
138834
|
+
this.markDeliveredStmt.run(now, eventId);
|
|
138835
|
+
}
|
|
138836
|
+
};
|
|
138837
|
+
|
|
138838
|
+
// src/api/routes/misc.routes.ts
|
|
138478
138839
|
init_logger();
|
|
138840
|
+
function numberOrNull(value) {
|
|
138841
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
138842
|
+
}
|
|
138479
138843
|
function readJsonBody(req) {
|
|
138480
138844
|
return new Promise((resolve4, reject) => {
|
|
138481
138845
|
const chunks = [];
|
|
@@ -138522,7 +138886,11 @@ var createMiscRoutes = (deps) => {
|
|
|
138522
138886
|
// Capability flag: this server serves /api/config/claude-flags. Additive —
|
|
138523
138887
|
// older clients ignore it, and clients talking to an older server see it
|
|
138524
138888
|
// absent and hide the UI rather than 404ing.
|
|
138525
|
-
claudeFlags: true
|
|
138889
|
+
claudeFlags: true,
|
|
138890
|
+
// Same contract: this server serves GET /api/config/feature-flags. Lives
|
|
138891
|
+
// here rather than behind /api/config (admin-only) so a read-only client
|
|
138892
|
+
// still learns the server supports flags even if it can't read values.
|
|
138893
|
+
featureFlags: true
|
|
138526
138894
|
});
|
|
138527
138895
|
});
|
|
138528
138896
|
app.get("/api/profiles", (c) => c.json([]));
|
|
@@ -138549,6 +138917,22 @@ var createMiscRoutes = (deps) => {
|
|
|
138549
138917
|
if (platform3 !== "ios" && platform3 !== "android") {
|
|
138550
138918
|
return c.json({ error: "platform must be 'ios' or 'android'" }, 400);
|
|
138551
138919
|
}
|
|
138920
|
+
const kind = body?.kind === void 0 ? DEFAULT_PUSH_TOKEN_KIND : body.kind;
|
|
138921
|
+
if (!isPushTokenKind(kind)) {
|
|
138922
|
+
return c.json(
|
|
138923
|
+
{ error: `kind must be one of ${PUSH_TOKEN_KINDS.join(", ")}`, code: "INVALID_KIND" },
|
|
138924
|
+
400
|
|
138925
|
+
);
|
|
138926
|
+
}
|
|
138927
|
+
if (kind === "liveactivity_update" && typeof body?.activityId !== "string") {
|
|
138928
|
+
return c.json(
|
|
138929
|
+
{
|
|
138930
|
+
error: "activityId is required for kind 'liveactivity_update'",
|
|
138931
|
+
code: "MISSING_ACTIVITY"
|
|
138932
|
+
},
|
|
138933
|
+
400
|
|
138934
|
+
);
|
|
138935
|
+
}
|
|
138552
138936
|
const repo = deps.pushRepo();
|
|
138553
138937
|
if (!repo) {
|
|
138554
138938
|
return c.json({ error: "Push registration is unavailable", code: "STORE_UNAVAILABLE" }, 503);
|
|
@@ -138556,7 +138940,13 @@ var createMiscRoutes = (deps) => {
|
|
|
138556
138940
|
repo.register({
|
|
138557
138941
|
token,
|
|
138558
138942
|
platform: platform3,
|
|
138559
|
-
deviceId: typeof body?.deviceId === "string" ? body.deviceId : null
|
|
138943
|
+
deviceId: typeof body?.deviceId === "string" ? body.deviceId : null,
|
|
138944
|
+
kind,
|
|
138945
|
+
activityId: typeof body?.activityId === "string" ? body.activityId : null,
|
|
138946
|
+
sessionId: typeof body?.sessionId === "string" ? body.sessionId : null,
|
|
138947
|
+
expiresAt: numberOrNull(body?.expiresAt),
|
|
138948
|
+
staleDate: numberOrNull(body?.staleDate),
|
|
138949
|
+
startedAt: numberOrNull(body?.startedAt)
|
|
138560
138950
|
});
|
|
138561
138951
|
return c.json({ ok: true });
|
|
138562
138952
|
});
|
|
@@ -139114,6 +139504,14 @@ var createSessionRoutes = (deps) => {
|
|
|
139114
139504
|
await deps.handleSetSessionName(c.req.param("id"), c.env.incoming, c.env.outgoing);
|
|
139115
139505
|
return alreadyHandled6();
|
|
139116
139506
|
});
|
|
139507
|
+
app.patch("/:id/model", async (c) => {
|
|
139508
|
+
await deps.handleSetSessionModel(c.req.param("id"), c.env.incoming, c.env.outgoing);
|
|
139509
|
+
return alreadyHandled6();
|
|
139510
|
+
});
|
|
139511
|
+
app.patch("/:id/effort", async (c) => {
|
|
139512
|
+
await deps.handleSetSessionEffort(c.req.param("id"), c.env.incoming, c.env.outgoing);
|
|
139513
|
+
return alreadyHandled6();
|
|
139514
|
+
});
|
|
139117
139515
|
app.post("/:id/adopt", async (c) => {
|
|
139118
139516
|
await deps.handleAdopt(c.req.param("id"), c.env.outgoing);
|
|
139119
139517
|
return alreadyHandled6();
|
|
@@ -139453,6 +139851,7 @@ CREATE TABLE IF NOT EXISTS conversation_meta (
|
|
|
139453
139851
|
);
|
|
139454
139852
|
CREATE INDEX IF NOT EXISTS idx_meta_last_activity ON conversation_meta(last_activity DESC);
|
|
139455
139853
|
CREATE INDEX IF NOT EXISTS idx_meta_project ON conversation_meta(project_path);
|
|
139854
|
+
CREATE INDEX IF NOT EXISTS idx_meta_file_path ON conversation_meta(file_path);
|
|
139456
139855
|
|
|
139457
139856
|
CREATE TABLE IF NOT EXISTS conversation_tail (
|
|
139458
139857
|
conversation_id TEXT PRIMARY KEY REFERENCES conversation_meta(id) ON DELETE CASCADE,
|
|
@@ -141004,125 +141403,6 @@ function deriveNameFromPath(path2) {
|
|
|
141004
141403
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
141005
141404
|
}
|
|
141006
141405
|
|
|
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
141406
|
// src/db/repositories/sessions.repository.ts
|
|
141127
141407
|
var SessionsRepository = class {
|
|
141128
141408
|
constructor(store) {
|
|
@@ -141156,6 +141436,9 @@ async function recordUpload(pool2, instanceId, row) {
|
|
|
141156
141436
|
);
|
|
141157
141437
|
}
|
|
141158
141438
|
|
|
141439
|
+
// src/server.ts
|
|
141440
|
+
init_feature_flags();
|
|
141441
|
+
|
|
141159
141442
|
// src/handlers/handleListProjects.ts
|
|
141160
141443
|
var import_fs18 = require("fs");
|
|
141161
141444
|
var import_os7 = require("os");
|
|
@@ -143429,10 +143712,10 @@ function fingerprintOf(ids) {
|
|
|
143429
143712
|
return `sha256:${(0, import_crypto11.createHash)("sha256").update(sorted.join("\n")).digest("hex")}`;
|
|
143430
143713
|
}
|
|
143431
143714
|
var CacheIntegrityMonitor = class {
|
|
143432
|
-
constructor(cache, wsHub,
|
|
143715
|
+
constructor(cache, wsHub, log12, cacheDir, rescan, runDuringReset) {
|
|
143433
143716
|
this.cache = cache;
|
|
143434
143717
|
this.wsHub = wsHub;
|
|
143435
|
-
this.log =
|
|
143718
|
+
this.log = log12;
|
|
143436
143719
|
this.cacheDir = cacheDir;
|
|
143437
143720
|
this.rescan = rescan;
|
|
143438
143721
|
this.runDuringReset = runDuringReset;
|
|
@@ -144264,6 +144547,585 @@ function deriveProjectChatTitle(input) {
|
|
|
144264
144547
|
return `Untitled \xB7 ${input.id.slice(0, 8)}`;
|
|
144265
144548
|
}
|
|
144266
144549
|
|
|
144550
|
+
// src/services/push/apnsClient.ts
|
|
144551
|
+
var import_node_crypto3 = require("crypto");
|
|
144552
|
+
var import_node_http2 = require("http2");
|
|
144553
|
+
init_logger();
|
|
144554
|
+
var log3 = getLogger("apns");
|
|
144555
|
+
var APNS_HOST_SANDBOX = "api.sandbox.push.apple.com";
|
|
144556
|
+
var APNS_MAX_PAYLOAD_BYTES = 4096;
|
|
144557
|
+
var JWT_TTL_SECONDS = 3e3;
|
|
144558
|
+
var DEAD_TOKEN_REASONS = /* @__PURE__ */ new Set([
|
|
144559
|
+
"BadDeviceToken",
|
|
144560
|
+
"DeviceTokenNotForTopic",
|
|
144561
|
+
"Unregistered",
|
|
144562
|
+
"ExpiredToken"
|
|
144563
|
+
]);
|
|
144564
|
+
function base64url3(input) {
|
|
144565
|
+
return Buffer.from(input).toString("base64url");
|
|
144566
|
+
}
|
|
144567
|
+
function readApnsCredentialsFromEnv(env = process.env) {
|
|
144568
|
+
const key = env.APNS_KEY;
|
|
144569
|
+
if (!key || key.trim().length === 0) return null;
|
|
144570
|
+
const keyId = env.APNS_KEY_ID?.trim();
|
|
144571
|
+
const teamId = env.APNS_TEAM_ID?.trim();
|
|
144572
|
+
const bundleId = env.APNS_BUNDLE_ID?.trim();
|
|
144573
|
+
if (!keyId || !teamId || !bundleId) return null;
|
|
144574
|
+
const host = env.APNS_HOST ?? APNS_HOST_SANDBOX;
|
|
144575
|
+
return { key, keyId, teamId, bundleId, host };
|
|
144576
|
+
}
|
|
144577
|
+
function describeMissingApnsCredentials(env = process.env) {
|
|
144578
|
+
if (!env.APNS_KEY || env.APNS_KEY.trim().length === 0) {
|
|
144579
|
+
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.";
|
|
144580
|
+
}
|
|
144581
|
+
const missing = [
|
|
144582
|
+
["APNS_KEY_ID", env.APNS_KEY_ID],
|
|
144583
|
+
["APNS_TEAM_ID", env.APNS_TEAM_ID],
|
|
144584
|
+
["APNS_BUNDLE_ID", env.APNS_BUNDLE_ID]
|
|
144585
|
+
].filter(([, value]) => !value || value.trim().length === 0).map(([name]) => name);
|
|
144586
|
+
if (missing.length === 0) return null;
|
|
144587
|
+
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.`;
|
|
144588
|
+
}
|
|
144589
|
+
var ApnsClient = class {
|
|
144590
|
+
constructor(creds) {
|
|
144591
|
+
this.creds = creds;
|
|
144592
|
+
}
|
|
144593
|
+
creds;
|
|
144594
|
+
session = null;
|
|
144595
|
+
cachedJwt = null;
|
|
144596
|
+
/**
|
|
144597
|
+
* The `apns-topic` for Live Activity pushes.
|
|
144598
|
+
*
|
|
144599
|
+
* The `.push-type.liveactivity` suffix is mandatory and is why the signing key
|
|
144600
|
+
* must be Team Scoped (All Topics) — a key scoped to the bundle id alone
|
|
144601
|
+
* cannot sign this topic.
|
|
144602
|
+
*/
|
|
144603
|
+
get topic() {
|
|
144604
|
+
return `${this.creds.bundleId}.push-type.liveactivity`;
|
|
144605
|
+
}
|
|
144606
|
+
/**
|
|
144607
|
+
* Mint or reuse the provider JWT.
|
|
144608
|
+
*
|
|
144609
|
+
* ES256 over the p8 key. Cached until shortly before expiry: Apple rejects a
|
|
144610
|
+
* token older than an hour, but minting one per request is wasteful and can
|
|
144611
|
+
* trip APNs' provider-token-update throttle.
|
|
144612
|
+
*/
|
|
144613
|
+
getJwt(now = Date.now()) {
|
|
144614
|
+
const nowSeconds = Math.floor(now / 1e3);
|
|
144615
|
+
if (this.cachedJwt && this.cachedJwt.expiresAt > nowSeconds + 60) {
|
|
144616
|
+
return this.cachedJwt.token;
|
|
144617
|
+
}
|
|
144618
|
+
const header = base64url3(JSON.stringify({ alg: "ES256", kid: this.creds.keyId, typ: "JWT" }));
|
|
144619
|
+
const payload = base64url3(JSON.stringify({ iss: this.creds.teamId, iat: nowSeconds }));
|
|
144620
|
+
const signingInput = `${header}.${payload}`;
|
|
144621
|
+
const signature = (0, import_node_crypto3.createSign)("SHA256").update(signingInput).sign({ key: this.creds.key, dsaEncoding: "ieee-p1363" });
|
|
144622
|
+
const token = `${signingInput}.${base64url3(signature)}`;
|
|
144623
|
+
this.cachedJwt = { token, expiresAt: nowSeconds + JWT_TTL_SECONDS };
|
|
144624
|
+
return token;
|
|
144625
|
+
}
|
|
144626
|
+
/**
|
|
144627
|
+
* Reuse one HTTP/2 session across sends.
|
|
144628
|
+
*
|
|
144629
|
+
* APNs expects a long-lived connection; a fresh TLS handshake per push is slow
|
|
144630
|
+
* and Apple treats connection churn as abuse.
|
|
144631
|
+
*/
|
|
144632
|
+
getSession() {
|
|
144633
|
+
if (this.session && !this.session.closed && !this.session.destroyed) {
|
|
144634
|
+
return this.session;
|
|
144635
|
+
}
|
|
144636
|
+
const session = (0, import_node_http2.connect)(`https://${this.creds.host}`);
|
|
144637
|
+
session.on("error", (err) => {
|
|
144638
|
+
log3.warn("apns.session_error", { event: "apns.session_error", err: String(err) });
|
|
144639
|
+
});
|
|
144640
|
+
this.session = session;
|
|
144641
|
+
return session;
|
|
144642
|
+
}
|
|
144643
|
+
/**
|
|
144644
|
+
* Send one push.
|
|
144645
|
+
*
|
|
144646
|
+
* Resolves with a result rather than rejecting on an APNs rejection: a
|
|
144647
|
+
* rejected push is an expected outcome the caller must act on (retire the
|
|
144648
|
+
* token), not an exception. Only a genuinely unexpected local failure throws,
|
|
144649
|
+
* and the caller logs it.
|
|
144650
|
+
*/
|
|
144651
|
+
async send(args) {
|
|
144652
|
+
const body = Buffer.from(JSON.stringify(args.payload), "utf-8");
|
|
144653
|
+
if (body.byteLength > APNS_MAX_PAYLOAD_BYTES) {
|
|
144654
|
+
throw new Error(
|
|
144655
|
+
`APNs payload is ${body.byteLength} bytes, over the ${APNS_MAX_PAYLOAD_BYTES} byte limit`
|
|
144656
|
+
);
|
|
144657
|
+
}
|
|
144658
|
+
const session = this.getSession();
|
|
144659
|
+
const headers = {
|
|
144660
|
+
[import_node_http2.constants.HTTP2_HEADER_METHOD]: "POST",
|
|
144661
|
+
[import_node_http2.constants.HTTP2_HEADER_PATH]: `/3/device/${args.deviceToken}`,
|
|
144662
|
+
[import_node_http2.constants.HTTP2_HEADER_AUTHORIZATION]: `bearer ${this.getJwt()}`,
|
|
144663
|
+
"apns-push-type": "liveactivity",
|
|
144664
|
+
"apns-topic": this.topic,
|
|
144665
|
+
"apns-priority": String(args.priority ?? 10),
|
|
144666
|
+
...args.expirationSeconds != null && {
|
|
144667
|
+
"apns-expiration": String(args.expirationSeconds)
|
|
144668
|
+
},
|
|
144669
|
+
[import_node_http2.constants.HTTP2_HEADER_CONTENT_TYPE]: "application/json",
|
|
144670
|
+
[import_node_http2.constants.HTTP2_HEADER_CONTENT_LENGTH]: String(body.byteLength)
|
|
144671
|
+
};
|
|
144672
|
+
return new Promise((resolve4, reject) => {
|
|
144673
|
+
const req = session.request(headers);
|
|
144674
|
+
req.setTimeout(args.timeoutMs ?? 1e4, () => {
|
|
144675
|
+
req.close(import_node_http2.constants.NGHTTP2_CANCEL);
|
|
144676
|
+
resolve4({ ok: false, status: 0, reason: "Timeout", tokenDead: false });
|
|
144677
|
+
});
|
|
144678
|
+
let status = 0;
|
|
144679
|
+
req.on("response", (resHeaders) => {
|
|
144680
|
+
status = Number(resHeaders[import_node_http2.constants.HTTP2_HEADER_STATUS] ?? 0);
|
|
144681
|
+
});
|
|
144682
|
+
const chunks = [];
|
|
144683
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
144684
|
+
req.on("error", reject);
|
|
144685
|
+
req.on("end", () => {
|
|
144686
|
+
const raw2 = Buffer.concat(chunks).toString("utf-8");
|
|
144687
|
+
let reason;
|
|
144688
|
+
if (raw2.length > 0) {
|
|
144689
|
+
try {
|
|
144690
|
+
reason = JSON.parse(raw2).reason;
|
|
144691
|
+
} catch {
|
|
144692
|
+
reason = raw2.slice(0, 200);
|
|
144693
|
+
}
|
|
144694
|
+
}
|
|
144695
|
+
resolve4({
|
|
144696
|
+
ok: status === 200,
|
|
144697
|
+
status,
|
|
144698
|
+
reason,
|
|
144699
|
+
tokenDead: reason != null && DEAD_TOKEN_REASONS.has(reason)
|
|
144700
|
+
});
|
|
144701
|
+
});
|
|
144702
|
+
req.end(body);
|
|
144703
|
+
});
|
|
144704
|
+
}
|
|
144705
|
+
/** Close the shared connection. Called on server shutdown. */
|
|
144706
|
+
close() {
|
|
144707
|
+
this.session?.close();
|
|
144708
|
+
this.session = null;
|
|
144709
|
+
}
|
|
144710
|
+
};
|
|
144711
|
+
|
|
144712
|
+
// src/services/push/liveActivityNotifier.ts
|
|
144713
|
+
init_logger();
|
|
144714
|
+
|
|
144715
|
+
// src/services/push/liveActivityContentState.ts
|
|
144716
|
+
var LAST_OUTPUT_MAX_LENGTH = 90;
|
|
144717
|
+
function toLiveActivityStatus(status) {
|
|
144718
|
+
return status === "running" || status === "waiting_input" ? status : null;
|
|
144719
|
+
}
|
|
144720
|
+
function truncateLastOutput(raw2) {
|
|
144721
|
+
const oneLine = raw2.replace(/\s+/g, " ").trim();
|
|
144722
|
+
return oneLine.length <= LAST_OUTPUT_MAX_LENGTH ? oneLine : oneLine.slice(0, LAST_OUTPUT_MAX_LENGTH);
|
|
144723
|
+
}
|
|
144724
|
+
|
|
144725
|
+
// src/services/push/liveActivityNotifier.ts
|
|
144726
|
+
var log4 = getLogger("live-activity");
|
|
144727
|
+
function contentStateForSession(args) {
|
|
144728
|
+
const status = toLiveActivityStatus(args.session.status);
|
|
144729
|
+
if (!status) return null;
|
|
144730
|
+
return {
|
|
144731
|
+
sessionId: args.session.id,
|
|
144732
|
+
serverId: args.serverId,
|
|
144733
|
+
projectName: args.session.projectName,
|
|
144734
|
+
status,
|
|
144735
|
+
startedAt: args.startedAtOverride ?? args.session.startedAt.getTime(),
|
|
144736
|
+
lastOutput: truncateLastOutput(args.session.lastOutput ?? ""),
|
|
144737
|
+
...args.serverLabel != null && { serverLabel: args.serverLabel }
|
|
144738
|
+
};
|
|
144739
|
+
}
|
|
144740
|
+
var LiveActivityNotifier = class {
|
|
144741
|
+
constructor(sender, serverId, serverLabel) {
|
|
144742
|
+
this.sender = sender;
|
|
144743
|
+
this.serverId = serverId;
|
|
144744
|
+
this.serverLabel = serverLabel;
|
|
144745
|
+
}
|
|
144746
|
+
sender;
|
|
144747
|
+
serverId;
|
|
144748
|
+
serverLabel;
|
|
144749
|
+
/**
|
|
144750
|
+
* Last status pushed per session.
|
|
144751
|
+
*
|
|
144752
|
+
* Live Activity pushes are rate-limited by iOS and the surface only renders
|
|
144753
|
+
* `running` vs `waiting_input`, so re-pushing an unchanged status is pure
|
|
144754
|
+
* budget spend for no visible change. This is what makes the notifier
|
|
144755
|
+
* edge-triggered rather than level-triggered.
|
|
144756
|
+
*/
|
|
144757
|
+
lastPushed = /* @__PURE__ */ new Map();
|
|
144758
|
+
/**
|
|
144759
|
+
* React to a session status change.
|
|
144760
|
+
*
|
|
144761
|
+
* Fire-and-forget by design: a push must never delay or fail a session
|
|
144762
|
+
* transition, so this returns a promise the caller may ignore and every error
|
|
144763
|
+
* is logged rather than propagated.
|
|
144764
|
+
*/
|
|
144765
|
+
async onStatusChange(session) {
|
|
144766
|
+
const status = toLiveActivityStatus(session.status);
|
|
144767
|
+
try {
|
|
144768
|
+
if (!status) {
|
|
144769
|
+
await this.endFor(session);
|
|
144770
|
+
return;
|
|
144771
|
+
}
|
|
144772
|
+
if (this.lastPushed.get(session.id) === status) return;
|
|
144773
|
+
const contentState = contentStateForSession({
|
|
144774
|
+
session,
|
|
144775
|
+
serverId: this.serverId,
|
|
144776
|
+
serverLabel: this.serverLabel
|
|
144777
|
+
});
|
|
144778
|
+
if (!contentState) return;
|
|
144779
|
+
const outcome = await this.sender.send({
|
|
144780
|
+
sessionId: session.id,
|
|
144781
|
+
event: "update",
|
|
144782
|
+
contentState
|
|
144783
|
+
});
|
|
144784
|
+
this.lastPushed.set(session.id, status);
|
|
144785
|
+
if (outcome.attempted > 0) {
|
|
144786
|
+
log4.info("live_activity.updated", {
|
|
144787
|
+
event: "live_activity.updated",
|
|
144788
|
+
sessionId: session.id,
|
|
144789
|
+
status,
|
|
144790
|
+
...outcome
|
|
144791
|
+
});
|
|
144792
|
+
}
|
|
144793
|
+
} catch (err) {
|
|
144794
|
+
log4.error("live_activity.notify_failed", {
|
|
144795
|
+
event: "live_activity.notify_failed",
|
|
144796
|
+
sessionId: session.id,
|
|
144797
|
+
status: session.status,
|
|
144798
|
+
err: String(err)
|
|
144799
|
+
});
|
|
144800
|
+
}
|
|
144801
|
+
}
|
|
144802
|
+
async endFor(session) {
|
|
144803
|
+
const lastStatus = this.lastPushed.get(session.id);
|
|
144804
|
+
this.lastPushed.delete(session.id);
|
|
144805
|
+
const contentState = contentStateForSession({
|
|
144806
|
+
session: {
|
|
144807
|
+
...session,
|
|
144808
|
+
status: lastStatus === "waiting_input" ? "waiting_input" : "running"
|
|
144809
|
+
},
|
|
144810
|
+
serverId: this.serverId,
|
|
144811
|
+
serverLabel: this.serverLabel
|
|
144812
|
+
});
|
|
144813
|
+
if (!contentState) return;
|
|
144814
|
+
const outcome = await this.sender.end({ sessionId: session.id, contentState });
|
|
144815
|
+
if (outcome.attempted > 0) {
|
|
144816
|
+
log4.info("live_activity.ended", {
|
|
144817
|
+
event: "live_activity.ended",
|
|
144818
|
+
sessionId: session.id,
|
|
144819
|
+
...outcome
|
|
144820
|
+
});
|
|
144821
|
+
}
|
|
144822
|
+
}
|
|
144823
|
+
/** Drop cached state for a session, so a resume re-pushes its first status. */
|
|
144824
|
+
forget(sessionId) {
|
|
144825
|
+
this.lastPushed.delete(sessionId);
|
|
144826
|
+
}
|
|
144827
|
+
};
|
|
144828
|
+
|
|
144829
|
+
// src/services/push/liveActivityRenewal.ts
|
|
144830
|
+
init_logger();
|
|
144831
|
+
|
|
144832
|
+
// src/services/push/liveActivitySender.ts
|
|
144833
|
+
init_logger();
|
|
144834
|
+
var log5 = getLogger("live-activity");
|
|
144835
|
+
var ACTIVITY_MAX_LIFETIME_MS = 8 * 60 * 60 * 1e3;
|
|
144836
|
+
function buildActivityKitPayload(args) {
|
|
144837
|
+
return {
|
|
144838
|
+
aps: {
|
|
144839
|
+
timestamp: Math.floor(args.now / 1e3),
|
|
144840
|
+
event: args.event,
|
|
144841
|
+
"content-state": args.contentState,
|
|
144842
|
+
...args.staleDate != null && { "stale-date": Math.floor(args.staleDate / 1e3) },
|
|
144843
|
+
...args.dismissalDate != null && {
|
|
144844
|
+
"dismissal-date": Math.floor(args.dismissalDate / 1e3)
|
|
144845
|
+
}
|
|
144846
|
+
}
|
|
144847
|
+
};
|
|
144848
|
+
}
|
|
144849
|
+
var LiveActivitySender = class {
|
|
144850
|
+
constructor(apns, repo) {
|
|
144851
|
+
this.apns = apns;
|
|
144852
|
+
this.repo = repo;
|
|
144853
|
+
}
|
|
144854
|
+
apns;
|
|
144855
|
+
repo;
|
|
144856
|
+
/**
|
|
144857
|
+
* Push to every live activity of a session.
|
|
144858
|
+
*
|
|
144859
|
+
* Sends are independent: one rejected token must not stop the others, because
|
|
144860
|
+
* a single dead device would otherwise silence every other device watching the
|
|
144861
|
+
* same session.
|
|
144862
|
+
*/
|
|
144863
|
+
async send(args) {
|
|
144864
|
+
const now = args.now ?? Date.now();
|
|
144865
|
+
return this.sendToTokens({
|
|
144866
|
+
tokens: this.repo.listForSession("liveactivity_update", args.sessionId, now),
|
|
144867
|
+
sessionId: args.sessionId,
|
|
144868
|
+
event: args.event,
|
|
144869
|
+
contentState: args.contentState,
|
|
144870
|
+
now,
|
|
144871
|
+
priority: args.priority
|
|
144872
|
+
});
|
|
144873
|
+
}
|
|
144874
|
+
/**
|
|
144875
|
+
* Push to an explicit token list.
|
|
144876
|
+
*
|
|
144877
|
+
* Renewal needs this: a replacement activity does not exist yet, so it is
|
|
144878
|
+
* started via the app-wide push-to-start token rather than any per-session
|
|
144879
|
+
* lookup. Shares one fan-out body with `send()` so failure handling cannot
|
|
144880
|
+
* drift between the two paths.
|
|
144881
|
+
*/
|
|
144882
|
+
async sendToTokens(args) {
|
|
144883
|
+
const now = args.now ?? Date.now();
|
|
144884
|
+
const tokens = args.tokens;
|
|
144885
|
+
const outcome = {
|
|
144886
|
+
attempted: tokens.length,
|
|
144887
|
+
succeeded: 0,
|
|
144888
|
+
retired: 0
|
|
144889
|
+
};
|
|
144890
|
+
if (tokens.length === 0) return outcome;
|
|
144891
|
+
const results = await Promise.all(
|
|
144892
|
+
tokens.map(
|
|
144893
|
+
(row) => this.sendToToken(row, args.event, args.contentState, now, args.priority, args.staleDate)
|
|
144894
|
+
)
|
|
144895
|
+
);
|
|
144896
|
+
for (const { row, result, error: error51 } of results) {
|
|
144897
|
+
if (error51) {
|
|
144898
|
+
log5.error("live_activity.send_failed", {
|
|
144899
|
+
event: "live_activity.send_failed",
|
|
144900
|
+
sessionId: args.sessionId,
|
|
144901
|
+
activityId: row.activity_id,
|
|
144902
|
+
apnsEvent: args.event,
|
|
144903
|
+
err: String(error51)
|
|
144904
|
+
});
|
|
144905
|
+
this.repo.recordFailure(row.token, "SendError", now);
|
|
144906
|
+
continue;
|
|
144907
|
+
}
|
|
144908
|
+
if (!result) continue;
|
|
144909
|
+
if (result.ok) {
|
|
144910
|
+
this.repo.recordSuccess(row.token, now);
|
|
144911
|
+
outcome.succeeded += 1;
|
|
144912
|
+
continue;
|
|
144913
|
+
}
|
|
144914
|
+
this.repo.recordFailure(row.token, result.reason ?? `HTTP_${result.status}`, now);
|
|
144915
|
+
if (result.tokenDead) {
|
|
144916
|
+
this.repo.expire(row.token, now);
|
|
144917
|
+
outcome.retired += 1;
|
|
144918
|
+
}
|
|
144919
|
+
log5.warn("live_activity.send_rejected", {
|
|
144920
|
+
event: "live_activity.send_rejected",
|
|
144921
|
+
sessionId: args.sessionId,
|
|
144922
|
+
activityId: row.activity_id,
|
|
144923
|
+
apnsEvent: args.event,
|
|
144924
|
+
status: result.status,
|
|
144925
|
+
reason: result.reason,
|
|
144926
|
+
tokenDead: result.tokenDead
|
|
144927
|
+
});
|
|
144928
|
+
}
|
|
144929
|
+
return outcome;
|
|
144930
|
+
}
|
|
144931
|
+
/**
|
|
144932
|
+
* End every live activity for a session and stop tracking them.
|
|
144933
|
+
*
|
|
144934
|
+
* Expiring locally is what stops the renewal sweep from later resurrecting an
|
|
144935
|
+
* activity for a session that has already finished.
|
|
144936
|
+
*/
|
|
144937
|
+
async end(args) {
|
|
144938
|
+
const now = args.now ?? Date.now();
|
|
144939
|
+
const outcome = await this.send({
|
|
144940
|
+
sessionId: args.sessionId,
|
|
144941
|
+
event: "end",
|
|
144942
|
+
contentState: args.contentState,
|
|
144943
|
+
now
|
|
144944
|
+
});
|
|
144945
|
+
this.repo.expireSessionActivities(args.sessionId, now);
|
|
144946
|
+
return outcome;
|
|
144947
|
+
}
|
|
144948
|
+
async sendToToken(row, event, contentState, now, priority, staleDateOverride) {
|
|
144949
|
+
const staleDate = event === "update" ? staleDateOverride ?? row.stale_date ?? contentState.startedAt + ACTIVITY_MAX_LIFETIME_MS : null;
|
|
144950
|
+
try {
|
|
144951
|
+
const result = await this.apns.send({
|
|
144952
|
+
deviceToken: row.token,
|
|
144953
|
+
payload: buildActivityKitPayload({ event, contentState, now, staleDate }),
|
|
144954
|
+
priority
|
|
144955
|
+
});
|
|
144956
|
+
return { row, result };
|
|
144957
|
+
} catch (error51) {
|
|
144958
|
+
return { row, error: error51 };
|
|
144959
|
+
}
|
|
144960
|
+
}
|
|
144961
|
+
};
|
|
144962
|
+
|
|
144963
|
+
// src/services/push/liveActivityRenewal.ts
|
|
144964
|
+
var log6 = getLogger("live-activity");
|
|
144965
|
+
var RENEWAL_LEAD_MS = 30 * 60 * 1e3;
|
|
144966
|
+
var MAX_TIMER_MS = 60 * 60 * 1e3;
|
|
144967
|
+
function renewalDueAt(row) {
|
|
144968
|
+
return row.stale_date == null ? null : row.stale_date - RENEWAL_LEAD_MS;
|
|
144969
|
+
}
|
|
144970
|
+
var LiveActivityRenewalScheduler = class {
|
|
144971
|
+
constructor(deps) {
|
|
144972
|
+
this.deps = deps;
|
|
144973
|
+
this.now = deps.now ?? (() => Date.now());
|
|
144974
|
+
}
|
|
144975
|
+
deps;
|
|
144976
|
+
timer = null;
|
|
144977
|
+
stopped = false;
|
|
144978
|
+
now;
|
|
144979
|
+
/**
|
|
144980
|
+
* Arm the scheduler from persisted state.
|
|
144981
|
+
*
|
|
144982
|
+
* Called on boot, which is what makes a renewal survive a restart: the
|
|
144983
|
+
* deadlines were never in memory to begin with.
|
|
144984
|
+
*/
|
|
144985
|
+
start() {
|
|
144986
|
+
this.stopped = false;
|
|
144987
|
+
void this.tick();
|
|
144988
|
+
}
|
|
144989
|
+
stop() {
|
|
144990
|
+
this.stopped = true;
|
|
144991
|
+
if (this.timer) {
|
|
144992
|
+
clearTimeout(this.timer);
|
|
144993
|
+
this.timer = null;
|
|
144994
|
+
}
|
|
144995
|
+
}
|
|
144996
|
+
/**
|
|
144997
|
+
* Renew everything due, then sleep until the next deadline.
|
|
144998
|
+
*
|
|
144999
|
+
* Re-reads from the DB every tick rather than caching a schedule in memory, so
|
|
145000
|
+
* an activity registered after boot is picked up without re-arming anything.
|
|
145001
|
+
*/
|
|
145002
|
+
async tick() {
|
|
145003
|
+
if (this.stopped) return;
|
|
145004
|
+
const now = this.now();
|
|
145005
|
+
try {
|
|
145006
|
+
for (const row of this.deps.repo.listRenewable()) {
|
|
145007
|
+
const dueAt = renewalDueAt(row);
|
|
145008
|
+
if (dueAt == null || dueAt > now) continue;
|
|
145009
|
+
await this.renew(row, now);
|
|
145010
|
+
}
|
|
145011
|
+
} catch (err) {
|
|
145012
|
+
log6.error("live_activity.renewal_sweep_failed", {
|
|
145013
|
+
event: "live_activity.renewal_sweep_failed",
|
|
145014
|
+
err: String(err)
|
|
145015
|
+
});
|
|
145016
|
+
}
|
|
145017
|
+
this.scheduleNext();
|
|
145018
|
+
}
|
|
145019
|
+
scheduleNext() {
|
|
145020
|
+
if (this.stopped) return;
|
|
145021
|
+
const now = this.now();
|
|
145022
|
+
const pending = this.deps.repo.listRenewable().map(renewalDueAt).filter((d) => d != null);
|
|
145023
|
+
const nextDue = pending.length > 0 ? Math.min(...pending) : now + MAX_TIMER_MS;
|
|
145024
|
+
const delay = Math.min(Math.max(nextDue - now, 0), MAX_TIMER_MS);
|
|
145025
|
+
this.timer = setTimeout(() => void this.tick(), delay);
|
|
145026
|
+
this.timer.unref?.();
|
|
145027
|
+
}
|
|
145028
|
+
/**
|
|
145029
|
+
* Renew one activity.
|
|
145030
|
+
*
|
|
145031
|
+
* Claims first: `claimRenewal()` succeeds exactly once per row, so a timer
|
|
145032
|
+
* re-armed after a restart mid-window cannot send a second time.
|
|
145033
|
+
*/
|
|
145034
|
+
async renew(row, now) {
|
|
145035
|
+
if (!row.session_id) return;
|
|
145036
|
+
const session = this.deps.sessionStore.getManaged(row.session_id);
|
|
145037
|
+
const status = session ? toLiveActivityStatus(session.status) : null;
|
|
145038
|
+
if (!session || !status) {
|
|
145039
|
+
this.deps.repo.claimRenewal(row.token, now);
|
|
145040
|
+
this.deps.repo.expire(row.token, now);
|
|
145041
|
+
log6.info("live_activity.renewal_skipped", {
|
|
145042
|
+
event: "live_activity.renewal_skipped",
|
|
145043
|
+
sessionId: row.session_id,
|
|
145044
|
+
activityId: row.activity_id,
|
|
145045
|
+
reason: session ? `status_${session.status}` : "session_gone"
|
|
145046
|
+
});
|
|
145047
|
+
return;
|
|
145048
|
+
}
|
|
145049
|
+
if (!this.deps.repo.claimRenewal(row.token, now)) {
|
|
145050
|
+
return;
|
|
145051
|
+
}
|
|
145052
|
+
const startedAt = row.started_at ?? session.startedAt.getTime();
|
|
145053
|
+
const contentState = {
|
|
145054
|
+
sessionId: session.id,
|
|
145055
|
+
serverId: this.deps.serverId,
|
|
145056
|
+
projectName: session.projectName,
|
|
145057
|
+
status,
|
|
145058
|
+
startedAt,
|
|
145059
|
+
lastOutput: session.lastOutput ?? "",
|
|
145060
|
+
...this.deps.serverLabel != null && { serverLabel: this.deps.serverLabel }
|
|
145061
|
+
};
|
|
145062
|
+
try {
|
|
145063
|
+
await this.deps.sender.send({
|
|
145064
|
+
sessionId: session.id,
|
|
145065
|
+
event: "end",
|
|
145066
|
+
contentState: { ...contentState, lastOutput: truncateLastOutput(contentState.lastOutput) },
|
|
145067
|
+
now
|
|
145068
|
+
});
|
|
145069
|
+
this.deps.repo.expire(row.token, now);
|
|
145070
|
+
const started = await this.startReplacement({
|
|
145071
|
+
sessionId: session.id,
|
|
145072
|
+
startedAt,
|
|
145073
|
+
now
|
|
145074
|
+
});
|
|
145075
|
+
log6.info("live_activity.renewed", {
|
|
145076
|
+
event: "live_activity.renewed",
|
|
145077
|
+
sessionId: session.id,
|
|
145078
|
+
activityId: row.activity_id,
|
|
145079
|
+
// Logged because a regression here is invisible on the server and only
|
|
145080
|
+
// shows up as a reset timer on someone's Lock Screen.
|
|
145081
|
+
startedAt,
|
|
145082
|
+
replacementRequested: started
|
|
145083
|
+
});
|
|
145084
|
+
} catch (err) {
|
|
145085
|
+
log6.error("live_activity.renewal_failed", {
|
|
145086
|
+
event: "live_activity.renewal_failed",
|
|
145087
|
+
sessionId: session.id,
|
|
145088
|
+
activityId: row.activity_id,
|
|
145089
|
+
err: String(err)
|
|
145090
|
+
});
|
|
145091
|
+
}
|
|
145092
|
+
}
|
|
145093
|
+
/**
|
|
145094
|
+
* Ask the device to start a replacement activity.
|
|
145095
|
+
*
|
|
145096
|
+
* Uses the app-wide push-to-start token, because the replacement does not
|
|
145097
|
+
* exist yet and therefore has no per-activity token. Returns false when the
|
|
145098
|
+
* device never registered one, which is not an error: the app simply cannot be
|
|
145099
|
+
* asked to start an activity remotely, and the next foreground WS update
|
|
145100
|
+
* recreates it.
|
|
145101
|
+
*/
|
|
145102
|
+
async startReplacement(args) {
|
|
145103
|
+
const starters = this.deps.repo.listByKind("liveactivity_start", args.now);
|
|
145104
|
+
if (starters.length === 0) return false;
|
|
145105
|
+
const session = this.deps.sessionStore.getManaged(args.sessionId);
|
|
145106
|
+
const status = session ? toLiveActivityStatus(session.status) : null;
|
|
145107
|
+
if (!session || !status) return false;
|
|
145108
|
+
await this.deps.sender.sendToTokens({
|
|
145109
|
+
tokens: starters,
|
|
145110
|
+
event: "update",
|
|
145111
|
+
sessionId: args.sessionId,
|
|
145112
|
+
contentState: {
|
|
145113
|
+
sessionId: session.id,
|
|
145114
|
+
serverId: this.deps.serverId,
|
|
145115
|
+
projectName: session.projectName,
|
|
145116
|
+
status,
|
|
145117
|
+
// Carried through unchanged — the whole point of the renewal.
|
|
145118
|
+
startedAt: args.startedAt,
|
|
145119
|
+
lastOutput: truncateLastOutput(session.lastOutput ?? ""),
|
|
145120
|
+
...this.deps.serverLabel != null && { serverLabel: this.deps.serverLabel }
|
|
145121
|
+
},
|
|
145122
|
+
now: args.now,
|
|
145123
|
+
staleDate: args.startedAt + ACTIVITY_MAX_LIFETIME_MS
|
|
145124
|
+
});
|
|
145125
|
+
return true;
|
|
145126
|
+
}
|
|
145127
|
+
};
|
|
145128
|
+
|
|
144267
145129
|
// src/services/questions/parseStatusLine.ts
|
|
144268
145130
|
var MODEL_RE = /(Opus|Sonnet|Haiku|Fable)\s+[\d.]+(?:\s*\([^)]*\))?/;
|
|
144269
145131
|
var EFFORT_RE = /●\s*([A-Za-z]+)\s*·\s*\/effort/;
|
|
@@ -144972,13 +145834,13 @@ function hashPrefix(text) {
|
|
|
144972
145834
|
}
|
|
144973
145835
|
|
|
144974
145836
|
// src/utils/conversationEtag.ts
|
|
144975
|
-
var
|
|
145837
|
+
var import_node_crypto4 = require("crypto");
|
|
144976
145838
|
function computeConversationEtag({
|
|
144977
145839
|
filePath,
|
|
144978
145840
|
messageCount,
|
|
144979
145841
|
timestamp: timestamp2
|
|
144980
145842
|
}) {
|
|
144981
|
-
const digest = (0,
|
|
145843
|
+
const digest = (0, import_node_crypto4.createHash)("sha1").update(`${filePath}:${messageCount}:${timestamp2}`).digest("hex").slice(0, 16);
|
|
144982
145844
|
return `"${digest}"`;
|
|
144983
145845
|
}
|
|
144984
145846
|
|
|
@@ -145067,6 +145929,24 @@ var WSHub = class {
|
|
|
145067
145929
|
this.clients.delete(client);
|
|
145068
145930
|
}
|
|
145069
145931
|
}
|
|
145932
|
+
// Scoped broadcast for high-frequency per-session messages (terminal_output,
|
|
145933
|
+
// user_message). Sending to every connected client for every PTY output
|
|
145934
|
+
// chunk made broadcast() cost scale with connections x active sessions;
|
|
145935
|
+
// this bounds it to only that session's subscribers.
|
|
145936
|
+
broadcastToClients(clients, message) {
|
|
145937
|
+
const data = JSON.stringify(message);
|
|
145938
|
+
for (const client of clients) {
|
|
145939
|
+
try {
|
|
145940
|
+
if (client.readyState === client.OPEN) {
|
|
145941
|
+
client.send(data);
|
|
145942
|
+
} else {
|
|
145943
|
+
this.clients.delete(client);
|
|
145944
|
+
}
|
|
145945
|
+
} catch {
|
|
145946
|
+
this.clients.delete(client);
|
|
145947
|
+
}
|
|
145948
|
+
}
|
|
145949
|
+
}
|
|
145070
145950
|
unicast(ws2, message) {
|
|
145071
145951
|
try {
|
|
145072
145952
|
if (ws2.readyState === ws2.OPEN) {
|
|
@@ -145129,6 +146009,7 @@ var ADOPT_KILL_TIMEOUT_MS = 5e3;
|
|
|
145129
146009
|
var ADOPT_KILL_POLL_MS = 100;
|
|
145130
146010
|
var REFRESH_TTL_MS = 2e3;
|
|
145131
146011
|
var START_READY_TIMEOUT_MS = 1e4;
|
|
146012
|
+
var MODEL_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
145132
146013
|
var EXTERNAL_TAIL_RECENCY_MS = RESUME_BUSY_WINDOW_MS;
|
|
145133
146014
|
var EXTERNAL_TAIL_MAX = 32;
|
|
145134
146015
|
var EXTERNAL_TAIL_IDLE_MS = 3e5;
|
|
@@ -145232,6 +146113,8 @@ var StreamerServer = class {
|
|
|
145232
146113
|
dbPool = null;
|
|
145233
146114
|
dbInstanceId = null;
|
|
145234
146115
|
disableDb = false;
|
|
146116
|
+
// Skip the startup warm-up scan (test hook; see ServerConfig.skipStartupWarmup).
|
|
146117
|
+
skipStartupWarmup;
|
|
145235
146118
|
browseRoot = null;
|
|
145236
146119
|
publicUrl = null;
|
|
145237
146120
|
browserCors;
|
|
@@ -145241,6 +146124,12 @@ var StreamerServer = class {
|
|
|
145241
146124
|
sessionInputAttempts = /* @__PURE__ */ new Map();
|
|
145242
146125
|
ptyGracePeriodMs;
|
|
145243
146126
|
defaultSystemPrompt;
|
|
146127
|
+
// Resolved once at boot; see src/feature-flags.ts. Total map — every registry
|
|
146128
|
+
// id is present, so indexing it never yields undefined.
|
|
146129
|
+
featureFlags;
|
|
146130
|
+
// Derived from featureFlags.codexSystemPrompt. Kept as its own field so the
|
|
146131
|
+
// read site in startFresh() is unchanged.
|
|
146132
|
+
codexSystemPromptEnabled;
|
|
145244
146133
|
defaultPermissionMode;
|
|
145245
146134
|
defaultModel;
|
|
145246
146135
|
defaultEffort;
|
|
@@ -145265,6 +146154,11 @@ var StreamerServer = class {
|
|
|
145265
146154
|
// every provider; read only by the idle reaper. Entries are dropped when the
|
|
145266
146155
|
// session leaves the runner (reap/exit/hold).
|
|
145267
146156
|
lastAgentChunkAt = /* @__PURE__ */ new Map();
|
|
146157
|
+
// sessionId → last terminal_output seq broadcast (starts at 1, per session).
|
|
146158
|
+
// Stamped on every terminal_output/terminal_replay so a client can detect a
|
|
146159
|
+
// stale chunk delivered after a reconnect race instead of trusting raw WS
|
|
146160
|
+
// arrival order. Entries dropped alongside lastAgentChunkAt.
|
|
146161
|
+
terminalSeq = /* @__PURE__ */ new Map();
|
|
145268
146162
|
// Recently accepted input idempotency keys (C4). A retried POST replays its
|
|
145269
146163
|
// original outcome instead of submitting the prompt to the agent twice.
|
|
145270
146164
|
idempotency = new IdempotencyStore();
|
|
@@ -145296,6 +146190,12 @@ var StreamerServer = class {
|
|
|
145296
146190
|
// Paired-device registry (C5). Null when the cache DB failed to open — auth
|
|
145297
146191
|
// then falls back to the shared API key alone, which is the pre-C5 behaviour.
|
|
145298
146192
|
devicesRepo = null;
|
|
146193
|
+
// Live Activity push (Feature 12). Null when APNS_KEY is unset — the ordinary
|
|
146194
|
+
// case on a dev machine and in CI, where the feature is simply off. Missing an
|
|
146195
|
+
// optional push credential must never stop the server from booting.
|
|
146196
|
+
apnsClient = null;
|
|
146197
|
+
liveActivityNotifier = null;
|
|
146198
|
+
liveActivityRenewal = null;
|
|
145299
146199
|
discoveryCache = null;
|
|
145300
146200
|
cacheDir;
|
|
145301
146201
|
tailSize;
|
|
@@ -145326,11 +146226,17 @@ var StreamerServer = class {
|
|
|
145326
146226
|
}
|
|
145327
146227
|
this.verbose = config2.verbose ?? false;
|
|
145328
146228
|
this.disableDb = config2.disableDb ?? false;
|
|
146229
|
+
this.skipStartupWarmup = config2.skipStartupWarmup ?? false;
|
|
145329
146230
|
this.scannerPersistenceDisabled = config2.scannerPersistent === false;
|
|
145330
146231
|
this.scanProfiles = config2.scanProfiles;
|
|
145331
146232
|
this.codexRoots = config2.codexRoots ?? [(0, import_path29.join)((0, import_os12.homedir)(), ".codex", "sessions")];
|
|
145332
146233
|
this.ptyGracePeriodMs = config2.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
|
|
145333
146234
|
this.defaultSystemPrompt = config2.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
146235
|
+
this.featureFlags = resolveFeatureFlags({ cli: config2.featureFlags, yaml: loadFeatureFlags() });
|
|
146236
|
+
if (config2.codexSystemPromptEnabled !== void 0) {
|
|
146237
|
+
this.featureFlags.codexSystemPrompt = config2.codexSystemPromptEnabled;
|
|
146238
|
+
}
|
|
146239
|
+
this.codexSystemPromptEnabled = this.featureFlags.codexSystemPrompt;
|
|
145334
146240
|
this.defaultPermissionMode = config2.defaultPermissionMode ?? loadDefaultPermissionMode() ?? "acceptEdits";
|
|
145335
146241
|
this.defaultModel = config2.defaultModel ?? "sonnet";
|
|
145336
146242
|
this.defaultEffort = config2.defaultEffort ?? "low";
|
|
@@ -145346,6 +146252,13 @@ var StreamerServer = class {
|
|
|
145346
146252
|
}, this.directoryDebounceMs);
|
|
145347
146253
|
this.includeAgents = parseIncludeAgentsEnv(process.env.THREADBASE_INCLUDE_AGENTS);
|
|
145348
146254
|
this.agentEntrypoints = parseAgentEntrypointsEnv(process.env.THREADBASE_AGENT_ENTRYPOINTS);
|
|
146255
|
+
const enabledFlags = nonDefaultFeatureFlags(this.featureFlags);
|
|
146256
|
+
if (enabledFlags.length > 0) {
|
|
146257
|
+
this.log.info(`Feature flags active: ${enabledFlags.join(", ")}`, {
|
|
146258
|
+
event: "config.feature_flags_active",
|
|
146259
|
+
flags: enabledFlags
|
|
146260
|
+
});
|
|
146261
|
+
}
|
|
145349
146262
|
const rawRoot = process.env.THREADBASE_BROWSE_ROOT ?? loadBrowseRoot() ?? config2.browseRoot;
|
|
145350
146263
|
if (rawRoot) {
|
|
145351
146264
|
(0, import_promises16.realpath)(rawRoot).then((resolved) => {
|
|
@@ -145462,10 +146375,22 @@ var StreamerServer = class {
|
|
|
145462
146375
|
logger: getLogger("pty"),
|
|
145463
146376
|
onOutput: (sessionId, data) => {
|
|
145464
146377
|
this.lastAgentChunkAt.set(sessionId, Date.now());
|
|
145465
|
-
this.
|
|
146378
|
+
const seq = (this.terminalSeq.get(sessionId) ?? 0) + 1;
|
|
146379
|
+
this.terminalSeq.set(sessionId, seq);
|
|
146380
|
+
this.wsHub.broadcastToClients(this.sessionSubscribers.get(sessionId) ?? [], {
|
|
146381
|
+
type: "terminal_output",
|
|
146382
|
+
sessionId,
|
|
146383
|
+
data,
|
|
146384
|
+
seq
|
|
146385
|
+
});
|
|
145466
146386
|
},
|
|
145467
146387
|
onUserMessage: (sessionId, text, ts2) => {
|
|
145468
|
-
this.wsHub.
|
|
146388
|
+
this.wsHub.broadcastToClients(this.sessionSubscribers.get(sessionId) ?? [], {
|
|
146389
|
+
type: "user_message",
|
|
146390
|
+
sessionId,
|
|
146391
|
+
text,
|
|
146392
|
+
ts: ts2
|
|
146393
|
+
});
|
|
145469
146394
|
},
|
|
145470
146395
|
onPermissionChange: (sessionId, gate) => {
|
|
145471
146396
|
this.handlePermissionChange(sessionId, gate);
|
|
@@ -145539,6 +146464,7 @@ var StreamerServer = class {
|
|
|
145539
146464
|
if (resp) {
|
|
145540
146465
|
this.wsHub.broadcast({ type: "session_update", session: resp });
|
|
145541
146466
|
}
|
|
146467
|
+
void this.liveActivityNotifier?.onStatusChange(session);
|
|
145542
146468
|
this.sessionStatusBus.emit(`status:${session.id}`, session.status);
|
|
145543
146469
|
}
|
|
145544
146470
|
});
|
|
@@ -145573,6 +146499,7 @@ var StreamerServer = class {
|
|
|
145573
146499
|
logMenubarRequests: this.logMenubarRequests,
|
|
145574
146500
|
rotateApiKey: () => this.rotateApiKey(),
|
|
145575
146501
|
claudeFlagsConfig: () => this.getClaudeFlagsConfig(),
|
|
146502
|
+
featureFlagsConfig: () => this.getFeatureFlagsConfig(),
|
|
145576
146503
|
setClaudeFlagsConfig: (values, extraArgs) => this.setClaudeFlagsConfig(values, extraArgs),
|
|
145577
146504
|
publicUrl: this.publicUrl,
|
|
145578
146505
|
browseRoot: this.browseRoot,
|
|
@@ -145600,6 +146527,8 @@ var StreamerServer = class {
|
|
|
145600
146527
|
handleCancel: (id, res) => this.handleCancel(id, res),
|
|
145601
146528
|
handleStopSession: (id, res) => this.handleStopSession(id, res),
|
|
145602
146529
|
handleSetSessionName: (id, req, res) => this.handleSetSessionName(id, req, res),
|
|
146530
|
+
handleSetSessionModel: (id, req, res) => this.applyLiveSessionSetting(id, req, res, "model"),
|
|
146531
|
+
handleSetSessionEffort: (id, req, res) => this.applyLiveSessionSetting(id, req, res, "effort"),
|
|
145603
146532
|
handleUploadFile: (id, req, res) => this.handleUploadFile(id, req, res),
|
|
145604
146533
|
handleAdopt: (id, res) => this.handleAdopt(id, res),
|
|
145605
146534
|
handleResume: (req, res) => this.handleResume(req, res),
|
|
@@ -145646,7 +146575,8 @@ var StreamerServer = class {
|
|
|
145646
146575
|
type: "terminal_replay",
|
|
145647
146576
|
sessionId: msg.sessionId,
|
|
145648
146577
|
lines,
|
|
145649
|
-
userMessages
|
|
146578
|
+
userMessages,
|
|
146579
|
+
seq: this.terminalSeq.get(msg.sessionId)
|
|
145650
146580
|
})
|
|
145651
146581
|
);
|
|
145652
146582
|
}
|
|
@@ -145804,6 +146734,41 @@ var StreamerServer = class {
|
|
|
145804
146734
|
}
|
|
145805
146735
|
this.ptyGraceDeferCounts.delete(sessionId);
|
|
145806
146736
|
}
|
|
146737
|
+
/**
|
|
146738
|
+
* Bring up Live Activity push, if credentials are present (Feature 12).
|
|
146739
|
+
*
|
|
146740
|
+
* APNS_KEY absent is the ordinary case on a dev machine and in CI, so this
|
|
146741
|
+
* logs once at info and leaves the feature off rather than failing: the server
|
|
146742
|
+
* must not refuse to boot over a missing optional push credential.
|
|
146743
|
+
*
|
|
146744
|
+
* The key is read from the environment as PEM contents and never from a path
|
|
146745
|
+
* on disk; neither it nor any device token is ever logged.
|
|
146746
|
+
*/
|
|
146747
|
+
initLiveActivityPush(pushRepo) {
|
|
146748
|
+
const creds = readApnsCredentialsFromEnv();
|
|
146749
|
+
if (!creds) {
|
|
146750
|
+
const why = describeMissingApnsCredentials();
|
|
146751
|
+
if (why) this.log.info(why, { event: "live_activity.disabled" });
|
|
146752
|
+
return;
|
|
146753
|
+
}
|
|
146754
|
+
this.apnsClient = new ApnsClient(creds);
|
|
146755
|
+
const sender = new LiveActivitySender(this.apnsClient, pushRepo);
|
|
146756
|
+
const serverId = process.env.THREADBASE_INSTANCE_ID ?? (0, import_os12.hostname)();
|
|
146757
|
+
this.liveActivityNotifier = new LiveActivityNotifier(sender, serverId, (0, import_os12.hostname)());
|
|
146758
|
+
this.liveActivityRenewal = new LiveActivityRenewalScheduler({
|
|
146759
|
+
repo: pushRepo,
|
|
146760
|
+
sender,
|
|
146761
|
+
sessionStore: this.sessionStore,
|
|
146762
|
+
serverId,
|
|
146763
|
+
serverLabel: (0, import_os12.hostname)()
|
|
146764
|
+
});
|
|
146765
|
+
this.liveActivityRenewal.start();
|
|
146766
|
+
this.log.info("Live Activity push enabled", {
|
|
146767
|
+
event: "live_activity.enabled",
|
|
146768
|
+
host: creds.host,
|
|
146769
|
+
topic: `${creds.bundleId}.push-type.liveactivity`
|
|
146770
|
+
});
|
|
146771
|
+
}
|
|
145807
146772
|
/**
|
|
145808
146773
|
* Classify sessions left behind by previous streamer runs (C1 Phase 3a).
|
|
145809
146774
|
*
|
|
@@ -145971,6 +146936,7 @@ var StreamerServer = class {
|
|
|
145971
146936
|
);
|
|
145972
146937
|
this.ptyManager.putOnHold(session.id);
|
|
145973
146938
|
this.lastAgentChunkAt.delete(session.id);
|
|
146939
|
+
this.terminalSeq.delete(session.id);
|
|
145974
146940
|
this.idempotency.clear(session.id);
|
|
145975
146941
|
this.sessionSubscribers.delete(session.id);
|
|
145976
146942
|
reaped.push(session.id);
|
|
@@ -146113,6 +147079,7 @@ var StreamerServer = class {
|
|
|
146113
147079
|
this.cacheMetadataRepo = new CacheMetadataRepository(db);
|
|
146114
147080
|
this.pushRepo = new PushRepository(db);
|
|
146115
147081
|
this.devicesRepo = new DevicesRepository(db);
|
|
147082
|
+
this.initLiveActivityPush(this.pushRepo);
|
|
146116
147083
|
this.cacheMonitor = new CacheIntegrityMonitor(
|
|
146117
147084
|
this.cache,
|
|
146118
147085
|
this.wsHub,
|
|
@@ -146144,6 +147111,14 @@ var StreamerServer = class {
|
|
|
146144
147111
|
);
|
|
146145
147112
|
this.scannerPersistenceDisabled = true;
|
|
146146
147113
|
}
|
|
147114
|
+
if (this.skipStartupWarmup) {
|
|
147115
|
+
this.log.debug?.("startup warm-up scan skipped (skipStartupWarmup)", {
|
|
147116
|
+
event: "cache.warmup_skipped"
|
|
147117
|
+
});
|
|
147118
|
+
this.finishWarmup(0);
|
|
147119
|
+
resolveWarm();
|
|
147120
|
+
return;
|
|
147121
|
+
}
|
|
146147
147122
|
const warmupStatCache = this.buildStatCache(null);
|
|
146148
147123
|
const warmupScanner = this.newScanner(warmupStatCache ? { persistent: false } : void 0);
|
|
146149
147124
|
this.allScanners.add(warmupScanner);
|
|
@@ -146335,6 +147310,7 @@ var StreamerServer = class {
|
|
|
146335
147310
|
this.idleReaperTimer = null;
|
|
146336
147311
|
}
|
|
146337
147312
|
this.lastAgentChunkAt.clear();
|
|
147313
|
+
this.terminalSeq.clear();
|
|
146338
147314
|
this.recordShutdownState();
|
|
146339
147315
|
this.markScannerStaleDebounced.cancel();
|
|
146340
147316
|
await Promise.all([...this.inFlightCacheWrites]);
|
|
@@ -146347,6 +147323,8 @@ var StreamerServer = class {
|
|
|
146347
147323
|
this.externalTails.clear();
|
|
146348
147324
|
this.wsHub.dispose();
|
|
146349
147325
|
this.pairTokens.dispose();
|
|
147326
|
+
this.liveActivityRenewal?.stop();
|
|
147327
|
+
this.apnsClient?.close();
|
|
146350
147328
|
if (this.dbPool) {
|
|
146351
147329
|
await this.dbPool.end();
|
|
146352
147330
|
}
|
|
@@ -146418,7 +147396,6 @@ var StreamerServer = class {
|
|
|
146418
147396
|
json2(res, 400, { error: message });
|
|
146419
147397
|
return;
|
|
146420
147398
|
}
|
|
146421
|
-
const { hostname: hostname4 } = require("os");
|
|
146422
147399
|
const ts2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
146423
147400
|
this.log.info(`[pair] token exchanged from ${ip} at ${ts2}`, {
|
|
146424
147401
|
event: "pair.token_exchanged",
|
|
@@ -146441,7 +147418,7 @@ var StreamerServer = class {
|
|
|
146441
147418
|
nonce: sealed.nonce,
|
|
146442
147419
|
ephemeralPublicKey: sealed.ephemeralPublicKey,
|
|
146443
147420
|
publicUrl: this.publicUrl,
|
|
146444
|
-
machineName:
|
|
147421
|
+
machineName: (0, import_os12.hostname)(),
|
|
146445
147422
|
...device && {
|
|
146446
147423
|
deviceId: device.deviceId,
|
|
146447
147424
|
deviceToken: device.deviceToken,
|
|
@@ -146463,6 +147440,16 @@ var StreamerServer = class {
|
|
|
146463
147440
|
});
|
|
146464
147441
|
return { newKey, persisted };
|
|
146465
147442
|
}
|
|
147443
|
+
/**
|
|
147444
|
+
* The registry ships with the values so a client renders the list from one
|
|
147445
|
+
* round-trip, same as getClaudeFlagsConfig().
|
|
147446
|
+
*
|
|
147447
|
+
* Deliberately no `persisted` field: unlike claude-flags there is no PUT, and
|
|
147448
|
+
* the absence of that field is the signal that this endpoint is read-only.
|
|
147449
|
+
*/
|
|
147450
|
+
getFeatureFlagsConfig() {
|
|
147451
|
+
return { registry: FEATURE_FLAGS, values: this.featureFlags };
|
|
147452
|
+
}
|
|
146466
147453
|
getClaudeFlagsConfig() {
|
|
146467
147454
|
return {
|
|
146468
147455
|
registry: CLAUDE_FLAGS,
|
|
@@ -146505,6 +147492,30 @@ var StreamerServer = class {
|
|
|
146505
147492
|
persisted: this.claudeFlagsPersistable
|
|
146506
147493
|
};
|
|
146507
147494
|
}
|
|
147495
|
+
/**
|
|
147496
|
+
* The three spawn options that a configured claude-flag can override, with
|
|
147497
|
+
* the boot-time CLI/yaml default as the fallback. Spread into every
|
|
147498
|
+
* start/resume/adopt call so all three paths agree.
|
|
147499
|
+
*
|
|
147500
|
+
* These ids are excluded from buildFlagArgs (SPAWN_POSITIONAL_FLAG_IDS)
|
|
147501
|
+
* precisely because they arrive here instead — the PTY spawn paths pass them
|
|
147502
|
+
* as explicit positionals, so emitting them from the allowlist too would
|
|
147503
|
+
* duplicate the flag.
|
|
147504
|
+
*
|
|
147505
|
+
* Narrowed with the type guards rather than cast: ClaudeFlagValues is a loose
|
|
147506
|
+
* Record by design, and while validateFlagValues already guarantees the shape
|
|
147507
|
+
* on the way in, TypeScript cannot see that through the record.
|
|
147508
|
+
*/
|
|
147509
|
+
spawnFlagOverrides() {
|
|
147510
|
+
const mode = this.claudeFlags.permissionMode;
|
|
147511
|
+
const model = this.claudeFlags.model;
|
|
147512
|
+
const effort = this.claudeFlags.effort;
|
|
147513
|
+
return {
|
|
147514
|
+
permissionMode: isPermissionMode(mode) ? mode : this.defaultPermissionMode,
|
|
147515
|
+
model: typeof model === "string" ? model : this.defaultModel,
|
|
147516
|
+
effort: isEffortLevel(effort) ? effort : this.defaultEffort
|
|
147517
|
+
};
|
|
147518
|
+
}
|
|
146508
147519
|
checkRateLimit(map2, key, limit, windowMs) {
|
|
146509
147520
|
const now = Date.now();
|
|
146510
147521
|
const arr = (map2.get(key) ?? []).filter((t) => now - t < windowMs);
|
|
@@ -147654,11 +148665,9 @@ var StreamerServer = class {
|
|
|
147654
148665
|
projectPath,
|
|
147655
148666
|
projectName: body.projectName,
|
|
147656
148667
|
branch: body.branch,
|
|
147657
|
-
permissionMode: this.defaultPermissionMode,
|
|
147658
148668
|
claudeFlags: this.claudeFlags,
|
|
147659
148669
|
claudeExtraArgs: this.claudeExtraArgs,
|
|
147660
|
-
|
|
147661
|
-
effort: this.defaultEffort
|
|
148670
|
+
...this.spawnFlagOverrides()
|
|
147662
148671
|
});
|
|
147663
148672
|
this.sessionStore.addManaged(session);
|
|
147664
148673
|
this.recordSessionSpawn(session);
|
|
@@ -148105,11 +149114,9 @@ var StreamerServer = class {
|
|
|
148105
149114
|
projectPath,
|
|
148106
149115
|
projectName,
|
|
148107
149116
|
branch,
|
|
148108
|
-
permissionMode: this.defaultPermissionMode,
|
|
148109
149117
|
claudeFlags: this.claudeFlags,
|
|
148110
149118
|
claudeExtraArgs: this.claudeExtraArgs,
|
|
148111
|
-
|
|
148112
|
-
effort: this.defaultEffort
|
|
149119
|
+
...this.spawnFlagOverrides()
|
|
148113
149120
|
});
|
|
148114
149121
|
this.sessionStore.addManaged(session);
|
|
148115
149122
|
this.recordSessionSpawn(session);
|
|
@@ -148175,17 +149182,16 @@ var StreamerServer = class {
|
|
|
148175
149182
|
BROWSE_SYSTEM_PROMPT(this.browseRoot),
|
|
148176
149183
|
typeof clientPrompt === "string" ? clientPrompt : null
|
|
148177
149184
|
].filter(Boolean);
|
|
149185
|
+
const includeSystemPrompt = provider !== CODEX_CLI_PROVIDER2 || this.codexSystemPromptEnabled;
|
|
148178
149186
|
try {
|
|
148179
149187
|
const session = await this.ptyManager.startFresh({
|
|
148180
149188
|
provider,
|
|
148181
149189
|
projectPath: resolvedPath,
|
|
148182
149190
|
projectName: body.projectName,
|
|
148183
|
-
systemPrompt: systemPromptParts.join("\n"),
|
|
148184
|
-
permissionMode: this.defaultPermissionMode,
|
|
149191
|
+
...includeSystemPrompt && { systemPrompt: systemPromptParts.join("\n") },
|
|
148185
149192
|
claudeFlags: this.claudeFlags,
|
|
148186
149193
|
claudeExtraArgs: this.claudeExtraArgs,
|
|
148187
|
-
|
|
148188
|
-
effort: this.defaultEffort
|
|
149194
|
+
...this.spawnFlagOverrides()
|
|
148189
149195
|
});
|
|
148190
149196
|
this.sessionStore.addManaged(session);
|
|
148191
149197
|
this.recordSessionSpawn(session);
|
|
@@ -148545,6 +149551,87 @@ var StreamerServer = class {
|
|
|
148545
149551
|
this.cache.upsertSessionName(sessionId, name);
|
|
148546
149552
|
json2(res, 200, { ok: true });
|
|
148547
149553
|
}
|
|
149554
|
+
/**
|
|
149555
|
+
* Retarget a LIVE session's model or effort by typing the corresponding
|
|
149556
|
+
* Claude Code slash command into its PTY.
|
|
149557
|
+
*
|
|
149558
|
+
* There is no CLI or IPC channel for this — `--model`/`--effort` are spawn
|
|
149559
|
+
* arguments — so the interactive `/model <x>` / `/effort <y>` commands are the
|
|
149560
|
+
* only way to change a session already running. Both accept an argument and
|
|
149561
|
+
* apply it without opening the picker (verified against Claude Code v2.1.220).
|
|
149562
|
+
*
|
|
149563
|
+
* Answers 202, not 200: the value is applied by the TUI on its next render, so
|
|
149564
|
+
* there is nothing truthful to echo back synchronously. Clients confirm with
|
|
149565
|
+
* `GET /api/sessions/:id`, which scrapes the applied value off the live status
|
|
149566
|
+
* line.
|
|
149567
|
+
*/
|
|
149568
|
+
async applyLiveSessionSetting(sessionId, req, res, setting) {
|
|
149569
|
+
const session = this.ptyManager.getSession(sessionId);
|
|
149570
|
+
if (!session) {
|
|
149571
|
+
const known = this.sessionStore.getManaged(sessionId);
|
|
149572
|
+
if (known) {
|
|
149573
|
+
json2(res, 409, {
|
|
149574
|
+
error: "Session has no live PTY; resume it first",
|
|
149575
|
+
code: "SESSION_IDLE"
|
|
149576
|
+
});
|
|
149577
|
+
return;
|
|
149578
|
+
}
|
|
149579
|
+
json2(res, 404, { error: "Session not found" });
|
|
149580
|
+
return;
|
|
149581
|
+
}
|
|
149582
|
+
if ((session.provider ?? CLAUDE_CODE_PROVIDER2) !== CLAUDE_CODE_PROVIDER2) {
|
|
149583
|
+
json2(res, 501, {
|
|
149584
|
+
error: `Setting ${setting} on a ${session.provider} session is not supported`,
|
|
149585
|
+
code: "UNSUPPORTED_PROVIDER"
|
|
149586
|
+
});
|
|
149587
|
+
return;
|
|
149588
|
+
}
|
|
149589
|
+
if (session.status === "running") {
|
|
149590
|
+
json2(res, 409, {
|
|
149591
|
+
error: "Session is mid-turn; retry once it is waiting for input",
|
|
149592
|
+
code: "SESSION_BUSY"
|
|
149593
|
+
});
|
|
149594
|
+
return;
|
|
149595
|
+
}
|
|
149596
|
+
let parsed;
|
|
149597
|
+
try {
|
|
149598
|
+
parsed = await readBody(req);
|
|
149599
|
+
} catch {
|
|
149600
|
+
json2(res, 400, { error: "Invalid JSON" });
|
|
149601
|
+
return;
|
|
149602
|
+
}
|
|
149603
|
+
let value;
|
|
149604
|
+
if (setting === "effort") {
|
|
149605
|
+
if (!isEffortLevel(parsed.effort)) {
|
|
149606
|
+
json2(res, 400, {
|
|
149607
|
+
error: `effort must be one of ${EFFORT_LEVELS.join(", ")}`
|
|
149608
|
+
});
|
|
149609
|
+
return;
|
|
149610
|
+
}
|
|
149611
|
+
value = parsed.effort;
|
|
149612
|
+
} else {
|
|
149613
|
+
if (typeof parsed.model !== "string" || !MODEL_NAME_RE.test(parsed.model)) {
|
|
149614
|
+
json2(res, 400, {
|
|
149615
|
+
error: "model must be an alias or full model name (letters, digits, dot, dash, underscore)"
|
|
149616
|
+
});
|
|
149617
|
+
return;
|
|
149618
|
+
}
|
|
149619
|
+
value = parsed.model;
|
|
149620
|
+
}
|
|
149621
|
+
try {
|
|
149622
|
+
this.ptyManager.sendKeys(sessionId, `/${setting} ${value}\r`);
|
|
149623
|
+
} catch (err) {
|
|
149624
|
+
json2(res, 400, { error: err instanceof Error ? err.message : "Failed to write to session" });
|
|
149625
|
+
return;
|
|
149626
|
+
}
|
|
149627
|
+
this.log.info(`Live session ${setting} set to ${value}`, {
|
|
149628
|
+
event: "session.setting_applied",
|
|
149629
|
+
sessionId,
|
|
149630
|
+
setting,
|
|
149631
|
+
value
|
|
149632
|
+
});
|
|
149633
|
+
json2(res, 202, { id: sessionId, [setting]: value });
|
|
149634
|
+
}
|
|
148548
149635
|
handleGetSessionNames(res) {
|
|
148549
149636
|
if (!this.cache) {
|
|
148550
149637
|
json2(res, 200, {});
|
|
@@ -148886,7 +149973,7 @@ function isBrewInstall(scriptPath = process.argv[1] ?? "") {
|
|
|
148886
149973
|
}
|
|
148887
149974
|
|
|
148888
149975
|
// src/updater/download.ts
|
|
148889
|
-
var
|
|
149976
|
+
var import_node_crypto5 = require("crypto");
|
|
148890
149977
|
var import_node_fs9 = require("fs");
|
|
148891
149978
|
var import_node_path10 = require("path");
|
|
148892
149979
|
var import_promises17 = require("stream/promises");
|
|
@@ -148953,7 +150040,7 @@ async function downloadAndVerify(opts) {
|
|
|
148953
150040
|
if (!res.ok || !res.body) {
|
|
148954
150041
|
throw new Error(`Failed to download ${artifact.filename}: ${res.status} ${res.statusText}`);
|
|
148955
150042
|
}
|
|
148956
|
-
const hash2 = (0,
|
|
150043
|
+
const hash2 = (0, import_node_crypto5.createHash)("sha256");
|
|
148957
150044
|
const out = (0, import_node_fs9.createWriteStream)(targetPath);
|
|
148958
150045
|
let bytes = 0;
|
|
148959
150046
|
const measured = new TransformStream({
|
|
@@ -149258,7 +150345,7 @@ var import_node_path17 = require("path");
|
|
|
149258
150345
|
var import_path32 = __toESM(require("path"), 1);
|
|
149259
150346
|
var import_node_fs13 = __toESM(require("fs"), 1);
|
|
149260
150347
|
var import_node_assert = __toESM(require("assert"), 1);
|
|
149261
|
-
var
|
|
150348
|
+
var import_node_crypto6 = require("crypto");
|
|
149262
150349
|
var import_node_fs14 = __toESM(require("fs"), 1);
|
|
149263
150350
|
var import_node_path18 = __toESM(require("path"), 1);
|
|
149264
150351
|
var import_fs33 = __toESM(require("fs"), 1);
|
|
@@ -151666,7 +152753,7 @@ var Te = fo === "win32";
|
|
|
151666
152753
|
var mo = 1024;
|
|
151667
152754
|
var uo = (s3, t) => {
|
|
151668
152755
|
if (!Te) return import_node_fs14.default.unlink(s3, t);
|
|
151669
|
-
let e = s3 + ".DELETE." + (0,
|
|
152756
|
+
let e = s3 + ".DELETE." + (0, import_node_crypto6.randomBytes)(16).toString("hex");
|
|
151670
152757
|
import_node_fs14.default.rename(s3, e, (i) => {
|
|
151671
152758
|
if (i) return t(i);
|
|
151672
152759
|
import_node_fs14.default.unlink(e, t);
|
|
@@ -151674,7 +152761,7 @@ var uo = (s3, t) => {
|
|
|
151674
152761
|
};
|
|
151675
152762
|
var po = (s3) => {
|
|
151676
152763
|
if (!Te) return import_node_fs14.default.unlinkSync(s3);
|
|
151677
|
-
let t = s3 + ".DELETE." + (0,
|
|
152764
|
+
let t = s3 + ".DELETE." + (0, import_node_crypto6.randomBytes)(16).toString("hex");
|
|
151678
152765
|
import_node_fs14.default.renameSync(s3, t), import_node_fs14.default.unlinkSync(t);
|
|
151679
152766
|
};
|
|
151680
152767
|
var vr = (s3, t, e) => s3 !== void 0 && s3 === s3 >>> 0 ? s3 : t !== void 0 && t === t >>> 0 ? t : e;
|
|
@@ -152331,7 +153418,7 @@ init_launchd();
|
|
|
152331
153418
|
init_marker();
|
|
152332
153419
|
init_platform();
|
|
152333
153420
|
init_logger();
|
|
152334
|
-
var
|
|
153421
|
+
var log8 = getLogger("prod");
|
|
152335
153422
|
function clearSupervisorLogs() {
|
|
152336
153423
|
let paths;
|
|
152337
153424
|
try {
|
|
@@ -152488,12 +153575,12 @@ function registerProdCommands(program3) {
|
|
|
152488
153575
|
prod.command("start").description("Restore prod after a user-held suspension").option("--clear-logs", "Clear existing stdout + stderr logs", false).action(async (opts) => {
|
|
152489
153576
|
if (opts.clearLogs === true) clearSupervisorLogs();
|
|
152490
153577
|
const r = await runProdStart();
|
|
152491
|
-
|
|
153578
|
+
log8.info(r.message, void 0, "console");
|
|
152492
153579
|
if (!r.ok) process.exitCode = 1;
|
|
152493
153580
|
});
|
|
152494
153581
|
prod.command("stop").description("Unload the launchd agent (prod will not auto-restart)").action(async () => {
|
|
152495
153582
|
const r = await runProdStop();
|
|
152496
|
-
|
|
153583
|
+
log8.info(r.message, void 0, "console");
|
|
152497
153584
|
});
|
|
152498
153585
|
prod.command("status").description("Report whether prod is supervised, suspended, or down").action(async () => {
|
|
152499
153586
|
const s3 = await runProdStatus();
|
|
@@ -152502,7 +153589,7 @@ function registerProdCommands(program3) {
|
|
|
152502
153589
|
`pid: ${s3.agentPid ?? "(none)"}`,
|
|
152503
153590
|
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
153591
|
];
|
|
152505
|
-
|
|
153592
|
+
log8.info(parts.join("\n "), void 0, "console");
|
|
152506
153593
|
});
|
|
152507
153594
|
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
153595
|
const sup = getSupervisor();
|
|
@@ -152511,17 +153598,17 @@ function registerProdCommands(program3) {
|
|
|
152511
153598
|
sup.bootoutAgent();
|
|
152512
153599
|
sup.bootstrapAgent(specPath, { afterBootout: true });
|
|
152513
153600
|
const what = process.platform === "darwin" ? `agent restarted from ${specPath}` : `task '${TASK_NAME}' restarted`;
|
|
152514
|
-
|
|
153601
|
+
log8.info(what, void 0, "console");
|
|
152515
153602
|
});
|
|
152516
153603
|
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
153604
|
const r = await runProdDoctor({ fix: opts.fix === true });
|
|
152518
|
-
|
|
152519
|
-
for (const f2 of r.findings)
|
|
153605
|
+
log8.info(`findings: ${r.findings.length === 0 ? "(none)" : ""}`, void 0, "console");
|
|
153606
|
+
for (const f2 of r.findings) log8.info(` - ${f2}`, void 0, "console");
|
|
152520
153607
|
if (r.repairs.length) {
|
|
152521
|
-
|
|
152522
|
-
for (const fix of r.repairs)
|
|
153608
|
+
log8.info(`repairs:`, void 0, "console");
|
|
153609
|
+
for (const fix of r.repairs) log8.info(` - ${fix}`, void 0, "console");
|
|
152523
153610
|
} else if (!opts.fix && r.findings.length > 0) {
|
|
152524
|
-
|
|
153611
|
+
log8.info(`(re-run with --fix to apply repairs)`, void 0, "console");
|
|
152525
153612
|
}
|
|
152526
153613
|
});
|
|
152527
153614
|
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 +153619,14 @@ function registerProdCommands(program3) {
|
|
|
152532
153619
|
errorsOnly: opts.errorsOnly === true,
|
|
152533
153620
|
clear: opts.clear === true
|
|
152534
153621
|
});
|
|
152535
|
-
if (r.message)
|
|
153622
|
+
if (r.message) log8.info(r.message, void 0, "console");
|
|
152536
153623
|
if (!r.ok) process.exitCode = 1;
|
|
152537
153624
|
});
|
|
152538
153625
|
program3.addCommand(prod);
|
|
152539
153626
|
}
|
|
152540
153627
|
|
|
152541
153628
|
// cli/index.ts
|
|
152542
|
-
var
|
|
153629
|
+
var log11 = getLogger("cli");
|
|
152543
153630
|
var program2 = new Command();
|
|
152544
153631
|
program2.name("threadbase-streamer").description("PTY session management, WebSocket streaming, and REST API server for Claude Code").version(getVersion());
|
|
152545
153632
|
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 +153648,10 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152561
153648
|
"--claude-flag <id=value>",
|
|
152562
153649
|
"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
153650
|
(value, previous = []) => [...previous, value]
|
|
153651
|
+
).option(
|
|
153652
|
+
"--feature <id=bool>",
|
|
153653
|
+
"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.",
|
|
153654
|
+
(value, previous = []) => [...previous, value]
|
|
152564
153655
|
).option(
|
|
152565
153656
|
"--claude-extra-args <args>",
|
|
152566
153657
|
"Free-text argv appended verbatim to every spawned Claude session, after the allowlisted flags. Unvalidated escape hatch."
|
|
@@ -152577,14 +153668,14 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152577
153668
|
const { checkSqliteAbi: checkSqliteAbi2 } = await Promise.resolve().then(() => (init_check_sqlite_abi(), check_sqlite_abi_exports));
|
|
152578
153669
|
checkSqliteAbi2();
|
|
152579
153670
|
} catch (err) {
|
|
152580
|
-
|
|
153671
|
+
log11.error(err instanceof Error ? err.message : String(err), void 0, "console");
|
|
152581
153672
|
process.exit(1);
|
|
152582
153673
|
}
|
|
152583
153674
|
if (opts.multiAgentFlow) {
|
|
152584
153675
|
process.env.MULTI_AGENT_FLOW = "true";
|
|
152585
153676
|
}
|
|
152586
153677
|
if (opts.defaultPermissionMode !== void 0 && !isPermissionMode(opts.defaultPermissionMode)) {
|
|
152587
|
-
|
|
153678
|
+
log11.error(
|
|
152588
153679
|
`Invalid --default-permission-mode: ${opts.defaultPermissionMode} (expected one of ${PERMISSION_MODES.join(", ")})`,
|
|
152589
153680
|
void 0,
|
|
152590
153681
|
"console"
|
|
@@ -152600,7 +153691,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152600
153691
|
const value = eq === -1 ? true : entry.slice(eq + 1);
|
|
152601
153692
|
const def = findFlag(id);
|
|
152602
153693
|
if (!def) {
|
|
152603
|
-
|
|
153694
|
+
log11.error(
|
|
152604
153695
|
`Invalid --claude-flag id: ${id} (expected one of ${CLAUDE_FLAGS.map((f2) => f2.id).join(", ")})`,
|
|
152605
153696
|
void 0,
|
|
152606
153697
|
"console"
|
|
@@ -152617,15 +153708,23 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152617
153708
|
claudeFlags = validateFlagValues(raw2);
|
|
152618
153709
|
for (const id of Object.keys(raw2)) {
|
|
152619
153710
|
if (!(id in claudeFlags)) {
|
|
152620
|
-
|
|
153711
|
+
log11.error(`Invalid value for --claude-flag ${id}`, void 0, "console");
|
|
152621
153712
|
process.exit(1);
|
|
152622
153713
|
}
|
|
152623
153714
|
}
|
|
152624
153715
|
}
|
|
152625
|
-
|
|
152626
|
-
if (opts.
|
|
152627
|
-
|
|
152628
|
-
|
|
153716
|
+
let featureFlags;
|
|
153717
|
+
if (Array.isArray(opts.feature) && opts.feature.length > 0) {
|
|
153718
|
+
const parsed = parseFeatureFlagArgs(opts.feature);
|
|
153719
|
+
if (parsed.errors.length > 0) {
|
|
153720
|
+
for (const error51 of parsed.errors) log11.error(`--feature: ${error51}`, void 0, "console");
|
|
153721
|
+
process.exit(1);
|
|
153722
|
+
}
|
|
153723
|
+
featureFlags = parsed.values;
|
|
153724
|
+
}
|
|
153725
|
+
if (opts.defaultEffort !== void 0 && !isEffortLevel(opts.defaultEffort)) {
|
|
153726
|
+
log11.error(
|
|
153727
|
+
`Invalid --default-effort: ${opts.defaultEffort} (expected one of ${EFFORT_LEVELS.join(", ")})`,
|
|
152629
153728
|
void 0,
|
|
152630
153729
|
"console"
|
|
152631
153730
|
);
|
|
@@ -152635,7 +153734,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152635
153734
|
if (opts.ptyGracePeriodMs !== void 0) {
|
|
152636
153735
|
const parsed = Number(opts.ptyGracePeriodMs);
|
|
152637
153736
|
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
152638
|
-
|
|
153737
|
+
log11.error(
|
|
152639
153738
|
`Invalid --pty-grace-period-ms: ${opts.ptyGracePeriodMs} (expected a non-negative integer; 0 disables auto-hold)`,
|
|
152640
153739
|
void 0,
|
|
152641
153740
|
"console"
|
|
@@ -152655,7 +153754,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152655
153754
|
const { detectConflictingAgents: detectConflictingAgents2, formatConflictMessage: formatConflictMessage2 } = await Promise.resolve().then(() => (init_conflict_check(), conflict_check_exports));
|
|
152656
153755
|
const conflicts = detectConflictingAgents2();
|
|
152657
153756
|
if (conflicts.length > 0) {
|
|
152658
|
-
|
|
153757
|
+
log11.warn(formatConflictMessage2(conflicts), void 0, "console");
|
|
152659
153758
|
}
|
|
152660
153759
|
}
|
|
152661
153760
|
let resolvedDefaultPermissionMode = opts.defaultPermissionMode;
|
|
@@ -152701,20 +153800,21 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152701
153800
|
defaultEffort: opts.defaultEffort,
|
|
152702
153801
|
ptyGracePeriodMs,
|
|
152703
153802
|
claudeFlags,
|
|
153803
|
+
featureFlags,
|
|
152704
153804
|
claudeExtraArgs: opts.claudeExtraArgs
|
|
152705
153805
|
});
|
|
152706
153806
|
await server.listen(resolvedPort);
|
|
152707
153807
|
{
|
|
152708
153808
|
const v2 = getVersion();
|
|
152709
|
-
|
|
153809
|
+
log11.info(`Threadbase Streamer v${v2}`, { version: v2, port: resolvedPort });
|
|
152710
153810
|
}
|
|
152711
|
-
|
|
153811
|
+
log11.info(`Listening on http://localhost:${resolvedPort}`, {
|
|
152712
153812
|
url: `http://localhost:${resolvedPort}`
|
|
152713
153813
|
});
|
|
152714
|
-
|
|
153814
|
+
log11.info(`WebSocket at ws://localhost:${resolvedPort}/ws`, {
|
|
152715
153815
|
wsUrl: `ws://localhost:${resolvedPort}/ws`
|
|
152716
153816
|
});
|
|
152717
|
-
|
|
153817
|
+
log11.info(`API key: ${apiKey}`, { apiKeyMasked: `${apiKey.slice(0, 6)}\u2026` });
|
|
152718
153818
|
try {
|
|
152719
153819
|
await printServerBanner({
|
|
152720
153820
|
port: resolvedPort,
|
|
@@ -152724,15 +153824,15 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152724
153824
|
});
|
|
152725
153825
|
} catch (err) {
|
|
152726
153826
|
const message = err instanceof Error ? err.message : String(err);
|
|
152727
|
-
|
|
152728
|
-
|
|
153827
|
+
log11.warn(`(skipped pairing QR: ${message})`, { reason: message });
|
|
153828
|
+
log11.info(
|
|
152729
153829
|
printUrlBanner({ url: resolveServerUrl({ publicUrl, port: resolvedPort }) }),
|
|
152730
153830
|
void 0,
|
|
152731
153831
|
"console"
|
|
152732
153832
|
);
|
|
152733
153833
|
}
|
|
152734
153834
|
const shutdown = async () => {
|
|
152735
|
-
|
|
153835
|
+
log11.info("Shutting down...");
|
|
152736
153836
|
await server.close();
|
|
152737
153837
|
process.exit(0);
|
|
152738
153838
|
};
|
|
@@ -152740,7 +153840,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152740
153840
|
process.on("SIGINT", shutdown);
|
|
152741
153841
|
process.on("SIGTERM", shutdown);
|
|
152742
153842
|
process.on("uncaughtException", (err) => {
|
|
152743
|
-
|
|
153843
|
+
log11.error(`uncaught: ${err.message}`, {
|
|
152744
153844
|
error: err.message,
|
|
152745
153845
|
stack: err.stack,
|
|
152746
153846
|
event: "process.uncaught"
|
|
@@ -152749,7 +153849,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
152749
153849
|
});
|
|
152750
153850
|
process.on("unhandledRejection", (reason) => {
|
|
152751
153851
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
152752
|
-
|
|
153852
|
+
log11.error(`unhandled rejection: ${msg}`, {
|
|
152753
153853
|
error: msg,
|
|
152754
153854
|
event: "process.unhandled_rejection"
|
|
152755
153855
|
});
|
|
@@ -152774,10 +153874,10 @@ program2.command("cache").description("Manage the local SQLite conversation cach
|
|
|
152774
153874
|
const f2 = dbPath + suffix;
|
|
152775
153875
|
if (existsSync15(f2)) {
|
|
152776
153876
|
rmSync6(f2);
|
|
152777
|
-
|
|
153877
|
+
log11.info(`Deleted ${f2}`, { path: f2 }, "console");
|
|
152778
153878
|
}
|
|
152779
153879
|
}
|
|
152780
|
-
|
|
153880
|
+
log11.info("Cache cleared. Restart the server to rebuild.", void 0, "console");
|
|
152781
153881
|
})
|
|
152782
153882
|
);
|
|
152783
153883
|
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 +153902,7 @@ program2.command("set-key [key]").description("Set the streamer API key in ~/.th
|
|
|
152802
153902
|
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
153903
|
const cfg = loadUpdateConfig();
|
|
152804
153904
|
if (!cfg) {
|
|
152805
|
-
|
|
153905
|
+
log11.warn(
|
|
152806
153906
|
`No update config found at ${DEFAULT_CONFIG_PATH}. Create one with at least 'github_repo: owner/name' to enable updates.`,
|
|
152807
153907
|
void 0,
|
|
152808
153908
|
"console"
|
|
@@ -152821,11 +153921,11 @@ program2.command("update").description("Check for streamer updates from GitHub R
|
|
|
152821
153921
|
appendUpdateLog(
|
|
152822
153922
|
`[check] current=${result2.current} latest=${result2.latest ?? "none"} status=${result2.reason}`
|
|
152823
153923
|
);
|
|
152824
|
-
|
|
152825
|
-
|
|
152826
|
-
|
|
152827
|
-
|
|
152828
|
-
|
|
153924
|
+
log11.info(`Current : ${result2.current}`, void 0, "console");
|
|
153925
|
+
log11.info(`Latest : ${result2.latest ?? "(none)"}`, void 0, "console");
|
|
153926
|
+
log11.info(`Channel : ${cfg.channel}`, void 0, "console");
|
|
153927
|
+
log11.info(`Diff : ${result2.diff ?? "(none)"}`, void 0, "console");
|
|
153928
|
+
log11.info(`Status : ${result2.reason}`, void 0, "console");
|
|
152829
153929
|
return;
|
|
152830
153930
|
}
|
|
152831
153931
|
const port = Number.parseInt(opts.port, 10);
|
|
@@ -152841,20 +153941,20 @@ program2.command("update").description("Check for streamer updates from GitHub R
|
|
|
152841
153941
|
});
|
|
152842
153942
|
switch (result.kind) {
|
|
152843
153943
|
case "no-op":
|
|
152844
|
-
|
|
152845
|
-
|
|
152846
|
-
|
|
153944
|
+
log11.info(`Current : ${result.current}`, void 0, "console");
|
|
153945
|
+
log11.info(`Latest : ${result.latest ?? "(none)"}`, void 0, "console");
|
|
153946
|
+
log11.info(`Status : ${result.reason}`, void 0, "console");
|
|
152847
153947
|
break;
|
|
152848
153948
|
case "unsupported-install":
|
|
152849
|
-
|
|
153949
|
+
log11.warn(result.reason, void 0, "console");
|
|
152850
153950
|
process.exitCode = 2;
|
|
152851
153951
|
break;
|
|
152852
153952
|
case "deferred":
|
|
152853
|
-
|
|
153953
|
+
log11.warn(`Deferred: ${result.reason}`, void 0, "console");
|
|
152854
153954
|
process.exitCode = 2;
|
|
152855
153955
|
break;
|
|
152856
153956
|
case "dry-run":
|
|
152857
|
-
|
|
153957
|
+
log11.info(
|
|
152858
153958
|
`Would install ${result.latest} from ${result.tarballUrl}`,
|
|
152859
153959
|
void 0,
|
|
152860
153960
|
"console"
|
|
@@ -152862,28 +153962,28 @@ program2.command("update").description("Check for streamer updates from GitHub R
|
|
|
152862
153962
|
break;
|
|
152863
153963
|
case "installed":
|
|
152864
153964
|
if (result.restart.method.startsWith("failed:")) {
|
|
152865
|
-
|
|
153965
|
+
log11.error(
|
|
152866
153966
|
`Installed ${result.installed} on disk, but the running service was not updated. Restart: ${result.restart.method}.`,
|
|
152867
153967
|
void 0,
|
|
152868
153968
|
"console"
|
|
152869
153969
|
);
|
|
152870
153970
|
process.exitCode = 1;
|
|
152871
153971
|
} else {
|
|
152872
|
-
|
|
153972
|
+
log11.info(
|
|
152873
153973
|
`Installed ${result.installed} (was ${result.previous}). Restart: ${result.restart.method}.`,
|
|
152874
153974
|
void 0,
|
|
152875
153975
|
"console"
|
|
152876
153976
|
);
|
|
152877
153977
|
}
|
|
152878
153978
|
if (result.pruned.length > 0) {
|
|
152879
|
-
|
|
153979
|
+
log11.info(`Pruned old releases: ${result.pruned.join(", ")}`, void 0, "console");
|
|
152880
153980
|
}
|
|
152881
153981
|
break;
|
|
152882
153982
|
}
|
|
152883
153983
|
} catch (err) {
|
|
152884
153984
|
const message = err instanceof Error ? err.message : String(err);
|
|
152885
153985
|
appendUpdateLog(`[error] ${message}`);
|
|
152886
|
-
|
|
153986
|
+
log11.error(`Update failed: ${message}`, { error: message }, "console");
|
|
152887
153987
|
process.exitCode = 1;
|
|
152888
153988
|
}
|
|
152889
153989
|
});
|
|
@@ -152929,7 +154029,7 @@ async function printServerBanner({
|
|
|
152929
154029
|
}) {
|
|
152930
154030
|
const url2 = resolveServerUrl({ publicUrl, port });
|
|
152931
154031
|
if (!includeQr) {
|
|
152932
|
-
|
|
154032
|
+
log11.info(printUrlBanner({ url: url2 }), void 0, "console");
|
|
152933
154033
|
return;
|
|
152934
154034
|
}
|
|
152935
154035
|
const res = await fetch(`http://localhost:${port}/api/pair/start`, {
|
|
@@ -152944,9 +154044,9 @@ async function printServerBanner({
|
|
|
152944
154044
|
const expSeconds = Math.floor(expiresAt / 1e3);
|
|
152945
154045
|
const payload = `threadbase://pair?url=${encodeURIComponent(url2)}&token=${token}&exp=${expSeconds}`;
|
|
152946
154046
|
const qr2 = await generateQr(payload);
|
|
152947
|
-
|
|
152948
|
-
|
|
152949
|
-
|
|
154047
|
+
log11.info(printUrlBanner({ url: url2, qr: qr2, expiresAt }), void 0, "console");
|
|
154048
|
+
log11.info(`Pair URL: ${payload}`, void 0, "console");
|
|
154049
|
+
log11.info(`Expires in ${expiresInSeconds}s`, void 0, "console");
|
|
152950
154050
|
}
|
|
152951
154051
|
/*! Bundled license information:
|
|
152952
154052
|
|