@timurproko/a1 0.1.8-dev.157 → 0.1.8-dev.214

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.
Files changed (171) hide show
  1. package/README.md +32 -0
  2. package/bin/cli.js +2 -2
  3. package/bin/sync-pi-tui-proxy.js +1 -1
  4. package/bin/ui.js +18 -3
  5. package/dist/cli/dispatch.js +3 -3
  6. package/dist/cli/packages.js +1 -1
  7. package/dist/cli/version-stats.js +3 -3
  8. package/dist/composition/owned-ui.d.ts +4 -1
  9. package/dist/composition/owned-ui.js +9 -6
  10. package/dist/composition/settings-route-host.js +6 -4
  11. package/dist/contracts/agent-engine/capability-ports.d.ts +2 -2
  12. package/dist/contracts/agent-engine/domain-validation.js +22 -4
  13. package/dist/contracts/agent-engine/domain.d.ts +17 -0
  14. package/dist/contracts/owned-ui/model.d.ts +8 -0
  15. package/dist/contracts/owned-ui/validation.js +11 -0
  16. package/dist/features/launch/development-launch.js +1 -1
  17. package/dist/features/owned-ui/customization.d.ts +2 -0
  18. package/dist/features/owned-ui/customization.js +2 -0
  19. package/dist/features/owned-ui/diagnostics.d.ts +1 -0
  20. package/dist/features/owned-ui/diagnostics.js +1 -0
  21. package/dist/features/owned-ui/index.d.ts +1 -0
  22. package/dist/features/owned-ui/index.js +1 -0
  23. package/dist/features/owned-ui/project-trust-prompt.d.ts +25 -0
  24. package/dist/features/owned-ui/project-trust-prompt.js +149 -0
  25. package/dist/features/owned-ui/settings-app.js +69 -44
  26. package/dist/features/workspace/reducer.d.ts +1 -0
  27. package/dist/features/workspace/reducer.js +1 -0
  28. package/dist/features/workspace/router.d.ts +1 -0
  29. package/dist/features/workspace/router.js +1 -0
  30. package/dist/features/workspace/store.d.ts +1 -0
  31. package/dist/features/workspace/store.js +1 -0
  32. package/dist/foundation/launch-guardian/main.js +1 -1
  33. package/dist/foundation/native-host-protocol/codec.d.ts +2 -0
  34. package/dist/foundation/native-host-protocol/codec.js +2 -0
  35. package/dist/foundation/process-containment/linux-process-inspector.d.ts +1 -0
  36. package/dist/foundation/process-containment/linux-process-inspector.js +1 -0
  37. package/dist/foundation/process-containment/native-guardian-containment.d.ts +1 -0
  38. package/dist/foundation/process-containment/native-guardian-containment.js +1 -0
  39. package/dist/foundation/process-containment/windows-process-inspector.d.ts +1 -0
  40. package/dist/foundation/process-containment/windows-process-inspector.js +1 -0
  41. package/dist/foundation/protocol/client.d.ts +1 -0
  42. package/dist/foundation/protocol/client.js +1 -0
  43. package/dist/foundation/protocol/messages.d.ts +1 -0
  44. package/dist/foundation/protocol/messages.js +1 -0
  45. package/dist/foundation/release/bootstrap.js +6 -6
  46. package/dist/foundation/release/cohort-state.d.ts +1 -0
  47. package/dist/foundation/release/cohort-state.js +1 -0
  48. package/dist/foundation/release/release-gc.js +1 -1
  49. package/dist/foundation/release/release.js +1 -1
  50. package/dist/foundation/release/update-transaction.d.ts +1 -0
  51. package/dist/foundation/release/update-transaction.js +1 -0
  52. package/dist/foundation/release/update.js +12 -12
  53. package/dist/foundation/storage/control-store.d.ts +1 -0
  54. package/dist/foundation/storage/control-store.js +1 -0
  55. package/dist/foundation/structured-agent-runtime/backpressure.d.ts +1 -0
  56. package/dist/foundation/structured-agent-runtime/backpressure.js +1 -0
  57. package/dist/foundation/structured-agent-runtime/commands.d.ts +1 -0
  58. package/dist/foundation/structured-agent-runtime/commands.js +1 -0
  59. package/dist/foundation/structured-agent-runtime/reconnection.d.ts +1 -0
  60. package/dist/foundation/structured-agent-runtime/reconnection.js +1 -0
  61. package/dist/foundation/structured-agent-runtime/state.d.ts +1 -0
  62. package/dist/foundation/structured-agent-runtime/state.js +1 -0
  63. package/dist/foundation/supervision/main.js +1 -1
  64. package/dist/foundation/supervision/server.d.ts +1 -0
  65. package/dist/foundation/supervision/server.js +4 -6
  66. package/dist/integrations/pi/components/conformance.d.ts +1 -0
  67. package/dist/integrations/pi/components/conformance.js +1 -0
  68. package/dist/integrations/pi/components/shell-components.d.ts +1 -0
  69. package/dist/integrations/pi/components/shell-components.js +1 -0
  70. package/dist/integrations/pi/components/shell-editor-autocomplete.js +10 -0
  71. package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
  72. package/dist/integrations/pi/components/shell-footer-status.js +19 -13
  73. package/dist/integrations/pi/components/shell-presenters-info.d.ts +36 -0
  74. package/dist/integrations/pi/components/shell-presenters-info.js +78 -0
  75. package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -36
  76. package/dist/integrations/pi/components/shell-presenters-transcript.js +88 -128
  77. package/dist/integrations/pi/components/shell-shared-facade.d.ts +11 -1
  78. package/dist/integrations/pi/components/shell-shared-facade.js +1 -1
  79. package/dist/integrations/pi/engine/adapter.d.ts +22 -4
  80. package/dist/integrations/pi/engine/adapter.js +220 -70
  81. package/dist/integrations/pi/engine/conformance.d.ts +2 -0
  82. package/dist/integrations/pi/engine/conformance.js +2 -0
  83. package/dist/integrations/pi/engine/http-dispatcher.d.ts +4 -0
  84. package/dist/integrations/pi/engine/http-dispatcher.js +25 -0
  85. package/dist/integrations/pi/engine/index.d.ts +3 -0
  86. package/dist/integrations/pi/engine/index.js +3 -0
  87. package/dist/integrations/pi/engine/model-auth-integration.d.ts +1 -0
  88. package/dist/integrations/pi/engine/model-auth-integration.js +1 -0
  89. package/dist/integrations/pi/engine/package-integration.js +1 -1
  90. package/dist/integrations/pi/engine/project-trust-preflight.d.ts +22 -0
  91. package/dist/integrations/pi/engine/project-trust-preflight.js +50 -0
  92. package/dist/integrations/pi/engine/resource-extension-integration.d.ts +1 -0
  93. package/dist/integrations/pi/engine/resource-extension-integration.js +1 -0
  94. package/dist/integrations/pi/engine/runtime-integration.d.ts +16 -1
  95. package/dist/integrations/pi/engine/runtime-integration.js +34 -4
  96. package/dist/integrations/pi/engine/session-integration.d.ts +1 -0
  97. package/dist/integrations/pi/engine/session-integration.js +2 -1
  98. package/dist/integrations/pi/engine/settings-effects.d.ts +66 -0
  99. package/dist/integrations/pi/engine/settings-effects.js +249 -0
  100. package/dist/integrations/pi/engine/settings-integration.d.ts +14 -26
  101. package/dist/integrations/pi/engine/settings-integration.js +102 -108
  102. package/dist/integrations/pi/engine/workflow-controllers.d.ts +2 -1
  103. package/dist/integrations/pi/engine/workflow-controllers.js +1 -0
  104. package/dist/integrations/pi/session-ui/clipboard-image.d.ts +11 -0
  105. package/dist/integrations/pi/session-ui/clipboard-image.js +29 -0
  106. package/dist/integrations/pi/session-ui/index.d.ts +1 -0
  107. package/dist/integrations/pi/session-ui/index.js +1 -0
  108. package/dist/integrations/pi/session-ui/prompt-chips.js +6 -2
  109. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +39 -4
  110. package/dist/integrations/pi/session-ui/session-shell-root.js +215 -80
  111. package/dist/integrations/pi/session-ui/session-shell.d.ts +10 -1
  112. package/dist/integrations/pi/session-ui/session-shell.js +256 -45
  113. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +5 -0
  114. package/dist/integrations/pi/session-ui/session-viewport-controller.js +76 -11
  115. package/dist/integrations/pi/session-ui/stream-presentation-coalescer.d.ts +29 -0
  116. package/dist/integrations/pi/session-ui/stream-presentation-coalescer.js +79 -0
  117. package/dist/integrations/pi/session-ui/system-clipboard.d.ts +9 -0
  118. package/dist/integrations/pi/session-ui/system-clipboard.js +38 -9
  119. package/dist/integrations/pi/tui-runtime/adapter.d.ts +6 -0
  120. package/dist/integrations/pi/tui-runtime/adapter.js +166 -8
  121. package/dist/integrations/pi/tui-runtime/contracts.d.ts +25 -0
  122. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +63 -0
  123. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +240 -0
  124. package/dist/integrations/pi/tui-runtime/index.d.ts +2 -0
  125. package/dist/integrations/pi/tui-runtime/index.js +2 -0
  126. package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.d.ts +39 -0
  127. package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.js +139 -0
  128. package/dist/native/darwin-arm64/manifest.json +1 -1
  129. package/dist/native/linux-x64/manifest.json +1 -1
  130. package/dist/native/win32-x64/manifest.json +2 -2
  131. package/dist/native/win32-x64/process-guardian.exe +0 -0
  132. package/dist/ui/apps/host.js +5 -9
  133. package/dist/ui/apps/registry.d.ts +1 -0
  134. package/dist/ui/apps/registry.js +1 -0
  135. package/dist/ui/components/dialog-panel.js +5 -3
  136. package/dist/ui/components/frame.d.ts +1 -0
  137. package/dist/ui/components/frame.js +1 -0
  138. package/dist/ui/components/index.d.ts +1 -0
  139. package/dist/ui/components/index.js +1 -0
  140. package/dist/ui/components/line-input.d.ts +1 -6
  141. package/dist/ui/components/line-input.js +4 -9
  142. package/dist/ui/components/list-view.d.ts +0 -6
  143. package/dist/ui/components/list-view.js +14 -7
  144. package/dist/ui/components/progress-status.d.ts +2 -0
  145. package/dist/ui/components/progress-status.js +4 -0
  146. package/dist/ui/components/scrollbar.js +1 -1
  147. package/dist/ui/components/shortcuts.d.ts +1 -0
  148. package/dist/ui/components/shortcuts.js +1 -0
  149. package/dist/ui/components/spans.d.ts +2 -5
  150. package/dist/ui/components/spans.js +28 -8
  151. package/dist/ui/components/surface.d.ts +0 -5
  152. package/dist/ui/components/surface.js +1 -5
  153. package/dist/ui/components/text-selection.d.ts +10 -1
  154. package/dist/ui/components/text-selection.js +96 -29
  155. package/dist/ui/components/text.js +3 -3
  156. package/dist/ui/components/transcript-viewport.d.ts +42 -0
  157. package/dist/ui/components/transcript-viewport.js +287 -39
  158. package/dist/ui/settings/sections.d.ts +6 -19
  159. package/dist/ui/settings/sections.js +17 -15
  160. package/dist/ui/settings/session.d.ts +8 -18
  161. package/dist/ui/settings/session.js +60 -52
  162. package/dist/ui/settings/store.d.ts +1 -0
  163. package/dist/ui/settings/store.js +2 -1
  164. package/docs/architecture/code-documentation.md +77 -0
  165. package/docs/architecture/project-structure.md +15 -1
  166. package/docs/architecture/ui-reference-provenance.md +23 -3
  167. package/docs/ci-release-runbook.md +98 -7
  168. package/docs/manual-owned-ui-checkpoint.md +28 -1
  169. package/docs/manual-terminal-colour-check.md +4 -3
  170. package/docs/repository-governance-live-acceptance.md +77 -0
  171. package/package.json +11 -3
package/README.md CHANGED
@@ -78,6 +78,38 @@ npm run test:fast # typecheck + fast suite (alias: npm test)
78
78
  npm run test:full # complete non-physical suite
79
79
  ```
80
80
 
81
+ Create every task worktree at `{working-dir}/.worktrees/<task-id>`, where
82
+ `{working-dir}` is the session's initial working directory. For working directory
83
+ `D:/Git/a1`, use `D:/Git/a1/.worktrees/<task-id>`—never a sibling such as
84
+ `D:/Git/a1-<task-id>`. The primary worktree remains on `develop` for integration only.
85
+
86
+ ## Pull request integration
87
+
88
+ Pull requests whose complete diff is only under `openspec/**`, under `docs/**`,
89
+ exactly the root `README.md`, or a combination of those paths are automatically
90
+ squash-merged after `Development validation required` succeeds. The automation
91
+ reads the complete GitHub changed-file list, including both sides of renames, and
92
+ runs only for trusted branches in this repository.
93
+
94
+ Any other path makes the pull request code/operational. That includes source, tests,
95
+ scripts, workflows, configuration, generated baselines, arbitrary root Markdown,
96
+ and a mixed documentation-plus-code change. Those pull requests remain open after
97
+ CI for local maintainer validation and explicit manual merge; automation disables
98
+ auto-merge if it was armed. Documentation remains exempt from product builds and
99
+ tests, but docs-sensitive generated governance and strict OpenSpec consistency are
100
+ checked before integration.
101
+
102
+ After any same-repository pull request into `develop` merges, trusted automation
103
+ reconciles its remote topic branch. Human merges use the close-event workflow;
104
+ documentation merges authored by `GITHUB_TOKEN` use a synchronous fallback because
105
+ GitHub suppresses recursive workflow events. Both delete only an unprotected live
106
+ ref that still equals the pull request's exact merged head SHA. Fork, advanced,
107
+ reserved, protected, malformed, and unmerged refs are preserved and reported.
108
+
109
+ Specification approval and implementation remain separate pull requests. An
110
+ implementation starts from updated `origin/develop` only after its specification
111
+ has merged and implementation was explicitly requested.
112
+
81
113
  ## Release
82
114
 
83
115
  Two channels, both published by CI from the exact bytes it validated — never from
package/bin/cli.js CHANGED
@@ -5,7 +5,7 @@ const { fileURLToPath } = await import("node:url");
5
5
  const { readFile } = await import("node:fs/promises");
6
6
  const { cliCapabilities, dispatchCli } = await import("../dist/cli/index.js");
7
7
 
8
- // Which commands this build exposes follows from the build's own version, so a
8
+ // Compatibility: which commands this build exposes follows from the build's own version, so a
9
9
  // released a1 cannot be argued into offering the development profiles.
10
10
  const capabilities = cliCapabilities(JSON.parse(await readFile(new URL("package.json", packageRoot), "utf8")).version);
11
11
 
@@ -16,7 +16,7 @@ process.exitCode = await dispatchCli(process.argv.slice(2), {
16
16
  import("../dist/foundation/release/index.js"),
17
17
  import("./module-identity.js"),
18
18
  ]);
19
- // Self-heal the installed tree before the release store copies it: npm 12
19
+ // Compatibility: self-heal the installed tree before the release store copies it: npm 12
20
20
  // blocks install scripts by default, so the postinstall that normally
21
21
  // repairs module identity may never have run (see bin/module-identity.js).
22
22
  const packageRootPath = fileURLToPath(packageRoot);
@@ -35,7 +35,7 @@ const RE_EXPORT_LINE = /^export \* from "[^"]+";$/m;
35
35
  * pinned Pi resolves. Returns a discriminated outcome; never throws.
36
36
  */
37
37
  export function syncPiTuiProxy(packageRoot) {
38
- // resolvePinnedPiTui answers with a canonical real path, so the directory the
38
+ // Platform: resolvePinnedPiTui answers with a canonical real path, so the directory the
39
39
  // relative specifier is computed from must be canonical too (Windows short
40
40
  // names, symlinked installs).
41
41
  const canonicalRoot = canonical(packageRoot);
package/bin/ui.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const { assertSinglePiTuiModuleAtLaunch } = await import("./module-identity.js");
4
4
  const { fileURLToPath } = await import("node:url");
5
5
 
6
- // Before the composition loads pinned Pi's terminal stack: confirm A1 and Pi
6
+ // Compatibility: before the composition loads pinned Pi's terminal stack: confirm A1 and Pi
7
7
  // resolve it to the same copy, so extensions and the owned UI share one module
8
8
  // identity (see bin/module-identity.js for the full story). Nothing is repaired
9
9
  // here — A1's package manifest decides which copy wins, and this only reports
@@ -12,13 +12,28 @@ assertSinglePiTuiModuleAtLaunch(fileURLToPath(new URL("..", import.meta.url)), m
12
12
 
13
13
  const { runSelectedInteractiveRuntime } = await import("../dist/features/launch/index.js");
14
14
 
15
+ const launchArgs = process.argv.slice(2);
16
+ let sessionPath;
17
+ if (launchArgs.length > 0) {
18
+ if (launchArgs.length !== 2 || launchArgs[0] !== "--session" || launchArgs[1].trim().length === 0) {
19
+ throw new Error("Usage: a1 [--session <session-file>]");
20
+ }
21
+ sessionPath = launchArgs[1];
22
+ }
23
+
15
24
  runSelectedInteractiveRuntime(process.env.A1_LAUNCH_PROFILE ?? "a1", {
16
25
  ownedUi: async (profileId, ownedSurfaces) => {
17
- const [{ runOwnedUi }, { composeOwnedUi }] = await Promise.all([
26
+ const [{ createConsoleProjectTrustPrompt, runOwnedUi }, { composeOwnedUi }] = await Promise.all([
18
27
  import("../dist/features/owned-ui/index.js"),
19
28
  import("../dist/composition/index.js"),
20
29
  ]);
21
- const { application, settings } = await composeOwnedUi({ cwd: process.cwd(), profileId, ownedSurfaces });
30
+ const { application, settings } = await composeOwnedUi({
31
+ cwd: process.cwd(),
32
+ profileId,
33
+ ownedSurfaces,
34
+ projectTrustPrompt: createConsoleProjectTrustPrompt(),
35
+ ...(sessionPath === undefined ? {} : { sessionPath }),
36
+ });
22
37
  return await runOwnedUi({ application, ...(settings === null ? {} : { settings }) });
23
38
  },
24
39
  }).then(
@@ -85,7 +85,7 @@ export function parseCliCommand(arguments_, capabilities) {
85
85
  return parseUpdate(rest);
86
86
  if (command?.startsWith("update:"))
87
87
  return { kind: "noop" };
88
- // Unsupported and reserved command spaces are deliberately quiet. Help is
88
+ // Rationale: unsupported and reserved command spaces are deliberately quiet. Help is
89
89
  // explicit, and a typo must never start an interactive or maintenance path.
90
90
  return { kind: "noop" };
91
91
  }
@@ -98,7 +98,7 @@ function parseUpdate(rest) {
98
98
  if (rest.length === 0)
99
99
  return { kind: "update", channel: "stable" };
100
100
  const [selector, ...values] = rest;
101
- // Removed compatibility notation is unsupported rather than deprecated.
101
+ // Compatibility: removed notation is unsupported rather than deprecated.
102
102
  if (selector === "self")
103
103
  return { kind: "noop" };
104
104
  if (selector === "--develop") {
@@ -121,7 +121,7 @@ function parseUpdate(rest) {
121
121
  return pinnedPiUpdateError();
122
122
  if (selector !== undefined && selector.startsWith("-"))
123
123
  return unknownOption(selector, "update");
124
- // Positional update targets are reserved for a future A1-native plugin model.
124
+ // Rationale: positional update targets are reserved for a future A1-native plugin model.
125
125
  return { kind: "noop" };
126
126
  }
127
127
  function isDevelopmentTarget(target) {
@@ -40,7 +40,7 @@ async function runVerb(port, request) {
40
40
  return request.verb === "install" ? await port.install(request.source) : await port.remove(request.source);
41
41
  }
42
42
  export function renderPackageOutcome(outcome, profileRoot, style = chalk) {
43
- // Model refresh remains an A1 top-level command. The `a1 pi` compatibility
43
+ // Compatibility: model refresh remains an A1 top-level command. The `a1 pi` compatibility
44
44
  // transcript applies to package operations only.
45
45
  if (outcome.operation === "refresh-models") {
46
46
  if (outcome.status === "failed") {
@@ -20,7 +20,7 @@ export async function runVersionStats(options) {
20
20
  output.stderr(`${PRODUCT_TEXT.diagnostic(`could not read its installed version: ${message(error)}`)}\n`);
21
21
  return 1;
22
22
  }
23
- // Match Pi's release behavior: stable builds print only their installed version.
23
+ // Compatibility: match Pi's release behavior: stable builds print only their installed version.
24
24
  // Development builds retain channel visibility for preview comparison and updates.
25
25
  if (prerelease(installed) === null) {
26
26
  output.stdout(`${installed}\n`);
@@ -32,7 +32,7 @@ export async function runVersionStats(options) {
32
32
  output.stderr(`${PRODUCT_TEXT.diagnostic(`could not resolve npm dist-tags: ${remote.error}`)}\n`);
33
33
  return 0;
34
34
  }
35
- // npm view respects the user's .npmrc registry and proxy, so it stays primary; the direct
35
+ // Rationale: npm view respects the user's .npmrc registry and proxy, so it stays primary; the direct
36
36
  // registry fetch below covers npm CLI versions whose --json output shape we cannot parse.
37
37
  async function queryDistTags(runner, fetcher) {
38
38
  const fromNpm = await queryDistTagsViaNpm(runner);
@@ -55,7 +55,7 @@ async function queryDistTagsViaNpm(runner) {
55
55
  return unavailable(`npm exited with status ${result.code ?? "unknown"}`);
56
56
  try {
57
57
  const parsed = JSON.parse(result.stdout);
58
- // npm 12 wraps `npm view <pkg> dist-tags --json` output in a one-element array; older npm returns the bare object.
58
+ // Compatibility: npm 12 wraps `npm view <pkg> dist-tags --json` output in a one-element array; older npm returns the bare object.
59
59
  const metadata = Array.isArray(parsed) && parsed.length === 1 ? parsed[0] : parsed;
60
60
  return parseDistTags(metadata, "npm");
61
61
  }
@@ -1,10 +1,12 @@
1
- import { type PiEngineAdapter } from "../integrations/pi/engine/index.js";
1
+ import { type PiEngineAdapter, type PiProjectTrustPreflightPrompt } from "../integrations/pi/engine/index.js";
2
2
  import { OwnedUiSettingsSession } from "../ui/settings/index.js";
3
3
  import type { OwnedUiApplicationPort, PresentationTerminalPort } from "../contracts/presentation/index.js";
4
4
  export interface OwnedUiCompositionOptions {
5
5
  readonly cwd?: string;
6
6
  readonly terminal?: PresentationTerminalPort;
7
7
  readonly createPiAdapter?: () => Promise<PiEngineAdapter>;
8
+ /** Exact persisted session selected by the narrow `--session` launch form. */
9
+ readonly sessionPath?: string;
8
10
  /**
9
11
  * A1 profile whose settings this session reads and writes. Omitted keeps the
10
12
  * session settings-free, which is what the pinned comparison paths use.
@@ -15,6 +17,7 @@ export interface OwnedUiCompositionOptions {
15
17
  * use the same composition with those surfaces withheld.
16
18
  */
17
19
  readonly ownedSurfaces?: "on" | "off";
20
+ readonly projectTrustPrompt?: PiProjectTrustPreflightPrompt;
18
21
  }
19
22
  export interface OwnedUiComposition {
20
23
  readonly application: OwnedUiApplicationPort;
@@ -1,6 +1,6 @@
1
1
  import { resolveProductPaths } from "../foundation/lifecycle/index.js";
2
2
  import { applyConfiguredPiTheme, getAvailablePiThemes } from "../integrations/pi/components/index.js";
3
- import { createPiEngineAdapter } from "../integrations/pi/engine/index.js";
3
+ import { createPiEngineAdapter, } from "../integrations/pi/engine/index.js";
4
4
  import { OwnedUiSessionShell } from "../integrations/pi/session-ui/index.js";
5
5
  import { OwnedUiSettingsSession, OwnedUiSettingsStore } from "../ui/settings/index.js";
6
6
  import { createPiTerminalBridge } from "../integrations/pi/tui-runtime/index.js";
@@ -12,18 +12,21 @@ export async function composeOwnedUi(options = {}) {
12
12
  const cwd = options.cwd ?? process.cwd();
13
13
  const adapter = options.createPiAdapter
14
14
  ? await options.createPiAdapter()
15
- : await createPiEngineAdapter({ cwd, availableThemes: () => getAvailablePiThemes().map(theme => theme.name) });
15
+ : await createPiEngineAdapter({
16
+ cwd,
17
+ availableThemes: () => getAvailablePiThemes().map(theme => theme.name),
18
+ settingsProductMode: options.ownedSurfaces === "off" ? "comparison" : "bare",
19
+ ...(options.sessionPath === undefined ? {} : { sessionPath: options.sessionPath }),
20
+ ...(options.projectTrustPrompt === undefined ? {} : { projectTrustPrompt: options.projectTrustPrompt }),
21
+ });
16
22
  const ownedSurfaces = options.ownedSurfaces !== "off";
17
23
  const settings = options.profileId === undefined
18
24
  ? null
19
25
  : new OwnedUiSettingsSession({
20
26
  store: new OwnedUiSettingsStore({ configDir: resolveProductPaths().configDir, profileId: options.profileId }),
21
27
  agentProvider: () => adapter.settingsPort(),
22
- ...(ownedSurfaces ? {
23
- hiddenAgentSettingIds: ["tuiMode", "theme", "fullscreenScrollbar", "quietStartup"],
24
- } : {}),
25
28
  });
26
- // Bare A1 intentionally ships one visual target while its UI is being completed:
29
+ // Compatibility: bare A1 intentionally ships one visual target while its UI is being completed:
27
30
  // dark, regardless of terminal detection or a previously stored Pi theme. The
28
31
  // comparison profile keeps Pi's configured theme behavior and settings surface.
29
32
  applyConfiguredPiTheme(ownedSurfaces ? "dark" : adapter.configuredTheme());
@@ -61,13 +61,15 @@ function pinnedTheme() {
61
61
  return {
62
62
  fg: (token, text) => piTheme().fg(token, text),
63
63
  bold: (text) => piTheme().bold(text),
64
- // Unpainted, so a list reads against the terminal the reader actually has
64
+ // Rationale: unpainted, so a list reads against the terminal the reader actually has
65
65
  // rather than against the background the configured theme was built for.
66
66
  plain: (text) => text,
67
- // Quiet, and quieter again: the terminal's own faint attribute over the
67
+ // Rationale: quiet, and quieter again: the terminal's own faint attribute over the
68
68
  // dimmest colour the theme has.
69
69
  disabled: (text) => faint(piTheme().fg("dim", text)),
70
- highlight: (text) => `${text}`,
71
- panel: (text) => `${text}`,
70
+ // Rationale: physical review requires the shared value menu to read as a floating
71
+ // product surface instead of blending into the settings rows behind it.
72
+ highlight: (text) => `\u001b[48;2;82;82;82m\u001b[97m${text}\u001b[39m\u001b[49m`,
73
+ panel: (text) => `\u001b[48;2;55;55;55m${text}\u001b[49m`,
72
74
  };
73
75
  }
@@ -1,4 +1,4 @@
1
- import type { AgentJsonValue, AgentModelDescriptor, AgentResourceDescriptor, AgentSettingDescriptor } from "./domain.js";
1
+ import type { AgentJsonValue, AgentModelDescriptor, AgentSettingChangeOutcome, AgentResourceDescriptor, AgentSettingDescriptor } from "./domain.js";
2
2
  export interface AgentModelPort {
3
3
  readonly capabilities: {
4
4
  readonly selection: boolean;
@@ -26,7 +26,7 @@ export interface AgentSettingsPort {
26
26
  };
27
27
  listSettings(): Promise<readonly AgentSettingDescriptor[]>;
28
28
  readSetting(key: string): Promise<AgentJsonValue | undefined>;
29
- writeSetting?(key: string, value: AgentJsonValue): Promise<void>;
29
+ writeSetting?(key: string, value: AgentJsonValue): Promise<AgentSettingChangeOutcome>;
30
30
  flush?(): Promise<void>;
31
31
  }
32
32
  export interface AgentResourcesPort {
@@ -52,10 +52,28 @@ export function assertAgentUsage(value) { for (const amount of [value.inputToken
52
52
  nonNegative(amount, "usage token count"); if (value.cost !== null && (!(typeof value.cost === "number") || !Number.isFinite(value.cost) || value.cost < 0))
53
53
  throw new TypeError("usage cost is invalid"); }
54
54
  export function assertAgentModelDescriptor(value) { id(value.providerId, "model provider id"); id(value.modelId, "model id"); text(value.displayName, "model display name"); nonNegative(value.contextWindow, "model context window"); unique(value.thinkingLevels, undefined, "model thinking levels"); }
55
- export function assertAgentSettingDescriptor(value) { id(value.key, "setting key"); if (!["boolean", "number", "string", "enum", "json"].includes(value.valueType) || typeof value.writable !== "boolean")
56
- throw new TypeError("setting descriptor is invalid"); if (value.choices)
57
- for (const choice of value.choices)
58
- json(choice, "setting choice"); }
55
+ export function assertAgentSettingDescriptor(value) {
56
+ id(value.key, "setting key");
57
+ if (!["boolean", "number", "string", "enum", "json"].includes(value.valueType)
58
+ || typeof value.writable !== "boolean"
59
+ || !["live", "next-session", "next-start", "current-exit"].includes(value.application)
60
+ || !["agent", "shell", "terminal", "startup", "shutdown", "installation"].includes(value.owner)
61
+ || typeof value.available !== "boolean"
62
+ || !(value.limitationReason === null || typeof value.limitationReason === "string")) {
63
+ throw new TypeError("setting descriptor is invalid");
64
+ }
65
+ json(value.storedValue, "stored setting value");
66
+ json(value.effectiveValue, "effective setting value");
67
+ if ((value.available && value.limitationReason !== null)
68
+ || (!value.available && (value.limitationReason === null || value.limitationReason.length === 0))) {
69
+ throw new TypeError("setting descriptor availability is contradictory");
70
+ }
71
+ if (value.writable && !value.available)
72
+ throw new TypeError("unavailable setting cannot be writable");
73
+ if (value.choices)
74
+ for (const choice of value.choices)
75
+ json(choice, "setting choice");
76
+ }
59
77
  export function assertAgentResourceDescriptor(value) { id(value.id, "resource id"); if (!["command", "prompt", "skill", "extension", "other"].includes(value.kind))
60
78
  throw new TypeError("resource kind is invalid"); text(value.label, "resource label"); json(value.metadata, "resource metadata"); }
61
79
  export function assertAgentThemeDescriptor(value) { id(value.id, "theme id"); text(value.label, "theme label"); if (!value.tokens || typeof value.tokens !== "object" || Object.values(value.tokens).some(token => typeof token !== "string"))
@@ -48,10 +48,27 @@ export interface AgentModelDescriptor {
48
48
  readonly contextWindow: number;
49
49
  readonly thinkingLevels: readonly string[];
50
50
  }
51
+ export type AgentSettingApplicationBoundary = "live" | "next-session" | "next-start" | "current-exit";
52
+ export type AgentSettingOwner = "agent" | "shell" | "terminal" | "startup" | "shutdown" | "installation";
53
+ export interface AgentSettingChangeOutcome {
54
+ readonly status: "applied" | "deferred" | "unavailable" | "failed";
55
+ readonly application: AgentSettingApplicationBoundary;
56
+ readonly storedValue: AgentJsonValue;
57
+ readonly effectiveValue: AgentJsonValue;
58
+ readonly failure: string | null;
59
+ readonly limitationReason: string | null;
60
+ }
51
61
  export interface AgentSettingDescriptor {
52
62
  readonly key: string;
53
63
  readonly valueType: "boolean" | "number" | "string" | "enum" | "json";
64
+ /** A setting is writable only when its declared owner/effect is available. */
54
65
  readonly writable: boolean;
66
+ readonly application: AgentSettingApplicationBoundary;
67
+ readonly owner: AgentSettingOwner;
68
+ readonly available: boolean;
69
+ readonly limitationReason: string | null;
70
+ readonly storedValue: AgentJsonValue;
71
+ readonly effectiveValue: AgentJsonValue;
55
72
  readonly choices?: readonly AgentJsonValue[];
56
73
  /** Label the engine shows for this setting, when it has one. */
57
74
  readonly label?: string;
@@ -91,6 +91,12 @@ export interface OwnedUiOverlay {
91
91
  readonly modal: boolean;
92
92
  readonly payload: unknown;
93
93
  }
94
+ export interface OwnedUiTranscriptImageReference {
95
+ readonly assetId: OwnedUiEntityId;
96
+ readonly mimeType: string;
97
+ readonly byteLength: number;
98
+ readonly source: "user" | "tool-result";
99
+ }
94
100
  export interface OwnedUiTranscriptBlock {
95
101
  readonly id: OwnedUiEntityId;
96
102
  readonly kind: OwnedUiTranscriptBlockKind;
@@ -99,6 +105,8 @@ export interface OwnedUiTranscriptBlock {
99
105
  readonly title: string | null;
100
106
  readonly text: string;
101
107
  readonly payload: unknown;
108
+ /** Bounded opaque references; image bytes remain in the session-scoped engine asset store. */
109
+ readonly imageReferences?: readonly OwnedUiTranscriptImageReference[];
102
110
  }
103
111
  export type OwnedUiSlotId = "theme" | "transcript-block" | "tool-card" | "editor" | "status" | "command" | "selector" | "dialog" | "overlay" | "layout";
104
112
  export interface OwnedUiCustomization {
@@ -14,6 +14,7 @@ const MAX_QUEUE = 32;
14
14
  const MAX_BADGES = 32;
15
15
  const MAX_STATUS_DIAGNOSTICS = 32;
16
16
  const MAX_ACTIVE_COMMANDS = 64;
17
+ const IMAGE_REFERENCE_SOURCES = new Set(["user", "tool-result"]);
17
18
  const BLOCK_KINDS = new Set([
18
19
  "user",
19
20
  "assistant",
@@ -225,6 +226,16 @@ export function assertOwnedUiTranscriptBlock(block) {
225
226
  assertOptionalText(block.title, "owned-UI transcript block title", MAX_LABEL_LENGTH);
226
227
  assertPossiblyEmptyText(block.text, "owned-UI transcript block text", MAX_TEXT_BYTES);
227
228
  assertJsonValue(block.payload, "owned-UI transcript block payload", MAX_PAYLOAD_BYTES);
229
+ if (block.imageReferences !== undefined) {
230
+ assertCollection(block.imageReferences, "owned-UI transcript image references", 16);
231
+ for (const reference of block.imageReferences) {
232
+ assertId(reference.assetId, "owned-UI transcript image asset id");
233
+ if (!/^image\/[a-z0-9.+-]+$/i.test(reference.mimeType))
234
+ throw new TypeError("owned-UI transcript image MIME type is invalid");
235
+ assertIntegerInRange(reference.byteLength, 1, 20 * 1024 * 1024, "owned-UI transcript image byte length");
236
+ assertEnum(reference.source, IMAGE_REFERENCE_SOURCES, "owned-UI transcript image source");
237
+ }
238
+ }
228
239
  }
229
240
  export function assertOwnedUiEditorState(editor) {
230
241
  assertPossiblyEmptyText(editor.text, "owned-UI editor text", MAX_TEXT_BYTES);
@@ -12,7 +12,7 @@ export function resolveDevelopmentLaunchEnvironment(canonicalPackageRoot, releas
12
12
  const selectedDevelopmentRoot = source[PRODUCT_IDENTITY.environment.developmentRoot];
13
13
  const developmentRoot = resolve(selectedDevelopmentRoot
14
14
  ?? join(checkoutRoot, releaseId, "instances", instanceId));
15
- // Runtime/data remain instance-isolated, but product settings are user
15
+ // Rationale: runtime/data remain instance-isolated, but product settings are user
16
16
  // preferences and must survive a fresh local launch (and a rebuilt candidate).
17
17
  // An explicit development root retains its historical self-contained policy.
18
18
  const persistentConfigDir = selectedDevelopmentRoot
@@ -12,6 +12,7 @@ export interface OwnedCommandHandlerContext {
12
12
  readonly correlationId: string;
13
13
  }
14
14
  export type OwnedCommandHandler = (context: OwnedCommandHandlerContext) => OwnedUiCommand | Promise<OwnedUiCommand>;
15
+ /** Registers owned UI commands and assigns a monotonic correlation identifier to each dispatch. */
15
16
  export declare class OwnedCommandSurface {
16
17
  #private;
17
18
  register(name: string, handler: OwnedCommandHandler): () => void;
@@ -22,6 +23,7 @@ export interface ResolvedOwnedUiSlot {
22
23
  readonly customization: OwnedUiCustomization;
23
24
  readonly implementation: OwnedUiSlotImplementation;
24
25
  }
26
+ /** Resolves one customization per slot by precedence and version while preserving registration identity. */
25
27
  export declare class OwnedUiCustomizationRegistry {
26
28
  #private;
27
29
  register(customization: OwnedUiCustomization, implementation: OwnedUiSlotImplementation): () => void;
@@ -1,4 +1,5 @@
1
1
  import { assertOwnedUiCustomization, } from "../../contracts/owned-ui/index.js";
2
+ /** Registers owned UI commands and assigns a monotonic correlation identifier to each dispatch. */
2
3
  export class OwnedCommandSurface {
3
4
  #handlers = new Map();
4
5
  #sequence = 0;
@@ -17,6 +18,7 @@ export class OwnedCommandSurface {
17
18
  return handler({ sessionId, correlationId: `ui-command-${this.#sequence}` });
18
19
  }
19
20
  }
21
+ /** Resolves one customization per slot by precedence and version while preserving registration identity. */
20
22
  export class OwnedUiCustomizationRegistry {
21
23
  #registrations = new Map();
22
24
  register(customization, implementation) {
@@ -17,6 +17,7 @@ export interface OwnedUiDiagnosticsSnapshot {
17
17
  readonly resources: OwnedUiResourceObservation | null;
18
18
  readonly terminalRestorationFailed: boolean;
19
19
  }
20
+ /** Retains bounded, redacted UI diagnostics together with frame and resource observations. */
20
21
  export declare class OwnedUiDiagnosticsRecorder {
21
22
  #private;
22
23
  readonly maximumEntries: number;
@@ -1,3 +1,4 @@
1
+ /** Retains bounded, redacted UI diagnostics together with frame and resource observations. */
1
2
  export class OwnedUiDiagnosticsRecorder {
2
3
  maximumEntries;
3
4
  #entries = [];
@@ -1,4 +1,5 @@
1
1
  export * from "./customization.js";
2
2
  export * from "./diagnostics.js";
3
3
  export * from "./run.js";
4
+ export * from "./project-trust-prompt.js";
4
5
  export * from "./settings-app.js";
@@ -1,4 +1,5 @@
1
1
  export * from "./customization.js";
2
2
  export * from "./diagnostics.js";
3
3
  export * from "./run.js";
4
+ export * from "./project-trust-prompt.js";
4
5
  export * from "./settings-app.js";
@@ -0,0 +1,25 @@
1
+ import type { Readable, Writable } from "node:stream";
2
+ export interface OwnedProjectTrustPromptRequest {
3
+ readonly cwd: string;
4
+ readonly defaultDecision: "ask" | "always" | "never";
5
+ }
6
+ export type OwnedProjectTrustPrompt = (request: OwnedProjectTrustPromptRequest) => Promise<boolean | null>;
7
+ interface RawTtyInput extends Readable {
8
+ readonly isTTY?: boolean;
9
+ readonly isRaw?: boolean;
10
+ setRawMode?(enabled: boolean): this;
11
+ }
12
+ export interface ConsoleProjectTrustPromptOptions {
13
+ readonly input?: RawTtyInput;
14
+ readonly output?: Writable & {
15
+ readonly isTTY?: boolean;
16
+ readonly columns?: number;
17
+ };
18
+ }
19
+ /**
20
+ * Pre-resource startup selector. It uses only fixed product wording and terminal
21
+ * controls: no project setting, theme, extension, prompt, package, or skill is
22
+ * consulted before the trust result exists.
23
+ */
24
+ export declare function createConsoleProjectTrustPrompt(options?: ConsoleProjectTrustPromptOptions): OwnedProjectTrustPrompt;
25
+ export {};
@@ -0,0 +1,149 @@
1
+ const ENTER_ALTERNATE_SCREEN = "\u001b[?1049h";
2
+ const LEAVE_ALTERNATE_SCREEN = "\u001b[?1049l";
3
+ const HIDE_CURSOR = "\u001b[?25l";
4
+ const SHOW_CURSOR = "\u001b[?25h";
5
+ const CLEAR_HOME = "\u001b[2J\u001b[H";
6
+ const ACCENT = "\u001b[38;2;138;190;183m";
7
+ const MUTED = "\u001b[38;2;128;128;128m";
8
+ const DIM = "\u001b[38;2;102;102;102m";
9
+ const RESET_FG = "\u001b[39m";
10
+ const BOLD = "\u001b[1m";
11
+ const RESET_BOLD = "\u001b[22m";
12
+ /**
13
+ * Pre-resource startup selector. It uses only fixed product wording and terminal
14
+ * controls: no project setting, theme, extension, prompt, package, or skill is
15
+ * consulted before the trust result exists.
16
+ */
17
+ export function createConsoleProjectTrustPrompt(options = {}) {
18
+ const input = options.input ?? process.stdin;
19
+ const output = options.output ?? process.stdout;
20
+ return async ({ cwd }) => {
21
+ if (input.isTTY !== true || output.isTTY !== true) {
22
+ throw new Error("an interactive terminal is unavailable");
23
+ }
24
+ const wasRaw = input.isRaw === true;
25
+ let selected = 0;
26
+ let restored = false;
27
+ const restore = () => {
28
+ if (restored)
29
+ return;
30
+ restored = true;
31
+ input.setRawMode?.(wasRaw);
32
+ output.write(`${CLEAR_HOME}${SHOW_CURSOR}${LEAVE_ALTERNATE_SCREEN}`);
33
+ };
34
+ const render = () => {
35
+ const width = Math.max(20, output.columns ?? 80);
36
+ const lines = [
37
+ `${BOLD}${ACCENT}Trust project folder?${RESET_FG}${RESET_BOLD}`,
38
+ cwd,
39
+ "",
40
+ "This allows A1 to load project settings and resources, install missing project packages, and execute project extensions.",
41
+ "",
42
+ optionRow("Trust", selected === 0),
43
+ optionRow("Do not trust", selected === 1),
44
+ "",
45
+ `${DIM} ↑/↓ to navigate · Enter to select · Esc to cancel${RESET_FG}`,
46
+ ];
47
+ output.write(`${CLEAR_HOME}${lines.map(line => clipAnsiSafe(line, width)).join("\n")}`);
48
+ };
49
+ try {
50
+ output.write(`${ENTER_ALTERNATE_SCREEN}${HIDE_CURSOR}`);
51
+ input.setRawMode?.(true);
52
+ render();
53
+ return await new Promise((resolve, reject) => {
54
+ let settled = false;
55
+ const finish = (value) => {
56
+ if (settled)
57
+ return;
58
+ settled = true;
59
+ cleanup();
60
+ restore();
61
+ resolve(value);
62
+ };
63
+ const fail = (error) => {
64
+ if (settled)
65
+ return;
66
+ settled = true;
67
+ cleanup();
68
+ restore();
69
+ reject(error);
70
+ };
71
+ const onData = (chunk) => {
72
+ const data = chunk.toString();
73
+ for (let index = 0; index < data.length;) {
74
+ if (data.startsWith("\u001b[A", index)) {
75
+ selected = 0;
76
+ index += 3;
77
+ render();
78
+ continue;
79
+ }
80
+ if (data.startsWith("\u001b[B", index) || data[index] === "\t") {
81
+ selected = selected === 0 ? 1 : 0;
82
+ index += data[index] === "\t" ? 1 : 3;
83
+ render();
84
+ continue;
85
+ }
86
+ const key = data[index] ?? "";
87
+ index += 1;
88
+ if (key === "\r" || key === "\n") {
89
+ finish(selected === 0);
90
+ return;
91
+ }
92
+ if (key === "\u001b" || key === "\u0003") {
93
+ finish(null);
94
+ return;
95
+ }
96
+ // Compatibility: retain y/n aliases for terminals or automation that cannot send
97
+ // navigation keys; the visible interaction remains selector-first.
98
+ if (key === "y" || key === "Y") {
99
+ finish(true);
100
+ return;
101
+ }
102
+ if (key === "n" || key === "N") {
103
+ finish(false);
104
+ return;
105
+ }
106
+ }
107
+ };
108
+ const onEnd = () => finish(null);
109
+ const onError = (error) => fail(error);
110
+ const cleanup = () => {
111
+ input.off("data", onData);
112
+ input.off("end", onEnd);
113
+ input.off("error", onError);
114
+ };
115
+ input.on("data", onData);
116
+ input.once("end", onEnd);
117
+ input.once("error", onError);
118
+ input.resume();
119
+ });
120
+ }
121
+ finally {
122
+ restore();
123
+ }
124
+ };
125
+ }
126
+ function optionRow(label, selected) {
127
+ return selected
128
+ ? `${ACCENT}→ ${label}${RESET_FG}`
129
+ : ` ${label}`;
130
+ }
131
+ /** Avoid replaying or manufacturing control payloads while keeping fixed SGR. */
132
+ function clipAnsiSafe(line, width) {
133
+ let visible = 0;
134
+ let output = "";
135
+ for (let index = 0; index < line.length && visible < width;) {
136
+ if (line[index] === "\u001b") {
137
+ const match = line.slice(index).match(/^\u001b\[[0-9;:]*m/);
138
+ if (match !== null) {
139
+ output += match[0];
140
+ index += match[0].length;
141
+ continue;
142
+ }
143
+ }
144
+ output += line[index];
145
+ index += 1;
146
+ visible += 1;
147
+ }
148
+ return `${output}${RESET_FG}${RESET_BOLD}`;
149
+ }