@cotal-ai/manager 0.116.0 → 0.116.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/dist/manager.js CHANGED
@@ -17,6 +17,7 @@ import { authorizeLaunch, authorizeNamedControl } from "./authorize.js";
17
17
  import { controlShutdown } from "./control-shutdown.js";
18
18
  import { RunHosting } from "./run-hosting.js";
19
19
  import { enrolledAcl } from "./remote-authority.js";
20
+ import { rejectionText } from "./rejection-text.js";
20
21
  import { controlSession } from "./control-session.js";
21
22
  import { MAX_AGENTS, parseResumeCommitArgs, parseResumeControlArgs, parseResumeFinalizeArgs, readSeatRecord, removeSeatRecord, writeSeatRecord } from "./resume.js";
22
23
  import { TranscriptReceiver } from "./transcript-receive.js";
@@ -159,19 +160,6 @@ function resumeOwner(entry) {
159
160
  * dash) — what {@link Manager.psOwnerFilter} returns for an unparseable caller so a malformed
160
161
  * principal fail-closes to an empty `ps` instead of an unbounded one. */
161
162
  const NO_OWNER_MATCHES = "-no-owner-";
162
- /** A caught value's text for a refusal or a log line. A host-supplied store, runtime or callback may
163
- * reject with any value, including `null` or one whose `message` is a Symbol or whose `message`
164
- * getter or `toString` throws. A handler that throws while building its text skips the work after
165
- * it, and on a detached chain ends the manager with an unhandled rejection, so the coercion to text
166
- * runs inside the guard. */
167
- export function rejectionText(e) {
168
- try {
169
- return String(e?.message ?? e);
170
- }
171
- catch {
172
- return "an unreadable rejection";
173
- }
174
- }
175
163
  /** A caught value as a lifecycle envelope, or undefined when it is not one. A thrown Proxy can throw
176
164
  * from `instanceof` itself, so the test runs inside the guard. */
177
165
  function caughtEnvelope(e) {
@@ -2629,13 +2617,23 @@ export class Manager {
2629
2617
  }
2630
2618
  }
2631
2619
  /** Stop the v0.4 service-endpoint serve loop (drain subscriptions, await in-flight handlers)
2632
- * and drop its dedicated connection. Best-effort so a graceful stop completes its remaining
2633
- * teardown even when the broker is gone. */
2620
+ * and drop its dedicated connection, or only close the connection while its transport is down.
2621
+ * Best-effort so a graceful stop completes its remaining teardown even when the broker is gone. */
2634
2622
  async stopServiceServe() {
2635
2623
  const s = this.serviceServe;
2636
2624
  if (!s)
2637
2625
  return;
2638
2626
  this.serviceServe = undefined;
2627
+ // A drain on a reconnecting connection does not fail: it waits for the reconnect, so the stop
2628
+ // would last as long as the outage. A broker this connection cannot reach has nothing left to
2629
+ // flush, so the retired connection is closed instead.
2630
+ if (this.serveTransportDown.has(s.nc)) {
2631
+ try {
2632
+ await s.nc.close();
2633
+ }
2634
+ catch { /* best effort */ }
2635
+ return;
2636
+ }
2639
2637
  try {
2640
2638
  await s.handle.stop();
2641
2639
  }
@@ -5435,13 +5433,22 @@ export class Manager {
5435
5433
  // One read of the cotal config per spawn: the delegated refusal, the model policy and the launch
5436
5434
  // fields below all take this value, so the awaits between the policy check and the launch cannot
5437
5435
  // admit the seat under one file and launch it with another's servers and env allowlist. Read per
5438
- // spawn, so a policy edit reaches the next spawn without a restart.
5436
+ // spawn, so a policy edit reaches the next spawn without a restart. The launch fields are resolved
5437
+ // here too, before the spawn is accepted, so a sharing selection that names an undeclared server
5438
+ // is refused while there is nothing to roll back.
5439
5439
  let cotalConfig;
5440
+ let hostFields;
5440
5441
  try {
5441
5442
  cotalConfig = loadCotalConfig(this.workspaceRoot);
5443
+ hostFields = this.hostLaunchFields(cotalConfig, agent, opts.shareTools, harness);
5442
5444
  }
5443
5445
  catch (e) {
5444
- return { ok: false, error: rejectionText(e) };
5446
+ const error = rejectionText(e);
5447
+ // An input refusal here precedes allocation and onAccepted, like a connector refusal above:
5448
+ // a hosted run proves it not-executed instead of binding a goal to a spawn that never starts.
5449
+ if (hooks?.onAccepted)
5450
+ throw new EpEnvelopeError("failed-precondition", error, undefined, "not-executed");
5451
+ return { ok: false, error };
5445
5452
  }
5446
5453
  // A delegated seat starts outside this host from values alone, so a choice only this host can
5447
5454
  // honour is refused here, before enrollment, and named rather than dropped.
@@ -5461,7 +5468,7 @@ export class Manager {
5461
5468
  return { ok: false, error: `${delegatedBy}, so it cannot reopen a session held on this host (continuity: exact)` };
5462
5469
  if (typeof opts.cwd === "string" && opts.cwd !== "")
5463
5470
  return { ok: false, error: `${delegatedBy}, so it cannot run in a directory on this host's filesystem (cwd); the seat runs in its runtime resource's own directory` };
5464
- const shared = Object.keys(connectorServers(cotalConfig, agent, opts.shareTools));
5471
+ const shared = Object.keys(hostFields.mcpServers);
5465
5472
  if (shared.length)
5466
5473
  return { ok: false, error: `${delegatedBy}, so it cannot share MCP servers that run on this host (${shared.join(", ")}); pass --share-tools none` };
5467
5474
  }
@@ -5897,7 +5904,7 @@ export class Manager {
5897
5904
  : undefined;
5898
5905
  const manifestSha256 = manifestPath ? this.fileDigest(manifestPath) : undefined;
5899
5906
  const launchOpts = {
5900
- ...this.hostLaunchFields(cotalConfig, agent, opts.shareTools, harness),
5907
+ ...hostFields,
5901
5908
  name,
5902
5909
  role,
5903
5910
  // User mode: the principal IS the identity (the endpoint derives card.id from owner+actor);