agent-transport-system 0.1.6 → 0.1.8
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/ats.js +1326 -676
- package/dist/ats.js.map +1 -1
- package/package.json +4 -2
package/dist/ats.js
CHANGED
|
@@ -17,7 +17,7 @@ import WebSocket from "ws";
|
|
|
17
17
|
import { clearLine, createInterface, cursorTo, moveCursor } from "node:readline";
|
|
18
18
|
|
|
19
19
|
//#region package.json
|
|
20
|
-
var version$1 = "0.1.
|
|
20
|
+
var version$1 = "0.1.8";
|
|
21
21
|
var package_default = {
|
|
22
22
|
name: "agent-transport-system",
|
|
23
23
|
version: version$1,
|
|
@@ -40,7 +40,9 @@ var package_default = {
|
|
|
40
40
|
"build:deps": "pnpm --filter @agent-transport-system/agent-runtime build && pnpm --filter @agent-transport-system/protocol build && pnpm --filter @agent-transport-system/gateway-contract build && pnpm --filter @agent-transport-system/schemas build && pnpm --filter @agent-transport-system/adapter-core build && pnpm --filter @agent-transport-system/adapter-claude-code build && pnpm --filter @agent-transport-system/adapter-codex build && pnpm --filter @agent-transport-system/adapter-openclaw build && pnpm --filter @agent-transport-system/atsd build",
|
|
41
41
|
"prebuild": "pnpm run build:deps",
|
|
42
42
|
"build": "tsdown",
|
|
43
|
-
"prepack": "pnpm run build",
|
|
43
|
+
"prepack": "pnpm run build && pnpm run verify:pack-readme",
|
|
44
|
+
"prepublishOnly": "pnpm run verify:pack-readme",
|
|
45
|
+
"verify:pack-readme": "node scripts/verify-npm-pack-readme.mjs",
|
|
44
46
|
"pretypecheck": "pnpm run build:deps",
|
|
45
47
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
46
48
|
"precheck": "pnpm run build:deps",
|
|
@@ -6141,59 +6143,6 @@ const signalTextEnvelopeSchema = object({
|
|
|
6141
6143
|
const signalEnvelopeSchema = union([presenceEnvelopeSchema, signalTextEnvelopeSchema]);
|
|
6142
6144
|
const incomingServerMessageSchema = union([signalEnvelopeSchema, helloAckSchema]);
|
|
6143
6145
|
|
|
6144
|
-
//#endregion
|
|
6145
|
-
//#region src/protocol/urls.ts
|
|
6146
|
-
const TRAILING_SLASHES_REGEX = /\/+$/;
|
|
6147
|
-
const ROOT_PATH_REGEX = /^\/+$/;
|
|
6148
|
-
function normalizeBaseUrl(baseUrl) {
|
|
6149
|
-
const url = new URL(baseUrl);
|
|
6150
|
-
if (!ROOT_PATH_REGEX.test(url.pathname)) throw new Error("baseUrl must be origin-only (no path prefix)");
|
|
6151
|
-
if (url.search || url.hash) throw new Error("baseUrl must be origin-only (no query or hash)");
|
|
6152
|
-
url.pathname = "";
|
|
6153
|
-
url.search = "";
|
|
6154
|
-
url.hash = "";
|
|
6155
|
-
return url.toString().replace(TRAILING_SLASHES_REGEX, "");
|
|
6156
|
-
}
|
|
6157
|
-
function toSpaceSignalsUrl(baseUrl, space) {
|
|
6158
|
-
const url = new URL(normalizeBaseUrl(baseUrl));
|
|
6159
|
-
url.pathname = `/v1/spaces/${encodeURIComponent(space)}/signals`;
|
|
6160
|
-
return url.toString();
|
|
6161
|
-
}
|
|
6162
|
-
function toSpaceCreateUrl(baseUrl) {
|
|
6163
|
-
const url = new URL(normalizeBaseUrl(baseUrl));
|
|
6164
|
-
url.pathname = "/v1/spaces";
|
|
6165
|
-
return url.toString();
|
|
6166
|
-
}
|
|
6167
|
-
function toSpaceDeleteUrl(baseUrl, space) {
|
|
6168
|
-
const url = new URL(normalizeBaseUrl(baseUrl));
|
|
6169
|
-
url.pathname = `/v1/spaces/${encodeURIComponent(space)}`;
|
|
6170
|
-
return url.toString();
|
|
6171
|
-
}
|
|
6172
|
-
function toSpaceMetaUrl(baseUrl, space) {
|
|
6173
|
-
const url = new URL(normalizeBaseUrl(baseUrl));
|
|
6174
|
-
url.pathname = `/v1/spaces/${encodeURIComponent(space)}/meta`;
|
|
6175
|
-
return url.toString();
|
|
6176
|
-
}
|
|
6177
|
-
function toSpacePasswordUrl(baseUrl, space) {
|
|
6178
|
-
const url = new URL(normalizeBaseUrl(baseUrl));
|
|
6179
|
-
url.pathname = `/v1/spaces/${encodeURIComponent(space)}/password`;
|
|
6180
|
-
return url.toString();
|
|
6181
|
-
}
|
|
6182
|
-
function toSpaceCursorUrl(baseUrl, space, profileId) {
|
|
6183
|
-
const url = new URL(normalizeBaseUrl(baseUrl));
|
|
6184
|
-
url.pathname = `/v1/spaces/${encodeURIComponent(space)}/cursors/${encodeURIComponent(profileId)}`;
|
|
6185
|
-
return url.toString();
|
|
6186
|
-
}
|
|
6187
|
-
function toSpaceWsUrl(baseUrl, space) {
|
|
6188
|
-
const http = new URL(normalizeBaseUrl(baseUrl));
|
|
6189
|
-
let wsScheme;
|
|
6190
|
-
if (http.protocol === "http:") wsScheme = "ws:";
|
|
6191
|
-
else if (http.protocol === "https:") wsScheme = "wss:";
|
|
6192
|
-
else if (http.protocol === "ws:" || http.protocol === "wss:") wsScheme = http.protocol;
|
|
6193
|
-
else throw new Error(`unsupported baseUrl protocol for websocket URL: ${http.protocol}`);
|
|
6194
|
-
return `${wsScheme}//${http.host}/v1/spaces/${encodeURIComponent(space)}/stream`;
|
|
6195
|
-
}
|
|
6196
|
-
|
|
6197
6146
|
//#endregion
|
|
6198
6147
|
//#region src/config/paths.ts
|
|
6199
6148
|
const MAX_ACCOUNT_PATH_SEGMENT_LENGTH = 128;
|
|
@@ -6452,6 +6401,517 @@ function getErrorCode$2(err) {
|
|
|
6452
6401
|
return typeof code === "string" ? code : void 0;
|
|
6453
6402
|
}
|
|
6454
6403
|
|
|
6404
|
+
//#endregion
|
|
6405
|
+
//#region src/config/daemon-state.ts
|
|
6406
|
+
const DAEMON_STATE_SCHEMA = "ats-daemon-install-v1";
|
|
6407
|
+
const DAEMON_SERVICE_RUNTIME_STATE_SCHEMA = "ats-daemon-service-state-v1";
|
|
6408
|
+
const DAEMON_SERVICE_RUNTIME_MODE_ENV = "ATS_DAEMON_RUNTIME_MODE";
|
|
6409
|
+
const DAEMON_SERVICE_RUNTIME_DETACHED_ENV = "ATS_DAEMON_RUNTIME_DETACHED";
|
|
6410
|
+
const DAEMON_SERVICE_RUNTIME_CONTROLLER_ENV = "ATS_DAEMON_SERVICE_CONTROLLER";
|
|
6411
|
+
const DAEMON_SERVICE_RUNTIME_AUTO_START_ENV = "ATS_DAEMON_SERVICE_AUTO_START";
|
|
6412
|
+
const DAEMON_SERVICE_RUNTIME_SYSTEM_SERVICE_ENV = "ATS_DAEMON_RUNTIME_SYSTEM_SERVICE";
|
|
6413
|
+
const DAEMON_TOKEN_SCHEMA = "ats-daemon-token-v1";
|
|
6414
|
+
const DAEMON_TOKEN_ROTATE_MAX_AGE_MS = 1440 * 60 * 1e3;
|
|
6415
|
+
const DAEMON_HEARTBEAT_STALE_MS = 180 * 1e3;
|
|
6416
|
+
const DAEMON_SERVICE_LOCK_PROFILE = "daemon-service";
|
|
6417
|
+
const DAEMON_SERVICE_LOCK_KEY = "daemon-service-run";
|
|
6418
|
+
const DAEMON_SERVICE_RECONCILE_LOCK_KEY = DAEMON_SERVICE_LOCK_KEY;
|
|
6419
|
+
async function getDaemonStatus() {
|
|
6420
|
+
const [state, runtimeExists] = await Promise.all([readDaemonInstallState(), pathExists$1(daemonRuntimeRootPath())]);
|
|
6421
|
+
if (state && runtimeExists) return {
|
|
6422
|
+
installed: true,
|
|
6423
|
+
state
|
|
6424
|
+
};
|
|
6425
|
+
return {
|
|
6426
|
+
installed: false,
|
|
6427
|
+
state: null
|
|
6428
|
+
};
|
|
6429
|
+
}
|
|
6430
|
+
async function getDaemonRuntimeStatus() {
|
|
6431
|
+
const state = await readDaemonServiceRuntimeState();
|
|
6432
|
+
if (!state) return {
|
|
6433
|
+
status: "not_running",
|
|
6434
|
+
mode: null,
|
|
6435
|
+
pid: null,
|
|
6436
|
+
startedAt: null,
|
|
6437
|
+
updatedAt: null,
|
|
6438
|
+
managedBySystemService: false,
|
|
6439
|
+
serviceController: "unknown",
|
|
6440
|
+
autoStart: false
|
|
6441
|
+
};
|
|
6442
|
+
if (!Number.isInteger(state.pid) || state.pid <= 0) return {
|
|
6443
|
+
status: "stale",
|
|
6444
|
+
mode: state.mode,
|
|
6445
|
+
pid: state.pid,
|
|
6446
|
+
startedAt: state.startedAt,
|
|
6447
|
+
updatedAt: state.updatedAt,
|
|
6448
|
+
managedBySystemService: state.managedBySystemService,
|
|
6449
|
+
serviceController: state.serviceController,
|
|
6450
|
+
autoStart: state.autoStart,
|
|
6451
|
+
reason: "invalid_pid"
|
|
6452
|
+
};
|
|
6453
|
+
if (!isPidAlive$2(state.pid)) return {
|
|
6454
|
+
status: "stale",
|
|
6455
|
+
mode: state.mode,
|
|
6456
|
+
pid: state.pid,
|
|
6457
|
+
startedAt: state.startedAt,
|
|
6458
|
+
updatedAt: state.updatedAt,
|
|
6459
|
+
managedBySystemService: state.managedBySystemService,
|
|
6460
|
+
serviceController: state.serviceController,
|
|
6461
|
+
autoStart: state.autoStart,
|
|
6462
|
+
reason: "process_not_alive"
|
|
6463
|
+
};
|
|
6464
|
+
if (isDaemonHeartbeatStale(state)) return {
|
|
6465
|
+
status: "stale",
|
|
6466
|
+
mode: state.mode,
|
|
6467
|
+
pid: state.pid,
|
|
6468
|
+
startedAt: state.startedAt,
|
|
6469
|
+
updatedAt: state.updatedAt,
|
|
6470
|
+
managedBySystemService: state.managedBySystemService,
|
|
6471
|
+
serviceController: state.serviceController,
|
|
6472
|
+
autoStart: state.autoStart,
|
|
6473
|
+
reason: "heartbeat_stale"
|
|
6474
|
+
};
|
|
6475
|
+
return {
|
|
6476
|
+
status: "running",
|
|
6477
|
+
mode: state.mode,
|
|
6478
|
+
pid: state.pid,
|
|
6479
|
+
startedAt: state.startedAt,
|
|
6480
|
+
updatedAt: state.updatedAt,
|
|
6481
|
+
managedBySystemService: state.managedBySystemService,
|
|
6482
|
+
serviceController: state.serviceController,
|
|
6483
|
+
autoStart: state.autoStart
|
|
6484
|
+
};
|
|
6485
|
+
}
|
|
6486
|
+
function resolveDaemonServiceRuntimeMetadata(input = {}) {
|
|
6487
|
+
const env = input.env ?? process.env;
|
|
6488
|
+
const managedBySystemService = input.managedBySystemService === true || env[DAEMON_SERVICE_RUNTIME_SYSTEM_SERVICE_ENV] === "1";
|
|
6489
|
+
const serviceControllerInput = typeof input.serviceController === "string" && input.serviceController.length > 0 ? input.serviceController : env[DAEMON_SERVICE_RUNTIME_CONTROLLER_ENV];
|
|
6490
|
+
const autoStart = input.autoStart === true || env[DAEMON_SERVICE_RUNTIME_AUTO_START_ENV] === "1";
|
|
6491
|
+
return {
|
|
6492
|
+
managedBySystemService,
|
|
6493
|
+
serviceController: parseDaemonServiceController(serviceControllerInput),
|
|
6494
|
+
autoStart
|
|
6495
|
+
};
|
|
6496
|
+
}
|
|
6497
|
+
async function setDaemonServiceRuntimeState(state) {
|
|
6498
|
+
await writeDaemonServiceRuntimeState(state);
|
|
6499
|
+
}
|
|
6500
|
+
async function clearDaemonServiceRuntimeState() {
|
|
6501
|
+
await rm(daemonServiceRuntimeStatePath(), { force: true });
|
|
6502
|
+
}
|
|
6503
|
+
async function cleanupDaemonServiceRuntimeState() {
|
|
6504
|
+
const status = await getDaemonRuntimeStatus();
|
|
6505
|
+
if (status.status === "running") return;
|
|
6506
|
+
if (status.status === "not_running") return;
|
|
6507
|
+
await clearDaemonServiceRuntimeState();
|
|
6508
|
+
}
|
|
6509
|
+
async function withDaemonServiceRunLock(callback) {
|
|
6510
|
+
const lock = await acquireLock({
|
|
6511
|
+
profile: DAEMON_SERVICE_LOCK_PROFILE,
|
|
6512
|
+
key: DAEMON_SERVICE_LOCK_KEY
|
|
6513
|
+
});
|
|
6514
|
+
try {
|
|
6515
|
+
return await callback();
|
|
6516
|
+
} finally {
|
|
6517
|
+
await releaseLock(lock);
|
|
6518
|
+
}
|
|
6519
|
+
}
|
|
6520
|
+
async function withDaemonServiceReconcileLock(callback) {
|
|
6521
|
+
const lock = await acquireLock({
|
|
6522
|
+
profile: DAEMON_SERVICE_LOCK_PROFILE,
|
|
6523
|
+
key: DAEMON_SERVICE_RECONCILE_LOCK_KEY
|
|
6524
|
+
});
|
|
6525
|
+
try {
|
|
6526
|
+
return await callback();
|
|
6527
|
+
} finally {
|
|
6528
|
+
await releaseLock(lock);
|
|
6529
|
+
}
|
|
6530
|
+
}
|
|
6531
|
+
async function installDaemon(input) {
|
|
6532
|
+
const existingState = await readDaemonInstallState();
|
|
6533
|
+
const runtimeExists = await pathExists$1(daemonRuntimeRootPath());
|
|
6534
|
+
if (existingState && runtimeExists && existingState.daemonVersion === input.daemonVersion) {
|
|
6535
|
+
await ensureDaemonTokenState();
|
|
6536
|
+
return {
|
|
6537
|
+
status: "already_installed",
|
|
6538
|
+
state: existingState
|
|
6539
|
+
};
|
|
6540
|
+
}
|
|
6541
|
+
await ensureDaemonRuntimeLayout();
|
|
6542
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6543
|
+
const nextState = {
|
|
6544
|
+
v: 1,
|
|
6545
|
+
schema: DAEMON_STATE_SCHEMA,
|
|
6546
|
+
daemonVersion: input.daemonVersion,
|
|
6547
|
+
installSource: input.source,
|
|
6548
|
+
installedAt: existingState?.installedAt ?? now,
|
|
6549
|
+
updatedAt: now
|
|
6550
|
+
};
|
|
6551
|
+
await writeDaemonInstallState(nextState);
|
|
6552
|
+
await ensureDaemonTokenState();
|
|
6553
|
+
return {
|
|
6554
|
+
status: "installed",
|
|
6555
|
+
state: nextState
|
|
6556
|
+
};
|
|
6557
|
+
}
|
|
6558
|
+
async function uninstallDaemon() {
|
|
6559
|
+
const [hasState, hasRuntimeDir, hasToken] = await Promise.all([
|
|
6560
|
+
pathExists$1(daemonStatePath()),
|
|
6561
|
+
pathExists$1(daemonRuntimeRootPath()),
|
|
6562
|
+
pathExists$1(daemonTokenPath())
|
|
6563
|
+
]);
|
|
6564
|
+
await Promise.all([
|
|
6565
|
+
rm(daemonStatePath(), { force: true }),
|
|
6566
|
+
rm(daemonTokenPath(), { force: true }),
|
|
6567
|
+
rm(daemonServiceRuntimeStatePath(), { force: true }),
|
|
6568
|
+
rm(daemonRuntimeRootPath(), {
|
|
6569
|
+
recursive: true,
|
|
6570
|
+
force: true
|
|
6571
|
+
})
|
|
6572
|
+
]);
|
|
6573
|
+
return { removed: hasState || hasRuntimeDir || hasToken };
|
|
6574
|
+
}
|
|
6575
|
+
async function refreshDaemonTokenLifecycle() {
|
|
6576
|
+
if (!(await getDaemonStatus()).installed) return null;
|
|
6577
|
+
return { tokenUpdatedAt: (await ensureDaemonTokenState()).updatedAt };
|
|
6578
|
+
}
|
|
6579
|
+
function daemonStatePath() {
|
|
6580
|
+
return join(systemDir(), "daemon-install.json");
|
|
6581
|
+
}
|
|
6582
|
+
function daemonRuntimeRootPath() {
|
|
6583
|
+
return join(runtimeDir(), "daemon");
|
|
6584
|
+
}
|
|
6585
|
+
function daemonServiceRuntimeStatePath() {
|
|
6586
|
+
return join(daemonRuntimeRootPath(), "service-runtime.json");
|
|
6587
|
+
}
|
|
6588
|
+
function daemonRuntimeDataPath() {
|
|
6589
|
+
return join(daemonRuntimeRootPath(), "data");
|
|
6590
|
+
}
|
|
6591
|
+
function daemonTokenPath() {
|
|
6592
|
+
return join(systemDir(), "daemon-token.json");
|
|
6593
|
+
}
|
|
6594
|
+
function isPidAlive$2(pid) {
|
|
6595
|
+
try {
|
|
6596
|
+
process.kill(pid, 0);
|
|
6597
|
+
return true;
|
|
6598
|
+
} catch (error) {
|
|
6599
|
+
return !(error instanceof Error && error.code === "ESRCH");
|
|
6600
|
+
}
|
|
6601
|
+
}
|
|
6602
|
+
async function readDaemonServiceRuntimeState() {
|
|
6603
|
+
const raw = await readFile(daemonServiceRuntimeStatePath(), "utf8").catch((error) => {
|
|
6604
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return null;
|
|
6605
|
+
throw error;
|
|
6606
|
+
});
|
|
6607
|
+
if (raw === null) return null;
|
|
6608
|
+
return parseDaemonServiceRuntimeState(raw);
|
|
6609
|
+
}
|
|
6610
|
+
function parseDaemonServiceRuntimeState(raw) {
|
|
6611
|
+
try {
|
|
6612
|
+
const parsed = JSON.parse(raw);
|
|
6613
|
+
if (!isDaemonServiceRuntimeStateObject(parsed)) return null;
|
|
6614
|
+
return {
|
|
6615
|
+
v: 1,
|
|
6616
|
+
schema: DAEMON_SERVICE_RUNTIME_STATE_SCHEMA,
|
|
6617
|
+
status: "running",
|
|
6618
|
+
pid: parsed.pid,
|
|
6619
|
+
mode: parsed.mode,
|
|
6620
|
+
command: parsed.command,
|
|
6621
|
+
gatewayUrl: parseNullableString(parsed.gatewayUrl),
|
|
6622
|
+
profile: parseNullableString(parsed.profile),
|
|
6623
|
+
stopRequestedAt: parseNullableString(parsed.stopRequestedAt),
|
|
6624
|
+
heartbeatAt: parseNullableString(parsed.heartbeatAt),
|
|
6625
|
+
startedAt: parsed.startedAt,
|
|
6626
|
+
updatedAt: parsed.updatedAt,
|
|
6627
|
+
managedBySystemService: parsed.managedBySystemService === true,
|
|
6628
|
+
serviceController: parseDaemonServiceController(parsed.serviceController),
|
|
6629
|
+
autoStart: parsed.autoStart === true
|
|
6630
|
+
};
|
|
6631
|
+
} catch {
|
|
6632
|
+
return null;
|
|
6633
|
+
}
|
|
6634
|
+
}
|
|
6635
|
+
function isDaemonServiceRuntimeStateObject(value) {
|
|
6636
|
+
if (!value || typeof value !== "object") return false;
|
|
6637
|
+
const candidate = value;
|
|
6638
|
+
return candidate.v === 1 && candidate.schema === DAEMON_SERVICE_RUNTIME_STATE_SCHEMA && candidate.status === "running" && typeof candidate.pid === "number" && candidate.pid > 0 && (candidate.mode === "foreground" || candidate.mode === "background") && typeof candidate.command === "string" && candidate.command.length > 0 && isValidNullableString(candidate.gatewayUrl) && isValidNullableString(candidate.profile) && isValidNullableString(candidate.stopRequestedAt) && isValidNullableString(candidate.heartbeatAt) && typeof candidate.startedAt === "string" && candidate.startedAt.length > 0 && typeof candidate.updatedAt === "string" && candidate.updatedAt.length > 0;
|
|
6639
|
+
}
|
|
6640
|
+
function parseDaemonServiceController(value) {
|
|
6641
|
+
if (value === "launchd" || value === "systemd" || value === "windows-service" || value === "legacy-detached" || value === "unknown") return value;
|
|
6642
|
+
return "unknown";
|
|
6643
|
+
}
|
|
6644
|
+
function isValidNullableString(value) {
|
|
6645
|
+
return value === null || typeof value === "string";
|
|
6646
|
+
}
|
|
6647
|
+
function parseNullableString(value) {
|
|
6648
|
+
if (value === null) return null;
|
|
6649
|
+
if (typeof value !== "string") return null;
|
|
6650
|
+
return value;
|
|
6651
|
+
}
|
|
6652
|
+
function isDaemonHeartbeatStale(state) {
|
|
6653
|
+
const latestSignalAt = Math.max(parseIsoTimestampOrZero(state.heartbeatAt), parseIsoTimestampOrZero(state.updatedAt));
|
|
6654
|
+
if (latestSignalAt <= 0) return false;
|
|
6655
|
+
return Date.now() - latestSignalAt > DAEMON_HEARTBEAT_STALE_MS;
|
|
6656
|
+
}
|
|
6657
|
+
function parseIsoTimestampOrZero(value) {
|
|
6658
|
+
if (!value) return 0;
|
|
6659
|
+
const parsed = Date.parse(value);
|
|
6660
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
6661
|
+
}
|
|
6662
|
+
async function writeDaemonServiceRuntimeState(state) {
|
|
6663
|
+
const path = daemonServiceRuntimeStatePath();
|
|
6664
|
+
await mkdir(daemonRuntimeRootPath(), { recursive: true });
|
|
6665
|
+
const tempPath = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
6666
|
+
let shouldCleanupTemp = true;
|
|
6667
|
+
try {
|
|
6668
|
+
await writeFile(tempPath, `${JSON.stringify(state, null, 2)}\n`, {
|
|
6669
|
+
encoding: "utf8",
|
|
6670
|
+
mode: 384
|
|
6671
|
+
});
|
|
6672
|
+
await rename(tempPath, path);
|
|
6673
|
+
await chmod(path, 384);
|
|
6674
|
+
shouldCleanupTemp = false;
|
|
6675
|
+
} finally {
|
|
6676
|
+
if (shouldCleanupTemp) await rm(tempPath, { force: true }).catch(() => void 0);
|
|
6677
|
+
}
|
|
6678
|
+
}
|
|
6679
|
+
async function ensureDaemonRuntimeLayout() {
|
|
6680
|
+
await Promise.all([
|
|
6681
|
+
mkdir(systemDir(), { recursive: true }),
|
|
6682
|
+
mkdir(join(daemonRuntimeRootPath(), "bin"), { recursive: true }),
|
|
6683
|
+
mkdir(join(daemonRuntimeRootPath(), "data"), { recursive: true }),
|
|
6684
|
+
mkdir(join(daemonRuntimeRootPath(), "logs"), { recursive: true })
|
|
6685
|
+
]);
|
|
6686
|
+
}
|
|
6687
|
+
async function ensureDaemonTokenState() {
|
|
6688
|
+
const current = await readDaemonTokenState();
|
|
6689
|
+
if (current && !isDaemonTokenExpired(current)) {
|
|
6690
|
+
await chmod(daemonTokenPath(), 384).catch(() => void 0);
|
|
6691
|
+
return current;
|
|
6692
|
+
}
|
|
6693
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
6694
|
+
const next = {
|
|
6695
|
+
v: 1,
|
|
6696
|
+
schema: DAEMON_TOKEN_SCHEMA,
|
|
6697
|
+
token: createDaemonTokenValue(),
|
|
6698
|
+
issuedAt: nowIso,
|
|
6699
|
+
updatedAt: nowIso
|
|
6700
|
+
};
|
|
6701
|
+
await writeDaemonTokenState(next);
|
|
6702
|
+
return next;
|
|
6703
|
+
}
|
|
6704
|
+
async function readDaemonInstallState() {
|
|
6705
|
+
const raw = await readFile(daemonStatePath(), "utf8").catch((error) => {
|
|
6706
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return null;
|
|
6707
|
+
throw error;
|
|
6708
|
+
});
|
|
6709
|
+
if (raw === null) return null;
|
|
6710
|
+
return parseDaemonInstallState(raw);
|
|
6711
|
+
}
|
|
6712
|
+
function parseDaemonInstallState(raw) {
|
|
6713
|
+
try {
|
|
6714
|
+
const parsed = JSON.parse(raw);
|
|
6715
|
+
if (parsed.v !== 1) return null;
|
|
6716
|
+
if (parsed.schema !== DAEMON_STATE_SCHEMA) return null;
|
|
6717
|
+
if (typeof parsed.daemonVersion !== "string" || !parsed.daemonVersion) return null;
|
|
6718
|
+
if (!isDaemonInstallSource(parsed.installSource)) return null;
|
|
6719
|
+
if (typeof parsed.installedAt !== "string" || !parsed.installedAt) return null;
|
|
6720
|
+
if (typeof parsed.updatedAt !== "string" || !parsed.updatedAt) return null;
|
|
6721
|
+
return {
|
|
6722
|
+
v: 1,
|
|
6723
|
+
schema: DAEMON_STATE_SCHEMA,
|
|
6724
|
+
daemonVersion: parsed.daemonVersion,
|
|
6725
|
+
installSource: parsed.installSource,
|
|
6726
|
+
installedAt: parsed.installedAt,
|
|
6727
|
+
updatedAt: parsed.updatedAt
|
|
6728
|
+
};
|
|
6729
|
+
} catch {
|
|
6730
|
+
return null;
|
|
6731
|
+
}
|
|
6732
|
+
}
|
|
6733
|
+
function isDaemonInstallSource(value) {
|
|
6734
|
+
return value === "cli.auto_check" || value === "daemon.command.install";
|
|
6735
|
+
}
|
|
6736
|
+
async function writeDaemonInstallState(state) {
|
|
6737
|
+
const path = daemonStatePath();
|
|
6738
|
+
await mkdir(systemDir(), { recursive: true });
|
|
6739
|
+
const tempPath = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
6740
|
+
let shouldCleanupTemp = true;
|
|
6741
|
+
try {
|
|
6742
|
+
await writeFile(tempPath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
6743
|
+
await rename(tempPath, path);
|
|
6744
|
+
shouldCleanupTemp = false;
|
|
6745
|
+
} finally {
|
|
6746
|
+
if (shouldCleanupTemp) await rm(tempPath, { force: true }).catch(() => void 0);
|
|
6747
|
+
}
|
|
6748
|
+
}
|
|
6749
|
+
async function readDaemonTokenState() {
|
|
6750
|
+
const raw = await readFile(daemonTokenPath(), "utf8").catch((error) => {
|
|
6751
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return null;
|
|
6752
|
+
throw error;
|
|
6753
|
+
});
|
|
6754
|
+
if (raw === null) return null;
|
|
6755
|
+
return parseDaemonTokenState(raw);
|
|
6756
|
+
}
|
|
6757
|
+
function parseDaemonTokenState(raw) {
|
|
6758
|
+
try {
|
|
6759
|
+
const parsed = JSON.parse(raw);
|
|
6760
|
+
if (parsed.v !== 1 || parsed.schema !== DAEMON_TOKEN_SCHEMA) return null;
|
|
6761
|
+
if (typeof parsed.token !== "string" || parsed.token.length < 16) return null;
|
|
6762
|
+
if (typeof parsed.issuedAt !== "string" || !parsed.issuedAt) return null;
|
|
6763
|
+
if (typeof parsed.updatedAt !== "string" || !parsed.updatedAt) return null;
|
|
6764
|
+
return {
|
|
6765
|
+
v: 1,
|
|
6766
|
+
schema: DAEMON_TOKEN_SCHEMA,
|
|
6767
|
+
token: parsed.token,
|
|
6768
|
+
issuedAt: parsed.issuedAt,
|
|
6769
|
+
updatedAt: parsed.updatedAt
|
|
6770
|
+
};
|
|
6771
|
+
} catch {
|
|
6772
|
+
return null;
|
|
6773
|
+
}
|
|
6774
|
+
}
|
|
6775
|
+
async function writeDaemonTokenState(state) {
|
|
6776
|
+
const path = daemonTokenPath();
|
|
6777
|
+
await mkdir(systemDir(), { recursive: true });
|
|
6778
|
+
const tempPath = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
6779
|
+
let shouldCleanupTemp = true;
|
|
6780
|
+
try {
|
|
6781
|
+
await writeFile(tempPath, `${JSON.stringify(state, null, 2)}\n`, {
|
|
6782
|
+
encoding: "utf8",
|
|
6783
|
+
mode: 384
|
|
6784
|
+
});
|
|
6785
|
+
await rename(tempPath, path);
|
|
6786
|
+
await chmod(path, 384);
|
|
6787
|
+
shouldCleanupTemp = false;
|
|
6788
|
+
} finally {
|
|
6789
|
+
if (shouldCleanupTemp) await rm(tempPath, { force: true }).catch(() => void 0);
|
|
6790
|
+
}
|
|
6791
|
+
}
|
|
6792
|
+
function isDaemonTokenExpired(state) {
|
|
6793
|
+
const updatedAtMs = Date.parse(state.updatedAt);
|
|
6794
|
+
if (!Number.isFinite(updatedAtMs)) return true;
|
|
6795
|
+
return Date.now() - updatedAtMs >= DAEMON_TOKEN_ROTATE_MAX_AGE_MS;
|
|
6796
|
+
}
|
|
6797
|
+
function createDaemonTokenValue() {
|
|
6798
|
+
return randomBytes(32).toString("base64url");
|
|
6799
|
+
}
|
|
6800
|
+
async function pathExists$1(path) {
|
|
6801
|
+
try {
|
|
6802
|
+
await stat(path);
|
|
6803
|
+
return true;
|
|
6804
|
+
} catch (error) {
|
|
6805
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return false;
|
|
6806
|
+
throw error;
|
|
6807
|
+
}
|
|
6808
|
+
}
|
|
6809
|
+
|
|
6810
|
+
//#endregion
|
|
6811
|
+
//#region src/daemon/state/catalog-sync-signal.ts
|
|
6812
|
+
const CATALOG_SYNC_SIGNAL_FILE = "catalog-sync-signal.json";
|
|
6813
|
+
const CATALOG_SYNC_SIGNAL_SCHEMA = "ats-daemon-catalog-sync-signal-v1";
|
|
6814
|
+
async function touchDaemonCatalogSyncSignal(input) {
|
|
6815
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6816
|
+
const signalPath = daemonCatalogSyncSignalPath();
|
|
6817
|
+
await mkdir(daemonRuntimeDataPath(), { recursive: true });
|
|
6818
|
+
const next = {
|
|
6819
|
+
v: 1,
|
|
6820
|
+
schema: CATALOG_SYNC_SIGNAL_SCHEMA,
|
|
6821
|
+
updatedAt: now,
|
|
6822
|
+
reason: normalizeReason(input.reason)
|
|
6823
|
+
};
|
|
6824
|
+
const tempPath = `${signalPath}.tmp-${process.pid}-${Date.now()}`;
|
|
6825
|
+
let shouldCleanupTemp = true;
|
|
6826
|
+
try {
|
|
6827
|
+
await writeFile(tempPath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
6828
|
+
await rename(tempPath, signalPath);
|
|
6829
|
+
shouldCleanupTemp = false;
|
|
6830
|
+
} finally {
|
|
6831
|
+
if (shouldCleanupTemp) await rm(tempPath, { force: true }).catch(() => void 0);
|
|
6832
|
+
}
|
|
6833
|
+
}
|
|
6834
|
+
async function readDaemonCatalogSyncSignal() {
|
|
6835
|
+
const raw = await readFile(daemonCatalogSyncSignalPath(), "utf8").catch(() => null);
|
|
6836
|
+
if (!raw) return null;
|
|
6837
|
+
try {
|
|
6838
|
+
const parsed = JSON.parse(raw);
|
|
6839
|
+
if (parsed.v !== 1) return null;
|
|
6840
|
+
if (parsed.schema !== CATALOG_SYNC_SIGNAL_SCHEMA || typeof parsed.updatedAt !== "string" || parsed.updatedAt.trim().length === 0 || typeof parsed.reason !== "string") return null;
|
|
6841
|
+
const updatedAt = parsed.updatedAt.trim();
|
|
6842
|
+
const updatedAtMs = Date.parse(updatedAt);
|
|
6843
|
+
if (!Number.isFinite(updatedAtMs) || updatedAtMs <= 0) return null;
|
|
6844
|
+
return {
|
|
6845
|
+
updatedAt,
|
|
6846
|
+
updatedAtMs,
|
|
6847
|
+
reason: normalizeReason(parsed.reason)
|
|
6848
|
+
};
|
|
6849
|
+
} catch {
|
|
6850
|
+
return null;
|
|
6851
|
+
}
|
|
6852
|
+
}
|
|
6853
|
+
function daemonCatalogSyncSignalPath() {
|
|
6854
|
+
return join(daemonRuntimeDataPath(), CATALOG_SYNC_SIGNAL_FILE);
|
|
6855
|
+
}
|
|
6856
|
+
function normalizeReason(value) {
|
|
6857
|
+
const normalized = value.trim();
|
|
6858
|
+
if (normalized.length > 0) return normalized;
|
|
6859
|
+
return "local_state_changed";
|
|
6860
|
+
}
|
|
6861
|
+
|
|
6862
|
+
//#endregion
|
|
6863
|
+
//#region src/protocol/urls.ts
|
|
6864
|
+
const TRAILING_SLASHES_REGEX = /\/+$/;
|
|
6865
|
+
const ROOT_PATH_REGEX = /^\/+$/;
|
|
6866
|
+
function normalizeBaseUrl(baseUrl) {
|
|
6867
|
+
const url = new URL(baseUrl);
|
|
6868
|
+
if (!ROOT_PATH_REGEX.test(url.pathname)) throw new Error("baseUrl must be origin-only (no path prefix)");
|
|
6869
|
+
if (url.search || url.hash) throw new Error("baseUrl must be origin-only (no query or hash)");
|
|
6870
|
+
url.pathname = "";
|
|
6871
|
+
url.search = "";
|
|
6872
|
+
url.hash = "";
|
|
6873
|
+
return url.toString().replace(TRAILING_SLASHES_REGEX, "");
|
|
6874
|
+
}
|
|
6875
|
+
function toSpaceSignalsUrl(baseUrl, space) {
|
|
6876
|
+
const url = new URL(normalizeBaseUrl(baseUrl));
|
|
6877
|
+
url.pathname = `/v1/spaces/${encodeURIComponent(space)}/signals`;
|
|
6878
|
+
return url.toString();
|
|
6879
|
+
}
|
|
6880
|
+
function toSpaceCreateUrl(baseUrl) {
|
|
6881
|
+
const url = new URL(normalizeBaseUrl(baseUrl));
|
|
6882
|
+
url.pathname = "/v1/spaces";
|
|
6883
|
+
return url.toString();
|
|
6884
|
+
}
|
|
6885
|
+
function toSpaceDeleteUrl(baseUrl, space) {
|
|
6886
|
+
const url = new URL(normalizeBaseUrl(baseUrl));
|
|
6887
|
+
url.pathname = `/v1/spaces/${encodeURIComponent(space)}`;
|
|
6888
|
+
return url.toString();
|
|
6889
|
+
}
|
|
6890
|
+
function toSpaceMetaUrl(baseUrl, space) {
|
|
6891
|
+
const url = new URL(normalizeBaseUrl(baseUrl));
|
|
6892
|
+
url.pathname = `/v1/spaces/${encodeURIComponent(space)}/meta`;
|
|
6893
|
+
return url.toString();
|
|
6894
|
+
}
|
|
6895
|
+
function toSpacePasswordUrl(baseUrl, space) {
|
|
6896
|
+
const url = new URL(normalizeBaseUrl(baseUrl));
|
|
6897
|
+
url.pathname = `/v1/spaces/${encodeURIComponent(space)}/password`;
|
|
6898
|
+
return url.toString();
|
|
6899
|
+
}
|
|
6900
|
+
function toSpaceCursorUrl(baseUrl, space, profileId) {
|
|
6901
|
+
const url = new URL(normalizeBaseUrl(baseUrl));
|
|
6902
|
+
url.pathname = `/v1/spaces/${encodeURIComponent(space)}/cursors/${encodeURIComponent(profileId)}`;
|
|
6903
|
+
return url.toString();
|
|
6904
|
+
}
|
|
6905
|
+
function toSpaceWsUrl(baseUrl, space) {
|
|
6906
|
+
const http = new URL(normalizeBaseUrl(baseUrl));
|
|
6907
|
+
let wsScheme;
|
|
6908
|
+
if (http.protocol === "http:") wsScheme = "ws:";
|
|
6909
|
+
else if (http.protocol === "https:") wsScheme = "wss:";
|
|
6910
|
+
else if (http.protocol === "ws:" || http.protocol === "wss:") wsScheme = http.protocol;
|
|
6911
|
+
else throw new Error(`unsupported baseUrl protocol for websocket URL: ${http.protocol}`);
|
|
6912
|
+
return `${wsScheme}//${http.host}/v1/spaces/${encodeURIComponent(space)}/stream`;
|
|
6913
|
+
}
|
|
6914
|
+
|
|
6455
6915
|
//#endregion
|
|
6456
6916
|
//#region src/config/runtime-config.ts
|
|
6457
6917
|
const DEFAULT_RUNTIME_AGENT_TRANSPORT_MODE = "local-cli";
|
|
@@ -6561,6 +7021,7 @@ async function updateConfiguredDeviceRuntimeState(updater) {
|
|
|
6561
7021
|
const nextDeviceRuntimeState = updater(previousDeviceRuntimeState);
|
|
6562
7022
|
if (JSON.stringify(previousDeviceRuntimeState) === JSON.stringify(nextDeviceRuntimeState)) return;
|
|
6563
7023
|
await writeRuntimeConfig(buildNextConfig(previous, { deviceRuntimeState: nextDeviceRuntimeState }));
|
|
7024
|
+
await emitDaemonCatalogSyncSignal$1("device_runtime_state_updated");
|
|
6564
7025
|
} finally {
|
|
6565
7026
|
await releaseLock(lock);
|
|
6566
7027
|
}
|
|
@@ -6767,6 +7228,9 @@ async function writeRuntimeConfig(next) {
|
|
|
6767
7228
|
throw error;
|
|
6768
7229
|
}
|
|
6769
7230
|
}
|
|
7231
|
+
async function emitDaemonCatalogSyncSignal$1(reason) {
|
|
7232
|
+
await touchDaemonCatalogSyncSignal({ reason }).catch(() => void 0);
|
|
7233
|
+
}
|
|
6770
7234
|
function delay$1(ms) {
|
|
6771
7235
|
return new Promise((resolve) => {
|
|
6772
7236
|
setTimeout(resolve, ms);
|
|
@@ -8249,7 +8713,7 @@ async function resolveAtsProfile(input) {
|
|
|
8249
8713
|
async function createAtsAgentProfile(input) {
|
|
8250
8714
|
const profileName = normalizeOptionalString$8(input.profileName);
|
|
8251
8715
|
if (!profileName) throw new Error("profile name is required");
|
|
8252
|
-
|
|
8716
|
+
const created = parseAtsProfileItem(readRecordField(await requestAtsProfilesApi({
|
|
8253
8717
|
method: "POST",
|
|
8254
8718
|
path: "/v1/profiles/agents",
|
|
8255
8719
|
body: {
|
|
@@ -8259,15 +8723,19 @@ async function createAtsAgentProfile(input) {
|
|
|
8259
8723
|
...typeof input.controllerEnabled === "boolean" ? { controllerEnabled: input.controllerEnabled } : {}
|
|
8260
8724
|
}
|
|
8261
8725
|
}), "profile"));
|
|
8726
|
+
await emitDaemonCatalogSyncSignal("profile_created");
|
|
8727
|
+
return created;
|
|
8262
8728
|
}
|
|
8263
8729
|
async function createAtsHumanProfile(input) {
|
|
8264
8730
|
const profileName = normalizeOptionalString$8(input.profileName);
|
|
8265
8731
|
if (!profileName) throw new Error("profile name is required");
|
|
8266
|
-
|
|
8732
|
+
const created = parseAtsProfileItem(readRecordField(await requestAtsProfilesApi({
|
|
8267
8733
|
method: "POST",
|
|
8268
8734
|
path: "/v1/profiles/humans",
|
|
8269
8735
|
body: { profileName }
|
|
8270
8736
|
}), "profile"));
|
|
8737
|
+
await emitDaemonCatalogSyncSignal("profile_created");
|
|
8738
|
+
return created;
|
|
8271
8739
|
}
|
|
8272
8740
|
async function updateAtsProfile(input) {
|
|
8273
8741
|
const atsProfileId = normalizeOptionalString$8(input.atsProfileId);
|
|
@@ -8280,7 +8748,7 @@ async function updateAtsProfile(input) {
|
|
|
8280
8748
|
const hasControllerEnabled = typeof input.controllerEnabled === "boolean";
|
|
8281
8749
|
const hasControllerPayload = input.controllerKind === null || hasControllerKind || hasControllerRef || hasControllerEnabled;
|
|
8282
8750
|
if (!(hasProfileName || hasControllerPayload)) throw new Error("at least one field is required: profileName or controller binding");
|
|
8283
|
-
|
|
8751
|
+
const updated = parseAtsProfileItem(readRecordField(await requestAtsProfilesApi({
|
|
8284
8752
|
method: "PATCH",
|
|
8285
8753
|
path: `/v1/profiles/${encodeURIComponent(atsProfileId)}`,
|
|
8286
8754
|
body: {
|
|
@@ -8290,14 +8758,18 @@ async function updateAtsProfile(input) {
|
|
|
8290
8758
|
...hasControllerEnabled ? { controllerEnabled: input.controllerEnabled } : {}
|
|
8291
8759
|
}
|
|
8292
8760
|
}), "profile"));
|
|
8761
|
+
await emitDaemonCatalogSyncSignal("profile_updated");
|
|
8762
|
+
return updated;
|
|
8293
8763
|
}
|
|
8294
8764
|
async function deleteAtsProfile(input) {
|
|
8295
8765
|
const atsProfileId = normalizeOptionalString$8(input.atsProfileId);
|
|
8296
8766
|
if (!atsProfileId) throw new Error("atsProfileId is required");
|
|
8297
|
-
|
|
8767
|
+
const deleted = parseAtsProfileItem(readRecordField(await requestAtsProfilesApi({
|
|
8298
8768
|
method: "DELETE",
|
|
8299
8769
|
path: `/v1/profiles/${encodeURIComponent(atsProfileId)}`
|
|
8300
8770
|
}), "profile"));
|
|
8771
|
+
await emitDaemonCatalogSyncSignal("profile_deleted");
|
|
8772
|
+
return deleted;
|
|
8301
8773
|
}
|
|
8302
8774
|
async function requestAtsProfilesApi(input) {
|
|
8303
8775
|
const session = await requireAtsProfilesSession();
|
|
@@ -8377,6 +8849,9 @@ async function requireAtsProfilesSession() {
|
|
|
8377
8849
|
function isSameStoredAuthSession(a, b) {
|
|
8378
8850
|
return a.authBaseUrl === b.authBaseUrl && a.clientId === b.clientId && a.tokenType === b.tokenType && a.accessToken === b.accessToken && (a.expiresAt ?? null) === (b.expiresAt ?? null);
|
|
8379
8851
|
}
|
|
8852
|
+
async function emitDaemonCatalogSyncSignal(reason) {
|
|
8853
|
+
await touchDaemonCatalogSyncSignal({ reason }).catch(() => void 0);
|
|
8854
|
+
}
|
|
8380
8855
|
function parseAtsProfileItem(value) {
|
|
8381
8856
|
const record = isRecord$2(value) ? value : {};
|
|
8382
8857
|
const atsProfileId = readOptionalStringField$1(record, "atsProfileId");
|
|
@@ -8700,8 +9175,8 @@ const HUMAN_MESSAGES = {
|
|
|
8700
9175
|
"connect.closing": (params) => `closing (${String(params?.why ?? "")})...`,
|
|
8701
9176
|
"connect.watch_ready": () => "watching stream. Press Ctrl+C to quit.",
|
|
8702
9177
|
"connect.connected_no_stdio": () => "connected (no --stdio). Press Ctrl+C to quit.",
|
|
8703
|
-
"connect.chat_ready": () => "
|
|
8704
|
-
"connect.chat_exit_hint": () => "
|
|
9178
|
+
"connect.chat_ready": () => "Type your message and press Enter to send.",
|
|
9179
|
+
"connect.chat_exit_hint": () => "Exit: type '/quit' or ':q', Ctrl+D, Ctrl+C.",
|
|
8705
9180
|
"connect.connected_stdio": () => "connected (--stdio). Type a line and press Enter. Ctrl+D / Ctrl+C to quit. You can also type \"/quit\" or \":q\".",
|
|
8706
9181
|
"connect.reject": (params) => `reject: ${String(params?.reason ?? "unknown")}`,
|
|
8707
9182
|
"connect.space_guidelines": (params) => `space guidelines (${String(params?.spaceId ?? "unknown")}):`
|
|
@@ -11810,590 +12285,184 @@ function resolveClientId(value) {
|
|
|
11810
12285
|
if (!DEVICE_CLIENT_ID_PATTERN.test(clientId)) throw new Error(`invalid client_id "${clientId}" (allowed: letters, numbers, . _ : -)`);
|
|
11811
12286
|
return clientId;
|
|
11812
12287
|
}
|
|
11813
|
-
function normalizeRequiredString(value, message) {
|
|
11814
|
-
const normalized = normalizeOptionalString$3(value);
|
|
11815
|
-
if (!normalized) throw new Error(message);
|
|
11816
|
-
return normalized;
|
|
11817
|
-
}
|
|
11818
|
-
function normalizeOptionalString$3(value) {
|
|
11819
|
-
const normalized = String(value ?? "").trim();
|
|
11820
|
-
return normalized.length > 0 ? normalized : null;
|
|
11821
|
-
}
|
|
11822
|
-
function toErrorMessage$16(error) {
|
|
11823
|
-
if (error instanceof Error) return error.message;
|
|
11824
|
-
return String(error);
|
|
11825
|
-
}
|
|
11826
|
-
function readStringField(payload, key) {
|
|
11827
|
-
if (!(payload && typeof payload === "object" && key in payload)) return null;
|
|
11828
|
-
const value = payload[key];
|
|
11829
|
-
return typeof value === "string" ? value : null;
|
|
11830
|
-
}
|
|
11831
|
-
function readNumberField(payload, key) {
|
|
11832
|
-
if (!(payload && typeof payload === "object" && key in payload)) return;
|
|
11833
|
-
const value = payload[key];
|
|
11834
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
11835
|
-
}
|
|
11836
|
-
async function parseResponseJson(response) {
|
|
11837
|
-
const raw = await response.text();
|
|
11838
|
-
if (!raw) return null;
|
|
11839
|
-
try {
|
|
11840
|
-
return JSON.parse(raw);
|
|
11841
|
-
} catch {
|
|
11842
|
-
return { message: raw };
|
|
11843
|
-
}
|
|
11844
|
-
}
|
|
11845
|
-
function extractApiErrorMessage(payload, status) {
|
|
11846
|
-
const errorDescription = normalizeOptionalString$3(readStringField(payload, "error_description"));
|
|
11847
|
-
if (errorDescription) return errorDescription;
|
|
11848
|
-
const detail = normalizeOptionalString$3(readStringField(payload, "detail"));
|
|
11849
|
-
if (detail) return detail;
|
|
11850
|
-
const message = normalizeOptionalString$3(readStringField(payload, "message"));
|
|
11851
|
-
if (message) return message;
|
|
11852
|
-
const errorCode = normalizeOptionalString$3(readStringField(payload, "error"));
|
|
11853
|
-
if (errorCode) return errorCode;
|
|
11854
|
-
return `HTTP ${status}`;
|
|
11855
|
-
}
|
|
11856
|
-
function createPollingProgressEmitter(input) {
|
|
11857
|
-
let lastPendingEmitAt = 0;
|
|
11858
|
-
const isHumanView = input.resolvedView === "human";
|
|
11859
|
-
return (progress) => {
|
|
11860
|
-
const now = Date.now();
|
|
11861
|
-
if (progress.event === "authorization_pending" && now - lastPendingEmitAt < POLL_PROGRESS_EMIT_INTERVAL_MS) return;
|
|
11862
|
-
if (progress.event === "authorization_pending") {
|
|
11863
|
-
lastPendingEmitAt = now;
|
|
11864
|
-
const remaining = formatCountdown(progress.remainingSeconds);
|
|
11865
|
-
emitLineWithHumanSpacer({
|
|
11866
|
-
presenter: input.presenter,
|
|
11867
|
-
isHumanView,
|
|
11868
|
-
code: "auth.login.polling.pending",
|
|
11869
|
-
text: isHumanView ? `${pc.yellow("⏳")} ${pc.bold(remaining)} left · poll ${progress.intervalSeconds}s · try ${progress.attempt}` : `waiting for approval (attempt ${progress.attempt}, poll in ${progress.intervalSeconds}s, remaining ${progress.remainingSeconds}s)`,
|
|
11870
|
-
data: progress
|
|
11871
|
-
});
|
|
11872
|
-
return;
|
|
11873
|
-
}
|
|
11874
|
-
const remaining = formatCountdown(progress.remainingSeconds);
|
|
11875
|
-
emitLineWithHumanSpacer({
|
|
11876
|
-
presenter: input.presenter,
|
|
11877
|
-
isHumanView,
|
|
11878
|
-
code: "auth.login.polling.slow_down",
|
|
11879
|
-
text: isHumanView ? `${pc.yellow("⚠️ Poll Slowed")} ${pc.bold(remaining)} left · poll ${progress.intervalSeconds}s` : `server requested slower polling; now every ${progress.intervalSeconds}s (remaining ${progress.remainingSeconds}s)`,
|
|
11880
|
-
data: progress
|
|
11881
|
-
});
|
|
11882
|
-
};
|
|
11883
|
-
}
|
|
11884
|
-
function emitLineWithHumanSpacer(input) {
|
|
11885
|
-
input.presenter.line({
|
|
11886
|
-
code: input.code,
|
|
11887
|
-
text: input.text,
|
|
11888
|
-
...input.data ? { data: input.data } : {}
|
|
11889
|
-
});
|
|
11890
|
-
if (!input.isHumanView) return;
|
|
11891
|
-
input.presenter.line({
|
|
11892
|
-
code: `${input.code}.spacer`,
|
|
11893
|
-
text: ""
|
|
11894
|
-
});
|
|
11895
|
-
}
|
|
11896
|
-
async function fetchAuthUserSnapshot(input) {
|
|
11897
|
-
const endpoint = toAuthEndpoint(input.authBaseUrl, "/api/auth/get-session");
|
|
11898
|
-
try {
|
|
11899
|
-
const response = await fetch(endpoint, {
|
|
11900
|
-
method: "GET",
|
|
11901
|
-
headers: { authorization: `Bearer ${input.accessToken}` },
|
|
11902
|
-
signal: AbortSignal.timeout(1e4)
|
|
11903
|
-
});
|
|
11904
|
-
if (!response.ok) return null;
|
|
11905
|
-
return readAuthUserFromSessionPayload(await parseResponseJson(response));
|
|
11906
|
-
} catch {
|
|
11907
|
-
return null;
|
|
11908
|
-
}
|
|
11909
|
-
}
|
|
11910
|
-
function readAuthUserFromSessionPayload(payload) {
|
|
11911
|
-
if (!(payload && typeof payload === "object" && "user" in payload)) return null;
|
|
11912
|
-
const user = payload.user;
|
|
11913
|
-
if (!(user && typeof user === "object")) return null;
|
|
11914
|
-
const userRecord = user;
|
|
11915
|
-
const id = readOptionalStringField(userRecord, "id");
|
|
11916
|
-
const email = readOptionalStringField(userRecord, "email");
|
|
11917
|
-
const name = readOptionalStringField(userRecord, "name");
|
|
11918
|
-
if (!(id || email || name)) return null;
|
|
11919
|
-
return {
|
|
11920
|
-
...id ? { id } : {},
|
|
11921
|
-
...email ? { email } : {},
|
|
11922
|
-
...name ? { name } : {}
|
|
11923
|
-
};
|
|
11924
|
-
}
|
|
11925
|
-
function resolveHumanLoginSuccessText(user) {
|
|
11926
|
-
const email = normalizeOptionalString$3(user?.email);
|
|
11927
|
-
if (email) return `✅ Welcome ${email}`;
|
|
11928
|
-
return "✅ Signed in.";
|
|
11929
|
-
}
|
|
11930
|
-
function readOptionalStringField(input, key) {
|
|
11931
|
-
const value = input[key];
|
|
11932
|
-
if (typeof value !== "string") return;
|
|
11933
|
-
return normalizeOptionalString$3(value) ?? void 0;
|
|
11934
|
-
}
|
|
11935
|
-
function toDeviceAuthError(error) {
|
|
11936
|
-
if (error instanceof DeviceAuthError) return error;
|
|
11937
|
-
if (error instanceof Error) return new DeviceAuthError("unknown", error.message);
|
|
11938
|
-
return new DeviceAuthError("unknown", String(error));
|
|
11939
|
-
}
|
|
11940
|
-
function toOneTimeTokenAuthError(error) {
|
|
11941
|
-
if (error instanceof OneTimeTokenAuthError) return error;
|
|
11942
|
-
if (error instanceof Error) return new OneTimeTokenAuthError("unknown", error.message);
|
|
11943
|
-
return new OneTimeTokenAuthError("unknown", String(error));
|
|
11944
|
-
}
|
|
11945
|
-
function buildAgentFailureNextSteps(code) {
|
|
11946
|
-
if (code === "access_denied") return [
|
|
11947
|
-
"Ask the human to restart `ats login` and approve the device request.",
|
|
11948
|
-
"Confirm the verification URL and user_code were opened and submitted.",
|
|
11949
|
-
"If denial was intentional, stop retrying."
|
|
11950
|
-
];
|
|
11951
|
-
if (code === "expired_token") return ["Run `ats login` again to get a fresh device_code.", "Complete approval promptly before token expiry."];
|
|
11952
|
-
if (code === "invalid_grant") return [
|
|
11953
|
-
"Restart `ats login` to refresh device_code and user_code.",
|
|
11954
|
-
"Verify client_id is allowed (ats-cli or ats-cli-dev).",
|
|
11955
|
-
"Verify auth URL points to the expected auth worker."
|
|
11956
|
-
];
|
|
11957
|
-
return [
|
|
11958
|
-
"Retry `ats login` once.",
|
|
11959
|
-
"If still failing, check auth worker logs and response payload.",
|
|
11960
|
-
"Verify auth URL and client_id configuration."
|
|
11961
|
-
];
|
|
11962
|
-
}
|
|
11963
|
-
function buildAgentOttFailureNextSteps(input) {
|
|
11964
|
-
if (input.code === "missing_access_token") return ["Auth worker returned no set-auth-token header; verify bearer plugin is enabled.", "Retry after confirming auth worker deployment includes one-time-token + bearer plugins."];
|
|
11965
|
-
if (input.code === "expired_token") return [`Open ${input.authBaseUrl}/ and generate a fresh one-time token.`, "Run `ats login --view agent --ott <new-token>` immediately."];
|
|
11966
|
-
if (input.code === "invalid_token") return [`Open ${input.authBaseUrl}/ and generate a new one-time token.`, "Use the latest token exactly once: `ats login --view agent --ott <token>`."];
|
|
11967
|
-
return [
|
|
11968
|
-
"Retry once with a newly generated one-time token.",
|
|
11969
|
-
`If it still fails, verify auth URL points to the expected worker: ${input.authBaseUrl}`,
|
|
11970
|
-
"Check auth worker logs for /api/auth/one-time-token/verify."
|
|
11971
|
-
];
|
|
11972
|
-
}
|
|
11973
|
-
function resolveLocalAuthAdaptationHint(error, context) {
|
|
11974
|
-
if (!(error instanceof AuthHttpError)) return null;
|
|
11975
|
-
if (error.status !== 404) return null;
|
|
11976
|
-
if (!error.endpoint.endsWith("/api/auth/device/code")) return null;
|
|
11977
|
-
if (!(context.authBaseUrl.replace(TRAILING_SLASH_PATTERN, "") === "https://auth.ats.sh")) return null;
|
|
11978
|
-
return [
|
|
11979
|
-
"auth worker does not expose device endpoint on current auth URL.",
|
|
11980
|
-
"for local dev: run `ats login --auth-url http://localhost:8788 --client-id ats-cli-dev`.",
|
|
11981
|
-
"or set `ATS_AUTH_URL=http://localhost:8788` before running `ats login`."
|
|
11982
|
-
].join(" ");
|
|
11983
|
-
}
|
|
11984
|
-
function formatCountdown(inputSeconds) {
|
|
11985
|
-
const totalSeconds = Math.max(0, Math.floor(inputSeconds));
|
|
11986
|
-
const hours = Math.floor(totalSeconds / 3600);
|
|
11987
|
-
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
11988
|
-
const seconds = totalSeconds % 60;
|
|
11989
|
-
if (hours > 0) return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
11990
|
-
return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
11991
|
-
}
|
|
11992
|
-
|
|
11993
|
-
//#endregion
|
|
11994
|
-
//#region src/config/daemon-state.ts
|
|
11995
|
-
const DAEMON_STATE_SCHEMA = "ats-daemon-install-v1";
|
|
11996
|
-
const DAEMON_SERVICE_RUNTIME_STATE_SCHEMA = "ats-daemon-service-state-v1";
|
|
11997
|
-
const DAEMON_SERVICE_RUNTIME_MODE_ENV = "ATS_DAEMON_RUNTIME_MODE";
|
|
11998
|
-
const DAEMON_SERVICE_RUNTIME_DETACHED_ENV = "ATS_DAEMON_RUNTIME_DETACHED";
|
|
11999
|
-
const DAEMON_SERVICE_RUNTIME_CONTROLLER_ENV = "ATS_DAEMON_SERVICE_CONTROLLER";
|
|
12000
|
-
const DAEMON_SERVICE_RUNTIME_AUTO_START_ENV = "ATS_DAEMON_SERVICE_AUTO_START";
|
|
12001
|
-
const DAEMON_SERVICE_RUNTIME_SYSTEM_SERVICE_ENV = "ATS_DAEMON_RUNTIME_SYSTEM_SERVICE";
|
|
12002
|
-
const DAEMON_TOKEN_SCHEMA = "ats-daemon-token-v1";
|
|
12003
|
-
const DAEMON_TOKEN_ROTATE_MAX_AGE_MS = 1440 * 60 * 1e3;
|
|
12004
|
-
const DAEMON_HEARTBEAT_STALE_MS = 180 * 1e3;
|
|
12005
|
-
const DAEMON_SERVICE_LOCK_PROFILE = "daemon-service";
|
|
12006
|
-
const DAEMON_SERVICE_LOCK_KEY = "daemon-service-run";
|
|
12007
|
-
const DAEMON_SERVICE_RECONCILE_LOCK_KEY = DAEMON_SERVICE_LOCK_KEY;
|
|
12008
|
-
async function getDaemonStatus() {
|
|
12009
|
-
const [state, runtimeExists] = await Promise.all([readDaemonInstallState(), pathExists$1(daemonRuntimeRootPath())]);
|
|
12010
|
-
if (state && runtimeExists) return {
|
|
12011
|
-
installed: true,
|
|
12012
|
-
state
|
|
12013
|
-
};
|
|
12014
|
-
return {
|
|
12015
|
-
installed: false,
|
|
12016
|
-
state: null
|
|
12017
|
-
};
|
|
12018
|
-
}
|
|
12019
|
-
async function getDaemonRuntimeStatus() {
|
|
12020
|
-
const state = await readDaemonServiceRuntimeState();
|
|
12021
|
-
if (!state) return {
|
|
12022
|
-
status: "not_running",
|
|
12023
|
-
mode: null,
|
|
12024
|
-
pid: null,
|
|
12025
|
-
startedAt: null,
|
|
12026
|
-
updatedAt: null,
|
|
12027
|
-
managedBySystemService: false,
|
|
12028
|
-
serviceController: "unknown",
|
|
12029
|
-
autoStart: false
|
|
12030
|
-
};
|
|
12031
|
-
if (!Number.isInteger(state.pid) || state.pid <= 0) return {
|
|
12032
|
-
status: "stale",
|
|
12033
|
-
mode: state.mode,
|
|
12034
|
-
pid: state.pid,
|
|
12035
|
-
startedAt: state.startedAt,
|
|
12036
|
-
updatedAt: state.updatedAt,
|
|
12037
|
-
managedBySystemService: state.managedBySystemService,
|
|
12038
|
-
serviceController: state.serviceController,
|
|
12039
|
-
autoStart: state.autoStart,
|
|
12040
|
-
reason: "invalid_pid"
|
|
12041
|
-
};
|
|
12042
|
-
if (!isPidAlive$2(state.pid)) return {
|
|
12043
|
-
status: "stale",
|
|
12044
|
-
mode: state.mode,
|
|
12045
|
-
pid: state.pid,
|
|
12046
|
-
startedAt: state.startedAt,
|
|
12047
|
-
updatedAt: state.updatedAt,
|
|
12048
|
-
managedBySystemService: state.managedBySystemService,
|
|
12049
|
-
serviceController: state.serviceController,
|
|
12050
|
-
autoStart: state.autoStart,
|
|
12051
|
-
reason: "process_not_alive"
|
|
12052
|
-
};
|
|
12053
|
-
if (isDaemonHeartbeatStale(state)) return {
|
|
12054
|
-
status: "stale",
|
|
12055
|
-
mode: state.mode,
|
|
12056
|
-
pid: state.pid,
|
|
12057
|
-
startedAt: state.startedAt,
|
|
12058
|
-
updatedAt: state.updatedAt,
|
|
12059
|
-
managedBySystemService: state.managedBySystemService,
|
|
12060
|
-
serviceController: state.serviceController,
|
|
12061
|
-
autoStart: state.autoStart,
|
|
12062
|
-
reason: "heartbeat_stale"
|
|
12063
|
-
};
|
|
12064
|
-
return {
|
|
12065
|
-
status: "running",
|
|
12066
|
-
mode: state.mode,
|
|
12067
|
-
pid: state.pid,
|
|
12068
|
-
startedAt: state.startedAt,
|
|
12069
|
-
updatedAt: state.updatedAt,
|
|
12070
|
-
managedBySystemService: state.managedBySystemService,
|
|
12071
|
-
serviceController: state.serviceController,
|
|
12072
|
-
autoStart: state.autoStart
|
|
12073
|
-
};
|
|
12074
|
-
}
|
|
12075
|
-
function resolveDaemonServiceRuntimeMetadata(input = {}) {
|
|
12076
|
-
const env = input.env ?? process.env;
|
|
12077
|
-
const managedBySystemService = input.managedBySystemService === true || env[DAEMON_SERVICE_RUNTIME_SYSTEM_SERVICE_ENV] === "1";
|
|
12078
|
-
const serviceControllerInput = typeof input.serviceController === "string" && input.serviceController.length > 0 ? input.serviceController : env[DAEMON_SERVICE_RUNTIME_CONTROLLER_ENV];
|
|
12079
|
-
const autoStart = input.autoStart === true || env[DAEMON_SERVICE_RUNTIME_AUTO_START_ENV] === "1";
|
|
12080
|
-
return {
|
|
12081
|
-
managedBySystemService,
|
|
12082
|
-
serviceController: parseDaemonServiceController(serviceControllerInput),
|
|
12083
|
-
autoStart
|
|
12084
|
-
};
|
|
12085
|
-
}
|
|
12086
|
-
async function setDaemonServiceRuntimeState(state) {
|
|
12087
|
-
await writeDaemonServiceRuntimeState(state);
|
|
12088
|
-
}
|
|
12089
|
-
async function clearDaemonServiceRuntimeState() {
|
|
12090
|
-
await rm(daemonServiceRuntimeStatePath(), { force: true });
|
|
12091
|
-
}
|
|
12092
|
-
async function cleanupDaemonServiceRuntimeState() {
|
|
12093
|
-
const status = await getDaemonRuntimeStatus();
|
|
12094
|
-
if (status.status === "running") return;
|
|
12095
|
-
if (status.status === "not_running") return;
|
|
12096
|
-
await clearDaemonServiceRuntimeState();
|
|
12097
|
-
}
|
|
12098
|
-
async function withDaemonServiceRunLock(callback) {
|
|
12099
|
-
const lock = await acquireLock({
|
|
12100
|
-
profile: DAEMON_SERVICE_LOCK_PROFILE,
|
|
12101
|
-
key: DAEMON_SERVICE_LOCK_KEY
|
|
12102
|
-
});
|
|
12103
|
-
try {
|
|
12104
|
-
return await callback();
|
|
12105
|
-
} finally {
|
|
12106
|
-
await releaseLock(lock);
|
|
12107
|
-
}
|
|
12108
|
-
}
|
|
12109
|
-
async function withDaemonServiceReconcileLock(callback) {
|
|
12110
|
-
const lock = await acquireLock({
|
|
12111
|
-
profile: DAEMON_SERVICE_LOCK_PROFILE,
|
|
12112
|
-
key: DAEMON_SERVICE_RECONCILE_LOCK_KEY
|
|
12113
|
-
});
|
|
12114
|
-
try {
|
|
12115
|
-
return await callback();
|
|
12116
|
-
} finally {
|
|
12117
|
-
await releaseLock(lock);
|
|
12118
|
-
}
|
|
12119
|
-
}
|
|
12120
|
-
async function installDaemon(input) {
|
|
12121
|
-
const existingState = await readDaemonInstallState();
|
|
12122
|
-
const runtimeExists = await pathExists$1(daemonRuntimeRootPath());
|
|
12123
|
-
if (existingState && runtimeExists && existingState.daemonVersion === input.daemonVersion) {
|
|
12124
|
-
await ensureDaemonTokenState();
|
|
12125
|
-
return {
|
|
12126
|
-
status: "already_installed",
|
|
12127
|
-
state: existingState
|
|
12128
|
-
};
|
|
12129
|
-
}
|
|
12130
|
-
await ensureDaemonRuntimeLayout();
|
|
12131
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12132
|
-
const nextState = {
|
|
12133
|
-
v: 1,
|
|
12134
|
-
schema: DAEMON_STATE_SCHEMA,
|
|
12135
|
-
daemonVersion: input.daemonVersion,
|
|
12136
|
-
installSource: input.source,
|
|
12137
|
-
installedAt: existingState?.installedAt ?? now,
|
|
12138
|
-
updatedAt: now
|
|
12139
|
-
};
|
|
12140
|
-
await writeDaemonInstallState(nextState);
|
|
12141
|
-
await ensureDaemonTokenState();
|
|
12142
|
-
return {
|
|
12143
|
-
status: "installed",
|
|
12144
|
-
state: nextState
|
|
12145
|
-
};
|
|
12146
|
-
}
|
|
12147
|
-
async function uninstallDaemon() {
|
|
12148
|
-
const [hasState, hasRuntimeDir, hasToken] = await Promise.all([
|
|
12149
|
-
pathExists$1(daemonStatePath()),
|
|
12150
|
-
pathExists$1(daemonRuntimeRootPath()),
|
|
12151
|
-
pathExists$1(daemonTokenPath())
|
|
12152
|
-
]);
|
|
12153
|
-
await Promise.all([
|
|
12154
|
-
rm(daemonStatePath(), { force: true }),
|
|
12155
|
-
rm(daemonTokenPath(), { force: true }),
|
|
12156
|
-
rm(daemonServiceRuntimeStatePath(), { force: true }),
|
|
12157
|
-
rm(daemonRuntimeRootPath(), {
|
|
12158
|
-
recursive: true,
|
|
12159
|
-
force: true
|
|
12160
|
-
})
|
|
12161
|
-
]);
|
|
12162
|
-
return { removed: hasState || hasRuntimeDir || hasToken };
|
|
12163
|
-
}
|
|
12164
|
-
async function refreshDaemonTokenLifecycle() {
|
|
12165
|
-
if (!(await getDaemonStatus()).installed) return null;
|
|
12166
|
-
return { tokenUpdatedAt: (await ensureDaemonTokenState()).updatedAt };
|
|
12167
|
-
}
|
|
12168
|
-
function daemonStatePath() {
|
|
12169
|
-
return join(systemDir(), "daemon-install.json");
|
|
12170
|
-
}
|
|
12171
|
-
function daemonRuntimeRootPath() {
|
|
12172
|
-
return join(runtimeDir(), "daemon");
|
|
12173
|
-
}
|
|
12174
|
-
function daemonServiceRuntimeStatePath() {
|
|
12175
|
-
return join(daemonRuntimeRootPath(), "service-runtime.json");
|
|
12176
|
-
}
|
|
12177
|
-
function daemonRuntimeDataPath() {
|
|
12178
|
-
return join(daemonRuntimeRootPath(), "data");
|
|
12179
|
-
}
|
|
12180
|
-
function daemonTokenPath() {
|
|
12181
|
-
return join(systemDir(), "daemon-token.json");
|
|
12182
|
-
}
|
|
12183
|
-
function isPidAlive$2(pid) {
|
|
12184
|
-
try {
|
|
12185
|
-
process.kill(pid, 0);
|
|
12186
|
-
return true;
|
|
12187
|
-
} catch (error) {
|
|
12188
|
-
return !(error instanceof Error && error.code === "ESRCH");
|
|
12189
|
-
}
|
|
12190
|
-
}
|
|
12191
|
-
async function readDaemonServiceRuntimeState() {
|
|
12192
|
-
const raw = await readFile(daemonServiceRuntimeStatePath(), "utf8").catch((error) => {
|
|
12193
|
-
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return null;
|
|
12194
|
-
throw error;
|
|
12195
|
-
});
|
|
12196
|
-
if (raw === null) return null;
|
|
12197
|
-
return parseDaemonServiceRuntimeState(raw);
|
|
12198
|
-
}
|
|
12199
|
-
function parseDaemonServiceRuntimeState(raw) {
|
|
12200
|
-
try {
|
|
12201
|
-
const parsed = JSON.parse(raw);
|
|
12202
|
-
if (!isDaemonServiceRuntimeStateObject(parsed)) return null;
|
|
12203
|
-
return {
|
|
12204
|
-
v: 1,
|
|
12205
|
-
schema: DAEMON_SERVICE_RUNTIME_STATE_SCHEMA,
|
|
12206
|
-
status: "running",
|
|
12207
|
-
pid: parsed.pid,
|
|
12208
|
-
mode: parsed.mode,
|
|
12209
|
-
command: parsed.command,
|
|
12210
|
-
gatewayUrl: parseNullableString(parsed.gatewayUrl),
|
|
12211
|
-
profile: parseNullableString(parsed.profile),
|
|
12212
|
-
stopRequestedAt: parseNullableString(parsed.stopRequestedAt),
|
|
12213
|
-
heartbeatAt: parseNullableString(parsed.heartbeatAt),
|
|
12214
|
-
startedAt: parsed.startedAt,
|
|
12215
|
-
updatedAt: parsed.updatedAt,
|
|
12216
|
-
managedBySystemService: parsed.managedBySystemService === true,
|
|
12217
|
-
serviceController: parseDaemonServiceController(parsed.serviceController),
|
|
12218
|
-
autoStart: parsed.autoStart === true
|
|
12219
|
-
};
|
|
12220
|
-
} catch {
|
|
12221
|
-
return null;
|
|
12222
|
-
}
|
|
12223
|
-
}
|
|
12224
|
-
function isDaemonServiceRuntimeStateObject(value) {
|
|
12225
|
-
if (!value || typeof value !== "object") return false;
|
|
12226
|
-
const candidate = value;
|
|
12227
|
-
return candidate.v === 1 && candidate.schema === DAEMON_SERVICE_RUNTIME_STATE_SCHEMA && candidate.status === "running" && typeof candidate.pid === "number" && candidate.pid > 0 && (candidate.mode === "foreground" || candidate.mode === "background") && typeof candidate.command === "string" && candidate.command.length > 0 && isValidNullableString(candidate.gatewayUrl) && isValidNullableString(candidate.profile) && isValidNullableString(candidate.stopRequestedAt) && isValidNullableString(candidate.heartbeatAt) && typeof candidate.startedAt === "string" && candidate.startedAt.length > 0 && typeof candidate.updatedAt === "string" && candidate.updatedAt.length > 0;
|
|
12228
|
-
}
|
|
12229
|
-
function parseDaemonServiceController(value) {
|
|
12230
|
-
if (value === "launchd" || value === "systemd" || value === "windows-service" || value === "legacy-detached" || value === "unknown") return value;
|
|
12231
|
-
return "unknown";
|
|
12232
|
-
}
|
|
12233
|
-
function isValidNullableString(value) {
|
|
12234
|
-
return value === null || typeof value === "string";
|
|
12288
|
+
function normalizeRequiredString(value, message) {
|
|
12289
|
+
const normalized = normalizeOptionalString$3(value);
|
|
12290
|
+
if (!normalized) throw new Error(message);
|
|
12291
|
+
return normalized;
|
|
12235
12292
|
}
|
|
12236
|
-
function
|
|
12237
|
-
|
|
12238
|
-
|
|
12239
|
-
return value;
|
|
12293
|
+
function normalizeOptionalString$3(value) {
|
|
12294
|
+
const normalized = String(value ?? "").trim();
|
|
12295
|
+
return normalized.length > 0 ? normalized : null;
|
|
12240
12296
|
}
|
|
12241
|
-
function
|
|
12242
|
-
|
|
12243
|
-
|
|
12244
|
-
return Date.now() - latestSignalAt > DAEMON_HEARTBEAT_STALE_MS;
|
|
12297
|
+
function toErrorMessage$16(error) {
|
|
12298
|
+
if (error instanceof Error) return error.message;
|
|
12299
|
+
return String(error);
|
|
12245
12300
|
}
|
|
12246
|
-
function
|
|
12247
|
-
if (!
|
|
12248
|
-
const
|
|
12249
|
-
return
|
|
12301
|
+
function readStringField(payload, key) {
|
|
12302
|
+
if (!(payload && typeof payload === "object" && key in payload)) return null;
|
|
12303
|
+
const value = payload[key];
|
|
12304
|
+
return typeof value === "string" ? value : null;
|
|
12250
12305
|
}
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
|
|
12254
|
-
|
|
12255
|
-
|
|
12306
|
+
function readNumberField(payload, key) {
|
|
12307
|
+
if (!(payload && typeof payload === "object" && key in payload)) return;
|
|
12308
|
+
const value = payload[key];
|
|
12309
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
12310
|
+
}
|
|
12311
|
+
async function parseResponseJson(response) {
|
|
12312
|
+
const raw = await response.text();
|
|
12313
|
+
if (!raw) return null;
|
|
12256
12314
|
try {
|
|
12257
|
-
|
|
12258
|
-
|
|
12259
|
-
|
|
12260
|
-
});
|
|
12261
|
-
await rename(tempPath, path);
|
|
12262
|
-
await chmod(path, 384);
|
|
12263
|
-
shouldCleanupTemp = false;
|
|
12264
|
-
} finally {
|
|
12265
|
-
if (shouldCleanupTemp) await rm(tempPath, { force: true }).catch(() => void 0);
|
|
12315
|
+
return JSON.parse(raw);
|
|
12316
|
+
} catch {
|
|
12317
|
+
return { message: raw };
|
|
12266
12318
|
}
|
|
12267
12319
|
}
|
|
12268
|
-
|
|
12269
|
-
|
|
12270
|
-
|
|
12271
|
-
|
|
12272
|
-
|
|
12273
|
-
|
|
12274
|
-
|
|
12320
|
+
function extractApiErrorMessage(payload, status) {
|
|
12321
|
+
const errorDescription = normalizeOptionalString$3(readStringField(payload, "error_description"));
|
|
12322
|
+
if (errorDescription) return errorDescription;
|
|
12323
|
+
const detail = normalizeOptionalString$3(readStringField(payload, "detail"));
|
|
12324
|
+
if (detail) return detail;
|
|
12325
|
+
const message = normalizeOptionalString$3(readStringField(payload, "message"));
|
|
12326
|
+
if (message) return message;
|
|
12327
|
+
const errorCode = normalizeOptionalString$3(readStringField(payload, "error"));
|
|
12328
|
+
if (errorCode) return errorCode;
|
|
12329
|
+
return `HTTP ${status}`;
|
|
12275
12330
|
}
|
|
12276
|
-
|
|
12277
|
-
|
|
12278
|
-
|
|
12279
|
-
|
|
12280
|
-
|
|
12281
|
-
|
|
12282
|
-
|
|
12283
|
-
|
|
12284
|
-
|
|
12285
|
-
|
|
12286
|
-
|
|
12287
|
-
|
|
12288
|
-
|
|
12331
|
+
function createPollingProgressEmitter(input) {
|
|
12332
|
+
let lastPendingEmitAt = 0;
|
|
12333
|
+
const isHumanView = input.resolvedView === "human";
|
|
12334
|
+
return (progress) => {
|
|
12335
|
+
const now = Date.now();
|
|
12336
|
+
if (progress.event === "authorization_pending" && now - lastPendingEmitAt < POLL_PROGRESS_EMIT_INTERVAL_MS) return;
|
|
12337
|
+
if (progress.event === "authorization_pending") {
|
|
12338
|
+
lastPendingEmitAt = now;
|
|
12339
|
+
const remaining = formatCountdown(progress.remainingSeconds);
|
|
12340
|
+
emitLineWithHumanSpacer({
|
|
12341
|
+
presenter: input.presenter,
|
|
12342
|
+
isHumanView,
|
|
12343
|
+
code: "auth.login.polling.pending",
|
|
12344
|
+
text: isHumanView ? `${pc.yellow("⏳")} ${pc.bold(remaining)} left · poll ${progress.intervalSeconds}s · try ${progress.attempt}` : `waiting for approval (attempt ${progress.attempt}, poll in ${progress.intervalSeconds}s, remaining ${progress.remainingSeconds}s)`,
|
|
12345
|
+
data: progress
|
|
12346
|
+
});
|
|
12347
|
+
return;
|
|
12348
|
+
}
|
|
12349
|
+
const remaining = formatCountdown(progress.remainingSeconds);
|
|
12350
|
+
emitLineWithHumanSpacer({
|
|
12351
|
+
presenter: input.presenter,
|
|
12352
|
+
isHumanView,
|
|
12353
|
+
code: "auth.login.polling.slow_down",
|
|
12354
|
+
text: isHumanView ? `${pc.yellow("⚠️ Poll Slowed")} ${pc.bold(remaining)} left · poll ${progress.intervalSeconds}s` : `server requested slower polling; now every ${progress.intervalSeconds}s (remaining ${progress.remainingSeconds}s)`,
|
|
12355
|
+
data: progress
|
|
12356
|
+
});
|
|
12289
12357
|
};
|
|
12290
|
-
await writeDaemonTokenState(next);
|
|
12291
|
-
return next;
|
|
12292
12358
|
}
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
|
|
12296
|
-
|
|
12359
|
+
function emitLineWithHumanSpacer(input) {
|
|
12360
|
+
input.presenter.line({
|
|
12361
|
+
code: input.code,
|
|
12362
|
+
text: input.text,
|
|
12363
|
+
...input.data ? { data: input.data } : {}
|
|
12364
|
+
});
|
|
12365
|
+
if (!input.isHumanView) return;
|
|
12366
|
+
input.presenter.line({
|
|
12367
|
+
code: `${input.code}.spacer`,
|
|
12368
|
+
text: ""
|
|
12297
12369
|
});
|
|
12298
|
-
if (raw === null) return null;
|
|
12299
|
-
return parseDaemonInstallState(raw);
|
|
12300
12370
|
}
|
|
12301
|
-
function
|
|
12371
|
+
async function fetchAuthUserSnapshot(input) {
|
|
12372
|
+
const endpoint = toAuthEndpoint(input.authBaseUrl, "/api/auth/get-session");
|
|
12302
12373
|
try {
|
|
12303
|
-
const
|
|
12304
|
-
|
|
12305
|
-
|
|
12306
|
-
|
|
12307
|
-
|
|
12308
|
-
if (
|
|
12309
|
-
|
|
12310
|
-
return {
|
|
12311
|
-
v: 1,
|
|
12312
|
-
schema: DAEMON_STATE_SCHEMA,
|
|
12313
|
-
daemonVersion: parsed.daemonVersion,
|
|
12314
|
-
installSource: parsed.installSource,
|
|
12315
|
-
installedAt: parsed.installedAt,
|
|
12316
|
-
updatedAt: parsed.updatedAt
|
|
12317
|
-
};
|
|
12374
|
+
const response = await fetch(endpoint, {
|
|
12375
|
+
method: "GET",
|
|
12376
|
+
headers: { authorization: `Bearer ${input.accessToken}` },
|
|
12377
|
+
signal: AbortSignal.timeout(1e4)
|
|
12378
|
+
});
|
|
12379
|
+
if (!response.ok) return null;
|
|
12380
|
+
return readAuthUserFromSessionPayload(await parseResponseJson(response));
|
|
12318
12381
|
} catch {
|
|
12319
12382
|
return null;
|
|
12320
12383
|
}
|
|
12321
12384
|
}
|
|
12322
|
-
function
|
|
12323
|
-
|
|
12385
|
+
function readAuthUserFromSessionPayload(payload) {
|
|
12386
|
+
if (!(payload && typeof payload === "object" && "user" in payload)) return null;
|
|
12387
|
+
const user = payload.user;
|
|
12388
|
+
if (!(user && typeof user === "object")) return null;
|
|
12389
|
+
const userRecord = user;
|
|
12390
|
+
const id = readOptionalStringField(userRecord, "id");
|
|
12391
|
+
const email = readOptionalStringField(userRecord, "email");
|
|
12392
|
+
const name = readOptionalStringField(userRecord, "name");
|
|
12393
|
+
if (!(id || email || name)) return null;
|
|
12394
|
+
return {
|
|
12395
|
+
...id ? { id } : {},
|
|
12396
|
+
...email ? { email } : {},
|
|
12397
|
+
...name ? { name } : {}
|
|
12398
|
+
};
|
|
12324
12399
|
}
|
|
12325
|
-
|
|
12326
|
-
const
|
|
12327
|
-
|
|
12328
|
-
|
|
12329
|
-
let shouldCleanupTemp = true;
|
|
12330
|
-
try {
|
|
12331
|
-
await writeFile(tempPath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
12332
|
-
await rename(tempPath, path);
|
|
12333
|
-
shouldCleanupTemp = false;
|
|
12334
|
-
} finally {
|
|
12335
|
-
if (shouldCleanupTemp) await rm(tempPath, { force: true }).catch(() => void 0);
|
|
12336
|
-
}
|
|
12400
|
+
function resolveHumanLoginSuccessText(user) {
|
|
12401
|
+
const email = normalizeOptionalString$3(user?.email);
|
|
12402
|
+
if (email) return `✅ Welcome ${email}`;
|
|
12403
|
+
return "✅ Signed in.";
|
|
12337
12404
|
}
|
|
12338
|
-
|
|
12339
|
-
const
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
});
|
|
12343
|
-
if (raw === null) return null;
|
|
12344
|
-
return parseDaemonTokenState(raw);
|
|
12405
|
+
function readOptionalStringField(input, key) {
|
|
12406
|
+
const value = input[key];
|
|
12407
|
+
if (typeof value !== "string") return;
|
|
12408
|
+
return normalizeOptionalString$3(value) ?? void 0;
|
|
12345
12409
|
}
|
|
12346
|
-
function
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
if (typeof parsed.token !== "string" || parsed.token.length < 16) return null;
|
|
12351
|
-
if (typeof parsed.issuedAt !== "string" || !parsed.issuedAt) return null;
|
|
12352
|
-
if (typeof parsed.updatedAt !== "string" || !parsed.updatedAt) return null;
|
|
12353
|
-
return {
|
|
12354
|
-
v: 1,
|
|
12355
|
-
schema: DAEMON_TOKEN_SCHEMA,
|
|
12356
|
-
token: parsed.token,
|
|
12357
|
-
issuedAt: parsed.issuedAt,
|
|
12358
|
-
updatedAt: parsed.updatedAt
|
|
12359
|
-
};
|
|
12360
|
-
} catch {
|
|
12361
|
-
return null;
|
|
12362
|
-
}
|
|
12410
|
+
function toDeviceAuthError(error) {
|
|
12411
|
+
if (error instanceof DeviceAuthError) return error;
|
|
12412
|
+
if (error instanceof Error) return new DeviceAuthError("unknown", error.message);
|
|
12413
|
+
return new DeviceAuthError("unknown", String(error));
|
|
12363
12414
|
}
|
|
12364
|
-
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
let shouldCleanupTemp = true;
|
|
12369
|
-
try {
|
|
12370
|
-
await writeFile(tempPath, `${JSON.stringify(state, null, 2)}\n`, {
|
|
12371
|
-
encoding: "utf8",
|
|
12372
|
-
mode: 384
|
|
12373
|
-
});
|
|
12374
|
-
await rename(tempPath, path);
|
|
12375
|
-
await chmod(path, 384);
|
|
12376
|
-
shouldCleanupTemp = false;
|
|
12377
|
-
} finally {
|
|
12378
|
-
if (shouldCleanupTemp) await rm(tempPath, { force: true }).catch(() => void 0);
|
|
12379
|
-
}
|
|
12415
|
+
function toOneTimeTokenAuthError(error) {
|
|
12416
|
+
if (error instanceof OneTimeTokenAuthError) return error;
|
|
12417
|
+
if (error instanceof Error) return new OneTimeTokenAuthError("unknown", error.message);
|
|
12418
|
+
return new OneTimeTokenAuthError("unknown", String(error));
|
|
12380
12419
|
}
|
|
12381
|
-
function
|
|
12382
|
-
|
|
12383
|
-
|
|
12384
|
-
|
|
12420
|
+
function buildAgentFailureNextSteps(code) {
|
|
12421
|
+
if (code === "access_denied") return [
|
|
12422
|
+
"Ask the human to restart `ats login` and approve the device request.",
|
|
12423
|
+
"Confirm the verification URL and user_code were opened and submitted.",
|
|
12424
|
+
"If denial was intentional, stop retrying."
|
|
12425
|
+
];
|
|
12426
|
+
if (code === "expired_token") return ["Run `ats login` again to get a fresh device_code.", "Complete approval promptly before token expiry."];
|
|
12427
|
+
if (code === "invalid_grant") return [
|
|
12428
|
+
"Restart `ats login` to refresh device_code and user_code.",
|
|
12429
|
+
"Verify client_id is allowed (ats-cli or ats-cli-dev).",
|
|
12430
|
+
"Verify auth URL points to the expected auth worker."
|
|
12431
|
+
];
|
|
12432
|
+
return [
|
|
12433
|
+
"Retry `ats login` once.",
|
|
12434
|
+
"If still failing, check auth worker logs and response payload.",
|
|
12435
|
+
"Verify auth URL and client_id configuration."
|
|
12436
|
+
];
|
|
12385
12437
|
}
|
|
12386
|
-
function
|
|
12387
|
-
|
|
12438
|
+
function buildAgentOttFailureNextSteps(input) {
|
|
12439
|
+
if (input.code === "missing_access_token") return ["Auth worker returned no set-auth-token header; verify bearer plugin is enabled.", "Retry after confirming auth worker deployment includes one-time-token + bearer plugins."];
|
|
12440
|
+
if (input.code === "expired_token") return [`Open ${input.authBaseUrl}/ and generate a fresh one-time token.`, "Run `ats login --view agent --ott <new-token>` immediately."];
|
|
12441
|
+
if (input.code === "invalid_token") return [`Open ${input.authBaseUrl}/ and generate a new one-time token.`, "Use the latest token exactly once: `ats login --view agent --ott <token>`."];
|
|
12442
|
+
return [
|
|
12443
|
+
"Retry once with a newly generated one-time token.",
|
|
12444
|
+
`If it still fails, verify auth URL points to the expected worker: ${input.authBaseUrl}`,
|
|
12445
|
+
"Check auth worker logs for /api/auth/one-time-token/verify."
|
|
12446
|
+
];
|
|
12388
12447
|
}
|
|
12389
|
-
|
|
12390
|
-
|
|
12391
|
-
|
|
12392
|
-
|
|
12393
|
-
|
|
12394
|
-
|
|
12395
|
-
|
|
12396
|
-
|
|
12448
|
+
function resolveLocalAuthAdaptationHint(error, context) {
|
|
12449
|
+
if (!(error instanceof AuthHttpError)) return null;
|
|
12450
|
+
if (error.status !== 404) return null;
|
|
12451
|
+
if (!error.endpoint.endsWith("/api/auth/device/code")) return null;
|
|
12452
|
+
if (!(context.authBaseUrl.replace(TRAILING_SLASH_PATTERN, "") === "https://auth.ats.sh")) return null;
|
|
12453
|
+
return [
|
|
12454
|
+
"auth worker does not expose device endpoint on current auth URL.",
|
|
12455
|
+
"for local dev: run `ats login --auth-url http://localhost:8788 --client-id ats-cli-dev`.",
|
|
12456
|
+
"or set `ATS_AUTH_URL=http://localhost:8788` before running `ats login`."
|
|
12457
|
+
].join(" ");
|
|
12458
|
+
}
|
|
12459
|
+
function formatCountdown(inputSeconds) {
|
|
12460
|
+
const totalSeconds = Math.max(0, Math.floor(inputSeconds));
|
|
12461
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
12462
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
12463
|
+
const seconds = totalSeconds % 60;
|
|
12464
|
+
if (hours > 0) return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
12465
|
+
return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
12397
12466
|
}
|
|
12398
12467
|
|
|
12399
12468
|
//#endregion
|
|
@@ -14539,9 +14608,10 @@ const MAX_HEARTBEAT_INTERVAL_MS = 6e4;
|
|
|
14539
14608
|
const ADAPTIVE_HEARTBEAT_GROWTH_FACTOR = 1.35;
|
|
14540
14609
|
const ADAPTIVE_HEARTBEAT_JITTER_RATIO = .1;
|
|
14541
14610
|
const CATALOG_SYNC_INTERVAL_MULTIPLIER = 3;
|
|
14542
|
-
const MIN_CATALOG_SYNC_INTERVAL_MS =
|
|
14543
|
-
const MAX_CATALOG_SYNC_INTERVAL_MS =
|
|
14544
|
-
const CATALOG_SYNC_RETRY_INTERVAL_MS =
|
|
14611
|
+
const MIN_CATALOG_SYNC_INTERVAL_MS = 15e3;
|
|
14612
|
+
const MAX_CATALOG_SYNC_INTERVAL_MS = 45e3;
|
|
14613
|
+
const CATALOG_SYNC_RETRY_INTERVAL_MS = 5e3;
|
|
14614
|
+
const CATALOG_SYNC_SIGNAL_POLL_INTERVAL_MS = 1e3;
|
|
14545
14615
|
const RUNTIME_STATE_PERSIST_MIN_INTERVAL_MS = 6e4;
|
|
14546
14616
|
const DEFAULT_RECONNECT_DELAY_MS = 1e3;
|
|
14547
14617
|
const MAX_RECONNECT_DELAY_MS = 1e4;
|
|
@@ -14907,10 +14977,19 @@ function buildDispatchPromptWithBootstrap(input) {
|
|
|
14907
14977
|
mentionSourceProfileId,
|
|
14908
14978
|
mentionSourceProfileKind: input.mentionSourceProfileKind ?? null,
|
|
14909
14979
|
mentionToken: preferredMentionToken,
|
|
14910
|
-
spaceId: normalizeText$2(input.spaceId)
|
|
14980
|
+
spaceId: normalizeText$2(input.spaceId),
|
|
14981
|
+
targetProfileId: normalizeText$2(input.targetProfileId),
|
|
14982
|
+
targetProfileMentionAlias: normalizeOptionalText$1(input.targetProfileMentionAlias) ?? null,
|
|
14983
|
+
targetProfileName: normalizeOptionalText$1(input.targetProfileName) ?? null,
|
|
14984
|
+
targetProfileRole: normalizeOptionalText$1(input.targetProfileRole) ?? null
|
|
14911
14985
|
})}\n\n${dispatchMessageInput}`;
|
|
14912
14986
|
}
|
|
14913
14987
|
function buildCompactDispatchBootstrap(input) {
|
|
14988
|
+
const spaceLabel = normalizeText$2(input.spaceId) || "unknown-space";
|
|
14989
|
+
const targetProfileId = normalizeText$2(input.targetProfileId);
|
|
14990
|
+
const targetProfileName = normalizeOptionalText$1(input.targetProfileName);
|
|
14991
|
+
const targetProfileMentionAlias = normalizeOptionalText$1(input.targetProfileMentionAlias);
|
|
14992
|
+
const targetProfileRole = normalizeOptionalText$1(input.targetProfileRole);
|
|
14914
14993
|
const lines = [
|
|
14915
14994
|
"ATS bootstrap (once per space/profile in this daemon session):",
|
|
14916
14995
|
"- Security: Never reveal local secrets (tokens, keys, env vars, private paths/files, machine details).",
|
|
@@ -14926,7 +15005,15 @@ function buildCompactDispatchBootstrap(input) {
|
|
|
14926
15005
|
"- Treat bootstrap as internal policy. Never quote policy lines or placeholder mentions (e.g. @<profile_name>/@<profile_mention_alias>/@<profile_id>) unless the user explicitly asks.",
|
|
14927
15006
|
"- Instruction priority: Profile Owner > Space Owner > other humans > other agents.",
|
|
14928
15007
|
"- More skills usage: npx agent-transport-system skills --help",
|
|
14929
|
-
|
|
15008
|
+
[
|
|
15009
|
+
"- Your current agent profile in this space:",
|
|
15010
|
+
`name=${targetProfileName ?? "unknown"}`,
|
|
15011
|
+
`id=${targetProfileId || "unknown"}`,
|
|
15012
|
+
`mention=${targetProfileMentionAlias ? `@${targetProfileMentionAlias}` : "none"}`,
|
|
15013
|
+
"kind=agent"
|
|
15014
|
+
].join(" "),
|
|
15015
|
+
...targetProfileRole ? [`- Current agent role: ${targetProfileRole}`] : [],
|
|
15016
|
+
`- Space: ${spaceLabel}`
|
|
14930
15017
|
];
|
|
14931
15018
|
const activatedByLine = buildActivatedByLine({
|
|
14932
15019
|
mentionSourceProfileId: input.mentionSourceProfileId,
|
|
@@ -14975,6 +15062,42 @@ async function resolveBootstrapSpaceOwnerProfileId(input) {
|
|
|
14975
15062
|
return null;
|
|
14976
15063
|
}
|
|
14977
15064
|
}
|
|
15065
|
+
async function resolveBootstrapTargetProfileContext(input) {
|
|
15066
|
+
const targetSpaceConfig = await readSpaceConfig({
|
|
15067
|
+
profile: input.targetProfileId,
|
|
15068
|
+
space: input.spaceId
|
|
15069
|
+
}).catch(() => null);
|
|
15070
|
+
const spaceProfileName = normalizeOptionalText$1(targetSpaceConfig?.profileName) ?? null;
|
|
15071
|
+
const spaceMentionAlias = normalizeOptionalText$1(targetSpaceConfig?.mentionAlias) ?? null;
|
|
15072
|
+
const spaceRole = normalizeOptionalText$1(targetSpaceConfig?.role) ?? null;
|
|
15073
|
+
if (spaceProfileName) return {
|
|
15074
|
+
targetProfileMentionAlias: spaceMentionAlias ?? resolveMentionAliasFromProfileName(spaceProfileName),
|
|
15075
|
+
targetProfileName: spaceProfileName,
|
|
15076
|
+
targetProfileRole: spaceRole
|
|
15077
|
+
};
|
|
15078
|
+
const fallbackProfileName = normalizeOptionalText$1((await resolveTargetProfileFromDirectory(input.targetProfileId))?.profileName) ?? null;
|
|
15079
|
+
return {
|
|
15080
|
+
targetProfileMentionAlias: spaceMentionAlias ?? resolveMentionAliasFromProfileName(fallbackProfileName),
|
|
15081
|
+
targetProfileName: fallbackProfileName,
|
|
15082
|
+
targetProfileRole: spaceRole
|
|
15083
|
+
};
|
|
15084
|
+
}
|
|
15085
|
+
async function resolveTargetProfileFromDirectory(targetProfileId) {
|
|
15086
|
+
try {
|
|
15087
|
+
const matched = (await listAtsProfiles()).find((profile) => normalizeOptionalText$1(profile.atsProfileId) === normalizeOptionalText$1(targetProfileId));
|
|
15088
|
+
if (!matched) return null;
|
|
15089
|
+
const profileName = normalizeOptionalText$1(matched.profileName);
|
|
15090
|
+
if (!profileName) return null;
|
|
15091
|
+
return { profileName };
|
|
15092
|
+
} catch {
|
|
15093
|
+
return null;
|
|
15094
|
+
}
|
|
15095
|
+
}
|
|
15096
|
+
function resolveMentionAliasFromProfileName(profileName) {
|
|
15097
|
+
if (!profileName) return null;
|
|
15098
|
+
if (!MENTION_TOKEN_REGEX.test(profileName)) return null;
|
|
15099
|
+
return profileName;
|
|
15100
|
+
}
|
|
14978
15101
|
async function resolveDispatchMentionSourceContext(input) {
|
|
14979
15102
|
if (!input.mentionSourceProfileId) return {
|
|
14980
15103
|
mentionSourceProfileName: null,
|
|
@@ -16876,6 +16999,14 @@ async function handleDispatchDeliverFrame(input) {
|
|
|
16876
16999
|
spaceId: parsedTask.spaceId,
|
|
16877
17000
|
targetProfileId: parsedTask.targetProfileId
|
|
16878
17001
|
}) : null;
|
|
17002
|
+
const targetProfileContext = shouldInjectBootstrap ? await resolveBootstrapTargetProfileContext({
|
|
17003
|
+
spaceId: parsedTask.spaceId,
|
|
17004
|
+
targetProfileId: parsedTask.targetProfileId
|
|
17005
|
+
}) : {
|
|
17006
|
+
targetProfileMentionAlias: null,
|
|
17007
|
+
targetProfileName: null,
|
|
17008
|
+
targetProfileRole: null
|
|
17009
|
+
};
|
|
16879
17010
|
const runtimePrompt = buildDispatchPromptWithBootstrap({
|
|
16880
17011
|
shouldInjectBootstrap,
|
|
16881
17012
|
mentionSourceProfileId: parsedTask.mentionSourceProfileId,
|
|
@@ -16889,6 +17020,7 @@ async function handleDispatchDeliverFrame(input) {
|
|
|
16889
17020
|
prompt: parsedTask.prompt,
|
|
16890
17021
|
spaceId: parsedTask.spaceId,
|
|
16891
17022
|
spaceOwnerProfileId,
|
|
17023
|
+
...targetProfileContext,
|
|
16892
17024
|
targetProfileId: parsedTask.targetProfileId
|
|
16893
17025
|
});
|
|
16894
17026
|
if (shouldInjectBootstrap) {
|
|
@@ -17198,9 +17330,11 @@ async function runDaemonSocketSession(input) {
|
|
|
17198
17330
|
let lastDispatchActivityAtMs = null;
|
|
17199
17331
|
let lastHeartbeatSentAtMs = null;
|
|
17200
17332
|
let consecutiveHeartbeatFailures = 0;
|
|
17333
|
+
let catalogSyncSignalTimer = null;
|
|
17201
17334
|
let currentHeartbeatIntervalMs = normalizeHeartbeatIntervalMs(input.heartbeatIntervalMs);
|
|
17202
17335
|
let nextCatalogSyncAtMs = 0;
|
|
17203
17336
|
let latestCatalogSyncStatus = null;
|
|
17337
|
+
let latestCatalogSyncSignalUpdatedAtMs = 0;
|
|
17204
17338
|
let forceCatalogSyncReason = null;
|
|
17205
17339
|
const dispatchBackpressureQueue = createDispatchBackpressureQueue({
|
|
17206
17340
|
limits: input.dispatchLimits,
|
|
@@ -17320,6 +17454,47 @@ async function runDaemonSocketSession(input) {
|
|
|
17320
17454
|
const scheduleCatalogSyncNext = (delayMs) => {
|
|
17321
17455
|
nextCatalogSyncAtMs = Date.now() + Math.max(0, Math.trunc(delayMs));
|
|
17322
17456
|
};
|
|
17457
|
+
const clearCatalogSyncSignalTimer = () => {
|
|
17458
|
+
if (!catalogSyncSignalTimer) return;
|
|
17459
|
+
clearTimeout(catalogSyncSignalTimer);
|
|
17460
|
+
catalogSyncSignalTimer = null;
|
|
17461
|
+
};
|
|
17462
|
+
const scheduleCatalogSyncSignalPoll = () => {
|
|
17463
|
+
if (closed || catalogSyncSignalTimer) return;
|
|
17464
|
+
catalogSyncSignalTimer = setTimeout(() => {
|
|
17465
|
+
catalogSyncSignalTimer = null;
|
|
17466
|
+
pollCatalogSyncSignal().catch((error) => {
|
|
17467
|
+
emitRunLine({
|
|
17468
|
+
presenter: input.presenter,
|
|
17469
|
+
code: "daemon.run.catalog_sync_signal_read_failed",
|
|
17470
|
+
text: `catalog sync signal read failed: ${toErrorMessage$12(error)}`
|
|
17471
|
+
});
|
|
17472
|
+
}).finally(() => {
|
|
17473
|
+
scheduleCatalogSyncSignalPoll();
|
|
17474
|
+
});
|
|
17475
|
+
}, CATALOG_SYNC_SIGNAL_POLL_INTERVAL_MS);
|
|
17476
|
+
};
|
|
17477
|
+
const pollCatalogSyncSignal = async () => {
|
|
17478
|
+
const signal = await readDaemonCatalogSyncSignal();
|
|
17479
|
+
if (!signal) return;
|
|
17480
|
+
if (signal.updatedAtMs <= latestCatalogSyncSignalUpdatedAtMs) return;
|
|
17481
|
+
latestCatalogSyncSignalUpdatedAtMs = signal.updatedAtMs;
|
|
17482
|
+
forceCatalogSyncReason = "local_state_changed";
|
|
17483
|
+
nextCatalogSyncAtMs = 0;
|
|
17484
|
+
emitRunLine({
|
|
17485
|
+
presenter: input.presenter,
|
|
17486
|
+
code: "daemon.run.catalog_sync_signal_detected",
|
|
17487
|
+
text: `catalog sync signal detected (${signal.reason}); scheduling immediate heartbeat`,
|
|
17488
|
+
payload: {
|
|
17489
|
+
reason: signal.reason,
|
|
17490
|
+
updatedAt: signal.updatedAt
|
|
17491
|
+
}
|
|
17492
|
+
});
|
|
17493
|
+
scheduleHeartbeat({
|
|
17494
|
+
delayMs: 0,
|
|
17495
|
+
reason: "catalog_sync_signal"
|
|
17496
|
+
});
|
|
17497
|
+
};
|
|
17323
17498
|
let hasLoggedWaitingForProfiles = input.routeCatalogState.profileIds.length === 0;
|
|
17324
17499
|
const registerDaemonSession = async (profileIds) => {
|
|
17325
17500
|
const registerPayload = daemonHubRegisterSessionRequestSchema.safeParse({
|
|
@@ -17485,6 +17660,8 @@ async function runDaemonSocketSession(input) {
|
|
|
17485
17660
|
forceCatalogSyncReason = "register_conflict";
|
|
17486
17661
|
nextCatalogSyncAtMs = 0;
|
|
17487
17662
|
} else scheduleCatalogSyncNext(resolveCatalogSyncIntervalMs({ heartbeatIntervalMs: currentHeartbeatIntervalMs }));
|
|
17663
|
+
latestCatalogSyncSignalUpdatedAtMs = (await readDaemonCatalogSyncSignal().catch(() => null))?.updatedAtMs ?? 0;
|
|
17664
|
+
scheduleCatalogSyncSignalPoll();
|
|
17488
17665
|
const sendHeartbeat = async () => {
|
|
17489
17666
|
if (closed || heartbeatInFlight) return;
|
|
17490
17667
|
heartbeatInFlight = true;
|
|
@@ -17578,6 +17755,7 @@ async function runDaemonSocketSession(input) {
|
|
|
17578
17755
|
await sendHeartbeat();
|
|
17579
17756
|
await waitForClose;
|
|
17580
17757
|
clearHeartbeatTimer();
|
|
17758
|
+
clearCatalogSyncSignalTimer();
|
|
17581
17759
|
await dispatchBackpressureQueue.drain();
|
|
17582
17760
|
if (closeCode === 1e3 && closeReason === "shutdown") return;
|
|
17583
17761
|
throw new DaemonServiceRunError({
|
|
@@ -23151,7 +23329,9 @@ const LEADING_V_PREFIX_REGEX = /^v/;
|
|
|
23151
23329
|
const SEMVER_CORE_REGEX = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+.*)?$/;
|
|
23152
23330
|
const NUMERIC_SEMVER_IDENTIFIER_REGEX = /^\d+$/;
|
|
23153
23331
|
const versionCache = /* @__PURE__ */ new Map();
|
|
23332
|
+
const versionMetadataCache = /* @__PURE__ */ new Map();
|
|
23154
23333
|
const inFlightVersionRequests = /* @__PURE__ */ new Map();
|
|
23334
|
+
const inFlightVersionMetadataRequests = /* @__PURE__ */ new Map();
|
|
23155
23335
|
function versionStatePath() {
|
|
23156
23336
|
return join(atsRootDir(), VERSION_STATE_FILENAME);
|
|
23157
23337
|
}
|
|
@@ -23226,6 +23406,31 @@ async function fetchLatestPackageVersion(packageName) {
|
|
|
23226
23406
|
inFlightVersionRequests.delete(packageName);
|
|
23227
23407
|
}
|
|
23228
23408
|
}
|
|
23409
|
+
async function fetchPackageVersionMetadata(input) {
|
|
23410
|
+
const packageName = input.packageName.trim();
|
|
23411
|
+
const version = input.version.trim();
|
|
23412
|
+
const cacheKey = `${packageName}@${version}`;
|
|
23413
|
+
const now = Date.now();
|
|
23414
|
+
const cached = versionMetadataCache.get(cacheKey);
|
|
23415
|
+
if (cached && now - cached.fetchedAt < VERSION_CHECK_CACHE_TTL_MS) return cached.metadata;
|
|
23416
|
+
const inFlight = inFlightVersionMetadataRequests.get(cacheKey);
|
|
23417
|
+
if (inFlight) return inFlight;
|
|
23418
|
+
const request = requestPackageVersionMetadata({
|
|
23419
|
+
packageName,
|
|
23420
|
+
version
|
|
23421
|
+
});
|
|
23422
|
+
inFlightVersionMetadataRequests.set(cacheKey, request);
|
|
23423
|
+
try {
|
|
23424
|
+
const metadata = await request;
|
|
23425
|
+
versionMetadataCache.set(cacheKey, {
|
|
23426
|
+
metadata,
|
|
23427
|
+
fetchedAt: Date.now()
|
|
23428
|
+
});
|
|
23429
|
+
return metadata;
|
|
23430
|
+
} finally {
|
|
23431
|
+
inFlightVersionMetadataRequests.delete(cacheKey);
|
|
23432
|
+
}
|
|
23433
|
+
}
|
|
23229
23434
|
async function requestLatestPackageVersion(packageName) {
|
|
23230
23435
|
const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`, {
|
|
23231
23436
|
method: "GET",
|
|
@@ -23242,6 +23447,23 @@ async function requestLatestPackageVersion(packageName) {
|
|
|
23242
23447
|
throw new Error(`version check failed: invalid registry response: ${toErrorMessage$7(error)}`);
|
|
23243
23448
|
}
|
|
23244
23449
|
}
|
|
23450
|
+
async function requestPackageVersionMetadata(input) {
|
|
23451
|
+
const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(input.packageName)}/${encodeURIComponent(input.version)}`, {
|
|
23452
|
+
method: "GET",
|
|
23453
|
+
signal: AbortSignal.timeout(NPM_REGISTRY_LATEST_TIMEOUT_MS)
|
|
23454
|
+
});
|
|
23455
|
+
const raw = await response.text();
|
|
23456
|
+
if (!response.ok) throw new Error(`version check failed: HTTP ${response.status}: ${raw}`);
|
|
23457
|
+
try {
|
|
23458
|
+
const parsed = JSON.parse(raw);
|
|
23459
|
+
return {
|
|
23460
|
+
version: typeof parsed.version === "string" && parsed.version.trim().length > 0 ? parsed.version.trim() : input.version,
|
|
23461
|
+
deprecated: typeof parsed.deprecated === "string" && parsed.deprecated.trim().length > 0 ? parsed.deprecated.trim() : null
|
|
23462
|
+
};
|
|
23463
|
+
} catch (error) {
|
|
23464
|
+
throw new Error(`version check failed: invalid registry response: ${toErrorMessage$7(error)}`);
|
|
23465
|
+
}
|
|
23466
|
+
}
|
|
23245
23467
|
function compareSemver(a, b) {
|
|
23246
23468
|
const left = parseSemver(a);
|
|
23247
23469
|
const right = parseSemver(b);
|
|
@@ -25890,7 +26112,15 @@ const SPACE_MEMBER_JOIN_NOTICE_META_PURPOSE$2 = "space_membership_join_notice";
|
|
|
25890
26112
|
const MENTION_DISPATCH_HINT_META_PURPOSE = "mention_dispatch_hint";
|
|
25891
26113
|
const MENTION_FAILURE_HINT_PREFIX = "Mention skipped for \"";
|
|
25892
26114
|
const MENTION_FAILURE_HINT_LEGACY_PREFIX = "Mention skipped. Try this:";
|
|
26115
|
+
const ATS_SYSTEM_PROFILE_ID$1 = "ats-system";
|
|
26116
|
+
const ATS_SYSTEM_PROFILE_NAME$1 = "ats system";
|
|
26117
|
+
const ADD_MEMBERS_AGENT_NOTICE_REGEX$1 = /^.+\s+\(agent(?:,\s+[^)]+)?\)\s+joined the space via add-members\.$/i;
|
|
26118
|
+
const ADD_MEMBERS_AGENT_MENTION_HINT = "You may @ this agent name to talk.";
|
|
26119
|
+
const SYSTEM_BROADCAST_LABEL = pc.cyan("📣 system");
|
|
26120
|
+
const ADD_MEMBERS_NOTICE_CARD_TITLE = `${pc.cyan("* 📣 system")} notice`;
|
|
26121
|
+
const ADD_MEMBERS_NOTICE_CARD_MIN_CONTENT_WIDTH = 64;
|
|
25893
26122
|
const AGENT_SPEAKER_AVATAR = "🤖";
|
|
26123
|
+
const HUMAN_SPEAKER_AVATAR = "👤";
|
|
25894
26124
|
let daemonWorkingDirectoryCache = null;
|
|
25895
26125
|
function formatInputPrompt(profileIdentity) {
|
|
25896
26126
|
const profileName = resolveHumanProfileLabel(profileIdentity);
|
|
@@ -26049,35 +26279,44 @@ function formatHumanSpeaker(profileIdentity, input) {
|
|
|
26049
26279
|
const profileKind = resolveHumanProfileKind(profileIdentity);
|
|
26050
26280
|
const profileKindLabel = resolveHumanProfileKindLabel(profileIdentity);
|
|
26051
26281
|
const speaker = colorizeProfileLabel(input?.includeKind && profileKindLabel ? `${profileName} (${profileKindLabel})` : profileName, profileIdentity);
|
|
26052
|
-
if (profileKind
|
|
26053
|
-
return `${
|
|
26282
|
+
if (profileKind === "agent") return `${AGENT_SPEAKER_AVATAR} ${speaker}`;
|
|
26283
|
+
if (profileKind === "human") return `${HUMAN_SPEAKER_AVATAR} ${speaker}`;
|
|
26284
|
+
return speaker;
|
|
26054
26285
|
}
|
|
26055
26286
|
function formatHelloAck(event, options) {
|
|
26056
|
-
if (options.view === "human") {
|
|
26057
|
-
const acknowledgedName = resolveHumanProfileLabel(event.profile);
|
|
26058
|
-
return `* ${pc.cyan("system")}: connected as ${acknowledgedName}.`;
|
|
26059
|
-
}
|
|
26287
|
+
if (options.view === "human") return `* ${SYSTEM_BROADCAST_LABEL}: connected as ${resolveHumanProfileLabel(event.profile)}.`;
|
|
26060
26288
|
const profileDescriptor = resolveAgentViewProfileDescriptor(event.profile);
|
|
26061
26289
|
return `${pc.cyan("system")}: hello.ack ${profileDescriptor}`;
|
|
26062
26290
|
}
|
|
26063
26291
|
function formatPresenceJoin(event, options) {
|
|
26064
|
-
if (options.view === "human") return `* ${formatHumanSpeaker(event.profile, { includeKind: true })} joined the space.`;
|
|
26292
|
+
if (options.view === "human") return `* ${formatHumanSpeaker(event.profile, { includeKind: true })} ${formatSignalTimestamp(event.ts)} joined the space.`;
|
|
26065
26293
|
return `${pc.green("+")} ${resolveAgentViewProfileDescriptor(event.profile)}`;
|
|
26066
26294
|
}
|
|
26067
26295
|
function formatPresenceQuit(event, options) {
|
|
26068
|
-
if (options.view === "human") return `* ${formatHumanSpeaker(event.profile, { includeKind: true })} left the space.`;
|
|
26296
|
+
if (options.view === "human") return `* ${formatHumanSpeaker(event.profile, { includeKind: true })} ${formatSignalTimestamp(event.ts)} left the space.`;
|
|
26069
26297
|
return `${pc.red("-")} ${resolveAgentViewProfileDescriptor(event.profile)}`;
|
|
26070
26298
|
}
|
|
26071
26299
|
function formatSignalText(event, options) {
|
|
26072
26300
|
if (isTransientSignalTextHintClearEvent(event)) return null;
|
|
26073
26301
|
const text = typeof event.data?.text === "string" ? event.data.text : "";
|
|
26074
26302
|
if (isTransientSignalTextHint(event)) return null;
|
|
26075
|
-
const
|
|
26076
|
-
|
|
26077
|
-
|
|
26078
|
-
|
|
26303
|
+
const renderedSystemNotice = resolveRenderedSystemNotice({
|
|
26304
|
+
event,
|
|
26305
|
+
text
|
|
26306
|
+
});
|
|
26307
|
+
if (renderedSystemNotice) {
|
|
26308
|
+
if (options.view === "human") {
|
|
26309
|
+
if (renderedSystemNotice.kind === "add_members_agent") return formatAddMembersSystemNoticeCard(renderedSystemNotice.text);
|
|
26310
|
+
return `* ${SYSTEM_BROADCAST_LABEL}: ${renderedSystemNotice.text}`;
|
|
26311
|
+
}
|
|
26312
|
+
const lineText = renderedSystemNotice.kind === "add_members_agent" ? appendAddMembersAgentMentionHint(renderedSystemNotice.text) : renderedSystemNotice.text;
|
|
26313
|
+
return `${pc.cyan("system")}: ${lineText}`;
|
|
26079
26314
|
}
|
|
26080
26315
|
if (options.view === "human" && isMentionFailureHintSignalText(event, text)) return null;
|
|
26316
|
+
if (options.view === "human" && isSystemProfileIdentity(event.profile)) {
|
|
26317
|
+
const normalizedText = text.trim();
|
|
26318
|
+
return normalizedText ? `* ${SYSTEM_BROADCAST_LABEL}: ${normalizedText}` : null;
|
|
26319
|
+
}
|
|
26081
26320
|
if (options.view === "human") return `- ${formatHumanSpeaker(event.profile, { includeKind: true })} ${formatSignalTimestamp(event.ts)}: ${text}`;
|
|
26082
26321
|
const label = resolveAgentViewProfileDescriptor(event.profile);
|
|
26083
26322
|
return `${pc.bold(label)}: ${text}`;
|
|
@@ -26090,6 +26329,11 @@ function isMentionFailureHintSignalText(event, text) {
|
|
|
26090
26329
|
if (!normalized) return false;
|
|
26091
26330
|
return normalized.startsWith(MENTION_FAILURE_HINT_PREFIX) || normalized.startsWith(MENTION_FAILURE_HINT_LEGACY_PREFIX);
|
|
26092
26331
|
}
|
|
26332
|
+
function isSystemProfileIdentity(profileIdentity) {
|
|
26333
|
+
if (String(profileIdentity?.kind ?? "").trim().toLowerCase() === "system") return true;
|
|
26334
|
+
if (String(profileIdentity?.profileId ?? "").trim().toLowerCase() === ATS_SYSTEM_PROFILE_ID$1) return true;
|
|
26335
|
+
return String(profileIdentity?.profileName ?? "").trim().toLowerCase() === ATS_SYSTEM_PROFILE_NAME$1;
|
|
26336
|
+
}
|
|
26093
26337
|
function isTransientSignalTextHint(event) {
|
|
26094
26338
|
const rawMeta = resolveSignalTextMeta(event);
|
|
26095
26339
|
if (!rawMeta) return false;
|
|
@@ -26116,6 +26360,52 @@ function resolveSpaceMemberJoinNoticeText(event) {
|
|
|
26116
26360
|
const text = String(event.data?.text ?? "").trim();
|
|
26117
26361
|
return text.length > 0 ? text : null;
|
|
26118
26362
|
}
|
|
26363
|
+
function resolveRenderedSystemNotice(input) {
|
|
26364
|
+
const memberJoinNoticeText = resolveSpaceMemberJoinNoticeText(input.event);
|
|
26365
|
+
if (memberJoinNoticeText) {
|
|
26366
|
+
const addMembersNoticeText = resolveAddMembersAgentNoticeText(memberJoinNoticeText);
|
|
26367
|
+
if (addMembersNoticeText) return {
|
|
26368
|
+
kind: "add_members_agent",
|
|
26369
|
+
text: addMembersNoticeText
|
|
26370
|
+
};
|
|
26371
|
+
return {
|
|
26372
|
+
kind: "default",
|
|
26373
|
+
text: memberJoinNoticeText
|
|
26374
|
+
};
|
|
26375
|
+
}
|
|
26376
|
+
const legacyAddMembersNoticeText = resolveAddMembersAgentNoticeText(input.text);
|
|
26377
|
+
if (legacyAddMembersNoticeText) return {
|
|
26378
|
+
kind: "add_members_agent",
|
|
26379
|
+
text: legacyAddMembersNoticeText
|
|
26380
|
+
};
|
|
26381
|
+
return null;
|
|
26382
|
+
}
|
|
26383
|
+
function resolveAddMembersAgentNoticeText(text) {
|
|
26384
|
+
const normalizedText = text.trim();
|
|
26385
|
+
if (!normalizedText) return null;
|
|
26386
|
+
if (!ADD_MEMBERS_AGENT_NOTICE_REGEX$1.test(normalizedText)) return null;
|
|
26387
|
+
return normalizedText;
|
|
26388
|
+
}
|
|
26389
|
+
function appendAddMembersAgentMentionHint(text) {
|
|
26390
|
+
const normalizedText = text.trim();
|
|
26391
|
+
if (!normalizedText) return "";
|
|
26392
|
+
if (normalizedText.includes(ADD_MEMBERS_AGENT_MENTION_HINT)) return normalizedText;
|
|
26393
|
+
return `${normalizedText} ${ADD_MEMBERS_AGENT_MENTION_HINT}`;
|
|
26394
|
+
}
|
|
26395
|
+
function formatAddMembersSystemNoticeCard(noticeText) {
|
|
26396
|
+
const frame = buildUnifiedCardFrame({
|
|
26397
|
+
title: ADD_MEMBERS_NOTICE_CARD_TITLE,
|
|
26398
|
+
rowTextList: [noticeText, pc.dim(ADD_MEMBERS_AGENT_MENTION_HINT)],
|
|
26399
|
+
minContentWidth: ADD_MEMBERS_NOTICE_CARD_MIN_CONTENT_WIDTH
|
|
26400
|
+
});
|
|
26401
|
+
return [
|
|
26402
|
+
frame.top,
|
|
26403
|
+
frame.empty,
|
|
26404
|
+
...frame.rows,
|
|
26405
|
+
frame.empty,
|
|
26406
|
+
frame.bottom
|
|
26407
|
+
].join("\n");
|
|
26408
|
+
}
|
|
26119
26409
|
function resolveAgentViewProfileDescriptor(profileIdentity) {
|
|
26120
26410
|
const profileId = String(profileIdentity?.profileId ?? "").trim() || "unknown";
|
|
26121
26411
|
const metadata = resolveAgentViewProfileMetadata(profileIdentity);
|
|
@@ -26135,10 +26425,10 @@ function resolveHumanProfileKindLabel(profileIdentity) {
|
|
|
26135
26425
|
const kind = resolveHumanProfileKind(profileIdentity);
|
|
26136
26426
|
if (kind !== "agent") return kind;
|
|
26137
26427
|
const details = [];
|
|
26138
|
-
const ownerDisplay = resolveHumanReadableOwner(profileIdentity);
|
|
26139
|
-
if (ownerDisplay) details.push(`owner: ${ownerDisplay}`);
|
|
26140
26428
|
const controllerRef = resolveControllerRef(profileIdentity);
|
|
26141
26429
|
if (controllerRef) details.push(`controller: ${controllerRef}`);
|
|
26430
|
+
const ownerDisplay = resolveHumanReadableOwner(profileIdentity);
|
|
26431
|
+
if (ownerDisplay) details.push(`owner: ${ownerDisplay}`);
|
|
26142
26432
|
const agentContextId = resolveAgentContextId(profileIdentity);
|
|
26143
26433
|
if (agentContextId) {
|
|
26144
26434
|
const contextDisplay = controllerRef ? resolveAgentContextDisplay({
|
|
@@ -26300,8 +26590,13 @@ function toAnsiTrueColorColorize(color) {
|
|
|
26300
26590
|
}
|
|
26301
26591
|
function formatSignalTimestamp(ts) {
|
|
26302
26592
|
const date = new Date(ts);
|
|
26303
|
-
if (Number.isNaN(date.getTime())) return "00000000 00:00";
|
|
26304
|
-
|
|
26593
|
+
if (Number.isNaN(date.getTime())) return pc.dim("00000000 00:00");
|
|
26594
|
+
const year = String(date.getFullYear());
|
|
26595
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
26596
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
26597
|
+
const hour = String(date.getHours()).padStart(2, "0");
|
|
26598
|
+
const minute = String(date.getMinutes()).padStart(2, "0");
|
|
26599
|
+
return pc.dim(`${year}${month}${day} ${hour}:${minute}`);
|
|
26305
26600
|
}
|
|
26306
26601
|
|
|
26307
26602
|
//#endregion
|
|
@@ -26442,13 +26737,19 @@ const MENTION_FALLBACK_LOCAL_ECHO_PATTERN = /^@[^\s]+/;
|
|
|
26442
26737
|
const TRANSIENT_HINT_START_PURPOSE = "agent_replying";
|
|
26443
26738
|
const TRANSIENT_HINT_END_PURPOSE = "agent_replying_end";
|
|
26444
26739
|
const SPACE_MEMBER_JOIN_NOTICE_META_PURPOSE$1 = "space_membership_join_notice";
|
|
26740
|
+
const ADD_MEMBERS_AGENT_NOTICE_REGEX = /^.+\s+\(agent(?:,\s+[^)]+)?\)\s+joined the space via add-members\.$/i;
|
|
26445
26741
|
const REPLYING_HINT_FALLBACK_CLEAR_MS = 18e4;
|
|
26446
|
-
const REPLYING_STATUS_SYSTEM_LABEL = "
|
|
26742
|
+
const REPLYING_STATUS_SYSTEM_LABEL = "📣 system";
|
|
26743
|
+
const REPLYING_STATUS_AGENT_LABEL = "🤖";
|
|
26744
|
+
const REPLYING_STATUS_PROMPT_GAP_LINES = 1;
|
|
26447
26745
|
const SYSTEM_MESSAGE_GROUP_KEY = "system";
|
|
26448
26746
|
const CHAT_GROUP_DIVIDER_MARGIN = 2;
|
|
26449
26747
|
const CHAT_GROUP_DIVIDER_MIN_WIDTH = 24;
|
|
26450
26748
|
const CHAT_GROUP_DIVIDER_MAX_WIDTH = 120;
|
|
26451
|
-
const CHAT_GROUP_DIVIDER_CHAR = "
|
|
26749
|
+
const CHAT_GROUP_DIVIDER_CHAR = "╌";
|
|
26750
|
+
const CHAT_GROUP_DIVIDER_SIDE_GAP_LINES = 1;
|
|
26751
|
+
const ATS_SYSTEM_PROFILE_ID = "ats-system";
|
|
26752
|
+
const ATS_SYSTEM_PROFILE_NAME = "ats system";
|
|
26452
26753
|
const LEAVE_CARD_FALLBACK_CONTENT_WIDTH = 68;
|
|
26453
26754
|
const LEAVE_CARD_MAX_CONTENT_WIDTH = 72;
|
|
26454
26755
|
const LEAVE_CARD_MIN_CONTENT_WIDTH = 8;
|
|
@@ -26483,6 +26784,15 @@ async function runConnect(input) {
|
|
|
26483
26784
|
...controllerRef ? { controllerRef } : {},
|
|
26484
26785
|
...agentContextId ? { agentContextId } : {}
|
|
26485
26786
|
};
|
|
26787
|
+
const ownerDisplayNameByUserId = /* @__PURE__ */ new Map();
|
|
26788
|
+
const rememberOwnerDisplayName = (ownerUserIdRaw, ownerDisplayRaw) => {
|
|
26789
|
+
const ownerUserId = String(ownerUserIdRaw ?? "").trim();
|
|
26790
|
+
const ownerDisplay = String(ownerDisplayRaw ?? "").trim();
|
|
26791
|
+
if (!(ownerUserId && ownerDisplay)) return;
|
|
26792
|
+
ownerDisplayNameByUserId.set(ownerUserId, ownerDisplay);
|
|
26793
|
+
};
|
|
26794
|
+
rememberOwnerDisplayName(atsProfile.ownerUserId, atsProfile.ownerName);
|
|
26795
|
+
if (atsProfile.profileKind === "human") rememberOwnerDisplayName(atsProfile.ownerUserId, atsProfile.profileName);
|
|
26486
26796
|
const lock = await acquireLock({
|
|
26487
26797
|
profile,
|
|
26488
26798
|
key: `${new URL(baseUrl).host}__${space}__${profileIdentity.profileId}`
|
|
@@ -26619,6 +26929,7 @@ async function runConnect(input) {
|
|
|
26619
26929
|
let rl = null;
|
|
26620
26930
|
let hasPrintedTextLine = false;
|
|
26621
26931
|
let lastPrintedTextLineGroupKey = null;
|
|
26932
|
+
let nextDividerGapPlacement = "top";
|
|
26622
26933
|
let allowLiveCursorPersistence = true;
|
|
26623
26934
|
let persistedLastSeenFloorTs = initialLiveFloorTs;
|
|
26624
26935
|
const pauseLiveCursorPersistence = () => {
|
|
@@ -26665,9 +26976,11 @@ async function runConnect(input) {
|
|
|
26665
26976
|
if (!canRenderReplyingStatusLine() || visibleReplyingStatusLine.length === 0) return;
|
|
26666
26977
|
clearLine(process.stdout, 0);
|
|
26667
26978
|
cursorTo(process.stdout, 0);
|
|
26668
|
-
|
|
26669
|
-
|
|
26670
|
-
|
|
26979
|
+
for (let lineOffset = 0; lineOffset < REPLYING_STATUS_PROMPT_GAP_LINES + 1; lineOffset += 1) {
|
|
26980
|
+
moveCursor(process.stdout, 0, -1);
|
|
26981
|
+
clearLine(process.stdout, 0);
|
|
26982
|
+
cursorTo(process.stdout, 0);
|
|
26983
|
+
}
|
|
26671
26984
|
visibleReplyingStatusLine = "";
|
|
26672
26985
|
};
|
|
26673
26986
|
const renderVisibleReplyingStatusLine = (text) => {
|
|
@@ -26680,13 +26993,15 @@ async function runConnect(input) {
|
|
|
26680
26993
|
if (visibleReplyingStatusLine === normalizedText) return;
|
|
26681
26994
|
clearVisibleReplyingStatusLine();
|
|
26682
26995
|
clearPromptLineBeforeExternalWrite();
|
|
26683
|
-
process.stdout.write(`${normalizedText}\n`);
|
|
26996
|
+
process.stdout.write(`${normalizedText}${"\n".repeat(REPLYING_STATUS_PROMPT_GAP_LINES + 1)}`);
|
|
26684
26997
|
visibleReplyingStatusLine = normalizedText;
|
|
26685
26998
|
};
|
|
26686
26999
|
const syncReplyingStatusPresentation = () => {
|
|
26687
27000
|
const promptStatus = resolveReplyingPromptStatus();
|
|
26688
|
-
if (promptStatus)
|
|
26689
|
-
|
|
27001
|
+
if (promptStatus) {
|
|
27002
|
+
const decoratedReplyingStatus = `${REPLYING_STATUS_AGENT_LABEL} ${promptStatus}`;
|
|
27003
|
+
renderVisibleReplyingStatusLine(`${pc.dim(`* ${REPLYING_STATUS_SYSTEM_LABEL}`)} ${pc.dim("·")} ${pc.dim(decoratedReplyingStatus)}`);
|
|
27004
|
+
} else clearVisibleReplyingStatusLine();
|
|
26690
27005
|
refreshInputPrompt();
|
|
26691
27006
|
};
|
|
26692
27007
|
const scheduleReplyingStatusSweep = () => {
|
|
@@ -26782,21 +27097,48 @@ async function runConnect(input) {
|
|
|
26782
27097
|
if (!dequeueLocalSelfEchoIfHeadMatch(localSelfEchoQueue, typeof event.data?.text === "string" ? event.data.text : "")) return false;
|
|
26783
27098
|
return true;
|
|
26784
27099
|
};
|
|
26785
|
-
const
|
|
26786
|
-
if (
|
|
26787
|
-
|
|
26788
|
-
|
|
26789
|
-
|
|
26790
|
-
|
|
26791
|
-
|
|
27100
|
+
const resolveTextRenderEvent = (event) => {
|
|
27101
|
+
if (!isSignalEnvelope(event) || event.profile?.kind !== "agent") return event;
|
|
27102
|
+
const ownerUserId = String(event.profile.owner ?? "").trim();
|
|
27103
|
+
if (!ownerUserId) return event;
|
|
27104
|
+
const currentOwnerName = String(event.profile.ownerName ?? "").trim();
|
|
27105
|
+
if (currentOwnerName) rememberOwnerDisplayName(ownerUserId, currentOwnerName);
|
|
27106
|
+
const resolvedOwnerName = ownerDisplayNameByUserId.get(ownerUserId) ?? currentOwnerName;
|
|
27107
|
+
if (!(resolvedOwnerName && resolvedOwnerName !== currentOwnerName)) return event;
|
|
27108
|
+
return {
|
|
27109
|
+
...event,
|
|
27110
|
+
profile: {
|
|
27111
|
+
...event.profile,
|
|
27112
|
+
ownerName: resolvedOwnerName
|
|
26792
27113
|
}
|
|
27114
|
+
};
|
|
27115
|
+
};
|
|
27116
|
+
const emitTextGroupDivider = () => {
|
|
27117
|
+
const dividerLine = buildChatGroupDividerLine();
|
|
27118
|
+
if (!dividerLine) {
|
|
27119
|
+
process.stdout.write("\n");
|
|
27120
|
+
return;
|
|
27121
|
+
}
|
|
27122
|
+
const useTopGap = nextDividerGapPlacement === "top";
|
|
27123
|
+
const topGapLines = useTopGap ? CHAT_GROUP_DIVIDER_SIDE_GAP_LINES : 0;
|
|
27124
|
+
const bottomGapLines = useTopGap ? 0 : CHAT_GROUP_DIVIDER_SIDE_GAP_LINES;
|
|
27125
|
+
process.stdout.write(`${"\n".repeat(topGapLines)}${dividerLine}\n${"\n".repeat(bottomGapLines)}`);
|
|
27126
|
+
nextDividerGapPlacement = useTopGap ? "bottom" : "top";
|
|
27127
|
+
};
|
|
27128
|
+
const emitTextLine = (inputLine) => {
|
|
27129
|
+
if (runtime.resolvedView !== "human") {
|
|
26793
27130
|
process.stdout.write(`${inputLine.line}\n`);
|
|
26794
|
-
hasPrintedTextLine = true;
|
|
26795
|
-
lastPrintedTextLineGroupKey = inputLine.groupKey;
|
|
26796
|
-
if (inputLine.refreshPrompt !== false && rl) refreshInputPrompt();
|
|
26797
27131
|
return;
|
|
26798
27132
|
}
|
|
27133
|
+
clearPromptLineBeforeExternalWrite();
|
|
27134
|
+
if (hasPrintedTextLine && lastPrintedTextLineGroupKey !== inputLine.groupKey) emitTextGroupDivider();
|
|
26799
27135
|
process.stdout.write(`${inputLine.line}\n`);
|
|
27136
|
+
hasPrintedTextLine = true;
|
|
27137
|
+
lastPrintedTextLineGroupKey = inputLine.groupKey;
|
|
27138
|
+
if (inputLine.refreshPrompt !== false && rl) {
|
|
27139
|
+
process.stdout.write("\n");
|
|
27140
|
+
refreshInputPrompt();
|
|
27141
|
+
}
|
|
26800
27142
|
};
|
|
26801
27143
|
const clearSubmittedInputLine = () => {
|
|
26802
27144
|
if (!(shouldHideSubmittedInputLine && process.stdout.isTTY)) return;
|
|
@@ -26822,13 +27164,14 @@ async function runConnect(input) {
|
|
|
26822
27164
|
outJsonLine(event);
|
|
26823
27165
|
return;
|
|
26824
27166
|
}
|
|
26825
|
-
const
|
|
27167
|
+
const textRenderEvent = resolveTextRenderEvent(event);
|
|
27168
|
+
const line = formatTextEvent(textRenderEvent, {
|
|
26826
27169
|
view: runtime.resolvedView,
|
|
26827
27170
|
selfProfileId: profileIdentity.profileId
|
|
26828
27171
|
});
|
|
26829
27172
|
if (line) emitTextLine({
|
|
26830
27173
|
line,
|
|
26831
|
-
groupKey: resolveTextLineGroupKey(
|
|
27174
|
+
groupKey: resolveTextLineGroupKey(textRenderEvent)
|
|
26832
27175
|
});
|
|
26833
27176
|
};
|
|
26834
27177
|
const sessionMeta = resolveSessionMeta(space, spaceMeta, localSpaceConfig?.name, localSpaceConfig?.spaceCreatedAt);
|
|
@@ -27019,8 +27362,11 @@ async function runConnect(input) {
|
|
|
27019
27362
|
return;
|
|
27020
27363
|
}
|
|
27021
27364
|
if (output === "text") {
|
|
27022
|
-
|
|
27023
|
-
|
|
27365
|
+
if (runtime.resolvedView === "human") process.stdout.write("\n");
|
|
27366
|
+
const chatReadyText = formatMessage(runtime.resolvedView, "connect.chat_ready");
|
|
27367
|
+
const chatExitHintText = formatMessage(runtime.resolvedView, "connect.chat_exit_hint");
|
|
27368
|
+
process.stdout.write(`${pc.dim(chatReadyText)}\n`);
|
|
27369
|
+
process.stdout.write(`${pc.dim(chatExitHintText)}\n`);
|
|
27024
27370
|
if (runtime.resolvedView === "human") process.stdout.write("\n");
|
|
27025
27371
|
} else presenter.event("info", "connect.connected_stdio");
|
|
27026
27372
|
rl = createInterface({
|
|
@@ -27063,7 +27409,7 @@ async function runConnect(input) {
|
|
|
27063
27409
|
const shouldEmitLocalEcho = enableLocalEcho || shouldEnableFallbackLocalEchoForText(outboundText);
|
|
27064
27410
|
if (shouldEmitLocalEcho) {
|
|
27065
27411
|
enqueueLocalSelfEcho(localSelfEchoQueue, outboundText);
|
|
27066
|
-
const
|
|
27412
|
+
const localTextRenderEvent = resolveTextRenderEvent({
|
|
27067
27413
|
v: 1,
|
|
27068
27414
|
id: "local-self",
|
|
27069
27415
|
spaceId: space,
|
|
@@ -27075,14 +27421,14 @@ async function runConnect(input) {
|
|
|
27075
27421
|
},
|
|
27076
27422
|
type: "signal.text",
|
|
27077
27423
|
data: { text: outboundText }
|
|
27078
|
-
};
|
|
27079
|
-
const localLine = formatTextEvent(
|
|
27424
|
+
});
|
|
27425
|
+
const localLine = formatTextEvent(localTextRenderEvent, {
|
|
27080
27426
|
view: runtime.resolvedView,
|
|
27081
27427
|
selfProfileId: profileIdentity.profileId
|
|
27082
27428
|
});
|
|
27083
27429
|
if (localLine) emitTextLine({
|
|
27084
27430
|
line: localLine,
|
|
27085
|
-
groupKey: resolveTextLineGroupKey(
|
|
27431
|
+
groupKey: resolveTextLineGroupKey(localTextRenderEvent),
|
|
27086
27432
|
refreshPrompt: false
|
|
27087
27433
|
});
|
|
27088
27434
|
}
|
|
@@ -27885,7 +28231,7 @@ function buildChatGroupDividerLine() {
|
|
|
27885
28231
|
if (process.stdout.isTTY !== true) return "";
|
|
27886
28232
|
const width = clampNumber(resolveTerminalColumns() - CHAT_GROUP_DIVIDER_MARGIN * 2, CHAT_GROUP_DIVIDER_MIN_WIDTH, CHAT_GROUP_DIVIDER_MAX_WIDTH);
|
|
27887
28233
|
const margin = " ".repeat(CHAT_GROUP_DIVIDER_MARGIN);
|
|
27888
|
-
return pc.dim(`${margin}${CHAT_GROUP_DIVIDER_CHAR.repeat(width)}`);
|
|
28234
|
+
return pc.dim(pc.gray(`${margin}${CHAT_GROUP_DIVIDER_CHAR.repeat(width)}`));
|
|
27889
28235
|
}
|
|
27890
28236
|
function resolveTerminalColumns() {
|
|
27891
28237
|
const rawColumns = process.stdout.columns;
|
|
@@ -27907,8 +28253,17 @@ function resolveProfileGroupKey(profile, fallbackType) {
|
|
|
27907
28253
|
}
|
|
27908
28254
|
function isSystemRenderedSignalTextEvent(event) {
|
|
27909
28255
|
if (event.type !== "signal.text") return false;
|
|
28256
|
+
if (isSystemProfileForTextGrouping(event.profile)) return true;
|
|
27910
28257
|
const meta = resolveSignalTextMetaFromEnvelope(event);
|
|
27911
|
-
|
|
28258
|
+
if (String(meta?.purpose ?? "").trim() === SPACE_MEMBER_JOIN_NOTICE_META_PURPOSE$1) return true;
|
|
28259
|
+
const text = String(event.data?.text ?? "").trim();
|
|
28260
|
+
if (!text) return false;
|
|
28261
|
+
return ADD_MEMBERS_AGENT_NOTICE_REGEX.test(text);
|
|
28262
|
+
}
|
|
28263
|
+
function isSystemProfileForTextGrouping(profile) {
|
|
28264
|
+
if (String(profile?.kind ?? "").trim().toLowerCase() === "system") return true;
|
|
28265
|
+
if (String(profile?.profileId ?? "").trim().toLowerCase() === ATS_SYSTEM_PROFILE_ID) return true;
|
|
28266
|
+
return String(profile?.profileName ?? "").trim().toLowerCase() === ATS_SYSTEM_PROFILE_NAME;
|
|
27912
28267
|
}
|
|
27913
28268
|
function resolveReplyingHintEvent(event) {
|
|
27914
28269
|
if (event.type !== "signal.text") return null;
|
|
@@ -29849,7 +30204,9 @@ async function resolveSpaceAddMembersTarget(input) {
|
|
|
29849
30204
|
});
|
|
29850
30205
|
}
|
|
29851
30206
|
async function resolveSpaceMemberCandidatesForAdd(input) {
|
|
29852
|
-
const
|
|
30207
|
+
const profiles = await listAtsProfiles();
|
|
30208
|
+
const ownerProfileNameByOwnerUserId = buildOwnerProfileNameByOwnerUserId(profiles);
|
|
30209
|
+
const candidateProfiles = profiles.filter((profile) => profile.status === "active" && profile.atsProfileId !== input.currentProfileId).filter((profile) => typeof input.currentOwnerUserId !== "string" || input.currentOwnerUserId.trim().length === 0 || profile.ownerUserId === input.currentOwnerUserId);
|
|
29853
30210
|
if (candidateProfiles.length === 0) return [];
|
|
29854
30211
|
const membershipStatusMap = await resolveSpaceMemberMembershipStatusMap({
|
|
29855
30212
|
space: input.space,
|
|
@@ -29870,6 +30227,10 @@ async function resolveSpaceMemberCandidatesForAdd(input) {
|
|
|
29870
30227
|
profileId: profile.atsProfileId,
|
|
29871
30228
|
profileName: profile.profileName,
|
|
29872
30229
|
profileKind: profile.profileKind,
|
|
30230
|
+
ownerProfileName: ownerProfileNameByOwnerUserId.get(profile.ownerUserId),
|
|
30231
|
+
ownerUserId: profile.ownerUserId,
|
|
30232
|
+
ownerName: profile.ownerName,
|
|
30233
|
+
controllerRef: profile.controllerBinding?.controllerRef,
|
|
29873
30234
|
alreadyJoined,
|
|
29874
30235
|
needsActivation
|
|
29875
30236
|
};
|
|
@@ -29879,6 +30240,21 @@ async function resolveSpaceMemberCandidatesForAdd(input) {
|
|
|
29879
30240
|
return left.profileId.localeCompare(right.profileId);
|
|
29880
30241
|
});
|
|
29881
30242
|
}
|
|
30243
|
+
function buildOwnerProfileNameByOwnerUserId(profiles) {
|
|
30244
|
+
const ownerProfileMap = /* @__PURE__ */ new Map();
|
|
30245
|
+
const activeHumanProfiles = profiles.filter((profile) => profile.status === "active").filter((profile) => profile.profileKind === "human");
|
|
30246
|
+
for (const profile of activeHumanProfiles) {
|
|
30247
|
+
const ownerUserId = normalizeOptionalString$1(profile.ownerUserId);
|
|
30248
|
+
const profileName = normalizeOptionalString$1(profile.profileName);
|
|
30249
|
+
if (!(ownerUserId && profileName)) continue;
|
|
30250
|
+
if (!ownerProfileMap.has(ownerUserId)) {
|
|
30251
|
+
ownerProfileMap.set(ownerUserId, profileName);
|
|
30252
|
+
continue;
|
|
30253
|
+
}
|
|
30254
|
+
if (profile.isDefault === true) ownerProfileMap.set(ownerUserId, profileName);
|
|
30255
|
+
}
|
|
30256
|
+
return ownerProfileMap;
|
|
30257
|
+
}
|
|
29882
30258
|
async function resolveSpaceMemberMembershipStatusMap(input) {
|
|
29883
30259
|
const targetSpaceKey = normalizeKnownSpaceMergeKey(input.space);
|
|
29884
30260
|
const pairs = await Promise.all(input.candidates.map(async (candidate) => {
|
|
@@ -29952,7 +30328,10 @@ async function resolveSpaceAddMemberSelection(input) {
|
|
|
29952
30328
|
}
|
|
29953
30329
|
const selected = await multiselect({
|
|
29954
30330
|
message: "Choose profile(s) to add as members",
|
|
29955
|
-
options:
|
|
30331
|
+
options: sortSpaceMemberCandidatesForPrompt({
|
|
30332
|
+
candidates: input.candidates,
|
|
30333
|
+
initialSelectedProfileIds
|
|
30334
|
+
}).map((candidate) => ({
|
|
29956
30335
|
value: candidate.profileId,
|
|
29957
30336
|
label: formatSpaceMemberCandidateLabel(candidate),
|
|
29958
30337
|
hint: formatSpaceMemberCandidateHint(candidate),
|
|
@@ -29968,6 +30347,15 @@ async function resolveSpaceAddMemberSelection(input) {
|
|
|
29968
30347
|
const normalized = normalizeRequestedMemberIds(selected);
|
|
29969
30348
|
return normalized.length > 0 ? normalized : [];
|
|
29970
30349
|
}
|
|
30350
|
+
function sortSpaceMemberCandidatesForPrompt(input) {
|
|
30351
|
+
if (input.initialSelectedProfileIds.length === 0) return input.candidates;
|
|
30352
|
+
const selectedProfileIds = new Set(input.initialSelectedProfileIds);
|
|
30353
|
+
return [...input.candidates].sort((left, right) => {
|
|
30354
|
+
const leftSelected = selectedProfileIds.has(left.profileId);
|
|
30355
|
+
if (leftSelected === selectedProfileIds.has(right.profileId)) return 0;
|
|
30356
|
+
return leftSelected ? -1 : 1;
|
|
30357
|
+
});
|
|
30358
|
+
}
|
|
29971
30359
|
async function applySpaceAddMembers(input) {
|
|
29972
30360
|
const result = {
|
|
29973
30361
|
added: [],
|
|
@@ -30165,7 +30553,20 @@ async function postSpaceAddMemberJoinNotice(input) {
|
|
|
30165
30553
|
if (!response.ok) throw new Error(`HTTP ${response.status}: ${raw}`);
|
|
30166
30554
|
}
|
|
30167
30555
|
function buildSpaceAddMemberJoinNoticeText(member) {
|
|
30168
|
-
return `${member
|
|
30556
|
+
return `${resolveSpaceAddMemberNoticeProfileNameLabel(member)} (${resolveSpaceAddMemberNoticeProfileKindLabel(member)}) joined the space via add-members.`;
|
|
30557
|
+
}
|
|
30558
|
+
function resolveSpaceAddMemberNoticeProfileNameLabel(member) {
|
|
30559
|
+
if (member.profileKind !== "agent") return member.profileName;
|
|
30560
|
+
return `🤖 ${member.profileName}`;
|
|
30561
|
+
}
|
|
30562
|
+
function resolveSpaceAddMemberNoticeProfileKindLabel(member) {
|
|
30563
|
+
if (member.profileKind !== "agent") return member.profileKind;
|
|
30564
|
+
const detailSegments = [];
|
|
30565
|
+
const ownerDisplay = normalizeOptionalString$1(member.ownerProfileName) ?? normalizeOptionalString$1(member.ownerName) ?? normalizeOptionalString$1(member.ownerUserId);
|
|
30566
|
+
const controllerRef = normalizeOptionalString$1(member.controllerRef);
|
|
30567
|
+
if (controllerRef) detailSegments.push(`controller: ${controllerRef}`);
|
|
30568
|
+
if (ownerDisplay) detailSegments.push(`owner: ${ownerDisplay}`);
|
|
30569
|
+
return detailSegments.length > 0 ? `agent, ${detailSegments.join(", ")}` : "agent";
|
|
30169
30570
|
}
|
|
30170
30571
|
function formatSpaceMemberCandidateLabel(candidate) {
|
|
30171
30572
|
const label = `${candidate.profileName} (${candidate.profileKind}) · ${candidate.profileId}`;
|
|
@@ -32938,7 +33339,7 @@ function toErrorMessage$2(error) {
|
|
|
32938
33339
|
|
|
32939
33340
|
//#endregion
|
|
32940
33341
|
//#region src/commands/upgrade.ts
|
|
32941
|
-
const ATS_PACKAGE_NAME = "agent-transport-system";
|
|
33342
|
+
const ATS_PACKAGE_NAME$1 = "agent-transport-system";
|
|
32942
33343
|
async function runUpgrade(input) {
|
|
32943
33344
|
const runtime = await resolveRuntimeContext({ view: input.view });
|
|
32944
33345
|
const presenter = createPresenter(runtime);
|
|
@@ -33025,7 +33426,7 @@ async function runUpgrade(input) {
|
|
|
33025
33426
|
}
|
|
33026
33427
|
async function resolveUpgradeStatus(input) {
|
|
33027
33428
|
try {
|
|
33028
|
-
const latestVersion = await fetchLatestPackageVersion(ATS_PACKAGE_NAME);
|
|
33429
|
+
const latestVersion = await fetchLatestPackageVersion(ATS_PACKAGE_NAME$1);
|
|
33029
33430
|
return {
|
|
33030
33431
|
status: compareSemver(input.currentVersion, latestVersion) < 0 ? "outdated" : "up_to_date",
|
|
33031
33432
|
currentVersion: input.currentVersion,
|
|
@@ -33063,7 +33464,7 @@ async function installLatestPackage() {
|
|
|
33063
33464
|
const args = [
|
|
33064
33465
|
"install",
|
|
33065
33466
|
"-g",
|
|
33066
|
-
`${ATS_PACKAGE_NAME}@latest`
|
|
33467
|
+
`${ATS_PACKAGE_NAME$1}@latest`
|
|
33067
33468
|
];
|
|
33068
33469
|
const result = await new Promise((resolve, reject) => {
|
|
33069
33470
|
const child = spawn(command, args, {
|
|
@@ -33668,6 +34069,129 @@ function buildDaemonBootstrapRecommendationResult(input) {
|
|
|
33668
34069
|
return parsed.data;
|
|
33669
34070
|
}
|
|
33670
34071
|
|
|
34072
|
+
//#endregion
|
|
34073
|
+
//#region src/system/startup-upgrade-check.ts
|
|
34074
|
+
const ATS_PACKAGE_NAME = "agent-transport-system";
|
|
34075
|
+
const STARTUP_UPGRADE_SKIP_COMMANDS = new Set(["upgrade"]);
|
|
34076
|
+
const STARTUP_INTERNAL_SKIP_ENV = "ATS_INTERNAL_SKIP_STARTUP_UPGRADE";
|
|
34077
|
+
const STARTUP_AUTO_UPGRADE_ENV = "ATS_STARTUP_AUTO_UPGRADE";
|
|
34078
|
+
async function maybeRunStartupUpgradeCheck(input) {
|
|
34079
|
+
if (shouldSkipStartupUpgradeCheckByEnv()) return {
|
|
34080
|
+
upgraded: false,
|
|
34081
|
+
resolvedView: null,
|
|
34082
|
+
deprecatedWarning: null,
|
|
34083
|
+
trigger: null,
|
|
34084
|
+
latestVersion: null
|
|
34085
|
+
};
|
|
34086
|
+
const firstCommand = parseFirstCommandTokenFromArgv(input.argv);
|
|
34087
|
+
if (!firstCommand) return {
|
|
34088
|
+
upgraded: false,
|
|
34089
|
+
resolvedView: null,
|
|
34090
|
+
deprecatedWarning: null,
|
|
34091
|
+
trigger: null,
|
|
34092
|
+
latestVersion: null
|
|
34093
|
+
};
|
|
34094
|
+
if (!input.isKnownTopLevelCommand(firstCommand)) return {
|
|
34095
|
+
upgraded: false,
|
|
34096
|
+
resolvedView: null,
|
|
34097
|
+
deprecatedWarning: null,
|
|
34098
|
+
trigger: null,
|
|
34099
|
+
latestVersion: null
|
|
34100
|
+
};
|
|
34101
|
+
if (STARTUP_UPGRADE_SKIP_COMMANDS.has(firstCommand)) return {
|
|
34102
|
+
upgraded: false,
|
|
34103
|
+
resolvedView: null,
|
|
34104
|
+
deprecatedWarning: null,
|
|
34105
|
+
trigger: null,
|
|
34106
|
+
latestVersion: null
|
|
34107
|
+
};
|
|
34108
|
+
const explicitView = parseViewFromArgv(input.argv, { allowMissingValue: true });
|
|
34109
|
+
const runtime = await resolveRuntimeContext({ ...explicitView ? { view: explicitView } : {} });
|
|
34110
|
+
const currentVersion = version$1;
|
|
34111
|
+
const deprecatedWarning = await resolveDeprecatedVersionWarning({ currentVersion });
|
|
34112
|
+
if (!(runtime.resolvedView === "human" && runtime.interactive || isStartupAutoUpgradeEnabled())) return {
|
|
34113
|
+
upgraded: false,
|
|
34114
|
+
resolvedView: runtime.resolvedView,
|
|
34115
|
+
deprecatedWarning,
|
|
34116
|
+
trigger: null,
|
|
34117
|
+
latestVersion: null
|
|
34118
|
+
};
|
|
34119
|
+
const latestVersion = await fetchLatestPackageVersion(ATS_PACKAGE_NAME).catch(() => null);
|
|
34120
|
+
if (!latestVersion || compareSemver(currentVersion, latestVersion) >= 0) return {
|
|
34121
|
+
upgraded: false,
|
|
34122
|
+
resolvedView: runtime.resolvedView,
|
|
34123
|
+
deprecatedWarning,
|
|
34124
|
+
trigger: null,
|
|
34125
|
+
...latestVersion ? { latestVersion } : { latestVersion: null }
|
|
34126
|
+
};
|
|
34127
|
+
if (runtime.resolvedView === "human" && runtime.interactive) {
|
|
34128
|
+
const selected = await confirm({
|
|
34129
|
+
message: `Upgrade ATS CLI from ${currentVersion} to ${latestVersion}?`,
|
|
34130
|
+
initialValue: true
|
|
34131
|
+
});
|
|
34132
|
+
if (isCancel(selected) || !selected) return {
|
|
34133
|
+
upgraded: false,
|
|
34134
|
+
resolvedView: runtime.resolvedView,
|
|
34135
|
+
deprecatedWarning,
|
|
34136
|
+
trigger: null,
|
|
34137
|
+
latestVersion
|
|
34138
|
+
};
|
|
34139
|
+
await ensureVersionStateReady(currentVersion);
|
|
34140
|
+
await runUpgrade({
|
|
34141
|
+
yes: true,
|
|
34142
|
+
view: runtime.resolvedView
|
|
34143
|
+
});
|
|
34144
|
+
return {
|
|
34145
|
+
upgraded: true,
|
|
34146
|
+
resolvedView: runtime.resolvedView,
|
|
34147
|
+
deprecatedWarning,
|
|
34148
|
+
trigger: "interactive_prompt",
|
|
34149
|
+
latestVersion
|
|
34150
|
+
};
|
|
34151
|
+
}
|
|
34152
|
+
await ensureVersionStateReady(currentVersion);
|
|
34153
|
+
await runUpgrade({
|
|
34154
|
+
yes: true,
|
|
34155
|
+
view: runtime.resolvedView
|
|
34156
|
+
});
|
|
34157
|
+
return {
|
|
34158
|
+
upgraded: true,
|
|
34159
|
+
resolvedView: runtime.resolvedView,
|
|
34160
|
+
deprecatedWarning,
|
|
34161
|
+
trigger: "non_interactive_auto",
|
|
34162
|
+
latestVersion
|
|
34163
|
+
};
|
|
34164
|
+
}
|
|
34165
|
+
async function resolveDeprecatedVersionWarning(input) {
|
|
34166
|
+
try {
|
|
34167
|
+
const metadata = await fetchPackageVersionMetadata({
|
|
34168
|
+
packageName: ATS_PACKAGE_NAME,
|
|
34169
|
+
version: input.currentVersion
|
|
34170
|
+
});
|
|
34171
|
+
if (!metadata.deprecated) return null;
|
|
34172
|
+
return {
|
|
34173
|
+
currentVersion: input.currentVersion,
|
|
34174
|
+
deprecatedMessage: metadata.deprecated,
|
|
34175
|
+
upgradeCommand: "ats upgrade"
|
|
34176
|
+
};
|
|
34177
|
+
} catch {
|
|
34178
|
+
return null;
|
|
34179
|
+
}
|
|
34180
|
+
}
|
|
34181
|
+
async function ensureVersionStateReady(currentVersion) {
|
|
34182
|
+
if (await readVersionState()) return;
|
|
34183
|
+
await writeVersionState(createInitialVersionState({ currentVersion }));
|
|
34184
|
+
}
|
|
34185
|
+
function shouldSkipStartupUpgradeCheckByEnv() {
|
|
34186
|
+
return String(process.env[STARTUP_INTERNAL_SKIP_ENV] ?? "").trim() === "1";
|
|
34187
|
+
}
|
|
34188
|
+
function isStartupAutoUpgradeEnabled() {
|
|
34189
|
+
const raw = String(process.env[STARTUP_AUTO_UPGRADE_ENV] ?? "").trim();
|
|
34190
|
+
if (!raw) return true;
|
|
34191
|
+
const normalized = raw.toLowerCase();
|
|
34192
|
+
return !(normalized === "0" || normalized === "false" || normalized === "off" || normalized === "no");
|
|
34193
|
+
}
|
|
34194
|
+
|
|
33671
34195
|
//#endregion
|
|
33672
34196
|
//#region src/ats.ts
|
|
33673
34197
|
const program = new Command();
|
|
@@ -33687,6 +34211,7 @@ const CLI_NAME = resolveCliNameFromPackageJson(package_default);
|
|
|
33687
34211
|
const DEFAULT_AGENT_EXAMPLE_ID = resolveAgentExampleIds(1)[0] ?? "<agent-id>";
|
|
33688
34212
|
const DEFAULT_AGENT_EXAMPLE_PAIR = resolveAgentExampleIds(2).join(" ") || "<agent-a> <agent-b>";
|
|
33689
34213
|
const AGENT_VIEW_PROFILE_CONTROLLER_RULE_TEXT = "Agent-view profile rule: when creating/updating an agent profile, use the controller that matches your current runtime agent when supported (for example codex->codex, claude-code->claude-code). If your runtime agent is unsupported, continue as unbound (no controller) and explicitly tell the user.";
|
|
34214
|
+
const STARTUP_INTERNAL_SKIP_UPGRADE_ENV = "ATS_INTERNAL_SKIP_STARTUP_UPGRADE";
|
|
33690
34215
|
function resolveCliNameFromPackageJson(manifest) {
|
|
33691
34216
|
if (typeof manifest.bin === "string") {
|
|
33692
34217
|
const cliNameFromPath = parse(manifest.bin).name.trim();
|
|
@@ -34286,6 +34811,31 @@ async function main() {
|
|
|
34286
34811
|
try {
|
|
34287
34812
|
await ensureSystemLayout();
|
|
34288
34813
|
await runDaemonStartupCheck(argv);
|
|
34814
|
+
const startupUpgradeCheck = await maybeRunStartupUpgradeCheck({
|
|
34815
|
+
argv,
|
|
34816
|
+
isKnownTopLevelCommand
|
|
34817
|
+
}).catch(async (error) => {
|
|
34818
|
+
emitStartupAutoUpgradeFailed({
|
|
34819
|
+
view: await resolveEffectiveViewForOutput().catch(() => {
|
|
34820
|
+
return "human";
|
|
34821
|
+
}),
|
|
34822
|
+
errorMessage: toErrorMessage(error)
|
|
34823
|
+
});
|
|
34824
|
+
throw new Error(`startup auto-upgrade failed: ${toErrorMessage(error)}\nRun \`ats upgrade --yes\` and retry.`);
|
|
34825
|
+
});
|
|
34826
|
+
emitStartupDeprecatedVersionWarning({
|
|
34827
|
+
warning: startupUpgradeCheck.deprecatedWarning,
|
|
34828
|
+
view: startupUpgradeCheck.resolvedView
|
|
34829
|
+
});
|
|
34830
|
+
if (startupUpgradeCheck.upgraded) {
|
|
34831
|
+
await rerunCommandAfterStartupUpgrade({
|
|
34832
|
+
argv,
|
|
34833
|
+
trigger: startupUpgradeCheck.trigger,
|
|
34834
|
+
view: startupUpgradeCheck.resolvedView,
|
|
34835
|
+
latestVersion: startupUpgradeCheck.latestVersion
|
|
34836
|
+
});
|
|
34837
|
+
return;
|
|
34838
|
+
}
|
|
34289
34839
|
if (argv.length <= 2) {
|
|
34290
34840
|
printAtsBanner();
|
|
34291
34841
|
return;
|
|
@@ -34337,6 +34887,25 @@ async function main() {
|
|
|
34337
34887
|
process.exitCode = 1;
|
|
34338
34888
|
}
|
|
34339
34889
|
}
|
|
34890
|
+
function emitStartupDeprecatedVersionWarning(input) {
|
|
34891
|
+
if (!(input.warning && input.view)) return;
|
|
34892
|
+
if (input.view === "agent") {
|
|
34893
|
+
outJsonLine({
|
|
34894
|
+
type: "line",
|
|
34895
|
+
code: "upgrade.version_deprecated",
|
|
34896
|
+
text: "current ATS CLI version is deprecated; run `ats upgrade` immediately.",
|
|
34897
|
+
data: {
|
|
34898
|
+
severity: "warning",
|
|
34899
|
+
currentVersion: input.warning.currentVersion,
|
|
34900
|
+
deprecatedMessage: input.warning.deprecatedMessage,
|
|
34901
|
+
upgradeCommand: input.warning.upgradeCommand
|
|
34902
|
+
}
|
|
34903
|
+
});
|
|
34904
|
+
return;
|
|
34905
|
+
}
|
|
34906
|
+
outLine(`🚨 ATS CLI v${input.warning.currentVersion} is deprecated: ${input.warning.deprecatedMessage}`);
|
|
34907
|
+
outLine("⚠️ Please run `ats upgrade` immediately.");
|
|
34908
|
+
}
|
|
34340
34909
|
async function runDaemonStartupCheck(argv) {
|
|
34341
34910
|
const firstCommand = parseFirstCommandTokenFromArgv(argv);
|
|
34342
34911
|
if (firstCommand && !isKnownTopLevelCommand(firstCommand)) return;
|
|
@@ -34741,6 +35310,87 @@ function rerunCommandInHumanView(argv) {
|
|
|
34741
35310
|
});
|
|
34742
35311
|
});
|
|
34743
35312
|
}
|
|
35313
|
+
async function rerunCommandAfterStartupUpgrade(input) {
|
|
35314
|
+
const resolvedView = input.view ?? await resolveEffectiveViewForOutput();
|
|
35315
|
+
if (input.trigger === "non_interactive_auto") emitStartupAutoUpgradeApplied({
|
|
35316
|
+
latestVersion: input.latestVersion,
|
|
35317
|
+
view: resolvedView
|
|
35318
|
+
});
|
|
35319
|
+
emitStartupAutoUpgradeRerun({ view: resolvedView });
|
|
35320
|
+
const retryExitCode = await rerunCurrentCommandWithEnv({
|
|
35321
|
+
argv: input.argv,
|
|
35322
|
+
env: { [STARTUP_INTERNAL_SKIP_UPGRADE_ENV]: "1" }
|
|
35323
|
+
});
|
|
35324
|
+
process.exitCode = retryExitCode;
|
|
35325
|
+
}
|
|
35326
|
+
function emitStartupAutoUpgradeApplied(input) {
|
|
35327
|
+
if (input.view === "agent") {
|
|
35328
|
+
outJsonLine({
|
|
35329
|
+
type: "line",
|
|
35330
|
+
code: "upgrade.startup.auto_upgrade.applied",
|
|
35331
|
+
text: "startup auto-upgrade applied.",
|
|
35332
|
+
data: { ...input.latestVersion ? { latestVersion: input.latestVersion } : {} }
|
|
35333
|
+
});
|
|
35334
|
+
return;
|
|
35335
|
+
}
|
|
35336
|
+
if (input.latestVersion) {
|
|
35337
|
+
outLine(`Startup auto-upgrade applied. ATS CLI is now on v${input.latestVersion}.`);
|
|
35338
|
+
return;
|
|
35339
|
+
}
|
|
35340
|
+
outLine("Startup auto-upgrade applied.");
|
|
35341
|
+
}
|
|
35342
|
+
function emitStartupAutoUpgradeRerun(input) {
|
|
35343
|
+
if (input.view === "agent") {
|
|
35344
|
+
outJsonLine({
|
|
35345
|
+
type: "line",
|
|
35346
|
+
code: "upgrade.startup.auto_upgrade.rerun",
|
|
35347
|
+
text: "re-running original command after startup upgrade."
|
|
35348
|
+
});
|
|
35349
|
+
return;
|
|
35350
|
+
}
|
|
35351
|
+
outLine("Re-running your original command with the upgraded ATS CLI...");
|
|
35352
|
+
}
|
|
35353
|
+
function emitStartupAutoUpgradeFailed(input) {
|
|
35354
|
+
if (input.view === "agent") {
|
|
35355
|
+
outJsonLine({
|
|
35356
|
+
type: "line",
|
|
35357
|
+
code: "upgrade.startup.auto_upgrade.failed",
|
|
35358
|
+
text: "startup auto-upgrade failed; run `ats upgrade --yes`.",
|
|
35359
|
+
data: {
|
|
35360
|
+
errorMessage: input.errorMessage,
|
|
35361
|
+
recoveryCommand: "ats upgrade --yes"
|
|
35362
|
+
}
|
|
35363
|
+
});
|
|
35364
|
+
return;
|
|
35365
|
+
}
|
|
35366
|
+
outLine(`⚠️ Startup auto-upgrade failed: ${input.errorMessage}\nRun \`ats upgrade --yes\` and retry.`);
|
|
35367
|
+
}
|
|
35368
|
+
function rerunCurrentCommandWithEnv(input) {
|
|
35369
|
+
const scriptPath = input.argv[1];
|
|
35370
|
+
if (!scriptPath) return Promise.resolve(1);
|
|
35371
|
+
const runtimeSession = resolveRuntimeSession();
|
|
35372
|
+
const env = {
|
|
35373
|
+
...runtimeSession ? {
|
|
35374
|
+
...process.env,
|
|
35375
|
+
ATS_AGENT_SESSION_ID: runtimeSession.runtimeSessionId
|
|
35376
|
+
} : process.env,
|
|
35377
|
+
...input.env
|
|
35378
|
+
};
|
|
35379
|
+
return new Promise((resolve) => {
|
|
35380
|
+
const child = spawn(process.execPath, [scriptPath, ...input.argv.slice(2)], {
|
|
35381
|
+
stdio: "inherit",
|
|
35382
|
+
env
|
|
35383
|
+
});
|
|
35384
|
+
child.once("error", () => resolve(1));
|
|
35385
|
+
child.once("exit", (code, signal) => {
|
|
35386
|
+
if (signal) {
|
|
35387
|
+
resolve(1);
|
|
35388
|
+
return;
|
|
35389
|
+
}
|
|
35390
|
+
resolve(code ?? 0);
|
|
35391
|
+
});
|
|
35392
|
+
});
|
|
35393
|
+
}
|
|
34744
35394
|
function buildFallbackAgentViewGuideMessage(message) {
|
|
34745
35395
|
const normalizedMessage = normalizeErrorSummaryForGuide(message);
|
|
34746
35396
|
return buildDualViewGuideMessage({
|