@cmmd-center/forge 0.9.6 → 0.9.8
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/bin.cjs +82 -5
- package/dist/bin.mjs +82 -5
- package/package.json +1 -1
package/dist/bin.cjs
CHANGED
|
@@ -95950,6 +95950,35 @@ const prepareReplicaSummary = require_Schema$1.fn("prepareReplicaSummary")(funct
|
|
|
95950
95950
|
candidate
|
|
95951
95951
|
});
|
|
95952
95952
|
});
|
|
95953
|
+
/**
|
|
95954
|
+
* Contains a per-thread hydration failure so it cannot brick the runtime.
|
|
95955
|
+
*
|
|
95956
|
+
* Hydration is a recovery convenience: it replays durable history into a fresh
|
|
95957
|
+
* runtime. One thread that fails identity or generation checks used to fail the
|
|
95958
|
+
* WHOLE hydration, which `serverRuntimeStartup` turned into
|
|
95959
|
+
* `commandGate.failCommandReady()` — the HTTP server kept serving 200 and the
|
|
95960
|
+
* runtime still reported `server_ready`, while every turn was refused with
|
|
95961
|
+
* "Could not connect".
|
|
95962
|
+
*
|
|
95963
|
+
* Observed in production 2026-08-31: a thread replayed from a DESTROYED runtime
|
|
95964
|
+
* failed its generation check, and because the poison row lives in CMMD's
|
|
95965
|
+
* durable replica rather than on the guest, destroying and reprovisioning the
|
|
95966
|
+
* sprite did not clear it. One bad row could brick every future runtime for
|
|
95967
|
+
* that user, permanently.
|
|
95968
|
+
*
|
|
95969
|
+
* Structural failures (index runtime identity changing between pages, a
|
|
95970
|
+
* repeated cursor) stay fatal on purpose: those mean the index itself cannot be
|
|
95971
|
+
* trusted, so continuing would hydrate an unknown mixture.
|
|
95972
|
+
*/
|
|
95973
|
+
function skipUnhydratableThread(operation, threadId, state) {
|
|
95974
|
+
return operation.pipe(require_Schema$1.catchTag("RuntimeReplicaHydrationError", (error) => require_Schema$1.gen(function* () {
|
|
95975
|
+
state.unhydratableThreadIds.push(threadId);
|
|
95976
|
+
yield* require_Schema$1.logWarning("skipped a thread that could not be hydrated", {
|
|
95977
|
+
threadId,
|
|
95978
|
+
detail: error.detail
|
|
95979
|
+
});
|
|
95980
|
+
})));
|
|
95981
|
+
}
|
|
95953
95982
|
const applyPreparedReplica = require_Schema$1.fn("applyPreparedReplica")(function* (expectedRuntimeId, prepared, state, dependencies) {
|
|
95954
95983
|
const { summary, candidate } = prepared;
|
|
95955
95984
|
if (!(yield* dependencies.restoreThreadCursor({
|
|
@@ -95975,7 +96004,7 @@ function prepareReplicaPages(expectedRuntimeId, cursor, state, dependencies) {
|
|
|
95975
96004
|
if (index === null) return;
|
|
95976
96005
|
if (state.runtimeIdentity === null) state.runtimeIdentity = index.runtime;
|
|
95977
96006
|
else if (!replicaRuntimeMatches(state.runtimeIdentity, index.runtime)) return yield* replicaHydrationFailure("index runtime identity changed between pages");
|
|
95978
|
-
yield* require_Schema$1.forEach(index.threads, (summary) => prepareReplicaSummary(expectedRuntimeId, index, summary, state, dependencies), {
|
|
96007
|
+
yield* require_Schema$1.forEach(index.threads, (summary) => skipUnhydratableThread(prepareReplicaSummary(expectedRuntimeId, index, summary, state, dependencies), summary.threadId, state), {
|
|
95979
96008
|
concurrency: 1,
|
|
95980
96009
|
discard: true
|
|
95981
96010
|
});
|
|
@@ -95995,16 +96024,19 @@ const hydrateRuntimeReplica = require_Schema$1.fn("hydrateRuntimeReplica")(funct
|
|
|
95995
96024
|
prepared: [],
|
|
95996
96025
|
runtimeIdentity: null,
|
|
95997
96026
|
hydrated: 0,
|
|
95998
|
-
skippedExisting: 0
|
|
96027
|
+
skippedExisting: 0,
|
|
96028
|
+
unhydratableThreadIds: []
|
|
95999
96029
|
};
|
|
96000
96030
|
yield* prepareReplicaPages(input.expectedRuntimeId, void 0, state, dependencies);
|
|
96001
|
-
yield* require_Schema$1.forEach(state.prepared, (prepared) => applyPreparedReplica(input.expectedRuntimeId, prepared, state, dependencies), {
|
|
96031
|
+
yield* require_Schema$1.forEach(state.prepared, (prepared) => skipUnhydratableThread(applyPreparedReplica(input.expectedRuntimeId, prepared, state, dependencies), prepared.summary.threadId, state), {
|
|
96002
96032
|
concurrency: 1,
|
|
96003
96033
|
discard: true
|
|
96004
96034
|
});
|
|
96005
96035
|
return {
|
|
96006
96036
|
hydrated: state.hydrated,
|
|
96007
|
-
skippedExisting: state.skippedExisting
|
|
96037
|
+
skippedExisting: state.skippedExisting,
|
|
96038
|
+
skippedUnhydratable: state.unhydratableThreadIds.length,
|
|
96039
|
+
unhydratableThreadIds: [...state.unhydratableThreadIds]
|
|
96008
96040
|
};
|
|
96009
96041
|
});
|
|
96010
96042
|
//#endregion
|
|
@@ -301957,8 +301989,52 @@ function serializeNativeToolActivity(activity) {
|
|
|
301957
301989
|
}
|
|
301958
301990
|
};
|
|
301959
301991
|
}
|
|
301992
|
+
/** The failure activities `appendProviderFailureActivity` emits, all `provider.*.failed`. */
|
|
301993
|
+
function isProviderFailureActivity(activity) {
|
|
301994
|
+
return activity.kind.startsWith("provider.") && activity.kind.endsWith(".failed");
|
|
301995
|
+
}
|
|
301996
|
+
function failureDetail(activity) {
|
|
301997
|
+
const payload = activity.payload;
|
|
301998
|
+
if (!payload || typeof payload !== "object" || !("detail" in payload)) return null;
|
|
301999
|
+
const detail = payload.detail;
|
|
302000
|
+
return typeof detail === "string" && detail.trim() ? detail.trim() : null;
|
|
302001
|
+
}
|
|
302002
|
+
/**
|
|
302003
|
+
* Replicates why a turn failed.
|
|
302004
|
+
*
|
|
302005
|
+
* Without this a failure never crossed the replication boundary — only `tool.`
|
|
302006
|
+
* activities were serialized — so durable history could not show one and every
|
|
302007
|
+
* failure reached the user as an endless "Working" spinner. Measured in
|
|
302008
|
+
* production on 2026-08-30: zero failure events had EVER been recorded against
|
|
302009
|
+
* 652 tool_call rows, which is how a workspace-path block, a broker-token
|
|
302010
|
+
* refusal and a dispatch into a deleted directory all looked identical.
|
|
302011
|
+
*
|
|
302012
|
+
* `error_summary` is used rather than a new kind because both the Forge
|
|
302013
|
+
* protocol and CMMD's ingest schema already accept it; nothing here needed a
|
|
302014
|
+
* contract change. The detail carries the actionable sentence, so it is joined
|
|
302015
|
+
* onto the summary — the event has no separate detail field.
|
|
302016
|
+
*/
|
|
302017
|
+
function serializeProviderFailureActivity(activity) {
|
|
302018
|
+
if (!isProviderFailureActivity(activity)) return null;
|
|
302019
|
+
const summary = text(activity.summary, 4e3);
|
|
302020
|
+
if (summary === null) return null;
|
|
302021
|
+
const detail = failureDetail(activity);
|
|
302022
|
+
return {
|
|
302023
|
+
sourceSequence: null,
|
|
302024
|
+
event: {
|
|
302025
|
+
eventId: `activity:${activity.id}`,
|
|
302026
|
+
occurredAt: canonicalRuntimeEventTimestamp(activity.createdAt),
|
|
302027
|
+
kind: "error_summary",
|
|
302028
|
+
summary: text(detail === null ? summary : `${summary}: ${detail}`, 4e3) ?? summary
|
|
302029
|
+
}
|
|
302030
|
+
};
|
|
302031
|
+
}
|
|
301960
302032
|
function serializeRuntimeThreadEvents(thread) {
|
|
301961
302033
|
const messages = thread.messages.flatMap(serializeThreadMessage);
|
|
302034
|
+
const failureActivities = thread.activities.flatMap((activity) => {
|
|
302035
|
+
const event = serializeProviderFailureActivity(activity);
|
|
302036
|
+
return event === null ? [] : [event];
|
|
302037
|
+
});
|
|
301962
302038
|
const replicaActivities = thread.activities.flatMap((activity) => {
|
|
301963
302039
|
const event = serializeReplicaActivity(activity);
|
|
301964
302040
|
return event === null ? [] : [event];
|
|
@@ -301970,7 +302046,8 @@ function serializeRuntimeThreadEvents(thread) {
|
|
|
301970
302046
|
return [
|
|
301971
302047
|
...messages,
|
|
301972
302048
|
...replicaActivities,
|
|
301973
|
-
...toolActivities
|
|
302049
|
+
...toolActivities,
|
|
302050
|
+
...failureActivities
|
|
301974
302051
|
].toSorted((left, right) => {
|
|
301975
302052
|
if (left.sourceSequence !== null && right.sourceSequence !== null) return left.sourceSequence - right.sourceSequence;
|
|
301976
302053
|
if (left.sourceSequence !== null) return -1;
|
package/dist/bin.mjs
CHANGED
|
@@ -95649,6 +95649,35 @@ const prepareReplicaSummary = fn$1("prepareReplicaSummary")(function* (expectedR
|
|
|
95649
95649
|
candidate
|
|
95650
95650
|
});
|
|
95651
95651
|
});
|
|
95652
|
+
/**
|
|
95653
|
+
* Contains a per-thread hydration failure so it cannot brick the runtime.
|
|
95654
|
+
*
|
|
95655
|
+
* Hydration is a recovery convenience: it replays durable history into a fresh
|
|
95656
|
+
* runtime. One thread that fails identity or generation checks used to fail the
|
|
95657
|
+
* WHOLE hydration, which `serverRuntimeStartup` turned into
|
|
95658
|
+
* `commandGate.failCommandReady()` — the HTTP server kept serving 200 and the
|
|
95659
|
+
* runtime still reported `server_ready`, while every turn was refused with
|
|
95660
|
+
* "Could not connect".
|
|
95661
|
+
*
|
|
95662
|
+
* Observed in production 2026-08-31: a thread replayed from a DESTROYED runtime
|
|
95663
|
+
* failed its generation check, and because the poison row lives in CMMD's
|
|
95664
|
+
* durable replica rather than on the guest, destroying and reprovisioning the
|
|
95665
|
+
* sprite did not clear it. One bad row could brick every future runtime for
|
|
95666
|
+
* that user, permanently.
|
|
95667
|
+
*
|
|
95668
|
+
* Structural failures (index runtime identity changing between pages, a
|
|
95669
|
+
* repeated cursor) stay fatal on purpose: those mean the index itself cannot be
|
|
95670
|
+
* trusted, so continuing would hydrate an unknown mixture.
|
|
95671
|
+
*/
|
|
95672
|
+
function skipUnhydratableThread(operation, threadId, state) {
|
|
95673
|
+
return operation.pipe(catchTag("RuntimeReplicaHydrationError", (error) => gen(function* () {
|
|
95674
|
+
state.unhydratableThreadIds.push(threadId);
|
|
95675
|
+
yield* logWarning$1("skipped a thread that could not be hydrated", {
|
|
95676
|
+
threadId,
|
|
95677
|
+
detail: error.detail
|
|
95678
|
+
});
|
|
95679
|
+
})));
|
|
95680
|
+
}
|
|
95652
95681
|
const applyPreparedReplica = fn$1("applyPreparedReplica")(function* (expectedRuntimeId, prepared, state, dependencies) {
|
|
95653
95682
|
const { summary, candidate } = prepared;
|
|
95654
95683
|
if (!(yield* dependencies.restoreThreadCursor({
|
|
@@ -95674,7 +95703,7 @@ function prepareReplicaPages(expectedRuntimeId, cursor, state, dependencies) {
|
|
|
95674
95703
|
if (index === null) return;
|
|
95675
95704
|
if (state.runtimeIdentity === null) state.runtimeIdentity = index.runtime;
|
|
95676
95705
|
else if (!replicaRuntimeMatches(state.runtimeIdentity, index.runtime)) return yield* replicaHydrationFailure("index runtime identity changed between pages");
|
|
95677
|
-
yield* forEach(index.threads, (summary) => prepareReplicaSummary(expectedRuntimeId, index, summary, state, dependencies), {
|
|
95706
|
+
yield* forEach(index.threads, (summary) => skipUnhydratableThread(prepareReplicaSummary(expectedRuntimeId, index, summary, state, dependencies), summary.threadId, state), {
|
|
95678
95707
|
concurrency: 1,
|
|
95679
95708
|
discard: true
|
|
95680
95709
|
});
|
|
@@ -95694,16 +95723,19 @@ const hydrateRuntimeReplica = fn$1("hydrateRuntimeReplica")(function* (input, de
|
|
|
95694
95723
|
prepared: [],
|
|
95695
95724
|
runtimeIdentity: null,
|
|
95696
95725
|
hydrated: 0,
|
|
95697
|
-
skippedExisting: 0
|
|
95726
|
+
skippedExisting: 0,
|
|
95727
|
+
unhydratableThreadIds: []
|
|
95698
95728
|
};
|
|
95699
95729
|
yield* prepareReplicaPages(input.expectedRuntimeId, void 0, state, dependencies);
|
|
95700
|
-
yield* forEach(state.prepared, (prepared) => applyPreparedReplica(input.expectedRuntimeId, prepared, state, dependencies), {
|
|
95730
|
+
yield* forEach(state.prepared, (prepared) => skipUnhydratableThread(applyPreparedReplica(input.expectedRuntimeId, prepared, state, dependencies), prepared.summary.threadId, state), {
|
|
95701
95731
|
concurrency: 1,
|
|
95702
95732
|
discard: true
|
|
95703
95733
|
});
|
|
95704
95734
|
return {
|
|
95705
95735
|
hydrated: state.hydrated,
|
|
95706
|
-
skippedExisting: state.skippedExisting
|
|
95736
|
+
skippedExisting: state.skippedExisting,
|
|
95737
|
+
skippedUnhydratable: state.unhydratableThreadIds.length,
|
|
95738
|
+
unhydratableThreadIds: [...state.unhydratableThreadIds]
|
|
95707
95739
|
};
|
|
95708
95740
|
});
|
|
95709
95741
|
//#endregion
|
|
@@ -301412,8 +301444,52 @@ function serializeNativeToolActivity(activity) {
|
|
|
301412
301444
|
}
|
|
301413
301445
|
};
|
|
301414
301446
|
}
|
|
301447
|
+
/** The failure activities `appendProviderFailureActivity` emits, all `provider.*.failed`. */
|
|
301448
|
+
function isProviderFailureActivity(activity) {
|
|
301449
|
+
return activity.kind.startsWith("provider.") && activity.kind.endsWith(".failed");
|
|
301450
|
+
}
|
|
301451
|
+
function failureDetail(activity) {
|
|
301452
|
+
const payload = activity.payload;
|
|
301453
|
+
if (!payload || typeof payload !== "object" || !("detail" in payload)) return null;
|
|
301454
|
+
const detail = payload.detail;
|
|
301455
|
+
return typeof detail === "string" && detail.trim() ? detail.trim() : null;
|
|
301456
|
+
}
|
|
301457
|
+
/**
|
|
301458
|
+
* Replicates why a turn failed.
|
|
301459
|
+
*
|
|
301460
|
+
* Without this a failure never crossed the replication boundary — only `tool.`
|
|
301461
|
+
* activities were serialized — so durable history could not show one and every
|
|
301462
|
+
* failure reached the user as an endless "Working" spinner. Measured in
|
|
301463
|
+
* production on 2026-08-30: zero failure events had EVER been recorded against
|
|
301464
|
+
* 652 tool_call rows, which is how a workspace-path block, a broker-token
|
|
301465
|
+
* refusal and a dispatch into a deleted directory all looked identical.
|
|
301466
|
+
*
|
|
301467
|
+
* `error_summary` is used rather than a new kind because both the Forge
|
|
301468
|
+
* protocol and CMMD's ingest schema already accept it; nothing here needed a
|
|
301469
|
+
* contract change. The detail carries the actionable sentence, so it is joined
|
|
301470
|
+
* onto the summary — the event has no separate detail field.
|
|
301471
|
+
*/
|
|
301472
|
+
function serializeProviderFailureActivity(activity) {
|
|
301473
|
+
if (!isProviderFailureActivity(activity)) return null;
|
|
301474
|
+
const summary = text(activity.summary, 4e3);
|
|
301475
|
+
if (summary === null) return null;
|
|
301476
|
+
const detail = failureDetail(activity);
|
|
301477
|
+
return {
|
|
301478
|
+
sourceSequence: null,
|
|
301479
|
+
event: {
|
|
301480
|
+
eventId: `activity:${activity.id}`,
|
|
301481
|
+
occurredAt: canonicalRuntimeEventTimestamp(activity.createdAt),
|
|
301482
|
+
kind: "error_summary",
|
|
301483
|
+
summary: text(detail === null ? summary : `${summary}: ${detail}`, 4e3) ?? summary
|
|
301484
|
+
}
|
|
301485
|
+
};
|
|
301486
|
+
}
|
|
301415
301487
|
function serializeRuntimeThreadEvents(thread) {
|
|
301416
301488
|
const messages = thread.messages.flatMap(serializeThreadMessage);
|
|
301489
|
+
const failureActivities = thread.activities.flatMap((activity) => {
|
|
301490
|
+
const event = serializeProviderFailureActivity(activity);
|
|
301491
|
+
return event === null ? [] : [event];
|
|
301492
|
+
});
|
|
301417
301493
|
const replicaActivities = thread.activities.flatMap((activity) => {
|
|
301418
301494
|
const event = serializeReplicaActivity(activity);
|
|
301419
301495
|
return event === null ? [] : [event];
|
|
@@ -301425,7 +301501,8 @@ function serializeRuntimeThreadEvents(thread) {
|
|
|
301425
301501
|
return [
|
|
301426
301502
|
...messages,
|
|
301427
301503
|
...replicaActivities,
|
|
301428
|
-
...toolActivities
|
|
301504
|
+
...toolActivities,
|
|
301505
|
+
...failureActivities
|
|
301429
301506
|
].toSorted((left, right) => {
|
|
301430
301507
|
if (left.sourceSequence !== null && right.sourceSequence !== null) return left.sourceSequence - right.sourceSequence;
|
|
301431
301508
|
if (left.sourceSequence !== null) return -1;
|