@github/copilot-sdk 1.0.9 → 1.0.11-preview.1

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/dist/session.js CHANGED
@@ -1,14 +1,30 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
1
2
  import { ConnectionError, ErrorCodes, ResponseError } from "vscode-jsonrpc/node.js";
2
3
  import { createSessionRpc } from "./generated/rpc.js";
3
4
  import { CanvasError } from "./canvas.js";
4
5
  import { getTraceContext } from "./telemetry.js";
5
6
  import {
7
+ FACTORY_AGENT_OPTION_KEYS,
6
8
  getFactoryDefinition,
7
9
  FactoryResumeError,
8
10
  isFactoryRunTerminal
9
11
  } from "./factory.js";
10
12
  function isFactoryResumeErrorCode(value) {
11
- return value === "not_found" || value === "non_resumable" || value === "already_active" || value === "reapproval_declined" || value === "no_approval_provider";
13
+ return value === "not_found" || value === "non_resumable" || value === "already_active" || value === "factory_already_running" || value === "factory_limits_invalid" || value === "factory_session_disposed" || value === "factory_storage_unavailable" || value === "factory_storage_corrupt";
14
+ }
15
+ function copyDefinedFactoryAgentOption(source, target, key) {
16
+ const value = source[key];
17
+ if (value !== void 0) {
18
+ target[key] = value;
19
+ }
20
+ }
21
+ const factoryExecutionStore = new AsyncLocalStorage();
22
+ function throwIfFactoryExecutionIsActive() {
23
+ if (factoryExecutionStore.getStore()?.active) {
24
+ throw new Error(
25
+ "factory.run and factory.resume are not allowed while a factory body is running on this call path."
26
+ );
27
+ }
12
28
  }
13
29
  function deserializeHookInput(raw) {
14
30
  if (!raw || typeof raw !== "object" || typeof raw.timestamp !== "number") {
@@ -129,7 +145,10 @@ class FactoryProgressBuffer {
129
145
  const lines = this.pending.splice(0);
130
146
  await this.flushTail;
131
147
  if (this.flushFailed) {
132
- throw this.flushError;
148
+ console.warn(
149
+ "Ignoring a background factory progress flush failure after the factory body settled",
150
+ this.flushError
151
+ );
133
152
  }
134
153
  if (lines.length > 0) {
135
154
  try {
@@ -160,9 +179,6 @@ class FactoryProgressBuffer {
160
179
  }
161
180
  }
162
181
  }
163
- function toPublicFactoryRunResult(envelope) {
164
- return envelope;
165
- }
166
182
  async function awaitFactoryOperation(operation, signal) {
167
183
  let rejectAbort;
168
184
  const abortPromise = new Promise((_resolve, reject) => {
@@ -242,6 +258,7 @@ class CopilotSession {
242
258
  */
243
259
  factory = {
244
260
  run: (async (nameOrHandle, options) => {
261
+ throwIfFactoryExecutionIsActive();
245
262
  const name = typeof nameOrHandle === "string" ? nameOrHandle : getFactoryDefinition(nameOrHandle).meta.name;
246
263
  if (options?.resumeFromRunId !== void 0) {
247
264
  return this.factory.resume(options.resumeFromRunId, {
@@ -258,6 +275,7 @@ class CopilotSession {
258
275
  return this.settleFactoryRun(envelope);
259
276
  }),
260
277
  resume: (async (runId, options) => {
278
+ throwIfFactoryExecutionIsActive();
261
279
  let response;
262
280
  try {
263
281
  response = await this.rpc.factory.resume({
@@ -275,12 +293,12 @@ class CopilotSession {
275
293
  }
276
294
  return this.settleFactoryRun(response.run);
277
295
  }),
278
- getRun: async (runId) => toPublicFactoryRunResult(await this.rpc.factory.getRun({ runId })),
296
+ getRun: async (runId) => this.rpc.factory.getRun({ runId }),
279
297
  waitForRun: (runId, options) => this.waitForFactoryRun(runId, options?.signal),
280
- listRuns: async () => (await this.rpc.factory.listRuns()).runs,
298
+ listRuns: async () => (await this.rpc.factory.listRuns({})).runs,
281
299
  getRunDetail: (runId) => this.rpc.factory.getRunDetail({ runId }),
282
300
  getRunProgress: (runId, options = {}) => this.rpc.factory.getRunProgress({ runId, ...options }),
283
- cancel: async (runId) => toPublicFactoryRunResult(await this.rpc.factory.cancel({ runId }))
301
+ cancel: async (runId) => this.rpc.factory.cancel({ runId })
284
302
  };
285
303
  /**
286
304
  * Resolve a start/resume envelope into the terminal envelope callers expect.
@@ -291,7 +309,7 @@ class CopilotSession {
291
309
  */
292
310
  settleFactoryRun(envelope) {
293
311
  if (isFactoryRunTerminal(envelope.status)) {
294
- return Promise.resolve(toPublicFactoryRunResult(envelope));
312
+ return Promise.resolve(envelope);
295
313
  }
296
314
  return this.waitForFactoryRun(envelope.runId);
297
315
  }
@@ -345,7 +363,7 @@ class CopilotSession {
345
363
  rereadRequested = false;
346
364
  const envelope = await this.rpc.factory.getRun({ runId });
347
365
  if (isFactoryRunTerminal(envelope.status)) {
348
- finish(() => resolve(toPublicFactoryRunResult(envelope)));
366
+ finish(() => resolve(envelope));
349
367
  return;
350
368
  }
351
369
  } while (rereadRequested && !settled);
@@ -945,16 +963,16 @@ class CopilotSession {
945
963
  },
946
964
  agent: async (prompt, options = {}) => {
947
965
  await progress.flush();
966
+ const opts = {};
967
+ for (const key of FACTORY_AGENT_OPTION_KEYS) {
968
+ copyDefinedFactoryAgentOption(options, opts, key);
969
+ }
948
970
  const response = await awaitFactoryOperation(
949
971
  () => self.rpc.factory.agent({
950
972
  factoryRunId: params.runId,
951
973
  executionToken: params.executionToken,
952
974
  prompt,
953
- opts: {
954
- label: options.label,
955
- schema: options.schema,
956
- model: options.model
957
- }
975
+ opts
958
976
  }),
959
977
  controller.signal
960
978
  );
@@ -1002,7 +1020,14 @@ class CopilotSession {
1002
1020
  throw new Error("nested factories are not supported");
1003
1021
  }
1004
1022
  };
1005
- const result = await definition.run(context);
1023
+ const execution = { active: true };
1024
+ const result = await factoryExecutionStore.run(execution, async () => {
1025
+ try {
1026
+ return await definition.run(context);
1027
+ } finally {
1028
+ execution.active = false;
1029
+ }
1030
+ });
1006
1031
  if (result === void 0) {
1007
1032
  return {};
1008
1033
  }
package/dist/types.d.ts CHANGED
@@ -6,6 +6,7 @@ import type { SessionFsProvider } from "./sessionFsProvider.js";
6
6
  import type { CopilotRequestHandler } from "./copilotRequestHandler.js";
7
7
  import type { PermissionRequest as GeneratedPermissionRequest, PermissionRequestedData as GeneratedPermissionRequestedData, PermissionRequestedEvent as GeneratedPermissionRequestedEvent, ReasoningSummary, SessionLimitsConfig, SessionEvent as GeneratedSessionEvent } from "./generated/session-events.js";
8
8
  import type { CopilotSession } from "./session.js";
9
+ import type { JsonValue } from "./factory.js";
9
10
  import type { GitHubTelemetryNotification, ModelBillingTokenPrices, OpenCanvasInstance, RemoteSessionMode, CurrentToolMetadata } from "./generated/rpc.js";
10
11
  import type { ToolSet } from "./toolSet.js";
11
12
  export type { RemoteSessionMode } from "./generated/rpc.js";
@@ -355,7 +356,7 @@ export type ToolBinaryResult = {
355
356
  type: "image" | "resource";
356
357
  description?: string;
357
358
  };
358
- export type ToolTelemetry = Record<string, Record<string, unknown> | undefined>;
359
+ export type ToolTelemetry = Record<string, Record<string, JsonValue> | undefined>;
359
360
  export type ToolResultObject = {
360
361
  textResultForLlm: string;
361
362
  binaryResultsForLlm?: ToolBinaryResult[];
@@ -484,6 +485,17 @@ export interface Tool<TArgs = unknown> {
484
485
  * Unknown keys are preserved and round-tripped untouched.
485
486
  */
486
487
  metadata?: Record<string, unknown>;
488
+ /**
489
+ * When true, a successful call to this tool ends the agent turn: the runtime's
490
+ * tool phase halts instead of feeding the tool result back to the model for
491
+ * another round. A failed call (for example input validation) leaves the loop
492
+ * running so the model can read the error and retry.
493
+ *
494
+ * Use this for tools whose whole purpose is to terminate the turn, such as a
495
+ * context clear that replaces the conversation the model would otherwise
496
+ * continue from.
497
+ */
498
+ isTerminal?: boolean;
487
499
  }
488
500
  /**
489
501
  * Helper to define a tool with Zod schema and get type inference for the handler.
@@ -497,6 +509,7 @@ export declare function defineTool<T = unknown>(name: string, config: {
497
509
  skipPermission?: boolean;
498
510
  defer?: "auto" | "never";
499
511
  metadata?: Record<string, unknown>;
512
+ isTerminal?: boolean;
500
513
  }): Tool<T>;
501
514
  /**
502
515
  * SDK-supplied override for the runtime's built-in tool-search behavior.
@@ -1682,6 +1695,43 @@ export interface GitHubMcpToolConfig {
1682
1695
  enableInsidersMode?: boolean;
1683
1696
  disableFormDeferral?: boolean;
1684
1697
  }
1698
+ /**
1699
+ * Permissions-only managed policy injected by the host via
1700
+ * {@link SessionConfigBase.managedSettings}.
1701
+ *
1702
+ * Rule strings use the same vocabulary the runtime accepts for fetched managed
1703
+ * policy (e.g. `"Read(**)"`, `"Shell(git push *)"`); malformed rules are
1704
+ * rejected at session creation.
1705
+ */
1706
+ export interface ManagedSettingsPermissions {
1707
+ /**
1708
+ * When set to `"disable"`, bypass-permissions ("yolo") mode is turned off
1709
+ * for the session. This is deny-wins: it cannot be re-enabled by any other
1710
+ * layer.
1711
+ */
1712
+ disableBypassPermissionsMode?: "disable";
1713
+ /** Operations that must always be denied. Unioned across managed layers. */
1714
+ deny?: string[];
1715
+ /**
1716
+ * Operations that must prompt for approval. Unioned across managed layers.
1717
+ */
1718
+ ask?: string[];
1719
+ /**
1720
+ * Operations permitted without prompting. Every declared `allow` list
1721
+ * (across managed layers) must admit an operation for it to be allowed.
1722
+ */
1723
+ allow?: string[];
1724
+ }
1725
+ /**
1726
+ * Host-injected enterprise managed settings. The first supported contract is
1727
+ * permissions-only; unknown sibling keys are rejected by the runtime.
1728
+ *
1729
+ * @see {@link SessionConfigBase.managedSettings}
1730
+ */
1731
+ export interface ManagedSettings {
1732
+ /** Managed permission policy for the session. */
1733
+ permissions?: ManagedSettingsPermissions;
1734
+ }
1685
1735
  /**
1686
1736
  * Shared configuration fields used by both {@link SessionConfig} (for
1687
1737
  * creating a new session) and {@link ResumeSessionConfig} (for resuming
@@ -1888,6 +1938,13 @@ export interface SessionConfigBase {
1888
1938
  * @experimental
1889
1939
  */
1890
1940
  enableCitations?: boolean;
1941
+ /**
1942
+ * Opt in to capturing file changes for session rewind and cumulative session
1943
+ * diff. On create, capture starts with the first turn. On resume, this can
1944
+ * enable tracking only when the session still has a valid baseline; it cannot
1945
+ * reconstruct changes from earlier untracked turns.
1946
+ */
1947
+ enableFileChangeTracking?: boolean;
1891
1948
  /**
1892
1949
  * Limits applied to this session's current accounting window.
1893
1950
  *
@@ -2118,6 +2175,30 @@ export interface SessionConfigBase {
2118
2175
  * if omitted, the runtime is expected to reject session creation (fail-closed).
2119
2176
  */
2120
2177
  enableManagedSettings?: boolean;
2178
+ /**
2179
+ * Host-injected enterprise managed settings for this session.
2180
+ *
2181
+ * Unlike {@link SessionConfigBase.enableManagedSettings} — which asks the
2182
+ * runtime to *self-fetch* account/org and device policy — this field lets
2183
+ * the host supply the managed policy directly. The runtime validates it
2184
+ * with the same managed-permission parser it uses for fetched policy and
2185
+ * composes it restrictively with any self-fetched (server) and
2186
+ * device-managed (MDM) layers: `deny`/`ask` rules are unioned, every
2187
+ * declared `allow` list must admit an operation, and
2188
+ * `disableBypassPermissionsMode: "disable"` is deny-wins.
2189
+ *
2190
+ * This is startup-only. It is **not** persisted: it must be re-supplied on
2191
+ * {@link CopilotClient.resumeSession | resume}, where it replaces the prior
2192
+ * injected layer (omitting it clears the layer, so warm and cold resume
2193
+ * behave identically). It may be combined with `enableManagedSettings`;
2194
+ * when both are supplied the injected, server, and device restrictions all
2195
+ * apply.
2196
+ *
2197
+ * Requires a Copilot runtime whose RPC schema includes `managedSettings`.
2198
+ * Older runtimes may ignore this additive field, so hosts must not rely on
2199
+ * injected policy until they ship a compatible runtime.
2200
+ */
2201
+ managedSettings?: ManagedSettings;
2121
2202
  /**
2122
2203
  * When true, skips embedding-based retrieval for this session.
2123
2204
  * Use in multitenant deployments to prevent cross-session information leakage
package/docs/factories.md CHANGED
@@ -53,7 +53,7 @@ The `run()` context provides:
53
53
 
54
54
  - `ctx.runId`: Stable ID reused across resumed attempts.
55
55
  - `ctx.args`: Invocation arguments, forwarded verbatim. When the caller omits `args`, this is `{}` rather than `undefined`.
56
- - `ctx.agent(prompt, options?)`: Runs one factory-owned subagent. Options are exactly `label`, `schema`, and `model`. See [Subagent calls](#subagent-calls).
56
+ - `ctx.agent(prompt, options?)`: Runs one factory-owned subagent. Options are exactly `label`, `schema`, `model`, `agent`, `reasoningEffort`, and `contextTier`. See [Subagent calls](#subagent-calls).
57
57
  - `ctx.parallel(thunks)`: Runs thunks concurrently and awaits all of them (a barrier). A thunk that throws becomes `null` in the result array, so one failed item does not lose the rest. Cancellation and hard runtime failures (`ResponseError`, `ConnectionError`) are the exception — those propagate and reject the whole call, because they mean the run itself is in trouble rather than one item having failed. Handle them at run level; do not assume every failure arrives as a `null`. Rejects above 4096 items.
58
58
  - `ctx.pipeline(items, ...stages)`: Flows each item through every stage without a barrier between stages, so one item can be in a later stage while another is still in an earlier one. Each stage is called as `(previous, item, index)`, where `previous` is the prior stage's result and `item` is the original input. A stage that throws drops that item to `null` and skips its remaining stages, with the same exception for cancellation and hard runtime failures. Rejects above 4096 items.
59
59
  - `ctx.phase(title)`: Starts a named progress phase. This sets a single run-global value, so calling it from inside concurrent `parallel`/`pipeline` stages races. Call it at run-level transitions and distinguish concurrent work by `label` instead.
@@ -61,7 +61,7 @@ The `run()` context provides:
61
61
  - `ctx.step(key, producer, options?)`: Journals the producer's JSON result under a stable key so a resume replays it without re-running the producer. A journaled (default) producer must return a JSON-serializable value; `undefined` or a non-JSON value is rejected. Pass `{ volatile: true }` to bypass the journal and run the producer every time.
62
62
 
63
63
  The key is the *sole* identity: neither the producer body nor its inputs contribute to it. A resume replays the cached value for a matching key even if the producer has since changed, so version the key (`"scan-v2"`) whenever its inputs or meaning change. Journaled producers are best-effort at-least-once and may run again across crashes or concurrent same-key callers, so keep side effects idempotent.
64
- - `ctx.session`: The full session returned by `joinSession`.
64
+ - `ctx.session`: The session returned by `joinSession`. It refuses calls that start or resume a factory run. Call `extensions_manage` with `operation: "guide"` to read more about the session APIs.
65
65
  - `ctx.signal`: Cooperative cancellation signal for extension work and subprocesses.
66
66
  - `ctx.factory(...)`: Always rejects because nested factories are not supported.
67
67
 
@@ -156,7 +156,7 @@ session.factory.resume(
156
156
  ): Promise<FactoryRunResult>;
157
157
  ```
158
158
 
159
- Both resolve with the run envelope (`FactoryRunResult`) for **every** outcome — `completed`, `error`, `halted`, and `cancelled` alike. Inspect `status` and read `result` only when the run completed; a limit breach carries a typed `failure`. A declined fresh run is not a pre-execution failure: the run row already exists by the time the prompt is answered, so it resolves with a terminal `cancelled` envelope carrying the run ID. Only failures that occur *before* a run exists reject: an unknown factory name or an already-active session. Pre-execution resume failures, including a declined reapproval, throw `FactoryResumeError`, whose `code` is one of `not_found`, `non_resumable`, `already_active`, `reapproval_declined`, or `no_approval_provider`.
159
+ Both resolve with the run envelope (`FactoryRunResult`) for **every** outcome — `completed`, `error`, `halted`, and `cancelled` alike. Inspect `status` and read `result` only when the run completed; a limit breach carries a typed `failure`. SDK-initiated `run` and `resume` do not request permission, so they have no declined outcome. The model's `run_factory` tool requests permission before the durable row exists; declining it creates no run row. An SDK-initiated run is refused only when the session already has its maximum number of active top-level runs. Pre-execution resume failures throw `FactoryResumeError`, whose `code` is one of `not_found`, `non_resumable`, `already_active`, `factory_already_running`, `factory_limits_invalid`, `factory_session_disposed`, `factory_storage_unavailable`, or `factory_storage_corrupt`.
160
160
 
161
161
  An agent that no longer has a prior run's ID in context can recover it with `factories_manage` and `operation: "runs"`, which lists the session's factory runs with their IDs and statuses. This matters for resume: a run that reached a limit keeps its journal, so resuming it replays completed work for free, while restarting it from scratch pays for that work twice.
162
162
 
@@ -210,7 +210,7 @@ const page = await session.factory.getRunProgress(runId, {
210
210
  });
211
211
  ```
212
212
 
213
- - `listRuns()` returns summaries in durable creation order.
213
+ - `listRuns()` returns the newest default page of this session's durable factory runs.
214
214
  - `getRunDetail(runId)` returns phases, prompt-safe agent summaries, and the latest progress page.
215
215
  - `getRunProgress(runId, options?)` pages progress forward, backward, by phase, or from the latest tail.
216
216
 
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "git",
5
5
  "url": "https://github.com/github/copilot-sdk.git"
6
6
  },
7
- "version": "1.0.9",
7
+ "version": "1.0.11-preview.1",
8
8
  "description": "TypeScript SDK for programmatic control of GitHub Copilot CLI via JSON-RPC",
9
9
  "main": "./dist/cjs/index.js",
10
10
  "types": "./dist/index.d.ts",
@@ -56,7 +56,7 @@
56
56
  "author": "GitHub",
57
57
  "license": "MIT",
58
58
  "dependencies": {
59
- "@github/copilot": "^1.0.78",
59
+ "@github/copilot": "^1.0.79",
60
60
  "koffi": "^3.1.0",
61
61
  "vscode-jsonrpc": "^8.2.1",
62
62
  "zod": "^4.3.6"