@agent-team-foundation/first-tree-hub 0.14.6 → 0.14.7-alpha.280.1
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
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as SdkError, A as printResults, B as ClientRuntime, C as migrateLocalAgentDirs, D as checkNodeVersion, E as checkClientConfig, G as removeLocalAgent, I as restartClientService, J as fail, K as resolveReplyToFromEnv, L as startClientService, M as getClientServiceStatus, N as installClientService, O as checkServerReachable, P as isServiceSupported, Q as FirstTreeHubSDK, R as stopClientService, S as createApiNameResolver, T as checkBackgroundService, U as findStaleAliases, V as handleClientOrgMismatch, W as formatStaleReason, X as ClientOrgMismatchError, Y as success, Z as ClientUserMismatchError, _ as loadOnboardState, a as declineUpdate, b as saveOnboardState, c as detectInstallMode, d as reconcileLocalRuntimeProviders, et as SessionRegistry, f as uploadClientCapabilities, g as formatCheckReport, h as promptMissingFields, i as createExecuteUpdate, it as configureClientLoggerForService, j as reconcileAgentConfigs, k as checkWebSocket, l as fetchLatestVersion, m as promptAddAgent, nt as probeCapabilities, o as promptUpdate, p as isInteractive, q as resolveSenderName, r as registerSaaSConnectCommand, rt as applyClientLoggerConfig, s as PACKAGE_NAME, tt as cleanWorkspaces, u as installGlobalLatest, v as onboardCheck, w as checkAgentConfigs, x as runHomeMigration, y as onboardCreate } from "../saas-connect-
|
|
2
|
+
import { $ as SdkError, A as printResults, B as ClientRuntime, C as migrateLocalAgentDirs, D as checkNodeVersion, E as checkClientConfig, G as removeLocalAgent, I as restartClientService, J as fail, K as resolveReplyToFromEnv, L as startClientService, M as getClientServiceStatus, N as installClientService, O as checkServerReachable, P as isServiceSupported, Q as FirstTreeHubSDK, R as stopClientService, S as createApiNameResolver, T as checkBackgroundService, U as findStaleAliases, V as handleClientOrgMismatch, W as formatStaleReason, X as ClientOrgMismatchError, Y as success, Z as ClientUserMismatchError, _ as loadOnboardState, a as declineUpdate, b as saveOnboardState, c as detectInstallMode, d as reconcileLocalRuntimeProviders, et as SessionRegistry, f as uploadClientCapabilities, g as formatCheckReport, h as promptMissingFields, i as createExecuteUpdate, it as configureClientLoggerForService, j as reconcileAgentConfigs, k as checkWebSocket, l as fetchLatestVersion, m as promptAddAgent, nt as probeCapabilities, o as promptUpdate, p as isInteractive, q as resolveSenderName, r as registerSaaSConnectCommand, rt as applyClientLoggerConfig, s as PACKAGE_NAME, tt as cleanWorkspaces, u as installGlobalLatest, v as onboardCheck, w as checkAgentConfigs, x as runHomeMigration, y as onboardCreate } from "../saas-connect-gXv14p6J.mjs";
|
|
3
3
|
import { C as resolveConfigReadonly, S as resetConfigMeta, _ as initConfig, a as loadCredentials, b as readConfigFile, c as saveAgentConfig, d as DEFAULT_DATA_DIR, f as DEFAULT_HOME_DIR, g as getConfigValue, i as ensureFreshAdminToken, m as clientConfigSchema, p as agentConfigSchema, r as ensureFreshAccessToken, s as resolveServerUrl, u as DEFAULT_CONFIG_DIR, v as loadAgents, w as setConfigValue, x as resetConfig } from "../bootstrap-CmkHQsnS.mjs";
|
|
4
4
|
import { a as print, n as CLI_USER_AGENT, o as setJsonMode, r as COMMAND_VERSION, t as cliFetch } from "../cli-fetch-BGVItZxo.mjs";
|
|
5
|
-
import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-
|
|
5
|
+
import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-BBUEVTnb.mjs";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
|
|
8
8
|
import * as semver from "semver";
|
|
@@ -545,10 +545,7 @@ const chatMetadataSchema = z.discriminatedUnion("source", [githubChatMetadataSch
|
|
|
545
545
|
const optionalChatMetadataSchema = z.union([z.object({}).strict(), chatMetadataSchema]);
|
|
546
546
|
const chatSourceSchema = z.enum([
|
|
547
547
|
"manual",
|
|
548
|
-
"
|
|
549
|
-
"github_pull_request",
|
|
550
|
-
"github_discussion",
|
|
551
|
-
"github_commit",
|
|
548
|
+
"github",
|
|
552
549
|
"feishu"
|
|
553
550
|
]);
|
|
554
551
|
const chatTypeSchema = z.enum(["direct", "group"]);
|
|
@@ -577,7 +574,9 @@ const chatParticipantDetailSchema = z.object({
|
|
|
577
574
|
}).extend({
|
|
578
575
|
name: z.string().nullable(),
|
|
579
576
|
displayName: z.string(),
|
|
580
|
-
type: z.string()
|
|
577
|
+
type: z.string(),
|
|
578
|
+
avatarColorToken: z.string().nullable(),
|
|
579
|
+
avatarImageUrl: z.string().nullable()
|
|
581
580
|
});
|
|
582
581
|
z.object({
|
|
583
582
|
id: z.string(),
|
|
@@ -598,6 +597,47 @@ z.object({
|
|
|
598
597
|
z.object({ topic: z.string().trim().max(500).nullable() });
|
|
599
598
|
z.object({ agentId: z.string().min(1) });
|
|
600
599
|
z.object({ agentId: z.string().min(1) });
|
|
600
|
+
/**
|
|
601
|
+
* Schema for `GET /api/v1/chats/:chatId/github-entities`.
|
|
602
|
+
*
|
|
603
|
+
* Each item describes one entity bound to the chat via
|
|
604
|
+
* `github_entity_chat_mappings`. Live state (`title`, `state`) is fetched
|
|
605
|
+
* per-request from the GitHub REST API at request time and NEVER
|
|
606
|
+
* persisted to the database — the user explicitly opted for the
|
|
607
|
+
* fresh-on-every-open model rather than a webhook-synced column.
|
|
608
|
+
*
|
|
609
|
+
* Live fields may be `null` when the GitHub call failed (token unavailable,
|
|
610
|
+
* rate-limited, 404 on a deleted entity, etc.). The client falls back to
|
|
611
|
+
* rendering `entityKey + htmlUrl` so the row is still a working link.
|
|
612
|
+
*/
|
|
613
|
+
const githubEntityBoundViaSchema = z.enum([
|
|
614
|
+
"direct",
|
|
615
|
+
"fixes_link",
|
|
616
|
+
"agent_created"
|
|
617
|
+
]);
|
|
618
|
+
/**
|
|
619
|
+
* Coarse live state. PR-specific values (`merged`, `draft`) are folded
|
|
620
|
+
* into the same enum as the issue's `open` / `closed` so the UI can
|
|
621
|
+
* switch on a single field. `null` means we couldn't reach GitHub for
|
|
622
|
+
* this entity in this request — the row still renders, just without
|
|
623
|
+
* the state badge.
|
|
624
|
+
*/
|
|
625
|
+
const githubEntityLiveStateSchema = z.enum([
|
|
626
|
+
"open",
|
|
627
|
+
"closed",
|
|
628
|
+
"merged",
|
|
629
|
+
"draft"
|
|
630
|
+
]);
|
|
631
|
+
const chatGithubEntitySchema = z.object({
|
|
632
|
+
entityType: githubEntityTypeSchema,
|
|
633
|
+
entityKey: z.string().min(1),
|
|
634
|
+
boundVia: githubEntityBoundViaSchema,
|
|
635
|
+
htmlUrl: z.string().url(),
|
|
636
|
+
title: z.string().nullable(),
|
|
637
|
+
state: githubEntityLiveStateSchema.nullable(),
|
|
638
|
+
number: z.number().int().nullable()
|
|
639
|
+
});
|
|
640
|
+
z.object({ items: z.array(chatGithubEntitySchema) });
|
|
601
641
|
const clientStatusSchema = z.enum(["connected", "disconnected"]);
|
|
602
642
|
/**
|
|
603
643
|
* Auth health channel surfaced to the Web admin dashboard. Computed
|
|
@@ -771,10 +811,20 @@ const contextTreeSummarySchema = z.object({
|
|
|
771
811
|
removedCount: z.number().int().nonnegative(),
|
|
772
812
|
changedNodeCount: z.number().int().nonnegative()
|
|
773
813
|
});
|
|
814
|
+
const contextTreeUsageEventSchema = z.object({
|
|
815
|
+
id: z.string(),
|
|
816
|
+
agentId: z.string(),
|
|
817
|
+
agentName: z.string(),
|
|
818
|
+
agentAvatarColorToken: z.string().nullable(),
|
|
819
|
+
chatId: z.string().nullable(),
|
|
820
|
+
chatTitle: z.string().nullable(),
|
|
821
|
+
createdAt: z.string()
|
|
822
|
+
});
|
|
774
823
|
const contextTreeUsageSummarySchema = z.object({
|
|
775
824
|
windowDays: z.number().int().positive(),
|
|
776
825
|
agentCount: z.number().int().nonnegative(),
|
|
777
|
-
usageCount: z.number().int().nonnegative()
|
|
826
|
+
usageCount: z.number().int().nonnegative(),
|
|
827
|
+
recentEvents: z.array(contextTreeUsageEventSchema)
|
|
778
828
|
});
|
|
779
829
|
z.object({
|
|
780
830
|
repo: z.string().nullable(),
|
|
@@ -1031,23 +1081,41 @@ z.object({
|
|
|
1031
1081
|
});
|
|
1032
1082
|
z.object({ token: z.string().min(1) });
|
|
1033
1083
|
z.object({}).optional();
|
|
1034
|
-
const meChatFilterSchema = z.enum([
|
|
1035
|
-
"all",
|
|
1036
|
-
"unread",
|
|
1037
|
-
"watching"
|
|
1038
|
-
]);
|
|
1084
|
+
const meChatFilterSchema = z.enum(["all", "unread"]);
|
|
1039
1085
|
const meChatMembershipKindSchema = z.enum(["participant", "watching"]);
|
|
1040
1086
|
const chatEngagementViewSchema = z.enum([
|
|
1041
1087
|
"active",
|
|
1042
1088
|
"archived",
|
|
1043
1089
|
"all"
|
|
1044
1090
|
]);
|
|
1091
|
+
/**
|
|
1092
|
+
* Coerce a CSV string like `"manual,pr,issue"` to an array of trimmed
|
|
1093
|
+
* non-empty tokens. Lets the wire accept both repeated query params
|
|
1094
|
+
* (`?origin=manual&origin=pr`) and the comma-joined form the workspace
|
|
1095
|
+
* URL uses (`?origin=manual,pr`). Returns `undefined` when the input is
|
|
1096
|
+
* missing or empty so the caller can treat "no filter" and "filter to
|
|
1097
|
+
* empty array" identically.
|
|
1098
|
+
*/
|
|
1099
|
+
function csvArrayPreprocess(input) {
|
|
1100
|
+
if (typeof input !== "string") return input;
|
|
1101
|
+
const trimmed = input.trim();
|
|
1102
|
+
if (trimmed === "") return void 0;
|
|
1103
|
+
return trimmed.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1104
|
+
}
|
|
1045
1105
|
z.object({
|
|
1046
1106
|
cursor: z.string().optional(),
|
|
1047
1107
|
limit: z.coerce.number().int().min(1).max(200).default(50),
|
|
1048
1108
|
filter: meChatFilterSchema.default("all"),
|
|
1049
1109
|
engagement: chatEngagementViewSchema.default("active"),
|
|
1050
|
-
|
|
1110
|
+
origin: z.preprocess(csvArrayPreprocess, z.array(chatSourceSchema).optional()),
|
|
1111
|
+
with: z.preprocess(csvArrayPreprocess, z.array(z.string().min(1)).optional()),
|
|
1112
|
+
watching: z.preprocess((v) => {
|
|
1113
|
+
if (typeof v === "string") {
|
|
1114
|
+
if (v === "1" || v.toLowerCase() === "true") return true;
|
|
1115
|
+
if (v === "0" || v.toLowerCase() === "false" || v === "") return false;
|
|
1116
|
+
}
|
|
1117
|
+
return v;
|
|
1118
|
+
}, z.boolean().optional())
|
|
1051
1119
|
});
|
|
1052
1120
|
const meChatParticipantSchema = z.object({
|
|
1053
1121
|
agentId: z.string(),
|
|
@@ -1080,6 +1148,8 @@ const meChatRowSchema = z.object({
|
|
|
1080
1148
|
chatId: z.string(),
|
|
1081
1149
|
type: z.string(),
|
|
1082
1150
|
membershipKind: meChatMembershipKindSchema,
|
|
1151
|
+
source: chatSourceSchema.default("manual"),
|
|
1152
|
+
entityType: githubEntityTypeSchema.nullable().default(null),
|
|
1083
1153
|
title: z.string(),
|
|
1084
1154
|
topic: z.string().nullable(),
|
|
1085
1155
|
participants: z.array(meChatParticipantSchema),
|
|
@@ -1347,34 +1417,16 @@ z.object({
|
|
|
1347
1417
|
}),
|
|
1348
1418
|
mentionedUser: z.string().optional()
|
|
1349
1419
|
});
|
|
1350
|
-
|
|
1420
|
+
z.enum([
|
|
1351
1421
|
"agent_error",
|
|
1352
1422
|
"agent_blocked",
|
|
1353
1423
|
"agent_stale"
|
|
1354
1424
|
]);
|
|
1355
|
-
|
|
1425
|
+
z.enum([
|
|
1356
1426
|
"high",
|
|
1357
1427
|
"medium",
|
|
1358
1428
|
"low"
|
|
1359
1429
|
]);
|
|
1360
|
-
z.object({
|
|
1361
|
-
id: z.string(),
|
|
1362
|
-
organizationId: z.string(),
|
|
1363
|
-
type: notificationTypeSchema,
|
|
1364
|
-
severity: notificationSeveritySchema,
|
|
1365
|
-
agentId: z.string().nullable(),
|
|
1366
|
-
chatId: z.string().nullable(),
|
|
1367
|
-
message: z.string(),
|
|
1368
|
-
read: z.boolean(),
|
|
1369
|
-
createdAt: z.string()
|
|
1370
|
-
});
|
|
1371
|
-
z.object({
|
|
1372
|
-
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
1373
|
-
cursor: z.string().optional(),
|
|
1374
|
-
severity: notificationSeveritySchema.optional(),
|
|
1375
|
-
read: z.enum(["true", "false"]).transform((v) => v === "true").optional(),
|
|
1376
|
-
agentId: z.string().optional()
|
|
1377
|
-
});
|
|
1378
1430
|
z.object({ next: z.string().max(256).optional() });
|
|
1379
1431
|
z.object({
|
|
1380
1432
|
code: z.string().min(1),
|
|
@@ -1403,8 +1455,8 @@ z.object({
|
|
|
1403
1455
|
*
|
|
1404
1456
|
* Each namespace has three schemas:
|
|
1405
1457
|
* - `storage` — what is persisted in `organization_settings.value`. For
|
|
1406
|
-
* namespaces with secrets, the storage schema names the *cipher* field
|
|
1407
|
-
*
|
|
1458
|
+
* namespaces with secrets, the storage schema names the *cipher* field;
|
|
1459
|
+
* plaintext never touches the row.
|
|
1408
1460
|
* - `input` — what the admin API accepts in PUT bodies. For namespaces
|
|
1409
1461
|
* with secrets, `webhookSecret` is plaintext; the service layer
|
|
1410
1462
|
* encrypts it before merging into storage.
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $ as SdkError, A as printResults, B as ClientRuntime, D as checkNodeVersion, E as checkClientConfig, F as resolveCliInvocation, H as rotateClientIdWithBackup, I as restartClientService, L as startClientService, M as getClientServiceStatus, N as installClientService, O as checkServerReachable, P as isServiceSupported, Q as FirstTreeHubSDK, R as stopClientService, V as handleClientOrgMismatch, g as formatCheckReport, h as promptMissingFields, k as checkWebSocket, m as promptAddAgent, n as deriveHubUrlFromToken, p as isInteractive, t as HubUrlDerivationError, v as onboardCheck, w as checkAgentConfigs, x as runHomeMigration, y as onboardCreate, z as uninstallClientService } from "./saas-connect-
|
|
1
|
+
import { $ as SdkError, A as printResults, B as ClientRuntime, D as checkNodeVersion, E as checkClientConfig, F as resolveCliInvocation, H as rotateClientIdWithBackup, I as restartClientService, L as startClientService, M as getClientServiceStatus, N as installClientService, O as checkServerReachable, P as isServiceSupported, Q as FirstTreeHubSDK, R as stopClientService, V as handleClientOrgMismatch, g as formatCheckReport, h as promptMissingFields, k as checkWebSocket, m as promptAddAgent, n as deriveHubUrlFromToken, p as isInteractive, t as HubUrlDerivationError, v as onboardCheck, w as checkAgentConfigs, x as runHomeMigration, y as onboardCreate, z as uninstallClientService } from "./saas-connect-gXv14p6J.mjs";
|
|
2
2
|
import { i as ensureFreshAdminToken, n as AuthRefreshRateLimitedError, o as resolveAccessToken, r as ensureFreshAccessToken, s as resolveServerUrl, t as AuthRefreshFailedError } from "./bootstrap-CmkHQsnS.mjs";
|
|
3
3
|
import { i as blank, s as status } from "./cli-fetch-BGVItZxo.mjs";
|
|
4
|
-
import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-
|
|
4
|
+
import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-BBUEVTnb.mjs";
|
|
5
5
|
export { AuthRefreshFailedError, AuthRefreshRateLimitedError, ClientRuntime, FirstTreeHubSDK, HubUrlDerivationError, SdkError, bindFeishuBot, bindFeishuUser, blank, checkAgentConfigs, checkClientConfig, checkNodeVersion, checkServerReachable, checkWebSocket, deriveHubUrlFromToken, ensureFreshAccessToken, ensureFreshAdminToken, formatCheckReport, getClientServiceStatus, handleClientOrgMismatch, installClientService, isInteractive, isServiceSupported, onboardCheck, onboardCreate, printResults, promptAddAgent, promptMissingFields, resolveAccessToken, resolveCliInvocation, resolveServerUrl, restartClientService, rotateClientIdWithBackup, runHomeMigration, startClientService, status, stopClientService, uninstallClientService };
|
|
@@ -3677,6 +3677,152 @@ function configureClientLoggerForService(logDir) {
|
|
|
3677
3677
|
//#endregion
|
|
3678
3678
|
//#region ../client/dist/index.mjs
|
|
3679
3679
|
/**
|
|
3680
|
+
* Shared scanner for plain markdown document-path tokens inside chat text.
|
|
3681
|
+
*
|
|
3682
|
+
* Runtime (client) calls this to know which `.md` paths to read off disk and
|
|
3683
|
+
* embed as snapshots in `metadata.documentContext`. Web calls the same scanner
|
|
3684
|
+
* to know which tokens to wrap in `[path](path)` so react-markdown renders
|
|
3685
|
+
* them as clickable links. Sharing one scanner is what guarantees that every
|
|
3686
|
+
* link the user sees in the UI has a matching snapshot in metadata — the
|
|
3687
|
+
* cache-key on both sides is the canonical path the scanner reports here.
|
|
3688
|
+
*
|
|
3689
|
+
* Skipped constructs (must stay aligned with markdown rules so we don't
|
|
3690
|
+
* rewrite something the renderer already treats specially):
|
|
3691
|
+
* - inline markdown links `[text](path.md)` (the runtime scans those
|
|
3692
|
+
* separately via `scanInlineMarkdownLinks`; this scanner intentionally
|
|
3693
|
+
* does not double-handle them)
|
|
3694
|
+
* - inline code spans `` `...` ``
|
|
3695
|
+
* - fenced code blocks (``` ``` ``` ``` / `~~~`)
|
|
3696
|
+
* - indented code blocks (4+ spaces or leading tab)
|
|
3697
|
+
* - HTML tag bodies `<...>` (so href / src attribute values aren't
|
|
3698
|
+
* re-linkified)
|
|
3699
|
+
* - reference-link definitions `[ref]: target`
|
|
3700
|
+
* - domain-shaped tokens like `example.com/readme.md` — these are URLs the
|
|
3701
|
+
* user almost certainly meant as external links, not workspace paths.
|
|
3702
|
+
*
|
|
3703
|
+
* The function returns matches WITH offsets so callers that need to rewrite
|
|
3704
|
+
* the source string (web's `linkifyMarkdownDocPaths`) can do so without
|
|
3705
|
+
* re-running the same regex.
|
|
3706
|
+
*/
|
|
3707
|
+
/**
|
|
3708
|
+
* v1 limitation — the path-segment character class is intentionally ASCII
|
|
3709
|
+
* only. Unicode filenames and Windows backslash-separated paths are NOT
|
|
3710
|
+
* linkified. Workspaces in practice use POSIX-style ASCII paths, and the
|
|
3711
|
+
* runtime's `normalizeDocLinkPath` also rejects absolute paths, so this
|
|
3712
|
+
* matches the rest of the pipeline. Loosening the character class later
|
|
3713
|
+
* is straightforward but requires re-validating that we don't accidentally
|
|
3714
|
+
* absorb adjacent punctuation as part of a "filename".
|
|
3715
|
+
*/
|
|
3716
|
+
const BARE_PATH_RE = /(^|[\s([{"'])(?<path>(?:\.{1,2}\/|\/)?(?:[A-Za-z0-9_.~+@%-]+\/)*[A-Za-z0-9_.~+@%-]+\.md(?::\d+(?::\d+)?)?)(?=$|[\s)\]}"',.;!?])/g;
|
|
3717
|
+
const INLINE_MARKDOWN_LINK_RE = /\[(?:[^\]\\\n]|\\.)*\]\([^)\n]*\)/g;
|
|
3718
|
+
const REFERENCE_LINK_DEFINITION_RE = /^\s*\[[^\]\n]+\]:\s*\S+/;
|
|
3719
|
+
const HTML_TAG_RE = /<\/?[A-Za-z][^>\n]*>/g;
|
|
3720
|
+
const FENCE_OPEN_RE = /^\s*(```|~~~)/;
|
|
3721
|
+
const INDENT_CODE_RE = /^(?: {4}|\t)/;
|
|
3722
|
+
const DOMAIN_LIKE_PREFIX_RE = /^[a-z][a-z0-9.-]*\.[a-z]{2,}\//i;
|
|
3723
|
+
/**
|
|
3724
|
+
* Scan `text` for plain `.md` path tokens that are NOT already wrapped in a
|
|
3725
|
+
* markdown link, code span, HTML tag, or fenced/indented code block.
|
|
3726
|
+
*
|
|
3727
|
+
* The returned tokens still need to be passed through
|
|
3728
|
+
* `normalizeDocLinkPath(stripLineSuffix(raw))` before they are treated as
|
|
3729
|
+
* canonical workspace paths.
|
|
3730
|
+
*/
|
|
3731
|
+
function scanBareDocPathTokens(text) {
|
|
3732
|
+
const out = [];
|
|
3733
|
+
const lines = text.split(/(\r?\n)/);
|
|
3734
|
+
let inFence = false;
|
|
3735
|
+
let absoluteOffset = 0;
|
|
3736
|
+
for (const line of lines) {
|
|
3737
|
+
if (line === "\n" || line === "\r\n") {
|
|
3738
|
+
absoluteOffset += line.length;
|
|
3739
|
+
continue;
|
|
3740
|
+
}
|
|
3741
|
+
if (FENCE_OPEN_RE.test(line)) {
|
|
3742
|
+
inFence = !inFence;
|
|
3743
|
+
absoluteOffset += line.length;
|
|
3744
|
+
continue;
|
|
3745
|
+
}
|
|
3746
|
+
if (inFence) {
|
|
3747
|
+
absoluteOffset += line.length;
|
|
3748
|
+
continue;
|
|
3749
|
+
}
|
|
3750
|
+
if (INDENT_CODE_RE.test(line) || REFERENCE_LINK_DEFINITION_RE.test(line)) {
|
|
3751
|
+
absoluteOffset += line.length;
|
|
3752
|
+
continue;
|
|
3753
|
+
}
|
|
3754
|
+
const skipRanges = findInlineSkipRanges(line);
|
|
3755
|
+
BARE_PATH_RE.lastIndex = 0;
|
|
3756
|
+
let match = BARE_PATH_RE.exec(line);
|
|
3757
|
+
while (match !== null) {
|
|
3758
|
+
const boundary = match[1] ?? "";
|
|
3759
|
+
const path = match.groups?.path ?? "";
|
|
3760
|
+
const pathStart = match.index + boundary.length;
|
|
3761
|
+
const pathEnd = pathStart + path.length;
|
|
3762
|
+
if (path && !isInsideAnyRange(pathStart, skipRanges) && !isDomainLike(path)) out.push({
|
|
3763
|
+
raw: path,
|
|
3764
|
+
start: absoluteOffset + pathStart,
|
|
3765
|
+
end: absoluteOffset + pathEnd
|
|
3766
|
+
});
|
|
3767
|
+
match = BARE_PATH_RE.exec(line);
|
|
3768
|
+
}
|
|
3769
|
+
absoluteOffset += line.length;
|
|
3770
|
+
}
|
|
3771
|
+
return out;
|
|
3772
|
+
}
|
|
3773
|
+
/**
|
|
3774
|
+
* Strip the `:line[:col]` suffix that agents often append to file references.
|
|
3775
|
+
* Returns the path portion (without the trailing line/column digits) so
|
|
3776
|
+
* callers can hand it to `normalizeDocLinkPath`. We accept and discard the
|
|
3777
|
+
* line/column information — preview-time line scrolling is out of scope
|
|
3778
|
+
* for the snapshot link path.
|
|
3779
|
+
*/
|
|
3780
|
+
function stripDocPathLineSuffix(raw) {
|
|
3781
|
+
return /^(?<path>.*?\.md)(?::\d+(?::\d+)?)?$/i.exec(raw)?.groups?.path ?? raw;
|
|
3782
|
+
}
|
|
3783
|
+
function findInlineSkipRanges(line) {
|
|
3784
|
+
const ranges = [];
|
|
3785
|
+
for (const match of line.matchAll(INLINE_MARKDOWN_LINK_RE)) if (match.index !== void 0) ranges.push({
|
|
3786
|
+
start: match.index,
|
|
3787
|
+
end: match.index + match[0].length
|
|
3788
|
+
});
|
|
3789
|
+
for (const match of line.matchAll(HTML_TAG_RE)) if (match.index !== void 0) ranges.push({
|
|
3790
|
+
start: match.index,
|
|
3791
|
+
end: match.index + match[0].length
|
|
3792
|
+
});
|
|
3793
|
+
for (let idx = 0; idx < line.length;) {
|
|
3794
|
+
if (line[idx] !== "`") {
|
|
3795
|
+
idx += 1;
|
|
3796
|
+
continue;
|
|
3797
|
+
}
|
|
3798
|
+
const start = idx;
|
|
3799
|
+
while (line[idx] === "`") idx += 1;
|
|
3800
|
+
const ticks = line.slice(start, idx);
|
|
3801
|
+
const end = line.indexOf(ticks, idx);
|
|
3802
|
+
if (end === -1) continue;
|
|
3803
|
+
ranges.push({
|
|
3804
|
+
start,
|
|
3805
|
+
end: end + ticks.length
|
|
3806
|
+
});
|
|
3807
|
+
idx = end + ticks.length;
|
|
3808
|
+
}
|
|
3809
|
+
return ranges;
|
|
3810
|
+
}
|
|
3811
|
+
function isInsideAnyRange(index, ranges) {
|
|
3812
|
+
return ranges.some((range) => index >= range.start && index < range.end);
|
|
3813
|
+
}
|
|
3814
|
+
/**
|
|
3815
|
+
* Decide whether the first path segment looks like a bare domain name
|
|
3816
|
+
* (`example.com/...`) rather than a real `.md` filename. The check is
|
|
3817
|
+
* suppressed when the first segment already ends in `.md`, since a directory
|
|
3818
|
+
* named `notes.md/` is a legitimate workspace path even though it matches
|
|
3819
|
+
* the domain-shape regex.
|
|
3820
|
+
*/
|
|
3821
|
+
function isDomainLike(path) {
|
|
3822
|
+
if ((path.split("/", 1).at(0) ?? "").toLowerCase().endsWith(".md")) return false;
|
|
3823
|
+
return DOMAIN_LIKE_PREFIX_RE.test(path);
|
|
3824
|
+
}
|
|
3825
|
+
/**
|
|
3680
3826
|
* Workspace-relative markdown doc path normalisation.
|
|
3681
3827
|
*
|
|
3682
3828
|
* Shared by:
|
|
@@ -4226,10 +4372,7 @@ const chatMetadataSchema = z.discriminatedUnion("source", [githubChatMetadataSch
|
|
|
4226
4372
|
const optionalChatMetadataSchema = z.union([z.object({}).strict(), chatMetadataSchema]);
|
|
4227
4373
|
const chatSourceSchema = z.enum([
|
|
4228
4374
|
"manual",
|
|
4229
|
-
"
|
|
4230
|
-
"github_pull_request",
|
|
4231
|
-
"github_discussion",
|
|
4232
|
-
"github_commit",
|
|
4375
|
+
"github",
|
|
4233
4376
|
"feishu"
|
|
4234
4377
|
]);
|
|
4235
4378
|
const chatTypeSchema = z.enum(["direct", "group"]);
|
|
@@ -4258,7 +4401,9 @@ const chatParticipantDetailSchema = z.object({
|
|
|
4258
4401
|
}).extend({
|
|
4259
4402
|
name: z.string().nullable(),
|
|
4260
4403
|
displayName: z.string(),
|
|
4261
|
-
type: z.string()
|
|
4404
|
+
type: z.string(),
|
|
4405
|
+
avatarColorToken: z.string().nullable(),
|
|
4406
|
+
avatarImageUrl: z.string().nullable()
|
|
4262
4407
|
});
|
|
4263
4408
|
z.object({
|
|
4264
4409
|
id: z.string(),
|
|
@@ -4279,6 +4424,47 @@ z.object({
|
|
|
4279
4424
|
z.object({ topic: z.string().trim().max(500).nullable() });
|
|
4280
4425
|
z.object({ agentId: z.string().min(1) });
|
|
4281
4426
|
z.object({ agentId: z.string().min(1) });
|
|
4427
|
+
/**
|
|
4428
|
+
* Schema for `GET /api/v1/chats/:chatId/github-entities`.
|
|
4429
|
+
*
|
|
4430
|
+
* Each item describes one entity bound to the chat via
|
|
4431
|
+
* `github_entity_chat_mappings`. Live state (`title`, `state`) is fetched
|
|
4432
|
+
* per-request from the GitHub REST API at request time and NEVER
|
|
4433
|
+
* persisted to the database — the user explicitly opted for the
|
|
4434
|
+
* fresh-on-every-open model rather than a webhook-synced column.
|
|
4435
|
+
*
|
|
4436
|
+
* Live fields may be `null` when the GitHub call failed (token unavailable,
|
|
4437
|
+
* rate-limited, 404 on a deleted entity, etc.). The client falls back to
|
|
4438
|
+
* rendering `entityKey + htmlUrl` so the row is still a working link.
|
|
4439
|
+
*/
|
|
4440
|
+
const githubEntityBoundViaSchema = z.enum([
|
|
4441
|
+
"direct",
|
|
4442
|
+
"fixes_link",
|
|
4443
|
+
"agent_created"
|
|
4444
|
+
]);
|
|
4445
|
+
/**
|
|
4446
|
+
* Coarse live state. PR-specific values (`merged`, `draft`) are folded
|
|
4447
|
+
* into the same enum as the issue's `open` / `closed` so the UI can
|
|
4448
|
+
* switch on a single field. `null` means we couldn't reach GitHub for
|
|
4449
|
+
* this entity in this request — the row still renders, just without
|
|
4450
|
+
* the state badge.
|
|
4451
|
+
*/
|
|
4452
|
+
const githubEntityLiveStateSchema = z.enum([
|
|
4453
|
+
"open",
|
|
4454
|
+
"closed",
|
|
4455
|
+
"merged",
|
|
4456
|
+
"draft"
|
|
4457
|
+
]);
|
|
4458
|
+
const chatGithubEntitySchema = z.object({
|
|
4459
|
+
entityType: githubEntityTypeSchema,
|
|
4460
|
+
entityKey: z.string().min(1),
|
|
4461
|
+
boundVia: githubEntityBoundViaSchema,
|
|
4462
|
+
htmlUrl: z.string().url(),
|
|
4463
|
+
title: z.string().nullable(),
|
|
4464
|
+
state: githubEntityLiveStateSchema.nullable(),
|
|
4465
|
+
number: z.number().int().nullable()
|
|
4466
|
+
});
|
|
4467
|
+
z.object({ items: z.array(chatGithubEntitySchema) });
|
|
4282
4468
|
const clientStatusSchema = z.enum(["connected", "disconnected"]);
|
|
4283
4469
|
/**
|
|
4284
4470
|
* Auth health channel surfaced to the Web admin dashboard. Computed
|
|
@@ -4452,10 +4638,20 @@ const contextTreeSummarySchema = z.object({
|
|
|
4452
4638
|
removedCount: z.number().int().nonnegative(),
|
|
4453
4639
|
changedNodeCount: z.number().int().nonnegative()
|
|
4454
4640
|
});
|
|
4641
|
+
const contextTreeUsageEventSchema = z.object({
|
|
4642
|
+
id: z.string(),
|
|
4643
|
+
agentId: z.string(),
|
|
4644
|
+
agentName: z.string(),
|
|
4645
|
+
agentAvatarColorToken: z.string().nullable(),
|
|
4646
|
+
chatId: z.string().nullable(),
|
|
4647
|
+
chatTitle: z.string().nullable(),
|
|
4648
|
+
createdAt: z.string()
|
|
4649
|
+
});
|
|
4455
4650
|
const contextTreeUsageSummarySchema = z.object({
|
|
4456
4651
|
windowDays: z.number().int().positive(),
|
|
4457
4652
|
agentCount: z.number().int().nonnegative(),
|
|
4458
|
-
usageCount: z.number().int().nonnegative()
|
|
4653
|
+
usageCount: z.number().int().nonnegative(),
|
|
4654
|
+
recentEvents: z.array(contextTreeUsageEventSchema)
|
|
4459
4655
|
});
|
|
4460
4656
|
z.object({
|
|
4461
4657
|
repo: z.string().nullable(),
|
|
@@ -4743,23 +4939,41 @@ z.object({
|
|
|
4743
4939
|
});
|
|
4744
4940
|
z.object({ token: z.string().min(1) });
|
|
4745
4941
|
z.object({}).optional();
|
|
4746
|
-
const meChatFilterSchema = z.enum([
|
|
4747
|
-
"all",
|
|
4748
|
-
"unread",
|
|
4749
|
-
"watching"
|
|
4750
|
-
]);
|
|
4942
|
+
const meChatFilterSchema = z.enum(["all", "unread"]);
|
|
4751
4943
|
const meChatMembershipKindSchema = z.enum(["participant", "watching"]);
|
|
4752
4944
|
const chatEngagementViewSchema = z.enum([
|
|
4753
4945
|
"active",
|
|
4754
4946
|
"archived",
|
|
4755
4947
|
"all"
|
|
4756
4948
|
]);
|
|
4949
|
+
/**
|
|
4950
|
+
* Coerce a CSV string like `"manual,pr,issue"` to an array of trimmed
|
|
4951
|
+
* non-empty tokens. Lets the wire accept both repeated query params
|
|
4952
|
+
* (`?origin=manual&origin=pr`) and the comma-joined form the workspace
|
|
4953
|
+
* URL uses (`?origin=manual,pr`). Returns `undefined` when the input is
|
|
4954
|
+
* missing or empty so the caller can treat "no filter" and "filter to
|
|
4955
|
+
* empty array" identically.
|
|
4956
|
+
*/
|
|
4957
|
+
function csvArrayPreprocess(input) {
|
|
4958
|
+
if (typeof input !== "string") return input;
|
|
4959
|
+
const trimmed = input.trim();
|
|
4960
|
+
if (trimmed === "") return void 0;
|
|
4961
|
+
return trimmed.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
4962
|
+
}
|
|
4757
4963
|
z.object({
|
|
4758
4964
|
cursor: z.string().optional(),
|
|
4759
4965
|
limit: z.coerce.number().int().min(1).max(200).default(50),
|
|
4760
4966
|
filter: meChatFilterSchema.default("all"),
|
|
4761
4967
|
engagement: chatEngagementViewSchema.default("active"),
|
|
4762
|
-
|
|
4968
|
+
origin: z.preprocess(csvArrayPreprocess, z.array(chatSourceSchema).optional()),
|
|
4969
|
+
with: z.preprocess(csvArrayPreprocess, z.array(z.string().min(1)).optional()),
|
|
4970
|
+
watching: z.preprocess((v) => {
|
|
4971
|
+
if (typeof v === "string") {
|
|
4972
|
+
if (v === "1" || v.toLowerCase() === "true") return true;
|
|
4973
|
+
if (v === "0" || v.toLowerCase() === "false" || v === "") return false;
|
|
4974
|
+
}
|
|
4975
|
+
return v;
|
|
4976
|
+
}, z.boolean().optional())
|
|
4763
4977
|
});
|
|
4764
4978
|
const meChatParticipantSchema = z.object({
|
|
4765
4979
|
agentId: z.string(),
|
|
@@ -4792,6 +5006,8 @@ const meChatRowSchema = z.object({
|
|
|
4792
5006
|
chatId: z.string(),
|
|
4793
5007
|
type: z.string(),
|
|
4794
5008
|
membershipKind: meChatMembershipKindSchema,
|
|
5009
|
+
source: chatSourceSchema.default("manual"),
|
|
5010
|
+
entityType: githubEntityTypeSchema.nullable().default(null),
|
|
4795
5011
|
title: z.string(),
|
|
4796
5012
|
topic: z.string().nullable(),
|
|
4797
5013
|
participants: z.array(meChatParticipantSchema),
|
|
@@ -5074,34 +5290,16 @@ z.object({
|
|
|
5074
5290
|
}),
|
|
5075
5291
|
mentionedUser: z.string().optional()
|
|
5076
5292
|
});
|
|
5077
|
-
|
|
5293
|
+
z.enum([
|
|
5078
5294
|
"agent_error",
|
|
5079
5295
|
"agent_blocked",
|
|
5080
5296
|
"agent_stale"
|
|
5081
5297
|
]);
|
|
5082
|
-
|
|
5298
|
+
z.enum([
|
|
5083
5299
|
"high",
|
|
5084
5300
|
"medium",
|
|
5085
5301
|
"low"
|
|
5086
5302
|
]);
|
|
5087
|
-
z.object({
|
|
5088
|
-
id: z.string(),
|
|
5089
|
-
organizationId: z.string(),
|
|
5090
|
-
type: notificationTypeSchema,
|
|
5091
|
-
severity: notificationSeveritySchema,
|
|
5092
|
-
agentId: z.string().nullable(),
|
|
5093
|
-
chatId: z.string().nullable(),
|
|
5094
|
-
message: z.string(),
|
|
5095
|
-
read: z.boolean(),
|
|
5096
|
-
createdAt: z.string()
|
|
5097
|
-
});
|
|
5098
|
-
z.object({
|
|
5099
|
-
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
5100
|
-
cursor: z.string().optional(),
|
|
5101
|
-
severity: notificationSeveritySchema.optional(),
|
|
5102
|
-
read: z.enum(["true", "false"]).transform((v) => v === "true").optional(),
|
|
5103
|
-
agentId: z.string().optional()
|
|
5104
|
-
});
|
|
5105
5303
|
z.object({ next: z.string().max(256).optional() });
|
|
5106
5304
|
z.object({
|
|
5107
5305
|
code: z.string().min(1),
|
|
@@ -5130,8 +5328,8 @@ z.object({
|
|
|
5130
5328
|
*
|
|
5131
5329
|
* Each namespace has three schemas:
|
|
5132
5330
|
* - `storage` — what is persisted in `organization_settings.value`. For
|
|
5133
|
-
* namespaces with secrets, the storage schema names the *cipher* field
|
|
5134
|
-
*
|
|
5331
|
+
* namespaces with secrets, the storage schema names the *cipher* field;
|
|
5332
|
+
* plaintext never touches the row.
|
|
5135
5333
|
* - `input` — what the admin API accepts in PUT bodies. For namespaces
|
|
5136
5334
|
* with secrets, `webhookSecret` is plaintext; the service layer
|
|
5137
5335
|
* encrypts it before merging into storage.
|
|
@@ -5900,6 +6098,10 @@ var FirstTreeHubSDK = class {
|
|
|
5900
6098
|
async listChatParticipants(chatId) {
|
|
5901
6099
|
return this.requestJson(`/api/v1/agent/chats/${chatId}/participants`);
|
|
5902
6100
|
}
|
|
6101
|
+
/** Fetch Context Tree configuration for this SDK's authenticated agent. */
|
|
6102
|
+
async getAgentContextTreeConfig() {
|
|
6103
|
+
return this.requestJson("/api/v1/agent/context-tree/info");
|
|
6104
|
+
}
|
|
5903
6105
|
queryString(options) {
|
|
5904
6106
|
const params = new URLSearchParams();
|
|
5905
6107
|
if (options?.limit !== void 0) params.set("limit", String(options.limit));
|
|
@@ -6646,14 +6848,19 @@ function getHandlerFactory(type) {
|
|
|
6646
6848
|
}
|
|
6647
6849
|
return factory;
|
|
6648
6850
|
}
|
|
6649
|
-
const
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6851
|
+
const CONTEXT_TREE_REPOS_DIR = join(DEFAULT_DATA_DIR, "context-tree-repos");
|
|
6852
|
+
const contextTreeSyncLocks = /* @__PURE__ */ new Map();
|
|
6853
|
+
function contextTreeCloneDir(repo, branch) {
|
|
6854
|
+
return join(CONTEXT_TREE_REPOS_DIR, createHash("sha256").update(`${repo}\0${branch}`).digest("hex"));
|
|
6855
|
+
}
|
|
6856
|
+
function withContextTreeSyncLock(key, fn) {
|
|
6857
|
+
const next = (contextTreeSyncLocks.get(key) ?? Promise.resolve(null)).catch(() => null).then(fn).finally(() => {
|
|
6858
|
+
if (contextTreeSyncLocks.get(key) === next) contextTreeSyncLocks.delete(key);
|
|
6859
|
+
});
|
|
6860
|
+
contextTreeSyncLocks.set(key, next);
|
|
6861
|
+
return next;
|
|
6862
|
+
}
|
|
6863
|
+
async function resolveContextTreeBinding(fetchConfig, log) {
|
|
6657
6864
|
try {
|
|
6658
6865
|
execFileSync("git", ["--version"], { stdio: "ignore" });
|
|
6659
6866
|
} catch {
|
|
@@ -6663,11 +6870,7 @@ async function syncContextTree(serverUrl, getAccessToken, log, userAgent) {
|
|
|
6663
6870
|
let repo;
|
|
6664
6871
|
let branch;
|
|
6665
6872
|
try {
|
|
6666
|
-
const config = await
|
|
6667
|
-
serverUrl,
|
|
6668
|
-
getAccessToken,
|
|
6669
|
-
userAgent
|
|
6670
|
-
}).getContextTreeConfig();
|
|
6873
|
+
const config = await fetchConfig();
|
|
6671
6874
|
if (!config.repo) {
|
|
6672
6875
|
log("Context Tree sync skipped: not configured on server");
|
|
6673
6876
|
return null;
|
|
@@ -6678,39 +6881,43 @@ async function syncContextTree(serverUrl, getAccessToken, log, userAgent) {
|
|
|
6678
6881
|
log(`Context Tree sync skipped: failed to fetch config from server (${err instanceof Error ? err.message : String(err)})`);
|
|
6679
6882
|
return null;
|
|
6680
6883
|
}
|
|
6884
|
+
const cloneDir = contextTreeCloneDir(repo, branch);
|
|
6885
|
+
return withContextTreeSyncLock(cloneDir, () => syncContextTreeRepo(repo, branch, cloneDir, log));
|
|
6886
|
+
}
|
|
6887
|
+
async function syncContextTreeRepo(repo, branch, cloneDir, log) {
|
|
6681
6888
|
try {
|
|
6682
|
-
if (existsSync(join(
|
|
6889
|
+
if (existsSync(join(cloneDir, ".git"))) {
|
|
6683
6890
|
if (execFileSync("git", [
|
|
6684
6891
|
"rev-parse",
|
|
6685
6892
|
"--abbrev-ref",
|
|
6686
6893
|
"HEAD"
|
|
6687
6894
|
], {
|
|
6688
|
-
cwd:
|
|
6895
|
+
cwd: cloneDir,
|
|
6689
6896
|
encoding: "utf-8",
|
|
6690
6897
|
timeout: 5e3
|
|
6691
6898
|
}).trim() !== branch) {
|
|
6692
6899
|
execFileSync("git", ["checkout", branch], {
|
|
6693
|
-
cwd:
|
|
6900
|
+
cwd: cloneDir,
|
|
6694
6901
|
stdio: "pipe",
|
|
6695
6902
|
timeout: 1e4
|
|
6696
6903
|
});
|
|
6697
6904
|
log(`Context Tree switched to branch ${branch}`);
|
|
6698
6905
|
}
|
|
6699
6906
|
execFileSync("git", ["pull", "--ff-only"], {
|
|
6700
|
-
cwd:
|
|
6907
|
+
cwd: cloneDir,
|
|
6701
6908
|
stdio: "pipe",
|
|
6702
6909
|
timeout: 3e4
|
|
6703
6910
|
});
|
|
6704
6911
|
log(`Context Tree updated (pull)`);
|
|
6705
6912
|
} else {
|
|
6706
|
-
mkdirSync(
|
|
6913
|
+
mkdirSync(cloneDir, { recursive: true });
|
|
6707
6914
|
execFileSync("git", [
|
|
6708
6915
|
"clone",
|
|
6709
6916
|
"--branch",
|
|
6710
6917
|
branch,
|
|
6711
6918
|
"--single-branch",
|
|
6712
6919
|
repo,
|
|
6713
|
-
|
|
6920
|
+
cloneDir
|
|
6714
6921
|
], {
|
|
6715
6922
|
stdio: "pipe",
|
|
6716
6923
|
timeout: 6e4
|
|
@@ -6718,7 +6925,7 @@ async function syncContextTree(serverUrl, getAccessToken, log, userAgent) {
|
|
|
6718
6925
|
log(`Context Tree cloned from ${repo} (branch: ${branch})`);
|
|
6719
6926
|
}
|
|
6720
6927
|
return {
|
|
6721
|
-
path:
|
|
6928
|
+
path: cloneDir,
|
|
6722
6929
|
repoUrl: repo,
|
|
6723
6930
|
branch
|
|
6724
6931
|
};
|
|
@@ -6726,28 +6933,28 @@ async function syncContextTree(serverUrl, getAccessToken, log, userAgent) {
|
|
|
6726
6933
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6727
6934
|
log(`Context Tree sync failed: ${msg}`);
|
|
6728
6935
|
log("Check that git credentials (SSH key or credential helper) are configured for this repo");
|
|
6729
|
-
if ((msg.includes("cannot fast-forward") || msg.includes("not possible to fast-forward") || msg.includes("CONFLICT")) && existsSync(join(
|
|
6936
|
+
if ((msg.includes("cannot fast-forward") || msg.includes("not possible to fast-forward") || msg.includes("CONFLICT")) && existsSync(join(cloneDir, ".git"))) {
|
|
6730
6937
|
log("Diverged history detected, attempting fresh clone...");
|
|
6731
6938
|
try {
|
|
6732
|
-
rmSync(
|
|
6939
|
+
rmSync(cloneDir, {
|
|
6733
6940
|
recursive: true,
|
|
6734
6941
|
force: true
|
|
6735
6942
|
});
|
|
6736
|
-
mkdirSync(
|
|
6943
|
+
mkdirSync(cloneDir, { recursive: true });
|
|
6737
6944
|
execFileSync("git", [
|
|
6738
6945
|
"clone",
|
|
6739
6946
|
"--branch",
|
|
6740
6947
|
branch,
|
|
6741
6948
|
"--single-branch",
|
|
6742
6949
|
repo,
|
|
6743
|
-
|
|
6950
|
+
cloneDir
|
|
6744
6951
|
], {
|
|
6745
6952
|
stdio: "pipe",
|
|
6746
6953
|
timeout: 6e4
|
|
6747
6954
|
});
|
|
6748
6955
|
log("Context Tree re-cloned successfully");
|
|
6749
6956
|
return {
|
|
6750
|
-
path:
|
|
6957
|
+
path: cloneDir,
|
|
6751
6958
|
repoUrl: repo,
|
|
6752
6959
|
branch
|
|
6753
6960
|
};
|
|
@@ -6755,10 +6962,10 @@ async function syncContextTree(serverUrl, getAccessToken, log, userAgent) {
|
|
|
6755
6962
|
log("Context Tree re-clone also failed, continuing without context");
|
|
6756
6963
|
}
|
|
6757
6964
|
}
|
|
6758
|
-
if (existsSync(join(
|
|
6965
|
+
if (existsSync(join(cloneDir, ".git"))) {
|
|
6759
6966
|
log("Using existing Context Tree clone despite sync failure");
|
|
6760
6967
|
return {
|
|
6761
|
-
path:
|
|
6968
|
+
path: cloneDir,
|
|
6762
6969
|
repoUrl: repo,
|
|
6763
6970
|
branch
|
|
6764
6971
|
};
|
|
@@ -6767,6 +6974,16 @@ async function syncContextTree(serverUrl, getAccessToken, log, userAgent) {
|
|
|
6767
6974
|
}
|
|
6768
6975
|
}
|
|
6769
6976
|
/**
|
|
6977
|
+
* Sync the Context Tree checkout for the authenticated runtime agent.
|
|
6978
|
+
*
|
|
6979
|
+
* Uses the SDK's agent-scoped `/api/v1/agent/context-tree/info` route, so the
|
|
6980
|
+
* binding follows the agent's own organization rather than the caller's
|
|
6981
|
+
* default organization. Local checkouts are still isolated per `(repo, branch)`.
|
|
6982
|
+
*/
|
|
6983
|
+
async function syncAgentContextTree(sdk, log) {
|
|
6984
|
+
return resolveContextTreeBinding(() => sdk.getAgentContextTreeConfig(), log);
|
|
6985
|
+
}
|
|
6986
|
+
/**
|
|
6770
6987
|
* Marker file written into every workspace so the Codex CLI's project-root
|
|
6771
6988
|
* detection (configured via `project_root_markers: ["first-tree-workspace"]`)
|
|
6772
6989
|
* stops at the workspace boundary instead of walking up the filesystem and
|
|
@@ -9813,21 +10030,23 @@ var Deduplicator = class {
|
|
|
9813
10030
|
* offending link simply downgrades to a plain link in the UI.
|
|
9814
10031
|
*/
|
|
9815
10032
|
async function buildMessageDocumentSnapshots(text, root) {
|
|
9816
|
-
const
|
|
9817
|
-
|
|
10033
|
+
const inline = scanInlineMarkdownLinks(text);
|
|
10034
|
+
const bare = scanBareDocPathTokens(text).map((m) => stripDocPathLineSuffix(m.raw));
|
|
10035
|
+
const candidates = [...inline, ...bare];
|
|
10036
|
+
if (candidates.length === 0) return {
|
|
9818
10037
|
docs: [],
|
|
9819
10038
|
skipped: 0
|
|
9820
10039
|
};
|
|
9821
10040
|
const rootReal = await safeRealpath(root);
|
|
9822
10041
|
if (!rootReal) return {
|
|
9823
10042
|
docs: [],
|
|
9824
|
-
skipped:
|
|
10043
|
+
skipped: candidates.length
|
|
9825
10044
|
};
|
|
9826
10045
|
const docs = [];
|
|
9827
10046
|
let totalBytes = 0;
|
|
9828
10047
|
let skipped = 0;
|
|
9829
10048
|
const seenCanonical = /* @__PURE__ */ new Set();
|
|
9830
|
-
for (const rawPath of
|
|
10049
|
+
for (const rawPath of candidates) {
|
|
9831
10050
|
if (docs.length >= 5) {
|
|
9832
10051
|
skipped += 1;
|
|
9833
10052
|
continue;
|
|
@@ -10722,7 +10941,7 @@ var AgentSlot = class {
|
|
|
10722
10941
|
lastActivityMs: 0
|
|
10723
10942
|
};
|
|
10724
10943
|
}
|
|
10725
|
-
async start(
|
|
10944
|
+
async start() {
|
|
10726
10945
|
const sdk = (await this.clientConnection.bindAgent(this.config.agentId, this.config.runtimeType ?? this.config.type, this.config.runtimeVersion)).sdk;
|
|
10727
10946
|
const agent = await sdk.register();
|
|
10728
10947
|
this.logger.info({ displayName: agent.displayName }, "agent bound");
|
|
@@ -10743,6 +10962,8 @@ var AgentSlot = class {
|
|
|
10743
10962
|
throw new Error(`Hub unreachable while loading agent config: ${msg}`);
|
|
10744
10963
|
}
|
|
10745
10964
|
this.inboxId = agent.inboxId;
|
|
10965
|
+
const contextTreeBinding = await syncAgentContextTree(sdk, (msg) => this.logger.info(msg));
|
|
10966
|
+
if (!contextTreeBinding) this.logger.info("context tree not configured or sync skipped — agent will start without organizational context");
|
|
10746
10967
|
const onInboxDeliver = (inboxId, frame) => {
|
|
10747
10968
|
if (inboxId !== this.inboxId) return;
|
|
10748
10969
|
this.dispatchPushedFrame(frame).catch((err) => {
|
|
@@ -11593,14 +11814,6 @@ var ClientRuntime = class {
|
|
|
11593
11814
|
watcher = null;
|
|
11594
11815
|
debounceTimer = null;
|
|
11595
11816
|
/**
|
|
11596
|
-
* Per-org Context Tree binding resolved at `start()`. Threaded through every
|
|
11597
|
-
* `slot.start()` so handlers can copy `AGENT.md` / root `NODE.md` into the
|
|
11598
|
-
* agent workspace's `.agent/context/` and install the first-tree skill.
|
|
11599
|
-
* `null` when the user has no primary org, the org has no tree configured,
|
|
11600
|
-
* or git sync failed — handlers degrade gracefully (empty context dir).
|
|
11601
|
-
*/
|
|
11602
|
-
contextTreeBinding = null;
|
|
11603
|
-
/**
|
|
11604
11817
|
* Directory we write auto-registered agent configs into (same path that
|
|
11605
11818
|
* `first-tree-hub agent add` uses). Set by `watchAgentsDir` so the
|
|
11606
11819
|
* `agent:pinned` handler knows where to materialise new configs.
|
|
@@ -11671,7 +11884,6 @@ var ClientRuntime = class {
|
|
|
11671
11884
|
} catch (err) {
|
|
11672
11885
|
print.status("⚠️", `git-mirror orphan sweep failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
11673
11886
|
}
|
|
11674
|
-
this.contextTreeBinding = await syncContextTree(this.serverUrl, (opts) => ensureFreshAccessToken(opts), (msg) => print.status("[context-tree]", msg), CLI_USER_AGENT);
|
|
11675
11887
|
if (this.options.currentVersion && this.options.update) this.updateManager = UpdateManager.attach(this.connection, {
|
|
11676
11888
|
currentVersion: this.options.currentVersion,
|
|
11677
11889
|
...this.options.update,
|
|
@@ -11690,7 +11902,7 @@ var ClientRuntime = class {
|
|
|
11690
11902
|
}
|
|
11691
11903
|
await Promise.allSettled(this.agents.map(async (agent) => {
|
|
11692
11904
|
try {
|
|
11693
|
-
const identity = await agent.slot.start(
|
|
11905
|
+
const identity = await agent.slot.start();
|
|
11694
11906
|
print.check(true, `${agent.name}: connected`, `agent: ${identity.displayName ?? identity.agentId}`);
|
|
11695
11907
|
} catch (error) {
|
|
11696
11908
|
const msg = error instanceof Error ? error.message : String(error);
|
|
@@ -11817,7 +12029,7 @@ var ClientRuntime = class {
|
|
|
11817
12029
|
startAgent(name) {
|
|
11818
12030
|
const entry = this.agents.find((a) => a.name === name);
|
|
11819
12031
|
if (!entry) return;
|
|
11820
|
-
entry.slot.start(
|
|
12032
|
+
entry.slot.start().then((identity) => {
|
|
11821
12033
|
print.check(true, `${name}: connected`, `agent: ${identity.displayName ?? identity.agentId}`);
|
|
11822
12034
|
}).catch((err) => {
|
|
11823
12035
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -13112,7 +13324,7 @@ async function onboardCreate(args) {
|
|
|
13112
13324
|
}
|
|
13113
13325
|
const runtimeAgent = args.type === "human" ? args.assistant : args.id;
|
|
13114
13326
|
if (args.feishuBotAppId && args.feishuBotAppSecret) {
|
|
13115
|
-
const { bindFeishuBot } = await import("./feishu-
|
|
13327
|
+
const { bindFeishuBot } = await import("./feishu-DLt219hJ.mjs");
|
|
13116
13328
|
const targetAgentUuid = args.type === "human" ? assistantUuid : primary.uuid;
|
|
13117
13329
|
if (!targetAgentUuid) print.line(`Warning: Cannot bind Feishu bot — no runtime agent available for "${args.id}".\n`);
|
|
13118
13330
|
else {
|