@abelxiaoxing/cadence 2.0.9 → 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 CHANGED
@@ -379,6 +379,7 @@ Byte values are UTF-8/encoded bounds where specified; they are not targets to si
379
379
  | Token source the user edits (`CADENCE_TOKEN` in `.env`) | Read once before binding, never bound for the run: an edit cannot fence Stop |
380
380
  | Saved port/token source differs from `.env` | Only when stopped, reselect just those under the reservation; running ones refuse |
381
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 |
382
383
 
383
384
  Good: current context → explicit grant → durable acceptance → genuine original native/storage settlement → exact retained dependency → real checks → human review.
384
385
  Base: a client leaves and another reattaches to the same accepted run without launching it again.
@@ -424,6 +425,14 @@ Review the independently enumerated package member list and every emitted body,
424
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.
425
426
  Preserve original license bytes through their precise Git attributes; line-ending normalization must not invalidate audited source/notice hashes.
426
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
+
427
436
  ### Release 2.0.9 acceptance snapshot
428
437
 
429
438
  Full `verify` passed on the 2.0.9 release tree (`0e720a1` 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abelxiaoxing/cadence",
3
- "version": "2.0.9",
3
+ "version": "2.0.10",
4
4
  "description": "Shared Pi task workspace: native terminal and WebUI, persistent service, checks and human review.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -2,5 +2,5 @@
2
2
  export const nativeBuild = {
3
3
  kind: "native",
4
4
  contractHash:
5
- "ff017feffc6b6b4521e470d82514d0ca008e15472fa1442ec498b243b0f7cf87",
5
+ "c2bb81519387005a71e88a64364dff47d5c39dab17812833ca898bc51055e59d",
6
6
  } as const;
@@ -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, ["version", "selected", "status", "entries"]);
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
  }
@@ -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, ["version", "selected", "status", "entries"]);
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
- if (status === "capacity" && entries.length || new Set(entries.map((e) => canonicalJson([e.provider, e.model]))).size !== entries.length)
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.publicIdentifier(provider, literals);
17974
- this.publicIdentifier(model.id, literals);
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 (/[\p{Cc}\p{Cf}\p{Cs}]/u.test(value) || diagnosticText(value, 512, literals) !== value || sanitizeText(value, literals) !== value)
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 = "ff017feffc6b6b4521e470d82514d0ca008e15472fa1442ec498b243b0f7cf87";
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;