@getpaseo/server 0.1.100 → 0.1.101
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/server/executable-resolution/windows.js +3 -0
- package/dist/server/server/agent/agent-manager.d.ts +10 -0
- package/dist/server/server/agent/agent-manager.js +65 -27
- package/dist/server/server/agent/agent-sdk-types.d.ts +8 -0
- package/dist/server/server/agent/mcp-server.d.ts +2 -45
- package/dist/server/server/agent/mcp-server.js +45 -1985
- package/dist/server/server/agent/prompt-attachments.js +6 -2
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +4 -0
- package/dist/server/server/agent/provider-snapshot-manager.js +58 -13
- package/dist/server/server/agent/providers/acp-agent.d.ts +20 -1
- package/dist/server/server/agent/providers/acp-agent.js +170 -26
- package/dist/server/server/agent/providers/claude/agent.js +60 -10
- package/dist/server/server/agent/providers/codex-app-server-agent.js +6 -57
- package/dist/server/server/agent/providers/diagnostic-utils.d.ts +1 -0
- package/dist/server/server/agent/providers/diagnostic-utils.js +1 -1
- package/dist/server/server/agent/providers/generic-acp-agent.d.ts +3 -0
- package/dist/server/server/agent/providers/generic-acp-agent.js +41 -23
- package/dist/server/server/agent/providers/mock-load-test-agent.js +4 -2
- package/dist/server/server/agent/providers/pi/agent.d.ts +2 -1
- package/dist/server/server/agent/providers/pi/agent.js +3 -0
- package/dist/server/server/agent/providers/provider-image-output.d.ts +5 -0
- package/dist/server/server/agent/providers/provider-image-output.js +55 -0
- package/dist/server/server/agent/tools/paseo-tools.d.ts +48 -0
- package/dist/server/server/agent/tools/paseo-tools.js +2121 -0
- package/dist/server/server/agent/tools/types.d.ts +36 -0
- package/dist/server/server/agent/tools/types.js +2 -0
- package/dist/server/server/bootstrap.js +71 -62
- package/dist/server/server/persisted-config.d.ts +5 -0
- package/dist/server/server/persisted-config.js +10 -2
- package/dist/server/server/session/agent-updates/agent-updates-service.d.ts +59 -0
- package/dist/server/server/session/agent-updates/agent-updates-service.js +220 -0
- package/dist/server/server/session/checkout/checkout-session.d.ts +13 -15
- package/dist/server/server/session/checkout/checkout-session.js +18 -16
- package/dist/server/server/session/checkout/git-metadata-generator.d.ts +53 -0
- package/dist/server/server/session/checkout/git-metadata-generator.js +159 -0
- package/dist/server/server/session/daemon/daemon-session.d.ts +14 -0
- package/dist/server/server/session/daemon/daemon-session.js +38 -0
- package/dist/server/server/session/daemon/diagnostics.d.ts +41 -0
- package/dist/server/server/session/daemon/diagnostics.js +421 -0
- package/dist/server/server/session/git-mutation/git-mutation-service.d.ts +34 -0
- package/dist/server/server/session/git-mutation/git-mutation-service.js +71 -0
- package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.d.ts +36 -0
- package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.js +134 -0
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.d.ts +34 -0
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.js +190 -0
- package/dist/server/server/session/workspace-scripts/workspace-scripts-service.d.ts +41 -0
- package/dist/server/server/session/workspace-scripts/workspace-scripts-service.js +100 -0
- package/dist/server/server/session.d.ts +7 -51
- package/dist/server/server/session.js +113 -938
- package/dist/server/server/speech/providers/openai/config.d.ts +1 -2
- package/dist/server/server/speech/providers/openai/config.js +13 -9
- package/dist/server/server/speech/providers/openai/runtime.js +2 -16
- package/dist/server/server/speech/providers/openai/stt.d.ts +1 -0
- package/dist/server/server/speech/providers/openai/stt.js +4 -2
- package/dist/server/server/speech/providers/openai/tts.d.ts +1 -0
- package/dist/server/server/speech/providers/openai/tts.js +1 -0
- package/dist/server/server/websocket/runtime-metrics.d.ts +20 -0
- package/dist/server/server/websocket-server.d.ts +1 -2
- package/dist/server/server/websocket-server.js +26 -21
- package/dist/server/server/worktree-bootstrap.d.ts +1 -1
- package/dist/server/server/worktree-branch-name-generator.js +3 -1
- package/dist/server/utils/checkout-git.js +51 -26
- package/dist/src/executable-resolution/windows.js +3 -0
- package/dist/src/server/persisted-config.js +10 -2
- package/package.json +5 -5
- package/dist/server/server/speech/providers/openai/realtime-transcription-session.d.ts +0 -42
- package/dist/server/server/speech/providers/openai/realtime-transcription-session.js +0 -168
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
export interface PaseoToolExecutionContext {
|
|
3
|
+
signal?: AbortSignal;
|
|
4
|
+
}
|
|
5
|
+
export interface PaseoToolResult {
|
|
6
|
+
content: Array<{
|
|
7
|
+
type: string;
|
|
8
|
+
text?: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}>;
|
|
11
|
+
structuredContent?: unknown;
|
|
12
|
+
isError?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface PaseoToolConfig {
|
|
15
|
+
title?: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
inputSchema?: z.ZodRawShape | z.ZodType;
|
|
18
|
+
outputSchema?: z.ZodRawShape | z.ZodType;
|
|
19
|
+
}
|
|
20
|
+
export interface PaseoToolDefinition extends PaseoToolConfig {
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
handler: (input: unknown, context: PaseoToolExecutionContext) => Promise<PaseoToolResult>;
|
|
24
|
+
}
|
|
25
|
+
export interface PaseoToolCatalog {
|
|
26
|
+
tools: ReadonlyMap<string, PaseoToolDefinition>;
|
|
27
|
+
getTool(name: string): PaseoToolDefinition | undefined;
|
|
28
|
+
executeTool(name: string, input: unknown, context?: PaseoToolExecutionContext): Promise<PaseoToolResult>;
|
|
29
|
+
}
|
|
30
|
+
export interface PaseoToolRuntimeContext {
|
|
31
|
+
callerAgentId?: string;
|
|
32
|
+
enableVoiceTools?: boolean;
|
|
33
|
+
voiceOnly?: boolean;
|
|
34
|
+
}
|
|
35
|
+
export type PaseoToolCatalogFactory = (context: PaseoToolRuntimeContext) => PaseoToolCatalog | Promise<PaseoToolCatalog>;
|
|
36
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -83,6 +83,7 @@ import { AgentManager } from "./agent/agent-manager.js";
|
|
|
83
83
|
import { AgentStorage } from "./agent/agent-storage.js";
|
|
84
84
|
import { attachAgentStoragePersistence } from "./persistence-hooks.js";
|
|
85
85
|
import { createAgentMcpServer } from "./agent/mcp-server.js";
|
|
86
|
+
import { createPaseoToolCatalog, } from "./agent/tools/paseo-tools.js";
|
|
86
87
|
import { ProviderSnapshotManager } from "./agent/provider-snapshot-manager.js";
|
|
87
88
|
import { bootstrapWorkspaceRegistries } from "./workspace-registry-bootstrap.js";
|
|
88
89
|
import { WorkspaceReconciliationService } from "./workspace-reconciliation-service.js";
|
|
@@ -612,73 +613,79 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
612
613
|
clearWorkspaceArchiving: clearWorkspaceArchivingExternal,
|
|
613
614
|
emitWorkspaceUpdatesForWorkspaceIds: emitWorkspaceUpdatesExternal,
|
|
614
615
|
});
|
|
616
|
+
const createPaseoWorktreeForTools = async (input, serviceOptions) => {
|
|
617
|
+
return createPaseoWorktreeWorkflow({
|
|
618
|
+
paseoHome: config.paseoHome,
|
|
619
|
+
worktreesRoot: config.worktreesRoot,
|
|
620
|
+
createPaseoWorktree: async (workflowInput, workflowOptions) => {
|
|
621
|
+
return createRegisteredPaseoWorktree(workflowInput, {
|
|
622
|
+
github,
|
|
623
|
+
...(workflowOptions?.resolveDefaultBranch
|
|
624
|
+
? {
|
|
625
|
+
resolveDefaultBranch: workflowOptions.resolveDefaultBranch,
|
|
626
|
+
}
|
|
627
|
+
: {}),
|
|
628
|
+
projectRegistry,
|
|
629
|
+
workspaceRegistry,
|
|
630
|
+
workspaceGitService,
|
|
631
|
+
});
|
|
632
|
+
},
|
|
633
|
+
warmWorkspaceGitData: async (workspace) => {
|
|
634
|
+
await Promise.all(wsServer
|
|
635
|
+
?.listActiveSessions()
|
|
636
|
+
.map((session) => session.warmWorkspaceGitDataForWorkspace(workspace)) ?? []);
|
|
637
|
+
},
|
|
638
|
+
emitWorkspaceUpdateForWorkspaceId: async (workspaceId) => {
|
|
639
|
+
await emitWorkspaceUpdatesExternal([workspaceId]);
|
|
640
|
+
},
|
|
641
|
+
cacheWorkspaceSetupSnapshot: () => { },
|
|
642
|
+
emit: emitExternalSessionMessage,
|
|
643
|
+
sessionLogger: logger,
|
|
644
|
+
terminalManager,
|
|
645
|
+
archiveWorkspaceRecord: archiveWorkspaceRecordExternal,
|
|
646
|
+
serviceProxy,
|
|
647
|
+
scriptRuntimeStore,
|
|
648
|
+
getDaemonTcpPort: () => (boundListenTarget?.type === "tcp" ? boundListenTarget.port : null),
|
|
649
|
+
getDaemonTcpHost: () => (boundListenTarget?.type === "tcp" ? boundListenTarget.host : null),
|
|
650
|
+
serviceProxyPublicBaseUrl,
|
|
651
|
+
onScriptsChanged: null,
|
|
652
|
+
}, input, serviceOptions);
|
|
653
|
+
};
|
|
654
|
+
const createAgentToolHostDependencies = (runtime) => ({
|
|
655
|
+
agentManager,
|
|
656
|
+
agentStorage,
|
|
657
|
+
terminalManager,
|
|
658
|
+
getDaemonTcpPort: () => (boundListenTarget?.type === "tcp" ? boundListenTarget.port : null),
|
|
659
|
+
scheduleService,
|
|
660
|
+
providerSnapshotManager,
|
|
661
|
+
github,
|
|
662
|
+
workspaceGitService,
|
|
663
|
+
findWorkspaceIdForCwd: findWorkspaceIdForCwdExternal,
|
|
664
|
+
listActiveWorkspaces: listActiveWorkspacesExternal,
|
|
665
|
+
archiveWorkspaceRecord: archiveWorkspaceRecordExternal,
|
|
666
|
+
emitWorkspaceUpdatesForWorkspaceIds: emitWorkspaceUpdatesExternal,
|
|
667
|
+
markWorkspaceArchiving: markWorkspaceArchivingExternal,
|
|
668
|
+
clearWorkspaceArchiving: clearWorkspaceArchivingExternal,
|
|
669
|
+
ensureWorkspaceForCreate: ensureWorkspaceForCreateExternal,
|
|
670
|
+
createPaseoWorktree: createPaseoWorktreeForTools,
|
|
671
|
+
paseoHome: config.paseoHome,
|
|
672
|
+
worktreesRoot: config.worktreesRoot,
|
|
673
|
+
callerAgentId: runtime.callerAgentId,
|
|
674
|
+
enableVoiceTools: runtime.enableVoiceTools,
|
|
675
|
+
voiceOnly: runtime.voiceOnly,
|
|
676
|
+
resolveSpeakHandler: (agentId) => wsServer?.resolveVoiceSpeakHandler(agentId) ?? null,
|
|
677
|
+
resolveCallerContext: (agentId) => wsServer?.resolveVoiceCallerContext(agentId) ?? null,
|
|
678
|
+
logger,
|
|
679
|
+
});
|
|
680
|
+
const createAgentToolCatalog = (runtime) => createPaseoToolCatalog(createAgentToolHostDependencies(runtime));
|
|
681
|
+
agentManager.setPaseoToolCatalogFactory(createAgentToolCatalog);
|
|
682
|
+
agentManager.setPaseoToolsEnabled(config.mcpInjectIntoAgents !== false);
|
|
615
683
|
const mcpEnabled = config.mcpEnabled ?? true;
|
|
616
684
|
let agentMcpBaseUrl = null;
|
|
617
685
|
if (mcpEnabled) {
|
|
618
686
|
const agentMcpRoute = "/mcp/agents";
|
|
619
687
|
const createAgentMcpSession = async (callerAgentId) => {
|
|
620
|
-
const agentMcpServer = await createAgentMcpServer({
|
|
621
|
-
agentManager,
|
|
622
|
-
agentStorage,
|
|
623
|
-
terminalManager,
|
|
624
|
-
getDaemonTcpPort: () => (boundListenTarget?.type === "tcp" ? boundListenTarget.port : null),
|
|
625
|
-
scheduleService,
|
|
626
|
-
providerSnapshotManager,
|
|
627
|
-
github,
|
|
628
|
-
workspaceGitService,
|
|
629
|
-
findWorkspaceIdForCwd: findWorkspaceIdForCwdExternal,
|
|
630
|
-
listActiveWorkspaces: listActiveWorkspacesExternal,
|
|
631
|
-
archiveWorkspaceRecord: archiveWorkspaceRecordExternal,
|
|
632
|
-
emitWorkspaceUpdatesForWorkspaceIds: emitWorkspaceUpdatesExternal,
|
|
633
|
-
markWorkspaceArchiving: markWorkspaceArchivingExternal,
|
|
634
|
-
clearWorkspaceArchiving: clearWorkspaceArchivingExternal,
|
|
635
|
-
ensureWorkspaceForCreate: ensureWorkspaceForCreateExternal,
|
|
636
|
-
createPaseoWorktree: async (input, serviceOptions) => {
|
|
637
|
-
return createPaseoWorktreeWorkflow({
|
|
638
|
-
paseoHome: config.paseoHome,
|
|
639
|
-
worktreesRoot: config.worktreesRoot,
|
|
640
|
-
createPaseoWorktree: async (workflowInput, workflowOptions) => {
|
|
641
|
-
return createRegisteredPaseoWorktree(workflowInput, {
|
|
642
|
-
github,
|
|
643
|
-
...(workflowOptions?.resolveDefaultBranch
|
|
644
|
-
? {
|
|
645
|
-
resolveDefaultBranch: workflowOptions.resolveDefaultBranch,
|
|
646
|
-
}
|
|
647
|
-
: {}),
|
|
648
|
-
projectRegistry,
|
|
649
|
-
workspaceRegistry,
|
|
650
|
-
workspaceGitService,
|
|
651
|
-
});
|
|
652
|
-
},
|
|
653
|
-
warmWorkspaceGitData: async (workspace) => {
|
|
654
|
-
await Promise.all(wsServer
|
|
655
|
-
?.listActiveSessions()
|
|
656
|
-
.map((session) => session.warmWorkspaceGitDataForWorkspace(workspace)) ?? []);
|
|
657
|
-
},
|
|
658
|
-
emitWorkspaceUpdateForWorkspaceId: async (workspaceId) => {
|
|
659
|
-
await emitWorkspaceUpdatesExternal([workspaceId]);
|
|
660
|
-
},
|
|
661
|
-
cacheWorkspaceSetupSnapshot: () => { },
|
|
662
|
-
emit: emitExternalSessionMessage,
|
|
663
|
-
sessionLogger: logger,
|
|
664
|
-
terminalManager,
|
|
665
|
-
archiveWorkspaceRecord: archiveWorkspaceRecordExternal,
|
|
666
|
-
serviceProxy,
|
|
667
|
-
scriptRuntimeStore,
|
|
668
|
-
getDaemonTcpPort: () => boundListenTarget?.type === "tcp" ? boundListenTarget.port : null,
|
|
669
|
-
getDaemonTcpHost: () => boundListenTarget?.type === "tcp" ? boundListenTarget.host : null,
|
|
670
|
-
serviceProxyPublicBaseUrl,
|
|
671
|
-
onScriptsChanged: null,
|
|
672
|
-
}, input, serviceOptions);
|
|
673
|
-
},
|
|
674
|
-
paseoHome: config.paseoHome,
|
|
675
|
-
worktreesRoot: config.worktreesRoot,
|
|
676
|
-
callerAgentId,
|
|
677
|
-
enableVoiceTools: false,
|
|
678
|
-
resolveSpeakHandler: (agentId) => wsServer?.resolveVoiceSpeakHandler(agentId) ?? null,
|
|
679
|
-
resolveCallerContext: (agentId) => wsServer?.resolveVoiceCallerContext(agentId) ?? null,
|
|
680
|
-
logger,
|
|
681
|
-
});
|
|
688
|
+
const agentMcpServer = await createAgentMcpServer(createAgentToolHostDependencies({ callerAgentId }));
|
|
682
689
|
// Stateless mode: each HTTP request builds a fresh server + transport that is
|
|
683
690
|
// torn down when the response closes, so no per-session state is retained between
|
|
684
691
|
// requests. The agent control plane only lists and calls tools, neither of which
|
|
@@ -810,8 +817,10 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
810
817
|
const mcpBaseUrl = mcpEnabled ? createAgentMcpBaseUrl(boundListenTarget) : null;
|
|
811
818
|
agentMcpBaseUrl = config.mcpInjectIntoAgents === false ? null : mcpBaseUrl;
|
|
812
819
|
agentManager.setMcpBaseUrl(agentMcpBaseUrl);
|
|
820
|
+
agentManager.setPaseoToolsEnabled(config.mcpInjectIntoAgents !== false);
|
|
813
821
|
daemonConfigStore.onFieldChange("mcp.injectIntoAgents", (value) => {
|
|
814
822
|
agentManager.setMcpBaseUrl(value ? mcpBaseUrl : null);
|
|
823
|
+
agentManager.setPaseoToolsEnabled(value !== false);
|
|
815
824
|
});
|
|
816
825
|
daemonConfigStore.onFieldChange("appendSystemPrompt", (value) => {
|
|
817
826
|
agentManager.setAppendSystemPrompt(typeof value === "string" ? value : "");
|
|
@@ -133,6 +133,11 @@ export declare const PersistedConfigSchema: z.ZodObject<{
|
|
|
133
133
|
providers: z.ZodOptional<z.ZodObject<{
|
|
134
134
|
openai: z.ZodOptional<z.ZodObject<{
|
|
135
135
|
apiKey: z.ZodOptional<z.ZodString>;
|
|
136
|
+
voice: z.ZodOptional<z.ZodObject<{
|
|
137
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
138
|
+
baseUrl: z.ZodOptional<z.ZodString>;
|
|
139
|
+
}, z.core.$strict>>;
|
|
140
|
+
baseUrl: z.ZodOptional<z.ZodString>;
|
|
136
141
|
}, z.core.$strict>>;
|
|
137
142
|
local: z.ZodOptional<z.ZodObject<{
|
|
138
143
|
modelsDir: z.ZodOptional<z.ZodString>;
|
|
@@ -34,9 +34,17 @@ const LogConfigSchema = z
|
|
|
34
34
|
.optional(),
|
|
35
35
|
})
|
|
36
36
|
.strict();
|
|
37
|
-
const
|
|
37
|
+
const OpenAiVoiceProviderSchema = z
|
|
38
|
+
.object({
|
|
39
|
+
apiKey: z.string().trim().min(1).optional(),
|
|
40
|
+
baseUrl: z.string().trim().min(1).optional(),
|
|
41
|
+
})
|
|
42
|
+
.strict();
|
|
43
|
+
const OpenAiProviderSchema = z
|
|
38
44
|
.object({
|
|
39
45
|
apiKey: z.string().min(1).optional(),
|
|
46
|
+
voice: OpenAiVoiceProviderSchema.optional(),
|
|
47
|
+
baseUrl: z.string().trim().min(1).optional(),
|
|
40
48
|
})
|
|
41
49
|
.strict();
|
|
42
50
|
const LocalSpeechProviderSchema = z
|
|
@@ -46,7 +54,7 @@ const LocalSpeechProviderSchema = z
|
|
|
46
54
|
.strict();
|
|
47
55
|
const ProvidersSchema = z
|
|
48
56
|
.object({
|
|
49
|
-
openai:
|
|
57
|
+
openai: OpenAiProviderSchema.optional(),
|
|
50
58
|
local: LocalSpeechProviderSchema.optional(),
|
|
51
59
|
})
|
|
52
60
|
.strict();
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type pino from "pino";
|
|
2
|
+
import type { AgentSnapshotPayload, ProjectPlacementPayload, SessionInboundMessage, SessionOutboundMessage } from "../../messages.js";
|
|
3
|
+
import type { ManagedAgent } from "../../agent/agent-manager.js";
|
|
4
|
+
import type { StoredAgentRecord } from "../../agent/agent-storage.js";
|
|
5
|
+
type AgentUpdatesFilter = NonNullable<Extract<SessionInboundMessage, {
|
|
6
|
+
type: "fetch_agents_request";
|
|
7
|
+
}>["filter"]>;
|
|
8
|
+
/**
|
|
9
|
+
* Owns the single per-client `agent_update` subscription: when a client subscribes
|
|
10
|
+
* via `fetch_agents_request`, every later agent lifecycle change (live forward,
|
|
11
|
+
* stored-record archive/detach, delete) is filtered against the subscription's
|
|
12
|
+
* filter and either emitted or — while the initial snapshot is still being built —
|
|
13
|
+
* buffered and replayed on flush. Keeping the mutable subscription state, the
|
|
14
|
+
* bootstrap buffer, the provider-visibility gate, and the filter predicate behind
|
|
15
|
+
* one interface stops the rest of session.ts from poking the subscription shape or
|
|
16
|
+
* hand-rolling `agent_update` payloads, and the (previously untested) filter/buffer/
|
|
17
|
+
* flush branches become exercisable through injected fakes.
|
|
18
|
+
*
|
|
19
|
+
* The snapshot listing path applies the SAME filter via the pure
|
|
20
|
+
* `matchesAgentUpdatesFilter` so a subscription's initial page and its live updates
|
|
21
|
+
* stay consistent.
|
|
22
|
+
*/
|
|
23
|
+
export interface AgentUpdatesService {
|
|
24
|
+
beginSubscription(input: {
|
|
25
|
+
subscriptionId: string;
|
|
26
|
+
filter?: AgentUpdatesFilter;
|
|
27
|
+
}): void;
|
|
28
|
+
flushBootstrapped(subscriptionId: string, options?: {
|
|
29
|
+
snapshotUpdatedAtByAgentId?: Map<string, number>;
|
|
30
|
+
}): void;
|
|
31
|
+
clearSubscription(subscriptionId: string): void;
|
|
32
|
+
hasSubscription(): boolean;
|
|
33
|
+
forwardLiveAgent(agent: ManagedAgent): Promise<void>;
|
|
34
|
+
emitStoredRecord(record: StoredAgentRecord): Promise<AgentSnapshotPayload>;
|
|
35
|
+
removeAgent(agentId: string): void;
|
|
36
|
+
dispose(): void;
|
|
37
|
+
}
|
|
38
|
+
export interface AgentUpdatesServiceDeps {
|
|
39
|
+
emit(message: SessionOutboundMessage): void;
|
|
40
|
+
buildAgentPayload(agent: ManagedAgent): Promise<AgentSnapshotPayload>;
|
|
41
|
+
buildStoredAgentPayload(record: StoredAgentRecord): AgentSnapshotPayload;
|
|
42
|
+
isProviderVisibleToClient(provider: string): boolean;
|
|
43
|
+
buildProjectPlacementForWorkspaceId(workspaceId: string): Promise<ProjectPlacementPayload | null>;
|
|
44
|
+
emitWorkspaceUpdateForWorkspaceId(workspaceId: string): Promise<void>;
|
|
45
|
+
logger: pino.Logger;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Pure predicate shared by the live subscription stream and the snapshot listing
|
|
49
|
+
* pager: does an agent (with its resolved project placement) satisfy a
|
|
50
|
+
* `fetch_agents` filter?
|
|
51
|
+
*/
|
|
52
|
+
export declare function matchesAgentUpdatesFilter(input: {
|
|
53
|
+
agent: AgentSnapshotPayload;
|
|
54
|
+
project: ProjectPlacementPayload;
|
|
55
|
+
filter?: AgentUpdatesFilter;
|
|
56
|
+
}): boolean;
|
|
57
|
+
export declare function createAgentUpdatesService(deps: AgentUpdatesServiceDeps): AgentUpdatesService;
|
|
58
|
+
export {};
|
|
59
|
+
//# sourceMappingURL=agent-updates-service.d.ts.map
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { resolveEffectiveThinkingOptionId } from "../../agent/agent-projections.js";
|
|
2
|
+
function agentThinkingOptionMatchesFilter(agent, filter) {
|
|
3
|
+
if (filter.thinkingOptionId === undefined) {
|
|
4
|
+
return true;
|
|
5
|
+
}
|
|
6
|
+
const expectedThinkingOptionId = resolveEffectiveThinkingOptionId({
|
|
7
|
+
configuredThinkingOptionId: filter.thinkingOptionId ?? null,
|
|
8
|
+
});
|
|
9
|
+
const resolvedThinkingOptionId = agent.effectiveThinkingOptionId ??
|
|
10
|
+
resolveEffectiveThinkingOptionId({
|
|
11
|
+
runtimeInfo: agent.runtimeInfo,
|
|
12
|
+
configuredThinkingOptionId: agent.thinkingOptionId ?? null,
|
|
13
|
+
});
|
|
14
|
+
return resolvedThinkingOptionId === expectedThinkingOptionId;
|
|
15
|
+
}
|
|
16
|
+
function matchesAgentStructuralFilter(agent, project, filter) {
|
|
17
|
+
if (filter.statuses && filter.statuses.length > 0) {
|
|
18
|
+
const statuses = new Set(filter.statuses);
|
|
19
|
+
if (!statuses.has(agent.status)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (typeof filter.requiresAttention === "boolean") {
|
|
24
|
+
const requiresAttention = agent.requiresAttention ?? false;
|
|
25
|
+
if (requiresAttention !== filter.requiresAttention) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (filter.projectKeys && filter.projectKeys.length > 0) {
|
|
30
|
+
const projectKeys = new Set(filter.projectKeys.filter((item) => item.trim().length > 0));
|
|
31
|
+
if (projectKeys.size > 0 && !projectKeys.has(project.projectKey)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Pure predicate shared by the live subscription stream and the snapshot listing
|
|
39
|
+
* pager: does an agent (with its resolved project placement) satisfy a
|
|
40
|
+
* `fetch_agents` filter?
|
|
41
|
+
*/
|
|
42
|
+
export function matchesAgentUpdatesFilter(input) {
|
|
43
|
+
const { agent, project, filter } = input;
|
|
44
|
+
if (filter?.labels) {
|
|
45
|
+
const matchesLabels = Object.entries(filter.labels).every(([key, value]) => agent.labels[key] === value);
|
|
46
|
+
if (!matchesLabels) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const includeArchived = filter?.includeArchived ?? false;
|
|
51
|
+
if (!includeArchived && agent.archivedAt) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (filter && !agentThinkingOptionMatchesFilter(agent, filter)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (filter && !matchesAgentStructuralFilter(agent, project, filter)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
function agentUpdateTargetId(update) {
|
|
63
|
+
return update.kind === "remove" ? update.agentId : update.agent.id;
|
|
64
|
+
}
|
|
65
|
+
export function createAgentUpdatesService(deps) {
|
|
66
|
+
let subscription = null;
|
|
67
|
+
function bufferOrEmit(sub, payload) {
|
|
68
|
+
if (payload.kind === "upsert" && !deps.isProviderVisibleToClient(payload.agent.provider)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (sub.isBootstrapping) {
|
|
72
|
+
sub.pendingUpdatesByAgentId.set(agentUpdateTargetId(payload), payload);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
deps.emit({
|
|
76
|
+
type: "agent_update",
|
|
77
|
+
payload,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function beginSubscription(input) {
|
|
81
|
+
subscription = {
|
|
82
|
+
subscriptionId: input.subscriptionId,
|
|
83
|
+
filter: input.filter,
|
|
84
|
+
isBootstrapping: true,
|
|
85
|
+
pendingUpdatesByAgentId: new Map(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function flushBootstrapped(subscriptionId, options) {
|
|
89
|
+
if (!subscription || subscription.subscriptionId !== subscriptionId) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!subscription.isBootstrapping) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
subscription.isBootstrapping = false;
|
|
96
|
+
const pending = Array.from(subscription.pendingUpdatesByAgentId.values());
|
|
97
|
+
subscription.pendingUpdatesByAgentId.clear();
|
|
98
|
+
for (const payload of pending) {
|
|
99
|
+
if (payload.kind === "upsert") {
|
|
100
|
+
const snapshotUpdatedAt = options?.snapshotUpdatedAtByAgentId?.get(payload.agent.id);
|
|
101
|
+
if (typeof snapshotUpdatedAt === "number") {
|
|
102
|
+
const updateUpdatedAt = Date.parse(payload.agent.updatedAt);
|
|
103
|
+
if (!Number.isNaN(updateUpdatedAt) && updateUpdatedAt <= snapshotUpdatedAt) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
deps.emit({
|
|
109
|
+
type: "agent_update",
|
|
110
|
+
payload,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function clearSubscription(subscriptionId) {
|
|
115
|
+
if (subscription && subscription.subscriptionId === subscriptionId) {
|
|
116
|
+
subscription = null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function hasSubscription() {
|
|
120
|
+
return subscription !== null;
|
|
121
|
+
}
|
|
122
|
+
function removeAgent(agentId) {
|
|
123
|
+
if (!subscription) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
bufferOrEmit(subscription, { kind: "remove", agentId });
|
|
127
|
+
}
|
|
128
|
+
async function emitStoredRecord(record) {
|
|
129
|
+
const payload = deps.buildStoredAgentPayload(record);
|
|
130
|
+
const sub = subscription;
|
|
131
|
+
if (!sub) {
|
|
132
|
+
return payload;
|
|
133
|
+
}
|
|
134
|
+
const project = payload.workspaceId
|
|
135
|
+
? await deps.buildProjectPlacementForWorkspaceId(payload.workspaceId)
|
|
136
|
+
: null;
|
|
137
|
+
if (!project) {
|
|
138
|
+
bufferOrEmit(sub, {
|
|
139
|
+
kind: "remove",
|
|
140
|
+
agentId: payload.id,
|
|
141
|
+
});
|
|
142
|
+
return payload;
|
|
143
|
+
}
|
|
144
|
+
const matches = matchesAgentUpdatesFilter({
|
|
145
|
+
agent: payload,
|
|
146
|
+
project,
|
|
147
|
+
filter: sub.filter,
|
|
148
|
+
});
|
|
149
|
+
bufferOrEmit(sub, matches
|
|
150
|
+
? {
|
|
151
|
+
kind: "upsert",
|
|
152
|
+
agent: payload,
|
|
153
|
+
project,
|
|
154
|
+
}
|
|
155
|
+
: {
|
|
156
|
+
kind: "remove",
|
|
157
|
+
agentId: payload.id,
|
|
158
|
+
});
|
|
159
|
+
return payload;
|
|
160
|
+
}
|
|
161
|
+
async function forwardLiveAgent(agent) {
|
|
162
|
+
try {
|
|
163
|
+
const sub = subscription;
|
|
164
|
+
const payload = await deps.buildAgentPayload(agent);
|
|
165
|
+
if (sub) {
|
|
166
|
+
const project = payload.workspaceId
|
|
167
|
+
? await deps.buildProjectPlacementForWorkspaceId(payload.workspaceId)
|
|
168
|
+
: null;
|
|
169
|
+
if (!project) {
|
|
170
|
+
bufferOrEmit(sub, {
|
|
171
|
+
kind: "remove",
|
|
172
|
+
agentId: payload.id,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
const matches = matchesAgentUpdatesFilter({
|
|
177
|
+
agent: payload,
|
|
178
|
+
project,
|
|
179
|
+
filter: sub.filter,
|
|
180
|
+
});
|
|
181
|
+
if (matches) {
|
|
182
|
+
bufferOrEmit(sub, {
|
|
183
|
+
kind: "upsert",
|
|
184
|
+
agent: payload,
|
|
185
|
+
project,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
bufferOrEmit(sub, {
|
|
190
|
+
kind: "remove",
|
|
191
|
+
agentId: payload.id,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// A lifecycle change updates exactly the agent's owning workspace, never
|
|
197
|
+
// every workspace sharing its cwd. Ownership is the agent's workspaceId.
|
|
198
|
+
if (payload.workspaceId) {
|
|
199
|
+
await deps.emitWorkspaceUpdateForWorkspaceId(payload.workspaceId);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
deps.logger.error({ err: error }, "Failed to emit agent update");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function dispose() {
|
|
207
|
+
subscription = null;
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
beginSubscription,
|
|
211
|
+
flushBootstrapped,
|
|
212
|
+
clearSubscription,
|
|
213
|
+
hasSubscription,
|
|
214
|
+
forwardLiveAgent,
|
|
215
|
+
emitStoredRecord,
|
|
216
|
+
removeAgent,
|
|
217
|
+
dispose,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=agent-updates-service.js.map
|
|
@@ -2,32 +2,26 @@ import type pino from "pino";
|
|
|
2
2
|
import type { BranchSuggestionsRequest, CheckoutRefreshRequest, CheckoutRenameBranchRequest, CheckoutStatusRequest, SessionInboundMessage, SessionOutboundMessage, SubscribeCheckoutDiffRequest, UnsubscribeCheckoutDiffRequest, ValidateBranchRequest } from "../../messages.js";
|
|
3
3
|
import type { CheckoutDiffCompareInput, CheckoutDiffSnapshotPayload } from "../../checkout-diff-manager.js";
|
|
4
4
|
import type { WorkspaceGitRuntimeSnapshot, WorkspaceGitService } from "../../workspace-git-service.js";
|
|
5
|
+
import type { GitMutationService } from "../git-mutation/git-mutation-service.js";
|
|
5
6
|
import { type GitHubService } from "../../../services/github-service.js";
|
|
6
|
-
import
|
|
7
|
+
import type { GitMetadataGenerator } from "./git-metadata-generator.js";
|
|
7
8
|
/**
|
|
8
9
|
* The collaborators a checkout command reaches that are NOT part of the checkout
|
|
9
|
-
* domain
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* domain and stay owned by the Session shell: client emit, workspace-update
|
|
11
|
+
* emission, the git branch-snapshot notifier, and the current-branch rename
|
|
12
|
+
* primitive. CheckoutSession orchestrates them but does not own them. The
|
|
13
|
+
* git-mutation primitives it performs (switch branch, force snapshot refresh) are
|
|
14
|
+
* injected separately as `gitMutation`, since they are shared with worktree and
|
|
15
|
+
* workspace creation.
|
|
13
16
|
*/
|
|
14
17
|
export interface CheckoutSessionHost {
|
|
15
18
|
emit(msg: SessionOutboundMessage): void;
|
|
16
|
-
notifyGitMutation(cwd: string, reason: GitMutationRefreshReason, options?: {
|
|
17
|
-
invalidateGithub?: boolean;
|
|
18
|
-
}): Promise<void>;
|
|
19
19
|
emitWorkspaceUpdateForCwd(cwd: string): Promise<void>;
|
|
20
20
|
handleWorkspaceGitBranchSnapshot(cwd: string, branchName: string | null): void;
|
|
21
21
|
renameCurrentBranch(cwd: string, branch: string): Promise<{
|
|
22
22
|
previousBranch: string | null;
|
|
23
23
|
currentBranch: string | null;
|
|
24
24
|
}>;
|
|
25
|
-
checkoutExistingBranch(cwd: string, branch: string): Promise<CheckoutExistingBranchResult>;
|
|
26
|
-
generateCommitMessage(cwd: string): Promise<string>;
|
|
27
|
-
generatePullRequestText(cwd: string, baseRef?: string): Promise<{
|
|
28
|
-
title: string;
|
|
29
|
-
body: string;
|
|
30
|
-
}>;
|
|
31
25
|
}
|
|
32
26
|
/**
|
|
33
27
|
* The slice of CheckoutDiffManager that CheckoutSession needs: open a live diff
|
|
@@ -46,9 +40,11 @@ export interface CheckoutDiffSubscriber {
|
|
|
46
40
|
}
|
|
47
41
|
export interface CheckoutSessionOptions {
|
|
48
42
|
host: CheckoutSessionHost;
|
|
43
|
+
gitMutation: Pick<GitMutationService, "checkoutExistingBranch" | "notifyGitMutation">;
|
|
49
44
|
workspaceGitService: WorkspaceGitService;
|
|
50
45
|
github: GitHubService;
|
|
51
46
|
checkoutDiffManager: CheckoutDiffSubscriber;
|
|
47
|
+
gitMetadataGenerator: GitMetadataGenerator;
|
|
52
48
|
paseoHome: string;
|
|
53
49
|
worktreesRoot: string | undefined;
|
|
54
50
|
logger: pino.Logger;
|
|
@@ -60,15 +56,17 @@ export interface CheckoutSessionOptions {
|
|
|
60
56
|
* merge/pull/push/stash and the GitHub-PR operations).
|
|
61
57
|
*
|
|
62
58
|
* Command operations keep the live diff in sync by calling scheduleDiffRefresh()
|
|
63
|
-
* and refresh the workspace git snapshot through
|
|
59
|
+
* and refresh the workspace git snapshot through gitMutation.notifyGitMutation(); the
|
|
64
60
|
* workspace git observer streams branch changes through emitStatusUpdate().
|
|
65
61
|
*/
|
|
66
62
|
export declare class CheckoutSession {
|
|
67
63
|
private static readonly PASEO_STASH_PREFIX;
|
|
68
64
|
private readonly host;
|
|
65
|
+
private readonly gitMutation;
|
|
69
66
|
private readonly workspaceGitService;
|
|
70
67
|
private readonly github;
|
|
71
68
|
private readonly checkoutDiffManager;
|
|
69
|
+
private readonly gitMetadataGenerator;
|
|
72
70
|
private readonly paseoHome;
|
|
73
71
|
private readonly worktreesRoot;
|
|
74
72
|
private readonly logger;
|