@co0ontty/wand 4.102.0 → 4.103.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-team-live.js +1 -1
- package/dist/apns-client.d.ts +45 -0
- package/dist/apns-client.js +156 -0
- package/dist/apns-service.d.ts +29 -0
- package/dist/apns-service.js +156 -0
- package/dist/build-info.json +3 -3
- package/dist/conversation-service.d.ts +2 -0
- package/dist/conversation-service.js +23 -3
- package/dist/conversation-types.d.ts +2 -0
- package/dist/dingtalk-auth.d.ts +41 -0
- package/dist/dingtalk-auth.js +127 -0
- package/dist/dingtalk-dws.d.ts +45 -0
- package/dist/dingtalk-dws.js +240 -0
- package/dist/dingtalk-service.d.ts +100 -0
- package/dist/dingtalk-service.js +865 -0
- package/dist/dingtalk-stream.d.ts +24 -0
- package/dist/dingtalk-stream.js +115 -0
- package/dist/dingtalk-types.d.ts +138 -0
- package/dist/dingtalk-types.js +1 -0
- package/dist/employee-knowledge.js +8 -0
- package/dist/live-activity-service.d.ts +26 -0
- package/dist/live-activity-service.js +158 -0
- package/dist/live-activity-state.d.ts +37 -0
- package/dist/live-activity-state.js +108 -0
- package/dist/middleware/api-scope.js +2 -0
- package/dist/pi-cli-checkpoint.d.ts +17 -0
- package/dist/pi-cli-checkpoint.js +10 -0
- package/dist/pi-event-projection.js +13 -0
- package/dist/pi-execution.js +2 -1
- package/dist/pi-protocol-reader.d.ts +6 -2
- package/dist/pi-protocol-reader.js +36 -12
- package/dist/pi-tmux-host.d.ts +1 -1
- package/dist/pi-tmux-host.js +28 -8
- package/dist/pi-tool-result-preview.d.ts +23 -0
- package/dist/pi-tool-result-preview.js +185 -0
- package/dist/render-daemon-client.js +1 -1
- package/dist/render-host.d.ts +3 -3
- package/dist/render-host.js +3 -3
- package/dist/server-apns-routes.d.ts +12 -0
- package/dist/server-apns-routes.js +111 -0
- package/dist/server-dingtalk-routes.d.ts +8 -0
- package/dist/server-dingtalk-routes.js +35 -0
- package/dist/server.js +49 -3
- package/dist/session-completion.d.ts +2 -1
- package/dist/session-completion.js +6 -2
- package/dist/session-registry.d.ts +3 -1
- package/dist/session-registry.js +3 -1
- package/dist/storage/apns-repository.d.ts +52 -0
- package/dist/storage/apns-repository.js +108 -0
- package/dist/storage/conversations-repository.d.ts +2 -0
- package/dist/storage/conversations-repository.js +10 -0
- package/dist/storage/dingtalk-repository.d.ts +20 -0
- package/dist/storage/dingtalk-repository.js +75 -0
- package/dist/storage/live-activity-repository.d.ts +68 -0
- package/dist/storage/live-activity-repository.js +117 -0
- package/dist/storage/migrations.js +22 -0
- package/dist/storage/sessions-repository.d.ts +5 -0
- package/dist/storage/sessions-repository.js +43 -1
- package/dist/storage-schema.js +2 -0
- package/dist/storage.d.ts +7 -1
- package/dist/storage.js +10 -0
- package/dist/structured-exec-host.d.ts +9 -1
- package/dist/structured-exec-pump.d.ts +11 -0
- package/dist/structured-exec-pump.js +27 -8
- package/dist/structured-pi-adapter.js +5 -0
- package/dist/structured-runner.d.ts +2 -0
- package/dist/structured-session-manager.d.ts +2 -0
- package/dist/structured-session-manager.js +51 -4
- package/dist/types.d.ts +7 -0
- package/dist/web-ui/content/module-manifest.json +1 -1
- package/dist/web-ui/content/modules/{4f30a913cc5b3f89 → 02fad74223254019}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{acb21c7e7fd6cb67 → 033e0a741edd2ba1}/page.js +1 -1
- package/dist/web-ui/content/modules/{e61136c22675ec7d → 0c04f94bbfd39a2f}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{119874145414688d → 12e91deb96ace24d}/shell-file-panel-drawer.js +1 -1
- package/dist/web-ui/content/modules/15279373f0e1461f/host.js +1 -0
- package/dist/web-ui/content/modules/{78a46adbf9807f89 → 17603a34d545c784}/chunk.js +1 -1
- package/dist/web-ui/content/modules/1f041aa8d3673d93/chunk.js +1 -0
- package/dist/web-ui/content/modules/{96dd661559b67ebf → 20483d6e57cfe9d7}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{f55031a02775c89e → 217595f72a1c3fc5}/chunk.js +1 -1
- package/dist/web-ui/content/modules/2230c51655163cf3/runtime.js +279 -0
- package/dist/web-ui/content/modules/{82e40fdf0abfb8cd → 2460e872ab3a1f37}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{9e754b01fa4207fb → 2a47853ca7752c1f}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{e95b60ab18a79499 → 2afbc201474adacc}/login-visual.js +1 -1
- package/dist/web-ui/content/modules/{38917c19e2cf1702 → 30169d15cea782f0}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{5513bcbacdf44588 → 37abb74d1a0a0791}/notice-view.js +1 -1
- package/dist/web-ui/content/modules/{b62294e53dcb3147 → 38bb024e09cb5d5d}/host.js +1 -1
- package/dist/web-ui/content/modules/39faef0cffbad28e/home.js +6 -0
- package/dist/web-ui/content/modules/3a2fa12c8b537964/chunk.js +1 -0
- package/dist/web-ui/content/modules/{35a8fd2cfd31d7a0 → 3a72b79418f0f80a}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{321bd9c755bc30fc → 3bd3c84c4257ba0d}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{2981483671797b92 → 3d10e3f6f6f92b55}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{6f341fe3e8aee844 → 3d4451bc069c1498}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{4a9a3e1f87b2c32b → 3d5c498522b48225}/dialog-modal.js +1 -1
- package/dist/web-ui/content/modules/426b8133daebff78/host.js +1 -0
- package/dist/web-ui/content/modules/455436229dc57d7f/sidebar-peek.js +1 -0
- package/dist/web-ui/content/modules/46de3c2e2ca680c1/host.js +1 -0
- package/dist/web-ui/content/modules/47af6b35e1d01ea7/host.js +30 -0
- package/dist/web-ui/content/modules/4841e45cb7c5cc67/queue-view-adapter.js +3 -0
- package/dist/web-ui/content/modules/{47419445e0aaa3cc → 497789a07177ed7b}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{32c461164a1b364c → 5472191457a926d3}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{c152782274c04fad → 55a47b6e2c2016f2}/chunk.js +1 -1
- package/dist/web-ui/content/modules/5743d8116d15696b/employee-card.js +1 -0
- package/dist/web-ui/content/modules/{c81a3d95b14cdcd4 → 578703196f82222d}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{8ce71665ae55ccf1 → 57d68f0a47e8fd7c}/toast-region.js +1 -1
- package/dist/web-ui/content/modules/58866143a3365521/chunk.js +1 -0
- package/dist/web-ui/content/modules/{3601736ae2d678ef → 5bb5db89a343817f}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{a098c7b1aa4e2f6b → 5c9e8245d4a8a312}/sha256.js +1 -1
- package/dist/web-ui/content/modules/{8198bfd269c0caca → 5de35db482fb1a42}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{aab92f2990a7185f → 5e7104e569359449}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{4b9deeeb77570db3 → 601a2767ae24d1cb}/chunk.js +1 -1
- package/dist/web-ui/content/modules/6687ad7aa57f443b/task-board-host.js +22 -0
- package/dist/web-ui/content/modules/{c422f47fd13dc4bc → 684db83476dcba20}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{cf715621edb1d12e → 6b0bafa239dfe4e2}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{cd22aa55f6cc5276 → 6bd6ee3c9f05e2f5}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{09ef1bb1d195d727 → 79b0c18f2bf35740}/host.js +1 -1
- package/dist/web-ui/content/modules/{905f8bc3f8a3efb8 → 7ae83a3f06f2daa3}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{84b79a33c64e146c → 7f32261c1627c3cd}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{eece3c2d2374e8f7 → 805b859b1b76ce4d}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{eecaebc5482ee203 → 84358b5b729cbe48}/chunk.js +1 -1
- package/dist/web-ui/content/modules/85181d84d5ce62b3/employee-profile.js +1 -0
- package/dist/web-ui/content/modules/87edd7cc79811bb8/composer-surface-adapter.js +1 -0
- package/dist/web-ui/content/modules/{b0254a2d3652a925 → 8ea498cf092dba30}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{a4af270960a39ea6 → 8f0d4e22c18a58b0}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{c72c4f1d47c3726d → 907dcf0ea5b5f23f}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{9f33acc1c4284e03 → 916021ce252882ca}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{98c14fc0474455a5 → 91ee32135a3582b0}/chunk.js +1 -1
- package/dist/web-ui/content/modules/92df279c40bc4218/host.js +1 -0
- package/dist/web-ui/content/modules/{544dc812abdfbbb8 → 9661b076f08e64b4}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{5c94fb2b7abcd1a5 → 97ef63ff1f0784d9}/chunk.js +1 -1
- package/dist/web-ui/content/modules/99e48e07517d3f83/dropdown-surface.js +1 -0
- package/dist/web-ui/content/modules/9eb83751622e2b97/chunk.js +10 -0
- package/dist/web-ui/content/modules/{7375574a2ea42740 → a1715be0f97c2479}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{456634525d44777d → a2be1fd01cf06837}/chunk.js +1 -1
- package/dist/web-ui/content/modules/a33666b143331cd6/workspace-window.js +1 -0
- package/dist/web-ui/content/modules/{b38924f45bfa71d6 → a41351130003e6bb}/chunk.js +1 -1
- package/dist/web-ui/content/modules/a59c83b5b5b85da2/host.js +1 -0
- package/dist/web-ui/content/modules/{a4dca2ffaf656d3c → a63f0b3d73d97e0b}/chunk.js +1 -1
- package/dist/web-ui/content/modules/a685793c5d9b90d1/chunk.js +1 -0
- package/dist/web-ui/content/modules/a750f4868304b089/host.js +1 -0
- package/dist/web-ui/content/modules/abfe4b88dcd89d88/host.js +1 -0
- package/dist/web-ui/content/modules/{15bcc550ef9fd5b7 → acda148b7423bdab}/host.js +1 -1
- package/dist/web-ui/content/modules/{ad1136919a65fc6e → af6ff78aeb782cf2}/host.js +1 -1
- package/dist/web-ui/content/modules/b32c9addcd31f54b/panel-host.js +1 -0
- package/dist/web-ui/content/modules/{b937f1a939618be9 → b358eaf2e45849a6}/host.js +1 -1
- package/dist/web-ui/content/modules/{476ff7911bce1a38 → b7aeeb22bf604ec7}/chunk.js +1 -1
- package/dist/web-ui/content/modules/bad2c42c7ddeab94/host.js +1 -0
- package/dist/web-ui/content/modules/{622a8d12c4c622bf → bb59e8cb4323bf06}/host.js +1 -1
- package/dist/web-ui/content/modules/{08c7b047195f138d → be497f1d39753979}/chunk.js +1 -1
- package/dist/web-ui/content/modules/bfa8e2c827201374/chunk.js +1 -0
- package/dist/web-ui/content/modules/{5395fa57a47e9b96 → bfe51130a84795fb}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{8e29056fb56439f7 → c03e0f84f798d33a}/chunk.js +1 -1
- package/dist/web-ui/content/modules/c118e0c96bc023ee/host-bridge.js +1 -0
- package/dist/web-ui/content/modules/c3512b18c4c0e4ac/brand-identity.js +1 -0
- package/dist/web-ui/content/modules/c3e0de0a61f0db34/chunk.js +1 -0
- package/dist/web-ui/content/modules/{9d48d1ea3c9228bd → c3e1f083e07d6840}/library-bridge.js +1 -1
- package/dist/web-ui/content/modules/cd5e90a37ccdf2dc/group-editor.js +1 -0
- package/dist/web-ui/content/modules/{2edc19639ac1bd4b → cdb8281bb89da303}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{f6f86d43c12b0864 → ce90d0b55bbe6430}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{72b13a7486b6d437 → d274491ffc8ee646}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{b1d43ed06a5d1a57 → d3cc94cf4a47a12a}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{b0c4eed46c1b6ad4 → d4b07cef43d36e79}/chat-render.js +5 -4
- package/dist/web-ui/content/modules/{8a4f0ef2c0812af6 → d7b540917fb90755}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{ef7f76b551fdc619 → d856bfc56bea5f20}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{42c439d3acfe22bd → d857ea8244b1026e}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{36ce8123a2079807 → d9019922118b2c37}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{58836e616e6d12ff → d90e77f9f8d4f1ec}/workspaces-panel.js +1 -1
- package/dist/web-ui/content/modules/{4d4870f20eb1c52d → da1aa5fa0186fea7}/chunk.js +1 -1
- package/dist/web-ui/content/modules/de977ff0b3ad3882/progress.js +1 -0
- package/dist/web-ui/content/modules/{222f48948517167e → e01769df89873feb}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{5df1b522f5d3c99d → e0ab9fa5a8882433}/host.js +1 -1
- package/dist/web-ui/content/modules/e25dea52e13504b6/markdown.js +1 -0
- package/dist/web-ui/content/modules/{36bd608366c56b72 → e43af4aa407885fd}/chunk.js +1 -1
- package/dist/web-ui/content/modules/ee893f06cdb2b4f7/panel.js +1 -0
- package/dist/web-ui/content/modules/{f90f80d7207dff1f → f2bdbdc4a1141826}/host.js +1 -1
- package/dist/web-ui/content/modules/{e33f6d66a02ec26c → f36b4d3d44491852}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{665e81fe210f1b7a → fa79ed5d045c24dd}/chunk.js +1 -1
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +412 -406
- package/dist/web-ui/embedded-assets.js +4 -4
- package/package.json +1 -1
- package/scripts/install-render-binary.js +1 -1
- package/dist/web-ui/content/modules/1a7303b87763fd43/employee-profile.js +0 -1
- package/dist/web-ui/content/modules/1d35739f634745f6/task-board-host.js +0 -22
- package/dist/web-ui/content/modules/2099d37d57856cbd/brand-identity.js +0 -1
- package/dist/web-ui/content/modules/2d6339b2f6c2e2d0/host.js +0 -1
- package/dist/web-ui/content/modules/2f88fc37c6105f7e/sidebar-peek.js +0 -1
- package/dist/web-ui/content/modules/31124fb7ee6939df/host.js +0 -1
- package/dist/web-ui/content/modules/31b28c5d0019c363/host-bridge.js +0 -1
- package/dist/web-ui/content/modules/36e5c3a6d6705358/employee-card.js +0 -1
- package/dist/web-ui/content/modules/3700d99c54d3c400/host.js +0 -1
- package/dist/web-ui/content/modules/3ae29086b227f9d9/markdown.js +0 -1
- package/dist/web-ui/content/modules/42db858e4417c702/chunk.js +0 -10
- package/dist/web-ui/content/modules/443dc81eac29f233/host.js +0 -1
- package/dist/web-ui/content/modules/4af10e1f8a789204/runtime.js +0 -275
- package/dist/web-ui/content/modules/55b75ebcf6924001/host.js +0 -30
- package/dist/web-ui/content/modules/626c7ce0aa1714b0/host.js +0 -1
- package/dist/web-ui/content/modules/76adde4019731df1/chunk.js +0 -1
- package/dist/web-ui/content/modules/7925b65867b7aa4a/queue-view-adapter.js +0 -3
- package/dist/web-ui/content/modules/8a6844a4251a1271/chunk.js +0 -1
- package/dist/web-ui/content/modules/8bd214570df4a7a0/host.js +0 -1
- package/dist/web-ui/content/modules/8bff85e108320aab/panel.js +0 -1
- package/dist/web-ui/content/modules/988e01b67e6f1883/composer-surface-adapter.js +0 -1
- package/dist/web-ui/content/modules/a975ff38c3d43ba8/workspace-window.js +0 -1
- package/dist/web-ui/content/modules/b550260fc9c5363a/dropdown-surface.js +0 -1
- package/dist/web-ui/content/modules/bce467c603299460/chunk.js +0 -1
- package/dist/web-ui/content/modules/c138b442c1682189/panel-host.js +0 -1
- package/dist/web-ui/content/modules/c32d14632487364f/group-editor.js +0 -1
- package/dist/web-ui/content/modules/c6d0a1b2753a4883/host.js +0 -1
- package/dist/web-ui/content/modules/cdff6e7f72879c88/progress.js +0 -1
- package/dist/web-ui/content/modules/cfee7b0d79587d59/home.js +0 -6
- package/dist/web-ui/content/modules/e5e93fe5304838a1/chunk.js +0 -1
- package/dist/web-ui/content/modules/fbe35e7ddd7384fa/host.js +0 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { boundedFetch } from "./dingtalk-dws.js";
|
|
2
|
+
/** Native robot Stream ingress. DWS custom commands omit sender/group/message identity, so never use that escape hatch. */
|
|
3
|
+
export declare class DingTalkRobotStream {
|
|
4
|
+
private readonly credentials;
|
|
5
|
+
private readonly receive;
|
|
6
|
+
private readonly status;
|
|
7
|
+
private readonly request;
|
|
8
|
+
private socket?;
|
|
9
|
+
private retry?;
|
|
10
|
+
private heartbeat?;
|
|
11
|
+
private closed;
|
|
12
|
+
private epoch;
|
|
13
|
+
private failures;
|
|
14
|
+
private active;
|
|
15
|
+
constructor(credentials: {
|
|
16
|
+
clientId: string;
|
|
17
|
+
clientSecret: string;
|
|
18
|
+
}, receive: (payload: Record<string, any>) => Promise<void>, status: (connected: boolean) => void, request?: typeof boundedFetch);
|
|
19
|
+
get connected(): boolean;
|
|
20
|
+
start(): void;
|
|
21
|
+
private connect;
|
|
22
|
+
private reconnect;
|
|
23
|
+
dispose(): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
import { boundedFetch, DingTalkError, object, string } from "./dingtalk-dws.js";
|
|
3
|
+
/** Native robot Stream ingress. DWS custom commands omit sender/group/message identity, so never use that escape hatch. */
|
|
4
|
+
export class DingTalkRobotStream {
|
|
5
|
+
credentials;
|
|
6
|
+
receive;
|
|
7
|
+
status;
|
|
8
|
+
request;
|
|
9
|
+
socket;
|
|
10
|
+
retry;
|
|
11
|
+
heartbeat;
|
|
12
|
+
closed = false;
|
|
13
|
+
epoch = 0;
|
|
14
|
+
failures = 0;
|
|
15
|
+
active = 0;
|
|
16
|
+
constructor(credentials, receive, status, request = boundedFetch) {
|
|
17
|
+
this.credentials = credentials;
|
|
18
|
+
this.receive = receive;
|
|
19
|
+
this.status = status;
|
|
20
|
+
this.request = request;
|
|
21
|
+
}
|
|
22
|
+
get connected() { return !this.closed && this.socket?.readyState === WebSocket.OPEN; }
|
|
23
|
+
start() { void this.connect(); }
|
|
24
|
+
async connect() {
|
|
25
|
+
const epoch = ++this.epoch;
|
|
26
|
+
try {
|
|
27
|
+
const response = object(JSON.parse((await this.request("https://api.dingtalk.com/v1.0/gateway/connections/open", {
|
|
28
|
+
method: "POST", redirect: "error", headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
29
|
+
body: JSON.stringify({ ...this.credentials, ua: "Wand", subscriptions: [{ type: "CALLBACK", topic: "/v1.0/im/bot/messages/get" }] }),
|
|
30
|
+
})).toString()));
|
|
31
|
+
if (this.closed || epoch !== this.epoch)
|
|
32
|
+
return;
|
|
33
|
+
const url = new URL(string(response.endpoint));
|
|
34
|
+
// Credential-bearing tickets may only be sent to DingTalk's TLS stream hosts.
|
|
35
|
+
if (url.protocol !== "wss:" || !(url.hostname === "dingtalk.com" || url.hostname.endsWith(".dingtalk.com")) || url.username || url.password || url.port || !string(response.ticket))
|
|
36
|
+
throw new Error("invalid stream endpoint");
|
|
37
|
+
url.searchParams.set("ticket", response.ticket);
|
|
38
|
+
const socket = new WebSocket(url, { maxPayload: 512 * 1024, handshakeTimeout: 15_000, followRedirects: false });
|
|
39
|
+
this.socket = socket;
|
|
40
|
+
socket.on("error", () => { }); // WebSocket errors can contain a secret-bearing URL; never log them.
|
|
41
|
+
socket.once("open", () => {
|
|
42
|
+
if (this.closed || epoch !== this.epoch) {
|
|
43
|
+
socket.close();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
this.failures = 0;
|
|
47
|
+
this.status(true);
|
|
48
|
+
let alive = true;
|
|
49
|
+
socket.on("pong", () => { alive = true; });
|
|
50
|
+
this.heartbeat = setInterval(() => { if (!alive) {
|
|
51
|
+
socket.terminate();
|
|
52
|
+
return;
|
|
53
|
+
} alive = false; if (socket.readyState === WebSocket.OPEN)
|
|
54
|
+
socket.ping(); }, 30_000);
|
|
55
|
+
this.heartbeat.unref?.();
|
|
56
|
+
});
|
|
57
|
+
socket.on("message", raw => {
|
|
58
|
+
if (this.closed || epoch !== this.epoch || this.active >= 32)
|
|
59
|
+
return;
|
|
60
|
+
let frame;
|
|
61
|
+
try {
|
|
62
|
+
frame = object(JSON.parse(raw.toString()));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (frame.type === "SYSTEM") {
|
|
68
|
+
if (["ping", "disconnect"].includes(string(frame.headers?.topic))) {
|
|
69
|
+
socket.send(JSON.stringify({ code: 200, headers: frame.headers, message: "OK", data: frame.data }));
|
|
70
|
+
if (frame.headers.topic === "disconnect")
|
|
71
|
+
socket.close();
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (frame.type !== "CALLBACK" || frame.headers?.topic !== "/v1.0/im/bot/messages/get" || !string(frame.headers?.messageId))
|
|
76
|
+
return;
|
|
77
|
+
let data;
|
|
78
|
+
try {
|
|
79
|
+
data = object(JSON.parse(string(frame.data)));
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.active++;
|
|
85
|
+
// Acknowledge only after durable admission, not after model completion. On failure leave unacknowledged for redelivery.
|
|
86
|
+
void this.receive(data).then(() => {
|
|
87
|
+
if (!this.closed && socket.readyState === WebSocket.OPEN)
|
|
88
|
+
socket.send(JSON.stringify({ code: 200,
|
|
89
|
+
headers: { contentType: "application/json", messageId: frame.headers.messageId }, message: "OK", data: JSON.stringify({ response: {} }) }));
|
|
90
|
+
}).catch(() => { }).finally(() => { this.active--; });
|
|
91
|
+
});
|
|
92
|
+
socket.once("close", () => { if (this.heartbeat)
|
|
93
|
+
clearInterval(this.heartbeat); this.heartbeat = undefined; if (!this.closed && epoch === this.epoch) {
|
|
94
|
+
this.status(false);
|
|
95
|
+
this.reconnect(epoch);
|
|
96
|
+
} });
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (this.closed || epoch !== this.epoch)
|
|
100
|
+
return;
|
|
101
|
+
this.status(false);
|
|
102
|
+
if (!(error instanceof DingTalkError && [401, 403].includes(error.httpStatus ?? 0)))
|
|
103
|
+
this.reconnect(epoch);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
reconnect(epoch) {
|
|
107
|
+
if (this.closed || epoch !== this.epoch || this.retry || this.failures >= 6)
|
|
108
|
+
return;
|
|
109
|
+
this.retry = setTimeout(() => { this.retry = undefined; void this.connect(); }, Math.min(60_000, 1000 * 2 ** Math.min(6, this.failures++)));
|
|
110
|
+
this.retry.unref?.();
|
|
111
|
+
}
|
|
112
|
+
dispose() { this.closed = true; this.epoch++; if (this.retry)
|
|
113
|
+
clearTimeout(this.retry); if (this.heartbeat)
|
|
114
|
+
clearInterval(this.heartbeat); this.socket?.close(); }
|
|
115
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { WandTaskAgent } from "./task-types.js";
|
|
2
|
+
export type DingTalkBindingState = "creating" | "configuring" | "joining" | "verifying" | "ready" | "paused" | "restricted" | "failed" | "unknown";
|
|
3
|
+
export interface DingTalkAccount {
|
|
4
|
+
id: string;
|
|
5
|
+
corpId: string;
|
|
6
|
+
userId: string;
|
|
7
|
+
name: string;
|
|
8
|
+
corpName: string;
|
|
9
|
+
}
|
|
10
|
+
export interface DingTalkGroup {
|
|
11
|
+
id: string;
|
|
12
|
+
accountId: string;
|
|
13
|
+
corpId: string;
|
|
14
|
+
externalId: string;
|
|
15
|
+
name: string;
|
|
16
|
+
conversationId: string;
|
|
17
|
+
syncState: "connecting" | "live" | "at_only" | "disconnected" | "paused";
|
|
18
|
+
syncError?: string;
|
|
19
|
+
historyComplete: boolean;
|
|
20
|
+
updatedAt: string;
|
|
21
|
+
}
|
|
22
|
+
export interface DingTalkRobot {
|
|
23
|
+
id: string;
|
|
24
|
+
corpId: string;
|
|
25
|
+
employeeId: string;
|
|
26
|
+
accountId: string;
|
|
27
|
+
name: string;
|
|
28
|
+
state: DingTalkBindingState;
|
|
29
|
+
taskId?: string;
|
|
30
|
+
appId?: string;
|
|
31
|
+
robotCode?: string;
|
|
32
|
+
clientId?: string;
|
|
33
|
+
versionId?: string;
|
|
34
|
+
error?: string;
|
|
35
|
+
updatedAt: string;
|
|
36
|
+
}
|
|
37
|
+
export interface DingTalkBinding {
|
|
38
|
+
id: string;
|
|
39
|
+
groupId: string;
|
|
40
|
+
employeeId: string;
|
|
41
|
+
robotId: string;
|
|
42
|
+
state: DingTalkBindingState;
|
|
43
|
+
allowedUsers: string[];
|
|
44
|
+
ownerUserId?: string;
|
|
45
|
+
generation?: string;
|
|
46
|
+
fixedAgent?: WandTaskAgent;
|
|
47
|
+
error?: string;
|
|
48
|
+
removedAt?: string;
|
|
49
|
+
verifiedAt?: string;
|
|
50
|
+
updatedAt: string;
|
|
51
|
+
}
|
|
52
|
+
export interface DingTalkExternalConversation {
|
|
53
|
+
provider: "dingtalk";
|
|
54
|
+
groupId: string;
|
|
55
|
+
corpId: string;
|
|
56
|
+
externalId: string;
|
|
57
|
+
syncState: DingTalkGroup["syncState"];
|
|
58
|
+
syncError?: string;
|
|
59
|
+
historyComplete: boolean;
|
|
60
|
+
bindings: Array<Pick<DingTalkBinding, "id" | "employeeId" | "state" | "fixedAgent" | "error">>;
|
|
61
|
+
}
|
|
62
|
+
export interface DingTalkSelection {
|
|
63
|
+
accountId: string;
|
|
64
|
+
groupIds: string[];
|
|
65
|
+
employeeIds: string[];
|
|
66
|
+
}
|
|
67
|
+
export interface DingTalkPlan extends DingTalkSelection {
|
|
68
|
+
id: string;
|
|
69
|
+
expiresAt: string;
|
|
70
|
+
groups: Array<{
|
|
71
|
+
id: string;
|
|
72
|
+
name: string;
|
|
73
|
+
}>;
|
|
74
|
+
employees: Array<{
|
|
75
|
+
id: string;
|
|
76
|
+
name: string;
|
|
77
|
+
robotName: string;
|
|
78
|
+
appName: string;
|
|
79
|
+
candidateIndex?: number;
|
|
80
|
+
agent?: WandTaskAgent;
|
|
81
|
+
}>;
|
|
82
|
+
pairs: Array<{
|
|
83
|
+
groupId: string;
|
|
84
|
+
employeeId: string;
|
|
85
|
+
existing: boolean;
|
|
86
|
+
}>;
|
|
87
|
+
newRobots: number;
|
|
88
|
+
warning: string;
|
|
89
|
+
}
|
|
90
|
+
export interface DingTalkJob {
|
|
91
|
+
id: string;
|
|
92
|
+
planId: string;
|
|
93
|
+
state: "running" | "done" | "partial" | "unknown";
|
|
94
|
+
bindingIds: string[];
|
|
95
|
+
bindingGenerations?: Record<string, string | undefined>;
|
|
96
|
+
updatedAt: string;
|
|
97
|
+
}
|
|
98
|
+
export interface DingTalkDiscoveryGroup {
|
|
99
|
+
id: string;
|
|
100
|
+
name: string;
|
|
101
|
+
memberCount?: number;
|
|
102
|
+
}
|
|
103
|
+
export interface DingTalkMessage {
|
|
104
|
+
id: string;
|
|
105
|
+
groupId: string;
|
|
106
|
+
senderId: string;
|
|
107
|
+
senderName: string;
|
|
108
|
+
text: string;
|
|
109
|
+
createdAt: string;
|
|
110
|
+
senderIsBot: boolean;
|
|
111
|
+
mentionedRobotCodes: string[];
|
|
112
|
+
recalled?: boolean;
|
|
113
|
+
}
|
|
114
|
+
export interface DingTalkRun {
|
|
115
|
+
id: string;
|
|
116
|
+
groupId: string;
|
|
117
|
+
bindingId: string;
|
|
118
|
+
bindingGeneration?: string;
|
|
119
|
+
incomingId: string;
|
|
120
|
+
sessionId?: string;
|
|
121
|
+
state: "accepted" | "running" | "completed" | "failed" | "unknown" | "delivered";
|
|
122
|
+
text?: string;
|
|
123
|
+
error?: string;
|
|
124
|
+
delivery?: "pending" | "sending" | "sent" | "unknown" | "none";
|
|
125
|
+
updatedAt: string;
|
|
126
|
+
}
|
|
127
|
+
export interface DingTalkStatus {
|
|
128
|
+
installed: boolean;
|
|
129
|
+
version?: string;
|
|
130
|
+
accounts: DingTalkAccount[];
|
|
131
|
+
groups: DingTalkGroup[];
|
|
132
|
+
bindings: DingTalkBinding[];
|
|
133
|
+
robots: DingTalkRobot[];
|
|
134
|
+
jobs: DingTalkJob[];
|
|
135
|
+
/** Operational diagnostics, not the transport-level error envelope. Missing DWS is a normal state. */
|
|
136
|
+
diagnostic?: string;
|
|
137
|
+
}
|
|
138
|
+
export type DingTalkRecordKind = "group" | "binding" | "robot" | "plan" | "job" | "run" | "request" | "event";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -56,6 +56,14 @@ export function startEmployeeKnowledgeRunner(storage, runner, context, observer,
|
|
|
56
56
|
delete env.WAND_KNOWLEDGE_DB;
|
|
57
57
|
delete env.WAND_KNOWLEDGE_TOKEN;
|
|
58
58
|
delete env.WAND_WEB_LOGIN_PROOF;
|
|
59
|
+
// External group requests retain employee identity, but never inherit private knowledge,
|
|
60
|
+
// explicit-memory writes or owner Web-login capabilities. This is a trusted session tag,
|
|
61
|
+
// not a phrase read from the remote message.
|
|
62
|
+
if (context.session.automationId?.startsWith("dingtalk:")) {
|
|
63
|
+
for (const key of ["DWS_CLIENT_ID", "DWS_CLIENT_SECRET", "DWS_ACCESS_TOKEN", "DWS_TOKEN", "DWS_CONFIG_DIR"])
|
|
64
|
+
delete env[key];
|
|
65
|
+
return runner.start({ ...context, env }, observer);
|
|
66
|
+
}
|
|
59
67
|
if (!employeeId)
|
|
60
68
|
return runner.start({ ...context, env }, observer);
|
|
61
69
|
if (!storage.getSiliconEmployee(employeeId)) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ApnsConfiguration, ApnsTransport } from "./apns-client.js";
|
|
2
|
+
import type { WandStorage } from "./storage.js";
|
|
3
|
+
import type { ProcessEvent, SessionSnapshot } from "./types.js";
|
|
4
|
+
/** Updates already-created ActivityKit activities only. Never starts an activity or changes execution facts. */
|
|
5
|
+
export declare class LiveActivityService {
|
|
6
|
+
private readonly storage;
|
|
7
|
+
readonly configuration: ApnsConfiguration | null;
|
|
8
|
+
private readonly transport;
|
|
9
|
+
private readonly credentialDigest;
|
|
10
|
+
private readonly getSessions;
|
|
11
|
+
private readonly warn;
|
|
12
|
+
private readonly now;
|
|
13
|
+
private stopped;
|
|
14
|
+
private timer;
|
|
15
|
+
private running;
|
|
16
|
+
private lastSweep;
|
|
17
|
+
private nextWarningAt;
|
|
18
|
+
constructor(storage: WandStorage, configuration: ApnsConfiguration | null, transport: ApnsTransport | null, credentialDigest: () => string, getSessions: () => readonly SessionSnapshot[] | null, warn?: (message: string) => void, now?: () => number);
|
|
19
|
+
get available(): boolean;
|
|
20
|
+
private report;
|
|
21
|
+
start(): void;
|
|
22
|
+
ingest(event: ProcessEvent): void;
|
|
23
|
+
flush(): Promise<void>;
|
|
24
|
+
private drain;
|
|
25
|
+
dispose(): void;
|
|
26
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { LIVE_ACTIVITY_HEARTBEAT_MS, liveActivityPayload, projectLiveActivity } from "./live-activity-state.js";
|
|
2
|
+
function newAttentionState(previousSignature, projection) {
|
|
3
|
+
if (!projection.urgent)
|
|
4
|
+
return false;
|
|
5
|
+
if (projection.event === "end")
|
|
6
|
+
return true;
|
|
7
|
+
const attention = new Set(["permission", "waiting-input", "failed", "stopped", "done"]);
|
|
8
|
+
let previous = [];
|
|
9
|
+
try {
|
|
10
|
+
const parsed = JSON.parse(previousSignature);
|
|
11
|
+
if (Array.isArray(parsed) && Array.isArray(parsed[1]))
|
|
12
|
+
previous = parsed[1];
|
|
13
|
+
}
|
|
14
|
+
catch { /* first update */ }
|
|
15
|
+
const key = (entry) => JSON.stringify([entry.id, entry.stateRaw, entry.finishedAt]);
|
|
16
|
+
const known = new Set(previous.filter(entry => attention.has(entry.stateRaw)).map(key));
|
|
17
|
+
return projection.entries.some(entry => attention.has(entry.stateRaw) && !known.has(key(entry)));
|
|
18
|
+
}
|
|
19
|
+
/** Updates already-created ActivityKit activities only. Never starts an activity or changes execution facts. */
|
|
20
|
+
export class LiveActivityService {
|
|
21
|
+
storage;
|
|
22
|
+
configuration;
|
|
23
|
+
transport;
|
|
24
|
+
credentialDigest;
|
|
25
|
+
getSessions;
|
|
26
|
+
warn;
|
|
27
|
+
now;
|
|
28
|
+
stopped = false;
|
|
29
|
+
timer = null;
|
|
30
|
+
running = null;
|
|
31
|
+
lastSweep = 0;
|
|
32
|
+
nextWarningAt = 0;
|
|
33
|
+
constructor(storage, configuration, transport, credentialDigest, getSessions, warn = () => { }, now = Date.now) {
|
|
34
|
+
this.storage = storage;
|
|
35
|
+
this.configuration = configuration;
|
|
36
|
+
this.transport = transport;
|
|
37
|
+
this.credentialDigest = credentialDigest;
|
|
38
|
+
this.getSessions = getSessions;
|
|
39
|
+
this.warn = warn;
|
|
40
|
+
this.now = now;
|
|
41
|
+
storage.onClose(() => this.dispose());
|
|
42
|
+
}
|
|
43
|
+
get available() { return !!this.configuration && !!this.transport && !this.stopped; }
|
|
44
|
+
report(message) {
|
|
45
|
+
if (this.now() < this.nextWarningAt)
|
|
46
|
+
return;
|
|
47
|
+
this.nextWarningAt = this.now() + 60_000;
|
|
48
|
+
this.warn(message);
|
|
49
|
+
}
|
|
50
|
+
start() {
|
|
51
|
+
if (!this.available || this.timer)
|
|
52
|
+
return;
|
|
53
|
+
this.storage.liveActivities.wake(this.now());
|
|
54
|
+
this.timer = setInterval(() => { void this.flush(); }, 2000);
|
|
55
|
+
this.timer.unref();
|
|
56
|
+
void this.flush();
|
|
57
|
+
}
|
|
58
|
+
ingest(event) {
|
|
59
|
+
if (!this.available || !["started", "status", "task", "ended"].includes(event.type))
|
|
60
|
+
return;
|
|
61
|
+
try {
|
|
62
|
+
this.storage.liveActivities.dirty(this.now());
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
this.report("灵动岛状态暂不可入队;不影响任务执行。");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
flush() {
|
|
69
|
+
if (!this.available)
|
|
70
|
+
return Promise.resolve();
|
|
71
|
+
if (!this.running) {
|
|
72
|
+
this.running = this.drain().catch(() => {
|
|
73
|
+
if (!this.stopped)
|
|
74
|
+
this.report("灵动岛远程更新暂不可用,将稍后重试。");
|
|
75
|
+
}).finally(() => { this.running = null; });
|
|
76
|
+
}
|
|
77
|
+
return this.running;
|
|
78
|
+
}
|
|
79
|
+
async drain() {
|
|
80
|
+
if (this.now() - this.lastSweep >= 60_000) {
|
|
81
|
+
this.storage.liveActivities.sweep(this.now());
|
|
82
|
+
this.lastSweep = this.now();
|
|
83
|
+
}
|
|
84
|
+
for (let i = 0; i < 12 && !this.stopped; i++) {
|
|
85
|
+
let record = this.storage.liveActivities.due(this.now());
|
|
86
|
+
if (!record)
|
|
87
|
+
return;
|
|
88
|
+
if (record.expires_at <= this.now() || record.credential_digest !== this.credentialDigest()
|
|
89
|
+
|| record.topic !== this.configuration.topic || record.environment !== this.configuration.environment) {
|
|
90
|
+
this.storage.liveActivities.disable(record.id, "ExpiredOrCredentialChanged", record.generation);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const snapshots = this.getSessions();
|
|
94
|
+
// A failed/unfinished session recovery is not evidence that every running session has completed or disappeared.
|
|
95
|
+
if (!snapshots) {
|
|
96
|
+
this.storage.liveActivities.defer(record.id, record.generation, this.now() + 30_000);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const previous = JSON.parse(record.content_json);
|
|
100
|
+
const projection = projectLiveActivity(snapshots, previous, record.server_id, this.now());
|
|
101
|
+
// Foreground optimistic Activity.request may beat the server's input-acceptance event.
|
|
102
|
+
if (!projection.entries.length && !record.signature && !previous.length && this.now() - record.created_at < 30_000) {
|
|
103
|
+
this.storage.liveActivities.defer(record.id, record.generation, Math.min(record.created_at + 30_000, this.now() + 2000));
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (!record.pending_payload || record.pending_signature !== projection.signature) {
|
|
107
|
+
if (record.signature === projection.signature && this.now() - record.last_sent_at < LIVE_ACTIVITY_HEARTBEAT_MS) {
|
|
108
|
+
this.storage.liveActivities.defer(record.id, record.generation, Math.min(projection.nextAt, record.last_sent_at + LIVE_ACTIVITY_HEARTBEAT_MS));
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const timestamp = Math.floor(this.now() / 1000);
|
|
112
|
+
// ActivityKit orders by seconds. Never send equal/older timestamps or invent a future progress timestamp.
|
|
113
|
+
if (timestamp <= record.last_timestamp) {
|
|
114
|
+
this.storage.liveActivities.defer(record.id, record.generation, (record.last_timestamp + 1) * 1000);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const prepared = this.storage.liveActivities.prepare(record, {
|
|
118
|
+
payload: liveActivityPayload(projection.content, projection.event, timestamp),
|
|
119
|
+
signature: projection.signature, entries: projection.entries, event: projection.event,
|
|
120
|
+
priority: newAttentionState(record.signature, projection) ? 10 : 5, timestamp, nextAt: projection.nextAt,
|
|
121
|
+
}, this.now());
|
|
122
|
+
if (!prepared)
|
|
123
|
+
continue;
|
|
124
|
+
record = prepared;
|
|
125
|
+
}
|
|
126
|
+
let result;
|
|
127
|
+
try {
|
|
128
|
+
result = await this.transport.send({ id: record.pending_id, token: record.token,
|
|
129
|
+
payload: record.pending_payload, collapseId: record.id,
|
|
130
|
+
expiresAt: Math.min(record.expires_at, (record.last_timestamp + 180) * 1000),
|
|
131
|
+
pushType: "liveactivity", priority: record.pending_priority ?? 5 });
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
result = { outcome: "retry", reason: "TransportFailure" };
|
|
135
|
+
}
|
|
136
|
+
if (this.stopped)
|
|
137
|
+
return;
|
|
138
|
+
if (result.outcome === "invalid-device") {
|
|
139
|
+
this.storage.liveActivities.disable(record.id, result.reason, record.generation);
|
|
140
|
+
}
|
|
141
|
+
else if (result.outcome === "retry" && record.attempts < 4) {
|
|
142
|
+
this.storage.liveActivities.retry(record, result.reason, this.now());
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
this.storage.liveActivities.finish(record, result.outcome === "accepted" ? "accepted" : result.outcome === "rejected" ? "rejected" : "failed", result.reason, this.now());
|
|
146
|
+
if (result.outcome !== "accepted")
|
|
147
|
+
this.report(`APNs 未接受灵动岛更新(${result.reason});不影响普通消息推送与任务执行。`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
dispose() {
|
|
152
|
+
this.stopped = true;
|
|
153
|
+
if (this.timer)
|
|
154
|
+
clearInterval(this.timer);
|
|
155
|
+
this.timer = null;
|
|
156
|
+
this.transport?.close();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SessionSnapshot } from "./types.js";
|
|
2
|
+
/** Exactly the Codable fields in iOS SessionActivityAttributes.ContentState; dates use Apple's reference epoch. */
|
|
3
|
+
export interface LiveActivityEntry {
|
|
4
|
+
id: string;
|
|
5
|
+
serverID: string;
|
|
6
|
+
title: string;
|
|
7
|
+
providerRaw: string;
|
|
8
|
+
stateRaw: "responding" | "background" | "permission" | "waiting-input" | "failed" | "stopped" | "done";
|
|
9
|
+
taskTitle?: string;
|
|
10
|
+
queuedCount: number;
|
|
11
|
+
startedAt?: number;
|
|
12
|
+
finishedAt?: number;
|
|
13
|
+
engineRaw?: string;
|
|
14
|
+
pendingPrompt?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface LiveActivityCachedEntry extends LiveActivityEntry {
|
|
17
|
+
finishedUntil?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface LiveActivityContent {
|
|
20
|
+
sessions: LiveActivityEntry[];
|
|
21
|
+
updatedAt: number;
|
|
22
|
+
}
|
|
23
|
+
export declare const APPLE_REFERENCE_UNIX_SECONDS = 978307200;
|
|
24
|
+
export declare const LIVE_ACTIVITY_HEARTBEAT_MS = 60000;
|
|
25
|
+
export declare const LIVE_ACTIVITY_STALE_SECONDS = 180;
|
|
26
|
+
export declare function liveActivityText(value: string, maxCharacters: number, maxBytes: number): string;
|
|
27
|
+
export declare function liveActivityWireEntry(cached: LiveActivityCachedEntry): LiveActivityEntry;
|
|
28
|
+
/** A pure snapshot projection: no execution changes, fabricated progress, historical-completion replay or timer extension. */
|
|
29
|
+
export declare function projectLiveActivity(sessions: readonly SessionSnapshot[], previous: readonly LiveActivityCachedEntry[], serverID: string, now: number): {
|
|
30
|
+
entries: LiveActivityCachedEntry[];
|
|
31
|
+
content: LiveActivityContent;
|
|
32
|
+
event: "update" | "end";
|
|
33
|
+
signature: string;
|
|
34
|
+
urgent: boolean;
|
|
35
|
+
nextAt: number;
|
|
36
|
+
};
|
|
37
|
+
export declare function liveActivityPayload(content: LiveActivityContent, event: "update" | "end", timestamp: number): string;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export const APPLE_REFERENCE_UNIX_SECONDS = 978_307_200;
|
|
2
|
+
export const LIVE_ACTIVITY_HEARTBEAT_MS = 60_000;
|
|
3
|
+
export const LIVE_ACTIVITY_STALE_SECONDS = 180;
|
|
4
|
+
const priorities = {
|
|
5
|
+
permission: 0, "waiting-input": 1, failed: 2, responding: 3, background: 4, stopped: 5, done: 6,
|
|
6
|
+
};
|
|
7
|
+
const providers = new Set(["claude", "codex", "opencode", "grok", "qoder", "pi"]);
|
|
8
|
+
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
9
|
+
export function liveActivityText(value, maxCharacters, maxBytes) {
|
|
10
|
+
let text = "", bytes = 0, count = 0;
|
|
11
|
+
for (const { segment } of segmenter.segment(value.trim())) {
|
|
12
|
+
if (++count > maxCharacters || bytes + Buffer.byteLength(segment) > maxBytes)
|
|
13
|
+
break;
|
|
14
|
+
text += segment;
|
|
15
|
+
bytes += Buffer.byteLength(segment);
|
|
16
|
+
}
|
|
17
|
+
return text;
|
|
18
|
+
}
|
|
19
|
+
function appleDate(value) {
|
|
20
|
+
const ms = value ? Date.parse(value) : NaN;
|
|
21
|
+
return Number.isFinite(ms) ? ms / 1000 - APPLE_REFERENCE_UNIX_SECONDS : undefined;
|
|
22
|
+
}
|
|
23
|
+
function responding(session) {
|
|
24
|
+
if (session.sessionKind === "structured")
|
|
25
|
+
return session.structuredState?.inFlight === true;
|
|
26
|
+
if (session.status !== "running" || session.providerCliActive === false)
|
|
27
|
+
return false;
|
|
28
|
+
return providers.has(session.provider ?? "") ? session.ptyBusy === true : session.ptyCommandRunning === true;
|
|
29
|
+
}
|
|
30
|
+
function entry(session, serverID, stateRaw) {
|
|
31
|
+
const busy = stateRaw === "responding" || stateRaw === "background";
|
|
32
|
+
const title = liveActivityText(session.title || "会话", 24, 96);
|
|
33
|
+
return {
|
|
34
|
+
id: session.id, serverID, title,
|
|
35
|
+
providerRaw: liveActivityText(session.provider ?? "terminal", 40, 60), stateRaw,
|
|
36
|
+
taskTitle: session.currentTaskTitle ? liveActivityText(session.currentTaskTitle, 40, 120) : undefined,
|
|
37
|
+
queuedCount: Math.max(0, Math.min(9999, session.queuedMessages?.length ?? 0)),
|
|
38
|
+
startedAt: busy ? appleDate(session.structuredState?.turnStartedAt ?? session.ptyTurnStartedAt) : undefined,
|
|
39
|
+
engineRaw: session.structuredState?.engine,
|
|
40
|
+
// Do not put escalation arguments, targets, paths, transcript, command or cwd on Apple's servers.
|
|
41
|
+
pendingPrompt: stateRaw === "permission" ? "等待你的授权后继续" : stateRaw === "waiting-input" ? "等待你的回复后继续" : undefined,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function liveActivityWireEntry(cached) {
|
|
45
|
+
const { finishedUntil: _finishedUntil, ...wire } = cached;
|
|
46
|
+
return wire;
|
|
47
|
+
}
|
|
48
|
+
/** A pure snapshot projection: no execution changes, fabricated progress, historical-completion replay or timer extension. */
|
|
49
|
+
export function projectLiveActivity(sessions, previous, serverID, now) {
|
|
50
|
+
const oldById = new Map(previous.filter(item => item.serverID === serverID).map(item => [item.id, item]));
|
|
51
|
+
const entries = [];
|
|
52
|
+
for (const session of sessions) {
|
|
53
|
+
if (session.archived)
|
|
54
|
+
continue;
|
|
55
|
+
const old = oldById.get(session.id);
|
|
56
|
+
const pending = !!session.pendingEscalation || session.permissionBlocked === true;
|
|
57
|
+
if (pending) {
|
|
58
|
+
entries.push(entry(session, serverID, "permission"));
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (responding(session)) {
|
|
62
|
+
entries.push(entry(session, serverID, session.structuredState?.phase === "background" ? "background" : "responding"));
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// A terminal snapshot only earns a grace period if this activity actually observed it working.
|
|
66
|
+
if (!old)
|
|
67
|
+
continue;
|
|
68
|
+
let state;
|
|
69
|
+
if (session.status === "failed" || (session.status === "exited" && session.exitCode !== 0))
|
|
70
|
+
state = "failed";
|
|
71
|
+
else if (session.status === "stopped")
|
|
72
|
+
state = "stopped";
|
|
73
|
+
else if (["responding", "background", "done"].includes(old.stateRaw))
|
|
74
|
+
state = "done";
|
|
75
|
+
if (!state)
|
|
76
|
+
continue;
|
|
77
|
+
const finishedUntil = old.stateRaw === state && old.finishedUntil !== undefined
|
|
78
|
+
? old.finishedUntil : now + (state === "failed" ? 30_000 : 12_000);
|
|
79
|
+
if (finishedUntil <= now)
|
|
80
|
+
continue;
|
|
81
|
+
const finished = entry(session, serverID, state);
|
|
82
|
+
finished.finishedAt = (finishedUntil - (state === "failed" ? 30_000 : 12_000)) / 1000 - APPLE_REFERENCE_UNIX_SECONDS;
|
|
83
|
+
finished.queuedCount = 0;
|
|
84
|
+
delete finished.taskTitle;
|
|
85
|
+
delete finished.startedAt;
|
|
86
|
+
entries.push({ ...finished, finishedUntil });
|
|
87
|
+
}
|
|
88
|
+
entries.sort((a, b) => priorities[a.stateRaw] - priorities[b.stateRaw] || a.id.localeCompare(b.id));
|
|
89
|
+
entries.splice(8);
|
|
90
|
+
const updatedAt = now / 1000 - APPLE_REFERENCE_UNIX_SECONDS;
|
|
91
|
+
// Reserve ample room for APNs envelope/timestamp/stale-date. Never split a composed Unicode character.
|
|
92
|
+
while (entries.length > 1 && Buffer.byteLength(JSON.stringify({ sessions: entries.map(liveActivityWireEntry), updatedAt })) > 3300)
|
|
93
|
+
entries.pop();
|
|
94
|
+
const event = entries.length ? "update" : "end";
|
|
95
|
+
const wire = entries.length ? entries.map(liveActivityWireEntry) : previous.map(liveActivityWireEntry);
|
|
96
|
+
const content = { sessions: wire.slice(0, 8), updatedAt };
|
|
97
|
+
const signature = JSON.stringify([event, entries.map(liveActivityWireEntry)]);
|
|
98
|
+
const nextAt = Math.min(now + LIVE_ACTIVITY_HEARTBEAT_MS, ...entries.flatMap(item => item.finishedUntil === undefined ? [] : [item.finishedUntil]));
|
|
99
|
+
return { entries, content, event, signature,
|
|
100
|
+
urgent: event === "end" || entries.some(item => ["permission", "waiting-input", "failed", "done", "stopped"].includes(item.stateRaw)), nextAt };
|
|
101
|
+
}
|
|
102
|
+
export function liveActivityPayload(content, event, timestamp) {
|
|
103
|
+
const payload = JSON.stringify({ aps: { timestamp, event, "content-state": content,
|
|
104
|
+
...(event === "end" ? { "dismissal-date": 0 } : { "stale-date": timestamp + LIVE_ACTIVITY_STALE_SECONDS }) } });
|
|
105
|
+
if (Buffer.byteLength(payload) > 4096)
|
|
106
|
+
throw new Error("Live Activity payload exceeds APNs budget");
|
|
107
|
+
return payload;
|
|
108
|
+
}
|
|
@@ -35,6 +35,8 @@ export const API_ROUTE_RULES = [
|
|
|
35
35
|
{ prefix: "/api/settings", scope: "route-guarded" },
|
|
36
36
|
{ prefix: "/api/models", scope: "sessions" },
|
|
37
37
|
{ prefix: "/api/daemon-maintenance", scope: "route-guarded" },
|
|
38
|
+
// Mounted before the global guard: app-bearer registration, device-capability revocation only.
|
|
39
|
+
{ prefix: "/api/push", scope: "route-guarded" },
|
|
38
40
|
// ── 浏览器扩展密码库 ──
|
|
39
41
|
{ prefix: "/api/browser-extension", scope: "password-vault" },
|
|
40
42
|
// ── 文件与目录 ──
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { StructuredRunnerTurnState } from "./structured-runner.js";
|
|
2
|
+
/** Internal SQLite materialization, never a client DTO or a replay instruction. */
|
|
3
|
+
export interface PiCliCheckpoint {
|
|
4
|
+
version: 1;
|
|
5
|
+
incarnationId: string;
|
|
6
|
+
requestId: string;
|
|
7
|
+
source: {
|
|
8
|
+
file: string;
|
|
9
|
+
dev: number;
|
|
10
|
+
ino: number;
|
|
11
|
+
};
|
|
12
|
+
/** Native-file byte position immediately after a fully parsed record. */
|
|
13
|
+
offset: number;
|
|
14
|
+
state: StructuredRunnerTurnState;
|
|
15
|
+
primaryError: string | null;
|
|
16
|
+
}
|
|
17
|
+
export declare function validPiCliCheckpoint(value: unknown): value is PiCliCheckpoint;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function validPiCliCheckpoint(value) {
|
|
2
|
+
if (!value || typeof value !== "object")
|
|
3
|
+
return false;
|
|
4
|
+
const v = value;
|
|
5
|
+
return v.version === 1 && typeof v.incarnationId === "string" && typeof v.requestId === "string"
|
|
6
|
+
&& Number.isSafeInteger(v.offset) && v.offset >= 0
|
|
7
|
+
&& typeof v.source?.file === "string" && Number.isSafeInteger(v.source.dev) && Number.isSafeInteger(v.source.ino)
|
|
8
|
+
&& Array.isArray(v.state?.blocks) && typeof v.state.result === "string"
|
|
9
|
+
&& (v.primaryError === null || typeof v.primaryError === "string");
|
|
10
|
+
}
|