fix: ci and static

This commit is contained in:
imccyu
2026-08-13 01:00:49 +08:00
parent a7d4cd8e1b
commit 5a0283b04f
23 changed files with 260 additions and 207 deletions

View File

@@ -12,6 +12,7 @@
* @module @deepseek-ai/dsh-cordis-client-runner/client/api-catalog
*/
/* jscpd:ignore-start */
/** One named parameter in a Service method or Event listener. */
export interface ApiParameter {
/** Parameter name from the exact signature. */
@@ -967,3 +968,4 @@ export function queryEventApi(name?: string, events: readonly EventApiEntry[] =
referencedTypes: referencedTypeClosure([event.signature]),
}
}
/* jscpd:ignore-end */

View File

@@ -279,7 +279,7 @@ export function apply(ctx: Context): void {
activeRuns: orchestrator.activeRuns,
lastRunError: orchestrator.lastRunError,
renderFailures: runner.renderFailures,
reconcileApprovals: rows => { orchestrator.reconcileApprovals(rows) },
reconcileApprovals: (rows) => { orchestrator.reconcileApprovals(rows) },
approve: (requestId, approveFutureVersions) => orchestrator.approve(requestId, approveFutureVersions),
decline: requestId => orchestrator.decline(requestId),
startUserRun: request => orchestrator.startUserRun(request),

View File

@@ -3,12 +3,13 @@
import type { Context } from '@deepseek-ai/cordis'
import type { JsonValue } from '@deepseek-ai/dsh-api-remotes/client'
import type { SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
import type { ThemeService } from '@deepseek-ai/dsh-client-ui-theme/client'
import type {} from '@deepseek-ai/dsh-client-ui-theme/client'
import { queryEventApi, queryServiceApi } from './api-catalog.ts'
import type { ClientCordisInspectProviderRegistration } from './inspect-registry.ts'
import { CLIENT_SLOT_API } from './slot-catalog.ts'
import type { ClientSlotEntry } from './slot-catalog.ts'
/* jscpd:ignore-start */
const EMPTY_INPUT = { type: 'object', properties: {}, additionalProperties: false } as const
const ANY_OUTPUT = { description: 'JSON data owned by this inspect provider.' } as const
const SERVICE_INPUT = exactInput('service', 'Exact Service key. Omit it for the compact Service and method-signature directory.')
@@ -19,6 +20,7 @@ const SERVICE_OUTPUT = {
const EVENT_OUTPUT = {
description: 'Compact Event directory, or one exact Event contract with only its referenced type declarations.',
} as const
/* jscpd:ignore-end */
const SUBTREE_OUTPUT = {
description: 'Compact purpose/topology trees. With root, selected also contains that Slot\'s full contract and live occupants.',
} as const
@@ -78,7 +80,7 @@ export function clientInspectProviders(ctx: Context): ClientCordisInspectProvide
'Service',
'Progressive Client Service discovery: compact capability/signature directory, then one exact coding contract.',
'listService',
async input => queryServiceApi(readExact(input, 'service')) as unknown as JsonValue,
input => queryServiceApi(readExact(input, 'service')) as unknown as JsonValue,
SERVICE_INPUT,
SERVICE_OUTPUT,
),
@@ -86,11 +88,11 @@ export function clientInspectProviders(ctx: Context): ClientCordisInspectProvide
'Event',
'Progressive Client Event discovery: compact listener directory, then one exact event contract.',
'listEvents',
async input => queryEventApi(readExact(input, 'event')) as unknown as JsonValue,
input => queryEventApi(readExact(input, 'event')) as unknown as JsonValue,
EVENT_INPUT,
EVENT_OUTPUT,
),
registration('Builtin', 'Plain-JavaScript symbols available to a dynamic Client half.', 'listBuiltins', async () => ({
registration('Builtin', 'Plain-JavaScript symbols available to a dynamic Client half.', 'listBuiltins', () => ({
builtins: [...CLIENT_BUILTIN_INSPECTION],
referencedTypes: [],
})),
@@ -105,35 +107,36 @@ export function clientInspectProviders(ctx: Context): ClientCordisInspectProvide
outputSchema: SUBTREE_OUTPUT,
}],
},
async query(method, input) {
query(method, input) {
if (method !== 'listSubTree') throw new Error(`unknown Slots inspect method "${method}"`)
const slots = ctx.get('slots') as SlotsService | undefined
const slots = ctx.get('slots')
if (slots === undefined) throw new Error('Client Slots service is not running')
const root = typeof input === 'object' && input !== null && !Array.isArray(input)
&& typeof input.root === 'string' ? input.root : undefined
const trees = slots.snapshot(root)
const selected = trees[0]
return {
return Promise.resolve({
...root === undefined ? {} : { requestedRoot: { name: root, available: trees.length > 0 } },
trees: trees.map(compactSlotTree),
...root === undefined || selected === undefined ? {} : { selected: inspectLiveSlot(selected) },
referencedTypes: [],
} as unknown as JsonValue
})
},
},
registration('Theme', 'Current theme token names and light/dark override requirements.', 'listTokens', async () => {
const theme = ctx.get('theme') as ThemeService | undefined
registration('Theme', 'Current theme token names and light/dark override requirements.', 'listTokens', () => {
const theme = ctx.get('theme')
if (theme === undefined) throw new Error('Client Theme service is not running')
return { tokens: theme.exportInspectTokens(), referencedTypes: [] } as unknown as JsonValue
}),
]
}
/* jscpd:ignore-start */
function registration(
id: string,
description: string,
method: string,
query: (input: JsonValue | undefined) => Promise<JsonValue>,
query: (input: JsonValue | undefined) => JsonValue | Promise<JsonValue>,
inputSchema: JsonValue = EMPTY_INPUT,
outputSchema: JsonValue = ANY_OUTPUT,
): ClientCordisInspectProviderRegistration {
@@ -164,6 +167,7 @@ function readExact(input: JsonValue | undefined, field: string): string | undefi
const value = input[field]
return typeof value === 'string' ? value : undefined
}
/* jscpd:ignore-end */
type LiveSlotNode = ReturnType<SlotsService['snapshot']>[number]
@@ -188,7 +192,7 @@ function compactSlotTree(node: LiveSlotNode): JsonValue {
...catalog.keyDomain === '' ? {} : { keyDomain: catalog.keyDomain },
},
children: node.children.map(compactSlotTree),
} as unknown as JsonValue
}
}
function inspectLiveSlot(node: LiveSlotNode): JsonValue {
@@ -200,7 +204,7 @@ function inspectLiveSlot(node: LiveSlotNode): JsonValue {
...node.declaredBy === undefined ? {} : { declaredBy: node.declaredBy },
occupants: node.occupants.map(occupant => ({ ...occupant })),
...catalog === undefined ? {} : { catalog: inspectSlotCatalog(catalog) },
} as unknown as JsonValue
}
}
function inspectSlotCatalog(entry: ClientSlotEntry): JsonValue {
@@ -219,5 +223,5 @@ function inspectSlotCatalog(entry: ClientSlotEntry): JsonValue {
hookContext: entry.hookContext,
slotInject: entry.slotInject,
replaceRisk: entry.replaceRisk,
} as unknown as JsonValue
}
}

View File

@@ -422,12 +422,9 @@ export class DynamicCordisPackageRunner {
ledger,
claim,
allocatePriority: () => --this.nextPriority,
reportFailure: error => this.env.reportGuardFailure(
agentId,
pkg.pluginId,
pkg.pluginRunId,
errorDetails(error),
),
reportFailure: (error) => {
this.env.reportGuardFailure(agentId, pkg.pluginId, pkg.pluginRunId, errorDetails(error))
},
})
if (typeof plugin === 'function') {
return { name: moduleIdOf(pkg.pluginId), apply: (ctx: unknown) => plugin(guarded(ctx)) }
@@ -485,12 +482,16 @@ function indexable(component: unknown): component is object {
* @param error - original thrown value.
* @returns its message and original string stack, when present.
*/
/* jscpd:ignore-start */
export function errorDetails(error: unknown): CordisErrorDetails {
if (typeof error !== 'object' || error === null) return { message: String(error) }
const message = 'message' in error && typeof error.message === 'string' ? error.message : String(error)
const message = 'message' in error && typeof error.message === 'string'
? error.message
: Object.prototype.toString.call(error)
const stack = 'stack' in error && typeof error.stack === 'string' ? error.stack : undefined
return { message, ...stack === undefined ? {} : { stack } }
}
/* jscpd:ignore-end */
/**
* What the authoring session reads about one render crash. The slot says where it

View File

@@ -13,6 +13,7 @@
* @module @deepseek-ai/dsh-cordis-client-runner/client/slot-catalog
*/
/* jscpd:ignore-start */
/** One option a register call passes for a given slot cardinality. */
export interface ClientSlotOption {
/** Option name as written in the register options object. */
@@ -77,7 +78,6 @@ export const CLIENT_NOTES: readonly string[] = [
// Seats of one cardinality repeat their register options and framework props
// verbatim; that sameness IS the contract a registrant reads, so clone
// detection is told to skip the data rather than the file.
/* jscpd:ignore-start */
export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
{
key: 'conversation',

View File

@@ -3,6 +3,17 @@
import { Service } from '@deepseek-ai/cordis'
import type { Context } from '@deepseek-ai/cordis'
/*
* The browser Service preserves the vendored Host TimerService's erased callback tuples and arbitrary
* async-iterator return and rejection values, so narrowing these positions would change the public API.
*/
/* oxlint-disable typescript/no-explicit-any -- Exact Host TimerService API compatibility; see above. */
/* oxlint-disable typescript/no-unsafe-argument -- The erased callback tuples pass through unchanged. */
/* oxlint-disable typescript/no-unsafe-assignment -- The erased callback tuples pass through unchanged. */
/* oxlint-disable typescript/no-unsafe-member-access -- The returned wrapper retains its dispose property. */
/* oxlint-disable typescript/no-unsafe-return -- The erased generic return values pass through unchanged. */
/* oxlint-disable typescript/prefer-promise-reject-errors -- Async iterators preserve arbitrary throw reasons. */
declare module '@deepseek-ai/cordis' {
interface Context extends Pick<ClientTimerService, 'interval' | 'timeout' | 'throttle' | 'debounce' | 'setTimeout' | 'setInterval'> {
/** Browser timer Service used by the mixed-in Context helpers. */
@@ -64,7 +75,7 @@ export class ClientTimerService extends Service {
if (callback !== undefined) {
const dispose = this.ctx.effect(() => {
const timer = globalThis.setTimeout(() => {
dispose()
void dispose()
callback()
}, delay)
return () => { globalThis.clearTimeout(timer) }
@@ -80,7 +91,7 @@ export class ClientTimerService extends Service {
reject(new Error('Context has been disposed'))
}
}, 'ctx.timeout()')
return promise.finally(dispose)
return promise.finally(() => { void dispose() })
}
/**
@@ -128,13 +139,13 @@ export class ClientTimerService extends Service {
return: (value: any) => {
if (done === undefined) done = { kind: 'return', value }
nextTask?.resolve({ done: true, value })
dispose()
void dispose()
return Promise.resolve({ done: true, value })
},
throw: (reason: any) => {
if (done === undefined) done = { kind: 'throw', reason }
nextTask?.reject(reason)
dispose()
void dispose()
return Promise.resolve({ done: true, value: undefined })
},
[Symbol.asyncIterator]() {

View File

@@ -5,6 +5,8 @@
* what is under test is the round trip itself — the engine has its own account in
* runner.spec.
*/
/* oxlint-disable typescript/no-unsafe-assignment -- Vitest asymmetric matchers are typed as any. */
import { describe, expect, it, vi } from 'vitest'
import type {
ApprovalRequestId, CordisDynamicPackageId, CordisDynamicPluginId, CordisDynamicPluginRunId,

View File

@@ -6,6 +6,8 @@
* always reaches the console, and the fiber owns the runner's teardown. Plus the two plane-level companions: the
* node half's empty apply and the invariant registration.
*/
/* oxlint-disable typescript/no-unsafe-assignment -- Vitest asymmetric matchers are typed as any. */
import { Context } from '@deepseek-ai/cordis'
import { describe, expect, it, vi } from 'vitest'
import InvariantService from '@deepseek-ai/dsh-invariants'

View File

@@ -9,6 +9,8 @@
* guarded surface as a genuine plugin, or neither activation gating nor the
* disposal cascade under test would be real.
*/
/* oxlint-disable typescript/no-unsafe-assignment -- Vitest asymmetric matchers are typed as any. */
import { Context } from '@deepseek-ai/cordis'
import type { Loader } from '@deepseek-ai/cordis-plugin-loader'
import { describe, expect, it, vi } from 'vitest'

View File

@@ -7,7 +7,7 @@
import { Context } from '@deepseek-ai/cordis'
import type { Fiber } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'
import type { Agent, AgentRegistry } from '@deepseek-ai/dsh-agent'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { createUserMessage } from '@deepseek-ai/dsh-llm'
import type { JsonValue } from '@deepseek-ai/dsh-session/types'
import { GatewayService, Remote } from '@deepseek-ai/dsh-type-meta'
@@ -520,6 +520,7 @@ export class DynamicCordisRunnerService extends GatewayService {
* Frame-wide inventory, grouped as one row per stable Plugin.
* @returns Source-free metadata for every process-local Plugin.
*/
/* jscpd:ignore-start */
@Remote('inventory')
inventory(): DynamicCordisInventoryRow[] {
return this.registry.all().map(plugin => ({
@@ -540,6 +541,7 @@ export class DynamicCordisRunnerService extends GatewayService {
...plugin.latestRun === undefined ? {} : { latestRun: cloneAttempt(plugin.latestRun) },
}))
}
/* jscpd:ignore-end */
/**
* Read one Session's Host-rich state for inspection and result rendering.
@@ -659,12 +661,14 @@ export class DynamicCordisRunnerService extends GatewayService {
...definition.hostCode === undefined ? {} : { host: definition.hostCode },
...definition.clientCode === undefined ? {} : { client: definition.clientCode },
},
/* jscpd:ignore-start */
...plugin.currentPackageId === undefined ? {} : { currentPackageId: plugin.currentPackageId },
...plugin.nextPackageId === undefined ? {} : { nextPackageId: plugin.nextPackageId },
...plugin.run === undefined ? {} : {
activeRun: { pluginRunId: plugin.run.pluginRunId, packageId: plugin.run.packageId },
},
...plugin.latestRun === undefined ? {} : { latestRun: cloneAttempt(plugin.latestRun) },
/* jscpd:ignore-end */
}
}
@@ -846,7 +850,7 @@ export class DynamicCordisRunnerService extends GatewayService {
...requestId === undefined ? {} : { startedForRequest: requestId },
}
if (definition.hostCode !== undefined) {
const failure = await this.startHost(plugin, definition, run)
const failure = await this.startHost(plugin, definition.hostCode, run)
if (failure !== undefined) return { ok: false, ...failure }
}
plugin.run = run
@@ -878,7 +882,7 @@ export class DynamicCordisRunnerService extends GatewayService {
private async startHost(
plugin: DynamicCordisPlugin,
definition: DynamicCordisDefinition,
hostCode: string,
run: DynamicCordisRun,
): Promise<CordisErrorDetails | undefined> {
const handle = (method: unknown, fn: unknown): (() => void) => {
@@ -892,7 +896,7 @@ export class DynamicCordisRunnerService extends GatewayService {
}
try {
const sandbox = createSandbox(plugin.pluginId, { handle })
const evaluated = await evaluateHostCode(sandbox, definition.hostCode!, plugin.pluginId, this.resolved.vmTimeoutMs)
const evaluated = await evaluateHostCode(sandbox, hostCode, plugin.pluginId, this.resolved.vmTimeoutMs)
if (!isPlugin(evaluated)) {
throw new Error(evaluated === undefined
? 'the Host half returned `undefined` — did you forget `return`?'
@@ -901,7 +905,7 @@ export class DynamicCordisRunnerService extends GatewayService {
run.fiber = await startHostHalf(
this.requireGroup(),
evaluated,
error => this.steerGuardFailure(plugin, run, 'Host', errorDetails(error)),
(error) => { this.steerGuardFailure(plugin, run, 'Host', errorDetails(error)) },
)
return undefined
} catch (error) {
@@ -1016,7 +1020,7 @@ export class DynamicCordisRunnerService extends GatewayService {
pending: DynamicCordisPendingRequest,
settled: DynamicCordisRunResponse,
): void {
const agents = this.rootCtx.get('agents') as AgentRegistry | undefined
const agents = this.rootCtx.get('agents')
const agent = agents?.get(pending.agentId)
if (agent === undefined) return
const plugin = this.registry.get(pending.pluginId)
@@ -1071,7 +1075,7 @@ export class DynamicCordisRunnerService extends GatewayService {
): void {
const reportKey = `Host\u0000handler\u0000${method}\u0000${failure.message}`
if (!this.claimRuntimeFailure(plugin, run, reportKey)) return
const agents = this.rootCtx.get('agents') as AgentRegistry | undefined
const agents = this.rootCtx.get('agents')
const agent = agents?.get(plugin.sessionId)
if (agent === undefined) return
agent.steer(createUserMessage({
@@ -1088,6 +1092,7 @@ export class DynamicCordisRunnerService extends GatewayService {
}))
}
/* jscpd:ignore-start */
private steerGuardFailure(
plugin: DynamicCordisPlugin,
run: DynamicCordisRun,
@@ -1096,7 +1101,7 @@ export class DynamicCordisRunnerService extends GatewayService {
): void {
const reportKey = `${platform}\u0000guard\u0000${failure.message}`
if (!this.claimRuntimeFailure(plugin, run, reportKey)) return
const agents = this.rootCtx.get('agents') as AgentRegistry | undefined
const agents = this.rootCtx.get('agents')
const agent = agents?.get(plugin.sessionId)
if (agent === undefined) return
agent.steer(createUserMessage({
@@ -1110,6 +1115,7 @@ export class DynamicCordisRunnerService extends GatewayService {
source: { kind: 'plugin', plugin: 'cordis-host-runner' },
}))
}
/* jscpd:ignore-end */
private claimRuntimeFailure(plugin: DynamicCordisPlugin, run: DynamicCordisRun, key: string): boolean {
const attempt = plugin.latestRun
@@ -1142,7 +1148,7 @@ export class DynamicCordisRunnerService extends GatewayService {
}
private injectUserContext(agent: Agent, text: string): void {
const agents = this.rootCtx.get('agents') as AgentRegistry | undefined
const agents = this.rootCtx.get('agents')
if (agents?.get(agent.id) !== agent) return
agent.inject(createUserMessage({
content: [{ type: 'text', text }],
@@ -1244,7 +1250,9 @@ function missingPluginMessage(id: CordisDynamicPluginId): string {
function errorDetails(error: unknown): CordisErrorDetails {
if (typeof error !== 'object' || error === null) return { message: String(error) }
const message = 'message' in error && typeof error.message === 'string' ? error.message : String(error)
const message = 'message' in error && typeof error.message === 'string'
? error.message
: Object.prototype.toString.call(error)
const stack = 'stack' in error && typeof error.stack === 'string' ? error.stack : undefined
return { message, ...stack === undefined ? {} : { stack } }
}

View File

@@ -66,33 +66,33 @@ export async function setup(config?: Config): Promise<Harness> {
const answer = gateway.answer
const { requestId, pluginId, packageId, mode } = request
gateway.answering = Promise.resolve().then(async (): Promise<void> => {
if (answer === 'reject') {
await runner.resolveRequestRun(requestId, { ok: false, reason: 'rejected', message: 'not now' })
return
}
const half = await runner.runHostHalf(AGENT_A, pluginId, packageId, mode, requestId, false)
if (!half.ok) {
await runner.resolveRequestRun(requestId, {
ok: false, reason: 'host-half-failed', message: half.message,
})
return
}
if (typeof answer === 'object') {
await runner.resolveRequestRun(requestId, {
ok: false,
reason: 'client-half-failed',
pluginRunId: half.pluginRunId,
startedHere: half.startedHere,
message: answer.clientFails,
})
return
}
const source = runner.getClientCode(AGENT_A, pluginId, half.pluginRunId)
if (answer === 'reject') {
await runner.resolveRequestRun(requestId, { ok: false, reason: 'rejected', message: 'not now' })
return
}
const half = await runner.runHostHalf(AGENT_A, pluginId, packageId, mode, requestId, false)
if (!half.ok) {
await runner.resolveRequestRun(requestId, {
ok: true,
pluginRunId: source.pluginRunId,
...gateway.clientWaitingFor === undefined ? {} : { waitingFor: gateway.clientWaitingFor },
ok: false, reason: 'host-half-failed', message: half.message,
})
return
}
if (typeof answer === 'object') {
await runner.resolveRequestRun(requestId, {
ok: false,
reason: 'client-half-failed',
pluginRunId: half.pluginRunId,
startedHere: half.startedHere,
message: answer.clientFails,
})
return
}
const source = runner.getClientCode(AGENT_A, pluginId, half.pluginRunId)
await runner.resolveRequestRun(requestId, {
ok: true,
pluginRunId: source.pluginRunId,
...gateway.clientWaitingFor === undefined ? {} : { waitingFor: gateway.clientWaitingFor },
})
})
})
for (const name of ['cordis/request-run-resolved', 'cordis/dynamic-package', 'cordis/dynamic-retract'] as const) {

View File

@@ -13,6 +13,7 @@
* @module @deepseek-ai/dsh-tool-cordis/client-catalog
*/
/* jscpd:ignore-start */
/** One option a register call passes for a given slot cardinality. */
export interface ClientSlotOption {
/** Option name as written in the register options object. */
@@ -77,7 +78,6 @@ export const CLIENT_NOTES: readonly string[] = [
// Seats of one cardinality repeat their register options and framework props
// verbatim; that sameness IS the contract a registrant reads, so clone
// detection is told to skip the data rather than the file.
/* jscpd:ignore-start */
export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
{
key: 'conversation',

View File

@@ -29,7 +29,7 @@ export function hostInspectProviders(ctx: Context): HostCordisInspectProviderReg
'Service',
'Progressive Host Service discovery: compact capability/signature directory, then one exact coding contract.',
'listService',
async input => queryServiceApi(readExact(input, 'service')) as unknown as JsonValue,
input => queryServiceApi(readExact(input, 'service')) as unknown as JsonValue,
SERVICE_INPUT,
SERVICE_OUTPUT,
),
@@ -37,11 +37,11 @@ export function hostInspectProviders(ctx: Context): HostCordisInspectProviderReg
'Event',
'Progressive Host Event discovery: compact listener directory, then one exact event contract.',
'listEvents',
async input => queryEventApi(readExact(input, 'event'), HOST_EVENTS) as unknown as JsonValue,
input => queryEventApi(readExact(input, 'event'), HOST_EVENTS) as unknown as JsonValue,
EVENT_INPUT,
EVENT_OUTPUT,
),
registration('Builtin', 'Plain-JavaScript symbols available to a dynamic Host half.', 'listBuiltins', async () => ({
registration('Builtin', 'Plain-JavaScript symbols available to a dynamic Host half.', 'listBuiltins', () => ({
builtins: HOST_BUILTIN_INSPECTION,
referencedTypes: [],
} as unknown as JsonValue)),
@@ -68,7 +68,7 @@ function registration(
id: string,
description: string,
method: string,
query: (input: JsonValue | undefined) => Promise<JsonValue>,
query: (input: JsonValue | undefined) => JsonValue | Promise<JsonValue>,
inputSchema: JsonValue = EMPTY_INPUT,
outputSchema: JsonValue = ANY_OUTPUT,
): HostCordisInspectProviderRegistration {

View File

@@ -116,7 +116,7 @@ export function CordisDefineRow({
{hasSource && activeCode !== null && (
<section className={css.sourceCard}>
<div className={css.sourceTabs} role="tablist" aria-label={t('body.source')}>
{(['client', 'host'] as const).map(source => {
{(['client', 'host'] as const).map((source) => {
const available = source === 'client' ? card.clientCode !== null : card.hostCode !== null
return (
<button

View File

@@ -162,7 +162,7 @@ export function CordisPanel({
const runAction = async (pluginId: CordisDynamicPluginId, action: () => Promise<void | { ok: boolean; message?: string }>) => {
if (pending.has(pluginId)) return
setPending(currentPending => new Set(currentPending).add(pluginId))
setActionErrors(currentErrors => {
setActionErrors((currentErrors) => {
const next = new Map(currentErrors)
next.delete(pluginId)
return next
@@ -178,7 +178,7 @@ export function CordisPanel({
error instanceof Error ? error.message : String(error),
))
} finally {
setPending(currentPending => {
setPending((currentPending) => {
const next = new Set(currentPending)
next.delete(pluginId)
return next
@@ -210,8 +210,9 @@ export function CordisPanel({
const hostFailure = latest?.status === 'failed' ? latest.error : undefined
const renderFailure = renderFailures.get(pluginId)
const actionError = actionErrors.get(pluginId)
const hasFailedTransition = listed?.nextPackageId !== undefined
&& listed.nextPackageId !== listed.currentPackageId
const nextPackageId = listed?.nextPackageId !== undefined
&& listed.nextPackageId !== listed.currentPackageId ? listed.nextPackageId : undefined
const currentPackageId = listed?.currentPackageId
const runMode = listed?.currentPackageId !== undefined
&& selectedPackageId !== listed.currentPackageId ? 'update' as const : 'run' as const
@@ -313,7 +314,7 @@ export function CordisPanel({
onClick={() => { void runAction(pluginId, () => onRun({
agentId: listed.agentId,
pluginId,
packageId: selectedPackageId!,
packageId: selectedPackage.packageId,
mode: runMode,
hasClientHalf: selectedPackage.hasClientHalf,
})) }}
@@ -360,10 +361,10 @@ export function CordisPanel({
)}
</div>
</div>
{awaiting === undefined && hasFailedTransition && listed !== undefined && (
{awaiting === undefined && nextPackageId !== undefined && listed !== undefined && (
<div className={css.transition}>
<span>{listed.currentPackageId === undefined ? '' : t('panel.current', { packageId: listed.currentPackageId })}</span>
<span>{t('panel.next', { packageId: listed.nextPackageId! })}</span>
<span>{currentPackageId === undefined ? '' : t('panel.current', { packageId: currentPackageId })}</span>
<span>{t('panel.next', { packageId: nextPackageId })}</span>
<div className={css.transitionActions}>
<button
type="button"
@@ -371,21 +372,21 @@ export function CordisPanel({
onClick={() => { void runAction(pluginId, () => onRun({
agentId: listed.agentId,
pluginId,
packageId: listed.nextPackageId!,
mode: listed.currentPackageId === undefined ? 'run' : 'update',
hasClientHalf: packageOf(listed, listed.nextPackageId!)?.hasClientHalf === true,
packageId: nextPackageId,
mode: currentPackageId === undefined ? 'run' : 'update',
hasClientHalf: packageOf(listed, nextPackageId)?.hasClientHalf === true,
})) }}
>{t('action.retry')}</button>
{listed.currentPackageId !== undefined && (
{currentPackageId !== undefined && (
<button
type="button"
disabled={busy}
onClick={() => { void runAction(pluginId, () => onRun({
agentId: listed.agentId,
pluginId,
packageId: listed.currentPackageId!,
packageId: currentPackageId,
mode: 'run',
hasClientHalf: packageOf(listed, listed.currentPackageId!)?.hasClientHalf === true,
hasClientHalf: packageOf(listed, currentPackageId)?.hasClientHalf === true,
})) }}
>{t('action.rollback')}</button>
)}

View File

@@ -72,5 +72,5 @@ export function cordisToolViewKey(
pluginId: CordisDynamicPluginId,
packageId: CordisDynamicPackageId,
): CordisToolViewKey {
return `${pluginId}.${packageId}` as CordisToolViewKey
return `${pluginId}.${packageId}`
}

View File

@@ -685,6 +685,7 @@ function renderRuntimeApi(
' * @module @deepseek-ai/dsh-tool-cordis/api-catalog',
' */',
'',
'/* jscpd:ignore-start */',
'/** One named parameter in a Service method or Event listener. */',
'export interface ApiParameter {',
' /** Parameter name from the exact signature. */',
@@ -905,6 +906,7 @@ function renderRuntimeApi(
' referencedTypes: referencedTypeClosure([event.signature]),',
' }',
'}',
'/* jscpd:ignore-end */',
'',
)
return lines.join('\n')