@astrosheep/square 0.3.70 → 0.3.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/activity-feed.d.ts +4 -5
- package/dist/activity-feed.js +14 -10
- package/dist/artifact.d.ts +26 -33
- package/dist/artifact.js +346 -317
- package/dist/attention-presentation.js +2 -9
- package/dist/automatic-session.js +6 -6
- package/dist/boundary-presentation.js +3 -3
- package/dist/catch-decisions.d.ts +19 -12
- package/dist/catch-decisions.js +32 -68
- package/dist/claude-delivery.js +7 -7
- package/dist/claude-mod.js +18 -7
- package/dist/cli/catch-presentation.d.ts +7 -0
- package/dist/cli/catch-presentation.js +55 -0
- package/dist/cli/context.d.ts +1 -1
- package/dist/cli/context.js +5 -23
- package/dist/cli/express-presentation.d.ts +10 -0
- package/dist/cli/express-presentation.js +196 -0
- package/dist/cli/maintenance-commands.d.ts +4 -0
- package/dist/cli/maintenance-commands.js +29 -6
- package/dist/cli/observation-commands.d.ts +7 -5
- package/dist/cli/observation-commands.js +275 -262
- package/dist/cli/program.d.ts +1 -1
- package/dist/cli/program.js +43 -56
- package/dist/cli/registry.js +2 -1
- package/dist/cli/square-commands.d.ts +3 -9
- package/dist/cli/square-commands.js +134 -255
- package/dist/codex-queue.js +4 -1
- package/dist/decisions.d.ts +26 -74
- package/dist/decisions.js +190 -335
- package/dist/delivery-health.js +13 -22
- package/dist/delivery-operations.d.ts +0 -11
- package/dist/delivery-operations.js +44 -33
- package/dist/delivery.d.ts +33 -32
- package/dist/delivery.js +70 -81
- package/dist/help.js +6 -1
- package/dist/inbox.d.ts +27 -0
- package/dist/inbox.js +55 -18
- package/dist/index.d.ts +2 -4
- package/dist/index.js +1 -1
- package/dist/list.d.ts +7 -1
- package/dist/list.js +49 -122
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +7 -37
- package/dist/model.d.ts +4 -44
- package/dist/model.js +1 -3
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.js +13 -7
- package/dist/open-square.d.ts +16 -2
- package/dist/open-square.js +40 -1
- package/dist/packages/agent-delivery/src/claude.d.ts +2 -11
- package/dist/packages/agent-delivery/src/index.d.ts +2 -42
- package/dist/packages/agent-delivery/src/opencode.d.ts +2 -11
- package/dist/packages/agent-delivery/src/paseo.d.ts +2 -13
- package/dist/packages/agent-delivery/src/pi-client.d.ts +2 -9
- package/dist/packages/agent-delivery/src/types.d.ts +42 -0
- package/dist/participant-host.d.ts +6 -3
- package/dist/participant-host.js +44 -38
- package/dist/perception-projection.d.ts +6 -15
- package/dist/perception-projection.js +6 -26
- package/dist/plugins/opencode/delivery.js +9 -9
- package/dist/plugins/opencode/index.js +3 -3
- package/dist/ports.d.ts +156 -11
- package/dist/presentation-operations.d.ts +1 -1
- package/dist/presentation-operations.js +35 -44
- package/dist/presentation.d.ts +12 -19
- package/dist/presentation.js +33 -54
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +1 -1
- package/dist/routes.d.ts +7 -14
- package/dist/routes.js +34 -72
- package/dist/runtime.d.ts +12 -39
- package/dist/runtime.js +33 -164
- package/dist/square-actions.d.ts +11 -21
- package/dist/square-actions.js +171 -275
- package/dist/square-application.d.ts +31 -55
- package/dist/square-application.js +625 -141
- package/dist/square-core.d.ts +3 -94
- package/dist/square-core.js +8 -353
- package/dist/square-domain.d.ts +24 -0
- package/dist/square-domain.js +77 -0
- package/dist/square-facade.d.ts +360 -9
- package/dist/square-migration.d.ts +24 -0
- package/dist/square-migration.js +211 -0
- package/dist/square-projections.d.ts +2 -42
- package/dist/square-projections.js +1 -37
- package/dist/square-sqlite.d.ts +59 -0
- package/dist/square-sqlite.js +702 -0
- package/dist/stream.d.ts +3 -10
- package/dist/stream.js +81 -52
- package/dist/views.d.ts +27 -95
- package/dist/views.js +217 -87
- package/dist/wake-eligibility.d.ts +15 -5
- package/dist/wake-eligibility.js +32 -16
- package/dist/wakes.d.ts +3 -3
- package/dist/wakes.js +31 -27
- package/guides/application-facade-delivery-evidence.md +200 -0
- package/guides/application-facade-design.md +95 -0
- package/guides/change-waits.md +4 -4
- package/guides/migration.md +77 -0
- package/guides/postcommit-simplification-performance.md +36 -0
- package/guides/primitive-catch-consolidation.md +85 -0
- package/guides/primitive-history-consolidation.md +76 -0
- package/guides/primitive-status-consolidation.md +87 -0
- package/guides/primitive-storage-consolidation.md +128 -0
- package/guides/row-runtime-delivery-evidence.md +160 -0
- package/guides/row-storage-design.md +567 -0
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +11 -11
- package/plugins/opencode/index.ts +3 -3
- package/plugins/paseo/client/square-message.tsx +77 -64
- package/plugins/paseo/shared/message.ts +16 -0
- package/plugins/pi/square-pi.js +5 -1
- package/dist/activity.d.ts +0 -14
- package/dist/activity.js +0 -248
- package/dist/square-file-adapter.d.ts +0 -26
- package/dist/square-file-adapter.js +0 -85
- package/dist/square-state.d.ts +0 -6
- package/dist/square-state.js +0 -138
- package/dist/square-storage.d.ts +0 -14
- package/dist/square-storage.js +0 -188
- package/dist/square-wiring.d.ts +0 -33
- package/dist/square-wiring.js +0 -109
- package/dist/watch.d.ts +0 -8
- package/dist/watch.js +0 -252
|
@@ -1,65 +1,56 @@
|
|
|
1
1
|
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
/** Present one pending activity; artifact seen remains the authoritative receipt. */
|
|
2
|
+
import { pendingHead, sameJoin } from './delivery.js';
|
|
3
|
+
/** Presentation evidence is not a receipt; only the fenced receipt transaction marks seen. */
|
|
5
4
|
export async function presentPending(input) {
|
|
6
5
|
const index = typeof input.activity === 'number' ? input.activity : parseActivityId(input.activity);
|
|
7
6
|
if (index === undefined)
|
|
8
7
|
return { presented: false };
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
catch
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
const delivery = deriveDeliveryModel(before.state);
|
|
17
|
-
const item = before.state.acts.find((activity) => activity.index === index);
|
|
18
|
-
if (input.current?.(before.state) === false || item === undefined || delivery.isSeen(input.participant, index) || !delivery.pendingFor(input.participant).some((notification) => notification.item.index === index))
|
|
8
|
+
const before = await input.artifact.read((facts) => {
|
|
9
|
+
if (input.current?.(facts) === false || !pendingHead(facts, input.participant, index))
|
|
10
|
+
return undefined;
|
|
11
|
+
return { item: facts.activity(index), participant: facts.participant(input.participant) };
|
|
12
|
+
}, input.signal).catch(() => undefined);
|
|
13
|
+
if (before?.value === undefined)
|
|
19
14
|
return { presented: false };
|
|
15
|
+
const { item, participant } = before.value;
|
|
16
|
+
const current = (facts) => sameJoin(facts, participant) && input.current?.(facts) !== false
|
|
17
|
+
&& pendingHead(facts, participant.name, index) !== undefined;
|
|
18
|
+
const coordinate = { location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation' };
|
|
20
19
|
let claimToken;
|
|
20
|
+
const release = async () => {
|
|
21
|
+
if (claimToken !== undefined)
|
|
22
|
+
await input.hostLedger.releaseEvidence({ ...coordinate, claimToken });
|
|
23
|
+
};
|
|
21
24
|
if (input.hostLedger !== undefined && input.session !== undefined) {
|
|
22
|
-
const claim = await input.hostLedger.claimEvidence({
|
|
25
|
+
const claim = await input.hostLedger.claimEvidence({ ...coordinate, leaseMs: input.timeoutMs ?? 5000 });
|
|
23
26
|
if (claim.status !== 'acquired')
|
|
24
27
|
return { presented: false };
|
|
25
28
|
claimToken = claim.claimToken;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
await input.hostLedger.releaseEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', claimToken }).catch(() => undefined);
|
|
32
|
-
return { presented: false };
|
|
33
|
-
}
|
|
34
|
-
if (input.current?.(current.state) === false || deriveDeliveryModel(current.state).isSeen(input.participant, index)) {
|
|
35
|
-
await input.hostLedger.releaseEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', claimToken }).catch(() => undefined);
|
|
29
|
+
const fresh = await input.artifact.read(current, input.signal).catch(() => undefined);
|
|
30
|
+
if (!fresh?.value) {
|
|
31
|
+
await release().catch(() => undefined);
|
|
36
32
|
return { presented: false };
|
|
37
33
|
}
|
|
38
|
-
try {
|
|
39
|
-
await input.sink.present(item);
|
|
40
|
-
}
|
|
41
|
-
catch (error) {
|
|
42
|
-
await input.hostLedger.appendEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', outcome: 'failed', message: error instanceof Error ? error.message : String(error), claimToken });
|
|
43
|
-
throw error;
|
|
44
|
-
}
|
|
45
34
|
}
|
|
46
|
-
|
|
35
|
+
try {
|
|
47
36
|
await input.sink.present(item);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (claimToken !== undefined)
|
|
40
|
+
await input.hostLedger.appendEvidence({ ...coordinate, outcome: 'failed', message: error instanceof Error ? error.message : String(error), claimToken });
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
const { value: committed } = await input.artifact.transact((facts) => {
|
|
44
|
+
if (!current(facts))
|
|
45
|
+
return false;
|
|
46
|
+
return input.markSeen === false || facts.markSeen(participant.name, [index], input.now ?? Date.now()) > 0;
|
|
56
47
|
}, input.signal);
|
|
57
48
|
if (!committed) {
|
|
58
|
-
|
|
59
|
-
await input.hostLedger.releaseEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', claimToken });
|
|
49
|
+
await release();
|
|
60
50
|
return { presented: false };
|
|
61
51
|
}
|
|
62
|
-
if (
|
|
63
|
-
await input.hostLedger.appendEvidence({
|
|
52
|
+
if (claimToken !== undefined)
|
|
53
|
+
await input.hostLedger.appendEvidence({ ...coordinate, outcome: input.markSeen === false ? 'clipped' : 'presented',
|
|
54
|
+
...(input.markSeen === false ? { message: 'presentation clipped' } : {}), claimToken });
|
|
64
55
|
return { presented: true, activity: item };
|
|
65
56
|
}
|
package/dist/presentation.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type StoredAct, type
|
|
1
|
+
import { type StoredAct, type RoomChangeAct } from './model.js';
|
|
2
2
|
export { participantIdentity } from './participant-identity.js';
|
|
3
3
|
import { type Perception } from './square-core.js';
|
|
4
|
-
import type { UnreadActivitySummary,
|
|
4
|
+
import type { Activity, UnreadActivitySummary, ParticipantSummary } from './square-facade.js';
|
|
5
5
|
export type WatchStatus = 'stale' | 'empty-now' | 'quorum' | 'capped';
|
|
6
6
|
interface HeaderOptions {
|
|
7
7
|
participantCount?: number;
|
|
@@ -23,7 +23,7 @@ interface ActivityLimitOptions extends ParticipantOutputOptions {
|
|
|
23
23
|
interface ActivityBlockedOptions extends ParticipantOutputOptions {
|
|
24
24
|
retryCommand: string;
|
|
25
25
|
forceCommand: string;
|
|
26
|
-
activitySummaries: UnreadActivitySummary[];
|
|
26
|
+
activitySummaries: readonly UnreadActivitySummary[];
|
|
27
27
|
draftPath?: string;
|
|
28
28
|
}
|
|
29
29
|
interface ExpressWaitingOptions {
|
|
@@ -44,7 +44,7 @@ interface WatchStatusOptions extends ParticipantOutputOptions {
|
|
|
44
44
|
status: WatchStatus;
|
|
45
45
|
idleMs?: number;
|
|
46
46
|
presence?: {
|
|
47
|
-
participants:
|
|
47
|
+
participants: readonly ParticipantSummary[];
|
|
48
48
|
now: number;
|
|
49
49
|
};
|
|
50
50
|
showCatchHint?: boolean;
|
|
@@ -58,6 +58,7 @@ export declare function formatRefusal(squarePath: string, bodyLines: string[], o
|
|
|
58
58
|
participantCount?: number;
|
|
59
59
|
held?: boolean;
|
|
60
60
|
}): string;
|
|
61
|
+
export declare function refusalText(error: unknown): string;
|
|
61
62
|
export declare function quoteShell(value: string): string;
|
|
62
63
|
export declare function commandPrefix(squarePath: string): string;
|
|
63
64
|
export declare function participantCommandPrefix(squarePath: string, name: string): string;
|
|
@@ -66,44 +67,42 @@ export declare function joinRecoveryCommand(squarePath: string, name: string): s
|
|
|
66
67
|
/** Taking a name is an explicit recovery choice, never an automatic retry. */
|
|
67
68
|
export declare function takeoverRecoveryLines(squarePath: string, name: string): string[];
|
|
68
69
|
export declare function participantsRecoveryCommand(squarePath: string): string;
|
|
69
|
-
export declare function renderPresenceLines(
|
|
70
|
+
export declare function renderPresenceLines(recent: readonly ParticipantSummary[], now: number, limit?: number): string[];
|
|
70
71
|
export declare function expressHintLine(ownActivityCount: number): string | undefined;
|
|
71
72
|
export declare function truncateChars(body: string, maxChars: number): {
|
|
72
73
|
text: string;
|
|
73
74
|
remaining: number;
|
|
74
75
|
};
|
|
75
76
|
export declare function truncateExternalDiagnostic(diagnostic: string): string;
|
|
76
|
-
export declare function renderRoomChangeText(event: RoomChangeAct): string;
|
|
77
|
+
export declare function renderRoomChangeText(event: RoomChangeAct | Activity): string;
|
|
77
78
|
interface RenderedEvent {
|
|
78
79
|
text: string;
|
|
79
80
|
/** The rendered body was cut short by `preview`. */
|
|
80
81
|
bodyClipped: boolean;
|
|
81
82
|
}
|
|
82
|
-
export declare function renderEventCli(event: StoredAct, opts?: {
|
|
83
|
+
export declare function renderEventCli(event: StoredAct | Activity, opts?: {
|
|
83
84
|
now?: number;
|
|
84
85
|
preview?: number;
|
|
85
86
|
actNumber?: number;
|
|
86
87
|
mention?: string;
|
|
87
88
|
}): string;
|
|
88
|
-
export declare function renderAmbientEventFacts(event: StoredAct, viewer: string, opts?: {
|
|
89
|
+
export declare function renderAmbientEventFacts(event: StoredAct | Activity, viewer: string, opts?: {
|
|
89
90
|
now?: number;
|
|
90
91
|
preview?: number;
|
|
91
92
|
presencePreview?: number;
|
|
92
93
|
actNumber?: number;
|
|
93
94
|
mention?: string;
|
|
94
|
-
squareState?: SquareState;
|
|
95
95
|
perception?: Perception;
|
|
96
96
|
}): RenderedEvent;
|
|
97
|
-
export declare function renderAmbientEvent(event: StoredAct, viewer: string, opts?: {
|
|
97
|
+
export declare function renderAmbientEvent(event: StoredAct | Activity, viewer: string, opts?: {
|
|
98
98
|
now?: number;
|
|
99
99
|
preview?: number;
|
|
100
100
|
presencePreview?: number;
|
|
101
101
|
actNumber?: number;
|
|
102
102
|
mention?: string;
|
|
103
|
-
squareState?: SquareState;
|
|
104
103
|
perception?: Perception;
|
|
105
104
|
}): string;
|
|
106
|
-
export declare function renderPendingFeed(
|
|
105
|
+
export declare function renderPendingFeed(publicItems: readonly StoredAct[], sayNumbers: Readonly<Record<number, number>>, perceptions: Readonly<Record<number, Perception>>, viewer: string): string;
|
|
107
106
|
export declare function renderActivityBlocked(opts: ActivityBlockedOptions): string;
|
|
108
107
|
/** An indeterminate or already-committed send only points to inspection, never resend. */
|
|
109
108
|
export declare function renderActivityUncertain(opts: ParticipantOutputOptions & {
|
|
@@ -114,7 +113,7 @@ export declare function renderActivityUncertain(opts: ParticipantOutputOptions &
|
|
|
114
113
|
export declare function renderExpressWaiting(opts: ExpressWaitingOptions): string;
|
|
115
114
|
export declare function renderExpressNoWait(opts: ExpressNoWaitOptions): string;
|
|
116
115
|
export declare function renderPresenceAnchor(names: readonly string[]): string;
|
|
117
|
-
export declare function renderGrepActivitiesView(visible: StoredAct[], totalMatches: number, noTruncate: boolean | undefined, squarePath: string, pattern: string, fixed?: boolean, perception?: (act: StoredAct) => Perception): string;
|
|
116
|
+
export declare function renderGrepActivitiesView(visible: readonly StoredAct[], totalMatches: number, noTruncate: boolean | undefined, squarePath: string, pattern: string, fixed?: boolean, perception?: (act: StoredAct) => Perception): string;
|
|
118
117
|
export declare function renderActivityLimit(opts: ActivityLimitOptions): string;
|
|
119
118
|
export declare function renderWatchAlreadyActive(opts: ParticipantOutputOptions): string;
|
|
120
119
|
export declare function renderWatchForceTakeover(_opts: ParticipantOutputOptions): string;
|
|
@@ -123,9 +122,3 @@ export declare function renderWatchReplaced(_opts: ParticipantOutputOptions): st
|
|
|
123
122
|
export declare function renderWatchStatus(opts: WatchStatusOptions): string;
|
|
124
123
|
export declare function renderDoctorClean(): string;
|
|
125
124
|
export declare function renderDoctorUnfixable(reason: string): string;
|
|
126
|
-
export declare function renderWatchOutput(history: StoredAct[], publicItems: PublicAct[], opts: {
|
|
127
|
-
mention?: string;
|
|
128
|
-
viewer: string;
|
|
129
|
-
squareState?: SquareState;
|
|
130
|
-
perceptions?: ReadonlyMap<number, Perception>;
|
|
131
|
-
}): string;
|
package/dist/presentation.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import { sameName } from './model.js';
|
|
1
|
+
import { sameName, isSquareError } from './model.js';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { participantIdentity } from './participant-identity.js';
|
|
5
5
|
export { participantIdentity } from './participant-identity.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { actId, sayNumberFor, HELD_WAIT_BUDGET_MS } from './runtime.js';
|
|
6
|
+
import { formatActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
|
|
7
|
+
import { HELD_WAIT_BUDGET_MS } from './runtime.js';
|
|
9
8
|
import { formatDuration, formatRelativeTime, formatTimestamp } from './time.js';
|
|
10
|
-
import { compareParticipantActivity } from './decisions.js';
|
|
11
9
|
import { grepSnippet } from './search.js';
|
|
12
10
|
import { style } from './tty-style.js';
|
|
13
11
|
export function displayPath(squarePath, cwd = process.cwd()) {
|
|
@@ -39,6 +37,11 @@ export function formatRefusal(squarePath, bodyLines, opts = {}) {
|
|
|
39
37
|
return body === '' ? '' : `${body}\n`;
|
|
40
38
|
return withPathOutput(squarePath, body, { ...opts, plain: true });
|
|
41
39
|
}
|
|
40
|
+
export function refusalText(error) {
|
|
41
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
42
|
+
const marked = isSquareError(error) && ['never_joined', 'not_standing', 'unmatched_mention'].includes(error.facts?.reason ?? '');
|
|
43
|
+
return marked ? text.split('\n').map((line) => `✕ ${line}`).join('\n') : text;
|
|
44
|
+
}
|
|
42
45
|
export function quoteShell(value) {
|
|
43
46
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
44
47
|
}
|
|
@@ -69,11 +72,7 @@ function formatAge(ms) {
|
|
|
69
72
|
return `${Math.max(0, ms)}ms`;
|
|
70
73
|
return `${Math.floor(ms / 1000)}s`;
|
|
71
74
|
}
|
|
72
|
-
|
|
73
|
-
export function renderPresenceLines(participants, now, limit = 5) {
|
|
74
|
-
const recent = participants
|
|
75
|
-
.filter((p) => p.state === 'done' || p.presence === 'watching' || (p.lastActiveAt !== undefined && now - p.lastActiveAt <= PRESENCE_WINDOW_MS))
|
|
76
|
-
.sort(compareParticipantActivity);
|
|
75
|
+
export function renderPresenceLines(recent, now, limit = 5) {
|
|
77
76
|
const shown = recent.slice(0, limit);
|
|
78
77
|
if (shown.length === 0)
|
|
79
78
|
return [style('dim', ' ○ nobody nearby')];
|
|
@@ -137,9 +136,11 @@ export function renderRoomChangeText(event) {
|
|
|
137
136
|
case 'resume':
|
|
138
137
|
return `${identity} lowered the hand`;
|
|
139
138
|
case 'listen':
|
|
140
|
-
return `${identity} turned an ear toward ${participantIdentity(event.target)}`;
|
|
139
|
+
return `${identity} turned an ear toward ${participantIdentity(event.target ?? 'someone')}`;
|
|
141
140
|
case 'ignore':
|
|
142
|
-
return `${identity} turned away from ${participantIdentity(event.target)}`;
|
|
141
|
+
return `${identity} turned away from ${participantIdentity(event.target ?? 'someone')}`;
|
|
142
|
+
default:
|
|
143
|
+
throw new Error('Expected a lifecycle activity');
|
|
143
144
|
}
|
|
144
145
|
}
|
|
145
146
|
function renderedBody(body, maxChars) {
|
|
@@ -152,6 +153,12 @@ function bodySuffix(body) {
|
|
|
152
153
|
return '';
|
|
153
154
|
return `\n${body.split('\n').map((line) => ` ${line}`).join('\n')}`;
|
|
154
155
|
}
|
|
156
|
+
function eventId(event) {
|
|
157
|
+
return 'index' in event ? formatActivityId(event.index) : event.id;
|
|
158
|
+
}
|
|
159
|
+
function replyId(reply) {
|
|
160
|
+
return typeof reply === 'number' ? formatActivityId(reply) : reply;
|
|
161
|
+
}
|
|
155
162
|
function renderEventCliFacts(event, opts = {}) {
|
|
156
163
|
const now = opts.now;
|
|
157
164
|
const maxBody = opts.preview;
|
|
@@ -168,17 +175,17 @@ function renderEventCliFacts(event, opts = {}) {
|
|
|
168
175
|
case 'say': {
|
|
169
176
|
const body = renderedBody(event.body, maxBody);
|
|
170
177
|
const mention = opts.mention;
|
|
171
|
-
const mentionSuffix = mention !== undefined &&
|
|
178
|
+
const mentionSuffix = mention !== undefined && (event.reach === 'bell' || event.mentions?.some((name) => sameName(name, mention)))
|
|
172
179
|
? ` · calls your name across the square — ${participantIdentity(mention)}`
|
|
173
180
|
: '';
|
|
174
|
-
const replySuffix = event.reply === undefined ? '' : ` · replies to ${
|
|
175
|
-
const meta = ` #${opts.actNumber ?? 1} · ${
|
|
181
|
+
const replySuffix = event.reply === undefined ? '' : ` · replies to ${replyId(event.reply)}`;
|
|
182
|
+
const meta = ` #${opts.actNumber ?? 1} · ${eventId(event)} · ${formatRelativeTime(event.at, now)}${mentionSuffix}${replySuffix}`;
|
|
176
183
|
return { text: `● ${participantIdentity(event.actor)}${style('dim', meta)}${bodySuffix(body.text)}`, bodyClipped: body.clipped };
|
|
177
184
|
}
|
|
178
185
|
case 'done': {
|
|
179
186
|
const body = renderedBody(event.body, maxBody);
|
|
180
187
|
return {
|
|
181
|
-
text: `${style('dim', '○')} ${participantIdentity(event.actor)} stepped out of the square — done${style('dim', ` · ${
|
|
188
|
+
text: `${style('dim', '○')} ${participantIdentity(event.actor)} stepped out of the square — done${style('dim', ` · ${eventId(event)} · ${formatRelativeTime(event.at, now)}`)}${bodySuffix(body.text)}`,
|
|
182
189
|
bodyClipped: body.clipped,
|
|
183
190
|
};
|
|
184
191
|
}
|
|
@@ -190,9 +197,8 @@ export function renderEventCli(event, opts = {}) {
|
|
|
190
197
|
return renderEventCliFacts(event, opts).text;
|
|
191
198
|
}
|
|
192
199
|
function renderPresenceOnlySay(event, opts = {}) {
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
const meta = style('dim', ` #${opts.actNumber ?? 1} · ${actId(event)} · ${formatRelativeTime(event.at, opts.now)}`);
|
|
200
|
+
const targets = event.reach === 'bell' ? [] : event.mentions ?? [];
|
|
201
|
+
const meta = style('dim', ` #${opts.actNumber ?? 1} · ${eventId(event)} · ${formatRelativeTime(event.at, opts.now)}`);
|
|
196
202
|
if (targets.length === 0) {
|
|
197
203
|
return `● ${participantIdentity(event.actor)}${meta}`;
|
|
198
204
|
}
|
|
@@ -207,10 +213,9 @@ function renderPresenceOnlySay(event, opts = {}) {
|
|
|
207
213
|
export function renderAmbientEventFacts(event, viewer, opts = {}) {
|
|
208
214
|
if (event.kind !== 'say')
|
|
209
215
|
return renderEventCliFacts(event, opts);
|
|
210
|
-
if (opts.perception === undefined
|
|
211
|
-
throw new Error('Ambient say rendering requires a settled perception
|
|
212
|
-
|
|
213
|
-
const seen = opts.perception ?? perceiveActivity(opts.squareState, event, viewer);
|
|
216
|
+
if (opts.perception === undefined)
|
|
217
|
+
throw new Error('Ambient say rendering requires a settled perception');
|
|
218
|
+
const seen = opts.perception;
|
|
214
219
|
if (seen === 'presence') {
|
|
215
220
|
const presence = renderPresenceOnlySay(event, opts);
|
|
216
221
|
const body = opts.presencePreview === undefined ? { text: '', clipped: false } : renderedBody(event.body, opts.presencePreview);
|
|
@@ -248,17 +253,8 @@ function renderUnreadSummary(opts) {
|
|
|
248
253
|
...(remainingSummaries === 0 ? [] : [style('dim', ` · ${remainingSummaries} more participants have unread activity`)]),
|
|
249
254
|
];
|
|
250
255
|
}
|
|
251
|
-
export function renderPendingFeed(
|
|
252
|
-
|
|
253
|
-
for (const act of publicItems) {
|
|
254
|
-
const rendered = renderAmbientEvent(act, viewer, {
|
|
255
|
-
actNumber: act.kind === 'say' ? sayNumberFor(history, act) : undefined,
|
|
256
|
-
...(squareState === undefined ? {} : { squareState }),
|
|
257
|
-
});
|
|
258
|
-
if (rendered !== '')
|
|
259
|
-
lines.push(rendered);
|
|
260
|
-
}
|
|
261
|
-
return lines.join('\n\n');
|
|
256
|
+
export function renderPendingFeed(publicItems, sayNumbers, perceptions, viewer) {
|
|
257
|
+
return publicItems.map((act) => renderAmbientEvent(act, viewer, { actNumber: sayNumbers[act.index], perception: perceptions[act.index] })).filter(Boolean).join('\n\n');
|
|
262
258
|
}
|
|
263
259
|
export function renderActivityBlocked(opts) {
|
|
264
260
|
const readNowCommand = `${participantCommandPrefix(opts.squarePath, opts.name)} catch`;
|
|
@@ -344,7 +340,7 @@ export function renderGrepActivitiesView(visible, totalMatches, noTruncate, squa
|
|
|
344
340
|
let truncated = false;
|
|
345
341
|
for (const act of publicVisible) {
|
|
346
342
|
const rawBody = act.body ?? '';
|
|
347
|
-
const header = `${style('dim',
|
|
343
|
+
const header = `${style('dim', formatActivityId(act.index))} · ${act.actor === undefined ? 'unknown' : participantIdentity(act.actor)} · ${style('dim', formatTimestamp(act.at))}`;
|
|
348
344
|
if (perception?.(act) === 'presence') {
|
|
349
345
|
chunks.push(header);
|
|
350
346
|
continue;
|
|
@@ -370,10 +366,10 @@ export function renderGrepActivitiesView(visible, totalMatches, noTruncate, squa
|
|
|
370
366
|
chunks.push(`${header}\n ${text.trim()}${omitted}`);
|
|
371
367
|
}
|
|
372
368
|
if (publicVisible.length === 1) {
|
|
373
|
-
chunks.push(`${commandPrefix(squarePath)} history --at ${
|
|
369
|
+
chunks.push(`${commandPrefix(squarePath)} history --at ${formatActivityId(publicVisible[0].index)} -C 2${truncated ? ' --no-truncate' : ''}`);
|
|
374
370
|
}
|
|
375
371
|
else if (truncated && publicVisible.length > 1) {
|
|
376
|
-
chunks.push(`${commandPrefix(squarePath)} history --at ${
|
|
372
|
+
chunks.push(`${commandPrefix(squarePath)} history --at ${formatActivityId(publicVisible[0].index)} -C 2 --no-truncate`);
|
|
377
373
|
}
|
|
378
374
|
return chunks.join('\n\n');
|
|
379
375
|
}
|
|
@@ -447,20 +443,3 @@ export function renderDoctorClean() {
|
|
|
447
443
|
export function renderDoctorUnfixable(reason) {
|
|
448
444
|
return ['✕ unreadable artifact', ` · ${truncateExternalDiagnostic(reason)}`].join('\n');
|
|
449
445
|
}
|
|
450
|
-
export function renderWatchOutput(history, publicItems, opts) {
|
|
451
|
-
const sections = [];
|
|
452
|
-
if (publicItems.length > 0) {
|
|
453
|
-
const rendered = publicItems
|
|
454
|
-
.map((act) => renderAmbientEvent(act, opts.viewer, {
|
|
455
|
-
actNumber: act.kind === 'say' ? sayNumberFor(history, act) : undefined,
|
|
456
|
-
mention: opts.mention,
|
|
457
|
-
...(opts.perceptions?.has(act.index) ? { perception: opts.perceptions.get(act.index) } : {}),
|
|
458
|
-
...(opts.squareState === undefined ? {} : { squareState: opts.squareState }),
|
|
459
|
-
}))
|
|
460
|
-
.filter(Boolean)
|
|
461
|
-
.join('\n\n');
|
|
462
|
-
if (rendered !== '')
|
|
463
|
-
sections.push(rendered);
|
|
464
|
-
}
|
|
465
|
-
return sections.join('\n\n') + '\n';
|
|
466
|
-
}
|
package/dist/registry.d.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { type FileHostLedgerPort } from './host-ledger-file-adapter.js';
|
|
3
3
|
import type { HostLedgerPort, PresenceChannel, PresenceRecord } from './host-ledger.js';
|
|
4
4
|
export declare function presenceEpoch(record: PresenceRecord | undefined): number;
|
|
5
|
+
/** The current owner: the highest epoch wins, then the most recently updated row. */
|
|
6
|
+
export declare function currentOwner(rows: readonly PresenceRecord[]): PresenceRecord | undefined;
|
|
5
7
|
export declare function localParticipantName(squarePath: string, hostLedger: HostLedgerPort, env?: NodeJS.ProcessEnv): Promise<string | undefined>;
|
|
6
8
|
export declare function squareAssignedParticipantName(env?: NodeJS.ProcessEnv): string | undefined;
|
|
7
9
|
export type OwnershipClaim = {
|
package/dist/registry.js
CHANGED
|
@@ -8,7 +8,7 @@ export function presenceEpoch(record) {
|
|
|
8
8
|
return typeof record?.epoch === 'number' && Number.isSafeInteger(record.epoch) && record.epoch > 0 ? record.epoch : 0;
|
|
9
9
|
}
|
|
10
10
|
/** The current owner: the highest epoch wins, then the most recently updated row. */
|
|
11
|
-
function currentOwner(rows) {
|
|
11
|
+
export function currentOwner(rows) {
|
|
12
12
|
return [...rows].sort((left, right) => presenceEpoch(right) - presenceEpoch(left) || (right.updatedAt ?? 0) - (left.updatedAt ?? 0))[0];
|
|
13
13
|
}
|
|
14
14
|
export async function localParticipantName(squarePath, hostLedger, env = process.env) { const canonical = await canonicalPath(squarePath); const names = new Set((await Promise.all(localSessionIdentities(env).map(async (identity) => (await hostLedger.listPresence({ location: canonical, session: identity.sessionId })).map((row) => row.participant)))).flat()); return names.size === 1 ? [...names][0] : undefined; }
|
package/dist/routes.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type WakeRoute, type WakeRouteKind } from './model.js';
|
|
2
|
+
import type { SquareRead, SquareWrite } from './ports.js';
|
|
2
3
|
export { WAKE_ROUTE_KINDS } from './model.js';
|
|
3
4
|
export type { WakeRoute, WakeRouteKind } from './model.js';
|
|
4
5
|
export declare const ROUTE_FRESH_MS: number;
|
|
@@ -18,25 +19,17 @@ export declare function routeIdentityKey(route: Pick<WakeRoute, 'location' | 'pa
|
|
|
18
19
|
export type RouteEpoch = {
|
|
19
20
|
readonly epoch?: number;
|
|
20
21
|
};
|
|
21
|
-
export declare function
|
|
22
|
-
export declare function
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
*
|
|
26
|
-
* A route survives when the ending transaction did not commit `done` and this session
|
|
27
|
-
* still holds the sole live presence for its participant — an in-flight same-session
|
|
28
|
-
* rejoin. `liveParticipants === undefined` means the ledger could not be read, so
|
|
29
|
-
* nothing is dropped on suspicion. An explicit epoch fence additionally keeps routes
|
|
30
|
-
* published by a later generation.
|
|
31
|
-
*/
|
|
32
|
-
export declare function dropEndedSessionWakeRoutesFromState(state: import('./model.js').SquareState, location: string, sessionId: string, opts: {
|
|
22
|
+
export declare function applyWakeRoute(facts: SquareWrite, route: Omit<WakeRoute, 'updatedAt'> & RouteEpoch, at?: number): void;
|
|
23
|
+
export declare function dropSessionWakeRoutes(facts: SquareWrite, location: string, sessionId: string, expectedEpoch?: number): void;
|
|
24
|
+
/** Keep an in-flight same-session rejoin; unknown host evidence is not a reason to retire it. */
|
|
25
|
+
export declare function dropEndedSessionWakeRoutes(facts: SquareWrite, location: string, sessionId: string, opts: {
|
|
33
26
|
readonly expectedEpoch?: number;
|
|
34
27
|
readonly liveParticipants?: ReadonlySet<string>;
|
|
35
28
|
readonly force: boolean;
|
|
36
29
|
readonly participant?: string;
|
|
37
30
|
}): void;
|
|
38
|
-
export declare function
|
|
39
|
-
export declare function sessionCanEndParticipant(
|
|
31
|
+
export declare function dropParticipantWakeRoutes(facts: SquareWrite, location: string, participant: string): void;
|
|
32
|
+
export declare function sessionCanEndParticipant(facts: SquareRead, location: string, participant: string, sessionId: string, sessionIdNow?: string): boolean;
|
|
40
33
|
export declare function publishWakeRoute(artifact: import('./ports.js').SquareArtifactPort, route: Omit<WakeRoute, 'updatedAt'> & RouteEpoch, opts?: {
|
|
41
34
|
at?: number;
|
|
42
35
|
requireCurrentSession?: boolean;
|
package/dist/routes.js
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
import { nameKey } from './model.js';
|
|
2
|
-
import { isCurrentlyJoined } from './runtime.js';
|
|
3
2
|
import { canonicalPath, canonicalPathSync } from './canonical-path.js';
|
|
4
3
|
export { WAKE_ROUTE_KINDS } from './model.js';
|
|
5
4
|
export const ROUTE_FRESH_MS = 24 * 60 * 60 * 1000;
|
|
6
5
|
export async function defaultWakeRouteCapabilities(hostLedger) {
|
|
7
|
-
let userCapable = hostLedger !== undefined;
|
|
8
|
-
if (hostLedger !== undefined) {
|
|
9
|
-
try {
|
|
10
|
-
await hostLedger.listPresence({ now: Date.now() });
|
|
11
|
-
}
|
|
12
|
-
catch {
|
|
13
|
-
userCapable = false;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
6
|
const available = new Set();
|
|
17
7
|
try {
|
|
18
8
|
const { CodexQueueAdapter } = await import('./codex-queue.js');
|
|
@@ -24,7 +14,7 @@ export async function defaultWakeRouteCapabilities(hostLedger) {
|
|
|
24
14
|
available.add(new PaseoAdapter().kind);
|
|
25
15
|
}
|
|
26
16
|
catch { /* optional */ }
|
|
27
|
-
return { canUse: (kind, address) =>
|
|
17
|
+
return { canUse: (kind, address) => hostLedger !== undefined && available.has(kind) && Object.values(address).every((value) => value.trim() !== '') };
|
|
28
18
|
}
|
|
29
19
|
export function resolvePrimaryWakeRoute(boundary, env, capabilities) {
|
|
30
20
|
const paseoAgentId = env.PASEO_AGENT_ID?.trim();
|
|
@@ -44,89 +34,61 @@ function sameRouteAddress(left, right) {
|
|
|
44
34
|
const rightKeys = Object.keys(right).sort();
|
|
45
35
|
return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => rightKeys[index] === key && left[key] === right[key]);
|
|
46
36
|
}
|
|
47
|
-
export function
|
|
37
|
+
export function applyWakeRoute(facts, route, at = Date.now()) {
|
|
48
38
|
const location = canonicalPathSync(route.location);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}), { ...route, location, updatedAt: at }];
|
|
39
|
+
const identity = routeIdentityKey(route, location);
|
|
40
|
+
facts.replaceRoutes([...facts.routes().filter((item) => routeIdentityKey(item, canonicalPathSync(item.location)) !== identity),
|
|
41
|
+
{ ...route, location, updatedAt: at, extra: {} }]);
|
|
53
42
|
}
|
|
54
|
-
export function
|
|
43
|
+
export function dropSessionWakeRoutes(facts, location, sessionId, expectedEpoch) {
|
|
55
44
|
const canonical = canonicalPathSync(location);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return true;
|
|
59
|
-
if (expectedEpoch === undefined)
|
|
60
|
-
return false;
|
|
61
|
-
return item.epoch !== expectedEpoch;
|
|
62
|
-
});
|
|
45
|
+
facts.replaceRoutes(facts.routes().filter((item) => item.sessionId !== sessionId || canonicalPathSync(item.location) !== canonical
|
|
46
|
+
|| (expectedEpoch !== undefined && item.epoch !== expectedEpoch)));
|
|
63
47
|
}
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
*
|
|
67
|
-
* A route survives when the ending transaction did not commit `done` and this session
|
|
68
|
-
* still holds the sole live presence for its participant — an in-flight same-session
|
|
69
|
-
* rejoin. `liveParticipants === undefined` means the ledger could not be read, so
|
|
70
|
-
* nothing is dropped on suspicion. An explicit epoch fence additionally keeps routes
|
|
71
|
-
* published by a later generation.
|
|
72
|
-
*/
|
|
73
|
-
export function dropEndedSessionWakeRoutesFromState(state, location, sessionId, opts) {
|
|
48
|
+
/** Keep an in-flight same-session rejoin; unknown host evidence is not a reason to retire it. */
|
|
49
|
+
export function dropEndedSessionWakeRoutes(facts, location, sessionId, opts) {
|
|
74
50
|
const canonical = canonicalPathSync(location);
|
|
75
|
-
|
|
51
|
+
facts.replaceRoutes(facts.routes().filter((item) => {
|
|
76
52
|
if (item.sessionId !== sessionId || canonicalPathSync(item.location) !== canonical)
|
|
77
53
|
return true;
|
|
78
54
|
if (opts.participant !== undefined && nameKey(item.participant) !== nameKey(opts.participant))
|
|
79
55
|
return true;
|
|
80
|
-
if (!opts.force)
|
|
81
|
-
|
|
82
|
-
// holds the sole live presence — an in-flight same-session rejoin. A no-longer-joined
|
|
83
|
-
// participant is retired even when a stale presence row survives.
|
|
84
|
-
if (isCurrentlyJoined(state.acts, item.participant)
|
|
85
|
-
&& (opts.liveParticipants === undefined || opts.liveParticipants.has(nameKey(item.participant))))
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
if (opts.expectedEpoch !== undefined && item.epoch !== opts.expectedEpoch)
|
|
56
|
+
if (!opts.force && facts.participant(item.participant)?.joined
|
|
57
|
+
&& (opts.liveParticipants === undefined || opts.liveParticipants.has(nameKey(item.participant))))
|
|
89
58
|
return true;
|
|
90
|
-
return
|
|
91
|
-
});
|
|
59
|
+
return opts.expectedEpoch !== undefined && item.epoch !== opts.expectedEpoch;
|
|
60
|
+
}));
|
|
92
61
|
}
|
|
93
|
-
export function
|
|
62
|
+
export function dropParticipantWakeRoutes(facts, location, participant) {
|
|
94
63
|
const canonical = canonicalPathSync(location);
|
|
95
|
-
|
|
64
|
+
facts.replaceRoutes(facts.routes().filter((item) => !(nameKey(item.participant) === nameKey(participant) && canonicalPathSync(item.location) === canonical)));
|
|
96
65
|
}
|
|
97
|
-
export function sessionCanEndParticipant(
|
|
66
|
+
export function sessionCanEndParticipant(facts, location, participant, sessionId, sessionIdNow) {
|
|
98
67
|
const canonical = canonicalPathSync(location);
|
|
99
|
-
const owned =
|
|
68
|
+
const owned = facts.routes().filter((item) => nameKey(item.participant) === nameKey(participant) && canonicalPathSync(item.location) === canonical);
|
|
100
69
|
if (owned.length === 0)
|
|
101
|
-
return
|
|
102
|
-
return owned.every((item) => item.sessionId === sessionId) && (
|
|
70
|
+
return sessionIdNow === sessionId;
|
|
71
|
+
return owned.every((item) => item.sessionId === sessionId) && (sessionIdNow === undefined || sessionIdNow === sessionId);
|
|
103
72
|
}
|
|
104
73
|
export async function publishWakeRoute(artifact, route, opts = {}) {
|
|
105
74
|
const location = await canonicalPath(route.location);
|
|
106
75
|
const at = opts.at ?? Date.now();
|
|
107
|
-
await artifact.transact((
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
const matching = (state.routes ?? []).filter((item) => routeIdentityKey(item, canonicalPathSync(item.location)) === identity);
|
|
76
|
+
await artifact.transact((facts) => {
|
|
77
|
+
const routes = facts.routes();
|
|
78
|
+
if (opts.requireCurrentSession && (!facts.participant(route.participant)?.joined
|
|
79
|
+
|| routes.some((item) => nameKey(item.participant) === nameKey(route.participant) && canonicalPathSync(item.location) === location && item.sessionId !== route.sessionId)))
|
|
80
|
+
return;
|
|
81
|
+
const identity = routeIdentityKey(route, location);
|
|
82
|
+
const matching = routes.filter((item) => routeIdentityKey(item, canonicalPathSync(item.location)) === identity);
|
|
115
83
|
const existing = matching[0];
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return { state: { ...state, routes: [...(state.routes ?? []).filter((item) => routeIdentityKey(item, canonicalPathSync(item.location)) !== identity), { ...route, location, updatedAt: at }] }, result: undefined };
|
|
84
|
+
if (matching.length === 1 && existing.updatedAt > at - ROUTE_FRESH_MS && existing.kind === route.kind
|
|
85
|
+
&& existing.channel === route.channel && sameRouteAddress(existing.address, route.address) && existing.epoch === route.epoch)
|
|
86
|
+
return;
|
|
87
|
+
applyWakeRoute(facts, { ...route, location }, at);
|
|
121
88
|
});
|
|
122
89
|
}
|
|
123
90
|
export async function retireWakeRouteFromArtifact(artifact, route, opts = {}) {
|
|
124
91
|
const location = await canonicalPath(route.location);
|
|
125
|
-
const target = routeIdentityKey(
|
|
126
|
-
await artifact.transact((
|
|
127
|
-
const itemLocation = canonicalPathSync(item.location);
|
|
128
|
-
if (routeIdentityKey(item, itemLocation) !== target)
|
|
129
|
-
return true;
|
|
130
|
-
return opts.expectedEpoch !== undefined && item.epoch !== opts.expectedEpoch;
|
|
131
|
-
}) }, result: undefined }));
|
|
92
|
+
const target = routeIdentityKey(route, location);
|
|
93
|
+
await artifact.transact((facts) => facts.replaceRoutes(facts.routes().filter((item) => routeIdentityKey(item, canonicalPathSync(item.location)) !== target || (opts.expectedEpoch !== undefined && item.epoch !== opts.expectedEpoch))));
|
|
132
94
|
}
|