@nseng-ai/ccc 0.1.1
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/package.json +48 -0
- package/src/api/handlers.ts +75 -0
- package/src/api/index.ts +11 -0
- package/src/cmux/branch-slug.ts +174 -0
- package/src/cmux/claude-plan-tab.ts +124 -0
- package/src/cmux/command-surfaces.ts +51 -0
- package/src/cmux/dispatch-from-trunk.ts +177 -0
- package/src/cmux/dispatch-prompt.ts +459 -0
- package/src/cmux/objective-sidebar.ts +373 -0
- package/src/cmux/prompt-file.ts +34 -0
- package/src/cmux/sidebar.ts +386 -0
- package/src/cmux/slot-checkout.ts +46 -0
- package/src/cmux/slot-dispatch-plan.ts +560 -0
- package/src/cmux/slot-open-branch.ts +241 -0
- package/src/cmux/slot.ts +126 -0
- package/src/cmux/workspace-summary.ts +222 -0
- package/src/cmux/worktree-description.ts +87 -0
- package/src/ns/autobranch/checkpoint.ts +35 -0
- package/src/ns/autobranch/flow.ts +18 -0
- package/src/ns/autoslot-presentation.ts +33 -0
- package/src/ns/autoslot.ts +2 -0
- package/src/ns/cli-command-io.ts +23 -0
- package/src/ns/cli.ts +186 -0
- package/src/ns/land.ts +12 -0
- package/src/ns/trunk-pull.ts +2 -0
- package/src/pi/claude-plan-tab.ts +32 -0
- package/src/pi/command-backed-skills.ts +7 -0
- package/src/pi/dispatch-from-trunk.ts +41 -0
- package/src/pi/dispatch-prompt.ts +41 -0
- package/src/pi/extension.ts +23 -0
- package/src/pi/index.ts +11 -0
- package/src/pi/sidebar.ts +58 -0
- package/src/pi/slot-dispatch-plan.ts +70 -0
- package/src/pi/slot-open-branch.ts +39 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { DEFAULT_FAST_MODEL_REF, resolveModelRef } from "@nseng-ai/foundation/model-slug";
|
|
2
|
+
import {
|
|
3
|
+
chooseActiveObjectiveSlug,
|
|
4
|
+
objectiveSelectionContextFromCommandContext,
|
|
5
|
+
type ObjectiveSelectionSpec,
|
|
6
|
+
} from "@nseng-ai/objectives/api";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
CCC_SIDEBAR_BRANCH_STATE_SUMMARY_COMMAND_NAME,
|
|
10
|
+
CCC_SIDEBAR_SESSION_SUMMARY_COMMAND_NAME,
|
|
11
|
+
} from "./command-surfaces.ts";
|
|
12
|
+
import {
|
|
13
|
+
applyObjectiveSidebarFields,
|
|
14
|
+
formatObjectiveSidebarFields,
|
|
15
|
+
readCurrentBranchSlug,
|
|
16
|
+
resolveObjectiveSelector,
|
|
17
|
+
validateObjectiveSidebarSlug,
|
|
18
|
+
slotSlugFromCwd,
|
|
19
|
+
} from "./objective-sidebar.ts";
|
|
20
|
+
import { formatErrorMessage } from "@nseng-ai/foundation/primitives";
|
|
21
|
+
import type {
|
|
22
|
+
AgentEndContext,
|
|
23
|
+
CommandContext,
|
|
24
|
+
ExtensionAPI,
|
|
25
|
+
ModelInfo,
|
|
26
|
+
NotifyLevel,
|
|
27
|
+
ThinkingLevel,
|
|
28
|
+
} from "@nseng-ai/capability-kit/cmux/types";
|
|
29
|
+
|
|
30
|
+
const SKILL_NAME = "ccc-sidebar";
|
|
31
|
+
const PI_SIDEBAR_STATUS_KEY = "pi:ccc-sidebar";
|
|
32
|
+
const SIDEBAR_MODEL_ENV = "NS_CCC_SIDEBAR_MODEL";
|
|
33
|
+
const OBJECTIVE_SIDEBAR_SELECTION_SPEC = {
|
|
34
|
+
statusKey: PI_SIDEBAR_STATUS_KEY,
|
|
35
|
+
selectionTitle: "Select an active Objective for cmux sidebar",
|
|
36
|
+
shouldCompactDiffSuggestion: true,
|
|
37
|
+
} satisfies ObjectiveSelectionSpec;
|
|
38
|
+
|
|
39
|
+
interface RestoreState {
|
|
40
|
+
model?: ModelInfo;
|
|
41
|
+
thinkingLevel: ThinkingLevel;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface CccSidebarController {
|
|
45
|
+
handleSessionCommand(ctx: CommandContext): Promise<void>;
|
|
46
|
+
handleBranchStateCommand(ctx: CommandContext): Promise<void>;
|
|
47
|
+
handleObjectiveCommand(args: string, ctx: CommandContext): Promise<void>;
|
|
48
|
+
onAgentEnd: (event: unknown, ctx: AgentEndContext) => Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ObjectiveSidebarHandlerOptions {
|
|
52
|
+
expandSkillBlock: (cwd: string, skillName: string) => Promise<{ block: string | undefined }>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createCccSidebarController(
|
|
56
|
+
pi: ExtensionAPI,
|
|
57
|
+
objectiveSidebarOptions: ObjectiveSidebarHandlerOptions,
|
|
58
|
+
): CccSidebarController {
|
|
59
|
+
let pendingRestore: RestoreState | undefined;
|
|
60
|
+
|
|
61
|
+
const onAgentEnd = async (_event: unknown, ctx: AgentEndContext): Promise<void> => {
|
|
62
|
+
if (pendingRestore === undefined) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const restoreState = pendingRestore;
|
|
66
|
+
pendingRestore = undefined;
|
|
67
|
+
await restoreModelState(pi, ctx, restoreState);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
async handleSessionCommand(ctx): Promise<void> {
|
|
72
|
+
await queueSessionSidebar(pi, ctx, objectiveSidebarOptions, (state) => {
|
|
73
|
+
pendingRestore = state;
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
async handleBranchStateCommand(ctx): Promise<void> {
|
|
78
|
+
await queueBranchStateSidebar(pi, ctx, objectiveSidebarOptions, (state) => {
|
|
79
|
+
pendingRestore = state;
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
async handleObjectiveCommand(args, ctx): Promise<void> {
|
|
84
|
+
await handleDeterministicObjectiveSidebar(pi, args, ctx, objectiveSidebarOptions);
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
onAgentEnd,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function getCallerWorkspaceId(env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
92
|
+
const value = env.CMUX_WORKSPACE_ID ?? env.CMUX_TAB_ID;
|
|
93
|
+
const trimmed = value?.trim();
|
|
94
|
+
return trimmed && trimmed.length > 0 ? trimmed : undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function buildCmuxSessionSidebarPrompt(
|
|
98
|
+
skillBlock: string | undefined,
|
|
99
|
+
workspaceId: string,
|
|
100
|
+
): string {
|
|
101
|
+
return `${skillBlock ?? buildFallbackSessionSkillPrompt()}
|
|
102
|
+
|
|
103
|
+
Run the cmux session sidebar workflow now for the caller workspace.
|
|
104
|
+
|
|
105
|
+
Target workspace id/ref from this terminal environment: ${workspaceId}
|
|
106
|
+
|
|
107
|
+
Requested command: ${CCC_SIDEBAR_SESSION_SUMMARY_COMMAND_NAME}.
|
|
108
|
+
Summarize this Pi session's current task, progress, and likely next action.
|
|
109
|
+
The title must be exactly summary:<slug>, where <slug> is a concise lowercase hyphen slug for the session topic and the full title is max 45 chars.
|
|
110
|
+
The Goal line should describe what this session is trying to accomplish, not the cmux update itself.
|
|
111
|
+
|
|
112
|
+
Use the active Pi conversation context already available to you. Do not include this control prompt as the subject of the sidebar update. Generate compact title and description fields, apply the update with the ccc exec command when the source is resolved, then report the applied title briefly.`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function buildCmuxBranchStateSidebarPrompt(
|
|
116
|
+
skillBlock: string | undefined,
|
|
117
|
+
workspaceId: string,
|
|
118
|
+
): string {
|
|
119
|
+
return `${skillBlock ?? buildFallbackBranchStateSkillPrompt()}
|
|
120
|
+
|
|
121
|
+
Run the cmux branch-state sidebar workflow now for the caller workspace.
|
|
122
|
+
|
|
123
|
+
Target workspace id/ref from this terminal environment: ${workspaceId}
|
|
124
|
+
|
|
125
|
+
Requested command: ${CCC_SIDEBAR_BRANCH_STATE_SUMMARY_COMMAND_NAME}.
|
|
126
|
+
Summarize the current Git branch's implementation state relative to its parent branch.
|
|
127
|
+
Use read-only repository evidence: current branch, parent branch, porcelain status, branch-local commits, and a compact diffstat or short diff summary versus the parent. Prefer Graphite parent evidence when available, such as \`gt parent --no-interactive\`; if Graphite parent evidence is unavailable, explain the fallback basis tersely and use the best Git merge-base/upstream evidence you can resolve.
|
|
128
|
+
The title must be exactly state:<slug>, where <slug> is a concise lowercase hyphen slug for the branch topic and the full title is max 45 chars.
|
|
129
|
+
The State line should describe what the branch currently changes or needs next relative to its parent, not the cmux update itself.
|
|
130
|
+
|
|
131
|
+
Do not include this control prompt as the subject of the sidebar update. Run only read-only Git/Graphite inspection before applying the cmux update. Generate compact title and description fields, apply the update with the ccc exec command when the branch state is resolved, then report the applied title briefly.`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildFallbackSessionSkillPrompt(): string {
|
|
135
|
+
return `The ccc-sidebar skill was not found. Update the caller cmux workspace title and one-line Goal description for this Pi session using exactly one deterministic command. The title must be exactly summary:<slug>, where <slug> is a concise lowercase hyphen slug:
|
|
136
|
+
|
|
137
|
+
\`\`\`bash
|
|
138
|
+
ccc exec cmux-workspace-summary \\
|
|
139
|
+
--title 'summary:<slug>' \\
|
|
140
|
+
--description 'Goal: ...' \\
|
|
141
|
+
--format json
|
|
142
|
+
\`\`\`
|
|
143
|
+
|
|
144
|
+
The command clears the old cmux status pill. Do not assign shell variables. Do not pass --workspace. Do not run raw cmux commands.`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function buildFallbackBranchStateSkillPrompt(): string {
|
|
148
|
+
return `The ccc-sidebar skill was not found. Update the caller cmux workspace title and one-line State description for the current branch relative to its parent using exactly one deterministic apply command after read-only Git/Graphite inspection. The title must be exactly state:<slug>, where <slug> is a concise lowercase hyphen slug:
|
|
149
|
+
|
|
150
|
+
\`\`\`bash
|
|
151
|
+
ccc exec cmux-workspace-summary \\
|
|
152
|
+
--title 'state:<slug>' \\
|
|
153
|
+
--description 'State: ...' \\
|
|
154
|
+
--format json
|
|
155
|
+
\`\`\`
|
|
156
|
+
|
|
157
|
+
The command clears the old cmux status pill. Do not assign shell variables. Do not pass --workspace. Do not run raw cmux commands.`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function handleDeterministicObjectiveSidebar(
|
|
161
|
+
pi: ExtensionAPI,
|
|
162
|
+
args: string,
|
|
163
|
+
ctx: CommandContext,
|
|
164
|
+
_options: ObjectiveSidebarHandlerOptions,
|
|
165
|
+
): Promise<void> {
|
|
166
|
+
await ctx.waitForIdle();
|
|
167
|
+
|
|
168
|
+
const workspaceId = getCallerWorkspaceId();
|
|
169
|
+
if (!workspaceId) {
|
|
170
|
+
notify(ctx, "Not running inside a cmux caller workspace.", "warning");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const slug = await resolveObjectiveSidebarSlug(pi, args, ctx);
|
|
175
|
+
if (slug === undefined) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
setStatus(ctx, "preparing cmux Objective sidebar…");
|
|
180
|
+
try {
|
|
181
|
+
const validationResult = await validateObjectiveSidebarSlug(pi, ctx.cwd, slug);
|
|
182
|
+
if (validationResult.type === "failed") {
|
|
183
|
+
notify(ctx, validationResult.message, "error");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const branchResult = await readCurrentBranchSlug(pi, ctx.cwd);
|
|
188
|
+
if (branchResult.type === "failed") {
|
|
189
|
+
notify(ctx, branchResult.message, "error");
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const fields = formatObjectiveSidebarFields({
|
|
194
|
+
objectiveSlug: slug,
|
|
195
|
+
slotSlug: slotSlugFromCwd(ctx.cwd),
|
|
196
|
+
branchSlug: branchResult.branchSlug,
|
|
197
|
+
});
|
|
198
|
+
const applyResult = await applyObjectiveSidebarFields(pi, ctx.cwd, fields);
|
|
199
|
+
if (applyResult.type === "failed") {
|
|
200
|
+
notify(ctx, applyResult.message, "error");
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
notify(ctx, `Applied cmux Objective sidebar: ${fields.title}`, "success");
|
|
205
|
+
} finally {
|
|
206
|
+
setStatus(ctx, undefined);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function resolveObjectiveSidebarSlug(
|
|
211
|
+
pi: ExtensionAPI,
|
|
212
|
+
args: string,
|
|
213
|
+
ctx: CommandContext,
|
|
214
|
+
): Promise<string | undefined> {
|
|
215
|
+
if (args.trim().length > 0) {
|
|
216
|
+
const selector = resolveObjectiveSelector(args, ctx.cwd);
|
|
217
|
+
if (selector.type === "invalid") {
|
|
218
|
+
notify(ctx, selector.message, "warning");
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
return selector.slug;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (ctx.hasUI !== true || ctx.ui.select === undefined) {
|
|
225
|
+
notify(ctx, "Pass an Objective slug or .ns/objectives/<slug> path.", "warning");
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return chooseActiveObjectiveSlug(
|
|
230
|
+
pi,
|
|
231
|
+
objectiveSelectionContextFromCommandContext(ctx),
|
|
232
|
+
OBJECTIVE_SIDEBAR_SELECTION_SPEC,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function queueSessionSidebar(
|
|
237
|
+
pi: ExtensionAPI,
|
|
238
|
+
ctx: CommandContext,
|
|
239
|
+
options: ObjectiveSidebarHandlerOptions,
|
|
240
|
+
setPendingRestore: (state: RestoreState) => void,
|
|
241
|
+
): Promise<void> {
|
|
242
|
+
await queueModelAssistedSidebar(pi, ctx, options, setPendingRestore, {
|
|
243
|
+
status: "preparing cmux sidebar…",
|
|
244
|
+
successMessage: "Invoking cmux session sidebar summary.",
|
|
245
|
+
fallbackMessage: "cmux sidebar skill not found; using fallback prompt.",
|
|
246
|
+
buildPrompt: buildCmuxSessionSidebarPrompt,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function queueBranchStateSidebar(
|
|
251
|
+
pi: ExtensionAPI,
|
|
252
|
+
ctx: CommandContext,
|
|
253
|
+
options: ObjectiveSidebarHandlerOptions,
|
|
254
|
+
setPendingRestore: (state: RestoreState) => void,
|
|
255
|
+
): Promise<void> {
|
|
256
|
+
await queueModelAssistedSidebar(pi, ctx, options, setPendingRestore, {
|
|
257
|
+
status: "preparing cmux branch-state sidebar…",
|
|
258
|
+
successMessage: "Invoking cmux branch-state sidebar summary.",
|
|
259
|
+
fallbackMessage: "cmux sidebar skill not found; using branch-state fallback prompt.",
|
|
260
|
+
buildPrompt: buildCmuxBranchStateSidebarPrompt,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function queueModelAssistedSidebar(
|
|
265
|
+
pi: ExtensionAPI,
|
|
266
|
+
ctx: CommandContext,
|
|
267
|
+
sidebarOptions: ObjectiveSidebarHandlerOptions,
|
|
268
|
+
setPendingRestore: (state: RestoreState) => void,
|
|
269
|
+
options: {
|
|
270
|
+
status: string;
|
|
271
|
+
successMessage: string;
|
|
272
|
+
fallbackMessage: string;
|
|
273
|
+
buildPrompt(skillBlock: string | undefined, workspaceId: string): string;
|
|
274
|
+
},
|
|
275
|
+
): Promise<void> {
|
|
276
|
+
await ctx.waitForIdle();
|
|
277
|
+
|
|
278
|
+
const workspaceId = getCallerWorkspaceId();
|
|
279
|
+
if (!workspaceId) {
|
|
280
|
+
notify(ctx, "Not running inside a cmux caller workspace.", "warning");
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
setStatus(ctx, options.status);
|
|
285
|
+
let restoreState: RestoreState | undefined;
|
|
286
|
+
try {
|
|
287
|
+
const skillBlock = await expandSidebarSkillBlock(ctx, sidebarOptions);
|
|
288
|
+
restoreState = await switchToFastSidebarModel(pi, ctx);
|
|
289
|
+
if (restoreState !== undefined) {
|
|
290
|
+
setPendingRestore(restoreState);
|
|
291
|
+
}
|
|
292
|
+
notify(
|
|
293
|
+
ctx,
|
|
294
|
+
skillBlock ? options.successMessage : options.fallbackMessage,
|
|
295
|
+
skillBlock ? "info" : "warning",
|
|
296
|
+
);
|
|
297
|
+
pi.sendUserMessage(options.buildPrompt(skillBlock, workspaceId));
|
|
298
|
+
} catch (error) {
|
|
299
|
+
if (restoreState !== undefined) {
|
|
300
|
+
await restoreModelState(pi, ctx, restoreState);
|
|
301
|
+
}
|
|
302
|
+
notify(ctx, `Could not queue cmux sidebar update: ${formatErrorMessage(error)}`, "warning");
|
|
303
|
+
} finally {
|
|
304
|
+
setStatus(ctx, undefined);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function expandSidebarSkillBlock(
|
|
309
|
+
ctx: CommandContext,
|
|
310
|
+
options: ObjectiveSidebarHandlerOptions,
|
|
311
|
+
): Promise<string | undefined> {
|
|
312
|
+
try {
|
|
313
|
+
return (await options.expandSkillBlock(ctx.cwd, SKILL_NAME)).block;
|
|
314
|
+
} catch (error) {
|
|
315
|
+
notify(
|
|
316
|
+
ctx,
|
|
317
|
+
`Could not read cmux sidebar skill; using fallback prompt: ${formatErrorMessage(error)}`,
|
|
318
|
+
"warning",
|
|
319
|
+
);
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function switchToFastSidebarModel(
|
|
325
|
+
pi: ExtensionAPI,
|
|
326
|
+
ctx: CommandContext,
|
|
327
|
+
): Promise<RestoreState | undefined> {
|
|
328
|
+
const resolution = resolveModelRef(process.env, SIDEBAR_MODEL_ENV, DEFAULT_FAST_MODEL_REF);
|
|
329
|
+
if (!resolution.ok) {
|
|
330
|
+
notify(ctx, `${resolution.error} Using current model.`, "warning");
|
|
331
|
+
return undefined;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const { provider, modelId } = resolution.value;
|
|
335
|
+
const modelRef = `${provider}/${modelId}`;
|
|
336
|
+
const model = ctx.modelRegistry.find(provider, modelId);
|
|
337
|
+
if (model === undefined) {
|
|
338
|
+
notify(ctx, `Fast sidebar model ${modelRef} not found; using current model.`, "warning");
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const restoreState: RestoreState = {
|
|
343
|
+
thinkingLevel: pi.getThinkingLevel(),
|
|
344
|
+
};
|
|
345
|
+
if (ctx.model !== undefined) {
|
|
346
|
+
restoreState.model = ctx.model;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const switched = await pi.setModel(model);
|
|
350
|
+
if (!switched) {
|
|
351
|
+
notify(ctx, `Fast sidebar model ${modelRef} is unavailable; using current model.`, "warning");
|
|
352
|
+
return undefined;
|
|
353
|
+
}
|
|
354
|
+
pi.setThinkingLevel("minimal");
|
|
355
|
+
return restoreState;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function restoreModelState(
|
|
359
|
+
pi: ExtensionAPI,
|
|
360
|
+
ctx: AgentEndContext,
|
|
361
|
+
restoreState: RestoreState,
|
|
362
|
+
): Promise<void> {
|
|
363
|
+
if (restoreState.model !== undefined) {
|
|
364
|
+
const restored = await pi.setModel(restoreState.model);
|
|
365
|
+
if (!restored) {
|
|
366
|
+
notify(ctx, "Could not restore the previous model after cmux sidebar update.", "warning");
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
pi.setThinkingLevel(restoreState.thinkingLevel);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function notify(
|
|
373
|
+
ctx: { hasUI?: boolean; ui: { notify(message: string, level?: NotifyLevel): void } },
|
|
374
|
+
message: string,
|
|
375
|
+
level: NotifyLevel = "info",
|
|
376
|
+
): void {
|
|
377
|
+
if (ctx.hasUI !== false) {
|
|
378
|
+
ctx.ui.notify(message, level);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function setStatus(ctx: CommandContext, value: string | undefined): void {
|
|
383
|
+
if (ctx.hasUI !== false) {
|
|
384
|
+
ctx.ui.setStatus?.(PI_SIDEBAR_STATUS_KEY, value);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSlotClient,
|
|
3
|
+
type SlotCheckoutFailure,
|
|
4
|
+
type SlotCheckoutResult,
|
|
5
|
+
type SlotClient,
|
|
6
|
+
} from "@nseng-ai/slots/api";
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
SlotCheckoutFailure,
|
|
10
|
+
SlotCheckoutResult,
|
|
11
|
+
SlotCheckoutTarget,
|
|
12
|
+
SlotClient,
|
|
13
|
+
} from "@nseng-ai/slots/api";
|
|
14
|
+
|
|
15
|
+
export type SlotCheckoutRef = { kind: "branch"; branchName: string } | { kind: "current" };
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Composition-root factory for CCC's in-process slot checkouts. Construction is
|
|
19
|
+
* centralized here so command handlers (the composition roots) build one client
|
|
20
|
+
* per invocation instead of scattering the side-effect defaults across leaf
|
|
21
|
+
* helpers.
|
|
22
|
+
*/
|
|
23
|
+
export function createCccSlotClient(options: {
|
|
24
|
+
cwd: string;
|
|
25
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
26
|
+
}): SlotClient {
|
|
27
|
+
return createSlotClient({
|
|
28
|
+
cwd: options.cwd,
|
|
29
|
+
...(options.env === undefined ? {} : { env: options.env }),
|
|
30
|
+
sideEffects: { shouldCopyClipboard: false, shouldWriteCdDirective: false },
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function checkoutSlot(
|
|
35
|
+
slotClient: SlotClient,
|
|
36
|
+
ref: SlotCheckoutRef,
|
|
37
|
+
): Promise<SlotCheckoutResult> {
|
|
38
|
+
return ref.kind === "branch"
|
|
39
|
+
? await slotClient.checkoutBranch({ branchName: ref.branchName })
|
|
40
|
+
: await slotClient.checkoutCurrent();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Format a structured slot-checkout failure for human-facing display. */
|
|
44
|
+
export function formatSlotCheckoutFailureCause(failure: SlotCheckoutFailure): string {
|
|
45
|
+
return `Slot checkout failed (${failure.errorType}): ${failure.message}`;
|
|
46
|
+
}
|