@abelxiaoxing/cadence 2.0.13 → 2.0.15

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.
@@ -1,3 +1,7 @@
1
+ import type {
2
+ WorkbenchCommand,
3
+ WorkbenchRead,
4
+ } from "./service/contracts/workbench.ts";
1
5
  import type { ServiceTransport } from "./service-client.ts";
2
6
  import {
3
7
  type CancelCommand,
@@ -63,6 +67,8 @@ export class HttpServiceTransport implements ServiceTransport {
63
67
  constructor(
64
68
  private readonly options: {
65
69
  origin: string;
70
+ /** Authenticated same-origin project routing; never an arbitrary URL. */
71
+ project?: string;
66
72
  build?: ClientBuild;
67
73
  fetch?: typeof fetch;
68
74
  /** Optional total bound for finite replies only, never streaming export. */
@@ -78,12 +84,26 @@ export class HttpServiceTransport implements ServiceTransport {
78
84
  !/^http:\/\/(?:127\.0\.0\.1|\[::1\]):[1-9][0-9]*$/.test(options.origin))
79
85
  )
80
86
  throw new ContractError("invalid");
87
+ if (
88
+ options.project !== undefined &&
89
+ !/^[a-f0-9]{64}$/.test(options.project)
90
+ )
91
+ throw new ContractError("invalid");
81
92
  this.origin = options.origin;
82
93
  if (options.build) options.build = decodeClientBuild(options.build);
83
94
  // Native browser fetch requires its Window receiver. Node/injected arrow
84
95
  // fetchers tolerate the wrong `this`, so HTTP-only fixtures miss this bug.
85
96
  this.fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);
86
97
  }
98
+ workbenchRead(
99
+ request: WorkbenchRead,
100
+ signal?: AbortSignal,
101
+ ): Promise<unknown> {
102
+ return this.request("workbenchRead", request, signal);
103
+ }
104
+ workbenchCommand(request: WorkbenchCommand): Promise<unknown> {
105
+ return this.request("workbenchCommand", request);
106
+ }
87
107
  private headers(write: boolean): Record<string, string> {
88
108
  const headers: Record<string, string> = { "Cache-Control": "no-store" };
89
109
  if (this.options.build)
@@ -109,20 +129,23 @@ export class HttpServiceTransport implements ServiceTransport {
109
129
  write = wire.method === "POST";
110
130
  // No automatic session/preflight refresh, changed command ID, mutation retry,
111
131
  // credential query or redirect follow after an uncertain HTTP outcome.
112
- const response = await this.fetcher(`${this.origin}${wire.path}`, {
113
- method: wire.method,
114
- headers: this.headers(write),
115
- credentials: "same-origin",
116
- redirect: "error",
117
- signal:
118
- this.options.requestTimeoutMs === undefined
119
- ? signal
120
- : AbortSignal.any([
121
- ...(signal ? [signal] : []),
122
- AbortSignal.timeout(this.options.requestTimeoutMs),
123
- ]),
124
- ...(write ? { body: wire.body } : {}),
125
- });
132
+ const response = await this.fetcher(
133
+ `${this.origin}${this.options.project ? `/projects/${this.options.project}` : ""}${wire.path}`,
134
+ {
135
+ method: wire.method,
136
+ headers: this.headers(write),
137
+ credentials: "same-origin",
138
+ redirect: "error",
139
+ signal:
140
+ this.options.requestTimeoutMs === undefined
141
+ ? signal
142
+ : AbortSignal.any([
143
+ ...(signal ? [signal] : []),
144
+ AbortSignal.timeout(this.options.requestTimeoutMs),
145
+ ]),
146
+ ...(write ? { body: wire.body } : {}),
147
+ },
148
+ );
126
149
  // Keep a known allocation even if its original response body is lost or
127
150
  // malformed. Without a validated CSRF challenge detach may remain blocked;
128
151
  // never silently forget that slot and acquire 16 replacement sessions.
@@ -273,14 +296,17 @@ export class HttpServiceTransport implements ServiceTransport {
273
296
  ): AsyncGenerator<unknown> {
274
297
  const request = decodeExportRequest(input),
275
298
  wire = wireRequest("export", request);
276
- const response = await this.fetcher(`${this.origin}${wire.path}`, {
277
- method: wire.method,
278
- headers: this.headers(wire.method === "POST"),
279
- ...(wire.body === undefined ? {} : { body: wire.body }),
280
- credentials: "same-origin",
281
- redirect: "error",
282
- signal,
283
- });
299
+ const response = await this.fetcher(
300
+ `${this.origin}${this.options.project ? `/projects/${this.options.project}` : ""}${wire.path}`,
301
+ {
302
+ method: wire.method,
303
+ headers: this.headers(wire.method === "POST"),
304
+ ...(wire.body === undefined ? {} : { body: wire.body }),
305
+ credentials: "same-origin",
306
+ redirect: "error",
307
+ signal,
308
+ },
309
+ );
284
310
  if (!response.ok || !response.body) {
285
311
  await response.body?.cancel();
286
312
  throw new ContractError("unavailable");
@@ -336,7 +362,7 @@ export class HttpServiceTransport implements ServiceTransport {
336
362
  if (!this.attachment || !this.options.socket)
337
363
  throw new ContractError("unavailable");
338
364
  const socket = this.options.socket(
339
- `${this.origin.replace(/^http/, "ws")}${WS_PATH}?attachment=${this.attachment}`,
365
+ `${this.origin.replace(/^http/, "ws")}${this.options.project ? `/projects/${this.options.project}` : ""}${WS_PATH}?attachment=${this.attachment}`,
340
366
  );
341
367
  let active = false,
342
368
  closed = false,
@@ -401,7 +427,7 @@ export class HttpServiceTransport implements ServiceTransport {
401
427
  return stop;
402
428
  }
403
429
  }
404
- async function boundedText(
430
+ export async function boundedText(
405
431
  response: Response,
406
432
  maximum: number,
407
433
  ): Promise<string> {
@@ -1,4 +1,9 @@
1
1
  import { closed, oneOf } from "./service/contracts/core.ts";
2
+ import {
3
+ decodeWorkbenchCommand,
4
+ decodeWorkbenchRead,
5
+ decodeWorkbenchResult,
6
+ } from "./service/contracts/workbench.ts";
2
7
  /** Portable finite HTTP/WS route owner. Common DTO semantics stay in service-contract. */
3
8
  import {
4
9
  ContractError,
@@ -44,6 +49,8 @@ import {
44
49
 
45
50
  export const HTTP_ROUTES = {
46
51
  session: "/api/v1/session",
52
+ workbenchRead: "/api/v1/workbench/read",
53
+ workbenchCommand: "/api/v1/workbench/command",
47
54
  current: "/api/v1/current",
48
55
  currentChanges: "/api/v1/current-changes",
49
56
  activeRuns: "/api/v1/active-runs",
@@ -102,7 +109,9 @@ export function httpOutputLimit(
102
109
  operation === "activeRuns" ||
103
110
  operation === "workspace" ||
104
111
  operation === "models" ||
105
- operation === "chooseModel"
112
+ operation === "chooseModel" ||
113
+ operation === "workbenchRead" ||
114
+ operation === "workbenchCommand"
106
115
  )
107
116
  return LIMITS.pageBytes;
108
117
  if (operation === "graph") {
@@ -117,12 +126,21 @@ export function httpOutputLimit(
117
126
  return LIMITS.controlBytes;
118
127
  }
119
128
  export function mutation(operation: HttpOperation): boolean {
120
- return ["preflight", "command", "cancel", "detach", "chooseModel"].includes(
121
- operation,
122
- );
129
+ return [
130
+ "preflight",
131
+ "command",
132
+ "cancel",
133
+ "detach",
134
+ "chooseModel",
135
+ "workbenchCommand",
136
+ ].includes(operation);
123
137
  }
124
138
  export function decodeHttpInput(operation: HttpOperation, value: unknown) {
125
139
  switch (operation) {
140
+ case "workbenchRead":
141
+ return decodeWorkbenchRead(value);
142
+ case "workbenchCommand":
143
+ return decodeWorkbenchCommand(value);
126
144
  case "session":
127
145
  case "models":
128
146
  case "activeRuns":
@@ -179,6 +197,9 @@ export function decodeHttpOutput(
179
197
  if ((pageReaders as readonly string[]).includes(operation))
180
198
  return decodeReply(value, decodeHistoryReadResult);
181
199
  switch (operation) {
200
+ case "workbenchRead":
201
+ case "workbenchCommand":
202
+ return decodeReply(value, decodeWorkbenchResult);
182
203
  case "current":
183
204
  return decodeReply(value, decodeCurrentView);
184
205
  case "currentChanges":
@@ -225,6 +246,7 @@ export function decodeHttpOutput(
225
246
  export function readonlyBody(operation: HttpOperation): boolean {
226
247
  const operations: readonly HttpOperation[] = [
227
248
  ...pageReaders,
249
+ "workbenchRead",
228
250
  "current",
229
251
  "currentChanges",
230
252
  "checkObligations",
package/src/task-store.ts CHANGED
@@ -251,7 +251,7 @@ export function savedRecordIdentity(record: TaskRecord): string {
251
251
  return contentIdentity(recordText(record));
252
252
  }
253
253
  /** Resolve ambient aliases, including a missing tail, without creating anything. */
254
- function canonicalAmbientRoot(path: string): string {
254
+ export function canonicalAmbientRoot(path: string): string {
255
255
  const full = resolve(path);
256
256
  try {
257
257
  return realpathSync(full);
@@ -304,20 +304,21 @@ export function privateWorkspace(
304
304
  canonicalAmbientRoot(requested),
305
305
  );
306
306
  }
307
- export const LANDING_WORKSPACE = "cadence";
308
- /** A directory that contains the private state home (the home directory and
309
- * its ancestors) can never be a workspace; use ~/cadence instead of refusing.
310
- * Observation only: the caller decides whether the landing may be created. */
307
+ /** A project must be explicit and outside private state. Never choose, create or
308
+ * reconstruct a default workspace. The workbench itself has no project cwd. */
311
309
  export function effectiveWorkspace(
312
310
  requested: string,
313
311
  stateHome?: string,
314
- home = homedir(),
312
+ _home?: string,
315
313
  ): { workspace: string; redirected: boolean } {
316
- if (privateWorkspace(requested, stateHome))
317
- throw new Error("Private state cannot be a workspace");
318
- if (!stateHomeInside(resolveWorkspace(requested).root, stateHome))
319
- return { workspace: requested, redirected: false };
320
- return { workspace: join(home, LANDING_WORKSPACE), redirected: true };
314
+ if (
315
+ privateWorkspace(requested, stateHome) ||
316
+ stateHomeInside(resolveWorkspace(requested).root, stateHome)
317
+ )
318
+ throw new Error(
319
+ "Choose a project outside Cadence private state; no default workspace was created",
320
+ );
321
+ return { workspace: requested, redirected: false };
321
322
  }
322
323
  export interface CheckoutLock {
323
324
  schema: "cadence-assistant-lock/1";
@@ -96,6 +96,7 @@ export const ERROR_CODES = [
96
96
  "quarantine",
97
97
  "capacity",
98
98
  "unavailable",
99
+ "registry-locked",
99
100
  "auth-unavailable",
100
101
  "recording-exhausted",
101
102
  "receipt-inconclusive",