@kaoruisaac/pedelec 0.2.1 → 0.2.2

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
@@ -24,6 +24,7 @@ Pedelec is a browser SDK and local bridge for applications that want to work wit
24
24
  A web application can use Pedelec to:
25
25
 
26
26
  - create an agent session on the user's machine;
27
+ - choose an application-managed workspace with the native directory picker;
27
28
  - send user instructions and receive streamed assistant text;
28
29
  - expose narrowly scoped browser-side tools to the agent;
29
30
  - resume or end sessions; and
@@ -168,6 +169,21 @@ const session = await pedelec.createSession({
168
169
 
169
170
  Pedelec creates missing `skills/`, `assets/`, `logs/`, and `tmp/` directories and preserves existing files. It never deletes an explicit workspace, and multiple active sessions may share it. Filesystem write conflicts are the application's responsibility. An explicit path must not overlap Pedelec's managed sandbox root.
170
171
 
172
+ ## Selecting an Application Workspace
173
+
174
+ Use `directoryPicker()` when the application wants the user to choose a local workspace before creating a session:
175
+
176
+ ```ts
177
+ const path = await pedelec.directoryPicker();
178
+ if (path === null) return; // The user cancelled the native picker.
179
+
180
+ const session = await pedelec.createSession({
181
+ sandbox: { path },
182
+ });
183
+ ```
184
+
185
+ `directoryPicker()` returns `Promise<string | null>` and does not validate or initialize the selected directory. It requires the same origin approval as other sensitive Desktop APIs. The selected path remains application-managed; existing Core sandbox rules are applied later by `createSession()`, which may return `SANDBOX_PATH_INVALID` for an unacceptable path.
186
+
171
187
  ---
172
188
 
173
189
  ## Creating a Session
package/dist/index.d.ts CHANGED
@@ -233,11 +233,13 @@ export declare class Pedelec {
233
233
  constructor(options?: PedelecOptions);
234
234
  createSession(): Promise<PedelecSession<string>>;
235
235
  createSession<const TTools extends readonly ToolDefinition[]>(input: CreateSessionInput<TTools>): Promise<PedelecSession<ToolNameOf<TTools>>>;
236
+ directoryPicker(): Promise<string | null>;
236
237
  listProviders(): Promise<ProviderInfo[]>;
237
238
  getSettings(): Promise<PedelecSettings>;
238
239
  getApprovalStatus(): Promise<ApprovalStatus>;
239
240
  resumeSession(sessionId: string): Promise<PedelecSession<string>>;
240
241
  request<T>(type: string, payload?: Record<string, unknown>): Promise<T>;
242
+ private requestWithOptions;
241
243
  unregisterSession(sessionId: string): void;
242
244
  private resolveCreateSessionInput;
243
245
  checkAvailability(): Promise<PedelecAvailability>;
package/dist/index.js CHANGED
@@ -149,6 +149,10 @@ export class Pedelec {
149
149
  }
150
150
  return this.registerSession(result.sessionId, resolvedInput.provider, resolvedInput.effortLevel, resolvedInput.inlineToolHandlers);
151
151
  }
152
+ async directoryPicker() {
153
+ const result = await this.requestWithOptions("pick_directory", {}, { timeoutMs: null });
154
+ return normalizeDirectoryPickerResponse(result);
155
+ }
152
156
  async listProviders() {
153
157
  return normalizeProviderInfoList(await this.request("list_providers"));
154
158
  }
@@ -186,6 +190,9 @@ export class Pedelec {
186
190
  return this.registerSession(result.sessionId, "", undefined);
187
191
  }
188
192
  request(type, payload = {}) {
193
+ return this.requestWithOptions(type, payload);
194
+ }
195
+ requestWithOptions(type, payload = {}, options = {}) {
189
196
  let port;
190
197
  try {
191
198
  port = this.ensureExtensionPort();
@@ -196,13 +203,16 @@ export class Pedelec {
196
203
  const requestId = `sdk_${Date.now()}_${this.nextRequestNumber++}`;
197
204
  const message = { channelId: this.channelId, type, requestId, ...payload };
198
205
  return new Promise((resolve, reject) => {
199
- const timeoutId = setTimeout(() => {
200
- this.pendingRequests.delete(requestId);
201
- reject(makeError("SDK_BRIDGE_TIMEOUT", "Pedelec extension did not respond.", {
202
- requestId,
203
- type,
204
- }));
205
- }, this.bridgeTimeoutMs);
206
+ const timeoutMs = options.timeoutMs === undefined ? this.bridgeTimeoutMs : options.timeoutMs;
207
+ const timeoutId = timeoutMs === null
208
+ ? null
209
+ : setTimeout(() => {
210
+ this.pendingRequests.delete(requestId);
211
+ reject(makeError("SDK_BRIDGE_TIMEOUT", "Pedelec extension did not respond.", {
212
+ requestId,
213
+ type,
214
+ }));
215
+ }, timeoutMs);
206
216
  this.pendingRequests.set(requestId, {
207
217
  port,
208
218
  resolve: (value) => resolve(value),
@@ -214,7 +224,8 @@ export class Pedelec {
214
224
  }
215
225
  catch (err) {
216
226
  this.pendingRequests.delete(requestId);
217
- clearTimeout(timeoutId);
227
+ if (timeoutId !== null)
228
+ clearTimeout(timeoutId);
218
229
  const error = normalizeError(err, "EXTENSION_DISCONNECTED", "Pedelec extension disconnected.");
219
230
  if (this.port === port) {
220
231
  this.handleDisconnect(port, error);
@@ -398,7 +409,8 @@ export class Pedelec {
398
409
  if (!pending)
399
410
  return;
400
411
  this.pendingRequests.delete(message.requestId);
401
- clearTimeout(pending.timeoutId);
412
+ if (pending.timeoutId !== null)
413
+ clearTimeout(pending.timeoutId);
402
414
  if (message.ok) {
403
415
  pending.resolve(message.result);
404
416
  }
@@ -427,7 +439,8 @@ export class Pedelec {
427
439
  if (pending.port !== port)
428
440
  continue;
429
441
  this.pendingRequests.delete(requestId);
430
- clearTimeout(pending.timeoutId);
442
+ if (pending.timeoutId !== null)
443
+ clearTimeout(pending.timeoutId);
431
444
  pending.reject(error);
432
445
  }
433
446
  }
@@ -1013,6 +1026,16 @@ function normalizePedelecSettings(value) {
1013
1026
  }
1014
1027
  return { defaultProvider: value.defaultProvider };
1015
1028
  }
1029
+ function normalizeDirectoryPickerResponse(value) {
1030
+ if (!isPlainObject(value) || !("path" in value)) {
1031
+ throw makeError("SDK_PROTOCOL_ERROR", "pick_directory response had an invalid shape");
1032
+ }
1033
+ const path = value.path;
1034
+ if (path !== null && (typeof path !== "string" || path.length === 0)) {
1035
+ throw makeError("SDK_PROTOCOL_ERROR", "pick_directory response had an invalid shape");
1036
+ }
1037
+ return path;
1038
+ }
1016
1039
  function normalizeProviderInfoList(value) {
1017
1040
  if (!Array.isArray(value))
1018
1041
  throw makeError("SDK_PROTOCOL_ERROR", "list_providers response was not an array");
@@ -136,6 +136,11 @@ async function availabilityHasPublicType() {
136
136
  void availability;
137
137
  void promise;
138
138
  }
139
+ async function directoryPickerHasPublicType() {
140
+ const pedelec = new Pedelec();
141
+ const path = pedelec.directoryPicker();
142
+ void path;
143
+ }
139
144
  function publicSecurityTypesAreRestricted() {
140
145
  const status = {
141
146
  installed: true,
@@ -154,6 +159,7 @@ void noSkillsFallsBackToString;
154
159
  void effortLevelPublicTypeContract;
155
160
  void listAssetsHasPublicTypes;
156
161
  void availabilityHasPublicType;
162
+ void directoryPickerHasPublicType;
157
163
  void publicSecurityTypesAreRestricted;
158
164
  const baseContext = {
159
165
  sessionId: "thread_1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaoruisaac/pedelec",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",