@prismshadow/penguin-server 0.2.8 → 0.2.10

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.
Files changed (37) hide show
  1. package/README.md +1 -0
  2. package/dist/api/types.d.ts +645 -188
  3. package/dist/auth-token.js +4 -2
  4. package/dist/auth-token.js.map +1 -1
  5. package/dist/{chunk-622KL7BQ.js → chunk-ADZJYDNC.js} +205 -144
  6. package/dist/chunk-ADZJYDNC.js.map +1 -0
  7. package/dist/{chunk-OMBFPEGQ.js → chunk-GOO5VUAB.js} +10 -8
  8. package/dist/chunk-GOO5VUAB.js.map +1 -0
  9. package/dist/chunk-HMTT2PX4.js +93 -0
  10. package/dist/chunk-HMTT2PX4.js.map +1 -0
  11. package/dist/chunk-HOS3W3UP.js +127 -0
  12. package/dist/chunk-HOS3W3UP.js.map +1 -0
  13. package/dist/{chunk-UGAFPYVX.js → chunk-Q65VPAQS.js} +2 -2
  14. package/dist/chunk-Q65VPAQS.js.map +1 -0
  15. package/dist/index.js +6994 -3108
  16. package/dist/index.js.map +1 -1
  17. package/dist/lock.d.ts +9 -1
  18. package/dist/lock.js +3 -1
  19. package/dist/machine-status.d.ts +18 -0
  20. package/dist/machine-status.js +37 -0
  21. package/dist/machine-status.js.map +1 -0
  22. package/dist/reset-admin-password.js +6 -4
  23. package/dist/reset-admin-password.js.map +1 -1
  24. package/package.json +6 -3
  25. package/web-dist/assets/addon-clipboard-1cARCaK9.js +1 -0
  26. package/web-dist/assets/{addon-fit-DHOzI3Ur.js → addon-fit-BWdzl8Er.js} +1 -1
  27. package/web-dist/assets/{addon-web-links-DC7759H1.js → addon-web-links-DjXuvn4Z.js} +1 -1
  28. package/web-dist/assets/{highlighter-CGPO6Q4b.js → highlighter-Cu43iYr8.js} +1 -1
  29. package/web-dist/assets/index-Bm9n7ZV-.js +457 -0
  30. package/web-dist/assets/index-DR4PrRzS.css +1 -0
  31. package/web-dist/assets/{xterm-DY5ko-BU.js → xterm-DKcY6q-I.js} +1 -1
  32. package/web-dist/index.html +2 -2
  33. package/dist/chunk-622KL7BQ.js.map +0 -1
  34. package/dist/chunk-OMBFPEGQ.js.map +0 -1
  35. package/dist/chunk-UGAFPYVX.js.map +0 -1
  36. package/web-dist/assets/index-CaFDHl-w.js +0 -451
  37. package/web-dist/assets/index-D8Bk9LtJ.css +0 -1
@@ -370,7 +370,7 @@ interface ModelInfo {
370
370
  interface ModelsResponse {
371
371
  /** Paired reference to the default Model. */
372
372
  defaultModel?: ModelRefDto;
373
- /** Vision model used as a proxy reader for read_image (describes images when the session model has vision=false). */
373
+ /** Vision model used as a proxy reader for read_file (describes images when the session model has vision=false). */
374
374
  visionModel?: ModelRefDto;
375
375
  /**
376
376
  * When the Project's model/credential config last changed (ISO; the config file's mtime,
@@ -416,7 +416,7 @@ interface ModelUpdateEntry {
416
416
  interface ModelsUpdateRequest {
417
417
  /** Must be included in models (matched by paired reference). */
418
418
  defaultModel?: ModelRefDto;
419
- /** Vision model used as a proxy reader for read_image: must be included in models and not annotated vision=false; omitted keeps the existing value. */
419
+ /** Vision model used as a proxy reader for read_file: must be included in models and not annotated vision=false; omitted keeps the existing value. */
420
420
  visionModel?: ModelRefDto;
421
421
  models: ModelUpdateEntry[];
422
422
  }
@@ -615,6 +615,13 @@ interface ModelOAuthStatusResponse {
615
615
  status: "pending" | "done" | "error";
616
616
  /** The provider group the flow mints a key for. */
617
617
  provider: string;
618
+ /**
619
+ * How many models the minted key was written to — set on the `done` answer, the way
620
+ * {@link ModelOAuthCodeResponse} sets it on a redemption. The dialog reports this number, so
621
+ * it has to be the server's own: the caller's model table can outlive a rejected save and
622
+ * would otherwise name a count the server never wrote.
623
+ */
624
+ applied?: number;
618
625
  error?: ModelOAuthErrorCode;
619
626
  }
620
627
  /**
@@ -701,12 +708,12 @@ interface VaultEntryUpdate {
701
708
  interface VaultUpdateRequest {
702
709
  entries: VaultEntryUpdate[];
703
710
  }
704
- /** One installed Skill the library carries a higher version of (see {@link AgentSummary.skillUpdates}). */
705
- interface SkillUpdateRef {
706
- /** Skill directory name — the identity install / uninstall address it by. */
711
+ /** One installed plugin (a skill or a hook package) the library carries a higher version of (see {@link AgentSummary.pluginUpdates}). */
712
+ interface PluginUpdateRef {
713
+ /** Plugin name — what `POST …/plugins` reinstalls to bring the Agent up to date. */
707
714
  name: string;
708
- /** The LIBRARY's version, i.e. what installing again would bring, not what is on disk. */
709
- version: number;
715
+ /** The LIBRARY's version (`YYYY.MM.DD.N`), i.e. what installing again would bring, not what is on disk. */
716
+ version: string;
710
717
  }
711
718
  interface AgentSummary {
712
719
  agentId: string;
@@ -733,14 +740,17 @@ interface AgentSummary {
733
740
  scheduleCount: number;
734
741
  /** Installed Skill count (number of agent_state/skills/<name>/ directories with a SKILL.md). */
735
742
  skillCount: number;
736
- /**
737
- * Installed Skills the built-in library has moved past, each with the library version on
738
- * offer — the Skill-library update gate, riding along on the Agent list so a badge over
739
- * that page costs no request of its own. Empty when nothing is behind. Skills that are not
740
- * in the library (installed from a zip or a picked directory) are never listed: there is no
743
+ /** Installed hook-package count (number of agent_state/hooks/<name>/ directories with a hooks.json). */
744
+ hookCount: number;
745
+ /**
746
+ * Installed plugins the built-in library has moved past — a skill or a hook package whose
747
+ * on-disk version is behind the library plugin that ships it — each with the library version
748
+ * on offer: the plugin-library update gate, riding along on the Agent list so a badge over
749
+ * that page costs no request of its own. Empty when nothing is behind. Skills the library
750
+ * does not carry (installed from a zip or a picked directory) are never listed: there is no
741
751
  * library version for them to be behind.
742
752
  */
743
- skillUpdates: SkillUpdateRef[];
753
+ pluginUpdates: PluginUpdateRef[];
744
754
  /** Memory count (topic files summed over the scope directories under agent_state/memory/, independent of the memory switch). */
745
755
  memoryCount: number;
746
756
  }
@@ -754,11 +764,12 @@ interface AgentCreateRequest {
754
764
  name?: string;
755
765
  description?: string;
756
766
  /**
757
- * Library Skill names installed into the new Agent, seeding it at creation. Every name must
758
- * exist in the library (404 `unknown_skill` otherwise, before anything is created); omitted or
759
- * empty leaves the Agent with no Skills, which is what a plain Agent gets by default.
767
+ * Library plugin names installed into the new Agent, seeding it at creation — each plugin's
768
+ * skills and hook package. Every name must exist in the library (404 `unknown_plugin`
769
+ * otherwise, before anything is created); omitted or empty leaves the Agent with nothing
770
+ * installed, which is what a plain Agent gets by default.
760
771
  */
761
- skills?: string[];
772
+ plugins?: string[];
762
773
  /**
763
774
  * Skills imported from a directory on disk instead of the library. `skillsDirectory` is the
764
775
  * absolute path the user picked and `directorySkills` are the names to install from it, read
@@ -771,7 +782,7 @@ interface AgentCreateRequest {
771
782
  /**
772
783
  * Base64 of an exported Agent State snapshot package (`.tar.gz`): the new Agent is
773
784
  * initialized from the package instead of the default template. Mutually exclusive with
774
- * skill seeding (`skills` / `skillsDirectory`) — the package carries its own skills.
785
+ * seeding (`plugins` / `skillsDirectory`) — the package carries its own skills and hooks.
775
786
  * Explicit `name` / `description` override the package's values; absent ones keep them.
776
787
  */
777
788
  dataBase64?: string;
@@ -833,6 +844,16 @@ interface AgentSchedulesConfigDto {
833
844
  /** Whether the stored template carries `{{SCHEDULES}}`; POST …/schedules/template-placeholder inserts it explicitly. */
834
845
  templateHasPlaceholder: boolean;
835
846
  }
847
+ /**
848
+ * Hook config, edited on the Hooks tab. One Agent-level switch and no prompt: hook packages
849
+ * are scripts run at the loop's hook points, not text injected into the context. `enabled`
850
+ * reports the effective value (a config with no `hooks` section reads as enabled, matching
851
+ * core).
852
+ */
853
+ interface AgentHooksConfigDto {
854
+ /** Whether a Session created from now on runs the installed hook packages (they stay installed either way). */
855
+ enabled: boolean;
856
+ }
836
857
  /** Structured view of system_config.yaml (for the edit form). */
837
858
  interface AgentConfigDto {
838
859
  name?: string;
@@ -853,6 +874,7 @@ interface AgentConfigDto {
853
874
  vault: AgentVaultConfigDto;
854
875
  skills: AgentSkillsConfigDto;
855
876
  schedules: AgentSchedulesConfigDto;
877
+ hooks: AgentHooksConfigDto;
856
878
  toolsBuiltin: ToolDefinitionConfig[];
857
879
  mcpServers: MCPServerConfig[];
858
880
  }
@@ -912,6 +934,10 @@ interface AgentConfigUpdateRequest {
912
934
  enabled?: boolean;
913
935
  prompt?: string;
914
936
  };
937
+ /** The Agent-level hook switch; it has no prompt half. */
938
+ hooks?: {
939
+ enabled?: boolean;
940
+ };
915
941
  toolsBuiltin?: ToolDefinitionConfig[];
916
942
  mcpServers?: MCPServerConfig[];
917
943
  };
@@ -1037,10 +1063,12 @@ interface SessionInfo {
1037
1063
  approvalMode: ApprovalMode;
1038
1064
  /**
1039
1065
  * Thinking level pinned for this Session (set via PATCH; the Web App's in-chat picker).
1040
- * Unset = never pinned: runs that carry no level of their own fall back to the Agent
1041
- * config's `model.thinking_level`, so config edits keep taking effect. Once pinned it
1042
- * survives reloads and applies to every later run of this Session, and an Agent-config
1043
- * change no longer moves it.
1066
+ * Unset = never pinned: each model context the Session opens reads the Agent config's
1067
+ * `model.thinking_level`, so config edits keep taking effect. Once pinned it survives
1068
+ * reloads and applies from the Session's very next LLM request — the thinking level is
1069
+ * the soft-limited runtime parameter, changeable mid-context at the cost of the
1070
+ * provider's cached context (the picker advises compacting first) — and every later
1071
+ * context opens at it; an Agent-config change no longer moves it.
1044
1072
  */
1045
1073
  thinkingLevel?: ThinkingLevelName;
1046
1074
  /** Short title auto-generated by the model after the first turn; unset until generated (frontend shows "New Chat"). */
@@ -1083,6 +1111,23 @@ interface SessionInfo {
1083
1111
  tracePath?: string;
1084
1112
  /** Present when the Session has an ENABLED messaging binding: its channel (the sidebar row's per-channel indicator). */
1085
1113
  messagingChannel?: MessagingChannel;
1114
+ /**
1115
+ * Background work the Session's loaded runtime still owns: command sessions running past
1116
+ * their yield window (`exec_command` promotions and `run_in_background` launches) and
1117
+ * background subagent sessions mid-round. Read from the runtime's in-memory registries —
1118
+ * no Trace is consulted — and present only while at least one count is non-zero, so a
1119
+ * Session that is not loaded (a resumed entry starts with empty registries) and one with
1120
+ * nothing running both omit it. Changes are pushed as `session_background` on the user
1121
+ * channel; list rows and the single-session GET carry the same field.
1122
+ */
1123
+ backgroundTasks?: SessionBackgroundTasks;
1124
+ }
1125
+ /** The background-task counts of one Session (see SessionInfo.backgroundTasks). */
1126
+ interface SessionBackgroundTasks {
1127
+ /** Background command sessions whose process is still running. */
1128
+ processes: number;
1129
+ /** Background subagent sessions (promoted to a `subagent_id`) currently mid-round. */
1130
+ subagents: number;
1086
1131
  }
1087
1132
  /**
1088
1133
  * Session list category, the sidebar's four-way split applied server-side: archived wins
@@ -1103,6 +1148,13 @@ interface SessionsResponse {
1103
1148
  * content that lives in other Workspaces.
1104
1149
  */
1105
1150
  workspaceCounts?: Record<string, SessionCategoryCounts>;
1151
+ /**
1152
+ * Present with `counts`: each Workspace path's newest Session (any category), as its
1153
+ * `createdAt`. With `workspaceCounts` this is what lets the sidebar list every Workspace
1154
+ * that holds Sessions and place the groups by recency before — or without — loading any
1155
+ * of their rows.
1156
+ */
1157
+ workspaceLatest?: Record<string, string>;
1106
1158
  }
1107
1159
  /** Server directory browsing (advanced new-Workspace picker): starts from the home directory by default, can navigate up to the root. */
1108
1160
  interface DirEntryInfo {
@@ -1177,9 +1229,10 @@ interface SessionPatchRequest {
1177
1229
  approvalMode?: ApprovalMode;
1178
1230
  /**
1179
1231
  * Pin this Session's thinking level (`none | low | medium | high | xhigh | max`, anything else
1180
- * is a 400). It applies to every later run of the Session that doesn't carry its own
1181
- * level, and replaces the Agent-config fallback for this Session. There is no unpin:
1182
- * the picker only offers concrete levels.
1232
+ * is a 400). It replaces the Agent-config fallback for this Session and applies from the
1233
+ * very next LLM request — soft-limited: a mid-context change is allowed, at the cost of
1234
+ * the provider's cached context, which is why the picker advises compacting first. There
1235
+ * is no unpin: the picker only offers concrete levels.
1183
1236
  */
1184
1237
  thinkingLevel?: ThinkingLevelName;
1185
1238
  /** Archive / unarchive (default list hides archived). */
@@ -1243,6 +1296,14 @@ interface MessagesPageInfo {
1243
1296
  prior: {
1244
1297
  subagentTokens: number;
1245
1298
  elapsedMs: number;
1299
+ /**
1300
+ * Model-API time and tool wall time accrued before this window, the breakdown of
1301
+ * `elapsedMs` the chat header shows under it. Seeded together with their total: seeding
1302
+ * one alone would put a full elapsed time beside components covering only the window.
1303
+ * The two may overlap and do not partition `elapsedMs` (see `TraceTaskStats.toolMs`).
1304
+ */
1305
+ apiMs: number;
1306
+ toolMs: number;
1246
1307
  sessionTokens: number;
1247
1308
  contextTokens: number;
1248
1309
  };
@@ -1300,14 +1361,6 @@ type TaskInputPart = {
1300
1361
  };
1301
1362
  interface TaskCreateRequest {
1302
1363
  input: TaskInputPart[];
1303
- /**
1304
- * Thinking level for this Task's LLM requests (a per-turn parameter; one of
1305
- * `none | low | medium | high | xhigh | max`, anything else is a 400). Omitted = falls back to
1306
- * the Session's pinned level (`SessionInfo.thinkingLevel`) and, when that is unset too,
1307
- * to the Agent config's `model.thinking_level`. A queued follow-up (`queueIfBusy`) keeps
1308
- * its level and applies it when it auto-starts.
1309
- */
1310
- thinkingLevel?: ThinkingLevelName;
1311
1364
  /**
1312
1365
  * Queue instead of 409 when a Task/compaction is already in progress: the input is held
1313
1366
  * server-side and auto-starts as an ordinary next task once the session returns to idle
@@ -1317,14 +1370,16 @@ interface TaskCreateRequest {
1317
1370
  /**
1318
1371
  * Present = goal mode: the input's text becomes the objective (leading `[use_skills]`
1319
1372
  * blocks and the like are stripped from the recorded objective; the round-1 message keeps
1320
- * them) and the server loops the Session until the goal reaches a terminal state.
1321
- * `budget` is the token budget (uncached input + output); omitted or -1 = unlimited.
1373
+ * them) and the Session's installed `goal` hook package drives round after round until the
1374
+ * goal reaches a terminal state. `budget` is the token budget (uncached input + output);
1375
+ * omitted or -1 = unlimited. Requires the goal plugin installed on the Agent — 409
1376
+ * `goal_plugin_not_installed` otherwise.
1322
1377
  */
1323
1378
  goal?: {
1324
1379
  budget?: number;
1325
1380
  };
1326
1381
  }
1327
- /** Goal-mode run state (from goal_state; the chat page's banner restores from the latest row). */
1382
+ /** Goal-mode run state, read from the Session's GOAL.json — the goal plugin's file (the chat page's banner restores from it; a live status on an idle Session reads as `aborted`). */
1328
1383
  interface GoalStateView {
1329
1384
  objective: string;
1330
1385
  status: "active" | "complete" | "blocked" | "budget_limited" | "aborted";
@@ -1332,7 +1387,6 @@ interface GoalStateView {
1332
1387
  budget: number;
1333
1388
  used: number;
1334
1389
  rounds: number;
1335
- updatedAt: string;
1336
1390
  }
1337
1391
  interface GoalResponse {
1338
1392
  /** The Session's most recent goal run; null if it never ran one. */
@@ -1448,8 +1502,6 @@ interface RecalledMessageResponse {
1448
1502
  fileName: string;
1449
1503
  dataUrl: string;
1450
1504
  }[];
1451
- /** Follow-up recall only: the per-turn thinking level the entry was queued with, when one was. */
1452
- thinkingLevel?: ThinkingLevelName;
1453
1505
  }
1454
1506
  interface ApprovalDecisionRequest {
1455
1507
  decision: "allow" | "deny";
@@ -1483,7 +1535,7 @@ interface SessionProcessesResponse {
1483
1535
  processes: SessionProcessInfo[];
1484
1536
  }
1485
1537
  /** Messaging channels a Session can bind to. */
1486
- type MessagingChannel = "feishu" | "telegram" | "qq";
1538
+ type MessagingChannel = "feishu" | "telegram" | "qq" | "wechat";
1487
1539
  /** Event-connection runtime state of one binding (kept in memory, not persisted). */
1488
1540
  type MessagingRuntimeState = "disconnected" | "connecting" | "connected" | "error";
1489
1541
  /**
@@ -1542,18 +1594,24 @@ interface MessagingRuntimeStatus {
1542
1594
  detail: string;
1543
1595
  };
1544
1596
  }
1545
- /** The stored Feishu config, secret masked (plaintext never leaves the server). */
1546
- interface FeishuBindingInfo {
1547
- channel: "feishu";
1597
+ /**
1598
+ * What a messaging binding carries whatever channel it is: the connection intent, the three
1599
+ * delivery preferences, whether a chat is known, and the row's identity and timestamps.
1600
+ *
1601
+ * Split out because this is the half that grows. Three delivery preferences arrived in one
1602
+ * release, and each would otherwise have been written into three interfaces — the credential
1603
+ * slice is the only thing a channel genuinely differs in, and it is what stays per-channel.
1604
+ *
1605
+ * `channel` is NOT here: a discriminated union discriminates on a literal the member itself
1606
+ * declares, so each interface keeps its own.
1607
+ *
1608
+ * A channel may RE-DECLARE a field below when it has more to say about it — QQ does, for all
1609
+ * three preferences, because the platform's passive-reply budget and its expiring window
1610
+ * change what setting one costs there. The re-declared type must be identical, which the
1611
+ * compiler enforces, so the redeclaration can only add documentation and never a contract.
1612
+ */
1613
+ interface MessagingBindingCommon {
1548
1614
  sessionId: string;
1549
- appId: string;
1550
- /**
1551
- * Masked app secret (site-wide mask rule: `***`, or `first4…last4` for long values);
1552
- * absent when no secret is stored (never entered, or cleared) — the binding cannot be
1553
- * enabled until one is saved.
1554
- */
1555
- appSecretMasked?: string;
1556
- baseDomain: string;
1557
1615
  /** Connection INTENT (the state toggle's value); new bindings start disabled, and at most one of a Session's channels is enabled. */
1558
1616
  enabled: boolean;
1559
1617
  /**
@@ -1571,23 +1629,35 @@ interface FeishuBindingInfo {
1571
1629
  /**
1572
1630
  * Render a relayed reply's Markdown in this channel's own markup instead of sending its
1573
1631
  * characters as written. ON by default. Each channel shows what it can — Telegram has no
1574
- * headings, lists or tables, QQ has no code or tables, Feishu has all of them — and a
1632
+ * headings, lists or tables, QQ has no code or tables, Feishu has all of them, and WeChat
1633
+ * reads Markdown itself so the render is a subtraction rather than a translation — and a
1575
1634
  * rendering the channel refuses falls back to the plain source, so this can cost
1576
1635
  * formatting and never a message.
1577
1636
  */
1578
1637
  renderMarkdown: boolean;
1579
1638
  /**
1580
- * Whether an inbound Feishu chat is known (the bot has been messaged at least once).
1581
- * Replies and test messages target that chat; until it exists nothing can be sent.
1639
+ * Whether an inbound chat is known (the bot has been messaged at least once). Replies and
1640
+ * test messages target that chat; until it exists nothing can be sent.
1582
1641
  */
1583
1642
  lastChatKnown: boolean;
1584
1643
  createdAt: string;
1585
1644
  updatedAt: string;
1586
1645
  }
1646
+ /** The stored Feishu config, secret masked (plaintext never leaves the server). */
1647
+ interface FeishuBindingInfo extends MessagingBindingCommon {
1648
+ channel: "feishu";
1649
+ appId: string;
1650
+ /**
1651
+ * Masked app secret (site-wide mask rule: `***`, or `first4…last4` for long values);
1652
+ * absent when no secret is stored (never entered, or cleared) — the binding cannot be
1653
+ * enabled until one is saved.
1654
+ */
1655
+ appSecretMasked?: string;
1656
+ baseDomain: string;
1657
+ }
1587
1658
  /** The stored Telegram config, token masked (plaintext never leaves the server). */
1588
- interface TelegramBindingInfo {
1659
+ interface TelegramBindingInfo extends MessagingBindingCommon {
1589
1660
  channel: "telegram";
1590
- sessionId: string;
1591
1661
  /** The numeric bot id (the token's half before the colon) — the channel-scoped account identity, never secret. */
1592
1662
  botId: string;
1593
1663
  /**
@@ -1596,40 +1666,15 @@ interface TelegramBindingInfo {
1596
1666
  * is saved.
1597
1667
  */
1598
1668
  botTokenMasked?: string;
1599
- /** Connection INTENT (the state toggle's value); new bindings start disabled, and at most one of a Session's channels is enabled. */
1600
- enabled: boolean;
1601
- /**
1602
- * Send each non-blank line of a relayed assistant reply as its own message instead of one
1603
- * message per reply. Off by default; off is the original one-message-per-reply behaviour.
1604
- */
1605
- linePerMessage: boolean;
1606
- /**
1607
- * Relay only the LAST completed assistant message of a run, delivered when the run ends,
1608
- * instead of mirroring each completed message as it completes. Off by default; off is the
1609
- * original every-message behaviour. Independent of `linePerMessage`, which then applies to
1610
- * that one final message. The approval notice is not a reply and is unaffected.
1611
- */
1612
- finalReplyOnly: boolean;
1613
- /**
1614
- * Render a relayed reply's Markdown in this channel's own markup instead of sending its
1615
- * characters as written. ON by default. Each channel shows what it can — Telegram has no
1616
- * headings, lists or tables, QQ has no code or tables, Feishu has all of them — and a
1617
- * rendering the channel refuses falls back to the plain source, so this can cost
1618
- * formatting and never a message.
1619
- */
1620
- renderMarkdown: boolean;
1621
- /**
1622
- * Whether an inbound Telegram chat is known (the bot has been messaged at least once).
1623
- * Replies and test messages target that chat; until it exists nothing can be sent.
1624
- */
1625
- lastChatKnown: boolean;
1626
- createdAt: string;
1627
- updatedAt: string;
1628
1669
  }
1629
- /** The stored QQ config, secret masked (plaintext never leaves the server). */
1630
- interface QQBindingInfo {
1670
+ /**
1671
+ * The stored QQ config, secret masked (plaintext never leaves the server).
1672
+ *
1673
+ * The three preference fields are re-declared unchanged: every one of them costs something
1674
+ * different here, and the platform's reply budget is what a reader setting one needs to know.
1675
+ */
1676
+ interface QQBindingInfo extends MessagingBindingCommon {
1631
1677
  channel: "qq";
1632
- sessionId: string;
1633
1678
  /** The bot's App ID — the channel-scoped account identity, never secret. */
1634
1679
  appId: string;
1635
1680
  /**
@@ -1638,8 +1683,6 @@ interface QQBindingInfo {
1638
1683
  * enabled until one is saved.
1639
1684
  */
1640
1685
  appSecretMasked?: string;
1641
- /** Connection INTENT (the state toggle's value); new bindings start disabled, and at most one of a Session's channels is enabled. */
1642
- enabled: boolean;
1643
1686
  /**
1644
1687
  * Send each non-blank line of a relayed assistant reply as its own message instead of one
1645
1688
  * message per reply. Off by default. On QQ the split is additionally capped at the
@@ -1648,10 +1691,8 @@ interface QQBindingInfo {
1648
1691
  */
1649
1692
  linePerMessage: boolean;
1650
1693
  /**
1651
- * Relay only the LAST completed assistant message of a run, delivered when the run ends,
1652
- * instead of mirroring each completed message as it completes. Off by default; off is the
1653
- * original every-message behaviour. Independent of `linePerMessage`, which then applies to
1654
- * that one final message. The approval notice is not a reply and is unaffected.
1694
+ * Relay only the LAST completed assistant message of a run, delivered when the run ends.
1695
+ * Off by default.
1655
1696
  *
1656
1697
  * On QQ it cuts both ways, which is worth knowing before setting it here. It spends the
1657
1698
  * least of the platform's passive-reply budget a run can spend — one message, where an
@@ -1662,25 +1703,41 @@ interface QQBindingInfo {
1662
1703
  */
1663
1704
  finalReplyOnly: boolean;
1664
1705
  /**
1665
- * Render a relayed reply's Markdown in this channel's own markup instead of sending its
1666
- * characters as written. ON by default. QQ's own subset is the widest for prose and the
1667
- * narrowest for code: headings, lists, blockquotes and rules render, while inline code,
1668
- * fenced code and tables have no syntax at all here — a code block arrives as plain
1669
- * escaped lines. A rendering the platform refuses falls back to the plain source, at the
1670
- * cost of one more slot from the passive-reply budget.
1706
+ * Render a relayed reply's Markdown in this channel's own markup. ON by default. QQ's own
1707
+ * subset is the widest for prose and the narrowest for code: headings, lists, blockquotes
1708
+ * and rules render, while inline code, fenced code and tables have no syntax at all here —
1709
+ * a code block arrives as plain escaped lines. A rendering the platform refuses falls back
1710
+ * to the plain source, at the cost of one more slot from the passive-reply budget.
1671
1711
  */
1672
1712
  renderMarkdown: boolean;
1673
1713
  /**
1674
- * Whether an inbound QQ chat is known (the bot has been messaged at least once).
1675
- * Weaker than it looks on this channel: QQ accepts only replies to a recent message, so a
1676
- * known chat is necessary but not sufficient for anything to be deliverable right now.
1714
+ * Whether an inbound QQ chat is known (the bot has been messaged at least once). Weaker
1715
+ * than it looks on this channel: QQ accepts only replies to a recent message, so a known
1716
+ * chat is necessary but not sufficient for anything to be deliverable right now.
1677
1717
  */
1678
1718
  lastChatKnown: boolean;
1679
- createdAt: string;
1680
- updatedAt: string;
1719
+ }
1720
+ /**
1721
+ * The stored WeChat config, token masked (plaintext never leaves the server).
1722
+ *
1723
+ * No preference field is re-declared. The three delivery preferences cost exactly what they
1724
+ * cost on Feishu and Telegram here — this channel has no reply budget and no expiring reply
1725
+ * window — and re-declaring a field to say nothing new about it would only invite the next
1726
+ * reader to look for the difference.
1727
+ */
1728
+ interface WeChatBindingInfo extends MessagingBindingCommon {
1729
+ channel: "wechat";
1730
+ /** The bot id a scan issued — the channel-scoped account identity, never secret. */
1731
+ botId: string;
1732
+ /**
1733
+ * Masked bot token (site-wide mask rule: `***`, or `first4…last4` for long values);
1734
+ * absent when none is stored (cleared, or a scan that never completed) — the binding
1735
+ * cannot be enabled until a scan saves one.
1736
+ */
1737
+ botTokenMasked?: string;
1681
1738
  }
1682
1739
  /** A Session's saved config for one messaging channel (`channel` is the discriminant). */
1683
- type MessagingBindingInfo = FeishuBindingInfo | TelegramBindingInfo | QQBindingInfo;
1740
+ type MessagingBindingInfo = FeishuBindingInfo | TelegramBindingInfo | QQBindingInfo | WeChatBindingInfo;
1684
1741
  /** One saved channel config with its event-connection runtime status. */
1685
1742
  interface MessagingChannelState {
1686
1743
  binding: MessagingBindingInfo;
@@ -1710,12 +1767,32 @@ interface QQBindingResponse {
1710
1767
  binding: QQBindingInfo | null;
1711
1768
  status: MessagingRuntimeStatus;
1712
1769
  }
1770
+ /** GET / PUT …/messaging/wechat response (the WeChat narrowing of the same envelope). */
1771
+ interface WeChatBindingResponse {
1772
+ binding: WeChatBindingInfo | null;
1773
+ status: MessagingRuntimeStatus;
1774
+ }
1713
1775
  /**
1714
1776
  * PUT …/messaging/feishu — saves credentials/config ONLY, never flipping the connection
1715
1777
  * (exception: an enabled binding's connector restarts with the new credentials so stored
1716
1778
  * config and live connection never diverge). The connection toggle is POST …/state.
1717
1779
  */
1718
- interface FeishuBindingPutRequest {
1780
+ /**
1781
+ * The delivery preferences any channel's PUT may set. Each is optional and each behaves the
1782
+ * same way on every channel: an omitted field keeps the stored value, so a client that knows
1783
+ * about one preference can save it without having to send the others back. Their meanings are
1784
+ * on {@link MessagingBindingCommon}, and where a channel charges differently for one, on that
1785
+ * channel's own `*BindingInfo`.
1786
+ *
1787
+ * The defaults a binding created without them starts from: `linePerMessage` off,
1788
+ * `finalReplyOnly` off, `renderMarkdown` ON.
1789
+ */
1790
+ interface MessagingDeliveryPatch {
1791
+ linePerMessage?: boolean;
1792
+ finalReplyOnly?: boolean;
1793
+ renderMarkdown?: boolean;
1794
+ }
1795
+ interface FeishuBindingPutRequest extends MessagingDeliveryPatch {
1719
1796
  appId: string;
1720
1797
  /** Omitted or blank keeps the stored secret (the masked value never round-trips). */
1721
1798
  appSecret?: string;
@@ -1726,21 +1803,6 @@ interface FeishuBindingPutRequest {
1726
1803
  * it). Refused with 409 `messaging_disable_before_clear` while the binding is enabled.
1727
1804
  */
1728
1805
  clearAppSecret?: boolean;
1729
- /**
1730
- * Delivery preference (see `FeishuBindingInfo.linePerMessage`). Omitted keeps the stored
1731
- * value; a binding created without it starts with it off.
1732
- */
1733
- linePerMessage?: boolean;
1734
- /**
1735
- * Delivery preference (see `FeishuBindingInfo.finalReplyOnly`). Omitted keeps the stored
1736
- * value; a binding created without it starts with it off.
1737
- */
1738
- finalReplyOnly?: boolean;
1739
- /**
1740
- * Delivery preference (see `FeishuBindingInfo.renderMarkdown`). Omitted keeps the stored
1741
- * value; a binding created without it starts with it ON.
1742
- */
1743
- renderMarkdown?: boolean;
1744
1806
  }
1745
1807
  /**
1746
1808
  * PUT …/messaging/telegram — saves the credential ONLY, same contract as the Feishu PUT
@@ -1748,7 +1810,7 @@ interface FeishuBindingPutRequest {
1748
1810
  * POST …/state). Saving never conflicts across Sessions: the same bot may sit saved on
1749
1811
  * several, and only enabling it is exclusive.
1750
1812
  */
1751
- interface TelegramBindingPutRequest {
1813
+ interface TelegramBindingPutRequest extends MessagingDeliveryPatch {
1752
1814
  /** Omitted or blank keeps the stored token (the masked value never round-trips). */
1753
1815
  botToken?: string;
1754
1816
  /**
@@ -1757,21 +1819,6 @@ interface TelegramBindingPutRequest {
1757
1819
  * `messaging_disable_before_clear` while the binding is enabled.
1758
1820
  */
1759
1821
  clearBotToken?: boolean;
1760
- /**
1761
- * Delivery preference (see `TelegramBindingInfo.linePerMessage`). Omitted keeps the stored
1762
- * value; a binding created without it starts with it off.
1763
- */
1764
- linePerMessage?: boolean;
1765
- /**
1766
- * Delivery preference (see `TelegramBindingInfo.finalReplyOnly`). Omitted keeps the stored
1767
- * value; a binding created without it starts with it off.
1768
- */
1769
- finalReplyOnly?: boolean;
1770
- /**
1771
- * Delivery preference (see `TelegramBindingInfo.renderMarkdown`). Omitted keeps the stored
1772
- * value; a binding created without it starts with it ON.
1773
- */
1774
- renderMarkdown?: boolean;
1775
1822
  }
1776
1823
  /**
1777
1824
  * PUT …/messaging/qq — saves the credential pair ONLY, same contract as the Feishu PUT
@@ -1779,7 +1826,7 @@ interface TelegramBindingPutRequest {
1779
1826
  * is POST …/state). The App ID is the account identity, so changing it rebinds the row to
1780
1827
  * a different bot and drops the remembered chat.
1781
1828
  */
1782
- interface QQBindingPutRequest {
1829
+ interface QQBindingPutRequest extends MessagingDeliveryPatch {
1783
1830
  appId: string;
1784
1831
  /** Omitted or blank keeps the stored secret (the masked value never round-trips). */
1785
1832
  appSecret?: string;
@@ -1788,21 +1835,23 @@ interface QQBindingPutRequest {
1788
1835
  * it). Refused with 409 `messaging_disable_before_clear` while the binding is enabled.
1789
1836
  */
1790
1837
  clearAppSecret?: boolean;
1838
+ }
1839
+ /**
1840
+ * PUT …/messaging/wechat — the delivery preferences ONLY.
1841
+ *
1842
+ * The one PUT on this router that carries no credential, because there is none to carry: a
1843
+ * WeChat bot token exists only where a scan put it, and there is no console to copy one out
1844
+ * of. Saving therefore presupposes a binding — a PUT before any scan answers 400
1845
+ * `wechat_token_required` rather than creating an empty row — and the connection toggle
1846
+ * stays POST …/state, as on every channel.
1847
+ */
1848
+ interface WeChatBindingPutRequest extends MessagingDeliveryPatch {
1791
1849
  /**
1792
- * Delivery preference (see `QQBindingInfo.linePerMessage`). Omitted keeps the stored
1793
- * value; a binding created without it starts with it off.
1794
- */
1795
- linePerMessage?: boolean;
1796
- /**
1797
- * Delivery preference (see `QQBindingInfo.finalReplyOnly`). Omitted keeps the stored
1798
- * value; a binding created without it starts with it off.
1799
- */
1800
- finalReplyOnly?: boolean;
1801
- /**
1802
- * Delivery preference (see `QQBindingInfo.renderMarkdown`). Omitted keeps the stored
1803
- * value; a binding created without it starts with it ON.
1850
+ * Drops the STORED token (the models-page clear idiom). Refused with 409
1851
+ * `messaging_disable_before_clear` while the binding is enabled. The row and its bot
1852
+ * identity stay; only a fresh scan can make it connectable again.
1804
1853
  */
1805
- renderMarkdown?: boolean;
1854
+ clearBotToken?: boolean;
1806
1855
  }
1807
1856
  /**
1808
1857
  * POST …/messaging/<channel>/state — enable connects with the STORED credentials,
@@ -1895,6 +1944,82 @@ interface QQScanPollResponse {
1895
1944
  /** The saved binding, present on `completed` so the editor refreshes without a second GET. */
1896
1945
  binding?: QQBindingInfo;
1897
1946
  }
1947
+ /**
1948
+ * WeChat credential-test outcome. There is no request body and no draft to probe: this
1949
+ * channel's credential exists only where a scan put it, so the test always probes the STORED
1950
+ * binding. It answers 400 `wechat_token_required` when there is none.
1951
+ *
1952
+ * No account label, for the same reason QQ has none: the probe (`getconfig`) reports the
1953
+ * bot's settings and names neither the bot nor the person.
1954
+ */
1955
+ interface WeChatTestResponse {
1956
+ ok: boolean;
1957
+ latencyMs?: number;
1958
+ error?: string;
1959
+ }
1960
+ /**
1961
+ * POST …/messaging/wechat/scan — starts a scan-to-connect flow, which on this channel is the
1962
+ * ONLY way to bind: the bot token has no console to be copied out of.
1963
+ *
1964
+ * The server registers a code and answers with what the browser may know. The platform's own
1965
+ * poll handle is absent from this type on purpose — it is what collects the bot token, so it
1966
+ * never leaves the server, and `taskId` is a handle this server mints in its place.
1967
+ */
1968
+ interface WeChatScanStartResponse {
1969
+ /** Opaque scan handle, passed back to the poll, verify and cancel endpoints. */
1970
+ taskId: string;
1971
+ /** The URL to ENCODE into a QR code. It is opened by WeChat, never fetched by the browser. */
1972
+ qrUrl: string;
1973
+ /** How often to poll, in milliseconds. */
1974
+ pollMs: number;
1975
+ }
1976
+ /**
1977
+ * Where a WeChat scan stands. Richer than QQ's four states because the flow is: WeChat
1978
+ * separates scanning from confirming, and may interpose a pairing code shown on the phone.
1979
+ *
1980
+ * - `pending` — the code is up and nothing has happened; keep polling.
1981
+ * - `scanned` — scanned; the phone is showing the confirmation prompt.
1982
+ * - `need_verify_code` — the phone is showing digits that must be sent to the verify
1983
+ * endpoint before the bind proceeds.
1984
+ * - `blocked` — too many wrong pairing codes; the code is spent and a new scan is needed.
1985
+ * - `expired` — the code lapsed before it was used; show a new one.
1986
+ * - `already_bound` — this bot is already bound to this server, so no new credentials were
1987
+ * issued and nothing was saved. Not a failure: the existing binding still works.
1988
+ * - `completed` — the server has already SAVED the binding; `botId` names the bot.
1989
+ */
1990
+ type WeChatScanStatus = "pending" | "scanned" | "need_verify_code" | "blocked" | "expired" | "already_bound" | "completed";
1991
+ /**
1992
+ * POST …/messaging/wechat/scan/poll — one step of the scan.
1993
+ *
1994
+ * `completed` means the server has decrypted nothing and stored everything: the credentials
1995
+ * went from the platform into storage without passing through the browser. Saving is all it
1996
+ * does — enabling the connection stays the separate, exclusive act it is on every channel. A
1997
+ * task id that is unknown, belongs to another Session, or was already resolved answers 404
1998
+ * `wechat_scan_task_unknown`.
1999
+ *
2000
+ * Unlike QQ's poll, an overlapping request answers `pending` rather than 404: the upstream
2001
+ * call is a LONG poll, so one of them spans several of the client's intervals, and that
2002
+ * overlap is the normal rhythm rather than a replay.
2003
+ */
2004
+ interface WeChatScanPollResponse {
2005
+ status: WeChatScanStatus;
2006
+ /** The bound bot's id; present only on `completed`. Never the token. */
2007
+ botId?: string;
2008
+ /** The saved binding, present on `completed` so the editor refreshes without a second GET. */
2009
+ binding?: WeChatBindingInfo;
2010
+ }
2011
+ /**
2012
+ * POST …/messaging/wechat/scan/verify — the digits WeChat showed on the phone.
2013
+ *
2014
+ * They ride the NEXT poll rather than a request of their own, because the platform takes the
2015
+ * pairing code as a parameter of the status call: this endpoint records them and answers
2016
+ * 204. A wrong code is not reported here — the next poll asks for one again, which is how
2017
+ * the platform reports it.
2018
+ */
2019
+ interface WeChatScanVerifyRequest {
2020
+ taskId: string;
2021
+ verifyCode: string;
2022
+ }
1898
2023
  /**
1899
2024
  * POST …/messaging/<channel>/test-message — sent to the last known chat (409
1900
2025
  * `feishu_no_chat` / `telegram_no_chat` / `qq_no_chat` before one exists). On QQ this can
@@ -1922,6 +2047,13 @@ type ServerEvent =
1922
2047
  queued?: number;
1923
2048
  /** Steering messages queued but not yet delivered (absent = none): lets the composer's hint and its content survive reloads. */
1924
2049
  pendingSteering?: PendingSteeringInfo[];
2050
+ /**
2051
+ * Steering the run ended without delivering (absent = none) — an interrupt while a tool
2052
+ * was running is the ordinary way to produce one. Handed back rather than discarded: the
2053
+ * composer recalls each by the same handle and restores it into the draft, so the typed
2054
+ * message returns to the input box instead of being lost.
2055
+ */
2056
+ returnedSteering?: PendingSteeringInfo[];
1925
2057
  /** Queued follow-up tasks awaiting auto-start (absent = none): per-entry content + recall handle, alongside the `queued` count. */
1926
2058
  pendingFollowUps?: PendingFollowUpInfo[];
1927
2059
  /**
@@ -1963,6 +2095,19 @@ type ServerEvent =
1963
2095
  lastActiveAt: string;
1964
2096
  hasTrace: boolean;
1965
2097
  }
2098
+ /**
2099
+ * A Session's background-task counts changed: a command promoted to the background, a
2100
+ * process that exited or was stopped, a background subagent starting or settling a round,
2101
+ * a released session. The counts are the row's `backgroundTasks` as they stand after the
2102
+ * change — zeros included, so a list can clear its mark without refetching. Published to
2103
+ * the user channels of the Project's owner and members, like `session_state`.
2104
+ */
2105
+ | {
2106
+ type: "session_background";
2107
+ sessionId: string;
2108
+ processes: number;
2109
+ subagents: number;
2110
+ }
1966
2111
  /** Last-Event-ID has been evicted from the buffer: the frontend should re-fetch the history endpoint before continuing to consume this connection. */
1967
2112
  | {
1968
2113
  type: "resync_required";
@@ -2053,6 +2198,24 @@ interface ContextToolShare {
2053
2198
  name: string;
2054
2199
  tokens: number;
2055
2200
  }
2201
+ /**
2202
+ * One file's share of the context: its `read_file` / `edit_file` / `write_file` calls plus their
2203
+ * results, keyed by the file each call named.
2204
+ */
2205
+ interface ContextFileShare {
2206
+ /**
2207
+ * Workspace-relative when the file is inside the Session's Workspace; otherwise absolute, with
2208
+ * the home directory shortened to `~`. Spellings that resolve to the same file are one row.
2209
+ */
2210
+ path: string;
2211
+ tokens: number;
2212
+ /** How many calls of each file tool named the file in this context. */
2213
+ ops: {
2214
+ read: number;
2215
+ edit: number;
2216
+ write: number;
2217
+ };
2218
+ }
2056
2219
  /**
2057
2220
  * What the Session's current model context is made of — the part derived from its messages.
2058
2221
  *
@@ -2063,7 +2226,8 @@ interface ContextToolShare {
2063
2226
  *
2064
2227
  * The six parts partition the context and sum to `total`; `topTools` is a ranking inside
2065
2228
  * `toolRequests + toolResults` and can sum to less than those two (a result whose call was not
2066
- * recorded in the same Trace shard has no tool to be attributed to).
2229
+ * recorded in the same Trace shard has no tool to be attributed to). `topFiles` is the same
2230
+ * ranking narrowed to the three file tools and keyed by the file each call named.
2067
2231
  */
2068
2232
  interface SessionContextParts {
2069
2233
  systemPrompt: number;
@@ -2076,6 +2240,8 @@ interface SessionContextParts {
2076
2240
  total: number;
2077
2241
  /** Tools ranked by the context their traffic occupies, descending; at most five. */
2078
2242
  topTools: ContextToolShare[];
2243
+ /** Files ranked by the context their file-tool traffic occupies, descending; at most five. */
2244
+ topFiles: ContextFileShare[];
2079
2245
  /**
2080
2246
  * A completed compaction closed the context these figures describe, and the next one has not
2081
2247
  * been written yet: the composition is of what was compacted away, not of what the model now
@@ -2195,6 +2361,16 @@ interface TraceTaskStats {
2195
2361
  cacheWrite: number;
2196
2362
  output: number;
2197
2363
  };
2364
+ /**
2365
+ * This turn's cost in USD: each Request's three buckets at the Project's current rates for
2366
+ * the file's model, at the tier that Request's own timestamp fell in — the rule and the
2367
+ * price lookup the cost center applies to the usage row the same `token_usage` produced,
2368
+ * so this figure, the toolbar's and the cost center's agree on what a request cost. Present
2369
+ * on every turn of a priced file (a turn with no Request reads 0); absent when the model has
2370
+ * no pricing or the file's head names no provider, and then absent from the response's
2371
+ * total as well.
2372
+ */
2373
+ cost?: number;
2198
2374
  /**
2199
2375
  * Total LLM generation duration for this turn (the denominator for output TPS; human
2200
2376
  * approval wait already deducted). The numerator is simply `tokens.output`: since
@@ -2202,6 +2378,21 @@ interface TraceTaskStats {
2202
2378
  * output — there's no second figure to reconcile.
2203
2379
  */
2204
2380
  llmMs: number;
2381
+ /**
2382
+ * Wall-clock time this turn spent executing tools: the **union** of its tool spans'
2383
+ * execution intervals (`approvalTs ?? callTs` to `outputTs`), so tools running in parallel
2384
+ * are counted once instead of summed. Two exclusions, both deliberate: the human approval
2385
+ * wait (`callTs` to `approvalTs`) is not tool work, and the argument-generation segment is
2386
+ * the model streaming arguments, already counted in `llmMs`. A span with no `outputTs` —
2387
+ * still running when the file ended, or interrupted — contributes nothing rather than being
2388
+ * extrapolated to now.
2389
+ *
2390
+ * This and `llmMs` may **overlap**: a tool started in the background keeps running while the
2391
+ * model decodes. They are two measured components of the turn's duration, not a partition of
2392
+ * it, and they need not add up to the turn's span (approval waits and harness overhead belong
2393
+ * to neither). Never derive one by subtracting the other from the duration.
2394
+ */
2395
+ toolMs: number;
2205
2396
  }
2206
2397
  /** Duration span of a single tool call (complete tool_call message → paired tool_call_output). */
2207
2398
  interface ToolCallSpan {
@@ -2224,6 +2415,21 @@ interface WorkspaceFilesResponse {
2224
2415
  path: string;
2225
2416
  entries: WorkspaceFileEntry[];
2226
2417
  }
2418
+ /** Write one Workspace file whole (the Upload button, a drop, and the Files panel's editor). */
2419
+ interface FilesWriteRequest {
2420
+ /** The entire file, base64-encoded (≤14MB decoded). */
2421
+ dataBase64: string;
2422
+ /**
2423
+ * Write precondition: the version marker the caller read off this file's `ETag` on
2424
+ * `GET files/content`. The server compares it against the file's state immediately
2425
+ * before writing and answers 409 `file_changed` — having written nothing — when they
2426
+ * differ, which is what stops the editor's save from quietly dropping what the Agent
2427
+ * wrote during the turn. Omitted by a caller that read no version (an upload creates or
2428
+ * replaces unconditionally); that absence, not a sentinel value, is what says there was
2429
+ * no prior version to match.
2430
+ */
2431
+ ifVersion?: string;
2432
+ }
2227
2433
  /** Batch file existence check (message file cards only list files that actually exist). */
2228
2434
  interface FilesStatRequest {
2229
2435
  /** Paths relative to the Workspace root (≤100 items, each ≤512 characters). */
@@ -2295,6 +2501,23 @@ interface TraceAnalysisResponse {
2295
2501
  * frontend's events are paginated, so self-aggregation would undercount.
2296
2502
  */
2297
2503
  elapsedMs: number;
2504
+ /**
2505
+ * The file's model-API time: the sum of the turns' `llmMs` (human approval wait deducted,
2506
+ * compaction requests included, so the scope matches `elapsedMs`).
2507
+ */
2508
+ apiMs: number;
2509
+ /**
2510
+ * The file's tool wall time: the sum of the turns' `toolMs` (parallel tools counted once
2511
+ * within a turn). Summed per turn rather than unioned across the file, so the global figure
2512
+ * stays the sum of the per-turn figures, exactly as `elapsedMs` is. May overlap `apiMs` —
2513
+ * see `TraceTaskStats.toolMs`.
2514
+ */
2515
+ toolMs: number;
2516
+ /**
2517
+ * The file's cost in USD: the sum of the turns' `cost` (compaction turns included, the
2518
+ * scope every total here shares). Absent exactly when the turns carry no `cost`.
2519
+ */
2520
+ cost?: number;
2298
2521
  requests: RequestSpan[];
2299
2522
  /** Token / duration aggregated per Task (used directly by the Trace page's context ring and per-turn TPS). */
2300
2523
  tasks: TraceTaskStats[];
@@ -2450,6 +2673,23 @@ interface UsageModelSeries extends UsageEntitySeriesCounts {
2450
2673
  provider: string;
2451
2674
  modelId: string;
2452
2675
  }
2676
+ /**
2677
+ * Lifetime Token total for one Model, keyed by the (provider, modelId) pair. Deliberately
2678
+ * unfiltered — no date range, no Agent: it answers "how much has this model been used", a
2679
+ * question with no window attached, and the models page that shows it carries no filters of its
2680
+ * own to honour. Models never used are absent rather than zero.
2681
+ */
2682
+ interface UsageModelTotal {
2683
+ provider: string;
2684
+ modelId: string;
2685
+ /** The three buckets summed (cache read + cache write + output), matching the cost center's Token figure. */
2686
+ tokens: number;
2687
+ requests: number;
2688
+ }
2689
+ /** Response of `GET /usage/model-totals`. */
2690
+ interface UsageModelTotals {
2691
+ totals: UsageModelTotal[];
2692
+ }
2453
2693
  /**
2454
2694
  * The two categories an error record is filed under: `unexpected` is a 500 or an unforeseen
2455
2695
  * runtime exception, `expected` an HttpError / business 4xx. The panel separates them by
@@ -2480,6 +2720,7 @@ interface UsageErrorItem {
2480
2720
  * items.
2481
2721
  */
2482
2722
  interface UsageErrors {
2723
+ /** Filtered row count — also what a clear of the same filter takes (see {@link UsageErrorsClearResponse}). */
2483
2724
  total: number;
2484
2725
  /** Count of unexpected ones (500 / runtime exceptions) among them — the part the frontend highlights. */
2485
2726
  unexpected: number;
@@ -2492,8 +2733,9 @@ interface UsageErrors {
2492
2733
  * GET /api/projects/:projectId/usage/errors — one page of the error detail table, newest
2493
2734
  * first. The dashboard response above already carries the first page; this exists so
2494
2735
  * "show me earlier ones" does not have to refetch the whole aggregate. It takes the same
2495
- * date/agent filter as the dashboard, so a page never widens what the summary counted, plus
2496
- * an optional `kind` ({@link UsageErrorKind}) narrowing to one of the two categories — which
2736
+ * date/agent filter as the dashboard (`fromTs`/`toTs` narrow it to a trailing window, both
2737
+ * or neither), so a page never widens what the summary counted, plus an optional `kind`
2738
+ * ({@link UsageErrorKind}) narrowing to one of the two categories — which
2497
2739
  * is how the cost-center badge asks "are there unexpected errors, and how new is the newest"
2498
2740
  * with `limit=1` instead of pulling the whole dashboard aggregate.
2499
2741
  */
@@ -2502,6 +2744,22 @@ interface UsageErrorsPage {
2502
2744
  /** Filtered row count, so the caller knows when it has reached the end. */
2503
2745
  total: number;
2504
2746
  }
2747
+ /**
2748
+ * DELETE /api/projects/:projectId/usage/errors — empties the error table for the filter the
2749
+ * panel is showing (its date range, the trailing window when one is on, and Agent), Project
2750
+ * owner only.
2751
+ *
2752
+ * Scoped to the filter rather than the Project's whole history, so a clear takes exactly the
2753
+ * rows on screen — for an admin, the unattributed rows an admin's panel shows included; a
2754
+ * member's panel never shows them and a member's clear never takes them.
2755
+ *
2756
+ * `from` and `to` are both required (400 otherwise), where the reads treat them as optional:
2757
+ * an absent bound is unbounded on that side, which is the whole history rather than a filter.
2758
+ */
2759
+ interface UsageErrorsClearResponse {
2760
+ /** How many rows were deleted, so the caller can say what went instead of guessing. */
2761
+ deleted: number;
2762
+ }
2505
2763
  interface UsageResponse {
2506
2764
  summary: {
2507
2765
  today: UsageBucket;
@@ -2677,31 +2935,99 @@ interface SkillMetadataItem {
2677
2935
  /** Short description for frontend display (frontmatter short_description, optional; falls back to description if missing). */
2678
2936
  shortDescription?: string;
2679
2937
  shortDescriptionZh?: string;
2680
- /** Custom icon (raw icon.svg text from the skill directory, optional; frontend falls back to a default book icon if missing). */
2938
+ /**
2939
+ * Raw icon.svg text from the installed skill directory: the plugin's icon for a library
2940
+ * install (written beside SKILL.md at install time), a custom one for a user-authored skill.
2941
+ * Only installed lists carry it — the library listing's skills share their plugin's icon,
2942
+ * which the plugin item carries once. Absent, the frontend draws the book glyph.
2943
+ */
2944
+ icon?: string;
2945
+ /** Version (`YYYY.MM.DD.N`, frontmatter version; a copy installed before that spelling still carries `YYYY-MM-DD.N`); an empty string when the frontmatter carries none or a malformed one. */
2946
+ version: string;
2947
+ }
2948
+ /** One installed hook package (`agent_state/hooks/<name>/`): its manifest, without the scripts. */
2949
+ interface HookItem {
2950
+ /** Hook package name — the plugin that shipped it (the identity uninstall addresses it by). */
2951
+ name: string;
2952
+ description: string;
2953
+ descriptionZh?: string;
2954
+ /** Version (`YYYY.MM.DD.N`; a copy installed before that spelling still carries `YYYY-MM-DD.N`); an empty string when the manifest carries none. */
2955
+ version: string;
2956
+ /** The hook points the package answers at, e.g. `["stop"]`. */
2957
+ events: string[];
2958
+ /** The plugin's raw icon.svg, written beside the manifest at install time; absent when the plugin ships none (the frontend draws the hook glyph). */
2959
+ icon?: string;
2960
+ }
2961
+ /** One library plugin as the listing describes it: the manifest fields plus what it ships (skill bodies and scripts are never sent). */
2962
+ interface PluginItem {
2963
+ name: string;
2964
+ description: string;
2965
+ descriptionZh?: string;
2966
+ shortDescription?: string;
2967
+ shortDescriptionZh?: string;
2968
+ /** `YYYY.MM.DD.N`. */
2969
+ version: string;
2970
+ /** The plugin's skills (metadata only). */
2971
+ skills: SkillMetadataItem[];
2972
+ /** The hook points the plugin's hook package answers at (`[]` without one). */
2973
+ hooks: string[];
2974
+ /** The plugin's raw icon.svg (beside plugin.json — every built-in plugin ships one), the icon of everything it ships; the frontend draws the puzzle-piece plugin glyph without it. */
2681
2975
  icon?: string;
2682
- /** Version number (natural number, frontmatter version; falls back to 1 if invalid). */
2683
- version: number;
2684
- /** Update timestamp (frontmatter updated, ISO 8601 UTC by convention; defaults to an empty string). */
2685
- updated: string;
2686
2976
  }
2687
- interface SkillGroupItem {
2977
+ interface PluginGroupItem {
2688
2978
  id: string;
2689
2979
  title: string;
2690
- /** Chinese group title (optional; the UI displays it per language). */
2980
+ /** Chinese category title (optional; the UI displays it per language). */
2691
2981
  titleZh?: string;
2692
- skills: SkillMetadataItem[];
2982
+ plugins: PluginItem[];
2983
+ }
2984
+ /** GET /api/plugins: the library by category (any logged-in user). */
2985
+ interface PluginLibraryResponse {
2986
+ groups: PluginGroupItem[];
2987
+ }
2988
+ /**
2989
+ * GET /api/plugins/:plugin/files: everything a library plugin ships, for the detail view's file
2990
+ * browser — each skill's installable SKILL.md and auxiliary files under `skills/<name>/`, the
2991
+ * hook package's scripts under `hooks/` — keyed by path relative to the plugin directory. The
2992
+ * manifest and the icon are not among them: the listing already carries both.
2993
+ */
2994
+ interface PluginFilesResponse {
2995
+ files: Record<string, string>;
2996
+ }
2997
+ /**
2998
+ * POST /api/projects/:p/agents/:a/plugins: install library plugins by name — each one's skills
2999
+ * and hook package; already-installed ones are overwritten with library content (i.e. updated).
3000
+ * Every name must be in the library (404 `unknown_plugin`, nothing written). 201 returns the
3001
+ * refreshed installed lists.
3002
+ */
3003
+ interface PluginInstallRequest {
3004
+ names: string[];
2693
3005
  }
2694
- /** GET /api/skills: library groups and metadata (excludes body content). */
2695
- interface SkillLibraryResponse {
2696
- groups: SkillGroupItem[];
3006
+ interface AgentPluginsInstallResponse {
3007
+ skills: SkillMetadataItem[];
3008
+ hooks: HookItem[];
2697
3009
  }
2698
- /** GET|POST /api/projects/:p/agents/:a/skills: Skills installed on this Agent. */
3010
+ /** GET /api/projects/:p/agents/:a/skills: Skills installed on this Agent. */
2699
3011
  interface AgentSkillsResponse {
2700
3012
  skills: SkillMetadataItem[];
2701
3013
  }
2702
- /** POST install request: all names must exist in the library; already-installed ones are overwritten with library content (i.e. updated). */
2703
- interface SkillInstallRequest {
2704
- names: string[];
3014
+ /** GET /api/projects/:p/agents/:a/hooks: hook packages installed on this Agent; DELETE …/hooks/:name uninstalls one (204). */
3015
+ interface AgentHooksResponse {
3016
+ hooks: HookItem[];
3017
+ }
3018
+ /**
3019
+ * POST /api/projects/:p/agents/:a/hooks/archive: install one hook package from an uploaded zip.
3020
+ * Layout: hooks.json and its scripts at the zip root, or exactly one top-level directory
3021
+ * containing them (the directory name is then the package name). 201 returns the refreshed
3022
+ * installed list (AgentHooksResponse); an already-installed name without `overwrite` is 409
3023
+ * `hook_exists`. GET …/hooks/:name/archive is the matching export: the installed directory as
3024
+ * a zip attachment, which round-trips through this POST.
3025
+ */
3026
+ interface HookArchiveInstallRequest {
3027
+ /** Base64-encoded zip archive (decoded size capped at 14MB, like the skill archive). */
3028
+ dataBase64: string;
3029
+ /** Replace an installed package of the same name instead of answering 409. */
3030
+ overwrite?: boolean;
2705
3031
  }
2706
3032
  /**
2707
3033
  * POST /api/projects/:p/agents/:a/skills/archive: install one Skill from an uploaded zip.
@@ -2752,7 +3078,9 @@ interface UpdateCheckResponse {
2752
3078
  * utilityProcess message channel and served at GET /api/desktop/update. `state` is the
2753
3079
  * discriminator; the optional fields belong to the states named on them.
2754
3080
  *
2755
- * A `downloaded` build stays the reported state until it is installed: a later periodic
3081
+ * The shell never downloads on its own: a check ends in `available`, and the download
3082
+ * starts only on the page's (or the native dialog's) say-so — the `download` command. A
3083
+ * `downloaded` build stays the reported state until it is installed: a later periodic
2756
3084
  * check (or its failure) must not hide the actionable "restart to install" step.
2757
3085
  */
2758
3086
  interface DesktopUpdateStatus {
@@ -2765,8 +3093,8 @@ interface DesktopUpdateStatus {
2765
3093
  * carry that signal (a check that ends where it started is byte-identical).
2766
3094
  */
2767
3095
  seq?: number;
2768
- state: "idle" | "checking" | "up-to-date" | "downloading" | "downloaded" | "error" | "unsupported";
2769
- /** Newer release being fetched / ready to install (`downloading`, `downloaded`). */
3096
+ state: "idle" | "checking" | "up-to-date" | "available" | "downloading" | "downloaded" | "error" | "unsupported";
3097
+ /** The newer release: offered (`available`), being fetched (`downloading`) or ready to install (`downloaded`). */
2770
3098
  version?: string;
2771
3099
  /** Download progress 0–100 (`downloading`). */
2772
3100
  percent?: number;
@@ -2787,15 +3115,19 @@ interface DesktopUpdaterStatusMessage {
2787
3115
  type: "desktop-updater-status";
2788
3116
  status: DesktopUpdateStatus;
2789
3117
  }
2790
- /** Server → shell command over the utilityProcess message channel (relayed from POST /api/desktop/update/check|install). */
3118
+ /**
3119
+ * Server → shell command over the utilityProcess message channel (relayed from
3120
+ * POST /api/desktop/update/check|download|install): look for a release, fetch the one
3121
+ * offered, or restart into the one downloaded.
3122
+ */
2791
3123
  interface DesktopUpdaterCommandMessage {
2792
3124
  type: "desktop-updater-command";
2793
- action: "check" | "install";
3125
+ action: "check" | "download" | "install";
2794
3126
  }
2795
3127
  /**
2796
- * POST /api/version/update (admin only): runs the CLI self-update (`penguin update --yes`)
2797
- * on the server host. `unsupported` covers both a server not launched via the CLI and the
2798
- * CLI's own refusals (source checkout, unrecognized install layout, Windows).
3128
+ * The outcome of one self-update run (`penguin update --yes` on the server host), carried
3129
+ * by {@link UpdateJobStatus.result}. `unsupported` covers both a server not launched via
3130
+ * the CLI and the CLI's own refusals (source checkout, unrecognized install layout, Windows).
2799
3131
  */
2800
3132
  interface UpdateRunResponse {
2801
3133
  status: "updated" | "failed" | "unsupported";
@@ -2806,6 +3138,44 @@ interface UpdateRunResponse {
2806
3138
  /** True when the install changed (or was already current): restart the service to run the new version. */
2807
3139
  needsRestart: boolean;
2808
3140
  }
3141
+ /**
3142
+ * Where a running self-update is: resolving the release and fetching the installer,
3143
+ * downloading the bundle (the one phase with a percentage), or verifying and installing it.
3144
+ */
3145
+ type UpdateJobPhase = "resolving" | "downloading" | "installing";
3146
+ /**
3147
+ * GET / POST /api/version/update (admin only): the self-update job. POST starts a run when
3148
+ * none is in flight — a finished run may be started again, which is how a failed one is
3149
+ * retried — and answers with the status exactly as GET does; the page polls GET while
3150
+ * `state` is `running`. One job per process: two admins clicking at once share the one run,
3151
+ * and the finished status stays readable until the next start.
3152
+ */
3153
+ interface UpdateJobStatus {
3154
+ state: "idle" | "running" | "done";
3155
+ /** The release the run targets — the update check's newest version when the run started; null when none was known. */
3156
+ targetVersion: string | null;
3157
+ /** Running only. */
3158
+ phase?: UpdateJobPhase;
3159
+ /** Running, `downloading` only: 0–100 read off the installer's progress bar; null until its first tick. */
3160
+ percent?: number | null;
3161
+ /** Tail of the update command's combined stdout+stderr so far (capped; empty when nothing ran). */
3162
+ output: string;
3163
+ /** Done only. */
3164
+ result?: UpdateRunResponse;
3165
+ startedAt?: string;
3166
+ finishedAt?: string;
3167
+ }
3168
+ /**
3169
+ * POST /api/version/restart (admin only): asks the process to exit with the supervisor's
3170
+ * restart code after a graceful shutdown, so `penguin server|web` relaunches it on the
3171
+ * installed release. `restarting: false` when nothing supervises this process — it was
3172
+ * started some other way than through the CLI, or it is a dev run — and the page shows the
3173
+ * manual restart hint instead.
3174
+ */
3175
+ interface RestartResponse {
3176
+ restarting: boolean;
3177
+ reason?: "no_supervisor";
3178
+ }
2809
3179
  /** One `Host` entry of the server's `~/.ssh/config`, as the Machines page lists it. */
2810
3180
  interface MachineInfo {
2811
3181
  /** `ssh:<alias>` — the id the install route is asked for. */
@@ -2817,9 +3187,9 @@ interface MachineInfo {
2817
3187
  */
2818
3188
  alias: string;
2819
3189
  /**
2820
- * The last install THIS server carried out there, remembered on disk so it survives a
2821
- * restart, a hot push, and installing on some other machine. Null when this server has
2822
- * never installed there.
3190
+ * The last install THIS server carried out there FOR THIS PROJECT, remembered in web.db so
3191
+ * it survives a restart, a hot push, and installing on some other machine. Null when this
3192
+ * Project has never installed there — a host another Project did is `elsewhere` below.
2823
3193
  *
2824
3194
  * A record of what was done, not a survey of the far side: a machine wiped by hand still
2825
3195
  * reads as installed until the next install probes it and corrects the record. Asking the
@@ -2830,25 +3200,112 @@ interface MachineInfo {
2830
3200
  version: string;
2831
3201
  at: string;
2832
3202
  } | null;
3203
+ /**
3204
+ * Installed by this server, but not this Project's machine — another Project's, or nobody's
3205
+ * since it was released — and so not a host nobody has touched either. Absent in every
3206
+ * other case, including when it IS this Project's (where `installed` carries the same record).
3207
+ *
3208
+ * Reported rather than folded into `installed` because the two lead to different actions:
3209
+ * one is a machine to use, the other is a machine to adopt, which costs a row and no ssh.
3210
+ * Reported rather than hidden because a row that silently looked uninstalled would send
3211
+ * someone to spend a 30 MB transfer re-doing what is already done.
3212
+ */
3213
+ elsewhere?: {
3214
+ version: string;
3215
+ at: string;
3216
+ };
3217
+ /**
3218
+ * The machine's OWN id — 16 base64url characters minted by the server that runs there,
3219
+ * stable across renames, re-aliasing and reinstalls. Null until a server has started on
3220
+ * that machine, since nothing has minted one yet.
3221
+ *
3222
+ * This is what anything stored should point at; `id` above is an ADDRESS (`ssh:<alias>`),
3223
+ * and `alias` is what people read. Two aliases for one host share a `machineId`, and an
3224
+ * alias repointed at a different host answers a different one — an id never changes for a
3225
+ * machine, so a change of id is a change of machine.
3226
+ */
3227
+ machineId: string | null;
3228
+ /** The host this server itself runs on. Always present, always installed, never a target. */
3229
+ local: boolean;
3230
+ /**
3231
+ * The connection this server holds to it — the one ssh session everything to the machine
3232
+ * rides (machines/transport/ssh-session.ts): a fact about a process on THIS side, which
3233
+ * outlives the far server. Present means `/server/<id>/api/…` has somewhere to go; whether
3234
+ * a server ANSWERS over there is `status`'s word, from the last probe — and the two must not
3235
+ * be read for each other: taking the connection for the machine's liveness is the mistake
3236
+ * behind the connect loop (#561). Always null for `local`, which needs no connection.
3237
+ */
3238
+ connection: {
3239
+ pid: number;
3240
+ } | null;
3241
+ /**
3242
+ * The machine's API as last seen by this server's proxy — stamped by traffic passing
3243
+ * through, never by a probe of its own. `answeredAt` when the last forwarded request got
3244
+ * an HTTP answer (any status: a server that refuses is still answering); `failedAt` and
3245
+ * the transport's own words when the connection had nowhere to deliver. Null until a first
3246
+ * request flows, and only as fresh as the last one — no traffic, no measurement, which is
3247
+ * honest: nothing burns ssh for a page nobody is looking at. Always null for `local`;
3248
+ * this server answering the request that fetched this list is that measurement.
3249
+ */
3250
+ api: {
3251
+ answeredAt: string;
3252
+ } | {
3253
+ failedAt: string;
3254
+ detail: string;
3255
+ } | null;
3256
+ /**
3257
+ * What the last probe found over there, or null when none has been taken. Never filled in
3258
+ * at list time: a probe costs an ssh round trip per machine, so the list reports the last
3259
+ * answer and the page asks for a fresh one when it wants one — on a widening schedule,
3260
+ * since each probe is an ssh round trip while the list is only the config's text.
3261
+ */
3262
+ status: MachineServerStatus | null;
3263
+ }
3264
+ /**
3265
+ * One machine's server state. There is deliberately no separate "ssh" status: ssh is the
3266
+ * transport, so a machine it cannot reach reads as `unreachable` with OpenSSH's own
3267
+ * diagnostic in `detail` rather than as two statuses a reader has to combine.
3268
+ */
3269
+ interface MachineServerStatus {
3270
+ state: "running" | "stopped" | "unreachable";
3271
+ /** ISO timestamp of the probe this answer came from. */
3272
+ checkedAt: string;
3273
+ /** The port it is serving on (`running`). */
3274
+ port?: number;
3275
+ /** Why the machine could not be reached (`unreachable`) — ssh's own words. */
3276
+ detail?: string;
2833
3277
  }
2834
3278
  /**
2835
- * The running or last install, polled by GET /api/machines while one runs. `log` carries the
2836
- * far side's own words where there are any: ssh's diagnostics and the remote installer's
2837
- * output say more about a refused key or an unusable Node than a paraphrase would.
3279
+ * The running or last job on a machine — an install, or a connect — polled by GET
3280
+ * /api/machines while one runs. One at a time. `log` carries the far side's own words where
3281
+ * there are any: ssh's diagnostics and the remote installer's output say more about a
3282
+ * refused key or an unusable Node than a paraphrase would.
2838
3283
  */
2839
- interface MachineInstallJob {
3284
+ interface MachineJob {
3285
+ kind: "install" | "connect" | "restart";
2840
3286
  machineId: string;
2841
3287
  alias: string;
2842
3288
  running: boolean;
2843
3289
  log: string[];
2844
3290
  result: null | {
2845
3291
  ok: true;
2846
- kind: "installed" | "already-installed";
3292
+ installed: "installed" | "already-installed";
2847
3293
  version: string | null;
3294
+ } | {
3295
+ ok: true;
3296
+ connected: true;
2848
3297
  } | {
2849
3298
  ok: false;
2850
3299
  step: string;
2851
3300
  message: string;
3301
+ /**
3302
+ * The failure has a next step this side can take, and it needs saying yes to:
3303
+ * installing the PROGRAM over there and restarting it. Set when a hot update could
3304
+ * not be handed over — the machine's store holds no CLI this server can talk to, and
3305
+ * installing is what replicates one. Offered rather than done, because it restarts a
3306
+ * server this Project does not own alone.
3307
+ */
3308
+ canReplaceProgram?: true;
2852
3309
  };
2853
3310
  }
2854
3311
  /** GET /api/machines, and the 202 body of POST /api/machines/:machineId/install. */
@@ -2860,7 +3317,7 @@ interface MachinesResponse {
2860
3317
  * checkout, which stands on no release the remote could download.
2861
3318
  */
2862
3319
  imageVersion: string | null;
2863
- job: MachineInstallJob | null;
3320
+ job: MachineJob | null;
2864
3321
  }
2865
3322
 
2866
- export type { AdminPasswordResetRequest, AdminUserCreateRequest, AdminUserCreateResponse, AdminUsersResponse, AgentCompactionConfigDto, AgentConfigDto, AgentConfigResponse, AgentConfigUpdateRequest, AgentCreateRequest, AgentCreateResponse, AgentImportRequest, AgentImportResponse, AgentKernelUpdateResponse, AgentMemoryConfigDto, AgentModelConfigDto, AgentSchedulesConfigDto, AgentSkillsConfigDto, AgentSkillsResponse, AgentSummary, AgentTraceDateGroup, AgentTraceFileRef, AgentTraceSessionEntry, AgentTraceSessionFile, AgentTraceSessionGroup, AgentTracesResponse, AgentVaultConfigDto, AgentsResponse, ApprovalDecisionRequest, ApprovalMode, AuthLoginRequest, AuthResponse, BenchmarkCaseScore, BenchmarkCaseSummary, BenchmarkCasesResponse, BenchmarkEvaluation, BenchmarkRunScore, BenchmarkSummary, BenchmarksResponse, CaseMaterial, ChatDefaultsDto, CommandPolicyDto, CommandPolicyRuleDto, ContextToolShare, CredentialInfo, DefaultModelResponse, DefaultModelUpdateRequest, DesktopUpdateStatus, DesktopUpdateStatusResponse, DesktopUpdaterCommandMessage, DesktopUpdaterStatusMessage, DirEntryInfo, DirListResponse, DirectorySkillItem, DirectorySkillsResponse, DraftShortcut, EndpointModelListRequest, EndpointModelListResponse, ErrorBody, FeishuBindingInfo, FeishuBindingPutRequest, FeishuBindingResponse, FeishuTestRequest, FeishuTestResponse, FilesStatRequest, FilesStatResponse, GoalResponse, GoalServerEvent, GoalStateView, HistoryMessage, InstallResponse, MachineInfo, MachineInstallJob, MachinesResponse, McpServerTestResponse, MeResponse, MemberAddRequest, MemberAddResponse, MemberInfo, MembersResponse, MemoryFileInfo, MemoryFileResponse, MemoryFilesResponse, MemoryImportMode, MemoryImportRequest, MemoryImportResponse, MemoryOverviewResponse, MemoryScopeExport, MemoryScopeInfo, MemoryTransferFile, MessagesLiveTail, MessagesPageInfo, MessagesResponse, MessagingBindingInfo, MessagingBindingStateRequest, MessagingBindingsResponse, MessagingChannel, MessagingChannelState, MessagingDeliveryError, MessagingRuntimeState, MessagingRuntimeStatus, MessagingTestMessageResponse, ModelInfo, ModelOAuthCodeRequest, ModelOAuthCodeResponse, ModelOAuthErrorCode, ModelOAuthMode, ModelOAuthStartRequest, ModelOAuthStartResponse, ModelOAuthStatusResponse, ModelPricingDto, ModelProtocolDetectRequest, ModelProtocolDetectResponse, ModelProtocolProbeDto, ModelRefDto, ModelTestRequest, ModelTestResponse, ModelUpdateEntry, ModelVisionDetectRequest, ModelVisionDetectResponse, ModelVisionOutcome, ModelsResponse, ModelsUpdateRequest, PasswordChangeRequest, PendingFollowUpInfo, PendingSteeringInfo, PrefsResponse, ProjectCreateRequest, ProjectCreateResponse, ProjectRole, ProjectSummary, ProjectUpdateRequest, ProjectUpdateResponse, ProjectsResponse, ProtocolProbeOutcome, QQBindingInfo, QQBindingPutRequest, QQBindingResponse, QQScanPollResponse, QQScanStartResponse, QQTestRequest, QQTestResponse, RecalledMessageResponse, RequestSpan, RetryNowResponse, ScheduleItem, ScheduleServerEvent, ScheduleStatus, ScheduleUpsertRequest, SchedulesResponse, ServerEvent, ServerSettings, ServerSettingsResponse, ServerSettingsUpdateRequest, SessionCategory, SessionCategoryCounts, SessionContextParts, SessionContextResponse, SessionCreateRequest, SessionCreateResponse, SessionForkRequest, SessionForkResponse, SessionInfo, SessionPatchRequest, SessionProcessInfo, SessionProcessesResponse, SessionResponse, SessionSource, SessionStatus, SessionTracesResponse, SessionsResponse, SkillArchiveInstallRequest, SkillGroupItem, SkillInstallRequest, SkillLibraryResponse, SkillMetadataItem, SkillUpdateRef, SteerRequest, SubagentMessageResponse, SubagentRuntimeInfo, TaskCreateRequest, TaskCreateResponse, TaskInputPart, TelegramBindingInfo, TelegramBindingPutRequest, TelegramBindingResponse, TelegramTestRequest, TelegramTestResponse, ToolCallSpan, TraceAnalysisResponse, TraceEventsResponse, TraceFileInfo, TraceImportRequest, TraceImportResponse, TraceModelSegment, TraceOtherSpan, TracePosition, TraceTaskStats, TraceToolSpan, UiPrefs, UpdateCheckResponse, UpdateRunResponse, UploadLimits, UsageAgentSeries, UsageBucket, UsageEntitySeriesCounts, UsageErrorCount, UsageErrorItem, UsageErrorKind, UsageErrors, UsageErrorsPage, UsageGranularity, UsageGroupBy, UsageGroupRow, UsageModelSeries, UsageResponse, UsageSeriesPoint, UsageTrendPointInTrace, UserInfo, VaultEntryInfo, VaultEntryUpdate, VaultResponse, VaultUpdateRequest, VersionResponse, WorkspaceFileEntry, WorkspaceFilesResponse };
3323
+ export type { AdminPasswordResetRequest, AdminUserCreateRequest, AdminUserCreateResponse, AdminUsersResponse, AgentCompactionConfigDto, AgentConfigDto, AgentConfigResponse, AgentConfigUpdateRequest, AgentCreateRequest, AgentCreateResponse, AgentHooksConfigDto, AgentHooksResponse, AgentImportRequest, AgentImportResponse, AgentKernelUpdateResponse, AgentMemoryConfigDto, AgentModelConfigDto, AgentPluginsInstallResponse, AgentSchedulesConfigDto, AgentSkillsConfigDto, AgentSkillsResponse, AgentSummary, AgentTraceDateGroup, AgentTraceFileRef, AgentTraceSessionEntry, AgentTraceSessionFile, AgentTraceSessionGroup, AgentTracesResponse, AgentVaultConfigDto, AgentsResponse, ApprovalDecisionRequest, ApprovalMode, AuthLoginRequest, AuthResponse, BenchmarkCaseScore, BenchmarkCaseSummary, BenchmarkCasesResponse, BenchmarkEvaluation, BenchmarkRunScore, BenchmarkSummary, BenchmarksResponse, CaseMaterial, ChatDefaultsDto, CommandPolicyDto, CommandPolicyRuleDto, ContextFileShare, ContextToolShare, CredentialInfo, DefaultModelResponse, DefaultModelUpdateRequest, DesktopUpdateStatus, DesktopUpdateStatusResponse, DesktopUpdaterCommandMessage, DesktopUpdaterStatusMessage, DirEntryInfo, DirListResponse, DirectorySkillItem, DirectorySkillsResponse, DraftShortcut, EndpointModelListRequest, EndpointModelListResponse, ErrorBody, FeishuBindingInfo, FeishuBindingPutRequest, FeishuBindingResponse, FeishuTestRequest, FeishuTestResponse, FilesStatRequest, FilesStatResponse, FilesWriteRequest, GoalResponse, GoalServerEvent, GoalStateView, HistoryMessage, HookArchiveInstallRequest, HookItem, InstallResponse, MachineInfo, MachineJob, MachineServerStatus, MachinesResponse, McpServerTestResponse, MeResponse, MemberAddRequest, MemberAddResponse, MemberInfo, MembersResponse, MemoryFileInfo, MemoryFileResponse, MemoryFilesResponse, MemoryImportMode, MemoryImportRequest, MemoryImportResponse, MemoryOverviewResponse, MemoryScopeExport, MemoryScopeInfo, MemoryTransferFile, MessagesLiveTail, MessagesPageInfo, MessagesResponse, MessagingBindingCommon, MessagingBindingInfo, MessagingBindingStateRequest, MessagingBindingsResponse, MessagingChannel, MessagingChannelState, MessagingDeliveryError, MessagingDeliveryPatch, MessagingRuntimeState, MessagingRuntimeStatus, MessagingTestMessageResponse, ModelInfo, ModelOAuthCodeRequest, ModelOAuthCodeResponse, ModelOAuthErrorCode, ModelOAuthMode, ModelOAuthStartRequest, ModelOAuthStartResponse, ModelOAuthStatusResponse, ModelPricingDto, ModelProtocolDetectRequest, ModelProtocolDetectResponse, ModelProtocolProbeDto, ModelRefDto, ModelTestRequest, ModelTestResponse, ModelUpdateEntry, ModelVisionDetectRequest, ModelVisionDetectResponse, ModelVisionOutcome, ModelsResponse, ModelsUpdateRequest, PasswordChangeRequest, PendingFollowUpInfo, PendingSteeringInfo, PluginFilesResponse, PluginGroupItem, PluginInstallRequest, PluginItem, PluginLibraryResponse, PluginUpdateRef, PrefsResponse, ProjectCreateRequest, ProjectCreateResponse, ProjectRole, ProjectSummary, ProjectUpdateRequest, ProjectUpdateResponse, ProjectsResponse, ProtocolProbeOutcome, QQBindingInfo, QQBindingPutRequest, QQBindingResponse, QQScanPollResponse, QQScanStartResponse, QQTestRequest, QQTestResponse, RecalledMessageResponse, RequestSpan, RestartResponse, RetryNowResponse, ScheduleItem, ScheduleServerEvent, ScheduleStatus, ScheduleUpsertRequest, SchedulesResponse, ServerEvent, ServerSettings, ServerSettingsResponse, ServerSettingsUpdateRequest, SessionBackgroundTasks, SessionCategory, SessionCategoryCounts, SessionContextParts, SessionContextResponse, SessionCreateRequest, SessionCreateResponse, SessionForkRequest, SessionForkResponse, SessionInfo, SessionPatchRequest, SessionProcessInfo, SessionProcessesResponse, SessionResponse, SessionSource, SessionStatus, SessionTracesResponse, SessionsResponse, SkillArchiveInstallRequest, SkillMetadataItem, SteerRequest, SubagentMessageResponse, SubagentRuntimeInfo, TaskCreateRequest, TaskCreateResponse, TaskInputPart, TelegramBindingInfo, TelegramBindingPutRequest, TelegramBindingResponse, TelegramTestRequest, TelegramTestResponse, ToolCallSpan, TraceAnalysisResponse, TraceEventsResponse, TraceFileInfo, TraceImportRequest, TraceImportResponse, TraceModelSegment, TraceOtherSpan, TracePosition, TraceTaskStats, TraceToolSpan, UiPrefs, UpdateCheckResponse, UpdateJobPhase, UpdateJobStatus, UpdateRunResponse, UploadLimits, UsageAgentSeries, UsageBucket, UsageEntitySeriesCounts, UsageErrorCount, UsageErrorItem, UsageErrorKind, UsageErrors, UsageErrorsClearResponse, UsageErrorsPage, UsageGranularity, UsageGroupBy, UsageGroupRow, UsageModelSeries, UsageModelTotal, UsageModelTotals, UsageResponse, UsageSeriesPoint, UsageTrendPointInTrace, UserInfo, VaultEntryInfo, VaultEntryUpdate, VaultResponse, VaultUpdateRequest, VersionResponse, WeChatBindingInfo, WeChatBindingPutRequest, WeChatBindingResponse, WeChatScanPollResponse, WeChatScanStartResponse, WeChatScanStatus, WeChatScanVerifyRequest, WeChatTestResponse, WorkspaceFileEntry, WorkspaceFilesResponse };