{"version":3,"file":"background-work.d.ts","sourceRoot":"","sources":["../../../src/api/background-work.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gCAAgC,IAAI,CAAC;AAClD,eAAO,MAAM,4BAA4B,oCAAoC,CAAC;AAS9E,MAAM,WAAW,kBAAkB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,8BAA8B;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,IAAI,SAAS,kBAAkB,EAAE,CAAC;IAChD,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,SAAS,CAAC,CAAC,OAAO,EAAE,8BAA8B,GAAG,IAAI,CAAC;CAC1D;AAED,MAAM,WAAW,4BAA6B,SAAQ,kBAAkB;IACvE,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACtC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,KAAK,EAAE,SAAS,4BAA4B,EAAE,CAAC;CAC/C;AA0FD;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,sBAAsB,GAAG,MAAM,IAAI,CAU3F;AAED,wBAAgB,2BAA2B,IAAI,SAAS,sBAAsB,EAAE,CAY/E;AAED,iFAAiF;AACjF,wBAAgB,8BAA8B,IAAI,SAAS,MAAM,EAAE,CAMlE;AAED,iFAAiF;AACjF,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,SAAa,GAAG,sBAAsB,CA+CpG","sourcesContent":["export const BACKGROUND_WORK_PROTOCOL_VERSION = 1;\nexport const BACKGROUND_WORK_REGISTRY_KEY = \"pi-subagents.background-work.v1\";\n\nconst MAX_PROVIDER_NAME_LENGTH = 128;\nconst MAX_PROVIDERS = 100;\nconst MAX_ITEM_ID_LENGTH = 256;\nconst MAX_SESSION_ID_LENGTH = 256;\nconst MAX_WAKE_CHANNEL_LENGTH = 256;\nconst MAX_ITEMS_PER_PROVIDER = 10_000;\n\nexport interface BackgroundWorkItem {\n\tid: string;\n\tsessionId: string;\n}\n\nexport interface BackgroundWorkReconcileContext {\n\tsessionId: string;\n\tnowMs: number;\n}\n\nexport interface BackgroundWorkProvider {\n\tname: string;\n\tlistActiveWork(): readonly BackgroundWorkItem[];\n\twakeChannels?: readonly string[];\n\treconcile?(context: BackgroundWorkReconcileContext): void;\n}\n\nexport interface RegisteredBackgroundWorkItem extends BackgroundWorkItem {\n\tprovider: string;\n}\n\nexport interface BackgroundWorkSnapshot {\n\tproviders: readonly string[];\n\titems: readonly RegisteredBackgroundWorkItem[];\n}\n\ninterface BackgroundWorkRegistry {\n\tversion: typeof BACKGROUND_WORK_PROTOCOL_VERSION;\n\tproviders: Map<string, BackgroundWorkProvider>;\n}\n\nfunction registry(): BackgroundWorkRegistry {\n\tconst key = Symbol.for(BACKGROUND_WORK_REGISTRY_KEY);\n\tconst globalObject = globalThis as Record<PropertyKey, unknown>;\n\tconst existing = globalObject[key];\n\tif (existing === undefined) {\n\t\tconst created: BackgroundWorkRegistry = {\n\t\t\tversion: BACKGROUND_WORK_PROTOCOL_VERSION,\n\t\t\tproviders: new Map(),\n\t\t};\n\t\tglobalObject[key] = created;\n\t\treturn created;\n\t}\n\tif (!existing || typeof existing !== \"object\" || Array.isArray(existing)) {\n\t\tthrow new Error(`Malformed background-work registry at Symbol.for(\"${BACKGROUND_WORK_REGISTRY_KEY}\").`);\n\t}\n\tconst candidate = existing as Partial<BackgroundWorkRegistry>;\n\tif (candidate.version !== BACKGROUND_WORK_PROTOCOL_VERSION || !(candidate.providers instanceof Map)) {\n\t\tthrow new Error(`Unsupported background-work registry at Symbol.for(\"${BACKGROUND_WORK_REGISTRY_KEY}\").`);\n\t}\n\treturn candidate as BackgroundWorkRegistry;\n}\n\nfunction validateString(value: unknown, field: string, maxLength: number): string {\n\tif (typeof value !== \"string\" || value.length === 0 || value.trim() !== value) {\n\t\tthrow new Error(`${field} must be a non-empty string without leading or trailing whitespace.`);\n\t}\n\tif (value.length > maxLength) throw new Error(`${field} must be at most ${maxLength} characters.`);\n\tif (value.includes(\"\\0\")) throw new Error(`${field} must not contain NUL characters.`);\n\treturn value;\n}\n\nfunction validateProvider(value: unknown): BackgroundWorkProvider {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tthrow new Error(\"Background-work provider must be an object.\");\n\t}\n\tconst provider = value as Record<string, unknown>;\n\tconst unknownFields = Object.keys(provider).filter(\n\t\t(key) => ![\"name\", \"listActiveWork\", \"wakeChannels\", \"reconcile\"].includes(key),\n\t);\n\tif (unknownFields.length > 0)\n\t\tthrow new Error(`Background-work provider has unknown fields: ${unknownFields.join(\", \")}.`);\n\tconst name = validateString(provider.name, \"Background-work provider name\", MAX_PROVIDER_NAME_LENGTH);\n\tif (typeof provider.listActiveWork !== \"function\") {\n\t\tthrow new Error(`Background-work provider '${name}' must expose listActiveWork().`);\n\t}\n\tif (provider.reconcile !== undefined && typeof provider.reconcile !== \"function\") {\n\t\tthrow new Error(`Background-work provider '${name}' reconcile must be a function when provided.`);\n\t}\n\tif (provider.wakeChannels !== undefined) {\n\t\tif (!Array.isArray(provider.wakeChannels)) {\n\t\t\tthrow new Error(`Background-work provider '${name}' wakeChannels must be an array when provided.`);\n\t\t}\n\t\tconst channels = provider.wakeChannels.map((channel, index) =>\n\t\t\tvalidateString(channel, `Background-work provider '${name}' wakeChannels[${index}]`, MAX_WAKE_CHANNEL_LENGTH),\n\t\t);\n\t\tif (new Set(channels).size !== channels.length) {\n\t\t\tthrow new Error(`Background-work provider '${name}' wakeChannels must not contain duplicates.`);\n\t\t}\n\t}\n\treturn value as BackgroundWorkProvider;\n}\n\nfunction validateItem(provider: string, value: unknown, index: number): BackgroundWorkItem {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tthrow new Error(`Background-work provider '${provider}' item ${index} must be an object.`);\n\t}\n\tconst item = value as Record<string, unknown>;\n\tconst unknownFields = Object.keys(item).filter((key) => key !== \"id\" && key !== \"sessionId\");\n\tif (unknownFields.length > 0) {\n\t\tthrow new Error(\n\t\t\t`Background-work provider '${provider}' item ${index} has unknown fields: ${unknownFields.join(\", \")}.`,\n\t\t);\n\t}\n\treturn {\n\t\tid: validateString(item.id, `Background-work provider '${provider}' item ${index} id`, MAX_ITEM_ID_LENGTH),\n\t\tsessionId: validateString(\n\t\t\titem.sessionId,\n\t\t\t`Background-work provider '${provider}' item ${index} sessionId`,\n\t\t\tMAX_SESSION_ID_LENGTH,\n\t\t),\n\t};\n}\n\n/**\n * Register or replace one process-local background-work provider. The returned\n * disposer only removes this exact registration, so an old extension reload\n * cannot unregister its replacement.\n */\nexport function registerBackgroundWorkProvider(provider: BackgroundWorkProvider): () => void {\n\tconst validated = validateProvider(provider);\n\tconst current = registry();\n\tif (!current.providers.has(validated.name) && current.providers.size >= MAX_PROVIDERS) {\n\t\tthrow new Error(`Background-work registry supports at most ${MAX_PROVIDERS} providers.`);\n\t}\n\tcurrent.providers.set(validated.name, validated);\n\treturn () => {\n\t\tif (current.providers.get(validated.name) === validated) current.providers.delete(validated.name);\n\t};\n}\n\nexport function listBackgroundWorkProviders(): readonly BackgroundWorkProvider[] {\n\tconst current = registry();\n\tif (current.providers.size > MAX_PROVIDERS)\n\t\tthrow new Error(`Background-work registry contains more than ${MAX_PROVIDERS} providers.`);\n\tconst providers: BackgroundWorkProvider[] = [];\n\tfor (const [key, value] of current.providers) {\n\t\tconst provider = validateProvider(value);\n\t\tif (key !== provider.name)\n\t\t\tthrow new Error(`Background-work registry key '${key}' does not match provider name '${provider.name}'.`);\n\t\tproviders.push(provider);\n\t}\n\treturn providers;\n}\n\n/** Read validated provider wake channels without reconciling or listing work. */\nexport function listBackgroundWorkWakeChannels(): readonly string[] {\n\tconst channels = new Set<string>();\n\tfor (const provider of listBackgroundWorkProviders()) {\n\t\tfor (const channel of provider.wakeChannels ?? []) channels.add(channel);\n\t}\n\treturn [...channels];\n}\n\n/** Reconcile and snapshot active provider work owned by one exact Pi session. */\nexport function snapshotBackgroundWork(sessionId: string, nowMs = Date.now()): BackgroundWorkSnapshot {\n\tvalidateString(sessionId, \"Background-work snapshot sessionId\", MAX_SESSION_ID_LENGTH);\n\tconst providers = listBackgroundWorkProviders();\n\tconst items: RegisteredBackgroundWorkItem[] = [];\n\tconst identities = new Set<string>();\n\tfor (const provider of providers) {\n\t\ttry {\n\t\t\tprovider.reconcile?.({ sessionId, nowMs });\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Background-work provider '${provider.name}' reconcile failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\t\tlet active: readonly BackgroundWorkItem[];\n\t\ttry {\n\t\t\tactive = provider.listActiveWork();\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Background-work provider '${provider.name}' listActiveWork failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\t\tif (!Array.isArray(active)) {\n\t\t\tthrow new Error(`Background-work provider '${provider.name}' listActiveWork() must return an array.`);\n\t\t}\n\t\tif (active.length > MAX_ITEMS_PER_PROVIDER) {\n\t\t\tthrow new Error(\n\t\t\t\t`Background-work provider '${provider.name}' returned ${active.length} items; maximum is ${MAX_ITEMS_PER_PROVIDER}.`,\n\t\t\t);\n\t\t}\n\t\tactive.forEach((value, index) => {\n\t\t\tconst item = validateItem(provider.name, value, index);\n\t\t\tconst identity = `${provider.name}\\0${item.sessionId}\\0${item.id}`;\n\t\t\tif (identities.has(identity)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Background-work provider '${provider.name}' returned duplicate item '${item.id}' for session '${item.sessionId}'.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tidentities.add(identity);\n\t\t\tif (item.sessionId === sessionId) items.push({ provider: provider.name, ...item });\n\t\t});\n\t}\n\treturn {\n\t\tproviders: providers.map((provider) => provider.name),\n\t\titems,\n\t};\n}\n"]}