@wowok/agent-mcp 2.6.0 → 2.6.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.
Files changed (186) hide show
  1. package/README.md +5 -3
  2. package/dist/customer/info-puzzle.d.ts +1 -1
  3. package/dist/customer/info-puzzle.js +4 -2
  4. package/dist/customer/risk-assessment.js +26 -4
  5. package/dist/customer/types.d.ts +2 -0
  6. package/dist/examples/guard-template-balance-check.json +38 -0
  7. package/dist/examples/guard-template-time-lock.json +39 -0
  8. package/dist/examples/machine-template-7node-rental.json +114 -0
  9. package/dist/examples/rental-ziroom-machine-create.json +137 -0
  10. package/dist/examples/rental-ziroom-permission-create.json +35 -0
  11. package/dist/examples/rental-ziroom-service-create.json +80 -0
  12. package/dist/examples/retail-myshop-service-create.json +88 -0
  13. package/dist/extensions/capability-manifest.d.ts +125 -0
  14. package/dist/extensions/capability-manifest.js +594 -0
  15. package/dist/extensions/constraint-registry.d.ts +24 -0
  16. package/dist/extensions/constraint-registry.js +196 -0
  17. package/dist/extensions/index.d.ts +12 -0
  18. package/dist/extensions/index.js +6 -0
  19. package/dist/extensions/metric-registry.d.ts +26 -0
  20. package/dist/extensions/metric-registry.js +257 -0
  21. package/dist/extensions/mode-evaluator.d.ts +15 -0
  22. package/dist/extensions/mode-evaluator.js +170 -0
  23. package/dist/extensions/modes.d.ts +2 -0
  24. package/dist/extensions/modes.js +493 -0
  25. package/dist/extensions/registry.d.ts +50 -0
  26. package/dist/extensions/registry.js +662 -0
  27. package/dist/extensions/types.d.ts +219 -0
  28. package/dist/extensions/types.js +1 -0
  29. package/dist/index.js +50 -0
  30. package/dist/knowledge/deployment-scanner.d.ts +3 -0
  31. package/dist/knowledge/deployment-scanner.js +64 -3
  32. package/dist/knowledge/fund-layer.d.ts +72 -0
  33. package/dist/knowledge/fund-layer.js +420 -0
  34. package/dist/knowledge/guard-render.d.ts +57 -0
  35. package/dist/knowledge/guard-render.js +700 -0
  36. package/dist/knowledge/guard-risk.d.ts +13 -0
  37. package/dist/knowledge/guard-risk.js +57 -0
  38. package/dist/knowledge/guard-submission-prompt.d.ts +31 -0
  39. package/dist/knowledge/guard-submission-prompt.js +171 -0
  40. package/dist/knowledge/guard-templates.js +278 -0
  41. package/dist/knowledge/machine-ledger.js +1 -1
  42. package/dist/knowledge/machine-render.d.ts +41 -0
  43. package/dist/knowledge/machine-render.js +565 -0
  44. package/dist/knowledge/machine-templates.js +24 -5
  45. package/dist/knowledge/reward-confirm.js +2 -2
  46. package/dist/knowledge/reward-puzzle.js +1 -1
  47. package/dist/knowledge/reward-risk.js +9 -9
  48. package/dist/knowledge/reward-templates.js +2 -2
  49. package/dist/knowledge/service-confirm.d.ts +15 -6
  50. package/dist/knowledge/service-confirm.js +119 -11
  51. package/dist/knowledge/service-context.js +1 -1
  52. package/dist/knowledge/service-ledger.js +1 -1
  53. package/dist/knowledge/service-risk.d.ts +1 -1
  54. package/dist/knowledge/service-risk.js +3 -3
  55. package/dist/knowledge/service-templates.js +2 -2
  56. package/dist/knowledge/service-translation.d.ts +1 -1
  57. package/dist/knowledge/service-translation.js +7 -7
  58. package/dist/knowledge/tool-constraints.js +6 -2
  59. package/dist/project/deployment-bridge.d.ts +1 -1
  60. package/dist/project/deployment-bridge.js +31 -7
  61. package/dist/project/deployment-doc.d.ts +3 -0
  62. package/dist/project/deployment-doc.js +76 -14
  63. package/dist/project/edit-planner.d.ts +123 -0
  64. package/dist/project/edit-planner.js +1371 -0
  65. package/dist/project/evaluation.d.ts +2 -0
  66. package/dist/project/evaluation.js +873 -88
  67. package/dist/project/graph-builder.d.ts +4 -1
  68. package/dist/project/graph-builder.js +132 -62
  69. package/dist/project/graph.d.ts +1 -0
  70. package/dist/project/handlers.d.ts +317 -6
  71. package/dist/project/handlers.js +1171 -19
  72. package/dist/project/stage-gate.d.ts +4 -0
  73. package/dist/project/stage-gate.js +64 -5
  74. package/dist/project/task-tracker.d.ts +26 -0
  75. package/dist/project/task-tracker.js +78 -0
  76. package/dist/safety/preview.js +16 -0
  77. package/dist/schema/call/allocation.d.ts +16 -16
  78. package/dist/schema/call/allocation.js +2 -2
  79. package/dist/schema/call/arbitration.js +19 -6
  80. package/dist/schema/call/base.d.ts +21 -13
  81. package/dist/schema/call/base.js +27 -6
  82. package/dist/schema/call/bridge.d.ts +5 -5
  83. package/dist/schema/call/bridge.js +3 -1
  84. package/dist/schema/call/contact.js +2 -2
  85. package/dist/schema/call/demand.d.ts +23 -31
  86. package/dist/schema/call/demand.js +2 -2
  87. package/dist/schema/call/guard.js +2 -2
  88. package/dist/schema/call/machine.d.ts +1976 -752
  89. package/dist/schema/call/machine.js +50 -4
  90. package/dist/schema/call/order.d.ts +149 -228
  91. package/dist/schema/call/order.js +4 -3
  92. package/dist/schema/call/payment.d.ts +183 -3
  93. package/dist/schema/call/payment.js +21 -3
  94. package/dist/schema/call/permission.js +2 -2
  95. package/dist/schema/call/personal.d.ts +241 -52
  96. package/dist/schema/call/progress.d.ts +53 -61
  97. package/dist/schema/call/progress.js +18 -4
  98. package/dist/schema/call/repository.d.ts +23 -31
  99. package/dist/schema/call/repository.js +2 -2
  100. package/dist/schema/call/reward.js +3 -3
  101. package/dist/schema/call/semantic.d.ts +1 -1
  102. package/dist/schema/call/semantic.js +37 -2
  103. package/dist/schema/call/service.d.ts +275 -127
  104. package/dist/schema/call/service.js +89 -13
  105. package/dist/schema/call/treasury.js +3 -3
  106. package/dist/schema/common/index.d.ts +13 -2
  107. package/dist/schema/common/index.js +80 -15
  108. package/dist/schema/local/index.d.ts +32 -35
  109. package/dist/schema/local/index.js +28 -6
  110. package/dist/schema/messenger/index.d.ts +274 -46
  111. package/dist/schema/operations.d.ts +1264 -544
  112. package/dist/schema/operations.js +65 -7
  113. package/dist/schema/project/index.d.ts +2846 -167
  114. package/dist/schema/project/index.js +570 -16
  115. package/dist/schema/query/index.d.ts +922 -349
  116. package/dist/schema/query/index.js +205 -42
  117. package/dist/schema/schema-query/index.d.ts +65 -3
  118. package/dist/schema/schema-query/index.js +38 -5
  119. package/dist/schema/utils/node-parser.js +20 -4
  120. package/dist/schema/utils/object-type-utils.d.ts +12 -0
  121. package/dist/schema/utils/object-type-utils.js +35 -0
  122. package/dist/schema/utils/permission-machine-check.d.ts +49 -0
  123. package/dist/schema/utils/permission-machine-check.js +121 -0
  124. package/dist/schema/utils/skills-recommendation.d.ts +2 -0
  125. package/dist/schema/utils/skills-recommendation.js +76 -0
  126. package/dist/schema-query/index.d.ts +20 -1
  127. package/dist/schema-query/index.js +306 -4
  128. package/dist/schemas/account_operation.output.json +7 -1
  129. package/dist/schemas/account_operation.schema.json +3 -3
  130. package/dist/schemas/bridge_operation.output.json +6 -0
  131. package/dist/schemas/bridge_operation.schema.json +1 -1
  132. package/dist/schemas/guard-templates.json +379 -0
  133. package/dist/schemas/guard2file.schema.json +1 -1
  134. package/dist/schemas/index.json +1 -1
  135. package/dist/schemas/local_info_operation.output.json +6 -0
  136. package/dist/schemas/local_mark_operation.output.json +7 -1
  137. package/dist/schemas/local_mark_operation.schema.json +1 -1
  138. package/dist/schemas/machineNode2file.schema.json +1 -1
  139. package/dist/schemas/messenger_operation.schema.json +4 -4
  140. package/dist/schemas/onchain_events.output.json +1 -1
  141. package/dist/schemas/onchain_operations.output.json +2820 -0
  142. package/dist/schemas/onchain_operations.schema.json +444 -330
  143. package/dist/schemas/onchain_operations_allocation.schema.json +37 -28
  144. package/dist/schemas/onchain_operations_arbitration.schema.json +16 -14
  145. package/dist/schemas/onchain_operations_contact.schema.json +10 -10
  146. package/dist/schemas/onchain_operations_demand.schema.json +10 -10
  147. package/dist/schemas/onchain_operations_gen_passport.schema.json +16 -16
  148. package/dist/schemas/onchain_operations_gen_proof.schema.json +2 -2
  149. package/dist/schemas/onchain_operations_guard.schema.json +2 -2
  150. package/dist/schemas/onchain_operations_machine.schema.json +43 -35
  151. package/dist/schemas/onchain_operations_order.schema.json +72 -78
  152. package/dist/schemas/onchain_operations_payment.schema.json +141 -111
  153. package/dist/schemas/onchain_operations_permission.schema.json +3 -3
  154. package/dist/schemas/onchain_operations_personal.schema.json +7 -7
  155. package/dist/schemas/onchain_operations_progress.schema.json +9 -9
  156. package/dist/schemas/onchain_operations_proof.schema.json +8 -8
  157. package/dist/schemas/onchain_operations_repository.schema.json +10 -10
  158. package/dist/schemas/onchain_operations_reward.schema.json +14 -14
  159. package/dist/schemas/onchain_operations_service.schema.json +119 -48
  160. package/dist/schemas/onchain_operations_treasury.schema.json +11 -11
  161. package/dist/schemas/onchain_table_data.output.json +33 -25
  162. package/dist/schemas/onchain_table_data.schema.json +22 -21
  163. package/dist/schemas/project_operation.output.json +2194 -23
  164. package/dist/schemas/project_operation.schema.json +84 -6
  165. package/dist/schemas/query_toolkit.output.json +108 -80
  166. package/dist/schemas/query_toolkit.schema.json +12 -12
  167. package/dist/schemas/schema_query.output.json +70 -3
  168. package/dist/schemas/schema_query.schema.json +18 -3
  169. package/dist/schemas/wowok_buildin_info.output.json +81 -8
  170. package/dist/schemas/wowok_buildin_info.schema.json +46 -2
  171. package/dist/tools/handlers/local.js +20 -5
  172. package/dist/tools/handlers/onchain.js +594 -6
  173. package/dist/tools/handlers/project.js +76 -4
  174. package/dist/tools/handlers/query.js +27 -0
  175. package/dist/tools/handlers/schema-query.js +43 -1
  176. package/dist/tools/handlers/task-status.d.ts +170 -0
  177. package/dist/tools/handlers/task-status.js +55 -0
  178. package/dist/tools/handlers/wip.js +47 -1
  179. package/dist/tools/index.d.ts +8 -0
  180. package/dist/tools/index.js +387 -12
  181. package/dist/tools/retry.d.ts +8 -0
  182. package/dist/tools/retry.js +85 -0
  183. package/dist/tools/wip-deploy-assist.d.ts +28 -0
  184. package/dist/tools/wip-deploy-assist.js +278 -0
  185. package/package.json +2 -2
  186. package/dist/schemas/guard-node-examples.md +0 -199
@@ -1,7 +1,11 @@
1
- import { LocalProject, LocalMark, query_objects, } from "@wowok/wowok";
1
+ import { LocalProject, LocalMark, query_objects, query_table, } from "@wowok/wowok";
2
2
  import { GraphBuilder } from "./graph-builder.js";
3
3
  import { SemanticAssembler } from "./semantic-assembler.js";
4
4
  import { runEvaluation, compareProjects, } from "./evaluation.js";
5
+ import { TaskTracker } from "./task-tracker.js";
6
+ import { renderGuardMermaid, renderGuardTreeIndented, renderGuardAscii, renderGuardMarkmap, } from "../knowledge/guard-render.js";
7
+ import { renderMachineMermaid, renderMachineTreeIndented, renderMachineAscii, renderMachineMarkmap, } from "../knowledge/machine-render.js";
8
+ import { isObjectOfType } from "../schema/utils/object-type-utils.js";
5
9
  const localProject = LocalProject.Instance();
6
10
  function requireField(value, name) {
7
11
  if (value === undefined || value === null) {
@@ -21,8 +25,17 @@ async function resolveProjectId(v) {
21
25
  throw new Error("Either 'project_id' or 'project' (prefix) is required for this action");
22
26
  }
23
27
  async function resolveObjectAddress(v) {
24
- if (v.object_address)
25
- return v.object_address;
28
+ if (v.object_address) {
29
+ const addr = v.object_address;
30
+ const isHexAddress = /^0x[0-9a-fA-F]{32,64}$/.test(addr);
31
+ const isDraftSentinel = addr.startsWith("draft:");
32
+ if (!isHexAddress && !isDraftSentinel) {
33
+ throw new Error(`Invalid object_address format: '${addr.slice(0, 40)}${addr.length > 40 ? "..." : ""}'. ` +
34
+ `Expected a valid Sui address (0x + 32-64 hex chars) or 'draft:<name>' sentinel. ` +
35
+ `To reference an object by name, use 'object_name_add' instead of 'object_address'.`);
36
+ }
37
+ return addr;
38
+ }
26
39
  if (v.object_name_add) {
27
40
  const mark = await LocalMark.Instance().get(v.object_name_add);
28
41
  if (mark?.address)
@@ -31,6 +44,17 @@ async function resolveObjectAddress(v) {
31
44
  }
32
45
  throw new Error("Either 'object_address' or 'object_name_add' is required for add_object");
33
46
  }
47
+ function isDraftObject(obj) {
48
+ if (obj.objectAddress && obj.objectAddress.startsWith("draft:"))
49
+ return true;
50
+ const meta = obj.metadata;
51
+ return !!(meta && meta.status === "draft");
52
+ }
53
+ function isOnchainObject(obj) {
54
+ if (isDraftObject(obj))
55
+ return false;
56
+ return !!obj.objectAddress && obj.objectAddress.startsWith("0x");
57
+ }
34
58
  export async function handleListProjects(v) {
35
59
  const filter = {};
36
60
  if (v.filter_status)
@@ -50,12 +74,13 @@ export async function handleListProjects(v) {
50
74
  if (o.objectType)
51
75
  types.add(o.objectType);
52
76
  }
77
+ const onchainCount = objs.filter(o => isOnchainObject(o)).length;
53
78
  if (p.status === "draft" && objs.length > 0) {
54
79
  const updated = await localProject.updateProject(p.id, { status: "active" });
55
80
  if (updated) {
56
81
  return {
57
82
  ...updated,
58
- objectCount: objs.length,
83
+ objectCount: onchainCount,
59
84
  evaluationCount: evals.length,
60
85
  objectTypes: Array.from(types),
61
86
  };
@@ -63,7 +88,7 @@ export async function handleListProjects(v) {
63
88
  }
64
89
  return {
65
90
  ...p,
66
- objectCount: objs.length,
91
+ objectCount: onchainCount,
67
92
  evaluationCount: evals.length,
68
93
  objectTypes: Array.from(types),
69
94
  };
@@ -75,12 +100,30 @@ export async function handleListProjects(v) {
75
100
  };
76
101
  }
77
102
  export async function handleCreateProject(v) {
78
- const prefix = requireField(v.project, "project (prefix)");
79
103
  const name = requireField(v.project_name, "project_name");
104
+ let prefix = v.project;
105
+ if (!prefix) {
106
+ prefix = name
107
+ .toLowerCase()
108
+ .trim()
109
+ .replace(/[^a-z0-9]+/g, "_")
110
+ .replace(/^_+|_+$/g, "")
111
+ .substring(0, 60);
112
+ if (!prefix) {
113
+ prefix = `proj_${Date.now().toString(36)}`;
114
+ }
115
+ }
80
116
  const existing = await localProject.getProjectByPrefix(prefix);
81
117
  if (existing) {
82
- throw new Error(`Project prefix '${prefix}' already exists (project: '${existing.name}', id: '${existing.id}'). ` +
83
- `Use a different prefix or update the existing project.`);
118
+ if (v.project) {
119
+ throw new Error(`Project prefix '${prefix}' already exists (project: '${existing.name}', id: '${existing.id}'). ` +
120
+ `Use a different prefix or update the existing project.`);
121
+ }
122
+ let suffix = 2;
123
+ while (await localProject.getProjectByPrefix(`${prefix}_${suffix}`)) {
124
+ suffix++;
125
+ }
126
+ prefix = `${prefix}_${suffix}`;
84
127
  }
85
128
  const create = {
86
129
  prefix,
@@ -93,7 +136,148 @@ export async function handleCreateProject(v) {
93
136
  network: v.project_network ?? "testnet",
94
137
  };
95
138
  const project = await localProject.createProject(create);
96
- return { action: "create_project", project };
139
+ if (v.service_address) {
140
+ try {
141
+ await localProject.addObject(project.id, {
142
+ objectAddress: v.service_address,
143
+ objectType: "Service",
144
+ objectName: v.service_address.startsWith("0x")
145
+ ? undefined
146
+ : v.service_address,
147
+ role: "core",
148
+ metadata: {
149
+ auto_added_from_service_address: true,
150
+ added_at: new Date().toISOString(),
151
+ },
152
+ });
153
+ }
154
+ catch (err) {
155
+ console.warn(`[create_project] Failed to auto-add Service '${v.service_address}': ${err.message}. ` +
156
+ `Use add_object action to manually add it.`);
157
+ }
158
+ }
159
+ const industry = v.project_industry;
160
+ const skipTemplates = v.skip_templates === true;
161
+ if (industry && industry !== "general" && !skipTemplates) {
162
+ try {
163
+ const { IndustryModeRegistry } = await import("../extensions/index.js");
164
+ const registry = IndustryModeRegistry.Instance();
165
+ const context = {
166
+ prefix,
167
+ version: project.currentVersion ?? "v1",
168
+ network: (project.network ?? "testnet"),
169
+ business_intent: v.project_description ?? name,
170
+ industry,
171
+ };
172
+ const blueprint = registry.applyTemplate(industry, context.business_intent, context);
173
+ const draftObjects = [];
174
+ for (const obj of blueprint.objects) {
175
+ const projectObj = await localProject.addObject(project.id, {
176
+ objectAddress: `draft:${obj.name}`,
177
+ objectType: obj.object_type,
178
+ objectName: obj.name,
179
+ role: obj.object_type === "service" ? "core" : "dependency",
180
+ metadata: {
181
+ status: "draft",
182
+ config: obj.config,
183
+ publish_required: obj.publish_required,
184
+ description: obj.description,
185
+ entry_id: obj.entry_id,
186
+ },
187
+ });
188
+ draftObjects.push(projectObj);
189
+ }
190
+ const graphEdges = blueprint.edges.map((e) => ({
191
+ projectId: project.id,
192
+ sourceObject: e.from,
193
+ targetObject: e.to,
194
+ edgeSource: "internal",
195
+ relationshipType: mapEdgeKindToRelationshipType(e.edge_kind),
196
+ fieldName: e.field_path,
197
+ depth: 0,
198
+ verified: false,
199
+ weight: 1.0,
200
+ metadata: {
201
+ blueprint: true,
202
+ required_at_publish: e.required_at_publish,
203
+ },
204
+ }));
205
+ await localProject.saveGraphEdges(project.id, graphEdges);
206
+ return {
207
+ action: "create_project",
208
+ project,
209
+ blueprint: {
210
+ objects_count: draftObjects.length,
211
+ edges_count: graphEdges.length,
212
+ industry_source: blueprint.industry_source,
213
+ applied_template: blueprint.applied_template,
214
+ steps_count: blueprint.steps.length,
215
+ scenarios_count: blueprint.scenarios?.length ?? 0,
216
+ key_risk: registry.getMode(industry)?.key_risk,
217
+ },
218
+ recommended_creation_order: getRecommendedCreationOrder(industry),
219
+ };
220
+ }
221
+ catch (err) {
222
+ return {
223
+ action: "create_project",
224
+ project,
225
+ blueprint_warning: `Industry template application failed: ${err.message}. Project created without blueprint — use add_object to manually add objects.`,
226
+ recommended_creation_order: getRecommendedCreationOrder(industry),
227
+ };
228
+ }
229
+ }
230
+ if (skipTemplates) {
231
+ return {
232
+ action: "create_project",
233
+ project,
234
+ blueprint_skipped: true,
235
+ hint: "skip_templates=true — no industry blueprint objects were created. Use add_object to manually add objects to this project.",
236
+ recommended_creation_order: getRecommendedCreationOrder(industry),
237
+ };
238
+ }
239
+ return {
240
+ action: "create_project",
241
+ project,
242
+ recommended_creation_order: getRecommendedCreationOrder(industry),
243
+ };
244
+ }
245
+ function getRecommendedCreationOrder(industry) {
246
+ const industryNote = industry && industry !== "general"
247
+ ? ` (industry="${industry}" — see IndustryMode.build_notes for overrides)`
248
+ : "";
249
+ return [
250
+ { step: 1, object: "Permission", reason: "Base permission object, referenced by all other objects.", required: true },
251
+ { step: 2, object: "Treasury", reason: "Standalone object, no dependencies. Optional but recommended for fund management.", required: false },
252
+ { step: 3, object: "Machine", reason: `Requires Permission. publish:true locks nodes/pairs/forwards permanently.${industryNote}`, required: true },
253
+ { step: 4, object: "Contact", reason: "Requires Permission. Used as Service.um (user manual / contact info).", required: true },
254
+ { step: 5, object: "Arbitration", reason: "Requires Permission. Optional but recommended for dispute resolution.", required: false },
255
+ { step: 6, object: "Service (unpublished)", reason: "Requires Permission + Machine + Contact + Arbitration. Create WITHOUT publish:true first.", required: true },
256
+ { step: 7, object: "Guard x N", reason: "Can reference Service via LocalMark. Create all Guards (buy_guard, allocator_guard, etc.) before publishing Service.", required: true },
257
+ { step: 8, object: "Service update + publish", reason: "Add order_allocators referencing Guards, then publish:true. After publish, machine/order_allocators are immutable.", required: true },
258
+ ];
259
+ }
260
+ function mapEdgeKindToRelationshipType(edgeKind) {
261
+ switch (edgeKind) {
262
+ case "machine":
263
+ case "compensation_fund":
264
+ case "parent":
265
+ return "bind";
266
+ case "order_allocators":
267
+ return "create";
268
+ case "arbitrations":
269
+ case "rewards":
270
+ return "trigger";
271
+ case "buy_guard":
272
+ case "permission":
273
+ case "guard":
274
+ return "validate";
275
+ case "service":
276
+ case "progress":
277
+ return "consume";
278
+ default:
279
+ return "bind";
280
+ }
97
281
  }
98
282
  export async function handleCreateProjectFromOnchain(v) {
99
283
  const serviceAddress = requireField(v.service_address, "service_address");
@@ -112,6 +296,35 @@ export async function handleCreateProjectFromOnchain(v) {
112
296
  }
113
297
  prefix = `${prefix}_${suffix}`;
114
298
  }
299
+ const otherNetwork = network === "testnet" ? "mainnet" : "testnet";
300
+ let rootType = null;
301
+ try {
302
+ const rootQuery = await query_objects({
303
+ objects: [serviceAddress],
304
+ network,
305
+ });
306
+ const rootObj = rootQuery?.objects?.[0];
307
+ if (rootObj && rootObj.object) {
308
+ rootType = rootObj.type ?? null;
309
+ }
310
+ }
311
+ catch (e) {
312
+ throw new Error(`Failed to query ${network} for the Service object: ` +
313
+ `${e instanceof Error ? e.message : String(e)}. ` +
314
+ `Check the network connection or try the ${otherNetwork} network.`);
315
+ }
316
+ if (!rootType) {
317
+ throw new Error(`Service object not found on ${network}: the address ${serviceAddress} does not exist on this network. ` +
318
+ `Switch to the ${otherNetwork} network and try again.`);
319
+ }
320
+ const normalizedType = rootType.toLowerCase();
321
+ const isService = normalizedType.includes("::service::service") ||
322
+ normalizedType === "service" ||
323
+ normalizedType.endsWith("::service");
324
+ if (!isService) {
325
+ throw new Error(`Object ${serviceAddress} is not a Service (its on-chain type is "${rootType}"). ` +
326
+ `Only on-chain Service objects can be imported as a project root.`);
327
+ }
115
328
  const project = await localProject.createProject({
116
329
  prefix,
117
330
  name,
@@ -123,7 +336,7 @@ export async function handleCreateProjectFromOnchain(v) {
123
336
  network: network,
124
337
  });
125
338
  const builder = new GraphBuilder({
126
- maxDepth: v.max_depth ?? 3,
339
+ maxDepth: v.max_depth ?? v.depth ?? 3,
127
340
  enableRegistrar: v.enable_registrar ?? true,
128
341
  enablePotential: v.enable_potential ?? false,
129
342
  projectId: project.id,
@@ -229,11 +442,75 @@ export async function handleGetProjectDetail(v) {
229
442
  }
230
443
  }
231
444
  }
445
+ let serviceStatus = null;
446
+ if (effectiveProject.serviceAddress && effectiveProject.network) {
447
+ try {
448
+ const svcQuery = await query_objects({
449
+ objects: [effectiveProject.serviceAddress],
450
+ network: effectiveProject.network,
451
+ });
452
+ const svcData = svcQuery?.objects?.[0];
453
+ if (svcData) {
454
+ const pub = svcData.bPublished ?? svcData.published ?? svcData.b_publish;
455
+ const pau = svcData.bPaused ?? svcData.paused ?? svcData.b_pause;
456
+ serviceStatus = {
457
+ published: typeof pub === "boolean" ? pub : null,
458
+ paused: typeof pau === "boolean" ? pau : null,
459
+ };
460
+ }
461
+ }
462
+ catch {
463
+ }
464
+ }
465
+ const VALID_ROLES = new Set(["core", "dependency", "reference"]);
466
+ const draftObjs = objects.filter(o => isDraftObject(o));
467
+ const onchainObjs = objects.filter(o => isOnchainObject(o));
468
+ const core = objects.filter(o => o.role === "core");
469
+ const dependency = objects.filter(o => o.role === "dependency");
470
+ const reference = objects.filter(o => o.role === "reference");
471
+ const other = objects.filter(o => !o.role || !VALID_ROLES.has(o.role));
472
+ if (other.length > 0) {
473
+ console.warn(`[handleGetProjectDetail] ${other.length} object(s) have invalid or missing role ` +
474
+ `(expected one of: ${Array.from(VALID_ROLES).join(", ")}). ` +
475
+ `They are grouped into 'other' for visibility. ` +
476
+ `Object names: ${other.map(o => o.objectName ?? o.objectAddress).slice(0, 5).join(", ")}${other.length > 5 ? "..." : ""}`);
477
+ }
478
+ const objectGroups = {
479
+ core,
480
+ dependency,
481
+ reference,
482
+ other,
483
+ draft: draftObjs,
484
+ stats: {
485
+ total: objects.length,
486
+ onchain_count: onchainObjs.length,
487
+ draft_count: draftObjs.length,
488
+ core_count: core.length,
489
+ dependency_count: dependency.length,
490
+ reference_count: reference.length,
491
+ other_count: other.length,
492
+ by_type: objects.reduce((acc, o) => {
493
+ const t = o.objectType ?? "unknown";
494
+ const prefix = t.includes("::") ? t.split("::")[0].toLowerCase() : t.toLowerCase();
495
+ acc[prefix] = (acc[prefix] ?? 0) + 1;
496
+ return acc;
497
+ }, {}),
498
+ },
499
+ };
500
+ const objectsWithDraftFlag = objects.map(o => ({
501
+ ...o,
502
+ isDraft: isDraftObject(o),
503
+ }));
232
504
  return {
233
505
  action: "get_project_detail",
234
506
  project: effectiveProject,
235
- objects,
507
+ objects: objectsWithDraftFlag,
508
+ objectCount: objects.length,
509
+ onchainCount: onchainObjs.length,
510
+ draftCount: draftObjs.length,
511
+ objectGroups,
236
512
  graphEdgeCount: edges.length,
513
+ serviceStatus,
237
514
  evaluations: evaluations.map(e => ({
238
515
  id: e.id,
239
516
  evalType: e.evalType,
@@ -249,9 +526,31 @@ export async function handleGetProjectDetail(v) {
249
526
  })),
250
527
  };
251
528
  }
529
+ const SHORT_TO_FULL_TYPE = {
530
+ "Service": "0x2::service::Service",
531
+ "Machine": "0x2::machine::Machine",
532
+ "Guard": "0x2::guard::Guard",
533
+ "Permission": "0x2::permission::Permission",
534
+ "Reward": "0x2::reward::Reward",
535
+ "Arbitration": "0x2::arbitration::Arbitration",
536
+ "Progress": "0x2::progress::Progress",
537
+ "Treasury": "0x2::treasury::Treasury",
538
+ "Demand": "0x2::demand::Demand",
539
+ "Repository": "0x2::repository::Repository",
540
+ "Contact": "0x2::contact::Contact",
541
+ "Order": "0x2::order::Order",
542
+ "Allocation": "0x2::allocation::Allocation",
543
+ "Personal": "0x2::personal::Personal",
544
+ "Payment": "0x2::payment::Payment",
545
+ "Proof": "0x2::proof::Proof",
546
+ };
252
547
  export async function handleAddObject(v) {
253
548
  const projectId = await resolveProjectId(v);
254
549
  const objectAddress = await resolveObjectAddress(v);
550
+ const rawType = v.object_type_add;
551
+ const objectType = rawType
552
+ ? (rawType.includes("::") ? rawType : (SHORT_TO_FULL_TYPE[rawType] ?? rawType))
553
+ : undefined;
255
554
  let objectName = v.object_name_add;
256
555
  let role = v.object_role;
257
556
  if (!objectName) {
@@ -261,7 +560,7 @@ export async function handleAddObject(v) {
261
560
  }
262
561
  }
263
562
  if (!role) {
264
- const objType = v.object_type_add ?? "";
563
+ const objType = objectType ?? "";
265
564
  if (objType.includes("service::Service")) {
266
565
  role = "core";
267
566
  }
@@ -284,7 +583,7 @@ export async function handleAddObject(v) {
284
583
  }
285
584
  const obj = await localProject.addObject(projectId, {
286
585
  objectAddress,
287
- objectType: v.object_type_add,
586
+ objectType,
288
587
  objectName,
289
588
  role,
290
589
  tags: v.object_tags_add,
@@ -335,21 +634,44 @@ export async function handleBuildGraph(v) {
335
634
  const project = await localProject.getProject(projectId);
336
635
  if (!project)
337
636
  throw new Error(`Project not found: ${projectId}`);
637
+ if (v.async_mode === true) {
638
+ const taskId = TaskTracker.create("build_graph", projectId);
639
+ (async () => {
640
+ try {
641
+ TaskTracker.update(taskId, 10, "Resolving root address");
642
+ const result = await buildGraphSync(projectId, project, v);
643
+ TaskTracker.complete(taskId, result);
644
+ }
645
+ catch (err) {
646
+ TaskTracker.fail(taskId, err.message ?? String(err));
647
+ }
648
+ })();
649
+ return {
650
+ action: "build_graph",
651
+ async_mode: true,
652
+ task_id: taskId,
653
+ hint: "Graph is building in the background. Call query_task_status with task_id to check progress and retrieve the result.",
654
+ };
655
+ }
656
+ return buildGraphSync(projectId, project, v);
657
+ }
658
+ async function buildGraphSync(projectId, project, v) {
338
659
  let rootAddress = project.serviceAddress;
339
660
  if (!rootAddress) {
340
661
  const objects = await localProject.listObjects(projectId);
341
662
  if (objects.length === 0) {
342
663
  throw new Error("Cannot build graph: project has no objects and no service_address");
343
664
  }
344
- const coreObj = objects.find(o => o.role === "core");
665
+ const coreObj = objects.find(o => o.role === "core")
666
+ ?? objects.find(o => o.objectType?.includes("service::Service") || o.objectType === "Service");
345
667
  rootAddress = (coreObj ?? objects[0]).objectAddress;
346
668
  }
347
669
  const builder = new GraphBuilder({
348
- maxDepth: v.max_depth ?? 3,
670
+ maxDepth: v.max_depth ?? v.depth ?? 3,
349
671
  enableRegistrar: v.enable_registrar ?? true,
350
672
  enablePotential: v.enable_potential ?? false,
351
673
  projectId,
352
- network: project.network,
674
+ network: project.network ?? undefined,
353
675
  });
354
676
  const result = await builder.buildRelationshipGraph(rootAddress);
355
677
  const edgesBySource = {};
@@ -357,6 +679,19 @@ export async function handleBuildGraph(v) {
357
679
  const src = edge.edgeSource ?? "unknown";
358
680
  edgesBySource[src] = (edgesBySource[src] ?? 0) + 1;
359
681
  }
682
+ const topEdges = result.graph.edges
683
+ .slice()
684
+ .sort((a, b) => (b.weight ?? 0) - (a.weight ?? 0))
685
+ .slice(0, 5)
686
+ .map(e => ({
687
+ source: e.sourceObject,
688
+ target: e.targetObject,
689
+ fieldName: e.fieldName,
690
+ relationshipType: e.relationshipType,
691
+ weight: e.weight,
692
+ verified: e.verified,
693
+ }));
694
+ const visualization = topEdges.map(e => ` ${shortLabel(e.source)} --${e.fieldName ?? e.relationshipType ?? ""}--> ${shortLabel(e.target)}`).join("\n");
360
695
  return {
361
696
  action: "build_graph",
362
697
  nodeCount: result.graph.nodes.length,
@@ -364,8 +699,108 @@ export async function handleBuildGraph(v) {
364
699
  edgesBySource,
365
700
  reputationCount: result.reputation.size,
366
701
  builtAt: result.graph.builtAt,
702
+ topEdges,
703
+ visualization: visualization || " (no edges to display)",
704
+ hint: `Showing top ${topEdges.length} critical edges. Call project_operation with action='get_graph' to list all ${result.graph.edges.length} edges.`,
705
+ binding_verification: await verifyServiceBindings(project.serviceAddress, project.network),
367
706
  };
368
707
  }
708
+ async function verifyServiceBindings(serviceAddress, network) {
709
+ if (!serviceAddress)
710
+ return null;
711
+ try {
712
+ const response = await query_objects({
713
+ objects: [serviceAddress],
714
+ network: network,
715
+ });
716
+ const svc = response?.objects?.[0];
717
+ if (!svc)
718
+ return null;
719
+ const allocCount = svc.order_allocators?.allocators?.length ?? 0;
720
+ const arbCount = Array.isArray(svc.arbitrations) ? svc.arbitrations.length : 0;
721
+ const bindings = {
722
+ "Service.machine": {
723
+ expected: "Machine",
724
+ actual: svc.machine ?? null,
725
+ verified: !!svc.machine,
726
+ },
727
+ "Service.permission": {
728
+ expected: "Permission",
729
+ actual: svc.permission ?? null,
730
+ verified: !!svc.permission,
731
+ },
732
+ "Service.buy_guard": {
733
+ expected: "Guard",
734
+ actual: svc.buy_guard ?? null,
735
+ verified: !!svc.buy_guard,
736
+ },
737
+ "Service.order_allocators": {
738
+ expected: "Guard[]",
739
+ actual: null,
740
+ count: allocCount,
741
+ verified: allocCount > 0,
742
+ },
743
+ "Service.arbitrations": {
744
+ expected: "Arbitration[]",
745
+ actual: null,
746
+ count: arbCount,
747
+ verified: arbCount > 0,
748
+ },
749
+ "Service.um": {
750
+ expected: "Contact",
751
+ actual: svc.um ?? null,
752
+ verified: !!svc.um,
753
+ },
754
+ };
755
+ const all_verified = Object.values(bindings).every((b) => b.verified);
756
+ return { service: serviceAddress, bindings, all_verified };
757
+ }
758
+ catch {
759
+ return null;
760
+ }
761
+ }
762
+ function synthesizeOnchainFallbackEdges(serviceAddress, bindings) {
763
+ const edges = [];
764
+ const now = Date.now();
765
+ const fieldMap = [
766
+ { key: "Service.machine", fieldName: "machine", relationshipType: "machine" },
767
+ { key: "Service.permission", fieldName: "permission", relationshipType: "permission" },
768
+ { key: "Service.buy_guard", fieldName: "buy_guard", relationshipType: "guard" },
769
+ { key: "Service.um", fieldName: "um", relationshipType: "um" },
770
+ { key: "Service.order_allocators", fieldName: "order_allocators", relationshipType: "guard" },
771
+ { key: "Service.arbitrations", fieldName: "arbitrations", relationshipType: "arbitration" },
772
+ ];
773
+ for (const fm of fieldMap) {
774
+ const b = bindings.bindings[fm.key];
775
+ if (!b || !b.verified)
776
+ continue;
777
+ const target = b.actual ?? `${serviceAddress}#${fm.fieldName}#synthetic`;
778
+ edges.push({
779
+ id: `onchain-fallback-${fm.fieldName}-${now}`,
780
+ projectId: null,
781
+ sourceObject: serviceAddress,
782
+ targetObject: target,
783
+ edgeSource: "internal",
784
+ relationshipType: fm.relationshipType,
785
+ fieldName: fm.fieldName,
786
+ depth: 1,
787
+ verified: true,
788
+ weight: 1,
789
+ direction: "outbound",
790
+ metadata: { onchain_fallback: true },
791
+ cachedAt: now,
792
+ });
793
+ }
794
+ return edges;
795
+ }
796
+ function shortLabel(addrOrName) {
797
+ if (!addrOrName)
798
+ return "(empty)";
799
+ if (!addrOrName.startsWith("0x")) {
800
+ return addrOrName.length <= 24 ? addrOrName : addrOrName.slice(0, 21) + "...";
801
+ }
802
+ return addrOrName.length <= 12 ? addrOrName : addrOrName.slice(0, 10) + "..";
803
+ }
369
804
  export async function handleGetGraph(v) {
370
805
  const projectId = await resolveProjectId(v);
371
806
  const edges = await localProject.getGraph(projectId);
@@ -390,11 +825,26 @@ export async function handleEvaluateProject(v) {
390
825
  const project = await localProject.getProject(projectId);
391
826
  if (!project)
392
827
  throw new Error(`Project not found: ${projectId}`);
393
- if (v.refresh_before_eval) {
394
- await refreshProjectObjects(projectId, project.network);
828
+ let refreshWarning;
829
+ if (v.refresh_before_eval !== false) {
830
+ const refreshResult = await refreshProjectObjects(projectId, project.network);
831
+ if (refreshResult.failedCount > 0) {
832
+ refreshWarning = `Refresh completed with ${refreshResult.failedCount} failed object(s) ` +
833
+ `out of ${refreshResult.refreshedCount + refreshResult.failedCount} total. ` +
834
+ `Evaluation may use stale data for failed objects.`;
835
+ }
395
836
  }
396
837
  const objects = await localProject.listObjects(projectId);
397
- const edges = await localProject.getGraph(projectId);
838
+ let edges = await localProject.getGraph(projectId);
839
+ let graphFallbackWarning;
840
+ if (edges.length === 0 && project.serviceAddress) {
841
+ const bindings = await verifyServiceBindings(project.serviceAddress, project.network);
842
+ if (bindings && Object.values(bindings.bindings).some(b => b.verified)) {
843
+ edges = synthesizeOnchainFallbackEdges(project.serviceAddress, bindings);
844
+ const verifiedCount = Object.values(bindings.bindings).filter(b => b.verified).length;
845
+ graphFallbackWarning = `Graph is empty; fell back to on-chain direct query (${verifiedCount}/${Object.keys(bindings.bindings).length} Service bindings verified). Call build_graph to construct the full relationship graph.`;
846
+ }
847
+ }
398
848
  const addresses = objects.map(o => o.objectAddress);
399
849
  const cacheEntries = await localProject.getCachedObjects(addresses);
400
850
  const objectCache = new Map();
@@ -447,6 +897,8 @@ export async function handleEvaluateProject(v) {
447
897
  needsLlm: result.needsLlm,
448
898
  llmPrompt: result.llmPrompt,
449
899
  evaluationId,
900
+ refreshWarning,
901
+ graphFallbackWarning,
450
902
  };
451
903
  }
452
904
  export async function handleSaveEvaluation(v) {
@@ -547,6 +999,175 @@ export async function handleDeleteProject(v) {
547
999
  await localProject.deleteProject(id);
548
1000
  return { action: "delete_project", deleted: true };
549
1001
  }
1002
+ export async function handleCloneProjectToNetwork(v) {
1003
+ const sourceProjectId = await resolveProjectId(v);
1004
+ const sourceProject = await localProject.getProject(sourceProjectId);
1005
+ if (!sourceProject) {
1006
+ throw new Error(`Source project not found: ${sourceProjectId}`);
1007
+ }
1008
+ const targetNetwork = requireField(v.target_network, "target_network");
1009
+ if (!["testnet", "mainnet"].includes(targetNetwork)) {
1010
+ throw new Error("target_network must be 'testnet' or 'mainnet'");
1011
+ }
1012
+ const actionName = (v.action === "migrate_network" ? "migrate_network" : "clone_project_to_network");
1013
+ const newPrefix = `${sourceProject.prefix}_${targetNetwork}`;
1014
+ const existing = await localProject.getProjectByPrefix(newPrefix);
1015
+ if (existing) {
1016
+ throw new Error(`Target project prefix '${newPrefix}' already exists (project: '${existing.name}', id: '${existing.id}'). ` +
1017
+ `Use update_project on the existing target or delete it first.`);
1018
+ }
1019
+ const newProject = await localProject.createProject({
1020
+ prefix: newPrefix,
1021
+ name: `${sourceProject.name} (${targetNetwork})`,
1022
+ description: sourceProject.description ?? undefined,
1023
+ industry: sourceProject.industry ?? undefined,
1024
+ perspective: sourceProject.perspective ?? "producer",
1025
+ labels: sourceProject.labels ?? undefined,
1026
+ network: targetNetwork,
1027
+ });
1028
+ const sourceObjects = await localProject.listObjects(sourceProjectId);
1029
+ let clonedCount = 0;
1030
+ for (const obj of sourceObjects) {
1031
+ await localProject.addObject(newProject.id, {
1032
+ objectAddress: `draft:${obj.objectName ?? "unnamed"}`,
1033
+ objectType: obj.objectType !== null ? obj.objectType : undefined,
1034
+ objectName: obj.objectName !== null ? obj.objectName : undefined,
1035
+ role: obj.role ?? "dependency",
1036
+ metadata: {
1037
+ status: "draft",
1038
+ cloned_from: sourceProjectId,
1039
+ cloned_at: Date.now(),
1040
+ },
1041
+ });
1042
+ clonedCount++;
1043
+ }
1044
+ return {
1045
+ action: actionName,
1046
+ new_project_id: newProject.id,
1047
+ new_prefix: newPrefix,
1048
+ target_network: targetNetwork,
1049
+ cloned_objects_count: clonedCount,
1050
+ message: `Project '${sourceProject.prefix}' cloned to ${targetNetwork} as '${newPrefix}'. ${clonedCount} object(s) copied as drafts — object addresses are empty pending on-chain deployment.`,
1051
+ next_steps: `Execute the same onchain_operations sequence with env.network='${targetNetwork}' to deploy objects. Then call update_project to set service_address, and build_graph to construct the new project's relationship graph.`,
1052
+ };
1053
+ }
1054
+ export async function handleMigrateToMainnet(v) {
1055
+ const sourceProjectId = await resolveProjectId(v);
1056
+ const sourceProject = await localProject.getProject(sourceProjectId);
1057
+ if (!sourceProject) {
1058
+ throw new Error(`Source project not found: ${sourceProjectId}`);
1059
+ }
1060
+ if (sourceProject.network === "mainnet") {
1061
+ throw new Error(`Source project '${sourceProject.prefix}' is already on mainnet. ` +
1062
+ `migrate_to_mainnet is for testnet→mainnet migration only. ` +
1063
+ `To clone to testnet, use clone_project_to_network with target_network='testnet'.`);
1064
+ }
1065
+ const cloneInput = { ...v, target_network: "mainnet" };
1066
+ const cloneResult = await handleCloneProjectToNetwork(cloneInput);
1067
+ const sourceObjects = await localProject.listObjects(sourceProjectId);
1068
+ const deploymentPlan = sourceObjects
1069
+ .filter(obj => obj.objectType !== null)
1070
+ .map((obj, idx) => ({
1071
+ order: idx + 1,
1072
+ object_type: obj.objectType,
1073
+ object_name: obj.objectName ?? "<unnamed>",
1074
+ operation_type: mapObjectTypeToOperationType(obj.objectType),
1075
+ localmark_name: obj.objectName ?? `<unnamed_${idx + 1}>`,
1076
+ note: `Deploy on mainnet with env.network='mainnet'. Use the SAME LocalMark name '${obj.objectName ?? "<unnamed>"}' — it will resolve to a DIFFERENT mainnet address. The testnet address is NOT reused.`,
1077
+ }));
1078
+ return {
1079
+ action: "migrate_to_mainnet",
1080
+ migration_status: "step_1_complete_steps_2_5_pending_user_action",
1081
+ source_project: {
1082
+ prefix: sourceProject.prefix,
1083
+ network: sourceProject.network,
1084
+ preserved: true,
1085
+ },
1086
+ target_project: {
1087
+ prefix: cloneResult.new_prefix,
1088
+ project_id: cloneResult.new_project_id,
1089
+ network: "mainnet",
1090
+ cloned_objects_count: cloneResult.cloned_objects_count,
1091
+ },
1092
+ key_principle: "testnet and mainnet are DIFFERENT projects. Same LocalMark names may exist in both networks, " +
1093
+ "but they correspond to DIFFERENT on-chain addresses. The testnet project is preserved unchanged. " +
1094
+ "LocalMark names are NETWORK-SCOPED: a name resolves to a testnet address when env.network=testnet, " +
1095
+ "and to a mainnet address when env.network=mainnet. Register the SAME name TWICE (once per network).",
1096
+ migration_plan: [
1097
+ {
1098
+ step: 1,
1099
+ status: "completed",
1100
+ action: "clone_project_to_network",
1101
+ description: "Clone source project metadata to mainnet project",
1102
+ result: `Created mainnet project '${cloneResult.new_prefix}' with ${cloneResult.cloned_objects_count} draft object(s).`,
1103
+ },
1104
+ {
1105
+ step: 2,
1106
+ status: "pending_user_action",
1107
+ action: "onchain_operations",
1108
+ description: "Re-deploy all objects on mainnet. Set env.network='mainnet' and env.project='" +
1109
+ cloneResult.new_prefix + "'. Use the SAME LocalMark names — they will resolve to " +
1110
+ "DIFFERENT mainnet addresses. Mainnet accounts need real (non-test) SUI for gas.",
1111
+ deployment_plan: deploymentPlan,
1112
+ critical_note: "Re-deploy in dependency order: Permission → Machine → Contact → Guard(s) → Service. " +
1113
+ "For the Service↔Guard circular dependency, use the 3-step pattern " +
1114
+ "(Service without order_allocators → Guards → Service with order_allocators + publish).",
1115
+ },
1116
+ {
1117
+ step: 3,
1118
+ status: "pending_user_action",
1119
+ action: "local_mark_operation",
1120
+ description: "Re-register LocalMark names on mainnet. Each name from step 2 must be registered " +
1121
+ "with its NEW mainnet address. The testnet LocalMark entries are NOT migrated " +
1122
+ "automatically — mainnet has its own name registry.",
1123
+ names_to_register: sourceObjects
1124
+ .filter(obj => obj.objectName !== null)
1125
+ .map(obj => obj.objectName),
1126
+ },
1127
+ {
1128
+ step: 4,
1129
+ status: "pending_user_action",
1130
+ action: "update_project",
1131
+ description: "Set service_address on the mainnet project to the mainnet Service address " +
1132
+ "(from step 2). Use project='" + cloneResult.new_prefix + "' and the mainnet " +
1133
+ "Service LocalMark name or 0x address.",
1134
+ },
1135
+ {
1136
+ step: 5,
1137
+ status: "pending_user_action",
1138
+ action: "build_graph",
1139
+ description: "Rebuild the relationship graph on the mainnet project. Use project='" +
1140
+ cloneResult.new_prefix + "'. This constructs edges from the newly-deployed " +
1141
+ "mainnet object bindings (machine, order_allocators, guards, etc.).",
1142
+ },
1143
+ ],
1144
+ summary: "Migration step 1 (clone) complete. Steps 2-5 require user action: deploy objects on mainnet, " +
1145
+ "re-register LocalMark names, set service_address, and rebuild the graph. The testnet project " +
1146
+ "is preserved unchanged — you can run both in parallel until ready to deprecate the testnet version.",
1147
+ };
1148
+ }
1149
+ function mapObjectTypeToOperationType(objectType) {
1150
+ const map = {
1151
+ service: "service",
1152
+ machine: "machine",
1153
+ guard: "guard",
1154
+ permission: "permission",
1155
+ progress: "progress",
1156
+ arb: "arbitration",
1157
+ arbitration: "arbitration",
1158
+ contact: "contact",
1159
+ treasury: "treasury",
1160
+ reward: "reward",
1161
+ allocation: "allocation",
1162
+ repository: "repository",
1163
+ demand: "demand",
1164
+ order: "order",
1165
+ personal: "personal",
1166
+ payment: "payment",
1167
+ proof: "proof",
1168
+ };
1169
+ return map[objectType.toLowerCase()] ?? objectType.toLowerCase();
1170
+ }
550
1171
  export async function handleGetObjectDiff(v) {
551
1172
  const address = requireField(v.diff_address, "diff_address");
552
1173
  const fromTime = requireField(v.diff_from_time, "diff_from_time");
@@ -565,3 +1186,534 @@ export async function handleGetObjectDiff(v) {
565
1186
  })),
566
1187
  };
567
1188
  }
1189
+ function normalizeForwards(raw) {
1190
+ if (!Array.isArray(raw))
1191
+ return [];
1192
+ return raw.map((f) => {
1193
+ const guardObj = f?.guard;
1194
+ const isGuardObj = guardObj && typeof guardObj === "object" && typeof guardObj.guard === "string";
1195
+ const guardAddress = isGuardObj
1196
+ ? guardObj.guard
1197
+ : typeof guardObj === "string" ? guardObj : null;
1198
+ const retainedSubmission = isGuardObj && Array.isArray(guardObj.retained_submission)
1199
+ ? guardObj.retained_submission
1200
+ : null;
1201
+ return {
1202
+ name: typeof f?.name === "string" ? f.name : "",
1203
+ weight: typeof f?.weight === "number" ? f.weight : 0,
1204
+ guard: guardAddress,
1205
+ retained_submission: retainedSubmission,
1206
+ named_operator: typeof f?.namedOperator === "string" ? f.namedOperator : null,
1207
+ permission_index: typeof f?.permissionIndex === "number" ? f.permissionIndex : null,
1208
+ };
1209
+ });
1210
+ }
1211
+ export async function handleGetMachineGraph(v) {
1212
+ const projectId = await resolveProjectId(v);
1213
+ const machineAddress = await resolveObjectAddress(v);
1214
+ const project = await localProject.getProject(projectId);
1215
+ if (!project)
1216
+ throw new Error(`Project not found (id: ${projectId})`);
1217
+ const objects = await localProject.listObjects(projectId);
1218
+ const machineObj = objects.find(o => o.objectAddress === machineAddress);
1219
+ if (!machineObj) {
1220
+ throw new Error(`Object ${machineAddress} is not part of project '${project.prefix}'. ` +
1221
+ `Add it via add_object first.`);
1222
+ }
1223
+ const network = (project.network ?? "testnet");
1224
+ let machineData = null;
1225
+ try {
1226
+ const queryResult = await query_objects({
1227
+ objects: [machineAddress],
1228
+ network,
1229
+ });
1230
+ machineData = queryResult?.objects?.[0] ?? null;
1231
+ }
1232
+ catch (e) {
1233
+ throw new Error(`Failed to query Machine on ${network}: ${e instanceof Error ? e.message : String(e)}`);
1234
+ }
1235
+ if (!machineData) {
1236
+ throw new Error(`Machine ${machineAddress} not found on ${network}. ` +
1237
+ `It may not exist on this network — try switching networks.`);
1238
+ }
1239
+ let machineName = null;
1240
+ const marks = await LocalMark.Instance().list({ address: machineAddress });
1241
+ if (marks.length > 0 && marks[0].name) {
1242
+ machineName = marks[0].name;
1243
+ }
1244
+ const published = typeof machineData.bPublished === "boolean" ? machineData.bPublished : null;
1245
+ const paused = typeof machineData.bPaused === "boolean" ? machineData.bPaused : null;
1246
+ const nodeMap = new Map();
1247
+ const edgeList = [];
1248
+ let rawNodes = [];
1249
+ try {
1250
+ const allFieldObjectIds = [];
1251
+ let cursor = undefined;
1252
+ for (;;) {
1253
+ const tableResult = await query_table({
1254
+ parent: machineAddress,
1255
+ cursor: cursor ?? undefined,
1256
+ network,
1257
+ });
1258
+ if (!tableResult?.items || tableResult.items.length === 0)
1259
+ break;
1260
+ for (const item of tableResult.items) {
1261
+ if (item.object)
1262
+ allFieldObjectIds.push(item.object);
1263
+ }
1264
+ if (!tableResult.hasNextPage || !tableResult.nextCursor)
1265
+ break;
1266
+ cursor = tableResult.nextCursor;
1267
+ }
1268
+ if (allFieldObjectIds.length > 0) {
1269
+ const BATCH = 50;
1270
+ for (let i = 0; i < allFieldObjectIds.length; i += BATCH) {
1271
+ const batch = allFieldObjectIds.slice(i, i + BATCH);
1272
+ const nodeQueryResult = await query_objects({
1273
+ objects: batch,
1274
+ network,
1275
+ });
1276
+ for (const obj of nodeQueryResult?.objects ?? []) {
1277
+ if (obj.type === "TableItem_MachineNode") {
1278
+ rawNodes.push(obj);
1279
+ }
1280
+ }
1281
+ }
1282
+ }
1283
+ }
1284
+ catch (e) {
1285
+ throw new Error(`Failed to query Machine nodes on ${network}: ${e instanceof Error ? e.message : String(e)}`);
1286
+ }
1287
+ for (const nodeObj of rawNodes) {
1288
+ const nodeName = nodeObj.name;
1289
+ if (!nodeName)
1290
+ continue;
1291
+ if (!nodeMap.has(nodeName)) {
1292
+ nodeMap.set(nodeName, { name: nodeName, description: null });
1293
+ }
1294
+ const pairs = Array.isArray(nodeObj.value) ? nodeObj.value : [];
1295
+ for (const pair of pairs) {
1296
+ if (!pair || typeof pair !== "object")
1297
+ continue;
1298
+ const from = typeof pair.prev_node === "string" ? pair.prev_node : "";
1299
+ if (!from)
1300
+ continue;
1301
+ const to = nodeName;
1302
+ const threshold = typeof pair.threshold === "number" ? pair.threshold : 1;
1303
+ const forwards = normalizeForwards(pair.forwards);
1304
+ edgeList.push({ from, to, threshold, forwards });
1305
+ }
1306
+ }
1307
+ for (const edge of edgeList) {
1308
+ if (!nodeMap.has(edge.from)) {
1309
+ nodeMap.set(edge.from, { name: edge.from, description: null });
1310
+ }
1311
+ if (!nodeMap.has(edge.to)) {
1312
+ nodeMap.set(edge.to, { name: edge.to, description: null });
1313
+ }
1314
+ }
1315
+ return {
1316
+ action: "get_machine_graph",
1317
+ machineAddress,
1318
+ machineName,
1319
+ nodes: Array.from(nodeMap.values()),
1320
+ edges: edgeList,
1321
+ published,
1322
+ paused,
1323
+ mermaid: renderMachineMermaid(Array.from(nodeMap.values()), edgeList),
1324
+ tree_outline: renderMachineTreeIndented({
1325
+ nodes: Array.from(nodeMap.values()),
1326
+ edges: edgeList,
1327
+ machineName,
1328
+ machineAddress,
1329
+ published,
1330
+ paused,
1331
+ }),
1332
+ ascii_art: renderMachineAscii({
1333
+ nodes: Array.from(nodeMap.values()),
1334
+ edges: edgeList,
1335
+ machineName,
1336
+ machineAddress,
1337
+ published,
1338
+ paused,
1339
+ }),
1340
+ markmap: renderMachineMarkmap({
1341
+ nodes: Array.from(nodeMap.values()),
1342
+ edges: edgeList,
1343
+ machineName,
1344
+ machineAddress,
1345
+ published,
1346
+ paused,
1347
+ }),
1348
+ };
1349
+ }
1350
+ export async function handleGetGuardGraph(v) {
1351
+ const projectId = await resolveProjectId(v);
1352
+ const guardAddress = await resolveObjectAddress(v);
1353
+ const project = await localProject.getProject(projectId);
1354
+ if (!project)
1355
+ throw new Error(`Project not found (id: ${projectId})`);
1356
+ const objects = await localProject.listObjects(projectId);
1357
+ const guardObj = objects.find(o => o.objectAddress === guardAddress);
1358
+ if (!guardObj) {
1359
+ throw new Error(`Object ${guardAddress} is not part of project '${project.prefix}'. ` +
1360
+ `Add it via add_object first.`);
1361
+ }
1362
+ const network = (project.network ?? "testnet");
1363
+ let guardData = null;
1364
+ try {
1365
+ const queryResult = await query_objects({
1366
+ objects: [guardAddress],
1367
+ network,
1368
+ });
1369
+ guardData = queryResult?.objects?.[0] ?? null;
1370
+ }
1371
+ catch (e) {
1372
+ throw new Error(`Failed to query Guard on ${network}: ${e instanceof Error ? e.message : String(e)}`);
1373
+ }
1374
+ if (!guardData) {
1375
+ throw new Error(`Guard ${guardAddress} not found on ${network}. ` +
1376
+ `It may not exist on this network — try switching networks.`);
1377
+ }
1378
+ let guardName = null;
1379
+ try {
1380
+ const mark = await LocalMark.Instance().get(guardAddress);
1381
+ guardName = mark?.name ?? null;
1382
+ }
1383
+ catch {
1384
+ }
1385
+ const root = guardData.root ?? guardData.data?.root ?? null;
1386
+ const table = guardData.table ?? guardData.data?.table ?? [];
1387
+ const rely = guardData.rely ?? guardData.data?.rely ?? null;
1388
+ const immutable = guardData.immutable ?? guardData.data?.immutable ?? null;
1389
+ if (!root) {
1390
+ throw new Error(`Guard ${guardAddress} has no root node. The Guard may be empty or corrupted. ` +
1391
+ `Use query_toolkit to inspect the raw object data.`);
1392
+ }
1393
+ const mermaidStr = renderGuardMermaid(root);
1394
+ const tableForRender = Array.isArray(table) ? table.map((t) => ({
1395
+ identifier: t.identifier,
1396
+ name: t.name,
1397
+ value_type: t.value_type,
1398
+ b_submission: t.b_submission,
1399
+ value: t.value,
1400
+ })) : [];
1401
+ const guardRenderInput = {
1402
+ root: root,
1403
+ table: tableForRender,
1404
+ guardName,
1405
+ guardAddress,
1406
+ rely: rely ?? null,
1407
+ };
1408
+ const treeOutline = renderGuardTreeIndented(guardRenderInput);
1409
+ const asciiArt = renderGuardAscii(guardRenderInput);
1410
+ const markmap = renderGuardMarkmap(guardRenderInput);
1411
+ return {
1412
+ action: "get_guard_graph",
1413
+ guardAddress,
1414
+ guardName,
1415
+ root,
1416
+ table: Array.isArray(table) ? table : [],
1417
+ rely: rely ?? null,
1418
+ immutable: immutable ?? null,
1419
+ mermaid: mermaidStr,
1420
+ tree_outline: treeOutline,
1421
+ ascii_art: asciiArt,
1422
+ markmap: markmap,
1423
+ };
1424
+ }
1425
+ export async function handlePreEvaluateCheck(v) {
1426
+ const projectId = await resolveProjectId(v);
1427
+ const project = await localProject.getProject(projectId);
1428
+ if (!project)
1429
+ throw new Error(`Project not found: ${projectId}`);
1430
+ const missing = [];
1431
+ const warnings = [];
1432
+ const objects = await localProject.listObjects(projectId);
1433
+ if (objects.length === 0) {
1434
+ missing.push({
1435
+ id: "PE-01",
1436
+ description: "Project has no objects (project_objects table is empty).",
1437
+ impact: "Evaluator will return a neutral score with no findings. " +
1438
+ "Call add_object or create_project_from_onchain first.",
1439
+ });
1440
+ return {
1441
+ action: "pre_evaluate_check",
1442
+ ready: false,
1443
+ missing,
1444
+ warnings,
1445
+ summary: "Project is empty — add objects before evaluating.",
1446
+ };
1447
+ }
1448
+ const edges = await localProject.getGraph(projectId);
1449
+ if (edges.length === 0) {
1450
+ warnings.push({
1451
+ id: "PE-02",
1452
+ description: "Project has no graph edges (graph_edges table is empty).",
1453
+ impact: "Evaluator's binding-based checks (machine, permission, " +
1454
+ "buy_guard, order_allocators) will return 0. Call build_graph " +
1455
+ "before evaluate_project to populate edges.",
1456
+ });
1457
+ }
1458
+ const addresses = objects.map(o => o.objectAddress);
1459
+ const cacheEntries = await localProject.getCachedObjects(addresses);
1460
+ const uncached = [];
1461
+ let staleCount = 0;
1462
+ const STALE_THRESHOLD_MS = 24 * 60 * 60 * 1000;
1463
+ const now = Date.now();
1464
+ for (const o of objects) {
1465
+ const entry = cacheEntries.get(o.objectAddress);
1466
+ if (!entry) {
1467
+ uncached.push(o.objectAddress);
1468
+ }
1469
+ else {
1470
+ const fetchedAt = entry?.fetchedAt;
1471
+ if (typeof fetchedAt === "number" && (now - fetchedAt) > STALE_THRESHOLD_MS) {
1472
+ staleCount++;
1473
+ }
1474
+ }
1475
+ }
1476
+ if (uncached.length > 0) {
1477
+ warnings.push({
1478
+ id: "PE-03",
1479
+ description: `${uncached.length}/${objects.length} project objects have no cached on-chain data.`,
1480
+ impact: "Evaluator will see empty object data for these objects. " +
1481
+ "Call refresh_objects before evaluate_project to fetch fresh data.",
1482
+ });
1483
+ }
1484
+ if (staleCount > 0) {
1485
+ warnings.push({
1486
+ id: "PE-04",
1487
+ description: `${staleCount}/${objects.length} project objects have cache older than 24 hours.`,
1488
+ impact: "Evaluator results may be based on stale state. " +
1489
+ "Call refresh_objects to fetch fresh data.",
1490
+ });
1491
+ }
1492
+ const knownAddresses = new Set(objects.map(o => o.objectAddress));
1493
+ const knownNames = new Set(objects.map(o => o.objectName).filter(Boolean));
1494
+ const danglingEdges = edges.filter((e) => !knownAddresses.has(e.sourceObject) && !knownNames.has(e.sourceObject)
1495
+ && !knownAddresses.has(e.targetObject) && !knownNames.has(e.targetObject));
1496
+ if (danglingEdges.length > 0) {
1497
+ warnings.push({
1498
+ id: "PE-05",
1499
+ description: `${danglingEdges.length} graph edges reference objects not in project_objects.`,
1500
+ impact: "Evaluator's binding index may include stale/dangling entries. " +
1501
+ "Call build_graph again to refresh edges, or remove orphan objects.",
1502
+ });
1503
+ }
1504
+ if (project.perspective === "producer") {
1505
+ const hasService = objects.some(o => isObjectOfType(o.objectType, "service"));
1506
+ if (!hasService) {
1507
+ missing.push({
1508
+ id: "PE-06",
1509
+ description: "Producer project has no Service-type object.",
1510
+ impact: "Evaluator's service-centric checks (publish state, " +
1511
+ "machine binding, order_allocators, etc.) will all return 0. " +
1512
+ "Add a Service object or import one via create_project_from_onchain.",
1513
+ });
1514
+ }
1515
+ }
1516
+ const draftCount = objects.filter((o) => {
1517
+ const meta = (o.metadata ?? {});
1518
+ return meta.status === "draft" || o.objectAddress.startsWith("draft:");
1519
+ }).length;
1520
+ if (draftCount === objects.length && objects.length > 0) {
1521
+ warnings.push({
1522
+ id: "PE-07",
1523
+ description: `All ${objects.length} objects are draft (not yet on-chain).`,
1524
+ impact: "Evaluator will assess the blueprint as if it were on-chain. " +
1525
+ "Real on-chain state may differ once deployed.",
1526
+ });
1527
+ }
1528
+ const ready = missing.length === 0;
1529
+ const summary = ready
1530
+ ? (warnings.length === 0
1531
+ ? `Ready to evaluate. ${objects.length} objects, ${edges.length} edges.`
1532
+ : `Ready to evaluate with ${warnings.length} warning(s). ${objects.length} objects, ${edges.length} edges.`)
1533
+ : `Not ready: ${missing.length} blocking issue(s) found.`;
1534
+ return {
1535
+ action: "pre_evaluate_check",
1536
+ ready,
1537
+ missing,
1538
+ warnings,
1539
+ summary,
1540
+ counts: {
1541
+ objects: objects.length,
1542
+ edges: edges.length,
1543
+ uncached: uncached.length,
1544
+ stale: staleCount,
1545
+ dangling_edges: danglingEdges.length,
1546
+ drafts: draftCount,
1547
+ },
1548
+ };
1549
+ }
1550
+ export async function handleVerifyDeployment(v) {
1551
+ const projectId = await resolveProjectId(v);
1552
+ const project = await localProject.getProject(projectId);
1553
+ if (!project)
1554
+ throw new Error(`Project not found: ${projectId}`);
1555
+ const objects = await localProject.listObjects(projectId);
1556
+ if (objects.length === 0) {
1557
+ return {
1558
+ action: "verify_deployment",
1559
+ verifiedCount: 0,
1560
+ matched: 0,
1561
+ mismatched: 0,
1562
+ missing: 0,
1563
+ unreachable: 0,
1564
+ results: [],
1565
+ summary: "Project has no objects to verify.",
1566
+ };
1567
+ }
1568
+ const network = (project.network ?? "testnet");
1569
+ const deployedObjects = objects.filter((o) => !o.objectAddress.startsWith("draft:") && o.objectAddress.startsWith("0x"));
1570
+ if (deployedObjects.length === 0) {
1571
+ return {
1572
+ action: "verify_deployment",
1573
+ verifiedCount: 0,
1574
+ matched: 0,
1575
+ mismatched: 0,
1576
+ missing: 0,
1577
+ unreachable: 0,
1578
+ results: [],
1579
+ summary: `All ${objects.length} objects are draft or non-address — nothing to verify on-chain.`,
1580
+ };
1581
+ }
1582
+ const batchSize = Math.min(deployedObjects.length, 50);
1583
+ const addressesToQuery = deployedObjects.slice(0, batchSize).map(o => o.objectAddress);
1584
+ let chainResult;
1585
+ try {
1586
+ chainResult = await query_objects({
1587
+ objects: addressesToQuery,
1588
+ network,
1589
+ });
1590
+ }
1591
+ catch (e) {
1592
+ return {
1593
+ action: "verify_deployment",
1594
+ verifiedCount: 0,
1595
+ matched: 0,
1596
+ mismatched: 0,
1597
+ missing: 0,
1598
+ unreachable: deployedObjects.length,
1599
+ results: deployedObjects.map(o => ({
1600
+ objectAddress: o.objectAddress,
1601
+ objectName: o.objectName,
1602
+ objectType: o.objectType,
1603
+ status: "unreachable",
1604
+ error: e instanceof Error ? e.message : String(e),
1605
+ })),
1606
+ summary: `Failed to query on-chain state: ${e instanceof Error ? e.message : String(e)}`,
1607
+ };
1608
+ }
1609
+ const chainObjects = chainResult?.objects ?? [];
1610
+ const chainByAddress = new Map();
1611
+ for (const co of chainObjects) {
1612
+ if (co && typeof co === "object") {
1613
+ const coRecord = co;
1614
+ const addr = coRecord.object ?? coRecord.objectId ?? coRecord.object_id;
1615
+ if (typeof addr === "string") {
1616
+ chainByAddress.set(addr, co);
1617
+ }
1618
+ }
1619
+ }
1620
+ const results = [];
1621
+ let matched = 0, mismatched = 0, missing = 0;
1622
+ for (const obj of deployedObjects.slice(0, batchSize)) {
1623
+ const chainObj = chainByAddress.get(obj.objectAddress);
1624
+ if (!chainObj) {
1625
+ missing++;
1626
+ results.push({
1627
+ objectAddress: obj.objectAddress,
1628
+ objectName: obj.objectName,
1629
+ objectType: obj.objectType,
1630
+ status: "missing",
1631
+ });
1632
+ continue;
1633
+ }
1634
+ const chainData = chainObj;
1635
+ const sqliteData = (await localProject.getCachedObjects([obj.objectAddress])).get(obj.objectAddress);
1636
+ const sqliteFields = sqliteData?.data ?? {};
1637
+ const differences = [];
1638
+ let bindings;
1639
+ if (isObjectOfType(obj.objectType, "service")) {
1640
+ const fieldsToCompare = ["machine", "permission", "buy_guard", "arbitrations", "rewards", "repositories"];
1641
+ for (const f of fieldsToCompare) {
1642
+ const sqliteVal = sqliteFields[f];
1643
+ const chainVal = chainData[f];
1644
+ const norm = (v) => (v === null || v === undefined || v === "") ? null : v;
1645
+ if (JSON.stringify(norm(sqliteVal)) !== JSON.stringify(norm(chainVal))) {
1646
+ differences.push({ field: f, sqlite: sqliteVal, onchain: chainVal });
1647
+ }
1648
+ }
1649
+ bindings = {
1650
+ machine: !!chainData.machine,
1651
+ permission: !!chainData.permission,
1652
+ buy_guard: !!chainData.buy_guard,
1653
+ order_allocators: !!chainData.order_allocators,
1654
+ };
1655
+ if (sqliteFields.bPublished !== undefined && chainData.bPublished !== undefined
1656
+ && sqliteFields.bPublished !== chainData.bPublished) {
1657
+ differences.push({
1658
+ field: "bPublished",
1659
+ sqlite: sqliteFields.bPublished,
1660
+ onchain: chainData.bPublished,
1661
+ });
1662
+ }
1663
+ }
1664
+ else if (isObjectOfType(obj.objectType, "machine")) {
1665
+ if (sqliteFields.bPublished !== undefined && chainData.bPublished !== undefined
1666
+ && sqliteFields.bPublished !== chainData.bPublished) {
1667
+ differences.push({
1668
+ field: "bPublished",
1669
+ sqlite: sqliteFields.bPublished,
1670
+ onchain: chainData.bPublished,
1671
+ });
1672
+ }
1673
+ const sqliteNodeCount = sqliteFields.node_count;
1674
+ const chainNodeCount = chainData.node_count;
1675
+ if (sqliteNodeCount !== undefined && chainNodeCount !== undefined
1676
+ && sqliteNodeCount !== chainNodeCount) {
1677
+ differences.push({
1678
+ field: "node_count",
1679
+ sqlite: sqliteNodeCount,
1680
+ onchain: chainNodeCount,
1681
+ });
1682
+ }
1683
+ }
1684
+ if (differences.length > 0) {
1685
+ mismatched++;
1686
+ results.push({
1687
+ objectAddress: obj.objectAddress,
1688
+ objectName: obj.objectName,
1689
+ objectType: obj.objectType,
1690
+ status: "mismatched",
1691
+ differences,
1692
+ bindings,
1693
+ });
1694
+ }
1695
+ else {
1696
+ matched++;
1697
+ results.push({
1698
+ objectAddress: obj.objectAddress,
1699
+ objectName: obj.objectName,
1700
+ objectType: obj.objectType,
1701
+ status: "matched",
1702
+ bindings,
1703
+ });
1704
+ }
1705
+ }
1706
+ const summary = matched === deployedObjects.length
1707
+ ? `All ${matched} deployed objects verified.`
1708
+ : `${matched} matched, ${mismatched} mismatched, ${missing} missing.`;
1709
+ return {
1710
+ action: "verify_deployment",
1711
+ verifiedCount: results.length,
1712
+ matched,
1713
+ mismatched,
1714
+ missing,
1715
+ unreachable: 0,
1716
+ results,
1717
+ summary,
1718
+ };
1719
+ }