@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.
- package/README.md +1 -0
- package/dist/api/types.d.ts +645 -188
- package/dist/auth-token.js +4 -2
- package/dist/auth-token.js.map +1 -1
- package/dist/{chunk-622KL7BQ.js → chunk-ADZJYDNC.js} +205 -144
- package/dist/chunk-ADZJYDNC.js.map +1 -0
- package/dist/{chunk-OMBFPEGQ.js → chunk-GOO5VUAB.js} +10 -8
- package/dist/chunk-GOO5VUAB.js.map +1 -0
- package/dist/chunk-HMTT2PX4.js +93 -0
- package/dist/chunk-HMTT2PX4.js.map +1 -0
- package/dist/chunk-HOS3W3UP.js +127 -0
- package/dist/chunk-HOS3W3UP.js.map +1 -0
- package/dist/{chunk-UGAFPYVX.js → chunk-Q65VPAQS.js} +2 -2
- package/dist/chunk-Q65VPAQS.js.map +1 -0
- package/dist/index.js +6994 -3108
- package/dist/index.js.map +1 -1
- package/dist/lock.d.ts +9 -1
- package/dist/lock.js +3 -1
- package/dist/machine-status.d.ts +18 -0
- package/dist/machine-status.js +37 -0
- package/dist/machine-status.js.map +1 -0
- package/dist/reset-admin-password.js +6 -4
- package/dist/reset-admin-password.js.map +1 -1
- package/package.json +6 -3
- package/web-dist/assets/addon-clipboard-1cARCaK9.js +1 -0
- package/web-dist/assets/{addon-fit-DHOzI3Ur.js → addon-fit-BWdzl8Er.js} +1 -1
- package/web-dist/assets/{addon-web-links-DC7759H1.js → addon-web-links-DjXuvn4Z.js} +1 -1
- package/web-dist/assets/{highlighter-CGPO6Q4b.js → highlighter-Cu43iYr8.js} +1 -1
- package/web-dist/assets/index-Bm9n7ZV-.js +457 -0
- package/web-dist/assets/index-DR4PrRzS.css +1 -0
- package/web-dist/assets/{xterm-DY5ko-BU.js → xterm-DKcY6q-I.js} +1 -1
- package/web-dist/index.html +2 -2
- package/dist/chunk-622KL7BQ.js.map +0 -1
- package/dist/chunk-OMBFPEGQ.js.map +0 -1
- package/dist/chunk-UGAFPYVX.js.map +0 -1
- package/web-dist/assets/index-CaFDHl-w.js +0 -451
- package/web-dist/assets/index-D8Bk9LtJ.css +0 -1
package/dist/api/types.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
705
|
-
interface
|
|
706
|
-
/**
|
|
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:
|
|
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
|
-
|
|
738
|
-
|
|
739
|
-
*
|
|
740
|
-
*
|
|
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
|
-
|
|
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
|
|
758
|
-
* exist in the library (404 `
|
|
759
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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:
|
|
1041
|
-
*
|
|
1042
|
-
*
|
|
1043
|
-
*
|
|
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
|
|
1181
|
-
*
|
|
1182
|
-
* the picker
|
|
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
|
|
1321
|
-
* `budget` is the token budget (uncached input + output);
|
|
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
|
|
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
|
-
/**
|
|
1546
|
-
|
|
1547
|
-
|
|
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
|
|
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
|
|
1581
|
-
*
|
|
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
|
-
/**
|
|
1630
|
-
|
|
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
|
-
*
|
|
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
|
|
1666
|
-
*
|
|
1667
|
-
*
|
|
1668
|
-
*
|
|
1669
|
-
*
|
|
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
|
-
*
|
|
1676
|
-
*
|
|
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
|
-
|
|
1680
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
1793
|
-
*
|
|
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
|
-
|
|
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
|
|
2496
|
-
*
|
|
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
|
-
/**
|
|
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
|
|
2977
|
+
interface PluginGroupItem {
|
|
2688
2978
|
id: string;
|
|
2689
2979
|
title: string;
|
|
2690
|
-
/** Chinese
|
|
2980
|
+
/** Chinese category title (optional; the UI displays it per language). */
|
|
2691
2981
|
titleZh?: string;
|
|
2692
|
-
|
|
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
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
3006
|
+
interface AgentPluginsInstallResponse {
|
|
3007
|
+
skills: SkillMetadataItem[];
|
|
3008
|
+
hooks: HookItem[];
|
|
2697
3009
|
}
|
|
2698
|
-
/** GET
|
|
3010
|
+
/** GET /api/projects/:p/agents/:a/skills: Skills installed on this Agent. */
|
|
2699
3011
|
interface AgentSkillsResponse {
|
|
2700
3012
|
skills: SkillMetadataItem[];
|
|
2701
3013
|
}
|
|
2702
|
-
/**
|
|
2703
|
-
interface
|
|
2704
|
-
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
2797
|
-
*
|
|
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
|
|
2821
|
-
* restart, a hot push, and installing on some other machine. Null when this
|
|
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
|
|
2836
|
-
*
|
|
2837
|
-
*
|
|
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
|
|
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
|
-
|
|
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:
|
|
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,
|
|
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 };
|