@timurproko/a1 0.1.8-dev.536 → 0.1.8-dev.546
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 +4 -0
- package/bin/update-recovery.js +25 -3
- package/dist/app/session-shell/session-shell-root.js +15 -5
- package/dist/cli/dispatch.d.ts +5 -0
- package/dist/cli/dispatch.js +26 -0
- package/dist/cli/session-context.d.ts +9 -0
- package/dist/cli/session-context.js +29 -0
- package/dist/composition/owned-ui.js +6 -2
- package/dist/contracts/owned-ui/model.d.ts +5 -0
- package/dist/contracts/owned-ui/validation.js +6 -0
- package/dist/contracts/presentation/index.d.ts +13 -0
- package/dist/contracts/presentation/index.js +30 -0
- package/dist/features/launch/index.d.ts +2 -0
- package/dist/features/launch/index.js +1 -0
- package/dist/features/launch/session-context.d.ts +8 -0
- package/dist/features/launch/session-context.js +8 -0
- package/dist/features/owned-ui/project-trust-prompt.js +1 -1
- package/dist/features/owned-ui/reference-screen-app.js +7 -8
- package/dist/features/owned-ui/settings-app.js +9 -5
- package/dist/foundation/lifecycle/index.d.ts +2 -0
- package/dist/foundation/lifecycle/index.js +1 -0
- package/dist/foundation/lifecycle/session-repository-context.d.ts +18 -0
- package/dist/foundation/lifecycle/session-repository-context.js +173 -0
- package/dist/foundation/release/index.d.ts +2 -2
- package/dist/foundation/release/index.js +1 -1
- package/dist/foundation/release/update-recovery.d.ts +7 -1
- package/dist/foundation/release/update-recovery.js +39 -9
- package/dist/foundation/release/update.d.ts +7 -0
- package/dist/foundation/release/update.js +115 -24
- package/dist/integrations/pi/components/models-dialog.js +9 -9
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +27 -3
- package/dist/integrations/pi/components/shell-extension-ui.js +4 -2
- package/dist/integrations/pi/components/shell-footer-status.js +1 -1
- package/dist/integrations/pi/components/shell-presenters-transcript.js +4 -2
- package/dist/integrations/pi/components/skills-command.d.ts +2 -0
- package/dist/integrations/pi/components/skills-command.js +11 -10
- package/dist/integrations/pi/components/skills-dialog.js +8 -3
- package/dist/integrations/pi/components/theme.d.ts +4 -0
- package/dist/integrations/pi/components/theme.js +10 -0
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +4 -4
- package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +2 -2
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +9 -10
- package/dist/integrations/pi/components/upstream/components/extension-input.d.ts +17 -0
- package/dist/integrations/pi/components/upstream/components/extension-input.js +72 -0
- package/dist/integrations/pi/components/upstream/components/extension-selector.d.ts +15 -0
- package/dist/integrations/pi/components/upstream/components/extension-selector.js +92 -0
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +3 -2
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +16 -15
- package/dist/integrations/pi/components/upstream/components/session-footer.js +25 -9
- package/dist/integrations/pi/components/upstream/components/session-selector.js +25 -16
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +6 -4
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +22 -12
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +3 -2
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +14 -12
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +11 -5
- package/dist/integrations/pi/engine/adapter.d.ts +2 -1
- package/dist/integrations/pi/engine/adapter.js +2 -0
- package/dist/integrations/pi/engine/message-values.js +1 -0
- package/dist/integrations/pi/engine/session-runtime.d.ts +11 -2
- package/dist/integrations/pi/engine/session-runtime.js +136 -33
- package/dist/integrations/pi/engine/transcript-projection.js +35 -0
- package/dist/integrations/pi/startup-public.manifest.json +2 -0
- 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 +1 -1
- package/dist/product-identity.js +1 -1
- package/dist/product-identity.json +2 -1
- package/dist/ui/components/dialog-panel.d.ts +2 -1
- package/dist/ui/components/dialog-panel.js +6 -1
- package/dist/ui/components/index.d.ts +2 -0
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/shortcut-hints.d.ts +7 -0
- package/dist/ui/components/shortcut-hints.js +12 -0
- package/dist/ui/components/shortcuts.d.ts +2 -0
- package/dist/ui/components/shortcuts.js +5 -2
- package/dist/ui/components/transcript-viewport.d.ts +1 -1
- package/docs/ci-release-runbook.md +9 -7
- package/docs/openspec-archive-automation.md +6 -0
- package/docs/validation.md +5 -2
- package/package.json +1 -1
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
* Modifications: Preserve the searchable thinking-level selector, current/default semantics,
|
|
5
5
|
* selection, save, cancellation, and focus while accepting the active bare-A1 cycle-key label from the
|
|
6
6
|
* shell, styling the title with the established bold semantic accent treatment, placing its muted hint
|
|
7
|
-
* directly below it, deduplicating levels, and rendering aligned muted descriptions after
|
|
8
|
-
*
|
|
9
|
-
* mode
|
|
10
|
-
*
|
|
7
|
+
* directly below it, deduplicating levels, and rendering aligned muted descriptions after adjacent
|
|
8
|
+
* active and bracketed default markers. All list and border colors use the owned theme and its
|
|
9
|
+
* explicit color mode, and the footer uses the shared bare-A1 modal shortcut row. The comparison
|
|
10
|
+
* profile retains the public pinned component.
|
|
11
|
+
* Deviations: owned-modal-shortcut-hints, owned-level-cycle-shortcut, owned-thinking-selector-heading.
|
|
11
12
|
*/
|
|
12
13
|
import { Container, fuzzyFilter, getKeybindings, Input, SelectList, Spacer, Text, } from "@earendil-works/pi-tui";
|
|
13
14
|
import { DynamicBorder } from "../../../startup-public.js";
|
|
14
|
-
import { piTheme } from "
|
|
15
|
+
import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
|
|
15
16
|
const THINKING_SELECT_LIST_LAYOUT = {
|
|
16
17
|
minPrimaryColumnWidth: 12,
|
|
17
18
|
maxPrimaryColumnWidth: 32,
|
|
@@ -35,6 +36,7 @@ export class OwnedThinkingSelectorComponent extends Container {
|
|
|
35
36
|
onCancel;
|
|
36
37
|
onSelectAsDefault;
|
|
37
38
|
currentLevel;
|
|
39
|
+
defaultThinkingLevel;
|
|
38
40
|
_focused = false;
|
|
39
41
|
get focused() {
|
|
40
42
|
return this._focused;
|
|
@@ -49,10 +51,11 @@ export class OwnedThinkingSelectorComponent extends Container {
|
|
|
49
51
|
this.onCancel = onCancel;
|
|
50
52
|
this.onSelectAsDefault = onSelectAsDefault;
|
|
51
53
|
this.currentLevel = currentLevel;
|
|
54
|
+
this.defaultThinkingLevel = defaultThinkingLevel;
|
|
52
55
|
this.allItems = [...new Set(availableLevels)].map((level) => ({
|
|
53
56
|
value: level,
|
|
54
57
|
label: level,
|
|
55
|
-
description:
|
|
58
|
+
description: LEVEL_DESCRIPTIONS[level],
|
|
56
59
|
}));
|
|
57
60
|
this.addChild(new DynamicBorder((text) => piTheme().fg("border", text)));
|
|
58
61
|
this.addChild(new Spacer(1));
|
|
@@ -67,7 +70,11 @@ export class OwnedThinkingSelectorComponent extends Container {
|
|
|
67
70
|
this.selectListChildIndex = this.children.length;
|
|
68
71
|
this.addChild(this.selectList);
|
|
69
72
|
this.addChild(new Spacer(1));
|
|
70
|
-
this.addChild(new Text(
|
|
73
|
+
this.addChild(new Text(renderPiModalShortcutHints([
|
|
74
|
+
{ key: this.keyDisplayText("tui.select.confirm"), action: "to select" },
|
|
75
|
+
{ key: this.keyDisplayText("app.thinking.save"), action: "to set as default" },
|
|
76
|
+
{ key: this.keyDisplayText("tui.select.cancel"), action: "to cancel" },
|
|
77
|
+
], 2), 0, 0));
|
|
71
78
|
this.addChild(new DynamicBorder((text) => piTheme().fg("border", text)));
|
|
72
79
|
}
|
|
73
80
|
keyDisplayText(keybinding) {
|
|
@@ -79,17 +86,20 @@ export class OwnedThinkingSelectorComponent extends Container {
|
|
|
79
86
|
.join("/");
|
|
80
87
|
}
|
|
81
88
|
buildSelectList(items, preselect) {
|
|
89
|
+
const markerWidth = (item) => (item.value === this.currentLevel ? 2 : 0) + (item.value === this.defaultThinkingLevel ? 10 : 0);
|
|
82
90
|
const primaryWidth = this.allItems.reduce((widest, item) => {
|
|
83
91
|
const level = item.label ?? item.value;
|
|
84
|
-
return Math.max(widest, level.length + (item
|
|
92
|
+
return Math.max(widest, level.length + markerWidth(item));
|
|
85
93
|
}, 0);
|
|
86
94
|
const themedItems = items.map((item) => {
|
|
87
95
|
const level = item.label ?? item.value;
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
96
|
+
const currentMarker = item.value === this.currentLevel ? ` ${piTheme().fg("success", "✓")}` : "";
|
|
97
|
+
const defaultMarker = item.value === this.defaultThinkingLevel
|
|
98
|
+
? ` ${piTheme().fg("muted", "[default]")}`
|
|
99
|
+
: "";
|
|
100
|
+
const separator = " ".repeat(Math.max(1, primaryWidth - level.length - markerWidth(item) + 1));
|
|
91
101
|
const description = item.description ? piTheme().fg("muted", item.description) : "";
|
|
92
|
-
return { value: item.value, label: `${level}${currentMarker}${separator}${description}` };
|
|
102
|
+
return { value: item.value, label: `${level}${currentMarker}${defaultMarker}${separator}${description}` };
|
|
93
103
|
});
|
|
94
104
|
// Invariant: the bare selector uses the owned theme's explicit color mode, not upstream's host detection.
|
|
95
105
|
const list = new SelectList(themedItems, Math.max(1, themedItems.length), {
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* packages/coding-agent/src/modes/interactive/components/tree-selector.ts.
|
|
4
4
|
* Modifications: Source-synchronized tree selector port: preserve filtering, folding, labels, copying,
|
|
5
5
|
* tree navigation, key hints, focus, and viewport behavior while remapping public types/components
|
|
6
|
-
* plus owned keybindings/theme helpers required to avoid the pinned package nested pi-tui singleton
|
|
7
|
-
*
|
|
6
|
+
* plus owned keybindings/theme helpers required to avoid the pinned package nested pi-tui singleton;
|
|
7
|
+
* bare A1 uses the shared semantic modal shortcut row.
|
|
8
|
+
* Deviations: owned-modal-shortcut-hints.
|
|
8
9
|
*/
|
|
9
10
|
import { type Component, Container, type Focusable } from "@earendil-works/pi-tui";
|
|
10
11
|
import { type SessionTreeNode } from "@earendil-works/pi-coding-agent";
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
* packages/coding-agent/src/modes/interactive/components/tree-selector.ts.
|
|
4
4
|
* Modifications: Source-synchronized tree selector port: preserve filtering, folding, labels, copying,
|
|
5
5
|
* tree navigation, key hints, focus, and viewport behavior while remapping public types/components
|
|
6
|
-
* plus owned keybindings/theme helpers required to avoid the pinned package nested pi-tui singleton
|
|
7
|
-
*
|
|
6
|
+
* plus owned keybindings/theme helpers required to avoid the pinned package nested pi-tui singleton;
|
|
7
|
+
* bare A1 uses the shared semantic modal shortcut row.
|
|
8
|
+
* Deviations: owned-modal-shortcut-hints.
|
|
8
9
|
*/
|
|
9
10
|
import { Container, Input, Spacer, sliceByColumn, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
|
|
10
11
|
import { DynamicBorder } from "../../../startup-public.js";
|
|
11
12
|
import { KeybindingsManager } from "../adjacent/core/keybindings.js";
|
|
12
|
-
import { piTheme } from "
|
|
13
|
+
import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
|
|
13
14
|
const theme = new Proxy({}, {
|
|
14
15
|
get(_target, property) {
|
|
15
16
|
const active = piTheme();
|
|
@@ -24,8 +25,8 @@ function formatKeyText(key) {
|
|
|
24
25
|
.map((value) => value.split("+").map((part) => process.platform === "darwin" && part.toLowerCase() === "alt" ? "option" : part).join("+"))
|
|
25
26
|
.join("/");
|
|
26
27
|
}
|
|
27
|
-
function
|
|
28
|
-
return
|
|
28
|
+
function shortcutHint(keybinding, action) {
|
|
29
|
+
return { key: formatKeyText(treeKeybindings.getKeys(keybinding).join("/")), action };
|
|
29
30
|
}
|
|
30
31
|
const TREE_GUTTER_WIDTH = 2;
|
|
31
32
|
const MIN_VISIBLE_ANCHOR_CONTENT_WIDTH = 4;
|
|
@@ -1075,14 +1076,12 @@ class TreeHelp {
|
|
|
1075
1076
|
invalidate() { }
|
|
1076
1077
|
render(width) {
|
|
1077
1078
|
const items = TREE_HELP_ITEMS.map(({ keys, label, labelFirst }) => {
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
return label;
|
|
1081
|
-
return labelFirst ? `${label} ${text}` : `${text} ${label}`;
|
|
1079
|
+
const key = formatHelpKeys(keys);
|
|
1080
|
+
return renderPiModalShortcutHints([key ? { key, action: label, ...(labelFirst === undefined ? {} : { actionFirst: labelFirst }) } : { action: label }]);
|
|
1082
1081
|
});
|
|
1083
1082
|
const availableWidth = Math.max(1, width);
|
|
1084
1083
|
const indent = " ";
|
|
1085
|
-
const separator = "
|
|
1084
|
+
const separator = " ";
|
|
1086
1085
|
const lines = [];
|
|
1087
1086
|
let currentLine = "";
|
|
1088
1087
|
for (const item of items) {
|
|
@@ -1101,7 +1100,7 @@ class TreeHelp {
|
|
|
1101
1100
|
if (currentLine) {
|
|
1102
1101
|
lines.push(...wrapTextWithAnsi(currentLine.trimEnd(), availableWidth));
|
|
1103
1102
|
}
|
|
1104
|
-
return lines
|
|
1103
|
+
return lines;
|
|
1105
1104
|
}
|
|
1106
1105
|
}
|
|
1107
1106
|
const TREE_HELP_ITEMS = [
|
|
@@ -1184,7 +1183,10 @@ class LabelInput {
|
|
|
1184
1183
|
const availableWidth = width - indent.length;
|
|
1185
1184
|
lines.push(truncateToWidth(`${indent}${theme.fg("muted", "Label (empty to remove):")}`, width));
|
|
1186
1185
|
lines.push(...this.input.render(availableWidth).map((line) => truncateToWidth(`${indent}${line}`, width)));
|
|
1187
|
-
lines.push(truncateToWidth(
|
|
1186
|
+
lines.push(truncateToWidth(renderPiModalShortcutHints([
|
|
1187
|
+
shortcutHint("tui.select.confirm", "save"),
|
|
1188
|
+
shortcutHint("tui.select.cancel", "cancel"),
|
|
1189
|
+
], indent.length), width));
|
|
1188
1190
|
return lines;
|
|
1189
1191
|
}
|
|
1190
1192
|
handleInput(keyData) {
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* Provenance: @earendil-works/pi-coding-agent 0.87.0 (MIT), commit 16787ad5b2dc748047f314ca1bfe7708f30f54f3,
|
|
3
3
|
* packages/coding-agent/src/modes/interactive/components/trust-selector.ts.
|
|
4
4
|
* Modifications: Mechanical source-synchronized trust selector port with injected public
|
|
5
|
-
* ProjectTrustStore-derived options
|
|
6
|
-
*
|
|
5
|
+
* ProjectTrustStore-derived options, remapped owned theme imports, and the shared bare-A1 modal
|
|
6
|
+
* shortcut row.
|
|
7
|
+
* Deviations: owned-modal-shortcut-hints.
|
|
7
8
|
*/
|
|
8
|
-
import { DynamicBorder
|
|
9
|
+
import { DynamicBorder } from "../../../startup-public.js";
|
|
9
10
|
import { Container, getKeybindings, Spacer, Text } from "@earendil-works/pi-tui";
|
|
10
|
-
import { piTheme } from "
|
|
11
|
+
import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
|
|
11
12
|
function formatDecision(trustPath, decision) {
|
|
12
13
|
if (decision === null)
|
|
13
14
|
return "none";
|
|
@@ -39,7 +40,12 @@ export class TrustSelectorComponent extends Container {
|
|
|
39
40
|
this.listContainer = new Container();
|
|
40
41
|
this.addChild(this.listContainer);
|
|
41
42
|
this.addChild(new Spacer(1));
|
|
42
|
-
|
|
43
|
+
const keybindings = getKeybindings();
|
|
44
|
+
this.addChild(new Text(renderPiModalShortcutHints([
|
|
45
|
+
{ key: "↑↓", action: "navigate" },
|
|
46
|
+
{ key: keybindings.getKeys("tui.select.confirm").join("/"), action: "save" },
|
|
47
|
+
{ key: keybindings.getKeys("tui.select.cancel").join("/"), action: "cancel" },
|
|
48
|
+
]), 1, 0));
|
|
43
49
|
this.addChild(new Spacer(1));
|
|
44
50
|
this.addChild(new DynamicBorder());
|
|
45
51
|
this.updateList(options.savedDecision);
|
|
@@ -9,7 +9,7 @@ import type { AgentSettingOwner } from "../../../contracts/agent-engine/index.js
|
|
|
9
9
|
import { type AdapterCommandResult } from "./command-dispatch.js";
|
|
10
10
|
import { type OwnedPiVisualExtensionSupport } from "./extension-ui-binding.js";
|
|
11
11
|
import { type OwnedPiExtensionResourceSummary, type OwnedPiResourceSummary } from "./resource-catalog.js";
|
|
12
|
-
import { type PiEnginePackageUpdateProbe, type PiEngineRuntimeFactory } from "./session-runtime.js";
|
|
12
|
+
import { type PiEnginePackageUpdateProbe, type PiEngineRuntimeFactory, type PiRepositoryContextReader } from "./session-runtime.js";
|
|
13
13
|
import { PiWorkflowContexts } from "./workflow-contexts.js";
|
|
14
14
|
export type { OwnedPiExtensionResourceSummary, OwnedPiExtensionSourceSummary, OwnedPiResourceSummary } from "./resource-catalog.js";
|
|
15
15
|
export type { PiEngineRuntimeFactory, PiEngineRuntimeFactoryInput } from "./session-runtime.js";
|
|
@@ -41,6 +41,7 @@ export interface PiEngineAdapterOptions {
|
|
|
41
41
|
* mode. Returns display names of packages with updates available.
|
|
42
42
|
*/
|
|
43
43
|
readonly checkPackageUpdates?: PiEnginePackageUpdateProbe;
|
|
44
|
+
readonly repositoryContextReader?: PiRepositoryContextReader;
|
|
44
45
|
}
|
|
45
46
|
/** Owns the pinned Pi session lifecycle and translates its events into the neutral agent-engine contract. */
|
|
46
47
|
export declare class PiEngineAdapter implements OwnedUiPromptSuggestionGeneratorPort {
|
|
@@ -133,6 +133,7 @@ export class PiEngineAdapter {
|
|
|
133
133
|
projectTrustPrompt: options.projectTrustPrompt,
|
|
134
134
|
createRuntime: options.createRuntime,
|
|
135
135
|
checkPackageUpdates: options.checkPackageUpdates,
|
|
136
|
+
...(options.repositoryContextReader === undefined ? {} : { repositoryContextReader: options.repositoryContextReader }),
|
|
136
137
|
host: this.#workflowHost,
|
|
137
138
|
}, {
|
|
138
139
|
disposed: () => this.#disposed,
|
|
@@ -428,6 +429,7 @@ export class PiEngineAdapter {
|
|
|
428
429
|
usage: this.#usageCache ??= readUsageView(this.#engine.session, this.#engine.runtime, this.#activeModel),
|
|
429
430
|
footer: {
|
|
430
431
|
branch: this.#engine.gitBranch,
|
|
432
|
+
repositoryPath: this.#engine.repositoryCwd,
|
|
431
433
|
pullRequest: this.#engine.pullRequest,
|
|
432
434
|
sessionName: this.#engine.session?.sessionManager?.getSessionName() ?? null,
|
|
433
435
|
availableProviderCount: new Set(this.#engine.runtime?.services.modelRuntime.getAvailableSnapshot?.().map(model => model.provider).filter(provider => provider !== undefined) ?? []).size,
|
|
@@ -29,6 +29,7 @@ export function sameBlockContent(left, right) {
|
|
|
29
29
|
&& left.status === right.status
|
|
30
30
|
&& left.title === right.title
|
|
31
31
|
&& left.text === right.text
|
|
32
|
+
&& sameValue(left.userPresentation, right.userPresentation)
|
|
32
33
|
&& sameValue(left.toolState, right.toolState)
|
|
33
34
|
&& sameValue(left.toolRendering, right.toolRendering)
|
|
34
35
|
&& sameValue(left.payload, right.payload)
|
|
@@ -16,6 +16,12 @@ export interface PiEngineRuntimeFactoryInput {
|
|
|
16
16
|
}
|
|
17
17
|
export type PiEngineRuntimeFactory = (input: PiEngineRuntimeFactoryInput) => Promise<AgentSessionRuntime>;
|
|
18
18
|
export type PiEnginePackageUpdateProbe = (settingsManager: AgentSessionServices["settingsManager"]) => Promise<readonly string[]>;
|
|
19
|
+
export interface PiRepositoryContextSelection {
|
|
20
|
+
readonly cwd: string;
|
|
21
|
+
readonly branch: string;
|
|
22
|
+
}
|
|
23
|
+
export type PiRepositoryContextReader = (sessionId: string, sessionFile: string, signal: AbortSignal) => Promise<PiRepositoryContextSelection | null>;
|
|
24
|
+
export type PiGitBranchReader = (cwd: string, signal: AbortSignal) => Promise<string | null>;
|
|
19
25
|
export interface PiEngineRuntimeOptions {
|
|
20
26
|
readonly cwd: string;
|
|
21
27
|
readonly agentDir: string;
|
|
@@ -28,12 +34,14 @@ export interface PiEngineRuntimeOptions {
|
|
|
28
34
|
readonly checkPackageUpdates: PiEnginePackageUpdateProbe | undefined;
|
|
29
35
|
readonly pullRequestProbe?: PiPullRequestProbe;
|
|
30
36
|
readonly pullRequestRefreshMs?: number;
|
|
31
|
-
readonly
|
|
37
|
+
readonly repositoryContextPollMs?: number;
|
|
38
|
+
readonly repositoryContextReader?: PiRepositoryContextReader;
|
|
39
|
+
readonly gitBranchReader?: PiGitBranchReader;
|
|
32
40
|
readonly host: PiWorkflowHost;
|
|
33
41
|
}
|
|
34
42
|
export interface PiEngineRuntimePorts {
|
|
35
43
|
disposed(): boolean;
|
|
36
|
-
/** The runtime exists and its cwd
|
|
44
|
+
/** The runtime exists and its startup cwd is known; repository metadata resolves after binding. */
|
|
37
45
|
started(runtime: AgentSessionRuntime): void;
|
|
38
46
|
/** True while a runtime-initiated rebind must be ignored: delivery overload, stopped admission, or disposal. */
|
|
39
47
|
rebindBlocked(): boolean;
|
|
@@ -65,6 +73,7 @@ export declare class PiEngineRuntime {
|
|
|
65
73
|
/** Actual session replacements, excluding delivery-only invalidation of callbacks. */
|
|
66
74
|
get bindingGeneration(): number;
|
|
67
75
|
get cwd(): string;
|
|
76
|
+
get repositoryCwd(): string;
|
|
68
77
|
get gitBranch(): string | null;
|
|
69
78
|
get pullRequest(): PiPullRequestIdentity | null;
|
|
70
79
|
get sessionCommands(): PiSessionCommandIntegration | undefined;
|
|
@@ -9,6 +9,9 @@ import { DefaultPackageManager, VERSION } from "../startup-public.js";
|
|
|
9
9
|
import { readOpenPullRequest, } from "./repository-pr.js";
|
|
10
10
|
const execFileAsync = promisify(execFile);
|
|
11
11
|
const PULL_REQUEST_REFRESH_MS = 60_000;
|
|
12
|
+
const REPOSITORY_CONTEXT_POLL_MS = 5_000;
|
|
13
|
+
const GIT_BRANCH_TIMEOUT_MS = 5_000;
|
|
14
|
+
const GIT_BRANCH_MAX_BUFFER_BYTES = 4 * 1024;
|
|
12
15
|
/**
|
|
13
16
|
* The pinned Pi runtime and the session bound to it: creation through the runtime factory, the
|
|
14
17
|
* session generation that stamps and invalidates events, the subscription that forwards session
|
|
@@ -22,6 +25,8 @@ export class PiEngineRuntime {
|
|
|
22
25
|
#checkPackageUpdates;
|
|
23
26
|
#pullRequestProbe;
|
|
24
27
|
#pullRequestRefreshMs;
|
|
28
|
+
#repositoryContextPollMs;
|
|
29
|
+
#repositoryContextReader;
|
|
25
30
|
#gitBranchReader;
|
|
26
31
|
#cwd;
|
|
27
32
|
#runtime;
|
|
@@ -31,15 +36,21 @@ export class PiEngineRuntime {
|
|
|
31
36
|
#sessionBindingGeneration = 0;
|
|
32
37
|
#sessionCommands;
|
|
33
38
|
#compactionProgress = null;
|
|
39
|
+
#repositoryCwd;
|
|
34
40
|
#gitBranch = null;
|
|
35
41
|
#pullRequest = null;
|
|
36
|
-
#
|
|
37
|
-
#
|
|
42
|
+
#repositoryRefreshTimer;
|
|
43
|
+
#repositoryRefreshAbort;
|
|
44
|
+
#repositoryRefreshTask;
|
|
45
|
+
#repositoryRefreshPending = false;
|
|
46
|
+
#lastPullRequestProbeAt = null;
|
|
47
|
+
#repositoryRefreshEnabled = false;
|
|
38
48
|
#disposed = false;
|
|
39
49
|
constructor(options, ports) {
|
|
40
50
|
this.#options = options;
|
|
41
51
|
this.#ports = ports;
|
|
42
52
|
this.#cwd = options.cwd;
|
|
53
|
+
this.#repositoryCwd = options.cwd;
|
|
43
54
|
this.#runtimeFactory = options.createRuntime ?? createDefaultPiRuntime;
|
|
44
55
|
this.#checkPackageUpdates = options.checkPackageUpdates
|
|
45
56
|
?? (options.createRuntime
|
|
@@ -47,6 +58,8 @@ export class PiEngineRuntime {
|
|
|
47
58
|
: settingsManager => checkDefaultPiPackageUpdates(this.#cwd, options.agentDir, settingsManager));
|
|
48
59
|
this.#pullRequestProbe = options.pullRequestProbe ?? readOpenPullRequest;
|
|
49
60
|
this.#pullRequestRefreshMs = options.pullRequestRefreshMs ?? PULL_REQUEST_REFRESH_MS;
|
|
61
|
+
this.#repositoryContextPollMs = options.repositoryContextPollMs ?? REPOSITORY_CONTEXT_POLL_MS;
|
|
62
|
+
this.#repositoryContextReader = options.repositoryContextReader ?? (async () => null);
|
|
50
63
|
this.#gitBranchReader = options.gitBranchReader ?? readGitBranch;
|
|
51
64
|
}
|
|
52
65
|
get runtime() {
|
|
@@ -66,6 +79,9 @@ export class PiEngineRuntime {
|
|
|
66
79
|
get cwd() {
|
|
67
80
|
return this.#runtime?.cwd ?? this.#cwd;
|
|
68
81
|
}
|
|
82
|
+
get repositoryCwd() {
|
|
83
|
+
return this.#repositoryCwd;
|
|
84
|
+
}
|
|
69
85
|
get gitBranch() {
|
|
70
86
|
return this.#gitBranch;
|
|
71
87
|
}
|
|
@@ -91,7 +107,7 @@ export class PiEngineRuntime {
|
|
|
91
107
|
});
|
|
92
108
|
this.#runtime = runtime;
|
|
93
109
|
this.#cwd = runtime.cwd ?? this.#cwd;
|
|
94
|
-
this.#
|
|
110
|
+
this.#repositoryCwd = this.#cwd;
|
|
95
111
|
this.#ports.started(runtime);
|
|
96
112
|
runtime.setRebindSession(async (session) => {
|
|
97
113
|
if (this.#ports.rebindBlocked())
|
|
@@ -104,7 +120,8 @@ export class PiEngineRuntime {
|
|
|
104
120
|
}
|
|
105
121
|
this.bindSession(runtime.session);
|
|
106
122
|
await this.#announceChangelog(runtime.services.settingsManager);
|
|
107
|
-
this.#
|
|
123
|
+
this.#repositoryRefreshEnabled = true;
|
|
124
|
+
this.#startRepositoryRefresh();
|
|
108
125
|
return runtime;
|
|
109
126
|
}
|
|
110
127
|
/** Probe extension packages for updates after startup and report them as an informational diagnostic. */
|
|
@@ -161,7 +178,10 @@ export class PiEngineRuntime {
|
|
|
161
178
|
// Invariant: the subscription is live before the adapter rebuilds its state so the extension
|
|
162
179
|
// rebind that finishes the rebuild cannot emit an event nobody is listening to.
|
|
163
180
|
this.#subscribe();
|
|
181
|
+
this.#resetRepositoryRefresh();
|
|
164
182
|
this.#ports.sessionReplaced(session);
|
|
183
|
+
if (this.#repositoryRefreshEnabled)
|
|
184
|
+
this.#startRepositoryRefresh();
|
|
165
185
|
}
|
|
166
186
|
/** Stop forwarding the current session's events and open a new generation; overload recovery resumes or abandons it. */
|
|
167
187
|
suspend() {
|
|
@@ -201,50 +221,126 @@ export class PiEngineRuntime {
|
|
|
201
221
|
/** Unsubscribe, stop observing compaction, and dispose the runtime; the session reference is kept for final reads. */
|
|
202
222
|
async dispose() {
|
|
203
223
|
this.#disposed = true;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
this.#
|
|
208
|
-
this.#
|
|
224
|
+
this.#repositoryRefreshEnabled = false;
|
|
225
|
+
if (this.#repositoryRefreshTimer !== undefined)
|
|
226
|
+
clearTimeout(this.#repositoryRefreshTimer);
|
|
227
|
+
this.#repositoryRefreshTimer = undefined;
|
|
228
|
+
this.#repositoryRefreshPending = false;
|
|
229
|
+
this.#repositoryRefreshAbort?.abort();
|
|
230
|
+
const repositoryRefreshTask = this.#repositoryRefreshTask;
|
|
209
231
|
this.#unsubscribe?.();
|
|
210
232
|
this.#unsubscribe = undefined;
|
|
211
233
|
this.#compactionProgress?.dispose();
|
|
212
234
|
this.#compactionProgress = null;
|
|
235
|
+
await repositoryRefreshTask;
|
|
213
236
|
await this.#runtime?.dispose();
|
|
214
237
|
}
|
|
215
|
-
#
|
|
216
|
-
if (this.#
|
|
238
|
+
#startRepositoryRefresh() {
|
|
239
|
+
if (!this.#repositoryRefreshEnabled || this.#disposed || this.#ports.disposed())
|
|
240
|
+
return;
|
|
241
|
+
if (this.#repositoryRefreshTask !== undefined) {
|
|
242
|
+
this.#repositoryRefreshPending = true;
|
|
217
243
|
return;
|
|
218
|
-
|
|
244
|
+
}
|
|
245
|
+
const task = this.#refreshRepositoryMetadata();
|
|
246
|
+
this.#repositoryRefreshTask = task;
|
|
247
|
+
const settled = () => {
|
|
248
|
+
if (this.#repositoryRefreshTask === task)
|
|
249
|
+
this.#repositoryRefreshTask = undefined;
|
|
250
|
+
if (!this.#repositoryRefreshPending)
|
|
251
|
+
return;
|
|
252
|
+
this.#repositoryRefreshPending = false;
|
|
253
|
+
this.#startRepositoryRefresh();
|
|
254
|
+
};
|
|
255
|
+
void task.then(settled, settled);
|
|
219
256
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
257
|
+
#resetRepositoryRefresh() {
|
|
258
|
+
if (this.#repositoryRefreshTimer !== undefined)
|
|
259
|
+
clearTimeout(this.#repositoryRefreshTimer);
|
|
260
|
+
this.#repositoryRefreshTimer = undefined;
|
|
261
|
+
this.#repositoryRefreshAbort?.abort();
|
|
262
|
+
this.#repositoryCwd = this.#cwd;
|
|
263
|
+
this.#gitBranch = null;
|
|
264
|
+
this.#pullRequest = null;
|
|
265
|
+
this.#lastPullRequestProbeAt = null;
|
|
266
|
+
}
|
|
267
|
+
async #refreshRepositoryMetadata() {
|
|
268
|
+
if (this.#repositoryRefreshAbort !== undefined || this.#disposed || this.#ports.disposed())
|
|
223
269
|
return;
|
|
224
270
|
const controller = new AbortController();
|
|
225
|
-
|
|
226
|
-
|
|
271
|
+
const generation = this.#sessionBindingGeneration;
|
|
272
|
+
this.#repositoryRefreshAbort = controller;
|
|
273
|
+
let associatedContext = null;
|
|
274
|
+
const manager = this.#session?.sessionManager;
|
|
275
|
+
const sessionId = manager?.getSessionId?.();
|
|
276
|
+
const sessionFile = manager?.getSessionFile?.();
|
|
227
277
|
try {
|
|
228
|
-
|
|
278
|
+
if (typeof sessionId === "string" && sessionId.length > 0 && typeof sessionFile === "string" && sessionFile.length > 0) {
|
|
279
|
+
try {
|
|
280
|
+
associatedContext = await this.#repositoryContextReader(sessionId, sessionFile, controller.signal);
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
associatedContext = null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const nextCwd = associatedContext?.cwd ?? this.#cwd;
|
|
287
|
+
let nextBranch = associatedContext?.branch ?? null;
|
|
288
|
+
if (associatedContext === null) {
|
|
289
|
+
try {
|
|
290
|
+
nextBranch = await this.#gitBranchReader(nextCwd, controller.signal);
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
nextBranch = null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const metadataChanged = nextCwd !== this.#repositoryCwd || nextBranch !== this.#gitBranch;
|
|
297
|
+
const remoteDue = metadataChanged || this.#lastPullRequestProbeAt === null
|
|
298
|
+
|| Date.now() - this.#lastPullRequestProbeAt >= this.#pullRequestRefreshMs;
|
|
299
|
+
if (remoteDue) {
|
|
300
|
+
let nextPullRequest = null;
|
|
301
|
+
try {
|
|
302
|
+
nextPullRequest = nextBranch === null
|
|
303
|
+
? null
|
|
304
|
+
: await this.#pullRequestProbe(nextCwd, nextBranch, controller.signal);
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
nextPullRequest = null;
|
|
308
|
+
}
|
|
309
|
+
if (controller.signal.aborted || generation !== this.#sessionBindingGeneration || this.#disposed || this.#ports.disposed())
|
|
310
|
+
return;
|
|
311
|
+
const changed = nextCwd !== this.#repositoryCwd
|
|
312
|
+
|| nextBranch !== this.#gitBranch
|
|
313
|
+
|| !samePullRequest(this.#pullRequest, nextPullRequest);
|
|
314
|
+
this.#repositoryCwd = nextCwd;
|
|
315
|
+
this.#gitBranch = nextBranch;
|
|
316
|
+
this.#pullRequest = nextPullRequest;
|
|
317
|
+
this.#lastPullRequestProbeAt = Date.now();
|
|
318
|
+
if (changed)
|
|
319
|
+
this.#ports.emitView();
|
|
320
|
+
}
|
|
229
321
|
}
|
|
230
322
|
catch {
|
|
231
|
-
|
|
323
|
+
if (!controller.signal.aborted && generation === this.#sessionBindingGeneration && !this.#disposed && !this.#ports.disposed()) {
|
|
324
|
+
const changed = this.#repositoryCwd !== this.#cwd || this.#gitBranch !== null || this.#pullRequest !== null;
|
|
325
|
+
this.#repositoryCwd = this.#cwd;
|
|
326
|
+
this.#gitBranch = null;
|
|
327
|
+
this.#pullRequest = null;
|
|
328
|
+
this.#lastPullRequestProbeAt = Date.now();
|
|
329
|
+
if (changed)
|
|
330
|
+
this.#ports.emitView();
|
|
331
|
+
}
|
|
232
332
|
}
|
|
233
333
|
finally {
|
|
234
|
-
if (this.#
|
|
235
|
-
this.#
|
|
334
|
+
if (this.#repositoryRefreshAbort === controller)
|
|
335
|
+
this.#repositoryRefreshAbort = undefined;
|
|
236
336
|
}
|
|
237
|
-
if (controller.signal.aborted || this.#disposed || this.#ports.disposed())
|
|
337
|
+
if (controller.signal.aborted || generation !== this.#sessionBindingGeneration || this.#disposed || this.#ports.disposed())
|
|
238
338
|
return;
|
|
239
|
-
|
|
240
|
-
this.#
|
|
241
|
-
this.#
|
|
242
|
-
}
|
|
243
|
-
this.#
|
|
244
|
-
this.#pullRequestTimer = undefined;
|
|
245
|
-
void this.#refreshPullRequest();
|
|
246
|
-
}, this.#pullRequestRefreshMs);
|
|
247
|
-
this.#pullRequestTimer.unref?.();
|
|
339
|
+
this.#repositoryRefreshTimer = setTimeout(() => {
|
|
340
|
+
this.#repositoryRefreshTimer = undefined;
|
|
341
|
+
this.#startRepositoryRefresh();
|
|
342
|
+
}, this.#repositoryContextPollMs);
|
|
343
|
+
this.#repositoryRefreshTimer.unref?.();
|
|
248
344
|
}
|
|
249
345
|
#subscribe() {
|
|
250
346
|
const session = this.#session;
|
|
@@ -296,9 +392,16 @@ async function checkDefaultPiPackageUpdates(cwd, agentDir, settingsManager) {
|
|
|
296
392
|
function samePullRequest(left, right) {
|
|
297
393
|
return left?.number === right?.number && left?.url === right?.url;
|
|
298
394
|
}
|
|
299
|
-
async function readGitBranch(cwd) {
|
|
395
|
+
async function readGitBranch(cwd, signal) {
|
|
300
396
|
try {
|
|
301
|
-
const { stdout } = await execFileAsync("git", ["branch", "--show-current"], {
|
|
397
|
+
const { stdout } = await execFileAsync("git", ["branch", "--show-current"], {
|
|
398
|
+
cwd,
|
|
399
|
+
windowsHide: true,
|
|
400
|
+
timeout: GIT_BRANCH_TIMEOUT_MS,
|
|
401
|
+
maxBuffer: GIT_BRANCH_MAX_BUFFER_BYTES,
|
|
402
|
+
signal,
|
|
403
|
+
encoding: "utf8",
|
|
404
|
+
});
|
|
302
405
|
const branch = stdout.trim();
|
|
303
406
|
return branch.length > 0 ? branch : null;
|
|
304
407
|
}
|
|
@@ -2,6 +2,39 @@ import { toolRenderingInput } from "./tool-rendering.js";
|
|
|
2
2
|
import { TranscriptImageAssets } from "./transcript-image-assets.js";
|
|
3
3
|
import { acceptsTranscriptUpdate, transcriptToolState } from "../../../contracts/owned-ui/index.js";
|
|
4
4
|
import { assistantContent, contentImageCount, isRecord, jsonSummary, messageFallbackKey, retainCompletedArguments, sameBlockContent, sanitizeJson, stringValue, textFromContent } from "./message-values.js";
|
|
5
|
+
const IMAGE_DIMENSION_NOTE = /^\[Image: original \d+x\d+, displayed at \d+x\d+\. Multiply coordinates by \d+(?:\.\d+)? to map to original image\.\]$/u;
|
|
6
|
+
const IMAGE_CONVERSION_NOTE = /^\[Image converted from image\/[a-z0-9.+-]+ to image\/[a-z0-9.+-]+\.\]$/iu;
|
|
7
|
+
const IMAGE_OMISSION_NOTE = /^\[Image omitted: could not be (?:converted to a supported inline image format|resized below the inline image size limit)\.\]$/u;
|
|
8
|
+
function isImageProcessingHint(line) {
|
|
9
|
+
return IMAGE_DIMENSION_NOTE.test(line) || IMAGE_CONVERSION_NOTE.test(line) || IMAGE_OMISSION_NOTE.test(line);
|
|
10
|
+
}
|
|
11
|
+
function userPresentation(content) {
|
|
12
|
+
const imageCount = contentImageCount(content);
|
|
13
|
+
if (imageCount === 0)
|
|
14
|
+
return undefined;
|
|
15
|
+
const lines = textFromContent(content).split("\n");
|
|
16
|
+
const retainedHints = [];
|
|
17
|
+
let dimensionCount = 0;
|
|
18
|
+
let hintStart = lines.length;
|
|
19
|
+
while (hintStart > 0) {
|
|
20
|
+
const line = lines[hintStart - 1];
|
|
21
|
+
if (!isImageProcessingHint(line))
|
|
22
|
+
break;
|
|
23
|
+
if (IMAGE_DIMENSION_NOTE.test(line))
|
|
24
|
+
dimensionCount++;
|
|
25
|
+
else
|
|
26
|
+
retainedHints.unshift(line);
|
|
27
|
+
hintStart--;
|
|
28
|
+
}
|
|
29
|
+
if (dimensionCount === 0 || dimensionCount > imageCount)
|
|
30
|
+
return undefined;
|
|
31
|
+
lines.splice(hintStart);
|
|
32
|
+
if (lines.at(-1) === "")
|
|
33
|
+
lines.pop();
|
|
34
|
+
if (retainedHints.length > 0)
|
|
35
|
+
lines.push("", ...retainedHints);
|
|
36
|
+
return { visibleText: lines.join("\n") };
|
|
37
|
+
}
|
|
5
38
|
/**
|
|
6
39
|
* The transcript as the owned UI sees it: Pi's session messages and tool events projected into
|
|
7
40
|
* ordered, revisioned blocks. The projection owns block identity (message and tool ids that stay
|
|
@@ -122,6 +155,7 @@ export class PiTranscriptProjection {
|
|
|
122
155
|
return [];
|
|
123
156
|
const baseId = this.#messageBlockId(message, fallbackIndex, status, occurrence);
|
|
124
157
|
if (message.role === "user") {
|
|
158
|
+
const presentation = userPresentation(message.content);
|
|
125
159
|
return [{
|
|
126
160
|
id: baseId,
|
|
127
161
|
kind: "user",
|
|
@@ -129,6 +163,7 @@ export class PiTranscriptProjection {
|
|
|
129
163
|
revision: this.nextRevision(baseId),
|
|
130
164
|
title: "User",
|
|
131
165
|
text: textFromContent(message.content),
|
|
166
|
+
...(presentation === undefined ? {} : { userPresentation: presentation }),
|
|
132
167
|
imageReferences: this.imageReferences(message.content, "user"),
|
|
133
168
|
payload: {
|
|
134
169
|
role: "user",
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
"dist/integrations/pi/components/upstream/adjacent/core/keybindings.js",
|
|
17
17
|
"dist/integrations/pi/components/upstream/components/earendil-announcement.js",
|
|
18
18
|
"dist/integrations/pi/components/upstream/components/extension-editor.js",
|
|
19
|
+
"dist/integrations/pi/components/upstream/components/extension-input.js",
|
|
20
|
+
"dist/integrations/pi/components/upstream/components/extension-selector.js",
|
|
19
21
|
"dist/integrations/pi/components/upstream/components/scoped-models-selector.js",
|
|
20
22
|
"dist/integrations/pi/components/upstream/components/session-selector.js",
|
|
21
23
|
"dist/integrations/pi/components/upstream/components/status-indicator.js",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "darwin",
|
|
6
6
|
"architecture": "arm64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-22T23:31:15.773Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "9db1726bbe3fc2e8292f2ead1e7e9d0fd4d7d0dc9217b372582bf354b0f565dc",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "linux",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-22T23:30:53.377Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "d8cda6b0c7cb36c0cc41e90802aceebf6c02eaebbc08a83d7d963d2e918dbd7a",
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"platform": "win32",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-22T23:31:43.770Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "57ed4829927f81b1cb638ffafa7a5b3832537284aa343c0eb0ef70592ed671d3",
|
|
12
12
|
"size": 177664
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|