@astrosheep/square 0.3.14 → 0.3.16
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/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity-feed.d.ts +2 -3
- package/dist/activity-feed.js +6 -15
- package/dist/activity.js +1 -1
- package/dist/artifact.js +17 -26
- package/dist/attention-presentation.d.ts +13 -0
- package/dist/attention-presentation.js +22 -0
- package/dist/automatic-session.js +6 -8
- package/dist/boundary-presentation.js +35 -14
- package/dist/claude-hook.js +5 -3
- package/dist/cli/context.d.ts +4 -4
- package/dist/cli/context.js +25 -4
- package/dist/cli/harness-command.js +1 -1
- package/dist/cli/maintenance-commands.js +5 -4
- package/dist/cli/observation-commands.js +24 -16
- package/dist/cli/program.js +1 -1
- package/dist/cli/registry.js +4 -1
- package/dist/cli/square-commands.d.ts +7 -0
- package/dist/cli/square-commands.js +123 -27
- package/dist/codex-hook.js +5 -3
- package/dist/decisions.d.ts +19 -0
- package/dist/decisions.js +45 -12
- package/dist/delivery.d.ts +14 -21
- package/dist/delivery.js +40 -74
- package/dist/help.js +8 -5
- package/dist/inbox.js +1 -0
- package/dist/index.d.ts +1 -10
- package/dist/index.js +0 -32
- package/dist/landing.d.ts +12 -0
- package/dist/landing.js +35 -4
- package/dist/model.d.ts +8 -11
- package/dist/model.js +2 -2
- package/dist/notifications.js +35 -11
- package/dist/paseo-delivery.js +7 -4
- package/dist/paseo.d.ts +6 -0
- package/dist/paseo.js +5 -0
- package/dist/presence.d.ts +3 -0
- package/dist/presence.js +40 -10
- package/dist/presentation.d.ts +6 -2
- package/dist/presentation.js +22 -9
- package/dist/presented.d.ts +2 -0
- package/dist/presented.js +20 -0
- package/dist/registry.js +4 -1
- package/dist/routes.d.ts +5 -0
- package/dist/routes.js +17 -0
- package/dist/runtime.d.ts +4 -5
- package/dist/runtime.js +35 -18
- package/dist/square-core.d.ts +17 -0
- package/dist/square-core.js +51 -1
- package/dist/square-facade.d.ts +9 -1
- package/dist/square-wiring.d.ts +8 -0
- package/dist/square-wiring.js +32 -3
- package/dist/views.d.ts +9 -3
- package/dist/views.js +16 -14
- package/dist/wake-port.d.ts +4 -1
- package/dist/wake-port.js +5 -0
- package/dist/wakes.js +3 -5
- package/dist/watch.js +1 -0
- package/package.json +19 -3
- package/skills/brainstorm/SKILL.md +1 -1
- package/skills/square/.claude-plugin/plugin.json +1 -1
- package/skills/square/SKILL.md +1 -1
package/dist/delivery.js
CHANGED
|
@@ -1,36 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { audienceOf, formatActivityId, resolveAudience } from './square-core.js';
|
|
5
|
-
import { actId, isCurrentlyJoined, lastJoinIndex, matchesMentionTarget, resolveRosterName, rosterNames } from './runtime.js';
|
|
1
|
+
import { findParticipantName, sameName, } from './model.js';
|
|
2
|
+
import { audienceBefore, audienceOf, formatActivityId } from './square-core.js';
|
|
3
|
+
import { isCurrentlyJoined, lastJoinIndex, matchesMentionTarget, observationFor, recordObservation, resolveRosterName, rosterNames } from './runtime.js';
|
|
6
4
|
export function notificationMessageId(squarePath, actIndex) {
|
|
7
5
|
return `square:${squarePath}#${formatActivityId(actIndex)}`;
|
|
8
6
|
}
|
|
9
7
|
function canonicalRecipient(squareState, name) {
|
|
10
8
|
return resolveRosterName(squareState, name) ?? name;
|
|
11
9
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return
|
|
15
|
-
}
|
|
16
|
-
export function deliveryReceiptFromRuntime(runtime, recipient, actOrIndex) {
|
|
17
|
-
return runtime.deliveryReceipts[recipient]?.[actId(actOrIndex)];
|
|
18
|
-
}
|
|
19
|
-
export function isDeliveryDelivered(squareState, name, actOrIndex) {
|
|
20
|
-
return deliveryReceipt(squareState, name, actOrIndex)?.status === 'delivered';
|
|
21
|
-
}
|
|
22
|
-
export function recordDeliveredDelivery(squareState, name, actOrIndex, receipt) {
|
|
23
|
-
return recordDeliveredRuntime(squareState.runtime, canonicalRecipient(squareState, name), actOrIndex, receipt);
|
|
24
|
-
}
|
|
25
|
-
export function recordDeliveredRuntime(runtime, recipient, actOrIndex, receipt) {
|
|
26
|
-
const id = actId(actOrIndex);
|
|
27
|
-
if (deliveryReceiptFromRuntime(runtime, recipient, actOrIndex)?.status === 'delivered')
|
|
28
|
-
return false;
|
|
29
|
-
(runtime.deliveryReceipts[recipient] ??= {})[id] = { status: 'delivered', ...receipt };
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
export function markDeliveredDelivery(squareState, name, actOrIndex, at = Date.now()) {
|
|
33
|
-
return recordDeliveredDelivery(squareState, name, actOrIndex, { at });
|
|
10
|
+
export function isActivitySeen(squareState, name, actOrIndex) {
|
|
11
|
+
const index = typeof actOrIndex === 'number' ? actOrIndex : actOrIndex.index;
|
|
12
|
+
return observationFor(squareState, canonicalRecipient(squareState, name), index)?.state === 'seen';
|
|
34
13
|
}
|
|
35
14
|
/**
|
|
36
15
|
* Derive delivery behavior once from the decoded Square state.
|
|
@@ -38,15 +17,25 @@ export function markDeliveredDelivery(squareState, name, actOrIndex, at = Date.n
|
|
|
38
17
|
*/
|
|
39
18
|
export function deriveDeliveryModel(squareState) {
|
|
40
19
|
const roster = rosterNames(squareState).filter((name) => isCurrentlyJoined(squareState.acts, name));
|
|
20
|
+
const plannedByIndex = new Map();
|
|
41
21
|
let pendingByRecipient;
|
|
42
22
|
function plan(item) {
|
|
43
23
|
if (item.kind !== 'say')
|
|
44
24
|
return [];
|
|
25
|
+
const cached = plannedByIndex.get(item.index);
|
|
26
|
+
if (cached !== undefined)
|
|
27
|
+
return [...cached];
|
|
45
28
|
const sayItem = item;
|
|
46
29
|
const audience = audienceOf(sayItem);
|
|
47
|
-
const recipients =
|
|
48
|
-
const
|
|
49
|
-
|
|
30
|
+
const recipients = audienceBefore(squareState.acts, sayItem);
|
|
31
|
+
const planned = recipients.map((recipient) => {
|
|
32
|
+
const route = audience.kind === 'bell'
|
|
33
|
+
? 'bell'
|
|
34
|
+
: matchesMentionTarget(sayItem, recipient) ? 'mention' : 'attention';
|
|
35
|
+
return { item: sayItem, recipient, route };
|
|
36
|
+
});
|
|
37
|
+
plannedByIndex.set(item.index, planned);
|
|
38
|
+
return [...planned];
|
|
50
39
|
}
|
|
51
40
|
function pendingFor(requestedRecipient) {
|
|
52
41
|
const recipient = findParticipantName(roster, requestedRecipient);
|
|
@@ -58,14 +47,11 @@ export function deriveDeliveryModel(squareState) {
|
|
|
58
47
|
for (const act of squareState.acts) {
|
|
59
48
|
if (act.kind !== 'say')
|
|
60
49
|
continue;
|
|
61
|
-
const audience = audienceOf(act);
|
|
62
|
-
if (audience.kind === 'mentions' && audience.names.length === 0)
|
|
63
|
-
continue;
|
|
64
50
|
for (const planned of plan(act)) {
|
|
65
51
|
const joinedAt = joinedAfter.get(planned.recipient);
|
|
66
52
|
if (joinedAt === undefined || act.index <= joinedAt)
|
|
67
53
|
continue;
|
|
68
|
-
if (
|
|
54
|
+
if (isActivitySeen(squareState, planned.recipient, act.index))
|
|
69
55
|
continue;
|
|
70
56
|
pendingByRecipient.get(planned.recipient)?.push(planned);
|
|
71
57
|
}
|
|
@@ -78,14 +64,21 @@ export function deriveDeliveryModel(squareState) {
|
|
|
78
64
|
export function planActNotifications(squareState, item) {
|
|
79
65
|
return deriveDeliveryModel(squareState).plan(item);
|
|
80
66
|
}
|
|
81
|
-
|
|
82
|
-
|
|
67
|
+
export function perceiveActivity(squareState, item, viewer) {
|
|
68
|
+
if (item.kind !== 'say' || sameName(item.actor, viewer))
|
|
69
|
+
return 'full';
|
|
70
|
+
return deriveDeliveryModel(squareState).plan(item).some((planned) => sameName(planned.recipient, viewer))
|
|
71
|
+
? 'full'
|
|
72
|
+
: 'presence';
|
|
73
|
+
}
|
|
74
|
+
/** Mark only the notifications selected by the canonical catch projection as fully seen. */
|
|
75
|
+
export function markSeenNotifications(squareState, recipient, delivered, at = Date.now()) {
|
|
83
76
|
const deliveredIndexes = new Set(delivered.map((item) => item.index));
|
|
84
77
|
let changed = false;
|
|
85
78
|
for (const notification of deriveDeliveryModel(squareState).pendingFor(recipient)) {
|
|
86
79
|
if (!deliveredIndexes.has(notification.item.index))
|
|
87
80
|
continue;
|
|
88
|
-
changed =
|
|
81
|
+
changed = recordObservation(squareState, notification.recipient, notification.item.index, 'seen', at) || changed;
|
|
89
82
|
}
|
|
90
83
|
return changed;
|
|
91
84
|
}
|
|
@@ -97,49 +90,22 @@ export function matchesCatchFilter(activity, filter) {
|
|
|
97
90
|
!filter.participants.some((participant) => sameName(participant, activity.actor))) {
|
|
98
91
|
return false;
|
|
99
92
|
}
|
|
100
|
-
|
|
93
|
+
if (filter.mention === undefined)
|
|
94
|
+
return true;
|
|
95
|
+
return activity.recipients?.some((recipient) => sameName(recipient, filter.mention)) === true
|
|
96
|
+
|| matchesMentionTarget(activity, filter.mention);
|
|
101
97
|
}
|
|
102
98
|
/** True only when the live catch's own filters would deliver this notification. */
|
|
103
99
|
export function leaseOwnsNotification(lease, notification) {
|
|
100
|
+
if (lease.filter?.mention !== undefined
|
|
101
|
+
&& notification.route !== 'bell'
|
|
102
|
+
&& notification.recipient !== undefined
|
|
103
|
+
&& !sameName(lease.filter.mention, notification.recipient))
|
|
104
|
+
return false;
|
|
104
105
|
return matchesCatchFilter({
|
|
105
106
|
actor: notification.actor,
|
|
106
107
|
body: notification.body,
|
|
108
|
+
...(notification.recipient === undefined ? {} : { recipients: [notification.recipient] }),
|
|
107
109
|
...(notification.route === 'bell' ? { reach: 'bell' } : {}),
|
|
108
110
|
}, lease.filter ?? {});
|
|
109
111
|
}
|
|
110
|
-
const SQUARE_ROUTE_KEYS = ['name', 'squarePath', 'v'];
|
|
111
|
-
function publicSquareFromCwd(cwd) {
|
|
112
|
-
return path.resolve(cwd, '.square', 'PUBLIC.square');
|
|
113
|
-
}
|
|
114
|
-
export function parseSquareRoute(value) {
|
|
115
|
-
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
116
|
-
throw new SquareError('invalid_args', 'Malformed Square route');
|
|
117
|
-
}
|
|
118
|
-
const record = value;
|
|
119
|
-
const keys = Object.keys(record).sort();
|
|
120
|
-
if (keys.length !== SQUARE_ROUTE_KEYS.length || keys.some((key, index) => key !== SQUARE_ROUTE_KEYS[index])) {
|
|
121
|
-
throw new SquareError('invalid_args', 'Malformed Square route');
|
|
122
|
-
}
|
|
123
|
-
if (record.v !== 1 ||
|
|
124
|
-
typeof record.squarePath !== 'string' || record.squarePath === '' ||
|
|
125
|
-
typeof record.name !== 'string' || record.name === '') {
|
|
126
|
-
throw new SquareError('invalid_args', 'Malformed Square route');
|
|
127
|
-
}
|
|
128
|
-
return {
|
|
129
|
-
v: 1,
|
|
130
|
-
squarePath: record.squarePath,
|
|
131
|
-
name: record.name,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
export function captureRoute(input) {
|
|
135
|
-
const env = input.env ?? process.env;
|
|
136
|
-
const squarePath = canonicalSquarePath(publicSquareFromCwd(input.cwd));
|
|
137
|
-
const name = localParticipantName(squarePath, env);
|
|
138
|
-
if (name === undefined)
|
|
139
|
-
return null;
|
|
140
|
-
return Object.freeze({
|
|
141
|
-
v: 1,
|
|
142
|
-
squarePath,
|
|
143
|
-
name,
|
|
144
|
-
});
|
|
145
|
-
}
|
package/dist/help.js
CHANGED
|
@@ -18,12 +18,15 @@ const COMMANDS = [
|
|
|
18
18
|
{
|
|
19
19
|
names: ['express'], usage: '--as <name> express [-f|--force] [--no-wait] [--bell] [--reply <activity-id>] <activity | ->', usesSquare: true, group: 'participant',
|
|
20
20
|
summary: 'Speak, gesture, or do both.',
|
|
21
|
-
details: ['Reach:', ' @name Address someone in the square. They hear the body; everyone else sees you walk over.', " --bell Call every participant's attention to this activity without a mention.", '', 'Options:', ' -f, --force Express without first catching unread activity.', ' --no-wait If held or throttled, save a draft and return.', ' --reply <activity-id> Mark this activity as a reply to an earlier activity (for example act/12).'],
|
|
21
|
+
details: ['Reach:', ' @name Address someone in the square. They hear the body; everyone else sees you walk over.', " --bell Call every participant's attention to this activity without a mention.", ' listen A participant turned toward you also hears your activity.', '', 'Options:', ' -f, --force Express without first catching unread activity or attention etiquette.', ' --no-wait If held or throttled, save a draft and return.', ' --reply <activity-id> Mark this activity as a reply to an earlier activity (for example act/12).'],
|
|
22
22
|
},
|
|
23
|
+
{ names: ['listen'], usage: '--as <name> listen <participant>', usesSquare: true, group: 'participant', summary: 'Turn an ear toward one participant.' },
|
|
24
|
+
{ names: ['ignore'], usage: '--as <name> ignore <participant>', usesSquare: true, group: 'participant', summary: 'Turn away from one participant.' },
|
|
25
|
+
{ names: ['listening'], usage: '--as <name> listening', usesSquare: true, group: 'participant', summary: 'Show who you are turned toward.' },
|
|
23
26
|
{
|
|
24
27
|
names: ['catch'], usage: '--as <name> catch (--now | --idle <duration>) [--from <names>] [--mention [name]] [--replace]', usesSquare: true, group: 'participant',
|
|
25
28
|
summary: 'Catch what others have said or done since you last looked.',
|
|
26
|
-
details: ['Modes:', ' --now Catch up immediately.', ' --idle <duration> Wait for something relevant, or for quiet to last this long.', '', 'Filters:', ' --from <names> Match only comma-separated participants.', ' --mention [name] Match
|
|
29
|
+
details: ['Modes:', ' --now Catch up immediately.', ' --idle <duration> Wait for something relevant, or for quiet to last this long.', '', 'Filters:', ' --from <names> Match only comma-separated participants.', ' --mention [name] Match direct attention for a name, or your own name when omitted.', '', 'Recovery:', ' --replace Replace another active catch for this participant.'],
|
|
27
30
|
},
|
|
28
31
|
{ names: ['done'], usage: '--as <name> done [final | -]', usesSquare: true, group: 'participant', summary: 'Step out, optionally leaving a final note.' },
|
|
29
32
|
{
|
|
@@ -40,7 +43,7 @@ const COMMANDS = [
|
|
|
40
43
|
{
|
|
41
44
|
names: ['history'], usage: '[--as <name>] history [filters] [output]', usesSquare: true, group: 'participant',
|
|
42
45
|
summary: 'Read or search the archive without changing what you have caught.',
|
|
43
|
-
details: ['Filters:', ' --from <names> Match activities from participants.', ' --since <time>, --until <time> Match a time window.', ' --grep <regex> | --fixed <s> Search activity ids, participants, and original bodies.', ' --mention <name> Match
|
|
46
|
+
details: ['Filters:', ' --from <names> Match activities from participants.', ' --since <time>, --until <time> Match a time window.', ' --grep <regex> | --fixed <s> Search activity ids, participants, and original bodies.', ' --mention <name> Match direct attention for a participant.', ' --pending Match attention waiting for --as <name>.', ' --at <ids> Center on comma-separated activity ids; may repeat.', ' -B, -A, -C <N> Set non-negative context around every --at coordinate.', ' --after <id> Match activities after an id.', '', 'Results:', ' --limit <N> | --all Bound the newest matches (default 10).', ' --order <asc|desc> Set display order (default oldest first).', '', 'Output:', ' --full --json --format <fields>', ' Without --as, the default view shows complete activity bodies.'],
|
|
44
47
|
},
|
|
45
48
|
{ names: ['status'], usage: '[--as <name>] status', usesSquare: true, group: 'participant', summary: 'Show who is present and what happened most recently.' },
|
|
46
49
|
{ names: ['participants'], usage: 'participants', usesSquare: true, group: 'host', summary: 'Show the full participant roster and current states.' },
|
|
@@ -74,7 +77,7 @@ function isHelpFlag(value) {
|
|
|
74
77
|
}
|
|
75
78
|
export function renderGlobalHelp() {
|
|
76
79
|
const groups = [
|
|
77
|
-
{ key: 'participant', title: 'In the square:', order: ['join', 'express', 'catch', 'history', 'status', 'hold', 'resume', 'done'] },
|
|
80
|
+
{ key: 'participant', title: 'In the square:', order: ['join', 'express', 'listen', 'ignore', 'listening', 'catch', 'history', 'status', 'hold', 'resume', 'done'] },
|
|
78
81
|
{ key: 'host', title: 'Prepare and manage:', order: ['build', 'list', 'participants'] },
|
|
79
82
|
{ key: 'maintenance', title: 'Setup:', order: ['install', 'uninstall', 'doctor'] },
|
|
80
83
|
];
|
|
@@ -88,7 +91,7 @@ export function renderGlobalHelp() {
|
|
|
88
91
|
]);
|
|
89
92
|
return [
|
|
90
93
|
'Usage: square [--location <square>] [--as <name>] <command> [args...]',
|
|
91
|
-
'Environment: SQUARE_LOCATION sets the CLI location; SQUARE_PARTICIPANT_NAME sets the participant name.',
|
|
94
|
+
'Environment: SQUARE_LOCATION sets the CLI location; square-accessing commands require it or --location. SQUARE_PARTICIPANT_NAME sets the participant name.',
|
|
92
95
|
'CLI flags override environment values. Automatic provider sessions use only .square/PUBLIC.square.',
|
|
93
96
|
'',
|
|
94
97
|
...commandLines,
|
package/dist/inbox.js
CHANGED
|
@@ -14,6 +14,7 @@ export async function sessionInbox(sessionId) {
|
|
|
14
14
|
inbox.push({
|
|
15
15
|
name: projection.name,
|
|
16
16
|
squarePath: binding.squarePath,
|
|
17
|
+
ownerId: binding.ownerId,
|
|
17
18
|
notifications: [...projection.notifications],
|
|
18
19
|
...(projection.catchLease !== undefined ? { catchLease: projection.catchLease } : {}),
|
|
19
20
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
import { type SquareRoute } from './delivery.js';
|
|
2
|
-
import type { ExpressResult } from './square-facade.js';
|
|
3
1
|
export { Square } from './square-wiring.js';
|
|
4
2
|
export { SquareError } from './model.js';
|
|
5
|
-
export { captureRoute } from './delivery.js';
|
|
6
|
-
export type { SquareRoute } from './delivery.js';
|
|
7
3
|
export type { ActivityId } from './square-core.js';
|
|
8
|
-
export type { Activity, CatchOptions, CatchResult, ExpressOptions, ExpressResult, HistoryQuery, OpenOptions, Participant, ParticipantStatus, PerceivedActivity, SquareAtInput, SquareBuildInput, SquareSnapshot, SquareSource, WakeNotifier, } from './square-facade.js';
|
|
9
|
-
export interface RouteExpressOptions {
|
|
10
|
-
readonly as: string;
|
|
11
|
-
readonly body: string;
|
|
12
|
-
}
|
|
13
|
-
export declare function express(route: SquareRoute, options: RouteExpressOptions): Promise<ExpressResult>;
|
|
4
|
+
export type { Activity, CatchOptions, CatchResult, ExpressOptions, ExpressResult, HistoryQuery, ListenerChangeResult, OpenOptions, Participant, ParticipantStatus, PerceivedActivity, SquareAtInput, SquareBuildInput, SquareSnapshot, SquareSource, WakeNotifier, } from './square-facade.js';
|
package/dist/index.js
CHANGED
|
@@ -1,34 +1,2 @@
|
|
|
1
|
-
import { extractMentions } from './square-core.js';
|
|
2
|
-
import { parseSquareRoute } from './delivery.js';
|
|
3
|
-
import { sameName, SquareError } from './model.js';
|
|
4
|
-
import { Square } from './square-wiring.js';
|
|
5
|
-
import { wakeNotifierForSquare } from './notifications.js';
|
|
6
1
|
export { Square } from './square-wiring.js';
|
|
7
2
|
export { SquareError } from './model.js';
|
|
8
|
-
export { captureRoute } from './delivery.js';
|
|
9
|
-
function directedBody(body, recipient) {
|
|
10
|
-
if (body.trim() === '')
|
|
11
|
-
throw new SquareError('invalid_args', 'express body cannot be empty');
|
|
12
|
-
if (extractMentions(body).some((mention) => sameName(mention, recipient)))
|
|
13
|
-
return body;
|
|
14
|
-
return `${body}${/\s$/u.test(body) ? '' : ' '}@${recipient}`;
|
|
15
|
-
}
|
|
16
|
-
export async function express(route, options) {
|
|
17
|
-
const parsed = parseSquareRoute(route);
|
|
18
|
-
if (options === null || typeof options !== 'object' ||
|
|
19
|
-
typeof options.as !== 'string' || options.as.trim() === '' ||
|
|
20
|
-
typeof options.body !== 'string') {
|
|
21
|
-
throw new SquareError('invalid_args', 'Route express requires { as, body }');
|
|
22
|
-
}
|
|
23
|
-
const square = await Square.at({ path: parsed.squarePath, notifier: wakeNotifierForSquare(parsed.squarePath) });
|
|
24
|
-
try {
|
|
25
|
-
const recipient = (await square.participants()).find((participant) => participant.state === 'joined' && sameName(participant.name, parsed.name));
|
|
26
|
-
if (recipient === undefined)
|
|
27
|
-
throw new SquareError('invalid_args', `Unknown participant "${parsed.name}"`);
|
|
28
|
-
const sender = await square.join(options.as);
|
|
29
|
-
return sender.express(directedBody(options.body, recipient.name));
|
|
30
|
-
}
|
|
31
|
-
finally {
|
|
32
|
-
await square.close();
|
|
33
|
-
}
|
|
34
|
-
}
|
package/dist/landing.d.ts
CHANGED
|
@@ -4,7 +4,19 @@ export declare function join(square: OpenSquare, name: string): Promise<{
|
|
|
4
4
|
readonly name: string;
|
|
5
5
|
readonly activity: Activity | null;
|
|
6
6
|
}>;
|
|
7
|
+
/** Automatic presence distinguishes first entry, current presence, and completed presence. */
|
|
8
|
+
export declare function implicitJoin(square: OpenSquare, name: string): Promise<{
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly state: 'joined' | 'active' | 'done';
|
|
11
|
+
readonly activity: Activity | null;
|
|
12
|
+
}>;
|
|
7
13
|
export declare function express(square: OpenSquare, name: string, body: string, options?: ExpressOptions): Promise<ExpressResult>;
|
|
14
|
+
export interface ListenerChangeResult {
|
|
15
|
+
readonly activity: Activity | null;
|
|
16
|
+
}
|
|
17
|
+
export declare function listen(square: OpenSquare, actor: string, target: string): Promise<ListenerChangeResult>;
|
|
18
|
+
export declare function ignore(square: OpenSquare, actor: string, target: string): Promise<ListenerChangeResult>;
|
|
19
|
+
export declare function listening(square: OpenSquare, actor: string): Promise<readonly string[]>;
|
|
8
20
|
export declare function done(square: OpenSquare, name: string, body?: string): Promise<ExpressResult>;
|
|
9
21
|
export declare function hold(square: OpenSquare, name: string, reason?: string): Promise<ExpressResult>;
|
|
10
22
|
export declare function resume(square: OpenSquare, name: string): Promise<ExpressResult>;
|
package/dist/landing.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import { extractMentions, formatActivityId, parseActivityId } from './square-core.js';
|
|
2
|
-
import { coreDone, coreHold, coreResume, decideAct, decideJoin } from './decisions.js';
|
|
2
|
+
import { coreDone, coreHold, coreIgnore, coreListen, coreListening, coreResume, decideAct, decideImplicitJoin, decideJoin } from './decisions.js';
|
|
3
3
|
import { deriveDeliveryModel } from './delivery.js';
|
|
4
4
|
import { SquareError } from './model.js';
|
|
5
5
|
import { participantIdentity } from './participant-identity.js';
|
|
6
|
-
import { touchPresenceCursor } from './runtime.js';
|
|
7
6
|
function storeActs(state, acts) {
|
|
8
7
|
const stored = [];
|
|
9
8
|
for (const act of acts) {
|
|
10
9
|
const item = { ...act, index: state.runtime.nextActIndex };
|
|
11
10
|
state.runtime.nextActIndex += 1;
|
|
12
11
|
state.acts.push(item);
|
|
13
|
-
if (item.actor !== undefined)
|
|
14
|
-
touchPresenceCursor(state, item.actor, item.at, item.index);
|
|
15
12
|
stored.push(item);
|
|
16
13
|
}
|
|
17
14
|
return stored;
|
|
@@ -29,6 +26,7 @@ function exposeActivity(stored) {
|
|
|
29
26
|
id: formatActivityId(stored.index), at: stored.at, kind: stored.kind, actor: stored.actor,
|
|
30
27
|
...('body' in stored && stored.body !== undefined ? { body: stored.body } : {}),
|
|
31
28
|
mentions: stored.kind === 'say' ? extractMentions(stored.body) : [],
|
|
29
|
+
...('target' in stored ? { target: stored.target } : {}),
|
|
32
30
|
...(stored.kind === 'say' && stored.reply !== undefined ? { reply: formatActivityId(stored.reply) } : {}),
|
|
33
31
|
};
|
|
34
32
|
}
|
|
@@ -56,6 +54,17 @@ export async function join(square, name) {
|
|
|
56
54
|
});
|
|
57
55
|
return { name: committed.name, activity: committed.stored === null ? null : exposeActivity(committed.stored) };
|
|
58
56
|
}
|
|
57
|
+
/** Automatic presence distinguishes first entry, current presence, and completed presence. */
|
|
58
|
+
export async function implicitJoin(square, name) {
|
|
59
|
+
const now = square.clock();
|
|
60
|
+
const committed = await square.cell.transact((state) => {
|
|
61
|
+
const decision = decideImplicitJoin(state, name, now);
|
|
62
|
+
if (decision.joinAct === undefined)
|
|
63
|
+
return { result: { name: decision.joinedName, state: decision.state, stored: null } };
|
|
64
|
+
return { state, result: { name: decision.joinedName, state: decision.state, stored: committedActivity(storeActs(state, [decision.joinAct]), 'join') } };
|
|
65
|
+
});
|
|
66
|
+
return { name: committed.name, state: committed.state, activity: committed.stored === null ? null : exposeActivity(committed.stored) };
|
|
67
|
+
}
|
|
59
68
|
export async function express(square, name, body, options = {}) {
|
|
60
69
|
const now = square.clock();
|
|
61
70
|
const reply = options.reply === undefined ? undefined : parseRequiredActivityId(options.reply);
|
|
@@ -82,6 +91,28 @@ export async function express(square, name, body, options = {}) {
|
|
|
82
91
|
await wakeAfterCommit(square, committed.recipients, activity);
|
|
83
92
|
return { activity };
|
|
84
93
|
}
|
|
94
|
+
async function landListenerChange(square, verb, actor, target) {
|
|
95
|
+
const now = square.clock();
|
|
96
|
+
const stored = await square.cell.transact((state) => {
|
|
97
|
+
const act = verb === 'listen'
|
|
98
|
+
? coreListen(state, actor, target, now)
|
|
99
|
+
: coreIgnore(state, actor, target, now);
|
|
100
|
+
if (act === undefined)
|
|
101
|
+
return { result: null };
|
|
102
|
+
return { state, result: committedActivity(storeActs(state, [act]), verb) };
|
|
103
|
+
});
|
|
104
|
+
return { activity: stored === null ? null : exposeActivity(stored) };
|
|
105
|
+
}
|
|
106
|
+
export function listen(square, actor, target) {
|
|
107
|
+
return landListenerChange(square, 'listen', actor, target);
|
|
108
|
+
}
|
|
109
|
+
export function ignore(square, actor, target) {
|
|
110
|
+
return landListenerChange(square, 'ignore', actor, target);
|
|
111
|
+
}
|
|
112
|
+
export async function listening(square, actor) {
|
|
113
|
+
const { state } = await square.cell.read();
|
|
114
|
+
return coreListening(state, actor);
|
|
115
|
+
}
|
|
85
116
|
async function landCore(square, verb, actor, body = '') {
|
|
86
117
|
const now = square.clock();
|
|
87
118
|
const stored = await square.cell.transact((state) => {
|
package/dist/model.d.ts
CHANGED
|
@@ -40,14 +40,11 @@ export type StoredAct = Act & {
|
|
|
40
40
|
at: number;
|
|
41
41
|
};
|
|
42
42
|
export type StoredActHead = StoredAct extends infer T ? T extends StoredAct ? Omit<T, 'body' | 'through' | 'index'> : never : never;
|
|
43
|
-
export
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
export interface DeliveryReceipt {
|
|
48
|
-
/** Inject presentation lives only in the machine-local presented ledger. */
|
|
49
|
-
status: 'delivered';
|
|
43
|
+
export type ObservationState = 'notified' | 'seen';
|
|
44
|
+
export interface ActivityObservation {
|
|
45
|
+
state: ObservationState;
|
|
50
46
|
at: number;
|
|
47
|
+
ownerId?: string;
|
|
51
48
|
}
|
|
52
49
|
export interface WatchLeaseFilter {
|
|
53
50
|
participants?: string[];
|
|
@@ -61,7 +58,7 @@ export interface WatchLease {
|
|
|
61
58
|
expiresAt: number;
|
|
62
59
|
filter?: WatchLeaseFilter;
|
|
63
60
|
}
|
|
64
|
-
export type DirectedNotificationRoute = 'mention' | 'bell';
|
|
61
|
+
export type DirectedNotificationRoute = 'mention' | 'attention' | 'bell';
|
|
65
62
|
export interface InboxNotification {
|
|
66
63
|
actIndex: number;
|
|
67
64
|
actor: string;
|
|
@@ -72,6 +69,7 @@ export interface InboxNotification {
|
|
|
72
69
|
export interface InboxMembership {
|
|
73
70
|
name: string;
|
|
74
71
|
squarePath: string;
|
|
72
|
+
ownerId?: string;
|
|
75
73
|
notifications: InboxNotification[];
|
|
76
74
|
catchLease?: WatchLease;
|
|
77
75
|
}
|
|
@@ -84,8 +82,7 @@ export interface NotifyLease {
|
|
|
84
82
|
}
|
|
85
83
|
export interface SquareRuntimeState {
|
|
86
84
|
nextActIndex: number;
|
|
87
|
-
|
|
88
|
-
deliveryReceipts: Record<string, Record<string, DeliveryReceipt>>;
|
|
85
|
+
observations: Record<string, Record<string, ActivityObservation>>;
|
|
89
86
|
leases: Record<string, WatchLease>;
|
|
90
87
|
notifyLeases: Record<string, NotifyLease>;
|
|
91
88
|
}
|
|
@@ -132,7 +129,7 @@ export type PublicAct = Extract<StoredAct, {
|
|
|
132
129
|
kind: 'say' | 'done';
|
|
133
130
|
}>;
|
|
134
131
|
export type RoomChangeAct = Extract<StoredAct, {
|
|
135
|
-
kind: 'join' | 'done' | 'hold' | 'resume';
|
|
132
|
+
kind: 'join' | 'done' | 'hold' | 'resume' | 'listen' | 'ignore';
|
|
136
133
|
}>;
|
|
137
134
|
export interface HoldState {
|
|
138
135
|
active: boolean;
|
package/dist/model.js
CHANGED
|
@@ -40,7 +40,7 @@ export function findParticipantName(participants, name) {
|
|
|
40
40
|
return participants.find((participant) => sameName(participant, name));
|
|
41
41
|
}
|
|
42
42
|
export function validateName(name) {
|
|
43
|
-
if (!name || !/^[\p{L}\p{N}_-]
|
|
44
|
-
throw new SquareError('invalid_name', 'Invalid name: names must
|
|
43
|
+
if (!name || !/^[\p{L}\p{N}_-]+(?:\/[\p{L}\p{N}_-]+)*$/u.test(name)) {
|
|
44
|
+
throw new SquareError('invalid_name', 'Invalid name: names must contain non-empty slash-separated segments using only Unicode letters, digits, hyphens, and underscores.');
|
|
45
45
|
}
|
|
46
46
|
}
|
package/dist/notifications.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
3
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
5
4
|
import { fileURLToPath } from 'node:url';
|
|
6
5
|
import { leaseOwnsNotification, planActNotifications, } from './delivery.js';
|
|
@@ -9,10 +8,13 @@ import { hasPresentedAttention } from './presented.js';
|
|
|
9
8
|
import { SquareError } from './model.js';
|
|
10
9
|
import { SLEEP_MS, matchesMentionTarget } from './runtime.js';
|
|
11
10
|
import { parseActivityId } from './square-core.js';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
11
|
+
import { quoteShell } from './presentation.js';
|
|
12
|
+
import { renderAttentionPreview } from './attention-presentation.js';
|
|
14
13
|
import { lookupParticipant } from './registry.js';
|
|
14
|
+
import { retireWakeRoute } from './routes.js';
|
|
15
|
+
import { recordPresentedForOwner } from './presented.js';
|
|
15
16
|
import { openSquare } from './square-file-adapter.js';
|
|
17
|
+
import { markNotificationNotified } from './square-wiring.js';
|
|
16
18
|
import { closeOpenSquare } from './open-square.js';
|
|
17
19
|
import { entryPresentation, notificationDelivered, notificationForAct, pendingDeliveries, resolveParticipant } from './views.js';
|
|
18
20
|
import { claimNotificationLease, releaseNotificationLease, transitionNotificationLease } from './wakes.js';
|
|
@@ -32,14 +34,18 @@ export function wakeGraceMs(env = process.env) {
|
|
|
32
34
|
function catchCommand(squarePath, recipient) {
|
|
33
35
|
return `square --as ${quoteShell(recipient)} --location ${quoteShell(squarePath)} catch --now`;
|
|
34
36
|
}
|
|
35
|
-
function renderWakePayload(request) {
|
|
36
|
-
const display = request.squarePath.startsWith(homedir())
|
|
37
|
-
? `~${request.squarePath.slice(homedir().length)}`
|
|
38
|
-
: request.squarePath;
|
|
37
|
+
function renderWakePayload(request, body) {
|
|
39
38
|
return [
|
|
40
39
|
'<system-reminder source="square">',
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
renderAttentionPreview({
|
|
41
|
+
squarePath: request.squarePath,
|
|
42
|
+
actIndex: request.actIndex,
|
|
43
|
+
recipient: request.recipient,
|
|
44
|
+
actor: request.actor,
|
|
45
|
+
route: request.route,
|
|
46
|
+
body,
|
|
47
|
+
}),
|
|
48
|
+
'The native adapter presented this attention. If you have not acted on it, pull from the square yourself.',
|
|
43
49
|
`\`${catchCommand(request.squarePath, request.recipient)}\``,
|
|
44
50
|
'</system-reminder>',
|
|
45
51
|
].join('\n');
|
|
@@ -53,6 +59,7 @@ async function waitForCatch(route, request, body) {
|
|
|
53
59
|
actor: request.actor,
|
|
54
60
|
body,
|
|
55
61
|
route: request.route,
|
|
62
|
+
recipient: request.recipient,
|
|
56
63
|
}))
|
|
57
64
|
return false;
|
|
58
65
|
const deadline = Date.now() + 180_000;
|
|
@@ -107,6 +114,16 @@ export async function waitForDeliveredNotification(squarePath, name, ref, opts =
|
|
|
107
114
|
}
|
|
108
115
|
return false;
|
|
109
116
|
}
|
|
117
|
+
async function defaultWakeAdapters() {
|
|
118
|
+
try {
|
|
119
|
+
const { PaseoAdapter } = await import('./paseo-delivery.js');
|
|
120
|
+
return [new PaseoAdapter()];
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Paseo is an optional integration; a core-only install simply has no Paseo adapter.
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
110
127
|
async function claimNotifyLease(square, recipient, actIndex) {
|
|
111
128
|
const leaseId = randomUUID();
|
|
112
129
|
return claimNotificationLease(square, recipient, actIndex, leaseId, NOTIFY_LEASE_MS);
|
|
@@ -149,7 +166,7 @@ async function processNotification(squarePath, notification, opts) {
|
|
|
149
166
|
const evidence = await wakeEvidence(squarePath, notification.recipient, notification.item.index, dispatchAt, env);
|
|
150
167
|
if (!wakeIsEligible(evidence))
|
|
151
168
|
return;
|
|
152
|
-
const port = new WakePort(opts.adapters ??
|
|
169
|
+
const port = new WakePort(opts.adapters ?? await defaultWakeAdapters());
|
|
153
170
|
const request = {
|
|
154
171
|
squarePath,
|
|
155
172
|
actIndex: notification.item.index,
|
|
@@ -157,7 +174,7 @@ async function processNotification(squarePath, notification, opts) {
|
|
|
157
174
|
actor: notification.item.actor,
|
|
158
175
|
route: notification.route,
|
|
159
176
|
};
|
|
160
|
-
await port.dispatch(evidence.attemptableRoutes, renderWakePayload(request), {
|
|
177
|
+
await port.dispatch(evidence.attemptableRoutes, renderWakePayload(request, notification.item.body), {
|
|
161
178
|
nextAttemptN: () => nextWakeAttemptNumber(attention, { env, now: now() }),
|
|
162
179
|
beforeSend: async (route, attemptN) => {
|
|
163
180
|
if (await waitForCatch(route, request, notification.item.body))
|
|
@@ -190,9 +207,16 @@ async function processNotification(squarePath, notification, opts) {
|
|
|
190
207
|
...('message' in outcome ? { message: outcome.message } : {}),
|
|
191
208
|
...('diagnostic' in outcome && outcome.diagnostic !== undefined ? { diagnostic: outcome.diagnostic } : {}),
|
|
192
209
|
}, env);
|
|
210
|
+
if (outcome.outcome === 'accepted') {
|
|
211
|
+
await markNotificationNotified(square, notification.recipient, notification.item.index, route.ownerId, now());
|
|
212
|
+
recordPresentedForOwner(route.ownerId, squarePath, notification.recipient, notification.item.index, env, now());
|
|
213
|
+
}
|
|
193
214
|
if (outcome.outcome !== 'failed')
|
|
194
215
|
releaseLease = true;
|
|
195
216
|
},
|
|
217
|
+
invalidate: async (route) => {
|
|
218
|
+
retireWakeRoute(route, { env, at: now() });
|
|
219
|
+
},
|
|
196
220
|
});
|
|
197
221
|
}
|
|
198
222
|
finally {
|
package/dist/paseo-delivery.js
CHANGED
|
@@ -34,7 +34,7 @@ export class PaseoAdapter {
|
|
|
34
34
|
const agentId = address.agentId?.trim();
|
|
35
35
|
if (!agentId) {
|
|
36
36
|
return {
|
|
37
|
-
outcome: '
|
|
37
|
+
outcome: 'unavailable',
|
|
38
38
|
signature: 'invalid_address',
|
|
39
39
|
message: 'Paseo route has no agent id.',
|
|
40
40
|
diagnostic: diagnostic('selection', address, 'invalid_address'),
|
|
@@ -43,27 +43,30 @@ export class PaseoAdapter {
|
|
|
43
43
|
const discovery = (this.opts.discover ?? discoverPaseoAgents)();
|
|
44
44
|
if (discovery.error && discovery.agents.length === 0) {
|
|
45
45
|
return {
|
|
46
|
-
outcome: '
|
|
46
|
+
outcome: 'unavailable',
|
|
47
47
|
signature: discoveryRetryable(discovery.error) ? 'discovery_transient' : 'discovery_rejected',
|
|
48
48
|
message: `Paseo unavailable: ${discovery.error}`,
|
|
49
49
|
diagnostic: diagnostic('discovery', address, 'unavailable'),
|
|
50
|
+
retainRoute: true,
|
|
50
51
|
};
|
|
51
52
|
}
|
|
52
53
|
const agent = discovery.agents.find((candidate) => candidate.id === agentId);
|
|
53
54
|
if (agent === undefined || (agent.status !== 'idle' && agent.status !== 'running')) {
|
|
54
55
|
return {
|
|
55
|
-
outcome: '
|
|
56
|
+
outcome: 'unavailable',
|
|
56
57
|
signature: agent === undefined ? 'address_not_found' : 'agent_not_active',
|
|
57
58
|
message: agent === undefined ? 'The registered Paseo agent was not found.' : 'The registered Paseo agent is not idle or running.',
|
|
58
59
|
diagnostic: diagnostic('selection', address, agent === undefined ? 'not_found' : 'not_active'),
|
|
60
|
+
...(agent === undefined ? {} : { retainRoute: true }),
|
|
59
61
|
};
|
|
60
62
|
}
|
|
61
63
|
if (!(await (this.opts.waitForBoundary ?? waitForPaseoWakeBoundary)(agent))) {
|
|
62
64
|
return {
|
|
63
|
-
outcome: '
|
|
65
|
+
outcome: 'unavailable',
|
|
64
66
|
signature: 'boundary_unavailable',
|
|
65
67
|
message: 'Paseo did not reach the current tool boundary before the wake timeout.',
|
|
66
68
|
diagnostic: diagnostic('boundary', address, 'unavailable'),
|
|
69
|
+
retainRoute: true,
|
|
67
70
|
};
|
|
68
71
|
}
|
|
69
72
|
if (!(await beforeSend()))
|
package/dist/paseo.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Optional Paseo integration. Importing this entry requires the Paseo peer dependencies. */
|
|
2
|
+
export { PaseoAdapter } from './paseo-delivery.js';
|
|
3
|
+
export { PaseoWakeSendError, sendPaseoWake } from './wake-sink.js';
|
|
4
|
+
export { connectPaseoDaemon, paseoDaemonHosts, resolvePaseoDaemonTarget, } from './paseo-connection.js';
|
|
5
|
+
export { discoverPaseoAgents, waitForPaseoWakeBoundary } from './paseo-state.js';
|
|
6
|
+
export type { PaseoAgent } from './paseo-state.js';
|
package/dist/paseo.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Optional Paseo integration. Importing this entry requires the Paseo peer dependencies. */
|
|
2
|
+
export { PaseoAdapter } from './paseo-delivery.js';
|
|
3
|
+
export { PaseoWakeSendError, sendPaseoWake } from './wake-sink.js';
|
|
4
|
+
export { connectPaseoDaemon, paseoDaemonHosts, resolvePaseoDaemonTarget, } from './paseo-connection.js';
|
|
5
|
+
export { discoverPaseoAgents, waitForPaseoWakeBoundary } from './paseo-state.js';
|
package/dist/presence.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import type { OpenSquare } from './open-square.js';
|
|
2
2
|
import type { CatchOptions, CatchResult } from './square-facade.js';
|
|
3
3
|
export declare function catchUp(square: OpenSquare, name: string, options?: CatchOptions): Promise<CatchResult>;
|
|
4
|
+
/** Commit seen only for complete, actually rendered boundary bodies. */
|
|
5
|
+
export declare function markBoundarySeen(squarePath: string, name: string, ownerId: string | undefined, actIndexes: readonly number[], at?: number): Promise<void>;
|
|
6
|
+
export declare function markNotificationNotified(square: OpenSquare, name: string, actIndex: number, ownerId: string | undefined, at?: number): Promise<void>;
|