oasis_test_v2 2.2.106 → 2.2.108

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 (2) hide show
  1. package/dist/index.js +173 -15
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -43276,7 +43276,7 @@ var require_websocket = __commonJS({
43276
43276
  var http2 = require("http");
43277
43277
  var net = require("net");
43278
43278
  var tls = require("tls");
43279
- var { randomBytes: randomBytes17, createHash: createHash46 } = require("crypto");
43279
+ var { randomBytes: randomBytes17, createHash: createHash47 } = require("crypto");
43280
43280
  var { Duplex, Readable } = require("stream");
43281
43281
  var { URL: URL2 } = require("url");
43282
43282
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -43944,7 +43944,7 @@ var require_websocket = __commonJS({
43944
43944
  abortHandshake(websocket, socket, "Invalid Upgrade header");
43945
43945
  return;
43946
43946
  }
43947
- const digest = createHash46("sha1").update(key2 + GUID).digest("base64");
43947
+ const digest = createHash47("sha1").update(key2 + GUID).digest("base64");
43948
43948
  if (res.headers["sec-websocket-accept"] !== digest) {
43949
43949
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
43950
43950
  return;
@@ -44313,7 +44313,7 @@ var require_websocket_server = __commonJS({
44313
44313
  var EventEmitter = require("events");
44314
44314
  var http2 = require("http");
44315
44315
  var { Duplex } = require("stream");
44316
- var { createHash: createHash46 } = require("crypto");
44316
+ var { createHash: createHash47 } = require("crypto");
44317
44317
  var extension3 = require_extension();
44318
44318
  var PerMessageDeflate2 = require_permessage_deflate();
44319
44319
  var subprotocol2 = require_subprotocol();
@@ -44620,7 +44620,7 @@ var require_websocket_server = __commonJS({
44620
44620
  );
44621
44621
  }
44622
44622
  if (this._state > RUNNING) return abortHandshake(socket, 503);
44623
- const digest = createHash46("sha1").update(key2 + GUID).digest("base64");
44623
+ const digest = createHash47("sha1").update(key2 + GUID).digest("base64");
44624
44624
  const headers = [
44625
44625
  "HTTP/1.1 101 Switching Protocols",
44626
44626
  "Upgrade: websocket",
@@ -206747,6 +206747,35 @@ var init_host_isolation = __esm({
206747
206747
  });
206748
206748
 
206749
206749
  // ../connectors/src/feishu/index.ts
206750
+ async function fetchFeishuTenantAccessToken(appId, appSecret) {
206751
+ try {
206752
+ const resp = await fetch(FEISHU_TENANT_TOKEN_URL, {
206753
+ method: "POST",
206754
+ headers: { "Content-Type": "application/json; charset=utf-8" },
206755
+ body: JSON.stringify({ app_id: appId, app_secret: appSecret }),
206756
+ signal: AbortSignal.timeout(TENANT_TOKEN_TIMEOUT_MS)
206757
+ });
206758
+ const data = await resp.json();
206759
+ if (!resp.ok || data.code !== 0 || typeof data.tenant_access_token !== "string" || !data.tenant_access_token) {
206760
+ log("[feishu]", `\u6362\u4F01\u4E1A token \u5931\u8D25\uFF08\u672C\u8F6E bot \u8EAB\u4EFD\u4E0D\u53EF\u7528\uFF09: HTTP ${resp.status} code=${String(data.code)} ${String(data.msg ?? "")}`.trim());
206761
+ return null;
206762
+ }
206763
+ return data.tenant_access_token;
206764
+ } catch (err) {
206765
+ log("[feishu]", `\u6362\u4F01\u4E1A token \u5931\u8D25\uFF08\u672C\u8F6E bot \u8EAB\u4EFD\u4E0D\u53EF\u7528\uFF09: ${String(err?.message ?? err).slice(0, 200)}`);
206766
+ return null;
206767
+ }
206768
+ }
206769
+ function previousTenantToken(credsFile, appId) {
206770
+ if (!credsFile) return null;
206771
+ try {
206772
+ const prev = readSessionCredsFile(credsFile);
206773
+ if (prev["LARKSUITE_CLI_APP_ID"] !== appId) return null;
206774
+ return prev["LARKSUITE_CLI_TENANT_ACCESS_TOKEN"] || null;
206775
+ } catch {
206776
+ return null;
206777
+ }
206778
+ }
206750
206779
  function parseFeishuTokenResponse(data, nowMs) {
206751
206780
  const accessToken = String(data["access_token"] ?? "");
206752
206781
  const refreshToken = String(data["refresh_token"] ?? "");
@@ -206765,7 +206794,7 @@ function parseFeishuTokenResponse(data, nowMs) {
206765
206794
  expiresAt: new Date(expiresAtMs)
206766
206795
  };
206767
206796
  }
206768
- var import_node_child_process14, import_node_util4, import_node_path16, import_node_url3, import_promises9, execFileAsync2, __dirname2, FEISHU_WELL_KNOWN_SKILLS_BASE, FEISHU_TOKEN_URL, FeishuConnector;
206797
+ var import_node_child_process14, import_node_util4, import_node_path16, import_node_url3, import_promises9, execFileAsync2, __dirname2, FEISHU_WELL_KNOWN_SKILLS_BASE, FEISHU_TOKEN_URL, FEISHU_TENANT_TOKEN_URL, TENANT_TOKEN_TIMEOUT_MS, FeishuConnector;
206769
206798
  var init_feishu = __esm({
206770
206799
  "../connectors/src/feishu/index.ts"() {
206771
206800
  "use strict";
@@ -206775,10 +206804,13 @@ var init_feishu = __esm({
206775
206804
  import_node_url3 = require("node:url");
206776
206805
  import_promises9 = require("node:fs/promises");
206777
206806
  init_base();
206807
+ init_logger();
206778
206808
  execFileAsync2 = (0, import_node_util4.promisify)(import_node_child_process14.execFile);
206779
206809
  __dirname2 = (0, import_node_path16.dirname)((0, import_node_url3.fileURLToPath)(__esm_import_meta_url));
206780
206810
  FEISHU_WELL_KNOWN_SKILLS_BASE = "https://open.feishu.cn/.well-known/skills";
206781
206811
  FEISHU_TOKEN_URL = "https://open.feishu.cn/open-apis/authen/v2/oauth/token";
206812
+ FEISHU_TENANT_TOKEN_URL = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal";
206813
+ TENANT_TOKEN_TIMEOUT_MS = 5e3;
206782
206814
  FeishuConnector = class extends CliConnector {
206783
206815
  config = {
206784
206816
  slug: "feishu",
@@ -206876,9 +206908,12 @@ var init_feishu = __esm({
206876
206908
  /** 厂商技能包源——平台自动拉取飞书官方技能包时用。 */
206877
206909
  wellKnownSkillsBase = FEISHU_WELL_KNOWN_SKILLS_BASE;
206878
206910
  larkBin;
206879
- constructor(larkBin) {
206911
+ fetchTenantToken;
206912
+ /** `fetchTenantToken` 只给测试替换(不打真飞书);生产一律用 {@link fetchFeishuTenantAccessToken}。 */
206913
+ constructor(larkBin, opts = {}) {
206880
206914
  super(__dirname2);
206881
206915
  this.larkBin = larkBin ?? this.resolveLarkBin();
206916
+ this.fetchTenantToken = opts.fetchTenantToken ?? fetchFeishuTenantAccessToken;
206882
206917
  }
206883
206918
  /**
206884
206919
  * 解析 user_info 的 JSON。lark-cli 的 `ok:false` 也是**退出码 0** 的正常输出,
@@ -206909,6 +206944,7 @@ var init_feishu = __esm({
206909
206944
  /**
206910
206945
  * Agent run 开始:把凭据作为 lark-cli「外部凭据」写进凭据文件(不进 agent env)。
206911
206946
  * credentials 接受:app_id, app_secret, user_access_token?(及其 FEISHU_* 别名)。
206947
+ * 企业 token 不随 job 下发,由这里用 app_id/app_secret 现换(换不到只是本轮没有 bot 身份,不抛)。
206912
206948
  *
206913
206949
  * 注入的 sessionEnv 键(全部非敏感):
206914
206950
  * FEISHU_CREDS_FILE — 凭据文件路径,wrapper source 它
@@ -206921,9 +206957,11 @@ var init_feishu = __esm({
206921
206957
  if (!appId) throw new Error("feishu inject: missing app_id");
206922
206958
  if (!appSecret) throw new Error("feishu inject: missing app_secret");
206923
206959
  const userAccessToken = credentials2["user_access_token"] ?? credentials2["FEISHU_USER_ACCESS_TOKEN"];
206960
+ const tenantAccessToken = this.isSimulated() ? null : await this.fetchTenantToken(appId, appSecret) ?? previousTenantToken(sessionEnv.get("FEISHU_CREDS_FILE"), appId);
206924
206961
  const creds = await writeSessionCredsFile(this.config.slug, {
206925
206962
  LARKSUITE_CLI_APP_ID: appId,
206926
206963
  LARKSUITE_CLI_APP_SECRET: appSecret,
206964
+ ...tenantAccessToken ? { LARKSUITE_CLI_TENANT_ACCESS_TOKEN: tenantAccessToken } : {},
206927
206965
  ...userAccessToken ? { LARKSUITE_CLI_USER_ACCESS_TOKEN: userAccessToken } : {}
206928
206966
  }, sessionEnv.get("FEISHU_RUN_TMPDIR"));
206929
206967
  const configDir = (0, import_node_path16.join)(creds.dir, "lark-config");
@@ -210646,7 +210684,7 @@ async function prepareConnectorsForJob(job, deps) {
210646
210684
  await connector.inject(credentials2, sessionEnv);
210647
210685
  for (const [k2, v2] of sessionEnv) envOverrides[k2] = v2;
210648
210686
  cleanups.push(() => connector.cleanup(sessionEnv));
210649
- injected.set(slug5, { connector, sessionEnv, credsFingerprint: credentialsFingerprint(credentials2) });
210687
+ injected.set(slug5, { connector, sessionEnv, credentials: credentials2, credsFingerprint: credentialsFingerprint(credentials2) });
210650
210688
  status.credsReady = true;
210651
210689
  } catch (err) {
210652
210690
  status.error = String(err);
@@ -210772,7 +210810,12 @@ async function renewInjectedCredentials(injected, fetchCredentials) {
210772
210810
  try {
210773
210811
  fresh = await fetchCredentials();
210774
210812
  } catch (err) {
210775
- for (const slug5 of injected.keys()) outcome.failed.push({ slug: slug5, error: String(err) });
210813
+ for (const [slug5, target] of injected) {
210814
+ outcome.failed.push({ slug: slug5, error: String(err) });
210815
+ await target.connector.inject(target.credentials, target.sessionEnv).catch((reErr) => {
210816
+ log("[node-connectors]", `\u26A0 \u7EED\u671F\uFF1A${slug5} \u6309\u65E7\u51ED\u636E\u91CD\u6CE8\u5931\u8D25: ${String(reErr)}`);
210817
+ });
210818
+ }
210776
210819
  log("[node-connectors]", `\u7EED\u671F\uFF1A\u5411 server \u53D6\u51ED\u636E\u5931\u8D25\uFF0C\u672C\u8F6E\u6CBF\u7528\u65E7\u51ED\u636E: ${String(err)}`);
210777
210820
  return outcome;
210778
210821
  }
@@ -210781,6 +210824,7 @@ async function renewInjectedCredentials(injected, fetchCredentials) {
210781
210824
  if (!target) continue;
210782
210825
  try {
210783
210826
  await target.connector.inject(credentials2, target.sessionEnv);
210827
+ target.credentials = credentials2;
210784
210828
  const fingerprint2 = credentialsFingerprint(credentials2);
210785
210829
  if (fingerprint2 !== target.credsFingerprint) {
210786
210830
  target.credsFingerprint = fingerprint2;
@@ -220270,6 +220314,9 @@ var init_memory_control_plane_store = __esm({
220270
220314
  /** protected:FileControlPlaneStore 子类 open() 回放快照时直接恢复(密码须持久化)。 */
220271
220315
  accountSecrets = /* @__PURE__ */ new Map();
220272
220316
  // accountId → { passwordHash?, tokenVersion }
220317
+ /** 已登出会话(ADR 0636;protected:文件版子类回放快照时直接恢复——不持久化的话重启即「复活」)。 */
220318
+ revokedSessions = /* @__PURE__ */ new Map();
220319
+ // tokenHash → record
220273
220320
  verificationCodes = /* @__PURE__ */ new Map();
220274
220321
  // email → record(短 TTL,文件版不持久化)
220275
220322
  /** 组织模型三表(protected:文件版子类回放快照时直接恢复)。 */
@@ -220631,6 +220678,15 @@ var init_memory_control_plane_store = __esm({
220631
220678
  this.accountSecrets.set(accountId, { ...cur, tokenVersion: next });
220632
220679
  return next;
220633
220680
  }
220681
+ // —— 已登出会话(ADR 0636)——
220682
+ async revokeSession(rec2) {
220683
+ const cutoff = Date.parse(rec2.revokedAt);
220684
+ for (const [k2, r] of this.revokedSessions) if (Date.parse(r.expiresAt) < cutoff) this.revokedSessions.delete(k2);
220685
+ if (!this.revokedSessions.has(rec2.tokenHash)) this.revokedSessions.set(rec2.tokenHash, { ...rec2 });
220686
+ }
220687
+ async isSessionRevoked(tokenHash) {
220688
+ return this.revokedSessions.has(tokenHash);
220689
+ }
220634
220690
  // —— 邮箱验证码(单邮箱一条 active)——
220635
220691
  async putVerificationCode(rec2) {
220636
220692
  this.verificationCodes.set(rec2.email, { ...rec2 });
@@ -225833,6 +225889,7 @@ var init_control_plane_file_store = __esm({
225833
225889
  "upsertAccount",
225834
225890
  "setAccountPassword",
225835
225891
  "bumpAccountTokenVersion",
225892
+ "revokeSession",
225836
225893
  "upsertDigitalEmployee",
225837
225894
  "patchDigitalEmployee",
225838
225895
  "putAgentCredential",
@@ -225872,6 +225929,7 @@ var init_control_plane_file_store = __esm({
225872
225929
  for (const link of snap.inviteLinks ?? []) await MemoryControlPlaneStore.prototype.createInviteLink.call(store, link);
225873
225930
  for (const a of snap.accounts ?? []) await MemoryControlPlaneStore.prototype.upsertAccount.call(store, a);
225874
225931
  for (const e of snap.accountSecrets ?? []) store.accountSecrets.set(e.accountId, { ...e.secret });
225932
+ for (const r of snap.revokedSessions ?? []) store.revokedSessions.set(r.tokenHash, { ...r });
225875
225933
  for (const e of snap.digitalEmployees ?? []) await MemoryControlPlaneStore.prototype.upsertDigitalEmployee.call(store, e);
225876
225934
  for (const c of snap.agentCredentials ?? []) await MemoryControlPlaneStore.prototype.putAgentCredential.call(store, c);
225877
225935
  for (const b2 of snap.principalBridges ?? []) await MemoryControlPlaneStore.prototype.putPrincipalBridge.call(store, b2);
@@ -225910,6 +225968,8 @@ var init_control_plane_file_store = __esm({
225910
225968
  inviteLinks: [...this.inviteLinks.values()],
225911
225969
  accounts,
225912
225970
  accountSecrets,
225971
+ // 只有按哈希查的读面,直接读 protected map(同 offboardings)。
225972
+ revokedSessions: [...this.revokedSessions.values()],
225913
225973
  digitalEmployees,
225914
225974
  agentCredentials,
225915
225975
  principalBridges,
@@ -230576,6 +230636,12 @@ function verifySession(token2, secret, nowMs) {
230576
230636
  if (typeof claims.exp !== "number" || claims.exp <= nowMs) return null;
230577
230637
  return claims;
230578
230638
  }
230639
+ function sessionTokenHash(token2) {
230640
+ return (0, import_node_crypto54.createHash)("sha256").update(token2).digest("base64url");
230641
+ }
230642
+ function newSessionId() {
230643
+ return (0, import_node_crypto54.randomBytes)(16).toString("base64url");
230644
+ }
230579
230645
  function generateCode() {
230580
230646
  return String((0, import_node_crypto54.randomInt)(0, 1e6)).padStart(6, "0");
230581
230647
  }
@@ -246652,7 +246718,7 @@ function createAuth(opts) {
246652
246718
  const passwordFailures = /* @__PURE__ */ new Map();
246653
246719
  const sessionCookie = (accountId, tokenVersion) => {
246654
246720
  const t = now();
246655
- const jwt = signSession({ sub: accountId, tokenVersion, iat: t, exp: t + sessionTtlMs }, jwtSecret);
246721
+ const jwt = signSession({ sub: accountId, tokenVersion, iat: t, exp: t + sessionTtlMs, jti: newSessionId() }, jwtSecret);
246656
246722
  const attrs = [`${cookieName}=${jwt}`, "Path=/", "HttpOnly", "SameSite=Lax", `Max-Age=${Math.floor(sessionTtlMs / 1e3)}`];
246657
246723
  if (opts.secureCookie) attrs.push("Secure");
246658
246724
  return attrs.join("; ");
@@ -246710,11 +246776,13 @@ function createAuth(opts) {
246710
246776
  await store.deleteVerificationCode(key2);
246711
246777
  return "ok";
246712
246778
  };
246779
+ const sessionToken = (headers) => parseCookies(typeof headers.cookie === "string" ? headers.cookie : void 0)[cookieName];
246713
246780
  const currentAccount = async (headers) => {
246714
- const raw = parseCookies(typeof headers.cookie === "string" ? headers.cookie : void 0)[cookieName];
246781
+ const raw = sessionToken(headers);
246715
246782
  if (!raw) return null;
246716
246783
  const claims = verifySession(raw, jwtSecret, now());
246717
246784
  if (!claims) return null;
246785
+ if (await store.isSessionRevoked(sessionTokenHash(raw))) return null;
246718
246786
  const sec = await store.getAccountSecret(claims.sub);
246719
246787
  if (!sec || sec.tokenVersion !== claims.tokenVersion) return null;
246720
246788
  const account = await store.getAccount(claims.sub);
@@ -246891,6 +246959,21 @@ function createAuth(opts) {
246891
246959
  return { status: 200, body: { ticket, expiresAt: new Date(exp).toISOString() } };
246892
246960
  }
246893
246961
  if (route2 === "logout") {
246962
+ const raw = sessionToken(headers);
246963
+ const claims = raw ? verifySession(raw, jwtSecret, now()) : null;
246964
+ if (raw && claims) {
246965
+ try {
246966
+ await store.revokeSession({
246967
+ tokenHash: sessionTokenHash(raw),
246968
+ accountId: claims.sub,
246969
+ revokedAt: new Date(now()).toISOString(),
246970
+ expiresAt: new Date(claims.exp).toISOString()
246971
+ });
246972
+ } catch (e) {
246973
+ console.error(`[auth] \u767B\u51FA\u540A\u9500\u5931\u8D25 ${claims.sub}: ${e instanceof Error ? e.message : String(e)}`);
246974
+ return { status: 500, body: { error: { code: "LOGOUT_REVOKE_FAILED", message: "\u767B\u51FA\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u8BD5" } }, setCookie: clearCookie() };
246975
+ }
246976
+ }
246894
246977
  return { status: 200, body: { ok: true }, setCookie: clearCookie() };
246895
246978
  }
246896
246979
  return { status: 404, body: { error: { code: "NOT_FOUND", message: pathname } } };
@@ -264917,6 +265000,14 @@ var init_postgres_control_plane = __esm({
264917
265000
  expires_at timestamptz NOT NULL,
264918
265001
  attempts int NOT NULL DEFAULT 0
264919
265002
  )`);
265003
+ await pool.query(`
265004
+ CREATE TABLE IF NOT EXISTS "${s2}".revoked_sessions (
265005
+ token_hash text PRIMARY KEY, -- SHA-256(base64url) of the oasis_auth JWT
265006
+ account_id text NOT NULL,
265007
+ revoked_at timestamptz NOT NULL,
265008
+ expires_at timestamptz NOT NULL -- = \u4EE4\u724C exp\uFF1B\u8FC7\u4E86\u4EE4\u724C\u672C\u6765\u5C31\u9A8C\u4E0D\u8FC7\uFF0C\u884C\u53EF\u5220
265009
+ )`);
265010
+ await pool.query(`CREATE INDEX IF NOT EXISTS revoked_sessions_expires_idx ON "${s2}".revoked_sessions (expires_at)`);
264920
265011
  await pool.query(`ALTER TABLE "${s2}".company_members ADD COLUMN IF NOT EXISTS display_name text`);
264921
265012
  await pool.query(`ALTER TABLE "${s2}".company_members ADD COLUMN IF NOT EXISTS created_at timestamptz`);
264922
265013
  await pool.query(`ALTER TABLE "${s2}".company_members ADD COLUMN IF NOT EXISTS updated_at timestamptz`);
@@ -265807,6 +265898,19 @@ var init_postgres_control_plane = __esm({
265807
265898
  if (r.rowCount === 0) throw new Error(`account not found: ${accountId}`);
265808
265899
  return Number(r.rows[0].token_version);
265809
265900
  }
265901
+ // —— 已登出会话(ADR 0636)——
265902
+ async revokeSession(rec2) {
265903
+ await this.pool.query(`DELETE FROM ${this.s}.revoked_sessions WHERE expires_at < $1`, [rec2.revokedAt]);
265904
+ await this.pool.query(
265905
+ `INSERT INTO ${this.s}.revoked_sessions (token_hash, account_id, revoked_at, expires_at) VALUES ($1,$2,$3,$4)
265906
+ ON CONFLICT (token_hash) DO NOTHING`,
265907
+ [rec2.tokenHash, rec2.accountId, rec2.revokedAt, rec2.expiresAt]
265908
+ );
265909
+ }
265910
+ async isSessionRevoked(tokenHash) {
265911
+ const r = await this.pool.query(`SELECT 1 FROM ${this.s}.revoked_sessions WHERE token_hash = $1`, [tokenHash]);
265912
+ return (r.rowCount ?? 0) > 0;
265913
+ }
265810
265914
  // —— 邮箱验证码(单邮箱一条 active)——
265811
265915
  async putVerificationCode(rec2) {
265812
265916
  await this.pool.query(
@@ -298359,12 +298463,13 @@ var COMMAND_DECLS = {
298359
298463
  // 填(agent 调 = 它绑定的那个真人),命令行不收。开关 OASIS_PERSONAL_TODO=off 时服务端 404。 ——
298360
298464
  todo: {
298361
298465
  group: "\u4E2A\u4EBA\u5F85\u529E",
298362
- usage: "oasis todo <add|list|edit|done|cancel|reopen> ...",
298466
+ usage: "oasis todo <add|list|show|edit|done|cancel|reopen> ...",
298363
298467
  description: "\u4E2A\u4EBA\u5F85\u529E\uFF1A\u66FF\u4F60\u7ED1\u5B9A\u7684\u90A3\u4E2A\u771F\u4EBA\u8BB0\u4E0B\u6709\u622A\u6B62\u65E5\u7684\u4E8B\uFF0C\u5230\u671F\u524D\u4E00\u5929 09:00\u3001\u5F53\u5929 09:00 \u7CFB\u7EDF\u4F1A\u63D0\u9192 TA\uFF08\u8BBE\u4E86\u5F00\u59CB\u65E5\u7684\uFF0C\u5F00\u59CB\u5F53\u5929 09:00 \u4E5F\u63D0\u9192\u4E00\u6B21\uFF09\u3002\u6BCF\u6761\u8F93\u51FA\u7B2C\u4E00\u884C\u662F\u300C\u4ECA\u5929\uFF1AYYYY-MM-DD \u5468X\uFF08\u65F6\u533A\uFF09\u300D\u2014\u2014\u6362\u7B97\u300C\u8FD9\u5468\u4E94\u300D\u300C\u6708\u5E95\u300D\u4E00\u5F8B\u4EE5\u5B83\u4E3A\u51C6\u3002**\u6CA1\u6709 `oasis todo` \u547D\u4EE4\u6267\u884C\u6210\u529F\uFF0C\u5C31\u4E0D\u80FD\u8BF4\u8BB0\u4E0B\u4E86\u3002**",
298364
- subCommands: ["todo add", "todo list", "todo edit", "todo done", "todo cancel", "todo reopen"],
298468
+ subCommands: ["todo add", "todo list", "todo show", "todo edit", "todo done", "todo cancel", "todo reopen"],
298365
298469
  examples: [
298366
298470
  'oasis todo add --title "\u4EA4 Q3 \u590D\u76D8\u62A5\u544A" --note "\u738B\u603B\u8981\uFF0C\u5468\u4F1A\u524D\u8FC7\u4E00\u904D" --due 2026-10-09',
298367
298471
  "oasis todo list",
298472
+ "oasis todo show td_3f9a1c2b",
298368
298473
  'oasis todo edit td_3f9a1c2b --due 2026-10-12 --reason "\u6392\u671F\u8C03\u6574"',
298369
298474
  "oasis todo done td_3f9a1c2b"
298370
298475
  ]
@@ -298389,6 +298494,13 @@ var COMMAND_DECLS = {
298389
298494
  description: "\u5217\u51FA\u5F85\u529E\uFF0C\u6309\u622A\u6B62\u65E5\u6392\u5E8F\uFF0C\u8FC7\u671F\u7684\u6807\u300C\u5DF2\u8FC7\u671F\u300D\uFF1B\u8BBE\u4E86\u5F00\u59CB\u65E5\u7684\u663E\u793A\u300CX \u6708 X \u65E5\u5F00\u59CB \u2192 X \u6708 X \u65E5\u622A\u6B62\u300D\u3002\u9ED8\u8BA4\u53EA\u5217\u672A\u5B8C\u6210\u7684\uFF1B--all \u8FDE\u5DF2\u5B8C\u6210\u3001\u5DF2\u53D6\u6D88\u7684\u4E00\u8D77\u5217\u3002",
298390
298495
  flags: [{ name: "all", desc: "\u8FDE\u5DF2\u5B8C\u6210 / \u5DF2\u53D6\u6D88\u7684\u4E00\u8D77\u5217", boolean: true, own: true }]
298391
298496
  },
298497
+ "todo show": {
298498
+ group: "\u4E2A\u4EBA\u5F85\u529E",
298499
+ usage: "oasis todo show <id>",
298500
+ description: "\u770B\u4E00\u6761\u5F85\u529E\u7684\u5168\u8C8C\uFF1A\u6807\u9898\u3001\u5F00\u59CB \u2192 \u622A\u6B62\u3001\u72B6\u6001\u3001\u5907\u6CE8\u5168\u6587\uFF0C\u4EE5\u53CA\u53D8\u66F4\u8BB0\u5F55\uFF08\u65F6\u95F4\u3001\u8C01\u6539\u7684 / \u7ECF\u624B agent\u3001\u5B57\u6BB5\u3001\u65E7\u503C \u2192 \u65B0\u503C\u3001\u539F\u56E0\uFF0C\u65B0\u7684\u5728\u524D\uFF09\u3002\u6539\u5907\u6CE8\u524D\u5148\u7528\u5B83\u8BFB\u51FA\u5F53\u524D\u5907\u6CE8\u5168\u6587\uFF1B\u7528\u6237\u95EE\u300C\u4E4B\u524D\u5199\u7684\u662F\u4EC0\u4E48\u300D\u4E5F\u67E5\u8FD9\u91CC\u3002",
298501
+ positional: [{ name: "<id>", required: true, desc: "\u5F85\u529E id\uFF08oasis todo list \u7B2C\u4E00\u5217\uFF09" }],
298502
+ examples: ["oasis todo show td_3f9a1c2b"]
298503
+ },
298392
298504
  "todo edit": {
298393
298505
  group: "\u4E2A\u4EBA\u5F85\u529E",
298394
298506
  usage: "oasis todo edit <id> [--title \u2026] [--note \u2026] [--start YYYY-MM-DD|none] [--start-time HH:mm|none] [--due YYYY-MM-DD] [--time HH:mm|none] [--workorder <\u5DE5\u5355 id>|none] [--reason \u2026]",
@@ -299279,7 +299391,7 @@ async function waitForCommandAuthorization(base, headers, draftId, fallback) {
299279
299391
  }
299280
299392
  }
299281
299393
  }
299282
- var CLI_VERSION = "2.2.106" ? "2.2.106" : "dev";
299394
+ var CLI_VERSION = "2.2.108" ? "2.2.108" : "dev";
299283
299395
  function apiHeaders(token2, company) {
299284
299396
  const headers = {
299285
299397
  authorization: `Bearer ${token2}`,
@@ -301711,6 +301823,52 @@ ${res.warning}`);
301711
301823
  }
301712
301824
  break;
301713
301825
  }
301826
+ if (sub === "show") {
301827
+ const id = idOf();
301828
+ const [one2, day] = await Promise.all([
301829
+ call("GET", todoPath(id)),
301830
+ call("GET", "/api/todos")
301831
+ ]);
301832
+ const t = one2.todo;
301833
+ const statusText = t.status === "done" ? "\u5DF2\u5B8C\u6210" : t.status === "cancelled" ? "\u5DF2\u53D6\u6D88" : t.dueDate < day.today.date ? "\u672A\u5B8C\u6210\uFF08\u5DF2\u8FC7\u671F\uFF09" : "\u672A\u5B8C\u6210";
301834
+ println(day.todayLine);
301835
+ println(`${t.id} ${t.title}`);
301836
+ println(`\u65F6\u95F4\uFF1A${due(t)}`);
301837
+ println(`\u72B6\u6001\uFF1A${statusText}`);
301838
+ if (t.linkedWorkorderId) println(`\u5DE5\u5355\uFF1A${t.linkedWorkorderId}`);
301839
+ println("\u5907\u6CE8\uFF1A");
301840
+ println(t.note ? t.note : "\uFF08\u65E0\uFF09");
301841
+ const FIELD = {
301842
+ created: "\u8BB0\u4E0B",
301843
+ status: "\u72B6\u6001",
301844
+ title: "\u6807\u9898",
301845
+ note: "\u5907\u6CE8",
301846
+ startDate: "\u5F00\u59CB\u65E5",
301847
+ startTime: "\u5F00\u59CB\u65F6\u523B",
301848
+ dueDate: "\u622A\u6B62\u65E5",
301849
+ dueTime: "\u622A\u6B62\u65F6\u523B",
301850
+ linkedWorkorderId: "\u5DE5\u5355"
301851
+ };
301852
+ const STATUS = { open: "\u672A\u5B8C\u6210", done: "\u5DF2\u5B8C\u6210", cancelled: "\u5DF2\u53D6\u6D88" };
301853
+ const val = (field, v2) => v2 === void 0 || v2 === "" ? "\uFF08\u7A7A\uFF09" : field === "status" ? STATUS[v2] ?? v2 : v2;
301854
+ const fmtAt = (iso9) => new Intl.DateTimeFormat("sv-SE", {
301855
+ timeZone: day.today.timeZone,
301856
+ year: "numeric",
301857
+ month: "2-digit",
301858
+ day: "2-digit",
301859
+ hour: "2-digit",
301860
+ minute: "2-digit",
301861
+ hour12: false
301862
+ }).format(new Date(iso9));
301863
+ const changes = [...one2.changes].sort((a, b2) => b2.at.localeCompare(a.at));
301864
+ println(`\u53D8\u66F4\u8BB0\u5F55\uFF08${changes.length} \u6761\uFF0C\u65B0\u7684\u5728\u524D\uFF09\uFF1A`);
301865
+ for (const c of changes) {
301866
+ const who = c.handActorId ? `${c.actorId}\uFF08\u7ECF\u624B ${c.handActorId}\uFF09` : c.actorId;
301867
+ const what = c.field === "created" ? `\u8BB0\u4E0B\uFF1A${c.newValue ?? ""}` : `${FIELD[c.field] ?? c.field}\uFF1A${val(c.field, c.oldValue)} \u2192 ${val(c.field, c.newValue)}`;
301868
+ println(`- ${fmtAt(c.at)} ${who} ${what}${c.reason ? ` \u539F\u56E0\uFF1A${c.reason}` : ""}`);
301869
+ }
301870
+ break;
301871
+ }
301714
301872
  if (sub === "edit") {
301715
301873
  const id = idOf();
301716
301874
  const body2 = {};
@@ -301736,7 +301894,7 @@ ${res.warning}`);
301736
301894
  println(`${sub === "done" ? "\u5DF2\u5B8C\u6210" : sub === "cancel" ? "\u5DF2\u53D6\u6D88" : "\u5DF2\u91CD\u65B0\u6253\u5F00"}\uFF1A${r.todo.title}\uFF0C${due(r.todo)}`);
301737
301895
  break;
301738
301896
  }
301739
- throw new Error("\u7528\u6CD5: oasis todo <add|list|edit|done|cancel|reopen> \u2026\uFF08oasis todo --help \u770B\u8BE6\u60C5\uFF09");
301897
+ throw new Error("\u7528\u6CD5: oasis todo <add|list|show|edit|done|cancel|reopen> \u2026\uFF08oasis todo --help \u770B\u8BE6\u60C5\uFF09");
301740
301898
  }
301741
301899
  case "skill": {
301742
301900
  const sub = positional[0];
@@ -303404,7 +303562,7 @@ function shimScript() {
303404
303562
  }
303405
303563
 
303406
303564
  // src/index.ts
303407
- var PKG_VERSION = true ? "2.2.106" : "dev";
303565
+ var PKG_VERSION = true ? "2.2.108" : "dev";
303408
303566
  var LOCAL_BIN = localBin();
303409
303567
  var NPM_PREFIX = npmPrefix();
303410
303568
  var INSTANCE = DEFAULT_INSTANCE;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oasis_test_v2",
3
- "version": "2.2.106",
3
+ "version": "2.2.108",
4
4
  "description": "Oasis node daemon + CLI — background daemon, auto-start, full server CLI",
5
5
  "bin": {
6
6
  "oasis": "./dist/index.js"
@@ -26,6 +26,6 @@
26
26
  "node": ">=20"
27
27
  },
28
28
  "oasisRelease": {
29
- "sourceHead": "f251b7721412da1529673cd12039208906bb0a6e"
29
+ "sourceHead": "a953575106dcf944d7b32f0eca289792092539ff"
30
30
  }
31
31
  }