@abelxiaoxing/cadence 2.0.8 → 2.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/docs/service-management.md +3 -0
- package/package.json +1 -1
- package/src/native-build.ts +1 -1
- package/src/service/contracts/models.ts +14 -2
- package/src/service-host.mjs +24 -10
- package/src/task-shell.ts +1 -1
- package/web/dist/assets/cadence.css +1 -1
- package/web/dist/assets/cadence.js +7 -7
- package/web/dist/assets/timeline-client.js +19 -20
- package/web/dist/manifest.json +9 -9
package/README.md
CHANGED
|
@@ -135,6 +135,8 @@ Every mutation needs its own context-bound preview/confirmation; stale/duplicate
|
|
|
135
135
|
The WebUI provides task/run/assignment navigation, long retained messages and tool detail, exact literal search, timeline and complete scoped NDJSON/Markdown downloads.
|
|
136
136
|
Desktop panes and phone drawers retain cancellation and keyboard controls.
|
|
137
137
|
Its chrome follows pi-web-ui: a compact topbar with view tabs and icon actions, a sectioned task sidebar, pill inspector tabs, transcript/tool cards and a bottom statusbar; phones wrap the topbar into an action row and an icon tab row.
|
|
138
|
+
The interface is in English or Simplified Chinese: it follows the browser's first preferred language, and the topbar's globe chip (`中文` / `EN`) switches it, remembered only in that browser.
|
|
139
|
+
Error codes, identifiers, commands and recorded content stay untranslated.
|
|
138
140
|
|
|
139
141
|
## Execution, checks and review
|
|
140
142
|
|
|
@@ -377,6 +379,7 @@ Byte values are UTF-8/encoded bounds where specified; they are not targets to si
|
|
|
377
379
|
| Token source the user edits (`CADENCE_TOKEN` in `.env`) | Read once before binding, never bound for the run: an edit cannot fence Stop |
|
|
378
380
|
| Saved port/token source differs from `.env` | Only when stopped, reselect just those under the reservation; running ones refuse |
|
|
379
381
|
| User-made `.env` (0755/0644, umask 002, dotfiles links) | Like Pi's sources: ownership only, links followed; each refusal names its reason |
|
|
382
|
+
| Credential value inside a provider/model ID (Ollama `ollama`) | Withhold and count only that catalog entry; choosing it refuses; others selectable |
|
|
380
383
|
|
|
381
384
|
Good: current context → explicit grant → durable acceptance → genuine original native/storage settlement → exact retained dependency → real checks → human review.
|
|
382
385
|
Base: a client leaves and another reattaches to the same accepted run without launching it again.
|
|
@@ -415,11 +418,28 @@ Regression owners:
|
|
|
415
418
|
- `workspace-landing`: home-directory redirection to `~/cadence` across management, native attach and observation.
|
|
416
419
|
|
|
417
420
|
Regenerate operator/service/native/browser outputs only through their canonical builders; `--check` variants compare new private output without overwriting canonical bytes.
|
|
421
|
+
WebUI text is keyed by its English source: fixed text goes through `useT().t`, whose type requires a `web/src/i18n/zh.ts` entry with the same `{name}` marks, and run-time status words or kept notices through `tx`, which passes unknown text and codes through.
|
|
422
|
+
The timeline client takes the chosen language from its bridge and is remounted on a switch; browser scenarios pin `locale: "en-US"` so English assertions do not follow the host's `LANG`.
|
|
418
423
|
Publish `web/dist` from Node: Vite run under Bun lays out its preload helper differently, so a Bun build fails a Node `build:web:check`.
|
|
419
424
|
Review the independently enumerated package member list and every emitted body, not a count or an expected list generated from the tar under test.
|
|
420
425
|
Keep public Pi peers external for the extension, the exact Undici/ws runtime edges, the exact optional SDK pins for the standalone owner and all upstream/font/vendor notices.
|
|
421
426
|
Preserve original license bytes through their precise Git attributes; line-ending normalization must not invalidate audited source/notice hashes.
|
|
422
427
|
|
|
428
|
+
### Release 2.0.10 acceptance snapshot
|
|
429
|
+
|
|
430
|
+
Full `verify` passed on the 2.0.10 release tree (`4843334` plus the version bump), in the 2.0.3 environment below.
|
|
431
|
+
It ran 1949 root tests in 78 files (27 Windows-only controls skipped on Linux), 116 web tests, 5 real-browser scenarios and the 132-member pack audit, and CI 40278386880 passed 12/12 on the candidate `3cd94f4` (this commit before its snapshot).
|
|
432
|
+
That CI run's first Windows Node 22 `parallel` attempt failed only `workspace-landing`'s cleanup (EBUSY removing the just-stopped landing workspace), as in 2.0.6's first run; the job passed when rerun.
|
|
433
|
+
The fix answers a 2.0.9 report: an Ollama `"apiKey": "ollama"` under a provider named `ollama` made the whole model catalog refuse `unsupported-context`.
|
|
434
|
+
An isolated owner with that `models.json` beside a second provider reproduced the refusal on 2.0.9; on 2.0.10 Chromium listed the second provider's models and explained the one withheld entry in English and Chinese.
|
|
435
|
+
|
|
436
|
+
### Release 2.0.9 acceptance snapshot
|
|
437
|
+
|
|
438
|
+
Full `verify` passed on the 2.0.9 release tree (`0e720a1` plus the version bump), in the 2.0.3 environment below.
|
|
439
|
+
It ran 1948 root tests in 78 files (27 Windows-only controls skipped on Linux), 116 web tests, 5 real-browser scenarios and the 132-member pack audit, and CI 40209338973 passed 12/12 on the candidate `8b53b64` (this commit before its snapshot).
|
|
440
|
+
The release adds the Simplified Chinese WebUI; Chromium at 1440 and 390px rendered the shell, inspector, dialogs and timeline in Chinese from a `zh-CN` browser locale.
|
|
441
|
+
On the feature tree, an earlier full run failed only the reuse provenance pin (refreshed), and one `accepting-browser` run failed after the second context's Start; that scenario passed in four later runs, and its cause was not identified.
|
|
442
|
+
|
|
423
443
|
### Release 2.0.8 acceptance snapshot
|
|
424
444
|
|
|
425
445
|
Full `verify` passed on the 2.0.8 release tree (`7a40be6` plus the version bump), in the 2.0.3 environment below.
|
|
@@ -152,6 +152,9 @@ At sign-out or shutdown Windows ends the owner without orderly Stop, which can l
|
|
|
152
152
|
`models.json` may use only fields the pinned SDK supports.
|
|
153
153
|
`supportsMidConvoSystemMessages` from newer Pi is accepted because the pinned SDK has no system-role conversation message; other unknown fields refuse rather than being silently ignored.
|
|
154
154
|
The owner model choice offers every thinking level the pinned SDK supports for a model, `off` through `xhigh` and `max`; a model whose `thinkingLevelMap` maps only `max` (as some DeepSeek configurations do) is chosen at `max`, and a child runs only at exactly that level.
|
|
155
|
+
A model whose provider or model ID contains a known credential value is withheld from the catalog, since showing the ID would reveal that value; the catalog says how many models are withheld, and the rest stay selectable.
|
|
156
|
+
Credential values are `auth.json` keys, `models.json` `apiKey` and header values (and the variables they name) and service-environment variables whose names contain `KEY`, `TOKEN`, `SECRET`, `PASS`, `AUTH` or `COOKIE`.
|
|
157
|
+
The common Ollama placeholder `"apiKey": "ollama"` under a provider named `ollama` is such a case: give the placeholder a value that matches no provider or model ID (for example `"local-no-auth"`), then restart the service.
|
|
155
158
|
The owner loads `models.json` once at start: after it changes, the model catalog, the chosen model and the workspace view refuse as `stale` (credential changes in `auth.json` are read fresh).
|
|
156
159
|
Restart the service to reload it (`cadence server restart`, or `stop` then `start`), then choose the owner model again.
|
|
157
160
|
- A foreground owner can be managed from another terminal.
|
package/package.json
CHANGED
package/src/native-build.ts
CHANGED
|
@@ -43,6 +43,9 @@ export interface ModelCandidate {
|
|
|
43
43
|
export interface ModelCatalog extends ModelChoiceState {
|
|
44
44
|
status: "complete" | "capacity";
|
|
45
45
|
entries: ModelCandidate[];
|
|
46
|
+
/** Models left out because a provider/model ID contains a known credential
|
|
47
|
+
* literal or control characters; never their identifiers. */
|
|
48
|
+
withheld: number;
|
|
46
49
|
}
|
|
47
50
|
export interface ChooseModelRequest {
|
|
48
51
|
expected: ChoiceVersion;
|
|
@@ -119,11 +122,19 @@ export function decodeModelCandidate(value: unknown): ModelCandidate {
|
|
|
119
122
|
}
|
|
120
123
|
export function decodeModelCatalog(value: unknown): ModelCatalog {
|
|
121
124
|
canonicalJson({ kind: "ok", value }, LIMITS.pageBytes);
|
|
122
|
-
const o = closed(value, [
|
|
125
|
+
const o = closed(value, [
|
|
126
|
+
"version",
|
|
127
|
+
"selected",
|
|
128
|
+
"status",
|
|
129
|
+
"entries",
|
|
130
|
+
"withheld",
|
|
131
|
+
]);
|
|
123
132
|
const entries = list(o.entries, LIMITS.maxPageEntries, decodeModelCandidate);
|
|
124
133
|
const status = oneOf(o.status, ["complete", "capacity"]);
|
|
134
|
+
const withheld = integer(o.withheld, 0, LIMITS.maxPageEntries);
|
|
125
135
|
if (
|
|
126
|
-
(status === "capacity" && entries.length) ||
|
|
136
|
+
(status === "capacity" && (entries.length || withheld)) ||
|
|
137
|
+
entries.length + withheld > LIMITS.maxPageEntries ||
|
|
127
138
|
new Set(entries.map((e) => canonicalJson([e.provider, e.model]))).size !==
|
|
128
139
|
entries.length
|
|
129
140
|
)
|
|
@@ -133,5 +144,6 @@ export function decodeModelCatalog(value: unknown): ModelCatalog {
|
|
|
133
144
|
selected: nullable(o.selected, decodeModelSelection),
|
|
134
145
|
status,
|
|
135
146
|
entries,
|
|
147
|
+
withheld,
|
|
136
148
|
};
|
|
137
149
|
}
|
package/src/service-host.mjs
CHANGED
|
@@ -4019,16 +4019,24 @@ function decodeModelCandidate(value) {
|
|
|
4019
4019
|
}
|
|
4020
4020
|
function decodeModelCatalog(value) {
|
|
4021
4021
|
canonicalJson({ kind: "ok", value }, LIMITS.pageBytes);
|
|
4022
|
-
const o = closed(value, [
|
|
4022
|
+
const o = closed(value, [
|
|
4023
|
+
"version",
|
|
4024
|
+
"selected",
|
|
4025
|
+
"status",
|
|
4026
|
+
"entries",
|
|
4027
|
+
"withheld"
|
|
4028
|
+
]);
|
|
4023
4029
|
const entries = list(o.entries, LIMITS.maxPageEntries, decodeModelCandidate);
|
|
4024
4030
|
const status = oneOf(o.status, ["complete", "capacity"]);
|
|
4025
|
-
|
|
4031
|
+
const withheld = integer(o.withheld, 0, LIMITS.maxPageEntries);
|
|
4032
|
+
if (status === "capacity" && (entries.length || withheld) || entries.length + withheld > LIMITS.maxPageEntries || new Set(entries.map((e) => canonicalJson([e.provider, e.model]))).size !== entries.length)
|
|
4026
4033
|
invalid();
|
|
4027
4034
|
return {
|
|
4028
4035
|
version: decodeChoiceVersion(o.version),
|
|
4029
4036
|
selected: nullable(o.selected, decodeModelSelection),
|
|
4030
4037
|
status,
|
|
4031
|
-
entries
|
|
4038
|
+
entries,
|
|
4039
|
+
withheld
|
|
4032
4040
|
};
|
|
4033
4041
|
}
|
|
4034
4042
|
// src/history/admission.ts
|
|
@@ -17959,19 +17967,22 @@ class OwnerModelContext {
|
|
|
17959
17967
|
throw new ContractError("unsupported-context");
|
|
17960
17968
|
count2 += runtime.getModels(provider).length;
|
|
17961
17969
|
if (count2 > LIMITS.maxPageEntries)
|
|
17962
|
-
return { status: "capacity", entries: [] };
|
|
17970
|
+
return { status: "capacity", entries: [], withheld: 0 };
|
|
17963
17971
|
}
|
|
17964
17972
|
const literals = this.catalogLiterals(current2);
|
|
17965
17973
|
const entries = [];
|
|
17974
|
+
let withheld = 0;
|
|
17966
17975
|
for (const provider of providers) {
|
|
17967
17976
|
const models = runtime.getModels(provider);
|
|
17968
|
-
if (entries.length + models.length > LIMITS.maxPageEntries)
|
|
17977
|
+
if (entries.length + withheld + models.length > LIMITS.maxPageEntries)
|
|
17969
17978
|
throw new ContractError("capacity");
|
|
17970
17979
|
for (const model of models) {
|
|
17971
17980
|
if (model.provider !== provider)
|
|
17972
17981
|
throw new ContractError("unsupported-context");
|
|
17973
|
-
this.
|
|
17974
|
-
|
|
17982
|
+
if (!this.isPublicIdentifier(provider, literals) || !this.isPublicIdentifier(model.id, literals)) {
|
|
17983
|
+
withheld++;
|
|
17984
|
+
continue;
|
|
17985
|
+
}
|
|
17975
17986
|
const api = modelApis.find((api2) => api2 === model.api) ?? null;
|
|
17976
17987
|
const levels = this.#thinking(model);
|
|
17977
17988
|
if (levels.length > 7)
|
|
@@ -18018,7 +18029,7 @@ class OwnerModelContext {
|
|
|
18018
18029
|
const after = this.catalogSource();
|
|
18019
18030
|
if (after.auth.identity !== current2.auth.identity || after.auth.stamp !== current2.auth.stamp)
|
|
18020
18031
|
throw new ContractError("stale");
|
|
18021
|
-
return { status: "complete", entries };
|
|
18032
|
+
return { status: "complete", entries, withheld };
|
|
18022
18033
|
} catch (error) {
|
|
18023
18034
|
throw new ContractError(error instanceof ContractError ? error.code : "corrupt-selected");
|
|
18024
18035
|
}
|
|
@@ -18089,8 +18100,11 @@ class OwnerModelContext {
|
|
|
18089
18100
|
add(value);
|
|
18090
18101
|
return [...literals];
|
|
18091
18102
|
}
|
|
18103
|
+
isPublicIdentifier(value, literals) {
|
|
18104
|
+
return !/[\p{Cc}\p{Cf}\p{Cs}]/u.test(value) && diagnosticText(value, 512, literals) === value && sanitizeText(value, literals) === value;
|
|
18105
|
+
}
|
|
18092
18106
|
publicIdentifier(value, literals) {
|
|
18093
|
-
if (
|
|
18107
|
+
if (!this.isPublicIdentifier(value, literals))
|
|
18094
18108
|
throw new ContractError("unsupported-context");
|
|
18095
18109
|
}
|
|
18096
18110
|
replaceSelection(input, beforeInstall) {
|
|
@@ -22871,7 +22885,7 @@ function refuseAt(stage, step) {
|
|
|
22871
22885
|
}
|
|
22872
22886
|
}
|
|
22873
22887
|
async function createServiceHost(value, sourceBuild) {
|
|
22874
|
-
const embedded = "
|
|
22888
|
+
const embedded = "c2bb81519387005a71e88a64364dff47d5c39dab17812833ca898bc51055e59d";
|
|
22875
22889
|
if (embedded && sourceBuild)
|
|
22876
22890
|
throw new ContractError("unsupported-context");
|
|
22877
22891
|
const contractHash = decodeHash(embedded ?? sourceBuild?.contractHash);
|
package/src/task-shell.ts
CHANGED
|
@@ -579,7 +579,7 @@ export function registerTaskShell(pi: ExtensionAPI): void {
|
|
|
579
579
|
`${e.provider}/${e.model} • ${e.api ?? "unsupported"} • ${e.eligibility}`,
|
|
580
580
|
);
|
|
581
581
|
const selected = await ui.select(
|
|
582
|
-
`Exact OWNER model (Pi conversation model is independent) • ${catalog.status}\nCurrent ${pretty(catalog.selected)}`,
|
|
582
|
+
`Exact OWNER model (Pi conversation model is independent) • ${catalog.status}\nCurrent ${pretty(catalog.selected)}${catalog.withheld ? `\n${catalog.withheld} withheld: a provider/model ID contains a credential value (such as an apiKey equal to the provider name) or control characters. Give each credential a value that matches no ID; restart the service after editing models.json.` : ""}`,
|
|
583
583
|
[...labels, "Return"],
|
|
584
584
|
);
|
|
585
585
|
if (!selected || !labels.includes(selected)) return;
|