@lucid-agents/taskmarket 1.20.2 → 1.21.0

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.
@@ -7,7 +7,7 @@ import {
7
7
  recordSessionEnd,
8
8
  recordSessionStart,
9
9
  uploadPlan
10
- } from "./chunk-2AED73YN.js";
10
+ } from "./chunk-UD5Z752S.js";
11
11
  import {
12
12
  TaskOperationError,
13
13
  authorizeUploads,
@@ -28,7 +28,7 @@ import {
28
28
  submitUploadedArtifacts,
29
29
  taskAccessGrantHeaders,
30
30
  taskAwardsOf
31
- } from "./chunk-TO6BB6N3.js";
31
+ } from "./chunk-5N25NATR.js";
32
32
  import {
33
33
  terminalPrompter
34
34
  } from "./chunk-ZIC2YCLV.js";
@@ -68,7 +68,7 @@ import {
68
68
  sha256Hex,
69
69
  specHash,
70
70
  taskStage
71
- } from "./chunk-F7ENDHGG.js";
71
+ } from "./chunk-ZZTCFKR3.js";
72
72
  import {
73
73
  BrokerConnector,
74
74
  OutputLimitError,
@@ -80,7 +80,7 @@ import {
80
80
  serveSandboxChannel,
81
81
  withFileLock,
82
82
  writeAtomic
83
- } from "./chunk-6LKXMM4U.js";
83
+ } from "./chunk-ZU33L5X5.js";
84
84
  import {
85
85
  applyComputeCreditSetup,
86
86
  atomicToUsd,
@@ -90,7 +90,7 @@ import {
90
90
  planComputeCreditSetup,
91
91
  readComputeCreditBalance,
92
92
  usdToAtomic
93
- } from "./chunk-WUSEHQHR.js";
93
+ } from "./chunk-TAMXCFLT.js";
94
94
  import {
95
95
  MAX_RETIRED_RAILS,
96
96
  WORKSPACE_CONSENT_VERSION,
@@ -110,7 +110,7 @@ import {
110
110
  routeOfCatalogRow,
111
111
  saveAgentPolicy,
112
112
  settledRetiredRails
113
- } from "./chunk-5D4HN65V.js";
113
+ } from "./chunk-OCQKVV4A.js";
114
114
  import {
115
115
  BASE_CHANNEL_RAIL_ID,
116
116
  createBaseChannelPayer,
@@ -124,7 +124,7 @@ import {
124
124
  rpcUrlForNetwork,
125
125
  saveX402Policy,
126
126
  x402SpendWindowAvailability
127
- } from "./chunk-AT2VG32A.js";
127
+ } from "./chunk-HE2N4GEO.js";
128
128
  import {
129
129
  IntentStatusResponseSchema,
130
130
  NetworkChainInfoSchema,
@@ -134,9 +134,10 @@ import {
134
134
  isRecordingPendingResponse,
135
135
  keystoreExists,
136
136
  loadKeystore,
137
+ piCodingAgentTools,
137
138
  selectedNetwork,
138
139
  taskmarketChain
139
- } from "./chunk-2JTB3UFM.js";
140
+ } from "./chunk-3IIVNF5F.js";
140
141
  import {
141
142
  AGENT_VIEW_VERSION,
142
143
  describeFailureDetail,
@@ -2459,6 +2460,15 @@ function createMarketActions(options) {
2459
2460
  };
2460
2461
  }
2461
2462
 
2463
+ // src/lib/agent/shared-tools-ui.ts
2464
+ var CONTROLLER_TOOLS = /* @__PURE__ */ new Set();
2465
+ function sharedToolNames(answered = CONTROLLER_TOOLS) {
2466
+ return piCodingAgentTools({
2467
+ transport: { answers: (name) => answered.has(name), call: async () => ({ success: false }) },
2468
+ sessionId: "names"
2469
+ }).map((tool) => tool.name);
2470
+ }
2471
+
2462
2472
  // src/lib/agent/deliverable.ts
2463
2473
  import { randomBytes } from "crypto";
2464
2474
  import path5 from "path";
@@ -4454,6 +4464,14 @@ function serveInferenceChannel(channel, transport, options = {}) {
4454
4464
  );
4455
4465
  return;
4456
4466
  }
4467
+ case "tool": {
4468
+ const { name, callId } = request;
4469
+ void answer(
4470
+ request.id,
4471
+ (!options.views?.tools ? Promise.reject(new XgateError("unsupported_by_transport")) : typeof name !== "string" || !CODE.test(name) ? Promise.reject(new XgateError("unknown_tool")) : typeof callId !== "string" || callId.length === 0 || callId.length > 200 ? Promise.reject(new XgateError("invalid_call_id")) : options.views.tools.call(name, callId, request.args).catch(stableCode("tool_unavailable"))).then((value) => ({ value }))
4472
+ );
4473
+ return;
4474
+ }
4457
4475
  case "market_action": {
4458
4476
  const actions = options.views?.marketAction;
4459
4477
  const { phase, tool, actionId } = request;
@@ -6557,8 +6575,13 @@ async function runSession(options, signals, keystore, workspace) {
6557
6575
  guardExtensionPath: piExtensionPath("lockdown"),
6558
6576
  // The marketplace tools the xgate extension registers: the read-only ones (market-ui.ts),
6559
6577
  // the claim, submit and stop requests (market-action-ui.ts) and the task workspace and
6560
- // validation (task-run-ui.ts).
6561
- marketplaceTools: [...MARKET_TOOL_NAMES, ...MARKET_ACTION_TOOL_NAMES, ...TASK_RUN_TOOL_NAMES],
6578
+ // validation (task-run-ui.ts), and the shared tools the controller answers (shared-tools-ui.ts).
6579
+ marketplaceTools: [
6580
+ ...MARKET_TOOL_NAMES,
6581
+ ...MARKET_ACTION_TOOL_NAMES,
6582
+ ...TASK_RUN_TOOL_NAMES,
6583
+ ...sharedToolNames()
6584
+ ],
6562
6585
  // The Pi bundled with this CLI, run with the current Node.
6563
6586
  piCliPath: options.pi.cliPath,
6564
6587
  piPackageDir: options.pi.packageDir,
@@ -22234,7 +22234,7 @@ var SshSandboxBackend = class {
22234
22234
  }
22235
22235
  async ensure(signal) {
22236
22236
  if (this.conn && this.files && !this.conn.closed) {
22237
- this.touch();
22237
+ await this.keepAlive();
22238
22238
  return { conn: this.conn, files: this.files };
22239
22239
  }
22240
22240
  if (this.failure && Date.now() < this.failure.until)
@@ -22278,6 +22278,7 @@ var SshSandboxBackend = class {
22278
22278
  this.files = files;
22279
22279
  this.info = { label: `${this.user}@${endpoint.host} (${this.root})` };
22280
22280
  this.failure = void 0;
22281
+ this.lastHeartbeat = Date.now();
22281
22282
  this.log(`${this.dialed ? "Reconnected" : "Connected"} to the sandbox at ${endpoint.host}`);
22282
22283
  this.dialed = true;
22283
22284
  conn.onClose(() => {
@@ -22298,13 +22299,59 @@ var SshSandboxBackend = class {
22298
22299
  throw failure;
22299
22300
  }
22300
22301
  }
22301
- /** Tells the broker the sandbox is in use, at most once a minute, without waiting. */
22302
- touch() {
22302
+ get heartbeatIntervalMs() {
22303
+ return this.options.heartbeatIntervalMs ?? HEARTBEAT_INTERVAL_MS;
22304
+ }
22305
+ /**
22306
+ * Tells the broker the sandbox is in use, and waits for the answer. A refusal means the broker is
22307
+ * stopping the sandbox for inactivity, so the call fails instead of running on a machine that is
22308
+ * going away. Skipped when the last answer is under an interval old.
22309
+ */
22310
+ async keepAlive(force = false) {
22303
22311
  const heartbeat = this.options.connector.heartbeat;
22304
- if (!heartbeat || Date.now() - this.lastHeartbeat < HEARTBEAT_INTERVAL_MS)
22312
+ if (!heartbeat || !force && Date.now() - this.lastHeartbeat < this.heartbeatIntervalMs)
22305
22313
  return;
22314
+ try {
22315
+ await heartbeat.call(this.options.connector);
22316
+ } catch (error) {
22317
+ const message = `the sandbox could not be kept alive: ${error.message}`;
22318
+ this.log(message);
22319
+ throw new SandboxUnavailableError(message, { cause: error });
22320
+ }
22306
22321
  this.lastHeartbeat = Date.now();
22307
- heartbeat.call(this.options.connector).catch((error) => this.log(`Heartbeat failed: ${error.message}`));
22322
+ }
22323
+ /**
22324
+ * Runs a command while heartbeating every interval, so the idle stop never takes the sandbox
22325
+ * under it. A refused heartbeat kills the command and fails the call with the refusal.
22326
+ */
22327
+ async leased(signal, run) {
22328
+ const controller = new AbortController();
22329
+ const onAbort = () => controller.abort();
22330
+ if (signal?.aborted)
22331
+ controller.abort();
22332
+ else
22333
+ signal?.addEventListener("abort", onAbort, { once: true });
22334
+ let refusal;
22335
+ let beating = false;
22336
+ const timer = setInterval(() => {
22337
+ if (beating)
22338
+ return;
22339
+ beating = true;
22340
+ this.keepAlive(true).catch((error) => {
22341
+ refusal = error;
22342
+ controller.abort();
22343
+ }).finally(() => {
22344
+ beating = false;
22345
+ });
22346
+ }, this.heartbeatIntervalMs);
22347
+ try {
22348
+ return await run(controller.signal);
22349
+ } catch (error) {
22350
+ throw refusal ?? error;
22351
+ } finally {
22352
+ clearInterval(timer);
22353
+ signal?.removeEventListener("abort", onAbort);
22354
+ }
22308
22355
  }
22309
22356
  async close() {
22310
22357
  this.generation++;
@@ -22341,18 +22388,18 @@ var SshSandboxBackend = class {
22341
22388
  const unit = this.nextUnit();
22342
22389
  const stderr = [];
22343
22390
  let stderrBytes = 0;
22344
- const { exitCode } = await conn.stream(wrapInScope(command, this.root, unit), {
22391
+ const { exitCode } = await this.leased(signal, (leaseSignal) => conn.stream(wrapInScope(command, this.root, unit), {
22345
22392
  onData: onStdout,
22346
22393
  onStderr: (chunk) => {
22347
22394
  stderrBytes += chunk.length;
22348
22395
  if (stderrBytes <= 16 * 1024)
22349
22396
  stderr.push(chunk);
22350
22397
  },
22351
- signal,
22398
+ signal: leaseSignal,
22352
22399
  maxStreamBytes: this.maxStreamBytes,
22353
22400
  killCommand: stopScopeCommand(unit),
22354
22401
  untilClose: true
22355
- });
22402
+ }));
22356
22403
  return { exitCode, stderr: Buffer.concat(stderr).toString("utf8").trim().slice(0, 2e3) };
22357
22404
  }
22358
22405
  async glob(dir, pattern, options) {
@@ -22479,13 +22526,13 @@ var SshSandboxBackend = class {
22479
22526
  async exec(command, cwd, options) {
22480
22527
  const { conn } = await this.ensure(options.signal);
22481
22528
  const unit = this.nextUnit();
22482
- return conn.stream(wrapInScope(command, cwd, unit), {
22529
+ return this.leased(options.signal, (signal) => conn.stream(wrapInScope(command, cwd, unit), {
22483
22530
  onData: options.onData,
22484
- signal: options.signal,
22531
+ signal,
22485
22532
  timeout: options.timeout,
22486
22533
  maxStreamBytes: this.maxStreamBytes,
22487
22534
  killCommand: stopScopeCommand(unit)
22488
- });
22535
+ }));
22489
22536
  }
22490
22537
  };
22491
22538