narraeon 0.6.0 → 0.6.2
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 +11 -0
- package/README.zh-CN.md +8 -0
- package/dist/node/protocol/v1.js +12 -2
- package/dist/node/runtime/FileNativeExclusiveLink.js +39 -0
- package/dist/node/runtime/V1Runtime.js +5 -5
- package/dist/node/runtime/archive/WorldArchives.js +14 -46
- package/dist/node/runtime/artifact/ArtifactStoreFiles.js +330 -0
- package/dist/node/runtime/artifact/FileNativeArtifactStore.js +495 -1535
- package/dist/node/runtime/artifact/LegacyArtifactStoreMigration.js +260 -0
- package/dist/node/runtime/authoring/ModelToolConversation.js +25 -2
- package/dist/node/runtime/config/AppPreferencesStore.js +0 -1
- package/dist/node/runtime/config/ProtagonistTemplateStore.js +0 -1
- package/dist/node/runtime/config/dataRootLock.js +108 -0
- package/dist/node/runtime/content/ContentWorkspace.js +0 -10
- package/dist/node/runtime/content/CurrentTreeContentLibrary.js +7 -44
- package/dist/node/runtime/extension/PackageScriptPermissions.js +0 -8
- package/dist/node/runtime/extension/WorldExtensionControls.js +0 -8
- package/dist/node/runtime/model/ModelConnectionStore.js +0 -1
- package/dist/node/runtime/play/FileNativeContinuityCorrection.js +3 -14
- package/dist/node/runtime/play/FileNativePlayAdvanceStore.js +32 -207
- package/dist/node/runtime/play/FileNativePlayTimelineStore.js +29 -239
- package/dist/node/runtime/play/PlayCallChain.js +338 -366
- package/dist/node/runtime/play/PlayDocumentTools.js +8 -125
- package/dist/node/runtime/play/PlayFollowupRequests.js +2 -2
- package/dist/node/runtime/play/PlayTraceRetention.js +8 -36
- package/dist/node/runtime/prompt/WorldMaintenanceReport.js +39 -7
- package/dist/node/runtime/setting/FileNativeSettingImprovementStore.js +7 -16
- package/dist/node/runtime/setting/SettingAuthoringTransaction.js +14 -6
- package/dist/node/runtime/setting/SettingImprovementSession.js +39 -25
- package/dist/node/runtime/world/FileNativeWorldStorageMigrator.js +213 -1033
- package/dist/node/runtime/world/FileNativeWorldStore.js +230 -781
- package/dist/node/runtime/world/{FileNativeAuthorityV3.js → LegacyAuthorityV3Reader.js} +13 -404
- package/dist/node/runtime/world/WorldActivity.js +14 -22
- package/dist/node/runtime/world/WorldDocumentMaintenance.js +2 -2
- package/dist/node/runtime/world/WorldLedgerAuthority.js +340 -0
- package/dist/node/runtime/world/WorldOperationCoordinator.js +59 -401
- package/dist/node/runtime/world/WorldQueue.js +46 -0
- package/dist/node/runtime/world/WorldTurnLedger.js +353 -0
- package/dist/node/runtime/world-revision/FileNativeWorldRevisionStore.js +1 -13
- package/dist/node/runtime/world-revision/WorldRevisionSession.js +29 -2
- package/dist/node/server/mobile.js +52 -0
- package/dist/node/server/startWebServer.js +15 -8
- package/dist/web/assets/index-CXnFaYK9.js +1323 -0
- package/dist/web/assets/{index-BJ-1Mb0e.css → index-LPMV0pl3.css} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +2 -1
- package/dist/node/runtime/FileNativePhysicalClone.js +0 -86
- package/dist/web/assets/index-BgBL8G-A.js +0 -1323
|
@@ -5,7 +5,6 @@ import { validateMaterialList } from "../prompt/MaterialSelection.js";
|
|
|
5
5
|
import { FileNativeWorldCreationError, } from "../world/FileNativeWorldStore.js";
|
|
6
6
|
import { WorldOperationBusyError, } from "../world/WorldOperationCoordinator.js";
|
|
7
7
|
import { beginWorldStateRevision, fileNativeStateChanges, reviseWorldState, worldDocumentRevisionFailureMessage, worldStateRevisionChanges, } from "../world/WorldStateRevision.js";
|
|
8
|
-
const claimHeartbeatIntervalMilliseconds = 20_000;
|
|
9
8
|
export class FileNativeContinuityCorrection {
|
|
10
9
|
#worlds;
|
|
11
10
|
#compiler;
|
|
@@ -59,16 +58,10 @@ export class FileNativeContinuityCorrection {
|
|
|
59
58
|
}
|
|
60
59
|
if (revision.snapshot.status !== "usable") {
|
|
61
60
|
this.#worlds.releaseControl(input.worldId, input.operationId);
|
|
62
|
-
await this.#worlds.
|
|
61
|
+
await this.#worlds.releaseOperationReservation(input.operationId);
|
|
63
62
|
await this.#worlds.operations.releaseCorrectionWorld(claimed.handle);
|
|
64
63
|
throw new Error("The correction candidate's world-state snapshot is unavailable");
|
|
65
64
|
}
|
|
66
|
-
const claimHeartbeat = setInterval(() => {
|
|
67
|
-
void this.#worlds.operations
|
|
68
|
-
.renewCorrectionWorld(claimed.handle)
|
|
69
|
-
.catch(() => undefined);
|
|
70
|
-
}, claimHeartbeatIntervalMilliseconds);
|
|
71
|
-
claimHeartbeat.unref();
|
|
72
65
|
try {
|
|
73
66
|
const candidate = {
|
|
74
67
|
...revision,
|
|
@@ -87,7 +80,6 @@ export class FileNativeContinuityCorrection {
|
|
|
87
80
|
reads: new Map(),
|
|
88
81
|
previewedVersion: null,
|
|
89
82
|
stateOperationClaim: claimed.handle,
|
|
90
|
-
claimHeartbeat,
|
|
91
83
|
};
|
|
92
84
|
this.#candidates.set(candidate.id, candidate);
|
|
93
85
|
return {
|
|
@@ -97,7 +89,6 @@ export class FileNativeContinuityCorrection {
|
|
|
97
89
|
};
|
|
98
90
|
}
|
|
99
91
|
catch (error) {
|
|
100
|
-
clearInterval(claimHeartbeat);
|
|
101
92
|
this.#worlds.releaseControl(input.worldId, input.operationId);
|
|
102
93
|
await Promise.allSettled([
|
|
103
94
|
this.#worlds.releaseOperationReservation(input.operationId),
|
|
@@ -200,11 +191,10 @@ export class FileNativeContinuityCorrection {
|
|
|
200
191
|
});
|
|
201
192
|
}
|
|
202
193
|
catch (error) {
|
|
203
|
-
await this.#worlds.
|
|
194
|
+
await this.#worlds.releaseOperationReservation(candidate.operationId);
|
|
204
195
|
throw error;
|
|
205
196
|
}
|
|
206
197
|
finally {
|
|
207
|
-
clearInterval(candidate.claimHeartbeat);
|
|
208
198
|
this.#worlds.releaseControl(candidate.worldId, candidate.operationId);
|
|
209
199
|
this.#candidates.delete(candidate.id);
|
|
210
200
|
await this.#worlds.operations
|
|
@@ -214,11 +204,10 @@ export class FileNativeContinuityCorrection {
|
|
|
214
204
|
}
|
|
215
205
|
async cancel(candidateId, expectedVersion) {
|
|
216
206
|
const candidate = this.#atVersion(candidateId, expectedVersion);
|
|
217
|
-
clearInterval(candidate.claimHeartbeat);
|
|
218
207
|
this.#candidates.delete(candidateId);
|
|
219
208
|
this.#worlds.releaseControl(candidate.worldId, candidate.operationId);
|
|
220
209
|
try {
|
|
221
|
-
await this.#worlds.
|
|
210
|
+
await this.#worlds.releaseOperationReservation(candidate.operationId);
|
|
222
211
|
}
|
|
223
212
|
finally {
|
|
224
213
|
await this.#worlds.operations.releaseCorrectionWorld(candidate.stateOperationClaim);
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { isNarrativeOrigin,
|
|
2
|
-
import { createHash,
|
|
3
|
-
import {
|
|
1
|
+
import { isNarrativeOrigin, } from "../history/HistoryBackground.js";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { isDeepStrictEqual } from "node:util";
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* The single in-progress play advance of a call chain, if any.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* It exists only between `begin` and `markSettled`. A marker that survives a
|
|
10
|
+
* restart means the advance was interrupted; the runtime marks the chain
|
|
11
|
+
* interrupted and the player retries. Legacy records from earlier layouts are
|
|
12
|
+
* ignored, so they never block a world from opening.
|
|
12
13
|
*/
|
|
13
14
|
export class FileNativePlayAdvanceStore {
|
|
14
15
|
#worldsRoot;
|
|
@@ -18,121 +19,41 @@ export class FileNativePlayAdvanceStore {
|
|
|
18
19
|
async begin(base) {
|
|
19
20
|
assertBase(base);
|
|
20
21
|
const current = await this.readCurrent(base.worldId, base.chainId);
|
|
21
|
-
if (current !== null
|
|
22
|
-
if (!sameAdvanceBase(current
|
|
22
|
+
if (current !== null) {
|
|
23
|
+
if (!sameAdvanceBase(current, base))
|
|
23
24
|
throw new Error("Another play advance is still awaiting settlement");
|
|
24
25
|
return;
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
-
const existing = await readOptionalJson(basePath);
|
|
28
|
-
if (existing === null)
|
|
29
|
-
await publishImmutableJson(basePath, base);
|
|
30
|
-
else {
|
|
31
|
-
assertBase(existing);
|
|
32
|
-
if (!sameAdvanceBase(existing, base))
|
|
33
|
-
throw new Error("A play advance identity is bound to different data");
|
|
34
|
-
}
|
|
35
|
-
await publishJson(this.#currentPath(base.worldId, base.chainId), {
|
|
36
|
-
schemaVersion: 1,
|
|
37
|
-
worldId: base.worldId,
|
|
38
|
-
chainId: base.chainId,
|
|
39
|
-
advanceId: base.advanceId,
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
async recordProviderCompleted(base, response) {
|
|
43
|
-
const durableBase = await this.#assertCurrent(base);
|
|
44
|
-
await publishImmutableJson(this.#factPath(base, "provider-completed.json"), {
|
|
45
|
-
schemaVersion: 1,
|
|
46
|
-
stage: "provider_completed",
|
|
47
|
-
response: structuredClone(response),
|
|
48
|
-
recordedAt: durableBase.createdAt,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
async recordSettlementPrepared(base, settlement) {
|
|
52
|
-
const durableBase = await this.#assertCurrent(base);
|
|
53
|
-
await publishImmutableJson(this.#factPath(base, "settlement-prepared.json"), {
|
|
54
|
-
schemaVersion: 1,
|
|
55
|
-
stage: "settlement_prepared",
|
|
56
|
-
settlement: structuredClone(settlement),
|
|
57
|
-
recordedAt: durableBase.createdAt,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Return the advance's secret resolution seed, creating it on first need.
|
|
62
|
-
* Recovery reads the same fact, so every tool call in the advance draws from
|
|
63
|
-
* one persisted stream and a crashed batch cannot resample.
|
|
64
|
-
*/
|
|
65
|
-
async resolutionSeed(base) {
|
|
66
|
-
const durableBase = await this.#assertCurrent(base);
|
|
67
|
-
const path = this.#factPath(base, "resolution-seed.json");
|
|
68
|
-
if ((await readOptionalJson(path)) === null) {
|
|
69
|
-
try {
|
|
70
|
-
await publishImmutableJson(path, {
|
|
71
|
-
schemaVersion: 1,
|
|
72
|
-
stage: "resolution_seed",
|
|
73
|
-
seed: randomBytes(32).toString("hex"),
|
|
74
|
-
recordedAt: durableBase.createdAt,
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
catch (error) {
|
|
78
|
-
// A concurrent writer may have published first; the stored fact wins.
|
|
79
|
-
if ((await readOptionalJson(path)) === null)
|
|
80
|
-
throw error;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
const fact = await readJson(path);
|
|
84
|
-
assertResolutionSeedFact(fact);
|
|
85
|
-
return fact.seed;
|
|
86
|
-
}
|
|
87
|
-
async markSettled(base, head) {
|
|
88
|
-
const durableBase = await this.#assertCurrent(base);
|
|
89
|
-
await publishImmutableJson(this.#factPath(base, "settled.json"), {
|
|
90
|
-
schemaVersion: 1,
|
|
91
|
-
stage: "settled",
|
|
92
|
-
head,
|
|
93
|
-
recordedAt: durableBase.createdAt,
|
|
94
|
-
});
|
|
27
|
+
await publishJson(this.#markerPath(base.worldId, base.chainId), base);
|
|
95
28
|
}
|
|
96
29
|
async readCurrent(worldId, chainId) {
|
|
97
30
|
assertIdentity(worldId, "World ID");
|
|
98
31
|
assertIdentity(chainId, "Call-chain ID");
|
|
99
|
-
const
|
|
100
|
-
if (
|
|
32
|
+
const value = await readOptionalJson(this.#markerPath(worldId, chainId));
|
|
33
|
+
if (value === null)
|
|
101
34
|
return null;
|
|
102
|
-
|
|
103
|
-
if (
|
|
104
|
-
throw new Error("Play advance
|
|
105
|
-
|
|
106
|
-
const [base, providerCompleted, settlementPrepared, settled] = await Promise.all([
|
|
107
|
-
readJson(join(recordRoot, "base.json")),
|
|
108
|
-
readOptionalJson(join(recordRoot, "provider-completed.json")),
|
|
109
|
-
readOptionalJson(join(recordRoot, "settlement-prepared.json")),
|
|
110
|
-
readOptionalJson(join(recordRoot, "settled.json")),
|
|
111
|
-
]);
|
|
112
|
-
assertBase(base);
|
|
113
|
-
if (base.worldId !== worldId ||
|
|
114
|
-
base.chainId !== chainId ||
|
|
115
|
-
base.advanceId !== pointer.advanceId)
|
|
116
|
-
throw new Error("Play advance durable identity is inconsistent");
|
|
117
|
-
assertProviderCompleted(providerCompleted);
|
|
118
|
-
assertSettlementPrepared(settlementPrepared);
|
|
119
|
-
assertSettled(settled);
|
|
120
|
-
return { base, providerCompleted, settlementPrepared, settled };
|
|
35
|
+
assertBase(value);
|
|
36
|
+
if (value.worldId !== worldId || value.chainId !== chainId)
|
|
37
|
+
throw new Error("Play advance marker belongs to another context");
|
|
38
|
+
return value;
|
|
121
39
|
}
|
|
122
|
-
async
|
|
40
|
+
async markSettled(base) {
|
|
123
41
|
const current = await this.readCurrent(base.worldId, base.chainId);
|
|
124
|
-
if (current === null
|
|
42
|
+
if (current === null)
|
|
43
|
+
return;
|
|
44
|
+
if (!sameAdvanceBase(current, base))
|
|
125
45
|
throw new Error("Play advance is no longer current");
|
|
126
|
-
|
|
46
|
+
await rm(this.#markerPath(base.worldId, base.chainId), { force: true });
|
|
127
47
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
48
|
+
/** Remove the in-progress marker and all advance records of a discarded chain. */
|
|
49
|
+
async discardChain(worldId, chainId) {
|
|
50
|
+
await rm(dirname(this.#markerPath(worldId, chainId)), {
|
|
51
|
+
recursive: true,
|
|
52
|
+
force: true,
|
|
53
|
+
});
|
|
133
54
|
}
|
|
134
|
-
#
|
|
135
|
-
return join(this.#
|
|
55
|
+
#markerPath(worldId, chainId) {
|
|
56
|
+
return join(this.#worldsRoot, worldId, "runtime", "play-advances", digest(chainId), "in-progress.json");
|
|
136
57
|
}
|
|
137
58
|
}
|
|
138
59
|
function sameAdvanceBase(left, right) {
|
|
@@ -142,15 +63,6 @@ function sameAdvanceBase(left, right) {
|
|
|
142
63
|
void rightCreatedAt;
|
|
143
64
|
return isDeepStrictEqual(leftIdentity, rightIdentity);
|
|
144
65
|
}
|
|
145
|
-
function assertResolutionSeedFact(value) {
|
|
146
|
-
if (!isRecord(value) ||
|
|
147
|
-
value.schemaVersion !== 1 ||
|
|
148
|
-
value.stage !== "resolution_seed" ||
|
|
149
|
-
typeof value.seed !== "string" ||
|
|
150
|
-
!/^[a-f0-9]{64}$/.test(value.seed) ||
|
|
151
|
-
typeof value.recordedAt !== "number")
|
|
152
|
-
throw new Error("Invalid persisted resolution seed");
|
|
153
|
-
}
|
|
154
66
|
function assertBase(value) {
|
|
155
67
|
if (!isRecord(value) ||
|
|
156
68
|
value.schemaVersion !== 1 ||
|
|
@@ -188,40 +100,6 @@ function assertBase(value) {
|
|
|
188
100
|
Number(value.attempt) < 1)
|
|
189
101
|
throw new Error("Response advance has an invalid shape");
|
|
190
102
|
}
|
|
191
|
-
function assertPointer(value) {
|
|
192
|
-
if (!isRecord(value) ||
|
|
193
|
-
value.schemaVersion !== 1 ||
|
|
194
|
-
typeof value.worldId !== "string" ||
|
|
195
|
-
typeof value.chainId !== "string" ||
|
|
196
|
-
typeof value.advanceId !== "string")
|
|
197
|
-
throw new Error("Play advance pointer has an invalid shape");
|
|
198
|
-
}
|
|
199
|
-
function assertProviderCompleted(value) {
|
|
200
|
-
if (value !== null &&
|
|
201
|
-
(value.schemaVersion !== 1 ||
|
|
202
|
-
value.stage !== "provider_completed" ||
|
|
203
|
-
!isRecord(value.response) ||
|
|
204
|
-
typeof value.recordedAt !== "number"))
|
|
205
|
-
throw new Error("Provider-completed play fact has an invalid shape");
|
|
206
|
-
}
|
|
207
|
-
function assertSettlementPrepared(value) {
|
|
208
|
-
if (value !== null &&
|
|
209
|
-
(value.schemaVersion !== 1 ||
|
|
210
|
-
value.stage !== "settlement_prepared" ||
|
|
211
|
-
!isRecord(value.settlement) ||
|
|
212
|
-
(value.settlement.background !== undefined &&
|
|
213
|
-
!isHistoryBackground(value.settlement.background)) ||
|
|
214
|
-
typeof value.recordedAt !== "number"))
|
|
215
|
-
throw new Error("Settlement-prepared play fact has an invalid shape");
|
|
216
|
-
}
|
|
217
|
-
function assertSettled(value) {
|
|
218
|
-
if (value !== null &&
|
|
219
|
-
(value.schemaVersion !== 1 ||
|
|
220
|
-
value.stage !== "settled" ||
|
|
221
|
-
typeof value.head !== "string" ||
|
|
222
|
-
typeof value.recordedAt !== "number"))
|
|
223
|
-
throw new Error("Settled play fact has an invalid shape");
|
|
224
|
-
}
|
|
225
103
|
function digest(value) {
|
|
226
104
|
return createHash("sha256").update(value).digest("hex");
|
|
227
105
|
}
|
|
@@ -232,12 +110,9 @@ function assertIdentity(value, label) {
|
|
|
232
110
|
function isRecord(value) {
|
|
233
111
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
234
112
|
}
|
|
235
|
-
async function readJson(path) {
|
|
236
|
-
return JSON.parse(await readFile(path, "utf8"));
|
|
237
|
-
}
|
|
238
113
|
async function readOptionalJson(path) {
|
|
239
114
|
try {
|
|
240
|
-
return await
|
|
115
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
241
116
|
}
|
|
242
117
|
catch (error) {
|
|
243
118
|
if (isNodeError(error) && error.code === "ENOENT")
|
|
@@ -245,6 +120,7 @@ async function readOptionalJson(path) {
|
|
|
245
120
|
throw error;
|
|
246
121
|
}
|
|
247
122
|
}
|
|
123
|
+
/** Atomic publish: write a temporary file, then rename over the target. */
|
|
248
124
|
async function publishJson(path, value) {
|
|
249
125
|
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
250
126
|
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
@@ -252,58 +128,7 @@ async function publishJson(path, value) {
|
|
|
252
128
|
encoding: "utf8",
|
|
253
129
|
mode: 0o600,
|
|
254
130
|
});
|
|
255
|
-
await syncFile(temporary);
|
|
256
131
|
await rename(temporary, path);
|
|
257
|
-
await syncDirectory(dirname(path));
|
|
258
|
-
}
|
|
259
|
-
async function publishImmutableJson(path, value) {
|
|
260
|
-
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
261
|
-
const existing = await readOptionalJson(path);
|
|
262
|
-
if (existing !== null) {
|
|
263
|
-
if (!isDeepStrictEqual(existing, value))
|
|
264
|
-
throw new Error("Immutable play-advance fact already has different data");
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
268
|
-
await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, {
|
|
269
|
-
encoding: "utf8",
|
|
270
|
-
mode: 0o600,
|
|
271
|
-
});
|
|
272
|
-
await syncFile(temporary);
|
|
273
|
-
try {
|
|
274
|
-
await link(temporary, path);
|
|
275
|
-
}
|
|
276
|
-
catch (error) {
|
|
277
|
-
if (!isNodeError(error) || error.code !== "EEXIST")
|
|
278
|
-
throw error;
|
|
279
|
-
const published = await readJson(path);
|
|
280
|
-
if (!isDeepStrictEqual(published, value))
|
|
281
|
-
throw new Error("Concurrent immutable play-advance fact conflicts", {
|
|
282
|
-
cause: error,
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
finally {
|
|
286
|
-
await rm(temporary, { force: true });
|
|
287
|
-
}
|
|
288
|
-
await syncDirectory(dirname(path));
|
|
289
|
-
}
|
|
290
|
-
async function syncFile(path) {
|
|
291
|
-
const handle = await open(path, "r");
|
|
292
|
-
try {
|
|
293
|
-
await handle.sync();
|
|
294
|
-
}
|
|
295
|
-
finally {
|
|
296
|
-
await handle.close();
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
async function syncDirectory(path) {
|
|
300
|
-
const handle = await open(path, "r");
|
|
301
|
-
try {
|
|
302
|
-
await handle.sync();
|
|
303
|
-
}
|
|
304
|
-
finally {
|
|
305
|
-
await handle.close();
|
|
306
|
-
}
|
|
307
132
|
}
|
|
308
133
|
function isNodeError(error) {
|
|
309
134
|
return error instanceof Error && "code" in error;
|