@astrosheep/square 0.3.72 → 0.3.74
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/artifact.d.ts +0 -3
- package/dist/artifact.js +5 -59
- package/dist/boundary-presentation.d.ts +8 -1
- package/dist/boundary-presentation.js +8 -1
- package/dist/catch-decisions.d.ts +7 -1
- package/dist/catch-decisions.js +21 -8
- package/dist/claude-delivery.d.ts +1 -1
- package/dist/claude-delivery.js +7 -3
- package/dist/cli/context.d.ts +1 -1
- package/dist/cli/ledger-command.d.ts +12 -0
- package/dist/cli/ledger-command.js +68 -0
- package/dist/cli/registry.js +2 -0
- package/dist/help.js +7 -2
- package/dist/host-ledger-file-adapter.d.ts +4 -10
- package/dist/host-ledger-file-adapter.js +238 -198
- package/dist/host-ledger-migration.d.ts +26 -0
- package/dist/host-ledger-migration.js +227 -0
- package/dist/host-ledger-sqlite.d.ts +62 -0
- package/dist/host-ledger-sqlite.js +341 -0
- package/dist/host-ledger.d.ts +13 -4
- package/dist/inbox.d.ts +2 -0
- package/dist/inbox.js +64 -22
- package/dist/ledger-maintenance.d.ts +30 -0
- package/dist/ledger-maintenance.js +110 -0
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.js +51 -26
- package/dist/model.js +5 -2
- package/dist/open-square.d.ts +12 -2
- package/dist/open-square.js +42 -4
- package/dist/participant-host.js +6 -2
- package/dist/ports.d.ts +17 -1
- package/dist/runtime.d.ts +2 -1
- package/dist/runtime.js +12 -6
- package/dist/sqlite-transaction.d.ts +5 -0
- package/dist/sqlite-transaction.js +72 -0
- package/dist/square-actions.d.ts +3 -1
- package/dist/square-actions.js +26 -13
- package/dist/square-application.d.ts +4 -0
- package/dist/square-application.js +41 -27
- package/dist/square-facade.d.ts +26 -22
- package/dist/square-migration.js +2 -1
- package/dist/square-sqlite.d.ts +2 -2
- package/dist/square-sqlite.js +15 -4
- package/dist/views.d.ts +3 -1
- package/dist/views.js +37 -29
- package/guides/application-demand-optimization.md +50 -0
- package/guides/catch-cold-path-optimization.md +56 -0
- package/guides/catch-contention-performance.md +157 -0
- package/guides/catch-lock-investigation.md +81 -0
- package/guides/change-waits.md +1 -1
- package/guides/host-ledger.md +53 -6
- package/guides/indexed-host-ledger-performance.md +39 -0
- package/guides/mcp-connection-lifetime.md +22 -0
- package/guides/pi-boundary-pending-reuse.md +13 -0
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/paseo/client/square-message.tsx +35 -19
- package/plugins/pi/square-pi.js +4 -8
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Indexed host-ledger measurement
|
|
2
|
+
|
|
3
|
+
The indexed host ledger eliminates whole-file NDJSON reads. This run does not show a consistent reduction in the 300-process catch-phase latency: the smaller-history case was slightly slower and the larger-history case was faster. Artifact contention remains substantial.
|
|
4
|
+
|
|
5
|
+
## Comparable workload
|
|
6
|
+
|
|
7
|
+
Baseline: `895ff50` (NDJSON with origin-separated instrumentation). SQLite plus safe retirement candidate: `60ea8b0`. Both used the same benchmark script, 300 processes, session discovery, active bell, disabled filesystem hints, and histories of 1,800 and 18,000 unrelated activities.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
node --import ./test/sandbox-env.js scripts/benchmark-idle-wait.mjs --workers 300 --mode session --active --no-hints
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The catch phase times opening the square, recognizing the participant, a first catch, a second catch checking duplicate consumption, and closing the square. These numbers are **not single-catch latency**, nor ordinary single-user latency. Discovery is measured separately. These are one completed run per implementation and history size, not repeated-run confidence intervals.
|
|
14
|
+
|
|
15
|
+
| Metric | NDJSON, 1,800 | SQLite, 1,800 | NDJSON, 18,000 | SQLite, 18,000 |
|
|
16
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
17
|
+
| Catch-phase p50 (ms) | 6,134.67 | 6,159.17 | 6,775.98 | 5,532.52 |
|
|
18
|
+
| Catch-phase p95 (ms) | 7,307.72 | 7,481.01 | 8,182.00 | 7,170.30 |
|
|
19
|
+
| Catch-phase aggregate CPU (ms) | 24,171.34 | 24,045.67 | 27,538.51 | 23,574.19 |
|
|
20
|
+
| Artifact busy observations | 46,710 | 52,686 | 56,401 | 46,069 |
|
|
21
|
+
| Catch-phase artifact body rows | 300 | 300 | 300 | 300 |
|
|
22
|
+
| Catch-phase artifact writes | 600 | 600 | 600 | 600 |
|
|
23
|
+
| Discovery p95 (ms) | 1,931 | 1,927 | 1,881 | 1,915 |
|
|
24
|
+
|
|
25
|
+
Every completed scenario discovered all 300 participants, consumed exactly one activity per participant, and consumed none again on the second catch.
|
|
26
|
+
|
|
27
|
+
## Ledger work
|
|
28
|
+
|
|
29
|
+
For either history size, the NDJSON catch phase performed 1,500 file reads totaling 95,520,000 bytes. The SQLite catch phase performed 1,500 data-read executions, returning 1,500 presence rows; all SQL result values together totaled 316,900 bytes (4,500 rows including header results). Host-ledger SQL busy observations were zero.
|
|
30
|
+
|
|
31
|
+
These are different transport measurements: file bytes versus SQL values returned to JavaScript. They demonstrate that whole-ledger materialization is gone; they do **not** measure equivalent physical disk I/O or prove a proportional speedup. SQLite bypasses the JavaScript filesystem hooks, so zero NDJSON operations/bytes is structural, not a standalone performance result.
|
|
32
|
+
|
|
33
|
+
The remaining catch-phase contention is attributed to the square artifact, separately from the host ledger. The result motivates investigating concurrent artifact transactions if reducing this latency is the next objective; it does not establish the benefit of any unimplemented change.
|
|
34
|
+
|
|
35
|
+
## Evidence and limits
|
|
36
|
+
|
|
37
|
+
Local raw reports: `/tmp/square-indexed-baseline-300.json` and `/tmp/square-indexed-final-300.json`. The completed SQLite run exited zero with empty stderr. An earlier invocation was terminated by the tool's 60-second execution limit before producing a report and was excluded; the completed run used the unchanged workload in a background process.
|
|
38
|
+
|
|
39
|
+
This benchmark uses an isolated ledger. It does not migrate the live host ledger, exercise real native transports, or measure GC reclamation. GC correctness is covered by the separate retirement, closure-proof, schema-upgrade, and CLI tests. Retirement leaves reusable SQLite pages and indefinitely retained Claude recognition tokens; neither immediate file shrinkage nor bounded total token growth is promised.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# MCP connection lifetime
|
|
2
|
+
|
|
3
|
+
An MCP server owns one artifact connection for its configured square. Its lifetime ends on close or disconnect; create a new server instance for a new connection. Replacing an artifact file under a running server remains unsupported. Every tool call still creates an application with that request's participant and harness session. Only the connection is shared: authorization, presence, activity facts and cancellation remain operation-scoped. The artifact port already serializes transactions on its connection.
|
|
4
|
+
|
|
5
|
+
`createSquareResourceOwner()` is the explicit lifetime boundary. `scoped()` borrows its connection; ordinary application callers still open and close their own. Cross-square `inbox()` reads remain independent. The connection captures no request abort signal. Explicit server close and transport disconnect share one shutdown path: the SDK cancels requests, handlers finish their cleanup, then the connection closes.
|
|
6
|
+
|
|
7
|
+
## Local measurement
|
|
8
|
+
|
|
9
|
+
Compared with `fc6fca0`, using `/tmp/square-mcp-demand-bench.mjs` and real MCP calls over the SDK's in-memory transport. Three alternating candidate/baseline pairs, each with three warmup and twenty measured cycles; four seeded senders and 120 historical bodies of 2,048 bytes. No tests or builds ran during these pairs. Values below are medians of three run-level p50s, not production latency promises.
|
|
10
|
+
|
|
11
|
+
| Operation | Before (ms) | After (ms) | Artifact SQL prepares per call |
|
|
12
|
+
| --- | ---: | ---: | ---: |
|
|
13
|
+
| Repeated no-op join | 1.000 | 0.630 | 15 → 11 |
|
|
14
|
+
| Express | 3.404 | 3.080 | 61 → 57 |
|
|
15
|
+
| Empty catch, one sender | 1.648 | 1.247 | 40 → 36 |
|
|
16
|
+
| Empty catch, three senders | 1.643 | 1.210 | 42 → 38 |
|
|
17
|
+
| History | 0.805 | 0.432 | 16 → 12 |
|
|
18
|
+
| Status | 2.135 | 1.702 | 94 → 90 |
|
|
19
|
+
|
|
20
|
+
Read transaction counts remain unchanged. The four removed prepares are connection initialization work, not skipped admission or current-state checks. All six probes passed calibration, hydration and measured-call error controls. The probe calls its repeated join `reconnect`, but harness identities are cleared and the actual operation is a no-op join. This workload has no ownership contention or bulk catch payload.
|
|
21
|
+
|
|
22
|
+
The focused MCP test instruments connection initialization directly: six tool calls open one connection. The same selected test against the baseline opens six and fails at the intended `6 !== 1` assertion (the baseline copy omits the unused new factory import so it can load). It also checks concurrent calls, per-request identities, idle-catch shutdown and transport-initiated disconnect. Raw local measurements are `/tmp/square-resource-cache-{before,after}-{1,2,3}.{json,log}`.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Pi pending content reuse
|
|
2
|
+
|
|
3
|
+
Pi observes pending notifications before presenting them. The presentation lookup now calls `SessionPendingObserver.read()` to reuse the observer's content projection when the committed artifact revision and current session binding still match.
|
|
4
|
+
|
|
5
|
+
`wait()` and `read()` have distinct jobs: waiting subscribes to filesystem hints and expiry; reading checks the database's revision directly. Both share membership hydration, lease expiry handling and live presentation-evidence filtering. A delayed filesystem hint can therefore delay a wake, but cannot authorize stale content at the presentation boundary. An unreadable current revision never falls back to cached membership.
|
|
6
|
+
|
|
7
|
+
`read()` does not create or retire watchers. It borrows the existing content cache when present and reads normally for newly discovered memberships. No authorization is reused from the cache; current bindings, live evidence and existing presentation checks still run. Existing presentation claims, pending checks, receipt transactions and cancellation remain in place.
|
|
8
|
+
|
|
9
|
+
## Verification
|
|
10
|
+
|
|
11
|
+
`test/pi-pending-cache.test.js` counts the artifact's recipient-projection queries from Pi session start to its first send. With one pending mention and one bound participant, the unchanged projection is hydrated once; a fresh `sessionInbox` call demonstrates the eliminated second hydration. This is a content-read count, not a promise of lower wall-clock latency: the boundary still pays for an authoritative revision read and live eligibility checks.
|
|
12
|
+
|
|
13
|
+
A second test suppresses filesystem hints and periodic reconciliation, warms the old version, then commits a catch lease. It proves the hint cache is still stale while `read()` observes the lease and yields the notification. Removing the lease restores eligibility without a hint. Existing observer and Pi lifecycle tests cover binding changes, lease expiry, unreadable artifacts, failed delivery and cancellation.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PluginTimelineItemProps } from "@getpaseo/plugin/client";
|
|
2
2
|
import { copyText, Icon, useToast } from "@getpaseo/plugin/client/react-native";
|
|
3
3
|
import { useMemo, useState } from "react";
|
|
4
|
-
import { Platform, Pressable, Text, View } from "react-native";
|
|
4
|
+
import { Platform, Pressable, ScrollView, Text, View } from "react-native";
|
|
5
5
|
import { squareHeadline, type SquareMessageData } from "../shared/message";
|
|
6
6
|
|
|
7
7
|
function withOpacity(color: string, opacity: number): string {
|
|
@@ -28,7 +28,7 @@ export function SquareMessage({ item, theme, layout }: PluginTimelineItemProps<S
|
|
|
28
28
|
bodyPanel: {
|
|
29
29
|
backgroundColor: theme.colors.surface1,
|
|
30
30
|
borderRadius: 12,
|
|
31
|
-
marginTop:
|
|
31
|
+
marginTop: 5,
|
|
32
32
|
marginLeft: 17,
|
|
33
33
|
overflow: "hidden" as const,
|
|
34
34
|
},
|
|
@@ -37,20 +37,14 @@ export function SquareMessage({ item, theme, layout }: PluginTimelineItemProps<S
|
|
|
37
37
|
fontSize: 12,
|
|
38
38
|
lineHeight: 21,
|
|
39
39
|
fontFamily: Platform.OS === "ios" ? "Menlo" : "monospace",
|
|
40
|
-
paddingHorizontal: layout.compact ?
|
|
41
|
-
paddingTop:
|
|
40
|
+
paddingHorizontal: layout.compact ? 10 : 12,
|
|
41
|
+
paddingTop: 8,
|
|
42
42
|
},
|
|
43
|
-
}), [
|
|
43
|
+
}), [foreground, layout.compact, muted, theme.colors.accent, theme.colors.surface1]);
|
|
44
44
|
|
|
45
45
|
return (
|
|
46
46
|
<View>
|
|
47
|
-
<
|
|
48
|
-
accessibilityRole="button"
|
|
49
|
-
accessibilityLabel={latest !== undefined ? squareHeadline(latest) : "Square context"}
|
|
50
|
-
aria-expanded={expanded}
|
|
51
|
-
onPress={() => setExpanded(value => !value)}
|
|
52
|
-
style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })}
|
|
53
|
-
>
|
|
47
|
+
<View>
|
|
54
48
|
<View style={{ flexDirection: "row", alignItems: "flex-start", gap: 7, paddingVertical: 2 }}>
|
|
55
49
|
<Text style={styles.marker}>■</Text>
|
|
56
50
|
<Text numberOfLines={1} style={styles.headline}>
|
|
@@ -58,7 +52,16 @@ export function SquareMessage({ item, theme, layout }: PluginTimelineItemProps<S
|
|
|
58
52
|
</Text>
|
|
59
53
|
{items && items.length > 1 && <Text style={styles.meta}>· {items.length} landed</Text>}
|
|
60
54
|
<View style={{ flex: 1 }} />
|
|
61
|
-
|
|
55
|
+
{!expanded && <Pressable
|
|
56
|
+
accessibilityRole="button"
|
|
57
|
+
accessibilityLabel={`展开 ${latest !== undefined ? squareHeadline(latest) : "Square context"}`}
|
|
58
|
+
aria-expanded={false}
|
|
59
|
+
onPress={() => setExpanded(true)}
|
|
60
|
+
hitSlop={6}
|
|
61
|
+
style={({ pressed }) => ({ padding: 5, opacity: pressed ? 0.6 : 1 })}
|
|
62
|
+
>
|
|
63
|
+
<Icon name="ChevronDown" size={18} color={muted} />
|
|
64
|
+
</Pressable>}
|
|
62
65
|
</View>
|
|
63
66
|
{(latest?.actor !== undefined || preview) && <View style={{ marginLeft: 17, marginTop: 2 }}>
|
|
64
67
|
<Text numberOfLines={1} style={styles.preview}>
|
|
@@ -67,17 +70,20 @@ export function SquareMessage({ item, theme, layout }: PluginTimelineItemProps<S
|
|
|
67
70
|
{preview}
|
|
68
71
|
</Text>
|
|
69
72
|
</View>}
|
|
70
|
-
</
|
|
73
|
+
</View>
|
|
71
74
|
{expanded && <View style={styles.bodyPanel}>
|
|
72
|
-
<
|
|
75
|
+
<ScrollView style={{ maxHeight: layout.compact ? 280 : 400 }} nestedScrollEnabled>
|
|
76
|
+
<Text selectable style={styles.bodyText}>{text}</Text>
|
|
77
|
+
</ScrollView>
|
|
73
78
|
<View style={{
|
|
74
|
-
marginHorizontal: layout.compact ?
|
|
75
|
-
marginTop:
|
|
79
|
+
marginHorizontal: layout.compact ? 10 : 12,
|
|
80
|
+
marginTop: 5,
|
|
76
81
|
borderTopWidth: 1,
|
|
77
82
|
borderTopColor: withOpacity(theme.colors.border, 0.55),
|
|
78
|
-
paddingVertical:
|
|
83
|
+
paddingVertical: 3,
|
|
79
84
|
flexDirection: "row",
|
|
80
|
-
|
|
85
|
+
alignItems: "center",
|
|
86
|
+
justifyContent: "space-between",
|
|
81
87
|
}}>
|
|
82
88
|
<Pressable
|
|
83
89
|
accessibilityRole="button"
|
|
@@ -94,6 +100,16 @@ export function SquareMessage({ item, theme, layout }: PluginTimelineItemProps<S
|
|
|
94
100
|
<Icon name="Copy" size={12} color={muted} />
|
|
95
101
|
<Text style={{ color: muted, fontSize: 11 }}>Copy content</Text>
|
|
96
102
|
</Pressable>
|
|
103
|
+
<Pressable
|
|
104
|
+
accessibilityRole="button"
|
|
105
|
+
accessibilityLabel="收起 Square context"
|
|
106
|
+
aria-expanded={true}
|
|
107
|
+
onPress={() => setExpanded(false)}
|
|
108
|
+
hitSlop={6}
|
|
109
|
+
style={({ pressed }) => ({ paddingHorizontal: 5, paddingVertical: 1, opacity: pressed ? 0.6 : 1 })}
|
|
110
|
+
>
|
|
111
|
+
<Icon name="ChevronUp" size={18} color={muted} />
|
|
112
|
+
</Pressable>
|
|
97
113
|
</View>
|
|
98
114
|
</View>}
|
|
99
115
|
</View>
|
package/plugins/pi/square-pi.js
CHANGED
|
@@ -254,16 +254,12 @@ export default function squarePiExtension(pi) {
|
|
|
254
254
|
}
|
|
255
255
|
return landing.promise;
|
|
256
256
|
},
|
|
257
|
-
async (
|
|
258
|
-
|
|
257
|
+
async (_id, _env, boundarySignal) => {
|
|
258
|
+
// Recheck committed revisions and current bindings before reusing observed content.
|
|
259
|
+
const inbox = await observer.read({ signal: boundarySignal, excludeKeys: handledPending });
|
|
259
260
|
if (signal.aborted || token !== generation) return [];
|
|
260
261
|
for (const key of inboxKeys(inbox)) observedPending.add(key);
|
|
261
|
-
return inbox
|
|
262
|
-
...membership,
|
|
263
|
-
notifications: membership.notifications.filter((note) => !handledPending.has(
|
|
264
|
-
`${membership.squarePath}\u0000${membership.name.toLocaleLowerCase()}\u0000${note.actIndex}`,
|
|
265
|
-
)),
|
|
266
|
-
}));
|
|
262
|
+
return inbox;
|
|
267
263
|
},
|
|
268
264
|
env,
|
|
269
265
|
signal,
|