@songsid/agend 2.2.0-beta.16 → 2.2.0-beta.18

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 (56) hide show
  1. package/README.md +19 -18
  2. package/README.zh-TW.md +19 -18
  3. package/dist/agent-whats-new.js +3 -1
  4. package/dist/agent-whats-new.js.map +1 -1
  5. package/dist/backend/antigravity.d.ts +25 -0
  6. package/dist/backend/antigravity.js +94 -9
  7. package/dist/backend/antigravity.js.map +1 -1
  8. package/dist/backend/kiro.d.ts +2 -0
  9. package/dist/backend/kiro.js +4 -0
  10. package/dist/backend/kiro.js.map +1 -1
  11. package/dist/backend/types.d.ts +5 -0
  12. package/dist/backend/types.js.map +1 -1
  13. package/dist/compact-watch.d.ts +9 -1
  14. package/dist/compact-watch.js +47 -6
  15. package/dist/compact-watch.js.map +1 -1
  16. package/dist/config-validator.js +26 -1
  17. package/dist/config-validator.js.map +1 -1
  18. package/dist/daemon.d.ts +8 -0
  19. package/dist/daemon.js +66 -6
  20. package/dist/daemon.js.map +1 -1
  21. package/dist/fleet-manager.js +78 -23
  22. package/dist/fleet-manager.js.map +1 -1
  23. package/dist/forged-envelope-scan.d.ts +4 -3
  24. package/dist/forged-envelope-scan.js +4 -3
  25. package/dist/forged-envelope-scan.js.map +1 -1
  26. package/dist/kiro-compact-pane.d.ts +38 -0
  27. package/dist/kiro-compact-pane.js +57 -0
  28. package/dist/kiro-compact-pane.js.map +1 -0
  29. package/dist/locale.js +8 -4
  30. package/dist/locale.js.map +1 -1
  31. package/dist/outbound-schemas.js +1 -1
  32. package/dist/outbound-schemas.js.map +1 -1
  33. package/dist/public-web-gateway.d.ts +2 -0
  34. package/dist/public-web-gateway.js +6 -1
  35. package/dist/public-web-gateway.js.map +1 -1
  36. package/dist/topic-commands.d.ts +9 -2
  37. package/dist/topic-commands.js +18 -6
  38. package/dist/topic-commands.js.map +1 -1
  39. package/dist/types.d.ts +8 -0
  40. package/dist/ui/chat-render.js +31 -6
  41. package/dist/ui/chat-store.js +5 -5
  42. package/dist/ui/chat-strings.js +6 -2
  43. package/dist/ui/chat-thread.js +90 -14
  44. package/dist/ui/panel-chat.js +1 -1
  45. package/dist/ui/panel-settings.js +1 -1
  46. package/dist/ui/shared/app.css +7 -0
  47. package/dist/web-api.d.ts +4 -0
  48. package/dist/web-api.js +135 -43
  49. package/dist/web-api.js.map +1 -1
  50. package/dist/web-chat-history.d.ts +14 -0
  51. package/dist/web-chat-history.js +18 -1
  52. package/dist/web-chat-history.js.map +1 -1
  53. package/dist/web-upload.d.ts +112 -1
  54. package/dist/web-upload.js +237 -36
  55. package/dist/web-upload.js.map +1 -1
  56. package/package.json +3 -3
@@ -7,7 +7,7 @@ import { SettingsExecution, settingsRevision, noteSettingsWrite, settingsFileRes
7
7
  import { performance } from "node:perf_hooks";
8
8
  import { withSpawnHoldReply } from "./spawn-hold-reply.js";
9
9
  import { gatewayRequestContext } from "./web-request-context.js";
10
- import { createPublicWebGateway } from "./public-web-gateway.js";
10
+ import { createPublicWebGateway, PUBLIC_GATEWAY_RECEIVE_MS } from "./public-web-gateway.js";
11
11
  import { handleWebIconRequest } from "./web-icons.js";
12
12
  import { trackWebServer } from "./web-server-drain.js";
13
13
  import { renderPublicLinkProgress, ThrottledMessageEditor } from "./public-link-progress.js";
@@ -144,7 +144,7 @@ import { WEB_NAME_FALLBACK, safeWebName, validateWebName, webNameInUse, webPerso
144
144
  import { ReplyButtonStore, parseReplyButtons, replyButtonClickText, replyButtonsFallbackText, REPLY_BUTTON_PREFIX } from "./reply-buttons.js";
145
145
  import { ReplyButtonsController } from "./reply-buttons-controller.js";
146
146
  import { parsePoll } from "./reply-polls.js";
147
- import { publicAttachment, sweepOrphanedUploads, WebFileLedger } from "./web-upload.js";
147
+ import { isUploadRequest, limitRequestReceive, publicAttachment, sweepOrphanedUploads, UPLOAD_REQUEST_TIMEOUT_MS, uploadLimits, WebFileLedger } from "./web-upload.js";
148
148
  import { REPLY_FILES_MAX_BYTES, snapshotReplyFile, sweepReplySnapshots, webFilesDir } from "./web-reply-files.js";
149
149
  import { handleViewRequest, isViewPath, profileIdentities, resolveInstanceIdentity } from "./view-api.js";
150
150
  import { downloadStickerImage } from "./channel/sticker-download.js";
@@ -162,7 +162,7 @@ import { tightenInstanceDirs } from "./private-dir.js";
162
162
  import { decideSlash } from "./slash-authz.js";
163
163
  import { canonicalCommand, commandSpec, decideCommand, ruleFor } from "./command-table.js";
164
164
  import { runVisibilityCommand } from "./cross-instance-notice.js";
165
- import { installedChannel, isPrereleaseVersion, updateNoticeKey } from "./update-check.js";
165
+ import { installedChannel, updateNoticeKey } from "./update-check.js";
166
166
  import { resolveInstalledAgend } from "./update-dispatch.js";
167
167
  import { resolveUpdateLaunch, openUpdateLog, updateLogPath, watchUpdateLaunch } from "./update-launch.js";
168
168
  import { admitProfileLogin, LoginController, LOGIN_TOKEN_RESEND_PREFIX, POST_LOGIN_RECOVERY_DEADLINE_MS } from "./login-controller.js";
@@ -195,6 +195,7 @@ import { buildOrgChart } from "./web-org.js";
195
195
  import { CacheService, WINDOWS } from "./cache-service.js";
196
196
  import { runWebCommand } from "./web-commands.js";
197
197
  import { CompactWatch, launchIdentity } from "./compact-watch.js";
198
+ import { agyLegacyAgentsFile, danglingSymlink, migrateAgyAgentsFile } from "./backend/antigravity.js";
198
199
  import { claudeConfigDirFor, claudeProjectKey } from "./backend/claude-code.js";
199
200
  import { sharedRolloutIndex } from "./rollout-index.js";
200
201
  import { mayUseTool, resolveToolSet, scheduleOpRefusal, toolForIpcType, toolRefusedMessage, } from "./tool-permissions.js";
@@ -801,7 +802,8 @@ export class FleetManager {
801
802
  /** #1565: the web chat history's files (set at fleet start, once the home and the instances are known). */
802
803
  webChatStore = null;
803
804
  /** Uploaded files and the files the dashboard may fetch back (uploads, reply attachments). */
804
- webFiles = new WebFileLedger();
805
+ // #1937: the upload limits are the config's, read on each use (agend reload applies a change to the next upload).
806
+ webFiles = new WebFileLedger({ limits: () => uploadLimits(this.fleetConfig?.web) });
805
807
  /** #1770: per instance, its last web reply still copying its files — the next one is shown after it, in order. */
806
808
  webReplyOrder = new Map();
807
809
  /** #1770: an instance's web-files byte cap (a field, so a test can make it small). */
@@ -1318,8 +1320,10 @@ export class FleetManager {
1318
1320
  return false;
1319
1321
  reactions.consume();
1320
1322
  this.afterUserInboundDelivered(set.instance, inbound, deliveryEpoch);
1323
+ // The web chat shows the click as its button's label (a chip); `text` stays what the agent was given.
1321
1324
  this.emitSseEvent("message", {
1322
1325
  instance: set.instance, sender: by.username, role: "user", text: content, ts, ...(web ? { messageId } : {}),
1326
+ click: { label: button.label, of: set.id },
1323
1327
  });
1324
1328
  return true;
1325
1329
  }
@@ -2827,6 +2831,14 @@ export class FleetManager {
2827
2831
  return null;
2828
2832
  }
2829
2833
  },
2834
+ paneFollow: (name) => {
2835
+ try {
2836
+ return this.lifecycle.daemons.get(name)?.compactPaneUi() != null;
2837
+ }
2838
+ catch {
2839
+ return false;
2840
+ }
2841
+ },
2830
2842
  changed: () => this.emitSseEvent("status", this.getUiStatus()),
2831
2843
  });
2832
2844
  }
@@ -7279,7 +7291,12 @@ export class FleetManager {
7279
7291
  else if (msg.type === "raw_paste_written") {
7280
7292
  // Board 003648ac: a web /compact's paste reached the pane (or never will): its watch starts, or ends as lost.
7281
7293
  if (typeof msg.id === "string")
7282
- this.compactWatchService?.pasted(name, msg.id, msg.outcome === "written", { owner: msg.owner, checkpoint: msg.checkpoint });
7294
+ this.compactWatchService?.pasted(name, msg.id, msg.outcome === "written", { owner: msg.owner, checkpoint: msg.checkpoint, pane: msg.pane });
7295
+ }
7296
+ else if (msg.type === "compact_pane_result") {
7297
+ // Board 907c7899: kiro's pane said how that /compact ended.
7298
+ if (typeof msg.id === "string")
7299
+ this.compactWatchService?.paneResult(name, msg.id, msg.owner, msg.outcome);
7283
7300
  }
7284
7301
  else if (msg.type === "runtime_switch_result") {
7285
7302
  // #1690: what our own runtime /model came to (the daemon answered claude's "Switch model?" or saw the result).
@@ -14608,6 +14625,7 @@ export class FleetManager {
14608
14625
  messageId: typeof m.messageId === "string" ? m.messageId : undefined,
14609
14626
  role: typeof m.role === "string" ? m.role : undefined,
14610
14627
  buttons: m.buttons,
14628
+ click: m.click,
14611
14629
  });
14612
14630
  broadcastSseEvent(this.sseClients, event, recorded, onError, this.webChatHistory.cursorOf(recorded));
14613
14631
  return;
@@ -14717,12 +14735,12 @@ export class FleetManager {
14717
14735
  "codex": "AGENTS.md",
14718
14736
  "opencode": "AGENTS.md",
14719
14737
  "kiro-cli": ".kiro/steering/project.md",
14720
- // Grok reads AGENTS.md project docs; agy reads .agents/agents.md — the
14738
+ // Grok reads AGENTS.md project docs; agy reads .agents/AGENTS.md (board 0340ec89: not the lowercase name) — the
14721
14739
  // same files their writeConfig() appends fleet instructions to.
14722
14740
  "grok": "AGENTS.md",
14723
14741
  // muse init scaffolds AGENTS.md and the binary reads it as project rules.
14724
14742
  "muse": "AGENTS.md",
14725
- "antigravity": ".agents/agents.md",
14743
+ "antigravity": ".agents/AGENTS.md",
14726
14744
  "mock": "CLAUDE.md",
14727
14745
  };
14728
14746
  static GENERAL_INSTRUCTIONS = `# Fleet Coordinator
@@ -14762,10 +14780,17 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
14762
14780
  ensureGeneralInstructionsSync(workDir, backendName, instanceName) {
14763
14781
  const backend = backendName ?? "claude-code";
14764
14782
  workDir = this.resolveKnowledgeWorkDir(workDir, backend, instanceName);
14783
+ // Board 0340ec89: General's file from before, under the lowercase name agy never read, becomes the one it reads.
14784
+ if (backend === "antigravity")
14785
+ migrateAgyAgentsFile(workDir);
14765
14786
  const filename = FleetManager.INSTRUCTIONS_FILENAME[backend] ?? "CLAUDE.md";
14766
14787
  const filePath = join(workDir, filename);
14767
14788
  mkdirSync(dirname(filePath), { recursive: true });
14768
- if (!existsSync(filePath)) {
14789
+ if (backend === "antigravity" && danglingSymlink(filePath)) {
14790
+ // #1914 review: a person's dangling symlink there is kept and not followed (nothing created where it points).
14791
+ this.logger.warn({ filePath }, "General's agy instructions file is a dangling symlink; left as it is, not written");
14792
+ }
14793
+ else if (!existsSync(filePath)) {
14769
14794
  writeFileSync(filePath, FleetManager.GENERAL_INSTRUCTIONS, "utf-8");
14770
14795
  this.logger.info({ filePath }, "Created general instance instructions file");
14771
14796
  }
@@ -14862,6 +14887,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
14862
14887
  }
14863
14888
  const filename = FleetManager.INSTRUCTIONS_FILENAME[backend] ?? "CLAUDE.md";
14864
14889
  const instructionsPath = join(workDir, filename);
14890
+ if (backend === "antigravity" && danglingSymlink(instructionsPath))
14891
+ return; // kept, not followed (#1914 review; logged above)
14865
14892
  const body = files.map(f => readFileSync(join(srcSteering, f), "utf-8").trim()).join("\n\n");
14866
14893
  const block = `${FleetManager.STEERING_BLOCK_BEGIN}\n${body}\n${FleetManager.STEERING_BLOCK_END}`;
14867
14894
  let existing = "";
@@ -14882,6 +14909,22 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
14882
14909
  mkdirSync(dirname(instructionsPath), { recursive: true });
14883
14910
  writeFileSync(instructionsPath, next);
14884
14911
  }
14912
+ // Board 0340ec89: a legacy agy file that stays beside the new one (a person's own) loses only AgEnD's block.
14913
+ const legacy = backend === "antigravity" ? agyLegacyAgentsFile(workDir) : null;
14914
+ if (legacy) {
14915
+ try {
14916
+ const old = readFileSync(legacy, "utf-8");
14917
+ const b = old.indexOf(FleetManager.STEERING_BLOCK_BEGIN), e = old.indexOf(FleetManager.STEERING_BLOCK_END);
14918
+ if (b !== -1 && e > b) {
14919
+ const rest = (old.slice(0, b).trimEnd() + "\n" + old.slice(e + FleetManager.STEERING_BLOCK_END.length).trimStart()).trim();
14920
+ if (rest)
14921
+ writeFileSync(legacy, rest + "\n");
14922
+ else
14923
+ rmSync(legacy, { force: true });
14924
+ }
14925
+ }
14926
+ catch { /* best effort: the new file already has the block */ }
14927
+ }
14885
14928
  }
14886
14929
  static STEERING_BLOCK_BEGIN = "<!-- >>> agend:core-rules — managed by AgEnD; edits inside this block are overwritten -->";
14887
14930
  static STEERING_BLOCK_END = "<!-- <<< agend:core-rules -->";
@@ -19345,37 +19388,41 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
19345
19388
  const currentVersion = JSON.parse(readFileSync(pkgPath, "utf-8")).version ?? "0.0.0";
19346
19389
  const latest = await npmVersion("@songsid/agend");
19347
19390
  let target = latest;
19391
+ let fromChannel = false;
19348
19392
  // The same channel rule as `agend update` (#1259): an alpha follows @alpha, any other prerelease @beta.
19349
19393
  const channel = installedChannel(currentVersion);
19350
19394
  if (channel !== "latest") {
19351
- // Prerelease users track their own channel (never fall back to @latest,
19352
- // which is older, and an alpha is never offered @beta), but should also
19353
- // hear when a newer STABLE ships — pick whichever is the newest.
19354
19395
  let pre = "";
19355
19396
  try {
19356
19397
  pre = await npmVersion(`@songsid/agend@${channel}`);
19357
19398
  }
19358
19399
  catch { /* no such tag */ }
19359
- target = pre || latest;
19360
- if (latest && this.semverGt(latest, target))
19400
+ // #1926: a beta install hears about @beta only: beta users stay on the beta line after a stable ships, so
19401
+ // @latest is not their target. Taking the higher of the two made a same-core stable (2.2.0 over beta.16) the
19402
+ // target, which the same-core guard then dropped, so the newer beta.17 was never announced.
19403
+ target = pre;
19404
+ fromChannel = true;
19405
+ // An alpha is still told about a stable that passed its own minor (with --stable, #1259): only a stable of
19406
+ // another core that is newer than its channel's release. A same-core @latest (2.2.0 for 2.2.0-alpha.3) is not
19407
+ // taken from @latest and no longer hides a newer alpha; once the release points @alpha at it, it comes from
19408
+ // @alpha like any channel release.
19409
+ const core = (v) => v.replace(/^v/, "").split("-")[0];
19410
+ if (channel === "alpha" && latest && core(latest) !== core(currentVersion) && (!pre || this.semverGt(latest, pre))) {
19361
19411
  target = latest;
19412
+ fromChannel = false;
19413
+ }
19362
19414
  }
19363
- // A beta already at/ahead of its matching stable must NOT be told to
19364
- // "update" to that stable — e.g. 2.0.11-beta.41 already contains everything
19365
- // in stable 2.0.11, so semverGt(2.0.11, 2.0.11-beta.41) being true (a stable
19366
- // outranks a prerelease of the same core) is a false positive here. Suppress
19367
- // only that same-core stable-vs-my-beta case; a higher stable core (2.0.12)
19368
- // or a newer beta (2.0.11-beta.50) still notifies via semverGt below.
19369
- const core = (v) => v.replace(/^v/, "").split("-")[0];
19370
- const betaSupersedesStable = isPrereleaseVersion(currentVersion) && !isPrereleaseVersion(target) && core(target) === core(currentVersion);
19371
19415
  // Only notify when target is genuinely newer (semver), so a beta user on
19372
19416
  // 2.0.8-beta.16 is never told that stable 2.0.7 is "available".
19373
- if (target && !betaSupersedesStable && this.semverGt(target, currentVersion)) {
19417
+ if (target && this.semverGt(target, currentVersion)) {
19374
19418
  const generalId = this.findGeneralInstance();
19375
19419
  if (generalId) {
19376
19420
  // No release URL — Discord's SuppressEmbeds proved unreliable and the
19377
19421
  // link preview looked bad. Version + /update instruction is enough.
19378
- this.notifyInstanceTopic(generalId, t(updateNoticeKey(currentVersion, target), `v${target}`, `v${currentVersion}`));
19422
+ // From the install's own channel tag, this is what /update installs — even a stable the release pointed the
19423
+ // tag at (2.2.0 on @alpha / @beta): the plain /update text, never "/update stays on … use --stable" (#1928).
19424
+ const key = fromChannel ? "update.available_current" : updateNoticeKey(currentVersion, target);
19425
+ this.notifyInstanceTopic(generalId, t(key, `v${target}`, `v${currentVersion}`));
19379
19426
  }
19380
19427
  }
19381
19428
  }
@@ -19442,6 +19489,10 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
19442
19489
  /** Shared handler dispatcher; gateway admission is enforced by its separate listener. */
19443
19490
  dispatchWebHttp(req, res, port, closing = false) {
19444
19491
  try {
19492
+ // #1937: the listeners allow an upload long enough for a slow link; every other request keeps its old bound on
19493
+ // arriving whole — 10 s through the public link's gateway, Node's 300 s here.
19494
+ if (!isUploadRequest(req))
19495
+ limitRequestReceive(req, gatewayRequestContext(req) ? PUBLIC_GATEWAY_RECEIVE_MS : 300_000);
19445
19496
  res.setHeader("Content-Type", "application/json");
19446
19497
  // No Referer to a tunnel host, an upstream proxy, or any page linked from
19447
19498
  // the panel — the dashboard URL is itself a credential-bearing address.
@@ -19783,6 +19834,10 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
19783
19834
  if (!this.webToken || !this.webSessions)
19784
19835
  this.initializeWebAuthTokens();
19785
19836
  const server = createServer((req, res) => this.dispatchWebHttp(req, res, port, drain.closing));
19837
+ // #1937: Node's default (300 s to receive a request) would cut a large upload on a slow link. The listener allows
19838
+ // more; the upload route keeps its own clocks (no byte for a minute, or 30 minutes in all), and every other request
19839
+ // keeps the old 300 s (limitRequestReceive in dispatchWebHttp).
19840
+ server.requestTimeout = UPLOAD_REQUEST_TIMEOUT_MS;
19786
19841
  const drain = trackWebServer(server);
19787
19842
  this.healthServer = server;
19788
19843
  this.healthServerDrain = drain;