@astrosheep/square 0.3.92 → 0.3.94
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/activity-feed.d.ts +3 -5
- package/dist/activity-feed.js +16 -14
- package/dist/artifact.js +2 -2
- package/dist/boundary-presentation.d.ts +2 -2
- package/dist/catch-decisions.d.ts +4 -6
- package/dist/catch-decisions.js +4 -4
- package/dist/claude-delivery-status.js +10 -4
- package/dist/claude-delivery.js +19 -11
- package/dist/claude-mod.js +24 -53
- package/dist/cli/context.d.ts +9 -4
- package/dist/cli/context.js +30 -25
- package/dist/cli/express-presentation.d.ts +3 -1
- package/dist/cli/express-presentation.js +26 -14
- package/dist/cli/json.d.ts +3 -0
- package/dist/cli/json.js +14 -0
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.d.ts +1 -3
- package/dist/cli/observation-commands.js +43 -56
- package/dist/cli/program.js +2 -2
- package/dist/cli/registry.js +6 -3
- package/dist/cli/square-commands.d.ts +1 -1
- package/dist/cli/square-commands.js +46 -54
- package/dist/codex-hook.d.ts +3 -2
- package/dist/codex-hook.js +13 -4
- package/dist/codex-queue.js +4 -4
- package/dist/decisions.d.ts +5 -11
- package/dist/decisions.js +9 -20
- package/dist/delivery-deferral.js +2 -2
- package/dist/delivery-health.d.ts +0 -1
- package/dist/delivery-health.js +1 -3
- package/dist/delivery-operations.d.ts +2 -2
- package/dist/delivery-operations.js +38 -38
- package/dist/delivery.d.ts +1 -1
- package/dist/delivery.js +1 -1
- package/dist/harness-links.d.ts +1 -10
- package/dist/harness-links.js +144 -109
- package/dist/harness.js +4 -13
- package/dist/help.js +12 -10
- package/dist/inbox.d.ts +9 -22
- package/dist/inbox.js +23 -27
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.js +23 -24
- package/dist/model.d.ts +8 -15
- package/dist/model.js +4 -7
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.js +6 -5
- package/dist/operation-presentation.d.ts +5 -4
- package/dist/operation-presentation.js +50 -45
- package/dist/owner.d.ts +11 -0
- package/dist/owner.js +32 -0
- package/dist/paseo-delivery.d.ts +4 -1
- package/dist/paseo-delivery.js +7 -12
- package/dist/paseo-state.d.ts +2 -2
- package/dist/paseo-state.js +6 -4
- package/dist/plugins/opencode/delivery.js +27 -15
- package/dist/ports.d.ts +13 -33
- package/dist/presentation-operations.js +4 -4
- package/dist/presentation.d.ts +5 -8
- package/dist/presentation.js +33 -43
- package/dist/runtime.d.ts +1 -7
- package/dist/runtime.js +0 -14
- package/dist/seats.d.ts +2 -3
- package/dist/seats.js +7 -15
- package/dist/session-lifecycle.d.ts +6 -2
- package/dist/session-lifecycle.js +39 -11
- package/dist/sqlite-square.d.ts +36 -22
- package/dist/sqlite-square.js +138 -161
- package/dist/square-api.d.ts +15 -10
- package/dist/square-api.js +26 -22
- package/dist/square-core.d.ts +0 -5
- package/dist/square-core.js +0 -9
- package/dist/square-domain.d.ts +4 -2
- package/dist/square-domain.js +56 -21
- package/dist/square-facade.d.ts +45 -87
- package/dist/square-index.d.ts +5 -13
- package/dist/square-index.js +13 -38
- package/dist/square-location.d.ts +1 -14
- package/dist/square-location.js +4 -69
- package/dist/square-migration-host-ledger.d.ts +2 -2
- package/dist/square-migration-host-ledger.js +6 -5
- package/dist/square-migration.d.ts +2 -1
- package/dist/square-migration.js +52 -30
- package/dist/square-sqlite.d.ts +6 -2
- package/dist/square-sqlite.js +54 -28
- package/dist/square-tools.d.ts +7 -10
- package/dist/square-tools.js +22 -54
- package/dist/stream.js +4 -5
- package/dist/views.d.ts +21 -10
- package/dist/views.js +38 -38
- package/dist/wakes.d.ts +1 -2
- package/dist/wakes.js +1 -1
- package/guides/application-facade-delivery-evidence.md +1 -1
- package/guides/claude-native.md +18 -9
- package/guides/codex-delivery.md +17 -6
- package/guides/mcp-connection-lifetime.md +1 -1
- package/guides/mcp.md +3 -3
- package/guides/migration.md +142 -151
- package/guides/operation-cancellation.md +1 -1
- package/guides/participant-ownership.md +78 -47
- package/guides/pi-tools.md +33 -4
- package/guides/primitive-history-consolidation.md +1 -1
- package/guides/row-storage-design.md +8 -8
- package/guides/square-api.md +244 -88
- package/package.json +2 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +27 -17
- package/plugins/paseo/client/palette.ts +18 -18
- package/plugins/paseo/client/square-message.tsx +17 -23
- package/plugins/pi/square-pi.js +17 -17
- package/dist/square-backend.d.ts +0 -13
- package/dist/square-backend.js +0 -1
- package/dist/square-stdio.d.ts +0 -22
- package/dist/square-stdio.js +0 -235
- package/dist/stdio-server.d.ts +0 -3
- package/dist/stdio-server.js +0 -212
- package/guides/backends.md +0 -193
package/dist/square-sqlite.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DatabaseSync as MemoryDatabase } from 'node:sqlite';
|
|
2
|
-
import {
|
|
2
|
+
import { SquareError, nameKey, isWakeRouteKind, validateOwner, validateActivityMetadata, 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 { legacyRowFramingIssue } from './square-legacy-framing.js';
|
|
@@ -11,7 +11,9 @@ const V1_USER_VERSION = 1;
|
|
|
11
11
|
export const V2_USER_VERSION = 2;
|
|
12
12
|
export const V3_USER_VERSION = 3;
|
|
13
13
|
export const V4_USER_VERSION = 4;
|
|
14
|
-
export const
|
|
14
|
+
export const V5_USER_VERSION = 5;
|
|
15
|
+
export const V6_USER_VERSION = 6;
|
|
16
|
+
export const CURRENT_USER_VERSION = 7;
|
|
15
17
|
export const V1_SNAPSHOT_TABLE = 'square_snapshot';
|
|
16
18
|
const V1_SNAPSHOT_DDL = `CREATE TABLE ${V1_SNAPSHOT_TABLE} (id INTEGER PRIMARY KEY CHECK(id=1), revision INTEGER NOT NULL CHECK(revision>=0), state TEXT NOT NULL)`;
|
|
17
19
|
const V1_SNAPSHOT_COLUMNS = [
|
|
@@ -87,6 +89,7 @@ export const DELIVERY_DEFERRALS_SQL = `CREATE TABLE delivery_deferrals (
|
|
|
87
89
|
PRIMARY KEY (participant_key_json,generation_json,deferral_id_json)
|
|
88
90
|
) WITHOUT ROWID;
|
|
89
91
|
CREATE INDEX delivery_deferral_expiry ON delivery_deferrals(participant_key_json,generation_json,expires_at);`;
|
|
92
|
+
const ATTACHMENT_COLUMN_SQL = "attachment TEXT NOT NULL DEFAULT 'attached' CHECK (attachment IN ('attached','detached'))";
|
|
90
93
|
export const SCHEMA_SQL = `CREATE TABLE square_meta (
|
|
91
94
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
92
95
|
revision INTEGER NOT NULL CHECK (revision BETWEEN 0 AND 9007199254740991),
|
|
@@ -119,13 +122,12 @@ CREATE TABLE catch_leases (
|
|
|
119
122
|
CREATE TABLE seats (
|
|
120
123
|
participant_key_json TEXT COLLATE BINARY PRIMARY KEY NOT NULL,
|
|
121
124
|
participant_json TEXT NOT NULL,
|
|
122
|
-
|
|
123
|
-
session_json TEXT NOT NULL,
|
|
124
|
-
agent_json TEXT,
|
|
125
|
+
owner_json TEXT NOT NULL,
|
|
125
126
|
generation_json TEXT NOT NULL,
|
|
126
|
-
wake_address_json TEXT CHECK (wake_address_json IS NULL OR json_valid(wake_address_json))
|
|
127
|
+
wake_address_json TEXT CHECK (wake_address_json IS NULL OR json_valid(wake_address_json)),
|
|
128
|
+
${ATTACHMENT_COLUMN_SQL}
|
|
127
129
|
) WITHOUT ROWID;
|
|
128
|
-
CREATE INDEX
|
|
130
|
+
CREATE INDEX seat_owner ON seats(owner_json);
|
|
129
131
|
CREATE TABLE delivery_evidence (
|
|
130
132
|
participant_key_json TEXT COLLATE BINARY NOT NULL,
|
|
131
133
|
participant_json TEXT NOT NULL,
|
|
@@ -180,7 +182,11 @@ CREATE TABLE landed_audience (
|
|
|
180
182
|
UNIQUE (act_index, recipient_order)
|
|
181
183
|
) WITHOUT ROWID;
|
|
182
184
|
CREATE INDEX audience_recipient ON landed_audience(recipient_key_json, act_index);`;
|
|
183
|
-
export const
|
|
185
|
+
export const FORMAT6_SCHEMA_SQL = SCHEMA_SQL.replace(` owner_json TEXT NOT NULL,\n generation_json TEXT NOT NULL,`, ` channel_json TEXT NOT NULL,\n session_json TEXT NOT NULL,\n agent_json TEXT,\n generation_json TEXT NOT NULL,`).replace('CREATE INDEX seat_owner ON seats(owner_json);', 'CREATE INDEX seat_body ON seats(channel_json, session_json, agent_json);');
|
|
186
|
+
export const FORMAT5_SCHEMA_SQL = FORMAT6_SCHEMA_SQL.replace(`,\n ${ATTACHMENT_COLUMN_SQL}`, '');
|
|
187
|
+
export const FORMAT4_SCHEMA_SQL = FORMAT5_SCHEMA_SQL.replace(DELIVERY_DEFERRALS_SQL, '');
|
|
188
|
+
const FORMAT6_SCHEMA = FORMAT6_SCHEMA_SQL.split(';').map(s => s.trim()).filter(Boolean).map(normalizedSql).sort();
|
|
189
|
+
const FORMAT5_SCHEMA = FORMAT5_SCHEMA_SQL.split(';').map(s => s.trim()).filter(Boolean).map(normalizedSql).sort();
|
|
184
190
|
const EXPECTED_SCHEMA_SQL = SCHEMA_SQL.split(';').map(s => s.trim()).filter(Boolean).map(normalizedSql).sort();
|
|
185
191
|
const FORMAT4_SCHEMA = FORMAT4_SCHEMA_SQL.split(';').map(s => s.trim()).filter(Boolean).map(normalizedSql).sort();
|
|
186
192
|
const encode = JSON.stringify;
|
|
@@ -372,17 +378,19 @@ function wakeAddress(value) {
|
|
|
372
378
|
stableJson(entries);
|
|
373
379
|
return immutable({ kind: address.kind, address: entries });
|
|
374
380
|
}
|
|
375
|
-
function
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
return
|
|
381
|
+
function ownerFrom(row) {
|
|
382
|
+
const owner = decoded(row.owner_json, true);
|
|
383
|
+
validateOwner(owner);
|
|
384
|
+
return owner;
|
|
379
385
|
}
|
|
380
386
|
function seat(row) {
|
|
381
387
|
const participant = decoded(row.participant_json, true);
|
|
382
388
|
requireFact(row.participant_key_json === key(participant));
|
|
383
389
|
const generation = activityId(decoded(row.generation_json, true));
|
|
384
390
|
const parsedWake = row.wake_address_json === null ? undefined : wakeAddress(json(row.wake_address_json));
|
|
385
|
-
|
|
391
|
+
requireFact(row.attachment === 'attached' || row.attachment === 'detached', 'seat attachment is malformed.');
|
|
392
|
+
requireFact(row.attachment !== 'detached' || parsedWake === undefined, 'a detached seat has a wake address.');
|
|
393
|
+
return immutable({ participant, owner: ownerFrom(row), generation, attachment: row.attachment, ...(parsedWake === undefined ? {} : { wakeAddress: parsedWake }) });
|
|
386
394
|
}
|
|
387
395
|
function nativeCorrelation(value) {
|
|
388
396
|
const item = shape(value, ['harness'], ['token', 'messageId', 'payloadHash']);
|
|
@@ -453,7 +461,7 @@ export function validateRowFraming(db, version, location = '') {
|
|
|
453
461
|
const identity = db.prepare('SELECT application_id,user_version FROM pragma_application_id,pragma_user_version').get();
|
|
454
462
|
requireFact(identity.application_id === APPLICATION_ID, 'unrelated SQLite database.');
|
|
455
463
|
requireFact(identity.user_version === version, 'unsupported SQLite format version.');
|
|
456
|
-
validateOwnedSchema(db, version === V4_USER_VERSION ? FORMAT4_SCHEMA : EXPECTED_SCHEMA_SQL, location);
|
|
464
|
+
validateOwnedSchema(db, version === V4_USER_VERSION ? FORMAT4_SCHEMA : version === V5_USER_VERSION ? FORMAT5_SCHEMA : version === V6_USER_VERSION ? FORMAT6_SCHEMA : EXPECTED_SCHEMA_SQL, location);
|
|
457
465
|
}
|
|
458
466
|
export function validateSqliteHeader(db, location = '') {
|
|
459
467
|
const identity = db.prepare('SELECT application_id,user_version FROM pragma_application_id,pragma_user_version').get();
|
|
@@ -462,7 +470,7 @@ export function validateSqliteHeader(db, location = '') {
|
|
|
462
470
|
validateLegacyFraming(db, APPLICATION_ID, location);
|
|
463
471
|
throw new SquareError('invalid_args', `Square artifact at ${location} needs migration.\nStop sessions using it, back it up, then migrate and verify before restarting.`, { reason: 'migration_required' });
|
|
464
472
|
}
|
|
465
|
-
if (identity.user_version === V2_USER_VERSION || identity.user_version === V3_USER_VERSION || identity.user_version === V4_USER_VERSION) {
|
|
473
|
+
if (identity.user_version === V2_USER_VERSION || identity.user_version === V3_USER_VERSION || identity.user_version === V4_USER_VERSION || identity.user_version === V5_USER_VERSION || identity.user_version === V6_USER_VERSION) {
|
|
466
474
|
validateRowFraming(db, identity.user_version, location);
|
|
467
475
|
throw new SquareError('invalid_args', `Square artifact at ${location} needs migration.\nStop sessions using it, back it up, then migrate and verify before restarting.`, { reason: 'migration_required' });
|
|
468
476
|
}
|
|
@@ -543,17 +551,16 @@ export function createSqliteDriver(db) {
|
|
|
543
551
|
const latestSeats = `seats s JOIN participants p ON p.name_key_json=s.participant_key_json
|
|
544
552
|
JOIN activities a ON a.act_index=p.last_join_index AND a.kind='join' AND a.actor_key_json=p.name_key_json
|
|
545
553
|
AND s.generation_json='"act/'||a.act_index||'"'`;
|
|
546
|
-
function
|
|
547
|
-
|
|
548
|
-
return immutable(all(`SELECT s.* FROM ${latestSeats} WHERE p
|
|
554
|
+
function ownerSeats(owner) {
|
|
555
|
+
validateOwner(owner);
|
|
556
|
+
return immutable(all(`SELECT s.* FROM ${latestSeats} WHERE p.joined=1 AND s.owner_json=? ORDER BY s.participant_key_json`, [encode(owner)]).map(seat));
|
|
549
557
|
}
|
|
550
558
|
const read = {
|
|
551
559
|
seat(name) {
|
|
552
560
|
const r = one(`SELECT s.* FROM ${latestSeats} WHERE p.joined=1 AND s.participant_key_json=?`, [key(name)]);
|
|
553
561
|
return r ? seat(r) : undefined;
|
|
554
562
|
},
|
|
555
|
-
|
|
556
|
-
completedSeatsForBody: body => bodySeats(body, 'done'),
|
|
563
|
+
seatsForOwner: ownerSeats,
|
|
557
564
|
deliveryEvidence(participant, generation, activityIndex) {
|
|
558
565
|
const args = [key(participant), encode(generation)];
|
|
559
566
|
const filter = activityIndex === undefined ? '' : ' AND act_index=?';
|
|
@@ -713,14 +720,14 @@ export function createSqliteDriver(db) {
|
|
|
713
720
|
};
|
|
714
721
|
const writes = {
|
|
715
722
|
storeSeat(value) {
|
|
716
|
-
|
|
723
|
+
validateOwner(value.owner);
|
|
717
724
|
activityId(value.generation);
|
|
718
725
|
string(value.participant, true);
|
|
719
726
|
const existing = read.seat(value.participant);
|
|
720
727
|
if (existing !== undefined && stableJson(existing) === stableJson(value))
|
|
721
728
|
return false;
|
|
722
729
|
const wake = value.wakeAddress === undefined ? null : encode(value.wakeAddress);
|
|
723
|
-
run('INSERT INTO seats(participant_key_json,participant_json,
|
|
730
|
+
run('INSERT INTO seats(participant_key_json,participant_json,owner_json,generation_json,wake_address_json,attachment) VALUES(?,?,?,?,?,?) ON CONFLICT(participant_key_json) DO UPDATE SET participant_json=excluded.participant_json,owner_json=excluded.owner_json,generation_json=excluded.generation_json,wake_address_json=excluded.wake_address_json,attachment=excluded.attachment', [key(value.participant), encode(value.participant), encode(value.owner), encode(value.generation), wake, value.attachment]);
|
|
724
731
|
run('DELETE FROM catch_leases WHERE owner_json=?', [encode(value.participant)]);
|
|
725
732
|
run('DELETE FROM delivery_deferrals WHERE participant_key_json=?', [key(value.participant)]);
|
|
726
733
|
deliveryChanged = true;
|
|
@@ -739,12 +746,28 @@ export function createSqliteDriver(db) {
|
|
|
739
746
|
deliveryChanged = true;
|
|
740
747
|
return count > 0;
|
|
741
748
|
},
|
|
749
|
+
reattachSeat(value, address) {
|
|
750
|
+
const current = read.seat(value.participant);
|
|
751
|
+
if (current?.attachment !== 'detached')
|
|
752
|
+
return false;
|
|
753
|
+
run("UPDATE seats SET attachment='attached',wake_address_json=? WHERE participant_key_json=? AND generation_json=?", [address === undefined ? null : encode(wakeAddress(address)), key(value.participant), encode(value.generation)]);
|
|
754
|
+
deliveryChanged = true;
|
|
755
|
+
return true;
|
|
756
|
+
},
|
|
757
|
+
detachSeat(value) {
|
|
758
|
+
const count = run("UPDATE seats SET attachment='detached',wake_address_json=NULL WHERE participant_key_json=? AND generation_json=? AND attachment='attached'", [key(value.participant), encode(value.generation)]);
|
|
759
|
+
if (!count)
|
|
760
|
+
return false;
|
|
761
|
+
run('DELETE FROM catch_leases WHERE owner_json=?', [encode(value.participant)]);
|
|
762
|
+
run('DELETE FROM delivery_deferrals WHERE participant_key_json=? AND generation_json=?', [key(value.participant), encode(value.generation)]);
|
|
763
|
+
deliveryChanged = true;
|
|
764
|
+
return true;
|
|
765
|
+
},
|
|
742
766
|
removeSeat(value) {
|
|
743
|
-
// Done hides current authority; retain only its latest coordinate for exact-body resume.
|
|
744
767
|
const row = one(`SELECT s.* FROM ${latestSeats} WHERE p.done=1 AND s.participant_key_json=?`, [key(value.participant)]);
|
|
745
|
-
if (row === undefined || decoded(row.generation_json) !== value.generation)
|
|
768
|
+
if (row === undefined || decoded(row.generation_json) !== value.generation || ownerFrom(row) !== value.owner)
|
|
746
769
|
return false;
|
|
747
|
-
run('
|
|
770
|
+
run('DELETE FROM seats WHERE participant_key_json=?', [key(value.participant)]);
|
|
748
771
|
run('DELETE FROM catch_leases WHERE owner_json=?', [encode(value.participant)]);
|
|
749
772
|
run('DELETE FROM delivery_deferrals WHERE participant_key_json=?', [key(value.participant)]);
|
|
750
773
|
changed = true;
|
|
@@ -872,7 +895,7 @@ export function createSqliteDriver(db) {
|
|
|
872
895
|
wakeAddress(input.route);
|
|
873
896
|
const currentSeat = read.seat(input.seat.participant);
|
|
874
897
|
const item = read.activity(index);
|
|
875
|
-
if (currentSeat === undefined || currentSeat.generation !== input.seat.generation ||
|
|
898
|
+
if (currentSeat === undefined || currentSeat.attachment !== 'attached' || currentSeat.generation !== input.seat.generation || currentSeat.owner !== input.seat.owner
|
|
876
899
|
|| item?.kind !== 'say' || read.directedAt(input.seat.participant, index) === undefined)
|
|
877
900
|
return { status: 'stale' };
|
|
878
901
|
if (matchingDeferrals(read.deliveryDeferrals(currentSeat.participant, currentSeat.generation), item.metadata, input.at).length > 0)
|
|
@@ -1023,15 +1046,18 @@ export function exportSqliteState(db) {
|
|
|
1023
1046
|
}
|
|
1024
1047
|
/** Full read-only integrity inspection, including maintained-index replay comparison. */
|
|
1025
1048
|
export function inspectSqlite(db) {
|
|
1049
|
+
return inspectRows(db, seat);
|
|
1050
|
+
}
|
|
1051
|
+
function inspectRows(db, inspectSeat) {
|
|
1026
1052
|
const check = db.prepare('PRAGMA integrity_check').all();
|
|
1027
1053
|
requireFact(check.length === 1 && check[0].integrity_check === 'ok', 'SQLite integrity check failed.');
|
|
1028
1054
|
const state = exportSqliteState(db), d = createSqliteDriver(db);
|
|
1029
1055
|
d.roster();
|
|
1030
1056
|
for (const row of db.prepare('SELECT * FROM seats').all())
|
|
1031
|
-
|
|
1057
|
+
inspectSeat(row);
|
|
1032
1058
|
for (const row of db.prepare('SELECT * FROM delivery_evidence').all())
|
|
1033
1059
|
deliveryEvidence(row);
|
|
1034
|
-
if (db.prepare('PRAGMA user_version').get().user_version
|
|
1060
|
+
if (Number(db.prepare('PRAGMA user_version').get().user_version) >= V5_USER_VERSION) {
|
|
1035
1061
|
for (const row of db.prepare('SELECT * FROM delivery_deferrals').all())
|
|
1036
1062
|
deliveryDeferral(row);
|
|
1037
1063
|
}
|
package/dist/square-tools.d.ts
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
2
|
import { type Square } from './square-api.js';
|
|
3
|
-
import type { OperationControl } from './square-facade.js';
|
|
3
|
+
import type { Owner, WakeAddress, OperationControl } from './square-facade.js';
|
|
4
|
+
export interface ToolIdentity {
|
|
5
|
+
readonly owner?: Owner;
|
|
6
|
+
readonly wakeAddress?: WakeAddress;
|
|
7
|
+
}
|
|
4
8
|
type JsonObject = Record<string, unknown>;
|
|
5
|
-
type Outcome = Promise<object>;
|
|
6
|
-
type ToolRender = (result: JsonObject, args: JsonObject) => string | undefined;
|
|
7
9
|
interface ToolDefinition {
|
|
8
10
|
name: string;
|
|
9
11
|
description: string;
|
|
10
12
|
inputSchema: z.ZodObject<z.ZodRawShape>;
|
|
11
|
-
|
|
12
|
-
|
|
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;
|
|
13
|
+
/** Human text identical to what the CLI prints; domain receipts stay inside the adapter. */
|
|
14
|
+
run(square: Square, args: JsonObject, control: OperationControl, identity: ToolIdentity): Promise<string>;
|
|
18
15
|
}
|
|
19
16
|
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.";
|
|
20
17
|
export declare const SQUARE_TOOLS: readonly ToolDefinition[];
|
package/dist/square-tools.js
CHANGED
|
@@ -1,51 +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';
|
|
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
|
-
const location = z.string().optional().describe('Which square: a .square file path
|
|
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.');
|
|
6
6
|
const participant = z.string().optional().describe('Your name in the square. Leave it out if you hold exactly one name here.');
|
|
7
7
|
const activityId = z.string().regex(/^act\/(0|[1-9][0-9]*)$/);
|
|
8
8
|
const stringArray = z.array(z.string());
|
|
9
|
-
/**
|
|
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. */
|
|
9
|
+
/** Each renderer declares the operation facts it reads. */
|
|
14
10
|
function rendering(render) {
|
|
15
11
|
return (result, args) => render(result, args);
|
|
16
12
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Tool receipts are the one machine surface without activity metadata: a receipt copy drops every
|
|
19
|
-
* property named `metadata` while domain activities keep theirs for SDK, CLI, stdio and stream readers.
|
|
20
|
-
*/
|
|
21
|
-
function withoutActivityMetadata(value) {
|
|
22
|
-
if (Array.isArray(value))
|
|
23
|
-
return value.map(withoutActivityMetadata);
|
|
24
|
-
if (value === null || typeof value !== 'object')
|
|
25
|
-
return value;
|
|
26
|
-
const prototype = Object.getPrototypeOf(value);
|
|
27
|
-
if (prototype !== Object.prototype && prototype !== null)
|
|
28
|
-
return value;
|
|
29
|
-
return Object.fromEntries(Object.entries(value).filter(([key]) => key !== 'metadata')
|
|
30
|
-
.map(([key, item]) => [key, withoutActivityMetadata(item)]));
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Human text renders from the unfiltered receipt, so metadata-only says keep the placeholder the
|
|
34
|
-
* CLI prints and every other sentence stays identical. Machines only ever receive the filtered copy.
|
|
35
|
-
*/
|
|
36
|
-
const renderingReceipts = new WeakMap();
|
|
37
|
-
function renderingReceipt(receipt) {
|
|
38
|
-
return (renderingReceipts.get(receipt) ?? receipt);
|
|
39
|
-
}
|
|
40
13
|
function tool(name, description, shape, run, render) {
|
|
41
14
|
const inputSchema = z.object({ ...shape, location }).strict();
|
|
42
|
-
return { name, description, inputSchema,
|
|
43
|
-
run: async (square, args, control) => {
|
|
15
|
+
return { name, description, inputSchema,
|
|
16
|
+
run: async (square, args, control, identity) => {
|
|
44
17
|
const { location: _location, ...operationArgs } = inputSchema.parse(args);
|
|
45
|
-
const receipt = await run(square, operationArgs, control);
|
|
46
|
-
|
|
47
|
-
renderingReceipts.set(filtered, receipt);
|
|
48
|
-
return filtered;
|
|
18
|
+
const receipt = await run(square, operationArgs, control, identity);
|
|
19
|
+
return render(receipt, operationArgs) ?? '';
|
|
49
20
|
} };
|
|
50
21
|
}
|
|
51
22
|
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.';
|
|
@@ -53,10 +24,7 @@ const target = z.string().describe('The participant to turn toward or away from.
|
|
|
53
24
|
export const SQUARE_TOOLS = [
|
|
54
25
|
tool('join', 'Step into the square under your name. Do this once before you speak or catch. You arrive seeing what was said most recently.', {
|
|
55
26
|
as: z.string().optional().describe('Your name in the square. Give it explicitly when joining.'), takeover: z.boolean().optional().describe('Claim this name even though another session holds it now. That session loses it.'),
|
|
56
|
-
},
|
|
57
|
-
const { participant, ...receipt } = await square.join(as, { takeover, ...observation(control) }, control);
|
|
58
|
-
return { ...receipt, participant: participant.name };
|
|
59
|
-
}, rendering((result) => renderJoinResult(result))),
|
|
27
|
+
}, (square, { as, takeover }, control, identity) => square.join(requireJoinName(as), { ...identity, owner: requireOwner(identity.owner), takeover, observe: true }, control), rendering((result) => renderJoinResult(result))),
|
|
60
28
|
tool('express', 'Speak or act in the square. Plain words, half-formed ideas and *actions* all belong here. Mention or reply to someone to be heard by them; ring the bell to be heard by everyone. Said to no one in particular, your words reach those who listen to you.', {
|
|
61
29
|
as: participant,
|
|
62
30
|
body: z.string().describe('What you say or do. Markdown works; *asterisks* mark an action.'),
|
|
@@ -66,10 +34,7 @@ export const SQUARE_TOOLS = [
|
|
|
66
34
|
noMention: z.boolean().optional().describe('Leave @names in the body as plain text. Mentions and reply still reach people.'),
|
|
67
35
|
force: z.boolean().optional().describe('Speak now, even if something reached you that you have not caught yet.'),
|
|
68
36
|
noWait: z.boolean().optional().describe('If a hand is raised or you are speaking too fast, return at once instead of waiting.'),
|
|
69
|
-
}, async (square, { body, as, ...options }, control) => {
|
|
70
|
-
const { delivery: _delivery, ...result } = await (await square.participant(as)).express(body, { ...options, ...observation(control) }, control);
|
|
71
|
-
return result;
|
|
72
|
-
}, rendering((result) => renderExpression(result.observation?.location ?? '', result))),
|
|
37
|
+
}, async (square, { body, as, ...options }, control, identity) => (await selectOwnSeat(square, identity.owner, as)).express(body, { ...options, observe: true }, control), rendering((result) => renderExpression(result.observation?.location ?? '', result))),
|
|
73
38
|
tool('catch', 'Take in what reached you since you last looked: mentions, replies, bells, and words from those you listen to. What you catch no longer waits for you; history lets you look back.', {
|
|
74
39
|
as: participant,
|
|
75
40
|
id: activityId.optional().describe('Take in this activity in full, like act/12, even if you caught it before. Do not combine with idle, from, mention or limit.'),
|
|
@@ -77,7 +42,7 @@ export const SQUARE_TOOLS = [
|
|
|
77
42
|
from: stringArray.optional().describe('Only what these participants said.'),
|
|
78
43
|
mention: z.boolean().optional().describe('Only what was addressed to you, and bells.'),
|
|
79
44
|
limit: z.number().int().min(1).optional().describe('At most this many at once (default 10, up to 100). The rest keeps waiting.'),
|
|
80
|
-
}, async (square, { as, ...options }, control) => (await square.
|
|
45
|
+
}, async (square, { as, ...options }, control, identity) => (await selectOwnSeat(square, identity.owner, as)).catch({ ...options, observe: true }, control), rendering((result, args) => {
|
|
81
46
|
// The CLI always spells the default cap in its continuation, so the tool does too.
|
|
82
47
|
const options = args;
|
|
83
48
|
const renderOptions = options.id === undefined ? { ...options, limit: options.limit ?? CATCH_DEFAULT_LIMIT } : options;
|
|
@@ -92,25 +57,28 @@ export const SQUARE_TOOLS = [
|
|
|
92
57
|
fixed: z.string().optional().describe('Only activities containing this exact text.'),
|
|
93
58
|
from: stringArray.optional().describe('Only what these participants said.'),
|
|
94
59
|
mention: z.string().optional().describe('Only what was addressed to this participant.'),
|
|
95
|
-
}, (square, args, control) => square.history({ ...args,
|
|
60
|
+
}, (square, args, control) => square.history({ ...args, observe: true }, control), rendering((result, args) => renderHistoryResult(result.observation?.location ?? '', result, { query: args }))),
|
|
96
61
|
tool('attention', 'Turn toward someone or away from them, from now on. Listen: hear everything they say, even to others. Unlisten: stop listening without clearing an ignore; mentions and replies still reach you unless ignored. Ignore: hear nothing from them but bells. What was already said stays as it was.', {
|
|
97
62
|
as: participant, action: z.enum(['listen', 'unlisten', 'ignore']).describe('"listen", "unlisten" or "ignore". Listening again ends an ignore.'), target,
|
|
98
|
-
}, async (square, { as, action, target }, control) => {
|
|
99
|
-
const self = await square.
|
|
100
|
-
return self[action](target,
|
|
63
|
+
}, async (square, { as, action, target }, control, identity) => {
|
|
64
|
+
const self = await selectOwnSeat(square, identity.owner, as);
|
|
65
|
+
return self[action](target, { observe: true }, control);
|
|
101
66
|
}, rendering((result, args) => renderListenerChange(result.observation?.location ?? '', args.action, result))),
|
|
102
67
|
tool('hold', 'Raise a hand to pause the conversation; lower it with release. While a hand is up, new words wait to land.', {
|
|
103
68
|
as: participant,
|
|
104
69
|
reason: z.string().optional().describe('Why you are pausing. Everyone sees it.'),
|
|
105
70
|
release: z.boolean().optional().describe('true lowers the raised hand so the conversation goes on.'),
|
|
106
|
-
}, async (square, { as, reason, release }, control) => {
|
|
107
|
-
const self = await square.
|
|
108
|
-
return release === true ? self.resume(
|
|
71
|
+
}, async (square, { as, reason, release }, control, identity) => {
|
|
72
|
+
const self = await selectOwnSeat(square, identity.owner, as);
|
|
73
|
+
return release === true ? self.resume({ observe: true }, control) : self.hold(reason, { observe: true }, control);
|
|
109
74
|
}, rendering((result, args) => renderLifecycleResult(result.observation?.location ?? '', args.release === true ? 'resume' : 'hold', result))),
|
|
110
|
-
tool('done', 'Step out of the square. Everyone sees you leave, and you stop listening to anyone. Leaving carries no words, so say goodbye with express first. Stay instead if someone may still need you; you can join again later.', { as: participant }, async (square, { as }, control) => (await square.
|
|
75
|
+
tool('done', 'Step out of the square. Everyone sees you leave, and you stop listening to anyone. Leaving carries no words, so say goodbye with express first. Stay instead if someone may still need you; you can join again later.', { as: participant }, async (square, { as }, control, identity) => (await selectOwnSeat(square, identity.owner, as)).done(undefined, { observe: true }, control), rendering((result) => renderLifecycleResult(result.observation?.location ?? '', 'done', result))),
|
|
111
76
|
tool('status', 'Look around the square: who is here, whether a hand is raised, the latest activity, and who you listen to.', {
|
|
112
77
|
as: z.string().optional().describe('Whose listening to show. Leave it out if you hold exactly one name here.'),
|
|
113
|
-
}, (square, { as }, control
|
|
78
|
+
}, async (square, { as }, control, identity) => {
|
|
79
|
+
const viewer = (await selectOwnSeat(square, identity.owner, as, { optional: true }))?.name;
|
|
80
|
+
return square.status({ viewer, observe: true }, control);
|
|
81
|
+
}, rendering((result) => renderStatusResult(result.observation?.location ?? '', result))),
|
|
114
82
|
];
|
|
115
83
|
export function squareToolFailure(error, signal) {
|
|
116
84
|
const normalized = signal?.aborted
|
package/dist/stream.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import { SquareError } from './model.js';
|
|
3
3
|
import { createArtifactPort, observeSquareRevision } from './artifact.js';
|
|
4
|
-
import { streamProjection, streamTailProjection } from './views.js';
|
|
4
|
+
import { parseRequiredActivityId, streamProjection, streamTailProjection } from './views.js';
|
|
5
5
|
/** Pull-based observation; return interrupts an outstanding pull before awaiting its cleanup. */
|
|
6
6
|
export function streamSquare(squarePath, options = {}, control, borrowed) {
|
|
7
7
|
return {
|
|
@@ -28,7 +28,7 @@ export function streamSquare(squarePath, options = {}, control, borrowed) {
|
|
|
28
28
|
}
|
|
29
29
|
signal.throwIfAborted();
|
|
30
30
|
const start = options.start ?? { kind: 'tail', last: 10 };
|
|
31
|
-
let cursor = start.kind === 'after' ? start.after : -1;
|
|
31
|
+
let cursor = start.kind === 'after' ? parseRequiredActivityId(start.after) : -1;
|
|
32
32
|
let initial = start.kind === 'tail' ? start : undefined;
|
|
33
33
|
const observer = await observeSquareRevision(squarePath);
|
|
34
34
|
const close = () => observer.close();
|
|
@@ -60,10 +60,9 @@ export function streamSquare(squarePath, options = {}, control, borrowed) {
|
|
|
60
60
|
await observer.hinted(generation, Infinity, signal);
|
|
61
61
|
continue;
|
|
62
62
|
}
|
|
63
|
-
for (const
|
|
63
|
+
for (const activity of projection.activities) {
|
|
64
64
|
signal.throwIfAborted();
|
|
65
|
-
yield
|
|
66
|
-
...(item.route === undefined ? {} : { route: item.route }) };
|
|
65
|
+
yield activity;
|
|
67
66
|
}
|
|
68
67
|
cursor = projection.cursor;
|
|
69
68
|
initial = undefined;
|
package/dist/views.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { type ActivityId } from './square-core.js';
|
|
2
2
|
import { type StoredAct } from './model.js';
|
|
3
3
|
import type { ActivityFilter, SquareRead } from './ports.js';
|
|
4
|
-
import type { Activity, ArchiveRecord, HistoryQuery, HistoryContinuation, ParticipantsResult, SquareSnapshot, EntryPresentation,
|
|
5
|
-
export type { EntryPresentation, ListPresentation, CatchPresence, StreamProjection } from './square-facade.js';
|
|
4
|
+
import type { Activity, ArchiveRecord, PerceivedActivity, HistoryQuery, HistoryContinuation, ParticipantsResult, SquareSnapshot, EntryPresentation, StatusResult, SquareObservation, CatchPresence } from './square-facade.js';
|
|
6
5
|
export declare const HISTORY_DEFAULT_LIMIT = 10;
|
|
7
6
|
export declare const HISTORY_MAX_LIMIT = 100;
|
|
8
7
|
export declare const NON_READ: readonly ["join", "done", "say", "hold", "resume", "listen", "ignore", "unlisten"];
|
|
9
8
|
/** The one StoredAct-to-public-Activity field mapping. Rejects read and actorless acts. */
|
|
10
9
|
export declare function toPublicActivity(stored: StoredAct): Activity;
|
|
10
|
+
export declare function toPerceivedActivity(facts: SquareRead, stored: StoredAct, viewer: string): PerceivedActivity;
|
|
11
11
|
export declare function parseRequiredActivityId(id: ActivityId): number;
|
|
12
12
|
/** Compile the public query directly to typed selectors; only search hydrates unselected bodies. */
|
|
13
13
|
export declare function selectHistory(facts: SquareRead, query: HistoryQuery, limit: number | null, kinds: ActivityFilter['kinds'], presentation?: boolean): {
|
|
@@ -19,20 +19,31 @@ export declare function selectHistory(facts: SquareRead, query: HistoryQuery, li
|
|
|
19
19
|
export declare function publicActivities(activities: readonly ArchiveRecord[]): Activity[];
|
|
20
20
|
export declare function snapshot(facts: SquareRead, now: number): SquareSnapshot;
|
|
21
21
|
export declare function recoveryObservation(facts: SquareRead, name: string, now: number): {
|
|
22
|
-
ownActivityCount: number;
|
|
23
|
-
hardCap: import("./model.js").HardCap;
|
|
24
|
-
holdReason?: string | undefined;
|
|
25
22
|
participant: string;
|
|
26
|
-
roster: string[];
|
|
27
23
|
activitySummaries: import("./square-facade.js").UnreadActivitySummary[];
|
|
28
24
|
standingCount: number;
|
|
29
25
|
held: boolean;
|
|
26
|
+
ownActivityCount: number;
|
|
27
|
+
hardCap: import("./model.js").HardCap;
|
|
30
28
|
};
|
|
31
29
|
export declare function entryPresentation(facts: SquareRead, name: string, lastN?: number | null): EntryPresentation;
|
|
32
|
-
type ParticipantQueryContext = Pick<SquareObservation, 'location' | 'now'
|
|
30
|
+
type ParticipantQueryContext = Pick<SquareObservation, 'location' | 'now'>;
|
|
33
31
|
export declare function queryParticipants(facts: SquareRead, context: ParticipantQueryContext, observe?: boolean): ParticipantsResult;
|
|
34
|
-
export declare function listPresentation(facts: SquareRead):
|
|
32
|
+
export declare function listPresentation(facts: SquareRead): {
|
|
33
|
+
context: readonly string[];
|
|
34
|
+
participants: string[];
|
|
35
|
+
activities: number;
|
|
36
|
+
};
|
|
35
37
|
export declare function queryStatus(facts: SquareRead, context: ParticipantQueryContext, viewer?: string, observe?: boolean): StatusResult;
|
|
36
38
|
export declare function catchPresence(facts: SquareRead, now: number): CatchPresence;
|
|
37
|
-
export declare function streamProjection(facts: SquareRead, cursor: number, recipient?: string):
|
|
38
|
-
|
|
39
|
+
export declare function streamProjection(facts: SquareRead, cursor: number, recipient?: string): {
|
|
40
|
+
activities: Activity[];
|
|
41
|
+
cursor: number;
|
|
42
|
+
hasMore: boolean;
|
|
43
|
+
};
|
|
44
|
+
export declare function streamTailProjection(facts: SquareRead, last?: number, recipient?: string): {
|
|
45
|
+
activities: Activity[];
|
|
46
|
+
cursor: number;
|
|
47
|
+
hasMore: boolean;
|
|
48
|
+
};
|
|
49
|
+
export {};
|
package/dist/views.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
2
2
|
import { compareParticipantActivity, coreParticipants, resolveKnownName, unreadActivitySummaries } from './decisions.js';
|
|
3
3
|
import { SquareError, nameKey } from './model.js';
|
|
4
|
-
import { inSquareCount
|
|
4
|
+
import { inSquareCount } from './runtime.js';
|
|
5
5
|
import { perceiveActivity } from './perception-projection.js';
|
|
6
6
|
import { compileSearchPattern } from './search.js';
|
|
7
7
|
const PAGE_SIZE = 100;
|
|
@@ -22,6 +22,11 @@ export function toPublicActivity(stored) {
|
|
|
22
22
|
mentions: stored.kind === 'say' ? stored.mentions ?? [] : [], ...('target' in stored ? { target: stored.target } : {}),
|
|
23
23
|
...(stored.kind === 'say' && stored.reply !== undefined ? { reply: formatActivityId(stored.reply) } : {}) };
|
|
24
24
|
}
|
|
25
|
+
export function toPerceivedActivity(facts, stored, viewer) {
|
|
26
|
+
const via = facts.directedAt(viewer, stored.index)?.route;
|
|
27
|
+
return { ...toPublicActivity(stored), perception: perceiveActivity(facts, stored, viewer),
|
|
28
|
+
...(via === undefined ? {} : { via }) };
|
|
29
|
+
}
|
|
25
30
|
export function parseRequiredActivityId(id) {
|
|
26
31
|
const index = parseActivityId(id);
|
|
27
32
|
if (index === undefined)
|
|
@@ -105,12 +110,6 @@ export function selectHistory(facts, query, limit, kinds, presentation = true) {
|
|
|
105
110
|
export function publicActivities(activities) {
|
|
106
111
|
return activities.flatMap((activity) => activity.kind === 'read' || activity.actor === undefined ? [] : [toPublicActivity(activity)]);
|
|
107
112
|
}
|
|
108
|
-
function statuses(facts, now) {
|
|
109
|
-
return coreParticipants(facts, now).filter((p) => p.state !== 'not joined').map((p) => {
|
|
110
|
-
const cursor = readCursor(facts, p.name);
|
|
111
|
-
return { name: p.name, state: p.state === 'done' ? 'done' : 'joined', consumedThrough: cursor < 0 ? null : formatActivityId(cursor), watching: p.presence === 'watching', listening: p.listening };
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
113
|
export function snapshot(facts, now) {
|
|
115
114
|
const header = facts.header(), hold = facts.hold();
|
|
116
115
|
const names = new Map(facts.roster().map((p) => [nameKey(p.name), p.name]));
|
|
@@ -118,30 +117,31 @@ export function snapshot(facts, now) {
|
|
|
118
117
|
return { context: [...header.preamble, ...header.warmup].join('\n'), actCount: facts.activityCount({ kinds: NON_READ }), hardCap: header.hardCap,
|
|
119
118
|
...(header.throttlePerMinute === undefined ? {} : { throttlePerMinute: header.throttlePerMinute }),
|
|
120
119
|
held: hold.active && hold.actor !== undefined ? { by: hold.actor, ...(hold.reason === undefined ? {} : { reason: hold.reason }) } : null,
|
|
121
|
-
participants:
|
|
120
|
+
participants: coreParticipants(facts, now).filter((p) => p.presence !== 'never-joined').map(({ name, presence, listening }) => ({ name, presence, listening })),
|
|
121
|
+
delivered(name, id) { return receipts.has(JSON.stringify([names.get(nameKey(name)) ?? name, parseRequiredActivityId(id)])); } };
|
|
122
122
|
}
|
|
123
123
|
export function recoveryObservation(facts, name, now) {
|
|
124
124
|
const known = resolveKnownName(facts, name), hold = facts.hold();
|
|
125
|
-
return { participant: known,
|
|
126
|
-
|
|
125
|
+
return { participant: known, activitySummaries: unreadActivitySummaries(facts, known, now), standingCount: inSquareCount(facts), held: hold.active,
|
|
126
|
+
ownActivityCount: facts.participant(known).sayCount, hardCap: facts.header().hardCap };
|
|
127
127
|
}
|
|
128
128
|
export function entryPresentation(facts, name, lastN = HISTORY_DEFAULT_LIMIT) {
|
|
129
129
|
const header = facts.header();
|
|
130
130
|
const recentActivities = selectHistory(facts, {}, lastN, ['say', 'done']).activities;
|
|
131
131
|
return { joinedBefore: facts.activityCount({ kinds: ['join'], actors: [name] }) > 1,
|
|
132
132
|
scene: header.warmup.join('\n').trim(),
|
|
133
|
-
joinContext: (header.preamble.at(-1) === '---' ? header.preamble.slice(0, -1) : header.preamble).join('\n').trim(),
|
|
134
|
-
|
|
135
|
-
sayNumbers: facts.sayOrdinals(recentActivities.map((a) => a.index)) };
|
|
133
|
+
joinContext: (header.preamble.at(-1) === '---' ? header.preamble.slice(0, -1) : header.preamble).join('\n').trim(),
|
|
134
|
+
recentActivities: recentActivities.map((activity) => ({ ...toPublicActivity(activity), perception: perceiveActivity(facts, activity, name) })) };
|
|
136
135
|
}
|
|
137
136
|
function classifyPresence(participants, now) {
|
|
138
|
-
const
|
|
137
|
+
const standing = (participant) => participant.presence === 'active' || participant.presence === 'watching';
|
|
138
|
+
const here = participants.filter((participant) => standing(participant) && (participant.presence === 'watching'
|
|
139
139
|
|| (participant.lastActiveAt !== undefined && now - participant.lastActiveAt <= STATUS_HERE_WINDOW_MS)));
|
|
140
140
|
const hereNames = new Set(here.map((participant) => participant.name));
|
|
141
141
|
return { orderedHereNames: [...here.filter((participant) => participant.presence === 'watching'), ...here.filter((participant) => participant.presence !== 'watching')].map((participant) => participant.name),
|
|
142
|
-
lingeringCount: participants.filter((participant) => participant
|
|
143
|
-
classificationByName: Object.fromEntries(participants.map((participant) => [participant.name, participant.
|
|
144
|
-
: participant.
|
|
142
|
+
lingeringCount: participants.filter((participant) => standing(participant) && !hereNames.has(participant.name)).length,
|
|
143
|
+
classificationByName: Object.fromEntries(participants.map((participant) => [participant.name, participant.presence === 'done' ? 'done'
|
|
144
|
+
: participant.presence === 'never-joined' ? 'not-joined' : participant.presence === 'away' ? 'away' : hereNames.has(participant.name) ? 'here' : 'lingering'])) };
|
|
145
145
|
}
|
|
146
146
|
export function queryParticipants(facts, context, observe = false) {
|
|
147
147
|
const participants = coreParticipants(facts, context.now);
|
|
@@ -158,61 +158,61 @@ export function listPresentation(facts) {
|
|
|
158
158
|
export function queryStatus(facts, context, viewer = '', observe = false) {
|
|
159
159
|
const participants = coreParticipants(facts, context.now);
|
|
160
160
|
const header = facts.header(), hold = facts.hold(), standingCount = inSquareCount(facts);
|
|
161
|
-
const listener = participants.find((p) => p.
|
|
161
|
+
const listener = participants.find((p) => p.presence !== 'never-joined' && nameKey(p.name) === nameKey(viewer));
|
|
162
162
|
const latest = facts.activityHeads({ kinds: ['say', 'done'] }, { edge: 'newest', limit: 1 }).items[0];
|
|
163
163
|
const latestAct = latest === undefined ? undefined : facts.activity(latest.index);
|
|
164
164
|
const data = {
|
|
165
165
|
hardCap: header.hardCap,
|
|
166
166
|
...(header.throttlePerMinute === undefined ? {} : { throttlePerMinute: header.throttlePerMinute }),
|
|
167
|
-
|
|
168
|
-
hold: hold.active ? {
|
|
167
|
+
held: hold.active ? {
|
|
169
168
|
...(hold.actor === undefined ? {} : { by: hold.actor }),
|
|
170
169
|
...(hold.reason === undefined ? {} : { reason: hold.reason }),
|
|
171
170
|
...(hold.at === undefined ? {} : { at: hold.at }),
|
|
172
171
|
} : null,
|
|
173
172
|
...(listener === undefined ? {} : { listening: listener.listening }),
|
|
174
|
-
participants: participants.filter((p) => p.
|
|
173
|
+
participants: participants.filter((p) => p.presence !== 'never-joined'), now: context.now,
|
|
175
174
|
...(latestAct === undefined || latestAct.actor === undefined || latestAct.kind === 'read' ? {} : { latestActivity: toPublicActivity(latestAct) }),
|
|
176
175
|
};
|
|
177
176
|
if (!observe)
|
|
178
177
|
return data;
|
|
179
178
|
const afterIndex = latestAct?.index ?? -1;
|
|
180
|
-
const joins = facts.activities(facts.activityHeads({ kinds: ['join'], afterIndex }, { edge: 'newest', limit: 3 }).items.map((h) => h.index)).map((activity) => ({
|
|
181
|
-
rejoined: facts.activityCount({ kinds: ['done'], actors: [activity.actor], actorMatch: 'exact', beforeIndex: activity.index }) > 0
|
|
179
|
+
const joins = facts.activities(facts.activityHeads({ kinds: ['join'], afterIndex }, { edge: 'newest', limit: 3 }).items.map((h) => h.index)).map((activity) => ({
|
|
180
|
+
rejoined: facts.activityCount({ kinds: ['done'], actors: [activity.actor], actorMatch: 'exact', beforeIndex: activity.index }) > 0,
|
|
181
|
+
activity: toPublicActivity(activity)
|
|
182
|
+
}));
|
|
182
183
|
const canonicalViewer = facts.participant(viewer)?.name ?? viewer;
|
|
183
184
|
return {
|
|
184
185
|
...data,
|
|
185
186
|
observation: {
|
|
186
|
-
|
|
187
|
+
location: context.location,
|
|
188
|
+
...classifyPresence(participants, context.now), standingCount,
|
|
187
189
|
knownParticipantCount: participants.length, viewer: canonicalViewer,
|
|
188
190
|
...(canonicalViewer === '' ? {} : { participant: canonicalViewer }),
|
|
189
191
|
joins, joinCount: facts.activityCount({ kinds: ['join'], afterIndex }),
|
|
190
192
|
attentionNames: participants.filter((p) => canonicalViewer === '' || nameKey(p.name) === nameKey(canonicalViewer)).map((p) => p.name),
|
|
191
193
|
...(latestAct === undefined ? {} : { latestPerception: perceiveActivity(facts, latestAct, canonicalViewer) }),
|
|
192
|
-
...(latestAct?.kind === 'say' ? { latestActNumber: facts.sayOrdinals([latestAct.index])[latestAct.index] } : {}),
|
|
193
194
|
},
|
|
194
195
|
};
|
|
195
196
|
}
|
|
196
197
|
export function catchPresence(facts, now) {
|
|
197
|
-
const participants = coreParticipants(facts, now).filter((participant) => participant.
|
|
198
|
+
const participants = coreParticipants(facts, now).filter((participant) => participant.presence === 'done' || participant.presence === 'away' || participant.presence === 'watching'
|
|
198
199
|
|| (participant.lastActiveAt !== undefined && now - participant.lastActiveAt <= PRESENCE_WINDOW_MS));
|
|
199
200
|
return { participants, now };
|
|
200
201
|
}
|
|
201
202
|
export function streamProjection(facts, cursor, recipient) {
|
|
202
|
-
const page = facts.activityHeads({ afterIndex: cursor }, { edge: 'oldest', limit: PAGE_SIZE });
|
|
203
|
-
const heads = page.items.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
return { activities: facts.activities(heads.map((h) => h.index)).map((activity) => {
|
|
208
|
-
const route = heads.find((h) => h.index === activity.index).route;
|
|
209
|
-
return { activity, ...(route === undefined ? {} : { route }) };
|
|
210
|
-
}), cursor: page.items.at(-1)?.index ?? cursor, hasMore: page.hasMore };
|
|
203
|
+
const page = facts.activityHeads({ afterIndex: cursor, kinds: NON_READ, actorRequired: true }, { edge: 'oldest', limit: PAGE_SIZE });
|
|
204
|
+
const heads = recipient === undefined ? page.items : page.items.filter((head) => facts.directedAt(recipient, head.index) !== undefined);
|
|
205
|
+
const activities = facts.activities(heads.map((head) => head.index)).map((activity) => recipient === undefined
|
|
206
|
+
? toPublicActivity(activity) : toPerceivedActivity(facts, activity, recipient));
|
|
207
|
+
return { activities, cursor: page.items.at(-1)?.index ?? cursor, hasMore: page.hasMore };
|
|
211
208
|
}
|
|
212
209
|
export function streamTailProjection(facts, last = HISTORY_DEFAULT_LIMIT, recipient) {
|
|
213
210
|
if (!Number.isSafeInteger(last) || last < 0 || last > PAGE_SIZE)
|
|
214
211
|
throw new SquareError('invalid_args', `Invalid stream tail: expected a non-negative safe integer no greater than ${PAGE_SIZE}.`);
|
|
215
|
-
const heads = last === 0 ? [] : recipient === undefined
|
|
216
|
-
|
|
217
|
-
|
|
212
|
+
const heads = last === 0 ? [] : recipient === undefined
|
|
213
|
+
? facts.activityHeads({ kinds: NON_READ, actorRequired: true }, { edge: 'newest', limit: last }).items
|
|
214
|
+
: facts.directed(recipient, {}, { edge: 'newest', limit: last }).items;
|
|
215
|
+
const activities = facts.activities(heads.map((head) => head.index)).map((activity) => recipient === undefined
|
|
216
|
+
? toPublicActivity(activity) : toPerceivedActivity(facts, activity, recipient));
|
|
217
|
+
return { activities, cursor: facts.header().lastActIndex, hasMore: false };
|
|
218
218
|
}
|