nextclaw 0.46.0 → 0.47.0

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 (111) hide show
  1. package/dist/cli/app/index.js +1 -1
  2. package/dist/{nextclaw-cli-app-BVdP2e0w.js → nextclaw-cli-app-wU9aUEKj.js} +233 -1
  3. package/dist/nextclaw-cli-app-wU9aUEKj.js.map +1 -0
  4. package/package.json +14 -14
  5. package/resources/apps/nextclaw-github-issue-watcher/README.md +43 -0
  6. package/resources/apps/nextclaw-github-issue-watcher/guest/Cargo.lock +455 -0
  7. package/resources/apps/nextclaw-github-issue-watcher/guest/Cargo.toml +27 -0
  8. package/resources/apps/nextclaw-github-issue-watcher/guest/src/bindings.rs +11177 -0
  9. package/resources/apps/nextclaw-github-issue-watcher/guest/src/lib.rs +307 -0
  10. package/resources/apps/nextclaw-github-issue-watcher/guest/wit/deps/clocks@0.2.6/monotonic-clock.wit +50 -0
  11. package/resources/apps/nextclaw-github-issue-watcher/guest/wit/deps/clocks@0.2.6/timezone.wit +55 -0
  12. package/resources/apps/nextclaw-github-issue-watcher/guest/wit/deps/clocks@0.2.6/wall-clock.wit +46 -0
  13. package/resources/apps/nextclaw-github-issue-watcher/guest/wit/deps/clocks@0.2.6/world.wit +11 -0
  14. package/resources/apps/nextclaw-github-issue-watcher/guest/wit/deps/config@0.2.0-draft-2024-09-27/package.wit +1 -0
  15. package/resources/apps/nextclaw-github-issue-watcher/guest/wit/deps/config@0.2.0-draft-2024-09-27/store.wit +9 -0
  16. package/resources/apps/nextclaw-github-issue-watcher/guest/wit/deps/http@0.2.6/handler.wit +49 -0
  17. package/resources/apps/nextclaw-github-issue-watcher/guest/wit/deps/http@0.2.6/package.wit +1 -0
  18. package/resources/apps/nextclaw-github-issue-watcher/guest/wit/deps/http@0.2.6/types.wit +688 -0
  19. package/resources/apps/nextclaw-github-issue-watcher/guest/wit/deps/io@0.2.6/error.wit +34 -0
  20. package/resources/apps/nextclaw-github-issue-watcher/guest/wit/deps/io@0.2.6/poll.wit +47 -0
  21. package/resources/apps/nextclaw-github-issue-watcher/guest/wit/deps/io@0.2.6/streams.wit +290 -0
  22. package/resources/apps/nextclaw-github-issue-watcher/guest/wit/deps/io@0.2.6/world.wit +10 -0
  23. package/resources/apps/nextclaw-github-issue-watcher/guest/wit/deps/keyvalue@0.2.0-draft2/store.wit +124 -0
  24. package/resources/apps/nextclaw-github-issue-watcher/guest/wit/portable-service.wit +56 -0
  25. package/resources/apps/nextclaw-github-issue-watcher/manifest.json +34 -0
  26. package/resources/apps/nextclaw-github-issue-watcher/marketplace.json +18 -0
  27. package/resources/apps/nextclaw-github-issue-watcher/panels/nextclaw-github-issue-watcher-panel.panel/assets/app.js +148 -0
  28. package/resources/apps/nextclaw-github-issue-watcher/panels/nextclaw-github-issue-watcher-panel.panel/index.html +190 -0
  29. package/resources/apps/nextclaw-github-issue-watcher/panels/nextclaw-github-issue-watcher-panel.panel/panel-app.json +11 -0
  30. package/resources/apps/nextclaw-github-issue-watcher/service-components/nextclaw-github-issue-watcher-service/service-app.json +36 -0
  31. package/resources/apps/nextclaw-github-issue-watcher/service-components/nextclaw-github-issue-watcher-service/service.wasm +0 -0
  32. package/resources/apps/nextclaw-github-issue-watcher/tests/public-github-runner-smoke.tools.mjs +150 -0
  33. package/resources/apps/nextclaw-github-issue-watcher/tests/service-smoke.json +12 -0
  34. package/resources/apps/nextclaw-portable-runtime-lab/README.md +5 -3
  35. package/resources/apps/nextclaw-portable-runtime-lab/manifest.json +14 -2
  36. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-contacts.panel/assets/app.js +21 -0
  37. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-contacts.panel/index.html +1 -0
  38. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-focus.panel/assets/app.js +21 -0
  39. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-focus.panel/index.html +1 -0
  40. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-notes.panel/assets/app.js +21 -0
  41. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-notes.panel/index.html +1 -0
  42. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-panel.panel/assets/app.js +201 -0
  43. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-panel.panel/assets/style.css +42 -0
  44. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-panel.panel/index.html +40 -19
  45. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-panel.panel/panel-app.json +2 -2
  46. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-todos.panel/assets/app.js +21 -0
  47. package/resources/apps/nextclaw-portable-runtime-lab/panels/nextclaw-portable-runtime-lab-todos.panel/index.html +1 -0
  48. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-capabilities/service-app.json +110 -3
  49. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-capabilities/service.wasm +0 -0
  50. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-composition/service-app.json +12 -3
  51. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-composition/service.wasm +0 -0
  52. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-provider/service-app.json +11 -0
  53. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-provider/service.wasm +0 -0
  54. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-resident/service.wasm +0 -0
  55. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-sqlite/service-app.json +37 -0
  56. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-sqlite/service.wasm +0 -0
  57. package/resources/apps/nextclaw-portable-runtime-lab/service-components/nextclaw-portable-runtime-lab-state/service.wasm +0 -0
  58. package/ui-dist/assets/{api-aEu9cEI8.js → api-n96IfCKb.js} +2 -2
  59. package/ui-dist/assets/{api-aEu9cEI8.js.gz → api-n96IfCKb.js.gz} +0 -0
  60. package/ui-dist/assets/{appearance-settings-page-z4DaHc_E.js → appearance-settings-page-C_AqxSF_.js} +1 -1
  61. package/ui-dist/assets/appearance-settings-page-C_AqxSF_.js.gz +0 -0
  62. package/ui-dist/assets/{channels-list-page-x07sGqsT.js → channels-list-page-uR-M4Ls8.js} +1 -1
  63. package/ui-dist/assets/channels-list-page-uR-M4Ls8.js.gz +0 -0
  64. package/ui-dist/assets/{confirm-dialog-BALTnP33.js → confirm-dialog-CHVBMpla.js} +1 -1
  65. package/ui-dist/assets/confirm-dialog-CHVBMpla.js.gz +0 -0
  66. package/ui-dist/assets/{desktop-capabilities-page-4-v3wKLV.js → desktop-capabilities-page-phMuNq1B.js} +1 -1
  67. package/ui-dist/assets/desktop-capabilities-page-phMuNq1B.js.gz +0 -0
  68. package/ui-dist/assets/{desktop-update-config-Cl-_XHqU.js → desktop-update-config-C77hkbor.js} +1 -1
  69. package/ui-dist/assets/desktop-update-config-C77hkbor.js.gz +0 -0
  70. package/ui-dist/assets/{extensions-DFQXmSwG.js → extensions-CQKXTqTI.js} +1 -1
  71. package/ui-dist/assets/extensions-CQKXTqTI.js.gz +0 -0
  72. package/ui-dist/assets/{index-zuqzAvNx.js → index-CLw9DuCJ.js} +4 -4
  73. package/ui-dist/assets/index-CLw9DuCJ.js.gz +0 -0
  74. package/ui-dist/assets/mcp-marketplace-page-CbWOamyW.js +1 -0
  75. package/ui-dist/assets/{mcp-marketplace-page-COUopIOc.js → mcp-marketplace-page-s9PTnSDT.js} +1 -1
  76. package/ui-dist/assets/mcp-marketplace-page-s9PTnSDT.js.gz +0 -0
  77. package/ui-dist/assets/{model-config-page-g9_-hZqZ.js → model-config-page-CxnCUWRW.js} +1 -1
  78. package/ui-dist/assets/model-config-page-CxnCUWRW.js.gz +0 -0
  79. package/ui-dist/assets/{privacy-settings-page-pNhQQT4r.js → privacy-settings-page-D8HZHHjL.js} +1 -1
  80. package/ui-dist/assets/privacy-settings-page-D8HZHHjL.js.gz +0 -0
  81. package/ui-dist/assets/{providers-config-page-DftW_6KU.js → providers-config-page-C0GIoGxm.js} +1 -1
  82. package/ui-dist/assets/providers-config-page-C0GIoGxm.js.gz +0 -0
  83. package/ui-dist/assets/remote-DvTCSAxQ.js +1 -0
  84. package/ui-dist/assets/{runtime-config-page-Cd9sOYvn.js → runtime-config-page-VNJZui5f.js} +1 -1
  85. package/ui-dist/assets/runtime-config-page-VNJZui5f.js.gz +0 -0
  86. package/ui-dist/assets/{search-config-page-BqoDbKpM.js → search-config-page-D3_qNXbB.js} +1 -1
  87. package/ui-dist/assets/search-config-page-D3_qNXbB.js.gz +0 -0
  88. package/ui-dist/assets/{secrets-config-page-GsahsEVw.js → secrets-config-page-Be4P1E8o.js} +1 -1
  89. package/ui-dist/assets/secrets-config-page-Be4P1E8o.js.gz +0 -0
  90. package/ui-dist/assets/security-config-BtcpKIhQ.js +1 -0
  91. package/ui-dist/assets/{tag-chip-ClBWeQ4p.js → tag-chip-Dc_NcCEe.js} +1 -1
  92. package/ui-dist/index.html +5 -5
  93. package/ui-dist/index.html.gz +0 -0
  94. package/dist/nextclaw-cli-app-BVdP2e0w.js.map +0 -1
  95. package/ui-dist/assets/appearance-settings-page-z4DaHc_E.js.gz +0 -0
  96. package/ui-dist/assets/channels-list-page-x07sGqsT.js.gz +0 -0
  97. package/ui-dist/assets/confirm-dialog-BALTnP33.js.gz +0 -0
  98. package/ui-dist/assets/desktop-capabilities-page-4-v3wKLV.js.gz +0 -0
  99. package/ui-dist/assets/desktop-update-config-Cl-_XHqU.js.gz +0 -0
  100. package/ui-dist/assets/extensions-DFQXmSwG.js.gz +0 -0
  101. package/ui-dist/assets/index-zuqzAvNx.js.gz +0 -0
  102. package/ui-dist/assets/mcp-marketplace-page-COUopIOc.js.gz +0 -0
  103. package/ui-dist/assets/mcp-marketplace-page-DxPPmYqh.js +0 -1
  104. package/ui-dist/assets/model-config-page-g9_-hZqZ.js.gz +0 -0
  105. package/ui-dist/assets/privacy-settings-page-pNhQQT4r.js.gz +0 -0
  106. package/ui-dist/assets/providers-config-page-DftW_6KU.js.gz +0 -0
  107. package/ui-dist/assets/remote-DS2TkMPm.js +0 -1
  108. package/ui-dist/assets/runtime-config-page-Cd9sOYvn.js.gz +0 -0
  109. package/ui-dist/assets/search-config-page-BqoDbKpM.js.gz +0 -0
  110. package/ui-dist/assets/secrets-config-page-GsahsEVw.js.gz +0 -0
  111. package/ui-dist/assets/security-config-BnHQyrtG.js +0 -1
@@ -0,0 +1,148 @@
1
+ const actions = {
2
+ sync: "nextclaw-github-issue-watcher-service.issues_sync",
3
+ list: "nextclaw-github-issue-watcher-service.issues_list",
4
+ };
5
+
6
+ const elements = {
7
+ form: document.querySelector("[data-sync-form]"),
8
+ repository: document.querySelector("[data-repository]"),
9
+ sync: document.querySelector("[data-sync]"),
10
+ refresh: document.querySelector("[data-refresh]"),
11
+ state: document.querySelector("[data-state]"),
12
+ notice: document.querySelector("[data-notice]"),
13
+ repositorySummary: document.querySelector("[data-repository-summary]"),
14
+ syncTime: document.querySelector("[data-sync-time]"),
15
+ count: document.querySelector("[data-count]"),
16
+ issues: document.querySelector("[data-issues]"),
17
+ };
18
+
19
+ function serviceActions() {
20
+ const bridge = window.nextclaw?.serviceActions;
21
+ if (!bridge) throw new Error("应用还没有连接到 NextClaw,请刷新后重试。");
22
+ return bridge;
23
+ }
24
+
25
+ async function invoke(action, input = {}) {
26
+ return await serviceActions().invoke(action, input);
27
+ }
28
+
29
+ function setNotice(message = "", tone = "") {
30
+ elements.notice.textContent = message;
31
+ elements.notice.dataset.tone = tone;
32
+ }
33
+
34
+ function setBusy(busy) {
35
+ elements.sync.disabled = busy;
36
+ elements.refresh.disabled = busy;
37
+ elements.repository.disabled = busy;
38
+ elements.state.disabled = busy;
39
+ }
40
+
41
+ function formatTime(epochMs) {
42
+ if (!Number.isFinite(Number(epochMs))) return "";
43
+ return `上次同步:${new Date(Number(epochMs)).toLocaleString("zh-CN")}`;
44
+ }
45
+
46
+ function issueRow(issue) {
47
+ const row = document.createElement("li");
48
+ row.className = "issue";
49
+ const link = document.createElement("a");
50
+ link.href = typeof issue.url === "string" ? issue.url : "#";
51
+ link.target = "_blank";
52
+ link.rel = "noreferrer";
53
+ link.textContent = `#${issue.number ?? "—"} ${issue.title || "未命名 Issue"}`;
54
+ const meta = document.createElement("div");
55
+ meta.className = "meta";
56
+ for (const text of [
57
+ issue.state === "closed" ? "已关闭" : "打开",
58
+ issue.author ? `@${issue.author}` : "",
59
+ issue.updatedAt
60
+ ? `更新于 ${new Date(issue.updatedAt).toLocaleString("zh-CN")}`
61
+ : "",
62
+ ].filter(Boolean)) {
63
+ const item = document.createElement("span");
64
+ item.textContent = text;
65
+ meta.append(item);
66
+ }
67
+ for (const label of Array.isArray(issue.labels) ? issue.labels : []) {
68
+ const chip = document.createElement("span");
69
+ chip.className = "label";
70
+ chip.textContent = label;
71
+ meta.append(chip);
72
+ }
73
+ row.append(link, meta);
74
+ return row;
75
+ }
76
+
77
+ function render(snapshot) {
78
+ const issues = Array.isArray(snapshot?.issues) ? snapshot.issues : [];
79
+ if (snapshot?.repository && !elements.repository.value.trim())
80
+ elements.repository.value = snapshot.repository;
81
+ elements.repositorySummary.textContent = snapshot?.repository
82
+ ? `${snapshot.repository} · ${snapshot.filter || elements.state.value}`
83
+ : "还没有同步仓库";
84
+ elements.syncTime.textContent = formatTime(snapshot?.syncedAtEpochMs);
85
+ elements.count.textContent = `${issues.length} 条 Issue`;
86
+ elements.issues.replaceChildren();
87
+ if (issues.length === 0) {
88
+ const empty = document.createElement("li");
89
+ empty.className = "empty";
90
+ empty.textContent = snapshot?.message || "这个筛选条件下暂时没有 Issue。";
91
+ elements.issues.append(empty);
92
+ return;
93
+ }
94
+ for (const issue of issues) elements.issues.append(issueRow(issue));
95
+ }
96
+
97
+ async function refresh() {
98
+ const snapshot = await invoke(actions.list, { state: elements.state.value });
99
+ render(snapshot);
100
+ return snapshot;
101
+ }
102
+
103
+ elements.form.addEventListener("submit", (event) => {
104
+ event.preventDefault();
105
+ const repository = elements.repository.value.trim();
106
+ if (!repository) {
107
+ setNotice("先输入 GitHub 仓库,例如 owner/repository。", "error");
108
+ elements.repository.focus();
109
+ return;
110
+ }
111
+ setBusy(true);
112
+ setNotice("正在从 GitHub 同步…");
113
+ void invoke(actions.sync, { repository })
114
+ .then(async (result) => {
115
+ await refresh();
116
+ setNotice(`已同步 ${result.issueCount ?? 0} 条 Issue。`, "success");
117
+ })
118
+ .catch((error) =>
119
+ setNotice(
120
+ error instanceof Error ? error.message : String(error),
121
+ "error",
122
+ ),
123
+ )
124
+ .finally(() => setBusy(false));
125
+ });
126
+
127
+ elements.refresh.addEventListener("click", () => {
128
+ setBusy(true);
129
+ void refresh()
130
+ .then(() => setNotice("列表已刷新。", "success"))
131
+ .catch((error) =>
132
+ setNotice(
133
+ error instanceof Error ? error.message : String(error),
134
+ "error",
135
+ ),
136
+ )
137
+ .finally(() => setBusy(false));
138
+ });
139
+
140
+ elements.state.addEventListener("change", () => {
141
+ void refresh().catch((error) =>
142
+ setNotice(error instanceof Error ? error.message : String(error), "error"),
143
+ );
144
+ });
145
+
146
+ void refresh().catch((error) =>
147
+ setNotice(error instanceof Error ? error.message : String(error), "error"),
148
+ );
@@ -0,0 +1,190 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>GitHub Issue Watcher</title>
7
+ <style>
8
+ :root {
9
+ color: #20242b;
10
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
11
+ }
12
+ body {
13
+ margin: 0;
14
+ background: #f7f8fa;
15
+ }
16
+ main {
17
+ max-width: 880px;
18
+ margin: 0 auto;
19
+ padding: 28px 22px 48px;
20
+ }
21
+ h1 {
22
+ margin: 0;
23
+ font-size: 26px;
24
+ }
25
+ .intro {
26
+ color: #606875;
27
+ margin: 8px 0 24px;
28
+ }
29
+ .controls,
30
+ .filters {
31
+ display: flex;
32
+ flex-wrap: wrap;
33
+ gap: 10px;
34
+ align-items: center;
35
+ }
36
+ .controls {
37
+ padding: 16px;
38
+ background: #fff;
39
+ border: 1px solid #e4e7ec;
40
+ border-radius: 12px;
41
+ }
42
+ input,
43
+ select,
44
+ button {
45
+ font: inherit;
46
+ border-radius: 8px;
47
+ }
48
+ input,
49
+ select {
50
+ min-height: 38px;
51
+ box-sizing: border-box;
52
+ border: 1px solid #cbd1d9;
53
+ padding: 0 10px;
54
+ background: #fff;
55
+ }
56
+ input {
57
+ flex: 1 1 220px;
58
+ }
59
+ button {
60
+ min-height: 38px;
61
+ padding: 0 14px;
62
+ border: 0;
63
+ background: #2463d4;
64
+ color: #fff;
65
+ cursor: pointer;
66
+ }
67
+ button.secondary {
68
+ background: #e8edf7;
69
+ color: #22324f;
70
+ }
71
+ button:disabled {
72
+ cursor: wait;
73
+ opacity: 0.62;
74
+ }
75
+ .summary {
76
+ display: flex;
77
+ justify-content: space-between;
78
+ gap: 16px;
79
+ margin: 20px 0 12px;
80
+ color: #56606d;
81
+ font-size: 14px;
82
+ }
83
+ [data-notice] {
84
+ min-height: 22px;
85
+ margin: 12px 0;
86
+ color: #46515f;
87
+ }
88
+ [data-notice][data-tone="error"] {
89
+ color: #ba1a1a;
90
+ }
91
+ [data-notice][data-tone="success"] {
92
+ color: #167443;
93
+ }
94
+ ul {
95
+ list-style: none;
96
+ padding: 0;
97
+ margin: 0;
98
+ display: grid;
99
+ gap: 10px;
100
+ }
101
+ .issue {
102
+ background: #fff;
103
+ border: 1px solid #e4e7ec;
104
+ border-radius: 12px;
105
+ padding: 14px 16px;
106
+ }
107
+ .issue a {
108
+ color: #153d88;
109
+ text-decoration: none;
110
+ font-weight: 650;
111
+ }
112
+ .issue a:hover {
113
+ text-decoration: underline;
114
+ }
115
+ .meta {
116
+ display: flex;
117
+ flex-wrap: wrap;
118
+ gap: 8px 12px;
119
+ color: #687280;
120
+ font-size: 13px;
121
+ margin-top: 7px;
122
+ }
123
+ .label {
124
+ padding: 2px 7px;
125
+ border-radius: 999px;
126
+ background: #edf1f7;
127
+ }
128
+ .empty {
129
+ padding: 30px 18px;
130
+ border: 1px dashed #cbd1d9;
131
+ border-radius: 12px;
132
+ background: #fff;
133
+ color: #687280;
134
+ text-align: center;
135
+ }
136
+ @media (max-width: 560px) {
137
+ main {
138
+ padding: 20px 14px 36px;
139
+ }
140
+ .summary {
141
+ display: block;
142
+ }
143
+ .summary > * + * {
144
+ display: block;
145
+ margin-top: 6px;
146
+ }
147
+ }
148
+ </style>
149
+ </head>
150
+ <body>
151
+ <main>
152
+ <h1>GitHub Issue Watcher</h1>
153
+ <p class="intro">
154
+ 把一个仓库的最近 Issue 保存到 NextClaw,之后可离线筛选查看。可选 GitHub
155
+ Token 仅用于请求,不会出现在这里。
156
+ </p>
157
+
158
+ <form class="controls" data-sync-form>
159
+ <input
160
+ data-repository
161
+ name="repository"
162
+ autocomplete="off"
163
+ placeholder="owner/repository,例如 Peiiii/nextclaw"
164
+ aria-label="GitHub 仓库"
165
+ />
166
+ <button type="submit" data-sync>同步 Issue</button>
167
+ <button class="secondary" type="button" data-refresh>刷新列表</button>
168
+ </form>
169
+ <p data-notice aria-live="polite"></p>
170
+
171
+ <div class="summary">
172
+ <span data-repository-summary>还没有同步仓库</span>
173
+ <span data-sync-time></span>
174
+ </div>
175
+ <div class="filters" aria-label="Issue 状态筛选">
176
+ <label
177
+ >显示
178
+ <select data-state>
179
+ <option value="open">打开的 Issue</option>
180
+ <option value="closed">已关闭的 Issue</option>
181
+ <option value="all">全部 Issue</option>
182
+ </select>
183
+ </label>
184
+ <span data-count></span>
185
+ </div>
186
+ <ul data-issues aria-live="polite"></ul>
187
+ </main>
188
+ <script src="assets/app.js"></script>
189
+ </body>
190
+ </html>
@@ -0,0 +1,11 @@
1
+ {
2
+ "id": "nextclaw-github-issue-watcher-panel",
3
+ "title": "GitHub Issue Watcher",
4
+ "description": "配置仓库、同步并筛选最近 Issue。",
5
+ "icon": "🔎",
6
+ "entry": "index.html",
7
+ "actions": [
8
+ "nextclaw-github-issue-watcher-service.issues_sync",
9
+ "nextclaw-github-issue-watcher-service.issues_list"
10
+ ]
11
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "id": "nextclaw-github-issue-watcher-service",
3
+ "title": "GitHub Issue Watcher",
4
+ "description": "同步并读取仓库 Issue;数据保存在此应用自己的存储空间。",
5
+ "protocol": "wasi-component",
6
+ "component": { "entry": "service.wasm" },
7
+ "actions": {
8
+ "issues_sync": {
9
+ "risk": "external",
10
+ "title": "同步 GitHub Issue",
11
+ "inputSchema": {
12
+ "type": "object",
13
+ "required": ["repository"],
14
+ "properties": {
15
+ "repository": { "type": "string", "pattern": "^[^/]+/[^/]+$" }
16
+ },
17
+ "additionalProperties": false
18
+ }
19
+ },
20
+ "issues_list": {
21
+ "risk": "read",
22
+ "title": "查看已同步 Issue",
23
+ "inputSchema": {
24
+ "type": "object",
25
+ "properties": {
26
+ "state": {
27
+ "type": "string",
28
+ "enum": ["open", "closed", "all"],
29
+ "default": "open"
30
+ }
31
+ },
32
+ "additionalProperties": false
33
+ }
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,150 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const args = new Map();
9
+ for (let index = 2; index < process.argv.length; index += 2) {
10
+ args.set(process.argv[index], process.argv[index + 1]);
11
+ }
12
+
13
+ const runnerPath =
14
+ args.get("--runner") ?? process.env.NEXTCLAW_WASMTIME_RUNNER_PATH;
15
+ const outputPath = args.get("--output");
16
+ if (!runnerPath) {
17
+ throw new Error("Pass --runner <path> or set NEXTCLAW_WASMTIME_RUNNER_PATH.");
18
+ }
19
+
20
+ const repository = args.get("--repository") ?? "Peiiii/nextclaw";
21
+ if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository)) {
22
+ throw new Error("--repository must be owner/repository.");
23
+ }
24
+
25
+ const appDirectory = path.resolve(
26
+ path.dirname(fileURLToPath(import.meta.url)),
27
+ "..",
28
+ );
29
+ const componentPath = path.join(
30
+ appDirectory,
31
+ "service-components",
32
+ "nextclaw-github-issue-watcher-service",
33
+ "service.wasm",
34
+ );
35
+ const dataDirectory = await mkdtemp(
36
+ path.join(tmpdir(), "nextclaw-github-issue-watcher-"),
37
+ );
38
+ const runner = spawn(runnerPath, [], { stdio: ["pipe", "pipe", "pipe"] });
39
+ const pendingResponses = new Map();
40
+ const pendingJobs = new Map();
41
+ let outputBuffer = "";
42
+ let stderr = "";
43
+
44
+ runner.stderr.on("data", (chunk) => {
45
+ stderr += chunk;
46
+ });
47
+ runner.stdout.on("data", (chunk) => {
48
+ outputBuffer += chunk;
49
+ while (true) {
50
+ const newline = outputBuffer.indexOf("\n");
51
+ if (newline < 0) break;
52
+ const line = outputBuffer.slice(0, newline);
53
+ outputBuffer = outputBuffer.slice(newline + 1);
54
+ const message = JSON.parse(line);
55
+ if (message.kind === "response") {
56
+ pendingResponses.get(message.requestId)?.(message);
57
+ } else if (message.kind === "job-terminal") {
58
+ pendingJobs.get(message.jobId)?.(message);
59
+ }
60
+ }
61
+ });
62
+
63
+ function runAction(actionName, input) {
64
+ return new Promise((resolve, reject) => {
65
+ const requestId = randomUUID();
66
+ const jobId = randomUUID();
67
+ const timeout = setTimeout(() => {
68
+ pendingResponses.delete(requestId);
69
+ pendingJobs.delete(jobId);
70
+ reject(new Error(`Timed out waiting for ${actionName}. ${stderr}`));
71
+ }, 30_000);
72
+ pendingResponses.set(requestId, (response) => {
73
+ pendingResponses.delete(requestId);
74
+ if (!response.ok) {
75
+ clearTimeout(timeout);
76
+ reject(new Error(JSON.stringify(response.error)));
77
+ return;
78
+ }
79
+ pendingJobs.set(jobId, (terminal) => {
80
+ pendingJobs.delete(jobId);
81
+ clearTimeout(timeout);
82
+ if (terminal.status === "succeeded") resolve(terminal.result);
83
+ else reject(new Error(`${JSON.stringify(terminal.error)}${stderr ? `\nrunner stderr:\n${stderr}` : ""}`));
84
+ });
85
+ });
86
+ runner.stdin.write(
87
+ `${JSON.stringify({
88
+ requestId,
89
+ jobId,
90
+ operation: "start-job",
91
+ actionName,
92
+ input,
93
+ timeoutMs: 20_000,
94
+ app: {
95
+ id: "nextclaw.github-issue-watcher.public-smoke",
96
+ componentPath,
97
+ dataDirectory,
98
+ storageEnabled: true,
99
+ allowedDomains: ["api.github.com"],
100
+ },
101
+ })}\n`,
102
+ );
103
+ });
104
+ }
105
+
106
+ try {
107
+ const before = await runAction("issues_list", { state: "all" });
108
+ const sync = await runAction("issues_sync", { repository });
109
+ const after = await runAction("issues_list", { state: "all" });
110
+ const firstIssue = after.issues?.[0];
111
+ const validIssue =
112
+ Number.isInteger(firstIssue?.number) &&
113
+ typeof firstIssue.title === "string" &&
114
+ firstIssue.title.length > 0 &&
115
+ typeof firstIssue.url === "string" &&
116
+ firstIssue.url.startsWith("https://github.com/");
117
+ if (
118
+ before.persistedBy !== "wasi:keyvalue/store" ||
119
+ sync.persistedBy !== "wasi:keyvalue/store" ||
120
+ sync.requestedVia !== "wasi:http/outgoing-handler" ||
121
+ after.persistedBy !== "wasi:keyvalue/store" ||
122
+ after.repository !== repository ||
123
+ !Array.isArray(after.issues) ||
124
+ after.issues.length === 0 ||
125
+ !validIssue
126
+ ) {
127
+ throw new Error(JSON.stringify({ before, sync, after }));
128
+ }
129
+ const summary = {
130
+ schemaVersion: 1,
131
+ kind: "nextclaw.portable-runtime.reference-app",
132
+ ok: true,
133
+ checks: ["github-sync", "persisted-list", "standard-wasi-kv", "public-issue-shape"],
134
+ repository,
135
+ issueCount: after.issues.length,
136
+ firstIssue: {
137
+ number: firstIssue.number,
138
+ title: firstIssue.title,
139
+ url: firstIssue.url,
140
+ },
141
+ persistedBy: after.persistedBy,
142
+ requestedVia: sync.requestedVia,
143
+ };
144
+ const serialized = `${JSON.stringify(summary, null, 2)}\n`;
145
+ if (outputPath) await writeFile(path.resolve(outputPath), serialized, "utf8");
146
+ process.stdout.write(serialized);
147
+ } finally {
148
+ runner.kill("SIGTERM");
149
+ await rm(dataDirectory, { recursive: true, force: true });
150
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "component": "nextclaw-github-issue-watcher-service",
4
+ "resetData": true,
5
+ "steps": [
6
+ {
7
+ "action": "issues_list",
8
+ "input": { "state": "open" },
9
+ "expect": { "issues": [], "persistedBy": "wasi:keyvalue/store" }
10
+ }
11
+ ]
12
+ }
@@ -11,7 +11,9 @@
11
11
  | 专注小钟 | 开始一轮专注、记录完成点,关掉页面后仍计时 | Resident Component、宿主事件投递、持久恢复 |
12
12
  | 联系人整理 | 清理姓名空格、邮箱大小写和重复标签 | Consumer 经宿主调用独立 Provider Component |
13
13
 
14
- 应用列表还保留一个「开发者验证台」。它不是普通用户的主入口,只用于查看共享 runner、受控网络、越权拒绝、超时恢复和运行时指标等底层证据。
14
+ 运行时包还包含一个仅用于验收的标准 Spin SQLite Component:它使用 `fermyon:spin@2.0.0/sqlite` 完成建表、写入和查询;每个 App 实例的数据目录独立,重启后仍从同一私有数据库读取,未授予存储时由标准接口返回拒绝。
15
+
16
+ 应用列表还保留一个「运行时验收与证据」。它不是普通用户的主入口,用来查看已安装实例真实产生的 PRT 验收记录、最近环境、运行时间和脱敏证据。没有记录的项目会显示“未验证”,不会把静态说明当成通过;当前尚未注册检查器的项目会标明“等待对应检查器”。
15
17
 
16
18
  ## Agent 也能使用
17
19
 
@@ -24,11 +26,11 @@
24
26
  → Service Action 授权
25
27
  → ServiceAppManager
26
28
  → wasi-component runtime
27
- → 共享 Rust/Wasmtime runner
29
+ → 共享 Rust/Spin runner(底层使用 Wasmtime)
28
30
  → Host capability / component-call
29
31
  → Rust/WASM Component
30
32
  ```
31
33
 
32
34
  ## 当前边界
33
35
 
34
- 这是单平台技术 MVP:官方 Guest 只覆盖 Rust,不承诺 Python/FastAPI 等现有框架直接编译,也不代表跨平台 runner 分发、签名、升级和生产级资源治理已经完成。
36
+ 官方 Guest 开发链路目前以 Rust 为主,不承诺 Python/FastAPI 等现有框架可以直接编译成 Component。具体能力是否完成、在哪个平台通过,以「运行时验收与证据」中当前安装实例的编号记录为准;没有当前证据就不会显示为通过。
@@ -2,11 +2,11 @@
2
2
  "schemaVersion": 2,
3
3
  "id": "nextclaw.portable-runtime-lab",
4
4
  "name": "日常小工具箱",
5
- "version": "0.5.2",
5
+ "version": "0.6.0",
6
6
  "description": "清单、便签、专注计时和联系人整理,四个能直接使用的日常小应用。",
7
7
  "icon": "assets/icon.svg",
8
8
  "engines": {
9
- "nextclaw": ">=0.43.0"
9
+ "nextclaw": ">=0.46.0"
10
10
  },
11
11
  "presentation": {
12
12
  "primaryPanel": "nextclaw-portable-runtime-lab-todos"
@@ -27,6 +27,14 @@
27
27
  },
28
28
  "allowedDomains": [
29
29
  "httpbin.org"
30
+ ],
31
+ "secrets": [
32
+ {
33
+ "id": "api-token",
34
+ "title": "验证用 API Token",
35
+ "description": "仅用于 Portable Capability Lab 的标准 WASI Secret 验证。",
36
+ "required": false
37
+ }
30
38
  ]
31
39
  },
32
40
  "components": [
@@ -58,6 +66,10 @@
58
66
  "kind": "service",
59
67
  "path": "service-components/nextclaw-portable-runtime-lab-capabilities"
60
68
  },
69
+ {
70
+ "kind": "service",
71
+ "path": "service-components/nextclaw-portable-runtime-lab-sqlite"
72
+ },
61
73
  {
62
74
  "kind": "service",
63
75
  "path": "service-components/nextclaw-portable-runtime-lab-resident"
@@ -27,6 +27,12 @@ function serviceActions() {
27
27
  return bridge;
28
28
  }
29
29
 
30
+ function verificationRecords() {
31
+ const bridge = window.nextclaw?.verificationRecords;
32
+ if (!bridge) throw new Error("验收记录桥尚未就绪。");
33
+ return bridge;
34
+ }
35
+
30
36
  async function invoke(actionId, input = {}) {
31
37
  return await serviceActions().invoke(actionId, input);
32
38
  }
@@ -90,3 +96,18 @@ elements.copy.addEventListener("click", () => {
90
96
  void invoke(actionIds.providerStatus).then((status) => {
91
97
  elements.count.textContent = `${status.providerCallCount ?? 0} 次`;
92
98
  }).catch(() => undefined);
99
+ async function refreshVerificationStatus() {
100
+ const element = document.querySelector("[data-verification-status]");
101
+ if (!element) return;
102
+ try {
103
+ const payload = await verificationRecords().list({ appId: "nextclaw.portable-runtime-lab", limit: 500 });
104
+ const entry = (payload?.entries || []).find((candidate) => candidate.acceptanceId === "PRT-COMP-001");
105
+ element.textContent = entry
106
+ ? `${entry.status === "passed" ? "已有局部证据" : entry.status} · ${new Date(entry.finishedAt).toLocaleString()}`
107
+ : "未验证(暂无 PRT-COMP-001 记录)";
108
+ } catch {
109
+ element.textContent = "暂时无法读取验收记录";
110
+ }
111
+ }
112
+
113
+ void refreshVerificationStatus();
@@ -35,6 +35,7 @@
35
35
  <details class="behind-scenes">
36
36
  <summary>这和普通表单有什么不同?</summary>
37
37
  <p>这个 Panel 只负责收集和展示。真正的规范化逻辑由独立 Provider Component 提供,Consumer 通过宿主受控调用进行组合。以后替换联系人规则,页面不用复制一份实现。</p>
38
+ <p><strong>已验证能力</strong>:<span data-verification-status>读取中…</span></p>
38
39
  <dl><div><dt>组合方式</dt><dd>Host mediated component call</dd></div><div><dt>Provider 已处理</dt><dd data-proof-count>等待执行</dd></div></dl>
39
40
  </details>
40
41
  </main>
@@ -21,6 +21,12 @@ function serviceActions() {
21
21
  return bridge;
22
22
  }
23
23
 
24
+ function verificationRecords() {
25
+ const bridge = window.nextclaw?.verificationRecords;
26
+ if (!bridge) throw new Error("验收记录桥尚未就绪。");
27
+ return bridge;
28
+ }
29
+
24
30
  async function invoke(actionId, input = {}) {
25
31
  return await serviceActions().invoke(actionId, input);
26
32
  }
@@ -130,3 +136,18 @@ const poll = window.setInterval(() => {
130
136
  if (document.visibilityState === "visible") void refresh();
131
137
  }, 1000);
132
138
  window.addEventListener("pagehide", () => window.clearInterval(poll), { once: true });
139
+ async function refreshVerificationStatus() {
140
+ const element = document.querySelector("[data-verification-status]");
141
+ if (!element) return;
142
+ try {
143
+ const payload = await verificationRecords().list({ appId: "nextclaw.portable-runtime-lab", limit: 500 });
144
+ const entry = (payload?.entries || []).find((candidate) => candidate.acceptanceId === "PRT-RES-001");
145
+ element.textContent = entry
146
+ ? `${entry.status === "passed" ? "已有局部证据" : entry.status} · ${new Date(entry.finishedAt).toLocaleString()}`
147
+ : "未验证(暂无 PRT-RES-001 记录)";
148
+ } catch {
149
+ element.textContent = "暂时无法读取验收记录";
150
+ }
151
+ }
152
+
153
+ void refreshVerificationStatus();