@astrosheep/square 0.3.11 → 0.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity.js +2 -7
- package/dist/artifact.js +335 -647
- package/dist/boundary-presentation.js +1 -1
- package/dist/cli/context.js +2 -2
- package/dist/cli/harness-command.js +1 -1
- package/dist/cli/maintenance-commands.js +12 -69
- package/dist/cli/observation-commands.js +11 -18
- package/dist/cli/program.js +3 -2
- package/dist/cli/registry.js +1 -2
- package/dist/cli/square-commands.js +17 -18
- package/dist/compact.js +4 -4
- package/dist/decisions.js +11 -5
- package/dist/delivery-health.js +2 -1
- package/dist/delivery.js +11 -47
- package/dist/help.js +8 -10
- package/dist/index.js +6 -4
- package/dist/list.js +3 -47
- package/dist/model.js +0 -6
- package/dist/notifications.js +16 -8
- package/dist/presentation.js +22 -37
- package/dist/runtime.js +6 -21
- package/dist/square-application.js +9 -78
- package/dist/square-core.js +56 -9
- package/dist/wake-attempts.js +8 -4
- package/guides/participant.md +7 -174
- package/package.json +1 -3
- package/skills/brainstorm/SKILL.md +6 -6
- package/skills/square/.claude-plugin/plugin.json +1 -1
- package/skills/square/SKILL.md +23 -14
- package/skills/square-feedback/SKILL.md +7 -7
- package/dist/doctor.js +0 -36
- package/template.md +0 -4
- package/templates/architect.md +0 -4
- package/templates/brainstorm.md +0 -4
|
@@ -16,7 +16,7 @@ export function pendingAtBoundary(inbox) {
|
|
|
16
16
|
return membership;
|
|
17
17
|
return {
|
|
18
18
|
...membership,
|
|
19
|
-
notifications: membership.notifications.filter((notification) => !leaseOwnsNotification(lease,
|
|
19
|
+
notifications: membership.notifications.filter((notification) => !leaseOwnsNotification(lease, notification)),
|
|
20
20
|
};
|
|
21
21
|
})
|
|
22
22
|
.filter((membership) => membership.notifications.length > 0);
|
package/dist/cli/context.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { loadSquare } from '../artifact.js';
|
|
5
5
|
import { commandUsageHint } from '../help.js';
|
|
6
6
|
import { parseParticipantList, validateName } from '../model.js';
|
|
7
|
-
export const DEFAULT_SQUARE_PATH = '.square/SQUARE.
|
|
7
|
+
export const DEFAULT_SQUARE_PATH = '.square/SQUARE.square';
|
|
8
8
|
export function readStdinSync() {
|
|
9
9
|
try {
|
|
10
10
|
return fs.readFileSync(0, 'utf8');
|
|
@@ -99,7 +99,7 @@ function resolveDefaultSquarePath() {
|
|
|
99
99
|
}
|
|
100
100
|
const candidates = [];
|
|
101
101
|
for (const entry of entries) {
|
|
102
|
-
if (!entry.isFile() || !entry.name.endsWith('.
|
|
102
|
+
if (!entry.isFile() || !entry.name.endsWith('.square'))
|
|
103
103
|
continue;
|
|
104
104
|
const fullPath = path.join(directory, entry.name);
|
|
105
105
|
try {
|
|
@@ -87,7 +87,7 @@ export function formatHarnessResult(result) {
|
|
|
87
87
|
return `${[...result.notes, ...result.lines].join('\n')}\n`;
|
|
88
88
|
}
|
|
89
89
|
export function runHarnessCommand(argv, squarePath) {
|
|
90
|
-
const context = { homeDir: os.homedir(), squarePath: squarePath ?? '.square/SQUARE.
|
|
90
|
+
const context = { homeDir: os.homedir(), squarePath: squarePath ?? '.square/SQUARE.square', command: 'harness' };
|
|
91
91
|
const intent = harnessCommand.parse(argv, context);
|
|
92
92
|
return Promise.resolve(harnessCommand.execute(intent, context)).then(formatHarnessResult);
|
|
93
93
|
}
|
|
@@ -1,82 +1,25 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { renderDoctorClean, renderDoctorProblems, renderDoctorRepaired, renderDoctorUnfixable, withPathOutput, } from '../presentation.js';
|
|
1
|
+
import { diagnoseSquareFile } from '../artifact.js';
|
|
2
|
+
import { renderDoctorClean, renderDoctorUnfixable, withPathOutput } from '../presentation.js';
|
|
4
3
|
import { inSquareCount } from '../runtime.js';
|
|
5
|
-
import { repairSquare } from '../square-application.js';
|
|
6
|
-
import { SquareError } from '../model.js';
|
|
7
|
-
import { pruneRegistry } from '../registry.js';
|
|
8
4
|
import { usage } from './context.js';
|
|
9
|
-
function readSquareText(squarePath) {
|
|
10
|
-
try {
|
|
11
|
-
return fs.readFileSync(squarePath, 'utf8');
|
|
12
|
-
}
|
|
13
|
-
catch (error) {
|
|
14
|
-
if (error.code === 'ENOENT')
|
|
15
|
-
throw new SquareError('not_found', `square file not found: ${squarePath}`);
|
|
16
|
-
throw error;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function quarantinePath(squarePath) {
|
|
20
|
-
return squarePath.replace(/\.md$/, '') + '.quarantine.md';
|
|
21
|
-
}
|
|
22
|
-
function registryActs(squarePath) {
|
|
23
|
-
if (!fs.existsSync(squarePath))
|
|
24
|
-
return [];
|
|
25
|
-
try {
|
|
26
|
-
return loadSquare(squarePath).acts;
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
// A temporarily unreadable artifact cannot disprove a cache binding.
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
5
|
export const doctorCommand = {
|
|
34
6
|
parse(argv, context) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (argument === '--fix')
|
|
39
|
-
fix = true;
|
|
40
|
-
else if (argument === '--before') {
|
|
41
|
-
index += 1;
|
|
42
|
-
if (argv[index] === undefined)
|
|
43
|
-
usage(context.command);
|
|
44
|
-
}
|
|
45
|
-
else
|
|
46
|
-
usage(context.command);
|
|
47
|
-
}
|
|
48
|
-
return { fix };
|
|
7
|
+
if (argv.length > 0)
|
|
8
|
+
usage(context.command);
|
|
9
|
+
return undefined;
|
|
49
10
|
},
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (diagnosis.unfixable) {
|
|
54
|
-
return {
|
|
55
|
-
output: withPathOutput(context.squarePath, renderDoctorUnfixable(diagnosis.unfixable), { participantCount: inSquareCount(loadSquare(context.squarePath)) }),
|
|
56
|
-
exitCode: 2,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
const summary = diagnosis.problems.length === 0 ? renderDoctorClean() : renderDoctorProblems(diagnosis.problems);
|
|
11
|
+
execute(_intent, context) {
|
|
12
|
+
const diagnosis = diagnoseSquareFile(context.squarePath);
|
|
13
|
+
if (diagnosis.unfixable !== undefined || diagnosis.doc === undefined) {
|
|
60
14
|
return {
|
|
61
|
-
output: withPathOutput(context.squarePath,
|
|
62
|
-
exitCode: diagnosis.problems.length === 0 ? 0 : 1,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
const repair = await repairSquare(context.squarePath);
|
|
66
|
-
if (repair.diagnosis.unfixable) {
|
|
67
|
-
return {
|
|
68
|
-
output: withPathOutput(context.squarePath, renderDoctorUnfixable(repair.diagnosis.unfixable), { participantCount: inSquareCount(loadSquare(context.squarePath)) }),
|
|
15
|
+
output: withPathOutput(context.squarePath, renderDoctorUnfixable(diagnosis.unfixable ?? 'the snapshot could not be decoded')),
|
|
69
16
|
exitCode: 2,
|
|
70
17
|
};
|
|
71
18
|
}
|
|
72
|
-
const repaired = repair.repaired;
|
|
73
|
-
const registry = pruneRegistry(registryActs);
|
|
74
|
-
if (registry.removed > 0) {
|
|
75
|
-
repaired.actions.push({ message: `pruned ${registry.removed} obsolete registry membership(s)` });
|
|
76
|
-
}
|
|
77
|
-
const sidecar = quarantinePath(context.squarePath);
|
|
78
19
|
return {
|
|
79
|
-
output: withPathOutput(context.squarePath,
|
|
20
|
+
output: withPathOutput(context.squarePath, renderDoctorClean(), {
|
|
21
|
+
participantCount: inSquareCount(diagnosis.doc),
|
|
22
|
+
}),
|
|
80
23
|
};
|
|
81
24
|
},
|
|
82
25
|
present(result) {
|
|
@@ -6,7 +6,8 @@ import { sessionInbox } from '../inbox.js';
|
|
|
6
6
|
import { sweepPendingNotifications } from '../notifications.js';
|
|
7
7
|
import { cmdListSquares } from '../list.js';
|
|
8
8
|
import { sameName } from '../model.js';
|
|
9
|
-
import {
|
|
9
|
+
import { parseActivityId } from '../square-core.js';
|
|
10
|
+
import { commandPrefix, participantCommandPrefix, renderActivitiesView, renderGrepActivitiesView, renderAmbientEvent, withPathOutput, } from '../presentation.js';
|
|
10
11
|
import { actId, inSquareCount, nowMs, sayNumberFor } from '../runtime.js';
|
|
11
12
|
import { cmdStream, cmdStreamNdjson } from '../stream.js';
|
|
12
13
|
import { formatRelativeTime, formatTimestamp, parseTimeOrRelative } from '../time.js';
|
|
@@ -98,10 +99,10 @@ export const catchCommand = {
|
|
|
98
99
|
present: () => { },
|
|
99
100
|
};
|
|
100
101
|
function parseActRef(value, flag) {
|
|
101
|
-
const
|
|
102
|
-
if (
|
|
103
|
-
fail(`Invalid ${flag}: expected an activity id like
|
|
104
|
-
return
|
|
102
|
+
const index = parseActivityId(value);
|
|
103
|
+
if (index === undefined)
|
|
104
|
+
fail(`Invalid ${flag}: expected an activity id like act/12`);
|
|
105
|
+
return index;
|
|
105
106
|
}
|
|
106
107
|
function parseTimestamp(value, flag) {
|
|
107
108
|
const timestamp = parseTimeOrRelative(value, nowMs());
|
|
@@ -305,24 +306,16 @@ export const historyCommand = {
|
|
|
305
306
|
return events.map((item) => renderFields(doc, item, options.format)).join('\n') + (events.length > 0 ? '\n' : '');
|
|
306
307
|
}
|
|
307
308
|
const pattern = options.grep ?? options.fixed;
|
|
309
|
+
const archive = options.atIndex != null
|
|
310
|
+
|| (options.ids !== undefined && options.ids.length > 0)
|
|
311
|
+
|| (options.lastN == null && options.full === true);
|
|
308
312
|
const output = pattern === undefined || pattern === ''
|
|
309
|
-
? renderActivitiesView(doc, events, null, options.full, context.squarePath, options.viewer ?? '')
|
|
313
|
+
? renderActivitiesView(doc, events, null, options.full, context.squarePath, options.viewer ?? '', archive ? 'archive' : 'ambient')
|
|
310
314
|
: renderGrepActivitiesView(events, totalMatches, options.full, context.squarePath, pattern, options.fixed !== undefined);
|
|
311
315
|
return withPathOutput(context.squarePath, output, { participantCount: inSquareCount(doc) });
|
|
312
316
|
},
|
|
313
317
|
present: (result) => process.stdout.write(result),
|
|
314
318
|
};
|
|
315
|
-
export const warmupCommand = {
|
|
316
|
-
parse(argv, context) { if (argv.length > 0)
|
|
317
|
-
usage(context.command); return undefined; },
|
|
318
|
-
execute(_intent, context) {
|
|
319
|
-
const doc = loadSquare(context.squarePath);
|
|
320
|
-
return withPathOutput(context.squarePath, doc.warmup.join('\n'), {
|
|
321
|
-
participantCount: inSquareCount(doc),
|
|
322
|
-
});
|
|
323
|
-
},
|
|
324
|
-
present: (result) => process.stdout.write(result),
|
|
325
|
-
};
|
|
326
319
|
export const participantsCommand = {
|
|
327
320
|
parse(argv, context) { if (argv.length > 0)
|
|
328
321
|
usage(context.command); return undefined; },
|
|
@@ -383,7 +376,7 @@ export const statusCommand = {
|
|
|
383
376
|
: undefined;
|
|
384
377
|
const visible = result.latestAct === undefined
|
|
385
378
|
? ''
|
|
386
|
-
:
|
|
379
|
+
: renderAmbientEvent(result.latestAct, context.name ?? '', {
|
|
387
380
|
now: result.now,
|
|
388
381
|
preview: 200,
|
|
389
382
|
actNumber: result.latestAct.kind === 'say'
|
package/dist/cli/program.js
CHANGED
|
@@ -3,9 +3,10 @@ import { SquareError } from '../model.js';
|
|
|
3
3
|
import { defaultContext, parseGlobalArgs } from './context.js';
|
|
4
4
|
import { executeRegisteredCommand, findCommand } from './registry.js';
|
|
5
5
|
function isMutatingCommand(command, argv) {
|
|
6
|
+
void argv;
|
|
6
7
|
if (['build', 'join', 'catch', 'express', 'done', 'hold', 'resume', 'compact'].includes(command))
|
|
7
8
|
return true;
|
|
8
|
-
return
|
|
9
|
+
return false;
|
|
9
10
|
}
|
|
10
11
|
function handleSquareError(error) {
|
|
11
12
|
if (error instanceof SquareError) {
|
|
@@ -19,7 +20,7 @@ export async function runCli(rawArgs = process.argv.slice(2)) {
|
|
|
19
20
|
try {
|
|
20
21
|
const requestedHelp = helpRequest(rawArgs);
|
|
21
22
|
if (requestedHelp !== undefined) {
|
|
22
|
-
await executeRegisteredCommand('help', requestedHelp.command === undefined ? [] : [requestedHelp.command], defaultContext('help', '.square/SQUARE.
|
|
23
|
+
await executeRegisteredCommand('help', requestedHelp.command === undefined ? [] : [requestedHelp.command], defaultContext('help', '.square/SQUARE.square'));
|
|
23
24
|
return;
|
|
24
25
|
}
|
|
25
26
|
const parsed = parseGlobalArgs(rawArgs);
|
package/dist/cli/registry.js
CHANGED
|
@@ -2,7 +2,7 @@ import { buildCommand, compactCommand, doneCommand, expressCommand, holdCommand,
|
|
|
2
2
|
import { doctorCommand } from './maintenance-commands.js';
|
|
3
3
|
import { harnessCommand, installCommand, uninstallCommand } from './harness-command.js';
|
|
4
4
|
import { helpCommand, versionCommand } from './meta-commands.js';
|
|
5
|
-
import { catchCommand, claudeHookCommand, codexHookCommand, historyCommand, inboxCommand, listCommand, participantsCommand, statusCommand, streamCommand,
|
|
5
|
+
import { catchCommand, claudeHookCommand, codexHookCommand, historyCommand, inboxCommand, listCommand, participantsCommand, statusCommand, streamCommand, } from './observation-commands.js';
|
|
6
6
|
/** Every public command is an executable adapter, including aliases and utility commands. */
|
|
7
7
|
export const commandRegistry = [
|
|
8
8
|
{ names: ['build'], spec: buildCommand },
|
|
@@ -23,7 +23,6 @@ export const commandRegistry = [
|
|
|
23
23
|
{ names: ['compact'], spec: compactCommand },
|
|
24
24
|
{ names: ['doctor'], spec: doctorCommand },
|
|
25
25
|
{ names: ['history'], spec: historyCommand },
|
|
26
|
-
{ names: ['warmup'], spec: warmupCommand },
|
|
27
26
|
{ names: ['status'], spec: statusCommand },
|
|
28
27
|
{ names: ['participants'], spec: participantsCommand },
|
|
29
28
|
{ names: ['help'], spec: helpCommand },
|
|
@@ -2,11 +2,12 @@ import { cmdActivity } from '../activity.js';
|
|
|
2
2
|
import { loadSquare } from '../artifact.js';
|
|
3
3
|
import { cmdCompact } from '../compact.js';
|
|
4
4
|
import { SquareError, formatHardCap, validateName, } from '../model.js';
|
|
5
|
-
import { participantCommandPrefix,
|
|
5
|
+
import { participantCommandPrefix, renderEventCli, renderPublicTail, withPathOutput, } from '../presentation.js';
|
|
6
6
|
import { hasAutomaticDeliveryIdentity, localParticipantOwner, recordLocalDone, recordLocalJoin } from '../registry.js';
|
|
7
7
|
import { sweepPendingNotifications } from '../notifications.js';
|
|
8
8
|
import { inSquareCount, isCurrentlyJoined, nowMs, resolveRosterName } from '../runtime.js';
|
|
9
9
|
import { createSquare, execute } from '../square-application.js';
|
|
10
|
+
import { formatActivityId, parseActivityId } from '../square-core.js';
|
|
10
11
|
import { fail, parseHardCap, parsePositiveInteger, readPipedBodyFallback, readStdinSync, requireParticipant, requireValue, resolveBody, usage, } from './context.js';
|
|
11
12
|
function parseBuild(argv) {
|
|
12
13
|
const options = { force: false, hardCap: null };
|
|
@@ -92,11 +93,12 @@ export const joinCommand = {
|
|
|
92
93
|
const fallback = hasAutomaticDeliveryIdentity()
|
|
93
94
|
? []
|
|
94
95
|
: ['', `» ${participantCommandPrefix(context.squarePath, joinedName)} catch --idle 30m`, ' no session delivery detected — keep this catch open for new activity'];
|
|
96
|
+
const scene = after.warmup.join('\n').trim();
|
|
95
97
|
const output = [
|
|
96
|
-
`●
|
|
98
|
+
`● You stepped into the square`,
|
|
99
|
+
...(isRejoin || scene === '' ? [] : ['', scene]),
|
|
97
100
|
...(isRejoin || contextText === '' ? [] : ['', 'context', contextText]),
|
|
98
101
|
...(activities === '' ? [] : ['', 'recent activity', activities]),
|
|
99
|
-
...(isRejoin ? [] : ['', `» ${participantCommandPrefix(context.squarePath, joinedName)} warmup`]),
|
|
100
102
|
...fallback,
|
|
101
103
|
].join('\n');
|
|
102
104
|
return withPathOutput(context.squarePath, output, { participantCount: inSquareCount(after) });
|
|
@@ -125,7 +127,9 @@ export const joinCommand = {
|
|
|
125
127
|
const line = reconnect && !intent.kick
|
|
126
128
|
? `● you are already in the square`
|
|
127
129
|
: `✓ you banished the original ${joinedName} — the name is yours`;
|
|
128
|
-
|
|
130
|
+
const scene = doc.warmup.join('\n').trim();
|
|
131
|
+
const showScene = !(reconnect && !intent.kick) && scene !== '';
|
|
132
|
+
return withPathOutput(context.squarePath, `${line}${showScene ? `\n\n${scene}` : ''}${fallback}`, { participantCount: inSquareCount(doc) });
|
|
129
133
|
}
|
|
130
134
|
},
|
|
131
135
|
present: (result) => process.stdout.write(result),
|
|
@@ -133,7 +137,6 @@ export const joinCommand = {
|
|
|
133
137
|
function parseActivity(argv, context) {
|
|
134
138
|
let force = false;
|
|
135
139
|
let noWait = false;
|
|
136
|
-
let beside;
|
|
137
140
|
let bell = false;
|
|
138
141
|
let reply;
|
|
139
142
|
const bodyArgs = [];
|
|
@@ -143,25 +146,21 @@ function parseActivity(argv, context) {
|
|
|
143
146
|
force = true;
|
|
144
147
|
else if (argument === '--no-wait')
|
|
145
148
|
noWait = true;
|
|
146
|
-
else if (argument === '--beside')
|
|
147
|
-
beside
|
|
148
|
-
index += 1;
|
|
149
|
-
}
|
|
149
|
+
else if (argument === '--beside')
|
|
150
|
+
fail('✕ express does not know --beside\n» square express --help');
|
|
150
151
|
else if (argument === '--bell')
|
|
151
152
|
bell = true;
|
|
152
153
|
else if (argument === '--reply') {
|
|
153
|
-
const
|
|
154
|
-
if (
|
|
155
|
-
fail('Invalid --reply: expected an activity id like
|
|
156
|
-
reply =
|
|
154
|
+
const replyIndex = parseActivityId(requireValue(argv, index, argument));
|
|
155
|
+
if (replyIndex === undefined)
|
|
156
|
+
fail('Invalid --reply: expected an activity id like act/12');
|
|
157
|
+
reply = replyIndex;
|
|
157
158
|
index += 1;
|
|
158
159
|
}
|
|
159
160
|
else
|
|
160
161
|
bodyArgs.push(argument);
|
|
161
162
|
}
|
|
162
|
-
|
|
163
|
-
fail('Invalid express options: --beside and --bell are mutually exclusive.');
|
|
164
|
-
const reach = bell ? 'bell' : beside === undefined ? undefined : { beside };
|
|
163
|
+
const reach = bell ? 'bell' : undefined;
|
|
165
164
|
if (bodyArgs.length !== 1) {
|
|
166
165
|
if (bodyArgs.length === 0) {
|
|
167
166
|
const piped = readPipedBodyFallback();
|
|
@@ -176,13 +175,13 @@ export const expressCommand = {
|
|
|
176
175
|
parse: parseActivity,
|
|
177
176
|
async execute(intent, context) {
|
|
178
177
|
await sweepPendingNotifications(context.squarePath);
|
|
179
|
-
const reachArg = intent.reach === 'bell' ? ' --bell' :
|
|
178
|
+
const reachArg = intent.reach === 'bell' ? ' --bell' : '';
|
|
180
179
|
await cmdActivity(context.squarePath, intent.name, intent.activity, resolveBody, {
|
|
181
180
|
force: intent.force,
|
|
182
181
|
noWait: intent.noWait,
|
|
183
182
|
reach: intent.reach,
|
|
184
183
|
reply: intent.reply,
|
|
185
|
-
forceCommand: `${participantCommandPrefix(context.squarePath, intent.name)} express --force${reachArg}${intent.reply === undefined ? '' : ` --reply
|
|
184
|
+
forceCommand: `${participantCommandPrefix(context.squarePath, intent.name)} express --force${reachArg}${intent.reply === undefined ? '' : ` --reply ${formatActivityId(intent.reply)}`} -`,
|
|
186
185
|
});
|
|
187
186
|
},
|
|
188
187
|
present: () => { },
|
package/dist/compact.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { SquareError } from './model.js';
|
|
2
2
|
import { withPathOutput } from './presentation.js';
|
|
3
3
|
import { execute } from './square-application.js';
|
|
4
|
-
function
|
|
5
|
-
return squarePath.replace(/\.
|
|
4
|
+
function archivePath(squarePath) {
|
|
5
|
+
return squarePath.replace(/\.square$/, '') + '.archive.square';
|
|
6
6
|
}
|
|
7
7
|
export async function cmdCompact(squarePath, opts) {
|
|
8
8
|
try {
|
|
9
9
|
let archivedCount;
|
|
10
10
|
let keptCount;
|
|
11
|
-
const archive =
|
|
11
|
+
const archive = archivePath(squarePath);
|
|
12
12
|
const committed = await execute(squarePath, { type: 'compact', keep: opts.keep, archivePath: archive });
|
|
13
13
|
const result = committed.result;
|
|
14
14
|
archivedCount = result.archived.length;
|
|
15
15
|
keptCount = result.doc.acts.length;
|
|
16
|
-
const summary = ['✓ compacted', ` · archived ${archivedCount} activities`, ` · kept ${keptCount} activities`, ...(archivedCount > 0 ? [` ·
|
|
16
|
+
const summary = ['✓ compacted', ` · archived ${archivedCount} activities`, ` · kept ${keptCount} activities`, ...(archivedCount > 0 ? [` · archive ${archive}`] : [])].join('\n');
|
|
17
17
|
process.stdout.write(withPathOutput(squarePath, summary));
|
|
18
18
|
}
|
|
19
19
|
catch (err) {
|
package/dist/decisions.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SquareError, sameName, validateName, } from './model.js';
|
|
2
2
|
import { UNREAD_BLOCK_GRACE_MS, actId, actStableIndex, foldedState, freshWatchLease, getReadState, publicActs, readCursor, resolveRosterName, rosterNames, matchesMentionTarget, THROTTLE_WINDOW_MS, } from './runtime.js';
|
|
3
3
|
import { actDelta, peerPublicActs, peerRoomChanges } from './activity-feed.js';
|
|
4
|
-
import { validate } from './square-core.js';
|
|
4
|
+
import { extractMentions, formatActivityId, validate } from './square-core.js';
|
|
5
5
|
import { deriveDeliveryModel } from './delivery.js';
|
|
6
6
|
import { compileSearchPattern } from './search.js';
|
|
7
7
|
export function resolveKnownName(doc, name) {
|
|
@@ -27,7 +27,7 @@ export function decideJoin(doc, name, now) {
|
|
|
27
27
|
return {
|
|
28
28
|
joinedName,
|
|
29
29
|
addParticipant: knownName === undefined,
|
|
30
|
-
joinAct: { kind: 'join', actor: joinedName, at: now
|
|
30
|
+
joinAct: { kind: 'join', actor: joinedName, at: now },
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
const UNREAD_PREVIEW_LIMIT = 3;
|
|
@@ -41,10 +41,15 @@ export function decideAct(doc, input) {
|
|
|
41
41
|
const reply = input.reply;
|
|
42
42
|
if (reply !== undefined) {
|
|
43
43
|
if (!Number.isSafeInteger(reply) || reply < 0 || reply >= doc.runtime.nextActIndex) {
|
|
44
|
-
|
|
44
|
+
const label = Number.isSafeInteger(reply) && reply >= 0 ? formatActivityId(reply) : String(reply);
|
|
45
|
+
throw new SquareError('invalid_args', `Unknown reply activity: ${label}`);
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
const state = foldedState(doc);
|
|
49
|
+
if (reach !== 'bell'
|
|
50
|
+
&& extractMentions(body).length === 0) {
|
|
51
|
+
throw new SquareError('invalid_args', 'express requires an @mention unless using --bell');
|
|
52
|
+
}
|
|
48
53
|
const current = participantState(state, name);
|
|
49
54
|
const result = validate(state, {
|
|
50
55
|
kind: 'say', actor: name, at: now, body,
|
|
@@ -97,7 +102,8 @@ export function decideAct(doc, input) {
|
|
|
97
102
|
}))
|
|
98
103
|
.sort((a, b) => a.latestActivityAgeMs - b.latestActivityAgeMs || a.name.localeCompare(b.name));
|
|
99
104
|
const latestActivityAgeMs = activitySummaries[0]?.latestActivityAgeMs;
|
|
100
|
-
const
|
|
105
|
+
const hasUnreadBlockingChange = unreadRoomChanges.some((act) => act.kind !== 'join');
|
|
106
|
+
const hasUnread = unreadPublic.length > 0 || hasUnreadBlockingChange;
|
|
101
107
|
const hasFreshUnreadActivity = latestActivityAgeMs !== undefined && latestActivityAgeMs <= UNREAD_BLOCK_GRACE_MS;
|
|
102
108
|
if (!force && hasUnread && !hasFreshUnreadActivity) {
|
|
103
109
|
return { type: 'blocked', activitySummaries, unreadRoomChanges };
|
|
@@ -124,7 +130,7 @@ export function coreHold(_doc, actor, body, now) {
|
|
|
124
130
|
return { kind: 'hold', actor, at: now, body: body.replace(/\r\n/g, '\n').trim() };
|
|
125
131
|
}
|
|
126
132
|
export function coreResume(_doc, actor, now) {
|
|
127
|
-
return { kind: 'resume', actor, at: now
|
|
133
|
+
return { kind: 'resume', actor, at: now };
|
|
128
134
|
}
|
|
129
135
|
function presenceFor(doc, snapshot, name, now) {
|
|
130
136
|
if (snapshot?.done)
|
package/dist/delivery-health.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { loadSquare } from './artifact.js';
|
|
2
2
|
import { deriveDeliveryModel, } from './delivery.js';
|
|
3
|
+
import { formatActivityId } from './square-core.js';
|
|
3
4
|
import { formatDuration } from './time.js';
|
|
4
5
|
import { joinedRecipients, wakeEvidence } from './wake-evidence.js';
|
|
5
6
|
const DISPLAY_ORDER = [
|
|
@@ -44,7 +45,7 @@ export function classifyDeliveryHealth(squarePath, opts) {
|
|
|
44
45
|
}
|
|
45
46
|
function formatItem(item) {
|
|
46
47
|
const evidence = item.attempt?.signature === undefined ? '' : ` · ${item.attempt.signature}`;
|
|
47
|
-
return ` ·
|
|
48
|
+
return ` · ${formatActivityId(item.actIndex)} → @${item.recipient} from @${item.actor} · ${formatDuration(item.ageMs)}${evidence}`;
|
|
48
49
|
}
|
|
49
50
|
export function doctorDeliveryHealth(squarePath, graceMs, now = Date.now(), env = process.env) {
|
|
50
51
|
const items = classifyDeliveryHealth(squarePath, { graceMs, now, env });
|
package/dist/delivery.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { findParticipantName, sameName, } from './model.js';
|
|
2
|
-
import {
|
|
2
|
+
import { audienceOf, formatActivityId, resolveAudience } from './square-core.js';
|
|
3
|
+
import { actId, isCurrentlyJoined, lastJoinIndex, matchesMentionTarget, resolveRosterName, rosterNames } from './runtime.js';
|
|
3
4
|
export function notificationMessageId(squarePath, actIndex) {
|
|
4
|
-
return `square:${squarePath}
|
|
5
|
-
}
|
|
6
|
-
export function isPendingNotification(notification) {
|
|
7
|
-
return notification.route !== 'broadcast';
|
|
5
|
+
return `square:${squarePath}#${formatActivityId(actIndex)}`;
|
|
8
6
|
}
|
|
9
7
|
function canonicalRecipient(doc, name) {
|
|
10
8
|
return resolveRosterName(doc, name) ?? name;
|
|
@@ -32,16 +30,6 @@ export function recordDeliveredRuntime(runtime, recipient, actOrIndex, receipt)
|
|
|
32
30
|
export function markDeliveredDelivery(doc, name, actOrIndex, at = Date.now()) {
|
|
33
31
|
return recordDeliveredDelivery(doc, name, actOrIndex, { at });
|
|
34
32
|
}
|
|
35
|
-
function uniqueKnownMentions(body, roster) {
|
|
36
|
-
const recipients = [];
|
|
37
|
-
for (const mention of extractMentions(body)) {
|
|
38
|
-
const known = findParticipantName(roster, mention);
|
|
39
|
-
if (known !== undefined && !recipients.some((recipient) => sameName(recipient, known))) {
|
|
40
|
-
recipients.push(known);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return recipients;
|
|
44
|
-
}
|
|
45
33
|
/**
|
|
46
34
|
* Derive delivery behavior once from the parsed Square document.
|
|
47
35
|
* All consumers share these targets instead of reinterpreting artifact text or cursor state.
|
|
@@ -53,23 +41,9 @@ export function deriveDeliveryModel(doc) {
|
|
|
53
41
|
if (item.kind !== 'say')
|
|
54
42
|
return [];
|
|
55
43
|
const sayItem = item;
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
.filter((recipient) => !sameName(recipient, actor))
|
|
60
|
-
.map((recipient) => ({ item: sayItem, recipient, route: 'bell' }));
|
|
61
|
-
}
|
|
62
|
-
if (sayItem.reach !== undefined) {
|
|
63
|
-
const recipient = findParticipantName(roster, sayItem.reach.beside);
|
|
64
|
-
return recipient === undefined || sameName(recipient, actor)
|
|
65
|
-
? []
|
|
66
|
-
: [{ item: sayItem, recipient, route: 'beside' }];
|
|
67
|
-
}
|
|
68
|
-
const mentions = uniqueKnownMentions(sayItem.body, roster).filter((recipient) => !sameName(recipient, actor));
|
|
69
|
-
const recipients = mentions.length > 0
|
|
70
|
-
? mentions
|
|
71
|
-
: roster.filter((recipient) => !sameName(recipient, actor));
|
|
72
|
-
const route = mentions.length > 0 ? 'mention' : 'broadcast';
|
|
44
|
+
const audience = audienceOf(sayItem);
|
|
45
|
+
const recipients = resolveAudience(audience, roster).filter((recipient) => !sameName(recipient, sayItem.actor));
|
|
46
|
+
const route = audience.kind === 'bell' ? 'bell' : 'mention';
|
|
73
47
|
return recipients.map((recipient) => ({ item: sayItem, recipient, route }));
|
|
74
48
|
}
|
|
75
49
|
function pendingFor(requestedRecipient) {
|
|
@@ -82,19 +56,16 @@ export function deriveDeliveryModel(doc) {
|
|
|
82
56
|
for (const act of doc.acts) {
|
|
83
57
|
if (act.kind !== 'say')
|
|
84
58
|
continue;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (act.reach === undefined && extractMentions(act.body).length === 0)
|
|
59
|
+
const audience = audienceOf(act);
|
|
60
|
+
if (audience.kind === 'mentions' && audience.names.length === 0)
|
|
88
61
|
continue;
|
|
89
62
|
for (const planned of plan(act)) {
|
|
90
|
-
if (planned.route === 'broadcast')
|
|
91
|
-
continue;
|
|
92
63
|
const joinedAt = joinedAfter.get(planned.recipient);
|
|
93
64
|
if (joinedAt === undefined || act.index <= joinedAt)
|
|
94
65
|
continue;
|
|
95
66
|
if (isDeliveryDelivered(doc, planned.recipient, act.index))
|
|
96
67
|
continue;
|
|
97
|
-
pendingByRecipient.get(planned.recipient)?.push(
|
|
68
|
+
pendingByRecipient.get(planned.recipient)?.push(planned);
|
|
98
69
|
}
|
|
99
70
|
}
|
|
100
71
|
}
|
|
@@ -118,7 +89,7 @@ export function markDeliveredNotifications(doc, recipient, delivered, at = Date.
|
|
|
118
89
|
}
|
|
119
90
|
/** Canonical say-activity filter shared by catch selection and hook ownership. */
|
|
120
91
|
export function matchesCatchFilter(activity, filter) {
|
|
121
|
-
if (activity.
|
|
92
|
+
if (audienceOf(activity).kind === 'bell')
|
|
122
93
|
return true;
|
|
123
94
|
if (filter.participants !== undefined &&
|
|
124
95
|
!filter.participants.some((participant) => sameName(participant, activity.actor))) {
|
|
@@ -128,16 +99,9 @@ export function matchesCatchFilter(activity, filter) {
|
|
|
128
99
|
}
|
|
129
100
|
/** True only when the live catch's own filters would deliver this notification. */
|
|
130
101
|
export function leaseOwnsNotification(lease, notification) {
|
|
131
|
-
const recipient = notification.recipient;
|
|
132
|
-
if (notification.route === 'beside' && recipient === undefined)
|
|
133
|
-
return false;
|
|
134
102
|
return matchesCatchFilter({
|
|
135
103
|
actor: notification.actor,
|
|
136
104
|
body: notification.body,
|
|
137
|
-
|
|
138
|
-
? 'bell'
|
|
139
|
-
: notification.route === 'beside'
|
|
140
|
-
? { beside: recipient }
|
|
141
|
-
: undefined,
|
|
105
|
+
...(notification.route === 'bell' ? { reach: 'bell' } : {}),
|
|
142
106
|
}, lease.filter ?? {});
|
|
143
107
|
}
|
package/dist/help.js
CHANGED
|
@@ -16,9 +16,9 @@ const COMMANDS = [
|
|
|
16
16
|
details: ['Options:', ' --last <N> Show the last N public activities (default 10).', ' --all Show the complete history.'],
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
|
-
names: ['express'], usage: '--as <name> express [-f|--force] [--no-wait] [--
|
|
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: ['Options:', ' -f, --force Express without first catching unread activity.', ' --no-wait If held or throttled, save a draft and return.', ' --
|
|
21
|
+
details: ['Reach:', ' @name Address someone in the square. They hear the body; everyone else sees you walk over.', " --bell Call every participant's attention to this activity without a mention.", '', 'Options:', ' -f, --force Express without first catching unread activity.', ' --no-wait If held or throttled, save a draft and return.', ' --reply <activity-id> Mark this activity as a reply to an earlier activity (for example act/12).'],
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
names: ['catch'], usage: '--as <name> catch (--now | --idle <duration>) [--from <names>] [--mention [name]] [--replace]', usesSquare: true, group: 'participant',
|
|
@@ -39,10 +39,9 @@ const COMMANDS = [
|
|
|
39
39
|
{ names: ['claude-hook', 'codex-hook'], usage: '{command}', summary: 'Present pending attention at one native agent boundary.', hiddenFromIndex: true },
|
|
40
40
|
{
|
|
41
41
|
names: ['history'], usage: '[--as <name>] history [filters] [output]', usesSquare: true, group: 'participant',
|
|
42
|
-
summary: 'Read or search
|
|
43
|
-
details: ['Filters:', ' --from <names> Match activities from participants.', ' --since <time>, --until <time> Match a time window.', ' --grep <regex> | --fixed <s> Search ids, participant names, and bodies.', ' --mention <name> Match mentions.', ' --pending Match attention waiting for --as <name>.', ' --ids <ids> | --at <id> Match stable activity ids.', ' -B, -A, -C <N> Set non-negative context around --at.', ' --after <id> Match activities after an id.', '', 'Results:', ' --limit <N> | --all Bound the newest matches (default 10).', ' --order <asc|desc> Set display order.', '', 'Output:', ' --full --json --format <fields> --count'],
|
|
42
|
+
summary: 'Read or search the archive without changing what you have caught.',
|
|
43
|
+
details: ['Filters:', ' --from <names> Match activities from participants.', ' --since <time>, --until <time> Match a time window.', ' --grep <regex> | --fixed <s> Search ids, participant names, and original bodies.', ' --mention <name> Match mentions.', ' --pending Match attention waiting for --as <name>.', ' --ids <ids> | --at <id> Match stable activity ids and show original bodies.', ' -B, -A, -C <N> Set non-negative context around --at.', ' --after <id> Match activities after an id.', '', 'Results:', ' --limit <N> | --all Bound the newest matches (default 10).', ' --order <asc|desc> Set display order.', '', 'Output:', ' --full --json --format <fields> --count'],
|
|
44
44
|
},
|
|
45
|
-
{ names: ['warmup'], usage: 'warmup', usesSquare: true, group: 'host', summary: 'Print the complete embedded participant warmup.' },
|
|
46
45
|
{ names: ['status'], usage: '[--as <name>] status', usesSquare: true, group: 'participant', summary: 'Show who is present and what happened most recently.' },
|
|
47
46
|
{ names: ['participants'], usage: 'participants', usesSquare: true, group: 'host', summary: 'Show the full participant roster and current states.' },
|
|
48
47
|
{ names: ['hold'], usage: '--as <name> hold [reason | -]', usesSquare: true, group: 'participant', summary: 'Raise a hand and pause participant activity.' },
|
|
@@ -64,9 +63,8 @@ const COMMANDS = [
|
|
|
64
63
|
},
|
|
65
64
|
{ names: ['compact'], usage: 'compact [--keep N]', usesSquare: true, group: 'host', summary: 'Move older activity out of the working artifact while keeping the latest N.' },
|
|
66
65
|
{
|
|
67
|
-
names: ['doctor'], usage: 'doctor
|
|
68
|
-
summary: '
|
|
69
|
-
details: ['Options:', ' --fix Repair recoverable artifact problems.'],
|
|
66
|
+
names: ['doctor'], usage: 'doctor', usesSquare: true, group: 'maintenance',
|
|
67
|
+
summary: 'Validate binary artifact integrity.',
|
|
70
68
|
},
|
|
71
69
|
];
|
|
72
70
|
function definitionFor(command) {
|
|
@@ -78,8 +76,8 @@ function isHelpFlag(value) {
|
|
|
78
76
|
export function renderGlobalHelp() {
|
|
79
77
|
const groups = [
|
|
80
78
|
{ key: 'participant', title: 'In the square:', order: ['join', 'express', 'catch', 'history', 'status', 'hold', 'resume', 'done'] },
|
|
81
|
-
{ key: 'host', title: 'Prepare and manage:', order: ['build', 'list', 'participants', '
|
|
82
|
-
{ key: 'maintenance', title: 'Setup
|
|
79
|
+
{ key: 'host', title: 'Prepare and manage:', order: ['build', 'list', 'participants', 'compact'] },
|
|
80
|
+
{ key: 'maintenance', title: 'Setup:', order: ['install', 'uninstall', 'doctor'] },
|
|
83
81
|
];
|
|
84
82
|
const commandLines = groups.flatMap(({ key, title, order }) => [
|
|
85
83
|
title,
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
export { SquareError } from './model.js';
|
|
2
|
+
export { extractMentions, formatActivityId, parseActivityId } from './square-core.js';
|
|
2
3
|
export { loadSquare } from './artifact.js';
|
|
3
|
-
export {
|
|
4
|
+
export { countSays, joinedNames, doneNames, isCurrentlyJoined, publicActs, readCursor, } from './runtime.js';
|
|
4
5
|
import { loadSquare } from './artifact.js';
|
|
5
6
|
import { dispatchActNotifications, hasDeliveredNotification as hasDeliveredNotificationImpl, sweepPendingNotifications, waitForDeliveredNotification as waitForDeliveredNotificationImpl, } from './notifications.js';
|
|
6
7
|
import { WATCH_STALE_MS, freshWatchLease, getReadState as getDocReadState, } from './runtime.js';
|
|
7
8
|
import { resolveKnownName } from './decisions.js';
|
|
8
9
|
import { execute } from './square-application.js';
|
|
10
|
+
import { parseActivityId } from './square-core.js';
|
|
9
11
|
export { WATCH_STALE_MS };
|
|
10
12
|
function actRefIndex(ref) {
|
|
11
13
|
if (typeof ref === 'number')
|
|
12
14
|
return ref;
|
|
13
|
-
const
|
|
14
|
-
if (
|
|
15
|
+
const index = parseActivityId(ref);
|
|
16
|
+
if (index === undefined)
|
|
15
17
|
throw new Error(`Invalid act ref: ${ref}`);
|
|
16
|
-
return
|
|
18
|
+
return index;
|
|
17
19
|
}
|
|
18
20
|
export function getReadState(squarePath, name) {
|
|
19
21
|
const doc = loadSquare(squarePath);
|