@timurproko/a1 0.2.7-dev.808 → 0.2.7-dev.809
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/session-shell/dialog-coordinator.d.ts +42 -0
- package/dist/app/session-shell/dialog-coordinator.js +133 -0
- package/dist/app/session-shell/dock-chrome.d.ts +34 -0
- package/dist/app/session-shell/dock-chrome.js +109 -0
- package/dist/app/session-shell/exit-notice.d.ts +1 -1
- package/dist/app/session-shell/exit-notice.js +1 -1
- package/dist/app/session-shell/extension-surface.d.ts +30 -0
- package/dist/app/session-shell/extension-surface.js +105 -0
- package/dist/app/session-shell/input-surface-switch.d.ts +36 -0
- package/dist/app/session-shell/input-surface-switch.js +85 -0
- package/dist/app/session-shell/login-interaction.d.ts +27 -0
- package/dist/app/session-shell/login-interaction.js +91 -0
- package/dist/app/session-shell/prompt-submission.d.ts +48 -0
- package/dist/app/session-shell/prompt-submission.js +276 -0
- package/dist/app/session-shell/resident-tab-coordinator.d.ts +41 -0
- package/dist/app/session-shell/resident-tab-coordinator.js +120 -0
- package/dist/app/session-shell/session-concern.d.ts +22 -0
- package/dist/app/session-shell/session-concern.js +29 -0
- package/dist/app/session-shell/session-lifecycle.d.ts +29 -0
- package/dist/app/session-shell/session-lifecycle.js +49 -0
- package/dist/app/session-shell/session-shell-root.d.ts +1 -1
- package/dist/app/session-shell/session-shell-root.js +182 -931
- package/dist/app/session-shell/session-shell.d.ts +2 -8
- package/dist/app/session-shell/session-shell.js +202 -901
- package/dist/app/session-shell/settings-bindings.d.ts +17 -0
- package/dist/app/session-shell/settings-bindings.js +91 -0
- package/dist/app/session-shell/shell-editor-factory.d.ts +27 -0
- package/dist/app/session-shell/shell-editor-factory.js +102 -0
- package/dist/app/session-shell/shell-host.d.ts +6 -0
- package/dist/app/session-shell/shell-host.js +8 -0
- package/dist/app/session-shell/shell-layout.d.ts +64 -0
- package/dist/app/session-shell/shell-layout.js +18 -0
- package/dist/app/session-shell/suggestion-surface.d.ts +25 -0
- package/dist/app/session-shell/suggestion-surface.js +63 -0
- package/dist/app/session-shell/transcript-document.d.ts +78 -0
- package/dist/app/session-shell/transcript-document.js +365 -0
- package/dist/app/session-shell/viewport-layout.d.ts +31 -0
- package/dist/app/session-shell/viewport-layout.js +160 -0
- package/dist/app/session-shell/workflow-output.d.ts +33 -0
- package/dist/app/session-shell/workflow-output.js +174 -0
- package/dist/app/session-shell/workflow-router.d.ts +25 -0
- package/dist/app/session-shell/workflow-router.js +39 -0
- package/dist/composition/owned-ui.d.ts +4 -25
- package/dist/composition/owned-ui.js +26 -270
- package/dist/composition/resident-tab-bridge.d.ts +16 -0
- package/dist/composition/resident-tab-bridge.js +159 -0
- package/dist/composition/session-backend.d.ts +19 -0
- package/dist/composition/session-backend.js +1 -0
- package/dist/composition/session-runtime-registry.d.ts +20 -0
- package/dist/composition/session-runtime-registry.js +85 -0
- package/dist/composition/startup-route.d.ts +32 -0
- package/dist/composition/startup-route.js +72 -0
- package/dist/integrations/pi/engine/adapter.d.ts +4 -77
- package/dist/integrations/pi/engine/adapter.js +85 -393
- package/dist/integrations/pi/engine/command-dispatch.d.ts +13 -0
- package/dist/integrations/pi/engine/command-dispatch.js +57 -0
- package/dist/integrations/pi/engine/host.js +2 -2
- package/dist/integrations/pi/engine/ports.d.ts +56 -0
- package/dist/integrations/pi/engine/ports.js +89 -0
- package/dist/integrations/pi/engine/view-state.d.ts +42 -0
- package/dist/integrations/pi/engine/view-state.js +126 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/darwin-arm64/terminal-host.manifest.json +3 -3
- package/dist/native/darwin-arm64/terminal-host.node +0 -0
- package/dist/native/darwin-x64/manifest.json +1 -1
- package/dist/native/darwin-x64/terminal-host.manifest.json +3 -3
- package/dist/native/darwin-x64/terminal-host.node +0 -0
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/linux-x64/terminal-host.manifest.json +3 -3
- package/dist/native/linux-x64/terminal-host.node +0 -0
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.node +0 -0
- package/dist/native/win32-x64/terminal-host.manifest.json +3 -3
- package/dist/native/win32-x64/terminal-host.node +0 -0
- package/docs/architecture/boundaries.md +14 -0
- package/docs/architecture/resident-tabs.md +6 -6
- package/package.json +1 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type OwnedUiCommand, type OwnedUiCommandResult, type OwnedUiDiagnostics } from "../../../contracts/owned-ui/index.js";
|
|
2
2
|
import type { PiEmittedEvent } from "./event-delivery.js";
|
|
3
|
+
import type { PiEngineRuntime } from "./session-runtime.js";
|
|
4
|
+
import type { EngineViewState } from "./view-state.js";
|
|
3
5
|
export type AdapterCommandResult = OwnedUiCommandResult;
|
|
4
6
|
export interface PiCommandDispatchPorts {
|
|
5
7
|
sessionId(): string;
|
|
@@ -36,3 +38,14 @@ export declare class PiCommandDispatch {
|
|
|
36
38
|
reset(): void;
|
|
37
39
|
execute(command: OwnedUiCommand): Promise<AdapterCommandResult>;
|
|
38
40
|
}
|
|
41
|
+
/** What carrying out a command reaches: the running engine, the view state it updates, and disposal. */
|
|
42
|
+
export interface EngineCommandContext {
|
|
43
|
+
readonly engine: Pick<PiEngineRuntime, "runtime" | "session" | "generation" | "sessionCommands">;
|
|
44
|
+
readonly state: Pick<EngineViewState, "activeModel" | "thinkingLevel" | "setTerminal" | "terminal">;
|
|
45
|
+
/** False while delivery is overloaded or admission is stopped; a late model change then stays unpublished. */
|
|
46
|
+
admissionOpen(): boolean;
|
|
47
|
+
emit(value: PiEmittedEvent): void;
|
|
48
|
+
dispose(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
/** Carries out one admitted command against the running engine; throws when no session runs or the command fails. */
|
|
51
|
+
export declare function performEngineCommand(command: OwnedUiCommand, context: EngineCommandContext): Promise<void>;
|
|
@@ -123,3 +123,60 @@ export class PiCommandDispatch {
|
|
|
123
123
|
return { outcome, diagnostic };
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
+
// Invariant: prompt input and session controls run through the engine's own session commands, which own queuing and images.
|
|
127
|
+
async function runSessionCommand(request, context) {
|
|
128
|
+
const result = await context.engine.sessionCommands?.execute(request);
|
|
129
|
+
if (!result || result.outcome === "rejected" || result.outcome === "failed") {
|
|
130
|
+
throw new Error(request.type === "retry" ? "no previous prompt is available to retry" : `Pi session command failed: ${request.type}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function runPromptCommand(command, context) {
|
|
134
|
+
return runSessionCommand({
|
|
135
|
+
type: command.type,
|
|
136
|
+
text: command.text,
|
|
137
|
+
...(command.images === undefined ? {} : { images: [...command.images] }),
|
|
138
|
+
}, context);
|
|
139
|
+
}
|
|
140
|
+
const ownedUiStateCommand = () => {
|
|
141
|
+
throw new Error("owned UI state commands belong to the owned UI layer, not the Pi engine adapter");
|
|
142
|
+
};
|
|
143
|
+
// Invariant: one handler per command type; the table is exhaustive, so a new command type cannot be silently ignored.
|
|
144
|
+
const ENGINE_COMMANDS = {
|
|
145
|
+
"prompt": (command, context) => runPromptCommand(command, context),
|
|
146
|
+
"steer": (command, context) => runPromptCommand(command, context),
|
|
147
|
+
"follow-up": (command, context) => runPromptCommand(command, context),
|
|
148
|
+
"abort": (_command, context) => runSessionCommand({ type: "abort" }, context),
|
|
149
|
+
"retry": (_command, context) => runSessionCommand({ type: "retry" }, context),
|
|
150
|
+
"compact": (_command, context) => runSessionCommand({ type: "compact" }, context),
|
|
151
|
+
"set-model": async (command, context, { runtime, session, generation }) => {
|
|
152
|
+
const model = runtime.services.modelRuntime.getModel(command.model.providerId, command.model.modelId);
|
|
153
|
+
if (!model)
|
|
154
|
+
throw new Error(`model is unavailable: ${command.model.providerId}/${command.model.modelId}`);
|
|
155
|
+
await session.setModel(model);
|
|
156
|
+
if (generation === context.engine.generation && context.admissionOpen())
|
|
157
|
+
context.state.activeModel = { ...command.model };
|
|
158
|
+
},
|
|
159
|
+
"set-thinking-level": (command, context, { session }) => {
|
|
160
|
+
session.setThinkingLevel(command.thinkingLevel);
|
|
161
|
+
context.state.thinkingLevel = command.thinkingLevel;
|
|
162
|
+
},
|
|
163
|
+
"new-session": async (_command, _context, { runtime }) => { await runtime.newSession(); },
|
|
164
|
+
"resume-session": async (command, _context, { runtime }) => { await runtime.switchSession(command.sessionPath); },
|
|
165
|
+
"resize-surface": (command, context) => {
|
|
166
|
+
context.state.setTerminal(command.surface);
|
|
167
|
+
context.emit({ type: "terminal-surface", surface: context.state.terminal });
|
|
168
|
+
},
|
|
169
|
+
"shutdown": (_command, context) => context.dispose(),
|
|
170
|
+
"set-setting": ownedUiStateCommand,
|
|
171
|
+
"apply-customization": ownedUiStateCommand,
|
|
172
|
+
"remove-customization": ownedUiStateCommand,
|
|
173
|
+
};
|
|
174
|
+
/** Carries out one admitted command against the running engine; throws when no session runs or the command fails. */
|
|
175
|
+
export async function performEngineCommand(command, context) {
|
|
176
|
+
const { runtime, session, generation } = context.engine;
|
|
177
|
+
if (!runtime || !session)
|
|
178
|
+
throw new Error("engine session is unavailable");
|
|
179
|
+
// Invariant: the table maps each type to the handler for exactly that command, so the command fits its own handler.
|
|
180
|
+
const handler = ENGINE_COMMANDS[command.type];
|
|
181
|
+
await handler(command, context, { runtime, session, generation });
|
|
182
|
+
}
|
|
@@ -86,7 +86,7 @@ class ProcessPiEngineHost {
|
|
|
86
86
|
return;
|
|
87
87
|
this.#disposed = true;
|
|
88
88
|
this.#abort.abort();
|
|
89
|
-
const live = [...this.#sessions.values()].filter(session => !session.disposed);
|
|
89
|
+
const live = [...this.#sessions.values()].filter(session => !session.identity.disposed);
|
|
90
90
|
this.#sessions.clear();
|
|
91
91
|
await Promise.all(live.map(session => session.dispose()));
|
|
92
92
|
}
|
|
@@ -107,7 +107,7 @@ class ProcessPiEngineHost {
|
|
|
107
107
|
}
|
|
108
108
|
#forgetDisposedSessions() {
|
|
109
109
|
for (const [sessionId, session] of this.#sessions)
|
|
110
|
-
if (session.disposed)
|
|
110
|
+
if (session.identity.disposed)
|
|
111
111
|
this.#sessions.delete(sessionId);
|
|
112
112
|
}
|
|
113
113
|
// Rationale: the profile's timeout is installed once, by whichever session loads it first; a written
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { AgentSession } from "../startup-public.js";
|
|
2
|
+
import type { OwnedUiExtensionPort, OwnedUiImageAttachment, OwnedUiReleaseUpdate, OwnedUiSessionCatalogPort, OwnedUiSessionIdentityPort, OwnedUiSessionPort, OwnedUiSessionSettingsPort, OwnedUiWorkflowPort } from "../../../contracts/owned-ui/index.js";
|
|
3
|
+
import type { PiPinnedSettingsCallback, PiPinnedSettingsSnapshot, PiSessionSelectorContext, PiTreeSelectorContext, PiWorkflowInteractionHost, PiWorkflowRequest, PiWorkflowResult } from "./workflows.js";
|
|
4
|
+
import type { PiExtensionUiBinding } from "./extension-ui-binding.js";
|
|
5
|
+
import type { PiResourceCatalog } from "./resource-catalog.js";
|
|
6
|
+
import type { PiEngineRuntime } from "./session-runtime.js";
|
|
7
|
+
import type { PiEngineSettings } from "./settings-port.js";
|
|
8
|
+
import type { PiWorkflowContexts } from "./workflow-contexts.js";
|
|
9
|
+
import type { PiWorkflowRunner } from "./workflow-runner.js";
|
|
10
|
+
/**
|
|
11
|
+
* The Pi objects pinned selectors and transcript renderers are built from. They are not part of the
|
|
12
|
+
* session backend contract; only the session presenters owner reads them.
|
|
13
|
+
*/
|
|
14
|
+
export interface PiSessionPresentationSource {
|
|
15
|
+
pinnedModelSelectorContext(): ReturnType<PiWorkflowContexts["pinnedModelSelectorContext"]>;
|
|
16
|
+
pinnedSessionSelectorContext(): PiSessionSelectorContext;
|
|
17
|
+
pinnedTreeSelectorContext(): PiTreeSelectorContext;
|
|
18
|
+
pinnedMessageRenderer(customType: string): unknown;
|
|
19
|
+
pinnedToolRenderers(toolName: string): unknown;
|
|
20
|
+
pinnedShortcutDescriptions(bindings: Parameters<AgentSession["extensionRunner"]["getShortcuts"]>[0]): readonly {
|
|
21
|
+
readonly key: string;
|
|
22
|
+
readonly description: string;
|
|
23
|
+
}[];
|
|
24
|
+
pinnedSettingsModels(): Pick<PiPinnedSettingsSnapshot, "currentModel" | "availableDefaultModels">;
|
|
25
|
+
applyPinnedSettingValue(callback: PiPinnedSettingsCallback, value: unknown): Promise<PiWorkflowResult>;
|
|
26
|
+
}
|
|
27
|
+
export interface PiEngineAdapterPorts {
|
|
28
|
+
readonly identity: OwnedUiSessionIdentityPort;
|
|
29
|
+
readonly session: OwnedUiSessionPort;
|
|
30
|
+
readonly workflows: OwnedUiWorkflowPort;
|
|
31
|
+
readonly settings: OwnedUiSessionSettingsPort;
|
|
32
|
+
readonly catalog: OwnedUiSessionCatalogPort;
|
|
33
|
+
readonly extensions: OwnedUiExtensionPort;
|
|
34
|
+
readonly presentation: PiSessionPresentationSource;
|
|
35
|
+
}
|
|
36
|
+
/** The adapter's collaborators and its own session operations, which every port member reaches at call time. */
|
|
37
|
+
export interface EnginePortParts {
|
|
38
|
+
readonly sessionId: string;
|
|
39
|
+
readonly agentDir: string;
|
|
40
|
+
readonly engine: Pick<PiEngineRuntime, "cwd" | "generation" | "bindingGeneration" | "currentSessionResumeMetadata" | "currentSessionFile">;
|
|
41
|
+
readonly contexts: PiWorkflowContexts;
|
|
42
|
+
readonly workflows: PiWorkflowRunner;
|
|
43
|
+
readonly settings: PiEngineSettings;
|
|
44
|
+
readonly resources: PiResourceCatalog;
|
|
45
|
+
readonly extensions: PiExtensionUiBinding;
|
|
46
|
+
readonly session: OwnedUiSessionPort;
|
|
47
|
+
disposed(): boolean;
|
|
48
|
+
executeWorkflow(request: PiWorkflowRequest): Promise<PiWorkflowResult>;
|
|
49
|
+
clearQueuedWorkflows(): readonly string[];
|
|
50
|
+
setWorkflowInteractionHost(interaction: PiWorkflowInteractionHost): void;
|
|
51
|
+
announceReleaseUpdate(release: OwnedUiReleaseUpdate): void;
|
|
52
|
+
resolveTranscriptImage(assetId: string): OwnedUiImageAttachment | null;
|
|
53
|
+
/** The running session's extension runner; throws when no session runs. */
|
|
54
|
+
extensionRunner(): AgentSession["extensionRunner"] | undefined;
|
|
55
|
+
}
|
|
56
|
+
export declare function createEnginePorts(parts: EnginePortParts): PiEngineAdapterPorts;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Invariant: every port member delegates at call time, so ports built once follow session replacement.
|
|
2
|
+
export function createEnginePorts(parts) {
|
|
3
|
+
const { engine, contexts, workflows, settings, resources, extensions } = parts;
|
|
4
|
+
return {
|
|
5
|
+
identity: {
|
|
6
|
+
sessionId: parts.sessionId,
|
|
7
|
+
agentDir: parts.agentDir,
|
|
8
|
+
get cwd() { return engine.cwd; },
|
|
9
|
+
get sessionGeneration() { return engine.generation; },
|
|
10
|
+
get sessionBindingGeneration() { return engine.bindingGeneration; },
|
|
11
|
+
get disposed() { return parts.disposed(); },
|
|
12
|
+
currentSessionResumeMetadata: () => engine.currentSessionResumeMetadata(),
|
|
13
|
+
currentSessionFile: () => engine.currentSessionFile(),
|
|
14
|
+
},
|
|
15
|
+
session: parts.session,
|
|
16
|
+
workflows: {
|
|
17
|
+
executeWorkflow: request => parts.executeWorkflow(request),
|
|
18
|
+
executeBashWorkflow: (command, excludeFromContext) => workflows.executeBashWorkflow(command, excludeFromContext),
|
|
19
|
+
cycleModelWorkflow: direction => workflows.cycleModelWorkflow(direction),
|
|
20
|
+
workflowAutocompleteCommands: () => resources.workflowAutocompleteCommands(),
|
|
21
|
+
clearQueuedWorkflows: () => parts.clearQueuedWorkflows(),
|
|
22
|
+
reloadBlockedResult: () => workflows.reloadBlockedResult(),
|
|
23
|
+
copyWorkflowText: text => workflows.copyWorkflowText(text),
|
|
24
|
+
setWorkflowInteractionHost: interaction => { parts.setWorkflowInteractionHost(interaction); },
|
|
25
|
+
},
|
|
26
|
+
settings: {
|
|
27
|
+
get productMode() { return settings.productMode; },
|
|
28
|
+
snapshot: () => {
|
|
29
|
+
// Invariant: the selector-only model values travel on the presentation source, not in the neutral snapshot.
|
|
30
|
+
const { currentModel: _currentModel, availableDefaultModels: _availableDefaultModels, ...snapshot } = settings.pinnedSettingsSnapshot();
|
|
31
|
+
return snapshot;
|
|
32
|
+
},
|
|
33
|
+
bindOwner: (owner, handlers) => settings.bindSettingsOwner(owner, handlers),
|
|
34
|
+
setDefaultThinkingLevel: level => { settings.setDefaultThinkingLevel(level); },
|
|
35
|
+
configuredTheme: () => settings.configuredTheme(),
|
|
36
|
+
},
|
|
37
|
+
catalog: {
|
|
38
|
+
modelsContext: () => contexts.modelsContext(),
|
|
39
|
+
setSessionModelScope: scopeIds => { contexts.setSessionModelScope(scopeIds); },
|
|
40
|
+
persistModelScope: scopeIds => { contexts.persistModelScope(scopeIds); },
|
|
41
|
+
refreshModels: signal => contexts.refreshModels(signal),
|
|
42
|
+
pinnedScopedModelsContext: () => contexts.pinnedScopedModelsContext(),
|
|
43
|
+
updateScopedModels: enabledModelIds => { contexts.updateScopedModels(enabledModelIds); },
|
|
44
|
+
persistScopedModels: enabledModelIds => { contexts.persistScopedModels(enabledModelIds); },
|
|
45
|
+
refreshScopedModels: signal => contexts.refreshScopedModels(signal),
|
|
46
|
+
pinnedProjectTrustContext: () => contexts.pinnedProjectTrustContext(),
|
|
47
|
+
persistProjectTrust: updates => { contexts.persistProjectTrust(updates); },
|
|
48
|
+
pinnedLoginOptions: authType => contexts.loginOptions(authType),
|
|
49
|
+
pinnedLoginMethodOptions: providerReference => contexts.pinnedLoginMethodOptions(providerReference),
|
|
50
|
+
pinnedAmbientAuthentication: selection => contexts.pinnedAmbientAuthentication(selection),
|
|
51
|
+
pinnedLogoutOptions: () => contexts.logoutOptions(),
|
|
52
|
+
pinnedForkOptions: () => contexts.pinnedForkOptions(),
|
|
53
|
+
},
|
|
54
|
+
extensions: {
|
|
55
|
+
nonVisualResources: () => resources.nonVisualResources(),
|
|
56
|
+
extensionResources: () => resources.extensionResources(),
|
|
57
|
+
resolveTranscriptImage: assetId => parts.resolveTranscriptImage(assetId),
|
|
58
|
+
visualExtensionSupport: () => extensions.support(),
|
|
59
|
+
bindExtensionUi: (ui, shutdown) => extensions.bind(ui, shutdown),
|
|
60
|
+
unbindExtensionUi: () => extensions.unbind(),
|
|
61
|
+
bindClipboardWriter: writer => workflows.bindClipboardWriter(writer),
|
|
62
|
+
announceReleaseUpdate: release => { parts.announceReleaseUpdate(release); },
|
|
63
|
+
},
|
|
64
|
+
presentation: {
|
|
65
|
+
pinnedModelSelectorContext: () => contexts.pinnedModelSelectorContext(),
|
|
66
|
+
pinnedSessionSelectorContext: () => contexts.pinnedSessionSelectorContext(),
|
|
67
|
+
pinnedTreeSelectorContext: () => contexts.pinnedTreeSelectorContext(),
|
|
68
|
+
pinnedMessageRenderer: customType => parts.extensionRunner()?.getMessageRenderer?.(customType),
|
|
69
|
+
pinnedToolRenderers: toolName => {
|
|
70
|
+
const runner = parts.extensionRunner();
|
|
71
|
+
if (runner === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
if (typeof runner.resolveToolRenderers === "function") {
|
|
74
|
+
return runner.resolveToolRenderers(toolName, () => runner.getToolDefinition?.(toolName));
|
|
75
|
+
}
|
|
76
|
+
return runner.getToolDefinition?.(toolName);
|
|
77
|
+
},
|
|
78
|
+
pinnedShortcutDescriptions: bindings => {
|
|
79
|
+
const shortcuts = parts.extensionRunner()?.getShortcuts?.(bindings);
|
|
80
|
+
return shortcuts === undefined ? [] : [...shortcuts].map(([key, shortcut]) => ({ key, description: shortcut.description ?? shortcut.extensionPath }));
|
|
81
|
+
},
|
|
82
|
+
pinnedSettingsModels: () => {
|
|
83
|
+
const { currentModel, availableDefaultModels } = settings.pinnedSettingsSnapshot();
|
|
84
|
+
return { ...(currentModel === undefined ? {} : { currentModel }), availableDefaultModels };
|
|
85
|
+
},
|
|
86
|
+
applyPinnedSettingValue: (callback, value) => settings.applyPinnedSettingValue(callback, value),
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { OwnedUiDiagnostics, OwnedUiEditorState, OwnedUiModelInfo, OwnedUiSessionViewModel, OwnedUiStatusView, OwnedUiTerminalSurface, OwnedUiThinkingLevel, OwnedUiUsageView } from "../../../contracts/owned-ui/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* The engine adapter's own view-model state: lifecycle, editor, status, terminal surface, active
|
|
4
|
+
* model, thinking level, the usage cache, diagnostics, and the view revision. Every change that
|
|
5
|
+
* touches more than one field is one named mutator, so the adapter and its collaborators never
|
|
6
|
+
* rebuild these objects inline.
|
|
7
|
+
*/
|
|
8
|
+
export declare class EngineViewState {
|
|
9
|
+
#private;
|
|
10
|
+
lifecycle: OwnedUiSessionViewModel["lifecycle"];
|
|
11
|
+
activeModel: OwnedUiModelInfo | null;
|
|
12
|
+
thinkingLevel: OwnedUiThinkingLevel;
|
|
13
|
+
get revision(): number;
|
|
14
|
+
get editor(): OwnedUiEditorState;
|
|
15
|
+
get status(): OwnedUiStatusView;
|
|
16
|
+
get terminal(): OwnedUiTerminalSurface;
|
|
17
|
+
get diagnostics(): readonly OwnedUiDiagnostics[];
|
|
18
|
+
/** Any emitted event advances the revision. */
|
|
19
|
+
advance(): void;
|
|
20
|
+
/** A full view is about to be published: usage is recomputed and the revision advances. */
|
|
21
|
+
invalidate(): void;
|
|
22
|
+
invalidateUsage(): void;
|
|
23
|
+
/** The cached usage view, computed once per published view. */
|
|
24
|
+
usage(read: () => OwnedUiUsageView): OwnedUiUsageView;
|
|
25
|
+
/** The engine started: the session is ready and accepts submissions. */
|
|
26
|
+
markReady(): void;
|
|
27
|
+
/** A run started with `message` as its working status; true when the session was already busy. */
|
|
28
|
+
enterWork(message: string): boolean;
|
|
29
|
+
leaveWork(): void;
|
|
30
|
+
/** Records compaction or retry progress; false when it did not change. */
|
|
31
|
+
setWorkProgress(percent: Exclude<OwnedUiStatusView["workingProgress"], undefined>): boolean;
|
|
32
|
+
setQueuedSubmissions(queuedSubmissions: readonly string[]): void;
|
|
33
|
+
/** Fills an empty editor with `text`, as tree navigation does for the selected branch's prompt. */
|
|
34
|
+
offerEditorText(text: string): void;
|
|
35
|
+
setHardwareCursor(hardwareCursor: boolean): void;
|
|
36
|
+
setTerminal(surface: OwnedUiTerminalSurface): void;
|
|
37
|
+
/** A replaced session starts with an empty editor that accepts submissions and no working status. */
|
|
38
|
+
replaceSession(): void;
|
|
39
|
+
/** Delivery overload ended the run: ready when it can resume, stopped once disposed, otherwise failed. */
|
|
40
|
+
settleOverload(canResume: boolean, disposed: boolean): void;
|
|
41
|
+
recordDiagnostic(severity: OwnedUiDiagnostics["severity"], code: string, message: string, recoverable: boolean): OwnedUiDiagnostics;
|
|
42
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const DEFAULT_SURFACE = {
|
|
2
|
+
columns: 100,
|
|
3
|
+
rows: 32,
|
|
4
|
+
focusedRegion: "editor",
|
|
5
|
+
hardwareCursor: false,
|
|
6
|
+
};
|
|
7
|
+
const MAX_DIAGNOSTICS = 100;
|
|
8
|
+
const MAX_STATUS_DIAGNOSTICS = 8;
|
|
9
|
+
/**
|
|
10
|
+
* The engine adapter's own view-model state: lifecycle, editor, status, terminal surface, active
|
|
11
|
+
* model, thinking level, the usage cache, diagnostics, and the view revision. Every change that
|
|
12
|
+
* touches more than one field is one named mutator, so the adapter and its collaborators never
|
|
13
|
+
* rebuild these objects inline.
|
|
14
|
+
*/
|
|
15
|
+
export class EngineViewState {
|
|
16
|
+
#revision = 0;
|
|
17
|
+
#editor = {
|
|
18
|
+
text: "",
|
|
19
|
+
queuedSubmissions: [],
|
|
20
|
+
selection: null,
|
|
21
|
+
cursorOffset: 0,
|
|
22
|
+
historyRevision: 0,
|
|
23
|
+
submitEnabled: false,
|
|
24
|
+
};
|
|
25
|
+
#status = {
|
|
26
|
+
title: "Pi",
|
|
27
|
+
workingMessage: null,
|
|
28
|
+
diagnostics: [],
|
|
29
|
+
badges: [],
|
|
30
|
+
};
|
|
31
|
+
#terminal = DEFAULT_SURFACE;
|
|
32
|
+
lifecycle = "starting";
|
|
33
|
+
activeModel = null;
|
|
34
|
+
thinkingLevel = "medium";
|
|
35
|
+
#usage;
|
|
36
|
+
#diagnostics = [];
|
|
37
|
+
get revision() { return this.#revision; }
|
|
38
|
+
get editor() { return this.#editor; }
|
|
39
|
+
get status() { return this.#status; }
|
|
40
|
+
get terminal() { return this.#terminal; }
|
|
41
|
+
get diagnostics() { return this.#diagnostics; }
|
|
42
|
+
/** Any emitted event advances the revision. */
|
|
43
|
+
advance() {
|
|
44
|
+
this.#revision += 1;
|
|
45
|
+
}
|
|
46
|
+
/** A full view is about to be published: usage is recomputed and the revision advances. */
|
|
47
|
+
invalidate() {
|
|
48
|
+
this.#usage = undefined;
|
|
49
|
+
this.#revision += 1;
|
|
50
|
+
}
|
|
51
|
+
invalidateUsage() {
|
|
52
|
+
this.#usage = undefined;
|
|
53
|
+
}
|
|
54
|
+
/** The cached usage view, computed once per published view. */
|
|
55
|
+
usage(read) {
|
|
56
|
+
return this.#usage ??= read();
|
|
57
|
+
}
|
|
58
|
+
/** The engine started: the session is ready and accepts submissions. */
|
|
59
|
+
markReady() {
|
|
60
|
+
this.lifecycle = "ready";
|
|
61
|
+
this.#editor = { ...this.#editor, submitEnabled: true };
|
|
62
|
+
}
|
|
63
|
+
/** A run started with `message` as its working status; true when the session was already busy. */
|
|
64
|
+
enterWork(message) {
|
|
65
|
+
const wasBusy = this.lifecycle === "busy";
|
|
66
|
+
this.lifecycle = "busy";
|
|
67
|
+
this.#status = { ...this.#status, workingMessage: message, workingProgress: null };
|
|
68
|
+
return wasBusy;
|
|
69
|
+
}
|
|
70
|
+
leaveWork() {
|
|
71
|
+
this.lifecycle = "ready";
|
|
72
|
+
this.#status = { ...this.#status, workingMessage: null, workingProgress: null };
|
|
73
|
+
}
|
|
74
|
+
/** Records compaction or retry progress; false when it did not change. */
|
|
75
|
+
setWorkProgress(percent) {
|
|
76
|
+
if (this.#status.workingProgress === percent)
|
|
77
|
+
return false;
|
|
78
|
+
this.#status = { ...this.#status, workingProgress: percent };
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
setQueuedSubmissions(queuedSubmissions) {
|
|
82
|
+
this.#editor = { ...this.#editor, queuedSubmissions: [...queuedSubmissions], historyRevision: this.#editor.historyRevision + 1 };
|
|
83
|
+
}
|
|
84
|
+
/** Fills an empty editor with `text`, as tree navigation does for the selected branch's prompt. */
|
|
85
|
+
offerEditorText(text) {
|
|
86
|
+
if (!this.#editor.text.trim()) {
|
|
87
|
+
this.#editor = { ...this.#editor, text, selection: null, cursorOffset: text.length };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
setHardwareCursor(hardwareCursor) {
|
|
91
|
+
this.#terminal = { ...this.#terminal, hardwareCursor };
|
|
92
|
+
}
|
|
93
|
+
setTerminal(surface) {
|
|
94
|
+
this.#terminal = { ...surface };
|
|
95
|
+
}
|
|
96
|
+
/** A replaced session starts with an empty editor that accepts submissions and no working status. */
|
|
97
|
+
replaceSession() {
|
|
98
|
+
this.#editor = {
|
|
99
|
+
text: "",
|
|
100
|
+
queuedSubmissions: [],
|
|
101
|
+
selection: null,
|
|
102
|
+
cursorOffset: 0,
|
|
103
|
+
historyRevision: this.#editor.historyRevision + 1,
|
|
104
|
+
submitEnabled: true,
|
|
105
|
+
};
|
|
106
|
+
this.#status = { ...this.#status, workingMessage: null, workingProgress: null, badges: [] };
|
|
107
|
+
}
|
|
108
|
+
/** Delivery overload ended the run: ready when it can resume, stopped once disposed, otherwise failed. */
|
|
109
|
+
settleOverload(canResume, disposed) {
|
|
110
|
+
this.#status = { ...this.#status, workingMessage: null, workingProgress: null };
|
|
111
|
+
this.lifecycle = disposed ? "stopped" : canResume ? "ready" : "failed";
|
|
112
|
+
this.#editor = { ...this.#editor, submitEnabled: canResume && !disposed };
|
|
113
|
+
this.#revision += 1;
|
|
114
|
+
}
|
|
115
|
+
recordDiagnostic(severity, code, message, recoverable) {
|
|
116
|
+
const diagnostic = { sequence: this.#diagnostics.length, code, severity, message, recoverable };
|
|
117
|
+
this.#diagnostics.push(diagnostic);
|
|
118
|
+
if (this.#diagnostics.length > MAX_DIAGNOSTICS)
|
|
119
|
+
this.#diagnostics.shift();
|
|
120
|
+
this.#status = {
|
|
121
|
+
...this.#status,
|
|
122
|
+
diagnostics: [...this.#status.diagnostics, message].slice(-MAX_STATUS_DIAGNOSTICS),
|
|
123
|
+
};
|
|
124
|
+
return diagnostic;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"platform": "darwin",
|
|
8
8
|
"architecture": "arm64",
|
|
9
9
|
"capability": "supported",
|
|
10
|
-
"builtAt": "2026-10-
|
|
10
|
+
"builtAt": "2026-10-20T08:53:57.218Z",
|
|
11
11
|
"artifact": {
|
|
12
12
|
"filename": "process-guardian.node",
|
|
13
13
|
"sha256": "a87eaaffcd986b624c8b044b756446cc1bdaf4334a1775b97820542fe34f9fb7",
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"architecture": "arm64",
|
|
10
10
|
"target": "aarch64-apple-darwin",
|
|
11
11
|
"capability": "supported",
|
|
12
|
-
"builtAt": "2026-10-
|
|
12
|
+
"builtAt": "2026-10-20T08:57:24.837Z",
|
|
13
13
|
"artifact": {
|
|
14
14
|
"filename": "terminal-host.node",
|
|
15
|
-
"sha256": "
|
|
16
|
-
"size":
|
|
15
|
+
"sha256": "1885cc705611eb1b5a06148d77cb3e85dc99321124b7fec4be382630292555c6",
|
|
16
|
+
"size": 5747616
|
|
17
17
|
},
|
|
18
18
|
"entry": {
|
|
19
19
|
"filename": "dist/foundation/resident-tabs/host-entry.js",
|
|
Binary file
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"platform": "darwin",
|
|
8
8
|
"architecture": "x64",
|
|
9
9
|
"capability": "supported",
|
|
10
|
-
"builtAt": "2026-10-
|
|
10
|
+
"builtAt": "2026-10-20T08:54:05.958Z",
|
|
11
11
|
"artifact": {
|
|
12
12
|
"filename": "process-guardian.node",
|
|
13
13
|
"sha256": "783a65f0de287a1c02cd5429dbe97bc8633f4cc8a7b92e3e961180b9bdcff814",
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"architecture": "x64",
|
|
10
10
|
"target": "x86_64-apple-darwin",
|
|
11
11
|
"capability": "supported",
|
|
12
|
-
"builtAt": "2026-10-
|
|
12
|
+
"builtAt": "2026-10-20T09:00:09.786Z",
|
|
13
13
|
"artifact": {
|
|
14
14
|
"filename": "terminal-host.node",
|
|
15
|
-
"sha256": "
|
|
16
|
-
"size":
|
|
15
|
+
"sha256": "a34271e9cc5ec28a638dd4bcaff97a7fb33ac0856f4cd5b18814400d540e8da7",
|
|
16
|
+
"size": 6263000
|
|
17
17
|
},
|
|
18
18
|
"entry": {
|
|
19
19
|
"filename": "dist/foundation/resident-tabs/host-entry.js",
|
|
Binary file
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"platform": "linux",
|
|
8
8
|
"architecture": "x64",
|
|
9
9
|
"capability": "supported",
|
|
10
|
-
"builtAt": "2026-10-
|
|
10
|
+
"builtAt": "2026-10-20T08:53:35.318Z",
|
|
11
11
|
"artifact": {
|
|
12
12
|
"filename": "process-guardian.node",
|
|
13
13
|
"sha256": "118e5954e3864672bd8482ab7edf419cb27dcdf2533720316979b7f5cc5a53a5",
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"architecture": "x64",
|
|
10
10
|
"target": "x86_64-unknown-linux-gnu",
|
|
11
11
|
"capability": "supported",
|
|
12
|
-
"builtAt": "2026-10-
|
|
12
|
+
"builtAt": "2026-10-20T08:55:32.085Z",
|
|
13
13
|
"artifact": {
|
|
14
14
|
"filename": "terminal-host.node",
|
|
15
|
-
"sha256": "
|
|
16
|
-
"size":
|
|
15
|
+
"sha256": "5c7b589cd36b901a4a02d172f84d70111f7f4cec5ad796a6ea13524109fa4b50",
|
|
16
|
+
"size": 6988960
|
|
17
17
|
},
|
|
18
18
|
"entry": {
|
|
19
19
|
"filename": "dist/foundation/resident-tabs/host-entry.js",
|
|
Binary file
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
"platform": "win32",
|
|
8
8
|
"architecture": "x64",
|
|
9
9
|
"capability": "supported",
|
|
10
|
-
"builtAt": "2026-10-
|
|
10
|
+
"builtAt": "2026-10-20T08:54:21.248Z",
|
|
11
11
|
"artifact": {
|
|
12
12
|
"filename": "process-guardian.node",
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "aa4a36d3cb4e7f4cb9157409bbe0215ac467140f5558ca154aaf736f6105ce9b",
|
|
14
14
|
"size": 402432
|
|
15
15
|
},
|
|
16
16
|
"provenance": {
|
|
Binary file
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"architecture": "x64",
|
|
10
10
|
"target": "x86_64-pc-windows-msvc",
|
|
11
11
|
"capability": "supported",
|
|
12
|
-
"builtAt": "2026-10-
|
|
12
|
+
"builtAt": "2026-10-20T08:58:10.619Z",
|
|
13
13
|
"artifact": {
|
|
14
14
|
"filename": "terminal-host.node",
|
|
15
|
-
"sha256": "
|
|
16
|
-
"size":
|
|
15
|
+
"sha256": "31360962d3614f342891adae1a8f79e65c3b1a323baab396f4a9a0f32afd3d99",
|
|
16
|
+
"size": 6072320
|
|
17
17
|
},
|
|
18
18
|
"entry": {
|
|
19
19
|
"filename": "dist/foundation/resident-tabs/host-entry.js",
|
|
Binary file
|
|
@@ -44,6 +44,20 @@ The JavaScript product path has no PTY or terminal-emulator dependency. Reintrod
|
|
|
44
44
|
|
|
45
45
|
`scripts/governance/check-architecture.mjs` enforces the structural parts of these boundaries. Cross-cutting rationale belongs here; implementation history belongs in Git and archived OpenSpec changes.
|
|
46
46
|
|
|
47
|
+
## Session shell composition
|
|
48
|
+
|
|
49
|
+
The session presenter (`src/app/session-shell/session-shell.ts`) coordinates; it does not own per-concern state. Each concern implements `SessionConcern` (`session-concern.ts`): it resets its own state in `onSessionReplaced` and releases it in `dispose`, and the presenter lists its concerns once, in reset order, in `#createConcerns`. New submission, routing, dialog, login, or resident-tab behavior goes into the concern that owns it, or a new concern added to that list. The render root (`session-shell-root.ts`) likewise picks one `ShellLayout` at construction and delegates transcript caching to `TranscriptDocument`, dock chrome to `DockChrome`, workflow output to `WorkflowOutput`, extension surfaces to `ExtensionSurface`, and the dock's input surface to `InputSurfaceSwitch`. The engine adapter serves the shell through the ports built in `src/integrations/pi/engine/ports.ts`, and the composition root delegates runtime claims, the startup release note, and the resident-tab bridge to their own modules under `src/composition/`.
|
|
50
|
+
|
|
51
|
+
## Unit size ratchet
|
|
52
|
+
|
|
53
|
+
Module boundaries do not stop one class or function inside a module from absorbing every feature. The architecture gate therefore measures every production TypeScript class under `src/` (methods and fields) and every TypeScript or Rust function under `src/` and `native/<crate>/src/` (inclusive lines), excluding vendored sources and Rust `#[cfg(test)]` code. A unit with more than 20 methods, 15 fields, or 100 function lines must be recorded in `config/baselines/size-ratchet.json`, and its recorded counts can only shrink:
|
|
54
|
+
|
|
55
|
+
- A recorded count that grows fails the gate. Split the unit instead. If growth is truly unavoidable, raise the entry by hand in the same pull request and say why in its description, so the growth is reviewed rather than silent.
|
|
56
|
+
- An unrecorded unit over a threshold fails the gate; split it before it lands.
|
|
57
|
+
- A count that shrinks, or a unit that falls below every threshold, fails until `node scripts/governance/update-size-ratchet.mjs` lowers or removes the entry. That script never adds or raises an entry; `--check` reports without writing.
|
|
58
|
+
|
|
59
|
+
The measurement in `scripts/governance/size-ratchet-policy.mjs` masks strings, comments, and regular expressions and counts braces, so it needs no build; a file whose braces do not balance is reported rather than mismeasured. Keys are `<path>::<Class>`, `<path>::<Class>.<method>`, or `<path>::<Type>::<fn>`, so moving or renaming a unit means moving its entry.
|
|
60
|
+
|
|
47
61
|
## Multi-session limits of the pinned engine
|
|
48
62
|
|
|
49
63
|
The engine host removes A1's own obstacles to several Pi sessions in one process, but three remain in the pinned Pi SDK and must be planned around, not discovered:
|
|
@@ -64,7 +64,7 @@ The read-only review used A1 `develop` at `b42b3c9e`, PR #586 at `8fdb7361`, `E:
|
|
|
64
64
|
|
|
65
65
|
### Milestone 3 failure-isolation contract (2026-10-14)
|
|
66
66
|
|
|
67
|
-
- The native `core/server.rs
|
|
67
|
+
- The native `core/server.rs`, `core/holder.rs`, and `core/attach/` state machines are deterministic and I/O-free; the production holder uses the holder core for bounded input admission and ordered writer effects, the production server uses the server core for heartbeat supervision, and the attach client steps the attach core for every interaction mode (popup, rename, drag), shortcut, deferred input, and server answer while `roles/attach.rs` keeps the connection, outer terminal, encoders, and presentation. Blocking identity/termination work runs through the bounded worker shell with deadlines and stale-incarnation filtering.
|
|
68
68
|
- Every attach, server, and holder role has an independent 15-second watchdog. Watchdog records are bounded, timestamped, owner-only metadata under the profile runtime directory; fault-only role freezes are refused by non-test release builds.
|
|
69
69
|
- Holders read PTY output continuously, use one bounded input authority, reserve complete pastes before delivery, and emit a typed uncertain-delivery result after an admitted write failure. Each holder independently refreshes the server's latest complete retained surface even while hidden. Server-to-client control lanes are frame/byte bounded, render lanes retain one replaceable full current surface, and only the viewed tab is delivered to each attach client.
|
|
70
70
|
- Server replacement is serialized by `server-start.lock` in the profile runtime directory. Recovery verifies the recorded native owner before termination, waits boundedly for the registry-writer lease, increments the durable epoch, derives holder credentials, and re-admits only exact holder/child identities and incarnations. A durable three-start/sixty-second budget stops automatic replacement; explicit `--restart` clears it.
|
|
@@ -395,12 +395,12 @@ Every queue, buffer, file, and collection the resident roles keep has a declared
|
|
|
395
395
|
| Structured complete surface | 512 × 256 geometry, 65,536 cells, 4,096 styles, 4,096 links, and 8 MiB decoded state | `protocol.rs` `StructuredSurface::validate` | `structured_surfaces_fail_closed_on_dimensions_indexes_and_control_injection` |
|
|
396
396
|
| Input per message and holder input queue | 1 MiB | `protocol.rs`, `core/holder.rs`, `roles/holder.rs` | `over_capacity_paste_delivers_no_prefix` |
|
|
397
397
|
| Holder PTY ingress and work round | 64 × 16 KiB lossless chunks; at most 256 KiB or 2 ms before yielding | `roles/holder.rs` | `verify-resident-parallel-progress.mjs` concurrent and stopped-stage cases |
|
|
398
|
-
| Per-client control lane | 1,024 frames and 4 MiB | `roles/server.rs` | `control_lane_is_bounded_by_frames_and_bytes` |
|
|
399
|
-
| Per-client render slot | one pending full surface; a newer one replaces it | `roles/server.rs` `Peer::offer_surface` | `stalled_client_gets_one_full_current_surface` |
|
|
400
|
-
| Concurrent endpoint connections | 128 | `roles/server.rs` `admit_connection` | `connections_beyond_the_cap_are_refused_and_released_slots_are_reused` |
|
|
401
|
-
| Attached viewing clients | 16 | `roles/server.rs` `client_slot_available` | `attached_clients_are_capped` |
|
|
398
|
+
| Per-client control lane | 1,024 frames and 4 MiB | `roles/server/mod.rs` | `control_lane_is_bounded_by_frames_and_bytes` |
|
|
399
|
+
| Per-client render slot | one pending full surface; a newer one replaces it | `roles/server/mod.rs` `Peer::offer_surface` | `stalled_client_gets_one_full_current_surface` |
|
|
400
|
+
| Concurrent endpoint connections | 128 | `roles/server/mod.rs` `admit_connection` | `connections_beyond_the_cap_are_refused_and_released_slots_are_reused` |
|
|
401
|
+
| Attached viewing clients | 16 | `roles/server/mod.rs` `client_slot_available` | `attached_clients_are_capped` |
|
|
402
402
|
| Concurrent holder starts | default 2, hard 8 | `core/server.rs` | `limits_starts_and_drops_stale_completion` |
|
|
403
|
-
| Stopped tab records | 16, oldest dropped first | `roles/server.rs` `prune_stopped_records` | `stopped_records_are_bounded_oldest_first_and_live_tabs_are_kept` |
|
|
403
|
+
| Stopped tab records | 16, oldest dropped first | `roles/server/mod.rs` `prune_stopped_records` | `stopped_records_are_bounded_oldest_first_and_live_tabs_are_kept` |
|
|
404
404
|
| Prior tab-set archives | bounded history | `roles/registry.rs` | `prior_tab_sets_are_bounded` |
|
|
405
405
|
| Abandoned blocking workers | 8 | `shell/workers.rs` | `fails_fast_after_eight_abandoned_workers` |
|
|
406
406
|
| Diagnostic logs | 5 MiB × 3 generations per role | `roles/diagnostics.rs` | `logs_rotate_at_their_cap_and_keep_a_bounded_number_of_generations` |
|
package/package.json
CHANGED