merge: integrate remote and local main histories

# Conflicts:
#	.project-docs/20-architecture/module-map.md
#	.project-docs/30-worklog/current-state.md
#	.project-docs/50-evidence/evidence-index.md
#	src/pages/MyPlugins/index.tsx
#	tests/unit/pi-agent-server-process-real.test.ts
#	tests/unit/pi-managed-worker-opener.test.ts
#	tests/unit/plugin-marketplace-pages.test.tsx
This commit is contained in:
inman
2026-09-03 10:44:04 +08:00
122 changed files with 10866 additions and 436 deletions

View File

@@ -1,8 +1,12 @@
import type { CapabilityResultV1 } from './data-service';
import type { ModelToolDetailsV1 } from './model-tools';
import type { DevicePackageToolDetailsV1 } from './device-packages';
export type { CapabilityBillingReceiptV1, CapabilityResultV1 } from './data-service';
export type { ModelToolDetailsV1 } from './model-tools';
export type { DevicePackageToolDetailsV1 } from './device-packages';
export type ConversationThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high';
export type ConversationThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'max';
export interface ProductModelRef {
accountId: string;
@@ -220,6 +224,8 @@ export type KnownToolDetails =
| GameAssetsDetailsV1
| RuntimeContextDetailsV1
| CapabilityResultV1
| ModelToolDetailsV1
| DevicePackageToolDetailsV1
| SubagentDetailsV1;
export interface ConversationToolNode {

View File

@@ -8,6 +8,14 @@ import type {
RuntimeContextDetailsV1,
TaskStateDetailsV1,
} from './coding-conversation-contracts';
import type { ModelToolDetailsV1, ModelWebSearchErrorCode } from './model-tools';
import {
DEVICE_PACKAGE_TOOL_NAMES,
type DevicePackageIndexV1,
type DevicePackageRecordV1,
type DevicePackageToolDetailsV1,
type InstallPreviewV1,
} from './device-packages';
import { DATA_SERVICE_PI_TOOL_NAMES } from './data-service';
const PRODUCT_TOOL_NAMES = new Set([
@@ -17,6 +25,8 @@ const PRODUCT_TOOL_NAMES = new Set([
'task_state',
'changed_file',
'runtime_context',
'web_search',
...DEVICE_PACKAGE_TOOL_NAMES,
...DATA_SERVICE_PI_TOOL_NAMES,
]);
@@ -192,6 +202,11 @@ function capabilityBilling(value: unknown): CapabilityBillingReceiptV1 | null {
? { mode: 'external_account', status: 'external' }
: null;
}
if (billing.mode === 'platform_metered' && billing.status === 'receipt_unavailable') {
return exactKeys(billing, ['mode', 'status'])
? { mode: 'platform_metered', status: 'receipt_unavailable' }
: null;
}
if (billing.mode !== 'platform_metered' || typeof billing.reserved_points !== 'string'
|| !CAPABILITY_DECIMAL_PATTERN.test(billing.reserved_points)
|| billing.reserved_points.length > 32) return null;
@@ -234,6 +249,7 @@ function capabilityDetails(value: Record<string, unknown>): CapabilityResultV1 |
|| value.schema !== 'makelore-capability.v1') return null;
if (typeof value.plugin_id !== 'string' || value.plugin_id.length > 48
|| !CAPABILITY_PLUGIN_ID_PATTERN.test(value.plugin_id)
|| value.plugin_id === 'makelore.web-search'
|| !text(value.plugin_version, 64)
|| typeof value.capability_id !== 'string' || !CAPABILITY_ID_PATTERN.test(value.capability_id)
|| typeof value.operation !== 'string' || !CAPABILITY_OPERATION_PATTERN.test(value.operation)
@@ -293,6 +309,186 @@ function capabilityDetails(value: Record<string, unknown>): CapabilityResultV1 |
};
}
const MODEL_WEB_SEARCH_ERROR_CONTRACT = Object.freeze({
model_web_search_unsupported: { httpStatus: 400, retryable: false },
model_context_changed: { httpStatus: 409, retryable: false },
model_web_search_rate_limited: { httpStatus: 429, retryable: false },
model_web_search_unavailable: { httpStatus: 503, retryable: true },
model_web_search_invalid_result: { httpStatus: 502, retryable: false },
} satisfies Record<ModelWebSearchErrorCode, { httpStatus: number; retryable: boolean }>);
function modelToolSource(value: unknown): { title: string; url: string } | null {
const source = record(value);
const title = text(source?.title, 240);
const rawUrl = text(source?.url, 2_048);
if (!source || !exactKeys(source, ['title', 'url']) || !title || !rawUrl) return null;
try {
const url = new URL(rawUrl);
if ((url.protocol !== 'http:' && url.protocol !== 'https:') || url.username || url.password) return null;
return { title, url: url.toString() };
} catch {
return null;
}
}
function modelToolDetails(value: Record<string, unknown>): ModelToolDetailsV1 | null {
if (value.schema !== 'makelore-model-tool.v1' || value.tool !== 'web_search') return null;
const modelId = text(value.modelId, 256);
if (!modelId) return null;
if (value.status === 'succeeded') {
if (!exactKeys(value, [
'schema', 'tool', 'status', 'modelId', 'answer', 'sources', 'sourceMode',
])) return null;
const answer = text(value.answer, 20_000);
if (!answer || !Array.isArray(value.sources) || value.sources.length > 20
|| (value.sourceMode !== 'structured' && value.sourceMode !== 'inline-or-structured')) return null;
const sources = value.sources.map(modelToolSource);
if (sources.some((source) => source === null)
|| (value.sourceMode === 'structured' && sources.length === 0)) return null;
return {
schema: 'makelore-model-tool.v1',
tool: 'web_search',
status: 'succeeded',
modelId,
answer,
sources: sources as Array<{ title: string; url: string }>,
sourceMode: value.sourceMode,
};
}
if (value.status !== 'failed'
|| !exactKeys(value, ['schema', 'tool', 'status', 'modelId', 'error'])) return null;
const error = record(value.error);
const code = error?.code;
const contract = typeof code === 'string'
? MODEL_WEB_SEARCH_ERROR_CONTRACT[code as ModelWebSearchErrorCode]
: undefined;
const message = text(error?.message, 2_000);
if (!error || !contract || !message
|| !exactKeys(error, ['code', 'message', 'httpStatus', 'retryable'])
|| error.httpStatus !== contract.httpStatus
|| error.retryable !== contract.retryable) return null;
return {
schema: 'makelore-model-tool.v1',
tool: 'web_search',
status: 'failed',
modelId,
error: {
code: code as ModelWebSearchErrorCode,
message,
httpStatus: contract.httpStatus as 400 | 409 | 429 | 502 | 503,
retryable: contract.retryable,
},
};
}
function deviceSkillEntries(value: unknown): DevicePackageRecordV1['skillEntries'] | null {
if (!Array.isArray(value) || value.length > 100) return null;
const entries: Array<{ id: string; entryPath: string }> = [];
for (const candidate of value) {
const item = record(candidate);
const id = text(item?.id, 128);
const entryPath = text(item?.entryPath, 1_024);
if (!item || !exactKeys(item, ['id', 'entryPath']) || !id || !entryPath) return null;
entries.push({ id, entryPath });
}
return entries;
}
function devicePackageKind(value: unknown): DevicePackageRecordV1['kind'] | null {
return value === 'skill-only' || value === 'pi-extension' || value === 'mixed' ? value : null;
}
function devicePackagePreview(value: unknown): InstallPreviewV1 | null {
const preview = record(value);
if (!preview || !exactKeys(preview, [
'schemaVersion', 'planId', 'expiresAt', 'requestedSource', 'resolvedSource',
'packageId', 'displayName', 'resolvedVersion', 'kind', 'skillEntries',
'extensionEntries', 'includesExecutableCode', 'ignoredLifecycleScripts', 'warnings',
'scope',
]) || preview.schemaVersion !== 1 || preview.scope !== 'device-parent-workers'
|| typeof preview.includesExecutableCode !== 'boolean') return null;
const planId = text(preview.planId, 128);
const expiresAt = text(preview.expiresAt, 64);
const requestedSource = text(preview.requestedSource, 2_048);
const resolvedSource = text(preview.resolvedSource, 4_096);
const packageId = text(preview.packageId, 128);
const displayName = text(preview.displayName, 240);
const resolvedVersion = text(preview.resolvedVersion, 128);
const kind = devicePackageKind(preview.kind);
const skillEntries = deviceSkillEntries(preview.skillEntries);
const extensionEntries = strings(preview.extensionEntries, 100);
const ignoredLifecycleScripts = strings(preview.ignoredLifecycleScripts, 20);
const warnings = strings(preview.warnings, 20);
if (!planId || !expiresAt || !requestedSource || !resolvedSource || !packageId
|| !displayName || !resolvedVersion || !kind || !skillEntries || !extensionEntries
|| !ignoredLifecycleScripts || !warnings) return null;
return {
schemaVersion: 1, planId, expiresAt, requestedSource, resolvedSource, packageId,
displayName, resolvedVersion, kind, skillEntries, extensionEntries,
includesExecutableCode: preview.includesExecutableCode,
ignoredLifecycleScripts, warnings, scope: 'device-parent-workers',
};
}
function devicePackageRecord(value: unknown): DevicePackageRecordV1 | null {
const item = record(value);
if (!item || !exactKeys(item, [
'schemaVersion', 'packageId', 'displayName', 'resolvedVersion', 'source', 'kind',
'skillEntries', 'extensionEntries', 'enabled', 'confirmedExecutableCode', 'installedAt',
]) || item.schemaVersion !== 1 || typeof item.enabled !== 'boolean'
|| typeof item.confirmedExecutableCode !== 'boolean') return null;
const source = record(item.source);
const packageId = text(item.packageId, 128);
const displayName = text(item.displayName, 240);
const resolvedVersion = text(item.resolvedVersion, 128);
const kind = devicePackageKind(item.kind);
const installedAt = text(item.installedAt, 64);
const skillEntries = deviceSkillEntries(item.skillEntries);
const extensionEntries = strings(item.extensionEntries, 100);
if (!source || !exactKeys(source, ['kind', 'requested', 'resolved'])
|| !['npm', 'git', 'file'].includes(String(source.kind))
|| !packageId || !displayName || !resolvedVersion || !kind || !installedAt
|| !skillEntries || !extensionEntries) return null;
const requested = text(source.requested, 2_048);
const resolved = text(source.resolved, 4_096);
if (!requested || !resolved) return null;
return {
schemaVersion: 1, packageId, displayName, resolvedVersion,
source: { kind: source.kind as DevicePackageRecordV1['source']['kind'], requested, resolved },
kind, skillEntries, extensionEntries, enabled: item.enabled,
confirmedExecutableCode: item.confirmedExecutableCode, installedAt,
};
}
function devicePackageIndex(value: unknown): DevicePackageIndexV1 | null {
const index = record(value);
if (!index || !exactKeys(index, ['schemaVersion', 'generation', 'packages'])
|| index.schemaVersion !== 1 || !Number.isSafeInteger(index.generation)
|| (index.generation as number) < 0 || !Array.isArray(index.packages)
|| index.packages.length > 200) return null;
const packages = index.packages.map(devicePackageRecord);
if (packages.some((item) => item === null)) return null;
return { schemaVersion: 1, generation: index.generation as number, packages: packages as DevicePackageRecordV1[] };
}
function devicePackageDetails(value: Record<string, unknown>): DevicePackageToolDetailsV1 | null {
if (value.schema !== 'makelore-device-package.v1' || value.success !== true
|| !['prepare', 'commit', 'list', 'set_enabled', 'uninstall'].includes(String(value.operation))) return null;
if (value.operation === 'prepare') {
if (!exactKeys(value, ['schema', 'operation', 'success', 'preview'])) return null;
const preview = devicePackagePreview(value.preview);
return preview ? { schema: 'makelore-device-package.v1', operation: 'prepare', success: true, preview } : null;
}
if (!exactKeys(value, ['schema', 'operation', 'success', 'index'])) return null;
const index = devicePackageIndex(value.index);
return index ? {
schema: 'makelore-device-package.v1',
operation: value.operation as Exclude<DevicePackageToolDetailsV1['operation'], 'prepare'>,
success: true,
index,
} : null;
}
export function productToolDetails(value: unknown): Exclude<KnownToolDetails, { schema: 'subagent.v1' | 'write-lease.v1' }> | null {
const details = record(value);
if (!details) return null;
@@ -305,6 +501,8 @@ export function productToolDetails(value: unknown): Exclude<KnownToolDetails, {
if (details.schema === 'game-assets.v1') return gameAssetDetails(details);
if (details.schema === 'runtime-context.v1') return runtimeContextDetails(details);
if (details.schema === 'makelore-capability.v1') return capabilityDetails(details);
if (details.schema === 'makelore-model-tool.v1') return modelToolDetails(details);
if (details.schema === 'makelore-device-package.v1') return devicePackageDetails(details);
return null;
}

View File

@@ -54,7 +54,7 @@ const RUN_STATUSES = new Set([
]);
const WORKER_STATUSES = new Set(['stopped', 'starting', 'ready', 'recovering', 'error']);
const THINKING_LEVELS = new Set(['off', 'minimal', 'low', 'medium', 'high']);
const THINKING_LEVELS = new Set(['off', 'minimal', 'low', 'medium', 'high', 'max']);
const ERROR_CODES = new Set([
'CODING_RUNTIME_START_FAILED',
'CODING_RUNTIME_READY_TIMEOUT',

View File

@@ -101,6 +101,21 @@ export const DATA_SERVICE_ADAPTER_ID = 'data-service' as const;
export const DATA_SERVICE_PROJECT_SETTINGS_SURFACE = 'data-service' as const;
export const DATA_SERVICE_PREVIEW_RUNTIME_SURFACE = 'data-service-v1' as const;
export const GAME_RESOURCE_PLUGIN_ID = 'makelore.game-resource' as const;
export const GAME_RESOURCE_BUNDLED_RELEASE_ID = '00000000-0000-4000-8000-000000000105' as const;
export const CODE_OWNED_OPTIONAL_BUNDLED_RELEASES = Object.freeze({
[GAME_RESOURCE_PLUGIN_ID]: Object.freeze({
releaseId: GAME_RESOURCE_BUNDLED_RELEASE_ID,
version: '1.0.0',
channel: 'stable' as const,
}),
});
export function isCodeOwnedOptionalBundledPluginId(pluginId: string): boolean {
return Object.prototype.hasOwnProperty.call(CODE_OWNED_OPTIONAL_BUNDLED_RELEASES, pluginId);
}
export const DATA_SERVICE_CAPABILITY_IDS = Object.freeze([
'data-service.control',
'data-service.documents',

View File

@@ -100,6 +100,11 @@ export type CapabilityBillingReceiptV1 =
actual_points: string;
usage_amount?: number;
unit?: string;
}
| {
/** Main-only projection used when a possibly-dispatched receipt cannot be read. */
mode: 'platform_metered';
status: 'receipt_unavailable';
};
export interface CapabilityResultV1<T = unknown> {

74
shared/device-packages.ts Normal file
View File

@@ -0,0 +1,74 @@
export type DevicePackageSourceKind = 'npm' | 'git' | 'file';
export type DevicePackageKind = 'skill-only' | 'pi-extension' | 'mixed';
export interface DevicePackageSkillEntry {
id: string;
entryPath: string;
}
export interface InstallPreviewV1 {
schemaVersion: 1;
planId: string;
expiresAt: string;
requestedSource: string;
resolvedSource: string;
packageId: string;
displayName: string;
resolvedVersion: string;
kind: DevicePackageKind;
skillEntries: readonly DevicePackageSkillEntry[];
extensionEntries: readonly string[];
includesExecutableCode: boolean;
ignoredLifecycleScripts: readonly string[];
warnings: readonly string[];
scope: 'device-parent-workers';
}
export interface DevicePackageRecordV1 {
schemaVersion: 1;
packageId: string;
displayName: string;
resolvedVersion: string;
source: Readonly<{
kind: DevicePackageSourceKind;
requested: string;
resolved: string;
}>;
kind: DevicePackageKind;
skillEntries: readonly DevicePackageSkillEntry[];
extensionEntries: readonly string[];
enabled: boolean;
confirmedExecutableCode: boolean;
installedAt: string;
}
export interface DevicePackageIndexV1 {
schemaVersion: 1;
generation: number;
packages: readonly DevicePackageRecordV1[];
}
export type DevicePackageToolOperation =
| 'prepare'
| 'commit'
| 'list'
| 'set_enabled'
| 'uninstall';
export const DEVICE_PACKAGE_TOOL_NAMES = Object.freeze([
'local_package_prepare',
'local_package_commit',
'local_package_list',
'local_package_set_enabled',
'local_package_uninstall',
] as const);
export type DevicePackageToolName = typeof DEVICE_PACKAGE_TOOL_NAMES[number];
export type DevicePackageToolDetailsV1 = Readonly<{
schema: 'makelore-device-package.v1';
operation: DevicePackageToolOperation;
success: true;
preview?: InstallPreviewV1;
index?: DevicePackageIndexV1;
}>;

View File

@@ -1,6 +1,37 @@
export type ImportedModelModality = 'text' | 'audio' | 'image' | 'pdf';
export type ImportedVisionTokenEstimator = 'qwen-32px-grid';
export type ImportedThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high';
export type ImportedThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'max';
export const IMPORTED_REASONING_EFFORTS = ['low', 'high', 'max'] as const;
export type ImportedReasoningEffort = (typeof IMPORTED_REASONING_EFFORTS)[number];
export type ImportedModelWebSearchCapability = Readonly<{
schemaVersion: 1;
adapter: 'openai-responses' | 'bailian-responses' | 'bailian-chat-completions';
supportsForcedSearch: true;
sourceMode: 'structured' | 'inline-or-structured';
billingAuthority: 'model-request';
}>;
export interface ImportedModelCapability {
reasoningEfforts: ImportedReasoningEffort[];
reasoningCanDisable: boolean;
webSearch?: ImportedModelWebSearchCapability;
}
export type ImportedModelCapabilities = Record<string, ImportedModelCapability>;
export function thinkingLevelMapForImportedModelCapability(
capability: ImportedModelCapability,
): Partial<Record<ImportedThinkingLevel, string | null>> {
return {
...(capability.reasoningCanDisable ? {} : { off: null }),
minimal: null,
low: capability.reasoningEfforts.includes('low') ? 'low' : null,
medium: null,
high: capability.reasoningEfforts.includes('high') ? 'high' : null,
max: capability.reasoningEfforts.includes('max') ? 'max' : null,
};
}
export interface ImportedPiModelProfile {
reasoning: boolean;
@@ -62,14 +93,15 @@ export function getImportedModelProfile(rawModelId: string): ImportedModelProfil
pi: {
reasoning: true,
thinkingLevelMap: {
off: null,
minimal: null,
low: null,
low: 'low',
medium: null,
high: 'high',
max: 'max',
},
compat: {
thinkingFormat: 'deepseek',
supportsReasoningEffort: true,
requiresReasoningContentOnAssistantMessages: true,
},
},

36
shared/model-tools.ts Normal file
View File

@@ -0,0 +1,36 @@
export type ModelWebSearchErrorCode =
| 'model_web_search_unsupported'
| 'model_context_changed'
| 'model_web_search_rate_limited'
| 'model_web_search_unavailable'
| 'model_web_search_invalid_result';
export type ModelWebSearchSourceV1 = Readonly<{
title: string;
url: string;
}>;
export type ModelWebSearchSuccessV1 = Readonly<{
schema: 'makelore-model-tool.v1';
tool: 'web_search';
status: 'succeeded';
modelId: string;
answer: string;
sources: ReadonlyArray<ModelWebSearchSourceV1>;
sourceMode: 'structured' | 'inline-or-structured';
}>;
export type ModelWebSearchFailureV1 = Readonly<{
schema: 'makelore-model-tool.v1';
tool: 'web_search';
status: 'failed';
modelId: string;
error: Readonly<{
code: ModelWebSearchErrorCode;
message: string;
httpStatus: 400 | 409 | 429 | 502 | 503;
retryable: boolean;
}>;
}>;
export type ModelToolDetailsV1 = ModelWebSearchSuccessV1 | ModelWebSearchFailureV1;

View File

@@ -1,3 +1,11 @@
import {
IMPORTED_REASONING_EFFORTS,
type ImportedModelCapabilities,
type ImportedModelCapability,
type ImportedModelWebSearchCapability,
type ImportedReasoningEffort,
} from './imported-model-profile';
export const NIANCODE_USER_MODEL_ACCOUNT_ID = 'niancode-user-models';
export const NIANCODE_USER_MODEL_ACCOUNT_LABEL = 'Makelore Models';
@@ -23,6 +31,94 @@ export function normalizeImportedUserModelId(rawModel: string): string {
: trimmed;
}
function normalizeImportedModelCapability(value: unknown): ImportedModelCapability | null {
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
const record = value as Record<string, unknown>;
const rawEfforts = record.reasoning_efforts ?? record.reasoningEfforts;
const reasoningCanDisable = record.reasoning_can_disable ?? record.reasoningCanDisable;
if (!Array.isArray(rawEfforts) || typeof reasoningCanDisable !== 'boolean') return null;
const reasoningEfforts = IMPORTED_REASONING_EFFORTS.filter((effort) => (
rawEfforts.some((candidate) => candidate === effort)
)) as ImportedReasoningEffort[];
if (reasoningEfforts.length === 0 && rawEfforts.length > 0) return null;
const webSearch = normalizeImportedModelWebSearchCapability(
record.web_search ?? record.webSearch,
);
return {
reasoningEfforts,
reasoningCanDisable,
...(webSearch ? { webSearch } : {}),
};
}
function normalizeImportedModelWebSearchCapability(
value: unknown,
): ImportedModelWebSearchCapability | null {
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
const record = value as Record<string, unknown>;
const schemaVersion = record.schema_version ?? record.schemaVersion;
const rawAdapter = record.adapter;
const supportsForcedSearch = record.supports_forced_search ?? record.supportsForcedSearch;
const rawSourceMode = record.source_mode ?? record.sourceMode;
const rawBillingAuthority = record.billing_authority ?? record.billingAuthority;
const adapters: Record<string, ImportedModelWebSearchCapability['adapter']> = {
openai_responses: 'openai-responses',
'openai-responses': 'openai-responses',
bailian_responses: 'bailian-responses',
'bailian-responses': 'bailian-responses',
bailian_chat_completions: 'bailian-chat-completions',
'bailian-chat-completions': 'bailian-chat-completions',
};
const sourceModes: Record<string, ImportedModelWebSearchCapability['sourceMode']> = {
structured: 'structured',
inline_or_structured: 'inline-or-structured',
'inline-or-structured': 'inline-or-structured',
};
const billingAuthorities: Record<string, ImportedModelWebSearchCapability['billingAuthority']> = {
model_request: 'model-request',
'model-request': 'model-request',
};
const adapter = typeof rawAdapter === 'string' ? adapters[rawAdapter] : undefined;
const sourceMode = typeof rawSourceMode === 'string' ? sourceModes[rawSourceMode] : undefined;
const billingAuthority = typeof rawBillingAuthority === 'string'
? billingAuthorities[rawBillingAuthority]
: undefined;
if (
schemaVersion !== 1
|| !adapter
|| supportsForcedSearch !== true
|| !sourceMode
|| !billingAuthority
) {
return null;
}
return {
schemaVersion: 1,
adapter,
supportsForcedSearch: true,
sourceMode,
billingAuthority,
};
}
export function normalizeImportedModelCapabilities(
value: unknown,
modelIds?: readonly string[],
): ImportedModelCapabilities | undefined {
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
const allowedModelIds = modelIds
? new Set(modelIds.map(normalizeImportedUserModelId))
: null;
const result: ImportedModelCapabilities = {};
for (const [rawModelId, rawCapability] of Object.entries(value as Record<string, unknown>)) {
const modelId = normalizeImportedUserModelId(rawModelId);
if (!modelId || (allowedModelIds && !allowedModelIds.has(modelId))) continue;
const capability = normalizeImportedModelCapability(rawCapability);
if (capability && !result[modelId]) result[modelId] = capability;
}
return Object.keys(result).length > 0 ? result : undefined;
}
export function selectUserModelRuntimeAccounts<T extends NianCodeUserModelAccountLike>(
accounts: T[],
): T[] {