@astrosheep/square 0.3.46 → 0.3.48
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/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/skills/square/SKILL.md +6 -1
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity.js +1 -3
- package/dist/artifact.js +2 -1
- package/dist/boundary-presentation.js +7 -4
- package/dist/catch-decisions.d.ts +2 -6
- package/dist/catch-decisions.js +27 -12
- package/dist/cli/observation-commands.js +19 -3
- package/dist/cli/square-commands.js +2 -0
- package/dist/delivery.js +8 -18
- package/dist/help.js +2 -2
- package/dist/host-ledger-file-adapter.js +14 -1
- package/dist/model.d.ts +2 -1
- package/dist/participant-host.d.ts +19 -0
- package/dist/participant-host.js +78 -0
- package/dist/perception-projection.d.ts +15 -0
- package/dist/perception-projection.js +28 -0
- package/dist/presence.d.ts +0 -4
- package/dist/presence.js +0 -13
- package/dist/routes.js +2 -1
- package/dist/square-actions.d.ts +4 -8
- package/dist/square-actions.js +3 -83
- package/dist/square-facade.d.ts +1 -0
- package/dist/watch.js +1 -0
- package/extensions/square-pi.js +169 -93
- package/package.json +1 -1
- package/skills/square/SKILL.md +6 -1
- package/dist/landing.d.ts +0 -26
- package/dist/landing.js +0 -42
|
@@ -6,6 +6,8 @@ allowed-tools: Bash(square *)
|
|
|
6
6
|
|
|
7
7
|
# Square
|
|
8
8
|
|
|
9
|
+
Don't treat Square as a public dumping ground. Speak here only when you genuinely think someone else needs to know; otherwise you are needlessly interrupting them.
|
|
10
|
+
|
|
9
11
|
A square is a physical place where participants catch up and express thoughts or actions. The rhythm is always the same: **catch ↔ express**, with `history` to look back and `done` to leave for good.
|
|
10
12
|
|
|
11
13
|
```text
|
|
@@ -74,13 +76,16 @@ Every human-CLI `express` must choose exactly one reach mode: one or more `--men
|
|
|
74
76
|
`catch` takes in the directed activity addressed to you since you last looked: mentions, bells, and bare activities from participants you are listening to.
|
|
75
77
|
|
|
76
78
|
```bash
|
|
79
|
+
square --location <square> --as <name> catch --id act/12 # take in exactly this activity, with its full body
|
|
77
80
|
square --location <square> --as <name> catch --now # take in what is pending
|
|
78
81
|
square --location <square> --as <name> catch --idle 30m # wait until something relevant lands, or 30m of quiet
|
|
79
82
|
square --location <square> --as <name> catch --now --mention # take in pending mentions
|
|
80
83
|
square --location <square> --as <name> catch --now --from <names> # take in pending activity from named participants
|
|
81
84
|
```
|
|
82
85
|
|
|
83
|
-
|
|
86
|
+
`catch --id act/12` immediately returns that activity if it was addressed to you and marks only it seen, leaving other unread activity pending. Repeating it returns the same full body. It accepts optional `--now`, but not `--idle`, `--from`, `--mention`, `--limit` or `--replace`. Unlike read-only `history`, it consumes the selected activity.
|
|
87
|
+
|
|
88
|
+
Without `--id`, every catch needs exactly one mode: `--now` or `--idle <duration>`. `--mention` and `--from` filter either mode; they do not replace it. Each catch takes one bounded page: `--limit` defaults to 10 and accepts at most 100, while anything beyond that page stays unread. Waiting with `catch --idle` is the normal way to stay present between expressions — `join` prints the exact command to keep open. Do not build a polling loop.
|
|
84
89
|
|
|
85
90
|
## Listen
|
|
86
91
|
|
package/dist/activity.js
CHANGED
|
@@ -79,14 +79,12 @@ export async function cmdActivity(squarePath, name, activity, resolveBody, opts)
|
|
|
79
79
|
const pendingPublic = before.pendingPublic;
|
|
80
80
|
const pendingRoomChanges = before.pendingRoomChanges;
|
|
81
81
|
try {
|
|
82
|
-
|
|
82
|
+
await participant.express(body, {
|
|
83
83
|
force,
|
|
84
84
|
...(opts.mentions === undefined ? {} : { mentions: opts.mentions }),
|
|
85
85
|
...(reach === undefined ? {} : { reach }),
|
|
86
86
|
...(opts.reply === undefined ? {} : { reply: formatActivityId(opts.reply) }),
|
|
87
87
|
});
|
|
88
|
-
if (result.delivery?.notCapable)
|
|
89
|
-
process.stderr.write(`! wake not-capable: ${result.delivery.notCapable}\n`);
|
|
90
88
|
const freshSquare = await openSquare(squarePath, { clock: nowMs });
|
|
91
89
|
const fresh = await activityPresentation(freshSquare, knownName).finally(() => closeOpenSquare(freshSquare));
|
|
92
90
|
const headerCount = fresh.participantCount;
|
package/dist/artifact.js
CHANGED
|
@@ -150,7 +150,8 @@ async function createTemporaryArtifact(squarePath) {
|
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
async function syncTemporaryArtifact(temporary) {
|
|
153
|
-
|
|
153
|
+
// Windows FlushFileBuffers requires a writable handle; r+ never creates a missing file.
|
|
154
|
+
const handle = await fs.promises.open(temporary, 'r+');
|
|
154
155
|
try {
|
|
155
156
|
await handle.sync();
|
|
156
157
|
}
|
|
@@ -30,6 +30,9 @@ export function pendingAtBoundary(inbox) {
|
|
|
30
30
|
export function renderPendingAtBoundary(inbox) {
|
|
31
31
|
return renderBoundary(inbox).context;
|
|
32
32
|
}
|
|
33
|
+
function frameBoundary(lines) {
|
|
34
|
+
return `\n${lines.join('\n')}\n`;
|
|
35
|
+
}
|
|
33
36
|
function renderBoundary(inbox) {
|
|
34
37
|
const count = pendingCount(inbox);
|
|
35
38
|
const noun = count === 1 ? 'notification' : 'notifications';
|
|
@@ -52,7 +55,7 @@ function renderBoundary(inbox) {
|
|
|
52
55
|
}),
|
|
53
56
|
].join('\n');
|
|
54
57
|
const omittedAfter = omitted + queued.length - index - 1;
|
|
55
|
-
const prospective = [
|
|
58
|
+
const prospective = frameBoundary([
|
|
56
59
|
header,
|
|
57
60
|
...blocks,
|
|
58
61
|
block,
|
|
@@ -60,7 +63,7 @@ function renderBoundary(inbox) {
|
|
|
60
63
|
? [`… ${omittedAfter} ${omittedAfter === 1 ? 'notification' : 'notifications'} omitted.`]
|
|
61
64
|
: []),
|
|
62
65
|
...footer,
|
|
63
|
-
]
|
|
66
|
+
]);
|
|
64
67
|
if (prospective.length > CONTEXT_MAX) {
|
|
65
68
|
omitted += 1;
|
|
66
69
|
continue;
|
|
@@ -73,14 +76,14 @@ function renderBoundary(inbox) {
|
|
|
73
76
|
});
|
|
74
77
|
}
|
|
75
78
|
return {
|
|
76
|
-
context: [
|
|
79
|
+
context: frameBoundary([
|
|
77
80
|
header,
|
|
78
81
|
...blocks,
|
|
79
82
|
...(omitted > 0
|
|
80
83
|
? [`… ${omitted} ${omitted === 1 ? 'notification' : 'notifications'} omitted.`]
|
|
81
84
|
: []),
|
|
82
85
|
...footer,
|
|
83
|
-
]
|
|
86
|
+
]),
|
|
84
87
|
complete,
|
|
85
88
|
};
|
|
86
89
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Perception } from './square-core.js';
|
|
2
|
+
import { type PerceptionProjection } from './perception-projection.js';
|
|
2
3
|
import { type SquareState, type StoredAct } from './model.js';
|
|
3
4
|
import type { CatchOptions } from './square-facade.js';
|
|
4
5
|
export interface CatchDecision {
|
|
@@ -11,11 +12,6 @@ export interface CatchDecision {
|
|
|
11
12
|
}
|
|
12
13
|
export declare const CATCH_DEFAULT_LIMIT = 10;
|
|
13
14
|
export declare const CATCH_MAX_LIMIT = 100;
|
|
14
|
-
export
|
|
15
|
-
cursorFor(name: string): number;
|
|
16
|
-
directedTo(activity: StoredAct, name: string): boolean;
|
|
17
|
-
perceive(activity: StoredAct, name: string): Perception;
|
|
18
|
-
isSeen(name: string, index: number): boolean;
|
|
19
|
-
}
|
|
15
|
+
export type CatchProjection = Pick<PerceptionProjection, 'cursorFor' | 'directedTo' | 'perceive' | 'isSeen'>;
|
|
20
16
|
/** Pure catch selection and acknowledgement over one SquareState snapshot. */
|
|
21
17
|
export declare function decideCatch(state: SquareState, requestedName: string, options: CatchOptions, at: number, project?: (state: SquareState) => CatchProjection): CatchDecision;
|
package/dist/catch-decisions.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { audienceOf, formatActivityId,
|
|
2
|
-
import {
|
|
1
|
+
import { audienceOf, formatActivityId, parseActivityId } from './square-core.js';
|
|
2
|
+
import { derivePerceptionProjection } from './perception-projection.js';
|
|
3
|
+
import { matchesMentionTarget, recordObservation } from './runtime.js';
|
|
3
4
|
import { participantIdentity } from './participant-identity.js';
|
|
4
5
|
import { SquareError, sameName, validateName } from './model.js';
|
|
5
6
|
import { resolveRosterName } from './runtime.js';
|
|
@@ -12,15 +13,6 @@ function catchLimit(value) {
|
|
|
12
13
|
}
|
|
13
14
|
return limit;
|
|
14
15
|
}
|
|
15
|
-
function defaultProjection(state) {
|
|
16
|
-
const landed = replayLandedAudiences(state.acts);
|
|
17
|
-
return {
|
|
18
|
-
cursorFor: (name) => readCursor(state, name, landed),
|
|
19
|
-
directedTo: (activity, name) => activity.kind === 'say' && landed.includes(activity, name),
|
|
20
|
-
perceive: (activity, name) => activity.kind !== 'say' || sameName(activity.actor, name) || landed.includes(activity, name) ? 'full' : 'presence',
|
|
21
|
-
isSeen: (name, index) => observationFor(state, name, index)?.state === 'seen',
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
16
|
function resolveCatchName(state, requestedName) {
|
|
25
17
|
validateName(requestedName);
|
|
26
18
|
const known = resolveRosterName(state, requestedName);
|
|
@@ -30,10 +22,33 @@ function resolveCatchName(state, requestedName) {
|
|
|
30
22
|
return known;
|
|
31
23
|
}
|
|
32
24
|
/** Pure catch selection and acknowledgement over one SquareState snapshot. */
|
|
33
|
-
export function decideCatch(state, requestedName, options, at, project =
|
|
25
|
+
export function decideCatch(state, requestedName, options, at, project = derivePerceptionProjection) {
|
|
34
26
|
const viewer = resolveCatchName(state, requestedName);
|
|
35
27
|
const limit = catchLimit(options.limit);
|
|
36
28
|
const delivery = project(state);
|
|
29
|
+
if (options.id !== undefined) {
|
|
30
|
+
if (['idle', 'from', 'mention', 'limit'].some((key) => Object.hasOwn(options, key))) {
|
|
31
|
+
throw new SquareError('invalid_args', 'Catch id cannot be combined with idle, from, mention or limit.');
|
|
32
|
+
}
|
|
33
|
+
const index = parseActivityId(options.id);
|
|
34
|
+
if (index === undefined)
|
|
35
|
+
throw new SquareError('invalid_args', 'Invalid catch id: expected an activity id like act/12.');
|
|
36
|
+
const activity = state.acts.find((item) => item.index === index);
|
|
37
|
+
if (activity === undefined || activity.kind !== 'say' || !delivery.directedTo(activity, viewer)) {
|
|
38
|
+
throw new SquareError('invalid_args', 'That activity is not available to catch.');
|
|
39
|
+
}
|
|
40
|
+
const perception = delivery.perceive(activity, viewer);
|
|
41
|
+
const changed = recordObservation(state, viewer, index, 'seen', at);
|
|
42
|
+
const cursor = delivery.cursorFor(viewer);
|
|
43
|
+
return {
|
|
44
|
+
viewer,
|
|
45
|
+
delivered: [activity],
|
|
46
|
+
perceptions: new Map([[index, perception]]),
|
|
47
|
+
consumedThrough: cursor < 0 ? null : formatActivityId(cursor),
|
|
48
|
+
changed,
|
|
49
|
+
remaining: state.acts.filter((item) => item.kind === 'say' && delivery.directedTo(item, viewer) && !delivery.isSeen(viewer, item.index)).length,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
37
52
|
const from = options.from;
|
|
38
53
|
const mentionOnly = options.mention === true;
|
|
39
54
|
const delivered = [];
|
|
@@ -86,12 +86,22 @@ export const catchCommand = {
|
|
|
86
86
|
const name = requireParticipant(context.name);
|
|
87
87
|
let idleMs;
|
|
88
88
|
let mention;
|
|
89
|
-
let limit
|
|
89
|
+
let limit;
|
|
90
|
+
let id;
|
|
90
91
|
let replace = false;
|
|
91
92
|
let now = false;
|
|
92
93
|
const participants = [];
|
|
93
94
|
for (let index = 0; index < argv.length; index++) {
|
|
94
|
-
if (argv[index] === '--
|
|
95
|
+
if (argv[index] === '--id') {
|
|
96
|
+
if (id !== undefined)
|
|
97
|
+
fail('✕ catch accepts one --id\n» square catch --help');
|
|
98
|
+
const value = requireValue(argv, index, '--id');
|
|
99
|
+
if (parseActivityId(value) === undefined)
|
|
100
|
+
fail('✕ invalid --id: expected an activity id like act/12\n» square catch --help');
|
|
101
|
+
id = value;
|
|
102
|
+
index += 1;
|
|
103
|
+
}
|
|
104
|
+
else if (argv[index] === '--from') {
|
|
95
105
|
participants.push(...parseNameList(requireValue(argv, index, argv[index]), argv[index]));
|
|
96
106
|
index += 1;
|
|
97
107
|
}
|
|
@@ -113,6 +123,12 @@ export const catchCommand = {
|
|
|
113
123
|
else
|
|
114
124
|
fail(`✕ catch does not know ${argv[index]}\n» square catch --help`);
|
|
115
125
|
}
|
|
126
|
+
if (id !== undefined) {
|
|
127
|
+
if (argv.some((flag) => ['--idle', '--from', '--mention', '--limit', '--replace'].includes(flag))) {
|
|
128
|
+
fail('✕ --id cannot be combined with --idle, --from, --mention, --limit or --replace\n» square catch --help');
|
|
129
|
+
}
|
|
130
|
+
return { id, now: true };
|
|
131
|
+
}
|
|
116
132
|
if (now === (idleMs !== undefined))
|
|
117
133
|
fail('catch requires exactly one mode: --now or --idle <duration>.');
|
|
118
134
|
if (replace && now)
|
|
@@ -120,7 +136,7 @@ export const catchCommand = {
|
|
|
120
136
|
return {
|
|
121
137
|
...(participants.length > 0 ? { participants } : {}),
|
|
122
138
|
...(mention === undefined ? {} : { mention }),
|
|
123
|
-
limit,
|
|
139
|
+
limit: limit ?? CATCH_DEFAULT_LIMIT,
|
|
124
140
|
...(idleMs === undefined ? {} : { idleMs }),
|
|
125
141
|
...(replace ? { replace } : {}),
|
|
126
142
|
...(now ? { now } : {}),
|
|
@@ -147,6 +147,8 @@ export const joinCommand = {
|
|
|
147
147
|
: `✓ you banished the original ${participantIdentity(joinedName)} — the name is yours`;
|
|
148
148
|
const output = [
|
|
149
149
|
entryLine,
|
|
150
|
+
'',
|
|
151
|
+
"Don't treat Square as a public dumping ground. Speak here only when you genuinely think someone else needs to know; otherwise you are needlessly interrupting them.",
|
|
150
152
|
...(reconnect || scene === '' ? [] : ['', scene]),
|
|
151
153
|
...(isRejoin || contextText === '' ? [] : ['', 'context', contextText]),
|
|
152
154
|
...(isRejoin || activities === '' ? [] : ['', 'recent activity', activities]),
|
package/dist/delivery.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { findParticipantName, sameName, } from './model.js';
|
|
2
2
|
import { audienceOf, formatActivityId, replayLandedAudiences } from './square-core.js';
|
|
3
|
-
import {
|
|
3
|
+
import { derivePerceptionProjection } from './perception-projection.js';
|
|
4
|
+
import { matchesMentionTarget, recordObservation } from './runtime.js';
|
|
4
5
|
export function notificationMessageId(squarePath, actIndex) {
|
|
5
6
|
return `square:${squarePath}#${formatActivityId(actIndex)}`;
|
|
6
7
|
}
|
|
@@ -13,16 +14,13 @@ export function isActivitySeen(squareState, name, actOrIndex) {
|
|
|
13
14
|
*/
|
|
14
15
|
export function deriveDeliveryModel(squareState) {
|
|
15
16
|
const landed = replayLandedAudiences(squareState.acts);
|
|
17
|
+
const perception = derivePerceptionProjection(squareState, landed);
|
|
16
18
|
const roster = [...landed.joined];
|
|
17
19
|
const plannedByIndex = new Map();
|
|
18
20
|
let pendingByRecipient;
|
|
19
|
-
function canonicalRecipient(name) {
|
|
20
|
-
return landed.resolveParticipant(name) ?? name;
|
|
21
|
-
}
|
|
22
21
|
function isSeen(requestedRecipient, actOrIndex) {
|
|
23
|
-
const recipient = canonicalRecipient(requestedRecipient);
|
|
24
22
|
const index = typeof actOrIndex === 'number' ? actOrIndex : actOrIndex.index;
|
|
25
|
-
return
|
|
23
|
+
return perception.isSeen(requestedRecipient, index);
|
|
26
24
|
}
|
|
27
25
|
function plan(item) {
|
|
28
26
|
if (item.kind !== 'say')
|
|
@@ -64,25 +62,17 @@ export function deriveDeliveryModel(squareState) {
|
|
|
64
62
|
}
|
|
65
63
|
return [...(pendingByRecipient.get(recipient) ?? [])];
|
|
66
64
|
}
|
|
67
|
-
function directedTo(item, recipient) {
|
|
68
|
-
return item.kind === 'say' && landed.includes(item, recipient);
|
|
69
|
-
}
|
|
70
|
-
function perceive(item, viewer) {
|
|
71
|
-
if (item.kind !== 'say' || sameName(item.actor, viewer))
|
|
72
|
-
return 'full';
|
|
73
|
-
return directedTo(item, viewer) ? 'full' : 'presence';
|
|
74
|
-
}
|
|
75
65
|
return {
|
|
76
66
|
plan,
|
|
77
67
|
pendingFor,
|
|
78
|
-
directedTo,
|
|
79
|
-
perceive,
|
|
80
|
-
cursorFor:
|
|
68
|
+
directedTo: perception.directedTo,
|
|
69
|
+
perceive: perception.perceive,
|
|
70
|
+
cursorFor: perception.cursorFor,
|
|
81
71
|
isSeen,
|
|
82
72
|
knownParticipant: (name) => landed.resolveParticipant(name),
|
|
83
73
|
participants: () => landed.participants,
|
|
84
74
|
joinedRecipients: () => roster,
|
|
85
|
-
replayedActivityCount:
|
|
75
|
+
replayedActivityCount: perception.replayedActivityCount,
|
|
86
76
|
};
|
|
87
77
|
}
|
|
88
78
|
export function planActNotifications(squareState, item, delivery = deriveDeliveryModel(squareState)) {
|
package/dist/help.js
CHANGED
|
@@ -24,9 +24,9 @@ const COMMANDS = [
|
|
|
24
24
|
{ names: ['ignore'], usage: '--as <name> ignore <participant>', usesSquare: true, group: 'participant', summary: 'Ignore one participant\'s future mentions and bare says.' },
|
|
25
25
|
{ names: ['listening'], usage: '--as <name> listening', usesSquare: true, group: 'participant', summary: 'Show who you are turned toward.', details: ['Listening is future-only: the edge is fixed when a say lands; it never rewrites history.'] },
|
|
26
26
|
{
|
|
27
|
-
names: ['catch'], usage: '--as <name> catch (--now | --idle <duration>) [--from <names>] [--mention] [--limit <count>] [--replace]', usesSquare: true, group: 'participant',
|
|
27
|
+
names: ['catch'], usage: '--as <name> catch (--id <activity-id> [--now] | --now | --idle <duration>) [--from <names>] [--mention] [--limit <count>] [--replace]', usesSquare: true, group: 'participant',
|
|
28
28
|
summary: 'Catch directed conversation since you last looked.',
|
|
29
|
-
details: ['Modes:', ' --now Catch up immediately.', ' --idle <duration> Wait for something relevant, or for quiet to last this long.', '', 'Attention:', ' Mentions arrive when you are addressed; bells arrive for everyone; bare says require listen.', ' listen and ignore are future-only and fixed when each say lands.', '', 'Filters:', ' --from <names> Match only comma-separated participants.', ' --mention Match direct attention for your current participant (and bells).', ' --limit <count> Return one page (default 10, maximum 100).', '', 'Recovery:', ' --replace Replace another active catch for this participant.'],
|
|
29
|
+
details: ['Modes:', ' --id <activity-id> Catch exactly one activity addressed to you, with its full body; implies --now.', ' Marks only this activity seen; repeats return it again. No filters, limit, idle or replace.', ' history stays read-only; catch --id consumes the selected activity.', ' --now Catch up immediately.', ' --idle <duration> Wait for something relevant, or for quiet to last this long.', '', 'Attention:', ' Mentions arrive when you are addressed; bells arrive for everyone; bare says require listen.', ' listen and ignore are future-only and fixed when each say lands.', '', 'Filters:', ' --from <names> Match only comma-separated participants.', ' --mention Match direct attention for your current participant (and bells).', ' --limit <count> Return one page (default 10, maximum 100).', '', 'Recovery:', ' --replace Replace another active catch for this participant.'],
|
|
30
30
|
},
|
|
31
31
|
{ names: ['done'], usage: '--as <name> done [final | -]', usesSquare: true, group: 'participant', summary: 'Step out, optionally leaving a final note.' },
|
|
32
32
|
{
|
|
@@ -2,11 +2,24 @@ import { promises as fs } from 'node:fs';
|
|
|
2
2
|
import fsSync from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { withFileLock } from './file-lock.js';
|
|
5
|
+
import { withFileLock as acquireFileLock } from './file-lock.js';
|
|
6
6
|
import { nameKey } from './model.js';
|
|
7
7
|
import { isCurrentlyJoined } from './runtime.js';
|
|
8
8
|
const LOCK = { retryMs: 10 };
|
|
9
9
|
const RETENTION = 7 * 86400000;
|
|
10
|
+
// Prepare before locking: lock databases and their journals are local runtime data too.
|
|
11
|
+
async function withFileLock(file, options, fn) {
|
|
12
|
+
const directory = path.dirname(file);
|
|
13
|
+
await fs.mkdir(directory, { recursive: true });
|
|
14
|
+
try {
|
|
15
|
+
await fs.writeFile(path.join(directory, '.gitignore'), '*\n', { flag: 'wx' });
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error.code !== 'EEXIST')
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
return acquireFileLock(file, options, fn);
|
|
22
|
+
}
|
|
10
23
|
async function canon(value) { const absolute = path.resolve(value); try {
|
|
11
24
|
return await fs.realpath(absolute);
|
|
12
25
|
}
|
package/dist/model.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Act } from './square-core.js';
|
|
1
|
+
import type { Act, ActivityId } from './square-core.js';
|
|
2
2
|
export { formatActivityId, parseActivityId } from './square-core.js';
|
|
3
3
|
export type { Act, ActivityId, Audience, Reach } from './square-core.js';
|
|
4
4
|
export declare const WAKE_ROUTE_KINDS: readonly ["opencode-server", "codex-queue", "claude-native", "pi-extension", "paseo"];
|
|
@@ -108,6 +108,7 @@ export interface ActivitiesOptions {
|
|
|
108
108
|
json?: boolean;
|
|
109
109
|
}
|
|
110
110
|
export interface WatchOptions {
|
|
111
|
+
id?: ActivityId;
|
|
111
112
|
participants?: string[];
|
|
112
113
|
mention?: string;
|
|
113
114
|
limit?: number;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { HostLedgerPort, PresenceChannel, SquareArtifactPort } from './ports.js';
|
|
2
|
+
import { type WakeRoute } from './routes.js';
|
|
3
|
+
export interface HostContext {
|
|
4
|
+
readonly artifact: SquareArtifactPort;
|
|
5
|
+
readonly clock: () => number;
|
|
6
|
+
readonly location?: string;
|
|
7
|
+
readonly hostLedger?: HostLedgerPort;
|
|
8
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
9
|
+
}
|
|
10
|
+
export declare function processIdentity(env: NodeJS.ProcessEnv): {
|
|
11
|
+
session: string;
|
|
12
|
+
channel: PresenceChannel;
|
|
13
|
+
};
|
|
14
|
+
export declare function identityRouteDraft(context: HostContext, participant: string): Promise<Omit<WakeRoute, 'updatedAt'> | undefined>;
|
|
15
|
+
export declare function publishIdentityRoute(context: HostContext, participant: string, epoch?: number): Promise<void>;
|
|
16
|
+
export declare function retireIdentityRoute(context: HostContext, participant: string, expectedEpoch?: number): Promise<void>;
|
|
17
|
+
export declare function assertLiveOwner(context: HostContext, participant: string, expectedEpoch?: number): Promise<boolean>;
|
|
18
|
+
/** Presence is best effort and runs only after the artifact mutation commits. */
|
|
19
|
+
export declare function ensureLocalPresence(context: HostContext, participant: string, epoch?: number): Promise<void>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { readParticipantOwner, sessionOwnsParticipant } from './registry.js';
|
|
2
|
+
import { defaultWakeRouteCapabilities, publishWakeRoute, retireWakeRouteFromArtifact, resolvePrimaryWakeRoute, ROUTE_FRESH_MS } from './routes.js';
|
|
3
|
+
export function processIdentity(env) {
|
|
4
|
+
const choices = [
|
|
5
|
+
[env.CLAUDE_CODE_SESSION_ID, 'claude-code'], [env.CODEX_THREAD_ID, 'codex'],
|
|
6
|
+
[env.OPENCODE_SESSION_ID, 'opencode'], [env.SQUARE_PI_SESSION_ID, 'pi'], [env.PASEO_AGENT_ID, 'paseo'],
|
|
7
|
+
];
|
|
8
|
+
const found = choices.find(([session]) => session?.trim());
|
|
9
|
+
return found === undefined ? { session: `process:${process.pid}`, channel: 'unknown' } : { session: found[0].trim(), channel: found[1] };
|
|
10
|
+
}
|
|
11
|
+
export async function identityRouteDraft(context, participant) {
|
|
12
|
+
if (context.location === undefined || context.location === 'memory')
|
|
13
|
+
return undefined;
|
|
14
|
+
const identity = processIdentity(context.env ?? process.env);
|
|
15
|
+
const provider = identity.channel === 'claude-code' ? 'claude' : identity.channel === 'opencode' ? 'opencode' : identity.channel === 'pi' ? 'pi' : identity.channel === 'paseo' ? 'paseo' : 'codex';
|
|
16
|
+
const capabilities = context.hostLedger === undefined ? { canUse: () => false } : await defaultWakeRouteCapabilities(context.hostLedger);
|
|
17
|
+
return resolvePrimaryWakeRoute({ location: context.location, participant, sessionId: identity.session, provider }, context.env ?? process.env, capabilities);
|
|
18
|
+
}
|
|
19
|
+
async function currentOwnerEpoch(context, participant) {
|
|
20
|
+
if (context.location === undefined || context.location === 'memory')
|
|
21
|
+
return undefined;
|
|
22
|
+
const owner = await readParticipantOwner(context.location, participant, context.env ?? process.env);
|
|
23
|
+
return owner?.epoch;
|
|
24
|
+
}
|
|
25
|
+
export async function publishIdentityRoute(context, participant, epoch) {
|
|
26
|
+
const route = await identityRouteDraft(context, participant);
|
|
27
|
+
if (route === undefined)
|
|
28
|
+
return;
|
|
29
|
+
const ownerEpoch = epoch ?? await currentOwnerEpoch(context, participant);
|
|
30
|
+
await publishWakeRoute(context.artifact, { ...route, ...(ownerEpoch === undefined ? {} : { epoch: ownerEpoch }) }, { at: context.clock(), requireCurrentSession: true }).catch(() => undefined);
|
|
31
|
+
}
|
|
32
|
+
export async function retireIdentityRoute(context, participant, expectedEpoch) {
|
|
33
|
+
if (context.location === undefined || context.location === 'memory')
|
|
34
|
+
return;
|
|
35
|
+
const identity = processIdentity(context.env ?? process.env);
|
|
36
|
+
await retireWakeRouteFromArtifact(context.artifact, { location: context.location, participant, sessionId: identity.session }, expectedEpoch === undefined ? {} : { expectedEpoch }).catch(() => undefined);
|
|
37
|
+
}
|
|
38
|
+
export async function assertLiveOwner(context, participant, expectedEpoch) {
|
|
39
|
+
if (context.hostLedger === undefined || context.location === undefined || context.location === 'memory')
|
|
40
|
+
return true;
|
|
41
|
+
const identity = processIdentity(context.env ?? process.env);
|
|
42
|
+
// Library callers without a harness session are not ownership-fenced unless an epoch was supplied.
|
|
43
|
+
if (identity.channel === 'unknown' && expectedEpoch === undefined)
|
|
44
|
+
return true;
|
|
45
|
+
return sessionOwnsParticipant(context.location, participant, identity.session, context.env ?? process.env, expectedEpoch);
|
|
46
|
+
}
|
|
47
|
+
/** Presence is best effort and runs only after the artifact mutation commits. */
|
|
48
|
+
export async function ensureLocalPresence(context, participant, epoch) {
|
|
49
|
+
if (context.hostLedger === undefined || context.location === undefined || context.location === 'memory')
|
|
50
|
+
return;
|
|
51
|
+
const identity = processIdentity(context.env ?? process.env);
|
|
52
|
+
const ownerEpoch = epoch ?? await currentOwnerEpoch(context, participant);
|
|
53
|
+
const now = context.clock();
|
|
54
|
+
// Semantic publish: an unexpired presence row already stands when it carries the same owner epoch.
|
|
55
|
+
try {
|
|
56
|
+
const existing = await context.hostLedger.listPresence({ location: context.location, participant, session: identity.session, scopes: ['local'], now });
|
|
57
|
+
if (existing.some((row) => row.channel === identity.channel
|
|
58
|
+
&& now - (row.updatedAt ?? 0) < ROUTE_FRESH_MS
|
|
59
|
+
&& (ownerEpoch === undefined || row.epoch === ownerEpoch)))
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
catch { /* fall through to the best-effort ensure below */ }
|
|
63
|
+
const result = await context.hostLedger.ensurePresence({
|
|
64
|
+
location: context.location,
|
|
65
|
+
participant,
|
|
66
|
+
session: identity.session,
|
|
67
|
+
channel: identity.channel,
|
|
68
|
+
// Presence rows are host-ledger wall-time evidence; the square clock belongs to artifact activities.
|
|
69
|
+
updatedAt: Date.now(),
|
|
70
|
+
...(ownerEpoch === undefined || ownerEpoch <= 0 ? {} : { epoch: ownerEpoch }),
|
|
71
|
+
}, 'local').catch((error) => ({
|
|
72
|
+
status: 'degraded',
|
|
73
|
+
record: { location: context.location, participant, session: identity.session, channel: identity.channel },
|
|
74
|
+
error,
|
|
75
|
+
}));
|
|
76
|
+
if (result.status === 'degraded')
|
|
77
|
+
process.stderr.write(`! host presence degraded: ${result.error instanceof Error ? result.error.message : String(result.error)}\n`);
|
|
78
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type LandedAudienceReplay, type Perception } from './square-core.js';
|
|
2
|
+
import { type SquareState, type StoredAct } from './model.js';
|
|
3
|
+
/**
|
|
4
|
+
* The small, state-only perception surface shared by catch and delivery.
|
|
5
|
+
* Notification planning deliberately remains outside this projection.
|
|
6
|
+
*/
|
|
7
|
+
export interface PerceptionProjection {
|
|
8
|
+
directedTo(activity: StoredAct, name: string): boolean;
|
|
9
|
+
perceive(activity: StoredAct, name: string): Perception;
|
|
10
|
+
cursorFor(name: string): number;
|
|
11
|
+
isSeen(name: string, index: number): boolean;
|
|
12
|
+
readonly replayedActivityCount: number;
|
|
13
|
+
}
|
|
14
|
+
/** Build perception over one audience replay of a SquareState snapshot. */
|
|
15
|
+
export declare function derivePerceptionProjection(state: SquareState, landed?: LandedAudienceReplay): PerceptionProjection;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { formatActivityId, replayLandedAudiences } from './square-core.js';
|
|
2
|
+
import { sameName } from './model.js';
|
|
3
|
+
import { readCursor } from './runtime.js';
|
|
4
|
+
/** Build perception over one audience replay of a SquareState snapshot. */
|
|
5
|
+
export function derivePerceptionProjection(state, landed = replayLandedAudiences(state.acts)) {
|
|
6
|
+
function canonicalName(name) {
|
|
7
|
+
return landed.resolveParticipant(name) ?? name;
|
|
8
|
+
}
|
|
9
|
+
function directedTo(activity, name) {
|
|
10
|
+
return activity.kind === 'say' && landed.includes(activity, name);
|
|
11
|
+
}
|
|
12
|
+
function perceive(activity, name) {
|
|
13
|
+
if (activity.kind !== 'say' || sameName(activity.actor, name))
|
|
14
|
+
return 'full';
|
|
15
|
+
return directedTo(activity, name) ? 'full' : 'presence';
|
|
16
|
+
}
|
|
17
|
+
function isSeen(name, index) {
|
|
18
|
+
const recipient = canonicalName(name);
|
|
19
|
+
return state.runtime.observations?.[recipient]?.[formatActivityId(index)]?.state === 'seen';
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
directedTo,
|
|
23
|
+
perceive,
|
|
24
|
+
cursorFor: (name) => readCursor(state, name, landed),
|
|
25
|
+
isSeen,
|
|
26
|
+
replayedActivityCount: landed.replayedActivityCount,
|
|
27
|
+
};
|
|
28
|
+
}
|
package/dist/presence.d.ts
CHANGED
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
import { type DeliveryModel } from './delivery.js';
|
|
2
|
-
import type { OpenSquare } from './open-square.js';
|
|
3
|
-
import type { CatchOptions, CatchResult } from './square-facade.js';
|
|
4
|
-
export declare function catchUp(square: OpenSquare, name: string, options?: CatchOptions, deriveDelivery?: (state: import('./model.js').SquareState) => DeliveryModel): Promise<CatchResult>;
|
|
5
1
|
/** Commit seen only for complete, actually rendered boundary bodies. */
|
|
6
2
|
export declare function markBoundarySeen(squarePath: string, name: string, actIndexes: readonly number[], at?: number): Promise<void>;
|
package/dist/presence.js
CHANGED
|
@@ -1,19 +1,6 @@
|
|
|
1
|
-
import { deriveDeliveryModel } from './delivery.js';
|
|
2
1
|
import { openSquare } from './square-file-adapter.js';
|
|
3
2
|
import { closeOpenSquare } from './open-square.js';
|
|
4
3
|
import { recordObservation } from './runtime.js';
|
|
5
|
-
import { catchUp as actionCatchUp } from './square-actions.js';
|
|
6
|
-
function operationContext(square) {
|
|
7
|
-
return 'artifact' in square
|
|
8
|
-
? { artifact: square.artifact, clock: square.clock, location: square.location, hostLedger: square.hostLedger, wakeTransport: square.wakeTransport, env: square.env }
|
|
9
|
-
: { artifact: square.cell, clock: square.clock };
|
|
10
|
-
}
|
|
11
|
-
export async function catchUp(square, name, options = {}, deriveDelivery = deriveDeliveryModel) {
|
|
12
|
-
const project = deriveDelivery === deriveDeliveryModel
|
|
13
|
-
? undefined
|
|
14
|
-
: (state) => deriveDelivery(state);
|
|
15
|
-
return actionCatchUp(operationContext(square), name, options, project);
|
|
16
|
-
}
|
|
17
4
|
/** Commit seen only for complete, actually rendered boundary bodies. */
|
|
18
5
|
export async function markBoundarySeen(squarePath, name, actIndexes, at = Date.now()) {
|
|
19
6
|
let square;
|
package/dist/routes.js
CHANGED
|
@@ -94,8 +94,9 @@ function sameRouteAddress(left, right) {
|
|
|
94
94
|
}
|
|
95
95
|
function canonicalRouteLocationSync(location) {
|
|
96
96
|
const absolute = path.resolve(location);
|
|
97
|
+
// Match fs.promises.realpath used by artifact and presence adapters, including Windows 8.3 aliases.
|
|
97
98
|
try {
|
|
98
|
-
return fs.realpathSync(absolute);
|
|
99
|
+
return fs.realpathSync.native(absolute);
|
|
99
100
|
}
|
|
100
101
|
catch {
|
|
101
102
|
return absolute;
|
package/dist/square-actions.d.ts
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { type SquareState } from './model.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { WakeTransportPort } from './ports.js';
|
|
3
3
|
import type { Activity, CatchOptions, CatchResult, ExpressOptions, ExpressResult, OwnershipFenceOptions } from './square-facade.js';
|
|
4
4
|
import { type CatchProjection } from './catch-decisions.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
readonly
|
|
8
|
-
readonly location?: string;
|
|
9
|
-
readonly hostLedger?: HostLedgerPort;
|
|
10
|
-
readonly wakeTransport?: import('./ports.js').WakeTransportPort;
|
|
11
|
-
readonly env?: NodeJS.ProcessEnv;
|
|
5
|
+
import { type HostContext } from './participant-host.js';
|
|
6
|
+
export interface OperationContext extends HostContext {
|
|
7
|
+
readonly wakeTransport?: WakeTransportPort;
|
|
12
8
|
}
|
|
13
9
|
export type { OwnershipFenceOptions };
|
|
14
10
|
export declare function catchUp(square: OperationContext, name: string, options?: CatchOptions, project?: (state: SquareState) => CatchProjection): Promise<CatchResult>;
|
package/dist/square-actions.js
CHANGED
|
@@ -4,89 +4,9 @@ import { isSquareError, nameKey, SquareError, validateName } from './model.js';
|
|
|
4
4
|
import { participantIdentity } from './participant-identity.js';
|
|
5
5
|
import { deliverPending } from './delivery-operations.js';
|
|
6
6
|
import { decideCatch } from './catch-decisions.js';
|
|
7
|
-
import { claimSessionParticipant, claimSessionTakeover, readParticipantOwner,
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
const choices = [
|
|
11
|
-
[env.CLAUDE_CODE_SESSION_ID, 'claude-code'], [env.CODEX_THREAD_ID, 'codex'],
|
|
12
|
-
[env.OPENCODE_SESSION_ID, 'opencode'], [env.SQUARE_PI_SESSION_ID, 'pi'], [env.PASEO_AGENT_ID, 'paseo'],
|
|
13
|
-
];
|
|
14
|
-
const found = choices.find(([session]) => session?.trim());
|
|
15
|
-
return found === undefined ? { session: `process:${process.pid}`, channel: 'unknown' } : { session: found[0].trim(), channel: found[1] };
|
|
16
|
-
}
|
|
17
|
-
async function identityRouteDraft(context, participant) {
|
|
18
|
-
if (context.location === undefined || context.location === 'memory')
|
|
19
|
-
return undefined;
|
|
20
|
-
const identity = processIdentity(context.env ?? process.env);
|
|
21
|
-
const provider = identity.channel === 'claude-code' ? 'claude' : identity.channel === 'opencode' ? 'opencode' : identity.channel === 'pi' ? 'pi' : identity.channel === 'paseo' ? 'paseo' : 'codex';
|
|
22
|
-
const capabilities = context.hostLedger === undefined ? { canUse: () => false } : await defaultWakeRouteCapabilities(context.hostLedger);
|
|
23
|
-
return resolvePrimaryWakeRoute({ location: context.location, participant, sessionId: identity.session, provider }, context.env ?? process.env, capabilities);
|
|
24
|
-
}
|
|
25
|
-
async function currentOwnerEpoch(context, participant) {
|
|
26
|
-
if (context.location === undefined || context.location === 'memory')
|
|
27
|
-
return undefined;
|
|
28
|
-
const owner = await readParticipantOwner(context.location, participant, context.env ?? process.env);
|
|
29
|
-
return owner?.epoch;
|
|
30
|
-
}
|
|
31
|
-
async function publishIdentityRoute(context, participant, epoch) {
|
|
32
|
-
if (context.location === undefined || context.location === 'memory')
|
|
33
|
-
return;
|
|
34
|
-
const identity = processIdentity(context.env ?? process.env);
|
|
35
|
-
const provider = identity.channel === 'claude-code' ? 'claude' : identity.channel === 'opencode' ? 'opencode' : identity.channel === 'pi' ? 'pi' : identity.channel === 'paseo' ? 'paseo' : 'codex';
|
|
36
|
-
const capabilities = context.hostLedger === undefined ? { canUse: () => false } : await defaultWakeRouteCapabilities(context.hostLedger);
|
|
37
|
-
const route = resolvePrimaryWakeRoute({ location: context.location, participant, sessionId: identity.session, provider }, context.env ?? process.env, capabilities);
|
|
38
|
-
if (route === undefined)
|
|
39
|
-
return;
|
|
40
|
-
const ownerEpoch = epoch ?? await currentOwnerEpoch(context, participant);
|
|
41
|
-
await publishWakeRoute(context.artifact, { ...route, ...(ownerEpoch === undefined ? {} : { epoch: ownerEpoch }) }, { at: context.clock(), requireCurrentSession: true }).catch(() => undefined);
|
|
42
|
-
}
|
|
43
|
-
async function retireIdentityRoute(context, participant, expectedEpoch) {
|
|
44
|
-
if (context.location === undefined || context.location === 'memory')
|
|
45
|
-
return;
|
|
46
|
-
const identity = processIdentity(context.env ?? process.env);
|
|
47
|
-
await retireWakeRouteFromArtifact(context.artifact, { location: context.location, participant, sessionId: identity.session }, expectedEpoch === undefined ? {} : { expectedEpoch }).catch(() => undefined);
|
|
48
|
-
}
|
|
49
|
-
async function assertLiveOwner(context, participant, expectedEpoch) {
|
|
50
|
-
if (context.hostLedger === undefined || context.location === undefined || context.location === 'memory')
|
|
51
|
-
return true;
|
|
52
|
-
const identity = processIdentity(context.env ?? process.env);
|
|
53
|
-
// Library callers without a harness session are not ownership-fenced unless an epoch was supplied.
|
|
54
|
-
if (identity.channel === 'unknown' && expectedEpoch === undefined)
|
|
55
|
-
return true;
|
|
56
|
-
return sessionOwnsParticipant(context.location, participant, identity.session, context.env ?? process.env, expectedEpoch);
|
|
57
|
-
}
|
|
58
|
-
/** Presence is best effort and runs only after the artifact mutation commits. */
|
|
59
|
-
async function ensureLocalPresence(context, participant, epoch) {
|
|
60
|
-
if (context.hostLedger === undefined || context.location === undefined || context.location === 'memory')
|
|
61
|
-
return;
|
|
62
|
-
const identity = processIdentity(context.env ?? process.env);
|
|
63
|
-
const ownerEpoch = epoch ?? await currentOwnerEpoch(context, participant);
|
|
64
|
-
const now = context.clock();
|
|
65
|
-
// Semantic publish: an unexpired presence row already stands when it carries the same owner epoch.
|
|
66
|
-
try {
|
|
67
|
-
const existing = await context.hostLedger.listPresence({ location: context.location, participant, session: identity.session, scopes: ['local'], now });
|
|
68
|
-
if (existing.some((row) => row.channel === identity.channel
|
|
69
|
-
&& now - (row.updatedAt ?? 0) < ROUTE_FRESH_MS
|
|
70
|
-
&& (ownerEpoch === undefined || row.epoch === ownerEpoch)))
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
catch { /* fall through to the best-effort ensure below */ }
|
|
74
|
-
const result = await context.hostLedger.ensurePresence({
|
|
75
|
-
location: context.location,
|
|
76
|
-
participant,
|
|
77
|
-
session: identity.session,
|
|
78
|
-
channel: identity.channel,
|
|
79
|
-
// Presence rows are host-ledger wall-time evidence; the square clock belongs to artifact activities.
|
|
80
|
-
updatedAt: Date.now(),
|
|
81
|
-
...(ownerEpoch === undefined || ownerEpoch <= 0 ? {} : { epoch: ownerEpoch }),
|
|
82
|
-
}, 'local').catch((error) => ({
|
|
83
|
-
status: 'degraded',
|
|
84
|
-
record: { location: context.location, participant, session: identity.session, channel: identity.channel },
|
|
85
|
-
error,
|
|
86
|
-
}));
|
|
87
|
-
if (result.status === 'degraded')
|
|
88
|
-
process.stderr.write(`! host presence degraded: ${result.error instanceof Error ? result.error.message : String(result.error)}\n`);
|
|
89
|
-
}
|
|
7
|
+
import { claimSessionParticipant, claimSessionTakeover, readParticipantOwner, withOwnershipClaimLock } from './registry.js';
|
|
8
|
+
import { assertLiveOwner, ensureLocalPresence, identityRouteDraft, processIdentity, publishIdentityRoute, retireIdentityRoute } from './participant-host.js';
|
|
9
|
+
import { applyWakeRouteToState, dropParticipantWakeRoutesFromState, dropSessionWakeRoutesFromState, sessionCanEndParticipant } from './routes.js';
|
|
90
10
|
function exposeCaught(activity, perception) {
|
|
91
11
|
if (activity.kind === 'read' || activity.actor === undefined)
|
|
92
12
|
throw new Error(`Cannot expose stored activity ${formatActivityId(activity.index)}`);
|
package/dist/square-facade.d.ts
CHANGED
package/dist/watch.js
CHANGED
|
@@ -126,6 +126,7 @@ async function cmdWatchNow(squarePath, name, opts) {
|
|
|
126
126
|
const facade = await openParticipant({ path: squarePath, clock: nowMs }, name);
|
|
127
127
|
try {
|
|
128
128
|
const caught = await facade.participant.catch({
|
|
129
|
+
...(opts.id === undefined ? {} : { id: opts.id }),
|
|
129
130
|
...(opts.participants === undefined ? {} : { from: opts.participants }),
|
|
130
131
|
...(opts.mention === undefined ? {} : { mention: true }),
|
|
131
132
|
...(opts.limit === undefined ? {} : { limit: opts.limit }),
|
package/extensions/square-pi.js
CHANGED
|
@@ -4,18 +4,17 @@ import { waitForSessionPending } from '../dist/inbox.js';
|
|
|
4
4
|
import { projectSessionBindings } from '../dist/square-projections.js';
|
|
5
5
|
import { createHostLedgerPort } from '../dist/host-ledger-file-adapter.js';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
class PiDeliveryDroppedError extends Error {
|
|
8
|
+
constructor() {
|
|
9
|
+
super('Pi discarded the queued Square notification');
|
|
10
|
+
this.name = 'PiDeliveryDroppedError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
9
13
|
|
|
10
14
|
function sessionBindings(sessionId) {
|
|
11
15
|
return projectSessionBindings({ hostLedger: createHostLedgerPort(), sessionId });
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
function piBoundaryTimeoutMs() {
|
|
15
|
-
const configured = Number.parseInt(process.env.SQUARE_PI_BOUNDARY_TIMEOUT_MS || '', 10);
|
|
16
|
-
return Number.isFinite(configured) && configured > 0 ? configured : DEFAULT_PI_BOUNDARY_TIMEOUT_MS;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
18
|
export function pendingInbox(inbox) {
|
|
20
19
|
return inbox.filter((item) => item.notifications?.length > 0);
|
|
21
20
|
}
|
|
@@ -33,41 +32,82 @@ export function renderPiInbox(inbox) {
|
|
|
33
32
|
export default function squarePiExtension(pi) {
|
|
34
33
|
let sessionId;
|
|
35
34
|
let sessionCwd;
|
|
36
|
-
let joiningContext;
|
|
37
35
|
let previousSessionId;
|
|
38
36
|
let watcher;
|
|
39
37
|
let watcherAbort;
|
|
40
38
|
let generation = 0;
|
|
41
|
-
let presenting = false;
|
|
42
|
-
let settledSerial = 0;
|
|
43
|
-
let settledWaiters = [];
|
|
44
39
|
const handledPending = new Set();
|
|
40
|
+
const landingAcks = new Map();
|
|
45
41
|
let retryAfterChange = false;
|
|
46
42
|
let retryWait;
|
|
47
|
-
|
|
43
|
+
let turnIndex = 0;
|
|
44
|
+
let activeTurn;
|
|
45
|
+
let currentRunSignal;
|
|
46
|
+
const deferredSteers = new Set();
|
|
48
47
|
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
48
|
+
const waitForAgentSettled = (signal) => new Promise((resolve, reject) => {
|
|
49
|
+
const deferred = {
|
|
50
|
+
release() {
|
|
51
|
+
deferredSteers.delete(deferred);
|
|
52
|
+
signal.removeEventListener('abort', abort);
|
|
53
|
+
resolve();
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
const abort = () => {
|
|
57
|
+
deferredSteers.delete(deferred);
|
|
58
|
+
signal.removeEventListener('abort', abort);
|
|
59
|
+
reject(signal.reason || new Error('Pi native injection aborted'));
|
|
60
|
+
};
|
|
61
|
+
deferredSteers.add(deferred);
|
|
62
|
+
if (signal.aborted) abort();
|
|
63
|
+
else signal.addEventListener('abort', abort, { once: true });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const releaseDeferredSteers = () => {
|
|
67
|
+
for (const deferred of [...deferredSteers]) deferred.release();
|
|
58
68
|
};
|
|
59
69
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
const failAcks = (error) => {
|
|
71
|
+
const acks = [...landingAcks.values()].flatMap((waiters) => [...waiters]);
|
|
72
|
+
for (const ack of acks) ack.settle(error);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const acknowledgeLanding = (message) => {
|
|
76
|
+
if (message?.role !== 'custom' || message.customType !== 'square' || typeof message.content !== 'string') return;
|
|
77
|
+
const acks = landingAcks.get(message.content);
|
|
78
|
+
if (acks === undefined) return;
|
|
79
|
+
for (const ack of [...acks]) ack.settle();
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const waitForLanding = (content, signal, kind) => {
|
|
83
|
+
let ack;
|
|
84
|
+
const promise = new Promise((resolve, reject) => {
|
|
85
|
+
const abort = () => ack.settle(signal.reason || new Error('Pi native injection aborted'));
|
|
86
|
+
ack = {
|
|
87
|
+
kind,
|
|
88
|
+
sentAfterTurn: turnIndex,
|
|
89
|
+
emptyTurnWindows: 0,
|
|
90
|
+
dropAfterSettled: false,
|
|
91
|
+
settle(error) {
|
|
92
|
+
if (ack.settled) return;
|
|
93
|
+
ack.settled = true;
|
|
94
|
+
signal.removeEventListener('abort', abort);
|
|
95
|
+
const acks = landingAcks.get(content);
|
|
96
|
+
if (acks?.delete(ack) && acks.size === 0) landingAcks.delete(content);
|
|
97
|
+
if (error === undefined) resolve();
|
|
98
|
+
else reject(error);
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
let acks = landingAcks.get(content);
|
|
102
|
+
if (acks === undefined) {
|
|
103
|
+
acks = new Set();
|
|
104
|
+
landingAcks.set(content, acks);
|
|
105
|
+
}
|
|
106
|
+
acks.add(ack);
|
|
107
|
+
if (signal.aborted) abort();
|
|
108
|
+
else signal.addEventListener('abort', abort, { once: true });
|
|
70
109
|
});
|
|
110
|
+
return { promise, ack };
|
|
71
111
|
};
|
|
72
112
|
|
|
73
113
|
// A transport may keep its promise pending while Pi is shutting down or
|
|
@@ -88,7 +128,7 @@ export default function squarePiExtension(pi) {
|
|
|
88
128
|
signal.addEventListener('abort', finish, { once: true });
|
|
89
129
|
});
|
|
90
130
|
|
|
91
|
-
const wake = async (
|
|
131
|
+
const wake = async (token, signal) => {
|
|
92
132
|
const armDeferredRetry = () => {
|
|
93
133
|
const controller = new AbortController();
|
|
94
134
|
let resolveArmed;
|
|
@@ -108,14 +148,26 @@ export default function squarePiExtension(pi) {
|
|
|
108
148
|
};
|
|
109
149
|
|
|
110
150
|
while (sessionId !== undefined && token === generation && !signal.aborted) {
|
|
111
|
-
|
|
151
|
+
try {
|
|
152
|
+
if ((await sessionBindings(sessionId)).length === 0) {
|
|
153
|
+
await pause(signal, 1_000);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
112
157
|
await pause(signal, 1_000);
|
|
113
158
|
continue;
|
|
114
159
|
}
|
|
160
|
+
|
|
115
161
|
const deferredRetry = retryAfterChange;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
162
|
+
let pending;
|
|
163
|
+
try {
|
|
164
|
+
pending = deferredRetry && retryWait !== undefined
|
|
165
|
+
? await retryWait
|
|
166
|
+
: await waitForSessionPending(sessionId, 30_000, { signal, excludeKeys: handledPending });
|
|
167
|
+
} catch {
|
|
168
|
+
await pause(signal, 1_000);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
119
171
|
if (sessionId === undefined || token !== generation || signal.aborted) return;
|
|
120
172
|
if (pending.length === 0) {
|
|
121
173
|
if (deferredRetry) {
|
|
@@ -126,112 +178,136 @@ export default function squarePiExtension(pi) {
|
|
|
126
178
|
}
|
|
127
179
|
retryAfterChange = false;
|
|
128
180
|
retryWait = undefined;
|
|
129
|
-
|
|
130
|
-
const serial = settledSerial;
|
|
131
|
-
await waitForSettled(serial, signal);
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
if (presenting) continue;
|
|
181
|
+
|
|
135
182
|
const keys = inboxKeys(pending);
|
|
136
|
-
presenting = true;
|
|
137
183
|
const deferred = armDeferredRetry();
|
|
138
184
|
const retryArmed = await deferred.armed;
|
|
139
185
|
if (!retryArmed) {
|
|
140
186
|
deferred.cancel();
|
|
141
|
-
presenting = false;
|
|
142
187
|
continue;
|
|
143
188
|
}
|
|
144
189
|
let keepDeferred = false;
|
|
145
190
|
try {
|
|
146
|
-
|
|
191
|
+
await presentPendingAtBoundary(
|
|
147
192
|
sessionId,
|
|
148
193
|
async (content) => {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
{ deliverAs: 'steer', triggerTurn: true },
|
|
152
|
-
));
|
|
153
|
-
let timer;
|
|
194
|
+
if (currentRunSignal?.aborted) await waitForAgentSettled(signal);
|
|
195
|
+
const landing = waitForLanding(content, signal, 'steer');
|
|
154
196
|
try {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
signal.addEventListener('abort', () => reject(signal.reason || new Error('Pi native injection aborted')), { once: true });
|
|
163
|
-
}),
|
|
164
|
-
new Promise((_, reject) => {
|
|
165
|
-
timer = setTimeout(() => reject(new Error('Pi native injection timed out')), PI_SEND_TIMEOUT_MS);
|
|
166
|
-
}),
|
|
167
|
-
]);
|
|
168
|
-
} finally {
|
|
169
|
-
if (timer !== undefined) clearTimeout(timer);
|
|
197
|
+
Promise.resolve(pi.sendMessage(
|
|
198
|
+
{ customType: 'square', content, display: true },
|
|
199
|
+
{ deliverAs: 'steer', triggerTurn: true },
|
|
200
|
+
)).catch((error) => landing.ack.settle(error));
|
|
201
|
+
} catch (error) {
|
|
202
|
+
landing.ack.settle(error);
|
|
170
203
|
}
|
|
171
|
-
return
|
|
204
|
+
return landing.promise;
|
|
172
205
|
},
|
|
173
206
|
undefined,
|
|
174
207
|
undefined,
|
|
175
208
|
signal,
|
|
176
209
|
);
|
|
177
|
-
|
|
178
|
-
|
|
210
|
+
for (const key of keys) handledPending.add(key);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (error instanceof PiDeliveryDroppedError) {
|
|
213
|
+
deferred.cancel();
|
|
214
|
+
continue;
|
|
179
215
|
}
|
|
180
|
-
} catch {
|
|
181
216
|
// The next state edge is already being observed before native injection starts.
|
|
182
217
|
retryAfterChange = true;
|
|
183
218
|
retryWait = deferred.pending;
|
|
184
219
|
keepDeferred = true;
|
|
185
220
|
} finally {
|
|
186
221
|
if (!keepDeferred) deferred.cancel();
|
|
187
|
-
presenting = false;
|
|
188
222
|
}
|
|
189
223
|
}
|
|
190
224
|
};
|
|
191
225
|
|
|
192
226
|
pi.on('session_start', async (_event, ctx) => {
|
|
193
227
|
generation += 1;
|
|
228
|
+
failAcks(new Error('Pi session replaced'));
|
|
194
229
|
stopWatcher();
|
|
195
230
|
handledPending.clear();
|
|
196
231
|
retryAfterChange = false;
|
|
197
232
|
retryWait = undefined;
|
|
233
|
+
turnIndex = 0;
|
|
234
|
+
activeTurn = undefined;
|
|
235
|
+
currentRunSignal = undefined;
|
|
198
236
|
sessionId = ctx.sessionManager.getSessionId();
|
|
199
237
|
sessionCwd = ctx.cwd || process.cwd();
|
|
200
238
|
previousSessionId = process.env.SQUARE_PI_SESSION_ID;
|
|
201
239
|
process.env.SQUARE_PI_SESSION_ID = sessionId;
|
|
202
|
-
joiningContext = undefined;
|
|
203
|
-
void automaticSessionStart('pi', sessionId, sessionCwd).catch(() => undefined);
|
|
204
|
-
watcherAbort = new AbortController();
|
|
205
240
|
const token = generation;
|
|
206
|
-
|
|
241
|
+
watcherAbort = new AbortController();
|
|
242
|
+
const signal = watcherAbort.signal;
|
|
243
|
+
void automaticSessionStart('pi', sessionId, sessionCwd).then((context) => {
|
|
244
|
+
if (context === undefined || sessionId === undefined || token !== generation) return;
|
|
245
|
+
const landing = waitForLanding(context, signal, 'nextTurn');
|
|
246
|
+
try {
|
|
247
|
+
Promise.resolve(pi.sendMessage(
|
|
248
|
+
{ customType: 'square', content: context, display: true },
|
|
249
|
+
{ deliverAs: 'nextTurn' },
|
|
250
|
+
)).catch((error) => landing.ack.settle(error));
|
|
251
|
+
} catch {
|
|
252
|
+
// Joining context is advisory; an unavailable Pi transport does not block startup.
|
|
253
|
+
landing.ack.settle(new Error('Pi joining-context injection failed'));
|
|
254
|
+
}
|
|
255
|
+
void landing.promise.catch(() => undefined);
|
|
256
|
+
}).catch(() => undefined);
|
|
257
|
+
watcher = wake(token, signal).catch(() => undefined);
|
|
207
258
|
});
|
|
208
259
|
|
|
209
|
-
pi.on('
|
|
210
|
-
|
|
211
|
-
const waiters = settledWaiters;
|
|
212
|
-
settledWaiters = [];
|
|
213
|
-
for (const waiter of waiters) waiter.resolve();
|
|
260
|
+
pi.on('agent_start', async (_event, ctx) => {
|
|
261
|
+
currentRunSignal = ctx?.getSignal?.();
|
|
214
262
|
});
|
|
215
263
|
|
|
216
|
-
pi.on('
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
264
|
+
pi.on('message_end', async (event) => {
|
|
265
|
+
if (activeTurn !== undefined && (event.message?.role === 'user' || event.message?.role === 'custom')) {
|
|
266
|
+
activeTurn.hasInput = true;
|
|
267
|
+
}
|
|
268
|
+
acknowledgeLanding(event.message);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
pi.on('turn_start', async () => {
|
|
272
|
+
turnIndex += 1;
|
|
273
|
+
activeTurn = { index: turnIndex, hasInput: false, signal: currentRunSignal };
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
pi.on('turn_end', async (event, ctx) => {
|
|
277
|
+
const turn = activeTurn;
|
|
278
|
+
activeTurn = undefined;
|
|
279
|
+
const acks = [...landingAcks.values()].flatMap((waiters) => [...waiters]);
|
|
280
|
+
if (event.message?.stopReason === 'aborted' && ctx?.mode === 'tui' && turn?.signal !== undefined) {
|
|
281
|
+
for (const ack of acks) {
|
|
282
|
+
if (ack.kind === 'steer') ack.dropAfterSettled = true;
|
|
222
283
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (turn === undefined) return;
|
|
287
|
+
for (const ack of acks) {
|
|
288
|
+
if (ack.kind !== 'steer' || ack.dropAfterSettled || turn.index <= ack.sentAfterTurn) continue;
|
|
289
|
+
if (turn.hasInput) {
|
|
290
|
+
ack.emptyTurnWindows = 0;
|
|
291
|
+
} else {
|
|
292
|
+
ack.emptyTurnWindows += 1;
|
|
293
|
+
if (ack.emptyTurnWindows >= 2) ack.settle(new PiDeliveryDroppedError());
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
pi.on('agent_settled', async () => {
|
|
299
|
+
currentRunSignal = undefined;
|
|
300
|
+
const acks = [...landingAcks.values()].flatMap((waiters) => [...waiters]);
|
|
301
|
+
for (const ack of acks) {
|
|
302
|
+
if (ack.dropAfterSettled) ack.settle(new PiDeliveryDroppedError());
|
|
227
303
|
}
|
|
304
|
+
releaseDeferredSteers();
|
|
228
305
|
});
|
|
229
306
|
|
|
230
307
|
pi.on('session_shutdown', async () => {
|
|
231
308
|
generation += 1;
|
|
309
|
+
failAcks(new Error('Pi session ended'));
|
|
232
310
|
stopWatcher();
|
|
233
|
-
for (const waiter of settledWaiters) waiter.resolve();
|
|
234
|
-
settledWaiters = [];
|
|
235
311
|
if (sessionId && sessionCwd) void automaticSessionEnd('pi', sessionId, sessionCwd).catch(() => undefined);
|
|
236
312
|
if (process.env.SQUARE_PI_SESSION_ID === sessionId) {
|
|
237
313
|
if (previousSessionId === undefined) delete process.env.SQUARE_PI_SESSION_ID;
|
|
@@ -239,8 +315,8 @@ export default function squarePiExtension(pi) {
|
|
|
239
315
|
}
|
|
240
316
|
sessionId = undefined;
|
|
241
317
|
sessionCwd = undefined;
|
|
242
|
-
joiningContext = undefined;
|
|
243
|
-
presenting = false;
|
|
244
318
|
retryWait = undefined;
|
|
319
|
+
activeTurn = undefined;
|
|
320
|
+
currentRunSignal = undefined;
|
|
245
321
|
});
|
|
246
322
|
}
|
package/package.json
CHANGED
package/skills/square/SKILL.md
CHANGED
|
@@ -6,6 +6,8 @@ allowed-tools: Bash(square *)
|
|
|
6
6
|
|
|
7
7
|
# Square
|
|
8
8
|
|
|
9
|
+
Don't treat Square as a public dumping ground. Speak here only when you genuinely think someone else needs to know; otherwise you are needlessly interrupting them.
|
|
10
|
+
|
|
9
11
|
A square is a physical place where participants catch up and express thoughts or actions. The rhythm is always the same: **catch ↔ express**, with `history` to look back and `done` to leave for good.
|
|
10
12
|
|
|
11
13
|
```text
|
|
@@ -74,13 +76,16 @@ Every human-CLI `express` must choose exactly one reach mode: one or more `--men
|
|
|
74
76
|
`catch` takes in the directed activity addressed to you since you last looked: mentions, bells, and bare activities from participants you are listening to.
|
|
75
77
|
|
|
76
78
|
```bash
|
|
79
|
+
square --location <square> --as <name> catch --id act/12 # take in exactly this activity, with its full body
|
|
77
80
|
square --location <square> --as <name> catch --now # take in what is pending
|
|
78
81
|
square --location <square> --as <name> catch --idle 30m # wait until something relevant lands, or 30m of quiet
|
|
79
82
|
square --location <square> --as <name> catch --now --mention # take in pending mentions
|
|
80
83
|
square --location <square> --as <name> catch --now --from <names> # take in pending activity from named participants
|
|
81
84
|
```
|
|
82
85
|
|
|
83
|
-
|
|
86
|
+
`catch --id act/12` immediately returns that activity if it was addressed to you and marks only it seen, leaving other unread activity pending. Repeating it returns the same full body. It accepts optional `--now`, but not `--idle`, `--from`, `--mention`, `--limit` or `--replace`. Unlike read-only `history`, it consumes the selected activity.
|
|
87
|
+
|
|
88
|
+
Without `--id`, every catch needs exactly one mode: `--now` or `--idle <duration>`. `--mention` and `--from` filter either mode; they do not replace it. Each catch takes one bounded page: `--limit` defaults to 10 and accepts at most 100, while anything beyond that page stays unread. Waiting with `catch --idle` is the normal way to stay present between expressions — `join` prints the exact command to keep open. Do not build a polling loop.
|
|
84
89
|
|
|
85
90
|
## Listen
|
|
86
91
|
|
package/dist/landing.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { SquareArtifactPort } from './ports.js';
|
|
2
|
-
import type { OpenSquare } from './open-square.js';
|
|
3
|
-
import { type ListenerChangeResult } from './square-actions.js';
|
|
4
|
-
import type { Activity, ExpressOptions, ExpressResult } from './square-facade.js';
|
|
5
|
-
/** Transitional adapter for existing internal callers until they migrate to SquareArtifactPort. */
|
|
6
|
-
type LegacySquare = {
|
|
7
|
-
readonly cell: SquareArtifactPort;
|
|
8
|
-
readonly clock: () => number;
|
|
9
|
-
};
|
|
10
|
-
export declare function join(square: OpenSquare | LegacySquare, name: string): Promise<{
|
|
11
|
-
readonly name: string;
|
|
12
|
-
readonly activity: Activity | null;
|
|
13
|
-
}>;
|
|
14
|
-
export declare function implicitJoin(square: OpenSquare | LegacySquare, name: string): Promise<{
|
|
15
|
-
readonly name: string;
|
|
16
|
-
readonly state: "joined" | "active" | "done";
|
|
17
|
-
readonly activity: Activity | null;
|
|
18
|
-
}>;
|
|
19
|
-
export declare function express(square: OpenSquare | LegacySquare, name: string, body: string, options?: ExpressOptions): Promise<ExpressResult>;
|
|
20
|
-
export declare function listen(square: OpenSquare | LegacySquare, actor: string, target: string): Promise<ListenerChangeResult>;
|
|
21
|
-
export declare function ignore(square: OpenSquare | LegacySquare, actor: string, target: string): Promise<ListenerChangeResult>;
|
|
22
|
-
export declare function listening(square: OpenSquare | LegacySquare, actor: string): Promise<readonly string[]>;
|
|
23
|
-
export declare function done(square: OpenSquare | LegacySquare, name: string, body?: string): Promise<ExpressResult>;
|
|
24
|
-
export declare function hold(square: OpenSquare | LegacySquare, name: string, reason?: string): Promise<ExpressResult>;
|
|
25
|
-
export declare function resume(square: OpenSquare | LegacySquare, name: string): Promise<ExpressResult>;
|
|
26
|
-
export {};
|
package/dist/landing.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { done as applyDone, express as applyExpress, hold as applyHold, ignore as applyIgnore, implicitJoin as applyImplicitJoin, join as applyJoin, listen as applyListen, listening as applyListening, resume as applyResume, } from './square-actions.js';
|
|
2
|
-
function artifactOf(square) {
|
|
3
|
-
if ('artifact' in square)
|
|
4
|
-
return square.artifact;
|
|
5
|
-
const { cell } = square;
|
|
6
|
-
return {
|
|
7
|
-
read: () => cell.read(),
|
|
8
|
-
transact: (fn) => cell.transact(fn),
|
|
9
|
-
changed: (sinceVersion, timeoutMs) => cell.changed(sinceVersion, timeoutMs),
|
|
10
|
-
close: () => cell.close(),
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
function contextOf(square) {
|
|
14
|
-
return { artifact: artifactOf(square), clock: square.clock, ...('location' in square ? { location: square.location, hostLedger: square.hostLedger, env: square.env } : {}) };
|
|
15
|
-
}
|
|
16
|
-
export function join(square, name) {
|
|
17
|
-
return applyJoin(contextOf(square), name);
|
|
18
|
-
}
|
|
19
|
-
export function implicitJoin(square, name) {
|
|
20
|
-
return applyImplicitJoin(contextOf(square), name);
|
|
21
|
-
}
|
|
22
|
-
export function express(square, name, body, options) {
|
|
23
|
-
return applyExpress(contextOf(square), name, body, options);
|
|
24
|
-
}
|
|
25
|
-
export function listen(square, actor, target) {
|
|
26
|
-
return applyListen(contextOf(square), actor, target);
|
|
27
|
-
}
|
|
28
|
-
export function ignore(square, actor, target) {
|
|
29
|
-
return applyIgnore(contextOf(square), actor, target);
|
|
30
|
-
}
|
|
31
|
-
export function listening(square, actor) {
|
|
32
|
-
return applyListening(contextOf(square), actor);
|
|
33
|
-
}
|
|
34
|
-
export function done(square, name, body = '') {
|
|
35
|
-
return applyDone(contextOf(square), name, body);
|
|
36
|
-
}
|
|
37
|
-
export function hold(square, name, reason = '') {
|
|
38
|
-
return applyHold(contextOf(square), name, reason);
|
|
39
|
-
}
|
|
40
|
-
export function resume(square, name) {
|
|
41
|
-
return applyResume(contextOf(square), name);
|
|
42
|
-
}
|