@astrosheep/square 0.3.82 → 0.3.84
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/claude-delivery-status.d.ts +7 -0
- package/dist/claude-delivery-status.js +24 -0
- package/dist/claude-delivery.js +9 -15
- package/dist/claude-mod.js +5 -0
- package/dist/cli/catch-presentation.d.ts +2 -6
- package/dist/cli/catch-presentation.js +2 -50
- package/dist/cli/express-presentation.d.ts +2 -2
- package/dist/cli/express-presentation.js +6 -29
- package/dist/cli/observation-commands.js +10 -142
- package/dist/cli/program.js +5 -28
- package/dist/cli/square-commands.js +14 -56
- package/dist/delivery-health.d.ts +2 -2
- package/dist/delivery-health.js +20 -2
- package/dist/harness-pi.js +26 -2
- package/dist/host-ledger-file-adapter.d.ts +3 -0
- package/dist/host-ledger-file-adapter.js +12 -0
- package/dist/host-ledger.d.ts +12 -0
- package/dist/mcp.js +17 -6
- package/dist/operation-presentation.d.ts +45 -0
- package/dist/operation-presentation.js +370 -0
- package/dist/packages/agent-delivery/src/claude-native.js +3 -1
- package/dist/routes.js +7 -2
- package/dist/square-tools.d.ts +14 -2
- package/dist/square-tools.js +36 -17
- package/dist/wake-evidence.d.ts +2 -1
- package/dist/wake-evidence.js +2 -1
- package/guides/catch-cold-path-optimization.md +1 -1
- package/guides/claude-native.md +32 -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/OVERVIEW.md +7 -4
- package/plugins/paseo/README.md +30 -6
- package/plugins/paseo/index.server.ts +10 -0
- package/plugins/paseo/package.json +3 -3
- package/plugins/paseo/paseo-plugin.json +1 -1
- package/plugins/paseo/server/record-delivery.ts +28 -0
package/dist/routes.js
CHANGED
|
@@ -19,10 +19,10 @@ export async function defaultWakeRouteCapabilities(hostLedger) {
|
|
|
19
19
|
export function resolvePrimaryWakeRoute(boundary, env, capabilities) {
|
|
20
20
|
const paseoAgentId = env.PASEO_AGENT_ID?.trim();
|
|
21
21
|
const candidates = [];
|
|
22
|
-
if (paseoAgentId)
|
|
23
|
-
candidates.push({ kind: 'paseo', address: { agentId: paseoAgentId } });
|
|
24
22
|
if (boundary.provider === 'codex')
|
|
25
23
|
candidates.push({ kind: 'codex-queue', address: { threadId: boundary.sessionId } });
|
|
24
|
+
if (paseoAgentId)
|
|
25
|
+
candidates.push({ kind: 'paseo', address: { agentId: paseoAgentId } });
|
|
26
26
|
const chosen = candidates.find((candidate) => Object.values(candidate.address).every((value) => value.trim() !== '') && capabilities.canUse(candidate.kind, candidate.address));
|
|
27
27
|
return chosen === undefined ? undefined : { location: boundary.location, participant: boundary.participant, sessionId: boundary.sessionId, channel: boundary.provider === 'paseo' ? 'paseo' : boundary.provider === 'claude' ? 'claude-code' : boundary.provider, ...chosen };
|
|
28
28
|
}
|
|
@@ -80,6 +80,11 @@ export async function publishWakeRoute(artifact, route, opts = {}) {
|
|
|
80
80
|
return;
|
|
81
81
|
const identity = routeIdentityKey(route, location);
|
|
82
82
|
const matching = routes.filter((item) => routeIdentityKey(item, canonicalPathSync(item.location)) === identity);
|
|
83
|
+
// A receiver's fresh native registration outranks a generic Paseo proposal.
|
|
84
|
+
// Arbitrate in the write transaction so tool publication and mod reconciliation cannot flip it back.
|
|
85
|
+
if (route.kind === 'paseo' && matching.some((item) => item.kind !== 'paseo' && item.channel === route.channel
|
|
86
|
+
&& item.epoch === route.epoch && item.updatedAt <= at && item.updatedAt > at - ROUTE_FRESH_MS))
|
|
87
|
+
return;
|
|
83
88
|
const existing = matching[0];
|
|
84
89
|
if (matching.length === 1 && existing.updatedAt > at - ROUTE_FRESH_MS && existing.kind === route.kind
|
|
85
90
|
&& existing.channel === route.channel && sameRouteAddress(existing.address, route.address) && existing.epoch === route.epoch)
|
package/dist/square-tools.d.ts
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
|
-
import type
|
|
2
|
+
import { type Square } from './square-api.js';
|
|
3
3
|
import type { OperationControl } from './square-facade.js';
|
|
4
4
|
type JsonObject = Record<string, unknown>;
|
|
5
5
|
type Outcome = Promise<object>;
|
|
6
|
+
type ToolRender = (result: JsonObject, args: JsonObject) => string | undefined;
|
|
6
7
|
interface ToolDefinition {
|
|
7
8
|
name: string;
|
|
8
9
|
description: string;
|
|
9
10
|
inputSchema: z.ZodObject<z.ZodRawShape>;
|
|
10
|
-
run(square: Square, args: JsonObject, control:
|
|
11
|
+
run(square: Square, args: JsonObject, control: ToolControl): Outcome;
|
|
12
|
+
/** Human text identical to what the CLI prints; transports show this to people. */
|
|
13
|
+
render: ToolRender;
|
|
14
|
+
}
|
|
15
|
+
/** A transport that shows people the square asks the same operation for its rendering facts. */
|
|
16
|
+
export interface ToolControl extends OperationControl {
|
|
17
|
+
readonly observe?: boolean;
|
|
11
18
|
}
|
|
12
19
|
export declare const SQUARE_TOOL_INSTRUCTIONS = "The catch tool consumes activities. Use history with act/<index> cursors for read-only archive access.";
|
|
13
20
|
export declare const SQUARE_TOOLS: readonly ToolDefinition[];
|
|
14
21
|
export declare function squareToolFailure(error: unknown, signal?: AbortSignal): JsonObject;
|
|
22
|
+
/** Readable failure text with the same recovery commands the CLI offers, never raw JSON. */
|
|
23
|
+
export declare function squareToolFailureText(failure: JsonObject, context?: {
|
|
24
|
+
location?: string;
|
|
25
|
+
participant?: string;
|
|
26
|
+
}): string;
|
|
15
27
|
export {};
|
package/dist/square-tools.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
2
|
import { isSquareError } from './model.js';
|
|
3
|
+
import { CATCH_DEFAULT_LIMIT } from './square-api.js';
|
|
4
|
+
import { renderCatchOutcome, renderExpression, renderFailureLines, renderHistoryResult, renderJoinResult, renderLifecycleResult, renderListenerChange, renderListeningResult, renderParticipantsResult, renderStatusResult, } from './operation-presentation.js';
|
|
3
5
|
const location = z.string().optional().describe('Path to the .square artifact, relative to the current working directory. Defaults to the configured square.');
|
|
4
6
|
const participant = z.string().optional().describe('Participant name. Defaults to the configured participant or the current session’s unambiguous participant.');
|
|
5
7
|
const activityId = z.string().regex(/^act\/(0|[1-9][0-9]*)$/);
|
|
6
8
|
const stringArray = z.array(z.string());
|
|
7
|
-
|
|
9
|
+
/** Keep the observation request internal: it is not an argument a caller decides. */
|
|
10
|
+
function observation(control) {
|
|
11
|
+
return control.observe === true ? { observe: true } : {};
|
|
12
|
+
}
|
|
13
|
+
/** Tool receipts are plain JSON; each renderer re-declares the facts it reads. */
|
|
14
|
+
function rendering(render) {
|
|
15
|
+
return (result, args) => render(result, args);
|
|
16
|
+
}
|
|
17
|
+
function tool(name, description, shape, run, render) {
|
|
8
18
|
const inputSchema = z.object({ ...shape, location }).strict();
|
|
9
|
-
return { name, description, inputSchema, run: (square, args, control) => {
|
|
19
|
+
return { name, description, inputSchema, render, run: (square, args, control) => {
|
|
10
20
|
const { location: _location, ...operationArgs } = inputSchema.parse(args);
|
|
11
21
|
return run(square, operationArgs, control);
|
|
12
22
|
} };
|
|
@@ -14,35 +24,40 @@ function tool(name, description, shape, run) {
|
|
|
14
24
|
export const SQUARE_TOOL_INSTRUCTIONS = 'The catch tool consumes activities. Use history with act/<index> cursors for read-only archive access.';
|
|
15
25
|
export const SQUARE_TOOLS = [
|
|
16
26
|
tool('join', 'Join the square as a participant.', { as: participant, takeover: z.boolean().optional().describe('Take the name from its current session owner.') }, async (square, { as, takeover }, control) => {
|
|
17
|
-
const { participant, ...receipt } = await square.join(as, { takeover }, control);
|
|
27
|
+
const { participant, ...receipt } = await square.join(as, { takeover, ...observation(control) }, control);
|
|
18
28
|
return { ...receipt, participant: participant.name };
|
|
19
|
-
}),
|
|
29
|
+
}, rendering((result) => renderJoinResult(result))),
|
|
20
30
|
tool('express', 'Express a message or embodied action in the square. Mentions and replies direct attention; bell reaches everyone and keeps body @names literal. Bell cannot combine with explicit mentions. Bare activity is valid even with no listeners.', {
|
|
21
31
|
as: participant, body: z.string(), mentions: stringArray.optional(), reach: z.enum(['bell']).optional(),
|
|
22
32
|
reply: activityId.optional(), noMention: z.boolean().optional().describe('Disable body @name scanning; explicit mentions and reply still apply.'),
|
|
23
33
|
force: z.boolean().optional().describe('Speak without first catching unread activity or satisfying attention etiquette.'),
|
|
24
34
|
noWait: z.boolean().optional().describe('Return an error rather than waiting when held or throttled.'),
|
|
25
35
|
}, async (square, { body, as, ...options }, control) => {
|
|
26
|
-
const { delivery: _delivery, ...result } = await (await square.participant(as)).express(body, options, control);
|
|
36
|
+
const { delivery: _delivery, ...result } = await (await square.participant(as)).express(body, { ...options, ...observation(control) }, control);
|
|
27
37
|
return result;
|
|
28
|
-
}),
|
|
38
|
+
}, rendering((result) => renderExpression(result.observation?.location ?? '', result))),
|
|
29
39
|
tool('catch', 'Catch and consume activities perceptible to the participant. idle waits for relevant activity or this many milliseconds of quiet; omitted means immediate. id catches one activity and cannot be combined with filters, idle or limit.', {
|
|
30
40
|
as: participant, id: activityId.optional(), idle: z.number().finite().min(0).optional(), from: stringArray.optional(),
|
|
31
41
|
mention: z.boolean().optional(), limit: z.number().int().min(1).optional(),
|
|
32
|
-
}, async (square, { as, ...options }, control) => (await square.participant(as)).catch(options, control)),
|
|
42
|
+
}, async (square, { as, ...options }, control) => (await square.participant(as)).catch({ ...options, ...observation(control) }, control), rendering((result, args) => {
|
|
43
|
+
// The CLI always spells the default cap in its continuation, so the tool does too.
|
|
44
|
+
const options = args;
|
|
45
|
+
const renderOptions = options.id === undefined ? { ...options, limit: options.limit ?? CATCH_DEFAULT_LIMIT } : options;
|
|
46
|
+
return renderCatchOutcome(result.observation?.location ?? '', result, renderOptions).stdout;
|
|
47
|
+
})),
|
|
33
48
|
tool('history', 'Read the activity stream without consuming it, using stable activity-id cursors. Default page size is 10, maximum 100. grep is a regex; fixed is literal text; use only one.', {
|
|
34
49
|
limit: z.number().int().min(1).optional(), order: z.enum(['asc', 'desc']).optional(), before: activityId.optional(), after: activityId.optional(),
|
|
35
50
|
grep: z.string().optional(), fixed: z.string().optional(), from: stringArray.optional(), mention: z.string().optional(),
|
|
36
|
-
}, (square, args, control) => square.history(args, control)),
|
|
37
|
-
tool('listen', 'Listen for future activity from a participant, clearing any ignore of them.', { as: participant, target: z.string() }, async (square, { as, target }, control) => (await square.participant(as)).listen(target,
|
|
38
|
-
tool('ignore', 'Block a participant’s future mentions and bare activity. Listening again clears the ignore; already landed activity is unchanged.', { as: participant, target: z.string() }, async (square, { as, target }, control) => (await square.participant(as)).ignore(target,
|
|
39
|
-
tool('unlisten', 'Stop listening to a participant’s future activity; mentions and replies still reach you. An existing ignore is unchanged.', { as: participant, target: z.string() }, async (square, { as, target }, control) => (await square.participant(as)).unlisten(target,
|
|
40
|
-
tool('listening', 'List participants this participant is listening to.', { as: participant }, async (square, { as }, control) => (await square.participant(as)).listening(
|
|
41
|
-
tool('hold', 'Raise a hand to hold the square.', { as: participant, reason: z.string().optional() }, async (square, { as, reason }, control) => (await square.participant(as)).hold(reason,
|
|
42
|
-
tool('resume', 'Release the current hold.', { as: participant }, async (square, { as }, control) => (await square.participant(as)).resume(
|
|
43
|
-
tool('done', 'Leave the square without adding a message.', { as: participant }, async (square, { as }, control) => (await square.participant(as)).done(undefined,
|
|
44
|
-
tool('status', 'Read the public status of the square.', {}, (square, _args, control) => square.status(
|
|
45
|
-
tool('participants', 'List participants standing in or done with the square.', {}, (square, _args, control) => square.participants(
|
|
51
|
+
}, (square, args, control) => square.history({ ...args, ...observation(control) }, control), rendering((result, args) => renderHistoryResult(result.observation?.location ?? '', result, { query: args }))),
|
|
52
|
+
tool('listen', 'Listen for future activity from a participant, clearing any ignore of them.', { as: participant, target: z.string() }, async (square, { as, target }, control) => (await square.participant(as)).listen(target, observation(control), control), rendering((result) => renderListenerChange(result.observation?.location ?? '', 'listen', result))),
|
|
53
|
+
tool('ignore', 'Block a participant’s future mentions and bare activity. Listening again clears the ignore; already landed activity is unchanged.', { as: participant, target: z.string() }, async (square, { as, target }, control) => (await square.participant(as)).ignore(target, observation(control), control), rendering((result) => renderListenerChange(result.observation?.location ?? '', 'ignore', result))),
|
|
54
|
+
tool('unlisten', 'Stop listening to a participant’s future activity; mentions and replies still reach you. An existing ignore is unchanged.', { as: participant, target: z.string() }, async (square, { as, target }, control) => (await square.participant(as)).unlisten(target, observation(control), control), rendering((result) => renderListenerChange(result.observation?.location ?? '', 'unlisten', result))),
|
|
55
|
+
tool('listening', 'List participants this participant is listening to.', { as: participant }, async (square, { as }, control) => (await square.participant(as)).listening(observation(control), control), rendering((result) => renderListeningResult(result.observation?.location ?? '', result))),
|
|
56
|
+
tool('hold', 'Raise a hand to hold the square.', { as: participant, reason: z.string().optional() }, async (square, { as, reason }, control) => (await square.participant(as)).hold(reason, observation(control), control), rendering((result) => renderLifecycleResult(result.observation?.location ?? '', 'hold', result))),
|
|
57
|
+
tool('resume', 'Release the current hold.', { as: participant }, async (square, { as }, control) => (await square.participant(as)).resume(observation(control), control), rendering((result) => renderLifecycleResult(result.observation?.location ?? '', 'resume', result))),
|
|
58
|
+
tool('done', 'Leave the square without adding a message.', { as: participant }, async (square, { as }, control) => (await square.participant(as)).done(undefined, observation(control), control), rendering((result) => renderLifecycleResult(result.observation?.location ?? '', 'done', result))),
|
|
59
|
+
tool('status', 'Read the public status of the square.', {}, (square, _args, control) => square.status(observation(control), control), rendering((result) => renderStatusResult(result.observation?.location ?? '', result))),
|
|
60
|
+
tool('participants', 'List participants standing in or done with the square.', {}, (square, _args, control) => square.participants(observation(control), control), rendering((result) => renderParticipantsResult(result.observation?.location ?? '', result))),
|
|
46
61
|
];
|
|
47
62
|
export function squareToolFailure(error, signal) {
|
|
48
63
|
const normalized = signal?.aborted
|
|
@@ -55,3 +70,7 @@ export function squareToolFailure(error, signal) {
|
|
|
55
70
|
const operation = error instanceof Error ? error.operation : undefined;
|
|
56
71
|
return operation === undefined ? normalized : { ...normalized, operation };
|
|
57
72
|
}
|
|
73
|
+
/** Readable failure text with the same recovery commands the CLI offers, never raw JSON. */
|
|
74
|
+
export function squareToolFailureText(failure, context = {}) {
|
|
75
|
+
return renderFailureLines(failure, { squarePath: context.location, name: context.participant }).join('\n');
|
|
76
|
+
}
|
package/dist/wake-evidence.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { EvidenceRecord, WakeAttention } from './host-ledger.js';
|
|
1
|
+
import type { EvidenceRecord, WakeAttention, NativeDeliveryObservation } from './host-ledger.js';
|
|
2
2
|
import type { WakeRouteKind } from './model.js';
|
|
3
3
|
export interface WakeAttempt {
|
|
4
|
+
readonly nativeObservations?: readonly NativeDeliveryObservation[];
|
|
4
5
|
readonly at: number;
|
|
5
6
|
readonly attention: WakeAttention;
|
|
6
7
|
readonly routeKind: WakeRouteKind;
|
package/dist/wake-evidence.js
CHANGED
|
@@ -27,5 +27,6 @@ export function decodeWakeEvidence(record, now) {
|
|
|
27
27
|
return undefined;
|
|
28
28
|
if (record.routeKind === undefined || typeof record.attemptN !== 'number')
|
|
29
29
|
return undefined;
|
|
30
|
-
return { kind: 'attempt', value: { ...value, routeKind: record.routeKind, attemptN: record.attemptN, outcome: record.outcome
|
|
30
|
+
return { kind: 'attempt', value: { ...value, routeKind: record.routeKind, attemptN: record.attemptN, outcome: record.outcome,
|
|
31
|
+
...(record.nativeObservations === undefined ? {} : { nativeObservations: record.nativeObservations }) } };
|
|
31
32
|
}
|
|
@@ -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.
|
|
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 predate the [native-first route policy](claude-native.md#native-first-routing-and-policy-holds); 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
|
@@ -19,6 +19,38 @@ peer/plugin authority. Native OS-user socket permissions and Claude policy apply
|
|
|
19
19
|
`square harness doctor delivery` derives current routes/evidence without sending.
|
|
20
20
|
An absent/broken endpoint is unavailable; it does not consume Square activity.
|
|
21
21
|
|
|
22
|
+
## Native-first routing and policy holds
|
|
23
|
+
|
|
24
|
+
Native delivery is preferred, including under Paseo. Codex proposes its capable
|
|
25
|
+
native queue before Paseo. Claude and OpenCode receivers register their native
|
|
26
|
+
routes only through their loaded integrations. The common publication transaction
|
|
27
|
+
keeps a fresh native registration ahead of a Paseo proposal for the same
|
|
28
|
+
participant, session, channel and ownership epoch. Tool operations cannot flip it
|
|
29
|
+
back to Paseo between receiver reconciliations. An absent, expired or explicitly
|
|
30
|
+
retired native registration does not block a Paseo fallback; a replacement epoch
|
|
31
|
+
cannot inherit an older native registration.
|
|
32
|
+
|
|
33
|
+
This is route preference, not permission to bypass receiver policy. Claude can
|
|
34
|
+
legitimately hold an unsolicited peer message with `no-mode-asserted`, especially
|
|
35
|
+
under `bypassPermissions`. Square neither asserts a made-up permission mode nor
|
|
36
|
+
changes `crossSessionInbound`. After bytes may have been sent, `unknown` remains
|
|
37
|
+
attention-wide and prevents another send through Paseo, even if routes later change.
|
|
38
|
+
|
|
39
|
+
Each native send persists a message ID before writing; the wire `uuid` and
|
|
40
|
+
`msg_id` carry that ID. On a matching Paseo build, the Square server plugin records
|
|
41
|
+
`held`, `released` and `dropped` observations from `agent.delivery_status` on the
|
|
42
|
+
original host-ledger wake attempt. The event ID is deduplicated and the native
|
|
43
|
+
reason and observation time remain diagnostic only. Neither a release nor a drop
|
|
44
|
+
is a queue receipt, proof of consumption or retry authorization. Unknown IDs do
|
|
45
|
+
not create evidence; older attempts without message IDs cannot be backfilled.
|
|
46
|
+
|
|
47
|
+
`square harness doctor delivery` shows native custody separately from pending
|
|
48
|
+
attention, including after the activity is seen or the participant leaves. Paseo
|
|
49
|
+
also presents the native state in its timeline. This integration requires the
|
|
50
|
+
matching local Paseo 0.12.1-square.1 lifecycle API and plugin SDK; see
|
|
51
|
+
[the Paseo plugin](../plugins/paseo/README.md). Acceptance means normal delivery
|
|
52
|
+
when policy allows it, or a visible reason without bypass/replay when policy holds it.
|
|
53
|
+
|
|
22
54
|
## MCP tools and call identity
|
|
23
55
|
|
|
24
56
|
The plugin's `.mcp.json` starts the existing Square MCP server. The mod no longer
|
package/package.json
CHANGED
|
@@ -5,10 +5,13 @@ who reached you and where. Expand it to see the original custom-message content,
|
|
|
5
5
|
select text, or copy it in full. The card follows the active Paseo theme on
|
|
6
6
|
desktop and mobile.
|
|
7
7
|
|
|
8
|
-
Requires Paseo 0.
|
|
8
|
+
Requires the local Paseo 0.12.1-square.1 patch build and its plugin SDK,
|
|
9
|
+
which add `agent.delivery_status` to official 0.12.0. Square
|
|
9
10
|
0.3.69 or newer supplies structured card headings. Older messages and joining
|
|
10
11
|
context use a neutral “Square context” heading.
|
|
11
12
|
|
|
12
|
-
The
|
|
13
|
-
context.
|
|
14
|
-
preview
|
|
13
|
+
The client changes presentation only. It does not send prompts or alter model
|
|
14
|
+
context. Expansion uses the complete renderer text when present, otherwise the
|
|
15
|
+
original preview; it never fetches extra activity. The server records Claude native
|
|
16
|
+
held/released/dropped diagnostics on Square's original wake attempt. It does not
|
|
17
|
+
change admission policy, confirm consumption, or resend notifications.
|
package/plugins/paseo/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Square for Paseo
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Presentation for Square custom messages, plus a server entry that records Claude
|
|
4
|
+
native custody diagnostics. It never changes admission policy, sends a second
|
|
5
|
+
notification, marks an activity seen, or alters model context.
|
|
5
6
|
|
|
6
7
|
## Install from GitHub
|
|
7
8
|
|
|
@@ -15,9 +16,13 @@ directory. Square tags up to v0.3.69 do not contain it.
|
|
|
15
16
|
Paseo supplies all runtime modules. No Git preparation step is needed. Plugins
|
|
16
17
|
are trusted, unsandboxed code running inside the Paseo app.
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
The server entry requires the local **Paseo 0.12.1-square.1** patch build, based
|
|
20
|
+
on official 0.12.0 and adding `agent.delivery_status`. Official 0.12.0 does not
|
|
21
|
+
provide that API. The manifest pins the tested patch build rather than claiming
|
|
22
|
+
support for an unpatched or hypothetical future official release. The client
|
|
23
|
+
card uses custom-message tool rows (#6090).
|
|
24
|
+
Square 0.3.69+ provides `details.items` for the collapsed heading. Messages without
|
|
25
|
+
those details still expand to their original content.
|
|
21
26
|
|
|
22
27
|
## Display
|
|
23
28
|
|
|
@@ -40,11 +45,29 @@ and compact layout on narrow displays. Expansion starts at the top-right arrow;
|
|
|
40
45
|
collapse returns from the arrow below the expanded content. Removing the plugin restores Paseo's
|
|
41
46
|
ordinary custom-message row.
|
|
42
47
|
|
|
48
|
+
## Native custody evidence
|
|
49
|
+
|
|
50
|
+
Paseo renders Claude `peer_message_hold` events as timeline notifications. The
|
|
51
|
+
server entry forwards socket observations (original event ID, native session and
|
|
52
|
+
message ID, held/released/dropped state, reason and observation time) to
|
|
53
|
+
`square claude-mod` over JSON stdin in the agent's working directory. Square
|
|
54
|
+
matches a message ID persisted before sending and appends the observation to the
|
|
55
|
+
original wake attempt. Unknown message IDs create no evidence. Failures surface
|
|
56
|
+
as plugin hook errors; forwarding is bounded to five seconds and honors hook
|
|
57
|
+
cancellation.
|
|
58
|
+
|
|
59
|
+
`held`, `released` and `dropped` are not queue receipts or proof of consumption.
|
|
60
|
+
They never turn `unknown` into `accepted` or authorize a replay. `square harness
|
|
61
|
+
doctor delivery` shows the native state and reason, including after the activity is seen.
|
|
62
|
+
The `square` executable must resolve to the matching Square release on the daemon's PATH.
|
|
63
|
+
|
|
43
64
|
## Develop
|
|
44
65
|
|
|
45
66
|
```bash
|
|
46
67
|
cd plugins/paseo
|
|
47
68
|
npm ci
|
|
69
|
+
# Link the SDK from the built Paseo 0.12.1-square.1 source checkout:
|
|
70
|
+
npm link --no-save /path/to/paseo/packages/plugin
|
|
48
71
|
npm run typecheck
|
|
49
72
|
npm test
|
|
50
73
|
paseo plugin install "$PWD"
|
|
@@ -56,4 +79,5 @@ After editing an installed directory source:
|
|
|
56
79
|
paseo plugin reload square
|
|
57
80
|
```
|
|
58
81
|
|
|
59
|
-
|
|
82
|
+
Plugin-only reloads need no Paseo restart once the host provides the lifecycle
|
|
83
|
+
API. Reloading a plugin cannot add that API to an older daemon. The plugin ID is `square`.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PluginServerContext } from "@getpaseo/plugin/server";
|
|
2
|
+
import { recordDeliveryStatus } from "./server/record-delivery.ts";
|
|
3
|
+
|
|
4
|
+
export default function contribute(server: Pick<PluginServerContext, "on">) {
|
|
5
|
+
return server.on("agent.delivery_status", async ({ agent, observation }, { signal }) => {
|
|
6
|
+
// Agent providers can wrap Claude (for example yescode); the observation identifies the native producer.
|
|
7
|
+
if (observation.provider !== "claude" || observation.lane !== "socket" || !observation.eventId) return;
|
|
8
|
+
await recordDeliveryStatus(agent.cwd, observation, signal);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
"test": "node --experimental-strip-types --test test/*.test.ts"
|
|
9
9
|
},
|
|
10
10
|
"devDependencies": {
|
|
11
|
-
"@getpaseo/plugin": "0.
|
|
12
|
-
"@types/react": "~19.2.0",
|
|
11
|
+
"@getpaseo/plugin": "0.12.0",
|
|
13
12
|
"@types/node": "^22",
|
|
13
|
+
"@types/react": "~19.2.0",
|
|
14
14
|
"react": "19.1.0",
|
|
15
15
|
"react-native": "0.81.5",
|
|
16
16
|
"typescript": "^5.9.3",
|
|
17
|
-
"zod": "
|
|
17
|
+
"zod": "4.4.3"
|
|
18
18
|
}
|
|
19
19
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { spawnProcess, type AgentDeliveryObservation } from "@getpaseo/plugin/server";
|
|
2
|
+
|
|
3
|
+
/** Forward native facts only. Square matches its pre-persisted message ID; no text parsing. */
|
|
4
|
+
export async function recordDeliveryStatus(
|
|
5
|
+
cwd: string,
|
|
6
|
+
observation: AgentDeliveryObservation,
|
|
7
|
+
signal: AbortSignal,
|
|
8
|
+
): Promise<void> {
|
|
9
|
+
const child = spawnProcess("square", ["claude-mod"], {
|
|
10
|
+
cwd, env: process.env, stdio: "pipe", signal, timeout: 5_000,
|
|
11
|
+
});
|
|
12
|
+
await new Promise<void>((resolve, reject) => {
|
|
13
|
+
child.once("error", reject);
|
|
14
|
+
child.stdin.once("error", reject);
|
|
15
|
+
// Output contains no user-facing content. Drain both pipes; the bridge exit is authoritative.
|
|
16
|
+
child.stdout.resume();
|
|
17
|
+
child.stderr.resume();
|
|
18
|
+
child.once("close", (code, killedBy) => {
|
|
19
|
+
if (code === 0) resolve();
|
|
20
|
+
else reject(new Error(`Square native status recorder failed (${killedBy ?? code}).`));
|
|
21
|
+
});
|
|
22
|
+
const { provider, sessionId, messageId, eventId, state, reason, observedAt } = observation;
|
|
23
|
+
child.stdin.end(JSON.stringify({ operation: "status", cwd, sessionId, status: {
|
|
24
|
+
provider, sessionId, messageId, eventId, state, observedAt,
|
|
25
|
+
...(reason === null ? {} : { reason }),
|
|
26
|
+
} }));
|
|
27
|
+
});
|
|
28
|
+
}
|