@timurproko/a1 0.1.8-dev.271 → 0.1.8-dev.279
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/composition/owned-ui.js +6 -0
- package/dist/contracts/owned-ui/index.d.ts +1 -0
- package/dist/contracts/owned-ui/index.js +1 -0
- package/dist/contracts/owned-ui/model.d.ts +52 -0
- package/dist/contracts/owned-ui/prompt-suggestions.d.ts +3 -0
- package/dist/contracts/owned-ui/prompt-suggestions.js +41 -0
- package/dist/contracts/owned-ui/validation.d.ts +5 -1
- package/dist/contracts/owned-ui/validation.js +67 -3
- package/dist/foundation/supervision/server.d.ts +11 -0
- package/dist/foundation/supervision/server.js +38 -5
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +2 -0
- package/dist/integrations/pi/components/owned-editor-ux.js +5 -3
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +15 -0
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +8 -0
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +15 -2
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +102 -3
- package/dist/integrations/pi/engine/adapter.d.ts +3 -2
- package/dist/integrations/pi/engine/adapter.js +106 -2
- package/dist/integrations/pi/engine/conformance.d.ts +1 -1
- package/dist/integrations/pi/engine/conformance.js +2 -2
- package/dist/integrations/pi/engine/workflow-controllers.d.ts +1 -1
- package/dist/integrations/pi/session-ui/index.d.ts +1 -0
- package/dist/integrations/pi/session-ui/index.js +1 -0
- package/dist/integrations/pi/session-ui/prompt-suggestion-controller.d.ts +27 -0
- package/dist/integrations/pi/session-ui/prompt-suggestion-controller.js +132 -0
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +12 -1
- package/dist/integrations/pi/session-ui/session-shell-root.js +27 -1
- package/dist/integrations/pi/session-ui/session-shell.js +86 -2
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/settings/declarations.d.ts +1 -1
- package/dist/ui/settings/declarations.js +9 -1
- package/dist/ui/settings/migrations.js +7 -0
- package/package.json +1 -1
|
@@ -37,6 +37,11 @@ export async function composeOwnedUi(options = {}) {
|
|
|
37
37
|
snapshot: () => viewportSettingsSnapshot(settings),
|
|
38
38
|
onChange: listener => settings.onChange(() => listener(viewportSettingsSnapshot(settings))),
|
|
39
39
|
};
|
|
40
|
+
const promptSuggestions = settings === null || !ownedSurfaces ? null : {
|
|
41
|
+
generator: adapter,
|
|
42
|
+
enabled: () => settings.value("promptSuggestions") !== false,
|
|
43
|
+
onChange: (listener) => settings.onChange(() => listener(settings.value("promptSuggestions") !== false)),
|
|
44
|
+
};
|
|
40
45
|
const shell = new OwnedUiSessionShell({
|
|
41
46
|
backend: adapter,
|
|
42
47
|
cwd: adapter.cwd,
|
|
@@ -44,6 +49,7 @@ export async function composeOwnedUi(options = {}) {
|
|
|
44
49
|
...(routeHost === null ? {} : { routeHost }),
|
|
45
50
|
...(ownedSurfaces ? { sessionLayout: "custom-viewport" } : {}),
|
|
46
51
|
...(viewportSettings === null ? {} : { viewportSettings }),
|
|
52
|
+
...(promptSuggestions === null ? {} : { promptSuggestions }),
|
|
47
53
|
});
|
|
48
54
|
const application = {
|
|
49
55
|
get disposed() { return adapter.disposed; },
|
|
@@ -11,6 +11,39 @@ export interface OwnedUiModelInfo {
|
|
|
11
11
|
readonly modelId: string;
|
|
12
12
|
readonly displayName: string;
|
|
13
13
|
}
|
|
14
|
+
export interface OwnedUiPromptSuggestionIdentity {
|
|
15
|
+
readonly sessionId: OwnedUiSessionId;
|
|
16
|
+
readonly sessionGeneration: number;
|
|
17
|
+
readonly runSequence: number;
|
|
18
|
+
readonly responseSequence: number;
|
|
19
|
+
readonly model: OwnedUiModelInfo;
|
|
20
|
+
}
|
|
21
|
+
export interface OwnedUiPromptSuggestionRequest {
|
|
22
|
+
readonly identity: OwnedUiPromptSuggestionIdentity;
|
|
23
|
+
readonly signal: AbortSignal;
|
|
24
|
+
}
|
|
25
|
+
export interface OwnedUiPromptSuggestionResult {
|
|
26
|
+
readonly identity: OwnedUiPromptSuggestionIdentity;
|
|
27
|
+
readonly text: string | null;
|
|
28
|
+
}
|
|
29
|
+
export type OwnedUiPromptSuggestionState = {
|
|
30
|
+
readonly status: "idle";
|
|
31
|
+
} | {
|
|
32
|
+
readonly status: "generating";
|
|
33
|
+
readonly identity: OwnedUiPromptSuggestionIdentity;
|
|
34
|
+
readonly settled: boolean;
|
|
35
|
+
} | {
|
|
36
|
+
readonly status: "prepared";
|
|
37
|
+
readonly identity: OwnedUiPromptSuggestionIdentity;
|
|
38
|
+
readonly text: string;
|
|
39
|
+
} | {
|
|
40
|
+
readonly status: "available";
|
|
41
|
+
readonly identity: OwnedUiPromptSuggestionIdentity;
|
|
42
|
+
readonly text: string;
|
|
43
|
+
};
|
|
44
|
+
export interface OwnedUiPromptSuggestionGeneratorPort {
|
|
45
|
+
generate(request: OwnedUiPromptSuggestionRequest): Promise<OwnedUiPromptSuggestionResult>;
|
|
46
|
+
}
|
|
14
47
|
export type OwnedUiThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
15
48
|
export interface OwnedUiViewportSettings {
|
|
16
49
|
readonly scrollbarAppearance: "auto" | "always" | "hidden";
|
|
@@ -220,11 +253,30 @@ export type OwnedUiEvent = {
|
|
|
220
253
|
readonly type: "assistant-message-completed";
|
|
221
254
|
readonly sessionId: OwnedUiSessionId;
|
|
222
255
|
readonly sequence: number;
|
|
256
|
+
readonly sessionGeneration: number;
|
|
257
|
+
readonly runSequence: number;
|
|
258
|
+
readonly responseSequence: number;
|
|
259
|
+
readonly model: OwnedUiModelInfo | null;
|
|
260
|
+
readonly assistantMessageCount: number;
|
|
261
|
+
readonly successful: boolean;
|
|
262
|
+
readonly stopReason: string | null;
|
|
263
|
+
readonly toolContinuation: boolean;
|
|
223
264
|
} | {
|
|
224
265
|
/** A fresh agent run has started, used by follow-mode surfaces. */
|
|
225
266
|
readonly type: "agent-run-started";
|
|
226
267
|
readonly sessionId: OwnedUiSessionId;
|
|
227
268
|
readonly sequence: number;
|
|
269
|
+
} | {
|
|
270
|
+
/** The final settlement of one run, after authoritative transcript reconciliation. */
|
|
271
|
+
readonly type: "agent-run-settled";
|
|
272
|
+
readonly sessionId: OwnedUiSessionId;
|
|
273
|
+
readonly sequence: number;
|
|
274
|
+
readonly sessionGeneration: number;
|
|
275
|
+
readonly runSequence: number;
|
|
276
|
+
readonly responseSequence: number;
|
|
277
|
+
readonly model: OwnedUiModelInfo | null;
|
|
278
|
+
readonly assistantMessageCount: number;
|
|
279
|
+
readonly successful: boolean;
|
|
228
280
|
} | {
|
|
229
281
|
readonly type: "editor-state";
|
|
230
282
|
readonly sessionId: OwnedUiSessionId;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const CONTEXTUAL_PROMPT_SUGGESTION_INSTRUCTION = "[NEXT USER INPUT]\nPredict the one short response the user is most likely to type next.\nUse the user's recent intent and writing style. Prefer a concrete continuation such as approving an offered action, choosing an offered option, running a requested check, committing, or pushing.\nReturn nothing when the next input is unclear, the previous response failed, or the user should assess or correct the result.\nDo not answer as the assistant. Do not add a label, explanation, quotation marks, Markdown, or multiple sentences.\nReturn only 2-12 words, except a natural one-word command or answer is allowed.";
|
|
2
|
+
/** Converts untrusted model output into one inert, bounded user-voice candidate. */
|
|
3
|
+
export declare function normalizePromptSuggestionCandidate(candidate: string | null | undefined): string | null;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export const CONTEXTUAL_PROMPT_SUGGESTION_INSTRUCTION = `[NEXT USER INPUT]
|
|
2
|
+
Predict the one short response the user is most likely to type next.
|
|
3
|
+
Use the user's recent intent and writing style. Prefer a concrete continuation such as approving an offered action, choosing an offered option, running a requested check, committing, or pushing.
|
|
4
|
+
Return nothing when the next input is unclear, the previous response failed, or the user should assess or correct the result.
|
|
5
|
+
Do not answer as the assistant. Do not add a label, explanation, quotation marks, Markdown, or multiple sentences.
|
|
6
|
+
Return only 2-12 words, except a natural one-word command or answer is allowed.`;
|
|
7
|
+
const ALLOWED_SINGLE_WORDS = new Set([
|
|
8
|
+
"yes", "yeah", "yep", "sure", "ok", "okay", "no",
|
|
9
|
+
"continue", "apply", "commit", "push", "deploy", "test", "check", "stop", "exit", "quit",
|
|
10
|
+
]);
|
|
11
|
+
/** Converts untrusted model output into one inert, bounded user-voice candidate. */
|
|
12
|
+
export function normalizePromptSuggestionCandidate(candidate) {
|
|
13
|
+
if (typeof candidate !== "string")
|
|
14
|
+
return null;
|
|
15
|
+
const suggestion = candidate.trim();
|
|
16
|
+
if (suggestion.length === 0 || [...suggestion].length >= 100)
|
|
17
|
+
return null;
|
|
18
|
+
if (/[\p{C}\r\n\t]/u.test(suggestion))
|
|
19
|
+
return null;
|
|
20
|
+
if (/[\*`#]|__|~~/.test(suggestion))
|
|
21
|
+
return null;
|
|
22
|
+
if (/[.!?]\s+\S/u.test(suggestion))
|
|
23
|
+
return null;
|
|
24
|
+
const lower = suggestion.toLowerCase();
|
|
25
|
+
if (/^(api error:|prompt is too long|request timed out|invalid api key|image was too large)/.test(lower))
|
|
26
|
+
return null;
|
|
27
|
+
if (lower === "done" || /^(nothing to suggest|no suggestion|stay silent|silence\b)/.test(lower))
|
|
28
|
+
return null;
|
|
29
|
+
if (/^\w+:\s/u.test(suggestion))
|
|
30
|
+
return null;
|
|
31
|
+
if (/^(let me|i(?:'|’)ll|i(?:'|’)ve|i(?:'|’)m|i can|i would|i think|here(?:'|’)s|here is|here are|you can|you should|you could|sure,|of course|certainly)\b/i.test(suggestion))
|
|
32
|
+
return null;
|
|
33
|
+
if (/\b(thanks|thank you|looks good|sounds good|that works|that worked|nice|great|perfect|awesome|excellent)\b/i.test(suggestion))
|
|
34
|
+
return null;
|
|
35
|
+
const words = suggestion.split(/\s+/u);
|
|
36
|
+
if (words.length > 12)
|
|
37
|
+
return null;
|
|
38
|
+
if (words.length === 1 && !suggestion.startsWith("/") && !ALLOWED_SINGLE_WORDS.has(lower))
|
|
39
|
+
return null;
|
|
40
|
+
return suggestion;
|
|
41
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { type OwnedUiCommand, type OwnedUiCustomization, type OwnedUiDiagnostics, type OwnedUiEditorState, type OwnedUiEvent, type OwnedUiSessionViewModel, type OwnedUiSnapshot, type OwnedUiStatusView, type OwnedUiTerminalSurface, type OwnedUiTranscriptBlock } from "./model.js";
|
|
1
|
+
import { type OwnedUiCommand, type OwnedUiCustomization, type OwnedUiDiagnostics, type OwnedUiEditorState, type OwnedUiEvent, type OwnedUiPromptSuggestionIdentity, type OwnedUiPromptSuggestionRequest, type OwnedUiPromptSuggestionResult, type OwnedUiPromptSuggestionState, type OwnedUiSessionViewModel, type OwnedUiSnapshot, type OwnedUiStatusView, type OwnedUiTerminalSurface, type OwnedUiTranscriptBlock } from "./model.js";
|
|
2
2
|
export declare function assertOwnedUiCommand(command: OwnedUiCommand): void;
|
|
3
3
|
export declare function assertOwnedUiEvent(event: OwnedUiEvent): void;
|
|
4
|
+
export declare function assertOwnedUiPromptSuggestionIdentity(identity: OwnedUiPromptSuggestionIdentity): void;
|
|
5
|
+
export declare function assertOwnedUiPromptSuggestionRequest(request: OwnedUiPromptSuggestionRequest): void;
|
|
6
|
+
export declare function assertOwnedUiPromptSuggestionResult(result: OwnedUiPromptSuggestionResult): void;
|
|
7
|
+
export declare function assertOwnedUiPromptSuggestionState(state: OwnedUiPromptSuggestionState): void;
|
|
4
8
|
export declare function assertOwnedUiSessionViewModel(view: OwnedUiSessionViewModel): void;
|
|
5
9
|
export declare function assertOwnedUiSnapshot(snapshot: OwnedUiSnapshot): void;
|
|
6
10
|
export declare function assertOwnedUiCustomization(customization: OwnedUiCustomization): void;
|
|
@@ -116,8 +116,30 @@ export function assertOwnedUiEvent(event) {
|
|
|
116
116
|
assertOwnedUiTranscriptBlock(event.block);
|
|
117
117
|
return;
|
|
118
118
|
case "assistant-message-completed":
|
|
119
|
+
assertNonNegativeInteger(event.sessionGeneration, "owned-UI assistant response session generation");
|
|
120
|
+
assertNonNegativeInteger(event.runSequence, "owned-UI assistant response run sequence");
|
|
121
|
+
assertNonNegativeInteger(event.responseSequence, "owned-UI assistant response sequence");
|
|
122
|
+
assertNonNegativeInteger(event.assistantMessageCount, "owned-UI assistant response count");
|
|
123
|
+
if (event.model !== null)
|
|
124
|
+
assertOwnedUiModelInfo(event.model);
|
|
125
|
+
if (typeof event.successful !== "boolean")
|
|
126
|
+
throw new TypeError("owned-UI assistant response success state is invalid");
|
|
127
|
+
assertOptionalText(event.stopReason, "owned-UI assistant response stop reason", MAX_LABEL_LENGTH);
|
|
128
|
+
if (typeof event.toolContinuation !== "boolean")
|
|
129
|
+
throw new TypeError("owned-UI assistant response tool-continuation state is invalid");
|
|
130
|
+
return;
|
|
119
131
|
case "agent-run-started":
|
|
120
132
|
return;
|
|
133
|
+
case "agent-run-settled":
|
|
134
|
+
assertNonNegativeInteger(event.sessionGeneration, "owned-UI settlement session generation");
|
|
135
|
+
assertNonNegativeInteger(event.runSequence, "owned-UI settlement run sequence");
|
|
136
|
+
assertNonNegativeInteger(event.responseSequence, "owned-UI settlement response sequence");
|
|
137
|
+
assertNonNegativeInteger(event.assistantMessageCount, "owned-UI settlement assistant message count");
|
|
138
|
+
if (event.model !== null)
|
|
139
|
+
assertOwnedUiModelInfo(event.model);
|
|
140
|
+
if (typeof event.successful !== "boolean")
|
|
141
|
+
throw new TypeError("owned-UI settlement success state is invalid");
|
|
142
|
+
return;
|
|
121
143
|
case "editor-state":
|
|
122
144
|
assertOwnedUiEditorState(event.editor);
|
|
123
145
|
return;
|
|
@@ -153,6 +175,38 @@ export function assertOwnedUiEvent(event) {
|
|
|
153
175
|
throw new TypeError("owned-UI event type is unknown");
|
|
154
176
|
}
|
|
155
177
|
}
|
|
178
|
+
export function assertOwnedUiPromptSuggestionIdentity(identity) {
|
|
179
|
+
assertId(identity.sessionId, "prompt-suggestion session id");
|
|
180
|
+
assertNonNegativeInteger(identity.sessionGeneration, "prompt-suggestion session generation");
|
|
181
|
+
assertNonNegativeInteger(identity.runSequence, "prompt-suggestion run sequence");
|
|
182
|
+
assertNonNegativeInteger(identity.responseSequence, "prompt-suggestion response sequence");
|
|
183
|
+
assertOwnedUiModelInfo(identity.model);
|
|
184
|
+
}
|
|
185
|
+
export function assertOwnedUiPromptSuggestionRequest(request) {
|
|
186
|
+
assertOwnedUiPromptSuggestionIdentity(request.identity);
|
|
187
|
+
if (typeof request.signal !== "object" || request.signal === null
|
|
188
|
+
|| typeof request.signal.aborted !== "boolean"
|
|
189
|
+
|| typeof request.signal.addEventListener !== "function") {
|
|
190
|
+
throw new TypeError("prompt-suggestion abort signal is invalid");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
export function assertOwnedUiPromptSuggestionResult(result) {
|
|
194
|
+
assertOwnedUiPromptSuggestionIdentity(result.identity);
|
|
195
|
+
assertPromptSuggestionText(result.text, true);
|
|
196
|
+
}
|
|
197
|
+
export function assertOwnedUiPromptSuggestionState(state) {
|
|
198
|
+
if (state.status === "idle")
|
|
199
|
+
return;
|
|
200
|
+
if (state.status !== "generating" && state.status !== "prepared" && state.status !== "available") {
|
|
201
|
+
throw new TypeError("prompt-suggestion state is invalid");
|
|
202
|
+
}
|
|
203
|
+
assertOwnedUiPromptSuggestionIdentity(state.identity);
|
|
204
|
+
if (state.status === "generating" && typeof state.settled !== "boolean") {
|
|
205
|
+
throw new TypeError("prompt-suggestion settlement state is invalid");
|
|
206
|
+
}
|
|
207
|
+
if (state.status === "prepared" || state.status === "available")
|
|
208
|
+
assertPromptSuggestionText(state.text, false);
|
|
209
|
+
}
|
|
156
210
|
export function assertOwnedUiSessionViewModel(view) {
|
|
157
211
|
if (view.contractVersion !== OWNED_UI_CONTRACT_VERSION) {
|
|
158
212
|
throw new TypeError("unsupported owned-UI contract version");
|
|
@@ -164,9 +218,7 @@ export function assertOwnedUiSessionViewModel(view) {
|
|
|
164
218
|
assertOwnedUiStatusView(view.status);
|
|
165
219
|
assertOwnedUiTerminalSurface(view.terminal);
|
|
166
220
|
if (view.activeModel !== null) {
|
|
167
|
-
|
|
168
|
-
assertId(view.activeModel.modelId, "owned-UI model id");
|
|
169
|
-
assertBoundedText(view.activeModel.displayName, "owned-UI model display name", MAX_LABEL_LENGTH);
|
|
221
|
+
assertOwnedUiModelInfo(view.activeModel);
|
|
170
222
|
}
|
|
171
223
|
assertEnum(view.thinkingLevel, THINKING_LEVELS, "owned-UI thinking level");
|
|
172
224
|
assertCollection(view.activeCommandIds, "owned-UI active commands", MAX_ACTIVE_COMMANDS);
|
|
@@ -331,6 +383,11 @@ export function assertOwnedUiDiagnostics(diagnostic) {
|
|
|
331
383
|
if (typeof diagnostic.recoverable !== "boolean")
|
|
332
384
|
throw new TypeError("owned-UI diagnostic recoverability is invalid");
|
|
333
385
|
}
|
|
386
|
+
function assertOwnedUiModelInfo(model) {
|
|
387
|
+
assertId(model.providerId, "owned-UI provider id");
|
|
388
|
+
assertId(model.modelId, "owned-UI model id");
|
|
389
|
+
assertBoundedText(model.displayName, "owned-UI model display name", MAX_LABEL_LENGTH);
|
|
390
|
+
}
|
|
334
391
|
function assertOwnedUiDialog(dialog) {
|
|
335
392
|
assertId(dialog.id, "owned-UI dialog id");
|
|
336
393
|
assertBoundedText(dialog.title, "owned-UI dialog title", MAX_LABEL_LENGTH);
|
|
@@ -381,6 +438,13 @@ function assertOptionalText(value, name, maximumBytes) {
|
|
|
381
438
|
return;
|
|
382
439
|
assertBoundedText(value, name, maximumBytes);
|
|
383
440
|
}
|
|
441
|
+
function assertPromptSuggestionText(value, allowEmpty) {
|
|
442
|
+
if (value === null)
|
|
443
|
+
return;
|
|
444
|
+
if (typeof value !== "string" || value.includes("\0") || (!allowEmpty && value.length === 0) || [...value].length >= 100) {
|
|
445
|
+
throw new TypeError("prompt-suggestion text is invalid");
|
|
446
|
+
}
|
|
447
|
+
}
|
|
384
448
|
function assertCollection(value, name, maximum) {
|
|
385
449
|
if (!Array.isArray(value) || value.length > maximum)
|
|
386
450
|
throw new RangeError(`${name} exceeds its maximum length`);
|
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
import { rename, rm, writeFile } from "node:fs/promises";
|
|
1
2
|
import { type SupervisorSnapshot } from "../lifecycle/index.js";
|
|
2
3
|
import { type MaterializedRelease } from "../release/index.js";
|
|
3
4
|
import { ControlStore } from "../storage/index.js";
|
|
4
5
|
import { type ProductPaths } from "./paths.js";
|
|
6
|
+
interface EndpointMetadataCommitOperations {
|
|
7
|
+
readonly platform?: NodeJS.Platform;
|
|
8
|
+
readonly write?: typeof writeFile;
|
|
9
|
+
readonly replace?: typeof rename;
|
|
10
|
+
readonly remove?: typeof rm;
|
|
11
|
+
readonly wait?: (delayMs: number) => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
/** Atomically replaces endpoint metadata while tolerating bounded Windows reader sharing. */
|
|
14
|
+
export declare function commitEndpointMetadata(path: string, source: string, operations?: EndpointMetadataCommitOperations): Promise<void>;
|
|
5
15
|
/** Owns one release-cohort endpoint and the authenticated launch instances registered through it. */
|
|
6
16
|
export declare class SupervisorServer {
|
|
7
17
|
#private;
|
|
@@ -38,3 +48,4 @@ export declare class SupervisorServer {
|
|
|
38
48
|
close(stopAgents?: boolean): Promise<void>;
|
|
39
49
|
closeForReleaseReplacement(stopAgents: boolean): Promise<void>;
|
|
40
50
|
}
|
|
51
|
+
export {};
|
|
@@ -9,6 +9,36 @@ import { processIsAlive } from "../release/index.js";
|
|
|
9
9
|
import { ControlStore } from "../storage/index.js";
|
|
10
10
|
import { resolveProductPaths } from "./paths.js";
|
|
11
11
|
import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
|
|
12
|
+
const WINDOWS_METADATA_REPLACE_RETRY_DELAYS_MS = [5, 10, 20, 40, 80, 160];
|
|
13
|
+
/** Atomically replaces endpoint metadata while tolerating bounded Windows reader sharing. */
|
|
14
|
+
export async function commitEndpointMetadata(path, source, operations = {}) {
|
|
15
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
16
|
+
const write = operations.write ?? writeFile;
|
|
17
|
+
const replace = operations.replace ?? rename;
|
|
18
|
+
const remove = operations.remove ?? rm;
|
|
19
|
+
const wait = operations.wait ?? (delayMs => new Promise(resolvePromise => setTimeout(resolvePromise, delayMs)));
|
|
20
|
+
let committed = false;
|
|
21
|
+
try {
|
|
22
|
+
await write(temporary, source, { mode: 0o600 });
|
|
23
|
+
for (let attempt = 0;; attempt += 1) {
|
|
24
|
+
try {
|
|
25
|
+
await replace(temporary, path);
|
|
26
|
+
committed = true;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
const delayMs = WINDOWS_METADATA_REPLACE_RETRY_DELAYS_MS[attempt];
|
|
31
|
+
if ((operations.platform ?? platform()) !== "win32" || delayMs === undefined || !isWindowsSharingViolation(error))
|
|
32
|
+
throw error;
|
|
33
|
+
await wait(delayMs);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
if (!committed)
|
|
39
|
+
await remove(temporary, { force: true }).catch(() => undefined);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
12
42
|
/** Owns one release-cohort endpoint and the authenticated launch instances registered through it. */
|
|
13
43
|
export class SupervisorServer {
|
|
14
44
|
store;
|
|
@@ -464,11 +494,10 @@ export class SupervisorServer {
|
|
|
464
494
|
uncertainInstanceIds: [...this.#uncertainInstances],
|
|
465
495
|
},
|
|
466
496
|
};
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
});
|
|
497
|
+
const commit = () => commitEndpointMetadata(this.paths.endpointMetadataPath, JSON.stringify(metadata, null, 2));
|
|
498
|
+
// Platform: one exhausted filesystem replacement must fail its command, but it must not
|
|
499
|
+
// poison every later ownership revision after the sharing condition has cleared.
|
|
500
|
+
this.#metadataWrites = this.#metadataWrites.then(commit, commit);
|
|
472
501
|
return this.#metadataWrites;
|
|
473
502
|
}
|
|
474
503
|
#send(socket, message) {
|
|
@@ -492,6 +521,10 @@ function sameContainmentIdentity(left, right) {
|
|
|
492
521
|
function isMessageType(value, type) {
|
|
493
522
|
return typeof value === "object" && value !== null && "type" in value && value.type === type;
|
|
494
523
|
}
|
|
524
|
+
function isWindowsSharingViolation(error) {
|
|
525
|
+
return error instanceof Error && "code" in error
|
|
526
|
+
&& (error.code === "EPERM" || error.code === "EACCES" || error.code === "EBUSY");
|
|
527
|
+
}
|
|
495
528
|
async function ensureManagedEndpointDirectory(path) {
|
|
496
529
|
await mkdir(path, { recursive: true, mode: 0o700 });
|
|
497
530
|
const metadata = await lstat(path);
|
|
@@ -51,5 +51,7 @@ export interface PromptSelectionUxOptions {
|
|
|
51
51
|
readonly decorateRow: (row: string, width: number) => string;
|
|
52
52
|
readonly requestRender: () => void;
|
|
53
53
|
readonly getRows: () => number;
|
|
54
|
+
/** Presentation-only columns reserved before semantic editor text. */
|
|
55
|
+
readonly promptPrefixWidth?: number;
|
|
54
56
|
}
|
|
55
57
|
export declare function createPromptSelectionInterceptor(editor: Editor, keybindings: KeybindingsManager, options: PromptSelectionUxOptions): OwnedEditorUxInterceptor;
|
|
@@ -200,7 +200,9 @@ class PromptSelectionInterceptor {
|
|
|
200
200
|
const rows = next().map(row => row.replaceAll(ATOMIC_SPACE_SENTINEL, " "));
|
|
201
201
|
const maxPadding = Math.max(0, Math.floor((width - 1) / 2));
|
|
202
202
|
const padding = Math.min(this.editor.getPaddingX(), maxPadding);
|
|
203
|
-
const
|
|
203
|
+
const prefixWidth = this.options.promptPrefixWidth ?? 0;
|
|
204
|
+
const innerWidth = Math.max(1, width - prefixWidth);
|
|
205
|
+
const contentWidth = Math.max(1, innerWidth - padding * 2);
|
|
204
206
|
const layoutWidth = Math.max(1, contentWidth - (padding ? 0 : 1));
|
|
205
207
|
const visualLines = editorVisualLineMap(this.editor, layoutWidth)
|
|
206
208
|
?? buildVisualLineMap(editorState(this.editor).lines, layoutWidth);
|
|
@@ -226,7 +228,7 @@ class PromptSelectionInterceptor {
|
|
|
226
228
|
if (to <= from)
|
|
227
229
|
continue;
|
|
228
230
|
const line = editorState(this.editor).lines[visual.logicalLine] ?? "";
|
|
229
|
-
const fromColumn = padding + visibleWidth(line.slice(segmentStart, from));
|
|
231
|
+
const fromColumn = prefixWidth + padding + visibleWidth(line.slice(segmentStart, from));
|
|
230
232
|
const toColumn = fromColumn + visibleWidth(line.slice(from, to));
|
|
231
233
|
const rendered = rows[row + 1];
|
|
232
234
|
if (rendered !== undefined && toColumn > fromColumn) {
|
|
@@ -338,7 +340,7 @@ class PromptSelectionInterceptor {
|
|
|
338
340
|
return undefined;
|
|
339
341
|
const line = editorState(this.editor).lines[visual.logicalLine] ?? "";
|
|
340
342
|
const segment = line.slice(visual.startCol, visual.startCol + visual.length);
|
|
341
|
-
const displayColumn = Math.max(0, column - 1 - geometry.padding);
|
|
343
|
+
const displayColumn = Math.max(0, column - 1 - geometry.padding - (this.options.promptPrefixWidth ?? 0));
|
|
342
344
|
return {
|
|
343
345
|
line: visual.logicalLine,
|
|
344
346
|
col: visual.startCol + indexAtDisplayWidth(segment, displayColumn),
|
|
@@ -42,6 +42,12 @@ export function createPiShellEditor(options) {
|
|
|
42
42
|
}, keybindings, {
|
|
43
43
|
paddingX: PINNED_PI_LAYOUT.editorPaddingX,
|
|
44
44
|
autocompleteMaxVisible: PINNED_PI_LAYOUT.autocompleteMaxVisible,
|
|
45
|
+
...(options.keybindingProfile === "a1" && options.promptPresentation !== undefined ? {
|
|
46
|
+
promptPrefix: options.promptPresentation.prefix,
|
|
47
|
+
styleSuggestion: options.promptPresentation.styleSuggestion,
|
|
48
|
+
styleSuggestionCaret: options.promptPresentation.styleSuggestionCaret,
|
|
49
|
+
terminalRows: options.getRows,
|
|
50
|
+
} : {}),
|
|
45
51
|
});
|
|
46
52
|
const editorUx = options.keybindingProfile === "a1"
|
|
47
53
|
? new OwnedEditorUxInterception([
|
|
@@ -55,6 +61,7 @@ export function createPiShellEditor(options) {
|
|
|
55
61
|
decorateRow: options.decorateEditorRow ?? (row => row),
|
|
56
62
|
requestRender: options.requestRender,
|
|
57
63
|
getRows: options.getRows,
|
|
64
|
+
...(options.promptPresentation === undefined ? {} : { promptPrefixWidth: 2 }),
|
|
58
65
|
}),
|
|
59
66
|
], {
|
|
60
67
|
render: width => editor.render(width),
|
|
@@ -128,6 +135,9 @@ export function createPiShellEditor(options) {
|
|
|
128
135
|
editor.onAction("app.message.followUp", options.onFollowUp);
|
|
129
136
|
if (options.onDequeue !== undefined)
|
|
130
137
|
editor.onAction("app.message.dequeue", options.onDequeue);
|
|
138
|
+
if (options.onPromptSuggestionAccepted !== undefined) {
|
|
139
|
+
editor.onPromptSuggestionAccepted = options.onPromptSuggestionAccepted;
|
|
140
|
+
}
|
|
131
141
|
return {
|
|
132
142
|
render: width => editorUx?.render(width) ?? editor.render(width),
|
|
133
143
|
activateKeybindings: () => setKeybindings(keybindings),
|
|
@@ -183,6 +193,11 @@ export function createPiShellEditor(options) {
|
|
|
183
193
|
thinkingLevel = level;
|
|
184
194
|
updateBorderColor();
|
|
185
195
|
},
|
|
196
|
+
setPromptSuggestion(text) {
|
|
197
|
+
editor.setPromptSuggestion(text);
|
|
198
|
+
editor.invalidate();
|
|
199
|
+
},
|
|
200
|
+
canPresentPromptSuggestion: () => editor.canPresentPromptSuggestion(),
|
|
186
201
|
hasSelection: () => editorUx?.hasSelection() ?? false,
|
|
187
202
|
ownsPointer: () => editorUx?.ownsPointer() ?? false,
|
|
188
203
|
handlePointer: event => editorUx?.handlePointer(event) ?? false,
|
|
@@ -41,6 +41,8 @@ export interface PiShellEditorPort extends PiShellComponentPort {
|
|
|
41
41
|
setAutocompleteMaxVisible(maxVisible: number): void;
|
|
42
42
|
addAutocompleteProvider(factory: unknown): void;
|
|
43
43
|
setThinkingLevel(level: OwnedUiThinkingLevel): void;
|
|
44
|
+
setPromptSuggestion(text: string | null): void;
|
|
45
|
+
canPresentPromptSuggestion(): boolean;
|
|
44
46
|
hasSelection(): boolean;
|
|
45
47
|
ownsPointer(): boolean;
|
|
46
48
|
handlePointer(event: PiShellEditorPointerEvent): boolean;
|
|
@@ -131,6 +133,7 @@ export interface PiShellEditorOptions {
|
|
|
131
133
|
readonly onMessageCopy?: (() => void) | undefined;
|
|
132
134
|
readonly onFollowUp?: (() => void) | undefined;
|
|
133
135
|
readonly onDequeue?: (() => void) | undefined;
|
|
136
|
+
readonly onPromptSuggestionAccepted?: (text: string) => void;
|
|
134
137
|
readonly onCopyText?: (text: string) => void;
|
|
135
138
|
readonly readClipboardContent?: () => Promise<PiShellClipboardContent | null>;
|
|
136
139
|
readonly transformPastedContent?: (content: PiShellClipboardContent) => string;
|
|
@@ -141,6 +144,11 @@ export interface PiShellEditorOptions {
|
|
|
141
144
|
readonly cwd?: string;
|
|
142
145
|
readonly agentDir?: string;
|
|
143
146
|
readonly autocompleteCommands?: readonly PiShellAutocompleteCommand[];
|
|
147
|
+
readonly promptPresentation?: {
|
|
148
|
+
readonly prefix: string;
|
|
149
|
+
readonly styleSuggestion: (text: string) => string;
|
|
150
|
+
readonly styleSuggestionCaret: (text: string) => string;
|
|
151
|
+
};
|
|
144
152
|
}
|
|
145
153
|
export interface PiShellSelectorOption {
|
|
146
154
|
readonly id: string;
|
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Adapted from @earendil-works/pi-coding-agent 0.84.2
|
|
3
3
|
* packages/coding-agent/src/modes/interactive/components/custom-editor.ts (MIT).
|
|
4
|
-
* Modifications: A1-owned class name
|
|
4
|
+
* Modifications: A1-owned class name, synchronized keybinding contract, and a semantic
|
|
5
|
+
* bare-A1 prompt-prefix/contextual-suggestion presentation branch.
|
|
5
6
|
*/
|
|
6
7
|
import { Editor, type EditorOptions, type EditorTheme, type TUI } from "#pi-tui";
|
|
7
8
|
import type { AppKeybinding, KeybindingsManager } from "../adjacent/core/keybindings.js";
|
|
9
|
+
export interface OwnedEditorOptions extends EditorOptions {
|
|
10
|
+
readonly promptPrefix?: string;
|
|
11
|
+
readonly styleSuggestion?: (text: string) => string;
|
|
12
|
+
readonly styleSuggestionCaret?: (text: string) => string;
|
|
13
|
+
readonly terminalRows?: () => number;
|
|
14
|
+
}
|
|
8
15
|
export declare class OwnedEditor extends Editor {
|
|
16
|
+
#private;
|
|
9
17
|
private readonly keybindings;
|
|
10
18
|
readonly actionHandlers: Map<keyof import("../adjacent/core/keybindings.js").AppKeybindings, () => void>;
|
|
11
19
|
onEscape?: () => void;
|
|
12
20
|
onCtrlD?: () => void;
|
|
13
21
|
onPasteImage?: () => void;
|
|
14
22
|
onExtensionShortcut?: (data: string) => boolean;
|
|
15
|
-
|
|
23
|
+
onPromptSuggestionAccepted?: (text: string) => void;
|
|
24
|
+
constructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: OwnedEditorOptions);
|
|
25
|
+
setPromptSuggestion(text: string | null): void;
|
|
26
|
+
canPresentPromptSuggestion(): boolean;
|
|
27
|
+
setText(text: string): void;
|
|
28
|
+
render(width: number): string[];
|
|
16
29
|
onAction(action: AppKeybinding, handler: () => void): void;
|
|
17
30
|
handleInput(data: string): void;
|
|
18
31
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Adapted from @earendil-works/pi-coding-agent 0.84.2
|
|
3
3
|
* packages/coding-agent/src/modes/interactive/components/custom-editor.ts (MIT).
|
|
4
|
-
* Modifications: A1-owned class name
|
|
4
|
+
* Modifications: A1-owned class name, synchronized keybinding contract, and a semantic
|
|
5
|
+
* bare-A1 prompt-prefix/contextual-suggestion presentation branch.
|
|
5
6
|
*/
|
|
6
|
-
import { Editor } from "#pi-tui";
|
|
7
|
+
import { CURSOR_MARKER, Editor, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "#pi-tui";
|
|
8
|
+
const PROMPT_GRAPHEMES = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
7
9
|
export class OwnedEditor extends Editor {
|
|
8
10
|
keybindings;
|
|
9
11
|
actionHandlers = new Map();
|
|
@@ -11,14 +13,63 @@ export class OwnedEditor extends Editor {
|
|
|
11
13
|
onCtrlD;
|
|
12
14
|
onPasteImage;
|
|
13
15
|
onExtensionShortcut;
|
|
14
|
-
|
|
16
|
+
onPromptSuggestionAccepted;
|
|
17
|
+
#promptSuggestion = null;
|
|
18
|
+
#promptPrefix;
|
|
19
|
+
#styleSuggestion;
|
|
20
|
+
#styleSuggestionCaret;
|
|
21
|
+
#terminalRows;
|
|
22
|
+
constructor(tui, theme, keybindings, options = {}) {
|
|
15
23
|
super(tui, theme, options);
|
|
16
24
|
this.keybindings = keybindings;
|
|
25
|
+
this.#promptPrefix = options.promptPrefix ?? "";
|
|
26
|
+
this.#styleSuggestion = options.styleSuggestion ?? (text => text);
|
|
27
|
+
this.#styleSuggestionCaret = options.styleSuggestionCaret ?? (text => `\u001b[7m${text}\u001b[27m`);
|
|
28
|
+
this.#terminalRows = options.terminalRows ?? (() => 24);
|
|
29
|
+
}
|
|
30
|
+
setPromptSuggestion(text) {
|
|
31
|
+
this.#promptSuggestion = text;
|
|
32
|
+
}
|
|
33
|
+
canPresentPromptSuggestion() {
|
|
34
|
+
return this.#promptPrefix.length > 0
|
|
35
|
+
&& this.focused
|
|
36
|
+
&& !this.disableSubmit
|
|
37
|
+
&& this.getText().length === 0
|
|
38
|
+
&& !this.isShowingAutocomplete();
|
|
39
|
+
}
|
|
40
|
+
setText(text) {
|
|
41
|
+
if (text.length > 0)
|
|
42
|
+
this.#promptSuggestion = null;
|
|
43
|
+
super.setText(text);
|
|
44
|
+
}
|
|
45
|
+
render(width) {
|
|
46
|
+
if (this.#promptPrefix.length === 0)
|
|
47
|
+
return super.render(width);
|
|
48
|
+
if (this.#promptSuggestion !== null && this.canPresentPromptSuggestion()) {
|
|
49
|
+
return this.#renderSuggestion(width);
|
|
50
|
+
}
|
|
51
|
+
return this.#renderPrefixedEditor(width);
|
|
17
52
|
}
|
|
18
53
|
onAction(action, handler) { this.actionHandlers.set(action, handler); }
|
|
19
54
|
handleInput(data) {
|
|
20
55
|
if (this.onExtensionShortcut?.(data))
|
|
21
56
|
return;
|
|
57
|
+
if (this.#promptSuggestion !== null
|
|
58
|
+
&& !this.isShowingAutocomplete()
|
|
59
|
+
&& this.canPresentPromptSuggestion()
|
|
60
|
+
&& this.keybindings.matches(data, "tui.input.submit")) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (this.#promptSuggestion !== null
|
|
64
|
+
&& !this.isShowingAutocomplete()
|
|
65
|
+
&& this.canPresentPromptSuggestion()
|
|
66
|
+
&& this.keybindings.matches(data, "tui.input.tab")) {
|
|
67
|
+
const accepted = this.#promptSuggestion;
|
|
68
|
+
this.#promptSuggestion = null;
|
|
69
|
+
super.setText(accepted);
|
|
70
|
+
this.onPromptSuggestionAccepted?.(accepted);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
22
73
|
if (this.keybindings.matches(data, "app.clipboard.pasteImage")) {
|
|
23
74
|
this.onPasteImage?.();
|
|
24
75
|
return;
|
|
@@ -52,4 +103,52 @@ export class OwnedEditor extends Editor {
|
|
|
52
103
|
}
|
|
53
104
|
super.handleInput(data);
|
|
54
105
|
}
|
|
106
|
+
#renderSuggestion(width) {
|
|
107
|
+
const prefixWidth = visibleWidth(this.#promptPrefix);
|
|
108
|
+
const innerWidth = Math.max(1, width - prefixWidth);
|
|
109
|
+
const maxPadding = Math.max(0, Math.floor((innerWidth - 1) / 2));
|
|
110
|
+
const paddingX = Math.min(this.getPaddingX(), maxPadding);
|
|
111
|
+
const contentWidth = Math.max(1, innerWidth - paddingX * 2);
|
|
112
|
+
const layoutWidth = Math.max(1, contentWidth - (paddingX ? 0 : 1));
|
|
113
|
+
const chunks = wrapTextWithAnsi(this.#promptSuggestion ?? "", layoutWidth).map(text => ({ text }));
|
|
114
|
+
const maxVisible = Math.max(5, Math.floor(this.#terminalRows() * 0.3));
|
|
115
|
+
const visible = chunks.slice(0, maxVisible);
|
|
116
|
+
const horizontal = this.borderColor("─".repeat(Math.max(0, width)));
|
|
117
|
+
const leftPadding = " ".repeat(paddingX);
|
|
118
|
+
const rightPadding = leftPadding;
|
|
119
|
+
const rows = visible.map((chunk, index) => {
|
|
120
|
+
let content;
|
|
121
|
+
if (index === 0) {
|
|
122
|
+
const first = [...PROMPT_GRAPHEMES.segment(chunk.text)][0]?.segment ?? " ";
|
|
123
|
+
const remaining = chunk.text.slice(first === " " && chunk.text.length === 0 ? 0 : first.length);
|
|
124
|
+
const marker = this.focused ? CURSOR_MARKER : "";
|
|
125
|
+
content = `${marker}${this.#styleSuggestionCaret(first)}${this.#styleSuggestion(remaining)}`;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
content = this.#styleSuggestion(chunk.text);
|
|
129
|
+
}
|
|
130
|
+
const plainWidth = visibleWidth(chunk.text);
|
|
131
|
+
const padding = " ".repeat(Math.max(0, contentWidth - plainWidth));
|
|
132
|
+
const prefix = index === 0 ? this.#promptPrefix : " ".repeat(prefixWidth);
|
|
133
|
+
return truncateToWidth(`${prefix}${leftPadding}${content}${padding}${rightPadding}`, width);
|
|
134
|
+
});
|
|
135
|
+
return [horizontal, ...rows, horizontal];
|
|
136
|
+
}
|
|
137
|
+
#renderPrefixedEditor(width) {
|
|
138
|
+
const prefixWidth = visibleWidth(this.#promptPrefix);
|
|
139
|
+
const innerWidth = Math.max(1, width - prefixWidth);
|
|
140
|
+
const rows = super.render(innerWidth);
|
|
141
|
+
const maxPadding = Math.max(0, Math.floor((innerWidth - 1) / 2));
|
|
142
|
+
const paddingX = Math.min(this.getPaddingX(), maxPadding);
|
|
143
|
+
const contentWidth = Math.max(1, innerWidth - paddingX * 2);
|
|
144
|
+
const layoutWidth = Math.max(1, contentWidth - (paddingX ? 0 : 1));
|
|
145
|
+
const layoutCount = this.getLines().flatMap(line => wrapTextWithAnsi(line, layoutWidth)).length || 1;
|
|
146
|
+
const visibleCount = Math.min(layoutCount, Math.max(5, Math.floor(this.#terminalRows() * 0.3)));
|
|
147
|
+
const bottomBorder = visibleCount + 1;
|
|
148
|
+
return rows.map((row, index) => {
|
|
149
|
+
if (index === 0 || index === bottomBorder)
|
|
150
|
+
return `${row}${this.borderColor("─".repeat(prefixWidth))}`;
|
|
151
|
+
return `${index === 1 ? this.#promptPrefix : " ".repeat(prefixWidth)}${row}`;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
55
154
|
}
|