@opencode-cockpit/trust 0.0.0-stage → 0.8.0

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 (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +241 -2
  3. package/dist/cli/preview.js +251 -0
  4. package/dist/core/adapt/seen.js +24 -0
  5. package/dist/core/adapt/v1.js +109 -0
  6. package/dist/core/adapt/v2.js +109 -0
  7. package/dist/core/config.js +93 -0
  8. package/dist/core/danger.js +366 -0
  9. package/dist/core/engine.js +245 -0
  10. package/dist/core/family.js +512 -0
  11. package/dist/core/history.js +169 -0
  12. package/dist/core/index.js +16 -0
  13. package/dist/core/keys.js +149 -0
  14. package/dist/core/ledger.js +202 -0
  15. package/dist/core/paths.js +38 -0
  16. package/dist/core/policy.js +128 -0
  17. package/dist/core/rules.js +152 -0
  18. package/dist/core/sample.js +311 -0
  19. package/dist/core/shell.js +253 -0
  20. package/dist/core/signature.js +38 -0
  21. package/dist/core/view/actions.js +321 -0
  22. package/dist/core/view/activity.js +557 -0
  23. package/dist/core/view/explorer.js +970 -0
  24. package/dist/core/view/model.js +193 -0
  25. package/dist/core/view/parts.js +290 -0
  26. package/dist/core/view/rows.js +165 -0
  27. package/dist/core/view/sidebar.js +170 -0
  28. package/dist/tui/index.js +920 -0
  29. package/dist/tui/journal.js +80 -0
  30. package/dist/tui/render.js +85 -0
  31. package/dist/tui/source.js +112 -0
  32. package/dist/tui/view/dialog.js +43 -0
  33. package/dist/tui/view/rows.js +69 -0
  34. package/package.json +61 -4
  35. package/tui.js +6 -0
  36. package/types/cli/preview.d.ts +22 -0
  37. package/types/core/adapt/seen.d.ts +46 -0
  38. package/types/core/adapt/v1.d.ts +18 -0
  39. package/types/core/adapt/v2.d.ts +20 -0
  40. package/types/core/config.d.ts +57 -0
  41. package/types/core/danger.d.ts +50 -0
  42. package/types/core/engine.d.ts +117 -0
  43. package/types/core/family.d.ts +97 -0
  44. package/types/core/history.d.ts +81 -0
  45. package/types/core/index.d.ts +16 -0
  46. package/types/core/keys.d.ts +68 -0
  47. package/types/core/ledger.d.ts +158 -0
  48. package/types/core/paths.d.ts +24 -0
  49. package/types/core/policy.d.ts +52 -0
  50. package/types/core/rules.d.ts +55 -0
  51. package/types/core/sample.d.ts +22 -0
  52. package/types/core/shell.d.ts +40 -0
  53. package/types/core/signature.d.ts +18 -0
  54. package/types/core/view/actions.d.ts +76 -0
  55. package/types/core/view/activity.d.ts +101 -0
  56. package/types/core/view/explorer.d.ts +154 -0
  57. package/types/core/view/model.d.ts +106 -0
  58. package/types/core/view/parts.d.ts +76 -0
  59. package/types/core/view/rows.d.ts +60 -0
  60. package/types/core/view/sidebar.d.ts +62 -0
  61. package/types/tui/index.d.ts +15 -0
  62. package/types/tui/journal.d.ts +19 -0
  63. package/types/tui/render.d.ts +14 -0
  64. package/types/tui/source.d.ts +35 -0
  65. package/types/tui/view/dialog.d.ts +24 -0
  66. package/types/tui/view/rows.d.ts +19 -0
@@ -0,0 +1,170 @@
1
+ /**
2
+ * The sidebar block: what Trust answered for you in this window, and what it is counting now.
3
+ *
4
+ * Trust 4 auto
5
+ * ● git status 7×
6
+ * ● edit src/app.ts 3×
7
+ * ○ docker compose up 2/3
8
+ *
9
+ * A filled dot is an answer Trust gave, with how many times it has answered that in all; a hollow
10
+ * one is a request on screen now and how far it is from being trusted. Answers are visible every
11
+ * time — that is the bargain that makes answering for you acceptable — but the block says nothing
12
+ * when there is nothing to say: no answers yet, nothing counting, not paused. A failure always speaks.
13
+ *
14
+ * Asked for (`shown`: the setting, or the palette's toggle), the heading says where the project
15
+ * stands rather than only this window — `Trust 2 auto · 5 trusted · 1 counting` — since a window
16
+ * just opened has answered nothing yet while the ledger holds plenty. It is never silent then: a
17
+ * project with nothing learned says so, or showing the block looks like a command that did nothing.
18
+ *
19
+ * An answer given through a family you widened says so — `● ls -R docs · any ls 1×` — because
20
+ * that rule was never approved by itself, and the answer should not look as if it had been.
21
+ * Commands are shown the way the ledger shows them (`family.showSubject`): `echo "---"`, never a
22
+ * bare `---` a font can merge into a line.
23
+ *
24
+ * Plain geometric marks, not ⚡: the emoji draws two columns wide in most terminals and one in some,
25
+ * which breaks a grid that has to be exact; Subagents' sidebar uses the same dots.
26
+ */
27
+
28
+ import { HEADING_GAP } from "@opencode-cockpit/client/design";
29
+ import { anyOf, showSubject } from "../family.js";
30
+ import { keyOf, standing } from "../ledger.js";
31
+ import { fit, spread, widthOf } from "./rows.js";
32
+
33
+ /** Commands in the project's ledger: trusted by some agent, or on their way there. */
34
+
35
+ /**
36
+ * One count per command, however many agents it was approved for — as the ledger lists them: trusted
37
+ * when any agent trusts it, counting when none does yet but one has approvals that still count.
38
+ */
39
+ export function tally(state, settings, now) {
40
+ const commands = new Map();
41
+ for (const entry of state.entries.values()) {
42
+ const where = standing(entry, entry.danger, settings, now);
43
+ if (!where.trusted && where.have === 0) continue;
44
+ const key = JSON.stringify([entry.permission, entry.subject]);
45
+ commands.set(key, commands.get(key) === true || where.trusted);
46
+ }
47
+ let trusted = 0;
48
+ for (const is of commands.values()) if (is) trusted++;
49
+ return {
50
+ trusted,
51
+ counting: commands.size - trusted
52
+ };
53
+ }
54
+
55
+ /**
56
+ * The heading's right side when you asked for the block: as much of `2 auto · 5 trusted · 1 counting`
57
+ * as fits beside the name, the least telling part going first.
58
+ */
59
+ function summary(count, project, room) {
60
+ const parts = [...(count > 0 ? [[{
61
+ text: `${count} auto`,
62
+ tone: "success"
63
+ }]] : []), ...(project.trusted > 0 ? [[{
64
+ text: `${project.trusted} trusted`,
65
+ tone: "success"
66
+ }]] : []), ...(project.counting > 0 ? [[{
67
+ text: `${project.counting} counting`,
68
+ tone: "warning"
69
+ }]] : [])];
70
+ if (parts.length === 0) return [{
71
+ text: "nothing learned yet",
72
+ tone: "muted"
73
+ }];
74
+ for (let take = parts.length; take > 1; take--) {
75
+ const row = parts.slice(0, take).flatMap((part, i) => i === 0 ? part : [{
76
+ text: " · ",
77
+ tone: "muted"
78
+ }, ...part]);
79
+ if (widthOf(row.map(run => run.text).join("")) <= room) return row;
80
+ }
81
+ return parts[0] ?? [];
82
+ }
83
+
84
+ /** `edit src/app.ts`, but a command is just the command: bash is the common case. */
85
+ export function labelOf(permission, label) {
86
+ return permission === "bash" ? [{
87
+ text: label,
88
+ tone: "text"
89
+ }] : [{
90
+ text: `${permission} `,
91
+ tone: "tool"
92
+ }, {
93
+ text: label,
94
+ tone: "text"
95
+ }];
96
+ }
97
+
98
+ /** How often Trust answered this in all: the least of its parts, since a line is answered whole. */
99
+ function times(state, answered) {
100
+ const counts = answered.subjects.map(subject => state.entries.get(keyOf(answered.permission, answered.agent, subject))?.autos ?? 0);
101
+ return Math.max(1, counts.length > 0 ? Math.min(...counts) : 1);
102
+ }
103
+ export function sidebarRows(input) {
104
+ const {
105
+ width,
106
+ state
107
+ } = input;
108
+ if (width < 8) return [];
109
+ const rows = [];
110
+
111
+ /** Requests on screen that Trust has started counting; a first sighting at 0 is not news. */
112
+ const counting = input.pending.flatMap(pending => {
113
+ const short = pending.judgement.progress.filter(p => !p.trusted);
114
+ if (short.length === 0 || pending.judgement.answer) return [];
115
+ const worst = short.reduce((a, b) => b.need - b.have > a.need - a.have ? b : a);
116
+ return worst.have > 0 ? [{
117
+ permission: pending.request.permission,
118
+ worst
119
+ }] : [];
120
+ });
121
+ const listed = [];
122
+ for (const answered of input.recent) {
123
+ if (listed.length >= input.limit) break;
124
+ if (listed.some(each => each.label === answered.label && each.permission === answered.permission)) continue;
125
+ listed.push(answered);
126
+ }
127
+ const quiet = input.count === 0 && counting.length === 0 && !state.paused && !input.trouble;
128
+ if (quiet && !input.shown) return [];
129
+ rows.push(spread([{
130
+ text: "Trust",
131
+ tone: "text",
132
+ bold: true
133
+ }], state.paused ? [{
134
+ text: "paused",
135
+ tone: "warning"
136
+ }] : input.shown ? summary(input.count, input.project ?? {
137
+ trusted: 0,
138
+ counting: 0
139
+ }, width - 6) : input.count > 0 ? [{
140
+ text: `${input.count} auto`,
141
+ tone: "success"
142
+ }] : [], width));
143
+ /** The same row of air under the heading every block has (client/design). */
144
+ for (let gap = 0; gap < HEADING_GAP; gap++) rows.push(spread([], [], width));
145
+ for (const answered of listed) rows.push(spread([{
146
+ text: "● ",
147
+ tone: "success"
148
+ }, ...labelOf(answered.permission, answered.subjects.map(subject => showSubject(answered.permission, subject)).join(" && ")), ...(answered.via !== undefined ? [{
149
+ text: ` · ${anyOf(answered.permission, answered.via).replace(/ …$/, "")}`,
150
+ tone: "muted"
151
+ }] : [])], [{
152
+ text: `${times(state, answered)}×`,
153
+ tone: "muted"
154
+ }], width));
155
+ for (const {
156
+ permission,
157
+ worst
158
+ } of counting) rows.push(spread([{
159
+ text: "○ ",
160
+ tone: "warning"
161
+ }, ...labelOf(permission, showSubject(permission, worst.subject))], [{
162
+ text: `${worst.have}/${worst.need}`,
163
+ tone: worst.danger ? "error" : "warning"
164
+ }], width));
165
+ if (input.trouble) rows.push(fit([{
166
+ text: `⚠ ${input.trouble}`,
167
+ tone: "error"
168
+ }], width));
169
+ return rows;
170
+ }