@astrosheep/square 0.3.95 → 0.3.96
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/attention-presentation.d.ts +5 -4
- package/dist/attention-presentation.js +18 -8
- package/dist/boundary-presentation.js +17 -8
- package/dist/channel.d.ts +72 -0
- package/dist/channel.js +86 -0
- package/dist/claude-delivery.js +2 -1
- package/dist/delivery.js +2 -2
- package/dist/inbox.js +3 -1
- package/dist/mcp.js +65 -63
- package/dist/model.d.ts +6 -0
- package/dist/model.js +19 -0
- package/dist/ports.d.ts +3 -0
- package/dist/presentation-operations.d.ts +1 -0
- package/dist/sqlite-square.d.ts +3 -1
- package/dist/sqlite-square.js +54 -2
- package/dist/square-core.d.ts +1 -0
- package/dist/square-domain.js +22 -7
- package/dist/square-facade.d.ts +20 -2
- package/dist/square-sqlite.js +12 -3
- package/dist/square-tools.d.ts +1 -0
- package/dist/square-tools.js +9 -1
- package/dist/views.js +1 -0
- package/package.json +5 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/paseo/client/marble.ts +7 -0
- package/plugins/paseo/client/markdown.tsx +65 -9
- package/plugins/paseo/client/palette.ts +43 -20
- package/plugins/paseo/client/square-message.tsx +57 -48
- package/plugins/paseo/shared/message.ts +1 -2
- package/plugins/pi/square-pi.js +6 -4
package/dist/sqlite-square.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { formatActivityId } from './square-core.js';
|
|
1
|
+
import { formatActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
|
|
2
2
|
import { assertCurrentSeat } from './square-domain.js';
|
|
3
3
|
import { canonicalPath } from './canonical-path.js';
|
|
4
4
|
import { validateMetadataMatch, validateDeferralDuration, validateDeferralId } from './delivery-deferral.js';
|
|
5
5
|
import { squareLocation } from './square-location.js';
|
|
6
6
|
import crypto from 'node:crypto';
|
|
7
7
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
8
|
-
import { InternalSquareError, SquareError, isSquareError, nameKey, validateName } from './model.js';
|
|
8
|
+
import { activityMetadataHasContent, InternalSquareError, SquareError, isSquareError, nameKey, sameName, validateActivityMetadata, validateName } from './model.js';
|
|
9
9
|
import { coreDone, coreIgnore, coreListen, coreListening, coreUnlisten, decideAct, decideJoin, resolveKnownName, validateDoneBody } from './decisions.js';
|
|
10
10
|
import { WATCH_HEARTBEAT_MS, inSquareCount, presenceAnchor } from './runtime.js';
|
|
11
11
|
import { catchLimit, decideCatch, observeCatchSelection, selectCatchPage } from './catch-decisions.js';
|
|
12
12
|
import { expressionContext } from './activity-feed.js';
|
|
13
13
|
import { expressHint } from './hints.js';
|
|
14
|
+
import { ATTENTION_BODY_MAX } from './attention-presentation.js';
|
|
14
15
|
import { asSeatRef, participantAuthority, requireOwner, requireJoinName } from './seats.js';
|
|
15
16
|
import { recoveryObservation, entryPresentation, snapshot, queryStatus, queryParticipants, catchPresence, parseRequiredActivityId, toPublicActivity, publicActivities, selectHistory, NON_READ, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
|
|
16
17
|
import { sessionInboxCounts } from './inbox.js';
|
|
@@ -133,6 +134,7 @@ export class SqliteSquare {
|
|
|
133
134
|
this.owner = owner;
|
|
134
135
|
this.seat = seat;
|
|
135
136
|
}
|
|
137
|
+
get generation() { return this.seat.generation; }
|
|
136
138
|
async express(bodyOrInput, optionsOrControl = {}, operationControl) {
|
|
137
139
|
const square = this.owner;
|
|
138
140
|
const input = typeof bodyOrInput === 'object' && bodyOrInput !== null ? bodyOrInput : undefined;
|
|
@@ -382,6 +384,56 @@ export class SqliteSquare {
|
|
|
382
384
|
}, control?.signal)).value;
|
|
383
385
|
}
|
|
384
386
|
};
|
|
387
|
+
/** Direct messages use explicit recipients, not participant expression admission or body attention. */
|
|
388
|
+
async send(input, control) {
|
|
389
|
+
checkControl(control);
|
|
390
|
+
const { from, to, body, truncate = ATTENTION_BODY_MAX } = input;
|
|
391
|
+
if (typeof from !== 'string')
|
|
392
|
+
throw new SquareError('invalid_args', 'Channel sender must be a name.');
|
|
393
|
+
validateName(from);
|
|
394
|
+
const targets = typeof to === 'string' ? [to] : to;
|
|
395
|
+
if (!Array.isArray(targets) || targets.length < 1 || targets.length > MAX_IDENTITY_SET_SIZE)
|
|
396
|
+
throw new SquareError('invalid_args', `Channel needs between 1 and ${MAX_IDENTITY_SET_SIZE} recipients.`);
|
|
397
|
+
const unique = [];
|
|
398
|
+
for (const target of targets) {
|
|
399
|
+
if (typeof target !== 'string')
|
|
400
|
+
throw new SquareError('invalid_args', 'Channel recipients must be names.');
|
|
401
|
+
validateName(target);
|
|
402
|
+
if (!unique.some(name => sameName(name, target)))
|
|
403
|
+
unique.push(target);
|
|
404
|
+
}
|
|
405
|
+
if (typeof body !== 'string' || body.trim() === '')
|
|
406
|
+
throw new SquareError('invalid_args', 'Channel body must be non-empty.');
|
|
407
|
+
if (truncate !== false && (!Number.isSafeInteger(truncate) || truncate < 1))
|
|
408
|
+
throw new SquareError('invalid_args', 'Channel truncate must be false or a positive integer.');
|
|
409
|
+
const fullBody = body.replace(/\r\n/g, '\n');
|
|
410
|
+
const preview = truncate !== false && fullBody.length > truncate
|
|
411
|
+
? `${fullBody.slice(0, truncate).trimEnd()}… (truncated ${fullBody.length - truncate} chars)`
|
|
412
|
+
: undefined;
|
|
413
|
+
const delivery = preview === undefined ? { mode: 'direct' } : { mode: 'direct', preview };
|
|
414
|
+
const metadata = validateActivityMetadata(input.metadata);
|
|
415
|
+
const { stored, resolvedTo } = (await this.artifact.transactAs({ kind: 'send', actor: from }, facts => {
|
|
416
|
+
const standing = facts.roster().filter(p => p.joined), resolvedTo = [], missing = [];
|
|
417
|
+
for (const target of unique) {
|
|
418
|
+
const recipient = standing.find(p => sameName(p.name, target));
|
|
419
|
+
if (recipient === undefined || facts.seat(recipient.name)?.attachment !== 'attached')
|
|
420
|
+
missing.push(target);
|
|
421
|
+
else
|
|
422
|
+
resolvedTo.push(recipient.name);
|
|
423
|
+
}
|
|
424
|
+
if (missing.length > 0)
|
|
425
|
+
throw new SquareError('invalid_args', `Channel recipients are not standing: ${missing.join(', ')}`, {
|
|
426
|
+
reason: 'not_standing', targets: missing, activityUnsent: true,
|
|
427
|
+
});
|
|
428
|
+
return { stored: facts.land({ kind: 'say', actor: from, at: this.clock(), body: fullBody,
|
|
429
|
+
...(activityMetadataHasContent(metadata) ? { metadata } : {}), delivery, mentions: resolvedTo }), resolvedTo };
|
|
430
|
+
}, control?.signal)).value;
|
|
431
|
+
if (this.wakeTransport !== undefined)
|
|
432
|
+
await deliverPending({ artifact: this.artifact, transport: this.wakeTransport, location: this.deliveryLocation,
|
|
433
|
+
activity: stored.index, now: stored.at }).catch(() => undefined);
|
|
434
|
+
return { id: formatActivityId(stored.index), at: stored.at, from: stored.actor, to: resolvedTo, body: preview ?? fullBody,
|
|
435
|
+
...(stored.kind !== 'say' || stored.metadata === undefined ? {} : { metadata: stored.metadata }) };
|
|
436
|
+
}
|
|
385
437
|
async participant(name, { owner }) {
|
|
386
438
|
validateName(name);
|
|
387
439
|
requireOwner(owner);
|
package/dist/square-core.d.ts
CHANGED
package/dist/square-domain.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import { SquareError, formatActivityId, parseActivityId, sameName, validateOwner, validateName } from './model.js';
|
|
1
|
+
import { SquareError, formatActivityId, parseActivityId, sameName, validateActivityDelivery, validateOwner, validateName } from './model.js';
|
|
2
2
|
import { resolveAudience } from './attention.js';
|
|
3
3
|
/** The single landing transition, shared by both adapters and explicit rebuild. */
|
|
4
4
|
export function deriveLanding(activity, facts) {
|
|
5
|
+
if (activity.kind === 'say' && activity.delivery?.mode === 'direct') {
|
|
6
|
+
const audience = [];
|
|
7
|
+
for (const mention of activity.mentions ?? []) {
|
|
8
|
+
const recipient = facts.standing.find(item => sameName(item.name, mention))?.name;
|
|
9
|
+
if (recipient !== undefined && !audience.some(item => sameName(item.recipient, recipient)))
|
|
10
|
+
audience.push({ recipient, route: 'mention' });
|
|
11
|
+
}
|
|
12
|
+
return { activity, audience };
|
|
13
|
+
}
|
|
5
14
|
const audience = activity.kind === 'say' ? resolveAudience({
|
|
6
15
|
actor: activity.actor, recipients: facts.standing.map((item) => item.name),
|
|
7
16
|
mentions: activity.mentions, replyAuthor: facts.replyAuthor, reach: activity.reach,
|
|
@@ -60,15 +69,15 @@ export function wrapSquareWrite(read, writes, authority) {
|
|
|
60
69
|
throw new SquareError('invalid_args', 'Artifact writes require an explicit authority.');
|
|
61
70
|
const { applyLanding, ...mutations } = writes;
|
|
62
71
|
const seated = authority.kind === 'participant' || authority.kind === 'host-cleanup';
|
|
63
|
-
const expectedName = seated ? authority.seat?.participant : authority.participant;
|
|
64
|
-
if (!['participant', 'host-cleanup', 'join', 'takeover'].includes(authority.kind) || typeof expectedName !== 'string')
|
|
72
|
+
const expectedName = seated ? authority.seat?.participant : authority.kind === 'send' ? authority.actor : authority.participant;
|
|
73
|
+
if (!['participant', 'host-cleanup', 'join', 'takeover', 'send'].includes(authority.kind) || typeof expectedName !== 'string')
|
|
65
74
|
throw new SquareError('invalid_args', 'Artifact write authority is malformed.');
|
|
66
75
|
validateName(expectedName);
|
|
67
76
|
if (seated) {
|
|
68
77
|
validateOwner(authority.seat.owner);
|
|
69
78
|
validateGeneration(authority.seat.generation);
|
|
70
79
|
}
|
|
71
|
-
else {
|
|
80
|
+
else if (authority.kind !== 'send') {
|
|
72
81
|
validateOwner(authority.owner);
|
|
73
82
|
if (authority.kind === 'takeover')
|
|
74
83
|
validateGeneration(authority.expectedGeneration);
|
|
@@ -100,7 +109,7 @@ export function wrapSquareWrite(read, writes, authority) {
|
|
|
100
109
|
reconnectSeat = seat;
|
|
101
110
|
}
|
|
102
111
|
}
|
|
103
|
-
else {
|
|
112
|
+
else if (authority.kind === 'takeover') {
|
|
104
113
|
const current = read.seat(expectedName);
|
|
105
114
|
if (current === undefined || current.generation !== authority.expectedGeneration)
|
|
106
115
|
throw new SquareError('already_joined', 'The participant seat changed before takeover.');
|
|
@@ -114,7 +123,13 @@ export function wrapSquareWrite(read, writes, authority) {
|
|
|
114
123
|
validateName(actor);
|
|
115
124
|
if (!sameName(actor, expectedName))
|
|
116
125
|
throw new SquareError('already_joined', 'Activity actor does not match the artifact authority.');
|
|
117
|
-
if (
|
|
126
|
+
if (activity.kind === 'say')
|
|
127
|
+
validateActivityDelivery(activity.delivery);
|
|
128
|
+
if (authority.kind === 'send') {
|
|
129
|
+
if (activity.kind !== 'say' || activity.delivery?.mode !== 'direct' || activity.reach !== undefined || activity.reply !== undefined)
|
|
130
|
+
throw new SquareError('invalid_args', 'Send authority may only land direct messages without bell or reply.');
|
|
131
|
+
}
|
|
132
|
+
else if (authority.kind === 'participant') {
|
|
118
133
|
assertSeat(authority.seat);
|
|
119
134
|
if (activity.kind === 'join')
|
|
120
135
|
throw new SquareError('invalid_args', 'A participant seat can only be re-established by join.');
|
|
@@ -150,7 +165,7 @@ export function wrapSquareWrite(read, writes, authority) {
|
|
|
150
165
|
const stored = { ...activity, index };
|
|
151
166
|
applyLanding(deriveLanding(stored, landingFacts(read, stored)));
|
|
152
167
|
if (stored.kind === 'join') {
|
|
153
|
-
if (
|
|
168
|
+
if (authority.kind !== 'join' && authority.kind !== 'takeover')
|
|
154
169
|
throw new SquareError('invalid_args', 'Seat authority cannot establish another seat.');
|
|
155
170
|
const seat = {
|
|
156
171
|
participant: stored.actor,
|
package/dist/square-facade.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { DeliveryDeferral, MetadataMatch } from './delivery-deferral.js';
|
|
2
2
|
export type { DeliveryDeferral, MetadataMatch, MetadataScalar } from './delivery-deferral.js';
|
|
3
3
|
import type { ActivityId, Perception, Reach } from './square-core.js';
|
|
4
|
-
import type { Owner, StoredAct, WakeAddress, ActivityMetadata, DirectedNotificationRoute } from './model.js';
|
|
4
|
+
import type { Owner, StoredAct, WakeAddress, ActivityMetadata, ActivityDelivery, DirectedNotificationRoute } from './model.js';
|
|
5
5
|
import type { WakeTransportPort, SquareResourceOwner } from './ports.js';
|
|
6
6
|
export type { ActivityId, Perception, Reach } from './square-core.js';
|
|
7
|
-
export type { Owner, WakeAddress, ActivityMetadata, JsonValue, DirectedNotificationRoute } from './model.js';
|
|
7
|
+
export type { Owner, WakeAddress, ActivityMetadata, ActivityDelivery, JsonValue, DirectedNotificationRoute } from './model.js';
|
|
8
8
|
/** Indexed archive event, including read bookkeeping. */
|
|
9
9
|
export type ArchiveRecord = Readonly<StoredAct>;
|
|
10
10
|
export interface Activity {
|
|
@@ -14,6 +14,7 @@ export interface Activity {
|
|
|
14
14
|
readonly actor: string;
|
|
15
15
|
readonly body?: string;
|
|
16
16
|
readonly metadata?: ActivityMetadata;
|
|
17
|
+
readonly delivery?: ActivityDelivery;
|
|
17
18
|
readonly mentions: readonly string[];
|
|
18
19
|
readonly reach?: Reach;
|
|
19
20
|
readonly target?: string;
|
|
@@ -49,6 +50,22 @@ export interface ExpressResult {
|
|
|
49
50
|
readonly hint?: string;
|
|
50
51
|
readonly observation?: ExpressObservation;
|
|
51
52
|
}
|
|
53
|
+
export type ChannelTruncate = number | false;
|
|
54
|
+
export interface ChannelSendInput {
|
|
55
|
+
readonly from: string;
|
|
56
|
+
readonly to: string | readonly string[];
|
|
57
|
+
readonly body: string;
|
|
58
|
+
readonly truncate?: ChannelTruncate;
|
|
59
|
+
readonly metadata?: ActivityMetadata;
|
|
60
|
+
}
|
|
61
|
+
export interface ChannelSendResult {
|
|
62
|
+
readonly id: ActivityId;
|
|
63
|
+
readonly at: number;
|
|
64
|
+
readonly from: string;
|
|
65
|
+
readonly to: readonly string[];
|
|
66
|
+
readonly body: string;
|
|
67
|
+
readonly metadata?: ActivityMetadata;
|
|
68
|
+
}
|
|
52
69
|
export interface ListenerChangeResult {
|
|
53
70
|
readonly activity: Activity | null;
|
|
54
71
|
readonly observation?: ListenerObservation;
|
|
@@ -170,6 +187,7 @@ export interface JoinResult {
|
|
|
170
187
|
}
|
|
171
188
|
export interface Participant {
|
|
172
189
|
readonly name: string;
|
|
190
|
+
readonly generation: ActivityId;
|
|
173
191
|
express(body: string | undefined, options?: ExpressOptions, control?: OperationControl): Promise<ExpressResult>;
|
|
174
192
|
express(input: ExpressInput, control?: OperationControl): Promise<ExpressResult>;
|
|
175
193
|
listen(target: string, options?: ObservationOptions, control?: OperationControl): Promise<ListenerChangeResult>;
|
package/dist/square-sqlite.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DatabaseSync as MemoryDatabase } from 'node:sqlite';
|
|
2
|
-
import { SquareError, nameKey, isWakeRouteKind, validateOwner, validateActivityMetadata, activityMetadataHasContent } from './model.js';
|
|
2
|
+
import { SquareError, nameKey, isWakeRouteKind, validateOwner, validateActivityMetadata, validateActivityDelivery, activityMetadataHasContent } from './model.js';
|
|
3
3
|
import { validateMetadataMatch, matchingDeferrals, validateDeferralId } from './delivery-deferral.js';
|
|
4
4
|
import { deriveLanding, landingFacts } from './square-domain.js';
|
|
5
5
|
import { claimAttempt, prepareAttempt, settleAttempt } from './delivery-attempts.js';
|
|
@@ -269,7 +269,16 @@ export function validateStoredActivity(value) {
|
|
|
269
269
|
string(a.actor, true);
|
|
270
270
|
switch (a.kind) {
|
|
271
271
|
case 'say':
|
|
272
|
-
shape(a, [...common], ['body', 'mentions', 'reach', 'reply', 'metadata']);
|
|
272
|
+
shape(a, [...common], ['body', 'mentions', 'reach', 'reply', 'metadata', 'delivery']);
|
|
273
|
+
if (Object.hasOwn(a, 'delivery')) {
|
|
274
|
+
requireFact(a.delivery !== undefined, 'say delivery is malformed.');
|
|
275
|
+
try {
|
|
276
|
+
validateActivityDelivery(a.delivery);
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
throw invalidArtifact('say delivery is malformed.');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
273
282
|
if (Object.hasOwn(a, 'body'))
|
|
274
283
|
string(a.body);
|
|
275
284
|
if (Object.hasOwn(a, 'metadata')) {
|
|
@@ -311,7 +320,7 @@ export function validateStoredActivity(value) {
|
|
|
311
320
|
function activity(row) {
|
|
312
321
|
const h = head(row), payload = object(json(row.payload_json));
|
|
313
322
|
// Payload cannot shadow indexed structural columns.
|
|
314
|
-
const allowed = h.kind === 'say' ? ['body', 'mentions', 'metadata'] : h.kind === 'done' ? ['body'] : h.kind === 'read' ? ['through'] : h.kind === 'listen' || h.kind === 'ignore' || h.kind === 'unlisten' ? ['target'] : [];
|
|
323
|
+
const allowed = h.kind === 'say' ? ['body', 'mentions', 'metadata', 'delivery'] : h.kind === 'done' ? ['body'] : h.kind === 'read' ? ['through'] : h.kind === 'listen' || h.kind === 'ignore' || h.kind === 'unlisten' ? ['target'] : [];
|
|
315
324
|
requireFact(Object.keys(payload).every(k => allowed.includes(k)));
|
|
316
325
|
return immutable(validateStoredActivity({ ...h, ...payload }));
|
|
317
326
|
}
|
package/dist/square-tools.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ interface ToolDefinition {
|
|
|
13
13
|
/** Human text identical to what the CLI prints; domain receipts stay inside the adapter. */
|
|
14
14
|
run(square: Square, args: JsonObject, control: OperationControl, identity: ToolIdentity): Promise<string>;
|
|
15
15
|
}
|
|
16
|
+
export declare function squareToolsEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
16
17
|
export declare const SQUARE_TOOL_INSTRUCTIONS = "Join once. Then express to speak, and catch to take in what reached you. History looks back without taking anything in.";
|
|
17
18
|
export declare const SQUARE_TOOLS: readonly ToolDefinition[];
|
|
18
19
|
export declare function squareToolFailure(error: unknown, signal?: AbortSignal): JsonObject;
|
package/dist/square-tools.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
|
-
import { isSquareError } from './model.js';
|
|
2
|
+
import { isSquareError, SquareError } from './model.js';
|
|
3
3
|
import { requireOwner, requireJoinName, selectOwnSeat, CATCH_DEFAULT_LIMIT } from './square-api.js';
|
|
4
4
|
import { renderCatchOutcome, renderExpression, renderFailureLines, renderHistoryResult, renderJoinResult, renderLifecycleResult, renderListenerChange, renderStatusResult, } from './operation-presentation.js';
|
|
5
5
|
const location = z.string().optional().describe('Which square: a .square file path. Relative paths start from the working directory. Leave it out for the configured square.');
|
|
@@ -19,6 +19,14 @@ function tool(name, description, shape, run, render) {
|
|
|
19
19
|
return render(receipt, operationArgs) ?? '';
|
|
20
20
|
} };
|
|
21
21
|
}
|
|
22
|
+
export function squareToolsEnabled(env = process.env) {
|
|
23
|
+
const raw = env.SQUARE_TOOLS?.trim().toLowerCase();
|
|
24
|
+
if (raw === undefined || raw === '' || raw === '1' || raw === 'true')
|
|
25
|
+
return true;
|
|
26
|
+
if (raw === '0' || raw === 'false' || raw === 'none')
|
|
27
|
+
return false;
|
|
28
|
+
throw new SquareError('invalid_args', 'Invalid SQUARE_TOOLS: expected 0, 1, true, false, or none.');
|
|
29
|
+
}
|
|
22
30
|
export const SQUARE_TOOL_INSTRUCTIONS = 'Join once. Then express to speak, and catch to take in what reached you. History looks back without taking anything in.';
|
|
23
31
|
const target = z.string().describe('The participant to turn toward or away from.');
|
|
24
32
|
export const SQUARE_TOOLS = [
|
package/dist/views.js
CHANGED
|
@@ -19,6 +19,7 @@ export function toPublicActivity(stored) {
|
|
|
19
19
|
...(stored.kind === 'say' && stored.reach !== undefined ? { reach: stored.reach } : {}),
|
|
20
20
|
...('body' in stored && stored.body !== undefined ? { body: stored.body } : {}),
|
|
21
21
|
...(stored.kind === 'say' && stored.metadata !== undefined ? { metadata: stored.metadata } : {}),
|
|
22
|
+
...(stored.kind === 'say' && stored.delivery !== undefined ? { delivery: stored.delivery } : {}),
|
|
22
23
|
mentions: stored.kind === 'say' ? stored.mentions ?? [] : [], ...('target' in stored ? { target: stored.target } : {}),
|
|
23
24
|
...(stored.kind === 'say' && stored.reply !== undefined ? { reply: formatActivityId(stored.reply) } : {}) };
|
|
24
25
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrosheep/square",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.96",
|
|
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": {
|
|
@@ -51,6 +51,10 @@
|
|
|
51
51
|
"ws": "^8.21.3"
|
|
52
52
|
},
|
|
53
53
|
"exports": {
|
|
54
|
+
"./channel": {
|
|
55
|
+
"types": "./dist/channel.d.ts",
|
|
56
|
+
"default": "./dist/channel.js"
|
|
57
|
+
},
|
|
54
58
|
"./delivery": {
|
|
55
59
|
"types": "./dist/delivery-primitives.d.ts",
|
|
56
60
|
"default": "./dist/delivery-primitives.js"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seamless 512px stone tiles, rendered once offline (float canvas noise, dithered, JPEG):
|
|
3
|
+
* veinless honed stone — only the milky drift, on muted grey-white (light) or charcoal (dark).
|
|
4
|
+
* Kept tiny so they inline as data URIs.
|
|
5
|
+
*/
|
|
6
|
+
export const MARBLE_LIGHT = "data:image/jpeg;base64,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";
|
|
7
|
+
export const MARBLE_DARK = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAYEBAUEBAYFBQUGBgYHCQ4JCQgICRINDQoOFRIWFhUSFBQXGiEcFxgfGRQUHScdHyIjJSUlFhwpLCgkKyEkJST/2wBDAQYGBgkICREJCREkGBQYJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCT/wAARCAIAAgADASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAECCP/EABYQAQEBAAAAAAAAAAAAAAAAAAABEf/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A54AVFEAUQAAAAAAAAAAAAAAAABVRQWKkAURVBFARQQAAAAQAEABEUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFiLAVUigKCgAgAgKIKKCIAAJUVAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUAVWV0GtNTTQaE00FEFFQEABQAQQABFARGkBMFKCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAumoA1oigoigAgKIoAACKgAAAKCFioDNFqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKigoCgaIgoigogAAAAAqAKgAgqUEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYJFBQAEVAAAU1AFQAAAFRQAUESqKMi1EAAAXDAQXAEFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACKkagAoCCgIACAAgAKIoCooACgAgYYqKJgogAAAqiIogyKAguIAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAEaZUGgAAAEUBEVAQVAURQURQAAAAUAABRBRADAAVAQVAQVARQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAUAFEFF0QQUEAABAAAAURQUAAAFEUAFUAEAAAAEFQERUAAARUAAAAAAAAAAAAAAAAAAAAAAAAAAAVAAAFEUAEAAAAAAAVFBQAAAAAVWVBTTUBRFAAARUBBUBBUBAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAVFBRFAABAAUAFAAABRAFQQFSiACAAAAAAAAAAAAAAAAEBQigIoCCmAiNICAAAAAAAAAAAAAAAAAAAAsEUAAAAFEUFEAVABRAAAEqKgAAAAAAAAAAAAAAAAAALFSAKAAqAKgAgqAgAAAAAAAAAAAAAAAAAAAAAAAKIoKIoCooCKAiKAiKAgAAAAAAAAAAAAAAAAACoAqoAogCgAAAlRagAAAAAAAAAAAAAAAAAAAAAAAAKqRQFAAEARUAQAAAAAAAAAAAAAAAAAAAAAAAXRAFNQBoQAqAAAAAAAAAAAAAAAAAAAAAAAAACxUgDQmgACglVEBAAAAAAAAAAAAAAAAAAAAAAAAAAABRFBAAAAAAAAAAAAAAAAAAAAAAAAAAAAU1DQXTUAXRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVFBBQEFAQAAAAAAAAAAAAAAAFAAABBUAAABQRQAAAAAAARUAAAAAAAAAAAAAAAAAAAAABQAUEFAQVAQVAAAAAAAAAAAAAAAUAAAAAAxTATBQEFQAAAEBRAAAAAAAAAAAAAAAAAAAAAAABUUBUUAUBBQEwxQGcRpAQAAAAAAAAABUAURQBQAFAAABVGRUQRGqgIAAAAAAAAAAAAAAAAAAAAAAAAAChi4AKAAqiAIAAIACIoCCoAAAAAAACgAsAXBYCKKCC4AgoCIoCM1pFGaLYiAAAAAAAAAAAAAACooCCgIKmACgILhgNYYoACggACLUABAAAQAEAAAAAAFAAAFiKCxYiwFCAAAAoohioCJWkBmo0iCIoCCgIAAAAAAAACgAoIooJiY0YDKrgCLgqigoIKAiNIDIqICKAgUARQERQEAAUAFAAUBBQBQAVBRQAFQBUABFQBFQERRBEVAAAAAAAAAFiKCgoAKAAAACKALFZVRQAEBBEVKCAKACACAIqAAoAAKqAKAACgAoIKigoIAgoAIAAAIoJiiCVlpKCAAAAAAAAKigsVAFABRFABFFEEFisqCiCioCCBUAAAAAQKCAAKigAAKigoAKEVQBAURQEAAAAQQFQBUAAAERQERUAAAAAABRFBQAAAFQBRNUARQFRQAABACoACCAuiAKgAAAAAoACgAoAoigCCiiAKIIAAAgCgAAAJVQBABKAAAAAAAAqAKqQBQAAQFEAURQFZAaEAUQARagCAAAAAAAAAAqKAqAKqKACqIoAgCAigIAACAqoAoigIqAgIAAAAAAAAAAAACiAKgAaaALpqACoAqooAACCUAAAAAAAAAAAABUUFVFAUAEVAAFAUQRFAQVAAAAAEABFQAAAAAAAAAAAAAAAAAAAAAAFAAAARUAAAAAAAAAAAAAABqKysBoRQAFEAQAVRBQEMUQQAERUAQAAAAAAAAAAAAAAAAAAAAAAAf/9k=";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useMemo, type ReactNode } from "react";
|
|
2
|
-
import { Platform, Text, View, type TextStyle
|
|
2
|
+
import { Platform, StyleSheet, Text, View, type TextStyle } from "react-native";
|
|
3
3
|
import type { PluginTheme } from "@getpaseo/plugin";
|
|
4
4
|
import { Lexer, lexer, type Token, type Tokens } from "marked";
|
|
5
5
|
|
|
@@ -9,6 +9,7 @@ function makeStyles(colors: PluginTheme["colors"]) {
|
|
|
9
9
|
return {
|
|
10
10
|
block: { gap: 6 },
|
|
11
11
|
text: { color: colors.foreground, fontSize: 13, lineHeight: 19 },
|
|
12
|
+
lead: { color: colors.accent, fontWeight: "600" as const },
|
|
12
13
|
strong: { fontWeight: "500" as const, color: colors.foreground },
|
|
13
14
|
em: { color: colors.foregroundMuted, fontStyle: "italic" as const },
|
|
14
15
|
del: { color: colors.foregroundMuted, textDecorationLine: "line-through" as const },
|
|
@@ -117,7 +118,7 @@ function renderBlocks(tokens: Token[], styles: Styles, prefix: string, textStyle
|
|
|
117
118
|
default: {
|
|
118
119
|
const block = token as Tokens.Generic;
|
|
119
120
|
return (
|
|
120
|
-
<Text key={key} style={[styles.text, textStyle]}>
|
|
121
|
+
<Text key={key} selectable={Platform.OS === "web"} style={[styles.text, textStyle]}>
|
|
121
122
|
{renderInlines(block.tokens ?? Lexer.lexInline(block.text ?? block.raw), styles, key)}
|
|
122
123
|
</Text>
|
|
123
124
|
);
|
|
@@ -126,24 +127,79 @@ function renderBlocks(tokens: Token[], styles: Styles, prefix: string, textStyle
|
|
|
126
127
|
});
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
/** The speaker run into the start of a line: the seal's blue, then the words. */
|
|
131
|
+
function Lead({ name, styles }: { name: string; styles: Styles }) {
|
|
132
|
+
return <Text style={styles.lead}>{name}{" "}</Text>;
|
|
133
|
+
}
|
|
134
|
+
|
|
129
135
|
/** One line (or a few) of Markdown, inline only: the collapsed preview on a notification card. */
|
|
130
|
-
export function SquareInline({ text, theme, numberOfLines,
|
|
136
|
+
export function SquareInline({ text, theme, numberOfLines, lead }: {
|
|
131
137
|
text: string;
|
|
132
138
|
theme: PluginTheme;
|
|
133
139
|
numberOfLines?: number;
|
|
134
|
-
|
|
140
|
+
lead?: string;
|
|
135
141
|
}) {
|
|
136
142
|
const styles = useMemo(() => makeStyles(theme.colors), [theme.colors]);
|
|
137
143
|
return (
|
|
138
|
-
<Text numberOfLines={numberOfLines} style={
|
|
144
|
+
<Text numberOfLines={numberOfLines} style={styles.text}>
|
|
145
|
+
{lead !== undefined && <Lead name={lead} styles={styles} />}
|
|
139
146
|
{renderInlines(Lexer.lexInline(text), styles, "inline")}
|
|
140
147
|
</Text>
|
|
141
148
|
);
|
|
142
149
|
}
|
|
143
150
|
|
|
144
|
-
/** A
|
|
145
|
-
|
|
151
|
+
/** A paragraph that is one *emphasis* and nothing else is an action, not a line. */
|
|
152
|
+
const isAction = (token: Token): token is Tokens.Paragraph =>
|
|
153
|
+
token.type === "paragraph" && children(token).length === 1 && children(token)[0]!.type === "em";
|
|
154
|
+
|
|
155
|
+
/** A bell or an action is stage direction: a caption on a hairline across the full width. */
|
|
156
|
+
export function SquareCue({ label, theme }: { label: ReactNode; theme: PluginTheme }) {
|
|
157
|
+
const rule = { flex: 1, height: StyleSheet.hairlineWidth, backgroundColor: theme.colors.border };
|
|
158
|
+
return (
|
|
159
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 8, marginVertical: 2 }}>
|
|
160
|
+
<View style={rule} />
|
|
161
|
+
<Text style={{ color: theme.colors.foregroundMuted, fontSize: 11.5, lineHeight: 16, letterSpacing: 0.4 }}>{label}</Text>
|
|
162
|
+
<View style={rule} />
|
|
163
|
+
</View>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* A body of Markdown: no envelope, no route line — the words only, at full width.
|
|
169
|
+
* `lead` runs the speaker into the first line; `actor` names whoever performs an action.
|
|
170
|
+
*/
|
|
171
|
+
export function SquareMarkdownBody({ text, theme, lead, actor }: {
|
|
172
|
+
text: string;
|
|
173
|
+
theme: PluginTheme;
|
|
174
|
+
lead?: string;
|
|
175
|
+
actor?: string;
|
|
176
|
+
}) {
|
|
146
177
|
const styles = useMemo(() => makeStyles(theme.colors), [theme.colors]);
|
|
147
|
-
const tokens = useMemo(() => lexer(text), [text]);
|
|
148
|
-
|
|
178
|
+
const tokens = useMemo(() => lexer(text).filter(token => token.type !== "space"), [text]);
|
|
179
|
+
let pending = lead;
|
|
180
|
+
const nodes = tokens.map((token, index) => {
|
|
181
|
+
const key = `body-${index}`;
|
|
182
|
+
if (isAction(token)) {
|
|
183
|
+
const words = renderInlines(children(children(token)[0]!), styles, key);
|
|
184
|
+
return <SquareCue key={key} theme={theme} label={actor === undefined ? words : <>{actor} {words}</>} />;
|
|
185
|
+
}
|
|
186
|
+
if (pending === undefined) return renderBlocks([token], styles, key);
|
|
187
|
+
const name = pending;
|
|
188
|
+
pending = undefined;
|
|
189
|
+
if (token.type === "paragraph") {
|
|
190
|
+
return (
|
|
191
|
+
<Text key={key} selectable={Platform.OS === "web"} style={styles.text}>
|
|
192
|
+
<Lead name={name} styles={styles} />
|
|
193
|
+
{renderInlines(children(token), styles, key)}
|
|
194
|
+
</Text>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
return [<Text key={`${key}-lead`} style={styles.text}><Lead name={name} styles={styles} /></Text>, ...renderBlocks([token], styles, key)];
|
|
198
|
+
});
|
|
199
|
+
return (
|
|
200
|
+
<View style={styles.block}>
|
|
201
|
+
{nodes}
|
|
202
|
+
{pending !== undefined && <Text style={styles.text}><Lead name={pending} styles={styles} /></Text>}
|
|
203
|
+
</View>
|
|
204
|
+
);
|
|
149
205
|
}
|
|
@@ -1,40 +1,50 @@
|
|
|
1
1
|
import type { PluginTheme } from "@getpaseo/plugin";
|
|
2
|
+
import { MARBLE_DARK, MARBLE_LIGHT } from "./marble";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* Square's own courtyard palette:
|
|
5
|
-
* slate
|
|
5
|
+
* Square's own courtyard palette: veinless honed stone (muted grey light, charcoal dark) and a
|
|
6
|
+
* slate seal ■ — everything half a step down, the quiet card on the timeline, deliberately no relative of notesoup's warm parchment or
|
|
6
7
|
* pi-meantime's neutral carbon. Every token here is opaque and self-contained — the card and
|
|
7
8
|
* Markdown never borrow the host's surface ramp or accent, only its appearance
|
|
8
9
|
* (read from the host ink) and its accentForeground, which paints text on top
|
|
9
10
|
* of an accent fill.
|
|
10
11
|
*/
|
|
11
12
|
const DARK = {
|
|
12
|
-
surface0: "#
|
|
13
|
-
surface1: "#
|
|
14
|
-
surface2: "#
|
|
15
|
-
border: "#
|
|
16
|
-
foreground: "#
|
|
17
|
-
foregroundMuted: "#
|
|
18
|
-
accent: "#
|
|
13
|
+
surface0: "#15171a", // inset / code
|
|
14
|
+
surface1: "#1a1d20", // card (charcoal stone)
|
|
15
|
+
surface2: "#15171a", // inset / code
|
|
16
|
+
border: "#2b2f34",
|
|
17
|
+
foreground: "#dde1e6",
|
|
18
|
+
foregroundMuted: "#858b94",
|
|
19
|
+
accent: "#8298ad", // plaza ■ / links
|
|
19
20
|
} as const;
|
|
20
21
|
|
|
21
22
|
const LIGHT = {
|
|
22
|
-
surface0: "#
|
|
23
|
-
surface1: "#
|
|
24
|
-
surface2: "#
|
|
25
|
-
border: "#
|
|
26
|
-
foreground: "#
|
|
27
|
-
foregroundMuted: "#
|
|
28
|
-
accent: "#
|
|
23
|
+
surface0: "#e9ebee",
|
|
24
|
+
surface1: "#f4f5f6", // card (grey stone)
|
|
25
|
+
surface2: "#e9ebee",
|
|
26
|
+
border: "#e0e2e5",
|
|
27
|
+
foreground: "#2b2e33",
|
|
28
|
+
foregroundMuted: "#90959c",
|
|
29
|
+
accent: "#56687e",
|
|
29
30
|
} as const;
|
|
30
31
|
|
|
31
|
-
const
|
|
32
|
+
const TILE = 512;
|
|
33
|
+
|
|
34
|
+
/** Web-only surface: react-native-web passes these through as inline CSS. */
|
|
35
|
+
const STONE = {
|
|
36
|
+
// A slab resting on the page: one darker cut along the bottom edge, a tight contact shadow, no sheen.
|
|
37
|
+
dark: { image: `url(${MARBLE_DARK})`, edge: "#34393f", boxShadow: "0 1px 0 rgba(0,0,0,0.3), 0 4px 10px -6px rgba(0,0,0,0.55)", inlay: "inset 0 0 0 0.5px rgba(0,0,0,0.4)" },
|
|
38
|
+
light: { image: `url(${MARBLE_LIGHT})`, edge: "#c9ccd1", boxShadow: "0 1px 0 rgba(24,26,30,0.04), 0 4px 10px -6px rgba(24,26,30,0.18)", inlay: "inset 0 0 0 0.5px rgba(0,0,0,0.26)" },
|
|
39
|
+
} as const;
|
|
32
40
|
|
|
33
41
|
export interface SquarePalette {
|
|
34
42
|
/** Complete theme for the card and its Markdown surfaces. */
|
|
35
43
|
theme: PluginTheme;
|
|
36
|
-
/**
|
|
37
|
-
|
|
44
|
+
/** Marble card surface for the web host, cut from the tile at `seed`; native keeps the flat surface1. */
|
|
45
|
+
stone(seed: string): { backgroundImage: string; backgroundSize: string; backgroundPosition: string; borderBottomColor: string; boxShadow: string };
|
|
46
|
+
/** Web-only: the ■ set into the stone. */
|
|
47
|
+
inlay: { boxShadow: string };
|
|
38
48
|
}
|
|
39
49
|
|
|
40
50
|
/** Paseo theme colors are hex (#RGB or #RRGGBB). */
|
|
@@ -57,6 +67,19 @@ export function squarePalette(theme: PluginTheme): SquarePalette {
|
|
|
57
67
|
statusWarning: theme.colors.statusWarning,
|
|
58
68
|
statusDanger: theme.colors.statusDanger,
|
|
59
69
|
} },
|
|
60
|
-
|
|
70
|
+
stone: seed => {
|
|
71
|
+
// Each card shows its own patch of the slab, so neighbours never repeat a vein.
|
|
72
|
+
let hash = 2166136261;
|
|
73
|
+
for (const char of seed) hash = Math.imul(hash ^ char.charCodeAt(0), 16777619);
|
|
74
|
+
const { image, edge, boxShadow } = dark ? STONE.dark : STONE.light;
|
|
75
|
+
return {
|
|
76
|
+
backgroundImage: image,
|
|
77
|
+
backgroundSize: `${TILE}px ${TILE}px`,
|
|
78
|
+
backgroundPosition: `${-((hash >>> 0) % TILE)}px ${-((hash >>> 9) % TILE)}px`,
|
|
79
|
+
borderBottomColor: edge,
|
|
80
|
+
boxShadow,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
inlay: { boxShadow: (dark ? STONE.dark : STONE.light).inlay },
|
|
61
84
|
};
|
|
62
85
|
}
|