@getpaseo/server 0.1.97-beta.1 → 0.1.97-beta.2
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/server/agent/create-agent-title.d.ts +2 -0
- package/dist/server/server/agent/create-agent-title.js +5 -0
- package/dist/server/server/bootstrap.js +34 -52
- package/dist/server/server/paseo-worktree-service.js +3 -0
- package/dist/server/server/session.d.ts +2 -0
- package/dist/server/server/session.js +66 -21
- package/dist/server/server/workspace-registry.js +3 -3
- package/dist/server/server/worktree-branch-name-generator.js +22 -6
- package/dist/server/utils/build-metadata-prompt.d.ts +8 -3
- package/dist/server/utils/build-metadata-prompt.js +10 -9
- package/package.json +5 -5
- package/dist/server/utils/wrap-user-instructions.d.ts +0 -2
- package/dist/server/utils/wrap-user-instructions.js +0 -13
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FirstAgentContext } from "@getpaseo/protocol/messages";
|
|
1
2
|
export declare function resolveCreateAgentTitles(options: {
|
|
2
3
|
configTitle?: string | null;
|
|
3
4
|
initialPrompt?: string | null;
|
|
@@ -5,4 +6,5 @@ export declare function resolveCreateAgentTitles(options: {
|
|
|
5
6
|
explicitTitle: string | null;
|
|
6
7
|
provisionalTitle: string | null;
|
|
7
8
|
};
|
|
9
|
+
export declare function resolveFirstAgentPromptTitle(firstAgentContext?: FirstAgentContext): string | null;
|
|
8
10
|
//# sourceMappingURL=create-agent-title.d.ts.map
|
|
@@ -26,4 +26,9 @@ export function resolveCreateAgentTitles(options) {
|
|
|
26
26
|
provisionalTitle,
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
export function resolveFirstAgentPromptTitle(firstAgentContext) {
|
|
30
|
+
return (resolveCreateAgentTitles({
|
|
31
|
+
initialPrompt: firstAgentContext?.prompt,
|
|
32
|
+
}).provisionalTitle ?? null);
|
|
33
|
+
}
|
|
29
34
|
//# sourceMappingURL=create-agent-title.js.map
|
|
@@ -6,7 +6,6 @@ import { randomUUID } from "node:crypto";
|
|
|
6
6
|
import { hostname as getHostname } from "node:os";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
9
|
-
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
10
9
|
import { z } from "zod";
|
|
11
10
|
import { createBranchChangeRouteHandler } from "./script-route-branch-handler.js";
|
|
12
11
|
function resolveBoundListenTarget(listenTarget, httpServer) {
|
|
@@ -587,8 +586,7 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
587
586
|
let agentMcpBaseUrl = null;
|
|
588
587
|
if (mcpEnabled) {
|
|
589
588
|
const agentMcpRoute = "/mcp/agents";
|
|
590
|
-
const
|
|
591
|
-
const createAgentMcpTransport = async (callerAgentId) => {
|
|
589
|
+
const createAgentMcpSession = async (callerAgentId) => {
|
|
592
590
|
const agentMcpServer = await createAgentMcpServer({
|
|
593
591
|
agentManager,
|
|
594
592
|
agentStorage,
|
|
@@ -651,32 +649,24 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
651
649
|
resolveCallerContext: (agentId) => wsServer?.resolveVoiceCallerContext(agentId) ?? null,
|
|
652
650
|
logger,
|
|
653
651
|
});
|
|
652
|
+
// Stateless mode: each HTTP request builds a fresh server + transport that is
|
|
653
|
+
// torn down when the response closes, so no per-session state is retained between
|
|
654
|
+
// requests. The agent control plane only lists and calls tools, neither of which
|
|
655
|
+
// needs cross-request state, so sessions would only pin memory for the life of the
|
|
656
|
+
// daemon (agents that exit without a clean DELETE never get reaped).
|
|
654
657
|
const transport = new StreamableHTTPServerTransport({
|
|
655
|
-
sessionIdGenerator:
|
|
656
|
-
onsessioninitialized: (sessionId) => {
|
|
657
|
-
agentMcpTransports.set(sessionId, transport);
|
|
658
|
-
logger.debug({ sessionId }, "Agent MCP session initialized");
|
|
659
|
-
},
|
|
660
|
-
onsessionclosed: (sessionId) => {
|
|
661
|
-
agentMcpTransports.delete(sessionId);
|
|
662
|
-
logger.debug({ sessionId }, "Agent MCP session closed");
|
|
663
|
-
},
|
|
658
|
+
sessionIdGenerator: undefined,
|
|
664
659
|
// NOTE: We enforce a Vite-like host allowlist at the app/websocket layer.
|
|
665
660
|
// StreamableHTTPServerTransport's built-in check requires exact Host header matches.
|
|
666
661
|
enableDnsRebindingProtection: false,
|
|
667
662
|
});
|
|
668
663
|
Object.assign(transport, {
|
|
669
|
-
onclose: () => {
|
|
670
|
-
if (transport.sessionId) {
|
|
671
|
-
agentMcpTransports.delete(transport.sessionId);
|
|
672
|
-
}
|
|
673
|
-
},
|
|
674
664
|
onerror: (err) => {
|
|
675
665
|
logger.error({ err }, "Agent MCP transport error");
|
|
676
666
|
},
|
|
677
667
|
});
|
|
678
668
|
await agentMcpServer.connect(transport);
|
|
679
|
-
return transport;
|
|
669
|
+
return { server: agentMcpServer, transport };
|
|
680
670
|
};
|
|
681
671
|
const runAgentMcpRequest = async (req, res) => {
|
|
682
672
|
// This route is exempt from the global daemon-password middleware, so it
|
|
@@ -701,41 +691,33 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
701
691
|
}, "Agent MCP request");
|
|
702
692
|
}
|
|
703
693
|
try {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
code: -32000,
|
|
723
|
-
message: "Initialization request expected",
|
|
724
|
-
},
|
|
725
|
-
id: null,
|
|
726
|
-
});
|
|
727
|
-
return;
|
|
728
|
-
}
|
|
729
|
-
const callerAgentIdRaw = req.query.callerAgentId;
|
|
730
|
-
let callerAgentId;
|
|
731
|
-
if (typeof callerAgentIdRaw === "string") {
|
|
732
|
-
callerAgentId = callerAgentIdRaw;
|
|
733
|
-
}
|
|
734
|
-
else if (Array.isArray(callerAgentIdRaw) && typeof callerAgentIdRaw[0] === "string") {
|
|
735
|
-
callerAgentId = callerAgentIdRaw[0];
|
|
736
|
-
}
|
|
737
|
-
transport = await createAgentMcpTransport(callerAgentId);
|
|
694
|
+
// Stateless: GET (standalone SSE) and DELETE (session termination) have no
|
|
695
|
+
// meaning without sessions. The MCP client tolerates 405 on the GET stream
|
|
696
|
+
// and never issues a DELETE because it is never handed a session id.
|
|
697
|
+
if (req.method !== "POST") {
|
|
698
|
+
res.status(405).json({
|
|
699
|
+
jsonrpc: "2.0",
|
|
700
|
+
error: {
|
|
701
|
+
code: -32000,
|
|
702
|
+
message: "Method not allowed",
|
|
703
|
+
},
|
|
704
|
+
id: null,
|
|
705
|
+
});
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
const callerAgentIdRaw = req.query.callerAgentId;
|
|
709
|
+
let callerAgentId;
|
|
710
|
+
if (typeof callerAgentIdRaw === "string") {
|
|
711
|
+
callerAgentId = callerAgentIdRaw;
|
|
738
712
|
}
|
|
713
|
+
else if (Array.isArray(callerAgentIdRaw) && typeof callerAgentIdRaw[0] === "string") {
|
|
714
|
+
callerAgentId = callerAgentIdRaw[0];
|
|
715
|
+
}
|
|
716
|
+
const { server, transport } = await createAgentMcpSession(callerAgentId);
|
|
717
|
+
res.on("close", () => {
|
|
718
|
+
void transport.close();
|
|
719
|
+
void server.close();
|
|
720
|
+
});
|
|
739
721
|
await transport.handleRequest(req, res, req.body);
|
|
740
722
|
}
|
|
741
723
|
catch (err) {
|
|
@@ -5,6 +5,7 @@ import { createWorktreeCore, } from "./worktree-core.js";
|
|
|
5
5
|
import { validateBranchSlug } from "../utils/worktree.js";
|
|
6
6
|
import { getCurrentBranch, localBranchExists, renameCurrentBranch } from "../utils/checkout-git.js";
|
|
7
7
|
import { markPaseoWorktreeFirstAgentBranchAutoNameAttempted, readPaseoWorktreeMetadata, writePaseoWorktreeFirstAgentBranchAutoNameMetadata, } from "../utils/worktree-metadata.js";
|
|
8
|
+
import { resolveFirstAgentPromptTitle } from "./agent/create-agent-title.js";
|
|
8
9
|
import { buildAgentBranchNameSeed } from "./agent/prompt-attachments.js";
|
|
9
10
|
export async function createPaseoWorktree(input, deps) {
|
|
10
11
|
const createdWorktree = await createWorktreeCore(input, deps);
|
|
@@ -14,6 +15,7 @@ export async function createPaseoWorktree(input, deps) {
|
|
|
14
15
|
projectId: input.projectId,
|
|
15
16
|
repoRoot: createdWorktree.repoRoot,
|
|
16
17
|
worktree: createdWorktree.worktree,
|
|
18
|
+
title: resolveFirstAgentPromptTitle(input.firstAgentContext),
|
|
17
19
|
deps,
|
|
18
20
|
});
|
|
19
21
|
deps.github.invalidate({ cwd: createdWorktree.worktree.worktreePath });
|
|
@@ -140,6 +142,7 @@ async function upsertWorkspaceForWorktree(options) {
|
|
|
140
142
|
kind: "worktree",
|
|
141
143
|
displayName: options.worktree.branchName || normalizedCwd,
|
|
142
144
|
branch: options.worktree.branchName || null,
|
|
145
|
+
title: options.title ?? null,
|
|
143
146
|
createdAt: now,
|
|
144
147
|
updatedAt: now,
|
|
145
148
|
archivedAt: null,
|
|
@@ -345,6 +345,7 @@ export declare class Session {
|
|
|
345
345
|
private scheduleAutoNameWorkspaceBranchForFirstAgent;
|
|
346
346
|
private maybeAutoNameWorkspaceBranchForFirstAgent;
|
|
347
347
|
private applyGeneratedWorkspaceTitle;
|
|
348
|
+
private writeInitialWorkspaceTitleIfUntitled;
|
|
348
349
|
private generateWorkspaceTitleFromContext;
|
|
349
350
|
private maybeAutoNameDirectoryWorkspaceTitle;
|
|
350
351
|
private scheduleAutoNameLocalWorkspaceTitleForFirstAgent;
|
|
@@ -479,6 +480,7 @@ export declare class Session {
|
|
|
479
480
|
private bufferOrEmitWorkspaceUpdate;
|
|
480
481
|
private flushBootstrappedWorkspaceUpdates;
|
|
481
482
|
private findOrCreateWorkspaceForDirectory;
|
|
483
|
+
private resolveOrCreateWorkspaceIdForCreateAgent;
|
|
482
484
|
private createWorkspaceForDirectory;
|
|
483
485
|
private reclassifyOrUnarchiveWorkspaceForDirectory;
|
|
484
486
|
private resolveProjectRecordForPlacement;
|
|
@@ -23,7 +23,7 @@ import { createVoiceTurnController, } from "./voice/voice-turn-controller.js";
|
|
|
23
23
|
import { buildConfigOverrides, extractTimestamps, isStoredAgentProviderAvailable, toAgentPersistenceHandle, } from "./persistence-hooks.js";
|
|
24
24
|
import { ensureAgentLoaded } from "./agent/agent-loading.js";
|
|
25
25
|
import { formatSystemNotificationPrompt, sendPromptToAgent, waitForAgentRunStartWithTimeout, unarchiveAgentState, } from "./agent/agent-prompt.js";
|
|
26
|
-
import { resolveCreateAgentTitles } from "./agent/create-agent-title.js";
|
|
26
|
+
import { resolveCreateAgentTitles, resolveFirstAgentPromptTitle, } from "./agent/create-agent-title.js";
|
|
27
27
|
import { respondToAgentPermission } from "./agent/permission-response.js";
|
|
28
28
|
import { experimental_createMCPClient } from "ai";
|
|
29
29
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
@@ -2169,6 +2169,7 @@ export class Session {
|
|
|
2169
2169
|
...(trimmedPrompt ? { prompt: trimmedPrompt } : {}),
|
|
2170
2170
|
...(attachments && attachments.length > 0 ? { attachments } : {}),
|
|
2171
2171
|
};
|
|
2172
|
+
const workspacePromptTitle = resolveFirstAgentPromptTitle(firstAgentContext);
|
|
2172
2173
|
const createdWorktree = await this.createAgentLifecycleDispatch.createWorktreeForRequest({
|
|
2173
2174
|
cwd: config.cwd,
|
|
2174
2175
|
target: worktree,
|
|
@@ -2179,12 +2180,12 @@ export class Session {
|
|
|
2179
2180
|
const createAgentConfig = createdWorktree
|
|
2180
2181
|
? { ...config, cwd: createdWorktree.worktree.worktreePath }
|
|
2181
2182
|
: config;
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2183
|
+
const workspaceId = await this.resolveOrCreateWorkspaceIdForCreateAgent({
|
|
2184
|
+
createdWorktree,
|
|
2185
|
+
requestedWorkspaceId: msg.workspaceId,
|
|
2186
|
+
cwd: createAgentConfig.cwd,
|
|
2187
|
+
initialTitle: workspacePromptTitle,
|
|
2188
|
+
});
|
|
2188
2189
|
const { snapshot, liveSnapshot } = await createAgentCommand({
|
|
2189
2190
|
agentManager: this.agentManager,
|
|
2190
2191
|
agentStorage: this.agentStorage,
|
|
@@ -2210,6 +2211,9 @@ export class Session {
|
|
|
2210
2211
|
buildSessionConfig: (sessionConfig, gitOptions, legacyWorktreeName, ctx) => this.buildAgentSessionConfig(sessionConfig, gitOptions, legacyWorktreeName, ctx),
|
|
2211
2212
|
});
|
|
2212
2213
|
createdAgentId = snapshot.id;
|
|
2214
|
+
if (!createdWorktree && msg.workspaceId) {
|
|
2215
|
+
await this.writeInitialWorkspaceTitleIfUntitled(workspaceId, workspacePromptTitle);
|
|
2216
|
+
}
|
|
2213
2217
|
await this.forwardAgentUpdate(snapshot);
|
|
2214
2218
|
if (!createdWorktree && trimmedPrompt) {
|
|
2215
2219
|
await this.scheduleAutoNameLocalWorkspaceTitleForFirstAgent({
|
|
@@ -2585,26 +2589,43 @@ export class Session {
|
|
|
2585
2589
|
await this.applyGeneratedWorkspaceTitle(input.workspace.workspaceId, {
|
|
2586
2590
|
title: generatedTitle,
|
|
2587
2591
|
branch: result.branchName,
|
|
2592
|
+
promptTitle: resolveFirstAgentPromptTitle(input.firstAgentContext),
|
|
2588
2593
|
});
|
|
2589
2594
|
await this.notifyGitMutation(input.workspace.cwd, "rename-branch");
|
|
2590
2595
|
await this.emitWorkspaceUpdateForCwd(input.workspace.cwd);
|
|
2591
2596
|
}
|
|
2592
|
-
//
|
|
2593
|
-
//
|
|
2594
|
-
// registry so concurrent upserts that happened after workspace creation are
|
|
2595
|
-
// not clobbered, while still persisting branch metadata from the rename path.
|
|
2597
|
+
// Generated names may replace the prompt title set at creation, but not a user
|
|
2598
|
+
// rename that landed while the async generator was running.
|
|
2596
2599
|
async applyGeneratedWorkspaceTitle(workspaceId, input) {
|
|
2597
2600
|
const current = await this.workspaceRegistry.get(workspaceId);
|
|
2598
2601
|
if (!current) {
|
|
2599
2602
|
return;
|
|
2600
2603
|
}
|
|
2604
|
+
let title = current.title;
|
|
2605
|
+
if (!title || (input.promptTitle && title === input.promptTitle)) {
|
|
2606
|
+
title = input.title;
|
|
2607
|
+
}
|
|
2601
2608
|
await this.workspaceRegistry.upsert({
|
|
2602
2609
|
...current,
|
|
2603
|
-
title
|
|
2610
|
+
title,
|
|
2604
2611
|
...(input.branch ? { branch: input.branch } : {}),
|
|
2605
2612
|
updatedAt: new Date().toISOString(),
|
|
2606
2613
|
});
|
|
2607
2614
|
}
|
|
2615
|
+
async writeInitialWorkspaceTitleIfUntitled(workspaceId, title) {
|
|
2616
|
+
if (!title) {
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
const current = await this.workspaceRegistry.get(workspaceId);
|
|
2620
|
+
if (!current || current.title) {
|
|
2621
|
+
return;
|
|
2622
|
+
}
|
|
2623
|
+
await this.workspaceRegistry.upsert({
|
|
2624
|
+
...current,
|
|
2625
|
+
title,
|
|
2626
|
+
updatedAt: new Date().toISOString(),
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2608
2629
|
// Wraps the injected workspace-name generator for a directory workspace.
|
|
2609
2630
|
async generateWorkspaceTitleFromContext(input) {
|
|
2610
2631
|
return this.generateWorkspaceName({
|
|
@@ -2619,8 +2640,7 @@ export class Session {
|
|
|
2619
2640
|
});
|
|
2620
2641
|
}
|
|
2621
2642
|
// Generates a human title for a directory workspace from the firstAgentContext
|
|
2622
|
-
// prompt
|
|
2623
|
-
// have no worktree git state.
|
|
2643
|
+
// prompt. No branch rename — directory workspaces have no worktree git state.
|
|
2624
2644
|
// TODO(K7): same-dir directory-workspace display disambiguation not yet implemented.
|
|
2625
2645
|
async maybeAutoNameDirectoryWorkspaceTitle(input) {
|
|
2626
2646
|
const generated = await this.generateWorkspaceTitleFromContext({
|
|
@@ -2633,7 +2653,10 @@ export class Session {
|
|
|
2633
2653
|
}
|
|
2634
2654
|
// K4: applyGeneratedWorkspaceTitle re-reads from the registry before writing.
|
|
2635
2655
|
// Directory workspaces have no branch — write only the title.
|
|
2636
|
-
await this.applyGeneratedWorkspaceTitle(input.workspaceId, {
|
|
2656
|
+
await this.applyGeneratedWorkspaceTitle(input.workspaceId, {
|
|
2657
|
+
title,
|
|
2658
|
+
promptTitle: resolveFirstAgentPromptTitle(input.firstAgentContext),
|
|
2659
|
+
});
|
|
2637
2660
|
await this.emitWorkspaceUpdateForWorkspaceId(input.workspaceId);
|
|
2638
2661
|
}
|
|
2639
2662
|
async scheduleAutoNameLocalWorkspaceTitleForFirstAgent(input) {
|
|
@@ -3011,8 +3034,13 @@ export class Session {
|
|
|
3011
3034
|
const prompt = await buildMetadataPrompt({
|
|
3012
3035
|
cwd,
|
|
3013
3036
|
workspaceGitService: this.workspaceGitService,
|
|
3014
|
-
|
|
3015
|
-
|
|
3037
|
+
contract: "Write a concise git commit message for the changes below.",
|
|
3038
|
+
styles: [
|
|
3039
|
+
{
|
|
3040
|
+
configKey: "commitMessage",
|
|
3041
|
+
default: "Concise, imperative mood, no trailing period.",
|
|
3042
|
+
},
|
|
3043
|
+
],
|
|
3016
3044
|
after: [
|
|
3017
3045
|
"Return JSON only with a single field 'message'.",
|
|
3018
3046
|
"",
|
|
@@ -3079,8 +3107,13 @@ export class Session {
|
|
|
3079
3107
|
const prompt = await buildMetadataPrompt({
|
|
3080
3108
|
cwd,
|
|
3081
3109
|
workspaceGitService: this.workspaceGitService,
|
|
3082
|
-
|
|
3083
|
-
|
|
3110
|
+
contract: "Write a pull request title and body for the changes below.",
|
|
3111
|
+
styles: [
|
|
3112
|
+
{
|
|
3113
|
+
configKey: "pullRequest",
|
|
3114
|
+
default: "Clear, descriptive title; body explaining what changed and why.",
|
|
3115
|
+
},
|
|
3116
|
+
],
|
|
3084
3117
|
after: [
|
|
3085
3118
|
"Return JSON only with fields 'title' and 'body'.",
|
|
3086
3119
|
"",
|
|
@@ -5221,7 +5254,16 @@ export class Session {
|
|
|
5221
5254
|
}
|
|
5222
5255
|
return this.createWorkspaceForDirectory(normalizedCwd);
|
|
5223
5256
|
}
|
|
5224
|
-
async
|
|
5257
|
+
async resolveOrCreateWorkspaceIdForCreateAgent(input) {
|
|
5258
|
+
if (input.createdWorktree) {
|
|
5259
|
+
return input.createdWorktree.workspace.workspaceId;
|
|
5260
|
+
}
|
|
5261
|
+
if (input.requestedWorkspaceId) {
|
|
5262
|
+
return input.requestedWorkspaceId;
|
|
5263
|
+
}
|
|
5264
|
+
return (await this.createWorkspaceForDirectory(input.cwd, input.initialTitle)).workspaceId;
|
|
5265
|
+
}
|
|
5266
|
+
async createWorkspaceForDirectory(cwd, title) {
|
|
5225
5267
|
const checkout = await this.workspaceGitService.getCheckout(cwd);
|
|
5226
5268
|
const membership = classifyDirectoryForProjectMembership({ cwd, checkout });
|
|
5227
5269
|
const timestamp = new Date().toISOString();
|
|
@@ -5236,6 +5278,7 @@ export class Session {
|
|
|
5236
5278
|
cwd,
|
|
5237
5279
|
kind: membership.workspaceKind,
|
|
5238
5280
|
displayName: membership.workspaceDisplayName,
|
|
5281
|
+
title: title ?? null,
|
|
5239
5282
|
createdAt: timestamp,
|
|
5240
5283
|
updatedAt: timestamp,
|
|
5241
5284
|
});
|
|
@@ -5765,7 +5808,9 @@ export class Session {
|
|
|
5765
5808
|
});
|
|
5766
5809
|
return;
|
|
5767
5810
|
}
|
|
5768
|
-
const
|
|
5811
|
+
const explicitTitle = request.title?.trim() || null;
|
|
5812
|
+
const promptTitle = resolveFirstAgentPromptTitle(request.firstAgentContext);
|
|
5813
|
+
const workspace = await createLocalCheckoutWorkspace({ cwd, title: explicitTitle ?? promptTitle }, {
|
|
5769
5814
|
projectRegistry: this.projectRegistry,
|
|
5770
5815
|
workspaceRegistry: this.workspaceRegistry,
|
|
5771
5816
|
workspaceGitService: this.workspaceGitService,
|
|
@@ -173,9 +173,9 @@ export function createPersistedWorkspaceRecord(input) {
|
|
|
173
173
|
archivedAt: input.archivedAt ?? null,
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
|
-
// The single workspace-name rule: the
|
|
177
|
-
//
|
|
178
|
-
//
|
|
176
|
+
// The single workspace-name rule: the title always wins; otherwise fall back to
|
|
177
|
+
// the freshest available derived display name (a live branch snapshot when the
|
|
178
|
+
// caller has one, the persisted displayName otherwise).
|
|
179
179
|
export function resolveWorkspaceName(input) {
|
|
180
180
|
return input.title ?? input.derivedDisplayName;
|
|
181
181
|
}
|
|
@@ -11,14 +11,30 @@ async function buildPrompt(seed, options) {
|
|
|
11
11
|
return buildMetadataPrompt({
|
|
12
12
|
cwd: options.cwd,
|
|
13
13
|
workspaceGitService: options.workspaceGitService,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
"Title: a short human-readable sentence-case label for the task (no slug rules, max 80 characters).",
|
|
18
|
-
"Branch: concise lowercase slug using letters, numbers, hyphens, and slashes only.",
|
|
19
|
-
"No spaces, no uppercase, no leading or trailing hyphen, no consecutive hyphens.",
|
|
14
|
+
contract: [
|
|
15
|
+
"Generate a title and a git branch name for a coding agent from the user prompt and attachments.",
|
|
16
|
+
"The branch must be a valid git ref: lowercase letters, numbers, hyphens, and slashes only, with no spaces, no uppercase, no leading or trailing hyphen, and no consecutive hyphens.",
|
|
20
17
|
"The branch is generated directly from the prompt — it is NEVER derived from or slugified from the title.",
|
|
21
18
|
].join("\n"),
|
|
19
|
+
styles: [
|
|
20
|
+
{
|
|
21
|
+
configKey: "title",
|
|
22
|
+
label: "Title style",
|
|
23
|
+
default: [
|
|
24
|
+
"A terse, task-shaped label naming what the task is about (sentence case, max 80 characters).",
|
|
25
|
+
"Aim for about 4 words. Go longer only when the task genuinely needs it; most titles must stay short.",
|
|
26
|
+
"Do not start with a generic 'do' verb (Fix, Add, Implement, Diagnose, Update, Change, Create, Set, Make) — every task is implicitly one of these, so the verb is noise. Name the thing instead.",
|
|
27
|
+
"Keep a verb only when it states the specific operation (Swap, Split, Extract, Rename, Merge, Inline).",
|
|
28
|
+
'Good titles: "Swap sidebar history icon", "Composer keyboard shift", "Agent auto-titling", "Worktree selection memory", "Split browser pane".',
|
|
29
|
+
'Bad titles: "Fix composer pushed up by keyboard in workspace", "Diagnose auto-titling still happening for agents", "Change sidebar history icon from clock to history icon".',
|
|
30
|
+
].join("\n"),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
configKey: "branchName",
|
|
34
|
+
label: "Branch style",
|
|
35
|
+
default: "A short, descriptive slug — a few lowercase words joined by hyphens.",
|
|
36
|
+
},
|
|
37
|
+
],
|
|
22
38
|
after: "Return JSON only with fields 'title' and 'branch'.",
|
|
23
39
|
trailing: `User context:\n${seed}`,
|
|
24
40
|
});
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
export type MetadataConfigKey = "branchName" | "commitMessage" | "pullRequest";
|
|
1
|
+
export type MetadataConfigKey = "title" | "branchName" | "commitMessage" | "pullRequest";
|
|
2
2
|
export interface RepoRootResolver {
|
|
3
3
|
resolveRepoRoot: (cwd: string) => Promise<string>;
|
|
4
4
|
}
|
|
5
|
+
export interface MetadataStyleSection {
|
|
6
|
+
configKey: MetadataConfigKey;
|
|
7
|
+
default: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
}
|
|
5
10
|
export interface BuildMetadataPromptOptions {
|
|
6
11
|
cwd: string;
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
contract: string;
|
|
13
|
+
styles: MetadataStyleSection[];
|
|
9
14
|
after: string;
|
|
10
15
|
trailing?: string;
|
|
11
16
|
workspaceGitService?: RepoRootResolver;
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import { readPaseoConfigJson } from "./paseo-config-file.js";
|
|
2
|
-
import { PaseoConfigSchema } from "@getpaseo/protocol/paseo-config-schema";
|
|
3
|
-
import { wrapWithUserInstructions } from "./wrap-user-instructions.js";
|
|
2
|
+
import { PaseoConfigSchema, } from "@getpaseo/protocol/paseo-config-schema";
|
|
4
3
|
export async function buildMetadataPrompt(options) {
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
: `${options.before}\n${options.after}`;
|
|
4
|
+
const overrides = await readProjectMetadataOverrides(options);
|
|
5
|
+
const styleBlocks = options.styles.map((section) => renderStyleSection(section, overrides?.[section.configKey]?.instructions));
|
|
6
|
+
const head = [options.contract, ...styleBlocks, options.after].join("\n\n");
|
|
9
7
|
return options.trailing ? `${head}\n\n${options.trailing}` : head;
|
|
10
8
|
}
|
|
11
|
-
|
|
9
|
+
function renderStyleSection(section, override) {
|
|
10
|
+
const body = isNonEmptyString(override) ? override.trim() : section.default;
|
|
11
|
+
return section.label ? `${section.label}:\n${body}` : body;
|
|
12
|
+
}
|
|
13
|
+
async function readProjectMetadataOverrides(options) {
|
|
12
14
|
if (!options.workspaceGitService) {
|
|
13
15
|
return undefined;
|
|
14
16
|
}
|
|
15
17
|
try {
|
|
16
18
|
const repoRoot = await options.workspaceGitService.resolveRepoRoot(options.cwd);
|
|
17
19
|
const json = readPaseoConfigJson(repoRoot);
|
|
18
|
-
|
|
19
|
-
return config.metadataGeneration?.[options.configKey]?.instructions;
|
|
20
|
+
return PaseoConfigSchema.parse(json).metadataGeneration;
|
|
20
21
|
}
|
|
21
22
|
catch {
|
|
22
23
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getpaseo/server",
|
|
3
|
-
"version": "0.1.97-beta.
|
|
3
|
+
"version": "0.1.97-beta.2",
|
|
4
4
|
"description": "Paseo backend server",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist/server",
|
|
@@ -64,10 +64,10 @@
|
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@agentclientprotocol/sdk": "^0.17.1",
|
|
66
66
|
"@anthropic-ai/claude-agent-sdk": "^0.2.133",
|
|
67
|
-
"@getpaseo/client": "0.1.97-beta.
|
|
68
|
-
"@getpaseo/highlight": "0.1.97-beta.
|
|
69
|
-
"@getpaseo/protocol": "0.1.97-beta.
|
|
70
|
-
"@getpaseo/relay": "0.1.97-beta.
|
|
67
|
+
"@getpaseo/client": "0.1.97-beta.2",
|
|
68
|
+
"@getpaseo/highlight": "0.1.97-beta.2",
|
|
69
|
+
"@getpaseo/protocol": "0.1.97-beta.2",
|
|
70
|
+
"@getpaseo/relay": "0.1.97-beta.2",
|
|
71
71
|
"@isaacs/ttlcache": "^2.1.4",
|
|
72
72
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
|
73
73
|
"@opencode-ai/sdk": "1.14.46",
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
const USER_INSTRUCTIONS_NOTICE = "The instructions below are provided by the project owner and override the guidelines above where they conflict.";
|
|
2
|
-
export function wrapWithUserInstructions(beforeBlock, instructions, afterBlock) {
|
|
3
|
-
return `${beforeBlock}
|
|
4
|
-
|
|
5
|
-
<user-instructions>
|
|
6
|
-
${USER_INSTRUCTIONS_NOTICE}
|
|
7
|
-
|
|
8
|
-
${instructions}
|
|
9
|
-
</user-instructions>
|
|
10
|
-
|
|
11
|
-
${afterBlock}`;
|
|
12
|
-
}
|
|
13
|
-
//# sourceMappingURL=wrap-user-instructions.js.map
|