@whittlelabs/sifter 0.9.0 → 0.11.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.
Files changed (3) hide show
  1. package/bin.js +198 -18
  2. package/bin.js.map +4 -4
  3. package/package.json +1 -1
package/bin.js CHANGED
@@ -15450,10 +15450,11 @@ var require_client = __commonJS({
15450
15450
  if (options?.sort)
15451
15451
  params.set("sort", options.sort);
15452
15452
  const qs = params.toString();
15453
- const { jobs, total } = await this.request("GET", `/api/my/jobs${qs ? `?${qs}` : ""}`);
15453
+ const { jobs, total, directives } = await this.request("GET", `/api/my/jobs${qs ? `?${qs}` : ""}`);
15454
15454
  return {
15455
15455
  data: jobs,
15456
- pagination: { total, limit: options?.limit ?? 25, offset: options?.offset ?? 0 }
15456
+ pagination: { total, limit: options?.limit ?? 25, offset: options?.offset ?? 0 },
15457
+ directives: directives ?? null
15457
15458
  };
15458
15459
  }
15459
15460
  // ── HTTP plumbing ────────────────────────────────────────────────
@@ -15547,11 +15548,22 @@ var require_subscribe = __commonJS({
15547
15548
  return;
15548
15549
  }
15549
15550
  try {
15550
- const { data } = await opts.jobsClient.listDispatchableJobs({
15551
+ const { data, directives } = await opts.jobsClient.listDispatchableJobs({
15551
15552
  pools: resolvedPoolIds,
15552
15553
  limit: maxConcurrency - inflight,
15553
15554
  sort: "priority:desc,queued_at:asc"
15554
15555
  });
15556
+ if (directives && Object.keys(directives).length > 0 && opts.onDirectives) {
15557
+ try {
15558
+ await opts.onDirectives(directives);
15559
+ } catch (err) {
15560
+ logger.error("onDirectives handler failed", {
15561
+ error: err instanceof Error ? err.message : String(err)
15562
+ });
15563
+ }
15564
+ }
15565
+ if (stopped)
15566
+ return;
15555
15567
  for (const job of data) {
15556
15568
  if (inflight >= maxConcurrency)
15557
15569
  break;
@@ -15644,6 +15656,78 @@ var require_dist2 = __commonJS({
15644
15656
  }
15645
15657
  });
15646
15658
 
15659
+ // ../../packages/shuttle/dist/lifecycle/self-update.js
15660
+ var require_self_update = __commonJS({
15661
+ "../../packages/shuttle/dist/lifecycle/self-update.js"(exports2) {
15662
+ "use strict";
15663
+ Object.defineProperty(exports2, "__esModule", { value: true });
15664
+ exports2.readUpgradeDirective = readUpgradeDirective;
15665
+ exports2.performSelfUpdate = performSelfUpdate;
15666
+ var child_process_1 = require("child_process");
15667
+ var version_check_1 = require_version_check();
15668
+ function readUpgradeDirective(directives, currentVersion, attemptedVersions) {
15669
+ const raw = directives.upgrade;
15670
+ if (!raw || typeof raw !== "object")
15671
+ return null;
15672
+ const requested = raw.requested_version;
15673
+ if (typeof requested !== "string" || requested.length === 0)
15674
+ return null;
15675
+ if (attemptedVersions.has(requested))
15676
+ return null;
15677
+ if ((0, version_check_1.compareSemver)(currentVersion, requested) >= 0)
15678
+ return null;
15679
+ return {
15680
+ requested_version: requested,
15681
+ requested_at: raw.requested_at
15682
+ };
15683
+ }
15684
+ async function performSelfUpdate(opts) {
15685
+ const { packageName, directive, logger, hooks } = opts;
15686
+ const spec = `${packageName}@${directive.requested_version}`;
15687
+ const install = hooks.install ?? defaultInstall;
15688
+ const respawn = hooks.respawn ?? defaultRespawn;
15689
+ logger.info("upgrade directive received; draining claim loop", {
15690
+ requestedVersion: directive.requested_version
15691
+ });
15692
+ await hooks.stopLoop();
15693
+ try {
15694
+ await install(spec);
15695
+ } catch (err) {
15696
+ logger.error("self-update install failed; resuming on the current version", {
15697
+ packageSpec: spec,
15698
+ error: err instanceof Error ? err.message : String(err)
15699
+ });
15700
+ await hooks.restartLoop();
15701
+ return false;
15702
+ }
15703
+ logger.info("self-update installed; restarting", { packageSpec: spec });
15704
+ respawn();
15705
+ return true;
15706
+ }
15707
+ function defaultInstall(packageSpec) {
15708
+ return new Promise((resolve, reject) => {
15709
+ const child = (0, child_process_1.spawn)("npm", ["install", "-g", packageSpec], {
15710
+ stdio: ["ignore", "pipe", "pipe"]
15711
+ });
15712
+ let output = "";
15713
+ child.stdout.on("data", (chunk) => output += chunk.toString());
15714
+ child.stderr.on("data", (chunk) => output += chunk.toString());
15715
+ child.on("error", reject);
15716
+ child.on("exit", (code) => {
15717
+ if (code === 0)
15718
+ resolve();
15719
+ else
15720
+ reject(new Error(`npm install -g exited ${code}: ${output.trim().slice(-500)}`));
15721
+ });
15722
+ });
15723
+ }
15724
+ function defaultRespawn() {
15725
+ const child = (0, child_process_1.spawn)(process.execPath, process.argv.slice(1), { stdio: "inherit" });
15726
+ child.on("exit", (code) => process.exit(code ?? 0));
15727
+ }
15728
+ }
15729
+ });
15730
+
15647
15731
  // ../../packages/loom/dist/runtime/prompt-execution.js
15648
15732
  var require_prompt_execution = __commonJS({
15649
15733
  "../../packages/loom/dist/runtime/prompt-execution.js"(exports2) {
@@ -19172,6 +19256,8 @@ var require_claude_code = __commonJS({
19172
19256
  };
19173
19257
  Object.defineProperty(exports2, "__esModule", { value: true });
19174
19258
  exports2.createClaudeCodeExecutor = createClaudeCodeExecutor;
19259
+ exports2.parseStreamJson = parseStreamJson;
19260
+ exports2.relativeWorkspaceReads = relativeWorkspaceReads;
19175
19261
  exports2.extractJsonObject = extractJsonObject;
19176
19262
  var child_process_1 = require("child_process");
19177
19263
  var fs_1 = require("fs");
@@ -19239,8 +19325,13 @@ var require_claude_code = __commonJS({
19239
19325
  const childEnv = buildChildEnv(credentials, configDir);
19240
19326
  const claudeHints = (0, loom_1.pickProviderHints)(spec, "claude-code");
19241
19327
  const hintedModel = typeof claudeHints.model === "string" && claudeHints.model.length > 0 ? claudeHints.model : void 0;
19242
- const response = await this.runClaude(prompt, spec, workspace?.cwd ?? this.config.cwd, childEnv, signal, hintedModel);
19328
+ const runCwd = workspace?.cwd ?? this.config.cwd;
19329
+ const { response, readPaths } = await this.runClaude(prompt, spec, runCwd, childEnv, signal, hintedModel);
19243
19330
  if (workspace) {
19331
+ response.outputs.push({
19332
+ label: "workspace_reads",
19333
+ content: { paths: relativeWorkspaceReads(readPaths, runCwd) }
19334
+ });
19244
19335
  response.outputs.push({
19245
19336
  label: "workspace",
19246
19337
  content: {
@@ -19266,9 +19357,9 @@ var require_claude_code = __commonJS({
19266
19357
  }
19267
19358
  runClaude(prompt, spec, cwd, childEnv, signal, modelOverride) {
19268
19359
  const outputLabel = spec.outputLabel;
19360
+ const model = modelOverride ?? this.config.model;
19269
19361
  return new Promise((resolve, reject) => {
19270
- const args = ["--print"];
19271
- const model = modelOverride ?? this.config.model;
19362
+ const args = ["--print", "--output-format", "stream-json", "--verbose"];
19272
19363
  if (model)
19273
19364
  args.push("--model", model);
19274
19365
  if (this.config.maxTurns !== void 0)
@@ -19327,13 +19418,22 @@ var require_claude_code = __commonJS({
19327
19418
  const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
19328
19419
  if (code === 0) {
19329
19420
  try {
19330
- const parsed = extractJsonObject(stdout);
19421
+ const stream = parseStreamJson(stdout);
19422
+ const answerText = stream.finalText ?? stdout;
19423
+ const parsed = extractJsonObject(answerText);
19331
19424
  (0, validate_output_1.assertOutputValid)(spec, parsed);
19332
19425
  settle(() => resolve({
19333
- outputs: [{ label: outputLabel, content: parsed ?? { raw: stdout } }],
19334
- rawText: stdout,
19335
- parsed,
19336
- usage: this.config.model ? { aiProvider: "claude-code", aiModel: this.config.model } : void 0
19426
+ response: {
19427
+ outputs: [{ label: outputLabel, content: parsed ?? { raw: answerText } }],
19428
+ rawText: answerText,
19429
+ parsed,
19430
+ usage: {
19431
+ aiProvider: "claude-code",
19432
+ ...model ? { aiModel: model } : {},
19433
+ ...stream.usage ?? {}
19434
+ }
19435
+ },
19436
+ readPaths: stream.readPaths
19337
19437
  }));
19338
19438
  } catch (err) {
19339
19439
  settle(() => reject(err instanceof Error ? err : new Error(String(err))));
@@ -19423,6 +19523,60 @@ var require_claude_code = __commonJS({
19423
19523
  return credentials;
19424
19524
  };
19425
19525
  }
19526
+ function parseStreamJson(stdout) {
19527
+ let finalText = null;
19528
+ const readPaths = [];
19529
+ let usage = null;
19530
+ for (const line of stdout.split("\n")) {
19531
+ const trimmed = line.trim();
19532
+ if (!trimmed.startsWith("{"))
19533
+ continue;
19534
+ let event;
19535
+ try {
19536
+ event = JSON.parse(trimmed);
19537
+ } catch {
19538
+ continue;
19539
+ }
19540
+ if (event.type === "assistant") {
19541
+ const content = event.message?.content;
19542
+ if (!Array.isArray(content))
19543
+ continue;
19544
+ for (const block of content) {
19545
+ const b = block;
19546
+ if (b?.type === "tool_use" && b.name === "Read" && typeof b.input?.file_path === "string") {
19547
+ readPaths.push(b.input.file_path);
19548
+ }
19549
+ }
19550
+ } else if (event.type === "result") {
19551
+ if (typeof event.result === "string")
19552
+ finalText = event.result;
19553
+ const u = event.usage;
19554
+ const cost = event.total_cost_usd;
19555
+ usage = {
19556
+ ...typeof u?.input_tokens === "number" ? { inputTokens: u.input_tokens } : {},
19557
+ ...typeof u?.output_tokens === "number" ? { outputTokens: u.output_tokens } : {},
19558
+ ...typeof cost === "number" ? { costUsd: cost } : {}
19559
+ };
19560
+ }
19561
+ }
19562
+ return { finalText, readPaths, usage };
19563
+ }
19564
+ var MAX_REPORTED_READS = 2e3;
19565
+ function relativeWorkspaceReads(readPaths, cwd) {
19566
+ const root = path_1.default.resolve(cwd);
19567
+ const out = /* @__PURE__ */ new Set();
19568
+ for (const p of readPaths) {
19569
+ const resolved = path_1.default.resolve(root, p);
19570
+ if (resolved === root)
19571
+ continue;
19572
+ if (!resolved.startsWith(root + path_1.default.sep))
19573
+ continue;
19574
+ out.add(resolved.slice(root.length + 1));
19575
+ if (out.size >= MAX_REPORTED_READS)
19576
+ break;
19577
+ }
19578
+ return [...out].sort();
19579
+ }
19426
19580
  function extractJsonObject(s) {
19427
19581
  const exact = tryParseObject(s);
19428
19582
  if (exact)
@@ -20551,7 +20705,9 @@ var require_shuttle = __commonJS({
20551
20705
  "use strict";
20552
20706
  Object.defineProperty(exports2, "__esModule", { value: true });
20553
20707
  exports2.Shuttle = void 0;
20708
+ var os_1 = require("os");
20554
20709
  var jobs_1 = require_dist2();
20710
+ var self_update_1 = require_self_update();
20555
20711
  var loom_1 = require_dist3();
20556
20712
  var chain_1 = require_chain();
20557
20713
  var keep_1 = require_dist4();
@@ -20691,6 +20847,13 @@ var require_shuttle = __commonJS({
20691
20847
  return { status: "completed", outputs };
20692
20848
  };
20693
20849
  const pools = this.config.pools.map((p) => p.id ? { id: p.id } : { name: p.name });
20850
+ const subscribeLogger = {
20851
+ debug: (msg, meta) => logger.debug(msg, meta),
20852
+ info: (msg, meta) => logger.info(msg, meta),
20853
+ warn: (msg, meta) => logger.warn(msg, meta),
20854
+ error: (msg, meta) => logger.error(msg, meta)
20855
+ };
20856
+ const attemptedUpgrades = /* @__PURE__ */ new Set();
20694
20857
  this.subscription = (0, jobs_1.subscribe)({
20695
20858
  jobsClient,
20696
20859
  pools,
@@ -20699,15 +20862,32 @@ var require_shuttle = __commonJS({
20699
20862
  identityId: this.config.agent.identityId,
20700
20863
  intelligence: "digital",
20701
20864
  displayName: this.config.agent.displayName,
20702
- metadata: this.config.agent.metadata
20865
+ // Self-reported build facts, refreshed on every registration.
20866
+ // Products read these off the pool-member row (Sift's settings
20867
+ // page shows the version and offers the upgrade action).
20868
+ metadata: {
20869
+ ...this.config.agent.metadata,
20870
+ client_version: this.brand.product.version,
20871
+ hostname: (0, os_1.hostname)()
20872
+ }
20703
20873
  },
20704
20874
  onJob,
20705
- logger: {
20706
- debug: (msg, meta) => logger.debug(msg, meta),
20707
- info: (msg, meta) => logger.info(msg, meta),
20708
- warn: (msg, meta) => logger.warn(msg, meta),
20709
- error: (msg, meta) => logger.error(msg, meta)
20875
+ onDirectives: async (directives) => {
20876
+ const directive = (0, self_update_1.readUpgradeDirective)(directives, this.brand.product.version, attemptedUpgrades);
20877
+ if (!directive)
20878
+ return;
20879
+ attemptedUpgrades.add(directive.requested_version);
20880
+ await (0, self_update_1.performSelfUpdate)({
20881
+ packageName: this.brand.product.packageName,
20882
+ directive,
20883
+ logger: subscribeLogger,
20884
+ hooks: {
20885
+ stopLoop: () => this.subscription?.stop() ?? Promise.resolve(),
20886
+ restartLoop: () => this.subscription?.start() ?? Promise.resolve()
20887
+ }
20888
+ });
20710
20889
  },
20890
+ logger: subscribeLogger,
20711
20891
  pollIntervalMs: this.config.polling.intervalMs,
20712
20892
  maxConcurrency: this.config.concurrency.maxJobs
20713
20893
  });
@@ -21304,7 +21484,7 @@ var import_shuttle = __toESM(require_dist5());
21304
21484
  // package.json
21305
21485
  var package_default = {
21306
21486
  name: "@whittlelabs/sifter",
21307
- version: "0.9.0",
21487
+ version: "0.11.0",
21308
21488
  description: "Whittle Sifter: paired AI reviewer for Whittle Sift job pools.",
21309
21489
  bin: {
21310
21490
  "whittle-sifter": "./dist/bin.js"