@astrosheep/square 0.3.62 → 0.3.64
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/claude-plugin/.claude-plugin/plugin.json +3 -2
- package/claude-plugin/hooks/hooks.json +1 -22
- package/claude-plugin/hooks/register.js +119 -0
- package/claude-plugin/types.d.ts +7 -0
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity-feed.d.ts +1 -10
- package/dist/activity-feed.js +1 -27
- package/dist/activity.js +57 -25
- package/dist/artifact.js +12 -24
- package/dist/attention-presentation.js +1 -0
- package/dist/automatic-session.d.ts +3 -1
- package/dist/automatic-session.js +21 -34
- package/dist/boundary-presentation.js +4 -13
- package/dist/canonical-path.d.ts +11 -0
- package/dist/canonical-path.js +47 -0
- package/dist/catch-decisions.js +14 -10
- package/dist/catch-selection.d.ts +6 -0
- package/dist/catch-selection.js +14 -0
- package/dist/claude-delivery.d.ts +11 -0
- package/dist/claude-delivery.js +145 -0
- package/dist/claude-mod.d.ts +8 -0
- package/dist/claude-mod.js +164 -0
- package/dist/cli/context.d.ts +0 -1
- package/dist/cli/context.js +9 -21
- package/dist/cli/harness-command.d.ts +0 -1
- package/dist/cli/harness-command.js +0 -6
- package/dist/cli/observation-commands.d.ts +1 -1
- package/dist/cli/observation-commands.js +31 -58
- package/dist/cli/program.js +20 -18
- package/dist/cli/registry.js +2 -2
- package/dist/cli/square-commands.js +10 -6
- package/dist/codex-hook.js +9 -12
- package/dist/codex-queue.d.ts +2 -2
- package/dist/codex-queue.js +7 -5
- package/dist/decisions.d.ts +2 -6
- package/dist/decisions.js +16 -17
- package/dist/delivery-health.d.ts +1 -2
- package/dist/delivery-health.js +4 -5
- package/dist/delivery-operations.d.ts +2 -5
- package/dist/delivery-operations.js +77 -214
- package/dist/delivery.d.ts +25 -47
- package/dist/delivery.js +14 -46
- package/dist/diagnostic-redaction.js +7 -9
- package/dist/file-changes.d.ts +0 -1
- package/dist/file-changes.js +2 -19
- package/dist/harness-claude.js +8 -6
- package/dist/harness-codex.d.ts +0 -2
- package/dist/harness-codex.js +0 -2
- package/dist/harness-links.js +13 -17
- package/dist/harness.js +3 -7
- package/dist/help.js +4 -3
- package/dist/host-ledger-file-adapter.d.ts +17 -8
- package/dist/host-ledger-file-adapter.js +164 -80
- package/dist/host-ledger.d.ts +55 -50
- package/dist/inbox.d.ts +0 -3
- package/dist/inbox.js +13 -30
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/list.js +1 -4
- package/dist/mcp.js +18 -40
- package/dist/mention-parse.js +3 -9
- package/dist/model.d.ts +11 -2
- package/dist/model.js +1 -1
- package/dist/notifications.d.ts +3 -16
- package/dist/notifications.js +41 -122
- package/dist/opencode-delivery.d.ts +14 -0
- package/dist/opencode-delivery.js +171 -0
- package/dist/opencode.d.ts +39 -18
- package/dist/opencode.js +145 -40
- package/dist/packages/agent-delivery/src/claude-auth.d.ts +13 -0
- package/dist/packages/agent-delivery/src/claude-auth.js +70 -0
- package/dist/packages/agent-delivery/src/claude-native.d.ts +28 -0
- package/dist/packages/agent-delivery/src/claude-native.js +65 -0
- package/dist/packages/agent-delivery/src/claude.d.ts +14 -0
- package/dist/packages/agent-delivery/src/claude.js +66 -0
- package/dist/packages/agent-delivery/src/deadline.d.ts +11 -0
- package/dist/packages/agent-delivery/src/deadline.js +41 -0
- package/dist/packages/agent-delivery/src/errors.d.ts +16 -0
- package/dist/packages/agent-delivery/src/errors.js +41 -0
- package/dist/packages/agent-delivery/src/guards.d.ts +6 -0
- package/dist/packages/agent-delivery/src/guards.js +6 -0
- package/dist/packages/agent-delivery/src/index.d.ts +83 -0
- package/dist/packages/agent-delivery/src/index.js +110 -0
- package/dist/packages/agent-delivery/src/local-endpoint.d.ts +26 -0
- package/dist/packages/agent-delivery/src/local-endpoint.js +114 -0
- package/dist/packages/agent-delivery/src/opencode-native.d.ts +33 -0
- package/dist/packages/agent-delivery/src/opencode-native.js +75 -0
- package/dist/packages/agent-delivery/src/opencode.d.ts +14 -0
- package/dist/packages/agent-delivery/src/opencode.js +127 -0
- package/dist/packages/agent-delivery/src/paseo-native.d.ts +58 -0
- package/dist/packages/agent-delivery/src/paseo-native.js +122 -0
- package/dist/packages/agent-delivery/src/paseo.d.ts +19 -0
- package/dist/packages/agent-delivery/src/paseo.js +106 -0
- package/dist/packages/agent-delivery/src/pi-client.d.ts +10 -0
- package/dist/packages/agent-delivery/src/pi-client.js +106 -0
- package/dist/packages/agent-delivery/src/pi-protocol.d.ts +15 -0
- package/dist/packages/agent-delivery/src/pi-protocol.js +50 -0
- package/dist/packages/agent-delivery/src/pi-send.d.ts +23 -0
- package/dist/packages/agent-delivery/src/pi-send.js +4 -0
- package/dist/packages/agent-delivery/src/pi.d.ts +25 -0
- package/dist/packages/agent-delivery/src/pi.js +209 -0
- package/dist/packages/agent-delivery/src/types.d.ts +120 -0
- package/dist/participant-host.js +12 -13
- package/dist/participant-identity.d.ts +21 -0
- package/dist/participant-identity.js +32 -11
- package/dist/paseo-delivery.d.ts +2 -2
- package/dist/paseo-delivery.js +35 -27
- package/dist/paseo-timeline.js +25 -21
- package/dist/paseo.d.ts +2 -2
- package/dist/paseo.js +1 -2
- package/dist/ports.d.ts +7 -20
- package/dist/presentation-operations.js +16 -4
- package/dist/presentation.d.ts +19 -10
- package/dist/presentation.js +99 -177
- package/dist/registry.d.ts +15 -66
- package/dist/registry.js +45 -123
- package/dist/routes.d.ts +1 -28
- package/dist/routes.js +22 -104
- package/dist/runtime.d.ts +3 -9
- package/dist/runtime.js +14 -67
- package/dist/search.d.ts +0 -2
- package/dist/search.js +3 -10
- package/dist/square-actions.js +48 -72
- package/dist/square-application.js +25 -23
- package/dist/square-core.d.ts +3 -0
- package/dist/square-core.js +22 -5
- package/dist/square-facade.d.ts +2 -0
- package/dist/square-file-adapter.js +4 -7
- package/dist/square-projections.d.ts +40 -45
- package/dist/square-projections.js +43 -73
- package/dist/square-state.js +1 -4
- package/dist/square-storage.d.ts +5 -8
- package/dist/square-storage.js +13 -36
- package/dist/square-wiring.d.ts +0 -5
- package/dist/square-wiring.js +3 -21
- package/dist/stream.js +4 -6
- package/dist/time.js +2 -7
- package/dist/views.d.ts +4 -16
- package/dist/views.js +12 -19
- package/dist/wake-attempts.d.ts +2 -56
- package/dist/wake-attempts.js +15 -110
- package/dist/wake-eligibility.d.ts +43 -0
- package/dist/wake-eligibility.js +106 -0
- package/dist/wake-evidence.d.ts +32 -9
- package/dist/wake-evidence.js +29 -24
- package/dist/wake-sink.d.ts +13 -9
- package/dist/wake-sink.js +35 -54
- package/dist/watch.js +13 -21
- package/extensions/square-pi.js +10 -5
- package/guides/architect.md +77 -77
- package/guides/brainstorm.md +197 -197
- package/guides/claude-native.md +101 -0
- package/guides/host-ledger.md +3 -1
- package/guides/participant.md +14 -7
- package/guides/pi-native-delivery.md +43 -0
- package/package.json +4 -3
- package/dist/claude-hook.d.ts +0 -10
- package/dist/claude-hook.js +0 -60
- package/dist/paseo-connection.d.ts +0 -15
- package/dist/paseo-connection.js +0 -141
- package/dist/presence.d.ts +0 -2
- package/dist/presence.js +0 -24
- package/dist/presented.d.ts +0 -2
- package/dist/presented.js +0 -10
- package/dist/state-cell.d.ts +0 -14
- package/dist/wake-port.d.ts +0 -23
- package/dist/wake-port.js +0 -27
- /package/dist/{state-cell.js → packages/agent-delivery/src/types.js} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
1
|
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
3
|
-
import { nameKey } from './model.js';
|
|
4
2
|
import { deriveDeliveryModel } from './delivery.js';
|
|
5
|
-
import {
|
|
3
|
+
import { projectWakeEvidenceFromState } from './square-projections.js';
|
|
4
|
+
import { wakeIsEligible } from './wake-eligibility.js';
|
|
5
|
+
import { canonicalPath } from './canonical-path.js';
|
|
6
6
|
import { retireWakeRouteFromArtifact } from './routes.js';
|
|
7
7
|
import { redactCurrentDiagnostic } from './diagnostic-redaction.js';
|
|
8
8
|
async function releaseWakeClaim(input, details) {
|
|
@@ -33,80 +33,43 @@ async function attemptWakeWithin(transport, request, timeoutMs, beforeSend) {
|
|
|
33
33
|
clearTimeout(timer);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
export async function observeSquare(input) {
|
|
37
|
-
const snapshot = await input.artifact.read();
|
|
38
|
-
const delivery = deriveDeliveryModel(snapshot.state);
|
|
39
|
-
let rows = [];
|
|
40
|
-
if (input.hostLedger !== undefined) {
|
|
41
|
-
try {
|
|
42
|
-
rows = await input.hostLedger.listPresence({ location: input.location, now: input.now });
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
rows = [];
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
const bindings = rows.map((record) => ({
|
|
49
|
-
location: record.location,
|
|
50
|
-
participant: record.participant,
|
|
51
|
-
sessionId: record.session,
|
|
52
|
-
channel: record.channel,
|
|
53
|
-
...(record.route === undefined ? {} : { route: { location: record.location, participant: record.participant, sessionId: record.session, channel: record.channel, kind: record.route.kind, address: { ...record.route.address }, updatedAt: record.updatedAt ?? 0 } }),
|
|
54
|
-
updatedAt: record.updatedAt ?? 0,
|
|
55
|
-
}));
|
|
56
|
-
return { ...(input.location === undefined ? {} : { location: input.location }), version: snapshot.version, state: snapshot.state, pending: delivery.joinedRecipients().map((recipient) => ({ recipient, notifications: delivery.pendingFor(recipient) })), bindings };
|
|
57
|
-
}
|
|
58
36
|
export async function deliverPending(input) {
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
const liveRoutes = currentSessionBindings(routes.filter((route) => observation.bindings.some((binding) => nameKey(binding.participant) === nameKey(route.participant)
|
|
69
|
-
&& binding.sessionId === route.session
|
|
70
|
-
&& binding.location === route.location)));
|
|
71
|
-
let presentations = [];
|
|
72
|
-
try {
|
|
73
|
-
presentations = await projectPresentationEvidence({ hostLedger: input.hostLedger, location: input.location, now: input.now });
|
|
74
|
-
}
|
|
75
|
-
catch { /* capability is handled by the route-level wake checks */ }
|
|
37
|
+
const location = await canonicalPath(input.location);
|
|
38
|
+
const { state } = await input.artifact.read();
|
|
39
|
+
const delivery = deriveDeliveryModel(state);
|
|
40
|
+
const eligibility = await projectWakeEvidenceFromState({ state, location, hostLedger: input.hostLedger, now: input.now ?? Date.now(), delivery });
|
|
41
|
+
const observeCurrentness = async (request, now) => {
|
|
42
|
+
const { state: currentState } = await input.artifact.read();
|
|
43
|
+
const projection = await projectWakeEvidenceFromState({ state: currentState, location, hostLedger: input.hostLedger, now });
|
|
44
|
+
return { ...projection.currentness({ ...request, location }), observationAvailable: true };
|
|
45
|
+
};
|
|
76
46
|
let attempted = 0;
|
|
77
47
|
let accepted = 0;
|
|
78
48
|
let failed = 0;
|
|
79
49
|
let unknown = 0;
|
|
80
50
|
let notCapable = 0;
|
|
81
|
-
for (const
|
|
82
|
-
for (const notification of
|
|
51
|
+
for (const recipient of delivery.joinedRecipients()) {
|
|
52
|
+
for (const notification of delivery.pendingFor(recipient)) {
|
|
83
53
|
if (input.activity !== undefined) {
|
|
84
54
|
const requested = typeof input.activity === 'number' ? input.activity : parseActivityId(input.activity);
|
|
85
55
|
if (requested === undefined || requested !== notification.item.index)
|
|
86
56
|
continue;
|
|
87
57
|
}
|
|
88
|
-
const
|
|
58
|
+
const evidence = eligibility.evidence(recipient, notification.item.index);
|
|
59
|
+
if (evidence.terminal !== undefined)
|
|
60
|
+
continue;
|
|
61
|
+
const candidates = evidence.attemptableRoutes;
|
|
89
62
|
let acceptedForAttention = false;
|
|
90
63
|
let failedForAttention = false;
|
|
91
64
|
let unknownForAttention = false;
|
|
92
65
|
let notCapableForAttention = false;
|
|
93
|
-
|
|
94
|
-
const prior = await input.hostLedger.listWakeAttempts({ attention: { squarePath: input.location, actIndex: notification.item.index, recipient: membership.recipient }, now: Date.now() });
|
|
95
|
-
acceptedForAttention = prior.some((attempt) => attempt.outcome === 'accepted');
|
|
96
|
-
}
|
|
97
|
-
catch { /* capability is handled by the route-level probe */ }
|
|
98
|
-
if (acceptedForAttention)
|
|
99
|
-
continue;
|
|
100
|
-
if (candidates.length === 0)
|
|
66
|
+
if (candidates.length === 0 && !evidence.presented)
|
|
101
67
|
notCapableForAttention = true;
|
|
102
68
|
for (const route of candidates) {
|
|
103
69
|
const activity = formatActivityId(notification.item.index);
|
|
104
|
-
const
|
|
105
|
-
if (presented.length > 0)
|
|
106
|
-
continue;
|
|
107
|
-
const attention = { squarePath: input.location, actIndex: notification.item.index, recipient: membership.recipient };
|
|
70
|
+
const attention = { squarePath: location, actIndex: notification.item.index, recipient };
|
|
108
71
|
const leaseMs = input.timeoutMs ?? 5000;
|
|
109
|
-
const requestRoute =
|
|
72
|
+
const requestRoute = route;
|
|
110
73
|
if (input.transport.probe !== undefined) {
|
|
111
74
|
try {
|
|
112
75
|
const probe = await input.transport.probe(requestRoute);
|
|
@@ -120,167 +83,101 @@ export async function deliverPending(input) {
|
|
|
120
83
|
continue;
|
|
121
84
|
}
|
|
122
85
|
}
|
|
123
|
-
let
|
|
124
|
-
let lease;
|
|
86
|
+
let claim;
|
|
125
87
|
try {
|
|
126
|
-
|
|
88
|
+
claim = await input.hostLedger.claimWakeAttempt({ attention, session: route.sessionId, routeKind: route.kind, leaseMs });
|
|
127
89
|
}
|
|
128
90
|
catch {
|
|
129
91
|
notCapableForAttention = true;
|
|
130
92
|
continue;
|
|
131
93
|
}
|
|
132
|
-
if (
|
|
133
|
-
let recovered = await input.hostLedger.claimEvidence({ location: input.location, participant: membership.recipient, session: route.session, activity, kind: 'wake', leaseMs, claimToken: lease.lease.leaseId });
|
|
134
|
-
if (recovered.status === 'busy' && recovered.record.claimToken !== undefined) {
|
|
135
|
-
await releaseWakeClaim({ hostLedger: input.hostLedger, location: input.location, participant: membership.recipient, session: route.session, activity, claimToken: recovered.record.claimToken, routeKind: lease.lease.routeKind ?? route.route.kind, attemptN: lease.lease.attemptN }, {
|
|
136
|
-
signature: 'stale_dispatch_claim_recovered',
|
|
137
|
-
message: 'A stale dispatch claim was released so recovery could continue.',
|
|
138
|
-
});
|
|
139
|
-
recovered = await input.hostLedger.claimEvidence({ location: input.location, participant: membership.recipient, session: route.session, activity, kind: 'wake', leaseMs, claimToken: lease.lease.leaseId });
|
|
140
|
-
}
|
|
141
|
-
if (recovered.status === 'acquired') {
|
|
142
|
-
await input.hostLedger.appendEvidence({ location: input.location, participant: membership.recipient, session: route.session, activity, kind: 'wake', outcome: 'unknown', routeKind: lease.lease.routeKind ?? route.route.kind, attemptN: lease.lease.attemptN ?? 1, signature: 'worker_interrupted_during_dispatch', message: 'The notification worker ended after dispatch began; transport acceptance is unknown.', claimToken: recovered.claimToken });
|
|
143
|
-
}
|
|
144
|
-
await input.hostLedger.releaseWakeDispatch({ attention, leaseId: lease.lease.leaseId, session: route.session });
|
|
145
|
-
break;
|
|
146
|
-
}
|
|
147
|
-
if (lease.type === 'busy')
|
|
148
|
-
break;
|
|
149
|
-
if (lease.type !== 'acquired')
|
|
150
|
-
continue;
|
|
151
|
-
try {
|
|
152
|
-
const completed = await input.hostLedger.listWakeAttempts({ attention, now: Date.now() });
|
|
153
|
-
if (completed.some((attempt) => attempt.outcome === 'accepted')) {
|
|
154
|
-
await input.hostLedger.releaseWakeDispatch({ attention, leaseId, session: route.session });
|
|
155
|
-
acceptedForAttention = true;
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
catch { /* continue with the evidence claim */ }
|
|
160
|
-
let attempts;
|
|
161
|
-
try {
|
|
162
|
-
attempts = await input.hostLedger.listWakeAttempts({ attention, now: Date.now() });
|
|
163
|
-
}
|
|
164
|
-
catch {
|
|
94
|
+
if (claim.status === 'degraded') {
|
|
165
95
|
notCapableForAttention = true;
|
|
166
|
-
await input.hostLedger.releaseWakeDispatch({ attention, leaseId, session: route.session }).catch(() => undefined);
|
|
167
96
|
continue;
|
|
168
97
|
}
|
|
169
|
-
|
|
170
|
-
|
|
98
|
+
// Busy and terminal outcomes are attention-wide: no other session or route may dispatch.
|
|
99
|
+
// A terminal attempt was already accepted or may already have been sent, so it reports nothing new.
|
|
100
|
+
if (claim.status === 'busy')
|
|
171
101
|
break;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
const request = { location: input.location, participant:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const dispatching = await input.hostLedger.transitionWakeDispatch({ attention, leaseId, phase: 'dispatching', leaseMs, routeKind: route.route.kind, attemptN, session: route.session });
|
|
186
|
-
if (!dispatching) {
|
|
187
|
-
await releaseWakeClaim({ hostLedger: input.hostLedger, location: input.location, participant: membership.recipient, session: route.session, activity, claimToken, routeKind: route.route.kind, attemptN }, {
|
|
188
|
-
signature: 'dispatch_claim_transition_failed',
|
|
189
|
-
message: 'The wake dispatch claim could not enter the dispatching phase.',
|
|
190
|
-
}).catch(() => undefined);
|
|
191
|
-
await input.hostLedger.releaseWakeDispatch({ attention, leaseId, session: route.session });
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
102
|
+
if (claim.status === 'terminal')
|
|
103
|
+
break;
|
|
104
|
+
const { claimToken, attemptN } = claim;
|
|
105
|
+
const request = { location: input.location, participant: recipient, activity, actor: notification.item.actor, route: requestRoute };
|
|
106
|
+
const claimRelease = { hostLedger: input.hostLedger, location, participant: recipient, session: route.sessionId, activity, claimToken, routeKind: route.kind, attemptN };
|
|
107
|
+
// One teardown path for every exit after claim acquisition; each call site keeps its own error policy.
|
|
108
|
+
const abandon = async (details, options = {}) => {
|
|
109
|
+
const release = releaseWakeClaim(claimRelease, details);
|
|
110
|
+
if (options.bestEffortClaim === true)
|
|
111
|
+
await release.catch(() => undefined);
|
|
112
|
+
else
|
|
113
|
+
await release;
|
|
114
|
+
};
|
|
194
115
|
let current;
|
|
195
116
|
try {
|
|
196
|
-
current = await
|
|
117
|
+
current = await observeCurrentness(request, input.now ?? Date.now());
|
|
197
118
|
}
|
|
198
119
|
catch {
|
|
199
|
-
current
|
|
200
|
-
}
|
|
201
|
-
const stillPending = current.pending.some((entry) => nameKey(entry.recipient) === nameKey(membership.recipient)
|
|
202
|
-
&& entry.notifications.some((entryNotification) => entryNotification.item.index === notification.item.index));
|
|
203
|
-
const stillBound = current.bindings.some((binding) => nameKey(binding.participant) === nameKey(route.participant)
|
|
204
|
-
&& binding.sessionId === route.session
|
|
205
|
-
&& binding.location === route.location);
|
|
206
|
-
const stillPublished = (current.state.routes ?? []).some((published) => published.location === route.location
|
|
207
|
-
&& nameKey(published.participant) === nameKey(route.participant)
|
|
208
|
-
&& published.sessionId === route.session
|
|
209
|
-
&& published.kind === route.route.kind
|
|
210
|
-
&& JSON.stringify(published.address) === JSON.stringify(route.route.address));
|
|
211
|
-
if (!stillPending || !stillBound || !stillPublished) {
|
|
212
|
-
await releaseWakeClaim({ hostLedger: input.hostLedger, location: input.location, participant: membership.recipient, session: route.session, activity, claimToken, routeKind: route.route.kind, attemptN }, {
|
|
213
|
-
signature: 'pre_send_revalidation_failed',
|
|
214
|
-
message: 'Wake was not sent because the activity, session binding, or published route changed before dispatch.',
|
|
215
|
-
diagnostic: { pending: stillPending, bound: stillBound, publishedRoute: stillPublished },
|
|
216
|
-
}).catch(() => undefined);
|
|
217
|
-
await input.hostLedger.releaseWakeDispatch({ attention, leaseId, session: route.session }).catch(() => undefined);
|
|
120
|
+
await abandon({ signature: 'pre_send_revalidation_failed', message: 'Wake was not sent because current Square delivery state could not be verified.', diagnostic: { observationAvailable: false } }, { bestEffortClaim: true }).catch(() => undefined);
|
|
218
121
|
continue;
|
|
219
122
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
await releaseWakeClaim({ hostLedger: input.hostLedger, location: input.location, participant: membership.recipient, session: route.session, activity, claimToken, routeKind: route.route.kind, attemptN }, {
|
|
227
|
-
signature: 'presentation_already_recorded',
|
|
228
|
-
message: 'Wake was suppressed because presentation evidence already exists.',
|
|
229
|
-
diagnostic: { outcomes: latestPresentations.map((row) => row.outcome) },
|
|
230
|
-
}).catch(() => undefined);
|
|
231
|
-
await input.hostLedger.releaseWakeDispatch({ attention, leaseId, session: route.session }).catch(() => undefined);
|
|
123
|
+
if (!current.current) {
|
|
124
|
+
await abandon({
|
|
125
|
+
signature: current.presented ? 'presentation_already_recorded' : 'pre_send_revalidation_failed',
|
|
126
|
+
message: current.presented ? 'Wake was suppressed because presentation evidence already exists.' : 'Wake was not sent because current attention, session binding, or route no longer matches.',
|
|
127
|
+
diagnostic: current,
|
|
128
|
+
}, { bestEffortClaim: true }).catch(() => undefined);
|
|
232
129
|
continue;
|
|
233
130
|
}
|
|
131
|
+
let outcome;
|
|
234
132
|
let suppressedDuringSend = false;
|
|
133
|
+
let finalCurrentness;
|
|
235
134
|
const beforeSend = async () => {
|
|
236
|
-
let finalPresentations = [];
|
|
237
135
|
try {
|
|
238
|
-
|
|
136
|
+
finalCurrentness = await observeCurrentness(request, Date.now());
|
|
137
|
+
suppressedDuringSend = !finalCurrentness.current;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
suppressedDuringSend = true;
|
|
239
141
|
}
|
|
240
|
-
catch { /* capability is handled by the transport path */ }
|
|
241
|
-
suppressedDuringSend = presentationSuppressesWake(finalPresentations);
|
|
242
142
|
return !suppressedDuringSend;
|
|
243
143
|
};
|
|
244
144
|
try {
|
|
245
|
-
outcome = await attemptWakeWithin(input.transport, request, leaseMs, beforeSend);
|
|
145
|
+
outcome = await attemptWakeWithin(input.transport, { ...request, claimToken, attemptN }, leaseMs, beforeSend);
|
|
246
146
|
}
|
|
247
147
|
catch (error) {
|
|
248
148
|
outcome = { outcome: 'unknown', diagnostic: error instanceof Error ? error.message : String(error) };
|
|
249
149
|
}
|
|
250
150
|
if (suppressedDuringSend) {
|
|
251
|
-
await
|
|
252
|
-
signature: 'presentation_recorded_during_dispatch',
|
|
253
|
-
message: 'Wake was cancelled because presentation
|
|
254
|
-
|
|
255
|
-
|
|
151
|
+
await abandon({
|
|
152
|
+
signature: finalCurrentness?.presented === true ? 'presentation_recorded_during_dispatch' : 'pre_send_revalidation_failed',
|
|
153
|
+
message: finalCurrentness === undefined ? 'Wake was not sent because current Square delivery state could not be verified.' : 'Wake was cancelled because current attention, ownership, cancellation, or presentation changed before the final send check.',
|
|
154
|
+
diagnostic: finalCurrentness ?? { observationAvailable: false },
|
|
155
|
+
}, { bestEffortClaim: true }).catch(() => undefined);
|
|
256
156
|
continue;
|
|
257
157
|
}
|
|
258
158
|
attempted += 1;
|
|
259
159
|
if (outcome.outcome === 'not-capable') {
|
|
260
|
-
await
|
|
160
|
+
await abandon({
|
|
261
161
|
signature: 'transport_not_capable',
|
|
262
162
|
message: 'The wake transport reported that it could not deliver this route.',
|
|
263
163
|
diagnostic: outcome.diagnostic,
|
|
264
|
-
}).catch(() => undefined);
|
|
265
|
-
await input.hostLedger.releaseWakeDispatch({ attention, leaseId, session: route.session }).catch(() => undefined);
|
|
164
|
+
}, { bestEffortClaim: true }).catch(() => undefined);
|
|
266
165
|
notCapableForAttention = true;
|
|
267
166
|
continue;
|
|
268
167
|
}
|
|
269
168
|
if (outcome.outcome === 'failed' && outcome.unavailable) {
|
|
270
169
|
if (outcome.routeStale === true)
|
|
271
|
-
await retireWakeRouteFromArtifact(input.artifact, { location: route.location, participant: route.participant, sessionId: route.
|
|
272
|
-
await
|
|
170
|
+
await retireWakeRouteFromArtifact(input.artifact, { location: route.location, participant: route.participant, sessionId: route.sessionId });
|
|
171
|
+
await abandon({
|
|
273
172
|
signature: outcome.signature ?? 'transport_unavailable',
|
|
274
173
|
message: outcome.message ?? 'The wake transport was unavailable.',
|
|
275
174
|
diagnostic: outcome.diagnostic,
|
|
276
175
|
});
|
|
277
|
-
await input.hostLedger.releaseWakeDispatch({ attention, leaseId, session: route.session });
|
|
278
176
|
failedForAttention = true;
|
|
279
177
|
continue;
|
|
280
178
|
}
|
|
281
179
|
const safeOutcome = redactCurrentDiagnostic(outcome);
|
|
282
|
-
await input.hostLedger.appendEvidence({ location
|
|
283
|
-
await input.hostLedger.releaseWakeDispatch({ attention, leaseId, session: route.session });
|
|
180
|
+
await input.hostLedger.appendEvidence({ location, participant: recipient, session: route.sessionId, activity, kind: 'wake', outcome: safeOutcome.outcome, routeKind: route.kind, attemptN, ...(safeOutcome.signature === undefined ? {} : { signature: safeOutcome.signature }), ...(safeOutcome.outcome !== 'accepted' && safeOutcome.message !== undefined ? { message: safeOutcome.message } : {}), ...(safeOutcome.outcome !== 'accepted' && safeOutcome.diagnostic !== undefined ? { diagnostic: safeOutcome.diagnostic } : {}), claimToken });
|
|
284
181
|
if (outcome.outcome === 'accepted') {
|
|
285
182
|
acceptedForAttention = true;
|
|
286
183
|
break;
|
|
@@ -304,55 +201,21 @@ export async function deliverPending(input) {
|
|
|
304
201
|
}
|
|
305
202
|
return { attempted, accepted, failed, unknown, notCapable };
|
|
306
203
|
}
|
|
307
|
-
export function selectPendingWakeActivities(state, routes, attempts, now, graceMs, limit, delivery = deriveDeliveryModel(state), presentations = []) {
|
|
308
|
-
const selected = new Set();
|
|
309
|
-
for (const membership of delivery.joinedRecipients()) {
|
|
310
|
-
for (const notification of delivery.pendingFor(membership)) {
|
|
311
|
-
if (now - notification.item.at <= graceMs)
|
|
312
|
-
continue;
|
|
313
|
-
if (attempts.some((attempt) => attempt.attention.actIndex === notification.item.index && nameKey(attempt.attention.recipient) === nameKey(membership) && attempt.outcome === 'accepted'))
|
|
314
|
-
continue;
|
|
315
|
-
const eligible = routes.some((binding) => {
|
|
316
|
-
if (binding.route === undefined || nameKey(binding.participant) !== nameKey(membership))
|
|
317
|
-
return false;
|
|
318
|
-
const presented = presentations.filter((row) => row.activity === formatActivityId(notification.item.index) && row.participant.toLocaleLowerCase() === membership.toLocaleLowerCase() && row.sessionId === binding.session && presentationSuppressesWake([row]));
|
|
319
|
-
if (presented.length > 0)
|
|
320
|
-
return false;
|
|
321
|
-
const matching = attempts.filter((attempt) => attempt.session === binding.session && nameKey(attempt.attention.recipient) === nameKey(membership) && attempt.attention.actIndex === notification.item.index);
|
|
322
|
-
return isWakeRouteAttemptable({ kind: binding.route.kind, updatedAt: binding.updatedAt ?? 0 }, matching);
|
|
323
|
-
});
|
|
324
|
-
if (eligible)
|
|
325
|
-
selected.add(notification.item.index);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
return [...selected].sort((left, right) => left - right).slice(0, Math.max(0, limit));
|
|
329
|
-
}
|
|
330
204
|
export async function sweepPending(input) {
|
|
331
205
|
const { state } = await input.artifact.read();
|
|
332
206
|
return sweepPendingFromState({ ...input, state });
|
|
333
207
|
}
|
|
334
208
|
export async function sweepPendingFromState(input) {
|
|
335
|
-
const bindings = currentSessionBindings((input.state.routes ?? []).map((route) => ({ location: input.location, participant: route.participant, session: route.sessionId, channel: route.channel, route: { kind: route.kind, address: route.address }, updatedAt: route.updatedAt })));
|
|
336
|
-
let records = [];
|
|
337
|
-
try {
|
|
338
|
-
records = await input.hostLedger.listWakeAttempts({ now: input.now });
|
|
339
|
-
}
|
|
340
|
-
catch {
|
|
341
|
-
records = [];
|
|
342
|
-
}
|
|
343
|
-
let presentations = [];
|
|
344
|
-
try {
|
|
345
|
-
presentations = await projectPresentationEvidence({ hostLedger: input.hostLedger, location: input.location, now: input.now });
|
|
346
|
-
}
|
|
347
|
-
catch {
|
|
348
|
-
presentations = [];
|
|
349
|
-
}
|
|
350
|
-
const attempts = records.flatMap((record) => {
|
|
351
|
-
const index = parseActivityId(record.activity);
|
|
352
|
-
if (index === undefined || record.routeKind === undefined || typeof record.attemptN !== 'number')
|
|
353
|
-
return [];
|
|
354
|
-
return [{ at: record.at ?? input.now, attention: { squarePath: record.location, actIndex: index, recipient: record.participant }, routeKind: record.routeKind, outcome: record.outcome, attemptN: record.attemptN, ...(record.session === undefined ? {} : { session: record.session }) }];
|
|
355
|
-
});
|
|
356
209
|
const delivery = input.deriveDelivery?.(input.state) ?? deriveDeliveryModel(input.state);
|
|
357
|
-
|
|
210
|
+
const eligibility = await projectWakeEvidenceFromState({ ...input, delivery });
|
|
211
|
+
const selected = new Set();
|
|
212
|
+
for (const recipient of delivery.joinedRecipients()) {
|
|
213
|
+
for (const notification of delivery.pendingFor(recipient)) {
|
|
214
|
+
if (input.now - notification.item.at <= input.graceMs)
|
|
215
|
+
continue;
|
|
216
|
+
if (wakeIsEligible(eligibility.evidence(recipient, notification.item.index)))
|
|
217
|
+
selected.add(notification.item.index);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return [...selected].sort((left, right) => left - right).slice(0, Math.max(0, input.limit));
|
|
358
221
|
}
|
package/dist/delivery.d.ts
CHANGED
|
@@ -1,61 +1,44 @@
|
|
|
1
|
-
import { type DirectedNotificationRoute, type SquareState, type StoredAct, type WatchLease, type
|
|
1
|
+
import { type DirectedNotificationRoute, type SquareState, type StoredAct, type WatchLease, type WakeRouteKind } from './model.js';
|
|
2
2
|
import { type Perception } from './square-core.js';
|
|
3
|
+
import type { WakeOutcome } from './ports.js';
|
|
3
4
|
export type { DirectedNotificationRoute } from './model.js';
|
|
4
5
|
export type SayItem = StoredAct & {
|
|
5
6
|
kind: 'say';
|
|
6
7
|
};
|
|
7
|
-
export declare function notificationMessageId(squarePath: string, actIndex: number): string;
|
|
8
8
|
export interface PlannedNotification {
|
|
9
9
|
item: SayItem;
|
|
10
10
|
recipient: string;
|
|
11
11
|
route: DirectedNotificationRoute;
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
outcome: '
|
|
22
|
-
} | {
|
|
23
|
-
outcome: 'unknown';
|
|
24
|
-
signature: string;
|
|
25
|
-
message: string;
|
|
26
|
-
diagnostic?: unknown;
|
|
27
|
-
} | {
|
|
28
|
-
outcome: 'failed';
|
|
29
|
-
signature: string;
|
|
30
|
-
message: string;
|
|
31
|
-
diagnostic?: unknown;
|
|
32
|
-
} | {
|
|
33
|
-
outcome: 'unavailable';
|
|
34
|
-
signature: string;
|
|
35
|
-
message: string;
|
|
36
|
-
diagnostic?: unknown;
|
|
37
|
-
retainRoute?: boolean;
|
|
38
|
-
routeStale?: boolean;
|
|
39
|
-
} | {
|
|
40
|
-
outcome: 'cancelled';
|
|
13
|
+
/**
|
|
14
|
+
* What one adapter reports about a wake it was handed.
|
|
15
|
+
* `gate-rejected` means exactly one thing: the supplied beforeSend gate returned false and nothing was sent.
|
|
16
|
+
* It is not a bin for other not-sent reasons; those stay `failed` with `unavailable: true`.
|
|
17
|
+
*/
|
|
18
|
+
export type WakeAdapterResult = Exclude<WakeOutcome, {
|
|
19
|
+
outcome: 'not-capable';
|
|
20
|
+
}> | {
|
|
21
|
+
outcome: 'gate-rejected';
|
|
41
22
|
};
|
|
23
|
+
/**
|
|
24
|
+
* The coordinates of one wake attempt. An adapter that must hand the receiver a
|
|
25
|
+
* stable dedup key derives it from these, so a retry of the same attempt reuses
|
|
26
|
+
* the same key and a later attempt gets a new one.
|
|
27
|
+
*/
|
|
28
|
+
export interface WakeDispatchContext {
|
|
29
|
+
readonly location: string;
|
|
30
|
+
readonly participant: string;
|
|
31
|
+
readonly activity: string;
|
|
32
|
+
readonly attemptN?: number;
|
|
33
|
+
}
|
|
42
34
|
export interface WakeAdapter {
|
|
43
35
|
readonly kind: WakeRouteKind;
|
|
44
|
-
dispatch(address: Readonly<Record<string, string>>, payload: string, beforeSend: () => Promise<boolean>, timeoutMs?: number): Promise<
|
|
36
|
+
dispatch(address: Readonly<Record<string, string>>, payload: string, beforeSend: () => Promise<boolean>, timeoutMs?: number, context?: WakeDispatchContext): Promise<WakeAdapterResult>;
|
|
45
37
|
}
|
|
46
38
|
export interface RoutedNotification {
|
|
47
39
|
actor: string;
|
|
48
|
-
body: string;
|
|
49
|
-
mentions?: readonly string[];
|
|
50
40
|
route: DirectedNotificationRoute;
|
|
51
|
-
recipient
|
|
52
|
-
}
|
|
53
|
-
export interface CatchFilterShape {
|
|
54
|
-
actor: string;
|
|
55
|
-
body: string;
|
|
56
|
-
mentions?: readonly string[];
|
|
57
|
-
reach?: Reach;
|
|
58
|
-
recipients?: readonly string[];
|
|
41
|
+
recipient: string;
|
|
59
42
|
}
|
|
60
43
|
export interface DeliveryModel {
|
|
61
44
|
plan(item: StoredAct): PlannedNotification[];
|
|
@@ -75,11 +58,6 @@ export declare function isActivitySeen(squareState: SquareState, name: string, a
|
|
|
75
58
|
* All consumers share these targets instead of reinterpreting artifact text or cursor state.
|
|
76
59
|
*/
|
|
77
60
|
export declare function deriveDeliveryModel(squareState: SquareState): DeliveryModel;
|
|
78
|
-
export declare function planActNotifications(squareState: SquareState, item: StoredAct, delivery?: DeliveryModel): PlannedNotification[];
|
|
79
61
|
export declare function perceiveActivity(squareState: SquareState, item: StoredAct, viewer: string, delivery?: DeliveryModel): Perception;
|
|
80
|
-
/**
|
|
81
|
-
export declare function markSeenNotifications(squareState: SquareState, recipient: string, delivered: StoredAct[], at?: number, delivery?: DeliveryModel): boolean;
|
|
82
|
-
/** Canonical say-activity filter shared by catch selection and hook ownership. */
|
|
83
|
-
export declare function matchesCatchFilter(activity: CatchFilterShape, filter: WatchLeaseFilter): boolean;
|
|
84
|
-
/** True only when the live catch's own filters would deliver this notification. */
|
|
62
|
+
/** True only when the live catch's own filters would deliver this unread notification, independent of page size. */
|
|
85
63
|
export declare function leaseOwnsNotification(lease: WatchLease, notification: RoutedNotification): boolean;
|
package/dist/delivery.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { findParticipantName, sameName, } from './model.js';
|
|
2
|
-
import {
|
|
2
|
+
import { activityAuthors, audienceOf, replayLandedAudiences } from './square-core.js';
|
|
3
3
|
import { derivePerceptionProjection } from './perception-projection.js';
|
|
4
|
-
import { matchesMentionTarget
|
|
5
|
-
|
|
6
|
-
return `square:${squarePath}#${formatActivityId(actIndex)}`;
|
|
7
|
-
}
|
|
4
|
+
import { matchesMentionTarget } from './runtime.js';
|
|
5
|
+
import { matchesCatchSelection } from './catch-selection.js';
|
|
8
6
|
export function isActivitySeen(squareState, name, actOrIndex) {
|
|
9
7
|
return deriveDeliveryModel(squareState).isSeen(name, actOrIndex);
|
|
10
8
|
}
|
|
@@ -14,6 +12,7 @@ export function isActivitySeen(squareState, name, actOrIndex) {
|
|
|
14
12
|
*/
|
|
15
13
|
export function deriveDeliveryModel(squareState) {
|
|
16
14
|
const landed = replayLandedAudiences(squareState.acts);
|
|
15
|
+
const authors = activityAuthors(squareState.acts);
|
|
17
16
|
const perception = derivePerceptionProjection(squareState, landed);
|
|
18
17
|
const roster = [...landed.joined];
|
|
19
18
|
const plannedByIndex = new Map();
|
|
@@ -31,10 +30,13 @@ export function deriveDeliveryModel(squareState) {
|
|
|
31
30
|
const sayItem = item;
|
|
32
31
|
const audience = audienceOf(sayItem);
|
|
33
32
|
const recipients = landed.recipientsFor(sayItem);
|
|
33
|
+
const replyAuthor = sayItem.reply === undefined ? undefined : authors.get(sayItem.reply);
|
|
34
34
|
const planned = recipients.map((recipient) => {
|
|
35
35
|
const route = audience.kind === 'bell'
|
|
36
36
|
? 'bell'
|
|
37
|
-
: matchesMentionTarget(sayItem, recipient) ? 'mention'
|
|
37
|
+
: matchesMentionTarget(sayItem, recipient) ? 'mention'
|
|
38
|
+
: replyAuthor !== undefined && sameName(replyAuthor, recipient) ? 'reply'
|
|
39
|
+
: 'attention';
|
|
38
40
|
return { item: sayItem, recipient, route };
|
|
39
41
|
});
|
|
40
42
|
plannedByIndex.set(item.index, planned);
|
|
@@ -75,48 +77,14 @@ export function deriveDeliveryModel(squareState) {
|
|
|
75
77
|
replayedActivityCount: perception.replayedActivityCount,
|
|
76
78
|
};
|
|
77
79
|
}
|
|
78
|
-
export function planActNotifications(squareState, item, delivery = deriveDeliveryModel(squareState)) {
|
|
79
|
-
return delivery.plan(item);
|
|
80
|
-
}
|
|
81
80
|
export function perceiveActivity(squareState, item, viewer, delivery = deriveDeliveryModel(squareState)) {
|
|
82
81
|
return delivery.perceive(item, viewer);
|
|
83
82
|
}
|
|
84
|
-
/**
|
|
85
|
-
export function markSeenNotifications(squareState, recipient, delivered, at = Date.now(), delivery = deriveDeliveryModel(squareState)) {
|
|
86
|
-
const deliveredIndexes = new Set(delivered.map((item) => item.index));
|
|
87
|
-
let changed = false;
|
|
88
|
-
for (const notification of delivery.pendingFor(recipient)) {
|
|
89
|
-
if (!deliveredIndexes.has(notification.item.index))
|
|
90
|
-
continue;
|
|
91
|
-
changed = recordObservation(squareState, notification.recipient, notification.item.index, 'seen', at) || changed;
|
|
92
|
-
}
|
|
93
|
-
return changed;
|
|
94
|
-
}
|
|
95
|
-
/** Canonical say-activity filter shared by catch selection and hook ownership. */
|
|
96
|
-
export function matchesCatchFilter(activity, filter) {
|
|
97
|
-
if (filter.participants !== undefined &&
|
|
98
|
-
!filter.participants.some((participant) => sameName(participant, activity.actor))) {
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
if (audienceOf(activity).kind === 'bell')
|
|
102
|
-
return true;
|
|
103
|
-
if (filter.mention === undefined)
|
|
104
|
-
return true;
|
|
105
|
-
return activity.recipients?.some((recipient) => sameName(recipient, filter.mention)) === true
|
|
106
|
-
|| matchesMentionTarget(activity, filter.mention);
|
|
107
|
-
}
|
|
108
|
-
/** True only when the live catch's own filters would deliver this notification. */
|
|
83
|
+
/** True only when the live catch's own filters would deliver this unread notification, independent of page size. */
|
|
109
84
|
export function leaseOwnsNotification(lease, notification) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return matchesCatchFilter({
|
|
116
|
-
actor: notification.actor,
|
|
117
|
-
body: notification.body,
|
|
118
|
-
...(notification.mentions === undefined ? {} : { mentions: notification.mentions }),
|
|
119
|
-
...(notification.recipient === undefined ? {} : { recipients: [notification.recipient] }),
|
|
120
|
-
...(notification.route === 'bell' ? { reach: 'bell' } : {}),
|
|
121
|
-
}, lease.filter ?? {});
|
|
85
|
+
return matchesCatchSelection(notification.actor, notification.route === 'bell'
|
|
86
|
+
? { kind: 'bell' }
|
|
87
|
+
: { kind: 'mentions', names: notification.route === 'mention' ? [notification.recipient] : [] }, lease.filter ?? {},
|
|
88
|
+
// A reply reaches the recipient as the author of the activity it answers.
|
|
89
|
+
notification.route === 'reply' ? notification.recipient : undefined);
|
|
122
90
|
}
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
const MAX_STRING_LENGTH = 1_000;
|
|
2
2
|
const MAX_COLLECTION_ITEMS = 32;
|
|
3
3
|
const MAX_NESTING = 8;
|
|
4
|
-
function redactText(value, secret) {
|
|
5
|
-
const withoutSecret = secret?.trim() ? value.split(secret.trim()).join('[redacted]') : value;
|
|
6
|
-
const withoutUrlPassword = withoutSecret.replace(/([?&]password=)[^&\s]+/gi, '$1[redacted]');
|
|
7
|
-
return [...withoutUrlPassword].length <= MAX_STRING_LENGTH
|
|
8
|
-
? withoutUrlPassword
|
|
9
|
-
: `${[...withoutUrlPassword].slice(0, MAX_STRING_LENGTH - 1).join('')}…`;
|
|
10
|
-
}
|
|
11
4
|
function redactValue(value, secret, seen, depth) {
|
|
12
|
-
if (typeof value === 'string')
|
|
13
|
-
|
|
5
|
+
if (typeof value === 'string') {
|
|
6
|
+
const withoutSecret = secret?.trim() ? value.split(secret.trim()).join('[redacted]') : value;
|
|
7
|
+
const withoutUrlPassword = withoutSecret.replace(/([?&]password=)[^&\s]+/gi, '$1[redacted]');
|
|
8
|
+
return [...withoutUrlPassword].length <= MAX_STRING_LENGTH
|
|
9
|
+
? withoutUrlPassword
|
|
10
|
+
: `${[...withoutUrlPassword].slice(0, MAX_STRING_LENGTH - 1).join('')}…`;
|
|
11
|
+
}
|
|
14
12
|
if (depth >= MAX_NESTING)
|
|
15
13
|
return '[truncated]';
|
|
16
14
|
if (Array.isArray(value)) {
|
package/dist/file-changes.d.ts
CHANGED