@nextclaw/x-harness 0.1.0 → 0.2.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 (113) hide show
  1. package/LICENSE +231 -6
  2. package/README.md +17 -3
  3. package/SOURCE.md +9 -0
  4. package/cli/main.js +232 -188
  5. package/dist/api.d.ts +56 -2
  6. package/dist/api.js +178 -75
  7. package/dist/boundary.js +1 -0
  8. package/dist/browser-common.d.ts +11 -0
  9. package/dist/browser-common.js +34 -18
  10. package/dist/browser-dom-capabilities.d.ts +9 -0
  11. package/dist/browser-dom-capabilities.js +131 -0
  12. package/dist/browser-dom-dependencies.d.ts +16 -0
  13. package/dist/browser-dom-dependencies.js +1 -0
  14. package/dist/browser-dom-editing.d.ts +166 -0
  15. package/dist/browser-dom-editing.js +418 -0
  16. package/dist/browser-dom-navigation.d.ts +18 -0
  17. package/dist/browser-dom-navigation.js +181 -0
  18. package/dist/browser-dom-network.d.ts +1 -0
  19. package/dist/browser-dom-network.js +128 -0
  20. package/dist/browser-dom-submission.d.ts +7 -0
  21. package/dist/browser-dom-submission.js +101 -0
  22. package/dist/browser-dom.d.ts +14 -2
  23. package/dist/browser-dom.js +120 -824
  24. package/dist/browser-native.d.ts +14 -1
  25. package/dist/browser-native.js +180 -143
  26. package/dist/browser.js +6 -2
  27. package/dist/creator-native.d.ts +3 -0
  28. package/dist/creator-native.js +113 -0
  29. package/dist/creator.js +1 -1
  30. package/dist/diagnostics.d.ts +11 -0
  31. package/dist/diagnostics.js +10 -0
  32. package/dist/growth-batch.d.ts +8 -0
  33. package/dist/growth-batch.js +190 -0
  34. package/dist/growth-constants.d.ts +5 -0
  35. package/dist/growth-constants.js +13 -0
  36. package/dist/growth-dependencies.d.ts +33 -0
  37. package/dist/growth-dependencies.js +1 -0
  38. package/dist/growth-execution.d.ts +17 -0
  39. package/dist/growth-execution.js +346 -0
  40. package/dist/growth-parallel.d.ts +8 -0
  41. package/dist/growth-parallel.js +332 -0
  42. package/dist/growth-plan.d.ts +9 -0
  43. package/dist/growth-plan.js +229 -0
  44. package/dist/growth-policy.d.ts +18 -0
  45. package/dist/growth-policy.js +208 -0
  46. package/dist/growth-publishing.d.ts +10 -0
  47. package/dist/growth-publishing.js +266 -0
  48. package/dist/growth-reading.d.ts +8 -0
  49. package/dist/growth-reading.js +157 -0
  50. package/dist/growth-recovery.d.ts +9 -0
  51. package/dist/growth-recovery.js +187 -0
  52. package/dist/growth-state.d.ts +16 -0
  53. package/dist/growth-state.js +220 -0
  54. package/dist/growth-tasks.d.ts +20 -0
  55. package/dist/growth-tasks.js +164 -0
  56. package/dist/growth.d.ts +26 -1
  57. package/dist/growth.js +211 -1908
  58. package/dist/platform-capabilities.d.ts +8 -0
  59. package/dist/platform-capabilities.js +29 -0
  60. package/dist/platform-collections.d.ts +14 -0
  61. package/dist/platform-collections.js +250 -0
  62. package/dist/platform-dependencies.d.ts +43 -0
  63. package/dist/platform-dependencies.js +1 -0
  64. package/dist/platform-diagnostics.d.ts +14 -0
  65. package/dist/platform-diagnostics.js +63 -0
  66. package/dist/platform-observation.d.ts +16 -0
  67. package/dist/platform-observation.js +96 -0
  68. package/dist/platform-rules.d.ts +17 -0
  69. package/dist/platform-rules.js +41 -0
  70. package/dist/platform-targets.d.ts +14 -0
  71. package/dist/platform-targets.js +154 -0
  72. package/dist/platform-writing.d.ts +16 -0
  73. package/dist/platform-writing.js +202 -0
  74. package/dist/platform.d.ts +23 -2
  75. package/dist/platform.js +125 -678
  76. package/dist/readers-actions.d.ts +2 -0
  77. package/dist/readers-actions.js +66 -0
  78. package/dist/readers-ax-host.d.ts +12 -0
  79. package/dist/readers-ax-host.js +134 -0
  80. package/dist/readers-ax-parser.d.ts +1 -0
  81. package/dist/readers-ax-parser.js +69 -0
  82. package/dist/readers-ax-program.d.ts +1 -0
  83. package/dist/readers-ax-program.js +5 -0
  84. package/dist/readers-ax-tree.d.ts +19 -0
  85. package/dist/readers-ax-tree.js +111 -0
  86. package/dist/readers-ax-views.d.ts +87 -0
  87. package/dist/readers-ax-views.js +247 -0
  88. package/dist/readers-cli.d.ts +6 -0
  89. package/dist/readers-cli.js +79 -0
  90. package/dist/readers-composer.d.ts +2 -0
  91. package/dist/readers-composer.js +131 -0
  92. package/dist/readers-navigation.d.ts +2 -0
  93. package/dist/readers-navigation.js +66 -0
  94. package/dist/readers-posts.d.ts +2 -0
  95. package/dist/readers-posts.js +141 -0
  96. package/dist/readers-profile.d.ts +3 -0
  97. package/dist/readers-profile.js +157 -0
  98. package/dist/readers.d.ts +9 -20
  99. package/dist/readers.js +9 -1045
  100. package/dist/results.js +1 -1
  101. package/dist/round-state.js +20 -3
  102. package/dist/store.d.ts +22 -4
  103. package/dist/store.js +173 -151
  104. package/dist/types.d.ts +10 -0
  105. package/dist/types.js +1 -0
  106. package/generated/observation.mjs +3 -2
  107. package/package.json +23 -9
  108. package/skill/SKILL.md +17 -36
  109. package/skill/references/api.md +7 -34
  110. package/skill/references/reads.md +23 -0
  111. package/skill/references/recovery.md +25 -0
  112. package/skill/references/setup.md +23 -0
  113. package/skill/references/writes.md +51 -0
package/dist/results.js CHANGED
@@ -21,7 +21,7 @@ export function diagnostic(value) {
21
21
  if (typeof raw.code !== "string")
22
22
  return undefined;
23
23
  return {
24
- ...pick(raw, ["operation", "category"]),
24
+ ...pick(raw, ["operation", "category", "next", "evidenceRef"]),
25
25
  code: stableReason(raw.code),
26
26
  };
27
27
  }
@@ -109,8 +109,8 @@ export function checkRound(state, { now = Date.now(), evidenceExists = existsSyn
109
109
  reply.unknown, "未提交欠量仍在;未知提交不能冻结新对象");
110
110
  if (exit.reason === "outside-window") {
111
111
  const shanghaiHour = (new Date(now).getUTCHours() + 8) % 24;
112
- require(shanghaiHour < 7 ||
113
- shanghaiHour >= 22, "仍在上海07至22点执行窗口,不能以时段截止结束");
112
+ require(shanghaiHour <
113
+ 7, "仍在上海07至24点评论执行窗口,不能以时段截止结束");
114
114
  }
115
115
  if (exit.reason === "user-stop")
116
116
  require(typeof exit.instructionEvidenceRef === "string" &&
@@ -141,7 +141,24 @@ export function runRoundCLI() {
141
141
  const file = process.argv.slice(2).find((a) => !a.startsWith("--"));
142
142
  if (!file)
143
143
  throw new Error("用法:node scripts/x-round-state.mjs <原台账> [--check-end]");
144
- const result = checkRound(readRound(readFileSync(file, "utf8")));
144
+ const state = readRound(readFileSync(file, "utf8"));
145
+ const executionId = process.argv
146
+ .find((a) => a.startsWith("--execution="))
147
+ ?.slice("--execution=".length);
148
+ let scoped = state;
149
+ if (executionId !== undefined) {
150
+ const execution = state.executions?.[executionId];
151
+ if (!execution?.scopeKinds?.length || !execution.evidenceRef)
152
+ throw new Error("原台账执行者或职责证据缺失");
153
+ scoped = {
154
+ ...state,
155
+ status: execution.status,
156
+ scopeKinds: execution.scopeKinds,
157
+ scopeEvidenceRef: execution.evidenceRef,
158
+ stop: execution.stop,
159
+ };
160
+ }
161
+ const result = checkRound(scoped);
145
162
  console.log(JSON.stringify(result));
146
163
  process.exitCode =
147
164
  result.valid && (!process.argv.includes("--check-end") || result.mayEnd)
package/dist/store.d.ts CHANGED
@@ -24,7 +24,25 @@ export interface RoundStore {
24
24
  }>;
25
25
  recordProof(proof: unknown): Promise<string>;
26
26
  }
27
- export declare function createFileRoundStore({ ledgerPath, proofDirectory, }: {
28
- ledgerPath: string;
29
- proofDirectory: string;
30
- }): RoundStore;
27
+ export declare class FileRoundStore implements RoundStore {
28
+ #private;
29
+ path: any;
30
+ evidenceExists: any;
31
+ processId: any;
32
+ constructor({ ledgerPath, proofDirectory, }: {
33
+ ledgerPath: string;
34
+ proofDirectory: string;
35
+ });
36
+ fingerprint(file: string): string;
37
+ ownerAlive(ownerPid: unknown): boolean;
38
+ transaction(fn: (snapshot: RoundSnapshot) => BoundaryRecord | undefined | Promise<BoundaryRecord | undefined>): Promise<RoundSnapshot>;
39
+ load(): Promise<RoundSnapshot>;
40
+ save(snapshot: RoundSnapshot, state: BoundaryRecord): Promise<RoundSnapshot>;
41
+ withLock<T>(fn: () => T | Promise<T>): Promise<T>;
42
+ recoverAbandonedLock(): Promise<{
43
+ recovered: boolean;
44
+ evidenceRef: string;
45
+ }>;
46
+ recordProof(proof: unknown): Promise<string>;
47
+ }
48
+ export declare function createFileRoundStore(...args: ConstructorParameters<typeof FileRoundStore>): RoundStore;
package/dist/store.js CHANGED
@@ -11,165 +11,187 @@ export class HarnessConflict extends Error {
11
11
  this.code = "CONFLICT";
12
12
  }
13
13
  }
14
- export function createFileRoundStore({ ledgerPath, proofDirectory, }) {
15
- const path = resolve(ledgerPath);
16
- const lockPath = join(dirname(path), ".x-harness.lock");
17
- const proofDir = resolve(proofDirectory);
18
- let held = false;
19
- return {
20
- path,
21
- evidenceExists: existsSync,
22
- fingerprint: (file) => hash(readFileSync(resolve(file))),
23
- processId: typeof process !== "undefined" ? process.pid : null,
24
- ownerAlive(ownerPid) {
25
- if (typeof ownerPid !== "number" ||
26
- !Number.isInteger(ownerPid) ||
27
- ownerPid < 1)
28
- return true;
29
- if (typeof process === "undefined" || typeof process.kill !== "function")
30
- return true;
31
- try {
32
- process.kill(ownerPid, 0);
33
- return true;
34
- }
35
- catch (error) {
36
- if (error.code === "ESRCH")
37
- return false;
38
- throw error;
39
- }
40
- },
41
- // Only bounded local state work belongs here. Browser I/O never holds this lock.
42
- async transaction(fn) {
43
- const start = Date.now();
44
- while (true) {
45
- let entered = false;
46
- try {
47
- return await this.withLock(async () => {
48
- entered = true;
49
- const snapshot = await this.load();
50
- const state = await fn(snapshot);
51
- return state ? this.save(snapshot, state) : snapshot;
52
- });
53
- }
54
- catch (error) {
55
- if (entered ||
56
- error.code !== "CONFLICT" ||
57
- Date.now() - start >= 2000)
58
- throw error;
59
- await new Promise((resolve) => setTimeout(resolve, 10));
60
- }
61
- }
62
- },
63
- async load() {
64
- const text = readFileSync(path, "utf8");
65
- return { state: readRound(text), revision: hash(text), path };
66
- },
67
- async save(snapshot, state) {
68
- if (!held)
69
- throw new HarnessConflict("保存原台账必须持有账号锁");
70
- const text = readFileSync(path, "utf8");
71
- if (snapshot.revision !== hash(text))
72
- throw new HarnessConflict("原台账已变化:重新定向读取,禁止覆盖");
73
- if (readRound(text).batchId !== state.batchId)
74
- throw new HarnessConflict("禁止替换原批次ID");
75
- const next = text.replace(block, () => "```x-round-state\n" + JSON.stringify(state, null, 2) + "\n```");
76
- const temp = `${path}.harness-${randomUUID()}.tmp`;
14
+ export class FileRoundStore {
15
+ #ledgerPath;
16
+ #proofDirectory;
17
+ #path;
18
+ #lockPath;
19
+ #proofDir;
20
+ #held;
21
+ path;
22
+ evidenceExists;
23
+ processId;
24
+ constructor({ ledgerPath, proofDirectory, }) {
25
+ this.recordProof = this.recordProof.bind(this);
26
+ this.recoverAbandonedLock = this.recoverAbandonedLock.bind(this);
27
+ this.withLock = this.withLock.bind(this);
28
+ this.save = this.save.bind(this);
29
+ this.load = this.load.bind(this);
30
+ this.transaction = this.transaction.bind(this);
31
+ this.ownerAlive = this.ownerAlive.bind(this);
32
+ this.fingerprint = this.fingerprint.bind(this);
33
+ this.#ledgerPath = ledgerPath;
34
+ this.#proofDirectory = proofDirectory;
35
+ this.#path = resolve(this.#ledgerPath);
36
+ this.#lockPath = join(dirname(this.#path), ".x-harness.lock");
37
+ this.#proofDir = resolve(this.#proofDirectory);
38
+ this.#held = false;
39
+ this.path = this.#path;
40
+ this.evidenceExists = existsSync;
41
+ this.processId = typeof process !== "undefined" ? process.pid : null;
42
+ }
43
+ fingerprint(file) {
44
+ return hash(readFileSync(resolve(file)));
45
+ }
46
+ ownerAlive(ownerPid) {
47
+ if (typeof ownerPid !== "number" ||
48
+ !Number.isInteger(ownerPid) ||
49
+ ownerPid < 1)
50
+ return true;
51
+ if (typeof process === "undefined" || typeof process.kill !== "function")
52
+ return true;
53
+ try {
54
+ process.kill(ownerPid, 0);
55
+ return true;
56
+ }
57
+ catch (error) {
58
+ if (error.code === "ESRCH")
59
+ return false;
60
+ throw error;
61
+ }
62
+ }
63
+ async transaction(fn) {
64
+ const start = Date.now();
65
+ while (true) {
66
+ let entered = false;
77
67
  try {
78
- writeFileSync(temp, next, {
79
- flag: "wx",
80
- mode: statSync(path).mode & 0o777,
68
+ return await this.withLock(async () => {
69
+ entered = true;
70
+ const snapshot = await this.load();
71
+ const state = await fn(snapshot);
72
+ return state ? this.save(snapshot, state) : snapshot;
81
73
  });
82
- // Other writers must honor the same lock; CAS additionally detects observed external edits.
83
- if (hash(readFileSync(path, "utf8")) !== snapshot.revision)
84
- throw new HarnessConflict("原台账在结算中变化");
85
- renameSync(temp, path);
86
- }
87
- finally {
88
- if (existsSync(temp))
89
- unlinkSync(temp);
90
- }
91
- return { state, revision: hash(next), path };
92
- },
93
- async withLock(fn) {
94
- if (held)
95
- throw new HarnessConflict("账号操作禁止重入");
96
- if (existsSync(`${lockPath}.recovery`))
97
- throw new HarnessConflict("原锁恢复核验进行中");
98
- let fd;
99
- try {
100
- fd = openSync(lockPath, "wx", 0o600);
101
74
  }
102
75
  catch (error) {
103
- if (error.code === "EEXIST")
104
- throw new HarnessConflict("账号锁占用:不并发写、不自动偷锁");
105
- throw error;
106
- }
107
- try {
108
- writeFileSync(fd, JSON.stringify({
109
- pid: typeof process !== "undefined" ? process.pid : null,
110
- acquiredAt: new Date().toISOString(),
111
- ledgerPath: path,
112
- }));
113
- held = true;
114
- return await fn();
115
- }
116
- finally {
117
- held = false;
118
- closeSync(fd);
119
- unlinkSync(lockPath);
76
+ if (entered ||
77
+ error.code !== "CONFLICT" ||
78
+ Date.now() - start >= 2000)
79
+ throw error;
80
+ await new Promise((resolve) => setTimeout(resolve, 10));
120
81
  }
121
- },
122
- async recoverAbandonedLock() {
123
- if (held)
124
- throw new HarnessConflict("不能恢复当前正在持有的锁");
125
- const recovery = `${lockPath}.recovery`;
126
- let fd;
82
+ }
83
+ }
84
+ async load() {
85
+ const text = readFileSync(this.#path, "utf8");
86
+ return { state: readRound(text), revision: hash(text), path: this.#path };
87
+ }
88
+ async save(snapshot, state) {
89
+ if (!this.#held)
90
+ throw new HarnessConflict("保存原台账必须持有账号锁");
91
+ const text = readFileSync(this.#path, "utf8");
92
+ if (snapshot.revision !== hash(text))
93
+ throw new HarnessConflict("原台账已变化:重新定向读取,禁止覆盖");
94
+ if (readRound(text).batchId !== state.batchId)
95
+ throw new HarnessConflict("禁止替换原批次ID");
96
+ const next = text.replace(block, () => "```x-round-state\n" + JSON.stringify(state, null, 2) + "\n```");
97
+ const temp = `${this.#path}.harness-${randomUUID()}.tmp`;
98
+ try {
99
+ writeFileSync(temp, next, {
100
+ flag: "wx",
101
+ mode: statSync(this.#path).mode & 0o777,
102
+ });
103
+ // Other writers must honor the same lock; CAS additionally detects observed external edits.
104
+ if (hash(readFileSync(this.#path, "utf8")) !== snapshot.revision)
105
+ throw new HarnessConflict("原台账在结算中变化");
106
+ renameSync(temp, this.#path);
107
+ }
108
+ finally {
109
+ if (existsSync(temp))
110
+ unlinkSync(temp);
111
+ }
112
+ return { state, revision: hash(next), path: this.#path };
113
+ }
114
+ async withLock(fn) {
115
+ if (this.#held)
116
+ throw new HarnessConflict("账号操作禁止重入");
117
+ if (existsSync(`${this.#lockPath}.recovery`))
118
+ throw new HarnessConflict("原锁恢复核验进行中");
119
+ let fd;
120
+ try {
121
+ fd = openSync(this.#lockPath, "wx", 0o600);
122
+ }
123
+ catch (error) {
124
+ if (error.code === "EEXIST")
125
+ throw new HarnessConflict("账号锁占用:不并发写、不自动偷锁");
126
+ throw error;
127
+ }
128
+ try {
129
+ writeFileSync(fd, JSON.stringify({
130
+ pid: typeof process !== "undefined" ? process.pid : null,
131
+ acquiredAt: new Date().toISOString(),
132
+ ledgerPath: this.#path,
133
+ }));
134
+ this.#held = true;
135
+ return await fn();
136
+ }
137
+ finally {
138
+ this.#held = false;
139
+ closeSync(fd);
140
+ unlinkSync(this.#lockPath);
141
+ }
142
+ }
143
+ async recoverAbandonedLock() {
144
+ if (this.#held)
145
+ throw new HarnessConflict("不能恢复当前正在持有的锁");
146
+ const recovery = `${this.#lockPath}.recovery`;
147
+ let fd;
148
+ try {
149
+ fd = openSync(recovery, "wx", 0o600);
150
+ }
151
+ catch (error) {
152
+ if (error.code === "EEXIST")
153
+ throw new HarnessConflict("另一执行器正在核验原锁");
154
+ throw error;
155
+ }
156
+ try {
157
+ const original = readFileSync(this.#lockPath, "utf8"), owner = JSON.parse(original);
158
+ if (!Number.isInteger(owner.pid) || owner.pid < 1)
159
+ throw new HarnessConflict("锁owner缺失,不能自动移除");
160
+ if (typeof process === "undefined" || typeof process.kill !== "function")
161
+ throw new HarnessConflict("当前宿主无进程核验能力,禁止恢复原锁");
127
162
  try {
128
- fd = openSync(recovery, "wx", 0o600);
163
+ process.kill(owner.pid, 0);
164
+ throw new HarnessConflict("原进程仍在:禁止抢占账号锁");
129
165
  }
130
166
  catch (error) {
131
- if (error.code === "EEXIST")
132
- throw new HarnessConflict("另一执行器正在核验原锁");
133
- throw error;
167
+ if (error.code !== "ESRCH")
168
+ throw error;
134
169
  }
135
- try {
136
- const original = readFileSync(lockPath, "utf8"), owner = JSON.parse(original);
137
- if (!Number.isInteger(owner.pid) || owner.pid < 1)
138
- throw new HarnessConflict("锁owner缺失,不能自动移除");
139
- if (typeof process === "undefined" ||
140
- typeof process.kill !== "function")
141
- throw new HarnessConflict("当前宿主无进程核验能力,禁止恢复原锁");
142
- try {
143
- process.kill(owner.pid, 0);
144
- throw new HarnessConflict("原进程仍在:禁止抢占账号锁");
145
- }
146
- catch (error) {
147
- if (error.code !== "ESRCH")
148
- throw error;
149
- }
150
- const proof = await this.recordProof({
151
- observedAt: new Date().toISOString(),
152
- recovery: "owner-process-confirmed-exited",
153
- owner,
154
- });
155
- if (readFileSync(lockPath, "utf8") !== original)
156
- throw new HarnessConflict("原锁已变化,禁止移除");
157
- unlinkSync(lockPath);
158
- return { recovered: true, evidenceRef: proof };
159
- }
160
- finally {
161
- closeSync(fd);
162
- unlinkSync(recovery);
163
- }
164
- },
165
- async recordProof(proof) {
166
- mkdirSync(proofDir, { recursive: true });
167
- const file = join(proofDir, `${randomUUID()}.json`);
168
- writeFileSync(file, JSON.stringify(proof, null, 2), {
169
- flag: "wx",
170
- mode: 0o600,
170
+ const proof = await this.recordProof({
171
+ observedAt: new Date().toISOString(),
172
+ recovery: "owner-process-confirmed-exited",
173
+ owner,
171
174
  });
172
- return file;
173
- },
174
- };
175
+ if (readFileSync(this.#lockPath, "utf8") !== original)
176
+ throw new HarnessConflict("原锁已变化,禁止移除");
177
+ unlinkSync(this.#lockPath);
178
+ return { recovered: true, evidenceRef: proof };
179
+ }
180
+ finally {
181
+ closeSync(fd);
182
+ unlinkSync(recovery);
183
+ }
184
+ }
185
+ async recordProof(proof) {
186
+ mkdirSync(this.#proofDir, { recursive: true });
187
+ const file = join(this.#proofDir, `${randomUUID()}.json`);
188
+ writeFileSync(file, JSON.stringify(proof, null, 2), {
189
+ flag: "wx",
190
+ mode: 0o600,
191
+ });
192
+ return file;
193
+ }
194
+ }
195
+ export function createFileRoundStore(...args) {
196
+ return new FileRoundStore(...args);
175
197
  }
package/dist/types.d.ts CHANGED
@@ -75,6 +75,8 @@ export interface Diagnostic {
75
75
  operation?: string;
76
76
  category?: string;
77
77
  code: string;
78
+ next?: "inspect-local-proof" | "restore-proof-store";
79
+ evidenceRef?: EvidenceRef;
78
80
  }
79
81
  export interface ReadResult {
80
82
  status: "observed" | "needsDecision" | "needsCapability";
@@ -196,6 +198,7 @@ export interface ActionResult {
196
198
  diagnostic?: Diagnostic;
197
199
  }
198
200
  export interface RoundResult {
201
+ diagnostic?: Diagnostic;
199
202
  status: string;
200
203
  taskId: string;
201
204
  mayEnd: boolean;
@@ -220,6 +223,7 @@ export interface RoundResult {
220
223
  };
221
224
  }
222
225
  export interface BatchResult {
226
+ diagnostic?: Diagnostic;
223
227
  status: string;
224
228
  taskId: string;
225
229
  batchId?: string;
@@ -265,6 +269,12 @@ export interface IndependentExecution {
265
269
  tabId: string;
266
270
  evidenceRef: EvidenceRef;
267
271
  scopeEvidenceRef: EvidenceRef;
272
+ windowAuthorization?: {
273
+ actionId: string;
274
+ userAuthorized: true;
275
+ expiresAt: string;
276
+ evidenceRef: EvidenceRef;
277
+ };
268
278
  }
269
279
  export interface PublishOptions {
270
280
  taskId: string;
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
- // Generated by packages/x-harness/scripts/generate-observation.mjs; edit TS reader owners only.
1
+ // Generated by scripts/generate-observation.mjs; edit TS reader owners only.
2
2
  export const readXObservation = (query) => {
3
3
  const readPosts = function readPosts({ offset = 0, limit = 5, targetId = "", } = {}) {
4
4
  if (!Number.isInteger(offset) ||
@@ -487,7 +487,8 @@ export const readXObservation = (query) => {
487
487
  const read = (node) => {
488
488
  if (node.nodeType === 3)
489
489
  return node.textContent ?? "";
490
- if (node.nodeType === 1 && node.tagName === "A") {
490
+ if (node.nodeType === 1 &&
491
+ node.tagName === "A") {
491
492
  const href = node.getAttribute("href") ?? "";
492
493
  if (/^\/\w{1,15}\/status\/\d+$/.test(href))
493
494
  return `https://x.com${href}`;
package/package.json CHANGED
@@ -1,32 +1,46 @@
1
1
  {
2
2
  "name": "@nextclaw/x-harness",
3
- "version": "0.1.0",
4
- "description": "X tasks for browser-enabled AI agents: compact results, complete evidence and verified writes.",
5
- "license": "SEE LICENSE IN LICENSE",
3
+ "version": "0.2.0",
6
4
  "type": "module",
5
+ "description": "Typed X task harness with compact business results and explicit diagnostics.",
7
6
  "main": "./dist/index.js",
8
7
  "types": "./dist/index.d.ts",
9
8
  "exports": {
10
9
  ".": {
11
10
  "types": "./dist/index.d.ts",
12
11
  "import": "./dist/index.js"
12
+ },
13
+ "./diagnostics": {
14
+ "types": "./dist/diagnostics.d.ts",
15
+ "import": "./dist/diagnostics.js"
13
16
  }
14
17
  },
15
- "bin": {
16
- "x-harness": "./cli/main.js"
17
- },
18
18
  "files": [
19
19
  "dist",
20
20
  "generated",
21
21
  "cli",
22
22
  "skill",
23
23
  "README.md",
24
- "LICENSE"
24
+ "LICENSE",
25
+ "SOURCE.md"
25
26
  ],
27
+ "license": "AGPL-3.0-only",
28
+ "homepage": "https://x-harness.gemigo.app/",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/Peiiii/x-harness.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/Peiiii/x-harness/issues"
35
+ },
26
36
  "engines": {
27
37
  "node": ">=22"
28
38
  },
39
+ "bin": {
40
+ "x-harness": "./cli/main.js"
41
+ },
29
42
  "publishConfig": {
30
43
  "access": "public"
31
- }
32
- }
44
+ },
45
+ "gitHead": "269a29c44c871bac51265653a4e559ffacbf2d84"
46
+ }
package/skill/SKILL.md CHANGED
@@ -1,52 +1,33 @@
1
1
  ---
2
2
  name: x-harness
3
- version: 0.1.0
4
- description: Use X through named task APIs with compact receipts, complete target evidence and verified writes. Requires a compatible CUA browser host and the user's own logged-in X account. Install and update with the npm-distributed X Harness runtime.
3
+ version: 0.2.0
4
+ description: Use X through task APIs with compact receipts, complete target evidence and verified writes; requires compatible CUA and the user's X login.
5
5
  ---
6
6
 
7
7
  # X Harness
8
8
 
9
- Use the installed runtime; normal tasks call its root business API. Do not recreate browser selectors, print full DOM/AX, or call lower layers for normal work. Keep observations local and only return the relevant complete evidence, receipt and actionable warnings.
9
+ Use the root business API. Load only the current task reference; reuse unchanged instructions and a valid binding. Normal use needs no implementation source or type-file reading.
10
10
 
11
- ## First use
11
+ ## Bind once
12
12
 
13
- Installed module: `{{MODULE_URL}}`
14
- CLI: `{{CLI_PATH}}`
13
+ Read current host CUA documentation; bind a real logged-in `tab` or native `app`, exactly one. Preserve other tasks' tabs/editors.
15
14
 
16
- If these placeholders are unresolved, install first:
17
- `npx @nextclaw/x-harness@latest setup --target codex` (or `--target claude`).
18
-
19
- Requires Node 22+, a host that exposes compatible CUA `tab` or native `app` methods, and the user's own X login. Codex desktop CUA is validated. Installing this Skill into Claude only installs instructions; confirm that host's browser methods before claiming it can execute. If browser access/login is absent, report the actual missing condition; never fabricate execution or borrow the publisher's account.
20
-
21
- Read the host's CUA documentation before using its controls. Bind/create a task tab through the host's documented API, preserving the user's session. A compatible tab has `url()`, `playwright.evaluate()`, locator methods and optional navigation/AX/screenshot methods; native apps have `getAXState()`, click/key/paste methods. Use the provided connection; do not launch an unrelated automation driver.
22
-
23
- Initialize the user's own workspace once with `node <CLI> init --directory <user workspace> --account <user handle>`. It returns `ledgerPath`, `proofDirectory`, `taskId` and account. Existing project configurations and ledgers remain the owner; reuse those instead of creating duplicate state. Never import the publisher's operations directories, policies or credentials.
24
-
25
- In the host's Node-capable REPL:
15
+ Installed module: `{{MODULE_URL}}`; CLI: `{{CLI_PATH}}`.
16
+ In this repository use the absolute file URL of `<harness-repository>/dist/index.js` with original ledger/proof paths. Unresolved placeholders do not require reinstalling an existing repository runtime. Only absent runtime/workspace or an update needs [setup](references/setup.md).
26
17
 
27
18
  ```js
28
- const { createXGrowthHarness } = await import("MODULE_URL_FROM_SETUP");
29
- const x = createXGrowthHarness({ tab, ledgerPath, proofDirectory });
30
- const result = await x.readCreator({
31
- handle: "USER_HANDLE",
32
- target: 5,
33
- mode: "decision",
34
- });
35
- // Print only the needed result; keep the full browser observation in the REPL.
19
+ var { createXGrowthHarness } = await import(moduleURL);
20
+ var x = createXGrowthHarness({ tab, ledgerPath, proofDirectory });
36
21
  ```
37
22
 
38
- Substitute the stable installed URL and initialized paths. Never assume an old imported module hot-reloads after updating. Read `references/api.md` only for the next operation's arguments and the installed `.d.ts` for detailed types.
39
-
40
- ## Execution
41
-
42
- Choose the single named task: readCreator/readBookmarks/searchSources/readPost/readPerson/readFeedback/inspectAccount; publishPost/replyToPost/followOne/followBatch; inspectRound/runGrowthRound; verifySubmission/verifyPublishedPost/verifyFollow. `receipt` is the default compact result; `decision` includes full relevant evidence; `delta` rereads and returns changed complete evidence plus unchanged IDs. Do not trim the target's body to save tokens.
43
-
44
- Writing needs explicit authorization from the current user, verified identity, their own ledger plan, reviewed prepared actions and existing evidence files. Initialization plans zero writes. Set the authorized planned count in the original ledger before execution; never expand an authorized scope yourself. Check the installed types for action, quality and executor fields; retain one owner for the ledger, do not invent approvals or evidence. The current runtime writes only 07:00–22:00 Asia/Shanghai and follow candidate gates require reviewed Chinese-language, blue-check candidates; disclose these conditions when relevant.
45
-
46
- Interpret returned status literally: `verified` requires public proof; `unknown` is not success. For an unknown submission, call verifySubmission on the same action before any new submission. `needsCapability`, `needsDecision`, `waiting` and partial windows must be reported, with the next required condition. Capture visual media before making claims about its contents.
23
+ ## Load by task
47
24
 
48
- ## Updates
25
+ | Current need | Read only |
26
+ | --- | --- |
27
+ | Creator/bookmarks/search/post/person/feedback/account | [reads](references/reads.md) |
28
+ | Publish/reply/follow or a round | [writes](references/writes.md), preamble + common review + matching operation heading |
29
+ | Unknown submission, missing capability or connection failure | [recovery](references/recovery.md) |
49
30
 
50
- Run `npx @nextclaw/x-harness@latest update --target codex` (or claude). It stages the exact fetched release, replaces only managed runtime/Skill directories, saves the previous version and retains user workspaces/configurations. It refuses unfamiliar directories. Start a fresh module binding afterward and verify the next real read operation. A release's distributed Skill and runtime use the same version.
31
+ Default `receipt` is for status; use `decision` on the first call needing content, `delta` for changed evidence with valid prior context. Keep observations in REPL/proof; return complete relevant evidence and actionable warnings. Never truncate target text or print full DOM/AX. Root calls own capability checks, intent, submission, verification and settlement; verified results need no extra preflight/reload/verify.
51
32
 
52
- Free use under the bundled proprietary license; the distributed JS is readable, the TS source repository remains private. Website: https://x-harness.gemigo.app/
33
+ Writes require user authorization, verified identity, reviewed evidence and the original ledger plan; project rules own policy. Unknown submission: verify the same action before any new submission. Follow recovery for other failures; read source only to repair a concrete defect/unsupported capability, verify, then return to root calls.