-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathplugin-api.json
More file actions
216 lines (183 loc) · 17.3 KB
/
Copy pathplugin-api.json
File metadata and controls
216 lines (183 loc) · 17.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
{
"$comment": "Single source of truth for the project-plugin ABI. Edit here, then run: node packages/benchday-protocol/generate-plugin-api.mjs",
"api_version": 3,
"manifest_schema": "benchday.plugin/1",
"report_envelope_schema": "benchday.plugin.report/1",
"report_map_schema": "benchday.plugin.report-map/1",
"contribution_schema": "benchday.plugin.contribution/1",
"action_schema": "benchday.plugin.action/1",
"probe_schema": "benchday.plugin.probe/1",
"collection_schema": "benchday.plugin.collection/1",
"slots": [
{ "id": "project.summary", "version": 1, "kinds": ["decoration"] },
{ "id": "work.row.badges", "version": 1, "kinds": ["decoration"] },
{ "id": "work.row.actions", "version": 1, "kinds": ["action"] },
{ "id": "work.detail.actions", "version": 1, "kinds": ["action", "decoration"] },
{ "id": "terminal.header.badges", "version": 1, "kinds": ["decoration"] },
{ "id": "terminal.context.actions", "version": 1, "kinds": ["action"] },
{ "id": "terminal.command.palette", "version": 1, "kinds": ["action"] },
{ "id": "terminal.header.indicators", "version": 1, "kinds": ["indicator"] },
{ "id": "terminal.standing.note", "version": 1, "kinds": ["note"] },
{ "id": "account.dashboard", "version": 1, "kinds": ["account-entry"] }
],
"$comment_indicator": "An `indicator` is a host-drawn word of at most `indicator_label_max_chars` characters sitting in the context rail -- GOAL, GIT -- drawn hollow when it has nothing to report and filled when it does, and opening a host-drawn sheet carrying its detail text and the package's own actions. It exists because the note it replaces was always at its largest: a full row above the terminal plus a second rail row for its action, for a feature whose job is to stay out of the way until asked. What makes it an ABI change rather than a client rendering is that an indicator is PRESENT ON THE SURFACE WHILE ABSENT IN CONTENT -- every other contribution resolves or does not, so `when: {fact_current: ...}` cannot express a hollow indicator. The declaration therefore splits AVAILABILITY (`when`: should this exist for this pane) from PRESENCE (`present`: does it currently have something to say). THIS is why api_version goes to 3: the slot carries a new KIND, so a consumer that merely knew the slot id would still not know how to draw it. Its downgrade CASCADES -- indicator -> note -> decoration (compat.ts) -- so a v2 client keeps the band it has today and a v1 client keeps its chip. ONE declared word serves both the rail and the sheet title: an earlier draft had a separate one- or two-character glyph, which forced `Goal` and `Git` onto the same letter and made the rail a puzzle to decode.",
"$comment_standing_note": "A `note` is a host-drawn band of at most three lines that stands above the terminal body for as long as the thing it reports is true — the first slot whose primitive is a SENTENCE rather than a label, because a decoration caps at 24 chars (64 for its one elastic run) and the goals people actually write run to 449. It passes the Primitive Admission Test (docs/extensions-architecture.md §7) on two independent uses that arrived from different directions: benchday.goal renders the conversation-scoped goal fact, and benchday.git renders `rebase/merge in progress`, which is a filesystem probe and carries no conversation. Its deterministic downgrade is a `terminal.header.badges` decoration holding the ellipsised first line (compat.ts::downgradeNote), which is why THIS is the change that takes api_version to 2: it is the first one that alters what a CLIENT must understand.",
"surfaces": [
{ "id": "files", "version": 1, "views": ["browser", "recent", "git"] },
{ "id": "extension.popup", "version": 1, "views": ["status"] },
{ "id": "extension.web", "version": 1, "views": ["page"] }
],
"$comment_extraction_is_additive": "The extraction lane does NOT bump api_version, deliberately. Every field it adds is an optional manifest key, so a package written before it parses unchanged; and because v1 admits FIRST-PARTY extractor packages only, no extractor package ever travels to a hub that does not ship it. Bumping would have forced all four bundled packages (one of them a submodule with its own repository) to raise `api.max` in lockstep or go dark, since compatibility is an overlap and not a floor (compat.ts::pluginApiCompatible). The bump belongs with the first change that alters what a CLIENT must understand — the standing-note slot.",
"$comment_extractors": "The conversation-extraction lane (openspec change a-plugin-can-publish-a-fact). An extractor is INLINE in the manifest, not a package file, so the declared surface a package can smuggle behaviour through does not grow. `match_forms` is deliberately CLOSED and linear-time: a package-supplied regular expression is both a ReDoS on the hub's event loop and a general-purpose extraction language over the user's private text. It is a complexity and DoS choice, NOT a confidentiality boundary — the confidentiality control is `conversation.read.user_messages` plus where an extracted value is allowed to travel.",
"extractor_streams": ["conversation.user_messages"],
"match_forms": ["equals", "prefix", "delimited"],
"normalize_forms": ["whitespace", "strip_guard"],
"$comment_facts": "A fact is the fourth artifact beside probe/report/contribution: a conversation-scoped, contract-typed value carrying the message that produced it. `fact_states` is deliberately two-valued and `fact_end_causes` names WHY a fact ended — storage expiry is NOT among them, because a row removed by a retention bound must never read as the work having finished.",
"$comment_no_boundary_cause": "There is deliberately NO boundary end cause. The substrate's `segmentBoundary` is ONE BIT that fires on a structural root, a /compact summary, /clear AND /new alike, so ending a fact on it would delete the goal in the middle of the long task the goal exists for. A package instead names the COMMANDS that end its fact (`clear_commands`), which the host resolves dialect-aware; /compact is simply not among them. A genuinely new conversation needs no cause at all, because a fact is scoped to one conversation and does not travel.",
"fact_states": ["current", "ended"],
"fact_end_causes": ["replaced", "cleared"],
"$comment_fact_contracts": "The CLOSED registry of contract ids. A package SELECTS a contract in `provides`; it never defines one, and the host owns each contract's value schema. Contract ids carry their major version, so a breaking change is a new id rather than a range nobody can test. `benchday.goal/2` adds the goal's STATUS, which /1 cannot express: /1 says only that a goal exists, so a completed goal and an ongoing one are the same value. It is a new id rather than an added field precisely because the registry promises a fixed shape per id — a consumer binding /1 must keep getting /1. Both stay registered and a package may provide both.",
"fact_contracts": ["benchday.goal/1", "benchday.goal/2"],
"$comment_indicator_statuses": "The CLOSED lifecycle vocabulary an indicator may resolve to. Three words, not an agent's own six: the four states that are neither running nor finished (paused, blocked, out of tokens, out of budget) differ in cause and agree in consequence — a person is needed — and a rail glyph has ONE attention rendering. The cause is not discarded, it travels in the indicator's detail, where there is room to say it in words. The host owns each word's glyph, tone and motion; a package selects a status expression and supplies no appearance, exactly as it selects a slot without describing one.",
"indicator_statuses": ["active", "waiting", "achieved"],
"$comment_fact_evidence": "Who says a fact's status is what it says. `reported` means an agent stated it; `asserted` means nothing has reported one and the host is reading absence of a contrary report as continuation. Two values rather than one boolean because the pair is rendered and reasoned about, and because AGENTS.md rule 13 requires a weaker claim to NAME itself rather than pass as an observation: `achieved` is only ever `reported`, and there is no such thing as finished-probably.",
"fact_evidence": ["reported", "asserted"],
"$comment_probe_kinds": "`json_dir` reads every JSON document in a declared project directory, newest-modified first, bounded by document count and bytes per document. It exists because a package could otherwise only read a FIXED FILENAME, which forces any producer-written fact into a single-writer file; the alternatives were a shell glob inside a probe or a dependency on a CLI being installed. It takes `project.read.files:<dir>` — the same capability as `checklist_md`, which is the directory-scanning, contents-reading probe it is modelled on.",
"probe_kinds": ["path_exists", "path_capture", "file_meta", "exec_version", "exec_json", "exec_text", "checklist_md", "exec_records", "json_dir"],
"$comment_expr_forms": "The report-map expression grammar (`ProbeExpr`). Hand-mirrored in Rust (plugins.rs) and TypeScript (schemas.ts) because an untagged union cannot be generated — so both are checked against THIS list instead of against each other. `object` is the catch-all and MUST stay last in every parser: a form ordered after it is silently read as a nested payload object rather than failing.",
"expr_forms": ["const", "exists", "count", "list", "line", "count_lines", "find", "count_where", "get", "object"],
"$comment_declaration_features": "Optional declaration features a report map may name in `features`. A daemon advertises the ones it implements as `plugin-decl:<feature>`; the hub refuses to send a map naming one a daemon lacks, because an untagged expression a build does not implement parses as something ELSE rather than failing.",
"declaration_features": ["records/1", "count_where/1", "json_dir/1"],
"probe_context_keys": ["pane.cwd", "pane.id", "project.root"],
"effect_classes": ["read", "validate", "write-project", "external"],
"effects_requiring_confirmation": ["write-project", "external"],
"$comment_item_verbs": "The CLOSED catalog of verbs an activated item may offer. A package SELECTS from these; it never describes one. Core owns each verb's label, icon, ordering, permission, confirmation, and behaviour — which is what stops a package's markup from becoming a UI or a command channel. `plugin.invoke` is the extension point: it runs a PluginAction the package already declared, through the ordinary action path, so nothing new is trusted. There is deliberately no send-to-pane verb: `compose.insert` composes and the user submits.",
"item_verbs": ["compose.insert", "clipboard.copy", "context.select", "plugin.invoke"],
"$comment_draft_kinds": "The CLOSED catalog of texts a package may ask the HOST to write. A package SELECTS a kind; it never supplies a prompt, a model, a context selector, or a temperature — exactly as `item_verbs` lets it select a verb without describing one. That is not tidiness: a package-supplied prompt is code with worse tooling, and it is the injection surface, because the prompt would be reading the user's own conversation. What the package learns is that it asked for a \"goal\"; the prompt, the inputs, the model chain, the bound on the output and where the text lands are all core's. And the text lands in the COMPOSER — there is no send-to-pane verb here for the same reason there is none in `item_verbs`.",
"draft_kinds": ["goal"],
"permission_classes": [
{ "id": "project.read.metadata", "parameterized": false },
{ "id": "project.read.files", "parameterized": true },
{ "id": "process.exec", "parameterized": true },
{ "id": "work.read.associations", "parameterized": false },
{ "id": "work.read.labels", "parameterized": false },
{ "$comment": "Reading what the person typed to their agent is NOT a parameter of project.read.* — a repository read and a conversation read are different grants and must not share a checkbox. Required to PRODUCE a conversation-derived fact and equally to CONSUME one, or a package without the grant receives the user's words through one that has it.", "id": "conversation.read.user_messages", "parameterized": false },
{ "$comment": "Narrow host-resolved aggregate snapshot. It never exposes receipts or an account selector and is accepted only for bundled first-party account-dashboard packages.", "id": "account.metrics.snapshot", "parameterized": false }
],
"publisher_trust_tiers": ["first-party", "verified", "community"],
"$comment_platforms": "Platforms a manifest may name in `platforms`. DECLARED reach, never discovered: a package says where it can render, and the catalog uses that to explain why an entry is listed but not usable on the device in the user's hand. There is deliberately no `desktop` — a grouping word hides which of three operating systems was actually meant, and the catalog has to say which one.",
"platforms": ["android", "ios", "macos", "linux", "windows", "web"],
"freshness": ["fresh", "stale", "unavailable"],
"completeness": ["complete", "partial"],
"evidence_classes": ["path", "exec", "cwd", "session", "user"],
"diagnostic_severities": ["info", "warning", "error"],
"tones": ["neutral", "info", "success", "warning", "danger"],
"$comment_progress_displays": "How a `progress {current,total}` decoration renders its NUMBER. `count` (the default, and the pre-existing behaviour) prints `current/total` — right when both are real quantities the reader recognises, like 9 of 12 tasks. `percent` prints one rounded percentage and hides the denominator — right when `total` is a scale rather than a quantity: `900/1000` told a reader nothing except that someone picked 1000. The package declares which its numbers ARE; core owns how each is drawn.",
"progress_displays": ["count", "percent"],
"fits": ["fixed", "marquee", "ellipsis"],
"icon_keys": [
"spec", "check", "warning", "error", "info", "list", "play", "refresh",
"doc", "folder", "tag", "clock", "question"
],
"limits": {
"package_max_files": 256,
"package_max_file_bytes": 262144,
"manifest_max_bytes": 65536,
"max_probes": 16,
"max_actions": 24,
"max_contributions": 32,
"max_collections": 16,
"probe_default_timeout_ms": 5000,
"probe_max_timeout_ms": 30000,
"probe_default_max_output_bytes": 65536,
"probe_max_output_bytes": 262144,
"probe_default_max_records": 64,
"probe_max_records": 256,
"probe_max_record_bytes": 4096,
"count_where_max_literals": 8,
"count_where_max_literal_chars": 16,
"max_concurrent_probes": 4,
"action_default_timeout_ms": 20000,
"action_max_timeout_ms": 120000,
"action_default_max_output_bytes": 65536,
"action_max_output_bytes": 262144,
"max_concurrent_actions": 2,
"action_max_args": 16,
"report_max_bytes": 131072,
"report_max_evidence": 32,
"report_max_diagnostics": 16,
"report_default_ttl_ms": 60000,
"note_max_chars": 480,
"note_max_lines": 3,
"note_max_source_chars": 24,
"note_max_per_pane": 1,
"indicator_label_max_chars": 6,
"indicator_max_per_pane": 4,
"decoration_max_label_chars": 24,
"decoration_max_segments": 4,
"decoration_max_segment_chars": 16,
"decoration_max_elastic_chars": 64,
"destination_max_params": 4,
"destination_max_param_chars": 128,
"popup_max_rows": 24,
"popup_max_title_chars": 48,
"popup_max_row_chars": 64,
"collection_max_rows": 64,
"collection_max_title_chars": 48,
"collection_max_description_chars": 512,
"collection_max_row_bytes": 16384,
"items_max": 64,
"item_actions_max": 4,
"item_text_max_chars": 256,
"web_page_max_bytes": 65536,
"work_max_panes_scanned": 32,
"work_max_candidates": 32,
"work_max_subjects_per_candidate": 3,
"work_max_possible_per_candidate": 1,
"work_max_subjects_total": 64,
"work_max_label_chars": 48,
"work_max_payload_bytes": 16384,
"footer_actions_max": 3,
"decoration_max_tooltip_chars": 120,
"action_max_label_chars": 32,
"max_contributions_per_slot": 3,
"max_contributions_per_plugin_per_slot": 2,
"account_dashboard_max_entries": 4,
"account_snapshot_max_bytes": 65536,
"failure_backoff_threshold": 3,
"failure_backoff_base_ms": 30000,
"failure_backoff_max_ms": 900000,
"cache_max_reports": 64,
"cache_max_bytes": 4194304,
"cache_max_age_ms": 604800000,
"audit_max_records": 512,
"audit_max_age_ms": 604800000,
"index_max_entries": 5000,
"index_max_bytes": 4194304,
"icon_max_bytes": 8192,
"screenshots_max": 5,
"categories_max": 3,
"install_max_packages_per_realm": 50,
"install_max_package_bytes": 4194304,
"$comment_max_requirements": "How many fact contracts one package may declare in `requires`. Small on purpose: a package needing more than a handful of other packages' facts to render is describing an application, not a contribution, and every entry is a provider the realm must hold before the package can activate at all.",
"max_requirements": 8,
"max_extractors": 4,
"extractor_max_marker_chars": 48,
"extractor_max_guard_chars": 32,
"extractor_max_input_bytes": 262144,
"extractor_max_messages_per_pass": 512,
"fact_max_value_bytes": 2048,
"fact_max_per_subject": 8,
"fact_max_diagnostics": 8
},
"reserved_core_fields": [
"attention",
"presence",
"lifecycle",
"rank",
"group",
"lease",
"navigation",
"input_routing"
]
}