@cjhyy/code-shell-web 0.9.29 → 0.9.31
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/hooks/mobileOutputClient.d.ts +37 -0
- package/dist/hooks/mobileOutputClient.js +351 -0
- package/dist/hooks/useRemoteApp.js +114 -4
- package/dist/lib/mobileOutputRecovery.d.ts +34 -0
- package/dist/lib/mobileOutputRecovery.js +123 -0
- package/dist-app/assets/index-DLKEXorj.js +57 -0
- package/dist-app/assets/{index-EIfqZrcz.css → index-kPe-x_IE.css} +1 -1
- package/dist-app/index.html +2 -2
- package/package.json +3 -3
- package/dist-app/assets/index-Do7zJ7tU.js +0 -57
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { applyOutputJournalPage, compareOutputCursors, } from "./outputJournalRecovery.js";
|
|
2
|
+
import { initialChatState, reduceStream } from "./streamReducer.js";
|
|
3
|
+
/** Valid identities may arrive before their new Core Run acquires a durable anchor. */
|
|
4
|
+
export class MobilePendingInput extends Error {
|
|
5
|
+
ids;
|
|
6
|
+
constructor(ids) {
|
|
7
|
+
super("Recovery input has no recorded anchor");
|
|
8
|
+
this.ids = ids;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** No journal has ever been published by this explicitly journal-owned producer. */
|
|
12
|
+
export class MobileAwaitingJournal extends Error {
|
|
13
|
+
constructor() {
|
|
14
|
+
super("Awaiting the first durable output cursor");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Verified pages progressed, but the observed live head kept moving within the bound. */
|
|
18
|
+
export class MobileOutputCatchupPending extends Error {
|
|
19
|
+
cursor;
|
|
20
|
+
resume;
|
|
21
|
+
constructor(cursor, resume) {
|
|
22
|
+
super("Recovery could not catch up within its bound");
|
|
23
|
+
this.cursor = cursor;
|
|
24
|
+
this.resume = resume;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Build a private candidate, then join its frozen prefix to an actual Main snapshot. */
|
|
28
|
+
export async function recoverMobileOutput(args) {
|
|
29
|
+
let reply = await args.read({});
|
|
30
|
+
if (!args.canContinue())
|
|
31
|
+
throw new Error("Recovery cancelled");
|
|
32
|
+
if (reply.page.status === "unavailable" && args.requireJournal) {
|
|
33
|
+
const snapshot = reply.snapshot;
|
|
34
|
+
if (!snapshot ||
|
|
35
|
+
snapshot.unpaired ||
|
|
36
|
+
!snapshot.epoch ||
|
|
37
|
+
!Number.isSafeInteger(snapshot.nextSeq) ||
|
|
38
|
+
snapshot.nextSeq < 1)
|
|
39
|
+
throw new Error("Required output journal is unavailable");
|
|
40
|
+
if (snapshot.outputCursor || args.latestCursor()) {
|
|
41
|
+
// The first canonical commit may happen after Main's unavailable read
|
|
42
|
+
// but before its snapshot/live reply. Re-read once, requiring real proof.
|
|
43
|
+
reply = await args.read({});
|
|
44
|
+
if (!args.canContinue())
|
|
45
|
+
throw new Error("Recovery cancelled");
|
|
46
|
+
if (reply.page.status === "unavailable")
|
|
47
|
+
throw new Error("Required output journal is unavailable");
|
|
48
|
+
}
|
|
49
|
+
else
|
|
50
|
+
throw new MobileAwaitingJournal();
|
|
51
|
+
}
|
|
52
|
+
if (reply.page.status === "unavailable")
|
|
53
|
+
return null;
|
|
54
|
+
if (reply.page.status !== "ok" ||
|
|
55
|
+
!reply.legacyBaseComplete ||
|
|
56
|
+
!reply.legacyBase ||
|
|
57
|
+
!Array.isArray(reply.legacyBase.events) ||
|
|
58
|
+
reply.legacyBase.events[0]?.type !== "session_started" ||
|
|
59
|
+
reply.legacyBase.throughEventId !== reply.page.legacyBaseThroughEventId ||
|
|
60
|
+
new TextEncoder().encode(JSON.stringify(reply.legacyBase)).length > 1024 * 1024 + 4096)
|
|
61
|
+
throw new Error("Recovery base is incomplete");
|
|
62
|
+
let chat = initialChatState();
|
|
63
|
+
for (const event of reply.legacyBase.events)
|
|
64
|
+
chat = reduceStream(chat, event);
|
|
65
|
+
// A legacy transcript establishes display history, never live completion.
|
|
66
|
+
chat = { ...chat, run: chat.run === "error" ? "error" : "idle", liveByAgent: {} };
|
|
67
|
+
let recovery = { incomplete: false };
|
|
68
|
+
let pages = 0;
|
|
69
|
+
// A continuation retains the verified private candidate and the same total
|
|
70
|
+
// page budget. No prefix is published until the eventual frozen join passes.
|
|
71
|
+
const advance = async () => {
|
|
72
|
+
for (let round = 0; round < 8; round++) {
|
|
73
|
+
while (true) {
|
|
74
|
+
if (!args.canContinue() || ++pages > 2048)
|
|
75
|
+
throw new Error("Recovery cancelled or bounded");
|
|
76
|
+
const complete = applyOutputJournalPage(recovery, reply.page, (event) => {
|
|
77
|
+
chat = reduceStream(chat, event);
|
|
78
|
+
});
|
|
79
|
+
if (recovery.incomplete)
|
|
80
|
+
throw new Error("Recovery is incomplete");
|
|
81
|
+
if (complete)
|
|
82
|
+
break;
|
|
83
|
+
reply = await args.read({ after: recovery.cursor, through: recovery.through });
|
|
84
|
+
}
|
|
85
|
+
if (!args.canContinue())
|
|
86
|
+
throw new Error("Recovery cancelled");
|
|
87
|
+
const snapshot = reply.snapshot;
|
|
88
|
+
if (!snapshot ||
|
|
89
|
+
snapshot.unpaired ||
|
|
90
|
+
!snapshot.epoch ||
|
|
91
|
+
!Number.isSafeInteger(snapshot.nextSeq) ||
|
|
92
|
+
snapshot.nextSeq < 1)
|
|
93
|
+
throw new Error("Recovery snapshot is unpaired");
|
|
94
|
+
let target = recovery.cursor;
|
|
95
|
+
for (const value of [snapshot.outputCursor, args.latestCursor()]) {
|
|
96
|
+
if (value === undefined)
|
|
97
|
+
continue;
|
|
98
|
+
const order = compareOutputCursors(value, target);
|
|
99
|
+
if (order === undefined)
|
|
100
|
+
throw new Error("Recovery identity changed");
|
|
101
|
+
if (order > 0)
|
|
102
|
+
target = value;
|
|
103
|
+
}
|
|
104
|
+
if (target === recovery.cursor) {
|
|
105
|
+
const ids = snapshot.inputIds;
|
|
106
|
+
if (ids !== undefined &&
|
|
107
|
+
(!Array.isArray(ids) ||
|
|
108
|
+
ids.length > 128 ||
|
|
109
|
+
new Set(ids).size !== ids.length ||
|
|
110
|
+
ids.some((id) => typeof id !== "string" || !id.trim() || id.length > 512)))
|
|
111
|
+
throw new Error("Recovery input identity is unpaired");
|
|
112
|
+
const missing = ids?.filter((id) => !chat.items.some((item) => item.kind === "user" && item.clientMessageId === id));
|
|
113
|
+
if (missing?.length)
|
|
114
|
+
throw new MobilePendingInput(missing);
|
|
115
|
+
return { chat, outputCursor: recovery.cursor, snapshot };
|
|
116
|
+
}
|
|
117
|
+
recovery = { incomplete: false, cursor: recovery.appliedCursor };
|
|
118
|
+
reply = await args.read({ after: recovery.cursor, through: target });
|
|
119
|
+
}
|
|
120
|
+
throw new MobileOutputCatchupPending(recovery.cursor, advance);
|
|
121
|
+
};
|
|
122
|
+
return advance();
|
|
123
|
+
}
|