@hiai-gg/hiai-opencode 0.1.5 → 0.1.7
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/.env.example +21 -8
- package/AGENTS.md +60 -6
- package/ARCHITECTURE.md +6 -3
- package/LICENSE.md +0 -1
- package/README.md +113 -33
- package/assets/cli/hiai-opencode.mjs +668 -7
- package/assets/mcp/mempalace.mjs +159 -25
- package/config/hiai-opencode.schema.json +29 -3
- package/dist/agents/agent-skills.d.ts +7 -0
- package/dist/agents/bob/default.d.ts +1 -0
- package/dist/agents/bob/gemini.d.ts +1 -0
- package/dist/agents/bob/gpt-pro.d.ts +1 -0
- package/dist/agents/brainstormer.d.ts +7 -0
- package/dist/agents/coder/gpt-codex.d.ts +1 -1
- package/dist/agents/coder/gpt-pro.d.ts +1 -0
- package/dist/agents/coder/gpt.d.ts +2 -1
- package/dist/agents/designer.d.ts +7 -0
- package/dist/agents/dynamic-agent-core-sections.d.ts +4 -1
- package/dist/agents/dynamic-agent-prompt-builder.d.ts +1 -1
- package/dist/agents/strategist/gemini.d.ts +1 -0
- package/dist/agents/strategist/gpt.d.ts +1 -0
- package/dist/agents/types.d.ts +3 -1
- package/dist/config/index.d.ts +0 -1
- package/dist/config/platform-schema.d.ts +34 -6
- package/dist/config/schema/commands.d.ts +1 -0
- package/dist/config/schema/hooks.d.ts +0 -2
- package/dist/config/schema/index.d.ts +0 -2
- package/dist/config/schema/oh-my-opencode-config.d.ts +1 -9
- package/dist/config/types.d.ts +4 -4
- package/dist/create-hooks.d.ts +0 -2
- package/dist/features/builtin-commands/templates/doctor.d.ts +1 -0
- package/dist/features/builtin-commands/types.d.ts +1 -1
- package/dist/features/builtin-skills/skills/hiai-opencode-setup.d.ts +2 -0
- package/dist/features/builtin-skills/skills/index.d.ts +2 -0
- package/dist/features/builtin-skills/skills/website-copywriting.d.ts +2 -0
- package/dist/hooks/agent-usage-reminder/constants.d.ts +1 -1
- package/dist/hooks/index.d.ts +0 -2
- package/dist/hooks/keyword-detector/ultrawork/default.d.ts +1 -1
- package/dist/hooks/keyword-detector/ultrawork/gemini.d.ts +1 -1
- package/dist/hooks/keyword-detector/ultrawork/gpt.d.ts +1 -1
- package/dist/hooks/keyword-detector/ultrawork/planner.d.ts +1 -1
- package/dist/index.js +7719 -153698
- package/dist/mcp/index.d.ts +0 -1
- package/dist/mcp/registry.d.ts +1 -1
- package/dist/plugin/hooks/create-core-hooks.d.ts +0 -2
- package/dist/plugin/hooks/create-session-hooks.d.ts +1 -3
- package/dist/plugin/startup-diagnostics.d.ts +1 -0
- package/dist/shared/logger.d.ts +2 -0
- package/dist/shared/mcp-static-export.d.ts +22 -0
- package/dist/shared/mode-routing.d.ts +6 -0
- package/dist/tools/ast-grep/constants.d.ts +1 -1
- package/dist/tools/ast-grep/environment-check.d.ts +1 -5
- package/dist/tools/ast-grep/language-support.d.ts +0 -1
- package/dist/tools/ast-grep/types.d.ts +1 -2
- package/dist/tools/delegate-task/git-categories.d.ts +2 -0
- package/dist/tools/delegate-task/sub-agent.d.ts +2 -0
- package/dist/tools/skill-mcp/constants.d.ts +1 -1
- package/hiai-opencode.json +50 -19
- package/package.json +10 -5
- package/src/agents/agent-skills.ts +70 -0
- package/src/agents/bob/default.ts +7 -1
- package/src/agents/bob/gemini.ts +1 -0
- package/src/agents/bob/gpt-pro.ts +3 -1
- package/src/agents/bob.ts +3 -0
- package/src/agents/brainstormer.ts +72 -0
- package/src/agents/builtin-agents.ts +59 -3
- package/src/agents/coder/gpt-codex.ts +5 -3
- package/src/agents/coder/gpt-pro.ts +4 -2
- package/src/agents/coder/gpt.ts +3 -1
- package/src/agents/critic/agent.ts +1 -0
- package/src/agents/designer.ts +70 -0
- package/src/agents/dynamic-agent-category-skills-guide.ts +6 -0
- package/src/agents/dynamic-agent-core-sections.ts +36 -0
- package/src/agents/dynamic-agent-prompt-builder.ts +1 -0
- package/src/agents/guard/default.ts +1 -0
- package/src/agents/guard/gemini.ts +1 -0
- package/src/agents/guard/gpt.ts +1 -0
- package/src/agents/platform-manager.ts +17 -1
- package/src/agents/prompt-library/platform.ts +34 -0
- package/src/agents/researcher.ts +1 -0
- package/src/agents/strategist/gemini.ts +1 -0
- package/src/agents/strategist/gpt.ts +1 -0
- package/src/agents/types.ts +4 -1
- package/src/agents/ui.ts +1 -0
- package/src/config/defaults.ts +45 -13
- package/src/config/index.ts +0 -1
- package/src/config/model-slots-and-export.test.ts +73 -0
- package/src/config/platform-schema.ts +3 -3
- package/src/config/schema/commands.ts +1 -0
- package/src/config/schema/hooks.ts +0 -2
- package/src/config/schema/index.ts +0 -2
- package/src/config/schema/oh-my-opencode-config.ts +0 -5
- package/src/config/types.ts +4 -4
- package/src/features/builtin-commands/commands.ts +7 -0
- package/src/features/builtin-commands/templates/doctor.ts +43 -0
- package/src/features/builtin-commands/types.ts +1 -1
- package/src/features/builtin-skills/skills/hiai-opencode-setup.ts +69 -0
- package/src/features/builtin-skills/skills/index.ts +2 -0
- package/src/features/builtin-skills/skills/website-copywriting.ts +41 -0
- package/src/features/builtin-skills/skills.test.ts +8 -0
- package/src/features/builtin-skills/skills.ts +12 -1
- package/src/features/skill-mcp-manager/AGENTS.md +1 -1
- package/src/hooks/agent-usage-reminder/constants.ts +4 -4
- package/src/hooks/index.ts +0 -2
- package/src/hooks/keyword-detector/ultrawork/default.ts +18 -18
- package/src/hooks/keyword-detector/ultrawork/gemini.ts +21 -21
- package/src/hooks/keyword-detector/ultrawork/gpt.ts +6 -8
- package/src/hooks/keyword-detector/ultrawork/planner.ts +5 -5
- package/src/index.ts +8 -78
- package/src/internals/plugins/subtask2/commands/manifest.ts +2 -6
- package/src/internals/plugins/subtask2/hooks/command-hooks.ts +2 -2
- package/src/internals/plugins/subtask2/hooks/message-hooks.ts +1 -1
- package/src/internals/plugins/subtask2/parsing/parallel.ts +13 -10
- package/src/mcp/index.ts +0 -1
- package/src/mcp/registry.ts +27 -0
- package/src/plugin/chat-message.ts +0 -2
- package/src/plugin/hooks/create-session-hooks.ts +0 -17
- package/src/plugin/startup-diagnostics.ts +27 -0
- package/src/plugin-handlers/agent-config-handler.ts +3 -2
- package/src/plugin-handlers/mcp-config-handler.test.ts +63 -0
- package/src/plugin-handlers/mcp-config-handler.ts +29 -14
- package/src/plugin-handlers/strategist-agent-config-builder.ts +1 -1
- package/src/shared/agent-display-names.test.ts +9 -0
- package/src/shared/agent-display-names.ts +5 -0
- package/src/shared/log-legacy-plugin-startup-warning.ts +6 -8
- package/src/shared/logger.ts +8 -0
- package/src/shared/mcp-static-export.ts +119 -0
- package/src/shared/migration/agent-names.ts +8 -0
- package/src/shared/migration/hook-names.ts +1 -1
- package/src/shared/mode-routing.test.ts +88 -0
- package/src/shared/mode-routing.ts +30 -0
- package/src/shared/startup-diagnostics.ts +6 -7
- package/src/tools/ast-grep/constants.ts +1 -1
- package/src/tools/ast-grep/environment-check.ts +2 -32
- package/src/tools/ast-grep/language-support.ts +0 -3
- package/src/tools/ast-grep/types.ts +1 -2
- package/src/tools/call-omo-agent/tools.ts +11 -4
- package/src/tools/delegate-task/anthropic-categories.ts +3 -3
- package/src/tools/delegate-task/builtin-categories.ts +2 -0
- package/src/tools/delegate-task/categories.test.ts +87 -0
- package/src/tools/delegate-task/category-resolver.ts +8 -9
- package/src/tools/delegate-task/git-categories.ts +30 -0
- package/src/tools/delegate-task/model-string-parser.test.ts +90 -0
- package/src/tools/delegate-task/openai-categories.ts +26 -22
- package/src/tools/delegate-task/sub-agent.ts +10 -0
- package/src/tools/delegate-task/subagent-discovery.test.ts +123 -0
- package/src/tools/delegate-task/subagent-resolver.ts +18 -1
- package/src/tools/skill-mcp/constants.ts +1 -1
- package/src/tools/skill-mcp/tools.test.ts +44 -0
- package/dist/ast-grep-napi.win32-x64-msvc-67c0y8nc.node +0 -0
- package/dist/config/loader.test.d.ts +0 -1
- package/dist/config/models.d.ts +0 -13
- package/dist/config/schema/websearch.d.ts +0 -13
- package/dist/hooks/no-bob-gpt/hook.d.ts +0 -16
- package/dist/hooks/no-bob-gpt/index.d.ts +0 -1
- package/dist/hooks/no-coder-non-gpt/hook.d.ts +0 -20
- package/dist/hooks/no-coder-non-gpt/index.d.ts +0 -1
- package/dist/internals/plugins/websearch-cited/google.d.ts +0 -38
- package/dist/internals/plugins/websearch-cited/index.d.ts +0 -17
- package/dist/internals/plugins/websearch-cited/openai.d.ts +0 -9
- package/dist/internals/plugins/websearch-cited/openrouter.d.ts +0 -2
- package/dist/internals/plugins/websearch-cited/types.d.ts +0 -5
- package/dist/mcp/grep-app.d.ts +0 -6
- package/dist/mcp/omo-mcp-index.d.ts +0 -10
- package/dist/mcp/websearch.d.ts +0 -11
- package/src/config/schema/websearch.ts +0 -15
- package/src/hooks/no-bob-gpt/hook.ts +0 -56
- package/src/hooks/no-bob-gpt/index.ts +0 -1
- package/src/hooks/no-coder-non-gpt/hook.ts +0 -67
- package/src/hooks/no-coder-non-gpt/index.ts +0 -1
- package/src/internals/plugins/websearch-cited/LICENSE +0 -214
- package/src/internals/plugins/websearch-cited/codex_prompt.txt +0 -79
- package/src/internals/plugins/websearch-cited/google.ts +0 -749
- package/src/internals/plugins/websearch-cited/index.ts +0 -306
- package/src/internals/plugins/websearch-cited/openai.ts +0 -407
- package/src/internals/plugins/websearch-cited/openrouter.ts +0 -190
- package/src/internals/plugins/websearch-cited/types.ts +0 -7
- package/src/mcp/grep-app.ts +0 -6
- package/src/mcp/omo-mcp-index.ts +0 -30
- package/src/mcp/websearch.ts +0 -44
package/src/index.ts
CHANGED
|
@@ -14,26 +14,20 @@ import { createPluginDispose, type PluginDispose } from "./plugin-dispose"
|
|
|
14
14
|
import { loadPluginConfig } from "./plugin-config"
|
|
15
15
|
import { createModelCacheState } from "./plugin-state"
|
|
16
16
|
import { createFirstMessageVariantGate } from "./shared/first-message-variant"
|
|
17
|
-
import { injectServerAuthIntoClient, log } from "./shared"
|
|
17
|
+
import { injectServerAuthIntoClient, log, logWarn, logError } from "./shared"
|
|
18
18
|
import { hydratePluginConfigWithPlatformDefaults } from "./shared/runtime-plugin-config"
|
|
19
19
|
import { detectExternalSkillPlugin, getSkillPluginConflictWarning } from "./shared/external-plugin-detector"
|
|
20
20
|
import { PLUGIN_NAME } from "./shared/plugin-identity"
|
|
21
|
+
import { autoExportStaticMcpJson } from "./shared/mcp-static-export"
|
|
21
22
|
import { warnIfListPluginEntry, warnMissingRequiredMcpEnv } from "./shared/startup-diagnostics"
|
|
23
|
+
import { lintModeAgentCapabilities } from "./plugin/startup-diagnostics"
|
|
22
24
|
import { startBackgroundCheck as startTmuxCheck } from "./tools/interactive-bash"
|
|
23
25
|
import { lspManager } from "./tools/lsp/client"
|
|
24
26
|
|
|
25
|
-
import { loadConfig
|
|
27
|
+
import { loadConfig } from "./config/loader"
|
|
26
28
|
import type { HiaiOpencodeConfig } from "./config/types"
|
|
27
29
|
|
|
28
30
|
import { createPlugin as createSubtask2Plugin } from "./internals/plugins/subtask2/core/plugin"
|
|
29
|
-
import WebsearchCitedPlugin, {
|
|
30
|
-
GOOGLE_PROVIDER_ID,
|
|
31
|
-
OPENAI_PROVIDER_ID,
|
|
32
|
-
OPENROUTER_PROVIDER_ID,
|
|
33
|
-
WebsearchCitedGooglePlugin,
|
|
34
|
-
WebsearchCitedOpenAIPlugin
|
|
35
|
-
} from "./internals/plugins/websearch-cited/index"
|
|
36
|
-
import type { WebsearchCitedFallback } from "./internals/plugins/websearch-cited/index"
|
|
37
31
|
import { createBuiltinSkills } from "./features/builtin-skills"
|
|
38
32
|
import {
|
|
39
33
|
materializeBuiltinSkills,
|
|
@@ -42,39 +36,6 @@ import {
|
|
|
42
36
|
|
|
43
37
|
let activePluginDispose: PluginDispose | null = null
|
|
44
38
|
|
|
45
|
-
function createWebsearchFallback(config: HiaiOpencodeConfig): WebsearchCitedFallback | undefined {
|
|
46
|
-
const model = config.agents?.researcher?.model?.trim()
|
|
47
|
-
if (!model) {
|
|
48
|
-
return undefined
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (model.startsWith("openrouter/")) {
|
|
52
|
-
return {
|
|
53
|
-
providerID: OPENROUTER_PROVIDER_ID,
|
|
54
|
-
model: model.slice("openrouter/".length),
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (model.startsWith("openai/")) {
|
|
59
|
-
return {
|
|
60
|
-
providerID: OPENAI_PROVIDER_ID,
|
|
61
|
-
model: model.slice("openai/".length),
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (model.startsWith("google/")) {
|
|
66
|
-
return {
|
|
67
|
-
providerID: GOOGLE_PROVIDER_ID,
|
|
68
|
-
model: model.slice("google/".length),
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
providerID: OPENROUTER_PROVIDER_ID,
|
|
74
|
-
model,
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
39
|
function configureBundledBunPtyLibrary(): void {
|
|
79
40
|
if (process.env.BUN_PTY_LIB?.trim()) {
|
|
80
41
|
return
|
|
@@ -111,7 +72,7 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
111
72
|
|
|
112
73
|
const skillPluginCheck = detectExternalSkillPlugin(ctx.directory)
|
|
113
74
|
if (skillPluginCheck.detected && skillPluginCheck.pluginName) {
|
|
114
|
-
|
|
75
|
+
logWarn(getSkillPluginConflictWarning(skillPluginCheck.pluginName))
|
|
115
76
|
}
|
|
116
77
|
|
|
117
78
|
injectServerAuthIntoClient(ctx.client)
|
|
@@ -126,6 +87,8 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
126
87
|
pluginConfig,
|
|
127
88
|
platformConfig: internalConfig,
|
|
128
89
|
})
|
|
90
|
+
lintModeAgentCapabilities()
|
|
91
|
+
autoExportStaticMcpJson(ctx.directory, internalConfig)
|
|
129
92
|
|
|
130
93
|
materializeBuiltinSkills(
|
|
131
94
|
createBuiltinSkills({
|
|
@@ -205,12 +168,8 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
205
168
|
const mod = await import("./internals/plugins/pty/plugin");
|
|
206
169
|
ptyResult = await mod.PTYPlugin(ctx);
|
|
207
170
|
} catch (err) {
|
|
208
|
-
|
|
171
|
+
logError("PTYPlugin failed to load:", err)
|
|
209
172
|
}
|
|
210
|
-
const websearchResult = await WebsearchCitedPlugin(ctx, createWebsearchFallback(internalConfig))
|
|
211
|
-
const websearchGoogleResult = await WebsearchCitedGooglePlugin(ctx)
|
|
212
|
-
const websearchOpenAIResult = await WebsearchCitedOpenAIPlugin(ctx)
|
|
213
|
-
|
|
214
173
|
const combinedResult = {
|
|
215
174
|
name: PLUGIN_NAME,
|
|
216
175
|
...pluginInterface,
|
|
@@ -219,7 +178,6 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
219
178
|
tool: {
|
|
220
179
|
...pluginInterface.tool,
|
|
221
180
|
...ptyResult.tool,
|
|
222
|
-
...websearchResult.tool,
|
|
223
181
|
},
|
|
224
182
|
|
|
225
183
|
// Chain hooks: command.execute.before
|
|
@@ -250,7 +208,6 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
250
208
|
config: async (input: any) => {
|
|
251
209
|
await pluginInterface.config?.(input);
|
|
252
210
|
await (subtask2Result as any).config?.(input);
|
|
253
|
-
await (websearchResult as any).config?.(input);
|
|
254
211
|
},
|
|
255
212
|
|
|
256
213
|
// Merge Events
|
|
@@ -260,33 +217,6 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
260
217
|
await (ptyResult as any).event?.(input);
|
|
261
218
|
},
|
|
262
219
|
|
|
263
|
-
// Auth (Consolidated)
|
|
264
|
-
auth: {
|
|
265
|
-
provider: "hiai-opencode",
|
|
266
|
-
methods: [
|
|
267
|
-
{ type: "api" as const, label: "Google Search API key" },
|
|
268
|
-
],
|
|
269
|
-
loader: async (getAuth: any) => {
|
|
270
|
-
const authData = await getAuth();
|
|
271
|
-
const { registerGetAuth, GOOGLE_PROVIDER_ID, OPENAI_PROVIDER_ID, OPENROUTER_PROVIDER_ID } = await import("./internals/plugins/websearch-cited/index");
|
|
272
|
-
|
|
273
|
-
const getConfiguredKey = (configKey?: string) => {
|
|
274
|
-
if (configKey) return resolveEnvVars(configKey);
|
|
275
|
-
return undefined;
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
const googleKey = authData["Google Search API key"] || getConfiguredKey(internalConfig.auth?.googleSearch);
|
|
279
|
-
const openaiKey = getConfiguredKey(internalConfig.auth?.openai);
|
|
280
|
-
const openRouterKey = getConfiguredKey(internalConfig.auth?.openrouter);
|
|
281
|
-
|
|
282
|
-
if (googleKey) registerGetAuth(GOOGLE_PROVIDER_ID, () => Promise.resolve({ type: "api", key: googleKey }));
|
|
283
|
-
if (openaiKey) registerGetAuth(OPENAI_PROVIDER_ID, () => Promise.resolve({ type: "api", key: openaiKey }));
|
|
284
|
-
if (openRouterKey) registerGetAuth(OPENROUTER_PROVIDER_ID, () => Promise.resolve({ type: "api", key: openRouterKey }));
|
|
285
|
-
|
|
286
|
-
return {};
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
|
-
|
|
290
220
|
"experimental.session.compacting": async (
|
|
291
221
|
_input: { sessionID: string },
|
|
292
222
|
output: { context: string[] },
|
|
@@ -3,11 +3,7 @@
|
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { getOpenCodeConfigDir } from "../../../../shared/opencode-config-dir";
|
|
5
5
|
import type { CommandConfig } from "../types";
|
|
6
|
-
import {
|
|
7
|
-
parseFrontmatter,
|
|
8
|
-
getTemplateBody,
|
|
9
|
-
parseParallelConfig,
|
|
10
|
-
} from "../parsing";
|
|
6
|
+
import { parseFrontmatter, getTemplateBody, parseParallelConfig, parseLoopConfig } from "../parsing";
|
|
11
7
|
|
|
12
8
|
/**
|
|
13
9
|
* Commands: Manifest building
|
|
@@ -43,7 +39,7 @@ export async function buildManifest(): Promise<Record<string, CommandConfig>> {
|
|
|
43
39
|
agent: fm.agent as string | undefined,
|
|
44
40
|
description: fm.description as string | undefined,
|
|
45
41
|
template: getTemplateBody(content),
|
|
46
|
-
loop: fm.loop
|
|
42
|
+
loop: parseLoopConfig(fm.loop),
|
|
47
43
|
model: fm.model as string | undefined,
|
|
48
44
|
auto: fm.subtask2 === "auto",
|
|
49
45
|
};
|
|
@@ -261,11 +261,11 @@ export async function commandExecuteBefore(
|
|
|
261
261
|
log(
|
|
262
262
|
`cmd.before: registered parent for prompt (${part.prompt.length} chars)`
|
|
263
263
|
);
|
|
264
|
-
if (
|
|
264
|
+
if ("as" in part && part.as) {
|
|
265
265
|
registerPendingResultCaptureByPrompt(
|
|
266
266
|
part.prompt,
|
|
267
267
|
input.sessionID,
|
|
268
|
-
(part
|
|
268
|
+
String(part.as)
|
|
269
269
|
);
|
|
270
270
|
}
|
|
271
271
|
}
|
|
@@ -177,7 +177,7 @@ export async function chatMessagesTransform(input: any, output: any) {
|
|
|
177
177
|
if (msg.info?.role !== "user") continue;
|
|
178
178
|
|
|
179
179
|
// Track processed messages by ID to avoid infinite loop
|
|
180
|
-
const msgId =
|
|
180
|
+
const msgId = msg.info?.id;
|
|
181
181
|
if (msgId && hasProcessedS2Message(msgId)) continue;
|
|
182
182
|
|
|
183
183
|
for (const part of msg.parts) {
|
|
@@ -27,11 +27,14 @@ export function parseLoopConfig(loop: unknown): LoopConfig | undefined {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// Parse a parallel item - handles "/cmd {model:...} args" syntax, plain "cmd", or {command, arguments} object
|
|
30
|
+
function isParallelCommandLike(p: unknown): p is { command: unknown; arguments?: unknown; prompt?: unknown; model?: unknown; agent?: unknown; loop?: unknown; as?: unknown; inline?: unknown } {
|
|
31
|
+
return typeof p === "object" && p !== null && "command" in p
|
|
32
|
+
}
|
|
33
|
+
|
|
30
34
|
export function parseParallelItem(p: unknown): ParallelCommand | null {
|
|
31
35
|
if (typeof p === "string") {
|
|
32
36
|
const trimmed = p.trim();
|
|
33
37
|
if (trimmed.startsWith("/")) {
|
|
34
|
-
// Parse /command {overrides} args syntax
|
|
35
38
|
const parsed = parseCommandWithOverrides(trimmed);
|
|
36
39
|
if (parsed.isInlineSubtask) {
|
|
37
40
|
return {
|
|
@@ -55,16 +58,16 @@ export function parseParallelItem(p: unknown): ParallelCommand | null {
|
|
|
55
58
|
}
|
|
56
59
|
return { command: trimmed };
|
|
57
60
|
}
|
|
58
|
-
if (
|
|
61
|
+
if (isParallelCommandLike(p)) {
|
|
59
62
|
return {
|
|
60
|
-
command: (p
|
|
61
|
-
arguments:
|
|
62
|
-
prompt:
|
|
63
|
-
model:
|
|
64
|
-
agent:
|
|
65
|
-
loop:
|
|
66
|
-
as:
|
|
67
|
-
inline:
|
|
63
|
+
command: String(p.command),
|
|
64
|
+
arguments: p.arguments !== undefined ? String(p.arguments) : undefined,
|
|
65
|
+
prompt: p.prompt !== undefined ? String(p.prompt) : undefined,
|
|
66
|
+
model: p.model !== undefined ? String(p.model) : undefined,
|
|
67
|
+
agent: p.agent !== undefined ? String(p.agent) : undefined,
|
|
68
|
+
loop: p.loop !== undefined ? parseLoopConfig(p.loop) : undefined,
|
|
69
|
+
as: p.as !== undefined ? String(p.as) : undefined,
|
|
70
|
+
inline: p.inline !== undefined ? Boolean(p.inline) : undefined,
|
|
68
71
|
};
|
|
69
72
|
}
|
|
70
73
|
return null;
|
package/src/mcp/index.ts
CHANGED
package/src/mcp/registry.ts
CHANGED
|
@@ -10,6 +10,8 @@ export type HiaiMcpName =
|
|
|
10
10
|
| "rag"
|
|
11
11
|
| "context7"
|
|
12
12
|
| "mempalace"
|
|
13
|
+
| "websearch"
|
|
14
|
+
| "grep_app"
|
|
13
15
|
|
|
14
16
|
export type HiaiMcpInstallKind = "bundled" | "npm" | "python" | "remote" | "user-service"
|
|
15
17
|
|
|
@@ -121,6 +123,31 @@ export const HIAI_MCP_REGISTRY: Record<HiaiMcpName, HiaiMcpRegistryEntry> = {
|
|
|
121
123
|
timeout: 600000,
|
|
122
124
|
},
|
|
123
125
|
},
|
|
126
|
+
websearch: {
|
|
127
|
+
name: "websearch",
|
|
128
|
+
enabledByDefault: true,
|
|
129
|
+
install: "remote",
|
|
130
|
+
optionalEnv: ["EXA_API_KEY", "TAVILY_API_KEY"],
|
|
131
|
+
config: {
|
|
132
|
+
enabled: true,
|
|
133
|
+
type: "remote",
|
|
134
|
+
url: "https://mcp.exa.ai/mcp?tools=web_search_exa",
|
|
135
|
+
headers: { "x-api-key": "{env:EXA_API_KEY}" },
|
|
136
|
+
timeout: 600000,
|
|
137
|
+
provider: "exa",
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
grep_app: {
|
|
141
|
+
name: "grep_app",
|
|
142
|
+
enabledByDefault: true,
|
|
143
|
+
install: "remote",
|
|
144
|
+
config: {
|
|
145
|
+
enabled: true,
|
|
146
|
+
type: "remote",
|
|
147
|
+
url: "https://mcp.grep.app",
|
|
148
|
+
timeout: 600000,
|
|
149
|
+
},
|
|
150
|
+
},
|
|
124
151
|
}
|
|
125
152
|
|
|
126
153
|
export function createDefaultMcpConfig(): Record<HiaiMcpName, McpServerConfig> {
|
|
@@ -236,8 +236,6 @@ export function createChatMessageHandler(args: {
|
|
|
236
236
|
await hooks.thinkMode?.["chat.message"]?.(input, output)
|
|
237
237
|
await hooks.claudeCodeHooks?.["chat.message"]?.(input, output)
|
|
238
238
|
await hooks.autoSlashCommand?.["chat.message"]?.(input, output)
|
|
239
|
-
await hooks.noBobGpt?.["chat.message"]?.(input, output)
|
|
240
|
-
await hooks.noCoderNonGpt?.["chat.message"]?.(input, output)
|
|
241
239
|
if (hooks.startWork && isStartWorkHookOutput(output)) {
|
|
242
240
|
const promptText = extractPromptText(output.parts)
|
|
243
241
|
if (isStartWorkFallbackTemplate(promptText)) {
|
|
@@ -19,8 +19,6 @@ import {
|
|
|
19
19
|
createStartWorkHook,
|
|
20
20
|
createStrategistMdOnlyHook,
|
|
21
21
|
createBobJuniorNotepadHook,
|
|
22
|
-
createNoBobGptHook,
|
|
23
|
-
createNoCoderNonGptHook,
|
|
24
22
|
createQuestionLabelTruncatorHook,
|
|
25
23
|
createPreemptiveCompactionHook,
|
|
26
24
|
createRuntimeFallbackHook,
|
|
@@ -54,8 +52,6 @@ export type SessionHooks = {
|
|
|
54
52
|
startWork: ReturnType<typeof createStartWorkHook> | null
|
|
55
53
|
strategistMdOnly: ReturnType<typeof createStrategistMdOnlyHook> | null
|
|
56
54
|
bobJuniorNotepad: ReturnType<typeof createBobJuniorNotepadHook> | null
|
|
57
|
-
noBobGpt: ReturnType<typeof createNoBobGptHook> | null
|
|
58
|
-
noCoderNonGpt: ReturnType<typeof createNoCoderNonGptHook> | null
|
|
59
55
|
questionLabelTruncator: ReturnType<typeof createQuestionLabelTruncatorHook> | null
|
|
60
56
|
taskResumeInfo: ReturnType<typeof createTaskResumeInfoHook> | null
|
|
61
57
|
anthropicEffort: ReturnType<typeof createAnthropicEffortHook> | null
|
|
@@ -219,17 +215,6 @@ export function createSessionHooks(args: {
|
|
|
219
215
|
? safeHook("sub-notepad", () => createBobJuniorNotepadHook(ctx))
|
|
220
216
|
: null
|
|
221
217
|
|
|
222
|
-
const noBobGpt = isHookEnabled("no-bob-gpt")
|
|
223
|
-
? safeHook("no-bob-gpt", () => createNoBobGptHook(ctx))
|
|
224
|
-
: null
|
|
225
|
-
|
|
226
|
-
const noCoderNonGpt = isHookEnabled("no-coder-non-gpt")
|
|
227
|
-
? safeHook("no-coder-non-gpt", () =>
|
|
228
|
-
createNoCoderNonGptHook(ctx, {
|
|
229
|
-
allowNonGptModel: pluginConfig.agents?.coder?.allow_non_gpt_model,
|
|
230
|
-
}))
|
|
231
|
-
: null
|
|
232
|
-
|
|
233
218
|
const questionLabelTruncator = isHookEnabled("question-label-truncator")
|
|
234
219
|
? safeHook("question-label-truncator", () => createQuestionLabelTruncatorHook())
|
|
235
220
|
: null
|
|
@@ -275,8 +260,6 @@ export function createSessionHooks(args: {
|
|
|
275
260
|
startWork,
|
|
276
261
|
strategistMdOnly,
|
|
277
262
|
bobJuniorNotepad,
|
|
278
|
-
noBobGpt,
|
|
279
|
-
noCoderNonGpt,
|
|
280
263
|
questionLabelTruncator,
|
|
281
264
|
taskResumeInfo,
|
|
282
265
|
anthropicEffort,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { MODE_TO_AGENT } from "../shared/mode-routing"
|
|
2
|
+
|
|
3
|
+
const WRITE_CAPABLE_AGENTS = new Set(["coder", "sub", "designer", "brainstormer"])
|
|
4
|
+
|
|
5
|
+
const WRITE_MODES = new Set([
|
|
6
|
+
"quick",
|
|
7
|
+
"bounded",
|
|
8
|
+
"deep",
|
|
9
|
+
"cross-module",
|
|
10
|
+
"visual-engineering",
|
|
11
|
+
"artistry",
|
|
12
|
+
"writing",
|
|
13
|
+
])
|
|
14
|
+
|
|
15
|
+
const READONLY_MODES = new Set(["ultrabrain", "git", "git-ops"])
|
|
16
|
+
|
|
17
|
+
export function lintModeAgentCapabilities(): void {
|
|
18
|
+
for (const [mode, agent] of Object.entries(MODE_TO_AGENT)) {
|
|
19
|
+
if (WRITE_MODES.has(mode) && !WRITE_CAPABLE_AGENTS.has(agent)) {
|
|
20
|
+
console.warn(`[startup-lint] Mode "${mode}" targets agent "${agent}" which cannot write/edit. Tasks using this mode may fail when attempting file operations.`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (READONLY_MODES.has(mode) && WRITE_CAPABLE_AGENTS.has(agent)) {
|
|
24
|
+
console.warn(`[startup-lint] Mode "${mode}" targets agent "${agent}" which can write. Consider verifying this is intentional.`)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -81,7 +81,7 @@ const RUNTIME_AGENT_DESCRIPTIONS: Partial<Record<string, string>> = {
|
|
|
81
81
|
"Compatibility wrapper for review functions now folded into Critic. (Quality Guardian - HiaiOpenCode)",
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
function forceVisiblePrimaryAgent(agent: unknown, name: string): unknown {
|
|
84
|
+
function forceVisiblePrimaryAgent(agent: unknown, name: string, forceMode?: "primary" | "all"): unknown {
|
|
85
85
|
if (typeof agent !== "object" || agent === null) {
|
|
86
86
|
return agent;
|
|
87
87
|
}
|
|
@@ -91,7 +91,7 @@ function forceVisiblePrimaryAgent(agent: unknown, name: string): unknown {
|
|
|
91
91
|
...base,
|
|
92
92
|
name,
|
|
93
93
|
hidden: false,
|
|
94
|
-
mode: "
|
|
94
|
+
mode: forceMode ?? "all",
|
|
95
95
|
...(typeof base.description === "string" && base.description.trim().length > 0
|
|
96
96
|
? {}
|
|
97
97
|
: { description: RUNTIME_AGENT_DESCRIPTIONS[name] }),
|
|
@@ -475,6 +475,7 @@ export async function applyAgentConfig(params: {
|
|
|
475
475
|
normalizedAgents[name] = forceVisiblePrimaryAgent(
|
|
476
476
|
normalizedAgents[name],
|
|
477
477
|
name,
|
|
478
|
+
name === "Bob" ? "primary" : "all",
|
|
478
479
|
);
|
|
479
480
|
}
|
|
480
481
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
|
|
3
|
+
import { applyMcpConfig } from "./mcp-config-handler"
|
|
4
|
+
|
|
5
|
+
const emptyPluginComponents = {
|
|
6
|
+
commands: {},
|
|
7
|
+
skills: {},
|
|
8
|
+
agents: {},
|
|
9
|
+
mcpServers: {},
|
|
10
|
+
hooksConfigs: [],
|
|
11
|
+
plugins: [],
|
|
12
|
+
errors: [],
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
test("context7 auth fallback from hiai-opencode config is used when env placeholder is empty", async () => {
|
|
16
|
+
const config: Record<string, unknown> = {}
|
|
17
|
+
|
|
18
|
+
await applyMcpConfig({
|
|
19
|
+
config,
|
|
20
|
+
pluginConfig: {
|
|
21
|
+
auth: { context7: "ctx-test-key" },
|
|
22
|
+
claude_code: { mcp: false },
|
|
23
|
+
__platformDefaults: {
|
|
24
|
+
mcp: {
|
|
25
|
+
context7: {
|
|
26
|
+
enabled: true,
|
|
27
|
+
type: "remote",
|
|
28
|
+
url: "https://mcp.context7.com/mcp",
|
|
29
|
+
headers: { "X-API-KEY": "{env:CONTEXT7_API_KEY}" },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
} as any,
|
|
34
|
+
pluginComponents: emptyPluginComponents,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
expect((config.mcp as any).context7.headers).toEqual({ "X-API-KEY": "ctx-test-key" })
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test("firecrawl auth fallback from hiai-opencode config is used when env placeholder is empty", async () => {
|
|
41
|
+
const config: Record<string, unknown> = {}
|
|
42
|
+
|
|
43
|
+
await applyMcpConfig({
|
|
44
|
+
config,
|
|
45
|
+
pluginConfig: {
|
|
46
|
+
auth: { firecrawl: "fc-test-key" },
|
|
47
|
+
claude_code: { mcp: false },
|
|
48
|
+
__platformDefaults: {
|
|
49
|
+
mcp: {
|
|
50
|
+
firecrawl: {
|
|
51
|
+
enabled: true,
|
|
52
|
+
type: "remote",
|
|
53
|
+
url: "http://localhost/firecrawl",
|
|
54
|
+
environment: { FIRECRAWL_API_KEY: "{env:FIRECRAWL_API_KEY}" },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
} as any,
|
|
59
|
+
pluginComponents: emptyPluginComponents,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
expect((config.mcp as any).firecrawl.environment).toEqual({ FIRECRAWL_API_KEY: "fc-test-key" })
|
|
63
|
+
})
|
|
@@ -2,7 +2,6 @@ import type { HiaiOpenCodeConfig } from "../config";
|
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { loadMcpConfigs } from "../features/claude-code-mcp-loader";
|
|
5
|
-
import { createBuiltinMcps } from "../mcp";
|
|
6
5
|
import type { PluginComponents } from "./plugin-components-loader";
|
|
7
6
|
import { log } from "../shared";
|
|
8
7
|
import { getPlatformMcpDefaults } from "../shared/runtime-plugin-config";
|
|
@@ -14,8 +13,20 @@ function resolveHeaderAuthFallback(
|
|
|
14
13
|
pluginConfig: HiaiOpenCodeConfig,
|
|
15
14
|
name: string,
|
|
16
15
|
): Record<string, string> | undefined {
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const resolveAuth = (value: string | undefined): string | undefined => {
|
|
17
|
+
if (!value?.trim()) return undefined;
|
|
18
|
+
const resolved = resolveEnvVars(value).trim();
|
|
19
|
+
return resolved.length > 0 ? resolved : undefined;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
if (name === "stitch") {
|
|
23
|
+
const key = resolveAuth(pluginConfig.auth?.stitch);
|
|
24
|
+
return key ? { "X-Goog-Api-Key": key } : undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (name === "context7") {
|
|
28
|
+
const key = resolveAuth(pluginConfig.auth?.context7);
|
|
29
|
+
return key ? { "X-API-KEY": key } : undefined;
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
return undefined;
|
|
@@ -25,8 +36,9 @@ function resolveEnvironmentAuthFallback(
|
|
|
25
36
|
pluginConfig: HiaiOpenCodeConfig,
|
|
26
37
|
name: string,
|
|
27
38
|
): Record<string, string> | undefined {
|
|
28
|
-
if (name === "firecrawl"
|
|
29
|
-
|
|
39
|
+
if (name === "firecrawl") {
|
|
40
|
+
const key = pluginConfig.auth?.firecrawl ? resolveEnvVars(pluginConfig.auth.firecrawl).trim() : "";
|
|
41
|
+
return key ? { FIRECRAWL_API_KEY: key } : undefined;
|
|
30
42
|
}
|
|
31
43
|
|
|
32
44
|
return undefined;
|
|
@@ -122,18 +134,20 @@ function normalizePlatformMcpDefaults(
|
|
|
122
134
|
);
|
|
123
135
|
const headerFallback = resolveHeaderAuthFallback(pluginConfig, name);
|
|
124
136
|
const environmentFallback = resolveEnvironmentAuthFallback(pluginConfig, name);
|
|
137
|
+
const usableHeaders = headers && !hasMissingResolvedValue(headers) ? headers : undefined;
|
|
138
|
+
const usableEnvironment = environment && !hasMissingResolvedValue(environment) ? environment : undefined;
|
|
125
139
|
const headersWithFallback =
|
|
126
|
-
|
|
127
|
-
? { ...headerFallback, ...
|
|
128
|
-
:
|
|
140
|
+
usableHeaders && headerFallback
|
|
141
|
+
? { ...headerFallback, ...usableHeaders }
|
|
142
|
+
: usableHeaders ?? headerFallback;
|
|
129
143
|
const environmentWithFallback =
|
|
130
|
-
|
|
131
|
-
? { ...environmentFallback, ...
|
|
132
|
-
:
|
|
144
|
+
usableEnvironment && environmentFallback
|
|
145
|
+
? { ...environmentFallback, ...usableEnvironment }
|
|
146
|
+
: usableEnvironment ?? environmentFallback;
|
|
133
147
|
|
|
134
148
|
const missingResolvedValues =
|
|
135
|
-
|
|
136
|
-
|
|
149
|
+
(!!headers && !usableHeaders && !headerFallback) ||
|
|
150
|
+
(!!environment && !usableEnvironment && !environmentFallback);
|
|
137
151
|
|
|
138
152
|
if (missingResolvedValues) {
|
|
139
153
|
log(`MCP server "${name}" is missing environment-backed auth; keeping it visible in config`);
|
|
@@ -152,6 +166,8 @@ function normalizePlatformMcpDefaults(
|
|
|
152
166
|
nextEntry.environment = environmentWithFallback;
|
|
153
167
|
}
|
|
154
168
|
|
|
169
|
+
delete nextEntry.provider;
|
|
170
|
+
|
|
155
171
|
normalized[name] = nextEntry;
|
|
156
172
|
}
|
|
157
173
|
|
|
@@ -184,7 +200,6 @@ export async function applyMcpConfig(params: {
|
|
|
184
200
|
getPlatformMcpDefaults(params.pluginConfig) as unknown as Record<string, unknown>,
|
|
185
201
|
params.pluginConfig,
|
|
186
202
|
),
|
|
187
|
-
...createBuiltinMcps(disabledMcps, params.pluginConfig),
|
|
188
203
|
...mcpResult.servers,
|
|
189
204
|
...(userMcp ?? {}),
|
|
190
205
|
...params.pluginComponents.mcpServers,
|
|
@@ -98,7 +98,7 @@ export async function buildStrategistAgentConfig(params: {
|
|
|
98
98
|
const base: Record<string, unknown> = {
|
|
99
99
|
...(resolvedModel ? { model: resolvedModel } : {}),
|
|
100
100
|
...(variantToUse ? { variant: variantToUse } : {}),
|
|
101
|
-
mode: "
|
|
101
|
+
mode: "all",
|
|
102
102
|
prompt: getStrategistPrompt(resolvedModel, params.disabledTools) + "\n\n" + CLOSURE_SCHEMA_PROMPT,
|
|
103
103
|
permission: PROMETHEUS_PERMISSION,
|
|
104
104
|
description: `${(params.configAgentPlan?.description as string) ?? "Plan agent"} (Strategist - HiaiOpenCode)`,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
|
|
3
|
+
import { getAgentConfigKey } from "./agent-display-names"
|
|
4
|
+
|
|
5
|
+
test("writer aliases resolve to brainstormer", () => {
|
|
6
|
+
expect(getAgentConfigKey("writer")).toBe("brainstormer")
|
|
7
|
+
expect(getAgentConfigKey("copywriter")).toBe("brainstormer")
|
|
8
|
+
expect(getAgentConfigKey("content-writer")).toBe("brainstormer")
|
|
9
|
+
})
|
|
@@ -110,6 +110,11 @@ const LEGACY_DISPLAY_NAMES: Record<string, string> = {
|
|
|
110
110
|
"platform manager (utility)": "platform-manager",
|
|
111
111
|
"platform manager - utility": "platform-manager",
|
|
112
112
|
"brainstormer - idea explorer": "brainstormer",
|
|
113
|
+
"writer": "brainstormer",
|
|
114
|
+
"copywriter": "brainstormer",
|
|
115
|
+
"content-writer": "brainstormer",
|
|
116
|
+
"content writer": "brainstormer",
|
|
117
|
+
"website-writer": "brainstormer",
|
|
113
118
|
"agent skills - skill composer": "agent-skills",
|
|
114
119
|
"subagent": "sub",
|
|
115
120
|
"ui": "multimodal",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { checkForLegacyPluginEntry } from "./legacy-plugin-warning"
|
|
2
|
-
import { log } from "./logger"
|
|
2
|
+
import { log, logWarn } from "./logger"
|
|
3
3
|
import { migrateLegacyPluginEntry } from "./migrate-legacy-plugin-entry"
|
|
4
4
|
import { toCanonicalEntry } from "./plugin-entry-migrator"
|
|
5
5
|
import { LEGACY_PLUGIN_NAME, PLUGIN_NAME } from "./plugin-identity"
|
|
@@ -28,18 +28,16 @@ export function logLegacyPluginStartupWarning(deps: LogLegacyPluginStartupWarnin
|
|
|
28
28
|
hasCanonicalEntry: result.hasCanonicalEntry,
|
|
29
29
|
})
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
`[hiai-opencode] WARNING: Your opencode.json uses the legacy package name "${LEGACY_PLUGIN_NAME}".`
|
|
31
|
+
logWarn(`Your opencode.json uses the legacy package name "${LEGACY_PLUGIN_NAME}".`
|
|
33
32
|
+ ` The package has been renamed to "${PLUGIN_NAME}".`
|
|
34
|
-
+ ` Attempting auto-migration
|
|
35
|
-
)
|
|
33
|
+
+ ` Attempting auto-migration...`)
|
|
36
34
|
|
|
37
35
|
const migrated = migrateLegacyPluginEntryFn(result.configPath!)
|
|
38
36
|
if (migrated) {
|
|
39
|
-
|
|
37
|
+
logWarn(`Auto-migrated opencode.json: ${result.legacyEntries.join(", ")} -> ${suggestedEntries.join(", ")}`)
|
|
40
38
|
} else {
|
|
41
|
-
|
|
42
|
-
`
|
|
39
|
+
logWarn(
|
|
40
|
+
`Could not auto-migrate. Please manually update your opencode.json:`
|
|
43
41
|
+ ` ${result.legacyEntries.map((e, i) => `"${e}" -> "${suggestedEntries[i]}"`).join(", ")}`,
|
|
44
42
|
)
|
|
45
43
|
}
|
package/src/shared/logger.ts
CHANGED
|
@@ -43,6 +43,14 @@ export function log(message: string, data?: unknown): void {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
export function logWarn(message: string, data?: unknown): void {
|
|
47
|
+
log(`WARN: ${message}`, data)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function logError(message: string, data?: unknown): void {
|
|
51
|
+
log(`ERROR: ${message}`, data)
|
|
52
|
+
}
|
|
53
|
+
|
|
46
54
|
export function getLogFilePath(): string {
|
|
47
55
|
return logFile
|
|
48
56
|
}
|