@astrosheep/square 0.3.10 → 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 +6 -7
- package/dist/artifact.js +337 -618
- package/dist/boundary-presentation.js +1 -1
- package/dist/cli/context.js +3 -3
- package/dist/cli/harness-command.js +1 -1
- package/dist/cli/maintenance-commands.js +12 -58
- package/dist/cli/observation-commands.js +14 -34
- package/dist/cli/program.js +3 -6
- package/dist/cli/registry.js +1 -2
- package/dist/cli/square-commands.js +39 -20
- package/dist/cmd/notify-once.js +5 -15
- package/dist/compact.js +4 -4
- package/dist/decisions.js +21 -7
- package/dist/delivery-health.js +56 -136
- package/dist/delivery.js +11 -47
- package/dist/file-lock.js +112 -0
- package/dist/harness-codex.js +35 -29
- package/dist/harness-links.js +0 -3
- package/dist/harness-pi.js +57 -0
- package/dist/harness.js +10 -15
- package/dist/help.js +16 -18
- package/dist/index.js +11 -5
- package/dist/list.js +3 -47
- package/dist/model.js +4 -6
- package/dist/notifications.js +217 -32
- package/dist/paseo-connection.js +135 -0
- package/dist/paseo-delivery.js +73 -144
- package/dist/paseo-state.js +1 -1
- package/dist/paseo-timeline.js +32 -42
- package/dist/presentation.js +24 -39
- package/dist/presented.js +10 -72
- package/dist/registry.js +23 -24
- package/dist/routes.js +153 -0
- package/dist/runtime.js +6 -21
- package/dist/square-application.js +56 -127
- package/dist/square-core.js +56 -9
- package/dist/stream.js +1 -1
- package/dist/wake-attempts.js +175 -0
- package/dist/wake-evidence.js +35 -0
- package/dist/wake-port.js +22 -0
- package/dist/wake-sink.js +45 -6
- package/dist/watch.js +1 -2
- package/guides/participant.md +7 -174
- package/package.json +6 -3
- package/skills/brainstorm/SKILL.md +28 -28
- 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 -35
- package/dist/notification-failures.js +0 -54
- package/template.md +0 -4
- package/templates/architect.md +0 -4
- package/templates/brainstorm.md +0 -4
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { DaemonClient } from '@getpaseo/client/internal/daemon-client';
|
|
5
|
+
import WebSocket from 'ws';
|
|
6
|
+
const DEFAULT_HOST = 'localhost:6767';
|
|
7
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 3_000;
|
|
8
|
+
function paseoHome(env) {
|
|
9
|
+
return env.PASEO_HOME?.trim() || path.join(homedir(), '.paseo');
|
|
10
|
+
}
|
|
11
|
+
function expandHome(value) {
|
|
12
|
+
return value === '~' ? homedir() : value.startsWith('~/') ? path.join(homedir(), value.slice(2)) : value;
|
|
13
|
+
}
|
|
14
|
+
function normalizeHost(raw) {
|
|
15
|
+
if (typeof raw !== 'string' || raw.trim() === '')
|
|
16
|
+
return undefined;
|
|
17
|
+
const value = raw.trim();
|
|
18
|
+
if (value.startsWith('unix://') || value.startsWith('pipe://') || value.startsWith('tcp://'))
|
|
19
|
+
return value;
|
|
20
|
+
if (value.startsWith('\\\\.\\pipe\\'))
|
|
21
|
+
return `pipe://${value}`;
|
|
22
|
+
if (value.startsWith('/') || value.startsWith('~/'))
|
|
23
|
+
return `unix://${expandHome(value)}`;
|
|
24
|
+
if (/^\d+$/.test(value))
|
|
25
|
+
return `127.0.0.1:${value}`;
|
|
26
|
+
return value.includes(':') ? value : undefined;
|
|
27
|
+
}
|
|
28
|
+
function configuredHost(env) {
|
|
29
|
+
try {
|
|
30
|
+
const config = JSON.parse(fs.readFileSync(path.join(paseoHome(env), 'config.json'), 'utf8'));
|
|
31
|
+
return normalizeHost(config.daemon?.listen ?? config.listen);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function pidHost(env) {
|
|
38
|
+
try {
|
|
39
|
+
const pid = JSON.parse(fs.readFileSync(path.join(paseoHome(env), 'paseo.pid'), 'utf8'));
|
|
40
|
+
return normalizeHost(pid.listen ?? pid.sockPath);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function isIpc(host) {
|
|
47
|
+
return host !== undefined && (host.startsWith('unix://') || host.startsWith('pipe://'));
|
|
48
|
+
}
|
|
49
|
+
/** Match the host precedence used by the Paseo CLI for local daemon connections. */
|
|
50
|
+
export function paseoDaemonHosts(env = process.env) {
|
|
51
|
+
const explicit = normalizeHost(env.PASEO_HOST);
|
|
52
|
+
if (explicit !== undefined)
|
|
53
|
+
return [explicit];
|
|
54
|
+
const candidates = [];
|
|
55
|
+
const listen = normalizeHost(env.PASEO_LISTEN);
|
|
56
|
+
const pid = pidHost(env);
|
|
57
|
+
const configured = configuredHost(env);
|
|
58
|
+
if (isIpc(listen))
|
|
59
|
+
candidates.push(listen);
|
|
60
|
+
if (isIpc(pid))
|
|
61
|
+
candidates.push(pid);
|
|
62
|
+
if (isIpc(configured))
|
|
63
|
+
candidates.push(configured);
|
|
64
|
+
if (configured !== undefined && !isIpc(configured) && configured !== '127.0.0.1:6767')
|
|
65
|
+
candidates.push(configured);
|
|
66
|
+
candidates.push(DEFAULT_HOST);
|
|
67
|
+
return [...new Set(candidates)];
|
|
68
|
+
}
|
|
69
|
+
function uriPassword(uri) {
|
|
70
|
+
const value = uri.searchParams.get('password');
|
|
71
|
+
return value === null || value === '' ? undefined : value;
|
|
72
|
+
}
|
|
73
|
+
export function resolvePaseoDaemonTarget(host, env = process.env) {
|
|
74
|
+
const passwordFromEnv = env.PASEO_PASSWORD?.trim() || undefined;
|
|
75
|
+
if (host.startsWith('unix://') || host.startsWith('pipe://')) {
|
|
76
|
+
const prefix = host.startsWith('unix://') ? 'unix://' : 'pipe://';
|
|
77
|
+
const socketPath = expandHome(host.slice(prefix.length).trim());
|
|
78
|
+
if (socketPath === '')
|
|
79
|
+
throw new Error('Invalid Paseo IPC target: missing socket path.');
|
|
80
|
+
return {
|
|
81
|
+
type: 'ipc',
|
|
82
|
+
url: host.startsWith('unix://') ? `ws+unix://${socketPath}:/ws` : 'ws://localhost/ws',
|
|
83
|
+
socketPath,
|
|
84
|
+
...(passwordFromEnv === undefined ? {} : { password: passwordFromEnv }),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (host.startsWith('tcp://')) {
|
|
88
|
+
const uri = new URL(host);
|
|
89
|
+
const hostname = uri.hostname.replace(/^\[|\]$/g, '');
|
|
90
|
+
const endpoint = `${hostname.includes(':') ? `[${hostname}]` : hostname}:${uri.port || '6767'}`;
|
|
91
|
+
const secure = uri.searchParams.get('ssl') === 'true';
|
|
92
|
+
const password = uriPassword(uri) ?? passwordFromEnv;
|
|
93
|
+
return {
|
|
94
|
+
type: 'tcp',
|
|
95
|
+
url: `${secure ? 'wss' : 'ws'}://${endpoint}/ws`,
|
|
96
|
+
...(password === undefined ? {} : { password }),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
type: 'tcp',
|
|
101
|
+
url: `ws://${host.replace(/\/$/, '')}/ws`,
|
|
102
|
+
...(passwordFromEnv === undefined ? {} : { password: passwordFromEnv }),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function webSocketFactory(target) {
|
|
106
|
+
return (url, options) => new WebSocket(url, options?.protocols, {
|
|
107
|
+
headers: options?.headers,
|
|
108
|
+
...(target.type === 'ipc' ? { socketPath: target.socketPath } : {}),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
export async function connectPaseoDaemon(env = process.env, connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS) {
|
|
112
|
+
let lastError;
|
|
113
|
+
for (const host of paseoDaemonHosts(env)) {
|
|
114
|
+
const target = resolvePaseoDaemonTarget(host, env);
|
|
115
|
+
const client = new DaemonClient({
|
|
116
|
+
url: target.url,
|
|
117
|
+
clientId: `square-${process.pid}-${Date.now()}`,
|
|
118
|
+
clientType: 'cli',
|
|
119
|
+
appVersion: 'square',
|
|
120
|
+
password: target.password,
|
|
121
|
+
connectTimeoutMs,
|
|
122
|
+
webSocketFactory: webSocketFactory(target),
|
|
123
|
+
reconnect: { enabled: false },
|
|
124
|
+
});
|
|
125
|
+
try {
|
|
126
|
+
await client.connect();
|
|
127
|
+
return client;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
lastError = error;
|
|
131
|
+
await client.close().catch(() => { });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
throw lastError instanceof Error ? lastError : new Error('Unable to connect to the Paseo daemon.');
|
|
135
|
+
}
|
package/dist/paseo-delivery.js
CHANGED
|
@@ -1,160 +1,89 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { isDeliveryDelivered, leaseOwnsNotification, } from './delivery.js';
|
|
5
|
-
import { sessionInbox } from './inbox.js';
|
|
6
|
-
import { hasPresentedForOwner, presentOnce } from './presented.js';
|
|
7
|
-
import { lookupParticipant } from './registry.js';
|
|
8
|
-
import { quoteShell } from './presentation.js';
|
|
9
|
-
import { isCurrentlyJoined, resolveRosterName } from './runtime.js';
|
|
10
|
-
import { discoverPaseoAgents, waitForPaseoWakeBoundary, } from './paseo-state.js';
|
|
11
|
-
import { sendPaseoWake } from './wake-sink.js';
|
|
12
|
-
export class PaseoWakeError extends Error {
|
|
13
|
-
diagnostic;
|
|
14
|
-
constructor(message, diagnostic) {
|
|
15
|
-
super(message);
|
|
16
|
-
this.diagnostic = diagnostic;
|
|
17
|
-
this.name = 'PaseoWakeError';
|
|
18
|
-
}
|
|
19
|
-
}
|
|
1
|
+
import { paseoDaemonHosts, resolvePaseoDaemonTarget } from './paseo-connection.js';
|
|
2
|
+
import { discoverPaseoAgents, waitForPaseoWakeBoundary } from './paseo-state.js';
|
|
3
|
+
import { PaseoWakeSendError, sendPaseoWake } from './wake-sink.js';
|
|
20
4
|
function endpoint() {
|
|
21
|
-
|
|
5
|
+
try {
|
|
6
|
+
return resolvePaseoDaemonTarget(paseoDaemonHosts()[0]).url;
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return 'unresolved';
|
|
10
|
+
}
|
|
22
11
|
}
|
|
23
|
-
function diagnostic(phase,
|
|
12
|
+
function diagnostic(phase, address, code) {
|
|
24
13
|
return {
|
|
25
14
|
phase,
|
|
26
15
|
code,
|
|
27
|
-
command: phase === 'discovery' ? 'paseo ls --json' : 'paseo send <agent-id> --prompt <prompt> --no-wait',
|
|
16
|
+
command: phase === 'discovery' ? 'paseo ls --global --json' : 'paseo send <agent-id> --prompt <prompt> --no-wait --json',
|
|
28
17
|
endpoint: endpoint(),
|
|
29
|
-
paseoAgentIds:
|
|
30
|
-
ownerIds: [...new Set(ownership.map((item) => item.ownerId))],
|
|
18
|
+
paseoAgentIds: [address.agentId].filter(Boolean),
|
|
31
19
|
passwordPresent: Boolean(process.env.PASEO_PASSWORD),
|
|
32
20
|
};
|
|
33
21
|
}
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const out = new Map();
|
|
39
|
-
for (const binding of bindings) {
|
|
40
|
-
if (!binding.paseoAgentId)
|
|
41
|
-
continue;
|
|
42
|
-
const owner = bindings.filter((item) => item.ownerId === binding.ownerId);
|
|
43
|
-
out.set(`${binding.ownerId}\0${binding.paseoAgentId}`, {
|
|
44
|
-
agentId: binding.paseoAgentId,
|
|
45
|
-
ownerId: binding.ownerId,
|
|
46
|
-
sessionId: owner.find(native)?.sessionId ?? binding.sessionId,
|
|
47
|
-
nativeGuarantee: owner.some(native),
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
return [...out.values()];
|
|
51
|
-
}
|
|
52
|
-
function selectActiveAgents(ownership, agents) {
|
|
53
|
-
const ids = new Set(ownership.map((item) => item.agentId));
|
|
54
|
-
return agents.filter((agent) => ids.has(agent.id) && (agent.status === 'idle' || agent.status === 'running'));
|
|
55
|
-
}
|
|
56
|
-
function catchCommand(squarePath, recipient) {
|
|
57
|
-
return `square --as ${quoteShell(recipient)} --square-path ${quoteShell(squarePath)} catch --now`;
|
|
22
|
+
function discoveryRetryable(message) {
|
|
23
|
+
if (/password|auth|unauthori[sz]ed/i.test(message))
|
|
24
|
+
return false;
|
|
25
|
+
return /DAEMON_NOT_RUNNING|ECONNREFUSED|ENOENT|not found.*executable|ETIMEDOUT|timed out|timeout/i.test(message);
|
|
58
26
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
`${notification.route === 'bell' ? 'Bell' : notification.route === 'beside' ? 'Beside' : 'Mention'} from @${notification.item.actor} in \`${display}\``,
|
|
65
|
-
nativeWake ? 'The native adapter will present it at the next boundary.' : `> ${body.replace(/\n/g, '\n> ')}`,
|
|
66
|
-
`\`${catchCommand(squarePath, notification.recipient)}\``,
|
|
67
|
-
'</system-reminder>',
|
|
68
|
-
].join('\n');
|
|
69
|
-
}
|
|
70
|
-
async function waitForCatch(squarePath, recipient, actIndex, ownerId) {
|
|
71
|
-
const deadline = Date.now() + 180_000;
|
|
72
|
-
while (Date.now() < deadline) {
|
|
73
|
-
const doc = loadSquare(squarePath);
|
|
74
|
-
if (isDeliveryDelivered(doc, recipient, actIndex))
|
|
75
|
-
return true;
|
|
76
|
-
const binding = lookupParticipant(squarePath, recipient).find((item) => item.ownerId === ownerId);
|
|
77
|
-
const lease = binding && sessionInbox(binding.sessionId).find((item) => item.name === recipient)?.catchLease;
|
|
78
|
-
if (!lease || lease.expiresAt <= Date.now())
|
|
79
|
-
return false;
|
|
80
|
-
await sleep(Math.min(250, lease.expiresAt - Date.now()));
|
|
27
|
+
export class PaseoAdapter {
|
|
28
|
+
opts;
|
|
29
|
+
kind = 'paseo';
|
|
30
|
+
constructor(opts = {}) {
|
|
31
|
+
this.opts = opts;
|
|
81
32
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
export async function dispatchPaseoNotification(notification, ctx) {
|
|
93
|
-
const initial = loadSquare(ctx.squarePath);
|
|
94
|
-
const recipient = resolveRosterName(initial, notification.recipient);
|
|
95
|
-
if (!recipient || !isCurrentlyJoined(initial.acts, recipient))
|
|
96
|
-
return;
|
|
97
|
-
const ownership = ownershipSnapshot(lookupParticipant(ctx.squarePath, recipient));
|
|
98
|
-
if (ownership.length === 0)
|
|
99
|
-
return;
|
|
100
|
-
const discovery = discoverPaseoAgents();
|
|
101
|
-
if (discovery.error && discovery.agents.length === 0) {
|
|
102
|
-
throw new PaseoWakeError(`Paseo unavailable: ${discovery.error}`, diagnostic('discovery', ownership, 'unavailable'));
|
|
103
|
-
}
|
|
104
|
-
const active = selectActiveAgents(ownership, discovery.agents);
|
|
105
|
-
if (active.length === 0) {
|
|
106
|
-
throw new PaseoWakeError('No registered Paseo agent is idle or running.', diagnostic('selection', ownership, 'not_active'));
|
|
107
|
-
}
|
|
108
|
-
let boundaryTimedOut = false;
|
|
109
|
-
for (const agent of active) {
|
|
110
|
-
const owner = ownership.find((item) => item.agentId === agent.id);
|
|
111
|
-
if (!owner || hasPresentedForOwner(owner.ownerId, ctx.squarePath, recipient, notification.item.index))
|
|
112
|
-
continue;
|
|
113
|
-
if (!(await waitForPaseoWakeBoundary(agent))) {
|
|
114
|
-
boundaryTimedOut = true;
|
|
115
|
-
continue;
|
|
33
|
+
async dispatch(address, payload, beforeSend) {
|
|
34
|
+
const agentId = address.agentId?.trim();
|
|
35
|
+
if (!agentId) {
|
|
36
|
+
return {
|
|
37
|
+
outcome: 'failed',
|
|
38
|
+
signature: 'invalid_address',
|
|
39
|
+
message: 'Paseo route has no agent id.',
|
|
40
|
+
diagnostic: diagnostic('selection', address, 'invalid_address'),
|
|
41
|
+
};
|
|
116
42
|
}
|
|
117
|
-
const
|
|
118
|
-
if (
|
|
119
|
-
return
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
leaseOwnsNotification(activeCatch, {
|
|
126
|
-
actor: notification.item.actor,
|
|
127
|
-
body: notification.item.body,
|
|
128
|
-
route: notification.route,
|
|
129
|
-
recipient,
|
|
130
|
-
}) &&
|
|
131
|
-
(await waitForCatch(ctx.squarePath, recipient, notification.item.index, owner.ownerId))) {
|
|
132
|
-
return;
|
|
43
|
+
const discovery = (this.opts.discover ?? discoverPaseoAgents)();
|
|
44
|
+
if (discovery.error && discovery.agents.length === 0) {
|
|
45
|
+
return {
|
|
46
|
+
outcome: 'failed',
|
|
47
|
+
signature: discoveryRetryable(discovery.error) ? 'discovery_transient' : 'discovery_rejected',
|
|
48
|
+
message: `Paseo unavailable: ${discovery.error}`,
|
|
49
|
+
diagnostic: diagnostic('discovery', address, 'unavailable'),
|
|
50
|
+
};
|
|
133
51
|
}
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
52
|
+
const agent = discovery.agents.find((candidate) => candidate.id === agentId);
|
|
53
|
+
if (agent === undefined || (agent.status !== 'idle' && agent.status !== 'running')) {
|
|
54
|
+
return {
|
|
55
|
+
outcome: 'failed',
|
|
56
|
+
signature: agent === undefined ? 'address_not_found' : 'agent_not_active',
|
|
57
|
+
message: agent === undefined ? 'The registered Paseo agent was not found.' : 'The registered Paseo agent is not idle or running.',
|
|
58
|
+
diagnostic: diagnostic('selection', address, agent === undefined ? 'not_found' : 'not_active'),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (!(await (this.opts.waitForBoundary ?? waitForPaseoWakeBoundary)(agent))) {
|
|
62
|
+
return {
|
|
63
|
+
outcome: 'failed',
|
|
64
|
+
signature: 'boundary_unavailable',
|
|
65
|
+
message: 'Paseo did not reach the current tool boundary before the wake timeout.',
|
|
66
|
+
diagnostic: diagnostic('boundary', address, 'unavailable'),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (!(await beforeSend()))
|
|
70
|
+
return { outcome: 'cancelled' };
|
|
71
|
+
try {
|
|
72
|
+
(this.opts.sendWake ?? sendPaseoWake)({ agentId, prompt: payload });
|
|
73
|
+
return { outcome: 'accepted' };
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
+
const kind = error instanceof PaseoWakeSendError ? error.kind : 'unknown';
|
|
78
|
+
const details = { ...diagnostic('send', address, 'failed'), outcome: kind };
|
|
79
|
+
if (kind === 'unknown')
|
|
80
|
+
return { outcome: 'unknown', signature: 'send_unknown', message, diagnostic: details };
|
|
81
|
+
return {
|
|
82
|
+
outcome: 'failed',
|
|
83
|
+
signature: kind === 'transient' ? 'send_pre_accept_transient' : 'send_pre_accept_rejected',
|
|
84
|
+
message,
|
|
85
|
+
diagnostic: details,
|
|
86
|
+
};
|
|
142
87
|
}
|
|
143
|
-
presentOnce(current.sessionId, (id) => sessionInbox(id)
|
|
144
|
-
.map((item) => ({ ...item, notifications: item.notifications.filter((note) => note.actIndex === notification.item.index) }))
|
|
145
|
-
.filter((item) => item.notifications.length > 0), () => {
|
|
146
|
-
send(request, ownership);
|
|
147
|
-
return true;
|
|
148
|
-
});
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
if (boundaryTimedOut) {
|
|
152
|
-
throw new PaseoWakeError('Paseo did not reach the current tool boundary before the wake timeout.', diagnostic('boundary', ownership, 'timeout'));
|
|
153
88
|
}
|
|
154
89
|
}
|
|
155
|
-
export function paseoWakeSink() {
|
|
156
|
-
return { name: 'paseo', dispatch: dispatchPaseoNotification };
|
|
157
|
-
}
|
|
158
|
-
export function defaultWakeSinks() {
|
|
159
|
-
return process.env.SQUARE_DISABLE_PASEO_WAKE === '1' ? [] : [paseoWakeSink()];
|
|
160
|
-
}
|
package/dist/paseo-state.js
CHANGED
|
@@ -2,7 +2,7 @@ import { execFileSync } from 'node:child_process';
|
|
|
2
2
|
import { waitForPaseoToolBoundary } from './paseo-timeline.js';
|
|
3
3
|
export function discoverPaseoAgents(timeoutMs = 5000) {
|
|
4
4
|
try {
|
|
5
|
-
const raw = execFileSync(process.env.SQUARE_PASEO_BIN || 'paseo', ['ls', '--json'], {
|
|
5
|
+
const raw = execFileSync(process.env.SQUARE_PASEO_BIN || 'paseo', ['ls', '--global', '--json'], {
|
|
6
6
|
encoding: 'utf8',
|
|
7
7
|
timeout: timeoutMs,
|
|
8
8
|
stdio: ['ignore', 'pipe', 'pipe'],
|
package/dist/paseo-timeline.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
2
|
+
import { connectPaseoDaemon } from './paseo-connection.js';
|
|
2
3
|
async function waitSnapshots(agentId, read, opts) {
|
|
3
4
|
const initial = await read(agentId);
|
|
4
5
|
if (initial.agentStatus === 'idle')
|
|
@@ -24,53 +25,42 @@ async function waitSnapshots(agentId, read, opts) {
|
|
|
24
25
|
}
|
|
25
26
|
return false;
|
|
26
27
|
}
|
|
27
|
-
function
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
await new Promise((resolve, reject) => {
|
|
40
|
-
const timer = setTimeout(() => { socket.close(); reject(new Error('Paseo timeline connection timed out.')); }, 3000);
|
|
41
|
-
socket.addEventListener('open', () => { clearTimeout(timer); resolve(); }, { once: true });
|
|
42
|
-
socket.addEventListener('error', () => { clearTimeout(timer); reject(new Error('Paseo timeline unavailable.')); }, { once: true });
|
|
43
|
-
});
|
|
44
|
-
return await new Promise((resolve, reject) => {
|
|
45
|
-
const timer = setTimeout(() => { socket.close(); reject(new Error('Paseo timeline request timed out.')); }, 3000);
|
|
46
|
-
const requestId = `${process.pid}-${Date.now()}`;
|
|
47
|
-
socket.addEventListener('message', (event) => {
|
|
48
|
-
try {
|
|
49
|
-
const outer = JSON.parse(String(event.data));
|
|
50
|
-
const payload = outer?.message?.payload;
|
|
51
|
-
if (outer?.message?.type !== 'fetch_agent_timeline_response' || payload?.requestId !== requestId)
|
|
52
|
-
return;
|
|
53
|
-
clearTimeout(timer);
|
|
54
|
-
socket.close();
|
|
55
|
-
const tools = new Map();
|
|
56
|
-
for (const entry of payload.entries ?? []) {
|
|
57
|
-
const item = entry?.item;
|
|
58
|
-
if (item?.type === 'tool_call' && typeof item.callId === 'string' && ['running', 'completed', 'failed'].includes(item.status))
|
|
59
|
-
tools.set(item.callId, item.status);
|
|
60
|
-
}
|
|
61
|
-
resolve({ agentStatus: typeof payload.agent?.status === 'string' ? payload.agent.status : 'unknown', toolCalls: [...tools].map(([callId, status]) => ({ callId, status })) });
|
|
62
|
-
}
|
|
63
|
-
catch { /* ignore unrelated frames */ }
|
|
64
|
-
});
|
|
65
|
-
socket.send(JSON.stringify({ type: 'hello', clientId: `square-${process.pid}`, clientType: 'cli', protocolVersion: 1 }));
|
|
66
|
-
socket.send(JSON.stringify({ type: 'session', message: { type: 'fetch_agent_timeline_request', agentId, requestId, direction: 'tail', limit: 200, projection: 'projected' } }));
|
|
67
|
-
});
|
|
28
|
+
function snapshotFromPayload(payload) {
|
|
29
|
+
const tools = new Map();
|
|
30
|
+
for (const entry of payload.entries ?? []) {
|
|
31
|
+
const item = entry.item;
|
|
32
|
+
if (item.type === 'tool_call' && ['running', 'completed', 'failed'].includes(item.status)) {
|
|
33
|
+
tools.set(item.callId, item.status);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
agentStatus: payload.agent?.status ?? 'unknown',
|
|
38
|
+
toolCalls: [...tools].map(([callId, status]) => ({ callId, status })),
|
|
39
|
+
};
|
|
68
40
|
}
|
|
69
41
|
export async function waitForPaseoToolBoundary(agentId, opts = {}) {
|
|
42
|
+
if (opts.readSnapshot !== undefined) {
|
|
43
|
+
try {
|
|
44
|
+
return await waitSnapshots(agentId, opts.readSnapshot, opts);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
let client;
|
|
70
51
|
try {
|
|
71
|
-
|
|
52
|
+
client = await connectPaseoDaemon();
|
|
53
|
+
return await waitSnapshots(agentId, async (id) => snapshotFromPayload(await client.fetchAgentTimeline(id, {
|
|
54
|
+
direction: 'tail',
|
|
55
|
+
limit: 200,
|
|
56
|
+
projection: 'projected',
|
|
57
|
+
timeout: 3_000,
|
|
58
|
+
})), opts);
|
|
72
59
|
}
|
|
73
60
|
catch {
|
|
74
61
|
return false;
|
|
75
62
|
}
|
|
63
|
+
finally {
|
|
64
|
+
await client?.close().catch(() => { });
|
|
65
|
+
}
|
|
76
66
|
}
|
package/dist/presentation.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { sameName } from './model.js';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import {
|
|
5
|
-
import { actId,
|
|
4
|
+
import { audienceIncludes, audienceOf, perceive } from './square-core.js';
|
|
5
|
+
import { actId, publicActs, readCursor, rosterNames, sayNumberFor } from './runtime.js';
|
|
6
6
|
import { formatDuration, formatRelativeTime, formatTimestamp } from './time.js';
|
|
7
7
|
import { grepSnippet } from './search.js';
|
|
8
8
|
function headerLine(squarePath, opts = {}) {
|
|
@@ -31,10 +31,10 @@ export function quoteShell(value) {
|
|
|
31
31
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
32
32
|
}
|
|
33
33
|
export function commandPrefix(squarePath) {
|
|
34
|
-
return `square --
|
|
34
|
+
return `square --location ${quoteShell(squarePath)}`;
|
|
35
35
|
}
|
|
36
36
|
export function participantCommandPrefix(squarePath, name) {
|
|
37
|
-
return `square --
|
|
37
|
+
return `square --location ${quoteShell(path.resolve(squarePath))} --as ${quoteShell(name)}`;
|
|
38
38
|
}
|
|
39
39
|
function formatAge(ms) {
|
|
40
40
|
if (ms === undefined)
|
|
@@ -150,7 +150,7 @@ export function renderEventCli(event, opts = {}) {
|
|
|
150
150
|
case 'say': {
|
|
151
151
|
const body = renderedBody(event.body, maxBody);
|
|
152
152
|
const mention = opts.mention;
|
|
153
|
-
const mentionSuffix = mention !== undefined &&
|
|
153
|
+
const mentionSuffix = mention !== undefined && audienceIncludes(audienceOf(event), mention)
|
|
154
154
|
? ` · calls your name across the square — @${mention}`
|
|
155
155
|
: '';
|
|
156
156
|
const replySuffix = event.reply === undefined ? '' : ` · replies to ${actId(event.reply)}`;
|
|
@@ -165,21 +165,15 @@ export function renderEventCli(event, opts = {}) {
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
function renderPresenceOnlySay(event) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
function perceptionFor(history, event, viewer) {
|
|
173
|
-
const cutoff = history.findIndex((item) => actStableIndex(item) === actStableIndex(event));
|
|
174
|
-
const acts = cutoff >= 0 ? history.slice(0, cutoff) : history;
|
|
175
|
-
return perceive(fold(acts), event, viewer);
|
|
168
|
+
const audience = audienceOf(event);
|
|
169
|
+
const targets = audience.kind === 'bell' ? [] : audience.names;
|
|
170
|
+
const dest = targets.length === 0 ? '' : ` to ${targets.map((name) => `@${name}`).join(' and ')}`;
|
|
171
|
+
return `*${event.actor} walks over${dest}*`;
|
|
176
172
|
}
|
|
177
|
-
export function
|
|
173
|
+
export function renderAmbientEvent(event, viewer, opts = {}) {
|
|
178
174
|
if (event.kind !== 'say')
|
|
179
175
|
return renderEventCli(event, opts);
|
|
180
|
-
const seen =
|
|
181
|
-
if (seen === 'none')
|
|
182
|
-
return '';
|
|
176
|
+
const seen = perceive(event, viewer);
|
|
183
177
|
if (seen === 'presence')
|
|
184
178
|
return renderPresenceOnlySay(event);
|
|
185
179
|
return renderEventCli(event, opts);
|
|
@@ -194,7 +188,7 @@ function renderUnreadSummary(opts) {
|
|
|
194
188
|
return [
|
|
195
189
|
...opts.activitySummaries.flatMap((item) => [
|
|
196
190
|
...item.previews.slice(-1).map((preview) => {
|
|
197
|
-
const rendered =
|
|
191
|
+
const rendered = renderAmbientEvent(preview.act, opts.viewer, { actNumber: preview.number });
|
|
198
192
|
if (rendered === '')
|
|
199
193
|
return ` · ${item.name} spoke — ${formatAge(item.latestActivityAgeMs)} ago`;
|
|
200
194
|
if (rendered.startsWith('*'))
|
|
@@ -208,7 +202,7 @@ function renderUnreadSummary(opts) {
|
|
|
208
202
|
export function renderPendingFeed(history, publicItems, roomChanges, viewer = '') {
|
|
209
203
|
const lines = [];
|
|
210
204
|
for (const act of publicItems) {
|
|
211
|
-
const rendered =
|
|
205
|
+
const rendered = renderAmbientEvent(act, viewer, {
|
|
212
206
|
actNumber: act.kind === 'say' ? sayNumberFor(history, act) : undefined,
|
|
213
207
|
});
|
|
214
208
|
if (rendered !== '')
|
|
@@ -262,7 +256,7 @@ export function renderPublicTail(events, lastN, now, viewer = '') {
|
|
|
262
256
|
const selected = lastN == null ? publicItems : publicItems.slice(-lastN);
|
|
263
257
|
const preview = lastN == null ? undefined : BODY_PREVIEW_LENGTH;
|
|
264
258
|
return selected
|
|
265
|
-
.map((event) =>
|
|
259
|
+
.map((event) => renderAmbientEvent(event, viewer, { now, preview, actNumber: event.kind === 'say' ? sayNumberFor(events, event) : undefined }))
|
|
266
260
|
.filter(Boolean)
|
|
267
261
|
.join('\n\n');
|
|
268
262
|
}
|
|
@@ -270,7 +264,7 @@ function lastPresenceAnchor(doc, name) {
|
|
|
270
264
|
const cursor = readCursor(doc, name);
|
|
271
265
|
for (let i = doc.acts.length - 1; i >= 0; i--) {
|
|
272
266
|
const event = doc.acts[i];
|
|
273
|
-
const index =
|
|
267
|
+
const index = event.index;
|
|
274
268
|
if (index > cursor)
|
|
275
269
|
continue;
|
|
276
270
|
if (event.kind === 'say' || event.kind === 'done')
|
|
@@ -281,7 +275,7 @@ function lastPresenceAnchor(doc, name) {
|
|
|
281
275
|
function renderLastPresenceMarker(name) {
|
|
282
276
|
return `· ${name}'s footprints reach here`;
|
|
283
277
|
}
|
|
284
|
-
export function renderActivitiesView(doc, visible, lastN, full, squarePath, viewer = '') {
|
|
278
|
+
export function renderActivitiesView(doc, visible, lastN, full, squarePath, viewer = '', mode = 'ambient') {
|
|
285
279
|
const publicVisible = visible.filter((act) => act.kind === 'say' || act.kind === 'done');
|
|
286
280
|
const shown = lastN == null ? publicVisible : publicVisible.slice(-lastN);
|
|
287
281
|
const previewLen = full ? undefined : BODY_PREVIEW_LENGTH;
|
|
@@ -293,10 +287,13 @@ export function renderActivitiesView(doc, visible, lastN, full, squarePath, view
|
|
|
293
287
|
}
|
|
294
288
|
const chunks = [];
|
|
295
289
|
for (const act of shown) {
|
|
296
|
-
const
|
|
290
|
+
const opts = {
|
|
297
291
|
preview: previewLen,
|
|
298
292
|
actNumber: act.kind === 'say' ? sayNumberFor(doc.acts, act) : undefined,
|
|
299
|
-
}
|
|
293
|
+
};
|
|
294
|
+
const rendered = mode === 'archive'
|
|
295
|
+
? renderEventCli(act, opts)
|
|
296
|
+
: renderAmbientEvent(act, viewer, opts);
|
|
300
297
|
if (rendered !== '')
|
|
301
298
|
chunks.push(rendered);
|
|
302
299
|
for (const participant of markers.get(act.index) ?? []) {
|
|
@@ -306,7 +303,7 @@ export function renderActivitiesView(doc, visible, lastN, full, squarePath, view
|
|
|
306
303
|
if (chunks.length === 0)
|
|
307
304
|
return 'latest\n ○ no public activity in this view';
|
|
308
305
|
if (previewLen !== undefined) {
|
|
309
|
-
const truncated = shown.some((act) => act.kind === 'say' && act.body.length > previewLen);
|
|
306
|
+
const truncated = shown.some((act) => act.kind === 'say' && act.body.length > previewLen && (mode === 'archive' || perceive(act, viewer) === 'full'));
|
|
310
307
|
if (truncated)
|
|
311
308
|
chunks.push(`» ${commandPrefix(squarePath)} history --full`);
|
|
312
309
|
}
|
|
@@ -412,20 +409,8 @@ function renderRoomChanges(changes) {
|
|
|
412
409
|
export function renderDoctorClean() {
|
|
413
410
|
return '✓ no problems found';
|
|
414
411
|
}
|
|
415
|
-
export function renderDoctorProblems(problems) {
|
|
416
|
-
return [`✕ ${problems.length} ${pluralize(problems.length, 'problem')} found`, ...problems.map((problem) => ` · ${problem.kind}: ${problem.message}`)].join('\n');
|
|
417
|
-
}
|
|
418
412
|
export function renderDoctorUnfixable(reason) {
|
|
419
|
-
return ['✕
|
|
420
|
-
}
|
|
421
|
-
export function renderDoctorRepaired(actions, quarantinedCount, sidecarPath) {
|
|
422
|
-
if (actions.length === 0)
|
|
423
|
-
return '✓ no problems found';
|
|
424
|
-
return [
|
|
425
|
-
'✓ repaired',
|
|
426
|
-
...actions.map((action) => ` · ${action.message}`),
|
|
427
|
-
...(quarantinedCount > 0 && sidecarPath !== undefined ? [` · quarantined ${quarantinedCount} act block(s)`, ` · sidecar ${sidecarPath}`] : []),
|
|
428
|
-
].join('\n');
|
|
413
|
+
return ['✕ unreadable artifact', ` · ${reason}`].join('\n');
|
|
429
414
|
}
|
|
430
415
|
export function renderWatchOutput(history, publicItems, roomChanges, opts) {
|
|
431
416
|
const sections = [];
|
|
@@ -448,7 +433,7 @@ export function renderWatchOutput(history, publicItems, roomChanges, opts) {
|
|
|
448
433
|
sections.push(room);
|
|
449
434
|
if (publicItems.length > 0) {
|
|
450
435
|
const rendered = publicItems
|
|
451
|
-
.map((act) =>
|
|
436
|
+
.map((act) => renderAmbientEvent(act, opts.viewer, {
|
|
452
437
|
actNumber: act.kind === 'say' ? sayNumberFor(history, act) : undefined,
|
|
453
438
|
mention: opts.mention,
|
|
454
439
|
}))
|