@drisp/cli 0.5.6 → 0.5.7

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.
@@ -2241,7 +2241,7 @@ var cachedVersion = null;
2241
2241
  function readVersion() {
2242
2242
  if (cachedVersion !== null) return cachedVersion;
2243
2243
  try {
2244
- const injected = "0.5.6";
2244
+ const injected = "0.5.7";
2245
2245
  if (typeof injected === "string" && injected.length > 0) {
2246
2246
  cachedVersion = injected;
2247
2247
  return cachedVersion;
@@ -7700,6 +7700,8 @@ function generateNeutralTitle(event, g) {
7700
7700
  return truncate(
7701
7701
  `Cloud fn \u2717 ${event.data.function_name}: ${event.data.error_message}`
7702
7702
  );
7703
+ case "artifacts.manifest":
7704
+ return "Artifacts manifest";
7703
7705
  }
7704
7706
  }
7705
7707
 
@@ -11665,6 +11667,7 @@ async function runExec(options) {
11665
11667
  let mappedFinalMessage = null;
11666
11668
  let adapterSessionId = null;
11667
11669
  let activeRunId = null;
11670
+ let beforeTerminalCompletionRan = false;
11668
11671
  let store;
11669
11672
  try {
11670
11673
  store = sessionStoreFactory({
@@ -11787,6 +11790,57 @@ async function runExec(options) {
11787
11790
  feedEvents
11788
11791
  });
11789
11792
  }
11793
+ const runBeforeTerminalCompletion = async () => {
11794
+ if (beforeTerminalCompletionRan || latch.hasFailure() || !options.beforeTerminalCompletion) {
11795
+ return;
11796
+ }
11797
+ beforeTerminalCompletionRan = true;
11798
+ const resolved = resolveFinalMessage({
11799
+ streamMessage: streamFinalMessage,
11800
+ mappedMessage: mappedFinalMessage
11801
+ });
11802
+ const provisionalResult = {
11803
+ success: true,
11804
+ exitCode: EXEC_EXIT_CODE.SUCCESS,
11805
+ athenaSessionId: options.ephemeral ? null : athenaSessionId,
11806
+ adapterSessionId,
11807
+ finalMessage: resolved.message,
11808
+ tokens: cumulativeTokens,
11809
+ durationMs: Math.max(0, now() - startTs)
11810
+ };
11811
+ try {
11812
+ const feedEvents = await options.beforeTerminalCompletion({
11813
+ result: provisionalResult,
11814
+ runId: activeRunId
11815
+ });
11816
+ if (feedEvents && feedEvents.length > 0) {
11817
+ publishFeedEvents(feedEvents);
11818
+ }
11819
+ } catch (error) {
11820
+ latch.register({
11821
+ kind: "output",
11822
+ message: `Artifact upload failed: ${error instanceof Error ? error.message : String(error)}`
11823
+ });
11824
+ }
11825
+ };
11826
+ const writeLastMessageBeforeTerminalCompletion = async () => {
11827
+ if (latch.hasFailure() || !options.outputLastMessagePath) return;
11828
+ const resolved = resolveFinalMessage({
11829
+ streamMessage: streamFinalMessage,
11830
+ mappedMessage: mappedFinalMessage
11831
+ });
11832
+ try {
11833
+ await output.writeLastMessage(
11834
+ options.outputLastMessagePath,
11835
+ resolved.message
11836
+ );
11837
+ } catch (error) {
11838
+ latch.register({
11839
+ kind: "output",
11840
+ message: `Failed writing --output-last-message: ${error instanceof Error ? error.message : String(error)}`
11841
+ });
11842
+ }
11843
+ };
11790
11844
  const unsubscribeEvent = runtime.onEvent((runtimeEvent) => {
11791
11845
  adapterSessionId = runtimeEvent.sessionId;
11792
11846
  if (runtimeEvent.sessionId && activeRunId && !linkedAdapterSessions.has(runtimeEvent.sessionId)) {
@@ -11956,6 +12010,8 @@ async function runExec(options) {
11956
12010
  if (dashboardDecisionTimer) {
11957
12011
  clearInterval(dashboardDecisionTimer);
11958
12012
  }
12013
+ await writeLastMessageBeforeTerminalCompletion();
12014
+ await runBeforeTerminalCompletion();
11959
12015
  await sessionController.kill();
11960
12016
  unsubscribeEvent();
11961
12017
  unsubscribeDecision();
@@ -11974,19 +12030,6 @@ async function runExec(options) {
11974
12030
  output.warn(warning);
11975
12031
  output.emitJsonEvent("exec.warning", { message: warning });
11976
12032
  }
11977
- if (!latch.hasFailure() && options.outputLastMessagePath) {
11978
- try {
11979
- await output.writeLastMessage(
11980
- options.outputLastMessagePath,
11981
- resolvedFinalMessage.message
11982
- );
11983
- } catch (error) {
11984
- latch.register({
11985
- kind: "output",
11986
- message: `Failed writing --output-last-message: ${error instanceof Error ? error.message : String(error)}`
11987
- });
11988
- }
11989
- }
11990
12033
  const failure = latch.current();
11991
12034
  const exitCode = exitCodeFromFailure(failure);
11992
12035
  const success = exitCode === EXEC_EXIT_CODE.SUCCESS;
@@ -12586,6 +12629,370 @@ async function createRemoteRunEventPublisher({
12586
12629
  };
12587
12630
  }
12588
12631
 
12632
+ // src/app/dashboard/artifactCapture.ts
12633
+ import { execFile } from "child_process";
12634
+ import crypto3 from "crypto";
12635
+ import fs21 from "fs/promises";
12636
+ import path19 from "path";
12637
+ import { promisify } from "util";
12638
+ var execFileAsync = promisify(execFile);
12639
+ function parseArtifactUploadSpec(value) {
12640
+ if (typeof value !== "object" || value === null) return null;
12641
+ const obj = value;
12642
+ const hasArtifactUpload = Object.hasOwn(obj, "artifactUpload");
12643
+ const hasArtifacts = Object.hasOwn(obj, "artifacts");
12644
+ if (!hasArtifactUpload && !hasArtifacts) return null;
12645
+ const candidate = hasArtifactUpload ? obj["artifactUpload"] : obj["artifacts"];
12646
+ if (typeof candidate !== "object" || candidate === null) {
12647
+ throw new Error("artifact upload spec must be an object");
12648
+ }
12649
+ const spec = candidate;
12650
+ const bucket = spec["bucket"];
12651
+ const prefix = spec["prefix"];
12652
+ const normalizedPrefix = typeof prefix === "string" ? prefix.replace(/^\/+|\/+$/g, "") : null;
12653
+ const accessToken = spec["accessToken"] ?? (typeof spec["credentials"] === "object" && spec["credentials"] !== null ? spec["credentials"]["accessToken"] : void 0);
12654
+ if (typeof bucket !== "string" || bucket.length === 0 || !normalizedPrefix || typeof accessToken !== "string" || accessToken.length === 0) {
12655
+ throw new Error(
12656
+ "artifact upload spec must include bucket, prefix, and accessToken"
12657
+ );
12658
+ }
12659
+ return {
12660
+ bucket,
12661
+ prefix: normalizedPrefix,
12662
+ accessToken,
12663
+ includeIgnored: stringArray(spec["includeIgnored"]),
12664
+ hardDeny: [...DEFAULT_HARD_DENY, ...stringArray(spec["hardDeny"])]
12665
+ };
12666
+ }
12667
+ async function captureAndUploadArtifacts(input) {
12668
+ const now = input.now ?? Date.now;
12669
+ const uploadObject = input.uploadObject ?? uploadGcsObject;
12670
+ const payloads = await collectArtifactPayloads({
12671
+ projectDir: input.projectDir,
12672
+ includeIgnored: input.spec.includeIgnored,
12673
+ hardDeny: input.spec.hardDeny
12674
+ });
12675
+ const entries = [];
12676
+ for (const [idx, payload] of payloads.entries()) {
12677
+ const id = `${String(idx + 1).padStart(4, "0")}-${safeObjectSegment(
12678
+ payload.kind
12679
+ )}`;
12680
+ const object = joinObjectName(
12681
+ input.spec.prefix,
12682
+ "payloads",
12683
+ `${id}-${safeObjectSegment(payload.path)}`
12684
+ );
12685
+ await uploadObject({
12686
+ bucket: input.spec.bucket,
12687
+ objectName: object,
12688
+ body: payload.bytes,
12689
+ contentType: contentTypeFor(payload.path),
12690
+ accessToken: input.spec.accessToken
12691
+ });
12692
+ entries.push({
12693
+ id,
12694
+ kind: payload.kind,
12695
+ path: payload.path,
12696
+ object,
12697
+ size: payload.bytes.byteLength,
12698
+ sha256: sha256(payload.bytes)
12699
+ });
12700
+ }
12701
+ const manifestObject = joinObjectName(input.spec.prefix, "manifest.json");
12702
+ const manifest = {
12703
+ version: 1,
12704
+ runId: input.runId,
12705
+ athenaSessionId: input.result.athenaSessionId,
12706
+ adapterSessionId: input.result.adapterSessionId,
12707
+ createdAt: new Date(now()).toISOString(),
12708
+ entries,
12709
+ objects: {
12710
+ bucket: input.spec.bucket,
12711
+ prefix: input.spec.prefix,
12712
+ manifest: manifestObject
12713
+ }
12714
+ };
12715
+ await uploadObject({
12716
+ bucket: input.spec.bucket,
12717
+ objectName: manifestObject,
12718
+ body: Buffer.from(`${JSON.stringify(manifest, null, 2)}
12719
+ `),
12720
+ contentType: "application/json",
12721
+ accessToken: input.spec.accessToken
12722
+ });
12723
+ return {
12724
+ manifest,
12725
+ feedEvent: makeArtifactManifestFeedEvent({
12726
+ manifest,
12727
+ result: input.result,
12728
+ runId: input.runId,
12729
+ ts: now()
12730
+ })
12731
+ };
12732
+ }
12733
+ async function collectArtifactPayloads(input) {
12734
+ const hardDeny = input.hardDeny ?? DEFAULT_HARD_DENY;
12735
+ const payloads = [];
12736
+ if (!await isGitWorkspace(input.projectDir)) {
12737
+ return payloads;
12738
+ }
12739
+ const trackedDiff = await gitDiffForAllowedPaths({
12740
+ projectDir: input.projectDir,
12741
+ nameArgs: ["diff", "--name-only", "-z", "--"],
12742
+ diffArgs: ["diff", "--binary", "--"],
12743
+ hardDeny
12744
+ });
12745
+ if (trackedDiff.length > 0) {
12746
+ payloads.push({
12747
+ kind: "tracked_diff",
12748
+ path: "git/tracked.diff",
12749
+ bytes: Buffer.from(trackedDiff)
12750
+ });
12751
+ }
12752
+ const stagedDiff = await gitDiffForAllowedPaths({
12753
+ projectDir: input.projectDir,
12754
+ nameArgs: ["diff", "--name-only", "-z", "--cached", "--"],
12755
+ diffArgs: ["diff", "--binary", "--cached", "--"],
12756
+ hardDeny
12757
+ });
12758
+ if (stagedDiff.length > 0) {
12759
+ payloads.push({
12760
+ kind: "staged_diff",
12761
+ path: "git/staged.diff",
12762
+ bytes: Buffer.from(stagedDiff)
12763
+ });
12764
+ }
12765
+ const upstream = await gitMaybe(input.projectDir, [
12766
+ "rev-parse",
12767
+ "--abbrev-ref",
12768
+ "--symbolic-full-name",
12769
+ "@{u}"
12770
+ ]);
12771
+ if (upstream.trim().length > 0) {
12772
+ const range = `${upstream.trim()}..HEAD`;
12773
+ const commits = await gitDiffForAllowedPaths({
12774
+ projectDir: input.projectDir,
12775
+ nameArgs: ["diff", "--name-only", "-z", range, "--"],
12776
+ diffArgs: ["format-patch", "--stdout", range, "--"],
12777
+ hardDeny
12778
+ });
12779
+ if (commits.length > 0) {
12780
+ payloads.push({
12781
+ kind: "unpushed_commits",
12782
+ path: "git/unpushed.patch",
12783
+ bytes: Buffer.from(commits)
12784
+ });
12785
+ }
12786
+ }
12787
+ const untracked = splitNul(
12788
+ await git(input.projectDir, [
12789
+ "ls-files",
12790
+ "--others",
12791
+ "--exclude-standard",
12792
+ "-z"
12793
+ ])
12794
+ );
12795
+ for (const rel of untracked) {
12796
+ if (!isAllowedRelativePath(rel, hardDeny)) continue;
12797
+ const bytes = await readWorkspaceFile(input.projectDir, rel);
12798
+ if (!bytes) continue;
12799
+ payloads.push({
12800
+ kind: "untracked_file",
12801
+ path: rel,
12802
+ bytes
12803
+ });
12804
+ }
12805
+ for (const rel of input.includeIgnored ?? []) {
12806
+ if (!isAllowedRelativePath(rel, hardDeny)) continue;
12807
+ if (!await isIgnored(input.projectDir, rel)) continue;
12808
+ const bytes = await readWorkspaceFile(input.projectDir, rel);
12809
+ if (!bytes) continue;
12810
+ payloads.push({
12811
+ kind: "included_ignored_file",
12812
+ path: rel,
12813
+ bytes
12814
+ });
12815
+ }
12816
+ return payloads;
12817
+ }
12818
+ async function readWorkspaceFile(projectDir, rel) {
12819
+ const absolute = path19.resolve(projectDir, rel);
12820
+ const workspaceRoot = await fs21.realpath(projectDir);
12821
+ let stat;
12822
+ try {
12823
+ stat = await fs21.lstat(absolute);
12824
+ } catch {
12825
+ return null;
12826
+ }
12827
+ if (!stat.isFile() || stat.isSymbolicLink()) return null;
12828
+ const real = await fs21.realpath(absolute);
12829
+ const relativeToRoot = path19.relative(workspaceRoot, real);
12830
+ if (relativeToRoot === ".." || relativeToRoot.startsWith(`..${path19.sep}`) || path19.isAbsolute(relativeToRoot)) {
12831
+ return null;
12832
+ }
12833
+ return fs21.readFile(absolute);
12834
+ }
12835
+ async function gitDiffForAllowedPaths(input) {
12836
+ const paths = splitNul(await git(input.projectDir, input.nameArgs)).filter(
12837
+ (rel) => isAllowedRelativePath(rel, input.hardDeny)
12838
+ );
12839
+ if (paths.length === 0) return "";
12840
+ return git(input.projectDir, [...input.diffArgs, ...paths]);
12841
+ }
12842
+ async function uploadGcsObject(input) {
12843
+ const url = new URL(
12844
+ `https://storage.googleapis.com/upload/storage/v1/b/${encodeURIComponent(
12845
+ input.bucket
12846
+ )}/o`
12847
+ );
12848
+ url.searchParams.set("uploadType", "media");
12849
+ url.searchParams.set("name", input.objectName);
12850
+ const response = await fetch(url, {
12851
+ method: "POST",
12852
+ headers: {
12853
+ authorization: `Bearer ${input.accessToken}`,
12854
+ "content-type": input.contentType
12855
+ },
12856
+ body: input.body.buffer.slice(
12857
+ input.body.byteOffset,
12858
+ input.body.byteOffset + input.body.byteLength
12859
+ )
12860
+ });
12861
+ if (!response.ok) {
12862
+ throw new Error(
12863
+ `GCS upload ${input.objectName} failed with HTTP ${response.status}`
12864
+ );
12865
+ }
12866
+ }
12867
+ function makeArtifactManifestFeedEvent(input) {
12868
+ return {
12869
+ event_id: `${input.runId}:artifacts-manifest`,
12870
+ seq: 0,
12871
+ ts: input.ts,
12872
+ session_id: input.result.athenaSessionId ?? input.runId,
12873
+ run_id: input.runId,
12874
+ kind: "artifacts.manifest",
12875
+ level: "info",
12876
+ actor_id: "system",
12877
+ title: "Artifacts manifest",
12878
+ data: { manifest: input.manifest }
12879
+ };
12880
+ }
12881
+ async function git(cwd, args) {
12882
+ const result = await execFileAsync("git", args, {
12883
+ cwd,
12884
+ encoding: "buffer",
12885
+ maxBuffer: 50 * 1024 * 1024
12886
+ });
12887
+ return result.stdout.toString("utf8");
12888
+ }
12889
+ async function gitMaybe(cwd, args) {
12890
+ try {
12891
+ return await git(cwd, args);
12892
+ } catch {
12893
+ return "";
12894
+ }
12895
+ }
12896
+ async function isGitWorkspace(cwd) {
12897
+ return (await gitMaybe(cwd, ["rev-parse", "--is-inside-work-tree"])).trim() === "true";
12898
+ }
12899
+ async function isIgnored(cwd, rel) {
12900
+ try {
12901
+ await execFileAsync("git", ["check-ignore", "--quiet", "--", rel], { cwd });
12902
+ return true;
12903
+ } catch {
12904
+ return false;
12905
+ }
12906
+ }
12907
+ function splitNul(value) {
12908
+ return value.split("\0").filter(Boolean);
12909
+ }
12910
+ function stringArray(value) {
12911
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
12912
+ }
12913
+ var DEFAULT_HARD_DENY = [
12914
+ ".git",
12915
+ ".git/**",
12916
+ ".env",
12917
+ ".env.*",
12918
+ "**/.env",
12919
+ "**/.env.*",
12920
+ ".ssh",
12921
+ ".ssh/**"
12922
+ ];
12923
+ function isAllowedRelativePath(rel, hardDeny) {
12924
+ if (path19.isAbsolute(rel) || rel.includes("\0")) return false;
12925
+ const normalized = path19.posix.normalize(rel.replaceAll(path19.sep, "/"));
12926
+ if (normalized === ".." || normalized.startsWith("../")) return false;
12927
+ return !hardDeny.some((pattern) => matchesDenyPattern(normalized, pattern));
12928
+ }
12929
+ function matchesDenyPattern(rel, pattern) {
12930
+ const normalized = pattern.replaceAll(path19.sep, "/");
12931
+ if (normalized.includes("*") || normalized.includes("?")) {
12932
+ return globToRegExp(normalized).test(rel);
12933
+ }
12934
+ if (normalized.endsWith("/**")) {
12935
+ const prefix = normalized.slice(0, -3);
12936
+ return rel === prefix || rel.startsWith(`${prefix}/`);
12937
+ }
12938
+ if (normalized.startsWith("**/")) {
12939
+ const suffix = normalized.slice(3);
12940
+ return rel === suffix || rel.endsWith(`/${suffix}`);
12941
+ }
12942
+ if (normalized.endsWith(".*")) {
12943
+ const prefix = normalized.slice(0, -1);
12944
+ return rel.startsWith(prefix);
12945
+ }
12946
+ return rel === normalized;
12947
+ }
12948
+ function globToRegExp(pattern) {
12949
+ let source = "^";
12950
+ for (let i = 0; i < pattern.length; i += 1) {
12951
+ const char = pattern[i];
12952
+ const next = pattern[i + 1];
12953
+ if (char === "*") {
12954
+ if (next === "*") {
12955
+ const after = pattern[i + 2];
12956
+ if (after === "/") {
12957
+ source += "(?:.*/)?";
12958
+ i += 2;
12959
+ } else {
12960
+ source += ".*";
12961
+ i += 1;
12962
+ }
12963
+ } else {
12964
+ source += "[^/]*";
12965
+ }
12966
+ } else if (char === "?") {
12967
+ source += "[^/]";
12968
+ } else {
12969
+ source += escapeRegExp(char);
12970
+ }
12971
+ }
12972
+ source += "$";
12973
+ return new RegExp(source);
12974
+ }
12975
+ function escapeRegExp(value) {
12976
+ return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
12977
+ }
12978
+ function sha256(bytes) {
12979
+ return crypto3.createHash("sha256").update(bytes).digest("hex");
12980
+ }
12981
+ function joinObjectName(...parts) {
12982
+ return parts.map((part) => part.replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
12983
+ }
12984
+ function safeObjectSegment(value) {
12985
+ const safe = value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
12986
+ return safe.length > 0 ? safe.slice(0, 160) : "artifact";
12987
+ }
12988
+ function contentTypeFor(filePath) {
12989
+ if (filePath.endsWith(".json")) return "application/json";
12990
+ if (filePath.endsWith(".diff") || filePath.endsWith(".patch")) {
12991
+ return "text/x-diff";
12992
+ }
12993
+ return "application/octet-stream";
12994
+ }
12995
+
12589
12996
  // src/app/dashboard/remoteRunExecutor.ts
12590
12997
  var DEFAULT_MARKETPLACE_SLUG = "lespaceman/athena-workflow-marketplace";
12591
12998
  function parseRemoteRunSpec(value) {
@@ -12717,6 +13124,8 @@ async function executeRemoteAssignment({
12717
13124
  resolveWorkflowInstallFn = resolveWorkflowInstall,
12718
13125
  installWorkflowFromSourceFn = installWorkflowFromSource,
12719
13126
  readGlobalConfigFn = readGlobalConfig,
13127
+ uploadArtifactObjectFn,
13128
+ dashboardFeedPublisher,
12720
13129
  runStreamConnectTimeoutMs = 5e3
12721
13130
  }) {
12722
13131
  const lastTerminalFailureMessage = { current: null };
@@ -12746,6 +13155,15 @@ async function executeRemoteAssignment({
12746
13155
  send("error", { message: "remote assignment missing prompt" });
12747
13156
  return;
12748
13157
  }
13158
+ let artifactUploadSpec;
13159
+ try {
13160
+ artifactUploadSpec = parseArtifactUploadSpec(frame.runSpec);
13161
+ } catch (err) {
13162
+ send("error", {
13163
+ message: err instanceof Error ? err.message : String(err)
13164
+ });
13165
+ return;
13166
+ }
12749
13167
  let runtimeConfig;
12750
13168
  try {
12751
13169
  const workflowOverride = ensureRemoteWorkflowInstalled({
@@ -12831,7 +13249,22 @@ async function executeRemoteAssignment({
12831
13249
  signal: abortSignal,
12832
13250
  stdout,
12833
13251
  stderr,
12834
- ...decisionInbox ? { dashboardDecisionInbox: decisionInbox } : {}
13252
+ ...decisionInbox ? { dashboardDecisionInbox: decisionInbox } : {},
13253
+ ...dashboardFeedPublisher ? { dashboardFeedPublisher } : {},
13254
+ ...artifactUploadSpec ? {
13255
+ beforeTerminalCompletion: async ({ result: result2, runId }) => {
13256
+ const artifactRunId = runId ?? frame.runId;
13257
+ const { feedEvent } = await captureAndUploadArtifacts({
13258
+ spec: artifactUploadSpec,
13259
+ projectDir,
13260
+ runId: artifactRunId,
13261
+ result: result2,
13262
+ now,
13263
+ ...uploadArtifactObjectFn ? { uploadObject: uploadArtifactObjectFn } : {}
13264
+ });
13265
+ return [feedEvent];
13266
+ }
13267
+ } : {}
12835
13268
  });
12836
13269
  const failedCompletion = deferredFailedCompletion.current;
12837
13270
  if (failedCompletion) {
@@ -12876,19 +13309,19 @@ async function executeRemoteAssignment({
12876
13309
  }
12877
13310
 
12878
13311
  // src/infra/config/attachmentMirror.ts
12879
- import crypto3 from "crypto";
12880
- import fs21 from "fs";
13312
+ import crypto4 from "crypto";
13313
+ import fs22 from "fs";
12881
13314
  import os12 from "os";
12882
- import path19 from "path";
13315
+ import path20 from "path";
12883
13316
  function attachmentMirrorPath(env = process.env) {
12884
13317
  const home = env["HOME"] ?? os12.homedir();
12885
- return path19.join(home, ".config", "athena", "attachments.json");
13318
+ return path20.join(home, ".config", "athena", "attachments.json");
12886
13319
  }
12887
13320
  function readAttachmentMirror(env = process.env) {
12888
13321
  const file = attachmentMirrorPath(env);
12889
13322
  let raw;
12890
13323
  try {
12891
- raw = fs21.readFileSync(file, "utf-8");
13324
+ raw = fs22.readFileSync(file, "utf-8");
12892
13325
  } catch (err) {
12893
13326
  if (err.code === "ENOENT") return null;
12894
13327
  throw err;
@@ -12912,29 +13345,29 @@ function readAttachmentMirror(env = process.env) {
12912
13345
  function writeAttachmentMirror(mirror, env = process.env) {
12913
13346
  const validated = parseAttachmentMirror(mirror);
12914
13347
  const file = attachmentMirrorPath(env);
12915
- const dir = path19.dirname(file);
12916
- fs21.mkdirSync(dir, { recursive: true, mode: 448 });
12917
- const tmp = `${file}.${process.pid}.${crypto3.randomBytes(4).toString("hex")}.tmp`;
12918
- const fd = fs21.openSync(tmp, "w", 384);
13348
+ const dir = path20.dirname(file);
13349
+ fs22.mkdirSync(dir, { recursive: true, mode: 448 });
13350
+ const tmp = `${file}.${process.pid}.${crypto4.randomBytes(4).toString("hex")}.tmp`;
13351
+ const fd = fs22.openSync(tmp, "w", 384);
12919
13352
  try {
12920
- fs21.writeSync(fd, JSON.stringify(validated, null, 2) + "\n");
12921
- fs21.fsyncSync(fd);
13353
+ fs22.writeSync(fd, JSON.stringify(validated, null, 2) + "\n");
13354
+ fs22.fsyncSync(fd);
12922
13355
  } finally {
12923
- fs21.closeSync(fd);
13356
+ fs22.closeSync(fd);
12924
13357
  }
12925
13358
  try {
12926
- fs21.renameSync(tmp, file);
13359
+ fs22.renameSync(tmp, file);
12927
13360
  } catch (err) {
12928
13361
  try {
12929
- fs21.unlinkSync(tmp);
13362
+ fs22.unlinkSync(tmp);
12930
13363
  } catch {
12931
13364
  }
12932
13365
  throw err;
12933
13366
  }
12934
13367
  if (process.platform !== "win32") {
12935
13368
  try {
12936
- fs21.chmodSync(dir, 448);
12937
- fs21.chmodSync(file, 384);
13369
+ fs22.chmodSync(dir, 448);
13370
+ fs22.chmodSync(file, 384);
12938
13371
  } catch {
12939
13372
  }
12940
13373
  }
@@ -12942,7 +13375,7 @@ function writeAttachmentMirror(mirror, env = process.env) {
12942
13375
  function removeAttachmentMirror(env = process.env) {
12943
13376
  const file = attachmentMirrorPath(env);
12944
13377
  try {
12945
- fs21.unlinkSync(file);
13378
+ fs22.unlinkSync(file);
12946
13379
  } catch (err) {
12947
13380
  if (err.code !== "ENOENT") throw err;
12948
13381
  }
@@ -13208,6 +13641,7 @@ function createDashboardPairedExecution(options) {
13208
13641
  });
13209
13642
  const maxConcurrentRuns = options.maxConcurrentRuns ?? DEFAULT_MAX_CONCURRENT_RUNS;
13210
13643
  const runHistoryLimit = options.runHistoryLimit ?? DEFAULT_RUN_HISTORY_LIMIT;
13644
+ const pairedFeedPublisher = options.pairedFeedPublisher;
13211
13645
  const now = options.now ?? (() => Date.now());
13212
13646
  let completedRuns = 0;
13213
13647
  const active = /* @__PURE__ */ new Map();
@@ -13281,7 +13715,8 @@ function createDashboardPairedExecution(options) {
13281
13715
  projectDir: input.projectDir ?? projectDir,
13282
13716
  log,
13283
13717
  abortSignal: controller.signal,
13284
- decisionInbox
13718
+ decisionInbox,
13719
+ ...pairedFeedPublisher ? { dashboardFeedPublisher: pairedFeedPublisher } : {}
13285
13720
  }).then(() => {
13286
13721
  if (record.status === "running") record.status = "completed";
13287
13722
  }).catch((err) => {
@@ -13352,9 +13787,9 @@ function createDashboardPairedExecution(options) {
13352
13787
  }
13353
13788
 
13354
13789
  // src/app/dashboard/remoteWorkspaceResolver.ts
13355
- import fs22 from "fs";
13790
+ import fs23 from "fs";
13356
13791
  import os13 from "os";
13357
- import path20 from "path";
13792
+ import path21 from "path";
13358
13793
  function resolveRemoteWorkspace(frame, options = {}) {
13359
13794
  const spec = parseRemoteRunSpec(frame.runSpec);
13360
13795
  if (!spec) {
@@ -13373,14 +13808,14 @@ function resolveRemoteWorkspace(frame, options = {}) {
13373
13808
  const runnerId = frame.runnerId ?? "legacy";
13374
13809
  const deploymentSlug = deploymentSlugFromUrl(options.dashboardUrl);
13375
13810
  const stateDir = daemonStatePaths(options.env).dir;
13376
- const projectDir = sessionId ? path20.join(
13811
+ const projectDir = sessionId ? path21.join(
13377
13812
  stateDir,
13378
13813
  "remote-workspaces",
13379
13814
  deploymentSlug,
13380
13815
  sanitizePathSegment(runnerId),
13381
13816
  "sessions",
13382
13817
  sanitizePathSegment(sessionId)
13383
- ) : path20.join(
13818
+ ) : path21.join(
13384
13819
  stateDir,
13385
13820
  "remote-workspaces",
13386
13821
  deploymentSlug,
@@ -13389,10 +13824,10 @@ function resolveRemoteWorkspace(frame, options = {}) {
13389
13824
  sanitizePathSegment(frame.runId)
13390
13825
  );
13391
13826
  try {
13392
- fs22.mkdirSync(projectDir, { recursive: true, mode: 448 });
13827
+ fs23.mkdirSync(projectDir, { recursive: true, mode: 448 });
13393
13828
  if (process.platform !== "win32") {
13394
13829
  try {
13395
- fs22.chmodSync(projectDir, 448);
13830
+ fs23.chmodSync(projectDir, 448);
13396
13831
  } catch {
13397
13832
  }
13398
13833
  }
@@ -13408,8 +13843,8 @@ function resolveRemoteWorkspace(frame, options = {}) {
13408
13843
  return validateProjectDir(projectDir, options.env);
13409
13844
  }
13410
13845
  function validateProjectDir(projectDir, env = process.env) {
13411
- const resolved = path20.resolve(projectDir);
13412
- if (!path20.isAbsolute(projectDir)) {
13846
+ const resolved = path21.resolve(projectDir);
13847
+ if (!path21.isAbsolute(projectDir)) {
13413
13848
  return {
13414
13849
  kind: "rejected",
13415
13850
  rejection: {
@@ -13418,7 +13853,7 @@ function validateProjectDir(projectDir, env = process.env) {
13418
13853
  }
13419
13854
  };
13420
13855
  }
13421
- const home = path20.resolve(env["HOME"] ?? os13.homedir());
13856
+ const home = path21.resolve(env["HOME"] ?? os13.homedir());
13422
13857
  if (resolved === home) {
13423
13858
  return {
13424
13859
  kind: "rejected",
@@ -13430,7 +13865,7 @@ function validateProjectDir(projectDir, env = process.env) {
13430
13865
  }
13431
13866
  let stat;
13432
13867
  try {
13433
- stat = fs22.statSync(resolved);
13868
+ stat = fs23.statSync(resolved);
13434
13869
  } catch {
13435
13870
  return {
13436
13871
  kind: "rejected",
@@ -13612,6 +14047,7 @@ async function runDashboardRuntimeDaemon(options = {}) {
13612
14047
  executor,
13613
14048
  projectDir,
13614
14049
  decisionInbox,
14050
+ pairedFeedPublisher,
13615
14051
  log,
13616
14052
  maxConcurrentRuns,
13617
14053
  now,
@@ -13855,18 +14291,18 @@ async function runDashboardRuntimeDaemon(options = {}) {
13855
14291
  }
13856
14292
 
13857
14293
  // src/infra/daemon/pidLock.ts
13858
- import fs23 from "fs";
14294
+ import fs24 from "fs";
13859
14295
  function acquirePidLock(pidPath) {
13860
14296
  const ownPid = process.pid;
13861
14297
  for (let attempt = 0; attempt < 2; attempt += 1) {
13862
14298
  try {
13863
- const fd = fs23.openSync(pidPath, "wx", 384);
14299
+ const fd = fs24.openSync(pidPath, "wx", 384);
13864
14300
  try {
13865
- fs23.writeSync(fd, `${ownPid}
14301
+ fs24.writeSync(fd, `${ownPid}
13866
14302
  `);
13867
- fs23.fsyncSync(fd);
14303
+ fs24.fsyncSync(fd);
13868
14304
  } finally {
13869
- fs23.closeSync(fd);
14305
+ fs24.closeSync(fd);
13870
14306
  }
13871
14307
  return makeHandle(pidPath, ownPid);
13872
14308
  } catch (err) {
@@ -13880,7 +14316,7 @@ function acquirePidLock(pidPath) {
13880
14316
  }
13881
14317
  if (existing.state === "stale") {
13882
14318
  try {
13883
- fs23.unlinkSync(pidPath);
14319
+ fs24.unlinkSync(pidPath);
13884
14320
  } catch (err) {
13885
14321
  if (err.code !== "ENOENT") throw err;
13886
14322
  }
@@ -13894,7 +14330,7 @@ function acquirePidLock(pidPath) {
13894
14330
  function readPidLock(pidPath) {
13895
14331
  let raw;
13896
14332
  try {
13897
- raw = fs23.readFileSync(pidPath, "utf-8");
14333
+ raw = fs24.readFileSync(pidPath, "utf-8");
13898
14334
  } catch (err) {
13899
14335
  if (err.code === "ENOENT") {
13900
14336
  return { state: "absent" };
@@ -13918,9 +14354,9 @@ function makeHandle(pidPath, pid) {
13918
14354
  if (released) return;
13919
14355
  released = true;
13920
14356
  try {
13921
- const raw = fs23.readFileSync(pidPath, "utf-8").trim();
14357
+ const raw = fs24.readFileSync(pidPath, "utf-8").trim();
13922
14358
  if (raw === String(pid)) {
13923
- fs23.unlinkSync(pidPath);
14359
+ fs24.unlinkSync(pidPath);
13924
14360
  }
13925
14361
  } catch (err) {
13926
14362
  if (err.code !== "ENOENT") {
@@ -13946,7 +14382,7 @@ function isProcessAlive(pid) {
13946
14382
  }
13947
14383
 
13948
14384
  // src/infra/daemon/udsIpc.ts
13949
- import fs24 from "fs";
14385
+ import fs25 from "fs";
13950
14386
  import net2 from "net";
13951
14387
 
13952
14388
  // src/infra/daemon/udsFrameCodec.ts
@@ -13991,7 +14427,7 @@ async function startUdsServer(socketPath, handler, log) {
13991
14427
  });
13992
14428
  if (process.platform !== "win32") {
13993
14429
  try {
13994
- fs24.chmodSync(socketPath, 384);
14430
+ fs25.chmodSync(socketPath, 384);
13995
14431
  } catch {
13996
14432
  }
13997
14433
  }
@@ -14001,7 +14437,7 @@ async function startUdsServer(socketPath, handler, log) {
14001
14437
  server.close(() => resolve());
14002
14438
  });
14003
14439
  try {
14004
- fs24.unlinkSync(socketPath);
14440
+ fs25.unlinkSync(socketPath);
14005
14441
  } catch (err) {
14006
14442
  if (err.code !== "ENOENT") {
14007
14443
  }
@@ -14119,7 +14555,7 @@ async function sendUdsRequest(socketPath, request, options = {}) {
14119
14555
  async function unlinkStaleSocket(socketPath) {
14120
14556
  let stat;
14121
14557
  try {
14122
- stat = fs24.statSync(socketPath);
14558
+ stat = fs25.statSync(socketPath);
14123
14559
  } catch (err) {
14124
14560
  if (err.code === "ENOENT") return;
14125
14561
  throw err;
@@ -14146,7 +14582,7 @@ async function unlinkStaleSocket(socketPath) {
14146
14582
  `uds path ${socketPath} is in use by another process; aborting`
14147
14583
  );
14148
14584
  }
14149
- fs24.unlinkSync(socketPath);
14585
+ fs25.unlinkSync(socketPath);
14150
14586
  }
14151
14587
 
14152
14588
  export {
@@ -14224,4 +14660,4 @@ export {
14224
14660
  startUdsServer,
14225
14661
  sendUdsRequest
14226
14662
  };
14227
- //# sourceMappingURL=chunk-ZU7M6YZW.js.map
14663
+ //# sourceMappingURL=chunk-UKMVFGY2.js.map
package/dist/cli.js CHANGED
@@ -74,7 +74,7 @@ import {
74
74
  writeAttachmentMirror,
75
75
  writeGatewayClientConfig,
76
76
  wsClientOptionsForEndpoint
77
- } from "./chunk-ZU7M6YZW.js";
77
+ } from "./chunk-UKMVFGY2.js";
78
78
  import {
79
79
  generateId as generateId2
80
80
  } from "./chunk-BTKQ67RE.js";
@@ -5910,6 +5910,13 @@ var gatewayFunctionCompleted = defaultRenderer(
5910
5910
  var gatewayFunctionFailed = defaultRenderer(
5911
5911
  (event) => `fn ${event.data.reason}: ${event.data.function_name} \u2014 ${event.data.error_message}`
5912
5912
  );
5913
+ var artifactsManifest = defaultRenderer(
5914
+ (event) => {
5915
+ const manifest = event.data.manifest;
5916
+ const count = Array.isArray(manifest.entries) ? manifest.entries.length : 0;
5917
+ return `artifacts manifest (${count} item${count === 1 ? "" : "s"})`;
5918
+ }
5919
+ );
5913
5920
  var RENDERERS = {
5914
5921
  "session.start": sessionStart,
5915
5922
  "session.end": sessionEnd,
@@ -5968,7 +5975,8 @@ var RENDERERS = {
5968
5975
  "channel.chat.outbound": channelChatOutbound,
5969
5976
  "gateway.function.invoked": gatewayFunctionInvoked,
5970
5977
  "gateway.function.completed": gatewayFunctionCompleted,
5971
- "gateway.function.failed": gatewayFunctionFailed
5978
+ "gateway.function.failed": gatewayFunctionFailed,
5979
+ "artifacts.manifest": artifactsManifest
5972
5980
  };
5973
5981
  function rendererFor(event) {
5974
5982
  return RENDERERS[event.kind];
@@ -6814,6 +6822,7 @@ function resolveEventToolColumn(event) {
6814
6822
  case "gateway.function.invoked":
6815
6823
  case "gateway.function.completed":
6816
6824
  case "gateway.function.failed":
6825
+ case "artifacts.manifest":
6817
6826
  return "";
6818
6827
  }
6819
6828
  }
@@ -8548,7 +8557,8 @@ function renderDetailLines(event, width, pairedPostEvent, theme = darkTheme) {
8548
8557
  case "channel.chat.outbound":
8549
8558
  case "gateway.function.invoked":
8550
8559
  case "gateway.function.completed":
8551
- case "gateway.function.failed": {
8560
+ case "gateway.function.failed":
8561
+ case "artifacts.manifest": {
8552
8562
  const header = buildCompactHeader(event, width, { theme });
8553
8563
  const json = JSON.stringify(event.raw ?? event.data, null, 2);
8554
8564
  const content = highlightCode(json, cw, "json");
@@ -15695,7 +15705,7 @@ var cachedVersion = null;
15695
15705
  function readPackageVersion() {
15696
15706
  if (cachedVersion !== null) return cachedVersion;
15697
15707
  try {
15698
- const injected = "0.5.6";
15708
+ const injected = "0.5.7";
15699
15709
  if (typeof injected === "string" && injected.length > 0) {
15700
15710
  cachedVersion = injected;
15701
15711
  return cachedVersion;
@@ -3,7 +3,7 @@ import {
3
3
  ensureDaemonStateDir,
4
4
  runDashboardRuntimeDaemon,
5
5
  startUdsServer
6
- } from "./chunk-ZU7M6YZW.js";
6
+ } from "./chunk-UKMVFGY2.js";
7
7
  import "./chunk-BTKQ67RE.js";
8
8
  import {
9
9
  readDashboardClientConfig,
package/package.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "hooks",
18
18
  "dashboard"
19
19
  ],
20
- "version": "0.5.6",
20
+ "version": "0.5.7",
21
21
  "license": "MIT",
22
22
  "bin": {
23
23
  "athena": "dist/cli.js",