@camerontaylor/paseo-client 0.8.0-fork.1 → 0.8.0-fork.3

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.
@@ -1,5 +1,28 @@
1
- import type { SessionEventSubscription } from "@camerontaylor/paseo-protocol/messages";
1
+ export { OwnedSubscriptions, type OwnedSubscription, type SubscriptionObserver } from "./owned.js";
2
+ import { OwnedSubscriptions, type OwnedSubscription, type OwnedSubscriptionsHost } from "./owned.js";
3
+ import { type ObservationRequest } from "./legacy.js";
4
+ import { type SessionInboundMessage, type SessionOutboundMessage, type ServerInfoStatusPayload } from "@camerontaylor/paseo-protocol/messages";
5
+ import { type ClientCapability } from "@camerontaylor/paseo-protocol/client-capabilities";
6
+ export declare class ConnectionSubscriptions extends OwnedSubscriptions {
7
+ private readonly connection;
8
+ private legacy;
9
+ constructor(connection: OwnedSubscriptionsHost & {
10
+ send(message: ObservationRequest): Promise<void>;
11
+ });
12
+ restore(info?: ServerInfoStatusPayload, capabilities?: Partial<Record<ClientCapability, unknown>>): void;
13
+ disconnected(): void;
14
+ observeRequest<T>(query: ObservationRequest, request: (query: ObservationRequest, accept: (snapshot: T) => void) => Promise<T>, signal?: AbortSignal, failed?: () => void): OwnedSubscription<T>;
15
+ owns(message: SessionOutboundMessage): boolean;
16
+ receive(message: SessionOutboundMessage): void;
17
+ normalize(message: SessionOutboundMessage): SessionOutboundMessage;
18
+ prepareRequest(message: SessionInboundMessage): {
19
+ message: SessionInboundMessage;
20
+ receive(message: SessionOutboundMessage): SessionOutboundMessage;
21
+ finish(): Promise<void>;
22
+ };
23
+ }
2
24
  export declare const DEFAULT_CLIENT_CAPABILITIES: {
25
+ owned_subscriptions: true;
3
26
  all_providers: true;
4
27
  selective_agent_timeline: true;
5
28
  reasoning_merge_enum: true;
@@ -16,27 +39,10 @@ export declare const DEFAULT_CLIENT_CAPABILITIES: {
16
39
  workspace_setup_blocked: true;
17
40
  explicit_event_subscriptions: true;
18
41
  };
19
- /** Calling releases demand; ready acknowledges the initial daemon membership. */
42
+ /** Calling releases demand; ready waits for membership, or local attachment on broadcast hosts. */
20
43
  export type TimelineSubscription = (() => void) & {
21
44
  readonly ready: Promise<void>;
45
+ readonly subscriptionId: string | null;
46
+ release(): Promise<void>;
22
47
  };
23
- /** Owns connection demand, independently of individual facades and React lifetimes. */
24
- export declare class ConnectionSubscriptions {
25
- private readonly send;
26
- private viewed;
27
- private timelines;
28
- private events;
29
- constructor(send: {
30
- timelines(agentIds: string[]): Promise<void> | null;
31
- events(events: SessionEventSubscription[]): Promise<void>;
32
- failed(error: unknown): void;
33
- });
34
- private agentIds;
35
- setViewed(agentIds: string[]): Promise<void>;
36
- observeTimeline(agentId: string): TimelineSubscription;
37
- private syncTimelines;
38
- setEvents(events: SessionEventSubscription[]): void;
39
- restore(): void;
40
- close(): void;
41
- }
42
48
  //# sourceMappingURL=index.d.ts.map
@@ -1,7 +1,96 @@
1
+ export { OwnedSubscriptions } from "./owned.js";
2
+ import { OwnedSubscriptions, } from "./owned.js";
3
+ import { LegacySubscriptions } from "./legacy.js";
1
4
  import { CLIENT_CAPS } from "@camerontaylor/paseo-protocol/client-capabilities";
5
+ export class ConnectionSubscriptions extends OwnedSubscriptions {
6
+ constructor(connection) {
7
+ super({
8
+ ...connection,
9
+ release: async (id) => {
10
+ if (!this.legacy)
11
+ return connection.release(id);
12
+ const message = this.legacy.release(id);
13
+ if (message)
14
+ await connection.send(message);
15
+ },
16
+ });
17
+ this.connection = connection;
18
+ this.legacy = null;
19
+ }
20
+ restore(info, capabilities) {
21
+ if (info) {
22
+ const clientCapabilities = capabilities ?? DEFAULT_CLIENT_CAPABILITIES;
23
+ const owned = info.features?.ownedSubscriptions === true &&
24
+ clientCapabilities[CLIENT_CAPS.ownedSubscriptions] === true;
25
+ this.legacy = null;
26
+ if (!owned) {
27
+ this.legacy = new LegacySubscriptions(info, this.connection.send, capabilities?.[CLIENT_CAPS.browserHost]);
28
+ }
29
+ }
30
+ super.restore();
31
+ }
32
+ disconnected() {
33
+ super.disconnected();
34
+ this.legacy = null;
35
+ }
36
+ observeRequest(query, request, signal, failed) {
37
+ return super.observe(async (accept) => {
38
+ const legacy = this.legacy;
39
+ if (!legacy)
40
+ return request(query, accept);
41
+ const interest = legacy.start(query);
42
+ const snapshot = (payload) => ({
43
+ ...payload,
44
+ subscriptionId: interest.id,
45
+ });
46
+ try {
47
+ const message = legacy.request(interest);
48
+ if (!message) {
49
+ // Broadcast-era hosts have no acknowledgement. Readiness means the
50
+ // local listener is attached; no server membership is being claimed.
51
+ const value = snapshot({
52
+ requestId: interest.id,
53
+ ...(query.type === "agent.timeline.set_subscription.request"
54
+ ? { agentIds: query.agentIds }
55
+ : {}),
56
+ });
57
+ accept(value);
58
+ return value;
59
+ }
60
+ return snapshot(await request(message, (payload) => accept(snapshot(payload))));
61
+ }
62
+ catch (error) {
63
+ const release = legacy.release(interest.id);
64
+ if (release && this.legacy === legacy)
65
+ await this.connection.send(release).catch(this.connection.failed);
66
+ throw error;
67
+ }
68
+ }, signal, failed);
69
+ }
70
+ owns(message) {
71
+ return this.legacy ? this.legacy.owns(message) : super.owns(message);
72
+ }
73
+ receive(message) {
74
+ if (this.legacy)
75
+ this.legacy.receive(message, (update) => super.receive(update));
76
+ else
77
+ super.receive(message);
78
+ }
79
+ normalize(message) {
80
+ return this.legacy?.normalize(message) ?? message;
81
+ }
82
+ prepareRequest(message) {
83
+ return (this.legacy?.prepareRequest(message) ?? {
84
+ message,
85
+ receive: (value) => value,
86
+ finish: async () => { },
87
+ });
88
+ }
89
+ }
2
90
  // Protocol support belongs to the installed client. Only browser hosting needs
3
91
  // a resource supplied by the caller. Keep this exhaustive as the protocol evolves.
4
92
  export const DEFAULT_CLIENT_CAPABILITIES = {
93
+ [CLIENT_CAPS.ownedSubscriptions]: true,
5
94
  [CLIENT_CAPS.allProviders]: true,
6
95
  [CLIENT_CAPS.selectiveAgentTimeline]: true,
7
96
  [CLIENT_CAPS.reasoningMergeEnum]: true,
@@ -23,89 +112,4 @@ export const DEFAULT_CLIENT_CAPABILITIES = {
23
112
  [CLIENT_CAPS.workspaceSetupBlocked]: true,
24
113
  [CLIENT_CAPS.explicitEventSubscriptions]: true,
25
114
  };
26
- class TimelineInterest {
27
- constructor() {
28
- this.ready = new Promise((resolve, reject) => {
29
- this.resolve = resolve;
30
- this.reject = reject;
31
- });
32
- // Readiness is optional for fire-and-forget listeners.
33
- void this.ready.catch(() => { });
34
- }
35
- }
36
- /** Owns connection demand, independently of individual facades and React lifetimes. */
37
- export class ConnectionSubscriptions {
38
- constructor(send) {
39
- this.send = send;
40
- this.viewed = new Set();
41
- this.timelines = new Map();
42
- this.events = [];
43
- }
44
- agentIds() {
45
- return [...new Set([...this.viewed, ...this.timelines.keys()])].sort();
46
- }
47
- setViewed(agentIds) {
48
- this.viewed = new Set(agentIds);
49
- return this.syncTimelines();
50
- }
51
- observeTimeline(agentId) {
52
- const interests = this.timelines.get(agentId) ?? new Set();
53
- const interest = new TimelineInterest();
54
- interests.add(interest);
55
- this.timelines.set(agentId, interests);
56
- // Each caller gets acknowledgement for its own registration, even when an
57
- // existing listener first subscribed on an earlier connection.
58
- void this.syncTimelines().catch(this.send.failed);
59
- let active = true;
60
- return Object.assign(() => {
61
- if (!active)
62
- return;
63
- active = false;
64
- interest.reject(new Error("Timeline subscription released before it was ready"));
65
- interests.delete(interest);
66
- if (interests.size === 0) {
67
- this.timelines.delete(agentId);
68
- void this.syncTimelines().catch(this.send.failed);
69
- }
70
- }, { ready: interest.ready });
71
- }
72
- async syncTimelines() {
73
- const interests = [];
74
- for (const listeners of this.timelines.values()) {
75
- for (const interest of listeners)
76
- interests.push(interest);
77
- }
78
- try {
79
- const sent = this.send.timelines(this.agentIds());
80
- if (!sent)
81
- return;
82
- await sent;
83
- for (const interest of interests)
84
- interest.resolve();
85
- }
86
- catch (error) {
87
- for (const interest of interests)
88
- interest.reject(error);
89
- throw error;
90
- }
91
- }
92
- setEvents(events) {
93
- if (JSON.stringify(this.events) === JSON.stringify(events))
94
- return;
95
- this.events = events;
96
- void this.send.events(events).catch(this.send.failed);
97
- }
98
- restore() {
99
- if (this.agentIds().length)
100
- void this.syncTimelines().catch(this.send.failed);
101
- if (this.events.length)
102
- void this.send.events(this.events).catch(this.send.failed);
103
- }
104
- close() {
105
- for (const interests of this.timelines.values()) {
106
- for (const interest of interests)
107
- interest.reject(new Error("Daemon client closed"));
108
- }
109
- }
110
- }
111
115
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,19 @@
1
+ import type { SessionOutboundMessage } from "@camerontaylor/paseo-protocol/messages";
2
+ type AgentEntry = Extract<SessionOutboundMessage, {
3
+ type: "fetch_agents_response";
4
+ }>["payload"]["entries"][number];
5
+ type Workspace = Extract<SessionOutboundMessage, {
6
+ type: "fetch_workspaces_response";
7
+ }>["payload"]["entries"][number];
8
+ type WorkspaceUpdate = Extract<SessionOutboundMessage, {
9
+ type: "workspace_update";
10
+ }>;
11
+ export declare class LegacyWorkspaces {
12
+ private readonly agents;
13
+ normalize(message: SessionOutboundMessage): SessionOutboundMessage;
14
+ read(entries: AgentEntry[], reset: boolean): Workspace[];
15
+ update(message: SessionOutboundMessage): WorkspaceUpdate[];
16
+ private workspaces;
17
+ }
18
+ export {};
19
+ //# sourceMappingURL=legacy-workspaces.d.ts.map
@@ -0,0 +1,160 @@
1
+ import { AgentSnapshotPayloadSchema } from "@camerontaylor/paseo-protocol/messages";
2
+ import { deriveAgentStateBucket, getWorkspaceStateBucketPriority, } from "@camerontaylor/paseo-protocol/agent-state-bucket";
3
+ // Preserve the pre-registry app's identity format. This is an opaque ID, never
4
+ // a filesystem path: converting C:\ to C: changes a drive root into a relative path.
5
+ function legacyWorkspaceId(value) {
6
+ return value.trim().replace(/\\/g, "/").replace(/\/+$/, "") || "/";
7
+ }
8
+ // COMPAT(legacyWorkspaceDaemon): restored in v0.8.0; remove after 2027-03-11 once daemon floor >= v0.1.97.
9
+ // The pre-registry app grouped agents by checkout path. Keep that representation
10
+ // at the client edge, including live updates, so app workflows need no version branch.
11
+ export class LegacyWorkspaces {
12
+ constructor() {
13
+ this.agents = new Map();
14
+ }
15
+ normalize(message) {
16
+ const stamp = (agent) => ({
17
+ ...agent,
18
+ workspaceId: agent.workspaceId ?? legacyWorkspaceId(agent.cwd),
19
+ });
20
+ if (message.type === "fetch_agents_response")
21
+ return {
22
+ ...message,
23
+ payload: {
24
+ ...message.payload,
25
+ entries: message.payload.entries.map((entry) => ({
26
+ ...entry,
27
+ agent: stamp(entry.agent),
28
+ })),
29
+ },
30
+ };
31
+ if (message.type === "agent_update" && message.payload.kind === "upsert")
32
+ return { ...message, payload: { ...message.payload, agent: stamp(message.payload.agent) } };
33
+ if ((message.type === "fetch_agent_response" ||
34
+ message.type === "fetch_agent_timeline_response" ||
35
+ message.type === "cancel_agent_response") &&
36
+ message.payload.agent) {
37
+ return {
38
+ ...message,
39
+ payload: { ...message.payload, agent: stamp(message.payload.agent) },
40
+ };
41
+ }
42
+ if (message.type === "status" &&
43
+ (message.payload.status === "agent_created" || message.payload.status === "agent_resumed")) {
44
+ return {
45
+ ...message,
46
+ payload: {
47
+ ...message.payload,
48
+ agent: stamp(AgentSnapshotPayloadSchema.parse(message.payload.agent)),
49
+ },
50
+ };
51
+ }
52
+ return message;
53
+ }
54
+ read(entries, reset) {
55
+ if (reset)
56
+ this.agents.clear();
57
+ for (const entry of entries)
58
+ this.agents.set(entry.agent.id, entry);
59
+ const pageIds = new Set(entries.map(workspaceId));
60
+ return [...this.workspaces().values()].filter((workspace) => pageIds.has(workspace.id));
61
+ }
62
+ update(message) {
63
+ if (message.type !== "agent_update")
64
+ return [];
65
+ const before = this.workspaces();
66
+ const update = message.payload;
67
+ if (update.kind === "remove")
68
+ this.agents.delete(update.agentId);
69
+ else {
70
+ const project = update.project ?? this.agents.get(update.agent.id)?.project;
71
+ if (update.agent.archivedAt)
72
+ this.agents.delete(update.agent.id);
73
+ else if (project)
74
+ this.agents.set(update.agent.id, { agent: update.agent, project });
75
+ }
76
+ const after = this.workspaces();
77
+ const changes = [];
78
+ for (const [id, workspace] of after) {
79
+ if (JSON.stringify(before.get(id)) !== JSON.stringify(workspace))
80
+ changes.push({ type: "workspace_update", payload: { kind: "upsert", workspace } });
81
+ }
82
+ for (const id of before.keys())
83
+ if (!after.has(id))
84
+ changes.push({ type: "workspace_update", payload: { kind: "remove", id } });
85
+ return changes;
86
+ }
87
+ workspaces() {
88
+ const workspaces = new Map();
89
+ for (const entry of this.agents.values()) {
90
+ const { agent, project } = entry;
91
+ const { checkout } = project;
92
+ const id = workspaceId(entry);
93
+ const status = deriveAgentStateBucket({
94
+ status: agent.status,
95
+ pendingPermissionCount: agent.pendingPermissions.length,
96
+ requiresAttention: agent.requiresAttention,
97
+ attentionReason: agent.attentionReason,
98
+ });
99
+ const existing = workspaces.get(id);
100
+ if (existing &&
101
+ getWorkspaceStateBucketPriority(existing.status) <= getWorkspaceStateBucketPriority(status))
102
+ continue;
103
+ workspaces.set(id, {
104
+ id,
105
+ projectId: project.projectKey,
106
+ projectDisplayName: project.projectName,
107
+ projectCustomName: null,
108
+ projectRootPath: checkout.mainRepoRoot ?? checkout.worktreeRoot ?? checkout.cwd,
109
+ workspaceDirectory: checkout.cwd,
110
+ projectKind: checkout.isGit ? "git" : "non_git",
111
+ workspaceKind: workspaceKind(checkout),
112
+ name: workspaceName(entry, id),
113
+ title: null,
114
+ status,
115
+ statusEnteredAt: agent.attentionTimestamp ?? agent.updatedAt,
116
+ activityAt: agent.updatedAt,
117
+ archivingAt: null,
118
+ diffStat: null,
119
+ scripts: [],
120
+ gitRuntime: gitRuntime(checkout),
121
+ githubRuntime: null,
122
+ project,
123
+ });
124
+ }
125
+ return workspaces;
126
+ }
127
+ }
128
+ function workspaceId(entry) {
129
+ return entry.agent.workspaceId ?? legacyWorkspaceId(entry.project.checkout.cwd);
130
+ }
131
+ function workspaceKind(checkout) {
132
+ if (!checkout.isGit)
133
+ return "directory";
134
+ if (checkout.isPaseoOwnedWorktree)
135
+ return "worktree";
136
+ return "checkout";
137
+ }
138
+ function workspaceName(entry, id) {
139
+ const name = entry.project.workspaceName?.trim();
140
+ if (name)
141
+ return name;
142
+ const branch = entry.project.checkout.currentBranch?.trim();
143
+ if (branch && branch !== "HEAD")
144
+ return branch;
145
+ return id.slice(id.lastIndexOf("/") + 1);
146
+ }
147
+ function gitRuntime(checkout) {
148
+ if (!checkout.isGit)
149
+ return null;
150
+ return {
151
+ currentBranch: checkout.currentBranch,
152
+ remoteUrl: checkout.remoteUrl,
153
+ isPaseoOwnedWorktree: checkout.isPaseoOwnedWorktree,
154
+ isDirty: null,
155
+ aheadBehind: null,
156
+ aheadOfOrigin: null,
157
+ behindOfOrigin: null,
158
+ };
159
+ }
160
+ //# sourceMappingURL=legacy-workspaces.js.map
@@ -0,0 +1,31 @@
1
+ import type { ServerInfoStatusPayload, SessionInboundMessage, SessionOutboundMessage } from "@camerontaylor/paseo-protocol/messages";
2
+ export type ObservationRequest = {
3
+ type: SessionInboundMessage["type"];
4
+ } & Record<string, unknown>;
5
+ interface Interest {
6
+ id: string;
7
+ query: ObservationRequest;
8
+ }
9
+ export declare class LegacySubscriptions {
10
+ private readonly info;
11
+ private readonly send;
12
+ private readonly browserHost;
13
+ private readonly interests;
14
+ private readonly workspaces;
15
+ constructor(info: ServerInfoStatusPayload, send: (message: ObservationRequest) => Promise<void>, browserHost: unknown);
16
+ normalize(message: SessionOutboundMessage): SessionOutboundMessage;
17
+ prepareRequest(message: SessionInboundMessage): {
18
+ message: SessionInboundMessage;
19
+ receive(message: SessionOutboundMessage): SessionOutboundMessage;
20
+ finish(): Promise<void>;
21
+ };
22
+ start(query: ObservationRequest): Interest;
23
+ request({ id, query }: Interest): ObservationRequest | null;
24
+ private members;
25
+ release(id: string): ObservationRequest | null;
26
+ owns(message: SessionOutboundMessage): boolean;
27
+ receive(message: SessionOutboundMessage, deliver: (message: SessionOutboundMessage) => void): void;
28
+ private matches;
29
+ }
30
+ export {};
31
+ //# sourceMappingURL=legacy.d.ts.map