@lazyingart/agintiflow 0.20.189 → 0.20.190

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.189",
3
+ "version": "0.20.190",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
6
6
  "license": "Apache-2.0",
@@ -0,0 +1,74 @@
1
+ # AgInTiFlow Intent Routing Overpatch Audit
2
+
3
+ Date: 2026-06-01
4
+
5
+ ## Problem
6
+
7
+ A bare user message, `hello`, was handled as an execution task. In the observed run, AgInTiFlow planned to inspect the workspace, created `hello.py`, and then stopped without a natural chat answer. That is the wrong default behavior for a general agent UI: a greeting should be answered directly, not converted into a workspace mutation.
8
+
9
+ ## Evidence
10
+
11
+ Observed session:
12
+
13
+ - Session: `/home/lachlan/.agintiflow/sessions/web-agent-f798c7cc-b3c2-439f-b2be-2a2746b4944d/state.json`
14
+ - Provider/model: `deepseek/deepseek-v4-flash`
15
+ - Goal: `hello`
16
+ - Task profile: `auto`
17
+ - Selected skills: `[]`
18
+ - Outcome shown by user: AgInTiFlow wrote `hello.py` with `print("Hello from agent!")`
19
+
20
+ The selected skills list was empty, so this was not caused by a custom skill. The behavior came from the core runtime prompt and the lack of a direct-answer lane.
21
+
22
+ ## Root Cause
23
+
24
+ The runtime had one dominant path:
25
+
26
+ 1. Create a plan.
27
+ 2. Continue with tools until completion.
28
+ 3. Prefer real workspace edits/checks over advice-only answers.
29
+
30
+ The strongest prompt pressure was in:
31
+
32
+ - `src/agent-runner.js`: system prompt said the plan is not final and the agent should actively use tools until complete.
33
+ - `src/model-client.js`: planner prompt said to prefer real workspace edits/checks over advice-only answers.
34
+ - `src/task-profiles.js`: the `auto` profile told the agent to infer task type and choose a tool mix, but did not define a no-tool conversational mode.
35
+
36
+ That design is useful for real tasks, but over-applies tool use to short social turns.
37
+
38
+ ## Design Fix
39
+
40
+ Add a deterministic pre-plan goal-intent classifier:
41
+
42
+ - Greetings, thanks, and explicit greeting requests finish directly.
43
+ - Explicit create/edit/run/read/search/download/build/test/coding/file/command requests remain agentic.
44
+ - File paths, file extensions, and command-looking inputs remain agentic.
45
+ - The direct path runs before planning, SCS, surgical context, snapshots, browser, shell, canvas, or file tools.
46
+
47
+ This keeps the fix general. It does not special-case `hello.py`, mock mode, DeepSeek, or a single UI path.
48
+
49
+ The prompts were also softened:
50
+
51
+ - Tool use is required only when the user actually asks for workspace/browser/shell/web/canvas/image/MCP/specialist work.
52
+ - Greetings and simple conversational turns should finish directly.
53
+ - Canvas should not be used for ordinary chat replies.
54
+
55
+ ## Regression Contract
56
+
57
+ Required behavior:
58
+
59
+ - `hello` returns a direct greeting.
60
+ - `hello` does not create a plan.
61
+ - `hello` does not call tools.
62
+ - `hello` does not create `hello.py`.
63
+ - `create hello.py` still requires tools.
64
+ - `write a hello-world Python script and run it` still requires tools.
65
+
66
+ ## Product Lesson
67
+
68
+ AgInTiFlow should solve problems at the correct layer:
69
+
70
+ - Core system flaw: no conversational/direct-answer lane. Fix in runtime intent routing.
71
+ - Core general skill incompleteness: missing reusable task classifier. Add deterministic classifier and tests.
72
+ - Custom skill issue: not applicable here because no skill was selected.
73
+
74
+ Avoid overpatching by not making every short input a file task and not routing every interaction through heavyweight planning/SCS/tool execution.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Date: 2026-05-20
4
4
 
5
- Updated: 2026-05-31
5
+ Updated: 2026-06-01
6
6
 
7
7
  This note records the working publication route for AgInTiFlow so future release work does not get blocked by stale local npm tokens or repeated discussion about why direct `npm publish` failed.
8
8
 
@@ -10,6 +10,17 @@ This note records the working publication route for AgInTiFlow so future release
10
10
 
11
11
  Latest verified release:
12
12
 
13
+ - Version: `0.20.189`
14
+ - Commit: `0095284`
15
+ - GitHub Actions run: `https://github.com/lazyingart/AgInTiFlow/actions/runs/26734901522`
16
+ - Workflow result: success
17
+ - npm registry check: `npm view @lazyingart/agintiflow version dist-tags.latest --registry=https://registry.npmjs.org` returned `0.20.189`.
18
+ - Installed verification: `npm install -g @lazyingart/agintiflow@0.20.189` then `aginti --version` returned `0.20.189`.
19
+ - Webapp verification: `aginti webapp restart --port 3210` then `curl -fsS http://127.0.0.1:3210/health` returned `version":"0.20.189"` from the global npm package path.
20
+ - Feature verification: `npm run smoke:model-roles` covered `OPENAI_BASE_URL`, provider-default reasoning, OpenAI chat `reasoning_effort`, and retry-without-reasoning for OpenAI-compatible gateways.
21
+
22
+ Previous verified release:
23
+
13
24
  - Version: `0.20.188`
14
25
  - Commit: `939e2ec`
15
26
  - GitHub Actions run: `https://github.com/lazyingart/AgInTiFlow/actions/runs/26705729544`
@@ -939,6 +939,23 @@ try {
939
939
  "surgical context message was not injected into model history"
940
940
  );
941
941
 
942
+ const helloRun = await runMock("hello", "coding-direct-hello");
943
+ assert(helloRun.result.result.includes("Hello"), "bare hello did not finish with a direct greeting");
944
+ assert(!helloRun.state?.plan, "bare hello should not create an execution plan");
945
+ assert(
946
+ helloRun.events.some((event) => event.type === "session.finished" && event.data?.mode === "direct-answer"),
947
+ "bare hello did not finish through the direct-answer path"
948
+ );
949
+ assert(
950
+ !helloRun.events.some((event) => event.type === "tool.started" || event.type === "tool.completed"),
951
+ "bare hello should not call tools"
952
+ );
953
+ const helloPyExists = await fs
954
+ .stat(path.join(workspace, "hello.py"))
955
+ .then(() => true)
956
+ .catch(() => false);
957
+ assert(!helloPyExists, "bare hello should not create hello.py");
958
+
942
959
  const writeRun = await runMock("Create notes/hello.md with a short coding smoke message.", "coding-write");
943
960
  const written = await fs.readFile(path.join(workspace, "notes/hello.md"), "utf8");
944
961
  assert(written.includes("Created by AgInTiFlow mock mode."), "mock write did not create expected file");
@@ -1214,6 +1231,7 @@ try {
1214
1231
  "scout_blackboard",
1215
1232
  "surgical_context_pack",
1216
1233
  "mock_inspect_project",
1234
+ "direct_greeting_no_tools",
1217
1235
  "write_file",
1218
1236
  "duplicate_write_failed",
1219
1237
  "resume_session_write",
@@ -23,6 +23,7 @@ import {
23
23
  } from "../src/scs-controller.js";
24
24
  import { buildScsEvidenceLedger, deriveScsTaskContract, evaluateScsEvidence } from "../src/scs-evidence.js";
25
25
  import { resolveRuntimeConfig } from "../src/config.js";
26
+ import { classifyGoalIntent, isDirectAnswerIntent } from "../src/goal-intent.js";
26
27
 
27
28
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
28
29
 
@@ -127,6 +128,14 @@ for (const [key, value] of Object.entries(envSnapshot)) {
127
128
  else process.env[key] = value;
128
129
  }
129
130
 
131
+ const helloIntent = classifyGoalIntent("hello");
132
+ assert(isDirectAnswerIntent(helloIntent), "bare hello should be classified as direct conversational intent");
133
+ assert(helloIntent.requiresTools === false, "bare hello should not require tools");
134
+ assert(classifyGoalIntent("你好").directAnswer.includes("你好"), "Chinese greeting should get a direct Chinese reply");
135
+ assert(classifyGoalIntent("say hello in Japanese").directAnswer.includes("こんにちは"), "Japanese greeting request should answer directly");
136
+ assert(classifyGoalIntent("create hello.py").requiresTools, "explicit file creation should remain agentic");
137
+ assert(classifyGoalIntent("write a hello-world Python script and run it").requiresTools, "explicit coding task should require tools");
138
+
130
139
  const complexRoute = selectModelRoute({
131
140
  routingMode: "complex",
132
141
  provider: "deepseek",
@@ -732,6 +741,7 @@ console.log(
732
741
  "openai-base-url",
733
742
  "provider-default-reasoning",
734
743
  "openai-chat-reasoning-payload",
744
+ "goal-intent-direct-answer",
735
745
  "route-overrides",
736
746
  "provider-groups",
737
747
  "auxiliary-catalog",
@@ -204,10 +204,11 @@ try {
204
204
  });
205
205
  await page.fill("#maxSteps", "4");
206
206
  await page.selectOption("#dynamicSteps", "off");
207
- await page.fill("#chat-input", "Say hello from the web UI composer in one concise sentence.");
207
+ const composerGoal = "Create notes/web-ui-smoke.md with one concise sentence from the web UI composer.";
208
+ await page.fill("#chat-input", composerGoal);
208
209
  await page.click("#chat-submit");
209
210
  const firstPayload = JSON.parse(runPayloads.at(-1) || "{}");
210
- if (firstPayload.goal !== "Say hello from the web UI composer in one concise sentence.") {
211
+ if (firstPayload.goal !== composerGoal) {
211
212
  throw new Error("composer text was not used as the first run goal");
212
213
  }
213
214
  if (firstPayload.enableScs !== "off" || firstPayload.taskProfile !== "auto") {
@@ -42,6 +42,7 @@ import { summarizeMcpConfig } from "./mcp/config.js";
42
42
  import { isMcpBridgeTool } from "./mcp/policy.js";
43
43
  import { executeMcpBridgeTool } from "./mcp/tool-bridge.js";
44
44
  import { longJobStatus, startLongJob } from "./long-job-tools.js";
45
+ import { classifyGoalIntent, isDirectAnswerIntent } from "./goal-intent.js";
45
46
  import {
46
47
  DEFAULT_SCS_MODE,
47
48
  buildSupervisorInstruction,
@@ -589,11 +590,11 @@ async function createInitialState(config, sessionId) {
589
590
  content: [
590
591
  "You are a careful browser and shell agent with a small tool surface.",
591
592
  "Use only the provided tools.",
592
- "The execution plan is not the final answer. After planning, actively use tools until the requested task is complete or genuinely blocked.",
593
+ "The execution plan is not the final answer. After planning, actively use tools until the requested task is complete or genuinely blocked. If the user only sends a greeting, thanks, or another simple conversational turn, finish directly without creating files, running shell commands, opening browsers, or sending canvas artifacts.",
593
594
  "If the shell tool can satisfy a local task, prefer it before opening a browser.",
594
595
  "Do not open a browser page just because a start URL exists. Treat it as a suggestion only.",
595
596
  "Only reference element ids from the latest browser snapshot.",
596
- "Prefer short, deliberate actions over guessing.",
597
+ "Prefer short, deliberate actions over guessing. Use tools only when the request actually needs workspace, browser, shell, web, canvas, image, MCP, or specialist work.",
597
598
  browserStateReconciliationGuidance(),
598
599
  "Never navigate outside the allowed domains when an allowlist exists.",
599
600
  "Avoid destructive actions, purchases, account changes, and sensitive workflows.",
@@ -645,7 +646,7 @@ async function createInitialState(config, sessionId) {
645
646
  `Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
646
647
  skillContext,
647
648
  engineeringGuidance,
648
- "A frontend canvas/artifacts tunnel exists. Use send_to_canvas when important markdown, diffs, screenshots, images, or workspace files should be highlighted in the UI. File paths sent to canvas are copied into session artifacts for durable preview, but user-requested outputs should also remain in a clear workspace path unless the user asked only for a temporary preview.",
649
+ "A frontend canvas/artifacts tunnel exists. Use send_to_canvas when important markdown, diffs, screenshots, images, or workspace files should be highlighted in the UI. File paths sent to canvas are copied into session artifacts for durable preview, but user-requested outputs should also remain in a clear workspace path unless the user asked only for a temporary preview. Do not use canvas for ordinary greetings or short chat replies.",
649
650
  "For visual-output requests such as draw, plot, graph, chart, diagram, figure, image, or visualization, proactively publish a canvas artifact even when the user does not mention canvas. If workspace file tools are enabled, prefer creating a small SVG or markdown artifact and call send_to_canvas with selected=true.",
650
651
  "Work like a practical coding agent: orient with inspect_project/search/read, patch code with apply_patch, run safe checks when they add confidence, iterate on failures, and keep outputs inside the workspace.",
651
652
  "For large projects, decompose into useful files and milestones, identify entry points/tests/contracts first, implement a coherent minimal version, then iterate with checks rather than only describing what you would do.",
@@ -1107,6 +1108,40 @@ function appendChatEntry(state, role, content) {
1107
1108
  });
1108
1109
  }
1109
1110
 
1111
+ async function finishWithDirectAnswer({ config, state, store, observers, sessionId, intent }) {
1112
+ const result = redactSensitiveText(intent.directAnswer || "I'm here. How can I help?");
1113
+ state.meta = state.meta || {};
1114
+ state.meta.goalIntent = intent;
1115
+ state.updatedAt = new Date().toISOString();
1116
+ state.messages.push({
1117
+ role: "assistant",
1118
+ content: result,
1119
+ });
1120
+ appendChatEntry(state, "assistant", result);
1121
+ await store.saveState(state);
1122
+ await store.appendEvent("intent.classified", intent);
1123
+ await store.appendEvent("session.finished", {
1124
+ result,
1125
+ mode: "direct-answer",
1126
+ intent: {
1127
+ kind: intent.kind,
1128
+ reason: intent.reason,
1129
+ requiresTools: intent.requiresTools,
1130
+ },
1131
+ });
1132
+ observers.event("intent.classified", intent);
1133
+ observers.event("session.finished", {
1134
+ result,
1135
+ sessionId,
1136
+ mode: "direct-answer",
1137
+ });
1138
+ emitConsole(config, result, { kind: "assistant", markdown: true });
1139
+ return {
1140
+ sessionId,
1141
+ result,
1142
+ };
1143
+ }
1144
+
1110
1145
  async function applyContinuationPrompt(state, config, observers) {
1111
1146
  if (!config.resume || !config.goal) return;
1112
1147
 
@@ -2360,6 +2395,16 @@ export async function runAgent(config) {
2360
2395
 
2361
2396
  try {
2362
2397
  throwIfAborted(config);
2398
+ const goalIntent = classifyGoalIntent(config.goal);
2399
+ const canFinishDirectly =
2400
+ !config.resume && !state.plan && Number(state.stepsCompleted || 0) === 0 && isDirectAnswerIntent(goalIntent);
2401
+ if (canFinishDirectly) {
2402
+ return await finishWithDirectAnswer({ config, state, store, observers, sessionId, intent: goalIntent });
2403
+ }
2404
+
2405
+ state.meta = state.meta || {};
2406
+ state.meta.goalIntent = goalIntent;
2407
+
2363
2408
  const scoutsWillRun = shouldRunParallelScouts(config, state);
2364
2409
  if (!scoutsWillRun) {
2365
2410
  await maybePrepareSurgicalContext(config, state, store, observers);
@@ -0,0 +1,101 @@
1
+ const GREETING_PATTERNS = [
2
+ /^(hi|hello|hey|hiya|yo|sup|howdy|good\s+(morning|afternoon|evening))[\s!.。!]*$/i,
3
+ /^(你好|您好|嗨|哈喽|哈啰|早上好|下午好|晚上好)[\s!.。!]*$/i,
4
+ /^(こんにちは|こんばんは|おはよう|おはようございます|やあ|もしもし)[\s!.。!]*$/i,
5
+ /^(안녕하세요|안녕|여보세요)[\s!.。!]*$/i,
6
+ /^(bonjour|salut|hola|hallo|ciao|olá|ola|namaste)[\s!.。!]*$/i,
7
+ ];
8
+
9
+ const THANKS_PATTERNS = [
10
+ /^(thanks|thank\s+you|thx|ty|much\s+appreciated)[\s!.。!]*$/i,
11
+ /^(谢谢|多谢|感谢|辛苦了)[\s!.。!]*$/i,
12
+ /^(ありがとう|ありがとうございます|助かりました)[\s!.。!]*$/i,
13
+ ];
14
+
15
+ const TOOL_INTENT_RE =
16
+ /\b(create|write|edit|update|fix|patch|modify|delete|remove|rename|move|copy|save|generate|draw|plot|build|compile|run|execute|test|debug|install|download|upload|clone|commit|push|publish|deploy|open|browse|search|read|inspect|analyze|analyse|summarize|summarise|convert|extract|classify|compare|review)\b/i;
17
+
18
+ const FILE_OR_COMMAND_HINT_RE =
19
+ /(^|\s)(\.{0,2}\/|~\/|[A-Za-z]:\\|[\w.-]+\.(js|ts|jsx|tsx|py|md|tex|json|csv|tsv|txt|yaml|yml|html|css|svg|png|jpg|jpeg|pdf|zip|tar|gz|sh|bash|zsh|rs|go|java|c|cpp|h|hpp|sql)\b|npm\s|node\s|python\s|pip\s|git\s|curl\s|wget\s|docker\s|tmux\s)/i;
20
+
21
+ function normalizedGoal(goal = "") {
22
+ return String(goal || "")
23
+ .replace(/\s+/g, " ")
24
+ .trim();
25
+ }
26
+
27
+ function directGreetingAnswer(goal) {
28
+ const text = normalizedGoal(goal);
29
+ if (/谢谢|多谢|感谢|辛苦了/.test(text)) return "不用谢。";
30
+ if (/ありがとう|ありがとうございます|助かりました/.test(text)) return "どういたしまして。";
31
+ if (/안녕|감사/.test(text)) return "천만에요.";
32
+ if (/你好|您好|嗨|哈喽|哈啰|早上好|下午好|晚上好/.test(text)) return "你好,我在。";
33
+ if (/こんにちは|こんばんは|おはよう|もしもし/.test(text)) return "こんにちは。";
34
+ if (/bonjour|salut/i.test(text)) return "Bonjour.";
35
+ if (/hola/i.test(text)) return "Hola.";
36
+ return "Hello. How can I help?";
37
+ }
38
+
39
+ function looksLikeDirectGreetingRequest(text) {
40
+ return /^(say|reply|respond)\s+(hi|hello|hey)\b/i.test(text) && !TOOL_INTENT_RE.test(text.replace(/^(say|reply|respond)\s+/i, ""));
41
+ }
42
+
43
+ export function classifyGoalIntent(goal, options = {}) {
44
+ const text = normalizedGoal(goal);
45
+ if (!text) {
46
+ return {
47
+ kind: "empty",
48
+ requiresTools: false,
49
+ directAnswer: "",
50
+ reason: "empty-input",
51
+ };
52
+ }
53
+
54
+ if (FILE_OR_COMMAND_HINT_RE.test(text) || TOOL_INTENT_RE.test(text)) {
55
+ return {
56
+ kind: "agentic",
57
+ requiresTools: true,
58
+ directAnswer: "",
59
+ reason: FILE_OR_COMMAND_HINT_RE.test(text) ? "file-or-command-hint" : "tool-intent-keyword",
60
+ };
61
+ }
62
+
63
+ if (GREETING_PATTERNS.some((pattern) => pattern.test(text)) || THANKS_PATTERNS.some((pattern) => pattern.test(text))) {
64
+ return {
65
+ kind: "casual",
66
+ requiresTools: false,
67
+ directAnswer: directGreetingAnswer(text),
68
+ reason: "short-social-turn",
69
+ };
70
+ }
71
+
72
+ if (looksLikeDirectGreetingRequest(text)) {
73
+ return {
74
+ kind: "direct-answer",
75
+ requiresTools: false,
76
+ directAnswer: /japanese|日本語|日语|日文/i.test(text) ? "こんにちは。" : directGreetingAnswer(text),
77
+ reason: "explicit-greeting-answer",
78
+ };
79
+ }
80
+
81
+ const shortQuestion = text.length <= 160 && /[??]$/.test(text);
82
+ if (shortQuestion && options.allowQuestionDirectAnswers) {
83
+ return {
84
+ kind: "direct-answer",
85
+ requiresTools: false,
86
+ directAnswer: "",
87
+ reason: "short-question",
88
+ };
89
+ }
90
+
91
+ return {
92
+ kind: "agentic",
93
+ requiresTools: true,
94
+ directAnswer: "",
95
+ reason: "default-agentic",
96
+ };
97
+ }
98
+
99
+ export function isDirectAnswerIntent(intent = {}) {
100
+ return Boolean(intent && intent.requiresTools === false && (intent.kind === "casual" || intent.kind === "direct-answer"));
101
+ }
@@ -742,7 +742,7 @@ export async function createPlan(client, config, state) {
742
742
  {
743
743
  role: "system",
744
744
  content:
745
- `You are planning a browser, shell, workspace, and coding-agent task. The plan is only a launchpad: after planning, the runtime will continue with tools until the task is complete or genuinely blocked. Prefer real workspace edits/checks over advice-only answers. If a local shell command can satisfy the goal, prefer that before browser actions. Treat any suggested start URL as optional. ${browserStateReconciliationGuidance()} ${formatBehaviorContractForPrompt({ mode: "plan" })} Write a concise execution plan with 3 to 6 steps. Mention risks or blockers when relevant. Keep it short and practical.`,
745
+ `You are planning a browser, shell, workspace, and coding-agent task. The plan is only a launchpad: after planning, the runtime will continue with tools until the task is complete or genuinely blocked. Use tools only when the user actually asks for workspace, browser, shell, web, canvas, image, MCP, or specialist work. For greetings, thanks, or simple conversational turns, finish directly; never invent file creation or shell work for them. Prefer real workspace edits/checks over advice-only answers only when the request is an explicit task or deliverable. If a local shell command can satisfy a local task, prefer that before browser actions. Treat any suggested start URL as optional. ${browserStateReconciliationGuidance()} ${formatBehaviorContractForPrompt({ mode: "plan" })} Write a concise execution plan with 3 to 6 steps only for requests that need execution. Mention risks or blockers when relevant. Keep it short and practical.`,
746
746
  },
747
747
  {
748
748
  role: "user",
@@ -3,7 +3,7 @@ export const TASK_PROFILES = {
3
3
  id: "auto",
4
4
  label: "Auto",
5
5
  prompt:
6
- "Act as the general-purpose AgInTiFlow agent. Infer the task type from the request, then choose the right mix of browser, shell, files, web search, canvas, and sandbox tools. For short tasks, use the smallest safe tool sequence that completes the work. For browser form/composer workflows, reconcile toward the requested state: inspect, set missing controls, verify, then submit when requested. For codebase, writing, website, LaTeX, system, debugging, migration, data cleanup, reporting, or multi-language tasks, automatically borrow the relevant specialized profile habits without becoming narrowly constrained. When you create or repair a deliverable, verify the produced output and polish obvious noise, duplicates, broken formatting, stale filenames, or misleading summaries before finalizing.",
6
+ "Act as the general-purpose AgInTiFlow agent. Infer the task type from the request, then choose the right mix of browser, shell, files, web search, canvas, and sandbox tools only when tools are actually needed. For greetings, thanks, or simple conversational turns, answer directly without creating files, running commands, opening browsers, or sending canvas artifacts. For short explicit tasks, use the smallest safe tool sequence that completes the work. For browser form/composer workflows, reconcile toward the requested state: inspect, set missing controls, verify, then submit when requested. For codebase, writing, website, LaTeX, system, debugging, migration, data cleanup, reporting, or multi-language tasks, automatically borrow the relevant specialized profile habits without becoming narrowly constrained. When you create or repair a deliverable, verify the produced output and polish obvious noise, duplicates, broken formatting, stale filenames, or misleading summaries before finalizing.",
7
7
  tools: ["browser", "shell", "files", "canvas", "inspect_project"],
8
8
  },
9
9
  code: {