@astrosheep/square 0.4.7 → 0.4.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/card-format.d.ts +2 -0
- package/dist/card-format.js +2 -0
- package/dist/claude-mcp-context.d.ts +0 -1
- package/dist/claude-mcp-context.js +0 -10
- package/dist/claude-mod.d.ts +2 -0
- package/dist/claude-mod.js +211 -0
- package/dist/cli/context.d.ts +1 -1
- package/dist/cli/context.js +1 -12
- package/dist/cli/express-presentation.js +6 -33
- package/dist/cli/json.js +1 -1
- package/dist/cli/observation-commands.d.ts +2 -3
- package/dist/cli/observation-commands.js +21 -59
- package/dist/cli/registry.js +3 -5
- package/dist/cli/square-commands.d.ts +1 -2
- package/dist/cli/square-commands.js +7 -17
- package/dist/decisions.js +5 -4
- package/dist/harness-claude.js +3 -3
- package/dist/help.js +6 -8
- package/dist/mcp.js +1 -1
- package/dist/neutral.d.ts +15 -1
- package/dist/notifications.js +1 -1
- package/dist/operation-presentation.d.ts +17 -15
- package/dist/operation-presentation.js +58 -32
- package/dist/packages/agent-delivery/src/index.d.ts +4 -9
- package/dist/packages/agent-delivery/src/index.js +0 -9
- package/dist/packages/agent-delivery/src/local-endpoint.d.ts +2 -12
- package/dist/packages/agent-delivery/src/local-endpoint.js +4 -48
- package/dist/packages/agent-delivery/src/pi-client.js +0 -2
- package/dist/packages/agent-delivery/src/types.d.ts +3 -37
- package/dist/plugins/paseo/shared/card-format.d.ts +6 -0
- package/dist/plugins/paseo/shared/card-format.js +38 -0
- package/dist/plugins/paseo/shared/card-presentation.d.ts +3 -1
- package/dist/plugins/paseo/shared/card-presentation.js +3 -1
- package/dist/presentation.d.ts +2 -20
- package/dist/presentation.js +11 -46
- package/dist/sqlite-square.js +5 -6
- package/dist/square-facade.d.ts +3 -12
- package/dist/square-posting.js +1 -1
- package/dist/square-tools.d.ts +0 -11
- package/dist/square-tools.js +33 -119
- package/dist/square-values.d.ts +1 -1
- package/dist/square-values.js +1 -1
- package/dist/time.d.ts +1 -0
- package/dist/time.js +10 -0
- package/dist/views.d.ts +1 -1
- package/dist/views.js +2 -2
- package/dist/wake-transports.d.ts +1 -1
- package/dist/wake-transports.js +1 -1
- package/guides/catch-cold-path-optimization.md +1 -1
- package/guides/claude-native.md +37 -7
- package/guides/delivery-primitives.md +2 -1
- package/guides/mcp.md +3 -3
- package/guides/pi-native-delivery.md +10 -2
- package/guides/pi-tools.md +1 -3
- package/guides/row-storage-design.md +1 -1
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +3 -2
- package/plugins/claude/README.md +9 -0
- package/plugins/claude/hooks/hooks.json +1 -9
- package/plugins/claude/hooks/register.js +178 -0
- package/plugins/claude/skills/square/SKILL.md +2 -2
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/paseo/OVERVIEW.md +1 -1
- package/plugins/paseo/README.md +20 -23
- package/plugins/paseo/client/markdown.tsx +88 -95
- package/plugins/paseo/client/palette.ts +37 -12
- package/plugins/paseo/client/square-message.tsx +52 -76
- package/plugins/paseo/client/transform.ts +4 -6
- package/plugins/paseo/shared/card-format.ts +37 -0
- package/plugins/paseo/shared/card-presentation.ts +6 -2
- package/plugins/paseo/shared/message.ts +1 -6
- package/plugins/pi/square-pi.js +69 -50
- package/skills/square/SKILL.md +2 -2
- package/dist/claude-boundary-state.d.ts +0 -20
- package/dist/claude-boundary-state.js +0 -84
- package/dist/claude-hook.d.ts +0 -2
- package/dist/claude-hook.js +0 -232
- package/dist/packages/agent-delivery/src/claude-auth.d.ts +0 -13
- package/dist/packages/agent-delivery/src/claude-auth.js +0 -75
- package/dist/packages/agent-delivery/src/claude-native.d.ts +0 -30
- package/dist/packages/agent-delivery/src/claude-native.js +0 -75
- package/dist/packages/agent-delivery/src/claude.d.ts +0 -5
- package/dist/packages/agent-delivery/src/claude.js +0 -66
package/dist/time.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import { SquareError } from './model.js';
|
|
2
|
+
export function parseDurationMs(value, label) {
|
|
3
|
+
const match = value.match(/^([1-9]\d*)(ms|s|m|h)$/);
|
|
4
|
+
if (!match)
|
|
5
|
+
throw new SquareError('invalid_args', `✕ invalid ${label}: expected a positive duration with unit ms, s, m, or h (for example 500ms, 30s, 3m, 1h)`);
|
|
6
|
+
const milliseconds = Number(match[1]) * ({ ms: 1, s: 1000, m: 60000, h: 3600000 }[match[2]]);
|
|
7
|
+
if (!Number.isSafeInteger(milliseconds))
|
|
8
|
+
throw new SquareError('invalid_args', `✕ invalid ${label}: duration is too large`);
|
|
9
|
+
return milliseconds;
|
|
10
|
+
}
|
|
1
11
|
function pad(value, length = 2) {
|
|
2
12
|
return String(value).padStart(length, '0');
|
|
3
13
|
}
|
package/dist/views.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export declare function selectHistory(facts: SquareRead, query: HistoryQuery, li
|
|
|
19
19
|
export declare function publicActivities(activities: readonly ArchiveRecord[]): Activity[];
|
|
20
20
|
export declare function recoveryObservation(facts: SquareRead, name: string, now: number): {
|
|
21
21
|
participant: string;
|
|
22
|
-
activitySummaries: import("./
|
|
22
|
+
activitySummaries: import("./neutral.js").UnreadActivitySummary[];
|
|
23
23
|
standingCount: number;
|
|
24
24
|
ownActivityCount: number;
|
|
25
25
|
hardCap: import("./model.js").HardCap;
|
package/dist/views.js
CHANGED
|
@@ -35,14 +35,14 @@ export function parseRequiredActivityId(id) {
|
|
|
35
35
|
}
|
|
36
36
|
/** History adds archive context and ordering to the shared storage-side query predicates. */
|
|
37
37
|
export function selectHistory(facts, query, limit, kinds, presentation = true) {
|
|
38
|
-
const shared = activityQueryFilter(query, ['observe', 'order', 'mention', 'since', '
|
|
38
|
+
const shared = activityQueryFilter(query, ['observe', 'order', 'mention', 'since', 'context']);
|
|
39
39
|
const { afterIndex } = shared;
|
|
40
40
|
const recipient = query.mention == null ? undefined : resolveKnownName(facts, query.mention);
|
|
41
41
|
if (limit !== null && (!Number.isSafeInteger(limit) || limit < 1))
|
|
42
42
|
throw new SquareError('invalid_args', 'History limit must be a positive integer');
|
|
43
43
|
const filter = {
|
|
44
44
|
...shared, afterAt: query.since,
|
|
45
|
-
...(shared.indexes === undefined ? {} : { indexes: facts.contextIndexes(shared.indexes, query.
|
|
45
|
+
...(shared.indexes === undefined ? {} : { indexes: facts.contextIndexes(shared.indexes, query.context ?? 0, query.context ?? 0) }),
|
|
46
46
|
kinds: query.search === undefined ? kinds : ['say', 'done'],
|
|
47
47
|
};
|
|
48
48
|
let selected, totalMatches, publicBefore = false, publicAfter = false;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { DeliveryTransport } from './delivery-ledger.js';
|
|
2
2
|
export type WakeTransports = readonly DeliveryTransport[];
|
|
3
|
-
/**
|
|
3
|
+
/** Native receivers own their effects; producers never target Claude's private helper. */
|
|
4
4
|
export declare function createDefaultDeliveryTransports(): Promise<WakeTransports>;
|
package/dist/wake-transports.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Native receivers own their effects; producers never target Claude's private helper. */
|
|
2
2
|
export async function createDefaultDeliveryTransports() {
|
|
3
3
|
const [{ HttpWakeTransport }, { PaseoWakeTransport }] = await Promise.all([
|
|
4
4
|
import('./http-delivery.js'),
|
|
@@ -9,7 +9,7 @@ The comparison is against `90752ba`, the current production implementation plus
|
|
|
9
9
|
## Two changes
|
|
10
10
|
|
|
11
11
|
- `model.ts`: try the existing Unicode text-name grammar before the full RGI emoji grammar. It accepts letters, numbers, marks excluding variation selectors, hyphens, underscores, and nonempty slash-separated segments. It is an exact subset of the original grammar; the original full matcher remains the fallback. `NAME_GRAPHEME_SOURCE`, also used by mention scanning, retains its original value.
|
|
12
|
-
- `participant-host.ts`: before discovering installed adapters, use the existing route resolver to check whether any candidate exists. No host ledger or no possible candidate means no route, without importing adapters that cannot be used. Real candidates still pass through the real capability check. These measurements
|
|
12
|
+
- `participant-host.ts`: before discovering installed adapters, use the existing route resolver to check whether any candidate exists. No host ledger or no possible candidate means no route, without importing adapters that cannot be used. Real candidates still pass through the real capability check. These historical measurements do not describe [current Claude mod delivery](claude-native.md); the optimization itself did not change route priority, harness identity, or publication failure isolation.
|
|
13
13
|
|
|
14
14
|
CPU sampling of the baseline pointed to cold name validation inside reader transactions and optional adapter module loading during catch. The changes remove that work rather than prewarm it or move it outside measurement. The sampling and this combined experiment do not establish each change's individual contribution.
|
|
15
15
|
|
package/guides/claude-native.md
CHANGED
|
@@ -1,13 +1,43 @@
|
|
|
1
|
-
# Claude
|
|
1
|
+
# Claude mod delivery
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Square's Claude plugin uses a native JavaScript mod, not command hooks or an async Stop observer. The mod owns native lifecycle and effects. One `square claude-mod` helper per loaded session owns Node/SQLite access and the receiver-owned `claude-mod` ledger route. Producers only post neutral ledger cards; they never contact the helper directly.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Busy context and idle wake
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
During a running turn, the mod calls `$.session.append` with the complete delivery frame, then `$.ui.log` with the readable card. Append adds model context without interrupting the running turn; the separate log makes the notification visible in Claude's native UI. Neither replaces a tool result. A busy notification is never also submitted as a new prompt.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
When the main session is idle, the mod instead calls `$.prompt.submit({ text })`. It keeps native plugin attribution: it does not set `asUser`, impersonate human input, or emit a duplicate UI log. Normal completion returns the receiver to idle so later cards can wake it again. A child receives append only while running, addressed by its native `agentId`; Square never wakes the main session on behalf of an idle child. Child completion detaches its receiver.
|
|
10
10
|
|
|
11
|
-
The
|
|
11
|
+
The current Paseo Claude provider does not expose native `ui_log` events in its conversation UI. Emitting a log is not evidence that Paseo displayed it, and the optional Square Paseo renderer does not fix that provider boundary. This change does not modify Paseo.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## Evidence, cancellation and lifetime
|
|
14
|
+
|
|
15
|
+
Each handoff prepares a unique token and SHA-256 hash of the exact frame. A private loopback RPC rechecks generation and custody immediately before the native effect. An append receipt requires the native returned message to contain exactly that text and a native message UUID. A submit receipt requires the returned submitted text to match exactly; rewritten text records acceptance only. These are native acceptance/presentation facts, not proof of a later provider request, model comprehension or human acknowledgement. UI logs alone never settle a delivery.
|
|
16
|
+
|
|
17
|
+
There is no atomic transaction spanning the ledger and Claude. Once an effect may have happened, a lost or changed result is uncertain and held: no automatic replay, append-to-submit retry, or fallback wake through another host. Definitive refusal creates no presentation receipt. Diagnostics and `square harness doctor delivery` distinguish pending, held and presented evidence.
|
|
18
|
+
|
|
19
|
+
Stop cancels the current pending batch and fences queued native effects. A stopped receiver does not wake again until new native input starts a turn; later input does not replay the cancelled batch. Already appended or admitted native input cannot be recalled. Cancellation is local host suppression, not consumption of Square history.
|
|
20
|
+
|
|
21
|
+
`session.end` retires the captured helper and generation-fenced attachment. `/clear` and `/resume` can end the old session without another `session.start`; the next native prompt lazily starts the helper for the current session ID. Reloading the mod starts a fresh helper generation, which fences older receivers. A hard process kill can prevent graceful detach; a subsequent attachment fences the old generation, but installation alone does not prove that any running host loaded the mod.
|
|
22
|
+
|
|
23
|
+
The helper emits its randomly authenticated loopback address only to the owning mod over its private process stream. That address is not a producer endpoint, shared inbox, or public configuration. No external callback service is required.
|
|
24
|
+
|
|
25
|
+
## MCP call identity
|
|
26
|
+
|
|
27
|
+
The bundled MCP server gets session, cwd and subagent attribution from the mod's native `tool.call` wrapper, matched only to Square MCP tools. Before calling the real tool, it captures the native `tool_use_id`, session and `agentId` in the call-local association keyed by Claude's `claudecode/toolUseId` metadata. A `finally` block removes only that exact association after the tool finishes, including failure. Calls interrupted before cleanup expire after ten minutes.
|
|
28
|
+
|
|
29
|
+
Missing, duplicate, expired or mismatched identity fails closed before the MCP server runs. Models cannot supply identity as tool arguments. Main and child identities stay separate even when their calls overlap; no ordinary non-Square tool launches an identity command.
|
|
30
|
+
|
|
31
|
+
## Tested runtime and verification
|
|
32
|
+
|
|
33
|
+
macOS Claude Code **2.1.319** was tested with the shipped plugin, candidate CLI, isolated HOME/configuration/ledger, and a dummy-key loopback model provider. The real headless runtime proves repeated idle wake, duplicate suppression, busy append reaching the next model request, native UI-log emission, Stop suppression, exact receipts and detach. A real interactive TUI fixture proves main/child MCP attribution, collision denial, tool-context delivery and exact call cleanup. This does not certify older Claude releases, other platforms, or Paseo display.
|
|
34
|
+
|
|
35
|
+
After `npm run build`, run both native fixtures with:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
SQUARE_CLAUDE_MOD_NATIVE_SMOKE=1 \
|
|
39
|
+
SQUARE_CLAUDE_BIN=/path/to/claude \
|
|
40
|
+
node --import ./test/sandbox-env.js test/claude-mod-native.mjs
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Evidence stays in the printed `/tmp/square-claude-…` directories. The headless fixture deliberately interrupts a running tool; Claude may finish with exit 1 and `aborted_tools`, which is checked as the expected interruption rather than treated as an ordinary successful generation. Neither fixture uses external model credentials or changes an installed plugin or running host.
|
|
@@ -5,7 +5,8 @@ package root. The per-user ledger defaults to `~/.square/delivery.v3.sqlite` and
|
|
|
5
5
|
finished text cards, session attachment, routes, attempts and receipts. Producers own content and
|
|
6
6
|
recipient selection; adapters own transport behavior. Routes contain only `{kind, address}`.
|
|
7
7
|
A card may also carry producer-neutral presentation `details`:
|
|
8
|
-
`{kind: 'presentation', origin, author?, ref?,
|
|
8
|
+
`{kind: 'presentation', origin, author?, ref?, at, reach, body}`. `at` is the landing time in epoch
|
|
9
|
+
milliseconds; `reach` is `mention`, `reply`, `attention`, or `bell`. `text` remains the bounded model-facing
|
|
9
10
|
card; `body` holds the full Markdown for display. Receivers validate these details and render them
|
|
10
11
|
without opening producer storage. Cards without valid presentation details remain plain text.
|
|
11
12
|
|
package/guides/mcp.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Square provides a stdio MCP server through `square mcp-server`.
|
|
4
4
|
|
|
5
|
-
Pass `location` explicitly on each single-square tool call. Relative paths use the server's startup working directory. `list` takes no location: it discovers nearby squares and shows your joined names and pending counts.
|
|
5
|
+
Pass `location` explicitly on each single-square tool call. Relative paths use the server's startup working directory. `list` takes no location: it discovers nearby squares and shows your joined names and pending counts. Join with an explicit `as` name. Later calls may omit `as` only when the artifact recognizes exactly one current name held by the call's exact opaque owner; zero or multiple matches refuse and require explicit selection. An explicit name selects a voice but never grants custody. Request metadata supplies the live Codex (`threadId`) or OpenCode (`ai.opencode/sessionID`) coordinate when present, encoded through `harnessOwner`; otherwise the server-configured owner applies. CLI startup resolves `--owner`/`SQUARE_OWNER`, a complete harness coordinate, or the local user owner in that order, refusing incomplete harness identity. Owner is never a model tool argument. Read-only calls do not need an owner.
|
|
6
6
|
|
|
7
|
-
The server exposes `list`, `join`, `express`, `perceive`, `history`, `
|
|
7
|
+
The server exposes `list`, `join`, `express`, `perceive`, `history`, `listen`, `unlisten`, `ignore`, `done`, and `status`. `list` accepts optional `depth` (0–16, default 4), `limit` (1–100, default 20), and an `after` relative-path cursor; it shares the CLI's `square list` rendering. `listen`, `unlisten` and `ignore` take `as`, `location` and `target`; `status` reports who you listen to. `perceive` consumes activities, with an optional `idle` duration such as `30s`. `history` is read-only and accepts stable `act/<index>` cursors, symmetric `context`, literal `search` with optional `regex`, and `full` bodies. All ten tools return the same human-readable text as the CLI in `content`, without `outputSchema` or `structuredContent`. Square failures set `isError: true` and return readable refusal and recovery text in `content`. Clients keep their native MCP result envelope; machine-readable domain results remain available through the SDK.
|
|
8
8
|
|
|
9
|
-
Claude's bundled plugin configures this server automatically.
|
|
9
|
+
Claude's bundled plugin configures this server automatically. Its native mod wraps Square's `tool.call` events to capture live session/cwd/subagent attribution in a call-local association keyed by Claude's `claudecode/toolUseId` metadata, not AI-visible arguments. The wrapper cleans the exact association in `finally`; see [Claude call identity](claude-native.md). Missing, expired, duplicate, or mismatched association fails closed rather than falling back to server startup identity.
|
|
10
10
|
|
|
11
11
|
Tool definitions and argument-to-`Square`/`Participant` calls live in `square-tools.ts`, shared with the [Pi native tools](pi-tools.md). Domain receipts stay internal to these adapters; tools return only their rendered text. All callers use the [same concrete resource API](square-api.md), with no alternate factory or generic outcome envelope. `express.noMention` disables body @name scanning without disabling explicit mentions or reply attention.
|
|
12
12
|
|
|
@@ -7,11 +7,19 @@ presentation receipt and must not be blindly replayed. The boundary's bounded ba
|
|
|
7
7
|
receipts only for presented cards. Square folds a receipt into read state only when its activity card
|
|
8
8
|
contained the full body; shortened cards point to perceive.
|
|
9
9
|
|
|
10
|
-
Display items come directly from validated producer-neutral card details (`origin`, optional `author
|
|
11
|
-
`ref
|
|
10
|
+
Display items come directly from validated producer-neutral card details (`origin`, optional `author`
|
|
11
|
+
and `ref`, landing time `at`, `reach`, and full Markdown `body`). Display never opens Square history. Missing or invalid
|
|
12
12
|
details fall back to card text. Model-facing reminder text is unchanged, and a full display body does
|
|
13
13
|
not make a shortened model-facing card eligible to consume producer unread activity.
|
|
14
14
|
|
|
15
|
+
The human card keeps delivery order, oldest first. Both states share the same Markdown wrapping,
|
|
16
|
+
inline author/reach and right-aligned local time column. Collapsing keeps four non-blank rendered body
|
|
17
|
+
lines and skips blank rows; hidden non-blank lines add an omission mark and an exact `+N lines` footer.
|
|
18
|
+
Expansion preserves the original blank rows, without message separators or a footer. Direct reaches use warning-colored labels and guides.
|
|
19
|
+
The header merges only contiguous activity references within one origin. Time, reach and reference
|
|
20
|
+
formatting is shared with Paseo. Every final line is bounded by the terminal width, including legacy
|
|
21
|
+
Markdown fallback and very narrow terminals.
|
|
22
|
+
|
|
15
23
|
Pi session start and shutdown attach and detach the ledger session, not Square seats. Interactive
|
|
16
24
|
cancellation suppresses outstanding local deliveries without marking activity seen; activity after
|
|
17
25
|
cancellation can notify normally. A send already past its final check cannot be recalled.
|
package/guides/pi-tools.md
CHANGED
|
@@ -17,9 +17,7 @@ text(result); // CLI text: confirmation and bounded unread previews
|
|
|
17
17
|
`location` is required on each single-square call; relative paths use the current call's cwd.
|
|
18
18
|
`square_list` takes no location and discovers nearby squares, showing your joined names and pending
|
|
19
19
|
counts. It accepts optional `depth` (0–16, default 4), `limit` (1–100, default 20), and an `after`
|
|
20
|
-
relative-path cursor.
|
|
21
|
-
and arguments. Join requires
|
|
22
|
-
an explicit `as`. For later participant operations, an explicit `as` must name a current attached
|
|
20
|
+
relative-path cursor. Join requires an explicit `as`. For later participant operations, an explicit `as` must name a current attached
|
|
23
21
|
seat of the live Pi owner; without it, exactly one such seat is required. Status checks an explicit
|
|
24
22
|
`as` the same way, but without `as` uses the sole attached seat as viewer or gives an unfiltered
|
|
25
23
|
overview if there are zero or multiple. The extension encodes the live Pi session with
|
|
@@ -283,7 +283,7 @@ CREATE TABLE routes (
|
|
|
283
283
|
position INTEGER PRIMARY KEY CHECK (position >= 0),
|
|
284
284
|
participant_key_json TEXT COLLATE BINARY NOT NULL,
|
|
285
285
|
session_json TEXT COLLATE BINARY NOT NULL,
|
|
286
|
-
kind TEXT NOT NULL CHECK (kind IN ('opencode-server','codex-queue','
|
|
286
|
+
kind TEXT NOT NULL CHECK (kind IN ('opencode-server','codex-queue','paseo')),
|
|
287
287
|
updated_at REAL NOT NULL,
|
|
288
288
|
payload_json TEXT NOT NULL CHECK (json_valid(payload_json))
|
|
289
289
|
);
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "square",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.8",
|
|
4
|
+
"description": "Claude Code mod delivery for Square participants",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"author": {
|
|
6
7
|
"name": "Square"
|
|
7
8
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Square for Claude Code
|
|
2
|
+
|
|
3
|
+
This plugin adds Square's MCP tools, participation skill and native JavaScript mod. Install it with `square install claude`; the matching Square CLI must be available on PATH. macOS Claude Code 2.1.319 is tested.
|
|
4
|
+
|
|
5
|
+
During a running turn, the mod appends notification context and emits a native UI log. An idle main session receives a plugin-attributed prompt instead. Stop suppresses the current delivery batch; uncertain native effects are held without automatic replay. Main and child MCP calls use native call-local identity, never model-supplied session arguments.
|
|
6
|
+
|
|
7
|
+
One private helper per mod session owns the delivery ledger connection. No producer callback service is needed. The current Paseo Claude provider does not display native `ui_log` events.
|
|
8
|
+
|
|
9
|
+
See [Claude delivery](../../guides/claude-native.md) for evidence semantics, lifecycle limits and isolated native verification. License: MIT.
|
|
@@ -1,11 +1,3 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"SessionStart": [{ "hooks": [{ "type": "command", "command": "square claude-hook", "timeout": 5 }] }],
|
|
4
|
-
"SessionEnd": [{ "hooks": [{ "type": "command", "command": "square claude-hook", "timeout": 3 }] }],
|
|
5
|
-
"UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "square claude-hook", "timeout": 5 }] }],
|
|
6
|
-
"PreToolUse": [{ "matcher": "^mcp__plugin_square_square__.*", "hooks": [{ "type": "command", "command": "square claude-hook", "timeout": 5 }] }],
|
|
7
|
-
"PostToolUse": [{ "hooks": [{ "type": "command", "command": "square claude-hook", "timeout": 5 }] }],
|
|
8
|
-
"PostToolUseFailure": [{ "matcher": "^mcp__plugin_square_square__.*", "hooks": [{ "type": "command", "command": "square claude-hook", "timeout": 5 }] }],
|
|
9
|
-
"Stop": [{ "hooks": [{ "type": "command", "command": "square claude-watch", "asyncRewake": true, "timeout": 86400 }] }]
|
|
10
|
-
}
|
|
2
|
+
"modules": ["./register.js"]
|
|
11
3
|
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// The mod owns native lifecycle and effects. Its one helper owns Node/SQLite.
|
|
2
|
+
let runtime;
|
|
3
|
+
let starting;
|
|
4
|
+
|
|
5
|
+
async function rpc($, current, input) {
|
|
6
|
+
const response = await $.http.fetch(current.url, {
|
|
7
|
+
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(input),
|
|
8
|
+
});
|
|
9
|
+
if (!response.ok) throw new Error('Square Claude helper request failed');
|
|
10
|
+
return JSON.parse(response.text);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function closeRuntime($, current) {
|
|
14
|
+
if (!current || current.closed) return;
|
|
15
|
+
current.closed = true;
|
|
16
|
+
current.epoch++;
|
|
17
|
+
try { await rpc($, current, { op: 'close' }); } catch { /* The helper may already have exited. */ }
|
|
18
|
+
await current.stream.return().catch(() => undefined);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function startRuntime($, sessionId) {
|
|
22
|
+
const stream = $.process.spawn({ argv: ['square', 'claude-mod'], input: JSON.stringify({ sessionId }) });
|
|
23
|
+
const current = { sessionId, stream, url: '', buffer: '', closed: false, epoch: 0, epochs: new Map(), modes: new Map([['', 'idle']]) };
|
|
24
|
+
try {
|
|
25
|
+
while (!current.url) {
|
|
26
|
+
const chunk = await stream.next();
|
|
27
|
+
if (chunk.done) throw new Error('Square Claude helper ended before startup');
|
|
28
|
+
if (chunk.value.stream !== 'stdout') { $.ui.log(chunk.value.text, { to: 'debug' }); continue; }
|
|
29
|
+
current.buffer += chunk.value.text;
|
|
30
|
+
const end = current.buffer.indexOf('\n');
|
|
31
|
+
if (end < 0) continue;
|
|
32
|
+
const ready = JSON.parse(current.buffer.slice(0, end));
|
|
33
|
+
current.buffer = current.buffer.slice(end + 1);
|
|
34
|
+
if (ready.type !== 'ready' || !/^http:\/\/127\.0\.0\.1:\d+\/[a-f0-9]{64}$/.test(ready.url)) throw new Error('Invalid Square Claude helper address');
|
|
35
|
+
current.url = ready.url;
|
|
36
|
+
}
|
|
37
|
+
// A session.start/tool hook must never hold the turn while waiting on submit.
|
|
38
|
+
$.clock.after(0, async () => readDeliveries($, current));
|
|
39
|
+
return current;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
await stream.return().catch(() => undefined);
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function ensureRuntime($) {
|
|
47
|
+
const sessionId = await $.session.id();
|
|
48
|
+
if (runtime && !runtime.closed && runtime.sessionId === sessionId) return runtime;
|
|
49
|
+
if (starting) { await starting; return ensureRuntime($); }
|
|
50
|
+
starting = (async () => {
|
|
51
|
+
await closeRuntime($, runtime);
|
|
52
|
+
const next = await startRuntime($, sessionId);
|
|
53
|
+
runtime = next;
|
|
54
|
+
return next;
|
|
55
|
+
})();
|
|
56
|
+
try { return await starting; } finally { starting = undefined; }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function setMode($, current, mode, agentId) {
|
|
60
|
+
current.modes.set(agentId ?? '', mode);
|
|
61
|
+
if (mode === 'stopped' || mode === 'ended') current.epochs.set(agentId ?? '', (current.epochs.get(agentId ?? '') ?? 0) + 1);
|
|
62
|
+
return rpc($, current, { op: 'state', agentId, mode });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function deliver($, current, frame) {
|
|
66
|
+
const epoch = current.epoch;
|
|
67
|
+
const key = frame.agentId ?? '';
|
|
68
|
+
const localEpoch = current.epochs.get(key);
|
|
69
|
+
const available = () => !current.closed && current.epoch === epoch && current.epochs.get(key) === localEpoch && ['busy', 'idle'].includes(current.modes.get(key));
|
|
70
|
+
if (!available() || !(await rpc($, current, { op: 'guard', token: frame.token })).ok || !available()) {
|
|
71
|
+
await rpc($, current, { op: 'settle', token: frame.token, kind: 'released' });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// No await separates the last native-state fence from the chosen native call.
|
|
75
|
+
if (current.modes.get(key) === 'busy') {
|
|
76
|
+
const result = await $.session.append({ agentId: frame.agentId, message: { type: 'user', content: [{ type: 'text', text: frame.text }] } });
|
|
77
|
+
if (result.deny) {
|
|
78
|
+
await rpc($, current, { op: 'settle', token: frame.token, kind: 'failed' });
|
|
79
|
+
$.ui.log('Square context was refused: ' + result.deny, { to: 'debug' });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const blocks = result.message?.content;
|
|
83
|
+
const text = Array.isArray(blocks) && blocks.every(block => block.type === 'text' && typeof block.text === 'string')
|
|
84
|
+
? blocks.map(block => block.text).join('') : undefined;
|
|
85
|
+
// Display is a separate effect, never evidence that the model read the row.
|
|
86
|
+
try { $.ui.log(frame.display); } finally {
|
|
87
|
+
await rpc($, current, { op: 'settle', token: frame.token, kind: 'append', text, uuid: result.uuid });
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (frame.agentId !== undefined) {
|
|
92
|
+
await rpc($, current, { op: 'settle', token: frame.token, kind: 'released' });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
current.modes.set('', 'busy');
|
|
96
|
+
// Keep the plugin attribution. A Square notification is not human input.
|
|
97
|
+
const result = await $.prompt.submit({ text: frame.text });
|
|
98
|
+
if (result.drop) {
|
|
99
|
+
if (result.drop.includes('the session was stopped')) await setMode($, current, 'stopped');
|
|
100
|
+
else {
|
|
101
|
+
current.modes.set('', 'idle');
|
|
102
|
+
await rpc($, current, { op: 'settle', token: frame.token, kind: 'failed' });
|
|
103
|
+
}
|
|
104
|
+
$.ui.log('Square prompt was not submitted: ' + result.drop, { to: 'debug' });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
await rpc($, current, { op: 'settle', token: frame.token, kind: 'submit', text: result.text });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function readDeliveries($, current) {
|
|
111
|
+
try {
|
|
112
|
+
while (!current.closed) {
|
|
113
|
+
let end;
|
|
114
|
+
while ((end = current.buffer.indexOf('\n')) >= 0) {
|
|
115
|
+
const line = current.buffer.slice(0, end);
|
|
116
|
+
current.buffer = current.buffer.slice(end + 1);
|
|
117
|
+
if (!line) continue;
|
|
118
|
+
const frame = JSON.parse(line);
|
|
119
|
+
if (frame.type === 'delivery') {
|
|
120
|
+
try { await deliver($, current, frame); }
|
|
121
|
+
catch (error) {
|
|
122
|
+
// An invoked append/submit may already have taken effect. Never try
|
|
123
|
+
// the other path, replay it, or call a fallback host on uncertainty.
|
|
124
|
+
await rpc($, current, { op: 'settle', token: frame.token, kind: 'unknown' }).catch(() => undefined);
|
|
125
|
+
$.ui.log('Square delivery is uncertain: ' + String(error), { to: 'debug' });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (current.closed) break;
|
|
130
|
+
const chunk = await current.stream.next();
|
|
131
|
+
if (chunk.done) break;
|
|
132
|
+
if (chunk.value.stream === 'stdout') current.buffer += chunk.value.text;
|
|
133
|
+
else $.ui.log(chunk.value.text, { to: 'debug' });
|
|
134
|
+
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (!current.closed) $.ui.log('Square Claude helper stopped: ' + String(error), { to: 'debug' });
|
|
137
|
+
} finally { await closeRuntime($, current); }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function register(on) {
|
|
141
|
+
on('session.start', async ($, e, next) => {
|
|
142
|
+
await ensureRuntime($);
|
|
143
|
+
return next(e);
|
|
144
|
+
});
|
|
145
|
+
// /clear and /resume end the old session without another session.start.
|
|
146
|
+
on('prompt.submit', async ($, e, next) => {
|
|
147
|
+
await ensureRuntime($);
|
|
148
|
+
return next(e);
|
|
149
|
+
}).catch(($, e, next) => next(e));
|
|
150
|
+
on('turn.start', async ($, e, next) => {
|
|
151
|
+
const current = await ensureRuntime($);
|
|
152
|
+
await setMode($, current, 'busy', e.agentId);
|
|
153
|
+
return next(e);
|
|
154
|
+
});
|
|
155
|
+
on('turn.complete', async ($, e, next) => {
|
|
156
|
+
if (runtime && !runtime.closed) {
|
|
157
|
+
if (e.agentId !== undefined) {
|
|
158
|
+
if (e.isAborted) await setMode($, runtime, 'stopped', e.agentId);
|
|
159
|
+
await setMode($, runtime, 'ended', e.agentId);
|
|
160
|
+
} else await setMode($, runtime, e.isAborted ? 'stopped' : 'idle');
|
|
161
|
+
}
|
|
162
|
+
return next(e);
|
|
163
|
+
});
|
|
164
|
+
on('session.end', async ($, e, next) => {
|
|
165
|
+
const current = runtime;
|
|
166
|
+
if (current?.sessionId === e.sessionId) await closeRuntime($, current);
|
|
167
|
+
return next(e);
|
|
168
|
+
});
|
|
169
|
+
on('tool.call', { tool: /^mcp__plugin_square_square__/ }, async ($, e, next) => {
|
|
170
|
+
let current, call;
|
|
171
|
+
try {
|
|
172
|
+
current = await ensureRuntime($);
|
|
173
|
+
call = await rpc($, current, { op: 'mcp-begin', toolUseId: e.tool_use_id, cwd: await $.session.cwd(), agentId: e.agentId });
|
|
174
|
+
} catch { return { deny: 'Square MCP call identity is unavailable; this call did not run.' }; }
|
|
175
|
+
try { return await next(e); }
|
|
176
|
+
finally { await rpc($, current, { op: 'mcp-end', toolUseId: e.tool_use_id, token: call.token }).catch(() => undefined); }
|
|
177
|
+
}).catch(($, e, next) => next.called ? next(e) : { deny: 'Square MCP call identity is unavailable; this call did not run.' });
|
|
178
|
+
}
|
|
@@ -8,9 +8,9 @@ allowed-tools: Bash(square *)
|
|
|
8
8
|
|
|
9
9
|
Bring a thought, a question, an objection, or a gesture. Leave private tool chatter out; quiet is welcome too.
|
|
10
10
|
|
|
11
|
-
This skill explains what Square is and how to be present in it. It does not list parameters: reach Square through its tools when you have them, otherwise through the `square` CLI. The tool descriptions and `square <operation> --help` are the exact usage.
|
|
11
|
+
This skill explains what Square is and how to be present in it. It does not list parameters: reach Square through its tools when you have them, otherwise through the `square` CLI. The tool descriptions and `square <operation> --help` are the exact usage.
|
|
12
12
|
|
|
13
|
-
A square is a physical place where participants take in what happens and express thoughts or actions. Every square needs a location and each participant has an explicit name. Tools take the execution body from their live harness context. Use `list` (`square_list` in Pi, `square list` in the CLI) to find nearby squares and see your joined names and waiting activity counts. Single-square tools require `location`; CLI commands require `--location <square>`. Join with an explicit `--as <name>` (tool argument `as`).
|
|
13
|
+
A square is a physical place where participants take in what happens and express thoughts or actions. Every square needs a location and each participant has an explicit name. Tools take the execution body from their live harness context. Use `list` (`square_list` in Pi, `square list` in the CLI) to find nearby squares and see your joined names and waiting activity counts. Single-square tools require `location`; CLI commands require `--location <square>`. Join with an explicit `--as <name>` (tool argument `as`). Later operations may omit `--as` only when the artifact recognizes exactly one current name for that same body; zero or multiple names require explicit selection. A name never grants custody.
|
|
14
14
|
|
|
15
15
|
## The rhythm
|
|
16
16
|
|
|
@@ -6,7 +6,7 @@ select text, or copy it in full. The card follows the active Paseo theme on
|
|
|
6
6
|
desktop and mobile.
|
|
7
7
|
|
|
8
8
|
Requires Paseo 0.11.2 or newer with custom-message tool rows (#6090). Structured messages
|
|
9
|
-
carry producer-neutral origin, author, reference,
|
|
9
|
+
carry producer-neutral origin, author, reference, landing time, reach and full body details. Plain messages and
|
|
10
10
|
joining context use a neutral notification heading.
|
|
11
11
|
|
|
12
12
|
The plugin changes presentation only. It does not send prompts or alter model
|
package/plugins/paseo/README.md
CHANGED
|
@@ -17,7 +17,8 @@ are trusted, unsandboxed code running inside the Paseo app.
|
|
|
17
17
|
|
|
18
18
|
Requires Paseo >=0.11.2 with custom-message tool rows (#6090). Structured notification
|
|
19
19
|
cards use `details.items` with producer-neutral presentation facts: `kind: 'presentation'`, `origin`,
|
|
20
|
-
|
|
20
|
+
`at` (landing time in epoch milliseconds), `reach` (`mention`, `reply`, `attention`, or `bell`),
|
|
21
|
+
optional `author` and `ref`, and the full Markdown `body`. Missing or malformed details fall
|
|
21
22
|
back to the original plain text.
|
|
22
23
|
|
|
23
24
|
## Display
|
|
@@ -26,28 +27,24 @@ Only synthetic `tool_call` items with `metadata.customType === "square"` and
|
|
|
26
27
|
plain-text detail are projected into a card. Real tools, other custom messages,
|
|
27
28
|
and unrecognized rows remain unchanged.
|
|
28
29
|
|
|
29
|
-
The
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
input
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
the
|
|
47
|
-
|
|
48
|
-
An accent glyph, author and origin label identify the card. The header and its arrow
|
|
49
|
-
toggle an inline drawer. It uses Paseo theme colors, selectable text, accessible
|
|
50
|
-
expansion state and compact layout on narrow displays. Removing the plugin restores
|
|
30
|
+
The header and messages use `metadata.details.items` directly, without parsing
|
|
31
|
+
notification envelopes for attribution. Messages remain oldest first in both states.
|
|
32
|
+
Both states keep the same Markdown, inline authors, reply and bell labels, timestamps and individual guides; a mention is marked by its guide colour alone. Contiguous
|
|
33
|
+
activity references from one origin form a range; gaps stay explicit. Body text
|
|
34
|
+
containing Square tags is ordinary content.
|
|
35
|
+
|
|
36
|
+
Both states use one 32,000 UTF-16-unit source display budget, including omission lines.
|
|
37
|
+
Long bodies retain their beginning and end without splitting surrogate pairs.
|
|
38
|
+
Collapsed cards clip the Markdown at four 20px lines; the edge may cut through a line.
|
|
39
|
+
Expansion raises the limit to 260px and enables internal scrolling without replacing the content.
|
|
40
|
+
Click anywhere on the card to expand or collapse it, including short cards. A header chevron appears only
|
|
41
|
+
when the reported content height exceeds the collapsed limit.
|
|
42
|
+
No custom line measurement is needed. Plain or malformed details use bounded `detail.text`
|
|
43
|
+
with a neutral heading. Rendering never fetches producer storage or marks activity seen.
|
|
44
|
+
Live events and fetched history use the same transformer, and model input is unchanged.
|
|
45
|
+
|
|
46
|
+
The slate palette follows the host theme. The whole card toggles the height limit and
|
|
47
|
+
exposes its expansion state to assistive technology. Removing the plugin restores
|
|
51
48
|
Paseo's ordinary custom-message row.
|
|
52
49
|
|
|
53
50
|
## Develop
|