@astrosheep/square 0.3.71 → 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/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/registry.d.ts +2 -0
- package/dist/registry.js +1 -1
- package/dist/routes.js +1 -10
- package/dist/square-actions.js +10 -14
- package/dist/square-application.js +2 -14
- package/guides/postcommit-simplification-performance.md +36 -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/paseo/client/square-message.tsx +19 -24
- package/plugins/paseo/shared/message.ts +1 -0
- package/plugins/pi/square-pi.js +2 -1
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
import type { ClaudeDeliveryResult, ClaudeSendTextOptions, ClaudeTarget } from './types.js';
|
|
2
|
-
export interface ClaudeConnectExistingOptions {
|
|
3
|
-
readonly harness: 'claude';
|
|
4
|
-
readonly sessionId: string;
|
|
5
|
-
readonly endpoint: string;
|
|
6
|
-
readonly token?: string;
|
|
7
|
-
readonly claudeHome?: string;
|
|
8
|
-
readonly timeoutMs?: number;
|
|
9
|
-
readonly signal?: AbortSignal;
|
|
10
|
-
}
|
|
1
|
+
import type { ClaudeConnectOptions, ClaudeDeliveryResult, ClaudeSendTextOptions, ClaudeTarget } from './types.js';
|
|
11
2
|
/** Resolve an explicitly identified existing Claude native inbox; never discovers or starts one. */
|
|
12
|
-
export declare function connectExisting(options:
|
|
3
|
+
export declare function connectExisting(options: ClaudeConnectOptions): Promise<ClaudeTarget>;
|
|
13
4
|
/** Write once to Claude's next native boundary; local write is never native admission. */
|
|
14
5
|
export declare function sendText(target: ClaudeTarget, text: string, options?: ClaudeSendTextOptions): Promise<ClaudeDeliveryResult>;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { DeliveryError } from './errors.js';
|
|
2
|
+
import type { OpenCodeConnectOptions, ClaudeConnectOptions, PiConnectOptions, PaseoConnectOptions } from './types.js';
|
|
3
|
+
export type { OpenCodeConnectOptions, ClaudeConnectOptions, PiConnectOptions, PaseoConnectOptions } from './types.js';
|
|
2
4
|
export interface SendOptions {
|
|
3
5
|
readonly timeoutMs?: number;
|
|
4
6
|
readonly signal?: AbortSignal;
|
|
@@ -16,48 +18,6 @@ export interface Steerable<O = SendOptions> {
|
|
|
16
18
|
export interface Queueable<O = SendOptions> {
|
|
17
19
|
queue(text: string, options?: O): Promise<Receipt>;
|
|
18
20
|
}
|
|
19
|
-
export interface OpenCodeConnectOptions {
|
|
20
|
-
readonly harness: 'opencode';
|
|
21
|
-
readonly sessionId: string;
|
|
22
|
-
readonly endpoint?: {
|
|
23
|
-
readonly url: string;
|
|
24
|
-
readonly auth?: {
|
|
25
|
-
readonly type: 'basic';
|
|
26
|
-
readonly username: string;
|
|
27
|
-
readonly password: string;
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
readonly registrationFile?: string;
|
|
31
|
-
readonly timeoutMs?: number;
|
|
32
|
-
readonly signal?: AbortSignal;
|
|
33
|
-
}
|
|
34
|
-
export interface ClaudeConnectOptions {
|
|
35
|
-
readonly harness: 'claude';
|
|
36
|
-
readonly sessionId: string;
|
|
37
|
-
readonly endpoint: string;
|
|
38
|
-
readonly token?: string;
|
|
39
|
-
readonly claudeHome?: string;
|
|
40
|
-
readonly timeoutMs?: number;
|
|
41
|
-
readonly signal?: AbortSignal;
|
|
42
|
-
}
|
|
43
|
-
export interface PiConnectOptions {
|
|
44
|
-
readonly harness: 'pi';
|
|
45
|
-
readonly sessionId: string;
|
|
46
|
-
readonly endpoint: string;
|
|
47
|
-
readonly timeoutMs?: number;
|
|
48
|
-
readonly signal?: AbortSignal;
|
|
49
|
-
}
|
|
50
|
-
export interface PaseoConnectOptions {
|
|
51
|
-
readonly harness: 'paseo';
|
|
52
|
-
readonly agentId: string;
|
|
53
|
-
readonly endpoint?: string;
|
|
54
|
-
readonly password?: string;
|
|
55
|
-
readonly authHeader?: string;
|
|
56
|
-
readonly localCredential?: string;
|
|
57
|
-
readonly paseoHome?: string;
|
|
58
|
-
readonly timeoutMs?: number;
|
|
59
|
-
readonly signal?: AbortSignal;
|
|
60
|
-
}
|
|
61
21
|
export type OpenCodeAgent = Steerable<KeyedSendOptions> & Queueable<KeyedSendOptions> & {
|
|
62
22
|
readonly harness: 'opencode';
|
|
63
23
|
readonly sessionId: string;
|
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { OpenCodeDeliveryResult, OpenCodeTarget, OpenCodeSendTextOptions } from './types.js';
|
|
3
|
-
export interface OpenCodeConnectExistingOptions {
|
|
4
|
-
readonly harness: 'opencode';
|
|
5
|
-
readonly sessionId: string;
|
|
6
|
-
readonly endpoint?: Endpoint;
|
|
7
|
-
readonly registrationFile?: string;
|
|
8
|
-
readonly timeoutMs?: number;
|
|
9
|
-
readonly signal?: AbortSignal;
|
|
10
|
-
}
|
|
1
|
+
import type { OpenCodeConnectOptions, OpenCodeDeliveryResult, OpenCodeTarget, OpenCodeSendTextOptions } from './types.js';
|
|
11
2
|
/** Resolve an existing service and persisted session. Never starts or stops a service. */
|
|
12
|
-
export declare function connectExisting(options:
|
|
3
|
+
export declare function connectExisting(options: OpenCodeConnectOptions): Promise<OpenCodeTarget>;
|
|
13
4
|
/** Submit once through the shared native sender; HTTP metadata alone refines rejection. */
|
|
14
5
|
export declare function sendText(target: OpenCodeTarget, text: string, options?: OpenCodeSendTextOptions): Promise<OpenCodeDeliveryResult>;
|
|
@@ -1,17 +1,6 @@
|
|
|
1
|
-
import type { PaseoDeliveryResult, PaseoSendTextOptions, PaseoTarget } from './types.js';
|
|
2
|
-
export interface PaseoConnectExistingOptions {
|
|
3
|
-
readonly harness: 'paseo';
|
|
4
|
-
readonly agentId: string;
|
|
5
|
-
readonly endpoint?: string;
|
|
6
|
-
readonly password?: string;
|
|
7
|
-
readonly authHeader?: string;
|
|
8
|
-
readonly localCredential?: string;
|
|
9
|
-
readonly paseoHome?: string;
|
|
10
|
-
readonly timeoutMs?: number;
|
|
11
|
-
readonly signal?: AbortSignal;
|
|
12
|
-
}
|
|
1
|
+
import type { PaseoConnectOptions, PaseoDeliveryResult, PaseoSendTextOptions, PaseoTarget } from './types.js';
|
|
13
2
|
/** Connect to an existing daemon and address one existing agent. Never starts or stops anything. */
|
|
14
|
-
export declare function connectPaseo(options:
|
|
3
|
+
export declare function connectPaseo(options: PaseoConnectOptions): Promise<PaseoTarget>;
|
|
15
4
|
/**
|
|
16
5
|
* Every call owns its daemon connection: one for connect to prove the endpoint
|
|
17
6
|
* and credential, and one per send, closed again as soon as the daemon answers.
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
import type { PiDeliveryResult, PiSendTextOptions, PiTarget } from './types.js';
|
|
2
|
-
export
|
|
3
|
-
readonly harness: 'pi';
|
|
4
|
-
readonly sessionId: string;
|
|
5
|
-
readonly endpoint: string;
|
|
6
|
-
readonly timeoutMs?: number;
|
|
7
|
-
readonly signal?: AbortSignal;
|
|
8
|
-
}
|
|
9
|
-
export declare function connectPi(options: PiConnectExistingOptions): Promise<PiTarget>;
|
|
1
|
+
import type { PiConnectOptions, PiDeliveryResult, PiSendTextOptions, PiTarget } from './types.js';
|
|
2
|
+
export declare function connectPi(options: PiConnectOptions): Promise<PiTarget>;
|
|
10
3
|
export declare function sendPiText(target: PiTarget, text: string, options?: PiSendTextOptions): Promise<PiDeliveryResult>;
|
|
@@ -46,6 +46,48 @@ export interface PaseoSendTextOptions {
|
|
|
46
46
|
readonly signal?: AbortSignal;
|
|
47
47
|
}
|
|
48
48
|
export type SendTextOptions = OpenCodeSendTextOptions | ClaudeSendTextOptions | PiSendTextOptions | PaseoSendTextOptions;
|
|
49
|
+
export interface OpenCodeConnectOptions {
|
|
50
|
+
readonly harness: 'opencode';
|
|
51
|
+
readonly sessionId: string;
|
|
52
|
+
readonly endpoint?: {
|
|
53
|
+
readonly url: string;
|
|
54
|
+
readonly auth?: {
|
|
55
|
+
readonly type: 'basic';
|
|
56
|
+
readonly username: string;
|
|
57
|
+
readonly password: string;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
readonly registrationFile?: string;
|
|
61
|
+
readonly timeoutMs?: number;
|
|
62
|
+
readonly signal?: AbortSignal;
|
|
63
|
+
}
|
|
64
|
+
export interface ClaudeConnectOptions {
|
|
65
|
+
readonly harness: 'claude';
|
|
66
|
+
readonly sessionId: string;
|
|
67
|
+
readonly endpoint: string;
|
|
68
|
+
readonly token?: string;
|
|
69
|
+
readonly claudeHome?: string;
|
|
70
|
+
readonly timeoutMs?: number;
|
|
71
|
+
readonly signal?: AbortSignal;
|
|
72
|
+
}
|
|
73
|
+
export interface PiConnectOptions {
|
|
74
|
+
readonly harness: 'pi';
|
|
75
|
+
readonly sessionId: string;
|
|
76
|
+
readonly endpoint: string;
|
|
77
|
+
readonly timeoutMs?: number;
|
|
78
|
+
readonly signal?: AbortSignal;
|
|
79
|
+
}
|
|
80
|
+
export interface PaseoConnectOptions {
|
|
81
|
+
readonly harness: 'paseo';
|
|
82
|
+
readonly agentId: string;
|
|
83
|
+
readonly endpoint?: string;
|
|
84
|
+
readonly password?: string;
|
|
85
|
+
readonly authHeader?: string;
|
|
86
|
+
readonly localCredential?: string;
|
|
87
|
+
readonly paseoHome?: string;
|
|
88
|
+
readonly timeoutMs?: number;
|
|
89
|
+
readonly signal?: AbortSignal;
|
|
90
|
+
}
|
|
49
91
|
interface OpenCodeAttempt {
|
|
50
92
|
readonly harness: 'opencode';
|
|
51
93
|
readonly sessionId: string;
|
|
@@ -12,8 +12,11 @@ export declare function processIdentity(env: NodeJS.ProcessEnv): {
|
|
|
12
12
|
channel: PresenceChannel;
|
|
13
13
|
};
|
|
14
14
|
export declare function identityRouteDraft(context: HostContext, participant: string): Promise<Omit<WakeRoute, 'updatedAt'> | undefined>;
|
|
15
|
-
|
|
15
|
+
/** One postcommit host observation; presence and route publication fail independently. */
|
|
16
|
+
export declare function publishCommittedIdentity(context: HostContext, participant: string, epoch?: number): Promise<void>;
|
|
16
17
|
export declare function retireIdentityRoute(context: HostContext, participant: string, expectedEpoch?: number): Promise<void>;
|
|
17
18
|
export declare function assertLiveOwner(context: HostContext, participant: string, expectedEpoch?: number): Promise<boolean>;
|
|
18
|
-
/**
|
|
19
|
-
export declare function ensureLocalPresence(context: HostContext, participant: string, epoch?: number): Promise<
|
|
19
|
+
/** Best-effort postcommit presence; a returned epoch also witnesses a readable host observation. */
|
|
20
|
+
export declare function ensureLocalPresence(context: HostContext, participant: string, epoch?: number): Promise<{
|
|
21
|
+
readonly epoch?: number;
|
|
22
|
+
} | undefined>;
|
package/dist/participant-host.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { currentOwner, presenceEpoch, sessionOwnsParticipant } from './registry.js';
|
|
2
2
|
import { firstHarnessSession } from './participant-identity.js';
|
|
3
|
+
import { canonicalPath } from './canonical-path.js';
|
|
3
4
|
import { defaultWakeRouteCapabilities, publishWakeRoute, retireWakeRouteFromArtifact, resolvePrimaryWakeRoute, ROUTE_FRESH_MS } from './routes.js';
|
|
4
5
|
export function processIdentity(env) {
|
|
5
6
|
const found = firstHarnessSession(env);
|
|
@@ -13,19 +14,17 @@ export async function identityRouteDraft(context, participant) {
|
|
|
13
14
|
const capabilities = context.hostLedger === undefined ? { canUse: () => false } : await defaultWakeRouteCapabilities(context.hostLedger);
|
|
14
15
|
return resolvePrimaryWakeRoute({ location: context.location, participant, sessionId: identity.session, provider }, context.env ?? process.env, capabilities);
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const owner = await readParticipantOwner(context.location, participant, hostLedger);
|
|
21
|
-
return owner === undefined ? undefined : presenceEpoch(owner);
|
|
22
|
-
}
|
|
23
|
-
export async function publishIdentityRoute(context, participant, epoch) {
|
|
24
|
-
const route = await identityRouteDraft(context, participant);
|
|
25
|
-
if (route === undefined)
|
|
17
|
+
/** One postcommit host observation; presence and route publication fail independently. */
|
|
18
|
+
export async function publishCommittedIdentity(context, participant, epoch) {
|
|
19
|
+
const observed = await ensureLocalPresence(context, participant, epoch).catch(() => undefined);
|
|
20
|
+
if (observed === undefined)
|
|
26
21
|
return;
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
try {
|
|
23
|
+
const route = await identityRouteDraft(context, participant);
|
|
24
|
+
if (route !== undefined)
|
|
25
|
+
await publishWakeRoute(context.artifact, { ...route, ...observed }, { at: context.clock(), requireCurrentSession: true });
|
|
26
|
+
}
|
|
27
|
+
catch { /* Optional route enrichment cannot undo the committed activity. */ }
|
|
29
28
|
}
|
|
30
29
|
export async function retireIdentityRoute(context, participant, expectedEpoch) {
|
|
31
30
|
if (context.location === undefined || context.location === 'memory')
|
|
@@ -43,35 +42,42 @@ export async function assertLiveOwner(context, participant, expectedEpoch) {
|
|
|
43
42
|
return true;
|
|
44
43
|
return sessionOwnsParticipant(context.location, participant, identity.session, hostLedger, expectedEpoch);
|
|
45
44
|
}
|
|
46
|
-
/**
|
|
45
|
+
/** Best-effort postcommit presence; a returned epoch also witnesses a readable host observation. */
|
|
47
46
|
export async function ensureLocalPresence(context, participant, epoch) {
|
|
48
47
|
if (context.hostLedger === undefined || context.location === undefined || context.location === 'memory')
|
|
49
48
|
return;
|
|
50
49
|
const identity = processIdentity(context.env ?? process.env);
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
// Semantic publish: an unexpired presence row already stands when it carries the same owner epoch.
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
let existing;
|
|
54
52
|
try {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
existing = await context.hostLedger.listPresence({ location: await canonicalPath(context.location), participant, now });
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
process.stderr.write(`! host presence degraded: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const owner = currentOwner(existing);
|
|
60
|
+
const ownerEpoch = epoch ?? (owner === undefined ? undefined : presenceEpoch(owner));
|
|
61
|
+
const observed = ownerEpoch === undefined ? {} : { epoch: ownerEpoch };
|
|
62
|
+
// A fresh exact local binding at the observed (or explicit claim) generation already stands.
|
|
63
|
+
if (existing.some((row) => row.session === identity.session && row.channel === identity.channel
|
|
64
|
+
&& now - (row.updatedAt ?? 0) < ROUTE_FRESH_MS
|
|
65
|
+
&& (ownerEpoch === undefined || presenceEpoch(row) === ownerEpoch)))
|
|
66
|
+
return observed;
|
|
67
|
+
try {
|
|
68
|
+
const result = await context.hostLedger.ensurePresence({
|
|
69
|
+
location: context.location,
|
|
70
|
+
participant,
|
|
71
|
+
session: identity.session,
|
|
72
|
+
channel: identity.channel,
|
|
73
|
+
updatedAt: Date.now(),
|
|
74
|
+
...(ownerEpoch === undefined || ownerEpoch <= 0 ? {} : { epoch: ownerEpoch }),
|
|
75
|
+
});
|
|
76
|
+
if (result.status === 'degraded')
|
|
77
|
+
throw result.error;
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
process.stderr.write(`! host presence degraded: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
60
81
|
}
|
|
61
|
-
|
|
62
|
-
const result = await context.hostLedger.ensurePresence({
|
|
63
|
-
location: context.location,
|
|
64
|
-
participant,
|
|
65
|
-
session: identity.session,
|
|
66
|
-
channel: identity.channel,
|
|
67
|
-
// Presence rows are host-ledger wall-time evidence; the square clock belongs to artifact activities.
|
|
68
|
-
updatedAt: Date.now(),
|
|
69
|
-
...(ownerEpoch === undefined || ownerEpoch <= 0 ? {} : { epoch: ownerEpoch }),
|
|
70
|
-
}).catch((error) => ({
|
|
71
|
-
status: 'degraded',
|
|
72
|
-
record: { location: context.location, participant, session: identity.session, channel: identity.channel },
|
|
73
|
-
error,
|
|
74
|
-
}));
|
|
75
|
-
if (result.status === 'degraded')
|
|
76
|
-
process.stderr.write(`! host presence degraded: ${result.error instanceof Error ? result.error.message : String(result.error)}\n`);
|
|
82
|
+
return observed;
|
|
77
83
|
}
|
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.js
CHANGED
|
@@ -3,15 +3,6 @@ import { canonicalPath, canonicalPathSync } from './canonical-path.js';
|
|
|
3
3
|
export { WAKE_ROUTE_KINDS } from './model.js';
|
|
4
4
|
export const ROUTE_FRESH_MS = 24 * 60 * 60 * 1000;
|
|
5
5
|
export async function defaultWakeRouteCapabilities(hostLedger) {
|
|
6
|
-
let userCapable = hostLedger !== undefined;
|
|
7
|
-
if (hostLedger !== undefined) {
|
|
8
|
-
try {
|
|
9
|
-
await hostLedger.listPresence({ now: Date.now() });
|
|
10
|
-
}
|
|
11
|
-
catch {
|
|
12
|
-
userCapable = false;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
6
|
const available = new Set();
|
|
16
7
|
try {
|
|
17
8
|
const { CodexQueueAdapter } = await import('./codex-queue.js');
|
|
@@ -23,7 +14,7 @@ export async function defaultWakeRouteCapabilities(hostLedger) {
|
|
|
23
14
|
available.add(new PaseoAdapter().kind);
|
|
24
15
|
}
|
|
25
16
|
catch { /* optional */ }
|
|
26
|
-
return { canUse: (kind, address) =>
|
|
17
|
+
return { canUse: (kind, address) => hostLedger !== undefined && available.has(kind) && Object.values(address).every((value) => value.trim() !== '') };
|
|
27
18
|
}
|
|
28
19
|
export function resolvePrimaryWakeRoute(boundary, env, capabilities) {
|
|
29
20
|
const paseoAgentId = env.PASEO_AGENT_ID?.trim();
|
package/dist/square-actions.js
CHANGED
|
@@ -5,14 +5,9 @@ import { deliverPending } from './delivery-operations.js';
|
|
|
5
5
|
import { decideCatch } from './catch-decisions.js';
|
|
6
6
|
import { inSquareCount } from './runtime.js';
|
|
7
7
|
import { claimSessionParticipant, claimSessionTakeover, presenceEpoch, releaseSessionParticipantClaim, readParticipantOwner } from './registry.js';
|
|
8
|
-
import { assertLiveOwner, ensureLocalPresence, identityRouteDraft, processIdentity,
|
|
8
|
+
import { assertLiveOwner, ensureLocalPresence, identityRouteDraft, processIdentity, publishCommittedIdentity, retireIdentityRoute } from './participant-host.js';
|
|
9
9
|
import { applyWakeRoute, dropEndedSessionWakeRoutes, dropParticipantWakeRoutes, dropSessionWakeRoutes, sessionCanEndParticipant } from './routes.js';
|
|
10
10
|
import { parseRequiredActivityId, toPublicActivity } from './views.js';
|
|
11
|
-
/** Host enrichment is best effort after an authoritative artifact commit. */
|
|
12
|
-
async function publishCommittedPresence(square, name, epoch) {
|
|
13
|
-
await ensureLocalPresence(square, name, epoch).catch(() => undefined);
|
|
14
|
-
await publishIdentityRoute(square, name, epoch).catch(() => undefined);
|
|
15
|
-
}
|
|
16
11
|
function throwIfAborted(control) { if (control?.signal?.aborted)
|
|
17
12
|
throw control.signal.reason ?? new Error('Operation aborted'); }
|
|
18
13
|
export async function catchUp(square, name, options = {}, control, leaseId) {
|
|
@@ -27,7 +22,7 @@ export async function catchUp(square, name, options = {}, control, leaseId) {
|
|
|
27
22
|
return decideCatch(facts, name, options, square.clock());
|
|
28
23
|
}, control?.signal)).value;
|
|
29
24
|
if (selected !== null)
|
|
30
|
-
await
|
|
25
|
+
await publishCommittedIdentity(square, selected.participant);
|
|
31
26
|
return selected;
|
|
32
27
|
}
|
|
33
28
|
export async function join(square, name, control) {
|
|
@@ -80,7 +75,7 @@ export async function join(square, name, control) {
|
|
|
80
75
|
await releaseSessionParticipantClaim(hostLedger, ownershipClaim).catch(() => undefined);
|
|
81
76
|
throw error;
|
|
82
77
|
}
|
|
83
|
-
await
|
|
78
|
+
await publishCommittedIdentity(square, committed.name, epoch);
|
|
84
79
|
return {
|
|
85
80
|
name: committed.name, activity: committed.stored === null ? null : toPublicActivity(committed.stored),
|
|
86
81
|
participantCount: committed.participantCount, reconnected: false,
|
|
@@ -118,7 +113,7 @@ export async function takeover(square, name, _oldSessionIds = [], control) {
|
|
|
118
113
|
}, async (claim) => {
|
|
119
114
|
throwIfAborted(control);
|
|
120
115
|
const committed = await commitLifecycle();
|
|
121
|
-
await
|
|
116
|
+
await publishCommittedIdentity(square, committed.name, claim.epoch);
|
|
122
117
|
return committed;
|
|
123
118
|
}, control?.signal);
|
|
124
119
|
if (outcome.status === 'busy')
|
|
@@ -126,7 +121,7 @@ export async function takeover(square, name, _oldSessionIds = [], control) {
|
|
|
126
121
|
return { name: outcome.result.name, activities: outcome.result.stored.map(toPublicActivity), participantCount: outcome.result.participantCount, epoch: outcome.epoch };
|
|
127
122
|
}
|
|
128
123
|
const committed = await commitLifecycle();
|
|
129
|
-
await
|
|
124
|
+
await publishCommittedIdentity(square, committed.name);
|
|
130
125
|
return { name: committed.name, activities: committed.stored.map(toPublicActivity), participantCount: committed.participantCount };
|
|
131
126
|
}
|
|
132
127
|
export async function implicitJoin(square, name, control) {
|
|
@@ -147,11 +142,12 @@ export async function implicitJoin(square, name, control) {
|
|
|
147
142
|
}
|
|
148
143
|
return { name: decision.joinedName, state: decision.state, stored: decision.joinAct === undefined ? null : facts.land(decision.joinAct) };
|
|
149
144
|
}, control?.signal)).value;
|
|
150
|
-
|
|
145
|
+
if (committed.stored !== null)
|
|
146
|
+
await publishCommittedIdentity(square, committed.name);
|
|
147
|
+
else
|
|
148
|
+
await ensureLocalPresence(square, committed.name);
|
|
151
149
|
if (committed.state === 'done')
|
|
152
150
|
await retireIdentityRoute(square, committed.name);
|
|
153
|
-
else if (committed.stored !== null)
|
|
154
|
-
await publishIdentityRoute(square, committed.name);
|
|
155
151
|
return { name: committed.name, state: committed.state, activity: committed.stored === null ? null : toPublicActivity(committed.stored) };
|
|
156
152
|
}
|
|
157
153
|
function rejectUnsentActivity(operation) {
|
|
@@ -210,7 +206,7 @@ export async function express(square, name, body, options = {}, control) {
|
|
|
210
206
|
...(decision.replyAuthorNotHere === undefined ? {} : { replyAuthorNotHere: decision.replyAuthorNotHere }),
|
|
211
207
|
};
|
|
212
208
|
}, control?.signal)).value;
|
|
213
|
-
await
|
|
209
|
+
await publishCommittedIdentity(square, name);
|
|
214
210
|
const delivery = square.wakeTransport !== undefined && square.hostLedger !== undefined && square.location !== undefined && square.location !== 'memory'
|
|
215
211
|
? await deliverPending({ artifact: square.artifact, hostLedger: square.hostLedger, transport: square.wakeTransport, location: square.location, activity: committed.stored.index, now })
|
|
216
212
|
.catch(() => ({ attempted: 0, accepted: 0, failed: 0, unknown: 0, notCapable: 1 }))
|
|
@@ -13,8 +13,7 @@ import { recoveryObservation, currentParticipant, entryPresentation, eventPresen
|
|
|
13
13
|
import { hasAutomaticDeliveryIdentity, hostLedgerForEnv, localParticipantName, localSessionIdentities, presenceEpoch, readParticipantOwner } from './registry.js';
|
|
14
14
|
import { projectSessionBindings } from './square-projections.js';
|
|
15
15
|
import { sessionIdsFromEnvironment } from './participant-identity.js';
|
|
16
|
-
import { createDefaultWakeTransport
|
|
17
|
-
import { sweepPending } from './delivery-operations.js';
|
|
16
|
+
import { createDefaultWakeTransport } from './notifications.js';
|
|
18
17
|
import { acquireWatchLease, ownsWatchLease, pulseWatchLease, releaseWatchLease } from './wakes.js';
|
|
19
18
|
import { createSquare, diagnoseSquareFile } from './artifact.js';
|
|
20
19
|
import { migrateSquareFile } from './square-migration.js';
|
|
@@ -187,7 +186,7 @@ function caughtResult({ selection, stop }) {
|
|
|
187
186
|
consumedThrough: selection.consumedThrough, remaining: selection.remaining, idleExpired: stop === 'stale',
|
|
188
187
|
};
|
|
189
188
|
}
|
|
190
|
-
/** Leases, quiet waiting
|
|
189
|
+
/** Leases, quiet waiting and selection form one lifecycle for both APIs. */
|
|
191
190
|
async function catchOwned(square, requested, options = {}, control) {
|
|
192
191
|
let name = requested, subject = 'caller';
|
|
193
192
|
let leaseId;
|
|
@@ -204,17 +203,6 @@ async function catchOwned(square, requested, options = {}, control) {
|
|
|
204
203
|
await releaseWatchLease(square, name, leaseId).catch(() => undefined);
|
|
205
204
|
leaseId = undefined;
|
|
206
205
|
}
|
|
207
|
-
if (!control?.signal?.aborted && stop !== 'replaced' && stop !== 'already-active'
|
|
208
|
-
&& (waitStarted || selected.activities.length > 0) && square.location !== 'memory'
|
|
209
|
-
&& square.hostLedger !== undefined && square.env?.SQUARE_DISABLE_PASEO_WAKE !== '1') {
|
|
210
|
-
try {
|
|
211
|
-
await sweepPending({
|
|
212
|
-
artifact: square.artifact, hostLedger: square.hostLedger, location: square.location,
|
|
213
|
-
now: square.clock(), graceMs: wakeGraceMs(square.env), limit: 8,
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
catch { /* a completed receipt is not conditional on notification enrichment */ }
|
|
217
|
-
}
|
|
218
206
|
return { selection: selected, stop: terminal, waitStarted, replaced };
|
|
219
207
|
}
|
|
220
208
|
const empty = (stop) => complete(undefined, stop);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Postcommit simplification: 300-process comparison
|
|
2
|
+
|
|
3
|
+
Measured on the same local host with Node v26.10.0. Baseline runtime: release 0.3.71 (`90478f8`). Candidate runtime: `03237aa`. The benchmark entrypoint repair in `2bfb68e` was used for both; it replaces imports of removed modules with current artifact/open-square exports, without changing the scenario or deadlines.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm run build
|
|
7
|
+
node --import ./test/sandbox-env.js scripts/benchmark-idle-wait.mjs --workers 300 --mode session --active --no-hints
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
The command exercises both 1,800 and 18,000 unrelated historical activities. Each of 300 processes observes one bell, recognizes its participant, performs one consuming catch and one empty repeat. These are synthetic Pi bindings without callable native routes. Counts below aggregate all 300 processes; catch latency describes that entire per-process catch phase, not one standalone API call. MB means decimal bytes. Ledger bytes measure instrumented file reads/writes, not physical disk traffic.
|
|
11
|
+
|
|
12
|
+
| Measurement | Baseline, 1,800 | Candidate, 1,800 | Baseline, 18,000 | Candidate, 18,000 |
|
|
13
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
14
|
+
| Ready / complete | 300 / 300 | 300 / 300 | 300 / 300 | 300 / 300 |
|
|
15
|
+
| Caught per process | 1 | 1 | 1 | 1 |
|
|
16
|
+
| Reconsumed total | 0 | 0 | 0 | 0 |
|
|
17
|
+
| Catch ledger MB | 229.248 | 95.520 | 229.248 | 95.520 |
|
|
18
|
+
| Catch ledger operations | 4,200 | 1,500 | 4,200 | 1,500 |
|
|
19
|
+
| Catch p50 seconds | 18.65 | 5.80 | 22.76 | 5.36 |
|
|
20
|
+
| Catch p95 seconds | 22.57 | 7.11 | 26.62 | 6.85 |
|
|
21
|
+
| Catch SQLite busy errors/retries | 174,082 | 45,555 | 221,701 | 45,028 |
|
|
22
|
+
| Catch CPU seconds, aggregate | 65.01 | 23.31 | 73.50 | 21.65 |
|
|
23
|
+
| Discovery p95 seconds | 1.870 | 1.877 | 1.877 | 1.897 |
|
|
24
|
+
| Five-second idle CPU seconds, aggregate | 3.181 | 3.241 | 3.594 | 3.088 |
|
|
25
|
+
|
|
26
|
+
These historical SQL/busy counts combined artifact SQL with host-ledger file-lock SQL; “busy errors/retries” counted thrown busy errors, not independently observed retries. They cannot be compared directly with the origin-separated counters now in the benchmark. Rerun both runtimes with the same new script. Its `ledgerSql` reports host-root SQL (including lock databases), separately from artifact SQL. Returned SQL string/blob bytes are not physical I/O or full JSON decode measurements; SQLite's zero instrumented filesystem bytes is a transport change, not a performance gain.
|
|
27
|
+
|
|
28
|
+
Idle body hydration and ledger bytes remained zero. Each discovery and catch phase hydrated 300 activity bodies. The original 20-second discovery deadline and derived phase guard were unchanged.
|
|
29
|
+
|
|
30
|
+
The candidate removes a discarded catch-completion sweep and combines postcommit identity publication around one fresh presence observation. Admission remains a separate ownership check. Source changes also consolidate duplicate native connection-option declarations; those type changes do not account for the performance result. Production TypeScript across the three changes is net -57 lines without formatting compression. The benchmark repair is separately net -1 tooling line.
|
|
31
|
+
|
|
32
|
+
Ledger bytes fell 58.3%, and ledger operation count fell 64.3%. The observed latency improvement is a single before/after local comparison, not an SLA or an isolated causal attribution: other agent work and verification occurred during the broader run, and host load was not controlled. The deterministic operation reduction supports the intended deletion of duplicate work. Idle discovery was not redesigned, and this does not measure callable-route publication or long-lived leased catch optimization.
|
|
33
|
+
|
|
34
|
+
Each change passed independent review and its declared verification before integration. Root CLI process checks passed all 48 cases; the native adapter package passed 42 cases with its existing Windows-only skip. Full root verification retained its existing skipped case.
|
|
35
|
+
|
|
36
|
+
Local raw reports: `/tmp/square-round2-baseline-300.json` and `/tmp/square-round2-final-300.json`. These temporary reports are diagnostic artifacts, not repository dependencies. No ledger storage migration or evidence GC change is included.
|
package/package.json
CHANGED
|
@@ -16,14 +16,15 @@ export function SquareMessage({ item, theme, layout }: PluginTimelineItemProps<S
|
|
|
16
16
|
const [expanded, setExpanded] = useState(false);
|
|
17
17
|
const toast = useToast();
|
|
18
18
|
const { text, items } = item.data;
|
|
19
|
-
const
|
|
19
|
+
const latest = items?.[items.length - 1];
|
|
20
|
+
const preview = latest?.preview?.replace(/\s+/g, " ").trim();
|
|
20
21
|
const muted = theme.colors.foregroundMuted;
|
|
21
22
|
const foreground = theme.colors.foreground;
|
|
22
|
-
const padding = layout.compact ? 10 : 14;
|
|
23
23
|
const styles = useMemo(() => ({
|
|
24
24
|
marker: { color: theme.colors.accent, fontSize: 10, marginTop: 2 },
|
|
25
25
|
headline: { color: foreground, fontSize: 13, lineHeight: 19, flexShrink: 1 },
|
|
26
26
|
meta: { color: muted, fontSize: 11, lineHeight: 16, flexShrink: 1 },
|
|
27
|
+
preview: { color: foreground, fontSize: 12, lineHeight: 18, flexShrink: 1 },
|
|
27
28
|
bodyPanel: {
|
|
28
29
|
backgroundColor: theme.colors.surface1,
|
|
29
30
|
borderRadius: 12,
|
|
@@ -45,33 +46,27 @@ export function SquareMessage({ item, theme, layout }: PluginTimelineItemProps<S
|
|
|
45
46
|
<View>
|
|
46
47
|
<Pressable
|
|
47
48
|
accessibilityRole="button"
|
|
48
|
-
accessibilityLabel={
|
|
49
|
+
accessibilityLabel={latest !== undefined ? squareHeadline(latest) : "Square context"}
|
|
49
50
|
aria-expanded={expanded}
|
|
50
51
|
onPress={() => setExpanded(value => !value)}
|
|
51
|
-
style={({ pressed }) => ({
|
|
52
|
-
flexDirection: "row",
|
|
53
|
-
alignItems: "flex-start",
|
|
54
|
-
gap: 7,
|
|
55
|
-
paddingVertical: 2,
|
|
56
|
-
opacity: pressed ? 0.6 : 1,
|
|
57
|
-
})}
|
|
52
|
+
style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })}
|
|
58
53
|
>
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
flexDirection: layout.compact ? "column" : "row",
|
|
64
|
-
alignItems: layout.compact ? "flex-start" : "center",
|
|
65
|
-
gap: layout.compact ? 2 : 8,
|
|
66
|
-
}}>
|
|
67
|
-
<Text numberOfLines={layout.compact ? 2 : 1} style={styles.headline}>
|
|
68
|
-
{first !== undefined ? squareHeadline(first) : "Square context"}
|
|
54
|
+
<View style={{ flexDirection: "row", alignItems: "flex-start", gap: 7, paddingVertical: 2 }}>
|
|
55
|
+
<Text style={styles.marker}>■</Text>
|
|
56
|
+
<Text numberOfLines={1} style={styles.headline}>
|
|
57
|
+
{latest?.square ?? "Square context"}
|
|
69
58
|
</Text>
|
|
70
|
-
{
|
|
71
|
-
|
|
72
|
-
|
|
59
|
+
{items && items.length > 1 && <Text style={styles.meta}>· {items.length} landed</Text>}
|
|
60
|
+
<View style={{ flex: 1 }} />
|
|
61
|
+
<View style={{ marginTop: 3 }}><Icon name={expanded ? "ChevronDown" : "ChevronRight"} size={14} color={muted} /></View>
|
|
73
62
|
</View>
|
|
74
|
-
<View style={{
|
|
63
|
+
{(latest?.actor !== undefined || preview) && <View style={{ marginLeft: 17, marginTop: 2 }}>
|
|
64
|
+
<Text numberOfLines={1} style={styles.preview}>
|
|
65
|
+
<Text style={{ color: theme.colors.accent, fontSize: 10 }}>● </Text>
|
|
66
|
+
{latest?.actor !== undefined && <Text style={styles.meta}>{latest.actor} </Text>}
|
|
67
|
+
{preview}
|
|
68
|
+
</Text>
|
|
69
|
+
</View>}
|
|
75
70
|
</Pressable>
|
|
76
71
|
{expanded && <View style={styles.bodyPanel}>
|
|
77
72
|
<Text selectable style={styles.bodyText}>{text}</Text>
|
|
@@ -6,6 +6,7 @@ export const squareCardDetailsSchema = z.object({
|
|
|
6
6
|
actor: z.string().optional(),
|
|
7
7
|
recipient: z.string().optional(),
|
|
8
8
|
route: z.enum(["mention", "reply", "attention", "bell"]).optional(),
|
|
9
|
+
preview: z.string().optional(),
|
|
9
10
|
description: z.string(),
|
|
10
11
|
})).min(1),
|
|
11
12
|
});
|
package/plugins/pi/square-pi.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { presentPendingAtBoundary, renderPendingAtBoundary } from '../../dist/boundary-presentation.js';
|
|
4
4
|
import { automaticSessionEnd, automaticSessionStart } from '../../dist/automatic-session.js';
|
|
5
5
|
import { sessionInbox, observeSessionPending } from '../../dist/inbox.js';
|
|
6
|
-
import { renderAttentionDescription } from '../../dist/attention-presentation.js';
|
|
6
|
+
import { previewAttentionBody, renderAttentionDescription } from '../../dist/attention-presentation.js';
|
|
7
7
|
import { createPiReceiver, sendPiMessage } from '../../dist/packages/agent-delivery/src/pi.js';
|
|
8
8
|
|
|
9
9
|
class PiDeliveryDroppedError extends Error {
|
|
@@ -38,6 +38,7 @@ export function pendingCardDetails(pending) {
|
|
|
38
38
|
actor: note.actor,
|
|
39
39
|
recipient: membership.name,
|
|
40
40
|
route: note.route,
|
|
41
|
+
preview: previewAttentionBody(note.body),
|
|
41
42
|
description: renderAttentionDescription({ actor: note.actor, recipient: membership.name, route: note.route }),
|
|
42
43
|
})));
|
|
43
44
|
return items.length === 0 ? undefined : { items };
|