@timurproko/a1 0.2.5 → 0.2.6-dev.727
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/bin/cli.js +13 -2
- package/bin/ui.js +13 -0
- package/dist/app/session-shell/prompt-suggestion-controller.d.ts +5 -4
- package/dist/app/session-shell/prompt-suggestion-controller.js +148 -58
- package/dist/app/session-shell/session-shell-root.d.ts +5 -1
- package/dist/app/session-shell/session-shell-root.js +13 -6
- package/dist/app/session-shell/session-shell.js +132 -51
- package/dist/app/session-shell/session-viewport-controller.js +33 -21
- package/dist/cli/dispatch.js +5 -3
- package/dist/cli/session-context.d.ts +5 -1
- package/dist/cli/session-context.js +25 -8
- package/dist/composition/owned-ui.d.ts +3 -0
- package/dist/composition/owned-ui.js +66 -6
- package/dist/composition/settings-route-host.js +4 -3
- package/dist/contracts/owned-ui/index.d.ts +4 -2
- package/dist/contracts/owned-ui/index.js +1 -1
- package/dist/contracts/owned-ui/model.d.ts +4 -0
- package/dist/contracts/owned-ui/suggestion-diagnostics.d.ts +5 -1
- package/dist/contracts/owned-ui/suggestion-diagnostics.js +3 -1
- package/dist/contracts/owned-ui/validation.js +7 -0
- package/dist/features/launch/index.d.ts +1 -1
- package/dist/features/launch/session-context.d.ts +17 -2
- package/dist/features/launch/session-context.js +9 -5
- package/dist/features/owned-ui/reference-routes.d.ts +1 -1
- package/dist/features/owned-ui/reference-routes.js +1 -1
- package/dist/features/owned-ui/settings-app.js +21 -39
- package/dist/features/prompt-suggestions/diagnostics.js +4 -1
- package/dist/foundation/lifecycle/index.d.ts +2 -2
- package/dist/foundation/lifecycle/index.js +1 -1
- package/dist/foundation/lifecycle/session-repository-context.d.ts +19 -2
- package/dist/foundation/lifecycle/session-repository-context.js +434 -66
- package/dist/foundation/process-containment/index.d.ts +1 -0
- package/dist/foundation/process-containment/index.js +1 -0
- package/dist/foundation/process-containment/native-process-inspector.d.ts +3 -0
- package/dist/foundation/process-containment/native-process-inspector.js +21 -0
- package/dist/foundation/release/progress-palette.generated.d.ts +2 -2
- package/dist/foundation/release/progress-palette.generated.js +2 -2
- package/dist/foundation/startup/startup-descriptor.js +2 -2
- package/dist/integrations/pi/components/components.js +2 -2
- package/dist/integrations/pi/components/index.d.ts +2 -2
- package/dist/integrations/pi/components/index.js +1 -1
- package/dist/integrations/pi/components/lazy-selectors.d.ts +42 -0
- package/dist/integrations/pi/components/lazy-selectors.js +32 -0
- package/dist/integrations/pi/components/modal-frame.js +3 -4
- package/dist/integrations/pi/components/models-dialog.js +5 -5
- package/dist/integrations/pi/components/prompt-input-port.d.ts +1 -0
- package/dist/integrations/pi/components/resources/builtin-themes.d.ts +2 -2
- package/dist/integrations/pi/components/resources/builtin-themes.js +2 -2
- package/dist/integrations/pi/components/share-operation-dialog.js +3 -3
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +8 -7
- package/dist/integrations/pi/components/shell-extension-ui.d.ts +2 -0
- package/dist/integrations/pi/components/shell-extension-ui.js +20 -10
- package/dist/integrations/pi/components/shell-hotkey-sections.js +2 -2
- package/dist/integrations/pi/components/shell-presenters-info.js +10 -10
- package/dist/integrations/pi/components/shell-presenters-transcript.js +10 -10
- package/dist/integrations/pi/components/shell-selectors-dialogs.js +5 -6
- package/dist/integrations/pi/components/shell-shared-facade.js +1 -1
- package/dist/integrations/pi/components/skills-dialog.js +3 -3
- package/dist/integrations/pi/components/submitted-prompt-adapter.js +2 -3
- package/dist/integrations/pi/components/theme.d.ts +9 -0
- package/dist/integrations/pi/components/theme.js +30 -0
- package/dist/integrations/pi/components/tool-result-adapter.js +6 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +1 -1
- package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
- package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +6 -6
- package/dist/integrations/pi/components/upstream/components/extension-input.js +4 -4
- package/dist/integrations/pi/components/upstream/components/extension-selector.js +4 -4
- package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +5 -5
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +8 -5
- package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +3 -3
- package/dist/integrations/pi/components/upstream/components/session-footer.js +1 -1
- package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
- package/dist/integrations/pi/components/upstream/components/session-selector.js +14 -20
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +3 -2
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +6 -3
- package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/status-indicator.js +1 -1
- package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +2 -2
- package/dist/integrations/pi/components/upstream/components/tool-execution.d.ts +4 -0
- package/dist/integrations/pi/components/upstream/components/tool-execution.js +11 -1
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +2 -2
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +27 -18
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +4 -4
- package/dist/integrations/pi/components/upstream/history/editor-core.js +11 -7
- package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
- package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
- package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
- package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
- package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +31 -0
- package/dist/integrations/pi/components/upstream/program-status-reporter.js +89 -0
- package/dist/integrations/pi/components/upstream/theme/system-theme.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/system-theme.js +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme.d.ts +18 -1
- package/dist/integrations/pi/components/upstream/theme/theme.js +207 -10
- package/dist/integrations/pi/engine/pi-settings-metadata.json +1 -1
- package/dist/integrations/pi/engine/resources/changelog.json +4 -4
- package/dist/integrations/pi/engine/session-events.js +10 -2
- package/dist/integrations/pi/engine/transcript-projection.js +2 -1
- package/dist/integrations/pi/startup-public.js +306 -290
- package/dist/integrations/pi/startup-public.manifest.json +289 -265
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +8 -1
- package/dist/integrations/pi/tui-runtime/adapter.js +31 -1
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +3 -1
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
- 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/product-identity.d.ts +2 -2
- package/dist/product-identity.js +2 -2
- package/dist/product-identity.json +3 -1
- package/dist/runtime-payload-inventory.json +3092 -2878
- package/dist/ui/components/prompt-input.d.ts +2 -0
- package/dist/ui/components/prompt-input.js +5 -1
- package/dist/ui/components/text-selection.d.ts +1 -1
- package/dist/ui/components/text-selection.js +3 -1
- package/dist/ui/components/theme.d.ts +2 -0
- package/dist/ui/components/transcript-viewport.d.ts +6 -4
- package/dist/ui/components/transcript-viewport.js +94 -16
- package/dist/ui/components/value-menu.d.ts +2 -0
- package/dist/ui/components/value-menu.js +13 -8
- package/dist/ui/settings/declarations.d.ts +26 -14
- package/dist/ui/settings/declarations.js +24 -11
- package/dist/ui/settings/migrations.js +16 -0
- package/docs/architecture/project-structure.md +4 -3
- package/docs/architecture/prompt-suggestions.md +14 -10
- package/docs/local-worktree-cleanup.md +23 -9
- package/docs/manual-owned-ui-checkpoint.md +1 -1
- package/docs/openspec-archive-automation.md +20 -19
- package/package.json +3 -3
package/bin/cli.js
CHANGED
|
@@ -55,8 +55,19 @@ process.exitCode = await dispatchCli(process.argv.slice(2), {
|
|
|
55
55
|
return await runPackageCommand(request, { createPort: createPiPackagesPort });
|
|
56
56
|
},
|
|
57
57
|
sessionContext: async request => {
|
|
58
|
-
const { runSessionContextCommand } = await
|
|
59
|
-
|
|
58
|
+
const [{ runSessionContextCommand }, { createNativeProcessInspector }, { readLaunchContext }] = await Promise.all([
|
|
59
|
+
import("../dist/cli/session-context.js"),
|
|
60
|
+
import("../dist/foundation/process-containment/native-process-inspector.js"),
|
|
61
|
+
import("../dist/foundation/launch-context/index.js"),
|
|
62
|
+
]);
|
|
63
|
+
const launch = readLaunchContext(process.env);
|
|
64
|
+
let inspector;
|
|
65
|
+
return await runSessionContextCommand(request, {
|
|
66
|
+
inspectProcess: async pid => {
|
|
67
|
+
inspector ??= createNativeProcessInspector(process.env, process.platform, launch.releaseRoot);
|
|
68
|
+
return (await inspector).observe(pid);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
60
71
|
},
|
|
61
72
|
}, {
|
|
62
73
|
stdout: message => process.stdout.write(message),
|
package/bin/ui.js
CHANGED
|
@@ -9,6 +9,13 @@ const { readLaunchContext } = await import("../dist/foundation/launch-context/in
|
|
|
9
9
|
const launchContext = readLaunchContext(process.env, "profile");
|
|
10
10
|
const profile = launchContext.launchProfile;
|
|
11
11
|
if (profile === undefined) throw new Error("A1 launch profile is missing");
|
|
12
|
+
if (profile === "a1") {
|
|
13
|
+
const [{ randomUUID }, { PRODUCT_IDENTITY }] = await Promise.all([
|
|
14
|
+
import("node:crypto"),
|
|
15
|
+
import("../dist/product-identity.js"),
|
|
16
|
+
]);
|
|
17
|
+
process.env[PRODUCT_IDENTITY.environment.sessionRuntimeId] = randomUUID();
|
|
18
|
+
}
|
|
12
19
|
const { installFatalExit } = await import("../dist/foundation/terminal-cleanup/fatal-exit.js");
|
|
13
20
|
const { resolveProductPaths } = await import("../dist/foundation/lifecycle/paths.js");
|
|
14
21
|
const { join } = await import("node:path");
|
|
@@ -49,10 +56,16 @@ Promise.resolve().then(() => {
|
|
|
49
56
|
if (sessionSelection && profile !== "a1") throw new Error("session selection requires the normal A1 profile");
|
|
50
57
|
return runSelectedInteractiveRuntime(profile, {
|
|
51
58
|
ownedUi: async (profileId, ownedSurfaces) => {
|
|
59
|
+
const processInspector = ownedSurfaces
|
|
60
|
+
? await import("../dist/foundation/process-containment/native-process-inspector.js")
|
|
61
|
+
.then(module => module.createNativeProcessInspector(process.env, process.platform, launchContext.releaseRoot))
|
|
62
|
+
.catch(() => null)
|
|
63
|
+
: null;
|
|
52
64
|
const { application, settings } = await composeOwnedUi({
|
|
53
65
|
cwd: process.cwd(),
|
|
54
66
|
profileId,
|
|
55
67
|
ownedSurfaces,
|
|
68
|
+
...(processInspector === null ? {} : { inspectProcess: pid => processInspector.observe(pid) }),
|
|
56
69
|
projectTrustPrompt: createConsoleProjectTrustPrompt({ presentation: profile === "a1" ? "bare" : "comparison" }),
|
|
57
70
|
sessionForkPrompt: createConsoleSessionForkPrompt(),
|
|
58
71
|
...(sessionSelection === undefined ? {} : { sessionSelection }),
|
|
@@ -17,7 +17,7 @@ export interface ContextualPromptSuggestionControllerOptions {
|
|
|
17
17
|
type DecisionIdentity = Omit<OwnedUiPromptSuggestionIdentity, "model"> & {
|
|
18
18
|
readonly model: OwnedUiPromptSuggestionIdentity["model"] | null;
|
|
19
19
|
};
|
|
20
|
-
/** Prefetches one inert candidate
|
|
20
|
+
/** Prefetches one inert candidate and owns one bounded settlement/retry recovery lifecycle. */
|
|
21
21
|
export declare class ContextualPromptSuggestionController {
|
|
22
22
|
#private;
|
|
23
23
|
readonly options: ContextualPromptSuggestionControllerOptions;
|
|
@@ -26,12 +26,13 @@ export declare class ContextualPromptSuggestionController {
|
|
|
26
26
|
setEnabled(enabled: boolean): void;
|
|
27
27
|
skip(identity: DecisionIdentity, reason: SuggestionDecisionReason): void;
|
|
28
28
|
consider(identity: OwnedUiPromptSuggestionIdentity, decision: boolean | SuggestionDecision): void;
|
|
29
|
-
|
|
29
|
+
/** Settles an existing prefetch or starts the first request when that eligible prefetch was missed. */
|
|
30
|
+
settle(identity: OwnedUiPromptSuggestionIdentity, decision?: boolean | SuggestionDecision): void;
|
|
30
31
|
accept(): void;
|
|
31
32
|
invalidate(): void;
|
|
32
|
-
/** Retires
|
|
33
|
+
/** Retires generating or scheduled recovery; a prepared or available suggestion survives a draft edit. */
|
|
33
34
|
abortPending(): void;
|
|
34
|
-
/** Reasserts retained ghost text
|
|
35
|
+
/** Reasserts retained ghost text or retries a deferred presentation when the editor is eligible. */
|
|
35
36
|
restoreAvailable(): void;
|
|
36
37
|
dispose(): void;
|
|
37
38
|
}
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { assertOwnedUiPromptSuggestionResult, normalizePromptSuggestionCandidate, } from "../../contracts/owned-ui/index.js";
|
|
2
|
-
|
|
2
|
+
const TRANSIENT_PRESENTATION_REASONS = new Set([
|
|
3
|
+
"not-ready", "not-focused", "autocomplete", "prompt-mode",
|
|
4
|
+
]);
|
|
5
|
+
const RETRYABLE_OUTCOMES = new Set([
|
|
6
|
+
"empty", "rejected", "provider-failure", "unavailable", "timeout",
|
|
7
|
+
]);
|
|
8
|
+
/** Prefetches one inert candidate and owns one bounded settlement/retry recovery lifecycle. */
|
|
3
9
|
export class ContextualPromptSuggestionController {
|
|
4
10
|
#state = { status: "idle" };
|
|
11
|
+
#lifecycle = null;
|
|
5
12
|
#request = null;
|
|
6
13
|
#lastConsidered = "";
|
|
14
|
+
#retiredIdentity = "";
|
|
7
15
|
#enabled;
|
|
8
16
|
#disposed = false;
|
|
9
17
|
#sessionKey = "";
|
|
@@ -27,6 +35,7 @@ export class ContextualPromptSuggestionController {
|
|
|
27
35
|
}
|
|
28
36
|
skip(identity, reason) {
|
|
29
37
|
this.invalidate();
|
|
38
|
+
this.#lastConsidered = decisionIdentityKey(identity);
|
|
30
39
|
this.#emit(this.#metadata(identity), "skipped", reason);
|
|
31
40
|
}
|
|
32
41
|
consider(identity, decision) {
|
|
@@ -34,51 +43,43 @@ export class ContextualPromptSuggestionController {
|
|
|
34
43
|
if (key === this.#lastConsidered)
|
|
35
44
|
return;
|
|
36
45
|
this.#lastConsidered = key;
|
|
37
|
-
this
|
|
38
|
-
const reason = this.#
|
|
39
|
-
: decision === false ? "ineligible" : decision === true ? null : decision;
|
|
46
|
+
this.#reset(true, true);
|
|
47
|
+
const reason = this.#decisionReason(decision);
|
|
40
48
|
if (reason !== null) {
|
|
41
49
|
this.#emit(this.#metadata(identity), "skipped", reason);
|
|
42
50
|
return;
|
|
43
51
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
catch { /* Security: isolate diagnostic metadata failure. */ }
|
|
49
|
-
const request = {
|
|
50
|
-
identity,
|
|
51
|
-
metadata: { ...this.#metadata(identity), request: ++this.#requestSequence, reasoning },
|
|
52
|
-
abort: new AbortController(), startedAt: this.#now(), terminal: false,
|
|
53
|
-
};
|
|
54
|
-
this.#request = request;
|
|
55
|
-
this.#state = { status: "generating", identity, settled: false };
|
|
56
|
-
this.#emit(request.metadata, "started");
|
|
57
|
-
request.timer = setTimeout(() => {
|
|
58
|
-
if (this.#request !== request || this.#state.status !== "generating")
|
|
59
|
-
return;
|
|
60
|
-
this.#finish(request, "timeout");
|
|
61
|
-
this.#state = { status: "idle" };
|
|
62
|
-
this.#request = null;
|
|
63
|
-
request.abort.abort();
|
|
64
|
-
}, this.#timeoutMs);
|
|
65
|
-
request.timer.unref?.();
|
|
66
|
-
try {
|
|
67
|
-
void this.options.generator.generate({ identity, signal: request.abort.signal })
|
|
68
|
-
.then(result => this.#receive(request, result), () => this.#failed(request))
|
|
69
|
-
.finally(() => clearTimeout(request.timer));
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
this.#failed(request);
|
|
73
|
-
}
|
|
52
|
+
this.#retiredIdentity = "";
|
|
53
|
+
this.#lifecycle = { identity, settled: false, attempts: 0, retryPending: false, presentationDeferred: false };
|
|
54
|
+
this.#startAttempt("prefetch");
|
|
74
55
|
}
|
|
75
|
-
|
|
76
|
-
|
|
56
|
+
/** Settles an existing prefetch or starts the first request when that eligible prefetch was missed. */
|
|
57
|
+
settle(identity, decision = null) {
|
|
58
|
+
if (this.#disposed || !this.#enabled || identityKey(identity) === this.#retiredIdentity)
|
|
77
59
|
return;
|
|
78
|
-
|
|
60
|
+
const reason = this.#decisionReason(decision);
|
|
61
|
+
const lifecycle = this.#lifecycle;
|
|
62
|
+
if (lifecycle !== null && !samePromptSuggestionIdentity(lifecycle.identity, identity)) {
|
|
79
63
|
if (this.#request)
|
|
80
64
|
this.#finish(this.#request, "stale-result", "stale-identity");
|
|
81
|
-
this
|
|
65
|
+
this.#reset(true, true);
|
|
66
|
+
}
|
|
67
|
+
if (reason !== null) {
|
|
68
|
+
if (this.#lifecycle !== null)
|
|
69
|
+
this.#reset(true, true);
|
|
70
|
+
else
|
|
71
|
+
this.#emit(this.#metadata(identity), "skipped", reason);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (this.#lifecycle === null) {
|
|
75
|
+
this.#retiredIdentity = "";
|
|
76
|
+
this.#lifecycle = { identity, settled: true, attempts: 0, retryPending: false, presentationDeferred: false };
|
|
77
|
+
this.#startAttempt("settlement");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.#lifecycle.settled = true;
|
|
81
|
+
if (this.#lifecycle.retryPending) {
|
|
82
|
+
this.#startAttempt("retry");
|
|
82
83
|
return;
|
|
83
84
|
}
|
|
84
85
|
if (this.#state.status === "generating")
|
|
@@ -86,23 +87,23 @@ export class ContextualPromptSuggestionController {
|
|
|
86
87
|
else if (this.#state.status === "prepared" && this.#request)
|
|
87
88
|
this.#show(this.#request, this.#state.text);
|
|
88
89
|
}
|
|
89
|
-
accept() { this.#
|
|
90
|
-
invalidate() { this.#
|
|
91
|
-
/** Retires
|
|
90
|
+
accept() { this.#reset(false, true); }
|
|
91
|
+
invalidate() { this.#reset(true, true); }
|
|
92
|
+
/** Retires generating or scheduled recovery; a prepared or available suggestion survives a draft edit. */
|
|
92
93
|
abortPending() {
|
|
93
|
-
if (this.#state.status !== "generating")
|
|
94
|
-
return;
|
|
95
|
-
const request = this.#request;
|
|
96
|
-
this.#request = null;
|
|
97
|
-
this.#state = { status: "idle" };
|
|
98
|
-
if (request === null)
|
|
94
|
+
if (this.#state.status !== "generating" && !(this.#state.status === "idle" && this.#lifecycle?.retryPending))
|
|
99
95
|
return;
|
|
100
|
-
this.#
|
|
101
|
-
request.abort.abort();
|
|
96
|
+
this.#reset(false, true);
|
|
102
97
|
}
|
|
103
|
-
/** Reasserts retained ghost text
|
|
98
|
+
/** Reasserts retained ghost text or retries a deferred presentation when the editor is eligible. */
|
|
104
99
|
restoreAvailable() {
|
|
105
|
-
if (this.#disposed || !this.#enabled
|
|
100
|
+
if (this.#disposed || !this.#enabled)
|
|
101
|
+
return;
|
|
102
|
+
if (this.#state.status === "prepared" && this.#lifecycle?.settled && this.#request) {
|
|
103
|
+
this.#show(this.#request, this.#state.text);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (this.#state.status !== "available")
|
|
106
107
|
return;
|
|
107
108
|
const reason = this.options.surface.presentationBlockReason?.(this.#state.identity)
|
|
108
109
|
?? (this.options.surface.canPresent(this.#state.identity) ? null : "presentation-unavailable");
|
|
@@ -114,16 +115,64 @@ export class ContextualPromptSuggestionController {
|
|
|
114
115
|
this.#disposed = true;
|
|
115
116
|
this.invalidate();
|
|
116
117
|
this.#lastConsidered = "";
|
|
118
|
+
this.#retiredIdentity = "";
|
|
117
119
|
this.#sessionKey = "";
|
|
118
120
|
}
|
|
119
|
-
#
|
|
121
|
+
#decisionReason(decision) {
|
|
122
|
+
return this.#disposed ? "disposed" : !this.#enabled ? "disabled"
|
|
123
|
+
: decision === false ? "ineligible" : decision === true ? null : decision;
|
|
124
|
+
}
|
|
125
|
+
#startAttempt(trigger) {
|
|
126
|
+
const lifecycle = this.#lifecycle;
|
|
127
|
+
if (lifecycle === null || this.#request !== null || lifecycle.attempts >= 2)
|
|
128
|
+
return;
|
|
129
|
+
lifecycle.retryPending = false;
|
|
130
|
+
lifecycle.presentationDeferred = false;
|
|
131
|
+
const attempt = ++lifecycle.attempts;
|
|
132
|
+
let reasoning = "unavailable";
|
|
133
|
+
try {
|
|
134
|
+
reasoning = this.options.generator.suggestionReasoningPolicy?.() ?? "unavailable";
|
|
135
|
+
}
|
|
136
|
+
catch { /* Security: isolate diagnostic metadata failure. */ }
|
|
137
|
+
const request = {
|
|
138
|
+
identity: lifecycle.identity,
|
|
139
|
+
metadata: { ...this.#metadata(lifecycle.identity), request: ++this.#requestSequence, reasoning, attempt, trigger },
|
|
140
|
+
abort: new AbortController(), startedAt: this.#now(), terminal: false,
|
|
141
|
+
};
|
|
142
|
+
this.#request = request;
|
|
143
|
+
this.#state = { status: "generating", identity: lifecycle.identity, settled: lifecycle.settled };
|
|
144
|
+
this.#emit(request.metadata, "started");
|
|
145
|
+
request.timer = setTimeout(() => {
|
|
146
|
+
if (this.#request !== request || this.#state.status !== "generating")
|
|
147
|
+
return;
|
|
148
|
+
this.#finish(request, "timeout");
|
|
149
|
+
this.#request = null;
|
|
150
|
+
this.#state = { status: "idle" };
|
|
151
|
+
request.abort.abort();
|
|
152
|
+
this.#completeNoCandidate(request, "timeout");
|
|
153
|
+
}, this.#timeoutMs);
|
|
154
|
+
request.timer.unref?.();
|
|
155
|
+
try {
|
|
156
|
+
void this.options.generator.generate({ identity: lifecycle.identity, signal: request.abort.signal })
|
|
157
|
+
.then(result => this.#receive(request, result), () => this.#failed(request))
|
|
158
|
+
.finally(() => clearTimeout(request.timer));
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
this.#failed(request);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
#reset(clearSurface, retire) {
|
|
120
165
|
const hadVisibleSuggestion = this.#state.status === "available";
|
|
166
|
+
const identity = this.#lifecycle?.identity ?? (this.#state.status === "idle" ? null : this.#state.identity);
|
|
121
167
|
const request = this.#request;
|
|
122
168
|
if (request)
|
|
123
169
|
this.#finish(request, "cancelled");
|
|
124
170
|
this.#request = null;
|
|
171
|
+
this.#lifecycle = null;
|
|
125
172
|
this.#state = { status: "idle" };
|
|
126
173
|
request?.abort.abort();
|
|
174
|
+
if (retire && identity !== null)
|
|
175
|
+
this.#retiredIdentity = identityKey(identity);
|
|
127
176
|
if (clearSurface && hadVisibleSuggestion) {
|
|
128
177
|
this.options.surface.clear();
|
|
129
178
|
this.options.surface.requestRender();
|
|
@@ -137,6 +186,7 @@ export class ContextualPromptSuggestionController {
|
|
|
137
186
|
this.#finish(request, "provider-failure");
|
|
138
187
|
this.#request = null;
|
|
139
188
|
this.#state = { status: "idle" };
|
|
189
|
+
this.#completeNoCandidate(request, "provider-failure");
|
|
140
190
|
}
|
|
141
191
|
#receive(request, result) {
|
|
142
192
|
if (this.#disposed || this.#request !== request || request.terminal || this.#state.status !== "generating") {
|
|
@@ -148,41 +198,76 @@ export class ContextualPromptSuggestionController {
|
|
|
148
198
|
}
|
|
149
199
|
catch {
|
|
150
200
|
this.#finish(request, "rejected");
|
|
151
|
-
this
|
|
201
|
+
this.#request = null;
|
|
202
|
+
this.#state = { status: "idle" };
|
|
203
|
+
this.#completeNoCandidate(request, "rejected");
|
|
152
204
|
return;
|
|
153
205
|
}
|
|
154
206
|
if (!samePromptSuggestionIdentity(request.identity, result.identity)) {
|
|
155
207
|
this.#finish(request, "stale-result", "stale-identity");
|
|
156
|
-
this
|
|
208
|
+
this.#reset(true, true);
|
|
157
209
|
return;
|
|
158
210
|
}
|
|
159
211
|
if (result.outcome !== "candidate") {
|
|
160
212
|
this.#finish(request, result.outcome);
|
|
161
|
-
this
|
|
213
|
+
this.#request = null;
|
|
214
|
+
this.#state = { status: "idle" };
|
|
215
|
+
if (RETRYABLE_OUTCOMES.has(result.outcome))
|
|
216
|
+
this.#completeNoCandidate(request, result.outcome);
|
|
217
|
+
else {
|
|
218
|
+
this.#retiredIdentity = identityKey(request.identity);
|
|
219
|
+
this.#lifecycle = null;
|
|
220
|
+
}
|
|
162
221
|
return;
|
|
163
222
|
}
|
|
164
223
|
const text = normalizePromptSuggestionCandidate(result.text);
|
|
165
224
|
if (text === null) {
|
|
166
225
|
this.#finish(request, "rejected");
|
|
167
|
-
this
|
|
226
|
+
this.#request = null;
|
|
227
|
+
this.#state = { status: "idle" };
|
|
228
|
+
this.#completeNoCandidate(request, "rejected");
|
|
168
229
|
return;
|
|
169
230
|
}
|
|
170
231
|
clearTimeout(request.timer);
|
|
171
|
-
if (this.#
|
|
232
|
+
if (this.#lifecycle?.settled)
|
|
172
233
|
this.#show(request, text);
|
|
173
234
|
else
|
|
174
235
|
this.#state = { status: "prepared", identity: request.identity, text };
|
|
175
236
|
}
|
|
237
|
+
#completeNoCandidate(request, outcome) {
|
|
238
|
+
const lifecycle = this.#lifecycle;
|
|
239
|
+
if (lifecycle === null || !samePromptSuggestionIdentity(lifecycle.identity, request.identity))
|
|
240
|
+
return;
|
|
241
|
+
if (lifecycle.attempts >= 2 || !RETRYABLE_OUTCOMES.has(outcome)) {
|
|
242
|
+
this.#emit({ ...request.metadata, elapsedMs: Math.max(0, this.#now() - request.startedAt) }, "retry-exhausted");
|
|
243
|
+
this.#retiredIdentity = identityKey(request.identity);
|
|
244
|
+
this.#lifecycle = null;
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
lifecycle.retryPending = true;
|
|
248
|
+
if (lifecycle.settled)
|
|
249
|
+
this.#startAttempt("retry");
|
|
250
|
+
}
|
|
176
251
|
#show(request, text) {
|
|
177
252
|
const reason = this.options.surface.presentationBlockReason?.(request.identity)
|
|
178
253
|
?? (this.options.surface.canPresent(request.identity) ? null : "presentation-unavailable");
|
|
254
|
+
if (reason !== null && TRANSIENT_PRESENTATION_REASONS.has(reason)) {
|
|
255
|
+
this.#state = { status: "prepared", identity: request.identity, text };
|
|
256
|
+
if (this.#lifecycle !== null && !this.#lifecycle.presentationDeferred) {
|
|
257
|
+
this.#lifecycle.presentationDeferred = true;
|
|
258
|
+
this.#emit({ ...request.metadata, elapsedMs: Math.max(0, this.#now() - request.startedAt) }, "presentation-deferred", reason);
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
179
262
|
if (reason !== null || !this.options.surface.present(text)) {
|
|
180
263
|
this.#finish(request, "presentation-blocked", reason ?? "presentation-unavailable");
|
|
181
|
-
this
|
|
264
|
+
this.#reset(true, true);
|
|
182
265
|
return;
|
|
183
266
|
}
|
|
184
267
|
this.#state = { status: "available", identity: request.identity, text };
|
|
185
268
|
this.#finish(request, "displayed");
|
|
269
|
+
if (this.#lifecycle !== null)
|
|
270
|
+
this.#lifecycle.presentationDeferred = false;
|
|
186
271
|
this.options.surface.requestRender();
|
|
187
272
|
}
|
|
188
273
|
#finish(request, event, reason) {
|
|
@@ -227,3 +312,8 @@ export function samePromptSuggestionIdentity(left, right) {
|
|
|
227
312
|
function identityKey(identity) {
|
|
228
313
|
return `${identity.sessionId}\0${identity.sessionGeneration}\0${identity.runSequence}\0${identity.responseSequence}\0${identity.model.providerId}\0${identity.model.modelId}`;
|
|
229
314
|
}
|
|
315
|
+
function decisionIdentityKey(identity) {
|
|
316
|
+
return identity.model === null
|
|
317
|
+
? `${identity.sessionId}\0${identity.sessionGeneration}\0${identity.runSequence}\0${identity.responseSequence}\0\0`
|
|
318
|
+
: identityKey(identity);
|
|
319
|
+
}
|
|
@@ -5,6 +5,7 @@ import type { UiRouteHost, UiRouteInput } from "../../ui/apps/contracts.js";
|
|
|
5
5
|
import type { TranscriptViewportFrame, TranscriptViewportFrameDescriptor } from "../../ui/components/transcript-viewport.js";
|
|
6
6
|
import type { PiEngineAdapter } from "../../integrations/pi/engine/adapter.js";
|
|
7
7
|
import type { PiWorkflowMessage, PiWorkflowResult } from "../../integrations/pi/engine/workflows.js";
|
|
8
|
+
import type { PiShellLazySelectorLoader } from "../../integrations/pi/components/lazy-selectors.js";
|
|
8
9
|
import { type PiShellHotkeysPresentation } from "../../integrations/pi/components/shell-presenters-info.js";
|
|
9
10
|
import { type PiShellClipboardContent, type PiShellComponentPort, type PiShellEditorPort, type PiShellExtensionRendererResolver, type PiShellHeaderOptions, type PiShellHeaderPort, type PiShellImageAssetResolver, type PiShellLoadedResourcesPort, type PiShellResourceEntry, type PiShellTranscriptComponentPort } from "../../integrations/pi/components/shell-shared-facade.js";
|
|
10
11
|
import { type PiTuiInputCoordinationScheduler, type PiTuiInputSurfaceKind } from "../../integrations/pi/tui-runtime/input-presentation-coordinator.js";
|
|
@@ -116,6 +117,8 @@ export interface OwnedUiShellDiagnosticOptions {
|
|
|
116
117
|
readonly paste?: (event: PasteEvent) => void;
|
|
117
118
|
/** Test seams for isolated paste preparation: an in-process starter, or the forked helper's entry. */
|
|
118
119
|
readonly pastePreparation?: Omit<PastePreparationClientOptions, "onEvent" | "spareIdleMs">;
|
|
120
|
+
/** Test seam for pending and failed optional selector-module preparation. */
|
|
121
|
+
readonly lazySelectors?: PiShellLazySelectorLoader;
|
|
119
122
|
}
|
|
120
123
|
/** Live prompt-image admission policy supplied only by the bare-A1 profile. */
|
|
121
124
|
export interface OwnedUiShellPromptImagesOptions {
|
|
@@ -169,6 +172,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
|
|
|
169
172
|
readonly onEditorChange?: (text: string) => void;
|
|
170
173
|
readonly onPromptSuggestionAccepted?: (text: string) => void;
|
|
171
174
|
readonly onInputSurfaceChanged?: () => void;
|
|
175
|
+
readonly onPromptSuggestionEligibilityChanged?: () => void;
|
|
172
176
|
readonly onCopyText?: (text: string) => void;
|
|
173
177
|
readonly readClipboardContent?: (signal?: AbortSignal) => Promise<PiShellClipboardContent | null>;
|
|
174
178
|
readonly captureClipboardPaste?: () => PasteSource;
|
|
@@ -242,7 +246,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
|
|
|
242
246
|
maxScroll: number;
|
|
243
247
|
cause: "initial" | "steady" | "dock-input" | "follow-shift" | "detached" | "geometry-change" | null;
|
|
244
248
|
};
|
|
245
|
-
/** Visible non-
|
|
249
|
+
/** Visible non-persistent Steering, alignment, and Working rows, for rendering evidence. */
|
|
246
250
|
viewportTransientTailRowCount(): number;
|
|
247
251
|
hasActiveSelection(): boolean;
|
|
248
252
|
setViewportConfig(config: OwnedUiViewportSettings): void;
|
|
@@ -48,6 +48,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
48
48
|
#viewportTheme;
|
|
49
49
|
#onViewportFrame;
|
|
50
50
|
#onInputSurfaceChanged;
|
|
51
|
+
#onPromptSuggestionEligibilityChanged;
|
|
51
52
|
#componentRuntime;
|
|
52
53
|
#toolsExpanded = false;
|
|
53
54
|
#thinkingVisible = true;
|
|
@@ -107,6 +108,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
107
108
|
this.#componentRuntime = handlers;
|
|
108
109
|
this.#onViewportFrame = handlers.onViewportFrame;
|
|
109
110
|
this.#onInputSurfaceChanged = handlers.onInputSurfaceChanged;
|
|
111
|
+
this.#onPromptSuggestionEligibilityChanged = handlers.onPromptSuggestionEligibilityChanged;
|
|
110
112
|
this.#dockInputReuseEnabled = handlers.enableDockInputReuse ?? true;
|
|
111
113
|
this.header = createPiShellHeader({
|
|
112
114
|
...startup,
|
|
@@ -223,8 +225,8 @@ export class OwnedUiSessionShellRoot {
|
|
|
223
225
|
// Invariant: colours come from the active theme, so rendered rows outlive their revision only
|
|
224
226
|
// until the theme under them changes.
|
|
225
227
|
this.#themeUnsubscribe = onPiThemeChange(() => {
|
|
226
|
-
this
|
|
227
|
-
this.#
|
|
228
|
+
this.invalidate();
|
|
229
|
+
this.#componentRuntime.requestRender(true);
|
|
228
230
|
});
|
|
229
231
|
}
|
|
230
232
|
setEditorPaddingX(padding) {
|
|
@@ -327,6 +329,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
327
329
|
this.editor.setPromptSuggestion(text);
|
|
328
330
|
}
|
|
329
331
|
update(view) {
|
|
332
|
+
const becameReady = this.#view.lifecycle !== "ready" && view.lifecycle === "ready";
|
|
330
333
|
if (view.diagnostics.length !== this.#view.diagnostics.length
|
|
331
334
|
|| view.diagnostics.some((diagnostic, index) => diagnostic.sequence !== this.#view.diagnostics[index]?.sequence)) {
|
|
332
335
|
this.#documentLayouts.clear();
|
|
@@ -349,6 +352,8 @@ export class OwnedUiSessionShellRoot {
|
|
|
349
352
|
this.editor.setThinkingLevel(view.thinkingLevel);
|
|
350
353
|
// Performance: semantic updates already invalidate affected transcript blocks. Chrome is a separate authority.
|
|
351
354
|
this.#invalidateChrome();
|
|
355
|
+
if (becameReady)
|
|
356
|
+
this.#onPromptSuggestionEligibilityChanged?.();
|
|
352
357
|
}
|
|
353
358
|
/**
|
|
354
359
|
* Applies one block: its component is created or updated in place and the order grows
|
|
@@ -458,13 +463,13 @@ export class OwnedUiSessionShellRoot {
|
|
|
458
463
|
if (frame === null) {
|
|
459
464
|
frame = this.#viewportController.compose({
|
|
460
465
|
// Performance: do not copy the complete document on unchanged dock-only input.
|
|
461
|
-
// Steering and Working retain their one non-
|
|
466
|
+
// Steering and Working retain their one non-persistent viewport-tail ownership.
|
|
462
467
|
documentRows: [...document.rows, ...steeringRows, ...statusRows],
|
|
463
468
|
...(this.#viewportController.transcriptPointerSelecting
|
|
464
469
|
? { paintDocumentRow: heldNativeHyperlinkStyle }
|
|
465
470
|
: {}),
|
|
466
|
-
// Invariant:
|
|
467
|
-
//
|
|
471
|
+
// Invariant: this boundary tracks persistent transcript rows for streaming and
|
|
472
|
+
// geometry; transient Steering and Working rows remain complete-frame selectable.
|
|
468
473
|
selectableDocumentRowCount,
|
|
469
474
|
...(document.liveTailStartRow === undefined ? {} : { liveTailStartRow: document.liveTailStartRow }),
|
|
470
475
|
bottomAlignedTailRowCount: steeringRows.length + statusRows.length,
|
|
@@ -527,7 +532,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
527
532
|
cause: frame?.descriptor.cause ?? null,
|
|
528
533
|
};
|
|
529
534
|
}
|
|
530
|
-
/** Visible non-
|
|
535
|
+
/** Visible non-persistent Steering, alignment, and Working rows, for rendering evidence. */
|
|
531
536
|
viewportTransientTailRowCount() {
|
|
532
537
|
return this.#viewportController.frame?.hits.transientTail.length ?? 0;
|
|
533
538
|
}
|
|
@@ -1179,6 +1184,8 @@ export class OwnedUiSessionShellRoot {
|
|
|
1179
1184
|
}
|
|
1180
1185
|
setFocused(focused) {
|
|
1181
1186
|
this.#inputSurface.setFocused?.(focused);
|
|
1187
|
+
if (focused)
|
|
1188
|
+
this.#onPromptSuggestionEligibilityChanged?.();
|
|
1182
1189
|
}
|
|
1183
1190
|
dispose() {
|
|
1184
1191
|
this.#clearCopyAcknowledgement(false);
|