@astrosheep/square 0.3.83 → 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.
Files changed (36) hide show
  1. package/dist/claude-delivery-status.d.ts +7 -0
  2. package/dist/claude-delivery-status.js +24 -0
  3. package/dist/claude-delivery.js +9 -15
  4. package/dist/claude-mod.js +5 -0
  5. package/dist/cli/catch-presentation.d.ts +2 -6
  6. package/dist/cli/catch-presentation.js +2 -50
  7. package/dist/cli/express-presentation.d.ts +2 -2
  8. package/dist/cli/express-presentation.js +6 -29
  9. package/dist/cli/observation-commands.js +10 -142
  10. package/dist/cli/program.js +5 -28
  11. package/dist/cli/square-commands.js +14 -56
  12. package/dist/delivery-health.d.ts +2 -2
  13. package/dist/delivery-health.js +20 -2
  14. package/dist/host-ledger-file-adapter.d.ts +3 -0
  15. package/dist/host-ledger-file-adapter.js +12 -0
  16. package/dist/host-ledger.d.ts +12 -0
  17. package/dist/mcp.js +17 -6
  18. package/dist/operation-presentation.d.ts +45 -0
  19. package/dist/operation-presentation.js +370 -0
  20. package/dist/packages/agent-delivery/src/claude-native.js +3 -1
  21. package/dist/routes.js +7 -2
  22. package/dist/square-tools.d.ts +14 -2
  23. package/dist/square-tools.js +36 -17
  24. package/dist/wake-evidence.d.ts +2 -1
  25. package/dist/wake-evidence.js +2 -1
  26. package/guides/catch-cold-path-optimization.md +1 -1
  27. package/guides/claude-native.md +32 -0
  28. package/package.json +1 -1
  29. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  30. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  31. package/plugins/paseo/OVERVIEW.md +7 -4
  32. package/plugins/paseo/README.md +30 -6
  33. package/plugins/paseo/index.server.ts +10 -0
  34. package/plugins/paseo/package.json +3 -3
  35. package/plugins/paseo/paseo-plugin.json +1 -1
  36. package/plugins/paseo/server/record-delivery.ts +28 -0
@@ -1,15 +1,27 @@
1
1
  import { z } from 'zod/v4';
2
- import type { Square } from './square-api.js';
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: OperationControl): Outcome;
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 {};
@@ -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
- function tool(name, description, shape, run) {
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, {}, control)),
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, {}, control)),
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, {}, control)),
40
- tool('listening', 'List participants this participant is listening to.', { as: participant }, async (square, { as }, control) => (await square.participant(as)).listening({}, control)),
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, {}, control)),
42
- tool('resume', 'Release the current hold.', { as: participant }, async (square, { as }, control) => (await square.participant(as)).resume({}, control)),
43
- tool('done', 'Leave the square without adding a message.', { as: participant }, async (square, { as }, control) => (await square.participant(as)).done(undefined, {}, control)),
44
- tool('status', 'Read the public status of the square.', {}, (square, _args, control) => square.status({}, control)),
45
- tool('participants', 'List participants standing in or done with the square.', {}, (square, _args, control) => square.participants({}, control)),
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
+ }
@@ -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;
@@ -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. Paseo precedence, Codex fallback, harness identity, and publication failure isolation remain unchanged.
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
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/square",
3
- "version": "0.3.83",
3
+ "version": "0.3.84",
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.83",
3
+ "version": "0.3.84",
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.83",
3
+ "version": "0.3.84",
4
4
  "description": "Shared Square activity with reliable participant attention at Codex boundaries.",
5
5
  "author": {
6
6
  "name": "Square"
@@ -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.11.2 or newer with custom-message tool rows (#6090). Square
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 plugin changes presentation only. It does not send prompts or alter model
13
- context. If Square clipped a body before delivery, expansion shows that same
14
- preview and the command for reading the rest, not an additional fetch.
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.
@@ -1,7 +1,8 @@
1
1
  # Square for Paseo
2
2
 
3
- A client-only presentation plugin for Square custom messages. No daemon restart,
4
- backend entry, extra notifications, or model-context changes.
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
- Requires Paseo >=0.11.2 with custom-message tool rows (#6090). Square 0.3.69+
19
- provides `details.items` for the collapsed heading. Messages without those
20
- details still expand to their original content.
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
- No Paseo restart is needed. The plugin ID is `square`.
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.11.2",
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": "^4.4.3"
17
+ "zod": "4.4.3"
18
18
  }
19
19
  }
@@ -2,6 +2,6 @@
2
2
  "id": "square",
3
3
  "name": "Square",
4
4
  "requirements": {
5
- "paseo": ">=0.11.2"
5
+ "paseo": "0.12.1-square.1"
6
6
  }
7
7
  }
@@ -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
+ }