dsh-multigameassistant 2.0.0 → 2.0.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/lib/game-intel.js CHANGED
@@ -7,26 +7,28 @@ import {
7
7
  GAME_MANIFEST_FILE,
8
8
  GameManifest,
9
9
  parseGameManifest
10
- } from "./chunk-7XMNAHZ2.js";
10
+ } from "./chunk-J46OASOG.js";
11
11
  import {
12
12
  sessionConversations
13
- } from "./chunk-FLQD3DKB.js";
14
- import "./chunk-2DZ75ANX.js";
13
+ } from "./chunk-BNGDT7YT.js";
14
+ import "./chunk-ZQ6KFVEO.js";
15
15
  import {
16
16
  writeFileAtomic
17
17
  } from "./chunk-CCAB6XXZ.js";
18
18
  import {
19
19
  GAMES_CAPABILITY,
20
20
  GamesSettings,
21
+ OWN_IMAGE,
22
+ SHOT_IMAGE,
21
23
  external_exports,
22
24
  taskMessage
23
- } from "./chunk-BL6OSR2I.js";
25
+ } from "./chunk-HPQCV6WO.js";
24
26
  import "./chunk-2UFLGPDY.js";
25
27
 
26
28
  // packages/game-intel/lib/index.js
27
29
  import { createHash } from "node:crypto";
28
- import { mkdir as mkdir2, readdir, readFile as readFile2, writeFile } from "node:fs/promises";
29
- import { join as join2 } from "node:path";
30
+ import { mkdir as mkdir2, readdir, readFile as readFile2, rm, writeFile } from "node:fs/promises";
31
+ import { isAbsolute, join as join2, resolve } from "node:path";
30
32
  import { Service } from "@deepseek-ai/cordis";
31
33
 
32
34
  // packages/game-intel/lib/calendar.js
@@ -162,6 +164,7 @@ var KEEP_UNDATED_MS = 14 * MS.DAY;
162
164
  var MISMATCH_MS = 30 * MS.HOUR;
163
165
  var DEFAULT_SETTINGS = { report: { enabled: true, at: "09:00", games: [], soonDays: 3, ai: true }, followed: [] };
164
166
  var AGENT_PAGE = { id: "agent", name: "agent \u67E5\u5230\u7684\u7F51\u9875" };
167
+ var OWN_FIELDS = ["name", "startsAt", "endsAt", "kind", "description", "url", "cover"];
165
168
  var StoredSchema = external_exports.object({
166
169
  activities: external_exports.array(external_exports.looseObject({ id: external_exports.string(), game: external_exports.string(), name: external_exports.string() })).default([]),
167
170
  refreshed: external_exports.record(external_exports.string(), external_exports.object({ at: external_exports.number(), ok: external_exports.boolean(), count: external_exports.number(), detail: external_exports.string().optional() })).default({}),
@@ -345,7 +348,7 @@ var Calendar = class {
345
348
  delete item.facts[source.id];
346
349
  this.#derive(item, timeZone);
347
350
  }
348
- this.#items = this.#items.filter((a) => a.game !== game || Object.keys(a.facts).length > 0 || a.screen !== void 0);
351
+ this.#items = this.#items.filter((a) => a.game !== game || this.#sourced(a) || a.mine !== void 0);
349
352
  this.#prune(now);
350
353
  this.#saved.refreshed[game] = { at: now, ok: true, count: events.length };
351
354
  await this.save();
@@ -369,7 +372,8 @@ var Calendar = class {
369
372
  * time, checked against the site's end); from a web page (`site`) it is
370
373
  * that page's word on the event (the user's page it came from, else
371
374
  * "agent 查到的网页"), kept beside the data sites' and used where they say
372
- * nothing.
375
+ * nothing. `shot`: the screenshot read from the screen, already kept
376
+ * (`shot:<file>`): the evidence, and the picture where no source has one.
373
377
  * @returns the events written, with how each was matched.
374
378
  */
375
379
  async applyRead(game, from, events, context) {
@@ -397,11 +401,13 @@ var Calendar = class {
397
401
  ...event.cover !== void 0 && /^https:\/\/\S+$/.test(event.cover) ? { cover: event.cover } : {}
398
402
  };
399
403
  } else {
404
+ const image = context.shot ?? item.screen?.image;
400
405
  item.screen = {
401
406
  at: now,
402
407
  ...event.remaining === void 0 ? {} : { remaining: event.remaining },
403
408
  ...end === void 0 ? {} : { endsAt: end },
404
- evidence: false,
409
+ evidence: context.shot !== void 0,
410
+ ...image === void 0 ? {} : { image },
405
411
  ...context.sessionId === void 0 ? {} : { sessionId: context.sessionId }
406
412
  };
407
413
  if (event.todo !== void 0)
@@ -414,14 +420,121 @@ var Calendar = class {
414
420
  delete item.progress;
415
421
  }
416
422
  this.#derive(item, timeZone);
417
- if (from === "screen" && context.evidence !== void 0)
418
- item.screen.evidence = await context.evidence(this.#public(item)).catch(() => false);
419
423
  written.push({ activity: this.#public(item), matched: existing !== void 0 });
420
424
  }
421
425
  this.#prune(now);
422
426
  await this.save();
423
427
  return written;
424
428
  }
429
+ /**
430
+ * The user's own word on an event (`target.id`), or an event of their own
431
+ * (a `name` and no id; one of that name already in the calendar is that
432
+ * one). Fields equal to what the sources say are not kept, so a later
433
+ * refresh still moves them; on an event only the user has, every field is.
434
+ * @returns the event; undefined when there is no event with that id in that game.
435
+ * @throws Error when a new one has no name.
436
+ */
437
+ async saveOwn(target, patch, now, timeZone) {
438
+ let item;
439
+ if (target.id !== void 0) {
440
+ item = this.#items.find((a) => a.id === target.id && a.game === target.game);
441
+ if (item === void 0)
442
+ return void 0;
443
+ } else {
444
+ const name = typeof patch.name === "string" ? patch.name.trim() : "";
445
+ if (name === "")
446
+ throw new Error("\u65B0\u6D3B\u52A8\u8981\u6709\u540D\u5B57");
447
+ item = this.#items.find((a) => a.game === target.game && a.names.some((n) => idKey(n) === idKey(name))) ?? this.#create(target.game, name);
448
+ }
449
+ const mine = { ...item.mine };
450
+ delete mine.at;
451
+ for (const key of OWN_FIELDS) {
452
+ const value = patch[key];
453
+ if (value === void 0)
454
+ continue;
455
+ if (value === null || typeof value === "string" && value.trim() === "")
456
+ delete mine[key];
457
+ else
458
+ mine[key] = typeof value === "string" ? value.trim() : value;
459
+ }
460
+ if (mine.startsAt !== void 0 && mine.endsAt !== void 0 && mine.endsAt <= mine.startsAt)
461
+ throw new Error("\u7ED3\u675F\u65F6\u95F4\u8981\u665A\u4E8E\u5F00\u59CB\u65F6\u95F4");
462
+ if (this.#sourced(item)) {
463
+ const base = structuredClone(item);
464
+ delete base.mine;
465
+ this.#derive(base, timeZone);
466
+ for (const key of OWN_FIELDS)
467
+ if (mine[key] !== void 0 && mine[key] === base[key])
468
+ delete mine[key];
469
+ }
470
+ if (Object.keys(mine).length === 0)
471
+ delete item.mine;
472
+ else
473
+ item.mine = { ...mine, at: now };
474
+ if (mine.name !== void 0)
475
+ item.names = unique([...item.names, mine.name]);
476
+ if (patch.todo !== void 0)
477
+ item.todo = unique(patch.todo.map((t) => t.trim()).filter((t) => t !== "" && !item.done.includes(t)));
478
+ if (!this.#sourced(item) && item.mine === void 0) {
479
+ this.#items = this.#items.filter((a) => a !== item);
480
+ await this.save();
481
+ return void 0;
482
+ }
483
+ this.#derive(item, timeZone);
484
+ await this.save();
485
+ return this.#public(item);
486
+ }
487
+ /**
488
+ * Take back what the user wrote on an event: the sources' word shows again;
489
+ * an event only the user had goes.
490
+ * @returns the event as it is now; undefined when it went (or was not there).
491
+ */
492
+ async resetOwn(id, timeZone) {
493
+ const item = this.#items.find((a) => a.id === id);
494
+ if (item === void 0)
495
+ return void 0;
496
+ delete item.mine;
497
+ if (!this.#sourced(item)) {
498
+ this.#items = this.#items.filter((a) => a !== item);
499
+ await this.save();
500
+ return void 0;
501
+ }
502
+ this.#derive(item, timeZone);
503
+ await this.save();
504
+ return this.#public(item);
505
+ }
506
+ /** The pictures the user added that an event still shows (`own:<file>`). */
507
+ ownImages() {
508
+ const out = /* @__PURE__ */ new Set();
509
+ for (const a of this.#items)
510
+ if (a.mine?.cover?.startsWith("own:") === true)
511
+ out.add(a.mine.cover);
512
+ return out;
513
+ }
514
+ /** The screenshots an event was read from that are still kept (`shot:<file>`). */
515
+ shotImages() {
516
+ const out = /* @__PURE__ */ new Set();
517
+ for (const a of this.#items)
518
+ if (a.screen?.image !== void 0)
519
+ out.add(a.screen.image);
520
+ return out;
521
+ }
522
+ /**
523
+ * Screenshots kept before they were pictures (one file per event): now
524
+ * kept as `shot:<file>`, by event id.
525
+ */
526
+ async adoptShots(shots, timeZone) {
527
+ if (shots.size === 0)
528
+ return;
529
+ for (const item of this.#items) {
530
+ const shot = shots.get(item.id);
531
+ if (shot === void 0 || item.screen === void 0)
532
+ continue;
533
+ item.screen.image = shot;
534
+ this.#derive(item, timeZone(item.game));
535
+ }
536
+ await this.save();
537
+ }
425
538
  async remove(id) {
426
539
  const before = this.#items.length;
427
540
  this.#items = this.#items.filter((a) => a.id !== id);
@@ -447,6 +560,10 @@ var Calendar = class {
447
560
  const running = loose.find((a) => (a.startsAt ?? 0) <= now && now <= (a.endsAt ?? Infinity));
448
561
  return running ?? loose[0];
449
562
  }
563
+ /** Some source (a data site, a web page, the game screen) knows it. */
564
+ #sourced(item) {
565
+ return Object.keys(item.facts).length > 0 || item.screen !== void 0;
566
+ }
450
567
  #create(game, name) {
451
568
  let id = `${game}/${idKey(name)}`;
452
569
  for (let n = 2; this.#items.some((a) => a.id === id); n++)
@@ -484,7 +601,23 @@ var Calendar = class {
484
601
  delete item.site;
485
602
  else
486
603
  item.site = { source: primary.id, name: primary.fact.name, at: primary.fact.at };
604
+ const mine = item.mine;
605
+ if (mine?.name !== void 0) {
606
+ if (item.name !== mine.name)
607
+ item.baseName = item.name;
608
+ item.name = mine.name;
609
+ } else if (item.baseName !== void 0) {
610
+ item.name = item.baseName;
611
+ delete item.baseName;
612
+ }
613
+ for (const key of ["startsAt", "endsAt", "kind", "description", "url", "cover"])
614
+ if (mine?.[key] !== void 0)
615
+ set(key, mine[key]);
616
+ if (item.cover === void 0 && item.screen?.image !== void 0)
617
+ item.cover = item.screen.image;
487
618
  delete item.mismatch;
619
+ if (mine?.endsAt !== void 0)
620
+ return;
488
621
  const siteEnd = site?.fact.endsAt;
489
622
  if (site === void 0 || siteEnd === void 0)
490
623
  return;
@@ -502,6 +635,8 @@ var Calendar = class {
502
635
  #prune(now) {
503
636
  this.#items = this.#items.filter((a) => {
504
637
  if (a.endsAt === void 0) {
638
+ if (a.mine !== void 0)
639
+ return true;
505
640
  const seen = [a.screen?.at, ...Object.values(a.facts).map((f) => f.at)].filter((t) => t !== void 0);
506
641
  return (seen.length === 0 ? now : Math.max(...seen)) + KEEP_UNDATED_MS > now;
507
642
  }
@@ -509,9 +644,10 @@ var Calendar = class {
509
644
  });
510
645
  }
511
646
  #public(item) {
512
- const { names: _names, facts, ...activity } = item;
647
+ const { names: _names, facts, mine, baseName: _baseName, ...activity } = item;
513
648
  const sources = Object.entries(facts).map(([id, f]) => ({ id, name: f.name, at: f.at, web: f.web, ...f.endsAt === void 0 ? {} : { endsAt: f.endsAt } }));
514
- return structuredClone({ ...activity, ...sources.length === 0 ? {} : { sources } });
649
+ const own = mine === void 0 ? void 0 : { at: mine.at, fields: OWN_FIELDS.filter((k) => mine[k] !== void 0) };
650
+ return structuredClone({ ...activity, ...sources.length === 0 ? {} : { sources }, ...own === void 0 ? {} : { own } });
515
651
  }
516
652
  };
517
653
  function migrate(item) {
@@ -988,6 +1124,7 @@ function followedManifest(game) {
988
1124
  }
989
1125
 
990
1126
  // packages/game-intel/lib/tools.js
1127
+ var OWN_KEYS = { name: "name", starts_at: "startsAt", ends_at: "endsAt", kind: "kind", description: "description", url: "url", image: "cover" };
991
1128
  function parse(schema, args) {
992
1129
  const parsed = schema.safeParse(args);
993
1130
  if (!parsed.success)
@@ -1022,7 +1159,10 @@ function gameTools(deps) {
1022
1159
  ...a.startsAt !== void 0 && a.startsAt > now ? { starts: shortDate(a.startsAt, timeZone) } : {},
1023
1160
  ends: a.endsAt === void 0 ? "\u672A\u77E5" : `${shortDate(a.endsAt, timeZone)}\uFF08${leftText(a.endsAt, now)}\uFF09`,
1024
1161
  ...a.shopEndsAt === void 0 ? {} : { shop_ends: shortDate(a.shopEndsAt, timeZone) },
1025
- dates_from: a.site?.name ?? (a.screen !== void 0 ? "\u6E38\u620F\u753B\u9762" : "\u672A\u77E5"),
1162
+ dates_from: a.own?.fields.some((f) => f === "startsAt" || f === "endsAt") === true ? "\u7528\u6237\u81EA\u5DF1\u586B\u7684" : a.site?.name ?? (a.screen !== void 0 ? "\u6E38\u620F\u753B\u9762" : "\u672A\u77E5"),
1163
+ ...a.own === void 0 ? {} : { set_by_user: a.own.fields.map((f) => Object.entries(OWN_KEYS).find(([, v]) => v === f)[0]), ...a.sources === void 0 && a.screen === void 0 ? { added_by_user: true } : {} },
1164
+ ...a.kind === void 0 ? {} : { kind: a.kind },
1165
+ ...a.cover === void 0 ? {} : { image: a.cover.startsWith("own:") ? "\u7528\u6237\u7684\u56FE\u7247" : a.cover.startsWith("shot:") ? "\u6E38\u620F\u622A\u56FE\uFF08\u6CA1\u6709\u522B\u7684\u56FE\u65F6\u7528\u5B83\uFF09" : a.cover },
1026
1166
  ...a.screen === void 0 ? {} : { read_in_game: shortDate(a.screen.at, timeZone), ...a.progress === void 0 ? {} : { progress: a.progress } },
1027
1167
  // The checklist (from the game screen or kept by the user on the board), and the user's own "done".
1028
1168
  ...a.todo.length + a.done.length === 0 ? {} : { todo: a.todo, done: a.done },
@@ -1207,6 +1347,87 @@ function gameTools(deps) {
1207
1347
  };
1208
1348
  }
1209
1349
  }),
1350
+ tool({
1351
+ name: "game_activity_save",
1352
+ description: [
1353
+ "\u66FF\u7528\u6237\u624B\u52A8\u6DFB\u52A0\u4E00\u4E2A\u6D3B\u52A8\uFF0C\u6216\u6539\u4E00\u4E2A\u5DF2\u6709\u6D3B\u52A8\uFF08id \u4ECE game_activities \u67E5\uFF09\u3002\u7528\u6237\u8BF4\u300C\u5E2E\u6211\u52A0\u4E00\u4E2A XX \u6D3B\u52A8\u300D\u300C\u628A XX \u7684\u7ED3\u675F\u65F6\u95F4\u6539\u6210\u2026\u300D\u300C\u7ED9\u8FD9\u4E2A\u6D3B\u52A8\u6362\u5F20\u56FE\u300D\u65F6\u7528\u3002",
1354
+ "\u5199\u8FDB\u53BB\u7684\u5B57\u6BB5\u662F\u7528\u6237\u81EA\u5DF1\u7684\u8BF4\u6CD5\uFF1A\u6BD4\u6570\u636E\u7AD9\u548C\u7F51\u9875\u4F18\u5148\uFF0C\u5237\u65B0\u4E0D\u4F1A\u6539\u6389\u4E5F\u4E0D\u4F1A\u5220\u6389\uFF1B\u548C\u6765\u6E90\u4E00\u6837\u7684\u503C\u4E0D\u5355\u72EC\u4FDD\u5B58\u3002\u8BFB\u5230\u7684\u7F51\u9875\u6216\u6E38\u620F\u753B\u9762\u7528 game_record\uFF0C\u4E0D\u8981\u7528\u8FD9\u4E2A\u3002",
1355
+ "image \u53EF\u4EE5\u662F\u56FE\u7247\u7F51\u5740\uFF08https\uFF0C\u4F1A\u4E0B\u8F7D\u4FDD\u5B58\uFF09\u3001\u672C\u673A\u56FE\u7247\u6587\u4EF6\u8DEF\u5F84\uFF08\u76F8\u5BF9\u8DEF\u5F84\u6309\u5DE5\u4F5C\u533A\uFF09\u3001\u6216 screenshot\uFF08\u8FD9\u4E2A\u5BF9\u8BDD\u6700\u8FD1\u4E00\u5F20\u622A\u56FE\uFF09\u3002clear \u91CC\u7684\u5B57\u6BB5\u6536\u56DE\u7528\u6237\u7684\u503C\uFF0C\u6062\u590D\u6210\u6765\u6E90\u7684\u3002"
1356
+ ].join(""),
1357
+ properties: {
1358
+ game: GAME,
1359
+ id: { type: "string", description: "\u8981\u6539\u7684\u6D3B\u52A8 id\uFF08\u4E0D\u586B\u5C31\u662F\u65B0\u52A0\uFF1B\u540C\u540D\u7684\u5DF2\u6709\u6D3B\u52A8\u4F1A\u5F53\u6210\u540C\u4E00\u4E2A\uFF09\u3002" },
1360
+ name: { type: "string", description: "\u6D3B\u52A8\u540D\uFF1B\u65B0\u52A0\u65F6\u5FC5\u586B\u3002" },
1361
+ starts_at: { type: "string", description: "\u5F00\u59CB\u65F6\u95F4\uFF0C\u5199\u6210 2026/10/25 04:00 \u6216 10/25 04:00\uFF08\u6E38\u620F\u670D\u52A1\u5668\u65F6\u95F4\uFF09\u3002" },
1362
+ ends_at: { type: "string", description: "\u7ED3\u675F\u65F6\u95F4\uFF0C\u5199\u6CD5\u540C\u4E0A\u3002" },
1363
+ kind: { type: "string", description: "\u7C7B\u578B\uFF08\u53EF\u9009\uFF09\uFF0C\u4F8B\u5982\u300CSideStory\u300D\u300C\u7B7E\u5230\u300D\u300C\u9650\u65F6\u5546\u5E97\u300D\u3002" },
1364
+ description: { type: "string", description: "\u8BF4\u660E\uFF08\u53EF\u9009\uFF09\uFF0C500 \u5B57\u4EE5\u5185\u3002" },
1365
+ url: { type: "string", description: "\u6D3B\u52A8\u9875\u9762\u5730\u5740\uFF08\u53EF\u9009\uFF09\u3002" },
1366
+ image: { type: "string", description: "\u6D3B\u52A8\u5361\u7247\u7684\u56FE\u7247\uFF1Ahttps \u56FE\u7247\u7F51\u5740\u3001\u672C\u673A\u56FE\u7247\u6587\u4EF6\u8DEF\u5F84\uFF0C\u6216 screenshot\u3002" },
1367
+ todo: { type: "array", items: { type: "string" }, description: "\u8981\u505A\u7684\u4E8B\uFF08\u53EF\u9009\uFF0C\u66FF\u6362\u539F\u6765\u6CA1\u505A\u5B8C\u7684\u6E05\u5355\uFF09\uFF0C\u4F8B\u5982\u300C\u6D3B\u52A8\u5546\u5E97\u6362\u5B8C\u300D\u3002" },
1368
+ clear: { type: "array", items: { type: "string", enum: Object.keys(OWN_KEYS) }, description: "\u6536\u56DE\u7528\u6237\u586B\u8FC7\u7684\u8FD9\u4E9B\u5B57\u6BB5\uFF08\u6062\u590D\u6210\u6765\u6E90\u7684\u503C\uFF09\u3002" }
1369
+ },
1370
+ required: ["game"],
1371
+ kind: "other",
1372
+ title: (a) => `${typeof a["id"] === "string" ? "\u4FEE\u6539" : "\u6DFB\u52A0"}\u6D3B\u52A8\uFF1A${String(a["name"] ?? a["id"] ?? "")}`,
1373
+ execute: async (args, exec) => {
1374
+ const a = parse(external_exports.object({
1375
+ game: external_exports.string(),
1376
+ id: external_exports.string().nullish(),
1377
+ name: external_exports.string().max(60).nullish(),
1378
+ starts_at: external_exports.string().nullish(),
1379
+ ends_at: external_exports.string().nullish(),
1380
+ kind: external_exports.string().max(20).nullish(),
1381
+ description: external_exports.string().max(500).nullish(),
1382
+ url: external_exports.string().regex(/^https?:\/\/\S+$/, "\u53EA\u80FD\u662F http \u6216 https \u7F51\u5740").nullish(),
1383
+ image: external_exports.string().nullish(),
1384
+ todo: external_exports.array(external_exports.string().min(1).max(80)).max(30).nullish(),
1385
+ clear: external_exports.array(external_exports.enum(Object.keys(OWN_KEYS))).nullish()
1386
+ }), args);
1387
+ const game = known(a.game);
1388
+ if (a.id == null && (a.name == null || a.name.trim() === ""))
1389
+ throw new Error("\u65B0\u52A0\u6D3B\u52A8\u8981\u5199 name\uFF1B\u6539\u5DF2\u6709\u7684\u6D3B\u52A8\u8981\u5199 id\uFF08game_activities \u53EF\u67E5\uFF09");
1390
+ const when = (text, what) => {
1391
+ if (text == null)
1392
+ return void 0;
1393
+ const at = parseDateText(text, game.timezone, deps.now());
1394
+ if (at == null)
1395
+ throw new Error(`${what}\u300C${text}\u300D\u770B\u4E0D\u61C2\uFF1A\u5199\u6210 2026/10/25 04:00 \u6216 10/25 04:00`);
1396
+ return at;
1397
+ };
1398
+ const patch = {
1399
+ ...a.name == null ? {} : { name: a.name },
1400
+ ...a.starts_at == null ? {} : { startsAt: when(a.starts_at, "\u5F00\u59CB\u65F6\u95F4") },
1401
+ ...a.ends_at == null ? {} : { endsAt: when(a.ends_at, "\u7ED3\u675F\u65F6\u95F4") },
1402
+ ...a.kind == null ? {} : { kind: a.kind },
1403
+ ...a.description == null ? {} : { description: a.description },
1404
+ ...a.url == null ? {} : { url: a.url },
1405
+ ...a.todo == null ? {} : { todo: a.todo }
1406
+ };
1407
+ for (const key of a.clear ?? [])
1408
+ patch[OWN_KEYS[key]] = null;
1409
+ const sessionId = exec.agent?.session.id;
1410
+ const activity = await deps.saveActivity(game.id, a.id ?? void 0, patch, a.image ?? void 0, sessionId);
1411
+ return { saved: brief(activity, game.timezone), note: a.id == null ? "\u5DF2\u52A0\u5230\u6D3B\u52A8\u65E5\u5386\uFF1B\u6E38\u620F\u9875\u548C\u65E5\u62A5\u91CC\u90FD\u80FD\u770B\u5230" : "\u5DF2\u6539\u597D" };
1412
+ }
1413
+ }),
1414
+ tool({
1415
+ name: "game_activity_remove",
1416
+ description: "\u53BB\u6389\u6D3B\u52A8\uFF1Awhat=mine \u53EA\u6536\u56DE\u7528\u6237\u81EA\u5DF1\u586B\u7684\u5185\u5BB9\uFF08\u540D\u5B57\u3001\u65F6\u95F4\u3001\u56FE\u7247\u7B49\u6062\u590D\u6210\u6765\u6E90\u7684\uFF1B\u53EA\u6709\u7528\u6237\u586B\u7684\u6D3B\u52A8\u4F1A\u6574\u4E2A\u53BB\u6389\uFF09\uFF1Bwhat=all \u628A\u6D3B\u52A8\u4ECE\u65E5\u5386\u53BB\u6389\uFF08\u6570\u636E\u7AD9\u8FD8\u5217\u7740\u7684\u8BDD\uFF0C\u4E0B\u6B21\u5237\u65B0\u4F1A\u518D\u51FA\u73B0\uFF09\u3002\u53EA\u5728\u7528\u6237\u8981\u6C42\u65F6\u7528\u3002",
1417
+ properties: {
1418
+ id: { type: "string", description: "\u6D3B\u52A8 id\uFF08game_activities \u53EF\u67E5\uFF09\u3002" },
1419
+ what: { type: "string", enum: ["mine", "all"], description: "mine\uFF1A\u53EA\u6536\u56DE\u7528\u6237\u586B\u7684\uFF1Ball\uFF1A\u6574\u4E2A\u53BB\u6389\uFF08\u9ED8\u8BA4\uFF09\u3002" }
1420
+ },
1421
+ required: ["id"],
1422
+ kind: "other",
1423
+ title: (a) => `${a["what"] === "mine" ? "\u6062\u590D" : "\u53BB\u6389"}\u6D3B\u52A8\uFF1A${String(a["id"] ?? "")}`,
1424
+ execute: async (args) => {
1425
+ const a = parse(external_exports.object({ id: external_exports.string(), what: external_exports.enum(["mine", "all"]).nullish() }), args);
1426
+ const activity = await deps.removeActivity(a.id, a.what ?? "all");
1427
+ const game = activity === void 0 ? void 0 : deps.games().find((g) => g.id === activity.game);
1428
+ return activity === void 0 || game === void 0 ? { removed: a.id } : { restored: brief(activity, game.timezone) };
1429
+ }
1430
+ }),
1210
1431
  tool({
1211
1432
  name: "game_launch",
1212
1433
  description: "\u6E38\u620F\u6CA1\u5F00\u65F6\u542F\u52A8\u5B83\uFF1A\u7528\u73A9\u5B83\u7684\u811A\u672C\u5728\u300C\u542F\u52A8\u6E38\u620F\u300D\u91CC\u7684\u8BBE\u7F6E\uFF08\u6A21\u62DF\u5668\u91CC\u7684\u6E38\u620F\u4F1A\u5148\u542F\u52A8\u6A21\u62DF\u5668\uFF1BPC \u7248\u6253\u5F00\u8BBE\u7F6E\u597D\u7684\u7A0B\u5E8F\u6216\u542F\u52A8\u5668\uFF09\uFF0C\u5DF2\u7ECF\u5F00\u7740\u5C31\u4EC0\u4E48\u90FD\u4E0D\u505A\u3002\u6709\u811A\u672C\u6B63\u5728\u7528\u8FD9\u53F0\u6A21\u62DF\u5668\u6216\u9F20\u6807\u952E\u76D8\u65F6\u4F1A\u62D2\u7EDD\u3002\u542F\u52A8\u540E\u622A\u56FE\u786E\u8BA4\uFF1A\u5173\u6389\u516C\u544A\u548C\u66F4\u65B0\u63D0\u793A\uFF0C\u542F\u52A8\u5668\u9700\u8981\u70B9\u300C\u5F00\u59CB\u6E38\u620F\u300D\u5C31\u70B9\uFF1B\u8981\u8F93\u5165\u8D26\u53F7\u5BC6\u7801\u6216\u9A8C\u8BC1\u7801\u65F6\u505C\u4E0B\u4EA4\u7ED9\u7528\u6237\u3002",
@@ -1318,6 +1539,7 @@ var AdgGameIntel = class extends Service {
1318
1539
  this.ready = (async () => {
1319
1540
  await calendar.load();
1320
1541
  await this.loadGames(log);
1542
+ await this.adoptEvidence().catch((error) => log.warn(`old screenshots not converted: ${String(error)}`));
1321
1543
  daily.start();
1322
1544
  })();
1323
1545
  ctx.effect(() => () => this.daily.stop(), "adg-game-intel daily report");
@@ -1379,6 +1601,18 @@ var AdgGameIntel = class extends Service {
1379
1601
  add: async (manifest, guide) => {
1380
1602
  await this.ready;
1381
1603
  return this.addGame(manifest, guide);
1604
+ },
1605
+ saveActivity: async (game, id, patch, image, sessionId) => {
1606
+ await this.ready;
1607
+ return this.saveActivity(game, id, patch, image === void 0 ? void 0 : await this.imageFrom(image, sessionId));
1608
+ },
1609
+ removeActivity: async (id, what) => {
1610
+ await this.ready;
1611
+ if (what === "mine")
1612
+ return this.resetActivity(id);
1613
+ if (!await this.removeActivity(id))
1614
+ throw new Error("\u6CA1\u6709\u8FD9\u4E2A\u6D3B\u52A8\uFF08\u53EF\u80FD\u5DF2\u7ECF\u7ED3\u675F\u88AB\u6E05\u6389\u4E86\uFF09");
1615
+ return void 0;
1382
1616
  }
1383
1617
  }))
1384
1618
  registered.push(agent.tools.register({ ...tool2, execute: async (args, exec) => {
@@ -1430,9 +1664,13 @@ var AdgGameIntel = class extends Service {
1430
1664
  return settings;
1431
1665
  },
1432
1666
  "games/evidence": async (params) => {
1667
+ await this.ready;
1433
1668
  const id = params.activityId;
1434
- if (calendar.get(id)?.screen?.evidence !== true)
1669
+ const screen = calendar.get(id)?.screen;
1670
+ if (screen?.evidence !== true)
1435
1671
  throw new Error("\u8FD9\u4E2A\u6D3B\u52A8\u6CA1\u6709\u4FDD\u5B58\u622A\u56FE");
1672
+ if (screen.image !== void 0)
1673
+ return this.picture(screen.image);
1436
1674
  return { mediaType: "image/png", data: (await readFile2(this.evidenceFile(id))).toString("base64") };
1437
1675
  },
1438
1676
  "games/image": async (params) => {
@@ -1491,9 +1729,18 @@ var AdgGameIntel = class extends Service {
1491
1729
  },
1492
1730
  "games/activity/remove": async (params) => {
1493
1731
  await this.ready;
1494
- await calendar.remove(params.activityId);
1495
- changed();
1732
+ await this.removeActivity(params.activityId);
1496
1733
  return {};
1734
+ },
1735
+ "games/activity/save": async (params) => {
1736
+ await this.ready;
1737
+ const { game, activityId, image, todo, ...fields } = params;
1738
+ return this.saveActivity(game, activityId, { ...fields, ...todo === void 0 ? {} : { todo } }, image === void 0 ? void 0 : Buffer.from(image.data, "base64"));
1739
+ },
1740
+ "games/activity/reset": async (params) => {
1741
+ await this.ready;
1742
+ const activity = await this.resetActivity(params.activityId);
1743
+ return activity === void 0 ? {} : { activity };
1497
1744
  }
1498
1745
  }
1499
1746
  });
@@ -1508,6 +1755,14 @@ var AdgGameIntel = class extends Service {
1508
1755
  * gave are fetched: this is not a general-purpose proxy.
1509
1756
  */
1510
1757
  async picture(url) {
1758
+ if (OWN_IMAGE.test(url) || SHOT_IMAGE.test(url)) {
1759
+ const [kind, file] = url.split(":");
1760
+ const mediaType2 = IMAGE_TYPES.find(([ext2]) => file.endsWith(`.${ext2}`))[1];
1761
+ const bytes2 = await readFile2(join2(this.imageDir(kind), file)).catch(() => {
1762
+ throw new Error("\u8FD9\u5F20\u56FE\u7247\u5DF2\u7ECF\u4E0D\u5728\u4E86");
1763
+ });
1764
+ return { mediaType: mediaType2, data: bytes2.toString("base64") };
1765
+ }
1511
1766
  if (!this.calendar.pictures().has(url) && !this.games.some((g) => g.manifest.art === url))
1512
1767
  throw new Error("\u8FD9\u5F20\u56FE\u7247\u4E0D\u662F\u6570\u636E\u7AD9\u6216\u6E38\u620F\u5305\u7ED9\u7684\u5730\u5740");
1513
1768
  const name = createHash("sha256").update(url).digest("hex").slice(0, 32);
@@ -1584,6 +1839,96 @@ var AdgGameIntel = class extends Service {
1584
1839
  await this.loadGames(this.log);
1585
1840
  this.changed(gameId);
1586
1841
  }
1842
+ // ── the user's own events ──
1843
+ /**
1844
+ * Add an event of the user's own, or write their word on one (see
1845
+ * `Calendar.saveOwn`); `image`: a new picture, kept under
1846
+ * `<data>/games/images/own/`. @throws Error with what is wrong.
1847
+ */
1848
+ async saveActivity(gameId, activityId, patch, image) {
1849
+ const { manifest } = this.game(gameId);
1850
+ if (activityId !== void 0 && this.calendar.get(activityId)?.game !== manifest.id)
1851
+ throw new Error(`${manifest.name}\u6CA1\u6709\u6D3B\u52A8\u300C${activityId}\u300D\uFF08\u53EF\u80FD\u5DF2\u7ECF\u7ED3\u675F\u88AB\u6E05\u6389\u4E86\uFF1Bgame_activities \u53EF\u67E5\uFF09`);
1852
+ const cover = image === void 0 ? patch.cover : await this.keepImage(image);
1853
+ const activity = await this.calendar.saveOwn({ game: manifest.id, id: activityId }, { ...patch, ...cover === void 0 ? {} : { cover } }, this.clock.now(), manifest.timezone);
1854
+ await this.sweepImages();
1855
+ this.changed(manifest.id);
1856
+ if (activity === void 0)
1857
+ throw new Error("\u4EC0\u4E48\u90FD\u6CA1\u7559\u4E0B\uFF1A\u8FD9\u4E2A\u6D3B\u52A8\u53EA\u6709\u4F60\u586B\u7684\u5185\u5BB9\uFF0C\u6E05\u7A7A\u540E\u5C31\u4ECE\u65E5\u5386\u53BB\u6389\u4E86");
1858
+ return activity;
1859
+ }
1860
+ /** Take back what the user wrote on an event. @returns it as it is now; undefined when it went. */
1861
+ async resetActivity(activityId) {
1862
+ const before = this.calendar.get(activityId);
1863
+ if (before === void 0)
1864
+ throw new Error("\u6CA1\u6709\u8FD9\u4E2A\u6D3B\u52A8\uFF08\u53EF\u80FD\u5DF2\u7ECF\u7ED3\u675F\u88AB\u6E05\u6389\u4E86\uFF09");
1865
+ const timezone = this.games.find((g) => g.manifest.id === before.game)?.manifest.timezone ?? "Asia/Shanghai";
1866
+ const activity = await this.calendar.resetOwn(activityId, timezone);
1867
+ await this.sweepImages();
1868
+ this.changed(before.game);
1869
+ return activity;
1870
+ }
1871
+ async removeActivity(activityId) {
1872
+ const removed = await this.calendar.remove(activityId);
1873
+ await this.sweepImages();
1874
+ this.changed();
1875
+ return removed;
1876
+ }
1877
+ /**
1878
+ * A picture for an event, as the agent names it: an http(s) address (fetched
1879
+ * now and kept, so it stays when the page goes), a file on this computer
1880
+ * (a path relative to the workspace is fine), or `screenshot`, the last
1881
+ * screenshot of this conversation.
1882
+ */
1883
+ async imageFrom(source, sessionId) {
1884
+ const from = source.trim();
1885
+ if (from === "screenshot") {
1886
+ const shot = sessionId === void 0 ? void 0 : this.ctx.get("adgIntervene")?.lastShot(sessionId);
1887
+ if (shot === void 0)
1888
+ throw new Error("\u8FD9\u4E2A\u5BF9\u8BDD\u8FD8\u6CA1\u6709\u622A\u56FE\uFF1A\u5148\u7528 screen_capture \u622A\u4E00\u5F20");
1889
+ return shot;
1890
+ }
1891
+ if (/^https?:\/\//i.test(from)) {
1892
+ const response = await fetch(from, { signal: AbortSignal.timeout(2e4), headers: { accept: "image/*", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) MultiGameAssistant" } });
1893
+ if (!response.ok)
1894
+ throw new Error(`\u56FE\u7247\u8BFB\u4E0D\u5230\uFF1AHTTP ${response.status}`);
1895
+ return Buffer.from(await response.arrayBuffer());
1896
+ }
1897
+ const path = from.replace(/^file:\/\/\/?/i, "");
1898
+ const workspace = this.ctx.get("adgGateway")?.workspaceDir();
1899
+ const full = isAbsolute(path) || workspace === void 0 ? path : resolve(workspace, path);
1900
+ return readFile2(full).catch(() => {
1901
+ throw new Error(`\u8BFB\u4E0D\u5230\u56FE\u7247\u6587\u4EF6\uFF1A${full}`);
1902
+ });
1903
+ }
1904
+ /**
1905
+ * Keep a picture the user added (`own`) or a screenshot an event was read
1906
+ * from (`shot`), named by its content. @returns its address (`own:<hash>.<ext>`).
1907
+ */
1908
+ async keepImage(bytes, kind = "own") {
1909
+ if (kind === "own" && bytes.length > MAX_IMAGE_BYTES)
1910
+ throw new Error("\u56FE\u7247\u592A\u5927\uFF08\u6700\u591A 8 MB\uFF09");
1911
+ const ext = imageType(bytes);
1912
+ if (ext === void 0)
1913
+ throw new Error("\u4E0D\u662F\u652F\u6301\u7684\u56FE\u7247\uFF08png\u3001jpg\u3001gif\u3001webp\u3001avif\uFF09");
1914
+ const file = `${createHash("sha256").update(bytes).digest("hex").slice(0, 32)}.${ext}`;
1915
+ const dir = this.imageDir(kind);
1916
+ await mkdir2(dir, { recursive: true });
1917
+ await writeFile(join2(dir, file), bytes);
1918
+ return `${kind}:${file}`;
1919
+ }
1920
+ imageDir(kind) {
1921
+ return join2(this.dir, "images", kind === "own" ? "own" : "shots");
1922
+ }
1923
+ /** Pictures the user added, and screenshots, that no event has any more. */
1924
+ async sweepImages() {
1925
+ for (const [kind, used] of [["own", this.calendar.ownImages()], ["shot", this.calendar.shotImages()]]) {
1926
+ const dir = this.imageDir(kind);
1927
+ const files = await readdir(dir).catch(() => []);
1928
+ await Promise.all(files.filter((f) => !used.has(`${kind}:${f}`)).map((f) => rm(join2(dir, f), { force: true }).catch(() => {
1929
+ })));
1930
+ }
1931
+ }
1587
1932
  /**
1588
1933
  * The scripts that play each game: the ones its pack lists, and installed
1589
1934
  * scripts whose id, name, repository or pack names it (script-match.ts),
@@ -1715,6 +2060,7 @@ var AdgGameIntel = class extends Service {
1715
2060
  results.push({ game: manifest.id, ok: failures.length === 0 || count > 0, count, ...failures.length === 0 ? {} : { detail: failures.join("\uFF1B") } });
1716
2061
  this.changed(manifest.id);
1717
2062
  }
2063
+ await this.sweepImages();
1718
2064
  return results;
1719
2065
  }
1720
2066
  // ── what the agent read ──
@@ -1731,21 +2077,38 @@ var AdgGameIntel = class extends Service {
1731
2077
  return { ...event, source: { id: link.id, name: link.name } };
1732
2078
  return host === void 0 ? event : { ...event, source: { id: "agent", name: `${host}\uFF08agent \u67E5\u5230\uFF09` } };
1733
2079
  });
1734
- const written = await this.calendar.applyRead(gameId, from, credited, {
1735
- now: this.clock.now(),
1736
- timeZone: manifest.timezone,
1737
- sessionId,
1738
- ...shot === void 0 ? {} : {
1739
- evidence: async (activity) => {
1740
- await mkdir2(join2(this.dir, "evidence"), { recursive: true });
1741
- await writeFile(this.evidenceFile(activity.id), shot);
1742
- return true;
1743
- }
1744
- }
2080
+ const kept = shot === void 0 ? void 0 : await this.keepImage(shot, "shot").catch((error) => {
2081
+ this.log.warn(`screenshot not kept: ${error instanceof Error ? error.message : String(error)}`);
2082
+ return void 0;
1745
2083
  });
2084
+ const written = await this.calendar.applyRead(gameId, from, credited, { now: this.clock.now(), timeZone: manifest.timezone, sessionId, shot: kept });
2085
+ await this.sweepImages();
1746
2086
  this.changed(gameId);
1747
2087
  return written;
1748
2088
  }
2089
+ /**
2090
+ * Screenshots kept before they were pictures: one file per event under
2091
+ * `evidence/`. Each becomes a `shot:` picture (and its event's background).
2092
+ */
2093
+ async adoptEvidence() {
2094
+ const shots = /* @__PURE__ */ new Map();
2095
+ for (const a of this.calendar.list()) {
2096
+ if (a.screen?.evidence !== true || a.screen.image !== void 0)
2097
+ continue;
2098
+ const file = this.evidenceFile(a.id);
2099
+ const bytes = await readFile2(file).catch(() => void 0);
2100
+ if (bytes === void 0)
2101
+ continue;
2102
+ const kept = await this.keepImage(bytes, "shot").catch(() => void 0);
2103
+ if (kept === void 0)
2104
+ continue;
2105
+ shots.set(a.id, kept);
2106
+ await rm(file, { force: true }).catch(() => {
2107
+ });
2108
+ }
2109
+ await this.calendar.adoptShots(shots, (game) => this.games.find((g) => g.manifest.id === game)?.manifest.timezone ?? "Asia/Shanghai");
2110
+ }
2111
+ /** Where a screenshot was kept before `shot:` pictures. */
1749
2112
  evidenceFile(activityId) {
1750
2113
  return join2(this.dir, "evidence", `${activityId.replace(/[^\p{L}\p{N}-]+/gu, "_")}.png`);
1751
2114
  }
@@ -1945,6 +2308,20 @@ ${guide.trim()}
1945
2308
  return { channels, text };
1946
2309
  }
1947
2310
  };
2311
+ function imageType(bytes) {
2312
+ const ascii = (from, to) => String.fromCharCode(...bytes.subarray(from, to));
2313
+ if (bytes[0] === 137 && ascii(1, 4) === "PNG")
2314
+ return "png";
2315
+ if (bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255)
2316
+ return "jpg";
2317
+ if (ascii(0, 4) === "GIF8")
2318
+ return "gif";
2319
+ if (ascii(0, 4) === "RIFF" && ascii(8, 12) === "WEBP")
2320
+ return "webp";
2321
+ if (ascii(4, 8) === "ftyp" && /^avi[fs]$/.test(ascii(8, 12)))
2322
+ return "avif";
2323
+ return void 0;
2324
+ }
1948
2325
  function hostName(url) {
1949
2326
  try {
1950
2327
  return new URL(url).hostname.replace(/^www\./, "");
@@ -1968,11 +2345,13 @@ export {
1968
2345
  DEFAULT_SETTINGS,
1969
2346
  DailyReport,
1970
2347
  GAME_ROLE,
2348
+ OWN_FIELDS,
1971
2349
  buildReport,
1972
2350
  daysLeft,
1973
2351
  lib_default as default,
1974
2352
  gameTools,
1975
2353
  idKey,
2354
+ imageType,
1976
2355
  leftText,
1977
2356
  localDay,
1978
2357
  nameKey,
package/lib/gateway.js CHANGED
@@ -17,9 +17,9 @@ import {
17
17
  copyLabel,
18
18
  lib_default,
19
19
  streamFacts
20
- } from "./chunk-2DZ75ANX.js";
20
+ } from "./chunk-ZQ6KFVEO.js";
21
21
  import "./chunk-CCAB6XXZ.js";
22
- import "./chunk-BL6OSR2I.js";
22
+ import "./chunk-HPQCV6WO.js";
23
23
  import "./chunk-2UFLGPDY.js";
24
24
  export {
25
25
  AGENT_PRESET,
package/lib/guard.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __adgCreateRequire } from 'node:module'; import { dirname as __adgDirname } from 'node:path'; import { fileURLToPath as __adgFileURLToPath } from 'node:url'; const require = __adgCreateRequire(import.meta.url); const __filename = __adgFileURLToPath(import.meta.url); const __dirname = __adgDirname(__filename);
2
2
  import {
3
3
  GUARD_CAPABILITY
4
- } from "./chunk-BL6OSR2I.js";
4
+ } from "./chunk-HPQCV6WO.js";
5
5
  import "./chunk-2UFLGPDY.js";
6
6
 
7
7
  // packages/guard/lib/index.js
package/lib/intervene.js CHANGED
@@ -2,8 +2,8 @@ import { createRequire as __adgCreateRequire } from 'node:module'; import { dirn
2
2
  import {
3
3
  finalText,
4
4
  sessionConversations
5
- } from "./chunk-FLQD3DKB.js";
6
- import "./chunk-2DZ75ANX.js";
5
+ } from "./chunk-BNGDT7YT.js";
6
+ import "./chunk-ZQ6KFVEO.js";
7
7
  import {
8
8
  writeFileAtomic
9
9
  } from "./chunk-CCAB6XXZ.js";
@@ -11,7 +11,7 @@ import {
11
11
  INTERVENE_CAPABILITY,
12
12
  UPDATE_RULE,
13
13
  external_exports
14
- } from "./chunk-BL6OSR2I.js";
14
+ } from "./chunk-HPQCV6WO.js";
15
15
  import "./chunk-2UFLGPDY.js";
16
16
 
17
17
  // packages/intervene/lib/index.js