dsh-lark-agent 0.2.0 → 0.3.1

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/client.cjs CHANGED
@@ -111,6 +111,15 @@ function parseSnapshot(value) {
111
111
  snapshot.documentSetup.qr = { url: officialUrl(qr.url), image, expiresAt: timestamp(qr.expiresAt) };
112
112
  }
113
113
  }
114
+ if (data.update !== void 0) {
115
+ const update = object(data.update);
116
+ const version = (value2) => {
117
+ const raw = text(value2, 64);
118
+ if (!/^\d{1,9}\.\d{1,9}\.\d{1,9}(?:-[0-9A-Za-z.-]{1,40})?$/.test(raw)) throw new Error("invalid version");
119
+ return raw;
120
+ };
121
+ snapshot.update = { current: version(update.current), latest: version(update.latest) };
122
+ }
114
123
  return snapshot;
115
124
  }
116
125
  function parseProfiles(value) {
@@ -275,6 +284,8 @@ var deepseek_harness_icon_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEU
275
284
 
276
285
  // src/client/ConnectionCard.tsx
277
286
  var import_jsx_runtime = require("react/jsx-runtime");
287
+ var UPGRADE_COMMAND = "dsh plugin --profile web update dsh-lark-agent@latest";
288
+ var UPGRADE_GUIDE = "https://github.com/Nirvana-Jie/dsh-lark-agent/blob/main/DEVELOPMENT.md#\u5347\u7EA7";
278
289
  var phaseLabels = {
279
290
  idle: "\u672A\u8FDE\u63A5",
280
291
  creating: "\u6B63\u5728\u521B\u5EFA",
@@ -319,7 +330,54 @@ function Steps({ active }) {
319
330
  label
320
331
  ] }, label)) });
321
332
  }
322
- function ConnectionCard({ api }) {
333
+ function UpdateNotice({ update, host, phase }) {
334
+ const [open, setOpen] = (0, import_react2.useState)(false);
335
+ const [status, setStatus] = (0, import_react2.useState)("");
336
+ const app = host?.app ?? false;
337
+ const settled = phase === "connected" || phase === "paused";
338
+ const copy = async () => {
339
+ try {
340
+ await navigator.clipboard.writeText(UPGRADE_COMMAND);
341
+ setStatus("\u5DF2\u590D\u5236\u5347\u7EA7\u547D\u4EE4");
342
+ } catch {
343
+ setStatus("\u8BF7\u9009\u4E2D\u4E0A\u65B9\u547D\u4EE4\u624B\u52A8\u590D\u5236");
344
+ }
345
+ };
346
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "lark-update", children: [
347
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "lark-update-row", children: [
348
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
349
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { children: [
350
+ "\u98DE\u4E66\u63D2\u4EF6\u6709\u65B0\u7248\u672C ",
351
+ update.latest
352
+ ] }),
353
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
354
+ "\u5F53\u524D ",
355
+ update.current,
356
+ "\uFF0C\u5347\u7EA7\u540E\u4FDD\u7559\u73B0\u6709\u8FDE\u63A5\u548C\u5BF9\u8BDD\u3002"
357
+ ] })
358
+ ] }),
359
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "lark-secondary", "aria-expanded": open, onClick: () => setOpen(!open), children: open ? "\u6536\u8D77" : "\u5347\u7EA7" })
360
+ ] }),
361
+ open && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
362
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ol", { className: "lark-instructions", children: [
363
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: phase === "paused" ? "\u8FDE\u63A5\u5DF2\u6682\u505C\uFF0C\u53EF\u4EE5\u76F4\u63A5\u5347\u7EA7\u3002" : settled ? "\u5148\u70B9\u4E0B\u65B9\u300C\u6682\u505C\u8FDE\u63A5\u300D\u3002" : "\u5148\u5B8C\u6210\u6216\u53D6\u6D88\u5F53\u524D\u7684\u8FDE\u63A5\u6B65\u9AA4\u3002" }),
364
+ app ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "\u5728\u300C\u63D2\u4EF6\u5217\u8868\u300D\u5378\u8F7D dsh-lark-agent\uFF0C\u518D\u70B9\u300C\u6DFB\u52A0\u63D2\u4EF6\u300D\u8F93\u5165 dsh-lark-agent \u5B89\u88C5\u65B0\u7248\u3002" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
365
+ "\u5728\u7EC8\u7AEF\u8FD0\u884C\u4E0B\u9762\u7684\u547D\u4EE4\uFF0C\u7136\u540E\u91CD\u542F dsh web\uFF1A",
366
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: "lark-command", children: UPGRADE_COMMAND })
367
+ ] }),
368
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: settled ? "\u56DE\u5230\u8FD9\u91CC\u70B9\u300C\u6062\u590D\u8FDE\u63A5\u300D\uFF0C\u5728\u98DE\u4E66\u53D1\u4E00\u53E5\u8BDD\u786E\u8BA4\u80FD\u56DE\u590D\u3002" : "\u5347\u7EA7\u540E\u56DE\u5230\u8FD9\u91CC\u7EE7\u7EED\u3002" })
369
+ ] }),
370
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "lark-actions", children: [
371
+ app ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "lark-secondary", onClick: () => {
372
+ if (!host?.openPlugins?.()) setStatus("\u8BF7\u5207\u5230\u4E0A\u65B9\u7684\u300C\u63D2\u4EF6\u5217\u8868\u300D\u7EE7\u7EED\u3002");
373
+ }, children: "\u6253\u5F00\u63D2\u4EF6\u5217\u8868" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "lark-secondary", onClick: () => void copy(), children: "\u590D\u5236\u5347\u7EA7\u547D\u4EE4" }),
374
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(External, { className: "lark-text-button", url: UPGRADE_GUIDE, children: "\u5347\u7EA7\u524D\u7684\u5907\u4EFD\u8BF4\u660E" })
375
+ ] }),
376
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "lark-copy-status", role: "status", children: status })
377
+ ] })
378
+ ] });
379
+ }
380
+ function ConnectionCard({ api, host }) {
323
381
  const { snapshot, error, pending, profiles, refresh, mutate, loadProfiles } = useConnection(api);
324
382
  const [chooseExisting, setChooseExisting] = (0, import_react2.useState)(false);
325
383
  const [selected, setSelected] = (0, import_react2.useState)("");
@@ -506,7 +564,7 @@ function ConnectionCard({ api }) {
506
564
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "lark-actions", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "lark-quiet", disabled, onClick: cancel, children: "\u53D6\u6D88\u8FDE\u63A5" }) })
507
565
  ] });
508
566
  if (phase === "connected" || phase === "paused") return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
509
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Steps, { active: 2 }),
567
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Steps, { active: 3 }),
510
568
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BrandLink, { connected: phase === "connected" }),
511
569
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "lark-intro-title", children: phase === "paused" ? "\u5DF2\u6682\u505C\u98DE\u4E66\u8FDE\u63A5" : "\u53EF\u4EE5\u5728\u98DE\u4E66\u627E\u6211\u4E86" }),
512
570
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "lark-copy lark-centered", children: phase === "paused" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
@@ -598,6 +656,7 @@ function ConnectionCard({ api }) {
598
656
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: error }),
599
657
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "lark-text-button", disabled, onClick: refresh, children: "\u91CD\u8BD5" })
600
658
  ] }),
659
+ snapshot?.update && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpdateNotice, { update: snapshot.update, host, phase }),
601
660
  body(),
602
661
  pending && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "lark-pending", role: "status", children: [
603
662
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "lark-spinner", "aria-hidden": "true" }),
@@ -612,10 +671,10 @@ function ConnectionCard({ api }) {
612
671
  // src/client/styles.ts
613
672
  var css = `
614
673
  .dsh-lark-settings{--lark-ink:#263247;--lark-muted:#738096;--lark-line:#e4eaf2;--lark-blue:#4268df;--lark-surface:#fff;--lark-soft:#f5f8fd;font:14px/1.65 -apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;color:var(--lark-ink);max-width:720px;margin:0 auto;padding:8px 0 20px;box-sizing:border-box}
615
- .dsh-lark-settings *{box-sizing:border-box}.dsh-lark-settings button,.dsh-lark-settings input{font:inherit}.dsh-lark-settings button,.dsh-lark-settings summary{cursor:pointer}.dsh-lark-settings button:disabled{opacity:.5;cursor:not-allowed}.dsh-lark-settings button:focus-visible,.dsh-lark-settings a:focus-visible,.dsh-lark-settings input:focus-visible,.dsh-lark-settings summary:focus-visible{outline:3px solid #a1b8ff;outline-offset:3px}.dsh-lark-settings a{overflow-wrap:anywhere}.lark-heading{margin:0 0 22px}.lark-heading h2{font-size:23px;font-weight:650;margin:0 0 5px;letter-spacing:-.4px}.lark-heading p,.lark-card-header p{color:var(--lark-muted);font-size:13px;margin:0}.lark-card{border:1px solid var(--lark-line);border-radius:15px;background:var(--lark-surface);overflow:hidden;box-shadow:0 2px 5px #27385504}.lark-card-header{display:flex;align-items:center;gap:12px;padding:19px 23px;border-bottom:1px solid var(--lark-line)}.lark-plugin-icon{width:43px;height:43px;object-fit:contain;flex-shrink:0}.lark-card-header h2{font-size:15px;font-weight:650;line-height:1.6;margin:0}.lark-card-header p{font-size:12px}.lark-status{margin-left:auto;display:flex;align-items:center;gap:6px;font-size:11px;color:var(--lark-muted);white-space:nowrap}.lark-status i{width:6px;height:6px;border-radius:50%;background:#9da9bc}.lark-status.online{color:#248470}.lark-status.online i{background:#248470}.lark-body{padding:28px;position:relative}.lark-body h3{font-size:20px;font-weight:650;letter-spacing:-.25px;margin:0 0 9px;line-height:1.5}.lark-copy{color:var(--lark-muted);font-size:13px;margin:0 0 20px}.lark-centered{text-align:center;line-height:1.9}.lark-body .lark-intro-title{text-align:center;font-size:21px}.lark-brands{display:flex;justify-content:center;align-items:center;gap:16px;margin:5px 0 25px}.lark-brand{width:53px;height:53px;display:grid;place-items:center;border-radius:14px;background:var(--lark-soft);border:1px solid var(--lark-line)}.lark-brand img{width:37px;height:37px;object-fit:contain}.lark-brand-line{width:78px;height:1px;background:#dbe3ee;position:relative}.lark-brand-line:after{content:"";position:absolute;left:calc(50% - 3px);top:-3px;width:7px;height:7px;border-radius:50%;background:#a9bbdc;border:2px solid var(--lark-surface)}.lark-brands-connected .lark-brand-line{background:#99d1c1}.lark-brands-connected .lark-brand-line:after{background:#248470}.lark-primary,.lark-secondary,.lark-quiet,.lark-danger{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:38px;padding:8px 16px;border-radius:8px;text-decoration:none;font-size:13px;font-weight:550;border:1px solid transparent;line-height:1.5;transition:background .14s}.lark-primary{background:var(--lark-blue);color:#fff}.lark-primary:hover{background:#3559ca}.lark-secondary{color:#485b79;background:var(--lark-surface);border-color:#dce3ed}.lark-secondary:hover{background:var(--lark-soft)}.lark-quiet{background:transparent;color:var(--lark-muted);font-weight:450}.lark-quiet:hover{background:var(--lark-soft)}.lark-danger{color:#ad4949;border-color:#e5bcbc;background:var(--lark-surface)}.lark-text-button{display:inline-flex;align-items:center;gap:7px;background:transparent;border:0;padding:5px 0;color:#5875a5;font-size:12px}.lark-entry{display:flex;flex-direction:column;align-items:center;gap:10px}.lark-entry .lark-primary{min-width:200px}.lark-actions{display:flex;gap:9px;flex-wrap:wrap;align-items:center;margin-top:23px}.lark-spread{justify-content:space-between}.lark-center{justify-content:center}.lark-footnote{color:var(--lark-muted);font-size:11px;text-align:center;margin:18px 0 0}.lark-left{text-align:left}.lark-bottom-note{text-align:center;color:var(--lark-muted);font-size:11px;margin:15px 0 0}.lark-steps{display:flex;list-style:none;gap:12px;margin:0 0 25px;padding:0;color:#8b98ad;font-size:11px}.lark-steps li{display:flex;gap:6px;align-items:center;white-space:nowrap}.lark-steps li+li:before{content:"";width:23px;height:1px;background:var(--lark-line);margin-right:5px}.lark-steps li span{width:18px;height:18px;display:grid;place-items:center;border-radius:50%;background:var(--lark-soft);font-size:10px}.lark-steps .active{color:var(--lark-blue);font-weight:600}.lark-steps .active span{background:#edf2ff}.lark-steps .done{color:#248470}.lark-steps .done span{background:#edf8f4}.lark-qr-layout{display:flex;align-items:center;gap:25px;margin:23px 0}.lark-qr{position:relative;width:166px;height:166px;padding:10px;background:white;border:1px solid var(--lark-line);border-radius:12px;flex-shrink:0}.lark-qr img{width:100%;height:100%;object-fit:contain}.lark-qr.expired img{opacity:.12}.lark-qr>span{position:absolute;inset:0;display:grid;place-items:center;color:#64758e;font-weight:600}.lark-qr-layout strong{font-weight:600;font-size:13px}.lark-qr-layout .lark-copy{margin:7px 0 10px}.lark-time{color:var(--lark-muted);font-size:11px;font-variant-numeric:tabular-nums}.lark-link-details{border-top:1px solid var(--lark-line);padding:13px 0 0;font-size:12px;color:var(--lark-muted)}.lark-link-details summary{margin-bottom:12px}.lark-raw-url{font-size:10px;line-height:1.65;overflow-wrap:anywhere;margin-top:10px;color:var(--lark-muted);user-select:all}.lark-notice{padding:13px 15px;background:#eef3fe;border-radius:9px;color:#526c95;font-size:12px;line-height:1.8}.lark-notice.warning{background:#fff7e9;color:#8b671e}.lark-state-icon{display:grid;place-items:center;width:40px;height:40px;border-radius:50%;background:#edf8f4;color:#248470;font-size:20px;margin:2px 0 18px}.lark-state-icon.warning{background:#fff7e9;color:#9b7530}.lark-checks{padding:0;list-style:none;margin:20px 0;font-size:13px;color:var(--lark-muted)}.lark-checks li{padding:10px 0;border-bottom:1px solid var(--lark-line)}.lark-checks li:before{content:"\xB7";margin-right:12px}.lark-pairing{background:var(--lark-soft);border:1px solid var(--lark-line);border-radius:11px;padding:17px 18px;display:flex;align-items:center;justify-content:space-between;gap:13px}.lark-pair-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:21px;letter-spacing:.02em;color:#3d5a91;margin:4px 0 2px;overflow-wrap:anywhere;user-select:all}.lark-pairing.expired .lark-pair-code{opacity:.5}.lark-caption{font-size:11px;color:var(--lark-muted)}.lark-copy-status{min-height:18px;font-size:11px;color:#248470;margin:5px 0}.lark-instructions{padding-left:20px;color:#536784;font-size:13px}.lark-instructions li{padding:3px 0}.lark-owner{padding:17px 18px;background:var(--lark-soft);border:1px solid var(--lark-line);border-radius:10px;display:grid;gap:6px}.lark-owner strong{font-size:15px;font-weight:600;overflow-wrap:anywhere}.lark-connected-row{border-top:1px solid var(--lark-line);border-bottom:1px solid var(--lark-line);display:flex;justify-content:space-between;align-items:center;gap:16px;padding:16px 0;margin:26px 0 0}.lark-connected-row strong{font-size:12px;font-weight:600}.lark-connected-row p{font-size:11px;color:var(--lark-muted);margin:3px 0 0}.lark-advanced{font-size:12px;color:var(--lark-muted);padding-top:14px}.lark-advanced p{margin:9px 0 0;line-height:1.9}.lark-disconnect{border:1px solid #eacbcb;padding:14px;border-radius:10px;margin-top:16px;font-size:12px}.lark-disconnect p{margin:0}.lark-disconnect .lark-actions{margin-top:12px}.lark-profile-list{border:0;margin:20px 0 0;padding:0;display:grid;gap:10px;min-width:0}.lark-profile{display:flex;align-items:center;gap:12px;border:1px solid var(--lark-line);border-radius:10px;padding:15px;background:var(--lark-soft);cursor:pointer;overflow-wrap:anywhere}.lark-profile input{accent-color:var(--lark-blue)}.lark-error{display:flex;align-items:center;justify-content:space-between;gap:12px;background:#fff1f1;border:1px solid #f0d5d5;color:#9b4e4e;border-radius:9px;padding:11px 13px;font-size:12px;margin-bottom:21px}.lark-error .lark-text-button{flex-shrink:0;color:#9b4e4e}.lark-loading{padding:27px 0;text-align:center}.lark-loading .lark-spinner{margin:0 auto 17px;display:block}.lark-loading p{font-size:13px;color:var(--lark-muted);margin:0}.lark-loading-row{display:flex;align-items:center;gap:12px;color:var(--lark-muted);font-size:13px;margin:4px 0 15px}.lark-loading-row h3{color:var(--lark-ink);margin:0}.lark-spinner{display:inline-block;width:18px;height:18px;border:2px solid #dfe6f4;border-top-color:var(--lark-blue);border-radius:50%;animation:lark-spin .85s linear infinite;flex-shrink:0}.lark-pending{display:flex;align-items:center;gap:9px;margin-top:19px;font-size:11px;color:var(--lark-muted)}.lark-pending .lark-spinner{width:13px;height:13px}.lark-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
674
+ .dsh-lark-settings *{box-sizing:border-box}.dsh-lark-settings button,.dsh-lark-settings input{font:inherit}.dsh-lark-settings button,.dsh-lark-settings summary{cursor:pointer}.dsh-lark-settings button:disabled{opacity:.5;cursor:not-allowed}.dsh-lark-settings button:focus-visible,.dsh-lark-settings a:focus-visible,.dsh-lark-settings input:focus-visible,.dsh-lark-settings summary:focus-visible{outline:3px solid #a1b8ff;outline-offset:3px}.dsh-lark-settings a{overflow-wrap:anywhere}.lark-heading{margin:0 0 22px}.lark-heading h2{font-size:23px;font-weight:650;margin:0 0 5px;letter-spacing:-.4px}.lark-heading p,.lark-card-header p{color:var(--lark-muted);font-size:13px;margin:0}.lark-card{border:1px solid var(--lark-line);border-radius:15px;background:var(--lark-surface);overflow:hidden;box-shadow:0 2px 5px #27385504}.lark-card-header{display:flex;align-items:center;gap:12px;padding:19px 23px;border-bottom:1px solid var(--lark-line)}.lark-plugin-icon{width:43px;height:43px;object-fit:contain;flex-shrink:0}.lark-card-header h2{font-size:15px;font-weight:650;line-height:1.6;margin:0}.lark-card-header p{font-size:12px}.lark-status{margin-left:auto;display:flex;align-items:center;gap:6px;font-size:11px;color:var(--lark-muted);white-space:nowrap}.lark-status i{width:6px;height:6px;border-radius:50%;background:#9da9bc}.lark-status.online{color:#248470}.lark-status.online i{background:#248470}.lark-body{padding:28px;position:relative}.lark-body h3{font-size:20px;font-weight:650;letter-spacing:-.25px;margin:0 0 9px;line-height:1.5}.lark-copy{color:var(--lark-muted);font-size:13px;margin:0 0 20px}.lark-centered{text-align:center;line-height:1.9}.lark-body .lark-intro-title{text-align:center;font-size:21px}.lark-brands{display:flex;justify-content:center;align-items:center;gap:16px;margin:5px 0 25px}.lark-brand{width:53px;height:53px;display:grid;place-items:center;border-radius:14px;background:var(--lark-soft);border:1px solid var(--lark-line)}.lark-brand img{width:37px;height:37px;object-fit:contain}.lark-brand-line{width:78px;height:1px;background:#dbe3ee;position:relative}.lark-brand-line:after{content:"";position:absolute;left:calc(50% - 3px);top:-3px;width:7px;height:7px;border-radius:50%;background:#a9bbdc;border:2px solid var(--lark-surface)}.lark-brands-connected .lark-brand-line{background:#99d1c1}.lark-brands-connected .lark-brand-line:after{background:#248470}.lark-primary,.lark-secondary,.lark-quiet,.lark-danger{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:38px;padding:8px 16px;border-radius:8px;text-decoration:none;font-size:13px;font-weight:550;border:1px solid transparent;line-height:1.5;transition:background .14s}.lark-primary{background:var(--lark-blue);color:#fff}.lark-primary:hover{background:#3559ca}.lark-secondary{color:#485b79;background:var(--lark-surface);border-color:#dce3ed}.lark-secondary:hover{background:var(--lark-soft)}.lark-quiet{background:transparent;color:var(--lark-muted);font-weight:450}.lark-quiet:hover{background:var(--lark-soft)}.lark-danger{color:#ad4949;border-color:#e5bcbc;background:var(--lark-surface)}.lark-text-button{display:inline-flex;align-items:center;gap:7px;background:transparent;border:0;padding:5px 0;color:#5875a5;font-size:12px}.lark-entry{display:flex;flex-direction:column;align-items:center;gap:10px}.lark-entry .lark-primary{min-width:200px}.lark-actions{display:flex;gap:9px;flex-wrap:wrap;align-items:center;margin-top:23px}.lark-spread{justify-content:space-between}.lark-center{justify-content:center}.lark-footnote{color:var(--lark-muted);font-size:11px;text-align:center;margin:18px 0 0}.lark-left{text-align:left}.lark-bottom-note{text-align:center;color:var(--lark-muted);font-size:11px;margin:15px 0 0}.lark-steps{display:flex;list-style:none;gap:12px;margin:0 0 25px;padding:0;color:#8b98ad;font-size:11px}.lark-steps li{display:flex;gap:6px;align-items:center;white-space:nowrap}.lark-steps li+li:before{content:"";width:23px;height:1px;background:var(--lark-line);margin-right:5px}.lark-steps li span{width:18px;height:18px;display:grid;place-items:center;border-radius:50%;background:var(--lark-soft);font-size:10px}.lark-steps .active{color:var(--lark-blue);font-weight:600}.lark-steps .active span{background:#edf2ff}.lark-steps .done{color:#248470}.lark-steps .done span{background:#edf8f4}.lark-qr-layout{display:flex;align-items:center;gap:25px;margin:23px 0}.lark-qr{position:relative;width:166px;height:166px;padding:10px;background:white;border:1px solid var(--lark-line);border-radius:12px;flex-shrink:0}.lark-qr img{width:100%;height:100%;object-fit:contain}.lark-qr.expired img{opacity:.12}.lark-qr>span{position:absolute;inset:0;display:grid;place-items:center;color:#64758e;font-weight:600}.lark-qr-layout strong{font-weight:600;font-size:13px}.lark-qr-layout .lark-copy{margin:7px 0 10px}.lark-time{color:var(--lark-muted);font-size:11px;font-variant-numeric:tabular-nums}.lark-link-details{border-top:1px solid var(--lark-line);padding:13px 0 0;font-size:12px;color:var(--lark-muted)}.lark-link-details summary{margin-bottom:12px}.lark-raw-url{font-size:10px;line-height:1.65;overflow-wrap:anywhere;margin-top:10px;color:var(--lark-muted);user-select:all}.lark-notice{padding:13px 15px;background:#eef3fe;border-radius:9px;color:#526c95;font-size:12px;line-height:1.8}.lark-notice.warning{background:#fff7e9;color:#8b671e}.lark-state-icon{display:grid;place-items:center;width:40px;height:40px;border-radius:50%;background:#edf8f4;color:#248470;font-size:20px;margin:2px 0 18px}.lark-state-icon.warning{background:#fff7e9;color:#9b7530}.lark-checks{padding:0;list-style:none;margin:20px 0;font-size:13px;color:var(--lark-muted)}.lark-checks li{padding:10px 0;border-bottom:1px solid var(--lark-line)}.lark-checks li:before{content:"\xB7";margin-right:12px}.lark-pairing{background:var(--lark-soft);border:1px solid var(--lark-line);border-radius:11px;padding:17px 18px;display:flex;align-items:center;justify-content:space-between;gap:13px}.lark-pair-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:21px;letter-spacing:.02em;color:#3d5a91;margin:4px 0 2px;overflow-wrap:anywhere;user-select:all}.lark-pairing.expired .lark-pair-code{opacity:.5}.lark-caption{font-size:11px;color:var(--lark-muted)}.lark-copy-status{min-height:18px;font-size:11px;color:#248470;margin:5px 0}.lark-instructions{padding-left:20px;color:#536784;font-size:13px}.lark-instructions li{padding:3px 0}.lark-owner{padding:17px 18px;background:var(--lark-soft);border:1px solid var(--lark-line);border-radius:10px;display:grid;gap:6px}.lark-owner strong{font-size:15px;font-weight:600;overflow-wrap:anywhere}.lark-connected-row{border-top:1px solid var(--lark-line);border-bottom:1px solid var(--lark-line);display:flex;justify-content:space-between;align-items:center;gap:16px;padding:16px 0;margin:26px 0 0}.lark-connected-row strong{font-size:12px;font-weight:600}.lark-connected-row p{font-size:11px;color:var(--lark-muted);margin:3px 0 0}.lark-advanced{font-size:12px;color:var(--lark-muted);padding-top:14px}.lark-advanced p{margin:9px 0 0;line-height:1.9}.lark-disconnect{border:1px solid #eacbcb;padding:14px;border-radius:10px;margin-top:16px;font-size:12px}.lark-disconnect p{margin:0}.lark-disconnect .lark-actions{margin-top:12px}.lark-profile-list{border:0;margin:20px 0 0;padding:0;display:grid;gap:10px;min-width:0}.lark-profile{display:flex;align-items:center;gap:12px;border:1px solid var(--lark-line);border-radius:10px;padding:15px;background:var(--lark-soft);cursor:pointer;overflow-wrap:anywhere}.lark-profile input{accent-color:var(--lark-blue)}.lark-error{display:flex;align-items:center;justify-content:space-between;gap:12px;background:#fff1f1;border:1px solid #f0d5d5;color:#9b4e4e;border-radius:9px;padding:11px 13px;font-size:12px;margin-bottom:21px}.lark-error .lark-text-button{flex-shrink:0;color:#9b4e4e}.lark-loading{padding:27px 0;text-align:center}.lark-loading .lark-spinner{margin:0 auto 17px;display:block}.lark-loading p{font-size:13px;color:var(--lark-muted);margin:0}.lark-loading-row{display:flex;align-items:center;gap:12px;color:var(--lark-muted);font-size:13px;margin:4px 0 15px}.lark-loading-row h3{color:var(--lark-ink);margin:0}.lark-spinner{display:inline-block;width:18px;height:18px;border:2px solid #dfe6f4;border-top-color:var(--lark-blue);border-radius:50%;animation:lark-spin .85s linear infinite;flex-shrink:0}.lark-pending{display:flex;align-items:center;gap:9px;margin-top:19px;font-size:11px;color:var(--lark-muted)}.lark-pending .lark-spinner{width:13px;height:13px}.lark-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.lark-update{border:1px solid #cfdcf8;background:#f4f7ff;border-radius:11px;padding:14px 16px;margin:0 0 22px}.lark-update-row{display:flex;align-items:center;justify-content:space-between;gap:14px}.lark-update strong{font-size:13px;font-weight:600}.lark-update p{font-size:12px;color:var(--lark-muted);margin:3px 0 0}.lark-update .lark-instructions{margin:12px 0 0}.lark-update .lark-actions{margin-top:12px}.lark-update .lark-copy-status{margin:6px 0 0}.lark-command{display:block;margin-top:6px;padding:8px 10px;border-radius:7px;background:var(--lark-surface);border:1px solid var(--lark-line);font:12px/1.6 ui-monospace,SFMono-Regular,Consolas,monospace;overflow-wrap:anywhere;user-select:all}
616
675
  @keyframes lark-spin{to{transform:rotate(360deg)}}
617
- @media(prefers-color-scheme:dark){.dsh-lark-settings{--lark-ink:#e0e7f1;--lark-muted:#96a6bd;--lark-line:#364357;--lark-surface:#202a39;--lark-soft:#283448;--lark-blue:#5077ec}.lark-secondary{color:#c6d5ed;border-color:#46536b}.lark-pair-code{color:#b9ceff}.lark-instructions{color:#b0c2de}.lark-notice{background:#273d5a;color:#c2d3ec}.lark-notice.warning,.lark-state-icon.warning{background:#44391f;color:#e4c886}.lark-error{background:#492e32;border-color:#684047;color:#edbec2}.lark-error .lark-text-button{color:#edbec2}.lark-steps .active span{background:#293e6b}.lark-steps .done span,.lark-state-icon{background:#23483f}.lark-status.online,.lark-steps .done{color:#78c5b0}}
618
- @media(max-width:520px){.lark-heading{margin-bottom:17px}.lark-card-header{padding:16px;gap:10px}.lark-card-header p{display:none}.lark-plugin-icon{width:37px;height:37px}.lark-body{padding:21px 18px}.lark-steps{gap:7px}.lark-steps li+li:before{width:12px;margin-right:2px}.lark-qr-layout{gap:17px;align-items:flex-start}.lark-qr{width:138px;height:138px}.lark-qr-layout strong{font-size:12px}.lark-qr-layout .lark-copy{font-size:12px}.lark-pairing{align-items:flex-start;flex-direction:column}.lark-pair-code{font-size:19px}.lark-body h3{font-size:19px}.lark-connected-row{gap:10px}.lark-actions{gap:8px}.lark-primary,.lark-secondary,.lark-quiet,.lark-danger{padding:8px 12px}.lark-copy{font-size:12px}}
676
+ @media(prefers-color-scheme:dark){.dsh-lark-settings{--lark-ink:#e0e7f1;--lark-muted:#96a6bd;--lark-line:#364357;--lark-surface:#202a39;--lark-soft:#283448;--lark-blue:#5077ec}.lark-secondary{color:#c6d5ed;border-color:#46536b}.lark-pair-code{color:#b9ceff}.lark-instructions{color:#b0c2de}.lark-notice{background:#273d5a;color:#c2d3ec}.lark-notice.warning,.lark-state-icon.warning{background:#44391f;color:#e4c886}.lark-error{background:#492e32;border-color:#684047;color:#edbec2}.lark-error .lark-text-button{color:#edbec2}.lark-steps .active span{background:#293e6b}.lark-steps .done span,.lark-state-icon{background:#23483f}.lark-status.online,.lark-steps .done{color:#78c5b0}.lark-update{background:#24324a;border-color:#3a4d70}}
677
+ @media(max-width:520px){.lark-heading{margin-bottom:17px}.lark-card-header{padding:16px;gap:10px}.lark-card-header p{display:none}.lark-plugin-icon{width:37px;height:37px}.lark-body{padding:21px 18px}.lark-steps{gap:7px}.lark-steps li+li:before{width:12px;margin-right:2px}.lark-qr-layout{gap:17px;align-items:flex-start}.lark-qr{width:138px;height:138px}.lark-qr-layout strong{font-size:12px}.lark-qr-layout .lark-copy{font-size:12px}.lark-pairing{align-items:flex-start;flex-direction:column}.lark-pair-code{font-size:19px}.lark-body h3{font-size:19px}.lark-connected-row{gap:10px}.lark-actions{gap:8px}.lark-primary,.lark-secondary,.lark-quiet,.lark-danger{padding:8px 12px}.lark-copy{font-size:12px}.lark-update-row{flex-direction:column;align-items:flex-start}}
619
678
  @media(max-width:360px){.lark-qr-layout{flex-direction:column;align-items:center;text-align:center}.lark-steps{font-size:10px}.lark-steps li+li:before{width:8px}.lark-status{font-size:10px}}
620
679
  @media(prefers-reduced-motion:reduce){.dsh-lark-settings *{animation:none!important;transition:none!important}}
621
680
  `;
@@ -630,14 +689,25 @@ function installStyles() {
630
689
  // src/client/index.tsx
631
690
  var inject = ["slots", "connection"];
632
691
  function apply(ctx) {
633
- const api = new ConnectionApi(ctx.connection.rpc, ctx.connection.generation !== void 0);
692
+ const app = ctx.connection.generation !== void 0;
693
+ const api = new ConnectionApi(ctx.connection.rpc, app);
694
+ const openPlugins = () => {
695
+ try {
696
+ const navigation = ctx.get?.("pluginNavigation");
697
+ if (typeof navigation?.openList !== "function") return false;
698
+ navigation.openList();
699
+ return true;
700
+ } catch {
701
+ return false;
702
+ }
703
+ };
634
704
  ctx.effect(installStyles, "dsh-larkbot: connection styles");
635
705
  ctx.slots.inject("settings.plugins.tab", () => ctx.slots.register({
636
706
  name: "settings.plugins.tab",
637
707
  id: "dsh-larkbot",
638
708
  order: 30,
639
709
  label: () => "\u98DE\u4E66",
640
- inject: () => ({ api })
710
+ inject: () => ({ api, host: { app, openPlugins } })
641
711
  }, ConnectionCard));
642
712
  }
643
713
  return module.exports; } });
@@ -47,10 +47,12 @@ export type TurnStreamEvent = {
47
47
  callId: string;
48
48
  name: string;
49
49
  detail?: string;
50
+ code?: boolean;
50
51
  } | {
51
52
  type: 'tool-done';
52
53
  callId: string;
53
54
  ok: boolean;
55
+ exitCode?: number;
54
56
  };
55
57
  export interface ApprovalPrompt {
56
58
  toolName: string;
@@ -79,9 +81,11 @@ export interface PermissionOption {
79
81
  name: string;
80
82
  description?: string;
81
83
  }
84
+ /** defaultPreset is the App's choice for new sessions, when it is one of the options. */
82
85
  export interface PermissionState {
83
86
  current: string;
84
87
  options: PermissionOption[];
88
+ defaultPreset?: string;
85
89
  }
86
90
  /** One turn's channel hooks. A harness-mode session asks its owner through them. */
87
91
  export interface TurnObserver {
@@ -123,14 +127,24 @@ export interface RuntimeSession {
123
127
  cancel(): void;
124
128
  dispose(): Promise<void>;
125
129
  modelSelection?(): ModelSelection;
126
- /** Call only after the bridge validates and durably saves the complete choice. */
130
+ /**
131
+ * Call only after the bridge validates and durably saves the complete choice.
132
+ * In a session that shares its model with the App it is recorded in the
133
+ * session itself, so checkpoint before confirming it.
134
+ */
127
135
  setModelSelection?(next: ModelSelection): void;
136
+ /**
137
+ * True when this session decides its model like the App's own sessions: its
138
+ * latest pick (from Feishu or the App), else its last request's model, else
139
+ * the host default. The App then shows and changes the same choice.
140
+ */
141
+ sharesModelSelection?: boolean;
128
142
  /** Harness sessions on a host with permission presets; undefined otherwise. */
129
143
  permissionState?(): PermissionState | undefined;
130
144
  /** Switch this session's permission preset, as the App's own selector does. */
131
145
  setPermission?(id: string): void;
132
146
  }
133
- export type RuntimeErrorCode = 'SETUP_FAILED' | 'RESTORE_FAILED' | 'SESSION_IN_USE' | 'PERSISTENCE_FAILED' | 'TURN_FAILED' | 'CANCELLED' | 'OUTPUT_LIMIT' | 'BUSY' | 'DISPOSED' | 'MODEL_UNAVAILABLE' | 'INVALID_MODEL_SELECTION' | 'PERMISSION_UNAVAILABLE';
147
+ export type RuntimeErrorCode = 'SETUP_FAILED' | 'RESTORE_FAILED' | 'SESSION_IN_USE' | 'PERSISTENCE_FAILED' | 'TURN_FAILED' | 'CANCELLED' | 'OUTPUT_LIMIT' | 'BUSY' | 'DISPOSED' | 'MODEL_UNAVAILABLE' | 'INVALID_MODEL_SELECTION' | 'MODEL_NOT_SAVED' | 'PERMISSION_UNAVAILABLE';
134
148
  /** Safe to show to a chat user: never contains a provider, tool, or host error. */
135
149
  export declare class DshRuntimeError extends Error {
136
150
  readonly code: RuntimeErrorCode;
@@ -158,6 +172,10 @@ export declare class DshRuntime {
158
172
  create(policy: AgentPolicy): Promise<RuntimeSession>;
159
173
  /** The host's selectable permission presets, for a chat that has no live session yet. */
160
174
  permissionOptions(): PermissionOption[];
175
+ /** Whether harness sessions here share their model choice with the App (see RuntimeSession.sharesModelSelection). */
176
+ sharesModelSelection(): boolean;
177
+ /** Make a choice the App's default for new sessions, as the App's own model selector does. */
178
+ saveDefaultModel(selection: ModelSelection): Promise<void>;
161
179
  /** The preset a stored session runs on: its current projection, else its header. */
162
180
  private recordedPreset;
163
181
  resume(policy: AgentPolicy, sessionId: string): Promise<RuntimeSession>;
@@ -27,6 +27,7 @@ export class DshRuntimeError extends Error {
27
27
  DISPOSED: '当前会话已关闭,请重新开始。',
28
28
  MODEL_UNAVAILABLE: '当前宿主暂时无法提供模型选项,请检查模型配置。',
29
29
  INVALID_MODEL_SELECTION: '这个模型或思考深度当前不可用,请刷新选项后重试。',
30
+ MODEL_NOT_SAVED: '模型设置没能保存到电脑上的 Harness,请稍后重试。',
30
31
  PERMISSION_UNAVAILABLE: '权限模式暂时无法在飞书切换,请在电脑上的 Harness 里调整。',
31
32
  };
32
33
  super(messages[code]);
@@ -61,6 +62,10 @@ function clip(value, limit) {
61
62
  const points = Array.from(value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, '').trim());
62
63
  return points.length > limit ? points.slice(0, limit).join('') + '…' : points.join('');
63
64
  }
65
+ // The App's order: a call's own title or description first, then what it acts on.
66
+ const DETAIL_KEYS = ['title', 'description', 'objective', 'task', 'task_name', 'name', 'question', 'questions', 'prompt', 'message',
67
+ 'command', 'cmd', 'queries', 'query', 'pattern', 'url', 'uri', 'file_path', 'path', 'target', 'action', 'status'];
68
+ const CODE_KEYS = new Set(['command', 'cmd', 'pattern', 'file_path', 'path']);
64
69
  /** What a call is about, from well-known argument fields only; never the whole argument object. */
65
70
  function callDetail(args, limit) {
66
71
  let value;
@@ -72,11 +77,13 @@ function callDetail(args, limit) {
72
77
  }
73
78
  if (!value || typeof value !== 'object' || Array.isArray(value))
74
79
  return;
75
- for (const key of ['url', 'query', 'queries', 'command', 'path', 'file_path', 'pattern']) {
80
+ for (const key of DETAIL_KEYS) {
76
81
  const field = value[key];
77
- const text = Array.isArray(field) ? field.filter((item) => typeof item === 'string').join('、') : field;
78
- if (typeof text === 'string' && text.trim())
79
- return clip(text.replace(/\s+/g, ' '), limit);
82
+ const items = Array.isArray(field) ? field.map(item => typeof item === 'string' ? item
83
+ : item && typeof item === 'object' && typeof item.question === 'string' ? item.question : undefined) : [field];
84
+ const text = items.filter((item) => typeof item === 'string' && Boolean(item.trim())).join(', ');
85
+ if (text.trim())
86
+ return { text: clip(text.replace(/\s+/g, ' ').trim(), limit), code: CODE_KEYS.has(key) };
80
87
  }
81
88
  }
82
89
  /** An approval is only informed if the owner sees the arguments being decided. */
@@ -258,6 +265,54 @@ function permissionOptions(service) {
258
265
  }
259
266
  return options;
260
267
  }
268
+ /** The App's default for new sessions, when the catalog names one of its own options. */
269
+ function permissionDefault(service, options) {
270
+ let catalog;
271
+ try {
272
+ catalog = service.catalog();
273
+ }
274
+ catch {
275
+ return;
276
+ }
277
+ const value = catalog && typeof catalog === 'object' ? catalog.defaultPreset : undefined;
278
+ return typeof value === 'string' && options.some(option => option.id === value) ? value : undefined;
279
+ }
280
+ function sessionProjections(ctx) {
281
+ let service;
282
+ try {
283
+ service = ctx.get('sessionProjections');
284
+ }
285
+ catch {
286
+ return;
287
+ }
288
+ return typeof service?.stateOf === 'function' ? service : undefined;
289
+ }
290
+ /**
291
+ * The model a session's next request uses under the App's own rule: its latest
292
+ * pick, else its last request's model (an effort the adapter filled in stays
293
+ * unset), else the host default. Undefined when the host keeps no such record.
294
+ */
295
+ function sessionChoice(projections, session, defaults) {
296
+ try {
297
+ const state = projections.stateOf(session, 'modelSelection');
298
+ if (!state || typeof state !== 'object')
299
+ return;
300
+ if (state.pending)
301
+ return nativeSelection(modelSelection(state.pending));
302
+ const header = session
303
+ .requestHeader?.();
304
+ if (header?.config) {
305
+ const { provider, model, reasoningEffort } = header.config;
306
+ return nativeSelection(modelSelection({ provider, model,
307
+ ...(reasoningEffort === undefined || header.adapterDefaults?.reasoningEffort === true ? {} : { reasoningEffort: String(reasoningEffort) }) }));
308
+ }
309
+ const fallback = defaults?.currentSelection();
310
+ return fallback ? nativeSelection(modelSelection(fallback)) : undefined;
311
+ }
312
+ catch {
313
+ return;
314
+ }
315
+ }
261
316
  const MODEL_CATALOG_TTL_MS = 30_000;
262
317
  const modelId = (value) => typeof value === 'string' && value.length > 0 && value.length <= 256
263
318
  && value.trim() === value && !/[\u0000-\u001f\u007f]/.test(value);
@@ -537,6 +592,22 @@ export class DshRuntime {
537
592
  const service = permissionPresets(this.ctx);
538
593
  return service ? permissionOptions(service) : [];
539
594
  }
595
+ /** Whether harness sessions here share their model choice with the App (see RuntimeSession.sharesModelSelection). */
596
+ sharesModelSelection() {
597
+ return Boolean(sessionProjections(this.ctx)) && typeof this.ctx.get('agentDefaultModel')?.saveSelection === 'function';
598
+ }
599
+ /** Make a choice the App's default for new sessions, as the App's own model selector does. */
600
+ async saveDefaultModel(selection) {
601
+ const service = this.ctx.get('agentDefaultModel');
602
+ if (typeof service?.saveSelection !== 'function')
603
+ return;
604
+ try {
605
+ await service.saveSelection(nativeSelection(modelSelection(selection)));
606
+ }
607
+ catch {
608
+ throw new DshRuntimeError('MODEL_NOT_SAVED');
609
+ }
610
+ }
540
611
  /** The preset a stored session runs on: its current projection, else its header. */
541
612
  recordedPreset(session) {
542
613
  const projections = this.ctx.get('sessionProjections');
@@ -570,7 +641,17 @@ export class DshRuntime {
570
641
  let memoryText = '';
571
642
  let artifactsText = '';
572
643
  let active;
573
- const selected = { current: undefined, assembled: undefined };
644
+ // A restricted session, or one on an administrator's fixed route, keeps that route.
645
+ // A harness session on the App host follows its own record once it exists (see sharesModelSelection).
646
+ const projections = harness && !(policy.provider && policy.model) ? sessionProjections(this.ctx) : undefined;
647
+ const defaultModel = projections ? this.ctx.get('agentDefaultModel') : undefined;
648
+ let fixed;
649
+ let shared;
650
+ const selected = {
651
+ get current() { return (shared && projections && sessionChoice(projections, shared, defaultModel)) || fixed; },
652
+ set current(next) { fixed = next; },
653
+ assembled: undefined,
654
+ };
574
655
  let handle;
575
656
  // Legacy hosts log raw chunks as session events, modern hosts publish live
576
657
  // frames. Use whichever arrives first so a host offering both shows text once.
@@ -600,8 +681,8 @@ export class DshRuntime {
600
681
  // Kept only so an approval prompt can show the exact call being decided.
601
682
  if (turn.calls.size < 256)
602
683
  turn.calls.set(block.id, args);
603
- const detail = callDetail(args, 120);
604
- emit(turn, { type: 'tool', callId: block.id, name: clip(block.name, 64), ...(detail ? { detail } : {}) });
684
+ const detail = callDetail(args, 160);
685
+ emit(turn, { type: 'tool', callId: block.id, name: clip(block.name, 64), ...(detail ? { detail: detail.text, ...(detail.code ? { code: true } : {}) } : {}) });
605
686
  };
606
687
  try {
607
688
  if (!isAbsolute(policy.cwd))
@@ -779,8 +860,12 @@ export class DshRuntime {
779
860
  }
780
861
  });
781
862
  agentCtx.on('tools/result', (execution, result) => {
782
- if (active && typeof execution.callId === 'string')
783
- emit(active, { type: 'tool-done', callId: execution.callId, ok: !result.isError });
863
+ if (!active || typeof execution.callId !== 'string')
864
+ return;
865
+ // A command that ran but exited non-zero still succeeded as a tool call; show its code like the App.
866
+ const exitCode = result.value?.exitCode;
867
+ emit(active, { type: 'tool-done', callId: execution.callId, ok: !result.isError,
868
+ ...(typeof exitCode === 'number' && Number.isInteger(exitCode) && exitCode !== 0 ? { exitCode } : {}) });
784
869
  });
785
870
  }
786
871
  else {
@@ -854,6 +939,8 @@ export class DshRuntime {
854
939
  throw new DshRuntimeError(resumeId ? 'RESTORE_FAILED' : 'SETUP_FAILED');
855
940
  }
856
941
  const agent = handle.agent;
942
+ if (projections && sessionChoice(projections, agent.session, defaultModel))
943
+ shared = agent.session;
857
944
  // Keep this capability while the owner is active. Cordis unloads sibling
858
945
  // effects concurrently, so native agent teardown may detach this session
859
946
  // before the bridge's teardown reaches it.
@@ -992,8 +1079,22 @@ export class DshRuntime {
992
1079
  throw new DshRuntimeError('DISPOSED');
993
1080
  if (active || agent.status === 'running')
994
1081
  throw new DshRuntimeError('BUSY');
995
- selected.current = nativeSelection(modelSelection(next));
1082
+ const choice = nativeSelection(modelSelection(next));
1083
+ if (!shared) {
1084
+ selected.current = choice;
1085
+ return;
1086
+ }
1087
+ // Recorded like the App selector's own pick, so the App shows it as this session's model.
1088
+ // Older SDK typings predate this event; only a host with the record gets here.
1089
+ try {
1090
+ agent.session.append('model/selection', { provider: choice.provider, model: choice.model,
1091
+ ...(choice.reasoningEffort === undefined ? {} : { reasoningEffort: String(choice.reasoningEffort) }) });
1092
+ }
1093
+ catch {
1094
+ throw new DshRuntimeError('INVALID_MODEL_SELECTION');
1095
+ }
996
1096
  },
1097
+ sharesModelSelection: Boolean(shared),
997
1098
  permissionState: () => {
998
1099
  const service = harness && !disposed ? permissionPresets(this.ctx) : undefined;
999
1100
  const options = service ? permissionOptions(service) : [];
@@ -1006,7 +1107,8 @@ export class DshRuntime {
1006
1107
  catch {
1007
1108
  return;
1008
1109
  }
1009
- return { current: typeof current === 'string' ? current : 'custom', options };
1110
+ const defaultPreset = permissionDefault(service, options);
1111
+ return { current: typeof current === 'string' ? current : 'custom', options, ...(defaultPreset ? { defaultPreset } : {}) };
1010
1112
  },
1011
1113
  setPermission: (id) => {
1012
1114
  if (disposed)
@@ -1,23 +1,28 @@
1
1
  import type { ApprovalPrompt, QuestionAnswer, UserQuestion } from './dsh-runtime.js';
2
- import { type ReplyPayload } from './replies.js';
2
+ import { type PanelTone, type ReplyPayload } from './replies.js';
3
3
  /** A verified card click on a pending approval or question. */
4
4
  export interface InteractionAnswer {
5
5
  verb: 'approve' | 'deny' | 'choose' | 'reply';
6
6
  index?: number;
7
7
  formValues?: unknown;
8
8
  }
9
+ /** How a card closed: allowed or answered (success), refused (error), or expired and stopped (muted). */
10
+ export interface InteractionOutcome {
11
+ text: string;
12
+ tone: PanelTone;
13
+ }
9
14
  /** Buttons answer one short single-choice question in one tap; anything else uses a form. */
10
15
  export declare function simpleQuestion(questions: readonly UserQuestion[]): boolean;
11
16
  /** The decision card. Without tokens it shows the settled outcome and no buttons. */
12
17
  export declare function approvalCard(prompt: ApprovalPrompt, tokens?: {
13
18
  allow: string;
14
19
  deny: string;
15
- }, outcome?: string): ReplyPayload;
20
+ }, outcome?: InteractionOutcome): ReplyPayload;
16
21
  /** A question card: buttons for one short choice, otherwise a form. Without tokens it shows the outcome. */
17
22
  export declare function questionCard(questions: readonly UserQuestion[], tokens?: {
18
23
  choose?: string[];
19
24
  reply?: string;
20
- }, outcome?: string): ReplyPayload;
25
+ }, outcome?: InteractionOutcome): ReplyPayload;
21
26
  /** Map a click or form back to the host's answer shape, echoing the original option labels. */
22
27
  export declare function questionAnswers(questions: readonly UserQuestion[], answer: InteractionAnswer): QuestionAnswer[] | undefined;
23
28
  export declare function answeredText(questions: readonly UserQuestion[], answers: readonly QuestionAnswer[]): string;
@@ -14,12 +14,12 @@ export function simpleQuestion(questions) {
14
14
  export function approvalCard(prompt, tokens, outcome) {
15
15
  return renderPanel({
16
16
  title: tokens ? '需要你确认' : '确认结果',
17
- summary: `是否允许使用工具 ${prompt.toolName}`,
17
+ summary: outcome?.text ?? `是否允许使用工具 ${prompt.toolName}`,
18
+ ...(outcome ? { tone: outcome.tone, status: outcome } : {}),
18
19
  sections: [
19
20
  { title: '工具', text: code(prompt.toolName) },
20
21
  ...(prompt.reason ? [{ title: '原因', text: prompt.reason }] : []),
21
22
  ...(prompt.detail ? [{ title: '参数', text: fenced(prompt.detail) }] : []),
22
- ...(outcome ? [{ text: `**${outcome}**` }] : []),
23
23
  ],
24
24
  ...(tokens ? { actions: [{ label: '允许一次', value: { token: tokens.allow }, primary: true }, { label: '拒绝', value: { token: tokens.deny } }],
25
25
  actionColumns: 2, note: '只允许这一次,不改变电脑上的权限设置。5 分钟内没有确认会按拒绝处理。' } : {}),
@@ -36,11 +36,11 @@ export function questionCard(questions, tokens, outcome) {
36
36
  const title = questions.length === 1 && first.header ? first.header : '需要你选择';
37
37
  const form = Boolean(tokens?.reply);
38
38
  const base = renderPanel({
39
- title, summary: `${title}:${first.question}`,
39
+ title, summary: outcome?.text ?? `${title}:${first.question}`,
40
+ ...(outcome ? { tone: outcome.tone, status: outcome } : {}),
40
41
  sections: [
41
42
  ...(form ? [{ text: questions.length > 1 ? `请回答下面 ${questions.length} 个问题,然后提交。` : '请选择或填写后提交。' }]
42
43
  : questions.map(question => ({ text: questionText(question) }))),
43
- ...(outcome ? [{ text: `**${outcome}**` }] : []),
44
44
  ],
45
45
  ...(tokens?.choose ? { actions: [...first.options.map((option, index) => ({ label: option.label, value: { token: tokens.choose[index] }, primary: index === 0 })),
46
46
  { label: '跳过', value: { token: tokens.choose[first.options.length] } }], actionColumns: 1 } : {}),
@@ -22,10 +22,12 @@ export interface LiveReplyFinal {
22
22
  note?: string;
23
23
  }
24
24
  /**
25
- * One streaming reply card: the answer, with reasoning and tool steps in a
26
- * panel that stays folded unless the reader opens it. Only the initial reply
27
- * is a send; every later operation updates the same card entity and is safe
28
- * to supersede or repeat. A reply never continues in another message.
25
+ * One streaming reply card: the answer, with the process in a panel that
26
+ * stays folded unless the reader opens it. Like the App, the process is a
27
+ * list of steps, thinking and tool calls, each on its own row. Only the
28
+ * initial reply is a send; every later operation updates the same card
29
+ * entity and is safe to supersede or repeat. A reply never continues in
30
+ * another message.
29
31
  */
30
32
  export declare class LiveReply {
31
33
  private readonly transport;
@@ -33,15 +35,18 @@ export declare class LiveReply {
33
35
  private readonly options;
34
36
  private phase;
35
37
  private readonly steps;
38
+ private nextStep;
36
39
  private trimmed;
37
40
  private answer;
38
41
  private separator;
39
42
  private mark;
40
43
  private tools;
41
44
  private note?;
45
+ private footer?;
42
46
  private cardId?;
43
47
  private sequence;
44
48
  private readonly sent;
49
+ private shown;
45
50
  private dirty;
46
51
  private layoutDirty;
47
52
  private degraded;
@@ -60,6 +65,8 @@ export declare class LiveReply {
60
65
  }, options?: LiveReplyOptions);
61
66
  /** Send the card. False means nothing was sent, so the caller keeps its ordinary reply. */
62
67
  open(): Promise<boolean>;
68
+ /** Trusted settings line under the answer, such as the model and thinking depth in use. */
69
+ setFooter(text: string): void;
63
70
  push(event: TurnStreamEvent): void;
64
71
  /** Stop streaming and write the final card. True only when Feishu confirmed it. */
65
72
  finish(final: LiveReplyFinal): Promise<boolean>;
@@ -73,15 +80,22 @@ export declare class LiveReply {
73
80
  /** Pacing between updates; finishing the reply ends it early. */
74
81
  private pause;
75
82
  private flush;
83
+ /** Streamable text elements of a card: its process rows and the answer. */
84
+ private textElements;
76
85
  private markSent;
77
- private stepsMarkdown;
78
- private thinkContent;
86
+ /** One step as a row, in the App's words: what was done and on what. */
87
+ private row;
79
88
  private answerContent;
80
89
  private panelTitle;
81
- /** The folded process panel. Readers open it when they want the reasoning and tool steps. */
90
+ /** The folded process panel. Readers open it when they want the thinking and tool steps. */
82
91
  private panel;
83
- /** Live layout when steps is undefined; a final layout otherwise ('' leaves out the panel). */
92
+ /**
93
+ * The rows that fit in maxBytes, newest last. Older steps give way first and
94
+ * leave a note; a single oversized thought keeps its most recent part.
95
+ */
96
+ private rows;
97
+ /** Live layout when final is undefined; a final layout otherwise (null leaves out the panel's rows). */
84
98
  private card;
85
- /** The answer comes first. The folded process only gets the space that is left. */
99
+ /** The answer comes first. The folded process gets all the space that is left. */
86
100
  private finalCard;
87
101
  }