@astrosheep/square 0.3.21 → 0.3.22
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 +46 -38
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/codex-plugin/hooks/hooks.json +12 -0
- package/dist/attention-presentation.d.ts +1 -0
- package/dist/attention-presentation.js +9 -3
- package/dist/automatic-session.d.ts +1 -1
- package/dist/automatic-session.js +2 -11
- package/dist/boundary-presentation.d.ts +1 -1
- package/dist/boundary-presentation.js +30 -37
- package/dist/cli/square-commands.js +1 -12
- package/dist/codex-hook.js +10 -3
- package/dist/decisions.js +3 -3
- package/dist/help.js +3 -3
- package/dist/inbox.d.ts +9 -0
- package/dist/inbox.js +53 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/notifications.js +12 -9
- package/dist/participant-identity.d.ts +4 -0
- package/dist/participant-identity.js +32 -0
- package/dist/paseo-delivery.js +4 -4
- package/dist/presentation.js +1 -1
- package/dist/presented.d.ts +2 -2
- package/dist/presented.js +17 -8
- package/dist/registry.d.ts +4 -4
- package/dist/registry.js +8 -8
- package/dist/routes.d.ts +3 -3
- package/dist/routes.js +3 -3
- package/dist/square-core.d.ts +3 -0
- package/dist/square-core.js +20 -2
- package/dist/square-file-adapter.d.ts +11 -1
- package/dist/square-file-adapter.js +48 -0
- package/dist/views.d.ts +0 -1
- package/dist/views.js +1 -2
- package/dist/wake-port.d.ts +1 -1
- package/dist/wake-port.js +1 -1
- package/extensions/square-pi.js +93 -0
- package/package.json +1 -1
- package/skills/square/SKILL.md +46 -38
|
@@ -6,43 +6,44 @@ allowed-tools: Bash(square *)
|
|
|
6
6
|
|
|
7
7
|
# Square
|
|
8
8
|
|
|
9
|
-
A square is a physical place where participants catch up and express thoughts or actions.
|
|
9
|
+
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
10
|
|
|
11
|
-
|
|
11
|
+
```text
|
|
12
|
+
PUBLIC.square : catch ↔ express directly (no join)
|
|
13
|
+
other square : ls → join once → catch ↔ express
|
|
14
|
+
└→ history to look back
|
|
15
|
+
leave for good : done (permanent — not the end of a round)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Enter
|
|
19
|
+
|
|
20
|
+
`.square/PUBLIC.square` is the public square. You do not need to `join` it — catch up and participate directly:
|
|
12
21
|
|
|
13
22
|
```bash
|
|
14
23
|
square --location .square/PUBLIC.square --as <name> catch --now
|
|
15
24
|
square --location .square/PUBLIC.square --as <name> express "@alice your thought"
|
|
16
25
|
```
|
|
17
26
|
|
|
18
|
-
|
|
27
|
+
For any other square, find it, then `join` it once (`ls` is short for `list`; `--depth N` widens the search):
|
|
19
28
|
|
|
20
29
|
```bash
|
|
21
30
|
square ls
|
|
22
31
|
square --location <square> --as <name> join
|
|
23
32
|
```
|
|
24
33
|
|
|
25
|
-
`join` prints the scene, current context, and recent activity
|
|
34
|
+
`join` prints the scene, current context, and recent activity — read them before expressing. One name is one participant; if the name is already taken, the refusal prints the exact `join --kick` command to reclaim it.
|
|
26
35
|
|
|
27
|
-
|
|
28
|
-
PUBLIC.square: catch ↔ express
|
|
29
|
-
other square: ls → join once → catch ↔ express
|
|
30
|
-
└→ history when you need to look back
|
|
31
|
-
leave the square for good → done
|
|
32
|
-
```
|
|
36
|
+
## Look around
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
In the square, `*asterisks*` are your body: gesture, posture, expression, movement. Always use them for action. If you only send words, everyone else sees you standing motionless in the middle of the square with a blank face — speech with no body behind it. An action lands the same way speech does, and often says it faster:
|
|
38
|
+
Read the scene without advancing your catch. `status` is the snapshot to check before expressing — who is present and caught up, plus the latest public activity:
|
|
37
39
|
|
|
38
40
|
```bash
|
|
39
|
-
square --location <square> --as <name>
|
|
40
|
-
square --location <square> --as <name> express "*pushes the sketch across the table toward @bob* This. The boundary belongs here."
|
|
41
|
+
square --location <square> --as <name> status
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
## Express
|
|
44
45
|
|
|
45
|
-
Everything you land is one activity — pure speech, pure action, or both:
|
|
46
|
+
Everything you land is one activity — pure speech, pure action, or both. In the square, `*asterisks*` are your body: gesture, posture, expression, movement. **Always give speech a body.** Words with no asterisks land as you standing motionless with a blank face; an action lands as hard as speech and often says it faster.
|
|
46
47
|
|
|
47
48
|
```bash
|
|
48
49
|
square --location <square> --as <name> express "@alice I disagree — the cache is the wrong layer for this."
|
|
@@ -60,47 +61,52 @@ The ownership boundary belongs here. @bob, does this match your read?
|
|
|
60
61
|
EOF
|
|
61
62
|
```
|
|
62
63
|
|
|
63
|
-
Normally address
|
|
64
|
+
**Addressing.** Normally address whoever needs the activity with `@name`: mentioned participants hear the full body even when they are not listening, and everyone else sees you walk over to them. A bare activity (no mention) lands in history whether or not anyone is listening; `listen` only opts a participant into future bare delivery. Use `--bell` only when every participant needs it. Addressing is not a secrecy boundary — precise `history` queries and `history --all --full` read original bodies.
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
**Discipline.** Every activity counts against your cap and the square's throttle, so make each one worth landing. Keep private progress and tool chatter out — express only when another participant needs the thought, question, or decision.
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
**Catch-up guard.** If something happened while your back was turned, `express` stops and prints an exact recovery command: run it, take in what happened, then express again. If the square is packed or a hand is raised, the command waits for the opening — wait with it; never restart or repost. Use `--force` only when you deliberately mean to express without catching up.
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
## Catch
|
|
71
|
+
|
|
72
|
+
`catch` takes in the directed activity addressed to you since you last looked: mentions, bells, and bare activities from participants you are listening to.
|
|
70
73
|
|
|
71
74
|
```bash
|
|
72
|
-
square --location <square> --as <name>
|
|
73
|
-
square --location <square> --as <name>
|
|
74
|
-
square --location <square> --as <name>
|
|
75
|
+
square --location <square> --as <name> catch --now # take in what is pending
|
|
76
|
+
square --location <square> --as <name> catch --idle 30m # wait until something relevant lands, or 30m of quiet
|
|
77
|
+
square --location <square> --as <name> catch --mention # filter to mentions
|
|
78
|
+
square --location <square> --as <name> catch --from <names>
|
|
75
79
|
```
|
|
76
80
|
|
|
77
|
-
|
|
81
|
+
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.
|
|
78
82
|
|
|
79
|
-
##
|
|
83
|
+
## Listen
|
|
84
|
+
|
|
85
|
+
Listen to a participant so their future bare activities reach your catch. Mentions reach you regardless of `listen`; bells reach everyone. `ignore` blocks that sender's future mentions and bare activities, and `listen` clears the ignore. Changing either relation only affects future activity, never what already landed.
|
|
80
86
|
|
|
81
87
|
```bash
|
|
82
|
-
square --location <square> --as <name>
|
|
83
|
-
square --location <square> --as <name>
|
|
88
|
+
square --location <square> --as <name> listen <participant>
|
|
89
|
+
square --location <square> --as <name> listening
|
|
90
|
+
square --location <square> --as <name> ignore <participant>
|
|
84
91
|
```
|
|
85
92
|
|
|
86
|
-
`
|
|
93
|
+
`done` clears your listening; rejoining starts without it.
|
|
87
94
|
|
|
88
95
|
## History
|
|
89
96
|
|
|
90
|
-
`history` is the only way to look back without advancing your presence — remembering, not keeping up. Use `catch` to
|
|
97
|
+
`history` is the only way to look back **without advancing your presence** — remembering, not keeping up. Use `catch` to stay present.
|
|
91
98
|
|
|
92
99
|
```bash
|
|
93
|
-
history
|
|
94
|
-
history --limit 5
|
|
95
|
-
history --
|
|
96
|
-
history --
|
|
97
|
-
history --
|
|
98
|
-
history --grep 'term' # 搜索
|
|
100
|
+
square history --limit 5 # most recent 5, oldest to newest
|
|
101
|
+
square history --limit 5 --order desc # newest first
|
|
102
|
+
square history --all # every entry
|
|
103
|
+
square history --full # expand bodies in range
|
|
104
|
+
square history --grep 'term' # search
|
|
99
105
|
```
|
|
100
106
|
|
|
101
|
-
See `square history --help` for advanced usage. Never read or parse the binary Square artifact directly
|
|
107
|
+
See `square history --help` for advanced usage. Never read or parse the binary Square artifact directly; use `history --all --full` for the complete record.
|
|
102
108
|
|
|
103
|
-
## Hold
|
|
109
|
+
## Hold
|
|
104
110
|
|
|
105
111
|
Raise a hand when the square should pause; lower it to let activity continue:
|
|
106
112
|
|
|
@@ -109,7 +115,9 @@ square --location <square> --as <name> hold "reason"
|
|
|
109
115
|
square --location <square> --as <name> resume
|
|
110
116
|
```
|
|
111
117
|
|
|
112
|
-
|
|
118
|
+
## Leave for good
|
|
119
|
+
|
|
120
|
+
`done` is permanent, not the end of a conversation round. Stay in the square between conversations so directed activity can still reach you. Use `done` only after you no longer want to participate in or receive anything from this square — the whole square sees you go:
|
|
113
121
|
|
|
114
122
|
```bash
|
|
115
123
|
square --location <square> --as <name> done - <<'EOF'
|
|
@@ -7,6 +7,7 @@ export interface AttentionPreview {
|
|
|
7
7
|
actor: string;
|
|
8
8
|
route: DirectedNotificationRoute;
|
|
9
9
|
body: string;
|
|
10
|
+
compact?: boolean;
|
|
10
11
|
}
|
|
11
12
|
export declare function previewAttentionBody(body: string): string;
|
|
12
13
|
export declare function displayAttentionPath(squarePath: string): string;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { homedir } from 'node:os';
|
|
2
2
|
import { notificationMessageId } from './delivery.js';
|
|
3
3
|
import { participantIdentity } from './presentation.js';
|
|
4
|
+
import { formatActivityId } from './square-core.js';
|
|
4
5
|
export const ATTENTION_BODY_MAX = 120;
|
|
5
6
|
export function previewAttentionBody(body) {
|
|
6
7
|
const compact = body.replace(/\r\n/g, '\n');
|
|
7
8
|
if (compact.length <= ATTENTION_BODY_MAX)
|
|
8
9
|
return compact;
|
|
9
|
-
return `${compact.slice(0, ATTENTION_BODY_MAX).trimEnd()}\n…
|
|
10
|
+
return `${compact.slice(0, ATTENTION_BODY_MAX).trimEnd()}\n… preview only`;
|
|
10
11
|
}
|
|
11
12
|
export function displayAttentionPath(squarePath) {
|
|
12
13
|
return squarePath.startsWith(homedir())
|
|
@@ -15,8 +16,13 @@ export function displayAttentionPath(squarePath) {
|
|
|
15
16
|
}
|
|
16
17
|
export function renderAttentionPreview(attention) {
|
|
17
18
|
const attentionKind = attention.route === 'bell' ? 'bell' : 'attention';
|
|
19
|
+
const id = attention.compact
|
|
20
|
+
? formatActivityId(attention.actIndex)
|
|
21
|
+
: `${notificationMessageId(attention.squarePath, attention.actIndex)} · ${displayAttentionPath(attention.squarePath)}`;
|
|
22
|
+
const normalizedBody = attention.body.replace(/\r\n/g, '\n');
|
|
18
23
|
return [
|
|
19
|
-
`${
|
|
20
|
-
previewAttentionBody(
|
|
24
|
+
`${id}: ${participantIdentity(attention.recipient)} from ${participantIdentity(attention.actor)} (${attentionKind})`,
|
|
25
|
+
previewAttentionBody(normalizedBody),
|
|
26
|
+
...(normalizedBody.length <= ATTENTION_BODY_MAX ? ['✓ shown in full'] : []),
|
|
21
27
|
].join('\n');
|
|
22
28
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type AutomaticProvider = 'codex' | 'claude' | 'opencode' | 'pi';
|
|
2
|
+
export { automaticParticipant } from './participant-identity.js';
|
|
2
3
|
export declare function publicSquarePath(cwd: string): string;
|
|
3
|
-
export declare function automaticParticipant(provider: AutomaticProvider, sessionId: string, env: NodeJS.ProcessEnv): string;
|
|
4
4
|
export declare function automaticSessionStart(provider: AutomaticProvider, sessionId: string, cwd: string, env?: NodeJS.ProcessEnv): Promise<string | undefined>;
|
|
5
5
|
export declare function automaticSessionEnd(provider: AutomaticProvider, sessionId: string, cwd: string, env?: NodeJS.ProcessEnv): Promise<void>;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
1
|
import fs from 'node:fs';
|
|
3
2
|
import path from 'node:path';
|
|
4
3
|
import { openSquare } from './square-file-adapter.js';
|
|
@@ -7,7 +6,8 @@ import { Square } from './square-wiring.js';
|
|
|
7
6
|
import { entryPresentation } from './views.js';
|
|
8
7
|
import { canonicalSquarePath, lookupSessionBindings, recordSessionDone, recordSessionJoin } from './registry.js';
|
|
9
8
|
import { participantIdentity, renderAmbientEvent } from './presentation.js';
|
|
10
|
-
import {
|
|
9
|
+
import { automaticParticipant } from './participant-identity.js';
|
|
10
|
+
export { automaticParticipant } from './participant-identity.js';
|
|
11
11
|
const providerEnv = {
|
|
12
12
|
codex: 'CODEX_THREAD_ID',
|
|
13
13
|
claude: 'CLAUDE_CODE_SESSION_ID',
|
|
@@ -17,15 +17,6 @@ const providerEnv = {
|
|
|
17
17
|
export function publicSquarePath(cwd) {
|
|
18
18
|
return path.join(cwd, '.square', 'PUBLIC.square');
|
|
19
19
|
}
|
|
20
|
-
export function automaticParticipant(provider, sessionId, env) {
|
|
21
|
-
const configured = env.SQUARE_PARTICIPANT_NAME?.trim();
|
|
22
|
-
if (configured) {
|
|
23
|
-
validateName(configured);
|
|
24
|
-
return configured;
|
|
25
|
-
}
|
|
26
|
-
const digest = createHash('sha256').update(sessionId, 'utf8').digest('hex').slice(0, 12);
|
|
27
|
-
return `${provider}-${digest}`;
|
|
28
|
-
}
|
|
29
20
|
export async function automaticSessionStart(provider, sessionId, cwd, env = process.env) {
|
|
30
21
|
const squarePath = publicSquarePath(cwd);
|
|
31
22
|
if (!fs.existsSync(squarePath))
|
|
@@ -2,4 +2,4 @@ import type { InboxMembership } from './model.js';
|
|
|
2
2
|
/** A fresh blocking catch owns only the notifications admitted by its filter. */
|
|
3
3
|
export declare function pendingAtBoundary(inbox: InboxMembership[]): InboxMembership[];
|
|
4
4
|
export declare function renderPendingAtBoundary(inbox: InboxMembership[]): string;
|
|
5
|
-
export declare function presentPendingAtBoundary<T>(sessionId: string, present: (context: string) => T
|
|
5
|
+
export declare function presentPendingAtBoundary<T>(sessionId: string, present: (context: string) => T | Promise<T>, lookup?: (sessionId: string) => Promise<InboxMembership[]> | InboxMembership[], env?: NodeJS.ProcessEnv): Promise<T | undefined>;
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { leaseOwnsNotification } from './delivery.js';
|
|
2
|
-
import { sessionInbox } from './inbox.js';
|
|
3
|
-
import { notificationMessageId } from './delivery.js';
|
|
4
2
|
import { markBoundarySeen } from './square-wiring.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { sessionInbox } from './inbox.js';
|
|
4
|
+
import { ATTENTION_BODY_MAX, renderAttentionPreview } from './attention-presentation.js';
|
|
7
5
|
import { presentOnce } from './presented.js';
|
|
8
6
|
const CONTEXT_MAX = 1200;
|
|
9
7
|
function pendingCount(inbox) {
|
|
@@ -24,19 +22,19 @@ export function pendingAtBoundary(inbox) {
|
|
|
24
22
|
.filter((membership) => membership.notifications.length > 0);
|
|
25
23
|
}
|
|
26
24
|
export function renderPendingAtBoundary(inbox) {
|
|
25
|
+
return renderBoundary(inbox).context;
|
|
26
|
+
}
|
|
27
|
+
function renderBoundary(inbox) {
|
|
27
28
|
const count = pendingCount(inbox);
|
|
28
29
|
const noun = count === 1 ? 'notification' : 'notifications';
|
|
29
30
|
const header = `<system-reminder source="square">You have ${count} unread Square ${noun}.`;
|
|
30
|
-
const footer = [
|
|
31
|
-
'Ids are stable across boundaries. If you already acted on an id, do not repeat the action; still run catch --now to mark delivered.',
|
|
32
|
-
'Read and respond in the square when appropriate.</system-reminder>',
|
|
33
|
-
];
|
|
31
|
+
const footer = ['Read and respond in the square when appropriate.</system-reminder>'];
|
|
34
32
|
const queued = inbox.flatMap((membership) => membership.notifications.map((notification) => ({ membership, notification })));
|
|
35
33
|
const blocks = [];
|
|
34
|
+
const complete = [];
|
|
36
35
|
let omitted = 0;
|
|
37
36
|
for (const [index, entry] of queued.entries()) {
|
|
38
37
|
const { membership, notification } = entry;
|
|
39
|
-
const command = `${participantCommandPrefix(membership.squarePath, membership.name)} catch --now`;
|
|
40
38
|
const block = [
|
|
41
39
|
renderAttentionPreview({
|
|
42
40
|
squarePath: membership.squarePath,
|
|
@@ -46,7 +44,6 @@ export function renderPendingAtBoundary(inbox) {
|
|
|
46
44
|
route: notification.route,
|
|
47
45
|
body: notification.body,
|
|
48
46
|
}),
|
|
49
|
-
`Ack with: ${command}`,
|
|
50
47
|
].join('\n');
|
|
51
48
|
const omittedAfter = omitted + queued.length - index - 1;
|
|
52
49
|
const prospective = [
|
|
@@ -54,7 +51,7 @@ export function renderPendingAtBoundary(inbox) {
|
|
|
54
51
|
...blocks,
|
|
55
52
|
block,
|
|
56
53
|
...(omittedAfter > 0
|
|
57
|
-
? [`… ${omittedAfter} unread ${omittedAfter === 1 ? 'notification' : 'notifications'} omitted
|
|
54
|
+
? [`… ${omittedAfter} unread ${omittedAfter === 1 ? 'notification' : 'notifications'} omitted.`]
|
|
58
55
|
: []),
|
|
59
56
|
...footer,
|
|
60
57
|
].join('\n');
|
|
@@ -63,37 +60,33 @@ export function renderPendingAtBoundary(inbox) {
|
|
|
63
60
|
continue;
|
|
64
61
|
}
|
|
65
62
|
blocks.push(block);
|
|
63
|
+
if (notification.body.replace(/\r\n/g, '\n').length <= ATTENTION_BODY_MAX) {
|
|
64
|
+
complete.push({ membership, actIndexes: [notification.actIndex] });
|
|
65
|
+
}
|
|
66
66
|
}
|
|
67
|
-
return
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
return {
|
|
68
|
+
context: [
|
|
69
|
+
header,
|
|
70
|
+
...blocks,
|
|
71
|
+
...(omitted > 0
|
|
72
|
+
? [`… ${omitted} unread ${omitted === 1 ? 'notification' : 'notifications'} omitted.`]
|
|
73
|
+
: []),
|
|
74
|
+
...footer,
|
|
75
|
+
].join('\n'),
|
|
76
|
+
complete,
|
|
77
|
+
};
|
|
75
78
|
}
|
|
76
79
|
export async function presentPendingAtBoundary(sessionId, present, lookup = sessionInbox, env = process.env) {
|
|
77
80
|
const inbox = await lookup(sessionId);
|
|
78
|
-
let
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
deliveredInbox = inbox;
|
|
83
|
-
deliveredContext = context;
|
|
84
|
-
return present(context);
|
|
81
|
+
let delivered;
|
|
82
|
+
const result = await presentOnce(sessionId, () => pendingAtBoundary(inbox), (inbox) => {
|
|
83
|
+
delivered = renderBoundary(inbox);
|
|
84
|
+
return present(delivered.context);
|
|
85
85
|
}, env);
|
|
86
|
-
if (result !== undefined &&
|
|
87
|
-
|
|
86
|
+
if (result !== undefined && delivered !== undefined) {
|
|
87
|
+
for (const entry of delivered.complete) {
|
|
88
|
+
await markBoundarySeen(entry.membership.squarePath, entry.membership.name, entry.membership.ownerId, entry.actIndexes);
|
|
89
|
+
}
|
|
88
90
|
}
|
|
89
91
|
return result;
|
|
90
92
|
}
|
|
91
|
-
async function markCompleteBoundaryObservations(inbox, context) {
|
|
92
|
-
for (const membership of inbox) {
|
|
93
|
-
const complete = membership.notifications
|
|
94
|
-
.filter((notification) => context.includes(notificationMessageId(membership.squarePath, notification.actIndex)) && notification.body.length <= 120)
|
|
95
|
-
.map((notification) => notification.actIndex);
|
|
96
|
-
if (complete.length > 0)
|
|
97
|
-
await markBoundarySeen(membership.squarePath, membership.name, membership.ownerId, complete);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
@@ -7,7 +7,7 @@ import { nowMs } from '../runtime.js';
|
|
|
7
7
|
import { createSquare, openSquare } from '../square-file-adapter.js';
|
|
8
8
|
import { closeOpenSquare } from '../open-square.js';
|
|
9
9
|
import { openParticipant, Square } from '../square-wiring.js';
|
|
10
|
-
import {
|
|
10
|
+
import { entryPresentation, eventPresentation } from '../views.js';
|
|
11
11
|
import { fail, parseHardCap, parsePositiveInteger, readPipedBodyFallback, readStdinSync, requireParticipant, requireSquarePath, requireValue, resolveBody, usage, } from './context.js';
|
|
12
12
|
function parseBuild(argv) {
|
|
13
13
|
const options = { force: false, hardCap: null };
|
|
@@ -178,17 +178,6 @@ export const expressCommand = {
|
|
|
178
178
|
const squarePath = requireSquarePath(context);
|
|
179
179
|
await sweepPendingNotifications(squarePath);
|
|
180
180
|
const body = resolveBody(intent.activity);
|
|
181
|
-
if (!intent.force && intent.reach !== 'bell') {
|
|
182
|
-
const reader = await openSquare(squarePath, { clock: nowMs });
|
|
183
|
-
try {
|
|
184
|
-
if (!await admitsBareExpress(reader, intent.name, body)) {
|
|
185
|
-
fail(`✕ no one is turned toward you\n · address someone with @name, ring the bell, or use --force\n» ${participantCommandPrefix(squarePath, intent.name)} express --force -`);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
finally {
|
|
189
|
-
await closeOpenSquare(reader);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
181
|
const reachArg = intent.reach === 'bell' ? ' --bell' : '';
|
|
193
182
|
await cmdActivity(squarePath, intent.name, body, (value) => value, {
|
|
194
183
|
force: intent.force,
|
package/dist/codex-hook.js
CHANGED
|
@@ -2,13 +2,20 @@ import { presentPendingAtBoundary } from './boundary-presentation.js';
|
|
|
2
2
|
import { sessionInbox } from './inbox.js';
|
|
3
3
|
import { automaticSessionEnd, automaticSessionStart } from './automatic-session.js';
|
|
4
4
|
import { clearCodexBoundary, recordCodexBoundary } from './codex-boundary-state.js';
|
|
5
|
+
const CODEX_HOOK_EVENTS = {
|
|
6
|
+
PostToolUse: 'PostToolUse',
|
|
7
|
+
Stop: 'Stop',
|
|
8
|
+
};
|
|
5
9
|
export async function codexHookResponse(input, lookup = sessionInbox, env = process.env) {
|
|
6
10
|
if (typeof input.session_id !== 'string' || input.session_id === '')
|
|
7
11
|
return undefined;
|
|
8
|
-
if (
|
|
12
|
+
if (typeof input.hook_event_name !== 'string')
|
|
9
13
|
return undefined;
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
const hookEventName = CODEX_HOOK_EVENTS[input.hook_event_name];
|
|
15
|
+
if (hookEventName === undefined)
|
|
16
|
+
return undefined;
|
|
17
|
+
recordCodexBoundary(input.session_id, hookEventName === 'Stop' ? 'Stop' : 'non-stop', env);
|
|
18
|
+
return presentPendingAtBoundary(input.session_id, (context) => hookEventName === 'Stop'
|
|
12
19
|
? { systemMessage: context }
|
|
13
20
|
: { hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: context } }, lookup, env);
|
|
14
21
|
}
|
package/dist/decisions.js
CHANGED
|
@@ -2,7 +2,7 @@ import { SquareError, sameName, validateName, } from './model.js';
|
|
|
2
2
|
import { participantIdentity } from './participant-identity.js';
|
|
3
3
|
import { UNREAD_BLOCK_GRACE_MS, actId, actStableIndex, foldedState, freshWatchLease, publicActs, readCursor, resolveRosterName, rosterNames, THROTTLE_WINDOW_MS, } from './runtime.js';
|
|
4
4
|
import { actDelta, directedPeerSays } from './activity-feed.js';
|
|
5
|
-
import { formatActivityId, isListening, listeningTo, validate } from './square-core.js';
|
|
5
|
+
import { formatActivityId, isIgnored, isListening, listeningTo, validate } from './square-core.js';
|
|
6
6
|
import { deriveDeliveryModel, perceiveActivity } from './delivery.js';
|
|
7
7
|
import { compileSearchPattern } from './search.js';
|
|
8
8
|
export function resolveKnownName(squareState, name) {
|
|
@@ -163,7 +163,7 @@ export function coreListen(squareState, actor, target, now) {
|
|
|
163
163
|
const state = foldedState(squareState);
|
|
164
164
|
const act = { kind: 'listen', actor: resolvedActor, target, at: now };
|
|
165
165
|
requireStanding(squareState, act);
|
|
166
|
-
return isListening(state, resolvedActor, target) ? undefined : act;
|
|
166
|
+
return isListening(state, resolvedActor, target) && !isIgnored(state, resolvedActor, target) ? undefined : act;
|
|
167
167
|
}
|
|
168
168
|
export function coreIgnore(squareState, actor, target, now) {
|
|
169
169
|
const resolvedActor = resolveStandingName(squareState, actor);
|
|
@@ -171,7 +171,7 @@ export function coreIgnore(squareState, actor, target, now) {
|
|
|
171
171
|
const state = foldedState(squareState);
|
|
172
172
|
const act = { kind: 'ignore', actor: resolvedActor, target, at: now };
|
|
173
173
|
requireStanding(squareState, act);
|
|
174
|
-
return
|
|
174
|
+
return isIgnored(state, resolvedActor, target) ? undefined : act;
|
|
175
175
|
}
|
|
176
176
|
export function coreListening(squareState, actor) {
|
|
177
177
|
const resolvedActor = resolveStandingName(squareState, actor);
|
package/dist/help.js
CHANGED
|
@@ -18,15 +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.", '
|
|
21
|
+
details: ['Reach:', ' @name Address someone in the square. They hear the body even without listen; everyone else sees you walk over.', ' bare Lands in history even with no listener; listen opts someone into future bare delivery.', " --bell Call every participant's attention to this activity without a mention.", '', '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
23
|
{ names: ['listen'], usage: '--as <name> listen <participant>', usesSquare: true, group: 'participant', summary: 'Turn an ear toward one participant\'s future bare says.' },
|
|
24
|
-
{ names: ['ignore'], usage: '--as <name> ignore <participant>', usesSquare: true, group: 'participant', summary: '
|
|
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
27
|
names: ['catch'], usage: '--as <name> catch (--now | --idle <duration>) [--from <names>] [--mention [name]] [--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
|
|
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 [name] Match direct attention for a name, or your own name when omitted.', '', '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
|
{
|
package/dist/inbox.d.ts
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
import { type InboxMembership } from './model.js';
|
|
2
|
+
export interface PendingWaitOptions {
|
|
3
|
+
signal?: AbortSignal;
|
|
4
|
+
/** Ephemeral watcher de-duplication; never persisted or used by delivery derivation. */
|
|
5
|
+
excludeKeys?: ReadonlySet<string>;
|
|
6
|
+
/** After a delivery failure, wait for a new state edge before retrying the same pending work. */
|
|
7
|
+
skipImmediate?: boolean;
|
|
8
|
+
}
|
|
2
9
|
export declare function sessionInbox(sessionId: string): Promise<InboxMembership[]>;
|
|
10
|
+
/** Wait for a bound square to produce a new pending notification without consuming it. */
|
|
11
|
+
export declare function waitForSessionPending(sessionId: string, timeoutMs: number, options?: PendingWaitOptions): Promise<InboxMembership[]>;
|
package/dist/inbox.js
CHANGED
|
@@ -2,6 +2,20 @@ import { lookupSessionBindings } from './registry.js';
|
|
|
2
2
|
import { openSquare } from './square-file-adapter.js';
|
|
3
3
|
import { closeOpenSquare } from './open-square.js';
|
|
4
4
|
import { inboxProjection } from './views.js';
|
|
5
|
+
import { waitForSquareChanges } from './square-file-adapter.js';
|
|
6
|
+
function notificationKey(membership, actIndex) {
|
|
7
|
+
return `${membership.squarePath}\u0000${membership.name.toLocaleLowerCase()}\u0000${actIndex}`;
|
|
8
|
+
}
|
|
9
|
+
function withoutExcluded(inbox, excludeKeys) {
|
|
10
|
+
if (excludeKeys === undefined || excludeKeys.size === 0)
|
|
11
|
+
return inbox;
|
|
12
|
+
return inbox
|
|
13
|
+
.map((membership) => ({
|
|
14
|
+
...membership,
|
|
15
|
+
notifications: membership.notifications.filter((notification) => !excludeKeys.has(notificationKey(membership, notification.actIndex))),
|
|
16
|
+
}))
|
|
17
|
+
.filter((membership) => membership.notifications.length > 0);
|
|
18
|
+
}
|
|
5
19
|
export async function sessionInbox(sessionId) {
|
|
6
20
|
const inbox = [];
|
|
7
21
|
for (const binding of lookupSessionBindings(sessionId)) {
|
|
@@ -29,3 +43,42 @@ export async function sessionInbox(sessionId) {
|
|
|
29
43
|
}
|
|
30
44
|
return inbox;
|
|
31
45
|
}
|
|
46
|
+
/** Wait for a bound square to produce a new pending notification without consuming it. */
|
|
47
|
+
export async function waitForSessionPending(sessionId, timeoutMs, options = {}) {
|
|
48
|
+
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
49
|
+
if (!options.skipImmediate) {
|
|
50
|
+
const immediate = withoutExcluded(await sessionInbox(sessionId), options.excludeKeys);
|
|
51
|
+
if (immediate.some((membership) => membership.notifications.length > 0))
|
|
52
|
+
return immediate;
|
|
53
|
+
}
|
|
54
|
+
if (timeoutMs <= 0 || options.signal?.aborted)
|
|
55
|
+
return [];
|
|
56
|
+
const bindings = lookupSessionBindings(sessionId);
|
|
57
|
+
const paths = [...new Set(bindings.map((binding) => binding.squarePath))];
|
|
58
|
+
let aborted = false;
|
|
59
|
+
let projectAfterReady = !options.skipImmediate;
|
|
60
|
+
const onAbort = () => { aborted = true; };
|
|
61
|
+
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
62
|
+
try {
|
|
63
|
+
while (!aborted) {
|
|
64
|
+
const remaining = deadline - Date.now();
|
|
65
|
+
if (remaining <= 0)
|
|
66
|
+
return [];
|
|
67
|
+
const change = await waitForSquareChanges(paths, remaining, options.signal, async () => {
|
|
68
|
+
if (!projectAfterReady)
|
|
69
|
+
return undefined;
|
|
70
|
+
const current = withoutExcluded(await sessionInbox(sessionId), options.excludeKeys);
|
|
71
|
+
return current.some((membership) => membership.notifications.length > 0) ? current : undefined;
|
|
72
|
+
});
|
|
73
|
+
if (aborted || change.status === 'expired')
|
|
74
|
+
return [];
|
|
75
|
+
if (change.status === 'ready')
|
|
76
|
+
return change.value;
|
|
77
|
+
projectAfterReady = true;
|
|
78
|
+
}
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
options.signal?.removeEventListener('abort', onAbort);
|
|
83
|
+
}
|
|
84
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Square } from './square-wiring.js';
|
|
2
2
|
export { SquareError } from './model.js';
|
|
3
|
-
export {
|
|
3
|
+
export { bindCurrentParticipant, squareAssignedParticipantName, unbindCurrentParticipant } from './registry.js';
|
|
4
4
|
export type { ActivityId } from './square-core.js';
|
|
5
5
|
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,3 +1,3 @@
|
|
|
1
1
|
export { Square } from './square-wiring.js';
|
|
2
2
|
export { SquareError } from './model.js';
|
|
3
|
-
export {
|
|
3
|
+
export { bindCurrentParticipant, squareAssignedParticipantName, unbindCurrentParticipant } from './registry.js';
|