@yuzc-001/grasp 0.6.6

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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +327 -0
  3. package/README.zh-CN.md +324 -0
  4. package/examples/README.md +31 -0
  5. package/examples/claude-desktop.json +8 -0
  6. package/examples/codex-config.toml +4 -0
  7. package/grasp.skill +0 -0
  8. package/index.js +87 -0
  9. package/package.json +48 -0
  10. package/scripts/grasp_openclaw_ctl.sh +122 -0
  11. package/scripts/run-search-benchmark.mjs +287 -0
  12. package/scripts/update-star-history.mjs +274 -0
  13. package/skill/SKILL.md +61 -0
  14. package/skill/references/tools.md +306 -0
  15. package/src/cli/auto-configure.js +116 -0
  16. package/src/cli/cmd-connect.js +148 -0
  17. package/src/cli/cmd-explain.js +42 -0
  18. package/src/cli/cmd-logs.js +55 -0
  19. package/src/cli/cmd-status.js +119 -0
  20. package/src/cli/config.js +27 -0
  21. package/src/cli/detect-chrome.js +58 -0
  22. package/src/grasp/handoff/events.js +67 -0
  23. package/src/grasp/handoff/persist.js +48 -0
  24. package/src/grasp/handoff/state.js +28 -0
  25. package/src/grasp/page/capture.js +34 -0
  26. package/src/grasp/page/state.js +273 -0
  27. package/src/grasp/verify/evidence.js +40 -0
  28. package/src/grasp/verify/pipeline.js +52 -0
  29. package/src/layer1-bridge/chrome.js +416 -0
  30. package/src/layer1-bridge/webmcp.js +143 -0
  31. package/src/layer2-perception/hints.js +284 -0
  32. package/src/layer3-action/actions.js +400 -0
  33. package/src/runtime/browser-instance.js +65 -0
  34. package/src/runtime/truth/model.js +94 -0
  35. package/src/runtime/truth/snapshot.js +51 -0
  36. package/src/server/affordances.js +47 -0
  37. package/src/server/audit.js +122 -0
  38. package/src/server/boss-fast-path.js +164 -0
  39. package/src/server/boundary-guard.js +53 -0
  40. package/src/server/content.js +97 -0
  41. package/src/server/continuity.js +256 -0
  42. package/src/server/engine-selection.js +29 -0
  43. package/src/server/entry-orchestrator.js +115 -0
  44. package/src/server/error-codes.js +7 -0
  45. package/src/server/explain-share-card.js +113 -0
  46. package/src/server/fast-path-router.js +134 -0
  47. package/src/server/form-runtime.js +602 -0
  48. package/src/server/form-tasks.js +254 -0
  49. package/src/server/gateway-response.js +62 -0
  50. package/src/server/index.js +22 -0
  51. package/src/server/observe.js +52 -0
  52. package/src/server/page-projection.js +31 -0
  53. package/src/server/page-state.js +27 -0
  54. package/src/server/postconditions.js +128 -0
  55. package/src/server/prompt-assembly.js +148 -0
  56. package/src/server/responses.js +44 -0
  57. package/src/server/route-boundary.js +174 -0
  58. package/src/server/route-policy.js +168 -0
  59. package/src/server/runtime-confirmation.js +87 -0
  60. package/src/server/runtime-status.js +7 -0
  61. package/src/server/share-artifacts.js +284 -0
  62. package/src/server/state.js +132 -0
  63. package/src/server/structured-extraction.js +131 -0
  64. package/src/server/surface-prompts.js +166 -0
  65. package/src/server/task-frame.js +11 -0
  66. package/src/server/tasks/search-task.js +321 -0
  67. package/src/server/tools.actions.js +1361 -0
  68. package/src/server/tools.form.js +526 -0
  69. package/src/server/tools.gateway.js +757 -0
  70. package/src/server/tools.handoff.js +210 -0
  71. package/src/server/tools.js +20 -0
  72. package/src/server/tools.legacy.js +983 -0
  73. package/src/server/tools.strategy.js +250 -0
  74. package/src/server/tools.task-surface.js +66 -0
  75. package/src/server/tools.workspace.js +873 -0
  76. package/src/server/workspace-runtime.js +1138 -0
  77. package/src/server/workspace-tasks.js +735 -0
  78. package/start-chrome.bat +84 -0
@@ -0,0 +1,250 @@
1
+ import { z } from 'zod';
2
+
3
+ import { getActivePage, navigateTo, pinTargetPage, trustedContextOpen } from '../layer1-bridge/chrome.js';
4
+ import { textResponse } from './responses.js';
5
+ import { syncPageState } from './state.js';
6
+ import { audit } from './audit.js';
7
+ import { requestHandoff } from '../grasp/handoff/events.js';
8
+ import { attachHandoffTaskMetadata, readHandoffState, writeHandoffState } from '../grasp/handoff/persist.js';
9
+ import { buildCheckpointHandoffSuggestion, buildSessionTrustPreflight } from './continuity.js';
10
+ import { createEntryOrchestrator } from './entry-orchestrator.js';
11
+ import { readBrowserInstance } from '../runtime/browser-instance.js';
12
+ import { requireConfirmedRuntimeInstance } from './runtime-confirmation.js';
13
+
14
+ function getEntryStrategies(preflight) {
15
+ if (preflight.recommended_entry_strategy === 'resume_existing_session') {
16
+ return ['trusted_context_open', 'direct_goto'];
17
+ }
18
+
19
+ if (preflight.recommended_entry_strategy === 'preheat_before_direct_entry') {
20
+ return ['trusted_context_open', 'direct_goto'];
21
+ }
22
+
23
+ return ['direct_goto'];
24
+ }
25
+
26
+ export async function enterWithStrategy({ url, state, deps = {} }) {
27
+ const getActivePageFn = deps.getActivePage ?? getActivePage;
28
+ const directGoto = deps.directGoto ?? deps.navigateTo ?? navigateTo;
29
+ const trustedContextOpenFn = deps.trustedContextOpen ?? trustedContextOpen;
30
+ const syncState = deps.syncPageState ?? syncPageState;
31
+ const readHandoff = deps.readHandoffState ?? readHandoffState;
32
+ const auditFn = deps.audit ?? audit;
33
+ const auditName = deps.auditName ?? 'navigate_with_strategy';
34
+
35
+ const handoff = await readHandoff();
36
+ let pageState = state.pageState ?? {};
37
+
38
+ try {
39
+ const activePage = await getActivePageFn({ state });
40
+ await syncState(activePage, state);
41
+ pageState = state.pageState ?? {};
42
+ } catch {
43
+ // allow strategy selection even before first active page is available
44
+ }
45
+
46
+ const preflight = buildSessionTrustPreflight(url, pageState, handoff);
47
+
48
+ if (preflight.recommended_entry_strategy === 'handoff_or_preheat') {
49
+ return { url, title: null, preflight, pageState, handoff };
50
+ }
51
+
52
+ const orchestrator = createEntryOrchestrator({
53
+ directGoto,
54
+ trustedContextOpen: trustedContextOpenFn,
55
+ });
56
+ const entry = await orchestrator.run({
57
+ targetUrl: url,
58
+ strategies: getEntryStrategies(preflight),
59
+ state,
60
+ });
61
+
62
+ const page = entry.page;
63
+ if (page) {
64
+ await syncState(page, state, { force: true });
65
+ await pinTargetPage(page, state);
66
+ }
67
+ await auditFn(auditName, `${entry.entry_method ?? preflight.recommended_entry_strategy} :: ${url}`);
68
+
69
+ return {
70
+ url,
71
+ title: page ? await page.title() : null,
72
+ preflight,
73
+ pageState: state.pageState ?? pageState,
74
+ handoff,
75
+ entry_method: entry.entry_method,
76
+ final_url: entry.final_url,
77
+ verified: entry.verified,
78
+ evidence: entry.evidence,
79
+ };
80
+ }
81
+
82
+ export function registerStrategyTools(server, state, deps = {}) {
83
+ const getPage = deps.getActivePage ?? getActivePage;
84
+ const getBrowserInstance = deps.getBrowserInstance ?? (() => readBrowserInstance(process.env.CHROME_CDP_URL || 'http://localhost:9222'));
85
+
86
+ server.registerTool(
87
+ 'preheat_session',
88
+ {
89
+ description: 'Warm up a target host before direct entry by visiting the origin and waiting for page state to settle.',
90
+ inputSchema: {
91
+ url: z.string().url().describe('Target URL whose host should be preheated'),
92
+ },
93
+ },
94
+ async ({ url }) => {
95
+ const instance = await getBrowserInstance();
96
+ const confirmationError = requireConfirmedRuntimeInstance(state, instance, 'preheat_session');
97
+ if (confirmationError) return confirmationError;
98
+ let origin = url;
99
+ try {
100
+ const parsed = new URL(url);
101
+ origin = `${parsed.protocol}//${parsed.host}/`;
102
+ } catch {}
103
+
104
+ const page = await navigateTo(origin);
105
+ await syncPageState(page, state, { force: true });
106
+ await audit('preheat_session', origin);
107
+
108
+ return textResponse([
109
+ `Preheated host: ${origin}`,
110
+ `Current page: ${await page.title()}`,
111
+ `Page role: ${state.pageState?.currentRole ?? 'unknown'}`,
112
+ `Risk gate detected: ${state.pageState?.riskGateDetected ? 'yes' : 'no'}`,
113
+ `Suggested next action: ${state.pageState?.suggestedNextAction ?? 'none'}`,
114
+ ], { origin, pageState: state.pageState });
115
+ }
116
+ );
117
+
118
+ server.registerTool(
119
+ 'navigate_with_strategy',
120
+ {
121
+ description: 'Run session trust preflight before navigation and apply the recommended entry strategy.',
122
+ inputSchema: {
123
+ url: z.string().url().describe('Target URL to open'),
124
+ },
125
+ },
126
+ async ({ url }) => {
127
+ const instance = await getBrowserInstance();
128
+ const confirmationError = requireConfirmedRuntimeInstance(state, instance, 'navigate_with_strategy');
129
+ if (confirmationError) return confirmationError;
130
+ const outcome = await enterWithStrategy({ url, state });
131
+ const { preflight, handoff, pageState } = outcome;
132
+
133
+ if (preflight.recommended_entry_strategy === 'handoff_or_preheat') {
134
+ const checkpointSuggestion = pageState.currentRole === 'checkpoint'
135
+ ? buildCheckpointHandoffSuggestion(pageState, url)
136
+ : null;
137
+ return textResponse([
138
+ `Navigation strategy: ${preflight.recommended_entry_strategy}`,
139
+ `Session trust: ${preflight.session_trust}`,
140
+ 'Direct navigation is not recommended right now.',
141
+ checkpointSuggestion
142
+ ? `Suggested next step: request_handoff_from_checkpoint (${checkpointSuggestion.reason})`
143
+ : 'Suggested next step: run preheat_session or enter the handoff path.',
144
+ ], { preflight, handoff, pageState, checkpointSuggestion });
145
+ }
146
+
147
+ const extra = preflight.recommended_entry_strategy === 'preheat_before_direct_entry'
148
+ ? 'Preheat recommended: this host looks high-risk for a cold direct entry.'
149
+ : 'Direct navigation accepted.';
150
+
151
+ return textResponse([
152
+ `Navigation strategy: ${preflight.recommended_entry_strategy}`,
153
+ `Session trust: ${preflight.session_trust}`,
154
+ `Navigated to: ${url}`,
155
+ `Page title: ${outcome.title}`,
156
+ `Entry method: ${outcome.entry_method ?? 'unknown'}`,
157
+ `Verified: ${outcome.verified ? 'yes' : 'no'}`,
158
+ extra,
159
+ ], {
160
+ preflight,
161
+ pageState: state.pageState,
162
+ entry_method: outcome.entry_method ?? null,
163
+ final_url: outcome.final_url ?? null,
164
+ verified: outcome.verified ?? false,
165
+ evidence: outcome.evidence ?? null,
166
+ });
167
+ }
168
+ );
169
+
170
+ server.registerTool(
171
+ 'session_trust_preflight',
172
+ {
173
+ description: 'Estimate session trust and recommended entry strategy before or during navigation to a high-friction site.',
174
+ inputSchema: {
175
+ url: z.string().url().describe('Target URL to evaluate'),
176
+ },
177
+ },
178
+ async ({ url }) => {
179
+ let pageState = state.pageState ?? {};
180
+ try {
181
+ const page = await getPage({ state });
182
+ await syncPageState(page, state);
183
+ pageState = state.pageState ?? {};
184
+ } catch {
185
+ // allow preflight without active page
186
+ }
187
+ const handoff = await readHandoffState();
188
+ const preflight = buildSessionTrustPreflight(url, pageState, handoff);
189
+ return textResponse([
190
+ `Target: ${preflight.target_url}`,
191
+ `Session trust: ${preflight.session_trust}`,
192
+ `Recommended entry strategy: ${preflight.recommended_entry_strategy}`,
193
+ `Same target context: ${preflight.same_target_context ? 'yes' : 'no'}`,
194
+ `Checkpoint active: ${preflight.checkpoint_active ? 'yes' : 'no'}`,
195
+ ...(preflight.trust_signals.length ? [`Trust signals: ${preflight.trust_signals.join(', ')}`] : []),
196
+ ], { preflight, handoff, pageState });
197
+ }
198
+ );
199
+
200
+ server.registerTool(
201
+ 'suggest_handoff',
202
+ {
203
+ description: 'Suggest a handoff payload based on the current page state, especially for checkpoint/gated pages.',
204
+ inputSchema: {},
205
+ },
206
+ async () => {
207
+ const page = await getPage({ state });
208
+ await syncPageState(page, state, { force: true });
209
+ const suggestion = buildCheckpointHandoffSuggestion(state.pageState, page.url());
210
+ return textResponse([
211
+ `Suggested reason: ${suggestion.reason}`,
212
+ `Suggested next action: ${suggestion.suggested_next_action}`,
213
+ `Checkpoint kind: ${suggestion.checkpoint_kind}`,
214
+ ...(suggestion.checkpoint_signals?.length ? [`Checkpoint signals: ${suggestion.checkpoint_signals.join(', ')}`] : []),
215
+ `Note: ${suggestion.note}`,
216
+ ], { suggestion, pageState: state.pageState });
217
+ }
218
+ );
219
+
220
+ server.registerTool(
221
+ 'request_handoff_from_checkpoint',
222
+ {
223
+ description: 'Create and persist a handoff directly from the current checkpoint/gated page state.',
224
+ inputSchema: {
225
+ note: z.string().optional().describe('Optional override note for the generated checkpoint handoff'),
226
+ },
227
+ },
228
+ async ({ note } = {}) => {
229
+ const page = await getPage({ state });
230
+ await syncPageState(page, state, { force: true });
231
+ const suggestion = buildCheckpointHandoffSuggestion(state.pageState, page.url());
232
+ state.handoff = attachHandoffTaskMetadata(requestHandoff(await readHandoffState(), suggestion.reason, note ?? suggestion.note, {
233
+ expected_url_contains: suggestion.expected_url_contains,
234
+ expected_page_role: suggestion.expected_page_role,
235
+ expected_selector: suggestion.expected_selector,
236
+ continuation_goal: suggestion.continuation_goal,
237
+ expected_hint_label: suggestion.expected_hint_label,
238
+ }), state);
239
+ await writeHandoffState(state.handoff);
240
+ await audit('handoff_request_from_checkpoint', `${suggestion.reason}${note ? ` :: ${note}` : ''}`);
241
+ return textResponse([
242
+ `Checkpoint handoff requested: ${suggestion.reason}`,
243
+ `Checkpoint kind: ${suggestion.checkpoint_kind}`,
244
+ `Suggested next action: ${suggestion.suggested_next_action}`,
245
+ `State: handoff_required`,
246
+ `Note: ${note ?? suggestion.note}`,
247
+ ], { handoff: state.handoff, suggestion, pageState: state.pageState });
248
+ }
249
+ );
250
+ }
@@ -0,0 +1,66 @@
1
+ import { z } from 'zod';
2
+ import { createTaskFrame } from './task-frame.js';
3
+ import { textResponse } from './responses.js';
4
+
5
+ export function registerTaskTools(server, state) {
6
+ server.registerTool(
7
+ 'list_tasks',
8
+ {
9
+ description: 'List all currently tracked tasks in the runtime.',
10
+ inputSchema: {},
11
+ },
12
+ async () => {
13
+ const tasks = Array.from(state.taskFrames.values()).map((frame) => ({
14
+ taskId: frame.taskId,
15
+ kind: frame.kind,
16
+ attempts: frame.attempts,
17
+ active: state.activeTaskId === frame.taskId,
18
+ }));
19
+
20
+ if (tasks.length === 0) {
21
+ return textResponse('No active tasks tracked.');
22
+ }
23
+
24
+ const lines = tasks.map((t) =>
25
+ `[${t.active ? '*' : ' '}] ${t.taskId} (kind: ${t.kind}, attempts: ${t.attempts})`
26
+ );
27
+
28
+ return textResponse([
29
+ 'Tracked Tasks:',
30
+ '',
31
+ ...lines,
32
+ '',
33
+ '* = currently active task',
34
+ ], { tasks });
35
+ }
36
+ );
37
+
38
+ server.registerTool(
39
+ 'switch_task',
40
+ {
41
+ description: 'Switch the active task context or create a new task frame.',
42
+ inputSchema: {
43
+ taskId: z.string().describe('Unique ID for the task'),
44
+ kind: z.enum(['read', 'extract', 'act', 'submit', 'workspace', 'collect']).optional().describe('Task kind (used when creating a new task)'),
45
+ },
46
+ },
47
+ async ({ taskId, kind = 'extract' }) => {
48
+ let frame = state.taskFrames.get(taskId);
49
+ const isNew = !frame;
50
+
51
+ if (isNew) {
52
+ frame = createTaskFrame({ taskId, kind });
53
+ state.taskFrames.set(taskId, frame);
54
+ }
55
+
56
+ state.activeTaskId = taskId;
57
+
58
+ return textResponse(
59
+ isNew
60
+ ? `Created and switched to new task: ${taskId} (kind: ${kind})`
61
+ : `Switched to existing task: ${taskId}`,
62
+ { taskId, kind: frame.kind, is_new: isNew }
63
+ );
64
+ }
65
+ );
66
+ }