@geminixiang/mikan 1.0.0-beta.35 → 1.0.0-beta.37
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/CHANGELOG.md +15 -0
- package/dist/commands/manifest.d.ts.map +1 -1
- package/dist/commands/manifest.js +2 -2
- package/dist/commands/manifest.js.map +1 -1
- package/dist/commands/sandbox.d.ts.map +1 -1
- package/dist/commands/sandbox.js +61 -2
- package/dist/commands/sandbox.js.map +1 -1
- package/dist/commands/types.d.ts +3 -1
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +61 -13
- package/dist/config.js.map +1 -1
- package/dist/provisioner.d.ts +18 -0
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +77 -11
- package/dist/provisioner.js.map +1 -1
- package/dist/settings-mutation.d.ts +31 -0
- package/dist/settings-mutation.d.ts.map +1 -1
- package/dist/settings-mutation.js +20 -1
- package/dist/settings-mutation.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/web/admin/portal.d.ts.map +1 -1
- package/dist/web/admin/portal.js +137 -8
- package/dist/web/admin/portal.js.map +1 -1
- package/dist/web/admin/types.d.ts +1 -0
- package/dist/web/admin/types.d.ts.map +1 -1
- package/dist/web/admin/types.js.map +1 -1
- package/package.json +1 -1
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AASjC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC","sourcesContent":["import type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport { execFile } from \"child_process\";\nimport { promisify } from \"util\";\nimport type {\n ImmediateEventPayload,\n OneShotEventPayload,\n PeriodicEventPayload,\n} from \"./harness/event-format.js\";\nimport type { ExtensionBlockAction } from \"./harness/extensions/types.js\";\nimport type { SubagentRunStatus } from \"./harness/types.js\";\n\nconst execFileAsync = promisify(execFile);\ntype ExecFileAsync = typeof execFileAsync;\n\n// ── adapter ───────────────────────────────────────────────────────────────────\n\nexport type ConversationKind = \"direct\" | \"shared\";\n\nexport type PlatformName = \"slack\" | \"discord\" | \"telegram\" | \"github\";\n\n/** Canonical platform-plus-raw identifier for one conversation office. */\nexport interface OfficeAddress {\n readonly platform: PlatformName;\n readonly conversationId: string;\n}\n\n/** Stable, filesystem-safe identity derived from an OfficeAddress. */\nexport type OfficeKey = string & { readonly __brand: \"OfficeKey\" };\n\nexport type OfficeMigrationStatus = \"needs-owner\" | \"prepared\" | \"moving\" | \"committed\" | \"failed\";\n\nexport interface OfficeMigrationRecord {\n readonly rawConversationId: string;\n readonly sourceDir: string;\n readonly workspaceRoot: string;\n readonly ownerPlatform?: PlatformName;\n readonly targetDir?: string;\n readonly status: OfficeMigrationStatus;\n readonly error?: string;\n readonly updatedAt: string;\n}\n\n/**\n * Directory entry for one conversation office. The workspace dir name stops\n * carrying the raw platform id once the ADR 0005 layout migration lands, so\n * the registry is the durable raw-id ↔ office mapping used for enumeration.\n */\nexport interface OfficeRecord {\n readonly platform: PlatformName;\n readonly conversationId: string;\n readonly recordedAt: string;\n}\n\nexport interface OfficeRegistryState {\n readonly version: 1;\n readonly enabledPlatforms: readonly PlatformName[];\n readonly offices: readonly OfficeRecord[];\n readonly migrations: readonly OfficeMigrationRecord[];\n}\n\nexport interface OfficeMigrationPreparation {\n readonly rawConversationId: string;\n readonly sourceDir: string;\n readonly workspaceRoot: string;\n readonly ownerPlatform?: PlatformName;\n}\n\n/**\n * Who can drive conversations on a platform — gates ambient credential policy.\n *\n * - `membership`: only invited workspace/server members (Slack/Discord/Telegram).\n * Safe to copy `sandbox.defaultSharedVault` into new conversation vaults.\n * - `open-trigger`: broader trigger surface (e.g. GitHub repo writers on public\n * issues/PRs). Ambient shared vault must not apply; use host-side platform\n * identity or an explicitly provisioned vault.\n */\nexport type PlatformTrustModel = \"membership\" | \"open-trigger\";\n\nexport interface ConversationMessage {\n id: string;\n /** Canonical identity of the conversation office. */\n address: OfficeAddress;\n /** @deprecated Use address.conversationId except at platform I/O seams. */\n conversationId?: string;\n sessionKey: string;\n conversationKind: ConversationKind;\n userId: string;\n userName?: string;\n text: string;\n attachments?: { name: string; localPath: string }[];\n threadTs?: string;\n}\n\nexport interface ChatToolResult {\n toolName: string;\n label?: string;\n args?: Record<string, unknown>;\n result: string;\n isError: boolean;\n durationMs: number;\n}\n\n/**\n * Derived from the run status rather than restated, so a new terminal status\n * in the harness reaches the dashboard instead of silently rendering as a\n * status the marker tables never learned about.\n */\nexport type SubagentProgressStatus = SubagentRunStatus | \"pending\" | \"running\" | \"skipped\";\n\nexport interface SubagentProgressNode {\n id: string;\n label: string;\n status: SubagentProgressStatus;\n /** Profile the node ran under; the first thing to check when a run is ungrounded. */\n profile?: string;\n turns?: number;\n toolCalls?: number;\n toolCallCounts?: Record<string, number>;\n tokens?: number;\n costUsd?: number;\n durationMs?: number;\n reason?: string;\n cleanupPending?: boolean;\n}\n\nexport interface SubagentProgressSnapshot {\n mode: \"single\" | \"parallel\" | \"dag\";\n nodes: SubagentProgressNode[];\n}\n\nexport interface ConversationResponder {\n respond(text: string): Promise<void>;\n appendResponseDelta?(delta: string): Promise<void>;\n finishResponse?(finalText?: string): Promise<void>;\n replaceResponse(text: string, options?: { createOverflowLink?: () => string }): Promise<void>;\n /**\n * Override only to convert the subagent dashboard for a pipeline that is\n * not response-source Markdown (Telegram HTML). Absent, the harness composes\n * the Markdown dashboard — optionally followed by the final answer — through\n * `replaceResponse`.\n */\n replaceSubagentProgress?(progress: SubagentProgressSnapshot, finalText?: string): Promise<void>;\n respondDiagnostic(text: string, options?: { style?: \"muted\" | \"error\" }): Promise<void>;\n respondToolResult(result: ChatToolResult): Promise<void>;\n setTyping(isTyping: boolean): Promise<void>;\n setWorking(working: boolean): Promise<void>;\n uploadFile(filePath: string, title?: string): Promise<void>;\n /**\n * React to the message that triggered this run with an emoji short name\n * (no colons). Optional: platforms/contexts without reaction support omit\n * it, and callers must handle its absence.\n */\n react?(emoji: string): Promise<void>;\n deleteResponse(): Promise<void>;\n}\n\nexport interface MessagingInfo {\n name: string;\n formattingGuide: string;\n channels: { id: string; name: string }[];\n users: { id: string; userName: string; displayName: string }[];\n /**\n * Trust boundary for ambient credentials. Defaults to `membership` when\n * omitted (closed chat platforms). Open-trigger platforms must set this\n * explicitly so vault policy does not key off platform name strings.\n */\n trustModel?: PlatformTrustModel;\n diagnostics?: {\n showUsageSummary?: boolean;\n };\n}\n\nexport interface ChatAdapter {\n start(): Promise<void>;\n stop(): Promise<void>;\n getMessagingInfo(): MessagingInfo;\n}\n\nexport type AgentEventPayload =\n | { kind: \"sessionStart\" }\n | { kind: \"responseDelta\"; delta: string }\n | { kind: \"responseFinal\"; text: string }\n | { kind: \"diagnostic\"; text: string }\n | { kind: \"toolStart\"; toolId: string; toolName: string; input?: unknown }\n | { kind: \"toolEnd\"; toolId: string }\n | { kind: \"turnEnd\"; awaitingInput?: boolean }\n | { kind: \"sessionEnd\"; reason?: string };\n\nexport interface AgentEventEnvelope {\n source: \"mikan\";\n sessionId: string;\n actorName: string;\n event: AgentEventPayload;\n}\n\n/**\n * A platform-agnostic event (message/mention) that triggers the agent.\n */\nexport interface ConversationEvent {\n type: string;\n /** Canonical identity created by the platform adapter at intake. */\n address: OfficeAddress;\n /** @deprecated Raw platform identifier; valid only at adapter I/O seams. */\n conversationId: string;\n /** Optional alternate conversation identity used for vault routing. */\n vaultConversationId?: string;\n /** Cross-platform conversation shape: direct message vs shared space */\n conversationKind: ConversationKind;\n /** Message timestamp or ID as string */\n ts: string;\n /** Parent message ID for threaded replies (optional) */\n thread_ts?: string;\n /** User ID */\n user: string;\n /** Message text (already stripped of bot mentions) */\n text: string;\n /** Downloaded attachments */\n attachments?: { name: string; localPath: string }[];\n /** Platform-computed session key; overrides default conversationId:thread_ts computation */\n sessionKey?: string;\n}\n\n/**\n * Minimum interface that every platform bot must implement,\n * used by the central handler in main.ts and by EventsWatcher.\n */\nexport interface MessagingBot {\n start(): Promise<void>;\n postMessage(channel: string, text: string): Promise<string>;\n updateMessage(channel: string, ts: string, text: string): Promise<void>;\n /**\n * Add an emoji reaction to a message. `emoji` is a platform-agnostic short\n * name without colons (e.g. \"eyes\", \"white_check_mark\"). Optional so\n * adapters adopt it incrementally; callers must handle its absence.\n */\n addReaction?(channel: string, messageTs: string, emoji: string): Promise<void>;\n /**\n * Upload a host-side file into a conversation. Optional so adapters adopt\n * it incrementally; callers must handle its absence. Existing adapter\n * implementations (Slack/Discord/Telegram) already match this shape.\n */\n uploadFile?(channel: string, filePath: string, title?: string): Promise<void>;\n enqueueEvent(event: ConversationEvent): boolean;\n getMessagingInfo(): MessagingInfo;\n postPrivate?(conversationId: string, userId: string, text: string): Promise<void>;\n postPrivateDiagnostic?(\n conversationId: string,\n userId: string,\n text: string,\n options?: { style?: \"muted\" | \"error\" },\n ): Promise<void>;\n}\n\n/**\n * Post a plain message to a conversation without triggering an agent run.\n * Backs extension `api.notify`; implemented in main.ts over the platform bots.\n * `platform` is required only when more than one platform is running.\n */\nexport type PlatformNotifier = (\n conversationId: string,\n text: string,\n platform?: string,\n) => Promise<void>;\n\n/**\n * Add an emoji reaction to a message without triggering an agent run. Backs\n * extension `api.react`; implemented in main.ts over the platform bots.\n */\nexport type PlatformReactor = (\n conversationId: string,\n messageTs: string,\n emoji: string,\n platform?: string,\n) => Promise<void>;\n\n/**\n * Upload a host-side file into a conversation without triggering an agent\n * run. Backs extension `api.uploadFile`; implemented in main.ts over the\n * platform bots. `platform` is required only when more than one is running.\n */\nexport type PlatformUploader = (\n conversationId: string,\n filePath: string,\n title?: string,\n platform?: string,\n) => Promise<void>;\n\n/**\n * Post and update interactive Block Kit messages without triggering an agent\n * run. Backs extension `api.blockkit`; implemented in main.ts over the\n * platform bots (Slack-only today — other platforms throw).\n */\nexport interface PlatformBlockKit {\n postBlocks(\n conversationId: string,\n message: { text: string; blocks: object[]; threadTs?: string },\n platform?: string,\n ): Promise<{ ts: string }>;\n updateBlocks(\n conversationId: string,\n messageTs: string,\n message: { text: string; blocks: object[] },\n platform?: string,\n ): Promise<void>;\n}\n\n/** Normalized platform data and reply hook for one event. */\nexport interface ConversationContext {\n /** Canonical identity for the office handling this turn. */\n address: OfficeAddress;\n message: ConversationMessage;\n responder: ConversationResponder;\n platform: MessagingInfo;\n}\n\nexport interface RunningSession {\n /** The office this run belongs to; two platforms may share a raw id. */\n address: OfficeAddress;\n sessionKey: string;\n startedAt: number;\n lastActivityAt?: number;\n currentTool?: string;\n}\n\n/**\n * Runtime state is addressed by an office plus that office's platform session\n * reference. The session key alone is a platform value and is not unique\n * across platforms, so every session-scoped call carries its `OfficeAddress`.\n */\nexport interface MessagingEventHandler {\n isRunning(address: OfficeAddress, sessionKey: string): boolean;\n getRunningSessions(): RunningSession[];\n handleEvent(\n event: ConversationEvent,\n bot: MessagingBot,\n context: ConversationContext,\n ): Promise<void>;\n handleStop(address: OfficeAddress, sessionKey: string, bot: MessagingBot): Promise<void>;\n forceStop(address: OfficeAddress, sessionKey: string): void;\n handleNewCommand(\n sessionKey: string,\n conversationId: string,\n bot: MessagingBot,\n message: ConversationMessage,\n responder: ConversationResponder,\n platform: MessagingInfo,\n ): Promise<void>;\n /**\n * Dispatch an extension-owned interactive block action (`ext:<slug>:`\n * namespaced) to its onAction handler — deterministic, no agent run. The\n * extension decides whether to involve the model (`triggerRun`). Returns\n * true when an extension consumed the action; namespaced actions never\n * fall through to the model, so the adapter drops unconsumed ones.\n */\n handleExtensionAction(params: {\n address: OfficeAddress;\n sessionKey: string;\n conversationKind: ConversationKind;\n slug: string;\n action: ExtensionBlockAction;\n }): Promise<boolean>;\n}\n\n// ── agent ─────────────────────────────────────────────────────────────────────\n\nexport interface PiAgentWrapper {\n syncChatHistory(currentMessageId?: string): void;\n run(\n message: ConversationMessage,\n responder: ConversationResponder,\n platform: MessagingInfo,\n ): Promise<{ stopReason: string; errorMessage?: string }>;\n /** Run a hidden Session Dream against the current session before resetting or rotating it. */\n dreamSessionMemory(\n message: ConversationMessage,\n platform: MessagingInfo,\n ): Promise<{ stopReason: string; errorMessage?: string }>;\n abort(): void;\n getCurrentStep(): { toolName?: string; label?: string } | undefined;\n /**\n * Dispatch a leading-slash message to an extension-registered command.\n * Returns true when a command consumed the message (no agent run follows).\n */\n tryExtensionCommand(\n message: ConversationMessage,\n responder: ConversationResponder,\n ): Promise<boolean>;\n /**\n * Dispatch an extension-owned interactive block action to its onAction\n * handler. Returns true when a handler consumed it (no agent run follows).\n */\n tryExtensionAction(slug: string, action: ExtensionBlockAction): Promise<boolean>;\n /** Run extension disposers. Call once when this wrapper is discarded. */\n dispose(): Promise<void>;\n}\n\n// ── config ────────────────────────────────────────────────────────────────────\n\nexport type WorkspaceDoorPolicy = \"isolated\" | \"trusted\";\nexport type WorkspaceLayout = \"conversation\" | \"shared-support\" | \"full\";\n\ninterface WorkspaceSettings {\n doorPolicy?: WorkspaceDoorPolicy;\n layout?: WorkspaceLayout;\n}\n\nexport interface SandboxSettings {\n cpus?: string;\n memory?: string;\n boost?: { cpus?: string; memory?: string };\n /** Legacy image-specific workspace setting; new installations use workspace. */\n image?: { workspaceMount?: \"private\" | \"full\" };\n /** Backend-neutral office data policy and layout. */\n workspace?: WorkspaceSettings;\n defaultSharedVault?: string;\n}\n\nexport interface AgentConfig {\n provider: string;\n model: string;\n thinkingLevel: ThinkingLevel;\n sentryDsn?: string;\n sandbox?: SandboxSettings;\n slack?: {\n replyMode?: \"top-level\" | \"thread\";\n };\n /**\n * Package sources (see `src/packages`) declared by ONE scope. Unlike every\n * other key here, packages are additive across scopes rather than\n * overriding: a conversation's list does not replace the global list, it\n * extends it. `resolveConversationSettings` therefore reports only the\n * conversation's own entries — combining the two scopes (and resolving\n * same-package collisions in the conversation's favour) is\n * `resolveConversationPackages`'s job.\n */\n packages?: string[];\n}\n\n/**\n * @deprecated Auto-reply is kept for compatibility while its future is undecided.\n */\nexport interface AutoReplyConfig {\n enabled: boolean;\n rules: string[];\n}\n\nexport interface JudgeModelConfig {\n provider: string;\n model: string;\n}\n\n// ── context ───────────────────────────────────────────────────────────────────\n\n/**\n * Platform conversation history entry from log.jsonl.\n */\nexport interface ConversationLogMessage {\n date?: string;\n ts?: string;\n threadTs?: string;\n user?: string;\n userName?: string;\n text?: string;\n isMessagingBot?: boolean;\n}\n\n// ── events ────────────────────────────────────────────────────────────────────\n// The wire format (payload union, schema, parser, builder) is owned by\n// src/harness/event-format.ts. These are the *resolved* runtime shapes: the\n// EventsWatcher fills in the platform default and infers the conversation\n// kind before an event reaches a bot.\n\ninterface ResolvedEventFields {\n platform: string;\n conversationKind: ConversationKind;\n}\n\nexport type ImmediateEvent = ImmediateEventPayload & ResolvedEventFields;\n\nexport type OneShotEvent = OneShotEventPayload & ResolvedEventFields;\n\nexport type PeriodicEvent = PeriodicEventPayload & ResolvedEventFields;\n\nexport type MikanEvent = ImmediateEvent | OneShotEvent | PeriodicEvent;\n\nexport interface PeriodicEventInfo {\n filename: string;\n platform: string;\n conversationId: string;\n conversationKind: ConversationKind;\n text: string;\n schedule: string;\n timezone: string;\n nextRun: string | null;\n}\n\n// ── execution-resolver ────────────────────────────────────────────────────────\n\nexport interface ActorContext {\n /** Canonical office identity; vault and package keys use its raw id. */\n address: OfficeAddress;\n userId: string;\n /** From MessagingInfo.trustModel; vault policy uses this, not platform name. */\n trustModel?: PlatformTrustModel;\n}\n\nexport type ImageWorkspaceMountMode = \"private\" | \"full\";\n\n// ── log ───────────────────────────────────────────────────────────────────────\n\nexport interface LogContext {\n conversationId: string;\n userName?: string;\n conversationName?: string;\n sessionId?: string;\n}\n\n// ── portal shell (src/web/portal-shell.ts) ────────────────────────────────────\n\ntype PortalView = \"admin\" | \"session\" | \"vault\";\n\nexport interface PortalShellOptions {\n activeView: PortalView;\n pageTitle: string;\n identity?: {\n primary: string;\n secondary?: string;\n };\n conversationSwitcher?: {\n currentId: string;\n options?: Array<{ id: string; label: string; running?: boolean }>;\n };\n navLinks?: Partial<Record<PortalView, string>>;\n body: string;\n extraStyles?: string;\n inlineScript?: string;\n extraHead?: string;\n bodyAttributes?: Record<string, string>;\n}\n\n// ── provisioner ───────────────────────────────────────────────────────────────\n\nexport interface ContainerMount {\n source: string;\n target: string;\n /**\n * Mount without write access. Used for content the host owns and the agent\n * must not edit — package-provided skills, whose host copy is a git checkout\n * that a reinstall replaces wholesale. Absent means read-write, which is the\n * right default for everything the agent is meant to author.\n */\n readOnly?: boolean;\n}\n\nexport interface ResourceLimits {\n cpus?: string;\n memory?: string;\n}\n\nexport interface SandboxLimitStatus {\n limits?: ResourceLimits;\n boosted: boolean;\n}\n\nexport interface SandboxResourceController {\n boost(key: string): Promise<SandboxLimitStatus>;\n setLimits(key: string, limits: ResourceLimits): Promise<SandboxLimitStatus>;\n getLimitStatus(key: string): SandboxLimitStatus;\n getDefaultLimits(): ResourceLimits | undefined;\n getBoostLimits(): ResourceLimits | undefined;\n}\n\nexport interface ProvisionOptions {\n containerName?: string;\n mounts?: ContainerMount[];\n conversationId?: string;\n}\n\nexport interface DockerContainerManagerOptions {\n limits?: ResourceLimits;\n boostLimits?: ResourceLimits;\n execFileImpl?: ExecFileAsync;\n}\n\nexport interface ExecutionPlan {\n credentialKey: string;\n resourceKey: string;\n sandboxConfig: import(\"./sandbox/types.js\").SandboxConfig;\n env?: Record<string, string>;\n mounts: ContainerMount[];\n}\n\n// ── store ─────────────────────────────────────────────────────────────────────\n\nexport interface Attachment {\n original: string;\n localPath: string;\n}\n\nexport interface LoggedMessage {\n date: string;\n ts: string;\n user: string;\n userName?: string;\n displayName?: string;\n text: string;\n attachments: Attachment[];\n isMessagingBot: boolean;\n threadTs?: string;\n}\n\nexport interface ChannelStoreConfig {\n workingDir: string;\n botToken: string;\n}\n\n// ── trigger ───────────────────────────────────────────────────────────────────\n\nexport type TriggerIntent = \"mention\" | \"direct\" | \"thread-continuation\" | \"auto-reply-candidate\";\n\nexport type TriggerResult = { trigger: true; reason: string } | { trigger: false; reason: string };\n\n/**\n * @deprecated Auto-reply is kept for compatibility while its future is undecided.\n */\nexport type AutoReplyJudge = (input: {\n event: ConversationEvent;\n rules: string[];\n conversationDir: string;\n}) => Promise<boolean>;\n\n// ── shared implementation contracts ─────────────────────────────────────────\n\ninterface EnvVarSpec {\n name: string;\n required?: boolean;\n secret?: boolean;\n deploy?: boolean;\n doc: string;\n}\n\nexport interface EnvGroup {\n key: string;\n title: string;\n kind: \"platform\" | \"feature\";\n vars: EnvVarSpec[];\n anyOf?: readonly string[];\n doc?: string;\n}\n\nexport interface RunnerCacheControl {\n switchConversationModel(address: OfficeAddress, provider: string, model: string): boolean;\n}\n\nexport interface GlobalRunnerCacheControl {\n refreshAllConversations(): { busy: OfficeAddress[] };\n}\n\nexport type SettingsApplyResult =\n | { ok: true; runtimeSwitched: boolean | null }\n | { ok: false; reason: \"busy\" };\n\nexport interface CreateRunnerOptions {\n sandboxConfig: import(\"./sandbox/types.js\").SandboxConfig;\n sessionKey: string;\n /** Canonical office identity for workspace paths and execution scoping. */\n address: OfficeAddress;\n /** Raw platform id; settings/vault/package keys keep it until ADR 0005. */\n conversationId: string;\n conversationDir: string;\n workspaceDir: string;\n sessionScope: import(\"./sessions/types.js\").ResolvedSessionScope;\n vaultManager?: import(\"./vault/types.js\").VaultManager;\n provisioner?: import(\"./provisioner.js\").DockerContainerManager;\n resourceController?: SandboxResourceController;\n sessionView?: {\n tokenStore: import(\"./commands/types.js\").SessionViewTokenStoreLike;\n portalBaseUrl?: string;\n };\n platformNotifier?: PlatformNotifier;\n platformReactor?: PlatformReactor;\n platformUploader?: PlatformUploader;\n platformBlockKit?: PlatformBlockKit;\n platformToolPackFactories?: readonly import(\"./tools/types.js\").PlatformToolPackFactory[];\n models?: import(\"./harness/models.js\").MikanModels;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AASjC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC","sourcesContent":["import type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport { execFile } from \"child_process\";\nimport { promisify } from \"util\";\nimport type {\n ImmediateEventPayload,\n OneShotEventPayload,\n PeriodicEventPayload,\n} from \"./harness/event-format.js\";\nimport type { ExtensionBlockAction } from \"./harness/extensions/types.js\";\nimport type { SubagentRunStatus } from \"./harness/types.js\";\n\nconst execFileAsync = promisify(execFile);\ntype ExecFileAsync = typeof execFileAsync;\n\n// ── adapter ───────────────────────────────────────────────────────────────────\n\nexport type ConversationKind = \"direct\" | \"shared\";\n\nexport type PlatformName = \"slack\" | \"discord\" | \"telegram\" | \"github\";\n\n/** Canonical platform-plus-raw identifier for one conversation office. */\nexport interface OfficeAddress {\n readonly platform: PlatformName;\n readonly conversationId: string;\n}\n\n/** Stable, filesystem-safe identity derived from an OfficeAddress. */\nexport type OfficeKey = string & { readonly __brand: \"OfficeKey\" };\n\nexport type OfficeMigrationStatus = \"needs-owner\" | \"prepared\" | \"moving\" | \"committed\" | \"failed\";\n\nexport interface OfficeMigrationRecord {\n readonly rawConversationId: string;\n readonly sourceDir: string;\n readonly workspaceRoot: string;\n readonly ownerPlatform?: PlatformName;\n readonly targetDir?: string;\n readonly status: OfficeMigrationStatus;\n readonly error?: string;\n readonly updatedAt: string;\n}\n\n/**\n * Directory entry for one conversation office. The workspace dir name stops\n * carrying the raw platform id once the ADR 0005 layout migration lands, so\n * the registry is the durable raw-id ↔ office mapping used for enumeration.\n */\nexport interface OfficeRecord {\n readonly platform: PlatformName;\n readonly conversationId: string;\n readonly recordedAt: string;\n}\n\nexport interface OfficeRegistryState {\n readonly version: 1;\n readonly enabledPlatforms: readonly PlatformName[];\n readonly offices: readonly OfficeRecord[];\n readonly migrations: readonly OfficeMigrationRecord[];\n}\n\nexport interface OfficeMigrationPreparation {\n readonly rawConversationId: string;\n readonly sourceDir: string;\n readonly workspaceRoot: string;\n readonly ownerPlatform?: PlatformName;\n}\n\n/**\n * Who can drive conversations on a platform — gates ambient credential policy.\n *\n * - `membership`: only invited workspace/server members (Slack/Discord/Telegram).\n * Safe to copy `sandbox.defaultSharedVault` into new conversation vaults.\n * - `open-trigger`: broader trigger surface (e.g. GitHub repo writers on public\n * issues/PRs). Ambient shared vault must not apply; use host-side platform\n * identity or an explicitly provisioned vault.\n */\nexport type PlatformTrustModel = \"membership\" | \"open-trigger\";\n\nexport interface ConversationMessage {\n id: string;\n /** Canonical identity of the conversation office. */\n address: OfficeAddress;\n /** @deprecated Use address.conversationId except at platform I/O seams. */\n conversationId?: string;\n sessionKey: string;\n conversationKind: ConversationKind;\n userId: string;\n userName?: string;\n text: string;\n attachments?: { name: string; localPath: string }[];\n threadTs?: string;\n}\n\nexport interface ChatToolResult {\n toolName: string;\n label?: string;\n args?: Record<string, unknown>;\n result: string;\n isError: boolean;\n durationMs: number;\n}\n\n/**\n * Derived from the run status rather than restated, so a new terminal status\n * in the harness reaches the dashboard instead of silently rendering as a\n * status the marker tables never learned about.\n */\nexport type SubagentProgressStatus = SubagentRunStatus | \"pending\" | \"running\" | \"skipped\";\n\nexport interface SubagentProgressNode {\n id: string;\n label: string;\n status: SubagentProgressStatus;\n /** Profile the node ran under; the first thing to check when a run is ungrounded. */\n profile?: string;\n turns?: number;\n toolCalls?: number;\n toolCallCounts?: Record<string, number>;\n tokens?: number;\n costUsd?: number;\n durationMs?: number;\n reason?: string;\n cleanupPending?: boolean;\n}\n\nexport interface SubagentProgressSnapshot {\n mode: \"single\" | \"parallel\" | \"dag\";\n nodes: SubagentProgressNode[];\n}\n\nexport interface ConversationResponder {\n respond(text: string): Promise<void>;\n appendResponseDelta?(delta: string): Promise<void>;\n finishResponse?(finalText?: string): Promise<void>;\n replaceResponse(text: string, options?: { createOverflowLink?: () => string }): Promise<void>;\n /**\n * Override only to convert the subagent dashboard for a pipeline that is\n * not response-source Markdown (Telegram HTML). Absent, the harness composes\n * the Markdown dashboard — optionally followed by the final answer — through\n * `replaceResponse`.\n */\n replaceSubagentProgress?(progress: SubagentProgressSnapshot, finalText?: string): Promise<void>;\n respondDiagnostic(text: string, options?: { style?: \"muted\" | \"error\" }): Promise<void>;\n respondToolResult(result: ChatToolResult): Promise<void>;\n setTyping(isTyping: boolean): Promise<void>;\n setWorking(working: boolean): Promise<void>;\n uploadFile(filePath: string, title?: string): Promise<void>;\n /**\n * React to the message that triggered this run with an emoji short name\n * (no colons). Optional: platforms/contexts without reaction support omit\n * it, and callers must handle its absence.\n */\n react?(emoji: string): Promise<void>;\n deleteResponse(): Promise<void>;\n}\n\nexport interface MessagingInfo {\n name: string;\n formattingGuide: string;\n channels: { id: string; name: string }[];\n users: { id: string; userName: string; displayName: string }[];\n /**\n * Trust boundary for ambient credentials. Defaults to `membership` when\n * omitted (closed chat platforms). Open-trigger platforms must set this\n * explicitly so vault policy does not key off platform name strings.\n */\n trustModel?: PlatformTrustModel;\n diagnostics?: {\n showUsageSummary?: boolean;\n };\n}\n\nexport interface ChatAdapter {\n start(): Promise<void>;\n stop(): Promise<void>;\n getMessagingInfo(): MessagingInfo;\n}\n\nexport type AgentEventPayload =\n | { kind: \"sessionStart\" }\n | { kind: \"responseDelta\"; delta: string }\n | { kind: \"responseFinal\"; text: string }\n | { kind: \"diagnostic\"; text: string }\n | { kind: \"toolStart\"; toolId: string; toolName: string; input?: unknown }\n | { kind: \"toolEnd\"; toolId: string }\n | { kind: \"turnEnd\"; awaitingInput?: boolean }\n | { kind: \"sessionEnd\"; reason?: string };\n\nexport interface AgentEventEnvelope {\n source: \"mikan\";\n sessionId: string;\n actorName: string;\n event: AgentEventPayload;\n}\n\n/**\n * A platform-agnostic event (message/mention) that triggers the agent.\n */\nexport interface ConversationEvent {\n type: string;\n /** Canonical identity created by the platform adapter at intake. */\n address: OfficeAddress;\n /** @deprecated Raw platform identifier; valid only at adapter I/O seams. */\n conversationId: string;\n /** Optional alternate conversation identity used for vault routing. */\n vaultConversationId?: string;\n /** Cross-platform conversation shape: direct message vs shared space */\n conversationKind: ConversationKind;\n /** Message timestamp or ID as string */\n ts: string;\n /** Parent message ID for threaded replies (optional) */\n thread_ts?: string;\n /** User ID */\n user: string;\n /** Message text (already stripped of bot mentions) */\n text: string;\n /** Downloaded attachments */\n attachments?: { name: string; localPath: string }[];\n /** Platform-computed session key; overrides default conversationId:thread_ts computation */\n sessionKey?: string;\n}\n\n/**\n * Minimum interface that every platform bot must implement,\n * used by the central handler in main.ts and by EventsWatcher.\n */\nexport interface MessagingBot {\n start(): Promise<void>;\n postMessage(channel: string, text: string): Promise<string>;\n updateMessage(channel: string, ts: string, text: string): Promise<void>;\n /**\n * Add an emoji reaction to a message. `emoji` is a platform-agnostic short\n * name without colons (e.g. \"eyes\", \"white_check_mark\"). Optional so\n * adapters adopt it incrementally; callers must handle its absence.\n */\n addReaction?(channel: string, messageTs: string, emoji: string): Promise<void>;\n /**\n * Upload a host-side file into a conversation. Optional so adapters adopt\n * it incrementally; callers must handle its absence. Existing adapter\n * implementations (Slack/Discord/Telegram) already match this shape.\n */\n uploadFile?(channel: string, filePath: string, title?: string): Promise<void>;\n enqueueEvent(event: ConversationEvent): boolean;\n getMessagingInfo(): MessagingInfo;\n postPrivate?(conversationId: string, userId: string, text: string): Promise<void>;\n postPrivateDiagnostic?(\n conversationId: string,\n userId: string,\n text: string,\n options?: { style?: \"muted\" | \"error\" },\n ): Promise<void>;\n}\n\n/**\n * Post a plain message to a conversation without triggering an agent run.\n * Backs extension `api.notify`; implemented in main.ts over the platform bots.\n * `platform` is required only when more than one platform is running.\n */\nexport type PlatformNotifier = (\n conversationId: string,\n text: string,\n platform?: string,\n) => Promise<void>;\n\n/**\n * Add an emoji reaction to a message without triggering an agent run. Backs\n * extension `api.react`; implemented in main.ts over the platform bots.\n */\nexport type PlatformReactor = (\n conversationId: string,\n messageTs: string,\n emoji: string,\n platform?: string,\n) => Promise<void>;\n\n/**\n * Upload a host-side file into a conversation without triggering an agent\n * run. Backs extension `api.uploadFile`; implemented in main.ts over the\n * platform bots. `platform` is required only when more than one is running.\n */\nexport type PlatformUploader = (\n conversationId: string,\n filePath: string,\n title?: string,\n platform?: string,\n) => Promise<void>;\n\n/**\n * Post and update interactive Block Kit messages without triggering an agent\n * run. Backs extension `api.blockkit`; implemented in main.ts over the\n * platform bots (Slack-only today — other platforms throw).\n */\nexport interface PlatformBlockKit {\n postBlocks(\n conversationId: string,\n message: { text: string; blocks: object[]; threadTs?: string },\n platform?: string,\n ): Promise<{ ts: string }>;\n updateBlocks(\n conversationId: string,\n messageTs: string,\n message: { text: string; blocks: object[] },\n platform?: string,\n ): Promise<void>;\n}\n\n/** Normalized platform data and reply hook for one event. */\nexport interface ConversationContext {\n /** Canonical identity for the office handling this turn. */\n address: OfficeAddress;\n message: ConversationMessage;\n responder: ConversationResponder;\n platform: MessagingInfo;\n}\n\nexport interface RunningSession {\n /** The office this run belongs to; two platforms may share a raw id. */\n address: OfficeAddress;\n sessionKey: string;\n startedAt: number;\n lastActivityAt?: number;\n currentTool?: string;\n}\n\n/**\n * Runtime state is addressed by an office plus that office's platform session\n * reference. The session key alone is a platform value and is not unique\n * across platforms, so every session-scoped call carries its `OfficeAddress`.\n */\nexport interface MessagingEventHandler {\n isRunning(address: OfficeAddress, sessionKey: string): boolean;\n getRunningSessions(): RunningSession[];\n handleEvent(\n event: ConversationEvent,\n bot: MessagingBot,\n context: ConversationContext,\n ): Promise<void>;\n handleStop(address: OfficeAddress, sessionKey: string, bot: MessagingBot): Promise<void>;\n forceStop(address: OfficeAddress, sessionKey: string): void;\n handleNewCommand(\n sessionKey: string,\n conversationId: string,\n bot: MessagingBot,\n message: ConversationMessage,\n responder: ConversationResponder,\n platform: MessagingInfo,\n ): Promise<void>;\n /**\n * Dispatch an extension-owned interactive block action (`ext:<slug>:`\n * namespaced) to its onAction handler — deterministic, no agent run. The\n * extension decides whether to involve the model (`triggerRun`). Returns\n * true when an extension consumed the action; namespaced actions never\n * fall through to the model, so the adapter drops unconsumed ones.\n */\n handleExtensionAction(params: {\n address: OfficeAddress;\n sessionKey: string;\n conversationKind: ConversationKind;\n slug: string;\n action: ExtensionBlockAction;\n }): Promise<boolean>;\n}\n\n// ── agent ─────────────────────────────────────────────────────────────────────\n\nexport interface PiAgentWrapper {\n syncChatHistory(currentMessageId?: string): void;\n run(\n message: ConversationMessage,\n responder: ConversationResponder,\n platform: MessagingInfo,\n ): Promise<{ stopReason: string; errorMessage?: string }>;\n /** Run a hidden Session Dream against the current session before resetting or rotating it. */\n dreamSessionMemory(\n message: ConversationMessage,\n platform: MessagingInfo,\n ): Promise<{ stopReason: string; errorMessage?: string }>;\n abort(): void;\n getCurrentStep(): { toolName?: string; label?: string } | undefined;\n /**\n * Dispatch a leading-slash message to an extension-registered command.\n * Returns true when a command consumed the message (no agent run follows).\n */\n tryExtensionCommand(\n message: ConversationMessage,\n responder: ConversationResponder,\n ): Promise<boolean>;\n /**\n * Dispatch an extension-owned interactive block action to its onAction\n * handler. Returns true when a handler consumed it (no agent run follows).\n */\n tryExtensionAction(slug: string, action: ExtensionBlockAction): Promise<boolean>;\n /** Run extension disposers. Call once when this wrapper is discarded. */\n dispose(): Promise<void>;\n}\n\n// ── config ────────────────────────────────────────────────────────────────────\n\nexport type WorkspaceDoorPolicy = \"isolated\" | \"trusted\";\nexport type WorkspaceLayout = \"conversation\" | \"shared-support\" | \"full\";\n\ninterface WorkspaceSettings {\n doorPolicy?: WorkspaceDoorPolicy;\n layout?: WorkspaceLayout;\n}\n\nexport interface SandboxSettings {\n cpus?: string;\n memory?: string;\n boost?: { cpus?: string; memory?: string };\n /** Legacy image-specific workspace setting; new installations use workspace. */\n image?: { workspaceMount?: \"private\" | \"full\" };\n /** Backend-neutral office data policy and layout. */\n workspace?: WorkspaceSettings;\n defaultSharedVault?: string;\n}\n\nexport interface AgentConfig {\n provider: string;\n model: string;\n thinkingLevel: ThinkingLevel;\n sentryDsn?: string;\n sandbox?: SandboxSettings;\n slack?: {\n replyMode?: \"top-level\" | \"thread\";\n };\n /**\n * Package sources (see `src/packages`) declared by ONE scope. Unlike every\n * other key here, packages are additive across scopes rather than\n * overriding: a conversation's list does not replace the global list, it\n * extends it. `resolveConversationSettings` therefore reports only the\n * conversation's own entries — combining the two scopes (and resolving\n * same-package collisions in the conversation's favour) is\n * `resolveConversationPackages`'s job.\n */\n packages?: string[];\n}\n\n/**\n * @deprecated Auto-reply is kept for compatibility while its future is undecided.\n */\nexport interface AutoReplyConfig {\n enabled: boolean;\n rules: string[];\n}\n\nexport interface JudgeModelConfig {\n provider: string;\n model: string;\n}\n\n// ── context ───────────────────────────────────────────────────────────────────\n\n/**\n * Platform conversation history entry from log.jsonl.\n */\nexport interface ConversationLogMessage {\n date?: string;\n ts?: string;\n threadTs?: string;\n user?: string;\n userName?: string;\n text?: string;\n isMessagingBot?: boolean;\n}\n\n// ── events ────────────────────────────────────────────────────────────────────\n// The wire format (payload union, schema, parser, builder) is owned by\n// src/harness/event-format.ts. These are the *resolved* runtime shapes: the\n// EventsWatcher fills in the platform default and infers the conversation\n// kind before an event reaches a bot.\n\ninterface ResolvedEventFields {\n platform: string;\n conversationKind: ConversationKind;\n}\n\nexport type ImmediateEvent = ImmediateEventPayload & ResolvedEventFields;\n\nexport type OneShotEvent = OneShotEventPayload & ResolvedEventFields;\n\nexport type PeriodicEvent = PeriodicEventPayload & ResolvedEventFields;\n\nexport type MikanEvent = ImmediateEvent | OneShotEvent | PeriodicEvent;\n\nexport interface PeriodicEventInfo {\n filename: string;\n platform: string;\n conversationId: string;\n conversationKind: ConversationKind;\n text: string;\n schedule: string;\n timezone: string;\n nextRun: string | null;\n}\n\n// ── execution-resolver ────────────────────────────────────────────────────────\n\nexport interface ActorContext {\n /** Canonical office identity; vault and package keys use its raw id. */\n address: OfficeAddress;\n userId: string;\n /** From MessagingInfo.trustModel; vault policy uses this, not platform name. */\n trustModel?: PlatformTrustModel;\n}\n\nexport type ImageWorkspaceMountMode = \"private\" | \"full\";\n\n// ── log ───────────────────────────────────────────────────────────────────────\n\nexport interface LogContext {\n conversationId: string;\n userName?: string;\n conversationName?: string;\n sessionId?: string;\n}\n\n// ── portal shell (src/web/portal-shell.ts) ────────────────────────────────────\n\ntype PortalView = \"admin\" | \"session\" | \"vault\";\n\nexport interface PortalShellOptions {\n activeView: PortalView;\n pageTitle: string;\n identity?: {\n primary: string;\n secondary?: string;\n };\n conversationSwitcher?: {\n currentId: string;\n options?: Array<{ id: string; label: string; running?: boolean }>;\n };\n navLinks?: Partial<Record<PortalView, string>>;\n body: string;\n extraStyles?: string;\n inlineScript?: string;\n extraHead?: string;\n bodyAttributes?: Record<string, string>;\n}\n\n// ── provisioner ───────────────────────────────────────────────────────────────\n\nexport interface ContainerMount {\n source: string;\n target: string;\n /**\n * Mount without write access. Used for content the host owns and the agent\n * must not edit — package-provided skills, whose host copy is a git checkout\n * that a reinstall replaces wholesale. Absent means read-write, which is the\n * right default for everything the agent is meant to author.\n */\n readOnly?: boolean;\n}\n\nexport interface ResourceLimits {\n cpus?: string;\n memory?: string;\n}\n\nexport interface SandboxLimitStatus {\n limits?: ResourceLimits;\n boosted: boolean;\n}\n\nexport interface SandboxResourceController {\n boost(key: string): Promise<SandboxLimitStatus>;\n setLimits(key: string, limits: ResourceLimits): Promise<SandboxLimitStatus>;\n getLimitStatus(key: string): SandboxLimitStatus;\n getDefaultLimits(): ResourceLimits | undefined;\n getBoostLimits(): ResourceLimits | undefined;\n}\n\nexport interface ProvisionOptions {\n containerName?: string;\n mounts?: ContainerMount[];\n conversationId?: string;\n}\n\nexport interface DockerContainerManagerOptions {\n limits?: ResourceLimits;\n boostLimits?: ResourceLimits;\n execFileImpl?: ExecFileAsync;\n}\n\nexport interface ExecutionPlan {\n credentialKey: string;\n resourceKey: string;\n sandboxConfig: import(\"./sandbox/types.js\").SandboxConfig;\n env?: Record<string, string>;\n mounts: ContainerMount[];\n}\n\n// ── store ─────────────────────────────────────────────────────────────────────\n\nexport interface Attachment {\n original: string;\n localPath: string;\n}\n\nexport interface LoggedMessage {\n date: string;\n ts: string;\n user: string;\n userName?: string;\n displayName?: string;\n text: string;\n attachments: Attachment[];\n isMessagingBot: boolean;\n threadTs?: string;\n}\n\nexport interface ChannelStoreConfig {\n workingDir: string;\n botToken: string;\n}\n\n// ── trigger ───────────────────────────────────────────────────────────────────\n\nexport type TriggerIntent = \"mention\" | \"direct\" | \"thread-continuation\" | \"auto-reply-candidate\";\n\nexport type TriggerResult = { trigger: true; reason: string } | { trigger: false; reason: string };\n\n/**\n * @deprecated Auto-reply is kept for compatibility while its future is undecided.\n */\nexport type AutoReplyJudge = (input: {\n event: ConversationEvent;\n rules: string[];\n conversationDir: string;\n}) => Promise<boolean>;\n\n// ── shared implementation contracts ─────────────────────────────────────────\n\ninterface EnvVarSpec {\n name: string;\n required?: boolean;\n secret?: boolean;\n deploy?: boolean;\n doc: string;\n}\n\nexport interface EnvGroup {\n key: string;\n title: string;\n kind: \"platform\" | \"feature\";\n vars: EnvVarSpec[];\n anyOf?: readonly string[];\n doc?: string;\n}\n\nexport interface RunnerCacheControl {\n switchConversationModel(address: OfficeAddress, provider: string, model: string): boolean;\n /** Clear the office's cached runner for a non-model settings change; false while busy. */\n refreshConversationEnvironment(address: OfficeAddress): boolean;\n}\n\nexport interface GlobalRunnerCacheControl {\n refreshAllConversations(): { busy: OfficeAddress[] };\n}\n\nexport type SettingsApplyResult =\n | { ok: true; runtimeSwitched: boolean | null }\n | { ok: false; reason: \"busy\" };\n\nexport interface CreateRunnerOptions {\n sandboxConfig: import(\"./sandbox/types.js\").SandboxConfig;\n sessionKey: string;\n /** Canonical office identity for workspace paths and execution scoping. */\n address: OfficeAddress;\n /** Raw platform id; settings/vault/package keys keep it until ADR 0005. */\n conversationId: string;\n conversationDir: string;\n workspaceDir: string;\n sessionScope: import(\"./sessions/types.js\").ResolvedSessionScope;\n vaultManager?: import(\"./vault/types.js\").VaultManager;\n provisioner?: import(\"./provisioner.js\").DockerContainerManager;\n resourceController?: SandboxResourceController;\n sessionView?: {\n tokenStore: import(\"./commands/types.js\").SessionViewTokenStoreLike;\n portalBaseUrl?: string;\n };\n platformNotifier?: PlatformNotifier;\n platformReactor?: PlatformReactor;\n platformUploader?: PlatformUploader;\n platformBlockKit?: PlatformBlockKit;\n platformToolPackFactories?: readonly import(\"./tools/types.js\").PlatformToolPackFactory[];\n models?: import(\"./harness/models.js\").MikanModels;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../../src/web/admin/portal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAG5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../../src/web/admin/portal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAG5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAkCvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAWtD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,aAAa,GACtB,OAAO,CAAC,OAAO,CAAC,CA6BlB;AAmJD,UAAU,sBAAsB;IAC9B,OAAO,EAAE,aAAa,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM,EACzB,KAAK,EAAE,UAAU,GAChB,sBAAsB,CAexB;AA6iCD,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,QAAQ,GAAG,cAAc,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAwBD,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,GAAG,UAAU,EAAE,CAuB/F;AA6JD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAiB9E"}
|
package/dist/web/admin/portal.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
2
2
|
import { basename, join, resolve as pathResolve, sep as pathSep } from "path";
|
|
3
3
|
import { MikanModels, parseFrontmatter, SessionStore } from "../../harness/index.js";
|
|
4
|
-
import { loadConversationAutoReplyConfig, loadGlobalSettings, resolveConversationSettings, saveConversationAutoReplyConfig, } from "../../config.js";
|
|
5
|
-
import { applyConversationSettings, applyGlobalSettings } from "../../settings-mutation.js";
|
|
4
|
+
import { loadConversationAutoReplyConfig, loadConversationWorkspaceOverride, loadGlobalSettings, resolveConversationSettings, saveConversationAutoReplyConfig, } from "../../config.js";
|
|
5
|
+
import { applyConversationSettings, applyConversationWorkspacePolicy, applyGlobalSettings, applyGlobalWorkspacePolicy, } from "../../settings-mutation.js";
|
|
6
6
|
import { effectiveStateDir } from "../../cli/arg-grammar.js";
|
|
7
7
|
import { addPackage, inspectConversationPackages, refreshPackage, removePackage, } from "../../packages/index.js";
|
|
8
8
|
import { escapeHtml } from "../../utils/html.js";
|
|
@@ -162,6 +162,10 @@ async function routeApiRequest(req, res, url, services) {
|
|
|
162
162
|
serveGlobalModelUpdate(res, body, services);
|
|
163
163
|
return;
|
|
164
164
|
}
|
|
165
|
+
if (url.pathname === "/admin/api/settings/workspace") {
|
|
166
|
+
serveGlobalWorkspaceUpdate(res, body, services);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
165
169
|
if (url.pathname === "/admin/api/settings/sandbox") {
|
|
166
170
|
serveGlobalSandboxUpdate(res, body, services);
|
|
167
171
|
return;
|
|
@@ -485,6 +489,7 @@ function serveConversationState(res, url, services, token) {
|
|
|
485
489
|
globalThinkingLevel: globalConfig.thinkingLevel,
|
|
486
490
|
workspaceDoorPolicy: conversationWorkspace.doorPolicy,
|
|
487
491
|
workspaceLayout: conversationWorkspace.layout,
|
|
492
|
+
workspaceOverride: doorPolicyChoiceKey(loadConversationWorkspaceOverride({ address: scope.address, conversationDir: dir })),
|
|
488
493
|
globalWorkspaceDoorPolicy: globalWorkspaceSettings?.doorPolicy ?? "isolated",
|
|
489
494
|
globalWorkspaceLayout: globalWorkspaceSettings?.layout ?? "conversation",
|
|
490
495
|
autoReplyEnabled: autoReply.enabled,
|
|
@@ -576,10 +581,71 @@ function serveConversationModelUpdate(res, body, services, token) {
|
|
|
576
581
|
jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
577
582
|
}
|
|
578
583
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
584
|
+
/** Wire values for a door-policy selection; "default" clears the office's override. */
|
|
585
|
+
function parseDoorPolicyChoice(value) {
|
|
586
|
+
switch (value) {
|
|
587
|
+
case "default":
|
|
588
|
+
return { choice: null };
|
|
589
|
+
case "isolated":
|
|
590
|
+
return { choice: { doorPolicy: "isolated" } };
|
|
591
|
+
case "trusted-shared-support":
|
|
592
|
+
return { choice: { doorPolicy: "trusted", layout: "shared-support" } };
|
|
593
|
+
case "trusted-full":
|
|
594
|
+
return { choice: { doorPolicy: "trusted", layout: "full" } };
|
|
595
|
+
default:
|
|
596
|
+
return undefined;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
function doorPolicyChoiceKey(choice) {
|
|
600
|
+
if (!choice)
|
|
601
|
+
return "default";
|
|
602
|
+
if (choice.doorPolicy === "isolated")
|
|
603
|
+
return "isolated";
|
|
604
|
+
return choice.layout === "full" ? "trusted-full" : "trusted-shared-support";
|
|
605
|
+
}
|
|
606
|
+
function serveConversationSandboxUpdate(res, body, services, token) {
|
|
607
|
+
const parsed = parseDoorPolicyChoice(body.doorPolicy);
|
|
608
|
+
if (!parsed) {
|
|
609
|
+
jsonRes(res, 400, {
|
|
610
|
+
error: "doorPolicy must be 'default', 'isolated', 'trusted-shared-support', or 'trusted-full'",
|
|
611
|
+
});
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
const scope = resolveTargetConversation(body, token);
|
|
615
|
+
if (scope.error) {
|
|
616
|
+
jsonRes(res, 403, { error: scope.error });
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const workingDir = requireAdminWorkingDir(res, services);
|
|
620
|
+
if (!workingDir)
|
|
621
|
+
return;
|
|
622
|
+
try {
|
|
623
|
+
const result = applyConversationWorkspacePolicy(services.runtime, workingDir, scope.address, parsed.choice);
|
|
624
|
+
if (!result.ok) {
|
|
625
|
+
jsonRes(res, 409, { error: "Conversation is busy; retry when the current run finishes" });
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
jsonRes(res, 200, { ok: true });
|
|
629
|
+
}
|
|
630
|
+
catch (err) {
|
|
631
|
+
jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
function serveGlobalWorkspaceUpdate(res, body, services) {
|
|
635
|
+
const parsed = parseDoorPolicyChoice(body.doorPolicy);
|
|
636
|
+
if (!parsed) {
|
|
637
|
+
jsonRes(res, 400, {
|
|
638
|
+
error: "doorPolicy must be 'default', 'isolated', 'trusted-shared-support', or 'trusted-full'",
|
|
639
|
+
});
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
try {
|
|
643
|
+
const result = applyGlobalWorkspacePolicy(services.runtime, parsed.choice);
|
|
644
|
+
jsonRes(res, 200, { ok: true, staleConversations: result.staleConversations.length });
|
|
645
|
+
}
|
|
646
|
+
catch (err) {
|
|
647
|
+
jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
648
|
+
}
|
|
583
649
|
}
|
|
584
650
|
function serveConversationSlackUpdate(res, body, services, token) {
|
|
585
651
|
const replyMode = body.replyMode;
|
|
@@ -1686,6 +1752,15 @@ function renderAdminPage(token) {
|
|
|
1686
1752
|
const globalReplyMode = (data.slack && data.slack.globalReplyMode) || 'top-level';
|
|
1687
1753
|
const globalModel = [data.globalProvider, data.globalModel].filter(Boolean).join('/');
|
|
1688
1754
|
const globalModelLabel = globalModel + (data.globalThinkingLevel ? ':' + data.globalThinkingLevel : '');
|
|
1755
|
+
const doorPolicyChoices = [
|
|
1756
|
+
['default', 'Global default (' + data.globalWorkspaceDoorPolicy + ' / ' + data.globalWorkspaceLayout + ')'],
|
|
1757
|
+
['isolated', 'isolated — own office only'],
|
|
1758
|
+
['trusted-shared-support', 'trusted / shared-support — office + shared memory, skills, events'],
|
|
1759
|
+
['trusted-full', 'trusted / full — entire workspace'],
|
|
1760
|
+
];
|
|
1761
|
+
const doorPolicyOpts = doorPolicyChoices.map(([value, label]) =>
|
|
1762
|
+
'<option value="' + value + '"' + ((data.workspaceOverride || 'default') === value ? ' selected' : '') + '>' + escHtml(label) + '</option>'
|
|
1763
|
+
).join('');
|
|
1689
1764
|
return [
|
|
1690
1765
|
'<div class="config-grid">',
|
|
1691
1766
|
'<div class="config-block">',
|
|
@@ -1705,7 +1780,10 @@ function renderAdminPage(token) {
|
|
|
1705
1780
|
'</div>',
|
|
1706
1781
|
'<div class="config-block">',
|
|
1707
1782
|
'<h3 class="card-subtitle">Office data policy</h3>',
|
|
1708
|
-
'<
|
|
1783
|
+
'<div class="config-row"><label>Door policy</label><select id="m-door-policy">' + doorPolicyOpts + '</select></div>',
|
|
1784
|
+
'<p class="muted-note">Effective: ' + escHtml(data.workspaceDoorPolicy + ' / ' + data.workspaceLayout) + '</p>',
|
|
1785
|
+
'<p class="muted-note">Changing the policy rebuilds the office sandbox container with the new mounts on its next message; the container contents are preserved.</p>',
|
|
1786
|
+
'<button class="primary-action-btn" onclick="saveDoorPolicy(this)">Save door policy</button>',
|
|
1709
1787
|
'<div id="mount-save-result" class="inline-result" style="display:none"></div>',
|
|
1710
1788
|
'</div>',
|
|
1711
1789
|
'<div class="config-block">',
|
|
@@ -1778,6 +1856,23 @@ function renderAdminPage(token) {
|
|
|
1778
1856
|
}
|
|
1779
1857
|
}
|
|
1780
1858
|
|
|
1859
|
+
async function saveDoorPolicy(btn) {
|
|
1860
|
+
const doorPolicy = document.getElementById('m-door-policy').value;
|
|
1861
|
+
const result = document.getElementById('mount-save-result');
|
|
1862
|
+
btn.disabled = true; btn.textContent = 'Saving…'; result.style.display = 'none';
|
|
1863
|
+
try {
|
|
1864
|
+
await apiPost('/admin/api/conversations/sandbox', {
|
|
1865
|
+
...scopeBody(), doorPolicy,
|
|
1866
|
+
});
|
|
1867
|
+
result.style.display = 'block'; result.className = 'inline-result ok'; result.textContent = 'Saved ✓';
|
|
1868
|
+
loadSettings();
|
|
1869
|
+
} catch (err) {
|
|
1870
|
+
result.style.display = 'block'; result.className = 'inline-result err'; result.textContent = err.message;
|
|
1871
|
+
} finally {
|
|
1872
|
+
btn.disabled = false; btn.textContent = 'Save door policy';
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1781
1876
|
// ── Workspace ────────────────────────────────────────────────────────────────
|
|
1782
1877
|
|
|
1783
1878
|
async function loadWorkspace() {
|
|
@@ -2307,6 +2402,17 @@ function renderAdminPage(token) {
|
|
|
2307
2402
|
const replyModeOpts = replyModes.map((m) =>
|
|
2308
2403
|
'<option value="' + m + '"' + (((data.slack && data.slack.replyMode) || 'top-level') === m ? ' selected' : '') + '>' + m + '</option>'
|
|
2309
2404
|
).join('');
|
|
2405
|
+
const gDoorKey = data.workspaceDoorPolicy === 'isolated'
|
|
2406
|
+
? 'isolated'
|
|
2407
|
+
: (data.workspaceLayout === 'full' ? 'trusted-full' : 'trusted-shared-support');
|
|
2408
|
+
const gDoorPolicyChoices = [
|
|
2409
|
+
['isolated', 'isolated — own office only (built-in default)'],
|
|
2410
|
+
['trusted-shared-support', 'trusted / shared-support — office + shared memory, skills, events'],
|
|
2411
|
+
['trusted-full', 'trusted / full — entire workspace'],
|
|
2412
|
+
];
|
|
2413
|
+
const gDoorPolicyOpts = gDoorPolicyChoices.map(([value, label]) =>
|
|
2414
|
+
'<option value="' + value + '"' + (gDoorKey === value ? ' selected' : '') + '>' + escHtml(label) + '</option>'
|
|
2415
|
+
).join('');
|
|
2310
2416
|
return [
|
|
2311
2417
|
'<div class="config-grid">',
|
|
2312
2418
|
'<div class="config-block">',
|
|
@@ -2322,10 +2428,16 @@ function renderAdminPage(token) {
|
|
|
2322
2428
|
'<div class="config-row"><label>Memory</label><input id="g-mem" placeholder="1g" value="' + escAttr(data.sandboxMemory || '') + '"></div>',
|
|
2323
2429
|
'<div class="config-row"><label>Boost CPUs</label><input id="g-bcpus" placeholder="2" value="' + escAttr(data.sandboxBoostCpus || '') + '"></div>',
|
|
2324
2430
|
'<div class="config-row"><label>Boost Mem</label><input id="g-bmem" placeholder="4g" value="' + escAttr(data.sandboxBoostMemory || '') + '"></div>',
|
|
2325
|
-
'<p class="muted-note">Office door policy is configured in host settings. Effective default: ' + escHtml(data.workspaceDoorPolicy + ' / ' + data.workspaceLayout) + '</p>',
|
|
2326
2431
|
'<button class="primary-action-btn" onclick="saveGlobalSandbox(this)">Save sandbox</button>',
|
|
2327
2432
|
'<div id="g-sandbox-result" class="inline-result" style="display:none"></div>',
|
|
2328
2433
|
'</div>',
|
|
2434
|
+
'<div class="config-block">',
|
|
2435
|
+
'<h3 class="card-subtitle">Office door policy</h3>',
|
|
2436
|
+
'<div class="config-row"><label>Default</label><select id="g-door-policy">' + gDoorPolicyOpts + '</select></div>',
|
|
2437
|
+
'<p class="muted-note">Applies to offices without their own policy. Affected offices rebuild their sandbox container with the new mounts on the next message; container contents are preserved.</p>',
|
|
2438
|
+
'<button class="primary-action-btn" onclick="saveGlobalWorkspace(this)">Save door policy</button>',
|
|
2439
|
+
'<div id="g-workspace-result" class="inline-result" style="display:none"></div>',
|
|
2440
|
+
'</div>',
|
|
2329
2441
|
'<div class="config-block">',
|
|
2330
2442
|
'<h3 class="card-subtitle">Slack</h3>',
|
|
2331
2443
|
'<div class="config-row"><label>Reply mode</label><select id="g-slack-reply-mode">' + replyModeOpts + '</select></div>',
|
|
@@ -2374,6 +2486,23 @@ function renderAdminPage(token) {
|
|
|
2374
2486
|
}
|
|
2375
2487
|
}
|
|
2376
2488
|
|
|
2489
|
+
async function saveGlobalWorkspace(btn) {
|
|
2490
|
+
const doorPolicy = document.getElementById('g-door-policy').value;
|
|
2491
|
+
const result = document.getElementById('g-workspace-result');
|
|
2492
|
+
btn.disabled = true; btn.textContent = 'Saving…'; result.style.display = 'none';
|
|
2493
|
+
try {
|
|
2494
|
+
const data = await apiPost('/admin/api/settings/workspace', { doorPolicy });
|
|
2495
|
+
result.style.display = 'block'; result.className = 'inline-result ok';
|
|
2496
|
+
result.textContent = data.staleConversations > 0
|
|
2497
|
+
? 'Saved ✓ (' + data.staleConversations + ' busy conversation(s) refresh after their current run)'
|
|
2498
|
+
: 'Saved ✓';
|
|
2499
|
+
} catch (err) {
|
|
2500
|
+
result.style.display = 'block'; result.className = 'inline-result err'; result.textContent = err.message;
|
|
2501
|
+
} finally {
|
|
2502
|
+
btn.disabled = false; btn.textContent = 'Save door policy';
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2377
2506
|
async function saveGlobalSlack(btn) {
|
|
2378
2507
|
const replyMode = document.getElementById('g-slack-reply-mode').value;
|
|
2379
2508
|
const result = document.getElementById('g-slack-result');
|