@astrosheep/square 0.4.6 → 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 +2 -2
- package/dist/cli/context.js +14 -16
- package/dist/cli/express-presentation.js +12 -53
- package/dist/cli/json.js +2 -2
- package/dist/cli/observation-commands.d.ts +2 -7
- package/dist/cli/observation-commands.js +31 -97
- package/dist/cli/registry.js +3 -6
- package/dist/cli/square-commands.d.ts +2 -3
- package/dist/cli/square-commands.js +12 -22
- package/dist/decisions.js +5 -4
- package/dist/harness-claude.js +3 -3
- package/dist/help.js +9 -16
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +7 -18
- package/dist/model.d.ts +1 -1
- package/dist/neutral.d.ts +15 -3
- package/dist/notifications.d.ts +1 -3
- package/dist/notifications.js +21 -23
- package/dist/operation-control.d.ts +0 -1
- package/dist/operation-control.js +0 -11
- package/dist/operation-presentation.d.ts +28 -21
- package/dist/operation-presentation.js +90 -87
- 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/participant-identity.d.ts +0 -2
- package/dist/participant-identity.js +0 -13
- 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 +5 -22
- package/dist/presentation.js +25 -58
- package/dist/runtime.d.ts +6 -6
- package/dist/runtime.js +5 -15
- package/dist/sqlite-square.d.ts +4 -5
- package/dist/sqlite-square.js +29 -32
- package/dist/square-api.d.ts +1 -1
- package/dist/square-api.js +1 -1
- package/dist/square-facade.d.ts +5 -14
- package/dist/square-location.d.ts +0 -1
- package/dist/square-posting.d.ts +4 -4
- package/dist/square-posting.js +14 -16
- package/dist/square-tools.d.ts +4 -17
- package/dist/square-tools.js +52 -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 +2 -2
- package/dist/wake-transports.js +7 -9
- package/dist/wakes.js +1 -1
- package/examples/symmetric-domains.mjs +1 -2
- 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 +76 -60
- 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
|
@@ -1,88 +1,64 @@
|
|
|
1
1
|
import type { PluginTimelineItemProps } from "@getpaseo/plugin/client";
|
|
2
2
|
import { Icon } from "@getpaseo/plugin/client/react-native";
|
|
3
|
-
import { useMemo, useState } from "react";
|
|
3
|
+
import { useMemo, useRef, useState } from "react";
|
|
4
4
|
import { Pressable, ScrollView, Text, View } from "react-native";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { cardReferences, reachLabel, timeLabel } from "../shared/card-format";
|
|
6
|
+
import type { SquareMessageData } from "../shared/message";
|
|
7
|
+
import { SquareMarkdownBody } from "./markdown";
|
|
7
8
|
import { squarePalette } from "./palette";
|
|
8
9
|
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* Collapsed: two preview lines. Expanded: the whole scene, at full width.
|
|
14
|
-
* Each structured message keeps its own author, reference and body.
|
|
15
|
-
*/
|
|
10
|
+
/** The first guide reaches 4px above its text, toward the seal; the collapsed window still shows 4 lines. */
|
|
11
|
+
const GUIDE_LEAD = 4;
|
|
12
|
+
const COLLAPSED_HEIGHT = 4 * 20 + GUIDE_LEAD;
|
|
13
|
+
|
|
16
14
|
export function SquareMessage({ item, theme, layout }: PluginTimelineItemProps<SquareMessageData>) {
|
|
17
15
|
const [expanded, setExpanded] = useState(false);
|
|
18
16
|
const [hovered, setHovered] = useState(false);
|
|
17
|
+
const [overflows, setOverflows] = useState(false);
|
|
18
|
+
const scroll = useRef<ScrollView>(null);
|
|
19
19
|
const palette = useMemo(() => squarePalette(theme), [theme]);
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
: latest?.preview ?? text;
|
|
27
|
-
const authors = [...new Set(presentations?.flatMap(entry => entry.author === undefined ? [] : [entry.author]))];
|
|
28
|
-
const chorus = authors.length > 1;
|
|
29
|
-
const origins = [...new Set(presentations?.map(entry => entry.origin))];
|
|
30
|
-
const originName = origins.join(", ") || "Notification";
|
|
31
|
-
const badge = presentations?.at(-1)?.badge;
|
|
32
|
-
// Exact references never imply intervening messages; each origin keeps its own references.
|
|
33
|
-
const trailing = [presentations?.flatMap(entry => entry.ref === undefined ? []
|
|
34
|
-
: [origins.length > 1 ? `${entry.origin} ${entry.ref}` : entry.ref]).join(", "),
|
|
35
|
-
presentations && presentations.length > 1 ? String(presentations.length) : undefined].filter(Boolean).join(" · ");
|
|
36
|
-
const { accent, border, foreground, foregroundMuted: muted, surface1 } = palette.theme.colors;
|
|
37
|
-
// On web the header brightens its details under the cursor; touch keeps them quiet.
|
|
38
|
-
const detail = layout.platform === "web" && hovered ? foreground : muted;
|
|
39
|
-
const row = { flexDirection: "row" as const, alignItems: "center" as const, gap: 6 };
|
|
40
|
-
const guide = { marginLeft: 2.5, paddingLeft: palette.quote.paddingLeft, borderLeftWidth: palette.quote.borderLeftWidth, borderLeftColor: palette.quote.borderLeftColor };
|
|
20
|
+
const entries = "items" in item.data ? item.data.items : [{ text: item.data.text }];
|
|
21
|
+
const presentations = entries.filter(entry => "origin" in entry);
|
|
22
|
+
const originName = [...new Set(presentations.map(entry => entry.origin))].join(", ") || "Notification";
|
|
23
|
+
const refs = cardReferences(presentations);
|
|
24
|
+
const { accent, border, foregroundMuted: muted, surface1 } = palette.theme.colors;
|
|
25
|
+
const detail = layout.platform === "web" && hovered ? muted : palette.faint;
|
|
41
26
|
|
|
42
|
-
return (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
<View style={{
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
<View key={index} style={index > 0 ? { paddingTop: 7, borderTopWidth: palette.card.borderWidth, borderTopColor: border } : undefined}>
|
|
78
|
-
{"text" in entry
|
|
79
|
-
? <SquareMarkdownBody text={entry.text} theme={palette.theme} />
|
|
80
|
-
: <SquareMarkdownBody text={entry.body} theme={palette.theme} lead={chorus ? entry.author : undefined} />}
|
|
81
|
-
</View>
|
|
82
|
-
))}
|
|
83
|
-
</View>
|
|
84
|
-
</ScrollView>
|
|
85
|
-
)}
|
|
27
|
+
return <Pressable onPress={() => {
|
|
28
|
+
scroll.current?.scrollTo({ y: 0, animated: false });
|
|
29
|
+
setExpanded(value => !value);
|
|
30
|
+
}} accessibilityRole="button"
|
|
31
|
+
accessibilityLabel={`${expanded ? "Collapse" : "Expand"} ${originName}`} aria-expanded={expanded}
|
|
32
|
+
onHoverIn={() => setHovered(true)} onHoverOut={() => setHovered(false)}
|
|
33
|
+
style={{ backgroundColor: surface1, borderWidth: palette.card.borderWidth,
|
|
34
|
+
borderColor: border, borderRadius: palette.card.borderRadius, paddingVertical: 9, paddingHorizontal: 12 }}>
|
|
35
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 7, height: 16 }}>
|
|
36
|
+
<View style={{ width: 5, height: 5, backgroundColor: accent }} />
|
|
37
|
+
<Text numberOfLines={1} style={{ color: muted, fontSize: 11, fontWeight: "500", flexShrink: 1 }}>{originName}</Text>
|
|
38
|
+
{entries.length > 1 && <Text style={{ color: detail, fontSize: 11 }}>{entries.length} messages</Text>}
|
|
39
|
+
<View style={{ flex: 1 }} />
|
|
40
|
+
{!!refs && <Text numberOfLines={1} style={{ color: detail, fontSize: 11, fontVariant: ["tabular-nums"], flexShrink: 1 }}>{refs}</Text>}
|
|
41
|
+
<View style={{ width: 10, height: 10 }}>
|
|
42
|
+
{overflows && <Icon name={expanded ? "ChevronUp" : "ChevronDown"} size={10} color={detail} />}
|
|
43
|
+
</View>
|
|
44
|
+
</View>
|
|
45
|
+
<View style={{ marginTop: 8 - GUIDE_LEAD, marginLeft: 2 }}>
|
|
46
|
+
<ScrollView ref={scroll} style={{ maxHeight: expanded ? 260 : COLLAPSED_HEIGHT }} scrollEnabled={expanded} nestedScrollEnabled
|
|
47
|
+
onContentSizeChange={(_, height) => setOverflows(height > COLLAPSED_HEIGHT)}>
|
|
48
|
+
{entries.map((entry, index) => {
|
|
49
|
+
const structured = "origin" in entry;
|
|
50
|
+
// A mention speaks through the guide colour alone; reply and bell keep their word.
|
|
51
|
+
const direct = structured && reachLabel(entry.reach) !== undefined;
|
|
52
|
+
const reach = structured && entry.reach !== "mention" ? reachLabel(entry.reach) : undefined;
|
|
53
|
+
return <View key={index} style={{ borderLeftWidth: 1,
|
|
54
|
+
borderLeftColor: direct ? palette.directGuide : palette.quote.borderLeftColor,
|
|
55
|
+
paddingLeft: palette.quote.paddingLeft, marginTop: index > 0 ? 8 : 0, paddingTop: index > 0 ? 0 : GUIDE_LEAD }}>
|
|
56
|
+
<SquareMarkdownBody text={structured ? entry.body : entry.text} palette={palette}
|
|
57
|
+
author={structured ? entry.author : undefined} reach={reach}
|
|
58
|
+
time={structured && entry.at !== undefined ? timeLabel(entry.at) : undefined} />
|
|
59
|
+
</View>;
|
|
60
|
+
})}
|
|
61
|
+
</ScrollView>
|
|
86
62
|
</View>
|
|
87
|
-
|
|
63
|
+
</Pressable>;
|
|
88
64
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PluginTimelineTransformerContribution } from "@getpaseo/plugin/client";
|
|
2
|
-
import {
|
|
2
|
+
import { OUTPUT_DISPLAY_MAX_CHARS, outputPreview, squareCardDetailsSchema, type SquareMessageData } from "../shared/message.ts";
|
|
3
3
|
|
|
4
4
|
// Recognition is metadata-based, not a guess from a tool name or body text.
|
|
5
5
|
// The same projection applies to live events and fetched history.
|
|
@@ -12,17 +12,15 @@ export const transformSquareMessage: PluginTimelineTransformerContribution<"tool
|
|
|
12
12
|
const details = squareCardDetailsSchema.safeParse(item.metadata.details);
|
|
13
13
|
let data: SquareMessageData;
|
|
14
14
|
if (details.success) {
|
|
15
|
-
const items = details.data.items
|
|
16
|
-
|
|
17
|
-
let remaining = OUTPUT_DISPLAY_MAX_CHARS - preview.length;
|
|
15
|
+
const items = details.data.items;
|
|
16
|
+
let remaining = OUTPUT_DISPLAY_MAX_CHARS;
|
|
18
17
|
const clipped = items.reduce((sum, entry) => sum + ("text" in entry ? entry.text : entry.body).length, 0) > remaining;
|
|
19
18
|
data = { items: items.map((entry, index) => {
|
|
20
19
|
const source = "text" in entry ? entry.text : entry.body;
|
|
21
20
|
const body = clipped ? outputPreview(source, Math.floor(remaining / (items.length - index))) : source;
|
|
22
21
|
remaining -= body.length;
|
|
23
22
|
if ("text" in entry) return { text: body };
|
|
24
|
-
|
|
25
|
-
return { ...presentation, body, ...(index === items.length - 1 ? { preview } : {}) };
|
|
23
|
+
return { ...entry, body };
|
|
26
24
|
}) };
|
|
27
25
|
} else {
|
|
28
26
|
data = { text: outputPreview(item.detail.text) };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { CardPresentation } from "./card-presentation.ts";
|
|
2
|
+
|
|
3
|
+
export function reachLabel(reach?: CardPresentation["reach"]): string | undefined {
|
|
4
|
+
return reach === "mention" ? "@you" : reach === "attention" ? undefined : reach;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** Calendar days, rather than elapsed hours, keep yesterday correct across DST. */
|
|
8
|
+
export function timeLabel(at: number, now = Date.now(), compact = false): string {
|
|
9
|
+
const date = new Date(at), today = new Date(now);
|
|
10
|
+
const day = (value: Date) => `${value.getFullYear()}-${value.getMonth()}-${value.getDate()}`;
|
|
11
|
+
const clock = `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
|
12
|
+
if (day(date) === day(today)) return clock;
|
|
13
|
+
const yesterday = new Date(today);
|
|
14
|
+
yesterday.setDate(today.getDate() - 1);
|
|
15
|
+
if (day(date) === day(yesterday)) return `${compact ? "yest" : "yesterday"} ${clock}`;
|
|
16
|
+
const shortDate = compact
|
|
17
|
+
? `${String(date.getMonth() + 1).padStart(2, "0")}/${String(date.getDate()).padStart(2, "0")}`
|
|
18
|
+
: date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
19
|
+
return `${shortDate} ${clock}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Only adjacent references from the same origin can form a range. */
|
|
23
|
+
export function cardReferences(items: readonly CardPresentation[], originLabel: (origin: string) => string = origin => origin): string {
|
|
24
|
+
const multipleOrigins = new Set(items.map(item => item.origin)).size > 1;
|
|
25
|
+
const runs: { origin: string; ref: string; first?: number; last?: number }[] = [];
|
|
26
|
+
for (const item of items) {
|
|
27
|
+
if (item.ref === undefined) continue;
|
|
28
|
+
const match = /^act\/(\d+)$/.exec(item.ref);
|
|
29
|
+
const index = match ? Number(match[1]) : undefined;
|
|
30
|
+
const previous = runs.at(-1);
|
|
31
|
+
if (index !== undefined && previous?.origin === item.origin && previous.last !== undefined && previous.last + 1 === index) {
|
|
32
|
+
previous.last = index;
|
|
33
|
+
} else runs.push({ origin: item.origin, ref: item.ref, first: index, last: index });
|
|
34
|
+
}
|
|
35
|
+
return runs.map(run => `${multipleOrigins ? `${originLabel(run.origin)} ` : ""}${run.first !== run.last
|
|
36
|
+
? `act/${run.first}–${run.last}` : run.ref}`).join(", ");
|
|
37
|
+
}
|
|
@@ -4,7 +4,9 @@ export type CardPresentation = {
|
|
|
4
4
|
readonly origin: string;
|
|
5
5
|
readonly author?: string;
|
|
6
6
|
readonly ref?: string;
|
|
7
|
-
|
|
7
|
+
/** Absent on cards produced before landing time and reach were recorded. */
|
|
8
|
+
readonly at?: number;
|
|
9
|
+
readonly reach?: 'mention' | 'reply' | 'attention' | 'bell';
|
|
8
10
|
readonly body: string;
|
|
9
11
|
};
|
|
10
12
|
|
|
@@ -12,5 +14,7 @@ export function isCardPresentation(value: unknown): value is CardPresentation {
|
|
|
12
14
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
13
15
|
const item = value as Record<string, unknown>;
|
|
14
16
|
return item.kind === 'presentation' && typeof item.origin === 'string' && typeof item.body === 'string'
|
|
15
|
-
&&
|
|
17
|
+
&& (item.at === undefined || (typeof item.at === 'number' && Number.isFinite(item.at)))
|
|
18
|
+
&& (item.reach === undefined || ['mention', 'reply', 'attention', 'bell'].includes(item.reach as string))
|
|
19
|
+
&& ['author', 'ref'].every(key => item[key] === undefined || typeof item[key] === 'string');
|
|
16
20
|
}
|
|
@@ -3,12 +3,10 @@ import { isCardPresentation, type CardPresentation } from "./card-presentation.t
|
|
|
3
3
|
|
|
4
4
|
/** Aggregate client display budget, including omission lines; source history stays unchanged. */
|
|
5
5
|
export const OUTPUT_DISPLAY_MAX_CHARS = 32_000;
|
|
6
|
-
export const COLLAPSED_PREVIEW_MAX_CHARS = 1_000;
|
|
7
6
|
export function outputPreview(text: string, maxChars = OUTPUT_DISPLAY_MAX_CHARS): string {
|
|
8
7
|
if (maxChars <= 0) return "";
|
|
9
8
|
if (text.length <= maxChars) return text;
|
|
10
9
|
const middle = "\n\n… Showing the beginning and end; the middle is hidden in this preview. …\n\n";
|
|
11
|
-
// Even a small share of a multi-message budget must fit, without splitting a surrogate pair.
|
|
12
10
|
if (maxChars <= middle.length) return text.slice(0, maxChars - 1).replace(/[\uD800-\uDBFF]$/, "") + "…";
|
|
13
11
|
const headChars = Math.floor((maxChars - middle.length) / 2);
|
|
14
12
|
const head = text.slice(0, headChars).replace(/[\uD800-\uDBFF]$/, "");
|
|
@@ -16,13 +14,10 @@ export function outputPreview(text: string, maxChars = OUTPUT_DISPLAY_MAX_CHARS)
|
|
|
16
14
|
return head + middle + tail;
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
const presentationItemSchema = z.custom<CardPresentation>(isCardPresentation)
|
|
21
|
-
.and(z.object({ preview: z.string().optional() }));
|
|
17
|
+
const presentationItemSchema = z.custom<CardPresentation>(isCardPresentation);
|
|
22
18
|
export const squareCardDetailsSchema = z.object({
|
|
23
19
|
items: z.array(z.union([presentationItemSchema, z.object({ text: z.string() })])).min(1),
|
|
24
20
|
});
|
|
25
|
-
|
|
26
21
|
export const squareMessageSchema = z.union([
|
|
27
22
|
squareCardDetailsSchema,
|
|
28
23
|
z.object({ text: z.string() }),
|
package/plugins/pi/square-pi.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { Box, Markdown, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
|
|
1
|
+
import { Box, Markdown, stripTerminalSequences, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
|
|
2
|
+
import { cardReferences, reachLabel, timeLabel } from '../../dist/card-format.js';
|
|
2
3
|
import { z } from 'zod/v4';
|
|
3
4
|
|
|
4
|
-
import { Square, discover
|
|
5
|
+
import { Square, discover } from '../../dist/square-api.js';
|
|
5
6
|
import { harnessOwner } from '../../dist/owner.js';
|
|
6
7
|
import { DeliveryLedger } from '../../dist/delivery-ledger.js';
|
|
7
8
|
import { deliveryFrameOverhead, renderDeliveryReminder } from '../../dist/delivery-frame.js';
|
|
8
9
|
import { isCardPresentation } from '../../dist/card-presentation.js';
|
|
9
10
|
import { SQUARE_TOOLS, SQUARE_TOOL_INSTRUCTIONS, createSquareToolRunner, squareToolsEnabled } from '../../dist/square-tools.js';
|
|
10
|
-
import {
|
|
11
|
-
import { attachDeliverySession, detachDeliverySession } from '../../dist/session-lifecycle.js';
|
|
11
|
+
import { attachDeliverySessionRoutes, detachDeliverySession } from '../../dist/session-lifecycle.js';
|
|
12
12
|
import { createPiReceiver, sendPiMessage } from '../../dist/packages/agent-delivery/src/pi.js';
|
|
13
13
|
|
|
14
14
|
const BUDGET_CHARS = 12_000;
|
|
@@ -26,81 +26,99 @@ function markdownTheme(theme) {
|
|
|
26
26
|
heading: color('mdHeading'), link: color('mdLink'), linkUrl: color('mdLinkUrl'), code: color('mdCode'),
|
|
27
27
|
codeBlock: color('mdCodeBlock'), codeBlockBorder: color('mdCodeBlockBorder'), quote: color('mdQuote'),
|
|
28
28
|
quoteBorder: color('mdQuoteBorder'), hr: color('mdHr'), listBullet: color('mdListBullet'),
|
|
29
|
-
|
|
29
|
+
// The inline parser has already applied the body foreground; let muted win without dropping bold/link styles.
|
|
30
|
+
bold: style(theme, 'bold'), italic: (text) => fg(theme, 'muted')(style(theme, 'italic')(
|
|
31
|
+
text.replace(/\x1b\[(?:38;[\d;]+|3[0-79]|9[0-7])m/g, ''))),
|
|
30
32
|
underline: style(theme, 'underline'), strikethrough: style(theme, 'strikethrough'),
|
|
31
33
|
};
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
/** Keep
|
|
36
|
+
/** Keep references intact before spending the remaining columns on the origin. */
|
|
35
37
|
function cardHeader(left, right, width) {
|
|
36
|
-
if (!right) return truncateToWidth(left, width);
|
|
38
|
+
if (!right) return truncateToWidth(left, width, '…');
|
|
37
39
|
const rightWidth = visibleWidth(right);
|
|
38
|
-
if (rightWidth + 4 >= width) return truncateToWidth(
|
|
40
|
+
if (rightWidth + 4 >= width) return truncateToWidth(right, width, '…');
|
|
39
41
|
const leftWidth = width - rightWidth - 2;
|
|
40
|
-
|
|
42
|
+
const label = truncateToWidth(left, leftWidth, '…');
|
|
43
|
+
return `${label}${' '.repeat(width - visibleWidth(label) - rightWidth)}${right}`;
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
const escapeMarkdown = text => text.replace(/[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/g, '\\$&');
|
|
44
47
|
const shortOrigin = origin => origin.split(/[\\/]/).filter(Boolean).at(-1) || origin;
|
|
45
48
|
|
|
46
|
-
/**
|
|
47
|
-
function
|
|
48
|
-
const source = lead ? `**${escapeMarkdown(lead)}** ${text}` : text;
|
|
49
|
-
const markdown = new Markdown(source, 0, 0, markdownTheme(theme), { color: fg(theme, 'customMessageText') });
|
|
50
|
-
const guide = fg(theme, 'mdQuoteBorder')('│ ');
|
|
49
|
+
/** A final fence also covers very narrow terminals and Markdown tables/code. */
|
|
50
|
+
function bounded(component) {
|
|
51
51
|
return {
|
|
52
|
-
invalidate() {
|
|
53
|
-
render(width) {
|
|
54
|
-
const contentWidth = Math.max(1, width - 2);
|
|
55
|
-
let lines = markdown.render(contentWidth);
|
|
56
|
-
if (preview) {
|
|
57
|
-
const visible = lines.filter(line => line.trim() !== '');
|
|
58
|
-
const clipped = visible.length > 2 || text.length >= 1_000;
|
|
59
|
-
lines = visible.slice(0, 2);
|
|
60
|
-
if (clipped && lines.length) lines[lines.length - 1] = truncateToWidth(`${lines.at(-1).trimEnd()}${fg(theme, 'dim')('…')}`, contentWidth);
|
|
61
|
-
}
|
|
62
|
-
return lines.map(line => guide + line);
|
|
63
|
-
},
|
|
52
|
+
invalidate() { component.invalidate?.(); },
|
|
53
|
+
render(width) { return component.render(Math.max(1, width)).map(line => truncateToWidth(line, Math.max(0, width), '…')); },
|
|
64
54
|
};
|
|
65
55
|
}
|
|
66
56
|
|
|
67
|
-
/**
|
|
57
|
+
/** Human display only: model-facing content and delivery receipts are independent. */
|
|
68
58
|
export function renderSquareCard(message, { expanded, outputPad = 1 }, theme) {
|
|
69
59
|
if (typeof message.content !== 'string') return undefined;
|
|
70
60
|
const items = message.details?.items;
|
|
71
61
|
if (!Array.isArray(items) || items.length === 0
|
|
72
62
|
|| items.some(item => !isCardPresentation(item) && typeof item?.text !== 'string')) {
|
|
73
|
-
return new Markdown(message.content, outputPad, 0, markdownTheme(theme));
|
|
63
|
+
return bounded(new Markdown(message.content, outputPad, 0, markdownTheme(theme)));
|
|
74
64
|
}
|
|
75
65
|
const presentations = items.filter(isCardPresentation);
|
|
76
|
-
const authors = [...new Set(presentations.flatMap(item => item.author === undefined ? [] : [item.author]))];
|
|
77
66
|
const origins = [...new Set(presentations.map(item => item.origin))];
|
|
78
67
|
const originName = origins.map(shortOrigin).join(', ') || 'Notification';
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
const
|
|
68
|
+
const refs = cardReferences(presentations, shortOrigin);
|
|
69
|
+
const count = items.length > 1 ? fg(theme, 'dim')(` ${items.length} messages`) : '';
|
|
70
|
+
const left = `${fg(theme, 'accent')('■')} ${fg(theme, 'muted')(originName)}${count}`;
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
const bodyColor = fg(theme, 'customMessageText');
|
|
73
|
+
// Restore the body color after each styled attribution inside the Markdown source.
|
|
74
|
+
const bodyPrefix = bodyColor('\0').split('\0')[0];
|
|
75
|
+
const messages = items.map(item => {
|
|
76
|
+
const structured = isCardPresentation(item);
|
|
77
|
+
const label = structured ? reachLabel(item.reach) : undefined;
|
|
78
|
+
const name = structured && item.author !== undefined
|
|
79
|
+
? fg(theme, 'accent')(style(theme, 'bold')(escapeMarkdown(item.author))) : '';
|
|
80
|
+
const lead = [name, label ? fg(theme, 'warning')(label) : ''].filter(Boolean).join(`${bodyPrefix} `);
|
|
81
|
+
const body = structured ? item.body : item.text;
|
|
82
|
+
// Block Markdown keeps its own first row when there is no paragraph to run into.
|
|
83
|
+
const block = /^(?: {0,3})(?:`{3}|~{3}|#{1,6}\s|>|[-+*]\s|\d+[.)]\s|\|)/.test(body);
|
|
84
|
+
const source = lead ? `${lead}${bodyPrefix}${block ? '\n\n' : ' '}${body}` : body;
|
|
85
|
+
return {
|
|
86
|
+
markdown: new Markdown(source, 0, 0, markdownTheme(theme), { color: bodyColor }),
|
|
87
|
+
guide: fg(theme, label ? 'warning' : 'borderMuted')('│ '),
|
|
88
|
+
time: structured && item.at !== undefined ? timeLabel(item.at, now, true) : '',
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
const timeWidth = Math.max(0, ...messages.map(item => visibleWidth(item.time)));
|
|
92
|
+
const gutter = timeWidth ? timeWidth + 2 : 0;
|
|
84
93
|
const box = new Box(outputPad, 1, text => theme.bg?.('customMessageBg', text) ?? text);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
94
|
+
box.addChild({
|
|
95
|
+
invalidate() { for (const item of messages) item.markdown.invalidate(); },
|
|
96
|
+
render(width) {
|
|
97
|
+
const bodyWidth = Math.max(1, width - 2 - gutter);
|
|
98
|
+
const lines = messages.flatMap(item => {
|
|
99
|
+
const body = item.markdown.render(bodyWidth);
|
|
100
|
+
// An empty activity can still carry attribution/time.
|
|
101
|
+
if (body.length === 0 && item.time) body.push('');
|
|
102
|
+
return body.map((words, index) => ({ guide: item.guide, words, time: index === 0 ? item.time : '' }));
|
|
103
|
+
});
|
|
104
|
+
const rows = expanded ? lines : lines.filter(line => stripTerminalSequences(line.words).trim() !== '');
|
|
105
|
+
const hidden = expanded ? 0 : Math.max(0, rows.length - 4);
|
|
106
|
+
const visible = (hidden ? rows.slice(0, 4) : rows).map((line, index) => {
|
|
107
|
+
let words = truncateToWidth(line.words, bodyWidth, '…');
|
|
108
|
+
if (hidden && index === 3) {
|
|
109
|
+
words = truncateToWidth(line.words, Math.max(0, bodyWidth - 1), '');
|
|
110
|
+
words = truncateToWidth(words, visibleWidth(stripTerminalSequences(words).trimEnd()), '') + fg(theme, 'dim')('…');
|
|
111
|
+
}
|
|
112
|
+
const time = line.time
|
|
113
|
+
? ' '.repeat(Math.max(2, width - 2 - visibleWidth(words) - visibleWidth(line.time))) + fg(theme, 'dim')(line.time)
|
|
114
|
+
: '';
|
|
115
|
+
return truncateToWidth(line.guide + words + time, width, '…');
|
|
116
|
+
});
|
|
117
|
+
if (hidden) visible.push(truncateToWidth(fg(theme, 'dim')(` +${hidden} ${hidden === 1 ? 'line' : 'lines'}`), width, '…'));
|
|
118
|
+
return [cardHeader(left, fg(theme, 'dim')(refs), width), ...visible];
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
return bounded(box);
|
|
104
122
|
}
|
|
105
123
|
|
|
106
124
|
export default function squarePiExtension(pi) {
|
|
@@ -116,13 +134,10 @@ export default function squarePiExtension(pi) {
|
|
|
116
134
|
async execute(_id, args, signal, _onUpdate, ctx) {
|
|
117
135
|
const { text, isError } = await invoke(tool, args, { signal }, () => {
|
|
118
136
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
119
|
-
const env = operationEnv('pi', sessionId, process.env);
|
|
120
137
|
return {
|
|
121
138
|
identity: { owner: harnessOwner({ channel: 'pi', session: sessionId }) },
|
|
122
|
-
open: (location, control) => Square.at({
|
|
123
|
-
|
|
124
|
-
}, control),
|
|
125
|
-
discover: async (options, control) => ({ result: await discover({ cwd: ctx.cwd, env }, options, control), now: squareClock(env)() }),
|
|
139
|
+
open: (location, control) => Square.at({ cwd: ctx.cwd, path: location }, control),
|
|
140
|
+
discover: (options, control) => discover({ cwd: ctx.cwd }, options, control),
|
|
126
141
|
};
|
|
127
142
|
});
|
|
128
143
|
return { ...(isError ? { isError } : {}), content: [{ type: 'text', text }] };
|
|
@@ -245,9 +260,10 @@ export default function squarePiExtension(pi) {
|
|
|
245
260
|
await previous.ledger.close();
|
|
246
261
|
}
|
|
247
262
|
if (token !== epoch || cancellationFailed) return;
|
|
248
|
-
const
|
|
263
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
264
|
+
const owner = harnessOwner({ channel: 'pi', session: sessionId });
|
|
249
265
|
const signal = AbortSignal.timeout(5000);
|
|
250
|
-
const attached = await
|
|
266
|
+
const attached = await attachDeliverySessionRoutes(owner, process.env, [{ kind: 'pi-native', address: { sessionId } }], signal);
|
|
251
267
|
if (!attached) return;
|
|
252
268
|
if (token !== epoch) {
|
|
253
269
|
await detachDeliverySession(owner, attached.generation, process.env, AbortSignal.timeout(5000));
|
package/skills/square/SKILL.md
CHANGED
|
@@ -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
|
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod/v4';
|
|
2
|
-
declare const stateSchema: z.ZodObject<{
|
|
3
|
-
generation: z.ZodString;
|
|
4
|
-
promptId: z.ZodOptional<z.ZodString>;
|
|
5
|
-
stopEpoch: z.ZodString;
|
|
6
|
-
consumed: z.ZodBoolean;
|
|
7
|
-
ended: z.ZodBoolean;
|
|
8
|
-
}, z.core.$strip>;
|
|
9
|
-
export type ClaudeBoundary = z.infer<typeof stateSchema>;
|
|
10
|
-
/** Receiver operational facts only: no cards, claims, or payload copies. */
|
|
11
|
-
export declare function claudeBoundaryStore(session: string, env: NodeJS.ProcessEnv): {
|
|
12
|
-
start(attach: () => Promise<string | false>, signal?: AbortSignal): Promise<void>;
|
|
13
|
-
end(signal?: AbortSignal): Promise<ClaudeBoundary | undefined>;
|
|
14
|
-
prompt(promptId: string | undefined, signal?: AbortSignal): Promise<void>;
|
|
15
|
-
capture(promptId: string, signal?: AbortSignal): Promise<ClaudeBoundary | undefined>;
|
|
16
|
-
current(captured: ClaudeBoundary, signal?: AbortSignal): Promise<boolean>;
|
|
17
|
-
consume(captured: ClaudeBoundary, signal?: AbortSignal): Promise<void>;
|
|
18
|
-
watch<T>(signal: AbortSignal, run: () => Promise<T>): Promise<T>;
|
|
19
|
-
};
|
|
20
|
-
export {};
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
-
import { promises as fs } from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { z } from 'zod/v4';
|
|
5
|
-
import { deliveryLedgerLocation } from './delivery-ledger.js';
|
|
6
|
-
import { withFileLock } from './file-lock.js';
|
|
7
|
-
const stateSchema = z.object({
|
|
8
|
-
generation: z.string().min(1), promptId: z.string().min(1).optional(),
|
|
9
|
-
stopEpoch: z.string().min(1), consumed: z.boolean(), ended: z.boolean(),
|
|
10
|
-
});
|
|
11
|
-
/** Receiver operational facts only: no cards, claims, or payload copies. */
|
|
12
|
-
export function claudeBoundaryStore(session, env) {
|
|
13
|
-
const root = path.join(path.dirname(deliveryLedgerLocation(env)), 'claude-hooks');
|
|
14
|
-
const key = createHash('sha256').update(session).digest('hex');
|
|
15
|
-
const file = path.join(root, `${key}.json`);
|
|
16
|
-
async function mutate(change, signal) {
|
|
17
|
-
await fs.mkdir(root, { recursive: true, mode: 0o700 });
|
|
18
|
-
await fs.chmod(root, 0o700);
|
|
19
|
-
return withFileLock(path.join(root, `${key}.lock`), { retryMs: 10, signal: signal ?? AbortSignal.timeout(2500) }, async () => {
|
|
20
|
-
let state;
|
|
21
|
-
try {
|
|
22
|
-
state = stateSchema.parse(JSON.parse(await fs.readFile(file, 'utf8')));
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
if (error.code !== 'ENOENT')
|
|
26
|
-
throw error;
|
|
27
|
-
}
|
|
28
|
-
return change(state, async (next) => {
|
|
29
|
-
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
30
|
-
try {
|
|
31
|
-
await fs.writeFile(temporary, JSON.stringify(next), { mode: 0o600, flag: 'wx' });
|
|
32
|
-
await fs.rename(temporary, file);
|
|
33
|
-
}
|
|
34
|
-
finally {
|
|
35
|
-
await fs.rm(temporary, { force: true });
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
const matches = (state, captured) => !!state && !state.ended && !state.consumed
|
|
41
|
-
&& state.generation === captured.generation && state.promptId === captured.promptId && state.stopEpoch === captured.stopEpoch;
|
|
42
|
-
return {
|
|
43
|
-
async start(attach, signal) {
|
|
44
|
-
await mutate(async (state, save) => {
|
|
45
|
-
if (state)
|
|
46
|
-
await save({ ...state, promptId: undefined, ended: true, stopEpoch: randomUUID() });
|
|
47
|
-
const generation = await attach();
|
|
48
|
-
if (generation)
|
|
49
|
-
await save({ generation, stopEpoch: randomUUID(), consumed: false, ended: false });
|
|
50
|
-
}, signal);
|
|
51
|
-
},
|
|
52
|
-
async end(signal) {
|
|
53
|
-
return mutate(async (state, save) => {
|
|
54
|
-
if (state)
|
|
55
|
-
await save({ ...state, ended: true, stopEpoch: randomUUID() });
|
|
56
|
-
return state;
|
|
57
|
-
}, signal);
|
|
58
|
-
},
|
|
59
|
-
async prompt(promptId, signal) {
|
|
60
|
-
await mutate(async (state, save) => {
|
|
61
|
-
if (state && !state.ended)
|
|
62
|
-
await save({ ...state, promptId, stopEpoch: randomUUID(), consumed: false });
|
|
63
|
-
}, signal);
|
|
64
|
-
},
|
|
65
|
-
capture(promptId, signal) {
|
|
66
|
-
return mutate(async (state) => state && !state.ended && !state.consumed && state.promptId === promptId ? state : undefined, signal);
|
|
67
|
-
},
|
|
68
|
-
current(captured, signal) { return mutate(async (state) => matches(state, captured), signal); },
|
|
69
|
-
async consume(captured, signal) {
|
|
70
|
-
await mutate(async (state, save) => {
|
|
71
|
-
if (matches(state, captured))
|
|
72
|
-
await save({ ...state, consumed: true });
|
|
73
|
-
}, signal);
|
|
74
|
-
},
|
|
75
|
-
async watch(signal, run) {
|
|
76
|
-
await fs.mkdir(root, { recursive: true, mode: 0o700 });
|
|
77
|
-
const lock = path.join(root, `${key}.watch.lock`);
|
|
78
|
-
const handle = await fs.open(lock, 'a', 0o600);
|
|
79
|
-
await handle.close();
|
|
80
|
-
// A newer Stop waits through bounded shutdown; it must not skip a busy lock.
|
|
81
|
-
return withFileLock(lock, { retryMs: 10, signal }, run);
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
}
|
package/dist/claude-hook.d.ts
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
export declare function runClaudeHook(text: string, env?: NodeJS.ProcessEnv, sink?: (value: string) => void | Promise<void>): Promise<string>;
|
|
2
|
-
export declare function runClaudeWatch(text: string, env?: NodeJS.ProcessEnv, signal?: AbortSignal, output?: (frame: string) => Promise<void>): Promise<string>;
|