chore(lint): restore zero-error eslint baseline
This commit is contained in:
@@ -20,6 +20,7 @@ Scope: Production-risk-first audit of bugs, code improvements, and feature oppor
|
|||||||
- ✅ F-015 addressed: retry defaults no longer classify timeout-style failures as non-retryable, improving resilience for transient timeout conditions.
|
- ✅ F-015 addressed: retry defaults no longer classify timeout-style failures as non-retryable, improving resilience for transient timeout conditions.
|
||||||
- ✅ F-011 addressed: Slack user-name resolution now uses bounded TTL+LRU caching to prevent unbounded growth.
|
- ✅ F-011 addressed: Slack user-name resolution now uses bounded TTL+LRU caching to prevent unbounded growth.
|
||||||
- ◑ F-013 partially addressed: reset-command normalization is now shared across Discord/Slack/WhatsApp adapters via `src/channels/utils.ts`, reducing duplicated command-parsing logic.
|
- ◑ F-013 partially addressed: reset-command normalization is now shared across Discord/Slack/WhatsApp adapters via `src/channels/utils.ts`, reducing duplicated command-parsing logic.
|
||||||
|
- ◑ F-004 partially addressed: lint error baseline is restored (`pnpm lint` now passes with 0 errors), while warning-burn-down remains open.
|
||||||
|
|
||||||
## Executive Summary
|
## Executive Summary
|
||||||
|
|
||||||
@@ -27,14 +28,14 @@ Current health snapshot:
|
|||||||
- `pnpm typecheck`: passing
|
- `pnpm typecheck`: passing
|
||||||
- `pnpm build`: passing
|
- `pnpm build`: passing
|
||||||
- `pnpm test:run`: passing (`140/140` files, `1773/1773` tests)
|
- `pnpm test:run`: passing (`140/140` files, `1773/1773` tests)
|
||||||
- `pnpm lint`: failing (`148 errors`, `530 warnings`)
|
- `pnpm lint`: passing with warnings only (`0 errors`, `539 warnings`)
|
||||||
|
|
||||||
Top conclusions:
|
Top conclusions:
|
||||||
- A critical Web UI security issue exists in markdown rendering (unsanitized HTML insertion).
|
- A critical Web UI security issue exists in markdown rendering (unsanitized HTML insertion).
|
||||||
- Runtime configuration edits from the settings page appear non-persistent across restart.
|
- Runtime configuration edits from the settings page appear non-persistent across restart.
|
||||||
- Tool timeout behavior likely allows underlying side effects to continue after timeout.
|
- Tool timeout behavior likely allows underlying side effects to continue after timeout.
|
||||||
- Gateway request-body handling and WebSocket ingress controls need abuse protections.
|
- Gateway request-body handling and WebSocket ingress controls need abuse protections.
|
||||||
- Lint quality gates are currently broken at scale, reducing CI signal quality.
|
- Lint error-level gate is restored, but warning debt remains high.
|
||||||
|
|
||||||
## Methodology and Scope
|
## Methodology and Scope
|
||||||
|
|
||||||
@@ -125,7 +126,7 @@ Remediation update (2026-02-16):
|
|||||||
- Severity: Medium
|
- Severity: Medium
|
||||||
- Impact: CI noise, reduced confidence in static analysis, and slower defect detection.
|
- Impact: CI noise, reduced confidence in static analysis, and slower defect detection.
|
||||||
- Evidence:
|
- Evidence:
|
||||||
- `pnpm -s lint` => `148 errors`, `530 warnings`
|
- `pnpm -s lint` => `0 errors`, `539 warnings`
|
||||||
- Error concentration:
|
- Error concentration:
|
||||||
- `src/daemon/models.ts` (90 errors)
|
- `src/daemon/models.ts` (90 errors)
|
||||||
- `src/cli/tui.ts` (25 errors)
|
- `src/cli/tui.ts` (25 errors)
|
||||||
@@ -142,6 +143,10 @@ Remediation update (2026-02-16):
|
|||||||
- CI check enforcing `eslint` errors = 0.
|
- CI check enforcing `eslint` errors = 0.
|
||||||
- Secondary threshold check for warning reduction trend.
|
- Secondary threshold check for warning reduction trend.
|
||||||
|
|
||||||
|
Remediation update (2026-02-16):
|
||||||
|
- Stage 1 complete: fixed all error-level ESLint violations in impacted high-error files so `pnpm lint` now passes with `0` errors.
|
||||||
|
- Stage 2 pending: warning-burn-down remains (currently `539` warnings).
|
||||||
|
|
||||||
### F-005 Medium: ESLint browser globals mismatch causes avoidable UI lint failures
|
### F-005 Medium: ESLint browser globals mismatch causes avoidable UI lint failures
|
||||||
|
|
||||||
- Severity: Medium
|
- Severity: Medium
|
||||||
@@ -443,9 +448,9 @@ pnpm -s lint
|
|||||||
|
|
||||||
Observed outcomes:
|
Observed outcomes:
|
||||||
- Typecheck/build/test: passing.
|
- Typecheck/build/test: passing.
|
||||||
- Lint: failing with `148 errors` and `530 warnings`.
|
- Lint: passing with warnings only (`0` errors, `539` warnings).
|
||||||
|
|
||||||
Top lint error concentration snapshot:
|
Historical pre-remediation lint error concentration snapshot:
|
||||||
- `src/daemon/models.ts`: 90 errors
|
- `src/daemon/models.ts`: 90 errors
|
||||||
- `src/cli/tui.ts`: 25 errors
|
- `src/cli/tui.ts`: 25 errors
|
||||||
- `src/daemon/routing.ts`: 14 errors
|
- `src/daemon/routing.ts`: 14 errors
|
||||||
|
|||||||
@@ -2628,6 +2628,25 @@
|
|||||||
"docs/plans/analysis/2026-02-16-codebase-audit-report.md"
|
"docs/plans/analysis/2026-02-16-codebase-audit-report.md"
|
||||||
],
|
],
|
||||||
"test_status": "pnpm test:run src/channels/utils.test.ts src/channels/discord/adapter.test.ts src/channels/slack/adapter.test.ts src/channels/whatsapp/adapter.test.ts + pnpm typecheck passing"
|
"test_status": "pnpm test:run src/channels/utils.test.ts src/channels/discord/adapter.test.ts src/channels/slack/adapter.test.ts src/channels/whatsapp/adapter.test.ts + pnpm typecheck passing"
|
||||||
|
},
|
||||||
|
"audit-followup-lint-error-baseline": {
|
||||||
|
"status": "completed",
|
||||||
|
"date": "2026-02-16",
|
||||||
|
"updated": "2026-02-16",
|
||||||
|
"summary": "Completed stage-1 lint recovery by clearing all error-level ESLint violations in high-error files (`daemon/models.ts`, `cli/tui.ts`, `daemon/routing.ts`, `gateway/ui/pages/settings.js`) and adjacent return-await/no-useless-return issues so `pnpm lint` now passes with warnings only.",
|
||||||
|
"files_modified": [
|
||||||
|
"src/daemon/models.ts",
|
||||||
|
"src/cli/tui.ts",
|
||||||
|
"src/daemon/routing.ts",
|
||||||
|
"src/gateway/ui/pages/settings.js",
|
||||||
|
"src/backends/native/orchestrator.ts",
|
||||||
|
"src/frontends/tui/components/App.tsx",
|
||||||
|
"src/gateway/server.test.ts",
|
||||||
|
"src/hooks/engine.ts",
|
||||||
|
"src/tools/executor.test.ts",
|
||||||
|
"docs/plans/analysis/2026-02-16-codebase-audit-report.md"
|
||||||
|
],
|
||||||
|
"test_status": "pnpm test:run src/gateway/server.test.ts src/tools/executor.test.ts src/backends/native/orchestrator.test.ts src/daemon/routing.test.ts + pnpm typecheck + pnpm lint passing (0 errors, warnings remain)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"overall_progress": {
|
"overall_progress": {
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ export class AgentOrchestrator {
|
|||||||
private _restoreHistory(messages: Message[]): void {
|
private _restoreHistory(messages: Message[]): void {
|
||||||
if (this._session) {
|
if (this._session) {
|
||||||
this._session.replaceHistory(messages);
|
this._session.replaceHistory(messages);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// No session available; nothing safe to do here.
|
// No session available; nothing safe to do here.
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-25
@@ -232,33 +232,33 @@ export function registerTuiCommand(program: Command): void {
|
|||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (opts.fullscreen) {
|
if (opts.fullscreen) {
|
||||||
await startFullscreenTui({
|
await startFullscreenTui({
|
||||||
session,
|
session,
|
||||||
modelClient: modelRouter,
|
modelClient: modelRouter,
|
||||||
modelRouter,
|
modelRouter,
|
||||||
systemPrompt,
|
systemPrompt,
|
||||||
model: config.models.default.model,
|
model: config.models.default.model,
|
||||||
agent,
|
agent,
|
||||||
hookEngine,
|
hookEngine,
|
||||||
modelProviderConfigs,
|
modelProviderConfigs,
|
||||||
onExit: cleanup,
|
onExit: cleanup,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let switchingToFullscreen = false;
|
let switchingToFullscreen = false;
|
||||||
|
|
||||||
const tui = new MinimalTui({
|
const tui = new MinimalTui({
|
||||||
session,
|
session,
|
||||||
modelClient: modelRouter,
|
modelClient: modelRouter,
|
||||||
modelRouter,
|
modelRouter,
|
||||||
systemPrompt,
|
systemPrompt,
|
||||||
agent,
|
agent,
|
||||||
hookEngine,
|
hookEngine,
|
||||||
pairingManager,
|
pairingManager,
|
||||||
localProviders: config.models.local_providers,
|
localProviders: config.models.local_providers,
|
||||||
modelProviderConfigs,
|
modelProviderConfigs,
|
||||||
currentLocalProvider: config.models.local?.provider,
|
currentLocalProvider: config.models.local?.provider,
|
||||||
onTransfer: (target) => {
|
onTransfer: (target) => {
|
||||||
if (target === 'telegram') {
|
if (target === 'telegram') {
|
||||||
if (config.telegram && config.telegram.allowed_chat_ids.length > 0) {
|
if (config.telegram && config.telegram.allowed_chat_ids.length > 0) {
|
||||||
const telegramUserId = String(config.telegram.allowed_chat_ids[0]);
|
const telegramUserId = String(config.telegram.allowed_chat_ids[0]);
|
||||||
|
|||||||
+96
-96
@@ -57,115 +57,115 @@ function resolveZaiCredential(cfg: ModelConfig): string {
|
|||||||
export function createClientFromConfig(cfg: ModelConfig): ModelClient {
|
export function createClientFromConfig(cfg: ModelConfig): ModelClient {
|
||||||
switch (cfg.provider) {
|
switch (cfg.provider) {
|
||||||
case 'anthropic':
|
case 'anthropic':
|
||||||
{
|
{
|
||||||
const authMode = getEffectiveAuthMode(cfg);
|
const authMode = getEffectiveAuthMode(cfg);
|
||||||
|
|
||||||
if (authMode === 'oauth') {
|
|
||||||
const token = cfg.auth_token ?? getAnthropicAuthToken();
|
|
||||||
if (!token) {
|
|
||||||
throw new Error(
|
|
||||||
'Anthropic auth token not configured (auth_mode: oauth). ' +
|
|
||||||
'Set ANTHROPIC_AUTH_TOKEN, run `flynn anthropic-auth --token`, or provide auth_token in config.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return new AnthropicClient({
|
|
||||||
model: cfg.model,
|
|
||||||
authToken: token,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authMode === 'api_key') {
|
|
||||||
const apiKey = cfg.api_key ?? getAnthropicApiKey();
|
|
||||||
if (!apiKey) {
|
|
||||||
throw new Error(
|
|
||||||
'Anthropic API key not configured (auth_mode: api_key). ' +
|
|
||||||
'Set ANTHROPIC_API_KEY, run `flynn anthropic-auth`, or provide api_key in config.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return new AnthropicClient({
|
|
||||||
model: cfg.model,
|
|
||||||
apiKey,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// auto: prefer API key, then token
|
|
||||||
const apiKey = cfg.api_key ?? getAnthropicApiKey();
|
|
||||||
if (apiKey) {
|
|
||||||
return new AnthropicClient({
|
|
||||||
model: cfg.model,
|
|
||||||
apiKey,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (authMode === 'oauth') {
|
||||||
const token = cfg.auth_token ?? getAnthropicAuthToken();
|
const token = cfg.auth_token ?? getAnthropicAuthToken();
|
||||||
if (token) {
|
if (!token) {
|
||||||
return new AnthropicClient({
|
throw new Error(
|
||||||
model: cfg.model,
|
'Anthropic auth token not configured (auth_mode: oauth). ' +
|
||||||
authToken: token,
|
'Set ANTHROPIC_AUTH_TOKEN, run `flynn anthropic-auth --token`, or provide auth_token in config.',
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
return new AnthropicClient({
|
||||||
|
model: cfg.model,
|
||||||
|
authToken: token,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(
|
if (authMode === 'api_key') {
|
||||||
'Anthropic credentials not configured (auth_mode: auto). ' +
|
const apiKey = cfg.api_key ?? getAnthropicApiKey();
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error(
|
||||||
|
'Anthropic API key not configured (auth_mode: api_key). ' +
|
||||||
|
'Set ANTHROPIC_API_KEY, run `flynn anthropic-auth`, or provide api_key in config.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return new AnthropicClient({
|
||||||
|
model: cfg.model,
|
||||||
|
apiKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// auto: prefer API key, then token
|
||||||
|
const apiKey = cfg.api_key ?? getAnthropicApiKey();
|
||||||
|
if (apiKey) {
|
||||||
|
return new AnthropicClient({
|
||||||
|
model: cfg.model,
|
||||||
|
apiKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = cfg.auth_token ?? getAnthropicAuthToken();
|
||||||
|
if (token) {
|
||||||
|
return new AnthropicClient({
|
||||||
|
model: cfg.model,
|
||||||
|
authToken: token,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
'Anthropic credentials not configured (auth_mode: auto). ' +
|
||||||
'Set ANTHROPIC_API_KEY (or run `flynn anthropic-auth`), ' +
|
'Set ANTHROPIC_API_KEY (or run `flynn anthropic-auth`), ' +
|
||||||
'or set ANTHROPIC_AUTH_TOKEN (or run `flynn anthropic-auth --token`).',
|
'or set ANTHROPIC_AUTH_TOKEN (or run `flynn anthropic-auth --token`).',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case 'openai':
|
case 'openai':
|
||||||
{
|
{
|
||||||
const authMode = getEffectiveAuthMode(cfg);
|
const authMode = getEffectiveAuthMode(cfg);
|
||||||
|
|
||||||
if (authMode === 'oauth') {
|
|
||||||
const existing = loadStoredOpenAIAuth();
|
|
||||||
if (!existing) {
|
|
||||||
throw new Error(
|
|
||||||
'OpenAI OAuth is not configured (auth_mode: oauth). ' +
|
|
||||||
'Run `flynn openai-auth` to authenticate.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return new OpenAIClient({
|
|
||||||
model: cfg.model,
|
|
||||||
useOAuth: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authMode === 'api_key') {
|
|
||||||
const apiKey = cfg.api_key ?? getOpenAIApiKey();
|
|
||||||
if (!apiKey) {
|
|
||||||
throw new Error(
|
|
||||||
'OpenAI API key not configured (auth_mode: api_key). ' +
|
|
||||||
'Set OPENAI_API_KEY, run `flynn openai-key`, or provide api_key in config.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return new OpenAIClient({
|
|
||||||
model: cfg.model,
|
|
||||||
apiKey,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// auto: prefer API key, then OAuth
|
|
||||||
const apiKey = cfg.api_key ?? getOpenAIApiKey();
|
|
||||||
if (apiKey) {
|
|
||||||
return new OpenAIClient({
|
|
||||||
model: cfg.model,
|
|
||||||
apiKey,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (authMode === 'oauth') {
|
||||||
const existing = loadStoredOpenAIAuth();
|
const existing = loadStoredOpenAIAuth();
|
||||||
if (existing) {
|
if (!existing) {
|
||||||
return new OpenAIClient({
|
throw new Error(
|
||||||
model: cfg.model,
|
'OpenAI OAuth is not configured (auth_mode: oauth). ' +
|
||||||
useOAuth: true,
|
'Run `flynn openai-auth` to authenticate.',
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
return new OpenAIClient({
|
||||||
|
model: cfg.model,
|
||||||
|
useOAuth: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(
|
if (authMode === 'api_key') {
|
||||||
'OpenAI credentials not configured (auth_mode: auto). ' +
|
const apiKey = cfg.api_key ?? getOpenAIApiKey();
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error(
|
||||||
|
'OpenAI API key not configured (auth_mode: api_key). ' +
|
||||||
|
'Set OPENAI_API_KEY, run `flynn openai-key`, or provide api_key in config.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return new OpenAIClient({
|
||||||
|
model: cfg.model,
|
||||||
|
apiKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// auto: prefer API key, then OAuth
|
||||||
|
const apiKey = cfg.api_key ?? getOpenAIApiKey();
|
||||||
|
if (apiKey) {
|
||||||
|
return new OpenAIClient({
|
||||||
|
model: cfg.model,
|
||||||
|
apiKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = loadStoredOpenAIAuth();
|
||||||
|
if (existing) {
|
||||||
|
return new OpenAIClient({
|
||||||
|
model: cfg.model,
|
||||||
|
useOAuth: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
'OpenAI credentials not configured (auth_mode: auto). ' +
|
||||||
'Set OPENAI_API_KEY (or run `flynn openai-key`), ' +
|
'Set OPENAI_API_KEY (or run `flynn openai-key`), ' +
|
||||||
'or run `flynn openai-auth` for OAuth.',
|
'or run `flynn openai-auth` for OAuth.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case 'ollama':
|
case 'ollama':
|
||||||
return new OllamaClient({
|
return new OllamaClient({
|
||||||
model: cfg.model,
|
model: cfg.model,
|
||||||
|
|||||||
+14
-14
@@ -199,7 +199,7 @@ export function createMessageRouter(deps: {
|
|||||||
effectiveToolRegistry = effectiveToolRegistry.clone();
|
effectiveToolRegistry = effectiveToolRegistry.clone();
|
||||||
effectiveToolRegistry.register(createMediaSendTool(collector));
|
effectiveToolRegistry.register(createMediaSendTool(collector));
|
||||||
|
|
||||||
const orchestrator = new AgentOrchestrator({
|
const orchestrator = new AgentOrchestrator({
|
||||||
modelRouter: deps.modelRouter,
|
modelRouter: deps.modelRouter,
|
||||||
systemPrompt: effectiveSystemPrompt,
|
systemPrompt: effectiveSystemPrompt,
|
||||||
session,
|
session,
|
||||||
@@ -221,19 +221,19 @@ export function createMessageRouter(deps: {
|
|||||||
memoryAutoExtract: deps.config.memory?.auto_extract,
|
memoryAutoExtract: deps.config.memory?.auto_extract,
|
||||||
memoryInjectionStrategy: deps.config.memory?.injection_strategy,
|
memoryInjectionStrategy: deps.config.memory?.injection_strategy,
|
||||||
memoryMaxInjectionTokens: deps.config.memory?.max_injection_tokens,
|
memoryMaxInjectionTokens: deps.config.memory?.max_injection_tokens,
|
||||||
toolPolicyContext: {
|
toolPolicyContext: {
|
||||||
agent: effectiveTier,
|
agent: effectiveTier,
|
||||||
provider: effectiveProvider,
|
provider: effectiveProvider,
|
||||||
sessionId: session.id,
|
sessionId: session.id,
|
||||||
channel,
|
channel,
|
||||||
sender: senderId,
|
sender: senderId,
|
||||||
tier: effectiveTier,
|
tier: effectiveTier,
|
||||||
autonomyLevel: deps.config.agents.autonomy_level ?? 'standard',
|
autonomyLevel: deps.config.agents.autonomy_level ?? 'standard',
|
||||||
skillName: activeSkillName,
|
skillName: activeSkillName,
|
||||||
skillPermissions: activeSkill?.manifest.permissions,
|
skillPermissions: activeSkill?.manifest.permissions,
|
||||||
allowedSecretScopes: activeSkill?.manifest.permissions?.secrets,
|
allowedSecretScopes: activeSkill?.manifest.permissions?.secrets,
|
||||||
executionEnvironment,
|
executionEnvironment,
|
||||||
},
|
},
|
||||||
attachmentCollector: collector,
|
attachmentCollector: collector,
|
||||||
});
|
});
|
||||||
entry = { orchestrator, collector };
|
entry = { orchestrator, collector };
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export function App({
|
|||||||
if (!hookEngine) {return;}
|
if (!hookEngine) {return;}
|
||||||
|
|
||||||
hookEngine.setInteractiveConfirmer(async (pending) => {
|
hookEngine.setInteractiveConfirmer(async (pending) => {
|
||||||
return await new Promise<HookResult>((resolve) => {
|
return new Promise<HookResult>((resolve) => {
|
||||||
confirmResolveRef.current = resolve;
|
confirmResolveRef.current = resolve;
|
||||||
setConfirmation({ tool: pending.tool, args: pending.args });
|
setConfirmation({ tool: pending.tool, args: pending.args });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import type { GatewayResponse, GatewayError, GatewayEvent } from './protocol.js'
|
|||||||
import { ErrorCode } from './protocol.js';
|
import { ErrorCode } from './protocol.js';
|
||||||
|
|
||||||
async function canListenOnLocalhost(): Promise<boolean> {
|
async function canListenOnLocalhost(): Promise<boolean> {
|
||||||
return await new Promise((resolvePromise) => {
|
return new Promise((resolvePromise) => {
|
||||||
const s = createServer();
|
const s = createServer();
|
||||||
s.once('error', () => resolvePromise(false));
|
s.once('error', () => resolvePromise(false));
|
||||||
s.listen(0, '127.0.0.1', () => {
|
s.listen(0, '127.0.0.1', () => {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ let _el = null;
|
|||||||
async function loadSettings() {
|
async function loadSettings() {
|
||||||
if (!_client || !_el) {return;}
|
if (!_client || !_el) {return;}
|
||||||
|
|
||||||
let config, tools, channels;
|
let config, tools, channels;
|
||||||
let services;
|
let services;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -101,18 +101,18 @@ async function loadSettings() {
|
|||||||
${serviceList.length > 0 ? `
|
${serviceList.length > 0 ? `
|
||||||
<div class="services-grid">
|
<div class="services-grid">
|
||||||
${serviceList.map(svc => {
|
${serviceList.map(svc => {
|
||||||
const typeIcon = svc.type === 'channel' ? '📡' : svc.type === 'automation' ? '⚙️' : '🔧';
|
const typeIcon = svc.type === 'channel' ? '📡' : svc.type === 'automation' ? '⚙️' : '🔧';
|
||||||
const statusClass = svc.status === 'connected'
|
const statusClass = svc.status === 'connected'
|
||||||
? 'connected'
|
? 'connected'
|
||||||
: svc.status === 'configured'
|
: svc.status === 'configured'
|
||||||
? 'configured'
|
? 'configured'
|
||||||
: svc.status === 'error'
|
: svc.status === 'error'
|
||||||
? 'error'
|
? 'error'
|
||||||
: svc.status === 'not_configured'
|
: svc.status === 'not_configured'
|
||||||
? 'not-configured'
|
? 'not-configured'
|
||||||
: 'disconnected';
|
: 'disconnected';
|
||||||
const itemCount = svc.itemCount ? ` (${svc.itemCount})` : '';
|
const itemCount = svc.itemCount ? ` (${svc.itemCount})` : '';
|
||||||
return `
|
return `
|
||||||
<div class="service-card service-${statusClass}">
|
<div class="service-card service-${statusClass}">
|
||||||
<span class="service-type-icon">${typeIcon}</span>
|
<span class="service-type-icon">${typeIcon}</span>
|
||||||
<span class="service-name">${escapeHtml(svc.name)}${itemCount}</span>
|
<span class="service-name">${escapeHtml(svc.name)}${itemCount}</span>
|
||||||
@@ -120,7 +120,7 @@ async function loadSettings() {
|
|||||||
<span class="service-description text-muted text-xs">${escapeHtml(svc.description ?? '')}</span>
|
<span class="service-description text-muted text-xs">${escapeHtml(svc.description ?? '')}</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}).join('')}
|
}).join('')}
|
||||||
</div>
|
</div>
|
||||||
` : '<div class="text-muted text-sm">No services found</div>'}
|
` : '<div class="text-muted text-sm">No services found</div>'}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ export class HookEngine {
|
|||||||
const id = randomUUID();
|
const id = randomUUID();
|
||||||
|
|
||||||
if (this.interactiveConfirmer) {
|
if (this.interactiveConfirmer) {
|
||||||
return await this.interactiveConfirmer({ id, tool, args });
|
return this.interactiveConfirmer({ id, tool, args });
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const cancellableTool: Tool = {
|
|||||||
description: 'Long-running cancellable tool',
|
description: 'Long-running cancellable tool',
|
||||||
inputSchema: { type: 'object', properties: {} },
|
inputSchema: { type: 'object', properties: {} },
|
||||||
execute: async (_args, context) => {
|
execute: async (_args, context) => {
|
||||||
return await new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const onAbort = () => resolve({ success: false, output: '', error: 'aborted' });
|
const onAbort = () => resolve({ success: false, output: '', error: 'aborted' });
|
||||||
if (context?.signal?.aborted) {
|
if (context?.signal?.aborted) {
|
||||||
onAbort();
|
onAbort();
|
||||||
@@ -65,7 +65,7 @@ function createSideEffectTool(sideEffect: { fired: boolean }): Tool {
|
|||||||
description: 'Cancellable side effect',
|
description: 'Cancellable side effect',
|
||||||
inputSchema: { type: 'object', properties: {} },
|
inputSchema: { type: 'object', properties: {} },
|
||||||
execute: async (_args, context) => {
|
execute: async (_args, context) => {
|
||||||
return await new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
sideEffect.fired = true;
|
sideEffect.fired = true;
|
||||||
resolve({ success: true, output: 'side effect fired' });
|
resolve({ success: true, output: 'side effect fired' });
|
||||||
|
|||||||
Reference in New Issue
Block a user