@astrosheep/square 0.3.72 → 0.3.73

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.
Files changed (36) hide show
  1. package/dist/artifact.d.ts +0 -3
  2. package/dist/artifact.js +5 -59
  3. package/dist/catch-decisions.d.ts +7 -1
  4. package/dist/catch-decisions.js +21 -8
  5. package/dist/claude-delivery.d.ts +1 -1
  6. package/dist/claude-delivery.js +7 -3
  7. package/dist/cli/ledger-command.d.ts +12 -0
  8. package/dist/cli/ledger-command.js +68 -0
  9. package/dist/cli/registry.js +2 -0
  10. package/dist/help.js +7 -2
  11. package/dist/host-ledger-file-adapter.d.ts +4 -10
  12. package/dist/host-ledger-file-adapter.js +238 -198
  13. package/dist/host-ledger-migration.d.ts +26 -0
  14. package/dist/host-ledger-migration.js +227 -0
  15. package/dist/host-ledger-sqlite.d.ts +62 -0
  16. package/dist/host-ledger-sqlite.js +341 -0
  17. package/dist/host-ledger.d.ts +13 -4
  18. package/dist/ledger-maintenance.d.ts +30 -0
  19. package/dist/ledger-maintenance.js +110 -0
  20. package/dist/ports.d.ts +7 -1
  21. package/dist/runtime.d.ts +2 -1
  22. package/dist/runtime.js +12 -6
  23. package/dist/sqlite-transaction.d.ts +5 -0
  24. package/dist/sqlite-transaction.js +72 -0
  25. package/dist/square-actions.js +16 -8
  26. package/dist/square-migration.js +2 -1
  27. package/dist/square-sqlite.d.ts +2 -2
  28. package/dist/square-sqlite.js +15 -4
  29. package/guides/catch-contention-performance.md +157 -0
  30. package/guides/change-waits.md +1 -1
  31. package/guides/host-ledger.md +53 -6
  32. package/guides/indexed-host-ledger-performance.md +39 -0
  33. package/package.json +1 -1
  34. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  35. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  36. package/plugins/paseo/client/square-message.tsx +35 -19
@@ -1,11 +1,58 @@
1
- # Host Ledger Configuration
1
+ # Host ledger
2
2
 
3
- Square uses one user-level host ledger for participant bindings, delivery evidence, and wake attempts.
3
+ Square uses one user-level `host-ledger.sqlite` for participant bindings, delivery evidence, and wake attempts. It is separate from every `.square` artifact: the ledger owns host identity, ownership epochs, discovery and delivery evidence; the artifact owns square activity and participant lifecycle.
4
4
 
5
- One wake attempt is one `evidence.ndjsonl` row: it carries the attention-wide exclusion for a recipient and activity, and the session-scoped history for that attempt. A row is `claimed` before any send and `dispatching` from the moment a send may happen; a stale `claimed` row is replaceable, while a stale `dispatching` row becomes `unknown` and forbids redispatch. Accepted and possibly-sent wake attempts stay readable at any age, and are eligible for explicit `gcEvidence` removal once their activity is no longer pending. Older Square versions kept a separate `wake-claims.ndjsonl`; that file is no longer read or written, and is left on disk untouched. Running old and new processes against the same ledger at once is unsupported: restart every host process together when upgrading.
5
+ The default root is `~/.square/host-ledger`. Tests and isolated harnesses may set `SQUARE_HOST_LEDGER_ROOT`; otherwise `SQUARE_REGISTRY` selects its containing directory. An explicit adapter `rootPath` takes precedence. These settings select a single root, not additional ledgers.
6
6
 
7
- The default root is `~/.square/host-ledger`. Tests and isolated harnesses may set `SQUARE_HOST_LEDGER_ROOT`; when it is absent, `SQUARE_REGISTRY` provides the test root. An explicit `rootPath` passed to a ledger adapter takes precedence over both environment defaults.
7
+ ## Storage and delivery
8
8
 
9
- The old `SQUARE_HOST_LEDGER_USER` and `SQUARE_HOST_LEDGER_LOCAL` settings, project-local `.square/host-ledger` fallback, scope arguments, and reconcile-based copying are removed. Existing local ledger files and directories are left untouched but are no longer read. Bindings that existed only in an old local ledger require the participant to rejoin or restart its host session.
9
+ SQLite uses DELETE journaling and FULL synchronous writes. Records retain original order and duplicates, with derived lookup indexes and separate presence/evidence revisions. Normal reads of an empty root create nothing. First mutation initializes storage only when neither legacy NDJSON file exists. Legacy-only storage refuses ordinary operation and requires explicit offline conversion; normal operation never dual-reads or automatically migrates.
10
10
 
11
- The host ledger remains separate from the `.square` artifact: it owns host identity, ownership epochs, discovery, and delivery evidence. The artifact remains authoritative for square activity and participant lifecycle.
11
+ One wake attempt is one evidence record. Acquisition creates `dispatching` directly, before a send may happen. An interrupted dispatch becomes `unknown`, which forbids redispatch; current token-fenced recovery may still accept a late native admission. Accepted and possibly-sent wake records remain readable at any age. Prepared Claude correlations also bypass age visibility and housekeeping across every outcome, including failed and released, until explicitly retired. Ordinary records retain their existing seven-day visibility horizon, with expired ordinary rows reclaimed only during actual mutations. There is no automatic maintenance GC or activity-id-only sweep; durable delivery evidence is retired only by the explicit, artifact-proven `square ledger gc` below.
12
+
13
+ ## Verify and convert offline
14
+
15
+ These commands need no participant or square location and use the resolved ledger root:
16
+
17
+ ```bash
18
+ square ledger verify
19
+ ```
20
+
21
+ Verification is read-only. It checks SQLite identity, version, schema, integrity, record codecs, derived indexes and revisions. With only legacy files, it scans both complete sources and reports accepted, ignored and rejected counts, readiness, and SHA-256 hashes of the original source bytes. Unsafe indexed records make readiness false without hiding the remaining counts or hashes; the CLI reports not ready and exits with status 2. With no files, it reports empty without creating a directory, database or lock. Output never includes record payloads, native credentials or endpoints.
22
+
23
+ To upgrade an NDJSON ledger:
24
+
25
+ 1. Stop **every** Square host using that ledger, including interactive harnesses and background agents. Do not run old and new hosts together.
26
+ 2. Keep a backup of the stopped legacy files.
27
+ 3. Run:
28
+
29
+ ```bash
30
+ square ledger verify
31
+ square ledger migrate
32
+ square ledger verify
33
+ ```
34
+
35
+ 4. Start all hosts with the updated Square version together, using the same root.
36
+
37
+ Migration does not stop or restart hosts. Its locks fence compatible writers but do not prove hosts are stopped. It acquires the ownership claim fence, then `presence.ndjsonl.lock`, then `evidence.ndjsonl.lock`.
38
+
39
+ An older SQLite ledger format is upgraded in place by the same `square ledger migrate` under the same stopped-hosts prerequisite and the same lock order. The upgrade preserves every record, duplicate, original order and both observer revisions. `square ledger verify` stays read-only: it refuses an older or unsupported database with the migrate command and never upgrades silently.
40
+
41
+ The importer preserves all ages, future records, duplicates, original order, native correlation and residual fields. It ignores malformed JSON, wrong versions and records without a numeric timestamp, as the old reader did, and reports those counts. Any valid version-1 record whose indexed fields are unsafe refuses the entire conversion rather than losing evidence. Refusal identifies only source kind, rejected count and first rejected line; no accepted subset is published. Legacy unknown wake sources are normalized once during import.
42
+
43
+ A temporary SQLite database is built and verified under the same root, then published without overwriting an existing destination. Legacy sources remain byte-for-byte untouched. A valid existing SQLite ledger is an idempotent no-op; an older SQLite ledger format is upgraded in place under the fence; invalid or unsupported destinations are refused without repair. There is no force option. On a reported failure, keep hosts stopped, resolve the cause and rerun verification/conversion. An abrupt process termination can leave an unpublished `.host-ledger-migrate-*` directory; after confirming no converter is running, it can be removed without touching the sources or published database.
44
+
45
+ Once SQLite exists, legacy files are not consulted. Keeping them does not provide a rollback path after new SQLite writes: reverting to them would lose ownership and possibly-sent evidence. For a live SQLite ledger, use SQLite backup facilities rather than copying only its main file.
46
+
47
+ ## Reclaim old evidence
48
+
49
+ ```bash
50
+ square ledger gc --dry-run
51
+ square ledger gc
52
+ ```
53
+
54
+ `gc` considers wake and presentation evidence at least seven days old whose attention is permanently closed, proven per row against the square artifact named in the row: the activity historically reached that participant, and either catch recorded an exact receipt or the participant's later rejoin moved past it. Rows still `dispatching`, lifecycle rows, rows with unusable coordinates and rows whose artifact is missing, invalid or unreadable are retained. Eligibility is a proof, not a promise: `retired` counts rows actually removed, and a row whose record changed after the proof, or whose Claude correlation cannot be retired safely, is retained instead. `--dry-run` proves the same set and changes neither the ledger nor any artifact.
55
+
56
+ Reclamation is counted in rows, not bytes. Deleted rows free storage for reuse but do not necessarily shrink the SQLite file immediately; `gc` never runs `VACUUM`. Retiring a prepared Claude correlation payload leaves only a permanent `(harness, session, token)` recognition record, without payload, endpoint, location, participant, epoch or timestamp. These records are absent from ordinary evidence and attempts and confer no admission, presentation or receipt authority. Any retired token vetoes the entire native frame, including a mixed live/retired frame. Total token count remains unbounded. There are no hooks, timers or install steps: maintenance runs only when asked.
57
+
58
+ The obsolete `SQUARE_HOST_LEDGER_USER` and `SQUARE_HOST_LEDGER_LOCAL` settings, project-local fallback and scope-based copying are not used. Older local files and `wake-claims.ndjsonl` remain untouched and are not imported. Bindings that existed only in those abandoned stores require their host to rejoin; do not discard the supported ledger to reset bindings.
@@ -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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/square",
3
- "version": "0.3.72",
3
+ "version": "0.3.73",
4
4
  "description": "A shared public square where agents join, catch activity, express, and step out when done.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.72",
3
+ "version": "0.3.73",
4
4
  "description": "Native Claude Code inbox delivery for Square participants",
5
5
  "types": "./types.d.ts",
6
6
  "author": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.72",
3
+ "version": "0.3.73",
4
4
  "description": "Shared Square activity with reliable participant attention at Codex boundaries.",
5
5
  "author": {
6
6
  "name": "Square"
@@ -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: 7,
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 ? 12 : 14,
41
- paddingTop: 12,
40
+ paddingHorizontal: layout.compact ? 10 : 12,
41
+ paddingTop: 8,
42
42
  },
43
- }), [expanded, foreground, layout.compact, muted, theme.colors.accent, theme.colors.surface1]);
43
+ }), [foreground, layout.compact, muted, theme.colors.accent, theme.colors.surface1]);
44
44
 
45
45
  return (
46
46
  <View>
47
- <Pressable
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
- <View style={{ marginTop: 3 }}><Icon name={expanded ? "ChevronDown" : "ChevronRight"} size={14} color={muted} /></View>
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
- </Pressable>
73
+ </View>
71
74
  {expanded && <View style={styles.bodyPanel}>
72
- <Text selectable style={styles.bodyText}>{text}</Text>
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 ? 12 : 14,
75
- marginTop: 12,
79
+ marginHorizontal: layout.compact ? 10 : 12,
80
+ marginTop: 5,
76
81
  borderTopWidth: 1,
77
82
  borderTopColor: withOpacity(theme.colors.border, 0.55),
78
- paddingVertical: 7,
83
+ paddingVertical: 3,
79
84
  flexDirection: "row",
80
- justifyContent: "flex-end",
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>