@timurproko/a1 0.1.8-dev.157 → 0.1.8-dev.182
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/README.md +32 -0
- package/bin/ui.js +17 -2
- package/dist/composition/owned-ui.d.ts +4 -1
- package/dist/composition/owned-ui.js +8 -5
- package/dist/contracts/agent-engine/capability-ports.d.ts +2 -2
- package/dist/contracts/agent-engine/domain-validation.js +22 -4
- package/dist/contracts/agent-engine/domain.d.ts +17 -0
- package/dist/contracts/owned-ui/model.d.ts +8 -0
- package/dist/contracts/owned-ui/validation.js +11 -0
- package/dist/features/owned-ui/index.d.ts +1 -0
- package/dist/features/owned-ui/index.js +1 -0
- package/dist/features/owned-ui/project-trust-prompt.d.ts +20 -0
- package/dist/features/owned-ui/project-trust-prompt.js +32 -0
- package/dist/features/owned-ui/settings-app.js +37 -9
- package/dist/integrations/pi/components/shell-components.d.ts +1 -0
- package/dist/integrations/pi/components/shell-components.js +1 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +10 -0
- package/dist/integrations/pi/components/shell-footer-status.js +14 -9
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +36 -0
- package/dist/integrations/pi/components/shell-presenters-info.js +78 -0
- package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -36
- package/dist/integrations/pi/components/shell-presenters-transcript.js +88 -128
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +11 -1
- package/dist/integrations/pi/engine/adapter.d.ts +15 -4
- package/dist/integrations/pi/engine/adapter.js +185 -37
- package/dist/integrations/pi/engine/http-dispatcher.d.ts +4 -0
- package/dist/integrations/pi/engine/http-dispatcher.js +25 -0
- package/dist/integrations/pi/engine/index.d.ts +3 -0
- package/dist/integrations/pi/engine/index.js +3 -0
- package/dist/integrations/pi/engine/project-trust-preflight.d.ts +22 -0
- package/dist/integrations/pi/engine/project-trust-preflight.js +50 -0
- package/dist/integrations/pi/engine/runtime-integration.d.ts +16 -1
- package/dist/integrations/pi/engine/runtime-integration.js +34 -4
- package/dist/integrations/pi/engine/settings-effects.d.ts +55 -0
- package/dist/integrations/pi/engine/settings-effects.js +229 -0
- package/dist/integrations/pi/engine/settings-integration.d.ts +14 -26
- package/dist/integrations/pi/engine/settings-integration.js +102 -108
- package/dist/integrations/pi/engine/workflow-controllers.d.ts +1 -1
- package/dist/integrations/pi/session-ui/clipboard-image.d.ts +11 -0
- package/dist/integrations/pi/session-ui/clipboard-image.js +29 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +5 -1
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +9 -2
- package/dist/integrations/pi/session-ui/session-shell-root.js +83 -9
- package/dist/integrations/pi/session-ui/session-shell.d.ts +2 -0
- package/dist/integrations/pi/session-ui/session-shell.js +151 -15
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +34 -2
- package/dist/integrations/pi/session-ui/system-clipboard.d.ts +9 -0
- package/dist/integrations/pi/session-ui/system-clipboard.js +34 -5
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +4 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +28 -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/ui/components/spans.d.ts +2 -0
- package/dist/ui/components/spans.js +25 -0
- package/dist/ui/settings/sections.d.ts +8 -19
- package/dist/ui/settings/sections.js +12 -4
- package/dist/ui/settings/session.d.ts +8 -18
- package/dist/ui/settings/session.js +59 -52
- package/docs/architecture/project-structure.md +14 -0
- package/docs/ci-release-runbook.md +69 -4
- package/docs/repository-governance-live-acceptance.md +77 -0
- package/package.json +5 -2
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
3
4
|
import { tmpdir } from "node:os";
|
|
4
5
|
import { dirname, join, resolve } from "node:path";
|
|
5
6
|
import { promisify } from "node:util";
|
|
6
7
|
import { PRODUCT_IDENTITY } from "../../../product-identity.js";
|
|
7
|
-
import {
|
|
8
|
+
import { configureOwnedHttpDispatcher } from "./http-dispatcher.js";
|
|
9
|
+
import { copyToClipboard, DefaultPackageManager, getAgentDir, ProjectTrustStore, SessionManager, VERSION, } from "@earendil-works/pi-coding-agent";
|
|
8
10
|
import { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, assertOwnedUiCommand, assertOwnedUiExtensionUiPort, assertOwnedUiSnapshot, } from "../../../contracts/owned-ui/index.js";
|
|
9
11
|
import { PINNED_PI_SETTINGS_CALLBACKS, PINNED_PI_WORKFLOW_COMMAND_NAMES, } from "./workflows.js";
|
|
10
12
|
import { createPiRuntimeIntegration } from "./runtime-integration.js";
|
|
@@ -33,6 +35,7 @@ export class PiEngineAdapter {
|
|
|
33
35
|
#cwd;
|
|
34
36
|
#agentDir;
|
|
35
37
|
#sessionId;
|
|
38
|
+
#sessionPath;
|
|
36
39
|
#workflowHost;
|
|
37
40
|
#workflowInteraction;
|
|
38
41
|
#listeners = new Set();
|
|
@@ -68,6 +71,7 @@ export class PiEngineAdapter {
|
|
|
68
71
|
#messageBlockIds = new WeakMap();
|
|
69
72
|
#messageFallbackIds = new Map();
|
|
70
73
|
#toolBlockIds = new Map();
|
|
74
|
+
#transcriptImageAssets = new Map();
|
|
71
75
|
#pendingToolUpdates = new Map();
|
|
72
76
|
#toolUpdateFlush = null;
|
|
73
77
|
#usageCache;
|
|
@@ -84,12 +88,17 @@ export class PiEngineAdapter {
|
|
|
84
88
|
#extensionShutdown;
|
|
85
89
|
#extensionBound = false;
|
|
86
90
|
#disposed = false;
|
|
91
|
+
#settingsProductMode;
|
|
92
|
+
#projectTrustPrompt;
|
|
93
|
+
#settingsIntegration;
|
|
94
|
+
#settingsIntegrationManager;
|
|
87
95
|
/** Lists the themes installed on this machine, when the caller supplies one. */
|
|
88
96
|
#availableThemes;
|
|
89
97
|
constructor(options = {}) {
|
|
90
98
|
this.#cwd = options.cwd ?? process.cwd();
|
|
91
99
|
this.#agentDir = options.agentDir ?? getAgentDir();
|
|
92
100
|
this.#sessionId = options.sessionId ?? "owned-session-1";
|
|
101
|
+
this.#sessionPath = options.sessionPath;
|
|
93
102
|
this.#runtimeFactory = options.createRuntime ?? createDefaultPiRuntime;
|
|
94
103
|
this.#checkPackageUpdates = options.checkPackageUpdates
|
|
95
104
|
?? (options.createRuntime
|
|
@@ -97,6 +106,8 @@ export class PiEngineAdapter {
|
|
|
97
106
|
: settingsManager => checkDefaultPiPackageUpdates(this.#cwd, this.#agentDir, settingsManager));
|
|
98
107
|
this.#workflowHost = options.workflowHost ?? defaultWorkflowHost();
|
|
99
108
|
this.#availableThemes = options.availableThemes ?? null;
|
|
109
|
+
this.#settingsProductMode = options.settingsProductMode ?? "bare";
|
|
110
|
+
this.#projectTrustPrompt = options.projectTrustPrompt;
|
|
100
111
|
this.#workflowInteraction = { prompt: async () => null, notify() { } };
|
|
101
112
|
}
|
|
102
113
|
setWorkflowInteractionHost(interaction) {
|
|
@@ -111,6 +122,13 @@ export class PiEngineAdapter {
|
|
|
111
122
|
get agentDir() {
|
|
112
123
|
return this.#agentDir;
|
|
113
124
|
}
|
|
125
|
+
resolveTranscriptImage(assetId) {
|
|
126
|
+
return this.#transcriptImageAssets.get(assetId) ?? null;
|
|
127
|
+
}
|
|
128
|
+
currentSessionFile() {
|
|
129
|
+
const value = this.#session?.sessionManager?.getSessionFile?.();
|
|
130
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
131
|
+
}
|
|
114
132
|
get disposed() {
|
|
115
133
|
return this.#disposed;
|
|
116
134
|
}
|
|
@@ -121,6 +139,8 @@ export class PiEngineAdapter {
|
|
|
121
139
|
cwd: this.#cwd,
|
|
122
140
|
agentDir: this.#agentDir,
|
|
123
141
|
sessionId: this.#sessionId,
|
|
142
|
+
...(this.#sessionPath === undefined ? {} : { sessionPath: this.#sessionPath }),
|
|
143
|
+
...(this.#projectTrustPrompt === undefined ? {} : { projectTrustPrompt: this.#projectTrustPrompt }),
|
|
124
144
|
}).catch(error => {
|
|
125
145
|
this.#lifecycle = "failed";
|
|
126
146
|
this.#addDiagnostic("error", "engine-startup", error instanceof Error ? error.message : String(error), false);
|
|
@@ -140,6 +160,7 @@ export class PiEngineAdapter {
|
|
|
140
160
|
this.#addDiagnostic(diagnostic.type === "error" ? "error" : diagnostic.type === "warning" ? "warning" : "info", "engine-startup", diagnostic.message, diagnostic.type !== "error");
|
|
141
161
|
}
|
|
142
162
|
this.#bindSession(runtime.session);
|
|
163
|
+
await this.#announceChangelog(runtime.services.settingsManager);
|
|
143
164
|
this.#lifecycle = "ready";
|
|
144
165
|
this.#editor = { ...this.#editor, submitEnabled: true };
|
|
145
166
|
this.#emitEvent({ type: "session-lifecycle", lifecycle: "ready", reason: null });
|
|
@@ -147,6 +168,19 @@ export class PiEngineAdapter {
|
|
|
147
168
|
void this.#announcePackageUpdates(runtime.services.settingsManager);
|
|
148
169
|
return this.view();
|
|
149
170
|
}
|
|
171
|
+
async #announceChangelog(settingsManager) {
|
|
172
|
+
if (!settingsManager || typeof settingsManager.getLastChangelogVersion !== "function"
|
|
173
|
+
|| typeof settingsManager.setLastChangelogVersion !== "function")
|
|
174
|
+
return;
|
|
175
|
+
if (settingsManager.getLastChangelogVersion() === VERSION)
|
|
176
|
+
return;
|
|
177
|
+
const markdown = await this.#workflowHost.readChangelog().catch(() => "");
|
|
178
|
+
if (markdown.trim().length > 0) {
|
|
179
|
+
this.#addDiagnostic("info", settingsManager.getCollapseChangelog() ? "changelog-collapsed" : "changelog-expanded", markdown, true);
|
|
180
|
+
}
|
|
181
|
+
settingsManager.setLastChangelogVersion(VERSION);
|
|
182
|
+
await settingsManager.flush();
|
|
183
|
+
}
|
|
150
184
|
async #announcePackageUpdates(settingsManager) {
|
|
151
185
|
if (process.env.PI_OFFLINE)
|
|
152
186
|
return;
|
|
@@ -432,16 +466,107 @@ export class PiEngineAdapter {
|
|
|
432
466
|
/** Settings port for the live runtime, or null before the runtime is available. */
|
|
433
467
|
settingsPort() {
|
|
434
468
|
const settings = this.#runtime?.services.settingsManager;
|
|
435
|
-
if (!settings)
|
|
469
|
+
if (!settings || typeof settings.getCompactionEnabled !== "function")
|
|
436
470
|
return null;
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
471
|
+
if (this.#settingsIntegration === undefined || this.#settingsIntegrationManager !== settings) {
|
|
472
|
+
this.#settingsIntegrationManager = settings;
|
|
473
|
+
configureOwnedHttpDispatcher(settings.getHttpIdleTimeoutMs());
|
|
474
|
+
this.#settingsIntegration = new PiSettingsIntegration(settings, {
|
|
475
|
+
...(this.#availableThemes === null ? {} : { themes: this.#availableThemes }),
|
|
476
|
+
thinkingLevels: () => {
|
|
477
|
+
const levels = this.#runtime?.session?.getAvailableThinkingLevels?.();
|
|
478
|
+
return Array.isArray(levels) ? levels.map(level => String(level)) : [];
|
|
479
|
+
},
|
|
480
|
+
productMode: this.#settingsProductMode,
|
|
481
|
+
});
|
|
482
|
+
this.#settingsIntegration.bindOwner("shell", {
|
|
483
|
+
enableSkillCommands: { apply: value => {
|
|
484
|
+
if (typeof value !== "boolean")
|
|
485
|
+
throw new TypeError("Skill commands value is invalid");
|
|
486
|
+
settings.setEnableSkillCommands(value);
|
|
487
|
+
} },
|
|
488
|
+
doubleEscapeAction: { apply: value => {
|
|
489
|
+
if (value !== "tree" && value !== "fork" && value !== "none")
|
|
490
|
+
throw new TypeError("Double-Escape action is invalid");
|
|
491
|
+
settings.setDoubleEscapeAction(value);
|
|
492
|
+
} },
|
|
493
|
+
treeFilterMode: { apply: value => {
|
|
494
|
+
if (value !== "default" && value !== "no-tools" && value !== "user-only" && value !== "labeled-only" && value !== "all")
|
|
495
|
+
throw new TypeError("Tree filter mode is invalid");
|
|
496
|
+
settings.setTreeFilterMode(value);
|
|
497
|
+
} },
|
|
498
|
+
showCacheMissNotices: { apply: value => {
|
|
499
|
+
if (typeof value !== "boolean")
|
|
500
|
+
throw new TypeError("Cache-miss notice setting is invalid");
|
|
501
|
+
settings.setShowCacheMissNotices(value);
|
|
502
|
+
} },
|
|
503
|
+
});
|
|
504
|
+
this.#settingsIntegration.bindOwner("startup", {
|
|
505
|
+
// Deferred application is the owner operation: the next preflight reads
|
|
506
|
+
// the persisted default before constructing project-backed services.
|
|
507
|
+
defaultProjectTrust: { apply() { } },
|
|
508
|
+
collapseChangelog: { apply() { } },
|
|
509
|
+
});
|
|
510
|
+
this.#settingsIntegration.bindOwner("agent", {
|
|
511
|
+
autoCompact: { apply: value => {
|
|
512
|
+
if (typeof value !== "boolean")
|
|
513
|
+
throw new TypeError("Auto compact value is invalid");
|
|
514
|
+
this.#requireWorkflowSession().setAutoCompactionEnabled(value);
|
|
515
|
+
} },
|
|
516
|
+
autoResizeImages: { apply: value => {
|
|
517
|
+
if (typeof value !== "boolean")
|
|
518
|
+
throw new TypeError("Auto-resize images value is invalid");
|
|
519
|
+
settings.setImageAutoResize(value);
|
|
520
|
+
} },
|
|
521
|
+
blockImages: { apply: value => {
|
|
522
|
+
if (typeof value !== "boolean")
|
|
523
|
+
throw new TypeError("Block images value is invalid");
|
|
524
|
+
settings.setBlockImages(value);
|
|
525
|
+
} },
|
|
526
|
+
steeringMode: { apply: value => {
|
|
527
|
+
if (value !== "all" && value !== "one-at-a-time")
|
|
528
|
+
throw new TypeError("Steering mode is invalid");
|
|
529
|
+
this.#requireWorkflowSession().setSteeringMode(value);
|
|
530
|
+
} },
|
|
531
|
+
followUpMode: { apply: value => {
|
|
532
|
+
if (value !== "all" && value !== "one-at-a-time")
|
|
533
|
+
throw new TypeError("Follow-up mode is invalid");
|
|
534
|
+
this.#requireWorkflowSession().setFollowUpMode(value);
|
|
535
|
+
} },
|
|
536
|
+
transport: { apply: value => {
|
|
537
|
+
if (value !== "sse" && value !== "websocket" && value !== "websocket-cached" && value !== "auto")
|
|
538
|
+
throw new TypeError("Transport is invalid");
|
|
539
|
+
this.#requireWorkflowSession().agent.transport = value;
|
|
540
|
+
} },
|
|
541
|
+
httpIdleTimeoutMs: { apply: value => {
|
|
542
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
543
|
+
throw new TypeError("HTTP idle timeout is invalid");
|
|
544
|
+
// Provider streaming reads the manager per request; global fetch uses
|
|
545
|
+
// the matching owned dispatcher and zero maps to disabled semantics.
|
|
546
|
+
configureOwnedHttpDispatcher(value);
|
|
547
|
+
settings.setHttpIdleTimeoutMs(value);
|
|
548
|
+
} },
|
|
549
|
+
thinkingLevel: { apply: value => {
|
|
550
|
+
if (!isThinkingLevel(value))
|
|
551
|
+
throw new TypeError("Thinking level is invalid");
|
|
552
|
+
this.#requireWorkflowSession().setThinkingLevel(value);
|
|
553
|
+
this.#thinkingLevel = readThinkingLevel(this.#requireWorkflowSession().thinkingLevel);
|
|
554
|
+
this.#emitView();
|
|
555
|
+
} },
|
|
556
|
+
warnings: { apply: value => {
|
|
557
|
+
if (!isRecord(value) || Object.values(value).some(flag => typeof flag !== "boolean"))
|
|
558
|
+
throw new TypeError("Warnings setting is invalid");
|
|
559
|
+
settings.setWarnings(value);
|
|
560
|
+
} },
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
return this.#settingsIntegration;
|
|
564
|
+
}
|
|
565
|
+
bindSettingsOwner(owner, handlers) {
|
|
566
|
+
const settings = this.settingsPort();
|
|
567
|
+
if (settings === null)
|
|
568
|
+
return () => { };
|
|
569
|
+
return settings.bindOwner(owner, handlers);
|
|
445
570
|
}
|
|
446
571
|
pinnedModelSelectorContext() {
|
|
447
572
|
const session = this.#requireWorkflowSession();
|
|
@@ -718,9 +843,9 @@ export class PiEngineAdapter {
|
|
|
718
843
|
excludeFromContext,
|
|
719
844
|
};
|
|
720
845
|
}
|
|
721
|
-
applyPinnedSettingValue(callback, value) {
|
|
846
|
+
async applyPinnedSettingValue(callback, value) {
|
|
722
847
|
try {
|
|
723
|
-
return this.#applyPinnedSetting(callback, value, true);
|
|
848
|
+
return await this.#applyPinnedSetting(callback, value, true);
|
|
724
849
|
}
|
|
725
850
|
catch (error) {
|
|
726
851
|
return workflowResult("settings", "failed", error instanceof Error ? error.message : String(error));
|
|
@@ -821,6 +946,7 @@ export class PiEngineAdapter {
|
|
|
821
946
|
this.#toolUpdateFlush = null;
|
|
822
947
|
}
|
|
823
948
|
this.#pendingToolUpdates.clear();
|
|
949
|
+
this.#transcriptImageAssets.clear();
|
|
824
950
|
this.#extensionBound = false;
|
|
825
951
|
this.#extensionUi = undefined;
|
|
826
952
|
this.#extensionShutdown = undefined;
|
|
@@ -846,7 +972,7 @@ export class PiEngineAdapter {
|
|
|
846
972
|
case "settings": {
|
|
847
973
|
if (!selection)
|
|
848
974
|
return workflowResult(request.command, "failed", "Settings requires the owned settings controller");
|
|
849
|
-
return this.#applyPinnedSetting(selection);
|
|
975
|
+
return await this.#applyPinnedSetting(selection);
|
|
850
976
|
}
|
|
851
977
|
case "model": {
|
|
852
978
|
const reference = selection ?? argument;
|
|
@@ -1236,7 +1362,7 @@ export class PiEngineAdapter {
|
|
|
1236
1362
|
return [];
|
|
1237
1363
|
return sessionInfoOptions(await SessionManager.list(cwd, typeof sessionDir === "string" ? sessionDir : undefined));
|
|
1238
1364
|
}
|
|
1239
|
-
#applyPinnedSetting(selection, selectedValue, hasSelectedValue = false) {
|
|
1365
|
+
async #applyPinnedSetting(selection, selectedValue, hasSelectedValue = false) {
|
|
1240
1366
|
if (!PINNED_PI_SETTINGS_CALLBACKS.includes(selection)) {
|
|
1241
1367
|
return workflowResult("settings", "failed", `Unknown setting callback: ${selection}`);
|
|
1242
1368
|
}
|
|
@@ -1283,29 +1409,19 @@ export class PiEngineAdapter {
|
|
|
1283
1409
|
selectedValue = currentValues[callback];
|
|
1284
1410
|
hasSelectedValue = true;
|
|
1285
1411
|
}
|
|
1286
|
-
const
|
|
1287
|
-
if (
|
|
1412
|
+
const port = this.settingsPort();
|
|
1413
|
+
if (port === null)
|
|
1288
1414
|
return workflowResult("settings", "failed", "Settings are unavailable");
|
|
1289
|
-
const
|
|
1290
|
-
if (
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
else {
|
|
1296
|
-
const key = settingKeyForCallback(callback);
|
|
1297
|
-
if (key === null)
|
|
1298
|
-
return workflowResult("settings", "failed", `${settingLabel(callback)} is unavailable in this runtime`);
|
|
1299
|
-
const value = agentJsonValue(selectedValue);
|
|
1300
|
-
if (callback === "onAutoCompactChange") {
|
|
1301
|
-
if (typeof value !== "boolean")
|
|
1302
|
-
return workflowResult("settings", "failed", "Auto compact value is invalid");
|
|
1303
|
-
session.setAutoCompactionEnabled?.(value);
|
|
1304
|
-
}
|
|
1305
|
-
new PiSettingsIntegration(settings).writeSettingNow(key, value);
|
|
1415
|
+
const key = settingKeyForCallback(callback);
|
|
1416
|
+
if (key === null)
|
|
1417
|
+
return workflowResult("settings", "failed", `${settingLabel(callback)} is unavailable in this runtime`);
|
|
1418
|
+
const result = await port.writeSetting(key, agentJsonValue(selectedValue));
|
|
1419
|
+
if (result.status === "failed" || result.status === "unavailable") {
|
|
1420
|
+
return workflowResult("settings", "failed", result.failure ?? result.limitationReason ?? `${settingLabel(callback)} is unavailable`);
|
|
1306
1421
|
}
|
|
1307
1422
|
this.#emitView();
|
|
1308
|
-
|
|
1423
|
+
const suffix = result.status === "deferred" ? ` (${result.application})` : "";
|
|
1424
|
+
return workflowResult("settings", "completed", `${settingLabel(callback)}: ${String(selectedValue)}${suffix}`);
|
|
1309
1425
|
}
|
|
1310
1426
|
async #perform(command) {
|
|
1311
1427
|
const runtime = this.#runtime;
|
|
@@ -1384,6 +1500,7 @@ export class PiEngineAdapter {
|
|
|
1384
1500
|
this.#activeModel = readModel(session.model);
|
|
1385
1501
|
this.#reconcileActiveModelAvailability();
|
|
1386
1502
|
this.#thinkingLevel = readThinkingLevel(session.thinkingLevel);
|
|
1503
|
+
this.#transcriptImageAssets.clear();
|
|
1387
1504
|
this.#rebuildTranscript(session.messages, "finalized");
|
|
1388
1505
|
this.#unsubscribe = session.subscribe(event => this.#handlePiEvent(event));
|
|
1389
1506
|
if (this.#extensionUi !== undefined)
|
|
@@ -1443,6 +1560,11 @@ export class PiEngineAdapter {
|
|
|
1443
1560
|
this.#transcriptIndex.clear();
|
|
1444
1561
|
for (const [index, block] of blocks.entries())
|
|
1445
1562
|
this.#transcriptIndex.set(block.id, index);
|
|
1563
|
+
const retainedAssets = new Set(blocks.flatMap(block => block.imageReferences?.map(reference => reference.assetId) ?? []));
|
|
1564
|
+
for (const assetId of this.#transcriptImageAssets.keys()) {
|
|
1565
|
+
if (!retainedAssets.has(assetId))
|
|
1566
|
+
this.#transcriptImageAssets.delete(assetId);
|
|
1567
|
+
}
|
|
1446
1568
|
this.#transcriptSnapshot = undefined;
|
|
1447
1569
|
}
|
|
1448
1570
|
#transcriptBlock(id) {
|
|
@@ -1702,6 +1824,7 @@ export class PiEngineAdapter {
|
|
|
1702
1824
|
revision: this.#nextBlockRevision(baseId),
|
|
1703
1825
|
title: "User",
|
|
1704
1826
|
text: textFromContent(message.content),
|
|
1827
|
+
imageReferences: this.#imageReferences(message.content, "user"),
|
|
1705
1828
|
payload: {
|
|
1706
1829
|
role: "user",
|
|
1707
1830
|
imageCount: contentImageCount(message.content),
|
|
@@ -1779,6 +1902,7 @@ export class PiEngineAdapter {
|
|
|
1779
1902
|
revision: this.#nextBlockRevision(blockId),
|
|
1780
1903
|
title: stringValue(message.toolName) ?? existing?.title ?? "Tool result",
|
|
1781
1904
|
text: textFromContent(message.content),
|
|
1905
|
+
imageReferences: this.#imageReferences(message.content, "tool-result"),
|
|
1782
1906
|
payload: {
|
|
1783
1907
|
...(existingPayload ?? {}),
|
|
1784
1908
|
role: "toolResult",
|
|
@@ -1834,6 +1958,24 @@ export class PiEngineAdapter {
|
|
|
1834
1958
|
}
|
|
1835
1959
|
return blocks;
|
|
1836
1960
|
}
|
|
1961
|
+
#imageReferences(content, source) {
|
|
1962
|
+
if (!Array.isArray(content))
|
|
1963
|
+
return [];
|
|
1964
|
+
const references = [];
|
|
1965
|
+
for (const item of content) {
|
|
1966
|
+
if (references.length >= 16 || !isRecord(item) || item.type !== "image"
|
|
1967
|
+
|| typeof item.data !== "string" || item.data.length === 0
|
|
1968
|
+
|| typeof item.mimeType !== "string" || !/^image\/[a-z0-9.+-]+$/i.test(item.mimeType))
|
|
1969
|
+
continue;
|
|
1970
|
+
const byteLength = Buffer.from(item.data, "base64").byteLength;
|
|
1971
|
+
if (byteLength < 1 || byteLength > 20 * 1024 * 1024)
|
|
1972
|
+
continue;
|
|
1973
|
+
const assetId = `image-${createHash("sha256").update(item.mimeType).update("\0").update(item.data).digest("hex").slice(0, 24)}`;
|
|
1974
|
+
this.#transcriptImageAssets.set(assetId, { type: "image", data: item.data, mimeType: item.mimeType });
|
|
1975
|
+
references.push({ assetId, mimeType: item.mimeType, byteLength, source });
|
|
1976
|
+
}
|
|
1977
|
+
return references;
|
|
1978
|
+
}
|
|
1837
1979
|
/**
|
|
1838
1980
|
* A busy tool streams far more output chunks than a terminal frame can show, and every
|
|
1839
1981
|
* chunk restates the whole accumulated output. Keeping only the newest chunk per tool
|
|
@@ -2056,7 +2198,12 @@ export async function createPiEngineAdapter(options = {}) {
|
|
|
2056
2198
|
return adapter;
|
|
2057
2199
|
}
|
|
2058
2200
|
async function createDefaultPiRuntime(input) {
|
|
2059
|
-
return createPiRuntimeIntegration({
|
|
2201
|
+
return createPiRuntimeIntegration({
|
|
2202
|
+
cwd: input.cwd,
|
|
2203
|
+
agentDir: input.agentDir,
|
|
2204
|
+
...(input.sessionPath === undefined ? {} : { sessionPath: input.sessionPath }),
|
|
2205
|
+
...(input.projectTrustPrompt === undefined ? {} : { projectTrustPrompt: input.projectTrustPrompt }),
|
|
2206
|
+
});
|
|
2060
2207
|
}
|
|
2061
2208
|
async function checkDefaultPiPackageUpdates(cwd, agentDir, settingsManager) {
|
|
2062
2209
|
const packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });
|
|
@@ -2282,7 +2429,7 @@ function settingKeyForCallback(callback) {
|
|
|
2282
2429
|
onAutoCompactChange: "autoCompact", onShowImagesChange: "showImages", onImageWidthCellsChange: "imageWidthCells",
|
|
2283
2430
|
onAutoResizeImagesChange: "autoResizeImages", onBlockImagesChange: "blockImages", onEnableSkillCommandsChange: "enableSkillCommands",
|
|
2284
2431
|
onSteeringModeChange: "steeringMode", onFollowUpModeChange: "followUpMode", onTransportChange: "transport",
|
|
2285
|
-
onHttpIdleTimeoutMsChange: "httpIdleTimeoutMs", onThemeChange: "theme", onThemePreview: "theme",
|
|
2432
|
+
onHttpIdleTimeoutMsChange: "httpIdleTimeoutMs", onThinkingLevelChange: "thinkingLevel", onThemeChange: "theme", onThemePreview: "theme",
|
|
2286
2433
|
onHideThinkingBlockChange: "hideThinkingBlock", onMermaidRenderingModeChange: "mermaidRenderingMode",
|
|
2287
2434
|
onShowCacheMissNoticesChange: "showCacheMissNotices", onCollapseChangelogChange: "collapseChangelog",
|
|
2288
2435
|
onEnableInstallTelemetryChange: "enableInstallTelemetry", onQuietStartupChange: "quietStartup",
|
|
@@ -2483,7 +2630,8 @@ function sameBlockContent(left, right) {
|
|
|
2483
2630
|
&& left.status === right.status
|
|
2484
2631
|
&& left.title === right.title
|
|
2485
2632
|
&& left.text === right.text
|
|
2486
|
-
&& sameValue(left.payload, right.payload)
|
|
2633
|
+
&& sameValue(left.payload, right.payload)
|
|
2634
|
+
&& sameValue(left.imageReferences ?? [], right.imageReferences ?? []);
|
|
2487
2635
|
}
|
|
2488
2636
|
function sameValue(left, right) {
|
|
2489
2637
|
if (left === right)
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Apply the profile's HTTP timeout to npm undici and its fetch globals. */
|
|
2
|
+
export declare function configureOwnedHttpDispatcher(timeoutMs: number): void;
|
|
3
|
+
/** Test and diagnostics seam; reports configured semantics, not the max-int implementation detail. */
|
|
4
|
+
export declare function configuredOwnedHttpIdleTimeoutMs(): number | null;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Mechanically adapted from Pi commit 914cf14
|
|
2
|
+
// packages/coding-agent/src/core/http-dispatcher.ts (MIT).
|
|
3
|
+
// Local modifications: expose only A1's owned timeout seam and retain no private Pi imports.
|
|
4
|
+
import { EnvHttpProxyAgent, install, setGlobalDispatcher } from "undici";
|
|
5
|
+
const MAX_DISABLED_TIMEOUT_MS = 2_147_483_647;
|
|
6
|
+
let configuredTimeoutMs = null;
|
|
7
|
+
/** Apply the profile's HTTP timeout to npm undici and its fetch globals. */
|
|
8
|
+
export function configureOwnedHttpDispatcher(timeoutMs) {
|
|
9
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0)
|
|
10
|
+
throw new TypeError("HTTP idle timeout is invalid");
|
|
11
|
+
const effectiveTimeoutMs = timeoutMs === 0 ? MAX_DISABLED_TIMEOUT_MS : timeoutMs;
|
|
12
|
+
const dispatcher = new EnvHttpProxyAgent({
|
|
13
|
+
allowH2: false,
|
|
14
|
+
bodyTimeout: effectiveTimeoutMs,
|
|
15
|
+
headersTimeout: effectiveTimeoutMs,
|
|
16
|
+
connect: { autoSelectFamilyAttemptTimeout: 2_000 },
|
|
17
|
+
});
|
|
18
|
+
setGlobalDispatcher(dispatcher);
|
|
19
|
+
install?.();
|
|
20
|
+
configuredTimeoutMs = timeoutMs;
|
|
21
|
+
}
|
|
22
|
+
/** Test and diagnostics seam; reports configured semantics, not the max-int implementation detail. */
|
|
23
|
+
export function configuredOwnedHttpIdleTimeoutMs() {
|
|
24
|
+
return configuredTimeoutMs;
|
|
25
|
+
}
|
|
@@ -4,6 +4,9 @@ export * from "./runtime-integration.js";
|
|
|
4
4
|
export * from "./session-integration.js";
|
|
5
5
|
export * from "./model-auth-integration.js";
|
|
6
6
|
export * from "./settings-integration.js";
|
|
7
|
+
export * from "./settings-effects.js";
|
|
8
|
+
export * from "./project-trust-preflight.js";
|
|
9
|
+
export * from "./http-dispatcher.js";
|
|
7
10
|
export * from "./package-integration.js";
|
|
8
11
|
export * from "./resource-extension-integration.js";
|
|
9
12
|
export * from "./workflow-controllers.js";
|
|
@@ -4,6 +4,9 @@ export * from "./runtime-integration.js";
|
|
|
4
4
|
export * from "./session-integration.js";
|
|
5
5
|
export * from "./model-auth-integration.js";
|
|
6
6
|
export * from "./settings-integration.js";
|
|
7
|
+
export * from "./settings-effects.js";
|
|
8
|
+
export * from "./project-trust-preflight.js";
|
|
9
|
+
export * from "./http-dispatcher.js";
|
|
7
10
|
export * from "./package-integration.js";
|
|
8
11
|
export * from "./resource-extension-integration.js";
|
|
9
12
|
export * from "./workflow-controllers.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface PiProjectTrustPreflightRequest {
|
|
2
|
+
readonly cwd: string;
|
|
3
|
+
readonly defaultDecision: "ask" | "always" | "never";
|
|
4
|
+
}
|
|
5
|
+
export type PiProjectTrustPreflightPrompt = (request: PiProjectTrustPreflightRequest) => Promise<boolean | null>;
|
|
6
|
+
export interface PiProjectTrustPreflightResult {
|
|
7
|
+
readonly trusted: boolean;
|
|
8
|
+
readonly source: "no-project-resources" | "saved" | "default" | "interactive" | "fail-closed";
|
|
9
|
+
readonly diagnostic: string | null;
|
|
10
|
+
}
|
|
11
|
+
export interface ResolvePiProjectTrustPreflightOptions {
|
|
12
|
+
readonly cwd: string;
|
|
13
|
+
readonly agentDir: string;
|
|
14
|
+
readonly prompt?: PiProjectTrustPreflightPrompt;
|
|
15
|
+
readonly hasProjectResources?: (cwd: string) => boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolves trust from global state only. It never constructs a project-trusted
|
|
19
|
+
* settings manager or resource loader; callers may do that only after this
|
|
20
|
+
* result exists.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolvePiProjectTrustPreflight(options: ResolvePiProjectTrustPreflightOptions): Promise<PiProjectTrustPreflightResult>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { hasTrustRequiringProjectResources, ProjectTrustStore, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
/**
|
|
3
|
+
* Resolves trust from global state only. It never constructs a project-trusted
|
|
4
|
+
* settings manager or resource loader; callers may do that only after this
|
|
5
|
+
* result exists.
|
|
6
|
+
*/
|
|
7
|
+
export async function resolvePiProjectTrustPreflight(options) {
|
|
8
|
+
const hasProjectResources = options.hasProjectResources ?? hasTrustRequiringProjectResources;
|
|
9
|
+
if (!hasProjectResources(options.cwd)) {
|
|
10
|
+
return { trusted: true, source: "no-project-resources", diagnostic: null };
|
|
11
|
+
}
|
|
12
|
+
const trustStore = new ProjectTrustStore(options.agentDir);
|
|
13
|
+
const saved = trustStore.get(options.cwd);
|
|
14
|
+
if (saved !== null)
|
|
15
|
+
return { trusted: saved, source: "saved", diagnostic: null };
|
|
16
|
+
// projectTrusted:false is the security boundary: this read may see global
|
|
17
|
+
// settings but cannot consume project settings or construct project resources.
|
|
18
|
+
const globalSettings = SettingsManager.create(options.cwd, options.agentDir, { projectTrusted: false });
|
|
19
|
+
const fallback = globalSettings.getDefaultProjectTrust();
|
|
20
|
+
if (fallback === "always")
|
|
21
|
+
return { trusted: true, source: "default", diagnostic: null };
|
|
22
|
+
if (fallback === "never")
|
|
23
|
+
return { trusted: false, source: "default", diagnostic: null };
|
|
24
|
+
if (options.prompt === undefined) {
|
|
25
|
+
return {
|
|
26
|
+
trusted: false,
|
|
27
|
+
source: "fail-closed",
|
|
28
|
+
diagnostic: `Project resources in ${options.cwd} were withheld because trust requires interaction`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const decision = await options.prompt({ cwd: options.cwd, defaultDecision: fallback });
|
|
33
|
+
if (decision === null) {
|
|
34
|
+
return {
|
|
35
|
+
trusted: false,
|
|
36
|
+
source: "fail-closed",
|
|
37
|
+
diagnostic: `Project resources in ${options.cwd} were withheld because trust selection was cancelled`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
trustStore.set(options.cwd, decision);
|
|
41
|
+
return { trusted: decision, source: "interactive", diagnostic: null };
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
return {
|
|
45
|
+
trusted: false,
|
|
46
|
+
source: "fail-closed",
|
|
47
|
+
diagnostic: `Project resources in ${options.cwd} were withheld because trust resolution failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import { type AgentSession, type AgentSessionRuntime, type AgentSessionServices, type ScopedModel } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import { createAgentSessionServices, SettingsManager, type AgentSession, type AgentSessionRuntime, type AgentSessionServices, type ScopedModel } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { resolvePiProjectTrustPreflight, type PiProjectTrustPreflightPrompt } from "./project-trust-preflight.js";
|
|
2
3
|
export interface PiRuntimeIntegrationOptions {
|
|
3
4
|
readonly cwd: string;
|
|
4
5
|
readonly agentDir: string;
|
|
5
6
|
readonly sessionDir?: string;
|
|
7
|
+
readonly sessionPath?: string;
|
|
8
|
+
readonly projectTrustPrompt?: PiProjectTrustPreflightPrompt;
|
|
9
|
+
readonly preflightDependencies?: PiRuntimePreflightDependencies;
|
|
10
|
+
}
|
|
11
|
+
export interface PiRuntimePreflightDependencies {
|
|
12
|
+
readonly resolveTrust?: typeof resolvePiProjectTrustPreflight;
|
|
13
|
+
readonly createSettingsManager?: (cwd: string, agentDir: string, projectTrusted: boolean) => SettingsManager;
|
|
14
|
+
readonly createServices?: typeof createAgentSessionServices;
|
|
6
15
|
}
|
|
7
16
|
export type PiSessionReplacement = {
|
|
8
17
|
readonly kind: "new";
|
|
@@ -26,6 +35,12 @@ interface ConfiguredModelScope {
|
|
|
26
35
|
* saved default when it is in scope, otherwise the first scoped model.
|
|
27
36
|
*/
|
|
28
37
|
export declare function resolveConfiguredModelScope(services: Pick<AgentSessionServices, "settingsManager" | "modelRuntime">): Promise<ConfiguredModelScope>;
|
|
38
|
+
export declare function createPiRuntimeServicesAfterTrust(options: Pick<PiRuntimeIntegrationOptions, "agentDir" | "projectTrustPrompt" | "preflightDependencies"> & {
|
|
39
|
+
readonly cwd: string;
|
|
40
|
+
}): Promise<{
|
|
41
|
+
readonly services: AgentSessionServices;
|
|
42
|
+
readonly trust: Awaited<ReturnType<typeof resolvePiProjectTrustPreflight>>;
|
|
43
|
+
}>;
|
|
29
44
|
export declare function createPiRuntimeIntegration(options: PiRuntimeIntegrationOptions): Promise<AgentSessionRuntime>;
|
|
30
45
|
export declare function bindPiRuntimeSession(runtime: AgentSessionRuntime, rebind: (session: AgentSession) => Promise<void>): () => void;
|
|
31
46
|
export declare function replacePiRuntimeSession(runtime: AgentSessionRuntime, replacement: PiSessionReplacement): Promise<{
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, resolveModelScopeWithDiagnostics, SessionManager, } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, resolveModelScopeWithDiagnostics, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { resolvePiProjectTrustPreflight, } from "./project-trust-preflight.js";
|
|
2
3
|
/**
|
|
3
4
|
* Mirrors pinned Pi's CLI startup: resolve the `models` patterns from settings
|
|
4
5
|
* into a scoped model list, keep the resolver's warnings (e.g. "No models match
|
|
@@ -29,10 +30,32 @@ export async function resolveConfiguredModelScope(services) {
|
|
|
29
30
|
diagnostics: diagnostics.map(diagnostic => ({ type: diagnostic.type, message: diagnostic.message })),
|
|
30
31
|
};
|
|
31
32
|
}
|
|
33
|
+
export async function createPiRuntimeServicesAfterTrust(options) {
|
|
34
|
+
const resolveTrust = options.preflightDependencies?.resolveTrust ?? resolvePiProjectTrustPreflight;
|
|
35
|
+
const createSettingsManager = options.preflightDependencies?.createSettingsManager
|
|
36
|
+
?? ((targetCwd, agentDir, projectTrusted) => SettingsManager.create(targetCwd, agentDir, { projectTrusted }));
|
|
37
|
+
const createServices = options.preflightDependencies?.createServices ?? createAgentSessionServices;
|
|
38
|
+
const trust = await resolveTrust({
|
|
39
|
+
cwd: options.cwd,
|
|
40
|
+
agentDir: options.agentDir,
|
|
41
|
+
...(options.projectTrustPrompt === undefined ? {} : { prompt: options.projectTrustPrompt }),
|
|
42
|
+
});
|
|
43
|
+
const settingsManager = createSettingsManager(options.cwd, options.agentDir, trust.trusted);
|
|
44
|
+
const services = await createServices({ cwd: options.cwd, agentDir: options.agentDir, settingsManager });
|
|
45
|
+
return { services, trust };
|
|
46
|
+
}
|
|
32
47
|
export async function createPiRuntimeIntegration(options) {
|
|
33
|
-
const
|
|
48
|
+
const sessionDir = options.sessionDir ?? process.env.PI_CODING_AGENT_SESSION_DIR;
|
|
49
|
+
const sessionManager = options.sessionPath === undefined
|
|
50
|
+
? SessionManager.create(options.cwd, sessionDir)
|
|
51
|
+
: SessionManager.open(options.sessionPath, sessionDir, options.cwd);
|
|
34
52
|
const createRuntime = async ({ cwd, sessionManager: targetSessionManager, sessionStartEvent, }) => {
|
|
35
|
-
const services = await
|
|
53
|
+
const { services, trust } = await createPiRuntimeServicesAfterTrust({
|
|
54
|
+
cwd,
|
|
55
|
+
agentDir: options.agentDir,
|
|
56
|
+
...(options.projectTrustPrompt === undefined ? {} : { projectTrustPrompt: options.projectTrustPrompt }),
|
|
57
|
+
...(options.preflightDependencies === undefined ? {} : { preflightDependencies: options.preflightDependencies }),
|
|
58
|
+
});
|
|
36
59
|
const modelScope = await resolveConfiguredModelScope(services);
|
|
37
60
|
const hasExistingSession = targetSessionManager.buildSessionContext().messages.length > 0;
|
|
38
61
|
const created = await createAgentSessionFromServices({
|
|
@@ -43,7 +66,14 @@ export async function createPiRuntimeIntegration(options) {
|
|
|
43
66
|
...(modelScope.thinkingLevel && !hasExistingSession ? { thinkingLevel: modelScope.thinkingLevel } : {}),
|
|
44
67
|
...(modelScope.scopedModels.length > 0 ? { scopedModels: [...modelScope.scopedModels] } : {}),
|
|
45
68
|
});
|
|
46
|
-
return {
|
|
69
|
+
return {
|
|
70
|
+
...created,
|
|
71
|
+
services,
|
|
72
|
+
diagnostics: [
|
|
73
|
+
...(trust.diagnostic === null ? [] : [{ type: "warning", message: trust.diagnostic }]),
|
|
74
|
+
...modelScope.diagnostics,
|
|
75
|
+
],
|
|
76
|
+
};
|
|
47
77
|
};
|
|
48
78
|
return createAgentSessionRuntime(createRuntime, {
|
|
49
79
|
cwd: options.cwd,
|