@agent-team-foundation/first-tree-hub 0.8.2 → 0.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.mjs +105 -4
- package/dist/{core-CNR-lUlr.mjs → core-VW2Qfs73.mjs} +276 -143
- package/dist/{feishu-BOISS0DK.mjs → feishu-OezhDY7x.mjs} +56 -9
- package/dist/index.mjs +2 -2
- package/dist/web/assets/index-9xygtFGL.css +1 -0
- package/dist/web/assets/index-DpobwdHT.js +333 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-7iSpxOWW.js +0 -333
- package/dist/web/assets/index-Cze8BC63.css +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { m as __toESM } from "./esm-CYu4tXXn.mjs";
|
|
2
2
|
import { C as setConfigValue, S as serverConfigSchema, _ as loadAgents, d as DEFAULT_HOME_DIR$1, f as agentConfigSchema, g as initConfig, i as loadCredentials, l as DEFAULT_CONFIG_DIR, m as collectMissingPrompts, n as ensureFreshAccessToken, o as resolveServerUrl, p as clientConfigSchema, s as saveAgentConfig, u as DEFAULT_DATA_DIR$1, x as resolveConfigReadonly } from "./bootstrap-99vUYmLs.mjs";
|
|
3
3
|
import { _ as withSpan, a as endWsConnectionSpan, b as require_pino, c as messageAttrs, d as rootLogger$1, g as startWsConnectionSpan, i as currentTraceId, n as applyLoggerConfig, o as getFastifyOtelPlugin, p as setWsConnectionAttrs, r as createLogger, t as adapterAttrs, u as observabilityPlugin, v as withWsMessageSpan, y as FIRST_TREE_HUB_ATTR } from "./observability-CJzDFY_G-CmvgUuzc.mjs";
|
|
4
|
-
import { $ as updateAgentRuntimeConfigSchema, A as createMemberSchema, B as notificationQuerySchema, C as agentTypeSchema$1, D as createAdapterMappingSchema, E as createAdapterConfigSchema, F as inboxPollQuerySchema, G as sendMessageSchema, H as refreshTokenSchema, I as isRedactedEnvValue, J as sessionEventMessageSchema, K as sendToAgentSchema, L as linkTaskChatSchema, M as createTaskSchema, N as delegateFeishuUserSchema, O as createAgentSchema, P as dryRunAgentRuntimeConfigSchema, Q as updateAdapterConfigSchema, R as loginSchema, S as agentRuntimeConfigPayloadSchema$1, T as connectTokenExchangeSchema, U as runtimeStateMessageSchema, V as paginationQuerySchema, W as selfServiceFeishuBotSchema, X as sessionStateMessageSchema, Y as sessionEventSchema$1, Z as taskListQuerySchema, _ as addParticipantSchema, a as AGENT_SELECTOR_HEADER$1, at as wsAuthFrameSchema, b as agentBindRequestSchema, c as AGENT_TYPES, d as SYSTEM_CONFIG_DEFAULTS, et as updateAgentSchema, f as TASK_CREATOR_TYPES, g as WS_AUTH_FRAME_TIMEOUT_MS, h as TASK_TERMINAL_STATUSES, i as AGENT_BIND_REJECT_REASONS, it as updateTaskStatusSchema, j as createOrganizationSchema, k as createChatSchema, l as AGENT_VISIBILITY, m as TASK_STATUSES, nt as updateOrganizationSchema, o as AGENT_SOURCES, p as TASK_HEALTH_SIGNALS, q as sessionCompletionMessageSchema, rt as updateSystemConfigSchema, s as AGENT_STATUSES, tt as updateMemberSchema, u as DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD, v as adminCreateTaskSchema, w as clientRegisterSchema, x as agentPinnedMessageSchema$1, y as adminUpdateTaskSchema, z as messageSourceSchema$1 } from "./feishu-
|
|
4
|
+
import { $ as updateAgentRuntimeConfigSchema, A as createMemberSchema, B as notificationQuerySchema, C as agentTypeSchema$1, D as createAdapterMappingSchema, E as createAdapterConfigSchema, F as inboxPollQuerySchema, G as sendMessageSchema, H as refreshTokenSchema, I as isRedactedEnvValue, J as sessionEventMessageSchema, K as sendToAgentSchema, L as linkTaskChatSchema, M as createTaskSchema, N as delegateFeishuUserSchema, O as createAgentSchema, P as dryRunAgentRuntimeConfigSchema, Q as updateAdapterConfigSchema, R as loginSchema, S as agentRuntimeConfigPayloadSchema$1, T as connectTokenExchangeSchema, U as runtimeStateMessageSchema, V as paginationQuerySchema, W as selfServiceFeishuBotSchema, X as sessionStateMessageSchema, Y as sessionEventSchema$1, Z as taskListQuerySchema, _ as addParticipantSchema, a as AGENT_SELECTOR_HEADER$1, at as wsAuthFrameSchema, b as agentBindRequestSchema, c as AGENT_TYPES, d as SYSTEM_CONFIG_DEFAULTS, et as updateAgentSchema, f as TASK_CREATOR_TYPES, g as WS_AUTH_FRAME_TIMEOUT_MS, h as TASK_TERMINAL_STATUSES, i as AGENT_BIND_REJECT_REASONS, it as updateTaskStatusSchema, j as createOrganizationSchema, k as createChatSchema, l as AGENT_VISIBILITY, m as TASK_STATUSES, nt as updateOrganizationSchema, o as AGENT_SOURCES, p as TASK_HEALTH_SIGNALS, q as sessionCompletionMessageSchema, rt as updateSystemConfigSchema, s as AGENT_STATUSES, tt as updateMemberSchema, u as DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD, v as adminCreateTaskSchema, w as clientRegisterSchema, x as agentPinnedMessageSchema$1, y as adminUpdateTaskSchema, z as messageSourceSchema$1 } from "./feishu-OezhDY7x.mjs";
|
|
5
5
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
|
|
6
6
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
7
7
|
import { ZodError, z } from "zod";
|
|
@@ -12,7 +12,7 @@ import { homedir, hostname, platform, userInfo } from "node:os";
|
|
|
12
12
|
import { EventEmitter } from "node:events";
|
|
13
13
|
import WebSocket from "ws";
|
|
14
14
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
15
|
-
import { execFileSync, execSync, spawn } from "node:child_process";
|
|
15
|
+
import { execFileSync, execSync, spawn, spawnSync } from "node:child_process";
|
|
16
16
|
import bcrypt from "bcrypt";
|
|
17
17
|
import { and, asc, count, desc, eq, gt, inArray, isNotNull, isNull, lt, ne, or, sql } from "drizzle-orm";
|
|
18
18
|
import { drizzle } from "drizzle-orm/postgres-js";
|
|
@@ -714,7 +714,13 @@ z.object({
|
|
|
714
714
|
organizationId: z.string(),
|
|
715
715
|
agents: z.record(z.string(), z.array(pulseBucketSchema).length(32))
|
|
716
716
|
});
|
|
717
|
-
const sessionEventKind = z.enum([
|
|
717
|
+
const sessionEventKind = z.enum([
|
|
718
|
+
"tool_call",
|
|
719
|
+
"error",
|
|
720
|
+
"assistant_text",
|
|
721
|
+
"thinking",
|
|
722
|
+
"turn_end"
|
|
723
|
+
]);
|
|
718
724
|
const toolCallEventPayload = z.object({
|
|
719
725
|
toolUseId: z.string(),
|
|
720
726
|
name: z.string(),
|
|
@@ -735,20 +741,61 @@ const errorEventPayload = z.object({
|
|
|
735
741
|
]),
|
|
736
742
|
message: z.string().max(2e3)
|
|
737
743
|
});
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
744
|
+
/**
|
|
745
|
+
* A text block emitted by the model within an assistant message. These are
|
|
746
|
+
* transient "in-progress" events used to render the assistant's reply body
|
|
747
|
+
* while a turn is still running. The final turn result is forwarded as a
|
|
748
|
+
* regular chat message (not an event); the frontend hides all assistant_text
|
|
749
|
+
* events for turns that have completed (i.e. once `turn_end` has been emitted).
|
|
750
|
+
*/
|
|
751
|
+
const assistantTextEventPayload = z.object({ text: z.string().max(8e3) });
|
|
752
|
+
/**
|
|
753
|
+
* Marker emitted when the model produces a `thinking` content block.
|
|
754
|
+
* We intentionally do NOT persist the thinking content — only a presence
|
|
755
|
+
* signal so the UI can render a lightweight "Thinking…" status indicator.
|
|
756
|
+
*/
|
|
757
|
+
const thinkingEventPayload = z.object({});
|
|
758
|
+
/**
|
|
759
|
+
* Turn boundary marker. Emitted once per completed query turn, regardless of
|
|
760
|
+
* success/failure, so the frontend can group events into turns and collapse
|
|
761
|
+
* completed turns to show only the final result message.
|
|
762
|
+
*/
|
|
763
|
+
const turnEndEventPayload = z.object({ status: z.enum(["success", "error"]) });
|
|
764
|
+
const sessionEventSchema = z.discriminatedUnion("kind", [
|
|
765
|
+
z.object({
|
|
766
|
+
kind: z.literal("tool_call"),
|
|
767
|
+
payload: toolCallEventPayload
|
|
768
|
+
}),
|
|
769
|
+
z.object({
|
|
770
|
+
kind: z.literal("error"),
|
|
771
|
+
payload: errorEventPayload
|
|
772
|
+
}),
|
|
773
|
+
z.object({
|
|
774
|
+
kind: z.literal("assistant_text"),
|
|
775
|
+
payload: assistantTextEventPayload
|
|
776
|
+
}),
|
|
777
|
+
z.object({
|
|
778
|
+
kind: z.literal("thinking"),
|
|
779
|
+
payload: thinkingEventPayload
|
|
780
|
+
}),
|
|
781
|
+
z.object({
|
|
782
|
+
kind: z.literal("turn_end"),
|
|
783
|
+
payload: turnEndEventPayload
|
|
784
|
+
})
|
|
785
|
+
]);
|
|
745
786
|
z.object({
|
|
746
787
|
id: z.string(),
|
|
747
788
|
agentId: z.string(),
|
|
748
789
|
chatId: z.string(),
|
|
749
790
|
seq: z.number().int().positive(),
|
|
750
791
|
kind: sessionEventKind,
|
|
751
|
-
payload: z.union([
|
|
792
|
+
payload: z.union([
|
|
793
|
+
toolCallEventPayload,
|
|
794
|
+
errorEventPayload,
|
|
795
|
+
assistantTextEventPayload,
|
|
796
|
+
thinkingEventPayload,
|
|
797
|
+
turnEndEventPayload
|
|
798
|
+
]),
|
|
752
799
|
createdAt: z.string()
|
|
753
800
|
});
|
|
754
801
|
z.object({
|
|
@@ -2272,6 +2319,7 @@ function cleanWorkspaces(workspaceRoot, activeChatIds, ttlMs = DEFAULT_WORKSPACE
|
|
|
2272
2319
|
}
|
|
2273
2320
|
const MAX_RETRIES = 2;
|
|
2274
2321
|
const TOOL_RESULT_PREVIEW_LIMIT = 400;
|
|
2322
|
+
const ASSISTANT_TEXT_EVENT_LIMIT = 8e3;
|
|
2275
2323
|
function extractContentBlocks(message) {
|
|
2276
2324
|
if (!message || typeof message !== "object") return [];
|
|
2277
2325
|
const inner = message.message;
|
|
@@ -2289,6 +2337,15 @@ function isToolResultBlock(block) {
|
|
|
2289
2337
|
const b = block;
|
|
2290
2338
|
return b.type === "tool_result" && typeof b.tool_use_id === "string";
|
|
2291
2339
|
}
|
|
2340
|
+
function isTextBlock(block) {
|
|
2341
|
+
if (!block || typeof block !== "object") return false;
|
|
2342
|
+
const b = block;
|
|
2343
|
+
return b.type === "text" && typeof b.text === "string";
|
|
2344
|
+
}
|
|
2345
|
+
function isThinkingBlock(block) {
|
|
2346
|
+
if (!block || typeof block !== "object") return false;
|
|
2347
|
+
return block.type === "thinking";
|
|
2348
|
+
}
|
|
2292
2349
|
function isResultMessage(message) {
|
|
2293
2350
|
if (!message || typeof message !== "object") return false;
|
|
2294
2351
|
const m = message;
|
|
@@ -2331,31 +2388,39 @@ function createToolCallProcessor(emit) {
|
|
|
2331
2388
|
onMessage(message) {
|
|
2332
2389
|
if (!message || typeof message !== "object") return;
|
|
2333
2390
|
const type = message.type;
|
|
2334
|
-
if (type === "assistant")
|
|
2335
|
-
if (
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2391
|
+
if (type === "assistant") {
|
|
2392
|
+
for (const block of extractContentBlocks(message)) if (isToolUseBlock(block)) {
|
|
2393
|
+
pending.set(block.id, {
|
|
2394
|
+
toolUseId: block.id,
|
|
2395
|
+
name: block.name,
|
|
2396
|
+
args: block.input,
|
|
2397
|
+
startedAt: Date.now()
|
|
2398
|
+
});
|
|
2399
|
+
emit({
|
|
2400
|
+
kind: "tool_call",
|
|
2401
|
+
payload: {
|
|
2402
|
+
toolUseId: block.id,
|
|
2403
|
+
name: block.name,
|
|
2404
|
+
args: block.input,
|
|
2405
|
+
status: "pending"
|
|
2406
|
+
}
|
|
2407
|
+
});
|
|
2408
|
+
} else if (isTextBlock(block)) {
|
|
2409
|
+
const text = block.text.trim();
|
|
2410
|
+
if (text.length === 0) continue;
|
|
2411
|
+
emit({
|
|
2412
|
+
kind: "assistant_text",
|
|
2413
|
+
payload: { text: text.slice(0, ASSISTANT_TEXT_EVENT_LIMIT) }
|
|
2414
|
+
});
|
|
2415
|
+
} else if (isThinkingBlock(block)) emit({
|
|
2416
|
+
kind: "thinking",
|
|
2417
|
+
payload: {}
|
|
2341
2418
|
});
|
|
2342
|
-
}
|
|
2343
|
-
else if (type === "user") {
|
|
2419
|
+
} else if (type === "user") {
|
|
2344
2420
|
for (const block of extractContentBlocks(message)) if (isToolResultBlock(block)) pairResult(block);
|
|
2345
2421
|
}
|
|
2346
2422
|
},
|
|
2347
2423
|
flush() {
|
|
2348
|
-
if (pending.size === 0) return;
|
|
2349
|
-
for (const entry of pending.values()) emit({
|
|
2350
|
-
kind: "tool_call",
|
|
2351
|
-
payload: {
|
|
2352
|
-
toolUseId: entry.toolUseId,
|
|
2353
|
-
name: entry.name,
|
|
2354
|
-
args: entry.args,
|
|
2355
|
-
status: "pending",
|
|
2356
|
-
durationMs: Date.now() - entry.startedAt
|
|
2357
|
-
}
|
|
2358
|
-
});
|
|
2359
2424
|
pending.clear();
|
|
2360
2425
|
}
|
|
2361
2426
|
};
|
|
@@ -2561,25 +2626,37 @@ const createClaudeCodeHandler = (config) => {
|
|
|
2561
2626
|
retryCount = 0;
|
|
2562
2627
|
if (message.result && sessionCtx.chatId) {
|
|
2563
2628
|
const resultText = message.result;
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2629
|
+
try {
|
|
2630
|
+
await sessionCtx.sdk.sendMessage(sessionCtx.chatId, {
|
|
2631
|
+
format: "text",
|
|
2632
|
+
content: resultText
|
|
2633
|
+
});
|
|
2568
2634
|
sessionCtx.log("Result forwarded to chat");
|
|
2569
2635
|
sessionCtx.reportSessionCompletion();
|
|
2570
|
-
|
|
2636
|
+
sessionCtx.emitEvent({
|
|
2637
|
+
kind: "turn_end",
|
|
2638
|
+
payload: { status: "success" }
|
|
2639
|
+
});
|
|
2640
|
+
} catch (err) {
|
|
2571
2641
|
const reason = err instanceof Error ? err.message : String(err);
|
|
2572
2642
|
sessionCtx.log(`Failed to forward result: ${reason}`);
|
|
2573
|
-
const
|
|
2643
|
+
const forwardErrMessage = `Result forward failed: ${reason}\n---\n${resultText.slice(0, 1500)}`.slice(0, 2e3);
|
|
2574
2644
|
sessionCtx.emitEvent({
|
|
2575
2645
|
kind: "error",
|
|
2576
2646
|
payload: {
|
|
2577
2647
|
source: "runtime",
|
|
2578
|
-
message
|
|
2648
|
+
message: forwardErrMessage
|
|
2579
2649
|
}
|
|
2580
2650
|
});
|
|
2581
|
-
|
|
2582
|
-
|
|
2651
|
+
sessionCtx.emitEvent({
|
|
2652
|
+
kind: "turn_end",
|
|
2653
|
+
payload: { status: "error" }
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2656
|
+
} else sessionCtx.emitEvent({
|
|
2657
|
+
kind: "turn_end",
|
|
2658
|
+
payload: { status: "success" }
|
|
2659
|
+
});
|
|
2583
2660
|
} else {
|
|
2584
2661
|
const errors = message.errors ? message.errors.join("; ") : message.subtype;
|
|
2585
2662
|
const errorLog = `Query result error: ${errors} (subtype=${message.subtype}, turns=${message.num_turns ?? "?"}, duration=${message.duration_ms ?? "?"}ms)`;
|
|
@@ -2591,6 +2668,10 @@ const createClaudeCodeHandler = (config) => {
|
|
|
2591
2668
|
message: errors
|
|
2592
2669
|
}
|
|
2593
2670
|
});
|
|
2671
|
+
sessionCtx.emitEvent({
|
|
2672
|
+
kind: "turn_end",
|
|
2673
|
+
payload: { status: "error" }
|
|
2674
|
+
});
|
|
2594
2675
|
}
|
|
2595
2676
|
sessionCtx.setRuntimeState("idle");
|
|
2596
2677
|
}
|
|
@@ -4521,7 +4602,7 @@ async function onboardCreate(args) {
|
|
|
4521
4602
|
}
|
|
4522
4603
|
const runtimeAgent = args.type === "human" ? args.assistant : args.id;
|
|
4523
4604
|
if (args.feishuBotAppId && args.feishuBotAppSecret) {
|
|
4524
|
-
const { bindFeishuBot } = await import("./feishu-
|
|
4605
|
+
const { bindFeishuBot } = await import("./feishu-OezhDY7x.mjs").then((n) => n.r);
|
|
4525
4606
|
const targetAgentUuid = args.type === "human" ? assistantUuid : primary.uuid;
|
|
4526
4607
|
if (!targetAgentUuid) process.stderr.write(`Warning: Cannot bind Feishu bot — no runtime agent available for "${args.id}".\n`);
|
|
4527
4608
|
else {
|
|
@@ -4662,7 +4743,7 @@ function setNestedByDot(obj, dotPath, value) {
|
|
|
4662
4743
|
if (lastKey !== void 0) current[lastKey] = value;
|
|
4663
4744
|
}
|
|
4664
4745
|
//#endregion
|
|
4665
|
-
//#region ../server/dist/app-
|
|
4746
|
+
//#region ../server/dist/app-BGneEeZO.mjs
|
|
4666
4747
|
var __defProp = Object.defineProperty;
|
|
4667
4748
|
var __exportAll = (all, no_symbols) => {
|
|
4668
4749
|
let target = {};
|
|
@@ -7742,13 +7823,20 @@ async function appendEvent(db, agentId, chatId, event) {
|
|
|
7742
7823
|
throw new Error(`session_events seq contention on ${agentId}/${chatId}`);
|
|
7743
7824
|
}
|
|
7744
7825
|
/**
|
|
7745
|
-
* List events for a session
|
|
7746
|
-
*
|
|
7826
|
+
* List events for a session with cursor pagination.
|
|
7827
|
+
*
|
|
7828
|
+
* - `direction: "asc"` (default) walks oldest → newest; cursor is the last
|
|
7829
|
+
* seq seen on the previous page (next page starts at seq > cursor).
|
|
7830
|
+
* - `direction: "desc"` walks newest → oldest; cursor is the last seq seen
|
|
7831
|
+
* on the previous page (next page starts at seq < cursor). The chat UI
|
|
7832
|
+
* uses desc so its turn-grouping filter always sees the most recent
|
|
7833
|
+
* `turn_end` even when the chat has thousands of events.
|
|
7747
7834
|
*/
|
|
7748
7835
|
async function listEvents(db, agentId, chatId, options) {
|
|
7749
7836
|
const limit = Math.min(Math.max(options?.limit ?? DEFAULT_LIMIT, 1), MAX_LIMIT);
|
|
7837
|
+
const direction = options?.direction ?? "asc";
|
|
7750
7838
|
const conditions = [eq(sessionEvents.agentId, agentId), eq(sessionEvents.chatId, chatId)];
|
|
7751
|
-
if (options?.cursor !== void 0) conditions.push(gt(sessionEvents.seq, options.cursor));
|
|
7839
|
+
if (options?.cursor !== void 0) conditions.push(direction === "desc" ? lt(sessionEvents.seq, options.cursor) : gt(sessionEvents.seq, options.cursor));
|
|
7752
7840
|
const rows = await db.select({
|
|
7753
7841
|
id: sessionEvents.id,
|
|
7754
7842
|
agentId: sessionEvents.agentId,
|
|
@@ -7757,7 +7845,7 @@ async function listEvents(db, agentId, chatId, options) {
|
|
|
7757
7845
|
kind: sessionEvents.kind,
|
|
7758
7846
|
payload: sessionEvents.payload,
|
|
7759
7847
|
createdAt: sessionEvents.createdAt
|
|
7760
|
-
}).from(sessionEvents).where(and(...conditions)).orderBy(asc(sessionEvents.seq)).limit(limit + 1);
|
|
7848
|
+
}).from(sessionEvents).where(and(...conditions)).orderBy(direction === "desc" ? desc(sessionEvents.seq) : asc(sessionEvents.seq)).limit(limit + 1);
|
|
7761
7849
|
const hasMore = rows.length > limit;
|
|
7762
7850
|
const items = (hasMore ? rows.slice(0, limit) : rows).map(rowToEvent);
|
|
7763
7851
|
const last = items[items.length - 1];
|
|
@@ -7820,16 +7908,23 @@ async function adminSessionRoutes(app) {
|
|
|
7820
7908
|
await assertChatAccess(app.db, scope, request.params.chatId);
|
|
7821
7909
|
return getSession(app.db, request.params.agentId, request.params.chatId);
|
|
7822
7910
|
});
|
|
7823
|
-
/**
|
|
7911
|
+
/**
|
|
7912
|
+
* GET /admin/sessions/agents/:agentId/:chatId/events — session event stream,
|
|
7913
|
+
* paged by `seq`. `direction=desc` returns newest-first; the chat UI uses
|
|
7914
|
+
* this so its turn-grouping filter always sees the latest `turn_end`
|
|
7915
|
+
* regardless of total event count.
|
|
7916
|
+
*/
|
|
7824
7917
|
app.get("/agents/:agentId/:chatId/events", async (request) => {
|
|
7825
7918
|
const scope = memberScope(request);
|
|
7826
7919
|
await assertAgentVisible(app.db, scope, request.params.agentId);
|
|
7827
7920
|
await assertChatAccess(app.db, scope, request.params.chatId);
|
|
7828
7921
|
const limit = request.query.limit !== void 0 ? Number.parseInt(request.query.limit, 10) : void 0;
|
|
7829
7922
|
const cursor = request.query.cursor !== void 0 ? Number.parseInt(request.query.cursor, 10) : void 0;
|
|
7923
|
+
const direction = request.query.direction === "desc" ? "desc" : "asc";
|
|
7830
7924
|
return listEvents(app.db, request.params.agentId, request.params.chatId, {
|
|
7831
7925
|
limit: Number.isFinite(limit) ? limit : void 0,
|
|
7832
|
-
cursor: Number.isFinite(cursor) ? cursor : void 0
|
|
7926
|
+
cursor: Number.isFinite(cursor) ? cursor : void 0,
|
|
7927
|
+
direction
|
|
7833
7928
|
});
|
|
7834
7929
|
});
|
|
7835
7930
|
/** POST /admin/sessions/agents/:agentId/:chatId/suspend — suspend a session */
|
|
@@ -11796,6 +11891,32 @@ function resolveWebDist() {
|
|
|
11796
11891
|
}
|
|
11797
11892
|
//#endregion
|
|
11798
11893
|
//#region src/core/service-install.ts
|
|
11894
|
+
/**
|
|
11895
|
+
* Run a subprocess capturing stderr so failures surface a meaningful error
|
|
11896
|
+
* instead of Node's opaque "Command failed". Used for launchctl/systemctl —
|
|
11897
|
+
* anywhere the stderr message is diagnostically crucial.
|
|
11898
|
+
*/
|
|
11899
|
+
function runCapture(program, args, timeoutMs) {
|
|
11900
|
+
const res = spawnSync(program, args, {
|
|
11901
|
+
encoding: "utf-8",
|
|
11902
|
+
timeout: timeoutMs,
|
|
11903
|
+
stdio: [
|
|
11904
|
+
"ignore",
|
|
11905
|
+
"pipe",
|
|
11906
|
+
"pipe"
|
|
11907
|
+
]
|
|
11908
|
+
});
|
|
11909
|
+
if (res.status === 0) return { ok: true };
|
|
11910
|
+
return {
|
|
11911
|
+
ok: false,
|
|
11912
|
+
stderr: (res.stderr ?? "").trim(),
|
|
11913
|
+
code: res.status
|
|
11914
|
+
};
|
|
11915
|
+
}
|
|
11916
|
+
function sleepSync(ms) {
|
|
11917
|
+
const shared = new Int32Array(new SharedArrayBuffer(4));
|
|
11918
|
+
Atomics.wait(shared, 0, 0, ms);
|
|
11919
|
+
}
|
|
11799
11920
|
const LAUNCHD_LABEL = "dev.first-tree-hub.client";
|
|
11800
11921
|
const SYSTEMD_UNIT = "first-tree-hub-client.service";
|
|
11801
11922
|
const LOG_DIR = join(DEFAULT_HOME_DIR$1, "logs");
|
|
@@ -11903,30 +12024,56 @@ function launchctlDomainTarget() {
|
|
|
11903
12024
|
}
|
|
11904
12025
|
function launchdState() {
|
|
11905
12026
|
if (!existsSync(launchdPlistPath())) return { state: "not-installed" };
|
|
11906
|
-
|
|
11907
|
-
|
|
11908
|
-
|
|
11909
|
-
|
|
11910
|
-
|
|
11911
|
-
|
|
11912
|
-
|
|
11913
|
-
|
|
11914
|
-
|
|
11915
|
-
|
|
11916
|
-
|
|
11917
|
-
|
|
11918
|
-
|
|
11919
|
-
|
|
11920
|
-
|
|
11921
|
-
|
|
11922
|
-
|
|
11923
|
-
|
|
11924
|
-
} catch {
|
|
12027
|
+
const res = spawnSync("launchctl", ["print", `${launchctlDomainTarget()}/${LAUNCHD_LABEL}`], {
|
|
12028
|
+
encoding: "utf-8",
|
|
12029
|
+
timeout: 5e3,
|
|
12030
|
+
stdio: [
|
|
12031
|
+
"ignore",
|
|
12032
|
+
"pipe",
|
|
12033
|
+
"pipe"
|
|
12034
|
+
]
|
|
12035
|
+
});
|
|
12036
|
+
if (res.status !== 0) return {
|
|
12037
|
+
state: "inactive",
|
|
12038
|
+
detail: "plist present but not loaded"
|
|
12039
|
+
};
|
|
12040
|
+
const out = res.stdout ?? "";
|
|
12041
|
+
const stateLine = out.split(/\r?\n/).find((l) => l.trim().startsWith("state ="));
|
|
12042
|
+
const pidLine = out.split(/\r?\n/).find((l) => l.trim().startsWith("pid ="));
|
|
12043
|
+
if (stateLine?.includes("running")) {
|
|
12044
|
+
const pid = pidLine?.split("=")[1]?.trim();
|
|
11925
12045
|
return {
|
|
11926
|
-
state: "
|
|
11927
|
-
detail:
|
|
12046
|
+
state: "active",
|
|
12047
|
+
detail: pid ? `pid ${pid}` : "running"
|
|
11928
12048
|
};
|
|
11929
12049
|
}
|
|
12050
|
+
return {
|
|
12051
|
+
state: "inactive",
|
|
12052
|
+
detail: stateLine?.trim() ?? "loaded"
|
|
12053
|
+
};
|
|
12054
|
+
}
|
|
12055
|
+
/**
|
|
12056
|
+
* Poll `launchctl print` until the label disappears, confirming launchd has
|
|
12057
|
+
* finished the async eviction kicked off by `bootout`. Required because
|
|
12058
|
+
* `bootout` returns before the actual unload completes when the service has
|
|
12059
|
+
* active WebSocket connections — a follow-up `bootstrap` against a still-
|
|
12060
|
+
* registered label fails with `Bootstrap failed: 5: Input/output error`.
|
|
12061
|
+
*/
|
|
12062
|
+
function waitForLabelEvicted(target, label, timeoutMs) {
|
|
12063
|
+
const deadline = Date.now() + timeoutMs;
|
|
12064
|
+
while (Date.now() < deadline) {
|
|
12065
|
+
if (spawnSync("launchctl", ["print", `${target}/${label}`], {
|
|
12066
|
+
encoding: "utf-8",
|
|
12067
|
+
timeout: 2e3,
|
|
12068
|
+
stdio: [
|
|
12069
|
+
"ignore",
|
|
12070
|
+
"ignore",
|
|
12071
|
+
"pipe"
|
|
12072
|
+
]
|
|
12073
|
+
}).status !== 0) return true;
|
|
12074
|
+
sleepSync(200);
|
|
12075
|
+
}
|
|
12076
|
+
return false;
|
|
11930
12077
|
}
|
|
11931
12078
|
function installLaunchd() {
|
|
11932
12079
|
const invocation = resolveCliInvocation();
|
|
@@ -11935,24 +12082,28 @@ function installLaunchd() {
|
|
|
11935
12082
|
mkdirSync(dirname(plistPath), { recursive: true });
|
|
11936
12083
|
writeFileSync(plistPath, renderPlist(invocation), { mode: 420 });
|
|
11937
12084
|
const target = launchctlDomainTarget();
|
|
11938
|
-
|
|
11939
|
-
|
|
11940
|
-
|
|
11941
|
-
|
|
11942
|
-
|
|
11943
|
-
|
|
11944
|
-
|
|
11945
|
-
"
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
|
|
11954
|
-
|
|
11955
|
-
|
|
12085
|
+
const bootoutRes = runCapture("launchctl", ["bootout", `${target}/${LAUNCHD_LABEL}`], 15e3);
|
|
12086
|
+
if (!bootoutRes.ok) {
|
|
12087
|
+
if (!/not find|no such|not loaded/i.test(bootoutRes.stderr)) process.stderr.write(` warning: launchctl bootout: ${bootoutRes.stderr || `exit ${bootoutRes.code ?? "unknown"}`}\n`);
|
|
12088
|
+
}
|
|
12089
|
+
waitForLabelEvicted(target, LAUNCHD_LABEL, 1e4);
|
|
12090
|
+
let lastBootstrapErr = null;
|
|
12091
|
+
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
12092
|
+
const res = runCapture("launchctl", [
|
|
12093
|
+
"bootstrap",
|
|
12094
|
+
target,
|
|
12095
|
+
plistPath
|
|
12096
|
+
], 1e4);
|
|
12097
|
+
if (res.ok) {
|
|
12098
|
+
lastBootstrapErr = null;
|
|
12099
|
+
break;
|
|
12100
|
+
}
|
|
12101
|
+
lastBootstrapErr = res;
|
|
12102
|
+
if (attempt < 2) sleepSync(1e3);
|
|
12103
|
+
}
|
|
12104
|
+
if (lastBootstrapErr) throw new Error(`launchctl bootstrap failed: ${lastBootstrapErr.stderr || `exit ${lastBootstrapErr.code ?? "unknown"}`}\n Command: launchctl bootstrap ${target} ${plistPath}\n Recovery: \`launchctl bootout ${target}/${LAUNCHD_LABEL}\` then \`first-tree-hub service install\`.`);
|
|
12105
|
+
const enableRes = runCapture("launchctl", ["enable", `${target}/${LAUNCHD_LABEL}`], 5e3);
|
|
12106
|
+
if (!enableRes.ok) process.stderr.write(` warning: launchctl enable: ${enableRes.stderr || `exit ${enableRes.code ?? "unknown"}`}\n`);
|
|
11956
12107
|
const { state, detail } = launchdState();
|
|
11957
12108
|
return {
|
|
11958
12109
|
platform: "launchd",
|
|
@@ -11965,13 +12116,8 @@ function installLaunchd() {
|
|
|
11965
12116
|
}
|
|
11966
12117
|
function uninstallLaunchd() {
|
|
11967
12118
|
const plistPath = launchdPlistPath();
|
|
11968
|
-
const
|
|
11969
|
-
|
|
11970
|
-
execFileSync("launchctl", ["bootout", `${target}/${LAUNCHD_LABEL}`], {
|
|
11971
|
-
stdio: "ignore",
|
|
11972
|
-
timeout: 5e3
|
|
11973
|
-
});
|
|
11974
|
-
} catch {}
|
|
12119
|
+
const res = runCapture("launchctl", ["bootout", `${launchctlDomainTarget()}/${LAUNCHD_LABEL}`], 15e3);
|
|
12120
|
+
if (!res.ok && !/not find|no such|not loaded/i.test(res.stderr)) process.stderr.write(` warning: bootout during uninstall: ${res.stderr || `exit ${res.code ?? "unknown"}`}\n`);
|
|
11975
12121
|
if (existsSync(plistPath)) rmSync(plistPath);
|
|
11976
12122
|
return {
|
|
11977
12123
|
platform: "launchd",
|
|
@@ -12009,29 +12155,28 @@ function shellQuote(value) {
|
|
|
12009
12155
|
}
|
|
12010
12156
|
function systemdState() {
|
|
12011
12157
|
if (!existsSync(systemdUnitPath())) return { state: "not-installed" };
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12016
|
-
|
|
12017
|
-
|
|
12018
|
-
|
|
12019
|
-
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
12028
|
-
|
|
12029
|
-
}
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
|
|
12034
|
-
}
|
|
12158
|
+
const res = spawnSync("systemctl", [
|
|
12159
|
+
"--user",
|
|
12160
|
+
"is-active",
|
|
12161
|
+
SYSTEMD_UNIT
|
|
12162
|
+
], {
|
|
12163
|
+
encoding: "utf-8",
|
|
12164
|
+
timeout: 5e3,
|
|
12165
|
+
stdio: [
|
|
12166
|
+
"ignore",
|
|
12167
|
+
"pipe",
|
|
12168
|
+
"pipe"
|
|
12169
|
+
]
|
|
12170
|
+
});
|
|
12171
|
+
const out = (res.stdout ?? "").trim();
|
|
12172
|
+
if (res.status === 0 && out === "active") return {
|
|
12173
|
+
state: "active",
|
|
12174
|
+
detail: "running"
|
|
12175
|
+
};
|
|
12176
|
+
return {
|
|
12177
|
+
state: "inactive",
|
|
12178
|
+
detail: out || "unit present but not active"
|
|
12179
|
+
};
|
|
12035
12180
|
}
|
|
12036
12181
|
function installSystemd() {
|
|
12037
12182
|
const invocation = resolveCliInvocation();
|
|
@@ -12039,19 +12184,15 @@ function installSystemd() {
|
|
|
12039
12184
|
const unitPath = systemdUnitPath();
|
|
12040
12185
|
mkdirSync(dirname(unitPath), { recursive: true });
|
|
12041
12186
|
writeFileSync(unitPath, renderSystemdUnit(invocation), { mode: 420 });
|
|
12042
|
-
|
|
12043
|
-
|
|
12044
|
-
|
|
12045
|
-
});
|
|
12046
|
-
execFileSync("systemctl", [
|
|
12187
|
+
const reloadRes = runCapture("systemctl", ["--user", "daemon-reload"], 5e3);
|
|
12188
|
+
if (!reloadRes.ok) throw new Error(`systemctl --user daemon-reload failed: ${reloadRes.stderr || `exit ${reloadRes.code ?? "unknown"}`}`);
|
|
12189
|
+
const enableRes = runCapture("systemctl", [
|
|
12047
12190
|
"--user",
|
|
12048
12191
|
"enable",
|
|
12049
12192
|
"--now",
|
|
12050
12193
|
SYSTEMD_UNIT
|
|
12051
|
-
],
|
|
12052
|
-
|
|
12053
|
-
timeout: 1e4
|
|
12054
|
-
});
|
|
12194
|
+
], 1e4);
|
|
12195
|
+
if (!enableRes.ok) throw new Error(`systemctl --user enable --now ${SYSTEMD_UNIT} failed: ${enableRes.stderr || `exit ${enableRes.code ?? "unknown"}`}\n Recovery: \`systemctl --user stop ${SYSTEMD_UNIT}\` then \`first-tree-hub service install\`.`);
|
|
12055
12196
|
const { state, detail } = systemdState();
|
|
12056
12197
|
return {
|
|
12057
12198
|
platform: "systemd",
|
|
@@ -12064,24 +12205,16 @@ function installSystemd() {
|
|
|
12064
12205
|
}
|
|
12065
12206
|
function uninstallSystemd() {
|
|
12066
12207
|
const unitPath = systemdUnitPath();
|
|
12067
|
-
|
|
12068
|
-
|
|
12069
|
-
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
12073
|
-
|
|
12074
|
-
stdio: "ignore",
|
|
12075
|
-
timeout: 1e4
|
|
12076
|
-
});
|
|
12077
|
-
} catch {}
|
|
12208
|
+
const disableRes = runCapture("systemctl", [
|
|
12209
|
+
"--user",
|
|
12210
|
+
"disable",
|
|
12211
|
+
"--now",
|
|
12212
|
+
SYSTEMD_UNIT
|
|
12213
|
+
], 1e4);
|
|
12214
|
+
if (!disableRes.ok && !/not found|no such|not loaded/i.test(disableRes.stderr)) process.stderr.write(` warning: systemctl disable during uninstall: ${disableRes.stderr || `exit ${disableRes.code ?? "unknown"}`}\n`);
|
|
12078
12215
|
if (existsSync(unitPath)) rmSync(unitPath);
|
|
12079
|
-
|
|
12080
|
-
|
|
12081
|
-
stdio: "ignore",
|
|
12082
|
-
timeout: 5e3
|
|
12083
|
-
});
|
|
12084
|
-
} catch {}
|
|
12216
|
+
const reloadRes = runCapture("systemctl", ["--user", "daemon-reload"], 5e3);
|
|
12217
|
+
if (!reloadRes.ok) process.stderr.write(` warning: systemctl daemon-reload during uninstall: ${reloadRes.stderr || `exit ${reloadRes.code ?? "unknown"}`}\n`);
|
|
12085
12218
|
return {
|
|
12086
12219
|
platform: "systemd",
|
|
12087
12220
|
label: SYSTEMD_UNIT,
|