@eyeauras/insight-cli 0.3.1 → 0.4.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 (152) hide show
  1. package/.cli-package-build.json +1 -1
  2. package/SKILL.md +4 -1
  3. package/dist/src/analysis-default.d.ts +26 -0
  4. package/dist/src/analysis-default.js +149 -0
  5. package/dist/src/analysis-storage.d.ts +39 -20
  6. package/dist/src/analysis-storage.js +112 -16
  7. package/dist/src/arguments.js +9 -4
  8. package/dist/src/cli.js +45 -37
  9. package/dist/src/code-references.d.ts +48 -0
  10. package/dist/src/code-references.js +154 -0
  11. package/dist/src/context-diagnostics.d.ts +25 -0
  12. package/dist/src/context-diagnostics.js +82 -0
  13. package/dist/src/contexts.d.ts +13 -1
  14. package/dist/src/contexts.js +38 -9
  15. package/dist/src/function-boundaries.d.ts +9 -0
  16. package/dist/src/function-boundaries.js +68 -0
  17. package/dist/src/function-entries.d.ts +33 -0
  18. package/dist/src/function-entries.js +84 -0
  19. package/dist/src/function-inspection.d.ts +4 -2
  20. package/dist/src/function-inspection.js +94 -59
  21. package/dist/src/function-results.d.ts +3 -1
  22. package/dist/src/function-results.js +9 -3
  23. package/dist/src/inline-script.d.ts +15 -0
  24. package/dist/src/inline-script.js +62 -0
  25. package/dist/src/internal-timing.d.ts +3 -0
  26. package/dist/src/internal-timing.js +52 -0
  27. package/dist/src/lifecycle.js +14 -4
  28. package/dist/src/native-inputs.d.ts +1 -1
  29. package/dist/src/native-inputs.js +1 -1
  30. package/dist/src/native-queries.js +1 -1
  31. package/dist/src/persistent-client.d.ts +30 -0
  32. package/dist/src/persistent-client.js +125 -0
  33. package/dist/src/queries.js +4 -1
  34. package/dist/src/query-policy.js +9 -1
  35. package/dist/src/research-view.d.ts +6 -0
  36. package/dist/src/research-view.js +9 -0
  37. package/dist/src/research.d.ts +38 -0
  38. package/dist/src/research.js +78 -8
  39. package/dist/src/string-anchors.d.ts +43 -0
  40. package/dist/src/string-anchors.js +176 -0
  41. package/dist/src/sync-policy.js +3 -0
  42. package/dist/src/timed-backend.d.ts +3 -0
  43. package/dist/src/timed-backend.js +16 -0
  44. package/dist/src/transport.d.ts +1 -0
  45. package/dist/src/transport.js +4 -0
  46. package/dist/src/workspace.js +5 -2
  47. package/package.json +12 -1
  48. package/references/native/InsightAnchorScan.java +135 -0
  49. package/references/native/InsightCodeReferences.java +129 -0
  50. package/references/native/InsightFunctionBoundaries.java +56 -0
  51. package/references/native/InsightInlineExecutor.java +143 -0
  52. package/references/native/InsightInlineScript.java +14 -0
  53. package/references/native/InsightPeRanges.java +92 -0
  54. package/references/native/InsightResearchQueries.java +22 -1
  55. package/references/native/InsightResearchView.java +69 -0
  56. package/references/native/InsightStringAnchors.java +49 -0
  57. package/references/persistent-client.mjs +125 -0
  58. package/references/persistent.md +50 -0
  59. package/references/query-semantics.md +12 -0
  60. package/references/recovery.md +36 -0
  61. package/references/research.md +87 -0
  62. package/references/setup.md +1 -1
  63. package/references/sparse-navigation.md +173 -0
  64. package/references/storage.md +35 -7
  65. package/runtime/GhidraMCP-7.0.0.jar +0 -0
  66. package/runtime/GhidraMCP-NOTICE +5 -0
  67. package/runtime/provenance.json +17 -3
  68. package/runtime/research-view.provenance.json +28 -0
  69. package/runtime/toolbox.json +10 -3
  70. package/skill-bundle/insight-artifacts/SKILL.md +1 -1
  71. package/skill-bundle/insight-artifacts/references/recovery.md +36 -0
  72. package/skill-bundle/insight-artifacts/references/storage.md +35 -7
  73. package/skill-bundle/insight-binary/SKILL.md +1 -1
  74. package/skill-bundle/insight-capture/SKILL.md +1 -1
  75. package/skill-bundle/insight-cli/SKILL.md +4 -1
  76. package/skill-bundle/insight-cli/references/persistent-client.mjs +125 -0
  77. package/skill-bundle/insight-cli/references/persistent.md +50 -0
  78. package/skill-bundle/insight-cli/references/recovery.md +36 -0
  79. package/skill-bundle/insight-cli/references/setup.md +1 -1
  80. package/skill-bundle/insight-cli/references/storage.md +35 -7
  81. package/skill-bundle/insight-die/SKILL.md +1 -1
  82. package/skill-bundle/insight-dnspy/SKILL.md +1 -1
  83. package/skill-bundle/insight-dotnet-dump/SKILL.md +1 -1
  84. package/skill-bundle/insight-dotnet-stack/SKILL.md +1 -1
  85. package/skill-bundle/insight-dotnet-trace/SKILL.md +1 -1
  86. package/skill-bundle/insight-frida/SKILL.md +1 -1
  87. package/skill-bundle/insight-ghidra/SKILL.md +14 -1
  88. package/skill-bundle/insight-ghidra/references/persistent-client.mjs +125 -0
  89. package/skill-bundle/insight-ghidra/references/persistent.md +50 -0
  90. package/skill-bundle/insight-ghidra/references/query-semantics.md +12 -0
  91. package/skill-bundle/insight-ghidra/references/recovery.md +36 -0
  92. package/skill-bundle/insight-ghidra/references/research.md +87 -0
  93. package/skill-bundle/insight-ghidra/references/setup.md +1 -1
  94. package/skill-bundle/insight-ghidra/references/sparse-navigation.md +173 -0
  95. package/skill-bundle/insight-ghidra/references/storage.md +35 -7
  96. package/skill-bundle/insight-ilspy/SKILL.md +1 -1
  97. package/skill-bundle/insight-interop/SKILL.md +1 -1
  98. package/skill-bundle/insight-lldb/SKILL.md +1 -1
  99. package/skill-bundle/insight-llvm/SKILL.md +1 -1
  100. package/skill-bundle/insight-managed-heap/SKILL.md +1 -1
  101. package/skill-bundle/insight-managed-metadata/SKILL.md +1 -1
  102. package/skill-bundle/insight-mitmproxy/SKILL.md +1 -1
  103. package/skill-bundle/insight-overview/SKILL.md +1 -1
  104. package/skill-bundle/insight-procmon/SKILL.md +1 -1
  105. package/skill-bundle/insight-re/SKILL.md +4 -1
  106. package/skill-bundle/insight-re/references/persistent-client.mjs +125 -0
  107. package/skill-bundle/insight-re/references/persistent.md +50 -0
  108. package/skill-bundle/insight-re/references/recovery.md +36 -0
  109. package/skill-bundle/insight-re/references/storage.md +35 -7
  110. package/skill-bundle/insight-sevenzip/SKILL.md +1 -1
  111. package/skill-bundle/insight-strace/SKILL.md +1 -1
  112. package/skill-bundle/insight-tshark/SKILL.md +1 -1
  113. package/skill-bundle/insight-windbg/SKILL.md +1 -1
  114. package/skill-bundle/manifest.json +1 -1
  115. package/skills/insight-artifacts/SKILL.md +1 -1
  116. package/skills/insight-artifacts/references/recovery.md +36 -0
  117. package/skills/insight-artifacts/references/storage.md +35 -7
  118. package/skills/insight-binary/SKILL.md +1 -1
  119. package/skills/insight-capture/SKILL.md +1 -1
  120. package/skills/insight-die/SKILL.md +1 -1
  121. package/skills/insight-dnspy/SKILL.md +1 -1
  122. package/skills/insight-dotnet-dump/SKILL.md +1 -1
  123. package/skills/insight-dotnet-stack/SKILL.md +1 -1
  124. package/skills/insight-dotnet-trace/SKILL.md +1 -1
  125. package/skills/insight-frida/SKILL.md +1 -1
  126. package/skills/insight-ghidra/SKILL.md +14 -1
  127. package/skills/insight-ghidra/references/persistent-client.mjs +125 -0
  128. package/skills/insight-ghidra/references/persistent.md +50 -0
  129. package/skills/insight-ghidra/references/query-semantics.md +12 -0
  130. package/skills/insight-ghidra/references/recovery.md +36 -0
  131. package/skills/insight-ghidra/references/research.md +87 -0
  132. package/skills/insight-ghidra/references/setup.md +1 -1
  133. package/skills/insight-ghidra/references/sparse-navigation.md +173 -0
  134. package/skills/insight-ghidra/references/storage.md +35 -7
  135. package/skills/insight-ilspy/SKILL.md +1 -1
  136. package/skills/insight-interop/SKILL.md +1 -1
  137. package/skills/insight-lldb/SKILL.md +1 -1
  138. package/skills/insight-llvm/SKILL.md +1 -1
  139. package/skills/insight-managed-heap/SKILL.md +1 -1
  140. package/skills/insight-managed-metadata/SKILL.md +1 -1
  141. package/skills/insight-mitmproxy/SKILL.md +1 -1
  142. package/skills/insight-overview/SKILL.md +1 -1
  143. package/skills/insight-procmon/SKILL.md +1 -1
  144. package/skills/insight-re/SKILL.md +4 -1
  145. package/skills/insight-re/references/persistent-client.mjs +125 -0
  146. package/skills/insight-re/references/persistent.md +50 -0
  147. package/skills/insight-re/references/recovery.md +36 -0
  148. package/skills/insight-re/references/storage.md +35 -7
  149. package/skills/insight-sevenzip/SKILL.md +1 -1
  150. package/skills/insight-strace/SKILL.md +1 -1
  151. package/skills/insight-tshark/SKILL.md +1 -1
  152. package/skills/insight-windbg/SKILL.md +1 -1
@@ -9,7 +9,7 @@ import { promisify } from 'node:util';
9
9
  import { fail, stable, text } from './model.js';
10
10
  import { managedExtensions, extensionJvmArguments } from './extensions.js';
11
11
  import { runtimeValues, runtimeSelection } from './runtime-selection.js';
12
- export const pinnedMcpJar = '7084e2fda16dc779dd05398e48821ff3f9ace4d32757ea143b257b39689e5fbd';
12
+ export const pinnedMcpJar = 'f4a9272e911466ed774c8cf73bc1adf1daef6009d895fda57aa0b28522fcdf32';
13
13
  export const packagedMcpJar = fileURLToPath(new URL('../../runtime/GhidraMCP-7.0.0.jar', import.meta.url));
14
14
  /** Ghidra has its own cache/settings overrides in addition to Java's home/tmp properties. */
15
15
  export const nativeJvmDirectories = (root) => ['-Duser.home=' + root, '-Djava.io.tmpdir=' + root,
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { object, fail } from './model.js';
4
4
  export async function nativeQuery(http, program, operation, parameters, write = false) {
5
- const base = operation.startsWith('/sdk/') ? 'InsightSdkScript' : operation.startsWith('/emulate_') ? 'InsightEmulation' : 'InsightResearchQueries';
5
+ const base = operation === '/code_references' ? 'InsightCodeReferences' : operation.startsWith('/sdk/') ? 'InsightSdkScript' : operation.startsWith('/emulate_') ? 'InsightEmulation' : 'InsightResearchQueries';
6
6
  const name = base + '_' + randomUUID().replaceAll('-', '');
7
7
  const source = await readFile(new URL('../../references/native/' + base + '.java', import.meta.url), 'utf8');
8
8
  const response = object(await http.call('/run_script_inline', { program, code: source.replace('public class ' + base + ' ', 'public class ' + name + ' '),
@@ -0,0 +1,30 @@
1
+ export declare class InsightCommandError extends Error {
2
+ readonly rpc: {
3
+ code: number;
4
+ message: string;
5
+ data?: unknown;
6
+ };
7
+ constructor(rpc: {
8
+ code: number;
9
+ message: string;
10
+ data?: unknown;
11
+ });
12
+ }
13
+ export declare class InsightTransportError extends Error {
14
+ readonly dispatched: boolean;
15
+ constructor(message: string, dispatched: boolean);
16
+ }
17
+ export interface InsightConnectionOptions {
18
+ executable: string;
19
+ /** For example [installedBin] with executable: process.execPath. No shell is used. */
20
+ arguments?: readonly string[];
21
+ cwd?: string;
22
+ env?: NodeJS.ProcessEnv;
23
+ }
24
+ export interface InsightConnection {
25
+ execute<T = unknown>(argv: readonly string[]): Promise<T>;
26
+ /** Drain admitted requests, then end this transport. Does not stop the owner or Ghidra. */
27
+ close(): Promise<void>;
28
+ }
29
+ /** A thin client of Factory's existing stream, with no reconnect or command replay. */
30
+ export declare function connectInsight(options: InsightConnectionOptions): Promise<InsightConnection>;
@@ -0,0 +1,125 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { StringDecoder } from 'node:string_decoder';
3
+ export class InsightCommandError extends Error {
4
+ rpc;
5
+ constructor(rpc) {
6
+ super(rpc.message);
7
+ this.rpc = rpc;
8
+ this.name = 'InsightCommandError';
9
+ }
10
+ }
11
+ export class InsightTransportError extends Error {
12
+ dispatched;
13
+ constructor(message, dispatched) {
14
+ super(message);
15
+ this.dispatched = dispatched;
16
+ this.name = 'InsightTransportError';
17
+ }
18
+ }
19
+ /** A thin client of Factory's existing stream, with no reconnect or command replay. */
20
+ export async function connectInsight(options) {
21
+ const child = spawn(options.executable, [...(options.arguments ?? []), '--json-rpc'], { cwd: options.cwd, env: options.env, stdio: ['pipe', 'pipe', 'pipe'], shell: false });
22
+ const maximum = 4 * 1024 ** 2, decoder = new StringDecoder('utf8');
23
+ let buffer = '', bytes = 0, sequence = 0, accepting = true, failed = false, ended = false;
24
+ let closePromise, tail = Promise.resolve();
25
+ const pending = new Map();
26
+ // Drain diagnostics without retaining arbitrary stderr, paths, tokens or unbounded output.
27
+ child.stderr.resume();
28
+ const fail = (message) => {
29
+ if (failed)
30
+ return;
31
+ failed = true;
32
+ accepting = false;
33
+ for (const p of pending.values())
34
+ p.reject(new InsightTransportError(message, true));
35
+ pending.clear();
36
+ child.stdin.destroy();
37
+ child.stdout.destroy();
38
+ };
39
+ child.on('error', () => fail('Persistent CLI process failed.'));
40
+ child.stdout.on('error', () => fail('Persistent CLI output was lost.'));
41
+ child.stderr.on('error', () => fail('Persistent CLI diagnostic stream was lost.'));
42
+ child.stdin.on('error', () => fail('Persistent CLI input was lost; inspect request/status before retrying a changing command.'));
43
+ const exited = new Promise((resolve, reject) => {
44
+ child.once('close', (code, signal) => {
45
+ ended = true;
46
+ if (code !== 0 || signal || pending.size || buffer.trim()) {
47
+ fail('Persistent CLI closed without a complete acknowledged response.');
48
+ reject(new InsightTransportError('Persistent CLI transport ended unexpectedly.', false));
49
+ }
50
+ else if (failed)
51
+ reject(new InsightTransportError('Persistent CLI transport failed.', false));
52
+ else
53
+ resolve();
54
+ });
55
+ });
56
+ void exited.catch(() => { });
57
+ child.stdout.on('data', (chunk) => {
58
+ if (failed)
59
+ return;
60
+ buffer += decoder.write(chunk);
61
+ bytes += chunk.length;
62
+ if (bytes > maximum) {
63
+ fail('Persistent CLI response exceeded the bounded transport limit.');
64
+ return;
65
+ }
66
+ for (;;) {
67
+ const newline = buffer.indexOf('\n');
68
+ if (newline < 0)
69
+ break;
70
+ const line = buffer.slice(0, newline);
71
+ buffer = buffer.slice(newline + 1);
72
+ bytes = Buffer.byteLength(buffer);
73
+ if (!line.trim())
74
+ continue;
75
+ try {
76
+ const r = JSON.parse(line);
77
+ if (!r || r.jsonrpc !== '2.0' || typeof r.id !== 'number' || !pending.has(r.id) || Object.hasOwn(r, 'result') === Object.hasOwn(r, 'error'))
78
+ throw Error();
79
+ const p = pending.get(r.id);
80
+ if (Object.hasOwn(r, 'error') && (!r.error || typeof r.error !== 'object' || typeof r.error.code !== 'number' || !Number.isInteger(r.error.code) || typeof r.error.message !== 'string'))
81
+ throw Error();
82
+ pending.delete(r.id);
83
+ if (r.error)
84
+ p.reject(new InsightCommandError(r.error));
85
+ else
86
+ p.resolve(r.result);
87
+ }
88
+ catch {
89
+ fail('Persistent CLI returned a malformed or uncorrelated response.');
90
+ return;
91
+ }
92
+ }
93
+ });
94
+ await new Promise((resolve, reject) => { child.once('spawn', resolve); child.once('error', () => reject(new InsightTransportError('Could not start persistent CLI.', false))); });
95
+ return {
96
+ execute(argv) {
97
+ if (!Array.isArray(argv) || argv.length > 512 || argv.some(x => typeof x !== 'string' || x.includes('\0')) || Buffer.byteLength(JSON.stringify(argv)) > 1024 ** 2)
98
+ return Promise.reject(new TypeError('argv must be a bounded array of strings without NUL.'));
99
+ if (!accepting || failed || ended)
100
+ return Promise.reject(new InsightTransportError('Persistent CLI connection is closed.', false));
101
+ const args = [...argv];
102
+ const request = tail.then(() => {
103
+ if (failed || ended)
104
+ throw new InsightTransportError('Persistent CLI connection was lost before dispatch.', false);
105
+ const id = ++sequence;
106
+ return new Promise((resolve, reject) => {
107
+ pending.set(id, { resolve, reject });
108
+ try {
109
+ child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method: 'cli.execute', params: { argv: args } }) + '\n');
110
+ }
111
+ catch {
112
+ fail('Persistent CLI input was lost; inspect request/status before retrying a changing command.');
113
+ }
114
+ });
115
+ });
116
+ tail = request.catch(() => { });
117
+ return request;
118
+ },
119
+ close() {
120
+ accepting = false;
121
+ return closePromise ??= tail.then(async () => { if (!failed && !ended)
122
+ child.stdin.end(); await exited; });
123
+ }
124
+ };
125
+ }
@@ -51,7 +51,7 @@ export function queryCommands(research) {
51
51
  parameters[p.name] = parsed(p, input);
52
52
  }
53
53
  if (q.path === '/get_functions')
54
- return research(c).inspectFunctions(scope(o), parameters, { limit: number(o.limitChars ?? 16000, 32768, 1), offset: number(o.offsetChars ?? 0, 4 * 1024 ** 2), total: number(o.totalChars ?? 64000, 128000, 1), ...(o.expectView === undefined ? {} : { expectView: String(o.expectView) }), ...(o.saveOwnerDirectory === undefined ? {} : { saveDirectory: String(o.saveOwnerDirectory) }) });
54
+ return research(c).inspectFunctions(scope(o), parameters, { limit: number(o.limitChars ?? 16000, 32768, 1), offset: number(o.offsetChars ?? 0, 4 * 1024 ** 2), total: number(o.totalChars ?? 64000, 128000, 1), ...(o.expectView === undefined ? {} : { expectView: String(o.expectView) }), ...(o.saveOwnerDirectory === undefined ? {} : { saveDirectory: String(o.saveOwnerDirectory) }), ...(o.resolveAddress === undefined ? {} : { resolveAddress: String(o.resolveAddress) }), ...(o.maxEntryDistance === undefined ? {} : { maxEntryDistance: number(o.maxEntryDistance, 65536, 1) }), ...(o.maxUnwindBytes === undefined ? {} : { maxUnwindBytes: number(o.maxUnwindBytes, 16777216, 1) }) });
55
55
  if (nativeQueries.has(q.path)) {
56
56
  if (Number(parameters.timeout_seconds ?? 5) > 30)
57
57
  fail('invalid', 'Native query timeout must be at most 30 seconds.');
@@ -65,6 +65,9 @@ export function queryCommands(research) {
65
65
  await research(c).read(who, q.path, parameters);
66
66
  return { ...who, value: value(qualifyQuery(q.path, queryResult(q.path, parameters, value(result)))) };
67
67
  }, { permission: Permission.ReadOnly, options: [...selectedOptions, ...q.params.map(option), ...(q.path === '/get_functions' ? [
68
+ { flags: '--resolve-address <mode>', description: 'containing-or-preceding or containing-or-pe-runtime; address selectors only' },
69
+ { flags: '--max-entry-distance <bytes>', description: 'Required with containing-or-preceding: fallback distance 1–65536 within the same initialized executable block' },
70
+ { flags: '--max-unwind-bytes <bytes>', description: 'PE runtime mode: 1–16777216 actual header/table bytes, default 4194304; fixed 5s cooperative lookup deadline' },
68
71
  { flags: '--limit-chars <count>', description: 'Per-function Unicode window, 1–32768', defaultValue: 16000 },
69
72
  { flags: '--offset-chars <count>', description: 'Per-function Unicode offset', defaultValue: 0 },
70
73
  { flags: '--total-chars <count>', description: 'Total returned decompiler characters, 1–128000', defaultValue: 64000 },
@@ -1,6 +1,6 @@
1
1
  import { fail, object } from './model.js';
2
2
  /** These operations have facade-owned, bounded native implementations. */
3
- export const nativeQueries = new Set(['/research_view', '/decode_instructions', '/find_dead_code', '/detect_crypto_constants', '/get_valid_data_types',
3
+ export const nativeQueries = new Set(['/code_references', '/navigation_diagnostic', '/research_view', '/decode_instructions', '/find_dead_code', '/detect_crypto_constants', '/get_valid_data_types',
4
4
  '/validate_data_type', '/validate_function_prototype', '/get_entry_points', '/list_globals',
5
5
  '/list_strings', '/search_strings', '/find_data_types', '/search_byte_patterns', '/search_instructions',
6
6
  '/emulate_function', '/emulate_hash_batch']);
@@ -25,6 +25,12 @@ const heuristics = Object.fromEntries([
25
25
  'Scores and confidence labels are not calibrated probabilities; the agent decides what to accept.',
26
26
  ] }]));
27
27
  export function queryDescription(path, fallback) {
28
+ if (path === '/force_decompile')
29
+ return 'Refresh decompiler output for an existing function entry; does not repair Listing or create functions';
30
+ if (path === '/disassemble_function')
31
+ return 'Read stored Listing instructions; for sparse/undefined code use functions decode';
32
+ if (path === '/get_functions')
33
+ return 'Inspect 1–20 functions; compact code/call edges: --fields decompiled_code,callees';
28
34
  if (path === '/find_dead_code')
29
35
  return 'Find blocks unreachable in the bounded static CFG; unresolved flow is reported';
30
36
  if (path === '/detect_crypto_constants')
@@ -39,6 +45,8 @@ export function queryDescription(path, fallback) {
39
45
  return 'Read external-entry metadata; report name/image-base candidates separately';
40
46
  if (path === '/list_globals')
41
47
  return 'Read structured global records with bounded symbol/reference scanning';
48
+ if (path === '/list_strings' || path === '/search_strings')
49
+ return 'Search defined string data only, with bounded visited-data scan; undefined literals: strings anchors';
42
50
  if (path.startsWith('/emulate_'))
43
51
  return 'Bounded isolated x86/x64 emulation with explicit ABI and termination reason';
44
52
  return heuristics[path] ? fallback + ' (qualified ' + heuristics[path].kind + '; not ground truth)' : fallback;
@@ -0,0 +1,6 @@
1
+ /** Strip transport versioning from the existing logical observation and its identity digest. */
2
+ export declare function researchView(raw: unknown): {
3
+ modification: string;
4
+ language: unknown;
5
+ compiler: unknown;
6
+ };
@@ -0,0 +1,9 @@
1
+ import { object, fail } from './model.js';
2
+ /** Strip transport versioning from the existing logical observation and its identity digest. */
3
+ export function researchView(raw) {
4
+ const r = object(raw);
5
+ if (r.protocol !== 'insight-research-view-v1' || typeof r.modification !== 'string' || !/^\d{1,20}$/.test(r.modification) ||
6
+ !['language', 'compiler'].every(k => typeof r[k] === 'string' && r[k].length > 0 && r[k].length <= 256 && !r[k].includes('\0')))
7
+ fail('partial', 'Native research-view observation is malformed or unsupported.');
8
+ return { modification: r.modification, language: r.language, compiler: r.compiler };
9
+ }
@@ -1,11 +1,18 @@
1
1
  import { type FunctionInspectionOptions } from './function-inspection.js';
2
+ import { type AnchorRequest } from './string-anchors.js';
2
3
  import type { Artifact } from './artifacts.js';
3
4
  import type { GhidraContext, Scope, ScalarChange, Publication, DebuggerState, GhidraLoadSettings } from './workspace.js';
4
5
  import { Workspace } from './workspace.js';
5
6
  import type { ToolMode } from './contexts.js';
6
7
  import type { ScriptPage } from './scripts.js';
8
+ import { type ReferenceRequest } from './code-references.js';
7
9
  export interface ResearchBackend {
10
+ inlineScript?(scope: Scope, parameters: Record<string, unknown>, requestId: string): Promise<{
11
+ response: Record<string, unknown>;
12
+ safe: boolean;
13
+ }>;
8
14
  observation?(scope: Scope): Promise<unknown>;
15
+ inspectObservation?(scope: Scope): Promise<unknown>;
9
16
  sdkFile?(operation: string, p: Record<string, unknown>): Promise<Record<string, unknown>>;
10
17
  archivePayloads?(scope: Scope, p: Record<string, unknown>): Promise<unknown>;
11
18
  debuggerStart?(scope: Scope): Promise<void>;
@@ -166,6 +173,36 @@ export declare class Research {
166
173
  maximumCharacters: number;
167
174
  returnedCharacters: number;
168
175
  };
176
+ boundaryLookup?: Record<string, unknown> | undefined;
177
+ };
178
+ session: string;
179
+ target: string;
180
+ }>;
181
+ stringAnchors(selected: Scope, request: AnchorRequest): Promise<{
182
+ value: {
183
+ view: {
184
+ identity: string;
185
+ };
186
+ cursor: string | null;
187
+ };
188
+ session: string;
189
+ target: string;
190
+ }>;
191
+ codeReferences(selected: Scope, request: ReferenceRequest): Promise<{
192
+ value: {
193
+ view: {
194
+ identity: string;
195
+ };
196
+ cursor: string | null;
197
+ };
198
+ session: string;
199
+ target: string;
200
+ }>;
201
+ navigation(selected: Scope, maximum: number): Promise<{
202
+ value: {
203
+ initialAnalysis: string;
204
+ priorAnalysis: string;
205
+ provenance: string;
169
206
  };
170
207
  session: string;
171
208
  target: string;
@@ -210,6 +247,7 @@ export declare class Research {
210
247
  expectedShared: string | null;
211
248
  }): Promise<unknown>;
212
249
  private sharedOrder;
250
+ reconcileRequests(selected: Scope, backend: ResearchBackend): Promise<void>;
213
251
  private reconcile;
214
252
  merge(selected: Scope, callerId?: string): Promise<unknown>;
215
253
  refresh(selected: Scope, callerId?: string): Promise<unknown>;
@@ -1,11 +1,16 @@
1
1
  import { textWindow } from './text-window.js';
2
2
  import { functionResult, functionFailure } from './function-results.js';
3
3
  import { inspectFunctions } from './function-inspection.js';
4
+ import { entryRequest } from './function-entries.js';
5
+ import { inspectAnchors } from './string-anchors.js';
4
6
  import { fail, id, object, text, digest } from './model.js';
5
7
  import { fieldKey, scalarMerge, scalarRefresh } from './merge.js';
6
8
  import { verifyMutation } from './mutation-effects.js';
7
9
  import { transferResult } from './cross-results.js';
8
10
  import { CliError } from '@eyeauras/cli-factory';
11
+ import { timed } from './internal-timing.js';
12
+ import { navigationResult } from './context-diagnostics.js';
13
+ import { inspectCodeReferences } from './code-references.js';
9
14
  export function address(value) {
10
15
  const input = text(value, 128);
11
16
  const match = /^(?:([A-Za-z0-9_]+):)?(?:0x)?([a-fA-F0-9]+)$/.exec(input);
@@ -66,7 +71,7 @@ export class Research {
66
71
  }
67
72
  async ordered(selected, action) {
68
73
  const key = this.workspace.context(selected).id, previous = this.tasks.get(key);
69
- const task = (previous ? previous.catch(() => { }) : Promise.resolve()).then(action);
74
+ const task = timed('facade.queue', 'research', async () => { await previous?.catch(() => { }); }).then(action);
70
75
  this.tasks.set(key, task);
71
76
  try {
72
77
  return await task;
@@ -77,7 +82,7 @@ export class Research {
77
82
  }
78
83
  }
79
84
  async boundary(selected, action) {
80
- const key = 'command:' + this.workspace.context(selected).id, previous = this.tasks.get(key), task = (previous ? previous.catch(() => { }) : Promise.resolve()).then(action);
85
+ const key = 'command:' + this.workspace.context(selected).id, previous = this.tasks.get(key), task = timed('facade.queue', 'command', async () => { await previous?.catch(() => { }); }).then(action);
81
86
  this.tasks.set(key, task);
82
87
  try {
83
88
  return await task;
@@ -147,10 +152,14 @@ export class Research {
147
152
  return opened(request.result);
148
153
  if (reserved.replay && request.state !== 'accepted')
149
154
  throw new CliError('This open retains an unproved outcome; inspect the retained context before continuing.', { code: 'coordinator.unknown', result: retained(request.state), next });
150
- if (this.workspace.unresolved(reserved.context).some(other => other.id !== request.id)) {
155
+ const unresolved = this.workspace.unresolved(reserved.context).filter(other => other.id !== request.id);
156
+ if (unresolved.length) {
151
157
  const result = { session: reserved.context.session, target, state: 'blocked' };
152
158
  this.workspace.recordRequest(request.id, 'failed', result);
153
- fail('blocked', 'This context retains an unproved effect; continuation did not start a second operation.', result);
159
+ const close = unresolved.find(other => other.operation === 'ghidra.close');
160
+ throw new CliError('This context retains an unproved effect; continuation did not start a second operation.', {
161
+ code: 'coordinator.blocked', result, next: close ? [['ghidra', 'close', '--session', reserved.context.session, '--target', target, '--request-id', close.callerId ?? close.id]] : next
162
+ });
154
163
  }
155
164
  this.workspace.startRequest(request.id);
156
165
  try {
@@ -292,13 +301,48 @@ export class Research {
292
301
  });
293
302
  }
294
303
  async inspectFunctions(selected, parameters, options) {
304
+ const lookup = entryRequest(parameters, options);
295
305
  return this.ordered(selected, async () => {
296
306
  const backend = await this.backend();
297
307
  await backend.open(selected);
298
308
  const context = this.workspace.context(selected);
299
- const observation = await backend.observation?.(selected);
309
+ const observe = backend.inspectObservation ? () => backend.inspectObservation(selected) : backend.observation ? () => backend.observation(selected) : undefined;
310
+ if (lookup && !observe)
311
+ fail('unsupported', 'Address resolution requires guarded native observations.');
312
+ const observation = await observe?.();
300
313
  const view = { revision: revision(context.baseRevision), identity: digest({ target: selected.target, context: context.id, generation: context.generation, snapshot: context.workingSnapshot, epoch: context.viewEpoch ?? 0, observation }) };
301
- return inspectFunctions(backend, selected, parameters, options, view, observation);
314
+ return inspectFunctions(backend, selected, parameters, options, view, observation, observe);
315
+ });
316
+ }
317
+ async stringAnchors(selected, request) {
318
+ return this.ordered(selected, async () => {
319
+ const backend = await this.backend();
320
+ await backend.open(selected);
321
+ const observe = backend.inspectObservation ? () => backend.inspectObservation(selected) : backend.observation ? () => backend.observation(selected) : undefined;
322
+ if (!observe)
323
+ fail('unsupported', 'String anchors require guarded native observations.');
324
+ const context = this.workspace.context(selected), observation = await observe();
325
+ const view = digest({ target: selected.target, context: context.id, generation: context.generation, snapshot: context.workingSnapshot, epoch: context.viewEpoch ?? 0, observation });
326
+ return inspectAnchors(backend, selected, request, view, observation, observe);
327
+ });
328
+ }
329
+ async codeReferences(selected, request) {
330
+ return this.ordered(selected, async () => {
331
+ const backend = await this.backend();
332
+ await backend.open(selected);
333
+ if (!backend.observation)
334
+ fail('unsupported', 'Code-reference continuation requires native view observation.');
335
+ const context = this.workspace.context(selected), observation = await backend.observation(selected);
336
+ const view = digest({ target: selected.target, context: context.id, generation: context.generation, snapshot: context.workingSnapshot, epoch: context.viewEpoch ?? 0, observation });
337
+ return inspectCodeReferences(backend, selected, request, view, observation);
338
+ });
339
+ }
340
+ async navigation(selected, maximum) {
341
+ return this.ordered(selected, async () => {
342
+ const backend = await this.backend();
343
+ await backend.open(selected);
344
+ const value = navigationResult(await backend.call(selected, '/navigation_diagnostic', { max_scan: maximum }), maximum);
345
+ return { ...selected, value: { ...value, initialAnalysis: this.workspace.model(selected.target).settings.autoAnalyze ? 'requested' : 'none', priorAnalysis: 'unknown', provenance: 'Initial facade policy only; imported/saved metadata may have an unknown analysis history.' } };
302
346
  });
303
347
  }
304
348
  async xrefs(selected, at, limit, offset) {
@@ -428,6 +472,9 @@ export class Research {
428
472
  this.tasks.delete(key);
429
473
  }
430
474
  }
475
+ async reconcileRequests(selected, backend) {
476
+ return this.sharedOrder(selected.target, () => this.reconcile(selected, backend));
477
+ }
431
478
  async reconcile(selected, backend) {
432
479
  const active = this.workspace.activePublication(selected.target);
433
480
  if (active) {
@@ -541,7 +588,7 @@ export class Research {
541
588
  if (!await backend.closed(selected))
542
589
  continue;
543
590
  const current = this.workspace.context(selected);
544
- this.workspace.completeSavedRequest(selected, request.id, current.generation, {}, { ...selected, state: 'closed', pendingChanges: count(current) });
591
+ this.workspace.completeSavedRequest(selected, request.id, current.generation, { state: 'stopped' }, { ...selected, state: 'closed', pendingChanges: count(current) });
545
592
  }
546
593
  catch { /* A stopped metadata flag alone never proves process release. */ }
547
594
  continue;
@@ -794,6 +841,10 @@ export class Research {
794
841
  if (reserved.replay && request.state !== 'accepted') {
795
842
  if (request.state === 'applied')
796
843
  return request.result;
844
+ if (operation === '/run_script_inline' && request.state === 'failed' && object(request.result ?? {}).reason === 'script_not_executed') {
845
+ const result = object(request.result), execution = object(result.execution);
846
+ fail('script.' + String(execution.stage), String(object(result.diagnostic).message), result);
847
+ }
797
848
  if (operation.startsWith('/sdk/') && request.state === 'failed' && object(request.result ?? {}).state === 'rejected')
798
849
  fail('rejected', 'This SDK transaction was rolled back and is not repeated.', request.result);
799
850
  fail('unknown', 'This saved-state operation retains an unresolved outcome; it is not re-executed.', request.result);
@@ -807,7 +858,24 @@ export class Research {
807
858
  savedVerification: !!verifySaved });
808
859
  let savedSnapshot, phase = 'operation';
809
860
  try {
810
- const raw = await backend.call(selected, operation, parameters), response = raw && typeof raw === 'object' && !Array.isArray(raw) ? object(raw) : { output: raw };
861
+ let raw;
862
+ if (operation === '/run_script_inline' && backend.inlineScript) {
863
+ const attempt = await backend.inlineScript(selected, parameters, request.id);
864
+ raw = attempt.response;
865
+ if (attempt.response.success === false) {
866
+ const result = { ...selected, requestId: request.callerId ?? request.id, state: attempt.safe ? 'rejected' : 'unknown',
867
+ contextState: attempt.safe ? 'ready' : 'needs_recovery', reason: attempt.safe ? 'script_not_executed' : 'script_execution_not_proven_complete',
868
+ execution: attempt.response.execution, diagnostic: attempt.response.diagnostic, diagnostics: attempt.response.diagnostics,
869
+ diagnosticsTruncated: attempt.response.diagnosticsTruncated, compilerOutput: attempt.response.compilerOutput,
870
+ output: attempt.response.console_output, outputTruncated: attempt.response.outputTruncated };
871
+ if (attempt.safe)
872
+ this.workspace.recordRequest(request.id, 'failed', result);
873
+ fail('script.' + String(object(attempt.response.execution).stage), String(object(attempt.response.diagnostic).message), result);
874
+ }
875
+ }
876
+ else
877
+ raw = await backend.call(selected, operation, parameters);
878
+ const response = raw && typeof raw === 'object' && !Array.isArray(raw) ? object(raw) : { output: raw };
811
879
  if (['rejected', 'partial', 'failed', 'error'].includes(String(response.status)) || response.success === false)
812
880
  fail('partial', 'Program operation reported a rejected or incomplete effect.');
813
881
  if (!verifySaved && response.success !== true && response.status !== 'success')
@@ -825,6 +893,8 @@ export class Research {
825
893
  return result;
826
894
  }
827
895
  catch (error) {
896
+ if (operation === '/run_script_inline' && this.workspace.request(request.id).state === 'failed' && object(this.workspace.request(request.id).result ?? {}).reason === 'script_not_executed')
897
+ throw error;
828
898
  if (operation.startsWith('/sdk/') && String(error.code).endsWith('.rejected')) {
829
899
  this.workspace.recordRequest(request.id, 'failed', { ...selected, state: 'rejected', reason: 'native_transaction_rolled_back' });
830
900
  throw error;
@@ -0,0 +1,43 @@
1
+ import { type CommandContext } from '@eyeauras/cli-factory';
2
+ import { type Research, type ResearchBackend } from './research.js';
3
+ import type { Scope } from './workspace.js';
4
+ export declare const anchorAlgorithm = "raw-string-anchors-v1";
5
+ export declare const anchorEncodings: readonly ["ascii", "utf16le"];
6
+ type Encoding = typeof anchorEncodings[number];
7
+ interface Frontier {
8
+ address: string;
9
+ encoding: Encoding;
10
+ }
11
+ interface Query {
12
+ text: string;
13
+ encodings: Encoding[];
14
+ start: string;
15
+ end: string;
16
+ cap: number;
17
+ }
18
+ interface Budgets {
19
+ max_bytes: number;
20
+ max_candidates: number;
21
+ timeout_seconds: number;
22
+ limit: number;
23
+ }
24
+ export interface AnchorRequest {
25
+ query: Query;
26
+ from: Frontier;
27
+ budgets: Budgets;
28
+ expectedView?: string;
29
+ }
30
+ export declare function anchorRequest(o: Record<string, unknown>, who: Scope): AnchorRequest;
31
+ export declare function anchorResult(raw: unknown, r: AnchorRequest, modification: string): Record<string, unknown>;
32
+ export declare function inspectAnchors(backend: ResearchBackend, who: Scope, r: AnchorRequest, view: string, observation: unknown, observe: () => Promise<unknown>): Promise<{
33
+ value: {
34
+ view: {
35
+ identity: string;
36
+ };
37
+ cursor: string | null;
38
+ };
39
+ session: string;
40
+ target: string;
41
+ }>;
42
+ export declare function stringAnchorCommands(research: (c: CommandContext) => Research): import("@eyeauras/cli-factory").CommandDefinition[];
43
+ export {};