desktop-pet-app 0.4.1 → 0.4.3

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/out/main/index.js CHANGED
@@ -183,7 +183,9 @@ const SKIN_IDS = [
183
183
  "tiger",
184
184
  "whale",
185
185
  "penguin",
186
- "robot-blue"
186
+ "robot-blue",
187
+ "yier",
188
+ "bubu"
187
189
  ];
188
190
  const SKIN_LABELS = {
189
191
  codex: "Codex 小宠",
@@ -197,7 +199,9 @@ const SKIN_LABELS = {
197
199
  tiger: "老虎",
198
200
  whale: "蓝鲸",
199
201
  penguin: "企鹅",
200
- "robot-blue": "蓝色协作机器人"
202
+ "robot-blue": "蓝色协作机器人",
203
+ yier: "一二(白色)",
204
+ bubu: "布布(棕色)"
201
205
  };
202
206
  const PET_EVENT = "pet:event";
203
207
  const PET_MOVE = "pet:move";
@@ -35954,6 +35954,12 @@ function verifyInlineArtifact(artifact) {
35954
35954
  function isWorkspaceSnapshotContextItem(item) {
35955
35955
  return item.kind === "file" && item.mimeType === WORKSPACE_SNAPSHOT_MIME && item.encoding === "binary" && item.transport === "relay-blob" && item.required && Boolean(item.artifactRef);
35956
35956
  }
35957
+ function hasGitWorkspaceContext(task) {
35958
+ return task.envelope.workspace?.kind === "git" && Boolean(task.envelope.workspace.baseCommit) && task.envelope.executionPolicy.writableScopes.length > 0;
35959
+ }
35960
+ function requiresGitPatchResult(task) {
35961
+ return hasGitWorkspaceContext(task) && task.envelope.outputContract.acceptedKinds.includes("patch");
35962
+ }
35957
35963
  function isTerminalTaskStatus(status) {
35958
35964
  return status === "completed" || status === "failed" || status === "cancelled" || status === "expired";
35959
35965
  }
@@ -36198,7 +36204,8 @@ nextContinuationToken: ${nextContinuationToken}`);
36198
36204
  );
36199
36205
  }
36200
36206
  function formatV1Task(task) {
36201
- const isCodeWriteTask = task.envelope.workspace?.kind === "git" && Boolean(task.envelope.workspace.baseCommit) && task.envelope.executionPolicy.writableScopes.length > 0;
36207
+ const isCodeWriteTask = requiresGitPatchResult(task);
36208
+ const isArtifactOnlyGitTask = hasGitWorkspaceContext(task) && !isCodeWriteTask;
36202
36209
  const hasPortableWorkspace = task.envelope.context.items.some(isWorkspaceSnapshotContextItem);
36203
36210
  const capabilityMode = task.envelope.executionPolicy.capabilityMode ?? "required";
36204
36211
  const capabilityInstruction = capabilityMode === "required" && task.envelope.executionPolicy.capabilities.length > 0 ? `\u8FD9\u662F\u786C\u6027\u63A5\u5355\u6761\u4EF6\uFF1A\u5F53\u524D\u8BBE\u5907\u5DF2\u7ECF\u7531\u670D\u52A1\u7AEF\u786E\u8BA4\u5177\u5907 ${task.envelope.executionPolicy.capabilities.join("\u3001")}\u3002\u6267\u884C\u4E2D\u82E5\u5B9E\u9645\u73AF\u5883\u4E0E\u58F0\u660E\u6F02\u79FB\uFF0C\u5FC5\u987B\u62A5\u544A\u771F\u5B9E\u963B\u585E\uFF0C\u4E0D\u80FD\u4F2A\u9020\u5B8C\u6210\u3002` : "executionPolicy.capabilities \u4EC5\u4E3A\u73AF\u5883\u63D0\u793A\uFF0C\u4E0D\u53C2\u4E0E\u63A5\u5355\u7B5B\u9009\uFF1B\u8BF7\u4F7F\u7528\u5F53\u524D\u5BBF\u4E3B\u5DF2\u6709\u7684\u7B49\u4EF7\u80FD\u529B\u5B8C\u6210\u4EFB\u52A1\uFF0C\u7F3A\u5C11\u6807\u7B7E\u672C\u8EAB\u4E0D\u6784\u6210\u5931\u8D25\u3002";
@@ -36208,7 +36215,13 @@ function formatV1Task(task) {
36208
36215
  hasPortableWorkspace ? "1. \u5148\u53EA\u7528 taskId \u8C03\u7528 prepare_task_workspace\uFF1B\u5DE5\u5177\u4F1A\u81EA\u52A8\u4E0B\u8F7D\u5DE5\u4F5C\u533A\uFF0C\u7981\u6B62\u8BE2\u95EE A \u7684\u672C\u673A\u4ED3\u5E93\u8DEF\u5F84\u3002" : "1. \u8FD9\u662F\u6CA1\u6709\u5FEB\u7167\u7684\u65E7\u4EFB\u52A1\uFF0C\u8C03\u7528 prepare_task_workspace(taskId, sourceWorkspaceRoot=B \u672C\u673A\u5BF9\u5E94\u4ED3\u5E93\u6839\u76EE\u5F55)\u3002",
36209
36216
  "2. \u53EA\u80FD\u5728\u8FD4\u56DE\u7684 worktreeRoot \u5185\u8BFB\u53D6\u3001\u7F16\u8F91\u548C\u8FD0\u884C\u6D4B\u8BD5\uFF1B\u7981\u6B62\u4FEE\u6539\u5F53\u524D checkout\u3002",
36210
36217
  "3. \u5B8C\u6210\u540E\u8C03\u7528 submit_task_workspace_result\uFF0C\u8BA9\u5DE5\u5177\u81EA\u52A8\u5BA1\u8BA1\u8303\u56F4\u5E76\u751F\u6210 patch\uFF1B\u4E0D\u8981\u624B\u5DE5\u63D0\u4EA4 patch\u3002"
36211
- ] : ["", "\u8BF7\u4EC5\u4F7F\u7528\u5141\u8BB8\u7684\u80FD\u529B\u548C\u53EF\u5199\u8303\u56F4\u5B8C\u6210\u4EFB\u52A1\uFF0C\u7136\u540E\u8C03\u7528 submit_task_result \u56DE\u4F20\u6700\u7EC8\u7ED3\u679C\u3002"];
36218
+ ] : [
36219
+ "",
36220
+ isArtifactOnlyGitTask ? hasPortableWorkspace ? "\u8BE5\u4EFB\u52A1\u643A\u5E26 Git \u5DE5\u4F5C\u533A\u4E0A\u4E0B\u6587\uFF0C\u4F46\u8F93\u51FA\u5951\u7EA6\u4E0D\u63A5\u53D7 patch\uFF0C\u56E0\u6B64\u4E0D\u662F Git \u8865\u4E01\u4EFB\u52A1\u3002\u82E5\u9700\u8981\u8BFB\u53D6\u9879\u76EE\u5185\u5BB9\u6216\u5728\u5141\u8BB8\u8303\u56F4\u751F\u6210\u6587\u4EF6\uFF0C\u5148\u53EA\u7528 taskId \u8C03\u7528 prepare_task_workspace\uFF1B\u6700\u7EC8\u4E0D\u8981\u8C03\u7528 submit_task_workspace_result\u3002" : "\u8BE5\u4EFB\u52A1\u4F7F\u7528\u6CA1\u6709\u53EF\u79FB\u690D\u5FEB\u7167\u7684\u65E7 Git \u5DE5\u4F5C\u533A\u4E0A\u4E0B\u6587\uFF0C\u4F46\u8F93\u51FA\u5951\u7EA6\u4E0D\u63A5\u53D7 patch\u3002\u9700\u8981\u9879\u76EE\u5185\u5BB9\u65F6\u53EF\u8C03\u7528 prepare_task_workspace(taskId, sourceWorkspaceRoot=B \u672C\u673A\u5BF9\u5E94\u4ED3\u5E93\u6839\u76EE\u5F55)\uFF1B\u6700\u7EC8\u4E0D\u8981\u8C03\u7528 submit_task_workspace_result\u3002" : null,
36221
+ "\u8BF7\u4EC5\u4F7F\u7528\u5141\u8BB8\u7684\u80FD\u529B\u548C\u53EF\u5199\u8303\u56F4\u5B8C\u6210\u4EFB\u52A1\uFF0C\u7136\u540E\u8C03\u7528 submit_task_result \u56DE\u4F20\u6700\u7EC8\u7ED3\u679C\u3002",
36222
+ task.envelope.outputContract.acceptedKinds.includes("files") ? "\u4EA4\u4ED8\u672C\u5730\u6587\u4EF6\u65F6\uFF1A\u6587\u672C\u6216 Markdown \u53EF\u4F5C\u4E3A\u5185\u8054 artifact\uFF1B\u4E8C\u8FDB\u5236\u6587\u4EF6\u6216\u8F83\u5927\u6587\u4EF6\u5FC5\u987B\u5148\u8C03\u7528 upload_task_artifact(direction=result)\uFF0C\u53D6\u5F97 relay artifact \u56DE\u6267\u540E\uFF0C\u518D\u901A\u8FC7 submit_task_result.relayArtifacts \u63D0\u4EA4\u3002\u4E0D\u5F97\u53EA\u5728 summary \u4E2D\u586B\u5199\u672C\u673A\u7EDD\u5BF9\u8DEF\u5F84\u3002" : null,
36223
+ "\u53EA\u6709 submit_task_result \u6210\u529F\u8FD4\u56DE\u670D\u52A1\u5668\u6301\u4E45\u5316\u786E\u8BA4\u540E\uFF0C\u624D\u80FD\u58F0\u79F0\u6210\u679C\u5DF2\u63D0\u4EA4\u6216\u5DF2\u4E0A\u4F20\uFF1B\u6B64\u524D\u53EA\u80FD\u8BF4\u660E\u6587\u4EF6\u5DF2\u5728\u672C\u5730\u751F\u6210\u3002"
36224
+ ];
36212
36225
  return [
36213
36226
  `taskId: ${task.taskId}`,
36214
36227
  `status: ${task.status}`,
@@ -37319,7 +37332,7 @@ status: ${data.task.status}${gitBoundary}${warning}`;
37319
37332
  server2.registerTool(
37320
37333
  "prepare_task_workspace",
37321
37334
  {
37322
- description: "\u4EE3\u7801\u5199\u5165\u4EFB\u52A1\u7684\u63A5\u6536\u65B9\u5FC5\u987B\u5148\u8C03\u7528\u6B64\u5DE5\u5177\u3002\u65B0\u4EFB\u52A1\u4F1A\u81EA\u52A8\u4E0B\u8F7D\u3001\u6821\u9A8C\u5E76\u89E3\u5305 A \u52A0\u5BC6\u4E0A\u4F20\u7684\u53EF\u79FB\u690D\u5DE5\u4F5C\u533A\u5FEB\u7167\uFF0C\u4E0D\u9700\u8981 B \u9884\u5148\u62E5\u6709\u4ED3\u5E93\u6216\u77E5\u9053 A \u7684\u672C\u673A\u8DEF\u5F84\uFF1B\u540E\u7EED\u53EA\u80FD\u7F16\u8F91\u8FD4\u56DE\u7684 worktreeRoot\u3002\u65E7\u4EFB\u52A1\u624D\u9700\u8981 sourceWorkspaceRoot\u3002",
37335
+ description: "\u9700\u8981 Git \u9879\u76EE\u4E0A\u4E0B\u6587\u7684\u63A5\u6536\u65B9\u8C03\u7528\u6B64\u5DE5\u5177\u3002\u65B0\u4EFB\u52A1\u4F1A\u81EA\u52A8\u4E0B\u8F7D\u3001\u6821\u9A8C\u5E76\u89E3\u5305 A \u52A0\u5BC6\u4E0A\u4F20\u7684\u53EF\u79FB\u690D\u5DE5\u4F5C\u533A\u5FEB\u7167\uFF0C\u4E0D\u9700\u8981 B \u9884\u5148\u62E5\u6709\u4ED3\u5E93\u6216\u77E5\u9053 A \u7684\u672C\u673A\u8DEF\u5F84\uFF1B\u540E\u7EED\u53EA\u80FD\u5728\u8FD4\u56DE\u7684 worktreeRoot \u5185\u8BFB\u53D6\u6216\u5728\u5141\u8BB8\u8303\u56F4\u751F\u6210\u6587\u4EF6\u3002\u8F93\u51FA\u5951\u7EA6\u63A5\u53D7 patch \u65F6\u6700\u7EC8\u8C03\u7528 submit_task_workspace_result\uFF1B\u53EA\u63A5\u53D7 text/markdown/files \u65F6\u901A\u8FC7 submit_task_result \u4EA4\u4ED8\uFF0C\u4E8C\u8FDB\u5236\u6216\u5927\u6587\u4EF6\u5148\u4E0A\u4F20 artifact\u3002\u65E7\u4EFB\u52A1\u624D\u9700\u8981 sourceWorkspaceRoot\u3002",
37323
37336
  inputSchema: {
37324
37337
  taskId: external_exports.string().uuid(),
37325
37338
  sourceWorkspaceRoot: external_exports.string().min(1).optional().describe("\u4EC5\u517C\u5BB9\u6CA1\u6709\u5DE5\u4F5C\u533A\u5FEB\u7167\u7684\u65E7\u4EFB\u52A1\uFF1B\u65B0\u4EFB\u52A1\u4E0D\u8981\u586B\u5199")
@@ -37330,8 +37343,8 @@ status: ${data.task.status}${gitBoundary}${warning}`;
37330
37343
  const task = await getTaskForRole(taskId, "executor");
37331
37344
  const baseCommit = task.envelope.workspace?.kind === "git" && task.envelope.workspace.baseCommit;
37332
37345
  if (!baseCommit) throw new Error("\u4EFB\u52A1\u672A\u58F0\u660E Git baseCommit\uFF0C\u4E0D\u80FD\u521B\u5EFA\u9694\u79BB\u4EE3\u7801\u5DE5\u4F5C\u533A");
37333
- if (!isGitWriteTask(task)) {
37334
- throw new Error("Git \u4EE3\u7801\u5199\u5165\u4EFB\u52A1\u5FC5\u987B\u58F0\u660E baseCommit \u548C writableScopes");
37346
+ if (!hasGitWorkspaceContext(task)) {
37347
+ throw new Error("\u4EFB\u52A1\u5FC5\u987B\u540C\u65F6\u58F0\u660E Git baseCommit \u548C writableScopes \u624D\u80FD\u51C6\u5907\u9694\u79BB\u5DE5\u4F5C\u533A");
37335
37348
  }
37336
37349
  if (!["delivered", "accepted", "running"].includes(task.status)) {
37337
37350
  throw new Error(`\u4EFB\u52A1\u72B6\u6001 ${task.status} \u4E0D\u5141\u8BB8\u51C6\u5907\u4EE3\u7801\u5DE5\u4F5C\u533A`);
@@ -37388,10 +37401,16 @@ status: ${data.task.status}${gitBoundary}${warning}`;
37388
37401
  writableScopes: task.envelope.executionPolicy.writableScopes
37389
37402
  });
37390
37403
  }
37391
- await publishAutomaticTaskProgress(taskId, "working", "\u5DE5\u4F5C\u533A\u5DF2\u5C31\u7EEA\uFF0CAgent \u6B63\u5728\u4FEE\u6539\u4EE3\u7801");
37404
+ const patchDelivery = requiresGitPatchResult(task);
37405
+ await publishAutomaticTaskProgress(
37406
+ taskId,
37407
+ "working",
37408
+ patchDelivery ? "\u5DE5\u4F5C\u533A\u5DF2\u5C31\u7EEA\uFF0CAgent \u6B63\u5728\u4FEE\u6539\u4EE3\u7801" : "\u5DE5\u4F5C\u533A\u5DF2\u5C31\u7EEA\uFF0CAgent \u6B63\u5728\u6574\u7406\u4EFB\u52A1\u4EA7\u7269"
37409
+ );
37410
+ const deliveryInstruction = patchDelivery ? "\u5B8C\u6210\u540E\u8C03\u7528 submit_task_workspace_result\uFF1B\u4E0D\u8981\u624B\u5DE5\u751F\u6210\u6216\u63D0\u4EA4 patch\u3002" : "\u8BE5\u4EFB\u52A1\u4E0D\u63A5\u53D7 patch\u3002\u5B8C\u6210\u540E\u8C03\u7528 submit_task_result\uFF1B\u4E8C\u8FDB\u5236\u6216\u8F83\u5927\u6587\u4EF6\u5148\u8C03\u7528 upload_task_artifact(direction=result)\uFF0C\u5E76\u63D0\u4EA4\u8FD4\u56DE\u7684 relay artifact\u3002";
37392
37411
  return ok(
37393
- `\u9694\u79BB\u4EFB\u52A1\u5DE5\u4F5C\u533A\u5DF2\u5C31\u7EEA\u3002\u53EA\u80FD\u5728 worktreeRoot \u5185\u7F16\u8F91\uFF0C\u5B8C\u6210\u540E\u8C03\u7528 submit_task_workspace_result\uFF1B\u4E0D\u8981\u624B\u5DE5\u751F\u6210\u6216\u63D0\u4EA4 patch\u3002
37394
- ${JSON.stringify(workspace, null, 2)}`
37412
+ `\u9694\u79BB\u4EFB\u52A1\u5DE5\u4F5C\u533A\u5DF2\u5C31\u7EEA\u3002\u53EA\u80FD\u5728 worktreeRoot \u5185\u8BFB\u53D6\u6216\u5728\u5141\u8BB8\u8303\u56F4\u751F\u6210\u6587\u4EF6\uFF1B${deliveryInstruction}
37413
+ ` + JSON.stringify(workspace, null, 2)
37395
37414
  );
37396
37415
  } catch (err) {
37397
37416
  return fail(err instanceof AppError ? err.code : "PET_REJECTED", err instanceof Error ? err.message : String(err));
@@ -37503,7 +37522,9 @@ ${JSON.stringify(workspace, null, 2)}`
37503
37522
  const violations = taskResultSubmissionViolations(task, result);
37504
37523
  if (violations.length > 0) throw new Error(violations.join("\uFF1B"));
37505
37524
  await callPet({ type: "submit_task_result", result });
37506
- return ok("\u7ED3\u6784\u5316\u4EFB\u52A1\u7ED3\u679C\u5DF2\u6301\u4E45\u5316\u5E76\u901A\u77E5\u53D1\u8D77\u65B9\u3002\u82E5\u7528\u6237\u5DF2\u8981\u6C42\u503C\u73ED\uFF0C\u5FC5\u987B\u7ACB\u5373\u518D\u6B21\u8C03\u7528 wait_for_remote_task \u7EE7\u7EED\u7B49\u5F85\uFF0C\u4E0D\u8981\u7ED3\u675F\u5F53\u524D\u8F6E\u6B21\u3002");
37525
+ return ok(
37526
+ `\u670D\u52A1\u5668\u5DF2\u786E\u8BA4\u4EFB\u52A1\u7ED3\u679C\u6301\u4E45\u5316\uFF1AtaskId=${taskId}\uFF1Bstatus=${status}\uFF1Bartifacts=${result.artifacts.length}\u3002\u82E5\u7528\u6237\u5DF2\u8981\u6C42\u503C\u73ED\uFF0C\u5FC5\u987B\u7ACB\u5373\u518D\u6B21\u8C03\u7528 wait_for_remote_task \u7EE7\u7EED\u7B49\u5F85\uFF0C\u4E0D\u8981\u7ED3\u675F\u5F53\u524D\u8F6E\u6B21\u3002`
37527
+ );
37507
37528
  } catch (err) {
37508
37529
  return fail(err instanceof AppError ? err.code : "BAD_EVENT", err instanceof Error ? err.message : String(err));
37509
37530
  }
@@ -37775,7 +37796,7 @@ function taskResultSubmissionViolations(task, result) {
37775
37796
  if (totalBytes > task.envelope.outputContract.maxTotalBytes) {
37776
37797
  violations.push(`\u4EA7\u7269\u603B\u5927\u5C0F ${totalBytes} bytes \u8D85\u8FC7 maxTotalBytes=${task.envelope.outputContract.maxTotalBytes}`);
37777
37798
  }
37778
- if (result.status === "completed" && isGitWriteTask(task)) {
37799
+ if (result.status === "completed" && requiresGitPatchResult(task)) {
37779
37800
  violations.push(
37780
37801
  "\u8FD9\u662F Git \u4EE3\u7801\u5199\u5165\u4EFB\u52A1\uFF1A\u7981\u6B62\u7528 submit_task_result \u76F4\u63A5\u7ED3\u675F\uFF1B\u8BF7\u5148\u8C03\u7528 prepare_task_workspace\uFF0C\u5B8C\u6210\u4FEE\u6539\u540E\u7528 submit_task_workspace_result \u81EA\u52A8\u751F\u6210\u5E76\u56DE\u4F20 patch"
37781
37802
  );
@@ -37786,9 +37807,6 @@ function missingRequiredChecks(task, tests) {
37786
37807
  const passed = new Set(tests.filter((test) => test.status === "passed").map((test) => test.name));
37787
37808
  return (task.envelope.outputContract.requiredChecks ?? []).filter((check2) => !passed.has(check2));
37788
37809
  }
37789
- function isGitWriteTask(task) {
37790
- return task.envelope.workspace?.kind === "git" && Boolean(task.envelope.workspace.baseCommit) && task.envelope.executionPolicy.writableScopes.length > 0;
37791
- }
37792
37810
  async function resolveDelegationWorkspaceRoot(server2, explicitRoot, taskHint) {
37793
37811
  if (explicitRoot) return explicitRoot;
37794
37812
  if (!server2.server.getClientCapabilities()?.roots) return void 0;
@@ -170,12 +170,15 @@ class SpriteRenderer {
170
170
  }
171
171
  ctx;
172
172
  img = new Image();
173
+ source = this.img;
173
174
  ready = false;
174
175
  load(skin) {
175
176
  this.ready = false;
177
+ this.source = this.img;
176
178
  this.canvas.width = skin.displaySize?.width ?? Math.round(skin.frameWidth * SPRITE_SCALE);
177
179
  this.canvas.height = skin.displaySize?.height ?? Math.round(skin.frameHeight * SPRITE_SCALE);
178
180
  this.img.onload = () => {
181
+ this.source = skin.chromaKey ? this.removeChromaKey(skin) : this.img;
179
182
  this.ready = true;
180
183
  this.onReady();
181
184
  };
@@ -202,14 +205,17 @@ class SpriteRenderer {
202
205
  ctx.scale(-1, 1);
203
206
  }
204
207
  ctx.imageSmoothingEnabled = skin.smoothing ?? true;
205
- const sx = skin.imageLayout === "single" ? 0 : frame * skin.frameWidth;
206
- const sy = skin.imageLayout === "single" ? 0 : s.row * skin.frameHeight;
208
+ const sourceRect = skin.sourceRect;
209
+ const sourceWidth = sourceRect?.width ?? skin.frameWidth;
210
+ const sourceHeight = sourceRect?.height ?? skin.frameHeight;
211
+ const sx = (sourceRect?.x ?? 0) + (skin.imageLayout === "single" ? 0 : frame * sourceWidth);
212
+ const sy = (sourceRect?.y ?? 0) + (skin.imageLayout === "single" ? 0 : s.row * sourceHeight);
207
213
  ctx.drawImage(
208
- this.img,
214
+ this.source,
209
215
  sx,
210
216
  sy,
211
- skin.frameWidth,
212
- skin.frameHeight,
217
+ sourceWidth,
218
+ sourceHeight,
213
219
  0,
214
220
  0,
215
221
  w,
@@ -231,6 +237,28 @@ class SpriteRenderer {
231
237
  ctx.fillText("Z", w - 16, 16 - off);
232
238
  }
233
239
  }
240
+ /**
241
+ * 生成图使用纯品红背景以避免透明通道被错误烘焙为棋盘格。
242
+ * 图片只在加载时处理一次,绘制循环复用离屏画布。
243
+ */
244
+ removeChromaKey(skin) {
245
+ const key = skin.chromaKey;
246
+ const tolerance = key.tolerance ?? 24;
247
+ const canvas2 = document.createElement("canvas");
248
+ canvas2.width = this.img.naturalWidth;
249
+ canvas2.height = this.img.naturalHeight;
250
+ const ctx = canvas2.getContext("2d", { willReadFrequently: true });
251
+ ctx.drawImage(this.img, 0, 0);
252
+ const imageData = ctx.getImageData(0, 0, canvas2.width, canvas2.height);
253
+ const pixels = imageData.data;
254
+ for (let i = 0; i < pixels.length; i += 4) {
255
+ if (isChromaPixel(pixels[i], pixels[i + 1], pixels[i + 2], key, tolerance)) {
256
+ pixels[i + 3] = 0;
257
+ }
258
+ }
259
+ ctx.putImageData(imageData, 0, 0);
260
+ return canvas2;
261
+ }
234
262
  drawHeart(ctx, px, py) {
235
263
  for (let y = 0; y < HEART.length; y++) {
236
264
  for (let x = 0; x < HEART[y].length; x++) {
@@ -239,6 +267,9 @@ class SpriteRenderer {
239
267
  }
240
268
  }
241
269
  }
270
+ function isChromaPixel(red, green, blue, key, tolerance = key.tolerance ?? 24) {
271
+ return Math.abs(red - key.red) <= tolerance && Math.abs(green - key.green) <= tolerance && Math.abs(blue - key.blue) <= tolerance;
272
+ }
242
273
  function proceduralMotion(state, elapsedMs) {
243
274
  const wave = Math.sin(elapsedMs / 240);
244
275
  switch (state) {
@@ -358,6 +389,37 @@ const bird = {
358
389
  failed: [FAILED$9]
359
390
  }
360
391
  };
392
+ const yierBubuUrl = "" + new URL("yier-bubu-v2-BaEcMG7v.png", import.meta.url).href;
393
+ const still$2 = { row: 0, frames: 1, frameMs: 180 };
394
+ const bubu = {
395
+ id: "bubu",
396
+ name: "布布",
397
+ image: yierBubuUrl,
398
+ imageLayout: "single",
399
+ sourceRect: { x: 800, y: 160, width: 600, height: 720 },
400
+ frameWidth: 600,
401
+ frameHeight: 720,
402
+ displaySize: { width: 120, height: 144 },
403
+ recommendedWindow: { width: 200, height: 190 },
404
+ smoothing: false,
405
+ chromaKey: { red: 255, green: 0, blue: 255, tolerance: 150 },
406
+ states: {
407
+ idle: { ...still$2, frameMs: 900 },
408
+ walk: { ...still$2, frameMs: 180 },
409
+ sleep: { ...still$2, frameMs: 900 },
410
+ excited: { ...still$2, frameMs: 160 },
411
+ drag: { ...still$2, frameMs: 160 },
412
+ working: { ...still$2, frameMs: 300 },
413
+ success: { ...still$2, frameMs: 160 },
414
+ failed: { ...still$2, frameMs: 400 }
415
+ },
416
+ anchors: {
417
+ ground: { x: 0.5, y: 0.99 },
418
+ bubble: { x: 0.5, y: 0.04 },
419
+ effects: { character: { x: 0.5, y: 0.5 } },
420
+ contentBounds: { x: 0.08, y: 0.03, width: 0.83, height: 0.96 }
421
+ }
422
+ };
361
423
  const IDLE$8 = [
362
424
  "...oo......oo...",
363
425
  "..oiao....oaio..",
@@ -1426,7 +1488,7 @@ const whale = {
1426
1488
  }
1427
1489
  };
1428
1490
  const robotBlueUrl = "" + new URL("robot-blue-CFH9bWjZ.png", import.meta.url).href;
1429
- const still = { row: 0, frames: 1, frameMs: 180 };
1491
+ const still$1 = { row: 0, frames: 1, frameMs: 180 };
1430
1492
  const robotBlue = {
1431
1493
  id: "robot-blue",
1432
1494
  name: "蓝色协作机器人",
@@ -1438,14 +1500,14 @@ const robotBlue = {
1438
1500
  recommendedWindow: { width: 260, height: 320 },
1439
1501
  smoothing: true,
1440
1502
  states: {
1441
- idle: { ...still, frameMs: 220 },
1442
- walk: { ...still, frameMs: 110 },
1443
- sleep: { ...still, frameMs: 260 },
1444
- excited: { ...still, frameMs: 110 },
1445
- drag: { ...still, frameMs: 120 },
1446
- working: { ...still, frameMs: 150 },
1447
- success: { ...still, frameMs: 110 },
1448
- failed: { ...still, frameMs: 180 }
1503
+ idle: { ...still$1, frameMs: 220 },
1504
+ walk: { ...still$1, frameMs: 110 },
1505
+ sleep: { ...still$1, frameMs: 260 },
1506
+ excited: { ...still$1, frameMs: 110 },
1507
+ drag: { ...still$1, frameMs: 120 },
1508
+ working: { ...still$1, frameMs: 150 },
1509
+ success: { ...still$1, frameMs: 110 },
1510
+ failed: { ...still$1, frameMs: 180 }
1449
1511
  },
1450
1512
  anchors: {
1451
1513
  ground: { x: 0.5, y: 0.955 },
@@ -1458,6 +1520,36 @@ const robotBlue = {
1458
1520
  contentBounds: { x: 0.08, y: 0.02, width: 0.84, height: 0.94 }
1459
1521
  }
1460
1522
  };
1523
+ const still = { row: 0, frames: 1, frameMs: 180 };
1524
+ const yier = {
1525
+ id: "yier",
1526
+ name: "一二",
1527
+ image: yierBubuUrl,
1528
+ imageLayout: "single",
1529
+ sourceRect: { x: 144, y: 160, width: 600, height: 720 },
1530
+ frameWidth: 600,
1531
+ frameHeight: 720,
1532
+ displaySize: { width: 120, height: 144 },
1533
+ recommendedWindow: { width: 200, height: 190 },
1534
+ smoothing: false,
1535
+ chromaKey: { red: 255, green: 0, blue: 255, tolerance: 150 },
1536
+ states: {
1537
+ idle: { ...still, frameMs: 900 },
1538
+ walk: { ...still, frameMs: 180 },
1539
+ sleep: { ...still, frameMs: 900 },
1540
+ excited: { ...still, frameMs: 160 },
1541
+ drag: { ...still, frameMs: 160 },
1542
+ working: { ...still, frameMs: 300 },
1543
+ success: { ...still, frameMs: 160 },
1544
+ failed: { ...still, frameMs: 400 }
1545
+ },
1546
+ anchors: {
1547
+ ground: { x: 0.5, y: 0.99 },
1548
+ bubble: { x: 0.5, y: 0.04 },
1549
+ effects: { character: { x: 0.5, y: 0.5 } },
1550
+ contentBounds: { x: 0.07, y: 0.03, width: 0.84, height: 0.96 }
1551
+ }
1552
+ };
1461
1553
  const skins = {
1462
1554
  codex,
1463
1555
  claude,
@@ -1470,7 +1562,12 @@ const skins = {
1470
1562
  whale,
1471
1563
  penguin
1472
1564
  };
1473
- const spriteSkins = { hoot, "robot-blue": robotBlue };
1565
+ const spriteSkins = {
1566
+ hoot,
1567
+ "robot-blue": robotBlue,
1568
+ yier,
1569
+ bubu
1570
+ };
1474
1571
  const defaultSkin = codex;
1475
1572
  function resolveSkin(id) {
1476
1573
  const ascii = skins[id];
@@ -238,7 +238,7 @@
238
238
  }
239
239
  }
240
240
  </style>
241
- <script type="module" crossorigin src="./assets/index-BWDd9Gk_.js"></script>
241
+ <script type="module" crossorigin src="./assets/index-CUcY_qKX.js"></script>
242
242
  </head>
243
243
  <body>
244
244
  <canvas id="pet"></canvas>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "desktop-pet-app",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "AI desktop pet with MCP support and a self-hosted relay server",
5
5
  "license": "MIT",
6
6
  "keywords": [