@orkestrel/agent 0.0.42 → 0.0.43

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.
@@ -5228,6 +5228,32 @@ function intersectFilters(parent, child) {
5228
5228
  };
5229
5229
  }
5230
5230
  /**
5231
+ * Resolves the workspace a run renders and acts on: the one `selected` names, else the registry's
5232
+ * active one, kept only while `allowed` admits its id.
5233
+ *
5234
+ * @remarks
5235
+ * A selection the registry does not hold resolves to `undefined` and never falls back to the active
5236
+ * workspace. `allowed` is three-way, as {@link import('../helpers.js').filterAllowList} reads it.
5237
+ * `AgentContext.build`, the caller an agent run hands each tool call, and a ledger's calibration
5238
+ * resolve through it, so what a request renders and what a call receives come from one rule.
5239
+ *
5240
+ * @param workspaces - The workspace registry to read
5241
+ * @param selected - The id a `switch` call or the `workspace` option selected, or `undefined`
5242
+ * @param allowed - The ceiling's `workspaces` list (`undefined` ⇒ every id, `[]` ⇒ none)
5243
+ * @returns The workspace, or `undefined` when none resolves
5244
+ *
5245
+ * @example
5246
+ * ```ts
5247
+ * import { resolveWorkspace } from '@orkestrel/agent'
5248
+ *
5249
+ * resolveWorkspace(agent.context.workspaces, 'returns', ['returns']) // the registered returns workspace
5250
+ * ```
5251
+ */
5252
+ function resolveWorkspace(workspaces, selected, allowed) {
5253
+ const workspace = selected === void 0 ? workspaces.active : workspaces.workspace(selected);
5254
+ return workspace !== void 0 && filterAllowList(allowed, [workspace.id], (id) => id).length > 0 ? workspace : void 0;
5255
+ }
5256
+ /**
5231
5257
  * Renders one index line for a memo or a skill — `- name: description`, the built-in `render` of
5232
5258
  * the memo and skill managers.
5233
5259
  *
@@ -6283,10 +6309,8 @@ var AgentContext = class {
6283
6309
  return skill === void 0 || skill.content.trim() === "" ? [] : [skill];
6284
6310
  }), renderSkillBody, void 0);
6285
6311
  if (bodies !== void 0) parts.push(bodies);
6286
- const selected = options?.disclosure?.workspace;
6287
- const workspace = selected === void 0 ? this.#workspaces.active : this.#workspaces.workspace(selected);
6288
- const shown = filterAllowList(scope.workspaces, workspace === void 0 ? [] : [workspace], (one) => one.id);
6289
- const files = filterAllowList(scope.files, shown.flatMap((one) => one.files()), (one) => one.path);
6312
+ const workspace = resolveWorkspace(this.#workspaces, options?.disclosure?.workspace, scope.workspaces);
6313
+ const files = filterAllowList(scope.files, workspace?.files() ?? [], (one) => one.path);
6290
6314
  const documented = renderSection(WORKSPACE_SECTION_HEADER, files.flatMap((file) => (0, _orkestrel_workspace.isTextContent)(file.content) ? [{
6291
6315
  path: file.path,
6292
6316
  language: file.content.language,
@@ -7253,6 +7277,35 @@ function isAuthorityDecision(value) {
7253
7277
  reason: _orkestrel_contract.isString
7254
7278
  }, ["reason"])(value);
7255
7279
  }
7280
+ /**
7281
+ * Checks whether a value is an agent caller: a plain record whose own keys are exactly `workspace`
7282
+ * and `files`, whose `workspace` is `undefined` or an object, and whose `files` is `undefined` or an
7283
+ * array of strings.
7284
+ *
7285
+ * @remarks
7286
+ * Total: each member is read once, and an omitted, inherited, or undeclared member, a throwing
7287
+ * accessor, and a revoked `Proxy` return false rather than throw. It checks the shape a caller
7288
+ * carries, never that `workspace` is a live workspace: a value that crossed a wire passes when it
7289
+ * holds that shape, so accept its `workspace` only as the very instance your own registry holds
7290
+ * under that id.
7291
+ *
7292
+ * @param value - The unknown caller candidate, such as `ToolContext.caller`
7293
+ * @returns True if the value satisfies the caller contract; false otherwise
7294
+ *
7295
+ * @example
7296
+ * ```ts
7297
+ * isAgentCaller({ workspace: undefined, files: undefined }) // true
7298
+ * isAgentCaller({ workspace: 'returns' }) // false
7299
+ * ```
7300
+ */
7301
+ function isAgentCaller(value) {
7302
+ return (0, _orkestrel_contract.holds)(() => {
7303
+ if (!(0, _orkestrel_contract.isRecord)(value) || !(0, _orkestrel_contract.matchesExactKeys)(value, ["workspace", "files"])) return false;
7304
+ const workspace = value.workspace;
7305
+ const files = value.files;
7306
+ return (workspace === void 0 || (0, _orkestrel_contract.isObject)(workspace)) && (files === void 0 || (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString)(files));
7307
+ });
7308
+ }
7256
7309
  // src/core/agents/helpers.ts
7257
7310
  /**
7258
7311
  * Projects an unknown value onto a fresh, exact `JSONValue` representation of an
@@ -8617,6 +8670,7 @@ function collectDisclosure(conversation, builtins) {
8617
8670
  * registers under a built-in's name is never read: `tool` and `execute` answer a built-in's name
8618
8671
  * from the run's manager and every other name from the host's. Each handler reads the ceiling when
8619
8672
  * it runs, the context scope intersected with the run scope at that moment, never a turn's copy.
8673
+ * The `caller` each call receives reads the ceiling the same way, as the call starts.
8620
8674
  */
8621
8675
  var AgentRun = class {
8622
8676
  #context;
@@ -8629,6 +8683,7 @@ var AgentRun = class {
8629
8683
  #ceiling = {};
8630
8684
  #shown = { skills: [] };
8631
8685
  #ids = [];
8686
+ #files = [];
8632
8687
  #advertised = [];
8633
8688
  #snapshot;
8634
8689
  #selection;
@@ -8712,6 +8767,31 @@ var AgentRun = class {
8712
8767
  return this.#advertised.some((definition) => definition.name === "switch");
8713
8768
  }
8714
8769
  /**
8770
+ * Returns what a tool call receives as `ToolContext.caller`: the workspace instance the working
8771
+ * head rendered, kept only while the ceiling at this moment admits its id, and that ceiling's
8772
+ * `files` list.
8773
+ *
8774
+ * @remarks
8775
+ * The instance is the one the head's build resolved, never the registry's `active` pointer at the
8776
+ * call, so a host's `switch` made while the provider answers reaches the next turn's head and not
8777
+ * this call. Each read of this getter reads the `workspaces` and `files` lists of the context
8778
+ * scope and the run scope one time each, and no other list, so a call reads them as it starts, a
8779
+ * narrowing made after the turn's decision reaches that call, and a turn still reads its `tools`
8780
+ * list once.
8781
+ *
8782
+ * @returns A fresh caller
8783
+ */
8784
+ get caller() {
8785
+ const outer = this.#context.scope;
8786
+ const inner = this.#scope;
8787
+ const rendered = this.#head?.rendered;
8788
+ const [workspace] = filterAllowList(intersectKeys(outer?.workspaces, inner?.workspaces), rendered === void 0 ? [] : [rendered], (one) => one.id);
8789
+ return {
8790
+ workspace,
8791
+ files: intersectKeys(outer?.files, inner?.files)
8792
+ };
8793
+ }
8794
+ /**
8715
8795
  * Returns the selection the working head rendered.
8716
8796
  *
8717
8797
  * @returns The selection, or undefined before the first build
@@ -8735,8 +8815,10 @@ var AgentRun = class {
8735
8815
  * `load`, `unload`, or `switch` is enabled, and only the records of an enabled built-in count.
8736
8816
  * Registered skills hold their tools back only while `load` is enabled. The view compared with
8737
8817
  * the working head's is the ceiling's `instructions`, `skills`, `memos`, `files`, and
8738
- * `workspaces` lists, the shown skills by id, the shown workspace, and whether `switch` is
8739
- * advertised.
8818
+ * `workspaces` lists, the shown skills by id, the shown workspace, the files that workspace
8819
+ * renders under the ceiling's `files` list, compared by identity, and whether `switch` is
8820
+ * advertised. An edit to a rendered file, by a tool or by the host, therefore makes the head
8821
+ * stale, and an edit to a file no build renders does not.
8740
8822
  *
8741
8823
  * @returns True if the working head was built with another view and must be rebuilt; false otherwise
8742
8824
  */
@@ -8751,13 +8833,19 @@ var AgentRun = class {
8751
8833
  this.#ceiling = ceiling;
8752
8834
  this.#shown = shown;
8753
8835
  this.#ids = shown.skills.flatMap((name) => this.#context.skills.skill(name)?.id ?? []);
8836
+ this.#files = filterAllowList(ceiling.files, resolveWorkspace(this.#context.workspaces, shown.workspace, ceiling.workspaces)?.files() ?? [], (file) => file.path);
8754
8837
  this.#advertised = filterAdvertisedTools(this.#candidates(ceiling), ceiling.tools, this.#builtins.has("load") ? this.#context.skills.skills() : [], shown.skills);
8755
8838
  const head = this.#head;
8756
- return head === void 0 || !matchesHeadLists(head.ceiling, ceiling) || head.ids.length !== this.#ids.length || head.ids.some((id, index) => id !== this.#ids[index]) || head.workspace !== shown.workspace || head.switchable !== this.switchable;
8839
+ return head === void 0 || !matchesHeadLists(head.ceiling, ceiling) || head.ids.length !== this.#ids.length || head.ids.some((id, index) => id !== this.#ids[index]) || head.workspace !== shown.workspace || head.switchable !== this.switchable || head.files.length !== this.#files.length || head.files.some((file, index) => file !== this.#files[index]);
8757
8840
  }
8758
8841
  /**
8759
- * Records what the working head was built with: the selection, the built array's length, and
8760
- * the view the last `observe` resolved.
8842
+ * Records what the working head was built with: the selection, the built array's length, the
8843
+ * view the last `observe` resolved, and the workspace instance the build rendered.
8844
+ *
8845
+ * @remarks
8846
+ * Call it right after the build: it resolves the rendered workspace from the inputs the build
8847
+ * read, the shown workspace and the context scope's `workspaces` list intersected with the
8848
+ * snapshot's.
8761
8849
  *
8762
8850
  * @param selection - The selection the build rendered
8763
8851
  * @param length - The length of the built array
@@ -8769,7 +8857,9 @@ var AgentRun = class {
8769
8857
  ceiling: this.#ceiling,
8770
8858
  ids: this.#ids,
8771
8859
  workspace: this.#shown.workspace,
8772
- switchable: this.switchable
8860
+ switchable: this.switchable,
8861
+ files: this.#files,
8862
+ rendered: resolveWorkspace(this.#context.workspaces, this.#shown.workspace, intersectKeys(this.#context.scope?.workspaces, this.#snapshot?.workspaces))
8773
8863
  };
8774
8864
  }
8775
8865
  /**
@@ -9674,7 +9764,10 @@ var Agent = class {
9674
9764
  const { allowed, denials } = authorization;
9675
9765
  const entries = [...allowed].sort(([left], [right]) => left - right);
9676
9766
  const started = entries.map(([, { call, tool }]) => {
9677
- if (!this.#changed(run, call, tool, advertised)) return run.execute(call, { signal });
9767
+ if (!this.#changed(run, call, tool, advertised)) return run.execute(call, {
9768
+ signal,
9769
+ caller: run.caller
9770
+ });
9678
9771
  this.#emitter.emit("deny", call, `${call.name} changed after its decision`, void 0);
9679
9772
  return denyCall(call, AUTHORITY_NOTES.denied);
9680
9773
  });
@@ -13675,8 +13768,7 @@ var Ledger = class {
13675
13768
  async #measureScale(head, bare, signal) {
13676
13769
  const rate = bare / estimateMessages(head);
13677
13770
  const workspace = this.#show(intersectFilters(this.#agent.context.scope, void 0)).workspace;
13678
- const workspaces = this.#agent.context.workspaces;
13679
- const files = ((workspace === void 0 ? workspaces.active : workspaces.workspace(workspace))?.files() ?? []).flatMap((file) => (0, _orkestrel_workspace.isTextContent)(file.content) ? [file.path] : []);
13771
+ const files = (resolveWorkspace(this.#agent.context.workspaces, workspace, void 0)?.files() ?? []).flatMap((file) => (0, _orkestrel_workspace.isTextContent)(file.content) ? [file.path] : []);
13680
13772
  const shown = this.#agent.context.build({
13681
13773
  selection: {
13682
13774
  messages: [],
@@ -14960,6 +15052,7 @@ exports.instructionInputShape = instructionInputShape;
14960
15052
  exports.intersectFilters = intersectFilters;
14961
15053
  exports.intersectKeys = intersectKeys;
14962
15054
  exports.isAgentBuiltin = isAgentBuiltin;
15055
+ exports.isAgentCaller = isAgentCaller;
14963
15056
  exports.isAgentError = isAgentError;
14964
15057
  exports.isAgentJobError = isAgentJobError;
14965
15058
  exports.isAuthorityDecision = isAuthorityDecision;
@@ -15092,6 +15185,7 @@ exports.requireWorkspace = requireWorkspace;
15092
15185
  exports.resolveDecision = resolveDecision;
15093
15186
  exports.resolveDisclosure = resolveDisclosure;
15094
15187
  exports.resolvePredict = resolvePredict;
15188
+ exports.resolveWorkspace = resolveWorkspace;
15095
15189
  exports.sanitizeToken = sanitizeToken;
15096
15190
  exports.sanitizeUsage = sanitizeUsage;
15097
15191
  exports.scanAmendments = scanAmendments;